diff options
author | Tomi Valkeinen <tomi.valkeinen@ti.com> | 2014-02-13 15:31:38 +0200 |
---|---|---|
committer | Tomi Valkeinen <tomi.valkeinen@ti.com> | 2014-04-17 08:10:19 +0300 |
commit | f7018c21350204c4cf628462f229d44d03545254 (patch) | |
tree | 408787177164cf51cc06f7aabdb04fcff8d2b6aa /drivers/video/fbdev | |
parent | c26ef3eb3c11274bad1b64498d0a134f85755250 (diff) | |
download | talos-obmc-linux-f7018c21350204c4cf628462f229d44d03545254.tar.gz talos-obmc-linux-f7018c21350204c4cf628462f229d44d03545254.zip |
video: move fbdev to drivers/video/fbdev
The drivers/video directory is a mess. It contains generic video related
files, directories for backlight, console, linux logo, lots of fbdev
device drivers, fbdev framework files.
Make some order into the chaos by creating drivers/video/fbdev
directory, and move all fbdev related files there.
No functionality is changed, although I guess it is possible that some
subtle Makefile build order related issue could be created by this
patch.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Geert Uytterhoeven <geert@linux-m68k.org>
Acked-by: Rob Clark <robdclark@gmail.com>
Acked-by: Jingoo Han <jg1.han@samsung.com>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Diffstat (limited to 'drivers/video/fbdev')
471 files changed, 302801 insertions, 0 deletions
diff --git a/drivers/video/fbdev/68328fb.c b/drivers/video/fbdev/68328fb.c new file mode 100644 index 000000000000..552258c8f99d --- /dev/null +++ b/drivers/video/fbdev/68328fb.c @@ -0,0 +1,503 @@ +/* + * linux/drivers/video/68328fb.c -- Low level implementation of the + * mc68x328 LCD frame buffer device + * + * Copyright (C) 2003 Georges Menie + * + * This driver assumes an already configured controller (e.g. from config.c) + * Keep the code clean of board specific initialization. + * + * This code has not been tested with colors, colormap management functions + * are minimal (no colormap data written to the 68328 registers...) + * + * initial version of this driver: + * Copyright (C) 1998,1999 Kenneth Albanowski <kjahds@kjahds.com>, + * The Silver Hammer Group, Ltd. + * + * this version is based on : + * + * linux/drivers/video/vfb.c -- Virtual frame buffer device + * + * Copyright (C) 2002 James Simmons + * + * Copyright (C) 1997 Geert Uytterhoeven + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <asm/uaccess.h> +#include <linux/fb.h> +#include <linux/init.h> + +#if defined(CONFIG_M68VZ328) +#include <asm/MC68VZ328.h> +#elif defined(CONFIG_M68EZ328) +#include <asm/MC68EZ328.h> +#elif defined(CONFIG_M68328) +#include <asm/MC68328.h> +#else +#error wrong architecture for the MC68x328 frame buffer device +#endif + +#if defined(CONFIG_FB_68328_INVERT) +#define MC68X328FB_MONO_VISUAL FB_VISUAL_MONO01 +#else +#define MC68X328FB_MONO_VISUAL FB_VISUAL_MONO10 +#endif + +static u_long videomemory; +static u_long videomemorysize; + +static struct fb_info fb_info; +static u32 mc68x328fb_pseudo_palette[16]; + +static struct fb_var_screeninfo mc68x328fb_default __initdata = { + .red = { 0, 8, 0 }, + .green = { 0, 8, 0 }, + .blue = { 0, 8, 0 }, + .activate = FB_ACTIVATE_TEST, + .height = -1, + .width = -1, + .pixclock = 20000, + .left_margin = 64, + .right_margin = 64, + .upper_margin = 32, + .lower_margin = 32, + .hsync_len = 64, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo mc68x328fb_fix __initdata = { + .id = "68328fb", + .type = FB_TYPE_PACKED_PIXELS, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 1, + .accel = FB_ACCEL_NONE, +}; + + /* + * Interface used by the world + */ +int mc68x328fb_init(void); +int mc68x328fb_setup(char *); + +static int mc68x328fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int mc68x328fb_set_par(struct fb_info *info); +static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int mc68x328fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma); + +static struct fb_ops mc68x328fb_ops = { + .fb_check_var = mc68x328fb_check_var, + .fb_set_par = mc68x328fb_set_par, + .fb_setcolreg = mc68x328fb_setcolreg, + .fb_pan_display = mc68x328fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = mc68x328fb_mmap, +}; + + /* + * Internal routines + */ + +static u_long get_line_length(int xres_virtual, int bpp) +{ + u_long length; + + length = xres_virtual * bpp; + length = (length + 31) & ~31; + length >>= 3; + return (length); +} + + /* + * Setting the video mode has been split into two parts. + * First part, xxxfb_check_var, must not write anything + * to hardware, it should only verify and adjust var. + * This means it doesn't alter par but it does use hardware + * data from it to check this var. + */ + +static int mc68x328fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + u_long line_length; + + /* + * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal! + * as FB_VMODE_SMOOTH_XPAN is only used internally + */ + + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = info->var.xoffset; + var->yoffset = info->var.yoffset; + } + + /* + * Some very basic checks + */ + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + if (var->bits_per_pixel <= 1) + var->bits_per_pixel = 1; + else 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 <= 24) + var->bits_per_pixel = 24; + else if (var->bits_per_pixel <= 32) + var->bits_per_pixel = 32; + else + return -EINVAL; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + /* + * Memory limit + */ + line_length = + get_line_length(var->xres_virtual, var->bits_per_pixel); + if (line_length * var->yres_virtual > videomemorysize) + return -ENOMEM; + + /* + * Now that we checked it we alter var. The reason being is that the video + * mode passed in might not work but slight changes to it might make it + * work. This way we let the user know what is acceptable. + */ + switch (var->bits_per_pixel) { + case 1: + var->red.offset = 0; + var->red.length = 1; + var->green.offset = 0; + var->green.length = 1; + var->blue.offset = 0; + var->blue.length = 1; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGBA 5551 */ + if (var->transp.length) { + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 10; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + } else { /* RGB 565 */ + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 11; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + break; + case 24: /* RGB 888 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: /* RGBA 8888 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + return 0; +} + +/* This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + */ +static int mc68x328fb_set_par(struct fb_info *info) +{ + info->fix.line_length = get_line_length(info->var.xres_virtual, + info->var.bits_per_pixel); + return 0; +} + + /* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ + +static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + /* + * Program hardware... do anything you want with transp + */ + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = + (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* Directcolor: + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * {hardwarespecific} contains width of RAMDAC + * cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset) + * RAMDAC[X] is programmed to (red, green, blue) + * + * Pseudocolor: + * uses offset = 0 && length = RAMDAC register width. + * var->{color}.offset is 0 + * var->{color}.length contains width of DAC + * cmap is not used + * RAMDAC[X] is programmed to (red, green, blue) + * Truecolor: + * does not use DAC. Usually 3 are present. + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * cmap is programmed to (red << red.offset) | (green << green.offset) | + * (blue << blue.offset) | (transp << transp.offset) + * RAMDAC does not exist + */ +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + + if (regno >= 16) + return 1; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + switch (info->var.bits_per_pixel) { + case 8: + break; + case 16: + ((u32 *) (info->pseudo_palette))[regno] = v; + break; + case 24: + case 32: + ((u32 *) (info->pseudo_palette))[regno] = v; + break; + } + return 0; + } + return 0; +} + + /* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ + +static int mc68x328fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->vmode & FB_VMODE_YWRAP) { + if (var->yoffset < 0 + || var->yoffset >= info->var.yres_virtual + || var->xoffset) + return -EINVAL; + } else { + if (var->xoffset + info->var.xres > info->var.xres_virtual || + var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + } + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + return 0; +} + + /* + * Most drivers don't need their own mmap function + */ + +static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ +#ifndef MMU + /* this is uClinux (no MMU) specific code */ + + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_start = videomemory; + + return 0; +#else + return -EINVAL; +#endif +} + +int __init mc68x328fb_setup(char *options) +{ +#if 0 + char *this_opt; +#endif + + if (!options || !*options) + return 1; +#if 0 + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + if (!strncmp(this_opt, "disable", 7)) + mc68x328fb_enable = 0; + } +#endif + return 1; +} + + /* + * Initialisation + */ + +int __init mc68x328fb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("68328fb", &option)) + return -ENODEV; + mc68x328fb_setup(option); +#endif + /* + * initialize the default mode from the LCD controller registers + */ + mc68x328fb_default.xres = LXMAX; + mc68x328fb_default.yres = LYMAX+1; + mc68x328fb_default.xres_virtual = mc68x328fb_default.xres; + mc68x328fb_default.yres_virtual = mc68x328fb_default.yres; + mc68x328fb_default.bits_per_pixel = 1 + (LPICF & 0x01); + videomemory = LSSA; + videomemorysize = (mc68x328fb_default.xres_virtual+7) / 8 * + mc68x328fb_default.yres_virtual * mc68x328fb_default.bits_per_pixel; + + fb_info.screen_base = (void *)videomemory; + fb_info.fbops = &mc68x328fb_ops; + fb_info.var = mc68x328fb_default; + fb_info.fix = mc68x328fb_fix; + fb_info.fix.smem_start = videomemory; + fb_info.fix.smem_len = videomemorysize; + fb_info.fix.line_length = + get_line_length(mc68x328fb_default.xres_virtual, mc68x328fb_default.bits_per_pixel); + fb_info.fix.visual = (mc68x328fb_default.bits_per_pixel) == 1 ? + MC68X328FB_MONO_VISUAL : FB_VISUAL_PSEUDOCOLOR; + if (fb_info.var.bits_per_pixel == 1) { + fb_info.var.red.length = fb_info.var.green.length = fb_info.var.blue.length = 1; + fb_info.var.red.offset = fb_info.var.green.offset = fb_info.var.blue.offset = 0; + } + fb_info.pseudo_palette = &mc68x328fb_pseudo_palette; + fb_info.flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + if (fb_alloc_cmap(&fb_info.cmap, 256, 0)) + return -ENOMEM; + + if (register_framebuffer(&fb_info) < 0) { + fb_dealloc_cmap(&fb_info.cmap); + return -EINVAL; + } + + fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id); + fb_info(&fb_info, "%dx%dx%d at 0x%08lx\n", + mc68x328fb_default.xres_virtual, + mc68x328fb_default.yres_virtual, + 1 << mc68x328fb_default.bits_per_pixel, videomemory); + + return 0; +} + +module_init(mc68x328fb_init); + +#ifdef MODULE + +static void __exit mc68x328fb_cleanup(void) +{ + unregister_framebuffer(&fb_info); + fb_dealloc_cmap(&fb_info.cmap); +} + +module_exit(mc68x328fb_cleanup); + +MODULE_LICENSE("GPL"); +#endif /* MODULE */ diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig new file mode 100644 index 000000000000..e1f47272fdea --- /dev/null +++ b/drivers/video/fbdev/Kconfig @@ -0,0 +1,2474 @@ +# +# fbdev configuration +# + +menuconfig FB + tristate "Support for frame buffer devices" + ---help--- + The frame buffer device provides an abstraction for the graphics + hardware. It represents the frame buffer of some video hardware and + allows application software to access the graphics hardware through + a well-defined interface, so the software doesn't need to know + anything about the low-level (hardware register) stuff. + + Frame buffer devices work identically across the different + architectures supported by Linux and make the implementation of + application programs easier and more portable; at this point, an X + server exists which uses the frame buffer device exclusively. + On several non-X86 architectures, the frame buffer device is the + only way to use the graphics hardware. + + The device is accessed through special device nodes, usually located + in the /dev directory, i.e. /dev/fb*. + + You need an utility program called fbset to make full use of frame + buffer devices. Please read <file:Documentation/fb/framebuffer.txt> + and the Framebuffer-HOWTO at + <http://www.munted.org.uk/programming/Framebuffer-HOWTO-1.3.html> for more + information. + + Say Y here and to the driver for your graphics board below if you + are compiling a kernel for a non-x86 architecture. + + If you are compiling for the x86 architecture, you can say Y if you + want to play with it, but it is not essential. Please note that + running graphical applications that directly touch the hardware + (e.g. an accelerated X server) and that are not frame buffer + device-aware may cause unexpected results. If unsure, say N. + +config FIRMWARE_EDID + bool "Enable firmware EDID" + depends on FB + default n + ---help--- + This enables access to the EDID transferred from the firmware. + On the i386, this is from the Video BIOS. Enable this if DDC/I2C + transfers do not work for your driver and if you are using + nvidiafb, i810fb or savagefb. + + In general, choosing Y for this option is safe. If you + experience extremely long delays while booting before you get + something on your display, try setting this to N. Matrox cards in + combination with certain motherboards and monitors are known to + suffer from this problem. + +config FB_DDC + tristate + depends on FB + select I2C_ALGOBIT + select I2C + default n + +config FB_BOOT_VESA_SUPPORT + bool + depends on FB + default n + ---help--- + If true, at least one selected framebuffer driver can take advantage + of VESA video modes set at an early boot stage via the vga= parameter. + +config FB_CFB_FILLRECT + tristate + depends on FB + default n + ---help--- + Include the cfb_fillrect function for generic software rectangle + filling. This is used by drivers that don't provide their own + (accelerated) version. + +config FB_CFB_COPYAREA + tristate + depends on FB + default n + ---help--- + Include the cfb_copyarea function for generic software area copying. + This is used by drivers that don't provide their own (accelerated) + version. + +config FB_CFB_IMAGEBLIT + tristate + depends on FB + default n + ---help--- + Include the cfb_imageblit function for generic software image + blitting. This is used by drivers that don't provide their own + (accelerated) version. + +config FB_CFB_REV_PIXELS_IN_BYTE + bool + depends on FB + default n + ---help--- + Allow generic frame-buffer functions to work on displays with 1, 2 + and 4 bits per pixel depths which has opposite order of pixels in + byte order to bytes in long order. + +config FB_SYS_FILLRECT + tristate + depends on FB + default n + ---help--- + Include the sys_fillrect function for generic software rectangle + filling. This is used by drivers that don't provide their own + (accelerated) version and the framebuffer is in system RAM. + +config FB_SYS_COPYAREA + tristate + depends on FB + default n + ---help--- + Include the sys_copyarea function for generic software area copying. + This is used by drivers that don't provide their own (accelerated) + version and the framebuffer is in system RAM. + +config FB_SYS_IMAGEBLIT + tristate + depends on FB + default n + ---help--- + Include the sys_imageblit function for generic software image + blitting. This is used by drivers that don't provide their own + (accelerated) version and the framebuffer is in system RAM. + +menuconfig FB_FOREIGN_ENDIAN + bool "Framebuffer foreign endianness support" + depends on FB + ---help--- + This menu will let you enable support for the framebuffers with + non-native endianness (e.g. Little-Endian framebuffer on a + Big-Endian machine). Most probably you don't have such hardware, + so it's safe to say "n" here. + +choice + prompt "Choice endianness support" + depends on FB_FOREIGN_ENDIAN + +config FB_BOTH_ENDIAN + bool "Support for Big- and Little-Endian framebuffers" + +config FB_BIG_ENDIAN + bool "Support for Big-Endian framebuffers only" + +config FB_LITTLE_ENDIAN + bool "Support for Little-Endian framebuffers only" + +endchoice + +config FB_SYS_FOPS + tristate + depends on FB + default n + +config FB_DEFERRED_IO + bool + depends on FB + +config FB_HECUBA + tristate + depends on FB + depends on FB_DEFERRED_IO + +config FB_SVGALIB + tristate + depends on FB + default n + ---help--- + Common utility functions useful to fbdev drivers of VGA-based + cards. + +config FB_MACMODES + tristate + depends on FB + default n + +config FB_BACKLIGHT + bool + depends on FB + select BACKLIGHT_LCD_SUPPORT + select BACKLIGHT_CLASS_DEVICE + default n + +config FB_MODE_HELPERS + bool "Enable Video Mode Handling Helpers" + depends on FB + default n + ---help--- + This enables functions for handling video modes using the + Generalized Timing Formula and the EDID parser. A few drivers rely + on this feature such as the radeonfb, rivafb, and the i810fb. If + your driver does not take advantage of this feature, choosing Y will + just increase the kernel size by about 5K. + +config FB_TILEBLITTING + bool "Enable Tile Blitting Support" + depends on FB + default n + ---help--- + This enables tile blitting. Tile blitting is a drawing technique + where the screen is divided into rectangular sections (tiles), whereas + the standard blitting divides the screen into pixels. Because the + default drawing element is a tile, drawing functions will be passed + parameters in terms of number of tiles instead of number of pixels. + For example, to draw a single character, instead of using bitmaps, + an index to an array of bitmaps will be used. To clear or move a + rectangular section of a screen, the rectangle will be described in + terms of number of tiles in the x- and y-axis. + + This is particularly important to one driver, matroxfb. If + unsure, say N. + +comment "Frame buffer hardware drivers" + depends on FB + +config FB_GRVGA + tristate "Aeroflex Gaisler framebuffer support" + depends on FB && SPARC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + This enables support for the SVGACTRL framebuffer in the GRLIB IP library from Aeroflex Gaisler. + +config FB_CIRRUS + tristate "Cirrus Logic support" + depends on FB && (ZORRO || PCI) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + This enables support for Cirrus Logic GD542x/543x based boards on + Amiga: SD64, Piccolo, Picasso II/II+, Picasso IV, or EGS Spectrum. + + If you have a PCI-based system, this enables support for these + chips: GD-543x, GD-544x, GD-5480. + + Please read the file <file:Documentation/fb/cirrusfb.txt>. + + Say N unless you have such a graphics board or plan to get one + before you next recompile the kernel. + +config FB_PM2 + tristate "Permedia2 support" + depends on FB && ((AMIGA && BROKEN) || PCI) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for cards based on + the 3D Labs Permedia, Permedia 2 and Permedia 2V chips. + The driver was tested on the following cards: + Diamond FireGL 1000 PRO AGP + ELSA Gloria Synergy PCI + Appian Jeronimo PRO (both heads) PCI + 3DLabs Oxygen ACX aka EONtronics Picasso P2 PCI + Techsource Raptor GFX-8P (aka Sun PGX-32) on SPARC + ASK Graphic Blaster Exxtreme AGP + + To compile this driver as a module, choose M here: the + module will be called pm2fb. + +config FB_PM2_FIFO_DISCONNECT + bool "enable FIFO disconnect feature" + depends on FB_PM2 && PCI + help + Support the Permedia2 FIFO disconnect feature. + +config FB_ARMCLCD + tristate "ARM PrimeCell PL110 support" + depends on ARM || ARM64 || COMPILE_TEST + depends on FB && ARM_AMBA + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This framebuffer device driver is for the ARM PrimeCell PL110 + Colour LCD controller. ARM PrimeCells provide the building + blocks for System on a Chip devices. + + If you want to compile this as a module (=code which can be + inserted into and removed from the running kernel), say M + here and read <file:Documentation/kbuild/modules.txt>. The module + will be called amba-clcd. + +config FB_ACORN + bool "Acorn VIDC support" + depends on (FB = y) && ARM && ARCH_ACORN + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Acorn VIDC graphics + hardware found in Acorn RISC PCs and other ARM-based machines. If + unsure, say N. + +config FB_CLPS711X + bool "CLPS711X LCD support" + depends on (FB = y) && ARM && ARCH_CLPS711X + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Say Y to enable the Framebuffer driver for the CLPS7111 and + EP7212 processors. + +config FB_SA1100 + bool "SA-1100 LCD support" + depends on (FB = y) && ARM && ARCH_SA1100 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is a framebuffer device for the SA-1100 LCD Controller. + See <http://www.linux-fbdev.org/> for information on framebuffer + devices. + + If you plan to use the LCD display with your SA-1100 system, say + Y here. + +config FB_IMX + tristate "Freescale i.MX1/21/25/27 LCD support" + depends on FB && ARCH_MXC + select BACKLIGHT_LCD_SUPPORT + select LCD_CLASS_DEVICE + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + +config FB_CYBER2000 + tristate "CyberPro 2000/2010/5000 support" + depends on FB && PCI && (BROKEN || !SPARC64) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This enables support for the Integraphics CyberPro 20x0 and 5000 + VGA chips used in the Rebel.com Netwinder and other machines. + Say Y if you have a NetWinder or a graphics card containing this + device, otherwise say N. + +config FB_CYBER2000_DDC + bool "DDC for CyberPro support" + depends on FB_CYBER2000 + select FB_DDC + default y + help + Say Y here if you want DDC support for your CyberPro graphics + card. This is only I2C bus support, driver does not use EDID. + +config FB_CYBER2000_I2C + bool "CyberPro 2000/2010/5000 I2C support" + depends on FB_CYBER2000 && I2C && ARCH_NETWINDER + select I2C_ALGOBIT + help + Enable support for the I2C video decoder interface on the + Integraphics CyberPro 20x0 and 5000 VGA chips. This is used + on the Netwinder machines for the SAA7111 video capture. + +config FB_APOLLO + bool + depends on (FB = y) && APOLLO + default y + select FB_CFB_FILLRECT + select FB_CFB_IMAGEBLIT + +config FB_Q40 + bool + depends on (FB = y) && Q40 + default y + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + +config FB_AMIGA + tristate "Amiga native chipset support" + depends on FB && AMIGA + help + This is the frame buffer device driver for the builtin graphics + chipset found in Amigas. + + To compile this driver as a module, choose M here: the + module will be called amifb. + +config FB_AMIGA_OCS + bool "Amiga OCS chipset support" + depends on FB_AMIGA + help + This enables support for the original Agnus and Denise video chips, + found in the Amiga 1000 and most A500's and A2000's. If you intend + to run Linux on any of these systems, say Y; otherwise say N. + +config FB_AMIGA_ECS + bool "Amiga ECS chipset support" + depends on FB_AMIGA + help + This enables support for the Enhanced Chip Set, found in later + A500's, later A2000's, the A600, the A3000, the A3000T and CDTV. If + you intend to run Linux on any of these systems, say Y; otherwise + say N. + +config FB_AMIGA_AGA + bool "Amiga AGA chipset support" + depends on FB_AMIGA + help + This enables support for the Advanced Graphics Architecture (also + known as the AGA or AA) Chip Set, found in the A1200, A4000, A4000T + and CD32. If you intend to run Linux on any of these systems, say Y; + otherwise say N. + +config FB_FM2 + bool "Amiga FrameMaster II/Rainbow II support" + depends on (FB = y) && ZORRO + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Amiga FrameMaster + card from BSC (exhibited 1992 but not shipped as a CBM product). + +config FB_ARC + tristate "Arc Monochrome LCD board support" + depends on FB && X86 + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + help + This enables support for the Arc Monochrome LCD board. The board + is based on the KS-108 lcd controller and is typically a matrix + of 2*n chips. This driver was tested with a 128x64 panel. This + driver supports it for use with x86 SBCs through a 16 bit GPIO + interface (8 bit data, 8 bit control). If you anticipate using + this driver, say Y or M; otherwise say N. You must specify the + GPIO IO address to be used for setting control and data. + +config FB_ATARI + bool "Atari native chipset support" + depends on (FB = y) && ATARI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the builtin graphics + chipset found in Ataris. + +config FB_OF + bool "Open Firmware frame buffer device support" + depends on (FB = y) && (PPC64 || PPC_OF) && (!PPC_PSERIES || PCI) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MACMODES + help + Say Y if you want support with Open Firmware for your graphics + board. + +config FB_CONTROL + bool "Apple \"control\" display support" + depends on (FB = y) && PPC_PMAC && PPC32 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MACMODES + help + This driver supports a frame buffer for the graphics adapter in the + Power Macintosh 7300 and others. + +config FB_PLATINUM + bool "Apple \"platinum\" display support" + depends on (FB = y) && PPC_PMAC && PPC32 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MACMODES + help + This driver supports a frame buffer for the "platinum" graphics + adapter in some Power Macintoshes. + +config FB_VALKYRIE + bool "Apple \"valkyrie\" display support" + depends on (FB = y) && (MAC || (PPC_PMAC && PPC32)) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MACMODES + help + This driver supports a frame buffer for the "valkyrie" graphics + adapter in some Power Macintoshes. + +config FB_CT65550 + bool "Chips 65550 display support" + depends on (FB = y) && PPC32 && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Chips & Technologies + 65550 graphics chip in PowerBooks. + +config FB_ASILIANT + bool "Asiliant (Chips) 69000 display support" + depends on (FB = y) && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Asiliant 69030 chipset + +config FB_IMSTT + bool "IMS Twin Turbo display support" + depends on (FB = y) && PCI + select FB_CFB_IMAGEBLIT + select FB_MACMODES if PPC + help + The IMS Twin Turbo is a PCI-based frame buffer card bundled with + many Macintosh and compatible computers. + +config FB_VGA16 + tristate "VGA 16-color graphics support" + depends on FB && (X86 || PPC) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select VGASTATE + select FONT_8x16 if FRAMEBUFFER_CONSOLE + help + This is the frame buffer device driver for VGA 16 color graphic + cards. Say Y if you have such a card. + + To compile this driver as a module, choose M here: the + module will be called vga16fb. + +config FB_BF54X_LQ043 + tristate "SHARP LQ043 TFT LCD (BF548 EZKIT)" + depends on FB && (BF54x) && !BF542 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer device driver for a SHARP LQ043T1DG01 TFT LCD + +config FB_BFIN_T350MCQB + tristate "Varitronix COG-T350MCQB TFT LCD display (BF527 EZKIT)" + depends on FB && BLACKFIN + select BFIN_GPTIMERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer device driver for a Varitronix VL-PS-COG-T350MCQB-01 display TFT LCD + This display is a QVGA 320x240 24-bit RGB display interfaced by an 8-bit wide PPI + It uses PPI[0..7] PPI_FS1, PPI_FS2 and PPI_CLK. + +config FB_BFIN_LQ035Q1 + tristate "SHARP LQ035Q1DH02 TFT LCD" + depends on FB && BLACKFIN && SPI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select BFIN_GPTIMERS + help + This is the framebuffer device driver for a SHARP LQ035Q1DH02 TFT display found on + the Blackfin Landscape LCD EZ-Extender Card. + This display is a QVGA 320x240 18-bit RGB display interfaced by an 16-bit wide PPI + It uses PPI[0..15] PPI_FS1, PPI_FS2 and PPI_CLK. + + To compile this driver as a module, choose M here: the + module will be called bfin-lq035q1-fb. + +config FB_BF537_LQ035 + tristate "SHARP LQ035 TFT LCD (BF537 STAMP)" + depends on FB && (BF534 || BF536 || BF537) && I2C_BLACKFIN_TWI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select BFIN_GPTIMERS + help + This is the framebuffer device for a SHARP LQ035Q7DB03 TFT LCD + attached to a BF537. + + To compile this driver as a module, choose M here: the + module will be called bf537-lq035. + +config FB_BFIN_7393 + tristate "Blackfin ADV7393 Video encoder" + depends on FB && BLACKFIN + select I2C + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer device for a ADV7393 video encoder + attached to a Blackfin on the PPI port. + If your Blackfin board has a ADV7393 select Y. + + To compile this driver as a module, choose M here: the + module will be called bfin_adv7393fb. + +choice + prompt "Video mode support" + depends on FB_BFIN_7393 + default NTSC + +config NTSC + bool 'NTSC 720x480' + +config PAL + bool 'PAL 720x576' + +config NTSC_640x480 + bool 'NTSC 640x480 (Experimental)' + +config PAL_640x480 + bool 'PAL 640x480 (Experimental)' + +config NTSC_YCBCR + bool 'NTSC 720x480 YCbCR input' + +config PAL_YCBCR + bool 'PAL 720x576 YCbCR input' + +endchoice + +choice + prompt "Size of ADV7393 frame buffer memory Single/Double Size" + depends on (FB_BFIN_7393) + default ADV7393_1XMEM + +config ADV7393_1XMEM + bool 'Single' + +config ADV7393_2XMEM + bool 'Double' +endchoice + +config FB_STI + tristate "HP STI frame buffer device support" + depends on FB && PARISC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select STI_CONSOLE + select VT + default y + ---help--- + STI refers to the HP "Standard Text Interface" which is a set of + BIOS routines contained in a ROM chip in HP PA-RISC based machines. + Enabling this option will implement the linux framebuffer device + using calls to the STI BIOS routines for initialisation. + + If you enable this option, you will get a planar framebuffer device + /dev/fb which will work on the most common HP graphic cards of the + NGLE family, including the artist chips (in the 7xx and Bxxx series), + HCRX, HCRX24, CRX, CRX24 and VisEG series. + + It is safe to enable this option, so you should probably say "Y". + +config FB_MAC + bool "Generic Macintosh display support" + depends on (FB = y) && MAC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MACMODES + +config FB_HP300 + bool + depends on (FB = y) && DIO + select FB_CFB_IMAGEBLIT + default y + +config FB_TGA + tristate "TGA/SFB+ framebuffer support" + depends on FB && (ALPHA || TC) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select BITREVERSE + ---help--- + This is the frame buffer device driver for generic TGA and SFB+ + graphic cards. These include DEC ZLXp-E1, -E2 and -E3 PCI cards, + also known as PBXGA-A, -B and -C, and DEC ZLX-E1, -E2 and -E3 + TURBOchannel cards, also known as PMAGD-A, -B and -C. + + Due to hardware limitations ZLX-E2 and E3 cards are not supported + for DECstation 5000/200 systems. Additionally due to firmware + limitations these cards may cause troubles with booting DECstation + 5000/240 and /260 systems, but are fully supported under Linux if + you manage to get it going. ;-) + + Say Y if you have one of those. + +config FB_UVESA + tristate "Userspace VESA VGA graphics support" + depends on FB && CONNECTOR + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + help + This is the frame buffer driver for generic VBE 2.0 compliant + graphic cards. It can also take advantage of VBE 3.0 features, + such as refresh rate adjustment. + + This driver generally provides more features than vesafb but + requires a userspace helper application called 'v86d'. See + <file:Documentation/fb/uvesafb.txt> for more information. + + If unsure, say N. + +config FB_VESA + bool "VESA VGA graphics support" + depends on (FB = y) && X86 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_BOOT_VESA_SUPPORT + help + This is the frame buffer device driver for generic VESA 2.0 + compliant graphic cards. The older VESA 1.2 cards are not supported. + You will get a boot time penguin logo at no additional cost. Please + read <file:Documentation/fb/vesafb.txt>. If unsure, say Y. + +config FB_EFI + bool "EFI-based Framebuffer Support" + depends on (FB = y) && X86 && EFI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the EFI frame buffer device driver. If the firmware on + your platform is EFI 1.10 or UEFI 2.0, select Y to add support for + using the EFI framebuffer as your console. + +config FB_N411 + tristate "N411 Apollo/Hecuba devkit support" + depends on FB && X86 && MMU + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + select FB_HECUBA + help + This enables support for the Apollo display controller in its + Hecuba form using the n411 devkit. + +config FB_HGA + tristate "Hercules mono graphics support" + depends on FB && X86 + help + Say Y here if you have a Hercules mono graphics card. + + To compile this driver as a module, choose M here: the + module will be called hgafb. + + As this card technology is at least 25 years old, + most people will answer N here. + +config FB_GBE + bool "SGI Graphics Backend frame buffer support" + depends on (FB = y) && SGI_IP32 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for SGI Graphics Backend. + This chip is used in SGI O2 and Visual Workstation 320/540. + +config FB_GBE_MEM + int "Video memory size in MB" + depends on FB_GBE + default 4 + help + This is the amount of memory reserved for the framebuffer, + which can be any value between 1MB and 8MB. + +config FB_SBUS + bool "SBUS and UPA framebuffers" + depends on (FB = y) && SPARC + help + Say Y if you want support for SBUS or UPA based frame buffer device. + +config FB_BW2 + bool "BWtwo support" + depends on (FB = y) && (SPARC && FB_SBUS) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the BWtwo frame buffer. + +config FB_CG3 + bool "CGthree support" + depends on (FB = y) && (SPARC && FB_SBUS) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the CGthree frame buffer. + +config FB_CG6 + bool "CGsix (GX,TurboGX) support" + depends on (FB = y) && (SPARC && FB_SBUS) + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the CGsix (GX, TurboGX) + frame buffer. + +config FB_FFB + bool "Creator/Creator3D/Elite3D support" + depends on FB_SBUS && SPARC64 + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Creator, Creator3D, + and Elite3D graphics boards. + +config FB_TCX + bool "TCX (SS4/SS5 only) support" + depends on FB_SBUS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the TCX 24/8bit frame + buffer. + +config FB_CG14 + bool "CGfourteen (SX) support" + depends on FB_SBUS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the CGfourteen frame + buffer on Desktop SPARCsystems with the SX graphics option. + +config FB_P9100 + bool "P9100 (Sparcbook 3 only) support" + depends on FB_SBUS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the P9100 card + supported on Sparcbook 3 machines. + +config FB_LEO + bool "Leo (ZX) support" + depends on FB_SBUS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the SBUS-based Sun ZX + (leo) frame buffer cards. + +config FB_IGA + bool "IGA 168x display support" + depends on (FB = y) && SPARC32 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer device for the INTERGRAPHICS 1680 and + successor frame buffer cards. + +config FB_XVR500 + bool "Sun XVR-500 3DLABS Wildcat support" + depends on (FB = y) && PCI && SPARC64 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer device for the Sun XVR-500 and similar + graphics cards based upon the 3DLABS Wildcat chipset. The driver + only works on sparc64 systems where the system firmware has + mostly initialized the card already. It is treated as a + completely dumb framebuffer device. + +config FB_XVR2500 + bool "Sun XVR-2500 3DLABS Wildcat support" + depends on (FB = y) && PCI && SPARC64 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer device for the Sun XVR-2500 and similar + graphics cards based upon the 3DLABS Wildcat chipset. The driver + only works on sparc64 systems where the system firmware has + mostly initialized the card already. It is treated as a + completely dumb framebuffer device. + +config FB_XVR1000 + bool "Sun XVR-1000 support" + depends on (FB = y) && SPARC64 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer device for the Sun XVR-1000 and similar + graphics cards. The driver only works on sparc64 systems where + the system firmware has mostly initialized the card already. It + is treated as a completely dumb framebuffer device. + +config FB_PVR2 + tristate "NEC PowerVR 2 display support" + depends on FB && SH_DREAMCAST + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Say Y here if you have a PowerVR 2 card in your box. If you plan to + run linux on your Dreamcast, you will have to say Y here. + This driver may or may not work on other PowerVR 2 cards, but is + totally untested. Use at your own risk. If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called pvr2fb. + + You can pass several parameters to the driver at boot time or at + module load time. The parameters look like "video=pvr2:XXX", where + the meaning of XXX can be found at the end of the main source file + (<file:drivers/video/pvr2fb.c>). Please see the file + <file:Documentation/fb/pvr2fb.txt>. + +config FB_OPENCORES + tristate "OpenCores VGA/LCD core 2.0 framebuffer support" + depends on FB && HAS_DMA + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This enables support for the OpenCores VGA/LCD core. + + The OpenCores VGA/LCD core is typically used together with + softcore CPUs (e.g. OpenRISC or Microblaze) or hard processor + systems (e.g. Altera socfpga or Xilinx Zynq) on FPGAs. + + The source code and specification for the core is available at + <http://opencores.org/project,vga_lcd> + +config FB_S1D13XXX + tristate "Epson S1D13XXX framebuffer support" + depends on FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Support for S1D13XXX framebuffer device family (currently only + working with S1D13806). Product specs at + <http://vdc.epson.com/> + +config FB_ATMEL + tristate "AT91/AT32 LCD Controller support" + depends on FB && HAVE_FB_ATMEL + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + help + This enables support for the AT91/AT32 LCD Controller. + +config FB_INTSRAM + bool "Frame Buffer in internal SRAM" + depends on FB_ATMEL && ARCH_AT91SAM9261 + help + Say Y if you want to map Frame Buffer in internal SRAM. Say N if you want + to let frame buffer in external SDRAM. + +config FB_ATMEL_STN + bool "Use a STN display with AT91/AT32 LCD Controller" + depends on FB_ATMEL && (MACH_AT91SAM9261EK || MACH_AT91SAM9G10EK) + default n + help + Say Y if you want to connect a STN LCD display to the AT91/AT32 LCD + Controller. Say N if you want to connect a TFT. + + If unsure, say N. + +config FB_NVIDIA + tristate "nVidia Framebuffer Support" + depends on FB && PCI + select FB_BACKLIGHT if FB_NVIDIA_BACKLIGHT + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select BITREVERSE + select VGASTATE + help + This driver supports graphics boards with the nVidia chips, TNT + and newer. For very old chipsets, such as the RIVA128, then use + the rivafb. + Say Y if you have such a graphics board. + + To compile this driver as a module, choose M here: the + module will be called nvidiafb. + +config FB_NVIDIA_I2C + bool "Enable DDC Support" + depends on FB_NVIDIA + select FB_DDC + help + This enables I2C support for nVidia Chipsets. This is used + only for getting EDID information from the attached display + allowing for robust video mode handling and switching. + + Because fbdev-2.6 requires that drivers must be able to + independently validate video mode parameters, you should say Y + here. + +config FB_NVIDIA_DEBUG + bool "Lots of debug output" + depends on FB_NVIDIA + default n + help + Say Y here if you want the nVidia driver to output all sorts + of debugging information to provide to the maintainer when + something goes wrong. + +config FB_NVIDIA_BACKLIGHT + bool "Support for backlight control" + depends on FB_NVIDIA + default y + help + Say Y here if you want to control the backlight of your display. + +config FB_RIVA + tristate "nVidia Riva support" + depends on FB && PCI + select FB_BACKLIGHT if FB_RIVA_BACKLIGHT + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select BITREVERSE + select VGASTATE + help + This driver supports graphics boards with the nVidia Riva/Geforce + chips. + Say Y if you have such a graphics board. + + To compile this driver as a module, choose M here: the + module will be called rivafb. + +config FB_RIVA_I2C + bool "Enable DDC Support" + depends on FB_RIVA + select FB_DDC + help + This enables I2C support for nVidia Chipsets. This is used + only for getting EDID information from the attached display + allowing for robust video mode handling and switching. + + Because fbdev-2.6 requires that drivers must be able to + independently validate video mode parameters, you should say Y + here. + +config FB_RIVA_DEBUG + bool "Lots of debug output" + depends on FB_RIVA + default n + help + Say Y here if you want the Riva driver to output all sorts + of debugging information to provide to the maintainer when + something goes wrong. + +config FB_RIVA_BACKLIGHT + bool "Support for backlight control" + depends on FB_RIVA + default y + help + Say Y here if you want to control the backlight of your display. + +config FB_I740 + tristate "Intel740 support" + depends on FB && PCI + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select VGASTATE + select FB_DDC + help + This driver supports graphics cards based on Intel740 chip. + +config FB_I810 + tristate "Intel 810/815 support" + depends on FB && PCI && X86_32 && AGP_INTEL + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select VGASTATE + help + This driver supports the on-board graphics built in to the Intel 810 + and 815 chipsets. Say Y if you have and plan to use such a board. + + To compile this driver as a module, choose M here: the + module will be called i810fb. + + For more information, please read + <file:Documentation/fb/intel810.txt> + +config FB_I810_GTF + bool "use VESA Generalized Timing Formula" + depends on FB_I810 + help + If you say Y, then the VESA standard, Generalized Timing Formula + or GTF, will be used to calculate the required video timing values + per video mode. Since the GTF allows nondiscrete timings + (nondiscrete being a range of values as opposed to discrete being a + set of values), you'll be able to use any combination of horizontal + and vertical resolutions, and vertical refresh rates without having + to specify your own timing parameters. This is especially useful + to maximize the performance of an aging display, or if you just + have a display with nonstandard dimensions. A VESA compliant + monitor is recommended, but can still work with non-compliant ones. + If you need or want this, then select this option. The timings may + not be compliant with Intel's recommended values. Use at your own + risk. + + If you say N, the driver will revert to discrete video timings + using a set recommended by Intel in their documentation. + + If unsure, say N. + +config FB_I810_I2C + bool "Enable DDC Support" + depends on FB_I810 && FB_I810_GTF + select FB_DDC + help + +config FB_LE80578 + tristate "Intel LE80578 (Vermilion) support" + depends on FB && PCI && X86 + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This driver supports the LE80578 (Vermilion Range) chipset + +config FB_CARILLO_RANCH + tristate "Intel Carillo Ranch support" + depends on FB_LE80578 && FB && PCI && X86 + help + This driver supports the LE80578 (Carillo Ranch) board + +config FB_INTEL + tristate "Intel 830M/845G/852GM/855GM/865G/915G/945G/945GM/965G/965GM support" + depends on FB && PCI && X86 && AGP_INTEL && EXPERT + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_BOOT_VESA_SUPPORT if FB_INTEL = y + depends on !DRM_I915 + help + This driver supports the on-board graphics built in to the Intel + 830M/845G/852GM/855GM/865G/915G/915GM/945G/945GM/965G/965GM chipsets. + Say Y if you have and plan to use such a board. + + To make FB_INTELFB=Y work you need to say AGP_INTEL=y too. + + To compile this driver as a module, choose M here: the + module will be called intelfb. + + For more information, please read <file:Documentation/fb/intelfb.txt> + +config FB_INTEL_DEBUG + bool "Intel driver Debug Messages" + depends on FB_INTEL + ---help--- + Say Y here if you want the Intel driver to output all sorts + of debugging information to provide to the maintainer when + something goes wrong. + +config FB_INTEL_I2C + bool "DDC/I2C for Intel framebuffer support" + depends on FB_INTEL + select FB_DDC + default y + help + Say Y here if you want DDC/I2C support for your on-board Intel graphics. + +config FB_MATROX + tristate "Matrox acceleration" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_TILEBLITTING + select FB_MACMODES if PPC_PMAC + ---help--- + Say Y here if you have a Matrox Millennium, Matrox Millennium II, + Matrox Mystique, Matrox Mystique 220, Matrox Productiva G100, Matrox + Mystique G200, Matrox Millennium G200, Matrox Marvel G200 video, + Matrox G400, G450 or G550 card in your box. + + To compile this driver as a module, choose M here: the + module will be called matroxfb. + + You can pass several parameters to the driver at boot time or at + module load time. The parameters look like "video=matroxfb:XXX", and + are described in <file:Documentation/fb/matroxfb.txt>. + +config FB_MATROX_MILLENIUM + bool "Millennium I/II support" + depends on FB_MATROX + help + Say Y here if you have a Matrox Millennium or Matrox Millennium II + video card. If you select "Advanced lowlevel driver options" below, + you should check 4 bpp packed pixel, 8 bpp packed pixel, 16 bpp + packed pixel, 24 bpp packed pixel and 32 bpp packed pixel. You can + also use font widths different from 8. + +config FB_MATROX_MYSTIQUE + bool "Mystique support" + depends on FB_MATROX + help + Say Y here if you have a Matrox Mystique or Matrox Mystique 220 + video card. If you select "Advanced lowlevel driver options" below, + you should check 8 bpp packed pixel, 16 bpp packed pixel, 24 bpp + packed pixel and 32 bpp packed pixel. You can also use font widths + different from 8. + +config FB_MATROX_G + bool "G100/G200/G400/G450/G550 support" + depends on FB_MATROX + ---help--- + Say Y here if you have a Matrox G100, G200, G400, G450 or G550 based + video card. If you select "Advanced lowlevel driver options", you + should check 8 bpp packed pixel, 16 bpp packed pixel, 24 bpp packed + pixel and 32 bpp packed pixel. You can also use font widths + different from 8. + + If you need support for G400 secondary head, you must say Y to + "Matrox I2C support" and "G400 second head support" right below. + G450/G550 secondary head and digital output are supported without + additional modules. + + The driver starts in monitor mode. You must use the matroxset tool + (available at <ftp://platan.vc.cvut.cz/pub/linux/matrox-latest/>) to + swap primary and secondary head outputs, or to change output mode. + Secondary head driver always start in 640x480 resolution and you + must use fbset to change it. + + Do not forget that second head supports only 16 and 32 bpp + packed pixels, so it is a good idea to compile them into the kernel + too. You can use only some font widths, as the driver uses generic + painting procedures (the secondary head does not use acceleration + engine). + + G450/G550 hardware can display TV picture only from secondary CRTC, + and it performs no scaling, so picture must have 525 or 625 lines. + +config FB_MATROX_I2C + tristate "Matrox I2C support" + depends on FB_MATROX + select FB_DDC + ---help--- + This drivers creates I2C buses which are needed for accessing the + DDC (I2C) bus present on all Matroxes, an I2C bus which + interconnects Matrox optional devices, like MGA-TVO on G200 and + G400, and the secondary head DDC bus, present on G400 only. + + You can say Y or M here if you want to experiment with monitor + detection code. You must say Y or M here if you want to use either + second head of G400 or MGA-TVO on G200 or G400. + + If you compile it as module, it will create a module named + i2c-matroxfb. + +config FB_MATROX_MAVEN + tristate "G400 second head support" + depends on FB_MATROX_G && FB_MATROX_I2C + ---help--- + WARNING !!! This support does not work with G450 !!! + + Say Y or M here if you want to use a secondary head (meaning two + monitors in parallel) on G400 or MGA-TVO add-on on G200. Secondary + head is not compatible with accelerated XFree 3.3.x SVGA servers - + secondary head output is blanked while you are in X. With XFree + 3.9.17 preview you can use both heads if you use SVGA over fbdev or + the fbdev driver on first head and the fbdev driver on second head. + + If you compile it as module, two modules are created, + matroxfb_crtc2 and matroxfb_maven. Matroxfb_maven is needed for + both G200 and G400, matroxfb_crtc2 is needed only by G400. You must + also load i2c-matroxfb to get it to run. + + The driver starts in monitor mode and you must use the matroxset + tool (available at + <ftp://platan.vc.cvut.cz/pub/linux/matrox-latest/>) to switch it to + PAL or NTSC or to swap primary and secondary head outputs. + Secondary head driver also always start in 640x480 resolution, you + must use fbset to change it. + + Also do not forget that second head supports only 16 and 32 bpp + packed pixels, so it is a good idea to compile them into the kernel + too. You can use only some font widths, as the driver uses generic + painting procedures (the secondary head does not use acceleration + engine). + +config FB_RADEON + tristate "ATI Radeon display support" + depends on FB && PCI + select FB_BACKLIGHT if FB_RADEON_BACKLIGHT + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MACMODES if PPC_OF + help + Choose this option if you want to use an ATI Radeon graphics card as + a framebuffer device. There are both PCI and AGP versions. You + don't need to choose this to run the Radeon in plain VGA mode. + + There is a product page at + http://products.amd.com/en-us/GraphicCardResult.aspx + +config FB_RADEON_I2C + bool "DDC/I2C for ATI Radeon support" + depends on FB_RADEON + select FB_DDC + default y + help + Say Y here if you want DDC/I2C support for your Radeon board. + +config FB_RADEON_BACKLIGHT + bool "Support for backlight control" + depends on FB_RADEON + default y + help + Say Y here if you want to control the backlight of your display. + +config FB_RADEON_DEBUG + bool "Lots of debug output from Radeon driver" + depends on FB_RADEON + default n + help + Say Y here if you want the Radeon driver to output all sorts + of debugging information to provide to the maintainer when + something goes wrong. + +config FB_ATY128 + tristate "ATI Rage128 display support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_BACKLIGHT if FB_ATY128_BACKLIGHT + select FB_MACMODES if PPC_PMAC + help + This driver supports graphics boards with the ATI Rage128 chips. + Say Y if you have such a graphics board and read + <file:Documentation/fb/aty128fb.txt>. + + To compile this driver as a module, choose M here: the + module will be called aty128fb. + +config FB_ATY128_BACKLIGHT + bool "Support for backlight control" + depends on FB_ATY128 + default y + help + Say Y here if you want to control the backlight of your display. + +config FB_ATY + tristate "ATI Mach64 display support" if PCI || ATARI + depends on FB && !SPARC32 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_BACKLIGHT if FB_ATY_BACKLIGHT + select FB_MACMODES if PPC + help + This driver supports graphics boards with the ATI Mach64 chips. + Say Y if you have such a graphics board. + + To compile this driver as a module, choose M here: the + module will be called atyfb. + +config FB_ATY_CT + bool "Mach64 CT/VT/GT/LT (incl. 3D RAGE) support" + depends on PCI && FB_ATY + default y if SPARC64 && PCI + help + Say Y here to support use of ATI's 64-bit Rage boards (or other + boards based on the Mach64 CT, VT, GT, and LT chipsets) as a + framebuffer device. The ATI product support page for these boards + is at <http://support.ati.com/products/pc/mach64/mach64.html>. + +config FB_ATY_GENERIC_LCD + bool "Mach64 generic LCD support" + depends on FB_ATY_CT + help + Say Y if you have a laptop with an ATI Rage LT PRO, Rage Mobility, + Rage XC, or Rage XL chipset. + +config FB_ATY_GX + bool "Mach64 GX support" if PCI + depends on FB_ATY + default y if ATARI + help + Say Y here to support use of the ATI Mach64 Graphics Expression + board (or other boards based on the Mach64 GX chipset) as a + framebuffer device. The ATI product support page for these boards + is at + <http://support.ati.com/products/pc/mach64/graphics_xpression.html>. + +config FB_ATY_BACKLIGHT + bool "Support for backlight control" + depends on FB_ATY + default y + help + Say Y here if you want to control the backlight of your display. + +config FB_S3 + tristate "S3 Trio/Virge support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_TILEBLITTING + select FB_SVGALIB + select VGASTATE + select FONT_8x16 if FRAMEBUFFER_CONSOLE + ---help--- + Driver for graphics boards with S3 Trio / S3 Virge chip. + +config FB_S3_DDC + bool "DDC for S3 support" + depends on FB_S3 + select FB_DDC + default y + help + Say Y here if you want DDC support for your S3 graphics card. + +config FB_SAVAGE + tristate "S3 Savage support" + depends on FB && PCI + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select VGASTATE + help + This driver supports notebooks and computers with S3 Savage PCI/AGP + chips. + + Say Y if you have such a graphics card. + + To compile this driver as a module, choose M here; the module + will be called savagefb. + +config FB_SAVAGE_I2C + bool "Enable DDC2 Support" + depends on FB_SAVAGE + select FB_DDC + help + This enables I2C support for S3 Savage Chipsets. This is used + only for getting EDID information from the attached display + allowing for robust video mode handling and switching. + + Because fbdev-2.6 requires that drivers must be able to + independently validate video mode parameters, you should say Y + here. + +config FB_SAVAGE_ACCEL + bool "Enable Console Acceleration" + depends on FB_SAVAGE + default n + help + This option will compile in console acceleration support. If + the resulting framebuffer console has bothersome glitches, then + choose N here. + +config FB_SIS + tristate "SiS/XGI display support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_BOOT_VESA_SUPPORT if FB_SIS = y + help + This is the frame buffer device driver for the SiS 300, 315, 330 + and 340 series as well as XGI V3XT, V5, V8, Z7 graphics chipsets. + Specs available at <http://www.sis.com> and <http://www.xgitech.com>. + + To compile this driver as a module, choose M here; the module + will be called sisfb. + +config FB_SIS_300 + bool "SiS 300 series support" + depends on FB_SIS + help + Say Y here to support use of the SiS 300/305, 540, 630 and 730. + +config FB_SIS_315 + bool "SiS 315/330/340 series and XGI support" + depends on FB_SIS + help + Say Y here to support use of the SiS 315, 330 and 340 series + (315/H/PRO, 55x, 650, 651, 740, 330, 661, 741, 760, 761) as well + as XGI V3XT, V5, V8 and Z7. + +config FB_VIA + tristate "VIA UniChrome (Pro) and Chrome9 display support" + depends on FB && PCI && X86 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select I2C_ALGOBIT + select I2C + select GPIOLIB + help + This is the frame buffer device driver for Graphics chips of VIA + UniChrome (Pro) Family (CLE266,PM800/CN400,P4M800CE/P4M800Pro/ + CN700/VN800,CX700/VX700,P4M890) and Chrome9 Family (K8M890,CN896 + /P4M900,VX800) + Say Y if you have a VIA UniChrome graphics board. + + To compile this driver as a module, choose M here: the + module will be called viafb. + +if FB_VIA + +config FB_VIA_DIRECT_PROCFS + bool "direct hardware access via procfs (DEPRECATED)(DANGEROUS)" + depends on FB_VIA + default n + help + Allow direct hardware access to some output registers via procfs. + This is dangerous but may provide the only chance to get the + correct output device configuration. + Its use is strongly discouraged. + +config FB_VIA_X_COMPATIBILITY + bool "X server compatibility" + depends on FB_VIA + default n + help + This option reduces the functionality (power saving, ...) of the + framebuffer to avoid negative impact on the OpenChrome X server. + If you use any X server other than fbdev you should enable this + otherwise it should be safe to disable it and allow using all + features. + +endif + +config FB_NEOMAGIC + tristate "NeoMagic display support" + depends on FB && PCI + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select VGASTATE + help + This driver supports notebooks with NeoMagic PCI chips. + Say Y if you have such a graphics card. + + To compile this driver as a module, choose M here: the + module will be called neofb. + +config FB_KYRO + tristate "IMG Kyro support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Say Y here if you have a STG4000 / Kyro / PowerVR 3 based + graphics board. + + To compile this driver as a module, choose M here: the + module will be called kyrofb. + +config FB_3DFX + tristate "3Dfx Banshee/Voodoo3/Voodoo5 display support" + depends on FB && PCI + select FB_CFB_IMAGEBLIT + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_MODE_HELPERS + help + This driver supports graphics boards with the 3Dfx Banshee, + Voodoo3 or VSA-100 (aka Voodoo4/5) chips. Say Y if you have + such a graphics board. + + To compile this driver as a module, choose M here: the + module will be called tdfxfb. + +config FB_3DFX_ACCEL + bool "3Dfx Acceleration functions" + depends on FB_3DFX + ---help--- + This will compile the 3Dfx Banshee/Voodoo3/VSA-100 frame buffer + device driver with acceleration functions. + +config FB_3DFX_I2C + bool "Enable DDC/I2C support" + depends on FB_3DFX + select FB_DDC + default y + help + Say Y here if you want DDC/I2C support for your 3dfx Voodoo3. + +config FB_VOODOO1 + tristate "3Dfx Voodoo Graphics (sst1) support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Say Y here if you have a 3Dfx Voodoo Graphics (Voodoo1/sst1) or + Voodoo2 (cvg) based graphics card. + + To compile this driver as a module, choose M here: the + module will be called sstfb. + + WARNING: Do not use any application that uses the 3D engine + (namely glide) while using this driver. + Please read the <file:Documentation/fb/sstfb.txt> for supported + options and other important info support. + +config FB_VT8623 + tristate "VIA VT8623 support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_TILEBLITTING + select FB_SVGALIB + select VGASTATE + select FONT_8x16 if FRAMEBUFFER_CONSOLE + ---help--- + Driver for CastleRock integrated graphics core in the + VIA VT8623 [Apollo CLE266] chipset. + +config FB_TRIDENT + tristate "Trident/CyberXXX/CyberBlade support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + This is the frame buffer device driver for Trident PCI/AGP chipsets. + Supported chipset families are TGUI 9440/96XX, 3DImage, Blade3D + and Blade XP. + There are also integrated versions of these chips called CyberXXXX, + CyberImage or CyberBlade. These chips are mostly found in laptops + but also on some motherboards including early VIA EPIA motherboards. + For more information, read <file:Documentation/fb/tridentfb.txt> + + Say Y if you have such a graphics board. + + To compile this driver as a module, choose M here: the + module will be called tridentfb. + +config FB_ARK + tristate "ARK 2000PV support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_TILEBLITTING + select FB_SVGALIB + select VGASTATE + select FONT_8x16 if FRAMEBUFFER_CONSOLE + ---help--- + Driver for PCI graphics boards with ARK 2000PV chip + and ICS 5342 RAMDAC. + +config FB_PM3 + tristate "Permedia3 support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the 3DLabs Permedia3 + chipset, used in Formac ProFormance III, 3DLabs Oxygen VX1 & + similar boards, 3DLabs Permedia3 Create!, Appian Jeronimo 2000 + and maybe other boards. + +config FB_CARMINE + tristate "Fujitsu carmine frame buffer support" + depends on FB && PCI + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Fujitsu Carmine chip. + The driver provides two independent frame buffer devices. + +choice + depends on FB_CARMINE + prompt "DRAM timing" + default FB_CARMINE_DRAM_EVAL + +config FB_CARMINE_DRAM_EVAL + bool "Eval board timings" + help + Use timings which work on the eval card. + +config CARMINE_DRAM_CUSTOM + bool "Custom board timings" + help + Use custom board timings. +endchoice + +config FB_AU1100 + bool "Au1100 LCD Driver" + depends on (FB = y) && MIPS_ALCHEMY + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the framebuffer driver for the AMD Au1100 SOC. It can drive + various panels and CRTs by passing in kernel cmd line option + au1100fb:panel=<name>. + +config FB_AU1200 + bool "Au1200/Au1300 LCD Driver" + depends on (FB = y) && MIPS_ALCHEMY + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + help + This is the framebuffer driver for the Au1200/Au1300 SOCs. + It can drive various panels and CRTs by passing in kernel cmd line + option au1200fb:panel=<name>. + +config FB_VT8500 + bool "VIA VT8500 framebuffer support" + depends on (FB = y) && ARM && ARCH_VT8500 + select FB_SYS_FILLRECT if (!FB_WMT_GE_ROPS) + select FB_SYS_COPYAREA if (!FB_WMT_GE_ROPS) + select FB_SYS_IMAGEBLIT + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + help + This is the framebuffer driver for VIA VT8500 integrated LCD + controller. + +config FB_WM8505 + bool "Wondermedia WM8xxx-series frame buffer support" + depends on (FB = y) && ARM && ARCH_VT8500 + select FB_SYS_FILLRECT if (!FB_WMT_GE_ROPS) + select FB_SYS_COPYAREA if (!FB_WMT_GE_ROPS) + select FB_SYS_IMAGEBLIT + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + help + This is the framebuffer driver for WonderMedia WM8xxx-series + integrated LCD controller. This driver covers the WM8505, WM8650 + and WM8850 SoCs. + +config FB_WMT_GE_ROPS + bool "VT8500/WM8xxx accelerated raster ops support" + depends on (FB = y) && (FB_VT8500 || FB_WM8505) + default n + help + This adds support for accelerated raster operations on the + VIA VT8500 and Wondermedia 85xx series SoCs. + +source "drivers/video/fbdev/geode/Kconfig" + +config FB_HIT + tristate "HD64461 Frame Buffer support" + depends on FB && HD64461 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Hitachi HD64461 LCD + frame buffer card. + +config FB_PMAG_AA + bool "PMAG-AA TURBOchannel framebuffer support" + depends on (FB = y) && TC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Support for the PMAG-AA TURBOchannel framebuffer card (1280x1024x1) + used mainly in the MIPS-based DECstation series. + +config FB_PMAG_BA + tristate "PMAG-BA TURBOchannel framebuffer support" + depends on FB && TC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Support for the PMAG-BA TURBOchannel framebuffer card (1024x864x8) + used mainly in the MIPS-based DECstation series. + +config FB_PMAGB_B + tristate "PMAGB-B TURBOchannel framebuffer support" + depends on FB && TC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Support for the PMAGB-B TURBOchannel framebuffer card used mainly + in the MIPS-based DECstation series. The card is currently only + supported in 1280x1024x8 mode. + +config FB_MAXINE + bool "Maxine (Personal DECstation) onboard framebuffer support" + depends on (FB = y) && MACH_DECSTATION + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Support for the onboard framebuffer (1024x768x8) in the Personal + DECstation series (Personal DECstation 5000/20, /25, /33, /50, + Codename "Maxine"). + +config FB_G364 + bool "G364 frame buffer support" + depends on (FB = y) && (MIPS_MAGNUM_4000 || OLIVETTI_M700) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + The G364 driver is the framebuffer used in MIPS Magnum 4000 and + Olivetti M700-10 systems. + +config FB_68328 + bool "Motorola 68328 native frame buffer support" + depends on (FB = y) && (M68328 || M68EZ328 || M68VZ328) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Say Y here if you want to support the built-in frame buffer of + the Motorola 68328 CPU family. + +config FB_PXA168 + tristate "PXA168/910 LCD framebuffer support" + depends on FB && (CPU_PXA168 || CPU_PXA910) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the built-in LCD controller in the Marvell + MMP processor. + +config FB_PXA + tristate "PXA LCD framebuffer support" + depends on FB && ARCH_PXA + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the built-in LCD controller in the Intel + PXA2x0 processor. + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called pxafb. If you want to compile it as a module, + say M here and read <file:Documentation/kbuild/modules.txt>. + + If unsure, say N. + +config FB_PXA_OVERLAY + bool "Support PXA27x/PXA3xx Overlay(s) as framebuffer" + default n + depends on FB_PXA && (PXA27x || PXA3xx) + +config FB_PXA_SMARTPANEL + bool "PXA Smartpanel LCD support" + default n + depends on FB_PXA + +config FB_PXA_PARAMETERS + bool "PXA LCD command line parameters" + default n + depends on FB_PXA + ---help--- + Enable the use of kernel command line or module parameters + to configure the physical properties of the LCD panel when + using the PXA LCD driver. + + This option allows you to override the panel parameters + supplied by the platform in order to support multiple + different models of flatpanel. If you will only be using a + single model of flatpanel then you can safely leave this + option disabled. + + <file:Documentation/fb/pxafb.txt> describes the available parameters. + +config PXA3XX_GCU + tristate "PXA3xx 2D graphics accelerator driver" + depends on FB_PXA + help + Kernelspace driver for the 2D graphics controller unit (GCU) + found on PXA3xx processors. There is a counterpart driver in the + DirectFB suite, see http://www.directfb.org/ + + If you compile this as a module, it will be called pxa3xx_gcu. + +config FB_MBX + tristate "2700G LCD framebuffer support" + depends on FB && ARCH_PXA + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Framebuffer driver for the Intel 2700G (Marathon) Graphics + Accelerator + +config FB_MBX_DEBUG + bool "Enable debugging info via debugfs" + depends on FB_MBX && DEBUG_FS + default n + ---help--- + Enable this if you want debugging information using the debug + filesystem (debugfs) + + If unsure, say N. + +config FB_FSL_DIU + tristate "Freescale DIU framebuffer support" + depends on FB && FSL_SOC + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select PPC_LIB_RHEAP + ---help--- + Framebuffer driver for the Freescale SoC DIU + +config FB_W100 + tristate "W100 frame buffer support" + depends on FB && ARCH_PXA + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the w100 as found on the Sharp SL-Cxx series. + It can also drive the w3220 chip found on iPAQ hx4700. + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called w100fb. If you want to compile it as a module, + say M here and read <file:Documentation/kbuild/modules.txt>. + + If unsure, say N. + +config FB_SH_MOBILE_LCDC + tristate "SuperH Mobile LCDC framebuffer support" + depends on FB && (SUPERH || ARCH_SHMOBILE) && HAVE_CLK + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + select FB_BACKLIGHT + select SH_MIPI_DSI if SH_LCD_MIPI_DSI + ---help--- + Frame buffer driver for the on-chip SH-Mobile LCD controller. + +config FB_SH_MOBILE_HDMI + tristate "SuperH Mobile HDMI controller support" + depends on FB_SH_MOBILE_LCDC + select FB_MODE_HELPERS + select SOUND + select SND + select SND_SOC + ---help--- + Driver for the on-chip SH-Mobile HDMI controller. + +config FB_TMIO + tristate "Toshiba Mobile IO FrameBuffer support" + depends on FB && MFD_CORE + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the Toshiba Mobile IO integrated as found + on the Sharp SL-6000 series + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called tmiofb. If you want to compile it as a module, + say M here and read <file:Documentation/kbuild/modules.txt>. + + If unsure, say N. + +config FB_TMIO_ACCELL + bool "tmiofb acceleration" + depends on FB_TMIO + default y + +config FB_S3C + tristate "Samsung S3C framebuffer support" + depends on FB && (CPU_S3C2416 || ARCH_S3C64XX || ARCH_S5P64X0 || \ + ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the built-in FB controller in the Samsung + SoC line from the S3C2443 onwards, including the S3C2416, S3C2450, + and the S3C64XX series such as the S3C6400 and S3C6410. + + These chips all have the same basic framebuffer design with the + actual capabilities depending on the chip. For instance the S3C6400 + and S3C6410 support 4 hardware windows whereas the S3C24XX series + currently only have two. + + Currently the support is only for the S3C6400 and S3C6410 SoCs. + +config FB_S3C_DEBUG_REGWRITE + bool "Debug register writes" + depends on FB_S3C + ---help--- + Show all register writes via pr_debug() + +config FB_S3C2410 + tristate "S3C2410 LCD framebuffer support" + depends on FB && ARCH_S3C24XX + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the built-in LCD controller in the Samsung + S3C2410 processor. + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called s3c2410fb. If you want to compile it as a module, + say M here and read <file:Documentation/kbuild/modules.txt>. + + If unsure, say N. +config FB_S3C2410_DEBUG + bool "S3C2410 lcd debug messages" + depends on FB_S3C2410 + help + Turn on debugging messages. Note that you can set/unset at run time + through sysfs + +config FB_NUC900 + bool "NUC900 LCD framebuffer support" + depends on FB && ARCH_W90X900 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the built-in LCD controller in the Nuvoton + NUC900 processor + +config GPM1040A0_320X240 + bool "Giantplus Technology GPM1040A0 320x240 Color TFT LCD" + depends on FB_NUC900 + +config FB_SM501 + tristate "Silicon Motion SM501 framebuffer support" + depends on FB && MFD_SM501 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for the CRT and LCD controllers in the Silicon + Motion SM501. + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called sm501fb. If you want to compile it as a module, + say M here and read <file:Documentation/kbuild/modules.txt>. + + If unsure, say N. + +config FB_SMSCUFX + tristate "SMSC UFX6000/7000 USB Framebuffer support" + depends on FB && USB + select FB_MODE_HELPERS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + ---help--- + This is a kernel framebuffer driver for SMSC UFX USB devices. + Supports fbdev clients like xf86-video-fbdev, kdrive, fbi, and + mplayer -vo fbdev. Supports both UFX6000 (USB 2.0) and UFX7000 + (USB 3.0) devices. + To compile as a module, choose M here: the module name is smscufx. + +config FB_UDL + tristate "Displaylink USB Framebuffer support" + depends on FB && USB + select FB_MODE_HELPERS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + ---help--- + This is a kernel framebuffer driver for DisplayLink USB devices. + Supports fbdev clients like xf86-video-fbdev, kdrive, fbi, and + mplayer -vo fbdev. Supports all USB 2.0 era DisplayLink devices. + To compile as a module, choose M here: the module name is udlfb. + +config FB_IBM_GXT4500 + tristate "Framebuffer support for IBM GXT4000P/4500P/6000P/6500P adaptors" + depends on FB && PPC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Say Y here to enable support for the IBM GXT4000P/6000P and + GXT4500P/6500P display adaptor based on Raster Engine RC1000, + found on some IBM System P (pSeries) machines. This driver + doesn't use Geometry Engine GT1000. + +config FB_PS3 + tristate "PS3 GPU framebuffer driver" + depends on FB && PS3_PS3AV + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE + ---help--- + Include support for the virtual frame buffer in the PS3 platform. + +config FB_PS3_DEFAULT_SIZE_M + int "PS3 default frame buffer size (in MiB)" + depends on FB_PS3 + default 9 + ---help--- + This is the default size (in MiB) of the virtual frame buffer in + the PS3. + The default value can be overridden on the kernel command line + using the "ps3fb" option (e.g. "ps3fb=9M"); + +config FB_XILINX + tristate "Xilinx frame buffer support" + depends on FB && (XILINX_VIRTEX || MICROBLAZE || ARCH_ZYNQ) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Include support for the Xilinx ML300/ML403 reference design + framebuffer. ML300 carries a 640*480 LCD display on the board, + ML403 uses a standard DB15 VGA connector. + +config FB_GOLDFISH + tristate "Goldfish Framebuffer" + depends on FB && HAS_DMA + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Framebuffer driver for Goldfish Virtual Platform + +config FB_COBALT + tristate "Cobalt server LCD frame buffer support" + depends on FB && (MIPS_COBALT || MIPS_SEAD3) + +config FB_SH7760 + bool "SH7760/SH7763/SH7720/SH7721 LCDC support" + depends on FB && (CPU_SUBTYPE_SH7760 || CPU_SUBTYPE_SH7763 \ + || CPU_SUBTYPE_SH7720 || CPU_SUBTYPE_SH7721) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Support for the SH7760/SH7763/SH7720/SH7721 integrated + (D)STN/TFT LCD Controller. + Supports display resolutions up to 1024x1024 pixel, grayscale and + color operation, with depths ranging from 1 bpp to 8 bpp monochrome + and 8, 15 or 16 bpp color; 90 degrees clockwise display rotation for + panels <= 320 pixel horizontal resolution. + +config FB_DA8XX + tristate "DA8xx/OMAP-L1xx/AM335x Framebuffer support" + depends on FB && (ARCH_DAVINCI_DA8XX || SOC_AM33XX) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_CFB_REV_PIXELS_IN_BYTE + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + ---help--- + This is the frame buffer device driver for the TI LCD controller + found on DA8xx/OMAP-L1xx/AM335x SoCs. + If unsure, say N. + +config FB_VIRTUAL + tristate "Virtual Frame Buffer support (ONLY FOR TESTING!)" + depends on FB + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + ---help--- + This is a `virtual' frame buffer device. It operates on a chunk of + unswappable kernel memory instead of on the memory of a graphics + board. This means you cannot see any output sent to this frame + buffer device, while it does consume precious memory. The main use + of this frame buffer device is testing and debugging the frame + buffer subsystem. Do NOT enable it for normal systems! To protect + the innocent, it has to be enabled explicitly at boot time using the + kernel option `video=vfb:'. + + To compile this driver as a module, choose M here: the + module will be called vfb. In order to load it, you must use + the vfb_enable=1 option. + + If unsure, say N. + +config XEN_FBDEV_FRONTEND + tristate "Xen virtual frame buffer support" + depends on FB && XEN + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + select INPUT_XEN_KBDDEV_FRONTEND if INPUT_MISC + select XEN_XENBUS_FRONTEND + default y + help + This driver implements the front-end of the Xen virtual + frame buffer driver. It communicates with a back-end + in another domain. + +config FB_METRONOME + tristate "E-Ink Metronome/8track controller support" + depends on FB + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + help + This driver implements support for the E-Ink Metronome + controller. The pre-release name for this device was 8track + and could also have been called by some vendors as PVI-nnnn. + +config FB_MB862XX + tristate "Fujitsu MB862xx GDC support" + depends on FB + depends on PCI || (OF && PPC) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Frame buffer driver for Fujitsu Carmine/Coral-P(A)/Lime controllers. + +choice + prompt "GDC variant" + depends on FB_MB862XX + +config FB_MB862XX_PCI_GDC + bool "Carmine/Coral-P(A) GDC" + depends on PCI + ---help--- + This enables framebuffer support for Fujitsu Carmine/Coral-P(A) + PCI graphics controller devices. + +config FB_MB862XX_LIME + bool "Lime GDC" + depends on OF && PPC + select FB_FOREIGN_ENDIAN + select FB_LITTLE_ENDIAN + ---help--- + Framebuffer support for Fujitsu Lime GDC on host CPU bus. + +endchoice + +config FB_MB862XX_I2C + bool "Support I2C bus on MB862XX GDC" + depends on FB_MB862XX && I2C + default y + help + Selecting this option adds Coral-P(A)/Lime GDC I2C bus adapter + driver to support accessing I2C devices on controller's I2C bus. + These are usually some video decoder chips. + +config FB_EP93XX + tristate "EP93XX frame buffer support" + depends on FB && ARCH_EP93XX + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Framebuffer driver for the Cirrus Logic EP93XX series of processors. + This driver is also available as a module. The module will be called + ep93xx-fb. + +config FB_PRE_INIT_FB + bool "Don't reinitialize, use bootloader's GDC/Display configuration" + depends on FB && FB_MB862XX_LIME + ---help--- + Select this option if display contents should be inherited as set by + the bootloader. + +config FB_MSM + tristate "MSM Framebuffer support" + depends on FB && ARCH_MSM + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + +config FB_MX3 + tristate "MX3 Framebuffer support" + depends on FB && MX3_IPU + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + default y + help + This is a framebuffer device for the i.MX31 LCD Controller. So + far only synchronous displays are supported. If you plan to use + an LCD display with your i.MX31 system, say Y here. + +config FB_BROADSHEET + tristate "E-Ink Broadsheet/Epson S1D13521 controller support" + depends on FB + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + help + This driver implements support for the E-Ink Broadsheet + controller. The release name for this device was Epson S1D13521 + and could also have been called by other names when coupled with + a bridge adapter. + +config FB_AUO_K190X + tristate "AUO-K190X EPD controller support" + depends on FB + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + help + Provides support for epaper controllers from the K190X series + of AUO. These controllers can be used to drive epaper displays + from Sipix. + + This option enables the common support, shared by the individual + controller drivers. You will also have to enable the driver + for the controller type used in your device. + +config FB_AUO_K1900 + tristate "AUO-K1900 EPD controller support" + depends on FB && FB_AUO_K190X + help + This driver implements support for the AUO K1900 epd-controller. + This controller can drive Sipix epaper displays but can only do + serial updates, reducing the number of possible frames per second. + +config FB_AUO_K1901 + tristate "AUO-K1901 EPD controller support" + depends on FB && FB_AUO_K190X + help + This driver implements support for the AUO K1901 epd-controller. + This controller can drive Sipix epaper displays and supports + concurrent updates, making higher frames per second possible. + +config FB_JZ4740 + tristate "JZ4740 LCD framebuffer support" + depends on FB && MACH_JZ4740 + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + help + Framebuffer support for the JZ4740 SoC. + +config FB_MXS + tristate "MXS LCD framebuffer support" + depends on FB && ARCH_MXS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + help + Framebuffer support for the MXS SoC. + +config FB_PUV3_UNIGFX + tristate "PKUnity v3 Unigfx framebuffer support" + depends on FB && UNICORE32 && ARCH_PUV3 + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + help + Choose this option if you want to use the Unigfx device as a + framebuffer device. Without the support of PCI & AGP. + +config FB_HYPERV + tristate "Microsoft Hyper-V Synthetic Video support" + depends on FB && HYPERV + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This framebuffer driver supports Microsoft Hyper-V Synthetic Video. + +config FB_SIMPLE + bool "Simple framebuffer support" + depends on (FB = y) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Say Y if you want support for a simple frame-buffer. + + This driver assumes that the display hardware has been initialized + before the kernel boots, and the kernel will simply render to the + pre-allocated frame buffer surface. + + Configuration re: surface address, size, and format must be provided + through device tree, or plain old platform data. + +source "drivers/video/fbdev/omap/Kconfig" +source "drivers/video/fbdev/omap2/Kconfig" +source "drivers/video/fbdev/exynos/Kconfig" +source "drivers/video/fbdev/mmp/Kconfig" + +config FB_SH_MOBILE_MERAM + tristate "SuperH Mobile MERAM read ahead support" + depends on (SUPERH || ARCH_SHMOBILE) + select GENERIC_ALLOCATOR + ---help--- + Enable MERAM support for the SuperH controller. + + This will allow for caching of the framebuffer to provide more + reliable access under heavy main memory bus traffic situations. + Up to 4 memory channels can be configured, allowing 4 RGB or + 2 YCbCr framebuffers to be configured. + +config FB_SSD1307 + tristate "Solomon SSD1307 framebuffer support" + depends on FB && I2C + depends on OF + depends on GPIOLIB + select FB_SYS_FOPS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_DEFERRED_IO + select PWM + help + This driver implements support for the Solomon SSD1307 + OLED controller over I2C. diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile new file mode 100644 index 000000000000..8a79eec2113b --- /dev/null +++ b/drivers/video/fbdev/Makefile @@ -0,0 +1,166 @@ +# Makefile for the Linux video drivers. +# 5 Aug 1999, James Simmons, <mailto:jsimmons@users.sf.net> +# Rewritten to use lists instead of if-statements. + +# Each configuration option enables a list of files. + +obj-y += fb_notify.o +obj-$(CONFIG_FB) += fb.o +fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ + modedb.o fbcvt.o +fb-objs := $(fb-y) + +obj-$(CONFIG_EXYNOS_VIDEO) += exynos/ + +obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o +obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o +obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o +obj-$(CONFIG_FB_SYS_FILLRECT) += sysfillrect.o +obj-$(CONFIG_FB_SYS_COPYAREA) += syscopyarea.o +obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o +obj-$(CONFIG_FB_SYS_FOPS) += fb_sys_fops.o +obj-$(CONFIG_FB_SVGALIB) += svgalib.o +obj-$(CONFIG_FB_MACMODES) += macmodes.o +obj-$(CONFIG_FB_DDC) += fb_ddc.o +obj-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o +obj-$(CONFIG_FB_WMT_GE_ROPS) += wmt_ge_rops.o + +# Hardware specific drivers go first +obj-$(CONFIG_FB_AMIGA) += amifb.o c2p_planar.o +obj-$(CONFIG_FB_ARC) += arcfb.o +obj-$(CONFIG_FB_CLPS711X) += clps711xfb.o +obj-$(CONFIG_FB_CYBER2000) += cyber2000fb.o +obj-$(CONFIG_FB_GRVGA) += grvga.o +obj-$(CONFIG_FB_PM2) += pm2fb.o +obj-$(CONFIG_FB_PM3) += pm3fb.o + +obj-$(CONFIG_FB_I740) += i740fb.o +obj-$(CONFIG_FB_MATROX) += matrox/ +obj-$(CONFIG_FB_RIVA) += riva/ +obj-$(CONFIG_FB_NVIDIA) += nvidia/ +obj-$(CONFIG_FB_ATY) += aty/ macmodes.o +obj-$(CONFIG_FB_ATY128) += aty/ macmodes.o +obj-$(CONFIG_FB_RADEON) += aty/ +obj-$(CONFIG_FB_SIS) += sis/ +obj-$(CONFIG_FB_VIA) += via/ +obj-$(CONFIG_FB_KYRO) += kyro/ +obj-$(CONFIG_FB_SAVAGE) += savage/ +obj-$(CONFIG_FB_GEODE) += geode/ +obj-$(CONFIG_FB_MBX) += mbx/ +obj-$(CONFIG_FB_NEOMAGIC) += neofb.o +obj-$(CONFIG_FB_3DFX) += tdfxfb.o +obj-$(CONFIG_FB_CONTROL) += controlfb.o +obj-$(CONFIG_FB_PLATINUM) += platinumfb.o +obj-$(CONFIG_FB_VALKYRIE) += valkyriefb.o +obj-$(CONFIG_FB_CT65550) += chipsfb.o +obj-$(CONFIG_FB_IMSTT) += imsttfb.o +obj-$(CONFIG_FB_FM2) += fm2fb.o +obj-$(CONFIG_FB_VT8623) += vt8623fb.o +obj-$(CONFIG_FB_TRIDENT) += tridentfb.o +obj-$(CONFIG_FB_LE80578) += vermilion/ +obj-$(CONFIG_FB_S3) += s3fb.o +obj-$(CONFIG_FB_ARK) += arkfb.o +obj-$(CONFIG_FB_STI) += stifb.o +obj-$(CONFIG_FB_FFB) += ffb.o sbuslib.o +obj-$(CONFIG_FB_CG6) += cg6.o sbuslib.o +obj-$(CONFIG_FB_CG3) += cg3.o sbuslib.o +obj-$(CONFIG_FB_BW2) += bw2.o sbuslib.o +obj-$(CONFIG_FB_CG14) += cg14.o sbuslib.o +obj-$(CONFIG_FB_P9100) += p9100.o sbuslib.o +obj-$(CONFIG_FB_TCX) += tcx.o sbuslib.o +obj-$(CONFIG_FB_LEO) += leo.o sbuslib.o +obj-$(CONFIG_FB_ACORN) += acornfb.o +obj-$(CONFIG_FB_ATARI) += atafb.o c2p_iplan2.o atafb_mfb.o \ + atafb_iplan2p2.o atafb_iplan2p4.o atafb_iplan2p8.o +obj-$(CONFIG_FB_MAC) += macfb.o +obj-$(CONFIG_FB_HECUBA) += hecubafb.o +obj-$(CONFIG_FB_N411) += n411.o +obj-$(CONFIG_FB_HGA) += hgafb.o +obj-$(CONFIG_FB_XVR500) += sunxvr500.o +obj-$(CONFIG_FB_XVR2500) += sunxvr2500.o +obj-$(CONFIG_FB_XVR1000) += sunxvr1000.o +obj-$(CONFIG_FB_IGA) += igafb.o +obj-$(CONFIG_FB_APOLLO) += dnfb.o +obj-$(CONFIG_FB_Q40) += q40fb.o +obj-$(CONFIG_FB_TGA) += tgafb.o +obj-$(CONFIG_FB_HP300) += hpfb.o +obj-$(CONFIG_FB_G364) += g364fb.o +obj-$(CONFIG_FB_EP93XX) += ep93xx-fb.o +obj-$(CONFIG_FB_SA1100) += sa1100fb.o +obj-$(CONFIG_FB_HIT) += hitfb.o +obj-$(CONFIG_FB_ATMEL) += atmel_lcdfb.o +obj-$(CONFIG_FB_PVR2) += pvr2fb.o +obj-$(CONFIG_FB_VOODOO1) += sstfb.o +obj-$(CONFIG_FB_ARMCLCD) += amba-clcd.o +obj-$(CONFIG_FB_GOLDFISH) += goldfishfb.o +obj-$(CONFIG_FB_68328) += 68328fb.o +obj-$(CONFIG_FB_GBE) += gbefb.o +obj-$(CONFIG_FB_CIRRUS) += cirrusfb.o +obj-$(CONFIG_FB_ASILIANT) += asiliantfb.o +obj-$(CONFIG_FB_PXA) += pxafb.o +obj-$(CONFIG_FB_PXA168) += pxa168fb.o +obj-$(CONFIG_PXA3XX_GCU) += pxa3xx-gcu.o +obj-$(CONFIG_MMP_DISP) += mmp/ +obj-$(CONFIG_FB_W100) += w100fb.o +obj-$(CONFIG_FB_TMIO) += tmiofb.o +obj-$(CONFIG_FB_AU1100) += au1100fb.o +obj-$(CONFIG_FB_AU1200) += au1200fb.o +obj-$(CONFIG_FB_VT8500) += vt8500lcdfb.o +obj-$(CONFIG_FB_WM8505) += wm8505fb.o +obj-$(CONFIG_FB_PMAG_AA) += pmag-aa-fb.o +obj-$(CONFIG_FB_PMAG_BA) += pmag-ba-fb.o +obj-$(CONFIG_FB_PMAGB_B) += pmagb-b-fb.o +obj-$(CONFIG_FB_MAXINE) += maxinefb.o +obj-$(CONFIG_FB_METRONOME) += metronomefb.o +obj-$(CONFIG_FB_BROADSHEET) += broadsheetfb.o +obj-$(CONFIG_FB_AUO_K190X) += auo_k190x.o +obj-$(CONFIG_FB_AUO_K1900) += auo_k1900fb.o +obj-$(CONFIG_FB_AUO_K1901) += auo_k1901fb.o +obj-$(CONFIG_FB_S1D13XXX) += s1d13xxxfb.o +obj-$(CONFIG_FB_SH7760) += sh7760fb.o +obj-$(CONFIG_FB_IMX) += imxfb.o +obj-$(CONFIG_FB_S3C) += s3c-fb.o +obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o +obj-$(CONFIG_FB_FSL_DIU) += fsl-diu-fb.o +obj-$(CONFIG_FB_COBALT) += cobalt_lcdfb.o +obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o +obj-$(CONFIG_FB_PS3) += ps3fb.o +obj-$(CONFIG_FB_SM501) += sm501fb.o +obj-$(CONFIG_FB_UDL) += udlfb.o +obj-$(CONFIG_FB_SMSCUFX) += smscufx.o +obj-$(CONFIG_FB_XILINX) += xilinxfb.o +obj-$(CONFIG_SH_MIPI_DSI) += sh_mipi_dsi.o +obj-$(CONFIG_FB_SH_MOBILE_HDMI) += sh_mobile_hdmi.o +obj-$(CONFIG_FB_SH_MOBILE_MERAM) += sh_mobile_meram.o +obj-$(CONFIG_FB_SH_MOBILE_LCDC) += sh_mobile_lcdcfb.o +obj-$(CONFIG_FB_OMAP) += omap/ +obj-y += omap2/ +obj-$(CONFIG_XEN_FBDEV_FRONTEND) += xen-fbfront.o +obj-$(CONFIG_FB_CARMINE) += carminefb.o +obj-$(CONFIG_FB_MB862XX) += mb862xx/ +obj-$(CONFIG_FB_MSM) += msm/ +obj-$(CONFIG_FB_NUC900) += nuc900fb.o +obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o +obj-$(CONFIG_FB_PUV3_UNIGFX) += fb-puv3.o +obj-$(CONFIG_FB_HYPERV) += hyperv_fb.o +obj-$(CONFIG_FB_OPENCORES) += ocfb.o + +# Platform or fallback drivers go here +obj-$(CONFIG_FB_UVESA) += uvesafb.o +obj-$(CONFIG_FB_VESA) += vesafb.o +obj-$(CONFIG_FB_EFI) += efifb.o +obj-$(CONFIG_FB_VGA16) += vga16fb.o +obj-$(CONFIG_FB_OF) += offb.o +obj-$(CONFIG_FB_BF537_LQ035) += bf537-lq035.o +obj-$(CONFIG_FB_BF54X_LQ043) += bf54x-lq043fb.o +obj-$(CONFIG_FB_BFIN_LQ035Q1) += bfin-lq035q1-fb.o +obj-$(CONFIG_FB_BFIN_T350MCQB) += bfin-t350mcqb-fb.o +obj-$(CONFIG_FB_BFIN_7393) += bfin_adv7393fb.o +obj-$(CONFIG_FB_MX3) += mx3fb.o +obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o +obj-$(CONFIG_FB_MXS) += mxsfb.o +obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o +obj-$(CONFIG_FB_SIMPLE) += simplefb.o + +# the test framebuffer is last +obj-$(CONFIG_FB_VIRTUAL) += vfb.o diff --git a/drivers/video/fbdev/acornfb.c b/drivers/video/fbdev/acornfb.c new file mode 100644 index 000000000000..a305caea58ee --- /dev/null +++ b/drivers/video/fbdev/acornfb.c @@ -0,0 +1,1143 @@ +/* + * linux/drivers/video/acornfb.c + * + * Copyright (C) 1998-2001 Russell King + * + * 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. + * + * Frame buffer code for Acorn platforms + * + * NOTE: Most of the modes with X!=640 will disappear shortly. + * NOTE: Startup setting of HS & VS polarity not supported. + * (do we need to support it if we're coming up in 640x480?) + * + * FIXME: (things broken by the "new improved" FBCON API) + * - Blanking 8bpp displays with VIDC + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/gfp.h> + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/mach-types.h> +#include <asm/pgtable.h> + +#include "acornfb.h" + +/* + * Default resolution. + * NOTE that it has to be supported in the table towards + * the end of this file. + */ +#define DEFAULT_XRES 640 +#define DEFAULT_YRES 480 +#define DEFAULT_BPP 4 + +/* + * define this to debug the video mode selection + */ +#undef DEBUG_MODE_SELECTION + +/* + * Translation from RISC OS monitor types to actual + * HSYNC and VSYNC frequency ranges. These are + * probably not right, but they're the best info I + * have. Allow 1% either way on the nominal for TVs. + */ +#define NR_MONTYPES 6 +static struct fb_monspecs monspecs[NR_MONTYPES] = { + { /* TV */ + .hfmin = 15469, + .hfmax = 15781, + .vfmin = 49, + .vfmax = 51, + }, { /* Multi Freq */ + .hfmin = 0, + .hfmax = 99999, + .vfmin = 0, + .vfmax = 199, + }, { /* Hi-res mono */ + .hfmin = 58608, + .hfmax = 58608, + .vfmin = 64, + .vfmax = 64, + }, { /* VGA */ + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + }, { /* SVGA */ + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 56, + .vfmax = 75, + }, { + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + } +}; + +static struct fb_info fb_info; +static struct acornfb_par current_par; +static struct vidc_timing current_vidc; + +extern unsigned int vram_size; /* set by setup.c */ + +#ifdef HAS_VIDC20 +#include <mach/acornfb.h> + +#define MAX_SIZE 2*1024*1024 + +/* VIDC20 has a different set of rules from the VIDC: + * hcr : must be multiple of 4 + * hswr : must be even + * hdsr : must be even + * hder : must be even + * vcr : >= 2, (interlace, must be odd) + * vswr : >= 1 + * vdsr : >= 1 + * vder : >= vdsr + */ +static void acornfb_set_timing(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct vidc_timing vidc; + u_int vcr, fsize; + u_int ext_ctl, dat_ctl; + u_int words_per_line; + + memset(&vidc, 0, sizeof(vidc)); + + vidc.h_sync_width = var->hsync_len - 8; + vidc.h_border_start = vidc.h_sync_width + var->left_margin + 8 - 12; + vidc.h_display_start = vidc.h_border_start + 12 - 18; + vidc.h_display_end = vidc.h_display_start + var->xres; + vidc.h_border_end = vidc.h_display_end + 18 - 12; + vidc.h_cycle = vidc.h_border_end + var->right_margin + 12 - 8; + vidc.h_interlace = vidc.h_cycle / 2; + vidc.v_sync_width = var->vsync_len - 1; + vidc.v_border_start = vidc.v_sync_width + var->upper_margin; + vidc.v_display_start = vidc.v_border_start; + vidc.v_display_end = vidc.v_display_start + var->yres; + vidc.v_border_end = vidc.v_display_end; + vidc.control = acornfb_default_control(); + + vcr = var->vsync_len + var->upper_margin + var->yres + + var->lower_margin; + + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + vidc.v_cycle = (vcr - 3) / 2; + vidc.control |= VIDC20_CTRL_INT; + } else + vidc.v_cycle = vcr - 2; + + switch (var->bits_per_pixel) { + case 1: vidc.control |= VIDC20_CTRL_1BPP; break; + case 2: vidc.control |= VIDC20_CTRL_2BPP; break; + case 4: vidc.control |= VIDC20_CTRL_4BPP; break; + default: + case 8: vidc.control |= VIDC20_CTRL_8BPP; break; + case 16: vidc.control |= VIDC20_CTRL_16BPP; break; + case 32: vidc.control |= VIDC20_CTRL_32BPP; break; + } + + acornfb_vidc20_find_rates(&vidc, var); + fsize = var->vsync_len + var->upper_margin + var->lower_margin - 1; + + if (memcmp(¤t_vidc, &vidc, sizeof(vidc))) { + current_vidc = vidc; + + vidc_writel(VIDC20_CTRL| vidc.control); + vidc_writel(0xd0000000 | vidc.pll_ctl); + vidc_writel(0x80000000 | vidc.h_cycle); + vidc_writel(0x81000000 | vidc.h_sync_width); + vidc_writel(0x82000000 | vidc.h_border_start); + vidc_writel(0x83000000 | vidc.h_display_start); + vidc_writel(0x84000000 | vidc.h_display_end); + vidc_writel(0x85000000 | vidc.h_border_end); + vidc_writel(0x86000000); + vidc_writel(0x87000000 | vidc.h_interlace); + vidc_writel(0x90000000 | vidc.v_cycle); + vidc_writel(0x91000000 | vidc.v_sync_width); + vidc_writel(0x92000000 | vidc.v_border_start); + vidc_writel(0x93000000 | vidc.v_display_start); + vidc_writel(0x94000000 | vidc.v_display_end); + vidc_writel(0x95000000 | vidc.v_border_end); + vidc_writel(0x96000000); + vidc_writel(0x97000000); + } + + iomd_writel(fsize, IOMD_FSIZE); + + ext_ctl = acornfb_default_econtrol(); + + if (var->sync & FB_SYNC_COMP_HIGH_ACT) /* should be FB_SYNC_COMP */ + ext_ctl |= VIDC20_ECTL_HS_NCSYNC | VIDC20_ECTL_VS_NCSYNC; + else { + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + ext_ctl |= VIDC20_ECTL_HS_HSYNC; + else + ext_ctl |= VIDC20_ECTL_HS_NHSYNC; + + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + ext_ctl |= VIDC20_ECTL_VS_VSYNC; + else + ext_ctl |= VIDC20_ECTL_VS_NVSYNC; + } + + vidc_writel(VIDC20_ECTL | ext_ctl); + + words_per_line = var->xres * var->bits_per_pixel / 32; + + if (current_par.using_vram && info->fix.smem_len == 2048*1024) + words_per_line /= 2; + + /* RiscPC doesn't use the VIDC's VRAM control. */ + dat_ctl = VIDC20_DCTL_VRAM_DIS | VIDC20_DCTL_SNA | words_per_line; + + /* The data bus width is dependent on both the type + * and amount of video memory. + * DRAM 32bit low + * 1MB VRAM 32bit + * 2MB VRAM 64bit + */ + if (current_par.using_vram && current_par.vram_half_sam == 2048) + dat_ctl |= VIDC20_DCTL_BUS_D63_0; + else + dat_ctl |= VIDC20_DCTL_BUS_D31_0; + + vidc_writel(VIDC20_DCTL | dat_ctl); + +#ifdef DEBUG_MODE_SELECTION + printk(KERN_DEBUG "VIDC registers for %dx%dx%d:\n", var->xres, + var->yres, var->bits_per_pixel); + printk(KERN_DEBUG " H-cycle : %d\n", vidc.h_cycle); + printk(KERN_DEBUG " H-sync-width : %d\n", vidc.h_sync_width); + printk(KERN_DEBUG " H-border-start : %d\n", vidc.h_border_start); + printk(KERN_DEBUG " H-display-start : %d\n", vidc.h_display_start); + printk(KERN_DEBUG " H-display-end : %d\n", vidc.h_display_end); + printk(KERN_DEBUG " H-border-end : %d\n", vidc.h_border_end); + printk(KERN_DEBUG " H-interlace : %d\n", vidc.h_interlace); + printk(KERN_DEBUG " V-cycle : %d\n", vidc.v_cycle); + printk(KERN_DEBUG " V-sync-width : %d\n", vidc.v_sync_width); + printk(KERN_DEBUG " V-border-start : %d\n", vidc.v_border_start); + printk(KERN_DEBUG " V-display-start : %d\n", vidc.v_display_start); + printk(KERN_DEBUG " V-display-end : %d\n", vidc.v_display_end); + printk(KERN_DEBUG " V-border-end : %d\n", vidc.v_border_end); + printk(KERN_DEBUG " Ext Ctrl (C) : 0x%08X\n", ext_ctl); + printk(KERN_DEBUG " PLL Ctrl (D) : 0x%08X\n", vidc.pll_ctl); + printk(KERN_DEBUG " Ctrl (E) : 0x%08X\n", vidc.control); + printk(KERN_DEBUG " Data Ctrl (F) : 0x%08X\n", dat_ctl); + printk(KERN_DEBUG " Fsize : 0x%08X\n", fsize); +#endif +} + +/* + * We have to take note of the VIDC20's 16-bit palette here. + * The VIDC20 looks up a 16 bit pixel as follows: + * + * bits 111111 + * 5432109876543210 + * red ++++++++ (8 bits, 7 to 0) + * green ++++++++ (8 bits, 11 to 4) + * blue ++++++++ (8 bits, 15 to 8) + * + * We use a pixel which looks like: + * + * bits 111111 + * 5432109876543210 + * red +++++ (5 bits, 4 to 0) + * green +++++ (5 bits, 9 to 5) + * blue +++++ (5 bits, 14 to 10) + */ +static int +acornfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + union palette pal; + + if (regno >= current_par.palette_size) + return 1; + + if (regno < 16 && info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + u32 pseudo_val; + + pseudo_val = regno << info->var.red.offset; + pseudo_val |= regno << info->var.green.offset; + pseudo_val |= regno << info->var.blue.offset; + + ((u32 *)info->pseudo_palette)[regno] = pseudo_val; + } + + pal.p = 0; + pal.vidc20.red = red >> 8; + pal.vidc20.green = green >> 8; + pal.vidc20.blue = blue >> 8; + + current_par.palette[regno] = pal; + + if (info->var.bits_per_pixel == 16) { + int i; + + pal.p = 0; + vidc_writel(0x10000000); + for (i = 0; i < 256; i += 1) { + pal.vidc20.red = current_par.palette[ i & 31].vidc20.red; + pal.vidc20.green = current_par.palette[(i >> 1) & 31].vidc20.green; + pal.vidc20.blue = current_par.palette[(i >> 2) & 31].vidc20.blue; + vidc_writel(pal.p); + /* Palette register pointer auto-increments */ + } + } else { + vidc_writel(0x10000000 | regno); + vidc_writel(pal.p); + } + + return 0; +} +#endif + +/* + * Before selecting the timing parameters, adjust + * the resolution to fit the rules. + */ +static int +acornfb_adjust_timing(struct fb_info *info, struct fb_var_screeninfo *var, u_int fontht) +{ + u_int font_line_len, sam_size, min_size, size, nr_y; + + /* xres must be even */ + var->xres = (var->xres + 1) & ~1; + + /* + * We don't allow xres_virtual to differ from xres + */ + var->xres_virtual = var->xres; + var->xoffset = 0; + + if (current_par.using_vram) + sam_size = current_par.vram_half_sam * 2; + else + sam_size = 16; + + /* + * Now, find a value for yres_virtual which allows + * us to do ywrap scrolling. The value of + * yres_virtual must be such that the end of the + * displayable frame buffer must be aligned with + * the start of a font line. + */ + font_line_len = var->xres * var->bits_per_pixel * fontht / 8; + min_size = var->xres * var->yres * var->bits_per_pixel / 8; + + /* + * If minimum screen size is greater than that we have + * available, reject it. + */ + if (min_size > info->fix.smem_len) + return -EINVAL; + + /* Find int 'y', such that y * fll == s * sam < maxsize + * y = s * sam / fll; s = maxsize / sam + */ + for (size = info->fix.smem_len; + nr_y = size / font_line_len, min_size <= size; + size -= sam_size) { + if (nr_y * font_line_len == size) + break; + } + nr_y *= fontht; + + if (var->accel_flags & FB_ACCELF_TEXT) { + if (min_size > size) { + /* + * failed, use ypan + */ + size = info->fix.smem_len; + var->yres_virtual = size / (font_line_len / fontht); + } else + var->yres_virtual = nr_y; + } else if (var->yres_virtual > nr_y) + var->yres_virtual = nr_y; + + current_par.screen_end = info->fix.smem_start + size; + + /* + * Fix yres & yoffset if needed. + */ + if (var->yres > var->yres_virtual) + var->yres = var->yres_virtual; + + if (var->vmode & FB_VMODE_YWRAP) { + if (var->yoffset > var->yres_virtual) + var->yoffset = var->yres_virtual; + } else { + if (var->yoffset + var->yres > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + } + + /* hsync_len must be even */ + var->hsync_len = (var->hsync_len + 1) & ~1; + +#if defined(HAS_VIDC20) + /* left_margin must be even */ + if (var->left_margin & 1) { + var->left_margin += 1; + var->right_margin -= 1; + } + + /* right_margin must be even */ + if (var->right_margin & 1) + var->right_margin += 1; +#endif + + if (var->vsync_len < 1) + var->vsync_len = 1; + + return 0; +} + +static int +acornfb_validate_timing(struct fb_var_screeninfo *var, + struct fb_monspecs *monspecs) +{ + unsigned long hs, vs; + + /* + * hs(Hz) = 10^12 / (pixclock * xtotal) + * vs(Hz) = hs(Hz) / ytotal + * + * No need to do long long divisions or anything + * like that if you factor it correctly + */ + hs = 1953125000 / var->pixclock; + hs = hs * 512 / + (var->xres + var->left_margin + var->right_margin + var->hsync_len); + vs = hs / + (var->yres + var->upper_margin + var->lower_margin + var->vsync_len); + + return (vs >= monspecs->vfmin && vs <= monspecs->vfmax && + hs >= monspecs->hfmin && hs <= monspecs->hfmax) ? 0 : -EINVAL; +} + +static inline void +acornfb_update_dma(struct fb_info *info, struct fb_var_screeninfo *var) +{ + u_int off = var->yoffset * info->fix.line_length; + +#if defined(HAS_MEMC) + memc_write(VDMA_INIT, off >> 2); +#elif defined(HAS_IOMD) + iomd_writel(info->fix.smem_start + off, IOMD_VIDINIT); +#endif +} + +static int +acornfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u_int fontht; + int err; + + /* + * FIXME: Find the font height + */ + fontht = 8; + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + switch (var->bits_per_pixel) { + case 1: case 2: case 4: case 8: + var->red.offset = 0; + var->red.length = var->bits_per_pixel; + var->green = var->red; + var->blue = var->red; + var->transp.offset = 0; + var->transp.length = 0; + break; + +#ifdef HAS_VIDC20 + case 16: + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 10; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + break; + + case 32: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 4; + break; +#endif + default: + return -EINVAL; + } + + /* + * Check to see if the pixel rate is valid. + */ + if (!acornfb_valid_pixrate(var)) + return -EINVAL; + + /* + * Validate and adjust the resolution to + * match the video generator hardware. + */ + err = acornfb_adjust_timing(info, var, fontht); + if (err) + return err; + + /* + * Validate the timing against the + * monitor hardware. + */ + return acornfb_validate_timing(var, &info->monspecs); +} + +static int acornfb_set_par(struct fb_info *info) +{ + switch (info->var.bits_per_pixel) { + case 1: + current_par.palette_size = 2; + info->fix.visual = FB_VISUAL_MONO10; + break; + case 2: + current_par.palette_size = 4; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + case 4: + current_par.palette_size = 16; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + case 8: + current_par.palette_size = VIDC_PALETTE_SIZE; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; +#ifdef HAS_VIDC20 + case 16: + current_par.palette_size = 32; + info->fix.visual = FB_VISUAL_DIRECTCOLOR; + break; + case 32: + current_par.palette_size = VIDC_PALETTE_SIZE; + info->fix.visual = FB_VISUAL_DIRECTCOLOR; + break; +#endif + default: + BUG(); + } + + info->fix.line_length = (info->var.xres * info->var.bits_per_pixel) / 8; + +#if defined(HAS_MEMC) + { + unsigned long size = info->fix.smem_len - VDMA_XFERSIZE; + + memc_write(VDMA_START, 0); + memc_write(VDMA_END, size >> 2); + } +#elif defined(HAS_IOMD) + { + unsigned long start, size; + u_int control; + + start = info->fix.smem_start; + size = current_par.screen_end; + + if (current_par.using_vram) { + size -= current_par.vram_half_sam; + control = DMA_CR_E | (current_par.vram_half_sam / 256); + } else { + size -= 16; + control = DMA_CR_E | DMA_CR_D | 16; + } + + iomd_writel(start, IOMD_VIDSTART); + iomd_writel(size, IOMD_VIDEND); + iomd_writel(control, IOMD_VIDCR); + } +#endif + + acornfb_update_dma(info, &info->var); + acornfb_set_timing(info); + + return 0; +} + +static int +acornfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u_int y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += info->var.yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + acornfb_update_dma(info, var); + + return 0; +} + +static struct fb_ops acornfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = acornfb_check_var, + .fb_set_par = acornfb_set_par, + .fb_setcolreg = acornfb_setcolreg, + .fb_pan_display = acornfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* + * Everything after here is initialisation!!! + */ +static struct fb_videomode modedb[] = { + { /* 320x256 @ 50Hz */ + NULL, 50, 320, 256, 125000, 92, 62, 35, 19, 38, 2, + FB_SYNC_COMP_HIGH_ACT, + FB_VMODE_NONINTERLACED + }, { /* 640x250 @ 50Hz, 15.6 kHz hsync */ + NULL, 50, 640, 250, 62500, 185, 123, 38, 21, 76, 3, + 0, + FB_VMODE_NONINTERLACED + }, { /* 640x256 @ 50Hz, 15.6 kHz hsync */ + NULL, 50, 640, 256, 62500, 185, 123, 35, 18, 76, 3, + 0, + FB_VMODE_NONINTERLACED + }, { /* 640x512 @ 50Hz, 26.8 kHz hsync */ + NULL, 50, 640, 512, 41667, 113, 87, 18, 1, 56, 3, + 0, + FB_VMODE_NONINTERLACED + }, { /* 640x250 @ 70Hz, 31.5 kHz hsync */ + NULL, 70, 640, 250, 39722, 48, 16, 109, 88, 96, 2, + 0, + FB_VMODE_NONINTERLACED + }, { /* 640x256 @ 70Hz, 31.5 kHz hsync */ + NULL, 70, 640, 256, 39722, 48, 16, 106, 85, 96, 2, + 0, + FB_VMODE_NONINTERLACED + }, { /* 640x352 @ 70Hz, 31.5 kHz hsync */ + NULL, 70, 640, 352, 39722, 48, 16, 58, 37, 96, 2, + 0, + FB_VMODE_NONINTERLACED + }, { /* 640x480 @ 60Hz, 31.5 kHz hsync */ + NULL, 60, 640, 480, 39722, 48, 16, 32, 11, 96, 2, + 0, + FB_VMODE_NONINTERLACED + }, { /* 800x600 @ 56Hz, 35.2 kHz hsync */ + NULL, 56, 800, 600, 27778, 101, 23, 22, 1, 100, 2, + 0, + FB_VMODE_NONINTERLACED + }, { /* 896x352 @ 60Hz, 21.8 kHz hsync */ + NULL, 60, 896, 352, 41667, 59, 27, 9, 0, 118, 3, + 0, + FB_VMODE_NONINTERLACED + }, { /* 1024x 768 @ 60Hz, 48.4 kHz hsync */ + NULL, 60, 1024, 768, 15385, 160, 24, 29, 3, 136, 6, + 0, + FB_VMODE_NONINTERLACED + }, { /* 1280x1024 @ 60Hz, 63.8 kHz hsync */ + NULL, 60, 1280, 1024, 9090, 186, 96, 38, 1, 160, 3, + 0, + FB_VMODE_NONINTERLACED + } +}; + +static struct fb_videomode acornfb_default_mode = { + .name = NULL, + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 39722, + .left_margin = 56, + .right_margin = 16, + .upper_margin = 34, + .lower_margin = 9, + .hsync_len = 88, + .vsync_len = 2, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED +}; + +static void acornfb_init_fbinfo(void) +{ + static int first = 1; + + if (!first) + return; + first = 0; + + fb_info.fbops = &acornfb_ops; + fb_info.flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + fb_info.pseudo_palette = current_par.pseudo_palette; + + strcpy(fb_info.fix.id, "Acorn"); + fb_info.fix.type = FB_TYPE_PACKED_PIXELS; + fb_info.fix.type_aux = 0; + fb_info.fix.xpanstep = 0; + fb_info.fix.ypanstep = 1; + fb_info.fix.ywrapstep = 1; + fb_info.fix.line_length = 0; + fb_info.fix.accel = FB_ACCEL_NONE; + + /* + * setup initial parameters + */ + memset(&fb_info.var, 0, sizeof(fb_info.var)); + +#if defined(HAS_VIDC20) + fb_info.var.red.length = 8; + fb_info.var.transp.length = 4; +#endif + fb_info.var.green = fb_info.var.red; + fb_info.var.blue = fb_info.var.red; + fb_info.var.nonstd = 0; + fb_info.var.activate = FB_ACTIVATE_NOW; + fb_info.var.height = -1; + fb_info.var.width = -1; + fb_info.var.vmode = FB_VMODE_NONINTERLACED; + fb_info.var.accel_flags = FB_ACCELF_TEXT; + + current_par.dram_size = 0; + current_par.montype = -1; + current_par.dpms = 0; +} + +/* + * setup acornfb options: + * + * mon:hmin-hmax:vmin-vmax:dpms:width:height + * Set monitor parameters: + * hmin = horizontal minimum frequency (Hz) + * hmax = horizontal maximum frequency (Hz) (optional) + * vmin = vertical minimum frequency (Hz) + * vmax = vertical maximum frequency (Hz) (optional) + * dpms = DPMS supported? (optional) + * width = width of picture in mm. (optional) + * height = height of picture in mm. (optional) + * + * montype:type + * Set RISC-OS style monitor type: + * 0 (or tv) - TV frequency + * 1 (or multi) - Multi frequency + * 2 (or hires) - Hi-res monochrome + * 3 (or vga) - VGA + * 4 (or svga) - SVGA + * auto, or option missing + * - try hardware detect + * + * dram:size + * Set the amount of DRAM to use for the frame buffer + * (even if you have VRAM). + * size can optionally be followed by 'M' or 'K' for + * MB or KB respectively. + */ +static void acornfb_parse_mon(char *opt) +{ + char *p = opt; + + current_par.montype = -2; + + fb_info.monspecs.hfmin = simple_strtoul(p, &p, 0); + if (*p == '-') + fb_info.monspecs.hfmax = simple_strtoul(p + 1, &p, 0); + else + fb_info.monspecs.hfmax = fb_info.monspecs.hfmin; + + if (*p != ':') + goto bad; + + fb_info.monspecs.vfmin = simple_strtoul(p + 1, &p, 0); + if (*p == '-') + fb_info.monspecs.vfmax = simple_strtoul(p + 1, &p, 0); + else + fb_info.monspecs.vfmax = fb_info.monspecs.vfmin; + + if (*p != ':') + goto check_values; + + fb_info.monspecs.dpms = simple_strtoul(p + 1, &p, 0); + + if (*p != ':') + goto check_values; + + fb_info.var.width = simple_strtoul(p + 1, &p, 0); + + if (*p != ':') + goto check_values; + + fb_info.var.height = simple_strtoul(p + 1, NULL, 0); + +check_values: + if (fb_info.monspecs.hfmax < fb_info.monspecs.hfmin || + fb_info.monspecs.vfmax < fb_info.monspecs.vfmin) + goto bad; + return; + +bad: + printk(KERN_ERR "Acornfb: bad monitor settings: %s\n", opt); + current_par.montype = -1; +} + +static void acornfb_parse_montype(char *opt) +{ + current_par.montype = -2; + + if (strncmp(opt, "tv", 2) == 0) { + opt += 2; + current_par.montype = 0; + } else if (strncmp(opt, "multi", 5) == 0) { + opt += 5; + current_par.montype = 1; + } else if (strncmp(opt, "hires", 5) == 0) { + opt += 5; + current_par.montype = 2; + } else if (strncmp(opt, "vga", 3) == 0) { + opt += 3; + current_par.montype = 3; + } else if (strncmp(opt, "svga", 4) == 0) { + opt += 4; + current_par.montype = 4; + } else if (strncmp(opt, "auto", 4) == 0) { + opt += 4; + current_par.montype = -1; + } else if (isdigit(*opt)) + current_par.montype = simple_strtoul(opt, &opt, 0); + + if (current_par.montype == -2 || + current_par.montype > NR_MONTYPES) { + printk(KERN_ERR "acornfb: unknown monitor type: %s\n", + opt); + current_par.montype = -1; + } else + if (opt && *opt) { + if (strcmp(opt, ",dpms") == 0) + current_par.dpms = 1; + else + printk(KERN_ERR + "acornfb: unknown monitor option: %s\n", + opt); + } +} + +static void acornfb_parse_dram(char *opt) +{ + unsigned int size; + + size = simple_strtoul(opt, &opt, 0); + + if (opt) { + switch (*opt) { + case 'M': + case 'm': + size *= 1024; + case 'K': + case 'k': + size *= 1024; + default: + break; + } + } + + current_par.dram_size = size; +} + +static struct options { + char *name; + void (*parse)(char *opt); +} opt_table[] = { + { "mon", acornfb_parse_mon }, + { "montype", acornfb_parse_montype }, + { "dram", acornfb_parse_dram }, + { NULL, NULL } +}; + +static int acornfb_setup(char *options) +{ + struct options *optp; + char *opt; + + if (!options || !*options) + return 0; + + acornfb_init_fbinfo(); + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + for (optp = opt_table; optp->name; optp++) { + int optlen; + + optlen = strlen(optp->name); + + if (strncmp(opt, optp->name, optlen) == 0 && + opt[optlen] == ':') { + optp->parse(opt + optlen + 1); + break; + } + } + + if (!optp->name) + printk(KERN_ERR "acornfb: unknown parameter: %s\n", + opt); + } + return 0; +} + +/* + * Detect type of monitor connected + * For now, we just assume SVGA + */ +static int acornfb_detect_monitortype(void) +{ + return 4; +} + +/* + * This enables the unused memory to be freed on older Acorn machines. + * We are freeing memory on behalf of the architecture initialisation + * code here. + */ +static inline void +free_unused_pages(unsigned int virtual_start, unsigned int virtual_end) +{ + int mb_freed = 0; + + /* + * Align addresses + */ + virtual_start = PAGE_ALIGN(virtual_start); + virtual_end = PAGE_ALIGN(virtual_end); + + while (virtual_start < virtual_end) { + struct page *page; + + /* + * Clear page reserved bit, + * set count to 1, and free + * the page. + */ + page = virt_to_page(virtual_start); + __free_reserved_page(page); + + virtual_start += PAGE_SIZE; + mb_freed += PAGE_SIZE / 1024; + } + + printk("acornfb: freed %dK memory\n", mb_freed); +} + +static int acornfb_probe(struct platform_device *dev) +{ + unsigned long size; + u_int h_sync, v_sync; + int rc, i; + char *option = NULL; + + if (fb_get_options("acornfb", &option)) + return -ENODEV; + acornfb_setup(option); + + acornfb_init_fbinfo(); + + current_par.dev = &dev->dev; + + if (current_par.montype == -1) + current_par.montype = acornfb_detect_monitortype(); + + if (current_par.montype == -1 || current_par.montype > NR_MONTYPES) + current_par.montype = 4; + + if (current_par.montype >= 0) { + fb_info.monspecs = monspecs[current_par.montype]; + fb_info.monspecs.dpms = current_par.dpms; + } + + /* + * Try to select a suitable default mode + */ + for (i = 0; i < ARRAY_SIZE(modedb); i++) { + unsigned long hs; + + hs = modedb[i].refresh * + (modedb[i].yres + modedb[i].upper_margin + + modedb[i].lower_margin + modedb[i].vsync_len); + if (modedb[i].xres == DEFAULT_XRES && + modedb[i].yres == DEFAULT_YRES && + modedb[i].refresh >= fb_info.monspecs.vfmin && + modedb[i].refresh <= fb_info.monspecs.vfmax && + hs >= fb_info.monspecs.hfmin && + hs <= fb_info.monspecs.hfmax) { + acornfb_default_mode = modedb[i]; + break; + } + } + + fb_info.screen_base = (char *)SCREEN_BASE; + fb_info.fix.smem_start = SCREEN_START; + current_par.using_vram = 0; + + /* + * If vram_size is set, we are using VRAM in + * a Risc PC. However, if the user has specified + * an amount of DRAM then use that instead. + */ + if (vram_size && !current_par.dram_size) { + size = vram_size; + current_par.vram_half_sam = vram_size / 1024; + current_par.using_vram = 1; + } else if (current_par.dram_size) + size = current_par.dram_size; + else + size = MAX_SIZE; + + /* + * Limit maximum screen size. + */ + if (size > MAX_SIZE) + size = MAX_SIZE; + + size = PAGE_ALIGN(size); + +#if defined(HAS_VIDC20) + if (!current_par.using_vram) { + dma_addr_t handle; + void *base; + + /* + * RiscPC needs to allocate the DRAM memory + * for the framebuffer if we are not using + * VRAM. + */ + base = dma_alloc_writecombine(current_par.dev, size, &handle, + GFP_KERNEL); + if (base == NULL) { + printk(KERN_ERR "acornfb: unable to allocate screen " + "memory\n"); + return -ENOMEM; + } + + fb_info.screen_base = base; + fb_info.fix.smem_start = handle; + } +#endif + fb_info.fix.smem_len = size; + current_par.palette_size = VIDC_PALETTE_SIZE; + + /* + * Lookup the timing for this resolution. If we can't + * find it, then we can't restore it if we change + * the resolution, so we disable this feature. + */ + do { + rc = fb_find_mode(&fb_info.var, &fb_info, NULL, modedb, + ARRAY_SIZE(modedb), + &acornfb_default_mode, DEFAULT_BPP); + /* + * If we found an exact match, all ok. + */ + if (rc == 1) + break; + + rc = fb_find_mode(&fb_info.var, &fb_info, NULL, NULL, 0, + &acornfb_default_mode, DEFAULT_BPP); + /* + * If we found an exact match, all ok. + */ + if (rc == 1) + break; + + rc = fb_find_mode(&fb_info.var, &fb_info, NULL, modedb, + ARRAY_SIZE(modedb), + &acornfb_default_mode, DEFAULT_BPP); + if (rc) + break; + + rc = fb_find_mode(&fb_info.var, &fb_info, NULL, NULL, 0, + &acornfb_default_mode, DEFAULT_BPP); + } while (0); + + /* + * If we didn't find an exact match, try the + * generic database. + */ + if (rc == 0) { + printk("Acornfb: no valid mode found\n"); + return -EINVAL; + } + + h_sync = 1953125000 / fb_info.var.pixclock; + h_sync = h_sync * 512 / (fb_info.var.xres + fb_info.var.left_margin + + fb_info.var.right_margin + fb_info.var.hsync_len); + v_sync = h_sync / (fb_info.var.yres + fb_info.var.upper_margin + + fb_info.var.lower_margin + fb_info.var.vsync_len); + + printk(KERN_INFO "Acornfb: %dkB %cRAM, %s, using %dx%d, " + "%d.%03dkHz, %dHz\n", + fb_info.fix.smem_len / 1024, + current_par.using_vram ? 'V' : 'D', + VIDC_NAME, fb_info.var.xres, fb_info.var.yres, + h_sync / 1000, h_sync % 1000, v_sync); + + printk(KERN_INFO "Acornfb: Monitor: %d.%03d-%d.%03dkHz, %d-%dHz%s\n", + fb_info.monspecs.hfmin / 1000, fb_info.monspecs.hfmin % 1000, + fb_info.monspecs.hfmax / 1000, fb_info.monspecs.hfmax % 1000, + fb_info.monspecs.vfmin, fb_info.monspecs.vfmax, + fb_info.monspecs.dpms ? ", DPMS" : ""); + + if (fb_set_var(&fb_info, &fb_info.var)) + printk(KERN_ERR "Acornfb: unable to set display parameters\n"); + + if (register_framebuffer(&fb_info) < 0) + return -EINVAL; + return 0; +} + +static struct platform_driver acornfb_driver = { + .probe = acornfb_probe, + .driver = { + .name = "acornfb", + }, +}; + +static int __init acornfb_init(void) +{ + return platform_driver_register(&acornfb_driver); +} + +module_init(acornfb_init); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("VIDC 1/1a/20 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/acornfb.h b/drivers/video/fbdev/acornfb.h new file mode 100644 index 000000000000..175c8ff3367c --- /dev/null +++ b/drivers/video/fbdev/acornfb.h @@ -0,0 +1,169 @@ +/* + * linux/drivers/video/acornfb.h + * + * Copyright (C) 1998,1999 Russell King + * + * 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. + * + * Frame buffer code for Acorn platforms + */ +#if defined(HAS_VIDC20) +#include <asm/hardware/iomd.h> +#define VIDC_PALETTE_SIZE 256 +#define VIDC_NAME "VIDC20" +#endif + +#define EXTEND8(x) ((x)|(x)<<8) +#define EXTEND4(x) ((x)|(x)<<4|(x)<<8|(x)<<12) + +struct vidc20_palette { + u_int red:8; + u_int green:8; + u_int blue:8; + u_int ext:4; + u_int unused:4; +}; + +struct vidc_palette { + u_int red:4; + u_int green:4; + u_int blue:4; + u_int trans:1; + u_int sbz1:13; + u_int reg:4; + u_int sbz2:2; +}; + +union palette { + struct vidc20_palette vidc20; + struct vidc_palette vidc; + u_int p; +}; + +struct acornfb_par { + struct device *dev; + unsigned long screen_end; + unsigned int dram_size; + unsigned int vram_half_sam; + unsigned int palette_size; + signed int montype; + unsigned int using_vram : 1; + unsigned int dpms : 1; + + union palette palette[VIDC_PALETTE_SIZE]; + + u32 pseudo_palette[16]; +}; + +struct vidc_timing { + u_int h_cycle; + u_int h_sync_width; + u_int h_border_start; + u_int h_display_start; + u_int h_display_end; + u_int h_border_end; + u_int h_interlace; + + u_int v_cycle; + u_int v_sync_width; + u_int v_border_start; + u_int v_display_start; + u_int v_display_end; + u_int v_border_end; + + u_int control; + + /* VIDC20 only */ + u_int pll_ctl; +}; + +struct modey_params { + u_int y_res; + u_int u_margin; + u_int b_margin; + u_int vsync_len; + u_int vf; +}; + +struct modex_params { + u_int x_res; + u_int l_margin; + u_int r_margin; + u_int hsync_len; + u_int clock; + u_int hf; + const struct modey_params *modey; +}; + +#ifdef HAS_VIDC20 +/* + * VIDC20 registers + */ +#define VIDC20_CTRL 0xe0000000 +#define VIDC20_CTRL_PIX_VCLK (0 << 0) +#define VIDC20_CTRL_PIX_HCLK (1 << 0) +#define VIDC20_CTRL_PIX_RCLK (2 << 0) +#define VIDC20_CTRL_PIX_CK (0 << 2) +#define VIDC20_CTRL_PIX_CK2 (1 << 2) +#define VIDC20_CTRL_PIX_CK3 (2 << 2) +#define VIDC20_CTRL_PIX_CK4 (3 << 2) +#define VIDC20_CTRL_PIX_CK5 (4 << 2) +#define VIDC20_CTRL_PIX_CK6 (5 << 2) +#define VIDC20_CTRL_PIX_CK7 (6 << 2) +#define VIDC20_CTRL_PIX_CK8 (7 << 2) +#define VIDC20_CTRL_1BPP (0 << 5) +#define VIDC20_CTRL_2BPP (1 << 5) +#define VIDC20_CTRL_4BPP (2 << 5) +#define VIDC20_CTRL_8BPP (3 << 5) +#define VIDC20_CTRL_16BPP (4 << 5) +#define VIDC20_CTRL_32BPP (6 << 5) +#define VIDC20_CTRL_FIFO_NS (0 << 8) +#define VIDC20_CTRL_FIFO_4 (1 << 8) +#define VIDC20_CTRL_FIFO_8 (2 << 8) +#define VIDC20_CTRL_FIFO_12 (3 << 8) +#define VIDC20_CTRL_FIFO_16 (4 << 8) +#define VIDC20_CTRL_FIFO_20 (5 << 8) +#define VIDC20_CTRL_FIFO_24 (6 << 8) +#define VIDC20_CTRL_FIFO_28 (7 << 8) +#define VIDC20_CTRL_INT (1 << 12) +#define VIDC20_CTRL_DUP (1 << 13) +#define VIDC20_CTRL_PDOWN (1 << 14) + +#define VIDC20_ECTL 0xc0000000 +#define VIDC20_ECTL_REG(x) ((x) & 0xf3) +#define VIDC20_ECTL_ECK (1 << 2) +#define VIDC20_ECTL_REDPED (1 << 8) +#define VIDC20_ECTL_GREENPED (1 << 9) +#define VIDC20_ECTL_BLUEPED (1 << 10) +#define VIDC20_ECTL_DAC (1 << 12) +#define VIDC20_ECTL_LCDGS (1 << 13) +#define VIDC20_ECTL_HRM (1 << 14) + +#define VIDC20_ECTL_HS_MASK (3 << 16) +#define VIDC20_ECTL_HS_HSYNC (0 << 16) +#define VIDC20_ECTL_HS_NHSYNC (1 << 16) +#define VIDC20_ECTL_HS_CSYNC (2 << 16) +#define VIDC20_ECTL_HS_NCSYNC (3 << 16) + +#define VIDC20_ECTL_VS_MASK (3 << 18) +#define VIDC20_ECTL_VS_VSYNC (0 << 18) +#define VIDC20_ECTL_VS_NVSYNC (1 << 18) +#define VIDC20_ECTL_VS_CSYNC (2 << 18) +#define VIDC20_ECTL_VS_NCSYNC (3 << 18) + +#define VIDC20_DCTL 0xf0000000 +/* 0-9 = number of words in scanline */ +#define VIDC20_DCTL_SNA (1 << 12) +#define VIDC20_DCTL_HDIS (1 << 13) +#define VIDC20_DCTL_BUS_NS (0 << 16) +#define VIDC20_DCTL_BUS_D31_0 (1 << 16) +#define VIDC20_DCTL_BUS_D63_32 (2 << 16) +#define VIDC20_DCTL_BUS_D63_0 (3 << 16) +#define VIDC20_DCTL_VRAM_DIS (0 << 18) +#define VIDC20_DCTL_VRAM_PXCLK (1 << 18) +#define VIDC20_DCTL_VRAM_PXCLK2 (2 << 18) +#define VIDC20_DCTL_VRAM_PXCLK4 (3 << 18) + +#endif diff --git a/drivers/video/fbdev/amba-clcd.c b/drivers/video/fbdev/amba-clcd.c new file mode 100644 index 000000000000..14d6b3793e0a --- /dev/null +++ b/drivers/video/fbdev/amba-clcd.c @@ -0,0 +1,656 @@ +/* + * linux/drivers/video/amba-clcd.c + * + * Copyright (C) 2001 ARM Limited, by David A Rusling + * Updated to 2.5, Deep Blue Solutions Ltd. + * + * 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. + * + * ARM PrimeCell PL110 Color LCD Controller + */ +#include <linux/dma-mapping.h> +#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/hardirq.h> + +#include <asm/sizes.h> + +#define to_clcd(info) container_of(info, struct clcd_fb, fb) + +/* This is limited to 16 characters when displayed by X startup */ +static const char *clcd_name = "CLCD FB"; + +/* + * Unfortunately, the enable/disable functions may be called either from + * process or IRQ context, and we _need_ to delay. This is _not_ good. + */ +static inline void clcdfb_sleep(unsigned int ms) +{ + if (in_atomic()) { + mdelay(ms); + } else { + msleep(ms); + } +} + +static inline void clcdfb_set_start(struct clcd_fb *fb) +{ + unsigned long ustart = fb->fb.fix.smem_start; + unsigned long lstart; + + ustart += fb->fb.var.yoffset * fb->fb.fix.line_length; + lstart = ustart + fb->fb.var.yres * fb->fb.fix.line_length / 2; + + writel(ustart, fb->regs + CLCD_UBAS); + writel(lstart, fb->regs + CLCD_LBAS); +} + +static void clcdfb_disable(struct clcd_fb *fb) +{ + u32 val; + + if (fb->board->disable) + fb->board->disable(fb); + + val = readl(fb->regs + fb->off_cntl); + if (val & CNTL_LCDPWR) { + val &= ~CNTL_LCDPWR; + writel(val, fb->regs + fb->off_cntl); + + clcdfb_sleep(20); + } + if (val & CNTL_LCDEN) { + val &= ~CNTL_LCDEN; + writel(val, fb->regs + fb->off_cntl); + } + + /* + * Disable CLCD clock source. + */ + if (fb->clk_enabled) { + fb->clk_enabled = false; + clk_disable(fb->clk); + } +} + +static void clcdfb_enable(struct clcd_fb *fb, u32 cntl) +{ + /* + * Enable the CLCD clock source. + */ + if (!fb->clk_enabled) { + fb->clk_enabled = true; + clk_enable(fb->clk); + } + + /* + * Bring up by first enabling.. + */ + cntl |= CNTL_LCDEN; + writel(cntl, fb->regs + fb->off_cntl); + + clcdfb_sleep(20); + + /* + * and now apply power. + */ + cntl |= CNTL_LCDPWR; + writel(cntl, fb->regs + fb->off_cntl); + + /* + * finally, enable the interface. + */ + if (fb->board->enable) + fb->board->enable(fb); +} + +static int +clcdfb_set_bitfields(struct clcd_fb *fb, struct fb_var_screeninfo *var) +{ + u32 caps; + int ret = 0; + + if (fb->panel->caps && fb->board->caps) + caps = fb->panel->caps & fb->board->caps; + else { + /* Old way of specifying what can be used */ + caps = fb->panel->cntl & CNTL_BGR ? + CLCD_CAP_BGR : CLCD_CAP_RGB; + /* But mask out 444 modes as they weren't supported */ + caps &= ~CLCD_CAP_444; + } + + /* Only TFT panels can do RGB888/BGR888 */ + if (!(fb->panel->cntl & CNTL_LCDTFT)) + caps &= ~CLCD_CAP_888; + + memset(&var->transp, 0, sizeof(var->transp)); + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + + switch (var->bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + /* If we can't do 5551, reject */ + caps &= CLCD_CAP_5551; + if (!caps) { + ret = -EINVAL; + break; + } + + var->red.length = var->bits_per_pixel; + var->red.offset = 0; + var->green.length = var->bits_per_pixel; + var->green.offset = 0; + var->blue.length = var->bits_per_pixel; + var->blue.offset = 0; + break; + + case 16: + /* If we can't do 444, 5551 or 565, reject */ + if (!(caps & (CLCD_CAP_444 | CLCD_CAP_5551 | CLCD_CAP_565))) { + ret = -EINVAL; + break; + } + + /* + * Green length can be 4, 5 or 6 depending whether + * we're operating in 444, 5551 or 565 mode. + */ + if (var->green.length == 4 && caps & CLCD_CAP_444) + caps &= CLCD_CAP_444; + if (var->green.length == 5 && caps & CLCD_CAP_5551) + caps &= CLCD_CAP_5551; + else if (var->green.length == 6 && caps & CLCD_CAP_565) + caps &= CLCD_CAP_565; + else { + /* + * PL110 officially only supports RGB555, + * but may be wired up to allow RGB565. + */ + if (caps & CLCD_CAP_565) { + var->green.length = 6; + caps &= CLCD_CAP_565; + } else if (caps & CLCD_CAP_5551) { + var->green.length = 5; + caps &= CLCD_CAP_5551; + } else { + var->green.length = 4; + caps &= CLCD_CAP_444; + } + } + + if (var->green.length >= 5) { + var->red.length = 5; + var->blue.length = 5; + } else { + var->red.length = 4; + var->blue.length = 4; + } + break; + case 32: + /* If we can't do 888, reject */ + caps &= CLCD_CAP_888; + if (!caps) { + ret = -EINVAL; + break; + } + + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + default: + ret = -EINVAL; + break; + } + + /* + * >= 16bpp displays have separate colour component bitfields + * encoded in the pixel data. Calculate their position from + * the bitfield length defined above. + */ + if (ret == 0 && var->bits_per_pixel >= 16) { + bool bgr, rgb; + + bgr = caps & CLCD_CAP_BGR && var->blue.offset == 0; + rgb = caps & CLCD_CAP_RGB && var->red.offset == 0; + + if (!bgr && !rgb) + /* + * The requested format was not possible, try just + * our capabilities. One of BGR or RGB must be + * supported. + */ + bgr = caps & CLCD_CAP_BGR; + + if (bgr) { + var->blue.offset = 0; + var->green.offset = var->blue.offset + var->blue.length; + var->red.offset = var->green.offset + var->green.length; + } else { + var->red.offset = 0; + var->green.offset = var->red.offset + var->red.length; + var->blue.offset = var->green.offset + var->green.length; + } + } + + return ret; +} + +static int clcdfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct clcd_fb *fb = to_clcd(info); + int ret = -EINVAL; + + if (fb->board->check) + ret = fb->board->check(fb, var); + + if (ret == 0 && + var->xres_virtual * var->bits_per_pixel / 8 * + var->yres_virtual > fb->fb.fix.smem_len) + ret = -EINVAL; + + if (ret == 0) + ret = clcdfb_set_bitfields(fb, var); + + return ret; +} + +static int clcdfb_set_par(struct fb_info *info) +{ + struct clcd_fb *fb = to_clcd(info); + struct clcd_regs regs; + + fb->fb.fix.line_length = fb->fb.var.xres_virtual * + fb->fb.var.bits_per_pixel / 8; + + if (fb->fb.var.bits_per_pixel <= 8) + fb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + fb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + + fb->board->decode(fb, ®s); + + clcdfb_disable(fb); + + writel(regs.tim0, fb->regs + CLCD_TIM0); + writel(regs.tim1, fb->regs + CLCD_TIM1); + writel(regs.tim2, fb->regs + CLCD_TIM2); + writel(regs.tim3, fb->regs + CLCD_TIM3); + + clcdfb_set_start(fb); + + clk_set_rate(fb->clk, (1000000000 / regs.pixclock) * 1000); + + fb->clcd_cntl = regs.cntl; + + clcdfb_enable(fb, regs.cntl); + +#ifdef DEBUG + printk(KERN_INFO + "CLCD: Registers set to\n" + " %08x %08x %08x %08x\n" + " %08x %08x %08x %08x\n", + readl(fb->regs + CLCD_TIM0), readl(fb->regs + CLCD_TIM1), + readl(fb->regs + CLCD_TIM2), readl(fb->regs + CLCD_TIM3), + readl(fb->regs + CLCD_UBAS), readl(fb->regs + CLCD_LBAS), + readl(fb->regs + fb->off_ienb), readl(fb->regs + fb->off_cntl)); +#endif + + return 0; +} + +static inline u32 convert_bitfield(int val, struct fb_bitfield *bf) +{ + unsigned int mask = (1 << bf->length) - 1; + + return (val >> (16 - bf->length) & mask) << bf->offset; +} + +/* + * Set a single color register. The values supplied have a 16 bit + * magnitude. Return != 0 for invalid regno. + */ +static int +clcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, + unsigned int blue, unsigned int transp, struct fb_info *info) +{ + struct clcd_fb *fb = to_clcd(info); + + if (regno < 16) + fb->cmap[regno] = convert_bitfield(transp, &fb->fb.var.transp) | + convert_bitfield(blue, &fb->fb.var.blue) | + convert_bitfield(green, &fb->fb.var.green) | + convert_bitfield(red, &fb->fb.var.red); + + if (fb->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR && regno < 256) { + int hw_reg = CLCD_PALETTE + ((regno * 2) & ~3); + u32 val, mask, newval; + + newval = (red >> 11) & 0x001f; + newval |= (green >> 6) & 0x03e0; + newval |= (blue >> 1) & 0x7c00; + + /* + * 3.2.11: if we're configured for big endian + * byte order, the palette entries are swapped. + */ + if (fb->clcd_cntl & CNTL_BEBO) + regno ^= 1; + + if (regno & 1) { + newval <<= 16; + mask = 0x0000ffff; + } else { + mask = 0xffff0000; + } + + val = readl(fb->regs + hw_reg) & mask; + writel(val | newval, fb->regs + hw_reg); + } + + return regno > 255; +} + +/* + * Blank the screen if blank_mode != 0, else unblank. If blank == NULL + * then the caller blanks by setting the CLUT (Color Look Up Table) to all + * black. Return 0 if blanking succeeded, != 0 if un-/blanking failed due + * to e.g. a video mode which doesn't support it. Implements VESA suspend + * and powerdown modes on hardware that supports disabling hsync/vsync: + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ +static int clcdfb_blank(int blank_mode, struct fb_info *info) +{ + struct clcd_fb *fb = to_clcd(info); + + if (blank_mode != 0) { + clcdfb_disable(fb); + } else { + clcdfb_enable(fb, fb->clcd_cntl); + } + return 0; +} + +static int clcdfb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + struct clcd_fb *fb = to_clcd(info); + unsigned long len, off = vma->vm_pgoff << PAGE_SHIFT; + int ret = -EINVAL; + + len = info->fix.smem_len; + + if (off <= len && vma->vm_end - vma->vm_start <= len - off && + fb->board->mmap) + ret = fb->board->mmap(fb, vma); + + return ret; +} + +static struct fb_ops clcdfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = clcdfb_check_var, + .fb_set_par = clcdfb_set_par, + .fb_setcolreg = clcdfb_setcolreg, + .fb_blank = clcdfb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = clcdfb_mmap, +}; + +static int clcdfb_register(struct clcd_fb *fb) +{ + int ret; + + /* + * ARM PL111 always has IENB at 0x1c; it's only PL110 + * which is reversed on some platforms. + */ + if (amba_manf(fb->dev) == 0x41 && amba_part(fb->dev) == 0x111) { + fb->off_ienb = CLCD_PL111_IENB; + fb->off_cntl = CLCD_PL111_CNTL; + } else { +#ifdef CONFIG_ARCH_VERSATILE + fb->off_ienb = CLCD_PL111_IENB; + fb->off_cntl = CLCD_PL111_CNTL; +#else + fb->off_ienb = CLCD_PL110_IENB; + fb->off_cntl = CLCD_PL110_CNTL; +#endif + } + + fb->clk = clk_get(&fb->dev->dev, NULL); + if (IS_ERR(fb->clk)) { + ret = PTR_ERR(fb->clk); + goto out; + } + + ret = clk_prepare(fb->clk); + if (ret) + goto free_clk; + + fb->fb.device = &fb->dev->dev; + + fb->fb.fix.mmio_start = fb->dev->res.start; + fb->fb.fix.mmio_len = resource_size(&fb->dev->res); + + fb->regs = ioremap(fb->fb.fix.mmio_start, fb->fb.fix.mmio_len); + if (!fb->regs) { + printk(KERN_ERR "CLCD: unable to remap registers\n"); + ret = -ENOMEM; + goto clk_unprep; + } + + fb->fb.fbops = &clcdfb_ops; + fb->fb.flags = FBINFO_FLAG_DEFAULT; + fb->fb.pseudo_palette = fb->cmap; + + strncpy(fb->fb.fix.id, clcd_name, sizeof(fb->fb.fix.id)); + fb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fb->fb.fix.type_aux = 0; + fb->fb.fix.xpanstep = 0; + fb->fb.fix.ypanstep = 0; + fb->fb.fix.ywrapstep = 0; + fb->fb.fix.accel = FB_ACCEL_NONE; + + fb->fb.var.xres = fb->panel->mode.xres; + fb->fb.var.yres = fb->panel->mode.yres; + fb->fb.var.xres_virtual = fb->panel->mode.xres; + fb->fb.var.yres_virtual = fb->panel->mode.yres; + fb->fb.var.bits_per_pixel = fb->panel->bpp; + fb->fb.var.grayscale = fb->panel->grayscale; + fb->fb.var.pixclock = fb->panel->mode.pixclock; + fb->fb.var.left_margin = fb->panel->mode.left_margin; + fb->fb.var.right_margin = fb->panel->mode.right_margin; + fb->fb.var.upper_margin = fb->panel->mode.upper_margin; + fb->fb.var.lower_margin = fb->panel->mode.lower_margin; + fb->fb.var.hsync_len = fb->panel->mode.hsync_len; + fb->fb.var.vsync_len = fb->panel->mode.vsync_len; + fb->fb.var.sync = fb->panel->mode.sync; + fb->fb.var.vmode = fb->panel->mode.vmode; + fb->fb.var.activate = FB_ACTIVATE_NOW; + fb->fb.var.nonstd = 0; + fb->fb.var.height = fb->panel->height; + fb->fb.var.width = fb->panel->width; + fb->fb.var.accel_flags = 0; + + fb->fb.monspecs.hfmin = 0; + fb->fb.monspecs.hfmax = 100000; + fb->fb.monspecs.vfmin = 0; + fb->fb.monspecs.vfmax = 400; + fb->fb.monspecs.dclkmin = 1000000; + fb->fb.monspecs.dclkmax = 100000000; + + /* + * Make sure that the bitfields are set appropriately. + */ + clcdfb_set_bitfields(fb, &fb->fb.var); + + /* + * Allocate colourmap. + */ + ret = fb_alloc_cmap(&fb->fb.cmap, 256, 0); + if (ret) + goto unmap; + + /* + * Ensure interrupts are disabled. + */ + writel(0, fb->regs + fb->off_ienb); + + fb_set_var(&fb->fb, &fb->fb.var); + + dev_info(&fb->dev->dev, "%s hardware, %s display\n", + fb->board->name, fb->panel->mode.name); + + ret = register_framebuffer(&fb->fb); + if (ret == 0) + goto out; + + printk(KERN_ERR "CLCD: cannot register framebuffer (%d)\n", ret); + + fb_dealloc_cmap(&fb->fb.cmap); + unmap: + iounmap(fb->regs); + clk_unprep: + clk_unprepare(fb->clk); + free_clk: + clk_put(fb->clk); + out: + return ret; +} + +static int clcdfb_probe(struct amba_device *dev, const struct amba_id *id) +{ + struct clcd_board *board = dev_get_platdata(&dev->dev); + struct clcd_fb *fb; + int ret; + + if (!board) + return -EINVAL; + + ret = dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)); + if (ret) + goto out; + + ret = amba_request_regions(dev, NULL); + if (ret) { + printk(KERN_ERR "CLCD: unable to reserve regs region\n"); + goto out; + } + + fb = kzalloc(sizeof(struct clcd_fb), GFP_KERNEL); + if (!fb) { + printk(KERN_INFO "CLCD: could not allocate new clcd_fb struct\n"); + ret = -ENOMEM; + goto free_region; + } + + fb->dev = dev; + fb->board = board; + + dev_info(&fb->dev->dev, "PL%03x rev%u at 0x%08llx\n", + amba_part(dev), amba_rev(dev), + (unsigned long long)dev->res.start); + + ret = fb->board->setup(fb); + if (ret) + goto free_fb; + + ret = clcdfb_register(fb); + if (ret == 0) { + amba_set_drvdata(dev, fb); + goto out; + } + + fb->board->remove(fb); + free_fb: + kfree(fb); + free_region: + amba_release_regions(dev); + out: + return ret; +} + +static int clcdfb_remove(struct amba_device *dev) +{ + struct clcd_fb *fb = amba_get_drvdata(dev); + + clcdfb_disable(fb); + unregister_framebuffer(&fb->fb); + if (fb->fb.cmap.len) + fb_dealloc_cmap(&fb->fb.cmap); + iounmap(fb->regs); + clk_unprepare(fb->clk); + clk_put(fb->clk); + + fb->board->remove(fb); + + kfree(fb); + + amba_release_regions(dev); + + return 0; +} + +static struct amba_id clcdfb_id_table[] = { + { + .id = 0x00041110, + .mask = 0x000ffffe, + }, + { 0, 0 }, +}; + +MODULE_DEVICE_TABLE(amba, clcdfb_id_table); + +static struct amba_driver clcd_driver = { + .drv = { + .name = "clcd-pl11x", + }, + .probe = clcdfb_probe, + .remove = clcdfb_remove, + .id_table = clcdfb_id_table, +}; + +static int __init amba_clcdfb_init(void) +{ + if (fb_get_options("ambafb", NULL)) + return -ENODEV; + + return amba_driver_register(&clcd_driver); +} + +module_init(amba_clcdfb_init); + +static void __exit amba_clcdfb_exit(void) +{ + amba_driver_unregister(&clcd_driver); +} + +module_exit(amba_clcdfb_exit); + +MODULE_DESCRIPTION("ARM PrimeCell PL110 CLCD core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/amifb.c b/drivers/video/fbdev/amifb.c new file mode 100644 index 000000000000..518f790ef88a --- /dev/null +++ b/drivers/video/fbdev/amifb.c @@ -0,0 +1,3792 @@ +/* + * linux/drivers/video/amifb.c -- Amiga builtin chipset frame buffer device + * + * Copyright (C) 1995-2003 Geert Uytterhoeven + * + * with work by Roman Zippel + * + * + * This file is based on the Atari frame buffer device (atafb.c): + * + * Copyright (C) 1994 Martin Schaller + * Roman Hodek + * + * with work by Andreas Schwab + * Guenther Kelleter + * + * and on the original Amiga console driver (amicon.c): + * + * Copyright (C) 1993 Hamish Macdonald + * Greg Harp + * Copyright (C) 1994 David Carter [carter@compsci.bristol.ac.uk] + * + * with work by William Rucklidge (wjr@cs.cornell.edu) + * Geert Uytterhoeven + * Jes Sorensen (jds@kom.auc.dk) + * + * + * History: + * + * - 24 Jul 96: Copper generates now vblank interrupt and + * VESA Power Saving Protocol is fully implemented + * - 14 Jul 96: Rework and hopefully last ECS bugs fixed + * - 7 Mar 96: Hardware sprite support by Roman Zippel + * - 18 Feb 96: OCS and ECS support by Roman Zippel + * Hardware functions completely rewritten + * - 2 Dec 95: AGA version by Geert Uytterhoeven + * + * 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/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> + +#include <asm/irq.h> +#include <asm/amigahw.h> +#include <asm/amigaints.h> +#include <asm/setup.h> + +#include "c2p.h" + + +#define DEBUG + +#if !defined(CONFIG_FB_AMIGA_OCS) && !defined(CONFIG_FB_AMIGA_ECS) && !defined(CONFIG_FB_AMIGA_AGA) +#define CONFIG_FB_AMIGA_OCS /* define at least one fb driver, this will change later */ +#endif + +#if !defined(CONFIG_FB_AMIGA_OCS) +# define IS_OCS (0) +#elif defined(CONFIG_FB_AMIGA_ECS) || defined(CONFIG_FB_AMIGA_AGA) +# define IS_OCS (chipset == TAG_OCS) +#else +# define CONFIG_FB_AMIGA_OCS_ONLY +# define IS_OCS (1) +#endif + +#if !defined(CONFIG_FB_AMIGA_ECS) +# define IS_ECS (0) +#elif defined(CONFIG_FB_AMIGA_OCS) || defined(CONFIG_FB_AMIGA_AGA) +# define IS_ECS (chipset == TAG_ECS) +#else +# define CONFIG_FB_AMIGA_ECS_ONLY +# define IS_ECS (1) +#endif + +#if !defined(CONFIG_FB_AMIGA_AGA) +# define IS_AGA (0) +#elif defined(CONFIG_FB_AMIGA_OCS) || defined(CONFIG_FB_AMIGA_ECS) +# define IS_AGA (chipset == TAG_AGA) +#else +# define CONFIG_FB_AMIGA_AGA_ONLY +# define IS_AGA (1) +#endif + +#ifdef DEBUG +# define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__ , ## args) +#else +# define DPRINTK(fmt, args...) +#endif + +/******************************************************************************* + + + Generic video timings + --------------------- + + Timings used by the frame buffer interface: + + +----------+---------------------------------------------+----------+-------+ + | | ^ | | | + | | |upper_margin | | | + | | v | | | + +----------###############################################----------+-------+ + | # ^ # | | + | # | # | | + | # | # | | + | # | # | | + | left # | # right | hsync | + | margin # | xres # margin | len | + |<-------->#<---------------+--------------------------->#<-------->|<----->| + | # | # | | + | # | # | | + | # | # | | + | # |yres # | | + | # | # | | + | # | # | | + | # | # | | + | # | # | | + | # | # | | + | # | # | | + | # | # | | + | # | # | | + | # v # | | + +----------###############################################----------+-------+ + | | ^ | | | + | | |lower_margin | | | + | | v | | | + +----------+---------------------------------------------+----------+-------+ + | | ^ | | | + | | |vsync_len | | | + | | v | | | + +----------+---------------------------------------------+----------+-------+ + + + Amiga video timings + ------------------- + + The Amiga native chipsets uses another timing scheme: + + - hsstrt: Start of horizontal synchronization pulse + - hsstop: End of horizontal synchronization pulse + - htotal: Last value on the line (i.e. line length = htotal + 1) + - vsstrt: Start of vertical synchronization pulse + - vsstop: End of vertical synchronization pulse + - vtotal: Last line value (i.e. number of lines = vtotal + 1) + - hcenter: Start of vertical retrace for interlace + + You can specify the blanking timings independently. Currently I just set + them equal to the respective synchronization values: + + - hbstrt: Start of horizontal blank + - hbstop: End of horizontal blank + - vbstrt: Start of vertical blank + - vbstop: End of vertical blank + + Horizontal values are in color clock cycles (280 ns), vertical values are in + scanlines. + + (0, 0) is somewhere in the upper-left corner :-) + + + Amiga visible window definitions + -------------------------------- + + Currently I only have values for AGA, SHRES (28 MHz dotclock). Feel free to + make corrections and/or additions. + + Within the above synchronization specifications, the visible window is + defined by the following parameters (actual register resolutions may be + different; all horizontal values are normalized with respect to the pixel + clock): + + - diwstrt_h: Horizontal start of the visible window + - diwstop_h: Horizontal stop + 1(*) of the visible window + - diwstrt_v: Vertical start of the visible window + - diwstop_v: Vertical stop of the visible window + - ddfstrt: Horizontal start of display DMA + - ddfstop: Horizontal stop of display DMA + - hscroll: Horizontal display output delay + + Sprite positioning: + + - sprstrt_h: Horizontal start - 4 of sprite + - sprstrt_v: Vertical start of sprite + + (*) Even Commodore did it wrong in the AGA monitor drivers by not adding 1. + + Horizontal values are in dotclock cycles (35 ns), vertical values are in + scanlines. + + (0, 0) is somewhere in the upper-left corner :-) + + + Dependencies (AGA, SHRES (35 ns dotclock)) + ------------------------------------------- + + Since there are much more parameters for the Amiga display than for the + frame buffer interface, there must be some dependencies among the Amiga + display parameters. Here's what I found out: + + - ddfstrt and ddfstop are best aligned to 64 pixels. + - the chipset needs 64 + 4 horizontal pixels after the DMA start before + the first pixel is output, so diwstrt_h = ddfstrt + 64 + 4 if you want + to display the first pixel on the line too. Increase diwstrt_h for + virtual screen panning. + - the display DMA always fetches 64 pixels at a time (fmode = 3). + - ddfstop is ddfstrt+#pixels - 64. + - diwstop_h = diwstrt_h + xres + 1. Because of the additional 1 this can + be 1 more than htotal. + - hscroll simply adds a delay to the display output. Smooth horizontal + panning needs an extra 64 pixels on the left to prefetch the pixels that + `fall off' on the left. + - if ddfstrt < 192, the sprite DMA cycles are all stolen by the bitplane + DMA, so it's best to make the DMA start as late as possible. + - you really don't want to make ddfstrt < 128, since this will steal DMA + cycles from the other DMA channels (audio, floppy and Chip RAM refresh). + - I make diwstop_h and diwstop_v as large as possible. + + General dependencies + -------------------- + + - all values are SHRES pixel (35ns) + + table 1:fetchstart table 2:prefetch table 3:fetchsize + ------------------ ---------------- ----------------- + Pixclock # SHRES|HIRES|LORES # SHRES|HIRES|LORES # SHRES|HIRES|LORES + -------------#------+-----+------#------+-----+------#------+-----+------ + Bus width 1x # 16 | 32 | 64 # 16 | 32 | 64 # 64 | 64 | 64 + Bus width 2x # 32 | 64 | 128 # 32 | 64 | 64 # 64 | 64 | 128 + Bus width 4x # 64 | 128 | 256 # 64 | 64 | 64 # 64 | 128 | 256 + + - chipset needs 4 pixels before the first pixel is output + - ddfstrt must be aligned to fetchstart (table 1) + - chipset needs also prefetch (table 2) to get first pixel data, so + ddfstrt = ((diwstrt_h - 4) & -fetchstart) - prefetch + - for horizontal panning decrease diwstrt_h + - the length of a fetchline must be aligned to fetchsize (table 3) + - if fetchstart is smaller than fetchsize, then ddfstrt can a little bit + moved to optimize use of dma (useful for OCS/ECS overscan displays) + - ddfstop is ddfstrt + ddfsize - fetchsize + - If C= didn't change anything for AGA, then at following positions the + dma bus is already used: + ddfstrt < 48 -> memory refresh + < 96 -> disk dma + < 160 -> audio dma + < 192 -> sprite 0 dma + < 416 -> sprite dma (32 per sprite) + - in accordance with the hardware reference manual a hardware stop is at + 192, but AGA (ECS?) can go below this. + + DMA priorities + -------------- + + Since there are limits on the earliest start value for display DMA and the + display of sprites, I use the following policy on horizontal panning and + the hardware cursor: + + - if you want to start display DMA too early, you lose the ability to + do smooth horizontal panning (xpanstep 1 -> 64). + - if you want to go even further, you lose the hardware cursor too. + + IMHO a hardware cursor is more important for X than horizontal scrolling, + so that's my motivation. + + + Implementation + -------------- + + ami_decode_var() converts the frame buffer values to the Amiga values. It's + just a `straightforward' implementation of the above rules. + + + Standard VGA timings + -------------------- + + xres yres left right upper lower hsync vsync + ---- ---- ---- ----- ----- ----- ----- ----- + 80x25 720 400 27 45 35 12 108 2 + 80x30 720 480 27 45 30 9 108 2 + + These were taken from a XFree86 configuration file, recalculated for a 28 MHz + dotclock (Amigas don't have a 25 MHz dotclock) and converted to frame buffer + generic timings. + + As a comparison, graphics/monitor.h suggests the following: + + xres yres left right upper lower hsync vsync + ---- ---- ---- ----- ----- ----- ----- ----- + + VGA 640 480 52 112 24 19 112 - 2 + + VGA70 640 400 52 112 27 21 112 - 2 - + + + Sync polarities + --------------- + + VSYNC HSYNC Vertical size Vertical total + ----- ----- ------------- -------------- + + + Reserved Reserved + + - 400 414 + - + 350 362 + - - 480 496 + + Source: CL-GD542X Technical Reference Manual, Cirrus Logic, Oct 1992 + + + Broadcast video timings + ----------------------- + + According to the CCIR and RETMA specifications, we have the following values: + + CCIR -> PAL + ----------- + + - a scanline is 64 µs long, of which 52.48 µs are visible. This is about + 736 visible 70 ns pixels per line. + - we have 625 scanlines, of which 575 are visible (interlaced); after + rounding this becomes 576. + + RETMA -> NTSC + ------------- + + - a scanline is 63.5 µs long, of which 53.5 µs are visible. This is about + 736 visible 70 ns pixels per line. + - we have 525 scanlines, of which 485 are visible (interlaced); after + rounding this becomes 484. + + Thus if you want a PAL compatible display, you have to do the following: + + - set the FB_SYNC_BROADCAST flag to indicate that standard broadcast + timings are to be used. + - make sure upper_margin + yres + lower_margin + vsync_len = 625 for an + interlaced, 312 for a non-interlaced and 156 for a doublescanned + display. + - make sure left_margin + xres + right_margin + hsync_len = 1816 for a + SHRES, 908 for a HIRES and 454 for a LORES display. + - the left visible part begins at 360 (SHRES; HIRES:180, LORES:90), + left_margin + 2 * hsync_len must be greater or equal. + - the upper visible part begins at 48 (interlaced; non-interlaced:24, + doublescanned:12), upper_margin + 2 * vsync_len must be greater or + equal. + - ami_encode_var() calculates margins with a hsync of 5320 ns and a vsync + of 4 scanlines + + The settings for a NTSC compatible display are straightforward. + + Note that in a strict sense the PAL and NTSC standards only define the + encoding of the color part (chrominance) of the video signal and don't say + anything about horizontal/vertical synchronization nor refresh rates. + + + -- Geert -- + +*******************************************************************************/ + + + /* + * Custom Chipset Definitions + */ + +#define CUSTOM_OFS(fld) ((long)&((struct CUSTOM*)0)->fld) + + /* + * BPLCON0 -- Bitplane Control Register 0 + */ + +#define BPC0_HIRES (0x8000) +#define BPC0_BPU2 (0x4000) /* Bit plane used count */ +#define BPC0_BPU1 (0x2000) +#define BPC0_BPU0 (0x1000) +#define BPC0_HAM (0x0800) /* HAM mode */ +#define BPC0_DPF (0x0400) /* Double playfield */ +#define BPC0_COLOR (0x0200) /* Enable colorburst */ +#define BPC0_GAUD (0x0100) /* Genlock audio enable */ +#define BPC0_UHRES (0x0080) /* Ultrahi res enable */ +#define BPC0_SHRES (0x0040) /* Super hi res mode */ +#define BPC0_BYPASS (0x0020) /* Bypass LUT - AGA */ +#define BPC0_BPU3 (0x0010) /* AGA */ +#define BPC0_LPEN (0x0008) /* Light pen enable */ +#define BPC0_LACE (0x0004) /* Interlace */ +#define BPC0_ERSY (0x0002) /* External resync */ +#define BPC0_ECSENA (0x0001) /* ECS enable */ + + /* + * BPLCON2 -- Bitplane Control Register 2 + */ + +#define BPC2_ZDBPSEL2 (0x4000) /* Bitplane to be used for ZD - AGA */ +#define BPC2_ZDBPSEL1 (0x2000) +#define BPC2_ZDBPSEL0 (0x1000) +#define BPC2_ZDBPEN (0x0800) /* Enable ZD with ZDBPSELx - AGA */ +#define BPC2_ZDCTEN (0x0400) /* Enable ZD with palette bit #31 - AGA */ +#define BPC2_KILLEHB (0x0200) /* Kill EHB mode - AGA */ +#define BPC2_RDRAM (0x0100) /* Color table accesses read, not write - AGA */ +#define BPC2_SOGEN (0x0080) /* SOG output pin high - AGA */ +#define BPC2_PF2PRI (0x0040) /* PF2 priority over PF1 */ +#define BPC2_PF2P2 (0x0020) /* PF2 priority wrt sprites */ +#define BPC2_PF2P1 (0x0010) +#define BPC2_PF2P0 (0x0008) +#define BPC2_PF1P2 (0x0004) /* ditto PF1 */ +#define BPC2_PF1P1 (0x0002) +#define BPC2_PF1P0 (0x0001) + + /* + * BPLCON3 -- Bitplane Control Register 3 (AGA) + */ + +#define BPC3_BANK2 (0x8000) /* Bits to select color register bank */ +#define BPC3_BANK1 (0x4000) +#define BPC3_BANK0 (0x2000) +#define BPC3_PF2OF2 (0x1000) /* Bits for color table offset when PF2 */ +#define BPC3_PF2OF1 (0x0800) +#define BPC3_PF2OF0 (0x0400) +#define BPC3_LOCT (0x0200) /* Color register writes go to low bits */ +#define BPC3_SPRES1 (0x0080) /* Sprite resolution bits */ +#define BPC3_SPRES0 (0x0040) +#define BPC3_BRDRBLNK (0x0020) /* Border blanked? */ +#define BPC3_BRDRTRAN (0x0010) /* Border transparent? */ +#define BPC3_ZDCLKEN (0x0004) /* ZD pin is 14 MHz (HIRES) clock output */ +#define BPC3_BRDRSPRT (0x0002) /* Sprites in border? */ +#define BPC3_EXTBLKEN (0x0001) /* BLANK programmable */ + + /* + * BPLCON4 -- Bitplane Control Register 4 (AGA) + */ + +#define BPC4_BPLAM7 (0x8000) /* bitplane color XOR field */ +#define BPC4_BPLAM6 (0x4000) +#define BPC4_BPLAM5 (0x2000) +#define BPC4_BPLAM4 (0x1000) +#define BPC4_BPLAM3 (0x0800) +#define BPC4_BPLAM2 (0x0400) +#define BPC4_BPLAM1 (0x0200) +#define BPC4_BPLAM0 (0x0100) +#define BPC4_ESPRM7 (0x0080) /* 4 high bits for even sprite colors */ +#define BPC4_ESPRM6 (0x0040) +#define BPC4_ESPRM5 (0x0020) +#define BPC4_ESPRM4 (0x0010) +#define BPC4_OSPRM7 (0x0008) /* 4 high bits for odd sprite colors */ +#define BPC4_OSPRM6 (0x0004) +#define BPC4_OSPRM5 (0x0002) +#define BPC4_OSPRM4 (0x0001) + + /* + * BEAMCON0 -- Beam Control Register + */ + +#define BMC0_HARDDIS (0x4000) /* Disable hardware limits */ +#define BMC0_LPENDIS (0x2000) /* Disable light pen latch */ +#define BMC0_VARVBEN (0x1000) /* Enable variable vertical blank */ +#define BMC0_LOLDIS (0x0800) /* Disable long/short line toggle */ +#define BMC0_CSCBEN (0x0400) /* Composite sync/blank */ +#define BMC0_VARVSYEN (0x0200) /* Enable variable vertical sync */ +#define BMC0_VARHSYEN (0x0100) /* Enable variable horizontal sync */ +#define BMC0_VARBEAMEN (0x0080) /* Enable variable beam counters */ +#define BMC0_DUAL (0x0040) /* Enable alternate horizontal beam counter */ +#define BMC0_PAL (0x0020) /* Set decodes for PAL */ +#define BMC0_VARCSYEN (0x0010) /* Enable variable composite sync */ +#define BMC0_BLANKEN (0x0008) /* Blank enable (no longer used on AGA) */ +#define BMC0_CSYTRUE (0x0004) /* CSY polarity */ +#define BMC0_VSYTRUE (0x0002) /* VSY polarity */ +#define BMC0_HSYTRUE (0x0001) /* HSY polarity */ + + + /* + * FMODE -- Fetch Mode Control Register (AGA) + */ + +#define FMODE_SSCAN2 (0x8000) /* Sprite scan-doubling */ +#define FMODE_BSCAN2 (0x4000) /* Use PF2 modulus every other line */ +#define FMODE_SPAGEM (0x0008) /* Sprite page mode */ +#define FMODE_SPR32 (0x0004) /* Sprite 32 bit fetch */ +#define FMODE_BPAGEM (0x0002) /* Bitplane page mode */ +#define FMODE_BPL32 (0x0001) /* Bitplane 32 bit fetch */ + + /* + * Tags used to indicate a specific Pixel Clock + * + * clk_shift is the shift value to get the timings in 35 ns units + */ + +enum { TAG_SHRES, TAG_HIRES, TAG_LORES }; + + /* + * Tags used to indicate the specific chipset + */ + +enum { TAG_OCS, TAG_ECS, TAG_AGA }; + + /* + * Tags used to indicate the memory bandwidth + */ + +enum { TAG_FMODE_1, TAG_FMODE_2, TAG_FMODE_4 }; + + + /* + * Clock Definitions, Maximum Display Depth + * + * These depend on the E-Clock or the Chipset, so they are filled in + * dynamically + */ + +static u_long pixclock[3]; /* SHRES/HIRES/LORES: index = clk_shift */ +static u_short maxdepth[3]; /* SHRES/HIRES/LORES: index = clk_shift */ +static u_short maxfmode, chipset; + + + /* + * Broadcast Video Timings + * + * Horizontal values are in 35 ns (SHRES) units + * Vertical values are in interlaced scanlines + */ + +#define PAL_DIWSTRT_H (360) /* PAL Window Limits */ +#define PAL_DIWSTRT_V (48) +#define PAL_HTOTAL (1816) +#define PAL_VTOTAL (625) + +#define NTSC_DIWSTRT_H (360) /* NTSC Window Limits */ +#define NTSC_DIWSTRT_V (40) +#define NTSC_HTOTAL (1816) +#define NTSC_VTOTAL (525) + + + /* + * Various macros + */ + +#define up2(v) (((v) + 1) & -2) +#define down2(v) ((v) & -2) +#define div2(v) ((v)>>1) +#define mod2(v) ((v) & 1) + +#define up4(v) (((v) + 3) & -4) +#define down4(v) ((v) & -4) +#define mul4(v) ((v) << 2) +#define div4(v) ((v)>>2) +#define mod4(v) ((v) & 3) + +#define up8(v) (((v) + 7) & -8) +#define down8(v) ((v) & -8) +#define div8(v) ((v)>>3) +#define mod8(v) ((v) & 7) + +#define up16(v) (((v) + 15) & -16) +#define down16(v) ((v) & -16) +#define div16(v) ((v)>>4) +#define mod16(v) ((v) & 15) + +#define up32(v) (((v) + 31) & -32) +#define down32(v) ((v) & -32) +#define div32(v) ((v)>>5) +#define mod32(v) ((v) & 31) + +#define up64(v) (((v) + 63) & -64) +#define down64(v) ((v) & -64) +#define div64(v) ((v)>>6) +#define mod64(v) ((v) & 63) + +#define upx(x, v) (((v) + (x) - 1) & -(x)) +#define downx(x, v) ((v) & -(x)) +#define modx(x, v) ((v) & ((x) - 1)) + +/* if x1 is not a constant, this macro won't make real sense :-) */ +#ifdef __mc68000__ +#define DIVUL(x1, x2) ({int res; asm("divul %1,%2,%3": "=d" (res): \ + "d" (x2), "d" ((long)((x1) / 0x100000000ULL)), "0" ((long)(x1))); res;}) +#else +/* We know a bit about the numbers, so we can do it this way */ +#define DIVUL(x1, x2) ((((long)((unsigned long long)x1 >> 8) / x2) << 8) + \ + ((((long)((unsigned long long)x1 >> 8) % x2) << 8) / x2)) +#endif + +#define highw(x) ((u_long)(x)>>16 & 0xffff) +#define loww(x) ((u_long)(x) & 0xffff) + +#define custom amiga_custom + +#define VBlankOn() custom.intena = IF_SETCLR|IF_COPER +#define VBlankOff() custom.intena = IF_COPER + + + /* + * Chip RAM we reserve for the Frame Buffer + * + * This defines the Maximum Virtual Screen Size + * (Setable per kernel options?) + */ + +#define VIDEOMEMSIZE_AGA_2M (1310720) /* AGA (2MB) : max 1280*1024*256 */ +#define VIDEOMEMSIZE_AGA_1M (786432) /* AGA (1MB) : max 1024*768*256 */ +#define VIDEOMEMSIZE_ECS_2M (655360) /* ECS (2MB) : max 1280*1024*16 */ +#define VIDEOMEMSIZE_ECS_1M (393216) /* ECS (1MB) : max 1024*768*16 */ +#define VIDEOMEMSIZE_OCS (262144) /* OCS : max ca. 800*600*16 */ + +#define SPRITEMEMSIZE (64 * 64 / 4) /* max 64*64*4 */ +#define DUMMYSPRITEMEMSIZE (8) +static u_long spritememory; + +#define CHIPRAM_SAFETY_LIMIT (16384) + +static u_long videomemory; + + /* + * This is the earliest allowed start of fetching display data. + * Only if you really want no hardware cursor and audio, + * set this to 128, but let it better at 192 + */ + +static u_long min_fstrt = 192; + +#define assignchunk(name, type, ptr, size) \ +{ \ + (name) = (type)(ptr); \ + ptr += size; \ +} + + + /* + * Copper Instructions + */ + +#define CMOVE(val, reg) (CUSTOM_OFS(reg) << 16 | (val)) +#define CMOVE2(val, reg) ((CUSTOM_OFS(reg) + 2) << 16 | (val)) +#define CWAIT(x, y) (((y) & 0x1fe) << 23 | ((x) & 0x7f0) << 13 | 0x0001fffe) +#define CEND (0xfffffffe) + + +typedef union { + u_long l; + u_short w[2]; +} copins; + +static struct copdisplay { + copins *init; + copins *wait; + copins *list[2][2]; + copins *rebuild[2]; +} copdisplay; + +static u_short currentcop = 0; + + /* + * Hardware Cursor API Definitions + * These used to be in linux/fb.h, but were preliminary and used by + * amifb only anyway + */ + +#define FBIOGET_FCURSORINFO 0x4607 +#define FBIOGET_VCURSORINFO 0x4608 +#define FBIOPUT_VCURSORINFO 0x4609 +#define FBIOGET_CURSORSTATE 0x460A +#define FBIOPUT_CURSORSTATE 0x460B + + +struct fb_fix_cursorinfo { + __u16 crsr_width; /* width and height of the cursor in */ + __u16 crsr_height; /* pixels (zero if no cursor) */ + __u16 crsr_xsize; /* cursor size in display pixels */ + __u16 crsr_ysize; + __u16 crsr_color1; /* colormap entry for cursor color1 */ + __u16 crsr_color2; /* colormap entry for cursor color2 */ +}; + +struct fb_var_cursorinfo { + __u16 width; + __u16 height; + __u16 xspot; + __u16 yspot; + __u8 data[1]; /* field with [height][width] */ +}; + +struct fb_cursorstate { + __s16 xoffset; + __s16 yoffset; + __u16 mode; +}; + +#define FB_CURSOR_OFF 0 +#define FB_CURSOR_ON 1 +#define FB_CURSOR_FLASH 2 + + + /* + * Hardware Cursor + */ + +static int cursorrate = 20; /* Number of frames/flash toggle */ +static u_short cursorstate = -1; +static u_short cursormode = FB_CURSOR_OFF; + +static u_short *lofsprite, *shfsprite, *dummysprite; + + /* + * Current Video Mode + */ + +struct amifb_par { + + /* General Values */ + + int xres; /* vmode */ + int yres; /* vmode */ + int vxres; /* vmode */ + int vyres; /* vmode */ + int xoffset; /* vmode */ + int yoffset; /* vmode */ + u_short bpp; /* vmode */ + u_short clk_shift; /* vmode */ + u_short line_shift; /* vmode */ + int vmode; /* vmode */ + u_short diwstrt_h; /* vmode */ + u_short diwstop_h; /* vmode */ + u_short diwstrt_v; /* vmode */ + u_short diwstop_v; /* vmode */ + u_long next_line; /* modulo for next line */ + u_long next_plane; /* modulo for next plane */ + + /* Cursor Values */ + + struct { + short crsr_x; /* movecursor */ + short crsr_y; /* movecursor */ + short spot_x; + short spot_y; + u_short height; + u_short width; + u_short fmode; + } crsr; + + /* OCS Hardware Registers */ + + u_long bplpt0; /* vmode, pan (Note: physical address) */ + u_long bplpt0wrap; /* vmode, pan (Note: physical address) */ + u_short ddfstrt; + u_short ddfstop; + u_short bpl1mod; + u_short bpl2mod; + u_short bplcon0; /* vmode */ + u_short bplcon1; /* vmode */ + u_short htotal; /* vmode */ + u_short vtotal; /* vmode */ + + /* Additional ECS Hardware Registers */ + + u_short bplcon3; /* vmode */ + u_short beamcon0; /* vmode */ + u_short hsstrt; /* vmode */ + u_short hsstop; /* vmode */ + u_short hbstrt; /* vmode */ + u_short hbstop; /* vmode */ + u_short vsstrt; /* vmode */ + u_short vsstop; /* vmode */ + u_short vbstrt; /* vmode */ + u_short vbstop; /* vmode */ + u_short hcenter; /* vmode */ + + /* Additional AGA Hardware Registers */ + + u_short fmode; /* vmode */ +}; + + + /* + * Saved color entry 0 so we can restore it when unblanking + */ + +static u_char red0, green0, blue0; + + +#if defined(CONFIG_FB_AMIGA_ECS) +static u_short ecs_palette[32]; +#endif + + + /* + * Latches for Display Changes during VBlank + */ + +static u_short do_vmode_full = 0; /* Change the Video Mode */ +static u_short do_vmode_pan = 0; /* Update the Video Mode */ +static short do_blank = 0; /* (Un)Blank the Screen (±1) */ +static u_short do_cursor = 0; /* Move the Cursor */ + + + /* + * Various Flags + */ + +static u_short is_blanked = 0; /* Screen is Blanked */ +static u_short is_lace = 0; /* Screen is laced */ + + /* + * Predefined Video Modes + * + */ + +static struct fb_videomode ami_modedb[] __initdata = { + + /* + * AmigaOS Video Modes + * + * If you change these, make sure to update DEFMODE_* as well! + */ + + { + /* 640x200, 15 kHz, 60 Hz (NTSC) */ + "ntsc", 60, 640, 200, TAG_HIRES, 106, 86, 44, 16, 76, 2, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x400, 15 kHz, 60 Hz interlaced (NTSC) */ + "ntsc-lace", 60, 640, 400, TAG_HIRES, 106, 86, 88, 33, 76, 4, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 640x256, 15 kHz, 50 Hz (PAL) */ + "pal", 50, 640, 256, TAG_HIRES, 106, 86, 40, 14, 76, 2, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x512, 15 kHz, 50 Hz interlaced (PAL) */ + "pal-lace", 50, 640, 512, TAG_HIRES, 106, 86, 80, 29, 76, 4, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 640x480, 29 kHz, 57 Hz */ + "multiscan", 57, 640, 480, TAG_SHRES, 96, 112, 29, 8, 72, 8, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x960, 29 kHz, 57 Hz interlaced */ + "multiscan-lace", 57, 640, 960, TAG_SHRES, 96, 112, 58, 16, 72, + 16, + 0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 640x200, 15 kHz, 72 Hz */ + "euro36", 72, 640, 200, TAG_HIRES, 92, 124, 6, 6, 52, 5, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x400, 15 kHz, 72 Hz interlaced */ + "euro36-lace", 72, 640, 400, TAG_HIRES, 92, 124, 12, 12, 52, + 10, + 0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 640x400, 29 kHz, 68 Hz */ + "euro72", 68, 640, 400, TAG_SHRES, 164, 92, 9, 9, 80, 8, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x800, 29 kHz, 68 Hz interlaced */ + "euro72-lace", 68, 640, 800, TAG_SHRES, 164, 92, 18, 18, 80, + 16, + 0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 800x300, 23 kHz, 70 Hz */ + "super72", 70, 800, 300, TAG_SHRES, 212, 140, 10, 11, 80, 7, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 800x600, 23 kHz, 70 Hz interlaced */ + "super72-lace", 70, 800, 600, TAG_SHRES, 212, 140, 20, 22, 80, + 14, + 0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 640x200, 27 kHz, 57 Hz doublescan */ + "dblntsc", 57, 640, 200, TAG_SHRES, 196, 124, 18, 17, 80, 4, + 0, FB_VMODE_DOUBLE | FB_VMODE_YWRAP + }, { + /* 640x400, 27 kHz, 57 Hz */ + "dblntsc-ff", 57, 640, 400, TAG_SHRES, 196, 124, 36, 35, 80, 7, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x800, 27 kHz, 57 Hz interlaced */ + "dblntsc-lace", 57, 640, 800, TAG_SHRES, 196, 124, 72, 70, 80, + 14, + 0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 640x256, 27 kHz, 47 Hz doublescan */ + "dblpal", 47, 640, 256, TAG_SHRES, 196, 124, 14, 13, 80, 4, + 0, FB_VMODE_DOUBLE | FB_VMODE_YWRAP + }, { + /* 640x512, 27 kHz, 47 Hz */ + "dblpal-ff", 47, 640, 512, TAG_SHRES, 196, 124, 28, 27, 80, 7, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x1024, 27 kHz, 47 Hz interlaced */ + "dblpal-lace", 47, 640, 1024, TAG_SHRES, 196, 124, 56, 54, 80, + 14, + 0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, + + /* + * VGA Video Modes + */ + + { + /* 640x480, 31 kHz, 60 Hz (VGA) */ + "vga", 60, 640, 480, TAG_SHRES, 64, 96, 30, 9, 112, 2, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x400, 31 kHz, 70 Hz (VGA) */ + "vga70", 70, 640, 400, TAG_SHRES, 64, 96, 35, 12, 112, 2, + FB_SYNC_VERT_HIGH_ACT | FB_SYNC_COMP_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, + +#if 0 + + /* + * A2024 video modes + * These modes don't work yet because there's no A2024 driver. + */ + + { + /* 1024x800, 10 Hz */ + "a2024-10", 10, 1024, 800, TAG_HIRES, 0, 0, 0, 0, 0, 0, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 1024x800, 15 Hz */ + "a2024-15", 15, 1024, 800, TAG_HIRES, 0, 0, 0, 0, 0, 0, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + } +#endif +}; + +#define NUM_TOTAL_MODES ARRAY_SIZE(ami_modedb) + +static char *mode_option __initdata = NULL; +static int round_down_bpp = 1; /* for mode probing */ + + /* + * Some default modes + */ + + +#define DEFMODE_PAL 2 /* "pal" for PAL OCS/ECS */ +#define DEFMODE_NTSC 0 /* "ntsc" for NTSC OCS/ECS */ +#define DEFMODE_AMBER_PAL 3 /* "pal-lace" for flicker fixed PAL (A3000) */ +#define DEFMODE_AMBER_NTSC 1 /* "ntsc-lace" for flicker fixed NTSC (A3000) */ +#define DEFMODE_AGA 19 /* "vga70" for AGA */ + + +static int amifb_ilbm = 0; /* interleaved or normal bitplanes */ + +static u32 amifb_hfmin __initdata; /* monitor hfreq lower limit (Hz) */ +static u32 amifb_hfmax __initdata; /* monitor hfreq upper limit (Hz) */ +static u16 amifb_vfmin __initdata; /* monitor vfreq lower limit (Hz) */ +static u16 amifb_vfmax __initdata; /* monitor vfreq upper limit (Hz) */ + + + /* + * Macros for the conversion from real world values to hardware register + * values + * + * This helps us to keep our attention on the real stuff... + * + * Hardware limits for AGA: + * + * parameter min max step + * --------- --- ---- ---- + * diwstrt_h 0 2047 1 + * diwstrt_v 0 2047 1 + * diwstop_h 0 4095 1 + * diwstop_v 0 4095 1 + * + * ddfstrt 0 2032 16 + * ddfstop 0 2032 16 + * + * htotal 8 2048 8 + * hsstrt 0 2040 8 + * hsstop 0 2040 8 + * vtotal 1 4096 1 + * vsstrt 0 4095 1 + * vsstop 0 4095 1 + * hcenter 0 2040 8 + * + * hbstrt 0 2047 1 + * hbstop 0 2047 1 + * vbstrt 0 4095 1 + * vbstop 0 4095 1 + * + * Horizontal values are in 35 ns (SHRES) pixels + * Vertical values are in half scanlines + */ + +/* bplcon1 (smooth scrolling) */ + +#define hscroll2hw(hscroll) \ + (((hscroll) << 12 & 0x3000) | ((hscroll) << 8 & 0xc300) | \ + ((hscroll) << 4 & 0x0c00) | ((hscroll) << 2 & 0x00f0) | \ + ((hscroll)>>2 & 0x000f)) + +/* diwstrt/diwstop/diwhigh (visible display window) */ + +#define diwstrt2hw(diwstrt_h, diwstrt_v) \ + (((diwstrt_v) << 7 & 0xff00) | ((diwstrt_h)>>2 & 0x00ff)) +#define diwstop2hw(diwstop_h, diwstop_v) \ + (((diwstop_v) << 7 & 0xff00) | ((diwstop_h)>>2 & 0x00ff)) +#define diwhigh2hw(diwstrt_h, diwstrt_v, diwstop_h, diwstop_v) \ + (((diwstop_h) << 3 & 0x2000) | ((diwstop_h) << 11 & 0x1800) | \ + ((diwstop_v)>>1 & 0x0700) | ((diwstrt_h)>>5 & 0x0020) | \ + ((diwstrt_h) << 3 & 0x0018) | ((diwstrt_v)>>9 & 0x0007)) + +/* ddfstrt/ddfstop (display DMA) */ + +#define ddfstrt2hw(ddfstrt) div8(ddfstrt) +#define ddfstop2hw(ddfstop) div8(ddfstop) + +/* hsstrt/hsstop/htotal/vsstrt/vsstop/vtotal/hcenter (sync timings) */ + +#define hsstrt2hw(hsstrt) (div8(hsstrt)) +#define hsstop2hw(hsstop) (div8(hsstop)) +#define htotal2hw(htotal) (div8(htotal) - 1) +#define vsstrt2hw(vsstrt) (div2(vsstrt)) +#define vsstop2hw(vsstop) (div2(vsstop)) +#define vtotal2hw(vtotal) (div2(vtotal) - 1) +#define hcenter2hw(htotal) (div8(htotal)) + +/* hbstrt/hbstop/vbstrt/vbstop (blanking timings) */ + +#define hbstrt2hw(hbstrt) (((hbstrt) << 8 & 0x0700) | ((hbstrt)>>3 & 0x00ff)) +#define hbstop2hw(hbstop) (((hbstop) << 8 & 0x0700) | ((hbstop)>>3 & 0x00ff)) +#define vbstrt2hw(vbstrt) (div2(vbstrt)) +#define vbstop2hw(vbstop) (div2(vbstop)) + +/* colour */ + +#define rgb2hw8_high(red, green, blue) \ + (((red & 0xf0) << 4) | (green & 0xf0) | ((blue & 0xf0)>>4)) +#define rgb2hw8_low(red, green, blue) \ + (((red & 0x0f) << 8) | ((green & 0x0f) << 4) | (blue & 0x0f)) +#define rgb2hw4(red, green, blue) \ + (((red & 0xf0) << 4) | (green & 0xf0) | ((blue & 0xf0)>>4)) +#define rgb2hw2(red, green, blue) \ + (((red & 0xc0) << 4) | (green & 0xc0) | ((blue & 0xc0)>>4)) + +/* sprpos/sprctl (sprite positioning) */ + +#define spr2hw_pos(start_v, start_h) \ + (((start_v) << 7 & 0xff00) | ((start_h)>>3 & 0x00ff)) +#define spr2hw_ctl(start_v, start_h, stop_v) \ + (((stop_v) << 7 & 0xff00) | ((start_v)>>4 & 0x0040) | \ + ((stop_v)>>5 & 0x0020) | ((start_h) << 3 & 0x0018) | \ + ((start_v)>>7 & 0x0004) | ((stop_v)>>8 & 0x0002) | \ + ((start_h)>>2 & 0x0001)) + +/* get current vertical position of beam */ +#define get_vbpos() ((u_short)((*(u_long volatile *)&custom.vposr >> 7) & 0xffe)) + + /* + * Copper Initialisation List + */ + +#define COPINITSIZE (sizeof(copins) * 40) + +enum { + cip_bplcon0 +}; + + /* + * Long Frame/Short Frame Copper List + * Don't change the order, build_copper()/rebuild_copper() rely on this + */ + +#define COPLISTSIZE (sizeof(copins) * 64) + +enum { + cop_wait, cop_bplcon0, + cop_spr0ptrh, cop_spr0ptrl, + cop_diwstrt, cop_diwstop, + cop_diwhigh, +}; + + /* + * Pixel modes for Bitplanes and Sprites + */ + +static u_short bplpixmode[3] = { + BPC0_SHRES, /* 35 ns */ + BPC0_HIRES, /* 70 ns */ + 0 /* 140 ns */ +}; + +static u_short sprpixmode[3] = { + BPC3_SPRES1 | BPC3_SPRES0, /* 35 ns */ + BPC3_SPRES1, /* 70 ns */ + BPC3_SPRES0 /* 140 ns */ +}; + + /* + * Fetch modes for Bitplanes and Sprites + */ + +static u_short bplfetchmode[3] = { + 0, /* 1x */ + FMODE_BPL32, /* 2x */ + FMODE_BPAGEM | FMODE_BPL32 /* 4x */ +}; + +static u_short sprfetchmode[3] = { + 0, /* 1x */ + FMODE_SPR32, /* 2x */ + FMODE_SPAGEM | FMODE_SPR32 /* 4x */ +}; + + +/* --------------------------- Hardware routines --------------------------- */ + + /* + * Get the video params out of `var'. If a value doesn't fit, round + * it up, if it's too big, return -EINVAL. + */ + +static int ami_decode_var(struct fb_var_screeninfo *var, struct amifb_par *par, + const struct fb_info *info) +{ + u_short clk_shift, line_shift; + u_long maxfetchstop, fstrt, fsize, fconst, xres_n, yres_n; + u_int htotal, vtotal; + + /* + * Find a matching Pixel Clock + */ + + for (clk_shift = TAG_SHRES; clk_shift <= TAG_LORES; clk_shift++) + if (var->pixclock <= pixclock[clk_shift]) + break; + if (clk_shift > TAG_LORES) { + DPRINTK("pixclock too high\n"); + return -EINVAL; + } + par->clk_shift = clk_shift; + + /* + * Check the Geometry Values + */ + + if ((par->xres = var->xres) < 64) + par->xres = 64; + if ((par->yres = var->yres) < 64) + par->yres = 64; + if ((par->vxres = var->xres_virtual) < par->xres) + par->vxres = par->xres; + if ((par->vyres = var->yres_virtual) < par->yres) + par->vyres = par->yres; + + par->bpp = var->bits_per_pixel; + if (!var->nonstd) { + if (par->bpp < 1) + par->bpp = 1; + if (par->bpp > maxdepth[clk_shift]) { + if (round_down_bpp && maxdepth[clk_shift]) + par->bpp = maxdepth[clk_shift]; + else { + DPRINTK("invalid bpp\n"); + return -EINVAL; + } + } + } else if (var->nonstd == FB_NONSTD_HAM) { + if (par->bpp < 6) + par->bpp = 6; + if (par->bpp != 6) { + if (par->bpp < 8) + par->bpp = 8; + if (par->bpp != 8 || !IS_AGA) { + DPRINTK("invalid bpp for ham mode\n"); + return -EINVAL; + } + } + } else { + DPRINTK("unknown nonstd mode\n"); + return -EINVAL; + } + + /* + * FB_VMODE_SMOOTH_XPAN will be cleared, if one of the following + * checks failed and smooth scrolling is not possible + */ + + par->vmode = var->vmode | FB_VMODE_SMOOTH_XPAN; + switch (par->vmode & FB_VMODE_MASK) { + case FB_VMODE_INTERLACED: + line_shift = 0; + break; + case FB_VMODE_NONINTERLACED: + line_shift = 1; + break; + case FB_VMODE_DOUBLE: + if (!IS_AGA) { + DPRINTK("double mode only possible with aga\n"); + return -EINVAL; + } + line_shift = 2; + break; + default: + DPRINTK("unknown video mode\n"); + return -EINVAL; + break; + } + par->line_shift = line_shift; + + /* + * Vertical and Horizontal Timings + */ + + xres_n = par->xres << clk_shift; + yres_n = par->yres << line_shift; + par->htotal = down8((var->left_margin + par->xres + var->right_margin + + var->hsync_len) << clk_shift); + par->vtotal = + down2(((var->upper_margin + par->yres + var->lower_margin + + var->vsync_len) << line_shift) + 1); + + if (IS_AGA) + par->bplcon3 = sprpixmode[clk_shift]; + else + par->bplcon3 = 0; + if (var->sync & FB_SYNC_BROADCAST) { + par->diwstop_h = par->htotal - + ((var->right_margin - var->hsync_len) << clk_shift); + if (IS_AGA) + par->diwstop_h += mod4(var->hsync_len); + else + par->diwstop_h = down4(par->diwstop_h); + + par->diwstrt_h = par->diwstop_h - xres_n; + par->diwstop_v = par->vtotal - + ((var->lower_margin - var->vsync_len) << line_shift); + par->diwstrt_v = par->diwstop_v - yres_n; + if (par->diwstop_h >= par->htotal + 8) { + DPRINTK("invalid diwstop_h\n"); + return -EINVAL; + } + if (par->diwstop_v > par->vtotal) { + DPRINTK("invalid diwstop_v\n"); + return -EINVAL; + } + + if (!IS_OCS) { + /* Initialize sync with some reasonable values for pwrsave */ + par->hsstrt = 160; + par->hsstop = 320; + par->vsstrt = 30; + par->vsstop = 34; + } else { + par->hsstrt = 0; + par->hsstop = 0; + par->vsstrt = 0; + par->vsstop = 0; + } + if (par->vtotal > (PAL_VTOTAL + NTSC_VTOTAL) / 2) { + /* PAL video mode */ + if (par->htotal != PAL_HTOTAL) { + DPRINTK("htotal invalid for pal\n"); + return -EINVAL; + } + if (par->diwstrt_h < PAL_DIWSTRT_H) { + DPRINTK("diwstrt_h too low for pal\n"); + return -EINVAL; + } + if (par->diwstrt_v < PAL_DIWSTRT_V) { + DPRINTK("diwstrt_v too low for pal\n"); + return -EINVAL; + } + htotal = PAL_HTOTAL>>clk_shift; + vtotal = PAL_VTOTAL>>1; + if (!IS_OCS) { + par->beamcon0 = BMC0_PAL; + par->bplcon3 |= BPC3_BRDRBLNK; + } else if (AMIGAHW_PRESENT(AGNUS_HR_PAL) || + AMIGAHW_PRESENT(AGNUS_HR_NTSC)) { + par->beamcon0 = BMC0_PAL; + par->hsstop = 1; + } else if (amiga_vblank != 50) { + DPRINTK("pal not supported by this chipset\n"); + return -EINVAL; + } + } else { + /* NTSC video mode + * In the AGA chipset seems to be hardware bug with BPC3_BRDRBLNK + * and NTSC activated, so than better let diwstop_h <= 1812 + */ + if (par->htotal != NTSC_HTOTAL) { + DPRINTK("htotal invalid for ntsc\n"); + return -EINVAL; + } + if (par->diwstrt_h < NTSC_DIWSTRT_H) { + DPRINTK("diwstrt_h too low for ntsc\n"); + return -EINVAL; + } + if (par->diwstrt_v < NTSC_DIWSTRT_V) { + DPRINTK("diwstrt_v too low for ntsc\n"); + return -EINVAL; + } + htotal = NTSC_HTOTAL>>clk_shift; + vtotal = NTSC_VTOTAL>>1; + if (!IS_OCS) { + par->beamcon0 = 0; + par->bplcon3 |= BPC3_BRDRBLNK; + } else if (AMIGAHW_PRESENT(AGNUS_HR_PAL) || + AMIGAHW_PRESENT(AGNUS_HR_NTSC)) { + par->beamcon0 = 0; + par->hsstop = 1; + } else if (amiga_vblank != 60) { + DPRINTK("ntsc not supported by this chipset\n"); + return -EINVAL; + } + } + if (IS_OCS) { + if (par->diwstrt_h >= 1024 || par->diwstop_h < 1024 || + par->diwstrt_v >= 512 || par->diwstop_v < 256) { + DPRINTK("invalid position for display on ocs\n"); + return -EINVAL; + } + } + } else if (!IS_OCS) { + /* Programmable video mode */ + par->hsstrt = var->right_margin << clk_shift; + par->hsstop = (var->right_margin + var->hsync_len) << clk_shift; + par->diwstop_h = par->htotal - mod8(par->hsstrt) + 8 - (1 << clk_shift); + if (!IS_AGA) + par->diwstop_h = down4(par->diwstop_h) - 16; + par->diwstrt_h = par->diwstop_h - xres_n; + par->hbstop = par->diwstrt_h + 4; + par->hbstrt = par->diwstop_h + 4; + if (par->hbstrt >= par->htotal + 8) + par->hbstrt -= par->htotal; + par->hcenter = par->hsstrt + (par->htotal >> 1); + par->vsstrt = var->lower_margin << line_shift; + par->vsstop = (var->lower_margin + var->vsync_len) << line_shift; + par->diwstop_v = par->vtotal; + if ((par->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) + par->diwstop_v -= 2; + par->diwstrt_v = par->diwstop_v - yres_n; + par->vbstop = par->diwstrt_v - 2; + par->vbstrt = par->diwstop_v - 2; + if (par->vtotal > 2048) { + DPRINTK("vtotal too high\n"); + return -EINVAL; + } + if (par->htotal > 2048) { + DPRINTK("htotal too high\n"); + return -EINVAL; + } + par->bplcon3 |= BPC3_EXTBLKEN; + par->beamcon0 = BMC0_HARDDIS | BMC0_VARVBEN | BMC0_LOLDIS | + BMC0_VARVSYEN | BMC0_VARHSYEN | BMC0_VARBEAMEN | + BMC0_PAL | BMC0_VARCSYEN; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + par->beamcon0 |= BMC0_HSYTRUE; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + par->beamcon0 |= BMC0_VSYTRUE; + if (var->sync & FB_SYNC_COMP_HIGH_ACT) + par->beamcon0 |= BMC0_CSYTRUE; + htotal = par->htotal>>clk_shift; + vtotal = par->vtotal>>1; + } else { + DPRINTK("only broadcast modes possible for ocs\n"); + return -EINVAL; + } + + /* + * Checking the DMA timing + */ + + fconst = 16 << maxfmode << clk_shift; + + /* + * smallest window start value without turn off other dma cycles + * than sprite1-7, unless you change min_fstrt + */ + + + fsize = ((maxfmode + clk_shift <= 1) ? fconst : 64); + fstrt = downx(fconst, par->diwstrt_h - 4) - fsize; + if (fstrt < min_fstrt) { + DPRINTK("fetch start too low\n"); + return -EINVAL; + } + + /* + * smallest window start value where smooth scrolling is possible + */ + + fstrt = downx(fconst, par->diwstrt_h - fconst + (1 << clk_shift) - 4) - + fsize; + if (fstrt < min_fstrt) + par->vmode &= ~FB_VMODE_SMOOTH_XPAN; + + maxfetchstop = down16(par->htotal - 80); + + fstrt = downx(fconst, par->diwstrt_h - 4) - 64 - fconst; + fsize = upx(fconst, xres_n + + modx(fconst, downx(1 << clk_shift, par->diwstrt_h - 4))); + if (fstrt + fsize > maxfetchstop) + par->vmode &= ~FB_VMODE_SMOOTH_XPAN; + + fsize = upx(fconst, xres_n); + if (fstrt + fsize > maxfetchstop) { + DPRINTK("fetch stop too high\n"); + return -EINVAL; + } + + if (maxfmode + clk_shift <= 1) { + fsize = up64(xres_n + fconst - 1); + if (min_fstrt + fsize - 64 > maxfetchstop) + par->vmode &= ~FB_VMODE_SMOOTH_XPAN; + + fsize = up64(xres_n); + if (min_fstrt + fsize - 64 > maxfetchstop) { + DPRINTK("fetch size too high\n"); + return -EINVAL; + } + + fsize -= 64; + } else + fsize -= fconst; + + /* + * Check if there is enough time to update the bitplane pointers for ywrap + */ + + if (par->htotal - fsize - 64 < par->bpp * 64) + par->vmode &= ~FB_VMODE_YWRAP; + + /* + * Bitplane calculations and check the Memory Requirements + */ + + if (amifb_ilbm) { + par->next_plane = div8(upx(16 << maxfmode, par->vxres)); + par->next_line = par->bpp * par->next_plane; + if (par->next_line * par->vyres > info->fix.smem_len) { + DPRINTK("too few video mem\n"); + return -EINVAL; + } + } else { + par->next_line = div8(upx(16 << maxfmode, par->vxres)); + par->next_plane = par->vyres * par->next_line; + if (par->next_plane * par->bpp > info->fix.smem_len) { + DPRINTK("too few video mem\n"); + return -EINVAL; + } + } + + /* + * Hardware Register Values + */ + + par->bplcon0 = BPC0_COLOR | bplpixmode[clk_shift]; + if (!IS_OCS) + par->bplcon0 |= BPC0_ECSENA; + if (par->bpp == 8) + par->bplcon0 |= BPC0_BPU3; + else + par->bplcon0 |= par->bpp << 12; + if (var->nonstd == FB_NONSTD_HAM) + par->bplcon0 |= BPC0_HAM; + if (var->sync & FB_SYNC_EXT) + par->bplcon0 |= BPC0_ERSY; + + if (IS_AGA) + par->fmode = bplfetchmode[maxfmode]; + + switch (par->vmode & FB_VMODE_MASK) { + case FB_VMODE_INTERLACED: + par->bplcon0 |= BPC0_LACE; + break; + case FB_VMODE_DOUBLE: + if (IS_AGA) + par->fmode |= FMODE_SSCAN2 | FMODE_BSCAN2; + break; + } + + if (!((par->vmode ^ var->vmode) & FB_VMODE_YWRAP)) { + par->xoffset = var->xoffset; + par->yoffset = var->yoffset; + if (par->vmode & FB_VMODE_YWRAP) { + if (par->xoffset || par->yoffset < 0 || + par->yoffset >= par->vyres) + par->xoffset = par->yoffset = 0; + } else { + if (par->xoffset < 0 || + par->xoffset > upx(16 << maxfmode, par->vxres - par->xres) || + par->yoffset < 0 || par->yoffset > par->vyres - par->yres) + par->xoffset = par->yoffset = 0; + } + } else + par->xoffset = par->yoffset = 0; + + par->crsr.crsr_x = par->crsr.crsr_y = 0; + par->crsr.spot_x = par->crsr.spot_y = 0; + par->crsr.height = par->crsr.width = 0; + + return 0; +} + + /* + * Fill the `var' structure based on the values in `par' and maybe + * other values read out of the hardware. + */ + +static void ami_encode_var(struct fb_var_screeninfo *var, + struct amifb_par *par) +{ + u_short clk_shift, line_shift; + + memset(var, 0, sizeof(struct fb_var_screeninfo)); + + clk_shift = par->clk_shift; + line_shift = par->line_shift; + + var->xres = par->xres; + var->yres = par->yres; + var->xres_virtual = par->vxres; + var->yres_virtual = par->vyres; + var->xoffset = par->xoffset; + var->yoffset = par->yoffset; + + var->bits_per_pixel = par->bpp; + var->grayscale = 0; + + var->red.offset = 0; + var->red.msb_right = 0; + var->red.length = par->bpp; + if (par->bplcon0 & BPC0_HAM) + var->red.length -= 2; + var->blue = var->green = var->red; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + + if (par->bplcon0 & BPC0_HAM) + var->nonstd = FB_NONSTD_HAM; + else + var->nonstd = 0; + var->activate = 0; + + var->height = -1; + var->width = -1; + + var->pixclock = pixclock[clk_shift]; + + if (IS_AGA && par->fmode & FMODE_BSCAN2) + var->vmode = FB_VMODE_DOUBLE; + else if (par->bplcon0 & BPC0_LACE) + var->vmode = FB_VMODE_INTERLACED; + else + var->vmode = FB_VMODE_NONINTERLACED; + + if (!IS_OCS && par->beamcon0 & BMC0_VARBEAMEN) { + var->hsync_len = (par->hsstop - par->hsstrt)>>clk_shift; + var->right_margin = par->hsstrt>>clk_shift; + var->left_margin = (par->htotal>>clk_shift) - var->xres - var->right_margin - var->hsync_len; + var->vsync_len = (par->vsstop - par->vsstrt)>>line_shift; + var->lower_margin = par->vsstrt>>line_shift; + var->upper_margin = (par->vtotal>>line_shift) - var->yres - var->lower_margin - var->vsync_len; + var->sync = 0; + if (par->beamcon0 & BMC0_HSYTRUE) + var->sync |= FB_SYNC_HOR_HIGH_ACT; + if (par->beamcon0 & BMC0_VSYTRUE) + var->sync |= FB_SYNC_VERT_HIGH_ACT; + if (par->beamcon0 & BMC0_CSYTRUE) + var->sync |= FB_SYNC_COMP_HIGH_ACT; + } else { + var->sync = FB_SYNC_BROADCAST; + var->hsync_len = (152>>clk_shift) + mod4(par->diwstop_h); + var->right_margin = ((par->htotal - down4(par->diwstop_h))>>clk_shift) + var->hsync_len; + var->left_margin = (par->htotal>>clk_shift) - var->xres - var->right_margin - var->hsync_len; + var->vsync_len = 4>>line_shift; + var->lower_margin = ((par->vtotal - par->diwstop_v)>>line_shift) + var->vsync_len; + var->upper_margin = (((par->vtotal - 2)>>line_shift) + 1) - var->yres - + var->lower_margin - var->vsync_len; + } + + if (par->bplcon0 & BPC0_ERSY) + var->sync |= FB_SYNC_EXT; + if (par->vmode & FB_VMODE_YWRAP) + var->vmode |= FB_VMODE_YWRAP; +} + + + /* + * Update hardware + */ + +static void ami_update_par(struct fb_info *info) +{ + struct amifb_par *par = info->par; + short clk_shift, vshift, fstrt, fsize, fstop, fconst, shift, move, mod; + + clk_shift = par->clk_shift; + + if (!(par->vmode & FB_VMODE_SMOOTH_XPAN)) + par->xoffset = upx(16 << maxfmode, par->xoffset); + + fconst = 16 << maxfmode << clk_shift; + vshift = modx(16 << maxfmode, par->xoffset); + fstrt = par->diwstrt_h - (vshift << clk_shift) - 4; + fsize = (par->xres + vshift) << clk_shift; + shift = modx(fconst, fstrt); + move = downx(2 << maxfmode, div8(par->xoffset)); + if (maxfmode + clk_shift > 1) { + fstrt = downx(fconst, fstrt) - 64; + fsize = upx(fconst, fsize); + fstop = fstrt + fsize - fconst; + } else { + mod = fstrt = downx(fconst, fstrt) - fconst; + fstop = fstrt + upx(fconst, fsize) - 64; + fsize = up64(fsize); + fstrt = fstop - fsize + 64; + if (fstrt < min_fstrt) { + fstop += min_fstrt - fstrt; + fstrt = min_fstrt; + } + move = move - div8((mod - fstrt)>>clk_shift); + } + mod = par->next_line - div8(fsize>>clk_shift); + par->ddfstrt = fstrt; + par->ddfstop = fstop; + par->bplcon1 = hscroll2hw(shift); + par->bpl2mod = mod; + if (par->bplcon0 & BPC0_LACE) + par->bpl2mod += par->next_line; + if (IS_AGA && (par->fmode & FMODE_BSCAN2)) + par->bpl1mod = -div8(fsize>>clk_shift); + else + par->bpl1mod = par->bpl2mod; + + if (par->yoffset) { + par->bplpt0 = info->fix.smem_start + + par->next_line * par->yoffset + move; + if (par->vmode & FB_VMODE_YWRAP) { + if (par->yoffset > par->vyres - par->yres) { + par->bplpt0wrap = info->fix.smem_start + move; + if (par->bplcon0 & BPC0_LACE && + mod2(par->diwstrt_v + par->vyres - + par->yoffset)) + par->bplpt0wrap += par->next_line; + } + } + } else + par->bplpt0 = info->fix.smem_start + move; + + if (par->bplcon0 & BPC0_LACE && mod2(par->diwstrt_v)) + par->bplpt0 += par->next_line; +} + + + /* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * in `var'. + */ + +static void ami_pan_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct amifb_par *par = info->par; + + par->xoffset = var->xoffset; + par->yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + par->vmode |= FB_VMODE_YWRAP; + else + par->vmode &= ~FB_VMODE_YWRAP; + + do_vmode_pan = 0; + ami_update_par(info); + do_vmode_pan = 1; +} + + +static void ami_update_display(const struct amifb_par *par) +{ + custom.bplcon1 = par->bplcon1; + custom.bpl1mod = par->bpl1mod; + custom.bpl2mod = par->bpl2mod; + custom.ddfstrt = ddfstrt2hw(par->ddfstrt); + custom.ddfstop = ddfstop2hw(par->ddfstop); +} + + /* + * Change the video mode (called by VBlank interrupt) + */ + +static void ami_init_display(const struct amifb_par *par) +{ + int i; + + custom.bplcon0 = par->bplcon0 & ~BPC0_LACE; + custom.bplcon2 = (IS_OCS ? 0 : BPC2_KILLEHB) | BPC2_PF2P2 | BPC2_PF1P2; + if (!IS_OCS) { + custom.bplcon3 = par->bplcon3; + if (IS_AGA) + custom.bplcon4 = BPC4_ESPRM4 | BPC4_OSPRM4; + if (par->beamcon0 & BMC0_VARBEAMEN) { + custom.htotal = htotal2hw(par->htotal); + custom.hbstrt = hbstrt2hw(par->hbstrt); + custom.hbstop = hbstop2hw(par->hbstop); + custom.hsstrt = hsstrt2hw(par->hsstrt); + custom.hsstop = hsstop2hw(par->hsstop); + custom.hcenter = hcenter2hw(par->hcenter); + custom.vtotal = vtotal2hw(par->vtotal); + custom.vbstrt = vbstrt2hw(par->vbstrt); + custom.vbstop = vbstop2hw(par->vbstop); + custom.vsstrt = vsstrt2hw(par->vsstrt); + custom.vsstop = vsstop2hw(par->vsstop); + } + } + if (!IS_OCS || par->hsstop) + custom.beamcon0 = par->beamcon0; + if (IS_AGA) + custom.fmode = par->fmode; + + /* + * The minimum period for audio depends on htotal + */ + + amiga_audio_min_period = div16(par->htotal); + + is_lace = par->bplcon0 & BPC0_LACE ? 1 : 0; +#if 1 + if (is_lace) { + i = custom.vposr >> 15; + } else { + custom.vposw = custom.vposr | 0x8000; + i = 1; + } +#else + i = 1; + custom.vposw = custom.vposr | 0x8000; +#endif + custom.cop2lc = (u_short *)ZTWO_PADDR(copdisplay.list[currentcop][i]); +} + + /* + * (Un)Blank the screen (called by VBlank interrupt) + */ + +static void ami_do_blank(const struct amifb_par *par) +{ +#if defined(CONFIG_FB_AMIGA_AGA) + u_short bplcon3 = par->bplcon3; +#endif + u_char red, green, blue; + + if (do_blank > 0) { + custom.dmacon = DMAF_RASTER | DMAF_SPRITE; + red = green = blue = 0; + if (!IS_OCS && do_blank > 1) { + switch (do_blank) { + case FB_BLANK_VSYNC_SUSPEND: + custom.hsstrt = hsstrt2hw(par->hsstrt); + custom.hsstop = hsstop2hw(par->hsstop); + custom.vsstrt = vsstrt2hw(par->vtotal + 4); + custom.vsstop = vsstop2hw(par->vtotal + 4); + break; + case FB_BLANK_HSYNC_SUSPEND: + custom.hsstrt = hsstrt2hw(par->htotal + 16); + custom.hsstop = hsstop2hw(par->htotal + 16); + custom.vsstrt = vsstrt2hw(par->vsstrt); + custom.vsstop = vsstrt2hw(par->vsstop); + break; + case FB_BLANK_POWERDOWN: + custom.hsstrt = hsstrt2hw(par->htotal + 16); + custom.hsstop = hsstop2hw(par->htotal + 16); + custom.vsstrt = vsstrt2hw(par->vtotal + 4); + custom.vsstop = vsstop2hw(par->vtotal + 4); + break; + } + if (!(par->beamcon0 & BMC0_VARBEAMEN)) { + custom.htotal = htotal2hw(par->htotal); + custom.vtotal = vtotal2hw(par->vtotal); + custom.beamcon0 = BMC0_HARDDIS | BMC0_VARBEAMEN | + BMC0_VARVSYEN | BMC0_VARHSYEN | BMC0_VARCSYEN; + } + } + } else { + custom.dmacon = DMAF_SETCLR | DMAF_RASTER | DMAF_SPRITE; + red = red0; + green = green0; + blue = blue0; + if (!IS_OCS) { + custom.hsstrt = hsstrt2hw(par->hsstrt); + custom.hsstop = hsstop2hw(par->hsstop); + custom.vsstrt = vsstrt2hw(par->vsstrt); + custom.vsstop = vsstop2hw(par->vsstop); + custom.beamcon0 = par->beamcon0; + } + } +#if defined(CONFIG_FB_AMIGA_AGA) + if (IS_AGA) { + custom.bplcon3 = bplcon3; + custom.color[0] = rgb2hw8_high(red, green, blue); + custom.bplcon3 = bplcon3 | BPC3_LOCT; + custom.color[0] = rgb2hw8_low(red, green, blue); + custom.bplcon3 = bplcon3; + } else +#endif +#if defined(CONFIG_FB_AMIGA_ECS) + if (par->bplcon0 & BPC0_SHRES) { + u_short color, mask; + int i; + + mask = 0x3333; + color = rgb2hw2(red, green, blue); + for (i = 12; i >= 0; i -= 4) + custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color; + mask <<= 2; color >>= 2; + for (i = 3; i >= 0; i--) + custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color; + } else +#endif + custom.color[0] = rgb2hw4(red, green, blue); + is_blanked = do_blank > 0 ? do_blank : 0; +} + +static int ami_get_fix_cursorinfo(struct fb_fix_cursorinfo *fix, + const struct amifb_par *par) +{ + fix->crsr_width = fix->crsr_xsize = par->crsr.width; + fix->crsr_height = fix->crsr_ysize = par->crsr.height; + fix->crsr_color1 = 17; + fix->crsr_color2 = 18; + return 0; +} + +static int ami_get_var_cursorinfo(struct fb_var_cursorinfo *var, + u_char __user *data, + const struct amifb_par *par) +{ + register u_short *lspr, *sspr; +#ifdef __mc68000__ + register u_long datawords asm ("d2"); +#else + register u_long datawords; +#endif + register short delta; + register u_char color; + short height, width, bits, words; + int size, alloc; + + size = par->crsr.height * par->crsr.width; + alloc = var->height * var->width; + var->height = par->crsr.height; + var->width = par->crsr.width; + var->xspot = par->crsr.spot_x; + var->yspot = par->crsr.spot_y; + if (size > var->height * var->width) + return -ENAMETOOLONG; + if (!access_ok(VERIFY_WRITE, data, size)) + return -EFAULT; + delta = 1 << par->crsr.fmode; + lspr = lofsprite + (delta << 1); + if (par->bplcon0 & BPC0_LACE) + sspr = shfsprite + (delta << 1); + else + sspr = NULL; + for (height = (short)var->height - 1; height >= 0; height--) { + bits = 0; words = delta; datawords = 0; + for (width = (short)var->width - 1; width >= 0; width--) { + if (bits == 0) { + bits = 16; --words; +#ifdef __mc68000__ + asm volatile ("movew %1@(%3:w:2),%0 ; swap %0 ; movew %1@+,%0" + : "=d" (datawords), "=a" (lspr) : "1" (lspr), "d" (delta)); +#else + datawords = (*(lspr + delta) << 16) | (*lspr++); +#endif + } + --bits; +#ifdef __mc68000__ + asm volatile ( + "clrb %0 ; swap %1 ; lslw #1,%1 ; roxlb #1,%0 ; " + "swap %1 ; lslw #1,%1 ; roxlb #1,%0" + : "=d" (color), "=d" (datawords) : "1" (datawords)); +#else + color = (((datawords >> 30) & 2) + | ((datawords >> 15) & 1)); + datawords <<= 1; +#endif + put_user(color, data++); + } + if (bits > 0) { + --words; ++lspr; + } + while (--words >= 0) + ++lspr; +#ifdef __mc68000__ + asm volatile ("lea %0@(%4:w:2),%0 ; tstl %1 ; jeq 1f ; exg %0,%1\n1:" + : "=a" (lspr), "=a" (sspr) : "0" (lspr), "1" (sspr), "d" (delta)); +#else + lspr += delta; + if (sspr) { + u_short *tmp = lspr; + lspr = sspr; + sspr = tmp; + } +#endif + } + return 0; +} + +static int ami_set_var_cursorinfo(struct fb_var_cursorinfo *var, + u_char __user *data, struct amifb_par *par) +{ + register u_short *lspr, *sspr; +#ifdef __mc68000__ + register u_long datawords asm ("d2"); +#else + register u_long datawords; +#endif + register short delta; + u_short fmode; + short height, width, bits, words; + + if (!var->width) + return -EINVAL; + else if (var->width <= 16) + fmode = TAG_FMODE_1; + else if (var->width <= 32) + fmode = TAG_FMODE_2; + else if (var->width <= 64) + fmode = TAG_FMODE_4; + else + return -EINVAL; + if (fmode > maxfmode) + return -EINVAL; + if (!var->height) + return -EINVAL; + if (!access_ok(VERIFY_READ, data, var->width * var->height)) + return -EFAULT; + delta = 1 << fmode; + lofsprite = shfsprite = (u_short *)spritememory; + lspr = lofsprite + (delta << 1); + if (par->bplcon0 & BPC0_LACE) { + if (((var->height + 4) << fmode << 2) > SPRITEMEMSIZE) + return -EINVAL; + memset(lspr, 0, (var->height + 4) << fmode << 2); + shfsprite += ((var->height + 5)&-2) << fmode; + sspr = shfsprite + (delta << 1); + } else { + if (((var->height + 2) << fmode << 2) > SPRITEMEMSIZE) + return -EINVAL; + memset(lspr, 0, (var->height + 2) << fmode << 2); + sspr = NULL; + } + for (height = (short)var->height - 1; height >= 0; height--) { + bits = 16; words = delta; datawords = 0; + for (width = (short)var->width - 1; width >= 0; width--) { + unsigned long tdata = 0; + get_user(tdata, data); + data++; +#ifdef __mc68000__ + asm volatile ( + "lsrb #1,%2 ; roxlw #1,%0 ; swap %0 ; " + "lsrb #1,%2 ; roxlw #1,%0 ; swap %0" + : "=d" (datawords) + : "0" (datawords), "d" (tdata)); +#else + datawords = ((datawords << 1) & 0xfffefffe); + datawords |= tdata & 1; + datawords |= (tdata & 2) << (16 - 1); +#endif + if (--bits == 0) { + bits = 16; --words; +#ifdef __mc68000__ + asm volatile ("swap %2 ; movew %2,%0@(%3:w:2) ; swap %2 ; movew %2,%0@+" + : "=a" (lspr) : "0" (lspr), "d" (datawords), "d" (delta)); +#else + *(lspr + delta) = (u_short) (datawords >> 16); + *lspr++ = (u_short) (datawords & 0xffff); +#endif + } + } + if (bits < 16) { + --words; +#ifdef __mc68000__ + asm volatile ( + "swap %2 ; lslw %4,%2 ; movew %2,%0@(%3:w:2) ; " + "swap %2 ; lslw %4,%2 ; movew %2,%0@+" + : "=a" (lspr) : "0" (lspr), "d" (datawords), "d" (delta), "d" (bits)); +#else + *(lspr + delta) = (u_short) (datawords >> (16 + bits)); + *lspr++ = (u_short) ((datawords & 0x0000ffff) >> bits); +#endif + } + while (--words >= 0) { +#ifdef __mc68000__ + asm volatile ("moveql #0,%%d0 ; movew %%d0,%0@(%2:w:2) ; movew %%d0,%0@+" + : "=a" (lspr) : "0" (lspr), "d" (delta) : "d0"); +#else + *(lspr + delta) = 0; + *lspr++ = 0; +#endif + } +#ifdef __mc68000__ + asm volatile ("lea %0@(%4:w:2),%0 ; tstl %1 ; jeq 1f ; exg %0,%1\n1:" + : "=a" (lspr), "=a" (sspr) : "0" (lspr), "1" (sspr), "d" (delta)); +#else + lspr += delta; + if (sspr) { + u_short *tmp = lspr; + lspr = sspr; + sspr = tmp; + } +#endif + } + par->crsr.height = var->height; + par->crsr.width = var->width; + par->crsr.spot_x = var->xspot; + par->crsr.spot_y = var->yspot; + par->crsr.fmode = fmode; + if (IS_AGA) { + par->fmode &= ~(FMODE_SPAGEM | FMODE_SPR32); + par->fmode |= sprfetchmode[fmode]; + custom.fmode = par->fmode; + } + return 0; +} + +static int ami_get_cursorstate(struct fb_cursorstate *state, + const struct amifb_par *par) +{ + state->xoffset = par->crsr.crsr_x; + state->yoffset = par->crsr.crsr_y; + state->mode = cursormode; + return 0; +} + +static int ami_set_cursorstate(struct fb_cursorstate *state, + struct amifb_par *par) +{ + par->crsr.crsr_x = state->xoffset; + par->crsr.crsr_y = state->yoffset; + if ((cursormode = state->mode) == FB_CURSOR_OFF) + cursorstate = -1; + do_cursor = 1; + return 0; +} + +static void ami_set_sprite(const struct amifb_par *par) +{ + copins *copl, *cops; + u_short hs, vs, ve; + u_long pl, ps, pt; + short mx, my; + + cops = copdisplay.list[currentcop][0]; + copl = copdisplay.list[currentcop][1]; + ps = pl = ZTWO_PADDR(dummysprite); + mx = par->crsr.crsr_x - par->crsr.spot_x; + my = par->crsr.crsr_y - par->crsr.spot_y; + if (!(par->vmode & FB_VMODE_YWRAP)) { + mx -= par->xoffset; + my -= par->yoffset; + } + if (!is_blanked && cursorstate > 0 && par->crsr.height > 0 && + mx > -(short)par->crsr.width && mx < par->xres && + my > -(short)par->crsr.height && my < par->yres) { + pl = ZTWO_PADDR(lofsprite); + hs = par->diwstrt_h + (mx << par->clk_shift) - 4; + vs = par->diwstrt_v + (my << par->line_shift); + ve = vs + (par->crsr.height << par->line_shift); + if (par->bplcon0 & BPC0_LACE) { + ps = ZTWO_PADDR(shfsprite); + lofsprite[0] = spr2hw_pos(vs, hs); + shfsprite[0] = spr2hw_pos(vs + 1, hs); + if (mod2(vs)) { + lofsprite[1 << par->crsr.fmode] = spr2hw_ctl(vs, hs, ve); + shfsprite[1 << par->crsr.fmode] = spr2hw_ctl(vs + 1, hs, ve + 1); + pt = pl; pl = ps; ps = pt; + } else { + lofsprite[1 << par->crsr.fmode] = spr2hw_ctl(vs, hs, ve + 1); + shfsprite[1 << par->crsr.fmode] = spr2hw_ctl(vs + 1, hs, ve); + } + } else { + lofsprite[0] = spr2hw_pos(vs, hs) | (IS_AGA && (par->fmode & FMODE_BSCAN2) ? 0x80 : 0); + lofsprite[1 << par->crsr.fmode] = spr2hw_ctl(vs, hs, ve); + } + } + copl[cop_spr0ptrh].w[1] = highw(pl); + copl[cop_spr0ptrl].w[1] = loww(pl); + if (par->bplcon0 & BPC0_LACE) { + cops[cop_spr0ptrh].w[1] = highw(ps); + cops[cop_spr0ptrl].w[1] = loww(ps); + } +} + + + /* + * Initialise the Copper Initialisation List + */ + +static void __init ami_init_copper(void) +{ + copins *cop = copdisplay.init; + u_long p; + int i; + + if (!IS_OCS) { + (cop++)->l = CMOVE(BPC0_COLOR | BPC0_SHRES | BPC0_ECSENA, bplcon0); + (cop++)->l = CMOVE(0x0181, diwstrt); + (cop++)->l = CMOVE(0x0281, diwstop); + (cop++)->l = CMOVE(0x0000, diwhigh); + } else + (cop++)->l = CMOVE(BPC0_COLOR, bplcon0); + p = ZTWO_PADDR(dummysprite); + for (i = 0; i < 8; i++) { + (cop++)->l = CMOVE(0, spr[i].pos); + (cop++)->l = CMOVE(highw(p), sprpt[i]); + (cop++)->l = CMOVE2(loww(p), sprpt[i]); + } + + (cop++)->l = CMOVE(IF_SETCLR | IF_COPER, intreq); + copdisplay.wait = cop; + (cop++)->l = CEND; + (cop++)->l = CMOVE(0, copjmp2); + cop->l = CEND; + + custom.cop1lc = (u_short *)ZTWO_PADDR(copdisplay.init); + custom.copjmp1 = 0; +} + +static void ami_reinit_copper(const struct amifb_par *par) +{ + copdisplay.init[cip_bplcon0].w[1] = ~(BPC0_BPU3 | BPC0_BPU2 | BPC0_BPU1 | BPC0_BPU0) & par->bplcon0; + copdisplay.wait->l = CWAIT(32, par->diwstrt_v - 4); +} + + + /* + * Rebuild the Copper List + * + * We only change the things that are not static + */ + +static void ami_rebuild_copper(const struct amifb_par *par) +{ + copins *copl, *cops; + u_short line, h_end1, h_end2; + short i; + u_long p; + + if (IS_AGA && maxfmode + par->clk_shift == 0) + h_end1 = par->diwstrt_h - 64; + else + h_end1 = par->htotal - 32; + h_end2 = par->ddfstop + 64; + + ami_set_sprite(par); + + copl = copdisplay.rebuild[1]; + p = par->bplpt0; + if (par->vmode & FB_VMODE_YWRAP) { + if ((par->vyres - par->yoffset) != 1 || !mod2(par->diwstrt_v)) { + if (par->yoffset > par->vyres - par->yres) { + for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) { + (copl++)->l = CMOVE(highw(p), bplpt[i]); + (copl++)->l = CMOVE2(loww(p), bplpt[i]); + } + line = par->diwstrt_v + ((par->vyres - par->yoffset) << par->line_shift) - 1; + while (line >= 512) { + (copl++)->l = CWAIT(h_end1, 510); + line -= 512; + } + if (line >= 510 && IS_AGA && maxfmode + par->clk_shift == 0) + (copl++)->l = CWAIT(h_end1, line); + else + (copl++)->l = CWAIT(h_end2, line); + p = par->bplpt0wrap; + } + } else + p = par->bplpt0wrap; + } + for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) { + (copl++)->l = CMOVE(highw(p), bplpt[i]); + (copl++)->l = CMOVE2(loww(p), bplpt[i]); + } + copl->l = CEND; + + if (par->bplcon0 & BPC0_LACE) { + cops = copdisplay.rebuild[0]; + p = par->bplpt0; + if (mod2(par->diwstrt_v)) + p -= par->next_line; + else + p += par->next_line; + if (par->vmode & FB_VMODE_YWRAP) { + if ((par->vyres - par->yoffset) != 1 || mod2(par->diwstrt_v)) { + if (par->yoffset > par->vyres - par->yres + 1) { + for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) { + (cops++)->l = CMOVE(highw(p), bplpt[i]); + (cops++)->l = CMOVE2(loww(p), bplpt[i]); + } + line = par->diwstrt_v + ((par->vyres - par->yoffset) << par->line_shift) - 2; + while (line >= 512) { + (cops++)->l = CWAIT(h_end1, 510); + line -= 512; + } + if (line > 510 && IS_AGA && maxfmode + par->clk_shift == 0) + (cops++)->l = CWAIT(h_end1, line); + else + (cops++)->l = CWAIT(h_end2, line); + p = par->bplpt0wrap; + if (mod2(par->diwstrt_v + par->vyres - + par->yoffset)) + p -= par->next_line; + else + p += par->next_line; + } + } else + p = par->bplpt0wrap - par->next_line; + } + for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) { + (cops++)->l = CMOVE(highw(p), bplpt[i]); + (cops++)->l = CMOVE2(loww(p), bplpt[i]); + } + cops->l = CEND; + } +} + + + /* + * Build the Copper List + */ + +static void ami_build_copper(struct fb_info *info) +{ + struct amifb_par *par = info->par; + copins *copl, *cops; + u_long p; + + currentcop = 1 - currentcop; + + copl = copdisplay.list[currentcop][1]; + + (copl++)->l = CWAIT(0, 10); + (copl++)->l = CMOVE(par->bplcon0, bplcon0); + (copl++)->l = CMOVE(0, sprpt[0]); + (copl++)->l = CMOVE2(0, sprpt[0]); + + if (par->bplcon0 & BPC0_LACE) { + cops = copdisplay.list[currentcop][0]; + + (cops++)->l = CWAIT(0, 10); + (cops++)->l = CMOVE(par->bplcon0, bplcon0); + (cops++)->l = CMOVE(0, sprpt[0]); + (cops++)->l = CMOVE2(0, sprpt[0]); + + (copl++)->l = CMOVE(diwstrt2hw(par->diwstrt_h, par->diwstrt_v + 1), diwstrt); + (copl++)->l = CMOVE(diwstop2hw(par->diwstop_h, par->diwstop_v + 1), diwstop); + (cops++)->l = CMOVE(diwstrt2hw(par->diwstrt_h, par->diwstrt_v), diwstrt); + (cops++)->l = CMOVE(diwstop2hw(par->diwstop_h, par->diwstop_v), diwstop); + if (!IS_OCS) { + (copl++)->l = CMOVE(diwhigh2hw(par->diwstrt_h, par->diwstrt_v + 1, + par->diwstop_h, par->diwstop_v + 1), diwhigh); + (cops++)->l = CMOVE(diwhigh2hw(par->diwstrt_h, par->diwstrt_v, + par->diwstop_h, par->diwstop_v), diwhigh); +#if 0 + if (par->beamcon0 & BMC0_VARBEAMEN) { + (copl++)->l = CMOVE(vtotal2hw(par->vtotal), vtotal); + (copl++)->l = CMOVE(vbstrt2hw(par->vbstrt + 1), vbstrt); + (copl++)->l = CMOVE(vbstop2hw(par->vbstop + 1), vbstop); + (cops++)->l = CMOVE(vtotal2hw(par->vtotal), vtotal); + (cops++)->l = CMOVE(vbstrt2hw(par->vbstrt), vbstrt); + (cops++)->l = CMOVE(vbstop2hw(par->vbstop), vbstop); + } +#endif + } + p = ZTWO_PADDR(copdisplay.list[currentcop][0]); + (copl++)->l = CMOVE(highw(p), cop2lc); + (copl++)->l = CMOVE2(loww(p), cop2lc); + p = ZTWO_PADDR(copdisplay.list[currentcop][1]); + (cops++)->l = CMOVE(highw(p), cop2lc); + (cops++)->l = CMOVE2(loww(p), cop2lc); + copdisplay.rebuild[0] = cops; + } else { + (copl++)->l = CMOVE(diwstrt2hw(par->diwstrt_h, par->diwstrt_v), diwstrt); + (copl++)->l = CMOVE(diwstop2hw(par->diwstop_h, par->diwstop_v), diwstop); + if (!IS_OCS) { + (copl++)->l = CMOVE(diwhigh2hw(par->diwstrt_h, par->diwstrt_v, + par->diwstop_h, par->diwstop_v), diwhigh); +#if 0 + if (par->beamcon0 & BMC0_VARBEAMEN) { + (copl++)->l = CMOVE(vtotal2hw(par->vtotal), vtotal); + (copl++)->l = CMOVE(vbstrt2hw(par->vbstrt), vbstrt); + (copl++)->l = CMOVE(vbstop2hw(par->vbstop), vbstop); + } +#endif + } + } + copdisplay.rebuild[1] = copl; + + ami_update_par(info); + ami_rebuild_copper(info->par); +} + + +static void __init amifb_setup_mcap(char *spec) +{ + char *p; + int vmin, vmax, hmin, hmax; + + /* Format for monitor capabilities is: <Vmin>;<Vmax>;<Hmin>;<Hmax> + * <V*> vertical freq. in Hz + * <H*> horizontal freq. in kHz + */ + + if (!(p = strsep(&spec, ";")) || !*p) + return; + vmin = simple_strtoul(p, NULL, 10); + if (vmin <= 0) + return; + if (!(p = strsep(&spec, ";")) || !*p) + return; + vmax = simple_strtoul(p, NULL, 10); + if (vmax <= 0 || vmax <= vmin) + return; + if (!(p = strsep(&spec, ";")) || !*p) + return; + hmin = 1000 * simple_strtoul(p, NULL, 10); + if (hmin <= 0) + return; + if (!(p = strsep(&spec, "")) || !*p) + return; + hmax = 1000 * simple_strtoul(p, NULL, 10); + if (hmax <= 0 || hmax <= hmin) + return; + + amifb_hfmin = hmin; + amifb_hfmax = hmax; + amifb_vfmin = vmin; + amifb_vfmax = vmax; +} + +static int __init amifb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + if (!strcmp(this_opt, "inverse")) { + fb_invert_cmaps(); + } else if (!strcmp(this_opt, "ilbm")) + amifb_ilbm = 1; + else if (!strncmp(this_opt, "monitorcap:", 11)) + amifb_setup_mcap(this_opt + 11); + else if (!strncmp(this_opt, "fstart:", 7)) + min_fstrt = simple_strtoul(this_opt + 7, NULL, 0); + else + mode_option = this_opt; + } + + if (min_fstrt < 48) + min_fstrt = 48; + + return 0; +} + + +static int amifb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int err; + struct amifb_par par; + + /* Validate wanted screen parameters */ + err = ami_decode_var(var, &par, info); + if (err) + return err; + + /* Encode (possibly rounded) screen parameters */ + ami_encode_var(var, &par); + return 0; +} + + +static int amifb_set_par(struct fb_info *info) +{ + struct amifb_par *par = info->par; + int error; + + do_vmode_pan = 0; + do_vmode_full = 0; + + /* Decode wanted screen parameters */ + error = ami_decode_var(&info->var, par, info); + if (error) + return error; + + /* Set new videomode */ + ami_build_copper(info); + + /* Set VBlank trigger */ + do_vmode_full = 1; + + /* Update fix for new screen parameters */ + if (par->bpp == 1) { + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + } else if (amifb_ilbm) { + info->fix.type = FB_TYPE_INTERLEAVED_PLANES; + info->fix.type_aux = par->next_line; + } else { + info->fix.type = FB_TYPE_PLANES; + info->fix.type_aux = 0; + } + info->fix.line_length = div8(upx(16 << maxfmode, par->vxres)); + + if (par->vmode & FB_VMODE_YWRAP) { + info->fix.ywrapstep = 1; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YWRAP | + FBINFO_READS_FAST; /* override SCROLL_REDRAW */ + } else { + info->fix.ywrapstep = 0; + if (par->vmode & FB_VMODE_SMOOTH_XPAN) + info->fix.xpanstep = 1; + else + info->fix.xpanstep = 16 << maxfmode; + info->fix.ypanstep = 1; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + } + return 0; +} + + + /* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ + +static int amifb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + const struct amifb_par *par = info->par; + + if (IS_AGA) { + if (regno > 255) + return 1; + } else if (par->bplcon0 & BPC0_SHRES) { + if (regno > 3) + return 1; + } else { + if (regno > 31) + return 1; + } + red >>= 8; + green >>= 8; + blue >>= 8; + if (!regno) { + red0 = red; + green0 = green; + blue0 = blue; + } + + /* + * Update the corresponding Hardware Color Register, unless it's Color + * Register 0 and the screen is blanked. + * + * VBlank is switched off to protect bplcon3 or ecs_palette[] from + * being changed by ami_do_blank() during the VBlank. + */ + + if (regno || !is_blanked) { +#if defined(CONFIG_FB_AMIGA_AGA) + if (IS_AGA) { + u_short bplcon3 = par->bplcon3; + VBlankOff(); + custom.bplcon3 = bplcon3 | (regno << 8 & 0xe000); + custom.color[regno & 31] = rgb2hw8_high(red, green, + blue); + custom.bplcon3 = bplcon3 | (regno << 8 & 0xe000) | + BPC3_LOCT; + custom.color[regno & 31] = rgb2hw8_low(red, green, + blue); + custom.bplcon3 = bplcon3; + VBlankOn(); + } else +#endif +#if defined(CONFIG_FB_AMIGA_ECS) + if (par->bplcon0 & BPC0_SHRES) { + u_short color, mask; + int i; + + mask = 0x3333; + color = rgb2hw2(red, green, blue); + VBlankOff(); + for (i = regno + 12; i >= (int)regno; i -= 4) + custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color; + mask <<= 2; color >>= 2; + regno = down16(regno) + mul4(mod4(regno)); + for (i = regno + 3; i >= (int)regno; i--) + custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color; + VBlankOn(); + } else +#endif + custom.color[regno] = rgb2hw4(red, green, blue); + } + return 0; +} + + + /* + * Blank the display. + */ + +static int amifb_blank(int blank, struct fb_info *info) +{ + do_blank = blank ? blank : -1; + + return 0; +} + + + /* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ + +static int amifb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->vmode & FB_VMODE_YWRAP) { + if (var->yoffset < 0 || + var->yoffset >= info->var.yres_virtual || var->xoffset) + return -EINVAL; + } else { + /* + * TODO: There will be problems when xpan!=1, so some columns + * on the right side will never be seen + */ + if (var->xoffset + info->var.xres > + upx(16 << maxfmode, info->var.xres_virtual) || + var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + } + ami_pan_var(var, info); + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + return 0; +} + + +#if BITS_PER_LONG == 32 +#define BYTES_PER_LONG 4 +#define SHIFT_PER_LONG 5 +#elif BITS_PER_LONG == 64 +#define BYTES_PER_LONG 8 +#define SHIFT_PER_LONG 6 +#else +#define Please update me +#endif + + + /* + * Compose two values, using a bitmask as decision value + * This is equivalent to (a & mask) | (b & ~mask) + */ + +static inline unsigned long comp(unsigned long a, unsigned long b, + unsigned long mask) +{ + return ((a ^ b) & mask) ^ b; +} + + +static inline unsigned long xor(unsigned long a, unsigned long b, + unsigned long mask) +{ + return (a & mask) ^ b; +} + + + /* + * Unaligned forward bit copy using 32-bit or 64-bit memory accesses + */ + +static void bitcpy(unsigned long *dst, int dst_idx, const unsigned long *src, + int src_idx, u32 n) +{ + unsigned long first, last; + int shift = dst_idx - src_idx, left, right; + unsigned long d0, d1; + int m; + + if (!n) + return; + + shift = dst_idx - src_idx; + first = ~0UL >> dst_idx; + last = ~(~0UL >> ((dst_idx + n) % BITS_PER_LONG)); + + if (!shift) { + // Same alignment for source and dest + + if (dst_idx + n <= BITS_PER_LONG) { + // Single word + if (last) + first &= last; + *dst = comp(*src, *dst, first); + } else { + // Multiple destination words + // Leading bits + if (first) { + *dst = comp(*src, *dst, first); + dst++; + src++; + n -= BITS_PER_LONG - dst_idx; + } + + // Main chunk + n /= BITS_PER_LONG; + while (n >= 8) { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + n -= 8; + } + while (n--) + *dst++ = *src++; + + // Trailing bits + if (last) + *dst = comp(*src, *dst, last); + } + } else { + // Different alignment for source and dest + + right = shift & (BITS_PER_LONG - 1); + left = -shift & (BITS_PER_LONG - 1); + + if (dst_idx + n <= BITS_PER_LONG) { + // Single destination word + if (last) + first &= last; + if (shift > 0) { + // Single source word + *dst = comp(*src >> right, *dst, first); + } else if (src_idx + n <= BITS_PER_LONG) { + // Single source word + *dst = comp(*src << left, *dst, first); + } else { + // 2 source words + d0 = *src++; + d1 = *src; + *dst = comp(d0 << left | d1 >> right, *dst, + first); + } + } else { + // Multiple destination words + d0 = *src++; + // Leading bits + if (shift > 0) { + // Single source word + *dst = comp(d0 >> right, *dst, first); + dst++; + n -= BITS_PER_LONG - dst_idx; + } else { + // 2 source words + d1 = *src++; + *dst = comp(d0 << left | d1 >> right, *dst, + first); + d0 = d1; + dst++; + n -= BITS_PER_LONG - dst_idx; + } + + // Main chunk + m = n % BITS_PER_LONG; + n /= BITS_PER_LONG; + while (n >= 4) { + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + n -= 4; + } + while (n--) { + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + } + + // Trailing bits + if (last) { + if (m <= right) { + // Single source word + *dst = comp(d0 << left, *dst, last); + } else { + // 2 source words + d1 = *src; + *dst = comp(d0 << left | d1 >> right, + *dst, last); + } + } + } + } +} + + + /* + * Unaligned reverse bit copy using 32-bit or 64-bit memory accesses + */ + +static void bitcpy_rev(unsigned long *dst, int dst_idx, + const unsigned long *src, int src_idx, u32 n) +{ + unsigned long first, last; + int shift = dst_idx - src_idx, left, right; + unsigned long d0, d1; + int m; + + if (!n) + return; + + dst += (n - 1) / BITS_PER_LONG; + src += (n - 1) / BITS_PER_LONG; + if ((n - 1) % BITS_PER_LONG) { + dst_idx += (n - 1) % BITS_PER_LONG; + dst += dst_idx >> SHIFT_PER_LONG; + dst_idx &= BITS_PER_LONG - 1; + src_idx += (n - 1) % BITS_PER_LONG; + src += src_idx >> SHIFT_PER_LONG; + src_idx &= BITS_PER_LONG - 1; + } + + shift = dst_idx - src_idx; + first = ~0UL << (BITS_PER_LONG - 1 - dst_idx); + last = ~(~0UL << (BITS_PER_LONG - 1 - ((dst_idx - n) % BITS_PER_LONG))); + + if (!shift) { + // Same alignment for source and dest + + if ((unsigned long)dst_idx + 1 >= n) { + // Single word + if (last) + first &= last; + *dst = comp(*src, *dst, first); + } else { + // Multiple destination words + // Leading bits + if (first) { + *dst = comp(*src, *dst, first); + dst--; + src--; + n -= dst_idx + 1; + } + + // Main chunk + n /= BITS_PER_LONG; + while (n >= 8) { + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + n -= 8; + } + while (n--) + *dst-- = *src--; + + // Trailing bits + if (last) + *dst = comp(*src, *dst, last); + } + } else { + // Different alignment for source and dest + + right = shift & (BITS_PER_LONG - 1); + left = -shift & (BITS_PER_LONG - 1); + + if ((unsigned long)dst_idx + 1 >= n) { + // Single destination word + if (last) + first &= last; + if (shift < 0) { + // Single source word + *dst = comp(*src << left, *dst, first); + } else if (1 + (unsigned long)src_idx >= n) { + // Single source word + *dst = comp(*src >> right, *dst, first); + } else { + // 2 source words + d0 = *src--; + d1 = *src; + *dst = comp(d0 >> right | d1 << left, *dst, + first); + } + } else { + // Multiple destination words + d0 = *src--; + // Leading bits + if (shift < 0) { + // Single source word + *dst = comp(d0 << left, *dst, first); + dst--; + n -= dst_idx + 1; + } else { + // 2 source words + d1 = *src--; + *dst = comp(d0 >> right | d1 << left, *dst, + first); + d0 = d1; + dst--; + n -= dst_idx + 1; + } + + // Main chunk + m = n % BITS_PER_LONG; + n /= BITS_PER_LONG; + while (n >= 4) { + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + n -= 4; + } + while (n--) { + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + } + + // Trailing bits + if (last) { + if (m <= left) { + // Single source word + *dst = comp(d0 >> right, *dst, last); + } else { + // 2 source words + d1 = *src; + *dst = comp(d0 >> right | d1 << left, + *dst, last); + } + } + } + } +} + + + /* + * Unaligned forward inverting bit copy using 32-bit or 64-bit memory + * accesses + */ + +static void bitcpy_not(unsigned long *dst, int dst_idx, + const unsigned long *src, int src_idx, u32 n) +{ + unsigned long first, last; + int shift = dst_idx - src_idx, left, right; + unsigned long d0, d1; + int m; + + if (!n) + return; + + shift = dst_idx - src_idx; + first = ~0UL >> dst_idx; + last = ~(~0UL >> ((dst_idx + n) % BITS_PER_LONG)); + + if (!shift) { + // Same alignment for source and dest + + if (dst_idx + n <= BITS_PER_LONG) { + // Single word + if (last) + first &= last; + *dst = comp(~*src, *dst, first); + } else { + // Multiple destination words + // Leading bits + if (first) { + *dst = comp(~*src, *dst, first); + dst++; + src++; + n -= BITS_PER_LONG - dst_idx; + } + + // Main chunk + n /= BITS_PER_LONG; + while (n >= 8) { + *dst++ = ~*src++; + *dst++ = ~*src++; + *dst++ = ~*src++; + *dst++ = ~*src++; + *dst++ = ~*src++; + *dst++ = ~*src++; + *dst++ = ~*src++; + *dst++ = ~*src++; + n -= 8; + } + while (n--) + *dst++ = ~*src++; + + // Trailing bits + if (last) + *dst = comp(~*src, *dst, last); + } + } else { + // Different alignment for source and dest + + right = shift & (BITS_PER_LONG - 1); + left = -shift & (BITS_PER_LONG - 1); + + if (dst_idx + n <= BITS_PER_LONG) { + // Single destination word + if (last) + first &= last; + if (shift > 0) { + // Single source word + *dst = comp(~*src >> right, *dst, first); + } else if (src_idx + n <= BITS_PER_LONG) { + // Single source word + *dst = comp(~*src << left, *dst, first); + } else { + // 2 source words + d0 = ~*src++; + d1 = ~*src; + *dst = comp(d0 << left | d1 >> right, *dst, + first); + } + } else { + // Multiple destination words + d0 = ~*src++; + // Leading bits + if (shift > 0) { + // Single source word + *dst = comp(d0 >> right, *dst, first); + dst++; + n -= BITS_PER_LONG - dst_idx; + } else { + // 2 source words + d1 = ~*src++; + *dst = comp(d0 << left | d1 >> right, *dst, + first); + d0 = d1; + dst++; + n -= BITS_PER_LONG - dst_idx; + } + + // Main chunk + m = n % BITS_PER_LONG; + n /= BITS_PER_LONG; + while (n >= 4) { + d1 = ~*src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = ~*src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = ~*src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = ~*src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + n -= 4; + } + while (n--) { + d1 = ~*src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + } + + // Trailing bits + if (last) { + if (m <= right) { + // Single source word + *dst = comp(d0 << left, *dst, last); + } else { + // 2 source words + d1 = ~*src; + *dst = comp(d0 << left | d1 >> right, + *dst, last); + } + } + } + } +} + + + /* + * Unaligned 32-bit pattern fill using 32/64-bit memory accesses + */ + +static void bitfill32(unsigned long *dst, int dst_idx, u32 pat, u32 n) +{ + unsigned long val = pat; + unsigned long first, last; + + if (!n) + return; + +#if BITS_PER_LONG == 64 + val |= val << 32; +#endif + + first = ~0UL >> dst_idx; + last = ~(~0UL >> ((dst_idx + n) % BITS_PER_LONG)); + + if (dst_idx + n <= BITS_PER_LONG) { + // Single word + if (last) + first &= last; + *dst = comp(val, *dst, first); + } else { + // Multiple destination words + // Leading bits + if (first) { + *dst = comp(val, *dst, first); + dst++; + n -= BITS_PER_LONG - dst_idx; + } + + // Main chunk + n /= BITS_PER_LONG; + while (n >= 8) { + *dst++ = val; + *dst++ = val; + *dst++ = val; + *dst++ = val; + *dst++ = val; + *dst++ = val; + *dst++ = val; + *dst++ = val; + n -= 8; + } + while (n--) + *dst++ = val; + + // Trailing bits + if (last) + *dst = comp(val, *dst, last); + } +} + + + /* + * Unaligned 32-bit pattern xor using 32/64-bit memory accesses + */ + +static void bitxor32(unsigned long *dst, int dst_idx, u32 pat, u32 n) +{ + unsigned long val = pat; + unsigned long first, last; + + if (!n) + return; + +#if BITS_PER_LONG == 64 + val |= val << 32; +#endif + + first = ~0UL >> dst_idx; + last = ~(~0UL >> ((dst_idx + n) % BITS_PER_LONG)); + + if (dst_idx + n <= BITS_PER_LONG) { + // Single word + if (last) + first &= last; + *dst = xor(val, *dst, first); + } else { + // Multiple destination words + // Leading bits + if (first) { + *dst = xor(val, *dst, first); + dst++; + n -= BITS_PER_LONG - dst_idx; + } + + // Main chunk + n /= BITS_PER_LONG; + while (n >= 4) { + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + n -= 4; + } + while (n--) + *dst++ ^= val; + + // Trailing bits + if (last) + *dst = xor(val, *dst, last); + } +} + +static inline void fill_one_line(int bpp, unsigned long next_plane, + unsigned long *dst, int dst_idx, u32 n, + u32 color) +{ + while (1) { + dst += dst_idx >> SHIFT_PER_LONG; + dst_idx &= (BITS_PER_LONG - 1); + bitfill32(dst, dst_idx, color & 1 ? ~0 : 0, n); + if (!--bpp) + break; + color >>= 1; + dst_idx += next_plane * 8; + } +} + +static inline void xor_one_line(int bpp, unsigned long next_plane, + unsigned long *dst, int dst_idx, u32 n, + u32 color) +{ + while (color) { + dst += dst_idx >> SHIFT_PER_LONG; + dst_idx &= (BITS_PER_LONG - 1); + bitxor32(dst, dst_idx, color & 1 ? ~0 : 0, n); + if (!--bpp) + break; + color >>= 1; + dst_idx += next_plane * 8; + } +} + + +static void amifb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct amifb_par *par = info->par; + int dst_idx, x2, y2; + unsigned long *dst; + u32 width, height; + + if (!rect->width || !rect->height) + return; + + /* + * We could use hardware clipping but on many cards you get around + * hardware clipping by writing to framebuffer directly. + * */ + x2 = rect->dx + rect->width; + y2 = rect->dy + rect->height; + x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual; + y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual; + width = x2 - rect->dx; + height = y2 - rect->dy; + + dst = (unsigned long *) + ((unsigned long)info->screen_base & ~(BYTES_PER_LONG - 1)); + dst_idx = ((unsigned long)info->screen_base & (BYTES_PER_LONG - 1)) * 8; + dst_idx += rect->dy * par->next_line * 8 + rect->dx; + while (height--) { + switch (rect->rop) { + case ROP_COPY: + fill_one_line(info->var.bits_per_pixel, + par->next_plane, dst, dst_idx, width, + rect->color); + break; + + case ROP_XOR: + xor_one_line(info->var.bits_per_pixel, par->next_plane, + dst, dst_idx, width, rect->color); + break; + } + dst_idx += par->next_line * 8; + } +} + +static inline void copy_one_line(int bpp, unsigned long next_plane, + unsigned long *dst, int dst_idx, + unsigned long *src, int src_idx, u32 n) +{ + while (1) { + dst += dst_idx >> SHIFT_PER_LONG; + dst_idx &= (BITS_PER_LONG - 1); + src += src_idx >> SHIFT_PER_LONG; + src_idx &= (BITS_PER_LONG - 1); + bitcpy(dst, dst_idx, src, src_idx, n); + if (!--bpp) + break; + dst_idx += next_plane * 8; + src_idx += next_plane * 8; + } +} + +static inline void copy_one_line_rev(int bpp, unsigned long next_plane, + unsigned long *dst, int dst_idx, + unsigned long *src, int src_idx, u32 n) +{ + while (1) { + dst += dst_idx >> SHIFT_PER_LONG; + dst_idx &= (BITS_PER_LONG - 1); + src += src_idx >> SHIFT_PER_LONG; + src_idx &= (BITS_PER_LONG - 1); + bitcpy_rev(dst, dst_idx, src, src_idx, n); + if (!--bpp) + break; + dst_idx += next_plane * 8; + src_idx += next_plane * 8; + } +} + + +static void amifb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct amifb_par *par = info->par; + int x2, y2; + u32 dx, dy, sx, sy, width, height; + unsigned long *dst, *src; + int dst_idx, src_idx; + int rev_copy = 0; + + /* clip the destination */ + x2 = area->dx + area->width; + y2 = area->dy + area->height; + dx = area->dx > 0 ? area->dx : 0; + dy = area->dy > 0 ? area->dy : 0; + x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual; + y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual; + width = x2 - dx; + height = y2 - dy; + + if (area->sx + dx < area->dx || area->sy + dy < area->dy) + return; + + /* update sx,sy */ + sx = area->sx + (dx - area->dx); + sy = area->sy + (dy - area->dy); + + /* the source must be completely inside the virtual screen */ + if (sx + width > info->var.xres_virtual || + sy + height > info->var.yres_virtual) + return; + + if (dy > sy || (dy == sy && dx > sx)) { + dy += height; + sy += height; + rev_copy = 1; + } + dst = (unsigned long *) + ((unsigned long)info->screen_base & ~(BYTES_PER_LONG - 1)); + src = dst; + dst_idx = ((unsigned long)info->screen_base & (BYTES_PER_LONG - 1)) * 8; + src_idx = dst_idx; + dst_idx += dy * par->next_line * 8 + dx; + src_idx += sy * par->next_line * 8 + sx; + if (rev_copy) { + while (height--) { + dst_idx -= par->next_line * 8; + src_idx -= par->next_line * 8; + copy_one_line_rev(info->var.bits_per_pixel, + par->next_plane, dst, dst_idx, src, + src_idx, width); + } + } else { + while (height--) { + copy_one_line(info->var.bits_per_pixel, + par->next_plane, dst, dst_idx, src, + src_idx, width); + dst_idx += par->next_line * 8; + src_idx += par->next_line * 8; + } + } +} + + +static inline void expand_one_line(int bpp, unsigned long next_plane, + unsigned long *dst, int dst_idx, u32 n, + const u8 *data, u32 bgcolor, u32 fgcolor) +{ + const unsigned long *src; + int src_idx; + + while (1) { + dst += dst_idx >> SHIFT_PER_LONG; + dst_idx &= (BITS_PER_LONG - 1); + if ((bgcolor ^ fgcolor) & 1) { + src = (unsigned long *) + ((unsigned long)data & ~(BYTES_PER_LONG - 1)); + src_idx = ((unsigned long)data & (BYTES_PER_LONG - 1)) * 8; + if (fgcolor & 1) + bitcpy(dst, dst_idx, src, src_idx, n); + else + bitcpy_not(dst, dst_idx, src, src_idx, n); + /* set or clear */ + } else + bitfill32(dst, dst_idx, fgcolor & 1 ? ~0 : 0, n); + if (!--bpp) + break; + bgcolor >>= 1; + fgcolor >>= 1; + dst_idx += next_plane * 8; + } +} + + +static void amifb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct amifb_par *par = info->par; + int x2, y2; + unsigned long *dst; + int dst_idx; + const char *src; + u32 dx, dy, width, height, pitch; + + /* + * We could use hardware clipping but on many cards you get around + * hardware clipping by writing to framebuffer directly like we are + * doing here. + */ + x2 = image->dx + image->width; + y2 = image->dy + image->height; + dx = image->dx; + dy = image->dy; + x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual; + y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual; + width = x2 - dx; + height = y2 - dy; + + if (image->depth == 1) { + dst = (unsigned long *) + ((unsigned long)info->screen_base & ~(BYTES_PER_LONG - 1)); + dst_idx = ((unsigned long)info->screen_base & (BYTES_PER_LONG - 1)) * 8; + dst_idx += dy * par->next_line * 8 + dx; + src = image->data; + pitch = (image->width + 7) / 8; + while (height--) { + expand_one_line(info->var.bits_per_pixel, + par->next_plane, dst, dst_idx, width, + src, image->bg_color, + image->fg_color); + dst_idx += par->next_line * 8; + src += pitch; + } + } else { + c2p_planar(info->screen_base, image->data, dx, dy, width, + height, par->next_line, par->next_plane, + image->width, info->var.bits_per_pixel); + } +} + + + /* + * Amiga Frame Buffer Specific ioctls + */ + +static int amifb_ioctl(struct fb_info *info, + unsigned int cmd, unsigned long arg) +{ + union { + struct fb_fix_cursorinfo fix; + struct fb_var_cursorinfo var; + struct fb_cursorstate state; + } crsr; + void __user *argp = (void __user *)arg; + int i; + + switch (cmd) { + case FBIOGET_FCURSORINFO: + i = ami_get_fix_cursorinfo(&crsr.fix, info->par); + if (i) + return i; + return copy_to_user(argp, &crsr.fix, + sizeof(crsr.fix)) ? -EFAULT : 0; + + case FBIOGET_VCURSORINFO: + i = ami_get_var_cursorinfo(&crsr.var, + ((struct fb_var_cursorinfo __user *)arg)->data, + info->par); + if (i) + return i; + return copy_to_user(argp, &crsr.var, + sizeof(crsr.var)) ? -EFAULT : 0; + + case FBIOPUT_VCURSORINFO: + if (copy_from_user(&crsr.var, argp, sizeof(crsr.var))) + return -EFAULT; + return ami_set_var_cursorinfo(&crsr.var, + ((struct fb_var_cursorinfo __user *)arg)->data, + info->par); + + case FBIOGET_CURSORSTATE: + i = ami_get_cursorstate(&crsr.state, info->par); + if (i) + return i; + return copy_to_user(argp, &crsr.state, + sizeof(crsr.state)) ? -EFAULT : 0; + + case FBIOPUT_CURSORSTATE: + if (copy_from_user(&crsr.state, argp, sizeof(crsr.state))) + return -EFAULT; + return ami_set_cursorstate(&crsr.state, info->par); + } + return -EINVAL; +} + + + /* + * Flash the cursor (called by VBlank interrupt) + */ + +static int flash_cursor(void) +{ + static int cursorcount = 1; + + if (cursormode == FB_CURSOR_FLASH) { + if (!--cursorcount) { + cursorstate = -cursorstate; + cursorcount = cursorrate; + if (!is_blanked) + return 1; + } + } + return 0; +} + + /* + * VBlank Display Interrupt + */ + +static irqreturn_t amifb_interrupt(int irq, void *dev_id) +{ + struct amifb_par *par = dev_id; + + if (do_vmode_pan || do_vmode_full) + ami_update_display(par); + + if (do_vmode_full) + ami_init_display(par); + + if (do_vmode_pan) { + flash_cursor(); + ami_rebuild_copper(par); + do_cursor = do_vmode_pan = 0; + } else if (do_cursor) { + flash_cursor(); + ami_set_sprite(par); + do_cursor = 0; + } else { + if (flash_cursor()) + ami_set_sprite(par); + } + + if (do_blank) { + ami_do_blank(par); + do_blank = 0; + } + + if (do_vmode_full) { + ami_reinit_copper(par); + do_vmode_full = 0; + } + return IRQ_HANDLED; +} + + +static struct fb_ops amifb_ops = { + .owner = THIS_MODULE, + .fb_check_var = amifb_check_var, + .fb_set_par = amifb_set_par, + .fb_setcolreg = amifb_setcolreg, + .fb_blank = amifb_blank, + .fb_pan_display = amifb_pan_display, + .fb_fillrect = amifb_fillrect, + .fb_copyarea = amifb_copyarea, + .fb_imageblit = amifb_imageblit, + .fb_ioctl = amifb_ioctl, +}; + + + /* + * Allocate, Clear and Align a Block of Chip Memory + */ + +static void *aligned_chipptr; + +static inline u_long __init chipalloc(u_long size) +{ + aligned_chipptr = amiga_chip_alloc(size, "amifb [RAM]"); + if (!aligned_chipptr) { + pr_err("amifb: No Chip RAM for frame buffer"); + return 0; + } + memset(aligned_chipptr, 0, size); + return (u_long)aligned_chipptr; +} + +static inline void chipfree(void) +{ + if (aligned_chipptr) + amiga_chip_free(aligned_chipptr); +} + + + /* + * Initialisation + */ + +static int __init amifb_probe(struct platform_device *pdev) +{ + struct fb_info *info; + int tag, i, err = 0; + u_long chipptr; + u_int defmode; + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("amifb", &option)) { + amifb_video_off(); + return -ENODEV; + } + amifb_setup(option); +#endif + custom.dmacon = DMAF_ALL | DMAF_MASTER; + + info = framebuffer_alloc(sizeof(struct amifb_par), &pdev->dev); + if (!info) { + dev_err(&pdev->dev, "framebuffer_alloc failed\n"); + return -ENOMEM; + } + + strcpy(info->fix.id, "Amiga "); + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.accel = FB_ACCEL_AMIGABLITT; + + switch (amiga_chipset) { +#ifdef CONFIG_FB_AMIGA_OCS + case CS_OCS: + strcat(info->fix.id, "OCS"); +default_chipset: + chipset = TAG_OCS; + maxdepth[TAG_SHRES] = 0; /* OCS means no SHRES */ + maxdepth[TAG_HIRES] = 4; + maxdepth[TAG_LORES] = 6; + maxfmode = TAG_FMODE_1; + defmode = amiga_vblank == 50 ? DEFMODE_PAL : DEFMODE_NTSC; + info->fix.smem_len = VIDEOMEMSIZE_OCS; + break; +#endif /* CONFIG_FB_AMIGA_OCS */ + +#ifdef CONFIG_FB_AMIGA_ECS + case CS_ECS: + strcat(info->fix.id, "ECS"); + chipset = TAG_ECS; + maxdepth[TAG_SHRES] = 2; + maxdepth[TAG_HIRES] = 4; + maxdepth[TAG_LORES] = 6; + maxfmode = TAG_FMODE_1; + if (AMIGAHW_PRESENT(AMBER_FF)) + defmode = amiga_vblank == 50 ? DEFMODE_AMBER_PAL + : DEFMODE_AMBER_NTSC; + else + defmode = amiga_vblank == 50 ? DEFMODE_PAL + : DEFMODE_NTSC; + if (amiga_chip_avail() - CHIPRAM_SAFETY_LIMIT > + VIDEOMEMSIZE_ECS_2M) + info->fix.smem_len = VIDEOMEMSIZE_ECS_2M; + else + info->fix.smem_len = VIDEOMEMSIZE_ECS_1M; + break; +#endif /* CONFIG_FB_AMIGA_ECS */ + +#ifdef CONFIG_FB_AMIGA_AGA + case CS_AGA: + strcat(info->fix.id, "AGA"); + chipset = TAG_AGA; + maxdepth[TAG_SHRES] = 8; + maxdepth[TAG_HIRES] = 8; + maxdepth[TAG_LORES] = 8; + maxfmode = TAG_FMODE_4; + defmode = DEFMODE_AGA; + if (amiga_chip_avail() - CHIPRAM_SAFETY_LIMIT > + VIDEOMEMSIZE_AGA_2M) + info->fix.smem_len = VIDEOMEMSIZE_AGA_2M; + else + info->fix.smem_len = VIDEOMEMSIZE_AGA_1M; + break; +#endif /* CONFIG_FB_AMIGA_AGA */ + + default: +#ifdef CONFIG_FB_AMIGA_OCS + printk("Unknown graphics chipset, defaulting to OCS\n"); + strcat(info->fix.id, "Unknown"); + goto default_chipset; +#else /* CONFIG_FB_AMIGA_OCS */ + err = -ENODEV; + goto release; +#endif /* CONFIG_FB_AMIGA_OCS */ + break; + } + + /* + * Calculate the Pixel Clock Values for this Machine + */ + + { + u_long tmp = DIVUL(200000000000ULL, amiga_eclock); + + pixclock[TAG_SHRES] = (tmp + 4) / 8; /* SHRES: 35 ns / 28 MHz */ + pixclock[TAG_HIRES] = (tmp + 2) / 4; /* HIRES: 70 ns / 14 MHz */ + pixclock[TAG_LORES] = (tmp + 1) / 2; /* LORES: 140 ns / 7 MHz */ + } + + /* + * Replace the Tag Values with the Real Pixel Clock Values + */ + + for (i = 0; i < NUM_TOTAL_MODES; i++) { + struct fb_videomode *mode = &ami_modedb[i]; + tag = mode->pixclock; + if (tag == TAG_SHRES || tag == TAG_HIRES || tag == TAG_LORES) { + mode->pixclock = pixclock[tag]; + } + } + + if (amifb_hfmin) { + info->monspecs.hfmin = amifb_hfmin; + info->monspecs.hfmax = amifb_hfmax; + info->monspecs.vfmin = amifb_vfmin; + info->monspecs.vfmax = amifb_vfmax; + } else { + /* + * These are for a typical Amiga monitor (e.g. A1960) + */ + info->monspecs.hfmin = 15000; + info->monspecs.hfmax = 38000; + info->monspecs.vfmin = 49; + info->monspecs.vfmax = 90; + } + + info->fbops = &amifb_ops; + info->flags = FBINFO_DEFAULT; + info->device = &pdev->dev; + + if (!fb_find_mode(&info->var, info, mode_option, ami_modedb, + NUM_TOTAL_MODES, &ami_modedb[defmode], 4)) { + err = -EINVAL; + goto release; + } + + fb_videomode_to_modelist(ami_modedb, NUM_TOTAL_MODES, + &info->modelist); + + round_down_bpp = 0; + chipptr = chipalloc(info->fix.smem_len + SPRITEMEMSIZE + + DUMMYSPRITEMEMSIZE + COPINITSIZE + + 4 * COPLISTSIZE); + if (!chipptr) { + err = -ENOMEM; + goto release; + } + + assignchunk(videomemory, u_long, chipptr, info->fix.smem_len); + assignchunk(spritememory, u_long, chipptr, SPRITEMEMSIZE); + assignchunk(dummysprite, u_short *, chipptr, DUMMYSPRITEMEMSIZE); + assignchunk(copdisplay.init, copins *, chipptr, COPINITSIZE); + assignchunk(copdisplay.list[0][0], copins *, chipptr, COPLISTSIZE); + assignchunk(copdisplay.list[0][1], copins *, chipptr, COPLISTSIZE); + assignchunk(copdisplay.list[1][0], copins *, chipptr, COPLISTSIZE); + assignchunk(copdisplay.list[1][1], copins *, chipptr, COPLISTSIZE); + + /* + * access the videomem with writethrough cache + */ + info->fix.smem_start = (u_long)ZTWO_PADDR(videomemory); + videomemory = (u_long)ioremap_writethrough(info->fix.smem_start, + info->fix.smem_len); + if (!videomemory) { + dev_warn(&pdev->dev, + "Unable to map videomem cached writethrough\n"); + info->screen_base = ZTWO_VADDR(info->fix.smem_start); + } else + info->screen_base = (char *)videomemory; + + memset(dummysprite, 0, DUMMYSPRITEMEMSIZE); + + /* + * Make sure the Copper has something to do + */ + ami_init_copper(); + + /* + * Enable Display DMA + */ + custom.dmacon = DMAF_SETCLR | DMAF_MASTER | DMAF_RASTER | DMAF_COPPER | + DMAF_BLITTER | DMAF_SPRITE; + + err = request_irq(IRQ_AMIGA_COPPER, amifb_interrupt, 0, + "fb vertb handler", info->par); + if (err) + goto disable_dma; + + err = fb_alloc_cmap(&info->cmap, 1 << info->var.bits_per_pixel, 0); + if (err) + goto free_irq; + + dev_set_drvdata(&pdev->dev, info); + + err = register_framebuffer(info); + if (err) + goto unset_drvdata; + + fb_info(info, "%s frame buffer device, using %dK of video memory\n", + info->fix.id, info->fix.smem_len>>10); + + return 0; + +unset_drvdata: + fb_dealloc_cmap(&info->cmap); +free_irq: + free_irq(IRQ_AMIGA_COPPER, info->par); +disable_dma: + custom.dmacon = DMAF_ALL | DMAF_MASTER; + if (videomemory) + iounmap((void *)videomemory); + chipfree(); +release: + framebuffer_release(info); + return err; +} + + +static int __exit amifb_remove(struct platform_device *pdev) +{ + struct fb_info *info = dev_get_drvdata(&pdev->dev); + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + free_irq(IRQ_AMIGA_COPPER, info->par); + custom.dmacon = DMAF_ALL | DMAF_MASTER; + if (videomemory) + iounmap((void *)videomemory); + chipfree(); + framebuffer_release(info); + amifb_video_off(); + return 0; +} + +static struct platform_driver amifb_driver = { + .remove = __exit_p(amifb_remove), + .driver = { + .name = "amiga-video", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver_probe(amifb_driver, amifb_probe); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:amiga-video"); diff --git a/drivers/video/fbdev/arcfb.c b/drivers/video/fbdev/arcfb.c new file mode 100644 index 000000000000..1b0b233b8b39 --- /dev/null +++ b/drivers/video/fbdev/arcfb.c @@ -0,0 +1,667 @@ +/* + * linux/drivers/video/arcfb.c -- FB driver for Arc monochrome LCD board + * + * Copyright (C) 2005, Jaya Kumar <jayalk@intworks.biz> + * + * 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. + * + * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. + * + * This driver was written to be used with the Arc LCD board. Arc uses a + * set of KS108 chips that control individual 64x64 LCD matrices. The board + * can be paneled in a variety of setups such as 2x1=128x64, 4x4=256x256 and + * so on. The interface between the board and the host is TTL based GPIO. The + * GPIO requirements are 8 writable data lines and 4+n lines for control. On a + * GPIO-less system, the board can be tested by connecting the respective sigs + * up to a parallel port connector. The driver requires the IO addresses for + * data and control GPIO at load time. It is unable to probe for the + * existence of the LCD so it must be told at load time whether it should + * be enabled or not. + * + * Todo: + * - testing with 4x4 + * - testing with interrupt hw + * + * General notes: + * - User must set tuhold. It's in microseconds. According to the 108 spec, + * the hold time is supposed to be at least 1 microsecond. + * - User must set num_cols=x num_rows=y, eg: x=2 means 128 + * - User must set arcfb_enable=1 to enable it + * - User must set dio_addr=0xIOADDR cio_addr=0xIOADDR + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/arcfb.h> +#include <linux/platform_device.h> + +#include <linux/uaccess.h> + +#define floor8(a) (a&(~0x07)) +#define floorXres(a,xres) (a&(~(xres - 1))) +#define iceil8(a) (((int)((a+7)/8))*8) +#define ceil64(a) (a|0x3F) +#define ceilXres(a,xres) (a|(xres - 1)) + +/* ks108 chipset specific defines and code */ + +#define KS_SET_DPY_START_LINE 0xC0 +#define KS_SET_PAGE_NUM 0xB8 +#define KS_SET_X 0x40 +#define KS_CEHI 0x01 +#define KS_CELO 0x00 +#define KS_SEL_CMD 0x08 +#define KS_SEL_DATA 0x00 +#define KS_DPY_ON 0x3F +#define KS_DPY_OFF 0x3E +#define KS_INTACK 0x40 +#define KS_CLRINT 0x02 + +struct arcfb_par { + unsigned long dio_addr; + unsigned long cio_addr; + unsigned long c2io_addr; + atomic_t ref_count; + unsigned char cslut[9]; + struct fb_info *info; + unsigned int irq; + spinlock_t lock; +}; + +static struct fb_fix_screeninfo arcfb_fix = { + .id = "arcfb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_MONO01, + .xpanstep = 0, + .ypanstep = 1, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo arcfb_var = { + .xres = 128, + .yres = 64, + .xres_virtual = 128, + .yres_virtual = 64, + .bits_per_pixel = 1, + .nonstd = 1, +}; + +static unsigned long num_cols; +static unsigned long num_rows; +static unsigned long dio_addr; +static unsigned long cio_addr; +static unsigned long c2io_addr; +static unsigned long splashval; +static unsigned long tuhold; +static unsigned int nosplash; +static unsigned int arcfb_enable; +static unsigned int irq; + +static DECLARE_WAIT_QUEUE_HEAD(arcfb_waitq); + +static void ks108_writeb_ctl(struct arcfb_par *par, + unsigned int chipindex, unsigned char value) +{ + unsigned char chipselval = par->cslut[chipindex]; + + outb(chipselval|KS_CEHI|KS_SEL_CMD, par->cio_addr); + outb(value, par->dio_addr); + udelay(tuhold); + outb(chipselval|KS_CELO|KS_SEL_CMD, par->cio_addr); +} + +static void ks108_writeb_mainctl(struct arcfb_par *par, unsigned char value) +{ + + outb(value, par->cio_addr); + udelay(tuhold); +} + +static unsigned char ks108_readb_ctl2(struct arcfb_par *par) +{ + return inb(par->c2io_addr); +} + +static void ks108_writeb_data(struct arcfb_par *par, + unsigned int chipindex, unsigned char value) +{ + unsigned char chipselval = par->cslut[chipindex]; + + outb(chipselval|KS_CEHI|KS_SEL_DATA, par->cio_addr); + outb(value, par->dio_addr); + udelay(tuhold); + outb(chipselval|KS_CELO|KS_SEL_DATA, par->cio_addr); +} + +static void ks108_set_start_line(struct arcfb_par *par, + unsigned int chipindex, unsigned char y) +{ + ks108_writeb_ctl(par, chipindex, KS_SET_DPY_START_LINE|y); +} + +static void ks108_set_yaddr(struct arcfb_par *par, + unsigned int chipindex, unsigned char y) +{ + ks108_writeb_ctl(par, chipindex, KS_SET_PAGE_NUM|y); +} + +static void ks108_set_xaddr(struct arcfb_par *par, + unsigned int chipindex, unsigned char x) +{ + ks108_writeb_ctl(par, chipindex, KS_SET_X|x); +} + +static void ks108_clear_lcd(struct arcfb_par *par, unsigned int chipindex) +{ + int i,j; + + for (i = 0; i <= 8; i++) { + ks108_set_yaddr(par, chipindex, i); + ks108_set_xaddr(par, chipindex, 0); + for (j = 0; j < 64; j++) { + ks108_writeb_data(par, chipindex, + (unsigned char) splashval); + } + } +} + +/* main arcfb functions */ + +static int arcfb_open(struct fb_info *info, int user) +{ + struct arcfb_par *par = info->par; + + atomic_inc(&par->ref_count); + return 0; +} + +static int arcfb_release(struct fb_info *info, int user) +{ + struct arcfb_par *par = info->par; + int count = atomic_read(&par->ref_count); + + if (!count) + return -EINVAL; + atomic_dec(&par->ref_count); + return 0; +} + +static int arcfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int i; + struct arcfb_par *par = info->par; + + if ((var->vmode & FB_VMODE_YWRAP) && (var->yoffset < 64) + && (info->var.yres <= 64)) { + for (i = 0; i < num_cols; i++) { + ks108_set_start_line(par, i, var->yoffset); + } + info->var.yoffset = var->yoffset; + return 0; + } + + return -EINVAL; +} + +static irqreturn_t arcfb_interrupt(int vec, void *dev_instance) +{ + struct fb_info *info = dev_instance; + unsigned char ctl2status; + struct arcfb_par *par = info->par; + + ctl2status = ks108_readb_ctl2(par); + + if (!(ctl2status & KS_INTACK)) /* not arc generated interrupt */ + return IRQ_NONE; + + ks108_writeb_mainctl(par, KS_CLRINT); + + spin_lock(&par->lock); + if (waitqueue_active(&arcfb_waitq)) { + wake_up(&arcfb_waitq); + } + spin_unlock(&par->lock); + + return IRQ_HANDLED; +} + +/* + * here we handle a specific page on the lcd. the complexity comes from + * the fact that the fb is laidout in 8xX vertical columns. we extract + * each write of 8 vertical pixels. then we shift out as we move along + * X. That's what rightshift does. bitmask selects the desired input bit. + */ +static void arcfb_lcd_update_page(struct arcfb_par *par, unsigned int upper, + unsigned int left, unsigned int right, unsigned int distance) +{ + unsigned char *src; + unsigned int xindex, yindex, chipindex, linesize; + int i; + unsigned char val; + unsigned char bitmask, rightshift; + + xindex = left >> 6; + yindex = upper >> 6; + chipindex = (xindex + (yindex*num_cols)); + + ks108_set_yaddr(par, chipindex, upper/8); + + linesize = par->info->var.xres/8; + src = (unsigned char __force *) par->info->screen_base + (left/8) + + (upper * linesize); + ks108_set_xaddr(par, chipindex, left); + + bitmask=1; + rightshift=0; + while (left <= right) { + val = 0; + for (i = 0; i < 8; i++) { + if ( i > rightshift) { + val |= (*(src + (i*linesize)) & bitmask) + << (i - rightshift); + } else { + val |= (*(src + (i*linesize)) & bitmask) + >> (rightshift - i); + } + } + ks108_writeb_data(par, chipindex, val); + left++; + if (bitmask == 0x80) { + bitmask = 1; + src++; + rightshift=0; + } else { + bitmask <<= 1; + rightshift++; + } + } +} + +/* + * here we handle the entire vertical page of the update. we write across + * lcd chips. update_page uses the upper/left values to decide which + * chip to select for the right. upper is needed for setting the page + * desired for the write. + */ +static void arcfb_lcd_update_vert(struct arcfb_par *par, unsigned int top, + unsigned int bottom, unsigned int left, unsigned int right) +{ + unsigned int distance, upper, lower; + + distance = (bottom - top) + 1; + upper = top; + lower = top + 7; + + while (distance > 0) { + distance -= 8; + arcfb_lcd_update_page(par, upper, left, right, 8); + upper = lower + 1; + lower = upper + 7; + } +} + +/* + * here we handle horizontal blocks for the update. update_vert will + * handle spaning multiple pages. we break out each horizontal + * block in to individual blocks no taller than 64 pixels. + */ +static void arcfb_lcd_update_horiz(struct arcfb_par *par, unsigned int left, + unsigned int right, unsigned int top, unsigned int h) +{ + unsigned int distance, upper, lower; + + distance = h; + upper = floor8(top); + lower = min(upper + distance - 1, ceil64(upper)); + + while (distance > 0) { + distance -= ((lower - upper) + 1 ); + arcfb_lcd_update_vert(par, upper, lower, left, right); + upper = lower + 1; + lower = min(upper + distance - 1, ceil64(upper)); + } +} + +/* + * here we start the process of splitting out the fb update into + * individual blocks of pixels. we end up splitting into 64x64 blocks + * and finally down to 64x8 pages. + */ +static void arcfb_lcd_update(struct arcfb_par *par, unsigned int dx, + unsigned int dy, unsigned int w, unsigned int h) +{ + unsigned int left, right, distance, y; + + /* align the request first */ + y = floor8(dy); + h += dy - y; + h = iceil8(h); + + distance = w; + left = dx; + right = min(left + w - 1, ceil64(left)); + + while (distance > 0) { + arcfb_lcd_update_horiz(par, left, right, y, h); + distance -= ((right - left) + 1); + left = right + 1; + right = min(left + distance - 1, ceil64(left)); + } +} + +static void arcfb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct arcfb_par *par = info->par; + + sys_fillrect(info, rect); + + /* update the physical lcd */ + arcfb_lcd_update(par, rect->dx, rect->dy, rect->width, rect->height); +} + +static void arcfb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct arcfb_par *par = info->par; + + sys_copyarea(info, area); + + /* update the physical lcd */ + arcfb_lcd_update(par, area->dx, area->dy, area->width, area->height); +} + +static void arcfb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct arcfb_par *par = info->par; + + sys_imageblit(info, image); + + /* update the physical lcd */ + arcfb_lcd_update(par, image->dx, image->dy, image->width, + image->height); +} + +static int arcfb_ioctl(struct fb_info *info, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct arcfb_par *par = info->par; + unsigned long flags; + + switch (cmd) { + case FBIO_WAITEVENT: + { + DEFINE_WAIT(wait); + /* illegal to wait on arc if no irq will occur */ + if (!par->irq) + return -EINVAL; + + /* wait until the Arc has generated an interrupt + * which will wake us up */ + spin_lock_irqsave(&par->lock, flags); + prepare_to_wait(&arcfb_waitq, &wait, + TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&par->lock, flags); + schedule(); + finish_wait(&arcfb_waitq, &wait); + } + case FBIO_GETCONTROL2: + { + unsigned char ctl2; + + ctl2 = ks108_readb_ctl2(info->par); + if (copy_to_user(argp, &ctl2, sizeof(ctl2))) + return -EFAULT; + return 0; + } + default: + return -EINVAL; + } +} + +/* + * this is the access path from userspace. they can seek and write to + * the fb. it's inefficient for them to do anything less than 64*8 + * writes since we update the lcd in each write() anyway. + */ +static ssize_t arcfb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* modded from epson 1355 */ + + unsigned long p; + int err=-EINVAL; + unsigned int fbmemlength,x,y,w,h, bitppos, startpos, endpos, bitcount; + struct arcfb_par *par; + unsigned int xres; + + p = *ppos; + par = info->par; + xres = info->var.xres; + fbmemlength = (xres * info->var.yres)/8; + + if (p > fbmemlength) + return -ENOSPC; + + err = 0; + if ((count + p) > fbmemlength) { + count = fbmemlength - p; + err = -ENOSPC; + } + + if (count) { + char *base_addr; + + base_addr = (char __force *)info->screen_base; + count -= copy_from_user(base_addr + p, buf, count); + *ppos += count; + err = -EFAULT; + } + + + bitppos = p*8; + startpos = floorXres(bitppos, xres); + endpos = ceilXres((bitppos + (count*8)), xres); + bitcount = endpos - startpos; + + x = startpos % xres; + y = startpos / xres; + w = xres; + h = bitcount / xres; + arcfb_lcd_update(par, x, y, w, h); + + if (count) + return count; + return err; +} + +static struct fb_ops arcfb_ops = { + .owner = THIS_MODULE, + .fb_open = arcfb_open, + .fb_read = fb_sys_read, + .fb_write = arcfb_write, + .fb_release = arcfb_release, + .fb_pan_display = arcfb_pan_display, + .fb_fillrect = arcfb_fillrect, + .fb_copyarea = arcfb_copyarea, + .fb_imageblit = arcfb_imageblit, + .fb_ioctl = arcfb_ioctl, +}; + +static int arcfb_probe(struct platform_device *dev) +{ + struct fb_info *info; + int retval = -ENOMEM; + int videomemorysize; + unsigned char *videomemory; + struct arcfb_par *par; + int i; + + videomemorysize = (((64*64)*num_cols)*num_rows)/8; + + /* We need a flat backing store for the Arc's + less-flat actual paged framebuffer */ + videomemory = vzalloc(videomemorysize); + if (!videomemory) + return retval; + + info = framebuffer_alloc(sizeof(struct arcfb_par), &dev->dev); + if (!info) + goto err; + + info->screen_base = (char __iomem *)videomemory; + info->fbops = &arcfb_ops; + + info->var = arcfb_var; + info->fix = arcfb_fix; + par = info->par; + par->info = info; + + if (!dio_addr || !cio_addr || !c2io_addr) { + printk(KERN_WARNING "no IO addresses supplied\n"); + goto err1; + } + par->dio_addr = dio_addr; + par->cio_addr = cio_addr; + par->c2io_addr = c2io_addr; + par->cslut[0] = 0x00; + par->cslut[1] = 0x06; + info->flags = FBINFO_FLAG_DEFAULT; + spin_lock_init(&par->lock); + retval = register_framebuffer(info); + if (retval < 0) + goto err1; + platform_set_drvdata(dev, info); + if (irq) { + par->irq = irq; + if (request_irq(par->irq, &arcfb_interrupt, IRQF_SHARED, + "arcfb", info)) { + printk(KERN_INFO + "arcfb: Failed req IRQ %d\n", par->irq); + retval = -EBUSY; + goto err1; + } + } + fb_info(info, "Arc frame buffer device, using %dK of video memory\n", + videomemorysize >> 10); + + /* this inits the lcd but doesn't clear dirty pixels */ + for (i = 0; i < num_cols * num_rows; i++) { + ks108_writeb_ctl(par, i, KS_DPY_OFF); + ks108_set_start_line(par, i, 0); + ks108_set_yaddr(par, i, 0); + ks108_set_xaddr(par, i, 0); + ks108_writeb_ctl(par, i, KS_DPY_ON); + } + + /* if we were told to splash the screen, we just clear it */ + if (!nosplash) { + for (i = 0; i < num_cols * num_rows; i++) { + fb_info(info, "splashing lcd %d\n", i); + ks108_set_start_line(par, i, 0); + ks108_clear_lcd(par, i); + } + } + + return 0; +err1: + framebuffer_release(info); +err: + vfree(videomemory); + return retval; +} + +static int arcfb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + unregister_framebuffer(info); + vfree((void __force *)info->screen_base); + framebuffer_release(info); + } + return 0; +} + +static struct platform_driver arcfb_driver = { + .probe = arcfb_probe, + .remove = arcfb_remove, + .driver = { + .name = "arcfb", + }, +}; + +static struct platform_device *arcfb_device; + +static int __init arcfb_init(void) +{ + int ret; + + if (!arcfb_enable) + return -ENXIO; + + ret = platform_driver_register(&arcfb_driver); + if (!ret) { + arcfb_device = platform_device_alloc("arcfb", 0); + if (arcfb_device) { + ret = platform_device_add(arcfb_device); + } else { + ret = -ENOMEM; + } + if (ret) { + platform_device_put(arcfb_device); + platform_driver_unregister(&arcfb_driver); + } + } + return ret; + +} + +static void __exit arcfb_exit(void) +{ + platform_device_unregister(arcfb_device); + platform_driver_unregister(&arcfb_driver); +} + +module_param(num_cols, ulong, 0); +MODULE_PARM_DESC(num_cols, "Num horiz panels, eg: 2 = 128 bit wide"); +module_param(num_rows, ulong, 0); +MODULE_PARM_DESC(num_rows, "Num vert panels, eg: 1 = 64 bit high"); +module_param(nosplash, uint, 0); +MODULE_PARM_DESC(nosplash, "Disable doing the splash screen"); +module_param(arcfb_enable, uint, 0); +MODULE_PARM_DESC(arcfb_enable, "Enable communication with Arc board"); +module_param(dio_addr, ulong, 0); +MODULE_PARM_DESC(dio_addr, "IO address for data, eg: 0x480"); +module_param(cio_addr, ulong, 0); +MODULE_PARM_DESC(cio_addr, "IO address for control, eg: 0x400"); +module_param(c2io_addr, ulong, 0); +MODULE_PARM_DESC(c2io_addr, "IO address for secondary control, eg: 0x408"); +module_param(splashval, ulong, 0); +MODULE_PARM_DESC(splashval, "Splash pattern: 0xFF is black, 0x00 is green"); +module_param(tuhold, ulong, 0); +MODULE_PARM_DESC(tuhold, "Time to hold between strobing data to Arc board"); +module_param(irq, uint, 0); +MODULE_PARM_DESC(irq, "IRQ for the Arc board"); + +module_init(arcfb_init); +module_exit(arcfb_exit); + +MODULE_DESCRIPTION("fbdev driver for Arc monochrome LCD board"); +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/arkfb.c b/drivers/video/fbdev/arkfb.c new file mode 100644 index 000000000000..adc4ea2cc5a0 --- /dev/null +++ b/drivers/video/fbdev/arkfb.c @@ -0,0 +1,1231 @@ +/* + * linux/drivers/video/arkfb.c -- Frame buffer device driver for ARK 2000PV + * with ICS 5342 dac (it is easy to add support for different dacs). + * + * Copyright (c) 2007 Ondrej Zajicek <santiago@crfreenet.org> + * + * 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. + * + * Code is based on s3fb + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/svga.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/console.h> /* Why should fb driver call console functions? because console_lock() */ +#include <video/vga.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +struct arkfb_info { + int mclk_freq; + int mtrr_reg; + + struct dac_info *dac; + struct vgastate state; + struct mutex open_lock; + unsigned int ref_count; + u32 pseudo_palette[16]; +}; + + +/* ------------------------------------------------------------------------- */ + + +static const struct svga_fb_format arkfb_formats[] = { + { 0, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 0, + FB_TYPE_TEXT, FB_AUX_TEXT_SVGA_STEP4, FB_VISUAL_PSEUDOCOLOR, 8, 8}, + { 4, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_PSEUDOCOLOR, 8, 16}, + { 4, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 1, + FB_TYPE_INTERLEAVED_PLANES, 1, FB_VISUAL_PSEUDOCOLOR, 8, 16}, + { 8, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_PSEUDOCOLOR, 8, 8}, + {16, {10, 5, 0}, {5, 5, 0}, {0, 5, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 4, 4}, + {16, {11, 5, 0}, {5, 6, 0}, {0, 5, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 4, 4}, + {24, {16, 8, 0}, {8, 8, 0}, {0, 8, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 8, 8}, + {32, {16, 8, 0}, {8, 8, 0}, {0, 8, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 2, 2}, + SVGA_FORMAT_END +}; + + +/* CRT timing register sets */ + +static const struct vga_regset ark_h_total_regs[] = {{0x00, 0, 7}, {0x41, 7, 7}, VGA_REGSET_END}; +static const struct vga_regset ark_h_display_regs[] = {{0x01, 0, 7}, {0x41, 6, 6}, VGA_REGSET_END}; +static const struct vga_regset ark_h_blank_start_regs[] = {{0x02, 0, 7}, {0x41, 5, 5}, VGA_REGSET_END}; +static const struct vga_regset ark_h_blank_end_regs[] = {{0x03, 0, 4}, {0x05, 7, 7 }, VGA_REGSET_END}; +static const struct vga_regset ark_h_sync_start_regs[] = {{0x04, 0, 7}, {0x41, 4, 4}, VGA_REGSET_END}; +static const struct vga_regset ark_h_sync_end_regs[] = {{0x05, 0, 4}, VGA_REGSET_END}; + +static const struct vga_regset ark_v_total_regs[] = {{0x06, 0, 7}, {0x07, 0, 0}, {0x07, 5, 5}, {0x40, 7, 7}, VGA_REGSET_END}; +static const struct vga_regset ark_v_display_regs[] = {{0x12, 0, 7}, {0x07, 1, 1}, {0x07, 6, 6}, {0x40, 6, 6}, VGA_REGSET_END}; +static const struct vga_regset ark_v_blank_start_regs[] = {{0x15, 0, 7}, {0x07, 3, 3}, {0x09, 5, 5}, {0x40, 5, 5}, VGA_REGSET_END}; +// const struct vga_regset ark_v_blank_end_regs[] = {{0x16, 0, 6}, VGA_REGSET_END}; +static const struct vga_regset ark_v_blank_end_regs[] = {{0x16, 0, 7}, VGA_REGSET_END}; +static const struct vga_regset ark_v_sync_start_regs[] = {{0x10, 0, 7}, {0x07, 2, 2}, {0x07, 7, 7}, {0x40, 4, 4}, VGA_REGSET_END}; +static const struct vga_regset ark_v_sync_end_regs[] = {{0x11, 0, 3}, VGA_REGSET_END}; + +static const struct vga_regset ark_line_compare_regs[] = {{0x18, 0, 7}, {0x07, 4, 4}, {0x09, 6, 6}, VGA_REGSET_END}; +static const struct vga_regset ark_start_address_regs[] = {{0x0d, 0, 7}, {0x0c, 0, 7}, {0x40, 0, 2}, VGA_REGSET_END}; +static const struct vga_regset ark_offset_regs[] = {{0x13, 0, 7}, {0x41, 3, 3}, VGA_REGSET_END}; + +static const struct svga_timing_regs ark_timing_regs = { + ark_h_total_regs, ark_h_display_regs, ark_h_blank_start_regs, + ark_h_blank_end_regs, ark_h_sync_start_regs, ark_h_sync_end_regs, + ark_v_total_regs, ark_v_display_regs, ark_v_blank_start_regs, + ark_v_blank_end_regs, ark_v_sync_start_regs, ark_v_sync_end_regs, +}; + + +/* ------------------------------------------------------------------------- */ + + +/* Module parameters */ + +static char *mode_option = "640x480-8@60"; + +#ifdef CONFIG_MTRR +static int mtrr = 1; +#endif + +MODULE_AUTHOR("(c) 2007 Ondrej Zajicek <santiago@crfreenet.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("fbdev driver for ARK 2000PV"); + +module_param(mode_option, charp, 0444); +MODULE_PARM_DESC(mode_option, "Default video mode ('640x480-8@60', etc)"); +module_param_named(mode, mode_option, charp, 0444); +MODULE_PARM_DESC(mode, "Default video mode ('640x480-8@60', etc) (deprecated)"); + +#ifdef CONFIG_MTRR +module_param(mtrr, int, 0444); +MODULE_PARM_DESC(mtrr, "Enable write-combining with MTRR (1=enable, 0=disable, default=1)"); +#endif + +static int threshold = 4; + +module_param(threshold, int, 0644); +MODULE_PARM_DESC(threshold, "FIFO threshold"); + + +/* ------------------------------------------------------------------------- */ + + +static void arkfb_settile(struct fb_info *info, struct fb_tilemap *map) +{ + const u8 *font = map->data; + u8 __iomem *fb = (u8 __iomem *)info->screen_base; + int i, c; + + if ((map->width != 8) || (map->height != 16) || + (map->depth != 1) || (map->length != 256)) { + fb_err(info, "unsupported font parameters: width %d, height %d, depth %d, length %d\n", + map->width, map->height, map->depth, map->length); + return; + } + + fb += 2; + for (c = 0; c < map->length; c++) { + for (i = 0; i < map->height; i++) { + fb_writeb(font[i], &fb[i * 4]); + fb_writeb(font[i], &fb[i * 4 + (128 * 8)]); + } + fb += 128; + + if ((c % 8) == 7) + fb += 128*8; + + font += map->height; + } +} + +static void arkfb_tilecursor(struct fb_info *info, struct fb_tilecursor *cursor) +{ + struct arkfb_info *par = info->par; + + svga_tilecursor(par->state.vgabase, info, cursor); +} + +static struct fb_tile_ops arkfb_tile_ops = { + .fb_settile = arkfb_settile, + .fb_tilecopy = svga_tilecopy, + .fb_tilefill = svga_tilefill, + .fb_tileblit = svga_tileblit, + .fb_tilecursor = arkfb_tilecursor, + .fb_get_tilemax = svga_get_tilemax, +}; + + +/* ------------------------------------------------------------------------- */ + + +/* image data is MSB-first, fb structure is MSB-first too */ +static inline u32 expand_color(u32 c) +{ + return ((c & 1) | ((c & 2) << 7) | ((c & 4) << 14) | ((c & 8) << 21)) * 0xFF; +} + +/* arkfb_iplan_imageblit silently assumes that almost everything is 8-pixel aligned */ +static void arkfb_iplan_imageblit(struct fb_info *info, const struct fb_image *image) +{ + u32 fg = expand_color(image->fg_color); + u32 bg = expand_color(image->bg_color); + const u8 *src1, *src; + u8 __iomem *dst1; + u32 __iomem *dst; + u32 val; + int x, y; + + src1 = image->data; + dst1 = info->screen_base + (image->dy * info->fix.line_length) + + ((image->dx / 8) * 4); + + for (y = 0; y < image->height; y++) { + src = src1; + dst = (u32 __iomem *) dst1; + for (x = 0; x < image->width; x += 8) { + val = *(src++) * 0x01010101; + val = (val & fg) | (~val & bg); + fb_writel(val, dst++); + } + src1 += image->width / 8; + dst1 += info->fix.line_length; + } + +} + +/* arkfb_iplan_fillrect silently assumes that almost everything is 8-pixel aligned */ +static void arkfb_iplan_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + u32 fg = expand_color(rect->color); + u8 __iomem *dst1; + u32 __iomem *dst; + int x, y; + + dst1 = info->screen_base + (rect->dy * info->fix.line_length) + + ((rect->dx / 8) * 4); + + for (y = 0; y < rect->height; y++) { + dst = (u32 __iomem *) dst1; + for (x = 0; x < rect->width; x += 8) { + fb_writel(fg, dst++); + } + dst1 += info->fix.line_length; + } + +} + + +/* image data is MSB-first, fb structure is high-nibble-in-low-byte-first */ +static inline u32 expand_pixel(u32 c) +{ + return (((c & 1) << 24) | ((c & 2) << 27) | ((c & 4) << 14) | ((c & 8) << 17) | + ((c & 16) << 4) | ((c & 32) << 7) | ((c & 64) >> 6) | ((c & 128) >> 3)) * 0xF; +} + +/* arkfb_cfb4_imageblit silently assumes that almost everything is 8-pixel aligned */ +static void arkfb_cfb4_imageblit(struct fb_info *info, const struct fb_image *image) +{ + u32 fg = image->fg_color * 0x11111111; + u32 bg = image->bg_color * 0x11111111; + const u8 *src1, *src; + u8 __iomem *dst1; + u32 __iomem *dst; + u32 val; + int x, y; + + src1 = image->data; + dst1 = info->screen_base + (image->dy * info->fix.line_length) + + ((image->dx / 8) * 4); + + for (y = 0; y < image->height; y++) { + src = src1; + dst = (u32 __iomem *) dst1; + for (x = 0; x < image->width; x += 8) { + val = expand_pixel(*(src++)); + val = (val & fg) | (~val & bg); + fb_writel(val, dst++); + } + src1 += image->width / 8; + dst1 += info->fix.line_length; + } + +} + +static void arkfb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + if ((info->var.bits_per_pixel == 4) && (image->depth == 1) + && ((image->width % 8) == 0) && ((image->dx % 8) == 0)) { + if (info->fix.type == FB_TYPE_INTERLEAVED_PLANES) + arkfb_iplan_imageblit(info, image); + else + arkfb_cfb4_imageblit(info, image); + } else + cfb_imageblit(info, image); +} + +static void arkfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + if ((info->var.bits_per_pixel == 4) + && ((rect->width % 8) == 0) && ((rect->dx % 8) == 0) + && (info->fix.type == FB_TYPE_INTERLEAVED_PLANES)) + arkfb_iplan_fillrect(info, rect); + else + cfb_fillrect(info, rect); +} + + +/* ------------------------------------------------------------------------- */ + + +enum +{ + DAC_PSEUDO8_8, + DAC_RGB1555_8, + DAC_RGB0565_8, + DAC_RGB0888_8, + DAC_RGB8888_8, + DAC_PSEUDO8_16, + DAC_RGB1555_16, + DAC_RGB0565_16, + DAC_RGB0888_16, + DAC_RGB8888_16, + DAC_MAX +}; + +struct dac_ops { + int (*dac_get_mode)(struct dac_info *info); + int (*dac_set_mode)(struct dac_info *info, int mode); + int (*dac_get_freq)(struct dac_info *info, int channel); + int (*dac_set_freq)(struct dac_info *info, int channel, u32 freq); + void (*dac_release)(struct dac_info *info); +}; + +typedef void (*dac_read_regs_t)(void *data, u8 *code, int count); +typedef void (*dac_write_regs_t)(void *data, u8 *code, int count); + +struct dac_info +{ + struct dac_ops *dacops; + dac_read_regs_t dac_read_regs; + dac_write_regs_t dac_write_regs; + void *data; +}; + + +static inline u8 dac_read_reg(struct dac_info *info, u8 reg) +{ + u8 code[2] = {reg, 0}; + info->dac_read_regs(info->data, code, 1); + return code[1]; +} + +static inline void dac_read_regs(struct dac_info *info, u8 *code, int count) +{ + info->dac_read_regs(info->data, code, count); +} + +static inline void dac_write_reg(struct dac_info *info, u8 reg, u8 val) +{ + u8 code[2] = {reg, val}; + info->dac_write_regs(info->data, code, 1); +} + +static inline void dac_write_regs(struct dac_info *info, u8 *code, int count) +{ + info->dac_write_regs(info->data, code, count); +} + +static inline int dac_set_mode(struct dac_info *info, int mode) +{ + return info->dacops->dac_set_mode(info, mode); +} + +static inline int dac_set_freq(struct dac_info *info, int channel, u32 freq) +{ + return info->dacops->dac_set_freq(info, channel, freq); +} + +static inline void dac_release(struct dac_info *info) +{ + info->dacops->dac_release(info); +} + + +/* ------------------------------------------------------------------------- */ + + +/* ICS5342 DAC */ + +struct ics5342_info +{ + struct dac_info dac; + u8 mode; +}; + +#define DAC_PAR(info) ((struct ics5342_info *) info) + +/* LSB is set to distinguish unused slots */ +static const u8 ics5342_mode_table[DAC_MAX] = { + [DAC_PSEUDO8_8] = 0x01, [DAC_RGB1555_8] = 0x21, [DAC_RGB0565_8] = 0x61, + [DAC_RGB0888_8] = 0x41, [DAC_PSEUDO8_16] = 0x11, [DAC_RGB1555_16] = 0x31, + [DAC_RGB0565_16] = 0x51, [DAC_RGB0888_16] = 0x91, [DAC_RGB8888_16] = 0x71 +}; + +static int ics5342_set_mode(struct dac_info *info, int mode) +{ + u8 code; + + if (mode >= DAC_MAX) + return -EINVAL; + + code = ics5342_mode_table[mode]; + + if (! code) + return -EINVAL; + + dac_write_reg(info, 6, code & 0xF0); + DAC_PAR(info)->mode = mode; + + return 0; +} + +static const struct svga_pll ics5342_pll = {3, 129, 3, 33, 0, 3, + 60000, 250000, 14318}; + +/* pd4 - allow only posdivider 4 (r=2) */ +static const struct svga_pll ics5342_pll_pd4 = {3, 129, 3, 33, 2, 2, + 60000, 335000, 14318}; + +/* 270 MHz should be upper bound for VCO clock according to specs, + but that is too restrictive in pd4 case */ + +static int ics5342_set_freq(struct dac_info *info, int channel, u32 freq) +{ + u16 m, n, r; + + /* only postdivider 4 (r=2) is valid in mode DAC_PSEUDO8_16 */ + int rv = svga_compute_pll((DAC_PAR(info)->mode == DAC_PSEUDO8_16) + ? &ics5342_pll_pd4 : &ics5342_pll, + freq, &m, &n, &r, 0); + + if (rv < 0) { + return -EINVAL; + } else { + u8 code[6] = {4, 3, 5, m-2, 5, (n-2) | (r << 5)}; + dac_write_regs(info, code, 3); + return 0; + } +} + +static void ics5342_release(struct dac_info *info) +{ + ics5342_set_mode(info, DAC_PSEUDO8_8); + kfree(info); +} + +static struct dac_ops ics5342_ops = { + .dac_set_mode = ics5342_set_mode, + .dac_set_freq = ics5342_set_freq, + .dac_release = ics5342_release +}; + + +static struct dac_info * ics5342_init(dac_read_regs_t drr, dac_write_regs_t dwr, void *data) +{ + struct dac_info *info = kzalloc(sizeof(struct ics5342_info), GFP_KERNEL); + + if (! info) + return NULL; + + info->dacops = &ics5342_ops; + info->dac_read_regs = drr; + info->dac_write_regs = dwr; + info->data = data; + DAC_PAR(info)->mode = DAC_PSEUDO8_8; /* estimation */ + return info; +} + + +/* ------------------------------------------------------------------------- */ + + +static unsigned short dac_regs[4] = {0x3c8, 0x3c9, 0x3c6, 0x3c7}; + +static void ark_dac_read_regs(void *data, u8 *code, int count) +{ + struct fb_info *info = data; + struct arkfb_info *par; + u8 regval; + + par = info->par; + regval = vga_rseq(par->state.vgabase, 0x1C); + while (count != 0) + { + vga_wseq(par->state.vgabase, 0x1C, regval | (code[0] & 4 ? 0x80 : 0)); + code[1] = vga_r(par->state.vgabase, dac_regs[code[0] & 3]); + count--; + code += 2; + } + + vga_wseq(par->state.vgabase, 0x1C, regval); +} + +static void ark_dac_write_regs(void *data, u8 *code, int count) +{ + struct fb_info *info = data; + struct arkfb_info *par; + u8 regval; + + par = info->par; + regval = vga_rseq(par->state.vgabase, 0x1C); + while (count != 0) + { + vga_wseq(par->state.vgabase, 0x1C, regval | (code[0] & 4 ? 0x80 : 0)); + vga_w(par->state.vgabase, dac_regs[code[0] & 3], code[1]); + count--; + code += 2; + } + + vga_wseq(par->state.vgabase, 0x1C, regval); +} + + +static void ark_set_pixclock(struct fb_info *info, u32 pixclock) +{ + struct arkfb_info *par = info->par; + u8 regval; + + int rv = dac_set_freq(par->dac, 0, 1000000000 / pixclock); + if (rv < 0) { + fb_err(info, "cannot set requested pixclock, keeping old value\n"); + return; + } + + /* Set VGA misc register */ + regval = vga_r(par->state.vgabase, VGA_MIS_R); + vga_w(par->state.vgabase, VGA_MIS_W, regval | VGA_MIS_ENB_PLL_LOAD); +} + + +/* Open framebuffer */ + +static int arkfb_open(struct fb_info *info, int user) +{ + struct arkfb_info *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + void __iomem *vgabase = par->state.vgabase; + + memset(&(par->state), 0, sizeof(struct vgastate)); + par->state.vgabase = vgabase; + par->state.flags = VGA_SAVE_MODE | VGA_SAVE_FONTS | VGA_SAVE_CMAP; + par->state.num_crtc = 0x60; + par->state.num_seq = 0x30; + save_vga(&(par->state)); + } + + par->ref_count++; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +/* Close framebuffer */ + +static int arkfb_release(struct fb_info *info, int user) +{ + struct arkfb_info *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + mutex_unlock(&(par->open_lock)); + return -EINVAL; + } + + if (par->ref_count == 1) { + restore_vga(&(par->state)); + dac_set_mode(par->dac, DAC_PSEUDO8_8); + } + + par->ref_count--; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +/* Validate passed in var */ + +static int arkfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int rv, mem, step; + + /* Find appropriate format */ + rv = svga_match_format (arkfb_formats, var, NULL); + if (rv < 0) + { + fb_err(info, "unsupported mode requested\n"); + return rv; + } + + /* Do not allow to have real resoulution larger than virtual */ + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + /* Round up xres_virtual to have proper alignment of lines */ + step = arkfb_formats[rv].xresstep - 1; + var->xres_virtual = (var->xres_virtual+step) & ~step; + + + /* Check whether have enough memory */ + mem = ((var->bits_per_pixel * var->xres_virtual) >> 3) * var->yres_virtual; + if (mem > info->screen_size) + { + fb_err(info, "not enough framebuffer memory (%d kB requested, %d kB available)\n", + mem >> 10, (unsigned int) (info->screen_size >> 10)); + return -EINVAL; + } + + rv = svga_check_timings (&ark_timing_regs, var, info->node); + if (rv < 0) + { + fb_err(info, "invalid timings requested\n"); + return rv; + } + + /* Interlaced mode is broken */ + if (var->vmode & FB_VMODE_INTERLACED) + return -EINVAL; + + return 0; +} + +/* Set video mode from par */ + +static int arkfb_set_par(struct fb_info *info) +{ + struct arkfb_info *par = info->par; + u32 value, mode, hmul, hdiv, offset_value, screen_size; + u32 bpp = info->var.bits_per_pixel; + u8 regval; + + if (bpp != 0) { + info->fix.ypanstep = 1; + info->fix.line_length = (info->var.xres_virtual * bpp) / 8; + + info->flags &= ~FBINFO_MISC_TILEBLITTING; + info->tileops = NULL; + + /* in 4bpp supports 8p wide tiles only, any tiles otherwise */ + info->pixmap.blit_x = (bpp == 4) ? (1 << (8 - 1)) : (~(u32)0); + info->pixmap.blit_y = ~(u32)0; + + offset_value = (info->var.xres_virtual * bpp) / 64; + screen_size = info->var.yres_virtual * info->fix.line_length; + } else { + info->fix.ypanstep = 16; + info->fix.line_length = 0; + + info->flags |= FBINFO_MISC_TILEBLITTING; + info->tileops = &arkfb_tile_ops; + + /* supports 8x16 tiles only */ + info->pixmap.blit_x = 1 << (8 - 1); + info->pixmap.blit_y = 1 << (16 - 1); + + offset_value = info->var.xres_virtual / 16; + screen_size = (info->var.xres_virtual * info->var.yres_virtual) / 64; + } + + info->var.xoffset = 0; + info->var.yoffset = 0; + info->var.activate = FB_ACTIVATE_NOW; + + /* Unlock registers */ + svga_wcrt_mask(par->state.vgabase, 0x11, 0x00, 0x80); + + /* Blank screen and turn off sync */ + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + svga_wcrt_mask(par->state.vgabase, 0x17, 0x00, 0x80); + + /* Set default values */ + svga_set_default_gfx_regs(par->state.vgabase); + svga_set_default_atc_regs(par->state.vgabase); + svga_set_default_seq_regs(par->state.vgabase); + svga_set_default_crt_regs(par->state.vgabase); + svga_wcrt_multi(par->state.vgabase, ark_line_compare_regs, 0xFFFFFFFF); + svga_wcrt_multi(par->state.vgabase, ark_start_address_regs, 0); + + /* ARK specific initialization */ + svga_wseq_mask(par->state.vgabase, 0x10, 0x1F, 0x1F); /* enable linear framebuffer and full memory access */ + svga_wseq_mask(par->state.vgabase, 0x12, 0x03, 0x03); /* 4 MB linear framebuffer size */ + + vga_wseq(par->state.vgabase, 0x13, info->fix.smem_start >> 16); + vga_wseq(par->state.vgabase, 0x14, info->fix.smem_start >> 24); + vga_wseq(par->state.vgabase, 0x15, 0); + vga_wseq(par->state.vgabase, 0x16, 0); + + /* Set the FIFO threshold register */ + /* It is fascinating way to store 5-bit value in 8-bit register */ + regval = 0x10 | ((threshold & 0x0E) >> 1) | (threshold & 0x01) << 7 | (threshold & 0x10) << 1; + vga_wseq(par->state.vgabase, 0x18, regval); + + /* Set the offset register */ + fb_dbg(info, "offset register : %d\n", offset_value); + svga_wcrt_multi(par->state.vgabase, ark_offset_regs, offset_value); + + /* fix for hi-res textmode */ + svga_wcrt_mask(par->state.vgabase, 0x40, 0x08, 0x08); + + if (info->var.vmode & FB_VMODE_DOUBLE) + svga_wcrt_mask(par->state.vgabase, 0x09, 0x80, 0x80); + else + svga_wcrt_mask(par->state.vgabase, 0x09, 0x00, 0x80); + + if (info->var.vmode & FB_VMODE_INTERLACED) + svga_wcrt_mask(par->state.vgabase, 0x44, 0x04, 0x04); + else + svga_wcrt_mask(par->state.vgabase, 0x44, 0x00, 0x04); + + hmul = 1; + hdiv = 1; + mode = svga_match_format(arkfb_formats, &(info->var), &(info->fix)); + + /* Set mode-specific register values */ + switch (mode) { + case 0: + fb_dbg(info, "text mode\n"); + svga_set_textmode_vga_regs(par->state.vgabase); + + vga_wseq(par->state.vgabase, 0x11, 0x10); /* basic VGA mode */ + svga_wcrt_mask(par->state.vgabase, 0x46, 0x00, 0x04); /* 8bit pixel path */ + dac_set_mode(par->dac, DAC_PSEUDO8_8); + + break; + case 1: + fb_dbg(info, "4 bit pseudocolor\n"); + vga_wgfx(par->state.vgabase, VGA_GFX_MODE, 0x40); + + vga_wseq(par->state.vgabase, 0x11, 0x10); /* basic VGA mode */ + svga_wcrt_mask(par->state.vgabase, 0x46, 0x00, 0x04); /* 8bit pixel path */ + dac_set_mode(par->dac, DAC_PSEUDO8_8); + break; + case 2: + fb_dbg(info, "4 bit pseudocolor, planar\n"); + + vga_wseq(par->state.vgabase, 0x11, 0x10); /* basic VGA mode */ + svga_wcrt_mask(par->state.vgabase, 0x46, 0x00, 0x04); /* 8bit pixel path */ + dac_set_mode(par->dac, DAC_PSEUDO8_8); + break; + case 3: + fb_dbg(info, "8 bit pseudocolor\n"); + + vga_wseq(par->state.vgabase, 0x11, 0x16); /* 8bpp accel mode */ + + if (info->var.pixclock > 20000) { + fb_dbg(info, "not using multiplex\n"); + svga_wcrt_mask(par->state.vgabase, 0x46, 0x00, 0x04); /* 8bit pixel path */ + dac_set_mode(par->dac, DAC_PSEUDO8_8); + } else { + fb_dbg(info, "using multiplex\n"); + svga_wcrt_mask(par->state.vgabase, 0x46, 0x04, 0x04); /* 16bit pixel path */ + dac_set_mode(par->dac, DAC_PSEUDO8_16); + hdiv = 2; + } + break; + case 4: + fb_dbg(info, "5/5/5 truecolor\n"); + + vga_wseq(par->state.vgabase, 0x11, 0x1A); /* 16bpp accel mode */ + svga_wcrt_mask(par->state.vgabase, 0x46, 0x04, 0x04); /* 16bit pixel path */ + dac_set_mode(par->dac, DAC_RGB1555_16); + break; + case 5: + fb_dbg(info, "5/6/5 truecolor\n"); + + vga_wseq(par->state.vgabase, 0x11, 0x1A); /* 16bpp accel mode */ + svga_wcrt_mask(par->state.vgabase, 0x46, 0x04, 0x04); /* 16bit pixel path */ + dac_set_mode(par->dac, DAC_RGB0565_16); + break; + case 6: + fb_dbg(info, "8/8/8 truecolor\n"); + + vga_wseq(par->state.vgabase, 0x11, 0x16); /* 8bpp accel mode ??? */ + svga_wcrt_mask(par->state.vgabase, 0x46, 0x04, 0x04); /* 16bit pixel path */ + dac_set_mode(par->dac, DAC_RGB0888_16); + hmul = 3; + hdiv = 2; + break; + case 7: + fb_dbg(info, "8/8/8/8 truecolor\n"); + + vga_wseq(par->state.vgabase, 0x11, 0x1E); /* 32bpp accel mode */ + svga_wcrt_mask(par->state.vgabase, 0x46, 0x04, 0x04); /* 16bit pixel path */ + dac_set_mode(par->dac, DAC_RGB8888_16); + hmul = 2; + break; + default: + fb_err(info, "unsupported mode - bug\n"); + return -EINVAL; + } + + ark_set_pixclock(info, (hdiv * info->var.pixclock) / hmul); + svga_set_timings(par->state.vgabase, &ark_timing_regs, &(info->var), hmul, hdiv, + (info->var.vmode & FB_VMODE_DOUBLE) ? 2 : 1, + (info->var.vmode & FB_VMODE_INTERLACED) ? 2 : 1, + hmul, info->node); + + /* Set interlaced mode start/end register */ + value = info->var.xres + info->var.left_margin + info->var.right_margin + info->var.hsync_len; + value = ((value * hmul / hdiv) / 8) - 5; + vga_wcrt(par->state.vgabase, 0x42, (value + 1) / 2); + + memset_io(info->screen_base, 0x00, screen_size); + /* Device and screen back on */ + svga_wcrt_mask(par->state.vgabase, 0x17, 0x80, 0x80); + svga_wseq_mask(par->state.vgabase, 0x01, 0x00, 0x20); + + return 0; +} + +/* Set a colour register */ + +static int arkfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *fb) +{ + switch (fb->var.bits_per_pixel) { + case 0: + case 4: + if (regno >= 16) + return -EINVAL; + + if ((fb->var.bits_per_pixel == 4) && + (fb->var.nonstd == 0)) { + outb(0xF0, VGA_PEL_MSK); + outb(regno*16, VGA_PEL_IW); + } else { + outb(0x0F, VGA_PEL_MSK); + outb(regno, VGA_PEL_IW); + } + outb(red >> 10, VGA_PEL_D); + outb(green >> 10, VGA_PEL_D); + outb(blue >> 10, VGA_PEL_D); + break; + case 8: + if (regno >= 256) + return -EINVAL; + + outb(0xFF, VGA_PEL_MSK); + outb(regno, VGA_PEL_IW); + outb(red >> 10, VGA_PEL_D); + outb(green >> 10, VGA_PEL_D); + outb(blue >> 10, VGA_PEL_D); + break; + case 16: + if (regno >= 16) + return 0; + + if (fb->var.green.length == 5) + ((u32*)fb->pseudo_palette)[regno] = ((red & 0xF800) >> 1) | + ((green & 0xF800) >> 6) | ((blue & 0xF800) >> 11); + else if (fb->var.green.length == 6) + ((u32*)fb->pseudo_palette)[regno] = (red & 0xF800) | + ((green & 0xFC00) >> 5) | ((blue & 0xF800) >> 11); + else + return -EINVAL; + break; + case 24: + case 32: + if (regno >= 16) + return 0; + + ((u32*)fb->pseudo_palette)[regno] = ((red & 0xFF00) << 8) | + (green & 0xFF00) | ((blue & 0xFF00) >> 8); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Set the display blanking state */ + +static int arkfb_blank(int blank_mode, struct fb_info *info) +{ + struct arkfb_info *par = info->par; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + fb_dbg(info, "unblank\n"); + svga_wseq_mask(par->state.vgabase, 0x01, 0x00, 0x20); + svga_wcrt_mask(par->state.vgabase, 0x17, 0x80, 0x80); + break; + case FB_BLANK_NORMAL: + fb_dbg(info, "blank\n"); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + svga_wcrt_mask(par->state.vgabase, 0x17, 0x80, 0x80); + break; + case FB_BLANK_POWERDOWN: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_VSYNC_SUSPEND: + fb_dbg(info, "sync down\n"); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + svga_wcrt_mask(par->state.vgabase, 0x17, 0x00, 0x80); + break; + } + return 0; +} + + +/* Pan the display */ + +static int arkfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct arkfb_info *par = info->par; + unsigned int offset; + + /* Calculate the offset */ + if (info->var.bits_per_pixel == 0) { + offset = (var->yoffset / 16) * (info->var.xres_virtual / 2) + + (var->xoffset / 2); + offset = offset >> 2; + } else { + offset = (var->yoffset * info->fix.line_length) + + (var->xoffset * info->var.bits_per_pixel / 8); + offset = offset >> ((info->var.bits_per_pixel == 4) ? 2 : 3); + } + + /* Set the offset */ + svga_wcrt_multi(par->state.vgabase, ark_start_address_regs, offset); + + return 0; +} + + +/* ------------------------------------------------------------------------- */ + + +/* Frame buffer operations */ + +static struct fb_ops arkfb_ops = { + .owner = THIS_MODULE, + .fb_open = arkfb_open, + .fb_release = arkfb_release, + .fb_check_var = arkfb_check_var, + .fb_set_par = arkfb_set_par, + .fb_setcolreg = arkfb_setcolreg, + .fb_blank = arkfb_blank, + .fb_pan_display = arkfb_pan_display, + .fb_fillrect = arkfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = arkfb_imageblit, + .fb_get_caps = svga_get_caps, +}; + + +/* ------------------------------------------------------------------------- */ + + +/* PCI probe */ +static int ark_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct pci_bus_region bus_reg; + struct resource vga_res; + struct fb_info *info; + struct arkfb_info *par; + int rc; + u8 regval; + + /* Ignore secondary VGA device because there is no VGA arbitration */ + if (! svga_primary_device(dev)) { + dev_info(&(dev->dev), "ignoring secondary device\n"); + return -ENODEV; + } + + /* Allocate and fill driver data structure */ + info = framebuffer_alloc(sizeof(struct arkfb_info), &(dev->dev)); + if (! info) { + dev_err(&(dev->dev), "cannot allocate memory\n"); + return -ENOMEM; + } + + par = info->par; + mutex_init(&par->open_lock); + + info->flags = FBINFO_PARTIAL_PAN_OK | FBINFO_HWACCEL_YPAN; + info->fbops = &arkfb_ops; + + /* Prepare PCI device */ + rc = pci_enable_device(dev); + if (rc < 0) { + dev_err(info->device, "cannot enable PCI device\n"); + goto err_enable_device; + } + + rc = pci_request_regions(dev, "arkfb"); + if (rc < 0) { + dev_err(info->device, "cannot reserve framebuffer region\n"); + goto err_request_regions; + } + + par->dac = ics5342_init(ark_dac_read_regs, ark_dac_write_regs, info); + if (! par->dac) { + rc = -ENOMEM; + dev_err(info->device, "RAMDAC initialization failed\n"); + goto err_dac; + } + + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = pci_resource_len(dev, 0); + + /* Map physical IO memory address into kernel space */ + info->screen_base = pci_iomap(dev, 0, 0); + if (! info->screen_base) { + rc = -ENOMEM; + dev_err(info->device, "iomap for framebuffer failed\n"); + goto err_iomap; + } + + bus_reg.start = 0; + bus_reg.end = 64 * 1024; + + vga_res.flags = IORESOURCE_IO; + + pcibios_bus_to_resource(dev->bus, &vga_res, &bus_reg); + + par->state.vgabase = (void __iomem *) vga_res.start; + + /* FIXME get memsize */ + regval = vga_rseq(par->state.vgabase, 0x10); + info->screen_size = (1 << (regval >> 6)) << 20; + info->fix.smem_len = info->screen_size; + + strcpy(info->fix.id, "ARK 2000PV"); + info->fix.mmio_start = 0; + info->fix.mmio_len = 0; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.ypanstep = 0; + info->fix.accel = FB_ACCEL_NONE; + info->pseudo_palette = (void*) (par->pseudo_palette); + + /* Prepare startup mode */ + rc = fb_find_mode(&(info->var), info, mode_option, NULL, 0, NULL, 8); + if (! ((rc == 1) || (rc == 2))) { + rc = -EINVAL; + dev_err(info->device, "mode %s not found\n", mode_option); + goto err_find_mode; + } + + rc = fb_alloc_cmap(&info->cmap, 256, 0); + if (rc < 0) { + dev_err(info->device, "cannot allocate colormap\n"); + goto err_alloc_cmap; + } + + rc = register_framebuffer(info); + if (rc < 0) { + dev_err(info->device, "cannot register framebuffer\n"); + goto err_reg_fb; + } + + fb_info(info, "%s on %s, %d MB RAM\n", + info->fix.id, pci_name(dev), info->fix.smem_len >> 20); + + /* Record a reference to the driver data */ + pci_set_drvdata(dev, info); + +#ifdef CONFIG_MTRR + if (mtrr) { + par->mtrr_reg = -1; + par->mtrr_reg = mtrr_add(info->fix.smem_start, info->fix.smem_len, MTRR_TYPE_WRCOMB, 1); + } +#endif + + return 0; + + /* Error handling */ +err_reg_fb: + fb_dealloc_cmap(&info->cmap); +err_alloc_cmap: +err_find_mode: + pci_iounmap(dev, info->screen_base); +err_iomap: + dac_release(par->dac); +err_dac: + pci_release_regions(dev); +err_request_regions: +/* pci_disable_device(dev); */ +err_enable_device: + framebuffer_release(info); + return rc; +} + +/* PCI remove */ + +static void ark_pci_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + + if (info) { + struct arkfb_info *par = info->par; + +#ifdef CONFIG_MTRR + if (par->mtrr_reg >= 0) { + mtrr_del(par->mtrr_reg, 0, 0); + par->mtrr_reg = -1; + } +#endif + + dac_release(par->dac); + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + pci_iounmap(dev, info->screen_base); + pci_release_regions(dev); +/* pci_disable_device(dev); */ + + framebuffer_release(info); + } +} + + +#ifdef CONFIG_PM +/* PCI suspend */ + +static int ark_pci_suspend (struct pci_dev* dev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct arkfb_info *par = info->par; + + dev_info(info->device, "suspend\n"); + + console_lock(); + mutex_lock(&(par->open_lock)); + + if ((state.event == PM_EVENT_FREEZE) || (par->ref_count == 0)) { + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; + } + + fb_set_suspend(info, 1); + + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, state)); + + mutex_unlock(&(par->open_lock)); + console_unlock(); + + return 0; +} + + +/* PCI resume */ + +static int ark_pci_resume (struct pci_dev* dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct arkfb_info *par = info->par; + + dev_info(info->device, "resume\n"); + + console_lock(); + mutex_lock(&(par->open_lock)); + + if (par->ref_count == 0) + goto fail; + + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + + if (pci_enable_device(dev)) + goto fail; + + pci_set_master(dev); + + arkfb_set_par(info); + fb_set_suspend(info, 0); + +fail: + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; +} +#else +#define ark_pci_suspend NULL +#define ark_pci_resume NULL +#endif /* CONFIG_PM */ + +/* List of boards that we are trying to support */ + +static struct pci_device_id ark_devices[] = { + {PCI_DEVICE(0xEDD8, 0xA099)}, + {0, 0, 0, 0, 0, 0, 0} +}; + + +MODULE_DEVICE_TABLE(pci, ark_devices); + +static struct pci_driver arkfb_pci_driver = { + .name = "arkfb", + .id_table = ark_devices, + .probe = ark_pci_probe, + .remove = ark_pci_remove, + .suspend = ark_pci_suspend, + .resume = ark_pci_resume, +}; + +/* Cleanup */ + +static void __exit arkfb_cleanup(void) +{ + pr_debug("arkfb: cleaning up\n"); + pci_unregister_driver(&arkfb_pci_driver); +} + +/* Driver Initialisation */ + +static int __init arkfb_init(void) +{ + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("arkfb", &option)) + return -ENODEV; + + if (option && *option) + mode_option = option; +#endif + + pr_debug("arkfb: initializing\n"); + return pci_register_driver(&arkfb_pci_driver); +} + +module_init(arkfb_init); +module_exit(arkfb_cleanup); diff --git a/drivers/video/fbdev/asiliantfb.c b/drivers/video/fbdev/asiliantfb.c new file mode 100644 index 000000000000..7e8ddf00ccc2 --- /dev/null +++ b/drivers/video/fbdev/asiliantfb.c @@ -0,0 +1,624 @@ +/* + * drivers/video/asiliantfb.c + * frame buffer driver for Asiliant 69000 chip + * Copyright (C) 2001-2003 Saito.K & Jeanne + * + * from driver/video/chipsfb.c and, + * + * drivers/video/asiliantfb.c -- frame buffer device for + * Asiliant 69030 chip (formerly Intel, formerly Chips & Technologies) + * Author: apc@agelectronics.co.uk + * Copyright (C) 2000 AG Electronics + * Note: the data sheets don't seem to be available from Asiliant. + * They are available by searching developer.intel.com, but are not otherwise + * linked to. + * + * This driver should be portable with minimal effort to the 69000 display + * chip, and to the twin-display mode of the 69030. + * Contains code from Thomas Hhenleitner <th@visuelle-maschinen.de> (thanks) + * + * Derived from the CT65550 driver chipsfb.c: + * Copyright (C) 1998 Paul Mackerras + * ...which was derived from the Powermac "chips" driver: + * Copyright (C) 1997 Fabio Riccardi. + * And from the frame buffer device for Open Firmware-initialized devices: + * Copyright (C) 1997 Geert Uytterhoeven. + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <asm/io.h> + +/* Built in clock of the 69030 */ +static const unsigned Fref = 14318180; + +#define mmio_base (p->screen_base + 0x400000) + +#define mm_write_ind(num, val, ap, dp) do { \ + writeb((num), mmio_base + (ap)); writeb((val), mmio_base + (dp)); \ +} while (0) + +static void mm_write_xr(struct fb_info *p, u8 reg, u8 data) +{ + mm_write_ind(reg, data, 0x7ac, 0x7ad); +} +#define write_xr(num, val) mm_write_xr(p, num, val) + +static void mm_write_fr(struct fb_info *p, u8 reg, u8 data) +{ + mm_write_ind(reg, data, 0x7a0, 0x7a1); +} +#define write_fr(num, val) mm_write_fr(p, num, val) + +static void mm_write_cr(struct fb_info *p, u8 reg, u8 data) +{ + mm_write_ind(reg, data, 0x7a8, 0x7a9); +} +#define write_cr(num, val) mm_write_cr(p, num, val) + +static void mm_write_gr(struct fb_info *p, u8 reg, u8 data) +{ + mm_write_ind(reg, data, 0x79c, 0x79d); +} +#define write_gr(num, val) mm_write_gr(p, num, val) + +static void mm_write_sr(struct fb_info *p, u8 reg, u8 data) +{ + mm_write_ind(reg, data, 0x788, 0x789); +} +#define write_sr(num, val) mm_write_sr(p, num, val) + +static void mm_write_ar(struct fb_info *p, u8 reg, u8 data) +{ + readb(mmio_base + 0x7b4); + mm_write_ind(reg, data, 0x780, 0x780); +} +#define write_ar(num, val) mm_write_ar(p, num, val) + +static int asiliantfb_pci_init(struct pci_dev *dp, const struct pci_device_id *); +static int asiliantfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int asiliantfb_set_par(struct fb_info *info); +static int asiliantfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); + +static struct fb_ops asiliantfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = asiliantfb_check_var, + .fb_set_par = asiliantfb_set_par, + .fb_setcolreg = asiliantfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* Calculate the ratios for the dot clocks without using a single long long + * value */ +static void asiliant_calc_dclk2(u32 *ppixclock, u8 *dclk2_m, u8 *dclk2_n, u8 *dclk2_div) +{ + unsigned pixclock = *ppixclock; + unsigned Ftarget = 1000000 * (1000000 / pixclock); + unsigned n; + unsigned best_error = 0xffffffff; + unsigned best_m = 0xffffffff, + best_n = 0xffffffff; + unsigned ratio; + unsigned remainder; + unsigned char divisor = 0; + + /* Calculate the frequency required. This is hard enough. */ + ratio = 1000000 / pixclock; + remainder = 1000000 % pixclock; + Ftarget = 1000000 * ratio + (1000000 * remainder) / pixclock; + + while (Ftarget < 100000000) { + divisor += 0x10; + Ftarget <<= 1; + } + + ratio = Ftarget / Fref; + remainder = Ftarget % Fref; + + /* This expresses the constraint that 150kHz <= Fref/n <= 5Mhz, + * together with 3 <= n <= 257. */ + for (n = 3; n <= 257; n++) { + unsigned m = n * ratio + (n * remainder) / Fref; + + /* 3 <= m <= 257 */ + if (m >= 3 && m <= 257) { + unsigned new_error = Ftarget * n >= Fref * m ? + ((Ftarget * n) - (Fref * m)) : ((Fref * m) - (Ftarget * n)); + if (new_error < best_error) { + best_n = n; + best_m = m; + best_error = new_error; + } + } + /* But if VLD = 4, then 4m <= 1028 */ + else if (m <= 1028) { + /* remember there are still only 8-bits of precision in m, so + * avoid over-optimistic error calculations */ + unsigned new_error = Ftarget * n >= Fref * (m & ~3) ? + ((Ftarget * n) - (Fref * (m & ~3))) : ((Fref * (m & ~3)) - (Ftarget * n)); + if (new_error < best_error) { + best_n = n; + best_m = m; + best_error = new_error; + } + } + } + if (best_m > 257) + best_m >>= 2; /* divide m by 4, and leave VCO loop divide at 4 */ + else + divisor |= 4; /* or set VCO loop divide to 1 */ + *dclk2_m = best_m - 2; + *dclk2_n = best_n - 2; + *dclk2_div = divisor; + *ppixclock = pixclock; + return; +} + +static void asiliant_set_timing(struct fb_info *p) +{ + unsigned hd = p->var.xres / 8; + unsigned hs = (p->var.xres + p->var.right_margin) / 8; + unsigned he = (p->var.xres + p->var.right_margin + p->var.hsync_len) / 8; + unsigned ht = (p->var.left_margin + p->var.xres + p->var.right_margin + p->var.hsync_len) / 8; + unsigned vd = p->var.yres; + unsigned vs = p->var.yres + p->var.lower_margin; + unsigned ve = p->var.yres + p->var.lower_margin + p->var.vsync_len; + unsigned vt = p->var.upper_margin + p->var.yres + p->var.lower_margin + p->var.vsync_len; + unsigned wd = (p->var.xres_virtual * ((p->var.bits_per_pixel+7)/8)) / 8; + + if ((p->var.xres == 640) && (p->var.yres == 480) && (p->var.pixclock == 39722)) { + write_fr(0x01, 0x02); /* LCD */ + } else { + write_fr(0x01, 0x01); /* CRT */ + } + + write_cr(0x11, (ve - 1) & 0x0f); + write_cr(0x00, (ht - 5) & 0xff); + write_cr(0x01, hd - 1); + write_cr(0x02, hd); + write_cr(0x03, ((ht - 1) & 0x1f) | 0x80); + write_cr(0x04, hs); + write_cr(0x05, (((ht - 1) & 0x20) <<2) | (he & 0x1f)); + write_cr(0x3c, (ht - 1) & 0xc0); + write_cr(0x06, (vt - 2) & 0xff); + write_cr(0x30, (vt - 2) >> 8); + write_cr(0x07, 0x00); + write_cr(0x08, 0x00); + write_cr(0x09, 0x00); + write_cr(0x10, (vs - 1) & 0xff); + write_cr(0x32, ((vs - 1) >> 8) & 0xf); + write_cr(0x11, ((ve - 1) & 0x0f) | 0x80); + write_cr(0x12, (vd - 1) & 0xff); + write_cr(0x31, ((vd - 1) & 0xf00) >> 8); + write_cr(0x13, wd & 0xff); + write_cr(0x41, (wd & 0xf00) >> 8); + write_cr(0x15, (vs - 1) & 0xff); + write_cr(0x33, ((vs - 1) >> 8) & 0xf); + write_cr(0x38, ((ht - 5) & 0x100) >> 8); + write_cr(0x16, (vt - 1) & 0xff); + write_cr(0x18, 0x00); + + if (p->var.xres == 640) { + writeb(0xc7, mmio_base + 0x784); /* set misc output reg */ + } else { + writeb(0x07, mmio_base + 0x784); /* set misc output reg */ + } +} + +static int asiliantfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *p) +{ + unsigned long Ftarget, ratio, remainder; + + ratio = 1000000 / var->pixclock; + remainder = 1000000 % var->pixclock; + Ftarget = 1000000 * ratio + (1000000 * remainder) / var->pixclock; + + /* First check the constraint that the maximum post-VCO divisor is 32, + * and the maximum Fvco is 220MHz */ + if (Ftarget > 220000000 || Ftarget < 3125000) { + printk(KERN_ERR "asiliantfb dotclock must be between 3.125 and 220MHz\n"); + return -ENXIO; + } + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + if (var->bits_per_pixel == 24) { + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = var->blue.length = var->green.length = 8; + } else if (var->bits_per_pixel == 16) { + switch (var->red.offset) { + case 11: + var->green.length = 6; + break; + case 10: + var->green.length = 5; + break; + default: + return -EINVAL; + } + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = var->blue.length = 5; + } else if (var->bits_per_pixel == 8) { + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + } + return 0; +} + +static int asiliantfb_set_par(struct fb_info *p) +{ + u8 dclk2_m; /* Holds m-2 value for register */ + u8 dclk2_n; /* Holds n-2 value for register */ + u8 dclk2_div; /* Holds divisor bitmask */ + + /* Set pixclock */ + asiliant_calc_dclk2(&p->var.pixclock, &dclk2_m, &dclk2_n, &dclk2_div); + + /* Set color depth */ + if (p->var.bits_per_pixel == 24) { + write_xr(0x81, 0x16); /* 24 bit packed color mode */ + write_xr(0x82, 0x00); /* Disable palettes */ + write_xr(0x20, 0x20); /* 24 bit blitter mode */ + } else if (p->var.bits_per_pixel == 16) { + if (p->var.red.offset == 11) + write_xr(0x81, 0x15); /* 16 bit color mode */ + else + write_xr(0x81, 0x14); /* 15 bit color mode */ + write_xr(0x82, 0x00); /* Disable palettes */ + write_xr(0x20, 0x10); /* 16 bit blitter mode */ + } else if (p->var.bits_per_pixel == 8) { + write_xr(0x0a, 0x02); /* Linear */ + write_xr(0x81, 0x12); /* 8 bit color mode */ + write_xr(0x82, 0x00); /* Graphics gamma enable */ + write_xr(0x20, 0x00); /* 8 bit blitter mode */ + } + p->fix.line_length = p->var.xres * (p->var.bits_per_pixel >> 3); + p->fix.visual = (p->var.bits_per_pixel == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + write_xr(0xc4, dclk2_m); + write_xr(0xc5, dclk2_n); + write_xr(0xc7, dclk2_div); + /* Set up the CR registers */ + asiliant_set_timing(p); + return 0; +} + +static int asiliantfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *p) +{ + if (regno > 255) + return 1; + red >>= 8; + green >>= 8; + blue >>= 8; + + /* Set hardware palete */ + writeb(regno, mmio_base + 0x790); + udelay(1); + writeb(red, mmio_base + 0x791); + writeb(green, mmio_base + 0x791); + writeb(blue, mmio_base + 0x791); + + if (regno < 16) { + switch(p->var.red.offset) { + case 10: /* RGB 555 */ + ((u32 *)(p->pseudo_palette))[regno] = + ((red & 0xf8) << 7) | + ((green & 0xf8) << 2) | + ((blue & 0xf8) >> 3); + break; + case 11: /* RGB 565 */ + ((u32 *)(p->pseudo_palette))[regno] = + ((red & 0xf8) << 8) | + ((green & 0xfc) << 3) | + ((blue & 0xf8) >> 3); + break; + case 16: /* RGB 888 */ + ((u32 *)(p->pseudo_palette))[regno] = + (red << 16) | + (green << 8) | + (blue); + break; + } + } + + return 0; +} + +struct chips_init_reg { + unsigned char addr; + unsigned char data; +}; + +static struct chips_init_reg chips_init_sr[] = +{ + {0x00, 0x03}, /* Reset register */ + {0x01, 0x01}, /* Clocking mode */ + {0x02, 0x0f}, /* Plane mask */ + {0x04, 0x0e} /* Memory mode */ +}; + +static struct chips_init_reg chips_init_gr[] = +{ + {0x03, 0x00}, /* Data rotate */ + {0x05, 0x00}, /* Graphics mode */ + {0x06, 0x01}, /* Miscellaneous */ + {0x08, 0x00} /* Bit mask */ +}; + +static struct chips_init_reg chips_init_ar[] = +{ + {0x10, 0x01}, /* Mode control */ + {0x11, 0x00}, /* Overscan */ + {0x12, 0x0f}, /* Memory plane enable */ + {0x13, 0x00} /* Horizontal pixel panning */ +}; + +static struct chips_init_reg chips_init_cr[] = +{ + {0x0c, 0x00}, /* Start address high */ + {0x0d, 0x00}, /* Start address low */ + {0x40, 0x00}, /* Extended Start Address */ + {0x41, 0x00}, /* Extended Start Address */ + {0x14, 0x00}, /* Underline location */ + {0x17, 0xe3}, /* CRT mode control */ + {0x70, 0x00} /* Interlace control */ +}; + + +static struct chips_init_reg chips_init_fr[] = +{ + {0x01, 0x02}, + {0x03, 0x08}, + {0x08, 0xcc}, + {0x0a, 0x08}, + {0x18, 0x00}, + {0x1e, 0x80}, + {0x40, 0x83}, + {0x41, 0x00}, + {0x48, 0x13}, + {0x4d, 0x60}, + {0x4e, 0x0f}, + + {0x0b, 0x01}, + + {0x21, 0x51}, + {0x22, 0x1d}, + {0x23, 0x5f}, + {0x20, 0x4f}, + {0x34, 0x00}, + {0x24, 0x51}, + {0x25, 0x00}, + {0x27, 0x0b}, + {0x26, 0x00}, + {0x37, 0x80}, + {0x33, 0x0b}, + {0x35, 0x11}, + {0x36, 0x02}, + {0x31, 0xea}, + {0x32, 0x0c}, + {0x30, 0xdf}, + {0x10, 0x0c}, + {0x11, 0xe0}, + {0x12, 0x50}, + {0x13, 0x00}, + {0x16, 0x03}, + {0x17, 0xbd}, + {0x1a, 0x00}, +}; + + +static struct chips_init_reg chips_init_xr[] = +{ + {0xce, 0x00}, /* set default memory clock */ + {0xcc, 200 }, /* MCLK ratio M */ + {0xcd, 18 }, /* MCLK ratio N */ + {0xce, 0x90}, /* MCLK divisor = 2 */ + + {0xc4, 209 }, + {0xc5, 118 }, + {0xc7, 32 }, + {0xcf, 0x06}, + {0x09, 0x01}, /* IO Control - CRT controller extensions */ + {0x0a, 0x02}, /* Frame buffer mapping */ + {0x0b, 0x01}, /* PCI burst write */ + {0x40, 0x03}, /* Memory access control */ + {0x80, 0x82}, /* Pixel pipeline configuration 0 */ + {0x81, 0x12}, /* Pixel pipeline configuration 1 */ + {0x82, 0x08}, /* Pixel pipeline configuration 2 */ + + {0xd0, 0x0f}, + {0xd1, 0x01}, +}; + +static void chips_hw_init(struct fb_info *p) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(chips_init_xr); ++i) + write_xr(chips_init_xr[i].addr, chips_init_xr[i].data); + write_xr(0x81, 0x12); + write_xr(0x82, 0x08); + write_xr(0x20, 0x00); + for (i = 0; i < ARRAY_SIZE(chips_init_sr); ++i) + write_sr(chips_init_sr[i].addr, chips_init_sr[i].data); + for (i = 0; i < ARRAY_SIZE(chips_init_gr); ++i) + write_gr(chips_init_gr[i].addr, chips_init_gr[i].data); + for (i = 0; i < ARRAY_SIZE(chips_init_ar); ++i) + write_ar(chips_init_ar[i].addr, chips_init_ar[i].data); + /* Enable video output in attribute index register */ + writeb(0x20, mmio_base + 0x780); + for (i = 0; i < ARRAY_SIZE(chips_init_cr); ++i) + write_cr(chips_init_cr[i].addr, chips_init_cr[i].data); + for (i = 0; i < ARRAY_SIZE(chips_init_fr); ++i) + write_fr(chips_init_fr[i].addr, chips_init_fr[i].data); +} + +static struct fb_fix_screeninfo asiliantfb_fix = { + .id = "Asiliant 69000", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .accel = FB_ACCEL_NONE, + .line_length = 640, + .smem_len = 0x200000, /* 2MB */ +}; + +static struct fb_var_screeninfo asiliantfb_var = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 8, + .red = { .length = 8 }, + .green = { .length = 8 }, + .blue = { .length = 8 }, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, + .pixclock = 39722, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, +}; + +static int init_asiliant(struct fb_info *p, unsigned long addr) +{ + int err; + + p->fix = asiliantfb_fix; + p->fix.smem_start = addr; + p->var = asiliantfb_var; + p->fbops = &asiliantfb_ops; + p->flags = FBINFO_DEFAULT; + + err = fb_alloc_cmap(&p->cmap, 256, 0); + if (err) { + printk(KERN_ERR "C&T 69000 fb failed to alloc cmap memory\n"); + return err; + } + + err = register_framebuffer(p); + if (err < 0) { + printk(KERN_ERR "C&T 69000 framebuffer failed to register\n"); + fb_dealloc_cmap(&p->cmap); + return err; + } + + fb_info(p, "Asiliant 69000 frame buffer (%dK RAM detected)\n", + p->fix.smem_len / 1024); + + writeb(0xff, mmio_base + 0x78c); + chips_hw_init(p); + return 0; +} + +static int asiliantfb_pci_init(struct pci_dev *dp, + const struct pci_device_id *ent) +{ + unsigned long addr, size; + struct fb_info *p; + int err; + + if ((dp->resource[0].flags & IORESOURCE_MEM) == 0) + return -ENODEV; + addr = pci_resource_start(dp, 0); + size = pci_resource_len(dp, 0); + if (addr == 0) + return -ENODEV; + if (!request_mem_region(addr, size, "asiliantfb")) + return -EBUSY; + + p = framebuffer_alloc(sizeof(u32) * 16, &dp->dev); + if (!p) { + release_mem_region(addr, size); + return -ENOMEM; + } + p->pseudo_palette = p->par; + p->par = NULL; + + p->screen_base = ioremap(addr, 0x800000); + if (p->screen_base == NULL) { + release_mem_region(addr, size); + framebuffer_release(p); + return -ENOMEM; + } + + pci_write_config_dword(dp, 4, 0x02800083); + writeb(3, p->screen_base + 0x400784); + + err = init_asiliant(p, addr); + if (err) { + iounmap(p->screen_base); + release_mem_region(addr, size); + framebuffer_release(p); + return err; + } + + pci_set_drvdata(dp, p); + return 0; +} + +static void asiliantfb_remove(struct pci_dev *dp) +{ + struct fb_info *p = pci_get_drvdata(dp); + + unregister_framebuffer(p); + fb_dealloc_cmap(&p->cmap); + iounmap(p->screen_base); + release_mem_region(pci_resource_start(dp, 0), pci_resource_len(dp, 0)); + framebuffer_release(p); +} + +static struct pci_device_id asiliantfb_pci_tbl[] = { + { PCI_VENDOR_ID_CT, PCI_DEVICE_ID_CT_69000, PCI_ANY_ID, PCI_ANY_ID }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, asiliantfb_pci_tbl); + +static struct pci_driver asiliantfb_driver = { + .name = "asiliantfb", + .id_table = asiliantfb_pci_tbl, + .probe = asiliantfb_pci_init, + .remove = asiliantfb_remove, +}; + +static int __init asiliantfb_init(void) +{ + if (fb_get_options("asiliantfb", NULL)) + return -ENODEV; + + return pci_register_driver(&asiliantfb_driver); +} + +module_init(asiliantfb_init); + +static void __exit asiliantfb_exit(void) +{ + pci_unregister_driver(&asiliantfb_driver); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/atafb.c b/drivers/video/fbdev/atafb.c new file mode 100644 index 000000000000..e21d1f58554c --- /dev/null +++ b/drivers/video/fbdev/atafb.c @@ -0,0 +1,3266 @@ +/* + * linux/drivers/video/atafb.c -- Atari builtin chipset frame buffer device + * + * Copyright (C) 1994 Martin Schaller & Roman Hodek + * + * 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. + * + * History: + * - 03 Jan 95: Original version by Martin Schaller: The TT driver and + * all the device independent stuff + * - 09 Jan 95: Roman: I've added the hardware abstraction (hw_switch) + * and wrote the Falcon, ST(E), and External drivers + * based on the original TT driver. + * - 07 May 95: Martin: Added colormap operations for the external driver + * - 21 May 95: Martin: Added support for overscan + * Andreas: some bug fixes for this + * - Jul 95: Guenther Kelleter <guenther@pool.informatik.rwth-aachen.de>: + * Programmable Falcon video modes + * (thanks to Christian Cartus for documentation + * of VIDEL registers). + * - 27 Dec 95: Guenther: Implemented user definable video modes "user[0-7]" + * on minor 24...31. "user0" may be set on commandline by + * "R<x>;<y>;<depth>". (Makes sense only on Falcon) + * Video mode switch on Falcon now done at next VBL interrupt + * to avoid the annoying right shift of the screen. + * - 23 Sep 97: Juergen: added xres_virtual for cards like ProMST + * The external-part is legacy, therefore hardware-specific + * functions like panning/hardwarescrolling/blanking isn't + * supported. + * - 29 Sep 97: Juergen: added Romans suggestion for pan_display + * (var->xoffset was changed even if no set_screen_base avail.) + * - 05 Oct 97: Juergen: extfb (PACKED_PIXEL) is FB_PSEUDOCOLOR 'cause + * we know how to set the colors + * ext_*palette: read from ext_colors (former MV300_colors) + * write to ext_colors and RAMDAC + * + * To do: + * - For the Falcon it is not possible to set random video modes on + * SM124 and SC/TV, only the bootup resolution is supported. + * + */ + +#define ATAFB_TT +#define ATAFB_STE +#define ATAFB_EXT +#define ATAFB_FALCON + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> + +#include <asm/setup.h> +#include <linux/uaccess.h> +#include <asm/pgtable.h> +#include <asm/irq.h> +#include <asm/io.h> + +#include <asm/atarihw.h> +#include <asm/atariints.h> +#include <asm/atari_stram.h> + +#include <linux/fb.h> +#include <asm/atarikb.h> + +#include "c2p.h" +#include "atafb.h" + +#define SWITCH_ACIA 0x01 /* modes for switch on OverScan */ +#define SWITCH_SND6 0x40 +#define SWITCH_SND7 0x80 +#define SWITCH_NONE 0x00 + + +#define up(x, r) (((x) + (r) - 1) & ~((r)-1)) + + /* + * Interface to the world + */ + +static int atafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +static int atafb_set_par(struct fb_info *info); +static int atafb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, + unsigned int blue, unsigned int transp, + struct fb_info *info); +static int atafb_blank(int blank, struct fb_info *info); +static int atafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static void atafb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect); +static void atafb_copyarea(struct fb_info *info, + const struct fb_copyarea *region); +static void atafb_imageblit(struct fb_info *info, const struct fb_image *image); +static int atafb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); + + +static int default_par; /* default resolution (0=none) */ + +static unsigned long default_mem_req; + +static int hwscroll = -1; + +static int use_hwscroll = 1; + +static int sttt_xres = 640, st_yres = 400, tt_yres = 480; +static int sttt_xres_virtual = 640, sttt_yres_virtual = 400; +static int ovsc_offset, ovsc_addlen; + + /* + * Hardware parameters for current mode + */ + +static struct atafb_par { + void *screen_base; + int yres_virtual; + u_long next_line; +#if defined ATAFB_TT || defined ATAFB_STE + union { + struct { + int mode; + int sync; + } tt, st; +#endif +#ifdef ATAFB_FALCON + struct falcon_hw { + /* Here are fields for storing a video mode, as direct + * parameters for the hardware. + */ + short sync; + short line_width; + short line_offset; + short st_shift; + short f_shift; + short vid_control; + short vid_mode; + short xoffset; + short hht, hbb, hbe, hdb, hde, hss; + short vft, vbb, vbe, vdb, vde, vss; + /* auxiliary information */ + short mono; + short ste_mode; + short bpp; + u32 pseudo_palette[16]; + } falcon; +#endif + /* Nothing needed for external mode */ + } hw; +} current_par; + +/* Don't calculate an own resolution, and thus don't change the one found when + * booting (currently used for the Falcon to keep settings for internal video + * hardware extensions (e.g. ScreenBlaster) */ +static int DontCalcRes = 0; + +#ifdef ATAFB_FALCON +#define HHT hw.falcon.hht +#define HBB hw.falcon.hbb +#define HBE hw.falcon.hbe +#define HDB hw.falcon.hdb +#define HDE hw.falcon.hde +#define HSS hw.falcon.hss +#define VFT hw.falcon.vft +#define VBB hw.falcon.vbb +#define VBE hw.falcon.vbe +#define VDB hw.falcon.vdb +#define VDE hw.falcon.vde +#define VSS hw.falcon.vss +#define VCO_CLOCK25 0x04 +#define VCO_CSYPOS 0x10 +#define VCO_VSYPOS 0x20 +#define VCO_HSYPOS 0x40 +#define VCO_SHORTOFFS 0x100 +#define VMO_DOUBLE 0x01 +#define VMO_INTER 0x02 +#define VMO_PREMASK 0x0c +#endif + +static struct fb_info fb_info = { + .fix = { + .id = "Atari ", + .visual = FB_VISUAL_PSEUDOCOLOR, + .accel = FB_ACCEL_NONE, + } +}; + +static void *screen_base; /* base address of screen */ +static void *real_screen_base; /* (only for Overscan) */ + +static int screen_len; + +static int current_par_valid; + +static int mono_moni; + + +#ifdef ATAFB_EXT + +/* external video handling */ +static unsigned int external_xres; +static unsigned int external_xres_virtual; +static unsigned int external_yres; + +/* + * not needed - atafb will never support panning/hardwarescroll with external + * static unsigned int external_yres_virtual; + */ +static unsigned int external_depth; +static int external_pmode; +static void *external_addr; +static unsigned long external_len; +static unsigned long external_vgaiobase; +static unsigned int external_bitspercol = 6; + +/* + * JOE <joe@amber.dinoco.de>: + * added card type for external driver, is only needed for + * colormap handling. + */ +enum cardtype { IS_VGA, IS_MV300 }; +static enum cardtype external_card_type = IS_VGA; + +/* + * The MV300 mixes the color registers. So we need an array of munged + * indices in order to access the correct reg. + */ +static int MV300_reg_1bit[2] = { + 0, 1 +}; +static int MV300_reg_4bit[16] = { + 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 +}; +static int MV300_reg_8bit[256] = { + 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255 +}; + +static int *MV300_reg = MV300_reg_8bit; +#endif /* ATAFB_EXT */ + + +static int inverse; + +extern int fontheight_8x8; +extern int fontwidth_8x8; +extern unsigned char fontdata_8x8[]; + +extern int fontheight_8x16; +extern int fontwidth_8x16; +extern unsigned char fontdata_8x16[]; + +/* + * struct fb_ops { + * * open/release and usage marking + * struct module *owner; + * int (*fb_open)(struct fb_info *info, int user); + * int (*fb_release)(struct fb_info *info, int user); + * + * * For framebuffers with strange non linear layouts or that do not + * * work with normal memory mapped access + * ssize_t (*fb_read)(struct file *file, char __user *buf, size_t count, loff_t *ppos); + * ssize_t (*fb_write)(struct file *file, const char __user *buf, size_t count, loff_t *ppos); + * + * * checks var and eventually tweaks it to something supported, + * * DOES NOT MODIFY PAR * + * int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info); + * + * * set the video mode according to info->var * + * int (*fb_set_par)(struct fb_info *info); + * + * * set color register * + * int (*fb_setcolreg)(unsigned int regno, unsigned int red, unsigned int green, + * unsigned int blue, unsigned int transp, struct fb_info *info); + * + * * set color registers in batch * + * int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info); + * + * * blank display * + * int (*fb_blank)(int blank, struct fb_info *info); + * + * * pan display * + * int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info); + * + * *** The meat of the drawing engine *** + * * Draws a rectangle * + * void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); + * * Copy data from area to another * + * void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region); + * * Draws a image to the display * + * void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); + * + * * Draws cursor * + * int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor); + * + * * Rotates the display * + * void (*fb_rotate)(struct fb_info *info, int angle); + * + * * wait for blit idle, optional * + * int (*fb_sync)(struct fb_info *info); + * + * * perform fb specific ioctl (optional) * + * int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, + * unsigned long arg); + * + * * Handle 32bit compat ioctl (optional) * + * int (*fb_compat_ioctl)(struct fb_info *info, unsigned int cmd, + * unsigned long arg); + * + * * perform fb specific mmap * + * int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma); + * } ; + */ + + +/* ++roman: This structure abstracts from the underlying hardware (ST(e), + * TT, or Falcon. + * + * int (*detect)(void) + * This function should detect the current video mode settings and + * store them in atafb_predefined[0] for later reference by the + * user. Return the index+1 of an equivalent predefined mode or 0 + * if there is no such. + * + * int (*encode_fix)(struct fb_fix_screeninfo *fix, + * struct atafb_par *par) + * This function should fill in the 'fix' structure based on the + * values in the 'par' structure. + * !!! Obsolete, perhaps !!! + * + * int (*decode_var)(struct fb_var_screeninfo *var, + * struct atafb_par *par) + * Get the video params out of 'var'. If a value doesn't fit, round + * it up, if it's too big, return EINVAL. + * Round up in the following order: bits_per_pixel, xres, yres, + * xres_virtual, yres_virtual, xoffset, yoffset, grayscale, bitfields, + * horizontal timing, vertical timing. + * + * int (*encode_var)(struct fb_var_screeninfo *var, + * struct atafb_par *par); + * Fill the 'var' structure based on the values in 'par' and maybe + * other values read out of the hardware. + * + * void (*get_par)(struct atafb_par *par) + * Fill the hardware's 'par' structure. + * !!! Used only by detect() !!! + * + * void (*set_par)(struct atafb_par *par) + * Set the hardware according to 'par'. + * + * void (*set_screen_base)(void *s_base) + * Set the base address of the displayed frame buffer. Only called + * if yres_virtual > yres or xres_virtual > xres. + * + * int (*blank)(int blank_mode) + * Blank the screen if blank_mode != 0, else unblank. If blank == NULL then + * the caller blanks by setting the CLUT to all black. Return 0 if blanking + * succeeded, !=0 if un-/blanking failed due to e.g. a video mode which + * doesn't support it. Implements VESA suspend and powerdown modes on + * hardware that supports disabling hsync/vsync: + * blank_mode == 2: suspend vsync, 3:suspend hsync, 4: powerdown. + */ + +static struct fb_hwswitch { + int (*detect)(void); + int (*encode_fix)(struct fb_fix_screeninfo *fix, + struct atafb_par *par); + int (*decode_var)(struct fb_var_screeninfo *var, + struct atafb_par *par); + int (*encode_var)(struct fb_var_screeninfo *var, + struct atafb_par *par); + void (*get_par)(struct atafb_par *par); + void (*set_par)(struct atafb_par *par); + void (*set_screen_base)(void *s_base); + int (*blank)(int blank_mode); + int (*pan_display)(struct fb_var_screeninfo *var, + struct fb_info *info); +} *fbhw; + +static char *autodetect_names[] = { "autodetect", NULL }; +static char *stlow_names[] = { "stlow", NULL }; +static char *stmid_names[] = { "stmid", "default5", NULL }; +static char *sthigh_names[] = { "sthigh", "default4", NULL }; +static char *ttlow_names[] = { "ttlow", NULL }; +static char *ttmid_names[] = { "ttmid", "default1", NULL }; +static char *tthigh_names[] = { "tthigh", "default2", NULL }; +static char *vga2_names[] = { "vga2", NULL }; +static char *vga4_names[] = { "vga4", NULL }; +static char *vga16_names[] = { "vga16", "default3", NULL }; +static char *vga256_names[] = { "vga256", NULL }; +static char *falh2_names[] = { "falh2", NULL }; +static char *falh16_names[] = { "falh16", NULL }; + +static char **fb_var_names[] = { + autodetect_names, + stlow_names, + stmid_names, + sthigh_names, + ttlow_names, + ttmid_names, + tthigh_names, + vga2_names, + vga4_names, + vga16_names, + vga256_names, + falh2_names, + falh16_names, + NULL +}; + +static struct fb_var_screeninfo atafb_predefined[] = { + /* + * yres_virtual == 0 means use hw-scrolling if possible, else yres + */ + { /* autodetect */ + 0, 0, 0, 0, 0, 0, 0, 0, /* xres-grayscale */ + {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, /* red green blue tran*/ + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* st low */ + 320, 200, 320, 0, 0, 0, 4, 0, + {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* st mid */ + 640, 200, 640, 0, 0, 0, 2, 0, + {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* st high */ + 640, 400, 640, 0, 0, 0, 1, 0, + {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* tt low */ + 320, 480, 320, 0, 0, 0, 8, 0, + {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* tt mid */ + 640, 480, 640, 0, 0, 0, 4, 0, + {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* tt high */ + 1280, 960, 1280, 0, 0, 0, 1, 0, + {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* vga2 */ + 640, 480, 640, 0, 0, 0, 1, 0, + {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* vga4 */ + 640, 480, 640, 0, 0, 0, 2, 0, + {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* vga16 */ + 640, 480, 640, 0, 0, 0, 4, 0, + {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* vga256 */ + 640, 480, 640, 0, 0, 0, 8, 0, + {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* falh2 */ + 896, 608, 896, 0, 0, 0, 1, 0, + {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { /* falh16 */ + 896, 608, 896, 0, 0, 0, 4, 0, + {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, +}; + +static int num_atafb_predefined = ARRAY_SIZE(atafb_predefined); + +static struct fb_videomode atafb_modedb[] __initdata = { + /* + * Atari Video Modes + * + * If you change these, make sure to update DEFMODE_* as well! + */ + + /* + * ST/TT Video Modes + */ + + { + /* 320x200, 15 kHz, 60 Hz (ST low) */ + "st-low", 60, 320, 200, 32000, 32, 16, 31, 14, 96, 4, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x200, 15 kHz, 60 Hz (ST medium) */ + "st-mid", 60, 640, 200, 32000, 32, 16, 31, 14, 96, 4, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x400, 30.25 kHz, 63.5 Hz (ST high) */ + "st-high", 63, 640, 400, 32000, 128, 0, 40, 14, 128, 4, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 320x480, 15 kHz, 60 Hz (TT low) */ + "tt-low", 60, 320, 480, 31041, 120, 100, 8, 16, 140, 30, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x480, 29 kHz, 57 Hz (TT medium) */ + "tt-mid", 60, 640, 480, 31041, 120, 100, 8, 16, 140, 30, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 1280x960, 29 kHz, 60 Hz (TT high) */ + "tt-high", 57, 640, 960, 31041, 120, 100, 8, 16, 140, 30, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, + + /* + * VGA Video Modes + */ + + { + /* 640x480, 31 kHz, 60 Hz (VGA) */ + "vga", 63.5, 640, 480, 32000, 18, 42, 31, 11, 96, 3, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, { + /* 640x400, 31 kHz, 70 Hz (VGA) */ + "vga70", 70, 640, 400, 32000, 18, 42, 31, 11, 96, 3, + FB_SYNC_VERT_HIGH_ACT | FB_SYNC_COMP_HIGH_ACT, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, + + /* + * Falcon HiRes Video Modes + */ + + { + /* 896x608, 31 kHz, 60 Hz (Falcon High) */ + "falh", 60, 896, 608, 32000, 18, 42, 31, 1, 96,3, + 0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP + }, +}; + +#define NUM_TOTAL_MODES ARRAY_SIZE(atafb_modedb) + +static char *mode_option __initdata = NULL; + + /* default modes */ + +#define DEFMODE_TT 5 /* "tt-high" for TT */ +#define DEFMODE_F30 7 /* "vga70" for Falcon */ +#define DEFMODE_STE 2 /* "st-high" for ST/E */ +#define DEFMODE_EXT 6 /* "vga" for external */ + + +static int get_video_mode(char *vname) +{ + char ***name_list; + char **name; + int i; + + name_list = fb_var_names; + for (i = 0; i < num_atafb_predefined; i++) { + name = *name_list++; + if (!name || !*name) + break; + while (*name) { + if (!strcmp(vname, *name)) + return i + 1; + name++; + } + } + return 0; +} + + + +/* ------------------- TT specific functions ---------------------- */ + +#ifdef ATAFB_TT + +static int tt_encode_fix(struct fb_fix_screeninfo *fix, struct atafb_par *par) +{ + int mode; + + strcpy(fix->id, "Atari Builtin"); + fix->smem_start = (unsigned long)real_screen_base; + fix->smem_len = screen_len; + fix->type = FB_TYPE_INTERLEAVED_PLANES; + fix->type_aux = 2; + fix->visual = FB_VISUAL_PSEUDOCOLOR; + mode = par->hw.tt.mode & TT_SHIFTER_MODEMASK; + if (mode == TT_SHIFTER_TTHIGH || mode == TT_SHIFTER_STHIGH) { + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + if (mode == TT_SHIFTER_TTHIGH) + fix->visual = FB_VISUAL_MONO01; + } + fix->xpanstep = 0; + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->line_length = par->next_line; + fix->accel = FB_ACCEL_ATARIBLITT; + return 0; +} + +static int tt_decode_var(struct fb_var_screeninfo *var, struct atafb_par *par) +{ + int xres = var->xres; + int yres = var->yres; + int bpp = var->bits_per_pixel; + int linelen; + int yres_virtual = var->yres_virtual; + + if (mono_moni) { + if (bpp > 1 || xres > sttt_xres * 2 || yres > tt_yres * 2) + return -EINVAL; + par->hw.tt.mode = TT_SHIFTER_TTHIGH; + xres = sttt_xres * 2; + yres = tt_yres * 2; + bpp = 1; + } else { + if (bpp > 8 || xres > sttt_xres || yres > tt_yres) + return -EINVAL; + if (bpp > 4) { + if (xres > sttt_xres / 2 || yres > tt_yres) + return -EINVAL; + par->hw.tt.mode = TT_SHIFTER_TTLOW; + xres = sttt_xres / 2; + yres = tt_yres; + bpp = 8; + } else if (bpp > 2) { + if (xres > sttt_xres || yres > tt_yres) + return -EINVAL; + if (xres > sttt_xres / 2 || yres > st_yres / 2) { + par->hw.tt.mode = TT_SHIFTER_TTMID; + xres = sttt_xres; + yres = tt_yres; + bpp = 4; + } else { + par->hw.tt.mode = TT_SHIFTER_STLOW; + xres = sttt_xres / 2; + yres = st_yres / 2; + bpp = 4; + } + } else if (bpp > 1) { + if (xres > sttt_xres || yres > st_yres / 2) + return -EINVAL; + par->hw.tt.mode = TT_SHIFTER_STMID; + xres = sttt_xres; + yres = st_yres / 2; + bpp = 2; + } else if (var->xres > sttt_xres || var->yres > st_yres) { + return -EINVAL; + } else { + par->hw.tt.mode = TT_SHIFTER_STHIGH; + xres = sttt_xres; + yres = st_yres; + bpp = 1; + } + } + if (yres_virtual <= 0) + yres_virtual = 0; + else if (yres_virtual < yres) + yres_virtual = yres; + if (var->sync & FB_SYNC_EXT) + par->hw.tt.sync = 0; + else + par->hw.tt.sync = 1; + linelen = xres * bpp / 8; + if (yres_virtual * linelen > screen_len && screen_len) + return -EINVAL; + if (yres * linelen > screen_len && screen_len) + return -EINVAL; + if (var->yoffset + yres > yres_virtual && yres_virtual) + return -EINVAL; + par->yres_virtual = yres_virtual; + par->screen_base = screen_base + var->yoffset * linelen; + par->next_line = linelen; + return 0; +} + +static int tt_encode_var(struct fb_var_screeninfo *var, struct atafb_par *par) +{ + int linelen; + memset(var, 0, sizeof(struct fb_var_screeninfo)); + var->red.offset = 0; + var->red.length = 4; + var->red.msb_right = 0; + var->grayscale = 0; + + var->pixclock = 31041; + var->left_margin = 120; /* these may be incorrect */ + var->right_margin = 100; + var->upper_margin = 8; + var->lower_margin = 16; + var->hsync_len = 140; + var->vsync_len = 30; + + var->height = -1; + var->width = -1; + + if (par->hw.tt.sync & 1) + var->sync = 0; + else + var->sync = FB_SYNC_EXT; + + switch (par->hw.tt.mode & TT_SHIFTER_MODEMASK) { + case TT_SHIFTER_STLOW: + var->xres = sttt_xres / 2; + var->xres_virtual = sttt_xres_virtual / 2; + var->yres = st_yres / 2; + var->bits_per_pixel = 4; + break; + case TT_SHIFTER_STMID: + var->xres = sttt_xres; + var->xres_virtual = sttt_xres_virtual; + var->yres = st_yres / 2; + var->bits_per_pixel = 2; + break; + case TT_SHIFTER_STHIGH: + var->xres = sttt_xres; + var->xres_virtual = sttt_xres_virtual; + var->yres = st_yres; + var->bits_per_pixel = 1; + break; + case TT_SHIFTER_TTLOW: + var->xres = sttt_xres / 2; + var->xres_virtual = sttt_xres_virtual / 2; + var->yres = tt_yres; + var->bits_per_pixel = 8; + break; + case TT_SHIFTER_TTMID: + var->xres = sttt_xres; + var->xres_virtual = sttt_xres_virtual; + var->yres = tt_yres; + var->bits_per_pixel = 4; + break; + case TT_SHIFTER_TTHIGH: + var->red.length = 0; + var->xres = sttt_xres * 2; + var->xres_virtual = sttt_xres_virtual * 2; + var->yres = tt_yres * 2; + var->bits_per_pixel = 1; + break; + } + var->blue = var->green = var->red; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + linelen = var->xres_virtual * var->bits_per_pixel / 8; + if (!use_hwscroll) + var->yres_virtual = var->yres; + else if (screen_len) { + if (par->yres_virtual) + var->yres_virtual = par->yres_virtual; + else + /* yres_virtual == 0 means use maximum */ + var->yres_virtual = screen_len / linelen; + } else { + if (hwscroll < 0) + var->yres_virtual = 2 * var->yres; + else + var->yres_virtual = var->yres + hwscroll * 16; + } + var->xoffset = 0; + if (screen_base) + var->yoffset = (par->screen_base - screen_base) / linelen; + else + var->yoffset = 0; + var->nonstd = 0; + var->activate = 0; + var->vmode = FB_VMODE_NONINTERLACED; + return 0; +} + +static void tt_get_par(struct atafb_par *par) +{ + unsigned long addr; + par->hw.tt.mode = shifter_tt.tt_shiftmode; + par->hw.tt.sync = shifter.syncmode; + addr = ((shifter.bas_hi & 0xff) << 16) | + ((shifter.bas_md & 0xff) << 8) | + ((shifter.bas_lo & 0xff)); + par->screen_base = phys_to_virt(addr); +} + +static void tt_set_par(struct atafb_par *par) +{ + shifter_tt.tt_shiftmode = par->hw.tt.mode; + shifter.syncmode = par->hw.tt.sync; + /* only set screen_base if really necessary */ + if (current_par.screen_base != par->screen_base) + fbhw->set_screen_base(par->screen_base); +} + +static int tt_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + if ((shifter_tt.tt_shiftmode & TT_SHIFTER_MODEMASK) == TT_SHIFTER_STHIGH) + regno += 254; + if (regno > 255) + return 1; + tt_palette[regno] = (((red >> 12) << 8) | ((green >> 12) << 4) | + (blue >> 12)); + if ((shifter_tt.tt_shiftmode & TT_SHIFTER_MODEMASK) == + TT_SHIFTER_STHIGH && regno == 254) + tt_palette[0] = 0; + return 0; +} + +static int tt_detect(void) +{ + struct atafb_par par; + + /* Determine the connected monitor: The DMA sound must be + * disabled before reading the MFP GPIP, because the Sound + * Done Signal and the Monochrome Detect are XORed together! + * + * Even on a TT, we should look if there is a DMA sound. It was + * announced that the Eagle is TT compatible, but only the PCM is + * missing... + */ + if (ATARIHW_PRESENT(PCM_8BIT)) { + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + udelay(20); /* wait a while for things to settle down */ + } + mono_moni = (st_mfp.par_dt_reg & 0x80) == 0; + + tt_get_par(&par); + tt_encode_var(&atafb_predefined[0], &par); + + return 1; +} + +#endif /* ATAFB_TT */ + +/* ------------------- Falcon specific functions ---------------------- */ + +#ifdef ATAFB_FALCON + +static int mon_type; /* Falcon connected monitor */ +static int f030_bus_width; /* Falcon ram bus width (for vid_control) */ +#define F_MON_SM 0 +#define F_MON_SC 1 +#define F_MON_VGA 2 +#define F_MON_TV 3 + +static struct pixel_clock { + unsigned long f; /* f/[Hz] */ + unsigned long t; /* t/[ps] (=1/f) */ + int right, hsync, left; /* standard timing in clock cycles, not pixel */ + /* hsync initialized in falcon_detect() */ + int sync_mask; /* or-mask for hw.falcon.sync to set this clock */ + int control_mask; /* ditto, for hw.falcon.vid_control */ +} f25 = { + 25175000, 39721, 18, 0, 42, 0x0, VCO_CLOCK25 +}, f32 = { + 32000000, 31250, 18, 0, 42, 0x0, 0 +}, fext = { + 0, 0, 18, 0, 42, 0x1, 0 +}; + +/* VIDEL-prescale values [mon_type][pixel_length from VCO] */ +static int vdl_prescale[4][3] = { + { 4,2,1 }, { 4,2,1 }, { 4,2,2 }, { 4,2,1 } +}; + +/* Default hsync timing [mon_type] in picoseconds */ +static long h_syncs[4] = { 3000000, 4875000, 4000000, 4875000 }; + +static inline int hxx_prescale(struct falcon_hw *hw) +{ + return hw->ste_mode ? 16 + : vdl_prescale[mon_type][hw->vid_mode >> 2 & 0x3]; +} + +static int falcon_encode_fix(struct fb_fix_screeninfo *fix, + struct atafb_par *par) +{ + strcpy(fix->id, "Atari Builtin"); + fix->smem_start = (unsigned long)real_screen_base; + fix->smem_len = screen_len; + fix->type = FB_TYPE_INTERLEAVED_PLANES; + fix->type_aux = 2; + fix->visual = FB_VISUAL_PSEUDOCOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + fix->ywrapstep = 0; + if (par->hw.falcon.mono) { + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + /* no smooth scrolling with longword aligned video mem */ + fix->xpanstep = 32; + } else if (par->hw.falcon.f_shift & 0x100) { + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + /* Is this ok or should it be DIRECTCOLOR? */ + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 2; + } + fix->line_length = par->next_line; + fix->accel = FB_ACCEL_ATARIBLITT; + return 0; +} + +static int falcon_decode_var(struct fb_var_screeninfo *var, + struct atafb_par *par) +{ + int bpp = var->bits_per_pixel; + int xres = var->xres; + int yres = var->yres; + int xres_virtual = var->xres_virtual; + int yres_virtual = var->yres_virtual; + int left_margin, right_margin, hsync_len; + int upper_margin, lower_margin, vsync_len; + int linelen; + int interlace = 0, doubleline = 0; + struct pixel_clock *pclock; + int plen; /* width of pixel in clock cycles */ + int xstretch; + int prescale; + int longoffset = 0; + int hfreq, vfreq; + int hdb_off, hde_off, base_off; + int gstart, gend1, gend2, align; + +/* + Get the video params out of 'var'. If a value doesn't fit, round + it up, if it's too big, return EINVAL. + Round up in the following order: bits_per_pixel, xres, yres, + xres_virtual, yres_virtual, xoffset, yoffset, grayscale, bitfields, + horizontal timing, vertical timing. + + There is a maximum of screen resolution determined by pixelclock + and minimum frame rate -- (X+hmarg.)*(Y+vmarg.)*vfmin <= pixelclock. + In interlace mode this is " * " *vfmin <= pixelclock. + Additional constraints: hfreq. + Frequency range for multisync monitors is given via command line. + For TV and SM124 both frequencies are fixed. + + X % 16 == 0 to fit 8x?? font (except 1 bitplane modes must use X%32 == 0) + Y % 16 == 0 to fit 8x16 font + Y % 8 == 0 if Y<400 + + Currently interlace and doubleline mode in var are ignored. + On SM124 and TV only the standard resolutions can be used. +*/ + + /* Reject uninitialized mode */ + if (!xres || !yres || !bpp) + return -EINVAL; + + if (mon_type == F_MON_SM && bpp != 1) + return -EINVAL; + + if (bpp <= 1) { + bpp = 1; + par->hw.falcon.f_shift = 0x400; + par->hw.falcon.st_shift = 0x200; + } else if (bpp <= 2) { + bpp = 2; + par->hw.falcon.f_shift = 0x000; + par->hw.falcon.st_shift = 0x100; + } else if (bpp <= 4) { + bpp = 4; + par->hw.falcon.f_shift = 0x000; + par->hw.falcon.st_shift = 0x000; + } else if (bpp <= 8) { + bpp = 8; + par->hw.falcon.f_shift = 0x010; + } else if (bpp <= 16) { + bpp = 16; /* packed pixel mode */ + par->hw.falcon.f_shift = 0x100; /* hicolor, no overlay */ + } else + return -EINVAL; + par->hw.falcon.bpp = bpp; + + if (mon_type == F_MON_SM || DontCalcRes) { + /* Skip all calculations. VGA/TV/SC1224 only supported. */ + struct fb_var_screeninfo *myvar = &atafb_predefined[0]; + + if (bpp > myvar->bits_per_pixel || + var->xres > myvar->xres || + var->yres > myvar->yres) + return -EINVAL; + fbhw->get_par(par); /* Current par will be new par */ + goto set_screen_base; /* Don't forget this */ + } + + /* Only some fixed resolutions < 640x400 */ + if (xres <= 320) + xres = 320; + else if (xres <= 640 && bpp != 16) + xres = 640; + if (yres <= 200) + yres = 200; + else if (yres <= 240) + yres = 240; + else if (yres <= 400) + yres = 400; + + /* 2 planes must use STE compatibility mode */ + par->hw.falcon.ste_mode = bpp == 2; + par->hw.falcon.mono = bpp == 1; + + /* Total and visible scanline length must be a multiple of one longword, + * this and the console fontwidth yields the alignment for xres and + * xres_virtual. + * TODO: this way "odd" fontheights are not supported + * + * Special case in STE mode: blank and graphic positions don't align, + * avoid trash at right margin + */ + if (par->hw.falcon.ste_mode) + xres = (xres + 63) & ~63; + else if (bpp == 1) + xres = (xres + 31) & ~31; + else + xres = (xres + 15) & ~15; + if (yres >= 400) + yres = (yres + 15) & ~15; + else + yres = (yres + 7) & ~7; + + if (xres_virtual < xres) + xres_virtual = xres; + else if (bpp == 1) + xres_virtual = (xres_virtual + 31) & ~31; + else + xres_virtual = (xres_virtual + 15) & ~15; + + if (yres_virtual <= 0) + yres_virtual = 0; + else if (yres_virtual < yres) + yres_virtual = yres; + + /* backward bug-compatibility */ + if (var->pixclock > 1) + var->pixclock -= 1; + + par->hw.falcon.line_width = bpp * xres / 16; + par->hw.falcon.line_offset = bpp * (xres_virtual - xres) / 16; + + /* single or double pixel width */ + xstretch = (xres < 640) ? 2 : 1; + +#if 0 /* SM124 supports only 640x400, this is rejected above */ + if (mon_type == F_MON_SM) { + if (xres != 640 && yres != 400) + return -EINVAL; + plen = 1; + pclock = &f32; + /* SM124-mode is special */ + par->hw.falcon.ste_mode = 1; + par->hw.falcon.f_shift = 0x000; + par->hw.falcon.st_shift = 0x200; + left_margin = hsync_len = 128 / plen; + right_margin = 0; + /* TODO set all margins */ + } else +#endif + if (mon_type == F_MON_SC || mon_type == F_MON_TV) { + plen = 2 * xstretch; + if (var->pixclock > f32.t * plen) + return -EINVAL; + pclock = &f32; + if (yres > 240) + interlace = 1; + if (var->pixclock == 0) { + /* set some minimal margins which center the screen */ + left_margin = 32; + right_margin = 18; + hsync_len = pclock->hsync / plen; + upper_margin = 31; + lower_margin = 14; + vsync_len = interlace ? 3 : 4; + } else { + left_margin = var->left_margin; + right_margin = var->right_margin; + hsync_len = var->hsync_len; + upper_margin = var->upper_margin; + lower_margin = var->lower_margin; + vsync_len = var->vsync_len; + if (var->vmode & FB_VMODE_INTERLACED) { + upper_margin = (upper_margin + 1) / 2; + lower_margin = (lower_margin + 1) / 2; + vsync_len = (vsync_len + 1) / 2; + } else if (var->vmode & FB_VMODE_DOUBLE) { + upper_margin *= 2; + lower_margin *= 2; + vsync_len *= 2; + } + } + } else { /* F_MON_VGA */ + if (bpp == 16) + xstretch = 2; /* Double pixel width only for hicolor */ + /* Default values are used for vert./hor. timing if no pixelclock given. */ + if (var->pixclock == 0) { + int linesize; + + /* Choose master pixelclock depending on hor. timing */ + plen = 1 * xstretch; + if ((plen * xres + f25.right + f25.hsync + f25.left) * + fb_info.monspecs.hfmin < f25.f) + pclock = &f25; + else if ((plen * xres + f32.right + f32.hsync + + f32.left) * fb_info.monspecs.hfmin < f32.f) + pclock = &f32; + else if ((plen * xres + fext.right + fext.hsync + + fext.left) * fb_info.monspecs.hfmin < fext.f && + fext.f) + pclock = &fext; + else + return -EINVAL; + + left_margin = pclock->left / plen; + right_margin = pclock->right / plen; + hsync_len = pclock->hsync / plen; + linesize = left_margin + xres + right_margin + hsync_len; + upper_margin = 31; + lower_margin = 11; + vsync_len = 3; + } else { + /* Choose largest pixelclock <= wanted clock */ + int i; + unsigned long pcl = ULONG_MAX; + pclock = 0; + for (i = 1; i <= 4; i *= 2) { + if (f25.t * i >= var->pixclock && + f25.t * i < pcl) { + pcl = f25.t * i; + pclock = &f25; + } + if (f32.t * i >= var->pixclock && + f32.t * i < pcl) { + pcl = f32.t * i; + pclock = &f32; + } + if (fext.t && fext.t * i >= var->pixclock && + fext.t * i < pcl) { + pcl = fext.t * i; + pclock = &fext; + } + } + if (!pclock) + return -EINVAL; + plen = pcl / pclock->t; + + left_margin = var->left_margin; + right_margin = var->right_margin; + hsync_len = var->hsync_len; + upper_margin = var->upper_margin; + lower_margin = var->lower_margin; + vsync_len = var->vsync_len; + /* Internal unit is [single lines per (half-)frame] */ + if (var->vmode & FB_VMODE_INTERLACED) { + /* # lines in half frame */ + /* External unit is [lines per full frame] */ + upper_margin = (upper_margin + 1) / 2; + lower_margin = (lower_margin + 1) / 2; + vsync_len = (vsync_len + 1) / 2; + } else if (var->vmode & FB_VMODE_DOUBLE) { + /* External unit is [double lines per frame] */ + upper_margin *= 2; + lower_margin *= 2; + vsync_len *= 2; + } + } + if (pclock == &fext) + longoffset = 1; /* VIDEL doesn't synchronize on short offset */ + } + /* Is video bus bandwidth (32MB/s) too low for this resolution? */ + /* this is definitely wrong if bus clock != 32MHz */ + if (pclock->f / plen / 8 * bpp > 32000000L) + return -EINVAL; + + if (vsync_len < 1) + vsync_len = 1; + + /* include sync lengths in right/lower margin for all calculations */ + right_margin += hsync_len; + lower_margin += vsync_len; + + /* ! In all calculations of margins we use # of lines in half frame + * (which is a full frame in non-interlace mode), so we can switch + * between interlace and non-interlace without messing around + * with these. + */ +again: + /* Set base_offset 128 and video bus width */ + par->hw.falcon.vid_control = mon_type | f030_bus_width; + if (!longoffset) + par->hw.falcon.vid_control |= VCO_SHORTOFFS; /* base_offset 64 */ + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + par->hw.falcon.vid_control |= VCO_HSYPOS; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + par->hw.falcon.vid_control |= VCO_VSYPOS; + /* Pixelclock */ + par->hw.falcon.vid_control |= pclock->control_mask; + /* External or internal clock */ + par->hw.falcon.sync = pclock->sync_mask | 0x2; + /* Pixellength and prescale */ + par->hw.falcon.vid_mode = (2 / plen) << 2; + if (doubleline) + par->hw.falcon.vid_mode |= VMO_DOUBLE; + if (interlace) + par->hw.falcon.vid_mode |= VMO_INTER; + + /********************* + * Horizontal timing: unit = [master clock cycles] + * unit of hxx-registers: [master clock cycles * prescale] + * Hxx-registers are 9 bit wide + * + * 1 line = ((hht + 2) * 2 * prescale) clock cycles + * + * graphic output = hdb & 0x200 ? + * ((hht + 2) * 2 - hdb + hde) * prescale - hdboff + hdeoff: + * (hht + 2 - hdb + hde) * prescale - hdboff + hdeoff + * (this must be a multiple of plen*128/bpp, on VGA pixels + * to the right may be cut off with a bigger right margin) + * + * start of graphics relative to start of 1st halfline = hdb & 0x200 ? + * (hdb - hht - 2) * prescale + hdboff : + * hdb * prescale + hdboff + * + * end of graphics relative to start of 1st halfline = + * (hde + hht + 2) * prescale + hdeoff + *********************/ + /* Calculate VIDEL registers */ +{ + prescale = hxx_prescale(&par->hw.falcon); + base_off = par->hw.falcon.vid_control & VCO_SHORTOFFS ? 64 : 128; + + /* Offsets depend on video mode */ + /* Offsets are in clock cycles, divide by prescale to + * calculate hd[be]-registers + */ + if (par->hw.falcon.f_shift & 0x100) { + align = 1; + hde_off = 0; + hdb_off = (base_off + 16 * plen) + prescale; + } else { + align = 128 / bpp; + hde_off = ((128 / bpp + 2) * plen); + if (par->hw.falcon.ste_mode) + hdb_off = (64 + base_off + (128 / bpp + 2) * plen) + prescale; + else + hdb_off = (base_off + (128 / bpp + 18) * plen) + prescale; + } + + gstart = (prescale / 2 + plen * left_margin) / prescale; + /* gend1 is for hde (gend-gstart multiple of align), shifter's xres */ + gend1 = gstart + roundup(xres, align) * plen / prescale; + /* gend2 is for hbb, visible xres (rest to gend1 is cut off by hblank) */ + gend2 = gstart + xres * plen / prescale; + par->HHT = plen * (left_margin + xres + right_margin) / + (2 * prescale) - 2; +/* par->HHT = (gend2 + plen * right_margin / prescale) / 2 - 2;*/ + + par->HDB = gstart - hdb_off / prescale; + par->HBE = gstart; + if (par->HDB < 0) + par->HDB += par->HHT + 2 + 0x200; + par->HDE = gend1 - par->HHT - 2 - hde_off / prescale; + par->HBB = gend2 - par->HHT - 2; +#if 0 + /* One more Videl constraint: data fetch of two lines must not overlap */ + if ((par->HDB & 0x200) && (par->HDB & ~0x200) - par->HDE <= 5) { + /* if this happens increase margins, decrease hfreq. */ + } +#endif + if (hde_off % prescale) + par->HBB++; /* compensate for non matching hde and hbb */ + par->HSS = par->HHT + 2 - plen * hsync_len / prescale; + if (par->HSS < par->HBB) + par->HSS = par->HBB; +} + + /* check hor. frequency */ + hfreq = pclock->f / ((par->HHT + 2) * prescale * 2); + if (hfreq > fb_info.monspecs.hfmax && mon_type != F_MON_VGA) { + /* ++guenther: ^^^^^^^^^^^^^^^^^^^ can't remember why I did this */ + /* Too high -> enlarge margin */ + left_margin += 1; + right_margin += 1; + goto again; + } + if (hfreq > fb_info.monspecs.hfmax || hfreq < fb_info.monspecs.hfmin) + return -EINVAL; + + /* Vxx-registers */ + /* All Vxx must be odd in non-interlace, since frame starts in the middle + * of the first displayed line! + * One frame consists of VFT+1 half lines. VFT+1 must be even in + * non-interlace, odd in interlace mode for synchronisation. + * Vxx-registers are 11 bit wide + */ + par->VBE = (upper_margin * 2 + 1); /* must begin on odd halfline */ + par->VDB = par->VBE; + par->VDE = yres; + if (!interlace) + par->VDE <<= 1; + if (doubleline) + par->VDE <<= 1; /* VDE now half lines per (half-)frame */ + par->VDE += par->VDB; + par->VBB = par->VDE; + par->VFT = par->VBB + (lower_margin * 2 - 1) - 1; + par->VSS = par->VFT + 1 - (vsync_len * 2 - 1); + /* vbb,vss,vft must be even in interlace mode */ + if (interlace) { + par->VBB++; + par->VSS++; + par->VFT++; + } + + /* V-frequency check, hope I didn't create any loop here. */ + /* Interlace and doubleline are mutually exclusive. */ + vfreq = (hfreq * 2) / (par->VFT + 1); + if (vfreq > fb_info.monspecs.vfmax && !doubleline && !interlace) { + /* Too high -> try again with doubleline */ + doubleline = 1; + goto again; + } else if (vfreq < fb_info.monspecs.vfmin && !interlace && !doubleline) { + /* Too low -> try again with interlace */ + interlace = 1; + goto again; + } else if (vfreq < fb_info.monspecs.vfmin && doubleline) { + /* Doubleline too low -> clear doubleline and enlarge margins */ + int lines; + doubleline = 0; + for (lines = 0; + (hfreq * 2) / (par->VFT + 1 + 4 * lines - 2 * yres) > + fb_info.monspecs.vfmax; + lines++) + ; + upper_margin += lines; + lower_margin += lines; + goto again; + } else if (vfreq > fb_info.monspecs.vfmax && doubleline) { + /* Doubleline too high -> enlarge margins */ + int lines; + for (lines = 0; + (hfreq * 2) / (par->VFT + 1 + 4 * lines) > + fb_info.monspecs.vfmax; + lines += 2) + ; + upper_margin += lines; + lower_margin += lines; + goto again; + } else if (vfreq > fb_info.monspecs.vfmax && interlace) { + /* Interlace, too high -> enlarge margins */ + int lines; + for (lines = 0; + (hfreq * 2) / (par->VFT + 1 + 4 * lines) > + fb_info.monspecs.vfmax; + lines++) + ; + upper_margin += lines; + lower_margin += lines; + goto again; + } else if (vfreq < fb_info.monspecs.vfmin || + vfreq > fb_info.monspecs.vfmax) + return -EINVAL; + +set_screen_base: + linelen = xres_virtual * bpp / 8; + if (yres_virtual * linelen > screen_len && screen_len) + return -EINVAL; + if (yres * linelen > screen_len && screen_len) + return -EINVAL; + if (var->yoffset + yres > yres_virtual && yres_virtual) + return -EINVAL; + par->yres_virtual = yres_virtual; + par->screen_base = screen_base + var->yoffset * linelen; + par->hw.falcon.xoffset = 0; + + par->next_line = linelen; + + return 0; +} + +static int falcon_encode_var(struct fb_var_screeninfo *var, + struct atafb_par *par) +{ +/* !!! only for VGA !!! */ + int linelen; + int prescale, plen; + int hdb_off, hde_off, base_off; + struct falcon_hw *hw = &par->hw.falcon; + + memset(var, 0, sizeof(struct fb_var_screeninfo)); + /* possible frequencies: 25.175 or 32MHz */ + var->pixclock = hw->sync & 0x1 ? fext.t : + hw->vid_control & VCO_CLOCK25 ? f25.t : f32.t; + + var->height = -1; + var->width = -1; + + var->sync = 0; + if (hw->vid_control & VCO_HSYPOS) + var->sync |= FB_SYNC_HOR_HIGH_ACT; + if (hw->vid_control & VCO_VSYPOS) + var->sync |= FB_SYNC_VERT_HIGH_ACT; + + var->vmode = FB_VMODE_NONINTERLACED; + if (hw->vid_mode & VMO_INTER) + var->vmode |= FB_VMODE_INTERLACED; + if (hw->vid_mode & VMO_DOUBLE) + var->vmode |= FB_VMODE_DOUBLE; + + /* visible y resolution: + * Graphics display starts at line VDB and ends at line + * VDE. If interlace mode off unit of VC-registers is + * half lines, else lines. + */ + var->yres = hw->vde - hw->vdb; + if (!(var->vmode & FB_VMODE_INTERLACED)) + var->yres >>= 1; + if (var->vmode & FB_VMODE_DOUBLE) + var->yres >>= 1; + + /* + * to get bpp, we must examine f_shift and st_shift. + * f_shift is valid if any of bits no. 10, 8 or 4 + * is set. Priority in f_shift is: 10 ">" 8 ">" 4, i.e. + * if bit 10 set then bit 8 and bit 4 don't care... + * If all these bits are 0 get display depth from st_shift + * (as for ST and STE) + */ + if (hw->f_shift & 0x400) /* 2 colors */ + var->bits_per_pixel = 1; + else if (hw->f_shift & 0x100) /* hicolor */ + var->bits_per_pixel = 16; + else if (hw->f_shift & 0x010) /* 8 bitplanes */ + var->bits_per_pixel = 8; + else if (hw->st_shift == 0) + var->bits_per_pixel = 4; + else if (hw->st_shift == 0x100) + var->bits_per_pixel = 2; + else /* if (hw->st_shift == 0x200) */ + var->bits_per_pixel = 1; + + var->xres = hw->line_width * 16 / var->bits_per_pixel; + var->xres_virtual = var->xres + hw->line_offset * 16 / var->bits_per_pixel; + if (hw->xoffset) + var->xres_virtual += 16; + + if (var->bits_per_pixel == 16) { + var->red.offset = 11; + var->red.length = 5; + var->red.msb_right = 0; + var->green.offset = 5; + var->green.length = 6; + var->green.msb_right = 0; + var->blue.offset = 0; + var->blue.length = 5; + var->blue.msb_right = 0; + } else { + var->red.offset = 0; + var->red.length = hw->ste_mode ? 4 : 6; + if (var->red.length > var->bits_per_pixel) + var->red.length = var->bits_per_pixel; + var->red.msb_right = 0; + var->grayscale = 0; + var->blue = var->green = var->red; + } + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + + linelen = var->xres_virtual * var->bits_per_pixel / 8; + if (screen_len) { + if (par->yres_virtual) + var->yres_virtual = par->yres_virtual; + else + /* yres_virtual == 0 means use maximum */ + var->yres_virtual = screen_len / linelen; + } else { + if (hwscroll < 0) + var->yres_virtual = 2 * var->yres; + else + var->yres_virtual = var->yres + hwscroll * 16; + } + var->xoffset = 0; /* TODO change this */ + + /* hdX-offsets */ + prescale = hxx_prescale(hw); + plen = 4 >> (hw->vid_mode >> 2 & 0x3); + base_off = hw->vid_control & VCO_SHORTOFFS ? 64 : 128; + if (hw->f_shift & 0x100) { + hde_off = 0; + hdb_off = (base_off + 16 * plen) + prescale; + } else { + hde_off = ((128 / var->bits_per_pixel + 2) * plen); + if (hw->ste_mode) + hdb_off = (64 + base_off + (128 / var->bits_per_pixel + 2) * plen) + + prescale; + else + hdb_off = (base_off + (128 / var->bits_per_pixel + 18) * plen) + + prescale; + } + + /* Right margin includes hsync */ + var->left_margin = hdb_off + prescale * ((hw->hdb & 0x1ff) - + (hw->hdb & 0x200 ? 2 + hw->hht : 0)); + if (hw->ste_mode || mon_type != F_MON_VGA) + var->right_margin = prescale * (hw->hht + 2 - hw->hde) - hde_off; + else + /* can't use this in ste_mode, because hbb is +1 off */ + var->right_margin = prescale * (hw->hht + 2 - hw->hbb); + var->hsync_len = prescale * (hw->hht + 2 - hw->hss); + + /* Lower margin includes vsync */ + var->upper_margin = hw->vdb / 2; /* round down to full lines */ + var->lower_margin = (hw->vft + 1 - hw->vde + 1) / 2; /* round up */ + var->vsync_len = (hw->vft + 1 - hw->vss + 1) / 2; /* round up */ + if (var->vmode & FB_VMODE_INTERLACED) { + var->upper_margin *= 2; + var->lower_margin *= 2; + var->vsync_len *= 2; + } else if (var->vmode & FB_VMODE_DOUBLE) { + var->upper_margin = (var->upper_margin + 1) / 2; + var->lower_margin = (var->lower_margin + 1) / 2; + var->vsync_len = (var->vsync_len + 1) / 2; + } + + var->pixclock *= plen; + var->left_margin /= plen; + var->right_margin /= plen; + var->hsync_len /= plen; + + var->right_margin -= var->hsync_len; + var->lower_margin -= var->vsync_len; + + if (screen_base) + var->yoffset = (par->screen_base - screen_base) / linelen; + else + var->yoffset = 0; + var->nonstd = 0; /* what is this for? */ + var->activate = 0; + return 0; +} + +static int f_change_mode; +static struct falcon_hw f_new_mode; +static int f_pan_display; + +static void falcon_get_par(struct atafb_par *par) +{ + unsigned long addr; + struct falcon_hw *hw = &par->hw.falcon; + + hw->line_width = shifter_f030.scn_width; + hw->line_offset = shifter_f030.off_next; + hw->st_shift = videl.st_shift & 0x300; + hw->f_shift = videl.f_shift; + hw->vid_control = videl.control; + hw->vid_mode = videl.mode; + hw->sync = shifter.syncmode & 0x1; + hw->xoffset = videl.xoffset & 0xf; + hw->hht = videl.hht; + hw->hbb = videl.hbb; + hw->hbe = videl.hbe; + hw->hdb = videl.hdb; + hw->hde = videl.hde; + hw->hss = videl.hss; + hw->vft = videl.vft; + hw->vbb = videl.vbb; + hw->vbe = videl.vbe; + hw->vdb = videl.vdb; + hw->vde = videl.vde; + hw->vss = videl.vss; + + addr = (shifter.bas_hi & 0xff) << 16 | + (shifter.bas_md & 0xff) << 8 | + (shifter.bas_lo & 0xff); + par->screen_base = phys_to_virt(addr); + + /* derived parameters */ + hw->ste_mode = (hw->f_shift & 0x510) == 0 && hw->st_shift == 0x100; + hw->mono = (hw->f_shift & 0x400) || + ((hw->f_shift & 0x510) == 0 && hw->st_shift == 0x200); +} + +static void falcon_set_par(struct atafb_par *par) +{ + f_change_mode = 0; + + /* only set screen_base if really necessary */ + if (current_par.screen_base != par->screen_base) + fbhw->set_screen_base(par->screen_base); + + /* Don't touch any other registers if we keep the default resolution */ + if (DontCalcRes) + return; + + /* Tell vbl-handler to change video mode. + * We change modes only on next VBL, to avoid desynchronisation + * (a shift to the right and wrap around by a random number of pixels + * in all monochrome modes). + * This seems to work on my Falcon. + */ + f_new_mode = par->hw.falcon; + f_change_mode = 1; +} + +static irqreturn_t falcon_vbl_switcher(int irq, void *dummy) +{ + struct falcon_hw *hw = &f_new_mode; + + if (f_change_mode) { + f_change_mode = 0; + + if (hw->sync & 0x1) { + /* Enable external pixelclock. This code only for ScreenWonder */ + *(volatile unsigned short *)0xffff9202 = 0xffbf; + } else { + /* Turn off external clocks. Read sets all output bits to 1. */ + *(volatile unsigned short *)0xffff9202; + } + shifter.syncmode = hw->sync; + + videl.hht = hw->hht; + videl.hbb = hw->hbb; + videl.hbe = hw->hbe; + videl.hdb = hw->hdb; + videl.hde = hw->hde; + videl.hss = hw->hss; + videl.vft = hw->vft; + videl.vbb = hw->vbb; + videl.vbe = hw->vbe; + videl.vdb = hw->vdb; + videl.vde = hw->vde; + videl.vss = hw->vss; + + videl.f_shift = 0; /* write enables Falcon palette, 0: 4 planes */ + if (hw->ste_mode) { + videl.st_shift = hw->st_shift; /* write enables STE palette */ + } else { + /* IMPORTANT: + * set st_shift 0, so we can tell the screen-depth if f_shift == 0. + * Writing 0 to f_shift enables 4 plane Falcon mode but + * doesn't set st_shift. st_shift != 0 (!= 4planes) is impossible + * with Falcon palette. + */ + videl.st_shift = 0; + /* now back to Falcon palette mode */ + videl.f_shift = hw->f_shift; + } + /* writing to st_shift changed scn_width and vid_mode */ + videl.xoffset = hw->xoffset; + shifter_f030.scn_width = hw->line_width; + shifter_f030.off_next = hw->line_offset; + videl.control = hw->vid_control; + videl.mode = hw->vid_mode; + } + if (f_pan_display) { + f_pan_display = 0; + videl.xoffset = current_par.hw.falcon.xoffset; + shifter_f030.off_next = current_par.hw.falcon.line_offset; + } + return IRQ_HANDLED; +} + +static int falcon_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct atafb_par *par = (struct atafb_par *)info->par; + + int xoffset; + int bpp = info->var.bits_per_pixel; + + if (bpp == 1) + var->xoffset = up(var->xoffset, 32); + if (bpp != 16) + par->hw.falcon.xoffset = var->xoffset & 15; + else { + par->hw.falcon.xoffset = 0; + var->xoffset = up(var->xoffset, 2); + } + par->hw.falcon.line_offset = bpp * + (info->var.xres_virtual - info->var.xres) / 16; + if (par->hw.falcon.xoffset) + par->hw.falcon.line_offset -= bpp; + xoffset = var->xoffset - par->hw.falcon.xoffset; + + par->screen_base = screen_base + + (var->yoffset * info->var.xres_virtual + xoffset) * bpp / 8; + if (fbhw->set_screen_base) + fbhw->set_screen_base(par->screen_base); + else + return -EINVAL; /* shouldn't happen */ + f_pan_display = 1; + return 0; +} + +static int falcon_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + if (regno > 255) + return 1; + f030_col[regno] = (((red & 0xfc00) << 16) | + ((green & 0xfc00) << 8) | + ((blue & 0xfc00) >> 8)); + if (regno < 16) { + shifter_tt.color_reg[regno] = + (((red & 0xe000) >> 13) | ((red & 0x1000) >> 12) << 8) | + (((green & 0xe000) >> 13) | ((green & 0x1000) >> 12) << 4) | + ((blue & 0xe000) >> 13) | ((blue & 0x1000) >> 12); + ((u32 *)info->pseudo_palette)[regno] = ((red & 0xf800) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11)); + } + return 0; +} + +static int falcon_blank(int blank_mode) +{ + /* ++guenther: we can switch off graphics by changing VDB and VDE, + * so VIDEL doesn't hog the bus while saving. + * (this may affect usleep()). + */ + int vdb, vss, hbe, hss; + + if (mon_type == F_MON_SM) /* this doesn't work on SM124 */ + return 1; + + vdb = current_par.VDB; + vss = current_par.VSS; + hbe = current_par.HBE; + hss = current_par.HSS; + + if (blank_mode >= 1) { + /* disable graphics output (this speeds up the CPU) ... */ + vdb = current_par.VFT + 1; + /* ... and blank all lines */ + hbe = current_par.HHT + 2; + } + /* use VESA suspend modes on VGA monitors */ + if (mon_type == F_MON_VGA) { + if (blank_mode == 2 || blank_mode == 4) + vss = current_par.VFT + 1; + if (blank_mode == 3 || blank_mode == 4) + hss = current_par.HHT + 2; + } + + videl.vdb = vdb; + videl.vss = vss; + videl.hbe = hbe; + videl.hss = hss; + + return 0; +} + +static int falcon_detect(void) +{ + struct atafb_par par; + unsigned char fhw; + + /* Determine connected monitor and set monitor parameters */ + fhw = *(unsigned char *)0xffff8006; + mon_type = fhw >> 6 & 0x3; + /* bit 1 of fhw: 1=32 bit ram bus, 0=16 bit */ + f030_bus_width = fhw << 6 & 0x80; + switch (mon_type) { + case F_MON_SM: + fb_info.monspecs.vfmin = 70; + fb_info.monspecs.vfmax = 72; + fb_info.monspecs.hfmin = 35713; + fb_info.monspecs.hfmax = 35715; + break; + case F_MON_SC: + case F_MON_TV: + /* PAL...NTSC */ + fb_info.monspecs.vfmin = 49; /* not 50, since TOS defaults to 49.9x Hz */ + fb_info.monspecs.vfmax = 60; + fb_info.monspecs.hfmin = 15620; + fb_info.monspecs.hfmax = 15755; + break; + } + /* initialize hsync-len */ + f25.hsync = h_syncs[mon_type] / f25.t; + f32.hsync = h_syncs[mon_type] / f32.t; + if (fext.t) + fext.hsync = h_syncs[mon_type] / fext.t; + + falcon_get_par(&par); + falcon_encode_var(&atafb_predefined[0], &par); + + /* Detected mode is always the "autodetect" slot */ + return 1; +} + +#endif /* ATAFB_FALCON */ + +/* ------------------- ST(E) specific functions ---------------------- */ + +#ifdef ATAFB_STE + +static int stste_encode_fix(struct fb_fix_screeninfo *fix, + struct atafb_par *par) +{ + int mode; + + strcpy(fix->id, "Atari Builtin"); + fix->smem_start = (unsigned long)real_screen_base; + fix->smem_len = screen_len; + fix->type = FB_TYPE_INTERLEAVED_PLANES; + fix->type_aux = 2; + fix->visual = FB_VISUAL_PSEUDOCOLOR; + mode = par->hw.st.mode & 3; + if (mode == ST_HIGH) { + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + fix->visual = FB_VISUAL_MONO10; + } + if (ATARIHW_PRESENT(EXTD_SHIFTER)) { + fix->xpanstep = 16; + fix->ypanstep = 1; + } else { + fix->xpanstep = 0; + fix->ypanstep = 0; + } + fix->ywrapstep = 0; + fix->line_length = par->next_line; + fix->accel = FB_ACCEL_ATARIBLITT; + return 0; +} + +static int stste_decode_var(struct fb_var_screeninfo *var, + struct atafb_par *par) +{ + int xres = var->xres; + int yres = var->yres; + int bpp = var->bits_per_pixel; + int linelen; + int yres_virtual = var->yres_virtual; + + if (mono_moni) { + if (bpp > 1 || xres > sttt_xres || yres > st_yres) + return -EINVAL; + par->hw.st.mode = ST_HIGH; + xres = sttt_xres; + yres = st_yres; + bpp = 1; + } else { + if (bpp > 4 || xres > sttt_xres || yres > st_yres) + return -EINVAL; + if (bpp > 2) { + if (xres > sttt_xres / 2 || yres > st_yres / 2) + return -EINVAL; + par->hw.st.mode = ST_LOW; + xres = sttt_xres / 2; + yres = st_yres / 2; + bpp = 4; + } else if (bpp > 1) { + if (xres > sttt_xres || yres > st_yres / 2) + return -EINVAL; + par->hw.st.mode = ST_MID; + xres = sttt_xres; + yres = st_yres / 2; + bpp = 2; + } else + return -EINVAL; + } + if (yres_virtual <= 0) + yres_virtual = 0; + else if (yres_virtual < yres) + yres_virtual = yres; + if (var->sync & FB_SYNC_EXT) + par->hw.st.sync = (par->hw.st.sync & ~1) | 1; + else + par->hw.st.sync = (par->hw.st.sync & ~1); + linelen = xres * bpp / 8; + if (yres_virtual * linelen > screen_len && screen_len) + return -EINVAL; + if (yres * linelen > screen_len && screen_len) + return -EINVAL; + if (var->yoffset + yres > yres_virtual && yres_virtual) + return -EINVAL; + par->yres_virtual = yres_virtual; + par->screen_base = screen_base + var->yoffset * linelen; + par->next_line = linelen; + return 0; +} + +static int stste_encode_var(struct fb_var_screeninfo *var, + struct atafb_par *par) +{ + int linelen; + memset(var, 0, sizeof(struct fb_var_screeninfo)); + var->red.offset = 0; + var->red.length = ATARIHW_PRESENT(EXTD_SHIFTER) ? 4 : 3; + var->red.msb_right = 0; + var->grayscale = 0; + + var->pixclock = 31041; + var->left_margin = 120; /* these are incorrect */ + var->right_margin = 100; + var->upper_margin = 8; + var->lower_margin = 16; + var->hsync_len = 140; + var->vsync_len = 30; + + var->height = -1; + var->width = -1; + + if (!(par->hw.st.sync & 1)) + var->sync = 0; + else + var->sync = FB_SYNC_EXT; + + switch (par->hw.st.mode & 3) { + case ST_LOW: + var->xres = sttt_xres / 2; + var->yres = st_yres / 2; + var->bits_per_pixel = 4; + break; + case ST_MID: + var->xres = sttt_xres; + var->yres = st_yres / 2; + var->bits_per_pixel = 2; + break; + case ST_HIGH: + var->xres = sttt_xres; + var->yres = st_yres; + var->bits_per_pixel = 1; + break; + } + var->blue = var->green = var->red; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->xres_virtual = sttt_xres_virtual; + linelen = var->xres_virtual * var->bits_per_pixel / 8; + ovsc_addlen = linelen * (sttt_yres_virtual - st_yres); + + if (!use_hwscroll) + var->yres_virtual = var->yres; + else if (screen_len) { + if (par->yres_virtual) + var->yres_virtual = par->yres_virtual; + else + /* yres_virtual == 0 means use maximum */ + var->yres_virtual = screen_len / linelen; + } else { + if (hwscroll < 0) + var->yres_virtual = 2 * var->yres; + else + var->yres_virtual = var->yres + hwscroll * 16; + } + var->xoffset = 0; + if (screen_base) + var->yoffset = (par->screen_base - screen_base) / linelen; + else + var->yoffset = 0; + var->nonstd = 0; + var->activate = 0; + var->vmode = FB_VMODE_NONINTERLACED; + return 0; +} + +static void stste_get_par(struct atafb_par *par) +{ + unsigned long addr; + par->hw.st.mode = shifter_tt.st_shiftmode; + par->hw.st.sync = shifter.syncmode; + addr = ((shifter.bas_hi & 0xff) << 16) | + ((shifter.bas_md & 0xff) << 8); + if (ATARIHW_PRESENT(EXTD_SHIFTER)) + addr |= (shifter.bas_lo & 0xff); + par->screen_base = phys_to_virt(addr); +} + +static void stste_set_par(struct atafb_par *par) +{ + shifter_tt.st_shiftmode = par->hw.st.mode; + shifter.syncmode = par->hw.st.sync; + /* only set screen_base if really necessary */ + if (current_par.screen_base != par->screen_base) + fbhw->set_screen_base(par->screen_base); +} + +static int stste_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + if (regno > 15) + return 1; + red >>= 12; + blue >>= 12; + green >>= 12; + if (ATARIHW_PRESENT(EXTD_SHIFTER)) + shifter_tt.color_reg[regno] = + (((red & 0xe) >> 1) | ((red & 1) << 3) << 8) | + (((green & 0xe) >> 1) | ((green & 1) << 3) << 4) | + ((blue & 0xe) >> 1) | ((blue & 1) << 3); + else + shifter_tt.color_reg[regno] = + ((red & 0xe) << 7) | + ((green & 0xe) << 3) | + ((blue & 0xe) >> 1); + return 0; +} + +static int stste_detect(void) +{ + struct atafb_par par; + + /* Determine the connected monitor: The DMA sound must be + * disabled before reading the MFP GPIP, because the Sound + * Done Signal and the Monochrome Detect are XORed together! + */ + if (ATARIHW_PRESENT(PCM_8BIT)) { + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + udelay(20); /* wait a while for things to settle down */ + } + mono_moni = (st_mfp.par_dt_reg & 0x80) == 0; + + stste_get_par(&par); + stste_encode_var(&atafb_predefined[0], &par); + + if (!ATARIHW_PRESENT(EXTD_SHIFTER)) + use_hwscroll = 0; + return 1; +} + +static void stste_set_screen_base(void *s_base) +{ + unsigned long addr; + addr = virt_to_phys(s_base); + /* Setup Screen Memory */ + shifter.bas_hi = (unsigned char)((addr & 0xff0000) >> 16); + shifter.bas_md = (unsigned char)((addr & 0x00ff00) >> 8); + if (ATARIHW_PRESENT(EXTD_SHIFTER)) + shifter.bas_lo = (unsigned char)(addr & 0x0000ff); +} + +#endif /* ATAFB_STE */ + +/* Switching the screen size should be done during vsync, otherwise + * the margins may get messed up. This is a well known problem of + * the ST's video system. + * + * Unfortunately there is hardly any way to find the vsync, as the + * vertical blank interrupt is no longer in time on machines with + * overscan type modifications. + * + * We can, however, use Timer B to safely detect the black shoulder, + * but then we've got to guess an appropriate delay to find the vsync. + * This might not work on every machine. + * + * martin_rogge @ ki.maus.de, 8th Aug 1995 + */ + +#define LINE_DELAY (mono_moni ? 30 : 70) +#define SYNC_DELAY (mono_moni ? 1500 : 2000) + +/* SWITCH_ACIA may be used for Falcon (ScreenBlaster III internal!) */ +static void st_ovsc_switch(void) +{ + unsigned long flags; + register unsigned char old, new; + + if (!(atari_switches & ATARI_SWITCH_OVSC_MASK)) + return; + local_irq_save(flags); + + st_mfp.tim_ct_b = 0x10; + st_mfp.active_edge |= 8; + st_mfp.tim_ct_b = 0; + st_mfp.tim_dt_b = 0xf0; + st_mfp.tim_ct_b = 8; + while (st_mfp.tim_dt_b > 1) /* TOS does it this way, don't ask why */ + ; + new = st_mfp.tim_dt_b; + do { + udelay(LINE_DELAY); + old = new; + new = st_mfp.tim_dt_b; + } while (old != new); + st_mfp.tim_ct_b = 0x10; + udelay(SYNC_DELAY); + + if (atari_switches & ATARI_SWITCH_OVSC_IKBD) + acia.key_ctrl = ACIA_DIV64 | ACIA_D8N1S | ACIA_RHTID | ACIA_RIE; + if (atari_switches & ATARI_SWITCH_OVSC_MIDI) + acia.mid_ctrl = ACIA_DIV16 | ACIA_D8N1S | ACIA_RHTID; + if (atari_switches & (ATARI_SWITCH_OVSC_SND6|ATARI_SWITCH_OVSC_SND7)) { + sound_ym.rd_data_reg_sel = 14; + sound_ym.wd_data = sound_ym.rd_data_reg_sel | + ((atari_switches & ATARI_SWITCH_OVSC_SND6) ? 0x40:0) | + ((atari_switches & ATARI_SWITCH_OVSC_SND7) ? 0x80:0); + } + local_irq_restore(flags); +} + +/* ------------------- External Video ---------------------- */ + +#ifdef ATAFB_EXT + +static int ext_encode_fix(struct fb_fix_screeninfo *fix, struct atafb_par *par) +{ + strcpy(fix->id, "Unknown Extern"); + fix->smem_start = (unsigned long)external_addr; + fix->smem_len = PAGE_ALIGN(external_len); + if (external_depth == 1) { + fix->type = FB_TYPE_PACKED_PIXELS; + /* The letters 'n' and 'i' in the "atavideo=external:" stand + * for "normal" and "inverted", rsp., in the monochrome case */ + fix->visual = + (external_pmode == FB_TYPE_INTERLEAVED_PLANES || + external_pmode == FB_TYPE_PACKED_PIXELS) ? + FB_VISUAL_MONO10 : FB_VISUAL_MONO01; + } else { + /* Use STATIC if we don't know how to access color registers */ + int visual = external_vgaiobase ? + FB_VISUAL_PSEUDOCOLOR : + FB_VISUAL_STATIC_PSEUDOCOLOR; + switch (external_pmode) { + case -1: /* truecolor */ + fix->type = FB_TYPE_PACKED_PIXELS; + fix->visual = FB_VISUAL_TRUECOLOR; + break; + case FB_TYPE_PACKED_PIXELS: + fix->type = FB_TYPE_PACKED_PIXELS; + fix->visual = visual; + break; + case FB_TYPE_PLANES: + fix->type = FB_TYPE_PLANES; + fix->visual = visual; + break; + case FB_TYPE_INTERLEAVED_PLANES: + fix->type = FB_TYPE_INTERLEAVED_PLANES; + fix->type_aux = 2; + fix->visual = visual; + break; + } + } + fix->xpanstep = 0; + fix->ypanstep = 0; + fix->ywrapstep = 0; + fix->line_length = par->next_line; + return 0; +} + +static int ext_decode_var(struct fb_var_screeninfo *var, struct atafb_par *par) +{ + struct fb_var_screeninfo *myvar = &atafb_predefined[0]; + + if (var->bits_per_pixel > myvar->bits_per_pixel || + var->xres > myvar->xres || + var->xres_virtual > myvar->xres_virtual || + var->yres > myvar->yres || + var->xoffset > 0 || + var->yoffset > 0) + return -EINVAL; + + par->next_line = external_xres_virtual * external_depth / 8; + return 0; +} + +static int ext_encode_var(struct fb_var_screeninfo *var, struct atafb_par *par) +{ + memset(var, 0, sizeof(struct fb_var_screeninfo)); + var->red.offset = 0; + var->red.length = (external_pmode == -1) ? external_depth / 3 : + (external_vgaiobase ? external_bitspercol : 0); + var->red.msb_right = 0; + var->grayscale = 0; + + var->pixclock = 31041; + var->left_margin = 120; /* these are surely incorrect */ + var->right_margin = 100; + var->upper_margin = 8; + var->lower_margin = 16; + var->hsync_len = 140; + var->vsync_len = 30; + + var->height = -1; + var->width = -1; + + var->sync = 0; + + var->xres = external_xres; + var->yres = external_yres; + var->xres_virtual = external_xres_virtual; + var->bits_per_pixel = external_depth; + + var->blue = var->green = var->red; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->yres_virtual = var->yres; + var->xoffset = 0; + var->yoffset = 0; + var->nonstd = 0; + var->activate = 0; + var->vmode = FB_VMODE_NONINTERLACED; + return 0; +} + +static void ext_get_par(struct atafb_par *par) +{ + par->screen_base = external_addr; +} + +static void ext_set_par(struct atafb_par *par) +{ +} + +#define OUTB(port,val) \ + *((unsigned volatile char *) ((port)+external_vgaiobase)) = (val) +#define INB(port) \ + (*((unsigned volatile char *) ((port)+external_vgaiobase))) +#define DACDelay \ + do { \ + unsigned char tmp = INB(0x3da); \ + tmp = INB(0x3da); \ + } while (0) + +static int ext_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + unsigned char colmask = (1 << external_bitspercol) - 1; + + if (!external_vgaiobase) + return 1; + + if (regno > 255) + return 1; + + switch (external_card_type) { + case IS_VGA: + OUTB(0x3c8, regno); + DACDelay; + OUTB(0x3c9, red & colmask); + DACDelay; + OUTB(0x3c9, green & colmask); + DACDelay; + OUTB(0x3c9, blue & colmask); + DACDelay; + return 0; + + case IS_MV300: + OUTB((MV300_reg[regno] << 2) + 1, red); + OUTB((MV300_reg[regno] << 2) + 1, green); + OUTB((MV300_reg[regno] << 2) + 1, blue); + return 0; + + default: + return 1; + } +} + +static int ext_detect(void) +{ + struct fb_var_screeninfo *myvar = &atafb_predefined[0]; + struct atafb_par dummy_par; + + myvar->xres = external_xres; + myvar->xres_virtual = external_xres_virtual; + myvar->yres = external_yres; + myvar->bits_per_pixel = external_depth; + ext_encode_var(myvar, &dummy_par); + return 1; +} + +#endif /* ATAFB_EXT */ + +/* ------ This is the same for most hardware types -------- */ + +static void set_screen_base(void *s_base) +{ + unsigned long addr; + + addr = virt_to_phys(s_base); + /* Setup Screen Memory */ + shifter.bas_hi = (unsigned char)((addr & 0xff0000) >> 16); + shifter.bas_md = (unsigned char)((addr & 0x00ff00) >> 8); + shifter.bas_lo = (unsigned char)(addr & 0x0000ff); +} + +static int pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct atafb_par *par = (struct atafb_par *)info->par; + + if (!fbhw->set_screen_base || + (!ATARIHW_PRESENT(EXTD_SHIFTER) && var->xoffset)) + return -EINVAL; + var->xoffset = up(var->xoffset, 16); + par->screen_base = screen_base + + (var->yoffset * info->var.xres_virtual + var->xoffset) + * info->var.bits_per_pixel / 8; + fbhw->set_screen_base(par->screen_base); + return 0; +} + +/* ------------ Interfaces to hardware functions ------------ */ + +#ifdef ATAFB_TT +static struct fb_hwswitch tt_switch = { + .detect = tt_detect, + .encode_fix = tt_encode_fix, + .decode_var = tt_decode_var, + .encode_var = tt_encode_var, + .get_par = tt_get_par, + .set_par = tt_set_par, + .set_screen_base = set_screen_base, + .pan_display = pan_display, +}; +#endif + +#ifdef ATAFB_FALCON +static struct fb_hwswitch falcon_switch = { + .detect = falcon_detect, + .encode_fix = falcon_encode_fix, + .decode_var = falcon_decode_var, + .encode_var = falcon_encode_var, + .get_par = falcon_get_par, + .set_par = falcon_set_par, + .set_screen_base = set_screen_base, + .blank = falcon_blank, + .pan_display = falcon_pan_display, +}; +#endif + +#ifdef ATAFB_STE +static struct fb_hwswitch st_switch = { + .detect = stste_detect, + .encode_fix = stste_encode_fix, + .decode_var = stste_decode_var, + .encode_var = stste_encode_var, + .get_par = stste_get_par, + .set_par = stste_set_par, + .set_screen_base = stste_set_screen_base, + .pan_display = pan_display +}; +#endif + +#ifdef ATAFB_EXT +static struct fb_hwswitch ext_switch = { + .detect = ext_detect, + .encode_fix = ext_encode_fix, + .decode_var = ext_decode_var, + .encode_var = ext_encode_var, + .get_par = ext_get_par, + .set_par = ext_set_par, +}; +#endif + +static void ata_get_par(struct atafb_par *par) +{ + if (current_par_valid) + *par = current_par; + else + fbhw->get_par(par); +} + +static void ata_set_par(struct atafb_par *par) +{ + fbhw->set_par(par); + current_par = *par; + current_par_valid = 1; +} + + +/* =========================================================== */ +/* ============== Hardware Independent Functions ============= */ +/* =========================================================== */ + +/* used for hardware scrolling */ + +static int do_fb_set_var(struct fb_var_screeninfo *var, int isactive) +{ + int err, activate; + struct atafb_par par; + + err = fbhw->decode_var(var, &par); + if (err) + return err; + activate = var->activate; + if (((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) && isactive) + ata_set_par(&par); + fbhw->encode_var(var, &par); + var->activate = activate; + return 0; +} + +/* fbhw->encode_fix() must be called with fb_info->mm_lock held + * if it is called after the register_framebuffer() - not a case here + */ +static int atafb_get_fix(struct fb_fix_screeninfo *fix, struct fb_info *info) +{ + struct atafb_par par; + int err; + // Get fix directly (case con == -1 before)?? + err = fbhw->decode_var(&info->var, &par); + if (err) + return err; + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + err = fbhw->encode_fix(fix, &par); + return err; +} + +static int atafb_get_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct atafb_par par; + + ata_get_par(&par); + fbhw->encode_var(var, &par); + + return 0; +} + +// No longer called by fbcon! +// Still called by set_var internally + +static void atafb_set_disp(struct fb_info *info) +{ + atafb_get_var(&info->var, info); + atafb_get_fix(&info->fix, info); + + info->screen_base = (void *)info->fix.smem_start; +} + +static int atafb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + red >>= 8; + green >>= 8; + blue >>= 8; + + return info->fbops->fb_setcolreg(regno, red, green, blue, transp, info); +} + +static int +atafb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int xoffset = var->xoffset; + int yoffset = var->yoffset; + int err; + + if (var->vmode & FB_VMODE_YWRAP) { + if (yoffset < 0 || yoffset >= info->var.yres_virtual || xoffset) + return -EINVAL; + } else { + if (xoffset + info->var.xres > info->var.xres_virtual || + yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + } + + if (fbhw->pan_display) { + err = fbhw->pan_display(var, info); + if (err) + return err; + } else + return -EINVAL; + + info->var.xoffset = xoffset; + info->var.yoffset = yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + return 0; +} + +/* + * generic drawing routines; imageblit needs updating for image depth > 1 + */ + +#if BITS_PER_LONG == 32 +#define BYTES_PER_LONG 4 +#define SHIFT_PER_LONG 5 +#elif BITS_PER_LONG == 64 +#define BYTES_PER_LONG 8 +#define SHIFT_PER_LONG 6 +#else +#define Please update me +#endif + + +static void atafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct atafb_par *par = (struct atafb_par *)info->par; + int x2, y2; + u32 width, height; + + if (!rect->width || !rect->height) + return; + +#ifdef ATAFB_FALCON + if (info->var.bits_per_pixel == 16) { + cfb_fillrect(info, rect); + return; + } +#endif + + /* + * We could use hardware clipping but on many cards you get around + * hardware clipping by writing to framebuffer directly. + * */ + x2 = rect->dx + rect->width; + y2 = rect->dy + rect->height; + x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual; + y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual; + width = x2 - rect->dx; + height = y2 - rect->dy; + + if (info->var.bits_per_pixel == 1) + atafb_mfb_fillrect(info, par->next_line, rect->color, + rect->dy, rect->dx, height, width); + else if (info->var.bits_per_pixel == 2) + atafb_iplan2p2_fillrect(info, par->next_line, rect->color, + rect->dy, rect->dx, height, width); + else if (info->var.bits_per_pixel == 4) + atafb_iplan2p4_fillrect(info, par->next_line, rect->color, + rect->dy, rect->dx, height, width); + else + atafb_iplan2p8_fillrect(info, par->next_line, rect->color, + rect->dy, rect->dx, height, width); + + return; +} + +static void atafb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct atafb_par *par = (struct atafb_par *)info->par; + int x2, y2; + u32 dx, dy, sx, sy, width, height; + int rev_copy = 0; + +#ifdef ATAFB_FALCON + if (info->var.bits_per_pixel == 16) { + cfb_copyarea(info, area); + return; + } +#endif + + /* clip the destination */ + x2 = area->dx + area->width; + y2 = area->dy + area->height; + dx = area->dx > 0 ? area->dx : 0; + dy = area->dy > 0 ? area->dy : 0; + x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual; + y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual; + width = x2 - dx; + height = y2 - dy; + + if (area->sx + dx < area->dx || area->sy + dy < area->dy) + return; + + /* update sx,sy */ + sx = area->sx + (dx - area->dx); + sy = area->sy + (dy - area->dy); + + /* the source must be completely inside the virtual screen */ + if (sx + width > info->var.xres_virtual || + sy + height > info->var.yres_virtual) + return; + + if (dy > sy || (dy == sy && dx > sx)) { + dy += height; + sy += height; + rev_copy = 1; + } + + if (info->var.bits_per_pixel == 1) + atafb_mfb_copyarea(info, par->next_line, sy, sx, dy, dx, height, width); + else if (info->var.bits_per_pixel == 2) + atafb_iplan2p2_copyarea(info, par->next_line, sy, sx, dy, dx, height, width); + else if (info->var.bits_per_pixel == 4) + atafb_iplan2p4_copyarea(info, par->next_line, sy, sx, dy, dx, height, width); + else + atafb_iplan2p8_copyarea(info, par->next_line, sy, sx, dy, dx, height, width); + + return; +} + +static void atafb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct atafb_par *par = (struct atafb_par *)info->par; + int x2, y2; + unsigned long *dst; + int dst_idx; + const char *src; + u32 dx, dy, width, height, pitch; + +#ifdef ATAFB_FALCON + if (info->var.bits_per_pixel == 16) { + cfb_imageblit(info, image); + return; + } +#endif + + /* + * We could use hardware clipping but on many cards you get around + * hardware clipping by writing to framebuffer directly like we are + * doing here. + */ + x2 = image->dx + image->width; + y2 = image->dy + image->height; + dx = image->dx; + dy = image->dy; + x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual; + y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual; + width = x2 - dx; + height = y2 - dy; + + if (image->depth == 1) { + // used for font data + dst = (unsigned long *) + ((unsigned long)info->screen_base & ~(BYTES_PER_LONG - 1)); + dst_idx = ((unsigned long)info->screen_base & (BYTES_PER_LONG - 1)) * 8; + dst_idx += dy * par->next_line * 8 + dx; + src = image->data; + pitch = (image->width + 7) / 8; + while (height--) { + + if (info->var.bits_per_pixel == 1) + atafb_mfb_linefill(info, par->next_line, + dy, dx, width, src, + image->bg_color, image->fg_color); + else if (info->var.bits_per_pixel == 2) + atafb_iplan2p2_linefill(info, par->next_line, + dy, dx, width, src, + image->bg_color, image->fg_color); + else if (info->var.bits_per_pixel == 4) + atafb_iplan2p4_linefill(info, par->next_line, + dy, dx, width, src, + image->bg_color, image->fg_color); + else + atafb_iplan2p8_linefill(info, par->next_line, + dy, dx, width, src, + image->bg_color, image->fg_color); + dy++; + src += pitch; + } + } else { + c2p_iplan2(info->screen_base, image->data, dx, dy, width, + height, par->next_line, image->width, + info->var.bits_per_pixel); + } +} + +static int +atafb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { +#ifdef FBCMD_GET_CURRENTPAR + case FBCMD_GET_CURRENTPAR: + if (copy_to_user((void *)arg, (void *)¤t_par, + sizeof(struct atafb_par))) + return -EFAULT; + return 0; +#endif +#ifdef FBCMD_SET_CURRENTPAR + case FBCMD_SET_CURRENTPAR: + if (copy_from_user((void *)¤t_par, (void *)arg, + sizeof(struct atafb_par))) + return -EFAULT; + ata_set_par(¤t_par); + return 0; +#endif + } + return -EINVAL; +} + +/* (un)blank/poweroff + * 0 = unblank + * 1 = blank + * 2 = suspend vsync + * 3 = suspend hsync + * 4 = off + */ +static int atafb_blank(int blank, struct fb_info *info) +{ + unsigned short black[16]; + struct fb_cmap cmap; + if (fbhw->blank && !fbhw->blank(blank)) + return 1; + if (blank) { + memset(black, 0, 16 * sizeof(unsigned short)); + cmap.red = black; + cmap.green = black; + cmap.blue = black; + cmap.transp = NULL; + cmap.start = 0; + cmap.len = 16; + fb_set_cmap(&cmap, info); + } +#if 0 + else + do_install_cmap(info); +#endif + return 0; +} + + /* + * New fbcon interface ... + */ + + /* check var by decoding var into hw par, rounding if necessary, + * then encoding hw par back into new, validated var */ +static int atafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int err; + struct atafb_par par; + + /* Validate wanted screen parameters */ + // if ((err = ata_decode_var(var, &par))) + err = fbhw->decode_var(var, &par); + if (err) + return err; + + /* Encode (possibly rounded) screen parameters */ + fbhw->encode_var(var, &par); + return 0; +} + + /* actually set hw par by decoding var, then setting hardware from + * hw par just decoded */ +static int atafb_set_par(struct fb_info *info) +{ + struct atafb_par *par = (struct atafb_par *)info->par; + + /* Decode wanted screen parameters */ + fbhw->decode_var(&info->var, par); + mutex_lock(&info->mm_lock); + fbhw->encode_fix(&info->fix, par); + mutex_unlock(&info->mm_lock); + + /* Set new videomode */ + ata_set_par(par); + + return 0; +} + + +static struct fb_ops atafb_ops = { + .owner = THIS_MODULE, + .fb_check_var = atafb_check_var, + .fb_set_par = atafb_set_par, + .fb_setcolreg = atafb_setcolreg, + .fb_blank = atafb_blank, + .fb_pan_display = atafb_pan_display, + .fb_fillrect = atafb_fillrect, + .fb_copyarea = atafb_copyarea, + .fb_imageblit = atafb_imageblit, + .fb_ioctl = atafb_ioctl, +}; + +static void check_default_par(int detected_mode) +{ + char default_name[10]; + int i; + struct fb_var_screeninfo var; + unsigned long min_mem; + + /* First try the user supplied mode */ + if (default_par) { + var = atafb_predefined[default_par - 1]; + var.activate = FB_ACTIVATE_TEST; + if (do_fb_set_var(&var, 1)) + default_par = 0; /* failed */ + } + /* Next is the autodetected one */ + if (!default_par) { + var = atafb_predefined[detected_mode - 1]; /* autodetect */ + var.activate = FB_ACTIVATE_TEST; + if (!do_fb_set_var(&var, 1)) + default_par = detected_mode; + } + /* If that also failed, try some default modes... */ + if (!default_par) { + /* try default1, default2... */ + for (i = 1; i < 10; i++) { + sprintf(default_name,"default%d", i); + default_par = get_video_mode(default_name); + if (!default_par) + panic("can't set default video mode"); + var = atafb_predefined[default_par - 1]; + var.activate = FB_ACTIVATE_TEST; + if (!do_fb_set_var(&var,1)) + break; /* ok */ + } + } + min_mem = var.xres_virtual * var.yres_virtual * var.bits_per_pixel / 8; + if (default_mem_req < min_mem) + default_mem_req = min_mem; +} + +#ifdef ATAFB_EXT +static void __init atafb_setup_ext(char *spec) +{ + int xres, xres_virtual, yres, depth, planes; + unsigned long addr, len; + char *p; + + /* Format is: <xres>;<yres>;<depth>;<plane organ.>; + * <screen mem addr> + * [;<screen mem length>[;<vgaiobase>[;<bits-per-col>[;<colorreg-type> + * [;<xres-virtual>]]]]] + * + * 09/23/97 Juergen + * <xres_virtual>: hardware's x-resolution (f.e. ProMST) + * + * Even xres_virtual is available, we neither support panning nor hw-scrolling! + */ + p = strsep(&spec, ";"); + if (!p || !*p) + return; + xres_virtual = xres = simple_strtoul(p, NULL, 10); + if (xres <= 0) + return; + + p = strsep(&spec, ";"); + if (!p || !*p) + return; + yres = simple_strtoul(p, NULL, 10); + if (yres <= 0) + return; + + p = strsep(&spec, ";"); + if (!p || !*p) + return; + depth = simple_strtoul(p, NULL, 10); + if (depth != 1 && depth != 2 && depth != 4 && depth != 8 && + depth != 16 && depth != 24) + return; + + p = strsep(&spec, ";"); + if (!p || !*p) + return; + if (*p == 'i') + planes = FB_TYPE_INTERLEAVED_PLANES; + else if (*p == 'p') + planes = FB_TYPE_PACKED_PIXELS; + else if (*p == 'n') + planes = FB_TYPE_PLANES; + else if (*p == 't') + planes = -1; /* true color */ + else + return; + + p = strsep(&spec, ";"); + if (!p || !*p) + return; + addr = simple_strtoul(p, NULL, 0); + + p = strsep(&spec, ";"); + if (!p || !*p) + len = xres * yres * depth / 8; + else + len = simple_strtoul(p, NULL, 0); + + p = strsep(&spec, ";"); + if (p && *p) + external_vgaiobase = simple_strtoul(p, NULL, 0); + + p = strsep(&spec, ";"); + if (p && *p) { + external_bitspercol = simple_strtoul(p, NULL, 0); + if (external_bitspercol > 8) + external_bitspercol = 8; + else if (external_bitspercol < 1) + external_bitspercol = 1; + } + + p = strsep(&spec, ";"); + if (p && *p) { + if (!strcmp(p, "vga")) + external_card_type = IS_VGA; + if (!strcmp(p, "mv300")) + external_card_type = IS_MV300; + } + + p = strsep(&spec, ";"); + if (p && *p) { + xres_virtual = simple_strtoul(p, NULL, 10); + if (xres_virtual < xres) + xres_virtual = xres; + if (xres_virtual * yres * depth / 8 > len) + len = xres_virtual * yres * depth / 8; + } + + external_xres = xres; + external_xres_virtual = xres_virtual; + external_yres = yres; + external_depth = depth; + external_pmode = planes; + external_addr = (void *)addr; + external_len = len; + + if (external_card_type == IS_MV300) { + switch (external_depth) { + case 1: + MV300_reg = MV300_reg_1bit; + break; + case 4: + MV300_reg = MV300_reg_4bit; + break; + case 8: + MV300_reg = MV300_reg_8bit; + break; + } + } +} +#endif /* ATAFB_EXT */ + +static void __init atafb_setup_int(char *spec) +{ + /* Format to config extended internal video hardware like OverScan: + * "internal:<xres>;<yres>;<xres_max>;<yres_max>;<offset>" + * Explanation: + * <xres>: x-resolution + * <yres>: y-resolution + * The following are only needed if you have an overscan which + * needs a black border: + * <xres_max>: max. length of a line in pixels your OverScan hardware would allow + * <yres_max>: max. number of lines your OverScan hardware would allow + * <offset>: Offset from physical beginning to visible beginning + * of screen in bytes + */ + int xres; + char *p; + + if (!(p = strsep(&spec, ";")) || !*p) + return; + xres = simple_strtoul(p, NULL, 10); + if (!(p = strsep(&spec, ";")) || !*p) + return; + sttt_xres = xres; + tt_yres = st_yres = simple_strtoul(p, NULL, 10); + if ((p = strsep(&spec, ";")) && *p) + sttt_xres_virtual = simple_strtoul(p, NULL, 10); + if ((p = strsep(&spec, ";")) && *p) + sttt_yres_virtual = simple_strtoul(p, NULL, 0); + if ((p = strsep(&spec, ";")) && *p) + ovsc_offset = simple_strtoul(p, NULL, 0); + + if (ovsc_offset || (sttt_yres_virtual != st_yres)) + use_hwscroll = 0; +} + +#ifdef ATAFB_FALCON +static void __init atafb_setup_mcap(char *spec) +{ + char *p; + int vmin, vmax, hmin, hmax; + + /* Format for monitor capabilities is: <Vmin>;<Vmax>;<Hmin>;<Hmax> + * <V*> vertical freq. in Hz + * <H*> horizontal freq. in kHz + */ + if (!(p = strsep(&spec, ";")) || !*p) + return; + vmin = simple_strtoul(p, NULL, 10); + if (vmin <= 0) + return; + if (!(p = strsep(&spec, ";")) || !*p) + return; + vmax = simple_strtoul(p, NULL, 10); + if (vmax <= 0 || vmax <= vmin) + return; + if (!(p = strsep(&spec, ";")) || !*p) + return; + hmin = 1000 * simple_strtoul(p, NULL, 10); + if (hmin <= 0) + return; + if (!(p = strsep(&spec, "")) || !*p) + return; + hmax = 1000 * simple_strtoul(p, NULL, 10); + if (hmax <= 0 || hmax <= hmin) + return; + + fb_info.monspecs.vfmin = vmin; + fb_info.monspecs.vfmax = vmax; + fb_info.monspecs.hfmin = hmin; + fb_info.monspecs.hfmax = hmax; +} +#endif /* ATAFB_FALCON */ + +static void __init atafb_setup_user(char *spec) +{ + /* Format of user defined video mode is: <xres>;<yres>;<depth> + */ + char *p; + int xres, yres, depth, temp; + + p = strsep(&spec, ";"); + if (!p || !*p) + return; + xres = simple_strtoul(p, NULL, 10); + p = strsep(&spec, ";"); + if (!p || !*p) + return; + yres = simple_strtoul(p, NULL, 10); + p = strsep(&spec, ""); + if (!p || !*p) + return; + depth = simple_strtoul(p, NULL, 10); + temp = get_video_mode("user0"); + if (temp) { + default_par = temp; + atafb_predefined[default_par - 1].xres = xres; + atafb_predefined[default_par - 1].yres = yres; + atafb_predefined[default_par - 1].bits_per_pixel = depth; + } +} + +int __init atafb_setup(char *options) +{ + char *this_opt; + int temp; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + if ((temp = get_video_mode(this_opt))) { + default_par = temp; + mode_option = this_opt; + } else if (!strcmp(this_opt, "inverse")) + inverse = 1; + else if (!strncmp(this_opt, "hwscroll_", 9)) { + hwscroll = simple_strtoul(this_opt + 9, NULL, 10); + if (hwscroll < 0) + hwscroll = 0; + if (hwscroll > 200) + hwscroll = 200; + } +#ifdef ATAFB_EXT + else if (!strcmp(this_opt, "mv300")) { + external_bitspercol = 8; + external_card_type = IS_MV300; + } else if (!strncmp(this_opt, "external:", 9)) + atafb_setup_ext(this_opt + 9); +#endif + else if (!strncmp(this_opt, "internal:", 9)) + atafb_setup_int(this_opt + 9); +#ifdef ATAFB_FALCON + else if (!strncmp(this_opt, "eclock:", 7)) { + fext.f = simple_strtoul(this_opt + 7, NULL, 10); + /* external pixelclock in kHz --> ps */ + fext.t = 1000000000 / fext.f; + fext.f *= 1000; + } else if (!strncmp(this_opt, "monitorcap:", 11)) + atafb_setup_mcap(this_opt + 11); +#endif + else if (!strcmp(this_opt, "keep")) + DontCalcRes = 1; + else if (!strncmp(this_opt, "R", 1)) + atafb_setup_user(this_opt + 1); + } + return 0; +} + +int __init atafb_init(void) +{ + int pad, detected_mode, error; + unsigned int defmode = 0; + unsigned long mem_req; + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("atafb", &option)) + return -ENODEV; + atafb_setup(option); +#endif + printk("atafb_init: start\n"); + + if (!MACH_IS_ATARI) + return -ENODEV; + + do { +#ifdef ATAFB_EXT + if (external_addr) { + printk("atafb_init: initializing external hw\n"); + fbhw = &ext_switch; + atafb_ops.fb_setcolreg = &ext_setcolreg; + defmode = DEFMODE_EXT; + break; + } +#endif +#ifdef ATAFB_TT + if (ATARIHW_PRESENT(TT_SHIFTER)) { + printk("atafb_init: initializing TT hw\n"); + fbhw = &tt_switch; + atafb_ops.fb_setcolreg = &tt_setcolreg; + defmode = DEFMODE_TT; + break; + } +#endif +#ifdef ATAFB_FALCON + if (ATARIHW_PRESENT(VIDEL_SHIFTER)) { + printk("atafb_init: initializing Falcon hw\n"); + fbhw = &falcon_switch; + atafb_ops.fb_setcolreg = &falcon_setcolreg; + error = request_irq(IRQ_AUTO_4, falcon_vbl_switcher, + IRQ_TYPE_PRIO, + "framebuffer:modeswitch", + falcon_vbl_switcher); + if (error) + return error; + defmode = DEFMODE_F30; + break; + } +#endif +#ifdef ATAFB_STE + if (ATARIHW_PRESENT(STND_SHIFTER) || + ATARIHW_PRESENT(EXTD_SHIFTER)) { + printk("atafb_init: initializing ST/E hw\n"); + fbhw = &st_switch; + atafb_ops.fb_setcolreg = &stste_setcolreg; + defmode = DEFMODE_STE; + break; + } + fbhw = &st_switch; + atafb_ops.fb_setcolreg = &stste_setcolreg; + printk("Cannot determine video hardware; defaulting to ST(e)\n"); +#else /* ATAFB_STE */ + /* no default driver included */ + /* Nobody will ever see this message :-) */ + panic("Cannot initialize video hardware"); +#endif + } while (0); + + /* Multisync monitor capabilities */ + /* Atari-TOS defaults if no boot option present */ + if (fb_info.monspecs.hfmin == 0) { + fb_info.monspecs.hfmin = 31000; + fb_info.monspecs.hfmax = 32000; + fb_info.monspecs.vfmin = 58; + fb_info.monspecs.vfmax = 62; + } + + detected_mode = fbhw->detect(); + check_default_par(detected_mode); +#ifdef ATAFB_EXT + if (!external_addr) { +#endif /* ATAFB_EXT */ + mem_req = default_mem_req + ovsc_offset + ovsc_addlen; + mem_req = PAGE_ALIGN(mem_req) + PAGE_SIZE; + screen_base = atari_stram_alloc(mem_req, "atafb"); + if (!screen_base) + panic("Cannot allocate screen memory"); + memset(screen_base, 0, mem_req); + pad = -(unsigned long)screen_base & (PAGE_SIZE - 1); + screen_base += pad; + real_screen_base = screen_base + ovsc_offset; + screen_len = (mem_req - pad - ovsc_offset) & PAGE_MASK; + st_ovsc_switch(); + if (CPU_IS_040_OR_060) { + /* On a '040+, the cache mode of video RAM must be set to + * write-through also for internal video hardware! */ + cache_push(virt_to_phys(screen_base), screen_len); + kernel_set_cachemode(screen_base, screen_len, + IOMAP_WRITETHROUGH); + } + printk("atafb: screen_base %p real_screen_base %p screen_len %d\n", + screen_base, real_screen_base, screen_len); +#ifdef ATAFB_EXT + } else { + /* Map the video memory (physical address given) to somewhere + * in the kernel address space. + */ + external_addr = ioremap_writethrough((unsigned long)external_addr, + external_len); + if (external_vgaiobase) + external_vgaiobase = + (unsigned long)ioremap(external_vgaiobase, 0x10000); + screen_base = + real_screen_base = external_addr; + screen_len = external_len & PAGE_MASK; + memset (screen_base, 0, external_len); + } +#endif /* ATAFB_EXT */ + +// strcpy(fb_info.mode->name, "Atari Builtin "); + fb_info.fbops = &atafb_ops; + // try to set default (detected; requested) var + do_fb_set_var(&atafb_predefined[default_par - 1], 1); + // reads hw state into current par, which may not be sane yet + ata_get_par(¤t_par); + fb_info.par = ¤t_par; + // tries to read from HW which may not be initialized yet + // so set sane var first, then call atafb_set_par + atafb_get_var(&fb_info.var, &fb_info); + +#ifdef ATAFB_FALCON + fb_info.pseudo_palette = current_par.hw.falcon.pseudo_palette; +#endif + fb_info.flags = FBINFO_FLAG_DEFAULT; + + if (!fb_find_mode(&fb_info.var, &fb_info, mode_option, atafb_modedb, + NUM_TOTAL_MODES, &atafb_modedb[defmode], + fb_info.var.bits_per_pixel)) { + return -EINVAL; + } + + fb_videomode_to_modelist(atafb_modedb, NUM_TOTAL_MODES, + &fb_info.modelist); + + atafb_set_disp(&fb_info); + + fb_alloc_cmap(&(fb_info.cmap), 1 << fb_info.var.bits_per_pixel, 0); + + + printk("Determined %dx%d, depth %d\n", + fb_info.var.xres, fb_info.var.yres, fb_info.var.bits_per_pixel); + if ((fb_info.var.xres != fb_info.var.xres_virtual) || + (fb_info.var.yres != fb_info.var.yres_virtual)) + printk(" virtual %dx%d\n", fb_info.var.xres_virtual, + fb_info.var.yres_virtual); + + if (register_framebuffer(&fb_info) < 0) { +#ifdef ATAFB_EXT + if (external_addr) { + iounmap(external_addr); + external_addr = NULL; + } + if (external_vgaiobase) { + iounmap((void*)external_vgaiobase); + external_vgaiobase = 0; + } +#endif + return -EINVAL; + } + + fb_info(&fb_info, "frame buffer device, using %dK of video memory\n", + screen_len >> 10); + + /* TODO: This driver cannot be unloaded yet */ + return 0; +} + +module_init(atafb_init); + +#ifdef MODULE +MODULE_LICENSE("GPL"); + +int cleanup_module(void) +{ + unregister_framebuffer(&fb_info); + return atafb_deinit(); +} +#endif /* MODULE */ diff --git a/drivers/video/fbdev/atafb.h b/drivers/video/fbdev/atafb.h new file mode 100644 index 000000000000..014e05906cb1 --- /dev/null +++ b/drivers/video/fbdev/atafb.h @@ -0,0 +1,36 @@ +#ifndef _VIDEO_ATAFB_H +#define _VIDEO_ATAFB_H + +void atafb_mfb_copyarea(struct fb_info *info, u_long next_line, int sy, int sx, int dy, + int dx, int height, int width); +void atafb_mfb_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width); +void atafb_mfb_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor); + +void atafb_iplan2p2_copyarea(struct fb_info *info, u_long next_line, int sy, int sx, int dy, + int dx, int height, int width); +void atafb_iplan2p2_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width); +void atafb_iplan2p2_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor); + +void atafb_iplan2p4_copyarea(struct fb_info *info, u_long next_line, int sy, int sx, int dy, + int dx, int height, int width); +void atafb_iplan2p4_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width); +void atafb_iplan2p4_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor); + +void atafb_iplan2p8_copyarea(struct fb_info *info, u_long next_line, int sy, int sx, int dy, + int dx, int height, int width); +void atafb_iplan2p8_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width); +void atafb_iplan2p8_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor); + +#endif /* _VIDEO_ATAFB_H */ diff --git a/drivers/video/fbdev/atafb_iplan2p2.c b/drivers/video/fbdev/atafb_iplan2p2.c new file mode 100644 index 000000000000..8cc9c50379d0 --- /dev/null +++ b/drivers/video/fbdev/atafb_iplan2p2.c @@ -0,0 +1,293 @@ +/* + * linux/drivers/video/iplan2p2.c -- Low level frame buffer operations for + * interleaved bitplanes Ă la Atari (2 + * planes, 2 bytes interleave) + * + * Created 5 Apr 1997 by Geert Uytterhoeven + * + * 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/string.h> +#include <linux/fb.h> + +#include <asm/setup.h> + +#include "atafb.h" + +#define BPL 2 +#include "atafb_utils.h" + +void atafb_iplan2p2_copyarea(struct fb_info *info, u_long next_line, + int sy, int sx, int dy, int dx, + int height, int width) +{ + /* bmove() has to distinguish two major cases: If both, source and + * destination, start at even addresses or both are at odd + * addresses, just the first odd and last even column (if present) + * require special treatment (memmove_col()). The rest between + * then can be copied by normal operations, because all adjacent + * bytes are affected and are to be stored in the same order. + * The pathological case is when the move should go from an odd + * address to an even or vice versa. Since the bytes in the plane + * words must be assembled in new order, it seems wisest to make + * all movements by memmove_col(). + */ + + u8 *src, *dst; + u32 *s, *d; + int w, l , i, j; + u_int colsize; + u_int upwards = (dy < sy) || (dy == sy && dx < sx); + + colsize = height; + if (!((sx ^ dx) & 15)) { + /* odd->odd or even->even */ + + if (upwards) { + src = (u8 *)info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL); + if (sx & 15) { + memmove32_col(dst, src, 0xff00ff, height, next_line - BPL * 2); + src += BPL * 2; + dst += BPL * 2; + width -= 8; + } + w = width >> 4; + if (w) { + s = (u32 *)src; + d = (u32 *)dst; + w *= BPL / 2; + l = next_line - w * 4; + for (j = height; j > 0; j--) { + for (i = w; i > 0; i--) + *d++ = *s++; + s = (u32 *)((u8 *)s + l); + d = (u32 *)((u8 *)d + l); + } + } + if (width & 15) + memmove32_col(dst + width / (8 / BPL), src + width / (8 / BPL), + 0xff00ff00, height, next_line - BPL * 2); + } else { + src = (u8 *)info->screen_base + (sy - 1) * next_line + ((sx + width + 8) & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + (dy - 1) * next_line + ((dx + width + 8) & ~15) / (8 / BPL); + + if ((sx + width) & 15) { + src -= BPL * 2; + dst -= BPL * 2; + memmove32_col(dst, src, 0xff00ff00, colsize, -next_line - BPL * 2); + width -= 8; + } + w = width >> 4; + if (w) { + s = (u32 *)src; + d = (u32 *)dst; + w *= BPL / 2; + l = next_line - w * 4; + for (j = height; j > 0; j--) { + for (i = w; i > 0; i--) + *--d = *--s; + s = (u32 *)((u8 *)s - l); + d = (u32 *)((u8 *)d - l); + } + } + if (sx & 15) + memmove32_col(dst - (width - 16) / (8 / BPL), + src - (width - 16) / (8 / BPL), + 0xff00ff, colsize, -next_line - BPL * 2); + } + } else { + /* odd->even or even->odd */ + if (upwards) { + u32 *src32, *dst32; + u32 pval[4], v, v1, mask; + int i, j, w, f; + + src = (u8 *)info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL); + + mask = 0xff00ff00; + f = 0; + w = width; + if (sx & 15) { + f = 1; + w += 8; + } + if ((sx + width) & 15) + f |= 2; + w >>= 4; + for (i = height; i; i--) { + src32 = (u32 *)src; + dst32 = (u32 *)dst; + + if (f & 1) { + pval[0] = (*src32++ << 8) & mask; + } else { + pval[0] = dst32[0] & mask; + } + + for (j = w; j > 0; j--) { + v = *src32++; + v1 = v & mask; + *dst32++ = pval[0] | (v1 >> 8); + pval[0] = (v ^ v1) << 8; + } + + if (f & 2) { + dst32[0] = (dst32[0] & mask) | pval[0]; + } + + src += next_line; + dst += next_line; + } + } else { + u32 *src32, *dst32; + u32 pval[4], v, v1, mask; + int i, j, w, f; + + src = (u8 *)info->screen_base + (sy - 1) * next_line + ((sx + width + 8) & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + (dy - 1) * next_line + ((dx + width + 8) & ~15) / (8 / BPL); + + mask = 0xff00ff; + f = 0; + w = width; + if ((dx + width) & 15) + f = 1; + if (sx & 15) { + f |= 2; + w += 8; + } + w >>= 4; + for (i = height; i; i--) { + src32 = (u32 *)src; + dst32 = (u32 *)dst; + + if (f & 1) { + pval[0] = dst32[-1] & mask; + } else { + pval[0] = (*--src32 >> 8) & mask; + } + + for (j = w; j > 0; j--) { + v = *--src32; + v1 = v & mask; + *--dst32 = pval[0] | (v1 << 8); + pval[0] = (v ^ v1) >> 8; + } + + if (!(f & 2)) { + dst32[-1] = (dst32[-1] & mask) | pval[0]; + } + + src -= next_line; + dst -= next_line; + } + } + } +} + +void atafb_iplan2p2_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width) +{ + u32 *dest; + int rows, i; + u32 cval[4]; + + dest = (u32 *)(info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL)); + if (sx & 15) { + u8 *dest8 = (u8 *)dest + 1; + + expand8_col2mask(color, cval); + + for (i = height; i; i--) { + fill8_col(dest8, cval); + dest8 += next_line; + } + dest += BPL / 2; + width -= 8; + } + + expand16_col2mask(color, cval); + rows = width >> 4; + if (rows) { + u32 *d = dest; + u32 off = next_line - rows * BPL * 2; + for (i = height; i; i--) { + d = fill16_col(d, rows, cval); + d = (u32 *)((long)d + off); + } + dest += rows * BPL / 2; + width &= 15; + } + + if (width) { + u8 *dest8 = (u8 *)dest; + + expand8_col2mask(color, cval); + + for (i = height; i; i--) { + fill8_col(dest8, cval); + dest8 += next_line; + } + } +} + +void atafb_iplan2p2_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor) +{ + u32 *dest; + const u16 *data16; + int rows; + u32 fgm[4], bgm[4], m; + + dest = (u32 *)(info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL)); + if (dx & 15) { + fill8_2col((u8 *)dest + 1, fgcolor, bgcolor, *data++); + dest += BPL / 2; + width -= 8; + } + + if (width >= 16) { + data16 = (const u16 *)data; + expand16_2col2mask(fgcolor, bgcolor, fgm, bgm); + + for (rows = width / 16; rows; rows--) { + u16 d = *data16++; + m = d | ((u32)d << 16); + *dest++ = (m & fgm[0]) ^ bgm[0]; + } + + data = (const u8 *)data16; + width &= 15; + } + + if (width) + fill8_2col((u8 *)dest, fgcolor, bgcolor, *data); +} + +#ifdef MODULE +MODULE_LICENSE("GPL"); + +int init_module(void) +{ + return 0; +} + +void cleanup_module(void) +{ +} +#endif /* MODULE */ + + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(atafb_iplan2p2_copyarea); +EXPORT_SYMBOL(atafb_iplan2p2_fillrect); +EXPORT_SYMBOL(atafb_iplan2p2_linefill); diff --git a/drivers/video/fbdev/atafb_iplan2p4.c b/drivers/video/fbdev/atafb_iplan2p4.c new file mode 100644 index 000000000000..bee0d89463f7 --- /dev/null +++ b/drivers/video/fbdev/atafb_iplan2p4.c @@ -0,0 +1,308 @@ +/* + * linux/drivers/video/iplan2p4.c -- Low level frame buffer operations for + * interleaved bitplanes Ă la Atari (4 + * planes, 2 bytes interleave) + * + * Created 5 Apr 1997 by Geert Uytterhoeven + * + * 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/string.h> +#include <linux/fb.h> + +#include <asm/setup.h> + +#include "atafb.h" + +#define BPL 4 +#include "atafb_utils.h" + +void atafb_iplan2p4_copyarea(struct fb_info *info, u_long next_line, + int sy, int sx, int dy, int dx, + int height, int width) +{ + /* bmove() has to distinguish two major cases: If both, source and + * destination, start at even addresses or both are at odd + * addresses, just the first odd and last even column (if present) + * require special treatment (memmove_col()). The rest between + * then can be copied by normal operations, because all adjacent + * bytes are affected and are to be stored in the same order. + * The pathological case is when the move should go from an odd + * address to an even or vice versa. Since the bytes in the plane + * words must be assembled in new order, it seems wisest to make + * all movements by memmove_col(). + */ + + u8 *src, *dst; + u32 *s, *d; + int w, l , i, j; + u_int colsize; + u_int upwards = (dy < sy) || (dy == sy && dx < sx); + + colsize = height; + if (!((sx ^ dx) & 15)) { + /* odd->odd or even->even */ + + if (upwards) { + src = (u8 *)info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL); + if (sx & 15) { + memmove32_col(dst, src, 0xff00ff, height, next_line - BPL * 2); + src += BPL * 2; + dst += BPL * 2; + width -= 8; + } + w = width >> 4; + if (w) { + s = (u32 *)src; + d = (u32 *)dst; + w *= BPL / 2; + l = next_line - w * 4; + for (j = height; j > 0; j--) { + for (i = w; i > 0; i--) + *d++ = *s++; + s = (u32 *)((u8 *)s + l); + d = (u32 *)((u8 *)d + l); + } + } + if (width & 15) + memmove32_col(dst + width / (8 / BPL), src + width / (8 / BPL), + 0xff00ff00, height, next_line - BPL * 2); + } else { + src = (u8 *)info->screen_base + (sy - 1) * next_line + ((sx + width + 8) & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + (dy - 1) * next_line + ((dx + width + 8) & ~15) / (8 / BPL); + + if ((sx + width) & 15) { + src -= BPL * 2; + dst -= BPL * 2; + memmove32_col(dst, src, 0xff00ff00, colsize, -next_line - BPL * 2); + width -= 8; + } + w = width >> 4; + if (w) { + s = (u32 *)src; + d = (u32 *)dst; + w *= BPL / 2; + l = next_line - w * 4; + for (j = height; j > 0; j--) { + for (i = w; i > 0; i--) + *--d = *--s; + s = (u32 *)((u8 *)s - l); + d = (u32 *)((u8 *)d - l); + } + } + if (sx & 15) + memmove32_col(dst - (width - 16) / (8 / BPL), + src - (width - 16) / (8 / BPL), + 0xff00ff, colsize, -next_line - BPL * 2); + } + } else { + /* odd->even or even->odd */ + if (upwards) { + u32 *src32, *dst32; + u32 pval[4], v, v1, mask; + int i, j, w, f; + + src = (u8 *)info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL); + + mask = 0xff00ff00; + f = 0; + w = width; + if (sx & 15) { + f = 1; + w += 8; + } + if ((sx + width) & 15) + f |= 2; + w >>= 4; + for (i = height; i; i--) { + src32 = (u32 *)src; + dst32 = (u32 *)dst; + + if (f & 1) { + pval[0] = (*src32++ << 8) & mask; + pval[1] = (*src32++ << 8) & mask; + } else { + pval[0] = dst32[0] & mask; + pval[1] = dst32[1] & mask; + } + + for (j = w; j > 0; j--) { + v = *src32++; + v1 = v & mask; + *dst32++ = pval[0] | (v1 >> 8); + pval[0] = (v ^ v1) << 8; + v = *src32++; + v1 = v & mask; + *dst32++ = pval[1] | (v1 >> 8); + pval[1] = (v ^ v1) << 8; + } + + if (f & 2) { + dst32[0] = (dst32[0] & mask) | pval[0]; + dst32[1] = (dst32[1] & mask) | pval[1]; + } + + src += next_line; + dst += next_line; + } + } else { + u32 *src32, *dst32; + u32 pval[4], v, v1, mask; + int i, j, w, f; + + src = (u8 *)info->screen_base + (sy - 1) * next_line + ((sx + width + 8) & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + (dy - 1) * next_line + ((dx + width + 8) & ~15) / (8 / BPL); + + mask = 0xff00ff; + f = 0; + w = width; + if ((dx + width) & 15) + f = 1; + if (sx & 15) { + f |= 2; + w += 8; + } + w >>= 4; + for (i = height; i; i--) { + src32 = (u32 *)src; + dst32 = (u32 *)dst; + + if (f & 1) { + pval[0] = dst32[-1] & mask; + pval[1] = dst32[-2] & mask; + } else { + pval[0] = (*--src32 >> 8) & mask; + pval[1] = (*--src32 >> 8) & mask; + } + + for (j = w; j > 0; j--) { + v = *--src32; + v1 = v & mask; + *--dst32 = pval[0] | (v1 << 8); + pval[0] = (v ^ v1) >> 8; + v = *--src32; + v1 = v & mask; + *--dst32 = pval[1] | (v1 << 8); + pval[1] = (v ^ v1) >> 8; + } + + if (!(f & 2)) { + dst32[-1] = (dst32[-1] & mask) | pval[0]; + dst32[-2] = (dst32[-2] & mask) | pval[1]; + } + + src -= next_line; + dst -= next_line; + } + } + } +} + +void atafb_iplan2p4_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width) +{ + u32 *dest; + int rows, i; + u32 cval[4]; + + dest = (u32 *)(info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL)); + if (sx & 15) { + u8 *dest8 = (u8 *)dest + 1; + + expand8_col2mask(color, cval); + + for (i = height; i; i--) { + fill8_col(dest8, cval); + dest8 += next_line; + } + dest += BPL / 2; + width -= 8; + } + + expand16_col2mask(color, cval); + rows = width >> 4; + if (rows) { + u32 *d = dest; + u32 off = next_line - rows * BPL * 2; + for (i = height; i; i--) { + d = fill16_col(d, rows, cval); + d = (u32 *)((long)d + off); + } + dest += rows * BPL / 2; + width &= 15; + } + + if (width) { + u8 *dest8 = (u8 *)dest; + + expand8_col2mask(color, cval); + + for (i = height; i; i--) { + fill8_col(dest8, cval); + dest8 += next_line; + } + } +} + +void atafb_iplan2p4_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor) +{ + u32 *dest; + const u16 *data16; + int rows; + u32 fgm[4], bgm[4], m; + + dest = (u32 *)(info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL)); + if (dx & 15) { + fill8_2col((u8 *)dest + 1, fgcolor, bgcolor, *data++); + dest += BPL / 2; + width -= 8; + } + + if (width >= 16) { + data16 = (const u16 *)data; + expand16_2col2mask(fgcolor, bgcolor, fgm, bgm); + + for (rows = width / 16; rows; rows--) { + u16 d = *data16++; + m = d | ((u32)d << 16); + *dest++ = (m & fgm[0]) ^ bgm[0]; + *dest++ = (m & fgm[1]) ^ bgm[1]; + } + + data = (const u8 *)data16; + width &= 15; + } + + if (width) + fill8_2col((u8 *)dest, fgcolor, bgcolor, *data); +} + +#ifdef MODULE +MODULE_LICENSE("GPL"); + +int init_module(void) +{ + return 0; +} + +void cleanup_module(void) +{ +} +#endif /* MODULE */ + + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(atafb_iplan2p4_copyarea); +EXPORT_SYMBOL(atafb_iplan2p4_fillrect); +EXPORT_SYMBOL(atafb_iplan2p4_linefill); diff --git a/drivers/video/fbdev/atafb_iplan2p8.c b/drivers/video/fbdev/atafb_iplan2p8.c new file mode 100644 index 000000000000..356fb52ce443 --- /dev/null +++ b/drivers/video/fbdev/atafb_iplan2p8.c @@ -0,0 +1,345 @@ +/* + * linux/drivers/video/iplan2p8.c -- Low level frame buffer operations for + * interleaved bitplanes Ă la Atari (8 + * planes, 2 bytes interleave) + * + * Created 5 Apr 1997 by Geert Uytterhoeven + * + * 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/string.h> +#include <linux/fb.h> + +#include <asm/setup.h> + +#include "atafb.h" + +#define BPL 8 +#include "atafb_utils.h" + + +/* Copies a 8 plane column from 's', height 'h', to 'd'. */ + +/* This expands a 8 bit color into two longs for two movepl (8 plane) + * operations. + */ + +void atafb_iplan2p8_copyarea(struct fb_info *info, u_long next_line, + int sy, int sx, int dy, int dx, + int height, int width) +{ + /* bmove() has to distinguish two major cases: If both, source and + * destination, start at even addresses or both are at odd + * addresses, just the first odd and last even column (if present) + * require special treatment (memmove_col()). The rest between + * then can be copied by normal operations, because all adjacent + * bytes are affected and are to be stored in the same order. + * The pathological case is when the move should go from an odd + * address to an even or vice versa. Since the bytes in the plane + * words must be assembled in new order, it seems wisest to make + * all movements by memmove_col(). + */ + + u8 *src, *dst; + u32 *s, *d; + int w, l , i, j; + u_int colsize; + u_int upwards = (dy < sy) || (dy == sy && dx < sx); + + colsize = height; + if (!((sx ^ dx) & 15)) { + /* odd->odd or even->even */ + + if (upwards) { + src = (u8 *)info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL); + if (sx & 15) { + memmove32_col(dst, src, 0xff00ff, height, next_line - BPL * 2); + src += BPL * 2; + dst += BPL * 2; + width -= 8; + } + w = width >> 4; + if (w) { + s = (u32 *)src; + d = (u32 *)dst; + w *= BPL / 2; + l = next_line - w * 4; + for (j = height; j > 0; j--) { + for (i = w; i > 0; i--) + *d++ = *s++; + s = (u32 *)((u8 *)s + l); + d = (u32 *)((u8 *)d + l); + } + } + if (width & 15) + memmove32_col(dst + width / (8 / BPL), src + width / (8 / BPL), + 0xff00ff00, height, next_line - BPL * 2); + } else { + src = (u8 *)info->screen_base + (sy - 1) * next_line + ((sx + width + 8) & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + (dy - 1) * next_line + ((dx + width + 8) & ~15) / (8 / BPL); + + if ((sx + width) & 15) { + src -= BPL * 2; + dst -= BPL * 2; + memmove32_col(dst, src, 0xff00ff00, colsize, -next_line - BPL * 2); + width -= 8; + } + w = width >> 4; + if (w) { + s = (u32 *)src; + d = (u32 *)dst; + w *= BPL / 2; + l = next_line - w * 4; + for (j = height; j > 0; j--) { + for (i = w; i > 0; i--) + *--d = *--s; + s = (u32 *)((u8 *)s - l); + d = (u32 *)((u8 *)d - l); + } + } + if (sx & 15) + memmove32_col(dst - (width - 16) / (8 / BPL), + src - (width - 16) / (8 / BPL), + 0xff00ff, colsize, -next_line - BPL * 2); + } + } else { + /* odd->even or even->odd */ + if (upwards) { + u32 *src32, *dst32; + u32 pval[4], v, v1, mask; + int i, j, w, f; + + src = (u8 *)info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL); + + mask = 0xff00ff00; + f = 0; + w = width; + if (sx & 15) { + f = 1; + w += 8; + } + if ((sx + width) & 15) + f |= 2; + w >>= 4; + for (i = height; i; i--) { + src32 = (u32 *)src; + dst32 = (u32 *)dst; + + if (f & 1) { + pval[0] = (*src32++ << 8) & mask; + pval[1] = (*src32++ << 8) & mask; + pval[2] = (*src32++ << 8) & mask; + pval[3] = (*src32++ << 8) & mask; + } else { + pval[0] = dst32[0] & mask; + pval[1] = dst32[1] & mask; + pval[2] = dst32[2] & mask; + pval[3] = dst32[3] & mask; + } + + for (j = w; j > 0; j--) { + v = *src32++; + v1 = v & mask; + *dst32++ = pval[0] | (v1 >> 8); + pval[0] = (v ^ v1) << 8; + v = *src32++; + v1 = v & mask; + *dst32++ = pval[1] | (v1 >> 8); + pval[1] = (v ^ v1) << 8; + v = *src32++; + v1 = v & mask; + *dst32++ = pval[2] | (v1 >> 8); + pval[2] = (v ^ v1) << 8; + v = *src32++; + v1 = v & mask; + *dst32++ = pval[3] | (v1 >> 8); + pval[3] = (v ^ v1) << 8; + } + + if (f & 2) { + dst32[0] = (dst32[0] & mask) | pval[0]; + dst32[1] = (dst32[1] & mask) | pval[1]; + dst32[2] = (dst32[2] & mask) | pval[2]; + dst32[3] = (dst32[3] & mask) | pval[3]; + } + + src += next_line; + dst += next_line; + } + } else { + u32 *src32, *dst32; + u32 pval[4], v, v1, mask; + int i, j, w, f; + + src = (u8 *)info->screen_base + (sy - 1) * next_line + ((sx + width + 8) & ~15) / (8 / BPL); + dst = (u8 *)info->screen_base + (dy - 1) * next_line + ((dx + width + 8) & ~15) / (8 / BPL); + + mask = 0xff00ff; + f = 0; + w = width; + if ((dx + width) & 15) + f = 1; + if (sx & 15) { + f |= 2; + w += 8; + } + w >>= 4; + for (i = height; i; i--) { + src32 = (u32 *)src; + dst32 = (u32 *)dst; + + if (f & 1) { + pval[0] = dst32[-1] & mask; + pval[1] = dst32[-2] & mask; + pval[2] = dst32[-3] & mask; + pval[3] = dst32[-4] & mask; + } else { + pval[0] = (*--src32 >> 8) & mask; + pval[1] = (*--src32 >> 8) & mask; + pval[2] = (*--src32 >> 8) & mask; + pval[3] = (*--src32 >> 8) & mask; + } + + for (j = w; j > 0; j--) { + v = *--src32; + v1 = v & mask; + *--dst32 = pval[0] | (v1 << 8); + pval[0] = (v ^ v1) >> 8; + v = *--src32; + v1 = v & mask; + *--dst32 = pval[1] | (v1 << 8); + pval[1] = (v ^ v1) >> 8; + v = *--src32; + v1 = v & mask; + *--dst32 = pval[2] | (v1 << 8); + pval[2] = (v ^ v1) >> 8; + v = *--src32; + v1 = v & mask; + *--dst32 = pval[3] | (v1 << 8); + pval[3] = (v ^ v1) >> 8; + } + + if (!(f & 2)) { + dst32[-1] = (dst32[-1] & mask) | pval[0]; + dst32[-2] = (dst32[-2] & mask) | pval[1]; + dst32[-3] = (dst32[-3] & mask) | pval[2]; + dst32[-4] = (dst32[-4] & mask) | pval[3]; + } + + src -= next_line; + dst -= next_line; + } + } + } +} + +void atafb_iplan2p8_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width) +{ + u32 *dest; + int rows, i; + u32 cval[4]; + + dest = (u32 *)(info->screen_base + sy * next_line + (sx & ~15) / (8 / BPL)); + if (sx & 15) { + u8 *dest8 = (u8 *)dest + 1; + + expand8_col2mask(color, cval); + + for (i = height; i; i--) { + fill8_col(dest8, cval); + dest8 += next_line; + } + dest += BPL / 2; + width -= 8; + } + + expand16_col2mask(color, cval); + rows = width >> 4; + if (rows) { + u32 *d = dest; + u32 off = next_line - rows * BPL * 2; + for (i = height; i; i--) { + d = fill16_col(d, rows, cval); + d = (u32 *)((long)d + off); + } + dest += rows * BPL / 2; + width &= 15; + } + + if (width) { + u8 *dest8 = (u8 *)dest; + + expand8_col2mask(color, cval); + + for (i = height; i; i--) { + fill8_col(dest8, cval); + dest8 += next_line; + } + } +} + +void atafb_iplan2p8_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor) +{ + u32 *dest; + const u16 *data16; + int rows; + u32 fgm[4], bgm[4], m; + + dest = (u32 *)(info->screen_base + dy * next_line + (dx & ~15) / (8 / BPL)); + if (dx & 15) { + fill8_2col((u8 *)dest + 1, fgcolor, bgcolor, *data++); + dest += BPL / 2; + width -= 8; + } + + if (width >= 16) { + data16 = (const u16 *)data; + expand16_2col2mask(fgcolor, bgcolor, fgm, bgm); + + for (rows = width / 16; rows; rows--) { + u16 d = *data16++; + m = d | ((u32)d << 16); + *dest++ = (m & fgm[0]) ^ bgm[0]; + *dest++ = (m & fgm[1]) ^ bgm[1]; + *dest++ = (m & fgm[2]) ^ bgm[2]; + *dest++ = (m & fgm[3]) ^ bgm[3]; + } + + data = (const u8 *)data16; + width &= 15; + } + + if (width) + fill8_2col((u8 *)dest, fgcolor, bgcolor, *data); +} + +#ifdef MODULE +MODULE_LICENSE("GPL"); + +int init_module(void) +{ + return 0; +} + +void cleanup_module(void) +{ +} +#endif /* MODULE */ + + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(atafb_iplan2p8_copyarea); +EXPORT_SYMBOL(atafb_iplan2p8_fillrect); +EXPORT_SYMBOL(atafb_iplan2p8_linefill); diff --git a/drivers/video/fbdev/atafb_mfb.c b/drivers/video/fbdev/atafb_mfb.c new file mode 100644 index 000000000000..6a352d62eecf --- /dev/null +++ b/drivers/video/fbdev/atafb_mfb.c @@ -0,0 +1,112 @@ +/* + * linux/drivers/video/mfb.c -- Low level frame buffer operations for + * monochrome + * + * Created 5 Apr 1997 by Geert Uytterhoeven + * + * 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/string.h> +#include <linux/fb.h> + +#include "atafb.h" +#include "atafb_utils.h" + + + /* + * Monochrome + */ + +void atafb_mfb_copyarea(struct fb_info *info, u_long next_line, + int sy, int sx, int dy, int dx, + int height, int width) +{ + u8 *src, *dest; + u_int rows; + + if (sx == 0 && dx == 0 && width == next_line) { + src = (u8 *)info->screen_base + sy * (width >> 3); + dest = (u8 *)info->screen_base + dy * (width >> 3); + fb_memmove(dest, src, height * (width >> 3)); + } else if (dy <= sy) { + src = (u8 *)info->screen_base + sy * next_line + (sx >> 3); + dest = (u8 *)info->screen_base + dy * next_line + (dx >> 3); + for (rows = height; rows--;) { + fb_memmove(dest, src, width >> 3); + src += next_line; + dest += next_line; + } + } else { + src = (u8 *)info->screen_base + (sy + height - 1) * next_line + (sx >> 3); + dest = (u8 *)info->screen_base + (dy + height - 1) * next_line + (dx >> 3); + for (rows = height; rows--;) { + fb_memmove(dest, src, width >> 3); + src -= next_line; + dest -= next_line; + } + } +} + +void atafb_mfb_fillrect(struct fb_info *info, u_long next_line, u32 color, + int sy, int sx, int height, int width) +{ + u8 *dest; + u_int rows; + + dest = (u8 *)info->screen_base + sy * next_line + (sx >> 3); + + if (sx == 0 && width == next_line) { + if (color) + fb_memset255(dest, height * (width >> 3)); + else + fb_memclear(dest, height * (width >> 3)); + } else { + for (rows = height; rows--; dest += next_line) { + if (color) + fb_memset255(dest, width >> 3); + else + fb_memclear_small(dest, width >> 3); + } + } +} + +void atafb_mfb_linefill(struct fb_info *info, u_long next_line, + int dy, int dx, u32 width, + const u8 *data, u32 bgcolor, u32 fgcolor) +{ + u8 *dest; + u_int rows; + + dest = (u8 *)info->screen_base + dy * next_line + (dx >> 3); + + for (rows = width / 8; rows--; /* check margins */ ) { + // use fast_memmove or fb_memmove + *dest++ = *data++; + } +} + +#ifdef MODULE +MODULE_LICENSE("GPL"); + +int init_module(void) +{ + return 0; +} + +void cleanup_module(void) +{ +} +#endif /* MODULE */ + + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(atafb_mfb_copyarea); +EXPORT_SYMBOL(atafb_mfb_fillrect); +EXPORT_SYMBOL(atafb_mfb_linefill); diff --git a/drivers/video/fbdev/atafb_utils.h b/drivers/video/fbdev/atafb_utils.h new file mode 100644 index 000000000000..ac9e19dc5057 --- /dev/null +++ b/drivers/video/fbdev/atafb_utils.h @@ -0,0 +1,400 @@ +#ifndef _VIDEO_ATAFB_UTILS_H +#define _VIDEO_ATAFB_UTILS_H + +/* ================================================================= */ +/* Utility Assembler Functions */ +/* ================================================================= */ + +/* ====================================================================== */ + +/* Those of a delicate disposition might like to skip the next couple of + * pages. + * + * These functions are drop in replacements for memmove and + * memset(_, 0, _). However their five instances add at least a kilobyte + * to the object file. You have been warned. + * + * Not a great fan of assembler for the sake of it, but I think + * that these routines are at least 10 times faster than their C + * equivalents for large blits, and that's important to the lowest level of + * a graphics driver. Question is whether some scheme with the blitter + * would be faster. I suspect not for simple text system - not much + * asynchrony. + * + * Code is very simple, just gruesome expansion. Basic strategy is to + * increase data moved/cleared at each step to 16 bytes to reduce + * instruction per data move overhead. movem might be faster still + * For more than 15 bytes, we try to align the write direction on a + * longword boundary to get maximum speed. This is even more gruesome. + * Unaligned read/write used requires 68020+ - think this is a problem? + * + * Sorry! + */ + + +/* ++roman: I've optimized Robert's original versions in some minor + * aspects, e.g. moveq instead of movel, let gcc choose the registers, + * use movem in some places... + * For other modes than 1 plane, lots of more such assembler functions + * were needed (e.g. the ones using movep or expanding color values). + */ + +/* ++andreas: more optimizations: + subl #65536,d0 replaced by clrw d0; subql #1,d0 for dbcc + addal is faster than addaw + movep is rather expensive compared to ordinary move's + some functions rewritten in C for clarity, no speed loss */ + +static inline void *fb_memclear_small(void *s, size_t count) +{ + if (!count) + return 0; + + asm volatile ("\n" + " lsr.l #1,%1 ; jcc 1f ; move.b %2,-(%0)\n" + "1: lsr.l #1,%1 ; jcc 1f ; move.w %2,-(%0)\n" + "1: lsr.l #1,%1 ; jcc 1f ; move.l %2,-(%0)\n" + "1: lsr.l #1,%1 ; jcc 1f ; move.l %2,-(%0) ; move.l %2,-(%0)\n" + "1:" + : "=a" (s), "=d" (count) + : "d" (0), "0" ((char *)s + count), "1" (count)); + asm volatile ("\n" + " subq.l #1,%1\n" + " jcs 3f\n" + " move.l %2,%%d4; move.l %2,%%d5; move.l %2,%%d6\n" + "2: movem.l %2/%%d4/%%d5/%%d6,-(%0)\n" + " dbra %1,2b\n" + "3:" + : "=a" (s), "=d" (count) + : "d" (0), "0" (s), "1" (count) + : "d4", "d5", "d6" + ); + + return 0; +} + + +static inline void *fb_memclear(void *s, size_t count) +{ + if (!count) + return 0; + + if (count < 16) { + asm volatile ("\n" + " lsr.l #1,%1 ; jcc 1f ; clr.b (%0)+\n" + "1: lsr.l #1,%1 ; jcc 1f ; clr.w (%0)+\n" + "1: lsr.l #1,%1 ; jcc 1f ; clr.l (%0)+\n" + "1: lsr.l #1,%1 ; jcc 1f ; clr.l (%0)+ ; clr.l (%0)+\n" + "1:" + : "=a" (s), "=d" (count) + : "0" (s), "1" (count)); + } else { + long tmp; + asm volatile ("\n" + " move.l %1,%2\n" + " lsr.l #1,%2 ; jcc 1f ; clr.b (%0)+ ; subq.w #1,%1\n" + " lsr.l #1,%2 ; jcs 2f\n" /* %0 increased=>bit 2 switched*/ + " clr.w (%0)+ ; subq.w #2,%1 ; jra 2f\n" + "1: lsr.l #1,%2 ; jcc 2f\n" + " clr.w (%0)+ ; subq.w #2,%1\n" + "2: move.w %1,%2; lsr.l #2,%1 ; jeq 6f\n" + " lsr.l #1,%1 ; jcc 3f ; clr.l (%0)+\n" + "3: lsr.l #1,%1 ; jcc 4f ; clr.l (%0)+ ; clr.l (%0)+\n" + "4: subq.l #1,%1 ; jcs 6f\n" + "5: clr.l (%0)+; clr.l (%0)+ ; clr.l (%0)+ ; clr.l (%0)+\n" + " dbra %1,5b ; clr.w %1; subq.l #1,%1; jcc 5b\n" + "6: move.w %2,%1; btst #1,%1 ; jeq 7f ; clr.w (%0)+\n" + "7: btst #0,%1 ; jeq 8f ; clr.b (%0)+\n" + "8:" + : "=a" (s), "=d" (count), "=d" (tmp) + : "0" (s), "1" (count)); + } + + return 0; +} + + +static inline void *fb_memset255(void *s, size_t count) +{ + if (!count) + return 0; + + asm volatile ("\n" + " lsr.l #1,%1 ; jcc 1f ; move.b %2,-(%0)\n" + "1: lsr.l #1,%1 ; jcc 1f ; move.w %2,-(%0)\n" + "1: lsr.l #1,%1 ; jcc 1f ; move.l %2,-(%0)\n" + "1: lsr.l #1,%1 ; jcc 1f ; move.l %2,-(%0) ; move.l %2,-(%0)\n" + "1:" + : "=a" (s), "=d" (count) + : "d" (-1), "0" ((char *)s+count), "1" (count)); + asm volatile ("\n" + " subq.l #1,%1 ; jcs 3f\n" + " move.l %2,%%d4; move.l %2,%%d5; move.l %2,%%d6\n" + "2: movem.l %2/%%d4/%%d5/%%d6,-(%0)\n" + " dbra %1,2b\n" + "3:" + : "=a" (s), "=d" (count) + : "d" (-1), "0" (s), "1" (count) + : "d4", "d5", "d6"); + + return 0; +} + + +static inline void *fb_memmove(void *d, const void *s, size_t count) +{ + if (d < s) { + if (count < 16) { + asm volatile ("\n" + " lsr.l #1,%2 ; jcc 1f ; move.b (%1)+,(%0)+\n" + "1: lsr.l #1,%2 ; jcc 1f ; move.w (%1)+,(%0)+\n" + "1: lsr.l #1,%2 ; jcc 1f ; move.l (%1)+,(%0)+\n" + "1: lsr.l #1,%2 ; jcc 1f ; move.l (%1)+,(%0)+ ; move.l (%1)+,(%0)+\n" + "1:" + : "=a" (d), "=a" (s), "=d" (count) + : "0" (d), "1" (s), "2" (count)); + } else { + long tmp; + asm volatile ("\n" + " move.l %0,%3\n" + " lsr.l #1,%3 ; jcc 1f ; move.b (%1)+,(%0)+ ; subqw #1,%2\n" + " lsr.l #1,%3 ; jcs 2f\n" /* %0 increased=>bit 2 switched*/ + " move.w (%1)+,(%0)+ ; subqw #2,%2 ; jra 2f\n" + "1: lsr.l #1,%3 ; jcc 2f\n" + " move.w (%1)+,(%0)+ ; subqw #2,%2\n" + "2: move.w %2,%-; lsr.l #2,%2 ; jeq 6f\n" + " lsr.l #1,%2 ; jcc 3f ; move.l (%1)+,(%0)+\n" + "3: lsr.l #1,%2 ; jcc 4f ; move.l (%1)+,(%0)+ ; move.l (%1)+,(%0)+\n" + "4: subq.l #1,%2 ; jcs 6f\n" + "5: move.l (%1)+,(%0)+; move.l (%1)+,(%0)+\n" + " move.l (%1)+,(%0)+; move.l (%1)+,(%0)+\n" + " dbra %2,5b ; clr.w %2; subq.l #1,%2; jcc 5b\n" + "6: move.w %+,%2; btst #1,%2 ; jeq 7f ; move.w (%1)+,(%0)+\n" + "7: btst #0,%2 ; jeq 8f ; move.b (%1)+,(%0)+\n" + "8:" + : "=a" (d), "=a" (s), "=d" (count), "=d" (tmp) + : "0" (d), "1" (s), "2" (count)); + } + } else { + if (count < 16) { + asm volatile ("\n" + " lsr.l #1,%2 ; jcc 1f ; move.b -(%1),-(%0)\n" + "1: lsr.l #1,%2 ; jcc 1f ; move.w -(%1),-(%0)\n" + "1: lsr.l #1,%2 ; jcc 1f ; move.l -(%1),-(%0)\n" + "1: lsr.l #1,%2 ; jcc 1f ; move.l -(%1),-(%0) ; move.l -(%1),-(%0)\n" + "1:" + : "=a" (d), "=a" (s), "=d" (count) + : "0" ((char *) d + count), "1" ((char *) s + count), "2" (count)); + } else { + long tmp; + + asm volatile ("\n" + " move.l %0,%3\n" + " lsr.l #1,%3 ; jcc 1f ; move.b -(%1),-(%0) ; subqw #1,%2\n" + " lsr.l #1,%3 ; jcs 2f\n" /* %0 increased=>bit 2 switched*/ + " move.w -(%1),-(%0) ; subqw #2,%2 ; jra 2f\n" + "1: lsr.l #1,%3 ; jcc 2f\n" + " move.w -(%1),-(%0) ; subqw #2,%2\n" + "2: move.w %2,%-; lsr.l #2,%2 ; jeq 6f\n" + " lsr.l #1,%2 ; jcc 3f ; move.l -(%1),-(%0)\n" + "3: lsr.l #1,%2 ; jcc 4f ; move.l -(%1),-(%0) ; move.l -(%1),-(%0)\n" + "4: subq.l #1,%2 ; jcs 6f\n" + "5: move.l -(%1),-(%0); move.l -(%1),-(%0)\n" + " move.l -(%1),-(%0); move.l -(%1),-(%0)\n" + " dbra %2,5b ; clr.w %2; subq.l #1,%2; jcc 5b\n" + "6: move.w %+,%2; btst #1,%2 ; jeq 7f ; move.w -(%1),-(%0)\n" + "7: btst #0,%2 ; jeq 8f ; move.b -(%1),-(%0)\n" + "8:" + : "=a" (d), "=a" (s), "=d" (count), "=d" (tmp) + : "0" ((char *) d + count), "1" ((char *) s + count), "2" (count)); + } + } + + return 0; +} + + +/* ++andreas: Simple and fast version of memmove, assumes size is + divisible by 16, suitable for moving the whole screen bitplane */ +static inline void fast_memmove(char *dst, const char *src, size_t size) +{ + if (!size) + return; + if (dst < src) + asm volatile ("\n" + "1: movem.l (%0)+,%%d0/%%d1/%%a0/%%a1\n" + " movem.l %%d0/%%d1/%%a0/%%a1,%1@\n" + " addq.l #8,%1; addq.l #8,%1\n" + " dbra %2,1b\n" + " clr.w %2; subq.l #1,%2\n" + " jcc 1b" + : "=a" (src), "=a" (dst), "=d" (size) + : "0" (src), "1" (dst), "2" (size / 16 - 1) + : "d0", "d1", "a0", "a1", "memory"); + else + asm volatile ("\n" + "1: subq.l #8,%0; subq.l #8,%0\n" + " movem.l %0@,%%d0/%%d1/%%a0/%%a1\n" + " movem.l %%d0/%%d1/%%a0/%%a1,-(%1)\n" + " dbra %2,1b\n" + " clr.w %2; subq.l #1,%2\n" + " jcc 1b" + : "=a" (src), "=a" (dst), "=d" (size) + : "0" (src + size), "1" (dst + size), "2" (size / 16 - 1) + : "d0", "d1", "a0", "a1", "memory"); +} + +#ifdef BPL + +/* + * This expands a up to 8 bit color into two longs + * for movel operations. + */ +static const u32 four2long[] = { + 0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff, + 0x00ff0000, 0x00ff00ff, 0x00ffff00, 0x00ffffff, + 0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, + 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff, +}; + +static inline void expand8_col2mask(u8 c, u32 m[]) +{ + m[0] = four2long[c & 15]; +#if BPL > 4 + m[1] = four2long[c >> 4]; +#endif +} + +static inline void expand8_2col2mask(u8 fg, u8 bg, u32 fgm[], u32 bgm[]) +{ + fgm[0] = four2long[fg & 15] ^ (bgm[0] = four2long[bg & 15]); +#if BPL > 4 + fgm[1] = four2long[fg >> 4] ^ (bgm[1] = four2long[bg >> 4]); +#endif +} + +/* + * set an 8bit value to a color + */ +static inline void fill8_col(u8 *dst, u32 m[]) +{ + u32 tmp = m[0]; + dst[0] = tmp; + dst[2] = (tmp >>= 8); +#if BPL > 2 + dst[4] = (tmp >>= 8); + dst[6] = tmp >> 8; +#endif +#if BPL > 4 + tmp = m[1]; + dst[8] = tmp; + dst[10] = (tmp >>= 8); + dst[12] = (tmp >>= 8); + dst[14] = tmp >> 8; +#endif +} + +/* + * set an 8bit value according to foreground/background color + */ +static inline void fill8_2col(u8 *dst, u8 fg, u8 bg, u32 mask) +{ + u32 fgm[2], bgm[2], tmp; + + expand8_2col2mask(fg, bg, fgm, bgm); + + mask |= mask << 8; +#if BPL > 2 + mask |= mask << 16; +#endif + tmp = (mask & fgm[0]) ^ bgm[0]; + dst[0] = tmp; + dst[2] = (tmp >>= 8); +#if BPL > 2 + dst[4] = (tmp >>= 8); + dst[6] = tmp >> 8; +#endif +#if BPL > 4 + tmp = (mask & fgm[1]) ^ bgm[1]; + dst[8] = tmp; + dst[10] = (tmp >>= 8); + dst[12] = (tmp >>= 8); + dst[14] = tmp >> 8; +#endif +} + +static const u32 two2word[] = { + 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff +}; + +static inline void expand16_col2mask(u8 c, u32 m[]) +{ + m[0] = two2word[c & 3]; +#if BPL > 2 + m[1] = two2word[(c >> 2) & 3]; +#endif +#if BPL > 4 + m[2] = two2word[(c >> 4) & 3]; + m[3] = two2word[c >> 6]; +#endif +} + +static inline void expand16_2col2mask(u8 fg, u8 bg, u32 fgm[], u32 bgm[]) +{ + bgm[0] = two2word[bg & 3]; + fgm[0] = two2word[fg & 3] ^ bgm[0]; +#if BPL > 2 + bgm[1] = two2word[(bg >> 2) & 3]; + fgm[1] = two2word[(fg >> 2) & 3] ^ bgm[1]; +#endif +#if BPL > 4 + bgm[2] = two2word[(bg >> 4) & 3]; + fgm[2] = two2word[(fg >> 4) & 3] ^ bgm[2]; + bgm[3] = two2word[bg >> 6]; + fgm[3] = two2word[fg >> 6] ^ bgm[3]; +#endif +} + +static inline u32 *fill16_col(u32 *dst, int rows, u32 m[]) +{ + while (rows) { + *dst++ = m[0]; +#if BPL > 2 + *dst++ = m[1]; +#endif +#if BPL > 4 + *dst++ = m[2]; + *dst++ = m[3]; +#endif + rows--; + } + return dst; +} + +static inline void memmove32_col(void *dst, void *src, u32 mask, u32 h, u32 bytes) +{ + u32 *s, *d, v; + + s = src; + d = dst; + do { + v = (*s++ & mask) | (*d & ~mask); + *d++ = v; +#if BPL > 2 + v = (*s++ & mask) | (*d & ~mask); + *d++ = v; +#endif +#if BPL > 4 + v = (*s++ & mask) | (*d & ~mask); + *d++ = v; + v = (*s++ & mask) | (*d & ~mask); + *d++ = v; +#endif + d = (u32 *)((u8 *)d + bytes); + s = (u32 *)((u8 *)s + bytes); + } while (--h); +} + +#endif + +#endif /* _VIDEO_ATAFB_UTILS_H */ diff --git a/drivers/video/fbdev/atmel_lcdfb.c b/drivers/video/fbdev/atmel_lcdfb.c new file mode 100644 index 000000000000..e683b6ef9594 --- /dev/null +++ b/drivers/video/fbdev/atmel_lcdfb.c @@ -0,0 +1,1453 @@ +/* + * Driver for AT91/AT32 LCD Controller + * + * Copyright (C) 2007 Atmel Corporation + * + * 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/kernel.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/backlight.h> +#include <linux/gfp.h> +#include <linux/module.h> +#include <linux/platform_data/atmel.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include <mach/cpu.h> +#include <asm/gpio.h> + +#include <video/atmel_lcdc.h> + +struct atmel_lcdfb_config { + bool have_alt_pixclock; + bool have_hozval; + bool have_intensity_bit; +}; + + /* LCD Controller info data structure, stored in device platform_data */ +struct atmel_lcdfb_info { + spinlock_t lock; + struct fb_info *info; + void __iomem *mmio; + int irq_base; + struct work_struct task; + + unsigned int smem_len; + struct platform_device *pdev; + struct clk *bus_clk; + struct clk *lcdc_clk; + + struct backlight_device *backlight; + u8 bl_power; + u8 saved_lcdcon; + + u32 pseudo_palette[16]; + bool have_intensity_bit; + + struct atmel_lcdfb_pdata pdata; + + struct atmel_lcdfb_config *config; +}; + +struct atmel_lcdfb_power_ctrl_gpio { + int gpio; + int active_low; + + struct list_head list; +}; + +#define lcdc_readl(sinfo, reg) __raw_readl((sinfo)->mmio+(reg)) +#define lcdc_writel(sinfo, reg, val) __raw_writel((val), (sinfo)->mmio+(reg)) + +/* configurable parameters */ +#define ATMEL_LCDC_CVAL_DEFAULT 0xc8 +#define ATMEL_LCDC_DMA_BURST_LEN 8 /* words */ +#define ATMEL_LCDC_FIFO_SIZE 512 /* words */ + +static struct atmel_lcdfb_config at91sam9261_config = { + .have_hozval = true, + .have_intensity_bit = true, +}; + +static struct atmel_lcdfb_config at91sam9263_config = { + .have_intensity_bit = true, +}; + +static struct atmel_lcdfb_config at91sam9g10_config = { + .have_hozval = true, +}; + +static struct atmel_lcdfb_config at91sam9g45_config = { + .have_alt_pixclock = true, +}; + +static struct atmel_lcdfb_config at91sam9g45es_config = { +}; + +static struct atmel_lcdfb_config at91sam9rl_config = { + .have_intensity_bit = true, +}; + +static struct atmel_lcdfb_config at32ap_config = { + .have_hozval = true, +}; + +static const struct platform_device_id atmel_lcdfb_devtypes[] = { + { + .name = "at91sam9261-lcdfb", + .driver_data = (unsigned long)&at91sam9261_config, + }, { + .name = "at91sam9263-lcdfb", + .driver_data = (unsigned long)&at91sam9263_config, + }, { + .name = "at91sam9g10-lcdfb", + .driver_data = (unsigned long)&at91sam9g10_config, + }, { + .name = "at91sam9g45-lcdfb", + .driver_data = (unsigned long)&at91sam9g45_config, + }, { + .name = "at91sam9g45es-lcdfb", + .driver_data = (unsigned long)&at91sam9g45es_config, + }, { + .name = "at91sam9rl-lcdfb", + .driver_data = (unsigned long)&at91sam9rl_config, + }, { + .name = "at32ap-lcdfb", + .driver_data = (unsigned long)&at32ap_config, + }, { + /* terminator */ + } +}; +MODULE_DEVICE_TABLE(platform, atmel_lcdfb_devtypes); + +static struct atmel_lcdfb_config * +atmel_lcdfb_get_config(struct platform_device *pdev) +{ + unsigned long data; + + data = platform_get_device_id(pdev)->driver_data; + + return (struct atmel_lcdfb_config *)data; +} + +#if defined(CONFIG_ARCH_AT91) +#define ATMEL_LCDFB_FBINFO_DEFAULT (FBINFO_DEFAULT \ + | FBINFO_PARTIAL_PAN_OK \ + | FBINFO_HWACCEL_YPAN) + +static inline void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, + struct fb_var_screeninfo *var, + struct fb_info *info) +{ + +} +#elif defined(CONFIG_AVR32) +#define ATMEL_LCDFB_FBINFO_DEFAULT (FBINFO_DEFAULT \ + | FBINFO_PARTIAL_PAN_OK \ + | FBINFO_HWACCEL_XPAN \ + | FBINFO_HWACCEL_YPAN) + +static void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, + struct fb_var_screeninfo *var, + struct fb_info *info) +{ + u32 dma2dcfg; + u32 pixeloff; + + pixeloff = (var->xoffset * info->var.bits_per_pixel) & 0x1f; + + dma2dcfg = (info->var.xres_virtual - info->var.xres) + * info->var.bits_per_pixel / 8; + dma2dcfg |= pixeloff << ATMEL_LCDC_PIXELOFF_OFFSET; + lcdc_writel(sinfo, ATMEL_LCDC_DMA2DCFG, dma2dcfg); + + /* Update configuration */ + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, + lcdc_readl(sinfo, ATMEL_LCDC_DMACON) + | ATMEL_LCDC_DMAUPDT); +} +#endif + +static u32 contrast_ctr = ATMEL_LCDC_PS_DIV8 + | ATMEL_LCDC_POL_POSITIVE + | ATMEL_LCDC_ENA_PWMENABLE; + +#ifdef CONFIG_BACKLIGHT_ATMEL_LCDC + +/* some bl->props field just changed */ +static int atmel_bl_update_status(struct backlight_device *bl) +{ + struct atmel_lcdfb_info *sinfo = bl_get_data(bl); + int power = sinfo->bl_power; + int brightness = bl->props.brightness; + + /* REVISIT there may be a meaningful difference between + * fb_blank and power ... there seem to be some cases + * this doesn't handle correctly. + */ + if (bl->props.fb_blank != sinfo->bl_power) + power = bl->props.fb_blank; + else if (bl->props.power != sinfo->bl_power) + power = bl->props.power; + + if (brightness < 0 && power == FB_BLANK_UNBLANK) + brightness = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); + else if (power != FB_BLANK_UNBLANK) + brightness = 0; + + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, brightness); + if (contrast_ctr & ATMEL_LCDC_POL_POSITIVE) + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, + brightness ? contrast_ctr : 0); + else + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); + + bl->props.fb_blank = bl->props.power = sinfo->bl_power = power; + + return 0; +} + +static int atmel_bl_get_brightness(struct backlight_device *bl) +{ + struct atmel_lcdfb_info *sinfo = bl_get_data(bl); + + return lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); +} + +static const struct backlight_ops atmel_lcdc_bl_ops = { + .update_status = atmel_bl_update_status, + .get_brightness = atmel_bl_get_brightness, +}; + +static void init_backlight(struct atmel_lcdfb_info *sinfo) +{ + struct backlight_properties props; + struct backlight_device *bl; + + sinfo->bl_power = FB_BLANK_UNBLANK; + + if (sinfo->backlight) + return; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 0xff; + bl = backlight_device_register("backlight", &sinfo->pdev->dev, sinfo, + &atmel_lcdc_bl_ops, &props); + if (IS_ERR(bl)) { + dev_err(&sinfo->pdev->dev, "error %ld on backlight register\n", + PTR_ERR(bl)); + return; + } + sinfo->backlight = bl; + + bl->props.power = FB_BLANK_UNBLANK; + bl->props.fb_blank = FB_BLANK_UNBLANK; + bl->props.brightness = atmel_bl_get_brightness(bl); +} + +static void exit_backlight(struct atmel_lcdfb_info *sinfo) +{ + if (!sinfo->backlight) + return; + + if (sinfo->backlight->ops) { + sinfo->backlight->props.power = FB_BLANK_POWERDOWN; + sinfo->backlight->ops->update_status(sinfo->backlight); + } + backlight_device_unregister(sinfo->backlight); +} + +#else + +static void init_backlight(struct atmel_lcdfb_info *sinfo) +{ + dev_warn(&sinfo->pdev->dev, "backlight control is not available\n"); +} + +static void exit_backlight(struct atmel_lcdfb_info *sinfo) +{ +} + +#endif + +static void init_contrast(struct atmel_lcdfb_info *sinfo) +{ + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + + /* contrast pwm can be 'inverted' */ + if (pdata->lcdcon_pol_negative) + contrast_ctr &= ~(ATMEL_LCDC_POL_POSITIVE); + + /* have some default contrast/backlight settings */ + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); + + if (pdata->lcdcon_is_backlight) + init_backlight(sinfo); +} + +static inline void atmel_lcdfb_power_control(struct atmel_lcdfb_info *sinfo, int on) +{ + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + + if (pdata->atmel_lcdfb_power_control) + pdata->atmel_lcdfb_power_control(pdata, on); +} + +static struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = { + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 1, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static unsigned long compute_hozval(struct atmel_lcdfb_info *sinfo, + unsigned long xres) +{ + unsigned long lcdcon2; + unsigned long value; + + if (!sinfo->config->have_hozval) + return xres; + + lcdcon2 = lcdc_readl(sinfo, ATMEL_LCDC_LCDCON2); + value = xres; + if ((lcdcon2 & ATMEL_LCDC_DISTYPE) != ATMEL_LCDC_DISTYPE_TFT) { + /* STN display */ + if ((lcdcon2 & ATMEL_LCDC_DISTYPE) == ATMEL_LCDC_DISTYPE_STNCOLOR) { + value *= 3; + } + if ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_4 + || ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_8 + && (lcdcon2 & ATMEL_LCDC_SCANMOD) == ATMEL_LCDC_SCANMOD_DUAL )) + value = DIV_ROUND_UP(value, 4); + else + value = DIV_ROUND_UP(value, 8); + } + + return value; +} + +static void atmel_lcdfb_stop_nowait(struct atmel_lcdfb_info *sinfo) +{ + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + + /* Turn off the LCD controller and the DMA controller */ + lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, + pdata->guard_time << ATMEL_LCDC_GUARDT_OFFSET); + + /* Wait for the LCDC core to become idle */ + while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY) + msleep(10); + + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0); +} + +static void atmel_lcdfb_stop(struct atmel_lcdfb_info *sinfo) +{ + atmel_lcdfb_stop_nowait(sinfo); + + /* Wait for DMA engine to become idle... */ + while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) + msleep(10); +} + +static void atmel_lcdfb_start(struct atmel_lcdfb_info *sinfo) +{ + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + + lcdc_writel(sinfo, ATMEL_LCDC_DMACON, pdata->default_dmacon); + lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, + (pdata->guard_time << ATMEL_LCDC_GUARDT_OFFSET) + | ATMEL_LCDC_PWR); +} + +static void atmel_lcdfb_update_dma(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct atmel_lcdfb_info *sinfo = info->par; + struct fb_fix_screeninfo *fix = &info->fix; + unsigned long dma_addr; + + dma_addr = (fix->smem_start + var->yoffset * fix->line_length + + var->xoffset * info->var.bits_per_pixel / 8); + + dma_addr &= ~3UL; + + /* Set framebuffer DMA base address and pixel offset */ + lcdc_writel(sinfo, ATMEL_LCDC_DMABADDR1, dma_addr); + + atmel_lcdfb_update_dma2d(sinfo, var, info); +} + +static inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo) +{ + struct fb_info *info = sinfo->info; + + dma_free_writecombine(info->device, info->fix.smem_len, + info->screen_base, info->fix.smem_start); +} + +/** + * atmel_lcdfb_alloc_video_memory - Allocate framebuffer memory + * @sinfo: the frame buffer to allocate memory for + * + * This function is called only from the atmel_lcdfb_probe() + * so no locking by fb_info->mm_lock around smem_len setting is needed. + */ +static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo) +{ + struct fb_info *info = sinfo->info; + struct fb_var_screeninfo *var = &info->var; + unsigned int smem_len; + + smem_len = (var->xres_virtual * var->yres_virtual + * ((var->bits_per_pixel + 7) / 8)); + info->fix.smem_len = max(smem_len, sinfo->smem_len); + + info->screen_base = dma_alloc_writecombine(info->device, info->fix.smem_len, + (dma_addr_t *)&info->fix.smem_start, GFP_KERNEL); + + if (!info->screen_base) { + return -ENOMEM; + } + + memset(info->screen_base, 0, info->fix.smem_len); + + return 0; +} + +static const struct fb_videomode *atmel_lcdfb_choose_mode(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct fb_videomode varfbmode; + const struct fb_videomode *fbmode = NULL; + + fb_var_to_videomode(&varfbmode, var); + fbmode = fb_find_nearest_mode(&varfbmode, &info->modelist); + if (fbmode) + fb_videomode_to_var(var, fbmode); + return fbmode; +} + + +/** + * atmel_lcdfb_check_var - Validates a var passed in. + * @var: frame buffer variable screen structure + * @info: frame buffer structure that represents a single frame buffer + * + * Checks to see if the hardware supports the state requested by + * var passed in. This function does not alter the hardware + * state!!! This means the data stored in struct fb_info and + * struct atmel_lcdfb_info do not change. This includes the var + * inside of struct fb_info. Do NOT change these. This function + * can be called on its own if we intent to only test a mode and + * not actually set it. The stuff in modedb.c is a example of + * this. If the var passed in is slightly off by what the + * hardware can support then we alter the var PASSED in to what + * we can do. If the hardware doesn't support mode change a + * -EINVAL will be returned by the upper layers. You don't need + * to implement this function then. If you hardware doesn't + * support changing the resolution then this function is not + * needed. In this case the driver would just provide a var that + * represents the static state the screen is in. + * + * Returns negative errno on error, or zero on success. + */ +static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct device *dev = info->device; + struct atmel_lcdfb_info *sinfo = info->par; + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + unsigned long clk_value_khz; + + clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; + + dev_dbg(dev, "%s:\n", __func__); + + if (!(var->pixclock && var->bits_per_pixel)) { + /* choose a suitable mode if possible */ + if (!atmel_lcdfb_choose_mode(var, info)) { + dev_err(dev, "needed value not specified\n"); + return -EINVAL; + } + } + + dev_dbg(dev, " resolution: %ux%u\n", var->xres, var->yres); + dev_dbg(dev, " pixclk: %lu KHz\n", PICOS2KHZ(var->pixclock)); + dev_dbg(dev, " bpp: %u\n", var->bits_per_pixel); + dev_dbg(dev, " clk: %lu KHz\n", clk_value_khz); + + if (PICOS2KHZ(var->pixclock) > clk_value_khz) { + dev_err(dev, "%lu KHz pixel clock is too fast\n", PICOS2KHZ(var->pixclock)); + return -EINVAL; + } + + /* Do not allow to have real resoulution larger than virtual */ + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + /* Force same alignment for each line */ + var->xres = (var->xres + 3) & ~3UL; + var->xres_virtual = (var->xres_virtual + 3) & ~3UL; + + var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0; + var->transp.msb_right = 0; + var->transp.offset = var->transp.length = 0; + var->xoffset = var->yoffset = 0; + + if (info->fix.smem_len) { + unsigned int smem_len = (var->xres_virtual * var->yres_virtual + * ((var->bits_per_pixel + 7) / 8)); + if (smem_len > info->fix.smem_len) { + dev_err(dev, "Frame buffer is too small (%u) for screen size (need at least %u)\n", + info->fix.smem_len, smem_len); + return -EINVAL; + } + } + + /* Saturate vertical and horizontal timings at maximum values */ + var->vsync_len = min_t(u32, var->vsync_len, + (ATMEL_LCDC_VPW >> ATMEL_LCDC_VPW_OFFSET) + 1); + var->upper_margin = min_t(u32, var->upper_margin, + ATMEL_LCDC_VBP >> ATMEL_LCDC_VBP_OFFSET); + var->lower_margin = min_t(u32, var->lower_margin, + ATMEL_LCDC_VFP); + var->right_margin = min_t(u32, var->right_margin, + (ATMEL_LCDC_HFP >> ATMEL_LCDC_HFP_OFFSET) + 1); + var->hsync_len = min_t(u32, var->hsync_len, + (ATMEL_LCDC_HPW >> ATMEL_LCDC_HPW_OFFSET) + 1); + var->left_margin = min_t(u32, var->left_margin, + ATMEL_LCDC_HBP + 1); + + /* Some parameters can't be zero */ + var->vsync_len = max_t(u32, var->vsync_len, 1); + var->right_margin = max_t(u32, var->right_margin, 1); + var->hsync_len = max_t(u32, var->hsync_len, 1); + var->left_margin = max_t(u32, var->left_margin, 1); + + switch (var->bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length + = var->bits_per_pixel; + break; + case 16: + /* Older SOCs use IBGR:555 rather than BGR:565. */ + if (sinfo->config->have_intensity_bit) + var->green.length = 5; + else + var->green.length = 6; + + if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { + /* RGB:5X5 mode */ + var->red.offset = var->green.length + 5; + var->blue.offset = 0; + } else { + /* BGR:5X5 mode */ + var->red.offset = 0; + var->blue.offset = var->green.length + 5; + } + var->green.offset = 5; + var->red.length = var->blue.length = 5; + break; + case 32: + var->transp.offset = 24; + var->transp.length = 8; + /* fall through */ + case 24: + if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { + /* RGB:888 mode */ + var->red.offset = 16; + var->blue.offset = 0; + } else { + /* BGR:888 mode */ + var->red.offset = 0; + var->blue.offset = 16; + } + var->green.offset = 8; + var->red.length = var->green.length = var->blue.length = 8; + break; + default: + dev_err(dev, "color depth %d not supported\n", + var->bits_per_pixel); + return -EINVAL; + } + + return 0; +} + +/* + * LCD reset sequence + */ +static void atmel_lcdfb_reset(struct atmel_lcdfb_info *sinfo) +{ + might_sleep(); + + atmel_lcdfb_stop(sinfo); + atmel_lcdfb_start(sinfo); +} + +/** + * atmel_lcdfb_set_par - Alters the hardware state. + * @info: frame buffer structure that represents a single frame buffer + * + * Using the fb_var_screeninfo in fb_info we set the resolution + * of the this particular framebuffer. This function alters the + * par AND the fb_fix_screeninfo stored in fb_info. It doesn't + * not alter var in fb_info since we are using that data. This + * means we depend on the data in var inside fb_info to be + * supported by the hardware. atmel_lcdfb_check_var is always called + * before atmel_lcdfb_set_par to ensure this. Again if you can't + * change the resolution you don't need this function. + * + */ +static int atmel_lcdfb_set_par(struct fb_info *info) +{ + struct atmel_lcdfb_info *sinfo = info->par; + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + unsigned long hozval_linesz; + unsigned long value; + unsigned long clk_value_khz; + unsigned long bits_per_line; + unsigned long pix_factor = 2; + + might_sleep(); + + dev_dbg(info->device, "%s:\n", __func__); + dev_dbg(info->device, " * resolution: %ux%u (%ux%u virtual)\n", + info->var.xres, info->var.yres, + info->var.xres_virtual, info->var.yres_virtual); + + atmel_lcdfb_stop_nowait(sinfo); + + if (info->var.bits_per_pixel == 1) + info->fix.visual = FB_VISUAL_MONO01; + else if (info->var.bits_per_pixel <= 8) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + bits_per_line = info->var.xres_virtual * info->var.bits_per_pixel; + info->fix.line_length = DIV_ROUND_UP(bits_per_line, 8); + + /* Re-initialize the DMA engine... */ + dev_dbg(info->device, " * update DMA engine\n"); + atmel_lcdfb_update_dma(info, &info->var); + + /* ...set frame size and burst length = 8 words (?) */ + value = (info->var.yres * info->var.xres * info->var.bits_per_pixel) / 32; + value |= ((ATMEL_LCDC_DMA_BURST_LEN - 1) << ATMEL_LCDC_BLENGTH_OFFSET); + lcdc_writel(sinfo, ATMEL_LCDC_DMAFRMCFG, value); + + /* Now, the LCDC core... */ + + /* Set pixel clock */ + if (sinfo->config->have_alt_pixclock) + pix_factor = 1; + + clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; + + value = DIV_ROUND_UP(clk_value_khz, PICOS2KHZ(info->var.pixclock)); + + if (value < pix_factor) { + dev_notice(info->device, "Bypassing pixel clock divider\n"); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, ATMEL_LCDC_BYPASS); + } else { + value = (value / pix_factor) - 1; + dev_dbg(info->device, " * programming CLKVAL = 0x%08lx\n", + value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, + value << ATMEL_LCDC_CLKVAL_OFFSET); + info->var.pixclock = + KHZ2PICOS(clk_value_khz / (pix_factor * (value + 1))); + dev_dbg(info->device, " updated pixclk: %lu KHz\n", + PICOS2KHZ(info->var.pixclock)); + } + + + /* Initialize control register 2 */ + value = pdata->default_lcdcon2; + + if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) + value |= ATMEL_LCDC_INVLINE_INVERTED; + if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) + value |= ATMEL_LCDC_INVFRAME_INVERTED; + + switch (info->var.bits_per_pixel) { + case 1: value |= ATMEL_LCDC_PIXELSIZE_1; break; + case 2: value |= ATMEL_LCDC_PIXELSIZE_2; break; + case 4: value |= ATMEL_LCDC_PIXELSIZE_4; break; + case 8: value |= ATMEL_LCDC_PIXELSIZE_8; break; + case 15: /* fall through */ + case 16: value |= ATMEL_LCDC_PIXELSIZE_16; break; + case 24: value |= ATMEL_LCDC_PIXELSIZE_24; break; + case 32: value |= ATMEL_LCDC_PIXELSIZE_32; break; + default: BUG(); break; + } + dev_dbg(info->device, " * LCDCON2 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCON2, value); + + /* Vertical timing */ + value = (info->var.vsync_len - 1) << ATMEL_LCDC_VPW_OFFSET; + value |= info->var.upper_margin << ATMEL_LCDC_VBP_OFFSET; + value |= info->var.lower_margin; + dev_dbg(info->device, " * LCDTIM1 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_TIM1, value); + + /* Horizontal timing */ + value = (info->var.right_margin - 1) << ATMEL_LCDC_HFP_OFFSET; + value |= (info->var.hsync_len - 1) << ATMEL_LCDC_HPW_OFFSET; + value |= (info->var.left_margin - 1); + dev_dbg(info->device, " * LCDTIM2 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_TIM2, value); + + /* Horizontal value (aka line size) */ + hozval_linesz = compute_hozval(sinfo, info->var.xres); + + /* Display size */ + value = (hozval_linesz - 1) << ATMEL_LCDC_HOZVAL_OFFSET; + value |= info->var.yres - 1; + dev_dbg(info->device, " * LCDFRMCFG = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDFRMCFG, value); + + /* FIFO Threshold: Use formula from data sheet */ + value = ATMEL_LCDC_FIFO_SIZE - (2 * ATMEL_LCDC_DMA_BURST_LEN + 3); + lcdc_writel(sinfo, ATMEL_LCDC_FIFO, value); + + /* Toggle LCD_MODE every frame */ + lcdc_writel(sinfo, ATMEL_LCDC_MVAL, 0); + + /* Disable all interrupts */ + lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); + /* Enable FIFO & DMA errors */ + lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); + + /* ...wait for DMA engine to become idle... */ + while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) + msleep(10); + + atmel_lcdfb_start(sinfo); + + dev_dbg(info->device, " * DONE\n"); + + return 0; +} + +static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +/** + * atmel_lcdfb_setcolreg - Optional function. Sets a color register. + * @regno: Which register in the CLUT we are programming + * @red: The red value which can be up to 16 bits wide + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + * + * Set a single color register. The values supplied have a 16 bit + * magnitude which needs to be scaled in this function for the hardware. + * Things to take into consideration are how many color registers, if + * any, are supported with the current color visual. With truecolor mode + * no color palettes are supported. Here a pseudo palette is created + * which we store the value in pseudo_palette in struct fb_info. For + * pseudocolor mode we have a limited color palette. To deal with this + * we can program what color is displayed for a particular pixel value. + * DirectColor is similar in that we can program each color field. If + * we have a static colormap we don't need to implement this function. + * + * Returns negative errno on error, or zero on success. In an + * ideal world, this would have been the case, but as it turns + * out, the other drivers return 1 on failure, so that's what + * we're going to do. + */ +static int atmel_lcdfb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct atmel_lcdfb_info *sinfo = info->par; + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + unsigned int val; + u32 *pal; + int ret = 1; + + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_PSEUDOCOLOR: + if (regno < 256) { + if (sinfo->config->have_intensity_bit) { + /* old style I+BGR:555 */ + val = ((red >> 11) & 0x001f); + val |= ((green >> 6) & 0x03e0); + val |= ((blue >> 1) & 0x7c00); + + /* + * TODO: intensity bit. Maybe something like + * ~(red[10] ^ green[10] ^ blue[10]) & 1 + */ + } else { + /* new style BGR:565 / RGB:565 */ + if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { + val = ((blue >> 11) & 0x001f); + val |= ((red >> 0) & 0xf800); + } else { + val = ((red >> 11) & 0x001f); + val |= ((blue >> 0) & 0xf800); + } + + val |= ((green >> 5) & 0x07e0); + } + + lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val); + ret = 0; + } + break; + + case FB_VISUAL_MONO01: + if (regno < 2) { + val = (regno == 0) ? 0x00 : 0x1F; + lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val); + ret = 0; + } + break; + + } + + return ret; +} + +static int atmel_lcdfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + dev_dbg(info->device, "%s\n", __func__); + + atmel_lcdfb_update_dma(info, var); + + return 0; +} + +static int atmel_lcdfb_blank(int blank_mode, struct fb_info *info) +{ + struct atmel_lcdfb_info *sinfo = info->par; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + atmel_lcdfb_start(sinfo); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + break; + case FB_BLANK_POWERDOWN: + atmel_lcdfb_stop(sinfo); + break; + default: + return -EINVAL; + } + + /* let fbcon do a soft blank for us */ + return ((blank_mode == FB_BLANK_NORMAL) ? 1 : 0); +} + +static struct fb_ops atmel_lcdfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = atmel_lcdfb_check_var, + .fb_set_par = atmel_lcdfb_set_par, + .fb_setcolreg = atmel_lcdfb_setcolreg, + .fb_blank = atmel_lcdfb_blank, + .fb_pan_display = atmel_lcdfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static irqreturn_t atmel_lcdfb_interrupt(int irq, void *dev_id) +{ + struct fb_info *info = dev_id; + struct atmel_lcdfb_info *sinfo = info->par; + u32 status; + + status = lcdc_readl(sinfo, ATMEL_LCDC_ISR); + if (status & ATMEL_LCDC_UFLWI) { + dev_warn(info->device, "FIFO underflow %#x\n", status); + /* reset DMA and FIFO to avoid screen shifting */ + schedule_work(&sinfo->task); + } + lcdc_writel(sinfo, ATMEL_LCDC_ICR, status); + return IRQ_HANDLED; +} + +/* + * LCD controller task (to reset the LCD) + */ +static void atmel_lcdfb_task(struct work_struct *work) +{ + struct atmel_lcdfb_info *sinfo = + container_of(work, struct atmel_lcdfb_info, task); + + atmel_lcdfb_reset(sinfo); +} + +static int __init atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info *sinfo) +{ + struct fb_info *info = sinfo->info; + int ret = 0; + + info->var.activate |= FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW; + + dev_info(info->device, + "%luKiB frame buffer at %08lx (mapped at %p)\n", + (unsigned long)info->fix.smem_len / 1024, + (unsigned long)info->fix.smem_start, + info->screen_base); + + /* Allocate colormap */ + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret < 0) + dev_err(info->device, "Alloc color map failed\n"); + + return ret; +} + +static void atmel_lcdfb_start_clock(struct atmel_lcdfb_info *sinfo) +{ + clk_prepare_enable(sinfo->bus_clk); + clk_prepare_enable(sinfo->lcdc_clk); +} + +static void atmel_lcdfb_stop_clock(struct atmel_lcdfb_info *sinfo) +{ + clk_disable_unprepare(sinfo->bus_clk); + clk_disable_unprepare(sinfo->lcdc_clk); +} + +#ifdef CONFIG_OF +static const struct of_device_id atmel_lcdfb_dt_ids[] = { + { .compatible = "atmel,at91sam9261-lcdc" , .data = &at91sam9261_config, }, + { .compatible = "atmel,at91sam9263-lcdc" , .data = &at91sam9263_config, }, + { .compatible = "atmel,at91sam9g10-lcdc" , .data = &at91sam9g10_config, }, + { .compatible = "atmel,at91sam9g45-lcdc" , .data = &at91sam9g45_config, }, + { .compatible = "atmel,at91sam9g45es-lcdc" , .data = &at91sam9g45es_config, }, + { .compatible = "atmel,at91sam9rl-lcdc" , .data = &at91sam9rl_config, }, + { .compatible = "atmel,at32ap-lcdc" , .data = &at32ap_config, }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_lcdfb_dt_ids); + +static const char *atmel_lcdfb_wiring_modes[] = { + [ATMEL_LCDC_WIRING_BGR] = "BRG", + [ATMEL_LCDC_WIRING_RGB] = "RGB", +}; + +const int atmel_lcdfb_get_of_wiring_modes(struct device_node *np) +{ + const char *mode; + int err, i; + + err = of_property_read_string(np, "atmel,lcd-wiring-mode", &mode); + if (err < 0) + return ATMEL_LCDC_WIRING_BGR; + + for (i = 0; i < ARRAY_SIZE(atmel_lcdfb_wiring_modes); i++) + if (!strcasecmp(mode, atmel_lcdfb_wiring_modes[i])) + return i; + + return -ENODEV; +} + +static void atmel_lcdfb_power_control_gpio(struct atmel_lcdfb_pdata *pdata, int on) +{ + struct atmel_lcdfb_power_ctrl_gpio *og; + + list_for_each_entry(og, &pdata->pwr_gpios, list) + gpio_set_value(og->gpio, on); +} + +static int atmel_lcdfb_of_init(struct atmel_lcdfb_info *sinfo) +{ + struct fb_info *info = sinfo->info; + struct atmel_lcdfb_pdata *pdata = &sinfo->pdata; + struct fb_var_screeninfo *var = &info->var; + struct device *dev = &sinfo->pdev->dev; + struct device_node *np =dev->of_node; + struct device_node *display_np; + struct device_node *timings_np; + struct display_timings *timings; + enum of_gpio_flags flags; + struct atmel_lcdfb_power_ctrl_gpio *og; + bool is_gpio_power = false; + int ret = -ENOENT; + int i, gpio; + + sinfo->config = (struct atmel_lcdfb_config*) + of_match_device(atmel_lcdfb_dt_ids, dev)->data; + + display_np = of_parse_phandle(np, "display", 0); + if (!display_np) { + dev_err(dev, "failed to find display phandle\n"); + return -ENOENT; + } + + ret = of_property_read_u32(display_np, "bits-per-pixel", &var->bits_per_pixel); + if (ret < 0) { + dev_err(dev, "failed to get property bits-per-pixel\n"); + goto put_display_node; + } + + ret = of_property_read_u32(display_np, "atmel,guard-time", &pdata->guard_time); + if (ret < 0) { + dev_err(dev, "failed to get property atmel,guard-time\n"); + goto put_display_node; + } + + ret = of_property_read_u32(display_np, "atmel,lcdcon2", &pdata->default_lcdcon2); + if (ret < 0) { + dev_err(dev, "failed to get property atmel,lcdcon2\n"); + goto put_display_node; + } + + ret = of_property_read_u32(display_np, "atmel,dmacon", &pdata->default_dmacon); + if (ret < 0) { + dev_err(dev, "failed to get property bits-per-pixel\n"); + goto put_display_node; + } + + ret = -ENOMEM; + for (i = 0; i < of_gpio_named_count(display_np, "atmel,power-control-gpio"); i++) { + gpio = of_get_named_gpio_flags(display_np, "atmel,power-control-gpio", + i, &flags); + if (gpio < 0) + continue; + + og = devm_kzalloc(dev, sizeof(*og), GFP_KERNEL); + if (!og) + goto put_display_node; + + og->gpio = gpio; + og->active_low = flags & OF_GPIO_ACTIVE_LOW; + is_gpio_power = true; + ret = devm_gpio_request(dev, gpio, "lcd-power-control-gpio"); + if (ret) { + dev_err(dev, "request gpio %d failed\n", gpio); + goto put_display_node; + } + + ret = gpio_direction_output(gpio, og->active_low); + if (ret) { + dev_err(dev, "set direction output gpio %d failed\n", gpio); + goto put_display_node; + } + } + + if (is_gpio_power) + pdata->atmel_lcdfb_power_control = atmel_lcdfb_power_control_gpio; + + ret = atmel_lcdfb_get_of_wiring_modes(display_np); + if (ret < 0) { + dev_err(dev, "invalid atmel,lcd-wiring-mode\n"); + goto put_display_node; + } + pdata->lcd_wiring_mode = ret; + + pdata->lcdcon_is_backlight = of_property_read_bool(display_np, "atmel,lcdcon-backlight"); + + timings = of_get_display_timings(display_np); + if (!timings) { + dev_err(dev, "failed to get display timings\n"); + goto put_display_node; + } + + timings_np = of_find_node_by_name(display_np, "display-timings"); + if (!timings_np) { + dev_err(dev, "failed to find display-timings node\n"); + goto put_display_node; + } + + for (i = 0; i < of_get_child_count(timings_np); i++) { + struct videomode vm; + struct fb_videomode fb_vm; + + ret = videomode_from_timings(timings, &vm, i); + if (ret < 0) + goto put_timings_node; + ret = fb_videomode_from_videomode(&vm, &fb_vm); + if (ret < 0) + goto put_timings_node; + + fb_add_videomode(&fb_vm, &info->modelist); + } + + return 0; + +put_timings_node: + of_node_put(timings_np); +put_display_node: + of_node_put(display_np); + return ret; +} +#else +static int atmel_lcdfb_of_init(struct atmel_lcdfb_info *sinfo) +{ + return 0; +} +#endif + +static int __init atmel_lcdfb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fb_info *info; + struct atmel_lcdfb_info *sinfo; + struct atmel_lcdfb_pdata *pdata = NULL; + struct resource *regs = NULL; + struct resource *map = NULL; + struct fb_modelist *modelist; + int ret; + + dev_dbg(dev, "%s BEGIN\n", __func__); + + ret = -ENOMEM; + info = framebuffer_alloc(sizeof(struct atmel_lcdfb_info), dev); + if (!info) { + dev_err(dev, "cannot allocate memory\n"); + goto out; + } + + sinfo = info->par; + sinfo->pdev = pdev; + sinfo->info = info; + + INIT_LIST_HEAD(&info->modelist); + + if (pdev->dev.of_node) { + ret = atmel_lcdfb_of_init(sinfo); + if (ret) + goto free_info; + } else if (dev_get_platdata(dev)) { + struct fb_monspecs *monspecs; + int i; + + pdata = dev_get_platdata(dev); + monspecs = pdata->default_monspecs; + sinfo->pdata = *pdata; + + for (i = 0; i < monspecs->modedb_len; i++) + fb_add_videomode(&monspecs->modedb[i], &info->modelist); + + sinfo->config = atmel_lcdfb_get_config(pdev); + + info->var.bits_per_pixel = pdata->default_bpp ? pdata->default_bpp : 16; + memcpy(&info->monspecs, pdata->default_monspecs, sizeof(info->monspecs)); + } else { + dev_err(dev, "cannot get default configuration\n"); + goto free_info; + } + + if (!sinfo->config) + goto free_info; + + info->flags = ATMEL_LCDFB_FBINFO_DEFAULT; + info->pseudo_palette = sinfo->pseudo_palette; + info->fbops = &atmel_lcdfb_ops; + + info->fix = atmel_lcdfb_fix; + strcpy(info->fix.id, sinfo->pdev->name); + + /* Enable LCDC Clocks */ + sinfo->bus_clk = clk_get(dev, "hclk"); + if (IS_ERR(sinfo->bus_clk)) { + ret = PTR_ERR(sinfo->bus_clk); + goto free_info; + } + sinfo->lcdc_clk = clk_get(dev, "lcdc_clk"); + if (IS_ERR(sinfo->lcdc_clk)) { + ret = PTR_ERR(sinfo->lcdc_clk); + goto put_bus_clk; + } + atmel_lcdfb_start_clock(sinfo); + + modelist = list_first_entry(&info->modelist, + struct fb_modelist, list); + fb_videomode_to_var(&info->var, &modelist->mode); + + atmel_lcdfb_check_var(&info->var, info); + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_err(dev, "resources unusable\n"); + ret = -ENXIO; + goto stop_clk; + } + + sinfo->irq_base = platform_get_irq(pdev, 0); + if (sinfo->irq_base < 0) { + dev_err(dev, "unable to get irq\n"); + ret = sinfo->irq_base; + goto stop_clk; + } + + /* Initialize video memory */ + map = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (map) { + /* use a pre-allocated memory buffer */ + info->fix.smem_start = map->start; + info->fix.smem_len = resource_size(map); + if (!request_mem_region(info->fix.smem_start, + info->fix.smem_len, pdev->name)) { + ret = -EBUSY; + goto stop_clk; + } + + info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); + if (!info->screen_base) { + ret = -ENOMEM; + goto release_intmem; + } + + /* + * Don't clear the framebuffer -- someone may have set + * up a splash image. + */ + } else { + /* allocate memory buffer */ + ret = atmel_lcdfb_alloc_video_memory(sinfo); + if (ret < 0) { + dev_err(dev, "cannot allocate framebuffer: %d\n", ret); + goto stop_clk; + } + } + + /* LCDC registers */ + info->fix.mmio_start = regs->start; + info->fix.mmio_len = resource_size(regs); + + if (!request_mem_region(info->fix.mmio_start, + info->fix.mmio_len, pdev->name)) { + ret = -EBUSY; + goto free_fb; + } + + sinfo->mmio = ioremap(info->fix.mmio_start, info->fix.mmio_len); + if (!sinfo->mmio) { + dev_err(dev, "cannot map LCDC registers\n"); + ret = -ENOMEM; + goto release_mem; + } + + /* Initialize PWM for contrast or backlight ("off") */ + init_contrast(sinfo); + + /* interrupt */ + ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info); + if (ret) { + dev_err(dev, "request_irq failed: %d\n", ret); + goto unmap_mmio; + } + + /* Some operations on the LCDC might sleep and + * require a preemptible task context */ + INIT_WORK(&sinfo->task, atmel_lcdfb_task); + + ret = atmel_lcdfb_init_fbinfo(sinfo); + if (ret < 0) { + dev_err(dev, "init fbinfo failed: %d\n", ret); + goto unregister_irqs; + } + + ret = atmel_lcdfb_set_par(info); + if (ret < 0) { + dev_err(dev, "set par failed: %d\n", ret); + goto unregister_irqs; + } + + dev_set_drvdata(dev, info); + + /* + * Tell the world that we're ready to go + */ + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(dev, "failed to register framebuffer device: %d\n", ret); + goto reset_drvdata; + } + + /* Power up the LCDC screen */ + atmel_lcdfb_power_control(sinfo, 1); + + dev_info(dev, "fb%d: Atmel LCDC at 0x%08lx (mapped at %p), irq %d\n", + info->node, info->fix.mmio_start, sinfo->mmio, sinfo->irq_base); + + return 0; + +reset_drvdata: + dev_set_drvdata(dev, NULL); + fb_dealloc_cmap(&info->cmap); +unregister_irqs: + cancel_work_sync(&sinfo->task); + free_irq(sinfo->irq_base, info); +unmap_mmio: + exit_backlight(sinfo); + iounmap(sinfo->mmio); +release_mem: + release_mem_region(info->fix.mmio_start, info->fix.mmio_len); +free_fb: + if (map) + iounmap(info->screen_base); + else + atmel_lcdfb_free_video_memory(sinfo); + +release_intmem: + if (map) + release_mem_region(info->fix.smem_start, info->fix.smem_len); +stop_clk: + atmel_lcdfb_stop_clock(sinfo); + clk_put(sinfo->lcdc_clk); +put_bus_clk: + clk_put(sinfo->bus_clk); +free_info: + framebuffer_release(info); +out: + dev_dbg(dev, "%s FAILED\n", __func__); + return ret; +} + +static int __exit atmel_lcdfb_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fb_info *info = dev_get_drvdata(dev); + struct atmel_lcdfb_info *sinfo; + struct atmel_lcdfb_pdata *pdata; + + if (!info || !info->par) + return 0; + sinfo = info->par; + pdata = &sinfo->pdata; + + cancel_work_sync(&sinfo->task); + exit_backlight(sinfo); + atmel_lcdfb_power_control(sinfo, 0); + unregister_framebuffer(info); + atmel_lcdfb_stop_clock(sinfo); + clk_put(sinfo->lcdc_clk); + clk_put(sinfo->bus_clk); + fb_dealloc_cmap(&info->cmap); + free_irq(sinfo->irq_base, info); + iounmap(sinfo->mmio); + release_mem_region(info->fix.mmio_start, info->fix.mmio_len); + if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) { + iounmap(info->screen_base); + release_mem_region(info->fix.smem_start, info->fix.smem_len); + } else { + atmel_lcdfb_free_video_memory(sinfo); + } + + framebuffer_release(info); + + return 0; +} + +#ifdef CONFIG_PM + +static int atmel_lcdfb_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct atmel_lcdfb_info *sinfo = info->par; + + /* + * We don't want to handle interrupts while the clock is + * stopped. It may take forever. + */ + lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); + + sinfo->saved_lcdcon = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_CTR); + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, 0); + atmel_lcdfb_power_control(sinfo, 0); + atmel_lcdfb_stop(sinfo); + atmel_lcdfb_stop_clock(sinfo); + + return 0; +} + +static int atmel_lcdfb_resume(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct atmel_lcdfb_info *sinfo = info->par; + + atmel_lcdfb_start_clock(sinfo); + atmel_lcdfb_start(sinfo); + atmel_lcdfb_power_control(sinfo, 1); + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, sinfo->saved_lcdcon); + + /* Enable FIFO & DMA errors */ + lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI + | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI); + + return 0; +} + +#else +#define atmel_lcdfb_suspend NULL +#define atmel_lcdfb_resume NULL +#endif + +static struct platform_driver atmel_lcdfb_driver = { + .remove = __exit_p(atmel_lcdfb_remove), + .suspend = atmel_lcdfb_suspend, + .resume = atmel_lcdfb_resume, + .id_table = atmel_lcdfb_devtypes, + .driver = { + .name = "atmel_lcdfb", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(atmel_lcdfb_dt_ids), + }, +}; + +module_platform_driver_probe(atmel_lcdfb_driver, atmel_lcdfb_probe); + +MODULE_DESCRIPTION("AT91/AT32 LCD Controller framebuffer driver"); +MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/aty/Makefile b/drivers/video/fbdev/aty/Makefile new file mode 100644 index 000000000000..a6cc0e9ec790 --- /dev/null +++ b/drivers/video/fbdev/aty/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_FB_ATY) += atyfb.o +obj-$(CONFIG_FB_ATY128) += aty128fb.o +obj-$(CONFIG_FB_RADEON) += radeonfb.o + +atyfb-y := atyfb_base.o mach64_accel.o mach64_cursor.o +atyfb-$(CONFIG_FB_ATY_GX) += mach64_gx.o +atyfb-$(CONFIG_FB_ATY_CT) += mach64_ct.o + +atyfb-objs := $(atyfb-y) + +radeonfb-y := radeon_base.o radeon_pm.o radeon_monitor.o radeon_accel.o +radeonfb-$(CONFIG_FB_RADEON_I2C) += radeon_i2c.o +radeonfb-$(CONFIG_FB_RADEON_BACKLIGHT) += radeon_backlight.o +radeonfb-objs := $(radeonfb-y) + diff --git a/drivers/video/fbdev/aty/ati_ids.h b/drivers/video/fbdev/aty/ati_ids.h new file mode 100644 index 000000000000..3e9d28bcd9f8 --- /dev/null +++ b/drivers/video/fbdev/aty/ati_ids.h @@ -0,0 +1,214 @@ +/* + * ATI PCI IDs from XFree86, kept here to make sync'ing with + * XFree much simpler. Currently, this list is only used by + * radeonfb + */ + +#define PCI_CHIP_RV380_3150 0x3150 +#define PCI_CHIP_RV380_3151 0x3151 +#define PCI_CHIP_RV380_3152 0x3152 +#define PCI_CHIP_RV380_3153 0x3153 +#define PCI_CHIP_RV380_3154 0x3154 +#define PCI_CHIP_RV380_3156 0x3156 +#define PCI_CHIP_RV380_3E50 0x3E50 +#define PCI_CHIP_RV380_3E51 0x3E51 +#define PCI_CHIP_RV380_3E52 0x3E52 +#define PCI_CHIP_RV380_3E53 0x3E53 +#define PCI_CHIP_RV380_3E54 0x3E54 +#define PCI_CHIP_RV380_3E56 0x3E56 +#define PCI_CHIP_RS100_4136 0x4136 +#define PCI_CHIP_RS200_4137 0x4137 +#define PCI_CHIP_R300_AD 0x4144 +#define PCI_CHIP_R300_AE 0x4145 +#define PCI_CHIP_R300_AF 0x4146 +#define PCI_CHIP_R300_AG 0x4147 +#define PCI_CHIP_R350_AH 0x4148 +#define PCI_CHIP_R350_AI 0x4149 +#define PCI_CHIP_R350_AJ 0x414A +#define PCI_CHIP_R350_AK 0x414B +#define PCI_CHIP_RV350_AP 0x4150 +#define PCI_CHIP_RV350_AQ 0x4151 +#define PCI_CHIP_RV360_AR 0x4152 +#define PCI_CHIP_RV350_AS 0x4153 +#define PCI_CHIP_RV350_AT 0x4154 +#define PCI_CHIP_RV350_AV 0x4156 +#define PCI_CHIP_MACH32 0x4158 +#define PCI_CHIP_RS250_4237 0x4237 +#define PCI_CHIP_R200_BB 0x4242 +#define PCI_CHIP_R200_BC 0x4243 +#define PCI_CHIP_RS100_4336 0x4336 +#define PCI_CHIP_RS200_4337 0x4337 +#define PCI_CHIP_MACH64CT 0x4354 +#define PCI_CHIP_MACH64CX 0x4358 +#define PCI_CHIP_RS250_4437 0x4437 +#define PCI_CHIP_MACH64ET 0x4554 +#define PCI_CHIP_MACH64GB 0x4742 +#define PCI_CHIP_MACH64GD 0x4744 +#define PCI_CHIP_MACH64GI 0x4749 +#define PCI_CHIP_MACH64GL 0x474C +#define PCI_CHIP_MACH64GM 0x474D +#define PCI_CHIP_MACH64GN 0x474E +#define PCI_CHIP_MACH64GO 0x474F +#define PCI_CHIP_MACH64GP 0x4750 +#define PCI_CHIP_MACH64GQ 0x4751 +#define PCI_CHIP_MACH64GR 0x4752 +#define PCI_CHIP_MACH64GS 0x4753 +#define PCI_CHIP_MACH64GT 0x4754 +#define PCI_CHIP_MACH64GU 0x4755 +#define PCI_CHIP_MACH64GV 0x4756 +#define PCI_CHIP_MACH64GW 0x4757 +#define PCI_CHIP_MACH64GX 0x4758 +#define PCI_CHIP_MACH64GY 0x4759 +#define PCI_CHIP_MACH64GZ 0x475A +#define PCI_CHIP_RV250_Id 0x4964 +#define PCI_CHIP_RV250_Ie 0x4965 +#define PCI_CHIP_RV250_If 0x4966 +#define PCI_CHIP_RV250_Ig 0x4967 +#define PCI_CHIP_R420_JH 0x4A48 +#define PCI_CHIP_R420_JI 0x4A49 +#define PCI_CHIP_R420_JJ 0x4A4A +#define PCI_CHIP_R420_JK 0x4A4B +#define PCI_CHIP_R420_JL 0x4A4C +#define PCI_CHIP_R420_JM 0x4A4D +#define PCI_CHIP_R420_JN 0x4A4E +#define PCI_CHIP_R420_JP 0x4A50 +#define PCI_CHIP_MACH64LB 0x4C42 +#define PCI_CHIP_MACH64LD 0x4C44 +#define PCI_CHIP_RAGE128LE 0x4C45 +#define PCI_CHIP_RAGE128LF 0x4C46 +#define PCI_CHIP_MACH64LG 0x4C47 +#define PCI_CHIP_MACH64LI 0x4C49 +#define PCI_CHIP_MACH64LM 0x4C4D +#define PCI_CHIP_MACH64LN 0x4C4E +#define PCI_CHIP_MACH64LP 0x4C50 +#define PCI_CHIP_MACH64LQ 0x4C51 +#define PCI_CHIP_MACH64LR 0x4C52 +#define PCI_CHIP_MACH64LS 0x4C53 +#define PCI_CHIP_MACH64LT 0x4C54 +#define PCI_CHIP_RADEON_LW 0x4C57 +#define PCI_CHIP_RADEON_LX 0x4C58 +#define PCI_CHIP_RADEON_LY 0x4C59 +#define PCI_CHIP_RADEON_LZ 0x4C5A +#define PCI_CHIP_RV250_Ld 0x4C64 +#define PCI_CHIP_RV250_Le 0x4C65 +#define PCI_CHIP_RV250_Lf 0x4C66 +#define PCI_CHIP_RV250_Lg 0x4C67 +#define PCI_CHIP_RV250_Ln 0x4C6E +#define PCI_CHIP_RAGE128MF 0x4D46 +#define PCI_CHIP_RAGE128ML 0x4D4C +#define PCI_CHIP_R300_ND 0x4E44 +#define PCI_CHIP_R300_NE 0x4E45 +#define PCI_CHIP_R300_NF 0x4E46 +#define PCI_CHIP_R300_NG 0x4E47 +#define PCI_CHIP_R350_NH 0x4E48 +#define PCI_CHIP_R350_NI 0x4E49 +#define PCI_CHIP_R360_NJ 0x4E4A +#define PCI_CHIP_R350_NK 0x4E4B +#define PCI_CHIP_RV350_NP 0x4E50 +#define PCI_CHIP_RV350_NQ 0x4E51 +#define PCI_CHIP_RV350_NR 0x4E52 +#define PCI_CHIP_RV350_NS 0x4E53 +#define PCI_CHIP_RV350_NT 0x4E54 +#define PCI_CHIP_RV350_NV 0x4E56 +#define PCI_CHIP_RAGE128PA 0x5041 +#define PCI_CHIP_RAGE128PB 0x5042 +#define PCI_CHIP_RAGE128PC 0x5043 +#define PCI_CHIP_RAGE128PD 0x5044 +#define PCI_CHIP_RAGE128PE 0x5045 +#define PCI_CHIP_RAGE128PF 0x5046 +#define PCI_CHIP_RAGE128PG 0x5047 +#define PCI_CHIP_RAGE128PH 0x5048 +#define PCI_CHIP_RAGE128PI 0x5049 +#define PCI_CHIP_RAGE128PJ 0x504A +#define PCI_CHIP_RAGE128PK 0x504B +#define PCI_CHIP_RAGE128PL 0x504C +#define PCI_CHIP_RAGE128PM 0x504D +#define PCI_CHIP_RAGE128PN 0x504E +#define PCI_CHIP_RAGE128PO 0x504F +#define PCI_CHIP_RAGE128PP 0x5050 +#define PCI_CHIP_RAGE128PQ 0x5051 +#define PCI_CHIP_RAGE128PR 0x5052 +#define PCI_CHIP_RAGE128PS 0x5053 +#define PCI_CHIP_RAGE128PT 0x5054 +#define PCI_CHIP_RAGE128PU 0x5055 +#define PCI_CHIP_RAGE128PV 0x5056 +#define PCI_CHIP_RAGE128PW 0x5057 +#define PCI_CHIP_RAGE128PX 0x5058 +#define PCI_CHIP_RADEON_QD 0x5144 +#define PCI_CHIP_RADEON_QE 0x5145 +#define PCI_CHIP_RADEON_QF 0x5146 +#define PCI_CHIP_RADEON_QG 0x5147 +#define PCI_CHIP_R200_QH 0x5148 +#define PCI_CHIP_R200_QI 0x5149 +#define PCI_CHIP_R200_QJ 0x514A +#define PCI_CHIP_R200_QK 0x514B +#define PCI_CHIP_R200_QL 0x514C +#define PCI_CHIP_R200_QM 0x514D +#define PCI_CHIP_R200_QN 0x514E +#define PCI_CHIP_R200_QO 0x514F +#define PCI_CHIP_RV200_QW 0x5157 +#define PCI_CHIP_RV200_QX 0x5158 +#define PCI_CHIP_RV100_QY 0x5159 +#define PCI_CHIP_RV100_QZ 0x515A +#define PCI_CHIP_RN50 0x515E +#define PCI_CHIP_RAGE128RE 0x5245 +#define PCI_CHIP_RAGE128RF 0x5246 +#define PCI_CHIP_RAGE128RG 0x5247 +#define PCI_CHIP_RAGE128RK 0x524B +#define PCI_CHIP_RAGE128RL 0x524C +#define PCI_CHIP_RAGE128SE 0x5345 +#define PCI_CHIP_RAGE128SF 0x5346 +#define PCI_CHIP_RAGE128SG 0x5347 +#define PCI_CHIP_RAGE128SH 0x5348 +#define PCI_CHIP_RAGE128SK 0x534B +#define PCI_CHIP_RAGE128SL 0x534C +#define PCI_CHIP_RAGE128SM 0x534D +#define PCI_CHIP_RAGE128SN 0x534E +#define PCI_CHIP_RAGE128TF 0x5446 +#define PCI_CHIP_RAGE128TL 0x544C +#define PCI_CHIP_RAGE128TR 0x5452 +#define PCI_CHIP_RAGE128TS 0x5453 +#define PCI_CHIP_RAGE128TT 0x5454 +#define PCI_CHIP_RAGE128TU 0x5455 +#define PCI_CHIP_RV370_5460 0x5460 +#define PCI_CHIP_RV370_5461 0x5461 +#define PCI_CHIP_RV370_5462 0x5462 +#define PCI_CHIP_RV370_5463 0x5463 +#define PCI_CHIP_RV370_5464 0x5464 +#define PCI_CHIP_RV370_5465 0x5465 +#define PCI_CHIP_RV370_5466 0x5466 +#define PCI_CHIP_RV370_5467 0x5467 +#define PCI_CHIP_R423_UH 0x5548 +#define PCI_CHIP_R423_UI 0x5549 +#define PCI_CHIP_R423_UJ 0x554A +#define PCI_CHIP_R423_UK 0x554B +#define PCI_CHIP_R423_UQ 0x5551 +#define PCI_CHIP_R423_UR 0x5552 +#define PCI_CHIP_R423_UT 0x5554 +#define PCI_CHIP_MACH64VT 0x5654 +#define PCI_CHIP_MACH64VU 0x5655 +#define PCI_CHIP_MACH64VV 0x5656 +#define PCI_CHIP_RC410_5A62 0x5A62 +#define PCI_CHIP_RS300_5834 0x5834 +#define PCI_CHIP_RS300_5835 0x5835 +#define PCI_CHIP_RS300_5836 0x5836 +#define PCI_CHIP_RS300_5837 0x5837 +#define PCI_CHIP_RS480_5955 0x5955 +#define PCI_CHIP_RV280_5960 0x5960 +#define PCI_CHIP_RV280_5961 0x5961 +#define PCI_CHIP_RV280_5962 0x5962 +#define PCI_CHIP_RV280_5964 0x5964 +#define PCI_CHIP_RS482_5975 0x5975 +#define PCI_CHIP_RV370_5B60 0x5B60 +#define PCI_CHIP_RV370_5B61 0x5B61 +#define PCI_CHIP_RV370_5B62 0x5B62 +#define PCI_CHIP_RV370_5B63 0x5B63 +#define PCI_CHIP_RV370_5B64 0x5B64 +#define PCI_CHIP_RV370_5B65 0x5B65 +#define PCI_CHIP_RV370_5B66 0x5B66 +#define PCI_CHIP_RV370_5B67 0x5B67 +#define PCI_CHIP_RV280_5C61 0x5C61 +#define PCI_CHIP_RV280_5C63 0x5C63 +#define PCI_CHIP_R423_5D57 0x5D57 +#define PCI_CHIP_RS350_7834 0x7834 +#define PCI_CHIP_RS350_7835 0x7835 diff --git a/drivers/video/fbdev/aty/aty128fb.c b/drivers/video/fbdev/aty/aty128fb.c new file mode 100644 index 000000000000..52108be69e77 --- /dev/null +++ b/drivers/video/fbdev/aty/aty128fb.c @@ -0,0 +1,2591 @@ +/* $Id: aty128fb.c,v 1.1.1.1.36.1 1999/12/11 09:03:05 Exp $ + * linux/drivers/video/aty128fb.c -- Frame buffer device for ATI Rage128 + * + * Copyright (C) 1999-2003, Brad Douglas <brad@neruo.com> + * Copyright (C) 1999, Anthony Tong <atong@uiuc.edu> + * + * Ani Joshi / Jeff Garzik + * - Code cleanup + * + * Michel Danzer <michdaen@iiic.ethz.ch> + * - 15/16 bit cleanup + * - fix panning + * + * Benjamin Herrenschmidt + * - pmac-specific PM stuff + * - various fixes & cleanups + * + * Andreas Hundt <andi@convergence.de> + * - FB_ACTIVATE fixes + * + * Paul Mackerras <paulus@samba.org> + * - Convert to new framebuffer API, + * fix colormap setting at 16 bits/pixel (565) + * + * Paul Mundt + * - PCI hotplug + * + * Jon Smirl <jonsmirl@yahoo.com> + * - PCI ID update + * - replace ROM BIOS search + * + * Based off of Geert's atyfb.c and vfb.c. + * + * TODO: + * - monitor sensing (DDC) + * - virtual display + * - other platform support (only ppc/x86 supported) + * - hardware cursor support + * + * Please cc: your patches to brad@neruo.com. + */ + +/* + * A special note of gratitude to ATI's devrel for providing documentation, + * example code and hardware. Thanks Nitya. -atong and brad + */ + + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/uaccess.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/console.h> +#include <linux/backlight.h> +#include <asm/io.h> + +#ifdef CONFIG_PPC_PMAC +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#include "../macmodes.h" +#endif + +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif + +#ifdef CONFIG_BOOTX_TEXT +#include <asm/btext.h> +#endif /* CONFIG_BOOTX_TEXT */ + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include <video/aty128.h> + +/* Debug flag */ +#undef DEBUG + +#ifdef DEBUG +#define DBG(fmt, args...) \ + printk(KERN_DEBUG "aty128fb: %s " fmt, __func__, ##args); +#else +#define DBG(fmt, args...) +#endif + +#ifndef CONFIG_PPC_PMAC +/* default mode */ +static struct fb_var_screeninfo default_var = { + /* 640x480, 60 Hz, Non-Interlaced (25.175 MHz dotclock) */ + 640, 480, 640, 480, 0, 0, 8, 0, + {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 39722, 48, 16, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED +}; + +#else /* CONFIG_PPC_PMAC */ +/* default to 1024x768 at 75Hz on PPC - this will work + * on the iMac, the usual 640x480 @ 60Hz doesn't. */ +static struct fb_var_screeninfo default_var = { + /* 1024x768, 75 Hz, Non-Interlaced (78.75 MHz dotclock) */ + 1024, 768, 1024, 768, 0, 0, 8, 0, + {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 12699, 160, 32, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED +}; +#endif /* CONFIG_PPC_PMAC */ + +/* default modedb mode */ +/* 640x480, 60 Hz, Non-Interlaced (25.172 MHz dotclock) */ +static struct fb_videomode defaultmode = { + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 39722, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED +}; + +/* Chip generations */ +enum { + rage_128, + rage_128_pci, + rage_128_pro, + rage_128_pro_pci, + rage_M3, + rage_M3_pci, + rage_M4, + rage_128_ultra, +}; + +/* Must match above enum */ +static char * const r128_family[] = { + "AGP", + "PCI", + "PRO AGP", + "PRO PCI", + "M3 AGP", + "M3 PCI", + "M4 AGP", + "Ultra AGP", +}; + +/* + * PCI driver prototypes + */ +static int aty128_probe(struct pci_dev *pdev, + const struct pci_device_id *ent); +static void aty128_remove(struct pci_dev *pdev); +static int aty128_pci_suspend(struct pci_dev *pdev, pm_message_t state); +static int aty128_pci_resume(struct pci_dev *pdev); +static int aty128_do_resume(struct pci_dev *pdev); + +/* supported Rage128 chipsets */ +static struct pci_device_id aty128_pci_tbl[] = { + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_LE, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_M3_pci }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_LF, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_M3 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_MF, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_M4 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_ML, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_M4 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PA, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PB, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PC, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PD, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro_pci }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PE, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PF, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PG, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PH, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PI, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PJ, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PK, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PM, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PN, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro_pci }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PQ, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro_pci }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PT, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PU, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PV, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PW, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_PX, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pro }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_RE, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pci }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_RF, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_RG, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_RK, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pci }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_RL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SE, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SF, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_pci }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SG, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SH, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SK, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SM, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_SN, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128 }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_TF, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_ultra }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_TL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_ultra }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_TR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_ultra }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_TS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_ultra }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_TT, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_ultra }, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RAGE128_TU, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, rage_128_ultra }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, aty128_pci_tbl); + +static struct pci_driver aty128fb_driver = { + .name = "aty128fb", + .id_table = aty128_pci_tbl, + .probe = aty128_probe, + .remove = aty128_remove, + .suspend = aty128_pci_suspend, + .resume = aty128_pci_resume, +}; + +/* packed BIOS settings */ +#ifndef CONFIG_PPC +typedef struct { + u8 clock_chip_type; + u8 struct_size; + u8 accelerator_entry; + u8 VGA_entry; + u16 VGA_table_offset; + u16 POST_table_offset; + u16 XCLK; + u16 MCLK; + u8 num_PLL_blocks; + u8 size_PLL_blocks; + u16 PCLK_ref_freq; + u16 PCLK_ref_divider; + u32 PCLK_min_freq; + u32 PCLK_max_freq; + u16 MCLK_ref_freq; + u16 MCLK_ref_divider; + u32 MCLK_min_freq; + u32 MCLK_max_freq; + u16 XCLK_ref_freq; + u16 XCLK_ref_divider; + u32 XCLK_min_freq; + u32 XCLK_max_freq; +} __attribute__ ((packed)) PLL_BLOCK; +#endif /* !CONFIG_PPC */ + +/* onboard memory information */ +struct aty128_meminfo { + u8 ML; + u8 MB; + u8 Trcd; + u8 Trp; + u8 Twr; + u8 CL; + u8 Tr2w; + u8 LoopLatency; + u8 DspOn; + u8 Rloop; + const char *name; +}; + +/* various memory configurations */ +static const struct aty128_meminfo sdr_128 = + { 4, 4, 3, 3, 1, 3, 1, 16, 30, 16, "128-bit SDR SGRAM (1:1)" }; +static const struct aty128_meminfo sdr_64 = + { 4, 8, 3, 3, 1, 3, 1, 17, 46, 17, "64-bit SDR SGRAM (1:1)" }; +static const struct aty128_meminfo sdr_sgram = + { 4, 4, 1, 2, 1, 2, 1, 16, 24, 16, "64-bit SDR SGRAM (2:1)" }; +static const struct aty128_meminfo ddr_sgram = + { 4, 4, 3, 3, 2, 3, 1, 16, 31, 16, "64-bit DDR SGRAM" }; + +static struct fb_fix_screeninfo aty128fb_fix = { + .id = "ATY Rage128", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 8, + .ypanstep = 1, + .mmio_len = 0x2000, + .accel = FB_ACCEL_ATI_RAGE128, +}; + +static char *mode_option = NULL; + +#ifdef CONFIG_PPC_PMAC +static int default_vmode = VMODE_1024_768_60; +static int default_cmode = CMODE_8; +#endif + +static int default_crt_on = 0; +static int default_lcd_on = 1; + +#ifdef CONFIG_MTRR +static bool mtrr = true; +#endif + +#ifdef CONFIG_FB_ATY128_BACKLIGHT +#ifdef CONFIG_PMAC_BACKLIGHT +static int backlight = 1; +#else +static int backlight = 0; +#endif +#endif + +/* PLL constants */ +struct aty128_constants { + u32 ref_clk; + u32 ppll_min; + u32 ppll_max; + u32 ref_divider; + u32 xclk; + u32 fifo_width; + u32 fifo_depth; +}; + +struct aty128_crtc { + u32 gen_cntl; + u32 h_total, h_sync_strt_wid; + u32 v_total, v_sync_strt_wid; + u32 pitch; + u32 offset, offset_cntl; + u32 xoffset, yoffset; + u32 vxres, vyres; + u32 depth, bpp; +}; + +struct aty128_pll { + u32 post_divider; + u32 feedback_divider; + u32 vclk; +}; + +struct aty128_ddafifo { + u32 dda_config; + u32 dda_on_off; +}; + +/* register values for a specific mode */ +struct aty128fb_par { + struct aty128_crtc crtc; + struct aty128_pll pll; + struct aty128_ddafifo fifo_reg; + u32 accel_flags; + struct aty128_constants constants; /* PLL and others */ + void __iomem *regbase; /* remapped mmio */ + u32 vram_size; /* onboard video ram */ + int chip_gen; + const struct aty128_meminfo *mem; /* onboard mem info */ +#ifdef CONFIG_MTRR + struct { int vram; int vram_valid; } mtrr; +#endif + int blitter_may_be_busy; + int fifo_slots; /* free slots in FIFO (64 max) */ + + int crt_on, lcd_on; + struct pci_dev *pdev; + struct fb_info *next; + int asleep; + int lock_blank; + + u8 red[32]; /* see aty128fb_setcolreg */ + u8 green[64]; + u8 blue[32]; + u32 pseudo_palette[16]; /* used for TRUECOLOR */ +}; + + +#define round_div(n, d) ((n+(d/2))/d) + +static int aty128fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int aty128fb_set_par(struct fb_info *info); +static int aty128fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int aty128fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fb); +static int aty128fb_blank(int blank, struct fb_info *fb); +static int aty128fb_ioctl(struct fb_info *info, u_int cmd, unsigned long arg); +static int aty128fb_sync(struct fb_info *info); + + /* + * Internal routines + */ + +static int aty128_encode_var(struct fb_var_screeninfo *var, + const struct aty128fb_par *par); +static int aty128_decode_var(struct fb_var_screeninfo *var, + struct aty128fb_par *par); +#if 0 +static void aty128_get_pllinfo(struct aty128fb_par *par, void __iomem *bios); +static void __iomem *aty128_map_ROM(struct pci_dev *pdev, + const struct aty128fb_par *par); +#endif +static void aty128_timings(struct aty128fb_par *par); +static void aty128_init_engine(struct aty128fb_par *par); +static void aty128_reset_engine(const struct aty128fb_par *par); +static void aty128_flush_pixel_cache(const struct aty128fb_par *par); +static void do_wait_for_fifo(u16 entries, struct aty128fb_par *par); +static void wait_for_fifo(u16 entries, struct aty128fb_par *par); +static void wait_for_idle(struct aty128fb_par *par); +static u32 depth_to_dst(u32 depth); + +#ifdef CONFIG_FB_ATY128_BACKLIGHT +static void aty128_bl_set_power(struct fb_info *info, int power); +#endif + +#define BIOS_IN8(v) (readb(bios + (v))) +#define BIOS_IN16(v) (readb(bios + (v)) | \ + (readb(bios + (v) + 1) << 8)) +#define BIOS_IN32(v) (readb(bios + (v)) | \ + (readb(bios + (v) + 1) << 8) | \ + (readb(bios + (v) + 2) << 16) | \ + (readb(bios + (v) + 3) << 24)) + + +static struct fb_ops aty128fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = aty128fb_check_var, + .fb_set_par = aty128fb_set_par, + .fb_setcolreg = aty128fb_setcolreg, + .fb_pan_display = aty128fb_pan_display, + .fb_blank = aty128fb_blank, + .fb_ioctl = aty128fb_ioctl, + .fb_sync = aty128fb_sync, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + + /* + * Functions to read from/write to the mmio registers + * - endian conversions may possibly be avoided by + * using the other register aperture. TODO. + */ +static inline u32 _aty_ld_le32(volatile unsigned int regindex, + const struct aty128fb_par *par) +{ + return readl (par->regbase + regindex); +} + +static inline void _aty_st_le32(volatile unsigned int regindex, u32 val, + const struct aty128fb_par *par) +{ + writel (val, par->regbase + regindex); +} + +static inline u8 _aty_ld_8(unsigned int regindex, + const struct aty128fb_par *par) +{ + return readb (par->regbase + regindex); +} + +static inline void _aty_st_8(unsigned int regindex, u8 val, + const struct aty128fb_par *par) +{ + writeb (val, par->regbase + regindex); +} + +#define aty_ld_le32(regindex) _aty_ld_le32(regindex, par) +#define aty_st_le32(regindex, val) _aty_st_le32(regindex, val, par) +#define aty_ld_8(regindex) _aty_ld_8(regindex, par) +#define aty_st_8(regindex, val) _aty_st_8(regindex, val, par) + + /* + * Functions to read from/write to the pll registers + */ + +#define aty_ld_pll(pll_index) _aty_ld_pll(pll_index, par) +#define aty_st_pll(pll_index, val) _aty_st_pll(pll_index, val, par) + + +static u32 _aty_ld_pll(unsigned int pll_index, + const struct aty128fb_par *par) +{ + aty_st_8(CLOCK_CNTL_INDEX, pll_index & 0x3F); + return aty_ld_le32(CLOCK_CNTL_DATA); +} + + +static void _aty_st_pll(unsigned int pll_index, u32 val, + const struct aty128fb_par *par) +{ + aty_st_8(CLOCK_CNTL_INDEX, (pll_index & 0x3F) | PLL_WR_EN); + aty_st_le32(CLOCK_CNTL_DATA, val); +} + + +/* return true when the PLL has completed an atomic update */ +static int aty_pll_readupdate(const struct aty128fb_par *par) +{ + return !(aty_ld_pll(PPLL_REF_DIV) & PPLL_ATOMIC_UPDATE_R); +} + + +static void aty_pll_wait_readupdate(const struct aty128fb_par *par) +{ + unsigned long timeout = jiffies + HZ/100; // should be more than enough + int reset = 1; + + while (time_before(jiffies, timeout)) + if (aty_pll_readupdate(par)) { + reset = 0; + break; + } + + if (reset) /* reset engine?? */ + printk(KERN_DEBUG "aty128fb: PLL write timeout!\n"); +} + + +/* tell PLL to update */ +static void aty_pll_writeupdate(const struct aty128fb_par *par) +{ + aty_pll_wait_readupdate(par); + + aty_st_pll(PPLL_REF_DIV, + aty_ld_pll(PPLL_REF_DIV) | PPLL_ATOMIC_UPDATE_W); +} + + +/* write to the scratch register to test r/w functionality */ +static int register_test(const struct aty128fb_par *par) +{ + u32 val; + int flag = 0; + + val = aty_ld_le32(BIOS_0_SCRATCH); + + aty_st_le32(BIOS_0_SCRATCH, 0x55555555); + if (aty_ld_le32(BIOS_0_SCRATCH) == 0x55555555) { + aty_st_le32(BIOS_0_SCRATCH, 0xAAAAAAAA); + + if (aty_ld_le32(BIOS_0_SCRATCH) == 0xAAAAAAAA) + flag = 1; + } + + aty_st_le32(BIOS_0_SCRATCH, val); // restore value + return flag; +} + + +/* + * Accelerator engine functions + */ +static void do_wait_for_fifo(u16 entries, struct aty128fb_par *par) +{ + int i; + + for (;;) { + for (i = 0; i < 2000000; i++) { + par->fifo_slots = aty_ld_le32(GUI_STAT) & 0x0fff; + if (par->fifo_slots >= entries) + return; + } + aty128_reset_engine(par); + } +} + + +static void wait_for_idle(struct aty128fb_par *par) +{ + int i; + + do_wait_for_fifo(64, par); + + for (;;) { + for (i = 0; i < 2000000; i++) { + if (!(aty_ld_le32(GUI_STAT) & (1 << 31))) { + aty128_flush_pixel_cache(par); + par->blitter_may_be_busy = 0; + return; + } + } + aty128_reset_engine(par); + } +} + + +static void wait_for_fifo(u16 entries, struct aty128fb_par *par) +{ + if (par->fifo_slots < entries) + do_wait_for_fifo(64, par); + par->fifo_slots -= entries; +} + + +static void aty128_flush_pixel_cache(const struct aty128fb_par *par) +{ + int i; + u32 tmp; + + tmp = aty_ld_le32(PC_NGUI_CTLSTAT); + tmp &= ~(0x00ff); + tmp |= 0x00ff; + aty_st_le32(PC_NGUI_CTLSTAT, tmp); + + for (i = 0; i < 2000000; i++) + if (!(aty_ld_le32(PC_NGUI_CTLSTAT) & PC_BUSY)) + break; +} + + +static void aty128_reset_engine(const struct aty128fb_par *par) +{ + u32 gen_reset_cntl, clock_cntl_index, mclk_cntl; + + aty128_flush_pixel_cache(par); + + clock_cntl_index = aty_ld_le32(CLOCK_CNTL_INDEX); + mclk_cntl = aty_ld_pll(MCLK_CNTL); + + aty_st_pll(MCLK_CNTL, mclk_cntl | 0x00030000); + + gen_reset_cntl = aty_ld_le32(GEN_RESET_CNTL); + aty_st_le32(GEN_RESET_CNTL, gen_reset_cntl | SOFT_RESET_GUI); + aty_ld_le32(GEN_RESET_CNTL); + aty_st_le32(GEN_RESET_CNTL, gen_reset_cntl & ~(SOFT_RESET_GUI)); + aty_ld_le32(GEN_RESET_CNTL); + + aty_st_pll(MCLK_CNTL, mclk_cntl); + aty_st_le32(CLOCK_CNTL_INDEX, clock_cntl_index); + aty_st_le32(GEN_RESET_CNTL, gen_reset_cntl); + + /* use old pio mode */ + aty_st_le32(PM4_BUFFER_CNTL, PM4_BUFFER_CNTL_NONPM4); + + DBG("engine reset"); +} + + +static void aty128_init_engine(struct aty128fb_par *par) +{ + u32 pitch_value; + + wait_for_idle(par); + + /* 3D scaler not spoken here */ + wait_for_fifo(1, par); + aty_st_le32(SCALE_3D_CNTL, 0x00000000); + + aty128_reset_engine(par); + + pitch_value = par->crtc.pitch; + if (par->crtc.bpp == 24) { + pitch_value = pitch_value * 3; + } + + wait_for_fifo(4, par); + /* setup engine offset registers */ + aty_st_le32(DEFAULT_OFFSET, 0x00000000); + + /* setup engine pitch registers */ + aty_st_le32(DEFAULT_PITCH, pitch_value); + + /* set the default scissor register to max dimensions */ + aty_st_le32(DEFAULT_SC_BOTTOM_RIGHT, (0x1FFF << 16) | 0x1FFF); + + /* set the drawing controls registers */ + aty_st_le32(DP_GUI_MASTER_CNTL, + GMC_SRC_PITCH_OFFSET_DEFAULT | + GMC_DST_PITCH_OFFSET_DEFAULT | + GMC_SRC_CLIP_DEFAULT | + GMC_DST_CLIP_DEFAULT | + GMC_BRUSH_SOLIDCOLOR | + (depth_to_dst(par->crtc.depth) << 8) | + GMC_SRC_DSTCOLOR | + GMC_BYTE_ORDER_MSB_TO_LSB | + GMC_DP_CONVERSION_TEMP_6500 | + ROP3_PATCOPY | + GMC_DP_SRC_RECT | + GMC_3D_FCN_EN_CLR | + GMC_DST_CLR_CMP_FCN_CLEAR | + GMC_AUX_CLIP_CLEAR | + GMC_WRITE_MASK_SET); + + wait_for_fifo(8, par); + /* clear the line drawing registers */ + aty_st_le32(DST_BRES_ERR, 0); + aty_st_le32(DST_BRES_INC, 0); + aty_st_le32(DST_BRES_DEC, 0); + + /* set brush color registers */ + aty_st_le32(DP_BRUSH_FRGD_CLR, 0xFFFFFFFF); /* white */ + aty_st_le32(DP_BRUSH_BKGD_CLR, 0x00000000); /* black */ + + /* set source color registers */ + aty_st_le32(DP_SRC_FRGD_CLR, 0xFFFFFFFF); /* white */ + aty_st_le32(DP_SRC_BKGD_CLR, 0x00000000); /* black */ + + /* default write mask */ + aty_st_le32(DP_WRITE_MASK, 0xFFFFFFFF); + + /* Wait for all the writes to be completed before returning */ + wait_for_idle(par); +} + + +/* convert depth values to their register representation */ +static u32 depth_to_dst(u32 depth) +{ + if (depth <= 8) + return DST_8BPP; + else if (depth <= 15) + return DST_15BPP; + else if (depth == 16) + return DST_16BPP; + else if (depth <= 24) + return DST_24BPP; + else if (depth <= 32) + return DST_32BPP; + + return -EINVAL; +} + +/* + * PLL informations retreival + */ + + +#ifndef __sparc__ +static void __iomem *aty128_map_ROM(const struct aty128fb_par *par, + struct pci_dev *dev) +{ + u16 dptr; + u8 rom_type; + void __iomem *bios; + size_t rom_size; + + /* Fix from ATI for problem with Rage128 hardware not leaving ROM enabled */ + unsigned int temp; + temp = aty_ld_le32(RAGE128_MPP_TB_CONFIG); + temp &= 0x00ffffffu; + temp |= 0x04 << 24; + aty_st_le32(RAGE128_MPP_TB_CONFIG, temp); + temp = aty_ld_le32(RAGE128_MPP_TB_CONFIG); + + bios = pci_map_rom(dev, &rom_size); + + if (!bios) { + printk(KERN_ERR "aty128fb: ROM failed to map\n"); + return NULL; + } + + /* Very simple test to make sure it appeared */ + if (BIOS_IN16(0) != 0xaa55) { + printk(KERN_DEBUG "aty128fb: Invalid ROM signature %x should " + " be 0xaa55\n", BIOS_IN16(0)); + goto failed; + } + + /* Look for the PCI data to check the ROM type */ + dptr = BIOS_IN16(0x18); + + /* Check the PCI data signature. If it's wrong, we still assume a normal + * x86 ROM for now, until I've verified this works everywhere. + * The goal here is more to phase out Open Firmware images. + * + * Currently, we only look at the first PCI data, we could iteratre and + * deal with them all, and we should use fb_bios_start relative to start + * of image and not relative start of ROM, but so far, I never found a + * dual-image ATI card. + * + * typedef struct { + * u32 signature; + 0x00 + * u16 vendor; + 0x04 + * u16 device; + 0x06 + * u16 reserved_1; + 0x08 + * u16 dlen; + 0x0a + * u8 drevision; + 0x0c + * u8 class_hi; + 0x0d + * u16 class_lo; + 0x0e + * u16 ilen; + 0x10 + * u16 irevision; + 0x12 + * u8 type; + 0x14 + * u8 indicator; + 0x15 + * u16 reserved_2; + 0x16 + * } pci_data_t; + */ + if (BIOS_IN32(dptr) != (('R' << 24) | ('I' << 16) | ('C' << 8) | 'P')) { + printk(KERN_WARNING "aty128fb: PCI DATA signature in ROM incorrect: %08x\n", + BIOS_IN32(dptr)); + goto anyway; + } + rom_type = BIOS_IN8(dptr + 0x14); + switch(rom_type) { + case 0: + printk(KERN_INFO "aty128fb: Found Intel x86 BIOS ROM Image\n"); + break; + case 1: + printk(KERN_INFO "aty128fb: Found Open Firmware ROM Image\n"); + goto failed; + case 2: + printk(KERN_INFO "aty128fb: Found HP PA-RISC ROM Image\n"); + goto failed; + default: + printk(KERN_INFO "aty128fb: Found unknown type %d ROM Image\n", + rom_type); + goto failed; + } + anyway: + return bios; + + failed: + pci_unmap_rom(dev, bios); + return NULL; +} + +static void aty128_get_pllinfo(struct aty128fb_par *par, + unsigned char __iomem *bios) +{ + unsigned int bios_hdr; + unsigned int bios_pll; + + bios_hdr = BIOS_IN16(0x48); + bios_pll = BIOS_IN16(bios_hdr + 0x30); + + par->constants.ppll_max = BIOS_IN32(bios_pll + 0x16); + par->constants.ppll_min = BIOS_IN32(bios_pll + 0x12); + par->constants.xclk = BIOS_IN16(bios_pll + 0x08); + par->constants.ref_divider = BIOS_IN16(bios_pll + 0x10); + par->constants.ref_clk = BIOS_IN16(bios_pll + 0x0e); + + DBG("ppll_max %d ppll_min %d xclk %d ref_divider %d ref clock %d\n", + par->constants.ppll_max, par->constants.ppll_min, + par->constants.xclk, par->constants.ref_divider, + par->constants.ref_clk); + +} + +#ifdef CONFIG_X86 +static void __iomem *aty128_find_mem_vbios(struct aty128fb_par *par) +{ + /* I simplified this code as we used to miss the signatures in + * a lot of case. It's now closer to XFree, we just don't check + * for signatures at all... Something better will have to be done + * if we end up having conflicts + */ + u32 segstart; + unsigned char __iomem *rom_base = NULL; + + for (segstart=0x000c0000; segstart<0x000f0000; segstart+=0x00001000) { + rom_base = ioremap(segstart, 0x10000); + if (rom_base == NULL) + return NULL; + if (readb(rom_base) == 0x55 && readb(rom_base + 1) == 0xaa) + break; + iounmap(rom_base); + rom_base = NULL; + } + return rom_base; +} +#endif +#endif /* ndef(__sparc__) */ + +/* fill in known card constants if pll_block is not available */ +static void aty128_timings(struct aty128fb_par *par) +{ +#ifdef CONFIG_PPC_OF + /* instead of a table lookup, assume OF has properly + * setup the PLL registers and use their values + * to set the XCLK values and reference divider values */ + + u32 x_mpll_ref_fb_div; + u32 xclk_cntl; + u32 Nx, M; + unsigned PostDivSet[] = { 0, 1, 2, 4, 8, 3, 6, 12 }; +#endif + + if (!par->constants.ref_clk) + par->constants.ref_clk = 2950; + +#ifdef CONFIG_PPC_OF + x_mpll_ref_fb_div = aty_ld_pll(X_MPLL_REF_FB_DIV); + xclk_cntl = aty_ld_pll(XCLK_CNTL) & 0x7; + Nx = (x_mpll_ref_fb_div & 0x00ff00) >> 8; + M = x_mpll_ref_fb_div & 0x0000ff; + + par->constants.xclk = round_div((2 * Nx * par->constants.ref_clk), + (M * PostDivSet[xclk_cntl])); + + par->constants.ref_divider = + aty_ld_pll(PPLL_REF_DIV) & PPLL_REF_DIV_MASK; +#endif + + if (!par->constants.ref_divider) { + par->constants.ref_divider = 0x3b; + + aty_st_pll(X_MPLL_REF_FB_DIV, 0x004c4c1e); + aty_pll_writeupdate(par); + } + aty_st_pll(PPLL_REF_DIV, par->constants.ref_divider); + aty_pll_writeupdate(par); + + /* from documentation */ + if (!par->constants.ppll_min) + par->constants.ppll_min = 12500; + if (!par->constants.ppll_max) + par->constants.ppll_max = 25000; /* 23000 on some cards? */ + if (!par->constants.xclk) + par->constants.xclk = 0x1d4d; /* same as mclk */ + + par->constants.fifo_width = 128; + par->constants.fifo_depth = 32; + + switch (aty_ld_le32(MEM_CNTL) & 0x3) { + case 0: + par->mem = &sdr_128; + break; + case 1: + par->mem = &sdr_sgram; + break; + case 2: + par->mem = &ddr_sgram; + break; + default: + par->mem = &sdr_sgram; + } +} + + + +/* + * CRTC programming + */ + +/* Program the CRTC registers */ +static void aty128_set_crtc(const struct aty128_crtc *crtc, + const struct aty128fb_par *par) +{ + aty_st_le32(CRTC_GEN_CNTL, crtc->gen_cntl); + aty_st_le32(CRTC_H_TOTAL_DISP, crtc->h_total); + aty_st_le32(CRTC_H_SYNC_STRT_WID, crtc->h_sync_strt_wid); + aty_st_le32(CRTC_V_TOTAL_DISP, crtc->v_total); + aty_st_le32(CRTC_V_SYNC_STRT_WID, crtc->v_sync_strt_wid); + aty_st_le32(CRTC_PITCH, crtc->pitch); + aty_st_le32(CRTC_OFFSET, crtc->offset); + aty_st_le32(CRTC_OFFSET_CNTL, crtc->offset_cntl); + /* Disable ATOMIC updating. Is this the right place? */ + aty_st_pll(PPLL_CNTL, aty_ld_pll(PPLL_CNTL) & ~(0x00030000)); +} + + +static int aty128_var_to_crtc(const struct fb_var_screeninfo *var, + struct aty128_crtc *crtc, + const struct aty128fb_par *par) +{ + u32 xres, yres, vxres, vyres, xoffset, yoffset, bpp, dst; + u32 left, right, upper, lower, hslen, vslen, sync, vmode; + u32 h_total, h_disp, h_sync_strt, h_sync_wid, h_sync_pol; + u32 v_total, v_disp, v_sync_strt, v_sync_wid, v_sync_pol, c_sync; + u32 depth, bytpp; + u8 mode_bytpp[7] = { 0, 0, 1, 2, 2, 3, 4 }; + + /* input */ + xres = var->xres; + yres = var->yres; + vxres = var->xres_virtual; + vyres = var->yres_virtual; + xoffset = var->xoffset; + yoffset = var->yoffset; + bpp = var->bits_per_pixel; + left = var->left_margin; + right = var->right_margin; + upper = var->upper_margin; + lower = var->lower_margin; + hslen = var->hsync_len; + vslen = var->vsync_len; + sync = var->sync; + vmode = var->vmode; + + if (bpp != 16) + depth = bpp; + else + depth = (var->green.length == 6) ? 16 : 15; + + /* check for mode eligibility + * accept only non interlaced modes */ + if ((vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) + return -EINVAL; + + /* convert (and round up) and validate */ + xres = (xres + 7) & ~7; + xoffset = (xoffset + 7) & ~7; + + if (vxres < xres + xoffset) + vxres = xres + xoffset; + + if (vyres < yres + yoffset) + vyres = yres + yoffset; + + /* convert depth into ATI register depth */ + dst = depth_to_dst(depth); + + if (dst == -EINVAL) { + printk(KERN_ERR "aty128fb: Invalid depth or RGBA\n"); + return -EINVAL; + } + + /* convert register depth to bytes per pixel */ + bytpp = mode_bytpp[dst]; + + /* make sure there is enough video ram for the mode */ + if ((u32)(vxres * vyres * bytpp) > par->vram_size) { + printk(KERN_ERR "aty128fb: Not enough memory for mode\n"); + return -EINVAL; + } + + h_disp = (xres >> 3) - 1; + h_total = (((xres + right + hslen + left) >> 3) - 1) & 0xFFFFL; + + v_disp = yres - 1; + v_total = (yres + upper + vslen + lower - 1) & 0xFFFFL; + + /* check to make sure h_total and v_total are in range */ + if (((h_total >> 3) - 1) > 0x1ff || (v_total - 1) > 0x7FF) { + printk(KERN_ERR "aty128fb: invalid width ranges\n"); + return -EINVAL; + } + + h_sync_wid = (hslen + 7) >> 3; + if (h_sync_wid == 0) + h_sync_wid = 1; + else if (h_sync_wid > 0x3f) /* 0x3f = max hwidth */ + h_sync_wid = 0x3f; + + h_sync_strt = (h_disp << 3) + right; + + v_sync_wid = vslen; + if (v_sync_wid == 0) + v_sync_wid = 1; + else if (v_sync_wid > 0x1f) /* 0x1f = max vwidth */ + v_sync_wid = 0x1f; + + v_sync_strt = v_disp + lower; + + h_sync_pol = sync & FB_SYNC_HOR_HIGH_ACT ? 0 : 1; + v_sync_pol = sync & FB_SYNC_VERT_HIGH_ACT ? 0 : 1; + + c_sync = sync & FB_SYNC_COMP_HIGH_ACT ? (1 << 4) : 0; + + crtc->gen_cntl = 0x3000000L | c_sync | (dst << 8); + + crtc->h_total = h_total | (h_disp << 16); + crtc->v_total = v_total | (v_disp << 16); + + crtc->h_sync_strt_wid = h_sync_strt | (h_sync_wid << 16) | + (h_sync_pol << 23); + crtc->v_sync_strt_wid = v_sync_strt | (v_sync_wid << 16) | + (v_sync_pol << 23); + + crtc->pitch = vxres >> 3; + + crtc->offset = 0; + + if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) + crtc->offset_cntl = 0x00010000; + else + crtc->offset_cntl = 0; + + crtc->vxres = vxres; + crtc->vyres = vyres; + crtc->xoffset = xoffset; + crtc->yoffset = yoffset; + crtc->depth = depth; + crtc->bpp = bpp; + + return 0; +} + + +static int aty128_pix_width_to_var(int pix_width, struct fb_var_screeninfo *var) +{ + + /* fill in pixel info */ + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.offset = 0; + var->blue.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + switch (pix_width) { + case CRTC_PIX_WIDTH_8BPP: + var->bits_per_pixel = 8; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.length = 8; + break; + case CRTC_PIX_WIDTH_15BPP: + var->bits_per_pixel = 16; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case CRTC_PIX_WIDTH_16BPP: + var->bits_per_pixel = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.length = 5; + break; + case CRTC_PIX_WIDTH_24BPP: + var->bits_per_pixel = 24; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case CRTC_PIX_WIDTH_32BPP: + var->bits_per_pixel = 32; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + default: + printk(KERN_ERR "aty128fb: Invalid pixel width\n"); + return -EINVAL; + } + + return 0; +} + + +static int aty128_crtc_to_var(const struct aty128_crtc *crtc, + struct fb_var_screeninfo *var) +{ + u32 xres, yres, left, right, upper, lower, hslen, vslen, sync; + u32 h_total, h_disp, h_sync_strt, h_sync_dly, h_sync_wid, h_sync_pol; + u32 v_total, v_disp, v_sync_strt, v_sync_wid, v_sync_pol, c_sync; + u32 pix_width; + + /* fun with masking */ + h_total = crtc->h_total & 0x1ff; + h_disp = (crtc->h_total >> 16) & 0xff; + h_sync_strt = (crtc->h_sync_strt_wid >> 3) & 0x1ff; + h_sync_dly = crtc->h_sync_strt_wid & 0x7; + h_sync_wid = (crtc->h_sync_strt_wid >> 16) & 0x3f; + h_sync_pol = (crtc->h_sync_strt_wid >> 23) & 0x1; + v_total = crtc->v_total & 0x7ff; + v_disp = (crtc->v_total >> 16) & 0x7ff; + v_sync_strt = crtc->v_sync_strt_wid & 0x7ff; + v_sync_wid = (crtc->v_sync_strt_wid >> 16) & 0x1f; + v_sync_pol = (crtc->v_sync_strt_wid >> 23) & 0x1; + c_sync = crtc->gen_cntl & CRTC_CSYNC_EN ? 1 : 0; + pix_width = crtc->gen_cntl & CRTC_PIX_WIDTH_MASK; + + /* do conversions */ + xres = (h_disp + 1) << 3; + yres = v_disp + 1; + left = ((h_total - h_sync_strt - h_sync_wid) << 3) - h_sync_dly; + right = ((h_sync_strt - h_disp) << 3) + h_sync_dly; + hslen = h_sync_wid << 3; + upper = v_total - v_sync_strt - v_sync_wid; + lower = v_sync_strt - v_disp; + vslen = v_sync_wid; + sync = (h_sync_pol ? 0 : FB_SYNC_HOR_HIGH_ACT) | + (v_sync_pol ? 0 : FB_SYNC_VERT_HIGH_ACT) | + (c_sync ? FB_SYNC_COMP_HIGH_ACT : 0); + + aty128_pix_width_to_var(pix_width, var); + + var->xres = xres; + var->yres = yres; + var->xres_virtual = crtc->vxres; + var->yres_virtual = crtc->vyres; + var->xoffset = crtc->xoffset; + var->yoffset = crtc->yoffset; + var->left_margin = left; + var->right_margin = right; + var->upper_margin = upper; + var->lower_margin = lower; + var->hsync_len = hslen; + var->vsync_len = vslen; + var->sync = sync; + var->vmode = FB_VMODE_NONINTERLACED; + + return 0; +} + +static void aty128_set_crt_enable(struct aty128fb_par *par, int on) +{ + if (on) { + aty_st_le32(CRTC_EXT_CNTL, aty_ld_le32(CRTC_EXT_CNTL) | + CRT_CRTC_ON); + aty_st_le32(DAC_CNTL, (aty_ld_le32(DAC_CNTL) | + DAC_PALETTE2_SNOOP_EN)); + } else + aty_st_le32(CRTC_EXT_CNTL, aty_ld_le32(CRTC_EXT_CNTL) & + ~CRT_CRTC_ON); +} + +static void aty128_set_lcd_enable(struct aty128fb_par *par, int on) +{ + u32 reg; +#ifdef CONFIG_FB_ATY128_BACKLIGHT + struct fb_info *info = pci_get_drvdata(par->pdev); +#endif + + if (on) { + reg = aty_ld_le32(LVDS_GEN_CNTL); + reg |= LVDS_ON | LVDS_EN | LVDS_BLON | LVDS_DIGION; + reg &= ~LVDS_DISPLAY_DIS; + aty_st_le32(LVDS_GEN_CNTL, reg); +#ifdef CONFIG_FB_ATY128_BACKLIGHT + aty128_bl_set_power(info, FB_BLANK_UNBLANK); +#endif + } else { +#ifdef CONFIG_FB_ATY128_BACKLIGHT + aty128_bl_set_power(info, FB_BLANK_POWERDOWN); +#endif + reg = aty_ld_le32(LVDS_GEN_CNTL); + reg |= LVDS_DISPLAY_DIS; + aty_st_le32(LVDS_GEN_CNTL, reg); + mdelay(100); + reg &= ~(LVDS_ON /*| LVDS_EN*/); + aty_st_le32(LVDS_GEN_CNTL, reg); + } +} + +static void aty128_set_pll(struct aty128_pll *pll, + const struct aty128fb_par *par) +{ + u32 div3; + + unsigned char post_conv[] = /* register values for post dividers */ + { 2, 0, 1, 4, 2, 2, 6, 2, 3, 2, 2, 2, 7 }; + + /* select PPLL_DIV_3 */ + aty_st_le32(CLOCK_CNTL_INDEX, aty_ld_le32(CLOCK_CNTL_INDEX) | (3 << 8)); + + /* reset PLL */ + aty_st_pll(PPLL_CNTL, + aty_ld_pll(PPLL_CNTL) | PPLL_RESET | PPLL_ATOMIC_UPDATE_EN); + + /* write the reference divider */ + aty_pll_wait_readupdate(par); + aty_st_pll(PPLL_REF_DIV, par->constants.ref_divider & 0x3ff); + aty_pll_writeupdate(par); + + div3 = aty_ld_pll(PPLL_DIV_3); + div3 &= ~PPLL_FB3_DIV_MASK; + div3 |= pll->feedback_divider; + div3 &= ~PPLL_POST3_DIV_MASK; + div3 |= post_conv[pll->post_divider] << 16; + + /* write feedback and post dividers */ + aty_pll_wait_readupdate(par); + aty_st_pll(PPLL_DIV_3, div3); + aty_pll_writeupdate(par); + + aty_pll_wait_readupdate(par); + aty_st_pll(HTOTAL_CNTL, 0); /* no horiz crtc adjustment */ + aty_pll_writeupdate(par); + + /* clear the reset, just in case */ + aty_st_pll(PPLL_CNTL, aty_ld_pll(PPLL_CNTL) & ~PPLL_RESET); +} + + +static int aty128_var_to_pll(u32 period_in_ps, struct aty128_pll *pll, + const struct aty128fb_par *par) +{ + const struct aty128_constants c = par->constants; + unsigned char post_dividers[] = {1,2,4,8,3,6,12}; + u32 output_freq; + u32 vclk; /* in .01 MHz */ + int i = 0; + u32 n, d; + + vclk = 100000000 / period_in_ps; /* convert units to 10 kHz */ + + /* adjust pixel clock if necessary */ + if (vclk > c.ppll_max) + vclk = c.ppll_max; + if (vclk * 12 < c.ppll_min) + vclk = c.ppll_min/12; + + /* now, find an acceptable divider */ + for (i = 0; i < ARRAY_SIZE(post_dividers); i++) { + output_freq = post_dividers[i] * vclk; + if (output_freq >= c.ppll_min && output_freq <= c.ppll_max) { + pll->post_divider = post_dividers[i]; + break; + } + } + + if (i == ARRAY_SIZE(post_dividers)) + return -EINVAL; + + /* calculate feedback divider */ + n = c.ref_divider * output_freq; + d = c.ref_clk; + + pll->feedback_divider = round_div(n, d); + pll->vclk = vclk; + + DBG("post %d feedback %d vlck %d output %d ref_divider %d " + "vclk_per: %d\n", pll->post_divider, + pll->feedback_divider, vclk, output_freq, + c.ref_divider, period_in_ps); + + return 0; +} + + +static int aty128_pll_to_var(const struct aty128_pll *pll, + struct fb_var_screeninfo *var) +{ + var->pixclock = 100000000 / pll->vclk; + + return 0; +} + + +static void aty128_set_fifo(const struct aty128_ddafifo *dsp, + const struct aty128fb_par *par) +{ + aty_st_le32(DDA_CONFIG, dsp->dda_config); + aty_st_le32(DDA_ON_OFF, dsp->dda_on_off); +} + + +static int aty128_ddafifo(struct aty128_ddafifo *dsp, + const struct aty128_pll *pll, + u32 depth, + const struct aty128fb_par *par) +{ + const struct aty128_meminfo *m = par->mem; + u32 xclk = par->constants.xclk; + u32 fifo_width = par->constants.fifo_width; + u32 fifo_depth = par->constants.fifo_depth; + s32 x, b, p, ron, roff; + u32 n, d, bpp; + + /* round up to multiple of 8 */ + bpp = (depth+7) & ~7; + + n = xclk * fifo_width; + d = pll->vclk * bpp; + x = round_div(n, d); + + ron = 4 * m->MB + + 3 * ((m->Trcd - 2 > 0) ? m->Trcd - 2 : 0) + + 2 * m->Trp + + m->Twr + + m->CL + + m->Tr2w + + x; + + DBG("x %x\n", x); + + b = 0; + while (x) { + x >>= 1; + b++; + } + p = b + 1; + + ron <<= (11 - p); + + n <<= (11 - p); + x = round_div(n, d); + roff = x * (fifo_depth - 4); + + if ((ron + m->Rloop) >= roff) { + printk(KERN_ERR "aty128fb: Mode out of range!\n"); + return -EINVAL; + } + + DBG("p: %x rloop: %x x: %x ron: %x roff: %x\n", + p, m->Rloop, x, ron, roff); + + dsp->dda_config = p << 16 | m->Rloop << 20 | x; + dsp->dda_on_off = ron << 16 | roff; + + return 0; +} + + +/* + * This actually sets the video mode. + */ +static int aty128fb_set_par(struct fb_info *info) +{ + struct aty128fb_par *par = info->par; + u32 config; + int err; + + if ((err = aty128_decode_var(&info->var, par)) != 0) + return err; + + if (par->blitter_may_be_busy) + wait_for_idle(par); + + /* clear all registers that may interfere with mode setting */ + aty_st_le32(OVR_CLR, 0); + aty_st_le32(OVR_WID_LEFT_RIGHT, 0); + aty_st_le32(OVR_WID_TOP_BOTTOM, 0); + aty_st_le32(OV0_SCALE_CNTL, 0); + aty_st_le32(MPP_TB_CONFIG, 0); + aty_st_le32(MPP_GP_CONFIG, 0); + aty_st_le32(SUBPIC_CNTL, 0); + aty_st_le32(VIPH_CONTROL, 0); + aty_st_le32(I2C_CNTL_1, 0); /* turn off i2c */ + aty_st_le32(GEN_INT_CNTL, 0); /* turn off interrupts */ + aty_st_le32(CAP0_TRIG_CNTL, 0); + aty_st_le32(CAP1_TRIG_CNTL, 0); + + aty_st_8(CRTC_EXT_CNTL + 1, 4); /* turn video off */ + + aty128_set_crtc(&par->crtc, par); + aty128_set_pll(&par->pll, par); + aty128_set_fifo(&par->fifo_reg, par); + + config = aty_ld_le32(CNFG_CNTL) & ~3; + +#if defined(__BIG_ENDIAN) + if (par->crtc.bpp == 32) + config |= 2; /* make aperture do 32 bit swapping */ + else if (par->crtc.bpp == 16) + config |= 1; /* make aperture do 16 bit swapping */ +#endif + + aty_st_le32(CNFG_CNTL, config); + aty_st_8(CRTC_EXT_CNTL + 1, 0); /* turn the video back on */ + + info->fix.line_length = (par->crtc.vxres * par->crtc.bpp) >> 3; + info->fix.visual = par->crtc.bpp == 8 ? FB_VISUAL_PSEUDOCOLOR + : FB_VISUAL_DIRECTCOLOR; + + if (par->chip_gen == rage_M3) { + aty128_set_crt_enable(par, par->crt_on); + aty128_set_lcd_enable(par, par->lcd_on); + } + if (par->accel_flags & FB_ACCELF_TEXT) + aty128_init_engine(par); + +#ifdef CONFIG_BOOTX_TEXT + btext_update_display(info->fix.smem_start, + (((par->crtc.h_total>>16) & 0xff)+1)*8, + ((par->crtc.v_total>>16) & 0x7ff)+1, + par->crtc.bpp, + par->crtc.vxres*par->crtc.bpp/8); +#endif /* CONFIG_BOOTX_TEXT */ + + return 0; +} + +/* + * encode/decode the User Defined Part of the Display + */ + +static int aty128_decode_var(struct fb_var_screeninfo *var, + struct aty128fb_par *par) +{ + int err; + struct aty128_crtc crtc; + struct aty128_pll pll; + struct aty128_ddafifo fifo_reg; + + if ((err = aty128_var_to_crtc(var, &crtc, par))) + return err; + + if ((err = aty128_var_to_pll(var->pixclock, &pll, par))) + return err; + + if ((err = aty128_ddafifo(&fifo_reg, &pll, crtc.depth, par))) + return err; + + par->crtc = crtc; + par->pll = pll; + par->fifo_reg = fifo_reg; + par->accel_flags = var->accel_flags; + + return 0; +} + + +static int aty128_encode_var(struct fb_var_screeninfo *var, + const struct aty128fb_par *par) +{ + int err; + + if ((err = aty128_crtc_to_var(&par->crtc, var))) + return err; + + if ((err = aty128_pll_to_var(&par->pll, var))) + return err; + + var->nonstd = 0; + var->activate = 0; + + var->height = -1; + var->width = -1; + var->accel_flags = par->accel_flags; + + return 0; +} + + +static int aty128fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct aty128fb_par par; + int err; + + par = *(struct aty128fb_par *)info->par; + if ((err = aty128_decode_var(var, &par)) != 0) + return err; + aty128_encode_var(var, &par); + return 0; +} + + +/* + * Pan or Wrap the Display + */ +static int aty128fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fb) +{ + struct aty128fb_par *par = fb->par; + u32 xoffset, yoffset; + u32 offset; + u32 xres, yres; + + xres = (((par->crtc.h_total >> 16) & 0xff) + 1) << 3; + yres = ((par->crtc.v_total >> 16) & 0x7ff) + 1; + + xoffset = (var->xoffset +7) & ~7; + yoffset = var->yoffset; + + if (xoffset+xres > par->crtc.vxres || yoffset+yres > par->crtc.vyres) + return -EINVAL; + + par->crtc.xoffset = xoffset; + par->crtc.yoffset = yoffset; + + offset = ((yoffset * par->crtc.vxres + xoffset) * (par->crtc.bpp >> 3)) + & ~7; + + if (par->crtc.bpp == 24) + offset += 8 * (offset % 3); /* Must be multiple of 8 and 3 */ + + aty_st_le32(CRTC_OFFSET, offset); + + return 0; +} + + +/* + * Helper function to store a single palette register + */ +static void aty128_st_pal(u_int regno, u_int red, u_int green, u_int blue, + struct aty128fb_par *par) +{ + if (par->chip_gen == rage_M3) { +#if 0 + /* Note: For now, on M3, we set palette on both heads, which may + * be useless. Can someone with a M3 check this ? + * + * This code would still be useful if using the second CRTC to + * do mirroring + */ + + aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) | + DAC_PALETTE_ACCESS_CNTL); + aty_st_8(PALETTE_INDEX, regno); + aty_st_le32(PALETTE_DATA, (red<<16)|(green<<8)|blue); +#endif + aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) & + ~DAC_PALETTE_ACCESS_CNTL); + } + + aty_st_8(PALETTE_INDEX, regno); + aty_st_le32(PALETTE_DATA, (red<<16)|(green<<8)|blue); +} + +static int aty128fb_sync(struct fb_info *info) +{ + struct aty128fb_par *par = info->par; + + if (par->blitter_may_be_busy) + wait_for_idle(par); + return 0; +} + +#ifndef MODULE +static int aty128fb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "lcd:", 4)) { + default_lcd_on = simple_strtoul(this_opt+4, NULL, 0); + continue; + } else if (!strncmp(this_opt, "crt:", 4)) { + default_crt_on = simple_strtoul(this_opt+4, NULL, 0); + continue; + } else if (!strncmp(this_opt, "backlight:", 10)) { +#ifdef CONFIG_FB_ATY128_BACKLIGHT + backlight = simple_strtoul(this_opt+10, NULL, 0); +#endif + continue; + } +#ifdef CONFIG_MTRR + if(!strncmp(this_opt, "nomtrr", 6)) { + mtrr = 0; + continue; + } +#endif +#ifdef CONFIG_PPC_PMAC + /* vmode and cmode deprecated */ + if (!strncmp(this_opt, "vmode:", 6)) { + unsigned int vmode = simple_strtoul(this_opt+6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX) + default_vmode = vmode; + continue; + } else if (!strncmp(this_opt, "cmode:", 6)) { + unsigned int cmode = simple_strtoul(this_opt+6, NULL, 0); + switch (cmode) { + case 0: + case 8: + default_cmode = CMODE_8; + break; + case 15: + case 16: + default_cmode = CMODE_16; + break; + case 24: + case 32: + default_cmode = CMODE_32; + break; + } + continue; + } +#endif /* CONFIG_PPC_PMAC */ + mode_option = this_opt; + } + return 0; +} +#endif /* MODULE */ + +/* Backlight */ +#ifdef CONFIG_FB_ATY128_BACKLIGHT +#define MAX_LEVEL 0xFF + +static int aty128_bl_get_level_brightness(struct aty128fb_par *par, + int level) +{ + struct fb_info *info = pci_get_drvdata(par->pdev); + int atylevel; + + /* Get and convert the value */ + /* No locking of bl_curve since we read a single value */ + atylevel = MAX_LEVEL - + (info->bl_curve[level] * FB_BACKLIGHT_MAX / MAX_LEVEL); + + if (atylevel < 0) + atylevel = 0; + else if (atylevel > MAX_LEVEL) + atylevel = MAX_LEVEL; + + return atylevel; +} + +/* We turn off the LCD completely instead of just dimming the backlight. + * This provides greater power saving and the display is useless without + * backlight anyway + */ +#define BACKLIGHT_LVDS_OFF +/* That one prevents proper CRT output with LCD off */ +#undef BACKLIGHT_DAC_OFF + +static int aty128_bl_update_status(struct backlight_device *bd) +{ + struct aty128fb_par *par = bl_get_data(bd); + unsigned int reg = aty_ld_le32(LVDS_GEN_CNTL); + int level; + + if (bd->props.power != FB_BLANK_UNBLANK || + bd->props.fb_blank != FB_BLANK_UNBLANK || + !par->lcd_on) + level = 0; + else + level = bd->props.brightness; + + reg |= LVDS_BL_MOD_EN | LVDS_BLON; + if (level > 0) { + reg |= LVDS_DIGION; + if (!(reg & LVDS_ON)) { + reg &= ~LVDS_BLON; + aty_st_le32(LVDS_GEN_CNTL, reg); + aty_ld_le32(LVDS_GEN_CNTL); + mdelay(10); + reg |= LVDS_BLON; + aty_st_le32(LVDS_GEN_CNTL, reg); + } + reg &= ~LVDS_BL_MOD_LEVEL_MASK; + reg |= (aty128_bl_get_level_brightness(par, level) << + LVDS_BL_MOD_LEVEL_SHIFT); +#ifdef BACKLIGHT_LVDS_OFF + reg |= LVDS_ON | LVDS_EN; + reg &= ~LVDS_DISPLAY_DIS; +#endif + aty_st_le32(LVDS_GEN_CNTL, reg); +#ifdef BACKLIGHT_DAC_OFF + aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) & (~DAC_PDWN)); +#endif + } else { + reg &= ~LVDS_BL_MOD_LEVEL_MASK; + reg |= (aty128_bl_get_level_brightness(par, 0) << + LVDS_BL_MOD_LEVEL_SHIFT); +#ifdef BACKLIGHT_LVDS_OFF + reg |= LVDS_DISPLAY_DIS; + aty_st_le32(LVDS_GEN_CNTL, reg); + aty_ld_le32(LVDS_GEN_CNTL); + udelay(10); + reg &= ~(LVDS_ON | LVDS_EN | LVDS_BLON | LVDS_DIGION); +#endif + aty_st_le32(LVDS_GEN_CNTL, reg); +#ifdef BACKLIGHT_DAC_OFF + aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) | DAC_PDWN); +#endif + } + + return 0; +} + +static int aty128_bl_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static const struct backlight_ops aty128_bl_data = { + .get_brightness = aty128_bl_get_brightness, + .update_status = aty128_bl_update_status, +}; + +static void aty128_bl_set_power(struct fb_info *info, int power) +{ + if (info->bl_dev) { + info->bl_dev->props.power = power; + backlight_update_status(info->bl_dev); + } +} + +static void aty128_bl_init(struct aty128fb_par *par) +{ + struct backlight_properties props; + struct fb_info *info = pci_get_drvdata(par->pdev); + struct backlight_device *bd; + char name[12]; + + /* Could be extended to Rage128Pro LVDS output too */ + if (par->chip_gen != rage_M3) + return; + +#ifdef CONFIG_PMAC_BACKLIGHT + if (!pmac_has_backlight_type("ati")) + return; +#endif + + snprintf(name, sizeof(name), "aty128bl%d", info->node); + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = FB_BACKLIGHT_LEVELS - 1; + bd = backlight_device_register(name, info->dev, par, &aty128_bl_data, + &props); + if (IS_ERR(bd)) { + info->bl_dev = NULL; + printk(KERN_WARNING "aty128: Backlight registration failed\n"); + goto error; + } + + info->bl_dev = bd; + fb_bl_default_curve(info, 0, + 63 * FB_BACKLIGHT_MAX / MAX_LEVEL, + 219 * FB_BACKLIGHT_MAX / MAX_LEVEL); + + bd->props.brightness = bd->props.max_brightness; + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("aty128: Backlight initialized (%s)\n", name); + + return; + +error: + return; +} + +static void aty128_bl_exit(struct backlight_device *bd) +{ + backlight_device_unregister(bd); + printk("aty128: Backlight unloaded\n"); +} +#endif /* CONFIG_FB_ATY128_BACKLIGHT */ + +/* + * Initialisation + */ + +#ifdef CONFIG_PPC_PMAC__disabled +static void aty128_early_resume(void *data) +{ + struct aty128fb_par *par = data; + + if (!console_trylock()) + return; + pci_restore_state(par->pdev); + aty128_do_resume(par->pdev); + console_unlock(); +} +#endif /* CONFIG_PPC_PMAC */ + +static int aty128_init(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct aty128fb_par *par = info->par; + struct fb_var_screeninfo var; + char video_card[50]; + u8 chip_rev; + u32 dac; + + /* Get the chip revision */ + chip_rev = (aty_ld_le32(CNFG_CNTL) >> 16) & 0x1F; + + strcpy(video_card, "Rage128 XX "); + video_card[8] = ent->device >> 8; + video_card[9] = ent->device & 0xFF; + + /* range check to make sure */ + if (ent->driver_data < ARRAY_SIZE(r128_family)) + strlcat(video_card, r128_family[ent->driver_data], + sizeof(video_card)); + + printk(KERN_INFO "aty128fb: %s [chip rev 0x%x] ", video_card, chip_rev); + + if (par->vram_size % (1024 * 1024) == 0) + printk("%dM %s\n", par->vram_size / (1024*1024), par->mem->name); + else + printk("%dk %s\n", par->vram_size / 1024, par->mem->name); + + par->chip_gen = ent->driver_data; + + /* fill in info */ + info->fbops = &aty128fb_ops; + info->flags = FBINFO_FLAG_DEFAULT; + + par->lcd_on = default_lcd_on; + par->crt_on = default_crt_on; + + var = default_var; +#ifdef CONFIG_PPC_PMAC + if (machine_is(powermac)) { + /* Indicate sleep capability */ + if (par->chip_gen == rage_M3) { + pmac_call_feature(PMAC_FTR_DEVICE_CAN_WAKE, NULL, 0, 1); +#if 0 /* Disable the early video resume hack for now as it's causing problems, + * among others we now rely on the PCI core restoring the config space + * for us, which isn't the case with that hack, and that code path causes + * various things to be called with interrupts off while they shouldn't. + * I'm leaving the code in as it can be useful for debugging purposes + */ + pmac_set_early_video_resume(aty128_early_resume, par); +#endif + } + + /* Find default mode */ + if (mode_option) { + if (!mac_find_mode(&var, info, mode_option, 8)) + var = default_var; + } else { + if (default_vmode <= 0 || default_vmode > VMODE_MAX) + default_vmode = VMODE_1024_768_60; + + /* iMacs need that resolution + * PowerMac2,1 first r128 iMacs + * PowerMac2,2 summer 2000 iMacs + * PowerMac4,1 january 2001 iMacs "flower power" + */ + if (of_machine_is_compatible("PowerMac2,1") || + of_machine_is_compatible("PowerMac2,2") || + of_machine_is_compatible("PowerMac4,1")) + default_vmode = VMODE_1024_768_75; + + /* iBook SE */ + if (of_machine_is_compatible("PowerBook2,2")) + default_vmode = VMODE_800_600_60; + + /* PowerBook Firewire (Pismo), iBook Dual USB */ + if (of_machine_is_compatible("PowerBook3,1") || + of_machine_is_compatible("PowerBook4,1")) + default_vmode = VMODE_1024_768_60; + + /* PowerBook Titanium */ + if (of_machine_is_compatible("PowerBook3,2")) + default_vmode = VMODE_1152_768_60; + + if (default_cmode > 16) + default_cmode = CMODE_32; + else if (default_cmode > 8) + default_cmode = CMODE_16; + else + default_cmode = CMODE_8; + + if (mac_vmode_to_var(default_vmode, default_cmode, &var)) + var = default_var; + } + } else +#endif /* CONFIG_PPC_PMAC */ + { + if (mode_option) + if (fb_find_mode(&var, info, mode_option, NULL, + 0, &defaultmode, 8) == 0) + var = default_var; + } + + var.accel_flags &= ~FB_ACCELF_TEXT; +// var.accel_flags |= FB_ACCELF_TEXT;/* FIXME Will add accel later */ + + if (aty128fb_check_var(&var, info)) { + printk(KERN_ERR "aty128fb: Cannot set default mode.\n"); + return 0; + } + + /* setup the DAC the way we like it */ + dac = aty_ld_le32(DAC_CNTL); + dac |= (DAC_8BIT_EN | DAC_RANGE_CNTL); + dac |= DAC_MASK; + if (par->chip_gen == rage_M3) + dac |= DAC_PALETTE2_SNOOP_EN; + aty_st_le32(DAC_CNTL, dac); + + /* turn off bus mastering, just in case */ + aty_st_le32(BUS_CNTL, aty_ld_le32(BUS_CNTL) | BUS_MASTER_DIS); + + info->var = var; + fb_alloc_cmap(&info->cmap, 256, 0); + + var.activate = FB_ACTIVATE_NOW; + + aty128_init_engine(par); + + par->pdev = pdev; + par->asleep = 0; + par->lock_blank = 0; + +#ifdef CONFIG_FB_ATY128_BACKLIGHT + if (backlight) + aty128_bl_init(par); +#endif + + if (register_framebuffer(info) < 0) + return 0; + + fb_info(info, "%s frame buffer device on %s\n", + info->fix.id, video_card); + + return 1; /* success! */ +} + +#ifdef CONFIG_PCI +/* register a card ++ajoshi */ +static int aty128_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + unsigned long fb_addr, reg_addr; + struct aty128fb_par *par; + struct fb_info *info; + int err; +#ifndef __sparc__ + void __iomem *bios = NULL; +#endif + + /* Enable device in PCI config */ + if ((err = pci_enable_device(pdev))) { + printk(KERN_ERR "aty128fb: Cannot enable PCI device: %d\n", + err); + return -ENODEV; + } + + fb_addr = pci_resource_start(pdev, 0); + if (!request_mem_region(fb_addr, pci_resource_len(pdev, 0), + "aty128fb FB")) { + printk(KERN_ERR "aty128fb: cannot reserve frame " + "buffer memory\n"); + return -ENODEV; + } + + reg_addr = pci_resource_start(pdev, 2); + if (!request_mem_region(reg_addr, pci_resource_len(pdev, 2), + "aty128fb MMIO")) { + printk(KERN_ERR "aty128fb: cannot reserve MMIO region\n"); + goto err_free_fb; + } + + /* We have the resources. Now virtualize them */ + info = framebuffer_alloc(sizeof(struct aty128fb_par), &pdev->dev); + if (info == NULL) { + printk(KERN_ERR "aty128fb: can't alloc fb_info_aty128\n"); + goto err_free_mmio; + } + par = info->par; + + info->pseudo_palette = par->pseudo_palette; + + /* Virtualize mmio region */ + info->fix.mmio_start = reg_addr; + par->regbase = pci_ioremap_bar(pdev, 2); + if (!par->regbase) + goto err_free_info; + + /* Grab memory size from the card */ + // How does this relate to the resource length from the PCI hardware? + par->vram_size = aty_ld_le32(CNFG_MEMSIZE) & 0x03FFFFFF; + + /* Virtualize the framebuffer */ + info->screen_base = ioremap(fb_addr, par->vram_size); + if (!info->screen_base) + goto err_unmap_out; + + /* Set up info->fix */ + info->fix = aty128fb_fix; + info->fix.smem_start = fb_addr; + info->fix.smem_len = par->vram_size; + info->fix.mmio_start = reg_addr; + + /* If we can't test scratch registers, something is seriously wrong */ + if (!register_test(par)) { + printk(KERN_ERR "aty128fb: Can't write to video register!\n"); + goto err_out; + } + +#ifndef __sparc__ + bios = aty128_map_ROM(par, pdev); +#ifdef CONFIG_X86 + if (bios == NULL) + bios = aty128_find_mem_vbios(par); +#endif + if (bios == NULL) + printk(KERN_INFO "aty128fb: BIOS not located, guessing timings.\n"); + else { + printk(KERN_INFO "aty128fb: Rage128 BIOS located\n"); + aty128_get_pllinfo(par, bios); + pci_unmap_rom(pdev, bios); + } +#endif /* __sparc__ */ + + aty128_timings(par); + pci_set_drvdata(pdev, info); + + if (!aty128_init(pdev, ent)) + goto err_out; + +#ifdef CONFIG_MTRR + if (mtrr) { + par->mtrr.vram = mtrr_add(info->fix.smem_start, + par->vram_size, MTRR_TYPE_WRCOMB, 1); + par->mtrr.vram_valid = 1; + /* let there be speed */ + printk(KERN_INFO "aty128fb: Rage128 MTRR set to ON\n"); + } +#endif /* CONFIG_MTRR */ + return 0; + +err_out: + iounmap(info->screen_base); +err_unmap_out: + iounmap(par->regbase); +err_free_info: + framebuffer_release(info); +err_free_mmio: + release_mem_region(pci_resource_start(pdev, 2), + pci_resource_len(pdev, 2)); +err_free_fb: + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + return -ENODEV; +} + +static void aty128_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct aty128fb_par *par; + + if (!info) + return; + + par = info->par; + + unregister_framebuffer(info); + +#ifdef CONFIG_FB_ATY128_BACKLIGHT + aty128_bl_exit(info->bl_dev); +#endif + +#ifdef CONFIG_MTRR + if (par->mtrr.vram_valid) + mtrr_del(par->mtrr.vram, info->fix.smem_start, + par->vram_size); +#endif /* CONFIG_MTRR */ + iounmap(par->regbase); + iounmap(info->screen_base); + + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + release_mem_region(pci_resource_start(pdev, 2), + pci_resource_len(pdev, 2)); + framebuffer_release(info); +} +#endif /* CONFIG_PCI */ + + + + /* + * Blank the display. + */ +static int aty128fb_blank(int blank, struct fb_info *fb) +{ + struct aty128fb_par *par = fb->par; + u8 state; + + if (par->lock_blank || par->asleep) + return 0; + + switch (blank) { + case FB_BLANK_NORMAL: + state = 4; + break; + case FB_BLANK_VSYNC_SUSPEND: + state = 6; + break; + case FB_BLANK_HSYNC_SUSPEND: + state = 5; + break; + case FB_BLANK_POWERDOWN: + state = 7; + break; + case FB_BLANK_UNBLANK: + default: + state = 0; + break; + } + aty_st_8(CRTC_EXT_CNTL+1, state); + + if (par->chip_gen == rage_M3) { + aty128_set_crt_enable(par, par->crt_on && !blank); + aty128_set_lcd_enable(par, par->lcd_on && !blank); + } + + return 0; +} + +/* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ +static int aty128fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct aty128fb_par *par = info->par; + + if (regno > 255 + || (par->crtc.depth == 16 && regno > 63) + || (par->crtc.depth == 15 && regno > 31)) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + if (regno < 16) { + int i; + u32 *pal = info->pseudo_palette; + + switch (par->crtc.depth) { + case 15: + pal[regno] = (regno << 10) | (regno << 5) | regno; + break; + case 16: + pal[regno] = (regno << 11) | (regno << 6) | regno; + break; + case 24: + pal[regno] = (regno << 16) | (regno << 8) | regno; + break; + case 32: + i = (regno << 8) | regno; + pal[regno] = (i << 16) | i; + break; + } + } + + if (par->crtc.depth == 16 && regno > 0) { + /* + * With the 5-6-5 split of bits for RGB at 16 bits/pixel, we + * have 32 slots for R and B values but 64 slots for G values. + * Thus the R and B values go in one slot but the G value + * goes in a different slot, and we have to avoid disturbing + * the other fields in the slots we touch. + */ + par->green[regno] = green; + if (regno < 32) { + par->red[regno] = red; + par->blue[regno] = blue; + aty128_st_pal(regno * 8, red, par->green[regno*2], + blue, par); + } + red = par->red[regno/2]; + blue = par->blue[regno/2]; + regno <<= 2; + } else if (par->crtc.bpp == 16) + regno <<= 3; + aty128_st_pal(regno, red, green, blue, par); + + return 0; +} + +#define ATY_MIRROR_LCD_ON 0x00000001 +#define ATY_MIRROR_CRT_ON 0x00000002 + +/* out param: u32* backlight value: 0 to 15 */ +#define FBIO_ATY128_GET_MIRROR _IOR('@', 1, __u32) +/* in param: u32* backlight value: 0 to 15 */ +#define FBIO_ATY128_SET_MIRROR _IOW('@', 2, __u32) + +static int aty128fb_ioctl(struct fb_info *info, u_int cmd, u_long arg) +{ + struct aty128fb_par *par = info->par; + u32 value; + int rc; + + switch (cmd) { + case FBIO_ATY128_SET_MIRROR: + if (par->chip_gen != rage_M3) + return -EINVAL; + rc = get_user(value, (__u32 __user *)arg); + if (rc) + return rc; + par->lcd_on = (value & 0x01) != 0; + par->crt_on = (value & 0x02) != 0; + if (!par->crt_on && !par->lcd_on) + par->lcd_on = 1; + aty128_set_crt_enable(par, par->crt_on); + aty128_set_lcd_enable(par, par->lcd_on); + return 0; + case FBIO_ATY128_GET_MIRROR: + if (par->chip_gen != rage_M3) + return -EINVAL; + value = (par->crt_on << 1) | par->lcd_on; + return put_user(value, (__u32 __user *)arg); + } + return -EINVAL; +} + +#if 0 + /* + * Accelerated functions + */ + +static inline void aty128_rectcopy(int srcx, int srcy, int dstx, int dsty, + u_int width, u_int height, + struct fb_info_aty128 *par) +{ + u32 save_dp_datatype, save_dp_cntl, dstval; + + if (!width || !height) + return; + + dstval = depth_to_dst(par->current_par.crtc.depth); + if (dstval == DST_24BPP) { + srcx *= 3; + dstx *= 3; + width *= 3; + } else if (dstval == -EINVAL) { + printk("aty128fb: invalid depth or RGBA\n"); + return; + } + + wait_for_fifo(2, par); + save_dp_datatype = aty_ld_le32(DP_DATATYPE); + save_dp_cntl = aty_ld_le32(DP_CNTL); + + wait_for_fifo(6, par); + aty_st_le32(SRC_Y_X, (srcy << 16) | srcx); + aty_st_le32(DP_MIX, ROP3_SRCCOPY | DP_SRC_RECT); + aty_st_le32(DP_CNTL, DST_X_LEFT_TO_RIGHT | DST_Y_TOP_TO_BOTTOM); + aty_st_le32(DP_DATATYPE, save_dp_datatype | dstval | SRC_DSTCOLOR); + + aty_st_le32(DST_Y_X, (dsty << 16) | dstx); + aty_st_le32(DST_HEIGHT_WIDTH, (height << 16) | width); + + par->blitter_may_be_busy = 1; + + wait_for_fifo(2, par); + aty_st_le32(DP_DATATYPE, save_dp_datatype); + aty_st_le32(DP_CNTL, save_dp_cntl); +} + + + /* + * Text mode accelerated functions + */ + +static void fbcon_aty128_bmove(struct display *p, int sy, int sx, int dy, + int dx, int height, int width) +{ + sx *= fontwidth(p); + sy *= fontheight(p); + dx *= fontwidth(p); + dy *= fontheight(p); + width *= fontwidth(p); + height *= fontheight(p); + + aty128_rectcopy(sx, sy, dx, dy, width, height, + (struct fb_info_aty128 *)p->fb_info); +} +#endif /* 0 */ + +static void aty128_set_suspend(struct aty128fb_par *par, int suspend) +{ + u32 pmgt; + struct pci_dev *pdev = par->pdev; + + if (!par->pdev->pm_cap) + return; + + /* Set the chip into the appropriate suspend mode (we use D2, + * D3 would require a complete re-initialisation of the chip, + * including PCI config registers, clocks, AGP configuration, ...) + * + * For resume, the core will have already brought us back to D0 + */ + if (suspend) { + /* Make sure CRTC2 is reset. Remove that the day we decide to + * actually use CRTC2 and replace it with real code for disabling + * the CRTC2 output during sleep + */ + aty_st_le32(CRTC2_GEN_CNTL, aty_ld_le32(CRTC2_GEN_CNTL) & + ~(CRTC2_EN)); + + /* Set the power management mode to be PCI based */ + /* Use this magic value for now */ + pmgt = 0x0c005407; + aty_st_pll(POWER_MANAGEMENT, pmgt); + (void)aty_ld_pll(POWER_MANAGEMENT); + aty_st_le32(BUS_CNTL1, 0x00000010); + aty_st_le32(MEM_POWER_MISC, 0x0c830000); + mdelay(100); + + /* Switch PCI power management to D2 */ + pci_set_power_state(pdev, PCI_D2); + } +} + +static int aty128_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct aty128fb_par *par = info->par; + + /* Because we may change PCI D state ourselves, we need to + * first save the config space content so the core can + * restore it properly on resume. + */ + pci_save_state(pdev); + + /* We don't do anything but D2, for now we return 0, but + * we may want to change that. How do we know if the BIOS + * can properly take care of D3 ? Also, with swsusp, we + * know we'll be rebooted, ... + */ +#ifndef CONFIG_PPC_PMAC + /* HACK ALERT ! Once I find a proper way to say to each driver + * individually what will happen with it's PCI slot, I'll change + * that. On laptops, the AGP slot is just unclocked, so D2 is + * expected, while on desktops, the card is powered off + */ + return 0; +#endif /* CONFIG_PPC_PMAC */ + + if (state.event == pdev->dev.power.power_state.event) + return 0; + + printk(KERN_DEBUG "aty128fb: suspending...\n"); + + console_lock(); + + fb_set_suspend(info, 1); + + /* Make sure engine is reset */ + wait_for_idle(par); + aty128_reset_engine(par); + wait_for_idle(par); + + /* Blank display and LCD */ + aty128fb_blank(FB_BLANK_POWERDOWN, info); + + /* Sleep */ + par->asleep = 1; + par->lock_blank = 1; + +#ifdef CONFIG_PPC_PMAC + /* On powermac, we have hooks to properly suspend/resume AGP now, + * use them here. We'll ultimately need some generic support here, + * but the generic code isn't quite ready for that yet + */ + pmac_suspend_agp_for_card(pdev); +#endif /* CONFIG_PPC_PMAC */ + + /* We need a way to make sure the fbdev layer will _not_ touch the + * framebuffer before we put the chip to suspend state. On 2.4, I + * used dummy fb ops, 2.5 need proper support for this at the + * fbdev level + */ + if (state.event != PM_EVENT_ON) + aty128_set_suspend(par, 1); + + console_unlock(); + + pdev->dev.power.power_state = state; + + return 0; +} + +static int aty128_do_resume(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct aty128fb_par *par = info->par; + + if (pdev->dev.power.power_state.event == PM_EVENT_ON) + return 0; + + /* PCI state will have been restored by the core, so + * we should be in D0 now with our config space fully + * restored + */ + + /* Wakeup chip */ + aty128_set_suspend(par, 0); + par->asleep = 0; + + /* Restore display & engine */ + aty128_reset_engine(par); + wait_for_idle(par); + aty128fb_set_par(info); + fb_pan_display(info, &info->var); + fb_set_cmap(&info->cmap, info); + + /* Refresh */ + fb_set_suspend(info, 0); + + /* Unblank */ + par->lock_blank = 0; + aty128fb_blank(0, info); + +#ifdef CONFIG_PPC_PMAC + /* On powermac, we have hooks to properly suspend/resume AGP now, + * use them here. We'll ultimately need some generic support here, + * but the generic code isn't quite ready for that yet + */ + pmac_resume_agp_for_card(pdev); +#endif /* CONFIG_PPC_PMAC */ + + pdev->dev.power.power_state = PMSG_ON; + + printk(KERN_DEBUG "aty128fb: resumed !\n"); + + return 0; +} + +static int aty128_pci_resume(struct pci_dev *pdev) +{ + int rc; + + console_lock(); + rc = aty128_do_resume(pdev); + console_unlock(); + + return rc; +} + + +static int aty128fb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("aty128fb", &option)) + return -ENODEV; + aty128fb_setup(option); +#endif + + return pci_register_driver(&aty128fb_driver); +} + +static void __exit aty128fb_exit(void) +{ + pci_unregister_driver(&aty128fb_driver); +} + +module_init(aty128fb_init); + +module_exit(aty128fb_exit); + +MODULE_AUTHOR("(c)1999-2003 Brad Douglas <brad@neruo.com>"); +MODULE_DESCRIPTION("FBDev driver for ATI Rage128 / Pro cards"); +MODULE_LICENSE("GPL"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); +#ifdef CONFIG_MTRR +module_param_named(nomtrr, mtrr, invbool, 0); +MODULE_PARM_DESC(nomtrr, "bool: Disable MTRR support (0 or 1=disabled) (default=0)"); +#endif + diff --git a/drivers/video/fbdev/aty/atyfb.h b/drivers/video/fbdev/aty/atyfb.h new file mode 100644 index 000000000000..1f39a62f899b --- /dev/null +++ b/drivers/video/fbdev/aty/atyfb.h @@ -0,0 +1,369 @@ +/* + * ATI Frame Buffer Device Driver Core Definitions + */ + +#include <linux/spinlock.h> +#include <linux/wait.h> + /* + * Elements of the hardware specific atyfb_par structure + */ + +struct crtc { + u32 vxres; + u32 vyres; + u32 xoffset; + u32 yoffset; + u32 bpp; + u32 h_tot_disp; + u32 h_sync_strt_wid; + u32 v_tot_disp; + u32 v_sync_strt_wid; + u32 vline_crnt_vline; + u32 off_pitch; + u32 gen_cntl; + u32 dp_pix_width; /* acceleration */ + u32 dp_chain_mask; /* acceleration */ +#ifdef CONFIG_FB_ATY_GENERIC_LCD + u32 horz_stretching; + u32 vert_stretching; + u32 ext_vert_stretch; + u32 shadow_h_tot_disp; + u32 shadow_h_sync_strt_wid; + u32 shadow_v_tot_disp; + u32 shadow_v_sync_strt_wid; + u32 lcd_gen_cntl; + u32 lcd_config_panel; + u32 lcd_index; +#endif +}; + +struct aty_interrupt { + wait_queue_head_t wait; + unsigned int count; + int pan_display; +}; + +struct pll_info { + int pll_max; + int pll_min; + int sclk, mclk, mclk_pm, xclk; + int ref_div; + int ref_clk; + int ecp_max; +}; + +typedef struct { + u16 unknown1; + u16 PCLK_min_freq; + u16 PCLK_max_freq; + u16 unknown2; + u16 ref_freq; + u16 ref_divider; + u16 unknown3; + u16 MCLK_pwd; + u16 MCLK_max_freq; + u16 XCLK_max_freq; + u16 SCLK_freq; +} __attribute__ ((packed)) PLL_BLOCK_MACH64; + +struct pll_514 { + u8 m; + u8 n; +}; + +struct pll_18818 { + u32 program_bits; + u32 locationAddr; + u32 period_in_ps; + u32 post_divider; +}; + +struct pll_ct { + u8 pll_ref_div; + u8 pll_gen_cntl; + u8 mclk_fb_div; + u8 mclk_fb_mult; /* 2 ro 4 */ + u8 sclk_fb_div; + u8 pll_vclk_cntl; + u8 vclk_post_div; + u8 vclk_fb_div; + u8 pll_ext_cntl; + u8 ext_vpll_cntl; + u8 spll_cntl2; + u32 dsp_config; /* Mach64 GTB DSP */ + u32 dsp_on_off; /* Mach64 GTB DSP */ + u32 dsp_loop_latency; + u32 fifo_size; + u32 xclkpagefaultdelay; + u32 xclkmaxrasdelay; + u8 xclk_ref_div; + u8 xclk_post_div; + u8 mclk_post_div_real; + u8 xclk_post_div_real; + u8 vclk_post_div_real; + u8 features; +#ifdef CONFIG_FB_ATY_GENERIC_LCD + u32 xres; /* use for LCD stretching/scaling */ +#endif +}; + +/* + for pll_ct.features +*/ +#define DONT_USE_SPLL 0x1 +#define DONT_USE_XDLL 0x2 +#define USE_CPUCLK 0x4 +#define POWERDOWN_PLL 0x8 + +union aty_pll { + struct pll_ct ct; + struct pll_514 ibm514; + struct pll_18818 ics2595; +}; + + /* + * The hardware parameters for each card + */ + +struct atyfb_par { + u32 pseudo_palette[16]; + struct { u8 red, green, blue; } palette[256]; + const struct aty_dac_ops *dac_ops; + const struct aty_pll_ops *pll_ops; + void __iomem *ati_regbase; + unsigned long clk_wr_offset; /* meaning overloaded, clock id by CT */ + struct crtc crtc; + union aty_pll pll; + struct pll_info pll_limits; + u32 features; + u32 ref_clk_per; + u32 pll_per; + u32 mclk_per; + u32 xclk_per; + u8 bus_type; + u8 ram_type; + u8 mem_refresh_rate; + u16 pci_id; + u32 accel_flags; + int blitter_may_be_busy; + int asleep; + int lock_blank; + unsigned long res_start; + unsigned long res_size; + struct pci_dev *pdev; +#ifdef __sparc__ + struct pci_mmap_map *mmap_map; + u8 mmaped; +#endif + int open; +#ifdef CONFIG_FB_ATY_GENERIC_LCD + unsigned long bios_base_phys; + unsigned long bios_base; + unsigned long lcd_table; + u16 lcd_width; + u16 lcd_height; + u32 lcd_pixclock; + u16 lcd_refreshrate; + u16 lcd_htotal; + u16 lcd_hdisp; + u16 lcd_hsync_dly; + u16 lcd_hsync_len; + u16 lcd_vtotal; + u16 lcd_vdisp; + u16 lcd_vsync_len; + u16 lcd_right_margin; + u16 lcd_lower_margin; + u16 lcd_hblank_len; + u16 lcd_vblank_len; +#endif + unsigned long aux_start; /* auxiliary aperture */ + unsigned long aux_size; + struct aty_interrupt vblank; + unsigned long irq_flags; + unsigned int irq; + spinlock_t int_lock; +#ifdef CONFIG_MTRR + int mtrr_aper; + int mtrr_reg; +#endif + u32 mem_cntl; + struct crtc saved_crtc; + union aty_pll saved_pll; +}; + + /* + * ATI Mach64 features + */ + +#define M64_HAS(feature) ((par)->features & (M64F_##feature)) + +#define M64F_RESET_3D 0x00000001 +#define M64F_MAGIC_FIFO 0x00000002 +#define M64F_GTB_DSP 0x00000004 +#define M64F_FIFO_32 0x00000008 +#define M64F_SDRAM_MAGIC_PLL 0x00000010 +#define M64F_MAGIC_POSTDIV 0x00000020 +#define M64F_INTEGRATED 0x00000040 +#define M64F_CT_BUS 0x00000080 +#define M64F_VT_BUS 0x00000100 +#define M64F_MOBIL_BUS 0x00000200 +#define M64F_GX 0x00000400 +#define M64F_CT 0x00000800 +#define M64F_VT 0x00001000 +#define M64F_GT 0x00002000 +#define M64F_MAGIC_VRAM_SIZE 0x00004000 +#define M64F_G3_PB_1_1 0x00008000 +#define M64F_G3_PB_1024x768 0x00010000 +#define M64F_EXTRA_BRIGHT 0x00020000 +#define M64F_LT_LCD_REGS 0x00040000 +#define M64F_XL_DLL 0x00080000 +#define M64F_MFB_FORCE_4 0x00100000 +#define M64F_HW_TRIPLE 0x00200000 +#define M64F_XL_MEM 0x00400000 + /* + * Register access + */ + +static inline u32 aty_ld_le32(int regindex, const struct atyfb_par *par) +{ + /* Hack for bloc 1, should be cleanly optimized by compiler */ + if (regindex >= 0x400) + regindex -= 0x800; + +#ifdef CONFIG_ATARI + return in_le32(par->ati_regbase + regindex); +#else + return readl(par->ati_regbase + regindex); +#endif +} + +static inline void aty_st_le32(int regindex, u32 val, const struct atyfb_par *par) +{ + /* Hack for bloc 1, should be cleanly optimized by compiler */ + if (regindex >= 0x400) + regindex -= 0x800; + +#ifdef CONFIG_ATARI + out_le32(par->ati_regbase + regindex, val); +#else + writel(val, par->ati_regbase + regindex); +#endif +} + +static inline void aty_st_le16(int regindex, u16 val, + const struct atyfb_par *par) +{ + /* Hack for bloc 1, should be cleanly optimized by compiler */ + if (regindex >= 0x400) + regindex -= 0x800; +#ifdef CONFIG_ATARI + out_le16(par->ati_regbase + regindex, val); +#else + writel(val, par->ati_regbase + regindex); +#endif +} + +static inline u8 aty_ld_8(int regindex, const struct atyfb_par *par) +{ + /* Hack for bloc 1, should be cleanly optimized by compiler */ + if (regindex >= 0x400) + regindex -= 0x800; +#ifdef CONFIG_ATARI + return in_8(par->ati_regbase + regindex); +#else + return readb(par->ati_regbase + regindex); +#endif +} + +static inline void aty_st_8(int regindex, u8 val, const struct atyfb_par *par) +{ + /* Hack for bloc 1, should be cleanly optimized by compiler */ + if (regindex >= 0x400) + regindex -= 0x800; + +#ifdef CONFIG_ATARI + out_8(par->ati_regbase + regindex, val); +#else + writeb(val, par->ati_regbase + regindex); +#endif +} + +#if defined(CONFIG_PM) || defined(CONFIG_PMAC_BACKLIGHT) || \ +defined (CONFIG_FB_ATY_GENERIC_LCD) || defined (CONFIG_FB_ATY_BACKLIGHT) +extern void aty_st_lcd(int index, u32 val, const struct atyfb_par *par); +extern u32 aty_ld_lcd(int index, const struct atyfb_par *par); +#endif + + /* + * DAC operations + */ + +struct aty_dac_ops { + int (*set_dac) (const struct fb_info * info, + const union aty_pll * pll, u32 bpp, u32 accel); +}; + +extern const struct aty_dac_ops aty_dac_ibm514; /* IBM RGB514 */ +extern const struct aty_dac_ops aty_dac_ati68860b; /* ATI 68860-B */ +extern const struct aty_dac_ops aty_dac_att21c498; /* AT&T 21C498 */ +extern const struct aty_dac_ops aty_dac_unsupported; /* unsupported */ +extern const struct aty_dac_ops aty_dac_ct; /* Integrated */ + + + /* + * Clock operations + */ + +struct aty_pll_ops { + int (*var_to_pll) (const struct fb_info * info, u32 vclk_per, u32 bpp, union aty_pll * pll); + u32 (*pll_to_var) (const struct fb_info * info, const union aty_pll * pll); + void (*set_pll) (const struct fb_info * info, const union aty_pll * pll); + void (*get_pll) (const struct fb_info *info, union aty_pll * pll); + int (*init_pll) (const struct fb_info * info, union aty_pll * pll); + void (*resume_pll)(const struct fb_info *info, union aty_pll *pll); +}; + +extern const struct aty_pll_ops aty_pll_ati18818_1; /* ATI 18818 */ +extern const struct aty_pll_ops aty_pll_stg1703; /* STG 1703 */ +extern const struct aty_pll_ops aty_pll_ch8398; /* Chrontel 8398 */ +extern const struct aty_pll_ops aty_pll_att20c408; /* AT&T 20C408 */ +extern const struct aty_pll_ops aty_pll_ibm514; /* IBM RGB514 */ +extern const struct aty_pll_ops aty_pll_unsupported; /* unsupported */ +extern const struct aty_pll_ops aty_pll_ct; /* Integrated */ + + +extern void aty_set_pll_ct(const struct fb_info *info, const union aty_pll *pll); +extern u8 aty_ld_pll_ct(int offset, const struct atyfb_par *par); + + + /* + * Hardware cursor support + */ + +extern int aty_init_cursor(struct fb_info *info); + + /* + * Hardware acceleration + */ + +static inline void wait_for_fifo(u16 entries, const struct atyfb_par *par) +{ + while ((aty_ld_le32(FIFO_STAT, par) & 0xffff) > + ((u32) (0x8000 >> entries))); +} + +static inline void wait_for_idle(struct atyfb_par *par) +{ + wait_for_fifo(16, par); + while ((aty_ld_le32(GUI_STAT, par) & 1) != 0); + par->blitter_may_be_busy = 0; +} + +extern void aty_reset_engine(const struct atyfb_par *par); +extern void aty_init_engine(struct atyfb_par *par, struct fb_info *info); +extern u8 aty_ld_pll_ct(int offset, const struct atyfb_par *par); + +void atyfb_copyarea(struct fb_info *info, const struct fb_copyarea *area); +void atyfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect); +void atyfb_imageblit(struct fb_info *info, const struct fb_image *image); + diff --git a/drivers/video/fbdev/aty/atyfb_base.c b/drivers/video/fbdev/aty/atyfb_base.c new file mode 100644 index 000000000000..c3d0074a32db --- /dev/null +++ b/drivers/video/fbdev/aty/atyfb_base.c @@ -0,0 +1,4029 @@ +/* + * ATI Frame Buffer Device Driver Core + * + * Copyright (C) 2004 Alex Kern <alex.kern@gmx.de> + * Copyright (C) 1997-2001 Geert Uytterhoeven + * Copyright (C) 1998 Bernd Harries + * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be) + * + * This driver supports the following ATI graphics chips: + * - ATI Mach64 + * + * To do: add support for + * - ATI Rage128 (from aty128fb.c) + * - ATI Radeon (from radeonfb.c) + * + * This driver is partly based on the PowerMac console driver: + * + * Copyright (C) 1996 Paul Mackerras + * + * and on the PowerMac ATI/mach64 display driver: + * + * Copyright (C) 1997 Michael AK Tesch + * + * with work by Jon Howell + * Harry AC Eaton + * Anthony Tong <atong@uiuc.edu> + * + * Generic LCD support written by Daniel Mantione, ported from 2.4.20 by Alex Kern + * Many Thanks to Ville Syrjälä for patches and fixing nasting 16 bit color bug. + * + * 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. + * + * Many thanks to Nitya from ATI devrel for support and patience ! + */ + +/****************************************************************************** + + TODO: + + - cursor support on all cards and all ramdacs. + - cursor parameters controlable via ioctl()s. + - guess PLL and MCLK based on the original PLL register values initialized + by Open Firmware (if they are initialized). BIOS is done + + (Anyone with Mac to help with this?) + +******************************************************************************/ + + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/compiler.h> +#include <linux/console.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/backlight.h> +#include <linux/reboot.h> +#include <linux/dmi.h> + +#include <asm/io.h> +#include <linux/uaccess.h> + +#include <video/mach64.h> +#include "atyfb.h" +#include "ati_ids.h" + +#ifdef __powerpc__ +#include <asm/machdep.h> +#include <asm/prom.h> +#include "../macmodes.h" +#endif +#ifdef __sparc__ +#include <asm/fbio.h> +#include <asm/oplib.h> +#include <asm/prom.h> +#endif + +#ifdef CONFIG_ADB_PMU +#include <linux/adb.h> +#include <linux/pmu.h> +#endif +#ifdef CONFIG_BOOTX_TEXT +#include <asm/btext.h> +#endif +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +/* + * Debug flags. + */ +#undef DEBUG +/*#define DEBUG*/ + +/* Make sure n * PAGE_SIZE is protected at end of Aperture for GUI-regs */ +/* - must be large enough to catch all GUI-Regs */ +/* - must be aligned to a PAGE boundary */ +#define GUI_RESERVE (1 * PAGE_SIZE) + +/* FIXME: remove the FAIL definition */ +#define FAIL(msg) do { \ + if (!(var->activate & FB_ACTIVATE_TEST)) \ + printk(KERN_CRIT "atyfb: " msg "\n"); \ + return -EINVAL; \ +} while (0) +#define FAIL_MAX(msg, x, _max_) do { \ + if (x > _max_) { \ + if (!(var->activate & FB_ACTIVATE_TEST)) \ + printk(KERN_CRIT "atyfb: " msg " %x(%x)\n", x, _max_); \ + return -EINVAL; \ + } \ +} while (0) +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(KERN_DEBUG "atyfb: " fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#define PRINTKI(fmt, args...) printk(KERN_INFO "atyfb: " fmt, ## args) +#define PRINTKE(fmt, args...) printk(KERN_ERR "atyfb: " fmt, ## args) + +#if defined(CONFIG_PM) || defined(CONFIG_PMAC_BACKLIGHT) || \ +defined (CONFIG_FB_ATY_GENERIC_LCD) || defined(CONFIG_FB_ATY_BACKLIGHT) +static const u32 lt_lcd_regs[] = { + CNFG_PANEL_LG, + LCD_GEN_CNTL_LG, + DSTN_CONTROL_LG, + HFB_PITCH_ADDR_LG, + HORZ_STRETCHING_LG, + VERT_STRETCHING_LG, + 0, /* EXT_VERT_STRETCH */ + LT_GIO_LG, + POWER_MANAGEMENT_LG +}; + +void aty_st_lcd(int index, u32 val, const struct atyfb_par *par) +{ + if (M64_HAS(LT_LCD_REGS)) { + aty_st_le32(lt_lcd_regs[index], val, par); + } else { + unsigned long temp; + + /* write addr byte */ + temp = aty_ld_le32(LCD_INDEX, par); + aty_st_le32(LCD_INDEX, (temp & ~LCD_INDEX_MASK) | index, par); + /* write the register value */ + aty_st_le32(LCD_DATA, val, par); + } +} + +u32 aty_ld_lcd(int index, const struct atyfb_par *par) +{ + if (M64_HAS(LT_LCD_REGS)) { + return aty_ld_le32(lt_lcd_regs[index], par); + } else { + unsigned long temp; + + /* write addr byte */ + temp = aty_ld_le32(LCD_INDEX, par); + aty_st_le32(LCD_INDEX, (temp & ~LCD_INDEX_MASK) | index, par); + /* read the register value */ + return aty_ld_le32(LCD_DATA, par); + } +} +#endif /* defined(CONFIG_PM) || defined(CONFIG_PMAC_BACKLIGHT) || defined (CONFIG_FB_ATY_GENERIC_LCD) */ + +#ifdef CONFIG_FB_ATY_GENERIC_LCD +/* + * ATIReduceRatio -- + * + * Reduce a fraction by factoring out the largest common divider of the + * fraction's numerator and denominator. + */ +static void ATIReduceRatio(int *Numerator, int *Denominator) +{ + int Multiplier, Divider, Remainder; + + Multiplier = *Numerator; + Divider = *Denominator; + + while ((Remainder = Multiplier % Divider)) { + Multiplier = Divider; + Divider = Remainder; + } + + *Numerator /= Divider; + *Denominator /= Divider; +} +#endif +/* + * The Hardware parameters for each card + */ + +struct pci_mmap_map { + unsigned long voff; + unsigned long poff; + unsigned long size; + unsigned long prot_flag; + unsigned long prot_mask; +}; + +static struct fb_fix_screeninfo atyfb_fix = { + .id = "ATY Mach64", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 8, + .ypanstep = 1, +}; + +/* + * Frame buffer device API + */ + +static int atyfb_open(struct fb_info *info, int user); +static int atyfb_release(struct fb_info *info, int user); +static int atyfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int atyfb_set_par(struct fb_info *info); +static int atyfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int atyfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int atyfb_blank(int blank, struct fb_info *info); +static int atyfb_ioctl(struct fb_info *info, u_int cmd, u_long arg); +#ifdef __sparc__ +static int atyfb_mmap(struct fb_info *info, struct vm_area_struct *vma); +#endif +static int atyfb_sync(struct fb_info *info); + +/* + * Internal routines + */ + +static int aty_init(struct fb_info *info); + +static void aty_get_crtc(const struct atyfb_par *par, struct crtc *crtc); + +static void aty_set_crtc(const struct atyfb_par *par, const struct crtc *crtc); +static int aty_var_to_crtc(const struct fb_info *info, + const struct fb_var_screeninfo *var, + struct crtc *crtc); +static int aty_crtc_to_var(const struct crtc *crtc, + struct fb_var_screeninfo *var); +static void set_off_pitch(struct atyfb_par *par, const struct fb_info *info); +#ifdef CONFIG_PPC +static int read_aty_sense(const struct atyfb_par *par); +#endif + +static DEFINE_MUTEX(reboot_lock); +static struct fb_info *reboot_info; + +/* + * Interface used by the world + */ + +static struct fb_var_screeninfo default_var = { + /* 640x480, 60 Hz, Non-Interlaced (25.175 MHz dotclock) */ + 640, 480, 640, 480, 0, 0, 8, 0, + {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0}, + 0, 0, -1, -1, 0, 39722, 48, 16, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED +}; + +static struct fb_videomode defmode = { + /* 640x480 @ 60 Hz, 31.5 kHz hsync */ + NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2, + 0, FB_VMODE_NONINTERLACED +}; + +static struct fb_ops atyfb_ops = { + .owner = THIS_MODULE, + .fb_open = atyfb_open, + .fb_release = atyfb_release, + .fb_check_var = atyfb_check_var, + .fb_set_par = atyfb_set_par, + .fb_setcolreg = atyfb_setcolreg, + .fb_pan_display = atyfb_pan_display, + .fb_blank = atyfb_blank, + .fb_ioctl = atyfb_ioctl, + .fb_fillrect = atyfb_fillrect, + .fb_copyarea = atyfb_copyarea, + .fb_imageblit = atyfb_imageblit, +#ifdef __sparc__ + .fb_mmap = atyfb_mmap, +#endif + .fb_sync = atyfb_sync, +}; + +static bool noaccel; +#ifdef CONFIG_MTRR +static bool nomtrr; +#endif +static int vram; +static int pll; +static int mclk; +static int xclk; +static int comp_sync = -1; +static char *mode; + +#ifdef CONFIG_PMAC_BACKLIGHT +static int backlight = 1; +#else +static int backlight = 0; +#endif + +#ifdef CONFIG_PPC +static int default_vmode = VMODE_CHOOSE; +static int default_cmode = CMODE_CHOOSE; + +module_param_named(vmode, default_vmode, int, 0); +MODULE_PARM_DESC(vmode, "int: video mode for mac"); +module_param_named(cmode, default_cmode, int, 0); +MODULE_PARM_DESC(cmode, "int: color mode for mac"); +#endif + +#ifdef CONFIG_ATARI +static unsigned int mach64_count = 0; +static unsigned long phys_vmembase[FB_MAX] = { 0, }; +static unsigned long phys_size[FB_MAX] = { 0, }; +static unsigned long phys_guiregbase[FB_MAX] = { 0, }; +#endif + +/* top -> down is an evolution of mach64 chipset, any corrections? */ +#define ATI_CHIP_88800GX (M64F_GX) +#define ATI_CHIP_88800CX (M64F_GX) + +#define ATI_CHIP_264CT (M64F_CT | M64F_INTEGRATED | M64F_CT_BUS | M64F_MAGIC_FIFO) +#define ATI_CHIP_264ET (M64F_CT | M64F_INTEGRATED | M64F_CT_BUS | M64F_MAGIC_FIFO) + +#define ATI_CHIP_264VT (M64F_VT | M64F_INTEGRATED | M64F_VT_BUS | M64F_MAGIC_FIFO) +#define ATI_CHIP_264GT (M64F_GT | M64F_INTEGRATED | M64F_MAGIC_FIFO | M64F_EXTRA_BRIGHT) + +#define ATI_CHIP_264VTB (M64F_VT | M64F_INTEGRATED | M64F_VT_BUS | M64F_GTB_DSP) +#define ATI_CHIP_264VT3 (M64F_VT | M64F_INTEGRATED | M64F_VT_BUS | M64F_GTB_DSP | M64F_SDRAM_MAGIC_PLL) +#define ATI_CHIP_264VT4 (M64F_VT | M64F_INTEGRATED | M64F_GTB_DSP) + +/* FIXME what is this chip? */ +#define ATI_CHIP_264LT (M64F_GT | M64F_INTEGRATED | M64F_GTB_DSP) + +/* make sets shorter */ +#define ATI_MODERN_SET (M64F_GT | M64F_INTEGRATED | M64F_GTB_DSP | M64F_EXTRA_BRIGHT) + +#define ATI_CHIP_264GTB (ATI_MODERN_SET | M64F_SDRAM_MAGIC_PLL) +/*#define ATI_CHIP_264GTDVD ?*/ +#define ATI_CHIP_264LTG (ATI_MODERN_SET | M64F_SDRAM_MAGIC_PLL) + +#define ATI_CHIP_264GT2C (ATI_MODERN_SET | M64F_SDRAM_MAGIC_PLL | M64F_HW_TRIPLE) +#define ATI_CHIP_264GTPRO (ATI_MODERN_SET | M64F_SDRAM_MAGIC_PLL | M64F_HW_TRIPLE | M64F_FIFO_32 | M64F_RESET_3D) +#define ATI_CHIP_264LTPRO (ATI_MODERN_SET | M64F_HW_TRIPLE | M64F_FIFO_32 | M64F_RESET_3D) + +#define ATI_CHIP_264XL (ATI_MODERN_SET | M64F_HW_TRIPLE | M64F_FIFO_32 | M64F_RESET_3D | M64F_XL_DLL | M64F_MFB_FORCE_4 | M64F_XL_MEM) +#define ATI_CHIP_MOBILITY (ATI_MODERN_SET | M64F_HW_TRIPLE | M64F_FIFO_32 | M64F_RESET_3D | M64F_XL_DLL | M64F_MFB_FORCE_4 | M64F_XL_MEM | M64F_MOBIL_BUS) + +static struct { + u16 pci_id; + const char *name; + int pll, mclk, xclk, ecp_max; + u32 features; +} aty_chips[] = { +#ifdef CONFIG_FB_ATY_GX + /* Mach64 GX */ + { PCI_CHIP_MACH64GX, "ATI888GX00 (Mach64 GX)", 135, 50, 50, 0, ATI_CHIP_88800GX }, + { PCI_CHIP_MACH64CX, "ATI888CX00 (Mach64 CX)", 135, 50, 50, 0, ATI_CHIP_88800CX }, +#endif /* CONFIG_FB_ATY_GX */ + +#ifdef CONFIG_FB_ATY_CT + { PCI_CHIP_MACH64CT, "ATI264CT (Mach64 CT)", 135, 60, 60, 0, ATI_CHIP_264CT }, + { PCI_CHIP_MACH64ET, "ATI264ET (Mach64 ET)", 135, 60, 60, 0, ATI_CHIP_264ET }, + + /* FIXME what is this chip? */ + { PCI_CHIP_MACH64LT, "ATI264LT (Mach64 LT)", 135, 63, 63, 0, ATI_CHIP_264LT }, + + { PCI_CHIP_MACH64VT, "ATI264VT (Mach64 VT)", 170, 67, 67, 80, ATI_CHIP_264VT }, + { PCI_CHIP_MACH64GT, "3D RAGE (Mach64 GT)", 135, 63, 63, 80, ATI_CHIP_264GT }, + + { PCI_CHIP_MACH64VU, "ATI264VT3 (Mach64 VU)", 200, 67, 67, 80, ATI_CHIP_264VT3 }, + { PCI_CHIP_MACH64GU, "3D RAGE II+ (Mach64 GU)", 200, 67, 67, 100, ATI_CHIP_264GTB }, + + { PCI_CHIP_MACH64LG, "3D RAGE LT (Mach64 LG)", 230, 63, 63, 100, ATI_CHIP_264LTG | M64F_LT_LCD_REGS | M64F_G3_PB_1024x768 }, + + { PCI_CHIP_MACH64VV, "ATI264VT4 (Mach64 VV)", 230, 83, 83, 100, ATI_CHIP_264VT4 }, + + { PCI_CHIP_MACH64GV, "3D RAGE IIC (Mach64 GV, PCI)", 230, 83, 83, 100, ATI_CHIP_264GT2C }, + { PCI_CHIP_MACH64GW, "3D RAGE IIC (Mach64 GW, AGP)", 230, 83, 83, 100, ATI_CHIP_264GT2C }, + { PCI_CHIP_MACH64GY, "3D RAGE IIC (Mach64 GY, PCI)", 230, 83, 83, 100, ATI_CHIP_264GT2C }, + { PCI_CHIP_MACH64GZ, "3D RAGE IIC (Mach64 GZ, AGP)", 230, 83, 83, 100, ATI_CHIP_264GT2C }, + + { PCI_CHIP_MACH64GB, "3D RAGE PRO (Mach64 GB, BGA, AGP)", 230, 100, 100, 125, ATI_CHIP_264GTPRO }, + { PCI_CHIP_MACH64GD, "3D RAGE PRO (Mach64 GD, BGA, AGP 1x)", 230, 100, 100, 125, ATI_CHIP_264GTPRO }, + { PCI_CHIP_MACH64GI, "3D RAGE PRO (Mach64 GI, BGA, PCI)", 230, 100, 100, 125, ATI_CHIP_264GTPRO | M64F_MAGIC_VRAM_SIZE }, + { PCI_CHIP_MACH64GP, "3D RAGE PRO (Mach64 GP, PQFP, PCI)", 230, 100, 100, 125, ATI_CHIP_264GTPRO }, + { PCI_CHIP_MACH64GQ, "3D RAGE PRO (Mach64 GQ, PQFP, PCI, limited 3D)", 230, 100, 100, 125, ATI_CHIP_264GTPRO }, + + { PCI_CHIP_MACH64LB, "3D RAGE LT PRO (Mach64 LB, AGP)", 236, 75, 100, 135, ATI_CHIP_264LTPRO }, + { PCI_CHIP_MACH64LD, "3D RAGE LT PRO (Mach64 LD, AGP)", 230, 100, 100, 135, ATI_CHIP_264LTPRO }, + { PCI_CHIP_MACH64LI, "3D RAGE LT PRO (Mach64 LI, PCI)", 230, 100, 100, 135, ATI_CHIP_264LTPRO | M64F_G3_PB_1_1 | M64F_G3_PB_1024x768 }, + { PCI_CHIP_MACH64LP, "3D RAGE LT PRO (Mach64 LP, PCI)", 230, 100, 100, 135, ATI_CHIP_264LTPRO | M64F_G3_PB_1024x768 }, + { PCI_CHIP_MACH64LQ, "3D RAGE LT PRO (Mach64 LQ, PCI)", 230, 100, 100, 135, ATI_CHIP_264LTPRO }, + + { PCI_CHIP_MACH64GM, "3D RAGE XL (Mach64 GM, AGP 2x)", 230, 83, 63, 135, ATI_CHIP_264XL }, + { PCI_CHIP_MACH64GN, "3D RAGE XC (Mach64 GN, AGP 2x)", 230, 83, 63, 135, ATI_CHIP_264XL }, + { PCI_CHIP_MACH64GO, "3D RAGE XL (Mach64 GO, PCI-66)", 230, 83, 63, 135, ATI_CHIP_264XL }, + { PCI_CHIP_MACH64GL, "3D RAGE XC (Mach64 GL, PCI-66)", 230, 83, 63, 135, ATI_CHIP_264XL }, + { PCI_CHIP_MACH64GR, "3D RAGE XL (Mach64 GR, PCI-33)", 230, 83, 63, 135, ATI_CHIP_264XL | M64F_SDRAM_MAGIC_PLL }, + { PCI_CHIP_MACH64GS, "3D RAGE XC (Mach64 GS, PCI-33)", 230, 83, 63, 135, ATI_CHIP_264XL }, + + { PCI_CHIP_MACH64LM, "3D RAGE Mobility P/M (Mach64 LM, AGP 2x)", 230, 83, 125, 135, ATI_CHIP_MOBILITY }, + { PCI_CHIP_MACH64LN, "3D RAGE Mobility L (Mach64 LN, AGP 2x)", 230, 83, 125, 135, ATI_CHIP_MOBILITY }, + { PCI_CHIP_MACH64LR, "3D RAGE Mobility P/M (Mach64 LR, PCI)", 230, 83, 125, 135, ATI_CHIP_MOBILITY }, + { PCI_CHIP_MACH64LS, "3D RAGE Mobility L (Mach64 LS, PCI)", 230, 83, 125, 135, ATI_CHIP_MOBILITY }, +#endif /* CONFIG_FB_ATY_CT */ +}; + +static int correct_chipset(struct atyfb_par *par) +{ + u8 rev; + u16 type; + u32 chip_id; + const char *name; + int i; + + for (i = (int)ARRAY_SIZE(aty_chips) - 1; i >= 0; i--) + if (par->pci_id == aty_chips[i].pci_id) + break; + + if (i < 0) + return -ENODEV; + + name = aty_chips[i].name; + par->pll_limits.pll_max = aty_chips[i].pll; + par->pll_limits.mclk = aty_chips[i].mclk; + par->pll_limits.xclk = aty_chips[i].xclk; + par->pll_limits.ecp_max = aty_chips[i].ecp_max; + par->features = aty_chips[i].features; + + chip_id = aty_ld_le32(CNFG_CHIP_ID, par); + type = chip_id & CFG_CHIP_TYPE; + rev = (chip_id & CFG_CHIP_REV) >> 24; + + switch (par->pci_id) { +#ifdef CONFIG_FB_ATY_GX + case PCI_CHIP_MACH64GX: + if (type != 0x00d7) + return -ENODEV; + break; + case PCI_CHIP_MACH64CX: + if (type != 0x0057) + return -ENODEV; + break; +#endif +#ifdef CONFIG_FB_ATY_CT + case PCI_CHIP_MACH64VT: + switch (rev & 0x07) { + case 0x00: + switch (rev & 0xc0) { + case 0x00: + name = "ATI264VT (A3) (Mach64 VT)"; + par->pll_limits.pll_max = 170; + par->pll_limits.mclk = 67; + par->pll_limits.xclk = 67; + par->pll_limits.ecp_max = 80; + par->features = ATI_CHIP_264VT; + break; + case 0x40: + name = "ATI264VT2 (A4) (Mach64 VT)"; + par->pll_limits.pll_max = 200; + par->pll_limits.mclk = 67; + par->pll_limits.xclk = 67; + par->pll_limits.ecp_max = 80; + par->features = ATI_CHIP_264VT | M64F_MAGIC_POSTDIV; + break; + } + break; + case 0x01: + name = "ATI264VT3 (B1) (Mach64 VT)"; + par->pll_limits.pll_max = 200; + par->pll_limits.mclk = 67; + par->pll_limits.xclk = 67; + par->pll_limits.ecp_max = 80; + par->features = ATI_CHIP_264VTB; + break; + case 0x02: + name = "ATI264VT3 (B2) (Mach64 VT)"; + par->pll_limits.pll_max = 200; + par->pll_limits.mclk = 67; + par->pll_limits.xclk = 67; + par->pll_limits.ecp_max = 80; + par->features = ATI_CHIP_264VT3; + break; + } + break; + case PCI_CHIP_MACH64GT: + switch (rev & 0x07) { + case 0x01: + name = "3D RAGE II (Mach64 GT)"; + par->pll_limits.pll_max = 170; + par->pll_limits.mclk = 67; + par->pll_limits.xclk = 67; + par->pll_limits.ecp_max = 80; + par->features = ATI_CHIP_264GTB; + break; + case 0x02: + name = "3D RAGE II+ (Mach64 GT)"; + par->pll_limits.pll_max = 200; + par->pll_limits.mclk = 67; + par->pll_limits.xclk = 67; + par->pll_limits.ecp_max = 100; + par->features = ATI_CHIP_264GTB; + break; + } + break; +#endif + } + + PRINTKI("%s [0x%04x rev 0x%02x]\n", name, type, rev); + return 0; +} + +static char ram_dram[] __maybe_unused = "DRAM"; +static char ram_resv[] __maybe_unused = "RESV"; +#ifdef CONFIG_FB_ATY_GX +static char ram_vram[] = "VRAM"; +#endif /* CONFIG_FB_ATY_GX */ +#ifdef CONFIG_FB_ATY_CT +static char ram_edo[] = "EDO"; +static char ram_sdram[] = "SDRAM (1:1)"; +static char ram_sgram[] = "SGRAM (1:1)"; +static char ram_sdram32[] = "SDRAM (2:1) (32-bit)"; +static char ram_wram[] = "WRAM"; +static char ram_off[] = "OFF"; +#endif /* CONFIG_FB_ATY_CT */ + + +#ifdef CONFIG_FB_ATY_GX +static char *aty_gx_ram[8] = { + ram_dram, ram_vram, ram_vram, ram_dram, + ram_dram, ram_vram, ram_vram, ram_resv +}; +#endif /* CONFIG_FB_ATY_GX */ + +#ifdef CONFIG_FB_ATY_CT +static char *aty_ct_ram[8] = { + ram_off, ram_dram, ram_edo, ram_edo, + ram_sdram, ram_sgram, ram_wram, ram_resv +}; +static char *aty_xl_ram[8] = { + ram_off, ram_dram, ram_edo, ram_edo, + ram_sdram, ram_sgram, ram_sdram32, ram_resv +}; +#endif /* CONFIG_FB_ATY_CT */ + +static u32 atyfb_get_pixclock(struct fb_var_screeninfo *var, + struct atyfb_par *par) +{ + u32 pixclock = var->pixclock; +#ifdef CONFIG_FB_ATY_GENERIC_LCD + u32 lcd_on_off; + par->pll.ct.xres = 0; + if (par->lcd_table != 0) { + lcd_on_off = aty_ld_lcd(LCD_GEN_CNTL, par); + if (lcd_on_off & LCD_ON) { + par->pll.ct.xres = var->xres; + pixclock = par->lcd_pixclock; + } + } +#endif + return pixclock; +} + +#if defined(CONFIG_PPC) + +/* + * Apple monitor sense + */ + +static int read_aty_sense(const struct atyfb_par *par) +{ + int sense, i; + + aty_st_le32(GP_IO, 0x31003100, par); /* drive outputs high */ + __delay(200); + aty_st_le32(GP_IO, 0, par); /* turn off outputs */ + __delay(2000); + i = aty_ld_le32(GP_IO, par); /* get primary sense value */ + sense = ((i & 0x3000) >> 3) | (i & 0x100); + + /* drive each sense line low in turn and collect the other 2 */ + aty_st_le32(GP_IO, 0x20000000, par); /* drive A low */ + __delay(2000); + i = aty_ld_le32(GP_IO, par); + sense |= ((i & 0x1000) >> 7) | ((i & 0x100) >> 4); + aty_st_le32(GP_IO, 0x20002000, par); /* drive A high again */ + __delay(200); + + aty_st_le32(GP_IO, 0x10000000, par); /* drive B low */ + __delay(2000); + i = aty_ld_le32(GP_IO, par); + sense |= ((i & 0x2000) >> 10) | ((i & 0x100) >> 6); + aty_st_le32(GP_IO, 0x10001000, par); /* drive B high again */ + __delay(200); + + aty_st_le32(GP_IO, 0x01000000, par); /* drive C low */ + __delay(2000); + sense |= (aty_ld_le32(GP_IO, par) & 0x3000) >> 12; + aty_st_le32(GP_IO, 0, par); /* turn off outputs */ + return sense; +} + +#endif /* defined(CONFIG_PPC) */ + +/* ------------------------------------------------------------------------- */ + +/* + * CRTC programming + */ + +static void aty_get_crtc(const struct atyfb_par *par, struct crtc *crtc) +{ +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + if (!M64_HAS(LT_LCD_REGS)) { + crtc->lcd_index = aty_ld_le32(LCD_INDEX, par); + aty_st_le32(LCD_INDEX, crtc->lcd_index, par); + } + crtc->lcd_config_panel = aty_ld_lcd(CNFG_PANEL, par); + crtc->lcd_gen_cntl = aty_ld_lcd(LCD_GEN_CNTL, par); + + + /* switch to non shadow registers */ + aty_st_lcd(LCD_GEN_CNTL, crtc->lcd_gen_cntl & + ~(CRTC_RW_SELECT | SHADOW_EN | SHADOW_RW_EN), par); + + /* save stretching */ + crtc->horz_stretching = aty_ld_lcd(HORZ_STRETCHING, par); + crtc->vert_stretching = aty_ld_lcd(VERT_STRETCHING, par); + if (!M64_HAS(LT_LCD_REGS)) + crtc->ext_vert_stretch = aty_ld_lcd(EXT_VERT_STRETCH, par); + } +#endif + crtc->h_tot_disp = aty_ld_le32(CRTC_H_TOTAL_DISP, par); + crtc->h_sync_strt_wid = aty_ld_le32(CRTC_H_SYNC_STRT_WID, par); + crtc->v_tot_disp = aty_ld_le32(CRTC_V_TOTAL_DISP, par); + crtc->v_sync_strt_wid = aty_ld_le32(CRTC_V_SYNC_STRT_WID, par); + crtc->vline_crnt_vline = aty_ld_le32(CRTC_VLINE_CRNT_VLINE, par); + crtc->off_pitch = aty_ld_le32(CRTC_OFF_PITCH, par); + crtc->gen_cntl = aty_ld_le32(CRTC_GEN_CNTL, par); + +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + /* switch to shadow registers */ + aty_st_lcd(LCD_GEN_CNTL, (crtc->lcd_gen_cntl & ~CRTC_RW_SELECT) | + SHADOW_EN | SHADOW_RW_EN, par); + + crtc->shadow_h_tot_disp = aty_ld_le32(CRTC_H_TOTAL_DISP, par); + crtc->shadow_h_sync_strt_wid = aty_ld_le32(CRTC_H_SYNC_STRT_WID, par); + crtc->shadow_v_tot_disp = aty_ld_le32(CRTC_V_TOTAL_DISP, par); + crtc->shadow_v_sync_strt_wid = aty_ld_le32(CRTC_V_SYNC_STRT_WID, par); + + aty_st_le32(LCD_GEN_CNTL, crtc->lcd_gen_cntl, par); + } +#endif /* CONFIG_FB_ATY_GENERIC_LCD */ +} + +static void aty_set_crtc(const struct atyfb_par *par, const struct crtc *crtc) +{ +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + /* stop CRTC */ + aty_st_le32(CRTC_GEN_CNTL, crtc->gen_cntl & + ~(CRTC_EXT_DISP_EN | CRTC_EN), par); + + /* update non-shadow registers first */ + aty_st_lcd(CNFG_PANEL, crtc->lcd_config_panel, par); + aty_st_lcd(LCD_GEN_CNTL, crtc->lcd_gen_cntl & + ~(CRTC_RW_SELECT | SHADOW_EN | SHADOW_RW_EN), par); + + /* temporarily disable stretching */ + aty_st_lcd(HORZ_STRETCHING, crtc->horz_stretching & + ~(HORZ_STRETCH_MODE | HORZ_STRETCH_EN), par); + aty_st_lcd(VERT_STRETCHING, crtc->vert_stretching & + ~(VERT_STRETCH_RATIO1 | VERT_STRETCH_RATIO2 | + VERT_STRETCH_USE0 | VERT_STRETCH_EN), par); + } +#endif + /* turn off CRT */ + aty_st_le32(CRTC_GEN_CNTL, crtc->gen_cntl & ~CRTC_EN, par); + + DPRINTK("setting up CRTC\n"); + DPRINTK("set primary CRT to %ix%i %c%c composite %c\n", + ((((crtc->h_tot_disp >> 16) & 0xff) + 1) << 3), + (((crtc->v_tot_disp >> 16) & 0x7ff) + 1), + (crtc->h_sync_strt_wid & 0x200000) ? 'N' : 'P', + (crtc->v_sync_strt_wid & 0x200000) ? 'N' : 'P', + (crtc->gen_cntl & CRTC_CSYNC_EN) ? 'P' : 'N'); + + DPRINTK("CRTC_H_TOTAL_DISP: %x\n", crtc->h_tot_disp); + DPRINTK("CRTC_H_SYNC_STRT_WID: %x\n", crtc->h_sync_strt_wid); + DPRINTK("CRTC_V_TOTAL_DISP: %x\n", crtc->v_tot_disp); + DPRINTK("CRTC_V_SYNC_STRT_WID: %x\n", crtc->v_sync_strt_wid); + DPRINTK("CRTC_OFF_PITCH: %x\n", crtc->off_pitch); + DPRINTK("CRTC_VLINE_CRNT_VLINE: %x\n", crtc->vline_crnt_vline); + DPRINTK("CRTC_GEN_CNTL: %x\n", crtc->gen_cntl); + + aty_st_le32(CRTC_H_TOTAL_DISP, crtc->h_tot_disp, par); + aty_st_le32(CRTC_H_SYNC_STRT_WID, crtc->h_sync_strt_wid, par); + aty_st_le32(CRTC_V_TOTAL_DISP, crtc->v_tot_disp, par); + aty_st_le32(CRTC_V_SYNC_STRT_WID, crtc->v_sync_strt_wid, par); + aty_st_le32(CRTC_OFF_PITCH, crtc->off_pitch, par); + aty_st_le32(CRTC_VLINE_CRNT_VLINE, crtc->vline_crnt_vline, par); + + aty_st_le32(CRTC_GEN_CNTL, crtc->gen_cntl, par); +#if 0 + FIXME + if (par->accel_flags & FB_ACCELF_TEXT) + aty_init_engine(par, info); +#endif +#ifdef CONFIG_FB_ATY_GENERIC_LCD + /* after setting the CRTC registers we should set the LCD registers. */ + if (par->lcd_table != 0) { + /* switch to shadow registers */ + aty_st_lcd(LCD_GEN_CNTL, (crtc->lcd_gen_cntl & ~CRTC_RW_SELECT) | + SHADOW_EN | SHADOW_RW_EN, par); + + DPRINTK("set shadow CRT to %ix%i %c%c\n", + ((((crtc->shadow_h_tot_disp >> 16) & 0xff) + 1) << 3), + (((crtc->shadow_v_tot_disp >> 16) & 0x7ff) + 1), + (crtc->shadow_h_sync_strt_wid & 0x200000) ? 'N' : 'P', + (crtc->shadow_v_sync_strt_wid & 0x200000) ? 'N' : 'P'); + + DPRINTK("SHADOW CRTC_H_TOTAL_DISP: %x\n", + crtc->shadow_h_tot_disp); + DPRINTK("SHADOW CRTC_H_SYNC_STRT_WID: %x\n", + crtc->shadow_h_sync_strt_wid); + DPRINTK("SHADOW CRTC_V_TOTAL_DISP: %x\n", + crtc->shadow_v_tot_disp); + DPRINTK("SHADOW CRTC_V_SYNC_STRT_WID: %x\n", + crtc->shadow_v_sync_strt_wid); + + aty_st_le32(CRTC_H_TOTAL_DISP, crtc->shadow_h_tot_disp, par); + aty_st_le32(CRTC_H_SYNC_STRT_WID, crtc->shadow_h_sync_strt_wid, par); + aty_st_le32(CRTC_V_TOTAL_DISP, crtc->shadow_v_tot_disp, par); + aty_st_le32(CRTC_V_SYNC_STRT_WID, crtc->shadow_v_sync_strt_wid, par); + + /* restore CRTC selection & shadow state and enable stretching */ + DPRINTK("LCD_GEN_CNTL: %x\n", crtc->lcd_gen_cntl); + DPRINTK("HORZ_STRETCHING: %x\n", crtc->horz_stretching); + DPRINTK("VERT_STRETCHING: %x\n", crtc->vert_stretching); + if (!M64_HAS(LT_LCD_REGS)) + DPRINTK("EXT_VERT_STRETCH: %x\n", crtc->ext_vert_stretch); + + aty_st_lcd(LCD_GEN_CNTL, crtc->lcd_gen_cntl, par); + aty_st_lcd(HORZ_STRETCHING, crtc->horz_stretching, par); + aty_st_lcd(VERT_STRETCHING, crtc->vert_stretching, par); + if (!M64_HAS(LT_LCD_REGS)) { + aty_st_lcd(EXT_VERT_STRETCH, crtc->ext_vert_stretch, par); + aty_ld_le32(LCD_INDEX, par); + aty_st_le32(LCD_INDEX, crtc->lcd_index, par); + } + } +#endif /* CONFIG_FB_ATY_GENERIC_LCD */ +} + +static u32 calc_line_length(struct atyfb_par *par, u32 vxres, u32 bpp) +{ + u32 line_length = vxres * bpp / 8; + + if (par->ram_type == SGRAM || + (!M64_HAS(XL_MEM) && par->ram_type == WRAM)) + line_length = (line_length + 63) & ~63; + + return line_length; +} + +static int aty_var_to_crtc(const struct fb_info *info, + const struct fb_var_screeninfo *var, + struct crtc *crtc) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 xres, yres, vxres, vyres, xoffset, yoffset, bpp; + u32 sync, vmode, vdisplay; + u32 h_total, h_disp, h_sync_strt, h_sync_end, h_sync_dly, h_sync_wid, h_sync_pol; + u32 v_total, v_disp, v_sync_strt, v_sync_end, v_sync_wid, v_sync_pol, c_sync; + u32 pix_width, dp_pix_width, dp_chain_mask; + u32 line_length; + + /* input */ + xres = (var->xres + 7) & ~7; + yres = var->yres; + vxres = (var->xres_virtual + 7) & ~7; + vyres = var->yres_virtual; + xoffset = (var->xoffset + 7) & ~7; + yoffset = var->yoffset; + bpp = var->bits_per_pixel; + if (bpp == 16) + bpp = (var->green.length == 5) ? 15 : 16; + sync = var->sync; + vmode = var->vmode; + + /* convert (and round up) and validate */ + if (vxres < xres + xoffset) + vxres = xres + xoffset; + h_disp = xres; + + if (vyres < yres + yoffset) + vyres = yres + yoffset; + v_disp = yres; + + if (bpp <= 8) { + bpp = 8; + pix_width = CRTC_PIX_WIDTH_8BPP; + dp_pix_width = HOST_8BPP | SRC_8BPP | DST_8BPP | + BYTE_ORDER_LSB_TO_MSB; + dp_chain_mask = DP_CHAIN_8BPP; + } else if (bpp <= 15) { + bpp = 16; + pix_width = CRTC_PIX_WIDTH_15BPP; + dp_pix_width = HOST_15BPP | SRC_15BPP | DST_15BPP | + BYTE_ORDER_LSB_TO_MSB; + dp_chain_mask = DP_CHAIN_15BPP; + } else if (bpp <= 16) { + bpp = 16; + pix_width = CRTC_PIX_WIDTH_16BPP; + dp_pix_width = HOST_16BPP | SRC_16BPP | DST_16BPP | + BYTE_ORDER_LSB_TO_MSB; + dp_chain_mask = DP_CHAIN_16BPP; + } else if (bpp <= 24 && M64_HAS(INTEGRATED)) { + bpp = 24; + pix_width = CRTC_PIX_WIDTH_24BPP; + dp_pix_width = HOST_8BPP | SRC_8BPP | DST_8BPP | + BYTE_ORDER_LSB_TO_MSB; + dp_chain_mask = DP_CHAIN_24BPP; + } else if (bpp <= 32) { + bpp = 32; + pix_width = CRTC_PIX_WIDTH_32BPP; + dp_pix_width = HOST_32BPP | SRC_32BPP | DST_32BPP | + BYTE_ORDER_LSB_TO_MSB; + dp_chain_mask = DP_CHAIN_32BPP; + } else + FAIL("invalid bpp"); + + line_length = calc_line_length(par, vxres, bpp); + + if (vyres * line_length > info->fix.smem_len) + FAIL("not enough video RAM"); + + h_sync_pol = sync & FB_SYNC_HOR_HIGH_ACT ? 0 : 1; + v_sync_pol = sync & FB_SYNC_VERT_HIGH_ACT ? 0 : 1; + + if ((xres > 1920) || (yres > 1200)) { + FAIL("MACH64 chips are designed for max 1920x1200\n" + "select another resolution."); + } + h_sync_strt = h_disp + var->right_margin; + h_sync_end = h_sync_strt + var->hsync_len; + h_sync_dly = var->right_margin & 7; + h_total = h_sync_end + h_sync_dly + var->left_margin; + + v_sync_strt = v_disp + var->lower_margin; + v_sync_end = v_sync_strt + var->vsync_len; + v_total = v_sync_end + var->upper_margin; + +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + if (!M64_HAS(LT_LCD_REGS)) { + u32 lcd_index = aty_ld_le32(LCD_INDEX, par); + crtc->lcd_index = lcd_index & + ~(LCD_INDEX_MASK | LCD_DISPLAY_DIS | + LCD_SRC_SEL | CRTC2_DISPLAY_DIS); + aty_st_le32(LCD_INDEX, lcd_index, par); + } + + if (!M64_HAS(MOBIL_BUS)) + crtc->lcd_index |= CRTC2_DISPLAY_DIS; + + crtc->lcd_config_panel = aty_ld_lcd(CNFG_PANEL, par) | 0x4000; + crtc->lcd_gen_cntl = aty_ld_lcd(LCD_GEN_CNTL, par) & ~CRTC_RW_SELECT; + + crtc->lcd_gen_cntl &= + ~(HORZ_DIVBY2_EN | DIS_HOR_CRT_DIVBY2 | TVCLK_PM_EN | + /*VCLK_DAC_PM_EN | USE_SHADOWED_VEND |*/ + USE_SHADOWED_ROWCUR | SHADOW_EN | SHADOW_RW_EN); + crtc->lcd_gen_cntl |= DONT_SHADOW_VPAR | LOCK_8DOT; + + if ((crtc->lcd_gen_cntl & LCD_ON) && + ((xres > par->lcd_width) || (yres > par->lcd_height))) { + /* + * We cannot display the mode on the LCD. If the CRT is + * enabled we can turn off the LCD. + * If the CRT is off, it isn't a good idea to switch it + * on; we don't know if one is connected. So it's better + * to fail then. + */ + if (crtc->lcd_gen_cntl & CRT_ON) { + if (!(var->activate & FB_ACTIVATE_TEST)) + PRINTKI("Disable LCD panel, because video mode does not fit.\n"); + crtc->lcd_gen_cntl &= ~LCD_ON; + /*aty_st_lcd(LCD_GEN_CNTL, crtc->lcd_gen_cntl, par);*/ + } else { + if (!(var->activate & FB_ACTIVATE_TEST)) + PRINTKE("Video mode exceeds size of LCD panel.\nConnect this computer to a conventional monitor if you really need this mode.\n"); + return -EINVAL; + } + } + } + + if ((par->lcd_table != 0) && (crtc->lcd_gen_cntl & LCD_ON)) { + int VScan = 1; + /* bpp -> bytespp, 1,4 -> 0; 8 -> 2; 15,16 -> 1; 24 -> 6; 32 -> 5 + const u8 DFP_h_sync_dly_LT[] = { 0, 2, 1, 6, 5 }; + const u8 ADD_to_strt_wid_and_dly_LT_DAC[] = { 0, 5, 6, 9, 9, 12, 12 }; */ + + vmode &= ~(FB_VMODE_DOUBLE | FB_VMODE_INTERLACED); + + /* + * This is horror! When we simulate, say 640x480 on an 800x600 + * LCD monitor, the CRTC should be programmed 800x600 values for + * the non visible part, but 640x480 for the visible part. + * This code has been tested on a laptop with it's 1400x1050 LCD + * monitor and a conventional monitor both switched on. + * Tested modes: 1280x1024, 1152x864, 1024x768, 800x600, + * works with little glitches also with DOUBLESCAN modes + */ + if (yres < par->lcd_height) { + VScan = par->lcd_height / yres; + if (VScan > 1) { + VScan = 2; + vmode |= FB_VMODE_DOUBLE; + } + } + + h_sync_strt = h_disp + par->lcd_right_margin; + h_sync_end = h_sync_strt + par->lcd_hsync_len; + h_sync_dly = /*DFP_h_sync_dly[ ( bpp + 1 ) / 3 ]; */par->lcd_hsync_dly; + h_total = h_disp + par->lcd_hblank_len; + + v_sync_strt = v_disp + par->lcd_lower_margin / VScan; + v_sync_end = v_sync_strt + par->lcd_vsync_len / VScan; + v_total = v_disp + par->lcd_vblank_len / VScan; + } +#endif /* CONFIG_FB_ATY_GENERIC_LCD */ + + h_disp = (h_disp >> 3) - 1; + h_sync_strt = (h_sync_strt >> 3) - 1; + h_sync_end = (h_sync_end >> 3) - 1; + h_total = (h_total >> 3) - 1; + h_sync_wid = h_sync_end - h_sync_strt; + + FAIL_MAX("h_disp too large", h_disp, 0xff); + FAIL_MAX("h_sync_strt too large", h_sync_strt, 0x1ff); + /*FAIL_MAX("h_sync_wid too large", h_sync_wid, 0x1f);*/ + if (h_sync_wid > 0x1f) + h_sync_wid = 0x1f; + FAIL_MAX("h_total too large", h_total, 0x1ff); + + if (vmode & FB_VMODE_DOUBLE) { + v_disp <<= 1; + v_sync_strt <<= 1; + v_sync_end <<= 1; + v_total <<= 1; + } + + vdisplay = yres; +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if ((par->lcd_table != 0) && (crtc->lcd_gen_cntl & LCD_ON)) + vdisplay = par->lcd_height; +#endif + + v_disp--; + v_sync_strt--; + v_sync_end--; + v_total--; + v_sync_wid = v_sync_end - v_sync_strt; + + FAIL_MAX("v_disp too large", v_disp, 0x7ff); + FAIL_MAX("v_sync_stsrt too large", v_sync_strt, 0x7ff); + /*FAIL_MAX("v_sync_wid too large", v_sync_wid, 0x1f);*/ + if (v_sync_wid > 0x1f) + v_sync_wid = 0x1f; + FAIL_MAX("v_total too large", v_total, 0x7ff); + + c_sync = sync & FB_SYNC_COMP_HIGH_ACT ? CRTC_CSYNC_EN : 0; + + /* output */ + crtc->vxres = vxres; + crtc->vyres = vyres; + crtc->xoffset = xoffset; + crtc->yoffset = yoffset; + crtc->bpp = bpp; + crtc->off_pitch = + ((yoffset * line_length + xoffset * bpp / 8) / 8) | + ((line_length / bpp) << 22); + crtc->vline_crnt_vline = 0; + + crtc->h_tot_disp = h_total | (h_disp << 16); + crtc->h_sync_strt_wid = (h_sync_strt & 0xff) | (h_sync_dly << 8) | + ((h_sync_strt & 0x100) << 4) | (h_sync_wid << 16) | + (h_sync_pol << 21); + crtc->v_tot_disp = v_total | (v_disp << 16); + crtc->v_sync_strt_wid = v_sync_strt | (v_sync_wid << 16) | + (v_sync_pol << 21); + + /* crtc->gen_cntl = aty_ld_le32(CRTC_GEN_CNTL, par) & CRTC_PRESERVED_MASK; */ + crtc->gen_cntl = CRTC_EXT_DISP_EN | CRTC_EN | pix_width | c_sync; + crtc->gen_cntl |= CRTC_VGA_LINEAR; + + /* Enable doublescan mode if requested */ + if (vmode & FB_VMODE_DOUBLE) + crtc->gen_cntl |= CRTC_DBL_SCAN_EN; + /* Enable interlaced mode if requested */ + if (vmode & FB_VMODE_INTERLACED) + crtc->gen_cntl |= CRTC_INTERLACE_EN; +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + vdisplay = yres; + if (vmode & FB_VMODE_DOUBLE) + vdisplay <<= 1; + crtc->gen_cntl &= ~(CRTC2_EN | CRTC2_PIX_WIDTH); + crtc->lcd_gen_cntl &= ~(HORZ_DIVBY2_EN | DIS_HOR_CRT_DIVBY2 | + /*TVCLK_PM_EN | VCLK_DAC_PM_EN |*/ + USE_SHADOWED_VEND | + USE_SHADOWED_ROWCUR | + SHADOW_EN | SHADOW_RW_EN); + crtc->lcd_gen_cntl |= DONT_SHADOW_VPAR/* | LOCK_8DOT*/; + + /* MOBILITY M1 tested, FIXME: LT */ + crtc->horz_stretching = aty_ld_lcd(HORZ_STRETCHING, par); + if (!M64_HAS(LT_LCD_REGS)) + crtc->ext_vert_stretch = aty_ld_lcd(EXT_VERT_STRETCH, par) & + ~(AUTO_VERT_RATIO | VERT_STRETCH_MODE | VERT_STRETCH_RATIO3); + + crtc->horz_stretching &= ~(HORZ_STRETCH_RATIO | + HORZ_STRETCH_LOOP | AUTO_HORZ_RATIO | + HORZ_STRETCH_MODE | HORZ_STRETCH_EN); + if (xres < par->lcd_width && crtc->lcd_gen_cntl & LCD_ON) { + do { + /* + * The horizontal blender misbehaves when + * HDisplay is less than a certain threshold + * (440 for a 1024-wide panel). It doesn't + * stretch such modes enough. Use pixel + * replication instead of blending to stretch + * modes that can be made to exactly fit the + * panel width. The undocumented "NoLCDBlend" + * option allows the pixel-replicated mode to + * be slightly wider or narrower than the + * panel width. It also causes a mode that is + * exactly half as wide as the panel to be + * pixel-replicated, rather than blended. + */ + int HDisplay = xres & ~7; + int nStretch = par->lcd_width / HDisplay; + int Remainder = par->lcd_width % HDisplay; + + if ((!Remainder && ((nStretch > 2))) || + (((HDisplay * 16) / par->lcd_width) < 7)) { + static const char StretchLoops[] = { 10, 12, 13, 15, 16 }; + int horz_stretch_loop = -1, BestRemainder; + int Numerator = HDisplay, Denominator = par->lcd_width; + int Index = 5; + ATIReduceRatio(&Numerator, &Denominator); + + BestRemainder = (Numerator * 16) / Denominator; + while (--Index >= 0) { + Remainder = ((Denominator - Numerator) * StretchLoops[Index]) % + Denominator; + if (Remainder < BestRemainder) { + horz_stretch_loop = Index; + if (!(BestRemainder = Remainder)) + break; + } + } + + if ((horz_stretch_loop >= 0) && !BestRemainder) { + int horz_stretch_ratio = 0, Accumulator = 0; + int reuse_previous = 1; + + Index = StretchLoops[horz_stretch_loop]; + + while (--Index >= 0) { + if (Accumulator > 0) + horz_stretch_ratio |= reuse_previous; + else + Accumulator += Denominator; + Accumulator -= Numerator; + reuse_previous <<= 1; + } + + crtc->horz_stretching |= (HORZ_STRETCH_EN | + ((horz_stretch_loop & HORZ_STRETCH_LOOP) << 16) | + (horz_stretch_ratio & HORZ_STRETCH_RATIO)); + break; /* Out of the do { ... } while (0) */ + } + } + + crtc->horz_stretching |= (HORZ_STRETCH_MODE | HORZ_STRETCH_EN | + (((HDisplay * (HORZ_STRETCH_BLEND + 1)) / par->lcd_width) & HORZ_STRETCH_BLEND)); + } while (0); + } + + if (vdisplay < par->lcd_height && crtc->lcd_gen_cntl & LCD_ON) { + crtc->vert_stretching = (VERT_STRETCH_USE0 | VERT_STRETCH_EN | + (((vdisplay * (VERT_STRETCH_RATIO0 + 1)) / par->lcd_height) & VERT_STRETCH_RATIO0)); + + if (!M64_HAS(LT_LCD_REGS) && + xres <= (M64_HAS(MOBIL_BUS) ? 1024 : 800)) + crtc->ext_vert_stretch |= VERT_STRETCH_MODE; + } else { + /* + * Don't use vertical blending if the mode is too wide + * or not vertically stretched. + */ + crtc->vert_stretching = 0; + } + /* copy to shadow crtc */ + crtc->shadow_h_tot_disp = crtc->h_tot_disp; + crtc->shadow_h_sync_strt_wid = crtc->h_sync_strt_wid; + crtc->shadow_v_tot_disp = crtc->v_tot_disp; + crtc->shadow_v_sync_strt_wid = crtc->v_sync_strt_wid; + } +#endif /* CONFIG_FB_ATY_GENERIC_LCD */ + + if (M64_HAS(MAGIC_FIFO)) { + /* FIXME: display FIFO low watermark values */ + crtc->gen_cntl |= (aty_ld_le32(CRTC_GEN_CNTL, par) & CRTC_FIFO_LWM); + } + crtc->dp_pix_width = dp_pix_width; + crtc->dp_chain_mask = dp_chain_mask; + + return 0; +} + +static int aty_crtc_to_var(const struct crtc *crtc, + struct fb_var_screeninfo *var) +{ + u32 xres, yres, bpp, left, right, upper, lower, hslen, vslen, sync; + u32 h_total, h_disp, h_sync_strt, h_sync_dly, h_sync_wid, h_sync_pol; + u32 v_total, v_disp, v_sync_strt, v_sync_wid, v_sync_pol, c_sync; + u32 pix_width; + u32 double_scan, interlace; + + /* input */ + h_total = crtc->h_tot_disp & 0x1ff; + h_disp = (crtc->h_tot_disp >> 16) & 0xff; + h_sync_strt = (crtc->h_sync_strt_wid & 0xff) | ((crtc->h_sync_strt_wid >> 4) & 0x100); + h_sync_dly = (crtc->h_sync_strt_wid >> 8) & 0x7; + h_sync_wid = (crtc->h_sync_strt_wid >> 16) & 0x1f; + h_sync_pol = (crtc->h_sync_strt_wid >> 21) & 0x1; + v_total = crtc->v_tot_disp & 0x7ff; + v_disp = (crtc->v_tot_disp >> 16) & 0x7ff; + v_sync_strt = crtc->v_sync_strt_wid & 0x7ff; + v_sync_wid = (crtc->v_sync_strt_wid >> 16) & 0x1f; + v_sync_pol = (crtc->v_sync_strt_wid >> 21) & 0x1; + c_sync = crtc->gen_cntl & CRTC_CSYNC_EN ? 1 : 0; + pix_width = crtc->gen_cntl & CRTC_PIX_WIDTH_MASK; + double_scan = crtc->gen_cntl & CRTC_DBL_SCAN_EN; + interlace = crtc->gen_cntl & CRTC_INTERLACE_EN; + + /* convert */ + xres = (h_disp + 1) * 8; + yres = v_disp + 1; + left = (h_total - h_sync_strt - h_sync_wid) * 8 - h_sync_dly; + right = (h_sync_strt - h_disp) * 8 + h_sync_dly; + hslen = h_sync_wid * 8; + upper = v_total - v_sync_strt - v_sync_wid; + lower = v_sync_strt - v_disp; + vslen = v_sync_wid; + sync = (h_sync_pol ? 0 : FB_SYNC_HOR_HIGH_ACT) | + (v_sync_pol ? 0 : FB_SYNC_VERT_HIGH_ACT) | + (c_sync ? FB_SYNC_COMP_HIGH_ACT : 0); + + switch (pix_width) { +#if 0 + case CRTC_PIX_WIDTH_4BPP: + bpp = 4; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; +#endif + case CRTC_PIX_WIDTH_8BPP: + bpp = 8; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case CRTC_PIX_WIDTH_15BPP: /* RGB 555 */ + bpp = 16; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case CRTC_PIX_WIDTH_16BPP: /* RGB 565 */ + bpp = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case CRTC_PIX_WIDTH_24BPP: /* RGB 888 */ + bpp = 24; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case CRTC_PIX_WIDTH_32BPP: /* ARGB 8888 */ + bpp = 32; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + default: + PRINTKE("Invalid pixel width\n"); + return -EINVAL; + } + + /* output */ + var->xres = xres; + var->yres = yres; + var->xres_virtual = crtc->vxres; + var->yres_virtual = crtc->vyres; + var->bits_per_pixel = bpp; + var->left_margin = left; + var->right_margin = right; + var->upper_margin = upper; + var->lower_margin = lower; + var->hsync_len = hslen; + var->vsync_len = vslen; + var->sync = sync; + var->vmode = FB_VMODE_NONINTERLACED; + /* + * In double scan mode, the vertical parameters are doubled, + * so we need to halve them to get the right values. + * In interlaced mode the values are already correct, + * so no correction is necessary. + */ + if (interlace) + var->vmode = FB_VMODE_INTERLACED; + + if (double_scan) { + var->vmode = FB_VMODE_DOUBLE; + var->yres >>= 1; + var->upper_margin >>= 1; + var->lower_margin >>= 1; + var->vsync_len >>= 1; + } + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static int atyfb_set_par(struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + struct fb_var_screeninfo *var = &info->var; + u32 tmp, pixclock; + int err; +#ifdef DEBUG + struct fb_var_screeninfo debug; + u32 pixclock_in_ps; +#endif + if (par->asleep) + return 0; + + err = aty_var_to_crtc(info, var, &par->crtc); + if (err) + return err; + + pixclock = atyfb_get_pixclock(var, par); + + if (pixclock == 0) { + PRINTKE("Invalid pixclock\n"); + return -EINVAL; + } else { + err = par->pll_ops->var_to_pll(info, pixclock, + var->bits_per_pixel, &par->pll); + if (err) + return err; + } + + par->accel_flags = var->accel_flags; /* hack */ + + if (var->accel_flags) { + info->fbops->fb_sync = atyfb_sync; + info->flags &= ~FBINFO_HWACCEL_DISABLED; + } else { + info->fbops->fb_sync = NULL; + info->flags |= FBINFO_HWACCEL_DISABLED; + } + + if (par->blitter_may_be_busy) + wait_for_idle(par); + + aty_set_crtc(par, &par->crtc); + par->dac_ops->set_dac(info, &par->pll, + var->bits_per_pixel, par->accel_flags); + par->pll_ops->set_pll(info, &par->pll); + +#ifdef DEBUG + if (par->pll_ops && par->pll_ops->pll_to_var) + pixclock_in_ps = par->pll_ops->pll_to_var(info, &par->pll); + else + pixclock_in_ps = 0; + + if (0 == pixclock_in_ps) { + PRINTKE("ALERT ops->pll_to_var get 0\n"); + pixclock_in_ps = pixclock; + } + + memset(&debug, 0, sizeof(debug)); + if (!aty_crtc_to_var(&par->crtc, &debug)) { + u32 hSync, vRefresh; + u32 h_disp, h_sync_strt, h_sync_end, h_total; + u32 v_disp, v_sync_strt, v_sync_end, v_total; + + h_disp = debug.xres; + h_sync_strt = h_disp + debug.right_margin; + h_sync_end = h_sync_strt + debug.hsync_len; + h_total = h_sync_end + debug.left_margin; + v_disp = debug.yres; + v_sync_strt = v_disp + debug.lower_margin; + v_sync_end = v_sync_strt + debug.vsync_len; + v_total = v_sync_end + debug.upper_margin; + + hSync = 1000000000 / (pixclock_in_ps * h_total); + vRefresh = (hSync * 1000) / v_total; + if (par->crtc.gen_cntl & CRTC_INTERLACE_EN) + vRefresh *= 2; + if (par->crtc.gen_cntl & CRTC_DBL_SCAN_EN) + vRefresh /= 2; + + DPRINTK("atyfb_set_par\n"); + DPRINTK(" Set Visible Mode to %ix%i-%i\n", + var->xres, var->yres, var->bits_per_pixel); + DPRINTK(" Virtual resolution %ix%i, " + "pixclock_in_ps %i (calculated %i)\n", + var->xres_virtual, var->yres_virtual, + pixclock, pixclock_in_ps); + DPRINTK(" Dot clock: %i MHz\n", + 1000000 / pixclock_in_ps); + DPRINTK(" Horizontal sync: %i kHz\n", hSync); + DPRINTK(" Vertical refresh: %i Hz\n", vRefresh); + DPRINTK(" x style: %i.%03i %i %i %i %i %i %i %i %i\n", + 1000000 / pixclock_in_ps, 1000000 % pixclock_in_ps, + h_disp, h_sync_strt, h_sync_end, h_total, + v_disp, v_sync_strt, v_sync_end, v_total); + DPRINTK(" fb style: %i %i %i %i %i %i %i %i %i\n", + pixclock_in_ps, + debug.left_margin, h_disp, debug.right_margin, debug.hsync_len, + debug.upper_margin, v_disp, debug.lower_margin, debug.vsync_len); + } +#endif /* DEBUG */ + + if (!M64_HAS(INTEGRATED)) { + /* Don't forget MEM_CNTL */ + tmp = aty_ld_le32(MEM_CNTL, par) & 0xf0ffffff; + switch (var->bits_per_pixel) { + case 8: + tmp |= 0x02000000; + break; + case 16: + tmp |= 0x03000000; + break; + case 32: + tmp |= 0x06000000; + break; + } + aty_st_le32(MEM_CNTL, tmp, par); + } else { + tmp = aty_ld_le32(MEM_CNTL, par) & 0xf00fffff; + if (!M64_HAS(MAGIC_POSTDIV)) + tmp |= par->mem_refresh_rate << 20; + switch (var->bits_per_pixel) { + case 8: + case 24: + tmp |= 0x00000000; + break; + case 16: + tmp |= 0x04000000; + break; + case 32: + tmp |= 0x08000000; + break; + } + if (M64_HAS(CT_BUS)) { + aty_st_le32(DAC_CNTL, 0x87010184, par); + aty_st_le32(BUS_CNTL, 0x680000f9, par); + } else if (M64_HAS(VT_BUS)) { + aty_st_le32(DAC_CNTL, 0x87010184, par); + aty_st_le32(BUS_CNTL, 0x680000f9, par); + } else if (M64_HAS(MOBIL_BUS)) { + aty_st_le32(DAC_CNTL, 0x80010102, par); + aty_st_le32(BUS_CNTL, 0x7b33a040 | (par->aux_start ? BUS_APER_REG_DIS : 0), par); + } else { + /* GT */ + aty_st_le32(DAC_CNTL, 0x86010102, par); + aty_st_le32(BUS_CNTL, 0x7b23a040 | (par->aux_start ? BUS_APER_REG_DIS : 0), par); + aty_st_le32(EXT_MEM_CNTL, aty_ld_le32(EXT_MEM_CNTL, par) | 0x5000001, par); + } + aty_st_le32(MEM_CNTL, tmp, par); + } + aty_st_8(DAC_MASK, 0xff, par); + + info->fix.line_length = calc_line_length(par, var->xres_virtual, + var->bits_per_pixel); + + info->fix.visual = var->bits_per_pixel <= 8 ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + + /* Initialize the graphics engine */ + if (par->accel_flags & FB_ACCELF_TEXT) + aty_init_engine(par, info); + +#ifdef CONFIG_BOOTX_TEXT + btext_update_display(info->fix.smem_start, + (((par->crtc.h_tot_disp >> 16) & 0xff) + 1) * 8, + ((par->crtc.v_tot_disp >> 16) & 0x7ff) + 1, + var->bits_per_pixel, + par->crtc.vxres * var->bits_per_pixel / 8); +#endif /* CONFIG_BOOTX_TEXT */ +#if 0 + /* switch to accelerator mode */ + if (!(par->crtc.gen_cntl & CRTC_EXT_DISP_EN)) + aty_st_le32(CRTC_GEN_CNTL, par->crtc.gen_cntl | CRTC_EXT_DISP_EN, par); +#endif +#ifdef DEBUG +{ + /* dump non shadow CRTC, pll, LCD registers */ + int i; u32 base; + + /* CRTC registers */ + base = 0x2000; + printk("debug atyfb: Mach64 non-shadow register values:"); + for (i = 0; i < 256; i = i+4) { + if (i % 16 == 0) + printk("\ndebug atyfb: 0x%04X: ", base + i); + printk(" %08X", aty_ld_le32(i, par)); + } + printk("\n\n"); + +#ifdef CONFIG_FB_ATY_CT + /* PLL registers */ + base = 0x00; + printk("debug atyfb: Mach64 PLL register values:"); + for (i = 0; i < 64; i++) { + if (i % 16 == 0) + printk("\ndebug atyfb: 0x%02X: ", base + i); + if (i % 4 == 0) + printk(" "); + printk("%02X", aty_ld_pll_ct(i, par)); + } + printk("\n\n"); +#endif /* CONFIG_FB_ATY_CT */ + +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + /* LCD registers */ + base = 0x00; + printk("debug atyfb: LCD register values:"); + if (M64_HAS(LT_LCD_REGS)) { + for (i = 0; i <= POWER_MANAGEMENT; i++) { + if (i == EXT_VERT_STRETCH) + continue; + printk("\ndebug atyfb: 0x%04X: ", + lt_lcd_regs[i]); + printk(" %08X", aty_ld_lcd(i, par)); + } + } else { + for (i = 0; i < 64; i++) { + if (i % 4 == 0) + printk("\ndebug atyfb: 0x%02X: ", + base + i); + printk(" %08X", aty_ld_lcd(i, par)); + } + } + printk("\n\n"); + } +#endif /* CONFIG_FB_ATY_GENERIC_LCD */ +} +#endif /* DEBUG */ + return 0; +} + +static int atyfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + int err; + struct crtc crtc; + union aty_pll pll; + u32 pixclock; + + memcpy(&pll, &par->pll, sizeof(pll)); + + err = aty_var_to_crtc(info, var, &crtc); + if (err) + return err; + + pixclock = atyfb_get_pixclock(var, par); + + if (pixclock == 0) { + if (!(var->activate & FB_ACTIVATE_TEST)) + PRINTKE("Invalid pixclock\n"); + return -EINVAL; + } else { + err = par->pll_ops->var_to_pll(info, pixclock, + var->bits_per_pixel, &pll); + if (err) + return err; + } + + if (var->accel_flags & FB_ACCELF_TEXT) + info->var.accel_flags = FB_ACCELF_TEXT; + else + info->var.accel_flags = 0; + + aty_crtc_to_var(&crtc, var); + var->pixclock = par->pll_ops->pll_to_var(info, &pll); + return 0; +} + +static void set_off_pitch(struct atyfb_par *par, const struct fb_info *info) +{ + u32 xoffset = info->var.xoffset; + u32 yoffset = info->var.yoffset; + u32 line_length = info->fix.line_length; + u32 bpp = info->var.bits_per_pixel; + + par->crtc.off_pitch = + ((yoffset * line_length + xoffset * bpp / 8) / 8) | + ((line_length / bpp) << 22); +} + + +/* + * Open/Release the frame buffer device + */ + +static int atyfb_open(struct fb_info *info, int user) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + + if (user) { + par->open++; +#ifdef __sparc__ + par->mmaped = 0; +#endif + } + return 0; +} + +static irqreturn_t aty_irq(int irq, void *dev_id) +{ + struct atyfb_par *par = dev_id; + int handled = 0; + u32 int_cntl; + + spin_lock(&par->int_lock); + + int_cntl = aty_ld_le32(CRTC_INT_CNTL, par); + + if (int_cntl & CRTC_VBLANK_INT) { + /* clear interrupt */ + aty_st_le32(CRTC_INT_CNTL, (int_cntl & CRTC_INT_EN_MASK) | + CRTC_VBLANK_INT_AK, par); + par->vblank.count++; + if (par->vblank.pan_display) { + par->vblank.pan_display = 0; + aty_st_le32(CRTC_OFF_PITCH, par->crtc.off_pitch, par); + } + wake_up_interruptible(&par->vblank.wait); + handled = 1; + } + + spin_unlock(&par->int_lock); + + return IRQ_RETVAL(handled); +} + +static int aty_enable_irq(struct atyfb_par *par, int reenable) +{ + u32 int_cntl; + + if (!test_and_set_bit(0, &par->irq_flags)) { + if (request_irq(par->irq, aty_irq, IRQF_SHARED, "atyfb", par)) { + clear_bit(0, &par->irq_flags); + return -EINVAL; + } + spin_lock_irq(&par->int_lock); + int_cntl = aty_ld_le32(CRTC_INT_CNTL, par) & CRTC_INT_EN_MASK; + /* clear interrupt */ + aty_st_le32(CRTC_INT_CNTL, int_cntl | CRTC_VBLANK_INT_AK, par); + /* enable interrupt */ + aty_st_le32(CRTC_INT_CNTL, int_cntl | CRTC_VBLANK_INT_EN, par); + spin_unlock_irq(&par->int_lock); + } else if (reenable) { + spin_lock_irq(&par->int_lock); + int_cntl = aty_ld_le32(CRTC_INT_CNTL, par) & CRTC_INT_EN_MASK; + if (!(int_cntl & CRTC_VBLANK_INT_EN)) { + printk("atyfb: someone disabled IRQ [%08x]\n", + int_cntl); + /* re-enable interrupt */ + aty_st_le32(CRTC_INT_CNTL, int_cntl | + CRTC_VBLANK_INT_EN, par); + } + spin_unlock_irq(&par->int_lock); + } + + return 0; +} + +static int aty_disable_irq(struct atyfb_par *par) +{ + u32 int_cntl; + + if (test_and_clear_bit(0, &par->irq_flags)) { + if (par->vblank.pan_display) { + par->vblank.pan_display = 0; + aty_st_le32(CRTC_OFF_PITCH, par->crtc.off_pitch, par); + } + spin_lock_irq(&par->int_lock); + int_cntl = aty_ld_le32(CRTC_INT_CNTL, par) & CRTC_INT_EN_MASK; + /* disable interrupt */ + aty_st_le32(CRTC_INT_CNTL, int_cntl & ~CRTC_VBLANK_INT_EN, par); + spin_unlock_irq(&par->int_lock); + free_irq(par->irq, par); + } + + return 0; +} + +static int atyfb_release(struct fb_info *info, int user) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; +#ifdef __sparc__ + int was_mmaped; +#endif + + if (!user) + return 0; + + par->open--; + mdelay(1); + wait_for_idle(par); + + if (par->open) + return 0; + +#ifdef __sparc__ + was_mmaped = par->mmaped; + + par->mmaped = 0; + + if (was_mmaped) { + struct fb_var_screeninfo var; + + /* + * Now reset the default display config, we have + * no idea what the program(s) which mmap'd the + * chip did to the configuration, nor whether it + * restored it correctly. + */ + var = default_var; + if (noaccel) + var.accel_flags &= ~FB_ACCELF_TEXT; + else + var.accel_flags |= FB_ACCELF_TEXT; + if (var.yres == var.yres_virtual) { + u32 videoram = (info->fix.smem_len - (PAGE_SIZE << 2)); + var.yres_virtual = + ((videoram * 8) / var.bits_per_pixel) / + var.xres_virtual; + if (var.yres_virtual < var.yres) + var.yres_virtual = var.yres; + } + } +#endif + aty_disable_irq(par); + + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ + +static int atyfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 xres, yres, xoffset, yoffset; + + xres = (((par->crtc.h_tot_disp >> 16) & 0xff) + 1) * 8; + yres = ((par->crtc.v_tot_disp >> 16) & 0x7ff) + 1; + if (par->crtc.gen_cntl & CRTC_DBL_SCAN_EN) + yres >>= 1; + xoffset = (var->xoffset + 7) & ~7; + yoffset = var->yoffset; + if (xoffset + xres > par->crtc.vxres || + yoffset + yres > par->crtc.vyres) + return -EINVAL; + info->var.xoffset = xoffset; + info->var.yoffset = yoffset; + if (par->asleep) + return 0; + + set_off_pitch(par, info); + if ((var->activate & FB_ACTIVATE_VBL) && !aty_enable_irq(par, 0)) { + par->vblank.pan_display = 1; + } else { + par->vblank.pan_display = 0; + aty_st_le32(CRTC_OFF_PITCH, par->crtc.off_pitch, par); + } + + return 0; +} + +static int aty_waitforvblank(struct atyfb_par *par, u32 crtc) +{ + struct aty_interrupt *vbl; + unsigned int count; + int ret; + + switch (crtc) { + case 0: + vbl = &par->vblank; + break; + default: + return -ENODEV; + } + + ret = aty_enable_irq(par, 0); + if (ret) + return ret; + + count = vbl->count; + ret = wait_event_interruptible_timeout(vbl->wait, + count != vbl->count, HZ/10); + if (ret < 0) + return ret; + if (ret == 0) { + aty_enable_irq(par, 1); + return -ETIMEDOUT; + } + + return 0; +} + + +#ifdef DEBUG +#define ATYIO_CLKR 0x41545900 /* ATY\00 */ +#define ATYIO_CLKW 0x41545901 /* ATY\01 */ + +struct atyclk { + u32 ref_clk_per; + u8 pll_ref_div; + u8 mclk_fb_div; + u8 mclk_post_div; /* 1,2,3,4,8 */ + u8 mclk_fb_mult; /* 2 or 4 */ + u8 xclk_post_div; /* 1,2,3,4,8 */ + u8 vclk_fb_div; + u8 vclk_post_div; /* 1,2,3,4,6,8,12 */ + u32 dsp_xclks_per_row; /* 0-16383 */ + u32 dsp_loop_latency; /* 0-15 */ + u32 dsp_precision; /* 0-7 */ + u32 dsp_on; /* 0-2047 */ + u32 dsp_off; /* 0-2047 */ +}; + +#define ATYIO_FEATR 0x41545902 /* ATY\02 */ +#define ATYIO_FEATW 0x41545903 /* ATY\03 */ +#endif + +static int atyfb_ioctl(struct fb_info *info, u_int cmd, u_long arg) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; +#ifdef __sparc__ + struct fbtype fbtyp; +#endif + + switch (cmd) { +#ifdef __sparc__ + case FBIOGTYPE: + fbtyp.fb_type = FBTYPE_PCI_GENERIC; + fbtyp.fb_width = par->crtc.vxres; + fbtyp.fb_height = par->crtc.vyres; + fbtyp.fb_depth = info->var.bits_per_pixel; + fbtyp.fb_cmsize = info->cmap.len; + fbtyp.fb_size = info->fix.smem_len; + if (copy_to_user((struct fbtype __user *) arg, &fbtyp, + sizeof(fbtyp))) + return -EFAULT; + break; +#endif /* __sparc__ */ + + case FBIO_WAITFORVSYNC: + { + u32 crtc; + + if (get_user(crtc, (__u32 __user *) arg)) + return -EFAULT; + + return aty_waitforvblank(par, crtc); + } + +#if defined(DEBUG) && defined(CONFIG_FB_ATY_CT) + case ATYIO_CLKR: + if (M64_HAS(INTEGRATED)) { + struct atyclk clk; + union aty_pll *pll = &par->pll; + u32 dsp_config = pll->ct.dsp_config; + u32 dsp_on_off = pll->ct.dsp_on_off; + clk.ref_clk_per = par->ref_clk_per; + clk.pll_ref_div = pll->ct.pll_ref_div; + clk.mclk_fb_div = pll->ct.mclk_fb_div; + clk.mclk_post_div = pll->ct.mclk_post_div_real; + clk.mclk_fb_mult = pll->ct.mclk_fb_mult; + clk.xclk_post_div = pll->ct.xclk_post_div_real; + clk.vclk_fb_div = pll->ct.vclk_fb_div; + clk.vclk_post_div = pll->ct.vclk_post_div_real; + clk.dsp_xclks_per_row = dsp_config & 0x3fff; + clk.dsp_loop_latency = (dsp_config >> 16) & 0xf; + clk.dsp_precision = (dsp_config >> 20) & 7; + clk.dsp_off = dsp_on_off & 0x7ff; + clk.dsp_on = (dsp_on_off >> 16) & 0x7ff; + if (copy_to_user((struct atyclk __user *) arg, &clk, + sizeof(clk))) + return -EFAULT; + } else + return -EINVAL; + break; + case ATYIO_CLKW: + if (M64_HAS(INTEGRATED)) { + struct atyclk clk; + union aty_pll *pll = &par->pll; + if (copy_from_user(&clk, (struct atyclk __user *) arg, + sizeof(clk))) + return -EFAULT; + par->ref_clk_per = clk.ref_clk_per; + pll->ct.pll_ref_div = clk.pll_ref_div; + pll->ct.mclk_fb_div = clk.mclk_fb_div; + pll->ct.mclk_post_div_real = clk.mclk_post_div; + pll->ct.mclk_fb_mult = clk.mclk_fb_mult; + pll->ct.xclk_post_div_real = clk.xclk_post_div; + pll->ct.vclk_fb_div = clk.vclk_fb_div; + pll->ct.vclk_post_div_real = clk.vclk_post_div; + pll->ct.dsp_config = (clk.dsp_xclks_per_row & 0x3fff) | + ((clk.dsp_loop_latency & 0xf) << 16) | + ((clk.dsp_precision & 7) << 20); + pll->ct.dsp_on_off = (clk.dsp_off & 0x7ff) | + ((clk.dsp_on & 0x7ff) << 16); + /*aty_calc_pll_ct(info, &pll->ct);*/ + aty_set_pll_ct(info, pll); + } else + return -EINVAL; + break; + case ATYIO_FEATR: + if (get_user(par->features, (u32 __user *) arg)) + return -EFAULT; + break; + case ATYIO_FEATW: + if (put_user(par->features, (u32 __user *) arg)) + return -EFAULT; + break; +#endif /* DEBUG && CONFIG_FB_ATY_CT */ + default: + return -EINVAL; + } + return 0; +} + +static int atyfb_sync(struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + + if (par->blitter_may_be_busy) + wait_for_idle(par); + return 0; +} + +#ifdef __sparc__ +static int atyfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + unsigned int size, page, map_size = 0; + unsigned long map_offset = 0; + unsigned long off; + int i; + + if (!par->mmap_map) + return -ENXIO; + + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + + off = vma->vm_pgoff << PAGE_SHIFT; + size = vma->vm_end - vma->vm_start; + + /* VM_IO | VM_DONTEXPAND | VM_DONTDUMP are set by remap_pfn_range() */ + + if (((vma->vm_pgoff == 0) && (size == info->fix.smem_len)) || + ((off == info->fix.smem_len) && (size == PAGE_SIZE))) + off += 0x8000000000000000UL; + + vma->vm_pgoff = off >> PAGE_SHIFT; /* propagate off changes */ + + /* Each page, see which map applies */ + for (page = 0; page < size;) { + map_size = 0; + for (i = 0; par->mmap_map[i].size; i++) { + unsigned long start = par->mmap_map[i].voff; + unsigned long end = start + par->mmap_map[i].size; + unsigned long offset = off + page; + + if (start > offset) + continue; + if (offset >= end) + continue; + + map_size = par->mmap_map[i].size - (offset - start); + map_offset = par->mmap_map[i].poff + (offset - start); + break; + } + if (!map_size) { + page += PAGE_SIZE; + continue; + } + if (page + map_size > size) + map_size = size - page; + + pgprot_val(vma->vm_page_prot) &= ~(par->mmap_map[i].prot_mask); + pgprot_val(vma->vm_page_prot) |= par->mmap_map[i].prot_flag; + + if (remap_pfn_range(vma, vma->vm_start + page, + map_offset >> PAGE_SHIFT, map_size, vma->vm_page_prot)) + return -EAGAIN; + + page += map_size; + } + + if (!map_size) + return -EINVAL; + + if (!par->mmaped) + par->mmaped = 1; + return 0; +} +#endif /* __sparc__ */ + + + +#if defined(CONFIG_PM) && defined(CONFIG_PCI) + +#ifdef CONFIG_PPC_PMAC +/* Power management routines. Those are used for PowerBook sleep. + */ +static int aty_power_mgmt(int sleep, struct atyfb_par *par) +{ + u32 pm; + int timeout; + + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + pm = (pm & ~PWR_MGT_MODE_MASK) | PWR_MGT_MODE_REG; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + + timeout = 2000; + if (sleep) { + /* Sleep */ + pm &= ~PWR_MGT_ON; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + udelay(10); + pm &= ~(PWR_BLON | AUTO_PWR_UP); + pm |= SUSPEND_NOW; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + udelay(10); + pm |= PWR_MGT_ON; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + do { + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + mdelay(1); + if ((--timeout) == 0) + break; + } while ((pm & PWR_MGT_STATUS_MASK) != PWR_MGT_STATUS_SUSPEND); + } else { + /* Wakeup */ + pm &= ~PWR_MGT_ON; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + udelay(10); + pm &= ~SUSPEND_NOW; + pm |= (PWR_BLON | AUTO_PWR_UP); + aty_st_lcd(POWER_MANAGEMENT, pm, par); + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + udelay(10); + pm |= PWR_MGT_ON; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + do { + pm = aty_ld_lcd(POWER_MANAGEMENT, par); + mdelay(1); + if ((--timeout) == 0) + break; + } while ((pm & PWR_MGT_STATUS_MASK) != 0); + } + mdelay(500); + + return timeout ? 0 : -EIO; +} +#endif /* CONFIG_PPC_PMAC */ + +static int atyfb_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct atyfb_par *par = (struct atyfb_par *) info->par; + + if (state.event == pdev->dev.power.power_state.event) + return 0; + + console_lock(); + + fb_set_suspend(info, 1); + + /* Idle & reset engine */ + wait_for_idle(par); + aty_reset_engine(par); + + /* Blank display and LCD */ + atyfb_blank(FB_BLANK_POWERDOWN, info); + + par->asleep = 1; + par->lock_blank = 1; + + /* + * Because we may change PCI D state ourselves, we need to + * first save the config space content so the core can + * restore it properly on resume. + */ + pci_save_state(pdev); + +#ifdef CONFIG_PPC_PMAC + /* Set chip to "suspend" mode */ + if (machine_is(powermac) && aty_power_mgmt(1, par)) { + par->asleep = 0; + par->lock_blank = 0; + atyfb_blank(FB_BLANK_UNBLANK, info); + fb_set_suspend(info, 0); + console_unlock(); + return -EIO; + } +#else + pci_set_power_state(pdev, pci_choose_state(pdev, state)); +#endif + + console_unlock(); + + pdev->dev.power.power_state = state; + + return 0; +} + +static void aty_resume_chip(struct fb_info *info) +{ + struct atyfb_par *par = info->par; + + aty_st_le32(MEM_CNTL, par->mem_cntl, par); + + if (par->pll_ops->resume_pll) + par->pll_ops->resume_pll(info, &par->pll); + + if (par->aux_start) + aty_st_le32(BUS_CNTL, + aty_ld_le32(BUS_CNTL, par) | BUS_APER_REG_DIS, par); +} + +static int atyfb_pci_resume(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct atyfb_par *par = (struct atyfb_par *) info->par; + + if (pdev->dev.power.power_state.event == PM_EVENT_ON) + return 0; + + console_lock(); + + /* + * PCI state will have been restored by the core, so + * we should be in D0 now with our config space fully + * restored + */ + +#ifdef CONFIG_PPC_PMAC + if (machine_is(powermac) && + pdev->dev.power.power_state.event == PM_EVENT_SUSPEND) + aty_power_mgmt(0, par); +#endif + + aty_resume_chip(info); + + par->asleep = 0; + + /* Restore display */ + atyfb_set_par(info); + + /* Refresh */ + fb_set_suspend(info, 0); + + /* Unblank */ + par->lock_blank = 0; + atyfb_blank(FB_BLANK_UNBLANK, info); + + console_unlock(); + + pdev->dev.power.power_state = PMSG_ON; + + return 0; +} + +#endif /* defined(CONFIG_PM) && defined(CONFIG_PCI) */ + +/* Backlight */ +#ifdef CONFIG_FB_ATY_BACKLIGHT +#define MAX_LEVEL 0xFF + +static int aty_bl_get_level_brightness(struct atyfb_par *par, int level) +{ + struct fb_info *info = pci_get_drvdata(par->pdev); + int atylevel; + + /* Get and convert the value */ + /* No locking of bl_curve since we read a single value */ + atylevel = info->bl_curve[level] * FB_BACKLIGHT_MAX / MAX_LEVEL; + + if (atylevel < 0) + atylevel = 0; + else if (atylevel > MAX_LEVEL) + atylevel = MAX_LEVEL; + + return atylevel; +} + +static int aty_bl_update_status(struct backlight_device *bd) +{ + struct atyfb_par *par = bl_get_data(bd); + unsigned int reg = aty_ld_lcd(LCD_MISC_CNTL, par); + int level; + + if (bd->props.power != FB_BLANK_UNBLANK || + bd->props.fb_blank != FB_BLANK_UNBLANK) + level = 0; + else + level = bd->props.brightness; + + reg |= (BLMOD_EN | BIASMOD_EN); + if (level > 0) { + reg &= ~BIAS_MOD_LEVEL_MASK; + reg |= (aty_bl_get_level_brightness(par, level) << BIAS_MOD_LEVEL_SHIFT); + } else { + reg &= ~BIAS_MOD_LEVEL_MASK; + reg |= (aty_bl_get_level_brightness(par, 0) << BIAS_MOD_LEVEL_SHIFT); + } + aty_st_lcd(LCD_MISC_CNTL, reg, par); + + return 0; +} + +static int aty_bl_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static const struct backlight_ops aty_bl_data = { + .get_brightness = aty_bl_get_brightness, + .update_status = aty_bl_update_status, +}; + +static void aty_bl_init(struct atyfb_par *par) +{ + struct backlight_properties props; + struct fb_info *info = pci_get_drvdata(par->pdev); + struct backlight_device *bd; + char name[12]; + +#ifdef CONFIG_PMAC_BACKLIGHT + if (!pmac_has_backlight_type("ati")) + return; +#endif + + snprintf(name, sizeof(name), "atybl%d", info->node); + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = FB_BACKLIGHT_LEVELS - 1; + bd = backlight_device_register(name, info->dev, par, &aty_bl_data, + &props); + if (IS_ERR(bd)) { + info->bl_dev = NULL; + printk(KERN_WARNING "aty: Backlight registration failed\n"); + goto error; + } + + info->bl_dev = bd; + fb_bl_default_curve(info, 0, + 0x3F * FB_BACKLIGHT_MAX / MAX_LEVEL, + 0xFF * FB_BACKLIGHT_MAX / MAX_LEVEL); + + bd->props.brightness = bd->props.max_brightness; + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("aty: Backlight initialized (%s)\n", name); + + return; + +error: + return; +} + +#ifdef CONFIG_PCI +static void aty_bl_exit(struct backlight_device *bd) +{ + backlight_device_unregister(bd); + printk("aty: Backlight unloaded\n"); +} +#endif /* CONFIG_PCI */ + +#endif /* CONFIG_FB_ATY_BACKLIGHT */ + +static void aty_calc_mem_refresh(struct atyfb_par *par, int xclk) +{ + const int ragepro_tbl[] = { + 44, 50, 55, 66, 75, 80, 100 + }; + const int ragexl_tbl[] = { + 50, 66, 75, 83, 90, 95, 100, 105, + 110, 115, 120, 125, 133, 143, 166 + }; + const int *refresh_tbl; + int i, size; + + if (M64_HAS(XL_MEM)) { + refresh_tbl = ragexl_tbl; + size = ARRAY_SIZE(ragexl_tbl); + } else { + refresh_tbl = ragepro_tbl; + size = ARRAY_SIZE(ragepro_tbl); + } + + for (i = 0; i < size; i++) { + if (xclk < refresh_tbl[i]) + break; + } + par->mem_refresh_rate = i; +} + +/* + * Initialisation + */ + +static struct fb_info *fb_list = NULL; + +#if defined(__i386__) && defined(CONFIG_FB_ATY_GENERIC_LCD) +static int atyfb_get_timings_from_lcd(struct atyfb_par *par, + struct fb_var_screeninfo *var) +{ + int ret = -EINVAL; + + if (par->lcd_table != 0 && (aty_ld_lcd(LCD_GEN_CNTL, par) & LCD_ON)) { + *var = default_var; + var->xres = var->xres_virtual = par->lcd_hdisp; + var->right_margin = par->lcd_right_margin; + var->left_margin = par->lcd_hblank_len - + (par->lcd_right_margin + par->lcd_hsync_dly + + par->lcd_hsync_len); + var->hsync_len = par->lcd_hsync_len + par->lcd_hsync_dly; + var->yres = var->yres_virtual = par->lcd_vdisp; + var->lower_margin = par->lcd_lower_margin; + var->upper_margin = par->lcd_vblank_len - + (par->lcd_lower_margin + par->lcd_vsync_len); + var->vsync_len = par->lcd_vsync_len; + var->pixclock = par->lcd_pixclock; + ret = 0; + } + + return ret; +} +#endif /* defined(__i386__) && defined(CONFIG_FB_ATY_GENERIC_LCD) */ + +static int aty_init(struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + const char *ramname = NULL, *xtal; + int gtb_memsize, has_var = 0; + struct fb_var_screeninfo var; + int ret; + + init_waitqueue_head(&par->vblank.wait); + spin_lock_init(&par->int_lock); + +#ifdef CONFIG_FB_ATY_GX + if (!M64_HAS(INTEGRATED)) { + u32 stat0; + u8 dac_type, dac_subtype, clk_type; + stat0 = aty_ld_le32(CNFG_STAT0, par); + par->bus_type = (stat0 >> 0) & 0x07; + par->ram_type = (stat0 >> 3) & 0x07; + ramname = aty_gx_ram[par->ram_type]; + /* FIXME: clockchip/RAMDAC probing? */ + dac_type = (aty_ld_le32(DAC_CNTL, par) >> 16) & 0x07; +#ifdef CONFIG_ATARI + clk_type = CLK_ATI18818_1; + dac_type = (stat0 >> 9) & 0x07; + if (dac_type == 0x07) + dac_subtype = DAC_ATT20C408; + else + dac_subtype = (aty_ld_8(SCRATCH_REG1 + 1, par) & 0xF0) | dac_type; +#else + dac_type = DAC_IBMRGB514; + dac_subtype = DAC_IBMRGB514; + clk_type = CLK_IBMRGB514; +#endif + switch (dac_subtype) { + case DAC_IBMRGB514: + par->dac_ops = &aty_dac_ibm514; + break; +#ifdef CONFIG_ATARI + case DAC_ATI68860_B: + case DAC_ATI68860_C: + par->dac_ops = &aty_dac_ati68860b; + break; + case DAC_ATT20C408: + case DAC_ATT21C498: + par->dac_ops = &aty_dac_att21c498; + break; +#endif + default: + PRINTKI("aty_init: DAC type not implemented yet!\n"); + par->dac_ops = &aty_dac_unsupported; + break; + } + switch (clk_type) { +#ifdef CONFIG_ATARI + case CLK_ATI18818_1: + par->pll_ops = &aty_pll_ati18818_1; + break; +#else + case CLK_IBMRGB514: + par->pll_ops = &aty_pll_ibm514; + break; +#endif +#if 0 /* dead code */ + case CLK_STG1703: + par->pll_ops = &aty_pll_stg1703; + break; + case CLK_CH8398: + par->pll_ops = &aty_pll_ch8398; + break; + case CLK_ATT20C408: + par->pll_ops = &aty_pll_att20c408; + break; +#endif + default: + PRINTKI("aty_init: CLK type not implemented yet!"); + par->pll_ops = &aty_pll_unsupported; + break; + } + } +#endif /* CONFIG_FB_ATY_GX */ +#ifdef CONFIG_FB_ATY_CT + if (M64_HAS(INTEGRATED)) { + par->dac_ops = &aty_dac_ct; + par->pll_ops = &aty_pll_ct; + par->bus_type = PCI; + par->ram_type = (aty_ld_le32(CNFG_STAT0, par) & 0x07); + if (M64_HAS(XL_MEM)) + ramname = aty_xl_ram[par->ram_type]; + else + ramname = aty_ct_ram[par->ram_type]; + /* for many chips, the mclk is 67 MHz for SDRAM, 63 MHz otherwise */ + if (par->pll_limits.mclk == 67 && par->ram_type < SDRAM) + par->pll_limits.mclk = 63; + /* Mobility + 32bit memory interface need halved XCLK. */ + if (M64_HAS(MOBIL_BUS) && par->ram_type == SDRAM32) + par->pll_limits.xclk = (par->pll_limits.xclk + 1) >> 1; + } +#endif +#ifdef CONFIG_PPC_PMAC + /* + * The Apple iBook1 uses non-standard memory frequencies. + * We detect it and set the frequency manually. + */ + if (of_machine_is_compatible("PowerBook2,1")) { + par->pll_limits.mclk = 70; + par->pll_limits.xclk = 53; + } +#endif + + /* Allow command line to override clocks. */ + if (pll) + par->pll_limits.pll_max = pll; + if (mclk) + par->pll_limits.mclk = mclk; + if (xclk) + par->pll_limits.xclk = xclk; + + aty_calc_mem_refresh(par, par->pll_limits.xclk); + par->pll_per = 1000000/par->pll_limits.pll_max; + par->mclk_per = 1000000/par->pll_limits.mclk; + par->xclk_per = 1000000/par->pll_limits.xclk; + + par->ref_clk_per = 1000000000000ULL / 14318180; + xtal = "14.31818"; + +#ifdef CONFIG_FB_ATY_CT + if (M64_HAS(GTB_DSP)) { + u8 pll_ref_div = aty_ld_pll_ct(PLL_REF_DIV, par); + + if (pll_ref_div) { + int diff1, diff2; + diff1 = 510 * 14 / pll_ref_div - par->pll_limits.pll_max; + diff2 = 510 * 29 / pll_ref_div - par->pll_limits.pll_max; + if (diff1 < 0) + diff1 = -diff1; + if (diff2 < 0) + diff2 = -diff2; + if (diff2 < diff1) { + par->ref_clk_per = 1000000000000ULL / 29498928; + xtal = "29.498928"; + } + } + } +#endif /* CONFIG_FB_ATY_CT */ + + /* save previous video mode */ + aty_get_crtc(par, &par->saved_crtc); + if (par->pll_ops->get_pll) + par->pll_ops->get_pll(info, &par->saved_pll); + + par->mem_cntl = aty_ld_le32(MEM_CNTL, par); + gtb_memsize = M64_HAS(GTB_DSP); + if (gtb_memsize) + /* 0xF used instead of MEM_SIZE_ALIAS */ + switch (par->mem_cntl & 0xF) { + case MEM_SIZE_512K: + info->fix.smem_len = 0x80000; + break; + case MEM_SIZE_1M: + info->fix.smem_len = 0x100000; + break; + case MEM_SIZE_2M_GTB: + info->fix.smem_len = 0x200000; + break; + case MEM_SIZE_4M_GTB: + info->fix.smem_len = 0x400000; + break; + case MEM_SIZE_6M_GTB: + info->fix.smem_len = 0x600000; + break; + case MEM_SIZE_8M_GTB: + info->fix.smem_len = 0x800000; + break; + default: + info->fix.smem_len = 0x80000; + } else + switch (par->mem_cntl & MEM_SIZE_ALIAS) { + case MEM_SIZE_512K: + info->fix.smem_len = 0x80000; + break; + case MEM_SIZE_1M: + info->fix.smem_len = 0x100000; + break; + case MEM_SIZE_2M: + info->fix.smem_len = 0x200000; + break; + case MEM_SIZE_4M: + info->fix.smem_len = 0x400000; + break; + case MEM_SIZE_6M: + info->fix.smem_len = 0x600000; + break; + case MEM_SIZE_8M: + info->fix.smem_len = 0x800000; + break; + default: + info->fix.smem_len = 0x80000; + } + + if (M64_HAS(MAGIC_VRAM_SIZE)) { + if (aty_ld_le32(CNFG_STAT1, par) & 0x40000000) + info->fix.smem_len += 0x400000; + } + + if (vram) { + info->fix.smem_len = vram * 1024; + par->mem_cntl &= ~(gtb_memsize ? 0xF : MEM_SIZE_ALIAS); + if (info->fix.smem_len <= 0x80000) + par->mem_cntl |= MEM_SIZE_512K; + else if (info->fix.smem_len <= 0x100000) + par->mem_cntl |= MEM_SIZE_1M; + else if (info->fix.smem_len <= 0x200000) + par->mem_cntl |= gtb_memsize ? MEM_SIZE_2M_GTB : MEM_SIZE_2M; + else if (info->fix.smem_len <= 0x400000) + par->mem_cntl |= gtb_memsize ? MEM_SIZE_4M_GTB : MEM_SIZE_4M; + else if (info->fix.smem_len <= 0x600000) + par->mem_cntl |= gtb_memsize ? MEM_SIZE_6M_GTB : MEM_SIZE_6M; + else + par->mem_cntl |= gtb_memsize ? MEM_SIZE_8M_GTB : MEM_SIZE_8M; + aty_st_le32(MEM_CNTL, par->mem_cntl, par); + } + + /* + * Reg Block 0 (CT-compatible block) is at mmio_start + * Reg Block 1 (multimedia extensions) is at mmio_start - 0x400 + */ + if (M64_HAS(GX)) { + info->fix.mmio_len = 0x400; + info->fix.accel = FB_ACCEL_ATI_MACH64GX; + } else if (M64_HAS(CT)) { + info->fix.mmio_len = 0x400; + info->fix.accel = FB_ACCEL_ATI_MACH64CT; + } else if (M64_HAS(VT)) { + info->fix.mmio_start -= 0x400; + info->fix.mmio_len = 0x800; + info->fix.accel = FB_ACCEL_ATI_MACH64VT; + } else {/* GT */ + info->fix.mmio_start -= 0x400; + info->fix.mmio_len = 0x800; + info->fix.accel = FB_ACCEL_ATI_MACH64GT; + } + + PRINTKI("%d%c %s, %s MHz XTAL, %d MHz PLL, %d Mhz MCLK, %d MHz XCLK\n", + info->fix.smem_len == 0x80000 ? 512 : (info->fix.smem_len>>20), + info->fix.smem_len == 0x80000 ? 'K' : 'M', ramname, xtal, + par->pll_limits.pll_max, par->pll_limits.mclk, + par->pll_limits.xclk); + +#if defined(DEBUG) && defined(CONFIG_FB_ATY_CT) + if (M64_HAS(INTEGRATED)) { + int i; + printk("debug atyfb: BUS_CNTL DAC_CNTL MEM_CNTL " + "EXT_MEM_CNTL CRTC_GEN_CNTL DSP_CONFIG " + "DSP_ON_OFF CLOCK_CNTL\n" + "debug atyfb: %08x %08x %08x " + "%08x %08x %08x " + "%08x %08x\n" + "debug atyfb: PLL", + aty_ld_le32(BUS_CNTL, par), + aty_ld_le32(DAC_CNTL, par), + aty_ld_le32(MEM_CNTL, par), + aty_ld_le32(EXT_MEM_CNTL, par), + aty_ld_le32(CRTC_GEN_CNTL, par), + aty_ld_le32(DSP_CONFIG, par), + aty_ld_le32(DSP_ON_OFF, par), + aty_ld_le32(CLOCK_CNTL, par)); + for (i = 0; i < 40; i++) + printk(" %02x", aty_ld_pll_ct(i, par)); + printk("\n"); + } +#endif + if (par->pll_ops->init_pll) + par->pll_ops->init_pll(info, &par->pll); + if (par->pll_ops->resume_pll) + par->pll_ops->resume_pll(info, &par->pll); + + /* + * Last page of 8 MB (4 MB on ISA) aperture is MMIO, + * unless the auxiliary register aperture is used. + */ + if (!par->aux_start && + (info->fix.smem_len == 0x800000 || + (par->bus_type == ISA && info->fix.smem_len == 0x400000))) + info->fix.smem_len -= GUI_RESERVE; + + /* + * Disable register access through the linear aperture + * if the auxiliary aperture is used so we can access + * the full 8 MB of video RAM on 8 MB boards. + */ + if (par->aux_start) + aty_st_le32(BUS_CNTL, aty_ld_le32(BUS_CNTL, par) | + BUS_APER_REG_DIS, par); + +#ifdef CONFIG_MTRR + par->mtrr_aper = -1; + par->mtrr_reg = -1; + if (!nomtrr) { + /* Cover the whole resource. */ + par->mtrr_aper = mtrr_add(par->res_start, par->res_size, + MTRR_TYPE_WRCOMB, 1); + if (par->mtrr_aper >= 0 && !par->aux_start) { + /* Make a hole for mmio. */ + par->mtrr_reg = mtrr_add(par->res_start + 0x800000 - + GUI_RESERVE, GUI_RESERVE, + MTRR_TYPE_UNCACHABLE, 1); + if (par->mtrr_reg < 0) { + mtrr_del(par->mtrr_aper, 0, 0); + par->mtrr_aper = -1; + } + } + } +#endif + + info->fbops = &atyfb_ops; + info->pseudo_palette = par->pseudo_palette; + info->flags = FBINFO_DEFAULT | + FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_YPAN | + FBINFO_READS_FAST; + +#ifdef CONFIG_PMAC_BACKLIGHT + if (M64_HAS(G3_PB_1_1) && of_machine_is_compatible("PowerBook1,1")) { + /* + * these bits let the 101 powerbook + * wake up from sleep -- paulus + */ + aty_st_lcd(POWER_MANAGEMENT, aty_ld_lcd(POWER_MANAGEMENT, par) | + USE_F32KHZ | TRISTATE_MEM_EN, par); + } else +#endif + if (M64_HAS(MOBIL_BUS) && backlight) { +#ifdef CONFIG_FB_ATY_BACKLIGHT + aty_bl_init(par); +#endif + } + + memset(&var, 0, sizeof(var)); +#ifdef CONFIG_PPC + if (machine_is(powermac)) { + /* + * FIXME: The NVRAM stuff should be put in a Mac-specific file, + * as it applies to all Mac video cards + */ + if (mode) { + if (mac_find_mode(&var, info, mode, 8)) + has_var = 1; + } else { + if (default_vmode == VMODE_CHOOSE) { + int sense; + if (M64_HAS(G3_PB_1024x768)) + /* G3 PowerBook with 1024x768 LCD */ + default_vmode = VMODE_1024_768_60; + else if (of_machine_is_compatible("iMac")) + default_vmode = VMODE_1024_768_75; + else if (of_machine_is_compatible("PowerBook2,1")) + /* iBook with 800x600 LCD */ + default_vmode = VMODE_800_600_60; + else + default_vmode = VMODE_640_480_67; + sense = read_aty_sense(par); + PRINTKI("monitor sense=%x, mode %d\n", + sense, mac_map_monitor_sense(sense)); + } + if (default_vmode <= 0 || default_vmode > VMODE_MAX) + default_vmode = VMODE_640_480_60; + if (default_cmode < CMODE_8 || default_cmode > CMODE_32) + default_cmode = CMODE_8; + if (!mac_vmode_to_var(default_vmode, default_cmode, + &var)) + has_var = 1; + } + } + +#endif /* !CONFIG_PPC */ + +#if defined(__i386__) && defined(CONFIG_FB_ATY_GENERIC_LCD) + if (!atyfb_get_timings_from_lcd(par, &var)) + has_var = 1; +#endif + + if (mode && fb_find_mode(&var, info, mode, NULL, 0, &defmode, 8)) + has_var = 1; + + if (!has_var) + var = default_var; + + if (noaccel) + var.accel_flags &= ~FB_ACCELF_TEXT; + else + var.accel_flags |= FB_ACCELF_TEXT; + + if (comp_sync != -1) { + if (!comp_sync) + var.sync &= ~FB_SYNC_COMP_HIGH_ACT; + else + var.sync |= FB_SYNC_COMP_HIGH_ACT; + } + + if (var.yres == var.yres_virtual) { + u32 videoram = (info->fix.smem_len - (PAGE_SIZE << 2)); + var.yres_virtual = ((videoram * 8) / var.bits_per_pixel) / var.xres_virtual; + if (var.yres_virtual < var.yres) + var.yres_virtual = var.yres; + } + + ret = atyfb_check_var(&var, info); + if (ret) { + PRINTKE("can't set default video mode\n"); + goto aty_init_exit; + } + +#ifdef CONFIG_FB_ATY_CT + if (!noaccel && M64_HAS(INTEGRATED)) + aty_init_cursor(info); +#endif /* CONFIG_FB_ATY_CT */ + info->var = var; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret < 0) + goto aty_init_exit; + + ret = register_framebuffer(info); + if (ret < 0) { + fb_dealloc_cmap(&info->cmap); + goto aty_init_exit; + } + + fb_list = info; + + PRINTKI("fb%d: %s frame buffer device on %s\n", + info->node, info->fix.id, par->bus_type == ISA ? "ISA" : "PCI"); + return 0; + +aty_init_exit: + /* restore video mode */ + aty_set_crtc(par, &par->saved_crtc); + par->pll_ops->set_pll(info, &par->saved_pll); + +#ifdef CONFIG_MTRR + if (par->mtrr_reg >= 0) { + mtrr_del(par->mtrr_reg, 0, 0); + par->mtrr_reg = -1; + } + if (par->mtrr_aper >= 0) { + mtrr_del(par->mtrr_aper, 0, 0); + par->mtrr_aper = -1; + } +#endif + return ret; +} + +#if defined(CONFIG_ATARI) && !defined(MODULE) +static int store_video_par(char *video_str, unsigned char m64_num) +{ + char *p; + unsigned long vmembase, size, guiregbase; + + PRINTKI("store_video_par() '%s' \n", video_str); + + if (!(p = strsep(&video_str, ";")) || !*p) + goto mach64_invalid; + vmembase = simple_strtoul(p, NULL, 0); + if (!(p = strsep(&video_str, ";")) || !*p) + goto mach64_invalid; + size = simple_strtoul(p, NULL, 0); + if (!(p = strsep(&video_str, ";")) || !*p) + goto mach64_invalid; + guiregbase = simple_strtoul(p, NULL, 0); + + phys_vmembase[m64_num] = vmembase; + phys_size[m64_num] = size; + phys_guiregbase[m64_num] = guiregbase; + PRINTKI("stored them all: $%08lX $%08lX $%08lX \n", vmembase, size, + guiregbase); + return 0; + + mach64_invalid: + phys_vmembase[m64_num] = 0; + return -1; +} +#endif /* CONFIG_ATARI && !MODULE */ + +/* + * Blank the display. + */ + +static int atyfb_blank(int blank, struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 gen_cntl; + + if (par->lock_blank || par->asleep) + return 0; + +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table && blank > FB_BLANK_NORMAL && + (aty_ld_lcd(LCD_GEN_CNTL, par) & LCD_ON)) { + u32 pm = aty_ld_lcd(POWER_MANAGEMENT, par); + pm &= ~PWR_BLON; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + } +#endif + + gen_cntl = aty_ld_le32(CRTC_GEN_CNTL, par); + gen_cntl &= ~0x400004c; + switch (blank) { + case FB_BLANK_UNBLANK: + break; + case FB_BLANK_NORMAL: + gen_cntl |= 0x4000040; + break; + case FB_BLANK_VSYNC_SUSPEND: + gen_cntl |= 0x4000048; + break; + case FB_BLANK_HSYNC_SUSPEND: + gen_cntl |= 0x4000044; + break; + case FB_BLANK_POWERDOWN: + gen_cntl |= 0x400004c; + break; + } + aty_st_le32(CRTC_GEN_CNTL, gen_cntl, par); + +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table && blank <= FB_BLANK_NORMAL && + (aty_ld_lcd(LCD_GEN_CNTL, par) & LCD_ON)) { + u32 pm = aty_ld_lcd(POWER_MANAGEMENT, par); + pm |= PWR_BLON; + aty_st_lcd(POWER_MANAGEMENT, pm, par); + } +#endif + + return 0; +} + +static void aty_st_pal(u_int regno, u_int red, u_int green, u_int blue, + const struct atyfb_par *par) +{ + aty_st_8(DAC_W_INDEX, regno, par); + aty_st_8(DAC_DATA, red, par); + aty_st_8(DAC_DATA, green, par); + aty_st_8(DAC_DATA, blue, par); +} + +/* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + * !! 4 & 8 = PSEUDO, > 8 = DIRECTCOLOR + */ + +static int atyfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + int i, depth; + u32 *pal = info->pseudo_palette; + + depth = info->var.bits_per_pixel; + if (depth == 16) + depth = (info->var.green.length == 5) ? 15 : 16; + + if (par->asleep) + return 0; + + if (regno > 255 || + (depth == 16 && regno > 63) || + (depth == 15 && regno > 31)) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + par->palette[regno].red = red; + par->palette[regno].green = green; + par->palette[regno].blue = blue; + + if (regno < 16) { + switch (depth) { + case 15: + pal[regno] = (regno << 10) | (regno << 5) | regno; + break; + case 16: + pal[regno] = (regno << 11) | (regno << 5) | regno; + break; + case 24: + pal[regno] = (regno << 16) | (regno << 8) | regno; + break; + case 32: + i = (regno << 8) | regno; + pal[regno] = (i << 16) | i; + break; + } + } + + i = aty_ld_8(DAC_CNTL, par) & 0xfc; + if (M64_HAS(EXTRA_BRIGHT)) + i |= 0x2; /* DAC_CNTL | 0x2 turns off the extra brightness for gt */ + aty_st_8(DAC_CNTL, i, par); + aty_st_8(DAC_MASK, 0xff, par); + + if (M64_HAS(INTEGRATED)) { + if (depth == 16) { + if (regno < 32) + aty_st_pal(regno << 3, red, + par->palette[regno << 1].green, + blue, par); + red = par->palette[regno >> 1].red; + blue = par->palette[regno >> 1].blue; + regno <<= 2; + } else if (depth == 15) { + regno <<= 3; + for (i = 0; i < 8; i++) + aty_st_pal(regno + i, red, green, blue, par); + } + } + aty_st_pal(regno, red, green, blue, par); + + return 0; +} + +#ifdef CONFIG_PCI + +#ifdef __sparc__ + +static int atyfb_setup_sparc(struct pci_dev *pdev, struct fb_info *info, + unsigned long addr) +{ + struct atyfb_par *par = info->par; + struct device_node *dp; + u32 mem, chip_id; + int i, j, ret; + + /* + * Map memory-mapped registers. + */ + par->ati_regbase = (void *)addr + 0x7ffc00UL; + info->fix.mmio_start = addr + 0x7ffc00UL; + + /* + * Map in big-endian aperture. + */ + info->screen_base = (char *) (addr + 0x800000UL); + info->fix.smem_start = addr + 0x800000UL; + + /* + * Figure mmap addresses from PCI config space. + * Split Framebuffer in big- and little-endian halfs. + */ + for (i = 0; i < 6 && pdev->resource[i].start; i++) + /* nothing */ ; + j = i + 4; + + par->mmap_map = kcalloc(j, sizeof(*par->mmap_map), GFP_ATOMIC); + if (!par->mmap_map) { + PRINTKE("atyfb_setup_sparc() can't alloc mmap_map\n"); + return -ENOMEM; + } + + for (i = 0, j = 2; i < 6 && pdev->resource[i].start; i++) { + struct resource *rp = &pdev->resource[i]; + int io, breg = PCI_BASE_ADDRESS_0 + (i << 2); + unsigned long base; + u32 size, pbase; + + base = rp->start; + + io = (rp->flags & IORESOURCE_IO); + + size = rp->end - base + 1; + + pci_read_config_dword(pdev, breg, &pbase); + + if (io) + size &= ~1; + + /* + * Map the framebuffer a second time, this time without + * the braindead _PAGE_IE setting. This is used by the + * fixed Xserver, but we need to maintain the old mapping + * to stay compatible with older ones... + */ + if (base == addr) { + par->mmap_map[j].voff = (pbase + 0x10000000) & PAGE_MASK; + par->mmap_map[j].poff = base & PAGE_MASK; + par->mmap_map[j].size = (size + ~PAGE_MASK) & PAGE_MASK; + par->mmap_map[j].prot_mask = _PAGE_CACHE; + par->mmap_map[j].prot_flag = _PAGE_E; + j++; + } + + /* + * Here comes the old framebuffer mapping with _PAGE_IE + * set for the big endian half of the framebuffer... + */ + if (base == addr) { + par->mmap_map[j].voff = (pbase + 0x800000) & PAGE_MASK; + par->mmap_map[j].poff = (base + 0x800000) & PAGE_MASK; + par->mmap_map[j].size = 0x800000; + par->mmap_map[j].prot_mask = _PAGE_CACHE; + par->mmap_map[j].prot_flag = _PAGE_E | _PAGE_IE; + size -= 0x800000; + j++; + } + + par->mmap_map[j].voff = pbase & PAGE_MASK; + par->mmap_map[j].poff = base & PAGE_MASK; + par->mmap_map[j].size = (size + ~PAGE_MASK) & PAGE_MASK; + par->mmap_map[j].prot_mask = _PAGE_CACHE; + par->mmap_map[j].prot_flag = _PAGE_E; + j++; + } + + ret = correct_chipset(par); + if (ret) + return ret; + + if (IS_XL(pdev->device)) { + /* + * Fix PROMs idea of MEM_CNTL settings... + */ + mem = aty_ld_le32(MEM_CNTL, par); + chip_id = aty_ld_le32(CNFG_CHIP_ID, par); + if (((chip_id & CFG_CHIP_TYPE) == VT_CHIP_ID) && !((chip_id >> 24) & 1)) { + switch (mem & 0x0f) { + case 3: + mem = (mem & ~(0x0f)) | 2; + break; + case 7: + mem = (mem & ~(0x0f)) | 3; + break; + case 9: + mem = (mem & ~(0x0f)) | 4; + break; + case 11: + mem = (mem & ~(0x0f)) | 5; + break; + default: + break; + } + if ((aty_ld_le32(CNFG_STAT0, par) & 7) >= SDRAM) + mem &= ~(0x00700000); + } + mem &= ~(0xcf80e000); /* Turn off all undocumented bits. */ + aty_st_le32(MEM_CNTL, mem, par); + } + + dp = pci_device_to_OF_node(pdev); + if (dp == of_console_device) { + struct fb_var_screeninfo *var = &default_var; + unsigned int N, P, Q, M, T, R; + u32 v_total, h_total; + struct crtc crtc; + u8 pll_regs[16]; + u8 clock_cntl; + + crtc.vxres = of_getintprop_default(dp, "width", 1024); + crtc.vyres = of_getintprop_default(dp, "height", 768); + var->bits_per_pixel = of_getintprop_default(dp, "depth", 8); + var->xoffset = var->yoffset = 0; + crtc.h_tot_disp = aty_ld_le32(CRTC_H_TOTAL_DISP, par); + crtc.h_sync_strt_wid = aty_ld_le32(CRTC_H_SYNC_STRT_WID, par); + crtc.v_tot_disp = aty_ld_le32(CRTC_V_TOTAL_DISP, par); + crtc.v_sync_strt_wid = aty_ld_le32(CRTC_V_SYNC_STRT_WID, par); + crtc.gen_cntl = aty_ld_le32(CRTC_GEN_CNTL, par); + aty_crtc_to_var(&crtc, var); + + h_total = var->xres + var->right_margin + var->hsync_len + var->left_margin; + v_total = var->yres + var->lower_margin + var->vsync_len + var->upper_margin; + + /* + * Read the PLL to figure actual Refresh Rate. + */ + clock_cntl = aty_ld_8(CLOCK_CNTL, par); + /* DPRINTK("CLOCK_CNTL %02x\n", clock_cntl); */ + for (i = 0; i < 16; i++) + pll_regs[i] = aty_ld_pll_ct(i, par); + + /* + * PLL Reference Divider M: + */ + M = pll_regs[2]; + + /* + * PLL Feedback Divider N (Dependent on CLOCK_CNTL): + */ + N = pll_regs[7 + (clock_cntl & 3)]; + + /* + * PLL Post Divider P (Dependent on CLOCK_CNTL): + */ + P = 1 << (pll_regs[6] >> ((clock_cntl & 3) << 1)); + + /* + * PLL Divider Q: + */ + Q = N / P; + + /* + * Target Frequency: + * + * T * M + * Q = ------- + * 2 * R + * + * where R is XTALIN (= 14318 or 29498 kHz). + */ + if (IS_XL(pdev->device)) + R = 29498; + else + R = 14318; + + T = 2 * Q * R / M; + + default_var.pixclock = 1000000000 / T; + } + + return 0; +} + +#else /* __sparc__ */ + +#ifdef __i386__ +#ifdef CONFIG_FB_ATY_GENERIC_LCD +static void aty_init_lcd(struct atyfb_par *par, u32 bios_base) +{ + u32 driv_inf_tab, sig; + u16 lcd_ofs; + + /* + * To support an LCD panel, we should know it's dimensions and + * it's desired pixel clock. + * There are two ways to do it: + * - Check the startup video mode and calculate the panel + * size from it. This is unreliable. + * - Read it from the driver information table in the video BIOS. + */ + /* Address of driver information table is at offset 0x78. */ + driv_inf_tab = bios_base + *((u16 *)(bios_base+0x78)); + + /* Check for the driver information table signature. */ + sig = *(u32 *)driv_inf_tab; + if ((sig == 0x54504c24) || /* Rage LT pro */ + (sig == 0x544d5224) || /* Rage mobility */ + (sig == 0x54435824) || /* Rage XC */ + (sig == 0x544c5824)) { /* Rage XL */ + PRINTKI("BIOS contains driver information table.\n"); + lcd_ofs = *(u16 *)(driv_inf_tab + 10); + par->lcd_table = 0; + if (lcd_ofs != 0) + par->lcd_table = bios_base + lcd_ofs; + } + + if (par->lcd_table != 0) { + char model[24]; + char strbuf[16]; + char refresh_rates_buf[100]; + int id, tech, f, i, m, default_refresh_rate; + char *txtcolour; + char *txtmonitor; + char *txtdual; + char *txtformat; + u16 width, height, panel_type, refresh_rates; + u16 *lcdmodeptr; + u32 format; + u8 lcd_refresh_rates[16] = { 50, 56, 60, 67, 70, 72, 75, 76, 85, + 90, 100, 120, 140, 150, 160, 200 }; + /* + * The most important information is the panel size at + * offset 25 and 27, but there's some other nice information + * which we print to the screen. + */ + id = *(u8 *)par->lcd_table; + strncpy(model, (char *)par->lcd_table+1, 24); + model[23] = 0; + + width = par->lcd_width = *(u16 *)(par->lcd_table+25); + height = par->lcd_height = *(u16 *)(par->lcd_table+27); + panel_type = *(u16 *)(par->lcd_table+29); + if (panel_type & 1) + txtcolour = "colour"; + else + txtcolour = "monochrome"; + if (panel_type & 2) + txtdual = "dual (split) "; + else + txtdual = ""; + tech = (panel_type >> 2) & 63; + switch (tech) { + case 0: + txtmonitor = "passive matrix"; + break; + case 1: + txtmonitor = "active matrix"; + break; + case 2: + txtmonitor = "active addressed STN"; + break; + case 3: + txtmonitor = "EL"; + break; + case 4: + txtmonitor = "plasma"; + break; + default: + txtmonitor = "unknown"; + } + format = *(u32 *)(par->lcd_table+57); + if (tech == 0 || tech == 2) { + switch (format & 7) { + case 0: + txtformat = "12 bit interface"; + break; + case 1: + txtformat = "16 bit interface"; + break; + case 2: + txtformat = "24 bit interface"; + break; + default: + txtformat = "unknown format"; + } + } else { + switch (format & 7) { + case 0: + txtformat = "8 colours"; + break; + case 1: + txtformat = "512 colours"; + break; + case 2: + txtformat = "4096 colours"; + break; + case 4: + txtformat = "262144 colours (LT mode)"; + break; + case 5: + txtformat = "16777216 colours"; + break; + case 6: + txtformat = "262144 colours (FDPI-2 mode)"; + break; + default: + txtformat = "unknown format"; + } + } + PRINTKI("%s%s %s monitor detected: %s\n", + txtdual, txtcolour, txtmonitor, model); + PRINTKI(" id=%d, %dx%d pixels, %s\n", + id, width, height, txtformat); + refresh_rates_buf[0] = 0; + refresh_rates = *(u16 *)(par->lcd_table+62); + m = 1; + f = 0; + for (i = 0; i < 16; i++) { + if (refresh_rates & m) { + if (f == 0) { + sprintf(strbuf, "%d", + lcd_refresh_rates[i]); + f++; + } else { + sprintf(strbuf, ",%d", + lcd_refresh_rates[i]); + } + strcat(refresh_rates_buf, strbuf); + } + m = m << 1; + } + default_refresh_rate = (*(u8 *)(par->lcd_table+61) & 0xf0) >> 4; + PRINTKI(" supports refresh rates [%s], default %d Hz\n", + refresh_rates_buf, lcd_refresh_rates[default_refresh_rate]); + par->lcd_refreshrate = lcd_refresh_rates[default_refresh_rate]; + /* + * We now need to determine the crtc parameters for the + * LCD monitor. This is tricky, because they are not stored + * individually in the BIOS. Instead, the BIOS contains a + * table of display modes that work for this monitor. + * + * The idea is that we search for a mode of the same dimensions + * as the dimensions of the LCD monitor. Say our LCD monitor + * is 800x600 pixels, we search for a 800x600 monitor. + * The CRTC parameters we find here are the ones that we need + * to use to simulate other resolutions on the LCD screen. + */ + lcdmodeptr = (u16 *)(par->lcd_table + 64); + while (*lcdmodeptr != 0) { + u32 modeptr; + u16 mwidth, mheight, lcd_hsync_start, lcd_vsync_start; + modeptr = bios_base + *lcdmodeptr; + + mwidth = *((u16 *)(modeptr+0)); + mheight = *((u16 *)(modeptr+2)); + + if (mwidth == width && mheight == height) { + par->lcd_pixclock = 100000000 / *((u16 *)(modeptr+9)); + par->lcd_htotal = *((u16 *)(modeptr+17)) & 511; + par->lcd_hdisp = *((u16 *)(modeptr+19)) & 511; + lcd_hsync_start = *((u16 *)(modeptr+21)) & 511; + par->lcd_hsync_dly = (*((u16 *)(modeptr+21)) >> 9) & 7; + par->lcd_hsync_len = *((u8 *)(modeptr+23)) & 63; + + par->lcd_vtotal = *((u16 *)(modeptr+24)) & 2047; + par->lcd_vdisp = *((u16 *)(modeptr+26)) & 2047; + lcd_vsync_start = *((u16 *)(modeptr+28)) & 2047; + par->lcd_vsync_len = (*((u16 *)(modeptr+28)) >> 11) & 31; + + par->lcd_htotal = (par->lcd_htotal + 1) * 8; + par->lcd_hdisp = (par->lcd_hdisp + 1) * 8; + lcd_hsync_start = (lcd_hsync_start + 1) * 8; + par->lcd_hsync_len = par->lcd_hsync_len * 8; + + par->lcd_vtotal++; + par->lcd_vdisp++; + lcd_vsync_start++; + + par->lcd_right_margin = lcd_hsync_start - par->lcd_hdisp; + par->lcd_lower_margin = lcd_vsync_start - par->lcd_vdisp; + par->lcd_hblank_len = par->lcd_htotal - par->lcd_hdisp; + par->lcd_vblank_len = par->lcd_vtotal - par->lcd_vdisp; + break; + } + + lcdmodeptr++; + } + if (*lcdmodeptr == 0) { + PRINTKE("LCD monitor CRTC parameters not found!!!\n"); + /* To do: Switch to CRT if possible. */ + } else { + PRINTKI(" LCD CRTC parameters: %d.%d %d %d %d %d %d %d %d %d\n", + 1000000 / par->lcd_pixclock, 1000000 % par->lcd_pixclock, + par->lcd_hdisp, + par->lcd_hdisp + par->lcd_right_margin, + par->lcd_hdisp + par->lcd_right_margin + + par->lcd_hsync_dly + par->lcd_hsync_len, + par->lcd_htotal, + par->lcd_vdisp, + par->lcd_vdisp + par->lcd_lower_margin, + par->lcd_vdisp + par->lcd_lower_margin + par->lcd_vsync_len, + par->lcd_vtotal); + PRINTKI(" : %d %d %d %d %d %d %d %d %d\n", + par->lcd_pixclock, + par->lcd_hblank_len - (par->lcd_right_margin + + par->lcd_hsync_dly + par->lcd_hsync_len), + par->lcd_hdisp, + par->lcd_right_margin, + par->lcd_hsync_len, + par->lcd_vblank_len - (par->lcd_lower_margin + par->lcd_vsync_len), + par->lcd_vdisp, + par->lcd_lower_margin, + par->lcd_vsync_len); + } + } +} +#endif /* CONFIG_FB_ATY_GENERIC_LCD */ + +static int init_from_bios(struct atyfb_par *par) +{ + u32 bios_base, rom_addr; + int ret; + + rom_addr = 0xc0000 + ((aty_ld_le32(SCRATCH_REG1, par) & 0x7f) << 11); + bios_base = (unsigned long)ioremap(rom_addr, 0x10000); + + /* The BIOS starts with 0xaa55. */ + if (*((u16 *)bios_base) == 0xaa55) { + + u8 *bios_ptr; + u16 rom_table_offset, freq_table_offset; + PLL_BLOCK_MACH64 pll_block; + + PRINTKI("Mach64 BIOS is located at %x, mapped at %x.\n", rom_addr, bios_base); + + /* check for frequncy table */ + bios_ptr = (u8*)bios_base; + rom_table_offset = (u16)(bios_ptr[0x48] | (bios_ptr[0x49] << 8)); + freq_table_offset = bios_ptr[rom_table_offset + 16] | (bios_ptr[rom_table_offset + 17] << 8); + memcpy(&pll_block, bios_ptr + freq_table_offset, sizeof(PLL_BLOCK_MACH64)); + + PRINTKI("BIOS frequency table:\n"); + PRINTKI("PCLK_min_freq %d, PCLK_max_freq %d, ref_freq %d, ref_divider %d\n", + pll_block.PCLK_min_freq, pll_block.PCLK_max_freq, + pll_block.ref_freq, pll_block.ref_divider); + PRINTKI("MCLK_pwd %d, MCLK_max_freq %d, XCLK_max_freq %d, SCLK_freq %d\n", + pll_block.MCLK_pwd, pll_block.MCLK_max_freq, + pll_block.XCLK_max_freq, pll_block.SCLK_freq); + + par->pll_limits.pll_min = pll_block.PCLK_min_freq/100; + par->pll_limits.pll_max = pll_block.PCLK_max_freq/100; + par->pll_limits.ref_clk = pll_block.ref_freq/100; + par->pll_limits.ref_div = pll_block.ref_divider; + par->pll_limits.sclk = pll_block.SCLK_freq/100; + par->pll_limits.mclk = pll_block.MCLK_max_freq/100; + par->pll_limits.mclk_pm = pll_block.MCLK_pwd/100; + par->pll_limits.xclk = pll_block.XCLK_max_freq/100; +#ifdef CONFIG_FB_ATY_GENERIC_LCD + aty_init_lcd(par, bios_base); +#endif + ret = 0; + } else { + PRINTKE("no BIOS frequency table found, use parameters\n"); + ret = -ENXIO; + } + iounmap((void __iomem *)bios_base); + + return ret; +} +#endif /* __i386__ */ + +static int atyfb_setup_generic(struct pci_dev *pdev, struct fb_info *info, + unsigned long addr) +{ + struct atyfb_par *par = info->par; + u16 tmp; + unsigned long raddr; + struct resource *rrp; + int ret = 0; + + raddr = addr + 0x7ff000UL; + rrp = &pdev->resource[2]; + if ((rrp->flags & IORESOURCE_MEM) && + request_mem_region(rrp->start, resource_size(rrp), "atyfb")) { + par->aux_start = rrp->start; + par->aux_size = resource_size(rrp); + raddr = rrp->start; + PRINTKI("using auxiliary register aperture\n"); + } + + info->fix.mmio_start = raddr; + par->ati_regbase = ioremap(info->fix.mmio_start, 0x1000); + if (par->ati_regbase == NULL) + return -ENOMEM; + + info->fix.mmio_start += par->aux_start ? 0x400 : 0xc00; + par->ati_regbase += par->aux_start ? 0x400 : 0xc00; + + /* + * Enable memory-space accesses using config-space + * command register. + */ + pci_read_config_word(pdev, PCI_COMMAND, &tmp); + if (!(tmp & PCI_COMMAND_MEMORY)) { + tmp |= PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, tmp); + } +#ifdef __BIG_ENDIAN + /* Use the big-endian aperture */ + addr += 0x800000; +#endif + + /* Map in frame buffer */ + info->fix.smem_start = addr; + info->screen_base = ioremap(addr, 0x800000); + if (info->screen_base == NULL) { + ret = -ENOMEM; + goto atyfb_setup_generic_fail; + } + + ret = correct_chipset(par); + if (ret) + goto atyfb_setup_generic_fail; +#ifdef __i386__ + ret = init_from_bios(par); + if (ret) + goto atyfb_setup_generic_fail; +#endif + if (!(aty_ld_le32(CRTC_GEN_CNTL, par) & CRTC_EXT_DISP_EN)) + par->clk_wr_offset = (inb(R_GENMO) & 0x0CU) >> 2; + else + par->clk_wr_offset = aty_ld_8(CLOCK_CNTL, par) & 0x03U; + + /* according to ATI, we should use clock 3 for acelerated mode */ + par->clk_wr_offset = 3; + + return 0; + +atyfb_setup_generic_fail: + iounmap(par->ati_regbase); + par->ati_regbase = NULL; + if (info->screen_base) { + iounmap(info->screen_base); + info->screen_base = NULL; + } + return ret; +} + +#endif /* !__sparc__ */ + +static int atyfb_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + unsigned long addr, res_start, res_size; + struct fb_info *info; + struct resource *rp; + struct atyfb_par *par; + int rc = -ENOMEM; + + /* Enable device in PCI config */ + if (pci_enable_device(pdev)) { + PRINTKE("Cannot enable PCI device\n"); + return -ENXIO; + } + + /* Find which resource to use */ + rp = &pdev->resource[0]; + if (rp->flags & IORESOURCE_IO) + rp = &pdev->resource[1]; + addr = rp->start; + if (!addr) + return -ENXIO; + + /* Reserve space */ + res_start = rp->start; + res_size = resource_size(rp); + if (!request_mem_region(res_start, res_size, "atyfb")) + return -EBUSY; + + /* Allocate framebuffer */ + info = framebuffer_alloc(sizeof(struct atyfb_par), &pdev->dev); + if (!info) { + PRINTKE("atyfb_pci_probe() can't alloc fb_info\n"); + return -ENOMEM; + } + par = info->par; + info->fix = atyfb_fix; + info->device = &pdev->dev; + par->pci_id = pdev->device; + par->res_start = res_start; + par->res_size = res_size; + par->irq = pdev->irq; + par->pdev = pdev; + + /* Setup "info" structure */ +#ifdef __sparc__ + rc = atyfb_setup_sparc(pdev, info, addr); +#else + rc = atyfb_setup_generic(pdev, info, addr); +#endif + if (rc) + goto err_release_mem; + + pci_set_drvdata(pdev, info); + + /* Init chip & register framebuffer */ + rc = aty_init(info); + if (rc) + goto err_release_io; + +#ifdef __sparc__ + /* + * Add /dev/fb mmap values. + */ + par->mmap_map[0].voff = 0x8000000000000000UL; + par->mmap_map[0].poff = (unsigned long) info->screen_base & PAGE_MASK; + par->mmap_map[0].size = info->fix.smem_len; + par->mmap_map[0].prot_mask = _PAGE_CACHE; + par->mmap_map[0].prot_flag = _PAGE_E; + par->mmap_map[1].voff = par->mmap_map[0].voff + info->fix.smem_len; + par->mmap_map[1].poff = (long)par->ati_regbase & PAGE_MASK; + par->mmap_map[1].size = PAGE_SIZE; + par->mmap_map[1].prot_mask = _PAGE_CACHE; + par->mmap_map[1].prot_flag = _PAGE_E; +#endif /* __sparc__ */ + + mutex_lock(&reboot_lock); + if (!reboot_info) + reboot_info = info; + mutex_unlock(&reboot_lock); + + return 0; + +err_release_io: +#ifdef __sparc__ + kfree(par->mmap_map); +#else + if (par->ati_regbase) + iounmap(par->ati_regbase); + if (info->screen_base) + iounmap(info->screen_base); +#endif +err_release_mem: + if (par->aux_start) + release_mem_region(par->aux_start, par->aux_size); + + release_mem_region(par->res_start, par->res_size); + framebuffer_release(info); + + return rc; +} + +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_ATARI + +static int __init atyfb_atari_probe(void) +{ + struct atyfb_par *par; + struct fb_info *info; + int m64_num; + u32 clock_r; + int num_found = 0; + + for (m64_num = 0; m64_num < mach64_count; m64_num++) { + if (!phys_vmembase[m64_num] || !phys_size[m64_num] || + !phys_guiregbase[m64_num]) { + PRINTKI("phys_*[%d] parameters not set => " + "returning early. \n", m64_num); + continue; + } + + info = framebuffer_alloc(sizeof(struct atyfb_par), NULL); + if (!info) { + PRINTKE("atyfb_atari_probe() can't alloc fb_info\n"); + return -ENOMEM; + } + par = info->par; + + info->fix = atyfb_fix; + + par->irq = (unsigned int) -1; /* something invalid */ + + /* + * Map the video memory (physical address given) + * to somewhere in the kernel address space. + */ + info->screen_base = ioremap(phys_vmembase[m64_num], phys_size[m64_num]); + info->fix.smem_start = (unsigned long)info->screen_base; /* Fake! */ + par->ati_regbase = ioremap(phys_guiregbase[m64_num], 0x10000) + + 0xFC00ul; + info->fix.mmio_start = (unsigned long)par->ati_regbase; /* Fake! */ + + aty_st_le32(CLOCK_CNTL, 0x12345678, par); + clock_r = aty_ld_le32(CLOCK_CNTL, par); + + switch (clock_r & 0x003F) { + case 0x12: + par->clk_wr_offset = 3; /* */ + break; + case 0x34: + par->clk_wr_offset = 2; /* Medusa ST-IO ISA Adapter etc. */ + break; + case 0x16: + par->clk_wr_offset = 1; /* */ + break; + case 0x38: + par->clk_wr_offset = 0; /* Panther 1 ISA Adapter (Gerald) */ + break; + } + + /* Fake pci_id for correct_chipset() */ + switch (aty_ld_le32(CNFG_CHIP_ID, par) & CFG_CHIP_TYPE) { + case 0x00d7: + par->pci_id = PCI_CHIP_MACH64GX; + break; + case 0x0057: + par->pci_id = PCI_CHIP_MACH64CX; + break; + default: + break; + } + + if (correct_chipset(par) || aty_init(info)) { + iounmap(info->screen_base); + iounmap(par->ati_regbase); + framebuffer_release(info); + } else { + num_found++; + } + } + + return num_found ? 0 : -ENXIO; +} + +#endif /* CONFIG_ATARI */ + +#ifdef CONFIG_PCI + +static void atyfb_remove(struct fb_info *info) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + + /* restore video mode */ + aty_set_crtc(par, &par->saved_crtc); + par->pll_ops->set_pll(info, &par->saved_pll); + + unregister_framebuffer(info); + +#ifdef CONFIG_FB_ATY_BACKLIGHT + if (M64_HAS(MOBIL_BUS)) + aty_bl_exit(info->bl_dev); +#endif + +#ifdef CONFIG_MTRR + if (par->mtrr_reg >= 0) { + mtrr_del(par->mtrr_reg, 0, 0); + par->mtrr_reg = -1; + } + if (par->mtrr_aper >= 0) { + mtrr_del(par->mtrr_aper, 0, 0); + par->mtrr_aper = -1; + } +#endif +#ifndef __sparc__ + if (par->ati_regbase) + iounmap(par->ati_regbase); + if (info->screen_base) + iounmap(info->screen_base); +#ifdef __BIG_ENDIAN + if (info->sprite.addr) + iounmap(info->sprite.addr); +#endif +#endif +#ifdef __sparc__ + kfree(par->mmap_map); +#endif + if (par->aux_start) + release_mem_region(par->aux_start, par->aux_size); + + if (par->res_start) + release_mem_region(par->res_start, par->res_size); + + framebuffer_release(info); +} + + +static void atyfb_pci_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + + mutex_lock(&reboot_lock); + if (reboot_info == info) + reboot_info = NULL; + mutex_unlock(&reboot_lock); + + atyfb_remove(info); +} + +static struct pci_device_id atyfb_pci_tbl[] = { +#ifdef CONFIG_FB_ATY_GX + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GX) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64CX) }, +#endif /* CONFIG_FB_ATY_GX */ + +#ifdef CONFIG_FB_ATY_CT + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64CT) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64ET) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LT) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64VT) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GT) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64VU) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GU) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LG) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64VV) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GV) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GW) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GY) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GZ) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GB) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GD) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GI) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GP) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GQ) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LB) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LD) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LI) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LP) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LQ) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GM) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GN) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GO) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GL) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GR) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64GS) }, + + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LM) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LN) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LR) }, + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_CHIP_MACH64LS) }, +#endif /* CONFIG_FB_ATY_CT */ + { } +}; + +MODULE_DEVICE_TABLE(pci, atyfb_pci_tbl); + +static struct pci_driver atyfb_driver = { + .name = "atyfb", + .id_table = atyfb_pci_tbl, + .probe = atyfb_pci_probe, + .remove = atyfb_pci_remove, +#ifdef CONFIG_PM + .suspend = atyfb_pci_suspend, + .resume = atyfb_pci_resume, +#endif /* CONFIG_PM */ +}; + +#endif /* CONFIG_PCI */ + +#ifndef MODULE +static int __init atyfb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "noaccel", 7)) { + noaccel = 1; +#ifdef CONFIG_MTRR + } else if (!strncmp(this_opt, "nomtrr", 6)) { + nomtrr = 1; +#endif + } else if (!strncmp(this_opt, "vram:", 5)) + vram = simple_strtoul(this_opt + 5, NULL, 0); + else if (!strncmp(this_opt, "pll:", 4)) + pll = simple_strtoul(this_opt + 4, NULL, 0); + else if (!strncmp(this_opt, "mclk:", 5)) + mclk = simple_strtoul(this_opt + 5, NULL, 0); + else if (!strncmp(this_opt, "xclk:", 5)) + xclk = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "comp_sync:", 10)) + comp_sync = simple_strtoul(this_opt+10, NULL, 0); + else if (!strncmp(this_opt, "backlight:", 10)) + backlight = simple_strtoul(this_opt+10, NULL, 0); +#ifdef CONFIG_PPC + else if (!strncmp(this_opt, "vmode:", 6)) { + unsigned int vmode = + simple_strtoul(this_opt + 6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX) + default_vmode = vmode; + } else if (!strncmp(this_opt, "cmode:", 6)) { + unsigned int cmode = + simple_strtoul(this_opt + 6, NULL, 0); + switch (cmode) { + case 0: + case 8: + default_cmode = CMODE_8; + break; + case 15: + case 16: + default_cmode = CMODE_16; + break; + case 24: + case 32: + default_cmode = CMODE_32; + break; + } + } +#endif +#ifdef CONFIG_ATARI + /* + * Why do we need this silly Mach64 argument? + * We are already here because of mach64= so its redundant. + */ + else if (MACH_IS_ATARI + && (!strncmp(this_opt, "Mach64:", 7))) { + static unsigned char m64_num; + static char mach64_str[80]; + strlcpy(mach64_str, this_opt + 7, sizeof(mach64_str)); + if (!store_video_par(mach64_str, m64_num)) { + m64_num++; + mach64_count = m64_num; + } + } +#endif + else + mode = this_opt; + } + return 0; +} +#endif /* MODULE */ + +static int atyfb_reboot_notify(struct notifier_block *nb, + unsigned long code, void *unused) +{ + struct atyfb_par *par; + + if (code != SYS_RESTART) + return NOTIFY_DONE; + + mutex_lock(&reboot_lock); + + if (!reboot_info) + goto out; + + if (!lock_fb_info(reboot_info)) + goto out; + + par = reboot_info->par; + + /* + * HP OmniBook 500's BIOS doesn't like the state of the + * hardware after atyfb has been used. Restore the hardware + * to the original state to allow successful reboots. + */ + aty_set_crtc(par, &par->saved_crtc); + par->pll_ops->set_pll(reboot_info, &par->saved_pll); + + unlock_fb_info(reboot_info); + out: + mutex_unlock(&reboot_lock); + + return NOTIFY_DONE; +} + +static struct notifier_block atyfb_reboot_notifier = { + .notifier_call = atyfb_reboot_notify, +}; + +static const struct dmi_system_id atyfb_reboot_ids[] = { + { + .ident = "HP OmniBook 500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP OmniBook PC"), + DMI_MATCH(DMI_PRODUCT_VERSION, "HP OmniBook 500 FA"), + }, + }, + + { } +}; + +static int __init atyfb_init(void) +{ + int err1 = 1, err2 = 1; +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("atyfb", &option)) + return -ENODEV; + atyfb_setup(option); +#endif + +#ifdef CONFIG_PCI + err1 = pci_register_driver(&atyfb_driver); +#endif +#ifdef CONFIG_ATARI + err2 = atyfb_atari_probe(); +#endif + + if (err1 && err2) + return -ENODEV; + + if (dmi_check_system(atyfb_reboot_ids)) + register_reboot_notifier(&atyfb_reboot_notifier); + + return 0; +} + +static void __exit atyfb_exit(void) +{ + if (dmi_check_system(atyfb_reboot_ids)) + unregister_reboot_notifier(&atyfb_reboot_notifier); + +#ifdef CONFIG_PCI + pci_unregister_driver(&atyfb_driver); +#endif +} + +module_init(atyfb_init); +module_exit(atyfb_exit); + +MODULE_DESCRIPTION("FBDev driver for ATI Mach64 cards"); +MODULE_LICENSE("GPL"); +module_param(noaccel, bool, 0); +MODULE_PARM_DESC(noaccel, "bool: disable acceleration"); +module_param(vram, int, 0); +MODULE_PARM_DESC(vram, "int: override size of video ram"); +module_param(pll, int, 0); +MODULE_PARM_DESC(pll, "int: override video clock"); +module_param(mclk, int, 0); +MODULE_PARM_DESC(mclk, "int: override memory clock"); +module_param(xclk, int, 0); +MODULE_PARM_DESC(xclk, "int: override accelerated engine clock"); +module_param(comp_sync, int, 0); +MODULE_PARM_DESC(comp_sync, "Set composite sync signal to low (0) or high (1)"); +module_param(mode, charp, 0); +MODULE_PARM_DESC(mode, "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); +#ifdef CONFIG_MTRR +module_param(nomtrr, bool, 0); +MODULE_PARM_DESC(nomtrr, "bool: disable use of MTRR registers"); +#endif diff --git a/drivers/video/fbdev/aty/mach64_accel.c b/drivers/video/fbdev/aty/mach64_accel.c new file mode 100644 index 000000000000..182bd680141f --- /dev/null +++ b/drivers/video/fbdev/aty/mach64_accel.c @@ -0,0 +1,430 @@ + +/* + * ATI Mach64 Hardware Acceleration + */ + +#include <linux/delay.h> +#include <asm/unaligned.h> +#include <linux/fb.h> +#include <video/mach64.h> +#include "atyfb.h" + + /* + * Generic Mach64 routines + */ + +/* this is for DMA GUI engine! work in progress */ +typedef struct { + u32 frame_buf_offset; + u32 system_mem_addr; + u32 command; + u32 reserved; +} BM_DESCRIPTOR_ENTRY; + +#define LAST_DESCRIPTOR (1 << 31) +#define SYSTEM_TO_FRAME_BUFFER 0 + +static u32 rotation24bpp(u32 dx, u32 direction) +{ + u32 rotation; + if (direction & DST_X_LEFT_TO_RIGHT) { + rotation = (dx / 4) % 6; + } else { + rotation = ((dx + 2) / 4) % 6; + } + + return ((rotation << 8) | DST_24_ROTATION_ENABLE); +} + +void aty_reset_engine(const struct atyfb_par *par) +{ + /* reset engine */ + aty_st_le32(GEN_TEST_CNTL, + aty_ld_le32(GEN_TEST_CNTL, par) & + ~(GUI_ENGINE_ENABLE | HWCURSOR_ENABLE), par); + /* enable engine */ + aty_st_le32(GEN_TEST_CNTL, + aty_ld_le32(GEN_TEST_CNTL, par) | GUI_ENGINE_ENABLE, par); + /* ensure engine is not locked up by clearing any FIFO or */ + /* HOST errors */ + aty_st_le32(BUS_CNTL, + aty_ld_le32(BUS_CNTL, par) | BUS_HOST_ERR_ACK | BUS_FIFO_ERR_ACK, par); +} + +static void reset_GTC_3D_engine(const struct atyfb_par *par) +{ + aty_st_le32(SCALE_3D_CNTL, 0xc0, par); + mdelay(GTC_3D_RESET_DELAY); + aty_st_le32(SETUP_CNTL, 0x00, par); + mdelay(GTC_3D_RESET_DELAY); + aty_st_le32(SCALE_3D_CNTL, 0x00, par); + mdelay(GTC_3D_RESET_DELAY); +} + +void aty_init_engine(struct atyfb_par *par, struct fb_info *info) +{ + u32 pitch_value; + u32 vxres; + + /* determine modal information from global mode structure */ + pitch_value = info->fix.line_length / (info->var.bits_per_pixel / 8); + vxres = info->var.xres_virtual; + + if (info->var.bits_per_pixel == 24) { + /* In 24 bpp, the engine is in 8 bpp - this requires that all */ + /* horizontal coordinates and widths must be adjusted */ + pitch_value *= 3; + vxres *= 3; + } + + /* On GTC (RagePro), we need to reset the 3D engine before */ + if (M64_HAS(RESET_3D)) + reset_GTC_3D_engine(par); + + /* Reset engine, enable, and clear any engine errors */ + aty_reset_engine(par); + /* Ensure that vga page pointers are set to zero - the upper */ + /* page pointers are set to 1 to handle overflows in the */ + /* lower page */ + aty_st_le32(MEM_VGA_WP_SEL, 0x00010000, par); + aty_st_le32(MEM_VGA_RP_SEL, 0x00010000, par); + + /* ---- Setup standard engine context ---- */ + + /* All GUI registers here are FIFOed - therefore, wait for */ + /* the appropriate number of empty FIFO entries */ + wait_for_fifo(14, par); + + /* enable all registers to be loaded for context loads */ + aty_st_le32(CONTEXT_MASK, 0xFFFFFFFF, par); + + /* set destination pitch to modal pitch, set offset to zero */ + aty_st_le32(DST_OFF_PITCH, (pitch_value / 8) << 22, par); + + /* zero these registers (set them to a known state) */ + aty_st_le32(DST_Y_X, 0, par); + aty_st_le32(DST_HEIGHT, 0, par); + aty_st_le32(DST_BRES_ERR, 0, par); + aty_st_le32(DST_BRES_INC, 0, par); + aty_st_le32(DST_BRES_DEC, 0, par); + + /* set destination drawing attributes */ + aty_st_le32(DST_CNTL, DST_LAST_PEL | DST_Y_TOP_TO_BOTTOM | + DST_X_LEFT_TO_RIGHT, par); + + /* set source pitch to modal pitch, set offset to zero */ + aty_st_le32(SRC_OFF_PITCH, (pitch_value / 8) << 22, par); + + /* set these registers to a known state */ + aty_st_le32(SRC_Y_X, 0, par); + aty_st_le32(SRC_HEIGHT1_WIDTH1, 1, par); + aty_st_le32(SRC_Y_X_START, 0, par); + aty_st_le32(SRC_HEIGHT2_WIDTH2, 1, par); + + /* set source pixel retrieving attributes */ + aty_st_le32(SRC_CNTL, SRC_LINE_X_LEFT_TO_RIGHT, par); + + /* set host attributes */ + wait_for_fifo(13, par); + aty_st_le32(HOST_CNTL, 0, par); + + /* set pattern attributes */ + aty_st_le32(PAT_REG0, 0, par); + aty_st_le32(PAT_REG1, 0, par); + aty_st_le32(PAT_CNTL, 0, par); + + /* set scissors to modal size */ + aty_st_le32(SC_LEFT, 0, par); + aty_st_le32(SC_TOP, 0, par); + aty_st_le32(SC_BOTTOM, par->crtc.vyres - 1, par); + aty_st_le32(SC_RIGHT, vxres - 1, par); + + /* set background color to minimum value (usually BLACK) */ + aty_st_le32(DP_BKGD_CLR, 0, par); + + /* set foreground color to maximum value (usually WHITE) */ + aty_st_le32(DP_FRGD_CLR, 0xFFFFFFFF, par); + + /* set write mask to effect all pixel bits */ + aty_st_le32(DP_WRITE_MASK, 0xFFFFFFFF, par); + + /* set foreground mix to overpaint and background mix to */ + /* no-effect */ + aty_st_le32(DP_MIX, FRGD_MIX_S | BKGD_MIX_D, par); + + /* set primary source pixel channel to foreground color */ + /* register */ + aty_st_le32(DP_SRC, FRGD_SRC_FRGD_CLR, par); + + /* set compare functionality to false (no-effect on */ + /* destination) */ + wait_for_fifo(3, par); + aty_st_le32(CLR_CMP_CLR, 0, par); + aty_st_le32(CLR_CMP_MASK, 0xFFFFFFFF, par); + aty_st_le32(CLR_CMP_CNTL, 0, par); + + /* set pixel depth */ + wait_for_fifo(2, par); + aty_st_le32(DP_PIX_WIDTH, par->crtc.dp_pix_width, par); + aty_st_le32(DP_CHAIN_MASK, par->crtc.dp_chain_mask, par); + + wait_for_fifo(5, par); + aty_st_le32(SCALE_3D_CNTL, 0, par); + aty_st_le32(Z_CNTL, 0, par); + aty_st_le32(CRTC_INT_CNTL, aty_ld_le32(CRTC_INT_CNTL, par) & ~0x20, + par); + aty_st_le32(GUI_TRAJ_CNTL, 0x100023, par); + + /* insure engine is idle before leaving */ + wait_for_idle(par); +} + + /* + * Accelerated functions + */ + +static inline void draw_rect(s16 x, s16 y, u16 width, u16 height, + struct atyfb_par *par) +{ + /* perform rectangle fill */ + wait_for_fifo(2, par); + aty_st_le32(DST_Y_X, (x << 16) | y, par); + aty_st_le32(DST_HEIGHT_WIDTH, (width << 16) | height, par); + par->blitter_may_be_busy = 1; +} + +void atyfb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 dy = area->dy, sy = area->sy, direction = DST_LAST_PEL; + u32 sx = area->sx, dx = area->dx, width = area->width, rotation = 0; + + if (par->asleep) + return; + if (!area->width || !area->height) + return; + if (!par->accel_flags) { + cfb_copyarea(info, area); + return; + } + + if (info->var.bits_per_pixel == 24) { + /* In 24 bpp, the engine is in 8 bpp - this requires that all */ + /* horizontal coordinates and widths must be adjusted */ + sx *= 3; + dx *= 3; + width *= 3; + } + + if (area->sy < area->dy) { + dy += area->height - 1; + sy += area->height - 1; + } else + direction |= DST_Y_TOP_TO_BOTTOM; + + if (sx < dx) { + dx += width - 1; + sx += width - 1; + } else + direction |= DST_X_LEFT_TO_RIGHT; + + if (info->var.bits_per_pixel == 24) { + rotation = rotation24bpp(dx, direction); + } + + wait_for_fifo(4, par); + aty_st_le32(DP_SRC, FRGD_SRC_BLIT, par); + aty_st_le32(SRC_Y_X, (sx << 16) | sy, par); + aty_st_le32(SRC_HEIGHT1_WIDTH1, (width << 16) | area->height, par); + aty_st_le32(DST_CNTL, direction | rotation, par); + draw_rect(dx, dy, width, area->height, par); +} + +void atyfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 color, dx = rect->dx, width = rect->width, rotation = 0; + + if (par->asleep) + return; + if (!rect->width || !rect->height) + return; + if (!par->accel_flags) { + cfb_fillrect(info, rect); + return; + } + + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) + color = ((u32 *)(info->pseudo_palette))[rect->color]; + else + color = rect->color; + + if (info->var.bits_per_pixel == 24) { + /* In 24 bpp, the engine is in 8 bpp - this requires that all */ + /* horizontal coordinates and widths must be adjusted */ + dx *= 3; + width *= 3; + rotation = rotation24bpp(dx, DST_X_LEFT_TO_RIGHT); + } + + wait_for_fifo(3, par); + aty_st_le32(DP_FRGD_CLR, color, par); + aty_st_le32(DP_SRC, + BKGD_SRC_BKGD_CLR | FRGD_SRC_FRGD_CLR | MONO_SRC_ONE, + par); + aty_st_le32(DST_CNTL, + DST_LAST_PEL | DST_Y_TOP_TO_BOTTOM | + DST_X_LEFT_TO_RIGHT | rotation, par); + draw_rect(dx, rect->dy, width, rect->height, par); +} + +void atyfb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 src_bytes, dx = image->dx, dy = image->dy, width = image->width; + u32 pix_width_save, pix_width, host_cntl, rotation = 0, src, mix; + + if (par->asleep) + return; + if (!image->width || !image->height) + return; + if (!par->accel_flags || + (image->depth != 1 && info->var.bits_per_pixel != image->depth)) { + cfb_imageblit(info, image); + return; + } + + pix_width = pix_width_save = aty_ld_le32(DP_PIX_WIDTH, par); + host_cntl = aty_ld_le32(HOST_CNTL, par) | HOST_BYTE_ALIGN; + + switch (image->depth) { + case 1: + pix_width &= ~(BYTE_ORDER_MASK | HOST_MASK); + pix_width |= (BYTE_ORDER_MSB_TO_LSB | HOST_1BPP); + break; + case 4: + pix_width &= ~(BYTE_ORDER_MASK | HOST_MASK); + pix_width |= (BYTE_ORDER_MSB_TO_LSB | HOST_4BPP); + break; + case 8: + pix_width &= ~HOST_MASK; + pix_width |= HOST_8BPP; + break; + case 15: + pix_width &= ~HOST_MASK; + pix_width |= HOST_15BPP; + break; + case 16: + pix_width &= ~HOST_MASK; + pix_width |= HOST_16BPP; + break; + case 24: + pix_width &= ~HOST_MASK; + pix_width |= HOST_24BPP; + break; + case 32: + pix_width &= ~HOST_MASK; + pix_width |= HOST_32BPP; + break; + } + + if (info->var.bits_per_pixel == 24) { + /* In 24 bpp, the engine is in 8 bpp - this requires that all */ + /* horizontal coordinates and widths must be adjusted */ + dx *= 3; + width *= 3; + + rotation = rotation24bpp(dx, DST_X_LEFT_TO_RIGHT); + + pix_width &= ~DST_MASK; + pix_width |= DST_8BPP; + + /* + * since Rage 3D IIc we have DP_HOST_TRIPLE_EN bit + * this hwaccelerated triple has an issue with not aligned data + */ + if (M64_HAS(HW_TRIPLE) && image->width % 8 == 0) + pix_width |= DP_HOST_TRIPLE_EN; + } + + if (image->depth == 1) { + u32 fg, bg; + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + fg = ((u32*)(info->pseudo_palette))[image->fg_color]; + bg = ((u32*)(info->pseudo_palette))[image->bg_color]; + } else { + fg = image->fg_color; + bg = image->bg_color; + } + + wait_for_fifo(2, par); + aty_st_le32(DP_BKGD_CLR, bg, par); + aty_st_le32(DP_FRGD_CLR, fg, par); + src = MONO_SRC_HOST | FRGD_SRC_FRGD_CLR | BKGD_SRC_BKGD_CLR; + mix = FRGD_MIX_S | BKGD_MIX_S; + } else { + src = MONO_SRC_ONE | FRGD_SRC_HOST; + mix = FRGD_MIX_D_XOR_S | BKGD_MIX_D; + } + + wait_for_fifo(6, par); + aty_st_le32(DP_WRITE_MASK, 0xFFFFFFFF, par); + aty_st_le32(DP_PIX_WIDTH, pix_width, par); + aty_st_le32(DP_MIX, mix, par); + aty_st_le32(DP_SRC, src, par); + aty_st_le32(HOST_CNTL, host_cntl, par); + aty_st_le32(DST_CNTL, DST_Y_TOP_TO_BOTTOM | DST_X_LEFT_TO_RIGHT | rotation, par); + + draw_rect(dx, dy, width, image->height, par); + src_bytes = (((image->width * image->depth) + 7) / 8) * image->height; + + /* manual triple each pixel */ + if (info->var.bits_per_pixel == 24 && !(pix_width & DP_HOST_TRIPLE_EN)) { + int inbit, outbit, mult24, byte_id_in_dword, width; + u8 *pbitmapin = (u8*)image->data, *pbitmapout; + u32 hostdword; + + for (width = image->width, inbit = 7, mult24 = 0; src_bytes; ) { + for (hostdword = 0, pbitmapout = (u8*)&hostdword, byte_id_in_dword = 0; + byte_id_in_dword < 4 && src_bytes; + byte_id_in_dword++, pbitmapout++) { + for (outbit = 7; outbit >= 0; outbit--) { + *pbitmapout |= (((*pbitmapin >> inbit) & 1) << outbit); + mult24++; + /* next bit */ + if (mult24 == 3) { + mult24 = 0; + inbit--; + width--; + } + + /* next byte */ + if (inbit < 0 || width == 0) { + src_bytes--; + pbitmapin++; + inbit = 7; + + if (width == 0) { + width = image->width; + outbit = 0; + } + } + } + } + wait_for_fifo(1, par); + aty_st_le32(HOST_DATA0, hostdword, par); + } + } else { + u32 *pbitmap, dwords = (src_bytes + 3) / 4; + for (pbitmap = (u32*)(image->data); dwords; dwords--, pbitmap++) { + wait_for_fifo(1, par); + aty_st_le32(HOST_DATA0, get_unaligned_le32(pbitmap), par); + } + } + + /* restore pix_width */ + wait_for_fifo(1, par); + aty_st_le32(DP_PIX_WIDTH, pix_width_save, par); +} diff --git a/drivers/video/fbdev/aty/mach64_ct.c b/drivers/video/fbdev/aty/mach64_ct.c new file mode 100644 index 000000000000..51f29d627ceb --- /dev/null +++ b/drivers/video/fbdev/aty/mach64_ct.c @@ -0,0 +1,649 @@ + +/* + * ATI Mach64 CT/VT/GT/LT Support + */ + +#include <linux/fb.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <video/mach64.h> +#include "atyfb.h" +#ifdef CONFIG_PPC +#include <asm/machdep.h> +#endif + +#undef DEBUG + +static int aty_valid_pll_ct (const struct fb_info *info, u32 vclk_per, struct pll_ct *pll); +static int aty_dsp_gt (const struct fb_info *info, u32 bpp, struct pll_ct *pll); +static int aty_var_to_pll_ct(const struct fb_info *info, u32 vclk_per, u32 bpp, union aty_pll *pll); +static u32 aty_pll_to_var_ct(const struct fb_info *info, const union aty_pll *pll); + +u8 aty_ld_pll_ct(int offset, const struct atyfb_par *par) +{ + u8 res; + + /* write addr byte */ + aty_st_8(CLOCK_CNTL_ADDR, (offset << 2) & PLL_ADDR, par); + /* read the register value */ + res = aty_ld_8(CLOCK_CNTL_DATA, par); + return res; +} + +static void aty_st_pll_ct(int offset, u8 val, const struct atyfb_par *par) +{ + /* write addr byte */ + aty_st_8(CLOCK_CNTL_ADDR, ((offset << 2) & PLL_ADDR) | PLL_WR_EN, par); + /* write the register value */ + aty_st_8(CLOCK_CNTL_DATA, val & PLL_DATA, par); + aty_st_8(CLOCK_CNTL_ADDR, ((offset << 2) & PLL_ADDR) & ~PLL_WR_EN, par); +} + +/* + * by Daniel Mantione + * <daniel.mantione@freepascal.org> + * + * + * ATI Mach64 CT clock synthesis description. + * + * All clocks on the Mach64 can be calculated using the same principle: + * + * XTALIN * x * FB_DIV + * CLK = ---------------------- + * PLL_REF_DIV * POST_DIV + * + * XTALIN is a fixed speed clock. Common speeds are 14.31 MHz and 29.50 MHz. + * PLL_REF_DIV can be set by the user, but is the same for all clocks. + * FB_DIV can be set by the user for each clock individually, it should be set + * between 128 and 255, the chip will generate a bad clock signal for too low + * values. + * x depends on the type of clock; usually it is 2, but for the MCLK it can also + * be set to 4. + * POST_DIV can be set by the user for each clock individually, Possible values + * are 1,2,4,8 and for some clocks other values are available too. + * CLK is of course the clock speed that is generated. + * + * The Mach64 has these clocks: + * + * MCLK The clock rate of the chip + * XCLK The clock rate of the on-chip memory + * VCLK0 First pixel clock of first CRT controller + * VCLK1 Second pixel clock of first CRT controller + * VCLK2 Third pixel clock of first CRT controller + * VCLK3 Fourth pixel clock of first CRT controller + * VCLK Selected pixel clock, one of VCLK0, VCLK1, VCLK2, VCLK3 + * V2CLK Pixel clock of the second CRT controller. + * SCLK Multi-purpose clock + * + * - MCLK and XCLK use the same FB_DIV + * - VCLK0 .. VCLK3 use the same FB_DIV + * - V2CLK is needed when the second CRTC is used (can be used for dualhead); + * i.e. CRT monitor connected to laptop has different resolution than built + * in LCD monitor. + * - SCLK is not available on all cards; it is know to exist on the Rage LT-PRO, + * Rage XL and Rage Mobility. It is know not to exist on the Mach64 VT. + * - V2CLK is not available on all cards, most likely only the Rage LT-PRO, + * the Rage XL and the Rage Mobility + * + * SCLK can be used to: + * - Clock the chip instead of MCLK + * - Replace XTALIN with a user defined frequency + * - Generate the pixel clock for the LCD monitor (instead of VCLK) + */ + + /* + * It can be quite hard to calculate XCLK and MCLK if they don't run at the + * same frequency. Luckily, until now all cards that need asynchrone clock + * speeds seem to have SCLK. + * So this driver uses SCLK to clock the chip and XCLK to clock the memory. + */ + +/* ------------------------------------------------------------------------- */ + +/* + * PLL programming (Mach64 CT family) + * + * + * This procedure sets the display fifo. The display fifo is a buffer that + * contains data read from the video memory that waits to be processed by + * the CRT controller. + * + * On the more modern Mach64 variants, the chip doesn't calculate the + * interval after which the display fifo has to be reloaded from memory + * automatically, the driver has to do it instead. + */ + +#define Maximum_DSP_PRECISION 7 +static u8 postdividers[] = {1,2,4,8,3}; + +static int aty_dsp_gt(const struct fb_info *info, u32 bpp, struct pll_ct *pll) +{ + u32 dsp_off, dsp_on, dsp_xclks; + u32 multiplier, divider, ras_multiplier, ras_divider, tmp; + u8 vshift, xshift; + s8 dsp_precision; + + multiplier = ((u32)pll->mclk_fb_div) * pll->vclk_post_div_real; + divider = ((u32)pll->vclk_fb_div) * pll->xclk_ref_div; + + ras_multiplier = pll->xclkmaxrasdelay; + ras_divider = 1; + + if (bpp>=8) + divider = divider * (bpp >> 2); + + vshift = (6 - 2) - pll->xclk_post_div; /* FIFO is 64 bits wide in accelerator mode ... */ + + if (bpp == 0) + vshift--; /* ... but only 32 bits in VGA mode. */ + +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (pll->xres != 0) { + struct atyfb_par *par = (struct atyfb_par *) info->par; + + multiplier = multiplier * par->lcd_width; + divider = divider * pll->xres & ~7; + + ras_multiplier = ras_multiplier * par->lcd_width; + ras_divider = ras_divider * pll->xres & ~7; + } +#endif + /* If we don't do this, 32 bits for multiplier & divider won't be + enough in certain situations! */ + while (((multiplier | divider) & 1) == 0) { + multiplier = multiplier >> 1; + divider = divider >> 1; + } + + /* Determine DSP precision first */ + tmp = ((multiplier * pll->fifo_size) << vshift) / divider; + + for (dsp_precision = -5; tmp; dsp_precision++) + tmp >>= 1; + if (dsp_precision < 0) + dsp_precision = 0; + else if (dsp_precision > Maximum_DSP_PRECISION) + dsp_precision = Maximum_DSP_PRECISION; + + xshift = 6 - dsp_precision; + vshift += xshift; + + /* Move on to dsp_off */ + dsp_off = ((multiplier * (pll->fifo_size - 1)) << vshift) / divider - + (1 << (vshift - xshift)); + +/* if (bpp == 0) + dsp_on = ((multiplier * 20 << vshift) + divider) / divider; + else */ + { + dsp_on = ((multiplier << vshift) + divider) / divider; + tmp = ((ras_multiplier << xshift) + ras_divider) / ras_divider; + if (dsp_on < tmp) + dsp_on = tmp; + dsp_on = dsp_on + (tmp * 2) + (pll->xclkpagefaultdelay << xshift); + } + + /* Calculate rounding factor and apply it to dsp_on */ + tmp = ((1 << (Maximum_DSP_PRECISION - dsp_precision)) - 1) >> 1; + dsp_on = ((dsp_on + tmp) / (tmp + 1)) * (tmp + 1); + + if (dsp_on >= ((dsp_off / (tmp + 1)) * (tmp + 1))) { + dsp_on = dsp_off - (multiplier << vshift) / divider; + dsp_on = (dsp_on / (tmp + 1)) * (tmp + 1); + } + + /* Last but not least: dsp_xclks */ + dsp_xclks = ((multiplier << (vshift + 5)) + divider) / divider; + + /* Get register values. */ + pll->dsp_on_off = (dsp_on << 16) + dsp_off; + pll->dsp_config = (dsp_precision << 20) | (pll->dsp_loop_latency << 16) | dsp_xclks; +#ifdef DEBUG + printk("atyfb(%s): dsp_config 0x%08x, dsp_on_off 0x%08x\n", + __func__, pll->dsp_config, pll->dsp_on_off); +#endif + return 0; +} + +static int aty_valid_pll_ct(const struct fb_info *info, u32 vclk_per, struct pll_ct *pll) +{ + u32 q; + struct atyfb_par *par = (struct atyfb_par *) info->par; + int pllvclk; + + /* FIXME: use the VTB/GTB /{3,6,12} post dividers if they're better suited */ + q = par->ref_clk_per * pll->pll_ref_div * 4 / vclk_per; + if (q < 16*8 || q > 255*8) { + printk(KERN_CRIT "atyfb: vclk out of range\n"); + return -EINVAL; + } else { + pll->vclk_post_div = (q < 128*8); + pll->vclk_post_div += (q < 64*8); + pll->vclk_post_div += (q < 32*8); + } + pll->vclk_post_div_real = postdividers[pll->vclk_post_div]; + // pll->vclk_post_div <<= 6; + pll->vclk_fb_div = q * pll->vclk_post_div_real / 8; + pllvclk = (1000000 * 2 * pll->vclk_fb_div) / + (par->ref_clk_per * pll->pll_ref_div); +#ifdef DEBUG + printk("atyfb(%s): pllvclk=%d MHz, vclk=%d MHz\n", + __func__, pllvclk, pllvclk / pll->vclk_post_div_real); +#endif + pll->pll_vclk_cntl = 0x03; /* VCLK = PLL_VCLK/VCLKx_POST */ + + /* Set ECP (scaler/overlay clock) divider */ + if (par->pll_limits.ecp_max) { + int ecp = pllvclk / pll->vclk_post_div_real; + int ecp_div = 0; + + while (ecp > par->pll_limits.ecp_max && ecp_div < 2) { + ecp >>= 1; + ecp_div++; + } + pll->pll_vclk_cntl |= ecp_div << 4; + } + + return 0; +} + +static int aty_var_to_pll_ct(const struct fb_info *info, u32 vclk_per, u32 bpp, union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + int err; + + if ((err = aty_valid_pll_ct(info, vclk_per, &pll->ct))) + return err; + if (M64_HAS(GTB_DSP) && (err = aty_dsp_gt(info, bpp, &pll->ct))) + return err; + /*aty_calc_pll_ct(info, &pll->ct);*/ + return 0; +} + +static u32 aty_pll_to_var_ct(const struct fb_info *info, const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 ret; + ret = par->ref_clk_per * pll->ct.pll_ref_div * pll->ct.vclk_post_div_real / pll->ct.vclk_fb_div / 2; +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if(pll->ct.xres > 0) { + ret *= par->lcd_width; + ret /= pll->ct.xres; + } +#endif +#ifdef DEBUG + printk("atyfb(%s): calculated 0x%08X(%i)\n", __func__, ret, ret); +#endif + return ret; +} + +void aty_set_pll_ct(const struct fb_info *info, const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 crtc_gen_cntl, lcd_gen_cntrl; + u8 tmp, tmp2; + + lcd_gen_cntrl = 0; +#ifdef DEBUG + printk("atyfb(%s): about to program:\n" + "pll_ext_cntl=0x%02x pll_gen_cntl=0x%02x pll_vclk_cntl=0x%02x\n", + __func__, + pll->ct.pll_ext_cntl, pll->ct.pll_gen_cntl, pll->ct.pll_vclk_cntl); + + printk("atyfb(%s): setting clock %lu for FeedBackDivider %i, ReferenceDivider %i, PostDivider %i(%i)\n", + __func__, + par->clk_wr_offset, pll->ct.vclk_fb_div, + pll->ct.pll_ref_div, pll->ct.vclk_post_div, pll->ct.vclk_post_div_real); +#endif +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + /* turn off LCD */ + lcd_gen_cntrl = aty_ld_lcd(LCD_GEN_CNTL, par); + aty_st_lcd(LCD_GEN_CNTL, lcd_gen_cntrl & ~LCD_ON, par); + } +#endif + aty_st_8(CLOCK_CNTL, par->clk_wr_offset | CLOCK_STROBE, par); + + /* Temporarily switch to accelerator mode */ + crtc_gen_cntl = aty_ld_le32(CRTC_GEN_CNTL, par); + if (!(crtc_gen_cntl & CRTC_EXT_DISP_EN)) + aty_st_le32(CRTC_GEN_CNTL, crtc_gen_cntl | CRTC_EXT_DISP_EN, par); + + /* Reset VCLK generator */ + aty_st_pll_ct(PLL_VCLK_CNTL, pll->ct.pll_vclk_cntl, par); + + /* Set post-divider */ + tmp2 = par->clk_wr_offset << 1; + tmp = aty_ld_pll_ct(VCLK_POST_DIV, par); + tmp &= ~(0x03U << tmp2); + tmp |= ((pll->ct.vclk_post_div & 0x03U) << tmp2); + aty_st_pll_ct(VCLK_POST_DIV, tmp, par); + + /* Set extended post-divider */ + tmp = aty_ld_pll_ct(PLL_EXT_CNTL, par); + tmp &= ~(0x10U << par->clk_wr_offset); + tmp &= 0xF0U; + tmp |= pll->ct.pll_ext_cntl; + aty_st_pll_ct(PLL_EXT_CNTL, tmp, par); + + /* Set feedback divider */ + tmp = VCLK0_FB_DIV + par->clk_wr_offset; + aty_st_pll_ct(tmp, (pll->ct.vclk_fb_div & 0xFFU), par); + + aty_st_pll_ct(PLL_GEN_CNTL, (pll->ct.pll_gen_cntl & (~(PLL_OVERRIDE | PLL_MCLK_RST))) | OSC_EN, par); + + /* End VCLK generator reset */ + aty_st_pll_ct(PLL_VCLK_CNTL, pll->ct.pll_vclk_cntl & ~(PLL_VCLK_RST), par); + mdelay(5); + + aty_st_pll_ct(PLL_GEN_CNTL, pll->ct.pll_gen_cntl, par); + aty_st_pll_ct(PLL_VCLK_CNTL, pll->ct.pll_vclk_cntl, par); + mdelay(1); + + /* Restore mode register */ + if (!(crtc_gen_cntl & CRTC_EXT_DISP_EN)) + aty_st_le32(CRTC_GEN_CNTL, crtc_gen_cntl, par); + + if (M64_HAS(GTB_DSP)) { + u8 dll_cntl; + + if (M64_HAS(XL_DLL)) + dll_cntl = 0x80; + else if (par->ram_type >= SDRAM) + dll_cntl = 0xa6; + else + dll_cntl = 0xa0; + aty_st_pll_ct(DLL_CNTL, dll_cntl, par); + aty_st_pll_ct(VFC_CNTL, 0x1b, par); + aty_st_le32(DSP_CONFIG, pll->ct.dsp_config, par); + aty_st_le32(DSP_ON_OFF, pll->ct.dsp_on_off, par); + + mdelay(10); + aty_st_pll_ct(DLL_CNTL, dll_cntl, par); + mdelay(10); + aty_st_pll_ct(DLL_CNTL, dll_cntl | 0x40, par); + mdelay(10); + aty_st_pll_ct(DLL_CNTL, dll_cntl & ~0x40, par); + } +#ifdef CONFIG_FB_ATY_GENERIC_LCD + if (par->lcd_table != 0) { + /* restore LCD */ + aty_st_lcd(LCD_GEN_CNTL, lcd_gen_cntrl, par); + } +#endif +} + +static void aty_get_pll_ct(const struct fb_info *info, union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u8 tmp, clock; + + clock = aty_ld_8(CLOCK_CNTL, par) & 0x03U; + tmp = clock << 1; + pll->ct.vclk_post_div = (aty_ld_pll_ct(VCLK_POST_DIV, par) >> tmp) & 0x03U; + + pll->ct.pll_ext_cntl = aty_ld_pll_ct(PLL_EXT_CNTL, par) & 0x0FU; + pll->ct.vclk_fb_div = aty_ld_pll_ct(VCLK0_FB_DIV + clock, par) & 0xFFU; + pll->ct.pll_ref_div = aty_ld_pll_ct(PLL_REF_DIV, par); + pll->ct.mclk_fb_div = aty_ld_pll_ct(MCLK_FB_DIV, par); + + pll->ct.pll_gen_cntl = aty_ld_pll_ct(PLL_GEN_CNTL, par); + pll->ct.pll_vclk_cntl = aty_ld_pll_ct(PLL_VCLK_CNTL, par); + + if (M64_HAS(GTB_DSP)) { + pll->ct.dsp_config = aty_ld_le32(DSP_CONFIG, par); + pll->ct.dsp_on_off = aty_ld_le32(DSP_ON_OFF, par); + } +} + +static int aty_init_pll_ct(const struct fb_info *info, union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u8 mpost_div, xpost_div, sclk_post_div_real; + u32 q, memcntl, trp; + u32 dsp_config, dsp_on_off, vga_dsp_config, vga_dsp_on_off; +#ifdef DEBUG + int pllmclk, pllsclk; +#endif + pll->ct.pll_ext_cntl = aty_ld_pll_ct(PLL_EXT_CNTL, par); + pll->ct.xclk_post_div = pll->ct.pll_ext_cntl & 0x07; + pll->ct.xclk_ref_div = 1; + switch (pll->ct.xclk_post_div) { + case 0: case 1: case 2: case 3: + break; + + case 4: + pll->ct.xclk_ref_div = 3; + pll->ct.xclk_post_div = 0; + break; + + default: + printk(KERN_CRIT "atyfb: Unsupported xclk source: %d.\n", pll->ct.xclk_post_div); + return -EINVAL; + } + pll->ct.mclk_fb_mult = 2; + if(pll->ct.pll_ext_cntl & PLL_MFB_TIMES_4_2B) { + pll->ct.mclk_fb_mult = 4; + pll->ct.xclk_post_div -= 1; + } + +#ifdef DEBUG + printk("atyfb(%s): mclk_fb_mult=%d, xclk_post_div=%d\n", + __func__, pll->ct.mclk_fb_mult, pll->ct.xclk_post_div); +#endif + + memcntl = aty_ld_le32(MEM_CNTL, par); + trp = (memcntl & 0x300) >> 8; + + pll->ct.xclkpagefaultdelay = ((memcntl & 0xc00) >> 10) + ((memcntl & 0x1000) >> 12) + trp + 2; + pll->ct.xclkmaxrasdelay = ((memcntl & 0x70000) >> 16) + trp + 2; + + if (M64_HAS(FIFO_32)) { + pll->ct.fifo_size = 32; + } else { + pll->ct.fifo_size = 24; + pll->ct.xclkpagefaultdelay += 2; + pll->ct.xclkmaxrasdelay += 3; + } + + switch (par->ram_type) { + case DRAM: + if (info->fix.smem_len<=ONE_MB) { + pll->ct.dsp_loop_latency = 10; + } else { + pll->ct.dsp_loop_latency = 8; + pll->ct.xclkpagefaultdelay += 2; + } + break; + case EDO: + case PSEUDO_EDO: + if (info->fix.smem_len<=ONE_MB) { + pll->ct.dsp_loop_latency = 9; + } else { + pll->ct.dsp_loop_latency = 8; + pll->ct.xclkpagefaultdelay += 1; + } + break; + case SDRAM: + if (info->fix.smem_len<=ONE_MB) { + pll->ct.dsp_loop_latency = 11; + } else { + pll->ct.dsp_loop_latency = 10; + pll->ct.xclkpagefaultdelay += 1; + } + break; + case SGRAM: + pll->ct.dsp_loop_latency = 8; + pll->ct.xclkpagefaultdelay += 3; + break; + default: + pll->ct.dsp_loop_latency = 11; + pll->ct.xclkpagefaultdelay += 3; + break; + } + + if (pll->ct.xclkmaxrasdelay <= pll->ct.xclkpagefaultdelay) + pll->ct.xclkmaxrasdelay = pll->ct.xclkpagefaultdelay + 1; + + /* Allow BIOS to override */ + dsp_config = aty_ld_le32(DSP_CONFIG, par); + dsp_on_off = aty_ld_le32(DSP_ON_OFF, par); + vga_dsp_config = aty_ld_le32(VGA_DSP_CONFIG, par); + vga_dsp_on_off = aty_ld_le32(VGA_DSP_ON_OFF, par); + + if (dsp_config) + pll->ct.dsp_loop_latency = (dsp_config & DSP_LOOP_LATENCY) >> 16; +#if 0 + FIXME: is it relevant for us? + if ((!dsp_on_off && !M64_HAS(RESET_3D)) || + ((dsp_on_off == vga_dsp_on_off) && + (!dsp_config || !((dsp_config ^ vga_dsp_config) & DSP_XCLKS_PER_QW)))) { + vga_dsp_on_off &= VGA_DSP_OFF; + vga_dsp_config &= VGA_DSP_XCLKS_PER_QW; + if (ATIDivide(vga_dsp_on_off, vga_dsp_config, 5, 1) > 24) + pll->ct.fifo_size = 32; + else + pll->ct.fifo_size = 24; + } +#endif + /* Exit if the user does not want us to tamper with the clock + rates of her chip. */ + if (par->mclk_per == 0) { + u8 mclk_fb_div, pll_ext_cntl; + pll->ct.pll_ref_div = aty_ld_pll_ct(PLL_REF_DIV, par); + pll_ext_cntl = aty_ld_pll_ct(PLL_EXT_CNTL, par); + pll->ct.xclk_post_div_real = postdividers[pll_ext_cntl & 0x07]; + mclk_fb_div = aty_ld_pll_ct(MCLK_FB_DIV, par); + if (pll_ext_cntl & PLL_MFB_TIMES_4_2B) + mclk_fb_div <<= 1; + pll->ct.mclk_fb_div = mclk_fb_div; + return 0; + } + + pll->ct.pll_ref_div = par->pll_per * 2 * 255 / par->ref_clk_per; + + /* FIXME: use the VTB/GTB /3 post divider if it's better suited */ + q = par->ref_clk_per * pll->ct.pll_ref_div * 8 / + (pll->ct.mclk_fb_mult * par->xclk_per); + + if (q < 16*8 || q > 255*8) { + printk(KERN_CRIT "atxfb: xclk out of range\n"); + return -EINVAL; + } else { + xpost_div = (q < 128*8); + xpost_div += (q < 64*8); + xpost_div += (q < 32*8); + } + pll->ct.xclk_post_div_real = postdividers[xpost_div]; + pll->ct.mclk_fb_div = q * pll->ct.xclk_post_div_real / 8; + +#ifdef CONFIG_PPC + if (machine_is(powermac)) { + /* Override PLL_EXT_CNTL & 0x07. */ + pll->ct.xclk_post_div = xpost_div; + pll->ct.xclk_ref_div = 1; + } +#endif + +#ifdef DEBUG + pllmclk = (1000000 * pll->ct.mclk_fb_mult * pll->ct.mclk_fb_div) / + (par->ref_clk_per * pll->ct.pll_ref_div); + printk("atyfb(%s): pllmclk=%d MHz, xclk=%d MHz\n", + __func__, pllmclk, pllmclk / pll->ct.xclk_post_div_real); +#endif + + if (M64_HAS(SDRAM_MAGIC_PLL) && (par->ram_type >= SDRAM)) + pll->ct.pll_gen_cntl = OSC_EN; + else + pll->ct.pll_gen_cntl = OSC_EN | DLL_PWDN /* | FORCE_DCLK_TRI_STATE */; + + if (M64_HAS(MAGIC_POSTDIV)) + pll->ct.pll_ext_cntl = 0; + else + pll->ct.pll_ext_cntl = xpost_div; + + if (pll->ct.mclk_fb_mult == 4) + pll->ct.pll_ext_cntl |= PLL_MFB_TIMES_4_2B; + + if (par->mclk_per == par->xclk_per) { + pll->ct.pll_gen_cntl |= (xpost_div << 4); /* mclk == xclk */ + } else { + /* + * The chip clock is not equal to the memory clock. + * Therefore we will use sclk to clock the chip. + */ + pll->ct.pll_gen_cntl |= (6 << 4); /* mclk == sclk */ + + q = par->ref_clk_per * pll->ct.pll_ref_div * 4 / par->mclk_per; + if (q < 16*8 || q > 255*8) { + printk(KERN_CRIT "atyfb: mclk out of range\n"); + return -EINVAL; + } else { + mpost_div = (q < 128*8); + mpost_div += (q < 64*8); + mpost_div += (q < 32*8); + } + sclk_post_div_real = postdividers[mpost_div]; + pll->ct.sclk_fb_div = q * sclk_post_div_real / 8; + pll->ct.spll_cntl2 = mpost_div << 4; +#ifdef DEBUG + pllsclk = (1000000 * 2 * pll->ct.sclk_fb_div) / + (par->ref_clk_per * pll->ct.pll_ref_div); + printk("atyfb(%s): use sclk, pllsclk=%d MHz, sclk=mclk=%d MHz\n", + __func__, pllsclk, pllsclk / sclk_post_div_real); +#endif + } + + /* Disable the extra precision pixel clock controls since we do not use them. */ + pll->ct.ext_vpll_cntl = aty_ld_pll_ct(EXT_VPLL_CNTL, par); + pll->ct.ext_vpll_cntl &= ~(EXT_VPLL_EN | EXT_VPLL_VGA_EN | EXT_VPLL_INSYNC); + + return 0; +} + +static void aty_resume_pll_ct(const struct fb_info *info, + union aty_pll *pll) +{ + struct atyfb_par *par = info->par; + + if (par->mclk_per != par->xclk_per) { + /* + * This disables the sclk, crashes the computer as reported: + * aty_st_pll_ct(SPLL_CNTL2, 3, info); + * + * So it seems the sclk must be enabled before it is used; + * so PLL_GEN_CNTL must be programmed *after* the sclk. + */ + aty_st_pll_ct(SCLK_FB_DIV, pll->ct.sclk_fb_div, par); + aty_st_pll_ct(SPLL_CNTL2, pll->ct.spll_cntl2, par); + /* + * SCLK has been started. Wait for the PLL to lock. 5 ms + * should be enough according to mach64 programmer's guide. + */ + mdelay(5); + } + + aty_st_pll_ct(PLL_REF_DIV, pll->ct.pll_ref_div, par); + aty_st_pll_ct(PLL_GEN_CNTL, pll->ct.pll_gen_cntl, par); + aty_st_pll_ct(MCLK_FB_DIV, pll->ct.mclk_fb_div, par); + aty_st_pll_ct(PLL_EXT_CNTL, pll->ct.pll_ext_cntl, par); + aty_st_pll_ct(EXT_VPLL_CNTL, pll->ct.ext_vpll_cntl, par); +} + +static int dummy(void) +{ + return 0; +} + +const struct aty_dac_ops aty_dac_ct = { + .set_dac = (void *) dummy, +}; + +const struct aty_pll_ops aty_pll_ct = { + .var_to_pll = aty_var_to_pll_ct, + .pll_to_var = aty_pll_to_var_ct, + .set_pll = aty_set_pll_ct, + .get_pll = aty_get_pll_ct, + .init_pll = aty_init_pll_ct, + .resume_pll = aty_resume_pll_ct, +}; diff --git a/drivers/video/fbdev/aty/mach64_cursor.c b/drivers/video/fbdev/aty/mach64_cursor.c new file mode 100644 index 000000000000..0fe02e22d9a4 --- /dev/null +++ b/drivers/video/fbdev/aty/mach64_cursor.c @@ -0,0 +1,225 @@ +/* + * ATI Mach64 CT/VT/GT/LT Cursor Support + */ + +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/string.h> +#include "../fb_draw.h" + +#include <asm/io.h> + +#ifdef __sparc__ +#include <asm/fbio.h> +#endif + +#include <video/mach64.h> +#include "atyfb.h" + +/* + * The hardware cursor definition requires 2 bits per pixel. The + * Cursor size reguardless of the visible cursor size is 64 pixels + * by 64 lines. The total memory required to define the cursor is + * 16 bytes / line for 64 lines or 1024 bytes of data. The data + * must be in a contigiuos format. The 2 bit cursor code values are + * as follows: + * + * 00 - pixel colour = CURSOR_CLR_0 + * 01 - pixel colour = CURSOR_CLR_1 + * 10 - pixel colour = transparent (current display pixel) + * 11 - pixel colour = 1's complement of current display pixel + * + * Cursor Offset 64 pixels Actual Displayed Area + * \_________________________/ + * | | | | + * |<--------------->| | | + * | CURS_HORZ_OFFSET| | | + * | |_______| | 64 Lines + * | ^ | | + * | | | | + * | CURS_VERT_OFFSET| | + * | | | | + * |____________________|____| | + * + * + * The Screen position of the top left corner of the displayed + * cursor is specificed by CURS_HORZ_VERT_POSN. Care must be taken + * when the cursor hot spot is not the top left corner and the + * physical cursor position becomes negative. It will be be displayed + * if either the horizontal or vertical cursor position is negative + * + * If x becomes negative the cursor manager must adjust the CURS_HORZ_OFFSET + * to a larger number and saturate CUR_HORZ_POSN to zero. + * + * if Y becomes negative, CUR_VERT_OFFSET must be adjusted to a larger number, + * CUR_OFFSET must be adjusted to a point to the appropriate line in the cursor + * definitation and CUR_VERT_POSN must be saturated to zero. + */ + + /* + * Hardware Cursor support. + */ +static const u8 cursor_bits_lookup[16] = { + 0x00, 0x40, 0x10, 0x50, 0x04, 0x44, 0x14, 0x54, + 0x01, 0x41, 0x11, 0x51, 0x05, 0x45, 0x15, 0x55 +}; + +static int atyfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u16 xoff, yoff; + int x, y, h; + +#ifdef __sparc__ + if (par->mmaped) + return -EPERM; +#endif + if (par->asleep) + return -EPERM; + + wait_for_fifo(1, par); + if (cursor->enable) + aty_st_le32(GEN_TEST_CNTL, aty_ld_le32(GEN_TEST_CNTL, par) + | HWCURSOR_ENABLE, par); + else + aty_st_le32(GEN_TEST_CNTL, aty_ld_le32(GEN_TEST_CNTL, par) + & ~HWCURSOR_ENABLE, par); + + /* set position */ + if (cursor->set & FB_CUR_SETPOS) { + x = cursor->image.dx - cursor->hot.x - info->var.xoffset; + if (x < 0) { + xoff = -x; + x = 0; + } else { + xoff = 0; + } + + y = cursor->image.dy - cursor->hot.y - info->var.yoffset; + if (y < 0) { + yoff = -y; + y = 0; + } else { + yoff = 0; + } + + h = cursor->image.height; + + /* + * In doublescan mode, the cursor location + * and heigh also needs to be doubled. + */ + if (par->crtc.gen_cntl & CRTC_DBL_SCAN_EN) { + y<<=1; + h<<=1; + } + wait_for_fifo(3, par); + aty_st_le32(CUR_OFFSET, (info->fix.smem_len >> 3) + (yoff << 1), par); + aty_st_le32(CUR_HORZ_VERT_OFF, + ((u32) (64 - h + yoff) << 16) | xoff, par); + aty_st_le32(CUR_HORZ_VERT_POSN, ((u32) y << 16) | x, par); + } + + /* Set color map */ + if (cursor->set & FB_CUR_SETCMAP) { + u32 fg_idx, bg_idx, fg, bg; + + fg_idx = cursor->image.fg_color; + bg_idx = cursor->image.bg_color; + + fg = ((info->cmap.red[fg_idx] & 0xff) << 24) | + ((info->cmap.green[fg_idx] & 0xff) << 16) | + ((info->cmap.blue[fg_idx] & 0xff) << 8) | 0xff; + + bg = ((info->cmap.red[bg_idx] & 0xff) << 24) | + ((info->cmap.green[bg_idx] & 0xff) << 16) | + ((info->cmap.blue[bg_idx] & 0xff) << 8); + + wait_for_fifo(2, par); + aty_st_le32(CUR_CLR0, bg, par); + aty_st_le32(CUR_CLR1, fg, par); + } + + if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETIMAGE)) { + u8 *src = (u8 *)cursor->image.data; + u8 *msk = (u8 *)cursor->mask; + u8 __iomem *dst = (u8 __iomem *)info->sprite.addr; + unsigned int width = (cursor->image.width + 7) >> 3; + unsigned int height = cursor->image.height; + unsigned int align = info->sprite.scan_align; + + unsigned int i, j, offset; + u8 m, b; + + // Clear cursor image with 1010101010... + fb_memset(dst, 0xaa, 1024); + + offset = align - width*2; + + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { + u16 l = 0xaaaa; + b = *src++; + m = *msk++; + switch (cursor->rop) { + case ROP_XOR: + // Upper 4 bits of mask data + l = cursor_bits_lookup[(b ^ m) >> 4] | + // Lower 4 bits of mask + (cursor_bits_lookup[(b ^ m) & 0x0f] << 8); + break; + case ROP_COPY: + // Upper 4 bits of mask data + l = cursor_bits_lookup[(b & m) >> 4] | + // Lower 4 bits of mask + (cursor_bits_lookup[(b & m) & 0x0f] << 8); + break; + } + /* + * If cursor size is not a multiple of 8 characters + * we must pad it with transparent pattern (0xaaaa). + */ + if ((j + 1) * 8 > cursor->image.width) { + l = comp(l, 0xaaaa, + (1 << ((cursor->image.width & 7) * 2)) - 1); + } + fb_writeb(l & 0xff, dst++); + fb_writeb(l >> 8, dst++); + } + dst += offset; + } + } + + return 0; +} + +int aty_init_cursor(struct fb_info *info) +{ + unsigned long addr; + + info->fix.smem_len -= PAGE_SIZE; + +#ifdef __sparc__ + addr = (unsigned long) info->screen_base - 0x800000 + info->fix.smem_len; + info->sprite.addr = (u8 *) addr; +#else +#ifdef __BIG_ENDIAN + addr = info->fix.smem_start - 0x800000 + info->fix.smem_len; + info->sprite.addr = (u8 *) ioremap(addr, 1024); +#else + addr = (unsigned long) info->screen_base + info->fix.smem_len; + info->sprite.addr = (u8 *) addr; +#endif +#endif + if (!info->sprite.addr) + return -ENXIO; + info->sprite.size = PAGE_SIZE; + info->sprite.scan_align = 16; /* Scratch pad 64 bytes wide */ + info->sprite.buf_align = 16; /* and 64 lines tall. */ + info->sprite.flags = FB_PIXMAP_IO; + + info->fbops->fb_cursor = atyfb_cursor; + + return 0; +} + diff --git a/drivers/video/fbdev/aty/mach64_gx.c b/drivers/video/fbdev/aty/mach64_gx.c new file mode 100644 index 000000000000..10c988aef58e --- /dev/null +++ b/drivers/video/fbdev/aty/mach64_gx.c @@ -0,0 +1,910 @@ + +/* + * ATI Mach64 GX Support + */ + +#include <linux/delay.h> +#include <linux/fb.h> + +#include <asm/io.h> + +#include <video/mach64.h> +#include "atyfb.h" + +/* Definitions for the ICS 2595 == ATI 18818_1 Clockchip */ + +#define REF_FREQ_2595 1432 /* 14.33 MHz (exact 14.31818) */ +#define REF_DIV_2595 46 /* really 43 on ICS 2595 !!! */ + /* ohne Prescaler */ +#define MAX_FREQ_2595 15938 /* 159.38 MHz (really 170.486) */ +#define MIN_FREQ_2595 8000 /* 80.00 MHz ( 85.565) */ + /* mit Prescaler 2, 4, 8 */ +#define ABS_MIN_FREQ_2595 1000 /* 10.00 MHz (really 10.697) */ +#define N_ADJ_2595 257 + +#define STOP_BITS_2595 0x1800 + + +#define MIN_N_408 2 + +#define MIN_N_1703 6 + +#define MIN_M 2 +#define MAX_M 30 +#define MIN_N 35 +#define MAX_N 255-8 + + + /* + * Support Functions + */ + +static void aty_dac_waste4(const struct atyfb_par *par) +{ + (void) aty_ld_8(DAC_REGS, par); + + (void) aty_ld_8(DAC_REGS + 2, par); + (void) aty_ld_8(DAC_REGS + 2, par); + (void) aty_ld_8(DAC_REGS + 2, par); + (void) aty_ld_8(DAC_REGS + 2, par); +} + +static void aty_StrobeClock(const struct atyfb_par *par) +{ + u8 tmp; + + udelay(26); + + tmp = aty_ld_8(CLOCK_CNTL, par); + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, tmp | CLOCK_STROBE, par); + return; +} + + + /* + * IBM RGB514 DAC and Clock Chip + */ + +static void aty_st_514(int offset, u8 val, const struct atyfb_par *par) +{ + aty_st_8(DAC_CNTL, 1, par); + /* right addr byte */ + aty_st_8(DAC_W_INDEX, offset & 0xff, par); + /* left addr byte */ + aty_st_8(DAC_DATA, (offset >> 8) & 0xff, par); + aty_st_8(DAC_MASK, val, par); + aty_st_8(DAC_CNTL, 0, par); +} + +static int aty_set_dac_514(const struct fb_info *info, + const union aty_pll *pll, u32 bpp, u32 accel) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + static struct { + u8 pixel_dly; + u8 misc2_cntl; + u8 pixel_rep; + u8 pixel_cntl_index; + u8 pixel_cntl_v1; + } tab[3] = { + { + 0, 0x41, 0x03, 0x71, 0x45}, /* 8 bpp */ + { + 0, 0x45, 0x04, 0x0c, 0x01}, /* 555 */ + { + 0, 0x45, 0x06, 0x0e, 0x00}, /* XRGB */ + }; + int i; + + switch (bpp) { + case 8: + default: + i = 0; + break; + case 16: + i = 1; + break; + case 32: + i = 2; + break; + } + aty_st_514(0x90, 0x00, par); /* VRAM Mask Low */ + aty_st_514(0x04, tab[i].pixel_dly, par); /* Horizontal Sync Control */ + aty_st_514(0x05, 0x00, par); /* Power Management */ + aty_st_514(0x02, 0x01, par); /* Misc Clock Control */ + aty_st_514(0x71, tab[i].misc2_cntl, par); /* Misc Control 2 */ + aty_st_514(0x0a, tab[i].pixel_rep, par); /* Pixel Format */ + aty_st_514(tab[i].pixel_cntl_index, tab[i].pixel_cntl_v1, par); + /* Misc Control 2 / 16 BPP Control / 32 BPP Control */ + return 0; +} + +static int aty_var_to_pll_514(const struct fb_info *info, u32 vclk_per, + u32 bpp, union aty_pll *pll) +{ + /* + * FIXME: use real calculations instead of using fixed values from the old + * driver + */ + static struct { + u32 limit; /* pixlock rounding limit (arbitrary) */ + u8 m; /* (df<<6) | vco_div_count */ + u8 n; /* ref_div_count */ + } RGB514_clocks[7] = { + { + 8000, (3 << 6) | 20, 9}, /* 7395 ps / 135.2273 MHz */ + { + 10000, (1 << 6) | 19, 3}, /* 9977 ps / 100.2273 MHz */ + { + 13000, (1 << 6) | 2, 3}, /* 12509 ps / 79.9432 MHz */ + { + 14000, (2 << 6) | 8, 7}, /* 13394 ps / 74.6591 MHz */ + { + 16000, (1 << 6) | 44, 6}, /* 15378 ps / 65.0284 MHz */ + { + 25000, (1 << 6) | 15, 5}, /* 17460 ps / 57.2727 MHz */ + { + 50000, (0 << 6) | 53, 7}, /* 33145 ps / 30.1705 MHz */ + }; + int i; + + for (i = 0; i < ARRAY_SIZE(RGB514_clocks); i++) + if (vclk_per <= RGB514_clocks[i].limit) { + pll->ibm514.m = RGB514_clocks[i].m; + pll->ibm514.n = RGB514_clocks[i].n; + return 0; + } + return -EINVAL; +} + +static u32 aty_pll_514_to_var(const struct fb_info *info, + const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u8 df, vco_div_count, ref_div_count; + + df = pll->ibm514.m >> 6; + vco_div_count = pll->ibm514.m & 0x3f; + ref_div_count = pll->ibm514.n; + + return ((par->ref_clk_per * ref_div_count) << (3 - df))/ + (vco_div_count + 65); +} + +static void aty_set_pll_514(const struct fb_info *info, + const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + + aty_st_514(0x06, 0x02, par); /* DAC Operation */ + aty_st_514(0x10, 0x01, par); /* PLL Control 1 */ + aty_st_514(0x70, 0x01, par); /* Misc Control 1 */ + aty_st_514(0x8f, 0x1f, par); /* PLL Ref. Divider Input */ + aty_st_514(0x03, 0x00, par); /* Sync Control */ + aty_st_514(0x05, 0x00, par); /* Power Management */ + aty_st_514(0x20, pll->ibm514.m, par); /* F0 / M0 */ + aty_st_514(0x21, pll->ibm514.n, par); /* F1 / N0 */ +} + +const struct aty_dac_ops aty_dac_ibm514 = { + .set_dac = aty_set_dac_514, +}; + +const struct aty_pll_ops aty_pll_ibm514 = { + .var_to_pll = aty_var_to_pll_514, + .pll_to_var = aty_pll_514_to_var, + .set_pll = aty_set_pll_514, +}; + + + /* + * ATI 68860-B DAC + */ + +static int aty_set_dac_ATI68860_B(const struct fb_info *info, + const union aty_pll *pll, u32 bpp, + u32 accel) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 gModeReg, devSetupRegA, temp, mask; + + gModeReg = 0; + devSetupRegA = 0; + + switch (bpp) { + case 8: + gModeReg = 0x83; + devSetupRegA = + 0x60 | 0x00 /*(info->mach64DAC8Bit ? 0x00 : 0x01) */ ; + break; + case 15: + gModeReg = 0xA0; + devSetupRegA = 0x60; + break; + case 16: + gModeReg = 0xA1; + devSetupRegA = 0x60; + break; + case 24: + gModeReg = 0xC0; + devSetupRegA = 0x60; + break; + case 32: + gModeReg = 0xE3; + devSetupRegA = 0x60; + break; + } + + if (!accel) { + gModeReg = 0x80; + devSetupRegA = 0x61; + } + + temp = aty_ld_8(DAC_CNTL, par); + aty_st_8(DAC_CNTL, (temp & ~DAC_EXT_SEL_RS2) | DAC_EXT_SEL_RS3, + par); + + aty_st_8(DAC_REGS + 2, 0x1D, par); + aty_st_8(DAC_REGS + 3, gModeReg, par); + aty_st_8(DAC_REGS, 0x02, par); + + temp = aty_ld_8(DAC_CNTL, par); + aty_st_8(DAC_CNTL, temp | DAC_EXT_SEL_RS2 | DAC_EXT_SEL_RS3, par); + + if (info->fix.smem_len < ONE_MB) + mask = 0x04; + else if (info->fix.smem_len == ONE_MB) + mask = 0x08; + else + mask = 0x0C; + + /* The following assumes that the BIOS has correctly set R7 of the + * Device Setup Register A at boot time. + */ +#define A860_DELAY_L 0x80 + + temp = aty_ld_8(DAC_REGS, par); + aty_st_8(DAC_REGS, (devSetupRegA | mask) | (temp & A860_DELAY_L), + par); + temp = aty_ld_8(DAC_CNTL, par); + aty_st_8(DAC_CNTL, (temp & ~(DAC_EXT_SEL_RS2 | DAC_EXT_SEL_RS3)), + par); + + aty_st_le32(BUS_CNTL, 0x890e20f1, par); + aty_st_le32(DAC_CNTL, 0x47052100, par); + return 0; +} + +const struct aty_dac_ops aty_dac_ati68860b = { + .set_dac = aty_set_dac_ATI68860_B, +}; + + + /* + * AT&T 21C498 DAC + */ + +static int aty_set_dac_ATT21C498(const struct fb_info *info, + const union aty_pll *pll, u32 bpp, + u32 accel) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 dotClock; + int muxmode = 0; + int DACMask = 0; + + dotClock = 100000000 / pll->ics2595.period_in_ps; + + switch (bpp) { + case 8: + if (dotClock > 8000) { + DACMask = 0x24; + muxmode = 1; + } else + DACMask = 0x04; + break; + case 15: + DACMask = 0x16; + break; + case 16: + DACMask = 0x36; + break; + case 24: + DACMask = 0xE6; + break; + case 32: + DACMask = 0xE6; + break; + } + + if (1 /* info->mach64DAC8Bit */ ) + DACMask |= 0x02; + + aty_dac_waste4(par); + aty_st_8(DAC_REGS + 2, DACMask, par); + + aty_st_le32(BUS_CNTL, 0x890e20f1, par); + aty_st_le32(DAC_CNTL, 0x00072000, par); + return muxmode; +} + +const struct aty_dac_ops aty_dac_att21c498 = { + .set_dac = aty_set_dac_ATT21C498, +}; + + + /* + * ATI 18818 / ICS 2595 Clock Chip + */ + +static int aty_var_to_pll_18818(const struct fb_info *info, u32 vclk_per, + u32 bpp, union aty_pll *pll) +{ + u32 MHz100; /* in 0.01 MHz */ + u32 program_bits; + u32 post_divider; + + /* Calculate the programming word */ + MHz100 = 100000000 / vclk_per; + + program_bits = -1; + post_divider = 1; + + if (MHz100 > MAX_FREQ_2595) { + MHz100 = MAX_FREQ_2595; + return -EINVAL; + } else if (MHz100 < ABS_MIN_FREQ_2595) { + program_bits = 0; /* MHz100 = 257 */ + return -EINVAL; + } else { + while (MHz100 < MIN_FREQ_2595) { + MHz100 *= 2; + post_divider *= 2; + } + } + MHz100 *= 1000; + MHz100 = (REF_DIV_2595 * MHz100) / REF_FREQ_2595; + + MHz100 += 500; /* + 0.5 round */ + MHz100 /= 1000; + + if (program_bits == -1) { + program_bits = MHz100 - N_ADJ_2595; + switch (post_divider) { + case 1: + program_bits |= 0x0600; + break; + case 2: + program_bits |= 0x0400; + break; + case 4: + program_bits |= 0x0200; + break; + case 8: + default: + break; + } + } + + program_bits |= STOP_BITS_2595; + + pll->ics2595.program_bits = program_bits; + pll->ics2595.locationAddr = 0; + pll->ics2595.post_divider = post_divider; + pll->ics2595.period_in_ps = vclk_per; + + return 0; +} + +static u32 aty_pll_18818_to_var(const struct fb_info *info, + const union aty_pll *pll) +{ + return (pll->ics2595.period_in_ps); /* default for now */ +} + +static void aty_ICS2595_put1bit(u8 data, const struct atyfb_par *par) +{ + u8 tmp; + + data &= 0x01; + tmp = aty_ld_8(CLOCK_CNTL, par); + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, + (tmp & ~0x04) | (data << 2), par); + + tmp = aty_ld_8(CLOCK_CNTL, par); + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, (tmp & ~0x08) | (0 << 3), + par); + + aty_StrobeClock(par); + + tmp = aty_ld_8(CLOCK_CNTL, par); + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, (tmp & ~0x08) | (1 << 3), + par); + + aty_StrobeClock(par); + return; +} + +static void aty_set_pll18818(const struct fb_info *info, + const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 program_bits; + u32 locationAddr; + + u32 i; + + u8 old_clock_cntl; + u8 old_crtc_ext_disp; + + old_clock_cntl = aty_ld_8(CLOCK_CNTL, par); + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, 0, par); + + old_crtc_ext_disp = aty_ld_8(CRTC_GEN_CNTL + 3, par); + aty_st_8(CRTC_GEN_CNTL + 3, + old_crtc_ext_disp | (CRTC_EXT_DISP_EN >> 24), par); + + mdelay(15); /* delay for 50 (15) ms */ + + program_bits = pll->ics2595.program_bits; + locationAddr = pll->ics2595.locationAddr; + + /* Program the clock chip */ + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, 0, par); /* Strobe = 0 */ + aty_StrobeClock(par); + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, 1, par); /* Strobe = 0 */ + aty_StrobeClock(par); + + aty_ICS2595_put1bit(1, par); /* Send start bits */ + aty_ICS2595_put1bit(0, par); /* Start bit */ + aty_ICS2595_put1bit(0, par); /* Read / ~Write */ + + for (i = 0; i < 5; i++) { /* Location 0..4 */ + aty_ICS2595_put1bit(locationAddr & 1, par); + locationAddr >>= 1; + } + + for (i = 0; i < 8 + 1 + 2 + 2; i++) { + aty_ICS2595_put1bit(program_bits & 1, par); + program_bits >>= 1; + } + + mdelay(1); /* delay for 1 ms */ + + (void) aty_ld_8(DAC_REGS, par); /* Clear DAC Counter */ + aty_st_8(CRTC_GEN_CNTL + 3, old_crtc_ext_disp, par); + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, + old_clock_cntl | CLOCK_STROBE, par); + + mdelay(50); /* delay for 50 (15) ms */ + aty_st_8(CLOCK_CNTL + par->clk_wr_offset, + ((pll->ics2595.locationAddr & 0x0F) | CLOCK_STROBE), par); + return; +} + +const struct aty_pll_ops aty_pll_ati18818_1 = { + .var_to_pll = aty_var_to_pll_18818, + .pll_to_var = aty_pll_18818_to_var, + .set_pll = aty_set_pll18818, +}; + + + /* + * STG 1703 Clock Chip + */ + +static int aty_var_to_pll_1703(const struct fb_info *info, u32 vclk_per, + u32 bpp, union aty_pll *pll) +{ + u32 mhz100; /* in 0.01 MHz */ + u32 program_bits; + /* u32 post_divider; */ + u32 mach64MinFreq, mach64MaxFreq, mach64RefFreq; + u32 temp, tempB; + u16 remainder, preRemainder; + short divider = 0, tempA; + + /* Calculate the programming word */ + mhz100 = 100000000 / vclk_per; + mach64MinFreq = MIN_FREQ_2595; + mach64MaxFreq = MAX_FREQ_2595; + mach64RefFreq = REF_FREQ_2595; /* 14.32 MHz */ + + /* Calculate program word */ + if (mhz100 == 0) + program_bits = 0xE0; + else { + if (mhz100 < mach64MinFreq) + mhz100 = mach64MinFreq; + if (mhz100 > mach64MaxFreq) + mhz100 = mach64MaxFreq; + + divider = 0; + while (mhz100 < (mach64MinFreq << 3)) { + mhz100 <<= 1; + divider += 0x20; + } + + temp = (unsigned int) (mhz100); + temp = (unsigned int) (temp * (MIN_N_1703 + 2)); + temp -= (short) (mach64RefFreq << 1); + + tempA = MIN_N_1703; + preRemainder = 0xffff; + + do { + tempB = temp; + remainder = tempB % mach64RefFreq; + tempB = tempB / mach64RefFreq; + + if ((tempB & 0xffff) <= 127 + && (remainder <= preRemainder)) { + preRemainder = remainder; + divider &= ~0x1f; + divider |= tempA; + divider = + (divider & 0x00ff) + + ((tempB & 0xff) << 8); + } + + temp += mhz100; + tempA++; + } while (tempA <= (MIN_N_1703 << 1)); + + program_bits = divider; + } + + pll->ics2595.program_bits = program_bits; + pll->ics2595.locationAddr = 0; + pll->ics2595.post_divider = divider; /* fuer nix */ + pll->ics2595.period_in_ps = vclk_per; + + return 0; +} + +static u32 aty_pll_1703_to_var(const struct fb_info *info, + const union aty_pll *pll) +{ + return (pll->ics2595.period_in_ps); /* default for now */ +} + +static void aty_set_pll_1703(const struct fb_info *info, + const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 program_bits; + u32 locationAddr; + + char old_crtc_ext_disp; + + old_crtc_ext_disp = aty_ld_8(CRTC_GEN_CNTL + 3, par); + aty_st_8(CRTC_GEN_CNTL + 3, + old_crtc_ext_disp | (CRTC_EXT_DISP_EN >> 24), par); + + program_bits = pll->ics2595.program_bits; + locationAddr = pll->ics2595.locationAddr; + + /* Program clock */ + aty_dac_waste4(par); + + (void) aty_ld_8(DAC_REGS + 2, par); + aty_st_8(DAC_REGS + 2, (locationAddr << 1) + 0x20, par); + aty_st_8(DAC_REGS + 2, 0, par); + aty_st_8(DAC_REGS + 2, (program_bits & 0xFF00) >> 8, par); + aty_st_8(DAC_REGS + 2, (program_bits & 0xFF), par); + + (void) aty_ld_8(DAC_REGS, par); /* Clear DAC Counter */ + aty_st_8(CRTC_GEN_CNTL + 3, old_crtc_ext_disp, par); + return; +} + +const struct aty_pll_ops aty_pll_stg1703 = { + .var_to_pll = aty_var_to_pll_1703, + .pll_to_var = aty_pll_1703_to_var, + .set_pll = aty_set_pll_1703, +}; + + + /* + * Chrontel 8398 Clock Chip + */ + +static int aty_var_to_pll_8398(const struct fb_info *info, u32 vclk_per, + u32 bpp, union aty_pll *pll) +{ + u32 tempA, tempB, fOut, longMHz100, diff, preDiff; + + u32 mhz100; /* in 0.01 MHz */ + u32 program_bits; + /* u32 post_divider; */ + u32 mach64MinFreq, mach64MaxFreq, mach64RefFreq; + u16 m, n, k = 0, save_m, save_n, twoToKth; + + /* Calculate the programming word */ + mhz100 = 100000000 / vclk_per; + mach64MinFreq = MIN_FREQ_2595; + mach64MaxFreq = MAX_FREQ_2595; + mach64RefFreq = REF_FREQ_2595; /* 14.32 MHz */ + + save_m = 0; + save_n = 0; + + /* Calculate program word */ + if (mhz100 == 0) + program_bits = 0xE0; + else { + if (mhz100 < mach64MinFreq) + mhz100 = mach64MinFreq; + if (mhz100 > mach64MaxFreq) + mhz100 = mach64MaxFreq; + + longMHz100 = mhz100 * 256 / 100; /* 8 bit scale this */ + + while (mhz100 < (mach64MinFreq << 3)) { + mhz100 <<= 1; + k++; + } + + twoToKth = 1 << k; + diff = 0; + preDiff = 0xFFFFFFFF; + + for (m = MIN_M; m <= MAX_M; m++) { + for (n = MIN_N; n <= MAX_N; n++) { + tempA = 938356; /* 14.31818 * 65536 */ + tempA *= (n + 8); /* 43..256 */ + tempB = twoToKth * 256; + tempB *= (m + 2); /* 4..32 */ + fOut = tempA / tempB; /* 8 bit scale */ + + if (longMHz100 > fOut) + diff = longMHz100 - fOut; + else + diff = fOut - longMHz100; + + if (diff < preDiff) { + save_m = m; + save_n = n; + preDiff = diff; + } + } + } + + program_bits = (k << 6) + (save_m) + (save_n << 8); + } + + pll->ics2595.program_bits = program_bits; + pll->ics2595.locationAddr = 0; + pll->ics2595.post_divider = 0; + pll->ics2595.period_in_ps = vclk_per; + + return 0; +} + +static u32 aty_pll_8398_to_var(const struct fb_info *info, + const union aty_pll *pll) +{ + return (pll->ics2595.period_in_ps); /* default for now */ +} + +static void aty_set_pll_8398(const struct fb_info *info, + const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 program_bits; + u32 locationAddr; + + char old_crtc_ext_disp; + char tmp; + + old_crtc_ext_disp = aty_ld_8(CRTC_GEN_CNTL + 3, par); + aty_st_8(CRTC_GEN_CNTL + 3, + old_crtc_ext_disp | (CRTC_EXT_DISP_EN >> 24), par); + + program_bits = pll->ics2595.program_bits; + locationAddr = pll->ics2595.locationAddr; + + /* Program clock */ + tmp = aty_ld_8(DAC_CNTL, par); + aty_st_8(DAC_CNTL, tmp | DAC_EXT_SEL_RS2 | DAC_EXT_SEL_RS3, par); + + aty_st_8(DAC_REGS, locationAddr, par); + aty_st_8(DAC_REGS + 1, (program_bits & 0xff00) >> 8, par); + aty_st_8(DAC_REGS + 1, (program_bits & 0xff), par); + + tmp = aty_ld_8(DAC_CNTL, par); + aty_st_8(DAC_CNTL, (tmp & ~DAC_EXT_SEL_RS2) | DAC_EXT_SEL_RS3, + par); + + (void) aty_ld_8(DAC_REGS, par); /* Clear DAC Counter */ + aty_st_8(CRTC_GEN_CNTL + 3, old_crtc_ext_disp, par); + + return; +} + +const struct aty_pll_ops aty_pll_ch8398 = { + .var_to_pll = aty_var_to_pll_8398, + .pll_to_var = aty_pll_8398_to_var, + .set_pll = aty_set_pll_8398, +}; + + + /* + * AT&T 20C408 Clock Chip + */ + +static int aty_var_to_pll_408(const struct fb_info *info, u32 vclk_per, + u32 bpp, union aty_pll *pll) +{ + u32 mhz100; /* in 0.01 MHz */ + u32 program_bits; + /* u32 post_divider; */ + u32 mach64MinFreq, mach64MaxFreq, mach64RefFreq; + u32 temp, tempB; + u16 remainder, preRemainder; + short divider = 0, tempA; + + /* Calculate the programming word */ + mhz100 = 100000000 / vclk_per; + mach64MinFreq = MIN_FREQ_2595; + mach64MaxFreq = MAX_FREQ_2595; + mach64RefFreq = REF_FREQ_2595; /* 14.32 MHz */ + + /* Calculate program word */ + if (mhz100 == 0) + program_bits = 0xFF; + else { + if (mhz100 < mach64MinFreq) + mhz100 = mach64MinFreq; + if (mhz100 > mach64MaxFreq) + mhz100 = mach64MaxFreq; + + while (mhz100 < (mach64MinFreq << 3)) { + mhz100 <<= 1; + divider += 0x40; + } + + temp = (unsigned int) mhz100; + temp = (unsigned int) (temp * (MIN_N_408 + 2)); + temp -= ((short) (mach64RefFreq << 1)); + + tempA = MIN_N_408; + preRemainder = 0xFFFF; + + do { + tempB = temp; + remainder = tempB % mach64RefFreq; + tempB = tempB / mach64RefFreq; + if (((tempB & 0xFFFF) <= 255) + && (remainder <= preRemainder)) { + preRemainder = remainder; + divider &= ~0x3f; + divider |= tempA; + divider = + (divider & 0x00FF) + + ((tempB & 0xFF) << 8); + } + temp += mhz100; + tempA++; + } while (tempA <= 32); + + program_bits = divider; + } + + pll->ics2595.program_bits = program_bits; + pll->ics2595.locationAddr = 0; + pll->ics2595.post_divider = divider; /* fuer nix */ + pll->ics2595.period_in_ps = vclk_per; + + return 0; +} + +static u32 aty_pll_408_to_var(const struct fb_info *info, + const union aty_pll *pll) +{ + return (pll->ics2595.period_in_ps); /* default for now */ +} + +static void aty_set_pll_408(const struct fb_info *info, + const union aty_pll *pll) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + u32 program_bits; + u32 locationAddr; + + u8 tmpA, tmpB, tmpC; + char old_crtc_ext_disp; + + old_crtc_ext_disp = aty_ld_8(CRTC_GEN_CNTL + 3, par); + aty_st_8(CRTC_GEN_CNTL + 3, + old_crtc_ext_disp | (CRTC_EXT_DISP_EN >> 24), par); + + program_bits = pll->ics2595.program_bits; + locationAddr = pll->ics2595.locationAddr; + + /* Program clock */ + aty_dac_waste4(par); + tmpB = aty_ld_8(DAC_REGS + 2, par) | 1; + aty_dac_waste4(par); + aty_st_8(DAC_REGS + 2, tmpB, par); + + tmpA = tmpB; + tmpC = tmpA; + tmpA |= 8; + tmpB = 1; + + aty_st_8(DAC_REGS, tmpB, par); + aty_st_8(DAC_REGS + 2, tmpA, par); + + udelay(400); /* delay for 400 us */ + + locationAddr = (locationAddr << 2) + 0x40; + tmpB = locationAddr; + tmpA = program_bits >> 8; + + aty_st_8(DAC_REGS, tmpB, par); + aty_st_8(DAC_REGS + 2, tmpA, par); + + tmpB = locationAddr + 1; + tmpA = (u8) program_bits; + + aty_st_8(DAC_REGS, tmpB, par); + aty_st_8(DAC_REGS + 2, tmpA, par); + + tmpB = locationAddr + 2; + tmpA = 0x77; + + aty_st_8(DAC_REGS, tmpB, par); + aty_st_8(DAC_REGS + 2, tmpA, par); + + udelay(400); /* delay for 400 us */ + tmpA = tmpC & (~(1 | 8)); + tmpB = 1; + + aty_st_8(DAC_REGS, tmpB, par); + aty_st_8(DAC_REGS + 2, tmpA, par); + + (void) aty_ld_8(DAC_REGS, par); /* Clear DAC Counter */ + aty_st_8(CRTC_GEN_CNTL + 3, old_crtc_ext_disp, par); + return; +} + +const struct aty_pll_ops aty_pll_att20c408 = { + .var_to_pll = aty_var_to_pll_408, + .pll_to_var = aty_pll_408_to_var, + .set_pll = aty_set_pll_408, +}; + + + /* + * Unsupported DAC and Clock Chip + */ + +static int aty_set_dac_unsupported(const struct fb_info *info, + const union aty_pll *pll, u32 bpp, + u32 accel) +{ + struct atyfb_par *par = (struct atyfb_par *) info->par; + + aty_st_le32(BUS_CNTL, 0x890e20f1, par); + aty_st_le32(DAC_CNTL, 0x47052100, par); + /* new in 2.2.3p1 from Geert. ???????? */ + aty_st_le32(BUS_CNTL, 0x590e10ff, par); + aty_st_le32(DAC_CNTL, 0x47012100, par); + return 0; +} + +static int dummy(void) +{ + return 0; +} + +const struct aty_dac_ops aty_dac_unsupported = { + .set_dac = aty_set_dac_unsupported, +}; + +const struct aty_pll_ops aty_pll_unsupported = { + .var_to_pll = (void *) dummy, + .pll_to_var = (void *) dummy, + .set_pll = (void *) dummy, +}; diff --git a/drivers/video/fbdev/aty/radeon_accel.c b/drivers/video/fbdev/aty/radeon_accel.c new file mode 100644 index 000000000000..a469a3d6edcb --- /dev/null +++ b/drivers/video/fbdev/aty/radeon_accel.c @@ -0,0 +1,328 @@ +#include "radeonfb.h" + +/* the accelerated functions here are patterned after the + * "ACCEL_MMIO" ifdef branches in XFree86 + * --dte + */ + +static void radeon_fixup_offset(struct radeonfb_info *rinfo) +{ + u32 local_base; + + /* *** Ugly workaround *** */ + /* + * On some platforms, the video memory is mapped at 0 in radeon chip space + * (like PPCs) by the firmware. X will always move it up so that it's seen + * by the chip to be at the same address as the PCI BAR. + * That means that when switching back from X, there is a mismatch between + * the offsets programmed into the engine. This means that potentially, + * accel operations done before radeonfb has a chance to re-init the engine + * will have incorrect offsets, and potentially trash system memory ! + * + * The correct fix is for fbcon to never call any accel op before the engine + * has properly been re-initialized (by a call to set_var), but this is a + * complex fix. This workaround in the meantime, called before every accel + * operation, makes sure the offsets are in sync. + */ + + radeon_fifo_wait (1); + local_base = INREG(MC_FB_LOCATION) << 16; + if (local_base == rinfo->fb_local_base) + return; + + rinfo->fb_local_base = local_base; + + radeon_fifo_wait (3); + OUTREG(DEFAULT_PITCH_OFFSET, (rinfo->pitch << 0x16) | + (rinfo->fb_local_base >> 10)); + OUTREG(DST_PITCH_OFFSET, (rinfo->pitch << 0x16) | (rinfo->fb_local_base >> 10)); + OUTREG(SRC_PITCH_OFFSET, (rinfo->pitch << 0x16) | (rinfo->fb_local_base >> 10)); +} + +static void radeonfb_prim_fillrect(struct radeonfb_info *rinfo, + const struct fb_fillrect *region) +{ + radeon_fifo_wait(4); + + OUTREG(DP_GUI_MASTER_CNTL, + rinfo->dp_gui_master_cntl /* contains, like GMC_DST_32BPP */ + | GMC_BRUSH_SOLID_COLOR + | ROP3_P); + if (radeon_get_dstbpp(rinfo->depth) != DST_8BPP) + OUTREG(DP_BRUSH_FRGD_CLR, rinfo->pseudo_palette[region->color]); + else + OUTREG(DP_BRUSH_FRGD_CLR, region->color); + OUTREG(DP_WRITE_MSK, 0xffffffff); + OUTREG(DP_CNTL, (DST_X_LEFT_TO_RIGHT | DST_Y_TOP_TO_BOTTOM)); + + radeon_fifo_wait(2); + OUTREG(DSTCACHE_CTLSTAT, RB2D_DC_FLUSH_ALL); + OUTREG(WAIT_UNTIL, (WAIT_2D_IDLECLEAN | WAIT_DMA_GUI_IDLE)); + + radeon_fifo_wait(2); + OUTREG(DST_Y_X, (region->dy << 16) | region->dx); + OUTREG(DST_WIDTH_HEIGHT, (region->width << 16) | region->height); +} + +void radeonfb_fillrect(struct fb_info *info, const struct fb_fillrect *region) +{ + struct radeonfb_info *rinfo = info->par; + struct fb_fillrect modded; + int vxres, vyres; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_fillrect(info, region); + return; + } + + radeon_fixup_offset(rinfo); + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + memcpy(&modded, region, sizeof(struct fb_fillrect)); + + if(!modded.width || !modded.height || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if(modded.dx + modded.width > vxres) modded.width = vxres - modded.dx; + if(modded.dy + modded.height > vyres) modded.height = vyres - modded.dy; + + radeonfb_prim_fillrect(rinfo, &modded); +} + +static void radeonfb_prim_copyarea(struct radeonfb_info *rinfo, + const struct fb_copyarea *area) +{ + int xdir, ydir; + u32 sx, sy, dx, dy, w, h; + + w = area->width; h = area->height; + dx = area->dx; dy = area->dy; + sx = area->sx; sy = area->sy; + xdir = sx - dx; + ydir = sy - dy; + + if ( xdir < 0 ) { sx += w-1; dx += w-1; } + if ( ydir < 0 ) { sy += h-1; dy += h-1; } + + radeon_fifo_wait(3); + OUTREG(DP_GUI_MASTER_CNTL, + rinfo->dp_gui_master_cntl /* i.e. GMC_DST_32BPP */ + | GMC_BRUSH_NONE + | GMC_SRC_DSTCOLOR + | ROP3_S + | DP_SRC_SOURCE_MEMORY ); + OUTREG(DP_WRITE_MSK, 0xffffffff); + OUTREG(DP_CNTL, (xdir>=0 ? DST_X_LEFT_TO_RIGHT : 0) + | (ydir>=0 ? DST_Y_TOP_TO_BOTTOM : 0)); + + radeon_fifo_wait(2); + OUTREG(DSTCACHE_CTLSTAT, RB2D_DC_FLUSH_ALL); + OUTREG(WAIT_UNTIL, (WAIT_2D_IDLECLEAN | WAIT_DMA_GUI_IDLE)); + + radeon_fifo_wait(3); + OUTREG(SRC_Y_X, (sy << 16) | sx); + OUTREG(DST_Y_X, (dy << 16) | dx); + OUTREG(DST_HEIGHT_WIDTH, (h << 16) | w); +} + + +void radeonfb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct radeonfb_info *rinfo = info->par; + struct fb_copyarea modded; + u32 vxres, vyres; + modded.sx = area->sx; + modded.sy = area->sy; + modded.dx = area->dx; + modded.dy = area->dy; + modded.width = area->width; + modded.height = area->height; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_copyarea(info, area); + return; + } + + radeon_fixup_offset(rinfo); + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if(!modded.width || !modded.height || + modded.sx >= vxres || modded.sy >= vyres || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if(modded.sx + modded.width > vxres) modded.width = vxres - modded.sx; + if(modded.dx + modded.width > vxres) modded.width = vxres - modded.dx; + if(modded.sy + modded.height > vyres) modded.height = vyres - modded.sy; + if(modded.dy + modded.height > vyres) modded.height = vyres - modded.dy; + + radeonfb_prim_copyarea(rinfo, &modded); +} + +void radeonfb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct radeonfb_info *rinfo = info->par; + + if (info->state != FBINFO_STATE_RUNNING) + return; + radeon_engine_idle(); + + cfb_imageblit(info, image); +} + +int radeonfb_sync(struct fb_info *info) +{ + struct radeonfb_info *rinfo = info->par; + + if (info->state != FBINFO_STATE_RUNNING) + return 0; + radeon_engine_idle(); + + return 0; +} + +void radeonfb_engine_reset(struct radeonfb_info *rinfo) +{ + u32 clock_cntl_index, mclk_cntl, rbbm_soft_reset; + u32 host_path_cntl; + + radeon_engine_flush (rinfo); + + clock_cntl_index = INREG(CLOCK_CNTL_INDEX); + mclk_cntl = INPLL(MCLK_CNTL); + + OUTPLL(MCLK_CNTL, (mclk_cntl | + FORCEON_MCLKA | + FORCEON_MCLKB | + FORCEON_YCLKA | + FORCEON_YCLKB | + FORCEON_MC | + FORCEON_AIC)); + + host_path_cntl = INREG(HOST_PATH_CNTL); + rbbm_soft_reset = INREG(RBBM_SOFT_RESET); + + if (IS_R300_VARIANT(rinfo)) { + u32 tmp; + + OUTREG(RBBM_SOFT_RESET, (rbbm_soft_reset | + SOFT_RESET_CP | + SOFT_RESET_HI | + SOFT_RESET_E2)); + INREG(RBBM_SOFT_RESET); + OUTREG(RBBM_SOFT_RESET, 0); + tmp = INREG(RB2D_DSTCACHE_MODE); + OUTREG(RB2D_DSTCACHE_MODE, tmp | (1 << 17)); /* FIXME */ + } else { + OUTREG(RBBM_SOFT_RESET, rbbm_soft_reset | + SOFT_RESET_CP | + SOFT_RESET_HI | + SOFT_RESET_SE | + SOFT_RESET_RE | + SOFT_RESET_PP | + SOFT_RESET_E2 | + SOFT_RESET_RB); + INREG(RBBM_SOFT_RESET); + OUTREG(RBBM_SOFT_RESET, rbbm_soft_reset & (u32) + ~(SOFT_RESET_CP | + SOFT_RESET_HI | + SOFT_RESET_SE | + SOFT_RESET_RE | + SOFT_RESET_PP | + SOFT_RESET_E2 | + SOFT_RESET_RB)); + INREG(RBBM_SOFT_RESET); + } + + OUTREG(HOST_PATH_CNTL, host_path_cntl | HDP_SOFT_RESET); + INREG(HOST_PATH_CNTL); + OUTREG(HOST_PATH_CNTL, host_path_cntl); + + if (!IS_R300_VARIANT(rinfo)) + OUTREG(RBBM_SOFT_RESET, rbbm_soft_reset); + + OUTREG(CLOCK_CNTL_INDEX, clock_cntl_index); + OUTPLL(MCLK_CNTL, mclk_cntl); +} + +void radeonfb_engine_init (struct radeonfb_info *rinfo) +{ + unsigned long temp; + + /* disable 3D engine */ + OUTREG(RB3D_CNTL, 0); + + radeonfb_engine_reset(rinfo); + + radeon_fifo_wait (1); + if (IS_R300_VARIANT(rinfo)) { + OUTREG(RB2D_DSTCACHE_MODE, INREG(RB2D_DSTCACHE_MODE) | + RB2D_DC_AUTOFLUSH_ENABLE | + RB2D_DC_DC_DISABLE_IGNORE_PE); + } else { + /* This needs to be double checked with ATI. Latest X driver + * completely "forgets" to set this register on < r3xx, and + * we used to just write 0 there... I'll keep the 0 and update + * that when we have sorted things out on X side. + */ + OUTREG(RB2D_DSTCACHE_MODE, 0); + } + + radeon_fifo_wait (3); + /* We re-read MC_FB_LOCATION from card as it can have been + * modified by XFree drivers (ouch !) + */ + rinfo->fb_local_base = INREG(MC_FB_LOCATION) << 16; + + OUTREG(DEFAULT_PITCH_OFFSET, (rinfo->pitch << 0x16) | + (rinfo->fb_local_base >> 10)); + OUTREG(DST_PITCH_OFFSET, (rinfo->pitch << 0x16) | (rinfo->fb_local_base >> 10)); + OUTREG(SRC_PITCH_OFFSET, (rinfo->pitch << 0x16) | (rinfo->fb_local_base >> 10)); + + radeon_fifo_wait (1); +#if defined(__BIG_ENDIAN) + OUTREGP(DP_DATATYPE, HOST_BIG_ENDIAN_EN, ~HOST_BIG_ENDIAN_EN); +#else + OUTREGP(DP_DATATYPE, 0, ~HOST_BIG_ENDIAN_EN); +#endif + radeon_fifo_wait (2); + OUTREG(DEFAULT_SC_TOP_LEFT, 0); + OUTREG(DEFAULT_SC_BOTTOM_RIGHT, (DEFAULT_SC_RIGHT_MAX | + DEFAULT_SC_BOTTOM_MAX)); + + temp = radeon_get_dstbpp(rinfo->depth); + rinfo->dp_gui_master_cntl = ((temp << 8) | GMC_CLR_CMP_CNTL_DIS); + + radeon_fifo_wait (1); + OUTREG(DP_GUI_MASTER_CNTL, (rinfo->dp_gui_master_cntl | + GMC_BRUSH_SOLID_COLOR | + GMC_SRC_DATATYPE_COLOR)); + + radeon_fifo_wait (7); + + /* clear line drawing regs */ + OUTREG(DST_LINE_START, 0); + OUTREG(DST_LINE_END, 0); + + /* set brush color regs */ + OUTREG(DP_BRUSH_FRGD_CLR, 0xffffffff); + OUTREG(DP_BRUSH_BKGD_CLR, 0x00000000); + + /* set source color regs */ + OUTREG(DP_SRC_FRGD_CLR, 0xffffffff); + OUTREG(DP_SRC_BKGD_CLR, 0x00000000); + + /* default write mask */ + OUTREG(DP_WRITE_MSK, 0xffffffff); + + radeon_engine_idle (); +} diff --git a/drivers/video/fbdev/aty/radeon_backlight.c b/drivers/video/fbdev/aty/radeon_backlight.c new file mode 100644 index 000000000000..db572df7e1ef --- /dev/null +++ b/drivers/video/fbdev/aty/radeon_backlight.c @@ -0,0 +1,221 @@ +/* + * Backlight code for ATI Radeon based graphic cards + * + * Copyright (c) 2000 Ani Joshi <ajoshi@kernel.crashing.org> + * Copyright (c) 2003 Benjamin Herrenschmidt <benh@kernel.crashing.org> + * Copyright (c) 2006 Michael Hanselmann <linux-kernel@hansmi.ch> + * + * 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. + */ + +#include "radeonfb.h" +#include <linux/backlight.h> +#include <linux/slab.h> + +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif + +#define MAX_RADEON_LEVEL 0xFF + +struct radeon_bl_privdata { + struct radeonfb_info *rinfo; + uint8_t negative; +}; + +static int radeon_bl_get_level_brightness(struct radeon_bl_privdata *pdata, + int level) +{ + int rlevel; + + /* Get and convert the value */ + /* No locking of bl_curve since we read a single value */ + rlevel = pdata->rinfo->info->bl_curve[level] * + FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL; + + if (rlevel < 0) + rlevel = 0; + else if (rlevel > MAX_RADEON_LEVEL) + rlevel = MAX_RADEON_LEVEL; + + if (pdata->negative) + rlevel = MAX_RADEON_LEVEL - rlevel; + + return rlevel; +} + +static int radeon_bl_update_status(struct backlight_device *bd) +{ + struct radeon_bl_privdata *pdata = bl_get_data(bd); + struct radeonfb_info *rinfo = pdata->rinfo; + u32 lvds_gen_cntl, tmpPixclksCntl; + int level; + + if (rinfo->mon1_type != MT_LCD) + return 0; + + /* We turn off the LCD completely instead of just dimming the + * backlight. This provides some greater power saving and the display + * is useless without backlight anyway. + */ + if (bd->props.power != FB_BLANK_UNBLANK || + bd->props.fb_blank != FB_BLANK_UNBLANK) + level = 0; + else + level = bd->props.brightness; + + del_timer_sync(&rinfo->lvds_timer); + radeon_engine_idle(); + + lvds_gen_cntl = INREG(LVDS_GEN_CNTL); + if (level > 0) { + lvds_gen_cntl &= ~LVDS_DISPLAY_DIS; + if (!(lvds_gen_cntl & LVDS_BLON) || !(lvds_gen_cntl & LVDS_ON)) { + lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_DIGON); + lvds_gen_cntl |= LVDS_BLON | LVDS_EN; + OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl); + lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK; + lvds_gen_cntl |= + (radeon_bl_get_level_brightness(pdata, level) << + LVDS_BL_MOD_LEVEL_SHIFT); + lvds_gen_cntl |= LVDS_ON; + lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_BL_MOD_EN); + rinfo->pending_lvds_gen_cntl = lvds_gen_cntl; + mod_timer(&rinfo->lvds_timer, + jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay)); + } else { + lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK; + lvds_gen_cntl |= + (radeon_bl_get_level_brightness(pdata, level) << + LVDS_BL_MOD_LEVEL_SHIFT); + OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl); + } + rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK; + rinfo->init_state.lvds_gen_cntl |= rinfo->pending_lvds_gen_cntl + & LVDS_STATE_MASK; + } else { + /* Asic bug, when turning off LVDS_ON, we have to make sure + RADEON_PIXCLK_LVDS_ALWAYS_ON bit is off + */ + tmpPixclksCntl = INPLL(PIXCLKS_CNTL); + if (rinfo->is_mobility || rinfo->is_IGP) + OUTPLLP(PIXCLKS_CNTL, 0, ~PIXCLK_LVDS_ALWAYS_ONb); + lvds_gen_cntl &= ~(LVDS_BL_MOD_LEVEL_MASK | LVDS_BL_MOD_EN); + lvds_gen_cntl |= (radeon_bl_get_level_brightness(pdata, 0) << + LVDS_BL_MOD_LEVEL_SHIFT); + lvds_gen_cntl |= LVDS_DISPLAY_DIS; + OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl); + udelay(100); + lvds_gen_cntl &= ~(LVDS_ON | LVDS_EN); + OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl); + lvds_gen_cntl &= ~(LVDS_DIGON); + rinfo->pending_lvds_gen_cntl = lvds_gen_cntl; + mod_timer(&rinfo->lvds_timer, + jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay)); + if (rinfo->is_mobility || rinfo->is_IGP) + OUTPLL(PIXCLKS_CNTL, tmpPixclksCntl); + } + rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK; + rinfo->init_state.lvds_gen_cntl |= (lvds_gen_cntl & LVDS_STATE_MASK); + + return 0; +} + +static int radeon_bl_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static const struct backlight_ops radeon_bl_data = { + .get_brightness = radeon_bl_get_brightness, + .update_status = radeon_bl_update_status, +}; + +void radeonfb_bl_init(struct radeonfb_info *rinfo) +{ + struct backlight_properties props; + struct backlight_device *bd; + struct radeon_bl_privdata *pdata; + char name[12]; + + if (rinfo->mon1_type != MT_LCD) + return; + +#ifdef CONFIG_PMAC_BACKLIGHT + if (!pmac_has_backlight_type("ati") && + !pmac_has_backlight_type("mnca")) + return; +#endif + + pdata = kmalloc(sizeof(struct radeon_bl_privdata), GFP_KERNEL); + if (!pdata) { + printk("radeonfb: Memory allocation failed\n"); + goto error; + } + + snprintf(name, sizeof(name), "radeonbl%d", rinfo->info->node); + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = FB_BACKLIGHT_LEVELS - 1; + bd = backlight_device_register(name, rinfo->info->dev, pdata, + &radeon_bl_data, &props); + if (IS_ERR(bd)) { + rinfo->info->bl_dev = NULL; + printk("radeonfb: Backlight registration failed\n"); + goto error; + } + + pdata->rinfo = rinfo; + + /* Pardon me for that hack... maybe some day we can figure out in what + * direction backlight should work on a given panel? + */ + pdata->negative = + (rinfo->family != CHIP_FAMILY_RV200 && + rinfo->family != CHIP_FAMILY_RV250 && + rinfo->family != CHIP_FAMILY_RV280 && + rinfo->family != CHIP_FAMILY_RV350); + +#ifdef CONFIG_PMAC_BACKLIGHT + pdata->negative = pdata->negative || + of_machine_is_compatible("PowerBook4,3") || + of_machine_is_compatible("PowerBook6,3") || + of_machine_is_compatible("PowerBook6,5"); +#endif + + rinfo->info->bl_dev = bd; + fb_bl_default_curve(rinfo->info, 0, + 63 * FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL, + 217 * FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL); + + bd->props.brightness = bd->props.max_brightness; + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("radeonfb: Backlight initialized (%s)\n", name); + + return; + +error: + kfree(pdata); + return; +} + +void radeonfb_bl_exit(struct radeonfb_info *rinfo) +{ + struct backlight_device *bd = rinfo->info->bl_dev; + + if (bd) { + struct radeon_bl_privdata *pdata; + + pdata = bl_get_data(bd); + backlight_device_unregister(bd); + kfree(pdata); + rinfo->info->bl_dev = NULL; + + printk("radeonfb: Backlight unloaded\n"); + } +} diff --git a/drivers/video/fbdev/aty/radeon_base.c b/drivers/video/fbdev/aty/radeon_base.c new file mode 100644 index 000000000000..26d80a4486fb --- /dev/null +++ b/drivers/video/fbdev/aty/radeon_base.c @@ -0,0 +1,2568 @@ +/* + * drivers/video/aty/radeon_base.c + * + * framebuffer driver for ATI Radeon chipset video boards + * + * Copyright 2003 Ben. Herrenschmidt <benh@kernel.crashing.org> + * Copyright 2000 Ani Joshi <ajoshi@kernel.crashing.org> + * + * i2c bits from Luca Tettamanti <kronos@kronoz.cjb.net> + * + * Special thanks to ATI DevRel team for their hardware donations. + * + * ...Insert GPL boilerplate here... + * + * Significant portions of this driver apdated from XFree86 Radeon + * driver which has the following copyright notice: + * + * Copyright 2000 ATI Technologies Inc., Markham, Ontario, and + * VA Linux Systems Inc., Fremont, California. + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation on the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT. IN NO EVENT SHALL ATI, VA LINUX SYSTEMS AND/OR + * THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * XFree86 driver authors: + * + * Kevin E. Martin <martin@xfree86.org> + * Rickard E. Faith <faith@valinux.com> + * Alan Hourihane <alanh@fairlite.demon.co.uk> + * + */ + + +#define RADEON_VERSION "0.2.0" + +#include "radeonfb.h" + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/device.h> + +#include <asm/io.h> +#include <linux/uaccess.h> + +#ifdef CONFIG_PPC_OF + +#include <asm/pci-bridge.h> +#include "../macmodes.h" + +#ifdef CONFIG_BOOTX_TEXT +#include <asm/btext.h> +#endif + +#endif /* CONFIG_PPC_OF */ + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include <video/radeon.h> +#include <linux/radeonfb.h> + +#include "../edid.h" // MOVE THAT TO include/video +#include "ati_ids.h" + +#define MAX_MAPPED_VRAM (2048*2048*4) +#define MIN_MAPPED_VRAM (1024*768*1) + +#define CHIP_DEF(id, family, flags) \ + { PCI_VENDOR_ID_ATI, id, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (flags) | (CHIP_FAMILY_##family) } + +static struct pci_device_id radeonfb_pci_table[] = { + /* Radeon Xpress 200m */ + CHIP_DEF(PCI_CHIP_RS480_5955, RS480, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RS482_5975, RS480, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + /* Mobility M6 */ + CHIP_DEF(PCI_CHIP_RADEON_LY, RV100, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RADEON_LZ, RV100, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + /* Radeon VE/7000 */ + CHIP_DEF(PCI_CHIP_RV100_QY, RV100, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV100_QZ, RV100, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RN50, RV100, CHIP_HAS_CRTC2), + /* Radeon IGP320M (U1) */ + CHIP_DEF(PCI_CHIP_RS100_4336, RS100, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + /* Radeon IGP320 (A3) */ + CHIP_DEF(PCI_CHIP_RS100_4136, RS100, CHIP_HAS_CRTC2 | CHIP_IS_IGP), + /* IGP330M/340M/350M (U2) */ + CHIP_DEF(PCI_CHIP_RS200_4337, RS200, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + /* IGP330/340/350 (A4) */ + CHIP_DEF(PCI_CHIP_RS200_4137, RS200, CHIP_HAS_CRTC2 | CHIP_IS_IGP), + /* Mobility 7000 IGP */ + CHIP_DEF(PCI_CHIP_RS250_4437, RS200, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + /* 7000 IGP (A4+) */ + CHIP_DEF(PCI_CHIP_RS250_4237, RS200, CHIP_HAS_CRTC2 | CHIP_IS_IGP), + /* 8500 AIW */ + CHIP_DEF(PCI_CHIP_R200_BB, R200, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R200_BC, R200, CHIP_HAS_CRTC2), + /* 8700/8800 */ + CHIP_DEF(PCI_CHIP_R200_QH, R200, CHIP_HAS_CRTC2), + /* 8500 */ + CHIP_DEF(PCI_CHIP_R200_QL, R200, CHIP_HAS_CRTC2), + /* 9100 */ + CHIP_DEF(PCI_CHIP_R200_QM, R200, CHIP_HAS_CRTC2), + /* Mobility M7 */ + CHIP_DEF(PCI_CHIP_RADEON_LW, RV200, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RADEON_LX, RV200, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + /* 7500 */ + CHIP_DEF(PCI_CHIP_RV200_QW, RV200, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV200_QX, RV200, CHIP_HAS_CRTC2), + /* Mobility M9 */ + CHIP_DEF(PCI_CHIP_RV250_Ld, RV250, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV250_Le, RV250, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV250_Lf, RV250, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV250_Lg, RV250, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + /* 9000/Pro */ + CHIP_DEF(PCI_CHIP_RV250_If, RV250, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV250_Ig, RV250, CHIP_HAS_CRTC2), + + CHIP_DEF(PCI_CHIP_RC410_5A62, RC410, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + /* Mobility 9100 IGP (U3) */ + CHIP_DEF(PCI_CHIP_RS300_5835, RS300, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RS350_7835, RS300, CHIP_HAS_CRTC2 | CHIP_IS_IGP | CHIP_IS_MOBILITY), + /* 9100 IGP (A5) */ + CHIP_DEF(PCI_CHIP_RS300_5834, RS300, CHIP_HAS_CRTC2 | CHIP_IS_IGP), + CHIP_DEF(PCI_CHIP_RS350_7834, RS300, CHIP_HAS_CRTC2 | CHIP_IS_IGP), + /* Mobility 9200 (M9+) */ + CHIP_DEF(PCI_CHIP_RV280_5C61, RV280, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV280_5C63, RV280, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + /* 9200 */ + CHIP_DEF(PCI_CHIP_RV280_5960, RV280, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV280_5961, RV280, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV280_5962, RV280, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV280_5964, RV280, CHIP_HAS_CRTC2), + /* 9500 */ + CHIP_DEF(PCI_CHIP_R300_AD, R300, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R300_AE, R300, CHIP_HAS_CRTC2), + /* 9600TX / FireGL Z1 */ + CHIP_DEF(PCI_CHIP_R300_AF, R300, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R300_AG, R300, CHIP_HAS_CRTC2), + /* 9700/9500/Pro/FireGL X1 */ + CHIP_DEF(PCI_CHIP_R300_ND, R300, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R300_NE, R300, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R300_NF, R300, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R300_NG, R300, CHIP_HAS_CRTC2), + /* Mobility M10/M11 */ + CHIP_DEF(PCI_CHIP_RV350_NP, RV350, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV350_NQ, RV350, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV350_NR, RV350, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV350_NS, RV350, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV350_NT, RV350, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV350_NV, RV350, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + /* 9600/FireGL T2 */ + CHIP_DEF(PCI_CHIP_RV350_AP, RV350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV350_AQ, RV350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV360_AR, RV350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV350_AS, RV350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV350_AT, RV350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV350_AV, RV350, CHIP_HAS_CRTC2), + /* 9800/Pro/FileGL X2 */ + CHIP_DEF(PCI_CHIP_R350_AH, R350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R350_AI, R350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R350_AJ, R350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R350_AK, R350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R350_NH, R350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R350_NI, R350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R360_NJ, R350, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R350_NK, R350, CHIP_HAS_CRTC2), + /* Newer stuff */ + CHIP_DEF(PCI_CHIP_RV380_3E50, RV380, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV380_3E54, RV380, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV380_3150, RV380, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV380_3154, RV380, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV370_5B60, RV380, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV370_5B62, RV380, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV370_5B63, RV380, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV370_5B64, RV380, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV370_5B65, RV380, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_RV370_5460, RV380, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_RV370_5464, RV380, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_R420_JH, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R420_JI, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R420_JJ, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R420_JK, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R420_JL, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R420_JM, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R420_JN, R420, CHIP_HAS_CRTC2 | CHIP_IS_MOBILITY), + CHIP_DEF(PCI_CHIP_R420_JP, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_UH, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_UI, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_UJ, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_UK, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_UQ, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_UR, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_UT, R420, CHIP_HAS_CRTC2), + CHIP_DEF(PCI_CHIP_R423_5D57, R420, CHIP_HAS_CRTC2), + /* Original Radeon/7200 */ + CHIP_DEF(PCI_CHIP_RADEON_QD, RADEON, 0), + CHIP_DEF(PCI_CHIP_RADEON_QE, RADEON, 0), + CHIP_DEF(PCI_CHIP_RADEON_QF, RADEON, 0), + CHIP_DEF(PCI_CHIP_RADEON_QG, RADEON, 0), + { 0, } +}; +MODULE_DEVICE_TABLE(pci, radeonfb_pci_table); + + +typedef struct { + u16 reg; + u32 val; +} reg_val; + + +/* these common regs are cleared before mode setting so they do not + * interfere with anything + */ +static reg_val common_regs[] = { + { OVR_CLR, 0 }, + { OVR_WID_LEFT_RIGHT, 0 }, + { OVR_WID_TOP_BOTTOM, 0 }, + { OV0_SCALE_CNTL, 0 }, + { SUBPIC_CNTL, 0 }, + { VIPH_CONTROL, 0 }, + { I2C_CNTL_1, 0 }, + { GEN_INT_CNTL, 0 }, + { CAP0_TRIG_CNTL, 0 }, + { CAP1_TRIG_CNTL, 0 }, +}; + +/* + * globals + */ + +static char *mode_option; +static char *monitor_layout; +static bool noaccel = 0; +static int default_dynclk = -2; +static bool nomodeset = 0; +static bool ignore_edid = 0; +static bool mirror = 0; +static int panel_yres = 0; +static bool force_dfp = 0; +static bool force_measure_pll = 0; +#ifdef CONFIG_MTRR +static bool nomtrr = 0; +#endif +static bool force_sleep; +static bool ignore_devlist; +#ifdef CONFIG_PMAC_BACKLIGHT +static int backlight = 1; +#else +static int backlight = 0; +#endif + +/* + * prototypes + */ + +static void radeon_unmap_ROM(struct radeonfb_info *rinfo, struct pci_dev *dev) +{ + if (!rinfo->bios_seg) + return; + pci_unmap_rom(dev, rinfo->bios_seg); +} + +static int radeon_map_ROM(struct radeonfb_info *rinfo, struct pci_dev *dev) +{ + void __iomem *rom; + u16 dptr; + u8 rom_type; + size_t rom_size; + + /* If this is a primary card, there is a shadow copy of the + * ROM somewhere in the first meg. We will just ignore the copy + * and use the ROM directly. + */ + + /* Fix from ATI for problem with Radeon hardware not leaving ROM enabled */ + unsigned int temp; + temp = INREG(MPP_TB_CONFIG); + temp &= 0x00ffffffu; + temp |= 0x04 << 24; + OUTREG(MPP_TB_CONFIG, temp); + temp = INREG(MPP_TB_CONFIG); + + rom = pci_map_rom(dev, &rom_size); + if (!rom) { + printk(KERN_ERR "radeonfb (%s): ROM failed to map\n", + pci_name(rinfo->pdev)); + return -ENOMEM; + } + + rinfo->bios_seg = rom; + + /* Very simple test to make sure it appeared */ + if (BIOS_IN16(0) != 0xaa55) { + printk(KERN_DEBUG "radeonfb (%s): Invalid ROM signature %x " + "should be 0xaa55\n", + pci_name(rinfo->pdev), BIOS_IN16(0)); + goto failed; + } + /* Look for the PCI data to check the ROM type */ + dptr = BIOS_IN16(0x18); + + /* Check the PCI data signature. If it's wrong, we still assume a normal x86 ROM + * for now, until I've verified this works everywhere. The goal here is more + * to phase out Open Firmware images. + * + * Currently, we only look at the first PCI data, we could iteratre and deal with + * them all, and we should use fb_bios_start relative to start of image and not + * relative start of ROM, but so far, I never found a dual-image ATI card + * + * typedef struct { + * u32 signature; + 0x00 + * u16 vendor; + 0x04 + * u16 device; + 0x06 + * u16 reserved_1; + 0x08 + * u16 dlen; + 0x0a + * u8 drevision; + 0x0c + * u8 class_hi; + 0x0d + * u16 class_lo; + 0x0e + * u16 ilen; + 0x10 + * u16 irevision; + 0x12 + * u8 type; + 0x14 + * u8 indicator; + 0x15 + * u16 reserved_2; + 0x16 + * } pci_data_t; + */ + if (BIOS_IN32(dptr) != (('R' << 24) | ('I' << 16) | ('C' << 8) | 'P')) { + printk(KERN_WARNING "radeonfb (%s): PCI DATA signature in ROM" + "incorrect: %08x\n", pci_name(rinfo->pdev), BIOS_IN32(dptr)); + goto anyway; + } + rom_type = BIOS_IN8(dptr + 0x14); + switch(rom_type) { + case 0: + printk(KERN_INFO "radeonfb: Found Intel x86 BIOS ROM Image\n"); + break; + case 1: + printk(KERN_INFO "radeonfb: Found Open Firmware ROM Image\n"); + goto failed; + case 2: + printk(KERN_INFO "radeonfb: Found HP PA-RISC ROM Image\n"); + goto failed; + default: + printk(KERN_INFO "radeonfb: Found unknown type %d ROM Image\n", rom_type); + goto failed; + } + anyway: + /* Locate the flat panel infos, do some sanity checking !!! */ + rinfo->fp_bios_start = BIOS_IN16(0x48); + return 0; + + failed: + rinfo->bios_seg = NULL; + radeon_unmap_ROM(rinfo, dev); + return -ENXIO; +} + +#ifdef CONFIG_X86 +static int radeon_find_mem_vbios(struct radeonfb_info *rinfo) +{ + /* I simplified this code as we used to miss the signatures in + * a lot of case. It's now closer to XFree, we just don't check + * for signatures at all... Something better will have to be done + * if we end up having conflicts + */ + u32 segstart; + void __iomem *rom_base = NULL; + + for(segstart=0x000c0000; segstart<0x000f0000; segstart+=0x00001000) { + rom_base = ioremap(segstart, 0x10000); + if (rom_base == NULL) + return -ENOMEM; + if (readb(rom_base) == 0x55 && readb(rom_base + 1) == 0xaa) + break; + iounmap(rom_base); + rom_base = NULL; + } + if (rom_base == NULL) + return -ENXIO; + + /* Locate the flat panel infos, do some sanity checking !!! */ + rinfo->bios_seg = rom_base; + rinfo->fp_bios_start = BIOS_IN16(0x48); + + return 0; +} +#endif + +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) +/* + * Read XTAL (ref clock), SCLK and MCLK from Open Firmware device + * tree. Hopefully, ATI OF driver is kind enough to fill these + */ +static int radeon_read_xtal_OF(struct radeonfb_info *rinfo) +{ + struct device_node *dp = rinfo->of_node; + const u32 *val; + + if (dp == NULL) + return -ENODEV; + val = of_get_property(dp, "ATY,RefCLK", NULL); + if (!val || !*val) { + printk(KERN_WARNING "radeonfb: No ATY,RefCLK property !\n"); + return -EINVAL; + } + + rinfo->pll.ref_clk = (*val) / 10; + + val = of_get_property(dp, "ATY,SCLK", NULL); + if (val && *val) + rinfo->pll.sclk = (*val) / 10; + + val = of_get_property(dp, "ATY,MCLK", NULL); + if (val && *val) + rinfo->pll.mclk = (*val) / 10; + + return 0; +} +#endif /* CONFIG_PPC_OF || CONFIG_SPARC */ + +/* + * Read PLL infos from chip registers + */ +static int radeon_probe_pll_params(struct radeonfb_info *rinfo) +{ + unsigned char ppll_div_sel; + unsigned Ns, Nm, M; + unsigned sclk, mclk, tmp, ref_div; + int hTotal, vTotal, num, denom, m, n; + unsigned long long hz, vclk; + long xtal; + struct timeval start_tv, stop_tv; + long total_secs, total_usecs; + int i; + + /* Ugh, we cut interrupts, bad bad bad, but we want some precision + * here, so... --BenH + */ + + /* Flush PCI buffers ? */ + tmp = INREG16(DEVICE_ID); + + local_irq_disable(); + + for(i=0; i<1000000; i++) + if (((INREG(CRTC_VLINE_CRNT_VLINE) >> 16) & 0x3ff) == 0) + break; + + do_gettimeofday(&start_tv); + + for(i=0; i<1000000; i++) + if (((INREG(CRTC_VLINE_CRNT_VLINE) >> 16) & 0x3ff) != 0) + break; + + for(i=0; i<1000000; i++) + if (((INREG(CRTC_VLINE_CRNT_VLINE) >> 16) & 0x3ff) == 0) + break; + + do_gettimeofday(&stop_tv); + + local_irq_enable(); + + total_secs = stop_tv.tv_sec - start_tv.tv_sec; + if (total_secs > 10) + return -1; + total_usecs = stop_tv.tv_usec - start_tv.tv_usec; + total_usecs += total_secs * 1000000; + if (total_usecs < 0) + total_usecs = -total_usecs; + hz = 1000000/total_usecs; + + hTotal = ((INREG(CRTC_H_TOTAL_DISP) & 0x1ff) + 1) * 8; + vTotal = ((INREG(CRTC_V_TOTAL_DISP) & 0x3ff) + 1); + vclk = (long long)hTotal * (long long)vTotal * hz; + + switch((INPLL(PPLL_REF_DIV) & 0x30000) >> 16) { + case 0: + default: + num = 1; + denom = 1; + break; + case 1: + n = ((INPLL(M_SPLL_REF_FB_DIV) >> 16) & 0xff); + m = (INPLL(M_SPLL_REF_FB_DIV) & 0xff); + num = 2*n; + denom = 2*m; + break; + case 2: + n = ((INPLL(M_SPLL_REF_FB_DIV) >> 8) & 0xff); + m = (INPLL(M_SPLL_REF_FB_DIV) & 0xff); + num = 2*n; + denom = 2*m; + break; + } + + ppll_div_sel = INREG8(CLOCK_CNTL_INDEX + 1) & 0x3; + radeon_pll_errata_after_index(rinfo); + + n = (INPLL(PPLL_DIV_0 + ppll_div_sel) & 0x7ff); + m = (INPLL(PPLL_REF_DIV) & 0x3ff); + + num *= n; + denom *= m; + + switch ((INPLL(PPLL_DIV_0 + ppll_div_sel) >> 16) & 0x7) { + case 1: + denom *= 2; + break; + case 2: + denom *= 4; + break; + case 3: + denom *= 8; + break; + case 4: + denom *= 3; + break; + case 6: + denom *= 6; + break; + case 7: + denom *= 12; + break; + } + + vclk *= denom; + do_div(vclk, 1000 * num); + xtal = vclk; + + if ((xtal > 26900) && (xtal < 27100)) + xtal = 2700; + else if ((xtal > 14200) && (xtal < 14400)) + xtal = 1432; + else if ((xtal > 29400) && (xtal < 29600)) + xtal = 2950; + else { + printk(KERN_WARNING "xtal calculation failed: %ld\n", xtal); + return -1; + } + + tmp = INPLL(M_SPLL_REF_FB_DIV); + ref_div = INPLL(PPLL_REF_DIV) & 0x3ff; + + Ns = (tmp & 0xff0000) >> 16; + Nm = (tmp & 0xff00) >> 8; + M = (tmp & 0xff); + sclk = round_div((2 * Ns * xtal), (2 * M)); + mclk = round_div((2 * Nm * xtal), (2 * M)); + + /* we're done, hopefully these are sane values */ + rinfo->pll.ref_clk = xtal; + rinfo->pll.ref_div = ref_div; + rinfo->pll.sclk = sclk; + rinfo->pll.mclk = mclk; + + return 0; +} + +/* + * Retrieve PLL infos by different means (BIOS, Open Firmware, register probing...) + */ +static void radeon_get_pllinfo(struct radeonfb_info *rinfo) +{ + /* + * In the case nothing works, these are defaults; they are mostly + * incomplete, however. It does provide ppll_max and _min values + * even for most other methods, however. + */ + switch (rinfo->chipset) { + case PCI_DEVICE_ID_ATI_RADEON_QW: + case PCI_DEVICE_ID_ATI_RADEON_QX: + rinfo->pll.ppll_max = 35000; + rinfo->pll.ppll_min = 12000; + rinfo->pll.mclk = 23000; + rinfo->pll.sclk = 23000; + rinfo->pll.ref_clk = 2700; + break; + case PCI_DEVICE_ID_ATI_RADEON_QL: + case PCI_DEVICE_ID_ATI_RADEON_QN: + case PCI_DEVICE_ID_ATI_RADEON_QO: + case PCI_DEVICE_ID_ATI_RADEON_Ql: + case PCI_DEVICE_ID_ATI_RADEON_BB: + rinfo->pll.ppll_max = 35000; + rinfo->pll.ppll_min = 12000; + rinfo->pll.mclk = 27500; + rinfo->pll.sclk = 27500; + rinfo->pll.ref_clk = 2700; + break; + case PCI_DEVICE_ID_ATI_RADEON_Id: + case PCI_DEVICE_ID_ATI_RADEON_Ie: + case PCI_DEVICE_ID_ATI_RADEON_If: + case PCI_DEVICE_ID_ATI_RADEON_Ig: + rinfo->pll.ppll_max = 35000; + rinfo->pll.ppll_min = 12000; + rinfo->pll.mclk = 25000; + rinfo->pll.sclk = 25000; + rinfo->pll.ref_clk = 2700; + break; + case PCI_DEVICE_ID_ATI_RADEON_ND: + case PCI_DEVICE_ID_ATI_RADEON_NE: + case PCI_DEVICE_ID_ATI_RADEON_NF: + case PCI_DEVICE_ID_ATI_RADEON_NG: + rinfo->pll.ppll_max = 40000; + rinfo->pll.ppll_min = 20000; + rinfo->pll.mclk = 27000; + rinfo->pll.sclk = 27000; + rinfo->pll.ref_clk = 2700; + break; + case PCI_DEVICE_ID_ATI_RADEON_QD: + case PCI_DEVICE_ID_ATI_RADEON_QE: + case PCI_DEVICE_ID_ATI_RADEON_QF: + case PCI_DEVICE_ID_ATI_RADEON_QG: + default: + rinfo->pll.ppll_max = 35000; + rinfo->pll.ppll_min = 12000; + rinfo->pll.mclk = 16600; + rinfo->pll.sclk = 16600; + rinfo->pll.ref_clk = 2700; + break; + } + rinfo->pll.ref_div = INPLL(PPLL_REF_DIV) & PPLL_REF_DIV_MASK; + + +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) + /* + * Retrieve PLL infos from Open Firmware first + */ + if (!force_measure_pll && radeon_read_xtal_OF(rinfo) == 0) { + printk(KERN_INFO "radeonfb: Retrieved PLL infos from Open Firmware\n"); + goto found; + } +#endif /* CONFIG_PPC_OF || CONFIG_SPARC */ + + /* + * Check out if we have an X86 which gave us some PLL informations + * and if yes, retrieve them + */ + if (!force_measure_pll && rinfo->bios_seg) { + u16 pll_info_block = BIOS_IN16(rinfo->fp_bios_start + 0x30); + + rinfo->pll.sclk = BIOS_IN16(pll_info_block + 0x08); + rinfo->pll.mclk = BIOS_IN16(pll_info_block + 0x0a); + rinfo->pll.ref_clk = BIOS_IN16(pll_info_block + 0x0e); + rinfo->pll.ref_div = BIOS_IN16(pll_info_block + 0x10); + rinfo->pll.ppll_min = BIOS_IN32(pll_info_block + 0x12); + rinfo->pll.ppll_max = BIOS_IN32(pll_info_block + 0x16); + + printk(KERN_INFO "radeonfb: Retrieved PLL infos from BIOS\n"); + goto found; + } + + /* + * We didn't get PLL parameters from either OF or BIOS, we try to + * probe them + */ + if (radeon_probe_pll_params(rinfo) == 0) { + printk(KERN_INFO "radeonfb: Retrieved PLL infos from registers\n"); + goto found; + } + + /* + * Fall back to already-set defaults... + */ + printk(KERN_INFO "radeonfb: Used default PLL infos\n"); + +found: + /* + * Some methods fail to retrieve SCLK and MCLK values, we apply default + * settings in this case (200Mhz). If that really happens often, we + * could fetch from registers instead... + */ + if (rinfo->pll.mclk == 0) + rinfo->pll.mclk = 20000; + if (rinfo->pll.sclk == 0) + rinfo->pll.sclk = 20000; + + printk("radeonfb: Reference=%d.%02d MHz (RefDiv=%d) Memory=%d.%02d Mhz, System=%d.%02d MHz\n", + rinfo->pll.ref_clk / 100, rinfo->pll.ref_clk % 100, + rinfo->pll.ref_div, + rinfo->pll.mclk / 100, rinfo->pll.mclk % 100, + rinfo->pll.sclk / 100, rinfo->pll.sclk % 100); + printk("radeonfb: PLL min %d max %d\n", rinfo->pll.ppll_min, rinfo->pll.ppll_max); +} + +static int radeonfb_check_var (struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct radeonfb_info *rinfo = info->par; + struct fb_var_screeninfo v; + int nom, den; + unsigned int pitch; + + if (radeon_match_mode(rinfo, &v, var)) + return -EINVAL; + + switch (v.bits_per_pixel) { + case 0 ... 8: + v.bits_per_pixel = 8; + break; + case 9 ... 16: + v.bits_per_pixel = 16; + break; + case 17 ... 24: +#if 0 /* Doesn't seem to work */ + v.bits_per_pixel = 24; + break; +#endif + return -EINVAL; + case 25 ... 32: + v.bits_per_pixel = 32; + break; + default: + return -EINVAL; + } + + switch (var_to_depth(&v)) { + case 8: + nom = den = 1; + v.red.offset = v.green.offset = v.blue.offset = 0; + v.red.length = v.green.length = v.blue.length = 8; + v.transp.offset = v.transp.length = 0; + break; + case 15: + nom = 2; + den = 1; + v.red.offset = 10; + v.green.offset = 5; + v.blue.offset = 0; + v.red.length = v.green.length = v.blue.length = 5; + v.transp.offset = v.transp.length = 0; + break; + case 16: + nom = 2; + den = 1; + v.red.offset = 11; + v.green.offset = 5; + v.blue.offset = 0; + v.red.length = 5; + v.green.length = 6; + v.blue.length = 5; + v.transp.offset = v.transp.length = 0; + break; + case 24: + nom = 4; + den = 1; + v.red.offset = 16; + v.green.offset = 8; + v.blue.offset = 0; + v.red.length = v.blue.length = v.green.length = 8; + v.transp.offset = v.transp.length = 0; + break; + case 32: + nom = 4; + den = 1; + v.red.offset = 16; + v.green.offset = 8; + v.blue.offset = 0; + v.red.length = v.blue.length = v.green.length = 8; + v.transp.offset = 24; + v.transp.length = 8; + break; + default: + printk ("radeonfb: mode %dx%dx%d rejected, color depth invalid\n", + var->xres, var->yres, var->bits_per_pixel); + return -EINVAL; + } + + if (v.yres_virtual < v.yres) + v.yres_virtual = v.yres; + if (v.xres_virtual < v.xres) + v.xres_virtual = v.xres; + + + /* XXX I'm adjusting xres_virtual to the pitch, that may help XFree + * with some panels, though I don't quite like this solution + */ + if (rinfo->info->flags & FBINFO_HWACCEL_DISABLED) { + v.xres_virtual = v.xres_virtual & ~7ul; + } else { + pitch = ((v.xres_virtual * ((v.bits_per_pixel + 1) / 8) + 0x3f) + & ~(0x3f)) >> 6; + v.xres_virtual = (pitch << 6) / ((v.bits_per_pixel + 1) / 8); + } + + if (((v.xres_virtual * v.yres_virtual * nom) / den) > rinfo->mapped_vram) + return -EINVAL; + + if (v.xres_virtual < v.xres) + v.xres = v.xres_virtual; + + if (v.xoffset > v.xres_virtual - v.xres) + v.xoffset = v.xres_virtual - v.xres - 1; + + if (v.yoffset > v.yres_virtual - v.yres) + v.yoffset = v.yres_virtual - v.yres - 1; + + v.red.msb_right = v.green.msb_right = v.blue.msb_right = + v.transp.offset = v.transp.length = + v.transp.msb_right = 0; + + memcpy(var, &v, sizeof(v)); + + return 0; +} + + +static int radeonfb_pan_display (struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct radeonfb_info *rinfo = info->par; + + if ((var->xoffset + info->var.xres > info->var.xres_virtual) + || (var->yoffset + info->var.yres > info->var.yres_virtual)) + return -EINVAL; + + if (rinfo->asleep) + return 0; + + radeon_fifo_wait(2); + OUTREG(CRTC_OFFSET, (var->yoffset * info->fix.line_length + + var->xoffset * info->var.bits_per_pixel / 8) & ~7); + return 0; +} + + +static int radeonfb_ioctl (struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct radeonfb_info *rinfo = info->par; + unsigned int tmp; + u32 value = 0; + int rc; + + switch (cmd) { + /* + * TODO: set mirror accordingly for non-Mobility chipsets with 2 CRTC's + * and do something better using 2nd CRTC instead of just hackish + * routing to second output + */ + case FBIO_RADEON_SET_MIRROR: + if (!rinfo->is_mobility) + return -EINVAL; + + rc = get_user(value, (__u32 __user *)arg); + + if (rc) + return rc; + + radeon_fifo_wait(2); + if (value & 0x01) { + tmp = INREG(LVDS_GEN_CNTL); + + tmp |= (LVDS_ON | LVDS_BLON); + } else { + tmp = INREG(LVDS_GEN_CNTL); + + tmp &= ~(LVDS_ON | LVDS_BLON); + } + + OUTREG(LVDS_GEN_CNTL, tmp); + + if (value & 0x02) { + tmp = INREG(CRTC_EXT_CNTL); + tmp |= CRTC_CRT_ON; + + mirror = 1; + } else { + tmp = INREG(CRTC_EXT_CNTL); + tmp &= ~CRTC_CRT_ON; + + mirror = 0; + } + + OUTREG(CRTC_EXT_CNTL, tmp); + + return 0; + case FBIO_RADEON_GET_MIRROR: + if (!rinfo->is_mobility) + return -EINVAL; + + tmp = INREG(LVDS_GEN_CNTL); + if ((LVDS_ON | LVDS_BLON) & tmp) + value |= 0x01; + + tmp = INREG(CRTC_EXT_CNTL); + if (CRTC_CRT_ON & tmp) + value |= 0x02; + + return put_user(value, (__u32 __user *)arg); + default: + return -EINVAL; + } + + return -EINVAL; +} + + +int radeon_screen_blank(struct radeonfb_info *rinfo, int blank, int mode_switch) +{ + u32 val; + u32 tmp_pix_clks; + int unblank = 0; + + if (rinfo->lock_blank) + return 0; + + radeon_engine_idle(); + + val = INREG(CRTC_EXT_CNTL); + val &= ~(CRTC_DISPLAY_DIS | CRTC_HSYNC_DIS | + CRTC_VSYNC_DIS); + switch (blank) { + case FB_BLANK_VSYNC_SUSPEND: + val |= (CRTC_DISPLAY_DIS | CRTC_VSYNC_DIS); + break; + case FB_BLANK_HSYNC_SUSPEND: + val |= (CRTC_DISPLAY_DIS | CRTC_HSYNC_DIS); + break; + case FB_BLANK_POWERDOWN: + val |= (CRTC_DISPLAY_DIS | CRTC_VSYNC_DIS | + CRTC_HSYNC_DIS); + break; + case FB_BLANK_NORMAL: + val |= CRTC_DISPLAY_DIS; + break; + case FB_BLANK_UNBLANK: + default: + unblank = 1; + } + OUTREG(CRTC_EXT_CNTL, val); + + + switch (rinfo->mon1_type) { + case MT_DFP: + if (unblank) + OUTREGP(FP_GEN_CNTL, (FP_FPON | FP_TMDS_EN), + ~(FP_FPON | FP_TMDS_EN)); + else { + if (mode_switch || blank == FB_BLANK_NORMAL) + break; + OUTREGP(FP_GEN_CNTL, 0, ~(FP_FPON | FP_TMDS_EN)); + } + break; + case MT_LCD: + del_timer_sync(&rinfo->lvds_timer); + val = INREG(LVDS_GEN_CNTL); + if (unblank) { + u32 target_val = (val & ~LVDS_DISPLAY_DIS) | LVDS_BLON | LVDS_ON + | LVDS_EN | (rinfo->init_state.lvds_gen_cntl + & (LVDS_DIGON | LVDS_BL_MOD_EN)); + if ((val ^ target_val) == LVDS_DISPLAY_DIS) + OUTREG(LVDS_GEN_CNTL, target_val); + else if ((val ^ target_val) != 0) { + OUTREG(LVDS_GEN_CNTL, target_val + & ~(LVDS_ON | LVDS_BL_MOD_EN)); + rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK; + rinfo->init_state.lvds_gen_cntl |= + target_val & LVDS_STATE_MASK; + if (mode_switch) { + radeon_msleep(rinfo->panel_info.pwr_delay); + OUTREG(LVDS_GEN_CNTL, target_val); + } + else { + rinfo->pending_lvds_gen_cntl = target_val; + mod_timer(&rinfo->lvds_timer, + jiffies + + msecs_to_jiffies(rinfo->panel_info.pwr_delay)); + } + } + } else { + val |= LVDS_DISPLAY_DIS; + OUTREG(LVDS_GEN_CNTL, val); + + /* We don't do a full switch-off on a simple mode switch */ + if (mode_switch || blank == FB_BLANK_NORMAL) + break; + + /* Asic bug, when turning off LVDS_ON, we have to make sure + * RADEON_PIXCLK_LVDS_ALWAYS_ON bit is off + */ + tmp_pix_clks = INPLL(PIXCLKS_CNTL); + if (rinfo->is_mobility || rinfo->is_IGP) + OUTPLLP(PIXCLKS_CNTL, 0, ~PIXCLK_LVDS_ALWAYS_ONb); + val &= ~(LVDS_BL_MOD_EN); + OUTREG(LVDS_GEN_CNTL, val); + udelay(100); + val &= ~(LVDS_ON | LVDS_EN); + OUTREG(LVDS_GEN_CNTL, val); + val &= ~LVDS_DIGON; + rinfo->pending_lvds_gen_cntl = val; + mod_timer(&rinfo->lvds_timer, + jiffies + + msecs_to_jiffies(rinfo->panel_info.pwr_delay)); + rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK; + rinfo->init_state.lvds_gen_cntl |= val & LVDS_STATE_MASK; + if (rinfo->is_mobility || rinfo->is_IGP) + OUTPLL(PIXCLKS_CNTL, tmp_pix_clks); + } + break; + case MT_CRT: + // todo: powerdown DAC + default: + break; + } + + return 0; +} + +static int radeonfb_blank (int blank, struct fb_info *info) +{ + struct radeonfb_info *rinfo = info->par; + + if (rinfo->asleep) + return 0; + + return radeon_screen_blank(rinfo, blank, 0); +} + +static int radeon_setcolreg (unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct radeonfb_info *rinfo) +{ + u32 pindex; + unsigned int i; + + + if (regno > 255) + return -EINVAL; + + red >>= 8; + green >>= 8; + blue >>= 8; + rinfo->palette[regno].red = red; + rinfo->palette[regno].green = green; + rinfo->palette[regno].blue = blue; + + /* default */ + pindex = regno; + + if (!rinfo->asleep) { + radeon_fifo_wait(9); + + if (rinfo->bpp == 16) { + pindex = regno * 8; + + if (rinfo->depth == 16 && regno > 63) + return -EINVAL; + if (rinfo->depth == 15 && regno > 31) + return -EINVAL; + + /* For 565, the green component is mixed one order + * below + */ + if (rinfo->depth == 16) { + OUTREG(PALETTE_INDEX, pindex>>1); + OUTREG(PALETTE_DATA, + (rinfo->palette[regno>>1].red << 16) | + (green << 8) | + (rinfo->palette[regno>>1].blue)); + green = rinfo->palette[regno<<1].green; + } + } + + if (rinfo->depth != 16 || regno < 32) { + OUTREG(PALETTE_INDEX, pindex); + OUTREG(PALETTE_DATA, (red << 16) | + (green << 8) | blue); + } + } + if (regno < 16) { + u32 *pal = rinfo->info->pseudo_palette; + switch (rinfo->depth) { + case 15: + pal[regno] = (regno << 10) | (regno << 5) | regno; + break; + case 16: + pal[regno] = (regno << 11) | (regno << 5) | regno; + break; + case 24: + pal[regno] = (regno << 16) | (regno << 8) | regno; + break; + case 32: + i = (regno << 8) | regno; + pal[regno] = (i << 16) | i; + break; + } + } + return 0; +} + +static int radeonfb_setcolreg (unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct radeonfb_info *rinfo = info->par; + u32 dac_cntl2, vclk_cntl = 0; + int rc; + + if (!rinfo->asleep) { + if (rinfo->is_mobility) { + vclk_cntl = INPLL(VCLK_ECP_CNTL); + OUTPLL(VCLK_ECP_CNTL, + vclk_cntl & ~PIXCLK_DAC_ALWAYS_ONb); + } + + /* Make sure we are on first palette */ + if (rinfo->has_CRTC2) { + dac_cntl2 = INREG(DAC_CNTL2); + dac_cntl2 &= ~DAC2_PALETTE_ACCESS_CNTL; + OUTREG(DAC_CNTL2, dac_cntl2); + } + } + + rc = radeon_setcolreg (regno, red, green, blue, transp, rinfo); + + if (!rinfo->asleep && rinfo->is_mobility) + OUTPLL(VCLK_ECP_CNTL, vclk_cntl); + + return rc; +} + +static int radeonfb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + struct radeonfb_info *rinfo = info->par; + u16 *red, *green, *blue, *transp; + u32 dac_cntl2, vclk_cntl = 0; + int i, start, rc = 0; + + if (!rinfo->asleep) { + if (rinfo->is_mobility) { + vclk_cntl = INPLL(VCLK_ECP_CNTL); + OUTPLL(VCLK_ECP_CNTL, + vclk_cntl & ~PIXCLK_DAC_ALWAYS_ONb); + } + + /* Make sure we are on first palette */ + if (rinfo->has_CRTC2) { + dac_cntl2 = INREG(DAC_CNTL2); + dac_cntl2 &= ~DAC2_PALETTE_ACCESS_CNTL; + OUTREG(DAC_CNTL2, dac_cntl2); + } + } + + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + start = cmap->start; + + for (i = 0; i < cmap->len; i++) { + u_int hred, hgreen, hblue, htransp = 0xffff; + + hred = *red++; + hgreen = *green++; + hblue = *blue++; + if (transp) + htransp = *transp++; + rc = radeon_setcolreg (start++, hred, hgreen, hblue, htransp, + rinfo); + if (rc) + break; + } + + if (!rinfo->asleep && rinfo->is_mobility) + OUTPLL(VCLK_ECP_CNTL, vclk_cntl); + + return rc; +} + +static void radeon_save_state (struct radeonfb_info *rinfo, + struct radeon_regs *save) +{ + /* CRTC regs */ + save->crtc_gen_cntl = INREG(CRTC_GEN_CNTL); + save->crtc_ext_cntl = INREG(CRTC_EXT_CNTL); + save->crtc_more_cntl = INREG(CRTC_MORE_CNTL); + save->dac_cntl = INREG(DAC_CNTL); + save->crtc_h_total_disp = INREG(CRTC_H_TOTAL_DISP); + save->crtc_h_sync_strt_wid = INREG(CRTC_H_SYNC_STRT_WID); + save->crtc_v_total_disp = INREG(CRTC_V_TOTAL_DISP); + save->crtc_v_sync_strt_wid = INREG(CRTC_V_SYNC_STRT_WID); + save->crtc_pitch = INREG(CRTC_PITCH); + save->surface_cntl = INREG(SURFACE_CNTL); + + /* FP regs */ + save->fp_crtc_h_total_disp = INREG(FP_CRTC_H_TOTAL_DISP); + save->fp_crtc_v_total_disp = INREG(FP_CRTC_V_TOTAL_DISP); + save->fp_gen_cntl = INREG(FP_GEN_CNTL); + save->fp_h_sync_strt_wid = INREG(FP_H_SYNC_STRT_WID); + save->fp_horz_stretch = INREG(FP_HORZ_STRETCH); + save->fp_v_sync_strt_wid = INREG(FP_V_SYNC_STRT_WID); + save->fp_vert_stretch = INREG(FP_VERT_STRETCH); + save->lvds_gen_cntl = INREG(LVDS_GEN_CNTL); + save->lvds_pll_cntl = INREG(LVDS_PLL_CNTL); + save->tmds_crc = INREG(TMDS_CRC); + save->tmds_transmitter_cntl = INREG(TMDS_TRANSMITTER_CNTL); + save->vclk_ecp_cntl = INPLL(VCLK_ECP_CNTL); + + /* PLL regs */ + save->clk_cntl_index = INREG(CLOCK_CNTL_INDEX) & ~0x3f; + radeon_pll_errata_after_index(rinfo); + save->ppll_div_3 = INPLL(PPLL_DIV_3); + save->ppll_ref_div = INPLL(PPLL_REF_DIV); +} + + +static void radeon_write_pll_regs(struct radeonfb_info *rinfo, struct radeon_regs *mode) +{ + int i; + + radeon_fifo_wait(20); + + /* Workaround from XFree */ + if (rinfo->is_mobility) { + /* A temporal workaround for the occasional blanking on certain laptop + * panels. This appears to related to the PLL divider registers + * (fail to lock?). It occurs even when all dividers are the same + * with their old settings. In this case we really don't need to + * fiddle with PLL registers. By doing this we can avoid the blanking + * problem with some panels. + */ + if ((mode->ppll_ref_div == (INPLL(PPLL_REF_DIV) & PPLL_REF_DIV_MASK)) && + (mode->ppll_div_3 == (INPLL(PPLL_DIV_3) & + (PPLL_POST3_DIV_MASK | PPLL_FB3_DIV_MASK)))) { + /* We still have to force a switch to selected PPLL div thanks to + * an XFree86 driver bug which will switch it away in some cases + * even when using UseFDev */ + OUTREGP(CLOCK_CNTL_INDEX, + mode->clk_cntl_index & PPLL_DIV_SEL_MASK, + ~PPLL_DIV_SEL_MASK); + radeon_pll_errata_after_index(rinfo); + radeon_pll_errata_after_data(rinfo); + return; + } + } + + /* Swich VCKL clock input to CPUCLK so it stays fed while PPLL updates*/ + OUTPLLP(VCLK_ECP_CNTL, VCLK_SRC_SEL_CPUCLK, ~VCLK_SRC_SEL_MASK); + + /* Reset PPLL & enable atomic update */ + OUTPLLP(PPLL_CNTL, + PPLL_RESET | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN, + ~(PPLL_RESET | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN)); + + /* Switch to selected PPLL divider */ + OUTREGP(CLOCK_CNTL_INDEX, + mode->clk_cntl_index & PPLL_DIV_SEL_MASK, + ~PPLL_DIV_SEL_MASK); + radeon_pll_errata_after_index(rinfo); + radeon_pll_errata_after_data(rinfo); + + /* Set PPLL ref. div */ + if (IS_R300_VARIANT(rinfo) || + rinfo->family == CHIP_FAMILY_RS300 || + rinfo->family == CHIP_FAMILY_RS400 || + rinfo->family == CHIP_FAMILY_RS480) { + if (mode->ppll_ref_div & R300_PPLL_REF_DIV_ACC_MASK) { + /* When restoring console mode, use saved PPLL_REF_DIV + * setting. + */ + OUTPLLP(PPLL_REF_DIV, mode->ppll_ref_div, 0); + } else { + /* R300 uses ref_div_acc field as real ref divider */ + OUTPLLP(PPLL_REF_DIV, + (mode->ppll_ref_div << R300_PPLL_REF_DIV_ACC_SHIFT), + ~R300_PPLL_REF_DIV_ACC_MASK); + } + } else + OUTPLLP(PPLL_REF_DIV, mode->ppll_ref_div, ~PPLL_REF_DIV_MASK); + + /* Set PPLL divider 3 & post divider*/ + OUTPLLP(PPLL_DIV_3, mode->ppll_div_3, ~PPLL_FB3_DIV_MASK); + OUTPLLP(PPLL_DIV_3, mode->ppll_div_3, ~PPLL_POST3_DIV_MASK); + + /* Write update */ + while (INPLL(PPLL_REF_DIV) & PPLL_ATOMIC_UPDATE_R) + ; + OUTPLLP(PPLL_REF_DIV, PPLL_ATOMIC_UPDATE_W, ~PPLL_ATOMIC_UPDATE_W); + + /* Wait read update complete */ + /* FIXME: Certain revisions of R300 can't recover here. Not sure of + the cause yet, but this workaround will mask the problem for now. + Other chips usually will pass at the very first test, so the + workaround shouldn't have any effect on them. */ + for (i = 0; (i < 10000 && INPLL(PPLL_REF_DIV) & PPLL_ATOMIC_UPDATE_R); i++) + ; + + OUTPLL(HTOTAL_CNTL, 0); + + /* Clear reset & atomic update */ + OUTPLLP(PPLL_CNTL, 0, + ~(PPLL_RESET | PPLL_SLEEP | PPLL_ATOMIC_UPDATE_EN | PPLL_VGA_ATOMIC_UPDATE_EN)); + + /* We may want some locking ... oh well */ + radeon_msleep(5); + + /* Switch back VCLK source to PPLL */ + OUTPLLP(VCLK_ECP_CNTL, VCLK_SRC_SEL_PPLLCLK, ~VCLK_SRC_SEL_MASK); +} + +/* + * Timer function for delayed LVDS panel power up/down + */ +static void radeon_lvds_timer_func(unsigned long data) +{ + struct radeonfb_info *rinfo = (struct radeonfb_info *)data; + + radeon_engine_idle(); + + OUTREG(LVDS_GEN_CNTL, rinfo->pending_lvds_gen_cntl); +} + +/* + * Apply a video mode. This will apply the whole register set, including + * the PLL registers, to the card + */ +void radeon_write_mode (struct radeonfb_info *rinfo, struct radeon_regs *mode, + int regs_only) +{ + int i; + int primary_mon = PRIMARY_MONITOR(rinfo); + + if (nomodeset) + return; + + if (!regs_only) + radeon_screen_blank(rinfo, FB_BLANK_NORMAL, 0); + + radeon_fifo_wait(31); + for (i=0; i<10; i++) + OUTREG(common_regs[i].reg, common_regs[i].val); + + /* Apply surface registers */ + for (i=0; i<8; i++) { + OUTREG(SURFACE0_LOWER_BOUND + 0x10*i, mode->surf_lower_bound[i]); + OUTREG(SURFACE0_UPPER_BOUND + 0x10*i, mode->surf_upper_bound[i]); + OUTREG(SURFACE0_INFO + 0x10*i, mode->surf_info[i]); + } + + OUTREG(CRTC_GEN_CNTL, mode->crtc_gen_cntl); + OUTREGP(CRTC_EXT_CNTL, mode->crtc_ext_cntl, + ~(CRTC_HSYNC_DIS | CRTC_VSYNC_DIS | CRTC_DISPLAY_DIS)); + OUTREG(CRTC_MORE_CNTL, mode->crtc_more_cntl); + OUTREGP(DAC_CNTL, mode->dac_cntl, DAC_RANGE_CNTL | DAC_BLANKING); + OUTREG(CRTC_H_TOTAL_DISP, mode->crtc_h_total_disp); + OUTREG(CRTC_H_SYNC_STRT_WID, mode->crtc_h_sync_strt_wid); + OUTREG(CRTC_V_TOTAL_DISP, mode->crtc_v_total_disp); + OUTREG(CRTC_V_SYNC_STRT_WID, mode->crtc_v_sync_strt_wid); + OUTREG(CRTC_OFFSET, 0); + OUTREG(CRTC_OFFSET_CNTL, 0); + OUTREG(CRTC_PITCH, mode->crtc_pitch); + OUTREG(SURFACE_CNTL, mode->surface_cntl); + + radeon_write_pll_regs(rinfo, mode); + + if ((primary_mon == MT_DFP) || (primary_mon == MT_LCD)) { + radeon_fifo_wait(10); + OUTREG(FP_CRTC_H_TOTAL_DISP, mode->fp_crtc_h_total_disp); + OUTREG(FP_CRTC_V_TOTAL_DISP, mode->fp_crtc_v_total_disp); + OUTREG(FP_H_SYNC_STRT_WID, mode->fp_h_sync_strt_wid); + OUTREG(FP_V_SYNC_STRT_WID, mode->fp_v_sync_strt_wid); + OUTREG(FP_HORZ_STRETCH, mode->fp_horz_stretch); + OUTREG(FP_VERT_STRETCH, mode->fp_vert_stretch); + OUTREG(FP_GEN_CNTL, mode->fp_gen_cntl); + OUTREG(TMDS_CRC, mode->tmds_crc); + OUTREG(TMDS_TRANSMITTER_CNTL, mode->tmds_transmitter_cntl); + } + + if (!regs_only) + radeon_screen_blank(rinfo, FB_BLANK_UNBLANK, 0); + + radeon_fifo_wait(2); + OUTPLL(VCLK_ECP_CNTL, mode->vclk_ecp_cntl); + + return; +} + +/* + * Calculate the PLL values for a given mode + */ +static void radeon_calc_pll_regs(struct radeonfb_info *rinfo, struct radeon_regs *regs, + unsigned long freq) +{ + const struct { + int divider; + int bitvalue; + } *post_div, + post_divs[] = { + { 1, 0 }, + { 2, 1 }, + { 4, 2 }, + { 8, 3 }, + { 3, 4 }, + { 16, 5 }, + { 6, 6 }, + { 12, 7 }, + { 0, 0 }, + }; + int fb_div, pll_output_freq = 0; + int uses_dvo = 0; + + /* Check if the DVO port is enabled and sourced from the primary CRTC. I'm + * not sure which model starts having FP2_GEN_CNTL, I assume anything more + * recent than an r(v)100... + */ +#if 1 + /* XXX I had reports of flicker happening with the cinema display + * on TMDS1 that seem to be fixed if I also forbit odd dividers in + * this case. This could just be a bandwidth calculation issue, I + * haven't implemented the bandwidth code yet, but in the meantime, + * forcing uses_dvo to 1 fixes it and shouln't have bad side effects, + * I haven't seen a case were were absolutely needed an odd PLL + * divider. I'll find a better fix once I have more infos on the + * real cause of the problem. + */ + while (rinfo->has_CRTC2) { + u32 fp2_gen_cntl = INREG(FP2_GEN_CNTL); + u32 disp_output_cntl; + int source; + + /* FP2 path not enabled */ + if ((fp2_gen_cntl & FP2_ON) == 0) + break; + /* Not all chip revs have the same format for this register, + * extract the source selection + */ + if (rinfo->family == CHIP_FAMILY_R200 || IS_R300_VARIANT(rinfo)) { + source = (fp2_gen_cntl >> 10) & 0x3; + /* sourced from transform unit, check for transform unit + * own source + */ + if (source == 3) { + disp_output_cntl = INREG(DISP_OUTPUT_CNTL); + source = (disp_output_cntl >> 12) & 0x3; + } + } else + source = (fp2_gen_cntl >> 13) & 0x1; + /* sourced from CRTC2 -> exit */ + if (source == 1) + break; + + /* so we end up on CRTC1, let's set uses_dvo to 1 now */ + uses_dvo = 1; + break; + } +#else + uses_dvo = 1; +#endif + if (freq > rinfo->pll.ppll_max) + freq = rinfo->pll.ppll_max; + if (freq*12 < rinfo->pll.ppll_min) + freq = rinfo->pll.ppll_min / 12; + pr_debug("freq = %lu, PLL min = %u, PLL max = %u\n", + freq, rinfo->pll.ppll_min, rinfo->pll.ppll_max); + + for (post_div = &post_divs[0]; post_div->divider; ++post_div) { + pll_output_freq = post_div->divider * freq; + /* If we output to the DVO port (external TMDS), we don't allow an + * odd PLL divider as those aren't supported on this path + */ + if (uses_dvo && (post_div->divider & 1)) + continue; + if (pll_output_freq >= rinfo->pll.ppll_min && + pll_output_freq <= rinfo->pll.ppll_max) + break; + } + + /* If we fall through the bottom, try the "default value" + given by the terminal post_div->bitvalue */ + if ( !post_div->divider ) { + post_div = &post_divs[post_div->bitvalue]; + pll_output_freq = post_div->divider * freq; + } + pr_debug("ref_div = %d, ref_clk = %d, output_freq = %d\n", + rinfo->pll.ref_div, rinfo->pll.ref_clk, + pll_output_freq); + + /* If we fall through the bottom, try the "default value" + given by the terminal post_div->bitvalue */ + if ( !post_div->divider ) { + post_div = &post_divs[post_div->bitvalue]; + pll_output_freq = post_div->divider * freq; + } + pr_debug("ref_div = %d, ref_clk = %d, output_freq = %d\n", + rinfo->pll.ref_div, rinfo->pll.ref_clk, + pll_output_freq); + + fb_div = round_div(rinfo->pll.ref_div*pll_output_freq, + rinfo->pll.ref_clk); + regs->ppll_ref_div = rinfo->pll.ref_div; + regs->ppll_div_3 = fb_div | (post_div->bitvalue << 16); + + pr_debug("post div = 0x%x\n", post_div->bitvalue); + pr_debug("fb_div = 0x%x\n", fb_div); + pr_debug("ppll_div_3 = 0x%x\n", regs->ppll_div_3); +} + +static int radeonfb_set_par(struct fb_info *info) +{ + struct radeonfb_info *rinfo = info->par; + struct fb_var_screeninfo *mode = &info->var; + struct radeon_regs *newmode; + int hTotal, vTotal, hSyncStart, hSyncEnd, + hSyncPol, vSyncStart, vSyncEnd, vSyncPol, cSync; + u8 hsync_adj_tab[] = {0, 0x12, 9, 9, 6, 5}; + u8 hsync_fudge_fp[] = {2, 2, 0, 0, 5, 5}; + u32 sync, h_sync_pol, v_sync_pol, dotClock, pixClock; + int i, freq; + int format = 0; + int nopllcalc = 0; + int hsync_start, hsync_fudge, bytpp, hsync_wid, vsync_wid; + int primary_mon = PRIMARY_MONITOR(rinfo); + int depth = var_to_depth(mode); + int use_rmx = 0; + + newmode = kmalloc(sizeof(struct radeon_regs), GFP_KERNEL); + if (!newmode) + return -ENOMEM; + + /* We always want engine to be idle on a mode switch, even + * if we won't actually change the mode + */ + radeon_engine_idle(); + + hSyncStart = mode->xres + mode->right_margin; + hSyncEnd = hSyncStart + mode->hsync_len; + hTotal = hSyncEnd + mode->left_margin; + + vSyncStart = mode->yres + mode->lower_margin; + vSyncEnd = vSyncStart + mode->vsync_len; + vTotal = vSyncEnd + mode->upper_margin; + pixClock = mode->pixclock; + + sync = mode->sync; + h_sync_pol = sync & FB_SYNC_HOR_HIGH_ACT ? 0 : 1; + v_sync_pol = sync & FB_SYNC_VERT_HIGH_ACT ? 0 : 1; + + if (primary_mon == MT_DFP || primary_mon == MT_LCD) { + if (rinfo->panel_info.xres < mode->xres) + mode->xres = rinfo->panel_info.xres; + if (rinfo->panel_info.yres < mode->yres) + mode->yres = rinfo->panel_info.yres; + + hTotal = mode->xres + rinfo->panel_info.hblank; + hSyncStart = mode->xres + rinfo->panel_info.hOver_plus; + hSyncEnd = hSyncStart + rinfo->panel_info.hSync_width; + + vTotal = mode->yres + rinfo->panel_info.vblank; + vSyncStart = mode->yres + rinfo->panel_info.vOver_plus; + vSyncEnd = vSyncStart + rinfo->panel_info.vSync_width; + + h_sync_pol = !rinfo->panel_info.hAct_high; + v_sync_pol = !rinfo->panel_info.vAct_high; + + pixClock = 100000000 / rinfo->panel_info.clock; + + if (rinfo->panel_info.use_bios_dividers) { + nopllcalc = 1; + newmode->ppll_div_3 = rinfo->panel_info.fbk_divider | + (rinfo->panel_info.post_divider << 16); + newmode->ppll_ref_div = rinfo->panel_info.ref_divider; + } + } + dotClock = 1000000000 / pixClock; + freq = dotClock / 10; /* x100 */ + + pr_debug("hStart = %d, hEnd = %d, hTotal = %d\n", + hSyncStart, hSyncEnd, hTotal); + pr_debug("vStart = %d, vEnd = %d, vTotal = %d\n", + vSyncStart, vSyncEnd, vTotal); + + hsync_wid = (hSyncEnd - hSyncStart) / 8; + vsync_wid = vSyncEnd - vSyncStart; + if (hsync_wid == 0) + hsync_wid = 1; + else if (hsync_wid > 0x3f) /* max */ + hsync_wid = 0x3f; + + if (vsync_wid == 0) + vsync_wid = 1; + else if (vsync_wid > 0x1f) /* max */ + vsync_wid = 0x1f; + + hSyncPol = mode->sync & FB_SYNC_HOR_HIGH_ACT ? 0 : 1; + vSyncPol = mode->sync & FB_SYNC_VERT_HIGH_ACT ? 0 : 1; + + cSync = mode->sync & FB_SYNC_COMP_HIGH_ACT ? (1 << 4) : 0; + + format = radeon_get_dstbpp(depth); + bytpp = mode->bits_per_pixel >> 3; + + if ((primary_mon == MT_DFP) || (primary_mon == MT_LCD)) + hsync_fudge = hsync_fudge_fp[format-1]; + else + hsync_fudge = hsync_adj_tab[format-1]; + + hsync_start = hSyncStart - 8 + hsync_fudge; + + newmode->crtc_gen_cntl = CRTC_EXT_DISP_EN | CRTC_EN | + (format << 8); + + /* Clear auto-center etc... */ + newmode->crtc_more_cntl = rinfo->init_state.crtc_more_cntl; + newmode->crtc_more_cntl &= 0xfffffff0; + + if ((primary_mon == MT_DFP) || (primary_mon == MT_LCD)) { + newmode->crtc_ext_cntl = VGA_ATI_LINEAR | XCRT_CNT_EN; + if (mirror) + newmode->crtc_ext_cntl |= CRTC_CRT_ON; + + newmode->crtc_gen_cntl &= ~(CRTC_DBL_SCAN_EN | + CRTC_INTERLACE_EN); + } else { + newmode->crtc_ext_cntl = VGA_ATI_LINEAR | XCRT_CNT_EN | + CRTC_CRT_ON; + } + + newmode->dac_cntl = /* INREG(DAC_CNTL) | */ DAC_MASK_ALL | DAC_VGA_ADR_EN | + DAC_8BIT_EN; + + newmode->crtc_h_total_disp = ((((hTotal / 8) - 1) & 0x3ff) | + (((mode->xres / 8) - 1) << 16)); + + newmode->crtc_h_sync_strt_wid = ((hsync_start & 0x1fff) | + (hsync_wid << 16) | (h_sync_pol << 23)); + + newmode->crtc_v_total_disp = ((vTotal - 1) & 0xffff) | + ((mode->yres - 1) << 16); + + newmode->crtc_v_sync_strt_wid = (((vSyncStart - 1) & 0xfff) | + (vsync_wid << 16) | (v_sync_pol << 23)); + + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) { + /* We first calculate the engine pitch */ + rinfo->pitch = ((mode->xres_virtual * ((mode->bits_per_pixel + 1) / 8) + 0x3f) + & ~(0x3f)) >> 6; + + /* Then, re-multiply it to get the CRTC pitch */ + newmode->crtc_pitch = (rinfo->pitch << 3) / ((mode->bits_per_pixel + 1) / 8); + } else + newmode->crtc_pitch = (mode->xres_virtual >> 3); + + newmode->crtc_pitch |= (newmode->crtc_pitch << 16); + + /* + * It looks like recent chips have a problem with SURFACE_CNTL, + * setting SURF_TRANSLATION_DIS completely disables the + * swapper as well, so we leave it unset now. + */ + newmode->surface_cntl = 0; + +#if defined(__BIG_ENDIAN) + + /* Setup swapping on both apertures, though we currently + * only use aperture 0, enabling swapper on aperture 1 + * won't harm + */ + switch (mode->bits_per_pixel) { + case 16: + newmode->surface_cntl |= NONSURF_AP0_SWP_16BPP; + newmode->surface_cntl |= NONSURF_AP1_SWP_16BPP; + break; + case 24: + case 32: + newmode->surface_cntl |= NONSURF_AP0_SWP_32BPP; + newmode->surface_cntl |= NONSURF_AP1_SWP_32BPP; + break; + } +#endif + + /* Clear surface registers */ + for (i=0; i<8; i++) { + newmode->surf_lower_bound[i] = 0; + newmode->surf_upper_bound[i] = 0x1f; + newmode->surf_info[i] = 0; + } + + pr_debug("h_total_disp = 0x%x\t hsync_strt_wid = 0x%x\n", + newmode->crtc_h_total_disp, newmode->crtc_h_sync_strt_wid); + pr_debug("v_total_disp = 0x%x\t vsync_strt_wid = 0x%x\n", + newmode->crtc_v_total_disp, newmode->crtc_v_sync_strt_wid); + + rinfo->bpp = mode->bits_per_pixel; + rinfo->depth = depth; + + pr_debug("pixclock = %lu\n", (unsigned long)pixClock); + pr_debug("freq = %lu\n", (unsigned long)freq); + + /* We use PPLL_DIV_3 */ + newmode->clk_cntl_index = 0x300; + + /* Calculate PPLL value if necessary */ + if (!nopllcalc) + radeon_calc_pll_regs(rinfo, newmode, freq); + + newmode->vclk_ecp_cntl = rinfo->init_state.vclk_ecp_cntl; + + if ((primary_mon == MT_DFP) || (primary_mon == MT_LCD)) { + unsigned int hRatio, vRatio; + + if (mode->xres > rinfo->panel_info.xres) + mode->xres = rinfo->panel_info.xres; + if (mode->yres > rinfo->panel_info.yres) + mode->yres = rinfo->panel_info.yres; + + newmode->fp_horz_stretch = (((rinfo->panel_info.xres / 8) - 1) + << HORZ_PANEL_SHIFT); + newmode->fp_vert_stretch = ((rinfo->panel_info.yres - 1) + << VERT_PANEL_SHIFT); + + if (mode->xres != rinfo->panel_info.xres) { + hRatio = round_div(mode->xres * HORZ_STRETCH_RATIO_MAX, + rinfo->panel_info.xres); + newmode->fp_horz_stretch = (((((unsigned long)hRatio) & HORZ_STRETCH_RATIO_MASK)) | + (newmode->fp_horz_stretch & + (HORZ_PANEL_SIZE | HORZ_FP_LOOP_STRETCH | + HORZ_AUTO_RATIO_INC))); + newmode->fp_horz_stretch |= (HORZ_STRETCH_BLEND | + HORZ_STRETCH_ENABLE); + use_rmx = 1; + } + newmode->fp_horz_stretch &= ~HORZ_AUTO_RATIO; + + if (mode->yres != rinfo->panel_info.yres) { + vRatio = round_div(mode->yres * VERT_STRETCH_RATIO_MAX, + rinfo->panel_info.yres); + newmode->fp_vert_stretch = (((((unsigned long)vRatio) & VERT_STRETCH_RATIO_MASK)) | + (newmode->fp_vert_stretch & + (VERT_PANEL_SIZE | VERT_STRETCH_RESERVED))); + newmode->fp_vert_stretch |= (VERT_STRETCH_BLEND | + VERT_STRETCH_ENABLE); + use_rmx = 1; + } + newmode->fp_vert_stretch &= ~VERT_AUTO_RATIO_EN; + + newmode->fp_gen_cntl = (rinfo->init_state.fp_gen_cntl & (u32) + ~(FP_SEL_CRTC2 | + FP_RMX_HVSYNC_CONTROL_EN | + FP_DFP_SYNC_SEL | + FP_CRT_SYNC_SEL | + FP_CRTC_LOCK_8DOT | + FP_USE_SHADOW_EN | + FP_CRTC_USE_SHADOW_VEND | + FP_CRT_SYNC_ALT)); + + newmode->fp_gen_cntl |= (FP_CRTC_DONT_SHADOW_VPAR | + FP_CRTC_DONT_SHADOW_HEND | + FP_PANEL_FORMAT); + + if (IS_R300_VARIANT(rinfo) || + (rinfo->family == CHIP_FAMILY_R200)) { + newmode->fp_gen_cntl &= ~R200_FP_SOURCE_SEL_MASK; + if (use_rmx) + newmode->fp_gen_cntl |= R200_FP_SOURCE_SEL_RMX; + else + newmode->fp_gen_cntl |= R200_FP_SOURCE_SEL_CRTC1; + } else + newmode->fp_gen_cntl |= FP_SEL_CRTC1; + + newmode->lvds_gen_cntl = rinfo->init_state.lvds_gen_cntl; + newmode->lvds_pll_cntl = rinfo->init_state.lvds_pll_cntl; + newmode->tmds_crc = rinfo->init_state.tmds_crc; + newmode->tmds_transmitter_cntl = rinfo->init_state.tmds_transmitter_cntl; + + if (primary_mon == MT_LCD) { + newmode->lvds_gen_cntl |= (LVDS_ON | LVDS_BLON); + newmode->fp_gen_cntl &= ~(FP_FPON | FP_TMDS_EN); + } else { + /* DFP */ + newmode->fp_gen_cntl |= (FP_FPON | FP_TMDS_EN); + newmode->tmds_transmitter_cntl &= ~(TMDS_PLLRST); + /* TMDS_PLL_EN bit is reversed on RV (and mobility) chips */ + if (IS_R300_VARIANT(rinfo) || + (rinfo->family == CHIP_FAMILY_R200) || !rinfo->has_CRTC2) + newmode->tmds_transmitter_cntl &= ~TMDS_PLL_EN; + else + newmode->tmds_transmitter_cntl |= TMDS_PLL_EN; + newmode->crtc_ext_cntl &= ~CRTC_CRT_ON; + } + + newmode->fp_crtc_h_total_disp = (((rinfo->panel_info.hblank / 8) & 0x3ff) | + (((mode->xres / 8) - 1) << 16)); + newmode->fp_crtc_v_total_disp = (rinfo->panel_info.vblank & 0xffff) | + ((mode->yres - 1) << 16); + newmode->fp_h_sync_strt_wid = ((rinfo->panel_info.hOver_plus & 0x1fff) | + (hsync_wid << 16) | (h_sync_pol << 23)); + newmode->fp_v_sync_strt_wid = ((rinfo->panel_info.vOver_plus & 0xfff) | + (vsync_wid << 16) | (v_sync_pol << 23)); + } + + /* do it! */ + if (!rinfo->asleep) { + memcpy(&rinfo->state, newmode, sizeof(*newmode)); + radeon_write_mode (rinfo, newmode, 0); + /* (re)initialize the engine */ + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) + radeonfb_engine_init (rinfo); + } + /* Update fix */ + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) + info->fix.line_length = rinfo->pitch*64; + else + info->fix.line_length = mode->xres_virtual + * ((mode->bits_per_pixel + 1) / 8); + info->fix.visual = rinfo->depth == 8 ? FB_VISUAL_PSEUDOCOLOR + : FB_VISUAL_DIRECTCOLOR; + +#ifdef CONFIG_BOOTX_TEXT + /* Update debug text engine */ + btext_update_display(rinfo->fb_base_phys, mode->xres, mode->yres, + rinfo->depth, info->fix.line_length); +#endif + + kfree(newmode); + return 0; +} + + +static struct fb_ops radeonfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = radeonfb_check_var, + .fb_set_par = radeonfb_set_par, + .fb_setcolreg = radeonfb_setcolreg, + .fb_setcmap = radeonfb_setcmap, + .fb_pan_display = radeonfb_pan_display, + .fb_blank = radeonfb_blank, + .fb_ioctl = radeonfb_ioctl, + .fb_sync = radeonfb_sync, + .fb_fillrect = radeonfb_fillrect, + .fb_copyarea = radeonfb_copyarea, + .fb_imageblit = radeonfb_imageblit, +}; + + +static int radeon_set_fbinfo(struct radeonfb_info *rinfo) +{ + struct fb_info *info = rinfo->info; + + info->par = rinfo; + info->pseudo_palette = rinfo->pseudo_palette; + info->flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_XPAN + | FBINFO_HWACCEL_YPAN; + info->fbops = &radeonfb_ops; + info->screen_base = rinfo->fb_base; + info->screen_size = rinfo->mapped_vram; + /* Fill fix common fields */ + strlcpy(info->fix.id, rinfo->name, sizeof(info->fix.id)); + info->fix.smem_start = rinfo->fb_base_phys; + info->fix.smem_len = rinfo->video_ram; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.xpanstep = 8; + info->fix.ypanstep = 1; + info->fix.ywrapstep = 0; + info->fix.type_aux = 0; + info->fix.mmio_start = rinfo->mmio_base_phys; + info->fix.mmio_len = RADEON_REGSIZE; + info->fix.accel = FB_ACCEL_ATI_RADEON; + + fb_alloc_cmap(&info->cmap, 256, 0); + + if (noaccel) + info->flags |= FBINFO_HWACCEL_DISABLED; + + return 0; +} + +/* + * This reconfigure the card's internal memory map. In theory, we'd like + * to setup the card's memory at the same address as it's PCI bus address, + * and the AGP aperture right after that so that system RAM on 32 bits + * machines at least, is directly accessible. However, doing so would + * conflict with the current XFree drivers... + * Ultimately, I hope XFree, GATOS and ATI binary drivers will all agree + * on the proper way to set this up and duplicate this here. In the meantime, + * I put the card's memory at 0 in card space and AGP at some random high + * local (0xe0000000 for now) that will be changed by XFree/DRI anyway + */ +#ifdef CONFIG_PPC_OF +#undef SET_MC_FB_FROM_APERTURE +static void fixup_memory_mappings(struct radeonfb_info *rinfo) +{ + u32 save_crtc_gen_cntl, save_crtc2_gen_cntl = 0; + u32 save_crtc_ext_cntl; + u32 aper_base, aper_size; + u32 agp_base; + + /* First, we disable display to avoid interfering */ + if (rinfo->has_CRTC2) { + save_crtc2_gen_cntl = INREG(CRTC2_GEN_CNTL); + OUTREG(CRTC2_GEN_CNTL, save_crtc2_gen_cntl | CRTC2_DISP_REQ_EN_B); + } + save_crtc_gen_cntl = INREG(CRTC_GEN_CNTL); + save_crtc_ext_cntl = INREG(CRTC_EXT_CNTL); + + OUTREG(CRTC_EXT_CNTL, save_crtc_ext_cntl | CRTC_DISPLAY_DIS); + OUTREG(CRTC_GEN_CNTL, save_crtc_gen_cntl | CRTC_DISP_REQ_EN_B); + mdelay(100); + + aper_base = INREG(CNFG_APER_0_BASE); + aper_size = INREG(CNFG_APER_SIZE); + +#ifdef SET_MC_FB_FROM_APERTURE + /* Set framebuffer to be at the same address as set in PCI BAR */ + OUTREG(MC_FB_LOCATION, + ((aper_base + aper_size - 1) & 0xffff0000) | (aper_base >> 16)); + rinfo->fb_local_base = aper_base; +#else + OUTREG(MC_FB_LOCATION, 0x7fff0000); + rinfo->fb_local_base = 0; +#endif + agp_base = aper_base + aper_size; + if (agp_base & 0xf0000000) + agp_base = (aper_base | 0x0fffffff) + 1; + + /* Set AGP to be just after the framebuffer on a 256Mb boundary. This + * assumes the FB isn't mapped to 0xf0000000 or above, but this is + * always the case on PPCs afaik. + */ +#ifdef SET_MC_FB_FROM_APERTURE + OUTREG(MC_AGP_LOCATION, 0xffff0000 | (agp_base >> 16)); +#else + OUTREG(MC_AGP_LOCATION, 0xffffe000); +#endif + + /* Fixup the display base addresses & engine offsets while we + * are at it as well + */ +#ifdef SET_MC_FB_FROM_APERTURE + OUTREG(DISPLAY_BASE_ADDR, aper_base); + if (rinfo->has_CRTC2) + OUTREG(CRTC2_DISPLAY_BASE_ADDR, aper_base); + OUTREG(OV0_BASE_ADDR, aper_base); +#else + OUTREG(DISPLAY_BASE_ADDR, 0); + if (rinfo->has_CRTC2) + OUTREG(CRTC2_DISPLAY_BASE_ADDR, 0); + OUTREG(OV0_BASE_ADDR, 0); +#endif + mdelay(100); + + /* Restore display settings */ + OUTREG(CRTC_GEN_CNTL, save_crtc_gen_cntl); + OUTREG(CRTC_EXT_CNTL, save_crtc_ext_cntl); + if (rinfo->has_CRTC2) + OUTREG(CRTC2_GEN_CNTL, save_crtc2_gen_cntl); + + pr_debug("aper_base: %08x MC_FB_LOC to: %08x, MC_AGP_LOC to: %08x\n", + aper_base, + ((aper_base + aper_size - 1) & 0xffff0000) | (aper_base >> 16), + 0xffff0000 | (agp_base >> 16)); +} +#endif /* CONFIG_PPC_OF */ + + +static void radeon_identify_vram(struct radeonfb_info *rinfo) +{ + u32 tmp; + + /* framebuffer size */ + if ((rinfo->family == CHIP_FAMILY_RS100) || + (rinfo->family == CHIP_FAMILY_RS200) || + (rinfo->family == CHIP_FAMILY_RS300) || + (rinfo->family == CHIP_FAMILY_RC410) || + (rinfo->family == CHIP_FAMILY_RS400) || + (rinfo->family == CHIP_FAMILY_RS480) ) { + u32 tom = INREG(NB_TOM); + tmp = ((((tom >> 16) - (tom & 0xffff) + 1) << 6) * 1024); + + radeon_fifo_wait(6); + OUTREG(MC_FB_LOCATION, tom); + OUTREG(DISPLAY_BASE_ADDR, (tom & 0xffff) << 16); + OUTREG(CRTC2_DISPLAY_BASE_ADDR, (tom & 0xffff) << 16); + OUTREG(OV0_BASE_ADDR, (tom & 0xffff) << 16); + + /* This is supposed to fix the crtc2 noise problem. */ + OUTREG(GRPH2_BUFFER_CNTL, INREG(GRPH2_BUFFER_CNTL) & ~0x7f0000); + + if ((rinfo->family == CHIP_FAMILY_RS100) || + (rinfo->family == CHIP_FAMILY_RS200)) { + /* This is to workaround the asic bug for RMX, some versions + of BIOS doesn't have this register initialized correctly. + */ + OUTREGP(CRTC_MORE_CNTL, CRTC_H_CUTOFF_ACTIVE_EN, + ~CRTC_H_CUTOFF_ACTIVE_EN); + } + } else { + tmp = INREG(CNFG_MEMSIZE); + } + + /* mem size is bits [28:0], mask off the rest */ + rinfo->video_ram = tmp & CNFG_MEMSIZE_MASK; + + /* + * Hack to get around some busted production M6's + * reporting no ram + */ + if (rinfo->video_ram == 0) { + switch (rinfo->pdev->device) { + case PCI_CHIP_RADEON_LY: + case PCI_CHIP_RADEON_LZ: + rinfo->video_ram = 8192 * 1024; + break; + default: + break; + } + } + + + /* + * Now try to identify VRAM type + */ + if (rinfo->is_IGP || (rinfo->family >= CHIP_FAMILY_R300) || + (INREG(MEM_SDRAM_MODE_REG) & (1<<30))) + rinfo->vram_ddr = 1; + else + rinfo->vram_ddr = 0; + + tmp = INREG(MEM_CNTL); + if (IS_R300_VARIANT(rinfo)) { + tmp &= R300_MEM_NUM_CHANNELS_MASK; + switch (tmp) { + case 0: rinfo->vram_width = 64; break; + case 1: rinfo->vram_width = 128; break; + case 2: rinfo->vram_width = 256; break; + default: rinfo->vram_width = 128; break; + } + } else if ((rinfo->family == CHIP_FAMILY_RV100) || + (rinfo->family == CHIP_FAMILY_RS100) || + (rinfo->family == CHIP_FAMILY_RS200)){ + if (tmp & RV100_MEM_HALF_MODE) + rinfo->vram_width = 32; + else + rinfo->vram_width = 64; + } else { + if (tmp & MEM_NUM_CHANNELS_MASK) + rinfo->vram_width = 128; + else + rinfo->vram_width = 64; + } + + /* This may not be correct, as some cards can have half of channel disabled + * ToDo: identify these cases + */ + + pr_debug("radeonfb (%s): Found %ldk of %s %d bits wide videoram\n", + pci_name(rinfo->pdev), + rinfo->video_ram / 1024, + rinfo->vram_ddr ? "DDR" : "SDRAM", + rinfo->vram_width); +} + +/* + * Sysfs + */ + +static ssize_t radeon_show_one_edid(char *buf, loff_t off, size_t count, const u8 *edid) +{ + return memory_read_from_buffer(buf, count, &off, edid, EDID_LENGTH); +} + + +static ssize_t radeon_show_edid1(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pci_dev *pdev = to_pci_dev(dev); + struct fb_info *info = pci_get_drvdata(pdev); + struct radeonfb_info *rinfo = info->par; + + return radeon_show_one_edid(buf, off, count, rinfo->mon1_EDID); +} + + +static ssize_t radeon_show_edid2(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pci_dev *pdev = to_pci_dev(dev); + struct fb_info *info = pci_get_drvdata(pdev); + struct radeonfb_info *rinfo = info->par; + + return radeon_show_one_edid(buf, off, count, rinfo->mon2_EDID); +} + +static struct bin_attribute edid1_attr = { + .attr = { + .name = "edid1", + .mode = 0444, + }, + .size = EDID_LENGTH, + .read = radeon_show_edid1, +}; + +static struct bin_attribute edid2_attr = { + .attr = { + .name = "edid2", + .mode = 0444, + }, + .size = EDID_LENGTH, + .read = radeon_show_edid2, +}; + + +static int radeonfb_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct fb_info *info; + struct radeonfb_info *rinfo; + int ret; + unsigned char c1, c2; + int err = 0; + + pr_debug("radeonfb_pci_register BEGIN\n"); + + /* Enable device in PCI config */ + ret = pci_enable_device(pdev); + if (ret < 0) { + printk(KERN_ERR "radeonfb (%s): Cannot enable PCI device\n", + pci_name(pdev)); + goto err_out; + } + + info = framebuffer_alloc(sizeof(struct radeonfb_info), &pdev->dev); + if (!info) { + printk (KERN_ERR "radeonfb (%s): could not allocate memory\n", + pci_name(pdev)); + ret = -ENOMEM; + goto err_disable; + } + rinfo = info->par; + rinfo->info = info; + rinfo->pdev = pdev; + + spin_lock_init(&rinfo->reg_lock); + init_timer(&rinfo->lvds_timer); + rinfo->lvds_timer.function = radeon_lvds_timer_func; + rinfo->lvds_timer.data = (unsigned long)rinfo; + + c1 = ent->device >> 8; + c2 = ent->device & 0xff; + if (isprint(c1) && isprint(c2)) + snprintf(rinfo->name, sizeof(rinfo->name), + "ATI Radeon %x \"%c%c\"", ent->device & 0xffff, c1, c2); + else + snprintf(rinfo->name, sizeof(rinfo->name), + "ATI Radeon %x", ent->device & 0xffff); + + rinfo->family = ent->driver_data & CHIP_FAMILY_MASK; + rinfo->chipset = pdev->device; + rinfo->has_CRTC2 = (ent->driver_data & CHIP_HAS_CRTC2) != 0; + rinfo->is_mobility = (ent->driver_data & CHIP_IS_MOBILITY) != 0; + rinfo->is_IGP = (ent->driver_data & CHIP_IS_IGP) != 0; + + /* Set base addrs */ + rinfo->fb_base_phys = pci_resource_start (pdev, 0); + rinfo->mmio_base_phys = pci_resource_start (pdev, 2); + + /* request the mem regions */ + ret = pci_request_region(pdev, 0, "radeonfb framebuffer"); + if (ret < 0) { + printk( KERN_ERR "radeonfb (%s): cannot request region 0.\n", + pci_name(rinfo->pdev)); + goto err_release_fb; + } + + ret = pci_request_region(pdev, 2, "radeonfb mmio"); + if (ret < 0) { + printk( KERN_ERR "radeonfb (%s): cannot request region 2.\n", + pci_name(rinfo->pdev)); + goto err_release_pci0; + } + + /* map the regions */ + rinfo->mmio_base = ioremap(rinfo->mmio_base_phys, RADEON_REGSIZE); + if (!rinfo->mmio_base) { + printk(KERN_ERR "radeonfb (%s): cannot map MMIO\n", + pci_name(rinfo->pdev)); + ret = -EIO; + goto err_release_pci2; + } + + rinfo->fb_local_base = INREG(MC_FB_LOCATION) << 16; + + /* + * Check for errata + */ + rinfo->errata = 0; + if (rinfo->family == CHIP_FAMILY_R300 && + (INREG(CNFG_CNTL) & CFG_ATI_REV_ID_MASK) + == CFG_ATI_REV_A11) + rinfo->errata |= CHIP_ERRATA_R300_CG; + + if (rinfo->family == CHIP_FAMILY_RV200 || + rinfo->family == CHIP_FAMILY_RS200) + rinfo->errata |= CHIP_ERRATA_PLL_DUMMYREADS; + + if (rinfo->family == CHIP_FAMILY_RV100 || + rinfo->family == CHIP_FAMILY_RS100 || + rinfo->family == CHIP_FAMILY_RS200) + rinfo->errata |= CHIP_ERRATA_PLL_DELAY; + +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) + /* On PPC, we obtain the OF device-node pointer to the firmware + * data for this chip + */ + rinfo->of_node = pci_device_to_OF_node(pdev); + if (rinfo->of_node == NULL) + printk(KERN_WARNING "radeonfb (%s): Cannot match card to OF node !\n", + pci_name(rinfo->pdev)); + +#endif /* CONFIG_PPC_OF || CONFIG_SPARC */ +#ifdef CONFIG_PPC_OF + /* On PPC, the firmware sets up a memory mapping that tends + * to cause lockups when enabling the engine. We reconfigure + * the card internal memory mappings properly + */ + fixup_memory_mappings(rinfo); +#endif /* CONFIG_PPC_OF */ + + /* Get VRAM size and type */ + radeon_identify_vram(rinfo); + + rinfo->mapped_vram = min_t(unsigned long, MAX_MAPPED_VRAM, rinfo->video_ram); + + do { + rinfo->fb_base = ioremap (rinfo->fb_base_phys, + rinfo->mapped_vram); + } while (rinfo->fb_base == NULL && + ((rinfo->mapped_vram /= 2) >= MIN_MAPPED_VRAM)); + + if (rinfo->fb_base == NULL) { + printk (KERN_ERR "radeonfb (%s): cannot map FB\n", + pci_name(rinfo->pdev)); + ret = -EIO; + goto err_unmap_rom; + } + + pr_debug("radeonfb (%s): mapped %ldk videoram\n", pci_name(rinfo->pdev), + rinfo->mapped_vram/1024); + + /* + * Map the BIOS ROM if any and retrieve PLL parameters from + * the BIOS. We skip that on mobility chips as the real panel + * values we need aren't in the ROM but in the BIOS image in + * memory. This is definitely not the best meacnism though, + * we really need the arch code to tell us which is the "primary" + * video adapter to use the memory image (or better, the arch + * should provide us a copy of the BIOS image to shield us from + * archs who would store that elsewhere and/or could initialize + * more than one adapter during boot). + */ + if (!rinfo->is_mobility) + radeon_map_ROM(rinfo, pdev); + + /* + * On x86, the primary display on laptop may have it's BIOS + * ROM elsewhere, try to locate it at the legacy memory hole. + * We probably need to make sure this is the primary display, + * but that is difficult without some arch support. + */ +#ifdef CONFIG_X86 + if (rinfo->bios_seg == NULL) + radeon_find_mem_vbios(rinfo); +#endif + + /* If both above failed, try the BIOS ROM again for mobility + * chips + */ + if (rinfo->bios_seg == NULL && rinfo->is_mobility) + radeon_map_ROM(rinfo, pdev); + + /* Get informations about the board's PLL */ + radeon_get_pllinfo(rinfo); + +#ifdef CONFIG_FB_RADEON_I2C + /* Register I2C bus */ + radeon_create_i2c_busses(rinfo); +#endif + + /* set all the vital stuff */ + radeon_set_fbinfo (rinfo); + + /* Probe screen types */ + radeon_probe_screens(rinfo, monitor_layout, ignore_edid); + + /* Build mode list, check out panel native model */ + radeon_check_modes(rinfo, mode_option); + + /* Register some sysfs stuff (should be done better) */ + if (rinfo->mon1_EDID) + err |= sysfs_create_bin_file(&rinfo->pdev->dev.kobj, + &edid1_attr); + if (rinfo->mon2_EDID) + err |= sysfs_create_bin_file(&rinfo->pdev->dev.kobj, + &edid2_attr); + if (err) + pr_warning("%s() Creating sysfs files failed, continuing\n", + __func__); + + /* save current mode regs before we switch into the new one + * so we can restore this upon __exit + */ + radeon_save_state (rinfo, &rinfo->init_state); + memcpy(&rinfo->state, &rinfo->init_state, sizeof(struct radeon_regs)); + + /* Setup Power Management capabilities */ + if (default_dynclk < -1) { + /* -2 is special: means ON on mobility chips and do not + * change on others + */ + radeonfb_pm_init(rinfo, rinfo->is_mobility ? 1 : -1, ignore_devlist, force_sleep); + } else + radeonfb_pm_init(rinfo, default_dynclk, ignore_devlist, force_sleep); + + pci_set_drvdata(pdev, info); + + /* Register with fbdev layer */ + ret = register_framebuffer(info); + if (ret < 0) { + printk (KERN_ERR "radeonfb (%s): could not register framebuffer\n", + pci_name(rinfo->pdev)); + goto err_unmap_fb; + } + +#ifdef CONFIG_MTRR + rinfo->mtrr_hdl = nomtrr ? -1 : mtrr_add(rinfo->fb_base_phys, + rinfo->video_ram, + MTRR_TYPE_WRCOMB, 1); +#endif + + if (backlight) + radeonfb_bl_init(rinfo); + + printk ("radeonfb (%s): %s\n", pci_name(rinfo->pdev), rinfo->name); + + if (rinfo->bios_seg) + radeon_unmap_ROM(rinfo, pdev); + pr_debug("radeonfb_pci_register END\n"); + + return 0; +err_unmap_fb: + iounmap(rinfo->fb_base); +err_unmap_rom: + kfree(rinfo->mon1_EDID); + kfree(rinfo->mon2_EDID); + if (rinfo->mon1_modedb) + fb_destroy_modedb(rinfo->mon1_modedb); + fb_dealloc_cmap(&info->cmap); +#ifdef CONFIG_FB_RADEON_I2C + radeon_delete_i2c_busses(rinfo); +#endif + if (rinfo->bios_seg) + radeon_unmap_ROM(rinfo, pdev); + iounmap(rinfo->mmio_base); +err_release_pci2: + pci_release_region(pdev, 2); +err_release_pci0: + pci_release_region(pdev, 0); +err_release_fb: + framebuffer_release(info); +err_disable: +err_out: + return ret; +} + + + +static void radeonfb_pci_unregister(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct radeonfb_info *rinfo = info->par; + + if (!rinfo) + return; + + radeonfb_pm_exit(rinfo); + + if (rinfo->mon1_EDID) + sysfs_remove_bin_file(&rinfo->pdev->dev.kobj, &edid1_attr); + if (rinfo->mon2_EDID) + sysfs_remove_bin_file(&rinfo->pdev->dev.kobj, &edid2_attr); + +#if 0 + /* restore original state + * + * Doesn't quite work yet, I suspect if we come from a legacy + * VGA mode (or worse, text mode), we need to do some VGA black + * magic here that I know nothing about. --BenH + */ + radeon_write_mode (rinfo, &rinfo->init_state, 1); + #endif + + del_timer_sync(&rinfo->lvds_timer); + +#ifdef CONFIG_MTRR + if (rinfo->mtrr_hdl >= 0) + mtrr_del(rinfo->mtrr_hdl, 0, 0); +#endif + + unregister_framebuffer(info); + + radeonfb_bl_exit(rinfo); + + iounmap(rinfo->mmio_base); + iounmap(rinfo->fb_base); + + pci_release_region(pdev, 2); + pci_release_region(pdev, 0); + + kfree(rinfo->mon1_EDID); + kfree(rinfo->mon2_EDID); + if (rinfo->mon1_modedb) + fb_destroy_modedb(rinfo->mon1_modedb); +#ifdef CONFIG_FB_RADEON_I2C + radeon_delete_i2c_busses(rinfo); +#endif + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + + +static struct pci_driver radeonfb_driver = { + .name = "radeonfb", + .id_table = radeonfb_pci_table, + .probe = radeonfb_pci_register, + .remove = radeonfb_pci_unregister, +#ifdef CONFIG_PM + .suspend = radeonfb_pci_suspend, + .resume = radeonfb_pci_resume, +#endif /* CONFIG_PM */ +}; + +#ifndef MODULE +static int __init radeonfb_setup (char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep (&options, ",")) != NULL) { + if (!*this_opt) + continue; + + if (!strncmp(this_opt, "noaccel", 7)) { + noaccel = 1; + } else if (!strncmp(this_opt, "mirror", 6)) { + mirror = 1; + } else if (!strncmp(this_opt, "force_dfp", 9)) { + force_dfp = 1; + } else if (!strncmp(this_opt, "panel_yres:", 11)) { + panel_yres = simple_strtoul((this_opt+11), NULL, 0); + } else if (!strncmp(this_opt, "backlight:", 10)) { + backlight = simple_strtoul(this_opt+10, NULL, 0); +#ifdef CONFIG_MTRR + } else if (!strncmp(this_opt, "nomtrr", 6)) { + nomtrr = 1; +#endif + } else if (!strncmp(this_opt, "nomodeset", 9)) { + nomodeset = 1; + } else if (!strncmp(this_opt, "force_measure_pll", 17)) { + force_measure_pll = 1; + } else if (!strncmp(this_opt, "ignore_edid", 11)) { + ignore_edid = 1; +#if defined(CONFIG_PM) && defined(CONFIG_X86) + } else if (!strncmp(this_opt, "force_sleep", 11)) { + force_sleep = 1; + } else if (!strncmp(this_opt, "ignore_devlist", 14)) { + ignore_devlist = 1; +#endif + } else + mode_option = this_opt; + } + return 0; +} +#endif /* MODULE */ + +static int __init radeonfb_init (void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("radeonfb", &option)) + return -ENODEV; + radeonfb_setup(option); +#endif + return pci_register_driver (&radeonfb_driver); +} + + +static void __exit radeonfb_exit (void) +{ + pci_unregister_driver (&radeonfb_driver); +} + +module_init(radeonfb_init); +module_exit(radeonfb_exit); + +MODULE_AUTHOR("Ani Joshi"); +MODULE_DESCRIPTION("framebuffer driver for ATI Radeon chipset"); +MODULE_LICENSE("GPL"); +module_param(noaccel, bool, 0); +module_param(default_dynclk, int, 0); +MODULE_PARM_DESC(default_dynclk, "int: -2=enable on mobility only,-1=do not change,0=off,1=on"); +MODULE_PARM_DESC(noaccel, "bool: disable acceleration"); +module_param(nomodeset, bool, 0); +MODULE_PARM_DESC(nomodeset, "bool: disable actual setting of video mode"); +module_param(mirror, bool, 0); +MODULE_PARM_DESC(mirror, "bool: mirror the display to both monitors"); +module_param(force_dfp, bool, 0); +MODULE_PARM_DESC(force_dfp, "bool: force display to dfp"); +module_param(ignore_edid, bool, 0); +MODULE_PARM_DESC(ignore_edid, "bool: Ignore EDID data when doing DDC probe"); +module_param(monitor_layout, charp, 0); +MODULE_PARM_DESC(monitor_layout, "Specify monitor mapping (like XFree86)"); +module_param(force_measure_pll, bool, 0); +MODULE_PARM_DESC(force_measure_pll, "Force measurement of PLL (debug)"); +#ifdef CONFIG_MTRR +module_param(nomtrr, bool, 0); +MODULE_PARM_DESC(nomtrr, "bool: disable use of MTRR registers"); +#endif +module_param(panel_yres, int, 0); +MODULE_PARM_DESC(panel_yres, "int: set panel yres"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); +#if defined(CONFIG_PM) && defined(CONFIG_X86) +module_param(force_sleep, bool, 0); +MODULE_PARM_DESC(force_sleep, "bool: force D2 sleep mode on all hardware"); +module_param(ignore_devlist, bool, 0); +MODULE_PARM_DESC(ignore_devlist, "bool: ignore workarounds for bugs in specific laptops"); +#endif diff --git a/drivers/video/fbdev/aty/radeon_i2c.c b/drivers/video/fbdev/aty/radeon_i2c.c new file mode 100644 index 000000000000..ab1d0fd76316 --- /dev/null +++ b/drivers/video/fbdev/aty/radeon_i2c.c @@ -0,0 +1,167 @@ +#include "radeonfb.h" + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/fb.h> + + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <asm/io.h> + +#include <video/radeon.h> +#include "../edid.h" + +static void radeon_gpio_setscl(void* data, int state) +{ + struct radeon_i2c_chan *chan = data; + struct radeonfb_info *rinfo = chan->rinfo; + u32 val; + + val = INREG(chan->ddc_reg) & ~(VGA_DDC_CLK_OUT_EN); + if (!state) + val |= VGA_DDC_CLK_OUT_EN; + + OUTREG(chan->ddc_reg, val); + (void)INREG(chan->ddc_reg); +} + +static void radeon_gpio_setsda(void* data, int state) +{ + struct radeon_i2c_chan *chan = data; + struct radeonfb_info *rinfo = chan->rinfo; + u32 val; + + val = INREG(chan->ddc_reg) & ~(VGA_DDC_DATA_OUT_EN); + if (!state) + val |= VGA_DDC_DATA_OUT_EN; + + OUTREG(chan->ddc_reg, val); + (void)INREG(chan->ddc_reg); +} + +static int radeon_gpio_getscl(void* data) +{ + struct radeon_i2c_chan *chan = data; + struct radeonfb_info *rinfo = chan->rinfo; + u32 val; + + val = INREG(chan->ddc_reg); + + return (val & VGA_DDC_CLK_INPUT) ? 1 : 0; +} + +static int radeon_gpio_getsda(void* data) +{ + struct radeon_i2c_chan *chan = data; + struct radeonfb_info *rinfo = chan->rinfo; + u32 val; + + val = INREG(chan->ddc_reg); + + return (val & VGA_DDC_DATA_INPUT) ? 1 : 0; +} + +static int radeon_setup_i2c_bus(struct radeon_i2c_chan *chan, const char *name) +{ + int rc; + + snprintf(chan->adapter.name, sizeof(chan->adapter.name), + "radeonfb %s", name); + chan->adapter.owner = THIS_MODULE; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = &chan->rinfo->pdev->dev; + chan->algo.setsda = radeon_gpio_setsda; + chan->algo.setscl = radeon_gpio_setscl; + chan->algo.getsda = radeon_gpio_getsda; + chan->algo.getscl = radeon_gpio_getscl; + chan->algo.udelay = 10; + chan->algo.timeout = 20; + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + /* Raise SCL and SDA */ + radeon_gpio_setsda(chan, 1); + radeon_gpio_setscl(chan, 1); + udelay(20); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + dev_dbg(&chan->rinfo->pdev->dev, "I2C bus %s registered.\n", name); + else + dev_warn(&chan->rinfo->pdev->dev, "Failed to register I2C bus %s.\n", name); + return rc; +} + +void radeon_create_i2c_busses(struct radeonfb_info *rinfo) +{ + rinfo->i2c[0].rinfo = rinfo; + rinfo->i2c[0].ddc_reg = GPIO_MONID; +#ifndef CONFIG_PPC + rinfo->i2c[0].adapter.class = I2C_CLASS_HWMON; +#endif + radeon_setup_i2c_bus(&rinfo->i2c[0], "monid"); + + rinfo->i2c[1].rinfo = rinfo; + rinfo->i2c[1].ddc_reg = GPIO_DVI_DDC; + radeon_setup_i2c_bus(&rinfo->i2c[1], "dvi"); + + rinfo->i2c[2].rinfo = rinfo; + rinfo->i2c[2].ddc_reg = GPIO_VGA_DDC; + radeon_setup_i2c_bus(&rinfo->i2c[2], "vga"); + + rinfo->i2c[3].rinfo = rinfo; + rinfo->i2c[3].ddc_reg = GPIO_CRT2_DDC; + radeon_setup_i2c_bus(&rinfo->i2c[3], "crt2"); +} + +void radeon_delete_i2c_busses(struct radeonfb_info *rinfo) +{ + if (rinfo->i2c[0].rinfo) + i2c_del_adapter(&rinfo->i2c[0].adapter); + rinfo->i2c[0].rinfo = NULL; + + if (rinfo->i2c[1].rinfo) + i2c_del_adapter(&rinfo->i2c[1].adapter); + rinfo->i2c[1].rinfo = NULL; + + if (rinfo->i2c[2].rinfo) + i2c_del_adapter(&rinfo->i2c[2].adapter); + rinfo->i2c[2].rinfo = NULL; + + if (rinfo->i2c[3].rinfo) + i2c_del_adapter(&rinfo->i2c[3].adapter); + rinfo->i2c[3].rinfo = NULL; +} + +int radeon_probe_i2c_connector(struct radeonfb_info *rinfo, int conn, + u8 **out_edid) +{ + u8 *edid; + + edid = fb_ddc_read(&rinfo->i2c[conn-1].adapter); + + if (out_edid) + *out_edid = edid; + if (!edid) { + pr_debug("radeonfb: I2C (port %d) ... not found\n", conn); + return MT_NONE; + } + if (edid[0x14] & 0x80) { + /* Fix detection using BIOS tables */ + if (rinfo->is_mobility /*&& conn == ddc_dvi*/ && + (INREG(LVDS_GEN_CNTL) & LVDS_ON)) { + pr_debug("radeonfb: I2C (port %d) ... found LVDS panel\n", conn); + return MT_LCD; + } else { + pr_debug("radeonfb: I2C (port %d) ... found TMDS panel\n", conn); + return MT_DFP; + } + } + pr_debug("radeonfb: I2C (port %d) ... found CRT display\n", conn); + return MT_CRT; +} + diff --git a/drivers/video/fbdev/aty/radeon_monitor.c b/drivers/video/fbdev/aty/radeon_monitor.c new file mode 100644 index 000000000000..bc078d50d8f1 --- /dev/null +++ b/drivers/video/fbdev/aty/radeon_monitor.c @@ -0,0 +1,1052 @@ +#include "radeonfb.h" + +#include <linux/slab.h> + +#include "../edid.h" + +static struct fb_var_screeninfo radeonfb_default_var = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 8, + .red = { .length = 8 }, + .green = { .length = 8 }, + .blue = { .length = 8 }, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .pixclock = 39721, + .left_margin = 40, + .right_margin = 24, + .upper_margin = 32, + .lower_margin = 11, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED +}; + +static char *radeon_get_mon_name(int type) +{ + char *pret = NULL; + + switch (type) { + case MT_NONE: + pret = "no"; + break; + case MT_CRT: + pret = "CRT"; + break; + case MT_DFP: + pret = "DFP"; + break; + case MT_LCD: + pret = "LCD"; + break; + case MT_CTV: + pret = "CTV"; + break; + case MT_STV: + pret = "STV"; + break; + } + + return pret; +} + + +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) +/* + * Try to find monitor informations & EDID data out of the Open Firmware + * device-tree. This also contains some "hacks" to work around a few machine + * models with broken OF probing by hard-coding known EDIDs for some Mac + * laptops internal LVDS panel. (XXX: not done yet) + */ +static int radeon_parse_montype_prop(struct device_node *dp, u8 **out_EDID, + int hdno) +{ + static char *propnames[] = { "DFP,EDID", "LCD,EDID", "EDID", + "EDID1", "EDID2", NULL }; + const u8 *pedid = NULL; + const u8 *pmt = NULL; + u8 *tmp; + int i, mt = MT_NONE; + + pr_debug("analyzing OF properties...\n"); + pmt = of_get_property(dp, "display-type", NULL); + if (!pmt) + return MT_NONE; + pr_debug("display-type: %s\n", pmt); + /* OF says "LCD" for DFP as well, we discriminate from the caller of this + * function + */ + if (!strcmp(pmt, "LCD") || !strcmp(pmt, "DFP")) + mt = MT_DFP; + else if (!strcmp(pmt, "CRT")) + mt = MT_CRT; + else { + if (strcmp(pmt, "NONE") != 0) + printk(KERN_WARNING "radeonfb: Unknown OF display-type: %s\n", + pmt); + return MT_NONE; + } + + for (i = 0; propnames[i] != NULL; ++i) { + pedid = of_get_property(dp, propnames[i], NULL); + if (pedid != NULL) + break; + } + /* We didn't find the EDID in the leaf node, some cards will actually + * put EDID1/EDID2 in the parent, look for these (typically M6 tipb). + * single-head cards have hdno == -1 and skip this step + */ + if (pedid == NULL && dp->parent && (hdno != -1)) + pedid = of_get_property(dp->parent, + (hdno == 0) ? "EDID1" : "EDID2", NULL); + if (pedid == NULL && dp->parent && (hdno == 0)) + pedid = of_get_property(dp->parent, "EDID", NULL); + if (pedid == NULL) + return mt; + + tmp = kmemdup(pedid, EDID_LENGTH, GFP_KERNEL); + if (!tmp) + return mt; + *out_EDID = tmp; + return mt; +} + +static int radeon_probe_OF_head(struct radeonfb_info *rinfo, int head_no, + u8 **out_EDID) +{ + struct device_node *dp; + + pr_debug("radeon_probe_OF_head\n"); + + dp = rinfo->of_node; + while (dp == NULL) + return MT_NONE; + + if (rinfo->has_CRTC2) { + const char *pname; + int len, second = 0; + + dp = dp->child; + do { + if (!dp) + return MT_NONE; + pname = of_get_property(dp, "name", NULL); + if (!pname) + return MT_NONE; + len = strlen(pname); + pr_debug("head: %s (letter: %c, head_no: %d)\n", + pname, pname[len-1], head_no); + if (pname[len-1] == 'A' && head_no == 0) { + int mt = radeon_parse_montype_prop(dp, out_EDID, 0); + /* Maybe check for LVDS_GEN_CNTL here ? I need to check out + * what OF does when booting with lid closed + */ + if (mt == MT_DFP && rinfo->is_mobility) + mt = MT_LCD; + return mt; + } else if (pname[len-1] == 'B' && head_no == 1) + return radeon_parse_montype_prop(dp, out_EDID, 1); + second = 1; + dp = dp->sibling; + } while(!second); + } else { + if (head_no > 0) + return MT_NONE; + return radeon_parse_montype_prop(dp, out_EDID, -1); + } + return MT_NONE; +} +#endif /* CONFIG_PPC_OF || CONFIG_SPARC */ + + +static int radeon_get_panel_info_BIOS(struct radeonfb_info *rinfo) +{ + unsigned long tmp, tmp0; + char stmp[30]; + int i; + + if (!rinfo->bios_seg) + return 0; + + if (!(tmp = BIOS_IN16(rinfo->fp_bios_start + 0x40))) { + printk(KERN_ERR "radeonfb: Failed to detect DFP panel info using BIOS\n"); + rinfo->panel_info.pwr_delay = 200; + return 0; + } + + for(i=0; i<24; i++) + stmp[i] = BIOS_IN8(tmp+i+1); + stmp[24] = 0; + printk("radeonfb: panel ID string: %s\n", stmp); + rinfo->panel_info.xres = BIOS_IN16(tmp + 25); + rinfo->panel_info.yres = BIOS_IN16(tmp + 27); + printk("radeonfb: detected LVDS panel size from BIOS: %dx%d\n", + rinfo->panel_info.xres, rinfo->panel_info.yres); + + rinfo->panel_info.pwr_delay = BIOS_IN16(tmp + 44); + pr_debug("BIOS provided panel power delay: %d\n", rinfo->panel_info.pwr_delay); + if (rinfo->panel_info.pwr_delay > 2000 || rinfo->panel_info.pwr_delay <= 0) + rinfo->panel_info.pwr_delay = 2000; + + /* + * Some panels only work properly with some divider combinations + */ + rinfo->panel_info.ref_divider = BIOS_IN16(tmp + 46); + rinfo->panel_info.post_divider = BIOS_IN8(tmp + 48); + rinfo->panel_info.fbk_divider = BIOS_IN16(tmp + 49); + if (rinfo->panel_info.ref_divider != 0 && + rinfo->panel_info.fbk_divider > 3) { + rinfo->panel_info.use_bios_dividers = 1; + printk(KERN_INFO "radeondb: BIOS provided dividers will be used\n"); + pr_debug("ref_divider = %x\n", rinfo->panel_info.ref_divider); + pr_debug("post_divider = %x\n", rinfo->panel_info.post_divider); + pr_debug("fbk_divider = %x\n", rinfo->panel_info.fbk_divider); + } + pr_debug("Scanning BIOS table ...\n"); + for(i=0; i<32; i++) { + tmp0 = BIOS_IN16(tmp+64+i*2); + if (tmp0 == 0) + break; + pr_debug(" %d x %d\n", BIOS_IN16(tmp0), BIOS_IN16(tmp0+2)); + if ((BIOS_IN16(tmp0) == rinfo->panel_info.xres) && + (BIOS_IN16(tmp0+2) == rinfo->panel_info.yres)) { + rinfo->panel_info.hblank = (BIOS_IN16(tmp0+17) - BIOS_IN16(tmp0+19)) * 8; + rinfo->panel_info.hOver_plus = ((BIOS_IN16(tmp0+21) - + BIOS_IN16(tmp0+19) -1) * 8) & 0x7fff; + rinfo->panel_info.hSync_width = BIOS_IN8(tmp0+23) * 8; + rinfo->panel_info.vblank = BIOS_IN16(tmp0+24) - BIOS_IN16(tmp0+26); + rinfo->panel_info.vOver_plus = (BIOS_IN16(tmp0+28) & 0x7ff) - BIOS_IN16(tmp0+26); + rinfo->panel_info.vSync_width = (BIOS_IN16(tmp0+28) & 0xf800) >> 11; + rinfo->panel_info.clock = BIOS_IN16(tmp0+9); + /* Assume high active syncs for now until ATI tells me more... maybe we + * can probe register values here ? + */ + rinfo->panel_info.hAct_high = 1; + rinfo->panel_info.vAct_high = 1; + /* Mark panel infos valid */ + rinfo->panel_info.valid = 1; + + pr_debug("Found panel in BIOS table:\n"); + pr_debug(" hblank: %d\n", rinfo->panel_info.hblank); + pr_debug(" hOver_plus: %d\n", rinfo->panel_info.hOver_plus); + pr_debug(" hSync_width: %d\n", rinfo->panel_info.hSync_width); + pr_debug(" vblank: %d\n", rinfo->panel_info.vblank); + pr_debug(" vOver_plus: %d\n", rinfo->panel_info.vOver_plus); + pr_debug(" vSync_width: %d\n", rinfo->panel_info.vSync_width); + pr_debug(" clock: %d\n", rinfo->panel_info.clock); + + return 1; + } + } + pr_debug("Didn't find panel in BIOS table !\n"); + + return 0; +} + +/* Try to extract the connector informations from the BIOS. This + * doesn't quite work yet, but it's output is still useful for + * debugging + */ +static void radeon_parse_connector_info(struct radeonfb_info *rinfo) +{ + int offset, chips, connectors, tmp, i, conn, type; + + static char* __conn_type_table[16] = { + "NONE", "Proprietary", "CRT", "DVI-I", "DVI-D", "Unknown", "Unknown", + "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", + "Unknown", "Unknown", "Unknown" + }; + + if (!rinfo->bios_seg) + return; + + offset = BIOS_IN16(rinfo->fp_bios_start + 0x50); + if (offset == 0) { + printk(KERN_WARNING "radeonfb: No connector info table detected\n"); + return; + } + + /* Don't do much more at this point but displaying the data if + * DEBUG is enabled + */ + chips = BIOS_IN8(offset++) >> 4; + pr_debug("%d chips in connector info\n", chips); + for (i = 0; i < chips; i++) { + tmp = BIOS_IN8(offset++); + connectors = tmp & 0x0f; + pr_debug(" - chip %d has %d connectors\n", tmp >> 4, connectors); + for (conn = 0; ; conn++) { + tmp = BIOS_IN16(offset); + if (tmp == 0) + break; + offset += 2; + type = (tmp >> 12) & 0x0f; + pr_debug(" * connector %d of type %d (%s) : %04x\n", + conn, type, __conn_type_table[type], tmp); + } + } +} + + +/* + * Probe physical connection of a CRT. This code comes from XFree + * as well and currently is only implemented for the CRT DAC, the + * code for the TVDAC is commented out in XFree as "non working" + */ +static int radeon_crt_is_connected(struct radeonfb_info *rinfo, int is_crt_dac) +{ + int connected = 0; + + /* the monitor either wasn't connected or it is a non-DDC CRT. + * try to probe it + */ + if (is_crt_dac) { + unsigned long ulOrigVCLK_ECP_CNTL; + unsigned long ulOrigDAC_CNTL; + unsigned long ulOrigDAC_EXT_CNTL; + unsigned long ulOrigCRTC_EXT_CNTL; + unsigned long ulData; + unsigned long ulMask; + + ulOrigVCLK_ECP_CNTL = INPLL(VCLK_ECP_CNTL); + + ulData = ulOrigVCLK_ECP_CNTL; + ulData &= ~(PIXCLK_ALWAYS_ONb + | PIXCLK_DAC_ALWAYS_ONb); + ulMask = ~(PIXCLK_ALWAYS_ONb + | PIXCLK_DAC_ALWAYS_ONb); + OUTPLLP(VCLK_ECP_CNTL, ulData, ulMask); + + ulOrigCRTC_EXT_CNTL = INREG(CRTC_EXT_CNTL); + ulData = ulOrigCRTC_EXT_CNTL; + ulData |= CRTC_CRT_ON; + OUTREG(CRTC_EXT_CNTL, ulData); + + ulOrigDAC_EXT_CNTL = INREG(DAC_EXT_CNTL); + ulData = ulOrigDAC_EXT_CNTL; + ulData &= ~DAC_FORCE_DATA_MASK; + ulData |= (DAC_FORCE_BLANK_OFF_EN + |DAC_FORCE_DATA_EN + |DAC_FORCE_DATA_SEL_MASK); + if ((rinfo->family == CHIP_FAMILY_RV250) || + (rinfo->family == CHIP_FAMILY_RV280)) + ulData |= (0x01b6 << DAC_FORCE_DATA_SHIFT); + else + ulData |= (0x01ac << DAC_FORCE_DATA_SHIFT); + + OUTREG(DAC_EXT_CNTL, ulData); + + ulOrigDAC_CNTL = INREG(DAC_CNTL); + ulData = ulOrigDAC_CNTL; + ulData |= DAC_CMP_EN; + ulData &= ~(DAC_RANGE_CNTL_MASK + | DAC_PDWN); + ulData |= 0x2; + OUTREG(DAC_CNTL, ulData); + + mdelay(1); + + ulData = INREG(DAC_CNTL); + connected = (DAC_CMP_OUTPUT & ulData) ? 1 : 0; + + ulData = ulOrigVCLK_ECP_CNTL; + ulMask = 0xFFFFFFFFL; + OUTPLLP(VCLK_ECP_CNTL, ulData, ulMask); + + OUTREG(DAC_CNTL, ulOrigDAC_CNTL ); + OUTREG(DAC_EXT_CNTL, ulOrigDAC_EXT_CNTL ); + OUTREG(CRTC_EXT_CNTL, ulOrigCRTC_EXT_CNTL); + } + + return connected ? MT_CRT : MT_NONE; +} + +/* + * Parse the "monitor_layout" string if any. This code is mostly + * copied from XFree's radeon driver + */ +static int radeon_parse_monitor_layout(struct radeonfb_info *rinfo, + const char *monitor_layout) +{ + char s1[5], s2[5]; + int i = 0, second = 0; + const char *s; + + if (!monitor_layout) + return 0; + + s = monitor_layout; + do { + switch(*s) { + case ',': + s1[i] = '\0'; + i = 0; + second = 1; + break; + case ' ': + case '\0': + break; + default: + if (i > 4) + break; + if (second) + s2[i] = *s; + else + s1[i] = *s; + i++; + } + + if (i > 4) + i = 4; + + } while (*s++); + if (second) + s2[i] = 0; + else { + s1[i] = 0; + s2[0] = 0; + } + if (strcmp(s1, "CRT") == 0) + rinfo->mon1_type = MT_CRT; + else if (strcmp(s1, "TMDS") == 0) + rinfo->mon1_type = MT_DFP; + else if (strcmp(s1, "LVDS") == 0) + rinfo->mon1_type = MT_LCD; + + if (strcmp(s2, "CRT") == 0) + rinfo->mon2_type = MT_CRT; + else if (strcmp(s2, "TMDS") == 0) + rinfo->mon2_type = MT_DFP; + else if (strcmp(s2, "LVDS") == 0) + rinfo->mon2_type = MT_LCD; + + return 1; +} + +/* + * Probe display on both primary and secondary card's connector (if any) + * by various available techniques (i2c, OF device tree, BIOS, ...) and + * try to retrieve EDID. The algorithm here comes from XFree's radeon + * driver + */ +void radeon_probe_screens(struct radeonfb_info *rinfo, + const char *monitor_layout, int ignore_edid) +{ +#ifdef CONFIG_FB_RADEON_I2C + int ddc_crt2_used = 0; +#endif + int tmp, i; + + radeon_parse_connector_info(rinfo); + + if (radeon_parse_monitor_layout(rinfo, monitor_layout)) { + + /* + * If user specified a monitor_layout option, use it instead + * of auto-detecting. Maybe we should only use this argument + * on the first radeon card probed or provide a way to specify + * a layout for each card ? + */ + + pr_debug("Using specified monitor layout: %s", monitor_layout); +#ifdef CONFIG_FB_RADEON_I2C + if (!ignore_edid) { + if (rinfo->mon1_type != MT_NONE) + if (!radeon_probe_i2c_connector(rinfo, ddc_dvi, &rinfo->mon1_EDID)) { + radeon_probe_i2c_connector(rinfo, ddc_crt2, &rinfo->mon1_EDID); + ddc_crt2_used = 1; + } + if (rinfo->mon2_type != MT_NONE) + if (!radeon_probe_i2c_connector(rinfo, ddc_vga, &rinfo->mon2_EDID) && + !ddc_crt2_used) + radeon_probe_i2c_connector(rinfo, ddc_crt2, &rinfo->mon2_EDID); + } +#endif /* CONFIG_FB_RADEON_I2C */ + if (rinfo->mon1_type == MT_NONE) { + if (rinfo->mon2_type != MT_NONE) { + rinfo->mon1_type = rinfo->mon2_type; + rinfo->mon1_EDID = rinfo->mon2_EDID; + } else { + rinfo->mon1_type = MT_CRT; + printk(KERN_INFO "radeonfb: No valid monitor, assuming CRT on first port\n"); + } + rinfo->mon2_type = MT_NONE; + rinfo->mon2_EDID = NULL; + } + } else { + /* + * Auto-detecting display type (well... trying to ...) + */ + + pr_debug("Starting monitor auto detection...\n"); + +#if defined(DEBUG) && defined(CONFIG_FB_RADEON_I2C) + { + u8 *EDIDs[4] = { NULL, NULL, NULL, NULL }; + int mon_types[4] = {MT_NONE, MT_NONE, MT_NONE, MT_NONE}; + int i; + + for (i = 0; i < 4; i++) + mon_types[i] = radeon_probe_i2c_connector(rinfo, + i+1, &EDIDs[i]); + } +#endif /* DEBUG */ + /* + * Old single head cards + */ + if (!rinfo->has_CRTC2) { +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = radeon_probe_OF_head(rinfo, 0, + &rinfo->mon1_EDID); +#endif /* CONFIG_PPC_OF || CONFIG_SPARC */ +#ifdef CONFIG_FB_RADEON_I2C + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = + radeon_probe_i2c_connector(rinfo, ddc_dvi, + &rinfo->mon1_EDID); + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = + radeon_probe_i2c_connector(rinfo, ddc_vga, + &rinfo->mon1_EDID); + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = + radeon_probe_i2c_connector(rinfo, ddc_crt2, + &rinfo->mon1_EDID); +#endif /* CONFIG_FB_RADEON_I2C */ + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = MT_CRT; + goto bail; + } + + /* + * Check for cards with reversed DACs or TMDS controllers using BIOS + */ + if (rinfo->bios_seg && + (tmp = BIOS_IN16(rinfo->fp_bios_start + 0x50))) { + for (i = 1; i < 4; i++) { + unsigned int tmp0; + + if (!BIOS_IN8(tmp + i*2) && i > 1) + break; + tmp0 = BIOS_IN16(tmp + i*2); + if ((!(tmp0 & 0x01)) && (((tmp0 >> 8) & 0x0f) == ddc_dvi)) { + rinfo->reversed_DAC = 1; + printk(KERN_INFO "radeonfb: Reversed DACs detected\n"); + } + if ((((tmp0 >> 8) & 0x0f) == ddc_dvi) && ((tmp0 >> 4) & 0x01)) { + rinfo->reversed_TMDS = 1; + printk(KERN_INFO "radeonfb: Reversed TMDS detected\n"); + } + } + } + + /* + * Probe primary head (DVI or laptop internal panel) + */ +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = radeon_probe_OF_head(rinfo, 0, + &rinfo->mon1_EDID); +#endif /* CONFIG_PPC_OF || CONFIG_SPARC */ +#ifdef CONFIG_FB_RADEON_I2C + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = radeon_probe_i2c_connector(rinfo, ddc_dvi, + &rinfo->mon1_EDID); + if (rinfo->mon1_type == MT_NONE) { + rinfo->mon1_type = radeon_probe_i2c_connector(rinfo, ddc_crt2, + &rinfo->mon1_EDID); + if (rinfo->mon1_type != MT_NONE) + ddc_crt2_used = 1; + } +#endif /* CONFIG_FB_RADEON_I2C */ + if (rinfo->mon1_type == MT_NONE && rinfo->is_mobility && + ((rinfo->bios_seg && (INREG(BIOS_4_SCRATCH) & 4)) + || (INREG(LVDS_GEN_CNTL) & LVDS_ON))) { + rinfo->mon1_type = MT_LCD; + printk("Non-DDC laptop panel detected\n"); + } + if (rinfo->mon1_type == MT_NONE) + rinfo->mon1_type = radeon_crt_is_connected(rinfo, rinfo->reversed_DAC); + + /* + * Probe secondary head (mostly VGA, can be DVI) + */ +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) + if (rinfo->mon2_type == MT_NONE) + rinfo->mon2_type = radeon_probe_OF_head(rinfo, 1, + &rinfo->mon2_EDID); +#endif /* CONFIG_PPC_OF || defined(CONFIG_SPARC) */ +#ifdef CONFIG_FB_RADEON_I2C + if (rinfo->mon2_type == MT_NONE) + rinfo->mon2_type = radeon_probe_i2c_connector(rinfo, ddc_vga, + &rinfo->mon2_EDID); + if (rinfo->mon2_type == MT_NONE && !ddc_crt2_used) + rinfo->mon2_type = radeon_probe_i2c_connector(rinfo, ddc_crt2, + &rinfo->mon2_EDID); +#endif /* CONFIG_FB_RADEON_I2C */ + if (rinfo->mon2_type == MT_NONE) + rinfo->mon2_type = radeon_crt_is_connected(rinfo, !rinfo->reversed_DAC); + + /* + * If we only detected port 2, we swap them, if none detected, + * assume CRT (maybe fallback to old BIOS_SCRATCH stuff ? or look + * at FP registers ?) + */ + if (rinfo->mon1_type == MT_NONE) { + if (rinfo->mon2_type != MT_NONE) { + rinfo->mon1_type = rinfo->mon2_type; + rinfo->mon1_EDID = rinfo->mon2_EDID; + } else + rinfo->mon1_type = MT_CRT; + rinfo->mon2_type = MT_NONE; + rinfo->mon2_EDID = NULL; + } + + /* + * Deal with reversed TMDS + */ + if (rinfo->reversed_TMDS) { + /* Always keep internal TMDS as primary head */ + if (rinfo->mon1_type == MT_DFP || rinfo->mon2_type == MT_DFP) { + int tmp_type = rinfo->mon1_type; + u8 *tmp_EDID = rinfo->mon1_EDID; + rinfo->mon1_type = rinfo->mon2_type; + rinfo->mon1_EDID = rinfo->mon2_EDID; + rinfo->mon2_type = tmp_type; + rinfo->mon2_EDID = tmp_EDID; + if (rinfo->mon1_type == MT_CRT || rinfo->mon2_type == MT_CRT) + rinfo->reversed_DAC ^= 1; + } + } + } + if (ignore_edid) { + kfree(rinfo->mon1_EDID); + rinfo->mon1_EDID = NULL; + kfree(rinfo->mon2_EDID); + rinfo->mon2_EDID = NULL; + } + + bail: + printk(KERN_INFO "radeonfb: Monitor 1 type %s found\n", + radeon_get_mon_name(rinfo->mon1_type)); + if (rinfo->mon1_EDID) + printk(KERN_INFO "radeonfb: EDID probed\n"); + if (!rinfo->has_CRTC2) + return; + printk(KERN_INFO "radeonfb: Monitor 2 type %s found\n", + radeon_get_mon_name(rinfo->mon2_type)); + if (rinfo->mon2_EDID) + printk(KERN_INFO "radeonfb: EDID probed\n"); +} + + +/* + * This functions applyes any arch/model/machine specific fixups + * to the panel info. It may eventually alter EDID block as + * well or whatever is specific to a given model and not probed + * properly by the default code + */ +static void radeon_fixup_panel_info(struct radeonfb_info *rinfo) +{ +#ifdef CONFIG_PPC_OF + /* + * LCD Flat panels should use fixed dividers, we enfore that on + * PPC only for now... + */ + if (!rinfo->panel_info.use_bios_dividers && rinfo->mon1_type == MT_LCD + && rinfo->is_mobility) { + int ppll_div_sel; + u32 ppll_divn; + ppll_div_sel = INREG8(CLOCK_CNTL_INDEX + 1) & 0x3; + radeon_pll_errata_after_index(rinfo); + ppll_divn = INPLL(PPLL_DIV_0 + ppll_div_sel); + rinfo->panel_info.ref_divider = rinfo->pll.ref_div; + rinfo->panel_info.fbk_divider = ppll_divn & 0x7ff; + rinfo->panel_info.post_divider = (ppll_divn >> 16) & 0x7; + rinfo->panel_info.use_bios_dividers = 1; + + printk(KERN_DEBUG "radeonfb: Using Firmware dividers 0x%08x " + "from PPLL %d\n", + rinfo->panel_info.fbk_divider | + (rinfo->panel_info.post_divider << 16), + ppll_div_sel); + } +#endif /* CONFIG_PPC_OF */ +} + + +/* + * Fill up panel infos from a mode definition, either returned by the EDID + * or from the default mode when we can't do any better + */ +static void radeon_var_to_panel_info(struct radeonfb_info *rinfo, struct fb_var_screeninfo *var) +{ + rinfo->panel_info.xres = var->xres; + rinfo->panel_info.yres = var->yres; + rinfo->panel_info.clock = 100000000 / var->pixclock; + rinfo->panel_info.hOver_plus = var->right_margin; + rinfo->panel_info.hSync_width = var->hsync_len; + rinfo->panel_info.hblank = var->left_margin + + (var->right_margin + var->hsync_len); + rinfo->panel_info.vOver_plus = var->lower_margin; + rinfo->panel_info.vSync_width = var->vsync_len; + rinfo->panel_info.vblank = var->upper_margin + + (var->lower_margin + var->vsync_len); + rinfo->panel_info.hAct_high = + (var->sync & FB_SYNC_HOR_HIGH_ACT) != 0; + rinfo->panel_info.vAct_high = + (var->sync & FB_SYNC_VERT_HIGH_ACT) != 0; + rinfo->panel_info.valid = 1; + /* We use a default of 200ms for the panel power delay, + * I need to have a real schedule() instead of mdelay's in the panel code. + * we might be possible to figure out a better power delay either from + * MacOS OF tree or from the EDID block (proprietary extensions ?) + */ + rinfo->panel_info.pwr_delay = 200; +} + +static void radeon_videomode_to_var(struct fb_var_screeninfo *var, + const struct fb_videomode *mode) +{ + var->xres = mode->xres; + var->yres = mode->yres; + var->xres_virtual = mode->xres; + var->yres_virtual = mode->yres; + var->xoffset = 0; + var->yoffset = 0; + var->pixclock = mode->pixclock; + var->left_margin = mode->left_margin; + var->right_margin = mode->right_margin; + var->upper_margin = mode->upper_margin; + var->lower_margin = mode->lower_margin; + var->hsync_len = mode->hsync_len; + var->vsync_len = mode->vsync_len; + var->sync = mode->sync; + var->vmode = mode->vmode; +} + +#ifdef CONFIG_PPC_PSERIES +static int is_powerblade(const char *model) +{ + struct device_node *root; + const char* cp; + int len, l, rc = 0; + + root = of_find_node_by_path("/"); + if (root && model) { + l = strlen(model); + cp = of_get_property(root, "model", &len); + if (cp) + rc = memcmp(model, cp, min(len, l)) == 0; + of_node_put(root); + } + return rc; +} +#endif + +/* + * Build the modedb for head 1 (head 2 will come later), check panel infos + * from either BIOS or EDID, and pick up the default mode + */ +void radeon_check_modes(struct radeonfb_info *rinfo, const char *mode_option) +{ + struct fb_info * info = rinfo->info; + int has_default_mode = 0; + + /* + * Fill default var first + */ + info->var = radeonfb_default_var; + INIT_LIST_HEAD(&info->modelist); + + /* + * First check out what BIOS has to say + */ + if (rinfo->mon1_type == MT_LCD) + radeon_get_panel_info_BIOS(rinfo); + + /* + * Parse EDID detailed timings and deduce panel infos if any. Right now + * we only deal with first entry returned by parse_EDID, we may do better + * some day... + */ + if (!rinfo->panel_info.use_bios_dividers && rinfo->mon1_type != MT_CRT + && rinfo->mon1_EDID) { + struct fb_var_screeninfo var; + pr_debug("Parsing EDID data for panel info\n"); + if (fb_parse_edid(rinfo->mon1_EDID, &var) == 0) { + if (var.xres >= rinfo->panel_info.xres && + var.yres >= rinfo->panel_info.yres) + radeon_var_to_panel_info(rinfo, &var); + } + } + + /* + * Do any additional platform/arch fixups to the panel infos + */ + radeon_fixup_panel_info(rinfo); + + /* + * If we have some valid panel infos, we setup the default mode based on + * those + */ + if (rinfo->mon1_type != MT_CRT && rinfo->panel_info.valid) { + struct fb_var_screeninfo *var = &info->var; + + pr_debug("Setting up default mode based on panel info\n"); + var->xres = rinfo->panel_info.xres; + var->yres = rinfo->panel_info.yres; + var->xres_virtual = rinfo->panel_info.xres; + var->yres_virtual = rinfo->panel_info.yres; + var->xoffset = var->yoffset = 0; + var->bits_per_pixel = 8; + var->pixclock = 100000000 / rinfo->panel_info.clock; + var->left_margin = (rinfo->panel_info.hblank - rinfo->panel_info.hOver_plus + - rinfo->panel_info.hSync_width); + var->right_margin = rinfo->panel_info.hOver_plus; + var->upper_margin = (rinfo->panel_info.vblank - rinfo->panel_info.vOver_plus + - rinfo->panel_info.vSync_width); + var->lower_margin = rinfo->panel_info.vOver_plus; + var->hsync_len = rinfo->panel_info.hSync_width; + var->vsync_len = rinfo->panel_info.vSync_width; + var->sync = 0; + if (rinfo->panel_info.hAct_high) + var->sync |= FB_SYNC_HOR_HIGH_ACT; + if (rinfo->panel_info.vAct_high) + var->sync |= FB_SYNC_VERT_HIGH_ACT; + var->vmode = 0; + has_default_mode = 1; + } + + /* + * Now build modedb from EDID + */ + if (rinfo->mon1_EDID) { + fb_edid_to_monspecs(rinfo->mon1_EDID, &info->monspecs); + fb_videomode_to_modelist(info->monspecs.modedb, + info->monspecs.modedb_len, + &info->modelist); + rinfo->mon1_modedb = info->monspecs.modedb; + rinfo->mon1_dbsize = info->monspecs.modedb_len; + } + + + /* + * Finally, if we don't have panel infos we need to figure some (or + * we try to read it from card), we try to pick a default mode + * and create some panel infos. Whatever... + */ + if (rinfo->mon1_type != MT_CRT && !rinfo->panel_info.valid) { + struct fb_videomode *modedb; + int dbsize; + char modename[32]; + + pr_debug("Guessing panel info...\n"); + if (rinfo->panel_info.xres == 0 || rinfo->panel_info.yres == 0) { + u32 tmp = INREG(FP_HORZ_STRETCH) & HORZ_PANEL_SIZE; + rinfo->panel_info.xres = ((tmp >> HORZ_PANEL_SHIFT) + 1) * 8; + tmp = INREG(FP_VERT_STRETCH) & VERT_PANEL_SIZE; + rinfo->panel_info.yres = (tmp >> VERT_PANEL_SHIFT) + 1; + } + if (rinfo->panel_info.xres == 0 || rinfo->panel_info.yres == 0) { + printk(KERN_WARNING "radeonfb: Can't find panel size, going back to CRT\n"); + rinfo->mon1_type = MT_CRT; + goto pickup_default; + } + printk(KERN_WARNING "radeonfb: Assuming panel size %dx%d\n", + rinfo->panel_info.xres, rinfo->panel_info.yres); + modedb = rinfo->mon1_modedb; + dbsize = rinfo->mon1_dbsize; + snprintf(modename, 31, "%dx%d", rinfo->panel_info.xres, rinfo->panel_info.yres); + if (fb_find_mode(&info->var, info, modename, + modedb, dbsize, NULL, 8) == 0) { + printk(KERN_WARNING "radeonfb: Can't find mode for panel size, going back to CRT\n"); + rinfo->mon1_type = MT_CRT; + goto pickup_default; + } + has_default_mode = 1; + radeon_var_to_panel_info(rinfo, &info->var); + } + + pickup_default: + /* + * Apply passed-in mode option if any + */ + if (mode_option) { + if (fb_find_mode(&info->var, info, mode_option, + info->monspecs.modedb, + info->monspecs.modedb_len, NULL, 8) != 0) + has_default_mode = 1; + } + +#ifdef CONFIG_PPC_PSERIES + if (!has_default_mode && ( + is_powerblade("IBM,8842") || /* JS20 */ + is_powerblade("IBM,8844") || /* JS21 */ + is_powerblade("IBM,7998") || /* JS12/JS21/JS22 */ + is_powerblade("IBM,0792") || /* QS21 */ + is_powerblade("IBM,0793") /* QS22 */ + )) { + printk("Falling back to 800x600 on JSxx hardware\n"); + if (fb_find_mode(&info->var, info, "800x600@60", + info->monspecs.modedb, + info->monspecs.modedb_len, NULL, 8) != 0) + has_default_mode = 1; + } +#endif + + /* + * Still no mode, let's pick up a default from the db + */ + if (!has_default_mode && info->monspecs.modedb != NULL) { + struct fb_monspecs *specs = &info->monspecs; + struct fb_videomode *modedb = NULL; + + /* get preferred timing */ + if (specs->misc & FB_MISC_1ST_DETAIL) { + int i; + + for (i = 0; i < specs->modedb_len; i++) { + if (specs->modedb[i].flag & FB_MODE_IS_FIRST) { + modedb = &specs->modedb[i]; + break; + } + } + } else { + /* otherwise, get first mode in database */ + modedb = &specs->modedb[0]; + } + if (modedb != NULL) { + info->var.bits_per_pixel = 8; + radeon_videomode_to_var(&info->var, modedb); + has_default_mode = 1; + } + } + if (1) { + struct fb_videomode mode; + /* Make sure that whatever mode got selected is actually in the + * modelist or the kernel may die + */ + fb_var_to_videomode(&mode, &info->var); + fb_add_videomode(&mode, &info->modelist); + } +} + +/* + * The code below is used to pick up a mode in check_var and + * set_var. It should be made generic + */ + +/* + * This is used when looking for modes. We assign a "distance" value + * to a mode in the modedb depending how "close" it is from what we + * are looking for. + * Currently, we don't compare that much, we could do better but + * the current fbcon doesn't quite mind ;) + */ +static int radeon_compare_modes(const struct fb_var_screeninfo *var, + const struct fb_videomode *mode) +{ + int distance = 0; + + distance = mode->yres - var->yres; + distance += (mode->xres - var->xres)/2; + return distance; +} + +/* + * This function is called by check_var, it gets the passed in mode parameter, and + * outputs a valid mode matching the passed-in one as closely as possible. + * We need something better ultimately. Things like fbcon basically pass us out + * current mode with xres/yres hacked, while things like XFree will actually + * produce a full timing that we should respect as much as possible. + * + * This is why I added the FB_ACTIVATE_FIND that is used by fbcon. Without this, + * we do a simple spec match, that's all. With it, we actually look for a mode in + * either our monitor modedb or the vesa one if none + * + */ +int radeon_match_mode(struct radeonfb_info *rinfo, + struct fb_var_screeninfo *dest, + const struct fb_var_screeninfo *src) +{ + const struct fb_videomode *db = vesa_modes; + int i, dbsize = 34; + int has_rmx, native_db = 0; + int distance = INT_MAX; + const struct fb_videomode *candidate = NULL; + + /* Start with a copy of the requested mode */ + memcpy(dest, src, sizeof(struct fb_var_screeninfo)); + + /* Check if we have a modedb built from EDID */ + if (rinfo->mon1_modedb) { + db = rinfo->mon1_modedb; + dbsize = rinfo->mon1_dbsize; + native_db = 1; + } + + /* Check if we have a scaler allowing any fancy mode */ + has_rmx = rinfo->mon1_type == MT_LCD || rinfo->mon1_type == MT_DFP; + + /* If we have a scaler and are passed FB_ACTIVATE_TEST or + * FB_ACTIVATE_NOW, just do basic checking and return if the + * mode match + */ + if ((src->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_TEST || + (src->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) { + /* We don't have an RMX, validate timings. If we don't have + * monspecs, we should be paranoid and not let use go above + * 640x480-60, but I assume userland knows what it's doing here + * (though I may be proven wrong...) + */ + if (has_rmx == 0 && rinfo->mon1_modedb) + if (fb_validate_mode((struct fb_var_screeninfo *)src, rinfo->info)) + return -EINVAL; + return 0; + } + + /* Now look for a mode in the database */ + while (db) { + for (i = 0; i < dbsize; i++) { + int d; + + if (db[i].yres < src->yres) + continue; + if (db[i].xres < src->xres) + continue; + d = radeon_compare_modes(src, &db[i]); + /* If the new mode is at least as good as the previous one, + * then it's our new candidate + */ + if (d < distance) { + candidate = &db[i]; + distance = d; + } + } + db = NULL; + /* If we have a scaler, we allow any mode from the database */ + if (native_db && has_rmx) { + db = vesa_modes; + dbsize = 34; + native_db = 0; + } + } + + /* If we have found a match, return it */ + if (candidate != NULL) { + radeon_videomode_to_var(dest, candidate); + return 0; + } + + /* If we haven't and don't have a scaler, fail */ + if (!has_rmx) + return -EINVAL; + + return 0; +} diff --git a/drivers/video/fbdev/aty/radeon_pm.c b/drivers/video/fbdev/aty/radeon_pm.c new file mode 100644 index 000000000000..46a12f1a93c3 --- /dev/null +++ b/drivers/video/fbdev/aty/radeon_pm.c @@ -0,0 +1,2906 @@ +/* + * drivers/video/aty/radeon_pm.c + * + * Copyright 2003,2004 Ben. Herrenschmidt <benh@kernel.crashing.org> + * Copyright 2004 Paul Mackerras <paulus@samba.org> + * + * This is the power management code for ATI radeon chipsets. It contains + * some dynamic clock PM enable/disable code similar to what X.org does, + * some D2-state (APM-style) sleep/wakeup code for use on some PowerMacs, + * and the necessary bits to re-initialize from scratch a few chips found + * on PowerMacs as well. The later could be extended to more platforms + * provided the memory controller configuration code be made more generic, + * and you can get the proper mode register commands for your RAMs. + * Those things may be found in the BIOS image... + */ + +#include "radeonfb.h" + +#include <linux/console.h> +#include <linux/agp_backend.h> + +#ifdef CONFIG_PPC_PMAC +#include <asm/machdep.h> +#include <asm/prom.h> +#include <asm/pmac_feature.h> +#endif + +#include "ati_ids.h" + +/* + * Workarounds for bugs in PC laptops: + * - enable D2 sleep in some IBM Thinkpads + * - special case for Samsung P35 + * + * Whitelist by subsystem vendor/device because + * its the subsystem vendor's fault! + */ + +#if defined(CONFIG_PM) && defined(CONFIG_X86) +static void radeon_reinitialize_M10(struct radeonfb_info *rinfo); + +struct radeon_device_id { + const char *ident; /* (arbitrary) Name */ + const unsigned short subsystem_vendor; /* Subsystem Vendor ID */ + const unsigned short subsystem_device; /* Subsystem Device ID */ + const enum radeon_pm_mode pm_mode_modifier; /* modify pm_mode */ + const reinit_function_ptr new_reinit_func; /* changed reinit_func */ +}; + +#define BUGFIX(model, sv, sd, pm, fn) { \ + .ident = model, \ + .subsystem_vendor = sv, \ + .subsystem_device = sd, \ + .pm_mode_modifier = pm, \ + .new_reinit_func = fn \ +} + +static struct radeon_device_id radeon_workaround_list[] = { + BUGFIX("IBM Thinkpad R32", + PCI_VENDOR_ID_IBM, 0x1905, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad R40", + PCI_VENDOR_ID_IBM, 0x0526, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad R40", + PCI_VENDOR_ID_IBM, 0x0527, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad R50/R51/T40/T41", + PCI_VENDOR_ID_IBM, 0x0531, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad R51/T40/T41/T42", + PCI_VENDOR_ID_IBM, 0x0530, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad T30", + PCI_VENDOR_ID_IBM, 0x0517, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad T40p", + PCI_VENDOR_ID_IBM, 0x054d, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad T42", + PCI_VENDOR_ID_IBM, 0x0550, + radeon_pm_d2, NULL), + BUGFIX("IBM Thinkpad X31/X32", + PCI_VENDOR_ID_IBM, 0x052f, + radeon_pm_d2, NULL), + BUGFIX("Samsung P35", + PCI_VENDOR_ID_SAMSUNG, 0xc00c, + radeon_pm_off, radeon_reinitialize_M10), + BUGFIX("Acer Aspire 2010", + PCI_VENDOR_ID_AI, 0x0061, + radeon_pm_off, radeon_reinitialize_M10), + BUGFIX("Acer Travelmate 290D/292LMi", + PCI_VENDOR_ID_AI, 0x005a, + radeon_pm_off, radeon_reinitialize_M10), + { .ident = NULL } +}; + +static int radeon_apply_workarounds(struct radeonfb_info *rinfo) +{ + struct radeon_device_id *id; + + for (id = radeon_workaround_list; id->ident != NULL; id++ ) + if ((id->subsystem_vendor == rinfo->pdev->subsystem_vendor ) && + (id->subsystem_device == rinfo->pdev->subsystem_device )) { + + /* we found a device that requires workaround */ + printk(KERN_DEBUG "radeonfb: %s detected" + ", enabling workaround\n", id->ident); + + rinfo->pm_mode |= id->pm_mode_modifier; + + if (id->new_reinit_func != NULL) + rinfo->reinit_func = id->new_reinit_func; + + return 1; + } + return 0; /* not found */ +} + +#else /* defined(CONFIG_PM) && defined(CONFIG_X86) */ +static inline int radeon_apply_workarounds(struct radeonfb_info *rinfo) +{ + return 0; +} +#endif /* defined(CONFIG_PM) && defined(CONFIG_X86) */ + + + +static void radeon_pm_disable_dynamic_mode(struct radeonfb_info *rinfo) +{ + u32 tmp; + + /* RV100 */ + if ((rinfo->family == CHIP_FAMILY_RV100) && (!rinfo->is_mobility)) { + if (rinfo->has_CRTC2) { + tmp = INPLL(pllSCLK_CNTL); + tmp &= ~SCLK_CNTL__DYN_STOP_LAT_MASK; + tmp |= SCLK_CNTL__CP_MAX_DYN_STOP_LAT | SCLK_CNTL__FORCEON_MASK; + OUTPLL(pllSCLK_CNTL, tmp); + } + tmp = INPLL(pllMCLK_CNTL); + tmp |= (MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_MCLKB | + MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB | + MCLK_CNTL__FORCE_AIC | + MCLK_CNTL__FORCE_MC); + OUTPLL(pllMCLK_CNTL, tmp); + return; + } + /* R100 */ + if (!rinfo->has_CRTC2) { + tmp = INPLL(pllSCLK_CNTL); + tmp |= (SCLK_CNTL__FORCE_CP | SCLK_CNTL__FORCE_HDP | + SCLK_CNTL__FORCE_DISP1 | SCLK_CNTL__FORCE_TOP | + SCLK_CNTL__FORCE_E2 | SCLK_CNTL__FORCE_SE | + SCLK_CNTL__FORCE_IDCT | SCLK_CNTL__FORCE_VIP | + SCLK_CNTL__FORCE_RE | SCLK_CNTL__FORCE_PB | + SCLK_CNTL__FORCE_TAM | SCLK_CNTL__FORCE_TDM | + SCLK_CNTL__FORCE_RB); + OUTPLL(pllSCLK_CNTL, tmp); + return; + } + /* RV350 (M10/M11) */ + if (rinfo->family == CHIP_FAMILY_RV350) { + /* for RV350/M10/M11, no delays are required. */ + tmp = INPLL(pllSCLK_CNTL2); + tmp |= (SCLK_CNTL2__R300_FORCE_TCL | + SCLK_CNTL2__R300_FORCE_GA | + SCLK_CNTL2__R300_FORCE_CBA); + OUTPLL(pllSCLK_CNTL2, tmp); + + tmp = INPLL(pllSCLK_CNTL); + tmp |= (SCLK_CNTL__FORCE_DISP2 | SCLK_CNTL__FORCE_CP | + SCLK_CNTL__FORCE_HDP | SCLK_CNTL__FORCE_DISP1 | + SCLK_CNTL__FORCE_TOP | SCLK_CNTL__FORCE_E2 | + SCLK_CNTL__R300_FORCE_VAP | SCLK_CNTL__FORCE_IDCT | + SCLK_CNTL__FORCE_VIP | SCLK_CNTL__R300_FORCE_SR | + SCLK_CNTL__R300_FORCE_PX | SCLK_CNTL__R300_FORCE_TX | + SCLK_CNTL__R300_FORCE_US | SCLK_CNTL__FORCE_TV_SCLK | + SCLK_CNTL__R300_FORCE_SU | SCLK_CNTL__FORCE_OV0); + OUTPLL(pllSCLK_CNTL, tmp); + + tmp = INPLL(pllSCLK_MORE_CNTL); + tmp |= (SCLK_MORE_CNTL__FORCE_DISPREGS | SCLK_MORE_CNTL__FORCE_MC_GUI | + SCLK_MORE_CNTL__FORCE_MC_HOST); + OUTPLL(pllSCLK_MORE_CNTL, tmp); + + tmp = INPLL(pllMCLK_CNTL); + tmp |= (MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_MCLKB | + MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB | + MCLK_CNTL__FORCE_MC); + OUTPLL(pllMCLK_CNTL, tmp); + + tmp = INPLL(pllVCLK_ECP_CNTL); + tmp &= ~(VCLK_ECP_CNTL__PIXCLK_ALWAYS_ONb | + VCLK_ECP_CNTL__PIXCLK_DAC_ALWAYS_ONb | + VCLK_ECP_CNTL__R300_DISP_DAC_PIXCLK_DAC_BLANK_OFF); + OUTPLL(pllVCLK_ECP_CNTL, tmp); + + tmp = INPLL(pllPIXCLKS_CNTL); + tmp &= ~(PIXCLKS_CNTL__PIX2CLK_ALWAYS_ONb | + PIXCLKS_CNTL__PIX2CLK_DAC_ALWAYS_ONb | + PIXCLKS_CNTL__DISP_TVOUT_PIXCLK_TV_ALWAYS_ONb | + PIXCLKS_CNTL__R300_DVOCLK_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_BLEND_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_GV_ALWAYS_ONb | + PIXCLKS_CNTL__R300_PIXCLK_DVO_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_LVDS_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_TMDS_ALWAYS_ONb | + PIXCLKS_CNTL__R300_PIXCLK_TRANS_ALWAYS_ONb | + PIXCLKS_CNTL__R300_PIXCLK_TVO_ALWAYS_ONb | + PIXCLKS_CNTL__R300_P2G2CLK_ALWAYS_ONb | + PIXCLKS_CNTL__R300_DISP_DAC_PIXCLK_DAC2_BLANK_OFF); + OUTPLL(pllPIXCLKS_CNTL, tmp); + + return; + } + + /* Default */ + + /* Force Core Clocks */ + tmp = INPLL(pllSCLK_CNTL); + tmp |= (SCLK_CNTL__FORCE_CP | SCLK_CNTL__FORCE_E2); + + /* XFree doesn't do that case, but we had this code from Apple and it + * seem necessary for proper suspend/resume operations + */ + if (rinfo->is_mobility) { + tmp |= SCLK_CNTL__FORCE_HDP| + SCLK_CNTL__FORCE_DISP1| + SCLK_CNTL__FORCE_DISP2| + SCLK_CNTL__FORCE_TOP| + SCLK_CNTL__FORCE_SE| + SCLK_CNTL__FORCE_IDCT| + SCLK_CNTL__FORCE_VIP| + SCLK_CNTL__FORCE_PB| + SCLK_CNTL__FORCE_RE| + SCLK_CNTL__FORCE_TAM| + SCLK_CNTL__FORCE_TDM| + SCLK_CNTL__FORCE_RB| + SCLK_CNTL__FORCE_TV_SCLK| + SCLK_CNTL__FORCE_SUBPIC| + SCLK_CNTL__FORCE_OV0; + } + else if (rinfo->family == CHIP_FAMILY_R300 || + rinfo->family == CHIP_FAMILY_R350) { + tmp |= SCLK_CNTL__FORCE_HDP | + SCLK_CNTL__FORCE_DISP1 | + SCLK_CNTL__FORCE_DISP2 | + SCLK_CNTL__FORCE_TOP | + SCLK_CNTL__FORCE_IDCT | + SCLK_CNTL__FORCE_VIP; + } + OUTPLL(pllSCLK_CNTL, tmp); + radeon_msleep(16); + + if (rinfo->family == CHIP_FAMILY_R300 || rinfo->family == CHIP_FAMILY_R350) { + tmp = INPLL(pllSCLK_CNTL2); + tmp |= SCLK_CNTL2__R300_FORCE_TCL | + SCLK_CNTL2__R300_FORCE_GA | + SCLK_CNTL2__R300_FORCE_CBA; + OUTPLL(pllSCLK_CNTL2, tmp); + radeon_msleep(16); + } + + tmp = INPLL(pllCLK_PIN_CNTL); + tmp &= ~CLK_PIN_CNTL__SCLK_DYN_START_CNTL; + OUTPLL(pllCLK_PIN_CNTL, tmp); + radeon_msleep(15); + + if (rinfo->is_IGP) { + /* Weird ... X is _un_ forcing clocks here, I think it's + * doing backward. Imitate it for now... + */ + tmp = INPLL(pllMCLK_CNTL); + tmp &= ~(MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_YCLKA); + OUTPLL(pllMCLK_CNTL, tmp); + radeon_msleep(16); + } + /* Hrm... same shit, X doesn't do that but I have to */ + else if (rinfo->is_mobility) { + tmp = INPLL(pllMCLK_CNTL); + tmp |= (MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_MCLKB | + MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB); + OUTPLL(pllMCLK_CNTL, tmp); + radeon_msleep(16); + + tmp = INPLL(pllMCLK_MISC); + tmp &= ~(MCLK_MISC__MC_MCLK_MAX_DYN_STOP_LAT| + MCLK_MISC__IO_MCLK_MAX_DYN_STOP_LAT| + MCLK_MISC__MC_MCLK_DYN_ENABLE| + MCLK_MISC__IO_MCLK_DYN_ENABLE); + OUTPLL(pllMCLK_MISC, tmp); + radeon_msleep(15); + } + + if (rinfo->is_mobility) { + tmp = INPLL(pllSCLK_MORE_CNTL); + tmp |= SCLK_MORE_CNTL__FORCE_DISPREGS| + SCLK_MORE_CNTL__FORCE_MC_GUI| + SCLK_MORE_CNTL__FORCE_MC_HOST; + OUTPLL(pllSCLK_MORE_CNTL, tmp); + radeon_msleep(16); + } + + tmp = INPLL(pllPIXCLKS_CNTL); + tmp &= ~(PIXCLKS_CNTL__PIXCLK_GV_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_BLEND_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_DIG_TMDS_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_LVDS_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_TMDS_ALWAYS_ONb| + PIXCLKS_CNTL__PIX2CLK_ALWAYS_ONb| + PIXCLKS_CNTL__PIX2CLK_DAC_ALWAYS_ONb); + OUTPLL(pllPIXCLKS_CNTL, tmp); + radeon_msleep(16); + + tmp = INPLL( pllVCLK_ECP_CNTL); + tmp &= ~(VCLK_ECP_CNTL__PIXCLK_ALWAYS_ONb | + VCLK_ECP_CNTL__PIXCLK_DAC_ALWAYS_ONb); + OUTPLL( pllVCLK_ECP_CNTL, tmp); + radeon_msleep(16); +} + +static void radeon_pm_enable_dynamic_mode(struct radeonfb_info *rinfo) +{ + u32 tmp; + + /* R100 */ + if (!rinfo->has_CRTC2) { + tmp = INPLL(pllSCLK_CNTL); + + if ((INREG(CNFG_CNTL) & CFG_ATI_REV_ID_MASK) > CFG_ATI_REV_A13) + tmp &= ~(SCLK_CNTL__FORCE_CP | SCLK_CNTL__FORCE_RB); + tmp &= ~(SCLK_CNTL__FORCE_HDP | SCLK_CNTL__FORCE_DISP1 | + SCLK_CNTL__FORCE_TOP | SCLK_CNTL__FORCE_SE | + SCLK_CNTL__FORCE_IDCT | SCLK_CNTL__FORCE_RE | + SCLK_CNTL__FORCE_PB | SCLK_CNTL__FORCE_TAM | + SCLK_CNTL__FORCE_TDM); + OUTPLL(pllSCLK_CNTL, tmp); + return; + } + + /* M10/M11 */ + if (rinfo->family == CHIP_FAMILY_RV350) { + tmp = INPLL(pllSCLK_CNTL2); + tmp &= ~(SCLK_CNTL2__R300_FORCE_TCL | + SCLK_CNTL2__R300_FORCE_GA | + SCLK_CNTL2__R300_FORCE_CBA); + tmp |= (SCLK_CNTL2__R300_TCL_MAX_DYN_STOP_LAT | + SCLK_CNTL2__R300_GA_MAX_DYN_STOP_LAT | + SCLK_CNTL2__R300_CBA_MAX_DYN_STOP_LAT); + OUTPLL(pllSCLK_CNTL2, tmp); + + tmp = INPLL(pllSCLK_CNTL); + tmp &= ~(SCLK_CNTL__FORCE_DISP2 | SCLK_CNTL__FORCE_CP | + SCLK_CNTL__FORCE_HDP | SCLK_CNTL__FORCE_DISP1 | + SCLK_CNTL__FORCE_TOP | SCLK_CNTL__FORCE_E2 | + SCLK_CNTL__R300_FORCE_VAP | SCLK_CNTL__FORCE_IDCT | + SCLK_CNTL__FORCE_VIP | SCLK_CNTL__R300_FORCE_SR | + SCLK_CNTL__R300_FORCE_PX | SCLK_CNTL__R300_FORCE_TX | + SCLK_CNTL__R300_FORCE_US | SCLK_CNTL__FORCE_TV_SCLK | + SCLK_CNTL__R300_FORCE_SU | SCLK_CNTL__FORCE_OV0); + tmp |= SCLK_CNTL__DYN_STOP_LAT_MASK; + OUTPLL(pllSCLK_CNTL, tmp); + + tmp = INPLL(pllSCLK_MORE_CNTL); + tmp &= ~SCLK_MORE_CNTL__FORCEON; + tmp |= SCLK_MORE_CNTL__DISPREGS_MAX_DYN_STOP_LAT | + SCLK_MORE_CNTL__MC_GUI_MAX_DYN_STOP_LAT | + SCLK_MORE_CNTL__MC_HOST_MAX_DYN_STOP_LAT; + OUTPLL(pllSCLK_MORE_CNTL, tmp); + + tmp = INPLL(pllVCLK_ECP_CNTL); + tmp |= (VCLK_ECP_CNTL__PIXCLK_ALWAYS_ONb | + VCLK_ECP_CNTL__PIXCLK_DAC_ALWAYS_ONb); + OUTPLL(pllVCLK_ECP_CNTL, tmp); + + tmp = INPLL(pllPIXCLKS_CNTL); + tmp |= (PIXCLKS_CNTL__PIX2CLK_ALWAYS_ONb | + PIXCLKS_CNTL__PIX2CLK_DAC_ALWAYS_ONb | + PIXCLKS_CNTL__DISP_TVOUT_PIXCLK_TV_ALWAYS_ONb | + PIXCLKS_CNTL__R300_DVOCLK_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_BLEND_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_GV_ALWAYS_ONb | + PIXCLKS_CNTL__R300_PIXCLK_DVO_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_LVDS_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_TMDS_ALWAYS_ONb | + PIXCLKS_CNTL__R300_PIXCLK_TRANS_ALWAYS_ONb | + PIXCLKS_CNTL__R300_PIXCLK_TVO_ALWAYS_ONb | + PIXCLKS_CNTL__R300_P2G2CLK_ALWAYS_ONb | + PIXCLKS_CNTL__R300_P2G2CLK_DAC_ALWAYS_ONb); + OUTPLL(pllPIXCLKS_CNTL, tmp); + + tmp = INPLL(pllMCLK_MISC); + tmp |= (MCLK_MISC__MC_MCLK_DYN_ENABLE | + MCLK_MISC__IO_MCLK_DYN_ENABLE); + OUTPLL(pllMCLK_MISC, tmp); + + tmp = INPLL(pllMCLK_CNTL); + tmp |= (MCLK_CNTL__FORCE_MCLKA | MCLK_CNTL__FORCE_MCLKB); + tmp &= ~(MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB | + MCLK_CNTL__FORCE_MC); + + /* Some releases of vbios have set DISABLE_MC_MCLKA + * and DISABLE_MC_MCLKB bits in the vbios table. Setting these + * bits will cause H/W hang when reading video memory with dynamic + * clocking enabled. + */ + if ((tmp & MCLK_CNTL__R300_DISABLE_MC_MCLKA) && + (tmp & MCLK_CNTL__R300_DISABLE_MC_MCLKB)) { + /* If both bits are set, then check the active channels */ + tmp = INPLL(pllMCLK_CNTL); + if (rinfo->vram_width == 64) { + if (INREG(MEM_CNTL) & R300_MEM_USE_CD_CH_ONLY) + tmp &= ~MCLK_CNTL__R300_DISABLE_MC_MCLKB; + else + tmp &= ~MCLK_CNTL__R300_DISABLE_MC_MCLKA; + } else { + tmp &= ~(MCLK_CNTL__R300_DISABLE_MC_MCLKA | + MCLK_CNTL__R300_DISABLE_MC_MCLKB); + } + } + OUTPLL(pllMCLK_CNTL, tmp); + return; + } + + /* R300 */ + if (rinfo->family == CHIP_FAMILY_R300 || rinfo->family == CHIP_FAMILY_R350) { + tmp = INPLL(pllSCLK_CNTL); + tmp &= ~(SCLK_CNTL__R300_FORCE_VAP); + tmp |= SCLK_CNTL__FORCE_CP; + OUTPLL(pllSCLK_CNTL, tmp); + radeon_msleep(15); + + tmp = INPLL(pllSCLK_CNTL2); + tmp &= ~(SCLK_CNTL2__R300_FORCE_TCL | + SCLK_CNTL2__R300_FORCE_GA | + SCLK_CNTL2__R300_FORCE_CBA); + OUTPLL(pllSCLK_CNTL2, tmp); + } + + /* Others */ + + tmp = INPLL( pllCLK_PWRMGT_CNTL); + tmp &= ~(CLK_PWRMGT_CNTL__ACTIVE_HILO_LAT_MASK| + CLK_PWRMGT_CNTL__DISP_DYN_STOP_LAT_MASK| + CLK_PWRMGT_CNTL__DYN_STOP_MODE_MASK); + tmp |= CLK_PWRMGT_CNTL__ENGINE_DYNCLK_MODE_MASK | + (0x01 << CLK_PWRMGT_CNTL__ACTIVE_HILO_LAT__SHIFT); + OUTPLL( pllCLK_PWRMGT_CNTL, tmp); + radeon_msleep(15); + + tmp = INPLL(pllCLK_PIN_CNTL); + tmp |= CLK_PIN_CNTL__SCLK_DYN_START_CNTL; + OUTPLL(pllCLK_PIN_CNTL, tmp); + radeon_msleep(15); + + /* When DRI is enabled, setting DYN_STOP_LAT to zero can cause some R200 + * to lockup randomly, leave them as set by BIOS. + */ + tmp = INPLL(pllSCLK_CNTL); + tmp &= ~SCLK_CNTL__FORCEON_MASK; + + /*RAGE_6::A11 A12 A12N1 A13, RV250::A11 A12, R300*/ + if ((rinfo->family == CHIP_FAMILY_RV250 && + ((INREG(CNFG_CNTL) & CFG_ATI_REV_ID_MASK) < CFG_ATI_REV_A13)) || + ((rinfo->family == CHIP_FAMILY_RV100) && + ((INREG(CNFG_CNTL) & CFG_ATI_REV_ID_MASK) <= CFG_ATI_REV_A13))) { + tmp |= SCLK_CNTL__FORCE_CP; + tmp |= SCLK_CNTL__FORCE_VIP; + } + OUTPLL(pllSCLK_CNTL, tmp); + radeon_msleep(15); + + if ((rinfo->family == CHIP_FAMILY_RV200) || + (rinfo->family == CHIP_FAMILY_RV250) || + (rinfo->family == CHIP_FAMILY_RV280)) { + tmp = INPLL(pllSCLK_MORE_CNTL); + tmp &= ~SCLK_MORE_CNTL__FORCEON; + + /* RV200::A11 A12 RV250::A11 A12 */ + if (((rinfo->family == CHIP_FAMILY_RV200) || + (rinfo->family == CHIP_FAMILY_RV250)) && + ((INREG(CNFG_CNTL) & CFG_ATI_REV_ID_MASK) < CFG_ATI_REV_A13)) + tmp |= SCLK_MORE_CNTL__FORCEON; + + OUTPLL(pllSCLK_MORE_CNTL, tmp); + radeon_msleep(15); + } + + + /* RV200::A11 A12, RV250::A11 A12 */ + if (((rinfo->family == CHIP_FAMILY_RV200) || + (rinfo->family == CHIP_FAMILY_RV250)) && + ((INREG(CNFG_CNTL) & CFG_ATI_REV_ID_MASK) < CFG_ATI_REV_A13)) { + tmp = INPLL(pllPLL_PWRMGT_CNTL); + tmp |= PLL_PWRMGT_CNTL__TCL_BYPASS_DISABLE; + OUTPLL(pllPLL_PWRMGT_CNTL, tmp); + radeon_msleep(15); + } + + tmp = INPLL(pllPIXCLKS_CNTL); + tmp |= PIXCLKS_CNTL__PIX2CLK_ALWAYS_ONb | + PIXCLKS_CNTL__PIX2CLK_DAC_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_BLEND_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_GV_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_DIG_TMDS_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_LVDS_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_TMDS_ALWAYS_ONb; + OUTPLL(pllPIXCLKS_CNTL, tmp); + radeon_msleep(15); + + tmp = INPLL(pllVCLK_ECP_CNTL); + tmp |= VCLK_ECP_CNTL__PIXCLK_ALWAYS_ONb | + VCLK_ECP_CNTL__PIXCLK_DAC_ALWAYS_ONb; + OUTPLL(pllVCLK_ECP_CNTL, tmp); + + /* X doesn't do that ... hrm, we do on mobility && Macs */ +#ifdef CONFIG_PPC_OF + if (rinfo->is_mobility) { + tmp = INPLL(pllMCLK_CNTL); + tmp &= ~(MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_MCLKB | + MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB); + OUTPLL(pllMCLK_CNTL, tmp); + radeon_msleep(15); + + tmp = INPLL(pllMCLK_MISC); + tmp |= MCLK_MISC__MC_MCLK_MAX_DYN_STOP_LAT| + MCLK_MISC__IO_MCLK_MAX_DYN_STOP_LAT| + MCLK_MISC__MC_MCLK_DYN_ENABLE| + MCLK_MISC__IO_MCLK_DYN_ENABLE; + OUTPLL(pllMCLK_MISC, tmp); + radeon_msleep(15); + } +#endif /* CONFIG_PPC_OF */ +} + +#ifdef CONFIG_PM + +static void OUTMC( struct radeonfb_info *rinfo, u8 indx, u32 value) +{ + OUTREG( MC_IND_INDEX, indx | MC_IND_INDEX__MC_IND_WR_EN); + OUTREG( MC_IND_DATA, value); +} + +static u32 INMC(struct radeonfb_info *rinfo, u8 indx) +{ + OUTREG( MC_IND_INDEX, indx); + return INREG( MC_IND_DATA); +} + +static void radeon_pm_save_regs(struct radeonfb_info *rinfo, int saving_for_d3) +{ + rinfo->save_regs[0] = INPLL(PLL_PWRMGT_CNTL); + rinfo->save_regs[1] = INPLL(CLK_PWRMGT_CNTL); + rinfo->save_regs[2] = INPLL(MCLK_CNTL); + rinfo->save_regs[3] = INPLL(SCLK_CNTL); + rinfo->save_regs[4] = INPLL(CLK_PIN_CNTL); + rinfo->save_regs[5] = INPLL(VCLK_ECP_CNTL); + rinfo->save_regs[6] = INPLL(PIXCLKS_CNTL); + rinfo->save_regs[7] = INPLL(MCLK_MISC); + rinfo->save_regs[8] = INPLL(P2PLL_CNTL); + + rinfo->save_regs[9] = INREG(DISP_MISC_CNTL); + rinfo->save_regs[10] = INREG(DISP_PWR_MAN); + rinfo->save_regs[11] = INREG(LVDS_GEN_CNTL); + rinfo->save_regs[13] = INREG(TV_DAC_CNTL); + rinfo->save_regs[14] = INREG(BUS_CNTL1); + rinfo->save_regs[15] = INREG(CRTC_OFFSET_CNTL); + rinfo->save_regs[16] = INREG(AGP_CNTL); + rinfo->save_regs[17] = (INREG(CRTC_GEN_CNTL) & 0xfdffffff) | 0x04000000; + rinfo->save_regs[18] = (INREG(CRTC2_GEN_CNTL) & 0xfdffffff) | 0x04000000; + rinfo->save_regs[19] = INREG(GPIOPAD_A); + rinfo->save_regs[20] = INREG(GPIOPAD_EN); + rinfo->save_regs[21] = INREG(GPIOPAD_MASK); + rinfo->save_regs[22] = INREG(ZV_LCDPAD_A); + rinfo->save_regs[23] = INREG(ZV_LCDPAD_EN); + rinfo->save_regs[24] = INREG(ZV_LCDPAD_MASK); + rinfo->save_regs[25] = INREG(GPIO_VGA_DDC); + rinfo->save_regs[26] = INREG(GPIO_DVI_DDC); + rinfo->save_regs[27] = INREG(GPIO_MONID); + rinfo->save_regs[28] = INREG(GPIO_CRT2_DDC); + + rinfo->save_regs[29] = INREG(SURFACE_CNTL); + rinfo->save_regs[30] = INREG(MC_FB_LOCATION); + rinfo->save_regs[31] = INREG(DISPLAY_BASE_ADDR); + rinfo->save_regs[32] = INREG(MC_AGP_LOCATION); + rinfo->save_regs[33] = INREG(CRTC2_DISPLAY_BASE_ADDR); + + rinfo->save_regs[34] = INPLL(SCLK_MORE_CNTL); + rinfo->save_regs[35] = INREG(MEM_SDRAM_MODE_REG); + rinfo->save_regs[36] = INREG(BUS_CNTL); + rinfo->save_regs[39] = INREG(RBBM_CNTL); + rinfo->save_regs[40] = INREG(DAC_CNTL); + rinfo->save_regs[41] = INREG(HOST_PATH_CNTL); + rinfo->save_regs[37] = INREG(MPP_TB_CONFIG); + rinfo->save_regs[38] = INREG(FCP_CNTL); + + if (rinfo->is_mobility) { + rinfo->save_regs[12] = INREG(LVDS_PLL_CNTL); + rinfo->save_regs[43] = INPLL(pllSSPLL_CNTL); + rinfo->save_regs[44] = INPLL(pllSSPLL_REF_DIV); + rinfo->save_regs[45] = INPLL(pllSSPLL_DIV_0); + rinfo->save_regs[90] = INPLL(pllSS_INT_CNTL); + rinfo->save_regs[91] = INPLL(pllSS_TST_CNTL); + rinfo->save_regs[81] = INREG(LVDS_GEN_CNTL); + } + + if (rinfo->family >= CHIP_FAMILY_RV200) { + rinfo->save_regs[42] = INREG(MEM_REFRESH_CNTL); + rinfo->save_regs[46] = INREG(MC_CNTL); + rinfo->save_regs[47] = INREG(MC_INIT_GFX_LAT_TIMER); + rinfo->save_regs[48] = INREG(MC_INIT_MISC_LAT_TIMER); + rinfo->save_regs[49] = INREG(MC_TIMING_CNTL); + rinfo->save_regs[50] = INREG(MC_READ_CNTL_AB); + rinfo->save_regs[51] = INREG(MC_IOPAD_CNTL); + rinfo->save_regs[52] = INREG(MC_CHIP_IO_OE_CNTL_AB); + rinfo->save_regs[53] = INREG(MC_DEBUG); + } + rinfo->save_regs[54] = INREG(PAMAC0_DLY_CNTL); + rinfo->save_regs[55] = INREG(PAMAC1_DLY_CNTL); + rinfo->save_regs[56] = INREG(PAD_CTLR_MISC); + rinfo->save_regs[57] = INREG(FW_CNTL); + + if (rinfo->family >= CHIP_FAMILY_R300) { + rinfo->save_regs[58] = INMC(rinfo, ixR300_MC_MC_INIT_WR_LAT_TIMER); + rinfo->save_regs[59] = INMC(rinfo, ixR300_MC_IMP_CNTL); + rinfo->save_regs[60] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_C0); + rinfo->save_regs[61] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_C1); + rinfo->save_regs[62] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_D0); + rinfo->save_regs[63] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_D1); + rinfo->save_regs[64] = INMC(rinfo, ixR300_MC_BIST_CNTL_3); + rinfo->save_regs[65] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_A0); + rinfo->save_regs[66] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_A1); + rinfo->save_regs[67] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_B0); + rinfo->save_regs[68] = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_B1); + rinfo->save_regs[69] = INMC(rinfo, ixR300_MC_DEBUG_CNTL); + rinfo->save_regs[70] = INMC(rinfo, ixR300_MC_DLL_CNTL); + rinfo->save_regs[71] = INMC(rinfo, ixR300_MC_IMP_CNTL_0); + rinfo->save_regs[72] = INMC(rinfo, ixR300_MC_ELPIDA_CNTL); + rinfo->save_regs[96] = INMC(rinfo, ixR300_MC_READ_CNTL_CD); + } else { + rinfo->save_regs[59] = INMC(rinfo, ixMC_IMP_CNTL); + rinfo->save_regs[65] = INMC(rinfo, ixMC_CHP_IO_CNTL_A0); + rinfo->save_regs[66] = INMC(rinfo, ixMC_CHP_IO_CNTL_A1); + rinfo->save_regs[67] = INMC(rinfo, ixMC_CHP_IO_CNTL_B0); + rinfo->save_regs[68] = INMC(rinfo, ixMC_CHP_IO_CNTL_B1); + rinfo->save_regs[71] = INMC(rinfo, ixMC_IMP_CNTL_0); + } + + rinfo->save_regs[73] = INPLL(pllMPLL_CNTL); + rinfo->save_regs[74] = INPLL(pllSPLL_CNTL); + rinfo->save_regs[75] = INPLL(pllMPLL_AUX_CNTL); + rinfo->save_regs[76] = INPLL(pllSPLL_AUX_CNTL); + rinfo->save_regs[77] = INPLL(pllM_SPLL_REF_FB_DIV); + rinfo->save_regs[78] = INPLL(pllAGP_PLL_CNTL); + rinfo->save_regs[79] = INREG(PAMAC2_DLY_CNTL); + + rinfo->save_regs[80] = INREG(OV0_BASE_ADDR); + rinfo->save_regs[82] = INREG(FP_GEN_CNTL); + rinfo->save_regs[83] = INREG(FP2_GEN_CNTL); + rinfo->save_regs[84] = INREG(TMDS_CNTL); + rinfo->save_regs[85] = INREG(TMDS_TRANSMITTER_CNTL); + rinfo->save_regs[86] = INREG(DISP_OUTPUT_CNTL); + rinfo->save_regs[87] = INREG(DISP_HW_DEBUG); + rinfo->save_regs[88] = INREG(TV_MASTER_CNTL); + rinfo->save_regs[89] = INPLL(pllP2PLL_REF_DIV); + rinfo->save_regs[92] = INPLL(pllPPLL_DIV_0); + rinfo->save_regs[93] = INPLL(pllPPLL_CNTL); + rinfo->save_regs[94] = INREG(GRPH_BUFFER_CNTL); + rinfo->save_regs[95] = INREG(GRPH2_BUFFER_CNTL); + rinfo->save_regs[96] = INREG(HDP_DEBUG); + rinfo->save_regs[97] = INPLL(pllMDLL_CKO); + rinfo->save_regs[98] = INPLL(pllMDLL_RDCKA); + rinfo->save_regs[99] = INPLL(pllMDLL_RDCKB); +} + +static void radeon_pm_restore_regs(struct radeonfb_info *rinfo) +{ + OUTPLL(P2PLL_CNTL, rinfo->save_regs[8] & 0xFFFFFFFE); /* First */ + + OUTPLL(PLL_PWRMGT_CNTL, rinfo->save_regs[0]); + OUTPLL(CLK_PWRMGT_CNTL, rinfo->save_regs[1]); + OUTPLL(MCLK_CNTL, rinfo->save_regs[2]); + OUTPLL(SCLK_CNTL, rinfo->save_regs[3]); + OUTPLL(CLK_PIN_CNTL, rinfo->save_regs[4]); + OUTPLL(VCLK_ECP_CNTL, rinfo->save_regs[5]); + OUTPLL(PIXCLKS_CNTL, rinfo->save_regs[6]); + OUTPLL(MCLK_MISC, rinfo->save_regs[7]); + if (rinfo->family == CHIP_FAMILY_RV350) + OUTPLL(SCLK_MORE_CNTL, rinfo->save_regs[34]); + + OUTREG(SURFACE_CNTL, rinfo->save_regs[29]); + OUTREG(MC_FB_LOCATION, rinfo->save_regs[30]); + OUTREG(DISPLAY_BASE_ADDR, rinfo->save_regs[31]); + OUTREG(MC_AGP_LOCATION, rinfo->save_regs[32]); + OUTREG(CRTC2_DISPLAY_BASE_ADDR, rinfo->save_regs[33]); + OUTREG(CNFG_MEMSIZE, rinfo->video_ram); + + OUTREG(DISP_MISC_CNTL, rinfo->save_regs[9]); + OUTREG(DISP_PWR_MAN, rinfo->save_regs[10]); + OUTREG(LVDS_GEN_CNTL, rinfo->save_regs[11]); + OUTREG(LVDS_PLL_CNTL,rinfo->save_regs[12]); + OUTREG(TV_DAC_CNTL, rinfo->save_regs[13]); + OUTREG(BUS_CNTL1, rinfo->save_regs[14]); + OUTREG(CRTC_OFFSET_CNTL, rinfo->save_regs[15]); + OUTREG(AGP_CNTL, rinfo->save_regs[16]); + OUTREG(CRTC_GEN_CNTL, rinfo->save_regs[17]); + OUTREG(CRTC2_GEN_CNTL, rinfo->save_regs[18]); + OUTPLL(P2PLL_CNTL, rinfo->save_regs[8]); + + OUTREG(GPIOPAD_A, rinfo->save_regs[19]); + OUTREG(GPIOPAD_EN, rinfo->save_regs[20]); + OUTREG(GPIOPAD_MASK, rinfo->save_regs[21]); + OUTREG(ZV_LCDPAD_A, rinfo->save_regs[22]); + OUTREG(ZV_LCDPAD_EN, rinfo->save_regs[23]); + OUTREG(ZV_LCDPAD_MASK, rinfo->save_regs[24]); + OUTREG(GPIO_VGA_DDC, rinfo->save_regs[25]); + OUTREG(GPIO_DVI_DDC, rinfo->save_regs[26]); + OUTREG(GPIO_MONID, rinfo->save_regs[27]); + OUTREG(GPIO_CRT2_DDC, rinfo->save_regs[28]); +} + +static void radeon_pm_disable_iopad(struct radeonfb_info *rinfo) +{ + OUTREG(GPIOPAD_MASK, 0x0001ffff); + OUTREG(GPIOPAD_EN, 0x00000400); + OUTREG(GPIOPAD_A, 0x00000000); + OUTREG(ZV_LCDPAD_MASK, 0x00000000); + OUTREG(ZV_LCDPAD_EN, 0x00000000); + OUTREG(ZV_LCDPAD_A, 0x00000000); + OUTREG(GPIO_VGA_DDC, 0x00030000); + OUTREG(GPIO_DVI_DDC, 0x00000000); + OUTREG(GPIO_MONID, 0x00030000); + OUTREG(GPIO_CRT2_DDC, 0x00000000); +} + +static void radeon_pm_program_v2clk(struct radeonfb_info *rinfo) +{ + /* Set v2clk to 65MHz */ + if (rinfo->family <= CHIP_FAMILY_RV280) { + OUTPLL(pllPIXCLKS_CNTL, + __INPLL(rinfo, pllPIXCLKS_CNTL) + & ~PIXCLKS_CNTL__PIX2CLK_SRC_SEL_MASK); + + OUTPLL(pllP2PLL_REF_DIV, 0x0000000c); + OUTPLL(pllP2PLL_CNTL, 0x0000bf00); + } else { + OUTPLL(pllP2PLL_REF_DIV, 0x0000000c); + INPLL(pllP2PLL_REF_DIV); + OUTPLL(pllP2PLL_CNTL, 0x0000a700); + } + + OUTPLL(pllP2PLL_DIV_0, 0x00020074 | P2PLL_DIV_0__P2PLL_ATOMIC_UPDATE_W); + + OUTPLL(pllP2PLL_CNTL, INPLL(pllP2PLL_CNTL) & ~P2PLL_CNTL__P2PLL_SLEEP); + mdelay(1); + + OUTPLL(pllP2PLL_CNTL, INPLL(pllP2PLL_CNTL) & ~P2PLL_CNTL__P2PLL_RESET); + mdelay( 1); + + OUTPLL(pllPIXCLKS_CNTL, + (INPLL(pllPIXCLKS_CNTL) & ~PIXCLKS_CNTL__PIX2CLK_SRC_SEL_MASK) + | (0x03 << PIXCLKS_CNTL__PIX2CLK_SRC_SEL__SHIFT)); + mdelay( 1); +} + +static void radeon_pm_low_current(struct radeonfb_info *rinfo) +{ + u32 reg; + + reg = INREG(BUS_CNTL1); + if (rinfo->family <= CHIP_FAMILY_RV280) { + reg &= ~BUS_CNTL1_MOBILE_PLATFORM_SEL_MASK; + reg |= BUS_CNTL1_AGPCLK_VALID | (1<<BUS_CNTL1_MOBILE_PLATFORM_SEL_SHIFT); + } else { + reg |= 0x4080; + } + OUTREG(BUS_CNTL1, reg); + + reg = INPLL(PLL_PWRMGT_CNTL); + reg |= PLL_PWRMGT_CNTL_SPLL_TURNOFF | PLL_PWRMGT_CNTL_PPLL_TURNOFF | + PLL_PWRMGT_CNTL_P2PLL_TURNOFF | PLL_PWRMGT_CNTL_TVPLL_TURNOFF; + reg &= ~PLL_PWRMGT_CNTL_SU_MCLK_USE_BCLK; + reg &= ~PLL_PWRMGT_CNTL_MOBILE_SU; + OUTPLL(PLL_PWRMGT_CNTL, reg); + + reg = INREG(TV_DAC_CNTL); + reg &= ~(TV_DAC_CNTL_BGADJ_MASK |TV_DAC_CNTL_DACADJ_MASK); + reg |=TV_DAC_CNTL_BGSLEEP | TV_DAC_CNTL_RDACPD | TV_DAC_CNTL_GDACPD | + TV_DAC_CNTL_BDACPD | + (8<<TV_DAC_CNTL_BGADJ__SHIFT) | (8<<TV_DAC_CNTL_DACADJ__SHIFT); + OUTREG(TV_DAC_CNTL, reg); + + reg = INREG(TMDS_TRANSMITTER_CNTL); + reg &= ~(TMDS_PLL_EN | TMDS_PLLRST); + OUTREG(TMDS_TRANSMITTER_CNTL, reg); + + reg = INREG(DAC_CNTL); + reg &= ~DAC_CMP_EN; + OUTREG(DAC_CNTL, reg); + + reg = INREG(DAC_CNTL2); + reg &= ~DAC2_CMP_EN; + OUTREG(DAC_CNTL2, reg); + + reg = INREG(TV_DAC_CNTL); + reg &= ~TV_DAC_CNTL_DETECT; + OUTREG(TV_DAC_CNTL, reg); +} + +static void radeon_pm_setup_for_suspend(struct radeonfb_info *rinfo) +{ + + u32 sclk_cntl, mclk_cntl, sclk_more_cntl; + + u32 pll_pwrmgt_cntl; + u32 clk_pwrmgt_cntl; + u32 clk_pin_cntl; + u32 vclk_ecp_cntl; + u32 pixclks_cntl; + u32 disp_mis_cntl; + u32 disp_pwr_man; + u32 tmp; + + /* Force Core Clocks */ + sclk_cntl = INPLL( pllSCLK_CNTL); + sclk_cntl |= SCLK_CNTL__IDCT_MAX_DYN_STOP_LAT| + SCLK_CNTL__VIP_MAX_DYN_STOP_LAT| + SCLK_CNTL__RE_MAX_DYN_STOP_LAT| + SCLK_CNTL__PB_MAX_DYN_STOP_LAT| + SCLK_CNTL__TAM_MAX_DYN_STOP_LAT| + SCLK_CNTL__TDM_MAX_DYN_STOP_LAT| + SCLK_CNTL__RB_MAX_DYN_STOP_LAT| + + SCLK_CNTL__FORCE_DISP2| + SCLK_CNTL__FORCE_CP| + SCLK_CNTL__FORCE_HDP| + SCLK_CNTL__FORCE_DISP1| + SCLK_CNTL__FORCE_TOP| + SCLK_CNTL__FORCE_E2| + SCLK_CNTL__FORCE_SE| + SCLK_CNTL__FORCE_IDCT| + SCLK_CNTL__FORCE_VIP| + + SCLK_CNTL__FORCE_PB| + SCLK_CNTL__FORCE_TAM| + SCLK_CNTL__FORCE_TDM| + SCLK_CNTL__FORCE_RB| + SCLK_CNTL__FORCE_TV_SCLK| + SCLK_CNTL__FORCE_SUBPIC| + SCLK_CNTL__FORCE_OV0; + if (rinfo->family <= CHIP_FAMILY_RV280) + sclk_cntl |= SCLK_CNTL__FORCE_RE; + else + sclk_cntl |= SCLK_CNTL__SE_MAX_DYN_STOP_LAT | + SCLK_CNTL__E2_MAX_DYN_STOP_LAT | + SCLK_CNTL__TV_MAX_DYN_STOP_LAT | + SCLK_CNTL__HDP_MAX_DYN_STOP_LAT | + SCLK_CNTL__CP_MAX_DYN_STOP_LAT; + + OUTPLL( pllSCLK_CNTL, sclk_cntl); + + sclk_more_cntl = INPLL(pllSCLK_MORE_CNTL); + sclk_more_cntl |= SCLK_MORE_CNTL__FORCE_DISPREGS | + SCLK_MORE_CNTL__FORCE_MC_GUI | + SCLK_MORE_CNTL__FORCE_MC_HOST; + + OUTPLL(pllSCLK_MORE_CNTL, sclk_more_cntl); + + + mclk_cntl = INPLL( pllMCLK_CNTL); + mclk_cntl &= ~( MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_MCLKB | + MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB | + MCLK_CNTL__FORCE_MC + ); + OUTPLL( pllMCLK_CNTL, mclk_cntl); + + /* Force Display clocks */ + vclk_ecp_cntl = INPLL( pllVCLK_ECP_CNTL); + vclk_ecp_cntl &= ~(VCLK_ECP_CNTL__PIXCLK_ALWAYS_ONb + | VCLK_ECP_CNTL__PIXCLK_DAC_ALWAYS_ONb); + vclk_ecp_cntl |= VCLK_ECP_CNTL__ECP_FORCE_ON; + OUTPLL( pllVCLK_ECP_CNTL, vclk_ecp_cntl); + + + pixclks_cntl = INPLL( pllPIXCLKS_CNTL); + pixclks_cntl &= ~( PIXCLKS_CNTL__PIXCLK_GV_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_BLEND_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_DIG_TMDS_ALWAYS_ONb | + PIXCLKS_CNTL__PIXCLK_LVDS_ALWAYS_ONb| + PIXCLKS_CNTL__PIXCLK_TMDS_ALWAYS_ONb| + PIXCLKS_CNTL__PIX2CLK_ALWAYS_ONb| + PIXCLKS_CNTL__PIX2CLK_DAC_ALWAYS_ONb); + + OUTPLL( pllPIXCLKS_CNTL, pixclks_cntl); + + /* Switch off LVDS interface */ + OUTREG(LVDS_GEN_CNTL, INREG(LVDS_GEN_CNTL) & + ~(LVDS_BLON | LVDS_EN | LVDS_ON | LVDS_DIGON)); + + /* Enable System power management */ + pll_pwrmgt_cntl = INPLL( pllPLL_PWRMGT_CNTL); + + pll_pwrmgt_cntl |= PLL_PWRMGT_CNTL__SPLL_TURNOFF | + PLL_PWRMGT_CNTL__MPLL_TURNOFF| + PLL_PWRMGT_CNTL__PPLL_TURNOFF| + PLL_PWRMGT_CNTL__P2PLL_TURNOFF| + PLL_PWRMGT_CNTL__TVPLL_TURNOFF; + + OUTPLL( pllPLL_PWRMGT_CNTL, pll_pwrmgt_cntl); + + clk_pwrmgt_cntl = INPLL( pllCLK_PWRMGT_CNTL); + + clk_pwrmgt_cntl &= ~( CLK_PWRMGT_CNTL__MPLL_PWRMGT_OFF| + CLK_PWRMGT_CNTL__SPLL_PWRMGT_OFF| + CLK_PWRMGT_CNTL__PPLL_PWRMGT_OFF| + CLK_PWRMGT_CNTL__P2PLL_PWRMGT_OFF| + CLK_PWRMGT_CNTL__MCLK_TURNOFF| + CLK_PWRMGT_CNTL__SCLK_TURNOFF| + CLK_PWRMGT_CNTL__PCLK_TURNOFF| + CLK_PWRMGT_CNTL__P2CLK_TURNOFF| + CLK_PWRMGT_CNTL__TVPLL_PWRMGT_OFF| + CLK_PWRMGT_CNTL__GLOBAL_PMAN_EN| + CLK_PWRMGT_CNTL__ENGINE_DYNCLK_MODE| + CLK_PWRMGT_CNTL__ACTIVE_HILO_LAT_MASK| + CLK_PWRMGT_CNTL__CG_NO1_DEBUG_MASK + ); + + clk_pwrmgt_cntl |= CLK_PWRMGT_CNTL__GLOBAL_PMAN_EN + | CLK_PWRMGT_CNTL__DISP_PM; + + OUTPLL( pllCLK_PWRMGT_CNTL, clk_pwrmgt_cntl); + + clk_pin_cntl = INPLL( pllCLK_PIN_CNTL); + + clk_pin_cntl &= ~CLK_PIN_CNTL__ACCESS_REGS_IN_SUSPEND; + + /* because both INPLL and OUTPLL take the same lock, that's why. */ + tmp = INPLL( pllMCLK_MISC) | MCLK_MISC__EN_MCLK_TRISTATE_IN_SUSPEND; + OUTPLL( pllMCLK_MISC, tmp); + + /* BUS_CNTL1__MOBILE_PLATORM_SEL setting is northbridge chipset + * and radeon chip dependent. Thus we only enable it on Mac for + * now (until we get more info on how to compute the correct + * value for various X86 bridges). + */ +#ifdef CONFIG_PPC_PMAC + if (machine_is(powermac)) { + /* AGP PLL control */ + if (rinfo->family <= CHIP_FAMILY_RV280) { + OUTREG(BUS_CNTL1, INREG(BUS_CNTL1) | BUS_CNTL1__AGPCLK_VALID); + OUTREG(BUS_CNTL1, + (INREG(BUS_CNTL1) & ~BUS_CNTL1__MOBILE_PLATFORM_SEL_MASK) + | (2<<BUS_CNTL1__MOBILE_PLATFORM_SEL__SHIFT)); // 440BX + } else { + OUTREG(BUS_CNTL1, INREG(BUS_CNTL1)); + OUTREG(BUS_CNTL1, (INREG(BUS_CNTL1) & ~0x4000) | 0x8000); + } + } +#endif + + OUTREG(CRTC_OFFSET_CNTL, (INREG(CRTC_OFFSET_CNTL) + & ~CRTC_OFFSET_CNTL__CRTC_STEREO_SYNC_OUT_EN)); + + clk_pin_cntl &= ~CLK_PIN_CNTL__CG_CLK_TO_OUTPIN; + clk_pin_cntl |= CLK_PIN_CNTL__XTALIN_ALWAYS_ONb; + OUTPLL( pllCLK_PIN_CNTL, clk_pin_cntl); + + /* Solano2M */ + OUTREG(AGP_CNTL, + (INREG(AGP_CNTL) & ~(AGP_CNTL__MAX_IDLE_CLK_MASK)) + | (0x20<<AGP_CNTL__MAX_IDLE_CLK__SHIFT)); + + /* ACPI mode */ + /* because both INPLL and OUTPLL take the same lock, that's why. */ + tmp = INPLL( pllPLL_PWRMGT_CNTL) & ~PLL_PWRMGT_CNTL__PM_MODE_SEL; + OUTPLL( pllPLL_PWRMGT_CNTL, tmp); + + + disp_mis_cntl = INREG(DISP_MISC_CNTL); + + disp_mis_cntl &= ~( DISP_MISC_CNTL__SOFT_RESET_GRPH_PP | + DISP_MISC_CNTL__SOFT_RESET_SUBPIC_PP | + DISP_MISC_CNTL__SOFT_RESET_OV0_PP | + DISP_MISC_CNTL__SOFT_RESET_GRPH_SCLK| + DISP_MISC_CNTL__SOFT_RESET_SUBPIC_SCLK| + DISP_MISC_CNTL__SOFT_RESET_OV0_SCLK| + DISP_MISC_CNTL__SOFT_RESET_GRPH2_PP| + DISP_MISC_CNTL__SOFT_RESET_GRPH2_SCLK| + DISP_MISC_CNTL__SOFT_RESET_LVDS| + DISP_MISC_CNTL__SOFT_RESET_TMDS| + DISP_MISC_CNTL__SOFT_RESET_DIG_TMDS| + DISP_MISC_CNTL__SOFT_RESET_TV); + + OUTREG(DISP_MISC_CNTL, disp_mis_cntl); + + disp_pwr_man = INREG(DISP_PWR_MAN); + + disp_pwr_man &= ~( DISP_PWR_MAN__DISP_PWR_MAN_D3_CRTC_EN | + DISP_PWR_MAN__DISP2_PWR_MAN_D3_CRTC2_EN | + DISP_PWR_MAN__DISP_PWR_MAN_DPMS_MASK| + DISP_PWR_MAN__DISP_D3_RST| + DISP_PWR_MAN__DISP_D3_REG_RST + ); + + disp_pwr_man |= DISP_PWR_MAN__DISP_D3_GRPH_RST| + DISP_PWR_MAN__DISP_D3_SUBPIC_RST| + DISP_PWR_MAN__DISP_D3_OV0_RST| + DISP_PWR_MAN__DISP_D1D2_GRPH_RST| + DISP_PWR_MAN__DISP_D1D2_SUBPIC_RST| + DISP_PWR_MAN__DISP_D1D2_OV0_RST| + DISP_PWR_MAN__DIG_TMDS_ENABLE_RST| + DISP_PWR_MAN__TV_ENABLE_RST| +// DISP_PWR_MAN__AUTO_PWRUP_EN| + 0; + + OUTREG(DISP_PWR_MAN, disp_pwr_man); + + clk_pwrmgt_cntl = INPLL( pllCLK_PWRMGT_CNTL); + pll_pwrmgt_cntl = INPLL( pllPLL_PWRMGT_CNTL) ; + clk_pin_cntl = INPLL( pllCLK_PIN_CNTL); + disp_pwr_man = INREG(DISP_PWR_MAN); + + + /* D2 */ + clk_pwrmgt_cntl |= CLK_PWRMGT_CNTL__DISP_PM; + pll_pwrmgt_cntl |= PLL_PWRMGT_CNTL__MOBILE_SU | PLL_PWRMGT_CNTL__SU_SCLK_USE_BCLK; + clk_pin_cntl |= CLK_PIN_CNTL__XTALIN_ALWAYS_ONb; + disp_pwr_man &= ~(DISP_PWR_MAN__DISP_PWR_MAN_D3_CRTC_EN_MASK + | DISP_PWR_MAN__DISP2_PWR_MAN_D3_CRTC2_EN_MASK); + + OUTPLL( pllCLK_PWRMGT_CNTL, clk_pwrmgt_cntl); + OUTPLL( pllPLL_PWRMGT_CNTL, pll_pwrmgt_cntl); + OUTPLL( pllCLK_PIN_CNTL, clk_pin_cntl); + OUTREG(DISP_PWR_MAN, disp_pwr_man); + + /* disable display request & disable display */ + OUTREG( CRTC_GEN_CNTL, (INREG( CRTC_GEN_CNTL) & ~CRTC_GEN_CNTL__CRTC_EN) + | CRTC_GEN_CNTL__CRTC_DISP_REQ_EN_B); + OUTREG( CRTC2_GEN_CNTL, (INREG( CRTC2_GEN_CNTL) & ~CRTC2_GEN_CNTL__CRTC2_EN) + | CRTC2_GEN_CNTL__CRTC2_DISP_REQ_EN_B); + + mdelay(17); + +} + +static void radeon_pm_yclk_mclk_sync(struct radeonfb_info *rinfo) +{ + u32 mc_chp_io_cntl_a1, mc_chp_io_cntl_b1; + + mc_chp_io_cntl_a1 = INMC( rinfo, ixMC_CHP_IO_CNTL_A1) + & ~MC_CHP_IO_CNTL_A1__MEM_SYNC_ENA_MASK; + mc_chp_io_cntl_b1 = INMC( rinfo, ixMC_CHP_IO_CNTL_B1) + & ~MC_CHP_IO_CNTL_B1__MEM_SYNC_ENB_MASK; + + OUTMC( rinfo, ixMC_CHP_IO_CNTL_A1, mc_chp_io_cntl_a1 + | (1<<MC_CHP_IO_CNTL_A1__MEM_SYNC_ENA__SHIFT)); + OUTMC( rinfo, ixMC_CHP_IO_CNTL_B1, mc_chp_io_cntl_b1 + | (1<<MC_CHP_IO_CNTL_B1__MEM_SYNC_ENB__SHIFT)); + + OUTMC( rinfo, ixMC_CHP_IO_CNTL_A1, mc_chp_io_cntl_a1); + OUTMC( rinfo, ixMC_CHP_IO_CNTL_B1, mc_chp_io_cntl_b1); + + mdelay( 1); +} + +static void radeon_pm_yclk_mclk_sync_m10(struct radeonfb_info *rinfo) +{ + u32 mc_chp_io_cntl_a1, mc_chp_io_cntl_b1; + + mc_chp_io_cntl_a1 = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_A1) + & ~MC_CHP_IO_CNTL_A1__MEM_SYNC_ENA_MASK; + mc_chp_io_cntl_b1 = INMC(rinfo, ixR300_MC_CHP_IO_CNTL_B1) + & ~MC_CHP_IO_CNTL_B1__MEM_SYNC_ENB_MASK; + + OUTMC( rinfo, ixR300_MC_CHP_IO_CNTL_A1, + mc_chp_io_cntl_a1 | (1<<MC_CHP_IO_CNTL_A1__MEM_SYNC_ENA__SHIFT)); + OUTMC( rinfo, ixR300_MC_CHP_IO_CNTL_B1, + mc_chp_io_cntl_b1 | (1<<MC_CHP_IO_CNTL_B1__MEM_SYNC_ENB__SHIFT)); + + OUTMC( rinfo, ixR300_MC_CHP_IO_CNTL_A1, mc_chp_io_cntl_a1); + OUTMC( rinfo, ixR300_MC_CHP_IO_CNTL_B1, mc_chp_io_cntl_b1); + + mdelay( 1); +} + +static void radeon_pm_program_mode_reg(struct radeonfb_info *rinfo, u16 value, + u8 delay_required) +{ + u32 mem_sdram_mode; + + mem_sdram_mode = INREG( MEM_SDRAM_MODE_REG); + + mem_sdram_mode &= ~MEM_SDRAM_MODE_REG__MEM_MODE_REG_MASK; + mem_sdram_mode |= (value<<MEM_SDRAM_MODE_REG__MEM_MODE_REG__SHIFT) + | MEM_SDRAM_MODE_REG__MEM_CFG_TYPE; + OUTREG( MEM_SDRAM_MODE_REG, mem_sdram_mode); + if (delay_required >= 2) + mdelay(1); + + mem_sdram_mode |= MEM_SDRAM_MODE_REG__MEM_SDRAM_RESET; + OUTREG( MEM_SDRAM_MODE_REG, mem_sdram_mode); + if (delay_required >= 2) + mdelay(1); + + mem_sdram_mode &= ~MEM_SDRAM_MODE_REG__MEM_SDRAM_RESET; + OUTREG( MEM_SDRAM_MODE_REG, mem_sdram_mode); + if (delay_required >= 2) + mdelay(1); + + if (delay_required) { + do { + if (delay_required >= 2) + mdelay(1); + } while ((INREG(MC_STATUS) + & (MC_STATUS__MEM_PWRUP_COMPL_A | + MC_STATUS__MEM_PWRUP_COMPL_B)) == 0); + } +} + +static void radeon_pm_m10_program_mode_wait(struct radeonfb_info *rinfo) +{ + int cnt; + + for (cnt = 0; cnt < 100; ++cnt) { + mdelay(1); + if (INREG(MC_STATUS) & (MC_STATUS__MEM_PWRUP_COMPL_A + | MC_STATUS__MEM_PWRUP_COMPL_B)) + break; + } +} + + +static void radeon_pm_enable_dll(struct radeonfb_info *rinfo) +{ +#define DLL_RESET_DELAY 5 +#define DLL_SLEEP_DELAY 1 + + u32 cko = INPLL(pllMDLL_CKO) | MDLL_CKO__MCKOA_SLEEP + | MDLL_CKO__MCKOA_RESET; + u32 cka = INPLL(pllMDLL_RDCKA) | MDLL_RDCKA__MRDCKA0_SLEEP + | MDLL_RDCKA__MRDCKA1_SLEEP | MDLL_RDCKA__MRDCKA0_RESET + | MDLL_RDCKA__MRDCKA1_RESET; + u32 ckb = INPLL(pllMDLL_RDCKB) | MDLL_RDCKB__MRDCKB0_SLEEP + | MDLL_RDCKB__MRDCKB1_SLEEP | MDLL_RDCKB__MRDCKB0_RESET + | MDLL_RDCKB__MRDCKB1_RESET; + + /* Setting up the DLL range for write */ + OUTPLL(pllMDLL_CKO, cko); + OUTPLL(pllMDLL_RDCKA, cka); + OUTPLL(pllMDLL_RDCKB, ckb); + + mdelay(DLL_RESET_DELAY*2); + + cko &= ~(MDLL_CKO__MCKOA_SLEEP | MDLL_CKO__MCKOB_SLEEP); + OUTPLL(pllMDLL_CKO, cko); + mdelay(DLL_SLEEP_DELAY); + cko &= ~(MDLL_CKO__MCKOA_RESET | MDLL_CKO__MCKOB_RESET); + OUTPLL(pllMDLL_CKO, cko); + mdelay(DLL_RESET_DELAY); + + cka &= ~(MDLL_RDCKA__MRDCKA0_SLEEP | MDLL_RDCKA__MRDCKA1_SLEEP); + OUTPLL(pllMDLL_RDCKA, cka); + mdelay(DLL_SLEEP_DELAY); + cka &= ~(MDLL_RDCKA__MRDCKA0_RESET | MDLL_RDCKA__MRDCKA1_RESET); + OUTPLL(pllMDLL_RDCKA, cka); + mdelay(DLL_RESET_DELAY); + + ckb &= ~(MDLL_RDCKB__MRDCKB0_SLEEP | MDLL_RDCKB__MRDCKB1_SLEEP); + OUTPLL(pllMDLL_RDCKB, ckb); + mdelay(DLL_SLEEP_DELAY); + ckb &= ~(MDLL_RDCKB__MRDCKB0_RESET | MDLL_RDCKB__MRDCKB1_RESET); + OUTPLL(pllMDLL_RDCKB, ckb); + mdelay(DLL_RESET_DELAY); + + +#undef DLL_RESET_DELAY +#undef DLL_SLEEP_DELAY +} + +static void radeon_pm_enable_dll_m10(struct radeonfb_info *rinfo) +{ + u32 dll_value; + u32 dll_sleep_mask = 0; + u32 dll_reset_mask = 0; + u32 mc; + +#define DLL_RESET_DELAY 5 +#define DLL_SLEEP_DELAY 1 + + OUTMC(rinfo, ixR300_MC_DLL_CNTL, rinfo->save_regs[70]); + mc = INREG(MC_CNTL); + /* Check which channels are enabled */ + switch (mc & 0x3) { + case 1: + if (mc & 0x4) + break; + case 2: + dll_sleep_mask |= MDLL_R300_RDCK__MRDCKB_SLEEP; + dll_reset_mask |= MDLL_R300_RDCK__MRDCKB_RESET; + case 0: + dll_sleep_mask |= MDLL_R300_RDCK__MRDCKA_SLEEP; + dll_reset_mask |= MDLL_R300_RDCK__MRDCKA_RESET; + } + switch (mc & 0x3) { + case 1: + if (!(mc & 0x4)) + break; + case 2: + dll_sleep_mask |= MDLL_R300_RDCK__MRDCKD_SLEEP; + dll_reset_mask |= MDLL_R300_RDCK__MRDCKD_RESET; + dll_sleep_mask |= MDLL_R300_RDCK__MRDCKC_SLEEP; + dll_reset_mask |= MDLL_R300_RDCK__MRDCKC_RESET; + } + + dll_value = INPLL(pllMDLL_RDCKA); + + /* Power Up */ + dll_value &= ~(dll_sleep_mask); + OUTPLL(pllMDLL_RDCKA, dll_value); + mdelay( DLL_SLEEP_DELAY); + + dll_value &= ~(dll_reset_mask); + OUTPLL(pllMDLL_RDCKA, dll_value); + mdelay( DLL_RESET_DELAY); + +#undef DLL_RESET_DELAY +#undef DLL_SLEEP_DELAY +} + + +static void radeon_pm_full_reset_sdram(struct radeonfb_info *rinfo) +{ + u32 crtcGenCntl, crtcGenCntl2, memRefreshCntl, crtc_more_cntl, + fp_gen_cntl, fp2_gen_cntl; + + crtcGenCntl = INREG( CRTC_GEN_CNTL); + crtcGenCntl2 = INREG( CRTC2_GEN_CNTL); + + crtc_more_cntl = INREG( CRTC_MORE_CNTL); + fp_gen_cntl = INREG( FP_GEN_CNTL); + fp2_gen_cntl = INREG( FP2_GEN_CNTL); + + + OUTREG( CRTC_MORE_CNTL, 0); + OUTREG( FP_GEN_CNTL, 0); + OUTREG( FP2_GEN_CNTL,0); + + OUTREG( CRTC_GEN_CNTL, (crtcGenCntl | CRTC_GEN_CNTL__CRTC_DISP_REQ_EN_B) ); + OUTREG( CRTC2_GEN_CNTL, (crtcGenCntl2 | CRTC2_GEN_CNTL__CRTC2_DISP_REQ_EN_B) ); + + /* This is the code for the Aluminium PowerBooks M10 / iBooks M11 */ + if (rinfo->family == CHIP_FAMILY_RV350) { + u32 sdram_mode_reg = rinfo->save_regs[35]; + static const u32 default_mrtable[] = + { 0x21320032, + 0x21321000, 0xa1321000, 0x21321000, 0xffffffff, + 0x21320032, 0xa1320032, 0x21320032, 0xffffffff, + 0x21321002, 0xa1321002, 0x21321002, 0xffffffff, + 0x21320132, 0xa1320132, 0x21320132, 0xffffffff, + 0x21320032, 0xa1320032, 0x21320032, 0xffffffff, + 0x31320032 }; + + const u32 *mrtable = default_mrtable; + int i, mrtable_size = ARRAY_SIZE(default_mrtable); + + mdelay(30); + + /* Disable refresh */ + memRefreshCntl = INREG( MEM_REFRESH_CNTL) + & ~MEM_REFRESH_CNTL__MEM_REFRESH_DIS; + OUTREG( MEM_REFRESH_CNTL, memRefreshCntl + | MEM_REFRESH_CNTL__MEM_REFRESH_DIS); + + /* Configure and enable M & SPLLs */ + radeon_pm_enable_dll_m10(rinfo); + radeon_pm_yclk_mclk_sync_m10(rinfo); + +#ifdef CONFIG_PPC_OF + if (rinfo->of_node != NULL) { + int size; + + mrtable = of_get_property(rinfo->of_node, "ATY,MRT", &size); + if (mrtable) + mrtable_size = size >> 2; + else + mrtable = default_mrtable; + } +#endif /* CONFIG_PPC_OF */ + + /* Program the SDRAM */ + sdram_mode_reg = mrtable[0]; + OUTREG(MEM_SDRAM_MODE_REG, sdram_mode_reg); + for (i = 0; i < mrtable_size; i++) { + if (mrtable[i] == 0xffffffffu) + radeon_pm_m10_program_mode_wait(rinfo); + else { + sdram_mode_reg &= ~(MEM_SDRAM_MODE_REG__MEM_MODE_REG_MASK + | MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE + | MEM_SDRAM_MODE_REG__MEM_SDRAM_RESET); + sdram_mode_reg |= mrtable[i]; + + OUTREG(MEM_SDRAM_MODE_REG, sdram_mode_reg); + mdelay(1); + } + } + + /* Restore memory refresh */ + OUTREG(MEM_REFRESH_CNTL, memRefreshCntl); + mdelay(30); + + } + /* Here come the desktop RV200 "QW" card */ + else if (!rinfo->is_mobility && rinfo->family == CHIP_FAMILY_RV200) { + /* Disable refresh */ + memRefreshCntl = INREG( MEM_REFRESH_CNTL) + & ~MEM_REFRESH_CNTL__MEM_REFRESH_DIS; + OUTREG(MEM_REFRESH_CNTL, memRefreshCntl + | MEM_REFRESH_CNTL__MEM_REFRESH_DIS); + mdelay(30); + + /* Reset memory */ + OUTREG(MEM_SDRAM_MODE_REG, + INREG( MEM_SDRAM_MODE_REG) & ~MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + + radeon_pm_program_mode_reg(rinfo, 0x2002, 2); + radeon_pm_program_mode_reg(rinfo, 0x0132, 2); + radeon_pm_program_mode_reg(rinfo, 0x0032, 2); + + OUTREG(MEM_SDRAM_MODE_REG, + INREG(MEM_SDRAM_MODE_REG) | MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + + OUTREG( MEM_REFRESH_CNTL, memRefreshCntl); + + } + /* The M6 */ + else if (rinfo->is_mobility && rinfo->family == CHIP_FAMILY_RV100) { + /* Disable refresh */ + memRefreshCntl = INREG(EXT_MEM_CNTL) & ~(1 << 20); + OUTREG( EXT_MEM_CNTL, memRefreshCntl | (1 << 20)); + + /* Reset memory */ + OUTREG( MEM_SDRAM_MODE_REG, + INREG( MEM_SDRAM_MODE_REG) + & ~MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + + /* DLL */ + radeon_pm_enable_dll(rinfo); + + /* MLCK / YCLK sync */ + radeon_pm_yclk_mclk_sync(rinfo); + + /* Program Mode Register */ + radeon_pm_program_mode_reg(rinfo, 0x2000, 1); + radeon_pm_program_mode_reg(rinfo, 0x2001, 1); + radeon_pm_program_mode_reg(rinfo, 0x2002, 1); + radeon_pm_program_mode_reg(rinfo, 0x0132, 1); + radeon_pm_program_mode_reg(rinfo, 0x0032, 1); + + /* Complete & re-enable refresh */ + OUTREG( MEM_SDRAM_MODE_REG, + INREG( MEM_SDRAM_MODE_REG) | MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + + OUTREG(EXT_MEM_CNTL, memRefreshCntl); + } + /* And finally, the M7..M9 models, including M9+ (RV280) */ + else if (rinfo->is_mobility) { + + /* Disable refresh */ + memRefreshCntl = INREG( MEM_REFRESH_CNTL) + & ~MEM_REFRESH_CNTL__MEM_REFRESH_DIS; + OUTREG( MEM_REFRESH_CNTL, memRefreshCntl + | MEM_REFRESH_CNTL__MEM_REFRESH_DIS); + + /* Reset memory */ + OUTREG( MEM_SDRAM_MODE_REG, + INREG( MEM_SDRAM_MODE_REG) + & ~MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + + /* DLL */ + radeon_pm_enable_dll(rinfo); + + /* MLCK / YCLK sync */ + radeon_pm_yclk_mclk_sync(rinfo); + + /* M6, M7 and M9 so far ... */ + if (rinfo->family <= CHIP_FAMILY_RV250) { + radeon_pm_program_mode_reg(rinfo, 0x2000, 1); + radeon_pm_program_mode_reg(rinfo, 0x2001, 1); + radeon_pm_program_mode_reg(rinfo, 0x2002, 1); + radeon_pm_program_mode_reg(rinfo, 0x0132, 1); + radeon_pm_program_mode_reg(rinfo, 0x0032, 1); + } + /* M9+ (iBook G4) */ + else if (rinfo->family == CHIP_FAMILY_RV280) { + radeon_pm_program_mode_reg(rinfo, 0x2000, 1); + radeon_pm_program_mode_reg(rinfo, 0x0132, 1); + radeon_pm_program_mode_reg(rinfo, 0x0032, 1); + } + + /* Complete & re-enable refresh */ + OUTREG( MEM_SDRAM_MODE_REG, + INREG( MEM_SDRAM_MODE_REG) | MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + + OUTREG( MEM_REFRESH_CNTL, memRefreshCntl); + } + + OUTREG( CRTC_GEN_CNTL, crtcGenCntl); + OUTREG( CRTC2_GEN_CNTL, crtcGenCntl2); + OUTREG( FP_GEN_CNTL, fp_gen_cntl); + OUTREG( FP2_GEN_CNTL, fp2_gen_cntl); + + OUTREG( CRTC_MORE_CNTL, crtc_more_cntl); + + mdelay( 15); +} + +#if defined(CONFIG_PM) +#if defined(CONFIG_X86) || defined(CONFIG_PPC_PMAC) +static void radeon_pm_reset_pad_ctlr_strength(struct radeonfb_info *rinfo) +{ + u32 tmp, tmp2; + int i,j; + + /* Reset the PAD_CTLR_STRENGTH & wait for it to be stable */ + INREG(PAD_CTLR_STRENGTH); + OUTREG(PAD_CTLR_STRENGTH, INREG(PAD_CTLR_STRENGTH) & ~PAD_MANUAL_OVERRIDE); + tmp = INREG(PAD_CTLR_STRENGTH); + for (i = j = 0; i < 65; ++i) { + mdelay(1); + tmp2 = INREG(PAD_CTLR_STRENGTH); + if (tmp != tmp2) { + tmp = tmp2; + i = 0; + j++; + if (j > 10) { + printk(KERN_WARNING "radeon: PAD_CTLR_STRENGTH doesn't " + "stabilize !\n"); + break; + } + } + } +} + +static void radeon_pm_all_ppls_off(struct radeonfb_info *rinfo) +{ + u32 tmp; + + tmp = INPLL(pllPPLL_CNTL); + OUTPLL(pllPPLL_CNTL, tmp | 0x3); + tmp = INPLL(pllP2PLL_CNTL); + OUTPLL(pllP2PLL_CNTL, tmp | 0x3); + tmp = INPLL(pllSPLL_CNTL); + OUTPLL(pllSPLL_CNTL, tmp | 0x3); + tmp = INPLL(pllMPLL_CNTL); + OUTPLL(pllMPLL_CNTL, tmp | 0x3); +} + +static void radeon_pm_start_mclk_sclk(struct radeonfb_info *rinfo) +{ + u32 tmp; + + /* Switch SPLL to PCI source */ + tmp = INPLL(pllSCLK_CNTL); + OUTPLL(pllSCLK_CNTL, tmp & ~SCLK_CNTL__SCLK_SRC_SEL_MASK); + + /* Reconfigure SPLL charge pump, VCO gain, duty cycle */ + tmp = INPLL(pllSPLL_CNTL); + OUTREG8(CLOCK_CNTL_INDEX, pllSPLL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA + 1, (tmp >> 8) & 0xff); + radeon_pll_errata_after_data(rinfo); + + /* Set SPLL feedback divider */ + tmp = INPLL(pllM_SPLL_REF_FB_DIV); + tmp = (tmp & 0xff00fffful) | (rinfo->save_regs[77] & 0x00ff0000ul); + OUTPLL(pllM_SPLL_REF_FB_DIV, tmp); + + /* Power up SPLL */ + tmp = INPLL(pllSPLL_CNTL); + OUTPLL(pllSPLL_CNTL, tmp & ~1); + (void)INPLL(pllSPLL_CNTL); + + mdelay(10); + + /* Release SPLL reset */ + tmp = INPLL(pllSPLL_CNTL); + OUTPLL(pllSPLL_CNTL, tmp & ~0x2); + (void)INPLL(pllSPLL_CNTL); + + mdelay(10); + + /* Select SCLK source */ + tmp = INPLL(pllSCLK_CNTL); + tmp &= ~SCLK_CNTL__SCLK_SRC_SEL_MASK; + tmp |= rinfo->save_regs[3] & SCLK_CNTL__SCLK_SRC_SEL_MASK; + OUTPLL(pllSCLK_CNTL, tmp); + (void)INPLL(pllSCLK_CNTL); + + mdelay(10); + + /* Reconfigure MPLL charge pump, VCO gain, duty cycle */ + tmp = INPLL(pllMPLL_CNTL); + OUTREG8(CLOCK_CNTL_INDEX, pllMPLL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA + 1, (tmp >> 8) & 0xff); + radeon_pll_errata_after_data(rinfo); + + /* Set MPLL feedback divider */ + tmp = INPLL(pllM_SPLL_REF_FB_DIV); + tmp = (tmp & 0xffff00fful) | (rinfo->save_regs[77] & 0x0000ff00ul); + + OUTPLL(pllM_SPLL_REF_FB_DIV, tmp); + /* Power up MPLL */ + tmp = INPLL(pllMPLL_CNTL); + OUTPLL(pllMPLL_CNTL, tmp & ~0x2); + (void)INPLL(pllMPLL_CNTL); + + mdelay(10); + + /* Un-reset MPLL */ + tmp = INPLL(pllMPLL_CNTL); + OUTPLL(pllMPLL_CNTL, tmp & ~0x1); + (void)INPLL(pllMPLL_CNTL); + + mdelay(10); + + /* Select source for MCLK */ + tmp = INPLL(pllMCLK_CNTL); + tmp |= rinfo->save_regs[2] & 0xffff; + OUTPLL(pllMCLK_CNTL, tmp); + (void)INPLL(pllMCLK_CNTL); + + mdelay(10); +} + +static void radeon_pm_m10_disable_spread_spectrum(struct radeonfb_info *rinfo) +{ + u32 r2ec; + + /* GACK ! I though we didn't have a DDA on Radeon's anymore + * here we rewrite with the same value, ... I suppose we clear + * some bits that are already clear ? Or maybe this 0x2ec + * register is something new ? + */ + mdelay(20); + r2ec = INREG(VGA_DDA_ON_OFF); + OUTREG(VGA_DDA_ON_OFF, r2ec); + mdelay(1); + + /* Spread spectrum PLLL off */ + OUTPLL(pllSSPLL_CNTL, 0xbf03); + + /* Spread spectrum disabled */ + OUTPLL(pllSS_INT_CNTL, rinfo->save_regs[90] & ~3); + + /* The trace shows read & rewrite of LVDS_PLL_CNTL here with same + * value, not sure what for... + */ + + r2ec |= 0x3f0; + OUTREG(VGA_DDA_ON_OFF, r2ec); + mdelay(1); +} + +static void radeon_pm_m10_enable_lvds_spread_spectrum(struct radeonfb_info *rinfo) +{ + u32 r2ec, tmp; + + /* GACK (bis) ! I though we didn't have a DDA on Radeon's anymore + * here we rewrite with the same value, ... I suppose we clear/set + * some bits that are already clear/set ? + */ + r2ec = INREG(VGA_DDA_ON_OFF); + OUTREG(VGA_DDA_ON_OFF, r2ec); + mdelay(1); + + /* Enable spread spectrum */ + OUTPLL(pllSSPLL_CNTL, rinfo->save_regs[43] | 3); + mdelay(3); + + OUTPLL(pllSSPLL_REF_DIV, rinfo->save_regs[44]); + OUTPLL(pllSSPLL_DIV_0, rinfo->save_regs[45]); + tmp = INPLL(pllSSPLL_CNTL); + OUTPLL(pllSSPLL_CNTL, tmp & ~0x2); + mdelay(6); + tmp = INPLL(pllSSPLL_CNTL); + OUTPLL(pllSSPLL_CNTL, tmp & ~0x1); + mdelay(5); + + OUTPLL(pllSS_INT_CNTL, rinfo->save_regs[90]); + + r2ec |= 8; + OUTREG(VGA_DDA_ON_OFF, r2ec); + mdelay(20); + + /* Enable LVDS interface */ + tmp = INREG(LVDS_GEN_CNTL); + OUTREG(LVDS_GEN_CNTL, tmp | LVDS_EN); + + /* Enable LVDS_PLL */ + tmp = INREG(LVDS_PLL_CNTL); + tmp &= ~0x30000; + tmp |= 0x10000; + OUTREG(LVDS_PLL_CNTL, tmp); + + OUTPLL(pllSCLK_MORE_CNTL, rinfo->save_regs[34]); + OUTPLL(pllSS_TST_CNTL, rinfo->save_regs[91]); + + /* The trace reads that one here, waiting for something to settle down ? */ + INREG(RBBM_STATUS); + + /* Ugh ? SS_TST_DEC is supposed to be a read register in the + * R300 register spec at least... + */ + tmp = INPLL(pllSS_TST_CNTL); + tmp |= 0x00400000; + OUTPLL(pllSS_TST_CNTL, tmp); +} + +static void radeon_pm_restore_pixel_pll(struct radeonfb_info *rinfo) +{ + u32 tmp; + + OUTREG8(CLOCK_CNTL_INDEX, pllHTOTAL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA, 0); + radeon_pll_errata_after_data(rinfo); + + tmp = INPLL(pllVCLK_ECP_CNTL); + OUTPLL(pllVCLK_ECP_CNTL, tmp | 0x80); + mdelay(5); + + tmp = INPLL(pllPPLL_REF_DIV); + tmp = (tmp & ~PPLL_REF_DIV_MASK) | rinfo->pll.ref_div; + OUTPLL(pllPPLL_REF_DIV, tmp); + INPLL(pllPPLL_REF_DIV); + + /* Reconfigure SPLL charge pump, VCO gain, duty cycle, + * probably useless since we already did it ... + */ + tmp = INPLL(pllPPLL_CNTL); + OUTREG8(CLOCK_CNTL_INDEX, pllSPLL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA + 1, (tmp >> 8) & 0xff); + radeon_pll_errata_after_data(rinfo); + + /* Restore our "reference" PPLL divider set by firmware + * according to proper spread spectrum calculations + */ + OUTPLL(pllPPLL_DIV_0, rinfo->save_regs[92]); + + tmp = INPLL(pllPPLL_CNTL); + OUTPLL(pllPPLL_CNTL, tmp & ~0x2); + mdelay(5); + + tmp = INPLL(pllPPLL_CNTL); + OUTPLL(pllPPLL_CNTL, tmp & ~0x1); + mdelay(5); + + tmp = INPLL(pllVCLK_ECP_CNTL); + OUTPLL(pllVCLK_ECP_CNTL, tmp | 3); + mdelay(5); + + tmp = INPLL(pllVCLK_ECP_CNTL); + OUTPLL(pllVCLK_ECP_CNTL, tmp | 3); + mdelay(5); + + /* Switch pixel clock to firmware default div 0 */ + OUTREG8(CLOCK_CNTL_INDEX+1, 0); + radeon_pll_errata_after_index(rinfo); + radeon_pll_errata_after_data(rinfo); +} + +static void radeon_pm_m10_reconfigure_mc(struct radeonfb_info *rinfo) +{ + OUTREG(MC_CNTL, rinfo->save_regs[46]); + OUTREG(MC_INIT_GFX_LAT_TIMER, rinfo->save_regs[47]); + OUTREG(MC_INIT_MISC_LAT_TIMER, rinfo->save_regs[48]); + OUTREG(MEM_SDRAM_MODE_REG, + rinfo->save_regs[35] & ~MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + OUTREG(MC_TIMING_CNTL, rinfo->save_regs[49]); + OUTREG(MEM_REFRESH_CNTL, rinfo->save_regs[42]); + OUTREG(MC_READ_CNTL_AB, rinfo->save_regs[50]); + OUTREG(MC_CHIP_IO_OE_CNTL_AB, rinfo->save_regs[52]); + OUTREG(MC_IOPAD_CNTL, rinfo->save_regs[51]); + OUTREG(MC_DEBUG, rinfo->save_regs[53]); + + OUTMC(rinfo, ixR300_MC_MC_INIT_WR_LAT_TIMER, rinfo->save_regs[58]); + OUTMC(rinfo, ixR300_MC_IMP_CNTL, rinfo->save_regs[59]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_C0, rinfo->save_regs[60]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_C1, rinfo->save_regs[61]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_D0, rinfo->save_regs[62]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_D1, rinfo->save_regs[63]); + OUTMC(rinfo, ixR300_MC_BIST_CNTL_3, rinfo->save_regs[64]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_A0, rinfo->save_regs[65]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_A1, rinfo->save_regs[66]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_B0, rinfo->save_regs[67]); + OUTMC(rinfo, ixR300_MC_CHP_IO_CNTL_B1, rinfo->save_regs[68]); + OUTMC(rinfo, ixR300_MC_DEBUG_CNTL, rinfo->save_regs[69]); + OUTMC(rinfo, ixR300_MC_DLL_CNTL, rinfo->save_regs[70]); + OUTMC(rinfo, ixR300_MC_IMP_CNTL_0, rinfo->save_regs[71]); + OUTMC(rinfo, ixR300_MC_ELPIDA_CNTL, rinfo->save_regs[72]); + OUTMC(rinfo, ixR300_MC_READ_CNTL_CD, rinfo->save_regs[96]); + OUTREG(MC_IND_INDEX, 0); +} + +static void radeon_reinitialize_M10(struct radeonfb_info *rinfo) +{ + u32 tmp, i; + + /* Restore a bunch of registers first */ + OUTREG(MC_AGP_LOCATION, rinfo->save_regs[32]); + OUTREG(DISPLAY_BASE_ADDR, rinfo->save_regs[31]); + OUTREG(CRTC2_DISPLAY_BASE_ADDR, rinfo->save_regs[33]); + OUTREG(MC_FB_LOCATION, rinfo->save_regs[30]); + OUTREG(OV0_BASE_ADDR, rinfo->save_regs[80]); + OUTREG(CNFG_MEMSIZE, rinfo->video_ram); + OUTREG(BUS_CNTL, rinfo->save_regs[36]); + OUTREG(BUS_CNTL1, rinfo->save_regs[14]); + OUTREG(MPP_TB_CONFIG, rinfo->save_regs[37]); + OUTREG(FCP_CNTL, rinfo->save_regs[38]); + OUTREG(RBBM_CNTL, rinfo->save_regs[39]); + OUTREG(DAC_CNTL, rinfo->save_regs[40]); + OUTREG(DAC_MACRO_CNTL, (INREG(DAC_MACRO_CNTL) & ~0x6) | 8); + OUTREG(DAC_MACRO_CNTL, (INREG(DAC_MACRO_CNTL) & ~0x6) | 8); + + /* Hrm... */ + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) | DAC2_EXPAND_MODE); + + /* Reset the PAD CTLR */ + radeon_pm_reset_pad_ctlr_strength(rinfo); + + /* Some PLLs are Read & written identically in the trace here... + * I suppose it's actually to switch them all off & reset, + * let's assume off is what we want. I'm just doing that for all major PLLs now. + */ + radeon_pm_all_ppls_off(rinfo); + + /* Clear tiling, reset swappers */ + INREG(SURFACE_CNTL); + OUTREG(SURFACE_CNTL, 0); + + /* Some black magic with TV_DAC_CNTL, we should restore those from backups + * rather than hard coding... + */ + tmp = INREG(TV_DAC_CNTL) & ~TV_DAC_CNTL_BGADJ_MASK; + tmp |= 8 << TV_DAC_CNTL_BGADJ__SHIFT; + OUTREG(TV_DAC_CNTL, tmp); + + tmp = INREG(TV_DAC_CNTL) & ~TV_DAC_CNTL_DACADJ_MASK; + tmp |= 7 << TV_DAC_CNTL_DACADJ__SHIFT; + OUTREG(TV_DAC_CNTL, tmp); + + /* More registers restored */ + OUTREG(AGP_CNTL, rinfo->save_regs[16]); + OUTREG(HOST_PATH_CNTL, rinfo->save_regs[41]); + OUTREG(DISP_MISC_CNTL, rinfo->save_regs[9]); + + /* Hrmmm ... What is that ? */ + tmp = rinfo->save_regs[1] + & ~(CLK_PWRMGT_CNTL__ACTIVE_HILO_LAT_MASK | + CLK_PWRMGT_CNTL__MC_BUSY); + OUTPLL(pllCLK_PWRMGT_CNTL, tmp); + + OUTREG(PAD_CTLR_MISC, rinfo->save_regs[56]); + OUTREG(FW_CNTL, rinfo->save_regs[57]); + OUTREG(HDP_DEBUG, rinfo->save_regs[96]); + OUTREG(PAMAC0_DLY_CNTL, rinfo->save_regs[54]); + OUTREG(PAMAC1_DLY_CNTL, rinfo->save_regs[55]); + OUTREG(PAMAC2_DLY_CNTL, rinfo->save_regs[79]); + + /* Restore Memory Controller configuration */ + radeon_pm_m10_reconfigure_mc(rinfo); + + /* Make sure CRTC's dont touch memory */ + OUTREG(CRTC_GEN_CNTL, INREG(CRTC_GEN_CNTL) + | CRTC_GEN_CNTL__CRTC_DISP_REQ_EN_B); + OUTREG(CRTC2_GEN_CNTL, INREG(CRTC2_GEN_CNTL) + | CRTC2_GEN_CNTL__CRTC2_DISP_REQ_EN_B); + mdelay(30); + + /* Disable SDRAM refresh */ + OUTREG(MEM_REFRESH_CNTL, INREG(MEM_REFRESH_CNTL) + | MEM_REFRESH_CNTL__MEM_REFRESH_DIS); + + /* Restore XTALIN routing (CLK_PIN_CNTL) */ + OUTPLL(pllCLK_PIN_CNTL, rinfo->save_regs[4]); + + /* Switch MCLK, YCLK and SCLK PLLs to PCI source & force them ON */ + tmp = rinfo->save_regs[2] & 0xff000000; + tmp |= MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_MCLKB | + MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB | + MCLK_CNTL__FORCE_MC; + OUTPLL(pllMCLK_CNTL, tmp); + + /* Force all clocks on in SCLK */ + tmp = INPLL(pllSCLK_CNTL); + tmp |= SCLK_CNTL__FORCE_DISP2| + SCLK_CNTL__FORCE_CP| + SCLK_CNTL__FORCE_HDP| + SCLK_CNTL__FORCE_DISP1| + SCLK_CNTL__FORCE_TOP| + SCLK_CNTL__FORCE_E2| + SCLK_CNTL__FORCE_SE| + SCLK_CNTL__FORCE_IDCT| + SCLK_CNTL__FORCE_VIP| + SCLK_CNTL__FORCE_PB| + SCLK_CNTL__FORCE_TAM| + SCLK_CNTL__FORCE_TDM| + SCLK_CNTL__FORCE_RB| + SCLK_CNTL__FORCE_TV_SCLK| + SCLK_CNTL__FORCE_SUBPIC| + SCLK_CNTL__FORCE_OV0; + tmp |= SCLK_CNTL__CP_MAX_DYN_STOP_LAT | + SCLK_CNTL__HDP_MAX_DYN_STOP_LAT | + SCLK_CNTL__TV_MAX_DYN_STOP_LAT | + SCLK_CNTL__E2_MAX_DYN_STOP_LAT | + SCLK_CNTL__SE_MAX_DYN_STOP_LAT | + SCLK_CNTL__IDCT_MAX_DYN_STOP_LAT| + SCLK_CNTL__VIP_MAX_DYN_STOP_LAT | + SCLK_CNTL__RE_MAX_DYN_STOP_LAT | + SCLK_CNTL__PB_MAX_DYN_STOP_LAT | + SCLK_CNTL__TAM_MAX_DYN_STOP_LAT | + SCLK_CNTL__TDM_MAX_DYN_STOP_LAT | + SCLK_CNTL__RB_MAX_DYN_STOP_LAT; + OUTPLL(pllSCLK_CNTL, tmp); + + OUTPLL(pllVCLK_ECP_CNTL, 0); + OUTPLL(pllPIXCLKS_CNTL, 0); + OUTPLL(pllMCLK_MISC, + MCLK_MISC__MC_MCLK_MAX_DYN_STOP_LAT | + MCLK_MISC__IO_MCLK_MAX_DYN_STOP_LAT); + + mdelay(5); + + /* Restore the M_SPLL_REF_FB_DIV, MPLL_AUX_CNTL and SPLL_AUX_CNTL values */ + OUTPLL(pllM_SPLL_REF_FB_DIV, rinfo->save_regs[77]); + OUTPLL(pllMPLL_AUX_CNTL, rinfo->save_regs[75]); + OUTPLL(pllSPLL_AUX_CNTL, rinfo->save_regs[76]); + + /* Now restore the major PLLs settings, keeping them off & reset though */ + OUTPLL(pllPPLL_CNTL, rinfo->save_regs[93] | 0x3); + OUTPLL(pllP2PLL_CNTL, rinfo->save_regs[8] | 0x3); + OUTPLL(pllMPLL_CNTL, rinfo->save_regs[73] | 0x03); + OUTPLL(pllSPLL_CNTL, rinfo->save_regs[74] | 0x03); + + /* Restore MC DLL state and switch it off/reset too */ + OUTMC(rinfo, ixR300_MC_DLL_CNTL, rinfo->save_regs[70]); + + /* Switch MDLL off & reset */ + OUTPLL(pllMDLL_RDCKA, rinfo->save_regs[98] | 0xff); + mdelay(5); + + /* Setup some black magic bits in PLL_PWRMGT_CNTL. Hrm... we saved + * 0xa1100007... and MacOS writes 0xa1000007 .. + */ + OUTPLL(pllPLL_PWRMGT_CNTL, rinfo->save_regs[0]); + + /* Restore more stuffs */ + OUTPLL(pllHTOTAL_CNTL, 0); + OUTPLL(pllHTOTAL2_CNTL, 0); + + /* More PLL initial configuration */ + tmp = INPLL(pllSCLK_CNTL2); /* What for ? */ + OUTPLL(pllSCLK_CNTL2, tmp); + + tmp = INPLL(pllSCLK_MORE_CNTL); + tmp |= SCLK_MORE_CNTL__FORCE_DISPREGS | /* a guess */ + SCLK_MORE_CNTL__FORCE_MC_GUI | + SCLK_MORE_CNTL__FORCE_MC_HOST; + OUTPLL(pllSCLK_MORE_CNTL, tmp); + + /* Now we actually start MCLK and SCLK */ + radeon_pm_start_mclk_sclk(rinfo); + + /* Full reset sdrams, this also re-inits the MDLL */ + radeon_pm_full_reset_sdram(rinfo); + + /* Fill palettes */ + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) | 0x20); + for (i=0; i<256; i++) + OUTREG(PALETTE_30_DATA, 0x15555555); + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) & ~20); + udelay(20); + for (i=0; i<256; i++) + OUTREG(PALETTE_30_DATA, 0x15555555); + + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) & ~0x20); + mdelay(3); + + /* Restore TMDS */ + OUTREG(FP_GEN_CNTL, rinfo->save_regs[82]); + OUTREG(FP2_GEN_CNTL, rinfo->save_regs[83]); + + /* Set LVDS registers but keep interface & pll down */ + OUTREG(LVDS_GEN_CNTL, rinfo->save_regs[11] & + ~(LVDS_EN | LVDS_ON | LVDS_DIGON | LVDS_BLON | LVDS_BL_MOD_EN)); + OUTREG(LVDS_PLL_CNTL, (rinfo->save_regs[12] & ~0xf0000) | 0x20000); + + OUTREG(DISP_OUTPUT_CNTL, rinfo->save_regs[86]); + + /* Restore GPIOPAD state */ + OUTREG(GPIOPAD_A, rinfo->save_regs[19]); + OUTREG(GPIOPAD_EN, rinfo->save_regs[20]); + OUTREG(GPIOPAD_MASK, rinfo->save_regs[21]); + + /* write some stuff to the framebuffer... */ + for (i = 0; i < 0x8000; ++i) + writeb(0, rinfo->fb_base + i); + + mdelay(40); + OUTREG(LVDS_GEN_CNTL, INREG(LVDS_GEN_CNTL) | LVDS_DIGON | LVDS_ON); + mdelay(40); + + /* Restore a few more things */ + OUTREG(GRPH_BUFFER_CNTL, rinfo->save_regs[94]); + OUTREG(GRPH2_BUFFER_CNTL, rinfo->save_regs[95]); + + /* Take care of spread spectrum & PPLLs now */ + radeon_pm_m10_disable_spread_spectrum(rinfo); + radeon_pm_restore_pixel_pll(rinfo); + + /* GRRRR... I can't figure out the proper LVDS power sequence, and the + * code I have for blank/unblank doesn't quite work on some laptop models + * it seems ... Hrm. What I have here works most of the time ... + */ + radeon_pm_m10_enable_lvds_spread_spectrum(rinfo); +} +#endif + +#ifdef CONFIG_PPC_OF +#ifdef CONFIG_PPC_PMAC +static void radeon_pm_m9p_reconfigure_mc(struct radeonfb_info *rinfo) +{ + OUTREG(MC_CNTL, rinfo->save_regs[46]); + OUTREG(MC_INIT_GFX_LAT_TIMER, rinfo->save_regs[47]); + OUTREG(MC_INIT_MISC_LAT_TIMER, rinfo->save_regs[48]); + OUTREG(MEM_SDRAM_MODE_REG, + rinfo->save_regs[35] & ~MEM_SDRAM_MODE_REG__MC_INIT_COMPLETE); + OUTREG(MC_TIMING_CNTL, rinfo->save_regs[49]); + OUTREG(MC_READ_CNTL_AB, rinfo->save_regs[50]); + OUTREG(MEM_REFRESH_CNTL, rinfo->save_regs[42]); + OUTREG(MC_IOPAD_CNTL, rinfo->save_regs[51]); + OUTREG(MC_DEBUG, rinfo->save_regs[53]); + OUTREG(MC_CHIP_IO_OE_CNTL_AB, rinfo->save_regs[52]); + + OUTMC(rinfo, ixMC_IMP_CNTL, rinfo->save_regs[59] /*0x00f460d6*/); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_A0, rinfo->save_regs[65] /*0xfecfa666*/); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_A1, rinfo->save_regs[66] /*0x141555ff*/); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_B0, rinfo->save_regs[67] /*0xfecfa666*/); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_B1, rinfo->save_regs[68] /*0x141555ff*/); + OUTMC(rinfo, ixMC_IMP_CNTL_0, rinfo->save_regs[71] /*0x00009249*/); + OUTREG(MC_IND_INDEX, 0); + OUTREG(CNFG_MEMSIZE, rinfo->video_ram); + + mdelay(20); +} + +static void radeon_reinitialize_M9P(struct radeonfb_info *rinfo) +{ + u32 tmp, i; + + /* Restore a bunch of registers first */ + OUTREG(SURFACE_CNTL, rinfo->save_regs[29]); + OUTREG(MC_AGP_LOCATION, rinfo->save_regs[32]); + OUTREG(DISPLAY_BASE_ADDR, rinfo->save_regs[31]); + OUTREG(CRTC2_DISPLAY_BASE_ADDR, rinfo->save_regs[33]); + OUTREG(MC_FB_LOCATION, rinfo->save_regs[30]); + OUTREG(OV0_BASE_ADDR, rinfo->save_regs[80]); + OUTREG(BUS_CNTL, rinfo->save_regs[36]); + OUTREG(BUS_CNTL1, rinfo->save_regs[14]); + OUTREG(MPP_TB_CONFIG, rinfo->save_regs[37]); + OUTREG(FCP_CNTL, rinfo->save_regs[38]); + OUTREG(RBBM_CNTL, rinfo->save_regs[39]); + + OUTREG(DAC_CNTL, rinfo->save_regs[40]); + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) | DAC2_EXPAND_MODE); + + /* Reset the PAD CTLR */ + radeon_pm_reset_pad_ctlr_strength(rinfo); + + /* Some PLLs are Read & written identically in the trace here... + * I suppose it's actually to switch them all off & reset, + * let's assume off is what we want. I'm just doing that for all major PLLs now. + */ + radeon_pm_all_ppls_off(rinfo); + + /* Clear tiling, reset swappers */ + INREG(SURFACE_CNTL); + OUTREG(SURFACE_CNTL, 0); + + /* Some black magic with TV_DAC_CNTL, we should restore those from backups + * rather than hard coding... + */ + tmp = INREG(TV_DAC_CNTL) & ~TV_DAC_CNTL_BGADJ_MASK; + tmp |= 6 << TV_DAC_CNTL_BGADJ__SHIFT; + OUTREG(TV_DAC_CNTL, tmp); + + tmp = INREG(TV_DAC_CNTL) & ~TV_DAC_CNTL_DACADJ_MASK; + tmp |= 6 << TV_DAC_CNTL_DACADJ__SHIFT; + OUTREG(TV_DAC_CNTL, tmp); + + OUTPLL(pllAGP_PLL_CNTL, rinfo->save_regs[78]); + + OUTREG(PAMAC0_DLY_CNTL, rinfo->save_regs[54]); + OUTREG(PAMAC1_DLY_CNTL, rinfo->save_regs[55]); + OUTREG(PAMAC2_DLY_CNTL, rinfo->save_regs[79]); + + OUTREG(AGP_CNTL, rinfo->save_regs[16]); + OUTREG(HOST_PATH_CNTL, rinfo->save_regs[41]); /* MacOS sets that to 0 !!! */ + OUTREG(DISP_MISC_CNTL, rinfo->save_regs[9]); + + tmp = rinfo->save_regs[1] + & ~(CLK_PWRMGT_CNTL__ACTIVE_HILO_LAT_MASK | + CLK_PWRMGT_CNTL__MC_BUSY); + OUTPLL(pllCLK_PWRMGT_CNTL, tmp); + + OUTREG(FW_CNTL, rinfo->save_regs[57]); + + /* Disable SDRAM refresh */ + OUTREG(MEM_REFRESH_CNTL, INREG(MEM_REFRESH_CNTL) + | MEM_REFRESH_CNTL__MEM_REFRESH_DIS); + + /* Restore XTALIN routing (CLK_PIN_CNTL) */ + OUTPLL(pllCLK_PIN_CNTL, rinfo->save_regs[4]); + + /* Force MCLK to be PCI sourced and forced ON */ + tmp = rinfo->save_regs[2] & 0xff000000; + tmp |= MCLK_CNTL__FORCE_MCLKA | + MCLK_CNTL__FORCE_MCLKB | + MCLK_CNTL__FORCE_YCLKA | + MCLK_CNTL__FORCE_YCLKB | + MCLK_CNTL__FORCE_MC | + MCLK_CNTL__FORCE_AIC; + OUTPLL(pllMCLK_CNTL, tmp); + + /* Force SCLK to be PCI sourced with a bunch forced */ + tmp = 0 | + SCLK_CNTL__FORCE_DISP2| + SCLK_CNTL__FORCE_CP| + SCLK_CNTL__FORCE_HDP| + SCLK_CNTL__FORCE_DISP1| + SCLK_CNTL__FORCE_TOP| + SCLK_CNTL__FORCE_E2| + SCLK_CNTL__FORCE_SE| + SCLK_CNTL__FORCE_IDCT| + SCLK_CNTL__FORCE_VIP| + SCLK_CNTL__FORCE_RE| + SCLK_CNTL__FORCE_PB| + SCLK_CNTL__FORCE_TAM| + SCLK_CNTL__FORCE_TDM| + SCLK_CNTL__FORCE_RB; + OUTPLL(pllSCLK_CNTL, tmp); + + /* Clear VCLK_ECP_CNTL & PIXCLKS_CNTL */ + OUTPLL(pllVCLK_ECP_CNTL, 0); + OUTPLL(pllPIXCLKS_CNTL, 0); + + /* Setup MCLK_MISC, non dynamic mode */ + OUTPLL(pllMCLK_MISC, + MCLK_MISC__MC_MCLK_MAX_DYN_STOP_LAT | + MCLK_MISC__IO_MCLK_MAX_DYN_STOP_LAT); + + mdelay(5); + + /* Set back the default clock dividers */ + OUTPLL(pllM_SPLL_REF_FB_DIV, rinfo->save_regs[77]); + OUTPLL(pllMPLL_AUX_CNTL, rinfo->save_regs[75]); + OUTPLL(pllSPLL_AUX_CNTL, rinfo->save_regs[76]); + + /* PPLL and P2PLL default values & off */ + OUTPLL(pllPPLL_CNTL, rinfo->save_regs[93] | 0x3); + OUTPLL(pllP2PLL_CNTL, rinfo->save_regs[8] | 0x3); + + /* S and M PLLs are reset & off, configure them */ + OUTPLL(pllMPLL_CNTL, rinfo->save_regs[73] | 0x03); + OUTPLL(pllSPLL_CNTL, rinfo->save_regs[74] | 0x03); + + /* Default values for MDLL ... fixme */ + OUTPLL(pllMDLL_CKO, 0x9c009c); + OUTPLL(pllMDLL_RDCKA, 0x08830883); + OUTPLL(pllMDLL_RDCKB, 0x08830883); + mdelay(5); + + /* Restore PLL_PWRMGT_CNTL */ // XXXX + tmp = rinfo->save_regs[0]; + tmp &= ~PLL_PWRMGT_CNTL_SU_SCLK_USE_BCLK; + tmp |= PLL_PWRMGT_CNTL_SU_MCLK_USE_BCLK; + OUTPLL(PLL_PWRMGT_CNTL, tmp); + + /* Clear HTOTAL_CNTL & HTOTAL2_CNTL */ + OUTPLL(pllHTOTAL_CNTL, 0); + OUTPLL(pllHTOTAL2_CNTL, 0); + + /* All outputs off */ + OUTREG(CRTC_GEN_CNTL, 0x04000000); + OUTREG(CRTC2_GEN_CNTL, 0x04000000); + OUTREG(FP_GEN_CNTL, 0x00004008); + OUTREG(FP2_GEN_CNTL, 0x00000008); + OUTREG(LVDS_GEN_CNTL, 0x08000008); + + /* Restore Memory Controller configuration */ + radeon_pm_m9p_reconfigure_mc(rinfo); + + /* Now we actually start MCLK and SCLK */ + radeon_pm_start_mclk_sclk(rinfo); + + /* Full reset sdrams, this also re-inits the MDLL */ + radeon_pm_full_reset_sdram(rinfo); + + /* Fill palettes */ + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) | 0x20); + for (i=0; i<256; i++) + OUTREG(PALETTE_30_DATA, 0x15555555); + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) & ~20); + udelay(20); + for (i=0; i<256; i++) + OUTREG(PALETTE_30_DATA, 0x15555555); + + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) & ~0x20); + mdelay(3); + + /* Restore TV stuff, make sure TV DAC is down */ + OUTREG(TV_MASTER_CNTL, rinfo->save_regs[88]); + OUTREG(TV_DAC_CNTL, rinfo->save_regs[13] | 0x07000000); + + /* Restore GPIOS. MacOS does some magic here with one of the GPIO bits, + * possibly related to the weird PLL related workarounds and to the + * fact that CLK_PIN_CNTL is tweaked in ways I don't fully understand, + * but we keep things the simple way here + */ + OUTREG(GPIOPAD_A, rinfo->save_regs[19]); + OUTREG(GPIOPAD_EN, rinfo->save_regs[20]); + OUTREG(GPIOPAD_MASK, rinfo->save_regs[21]); + + /* Now do things with SCLK_MORE_CNTL. Force bits are already set, copy + * high bits from backup + */ + tmp = INPLL(pllSCLK_MORE_CNTL) & 0x0000ffff; + tmp |= rinfo->save_regs[34] & 0xffff0000; + tmp |= SCLK_MORE_CNTL__FORCE_DISPREGS; + OUTPLL(pllSCLK_MORE_CNTL, tmp); + + tmp = INPLL(pllSCLK_MORE_CNTL) & 0x0000ffff; + tmp |= rinfo->save_regs[34] & 0xffff0000; + tmp |= SCLK_MORE_CNTL__FORCE_DISPREGS; + OUTPLL(pllSCLK_MORE_CNTL, tmp); + + OUTREG(LVDS_GEN_CNTL, rinfo->save_regs[11] & + ~(LVDS_EN | LVDS_ON | LVDS_DIGON | LVDS_BLON | LVDS_BL_MOD_EN)); + OUTREG(LVDS_GEN_CNTL, INREG(LVDS_GEN_CNTL) | LVDS_BLON); + OUTREG(LVDS_PLL_CNTL, (rinfo->save_regs[12] & ~0xf0000) | 0x20000); + mdelay(20); + + /* write some stuff to the framebuffer... */ + for (i = 0; i < 0x8000; ++i) + writeb(0, rinfo->fb_base + i); + + OUTREG(0x2ec, 0x6332a020); + OUTPLL(pllSSPLL_REF_DIV, rinfo->save_regs[44] /*0x3f */); + OUTPLL(pllSSPLL_DIV_0, rinfo->save_regs[45] /*0x000081bb */); + tmp = INPLL(pllSSPLL_CNTL); + tmp &= ~2; + OUTPLL(pllSSPLL_CNTL, tmp); + mdelay(6); + tmp &= ~1; + OUTPLL(pllSSPLL_CNTL, tmp); + mdelay(5); + tmp |= 3; + OUTPLL(pllSSPLL_CNTL, tmp); + mdelay(5); + + OUTPLL(pllSS_INT_CNTL, rinfo->save_regs[90] & ~3);/*0x0020300c*/ + OUTREG(0x2ec, 0x6332a3f0); + mdelay(17); + + OUTPLL(pllPPLL_REF_DIV, rinfo->pll.ref_div); + OUTPLL(pllPPLL_DIV_0, rinfo->save_regs[92]); + + mdelay(40); + OUTREG(LVDS_GEN_CNTL, INREG(LVDS_GEN_CNTL) | LVDS_DIGON | LVDS_ON); + mdelay(40); + + /* Restore a few more things */ + OUTREG(GRPH_BUFFER_CNTL, rinfo->save_regs[94]); + OUTREG(GRPH2_BUFFER_CNTL, rinfo->save_regs[95]); + + /* Restore PPLL, spread spectrum & LVDS */ + radeon_pm_m10_disable_spread_spectrum(rinfo); + radeon_pm_restore_pixel_pll(rinfo); + radeon_pm_m10_enable_lvds_spread_spectrum(rinfo); +} +#endif +#endif + +#if 0 /* Not ready yet */ +static void radeon_reinitialize_QW(struct radeonfb_info *rinfo) +{ + int i; + u32 tmp, tmp2; + u32 cko, cka, ckb; + u32 cgc, cec, c2gc; + + OUTREG(MC_AGP_LOCATION, rinfo->save_regs[32]); + OUTREG(DISPLAY_BASE_ADDR, rinfo->save_regs[31]); + OUTREG(CRTC2_DISPLAY_BASE_ADDR, rinfo->save_regs[33]); + OUTREG(MC_FB_LOCATION, rinfo->save_regs[30]); + OUTREG(BUS_CNTL, rinfo->save_regs[36]); + OUTREG(RBBM_CNTL, rinfo->save_regs[39]); + + INREG(PAD_CTLR_STRENGTH); + OUTREG(PAD_CTLR_STRENGTH, INREG(PAD_CTLR_STRENGTH) & ~0x10000); + for (i = 0; i < 65; ++i) { + mdelay(1); + INREG(PAD_CTLR_STRENGTH); + } + + OUTREG(DISP_TEST_DEBUG_CNTL, INREG(DISP_TEST_DEBUG_CNTL) | 0x10000000); + OUTREG(OV0_FLAG_CNTRL, INREG(OV0_FLAG_CNTRL) | 0x100); + OUTREG(CRTC_GEN_CNTL, INREG(CRTC_GEN_CNTL)); + OUTREG(DAC_CNTL, 0xff00410a); + OUTREG(CRTC2_GEN_CNTL, INREG(CRTC2_GEN_CNTL)); + OUTREG(DAC_CNTL2, INREG(DAC_CNTL2) | 0x4000); + + OUTREG(SURFACE_CNTL, rinfo->save_regs[29]); + OUTREG(AGP_CNTL, rinfo->save_regs[16]); + OUTREG(HOST_PATH_CNTL, rinfo->save_regs[41]); + OUTREG(DISP_MISC_CNTL, rinfo->save_regs[9]); + + OUTMC(rinfo, ixMC_CHP_IO_CNTL_A0, 0xf7bb4433); + OUTREG(MC_IND_INDEX, 0); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_B0, 0xf7bb4433); + OUTREG(MC_IND_INDEX, 0); + + OUTREG(CRTC_MORE_CNTL, INREG(CRTC_MORE_CNTL)); + + tmp = INPLL(pllVCLK_ECP_CNTL); + OUTPLL(pllVCLK_ECP_CNTL, tmp); + tmp = INPLL(pllPIXCLKS_CNTL); + OUTPLL(pllPIXCLKS_CNTL, tmp); + + OUTPLL(MCLK_CNTL, 0xaa3f0000); + OUTPLL(SCLK_CNTL, 0xffff0000); + OUTPLL(pllMPLL_AUX_CNTL, 6); + OUTPLL(pllSPLL_AUX_CNTL, 1); + OUTPLL(MDLL_CKO, 0x9f009f); + OUTPLL(MDLL_RDCKA, 0x830083); + OUTPLL(pllMDLL_RDCKB, 0x830083); + OUTPLL(PPLL_CNTL, 0xa433); + OUTPLL(P2PLL_CNTL, 0xa433); + OUTPLL(MPLL_CNTL, 0x0400a403); + OUTPLL(SPLL_CNTL, 0x0400a433); + + tmp = INPLL(M_SPLL_REF_FB_DIV); + OUTPLL(M_SPLL_REF_FB_DIV, tmp); + tmp = INPLL(M_SPLL_REF_FB_DIV); + OUTPLL(M_SPLL_REF_FB_DIV, tmp | 0xc); + INPLL(M_SPLL_REF_FB_DIV); + + tmp = INPLL(MPLL_CNTL); + OUTREG8(CLOCK_CNTL_INDEX, MPLL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA + 1, (tmp >> 8) & 0xff); + radeon_pll_errata_after_data(rinfo); + + tmp = INPLL(M_SPLL_REF_FB_DIV); + OUTPLL(M_SPLL_REF_FB_DIV, tmp | 0x5900); + + tmp = INPLL(MPLL_CNTL); + OUTPLL(MPLL_CNTL, tmp & ~0x2); + mdelay(1); + tmp = INPLL(MPLL_CNTL); + OUTPLL(MPLL_CNTL, tmp & ~0x1); + mdelay(10); + + OUTPLL(MCLK_CNTL, 0xaa3f1212); + mdelay(1); + + INPLL(M_SPLL_REF_FB_DIV); + INPLL(MCLK_CNTL); + INPLL(M_SPLL_REF_FB_DIV); + + tmp = INPLL(SPLL_CNTL); + OUTREG8(CLOCK_CNTL_INDEX, SPLL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA + 1, (tmp >> 8) & 0xff); + radeon_pll_errata_after_data(rinfo); + + tmp = INPLL(M_SPLL_REF_FB_DIV); + OUTPLL(M_SPLL_REF_FB_DIV, tmp | 0x780000); + + tmp = INPLL(SPLL_CNTL); + OUTPLL(SPLL_CNTL, tmp & ~0x1); + mdelay(1); + tmp = INPLL(SPLL_CNTL); + OUTPLL(SPLL_CNTL, tmp & ~0x2); + mdelay(10); + + tmp = INPLL(SCLK_CNTL); + OUTPLL(SCLK_CNTL, tmp | 2); + mdelay(1); + + cko = INPLL(pllMDLL_CKO); + cka = INPLL(pllMDLL_RDCKA); + ckb = INPLL(pllMDLL_RDCKB); + + cko &= ~(MDLL_CKO__MCKOA_SLEEP | MDLL_CKO__MCKOB_SLEEP); + OUTPLL(pllMDLL_CKO, cko); + mdelay(1); + cko &= ~(MDLL_CKO__MCKOA_RESET | MDLL_CKO__MCKOB_RESET); + OUTPLL(pllMDLL_CKO, cko); + mdelay(5); + + cka &= ~(MDLL_RDCKA__MRDCKA0_SLEEP | MDLL_RDCKA__MRDCKA1_SLEEP); + OUTPLL(pllMDLL_RDCKA, cka); + mdelay(1); + cka &= ~(MDLL_RDCKA__MRDCKA0_RESET | MDLL_RDCKA__MRDCKA1_RESET); + OUTPLL(pllMDLL_RDCKA, cka); + mdelay(5); + + ckb &= ~(MDLL_RDCKB__MRDCKB0_SLEEP | MDLL_RDCKB__MRDCKB1_SLEEP); + OUTPLL(pllMDLL_RDCKB, ckb); + mdelay(1); + ckb &= ~(MDLL_RDCKB__MRDCKB0_RESET | MDLL_RDCKB__MRDCKB1_RESET); + OUTPLL(pllMDLL_RDCKB, ckb); + mdelay(5); + + OUTMC(rinfo, ixMC_CHP_IO_CNTL_A1, 0x151550ff); + OUTREG(MC_IND_INDEX, 0); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_B1, 0x151550ff); + OUTREG(MC_IND_INDEX, 0); + mdelay(1); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_A1, 0x141550ff); + OUTREG(MC_IND_INDEX, 0); + OUTMC(rinfo, ixMC_CHP_IO_CNTL_B1, 0x141550ff); + OUTREG(MC_IND_INDEX, 0); + mdelay(1); + + OUTPLL(pllHTOTAL_CNTL, 0); + OUTPLL(pllHTOTAL2_CNTL, 0); + + OUTREG(MEM_CNTL, 0x29002901); + OUTREG(MEM_SDRAM_MODE_REG, 0x45320032); /* XXX use save_regs[35]? */ + OUTREG(EXT_MEM_CNTL, 0x1a394333); + OUTREG(MEM_IO_CNTL_A1, 0x0aac0aac); + OUTREG(MEM_INIT_LATENCY_TIMER, 0x34444444); + OUTREG(MEM_REFRESH_CNTL, 0x1f1f7218); /* XXX or save_regs[42]? */ + OUTREG(MC_DEBUG, 0); + OUTREG(MEM_IO_OE_CNTL, 0x04300430); + + OUTMC(rinfo, ixMC_IMP_CNTL, 0x00f460d6); + OUTREG(MC_IND_INDEX, 0); + OUTMC(rinfo, ixMC_IMP_CNTL_0, 0x00009249); + OUTREG(MC_IND_INDEX, 0); + + OUTREG(CNFG_MEMSIZE, rinfo->video_ram); + + radeon_pm_full_reset_sdram(rinfo); + + INREG(FP_GEN_CNTL); + OUTREG(TMDS_CNTL, 0x01000000); /* XXX ? */ + tmp = INREG(FP_GEN_CNTL); + tmp |= FP_CRTC_DONT_SHADOW_HEND | FP_CRTC_DONT_SHADOW_VPAR | 0x200; + OUTREG(FP_GEN_CNTL, tmp); + + tmp = INREG(DISP_OUTPUT_CNTL); + tmp &= ~0x400; + OUTREG(DISP_OUTPUT_CNTL, tmp); + + OUTPLL(CLK_PIN_CNTL, rinfo->save_regs[4]); + OUTPLL(CLK_PWRMGT_CNTL, rinfo->save_regs[1]); + OUTPLL(PLL_PWRMGT_CNTL, rinfo->save_regs[0]); + + tmp = INPLL(MCLK_MISC); + tmp |= MCLK_MISC__MC_MCLK_DYN_ENABLE | MCLK_MISC__IO_MCLK_DYN_ENABLE; + OUTPLL(MCLK_MISC, tmp); + + tmp = INPLL(SCLK_CNTL); + OUTPLL(SCLK_CNTL, tmp); + + OUTREG(CRTC_MORE_CNTL, 0); + OUTREG8(CRTC_GEN_CNTL+1, 6); + OUTREG8(CRTC_GEN_CNTL+3, 1); + OUTREG(CRTC_PITCH, 32); + + tmp = INPLL(VCLK_ECP_CNTL); + OUTPLL(VCLK_ECP_CNTL, tmp); + + tmp = INPLL(PPLL_CNTL); + OUTPLL(PPLL_CNTL, tmp); + + /* palette stuff and BIOS_1_SCRATCH... */ + + tmp = INREG(FP_GEN_CNTL); + tmp2 = INREG(TMDS_TRANSMITTER_CNTL); + tmp |= 2; + OUTREG(FP_GEN_CNTL, tmp); + mdelay(5); + OUTREG(FP_GEN_CNTL, tmp); + mdelay(5); + OUTREG(TMDS_TRANSMITTER_CNTL, tmp2); + OUTREG(CRTC_MORE_CNTL, 0); + mdelay(20); + + tmp = INREG(CRTC_MORE_CNTL); + OUTREG(CRTC_MORE_CNTL, tmp); + + cgc = INREG(CRTC_GEN_CNTL); + cec = INREG(CRTC_EXT_CNTL); + c2gc = INREG(CRTC2_GEN_CNTL); + + OUTREG(CRTC_H_SYNC_STRT_WID, 0x008e0580); + OUTREG(CRTC_H_TOTAL_DISP, 0x009f00d2); + OUTREG8(CLOCK_CNTL_INDEX, HTOTAL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA, 0); + radeon_pll_errata_after_data(rinfo); + OUTREG(CRTC_V_SYNC_STRT_WID, 0x00830403); + OUTREG(CRTC_V_TOTAL_DISP, 0x03ff0429); + OUTREG(FP_CRTC_H_TOTAL_DISP, 0x009f0033); + OUTREG(FP_H_SYNC_STRT_WID, 0x008e0080); + OUTREG(CRT_CRTC_H_SYNC_STRT_WID, 0x008e0080); + OUTREG(FP_CRTC_V_TOTAL_DISP, 0x03ff002a); + OUTREG(FP_V_SYNC_STRT_WID, 0x00830004); + OUTREG(CRT_CRTC_V_SYNC_STRT_WID, 0x00830004); + OUTREG(FP_HORZ_VERT_ACTIVE, 0x009f03ff); + OUTREG(FP_HORZ_STRETCH, 0); + OUTREG(FP_VERT_STRETCH, 0); + OUTREG(OVR_CLR, 0); + OUTREG(OVR_WID_LEFT_RIGHT, 0); + OUTREG(OVR_WID_TOP_BOTTOM, 0); + + tmp = INPLL(PPLL_REF_DIV); + tmp = (tmp & ~PPLL_REF_DIV_MASK) | rinfo->pll.ref_div; + OUTPLL(PPLL_REF_DIV, tmp); + INPLL(PPLL_REF_DIV); + + OUTREG8(CLOCK_CNTL_INDEX, PPLL_CNTL + PLL_WR_EN); + radeon_pll_errata_after_index(rinfo); + OUTREG8(CLOCK_CNTL_DATA + 1, 0xbc); + radeon_pll_errata_after_data(rinfo); + + tmp = INREG(CLOCK_CNTL_INDEX); + radeon_pll_errata_after_index(rinfo); + OUTREG(CLOCK_CNTL_INDEX, tmp & 0xff); + radeon_pll_errata_after_index(rinfo); + radeon_pll_errata_after_data(rinfo); + + OUTPLL(PPLL_DIV_0, 0x48090); + + tmp = INPLL(PPLL_CNTL); + OUTPLL(PPLL_CNTL, tmp & ~0x2); + mdelay(1); + tmp = INPLL(PPLL_CNTL); + OUTPLL(PPLL_CNTL, tmp & ~0x1); + mdelay(10); + + tmp = INPLL(VCLK_ECP_CNTL); + OUTPLL(VCLK_ECP_CNTL, tmp | 3); + mdelay(1); + + tmp = INPLL(VCLK_ECP_CNTL); + OUTPLL(VCLK_ECP_CNTL, tmp); + + c2gc |= CRTC2_DISP_REQ_EN_B; + OUTREG(CRTC2_GEN_CNTL, c2gc); + cgc |= CRTC_EN; + OUTREG(CRTC_GEN_CNTL, cgc); + OUTREG(CRTC_EXT_CNTL, cec); + OUTREG(CRTC_PITCH, 0xa0); + OUTREG(CRTC_OFFSET, 0); + OUTREG(CRTC_OFFSET_CNTL, 0); + + OUTREG(GRPH_BUFFER_CNTL, 0x20117c7c); + OUTREG(GRPH2_BUFFER_CNTL, 0x00205c5c); + + tmp2 = INREG(FP_GEN_CNTL); + tmp = INREG(TMDS_TRANSMITTER_CNTL); + OUTREG(0x2a8, 0x0000061b); + tmp |= TMDS_PLL_EN; + OUTREG(TMDS_TRANSMITTER_CNTL, tmp); + mdelay(1); + tmp &= ~TMDS_PLLRST; + OUTREG(TMDS_TRANSMITTER_CNTL, tmp); + tmp2 &= ~2; + tmp2 |= FP_TMDS_EN; + OUTREG(FP_GEN_CNTL, tmp2); + mdelay(5); + tmp2 |= FP_FPON; + OUTREG(FP_GEN_CNTL, tmp2); + + OUTREG(CUR_HORZ_VERT_OFF, CUR_LOCK | 1); + cgc = INREG(CRTC_GEN_CNTL); + OUTREG(CUR_HORZ_VERT_POSN, 0xbfff0fff); + cgc |= 0x10000; + OUTREG(CUR_OFFSET, 0); +} +#endif /* 0 */ + +#endif /* CONFIG_PPC_OF */ + +static void radeonfb_whack_power_state(struct radeonfb_info *rinfo, pci_power_t state) +{ + u16 pwr_cmd; + + for (;;) { + pci_read_config_word(rinfo->pdev, + rinfo->pdev->pm_cap + PCI_PM_CTRL, + &pwr_cmd); + if (pwr_cmd & state) + break; + pwr_cmd = (pwr_cmd & ~PCI_PM_CTRL_STATE_MASK) | state; + pci_write_config_word(rinfo->pdev, + rinfo->pdev->pm_cap + PCI_PM_CTRL, + pwr_cmd); + msleep(500); + } + rinfo->pdev->current_state = state; +} + +static void radeon_set_suspend(struct radeonfb_info *rinfo, int suspend) +{ + u32 tmp; + + if (!rinfo->pdev->pm_cap) + return; + + /* Set the chip into appropriate suspend mode (we use D2, + * D3 would require a compete re-initialization of the chip, + * including PCI config registers, clocks, AGP conf, ...) + */ + if (suspend) { + printk(KERN_DEBUG "radeonfb (%s): switching to D2 state...\n", + pci_name(rinfo->pdev)); + + /* Disable dynamic power management of clocks for the + * duration of the suspend/resume process + */ + radeon_pm_disable_dynamic_mode(rinfo); + + /* Save some registers */ + radeon_pm_save_regs(rinfo, 0); + + /* Prepare mobility chips for suspend. + */ + if (rinfo->is_mobility) { + /* Program V2CLK */ + radeon_pm_program_v2clk(rinfo); + + /* Disable IO PADs */ + radeon_pm_disable_iopad(rinfo); + + /* Set low current */ + radeon_pm_low_current(rinfo); + + /* Prepare chip for power management */ + radeon_pm_setup_for_suspend(rinfo); + + if (rinfo->family <= CHIP_FAMILY_RV280) { + /* Reset the MDLL */ + /* because both INPLL and OUTPLL take the same + * lock, that's why. */ + tmp = INPLL( pllMDLL_CKO) | MDLL_CKO__MCKOA_RESET + | MDLL_CKO__MCKOB_RESET; + OUTPLL( pllMDLL_CKO, tmp ); + } + } + + /* Switch PCI power management to D2. */ + pci_disable_device(rinfo->pdev); + pci_save_state(rinfo->pdev); + /* The chip seems to need us to whack the PM register + * repeatedly until it sticks. We do that -prior- to + * calling pci_set_power_state() + */ + radeonfb_whack_power_state(rinfo, PCI_D2); + __pci_complete_power_transition(rinfo->pdev, PCI_D2); + } else { + printk(KERN_DEBUG "radeonfb (%s): switching to D0 state...\n", + pci_name(rinfo->pdev)); + + if (rinfo->family <= CHIP_FAMILY_RV250) { + /* Reset the SDRAM controller */ + radeon_pm_full_reset_sdram(rinfo); + + /* Restore some registers */ + radeon_pm_restore_regs(rinfo); + } else { + /* Restore registers first */ + radeon_pm_restore_regs(rinfo); + /* init sdram controller */ + radeon_pm_full_reset_sdram(rinfo); + } + } +} + +int radeonfb_pci_suspend(struct pci_dev *pdev, pm_message_t mesg) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct radeonfb_info *rinfo = info->par; + + if (mesg.event == pdev->dev.power.power_state.event) + return 0; + + printk(KERN_DEBUG "radeonfb (%s): suspending for event: %d...\n", + pci_name(pdev), mesg.event); + + /* For suspend-to-disk, we cheat here. We don't suspend anything and + * let fbcon continue drawing until we are all set. That shouldn't + * really cause any problem at this point, provided that the wakeup + * code knows that any state in memory may not match the HW + */ + switch (mesg.event) { + case PM_EVENT_FREEZE: /* about to take snapshot */ + case PM_EVENT_PRETHAW: /* before restoring snapshot */ + goto done; + } + + console_lock(); + + fb_set_suspend(info, 1); + + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) { + /* Make sure engine is reset */ + radeon_engine_idle(); + radeonfb_engine_reset(rinfo); + radeon_engine_idle(); + } + + /* Blank display and LCD */ + radeon_screen_blank(rinfo, FB_BLANK_POWERDOWN, 1); + + /* Sleep */ + rinfo->asleep = 1; + rinfo->lock_blank = 1; + del_timer_sync(&rinfo->lvds_timer); + +#ifdef CONFIG_PPC_PMAC + /* On powermac, we have hooks to properly suspend/resume AGP now, + * use them here. We'll ultimately need some generic support here, + * but the generic code isn't quite ready for that yet + */ + pmac_suspend_agp_for_card(pdev); +#endif /* CONFIG_PPC_PMAC */ + + /* It's unclear whether or when the generic code will do that, so let's + * do it ourselves. We save state before we do any power management + */ + pci_save_state(pdev); + + /* If we support wakeup from poweroff, we save all regs we can including cfg + * space + */ + if (rinfo->pm_mode & radeon_pm_off) { + /* Always disable dynamic clocks or weird things are happening when + * the chip goes off (basically the panel doesn't shut down properly + * and we crash on wakeup), + * also, we want the saved regs context to have no dynamic clocks in + * it, we'll restore the dynamic clocks state on wakeup + */ + radeon_pm_disable_dynamic_mode(rinfo); + mdelay(50); + radeon_pm_save_regs(rinfo, 1); + + if (rinfo->is_mobility && !(rinfo->pm_mode & radeon_pm_d2)) { + /* Switch off LVDS interface */ + mdelay(1); + OUTREG(LVDS_GEN_CNTL, INREG(LVDS_GEN_CNTL) & ~(LVDS_BL_MOD_EN)); + mdelay(1); + OUTREG(LVDS_GEN_CNTL, INREG(LVDS_GEN_CNTL) & ~(LVDS_EN | LVDS_ON)); + OUTREG(LVDS_PLL_CNTL, (INREG(LVDS_PLL_CNTL) & ~30000) | 0x20000); + mdelay(20); + OUTREG(LVDS_GEN_CNTL, INREG(LVDS_GEN_CNTL) & ~(LVDS_DIGON)); + } + pci_disable_device(pdev); + } + /* If we support D2, we go to it (should be fixed later with a flag forcing + * D3 only for some laptops) + */ + if (rinfo->pm_mode & radeon_pm_d2) + radeon_set_suspend(rinfo, 1); + + console_unlock(); + + done: + pdev->dev.power.power_state = mesg; + + return 0; +} + +static int radeon_check_power_loss(struct radeonfb_info *rinfo) +{ + return rinfo->save_regs[4] != INPLL(CLK_PIN_CNTL) || + rinfo->save_regs[2] != INPLL(MCLK_CNTL) || + rinfo->save_regs[3] != INPLL(SCLK_CNTL); +} + +int radeonfb_pci_resume(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct radeonfb_info *rinfo = info->par; + int rc = 0; + + if (pdev->dev.power.power_state.event == PM_EVENT_ON) + return 0; + + if (rinfo->no_schedule) { + if (!console_trylock()) + return 0; + } else + console_lock(); + + printk(KERN_DEBUG "radeonfb (%s): resuming from state: %d...\n", + pci_name(pdev), pdev->dev.power.power_state.event); + + /* PCI state will have been restored by the core, so + * we should be in D0 now with our config space fully + * restored + */ + if (pdev->dev.power.power_state.event == PM_EVENT_SUSPEND) { + /* Wakeup chip */ + if ((rinfo->pm_mode & radeon_pm_off) && radeon_check_power_loss(rinfo)) { + if (rinfo->reinit_func != NULL) + rinfo->reinit_func(rinfo); + else { + printk(KERN_ERR "radeonfb (%s): can't resume radeon from" + " D3 cold, need softboot !", pci_name(pdev)); + rc = -EIO; + goto bail; + } + } + /* If we support D2, try to resume... we should check what was our + * state though... (were we really in D2 state ?). Right now, this code + * is only enable on Macs so it's fine. + */ + else if (rinfo->pm_mode & radeon_pm_d2) + radeon_set_suspend(rinfo, 0); + + rinfo->asleep = 0; + } else + radeon_engine_idle(); + + /* Restore display & engine */ + radeon_write_mode (rinfo, &rinfo->state, 1); + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) + radeonfb_engine_init (rinfo); + + fb_pan_display(info, &info->var); + fb_set_cmap(&info->cmap, info); + + /* Refresh */ + fb_set_suspend(info, 0); + + /* Unblank */ + rinfo->lock_blank = 0; + radeon_screen_blank(rinfo, FB_BLANK_UNBLANK, 1); + +#ifdef CONFIG_PPC_PMAC + /* On powermac, we have hooks to properly suspend/resume AGP now, + * use them here. We'll ultimately need some generic support here, + * but the generic code isn't quite ready for that yet + */ + pmac_resume_agp_for_card(pdev); +#endif /* CONFIG_PPC_PMAC */ + + + /* Check status of dynclk */ + if (rinfo->dynclk == 1) + radeon_pm_enable_dynamic_mode(rinfo); + else if (rinfo->dynclk == 0) + radeon_pm_disable_dynamic_mode(rinfo); + + pdev->dev.power.power_state = PMSG_ON; + + bail: + console_unlock(); + + return rc; +} + +#ifdef CONFIG_PPC_OF__disabled +static void radeonfb_early_resume(void *data) +{ + struct radeonfb_info *rinfo = data; + + rinfo->no_schedule = 1; + pci_restore_state(rinfo->pdev); + radeonfb_pci_resume(rinfo->pdev); + rinfo->no_schedule = 0; +} +#endif /* CONFIG_PPC_OF */ + +#endif /* CONFIG_PM */ + +void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk, int ignore_devlist, int force_sleep) +{ + /* Enable/Disable dynamic clocks: TODO add sysfs access */ + if (rinfo->family == CHIP_FAMILY_RS480) + rinfo->dynclk = -1; + else + rinfo->dynclk = dynclk; + + if (rinfo->dynclk == 1) { + radeon_pm_enable_dynamic_mode(rinfo); + printk("radeonfb: Dynamic Clock Power Management enabled\n"); + } else if (rinfo->dynclk == 0) { + radeon_pm_disable_dynamic_mode(rinfo); + printk("radeonfb: Dynamic Clock Power Management disabled\n"); + } + +#if defined(CONFIG_PM) +#if defined(CONFIG_PPC_PMAC) + /* Check if we can power manage on suspend/resume. We can do + * D2 on M6, M7 and M9, and we can resume from D3 cold a few other + * "Mac" cards, but that's all. We need more infos about what the + * BIOS does tho. Right now, all this PM stuff is pmac-only for that + * reason. --BenH + */ + if (machine_is(powermac) && rinfo->of_node) { + if (rinfo->is_mobility && rinfo->pdev->pm_cap && + rinfo->family <= CHIP_FAMILY_RV250) + rinfo->pm_mode |= radeon_pm_d2; + + /* We can restart Jasper (M10 chip in albooks), BlueStone (7500 chip + * in some desktop G4s), Via (M9+ chip on iBook G4) and + * Snowy (M11 chip on iBook G4 manufactured after July 2005) + */ + if (!strcmp(rinfo->of_node->name, "ATY,JasperParent") || + !strcmp(rinfo->of_node->name, "ATY,SnowyParent")) { + rinfo->reinit_func = radeon_reinitialize_M10; + rinfo->pm_mode |= radeon_pm_off; + } +#if 0 /* Not ready yet */ + if (!strcmp(rinfo->of_node->name, "ATY,BlueStoneParent")) { + rinfo->reinit_func = radeon_reinitialize_QW; + rinfo->pm_mode |= radeon_pm_off; + } +#endif + if (!strcmp(rinfo->of_node->name, "ATY,ViaParent")) { + rinfo->reinit_func = radeon_reinitialize_M9P; + rinfo->pm_mode |= radeon_pm_off; + } + + /* If any of the above is set, we assume the machine can sleep/resume. + * It's a bit of a "shortcut" but will work fine. Ideally, we need infos + * from the platform about what happens to the chip... + * Now we tell the platform about our capability + */ + if (rinfo->pm_mode != radeon_pm_none) { + pmac_call_feature(PMAC_FTR_DEVICE_CAN_WAKE, rinfo->of_node, 0, 1); +#if 0 /* Disable the early video resume hack for now as it's causing problems, among + * others we now rely on the PCI core restoring the config space for us, which + * isn't the case with that hack, and that code path causes various things to + * be called with interrupts off while they shouldn't. I'm leaving the code in + * as it can be useful for debugging purposes + */ + pmac_set_early_video_resume(radeonfb_early_resume, rinfo); +#endif + } + +#if 0 + /* Power down TV DAC, that saves a significant amount of power, + * we'll have something better once we actually have some TVOut + * support + */ + OUTREG(TV_DAC_CNTL, INREG(TV_DAC_CNTL) | 0x07000000); +#endif + } +#endif /* defined(CONFIG_PPC_PMAC) */ +#endif /* defined(CONFIG_PM) */ + + if (ignore_devlist) + printk(KERN_DEBUG + "radeonfb: skipping test for device workarounds\n"); + else + radeon_apply_workarounds(rinfo); + + if (force_sleep) { + printk(KERN_DEBUG + "radeonfb: forcefully enabling D2 sleep mode\n"); + rinfo->pm_mode |= radeon_pm_d2; + } +} + +void radeonfb_pm_exit(struct radeonfb_info *rinfo) +{ +#if defined(CONFIG_PM) && defined(CONFIG_PPC_PMAC) + if (rinfo->pm_mode != radeon_pm_none) + pmac_set_early_video_resume(NULL, NULL); +#endif +} diff --git a/drivers/video/fbdev/aty/radeonfb.h b/drivers/video/fbdev/aty/radeonfb.h new file mode 100644 index 000000000000..cb846044f57c --- /dev/null +++ b/drivers/video/fbdev/aty/radeonfb.h @@ -0,0 +1,634 @@ +#ifndef __RADEONFB_H__ +#define __RADEONFB_H__ + +#ifdef CONFIG_FB_RADEON_DEBUG +#define DEBUG 1 +#endif + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/fb.h> + + +#ifdef CONFIG_FB_RADEON_I2C +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#endif + +#include <asm/io.h> + +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) +#include <asm/prom.h> +#endif + +#include <video/radeon.h> + +/*************************************************************** + * Most of the definitions here are adapted right from XFree86 * + ***************************************************************/ + + +/* + * Chip families. Must fit in the low 16 bits of a long word + */ +enum radeon_family { + CHIP_FAMILY_UNKNOW, + CHIP_FAMILY_LEGACY, + CHIP_FAMILY_RADEON, + CHIP_FAMILY_RV100, + CHIP_FAMILY_RS100, /* U1 (IGP320M) or A3 (IGP320)*/ + CHIP_FAMILY_RV200, + CHIP_FAMILY_RS200, /* U2 (IGP330M/340M/350M) or A4 (IGP330/340/345/350), + RS250 (IGP 7000) */ + CHIP_FAMILY_R200, + CHIP_FAMILY_RV250, + CHIP_FAMILY_RS300, /* Radeon 9000 IGP */ + CHIP_FAMILY_RV280, + CHIP_FAMILY_R300, + CHIP_FAMILY_R350, + CHIP_FAMILY_RV350, + CHIP_FAMILY_RV380, /* RV370/RV380/M22/M24 */ + CHIP_FAMILY_R420, /* R420/R423/M18 */ + CHIP_FAMILY_RC410, + CHIP_FAMILY_RS400, + CHIP_FAMILY_RS480, + CHIP_FAMILY_LAST, +}; + +#define IS_RV100_VARIANT(rinfo) (((rinfo)->family == CHIP_FAMILY_RV100) || \ + ((rinfo)->family == CHIP_FAMILY_RV200) || \ + ((rinfo)->family == CHIP_FAMILY_RS100) || \ + ((rinfo)->family == CHIP_FAMILY_RS200) || \ + ((rinfo)->family == CHIP_FAMILY_RV250) || \ + ((rinfo)->family == CHIP_FAMILY_RV280) || \ + ((rinfo)->family == CHIP_FAMILY_RS300)) + + +#define IS_R300_VARIANT(rinfo) (((rinfo)->family == CHIP_FAMILY_R300) || \ + ((rinfo)->family == CHIP_FAMILY_RV350) || \ + ((rinfo)->family == CHIP_FAMILY_R350) || \ + ((rinfo)->family == CHIP_FAMILY_RV380) || \ + ((rinfo)->family == CHIP_FAMILY_R420) || \ + ((rinfo)->family == CHIP_FAMILY_RC410) || \ + ((rinfo)->family == CHIP_FAMILY_RS480)) + +/* + * Chip flags + */ +enum radeon_chip_flags { + CHIP_FAMILY_MASK = 0x0000ffffUL, + CHIP_FLAGS_MASK = 0xffff0000UL, + CHIP_IS_MOBILITY = 0x00010000UL, + CHIP_IS_IGP = 0x00020000UL, + CHIP_HAS_CRTC2 = 0x00040000UL, +}; + +/* + * Errata workarounds + */ +enum radeon_errata { + CHIP_ERRATA_R300_CG = 0x00000001, + CHIP_ERRATA_PLL_DUMMYREADS = 0x00000002, + CHIP_ERRATA_PLL_DELAY = 0x00000004, +}; + + +/* + * Monitor types + */ +enum radeon_montype { + MT_NONE = 0, + MT_CRT, /* CRT */ + MT_LCD, /* LCD */ + MT_DFP, /* DVI */ + MT_CTV, /* composite TV */ + MT_STV /* S-Video out */ +}; + +/* + * DDC i2c ports + */ +enum ddc_type { + ddc_none, + ddc_monid, + ddc_dvi, + ddc_vga, + ddc_crt2, +}; + +/* + * Connector types + */ +enum conn_type { + conn_none, + conn_proprietary, + conn_crt, + conn_DVI_I, + conn_DVI_D, +}; + + +/* + * PLL infos + */ +struct pll_info { + int ppll_max; + int ppll_min; + int sclk, mclk; + int ref_div; + int ref_clk; +}; + + +/* + * This structure contains the various registers manipulated by this + * driver for setting or restoring a mode. It's mostly copied from + * XFree's RADEONSaveRec structure. A few chip settings might still be + * tweaked without beeing reflected or saved in these registers though + */ +struct radeon_regs { + /* Common registers */ + u32 ovr_clr; + u32 ovr_wid_left_right; + u32 ovr_wid_top_bottom; + u32 ov0_scale_cntl; + u32 mpp_tb_config; + u32 mpp_gp_config; + u32 subpic_cntl; + u32 viph_control; + u32 i2c_cntl_1; + u32 gen_int_cntl; + u32 cap0_trig_cntl; + u32 cap1_trig_cntl; + u32 bus_cntl; + u32 surface_cntl; + u32 bios_5_scratch; + + /* Other registers to save for VT switches or driver load/unload */ + u32 dp_datatype; + u32 rbbm_soft_reset; + u32 clock_cntl_index; + u32 amcgpio_en_reg; + u32 amcgpio_mask; + + /* Surface/tiling registers */ + u32 surf_lower_bound[8]; + u32 surf_upper_bound[8]; + u32 surf_info[8]; + + /* CRTC registers */ + u32 crtc_gen_cntl; + u32 crtc_ext_cntl; + u32 dac_cntl; + u32 crtc_h_total_disp; + u32 crtc_h_sync_strt_wid; + u32 crtc_v_total_disp; + u32 crtc_v_sync_strt_wid; + u32 crtc_offset; + u32 crtc_offset_cntl; + u32 crtc_pitch; + u32 disp_merge_cntl; + u32 grph_buffer_cntl; + u32 crtc_more_cntl; + + /* CRTC2 registers */ + u32 crtc2_gen_cntl; + u32 dac2_cntl; + u32 disp_output_cntl; + u32 disp_hw_debug; + u32 disp2_merge_cntl; + u32 grph2_buffer_cntl; + u32 crtc2_h_total_disp; + u32 crtc2_h_sync_strt_wid; + u32 crtc2_v_total_disp; + u32 crtc2_v_sync_strt_wid; + u32 crtc2_offset; + u32 crtc2_offset_cntl; + u32 crtc2_pitch; + + /* Flat panel regs */ + u32 fp_crtc_h_total_disp; + u32 fp_crtc_v_total_disp; + u32 fp_gen_cntl; + u32 fp2_gen_cntl; + u32 fp_h_sync_strt_wid; + u32 fp2_h_sync_strt_wid; + u32 fp_horz_stretch; + u32 fp_panel_cntl; + u32 fp_v_sync_strt_wid; + u32 fp2_v_sync_strt_wid; + u32 fp_vert_stretch; + u32 lvds_gen_cntl; + u32 lvds_pll_cntl; + u32 tmds_crc; + u32 tmds_transmitter_cntl; + + /* Computed values for PLL */ + u32 dot_clock_freq; + int feedback_div; + int post_div; + + /* PLL registers */ + u32 ppll_div_3; + u32 ppll_ref_div; + u32 vclk_ecp_cntl; + u32 clk_cntl_index; + + /* Computed values for PLL2 */ + u32 dot_clock_freq_2; + int feedback_div_2; + int post_div_2; + + /* PLL2 registers */ + u32 p2pll_ref_div; + u32 p2pll_div_0; + u32 htotal_cntl2; + + /* Palette */ + int palette_valid; +}; + +struct panel_info { + int xres, yres; + int valid; + int clock; + int hOver_plus, hSync_width, hblank; + int vOver_plus, vSync_width, vblank; + int hAct_high, vAct_high, interlaced; + int pwr_delay; + int use_bios_dividers; + int ref_divider; + int post_divider; + int fbk_divider; +}; + +struct radeonfb_info; + +#ifdef CONFIG_FB_RADEON_I2C +struct radeon_i2c_chan { + struct radeonfb_info *rinfo; + u32 ddc_reg; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo; +}; +#endif + +enum radeon_pm_mode { + radeon_pm_none = 0, /* Nothing supported */ + radeon_pm_d2 = 0x00000001, /* Can do D2 state */ + radeon_pm_off = 0x00000002, /* Can resume from D3 cold */ +}; + +typedef void (*reinit_function_ptr)(struct radeonfb_info *rinfo); + +struct radeonfb_info { + struct fb_info *info; + + struct radeon_regs state; + struct radeon_regs init_state; + + char name[50]; + + unsigned long mmio_base_phys; + unsigned long fb_base_phys; + + void __iomem *mmio_base; + void __iomem *fb_base; + + unsigned long fb_local_base; + + struct pci_dev *pdev; +#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC) + struct device_node *of_node; +#endif + + void __iomem *bios_seg; + int fp_bios_start; + + u32 pseudo_palette[16]; + struct { u8 red, green, blue, pad; } + palette[256]; + + int chipset; + u8 family; + u8 rev; + unsigned int errata; + unsigned long video_ram; + unsigned long mapped_vram; + int vram_width; + int vram_ddr; + + int pitch, bpp, depth; + + int has_CRTC2; + int is_mobility; + int is_IGP; + int reversed_DAC; + int reversed_TMDS; + struct panel_info panel_info; + int mon1_type; + u8 *mon1_EDID; + struct fb_videomode *mon1_modedb; + int mon1_dbsize; + int mon2_type; + u8 *mon2_EDID; + + u32 dp_gui_master_cntl; + + struct pll_info pll; + + int mtrr_hdl; + + u32 save_regs[100]; + int asleep; + int lock_blank; + int dynclk; + int no_schedule; + enum radeon_pm_mode pm_mode; + reinit_function_ptr reinit_func; + + /* Lock on register access */ + spinlock_t reg_lock; + + /* Timer used for delayed LVDS operations */ + struct timer_list lvds_timer; + u32 pending_lvds_gen_cntl; + +#ifdef CONFIG_FB_RADEON_I2C + struct radeon_i2c_chan i2c[4]; +#endif +}; + + +#define PRIMARY_MONITOR(rinfo) (rinfo->mon1_type) + + +/* + * IO macros + */ + +/* Note about this function: we have some rare cases where we must not schedule, + * this typically happen with our special "wake up early" hook which allows us to + * wake up the graphic chip (and thus get the console back) before everything else + * on some machines that support that mechanism. At this point, interrupts are off + * and scheduling is not permitted + */ +static inline void _radeon_msleep(struct radeonfb_info *rinfo, unsigned long ms) +{ + if (rinfo->no_schedule || oops_in_progress) + mdelay(ms); + else + msleep(ms); +} + + +#define INREG8(addr) readb((rinfo->mmio_base)+addr) +#define OUTREG8(addr,val) writeb(val, (rinfo->mmio_base)+addr) +#define INREG16(addr) readw((rinfo->mmio_base)+addr) +#define OUTREG16(addr,val) writew(val, (rinfo->mmio_base)+addr) +#define INREG(addr) readl((rinfo->mmio_base)+addr) +#define OUTREG(addr,val) writel(val, (rinfo->mmio_base)+addr) + +static inline void _OUTREGP(struct radeonfb_info *rinfo, u32 addr, + u32 val, u32 mask) +{ + unsigned long flags; + unsigned int tmp; + + spin_lock_irqsave(&rinfo->reg_lock, flags); + tmp = INREG(addr); + tmp &= (mask); + tmp |= (val); + OUTREG(addr, tmp); + spin_unlock_irqrestore(&rinfo->reg_lock, flags); +} + +#define OUTREGP(addr,val,mask) _OUTREGP(rinfo, addr, val,mask) + +/* + * Note about PLL register accesses: + * + * I have removed the spinlock on them on purpose. The driver now + * expects that it will only manipulate the PLL registers in normal + * task environment, where radeon_msleep() will be called, protected + * by a semaphore (currently the console semaphore) so that no conflict + * will happen on the PLL register index. + * + * With the latest changes to the VT layer, this is guaranteed for all + * calls except the actual drawing/blits which aren't supposed to use + * the PLL registers anyway + * + * This is very important for the workarounds to work properly. The only + * possible exception to this rule is the call to unblank(), which may + * be done at irq time if an oops is in progress. + */ +static inline void radeon_pll_errata_after_index(struct radeonfb_info *rinfo) +{ + if (!(rinfo->errata & CHIP_ERRATA_PLL_DUMMYREADS)) + return; + + (void)INREG(CLOCK_CNTL_DATA); + (void)INREG(CRTC_GEN_CNTL); +} + +static inline void radeon_pll_errata_after_data(struct radeonfb_info *rinfo) +{ + if (rinfo->errata & CHIP_ERRATA_PLL_DELAY) { + /* we can't deal with posted writes here ... */ + _radeon_msleep(rinfo, 5); + } + if (rinfo->errata & CHIP_ERRATA_R300_CG) { + u32 save, tmp; + save = INREG(CLOCK_CNTL_INDEX); + tmp = save & ~(0x3f | PLL_WR_EN); + OUTREG(CLOCK_CNTL_INDEX, tmp); + tmp = INREG(CLOCK_CNTL_DATA); + OUTREG(CLOCK_CNTL_INDEX, save); + } +} + +static inline u32 __INPLL(struct radeonfb_info *rinfo, u32 addr) +{ + u32 data; + + OUTREG8(CLOCK_CNTL_INDEX, addr & 0x0000003f); + radeon_pll_errata_after_index(rinfo); + data = INREG(CLOCK_CNTL_DATA); + radeon_pll_errata_after_data(rinfo); + return data; +} + +static inline void __OUTPLL(struct radeonfb_info *rinfo, unsigned int index, + u32 val) +{ + + OUTREG8(CLOCK_CNTL_INDEX, (index & 0x0000003f) | 0x00000080); + radeon_pll_errata_after_index(rinfo); + OUTREG(CLOCK_CNTL_DATA, val); + radeon_pll_errata_after_data(rinfo); +} + + +static inline void __OUTPLLP(struct radeonfb_info *rinfo, unsigned int index, + u32 val, u32 mask) +{ + unsigned int tmp; + + tmp = __INPLL(rinfo, index); + tmp &= (mask); + tmp |= (val); + __OUTPLL(rinfo, index, tmp); +} + + +#define INPLL(addr) __INPLL(rinfo, addr) +#define OUTPLL(index, val) __OUTPLL(rinfo, index, val) +#define OUTPLLP(index, val, mask) __OUTPLLP(rinfo, index, val, mask) + + +#define BIOS_IN8(v) (readb(rinfo->bios_seg + (v))) +#define BIOS_IN16(v) (readb(rinfo->bios_seg + (v)) | \ + (readb(rinfo->bios_seg + (v) + 1) << 8)) +#define BIOS_IN32(v) (readb(rinfo->bios_seg + (v)) | \ + (readb(rinfo->bios_seg + (v) + 1) << 8) | \ + (readb(rinfo->bios_seg + (v) + 2) << 16) | \ + (readb(rinfo->bios_seg + (v) + 3) << 24)) + +/* + * Inline utilities + */ +static inline int round_div(int num, int den) +{ + return (num + (den / 2)) / den; +} + +static inline int var_to_depth(const struct fb_var_screeninfo *var) +{ + if (var->bits_per_pixel != 16) + return var->bits_per_pixel; + return (var->green.length == 5) ? 15 : 16; +} + +static inline u32 radeon_get_dstbpp(u16 depth) +{ + switch (depth) { + case 8: + return DST_8BPP; + case 15: + return DST_15BPP; + case 16: + return DST_16BPP; + case 32: + return DST_32BPP; + default: + return 0; + } +} + +/* + * 2D Engine helper routines + */ + +static inline void _radeon_fifo_wait(struct radeonfb_info *rinfo, int entries) +{ + int i; + + for (i=0; i<2000000; i++) { + if ((INREG(RBBM_STATUS) & 0x7f) >= entries) + return; + udelay(1); + } + printk(KERN_ERR "radeonfb: FIFO Timeout !\n"); +} + +static inline void radeon_engine_flush (struct radeonfb_info *rinfo) +{ + int i; + + /* Initiate flush */ + OUTREGP(DSTCACHE_CTLSTAT, RB2D_DC_FLUSH_ALL, + ~RB2D_DC_FLUSH_ALL); + + /* Ensure FIFO is empty, ie, make sure the flush commands + * has reached the cache + */ + _radeon_fifo_wait (rinfo, 64); + + /* Wait for the flush to complete */ + for (i=0; i < 2000000; i++) { + if (!(INREG(DSTCACHE_CTLSTAT) & RB2D_DC_BUSY)) + return; + udelay(1); + } + printk(KERN_ERR "radeonfb: Flush Timeout !\n"); +} + + +static inline void _radeon_engine_idle(struct radeonfb_info *rinfo) +{ + int i; + + /* ensure FIFO is empty before waiting for idle */ + _radeon_fifo_wait (rinfo, 64); + + for (i=0; i<2000000; i++) { + if (((INREG(RBBM_STATUS) & GUI_ACTIVE)) == 0) { + radeon_engine_flush (rinfo); + return; + } + udelay(1); + } + printk(KERN_ERR "radeonfb: Idle Timeout !\n"); +} + + +#define radeon_engine_idle() _radeon_engine_idle(rinfo) +#define radeon_fifo_wait(entries) _radeon_fifo_wait(rinfo,entries) +#define radeon_msleep(ms) _radeon_msleep(rinfo,ms) + + +/* I2C Functions */ +extern void radeon_create_i2c_busses(struct radeonfb_info *rinfo); +extern void radeon_delete_i2c_busses(struct radeonfb_info *rinfo); +extern int radeon_probe_i2c_connector(struct radeonfb_info *rinfo, int conn, u8 **out_edid); + +/* PM Functions */ +extern int radeonfb_pci_suspend(struct pci_dev *pdev, pm_message_t state); +extern int radeonfb_pci_resume(struct pci_dev *pdev); +extern void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk, int ignore_devlist, int force_sleep); +extern void radeonfb_pm_exit(struct radeonfb_info *rinfo); + +/* Monitor probe functions */ +extern void radeon_probe_screens(struct radeonfb_info *rinfo, + const char *monitor_layout, int ignore_edid); +extern void radeon_check_modes(struct radeonfb_info *rinfo, const char *mode_option); +extern int radeon_match_mode(struct radeonfb_info *rinfo, + struct fb_var_screeninfo *dest, + const struct fb_var_screeninfo *src); + +/* Accel functions */ +extern void radeonfb_fillrect(struct fb_info *info, const struct fb_fillrect *region); +extern void radeonfb_copyarea(struct fb_info *info, const struct fb_copyarea *area); +extern void radeonfb_imageblit(struct fb_info *p, const struct fb_image *image); +extern int radeonfb_sync(struct fb_info *info); +extern void radeonfb_engine_init (struct radeonfb_info *rinfo); +extern void radeonfb_engine_reset(struct radeonfb_info *rinfo); + +/* Other functions */ +extern int radeon_screen_blank(struct radeonfb_info *rinfo, int blank, int mode_switch); +extern void radeon_write_mode (struct radeonfb_info *rinfo, struct radeon_regs *mode, + int reg_only); + +/* Backlight functions */ +#ifdef CONFIG_FB_RADEON_BACKLIGHT +extern void radeonfb_bl_init(struct radeonfb_info *rinfo); +extern void radeonfb_bl_exit(struct radeonfb_info *rinfo); +#else +static inline void radeonfb_bl_init(struct radeonfb_info *rinfo) {} +static inline void radeonfb_bl_exit(struct radeonfb_info *rinfo) {} +#endif + +#endif /* __RADEONFB_H__ */ diff --git a/drivers/video/fbdev/au1100fb.c b/drivers/video/fbdev/au1100fb.c new file mode 100644 index 000000000000..372d4aea9d1c --- /dev/null +++ b/drivers/video/fbdev/au1100fb.c @@ -0,0 +1,642 @@ +/* + * BRIEF MODULE DESCRIPTION + * Au1100 LCD Driver. + * + * Rewritten for 2.6 by Embedded Alley Solutions + * <source@embeddedalley.com>, based on submissions by + * Karl Lessard <klessard@sunrisetelecom.com> + * <c.pellegrin@exadron.com> + * + * PM support added by Rodolfo Giometti <giometti@linux.it> + * Cursor enable/disable by Rodolfo Giometti <giometti@linux.it> + * + * Copyright 2002 MontaVista Software + * Author: MontaVista Software, Inc. + * ppopov@mvista.com or source@mvista.com + * + * Copyright 2002 Alchemy Semiconductor + * Author: Alchemy Semiconductor + * + * Based on: + * linux/drivers/video/skeletonfb.c -- Skeleton for a frame buffer device + * Created 28 Dec 1997 by Geert Uytterhoeven + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ctype.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <asm/mach-au1x00/au1000.h> + +#define DEBUG 0 + +#include "au1100fb.h" + +#define DRIVER_NAME "au1100fb" +#define DRIVER_DESC "LCD controller driver for AU1100 processors" + +#define to_au1100fb_device(_info) \ + (_info ? container_of(_info, struct au1100fb_device, info) : NULL); + +/* Bitfields format supported by the controller. Note that the order of formats + * SHOULD be the same as in the LCD_CONTROL_SBPPF field, so we can retrieve the + * right pixel format by doing rgb_bitfields[LCD_CONTROL_SBPPF_XXX >> LCD_CONTROL_SBPPF] + */ +struct fb_bitfield rgb_bitfields[][4] = +{ + /* Red, Green, Blue, Transp */ + { { 10, 6, 0 }, { 5, 5, 0 }, { 0, 5, 0 }, { 0, 0, 0 } }, + { { 11, 5, 0 }, { 5, 6, 0 }, { 0, 5, 0 }, { 0, 0, 0 } }, + { { 11, 5, 0 }, { 6, 5, 0 }, { 0, 6, 0 }, { 0, 0, 0 } }, + { { 10, 5, 0 }, { 5, 5, 0 }, { 0, 5, 0 }, { 15, 1, 0 } }, + { { 11, 5, 0 }, { 6, 5, 0 }, { 1, 5, 0 }, { 0, 1, 0 } }, + + /* The last is used to describe 12bpp format */ + { { 8, 4, 0 }, { 4, 4, 0 }, { 0, 4, 0 }, { 0, 0, 0 } }, +}; + +static struct fb_fix_screeninfo au1100fb_fix = { + .id = "AU1100 FB", + .xpanstep = 1, + .ypanstep = 1, + .type = FB_TYPE_PACKED_PIXELS, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo au1100fb_var = { + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, +}; + +/* fb_blank + * Blank the screen. Depending on the mode, the screen will be + * activated with the backlight color, or desactivated + */ +static int au1100fb_fb_blank(int blank_mode, struct fb_info *fbi) +{ + struct au1100fb_device *fbdev = to_au1100fb_device(fbi); + + print_dbg("fb_blank %d %p", blank_mode, fbi); + + switch (blank_mode) { + + case VESA_NO_BLANKING: + /* Turn on panel */ + fbdev->regs->lcd_control |= LCD_CONTROL_GO; + au_sync(); + break; + + case VESA_VSYNC_SUSPEND: + case VESA_HSYNC_SUSPEND: + case VESA_POWERDOWN: + /* Turn off panel */ + fbdev->regs->lcd_control &= ~LCD_CONTROL_GO; + au_sync(); + break; + default: + break; + + } + return 0; +} + +/* + * Set hardware with var settings. This will enable the controller with a specific + * mode, normally validated with the fb_check_var method + */ +int au1100fb_setmode(struct au1100fb_device *fbdev) +{ + struct fb_info *info = &fbdev->info; + u32 words; + int index; + + if (!fbdev) + return -EINVAL; + + /* Update var-dependent FB info */ + if (panel_is_active(fbdev->panel) || panel_is_color(fbdev->panel)) { + if (info->var.bits_per_pixel <= 8) { + /* palettized */ + info->var.red.offset = 0; + info->var.red.length = info->var.bits_per_pixel; + info->var.red.msb_right = 0; + + info->var.green.offset = 0; + info->var.green.length = info->var.bits_per_pixel; + info->var.green.msb_right = 0; + + info->var.blue.offset = 0; + info->var.blue.length = info->var.bits_per_pixel; + info->var.blue.msb_right = 0; + + info->var.transp.offset = 0; + info->var.transp.length = 0; + info->var.transp.msb_right = 0; + + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.line_length = info->var.xres_virtual / + (8/info->var.bits_per_pixel); + } else { + /* non-palettized */ + index = (fbdev->panel->control_base & LCD_CONTROL_SBPPF_MASK) >> LCD_CONTROL_SBPPF_BIT; + info->var.red = rgb_bitfields[index][0]; + info->var.green = rgb_bitfields[index][1]; + info->var.blue = rgb_bitfields[index][2]; + info->var.transp = rgb_bitfields[index][3]; + + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.xres_virtual << 1; /* depth=16 */ + } + } else { + /* mono */ + info->fix.visual = FB_VISUAL_MONO10; + info->fix.line_length = info->var.xres_virtual / 8; + } + + info->screen_size = info->fix.line_length * info->var.yres_virtual; + info->var.rotate = ((fbdev->panel->control_base&LCD_CONTROL_SM_MASK) \ + >> LCD_CONTROL_SM_BIT) * 90; + + /* Determine BPP mode and format */ + fbdev->regs->lcd_control = fbdev->panel->control_base; + fbdev->regs->lcd_horztiming = fbdev->panel->horztiming; + fbdev->regs->lcd_verttiming = fbdev->panel->verttiming; + fbdev->regs->lcd_clkcontrol = fbdev->panel->clkcontrol_base; + fbdev->regs->lcd_intenable = 0; + fbdev->regs->lcd_intstatus = 0; + fbdev->regs->lcd_dmaaddr0 = LCD_DMA_SA_N(fbdev->fb_phys); + + if (panel_is_dual(fbdev->panel)) { + /* Second panel display seconf half of screen if possible, + * otherwise display the same as the first panel */ + if (info->var.yres_virtual >= (info->var.yres << 1)) { + fbdev->regs->lcd_dmaaddr1 = LCD_DMA_SA_N(fbdev->fb_phys + + (info->fix.line_length * + (info->var.yres_virtual >> 1))); + } else { + fbdev->regs->lcd_dmaaddr1 = LCD_DMA_SA_N(fbdev->fb_phys); + } + } + + words = info->fix.line_length / sizeof(u32); + if (!info->var.rotate || (info->var.rotate == 180)) { + words *= info->var.yres_virtual; + if (info->var.rotate /* 180 */) { + words -= (words % 8); /* should be divisable by 8 */ + } + } + fbdev->regs->lcd_words = LCD_WRD_WRDS_N(words); + + fbdev->regs->lcd_pwmdiv = 0; + fbdev->regs->lcd_pwmhi = 0; + + /* Resume controller */ + fbdev->regs->lcd_control |= LCD_CONTROL_GO; + mdelay(10); + au1100fb_fb_blank(VESA_NO_BLANKING, info); + + return 0; +} + +/* fb_setcolreg + * Set color in LCD palette. + */ +int au1100fb_fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fbi) +{ + struct au1100fb_device *fbdev; + u32 *palette; + u32 value; + + fbdev = to_au1100fb_device(fbi); + palette = fbdev->regs->lcd_pallettebase; + + if (regno > (AU1100_LCD_NBR_PALETTE_ENTRIES - 1)) + return -EINVAL; + + if (fbi->var.grayscale) { + /* Convert color to grayscale */ + red = green = blue = + (19595 * red + 38470 * green + 7471 * blue) >> 16; + } + + if (fbi->fix.visual == FB_VISUAL_TRUECOLOR) { + /* Place color in the pseudopalette */ + if (regno > 16) + return -EINVAL; + + palette = (u32*)fbi->pseudo_palette; + + red >>= (16 - fbi->var.red.length); + green >>= (16 - fbi->var.green.length); + blue >>= (16 - fbi->var.blue.length); + + value = (red << fbi->var.red.offset) | + (green << fbi->var.green.offset)| + (blue << fbi->var.blue.offset); + value &= 0xFFFF; + + } else if (panel_is_active(fbdev->panel)) { + /* COLOR TFT PALLETTIZED (use RGB 565) */ + value = (red & 0xF800)|((green >> 5) & 0x07E0)|((blue >> 11) & 0x001F); + value &= 0xFFFF; + + } else if (panel_is_color(fbdev->panel)) { + /* COLOR STN MODE */ + value = (((panel_swap_rgb(fbdev->panel) ? blue : red) >> 12) & 0x000F) | + ((green >> 8) & 0x00F0) | + (((panel_swap_rgb(fbdev->panel) ? red : blue) >> 4) & 0x0F00); + value &= 0xFFF; + } else { + /* MONOCHROME MODE */ + value = (green >> 12) & 0x000F; + value &= 0xF; + } + + palette[regno] = value; + + return 0; +} + +/* fb_pan_display + * Pan display in x and/or y as specified + */ +int au1100fb_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + struct au1100fb_device *fbdev; + int dy; + + fbdev = to_au1100fb_device(fbi); + + print_dbg("fb_pan_display %p %p", var, fbi); + + if (!var || !fbdev) { + return -EINVAL; + } + + if (var->xoffset - fbi->var.xoffset) { + /* No support for X panning for now! */ + return -EINVAL; + } + + print_dbg("fb_pan_display 2 %p %p", var, fbi); + dy = var->yoffset - fbi->var.yoffset; + if (dy) { + + u32 dmaaddr; + + print_dbg("Panning screen of %d lines", dy); + + dmaaddr = fbdev->regs->lcd_dmaaddr0; + dmaaddr += (fbi->fix.line_length * dy); + + /* TODO: Wait for current frame to finished */ + fbdev->regs->lcd_dmaaddr0 = LCD_DMA_SA_N(dmaaddr); + + if (panel_is_dual(fbdev->panel)) { + dmaaddr = fbdev->regs->lcd_dmaaddr1; + dmaaddr += (fbi->fix.line_length * dy); + fbdev->regs->lcd_dmaaddr0 = LCD_DMA_SA_N(dmaaddr); + } + } + print_dbg("fb_pan_display 3 %p %p", var, fbi); + + return 0; +} + +/* fb_rotate + * Rotate the display of this angle. This doesn't seems to be used by the core, + * but as our hardware supports it, so why not implementing it... + */ +void au1100fb_fb_rotate(struct fb_info *fbi, int angle) +{ + struct au1100fb_device *fbdev = to_au1100fb_device(fbi); + + print_dbg("fb_rotate %p %d", fbi, angle); + + if (fbdev && (angle > 0) && !(angle % 90)) { + + fbdev->regs->lcd_control &= ~LCD_CONTROL_GO; + + fbdev->regs->lcd_control &= ~(LCD_CONTROL_SM_MASK); + fbdev->regs->lcd_control |= ((angle/90) << LCD_CONTROL_SM_BIT); + + fbdev->regs->lcd_control |= LCD_CONTROL_GO; + } +} + +/* fb_mmap + * Map video memory in user space. We don't use the generic fb_mmap method mainly + * to allow the use of the TLB streaming flag (CCA=6) + */ +int au1100fb_fb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + struct au1100fb_device *fbdev; + + fbdev = to_au1100fb_device(fbi); + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + pgprot_val(vma->vm_page_prot) |= (6 << 9); //CCA=6 + + return vm_iomap_memory(vma, fbdev->fb_phys, fbdev->fb_len); +} + +static struct fb_ops au1100fb_ops = +{ + .owner = THIS_MODULE, + .fb_setcolreg = au1100fb_fb_setcolreg, + .fb_blank = au1100fb_fb_blank, + .fb_pan_display = au1100fb_fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_rotate = au1100fb_fb_rotate, + .fb_mmap = au1100fb_fb_mmap, +}; + + +/*-------------------------------------------------------------------------*/ + +static int au1100fb_setup(struct au1100fb_device *fbdev) +{ + char *this_opt, *options; + int num_panels = ARRAY_SIZE(known_lcd_panels); + + if (num_panels <= 0) { + print_err("No LCD panels supported by driver!"); + return -ENODEV; + } + + if (fb_get_options(DRIVER_NAME, &options)) + return -ENODEV; + if (!options) + return -ENODEV; + + while ((this_opt = strsep(&options, ",")) != NULL) { + /* Panel option */ + if (!strncmp(this_opt, "panel:", 6)) { + int i; + this_opt += 6; + for (i = 0; i < num_panels; i++) { + if (!strncmp(this_opt, known_lcd_panels[i].name, + strlen(this_opt))) { + fbdev->panel = &known_lcd_panels[i]; + fbdev->panel_idx = i; + break; + } + } + if (i >= num_panels) { + print_warn("Panel '%s' not supported!", this_opt); + return -ENODEV; + } + } + /* Unsupported option */ + else + print_warn("Unsupported option \"%s\"", this_opt); + } + + print_info("Panel=%s", fbdev->panel->name); + + return 0; +} + +static int au1100fb_drv_probe(struct platform_device *dev) +{ + struct au1100fb_device *fbdev = NULL; + struct resource *regs_res; + unsigned long page; + u32 sys_clksrc; + + /* Allocate new device private */ + fbdev = devm_kzalloc(&dev->dev, sizeof(struct au1100fb_device), + GFP_KERNEL); + if (!fbdev) { + print_err("fail to allocate device private record"); + return -ENOMEM; + } + + if (au1100fb_setup(fbdev)) + goto failed; + + platform_set_drvdata(dev, (void *)fbdev); + + /* Allocate region for our registers and map them */ + regs_res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!regs_res) { + print_err("fail to retrieve registers resource"); + return -EFAULT; + } + + au1100fb_fix.mmio_start = regs_res->start; + au1100fb_fix.mmio_len = resource_size(regs_res); + + if (!devm_request_mem_region(&dev->dev, + au1100fb_fix.mmio_start, + au1100fb_fix.mmio_len, + DRIVER_NAME)) { + print_err("fail to lock memory region at 0x%08lx", + au1100fb_fix.mmio_start); + return -EBUSY; + } + + fbdev->regs = (struct au1100fb_regs*)KSEG1ADDR(au1100fb_fix.mmio_start); + + print_dbg("Register memory map at %p", fbdev->regs); + print_dbg("phys=0x%08x, size=%d", fbdev->regs_phys, fbdev->regs_len); + + /* Allocate the framebuffer to the maximum screen size * nbr of video buffers */ + fbdev->fb_len = fbdev->panel->xres * fbdev->panel->yres * + (fbdev->panel->bpp >> 3) * AU1100FB_NBR_VIDEO_BUFFERS; + + fbdev->fb_mem = dmam_alloc_coherent(&dev->dev, + PAGE_ALIGN(fbdev->fb_len), + &fbdev->fb_phys, GFP_KERNEL); + if (!fbdev->fb_mem) { + print_err("fail to allocate frambuffer (size: %dK))", + fbdev->fb_len / 1024); + return -ENOMEM; + } + + au1100fb_fix.smem_start = fbdev->fb_phys; + au1100fb_fix.smem_len = fbdev->fb_len; + + /* + * Set page reserved so that mmap will work. This is necessary + * since we'll be remapping normal memory. + */ + for (page = (unsigned long)fbdev->fb_mem; + page < PAGE_ALIGN((unsigned long)fbdev->fb_mem + fbdev->fb_len); + page += PAGE_SIZE) { +#ifdef CONFIG_DMA_NONCOHERENT + SetPageReserved(virt_to_page(CAC_ADDR((void *)page))); +#else + SetPageReserved(virt_to_page(page)); +#endif + } + + print_dbg("Framebuffer memory map at %p", fbdev->fb_mem); + print_dbg("phys=0x%08x, size=%dK", fbdev->fb_phys, fbdev->fb_len / 1024); + + /* Setup LCD clock to AUX (48 MHz) */ + sys_clksrc = au_readl(SYS_CLKSRC) & ~(SYS_CS_ML_MASK | SYS_CS_DL | SYS_CS_CL); + au_writel((sys_clksrc | (1 << SYS_CS_ML_BIT)), SYS_CLKSRC); + + /* load the panel info into the var struct */ + au1100fb_var.bits_per_pixel = fbdev->panel->bpp; + au1100fb_var.xres = fbdev->panel->xres; + au1100fb_var.xres_virtual = au1100fb_var.xres; + au1100fb_var.yres = fbdev->panel->yres; + au1100fb_var.yres_virtual = au1100fb_var.yres; + + fbdev->info.screen_base = fbdev->fb_mem; + fbdev->info.fbops = &au1100fb_ops; + fbdev->info.fix = au1100fb_fix; + + fbdev->info.pseudo_palette = + devm_kzalloc(&dev->dev, sizeof(u32) * 16, GFP_KERNEL); + if (!fbdev->info.pseudo_palette) + return -ENOMEM; + + if (fb_alloc_cmap(&fbdev->info.cmap, AU1100_LCD_NBR_PALETTE_ENTRIES, 0) < 0) { + print_err("Fail to allocate colormap (%d entries)", + AU1100_LCD_NBR_PALETTE_ENTRIES); + return -EFAULT; + } + + fbdev->info.var = au1100fb_var; + + /* Set h/w registers */ + au1100fb_setmode(fbdev); + + /* Register new framebuffer */ + if (register_framebuffer(&fbdev->info) < 0) { + print_err("cannot register new framebuffer"); + goto failed; + } + + return 0; + +failed: + if (fbdev->fb_mem) { + dma_free_noncoherent(&dev->dev, fbdev->fb_len, fbdev->fb_mem, + fbdev->fb_phys); + } + if (fbdev->info.cmap.len != 0) { + fb_dealloc_cmap(&fbdev->info.cmap); + } + + return -ENODEV; +} + +int au1100fb_drv_remove(struct platform_device *dev) +{ + struct au1100fb_device *fbdev = NULL; + + if (!dev) + return -ENODEV; + + fbdev = platform_get_drvdata(dev); + +#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) + au1100fb_fb_blank(VESA_POWERDOWN, &fbdev->info); +#endif + fbdev->regs->lcd_control &= ~LCD_CONTROL_GO; + + /* Clean up all probe data */ + unregister_framebuffer(&fbdev->info); + + fb_dealloc_cmap(&fbdev->info.cmap); + + return 0; +} + +#ifdef CONFIG_PM +static u32 sys_clksrc; +static struct au1100fb_regs fbregs; + +int au1100fb_drv_suspend(struct platform_device *dev, pm_message_t state) +{ + struct au1100fb_device *fbdev = platform_get_drvdata(dev); + + if (!fbdev) + return 0; + + /* Save the clock source state */ + sys_clksrc = au_readl(SYS_CLKSRC); + + /* Blank the LCD */ + au1100fb_fb_blank(VESA_POWERDOWN, &fbdev->info); + + /* Stop LCD clocking */ + au_writel(sys_clksrc & ~SYS_CS_ML_MASK, SYS_CLKSRC); + + memcpy(&fbregs, fbdev->regs, sizeof(struct au1100fb_regs)); + + return 0; +} + +int au1100fb_drv_resume(struct platform_device *dev) +{ + struct au1100fb_device *fbdev = platform_get_drvdata(dev); + + if (!fbdev) + return 0; + + memcpy(fbdev->regs, &fbregs, sizeof(struct au1100fb_regs)); + + /* Restart LCD clocking */ + au_writel(sys_clksrc, SYS_CLKSRC); + + /* Unblank the LCD */ + au1100fb_fb_blank(VESA_NO_BLANKING, &fbdev->info); + + return 0; +} +#else +#define au1100fb_drv_suspend NULL +#define au1100fb_drv_resume NULL +#endif + +static struct platform_driver au1100fb_driver = { + .driver = { + .name = "au1100-lcd", + .owner = THIS_MODULE, + }, + .probe = au1100fb_drv_probe, + .remove = au1100fb_drv_remove, + .suspend = au1100fb_drv_suspend, + .resume = au1100fb_drv_resume, +}; +module_platform_driver(au1100fb_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/au1100fb.h b/drivers/video/fbdev/au1100fb.h new file mode 100644 index 000000000000..12d9642d5465 --- /dev/null +++ b/drivers/video/fbdev/au1100fb.h @@ -0,0 +1,377 @@ +/* + * BRIEF MODULE DESCRIPTION + * Hardware definitions for the Au1100 LCD controller + * + * Copyright 2002 MontaVista Software + * Copyright 2002 Alchemy Semiconductor + * Author: Alchemy Semiconductor, MontaVista Software + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _AU1100LCD_H +#define _AU1100LCD_H + +#include <asm/mach-au1x00/au1000.h> + +#define print_err(f, arg...) printk(KERN_ERR DRIVER_NAME ": " f "\n", ## arg) +#define print_warn(f, arg...) printk(KERN_WARNING DRIVER_NAME ": " f "\n", ## arg) +#define print_info(f, arg...) printk(KERN_INFO DRIVER_NAME ": " f "\n", ## arg) + +#if DEBUG +#define print_dbg(f, arg...) printk(__FILE__ ": " f "\n", ## arg) +#else +#define print_dbg(f, arg...) do {} while (0) +#endif + +#if defined(__BIG_ENDIAN) +#define LCD_CONTROL_DEFAULT_PO LCD_CONTROL_PO_11 +#else +#define LCD_CONTROL_DEFAULT_PO LCD_CONTROL_PO_00 +#endif +#define LCD_CONTROL_DEFAULT_SBPPF LCD_CONTROL_SBPPF_565 + +/********************************************************************/ + +/* LCD controller restrictions */ +#define AU1100_LCD_MAX_XRES 800 +#define AU1100_LCD_MAX_YRES 600 +#define AU1100_LCD_MAX_BPP 16 +#define AU1100_LCD_MAX_CLK 48000000 +#define AU1100_LCD_NBR_PALETTE_ENTRIES 256 + +/* Default number of visible screen buffer to allocate */ +#define AU1100FB_NBR_VIDEO_BUFFERS 4 + +/********************************************************************/ + +struct au1100fb_panel +{ + const char name[25]; /* Full name <vendor>_<model> */ + + u32 control_base; /* Mode-independent control values */ + u32 clkcontrol_base; /* Panel pixclock preferences */ + + u32 horztiming; + u32 verttiming; + + u32 xres; /* Maximum horizontal resolution */ + u32 yres; /* Maximum vertical resolution */ + u32 bpp; /* Maximum depth supported */ +}; + +struct au1100fb_regs +{ + u32 lcd_control; + u32 lcd_intstatus; + u32 lcd_intenable; + u32 lcd_horztiming; + u32 lcd_verttiming; + u32 lcd_clkcontrol; + u32 lcd_dmaaddr0; + u32 lcd_dmaaddr1; + u32 lcd_words; + u32 lcd_pwmdiv; + u32 lcd_pwmhi; + u32 reserved[(0x0400-0x002C)/4]; + u32 lcd_pallettebase[256]; +}; + +struct au1100fb_device { + + struct fb_info info; /* FB driver info record */ + + struct au1100fb_panel *panel; /* Panel connected to this device */ + + struct au1100fb_regs* regs; /* Registers memory map */ + size_t regs_len; + unsigned int regs_phys; + + unsigned char* fb_mem; /* FrameBuffer memory map */ + size_t fb_len; + dma_addr_t fb_phys; + int panel_idx; +}; + +/********************************************************************/ + +#define LCD_CONTROL (AU1100_LCD_BASE + 0x0) + #define LCD_CONTROL_SBB_BIT 21 + #define LCD_CONTROL_SBB_MASK (0x3 << LCD_CONTROL_SBB_BIT) + #define LCD_CONTROL_SBB_1 (0 << LCD_CONTROL_SBB_BIT) + #define LCD_CONTROL_SBB_2 (1 << LCD_CONTROL_SBB_BIT) + #define LCD_CONTROL_SBB_3 (2 << LCD_CONTROL_SBB_BIT) + #define LCD_CONTROL_SBB_4 (3 << LCD_CONTROL_SBB_BIT) + #define LCD_CONTROL_SBPPF_BIT 18 + #define LCD_CONTROL_SBPPF_MASK (0x7 << LCD_CONTROL_SBPPF_BIT) + #define LCD_CONTROL_SBPPF_655 (0 << LCD_CONTROL_SBPPF_BIT) + #define LCD_CONTROL_SBPPF_565 (1 << LCD_CONTROL_SBPPF_BIT) + #define LCD_CONTROL_SBPPF_556 (2 << LCD_CONTROL_SBPPF_BIT) + #define LCD_CONTROL_SBPPF_1555 (3 << LCD_CONTROL_SBPPF_BIT) + #define LCD_CONTROL_SBPPF_5551 (4 << LCD_CONTROL_SBPPF_BIT) + #define LCD_CONTROL_WP (1<<17) + #define LCD_CONTROL_WD (1<<16) + #define LCD_CONTROL_C (1<<15) + #define LCD_CONTROL_SM_BIT 13 + #define LCD_CONTROL_SM_MASK (0x3 << LCD_CONTROL_SM_BIT) + #define LCD_CONTROL_SM_0 (0 << LCD_CONTROL_SM_BIT) + #define LCD_CONTROL_SM_90 (1 << LCD_CONTROL_SM_BIT) + #define LCD_CONTROL_SM_180 (2 << LCD_CONTROL_SM_BIT) + #define LCD_CONTROL_SM_270 (3 << LCD_CONTROL_SM_BIT) + #define LCD_CONTROL_DB (1<<12) + #define LCD_CONTROL_CCO (1<<11) + #define LCD_CONTROL_DP (1<<10) + #define LCD_CONTROL_PO_BIT 8 + #define LCD_CONTROL_PO_MASK (0x3 << LCD_CONTROL_PO_BIT) + #define LCD_CONTROL_PO_00 (0 << LCD_CONTROL_PO_BIT) + #define LCD_CONTROL_PO_01 (1 << LCD_CONTROL_PO_BIT) + #define LCD_CONTROL_PO_10 (2 << LCD_CONTROL_PO_BIT) + #define LCD_CONTROL_PO_11 (3 << LCD_CONTROL_PO_BIT) + #define LCD_CONTROL_MPI (1<<7) + #define LCD_CONTROL_PT (1<<6) + #define LCD_CONTROL_PC (1<<5) + #define LCD_CONTROL_BPP_BIT 1 + #define LCD_CONTROL_BPP_MASK (0x7 << LCD_CONTROL_BPP_BIT) + #define LCD_CONTROL_BPP_1 (0 << LCD_CONTROL_BPP_BIT) + #define LCD_CONTROL_BPP_2 (1 << LCD_CONTROL_BPP_BIT) + #define LCD_CONTROL_BPP_4 (2 << LCD_CONTROL_BPP_BIT) + #define LCD_CONTROL_BPP_8 (3 << LCD_CONTROL_BPP_BIT) + #define LCD_CONTROL_BPP_12 (4 << LCD_CONTROL_BPP_BIT) + #define LCD_CONTROL_BPP_16 (5 << LCD_CONTROL_BPP_BIT) + #define LCD_CONTROL_GO (1<<0) + +#define LCD_INTSTATUS (AU1100_LCD_BASE + 0x4) +#define LCD_INTENABLE (AU1100_LCD_BASE + 0x8) + #define LCD_INT_SD (1<<7) + #define LCD_INT_OF (1<<6) + #define LCD_INT_UF (1<<5) + #define LCD_INT_SA (1<<3) + #define LCD_INT_SS (1<<2) + #define LCD_INT_S1 (1<<1) + #define LCD_INT_S0 (1<<0) + +#define LCD_HORZTIMING (AU1100_LCD_BASE + 0xC) + #define LCD_HORZTIMING_HN2_BIT 24 + #define LCD_HORZTIMING_HN2_MASK (0xFF << LCD_HORZTIMING_HN2_BIT) + #define LCD_HORZTIMING_HN2_N(N) ((((N)-1) << LCD_HORZTIMING_HN2_BIT) & LCD_HORZTIMING_HN2_MASK) + #define LCD_HORZTIMING_HN1_BIT 16 + #define LCD_HORZTIMING_HN1_MASK (0xFF << LCD_HORZTIMING_HN1_BIT) + #define LCD_HORZTIMING_HN1_N(N) ((((N)-1) << LCD_HORZTIMING_HN1_BIT) & LCD_HORZTIMING_HN1_MASK) + #define LCD_HORZTIMING_HPW_BIT 10 + #define LCD_HORZTIMING_HPW_MASK (0x3F << LCD_HORZTIMING_HPW_BIT) + #define LCD_HORZTIMING_HPW_N(N) ((((N)-1) << LCD_HORZTIMING_HPW_BIT) & LCD_HORZTIMING_HPW_MASK) + #define LCD_HORZTIMING_PPL_BIT 0 + #define LCD_HORZTIMING_PPL_MASK (0x3FF << LCD_HORZTIMING_PPL_BIT) + #define LCD_HORZTIMING_PPL_N(N) ((((N)-1) << LCD_HORZTIMING_PPL_BIT) & LCD_HORZTIMING_PPL_MASK) + +#define LCD_VERTTIMING (AU1100_LCD_BASE + 0x10) + #define LCD_VERTTIMING_VN2_BIT 24 + #define LCD_VERTTIMING_VN2_MASK (0xFF << LCD_VERTTIMING_VN2_BIT) + #define LCD_VERTTIMING_VN2_N(N) ((((N)-1) << LCD_VERTTIMING_VN2_BIT) & LCD_VERTTIMING_VN2_MASK) + #define LCD_VERTTIMING_VN1_BIT 16 + #define LCD_VERTTIMING_VN1_MASK (0xFF << LCD_VERTTIMING_VN1_BIT) + #define LCD_VERTTIMING_VN1_N(N) ((((N)-1) << LCD_VERTTIMING_VN1_BIT) & LCD_VERTTIMING_VN1_MASK) + #define LCD_VERTTIMING_VPW_BIT 10 + #define LCD_VERTTIMING_VPW_MASK (0x3F << LCD_VERTTIMING_VPW_BIT) + #define LCD_VERTTIMING_VPW_N(N) ((((N)-1) << LCD_VERTTIMING_VPW_BIT) & LCD_VERTTIMING_VPW_MASK) + #define LCD_VERTTIMING_LPP_BIT 0 + #define LCD_VERTTIMING_LPP_MASK (0x3FF << LCD_VERTTIMING_LPP_BIT) + #define LCD_VERTTIMING_LPP_N(N) ((((N)-1) << LCD_VERTTIMING_LPP_BIT) & LCD_VERTTIMING_LPP_MASK) + +#define LCD_CLKCONTROL (AU1100_LCD_BASE + 0x14) + #define LCD_CLKCONTROL_IB (1<<18) + #define LCD_CLKCONTROL_IC (1<<17) + #define LCD_CLKCONTROL_IH (1<<16) + #define LCD_CLKCONTROL_IV (1<<15) + #define LCD_CLKCONTROL_BF_BIT 10 + #define LCD_CLKCONTROL_BF_MASK (0x1F << LCD_CLKCONTROL_BF_BIT) + #define LCD_CLKCONTROL_BF_N(N) ((((N)-1) << LCD_CLKCONTROL_BF_BIT) & LCD_CLKCONTROL_BF_MASK) + #define LCD_CLKCONTROL_PCD_BIT 0 + #define LCD_CLKCONTROL_PCD_MASK (0x3FF << LCD_CLKCONTROL_PCD_BIT) + #define LCD_CLKCONTROL_PCD_N(N) (((N) << LCD_CLKCONTROL_PCD_BIT) & LCD_CLKCONTROL_PCD_MASK) + +#define LCD_DMAADDR0 (AU1100_LCD_BASE + 0x18) +#define LCD_DMAADDR1 (AU1100_LCD_BASE + 0x1C) + #define LCD_DMA_SA_BIT 5 + #define LCD_DMA_SA_MASK (0x7FFFFFF << LCD_DMA_SA_BIT) + #define LCD_DMA_SA_N(N) ((N) & LCD_DMA_SA_MASK) + +#define LCD_WORDS (AU1100_LCD_BASE + 0x20) + #define LCD_WRD_WRDS_BIT 0 + #define LCD_WRD_WRDS_MASK (0xFFFFFFFF << LCD_WRD_WRDS_BIT) + #define LCD_WRD_WRDS_N(N) ((((N)-1) << LCD_WRD_WRDS_BIT) & LCD_WRD_WRDS_MASK) + +#define LCD_PWMDIV (AU1100_LCD_BASE + 0x24) + #define LCD_PWMDIV_EN (1<<12) + #define LCD_PWMDIV_PWMDIV_BIT 0 + #define LCD_PWMDIV_PWMDIV_MASK (0xFFF << LCD_PWMDIV_PWMDIV_BIT) + #define LCD_PWMDIV_PWMDIV_N(N) ((((N)-1) << LCD_PWMDIV_PWMDIV_BIT) & LCD_PWMDIV_PWMDIV_MASK) + +#define LCD_PWMHI (AU1100_LCD_BASE + 0x28) + #define LCD_PWMHI_PWMHI1_BIT 12 + #define LCD_PWMHI_PWMHI1_MASK (0xFFF << LCD_PWMHI_PWMHI1_BIT) + #define LCD_PWMHI_PWMHI1_N(N) (((N) << LCD_PWMHI_PWMHI1_BIT) & LCD_PWMHI_PWMHI1_MASK) + #define LCD_PWMHI_PWMHI0_BIT 0 + #define LCD_PWMHI_PWMHI0_MASK (0xFFF << LCD_PWMHI_PWMHI0_BIT) + #define LCD_PWMHI_PWMHI0_N(N) (((N) << LCD_PWMHI_PWMHI0_BIT) & LCD_PWMHI_PWMHI0_MASK) + +#define LCD_PALLETTEBASE (AU1100_LCD_BASE + 0x400) + #define LCD_PALLETTE_MONO_MI_BIT 0 + #define LCD_PALLETTE_MONO_MI_MASK (0xF << LCD_PALLETTE_MONO_MI_BIT) + #define LCD_PALLETTE_MONO_MI_N(N) (((N)<< LCD_PALLETTE_MONO_MI_BIT) & LCD_PALLETTE_MONO_MI_MASK) + + #define LCD_PALLETTE_COLOR_RI_BIT 8 + #define LCD_PALLETTE_COLOR_RI_MASK (0xF << LCD_PALLETTE_COLOR_RI_BIT) + #define LCD_PALLETTE_COLOR_RI_N(N) (((N)<< LCD_PALLETTE_COLOR_RI_BIT) & LCD_PALLETTE_COLOR_RI_MASK) + #define LCD_PALLETTE_COLOR_GI_BIT 4 + #define LCD_PALLETTE_COLOR_GI_MASK (0xF << LCD_PALLETTE_COLOR_GI_BIT) + #define LCD_PALLETTE_COLOR_GI_N(N) (((N)<< LCD_PALLETTE_COLOR_GI_BIT) & LCD_PALLETTE_COLOR_GI_MASK) + #define LCD_PALLETTE_COLOR_BI_BIT 0 + #define LCD_PALLETTE_COLOR_BI_MASK (0xF << LCD_PALLETTE_COLOR_BI_BIT) + #define LCD_PALLETTE_COLOR_BI_N(N) (((N)<< LCD_PALLETTE_COLOR_BI_BIT) & LCD_PALLETTE_COLOR_BI_MASK) + + #define LCD_PALLETTE_TFT_DC_BIT 0 + #define LCD_PALLETTE_TFT_DC_MASK (0xFFFF << LCD_PALLETTE_TFT_DC_BIT) + #define LCD_PALLETTE_TFT_DC_N(N) (((N)<< LCD_PALLETTE_TFT_DC_BIT) & LCD_PALLETTE_TFT_DC_MASK) + +/********************************************************************/ + +/* List of panels known to work with the AU1100 LCD controller. + * To add a new panel, enter the same specifications as the + * Generic_TFT one, and MAKE SURE that it doesn't conflicts + * with the controller restrictions. Restrictions are: + * + * STN color panels: max_bpp <= 12 + * STN mono panels: max_bpp <= 4 + * TFT panels: max_bpp <= 16 + * max_xres <= 800 + * max_yres <= 600 + */ +static struct au1100fb_panel known_lcd_panels[] = +{ + /* 800x600x16bpp CRT */ + [0] = { + .name = "CRT_800x600_16", + .xres = 800, + .yres = 600, + .bpp = 16, + .control_base = 0x0004886A | + LCD_CONTROL_DEFAULT_PO | LCD_CONTROL_DEFAULT_SBPPF | + LCD_CONTROL_BPP_16 | LCD_CONTROL_SBB_4, + .clkcontrol_base = 0x00020000, + .horztiming = 0x005aff1f, + .verttiming = 0x16000e57, + }, + /* just the standard LCD */ + [1] = { + .name = "WWPC LCD", + .xres = 240, + .yres = 320, + .bpp = 16, + .control_base = 0x0006806A, + .horztiming = 0x0A1010EF, + .verttiming = 0x0301013F, + .clkcontrol_base = 0x00018001, + }, + /* Sharp 320x240 TFT panel */ + [2] = { + .name = "Sharp_LQ038Q5DR01", + .xres = 320, + .yres = 240, + .bpp = 16, + .control_base = + ( LCD_CONTROL_SBPPF_565 + | LCD_CONTROL_C + | LCD_CONTROL_SM_0 + | LCD_CONTROL_DEFAULT_PO + | LCD_CONTROL_PT + | LCD_CONTROL_PC + | LCD_CONTROL_BPP_16 ), + .horztiming = + ( LCD_HORZTIMING_HN2_N(8) + | LCD_HORZTIMING_HN1_N(60) + | LCD_HORZTIMING_HPW_N(12) + | LCD_HORZTIMING_PPL_N(320) ), + .verttiming = + ( LCD_VERTTIMING_VN2_N(5) + | LCD_VERTTIMING_VN1_N(17) + | LCD_VERTTIMING_VPW_N(1) + | LCD_VERTTIMING_LPP_N(240) ), + .clkcontrol_base = LCD_CLKCONTROL_PCD_N(1), + }, + + /* Hitachi SP14Q005 and possibly others */ + [3] = { + .name = "Hitachi_SP14Qxxx", + .xres = 320, + .yres = 240, + .bpp = 4, + .control_base = + ( LCD_CONTROL_C + | LCD_CONTROL_BPP_4 ), + .horztiming = + ( LCD_HORZTIMING_HN2_N(1) + | LCD_HORZTIMING_HN1_N(1) + | LCD_HORZTIMING_HPW_N(1) + | LCD_HORZTIMING_PPL_N(320) ), + .verttiming = + ( LCD_VERTTIMING_VN2_N(1) + | LCD_VERTTIMING_VN1_N(1) + | LCD_VERTTIMING_VPW_N(1) + | LCD_VERTTIMING_LPP_N(240) ), + .clkcontrol_base = LCD_CLKCONTROL_PCD_N(4), + }, + + /* Generic 640x480 TFT panel */ + [4] = { + .name = "TFT_640x480_16", + .xres = 640, + .yres = 480, + .bpp = 16, + .control_base = 0x004806a | LCD_CONTROL_DEFAULT_PO, + .horztiming = 0x3434d67f, + .verttiming = 0x0e0e39df, + .clkcontrol_base = LCD_CLKCONTROL_PCD_N(1), + }, + + /* Pb1100 LCDB 640x480 PrimeView TFT panel */ + [5] = { + .name = "PrimeView_640x480_16", + .xres = 640, + .yres = 480, + .bpp = 16, + .control_base = 0x0004886a | LCD_CONTROL_DEFAULT_PO, + .horztiming = 0x0e4bfe7f, + .verttiming = 0x210805df, + .clkcontrol_base = 0x00038001, + }, +}; + +/********************************************************************/ + +/* Inline helpers */ + +#define panel_is_dual(panel) (panel->control_base & LCD_CONTROL_DP) +#define panel_is_active(panel)(panel->control_base & LCD_CONTROL_PT) +#define panel_is_color(panel) (panel->control_base & LCD_CONTROL_PC) +#define panel_swap_rgb(panel) (panel->control_base & LCD_CONTROL_CCO) + +#endif /* _AU1100LCD_H */ diff --git a/drivers/video/fbdev/au1200fb.c b/drivers/video/fbdev/au1200fb.c new file mode 100644 index 000000000000..4cfba78a1458 --- /dev/null +++ b/drivers/video/fbdev/au1200fb.c @@ -0,0 +1,1859 @@ +/* + * BRIEF MODULE DESCRIPTION + * Au1200 LCD Driver. + * + * Copyright 2004-2005 AMD + * Author: AMD + * + * Based on: + * linux/drivers/video/skeletonfb.c -- Skeleton for a frame buffer device + * Created 28 Dec 1997 by Geert Uytterhoeven + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ctype.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> + +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1200fb.h> /* platform_data */ +#include "au1200fb.h" + +#define DRIVER_NAME "au1200fb" +#define DRIVER_DESC "LCD controller driver for AU1200 processors" + +#define DEBUG 0 + +#define print_err(f, arg...) printk(KERN_ERR DRIVER_NAME ": " f "\n", ## arg) +#define print_warn(f, arg...) printk(KERN_WARNING DRIVER_NAME ": " f "\n", ## arg) +#define print_info(f, arg...) printk(KERN_INFO DRIVER_NAME ": " f "\n", ## arg) + +#if DEBUG +#define print_dbg(f, arg...) printk(KERN_DEBUG __FILE__ ": " f "\n", ## arg) +#else +#define print_dbg(f, arg...) do {} while (0) +#endif + + +#define AU1200_LCD_FB_IOCTL 0x46FF + +#define AU1200_LCD_SET_SCREEN 1 +#define AU1200_LCD_GET_SCREEN 2 +#define AU1200_LCD_SET_WINDOW 3 +#define AU1200_LCD_GET_WINDOW 4 +#define AU1200_LCD_SET_PANEL 5 +#define AU1200_LCD_GET_PANEL 6 + +#define SCREEN_SIZE (1<< 1) +#define SCREEN_BACKCOLOR (1<< 2) +#define SCREEN_BRIGHTNESS (1<< 3) +#define SCREEN_COLORKEY (1<< 4) +#define SCREEN_MASK (1<< 5) + +struct au1200_lcd_global_regs_t { + unsigned int flags; + unsigned int xsize; + unsigned int ysize; + unsigned int backcolor; + unsigned int brightness; + unsigned int colorkey; + unsigned int mask; + unsigned int panel_choice; + char panel_desc[80]; + +}; + +#define WIN_POSITION (1<< 0) +#define WIN_ALPHA_COLOR (1<< 1) +#define WIN_ALPHA_MODE (1<< 2) +#define WIN_PRIORITY (1<< 3) +#define WIN_CHANNEL (1<< 4) +#define WIN_BUFFER_FORMAT (1<< 5) +#define WIN_COLOR_ORDER (1<< 6) +#define WIN_PIXEL_ORDER (1<< 7) +#define WIN_SIZE (1<< 8) +#define WIN_COLORKEY_MODE (1<< 9) +#define WIN_DOUBLE_BUFFER_MODE (1<< 10) +#define WIN_RAM_ARRAY_MODE (1<< 11) +#define WIN_BUFFER_SCALE (1<< 12) +#define WIN_ENABLE (1<< 13) + +struct au1200_lcd_window_regs_t { + unsigned int flags; + unsigned int xpos; + unsigned int ypos; + unsigned int alpha_color; + unsigned int alpha_mode; + unsigned int priority; + unsigned int channel; + unsigned int buffer_format; + unsigned int color_order; + unsigned int pixel_order; + unsigned int xsize; + unsigned int ysize; + unsigned int colorkey_mode; + unsigned int double_buffer_mode; + unsigned int ram_array_mode; + unsigned int xscale; + unsigned int yscale; + unsigned int enable; +}; + + +struct au1200_lcd_iodata_t { + unsigned int subcmd; + struct au1200_lcd_global_regs_t global; + struct au1200_lcd_window_regs_t window; +}; + +#if defined(__BIG_ENDIAN) +#define LCD_CONTROL_DEFAULT_PO LCD_CONTROL_PO_11 +#else +#define LCD_CONTROL_DEFAULT_PO LCD_CONTROL_PO_00 +#endif +#define LCD_CONTROL_DEFAULT_SBPPF LCD_CONTROL_SBPPF_565 + +/* Private, per-framebuffer management information (independent of the panel itself) */ +struct au1200fb_device { + struct fb_info *fb_info; /* FB driver info record */ + struct au1200fb_platdata *pd; + + int plane; + unsigned char* fb_mem; /* FrameBuffer memory map */ + unsigned int fb_len; + dma_addr_t fb_phys; +}; + +/********************************************************************/ + +/* LCD controller restrictions */ +#define AU1200_LCD_MAX_XRES 1280 +#define AU1200_LCD_MAX_YRES 1024 +#define AU1200_LCD_MAX_BPP 32 +#define AU1200_LCD_MAX_CLK 96000000 /* fixme: this needs to go away ? */ +#define AU1200_LCD_NBR_PALETTE_ENTRIES 256 + +/* Default number of visible screen buffer to allocate */ +#define AU1200FB_NBR_VIDEO_BUFFERS 1 + +/* Default maximum number of fb devices to create */ +#define MAX_DEVICE_COUNT 4 + +/* Default window configuration entry to use (see windows[]) */ +#define DEFAULT_WINDOW_INDEX 2 + +/********************************************************************/ + +static struct fb_info *_au1200fb_infos[MAX_DEVICE_COUNT]; +static struct au1200_lcd *lcd = (struct au1200_lcd *) AU1200_LCD_ADDR; +static int device_count = MAX_DEVICE_COUNT; +static int window_index = DEFAULT_WINDOW_INDEX; /* default is zero */ +static int panel_index = 2; /* default is zero */ +static struct window_settings *win; +static struct panel_settings *panel; +static int noblanking = 1; +static int nohwcursor = 0; + +struct window_settings { + unsigned char name[64]; + uint32 mode_backcolor; + uint32 mode_colorkey; + uint32 mode_colorkeymsk; + struct { + int xres; + int yres; + int xpos; + int ypos; + uint32 mode_winctrl1; /* winctrl1[FRM,CCO,PO,PIPE] */ + uint32 mode_winenable; + } w[4]; +}; + +#if defined(__BIG_ENDIAN) +#define LCD_WINCTRL1_PO_16BPP LCD_WINCTRL1_PO_00 +#else +#define LCD_WINCTRL1_PO_16BPP LCD_WINCTRL1_PO_01 +#endif + +/* + * Default window configurations + */ +static struct window_settings windows[] = { + { /* Index 0 */ + "0-FS gfx, 1-video, 2-ovly gfx, 3-ovly gfx", + /* mode_backcolor */ 0x006600ff, + /* mode_colorkey,msk*/ 0, 0, + { + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP, + /* mode_winenable*/ LCD_WINENABLE_WEN0, + }, + { + /* xres, yres, xpos, ypos */ 100, 100, 100, 100, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP | + LCD_WINCTRL1_PIPE, + /* mode_winenable*/ LCD_WINENABLE_WEN1, + }, + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP, + /* mode_winenable*/ 0, + }, + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP | + LCD_WINCTRL1_PIPE, + /* mode_winenable*/ 0, + }, + }, + }, + + { /* Index 1 */ + "0-FS gfx, 1-video, 2-ovly gfx, 3-ovly gfx", + /* mode_backcolor */ 0x006600ff, + /* mode_colorkey,msk*/ 0, 0, + { + { + /* xres, yres, xpos, ypos */ 320, 240, 5, 5, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_24BPP | + LCD_WINCTRL1_PO_00, + /* mode_winenable*/ LCD_WINENABLE_WEN0, + }, + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 + | LCD_WINCTRL1_PO_16BPP, + /* mode_winenable*/ 0, + }, + { + /* xres, yres, xpos, ypos */ 100, 100, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP | + LCD_WINCTRL1_PIPE, + /* mode_winenable*/ 0/*LCD_WINENABLE_WEN2*/, + }, + { + /* xres, yres, xpos, ypos */ 200, 25, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP | + LCD_WINCTRL1_PIPE, + /* mode_winenable*/ 0, + }, + }, + }, + { /* Index 2 */ + "0-FS gfx, 1-video, 2-ovly gfx, 3-ovly gfx", + /* mode_backcolor */ 0x006600ff, + /* mode_colorkey,msk*/ 0, 0, + { + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP, + /* mode_winenable*/ LCD_WINENABLE_WEN0, + }, + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP, + /* mode_winenable*/ 0, + }, + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_32BPP | + LCD_WINCTRL1_PO_00|LCD_WINCTRL1_PIPE, + /* mode_winenable*/ 0/*LCD_WINENABLE_WEN2*/, + }, + { + /* xres, yres, xpos, ypos */ 0, 0, 0, 0, + /* mode_winctrl1 */ LCD_WINCTRL1_FRM_16BPP565 | + LCD_WINCTRL1_PO_16BPP | + LCD_WINCTRL1_PIPE, + /* mode_winenable*/ 0, + }, + }, + }, + /* Need VGA 640 @ 24bpp, @ 32bpp */ + /* Need VGA 800 @ 24bpp, @ 32bpp */ + /* Need VGA 1024 @ 24bpp, @ 32bpp */ +}; + +/* + * Controller configurations for various panels. + */ + +struct panel_settings +{ + const char name[25]; /* Full name <vendor>_<model> */ + + struct fb_monspecs monspecs; /* FB monitor specs */ + + /* panel timings */ + uint32 mode_screen; + uint32 mode_horztiming; + uint32 mode_verttiming; + uint32 mode_clkcontrol; + uint32 mode_pwmdiv; + uint32 mode_pwmhi; + uint32 mode_outmask; + uint32 mode_fifoctrl; + uint32 mode_toyclksrc; + uint32 mode_backlight; + uint32 mode_auxpll; +#define Xres min_xres +#define Yres min_yres + u32 min_xres; /* Minimum horizontal resolution */ + u32 max_xres; /* Maximum horizontal resolution */ + u32 min_yres; /* Minimum vertical resolution */ + u32 max_yres; /* Maximum vertical resolution */ +}; + +/********************************************************************/ +/* fixme: Maybe a modedb for the CRT ? otherwise panels should be as-is */ + +/* List of panels known to work with the AU1200 LCD controller. + * To add a new panel, enter the same specifications as the + * Generic_TFT one, and MAKE SURE that it doesn't conflicts + * with the controller restrictions. Restrictions are: + * + * STN color panels: max_bpp <= 12 + * STN mono panels: max_bpp <= 4 + * TFT panels: max_bpp <= 16 + * max_xres <= 800 + * max_yres <= 600 + */ +static struct panel_settings known_lcd_panels[] = +{ + [0] = { /* QVGA 320x240 H:33.3kHz V:110Hz */ + .name = "QVGA_320x240", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = LCD_SCREEN_SX_N(320) | + LCD_SCREEN_SY_N(240), + .mode_horztiming = 0x00c4623b, + .mode_verttiming = 0x00502814, + .mode_clkcontrol = 0x00020002, /* /4=24Mhz */ + .mode_pwmdiv = 0x00000000, + .mode_pwmhi = 0x00000000, + .mode_outmask = 0x00FFFFFF, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 8, /* 96MHz AUXPLL */ + 320, 320, + 240, 240, + }, + + [1] = { /* VGA 640x480 H:30.3kHz V:58Hz */ + .name = "VGA_640x480", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = 0x13f9df80, + .mode_horztiming = 0x003c5859, + .mode_verttiming = 0x00741201, + .mode_clkcontrol = 0x00020001, /* /4=24Mhz */ + .mode_pwmdiv = 0x00000000, + .mode_pwmhi = 0x00000000, + .mode_outmask = 0x00FFFFFF, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 8, /* 96MHz AUXPLL */ + 640, 480, + 640, 480, + }, + + [2] = { /* SVGA 800x600 H:46.1kHz V:69Hz */ + .name = "SVGA_800x600", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = 0x18fa5780, + .mode_horztiming = 0x00dc7e77, + .mode_verttiming = 0x00584805, + .mode_clkcontrol = 0x00020000, /* /2=48Mhz */ + .mode_pwmdiv = 0x00000000, + .mode_pwmhi = 0x00000000, + .mode_outmask = 0x00FFFFFF, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 8, /* 96MHz AUXPLL */ + 800, 800, + 600, 600, + }, + + [3] = { /* XVGA 1024x768 H:56.2kHz V:70Hz */ + .name = "XVGA_1024x768", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = 0x1ffaff80, + .mode_horztiming = 0x007d0e57, + .mode_verttiming = 0x00740a01, + .mode_clkcontrol = 0x000A0000, /* /1 */ + .mode_pwmdiv = 0x00000000, + .mode_pwmhi = 0x00000000, + .mode_outmask = 0x00FFFFFF, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 6, /* 72MHz AUXPLL */ + 1024, 1024, + 768, 768, + }, + + [4] = { /* XVGA XVGA 1280x1024 H:68.5kHz V:65Hz */ + .name = "XVGA_1280x1024", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = 0x27fbff80, + .mode_horztiming = 0x00cdb2c7, + .mode_verttiming = 0x00600002, + .mode_clkcontrol = 0x000A0000, /* /1 */ + .mode_pwmdiv = 0x00000000, + .mode_pwmhi = 0x00000000, + .mode_outmask = 0x00FFFFFF, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 10, /* 120MHz AUXPLL */ + 1280, 1280, + 1024, 1024, + }, + + [5] = { /* Samsung 1024x768 TFT */ + .name = "Samsung_1024x768_TFT", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = 0x1ffaff80, + .mode_horztiming = 0x018cc677, + .mode_verttiming = 0x00241217, + .mode_clkcontrol = 0x00000000, /* SCB 0x1 /4=24Mhz */ + .mode_pwmdiv = 0x8000063f, /* SCB 0x0 */ + .mode_pwmhi = 0x03400000, /* SCB 0x0 */ + .mode_outmask = 0x00FFFFFF, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 8, /* 96MHz AUXPLL */ + 1024, 1024, + 768, 768, + }, + + [6] = { /* Toshiba 640x480 TFT */ + .name = "Toshiba_640x480_TFT", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = LCD_SCREEN_SX_N(640) | + LCD_SCREEN_SY_N(480), + .mode_horztiming = LCD_HORZTIMING_HPW_N(96) | + LCD_HORZTIMING_HND1_N(13) | LCD_HORZTIMING_HND2_N(51), + .mode_verttiming = LCD_VERTTIMING_VPW_N(2) | + LCD_VERTTIMING_VND1_N(11) | LCD_VERTTIMING_VND2_N(32), + .mode_clkcontrol = 0x00000000, /* /4=24Mhz */ + .mode_pwmdiv = 0x8000063f, + .mode_pwmhi = 0x03400000, + .mode_outmask = 0x00fcfcfc, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 8, /* 96MHz AUXPLL */ + 640, 480, + 640, 480, + }, + + [7] = { /* Sharp 320x240 TFT */ + .name = "Sharp_320x240_TFT", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 12500, + .hfmax = 20000, + .vfmin = 38, + .vfmax = 81, + .dclkmin = 4500000, + .dclkmax = 6800000, + .input = FB_DISP_RGB, + }, + .mode_screen = LCD_SCREEN_SX_N(320) | + LCD_SCREEN_SY_N(240), + .mode_horztiming = LCD_HORZTIMING_HPW_N(60) | + LCD_HORZTIMING_HND1_N(13) | LCD_HORZTIMING_HND2_N(2), + .mode_verttiming = LCD_VERTTIMING_VPW_N(2) | + LCD_VERTTIMING_VND1_N(2) | LCD_VERTTIMING_VND2_N(5), + .mode_clkcontrol = LCD_CLKCONTROL_PCD_N(7), /*16=6Mhz*/ + .mode_pwmdiv = 0x8000063f, + .mode_pwmhi = 0x03400000, + .mode_outmask = 0x00fcfcfc, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 8, /* 96MHz AUXPLL */ + 320, 320, + 240, 240, + }, + + [8] = { /* Toppoly TD070WGCB2 7" 856x480 TFT */ + .name = "Toppoly_TD070WGCB2", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = LCD_SCREEN_SX_N(856) | + LCD_SCREEN_SY_N(480), + .mode_horztiming = LCD_HORZTIMING_HND2_N(43) | + LCD_HORZTIMING_HND1_N(43) | LCD_HORZTIMING_HPW_N(114), + .mode_verttiming = LCD_VERTTIMING_VND2_N(20) | + LCD_VERTTIMING_VND1_N(21) | LCD_VERTTIMING_VPW_N(4), + .mode_clkcontrol = 0x00020001, /* /4=24Mhz */ + .mode_pwmdiv = 0x8000063f, + .mode_pwmhi = 0x03400000, + .mode_outmask = 0x00fcfcfc, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = 8, /* 96MHz AUXPLL */ + 856, 856, + 480, 480, + }, + [9] = { + .name = "DB1300_800x480", + .monspecs = { + .modedb = NULL, + .modedb_len = 0, + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 60, + .vfmax = 60, + .dclkmin = 6000000, + .dclkmax = 28000000, + .input = FB_DISP_RGB, + }, + .mode_screen = LCD_SCREEN_SX_N(800) | + LCD_SCREEN_SY_N(480), + .mode_horztiming = LCD_HORZTIMING_HPW_N(5) | + LCD_HORZTIMING_HND1_N(16) | + LCD_HORZTIMING_HND2_N(8), + .mode_verttiming = LCD_VERTTIMING_VPW_N(4) | + LCD_VERTTIMING_VND1_N(8) | + LCD_VERTTIMING_VND2_N(5), + .mode_clkcontrol = LCD_CLKCONTROL_PCD_N(1) | + LCD_CLKCONTROL_IV | + LCD_CLKCONTROL_IH, + .mode_pwmdiv = 0x00000000, + .mode_pwmhi = 0x00000000, + .mode_outmask = 0x00FFFFFF, + .mode_fifoctrl = 0x2f2f2f2f, + .mode_toyclksrc = 0x00000004, /* AUXPLL directly */ + .mode_backlight = 0x00000000, + .mode_auxpll = (48/12) * 2, + 800, 800, + 480, 480, + }, +}; + +#define NUM_PANELS (ARRAY_SIZE(known_lcd_panels)) + +/********************************************************************/ + +static int winbpp (unsigned int winctrl1) +{ + int bits = 0; + + /* how many bits are needed for each pixel format */ + switch (winctrl1 & LCD_WINCTRL1_FRM) { + case LCD_WINCTRL1_FRM_1BPP: + bits = 1; + break; + case LCD_WINCTRL1_FRM_2BPP: + bits = 2; + break; + case LCD_WINCTRL1_FRM_4BPP: + bits = 4; + break; + case LCD_WINCTRL1_FRM_8BPP: + bits = 8; + break; + case LCD_WINCTRL1_FRM_12BPP: + case LCD_WINCTRL1_FRM_16BPP655: + case LCD_WINCTRL1_FRM_16BPP565: + case LCD_WINCTRL1_FRM_16BPP556: + case LCD_WINCTRL1_FRM_16BPPI1555: + case LCD_WINCTRL1_FRM_16BPPI5551: + case LCD_WINCTRL1_FRM_16BPPA1555: + case LCD_WINCTRL1_FRM_16BPPA5551: + bits = 16; + break; + case LCD_WINCTRL1_FRM_24BPP: + case LCD_WINCTRL1_FRM_32BPP: + bits = 32; + break; + } + + return bits; +} + +static int fbinfo2index (struct fb_info *fb_info) +{ + int i; + + for (i = 0; i < device_count; ++i) { + if (fb_info == _au1200fb_infos[i]) + return i; + } + printk("au1200fb: ERROR: fbinfo2index failed!\n"); + return -1; +} + +static int au1200_setlocation (struct au1200fb_device *fbdev, int plane, + int xpos, int ypos) +{ + uint32 winctrl0, winctrl1, winenable, fb_offset = 0; + int xsz, ysz; + + /* FIX!!! NOT CHECKING FOR COMPLETE OFFSCREEN YET */ + + winctrl0 = lcd->window[plane].winctrl0; + winctrl1 = lcd->window[plane].winctrl1; + winctrl0 &= (LCD_WINCTRL0_A | LCD_WINCTRL0_AEN); + winctrl1 &= ~(LCD_WINCTRL1_SZX | LCD_WINCTRL1_SZY); + + /* Check for off-screen adjustments */ + xsz = win->w[plane].xres; + ysz = win->w[plane].yres; + if ((xpos + win->w[plane].xres) > panel->Xres) { + /* Off-screen to the right */ + xsz = panel->Xres - xpos; /* off by 1 ??? */ + /*printk("off screen right\n");*/ + } + + if ((ypos + win->w[plane].yres) > panel->Yres) { + /* Off-screen to the bottom */ + ysz = panel->Yres - ypos; /* off by 1 ??? */ + /*printk("off screen bottom\n");*/ + } + + if (xpos < 0) { + /* Off-screen to the left */ + xsz = win->w[plane].xres + xpos; + fb_offset += (((0 - xpos) * winbpp(lcd->window[plane].winctrl1))/8); + xpos = 0; + /*printk("off screen left\n");*/ + } + + if (ypos < 0) { + /* Off-screen to the top */ + ysz = win->w[plane].yres + ypos; + /* fixme: fb_offset += ((0-ypos)*fb_pars[plane].line_length); */ + ypos = 0; + /*printk("off screen top\n");*/ + } + + /* record settings */ + win->w[plane].xpos = xpos; + win->w[plane].ypos = ypos; + + xsz -= 1; + ysz -= 1; + winctrl0 |= (xpos << 21); + winctrl0 |= (ypos << 10); + winctrl1 |= (xsz << 11); + winctrl1 |= (ysz << 0); + + /* Disable the window while making changes, then restore WINEN */ + winenable = lcd->winenable & (1 << plane); + au_sync(); + lcd->winenable &= ~(1 << plane); + lcd->window[plane].winctrl0 = winctrl0; + lcd->window[plane].winctrl1 = winctrl1; + lcd->window[plane].winbuf0 = + lcd->window[plane].winbuf1 = fbdev->fb_phys; + lcd->window[plane].winbufctrl = 0; /* select winbuf0 */ + lcd->winenable |= winenable; + au_sync(); + + return 0; +} + +static void au1200_setpanel(struct panel_settings *newpanel, + struct au1200fb_platdata *pd) +{ + /* + * Perform global setup/init of LCD controller + */ + uint32 winenable; + + /* Make sure all windows disabled */ + winenable = lcd->winenable; + lcd->winenable = 0; + au_sync(); + /* + * Ensure everything is disabled before reconfiguring + */ + if (lcd->screen & LCD_SCREEN_SEN) { + /* Wait for vertical sync period */ + lcd->intstatus = LCD_INT_SS; + while ((lcd->intstatus & LCD_INT_SS) == 0) { + au_sync(); + } + + lcd->screen &= ~LCD_SCREEN_SEN; /*disable the controller*/ + + do { + lcd->intstatus = lcd->intstatus; /*clear interrupts*/ + au_sync(); + /*wait for controller to shut down*/ + } while ((lcd->intstatus & LCD_INT_SD) == 0); + + /* Call shutdown of current panel (if up) */ + /* this must occur last, because if an external clock is driving + the controller, the clock cannot be turned off before first + shutting down the controller. + */ + if (pd->panel_shutdown) + pd->panel_shutdown(); + } + + /* Newpanel == NULL indicates a shutdown operation only */ + if (newpanel == NULL) + return; + + panel = newpanel; + + printk("Panel(%s), %dx%d\n", panel->name, panel->Xres, panel->Yres); + + /* + * Setup clocking if internal LCD clock source (assumes sys_auxpll valid) + */ + if (!(panel->mode_clkcontrol & LCD_CLKCONTROL_EXT)) + { + uint32 sys_clksrc; + au_writel(panel->mode_auxpll, SYS_AUXPLL); + sys_clksrc = au_readl(SYS_CLKSRC) & ~0x0000001f; + sys_clksrc |= panel->mode_toyclksrc; + au_writel(sys_clksrc, SYS_CLKSRC); + } + + /* + * Configure panel timings + */ + lcd->screen = panel->mode_screen; + lcd->horztiming = panel->mode_horztiming; + lcd->verttiming = panel->mode_verttiming; + lcd->clkcontrol = panel->mode_clkcontrol; + lcd->pwmdiv = panel->mode_pwmdiv; + lcd->pwmhi = panel->mode_pwmhi; + lcd->outmask = panel->mode_outmask; + lcd->fifoctrl = panel->mode_fifoctrl; + au_sync(); + + /* fixme: Check window settings to make sure still valid + * for new geometry */ +#if 0 + au1200_setlocation(fbdev, 0, win->w[0].xpos, win->w[0].ypos); + au1200_setlocation(fbdev, 1, win->w[1].xpos, win->w[1].ypos); + au1200_setlocation(fbdev, 2, win->w[2].xpos, win->w[2].ypos); + au1200_setlocation(fbdev, 3, win->w[3].xpos, win->w[3].ypos); +#endif + lcd->winenable = winenable; + + /* + * Re-enable screen now that it is configured + */ + lcd->screen |= LCD_SCREEN_SEN; + au_sync(); + + /* Call init of panel */ + if (pd->panel_init) + pd->panel_init(); + + /* FIX!!!! not appropriate on panel change!!! Global setup/init */ + lcd->intenable = 0; + lcd->intstatus = ~0; + lcd->backcolor = win->mode_backcolor; + + /* Setup Color Key - FIX!!! */ + lcd->colorkey = win->mode_colorkey; + lcd->colorkeymsk = win->mode_colorkeymsk; + + /* Setup HWCursor - FIX!!! Need to support this eventually */ + lcd->hwc.cursorctrl = 0; + lcd->hwc.cursorpos = 0; + lcd->hwc.cursorcolor0 = 0; + lcd->hwc.cursorcolor1 = 0; + lcd->hwc.cursorcolor2 = 0; + lcd->hwc.cursorcolor3 = 0; + + +#if 0 +#define D(X) printk("%25s: %08X\n", #X, X) + D(lcd->screen); + D(lcd->horztiming); + D(lcd->verttiming); + D(lcd->clkcontrol); + D(lcd->pwmdiv); + D(lcd->pwmhi); + D(lcd->outmask); + D(lcd->fifoctrl); + D(lcd->window[0].winctrl0); + D(lcd->window[0].winctrl1); + D(lcd->window[0].winctrl2); + D(lcd->window[0].winbuf0); + D(lcd->window[0].winbuf1); + D(lcd->window[0].winbufctrl); + D(lcd->window[1].winctrl0); + D(lcd->window[1].winctrl1); + D(lcd->window[1].winctrl2); + D(lcd->window[1].winbuf0); + D(lcd->window[1].winbuf1); + D(lcd->window[1].winbufctrl); + D(lcd->window[2].winctrl0); + D(lcd->window[2].winctrl1); + D(lcd->window[2].winctrl2); + D(lcd->window[2].winbuf0); + D(lcd->window[2].winbuf1); + D(lcd->window[2].winbufctrl); + D(lcd->window[3].winctrl0); + D(lcd->window[3].winctrl1); + D(lcd->window[3].winctrl2); + D(lcd->window[3].winbuf0); + D(lcd->window[3].winbuf1); + D(lcd->window[3].winbufctrl); + D(lcd->winenable); + D(lcd->intenable); + D(lcd->intstatus); + D(lcd->backcolor); + D(lcd->winenable); + D(lcd->colorkey); + D(lcd->colorkeymsk); + D(lcd->hwc.cursorctrl); + D(lcd->hwc.cursorpos); + D(lcd->hwc.cursorcolor0); + D(lcd->hwc.cursorcolor1); + D(lcd->hwc.cursorcolor2); + D(lcd->hwc.cursorcolor3); +#endif +} + +static void au1200_setmode(struct au1200fb_device *fbdev) +{ + int plane = fbdev->plane; + /* Window/plane setup */ + lcd->window[plane].winctrl1 = ( 0 + | LCD_WINCTRL1_PRI_N(plane) + | win->w[plane].mode_winctrl1 /* FRM,CCO,PO,PIPE */ + ) ; + + au1200_setlocation(fbdev, plane, win->w[plane].xpos, win->w[plane].ypos); + + lcd->window[plane].winctrl2 = ( 0 + | LCD_WINCTRL2_CKMODE_00 + | LCD_WINCTRL2_DBM + | LCD_WINCTRL2_BX_N(fbdev->fb_info->fix.line_length) + | LCD_WINCTRL2_SCX_1 + | LCD_WINCTRL2_SCY_1 + ) ; + lcd->winenable |= win->w[plane].mode_winenable; + au_sync(); +} + + +/* Inline helpers */ + +/*#define panel_is_dual(panel) ((panel->mode_screen & LCD_SCREEN_PT) == LCD_SCREEN_PT_010)*/ +/*#define panel_is_active(panel)((panel->mode_screen & LCD_SCREEN_PT) == LCD_SCREEN_PT_010)*/ + +#define panel_is_color(panel) ((panel->mode_screen & LCD_SCREEN_PT) <= LCD_SCREEN_PT_CDSTN) + +/* Bitfields format supported by the controller. */ +static struct fb_bitfield rgb_bitfields[][4] = { + /* Red, Green, Blue, Transp */ + [LCD_WINCTRL1_FRM_16BPP655 >> 25] = + { { 10, 6, 0 }, { 5, 5, 0 }, { 0, 5, 0 }, { 0, 0, 0 } }, + + [LCD_WINCTRL1_FRM_16BPP565 >> 25] = + { { 11, 5, 0 }, { 5, 6, 0 }, { 0, 5, 0 }, { 0, 0, 0 } }, + + [LCD_WINCTRL1_FRM_16BPP556 >> 25] = + { { 11, 5, 0 }, { 6, 5, 0 }, { 0, 6, 0 }, { 0, 0, 0 } }, + + [LCD_WINCTRL1_FRM_16BPPI1555 >> 25] = + { { 10, 5, 0 }, { 5, 5, 0 }, { 0, 5, 0 }, { 0, 0, 0 } }, + + [LCD_WINCTRL1_FRM_16BPPI5551 >> 25] = + { { 11, 5, 0 }, { 6, 5, 0 }, { 1, 5, 0 }, { 0, 0, 0 } }, + + [LCD_WINCTRL1_FRM_16BPPA1555 >> 25] = + { { 10, 5, 0 }, { 5, 5, 0 }, { 0, 5, 0 }, { 15, 1, 0 } }, + + [LCD_WINCTRL1_FRM_16BPPA5551 >> 25] = + { { 11, 5, 0 }, { 6, 5, 0 }, { 1, 5, 0 }, { 0, 1, 0 } }, + + [LCD_WINCTRL1_FRM_24BPP >> 25] = + { { 16, 8, 0 }, { 8, 8, 0 }, { 0, 8, 0 }, { 0, 0, 0 } }, + + [LCD_WINCTRL1_FRM_32BPP >> 25] = + { { 16, 8, 0 }, { 8, 8, 0 }, { 0, 8, 0 }, { 24, 0, 0 } }, +}; + +/*-------------------------------------------------------------------------*/ + +/* Helpers */ + +static void au1200fb_update_fbinfo(struct fb_info *fbi) +{ + /* FIX!!!! This also needs to take the window pixel format into account!!! */ + + /* Update var-dependent FB info */ + if (panel_is_color(panel)) { + if (fbi->var.bits_per_pixel <= 8) { + /* palettized */ + fbi->fix.visual = FB_VISUAL_PSEUDOCOLOR; + fbi->fix.line_length = fbi->var.xres_virtual / + (8/fbi->var.bits_per_pixel); + } else { + /* non-palettized */ + fbi->fix.visual = FB_VISUAL_TRUECOLOR; + fbi->fix.line_length = fbi->var.xres_virtual * (fbi->var.bits_per_pixel / 8); + } + } else { + /* mono FIX!!! mono 8 and 4 bits */ + fbi->fix.visual = FB_VISUAL_MONO10; + fbi->fix.line_length = fbi->var.xres_virtual / 8; + } + + fbi->screen_size = fbi->fix.line_length * fbi->var.yres_virtual; + print_dbg("line length: %d\n", fbi->fix.line_length); + print_dbg("bits_per_pixel: %d\n", fbi->var.bits_per_pixel); +} + +/*-------------------------------------------------------------------------*/ + +/* AU1200 framebuffer driver */ + +/* fb_check_var + * Validate var settings with hardware restrictions and modify it if necessary + */ +static int au1200fb_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct au1200fb_device *fbdev = fbi->par; + u32 pixclock; + int screen_size, plane; + + plane = fbdev->plane; + + /* Make sure that the mode respect all LCD controller and + * panel restrictions. */ + var->xres = win->w[plane].xres; + var->yres = win->w[plane].yres; + + /* No need for virtual resolution support */ + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + var->bits_per_pixel = winbpp(win->w[plane].mode_winctrl1); + + screen_size = var->xres_virtual * var->yres_virtual; + if (var->bits_per_pixel > 8) screen_size *= (var->bits_per_pixel / 8); + else screen_size /= (8/var->bits_per_pixel); + + if (fbdev->fb_len < screen_size) + return -EINVAL; /* Virtual screen is to big, abort */ + + /* FIX!!!! what are the implicaitons of ignoring this for windows ??? */ + /* The max LCD clock is fixed to 48MHz (value of AUX_CLK). The pixel + * clock can only be obtain by dividing this value by an even integer. + * Fallback to a slower pixel clock if necessary. */ + pixclock = max((u32)(PICOS2KHZ(var->pixclock) * 1000), fbi->monspecs.dclkmin); + pixclock = min3(pixclock, fbi->monspecs.dclkmax, (u32)AU1200_LCD_MAX_CLK/2); + + if (AU1200_LCD_MAX_CLK % pixclock) { + int diff = AU1200_LCD_MAX_CLK % pixclock; + pixclock -= diff; + } + + var->pixclock = KHZ2PICOS(pixclock/1000); +#if 0 + if (!panel_is_active(panel)) { + int pcd = AU1200_LCD_MAX_CLK / (pixclock * 2) - 1; + + if (!panel_is_color(panel) + && (panel->control_base & LCD_CONTROL_MPI) && (pcd < 3)) { + /* STN 8bit mono panel support is up to 6MHz pixclock */ + var->pixclock = KHZ2PICOS(6000); + } else if (!pcd) { + /* Other STN panel support is up to 12MHz */ + var->pixclock = KHZ2PICOS(12000); + } + } +#endif + /* Set bitfield accordingly */ + switch (var->bits_per_pixel) { + case 16: + { + /* 16bpp True color. + * These must be set to MATCH WINCTRL[FORM] */ + int idx; + idx = (win->w[0].mode_winctrl1 & LCD_WINCTRL1_FRM) >> 25; + var->red = rgb_bitfields[idx][0]; + var->green = rgb_bitfields[idx][1]; + var->blue = rgb_bitfields[idx][2]; + var->transp = rgb_bitfields[idx][3]; + break; + } + + case 32: + { + /* 32bpp True color. + * These must be set to MATCH WINCTRL[FORM] */ + int idx; + idx = (win->w[0].mode_winctrl1 & LCD_WINCTRL1_FRM) >> 25; + var->red = rgb_bitfields[idx][0]; + var->green = rgb_bitfields[idx][1]; + var->blue = rgb_bitfields[idx][2]; + var->transp = rgb_bitfields[idx][3]; + break; + } + default: + print_dbg("Unsupported depth %dbpp", var->bits_per_pixel); + return -EINVAL; + } + + return 0; +} + +/* fb_set_par + * Set hardware with var settings. This will enable the controller with a + * specific mode, normally validated with the fb_check_var method + */ +static int au1200fb_fb_set_par(struct fb_info *fbi) +{ + struct au1200fb_device *fbdev = fbi->par; + + au1200fb_update_fbinfo(fbi); + au1200_setmode(fbdev); + + return 0; +} + +/* fb_setcolreg + * Set color in LCD palette. + */ +static int au1200fb_fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *fbi) +{ + volatile u32 *palette = lcd->palette; + u32 value; + + if (regno > (AU1200_LCD_NBR_PALETTE_ENTRIES - 1)) + return -EINVAL; + + if (fbi->var.grayscale) { + /* Convert color to grayscale */ + red = green = blue = + (19595 * red + 38470 * green + 7471 * blue) >> 16; + } + + if (fbi->fix.visual == FB_VISUAL_TRUECOLOR) { + /* Place color in the pseudopalette */ + if (regno > 16) + return -EINVAL; + + palette = (u32*) fbi->pseudo_palette; + + red >>= (16 - fbi->var.red.length); + green >>= (16 - fbi->var.green.length); + blue >>= (16 - fbi->var.blue.length); + + value = (red << fbi->var.red.offset) | + (green << fbi->var.green.offset)| + (blue << fbi->var.blue.offset); + value &= 0xFFFF; + + } else if (1 /*FIX!!! panel_is_active(fbdev->panel)*/) { + /* COLOR TFT PALLETTIZED (use RGB 565) */ + value = (red & 0xF800)|((green >> 5) & + 0x07E0)|((blue >> 11) & 0x001F); + value &= 0xFFFF; + + } else if (0 /*panel_is_color(fbdev->panel)*/) { + /* COLOR STN MODE */ + value = 0x1234; + value &= 0xFFF; + } else { + /* MONOCHROME MODE */ + value = (green >> 12) & 0x000F; + value &= 0xF; + } + + palette[regno] = value; + + return 0; +} + +/* fb_blank + * Blank the screen. Depending on the mode, the screen will be + * activated with the backlight color, or desactivated + */ +static int au1200fb_fb_blank(int blank_mode, struct fb_info *fbi) +{ + struct au1200fb_device *fbdev = fbi->par; + + /* Short-circuit screen blanking */ + if (noblanking) + return 0; + + switch (blank_mode) { + + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + /* printk("turn on panel\n"); */ + au1200_setpanel(panel, fbdev->pd); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + /* printk("turn off panel\n"); */ + au1200_setpanel(NULL, fbdev->pd); + break; + default: + break; + + } + + /* FB_BLANK_NORMAL is a soft blank */ + return (blank_mode == FB_BLANK_NORMAL) ? -EINVAL : 0; +} + +/* fb_mmap + * Map video memory in user space. We don't use the generic fb_mmap + * method mainly to allow the use of the TLB streaming flag (CCA=6) + */ +static int au1200fb_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct au1200fb_device *fbdev = info->par; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + pgprot_val(vma->vm_page_prot) |= _CACHE_MASK; /* CCA=7 */ + + return vm_iomap_memory(vma, fbdev->fb_phys, fbdev->fb_len); +} + +static void set_global(u_int cmd, struct au1200_lcd_global_regs_t *pdata) +{ + + unsigned int hi1, divider; + + /* SCREEN_SIZE: user cannot reset size, must switch panel choice */ + + if (pdata->flags & SCREEN_BACKCOLOR) + lcd->backcolor = pdata->backcolor; + + if (pdata->flags & SCREEN_BRIGHTNESS) { + + // limit brightness pwm duty to >= 30/1600 + if (pdata->brightness < 30) { + pdata->brightness = 30; + } + divider = (lcd->pwmdiv & 0x3FFFF) + 1; + hi1 = (lcd->pwmhi >> 16) + 1; + hi1 = (((pdata->brightness & 0xFF)+1) * divider >> 8); + lcd->pwmhi &= 0xFFFF; + lcd->pwmhi |= (hi1 << 16); + } + + if (pdata->flags & SCREEN_COLORKEY) + lcd->colorkey = pdata->colorkey; + + if (pdata->flags & SCREEN_MASK) + lcd->colorkeymsk = pdata->mask; + au_sync(); +} + +static void get_global(u_int cmd, struct au1200_lcd_global_regs_t *pdata) +{ + unsigned int hi1, divider; + + pdata->xsize = ((lcd->screen & LCD_SCREEN_SX) >> 19) + 1; + pdata->ysize = ((lcd->screen & LCD_SCREEN_SY) >> 8) + 1; + + pdata->backcolor = lcd->backcolor; + pdata->colorkey = lcd->colorkey; + pdata->mask = lcd->colorkeymsk; + + // brightness + hi1 = (lcd->pwmhi >> 16) + 1; + divider = (lcd->pwmdiv & 0x3FFFF) + 1; + pdata->brightness = ((hi1 << 8) / divider) - 1; + au_sync(); +} + +static void set_window(unsigned int plane, + struct au1200_lcd_window_regs_t *pdata) +{ + unsigned int val, bpp; + + /* Window control register 0 */ + if (pdata->flags & WIN_POSITION) { + val = lcd->window[plane].winctrl0 & ~(LCD_WINCTRL0_OX | + LCD_WINCTRL0_OY); + val |= ((pdata->xpos << 21) & LCD_WINCTRL0_OX); + val |= ((pdata->ypos << 10) & LCD_WINCTRL0_OY); + lcd->window[plane].winctrl0 = val; + } + if (pdata->flags & WIN_ALPHA_COLOR) { + val = lcd->window[plane].winctrl0 & ~(LCD_WINCTRL0_A); + val |= ((pdata->alpha_color << 2) & LCD_WINCTRL0_A); + lcd->window[plane].winctrl0 = val; + } + if (pdata->flags & WIN_ALPHA_MODE) { + val = lcd->window[plane].winctrl0 & ~(LCD_WINCTRL0_AEN); + val |= ((pdata->alpha_mode << 1) & LCD_WINCTRL0_AEN); + lcd->window[plane].winctrl0 = val; + } + + /* Window control register 1 */ + if (pdata->flags & WIN_PRIORITY) { + val = lcd->window[plane].winctrl1 & ~(LCD_WINCTRL1_PRI); + val |= ((pdata->priority << 30) & LCD_WINCTRL1_PRI); + lcd->window[plane].winctrl1 = val; + } + if (pdata->flags & WIN_CHANNEL) { + val = lcd->window[plane].winctrl1 & ~(LCD_WINCTRL1_PIPE); + val |= ((pdata->channel << 29) & LCD_WINCTRL1_PIPE); + lcd->window[plane].winctrl1 = val; + } + if (pdata->flags & WIN_BUFFER_FORMAT) { + val = lcd->window[plane].winctrl1 & ~(LCD_WINCTRL1_FRM); + val |= ((pdata->buffer_format << 25) & LCD_WINCTRL1_FRM); + lcd->window[plane].winctrl1 = val; + } + if (pdata->flags & WIN_COLOR_ORDER) { + val = lcd->window[plane].winctrl1 & ~(LCD_WINCTRL1_CCO); + val |= ((pdata->color_order << 24) & LCD_WINCTRL1_CCO); + lcd->window[plane].winctrl1 = val; + } + if (pdata->flags & WIN_PIXEL_ORDER) { + val = lcd->window[plane].winctrl1 & ~(LCD_WINCTRL1_PO); + val |= ((pdata->pixel_order << 22) & LCD_WINCTRL1_PO); + lcd->window[plane].winctrl1 = val; + } + if (pdata->flags & WIN_SIZE) { + val = lcd->window[plane].winctrl1 & ~(LCD_WINCTRL1_SZX | + LCD_WINCTRL1_SZY); + val |= (((pdata->xsize << 11) - 1) & LCD_WINCTRL1_SZX); + val |= (((pdata->ysize) - 1) & LCD_WINCTRL1_SZY); + lcd->window[plane].winctrl1 = val; + /* program buffer line width */ + bpp = winbpp(val) / 8; + val = lcd->window[plane].winctrl2 & ~(LCD_WINCTRL2_BX); + val |= (((pdata->xsize * bpp) << 8) & LCD_WINCTRL2_BX); + lcd->window[plane].winctrl2 = val; + } + + /* Window control register 2 */ + if (pdata->flags & WIN_COLORKEY_MODE) { + val = lcd->window[plane].winctrl2 & ~(LCD_WINCTRL2_CKMODE); + val |= ((pdata->colorkey_mode << 24) & LCD_WINCTRL2_CKMODE); + lcd->window[plane].winctrl2 = val; + } + if (pdata->flags & WIN_DOUBLE_BUFFER_MODE) { + val = lcd->window[plane].winctrl2 & ~(LCD_WINCTRL2_DBM); + val |= ((pdata->double_buffer_mode << 23) & LCD_WINCTRL2_DBM); + lcd->window[plane].winctrl2 = val; + } + if (pdata->flags & WIN_RAM_ARRAY_MODE) { + val = lcd->window[plane].winctrl2 & ~(LCD_WINCTRL2_RAM); + val |= ((pdata->ram_array_mode << 21) & LCD_WINCTRL2_RAM); + lcd->window[plane].winctrl2 = val; + } + + /* Buffer line width programmed with WIN_SIZE */ + + if (pdata->flags & WIN_BUFFER_SCALE) { + val = lcd->window[plane].winctrl2 & ~(LCD_WINCTRL2_SCX | + LCD_WINCTRL2_SCY); + val |= ((pdata->xsize << 11) & LCD_WINCTRL2_SCX); + val |= ((pdata->ysize) & LCD_WINCTRL2_SCY); + lcd->window[plane].winctrl2 = val; + } + + if (pdata->flags & WIN_ENABLE) { + val = lcd->winenable; + val &= ~(1<<plane); + val |= (pdata->enable & 1) << plane; + lcd->winenable = val; + } + au_sync(); +} + +static void get_window(unsigned int plane, + struct au1200_lcd_window_regs_t *pdata) +{ + /* Window control register 0 */ + pdata->xpos = (lcd->window[plane].winctrl0 & LCD_WINCTRL0_OX) >> 21; + pdata->ypos = (lcd->window[plane].winctrl0 & LCD_WINCTRL0_OY) >> 10; + pdata->alpha_color = (lcd->window[plane].winctrl0 & LCD_WINCTRL0_A) >> 2; + pdata->alpha_mode = (lcd->window[plane].winctrl0 & LCD_WINCTRL0_AEN) >> 1; + + /* Window control register 1 */ + pdata->priority = (lcd->window[plane].winctrl1& LCD_WINCTRL1_PRI) >> 30; + pdata->channel = (lcd->window[plane].winctrl1 & LCD_WINCTRL1_PIPE) >> 29; + pdata->buffer_format = (lcd->window[plane].winctrl1 & LCD_WINCTRL1_FRM) >> 25; + pdata->color_order = (lcd->window[plane].winctrl1 & LCD_WINCTRL1_CCO) >> 24; + pdata->pixel_order = (lcd->window[plane].winctrl1 & LCD_WINCTRL1_PO) >> 22; + pdata->xsize = ((lcd->window[plane].winctrl1 & LCD_WINCTRL1_SZX) >> 11) + 1; + pdata->ysize = (lcd->window[plane].winctrl1 & LCD_WINCTRL1_SZY) + 1; + + /* Window control register 2 */ + pdata->colorkey_mode = (lcd->window[plane].winctrl2 & LCD_WINCTRL2_CKMODE) >> 24; + pdata->double_buffer_mode = (lcd->window[plane].winctrl2 & LCD_WINCTRL2_DBM) >> 23; + pdata->ram_array_mode = (lcd->window[plane].winctrl2 & LCD_WINCTRL2_RAM) >> 21; + + pdata->enable = (lcd->winenable >> plane) & 1; + au_sync(); +} + +static int au1200fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct au1200fb_device *fbdev = info->par; + int plane; + int val; + + plane = fbinfo2index(info); + print_dbg("au1200fb: ioctl %d on plane %d\n", cmd, plane); + + if (cmd == AU1200_LCD_FB_IOCTL) { + struct au1200_lcd_iodata_t iodata; + + if (copy_from_user(&iodata, (void __user *) arg, sizeof(iodata))) + return -EFAULT; + + print_dbg("FB IOCTL called\n"); + + switch (iodata.subcmd) { + case AU1200_LCD_SET_SCREEN: + print_dbg("AU1200_LCD_SET_SCREEN\n"); + set_global(cmd, &iodata.global); + break; + + case AU1200_LCD_GET_SCREEN: + print_dbg("AU1200_LCD_GET_SCREEN\n"); + get_global(cmd, &iodata.global); + break; + + case AU1200_LCD_SET_WINDOW: + print_dbg("AU1200_LCD_SET_WINDOW\n"); + set_window(plane, &iodata.window); + break; + + case AU1200_LCD_GET_WINDOW: + print_dbg("AU1200_LCD_GET_WINDOW\n"); + get_window(plane, &iodata.window); + break; + + case AU1200_LCD_SET_PANEL: + print_dbg("AU1200_LCD_SET_PANEL\n"); + if ((iodata.global.panel_choice >= 0) && + (iodata.global.panel_choice < + NUM_PANELS)) + { + struct panel_settings *newpanel; + panel_index = iodata.global.panel_choice; + newpanel = &known_lcd_panels[panel_index]; + au1200_setpanel(newpanel, fbdev->pd); + } + break; + + case AU1200_LCD_GET_PANEL: + print_dbg("AU1200_LCD_GET_PANEL\n"); + iodata.global.panel_choice = panel_index; + break; + + default: + return -EINVAL; + } + + val = copy_to_user((void __user *) arg, &iodata, sizeof(iodata)); + if (val) { + print_dbg("error: could not copy %d bytes\n", val); + return -EFAULT; + } + } + + return 0; +} + + +static struct fb_ops au1200fb_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = au1200fb_fb_check_var, + .fb_set_par = au1200fb_fb_set_par, + .fb_setcolreg = au1200fb_fb_setcolreg, + .fb_blank = au1200fb_fb_blank, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_sync = NULL, + .fb_ioctl = au1200fb_ioctl, + .fb_mmap = au1200fb_fb_mmap, +}; + +/*-------------------------------------------------------------------------*/ + +static irqreturn_t au1200fb_handle_irq(int irq, void* dev_id) +{ + /* Nothing to do for now, just clear any pending interrupt */ + lcd->intstatus = lcd->intstatus; + au_sync(); + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* AU1200 LCD device probe helpers */ + +static int au1200fb_init_fbinfo(struct au1200fb_device *fbdev) +{ + struct fb_info *fbi = fbdev->fb_info; + int bpp; + + fbi->fbops = &au1200fb_fb_ops; + + bpp = winbpp(win->w[fbdev->plane].mode_winctrl1); + + /* Copy monitor specs from panel data */ + /* fixme: we're setting up LCD controller windows, so these dont give a + damn as to what the monitor specs are (the panel itself does, but that + isn't done here...so maybe need a generic catchall monitor setting??? */ + memcpy(&fbi->monspecs, &panel->monspecs, sizeof(struct fb_monspecs)); + + /* We first try the user mode passed in argument. If that failed, + * or if no one has been specified, we default to the first mode of the + * panel list. Note that after this call, var data will be set */ + if (!fb_find_mode(&fbi->var, + fbi, + NULL, /* drv_info.opt_mode, */ + fbi->monspecs.modedb, + fbi->monspecs.modedb_len, + fbi->monspecs.modedb, + bpp)) { + + print_err("Cannot find valid mode for panel %s", panel->name); + return -EFAULT; + } + + fbi->pseudo_palette = kcalloc(16, sizeof(u32), GFP_KERNEL); + if (!fbi->pseudo_palette) { + return -ENOMEM; + } + + if (fb_alloc_cmap(&fbi->cmap, AU1200_LCD_NBR_PALETTE_ENTRIES, 0) < 0) { + print_err("Fail to allocate colormap (%d entries)", + AU1200_LCD_NBR_PALETTE_ENTRIES); + kfree(fbi->pseudo_palette); + return -EFAULT; + } + + strncpy(fbi->fix.id, "AU1200", sizeof(fbi->fix.id)); + fbi->fix.smem_start = fbdev->fb_phys; + fbi->fix.smem_len = fbdev->fb_len; + fbi->fix.type = FB_TYPE_PACKED_PIXELS; + fbi->fix.xpanstep = 0; + fbi->fix.ypanstep = 0; + fbi->fix.mmio_start = 0; + fbi->fix.mmio_len = 0; + fbi->fix.accel = FB_ACCEL_NONE; + + fbi->screen_base = (char __iomem *) fbdev->fb_mem; + + au1200fb_update_fbinfo(fbi); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + + +static int au1200fb_setup(struct au1200fb_platdata *pd) +{ + char *options = NULL; + char *this_opt, *endptr; + int num_panels = ARRAY_SIZE(known_lcd_panels); + int panel_idx = -1; + + fb_get_options(DRIVER_NAME, &options); + + if (!options) + goto out; + + while ((this_opt = strsep(&options, ",")) != NULL) { + /* Panel option - can be panel name, + * "bs" for board-switch, or number/index */ + if (!strncmp(this_opt, "panel:", 6)) { + int i; + long int li; + char *endptr; + this_opt += 6; + /* First check for index, which allows + * to short circuit this mess */ + li = simple_strtol(this_opt, &endptr, 0); + if (*endptr == '\0') + panel_idx = (int)li; + else if (strcmp(this_opt, "bs") == 0) + panel_idx = pd->panel_index(); + else { + for (i = 0; i < num_panels; i++) { + if (!strcmp(this_opt, + known_lcd_panels[i].name)) { + panel_idx = i; + break; + } + } + } + if ((panel_idx < 0) || (panel_idx >= num_panels)) + print_warn("Panel %s not supported!", this_opt); + else + panel_index = panel_idx; + + } else if (strncmp(this_opt, "nohwcursor", 10) == 0) + nohwcursor = 1; + else if (strncmp(this_opt, "devices:", 8) == 0) { + this_opt += 8; + device_count = simple_strtol(this_opt, &endptr, 0); + if ((device_count < 0) || + (device_count > MAX_DEVICE_COUNT)) + device_count = MAX_DEVICE_COUNT; + } else if (strncmp(this_opt, "wincfg:", 7) == 0) { + this_opt += 7; + window_index = simple_strtol(this_opt, &endptr, 0); + if ((window_index < 0) || + (window_index >= ARRAY_SIZE(windows))) + window_index = DEFAULT_WINDOW_INDEX; + } else if (strncmp(this_opt, "off", 3) == 0) + return 1; + else + print_warn("Unsupported option \"%s\"", this_opt); + } + +out: + return 0; +} + +/* AU1200 LCD controller device driver */ +static int au1200fb_drv_probe(struct platform_device *dev) +{ + struct au1200fb_device *fbdev; + struct au1200fb_platdata *pd; + struct fb_info *fbi = NULL; + unsigned long page; + int bpp, plane, ret, irq; + + print_info("" DRIVER_DESC ""); + + pd = dev->dev.platform_data; + if (!pd) + return -ENODEV; + + /* Setup driver with options */ + if (au1200fb_setup(pd)) + return -ENODEV; + + /* Point to the panel selected */ + panel = &known_lcd_panels[panel_index]; + win = &windows[window_index]; + + printk(DRIVER_NAME ": Panel %d %s\n", panel_index, panel->name); + printk(DRIVER_NAME ": Win %d %s\n", window_index, win->name); + + /* shut gcc up */ + ret = 0; + fbdev = NULL; + + for (plane = 0; plane < device_count; ++plane) { + bpp = winbpp(win->w[plane].mode_winctrl1); + if (win->w[plane].xres == 0) + win->w[plane].xres = panel->Xres; + if (win->w[plane].yres == 0) + win->w[plane].yres = panel->Yres; + + fbi = framebuffer_alloc(sizeof(struct au1200fb_device), + &dev->dev); + if (!fbi) + goto failed; + + _au1200fb_infos[plane] = fbi; + fbdev = fbi->par; + fbdev->fb_info = fbi; + fbdev->pd = pd; + + fbdev->plane = plane; + + /* Allocate the framebuffer to the maximum screen size */ + fbdev->fb_len = (win->w[plane].xres * win->w[plane].yres * bpp) / 8; + + fbdev->fb_mem = dmam_alloc_noncoherent(&dev->dev, + PAGE_ALIGN(fbdev->fb_len), + &fbdev->fb_phys, GFP_KERNEL); + if (!fbdev->fb_mem) { + print_err("fail to allocate frambuffer (size: %dK))", + fbdev->fb_len / 1024); + return -ENOMEM; + } + + /* + * Set page reserved so that mmap will work. This is necessary + * since we'll be remapping normal memory. + */ + for (page = (unsigned long)fbdev->fb_phys; + page < PAGE_ALIGN((unsigned long)fbdev->fb_phys + + fbdev->fb_len); + page += PAGE_SIZE) { + SetPageReserved(pfn_to_page(page >> PAGE_SHIFT)); /* LCD DMA is NOT coherent on Au1200 */ + } + print_dbg("Framebuffer memory map at %p", fbdev->fb_mem); + print_dbg("phys=0x%08x, size=%dK", fbdev->fb_phys, fbdev->fb_len / 1024); + + /* Init FB data */ + if ((ret = au1200fb_init_fbinfo(fbdev)) < 0) + goto failed; + + /* Register new framebuffer */ + ret = register_framebuffer(fbi); + if (ret < 0) { + print_err("cannot register new framebuffer"); + goto failed; + } + + au1200fb_fb_set_par(fbi); + +#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) + if (plane == 0) + if (fb_prepare_logo(fbi, FB_ROTATE_UR)) { + /* Start display and show logo on boot */ + fb_set_cmap(&fbi->cmap, fbi); + fb_show_logo(fbi, FB_ROTATE_UR); + } +#endif + } + + /* Now hook interrupt too */ + irq = platform_get_irq(dev, 0); + ret = request_irq(irq, au1200fb_handle_irq, + IRQF_SHARED, "lcd", (void *)dev); + if (ret) { + print_err("fail to request interrupt line %d (err: %d)", + irq, ret); + goto failed; + } + + platform_set_drvdata(dev, pd); + + /* Kickstart the panel */ + au1200_setpanel(panel, pd); + + return 0; + +failed: + /* NOTE: This only does the current plane/window that failed; others are still active */ + if (fbi) { + if (fbi->cmap.len != 0) + fb_dealloc_cmap(&fbi->cmap); + kfree(fbi->pseudo_palette); + } + if (plane == 0) + free_irq(AU1200_LCD_INT, (void*)dev); + return ret; +} + +static int au1200fb_drv_remove(struct platform_device *dev) +{ + struct au1200fb_platdata *pd = platform_get_drvdata(dev); + struct au1200fb_device *fbdev; + struct fb_info *fbi; + int plane; + + /* Turn off the panel */ + au1200_setpanel(NULL, pd); + + for (plane = 0; plane < device_count; ++plane) { + fbi = _au1200fb_infos[plane]; + fbdev = fbi->par; + + /* Clean up all probe data */ + unregister_framebuffer(fbi); + if (fbi->cmap.len != 0) + fb_dealloc_cmap(&fbi->cmap); + kfree(fbi->pseudo_palette); + + framebuffer_release(fbi); + _au1200fb_infos[plane] = NULL; + } + + free_irq(platform_get_irq(dev, 0), (void *)dev); + + return 0; +} + +#ifdef CONFIG_PM +static int au1200fb_drv_suspend(struct device *dev) +{ + struct au1200fb_platdata *pd = dev_get_drvdata(dev); + au1200_setpanel(NULL, pd); + + lcd->outmask = 0; + au_sync(); + + return 0; +} + +static int au1200fb_drv_resume(struct device *dev) +{ + struct au1200fb_platdata *pd = dev_get_drvdata(dev); + struct fb_info *fbi; + int i; + + /* Kickstart the panel */ + au1200_setpanel(panel, pd); + + for (i = 0; i < device_count; i++) { + fbi = _au1200fb_infos[i]; + au1200fb_fb_set_par(fbi); + } + + return 0; +} + +static const struct dev_pm_ops au1200fb_pmops = { + .suspend = au1200fb_drv_suspend, + .resume = au1200fb_drv_resume, + .freeze = au1200fb_drv_suspend, + .thaw = au1200fb_drv_resume, +}; + +#define AU1200FB_PMOPS (&au1200fb_pmops) + +#else +#define AU1200FB_PMOPS NULL +#endif /* CONFIG_PM */ + +static struct platform_driver au1200fb_driver = { + .driver = { + .name = "au1200-lcd", + .owner = THIS_MODULE, + .pm = AU1200FB_PMOPS, + }, + .probe = au1200fb_drv_probe, + .remove = au1200fb_drv_remove, +}; +module_platform_driver(au1200fb_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/au1200fb.h b/drivers/video/fbdev/au1200fb.h new file mode 100644 index 000000000000..e2672714d8d4 --- /dev/null +++ b/drivers/video/fbdev/au1200fb.h @@ -0,0 +1,572 @@ +/* + * BRIEF MODULE DESCRIPTION + * Hardware definitions for the Au1200 LCD controller + * + * Copyright 2004 AMD + * Author: AMD + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _AU1200LCD_H +#define _AU1200LCD_H + +/********************************************************************/ +#define AU1200_LCD_ADDR 0xB5000000 + +#define uint8 unsigned char +#define uint32 unsigned int + +struct au1200_lcd { + volatile uint32 reserved0; + volatile uint32 screen; + volatile uint32 backcolor; + volatile uint32 horztiming; + volatile uint32 verttiming; + volatile uint32 clkcontrol; + volatile uint32 pwmdiv; + volatile uint32 pwmhi; + volatile uint32 reserved1; + volatile uint32 winenable; + volatile uint32 colorkey; + volatile uint32 colorkeymsk; + struct + { + volatile uint32 cursorctrl; + volatile uint32 cursorpos; + volatile uint32 cursorcolor0; + volatile uint32 cursorcolor1; + volatile uint32 cursorcolor2; + uint32 cursorcolor3; + } hwc; + volatile uint32 intstatus; + volatile uint32 intenable; + volatile uint32 outmask; + volatile uint32 fifoctrl; + uint32 reserved2[(0x0100-0x0058)/4]; + struct + { + volatile uint32 winctrl0; + volatile uint32 winctrl1; + volatile uint32 winctrl2; + volatile uint32 winbuf0; + volatile uint32 winbuf1; + volatile uint32 winbufctrl; + uint32 winreserved0; + uint32 winreserved1; + } window[4]; + + uint32 reserved3[(0x0400-0x0180)/4]; + + volatile uint32 palette[(0x0800-0x0400)/4]; + + volatile uint8 cursorpattern[256]; +}; + +/* lcd_screen */ +#define LCD_SCREEN_SEN (1<<31) +#define LCD_SCREEN_SX (0x07FF<<19) +#define LCD_SCREEN_SY (0x07FF<< 8) +#define LCD_SCREEN_SWP (1<<7) +#define LCD_SCREEN_SWD (1<<6) +#define LCD_SCREEN_PT (7<<0) +#define LCD_SCREEN_PT_TFT (0<<0) +#define LCD_SCREEN_SX_N(WIDTH) ((WIDTH-1)<<19) +#define LCD_SCREEN_SY_N(HEIGHT) ((HEIGHT-1)<<8) +#define LCD_SCREEN_PT_CSTN (1<<0) +#define LCD_SCREEN_PT_CDSTN (2<<0) +#define LCD_SCREEN_PT_M8STN (3<<0) +#define LCD_SCREEN_PT_M4STN (4<<0) + +/* lcd_backcolor */ +#define LCD_BACKCOLOR_SBGR (0xFF<<16) +#define LCD_BACKCOLOR_SBGG (0xFF<<8) +#define LCD_BACKCOLOR_SBGB (0xFF<<0) +#define LCD_BACKCOLOR_SBGR_N(N) ((N)<<16) +#define LCD_BACKCOLOR_SBGG_N(N) ((N)<<8) +#define LCD_BACKCOLOR_SBGB_N(N) ((N)<<0) + +/* lcd_winenable */ +#define LCD_WINENABLE_WEN3 (1<<3) +#define LCD_WINENABLE_WEN2 (1<<2) +#define LCD_WINENABLE_WEN1 (1<<1) +#define LCD_WINENABLE_WEN0 (1<<0) + +/* lcd_colorkey */ +#define LCD_COLORKEY_CKR (0xFF<<16) +#define LCD_COLORKEY_CKG (0xFF<<8) +#define LCD_COLORKEY_CKB (0xFF<<0) +#define LCD_COLORKEY_CKR_N(N) ((N)<<16) +#define LCD_COLORKEY_CKG_N(N) ((N)<<8) +#define LCD_COLORKEY_CKB_N(N) ((N)<<0) + +/* lcd_colorkeymsk */ +#define LCD_COLORKEYMSK_CKMR (0xFF<<16) +#define LCD_COLORKEYMSK_CKMG (0xFF<<8) +#define LCD_COLORKEYMSK_CKMB (0xFF<<0) +#define LCD_COLORKEYMSK_CKMR_N(N) ((N)<<16) +#define LCD_COLORKEYMSK_CKMG_N(N) ((N)<<8) +#define LCD_COLORKEYMSK_CKMB_N(N) ((N)<<0) + +/* lcd windows control 0 */ +#define LCD_WINCTRL0_OX (0x07FF<<21) +#define LCD_WINCTRL0_OY (0x07FF<<10) +#define LCD_WINCTRL0_A (0x00FF<<2) +#define LCD_WINCTRL0_AEN (1<<1) +#define LCD_WINCTRL0_OX_N(N) ((N)<<21) +#define LCD_WINCTRL0_OY_N(N) ((N)<<10) +#define LCD_WINCTRL0_A_N(N) ((N)<<2) + +/* lcd windows control 1 */ +#define LCD_WINCTRL1_PRI (3<<30) +#define LCD_WINCTRL1_PIPE (1<<29) +#define LCD_WINCTRL1_FRM (0xF<<25) +#define LCD_WINCTRL1_CCO (1<<24) +#define LCD_WINCTRL1_PO (3<<22) +#define LCD_WINCTRL1_SZX (0x07FF<<11) +#define LCD_WINCTRL1_SZY (0x07FF<<0) +#define LCD_WINCTRL1_FRM_1BPP (0<<25) +#define LCD_WINCTRL1_FRM_2BPP (1<<25) +#define LCD_WINCTRL1_FRM_4BPP (2<<25) +#define LCD_WINCTRL1_FRM_8BPP (3<<25) +#define LCD_WINCTRL1_FRM_12BPP (4<<25) +#define LCD_WINCTRL1_FRM_16BPP655 (5<<25) +#define LCD_WINCTRL1_FRM_16BPP565 (6<<25) +#define LCD_WINCTRL1_FRM_16BPP556 (7<<25) +#define LCD_WINCTRL1_FRM_16BPPI1555 (8<<25) +#define LCD_WINCTRL1_FRM_16BPPI5551 (9<<25) +#define LCD_WINCTRL1_FRM_16BPPA1555 (10<<25) +#define LCD_WINCTRL1_FRM_16BPPA5551 (11<<25) +#define LCD_WINCTRL1_FRM_24BPP (12<<25) +#define LCD_WINCTRL1_FRM_32BPP (13<<25) +#define LCD_WINCTRL1_PRI_N(N) ((N)<<30) +#define LCD_WINCTRL1_PO_00 (0<<22) +#define LCD_WINCTRL1_PO_01 (1<<22) +#define LCD_WINCTRL1_PO_10 (2<<22) +#define LCD_WINCTRL1_PO_11 (3<<22) +#define LCD_WINCTRL1_SZX_N(N) ((N-1)<<11) +#define LCD_WINCTRL1_SZY_N(N) ((N-1)<<0) + +/* lcd windows control 2 */ +#define LCD_WINCTRL2_CKMODE (3<<24) +#define LCD_WINCTRL2_DBM (1<<23) +#define LCD_WINCTRL2_RAM (3<<21) +#define LCD_WINCTRL2_BX (0x1FFF<<8) +#define LCD_WINCTRL2_SCX (0xF<<4) +#define LCD_WINCTRL2_SCY (0xF<<0) +#define LCD_WINCTRL2_CKMODE_00 (0<<24) +#define LCD_WINCTRL2_CKMODE_01 (1<<24) +#define LCD_WINCTRL2_CKMODE_10 (2<<24) +#define LCD_WINCTRL2_CKMODE_11 (3<<24) +#define LCD_WINCTRL2_RAM_NONE (0<<21) +#define LCD_WINCTRL2_RAM_PALETTE (1<<21) +#define LCD_WINCTRL2_RAM_GAMMA (2<<21) +#define LCD_WINCTRL2_RAM_BUFFER (3<<21) +#define LCD_WINCTRL2_BX_N(N) ((N)<<8) +#define LCD_WINCTRL2_SCX_1 (0<<4) +#define LCD_WINCTRL2_SCX_2 (1<<4) +#define LCD_WINCTRL2_SCX_4 (2<<4) +#define LCD_WINCTRL2_SCY_1 (0<<0) +#define LCD_WINCTRL2_SCY_2 (1<<0) +#define LCD_WINCTRL2_SCY_4 (2<<0) + +/* lcd windows buffer control */ +#define LCD_WINBUFCTRL_DB (1<<1) +#define LCD_WINBUFCTRL_DBN (1<<0) + +/* lcd_intstatus, lcd_intenable */ +#define LCD_INT_IFO (0xF<<14) +#define LCD_INT_IFU (0xF<<10) +#define LCD_INT_OFO (1<<9) +#define LCD_INT_OFU (1<<8) +#define LCD_INT_WAIT (1<<3) +#define LCD_INT_SD (1<<2) +#define LCD_INT_SA (1<<1) +#define LCD_INT_SS (1<<0) + +/* lcd_horztiming */ +#define LCD_HORZTIMING_HND2 (0x1FF<<18) +#define LCD_HORZTIMING_HND1 (0x1FF<<9) +#define LCD_HORZTIMING_HPW (0x1FF<<0) +#define LCD_HORZTIMING_HND2_N(N)(((N)-1)<<18) +#define LCD_HORZTIMING_HND1_N(N)(((N)-1)<<9) +#define LCD_HORZTIMING_HPW_N(N) (((N)-1)<<0) + +/* lcd_verttiming */ +#define LCD_VERTTIMING_VND2 (0x1FF<<18) +#define LCD_VERTTIMING_VND1 (0x1FF<<9) +#define LCD_VERTTIMING_VPW (0x1FF<<0) +#define LCD_VERTTIMING_VND2_N(N)(((N)-1)<<18) +#define LCD_VERTTIMING_VND1_N(N)(((N)-1)<<9) +#define LCD_VERTTIMING_VPW_N(N) (((N)-1)<<0) + +/* lcd_clkcontrol */ +#define LCD_CLKCONTROL_EXT (1<<22) +#define LCD_CLKCONTROL_DELAY (3<<20) +#define LCD_CLKCONTROL_CDD (1<<19) +#define LCD_CLKCONTROL_IB (1<<18) +#define LCD_CLKCONTROL_IC (1<<17) +#define LCD_CLKCONTROL_IH (1<<16) +#define LCD_CLKCONTROL_IV (1<<15) +#define LCD_CLKCONTROL_BF (0x1F<<10) +#define LCD_CLKCONTROL_PCD (0x3FF<<0) +#define LCD_CLKCONTROL_BF_N(N) (((N)-1)<<10) +#define LCD_CLKCONTROL_PCD_N(N) ((N)<<0) + +/* lcd_pwmdiv */ +#define LCD_PWMDIV_EN (1<<31) +#define LCD_PWMDIV_PWMDIV (0x1FFFF<<0) +#define LCD_PWMDIV_PWMDIV_N(N) ((N)<<0) + +/* lcd_pwmhi */ +#define LCD_PWMHI_PWMHI1 (0xFFFF<<16) +#define LCD_PWMHI_PWMHI0 (0xFFFF<<0) +#define LCD_PWMHI_PWMHI1_N(N) ((N)<<16) +#define LCD_PWMHI_PWMHI0_N(N) ((N)<<0) + +/* lcd_hwccon */ +#define LCD_HWCCON_EN (1<<0) + +/* lcd_cursorpos */ +#define LCD_CURSORPOS_HWCXOFF (0x1F<<27) +#define LCD_CURSORPOS_HWCXPOS (0x07FF<<16) +#define LCD_CURSORPOS_HWCYOFF (0x1F<<11) +#define LCD_CURSORPOS_HWCYPOS (0x07FF<<0) +#define LCD_CURSORPOS_HWCXOFF_N(N) ((N)<<27) +#define LCD_CURSORPOS_HWCXPOS_N(N) ((N)<<16) +#define LCD_CURSORPOS_HWCYOFF_N(N) ((N)<<11) +#define LCD_CURSORPOS_HWCYPOS_N(N) ((N)<<0) + +/* lcd_cursorcolor */ +#define LCD_CURSORCOLOR_HWCA (0xFF<<24) +#define LCD_CURSORCOLOR_HWCR (0xFF<<16) +#define LCD_CURSORCOLOR_HWCG (0xFF<<8) +#define LCD_CURSORCOLOR_HWCB (0xFF<<0) +#define LCD_CURSORCOLOR_HWCA_N(N) ((N)<<24) +#define LCD_CURSORCOLOR_HWCR_N(N) ((N)<<16) +#define LCD_CURSORCOLOR_HWCG_N(N) ((N)<<8) +#define LCD_CURSORCOLOR_HWCB_N(N) ((N)<<0) + +/* lcd_fifoctrl */ +#define LCD_FIFOCTRL_F3IF (1<<29) +#define LCD_FIFOCTRL_F3REQ (0x1F<<24) +#define LCD_FIFOCTRL_F2IF (1<<29) +#define LCD_FIFOCTRL_F2REQ (0x1F<<16) +#define LCD_FIFOCTRL_F1IF (1<<29) +#define LCD_FIFOCTRL_F1REQ (0x1F<<8) +#define LCD_FIFOCTRL_F0IF (1<<29) +#define LCD_FIFOCTRL_F0REQ (0x1F<<0) +#define LCD_FIFOCTRL_F3REQ_N(N) ((N-1)<<24) +#define LCD_FIFOCTRL_F2REQ_N(N) ((N-1)<<16) +#define LCD_FIFOCTRL_F1REQ_N(N) ((N-1)<<8) +#define LCD_FIFOCTRL_F0REQ_N(N) ((N-1)<<0) + +/* lcd_outmask */ +#define LCD_OUTMASK_MASK (0x00FFFFFF) + +/********************************************************************/ +#endif /* _AU1200LCD_H */ +/* + * BRIEF MODULE DESCRIPTION + * Hardware definitions for the Au1200 LCD controller + * + * Copyright 2004 AMD + * Author: AMD + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _AU1200LCD_H +#define _AU1200LCD_H + +/********************************************************************/ +#define AU1200_LCD_ADDR 0xB5000000 + +#define uint8 unsigned char +#define uint32 unsigned int + +struct au1200_lcd { + volatile uint32 reserved0; + volatile uint32 screen; + volatile uint32 backcolor; + volatile uint32 horztiming; + volatile uint32 verttiming; + volatile uint32 clkcontrol; + volatile uint32 pwmdiv; + volatile uint32 pwmhi; + volatile uint32 reserved1; + volatile uint32 winenable; + volatile uint32 colorkey; + volatile uint32 colorkeymsk; + struct + { + volatile uint32 cursorctrl; + volatile uint32 cursorpos; + volatile uint32 cursorcolor0; + volatile uint32 cursorcolor1; + volatile uint32 cursorcolor2; + uint32 cursorcolor3; + } hwc; + volatile uint32 intstatus; + volatile uint32 intenable; + volatile uint32 outmask; + volatile uint32 fifoctrl; + uint32 reserved2[(0x0100-0x0058)/4]; + struct + { + volatile uint32 winctrl0; + volatile uint32 winctrl1; + volatile uint32 winctrl2; + volatile uint32 winbuf0; + volatile uint32 winbuf1; + volatile uint32 winbufctrl; + uint32 winreserved0; + uint32 winreserved1; + } window[4]; + + uint32 reserved3[(0x0400-0x0180)/4]; + + volatile uint32 palette[(0x0800-0x0400)/4]; + + volatile uint8 cursorpattern[256]; +}; + +/* lcd_screen */ +#define LCD_SCREEN_SEN (1<<31) +#define LCD_SCREEN_SX (0x07FF<<19) +#define LCD_SCREEN_SY (0x07FF<< 8) +#define LCD_SCREEN_SWP (1<<7) +#define LCD_SCREEN_SWD (1<<6) +#define LCD_SCREEN_PT (7<<0) +#define LCD_SCREEN_PT_TFT (0<<0) +#define LCD_SCREEN_SX_N(WIDTH) ((WIDTH-1)<<19) +#define LCD_SCREEN_SY_N(HEIGHT) ((HEIGHT-1)<<8) +#define LCD_SCREEN_PT_CSTN (1<<0) +#define LCD_SCREEN_PT_CDSTN (2<<0) +#define LCD_SCREEN_PT_M8STN (3<<0) +#define LCD_SCREEN_PT_M4STN (4<<0) + +/* lcd_backcolor */ +#define LCD_BACKCOLOR_SBGR (0xFF<<16) +#define LCD_BACKCOLOR_SBGG (0xFF<<8) +#define LCD_BACKCOLOR_SBGB (0xFF<<0) +#define LCD_BACKCOLOR_SBGR_N(N) ((N)<<16) +#define LCD_BACKCOLOR_SBGG_N(N) ((N)<<8) +#define LCD_BACKCOLOR_SBGB_N(N) ((N)<<0) + +/* lcd_winenable */ +#define LCD_WINENABLE_WEN3 (1<<3) +#define LCD_WINENABLE_WEN2 (1<<2) +#define LCD_WINENABLE_WEN1 (1<<1) +#define LCD_WINENABLE_WEN0 (1<<0) + +/* lcd_colorkey */ +#define LCD_COLORKEY_CKR (0xFF<<16) +#define LCD_COLORKEY_CKG (0xFF<<8) +#define LCD_COLORKEY_CKB (0xFF<<0) +#define LCD_COLORKEY_CKR_N(N) ((N)<<16) +#define LCD_COLORKEY_CKG_N(N) ((N)<<8) +#define LCD_COLORKEY_CKB_N(N) ((N)<<0) + +/* lcd_colorkeymsk */ +#define LCD_COLORKEYMSK_CKMR (0xFF<<16) +#define LCD_COLORKEYMSK_CKMG (0xFF<<8) +#define LCD_COLORKEYMSK_CKMB (0xFF<<0) +#define LCD_COLORKEYMSK_CKMR_N(N) ((N)<<16) +#define LCD_COLORKEYMSK_CKMG_N(N) ((N)<<8) +#define LCD_COLORKEYMSK_CKMB_N(N) ((N)<<0) + +/* lcd windows control 0 */ +#define LCD_WINCTRL0_OX (0x07FF<<21) +#define LCD_WINCTRL0_OY (0x07FF<<10) +#define LCD_WINCTRL0_A (0x00FF<<2) +#define LCD_WINCTRL0_AEN (1<<1) +#define LCD_WINCTRL0_OX_N(N) ((N)<<21) +#define LCD_WINCTRL0_OY_N(N) ((N)<<10) +#define LCD_WINCTRL0_A_N(N) ((N)<<2) + +/* lcd windows control 1 */ +#define LCD_WINCTRL1_PRI (3<<30) +#define LCD_WINCTRL1_PIPE (1<<29) +#define LCD_WINCTRL1_FRM (0xF<<25) +#define LCD_WINCTRL1_CCO (1<<24) +#define LCD_WINCTRL1_PO (3<<22) +#define LCD_WINCTRL1_SZX (0x07FF<<11) +#define LCD_WINCTRL1_SZY (0x07FF<<0) +#define LCD_WINCTRL1_FRM_1BPP (0<<25) +#define LCD_WINCTRL1_FRM_2BPP (1<<25) +#define LCD_WINCTRL1_FRM_4BPP (2<<25) +#define LCD_WINCTRL1_FRM_8BPP (3<<25) +#define LCD_WINCTRL1_FRM_12BPP (4<<25) +#define LCD_WINCTRL1_FRM_16BPP655 (5<<25) +#define LCD_WINCTRL1_FRM_16BPP565 (6<<25) +#define LCD_WINCTRL1_FRM_16BPP556 (7<<25) +#define LCD_WINCTRL1_FRM_16BPPI1555 (8<<25) +#define LCD_WINCTRL1_FRM_16BPPI5551 (9<<25) +#define LCD_WINCTRL1_FRM_16BPPA1555 (10<<25) +#define LCD_WINCTRL1_FRM_16BPPA5551 (11<<25) +#define LCD_WINCTRL1_FRM_24BPP (12<<25) +#define LCD_WINCTRL1_FRM_32BPP (13<<25) +#define LCD_WINCTRL1_PRI_N(N) ((N)<<30) +#define LCD_WINCTRL1_PO_00 (0<<22) +#define LCD_WINCTRL1_PO_01 (1<<22) +#define LCD_WINCTRL1_PO_10 (2<<22) +#define LCD_WINCTRL1_PO_11 (3<<22) +#define LCD_WINCTRL1_SZX_N(N) ((N-1)<<11) +#define LCD_WINCTRL1_SZY_N(N) ((N-1)<<0) + +/* lcd windows control 2 */ +#define LCD_WINCTRL2_CKMODE (3<<24) +#define LCD_WINCTRL2_DBM (1<<23) +#define LCD_WINCTRL2_RAM (3<<21) +#define LCD_WINCTRL2_BX (0x1FFF<<8) +#define LCD_WINCTRL2_SCX (0xF<<4) +#define LCD_WINCTRL2_SCY (0xF<<0) +#define LCD_WINCTRL2_CKMODE_00 (0<<24) +#define LCD_WINCTRL2_CKMODE_01 (1<<24) +#define LCD_WINCTRL2_CKMODE_10 (2<<24) +#define LCD_WINCTRL2_CKMODE_11 (3<<24) +#define LCD_WINCTRL2_RAM_NONE (0<<21) +#define LCD_WINCTRL2_RAM_PALETTE (1<<21) +#define LCD_WINCTRL2_RAM_GAMMA (2<<21) +#define LCD_WINCTRL2_RAM_BUFFER (3<<21) +#define LCD_WINCTRL2_BX_N(N) ((N)<<8) +#define LCD_WINCTRL2_SCX_1 (0<<4) +#define LCD_WINCTRL2_SCX_2 (1<<4) +#define LCD_WINCTRL2_SCX_4 (2<<4) +#define LCD_WINCTRL2_SCY_1 (0<<0) +#define LCD_WINCTRL2_SCY_2 (1<<0) +#define LCD_WINCTRL2_SCY_4 (2<<0) + +/* lcd windows buffer control */ +#define LCD_WINBUFCTRL_DB (1<<1) +#define LCD_WINBUFCTRL_DBN (1<<0) + +/* lcd_intstatus, lcd_intenable */ +#define LCD_INT_IFO (0xF<<14) +#define LCD_INT_IFU (0xF<<10) +#define LCD_INT_OFO (1<<9) +#define LCD_INT_OFU (1<<8) +#define LCD_INT_WAIT (1<<3) +#define LCD_INT_SD (1<<2) +#define LCD_INT_SA (1<<1) +#define LCD_INT_SS (1<<0) + +/* lcd_horztiming */ +#define LCD_HORZTIMING_HND2 (0x1FF<<18) +#define LCD_HORZTIMING_HND1 (0x1FF<<9) +#define LCD_HORZTIMING_HPW (0x1FF<<0) +#define LCD_HORZTIMING_HND2_N(N)(((N)-1)<<18) +#define LCD_HORZTIMING_HND1_N(N)(((N)-1)<<9) +#define LCD_HORZTIMING_HPW_N(N) (((N)-1)<<0) + +/* lcd_verttiming */ +#define LCD_VERTTIMING_VND2 (0x1FF<<18) +#define LCD_VERTTIMING_VND1 (0x1FF<<9) +#define LCD_VERTTIMING_VPW (0x1FF<<0) +#define LCD_VERTTIMING_VND2_N(N)(((N)-1)<<18) +#define LCD_VERTTIMING_VND1_N(N)(((N)-1)<<9) +#define LCD_VERTTIMING_VPW_N(N) (((N)-1)<<0) + +/* lcd_clkcontrol */ +#define LCD_CLKCONTROL_EXT (1<<22) +#define LCD_CLKCONTROL_DELAY (3<<20) +#define LCD_CLKCONTROL_CDD (1<<19) +#define LCD_CLKCONTROL_IB (1<<18) +#define LCD_CLKCONTROL_IC (1<<17) +#define LCD_CLKCONTROL_IH (1<<16) +#define LCD_CLKCONTROL_IV (1<<15) +#define LCD_CLKCONTROL_BF (0x1F<<10) +#define LCD_CLKCONTROL_PCD (0x3FF<<0) +#define LCD_CLKCONTROL_BF_N(N) (((N)-1)<<10) +#define LCD_CLKCONTROL_PCD_N(N) ((N)<<0) + +/* lcd_pwmdiv */ +#define LCD_PWMDIV_EN (1<<31) +#define LCD_PWMDIV_PWMDIV (0x1FFFF<<0) +#define LCD_PWMDIV_PWMDIV_N(N) ((N)<<0) + +/* lcd_pwmhi */ +#define LCD_PWMHI_PWMHI1 (0xFFFF<<16) +#define LCD_PWMHI_PWMHI0 (0xFFFF<<0) +#define LCD_PWMHI_PWMHI1_N(N) ((N)<<16) +#define LCD_PWMHI_PWMHI0_N(N) ((N)<<0) + +/* lcd_hwccon */ +#define LCD_HWCCON_EN (1<<0) + +/* lcd_cursorpos */ +#define LCD_CURSORPOS_HWCXOFF (0x1F<<27) +#define LCD_CURSORPOS_HWCXPOS (0x07FF<<16) +#define LCD_CURSORPOS_HWCYOFF (0x1F<<11) +#define LCD_CURSORPOS_HWCYPOS (0x07FF<<0) +#define LCD_CURSORPOS_HWCXOFF_N(N) ((N)<<27) +#define LCD_CURSORPOS_HWCXPOS_N(N) ((N)<<16) +#define LCD_CURSORPOS_HWCYOFF_N(N) ((N)<<11) +#define LCD_CURSORPOS_HWCYPOS_N(N) ((N)<<0) + +/* lcd_cursorcolor */ +#define LCD_CURSORCOLOR_HWCA (0xFF<<24) +#define LCD_CURSORCOLOR_HWCR (0xFF<<16) +#define LCD_CURSORCOLOR_HWCG (0xFF<<8) +#define LCD_CURSORCOLOR_HWCB (0xFF<<0) +#define LCD_CURSORCOLOR_HWCA_N(N) ((N)<<24) +#define LCD_CURSORCOLOR_HWCR_N(N) ((N)<<16) +#define LCD_CURSORCOLOR_HWCG_N(N) ((N)<<8) +#define LCD_CURSORCOLOR_HWCB_N(N) ((N)<<0) + +/* lcd_fifoctrl */ +#define LCD_FIFOCTRL_F3IF (1<<29) +#define LCD_FIFOCTRL_F3REQ (0x1F<<24) +#define LCD_FIFOCTRL_F2IF (1<<29) +#define LCD_FIFOCTRL_F2REQ (0x1F<<16) +#define LCD_FIFOCTRL_F1IF (1<<29) +#define LCD_FIFOCTRL_F1REQ (0x1F<<8) +#define LCD_FIFOCTRL_F0IF (1<<29) +#define LCD_FIFOCTRL_F0REQ (0x1F<<0) +#define LCD_FIFOCTRL_F3REQ_N(N) ((N-1)<<24) +#define LCD_FIFOCTRL_F2REQ_N(N) ((N-1)<<16) +#define LCD_FIFOCTRL_F1REQ_N(N) ((N-1)<<8) +#define LCD_FIFOCTRL_F0REQ_N(N) ((N-1)<<0) + +/* lcd_outmask */ +#define LCD_OUTMASK_MASK (0x00FFFFFF) + +/********************************************************************/ +#endif /* _AU1200LCD_H */ diff --git a/drivers/video/fbdev/auo_k1900fb.c b/drivers/video/fbdev/auo_k1900fb.c new file mode 100644 index 000000000000..f5b668e77af3 --- /dev/null +++ b/drivers/video/fbdev/auo_k1900fb.c @@ -0,0 +1,205 @@ +/* + * auok190xfb.c -- FB driver for AUO-K1900 controllers + * + * Copyright (C) 2011, 2012 Heiko Stuebner <heiko@sntech.de> + * + * based on broadsheetfb.c + * + * Copyright (C) 2008, Jaya Kumar + * + * 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. + * + * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. + * + * This driver is written to be used with the AUO-K1900 display controller. + * + * It is intended to be architecture independent. A board specific driver + * must be used to perform all the physical IO interactions. + * + * The controller supports different update modes: + * mode0+1 16 step gray (4bit) + * mode2 4 step gray (2bit) - FIXME: add strange refresh + * mode3 2 step gray (1bit) - FIXME: add strange refresh + * mode4 handwriting mode (strange behaviour) + * mode5 automatic selection of update mode + */ + +#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/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/pm_runtime.h> + +#include <video/auo_k190xfb.h> + +#include "auo_k190x.h" + +/* + * AUO-K1900 specific commands + */ + +#define AUOK1900_CMD_PARTIALDISP 0x1001 +#define AUOK1900_CMD_ROTATION 0x1006 +#define AUOK1900_CMD_LUT_STOP 0x1009 + +#define AUOK1900_INIT_TEMP_AVERAGE (1 << 13) +#define AUOK1900_INIT_ROTATE(_x) ((_x & 0x3) << 10) +#define AUOK1900_INIT_RESOLUTION(_res) ((_res & 0x7) << 2) + +static void auok1900_init(struct auok190xfb_par *par) +{ + struct device *dev = par->info->device; + struct auok190x_board *board = par->board; + u16 init_param = 0; + + pm_runtime_get_sync(dev); + + init_param |= AUOK1900_INIT_TEMP_AVERAGE; + init_param |= AUOK1900_INIT_ROTATE(par->rotation); + init_param |= AUOK190X_INIT_INVERSE_WHITE; + init_param |= AUOK190X_INIT_FORMAT0; + init_param |= AUOK1900_INIT_RESOLUTION(par->resolution); + init_param |= AUOK190X_INIT_SHIFT_RIGHT; + + auok190x_send_cmdargs(par, AUOK190X_CMD_INIT, 1, &init_param); + + /* let the controller finish */ + board->wait_for_rdy(par); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +static void auok1900_update_region(struct auok190xfb_par *par, int mode, + u16 y1, u16 y2) +{ + struct device *dev = par->info->device; + unsigned char *buf = (unsigned char *)par->info->screen_base; + int xres = par->info->var.xres; + int line_length = par->info->fix.line_length; + u16 args[4]; + + pm_runtime_get_sync(dev); + + mutex_lock(&(par->io_lock)); + + /* y1 and y2 must be a multiple of 2 so drop the lowest bit */ + y1 &= 0xfffe; + y2 &= 0xfffe; + + dev_dbg(dev, "update (x,y,w,h,mode)=(%d,%d,%d,%d,%d)\n", + 1, y1+1, xres, y2-y1, mode); + + /* to FIX handle different partial update modes */ + args[0] = mode | 1; + args[1] = y1 + 1; + args[2] = xres; + args[3] = y2 - y1; + buf += y1 * line_length; + auok190x_send_cmdargs_pixels(par, AUOK1900_CMD_PARTIALDISP, 4, args, + ((y2 - y1) * line_length)/2, (u16 *) buf); + auok190x_send_command(par, AUOK190X_CMD_DATA_STOP); + + par->update_cnt++; + + mutex_unlock(&(par->io_lock)); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +static void auok1900fb_dpy_update_pages(struct auok190xfb_par *par, + u16 y1, u16 y2) +{ + int mode; + + if (par->update_mode < 0) { + mode = AUOK190X_UPDATE_MODE(1); + par->last_mode = -1; + } else { + mode = AUOK190X_UPDATE_MODE(par->update_mode); + par->last_mode = par->update_mode; + } + + if (par->flash) + mode |= AUOK190X_UPDATE_NONFLASH; + + auok1900_update_region(par, mode, y1, y2); +} + +static void auok1900fb_dpy_update(struct auok190xfb_par *par) +{ + int mode; + + if (par->update_mode < 0) { + mode = AUOK190X_UPDATE_MODE(0); + par->last_mode = -1; + } else { + mode = AUOK190X_UPDATE_MODE(par->update_mode); + par->last_mode = par->update_mode; + } + + if (par->flash) + mode |= AUOK190X_UPDATE_NONFLASH; + + auok1900_update_region(par, mode, 0, par->info->var.yres); + par->update_cnt = 0; +} + +static bool auok1900fb_need_refresh(struct auok190xfb_par *par) +{ + return (par->update_cnt > 10); +} + +static int auok1900fb_probe(struct platform_device *pdev) +{ + struct auok190x_init_data init; + struct auok190x_board *board; + + /* pick up board specific routines */ + board = pdev->dev.platform_data; + if (!board) + return -EINVAL; + + /* fill temporary init struct for common init */ + init.id = "auo_k1900fb"; + init.board = board; + init.update_partial = auok1900fb_dpy_update_pages; + init.update_all = auok1900fb_dpy_update; + init.need_refresh = auok1900fb_need_refresh; + init.init = auok1900_init; + + return auok190x_common_probe(pdev, &init); +} + +static int auok1900fb_remove(struct platform_device *pdev) +{ + return auok190x_common_remove(pdev); +} + +static struct platform_driver auok1900fb_driver = { + .probe = auok1900fb_probe, + .remove = auok1900fb_remove, + .driver = { + .owner = THIS_MODULE, + .name = "auo_k1900fb", + .pm = &auok190x_pm, + }, +}; +module_platform_driver(auok1900fb_driver); + +MODULE_DESCRIPTION("framebuffer driver for the AUO-K1900 EPD controller"); +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/auo_k1901fb.c b/drivers/video/fbdev/auo_k1901fb.c new file mode 100644 index 000000000000..12b9adcb75c5 --- /dev/null +++ b/drivers/video/fbdev/auo_k1901fb.c @@ -0,0 +1,258 @@ +/* + * auok190xfb.c -- FB driver for AUO-K1901 controllers + * + * Copyright (C) 2011, 2012 Heiko Stuebner <heiko@sntech.de> + * + * based on broadsheetfb.c + * + * Copyright (C) 2008, Jaya Kumar + * + * 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. + * + * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. + * + * This driver is written to be used with the AUO-K1901 display controller. + * + * It is intended to be architecture independent. A board specific driver + * must be used to perform all the physical IO interactions. + * + * The controller supports different update modes: + * mode0+1 16 step gray (4bit) + * mode2+3 4 step gray (2bit) + * mode4+5 2 step gray (1bit) + * - mode4 is described as "without LUT" + * mode7 automatic selection of update mode + * + * The most interesting difference to the K1900 is the ability to do screen + * updates in an asynchronous fashion. Where the K1900 needs to wait for the + * current update to complete, the K1901 can process later updates already. + */ + +#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/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/pm_runtime.h> + +#include <video/auo_k190xfb.h> + +#include "auo_k190x.h" + +/* + * AUO-K1901 specific commands + */ + +#define AUOK1901_CMD_LUT_INTERFACE 0x0005 +#define AUOK1901_CMD_DMA_START 0x1001 +#define AUOK1901_CMD_CURSOR_START 0x1007 +#define AUOK1901_CMD_CURSOR_STOP AUOK190X_CMD_DATA_STOP +#define AUOK1901_CMD_DDMA_START 0x1009 + +#define AUOK1901_INIT_GATE_PULSE_LOW (0 << 14) +#define AUOK1901_INIT_GATE_PULSE_HIGH (1 << 14) +#define AUOK1901_INIT_SINGLE_GATE (0 << 13) +#define AUOK1901_INIT_DOUBLE_GATE (1 << 13) + +/* Bits to pixels + * Mode 15-12 11-8 7-4 3-0 + * format2 2 T 1 T + * format3 1 T 2 T + * format4 T 2 T 1 + * format5 T 1 T 2 + * + * halftone modes: + * format6 2 2 1 1 + * format7 1 1 2 2 + */ +#define AUOK1901_INIT_FORMAT2 (1 << 7) +#define AUOK1901_INIT_FORMAT3 ((1 << 7) | (1 << 6)) +#define AUOK1901_INIT_FORMAT4 (1 << 8) +#define AUOK1901_INIT_FORMAT5 ((1 << 8) | (1 << 6)) +#define AUOK1901_INIT_FORMAT6 ((1 << 8) | (1 << 7)) +#define AUOK1901_INIT_FORMAT7 ((1 << 8) | (1 << 7) | (1 << 6)) + +/* res[4] to bit 10 + * res[3-0] to bits 5-2 + */ +#define AUOK1901_INIT_RESOLUTION(_res) (((_res & (1 << 4)) << 6) \ + | ((_res & 0xf) << 2)) + +/* + * portrait / landscape orientation in AUOK1901_CMD_DMA_START + */ +#define AUOK1901_DMA_ROTATE90(_rot) ((_rot & 1) << 13) + +/* + * equivalent to 1 << 11, needs the ~ to have same rotation like K1900 + */ +#define AUOK1901_DDMA_ROTATE180(_rot) ((~_rot & 2) << 10) + +static void auok1901_init(struct auok190xfb_par *par) +{ + struct device *dev = par->info->device; + struct auok190x_board *board = par->board; + u16 init_param = 0; + + pm_runtime_get_sync(dev); + + init_param |= AUOK190X_INIT_INVERSE_WHITE; + init_param |= AUOK190X_INIT_FORMAT0; + init_param |= AUOK1901_INIT_RESOLUTION(par->resolution); + init_param |= AUOK190X_INIT_SHIFT_LEFT; + + auok190x_send_cmdargs(par, AUOK190X_CMD_INIT, 1, &init_param); + + /* let the controller finish */ + board->wait_for_rdy(par); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +static void auok1901_update_region(struct auok190xfb_par *par, int mode, + u16 y1, u16 y2) +{ + struct device *dev = par->info->device; + unsigned char *buf = (unsigned char *)par->info->screen_base; + int xres = par->info->var.xres; + int line_length = par->info->fix.line_length; + u16 args[5]; + + pm_runtime_get_sync(dev); + + mutex_lock(&(par->io_lock)); + + /* y1 and y2 must be a multiple of 2 so drop the lowest bit */ + y1 &= 0xfffe; + y2 &= 0xfffe; + + dev_dbg(dev, "update (x,y,w,h,mode)=(%d,%d,%d,%d,%d)\n", + 1, y1+1, xres, y2-y1, mode); + + /* K1901: first transfer the region data */ + args[0] = AUOK1901_DMA_ROTATE90(par->rotation) | 1; + args[1] = y1 + 1; + args[2] = xres; + args[3] = y2 - y1; + buf += y1 * line_length; + auok190x_send_cmdargs_pixels_nowait(par, AUOK1901_CMD_DMA_START, 4, + args, ((y2 - y1) * line_length)/2, + (u16 *) buf); + auok190x_send_command_nowait(par, AUOK190X_CMD_DATA_STOP); + + /* K1901: second tell the controller to update the region with mode */ + args[0] = mode | AUOK1901_DDMA_ROTATE180(par->rotation); + args[1] = 1; + args[2] = y1 + 1; + args[3] = xres; + args[4] = y2 - y1; + auok190x_send_cmdargs_nowait(par, AUOK1901_CMD_DDMA_START, 5, args); + + par->update_cnt++; + + mutex_unlock(&(par->io_lock)); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +static void auok1901fb_dpy_update_pages(struct auok190xfb_par *par, + u16 y1, u16 y2) +{ + int mode; + + if (par->update_mode < 0) { + mode = AUOK190X_UPDATE_MODE(1); + par->last_mode = -1; + } else { + mode = AUOK190X_UPDATE_MODE(par->update_mode); + par->last_mode = par->update_mode; + } + + if (par->flash) + mode |= AUOK190X_UPDATE_NONFLASH; + + auok1901_update_region(par, mode, y1, y2); +} + +static void auok1901fb_dpy_update(struct auok190xfb_par *par) +{ + int mode; + + /* When doing full updates, wait for the controller to be ready + * This will hopefully catch some hangs of the K1901 + */ + par->board->wait_for_rdy(par); + + if (par->update_mode < 0) { + mode = AUOK190X_UPDATE_MODE(0); + par->last_mode = -1; + } else { + mode = AUOK190X_UPDATE_MODE(par->update_mode); + par->last_mode = par->update_mode; + } + + if (par->flash) + mode |= AUOK190X_UPDATE_NONFLASH; + + auok1901_update_region(par, mode, 0, par->info->var.yres); + par->update_cnt = 0; +} + +static bool auok1901fb_need_refresh(struct auok190xfb_par *par) +{ + return (par->update_cnt > 10); +} + +static int auok1901fb_probe(struct platform_device *pdev) +{ + struct auok190x_init_data init; + struct auok190x_board *board; + + /* pick up board specific routines */ + board = pdev->dev.platform_data; + if (!board) + return -EINVAL; + + /* fill temporary init struct for common init */ + init.id = "auo_k1901fb"; + init.board = board; + init.update_partial = auok1901fb_dpy_update_pages; + init.update_all = auok1901fb_dpy_update; + init.need_refresh = auok1901fb_need_refresh; + init.init = auok1901_init; + + return auok190x_common_probe(pdev, &init); +} + +static int auok1901fb_remove(struct platform_device *pdev) +{ + return auok190x_common_remove(pdev); +} + +static struct platform_driver auok1901fb_driver = { + .probe = auok1901fb_probe, + .remove = auok1901fb_remove, + .driver = { + .owner = THIS_MODULE, + .name = "auo_k1901fb", + .pm = &auok190x_pm, + }, +}; +module_platform_driver(auok1901fb_driver); + +MODULE_DESCRIPTION("framebuffer driver for the AUO-K1901 EPD controller"); +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/auo_k190x.c b/drivers/video/fbdev/auo_k190x.c new file mode 100644 index 000000000000..8d2499d1cafb --- /dev/null +++ b/drivers/video/fbdev/auo_k190x.c @@ -0,0 +1,1198 @@ +/* + * Common code for AUO-K190X framebuffer drivers + * + * Copyright (C) 2012 Heiko Stuebner <heiko@sntech.de> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/uaccess.h> +#include <linux/vmalloc.h> +#include <linux/regulator/consumer.h> + +#include <video/auo_k190xfb.h> + +#include "auo_k190x.h" + +struct panel_info { + int w; + int h; +}; + +/* table of panel specific parameters to be indexed into by the board drivers */ +static struct panel_info panel_table[] = { + /* standard 6" */ + [AUOK190X_RESOLUTION_800_600] = { + .w = 800, + .h = 600, + }, + /* standard 9" */ + [AUOK190X_RESOLUTION_1024_768] = { + .w = 1024, + .h = 768, + }, + [AUOK190X_RESOLUTION_600_800] = { + .w = 600, + .h = 800, + }, + [AUOK190X_RESOLUTION_768_1024] = { + .w = 768, + .h = 1024, + }, +}; + +/* + * private I80 interface to the board driver + */ + +static void auok190x_issue_data(struct auok190xfb_par *par, u16 data) +{ + par->board->set_ctl(par, AUOK190X_I80_WR, 0); + par->board->set_hdb(par, data); + par->board->set_ctl(par, AUOK190X_I80_WR, 1); +} + +static void auok190x_issue_cmd(struct auok190xfb_par *par, u16 data) +{ + par->board->set_ctl(par, AUOK190X_I80_DC, 0); + auok190x_issue_data(par, data); + par->board->set_ctl(par, AUOK190X_I80_DC, 1); +} + +/** + * Conversion of 16bit color to 4bit grayscale + * does roughly (0.3 * R + 0.6 G + 0.1 B) / 2 + */ +static inline int rgb565_to_gray4(u16 data, struct fb_var_screeninfo *var) +{ + return ((((data & 0xF800) >> var->red.offset) * 77 + + ((data & 0x07E0) >> (var->green.offset + 1)) * 151 + + ((data & 0x1F) >> var->blue.offset) * 28) >> 8 >> 1); +} + +static int auok190x_issue_pixels_rgb565(struct auok190xfb_par *par, int size, + u16 *data) +{ + struct fb_var_screeninfo *var = &par->info->var; + struct device *dev = par->info->device; + int i; + u16 tmp; + + if (size & 7) { + dev_err(dev, "issue_pixels: size %d must be a multiple of 8\n", + size); + return -EINVAL; + } + + for (i = 0; i < (size >> 2); i++) { + par->board->set_ctl(par, AUOK190X_I80_WR, 0); + + tmp = (rgb565_to_gray4(data[4*i], var) & 0x000F); + tmp |= (rgb565_to_gray4(data[4*i+1], var) << 4) & 0x00F0; + tmp |= (rgb565_to_gray4(data[4*i+2], var) << 8) & 0x0F00; + tmp |= (rgb565_to_gray4(data[4*i+3], var) << 12) & 0xF000; + + par->board->set_hdb(par, tmp); + par->board->set_ctl(par, AUOK190X_I80_WR, 1); + } + + return 0; +} + +static int auok190x_issue_pixels_gray8(struct auok190xfb_par *par, int size, + u16 *data) +{ + struct device *dev = par->info->device; + int i; + u16 tmp; + + if (size & 3) { + dev_err(dev, "issue_pixels: size %d must be a multiple of 4\n", + size); + return -EINVAL; + } + + for (i = 0; i < (size >> 1); i++) { + par->board->set_ctl(par, AUOK190X_I80_WR, 0); + + /* simple reduction of 8bit staticgray to 4bit gray + * combines 4 * 4bit pixel values into a 16bit value + */ + tmp = (data[2*i] & 0xF0) >> 4; + tmp |= (data[2*i] & 0xF000) >> 8; + tmp |= (data[2*i+1] & 0xF0) << 4; + tmp |= (data[2*i+1] & 0xF000); + + par->board->set_hdb(par, tmp); + par->board->set_ctl(par, AUOK190X_I80_WR, 1); + } + + return 0; +} + +static int auok190x_issue_pixels(struct auok190xfb_par *par, int size, + u16 *data) +{ + struct fb_info *info = par->info; + struct device *dev = par->info->device; + + if (info->var.bits_per_pixel == 8 && info->var.grayscale) + auok190x_issue_pixels_gray8(par, size, data); + else if (info->var.bits_per_pixel == 16) + auok190x_issue_pixels_rgb565(par, size, data); + else + dev_err(dev, "unsupported color mode (bits: %d, gray: %d)\n", + info->var.bits_per_pixel, info->var.grayscale); + + return 0; +} + +static u16 auok190x_read_data(struct auok190xfb_par *par) +{ + u16 data; + + par->board->set_ctl(par, AUOK190X_I80_OE, 0); + data = par->board->get_hdb(par); + par->board->set_ctl(par, AUOK190X_I80_OE, 1); + + return data; +} + +/* + * Command interface for the controller drivers + */ + +void auok190x_send_command_nowait(struct auok190xfb_par *par, u16 data) +{ + par->board->set_ctl(par, AUOK190X_I80_CS, 0); + auok190x_issue_cmd(par, data); + par->board->set_ctl(par, AUOK190X_I80_CS, 1); +} +EXPORT_SYMBOL_GPL(auok190x_send_command_nowait); + +void auok190x_send_cmdargs_nowait(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv) +{ + int i; + + par->board->set_ctl(par, AUOK190X_I80_CS, 0); + auok190x_issue_cmd(par, cmd); + + for (i = 0; i < argc; i++) + auok190x_issue_data(par, argv[i]); + par->board->set_ctl(par, AUOK190X_I80_CS, 1); +} +EXPORT_SYMBOL_GPL(auok190x_send_cmdargs_nowait); + +int auok190x_send_command(struct auok190xfb_par *par, u16 data) +{ + int ret; + + ret = par->board->wait_for_rdy(par); + if (ret) + return ret; + + auok190x_send_command_nowait(par, data); + return 0; +} +EXPORT_SYMBOL_GPL(auok190x_send_command); + +int auok190x_send_cmdargs(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv) +{ + int ret; + + ret = par->board->wait_for_rdy(par); + if (ret) + return ret; + + auok190x_send_cmdargs_nowait(par, cmd, argc, argv); + return 0; +} +EXPORT_SYMBOL_GPL(auok190x_send_cmdargs); + +int auok190x_read_cmdargs(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv) +{ + int i, ret; + + ret = par->board->wait_for_rdy(par); + if (ret) + return ret; + + par->board->set_ctl(par, AUOK190X_I80_CS, 0); + auok190x_issue_cmd(par, cmd); + + for (i = 0; i < argc; i++) + argv[i] = auok190x_read_data(par); + par->board->set_ctl(par, AUOK190X_I80_CS, 1); + + return 0; +} +EXPORT_SYMBOL_GPL(auok190x_read_cmdargs); + +void auok190x_send_cmdargs_pixels_nowait(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv, int size, u16 *data) +{ + int i; + + par->board->set_ctl(par, AUOK190X_I80_CS, 0); + + auok190x_issue_cmd(par, cmd); + + for (i = 0; i < argc; i++) + auok190x_issue_data(par, argv[i]); + + auok190x_issue_pixels(par, size, data); + + par->board->set_ctl(par, AUOK190X_I80_CS, 1); +} +EXPORT_SYMBOL_GPL(auok190x_send_cmdargs_pixels_nowait); + +int auok190x_send_cmdargs_pixels(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv, int size, u16 *data) +{ + int ret; + + ret = par->board->wait_for_rdy(par); + if (ret) + return ret; + + auok190x_send_cmdargs_pixels_nowait(par, cmd, argc, argv, size, data); + + return 0; +} +EXPORT_SYMBOL_GPL(auok190x_send_cmdargs_pixels); + +/* + * fbdefio callbacks - common on both controllers. + */ + +static void auok190xfb_dpy_first_io(struct fb_info *info) +{ + /* tell runtime-pm that we wish to use the device in a short time */ + pm_runtime_get(info->device); +} + +/* this is called back from the deferred io workqueue */ +static void auok190xfb_dpy_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct auok190xfb_par *par = info->par; + u16 line_length = info->fix.line_length; + u16 yres = info->var.yres; + u16 y1 = 0, h = 0; + int prev_index = -1; + struct page *cur; + int h_inc; + int threshold; + + if (!list_empty(pagelist)) + /* the device resume should've been requested through first_io, + * if the resume did not finish until now, wait for it. + */ + pm_runtime_barrier(info->device); + else + /* We reached this via the fsync or some other way. + * In either case the first_io function did not run, + * so we runtime_resume the device here synchronously. + */ + pm_runtime_get_sync(info->device); + + /* Do a full screen update every n updates to prevent + * excessive darkening of the Sipix display. + * If we do this, there is no need to walk the pages. + */ + if (par->need_refresh(par)) { + par->update_all(par); + goto out; + } + + /* height increment is fixed per page */ + h_inc = DIV_ROUND_UP(PAGE_SIZE , line_length); + + /* calculate number of pages from pixel height */ + threshold = par->consecutive_threshold / h_inc; + if (threshold < 1) + threshold = 1; + + /* walk the written page list and swizzle the data */ + list_for_each_entry(cur, &fbdefio->pagelist, lru) { + if (prev_index < 0) { + /* just starting so assign first page */ + y1 = (cur->index << PAGE_SHIFT) / line_length; + h = h_inc; + } else if ((cur->index - prev_index) <= threshold) { + /* page is within our threshold for single updates */ + h += h_inc * (cur->index - prev_index); + } else { + /* page not consecutive, issue previous update first */ + par->update_partial(par, y1, y1 + h); + + /* start over with our non consecutive page */ + y1 = (cur->index << PAGE_SHIFT) / line_length; + h = h_inc; + } + prev_index = cur->index; + } + + /* if we still have any pages to update we do so now */ + if (h >= yres) + /* its a full screen update, just do it */ + par->update_all(par); + else + par->update_partial(par, y1, min((u16) (y1 + h), yres)); + +out: + pm_runtime_mark_last_busy(info->device); + pm_runtime_put_autosuspend(info->device); +} + +/* + * framebuffer operations + */ + +/* + * this is the slow path from userspace. they can seek and write to + * the fb. it's inefficient to do anything less than a full screen draw + */ +static ssize_t auok190xfb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct auok190xfb_par *par = info->par; + unsigned long p = *ppos; + void *dst; + int err = 0; + unsigned long total_size; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + total_size = info->fix.smem_len; + + if (p > total_size) + return -EFBIG; + + if (count > total_size) { + err = -EFBIG; + count = total_size; + } + + if (count + p > total_size) { + if (!err) + err = -ENOSPC; + + count = total_size - p; + } + + dst = (void *)(info->screen_base + p); + + if (copy_from_user(dst, buf, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + par->update_all(par); + + return (err) ? err : count; +} + +static void auok190xfb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct auok190xfb_par *par = info->par; + + sys_fillrect(info, rect); + + par->update_all(par); +} + +static void auok190xfb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct auok190xfb_par *par = info->par; + + sys_copyarea(info, area); + + par->update_all(par); +} + +static void auok190xfb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct auok190xfb_par *par = info->par; + + sys_imageblit(info, image); + + par->update_all(par); +} + +static int auok190xfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct device *dev = info->device; + struct auok190xfb_par *par = info->par; + struct panel_info *panel = &panel_table[par->resolution]; + int size; + + /* + * Color depth + */ + + if (var->bits_per_pixel == 8 && var->grayscale == 1) { + /* + * For 8-bit grayscale, R, G, and B offset are equal. + */ + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 0; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } else if (var->bits_per_pixel == 16) { + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } else { + dev_warn(dev, "unsupported color mode (bits: %d, grayscale: %d)\n", + info->var.bits_per_pixel, info->var.grayscale); + return -EINVAL; + } + + /* + * Dimensions + */ + + switch (var->rotate) { + case FB_ROTATE_UR: + case FB_ROTATE_UD: + var->xres = panel->w; + var->yres = panel->h; + break; + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + var->xres = panel->h; + var->yres = panel->w; + break; + default: + dev_dbg(dev, "Invalid rotation request\n"); + return -EINVAL; + } + + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + /* + * Memory limit + */ + + size = var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8; + if (size > info->fix.smem_len) { + dev_err(dev, "Memory limit exceeded, requested %dK\n", + size >> 10); + return -ENOMEM; + } + + return 0; +} + +static int auok190xfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = (var->grayscale) ? FB_VISUAL_STATIC_PSEUDOCOLOR + : FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + fix->ywrapstep = 0; + + return 0; +} + +static int auok190xfb_set_par(struct fb_info *info) +{ + struct auok190xfb_par *par = info->par; + + par->rotation = info->var.rotate; + auok190xfb_set_fix(info); + + /* reinit the controller to honor the rotation */ + par->init(par); + + /* wait for init to complete */ + par->board->wait_for_rdy(par); + + return 0; +} + +static struct fb_ops auok190xfb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = auok190xfb_write, + .fb_fillrect = auok190xfb_fillrect, + .fb_copyarea = auok190xfb_copyarea, + .fb_imageblit = auok190xfb_imageblit, + .fb_check_var = auok190xfb_check_var, + .fb_set_par = auok190xfb_set_par, +}; + +/* + * Controller-functions common to both K1900 and K1901 + */ + +static int auok190x_read_temperature(struct auok190xfb_par *par) +{ + struct device *dev = par->info->device; + u16 data[4]; + int temp; + + pm_runtime_get_sync(dev); + + mutex_lock(&(par->io_lock)); + + auok190x_read_cmdargs(par, AUOK190X_CMD_READ_VERSION, 4, data); + + mutex_unlock(&(par->io_lock)); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + /* sanitize and split of half-degrees for now */ + temp = ((data[0] & AUOK190X_VERSION_TEMP_MASK) >> 1); + + /* handle positive and negative temperatures */ + if (temp >= 201) + return (255 - temp + 1) * (-1); + else + return temp; +} + +static void auok190x_identify(struct auok190xfb_par *par) +{ + struct device *dev = par->info->device; + u16 data[4]; + + pm_runtime_get_sync(dev); + + mutex_lock(&(par->io_lock)); + + auok190x_read_cmdargs(par, AUOK190X_CMD_READ_VERSION, 4, data); + + mutex_unlock(&(par->io_lock)); + + par->epd_type = data[1] & AUOK190X_VERSION_TEMP_MASK; + + par->panel_size_int = AUOK190X_VERSION_SIZE_INT(data[2]); + par->panel_size_float = AUOK190X_VERSION_SIZE_FLOAT(data[2]); + par->panel_model = AUOK190X_VERSION_MODEL(data[2]); + + par->tcon_version = AUOK190X_VERSION_TCON(data[3]); + par->lut_version = AUOK190X_VERSION_LUT(data[3]); + + dev_dbg(dev, "panel %d.%din, model 0x%x, EPD 0x%x TCON-rev 0x%x, LUT-rev 0x%x", + par->panel_size_int, par->panel_size_float, par->panel_model, + par->epd_type, par->tcon_version, par->lut_version); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +/* + * Sysfs functions + */ + +static ssize_t update_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct auok190xfb_par *par = info->par; + + return sprintf(buf, "%d\n", par->update_mode); +} + +static ssize_t update_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct auok190xfb_par *par = info->par; + int mode, ret; + + ret = kstrtoint(buf, 10, &mode); + if (ret) + return ret; + + par->update_mode = mode; + + /* if we enter a better mode, do a full update */ + if (par->last_mode > 1 && mode < par->last_mode) + par->update_all(par); + + return count; +} + +static ssize_t flash_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct auok190xfb_par *par = info->par; + + return sprintf(buf, "%d\n", par->flash); +} + +static ssize_t flash_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct auok190xfb_par *par = info->par; + int flash, ret; + + ret = kstrtoint(buf, 10, &flash); + if (ret) + return ret; + + if (flash > 0) + par->flash = 1; + else + par->flash = 0; + + return count; +} + +static ssize_t temp_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct auok190xfb_par *par = info->par; + int temp; + + temp = auok190x_read_temperature(par); + return sprintf(buf, "%d\n", temp); +} + +static DEVICE_ATTR(update_mode, 0644, update_mode_show, update_mode_store); +static DEVICE_ATTR(flash, 0644, flash_show, flash_store); +static DEVICE_ATTR(temp, 0644, temp_show, NULL); + +static struct attribute *auok190x_attributes[] = { + &dev_attr_update_mode.attr, + &dev_attr_flash.attr, + &dev_attr_temp.attr, + NULL +}; + +static const struct attribute_group auok190x_attr_group = { + .attrs = auok190x_attributes, +}; + +static int auok190x_power(struct auok190xfb_par *par, bool on) +{ + struct auok190x_board *board = par->board; + int ret; + + if (on) { + /* We should maintain POWER up for at least 80ms before set + * RST_N and SLP_N to high (TCON spec 20100803_v35 p59) + */ + ret = regulator_enable(par->regulator); + if (ret) + return ret; + + msleep(200); + gpio_set_value(board->gpio_nrst, 1); + gpio_set_value(board->gpio_nsleep, 1); + msleep(200); + } else { + regulator_disable(par->regulator); + gpio_set_value(board->gpio_nrst, 0); + gpio_set_value(board->gpio_nsleep, 0); + } + + return 0; +} + +/* + * Recovery - powercycle the controller + */ + +static void auok190x_recover(struct auok190xfb_par *par) +{ + struct device *dev = par->info->device; + + auok190x_power(par, 0); + msleep(100); + auok190x_power(par, 1); + + /* after powercycling the device, it's always active */ + pm_runtime_set_active(dev); + par->standby = 0; + + par->init(par); + + /* wait for init to complete */ + par->board->wait_for_rdy(par); +} + +/* + * Power-management + */ + +#ifdef CONFIG_PM +static int auok190x_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fb_info *info = platform_get_drvdata(pdev); + struct auok190xfb_par *par = info->par; + struct auok190x_board *board = par->board; + u16 standby_param; + + /* take and keep the lock until we are resumed, as the controller + * will never reach the non-busy state when in standby mode + */ + mutex_lock(&(par->io_lock)); + + if (par->standby) { + dev_warn(dev, "already in standby, runtime-pm pairing mismatch\n"); + mutex_unlock(&(par->io_lock)); + return 0; + } + + /* according to runtime_pm.txt runtime_suspend only means, that the + * device will not process data and will not communicate with the CPU + * As we hold the lock, this stays true even without standby + */ + if (board->quirks & AUOK190X_QUIRK_STANDBYBROKEN) { + dev_dbg(dev, "runtime suspend without standby\n"); + goto finish; + } else if (board->quirks & AUOK190X_QUIRK_STANDBYPARAM) { + /* for some TCON versions STANDBY expects a parameter (0) but + * it seems the real tcon version has to be determined yet. + */ + dev_dbg(dev, "runtime suspend with additional empty param\n"); + standby_param = 0; + auok190x_send_cmdargs(par, AUOK190X_CMD_STANDBY, 1, + &standby_param); + } else { + dev_dbg(dev, "runtime suspend without param\n"); + auok190x_send_command(par, AUOK190X_CMD_STANDBY); + } + + msleep(64); + +finish: + par->standby = 1; + + return 0; +} + +static int auok190x_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fb_info *info = platform_get_drvdata(pdev); + struct auok190xfb_par *par = info->par; + struct auok190x_board *board = par->board; + + if (!par->standby) { + dev_warn(dev, "not in standby, runtime-pm pairing mismatch\n"); + return 0; + } + + if (board->quirks & AUOK190X_QUIRK_STANDBYBROKEN) { + dev_dbg(dev, "runtime resume without standby\n"); + } else { + /* when in standby, controller is always busy + * and only accepts the wakeup command + */ + dev_dbg(dev, "runtime resume from standby\n"); + auok190x_send_command_nowait(par, AUOK190X_CMD_WAKEUP); + + msleep(160); + + /* wait for the controller to be ready and release the lock */ + board->wait_for_rdy(par); + } + + par->standby = 0; + + mutex_unlock(&(par->io_lock)); + + return 0; +} + +static int auok190x_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fb_info *info = platform_get_drvdata(pdev); + struct auok190xfb_par *par = info->par; + struct auok190x_board *board = par->board; + int ret; + + dev_dbg(dev, "suspend\n"); + if (board->quirks & AUOK190X_QUIRK_STANDBYBROKEN) { + /* suspend via powering off the ic */ + dev_dbg(dev, "suspend with broken standby\n"); + + auok190x_power(par, 0); + } else { + dev_dbg(dev, "suspend using sleep\n"); + + /* the sleep state can only be entered from the standby state. + * pm_runtime_get_noresume gets called before the suspend call. + * So the devices usage count is >0 but it is not necessarily + * active. + */ + if (!pm_runtime_status_suspended(dev)) { + ret = auok190x_runtime_suspend(dev); + if (ret < 0) { + dev_err(dev, "auok190x_runtime_suspend failed with %d\n", + ret); + return ret; + } + par->manual_standby = 1; + } + + gpio_direction_output(board->gpio_nsleep, 0); + } + + msleep(100); + + return 0; +} + +static int auok190x_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fb_info *info = platform_get_drvdata(pdev); + struct auok190xfb_par *par = info->par; + struct auok190x_board *board = par->board; + + dev_dbg(dev, "resume\n"); + if (board->quirks & AUOK190X_QUIRK_STANDBYBROKEN) { + dev_dbg(dev, "resume with broken standby\n"); + + auok190x_power(par, 1); + + par->init(par); + } else { + dev_dbg(dev, "resume from sleep\n"); + + /* device should be in runtime suspend when we were suspended + * and pm_runtime_put_sync gets called after this function. + * So there is no need to touch the standby mode here at all. + */ + gpio_direction_output(board->gpio_nsleep, 1); + msleep(100); + + /* an additional init call seems to be necessary after sleep */ + auok190x_runtime_resume(dev); + par->init(par); + + /* if we were runtime-suspended before, suspend again*/ + if (!par->manual_standby) + auok190x_runtime_suspend(dev); + else + par->manual_standby = 0; + } + + return 0; +} +#endif + +const struct dev_pm_ops auok190x_pm = { + SET_RUNTIME_PM_OPS(auok190x_runtime_suspend, auok190x_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(auok190x_suspend, auok190x_resume) +}; +EXPORT_SYMBOL_GPL(auok190x_pm); + +/* + * Common probe and remove code + */ + +int auok190x_common_probe(struct platform_device *pdev, + struct auok190x_init_data *init) +{ + struct auok190x_board *board = init->board; + struct auok190xfb_par *par; + struct fb_info *info; + struct panel_info *panel; + int videomemorysize, ret; + unsigned char *videomemory; + + /* check board contents */ + if (!board->init || !board->cleanup || !board->wait_for_rdy + || !board->set_ctl || !board->set_hdb || !board->get_hdb + || !board->setup_irq) + return -EINVAL; + + info = framebuffer_alloc(sizeof(struct auok190xfb_par), &pdev->dev); + if (!info) + return -ENOMEM; + + par = info->par; + par->info = info; + par->board = board; + par->recover = auok190x_recover; + par->update_partial = init->update_partial; + par->update_all = init->update_all; + par->need_refresh = init->need_refresh; + par->init = init->init; + + /* init update modes */ + par->update_cnt = 0; + par->update_mode = -1; + par->last_mode = -1; + par->flash = 0; + + par->regulator = regulator_get(info->device, "vdd"); + if (IS_ERR(par->regulator)) { + ret = PTR_ERR(par->regulator); + dev_err(info->device, "Failed to get regulator: %d\n", ret); + goto err_reg; + } + + ret = board->init(par); + if (ret) { + dev_err(info->device, "board init failed, %d\n", ret); + goto err_board; + } + + ret = gpio_request(board->gpio_nsleep, "AUOK190x sleep"); + if (ret) { + dev_err(info->device, "could not request sleep gpio, %d\n", + ret); + goto err_gpio1; + } + + ret = gpio_direction_output(board->gpio_nsleep, 0); + if (ret) { + dev_err(info->device, "could not set sleep gpio, %d\n", ret); + goto err_gpio2; + } + + ret = gpio_request(board->gpio_nrst, "AUOK190x reset"); + if (ret) { + dev_err(info->device, "could not request reset gpio, %d\n", + ret); + goto err_gpio2; + } + + ret = gpio_direction_output(board->gpio_nrst, 0); + if (ret) { + dev_err(info->device, "could not set reset gpio, %d\n", ret); + goto err_gpio3; + } + + ret = auok190x_power(par, 1); + if (ret) { + dev_err(info->device, "could not power on the device, %d\n", + ret); + goto err_gpio3; + } + + mutex_init(&par->io_lock); + + init_waitqueue_head(&par->waitq); + + ret = par->board->setup_irq(par->info); + if (ret) { + dev_err(info->device, "could not setup ready-irq, %d\n", ret); + goto err_irq; + } + + /* wait for init to complete */ + par->board->wait_for_rdy(par); + + /* + * From here on the controller can talk to us + */ + + /* initialise fix, var, resolution and rotation */ + + strlcpy(info->fix.id, init->id, 16); + info->var.bits_per_pixel = 8; + info->var.grayscale = 1; + + panel = &panel_table[board->resolution]; + + par->resolution = board->resolution; + par->rotation = 0; + + /* videomemory handling */ + + videomemorysize = roundup((panel->w * panel->h) * 2, PAGE_SIZE); + videomemory = vmalloc(videomemorysize); + if (!videomemory) { + ret = -ENOMEM; + goto err_irq; + } + + memset(videomemory, 0, videomemorysize); + info->screen_base = (char *)videomemory; + info->fix.smem_len = videomemorysize; + + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + info->fbops = &auok190xfb_ops; + + ret = auok190xfb_check_var(&info->var, info); + if (ret) + goto err_defio; + + auok190xfb_set_fix(info); + + /* deferred io init */ + + info->fbdefio = devm_kzalloc(info->device, + sizeof(struct fb_deferred_io), + GFP_KERNEL); + if (!info->fbdefio) { + dev_err(info->device, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_defio; + } + + dev_dbg(info->device, "targeting %d frames per second\n", board->fps); + info->fbdefio->delay = HZ / board->fps; + info->fbdefio->first_io = auok190xfb_dpy_first_io, + info->fbdefio->deferred_io = auok190xfb_dpy_deferred_io, + fb_deferred_io_init(info); + + /* color map */ + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret < 0) { + dev_err(info->device, "Failed to allocate colormap\n"); + goto err_cmap; + } + + /* controller init */ + + par->consecutive_threshold = 100; + par->init(par); + auok190x_identify(par); + + platform_set_drvdata(pdev, info); + + ret = register_framebuffer(info); + if (ret < 0) + goto err_regfb; + + ret = sysfs_create_group(&info->device->kobj, &auok190x_attr_group); + if (ret) + goto err_sysfs; + + dev_info(info->device, "fb%d: %dx%d using %dK of video memory\n", + info->node, info->var.xres, info->var.yres, + videomemorysize >> 10); + + /* increase autosuspend_delay when we use alternative methods + * for runtime_pm + */ + par->autosuspend_delay = (board->quirks & AUOK190X_QUIRK_STANDBYBROKEN) + ? 1000 : 200; + + pm_runtime_set_active(info->device); + pm_runtime_enable(info->device); + pm_runtime_set_autosuspend_delay(info->device, par->autosuspend_delay); + pm_runtime_use_autosuspend(info->device); + + return 0; + +err_sysfs: + unregister_framebuffer(info); +err_regfb: + fb_dealloc_cmap(&info->cmap); +err_cmap: + fb_deferred_io_cleanup(info); +err_defio: + vfree((void *)info->screen_base); +err_irq: + auok190x_power(par, 0); +err_gpio3: + gpio_free(board->gpio_nrst); +err_gpio2: + gpio_free(board->gpio_nsleep); +err_gpio1: + board->cleanup(par); +err_board: + regulator_put(par->regulator); +err_reg: + framebuffer_release(info); + + return ret; +} +EXPORT_SYMBOL_GPL(auok190x_common_probe); + +int auok190x_common_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct auok190xfb_par *par = info->par; + struct auok190x_board *board = par->board; + + pm_runtime_disable(info->device); + + sysfs_remove_group(&info->device->kobj, &auok190x_attr_group); + + unregister_framebuffer(info); + + fb_dealloc_cmap(&info->cmap); + + fb_deferred_io_cleanup(info); + + vfree((void *)info->screen_base); + + auok190x_power(par, 0); + + gpio_free(board->gpio_nrst); + gpio_free(board->gpio_nsleep); + + board->cleanup(par); + + regulator_put(par->regulator); + + framebuffer_release(info); + + return 0; +} +EXPORT_SYMBOL_GPL(auok190x_common_remove); + +MODULE_DESCRIPTION("Common code for AUO-K190X controllers"); +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/auo_k190x.h b/drivers/video/fbdev/auo_k190x.h new file mode 100644 index 000000000000..e35af1f51b28 --- /dev/null +++ b/drivers/video/fbdev/auo_k190x.h @@ -0,0 +1,129 @@ +/* + * Private common definitions for AUO-K190X framebuffer drivers + * + * Copyright (C) 2012 Heiko Stuebner <heiko@sntech.de> + * + * 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. + */ + +/* + * I80 interface specific defines + */ + +#define AUOK190X_I80_CS 0x01 +#define AUOK190X_I80_DC 0x02 +#define AUOK190X_I80_WR 0x03 +#define AUOK190X_I80_OE 0x04 + +/* + * AUOK190x commands, common to both controllers + */ + +#define AUOK190X_CMD_INIT 0x0000 +#define AUOK190X_CMD_STANDBY 0x0001 +#define AUOK190X_CMD_WAKEUP 0x0002 +#define AUOK190X_CMD_TCON_RESET 0x0003 +#define AUOK190X_CMD_DATA_STOP 0x1002 +#define AUOK190X_CMD_LUT_START 0x1003 +#define AUOK190X_CMD_DISP_REFRESH 0x1004 +#define AUOK190X_CMD_DISP_RESET 0x1005 +#define AUOK190X_CMD_PRE_DISPLAY_START 0x100D +#define AUOK190X_CMD_PRE_DISPLAY_STOP 0x100F +#define AUOK190X_CMD_FLASH_W 0x2000 +#define AUOK190X_CMD_FLASH_E 0x2001 +#define AUOK190X_CMD_FLASH_STS 0x2002 +#define AUOK190X_CMD_FRAMERATE 0x3000 +#define AUOK190X_CMD_READ_VERSION 0x4000 +#define AUOK190X_CMD_READ_STATUS 0x4001 +#define AUOK190X_CMD_READ_LUT 0x4003 +#define AUOK190X_CMD_DRIVERTIMING 0x5000 +#define AUOK190X_CMD_LBALANCE 0x5001 +#define AUOK190X_CMD_AGINGMODE 0x6000 +#define AUOK190X_CMD_AGINGEXIT 0x6001 + +/* + * Common settings for AUOK190X_CMD_INIT + */ + +#define AUOK190X_INIT_DATA_FILTER (0 << 12) +#define AUOK190X_INIT_DATA_BYPASS (1 << 12) +#define AUOK190X_INIT_INVERSE_WHITE (0 << 9) +#define AUOK190X_INIT_INVERSE_BLACK (1 << 9) +#define AUOK190X_INIT_SCAN_DOWN (0 << 1) +#define AUOK190X_INIT_SCAN_UP (1 << 1) +#define AUOK190X_INIT_SHIFT_LEFT (0 << 0) +#define AUOK190X_INIT_SHIFT_RIGHT (1 << 0) + +/* Common bits to pixels + * Mode 15-12 11-8 7-4 3-0 + * format0 4 3 2 1 + * format1 3 4 1 2 + */ + +#define AUOK190X_INIT_FORMAT0 0 +#define AUOK190X_INIT_FORMAT1 (1 << 6) + +/* + * settings for AUOK190X_CMD_RESET + */ + +#define AUOK190X_RESET_TCON (0 << 0) +#define AUOK190X_RESET_NORMAL (1 << 0) +#define AUOK190X_RESET_PON (1 << 1) + +/* + * AUOK190X_CMD_VERSION + */ + +#define AUOK190X_VERSION_TEMP_MASK (0x1ff) +#define AUOK190X_VERSION_EPD_MASK (0xff) +#define AUOK190X_VERSION_SIZE_INT(_val) ((_val & 0xfc00) >> 10) +#define AUOK190X_VERSION_SIZE_FLOAT(_val) ((_val & 0x3c0) >> 6) +#define AUOK190X_VERSION_MODEL(_val) (_val & 0x3f) +#define AUOK190X_VERSION_LUT(_val) (_val & 0xff) +#define AUOK190X_VERSION_TCON(_val) ((_val & 0xff00) >> 8) + +/* + * update modes for CMD_PARTIALDISP on K1900 and CMD_DDMA on K1901 + */ + +#define AUOK190X_UPDATE_MODE(_res) ((_res & 0x7) << 12) +#define AUOK190X_UPDATE_NONFLASH (1 << 15) + +/* + * track panel specific parameters for common init + */ + +struct auok190x_init_data { + char *id; + struct auok190x_board *board; + + void (*update_partial)(struct auok190xfb_par *par, u16 y1, u16 y2); + void (*update_all)(struct auok190xfb_par *par); + bool (*need_refresh)(struct auok190xfb_par *par); + void (*init)(struct auok190xfb_par *par); +}; + + +extern void auok190x_send_command_nowait(struct auok190xfb_par *par, u16 data); +extern int auok190x_send_command(struct auok190xfb_par *par, u16 data); +extern void auok190x_send_cmdargs_nowait(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv); +extern int auok190x_send_cmdargs(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv); +extern void auok190x_send_cmdargs_pixels_nowait(struct auok190xfb_par *par, + u16 cmd, int argc, u16 *argv, + int size, u16 *data); +extern int auok190x_send_cmdargs_pixels(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv, int size, + u16 *data); +extern int auok190x_read_cmdargs(struct auok190xfb_par *par, u16 cmd, + int argc, u16 *argv); + +extern int auok190x_common_probe(struct platform_device *pdev, + struct auok190x_init_data *init); +extern int auok190x_common_remove(struct platform_device *pdev); + +extern const struct dev_pm_ops auok190x_pm; diff --git a/drivers/video/fbdev/bf537-lq035.c b/drivers/video/fbdev/bf537-lq035.c new file mode 100644 index 000000000000..a82d2578d976 --- /dev/null +++ b/drivers/video/fbdev/bf537-lq035.c @@ -0,0 +1,915 @@ +/* + * Analog Devices Blackfin(BF537 STAMP) + SHARP TFT LCD. + * http://docs.blackfin.uclinux.org/doku.php?id=hw:cards:tft-lcd + * + * Copyright 2006-2010 Analog Devices Inc. + * Licensed under the GPL-2. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/device.h> +#include <linux/backlight.h> +#include <linux/lcd.h> +#include <linux/i2c.h> +#include <linux/spinlock.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/platform_device.h> + +#include <asm/blackfin.h> +#include <asm/irq.h> +#include <asm/dpmc.h> +#include <asm/dma.h> +#include <asm/portmux.h> + +#define NO_BL 1 + +#define MAX_BRIGHENESS 95 +#define MIN_BRIGHENESS 5 +#define NBR_PALETTE 256 + +static const unsigned short ppi_pins[] = { + P_PPI0_CLK, P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, P_PPI0_D3, + P_PPI0_D4, P_PPI0_D5, P_PPI0_D6, P_PPI0_D7, + P_PPI0_D8, P_PPI0_D9, P_PPI0_D10, P_PPI0_D11, + P_PPI0_D12, P_PPI0_D13, P_PPI0_D14, P_PPI0_D15, 0 +}; + +static unsigned char *fb_buffer; /* RGB Buffer */ +static unsigned long *dma_desc_table; +static int t_conf_done, lq035_open_cnt; +static DEFINE_SPINLOCK(bfin_lq035_lock); + +static int landscape; +module_param(landscape, int, 0); +MODULE_PARM_DESC(landscape, + "LANDSCAPE use 320x240 instead of Native 240x320 Resolution"); + +static int bgr; +module_param(bgr, int, 0); +MODULE_PARM_DESC(bgr, + "BGR use 16-bit BGR-565 instead of RGB-565"); + +static int nocursor = 1; +module_param(nocursor, int, 0644); +MODULE_PARM_DESC(nocursor, "cursor enable/disable"); + +static unsigned long current_brightness; /* backlight */ + +/* AD5280 vcomm */ +static unsigned char vcomm_value = 150; +static struct i2c_client *ad5280_client; + +static void set_vcomm(void) +{ + int nr; + + if (!ad5280_client) + return; + + nr = i2c_smbus_write_byte_data(ad5280_client, 0x00, vcomm_value); + if (nr) + pr_err("i2c_smbus_write_byte_data fail: %d\n", nr); +} + +static int ad5280_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "SMBUS Byte Data not Supported\n"); + return -EIO; + } + + ret = i2c_smbus_write_byte_data(client, 0x00, vcomm_value); + if (ret) { + dev_err(&client->dev, "write fail: %d\n", ret); + return ret; + } + + ad5280_client = client; + + return 0; +} + +static int ad5280_remove(struct i2c_client *client) +{ + ad5280_client = NULL; + return 0; +} + +static const struct i2c_device_id ad5280_id[] = { + {"bf537-lq035-ad5280", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad5280_id); + +static struct i2c_driver ad5280_driver = { + .driver = { + .name = "bf537-lq035-ad5280", + }, + .probe = ad5280_probe, + .remove = ad5280_remove, + .id_table = ad5280_id, +}; + +#ifdef CONFIG_PNAV10 +#define MOD GPIO_PH13 + +#define bfin_write_TIMER_LP_CONFIG bfin_write_TIMER0_CONFIG +#define bfin_write_TIMER_LP_WIDTH bfin_write_TIMER0_WIDTH +#define bfin_write_TIMER_LP_PERIOD bfin_write_TIMER0_PERIOD +#define bfin_read_TIMER_LP_COUNTER bfin_read_TIMER0_COUNTER +#define TIMDIS_LP TIMDIS0 +#define TIMEN_LP TIMEN0 + +#define bfin_write_TIMER_SPS_CONFIG bfin_write_TIMER1_CONFIG +#define bfin_write_TIMER_SPS_WIDTH bfin_write_TIMER1_WIDTH +#define bfin_write_TIMER_SPS_PERIOD bfin_write_TIMER1_PERIOD +#define TIMDIS_SPS TIMDIS1 +#define TIMEN_SPS TIMEN1 + +#define bfin_write_TIMER_SP_CONFIG bfin_write_TIMER5_CONFIG +#define bfin_write_TIMER_SP_WIDTH bfin_write_TIMER5_WIDTH +#define bfin_write_TIMER_SP_PERIOD bfin_write_TIMER5_PERIOD +#define TIMDIS_SP TIMDIS5 +#define TIMEN_SP TIMEN5 + +#define bfin_write_TIMER_PS_CLS_CONFIG bfin_write_TIMER2_CONFIG +#define bfin_write_TIMER_PS_CLS_WIDTH bfin_write_TIMER2_WIDTH +#define bfin_write_TIMER_PS_CLS_PERIOD bfin_write_TIMER2_PERIOD +#define TIMDIS_PS_CLS TIMDIS2 +#define TIMEN_PS_CLS TIMEN2 + +#define bfin_write_TIMER_REV_CONFIG bfin_write_TIMER3_CONFIG +#define bfin_write_TIMER_REV_WIDTH bfin_write_TIMER3_WIDTH +#define bfin_write_TIMER_REV_PERIOD bfin_write_TIMER3_PERIOD +#define TIMDIS_REV TIMDIS3 +#define TIMEN_REV TIMEN3 +#define bfin_read_TIMER_REV_COUNTER bfin_read_TIMER3_COUNTER + +#define FREQ_PPI_CLK (5*1024*1024) /* PPI_CLK 5MHz */ + +#define TIMERS {P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR5, 0} + +#else + +#define UD GPIO_PF13 /* Up / Down */ +#define MOD GPIO_PF10 +#define LBR GPIO_PF14 /* Left Right */ + +#define bfin_write_TIMER_LP_CONFIG bfin_write_TIMER6_CONFIG +#define bfin_write_TIMER_LP_WIDTH bfin_write_TIMER6_WIDTH +#define bfin_write_TIMER_LP_PERIOD bfin_write_TIMER6_PERIOD +#define bfin_read_TIMER_LP_COUNTER bfin_read_TIMER6_COUNTER +#define TIMDIS_LP TIMDIS6 +#define TIMEN_LP TIMEN6 + +#define bfin_write_TIMER_SPS_CONFIG bfin_write_TIMER1_CONFIG +#define bfin_write_TIMER_SPS_WIDTH bfin_write_TIMER1_WIDTH +#define bfin_write_TIMER_SPS_PERIOD bfin_write_TIMER1_PERIOD +#define TIMDIS_SPS TIMDIS1 +#define TIMEN_SPS TIMEN1 + +#define bfin_write_TIMER_SP_CONFIG bfin_write_TIMER0_CONFIG +#define bfin_write_TIMER_SP_WIDTH bfin_write_TIMER0_WIDTH +#define bfin_write_TIMER_SP_PERIOD bfin_write_TIMER0_PERIOD +#define TIMDIS_SP TIMDIS0 +#define TIMEN_SP TIMEN0 + +#define bfin_write_TIMER_PS_CLS_CONFIG bfin_write_TIMER7_CONFIG +#define bfin_write_TIMER_PS_CLS_WIDTH bfin_write_TIMER7_WIDTH +#define bfin_write_TIMER_PS_CLS_PERIOD bfin_write_TIMER7_PERIOD +#define TIMDIS_PS_CLS TIMDIS7 +#define TIMEN_PS_CLS TIMEN7 + +#define bfin_write_TIMER_REV_CONFIG bfin_write_TIMER5_CONFIG +#define bfin_write_TIMER_REV_WIDTH bfin_write_TIMER5_WIDTH +#define bfin_write_TIMER_REV_PERIOD bfin_write_TIMER5_PERIOD +#define TIMDIS_REV TIMDIS5 +#define TIMEN_REV TIMEN5 +#define bfin_read_TIMER_REV_COUNTER bfin_read_TIMER5_COUNTER + +#define FREQ_PPI_CLK (6*1000*1000) /* PPI_CLK 6MHz */ +#define TIMERS {P_TMR0, P_TMR1, P_TMR5, P_TMR6, P_TMR7, 0} + +#endif + +#define LCD_X_RES 240 /* Horizontal Resolution */ +#define LCD_Y_RES 320 /* Vertical Resolution */ + +#define LCD_BBP 16 /* Bit Per Pixel */ + +/* the LCD and the DMA start counting differently; + * since one starts at 0 and the other starts at 1, + * we have a difference of 1 between START_LINES + * and U_LINES. + */ +#define START_LINES 8 /* lines for field flyback or field blanking signal */ +#define U_LINES 9 /* number of undisplayed blanking lines */ + +#define FRAMES_PER_SEC (60) + +#define DCLKS_PER_FRAME (FREQ_PPI_CLK/FRAMES_PER_SEC) +#define DCLKS_PER_LINE (DCLKS_PER_FRAME/(LCD_Y_RES+U_LINES)) + +#define PPI_CONFIG_VALUE (PORT_DIR|XFR_TYPE|DLEN_16|POLS) +#define PPI_DELAY_VALUE (0) +#define TIMER_CONFIG (PWM_OUT|PERIOD_CNT|TIN_SEL|CLK_SEL) + +#define ACTIVE_VIDEO_MEM_OFFSET (LCD_X_RES*START_LINES*(LCD_BBP/8)) +#define ACTIVE_VIDEO_MEM_SIZE (LCD_Y_RES*LCD_X_RES*(LCD_BBP/8)) +#define TOTAL_VIDEO_MEM_SIZE ((LCD_Y_RES+U_LINES)*LCD_X_RES*(LCD_BBP/8)) +#define TOTAL_DMA_DESC_SIZE (2 * sizeof(u32) * (LCD_Y_RES + U_LINES)) + +static void start_timers(void) /* CHECK with HW */ +{ + unsigned long flags; + + local_irq_save(flags); + + bfin_write_TIMER_ENABLE(TIMEN_REV); + SSYNC(); + + while (bfin_read_TIMER_REV_COUNTER() <= 11) + continue; + bfin_write_TIMER_ENABLE(TIMEN_LP); + SSYNC(); + + while (bfin_read_TIMER_LP_COUNTER() < 3) + continue; + bfin_write_TIMER_ENABLE(TIMEN_SP|TIMEN_SPS|TIMEN_PS_CLS); + SSYNC(); + t_conf_done = 1; + local_irq_restore(flags); +} + +static void config_timers(void) +{ + /* Stop timers */ + bfin_write_TIMER_DISABLE(TIMDIS_SP|TIMDIS_SPS|TIMDIS_REV| + TIMDIS_LP|TIMDIS_PS_CLS); + SSYNC(); + + /* LP, timer 6 */ + bfin_write_TIMER_LP_CONFIG(TIMER_CONFIG|PULSE_HI); + bfin_write_TIMER_LP_WIDTH(1); + + bfin_write_TIMER_LP_PERIOD(DCLKS_PER_LINE); + SSYNC(); + + /* SPS, timer 1 */ + bfin_write_TIMER_SPS_CONFIG(TIMER_CONFIG|PULSE_HI); + bfin_write_TIMER_SPS_WIDTH(DCLKS_PER_LINE*2); + bfin_write_TIMER_SPS_PERIOD((DCLKS_PER_LINE * (LCD_Y_RES+U_LINES))); + SSYNC(); + + /* SP, timer 0 */ + bfin_write_TIMER_SP_CONFIG(TIMER_CONFIG|PULSE_HI); + bfin_write_TIMER_SP_WIDTH(1); + bfin_write_TIMER_SP_PERIOD(DCLKS_PER_LINE); + SSYNC(); + + /* PS & CLS, timer 7 */ + bfin_write_TIMER_PS_CLS_CONFIG(TIMER_CONFIG); + bfin_write_TIMER_PS_CLS_WIDTH(LCD_X_RES + START_LINES); + bfin_write_TIMER_PS_CLS_PERIOD(DCLKS_PER_LINE); + + SSYNC(); + +#ifdef NO_BL + /* REV, timer 5 */ + bfin_write_TIMER_REV_CONFIG(TIMER_CONFIG|PULSE_HI); + + bfin_write_TIMER_REV_WIDTH(DCLKS_PER_LINE); + bfin_write_TIMER_REV_PERIOD(DCLKS_PER_LINE*2); + + SSYNC(); +#endif +} + +static void config_ppi(void) +{ + bfin_write_PPI_DELAY(PPI_DELAY_VALUE); + bfin_write_PPI_COUNT(LCD_X_RES-1); + /* 0x10 -> PORT_CFG -> 2 or 3 frame syncs */ + bfin_write_PPI_CONTROL((PPI_CONFIG_VALUE|0x10) & (~POLS)); +} + +static int config_dma(void) +{ + u32 i; + + if (landscape) { + + for (i = 0; i < U_LINES; ++i) { + /* blanking lines point to first line of fb_buffer */ + dma_desc_table[2*i] = (unsigned long)&dma_desc_table[2*i+2]; + dma_desc_table[2*i+1] = (unsigned long)fb_buffer; + } + + for (i = U_LINES; i < U_LINES + LCD_Y_RES; ++i) { + /* visible lines */ + dma_desc_table[2*i] = (unsigned long)&dma_desc_table[2*i+2]; + dma_desc_table[2*i+1] = (unsigned long)fb_buffer + + (LCD_Y_RES+U_LINES-1-i)*2; + } + + /* last descriptor points to first */ + dma_desc_table[2*(LCD_Y_RES+U_LINES-1)] = (unsigned long)&dma_desc_table[0]; + + set_dma_x_count(CH_PPI, LCD_X_RES); + set_dma_x_modify(CH_PPI, LCD_Y_RES * (LCD_BBP / 8)); + set_dma_y_count(CH_PPI, 0); + set_dma_y_modify(CH_PPI, 0); + set_dma_next_desc_addr(CH_PPI, (void *)dma_desc_table[0]); + set_dma_config(CH_PPI, DMAFLOW_LARGE | NDSIZE_4 | WDSIZE_16); + + } else { + + set_dma_config(CH_PPI, set_bfin_dma_config(DIR_READ, + DMA_FLOW_AUTO, + INTR_DISABLE, + DIMENSION_2D, + DATA_SIZE_16, + DMA_NOSYNC_KEEP_DMA_BUF)); + set_dma_x_count(CH_PPI, LCD_X_RES); + set_dma_x_modify(CH_PPI, LCD_BBP / 8); + set_dma_y_count(CH_PPI, LCD_Y_RES+U_LINES); + set_dma_y_modify(CH_PPI, LCD_BBP / 8); + set_dma_start_addr(CH_PPI, (unsigned long) fb_buffer); + } + + return 0; +} + +static int request_ports(void) +{ + u16 tmr_req[] = TIMERS; + + /* + UD: PF13 + MOD: PF10 + LBR: PF14 + PPI_CLK: PF15 + */ + + if (peripheral_request_list(ppi_pins, KBUILD_MODNAME)) { + pr_err("requesting PPI peripheral failed\n"); + return -EBUSY; + } + + if (peripheral_request_list(tmr_req, KBUILD_MODNAME)) { + peripheral_free_list(ppi_pins); + pr_err("requesting timer peripheral failed\n"); + return -EBUSY; + } + +#if (defined(UD) && defined(LBR)) + if (gpio_request_one(UD, GPIOF_OUT_INIT_LOW, KBUILD_MODNAME)) { + pr_err("requesting GPIO %d failed\n", UD); + return -EBUSY; + } + + if (gpio_request_one(LBR, GPIOF_OUT_INIT_HIGH, KBUILD_MODNAME)) { + pr_err("requesting GPIO %d failed\n", LBR); + gpio_free(UD); + return -EBUSY; + } +#endif + + if (gpio_request_one(MOD, GPIOF_OUT_INIT_HIGH, KBUILD_MODNAME)) { + pr_err("requesting GPIO %d failed\n", MOD); +#if (defined(UD) && defined(LBR)) + gpio_free(LBR); + gpio_free(UD); +#endif + return -EBUSY; + } + + SSYNC(); + return 0; +} + +static void free_ports(void) +{ + u16 tmr_req[] = TIMERS; + + peripheral_free_list(ppi_pins); + peripheral_free_list(tmr_req); + +#if defined(UD) && defined(LBR) + gpio_free(LBR); + gpio_free(UD); +#endif + gpio_free(MOD); +} + +static struct fb_info bfin_lq035_fb; + +static struct fb_var_screeninfo bfin_lq035_fb_defined = { + .bits_per_pixel = LCD_BBP, + .activate = FB_ACTIVATE_TEST, + .xres = LCD_X_RES, /*default portrait mode RGB*/ + .yres = LCD_Y_RES, + .xres_virtual = LCD_X_RES, + .yres_virtual = LCD_Y_RES, + .height = -1, + .width = -1, + .left_margin = 0, + .right_margin = 0, + .upper_margin = 0, + .lower_margin = 0, + .red = {11, 5, 0}, + .green = {5, 6, 0}, + .blue = {0, 5, 0}, + .transp = {0, 0, 0}, +}; + +static struct fb_fix_screeninfo bfin_lq035_fb_fix = { + .id = KBUILD_MODNAME, + .smem_len = ACTIVE_VIDEO_MEM_SIZE, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .line_length = LCD_X_RES*(LCD_BBP/8), + .accel = FB_ACCEL_NONE, +}; + + +static int bfin_lq035_fb_open(struct fb_info *info, int user) +{ + unsigned long flags; + + spin_lock_irqsave(&bfin_lq035_lock, flags); + lq035_open_cnt++; + spin_unlock_irqrestore(&bfin_lq035_lock, flags); + + if (lq035_open_cnt <= 1) { + bfin_write_PPI_CONTROL(0); + SSYNC(); + + set_vcomm(); + config_dma(); + config_ppi(); + + /* start dma */ + enable_dma(CH_PPI); + SSYNC(); + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() | PORT_EN); + SSYNC(); + + if (!t_conf_done) { + config_timers(); + start_timers(); + } + /* gpio_set_value(MOD,1); */ + } + + return 0; +} + +static int bfin_lq035_fb_release(struct fb_info *info, int user) +{ + unsigned long flags; + + spin_lock_irqsave(&bfin_lq035_lock, flags); + lq035_open_cnt--; + spin_unlock_irqrestore(&bfin_lq035_lock, flags); + + + if (lq035_open_cnt <= 0) { + + bfin_write_PPI_CONTROL(0); + SSYNC(); + + disable_dma(CH_PPI); + } + + return 0; +} + + +static int bfin_lq035_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + switch (var->bits_per_pixel) { + case 16:/* DIRECTCOLOUR, 64k */ + var->red.offset = info->var.red.offset; + var->green.offset = info->var.green.offset; + var->blue.offset = info->var.blue.offset; + var->red.length = info->var.red.length; + var->green.length = info->var.green.length; + var->blue.length = info->var.blue.length; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + break; + default: + pr_debug("%s: depth not supported: %u BPP\n", __func__, + var->bits_per_pixel); + return -EINVAL; + } + + if (info->var.xres != var->xres || + info->var.yres != var->yres || + info->var.xres_virtual != var->xres_virtual || + info->var.yres_virtual != var->yres_virtual) { + pr_debug("%s: Resolution not supported: X%u x Y%u\n", + __func__, var->xres, var->yres); + return -EINVAL; + } + + /* + * Memory limit + */ + + if ((info->fix.line_length * var->yres_virtual) > info->fix.smem_len) { + pr_debug("%s: Memory Limit requested yres_virtual = %u\n", + __func__, var->yres_virtual); + return -ENOMEM; + } + + return 0; +} + +/* fb_rotate + * Rotate the display of this angle. This doesn't seems to be used by the core, + * but as our hardware supports it, so why not implementing it... + */ +static void bfin_lq035_fb_rotate(struct fb_info *fbi, int angle) +{ + pr_debug("%s: %p %d", __func__, fbi, angle); +#if (defined(UD) && defined(LBR)) + switch (angle) { + + case 180: + gpio_set_value(LBR, 0); + gpio_set_value(UD, 1); + break; + default: + gpio_set_value(LBR, 1); + gpio_set_value(UD, 0); + break; + } +#endif +} + +static int bfin_lq035_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + if (nocursor) + return 0; + else + return -EINVAL; /* just to force soft_cursor() call */ +} + +static int bfin_lq035_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *info) +{ + if (regno >= NBR_PALETTE) + return -EINVAL; + + if (info->var.grayscale) + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + + u32 value; + /* Place color in the pseudopalette */ + if (regno > 16) + return -EINVAL; + + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + + value = (red << info->var.red.offset) | + (green << info->var.green.offset)| + (blue << info->var.blue.offset); + value &= 0xFFFF; + + ((u32 *) (info->pseudo_palette))[regno] = value; + + } + + return 0; +} + +static struct fb_ops bfin_lq035_fb_ops = { + .owner = THIS_MODULE, + .fb_open = bfin_lq035_fb_open, + .fb_release = bfin_lq035_fb_release, + .fb_check_var = bfin_lq035_fb_check_var, + .fb_rotate = bfin_lq035_fb_rotate, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = bfin_lq035_fb_cursor, + .fb_setcolreg = bfin_lq035_fb_setcolreg, +}; + +static int bl_get_brightness(struct backlight_device *bd) +{ + return current_brightness; +} + +static const struct backlight_ops bfin_lq035fb_bl_ops = { + .get_brightness = bl_get_brightness, +}; + +static struct backlight_device *bl_dev; + +static int bfin_lcd_get_power(struct lcd_device *dev) +{ + return 0; +} + +static int bfin_lcd_set_power(struct lcd_device *dev, int power) +{ + return 0; +} + +static int bfin_lcd_get_contrast(struct lcd_device *dev) +{ + return (int)vcomm_value; +} + +static int bfin_lcd_set_contrast(struct lcd_device *dev, int contrast) +{ + if (contrast > 255) + contrast = 255; + if (contrast < 0) + contrast = 0; + + vcomm_value = (unsigned char)contrast; + set_vcomm(); + return 0; +} + +static int bfin_lcd_check_fb(struct lcd_device *lcd, struct fb_info *fi) +{ + if (!fi || (fi == &bfin_lq035_fb)) + return 1; + return 0; +} + +static struct lcd_ops bfin_lcd_ops = { + .get_power = bfin_lcd_get_power, + .set_power = bfin_lcd_set_power, + .get_contrast = bfin_lcd_get_contrast, + .set_contrast = bfin_lcd_set_contrast, + .check_fb = bfin_lcd_check_fb, +}; + +static struct lcd_device *lcd_dev; + +static int bfin_lq035_probe(struct platform_device *pdev) +{ + struct backlight_properties props; + dma_addr_t dma_handle; + int ret; + + if (request_dma(CH_PPI, KBUILD_MODNAME)) { + pr_err("couldn't request PPI DMA\n"); + return -EFAULT; + } + + if (request_ports()) { + pr_err("couldn't request gpio port\n"); + ret = -EFAULT; + goto out_ports; + } + + fb_buffer = dma_alloc_coherent(NULL, TOTAL_VIDEO_MEM_SIZE, + &dma_handle, GFP_KERNEL); + if (fb_buffer == NULL) { + pr_err("couldn't allocate dma buffer\n"); + ret = -ENOMEM; + goto out_dma_coherent; + } + + if (L1_DATA_A_LENGTH) + dma_desc_table = l1_data_sram_zalloc(TOTAL_DMA_DESC_SIZE); + else + dma_desc_table = dma_alloc_coherent(NULL, TOTAL_DMA_DESC_SIZE, + &dma_handle, 0); + + if (dma_desc_table == NULL) { + pr_err("couldn't allocate dma descriptor\n"); + ret = -ENOMEM; + goto out_table; + } + + bfin_lq035_fb.screen_base = (void *)fb_buffer; + bfin_lq035_fb_fix.smem_start = (int)fb_buffer; + if (landscape) { + bfin_lq035_fb_defined.xres = LCD_Y_RES; + bfin_lq035_fb_defined.yres = LCD_X_RES; + bfin_lq035_fb_defined.xres_virtual = LCD_Y_RES; + bfin_lq035_fb_defined.yres_virtual = LCD_X_RES; + + bfin_lq035_fb_fix.line_length = LCD_Y_RES*(LCD_BBP/8); + } else { + bfin_lq035_fb.screen_base += ACTIVE_VIDEO_MEM_OFFSET; + bfin_lq035_fb_fix.smem_start += ACTIVE_VIDEO_MEM_OFFSET; + } + + bfin_lq035_fb_defined.green.msb_right = 0; + bfin_lq035_fb_defined.red.msb_right = 0; + bfin_lq035_fb_defined.blue.msb_right = 0; + bfin_lq035_fb_defined.green.offset = 5; + bfin_lq035_fb_defined.green.length = 6; + bfin_lq035_fb_defined.red.length = 5; + bfin_lq035_fb_defined.blue.length = 5; + + if (bgr) { + bfin_lq035_fb_defined.red.offset = 0; + bfin_lq035_fb_defined.blue.offset = 11; + } else { + bfin_lq035_fb_defined.red.offset = 11; + bfin_lq035_fb_defined.blue.offset = 0; + } + + bfin_lq035_fb.fbops = &bfin_lq035_fb_ops; + bfin_lq035_fb.var = bfin_lq035_fb_defined; + + bfin_lq035_fb.fix = bfin_lq035_fb_fix; + bfin_lq035_fb.flags = FBINFO_DEFAULT; + + + bfin_lq035_fb.pseudo_palette = devm_kzalloc(&pdev->dev, + sizeof(u32) * 16, + GFP_KERNEL); + if (bfin_lq035_fb.pseudo_palette == NULL) { + pr_err("failed to allocate pseudo_palette\n"); + ret = -ENOMEM; + goto out_table; + } + + if (fb_alloc_cmap(&bfin_lq035_fb.cmap, NBR_PALETTE, 0) < 0) { + pr_err("failed to allocate colormap (%d entries)\n", + NBR_PALETTE); + ret = -EFAULT; + goto out_table; + } + + if (register_framebuffer(&bfin_lq035_fb) < 0) { + pr_err("unable to register framebuffer\n"); + ret = -EINVAL; + goto out_reg; + } + + i2c_add_driver(&ad5280_driver); + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_RAW; + props.max_brightness = MAX_BRIGHENESS; + bl_dev = backlight_device_register("bf537-bl", NULL, NULL, + &bfin_lq035fb_bl_ops, &props); + + lcd_dev = lcd_device_register(KBUILD_MODNAME, &pdev->dev, NULL, + &bfin_lcd_ops); + if (IS_ERR(lcd_dev)) { + pr_err("unable to register lcd\n"); + ret = PTR_ERR(lcd_dev); + goto out_lcd; + } + lcd_dev->props.max_contrast = 255, + + pr_info("initialized"); + + return 0; +out_lcd: + unregister_framebuffer(&bfin_lq035_fb); +out_reg: + fb_dealloc_cmap(&bfin_lq035_fb.cmap); +out_table: + dma_free_coherent(NULL, TOTAL_VIDEO_MEM_SIZE, fb_buffer, 0); + fb_buffer = NULL; +out_dma_coherent: + free_ports(); +out_ports: + free_dma(CH_PPI); + return ret; +} + +static int bfin_lq035_remove(struct platform_device *pdev) +{ + if (fb_buffer != NULL) + dma_free_coherent(NULL, TOTAL_VIDEO_MEM_SIZE, fb_buffer, 0); + + if (L1_DATA_A_LENGTH) + l1_data_sram_free(dma_desc_table); + else + dma_free_coherent(NULL, TOTAL_DMA_DESC_SIZE, NULL, 0); + + bfin_write_TIMER_DISABLE(TIMEN_SP|TIMEN_SPS|TIMEN_PS_CLS| + TIMEN_LP|TIMEN_REV); + t_conf_done = 0; + + free_dma(CH_PPI); + + + fb_dealloc_cmap(&bfin_lq035_fb.cmap); + + + lcd_device_unregister(lcd_dev); + backlight_device_unregister(bl_dev); + + unregister_framebuffer(&bfin_lq035_fb); + i2c_del_driver(&ad5280_driver); + + free_ports(); + + pr_info("unregistered LCD driver\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_lq035_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (lq035_open_cnt > 0) { + bfin_write_PPI_CONTROL(0); + SSYNC(); + disable_dma(CH_PPI); + } + + return 0; +} + +static int bfin_lq035_resume(struct platform_device *pdev) +{ + if (lq035_open_cnt > 0) { + bfin_write_PPI_CONTROL(0); + SSYNC(); + + config_dma(); + config_ppi(); + + enable_dma(CH_PPI); + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() | PORT_EN); + SSYNC(); + + config_timers(); + start_timers(); + } else { + t_conf_done = 0; + } + + return 0; +} +#else +# define bfin_lq035_suspend NULL +# define bfin_lq035_resume NULL +#endif + +static struct platform_driver bfin_lq035_driver = { + .probe = bfin_lq035_probe, + .remove = bfin_lq035_remove, + .suspend = bfin_lq035_suspend, + .resume = bfin_lq035_resume, + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + }, +}; + +static int __init bfin_lq035_driver_init(void) +{ + request_module("i2c-bfin-twi"); + return platform_driver_register(&bfin_lq035_driver); +} +module_init(bfin_lq035_driver_init); + +static void __exit bfin_lq035_driver_cleanup(void) +{ + platform_driver_unregister(&bfin_lq035_driver); +} +module_exit(bfin_lq035_driver_cleanup); + +MODULE_DESCRIPTION("SHARP LQ035Q7DB03 TFT LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/bf54x-lq043fb.c b/drivers/video/fbdev/bf54x-lq043fb.c new file mode 100644 index 000000000000..e2c42ad8515a --- /dev/null +++ b/drivers/video/fbdev/bf54x-lq043fb.c @@ -0,0 +1,767 @@ +/* + * File: drivers/video/bf54x-lq043.c + * Based on: + * Author: Michael Hennerich <hennerich@blackfin.uclinux.org> + * + * Created: + * Description: ADSP-BF54x Framebuffer driver + * + * + * Modified: + * Copyright 2007-2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/device.h> +#include <linux/backlight.h> +#include <linux/lcd.h> +#include <linux/spinlock.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> + +#include <asm/blackfin.h> +#include <asm/irq.h> +#include <asm/dpmc.h> +#include <asm/dma-mapping.h> +#include <asm/dma.h> +#include <asm/portmux.h> + +#include <mach/bf54x-lq043.h> + +#define NO_BL_SUPPORT + +#define DRIVER_NAME "bf54x-lq043" +static char driver_name[] = DRIVER_NAME; + +#define BFIN_LCD_NBR_PALETTE_ENTRIES 256 + +#define EPPI0_18 {P_PPI0_CLK, P_PPI0_FS1, P_PPI0_FS2, P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, P_PPI0_D3, \ + P_PPI0_D4, P_PPI0_D5, P_PPI0_D6, P_PPI0_D7, P_PPI0_D8, P_PPI0_D9, P_PPI0_D10, \ + P_PPI0_D11, P_PPI0_D12, P_PPI0_D13, P_PPI0_D14, P_PPI0_D15, P_PPI0_D16, P_PPI0_D17, 0} + +#define EPPI0_24 {P_PPI0_D18, P_PPI0_D19, P_PPI0_D20, P_PPI0_D21, P_PPI0_D22, P_PPI0_D23, 0} + +struct bfin_bf54xfb_info { + struct fb_info *fb; + struct device *dev; + + struct bfin_bf54xfb_mach_info *mach_info; + + unsigned char *fb_buffer; /* RGB Buffer */ + + dma_addr_t dma_handle; + int lq043_open_cnt; + int irq; + spinlock_t lock; /* lock */ +}; + +static int nocursor; +module_param(nocursor, int, 0644); +MODULE_PARM_DESC(nocursor, "cursor enable/disable"); + +static int outp_rgb666; +module_param(outp_rgb666, int, 0); +MODULE_PARM_DESC(outp_rgb666, "Output 18-bit RGB666"); + +#define LCD_X_RES 480 /*Horizontal Resolution */ +#define LCD_Y_RES 272 /* Vertical Resolution */ + +#define LCD_BPP 24 /* Bit Per Pixel */ +#define DMA_BUS_SIZE 32 + +/* -- Horizontal synchronizing -- + * + * Timing characteristics taken from the SHARP LQ043T1DG01 datasheet + * (LCY-W-06602A Page 9 of 22) + * + * Clock Frequency 1/Tc Min 7.83 Typ 9.00 Max 9.26 MHz + * + * Period TH - 525 - Clock + * Pulse width THp - 41 - Clock + * Horizontal period THd - 480 - Clock + * Back porch THb - 2 - Clock + * Front porch THf - 2 - Clock + * + * -- Vertical synchronizing -- + * Period TV - 286 - Line + * Pulse width TVp - 10 - Line + * Vertical period TVd - 272 - Line + * Back porch TVb - 2 - Line + * Front porch TVf - 2 - Line + */ + +#define LCD_CLK (8*1000*1000) /* 8MHz */ + +/* # active data to transfer after Horizontal Delay clock */ +#define EPPI_HCOUNT LCD_X_RES + +/* # active lines to transfer after Vertical Delay clock */ +#define EPPI_VCOUNT LCD_Y_RES + +/* Samples per Line = 480 (active data) + 45 (padding) */ +#define EPPI_LINE 525 + +/* Lines per Frame = 272 (active data) + 14 (padding) */ +#define EPPI_FRAME 286 + +/* FS1 (Hsync) Width (Typical)*/ +#define EPPI_FS1W_HBL 41 + +/* FS1 (Hsync) Period (Typical) */ +#define EPPI_FS1P_AVPL EPPI_LINE + +/* Horizontal Delay clock after assertion of Hsync (Typical) */ +#define EPPI_HDELAY 43 + +/* FS2 (Vsync) Width = FS1 (Hsync) Period * 10 */ +#define EPPI_FS2W_LVB (EPPI_LINE * 10) + + /* FS2 (Vsync) Period = FS1 (Hsync) Period * Lines per Frame */ +#define EPPI_FS2P_LAVF (EPPI_LINE * EPPI_FRAME) + +/* Vertical Delay after assertion of Vsync (2 Lines) */ +#define EPPI_VDELAY 12 + +#define EPPI_CLIP 0xFF00FF00 + +/* EPPI Control register configuration value for RGB out + * - EPPI as Output + * GP 2 frame sync mode, + * Internal Clock generation disabled, Internal FS generation enabled, + * Receives samples on EPPI_CLK raising edge, Transmits samples on EPPI_CLK falling edge, + * FS1 & FS2 are active high, + * DLEN = 6 (24 bits for RGB888 out) or 5 (18 bits for RGB666 out) + * DMA Unpacking disabled when RGB Formating is enabled, otherwise DMA unpacking enabled + * Swapping Enabled, + * One (DMA) Channel Mode, + * RGB Formatting Enabled for RGB666 output, disabled for RGB888 output + * Regular watermark - when FIFO is 100% full, + * Urgent watermark - when FIFO is 75% full + */ + +#define EPPI_CONTROL (0x20136E2E | SWAPEN) + +static inline u16 get_eppi_clkdiv(u32 target_ppi_clk) +{ + u32 sclk = get_sclk(); + + /* EPPI_CLK = (SCLK) / (2 * (EPPI_CLKDIV[15:0] + 1)) */ + + return (((sclk / target_ppi_clk) / 2) - 1); +} + +static void config_ppi(struct bfin_bf54xfb_info *fbi) +{ + + u16 eppi_clkdiv = get_eppi_clkdiv(LCD_CLK); + + bfin_write_EPPI0_FS1W_HBL(EPPI_FS1W_HBL); + bfin_write_EPPI0_FS1P_AVPL(EPPI_FS1P_AVPL); + bfin_write_EPPI0_FS2W_LVB(EPPI_FS2W_LVB); + bfin_write_EPPI0_FS2P_LAVF(EPPI_FS2P_LAVF); + bfin_write_EPPI0_CLIP(EPPI_CLIP); + + bfin_write_EPPI0_FRAME(EPPI_FRAME); + bfin_write_EPPI0_LINE(EPPI_LINE); + + bfin_write_EPPI0_HCOUNT(EPPI_HCOUNT); + bfin_write_EPPI0_HDELAY(EPPI_HDELAY); + bfin_write_EPPI0_VCOUNT(EPPI_VCOUNT); + bfin_write_EPPI0_VDELAY(EPPI_VDELAY); + + bfin_write_EPPI0_CLKDIV(eppi_clkdiv); + +/* + * DLEN = 6 (24 bits for RGB888 out) or 5 (18 bits for RGB666 out) + * RGB Formatting Enabled for RGB666 output, disabled for RGB888 output + */ + if (outp_rgb666) + bfin_write_EPPI0_CONTROL((EPPI_CONTROL & ~DLENGTH) | DLEN_18 | + RGB_FMT_EN); + else + bfin_write_EPPI0_CONTROL(((EPPI_CONTROL & ~DLENGTH) | DLEN_24) & + ~RGB_FMT_EN); + + +} + +static int config_dma(struct bfin_bf54xfb_info *fbi) +{ + + set_dma_config(CH_EPPI0, + set_bfin_dma_config(DIR_READ, DMA_FLOW_AUTO, + INTR_DISABLE, DIMENSION_2D, + DATA_SIZE_32, + DMA_NOSYNC_KEEP_DMA_BUF)); + set_dma_x_count(CH_EPPI0, (LCD_X_RES * LCD_BPP) / DMA_BUS_SIZE); + set_dma_x_modify(CH_EPPI0, DMA_BUS_SIZE / 8); + set_dma_y_count(CH_EPPI0, LCD_Y_RES); + set_dma_y_modify(CH_EPPI0, DMA_BUS_SIZE / 8); + set_dma_start_addr(CH_EPPI0, (unsigned long)fbi->fb_buffer); + + return 0; +} + +static int request_ports(struct bfin_bf54xfb_info *fbi) +{ + + u16 eppi_req_18[] = EPPI0_18; + u16 disp = fbi->mach_info->disp; + + if (gpio_request_one(disp, GPIOF_OUT_INIT_HIGH, DRIVER_NAME)) { + printk(KERN_ERR "Requesting GPIO %d failed\n", disp); + return -EFAULT; + } + + if (peripheral_request_list(eppi_req_18, DRIVER_NAME)) { + printk(KERN_ERR "Requesting Peripherals failed\n"); + gpio_free(disp); + return -EFAULT; + } + + if (!outp_rgb666) { + + u16 eppi_req_24[] = EPPI0_24; + + if (peripheral_request_list(eppi_req_24, DRIVER_NAME)) { + printk(KERN_ERR "Requesting Peripherals failed\n"); + peripheral_free_list(eppi_req_18); + gpio_free(disp); + return -EFAULT; + } + } + + return 0; +} + +static void free_ports(struct bfin_bf54xfb_info *fbi) +{ + + u16 eppi_req_18[] = EPPI0_18; + + gpio_free(fbi->mach_info->disp); + + peripheral_free_list(eppi_req_18); + + if (!outp_rgb666) { + u16 eppi_req_24[] = EPPI0_24; + peripheral_free_list(eppi_req_24); + } +} + +static int bfin_bf54x_fb_open(struct fb_info *info, int user) +{ + struct bfin_bf54xfb_info *fbi = info->par; + + spin_lock(&fbi->lock); + fbi->lq043_open_cnt++; + + if (fbi->lq043_open_cnt <= 1) { + + bfin_write_EPPI0_CONTROL(0); + SSYNC(); + + config_dma(fbi); + config_ppi(fbi); + + /* start dma */ + enable_dma(CH_EPPI0); + bfin_write_EPPI0_CONTROL(bfin_read_EPPI0_CONTROL() | EPPI_EN); + } + + spin_unlock(&fbi->lock); + + return 0; +} + +static int bfin_bf54x_fb_release(struct fb_info *info, int user) +{ + struct bfin_bf54xfb_info *fbi = info->par; + + spin_lock(&fbi->lock); + + fbi->lq043_open_cnt--; + + if (fbi->lq043_open_cnt <= 0) { + + bfin_write_EPPI0_CONTROL(0); + SSYNC(); + disable_dma(CH_EPPI0); + } + + spin_unlock(&fbi->lock); + + return 0; +} + +static int bfin_bf54x_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + + switch (var->bits_per_pixel) { + case 24:/* TRUECOLOUR, 16m */ + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + break; + default: + pr_debug("%s: depth not supported: %u BPP\n", __func__, + var->bits_per_pixel); + return -EINVAL; + } + + if (info->var.xres != var->xres || info->var.yres != var->yres || + info->var.xres_virtual != var->xres_virtual || + info->var.yres_virtual != var->yres_virtual) { + pr_debug("%s: Resolution not supported: X%u x Y%u \n", + __func__, var->xres, var->yres); + return -EINVAL; + } + + /* + * Memory limit + */ + + if ((info->fix.line_length * var->yres_virtual) > info->fix.smem_len) { + pr_debug("%s: Memory Limit requested yres_virtual = %u\n", + __func__, var->yres_virtual); + return -ENOMEM; + } + + return 0; +} + +int bfin_bf54x_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + if (nocursor) + return 0; + else + return -EINVAL; /* just to force soft_cursor() call */ +} + +static int bfin_bf54x_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *info) +{ + if (regno >= BFIN_LCD_NBR_PALETTE_ENTRIES) + return -EINVAL; + + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + + u32 value; + /* Place color in the pseudopalette */ + if (regno > 16) + return -EINVAL; + + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + + value = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + value &= 0xFFFFFF; + + ((u32 *) (info->pseudo_palette))[regno] = value; + + } + + return 0; +} + +static struct fb_ops bfin_bf54x_fb_ops = { + .owner = THIS_MODULE, + .fb_open = bfin_bf54x_fb_open, + .fb_release = bfin_bf54x_fb_release, + .fb_check_var = bfin_bf54x_fb_check_var, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = bfin_bf54x_fb_cursor, + .fb_setcolreg = bfin_bf54x_fb_setcolreg, +}; + +#ifndef NO_BL_SUPPORT +static int bl_get_brightness(struct backlight_device *bd) +{ + return 0; +} + +static const struct backlight_ops bfin_lq043fb_bl_ops = { + .get_brightness = bl_get_brightness, +}; + +static struct backlight_device *bl_dev; + +static int bfin_lcd_get_power(struct lcd_device *dev) +{ + return 0; +} + +static int bfin_lcd_set_power(struct lcd_device *dev, int power) +{ + return 0; +} + +static int bfin_lcd_get_contrast(struct lcd_device *dev) +{ + return 0; +} + +static int bfin_lcd_set_contrast(struct lcd_device *dev, int contrast) +{ + + return 0; +} + +static int bfin_lcd_check_fb(struct lcd_device *dev, struct fb_info *fi) +{ + if (!fi || (fi == &bfin_bf54x_fb)) + return 1; + return 0; +} + +static struct lcd_ops bfin_lcd_ops = { + .get_power = bfin_lcd_get_power, + .set_power = bfin_lcd_set_power, + .get_contrast = bfin_lcd_get_contrast, + .set_contrast = bfin_lcd_set_contrast, + .check_fb = bfin_lcd_check_fb, +}; + +static struct lcd_device *lcd_dev; +#endif + +static irqreturn_t bfin_bf54x_irq_error(int irq, void *dev_id) +{ + /*struct bfin_bf54xfb_info *info = dev_id;*/ + + u16 status = bfin_read_EPPI0_STATUS(); + + bfin_write_EPPI0_STATUS(0xFFFF); + + if (status) { + bfin_write_EPPI0_CONTROL(bfin_read_EPPI0_CONTROL() & ~EPPI_EN); + disable_dma(CH_EPPI0); + + /* start dma */ + enable_dma(CH_EPPI0); + bfin_write_EPPI0_CONTROL(bfin_read_EPPI0_CONTROL() | EPPI_EN); + bfin_write_EPPI0_STATUS(0xFFFF); + } + + return IRQ_HANDLED; +} + +static int bfin_bf54x_probe(struct platform_device *pdev) +{ +#ifndef NO_BL_SUPPORT + struct backlight_properties props; +#endif + struct bfin_bf54xfb_info *info; + struct fb_info *fbinfo; + int ret; + + printk(KERN_INFO DRIVER_NAME ": FrameBuffer initializing...\n"); + + if (request_dma(CH_EPPI0, "CH_EPPI0") < 0) { + printk(KERN_ERR DRIVER_NAME + ": couldn't request CH_EPPI0 DMA\n"); + ret = -EFAULT; + goto out1; + } + + fbinfo = + framebuffer_alloc(sizeof(struct bfin_bf54xfb_info), &pdev->dev); + if (!fbinfo) { + ret = -ENOMEM; + goto out2; + } + + info = fbinfo->par; + info->fb = fbinfo; + info->dev = &pdev->dev; + spin_lock_init(&info->lock); + + platform_set_drvdata(pdev, fbinfo); + + strcpy(fbinfo->fix.id, driver_name); + + info->mach_info = pdev->dev.platform_data; + + if (info->mach_info == NULL) { + dev_err(&pdev->dev, + "no platform data for lcd, cannot attach\n"); + ret = -EINVAL; + goto out3; + } + + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_NONE; + fbinfo->fix.visual = FB_VISUAL_TRUECOLOR; + + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.height = info->mach_info->height; + fbinfo->var.width = info->mach_info->width; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + fbinfo->fbops = &bfin_bf54x_fb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + + fbinfo->var.xres = info->mach_info->xres.defval; + fbinfo->var.xres_virtual = info->mach_info->xres.defval; + fbinfo->var.yres = info->mach_info->yres.defval; + fbinfo->var.yres_virtual = info->mach_info->yres.defval; + fbinfo->var.bits_per_pixel = info->mach_info->bpp.defval; + + fbinfo->var.upper_margin = 0; + fbinfo->var.lower_margin = 0; + fbinfo->var.vsync_len = 0; + + fbinfo->var.left_margin = 0; + fbinfo->var.right_margin = 0; + fbinfo->var.hsync_len = 0; + + fbinfo->var.red.offset = 16; + fbinfo->var.green.offset = 8; + fbinfo->var.blue.offset = 0; + fbinfo->var.transp.offset = 0; + fbinfo->var.red.length = 8; + fbinfo->var.green.length = 8; + fbinfo->var.blue.length = 8; + fbinfo->var.transp.length = 0; + fbinfo->fix.smem_len = info->mach_info->xres.max * + info->mach_info->yres.max * info->mach_info->bpp.max / 8; + + fbinfo->fix.line_length = fbinfo->var.xres_virtual * + fbinfo->var.bits_per_pixel / 8; + + info->fb_buffer = + dma_alloc_coherent(NULL, fbinfo->fix.smem_len, &info->dma_handle, + GFP_KERNEL); + + if (NULL == info->fb_buffer) { + printk(KERN_ERR DRIVER_NAME + ": couldn't allocate dma buffer.\n"); + ret = -ENOMEM; + goto out3; + } + + fbinfo->screen_base = (void *)info->fb_buffer; + fbinfo->fix.smem_start = (int)info->fb_buffer; + + fbinfo->fbops = &bfin_bf54x_fb_ops; + + fbinfo->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, + GFP_KERNEL); + if (!fbinfo->pseudo_palette) { + printk(KERN_ERR DRIVER_NAME + "Fail to allocate pseudo_palette\n"); + + ret = -ENOMEM; + goto out4; + } + + if (fb_alloc_cmap(&fbinfo->cmap, BFIN_LCD_NBR_PALETTE_ENTRIES, 0) + < 0) { + printk(KERN_ERR DRIVER_NAME + "Fail to allocate colormap (%d entries)\n", + BFIN_LCD_NBR_PALETTE_ENTRIES); + ret = -EFAULT; + goto out4; + } + + if (request_ports(info)) { + printk(KERN_ERR DRIVER_NAME ": couldn't request gpio port.\n"); + ret = -EFAULT; + goto out6; + } + + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + ret = -EINVAL; + goto out7; + } + + if (request_irq(info->irq, bfin_bf54x_irq_error, 0, + "PPI ERROR", info) < 0) { + printk(KERN_ERR DRIVER_NAME + ": unable to request PPI ERROR IRQ\n"); + ret = -EFAULT; + goto out7; + } + + if (register_framebuffer(fbinfo) < 0) { + printk(KERN_ERR DRIVER_NAME + ": unable to register framebuffer.\n"); + ret = -EINVAL; + goto out8; + } +#ifndef NO_BL_SUPPORT + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 255; + bl_dev = backlight_device_register("bf54x-bl", NULL, NULL, + &bfin_lq043fb_bl_ops, &props); + if (IS_ERR(bl_dev)) { + printk(KERN_ERR DRIVER_NAME + ": unable to register backlight.\n"); + ret = -EINVAL; + unregister_framebuffer(fbinfo); + goto out8; + } + + lcd_dev = lcd_device_register(DRIVER_NAME, &pdev->dev, NULL, &bfin_lcd_ops); + lcd_dev->props.max_contrast = 255, printk(KERN_INFO "Done.\n"); +#endif + + return 0; + +out8: + free_irq(info->irq, info); +out7: + free_ports(info); +out6: + fb_dealloc_cmap(&fbinfo->cmap); +out4: + dma_free_coherent(NULL, fbinfo->fix.smem_len, info->fb_buffer, + info->dma_handle); +out3: + framebuffer_release(fbinfo); +out2: + free_dma(CH_EPPI0); +out1: + + return ret; +} + +static int bfin_bf54x_remove(struct platform_device *pdev) +{ + + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct bfin_bf54xfb_info *info = fbinfo->par; + + free_dma(CH_EPPI0); + free_irq(info->irq, info); + + if (info->fb_buffer != NULL) + dma_free_coherent(NULL, fbinfo->fix.smem_len, info->fb_buffer, + info->dma_handle); + + fb_dealloc_cmap(&fbinfo->cmap); + +#ifndef NO_BL_SUPPORT + lcd_device_unregister(lcd_dev); + backlight_device_unregister(bl_dev); +#endif + + unregister_framebuffer(fbinfo); + + free_ports(info); + + printk(KERN_INFO DRIVER_NAME ": Unregister LCD driver.\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_bf54x_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbinfo = platform_get_drvdata(pdev); + + bfin_write_EPPI0_CONTROL(bfin_read_EPPI0_CONTROL() & ~EPPI_EN); + disable_dma(CH_EPPI0); + bfin_write_EPPI0_STATUS(0xFFFF); + + return 0; +} + +static int bfin_bf54x_resume(struct platform_device *pdev) +{ + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct bfin_bf54xfb_info *info = fbinfo->par; + + if (info->lq043_open_cnt) { + + bfin_write_EPPI0_CONTROL(0); + SSYNC(); + + config_dma(info); + config_ppi(info); + + /* start dma */ + enable_dma(CH_EPPI0); + bfin_write_EPPI0_CONTROL(bfin_read_EPPI0_CONTROL() | EPPI_EN); + } + + return 0; +} +#else +#define bfin_bf54x_suspend NULL +#define bfin_bf54x_resume NULL +#endif + +static struct platform_driver bfin_bf54x_driver = { + .probe = bfin_bf54x_probe, + .remove = bfin_bf54x_remove, + .suspend = bfin_bf54x_suspend, + .resume = bfin_bf54x_resume, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; +module_platform_driver(bfin_bf54x_driver); + +MODULE_DESCRIPTION("Blackfin BF54x TFT LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/bfin-lq035q1-fb.c b/drivers/video/fbdev/bfin-lq035q1-fb.c new file mode 100644 index 000000000000..b594a58ff21d --- /dev/null +++ b/drivers/video/fbdev/bfin-lq035q1-fb.c @@ -0,0 +1,864 @@ +/* + * Blackfin LCD Framebuffer driver SHARP LQ035Q1DH02 + * + * Copyright 2008-2009 Analog Devices Inc. + * Licensed under the GPL-2 or later. + */ + +#define DRIVER_NAME "bfin-lq035q1" +#define pr_fmt(fmt) DRIVER_NAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/backlight.h> +#include <linux/lcd.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> + +#include <asm/blackfin.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/portmux.h> +#include <asm/gptimers.h> + +#include <asm/bfin-lq035q1.h> + +#if defined(BF533_FAMILY) || defined(BF538_FAMILY) +#define TIMER_HSYNC_id TIMER1_id +#define TIMER_HSYNCbit TIMER1bit +#define TIMER_HSYNC_STATUS_TRUN TIMER_STATUS_TRUN1 +#define TIMER_HSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL1 +#define TIMER_HSYNC_STATUS_TOVF TIMER_STATUS_TOVF1 + +#define TIMER_VSYNC_id TIMER2_id +#define TIMER_VSYNCbit TIMER2bit +#define TIMER_VSYNC_STATUS_TRUN TIMER_STATUS_TRUN2 +#define TIMER_VSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL2 +#define TIMER_VSYNC_STATUS_TOVF TIMER_STATUS_TOVF2 +#else +#define TIMER_HSYNC_id TIMER0_id +#define TIMER_HSYNCbit TIMER0bit +#define TIMER_HSYNC_STATUS_TRUN TIMER_STATUS_TRUN0 +#define TIMER_HSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL0 +#define TIMER_HSYNC_STATUS_TOVF TIMER_STATUS_TOVF0 + +#define TIMER_VSYNC_id TIMER1_id +#define TIMER_VSYNCbit TIMER1bit +#define TIMER_VSYNC_STATUS_TRUN TIMER_STATUS_TRUN1 +#define TIMER_VSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL1 +#define TIMER_VSYNC_STATUS_TOVF TIMER_STATUS_TOVF1 +#endif + +#define LCD_X_RES 320 /* Horizontal Resolution */ +#define LCD_Y_RES 240 /* Vertical Resolution */ +#define DMA_BUS_SIZE 16 +#define U_LINE 4 /* Blanking Lines */ + + +/* Interface 16/18-bit TFT over an 8-bit wide PPI using a small Programmable Logic Device (CPLD) + * http://blackfin.uclinux.org/gf/project/stamp/frs/?action=FrsReleaseBrowse&frs_package_id=165 + */ + + +#define BFIN_LCD_NBR_PALETTE_ENTRIES 256 + +#define PPI_TX_MODE 0x2 +#define PPI_XFER_TYPE_11 0xC +#define PPI_PORT_CFG_01 0x10 +#define PPI_POLS_1 0x8000 + +#define LQ035_INDEX 0x74 +#define LQ035_DATA 0x76 + +#define LQ035_DRIVER_OUTPUT_CTL 0x1 +#define LQ035_SHUT_CTL 0x11 + +#define LQ035_DRIVER_OUTPUT_MASK (LQ035_LR | LQ035_TB | LQ035_BGR | LQ035_REV) +#define LQ035_DRIVER_OUTPUT_DEFAULT (0x2AEF & ~LQ035_DRIVER_OUTPUT_MASK) + +#define LQ035_SHUT (1 << 0) /* Shutdown */ +#define LQ035_ON (0 << 0) /* Shutdown */ + +struct bfin_lq035q1fb_info { + struct fb_info *fb; + struct device *dev; + struct spi_driver spidrv; + struct bfin_lq035q1fb_disp_info *disp_info; + unsigned char *fb_buffer; /* RGB Buffer */ + dma_addr_t dma_handle; + int lq035_open_cnt; + int irq; + spinlock_t lock; /* lock */ + u32 pseudo_pal[16]; + + u32 lcd_bpp; + u32 h_actpix; + u32 h_period; + u32 h_pulse; + u32 h_start; + u32 v_lines; + u32 v_pulse; + u32 v_period; +}; + +static int nocursor; +module_param(nocursor, int, 0644); +MODULE_PARM_DESC(nocursor, "cursor enable/disable"); + +struct spi_control { + unsigned short mode; +}; + +static int lq035q1_control(struct spi_device *spi, unsigned char reg, unsigned short value) +{ + int ret; + u8 regs[3] = { LQ035_INDEX, 0, 0 }; + u8 dat[3] = { LQ035_DATA, 0, 0 }; + + if (!spi) + return -ENODEV; + + regs[2] = reg; + dat[1] = value >> 8; + dat[2] = value & 0xFF; + + ret = spi_write(spi, regs, ARRAY_SIZE(regs)); + ret |= spi_write(spi, dat, ARRAY_SIZE(dat)); + return ret; +} + +static int lq035q1_spidev_probe(struct spi_device *spi) +{ + int ret; + struct spi_control *ctl; + struct bfin_lq035q1fb_info *info = container_of(spi->dev.driver, + struct bfin_lq035q1fb_info, + spidrv.driver); + + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + + if (!ctl) + return -ENOMEM; + + ctl->mode = (info->disp_info->mode & + LQ035_DRIVER_OUTPUT_MASK) | LQ035_DRIVER_OUTPUT_DEFAULT; + + ret = lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_ON); + ret |= lq035q1_control(spi, LQ035_DRIVER_OUTPUT_CTL, ctl->mode); + if (ret) { + kfree(ctl); + return ret; + } + + spi_set_drvdata(spi, ctl); + + return 0; +} + +static int lq035q1_spidev_remove(struct spi_device *spi) +{ + return lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_SHUT); +} + +#ifdef CONFIG_PM_SLEEP +static int lq035q1_spidev_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + return lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_SHUT); +} + +static int lq035q1_spidev_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_control *ctl = spi_get_drvdata(spi); + int ret; + + ret = lq035q1_control(spi, LQ035_DRIVER_OUTPUT_CTL, ctl->mode); + if (ret) + return ret; + + return lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_ON); +} + +static SIMPLE_DEV_PM_OPS(lq035q1_spidev_pm_ops, lq035q1_spidev_suspend, + lq035q1_spidev_resume); +#define LQ035Q1_SPIDEV_PM_OPS (&lq035q1_spidev_pm_ops) + +#else +#define LQ035Q1_SPIDEV_PM_OPS NULL +#endif + +/* Power down all displays on reboot, poweroff or halt */ +static void lq035q1_spidev_shutdown(struct spi_device *spi) +{ + lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_SHUT); +} + +static int lq035q1_backlight(struct bfin_lq035q1fb_info *info, unsigned arg) +{ + if (info->disp_info->use_bl) + gpio_set_value(info->disp_info->gpio_bl, arg); + + return 0; +} + +static int bfin_lq035q1_calc_timing(struct bfin_lq035q1fb_info *fbi) +{ + unsigned long clocks_per_pix, cpld_pipeline_delay_cor; + + /* + * Interface 16/18-bit TFT over an 8-bit wide PPI using a small + * Programmable Logic Device (CPLD) + * http://blackfin.uclinux.org/gf/project/stamp/frs/?action=FrsReleaseBrowse&frs_package_id=165 + */ + + switch (fbi->disp_info->ppi_mode) { + case USE_RGB565_16_BIT_PPI: + fbi->lcd_bpp = 16; + clocks_per_pix = 1; + cpld_pipeline_delay_cor = 0; + break; + case USE_RGB565_8_BIT_PPI: + fbi->lcd_bpp = 16; + clocks_per_pix = 2; + cpld_pipeline_delay_cor = 3; + break; + case USE_RGB888_8_BIT_PPI: + fbi->lcd_bpp = 24; + clocks_per_pix = 3; + cpld_pipeline_delay_cor = 5; + break; + default: + return -EINVAL; + } + + /* + * HS and VS timing parameters (all in number of PPI clk ticks) + */ + + fbi->h_actpix = (LCD_X_RES * clocks_per_pix); /* active horizontal pixel */ + fbi->h_period = (336 * clocks_per_pix); /* HS period */ + fbi->h_pulse = (2 * clocks_per_pix); /* HS pulse width */ + fbi->h_start = (7 * clocks_per_pix + cpld_pipeline_delay_cor); /* first valid pixel */ + + fbi->v_lines = (LCD_Y_RES + U_LINE); /* total vertical lines */ + fbi->v_pulse = (2 * clocks_per_pix); /* VS pulse width (1-5 H_PERIODs) */ + fbi->v_period = (fbi->h_period * fbi->v_lines); /* VS period */ + + return 0; +} + +static void bfin_lq035q1_config_ppi(struct bfin_lq035q1fb_info *fbi) +{ + unsigned ppi_pmode; + + if (fbi->disp_info->ppi_mode == USE_RGB565_16_BIT_PPI) + ppi_pmode = DLEN_16; + else + ppi_pmode = (DLEN_8 | PACK_EN); + + bfin_write_PPI_DELAY(fbi->h_start); + bfin_write_PPI_COUNT(fbi->h_actpix - 1); + bfin_write_PPI_FRAME(fbi->v_lines); + + bfin_write_PPI_CONTROL(PPI_TX_MODE | /* output mode , PORT_DIR */ + PPI_XFER_TYPE_11 | /* sync mode XFR_TYPE */ + PPI_PORT_CFG_01 | /* two frame sync PORT_CFG */ + ppi_pmode | /* 8/16 bit data length / PACK_EN? */ + PPI_POLS_1); /* faling edge syncs POLS */ +} + +static inline void bfin_lq035q1_disable_ppi(void) +{ + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() & ~PORT_EN); +} + +static inline void bfin_lq035q1_enable_ppi(void) +{ + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() | PORT_EN); +} + +static void bfin_lq035q1_start_timers(void) +{ + enable_gptimers(TIMER_VSYNCbit | TIMER_HSYNCbit); +} + +static void bfin_lq035q1_stop_timers(void) +{ + disable_gptimers(TIMER_HSYNCbit | TIMER_VSYNCbit); + + set_gptimer_status(0, TIMER_HSYNC_STATUS_TRUN | TIMER_VSYNC_STATUS_TRUN | + TIMER_HSYNC_STATUS_TIMIL | TIMER_VSYNC_STATUS_TIMIL | + TIMER_HSYNC_STATUS_TOVF | TIMER_VSYNC_STATUS_TOVF); + +} + +static void bfin_lq035q1_init_timers(struct bfin_lq035q1fb_info *fbi) +{ + + bfin_lq035q1_stop_timers(); + + set_gptimer_period(TIMER_HSYNC_id, fbi->h_period); + set_gptimer_pwidth(TIMER_HSYNC_id, fbi->h_pulse); + set_gptimer_config(TIMER_HSYNC_id, TIMER_MODE_PWM | TIMER_PERIOD_CNT | + TIMER_TIN_SEL | TIMER_CLK_SEL| + TIMER_EMU_RUN); + + set_gptimer_period(TIMER_VSYNC_id, fbi->v_period); + set_gptimer_pwidth(TIMER_VSYNC_id, fbi->v_pulse); + set_gptimer_config(TIMER_VSYNC_id, TIMER_MODE_PWM | TIMER_PERIOD_CNT | + TIMER_TIN_SEL | TIMER_CLK_SEL | + TIMER_EMU_RUN); + +} + +static void bfin_lq035q1_config_dma(struct bfin_lq035q1fb_info *fbi) +{ + + + set_dma_config(CH_PPI, + set_bfin_dma_config(DIR_READ, DMA_FLOW_AUTO, + INTR_DISABLE, DIMENSION_2D, + DATA_SIZE_16, + DMA_NOSYNC_KEEP_DMA_BUF)); + set_dma_x_count(CH_PPI, (LCD_X_RES * fbi->lcd_bpp) / DMA_BUS_SIZE); + set_dma_x_modify(CH_PPI, DMA_BUS_SIZE / 8); + set_dma_y_count(CH_PPI, fbi->v_lines); + + set_dma_y_modify(CH_PPI, DMA_BUS_SIZE / 8); + set_dma_start_addr(CH_PPI, (unsigned long)fbi->fb_buffer); + +} + +static const u16 ppi0_req_16[] = {P_PPI0_CLK, P_PPI0_FS1, P_PPI0_FS2, + P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, + P_PPI0_D3, P_PPI0_D4, P_PPI0_D5, + P_PPI0_D6, P_PPI0_D7, P_PPI0_D8, + P_PPI0_D9, P_PPI0_D10, P_PPI0_D11, + P_PPI0_D12, P_PPI0_D13, P_PPI0_D14, + P_PPI0_D15, 0}; + +static const u16 ppi0_req_8[] = {P_PPI0_CLK, P_PPI0_FS1, P_PPI0_FS2, + P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, + P_PPI0_D3, P_PPI0_D4, P_PPI0_D5, + P_PPI0_D6, P_PPI0_D7, 0}; + +static inline void bfin_lq035q1_free_ports(unsigned ppi16) +{ + if (ppi16) + peripheral_free_list(ppi0_req_16); + else + peripheral_free_list(ppi0_req_8); + + if (ANOMALY_05000400) + gpio_free(P_IDENT(P_PPI0_FS3)); +} + +static int bfin_lq035q1_request_ports(struct platform_device *pdev, + unsigned ppi16) +{ + int ret; + /* ANOMALY_05000400 - PPI Does Not Start Properly In Specific Mode: + * Drive PPI_FS3 Low + */ + if (ANOMALY_05000400) { + int ret = gpio_request_one(P_IDENT(P_PPI0_FS3), + GPIOF_OUT_INIT_LOW, "PPI_FS3"); + if (ret) + return ret; + } + + if (ppi16) + ret = peripheral_request_list(ppi0_req_16, DRIVER_NAME); + else + ret = peripheral_request_list(ppi0_req_8, DRIVER_NAME); + + if (ret) { + dev_err(&pdev->dev, "requesting peripherals failed\n"); + return -EFAULT; + } + + return 0; +} + +static int bfin_lq035q1_fb_open(struct fb_info *info, int user) +{ + struct bfin_lq035q1fb_info *fbi = info->par; + + spin_lock(&fbi->lock); + fbi->lq035_open_cnt++; + + if (fbi->lq035_open_cnt <= 1) { + + bfin_lq035q1_disable_ppi(); + SSYNC(); + + bfin_lq035q1_config_dma(fbi); + bfin_lq035q1_config_ppi(fbi); + bfin_lq035q1_init_timers(fbi); + + /* start dma */ + enable_dma(CH_PPI); + bfin_lq035q1_enable_ppi(); + bfin_lq035q1_start_timers(); + lq035q1_backlight(fbi, 1); + } + + spin_unlock(&fbi->lock); + + return 0; +} + +static int bfin_lq035q1_fb_release(struct fb_info *info, int user) +{ + struct bfin_lq035q1fb_info *fbi = info->par; + + spin_lock(&fbi->lock); + + fbi->lq035_open_cnt--; + + if (fbi->lq035_open_cnt <= 0) { + lq035q1_backlight(fbi, 0); + bfin_lq035q1_disable_ppi(); + SSYNC(); + disable_dma(CH_PPI); + bfin_lq035q1_stop_timers(); + } + + spin_unlock(&fbi->lock); + + return 0; +} + +static int bfin_lq035q1_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct bfin_lq035q1fb_info *fbi = info->par; + + if (var->bits_per_pixel == fbi->lcd_bpp) { + var->red.offset = info->var.red.offset; + var->green.offset = info->var.green.offset; + var->blue.offset = info->var.blue.offset; + var->red.length = info->var.red.length; + var->green.length = info->var.green.length; + var->blue.length = info->var.blue.length; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + } else { + pr_debug("%s: depth not supported: %u BPP\n", __func__, + var->bits_per_pixel); + return -EINVAL; + } + + if (info->var.xres != var->xres || info->var.yres != var->yres || + info->var.xres_virtual != var->xres_virtual || + info->var.yres_virtual != var->yres_virtual) { + pr_debug("%s: Resolution not supported: X%u x Y%u \n", + __func__, var->xres, var->yres); + return -EINVAL; + } + + /* + * Memory limit + */ + + if ((info->fix.line_length * var->yres_virtual) > info->fix.smem_len) { + pr_debug("%s: Memory Limit requested yres_virtual = %u\n", + __func__, var->yres_virtual); + return -ENOMEM; + } + + + return 0; +} + +int bfin_lq035q1_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + if (nocursor) + return 0; + else + return -EINVAL; /* just to force soft_cursor() call */ +} + +static int bfin_lq035q1_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *info) +{ + if (regno >= BFIN_LCD_NBR_PALETTE_ENTRIES) + return -EINVAL; + + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + + u32 value; + /* Place color in the pseudopalette */ + if (regno > 16) + return -EINVAL; + + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + + value = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + value &= 0xFFFFFF; + + ((u32 *) (info->pseudo_palette))[regno] = value; + + } + + return 0; +} + +static struct fb_ops bfin_lq035q1_fb_ops = { + .owner = THIS_MODULE, + .fb_open = bfin_lq035q1_fb_open, + .fb_release = bfin_lq035q1_fb_release, + .fb_check_var = bfin_lq035q1_fb_check_var, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = bfin_lq035q1_fb_cursor, + .fb_setcolreg = bfin_lq035q1_fb_setcolreg, +}; + +static irqreturn_t bfin_lq035q1_irq_error(int irq, void *dev_id) +{ + /*struct bfin_lq035q1fb_info *info = (struct bfin_lq035q1fb_info *)dev_id;*/ + + u16 status = bfin_read_PPI_STATUS(); + bfin_write_PPI_STATUS(-1); + + if (status) { + bfin_lq035q1_disable_ppi(); + disable_dma(CH_PPI); + + /* start dma */ + enable_dma(CH_PPI); + bfin_lq035q1_enable_ppi(); + bfin_write_PPI_STATUS(-1); + } + + return IRQ_HANDLED; +} + +static int bfin_lq035q1_probe(struct platform_device *pdev) +{ + struct bfin_lq035q1fb_info *info; + struct fb_info *fbinfo; + u32 active_video_mem_offset; + int ret; + + ret = request_dma(CH_PPI, DRIVER_NAME"_CH_PPI"); + if (ret < 0) { + dev_err(&pdev->dev, "PPI DMA unavailable\n"); + goto out1; + } + + fbinfo = framebuffer_alloc(sizeof(*info), &pdev->dev); + if (!fbinfo) { + ret = -ENOMEM; + goto out2; + } + + info = fbinfo->par; + info->fb = fbinfo; + info->dev = &pdev->dev; + spin_lock_init(&info->lock); + + info->disp_info = pdev->dev.platform_data; + + platform_set_drvdata(pdev, fbinfo); + + ret = bfin_lq035q1_calc_timing(info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed PPI Mode\n"); + goto out3; + } + + strcpy(fbinfo->fix.id, DRIVER_NAME); + + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_NONE; + fbinfo->fix.visual = FB_VISUAL_TRUECOLOR; + + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.height = -1; + fbinfo->var.width = -1; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + fbinfo->var.xres = LCD_X_RES; + fbinfo->var.xres_virtual = LCD_X_RES; + fbinfo->var.yres = LCD_Y_RES; + fbinfo->var.yres_virtual = LCD_Y_RES; + fbinfo->var.bits_per_pixel = info->lcd_bpp; + + if (info->disp_info->mode & LQ035_BGR) { + if (info->lcd_bpp == 24) { + fbinfo->var.red.offset = 0; + fbinfo->var.green.offset = 8; + fbinfo->var.blue.offset = 16; + } else { + fbinfo->var.red.offset = 0; + fbinfo->var.green.offset = 5; + fbinfo->var.blue.offset = 11; + } + } else { + if (info->lcd_bpp == 24) { + fbinfo->var.red.offset = 16; + fbinfo->var.green.offset = 8; + fbinfo->var.blue.offset = 0; + } else { + fbinfo->var.red.offset = 11; + fbinfo->var.green.offset = 5; + fbinfo->var.blue.offset = 0; + } + } + + fbinfo->var.transp.offset = 0; + + if (info->lcd_bpp == 24) { + fbinfo->var.red.length = 8; + fbinfo->var.green.length = 8; + fbinfo->var.blue.length = 8; + } else { + fbinfo->var.red.length = 5; + fbinfo->var.green.length = 6; + fbinfo->var.blue.length = 5; + } + + fbinfo->var.transp.length = 0; + + active_video_mem_offset = ((U_LINE / 2) * LCD_X_RES * (info->lcd_bpp / 8)); + + fbinfo->fix.smem_len = LCD_X_RES * LCD_Y_RES * info->lcd_bpp / 8 + + active_video_mem_offset; + + fbinfo->fix.line_length = fbinfo->var.xres_virtual * + fbinfo->var.bits_per_pixel / 8; + + + fbinfo->fbops = &bfin_lq035q1_fb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + + info->fb_buffer = + dma_alloc_coherent(NULL, fbinfo->fix.smem_len, &info->dma_handle, + GFP_KERNEL); + + if (NULL == info->fb_buffer) { + dev_err(&pdev->dev, "couldn't allocate dma buffer\n"); + ret = -ENOMEM; + goto out3; + } + + fbinfo->screen_base = (void *)info->fb_buffer + active_video_mem_offset; + fbinfo->fix.smem_start = (int)info->fb_buffer + active_video_mem_offset; + + fbinfo->fbops = &bfin_lq035q1_fb_ops; + + fbinfo->pseudo_palette = &info->pseudo_pal; + + ret = fb_alloc_cmap(&fbinfo->cmap, BFIN_LCD_NBR_PALETTE_ENTRIES, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to allocate colormap (%d entries)\n", + BFIN_LCD_NBR_PALETTE_ENTRIES); + goto out4; + } + + ret = bfin_lq035q1_request_ports(pdev, + info->disp_info->ppi_mode == USE_RGB565_16_BIT_PPI); + if (ret) { + dev_err(&pdev->dev, "couldn't request gpio port\n"); + goto out6; + } + + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + ret = -EINVAL; + goto out7; + } + + ret = request_irq(info->irq, bfin_lq035q1_irq_error, 0, + DRIVER_NAME" PPI ERROR", info); + if (ret < 0) { + dev_err(&pdev->dev, "unable to request PPI ERROR IRQ\n"); + goto out7; + } + + info->spidrv.driver.name = DRIVER_NAME"-spi"; + info->spidrv.probe = lq035q1_spidev_probe; + info->spidrv.remove = lq035q1_spidev_remove; + info->spidrv.shutdown = lq035q1_spidev_shutdown; + info->spidrv.driver.pm = LQ035Q1_SPIDEV_PM_OPS; + + ret = spi_register_driver(&info->spidrv); + if (ret < 0) { + dev_err(&pdev->dev, "couldn't register SPI Interface\n"); + goto out8; + } + + if (info->disp_info->use_bl) { + ret = gpio_request_one(info->disp_info->gpio_bl, + GPIOF_OUT_INIT_LOW, "LQ035 Backlight"); + + if (ret) { + dev_err(&pdev->dev, "failed to request GPIO %d\n", + info->disp_info->gpio_bl); + goto out9; + } + } + + ret = register_framebuffer(fbinfo); + if (ret < 0) { + dev_err(&pdev->dev, "unable to register framebuffer\n"); + goto out10; + } + + dev_info(&pdev->dev, "%dx%d %d-bit RGB FrameBuffer initialized\n", + LCD_X_RES, LCD_Y_RES, info->lcd_bpp); + + return 0; + + out10: + if (info->disp_info->use_bl) + gpio_free(info->disp_info->gpio_bl); + out9: + spi_unregister_driver(&info->spidrv); + out8: + free_irq(info->irq, info); + out7: + bfin_lq035q1_free_ports(info->disp_info->ppi_mode == + USE_RGB565_16_BIT_PPI); + out6: + fb_dealloc_cmap(&fbinfo->cmap); + out4: + dma_free_coherent(NULL, fbinfo->fix.smem_len, info->fb_buffer, + info->dma_handle); + out3: + framebuffer_release(fbinfo); + out2: + free_dma(CH_PPI); + out1: + + return ret; +} + +static int bfin_lq035q1_remove(struct platform_device *pdev) +{ + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct bfin_lq035q1fb_info *info = fbinfo->par; + + if (info->disp_info->use_bl) + gpio_free(info->disp_info->gpio_bl); + + spi_unregister_driver(&info->spidrv); + + unregister_framebuffer(fbinfo); + + free_dma(CH_PPI); + free_irq(info->irq, info); + + if (info->fb_buffer != NULL) + dma_free_coherent(NULL, fbinfo->fix.smem_len, info->fb_buffer, + info->dma_handle); + + fb_dealloc_cmap(&fbinfo->cmap); + + bfin_lq035q1_free_ports(info->disp_info->ppi_mode == + USE_RGB565_16_BIT_PPI); + + framebuffer_release(fbinfo); + + dev_info(&pdev->dev, "unregistered LCD driver\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_lq035q1_suspend(struct device *dev) +{ + struct fb_info *fbinfo = dev_get_drvdata(dev); + struct bfin_lq035q1fb_info *info = fbinfo->par; + + if (info->lq035_open_cnt) { + lq035q1_backlight(info, 0); + bfin_lq035q1_disable_ppi(); + SSYNC(); + disable_dma(CH_PPI); + bfin_lq035q1_stop_timers(); + bfin_write_PPI_STATUS(-1); + } + + return 0; +} + +static int bfin_lq035q1_resume(struct device *dev) +{ + struct fb_info *fbinfo = dev_get_drvdata(dev); + struct bfin_lq035q1fb_info *info = fbinfo->par; + + if (info->lq035_open_cnt) { + bfin_lq035q1_disable_ppi(); + SSYNC(); + + bfin_lq035q1_config_dma(info); + bfin_lq035q1_config_ppi(info); + bfin_lq035q1_init_timers(info); + + /* start dma */ + enable_dma(CH_PPI); + bfin_lq035q1_enable_ppi(); + bfin_lq035q1_start_timers(); + lq035q1_backlight(info, 1); + } + + return 0; +} + +static struct dev_pm_ops bfin_lq035q1_dev_pm_ops = { + .suspend = bfin_lq035q1_suspend, + .resume = bfin_lq035q1_resume, +}; +#endif + +static struct platform_driver bfin_lq035q1_driver = { + .probe = bfin_lq035q1_probe, + .remove = bfin_lq035q1_remove, + .driver = { + .name = DRIVER_NAME, +#ifdef CONFIG_PM + .pm = &bfin_lq035q1_dev_pm_ops, +#endif + }, +}; + +module_platform_driver(bfin_lq035q1_driver); + +MODULE_DESCRIPTION("Blackfin TFT LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/bfin-t350mcqb-fb.c b/drivers/video/fbdev/bfin-t350mcqb-fb.c new file mode 100644 index 000000000000..b5cf1307a3d9 --- /dev/null +++ b/drivers/video/fbdev/bfin-t350mcqb-fb.c @@ -0,0 +1,670 @@ +/* + * File: drivers/video/bfin-t350mcqb-fb.c + * Based on: + * Author: Michael Hennerich <hennerich@blackfin.uclinux.org> + * + * Created: + * Description: Blackfin LCD Framebuffer driver + * + * + * Modified: + * Copyright 2004-2007 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/gfp.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/backlight.h> +#include <linux/lcd.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> + +#include <asm/blackfin.h> +#include <asm/irq.h> +#include <asm/dma-mapping.h> +#include <asm/dma.h> +#include <asm/portmux.h> +#include <asm/gptimers.h> + +#define NO_BL_SUPPORT + +#define LCD_X_RES 320 /* Horizontal Resolution */ +#define LCD_Y_RES 240 /* Vertical Resolution */ +#define LCD_BPP 24 /* Bit Per Pixel */ + +#define DMA_BUS_SIZE 16 +#define LCD_CLK (12*1000*1000) /* 12MHz */ + +#define CLOCKS_PER_PIX 3 + + /* + * HS and VS timing parameters (all in number of PPI clk ticks) + */ + +#define U_LINE 1 /* Blanking Lines */ + +#define H_ACTPIX (LCD_X_RES * CLOCKS_PER_PIX) /* active horizontal pixel */ +#define H_PERIOD (408 * CLOCKS_PER_PIX) /* HS period */ +#define H_PULSE 90 /* HS pulse width */ +#define H_START 204 /* first valid pixel */ + +#define V_LINES (LCD_Y_RES + U_LINE) /* total vertical lines */ +#define V_PULSE (3 * H_PERIOD) /* VS pulse width (1-5 H_PERIODs) */ +#define V_PERIOD (H_PERIOD * V_LINES) /* VS period */ + +#define ACTIVE_VIDEO_MEM_OFFSET (U_LINE * H_ACTPIX) + +#define BFIN_LCD_NBR_PALETTE_ENTRIES 256 + +#define DRIVER_NAME "bfin-t350mcqb" +static char driver_name[] = DRIVER_NAME; + +struct bfin_t350mcqbfb_info { + struct fb_info *fb; + struct device *dev; + unsigned char *fb_buffer; /* RGB Buffer */ + dma_addr_t dma_handle; + int lq043_open_cnt; + int irq; + spinlock_t lock; /* lock */ + u32 pseudo_pal[16]; +}; + +static int nocursor; +module_param(nocursor, int, 0644); +MODULE_PARM_DESC(nocursor, "cursor enable/disable"); + +#define PPI_TX_MODE 0x2 +#define PPI_XFER_TYPE_11 0xC +#define PPI_PORT_CFG_01 0x10 +#define PPI_PACK_EN 0x80 +#define PPI_POLS_1 0x8000 + +static void bfin_t350mcqb_config_ppi(struct bfin_t350mcqbfb_info *fbi) +{ + bfin_write_PPI_DELAY(H_START); + bfin_write_PPI_COUNT(H_ACTPIX-1); + bfin_write_PPI_FRAME(V_LINES); + + bfin_write_PPI_CONTROL(PPI_TX_MODE | /* output mode , PORT_DIR */ + PPI_XFER_TYPE_11 | /* sync mode XFR_TYPE */ + PPI_PORT_CFG_01 | /* two frame sync PORT_CFG */ + PPI_PACK_EN | /* packing enabled PACK_EN */ + PPI_POLS_1); /* faling edge syncs POLS */ +} + +static inline void bfin_t350mcqb_disable_ppi(void) +{ + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() & ~PORT_EN); +} + +static inline void bfin_t350mcqb_enable_ppi(void) +{ + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() | PORT_EN); +} + +static void bfin_t350mcqb_start_timers(void) +{ + unsigned long flags; + + local_irq_save(flags); + enable_gptimers(TIMER1bit); + enable_gptimers(TIMER0bit); + local_irq_restore(flags); +} + +static void bfin_t350mcqb_stop_timers(void) +{ + disable_gptimers(TIMER0bit | TIMER1bit); + + set_gptimer_status(0, TIMER_STATUS_TRUN0 | TIMER_STATUS_TRUN1 | + TIMER_STATUS_TIMIL0 | TIMER_STATUS_TIMIL1 | + TIMER_STATUS_TOVF0 | TIMER_STATUS_TOVF1); + +} + +static void bfin_t350mcqb_init_timers(void) +{ + + bfin_t350mcqb_stop_timers(); + + set_gptimer_period(TIMER0_id, H_PERIOD); + set_gptimer_pwidth(TIMER0_id, H_PULSE); + set_gptimer_config(TIMER0_id, TIMER_MODE_PWM | TIMER_PERIOD_CNT | + TIMER_TIN_SEL | TIMER_CLK_SEL| + TIMER_EMU_RUN); + + set_gptimer_period(TIMER1_id, V_PERIOD); + set_gptimer_pwidth(TIMER1_id, V_PULSE); + set_gptimer_config(TIMER1_id, TIMER_MODE_PWM | TIMER_PERIOD_CNT | + TIMER_TIN_SEL | TIMER_CLK_SEL | + TIMER_EMU_RUN); + +} + +static void bfin_t350mcqb_config_dma(struct bfin_t350mcqbfb_info *fbi) +{ + + set_dma_config(CH_PPI, + set_bfin_dma_config(DIR_READ, DMA_FLOW_AUTO, + INTR_DISABLE, DIMENSION_2D, + DATA_SIZE_16, + DMA_NOSYNC_KEEP_DMA_BUF)); + set_dma_x_count(CH_PPI, (LCD_X_RES * LCD_BPP) / DMA_BUS_SIZE); + set_dma_x_modify(CH_PPI, DMA_BUS_SIZE / 8); + set_dma_y_count(CH_PPI, V_LINES); + + set_dma_y_modify(CH_PPI, DMA_BUS_SIZE / 8); + set_dma_start_addr(CH_PPI, (unsigned long)fbi->fb_buffer); + +} + +static u16 ppi0_req_8[] = {P_PPI0_CLK, P_PPI0_FS1, P_PPI0_FS2, + P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, + P_PPI0_D3, P_PPI0_D4, P_PPI0_D5, + P_PPI0_D6, P_PPI0_D7, 0}; + +static int bfin_t350mcqb_request_ports(int action) +{ + if (action) { + if (peripheral_request_list(ppi0_req_8, DRIVER_NAME)) { + printk(KERN_ERR "Requesting Peripherals failed\n"); + return -EFAULT; + } + } else + peripheral_free_list(ppi0_req_8); + + return 0; +} + +static int bfin_t350mcqb_fb_open(struct fb_info *info, int user) +{ + struct bfin_t350mcqbfb_info *fbi = info->par; + + spin_lock(&fbi->lock); + fbi->lq043_open_cnt++; + + if (fbi->lq043_open_cnt <= 1) { + + bfin_t350mcqb_disable_ppi(); + SSYNC(); + + bfin_t350mcqb_config_dma(fbi); + bfin_t350mcqb_config_ppi(fbi); + bfin_t350mcqb_init_timers(); + + /* start dma */ + enable_dma(CH_PPI); + bfin_t350mcqb_enable_ppi(); + bfin_t350mcqb_start_timers(); + } + + spin_unlock(&fbi->lock); + + return 0; +} + +static int bfin_t350mcqb_fb_release(struct fb_info *info, int user) +{ + struct bfin_t350mcqbfb_info *fbi = info->par; + + spin_lock(&fbi->lock); + + fbi->lq043_open_cnt--; + + if (fbi->lq043_open_cnt <= 0) { + bfin_t350mcqb_disable_ppi(); + SSYNC(); + disable_dma(CH_PPI); + bfin_t350mcqb_stop_timers(); + } + + spin_unlock(&fbi->lock); + + return 0; +} + +static int bfin_t350mcqb_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + + switch (var->bits_per_pixel) { + case 24:/* TRUECOLOUR, 16m */ + var->red.offset = 0; + var->green.offset = 8; + var->blue.offset = 16; + var->red.length = var->green.length = var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + break; + default: + pr_debug("%s: depth not supported: %u BPP\n", __func__, + var->bits_per_pixel); + return -EINVAL; + } + + if (info->var.xres != var->xres || info->var.yres != var->yres || + info->var.xres_virtual != var->xres_virtual || + info->var.yres_virtual != var->yres_virtual) { + pr_debug("%s: Resolution not supported: X%u x Y%u \n", + __func__, var->xres, var->yres); + return -EINVAL; + } + + /* + * Memory limit + */ + + if ((info->fix.line_length * var->yres_virtual) > info->fix.smem_len) { + pr_debug("%s: Memory Limit requested yres_virtual = %u\n", + __func__, var->yres_virtual); + return -ENOMEM; + } + + return 0; +} + +int bfin_t350mcqb_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + if (nocursor) + return 0; + else + return -EINVAL; /* just to force soft_cursor() call */ +} + +static int bfin_t350mcqb_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *info) +{ + if (regno >= BFIN_LCD_NBR_PALETTE_ENTRIES) + return -EINVAL; + + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + + u32 value; + /* Place color in the pseudopalette */ + if (regno > 16) + return -EINVAL; + + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + + value = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + value &= 0xFFFFFF; + + ((u32 *) (info->pseudo_palette))[regno] = value; + + } + + return 0; +} + +static struct fb_ops bfin_t350mcqb_fb_ops = { + .owner = THIS_MODULE, + .fb_open = bfin_t350mcqb_fb_open, + .fb_release = bfin_t350mcqb_fb_release, + .fb_check_var = bfin_t350mcqb_fb_check_var, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = bfin_t350mcqb_fb_cursor, + .fb_setcolreg = bfin_t350mcqb_fb_setcolreg, +}; + +#ifndef NO_BL_SUPPORT +static int bl_get_brightness(struct backlight_device *bd) +{ + return 0; +} + +static const struct backlight_ops bfin_lq043fb_bl_ops = { + .get_brightness = bl_get_brightness, +}; + +static struct backlight_device *bl_dev; + +static int bfin_lcd_get_power(struct lcd_device *dev) +{ + return 0; +} + +static int bfin_lcd_set_power(struct lcd_device *dev, int power) +{ + return 0; +} + +static int bfin_lcd_get_contrast(struct lcd_device *dev) +{ + return 0; +} + +static int bfin_lcd_set_contrast(struct lcd_device *dev, int contrast) +{ + + return 0; +} + +static int bfin_lcd_check_fb(struct lcd_device *dev, struct fb_info *fi) +{ + if (!fi || (fi == &bfin_t350mcqb_fb)) + return 1; + return 0; +} + +static struct lcd_ops bfin_lcd_ops = { + .get_power = bfin_lcd_get_power, + .set_power = bfin_lcd_set_power, + .get_contrast = bfin_lcd_get_contrast, + .set_contrast = bfin_lcd_set_contrast, + .check_fb = bfin_lcd_check_fb, +}; + +static struct lcd_device *lcd_dev; +#endif + +static irqreturn_t bfin_t350mcqb_irq_error(int irq, void *dev_id) +{ + /*struct bfin_t350mcqbfb_info *info = (struct bfin_t350mcqbfb_info *)dev_id;*/ + + u16 status = bfin_read_PPI_STATUS(); + bfin_write_PPI_STATUS(0xFFFF); + + if (status) { + bfin_t350mcqb_disable_ppi(); + disable_dma(CH_PPI); + + /* start dma */ + enable_dma(CH_PPI); + bfin_t350mcqb_enable_ppi(); + bfin_write_PPI_STATUS(0xFFFF); + } + + return IRQ_HANDLED; +} + +static int bfin_t350mcqb_probe(struct platform_device *pdev) +{ +#ifndef NO_BL_SUPPORT + struct backlight_properties props; +#endif + struct bfin_t350mcqbfb_info *info; + struct fb_info *fbinfo; + int ret; + + printk(KERN_INFO DRIVER_NAME ": %dx%d %d-bit RGB FrameBuffer initializing...\n", + LCD_X_RES, LCD_Y_RES, LCD_BPP); + + if (request_dma(CH_PPI, "CH_PPI") < 0) { + printk(KERN_ERR DRIVER_NAME + ": couldn't request CH_PPI DMA\n"); + ret = -EFAULT; + goto out1; + } + + fbinfo = + framebuffer_alloc(sizeof(struct bfin_t350mcqbfb_info), &pdev->dev); + if (!fbinfo) { + ret = -ENOMEM; + goto out2; + } + + info = fbinfo->par; + info->fb = fbinfo; + info->dev = &pdev->dev; + spin_lock_init(&info->lock); + + platform_set_drvdata(pdev, fbinfo); + + strcpy(fbinfo->fix.id, driver_name); + + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_NONE; + fbinfo->fix.visual = FB_VISUAL_TRUECOLOR; + + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.height = 53; + fbinfo->var.width = 70; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + fbinfo->var.xres = LCD_X_RES; + fbinfo->var.xres_virtual = LCD_X_RES; + fbinfo->var.yres = LCD_Y_RES; + fbinfo->var.yres_virtual = LCD_Y_RES; + fbinfo->var.bits_per_pixel = LCD_BPP; + + fbinfo->var.red.offset = 0; + fbinfo->var.green.offset = 8; + fbinfo->var.blue.offset = 16; + fbinfo->var.transp.offset = 0; + fbinfo->var.red.length = 8; + fbinfo->var.green.length = 8; + fbinfo->var.blue.length = 8; + fbinfo->var.transp.length = 0; + fbinfo->fix.smem_len = LCD_X_RES * LCD_Y_RES * LCD_BPP / 8; + + fbinfo->fix.line_length = fbinfo->var.xres_virtual * + fbinfo->var.bits_per_pixel / 8; + + + fbinfo->fbops = &bfin_t350mcqb_fb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + + info->fb_buffer = dma_alloc_coherent(NULL, fbinfo->fix.smem_len + + ACTIVE_VIDEO_MEM_OFFSET, + &info->dma_handle, GFP_KERNEL); + + if (NULL == info->fb_buffer) { + printk(KERN_ERR DRIVER_NAME + ": couldn't allocate dma buffer.\n"); + ret = -ENOMEM; + goto out3; + } + + fbinfo->screen_base = (void *)info->fb_buffer + ACTIVE_VIDEO_MEM_OFFSET; + fbinfo->fix.smem_start = (int)info->fb_buffer + ACTIVE_VIDEO_MEM_OFFSET; + + fbinfo->fbops = &bfin_t350mcqb_fb_ops; + + fbinfo->pseudo_palette = &info->pseudo_pal; + + if (fb_alloc_cmap(&fbinfo->cmap, BFIN_LCD_NBR_PALETTE_ENTRIES, 0) + < 0) { + printk(KERN_ERR DRIVER_NAME + "Fail to allocate colormap (%d entries)\n", + BFIN_LCD_NBR_PALETTE_ENTRIES); + ret = -EFAULT; + goto out4; + } + + if (bfin_t350mcqb_request_ports(1)) { + printk(KERN_ERR DRIVER_NAME ": couldn't request gpio port.\n"); + ret = -EFAULT; + goto out6; + } + + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + ret = -EINVAL; + goto out7; + } + + ret = request_irq(info->irq, bfin_t350mcqb_irq_error, 0, + "PPI ERROR", info); + if (ret < 0) { + printk(KERN_ERR DRIVER_NAME + ": unable to request PPI ERROR IRQ\n"); + goto out7; + } + + if (register_framebuffer(fbinfo) < 0) { + printk(KERN_ERR DRIVER_NAME + ": unable to register framebuffer.\n"); + ret = -EINVAL; + goto out8; + } +#ifndef NO_BL_SUPPORT + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 255; + bl_dev = backlight_device_register("bf52x-bl", NULL, NULL, + &bfin_lq043fb_bl_ops, &props); + if (IS_ERR(bl_dev)) { + printk(KERN_ERR DRIVER_NAME + ": unable to register backlight.\n"); + ret = -EINVAL; + unregister_framebuffer(fbinfo); + goto out8; + } + + lcd_dev = lcd_device_register(DRIVER_NAME, NULL, &bfin_lcd_ops); + lcd_dev->props.max_contrast = 255, printk(KERN_INFO "Done.\n"); +#endif + + return 0; + +out8: + free_irq(info->irq, info); +out7: + bfin_t350mcqb_request_ports(0); +out6: + fb_dealloc_cmap(&fbinfo->cmap); +out4: + dma_free_coherent(NULL, fbinfo->fix.smem_len + ACTIVE_VIDEO_MEM_OFFSET, + info->fb_buffer, info->dma_handle); +out3: + framebuffer_release(fbinfo); +out2: + free_dma(CH_PPI); +out1: + + return ret; +} + +static int bfin_t350mcqb_remove(struct platform_device *pdev) +{ + + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct bfin_t350mcqbfb_info *info = fbinfo->par; + + unregister_framebuffer(fbinfo); + + free_dma(CH_PPI); + free_irq(info->irq, info); + + if (info->fb_buffer != NULL) + dma_free_coherent(NULL, fbinfo->fix.smem_len + + ACTIVE_VIDEO_MEM_OFFSET, info->fb_buffer, + info->dma_handle); + + fb_dealloc_cmap(&fbinfo->cmap); + +#ifndef NO_BL_SUPPORT + lcd_device_unregister(lcd_dev); + backlight_device_unregister(bl_dev); +#endif + + bfin_t350mcqb_request_ports(0); + + framebuffer_release(fbinfo); + + printk(KERN_INFO DRIVER_NAME ": Unregister LCD driver.\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_t350mcqb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct bfin_t350mcqbfb_info *fbi = fbinfo->par; + + if (fbi->lq043_open_cnt) { + bfin_t350mcqb_disable_ppi(); + disable_dma(CH_PPI); + bfin_t350mcqb_stop_timers(); + bfin_write_PPI_STATUS(-1); + } + + + return 0; +} + +static int bfin_t350mcqb_resume(struct platform_device *pdev) +{ + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct bfin_t350mcqbfb_info *fbi = fbinfo->par; + + if (fbi->lq043_open_cnt) { + bfin_t350mcqb_config_dma(fbi); + bfin_t350mcqb_config_ppi(fbi); + bfin_t350mcqb_init_timers(); + + /* start dma */ + enable_dma(CH_PPI); + bfin_t350mcqb_enable_ppi(); + bfin_t350mcqb_start_timers(); + } + + return 0; +} +#else +#define bfin_t350mcqb_suspend NULL +#define bfin_t350mcqb_resume NULL +#endif + +static struct platform_driver bfin_t350mcqb_driver = { + .probe = bfin_t350mcqb_probe, + .remove = bfin_t350mcqb_remove, + .suspend = bfin_t350mcqb_suspend, + .resume = bfin_t350mcqb_resume, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; +module_platform_driver(bfin_t350mcqb_driver); + +MODULE_DESCRIPTION("Blackfin TFT LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/bfin_adv7393fb.c b/drivers/video/fbdev/bfin_adv7393fb.c new file mode 100644 index 000000000000..a54f7f7d763b --- /dev/null +++ b/drivers/video/fbdev/bfin_adv7393fb.c @@ -0,0 +1,827 @@ +/* + * Frame buffer driver for ADV7393/2 video encoder + * + * Copyright 2006-2009 Analog Devices Inc. + * Licensed under the GPL-2 or late. + */ + +/* + * TODO: Remove Globals + * TODO: Code Cleanup + */ + +#define pr_fmt(fmt) DRIVER_NAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <asm/blackfin.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <linux/uaccess.h> +#include <linux/gpio.h> +#include <asm/portmux.h> + +#include <linux/dma-mapping.h> +#include <linux/proc_fs.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> + +#include "bfin_adv7393fb.h" + +static int mode = VMODE; +static int mem = VMEM; +static int nocursor = 1; + +static const unsigned short ppi_pins[] = { + P_PPI0_CLK, P_PPI0_FS1, P_PPI0_FS2, + P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, P_PPI0_D3, + P_PPI0_D4, P_PPI0_D5, P_PPI0_D6, P_PPI0_D7, + P_PPI0_D8, P_PPI0_D9, P_PPI0_D10, P_PPI0_D11, + P_PPI0_D12, P_PPI0_D13, P_PPI0_D14, P_PPI0_D15, + 0 +}; + +/* + * card parameters + */ + +static struct bfin_adv7393_fb_par { + /* structure holding blackfin / adv7393 parameters when + screen is blanked */ + struct { + u8 Mode; /* ntsc/pal/? */ + } vga_state; + atomic_t ref_count; +} bfin_par; + +/* --------------------------------------------------------------------- */ + +static struct fb_var_screeninfo bfin_adv7393_fb_defined = { + .xres = 720, + .yres = 480, + .xres_virtual = 720, + .yres_virtual = 480, + .bits_per_pixel = 16, + .activate = FB_ACTIVATE_TEST, + .height = -1, + .width = -1, + .left_margin = 0, + .right_margin = 0, + .upper_margin = 0, + .lower_margin = 0, + .vmode = FB_VMODE_INTERLACED, + .red = {11, 5, 0}, + .green = {5, 6, 0}, + .blue = {0, 5, 0}, + .transp = {0, 0, 0}, +}; + +static struct fb_fix_screeninfo bfin_adv7393_fb_fix = { + .id = "BFIN ADV7393", + .smem_len = 720 * 480 * 2, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .line_length = 720 * 2, + .accel = FB_ACCEL_NONE +}; + +static struct fb_ops bfin_adv7393_fb_ops = { + .owner = THIS_MODULE, + .fb_open = bfin_adv7393_fb_open, + .fb_release = bfin_adv7393_fb_release, + .fb_check_var = bfin_adv7393_fb_check_var, + .fb_pan_display = bfin_adv7393_fb_pan_display, + .fb_blank = bfin_adv7393_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = bfin_adv7393_fb_cursor, + .fb_setcolreg = bfin_adv7393_fb_setcolreg, +}; + +static int dma_desc_list(struct adv7393fb_device *fbdev, u16 arg) +{ + if (arg == BUILD) { /* Build */ + fbdev->vb1 = l1_data_sram_zalloc(sizeof(struct dmasg)); + if (fbdev->vb1 == NULL) + goto error; + + fbdev->av1 = l1_data_sram_zalloc(sizeof(struct dmasg)); + if (fbdev->av1 == NULL) + goto error; + + fbdev->vb2 = l1_data_sram_zalloc(sizeof(struct dmasg)); + if (fbdev->vb2 == NULL) + goto error; + + fbdev->av2 = l1_data_sram_zalloc(sizeof(struct dmasg)); + if (fbdev->av2 == NULL) + goto error; + + /* Build linked DMA descriptor list */ + fbdev->vb1->next_desc_addr = fbdev->av1; + fbdev->av1->next_desc_addr = fbdev->vb2; + fbdev->vb2->next_desc_addr = fbdev->av2; + fbdev->av2->next_desc_addr = fbdev->vb1; + + /* Save list head */ + fbdev->descriptor_list_head = fbdev->av2; + + /* Vertical Blanking Field 1 */ + fbdev->vb1->start_addr = VB_DUMMY_MEMORY_SOURCE; + fbdev->vb1->cfg = DMA_CFG_VAL; + + fbdev->vb1->x_count = + fbdev->modes[mode].xres + fbdev->modes[mode].boeft_blank; + + fbdev->vb1->x_modify = 0; + fbdev->vb1->y_count = fbdev->modes[mode].vb1_lines; + fbdev->vb1->y_modify = 0; + + /* Active Video Field 1 */ + + fbdev->av1->start_addr = (unsigned long)fbdev->fb_mem; + fbdev->av1->cfg = DMA_CFG_VAL; + fbdev->av1->x_count = + fbdev->modes[mode].xres + fbdev->modes[mode].boeft_blank; + fbdev->av1->x_modify = fbdev->modes[mode].bpp / 8; + fbdev->av1->y_count = fbdev->modes[mode].a_lines; + fbdev->av1->y_modify = + (fbdev->modes[mode].xres - fbdev->modes[mode].boeft_blank + + 1) * (fbdev->modes[mode].bpp / 8); + + /* Vertical Blanking Field 2 */ + + fbdev->vb2->start_addr = VB_DUMMY_MEMORY_SOURCE; + fbdev->vb2->cfg = DMA_CFG_VAL; + fbdev->vb2->x_count = + fbdev->modes[mode].xres + fbdev->modes[mode].boeft_blank; + + fbdev->vb2->x_modify = 0; + fbdev->vb2->y_count = fbdev->modes[mode].vb2_lines; + fbdev->vb2->y_modify = 0; + + /* Active Video Field 2 */ + + fbdev->av2->start_addr = + (unsigned long)fbdev->fb_mem + fbdev->line_len; + + fbdev->av2->cfg = DMA_CFG_VAL; + + fbdev->av2->x_count = + fbdev->modes[mode].xres + fbdev->modes[mode].boeft_blank; + + fbdev->av2->x_modify = (fbdev->modes[mode].bpp / 8); + fbdev->av2->y_count = fbdev->modes[mode].a_lines; + + fbdev->av2->y_modify = + (fbdev->modes[mode].xres - fbdev->modes[mode].boeft_blank + + 1) * (fbdev->modes[mode].bpp / 8); + + return 1; + } + +error: + l1_data_sram_free(fbdev->vb1); + l1_data_sram_free(fbdev->av1); + l1_data_sram_free(fbdev->vb2); + l1_data_sram_free(fbdev->av2); + + return 0; +} + +static int bfin_config_dma(struct adv7393fb_device *fbdev) +{ + BUG_ON(!(fbdev->fb_mem)); + + set_dma_x_count(CH_PPI, fbdev->descriptor_list_head->x_count); + set_dma_x_modify(CH_PPI, fbdev->descriptor_list_head->x_modify); + set_dma_y_count(CH_PPI, fbdev->descriptor_list_head->y_count); + set_dma_y_modify(CH_PPI, fbdev->descriptor_list_head->y_modify); + set_dma_start_addr(CH_PPI, fbdev->descriptor_list_head->start_addr); + set_dma_next_desc_addr(CH_PPI, + fbdev->descriptor_list_head->next_desc_addr); + set_dma_config(CH_PPI, fbdev->descriptor_list_head->cfg); + + return 1; +} + +static void bfin_disable_dma(void) +{ + bfin_write_DMA0_CONFIG(bfin_read_DMA0_CONFIG() & ~DMAEN); +} + +static void bfin_config_ppi(struct adv7393fb_device *fbdev) +{ + if (ANOMALY_05000183) { + bfin_write_TIMER2_CONFIG(WDTH_CAP); + bfin_write_TIMER_ENABLE(TIMEN2); + } + + bfin_write_PPI_CONTROL(0x381E); + bfin_write_PPI_FRAME(fbdev->modes[mode].tot_lines); + bfin_write_PPI_COUNT(fbdev->modes[mode].xres + + fbdev->modes[mode].boeft_blank - 1); + bfin_write_PPI_DELAY(fbdev->modes[mode].aoeft_blank - 1); +} + +static void bfin_enable_ppi(void) +{ + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() | PORT_EN); +} + +static void bfin_disable_ppi(void) +{ + bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() & ~PORT_EN); +} + +static inline int adv7393_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int adv7393_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +adv7393_write_block(struct i2c_client *client, + const u8 *data, unsigned int len) +{ + int ret = -1; + u8 reg; + + while (len >= 2) { + reg = *data++; + ret = adv7393_write(client, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + + return ret; +} + +static int adv7393_mode(struct i2c_client *client, u16 mode) +{ + switch (mode) { + case POWER_ON: /* ADV7393 Sleep mode OFF */ + adv7393_write(client, 0x00, 0x1E); + break; + case POWER_DOWN: /* ADV7393 Sleep mode ON */ + adv7393_write(client, 0x00, 0x1F); + break; + case BLANK_OFF: /* Pixel Data Valid */ + adv7393_write(client, 0x82, 0xCB); + break; + case BLANK_ON: /* Pixel Data Invalid */ + adv7393_write(client, 0x82, 0x8B); + break; + default: + return -EINVAL; + break; + } + return 0; +} + +static irqreturn_t ppi_irq_error(int irq, void *dev_id) +{ + + struct adv7393fb_device *fbdev = (struct adv7393fb_device *)dev_id; + + u16 status = bfin_read_PPI_STATUS(); + + pr_debug("%s: PPI Status = 0x%X\n", __func__, status); + + if (status) { + bfin_disable_dma(); /* TODO: Check Sequence */ + bfin_disable_ppi(); + bfin_clear_PPI_STATUS(); + bfin_config_dma(fbdev); + bfin_enable_ppi(); + } + + return IRQ_HANDLED; + +} + +static int proc_output(char *buf) +{ + char *p = buf; + + p += sprintf(p, + "Usage:\n" + "echo 0x[REG][Value] > adv7393\n" + "example: echo 0x1234 >adv7393\n" + "writes 0x34 into Register 0x12\n"); + + return p - buf; +} + +static ssize_t +adv7393_read_proc(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + static const char message[] = "Usage:\n" + "echo 0x[REG][Value] > adv7393\n" + "example: echo 0x1234 >adv7393\n" + "writes 0x34 into Register 0x12\n"; + return simple_read_from_buffer(buf, size, ppos, message, + sizeof(message)); +} + +static ssize_t +adv7393_write_proc(struct file *file, const char __user * buffer, + size_t count, loff_t *ppos) +{ + struct adv7393fb_device *fbdev = PDE_DATA(file_inode(file)); + unsigned int val; + int ret; + + ret = kstrtouint_from_user(buffer, count, 0, &val); + if (ret) + return -EFAULT; + + adv7393_write(fbdev->client, val >> 8, val & 0xff); + + return count; +} + +static const struct file_operations fops = { + .read = adv7393_read_proc, + .write = adv7393_write_proc, + .llseek = default_llseek, +}; + +static int bfin_adv7393_fb_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct proc_dir_entry *entry; + int num_modes = ARRAY_SIZE(known_modes); + + struct adv7393fb_device *fbdev = NULL; + + if (mem > 2) { + dev_err(&client->dev, "mem out of allowed range [1;2]\n"); + return -EINVAL; + } + + if (mode > num_modes) { + dev_err(&client->dev, "mode %d: not supported", mode); + return -EFAULT; + } + + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) { + dev_err(&client->dev, "failed to allocate device private record"); + return -ENOMEM; + } + + i2c_set_clientdata(client, fbdev); + + fbdev->modes = known_modes; + fbdev->client = client; + + fbdev->fb_len = + mem * fbdev->modes[mode].xres * fbdev->modes[mode].xres * + (fbdev->modes[mode].bpp / 8); + + fbdev->line_len = + fbdev->modes[mode].xres * (fbdev->modes[mode].bpp / 8); + + /* Workaround "PPI Does Not Start Properly In Specific Mode" */ + if (ANOMALY_05000400) { + ret = gpio_request_one(P_IDENT(P_PPI0_FS3), GPIOF_OUT_INIT_LOW, + "PPI0_FS3") + if (ret) { + dev_err(&client->dev, "PPI0_FS3 GPIO request failed\n"); + ret = -EBUSY; + goto free_fbdev; + } + } + + if (peripheral_request_list(ppi_pins, DRIVER_NAME)) { + dev_err(&client->dev, "requesting PPI peripheral failed\n"); + ret = -EFAULT; + goto free_gpio; + } + + fbdev->fb_mem = + dma_alloc_coherent(NULL, fbdev->fb_len, &fbdev->dma_handle, + GFP_KERNEL); + + if (NULL == fbdev->fb_mem) { + dev_err(&client->dev, "couldn't allocate dma buffer (%d bytes)\n", + (u32) fbdev->fb_len); + ret = -ENOMEM; + goto free_ppi_pins; + } + + fbdev->info.screen_base = (void *)fbdev->fb_mem; + bfin_adv7393_fb_fix.smem_start = (int)fbdev->fb_mem; + + bfin_adv7393_fb_fix.smem_len = fbdev->fb_len; + bfin_adv7393_fb_fix.line_length = fbdev->line_len; + + if (mem > 1) + bfin_adv7393_fb_fix.ypanstep = 1; + + bfin_adv7393_fb_defined.red.length = 5; + bfin_adv7393_fb_defined.green.length = 6; + bfin_adv7393_fb_defined.blue.length = 5; + + bfin_adv7393_fb_defined.xres = fbdev->modes[mode].xres; + bfin_adv7393_fb_defined.yres = fbdev->modes[mode].yres; + bfin_adv7393_fb_defined.xres_virtual = fbdev->modes[mode].xres; + bfin_adv7393_fb_defined.yres_virtual = mem * fbdev->modes[mode].yres; + bfin_adv7393_fb_defined.bits_per_pixel = fbdev->modes[mode].bpp; + + fbdev->info.fbops = &bfin_adv7393_fb_ops; + fbdev->info.var = bfin_adv7393_fb_defined; + fbdev->info.fix = bfin_adv7393_fb_fix; + fbdev->info.par = &bfin_par; + fbdev->info.flags = FBINFO_DEFAULT; + + fbdev->info.pseudo_palette = kzalloc(sizeof(u32) * 16, GFP_KERNEL); + if (!fbdev->info.pseudo_palette) { + dev_err(&client->dev, "failed to allocate pseudo_palette\n"); + ret = -ENOMEM; + goto free_fb_mem; + } + + if (fb_alloc_cmap(&fbdev->info.cmap, BFIN_LCD_NBR_PALETTE_ENTRIES, 0) < 0) { + dev_err(&client->dev, "failed to allocate colormap (%d entries)\n", + BFIN_LCD_NBR_PALETTE_ENTRIES); + ret = -EFAULT; + goto free_palette; + } + + if (request_dma(CH_PPI, "BF5xx_PPI_DMA") < 0) { + dev_err(&client->dev, "unable to request PPI DMA\n"); + ret = -EFAULT; + goto free_cmap; + } + + if (request_irq(IRQ_PPI_ERROR, ppi_irq_error, 0, + "PPI ERROR", fbdev) < 0) { + dev_err(&client->dev, "unable to request PPI ERROR IRQ\n"); + ret = -EFAULT; + goto free_ch_ppi; + } + + fbdev->open = 0; + + ret = adv7393_write_block(client, fbdev->modes[mode].adv7393_i2c_initd, + fbdev->modes[mode].adv7393_i2c_initd_len); + + if (ret) { + dev_err(&client->dev, "i2c attach: init error\n"); + goto free_irq_ppi; + } + + + if (register_framebuffer(&fbdev->info) < 0) { + dev_err(&client->dev, "unable to register framebuffer\n"); + ret = -EFAULT; + goto free_irq_ppi; + } + + dev_info(&client->dev, "fb%d: %s frame buffer device\n", + fbdev->info.node, fbdev->info.fix.id); + dev_info(&client->dev, "fb memory address : 0x%p\n", fbdev->fb_mem); + + entry = proc_create_data("driver/adv7393", 0, NULL, &fops, fbdev); + if (!entry) { + dev_err(&client->dev, "unable to create /proc entry\n"); + ret = -EFAULT; + goto free_fb; + } + return 0; + +free_fb: + unregister_framebuffer(&fbdev->info); +free_irq_ppi: + free_irq(IRQ_PPI_ERROR, fbdev); +free_ch_ppi: + free_dma(CH_PPI); +free_cmap: + fb_dealloc_cmap(&fbdev->info.cmap); +free_palette: + kfree(fbdev->info.pseudo_palette); +free_fb_mem: + dma_free_coherent(NULL, fbdev->fb_len, fbdev->fb_mem, + fbdev->dma_handle); +free_ppi_pins: + peripheral_free_list(ppi_pins); +free_gpio: + if (ANOMALY_05000400) + gpio_free(P_IDENT(P_PPI0_FS3)); +free_fbdev: + kfree(fbdev); + + return ret; +} + +static int bfin_adv7393_fb_open(struct fb_info *info, int user) +{ + struct adv7393fb_device *fbdev = to_adv7393fb_device(info); + + fbdev->info.screen_base = (void *)fbdev->fb_mem; + if (!fbdev->info.screen_base) { + dev_err(&fbdev->client->dev, "unable to map device\n"); + return -ENOMEM; + } + + fbdev->open = 1; + dma_desc_list(fbdev, BUILD); + adv7393_mode(fbdev->client, BLANK_OFF); + bfin_config_ppi(fbdev); + bfin_config_dma(fbdev); + bfin_enable_ppi(); + + return 0; +} + +static int bfin_adv7393_fb_release(struct fb_info *info, int user) +{ + struct adv7393fb_device *fbdev = to_adv7393fb_device(info); + + adv7393_mode(fbdev->client, BLANK_ON); + bfin_disable_dma(); + bfin_disable_ppi(); + dma_desc_list(fbdev, DESTRUCT); + fbdev->open = 0; + return 0; +} + +static int +bfin_adv7393_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + + switch (var->bits_per_pixel) { + case 16:/* DIRECTCOLOUR, 64k */ + var->red.offset = info->var.red.offset; + var->green.offset = info->var.green.offset; + var->blue.offset = info->var.blue.offset; + var->red.length = info->var.red.length; + var->green.length = info->var.green.length; + var->blue.length = info->var.blue.length; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + break; + default: + pr_debug("%s: depth not supported: %u BPP\n", __func__, + var->bits_per_pixel); + return -EINVAL; + } + + if (info->var.xres != var->xres || + info->var.yres != var->yres || + info->var.xres_virtual != var->xres_virtual || + info->var.yres_virtual != var->yres_virtual) { + pr_debug("%s: Resolution not supported: X%u x Y%u\n", + __func__, var->xres, var->yres); + return -EINVAL; + } + + /* + * Memory limit + */ + + if ((info->fix.line_length * var->yres_virtual) > info->fix.smem_len) { + pr_debug("%s: Memory Limit requested yres_virtual = %u\n", + __func__, var->yres_virtual); + return -ENOMEM; + } + + return 0; +} + +static int +bfin_adv7393_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int dy; + u32 dmaaddr; + struct adv7393fb_device *fbdev = to_adv7393fb_device(info); + + if (!var || !info) + return -EINVAL; + + if (var->xoffset - info->var.xoffset) { + /* No support for X panning for now! */ + return -EINVAL; + } + dy = var->yoffset - info->var.yoffset; + + if (dy) { + pr_debug("%s: Panning screen of %d lines\n", __func__, dy); + + dmaaddr = fbdev->av1->start_addr; + dmaaddr += (info->fix.line_length * dy); + /* TODO: Wait for current frame to finished */ + + fbdev->av1->start_addr = (unsigned long)dmaaddr; + fbdev->av2->start_addr = (unsigned long)dmaaddr + fbdev->line_len; + } + + return 0; + +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ +static int bfin_adv7393_fb_blank(int blank, struct fb_info *info) +{ + struct adv7393fb_device *fbdev = to_adv7393fb_device(info); + + switch (blank) { + + case VESA_NO_BLANKING: + /* Turn on panel */ + adv7393_mode(fbdev->client, BLANK_OFF); + break; + + case VESA_VSYNC_SUSPEND: + case VESA_HSYNC_SUSPEND: + case VESA_POWERDOWN: + /* Turn off panel */ + adv7393_mode(fbdev->client, BLANK_ON); + break; + + default: + return -EINVAL; + break; + } + return 0; +} + +int bfin_adv7393_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + if (nocursor) + return 0; + else + return -EINVAL; /* just to force soft_cursor() call */ +} + +static int bfin_adv7393_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *info) +{ + if (regno >= BFIN_LCD_NBR_PALETTE_ENTRIES) + return -EINVAL; + + if (info->var.grayscale) + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 value; + /* Place color in the pseudopalette */ + if (regno > 16) + return -EINVAL; + + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + + value = (red << info->var.red.offset) | + (green << info->var.green.offset)| + (blue << info->var.blue.offset); + value &= 0xFFFF; + + ((u32 *) (info->pseudo_palette))[regno] = value; + } + + return 0; +} + +static int bfin_adv7393_fb_remove(struct i2c_client *client) +{ + struct adv7393fb_device *fbdev = i2c_get_clientdata(client); + + adv7393_mode(client, POWER_DOWN); + + if (fbdev->fb_mem) + dma_free_coherent(NULL, fbdev->fb_len, fbdev->fb_mem, fbdev->dma_handle); + free_dma(CH_PPI); + free_irq(IRQ_PPI_ERROR, fbdev); + unregister_framebuffer(&fbdev->info); + remove_proc_entry("driver/adv7393", NULL); + fb_dealloc_cmap(&fbdev->info.cmap); + kfree(fbdev->info.pseudo_palette); + + if (ANOMALY_05000400) + gpio_free(P_IDENT(P_PPI0_FS3)); /* FS3 */ + peripheral_free_list(ppi_pins); + kfree(fbdev); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_adv7393_fb_suspend(struct device *dev) +{ + struct adv7393fb_device *fbdev = dev_get_drvdata(dev); + + if (fbdev->open) { + bfin_disable_dma(); + bfin_disable_ppi(); + dma_desc_list(fbdev, DESTRUCT); + } + adv7393_mode(fbdev->client, POWER_DOWN); + + return 0; +} + +static int bfin_adv7393_fb_resume(struct device *dev) +{ + struct adv7393fb_device *fbdev = dev_get_drvdata(dev); + + adv7393_mode(fbdev->client, POWER_ON); + + if (fbdev->open) { + dma_desc_list(fbdev, BUILD); + bfin_config_ppi(fbdev); + bfin_config_dma(fbdev); + bfin_enable_ppi(); + } + + return 0; +} + +static const struct dev_pm_ops bfin_adv7393_dev_pm_ops = { + .suspend = bfin_adv7393_fb_suspend, + .resume = bfin_adv7393_fb_resume, +}; +#endif + +static const struct i2c_device_id bfin_adv7393_id[] = { + {DRIVER_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bfin_adv7393_id); + +static struct i2c_driver bfin_adv7393_fb_driver = { + .driver = { + .name = DRIVER_NAME, +#ifdef CONFIG_PM + .pm = &bfin_adv7393_dev_pm_ops, +#endif + }, + .probe = bfin_adv7393_fb_probe, + .remove = bfin_adv7393_fb_remove, + .id_table = bfin_adv7393_id, +}; + +static int __init bfin_adv7393_fb_driver_init(void) +{ +#if defined(CONFIG_I2C_BLACKFIN_TWI) || defined(CONFIG_I2C_BLACKFIN_TWI_MODULE) + request_module("i2c-bfin-twi"); +#else + request_module("i2c-gpio"); +#endif + + return i2c_add_driver(&bfin_adv7393_fb_driver); +} +module_init(bfin_adv7393_fb_driver_init); + +static void __exit bfin_adv7393_fb_driver_cleanup(void) +{ + i2c_del_driver(&bfin_adv7393_fb_driver); +} +module_exit(bfin_adv7393_fb_driver_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Frame buffer driver for ADV7393/2 Video Encoder"); + +module_param(mode, int, 0); +MODULE_PARM_DESC(mode, + "Video Mode (0=NTSC,1=PAL,2=NTSC 640x480,3=PAL 640x480,4=NTSC YCbCr input,5=PAL YCbCr input)"); + +module_param(mem, int, 0); +MODULE_PARM_DESC(mem, + "Size of frame buffer memory 1=Single 2=Double Size (allows y-panning / frame stacking)"); + +module_param(nocursor, int, 0644); +MODULE_PARM_DESC(nocursor, "cursor enable/disable"); diff --git a/drivers/video/fbdev/bfin_adv7393fb.h b/drivers/video/fbdev/bfin_adv7393fb.h new file mode 100644 index 000000000000..cd591b5152a5 --- /dev/null +++ b/drivers/video/fbdev/bfin_adv7393fb.h @@ -0,0 +1,321 @@ +/* + * Frame buffer driver for ADV7393/2 video encoder + * + * Copyright 2006-2009 Analog Devices Inc. + * Licensed under the GPL-2 or late. + */ + +#ifndef __BFIN_ADV7393FB_H__ +#define __BFIN_ADV7393FB_H__ + +#define BFIN_LCD_NBR_PALETTE_ENTRIES 256 + +#ifdef CONFIG_NTSC +# define VMODE 0 +#endif +#ifdef CONFIG_PAL +# define VMODE 1 +#endif +#ifdef CONFIG_NTSC_640x480 +# define VMODE 2 +#endif +#ifdef CONFIG_PAL_640x480 +# define VMODE 3 +#endif +#ifdef CONFIG_NTSC_YCBCR +# define VMODE 4 +#endif +#ifdef CONFIG_PAL_YCBCR +# define VMODE 5 +#endif + +#ifndef VMODE +# define VMODE 1 +#endif + +#ifdef CONFIG_ADV7393_2XMEM +# define VMEM 2 +#else +# define VMEM 1 +#endif + +#if defined(CONFIG_BF537) || defined(CONFIG_BF536) || defined(CONFIG_BF534) +# define DMA_CFG_VAL 0x7935 /* Set Sync Bit */ +# define VB_DUMMY_MEMORY_SOURCE L1_DATA_B_START +#else +# define DMA_CFG_VAL 0x7915 +# define VB_DUMMY_MEMORY_SOURCE BOOT_ROM_START +#endif + +enum { + DESTRUCT, + BUILD, +}; + +enum { + POWER_ON, + POWER_DOWN, + BLANK_ON, + BLANK_OFF, +}; + +#define DRIVER_NAME "bfin-adv7393" + +struct adv7393fb_modes { + const s8 name[25]; /* Full name */ + u16 xres; /* Active Horizonzal Pixels */ + u16 yres; /* Active Vertical Pixels */ + u16 bpp; + u16 vmode; + u16 a_lines; /* Active Lines per Field */ + u16 vb1_lines; /* Vertical Blanking Field 1 Lines */ + u16 vb2_lines; /* Vertical Blanking Field 2 Lines */ + u16 tot_lines; /* Total Lines per Frame */ + u16 boeft_blank; /* Before Odd/Even Field Transition No. of Blank Pixels */ + u16 aoeft_blank; /* After Odd/Even Field Transition No. of Blank Pixels */ + const s8 *adv7393_i2c_initd; + u16 adv7393_i2c_initd_len; +}; + +static const u8 init_NTSC_TESTPATTERN[] = { + 0x00, 0x1E, /* Power up all DACs and PLL */ + 0x01, 0x00, /* SD-Only Mode */ + 0x80, 0x10, /* SSAF Luma Filter Enabled, NTSC Mode */ + 0x82, 0xCB, /* Step control on, pixel data valid, pedestal on, PrPb SSAF on, CVBS/YC output */ + 0x84, 0x40, /* SD Color Bar Test Pattern Enabled, DAC 2 = Luma, DAC 3 = Chroma */ +}; + +static const u8 init_NTSC[] = { + 0x00, 0x1E, /* Power up all DACs and PLL */ + 0xC3, 0x26, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xC5, 0x12, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xC2, 0x4A, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xC6, 0x5E, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xBD, 0x19, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xBF, 0x42, /* Program RGB->YCrCb Color Space conversion matrix */ + 0x8C, 0x1F, /* NTSC Subcarrier Frequency */ + 0x8D, 0x7C, /* NTSC Subcarrier Frequency */ + 0x8E, 0xF0, /* NTSC Subcarrier Frequency */ + 0x8F, 0x21, /* NTSC Subcarrier Frequency */ + 0x01, 0x00, /* SD-Only Mode */ + 0x80, 0x30, /* SSAF Luma Filter Enabled, NTSC Mode */ + 0x82, 0x8B, /* Step control on, pixel data invalid, pedestal on, PrPb SSAF on, CVBS/YC output */ + 0x87, 0x80, /* SD Color Bar Test Pattern Enabled, DAC 2 = Luma, DAC 3 = Chroma */ + 0x86, 0x82, + 0x8B, 0x11, + 0x88, 0x20, + 0x8A, 0x0d, +}; + +static const u8 init_PAL[] = { + 0x00, 0x1E, /* Power up all DACs and PLL */ + 0xC3, 0x26, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xC5, 0x12, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xC2, 0x4A, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xC6, 0x5E, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xBD, 0x19, /* Program RGB->YCrCb Color Space conversion matrix */ + 0xBF, 0x42, /* Program RGB->YCrCb Color Space conversion matrix */ + 0x8C, 0xCB, /* PAL Subcarrier Frequency */ + 0x8D, 0x8A, /* PAL Subcarrier Frequency */ + 0x8E, 0x09, /* PAL Subcarrier Frequency */ + 0x8F, 0x2A, /* PAL Subcarrier Frequency */ + 0x01, 0x00, /* SD-Only Mode */ + 0x80, 0x11, /* SSAF Luma Filter Enabled, PAL Mode */ + 0x82, 0x8B, /* Step control on, pixel data invalid, pedestal on, PrPb SSAF on, CVBS/YC output */ + 0x87, 0x80, /* SD Color Bar Test Pattern Enabled, DAC 2 = Luma, DAC 3 = Chroma */ + 0x86, 0x82, + 0x8B, 0x11, + 0x88, 0x20, + 0x8A, 0x0d, +}; + +static const u8 init_NTSC_YCbCr[] = { + 0x00, 0x1E, /* Power up all DACs and PLL */ + 0x8C, 0x1F, /* NTSC Subcarrier Frequency */ + 0x8D, 0x7C, /* NTSC Subcarrier Frequency */ + 0x8E, 0xF0, /* NTSC Subcarrier Frequency */ + 0x8F, 0x21, /* NTSC Subcarrier Frequency */ + 0x01, 0x00, /* SD-Only Mode */ + 0x80, 0x30, /* SSAF Luma Filter Enabled, NTSC Mode */ + 0x82, 0x8B, /* Step control on, pixel data invalid, pedestal on, PrPb SSAF on, CVBS/YC output */ + 0x87, 0x00, /* DAC 2 = Luma, DAC 3 = Chroma */ + 0x86, 0x82, + 0x8B, 0x11, + 0x88, 0x08, + 0x8A, 0x0d, +}; + +static const u8 init_PAL_YCbCr[] = { + 0x00, 0x1E, /* Power up all DACs and PLL */ + 0x8C, 0xCB, /* PAL Subcarrier Frequency */ + 0x8D, 0x8A, /* PAL Subcarrier Frequency */ + 0x8E, 0x09, /* PAL Subcarrier Frequency */ + 0x8F, 0x2A, /* PAL Subcarrier Frequency */ + 0x01, 0x00, /* SD-Only Mode */ + 0x80, 0x11, /* SSAF Luma Filter Enabled, PAL Mode */ + 0x82, 0x8B, /* Step control on, pixel data invalid, pedestal on, PrPb SSAF on, CVBS/YC output */ + 0x87, 0x00, /* DAC 2 = Luma, DAC 3 = Chroma */ + 0x86, 0x82, + 0x8B, 0x11, + 0x88, 0x08, + 0x8A, 0x0d, +}; + +static struct adv7393fb_modes known_modes[] = { + /* NTSC 720x480 CRT */ + { + .name = "NTSC 720x480", + .xres = 720, + .yres = 480, + .bpp = 16, + .vmode = FB_VMODE_INTERLACED, + .a_lines = 240, + .vb1_lines = 22, + .vb2_lines = 23, + .tot_lines = 525, + .boeft_blank = 16, + .aoeft_blank = 122, + .adv7393_i2c_initd = init_NTSC, + .adv7393_i2c_initd_len = sizeof(init_NTSC) + }, + /* PAL 720x480 CRT */ + { + .name = "PAL 720x576", + .xres = 720, + .yres = 576, + .bpp = 16, + .vmode = FB_VMODE_INTERLACED, + .a_lines = 288, + .vb1_lines = 24, + .vb2_lines = 25, + .tot_lines = 625, + .boeft_blank = 12, + .aoeft_blank = 132, + .adv7393_i2c_initd = init_PAL, + .adv7393_i2c_initd_len = sizeof(init_PAL) + }, + /* NTSC 640x480 CRT Experimental */ + { + .name = "NTSC 640x480", + .xres = 640, + .yres = 480, + .bpp = 16, + .vmode = FB_VMODE_INTERLACED, + .a_lines = 240, + .vb1_lines = 22, + .vb2_lines = 23, + .tot_lines = 525, + .boeft_blank = 16 + 40, + .aoeft_blank = 122 + 40, + .adv7393_i2c_initd = init_NTSC, + .adv7393_i2c_initd_len = sizeof(init_NTSC) + }, + /* PAL 640x480 CRT Experimental */ + { + .name = "PAL 640x480", + .xres = 640, + .yres = 480, + .bpp = 16, + .vmode = FB_VMODE_INTERLACED, + .a_lines = 288 - 20, + .vb1_lines = 24 + 20, + .vb2_lines = 25 + 20, + .tot_lines = 625, + .boeft_blank = 12 + 40, + .aoeft_blank = 132 + 40, + .adv7393_i2c_initd = init_PAL, + .adv7393_i2c_initd_len = sizeof(init_PAL) + }, + /* NTSC 720x480 YCbCR */ + { + .name = "NTSC 720x480 YCbCR", + .xres = 720, + .yres = 480, + .bpp = 16, + .vmode = FB_VMODE_INTERLACED, + .a_lines = 240, + .vb1_lines = 22, + .vb2_lines = 23, + .tot_lines = 525, + .boeft_blank = 16, + .aoeft_blank = 122, + .adv7393_i2c_initd = init_NTSC_YCbCr, + .adv7393_i2c_initd_len = sizeof(init_NTSC_YCbCr) + }, + /* PAL 720x480 CRT */ + { + .name = "PAL 720x576 YCbCR", + .xres = 720, + .yres = 576, + .bpp = 16, + .vmode = FB_VMODE_INTERLACED, + .a_lines = 288, + .vb1_lines = 24, + .vb2_lines = 25, + .tot_lines = 625, + .boeft_blank = 12, + .aoeft_blank = 132, + .adv7393_i2c_initd = init_PAL_YCbCr, + .adv7393_i2c_initd_len = sizeof(init_PAL_YCbCr) + } +}; + +struct adv7393fb_regs { + +}; + +struct adv7393fb_device { + struct fb_info info; /* FB driver info record */ + + struct i2c_client *client; + + struct dmasg *descriptor_list_head; + struct dmasg *vb1; + struct dmasg *av1; + struct dmasg *vb2; + struct dmasg *av2; + + dma_addr_t dma_handle; + + struct fb_info bfin_adv7393_fb; + + struct adv7393fb_modes *modes; + + struct adv7393fb_regs *regs; /* Registers memory map */ + size_t regs_len; + size_t fb_len; + size_t line_len; + u16 open; + u16 *fb_mem; /* RGB Buffer */ + +}; + +#define to_adv7393fb_device(_info) \ + (_info ? container_of(_info, struct adv7393fb_device, info) : NULL); + +static int bfin_adv7393_fb_open(struct fb_info *info, int user); +static int bfin_adv7393_fb_release(struct fb_info *info, int user); +static int bfin_adv7393_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); + +static int bfin_adv7393_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); + +static int bfin_adv7393_fb_blank(int blank, struct fb_info *info); + +static void bfin_config_ppi(struct adv7393fb_device *fbdev); +static int bfin_config_dma(struct adv7393fb_device *fbdev); +static void bfin_disable_dma(void); +static void bfin_enable_ppi(void); +static void bfin_disable_ppi(void); + +static inline int adv7393_write(struct i2c_client *client, u8 reg, u8 value); +static inline int adv7393_read(struct i2c_client *client, u8 reg); +static int adv7393_write_block(struct i2c_client *client, const u8 *data, + unsigned int len); + +int bfin_adv7393_fb_cursor(struct fb_info *info, struct fb_cursor *cursor); +static int bfin_adv7393_fb_setcolreg(u_int, u_int, u_int, u_int, + u_int, struct fb_info *info); + +#endif diff --git a/drivers/video/fbdev/broadsheetfb.c b/drivers/video/fbdev/broadsheetfb.c new file mode 100644 index 000000000000..8556264b16b7 --- /dev/null +++ b/drivers/video/fbdev/broadsheetfb.c @@ -0,0 +1,1223 @@ +/* + * broadsheetfb.c -- FB driver for E-Ink Broadsheet controller + * + * Copyright (C) 2008, Jaya Kumar + * + * 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. + * + * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. + * + * This driver is written to be used with the Broadsheet display controller. + * + * It is intended to be architecture independent. A board specific driver + * must be used to perform all the physical IO interactions. + * + */ + +#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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/firmware.h> +#include <linux/uaccess.h> + +#include <video/broadsheetfb.h> + +/* track panel specific parameters */ +struct panel_info { + int w; + int h; + u16 sdcfg; + u16 gdcfg; + u16 lutfmt; + u16 fsynclen; + u16 fendfbegin; + u16 lsynclen; + u16 lendlbegin; + u16 pixclk; +}; + +/* table of panel specific parameters to be indexed into by the board drivers */ +static struct panel_info panel_table[] = { + { /* standard 6" on TFT backplane */ + .w = 800, + .h = 600, + .sdcfg = (100 | (1 << 8) | (1 << 9)), + .gdcfg = 2, + .lutfmt = (4 | (1 << 7)), + .fsynclen = 4, + .fendfbegin = (10 << 8) | 4, + .lsynclen = 10, + .lendlbegin = (100 << 8) | 4, + .pixclk = 6, + }, + { /* custom 3.7" flexible on PET or steel */ + .w = 320, + .h = 240, + .sdcfg = (67 | (0 << 8) | (0 << 9) | (0 << 10) | (0 << 12)), + .gdcfg = 3, + .lutfmt = (4 | (1 << 7)), + .fsynclen = 0, + .fendfbegin = (80 << 8) | 4, + .lsynclen = 10, + .lendlbegin = (80 << 8) | 20, + .pixclk = 14, + }, + { /* standard 9.7" on TFT backplane */ + .w = 1200, + .h = 825, + .sdcfg = (100 | (1 << 8) | (1 << 9) | (0 << 10) | (0 << 12)), + .gdcfg = 2, + .lutfmt = (4 | (1 << 7)), + .fsynclen = 0, + .fendfbegin = (4 << 8) | 4, + .lsynclen = 4, + .lendlbegin = (60 << 8) | 10, + .pixclk = 3, + }, +}; + +#define DPY_W 800 +#define DPY_H 600 + +static struct fb_fix_screeninfo broadsheetfb_fix = { + .id = "broadsheetfb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_STATIC_PSEUDOCOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .line_length = DPY_W, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo broadsheetfb_var = { + .xres = DPY_W, + .yres = DPY_H, + .xres_virtual = DPY_W, + .yres_virtual = DPY_H, + .bits_per_pixel = 8, + .grayscale = 1, + .red = { 0, 4, 0 }, + .green = { 0, 4, 0 }, + .blue = { 0, 4, 0 }, + .transp = { 0, 0, 0 }, +}; + +/* main broadsheetfb functions */ +static void broadsheet_gpio_issue_data(struct broadsheetfb_par *par, u16 data) +{ + par->board->set_ctl(par, BS_WR, 0); + par->board->set_hdb(par, data); + par->board->set_ctl(par, BS_WR, 1); +} + +static void broadsheet_gpio_issue_cmd(struct broadsheetfb_par *par, u16 data) +{ + par->board->set_ctl(par, BS_DC, 0); + broadsheet_gpio_issue_data(par, data); +} + +static void broadsheet_gpio_send_command(struct broadsheetfb_par *par, u16 data) +{ + par->board->wait_for_rdy(par); + + par->board->set_ctl(par, BS_CS, 0); + broadsheet_gpio_issue_cmd(par, data); + par->board->set_ctl(par, BS_DC, 1); + par->board->set_ctl(par, BS_CS, 1); +} + +static void broadsheet_gpio_send_cmdargs(struct broadsheetfb_par *par, u16 cmd, + int argc, u16 *argv) +{ + int i; + + par->board->wait_for_rdy(par); + + par->board->set_ctl(par, BS_CS, 0); + broadsheet_gpio_issue_cmd(par, cmd); + par->board->set_ctl(par, BS_DC, 1); + + for (i = 0; i < argc; i++) + broadsheet_gpio_issue_data(par, argv[i]); + par->board->set_ctl(par, BS_CS, 1); +} + +static void broadsheet_mmio_send_cmdargs(struct broadsheetfb_par *par, u16 cmd, + int argc, u16 *argv) +{ + int i; + + par->board->mmio_write(par, BS_MMIO_CMD, cmd); + + for (i = 0; i < argc; i++) + par->board->mmio_write(par, BS_MMIO_DATA, argv[i]); +} + +static void broadsheet_send_command(struct broadsheetfb_par *par, u16 data) +{ + if (par->board->mmio_write) + par->board->mmio_write(par, BS_MMIO_CMD, data); + else + broadsheet_gpio_send_command(par, data); +} + +static void broadsheet_send_cmdargs(struct broadsheetfb_par *par, u16 cmd, + int argc, u16 *argv) +{ + if (par->board->mmio_write) + broadsheet_mmio_send_cmdargs(par, cmd, argc, argv); + else + broadsheet_gpio_send_cmdargs(par, cmd, argc, argv); +} + +static void broadsheet_gpio_burst_write(struct broadsheetfb_par *par, int size, + u16 *data) +{ + int i; + u16 tmp; + + par->board->set_ctl(par, BS_CS, 0); + par->board->set_ctl(par, BS_DC, 1); + + for (i = 0; i < size; i++) { + par->board->set_ctl(par, BS_WR, 0); + tmp = (data[i] & 0x0F) << 4; + tmp |= (data[i] & 0x0F00) << 4; + par->board->set_hdb(par, tmp); + par->board->set_ctl(par, BS_WR, 1); + } + + par->board->set_ctl(par, BS_CS, 1); +} + +static void broadsheet_mmio_burst_write(struct broadsheetfb_par *par, int size, + u16 *data) +{ + int i; + u16 tmp; + + for (i = 0; i < size; i++) { + tmp = (data[i] & 0x0F) << 4; + tmp |= (data[i] & 0x0F00) << 4; + par->board->mmio_write(par, BS_MMIO_DATA, tmp); + } + +} + +static void broadsheet_burst_write(struct broadsheetfb_par *par, int size, + u16 *data) +{ + if (par->board->mmio_write) + broadsheet_mmio_burst_write(par, size, data); + else + broadsheet_gpio_burst_write(par, size, data); +} + +static u16 broadsheet_gpio_get_data(struct broadsheetfb_par *par) +{ + u16 res; + /* wait for ready to go hi. (lo is busy) */ + par->board->wait_for_rdy(par); + + /* cs lo, dc lo for cmd, we lo for each data, db as usual */ + par->board->set_ctl(par, BS_DC, 1); + par->board->set_ctl(par, BS_CS, 0); + par->board->set_ctl(par, BS_WR, 0); + + res = par->board->get_hdb(par); + + /* strobe wr */ + par->board->set_ctl(par, BS_WR, 1); + par->board->set_ctl(par, BS_CS, 1); + + return res; +} + + +static u16 broadsheet_get_data(struct broadsheetfb_par *par) +{ + if (par->board->mmio_read) + return par->board->mmio_read(par); + else + return broadsheet_gpio_get_data(par); +} + +static void broadsheet_gpio_write_reg(struct broadsheetfb_par *par, u16 reg, + u16 data) +{ + /* wait for ready to go hi. (lo is busy) */ + par->board->wait_for_rdy(par); + + /* cs lo, dc lo for cmd, we lo for each data, db as usual */ + par->board->set_ctl(par, BS_CS, 0); + + broadsheet_gpio_issue_cmd(par, BS_CMD_WR_REG); + + par->board->set_ctl(par, BS_DC, 1); + + broadsheet_gpio_issue_data(par, reg); + broadsheet_gpio_issue_data(par, data); + + par->board->set_ctl(par, BS_CS, 1); +} + +static void broadsheet_mmio_write_reg(struct broadsheetfb_par *par, u16 reg, + u16 data) +{ + par->board->mmio_write(par, BS_MMIO_CMD, BS_CMD_WR_REG); + par->board->mmio_write(par, BS_MMIO_DATA, reg); + par->board->mmio_write(par, BS_MMIO_DATA, data); + +} + +static void broadsheet_write_reg(struct broadsheetfb_par *par, u16 reg, + u16 data) +{ + if (par->board->mmio_write) + broadsheet_mmio_write_reg(par, reg, data); + else + broadsheet_gpio_write_reg(par, reg, data); +} + +static void broadsheet_write_reg32(struct broadsheetfb_par *par, u16 reg, + u32 data) +{ + broadsheet_write_reg(par, reg, cpu_to_le32(data) & 0xFFFF); + broadsheet_write_reg(par, reg + 2, (cpu_to_le32(data) >> 16) & 0xFFFF); +} + + +static u16 broadsheet_read_reg(struct broadsheetfb_par *par, u16 reg) +{ + broadsheet_send_cmdargs(par, BS_CMD_RD_REG, 1, ®); + par->board->wait_for_rdy(par); + return broadsheet_get_data(par); +} + +/* functions for waveform manipulation */ +static int is_broadsheet_pll_locked(struct broadsheetfb_par *par) +{ + return broadsheet_read_reg(par, 0x000A) & 0x0001; +} + +static int broadsheet_setup_plls(struct broadsheetfb_par *par) +{ + int retry_count = 0; + u16 tmp; + + /* disable arral saemipu mode */ + broadsheet_write_reg(par, 0x0006, 0x0000); + + broadsheet_write_reg(par, 0x0010, 0x0004); + broadsheet_write_reg(par, 0x0012, 0x5949); + broadsheet_write_reg(par, 0x0014, 0x0040); + broadsheet_write_reg(par, 0x0016, 0x0000); + + do { + if (retry_count++ > 100) + return -ETIMEDOUT; + mdelay(1); + } while (!is_broadsheet_pll_locked(par)); + + tmp = broadsheet_read_reg(par, 0x0006); + tmp &= ~0x1; + broadsheet_write_reg(par, 0x0006, tmp); + + return 0; +} + +static int broadsheet_setup_spi(struct broadsheetfb_par *par) +{ + + broadsheet_write_reg(par, 0x0204, ((3 << 3) | 1)); + broadsheet_write_reg(par, 0x0208, 0x0001); + + return 0; +} + +static int broadsheet_setup_spiflash(struct broadsheetfb_par *par, + u16 *orig_sfmcd) +{ + + *orig_sfmcd = broadsheet_read_reg(par, 0x0204); + broadsheet_write_reg(par, 0x0208, 0); + broadsheet_write_reg(par, 0x0204, 0); + broadsheet_write_reg(par, 0x0204, ((3 << 3) | 1)); + + return 0; +} + +static int broadsheet_spiflash_wait_for_bit(struct broadsheetfb_par *par, + u16 reg, int bitnum, int val, + int timeout) +{ + u16 tmp; + + do { + tmp = broadsheet_read_reg(par, reg); + if (((tmp >> bitnum) & 1) == val) + return 0; + mdelay(1); + } while (timeout--); + + return -ETIMEDOUT; +} + +static int broadsheet_spiflash_write_byte(struct broadsheetfb_par *par, u8 data) +{ + broadsheet_write_reg(par, 0x0202, (data | 0x100)); + + return broadsheet_spiflash_wait_for_bit(par, 0x0206, 3, 0, 100); +} + +static int broadsheet_spiflash_read_byte(struct broadsheetfb_par *par, u8 *data) +{ + int err; + u16 tmp; + + broadsheet_write_reg(par, 0x0202, 0); + + err = broadsheet_spiflash_wait_for_bit(par, 0x0206, 3, 0, 100); + if (err) + return err; + + tmp = broadsheet_read_reg(par, 0x200); + + *data = tmp & 0xFF; + + return 0; +} + +static int broadsheet_spiflash_wait_for_status(struct broadsheetfb_par *par, + int timeout) +{ + u8 tmp; + int err; + + do { + broadsheet_write_reg(par, 0x0208, 1); + + err = broadsheet_spiflash_write_byte(par, 0x05); + if (err) + goto failout; + + err = broadsheet_spiflash_read_byte(par, &tmp); + if (err) + goto failout; + + broadsheet_write_reg(par, 0x0208, 0); + + if (!(tmp & 0x1)) + return 0; + + mdelay(5); + } while (timeout--); + + dev_err(par->info->device, "Timed out waiting for spiflash status\n"); + return -ETIMEDOUT; + +failout: + broadsheet_write_reg(par, 0x0208, 0); + return err; +} + +static int broadsheet_spiflash_op_on_address(struct broadsheetfb_par *par, + u8 op, u32 addr) +{ + int i; + u8 tmp; + int err; + + broadsheet_write_reg(par, 0x0208, 1); + + err = broadsheet_spiflash_write_byte(par, op); + if (err) + return err; + + for (i = 2; i >= 0; i--) { + tmp = ((addr >> (i * 8)) & 0xFF); + err = broadsheet_spiflash_write_byte(par, tmp); + if (err) + return err; + } + + return err; +} + +static int broadsheet_verify_spiflash(struct broadsheetfb_par *par, + int *flash_type) +{ + int err = 0; + u8 sig; + + err = broadsheet_spiflash_op_on_address(par, 0xAB, 0x00000000); + if (err) + goto failout; + + err = broadsheet_spiflash_read_byte(par, &sig); + if (err) + goto failout; + + if ((sig != 0x10) && (sig != 0x11)) { + dev_err(par->info->device, "Unexpected flash type\n"); + err = -EINVAL; + goto failout; + } + + *flash_type = sig; + +failout: + broadsheet_write_reg(par, 0x0208, 0); + return err; +} + +static int broadsheet_setup_for_wfm_write(struct broadsheetfb_par *par, + u16 *initial_sfmcd, int *flash_type) + +{ + int err; + + err = broadsheet_setup_plls(par); + if (err) + return err; + + broadsheet_write_reg(par, 0x0106, 0x0203); + + err = broadsheet_setup_spi(par); + if (err) + return err; + + err = broadsheet_setup_spiflash(par, initial_sfmcd); + if (err) + return err; + + return broadsheet_verify_spiflash(par, flash_type); +} + +static int broadsheet_spiflash_write_control(struct broadsheetfb_par *par, + int mode) +{ + int err; + + broadsheet_write_reg(par, 0x0208, 1); + if (mode) + err = broadsheet_spiflash_write_byte(par, 0x06); + else + err = broadsheet_spiflash_write_byte(par, 0x04); + + broadsheet_write_reg(par, 0x0208, 0); + return err; +} + +static int broadsheet_spiflash_erase_sector(struct broadsheetfb_par *par, + int addr) +{ + int err; + + broadsheet_spiflash_write_control(par, 1); + + err = broadsheet_spiflash_op_on_address(par, 0xD8, addr); + + broadsheet_write_reg(par, 0x0208, 0); + + if (err) + return err; + + err = broadsheet_spiflash_wait_for_status(par, 1000); + + return err; +} + +static int broadsheet_spiflash_read_range(struct broadsheetfb_par *par, + int addr, int size, char *data) +{ + int err; + int i; + + err = broadsheet_spiflash_op_on_address(par, 0x03, addr); + if (err) + goto failout; + + for (i = 0; i < size; i++) { + err = broadsheet_spiflash_read_byte(par, &data[i]); + if (err) + goto failout; + } + +failout: + broadsheet_write_reg(par, 0x0208, 0); + return err; +} + +#define BS_SPIFLASH_PAGE_SIZE 256 +static int broadsheet_spiflash_write_page(struct broadsheetfb_par *par, + int addr, const char *data) +{ + int err; + int i; + + broadsheet_spiflash_write_control(par, 1); + + err = broadsheet_spiflash_op_on_address(par, 0x02, addr); + if (err) + goto failout; + + for (i = 0; i < BS_SPIFLASH_PAGE_SIZE; i++) { + err = broadsheet_spiflash_write_byte(par, data[i]); + if (err) + goto failout; + } + + broadsheet_write_reg(par, 0x0208, 0); + + err = broadsheet_spiflash_wait_for_status(par, 100); + +failout: + return err; +} + +static int broadsheet_spiflash_write_sector(struct broadsheetfb_par *par, + int addr, const char *data, int sector_size) +{ + int i; + int err; + + for (i = 0; i < sector_size; i += BS_SPIFLASH_PAGE_SIZE) { + err = broadsheet_spiflash_write_page(par, addr + i, &data[i]); + if (err) + return err; + } + return 0; +} + +/* + * The caller must guarantee that the data to be rewritten is entirely + * contained within this sector. That is, data_start_addr + data_len + * must be less than sector_start_addr + sector_size. + */ +static int broadsheet_spiflash_rewrite_sector(struct broadsheetfb_par *par, + int sector_size, int data_start_addr, + int data_len, const char *data) +{ + int err; + char *sector_buffer; + int tail_start_addr; + int start_sector_addr; + + sector_buffer = kzalloc(sizeof(char)*sector_size, GFP_KERNEL); + if (!sector_buffer) + return -ENOMEM; + + /* the start address of the sector is the 0th byte of that sector */ + start_sector_addr = (data_start_addr / sector_size) * sector_size; + + /* + * check if there is head data that we need to readback into our sector + * buffer first + */ + if (data_start_addr != start_sector_addr) { + /* + * we need to read every byte up till the start address of our + * data and we put it into our sector buffer. + */ + err = broadsheet_spiflash_read_range(par, start_sector_addr, + data_start_addr, sector_buffer); + if (err) + return err; + } + + /* now we copy our data into the right place in the sector buffer */ + memcpy(sector_buffer + data_start_addr, data, data_len); + + /* + * now we check if there is a tail section of the sector that we need to + * readback. + */ + tail_start_addr = (data_start_addr + data_len) % sector_size; + + if (tail_start_addr) { + int tail_len; + + tail_len = sector_size - tail_start_addr; + + /* now we read this tail into our sector buffer */ + err = broadsheet_spiflash_read_range(par, tail_start_addr, + tail_len, sector_buffer + tail_start_addr); + if (err) + return err; + } + + /* if we got here we have the full sector that we want to rewrite. */ + + /* first erase the sector */ + err = broadsheet_spiflash_erase_sector(par, start_sector_addr); + if (err) + return err; + + /* now write it */ + err = broadsheet_spiflash_write_sector(par, start_sector_addr, + sector_buffer, sector_size); + return err; +} + +static int broadsheet_write_spiflash(struct broadsheetfb_par *par, u32 wfm_addr, + const u8 *wfm, int bytecount, int flash_type) +{ + int sector_size; + int err; + int cur_addr; + int writecount; + int maxlen; + int offset = 0; + + switch (flash_type) { + case 0x10: + sector_size = 32*1024; + break; + case 0x11: + default: + sector_size = 64*1024; + break; + } + + while (bytecount) { + cur_addr = wfm_addr + offset; + maxlen = roundup(cur_addr, sector_size) - cur_addr; + writecount = min(bytecount, maxlen); + + err = broadsheet_spiflash_rewrite_sector(par, sector_size, + cur_addr, writecount, wfm + offset); + if (err) + return err; + + offset += writecount; + bytecount -= writecount; + } + + return 0; +} + +static int broadsheet_store_waveform_to_spiflash(struct broadsheetfb_par *par, + const u8 *wfm, size_t wfm_size) +{ + int err = 0; + u16 initial_sfmcd = 0; + int flash_type = 0; + + err = broadsheet_setup_for_wfm_write(par, &initial_sfmcd, &flash_type); + if (err) + goto failout; + + err = broadsheet_write_spiflash(par, 0x886, wfm, wfm_size, flash_type); + +failout: + broadsheet_write_reg(par, 0x0204, initial_sfmcd); + return err; +} + +static ssize_t broadsheet_loadstore_waveform(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int err; + struct fb_info *info = dev_get_drvdata(dev); + struct broadsheetfb_par *par = info->par; + const struct firmware *fw_entry; + + if (len < 1) + return -EINVAL; + + err = request_firmware(&fw_entry, "broadsheet.wbf", dev); + if (err < 0) { + dev_err(dev, "Failed to get broadsheet waveform\n"); + goto err_failed; + } + + /* try to enforce reasonable min max on waveform */ + if ((fw_entry->size < 8*1024) || (fw_entry->size > 64*1024)) { + dev_err(dev, "Invalid waveform\n"); + err = -EINVAL; + goto err_failed; + } + + mutex_lock(&(par->io_lock)); + err = broadsheet_store_waveform_to_spiflash(par, fw_entry->data, + fw_entry->size); + + mutex_unlock(&(par->io_lock)); + if (err < 0) { + dev_err(dev, "Failed to store broadsheet waveform\n"); + goto err_failed; + } + + dev_info(dev, "Stored broadsheet waveform, size %zd\n", fw_entry->size); + + return len; + +err_failed: + return err; +} +static DEVICE_ATTR(loadstore_waveform, S_IWUSR, NULL, + broadsheet_loadstore_waveform); + +/* upper level functions that manipulate the display and other stuff */ +static void broadsheet_init_display(struct broadsheetfb_par *par) +{ + u16 args[5]; + int xres = par->info->var.xres; + int yres = par->info->var.yres; + + args[0] = panel_table[par->panel_index].w; + args[1] = panel_table[par->panel_index].h; + args[2] = panel_table[par->panel_index].sdcfg; + args[3] = panel_table[par->panel_index].gdcfg; + args[4] = panel_table[par->panel_index].lutfmt; + broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_CFG, 5, args); + + /* did the controller really set it? */ + broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_CFG, 5, args); + + args[0] = panel_table[par->panel_index].fsynclen; + args[1] = panel_table[par->panel_index].fendfbegin; + args[2] = panel_table[par->panel_index].lsynclen; + args[3] = panel_table[par->panel_index].lendlbegin; + args[4] = panel_table[par->panel_index].pixclk; + broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_TMG, 5, args); + + broadsheet_write_reg32(par, 0x310, xres*yres*2); + + /* setup waveform */ + args[0] = 0x886; + args[1] = 0; + broadsheet_send_cmdargs(par, BS_CMD_RD_WFM_INFO, 2, args); + + broadsheet_send_command(par, BS_CMD_UPD_GDRV_CLR); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); + + broadsheet_write_reg(par, 0x330, 0x84); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); + + args[0] = (0x3 << 4); + broadsheet_send_cmdargs(par, BS_CMD_LD_IMG, 1, args); + + args[0] = 0x154; + broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args); + + broadsheet_burst_write(par, (panel_table[par->panel_index].w * + panel_table[par->panel_index].h)/2, + (u16 *) par->info->screen_base); + + broadsheet_send_command(par, BS_CMD_LD_IMG_END); + + args[0] = 0x4300; + broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND); + + par->board->wait_for_rdy(par); +} + +static void broadsheet_identify(struct broadsheetfb_par *par) +{ + u16 rev, prc; + struct device *dev = par->info->device; + + rev = broadsheet_read_reg(par, BS_REG_REV); + prc = broadsheet_read_reg(par, BS_REG_PRC); + dev_info(dev, "Broadsheet Rev 0x%x, Product Code 0x%x\n", rev, prc); + + if (prc != 0x0047) + dev_warn(dev, "Unrecognized Broadsheet Product Code\n"); + if (rev != 0x0100) + dev_warn(dev, "Unrecognized Broadsheet Revision\n"); +} + +static void broadsheet_init(struct broadsheetfb_par *par) +{ + broadsheet_send_command(par, BS_CMD_INIT_SYS_RUN); + /* the controller needs a second */ + msleep(1000); + broadsheet_init_display(par); +} + +static void broadsheetfb_dpy_update_pages(struct broadsheetfb_par *par, + u16 y1, u16 y2) +{ + u16 args[5]; + unsigned char *buf = (unsigned char *)par->info->screen_base; + + mutex_lock(&(par->io_lock)); + /* y1 must be a multiple of 4 so drop the lower bits */ + y1 &= 0xFFFC; + /* y2 must be a multiple of 4 , but - 1 so up the lower bits */ + y2 |= 0x0003; + + args[0] = 0x3 << 4; + args[1] = 0; + args[2] = y1; + args[3] = cpu_to_le16(par->info->var.xres); + args[4] = y2; + broadsheet_send_cmdargs(par, BS_CMD_LD_IMG_AREA, 5, args); + + args[0] = 0x154; + broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args); + + buf += y1 * par->info->var.xres; + broadsheet_burst_write(par, ((1 + y2 - y1) * par->info->var.xres)/2, + (u16 *) buf); + + broadsheet_send_command(par, BS_CMD_LD_IMG_END); + + args[0] = 0x4300; + broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND); + + par->board->wait_for_rdy(par); + mutex_unlock(&(par->io_lock)); + +} + +static void broadsheetfb_dpy_update(struct broadsheetfb_par *par) +{ + u16 args[5]; + + mutex_lock(&(par->io_lock)); + args[0] = 0x3 << 4; + broadsheet_send_cmdargs(par, BS_CMD_LD_IMG, 1, args); + + args[0] = 0x154; + broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args); + broadsheet_burst_write(par, (panel_table[par->panel_index].w * + panel_table[par->panel_index].h)/2, + (u16 *) par->info->screen_base); + + broadsheet_send_command(par, BS_CMD_LD_IMG_END); + + args[0] = 0x4300; + broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); + + broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND); + + par->board->wait_for_rdy(par); + mutex_unlock(&(par->io_lock)); +} + +/* this is called back from the deferred io workqueue */ +static void broadsheetfb_dpy_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + u16 y1 = 0, h = 0; + int prev_index = -1; + struct page *cur; + struct fb_deferred_io *fbdefio = info->fbdefio; + int h_inc; + u16 yres = info->var.yres; + u16 xres = info->var.xres; + + /* height increment is fixed per page */ + h_inc = DIV_ROUND_UP(PAGE_SIZE , xres); + + /* walk the written page list and swizzle the data */ + list_for_each_entry(cur, &fbdefio->pagelist, lru) { + if (prev_index < 0) { + /* just starting so assign first page */ + y1 = (cur->index << PAGE_SHIFT) / xres; + h = h_inc; + } else if ((prev_index + 1) == cur->index) { + /* this page is consecutive so increase our height */ + h += h_inc; + } else { + /* page not consecutive, issue previous update first */ + broadsheetfb_dpy_update_pages(info->par, y1, y1 + h); + /* start over with our non consecutive page */ + y1 = (cur->index << PAGE_SHIFT) / xres; + h = h_inc; + } + prev_index = cur->index; + } + + /* if we still have any pages to update we do so now */ + if (h >= yres) { + /* its a full screen update, just do it */ + broadsheetfb_dpy_update(info->par); + } else { + broadsheetfb_dpy_update_pages(info->par, y1, + min((u16) (y1 + h), yres)); + } +} + +static void broadsheetfb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct broadsheetfb_par *par = info->par; + + sys_fillrect(info, rect); + + broadsheetfb_dpy_update(par); +} + +static void broadsheetfb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct broadsheetfb_par *par = info->par; + + sys_copyarea(info, area); + + broadsheetfb_dpy_update(par); +} + +static void broadsheetfb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct broadsheetfb_par *par = info->par; + + sys_imageblit(info, image); + + broadsheetfb_dpy_update(par); +} + +/* + * this is the slow path from userspace. they can seek and write to + * the fb. it's inefficient to do anything less than a full screen draw + */ +static ssize_t broadsheetfb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct broadsheetfb_par *par = info->par; + unsigned long p = *ppos; + void *dst; + int err = 0; + unsigned long total_size; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + total_size = info->fix.smem_len; + + if (p > total_size) + return -EFBIG; + + if (count > total_size) { + err = -EFBIG; + count = total_size; + } + + if (count + p > total_size) { + if (!err) + err = -ENOSPC; + + count = total_size - p; + } + + dst = (void *)(info->screen_base + p); + + if (copy_from_user(dst, buf, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + broadsheetfb_dpy_update(par); + + return (err) ? err : count; +} + +static struct fb_ops broadsheetfb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = broadsheetfb_write, + .fb_fillrect = broadsheetfb_fillrect, + .fb_copyarea = broadsheetfb_copyarea, + .fb_imageblit = broadsheetfb_imageblit, +}; + +static struct fb_deferred_io broadsheetfb_defio = { + .delay = HZ/4, + .deferred_io = broadsheetfb_dpy_deferred_io, +}; + +static int broadsheetfb_probe(struct platform_device *dev) +{ + struct fb_info *info; + struct broadsheet_board *board; + int retval = -ENOMEM; + int videomemorysize; + unsigned char *videomemory; + struct broadsheetfb_par *par; + int i; + int dpyw, dpyh; + int panel_index; + + /* pick up board specific routines */ + board = dev->dev.platform_data; + if (!board) + return -EINVAL; + + /* try to count device specific driver, if can't, platform recalls */ + if (!try_module_get(board->owner)) + return -ENODEV; + + info = framebuffer_alloc(sizeof(struct broadsheetfb_par), &dev->dev); + if (!info) + goto err; + + switch (board->get_panel_type()) { + case 37: + panel_index = 1; + break; + case 97: + panel_index = 2; + break; + case 6: + default: + panel_index = 0; + break; + } + + dpyw = panel_table[panel_index].w; + dpyh = panel_table[panel_index].h; + + videomemorysize = roundup((dpyw*dpyh), PAGE_SIZE); + + videomemory = vzalloc(videomemorysize); + if (!videomemory) + goto err_fb_rel; + + info->screen_base = (char *)videomemory; + info->fbops = &broadsheetfb_ops; + + broadsheetfb_var.xres = dpyw; + broadsheetfb_var.yres = dpyh; + broadsheetfb_var.xres_virtual = dpyw; + broadsheetfb_var.yres_virtual = dpyh; + info->var = broadsheetfb_var; + + broadsheetfb_fix.line_length = dpyw; + info->fix = broadsheetfb_fix; + info->fix.smem_len = videomemorysize; + par = info->par; + par->panel_index = panel_index; + par->info = info; + par->board = board; + par->write_reg = broadsheet_write_reg; + par->read_reg = broadsheet_read_reg; + init_waitqueue_head(&par->waitq); + + mutex_init(&par->io_lock); + + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + + info->fbdefio = &broadsheetfb_defio; + fb_deferred_io_init(info); + + retval = fb_alloc_cmap(&info->cmap, 16, 0); + if (retval < 0) { + dev_err(&dev->dev, "Failed to allocate colormap\n"); + goto err_vfree; + } + + /* set cmap */ + for (i = 0; i < 16; i++) + info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/32; + memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*16); + memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*16); + + retval = par->board->setup_irq(info); + if (retval < 0) + goto err_cmap; + + /* this inits the dpy */ + retval = board->init(par); + if (retval < 0) + goto err_free_irq; + + broadsheet_identify(par); + + broadsheet_init(par); + + retval = register_framebuffer(info); + if (retval < 0) + goto err_free_irq; + + platform_set_drvdata(dev, info); + + retval = device_create_file(&dev->dev, &dev_attr_loadstore_waveform); + if (retval < 0) + goto err_unreg_fb; + + fb_info(info, "Broadsheet frame buffer, using %dK of video memory\n", + videomemorysize >> 10); + + + return 0; + +err_unreg_fb: + unregister_framebuffer(info); +err_free_irq: + board->cleanup(par); +err_cmap: + fb_dealloc_cmap(&info->cmap); +err_vfree: + vfree(videomemory); +err_fb_rel: + framebuffer_release(info); +err: + module_put(board->owner); + return retval; + +} + +static int broadsheetfb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + struct broadsheetfb_par *par = info->par; + + device_remove_file(info->dev, &dev_attr_loadstore_waveform); + unregister_framebuffer(info); + fb_deferred_io_cleanup(info); + par->board->cleanup(par); + fb_dealloc_cmap(&info->cmap); + vfree((void *)info->screen_base); + module_put(par->board->owner); + framebuffer_release(info); + } + return 0; +} + +static struct platform_driver broadsheetfb_driver = { + .probe = broadsheetfb_probe, + .remove = broadsheetfb_remove, + .driver = { + .owner = THIS_MODULE, + .name = "broadsheetfb", + }, +}; +module_platform_driver(broadsheetfb_driver); + +MODULE_DESCRIPTION("fbdev driver for Broadsheet controller"); +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/bt431.h b/drivers/video/fbdev/bt431.h new file mode 100644 index 000000000000..04e0cfbba538 --- /dev/null +++ b/drivers/video/fbdev/bt431.h @@ -0,0 +1,235 @@ +/* + * linux/drivers/video/bt431.h + * + * Copyright 2003 Thiemo Seufer <seufer@csv.ica.uni-stuttgart.de> + * + * 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/types.h> + +/* + * Bt431 cursor generator registers, 32-bit aligned. + * Two twin Bt431 are used on the DECstation's PMAG-AA. + */ +struct bt431_regs { + volatile u16 addr_lo; + u16 pad0; + volatile u16 addr_hi; + u16 pad1; + volatile u16 addr_cmap; + u16 pad2; + volatile u16 addr_reg; + u16 pad3; +}; + +static inline u16 bt431_set_value(u8 val) +{ + return ((val << 8) | (val & 0xff)) & 0xffff; +} + +static inline u8 bt431_get_value(u16 val) +{ + return val & 0xff; +} + +/* + * Additional registers addressed indirectly. + */ +#define BT431_REG_CMD 0x0000 +#define BT431_REG_CXLO 0x0001 +#define BT431_REG_CXHI 0x0002 +#define BT431_REG_CYLO 0x0003 +#define BT431_REG_CYHI 0x0004 +#define BT431_REG_WXLO 0x0005 +#define BT431_REG_WXHI 0x0006 +#define BT431_REG_WYLO 0x0007 +#define BT431_REG_WYHI 0x0008 +#define BT431_REG_WWLO 0x0009 +#define BT431_REG_WWHI 0x000a +#define BT431_REG_WHLO 0x000b +#define BT431_REG_WHHI 0x000c + +#define BT431_REG_CRAM_BASE 0x0000 +#define BT431_REG_CRAM_END 0x01ff + +/* + * Command register. + */ +#define BT431_CMD_CURS_ENABLE 0x40 +#define BT431_CMD_XHAIR_ENABLE 0x20 +#define BT431_CMD_OR_CURSORS 0x10 +#define BT431_CMD_AND_CURSORS 0x00 +#define BT431_CMD_1_1_MUX 0x00 +#define BT431_CMD_4_1_MUX 0x04 +#define BT431_CMD_5_1_MUX 0x08 +#define BT431_CMD_xxx_MUX 0x0c +#define BT431_CMD_THICK_1 0x00 +#define BT431_CMD_THICK_3 0x01 +#define BT431_CMD_THICK_5 0x02 +#define BT431_CMD_THICK_7 0x03 + +static inline void bt431_select_reg(struct bt431_regs *regs, int ir) +{ + /* + * The compiler splits the write in two bytes without these + * helper variables. + */ + volatile u16 *lo = &(regs->addr_lo); + volatile u16 *hi = &(regs->addr_hi); + + mb(); + *lo = bt431_set_value(ir & 0xff); + wmb(); + *hi = bt431_set_value((ir >> 8) & 0xff); +} + +/* Autoincrement read/write. */ +static inline u8 bt431_read_reg_inc(struct bt431_regs *regs) +{ + /* + * The compiler splits the write in two bytes without the + * helper variable. + */ + volatile u16 *r = &(regs->addr_reg); + + mb(); + return bt431_get_value(*r); +} + +static inline void bt431_write_reg_inc(struct bt431_regs *regs, u8 value) +{ + /* + * The compiler splits the write in two bytes without the + * helper variable. + */ + volatile u16 *r = &(regs->addr_reg); + + mb(); + *r = bt431_set_value(value); +} + +static inline u8 bt431_read_reg(struct bt431_regs *regs, int ir) +{ + bt431_select_reg(regs, ir); + return bt431_read_reg_inc(regs); +} + +static inline void bt431_write_reg(struct bt431_regs *regs, int ir, u8 value) +{ + bt431_select_reg(regs, ir); + bt431_write_reg_inc(regs, value); +} + +/* Autoincremented read/write for the cursor map. */ +static inline u16 bt431_read_cmap_inc(struct bt431_regs *regs) +{ + /* + * The compiler splits the write in two bytes without the + * helper variable. + */ + volatile u16 *r = &(regs->addr_cmap); + + mb(); + return *r; +} + +static inline void bt431_write_cmap_inc(struct bt431_regs *regs, u16 value) +{ + /* + * The compiler splits the write in two bytes without the + * helper variable. + */ + volatile u16 *r = &(regs->addr_cmap); + + mb(); + *r = value; +} + +static inline u16 bt431_read_cmap(struct bt431_regs *regs, int cr) +{ + bt431_select_reg(regs, cr); + return bt431_read_cmap_inc(regs); +} + +static inline void bt431_write_cmap(struct bt431_regs *regs, int cr, u16 value) +{ + bt431_select_reg(regs, cr); + bt431_write_cmap_inc(regs, value); +} + +static inline void bt431_enable_cursor(struct bt431_regs *regs) +{ + bt431_write_reg(regs, BT431_REG_CMD, + BT431_CMD_CURS_ENABLE | BT431_CMD_OR_CURSORS + | BT431_CMD_4_1_MUX | BT431_CMD_THICK_1); +} + +static inline void bt431_erase_cursor(struct bt431_regs *regs) +{ + bt431_write_reg(regs, BT431_REG_CMD, BT431_CMD_4_1_MUX); +} + +static inline void bt431_position_cursor(struct bt431_regs *regs, u16 x, u16 y) +{ + /* + * Magic from the MACH sources. + * + * Cx = x + D + H - P + * P = 37 if 1:1, 52 if 4:1, 57 if 5:1 + * D = pixel skew between outdata and external data + * H = pixels between HSYNCH falling and active video + * + * Cy = y + V - 32 + * V = scanlines between HSYNCH falling, two or more + * clocks after VSYNCH falling, and active video + */ + x += 412 - 52; + y += 68 - 32; + + /* Use autoincrement. */ + bt431_select_reg(regs, BT431_REG_CXLO); + bt431_write_reg_inc(regs, x & 0xff); /* BT431_REG_CXLO */ + bt431_write_reg_inc(regs, (x >> 8) & 0x0f); /* BT431_REG_CXHI */ + bt431_write_reg_inc(regs, y & 0xff); /* BT431_REG_CYLO */ + bt431_write_reg_inc(regs, (y >> 8) & 0x0f); /* BT431_REG_CYHI */ +} + +static inline void bt431_set_font(struct bt431_regs *regs, u8 fgc, + u16 width, u16 height) +{ + int i; + u16 fgp = fgc ? 0xffff : 0x0000; + u16 bgp = fgc ? 0x0000 : 0xffff; + + bt431_select_reg(regs, BT431_REG_CRAM_BASE); + for (i = BT431_REG_CRAM_BASE; i <= BT431_REG_CRAM_END; i++) { + u16 value; + + if (height << 6 <= i << 3) + value = bgp; + else if (width <= i % 8 << 3) + value = bgp; + else if (((width >> 3) & 0xffff) > i % 8) + value = fgp; + else + value = fgp & ~(bgp << (width % 8 << 1)); + + bt431_write_cmap_inc(regs, value); + } +} + +static inline void bt431_init_cursor(struct bt431_regs *regs) +{ + /* no crosshair window */ + bt431_select_reg(regs, BT431_REG_WXLO); + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WXLO */ + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WXHI */ + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WYLO */ + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WYHI */ + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WWLO */ + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WWHI */ + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WHLO */ + bt431_write_reg_inc(regs, 0x00); /* BT431_REG_WHHI */ +} diff --git a/drivers/video/fbdev/bt455.h b/drivers/video/fbdev/bt455.h new file mode 100644 index 000000000000..80f61b03e9ae --- /dev/null +++ b/drivers/video/fbdev/bt455.h @@ -0,0 +1,94 @@ +/* + * linux/drivers/video/bt455.h + * + * Copyright 2003 Thiemo Seufer <seufer@csv.ica.uni-stuttgart.de> + * + * 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/types.h> + +/* + * Bt455 byte-wide registers, 32-bit aligned. + */ +struct bt455_regs { + volatile u8 addr_cmap; + u8 pad0[3]; + volatile u8 addr_cmap_data; + u8 pad1[3]; + volatile u8 addr_clr; + u8 pad2[3]; + volatile u8 addr_ovly; + u8 pad3[3]; +}; + +static inline void bt455_select_reg(struct bt455_regs *regs, int ir) +{ + mb(); + regs->addr_cmap = ir & 0x0f; +} + +/* + * Read/write to a Bt455 color map register. + */ +static inline void bt455_read_cmap_entry(struct bt455_regs *regs, int cr, + u8* red, u8* green, u8* blue) +{ + bt455_select_reg(regs, cr); + mb(); + *red = regs->addr_cmap_data & 0x0f; + rmb(); + *green = regs->addr_cmap_data & 0x0f; + rmb(); + *blue = regs->addr_cmap_data & 0x0f; +} + +static inline void bt455_write_cmap_entry(struct bt455_regs *regs, int cr, + u8 red, u8 green, u8 blue) +{ + bt455_select_reg(regs, cr); + wmb(); + regs->addr_cmap_data = red & 0x0f; + wmb(); + regs->addr_cmap_data = green & 0x0f; + wmb(); + regs->addr_cmap_data = blue & 0x0f; +} + +static inline void bt455_write_ovly_entry(struct bt455_regs *regs, int cr, + u8 red, u8 green, u8 blue) +{ + bt455_select_reg(regs, cr); + wmb(); + regs->addr_ovly = red & 0x0f; + wmb(); + regs->addr_ovly = green & 0x0f; + wmb(); + regs->addr_ovly = blue & 0x0f; +} + +static inline void bt455_set_cursor(struct bt455_regs *regs) +{ + mb(); + regs->addr_ovly = 0x0f; + wmb(); + regs->addr_ovly = 0x0f; + wmb(); + regs->addr_ovly = 0x0f; +} + +static inline void bt455_erase_cursor(struct bt455_regs *regs) +{ + /* bt455_write_cmap_entry(regs, 8, 0x00, 0x00, 0x00); */ + /* bt455_write_cmap_entry(regs, 9, 0x00, 0x00, 0x00); */ + bt455_write_ovly_entry(regs, 8, 0x03, 0x03, 0x03); + bt455_write_ovly_entry(regs, 9, 0x07, 0x07, 0x07); + + wmb(); + regs->addr_ovly = 0x09; + wmb(); + regs->addr_ovly = 0x09; + wmb(); + regs->addr_ovly = 0x09; +} diff --git a/drivers/video/fbdev/bw2.c b/drivers/video/fbdev/bw2.c new file mode 100644 index 000000000000..bc123d6947a4 --- /dev/null +++ b/drivers/video/fbdev/bw2.c @@ -0,0 +1,406 @@ +/* bw2.c: BWTWO frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int bw2_blank(int, struct fb_info *); + +static int bw2_mmap(struct fb_info *, struct vm_area_struct *); +static int bw2_ioctl(struct fb_info *, unsigned int, unsigned long); + +/* + * Frame buffer operations + */ + +static struct fb_ops bw2_ops = { + .owner = THIS_MODULE, + .fb_blank = bw2_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = bw2_mmap, + .fb_ioctl = bw2_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + +/* OBio addresses for the bwtwo registers */ +#define BWTWO_REGISTER_OFFSET 0x400000 + +struct bt_regs { + u32 addr; + u32 color_map; + u32 control; + u32 cursor; +}; + +struct bw2_regs { + struct bt_regs cmap; + u8 control; + u8 status; + u8 cursor_start; + u8 cursor_end; + u8 h_blank_start; + u8 h_blank_end; + u8 h_sync_start; + u8 h_sync_end; + u8 comp_sync_end; + u8 v_blank_start_high; + u8 v_blank_start_low; + u8 v_blank_end; + u8 v_sync_start; + u8 v_sync_end; + u8 xfer_holdoff_start; + u8 xfer_holdoff_end; +}; + +/* Status Register Constants */ +#define BWTWO_SR_RES_MASK 0x70 +#define BWTWO_SR_1600_1280 0x50 +#define BWTWO_SR_1152_900_76_A 0x40 +#define BWTWO_SR_1152_900_76_B 0x60 +#define BWTWO_SR_ID_MASK 0x0f +#define BWTWO_SR_ID_MONO 0x02 +#define BWTWO_SR_ID_MONO_ECL 0x03 +#define BWTWO_SR_ID_MSYNC 0x04 +#define BWTWO_SR_ID_NOCONN 0x0a + +/* Control Register Constants */ +#define BWTWO_CTL_ENABLE_INTS 0x80 +#define BWTWO_CTL_ENABLE_VIDEO 0x40 +#define BWTWO_CTL_ENABLE_TIMING 0x20 +#define BWTWO_CTL_ENABLE_CURCMP 0x10 +#define BWTWO_CTL_XTAL_MASK 0x0C +#define BWTWO_CTL_DIVISOR_MASK 0x03 + +/* Status Register Constants */ +#define BWTWO_STAT_PENDING_INT 0x80 +#define BWTWO_STAT_MSENSE_MASK 0x70 +#define BWTWO_STAT_ID_MASK 0x0f + +struct bw2_par { + spinlock_t lock; + struct bw2_regs __iomem *regs; + + u32 flags; +#define BW2_FLAG_BLANKED 0x00000001 + + unsigned long which_io; +}; + +/** + * bw2_blank - Optional function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int +bw2_blank(int blank, struct fb_info *info) +{ + struct bw2_par *par = (struct bw2_par *) info->par; + struct bw2_regs __iomem *regs = par->regs; + unsigned long flags; + u8 val; + + spin_lock_irqsave(&par->lock, flags); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + val = sbus_readb(®s->control); + val |= BWTWO_CTL_ENABLE_VIDEO; + sbus_writeb(val, ®s->control); + par->flags &= ~BW2_FLAG_BLANKED; + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + case FB_BLANK_POWERDOWN: /* Poweroff */ + val = sbus_readb(®s->control); + val &= ~BWTWO_CTL_ENABLE_VIDEO; + sbus_writeb(val, ®s->control); + par->flags |= BW2_FLAG_BLANKED; + break; + } + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static struct sbus_mmap_map bw2_mmap_map[] = { + { + .size = SBUS_MMAP_FBSIZE(1) + }, + { .size = 0 } +}; + +static int bw2_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct bw2_par *par = (struct bw2_par *)info->par; + + return sbusfb_mmap_helper(bw2_mmap_map, + info->fix.smem_start, info->fix.smem_len, + par->which_io, + vma); +} + +static int bw2_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + return sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_SUN2BW, 1, info->fix.smem_len); +} + +/* + * Initialisation + */ + +static void bw2_init_fix(struct fb_info *info, int linebytes) +{ + strlcpy(info->fix.id, "bwtwo", sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_MONO01; + + info->fix.line_length = linebytes; + + info->fix.accel = FB_ACCEL_SUN_BWTWO; +} + +static u8 bw2regs_1600[] = { + 0x14, 0x8b, 0x15, 0x28, 0x16, 0x03, 0x17, 0x13, + 0x18, 0x7b, 0x19, 0x05, 0x1a, 0x34, 0x1b, 0x2e, + 0x1c, 0x00, 0x1d, 0x0a, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x21, 0 +}; + +static u8 bw2regs_ecl[] = { + 0x14, 0x65, 0x15, 0x1e, 0x16, 0x04, 0x17, 0x0c, + 0x18, 0x5e, 0x19, 0x03, 0x1a, 0xa7, 0x1b, 0x23, + 0x1c, 0x00, 0x1d, 0x08, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x20, 0 +}; + +static u8 bw2regs_analog[] = { + 0x14, 0xbb, 0x15, 0x2b, 0x16, 0x03, 0x17, 0x13, + 0x18, 0xb0, 0x19, 0x03, 0x1a, 0xa6, 0x1b, 0x22, + 0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x20, 0 +}; + +static u8 bw2regs_76hz[] = { + 0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f, + 0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a, + 0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x24, 0 +}; + +static u8 bw2regs_66hz[] = { + 0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14, + 0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24, + 0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x20, 0 +}; + +static int bw2_do_default_mode(struct bw2_par *par, struct fb_info *info, + int *linebytes) +{ + u8 status, mon; + u8 *p; + + status = sbus_readb(&par->regs->status); + mon = status & BWTWO_SR_RES_MASK; + switch (status & BWTWO_SR_ID_MASK) { + case BWTWO_SR_ID_MONO_ECL: + if (mon == BWTWO_SR_1600_1280) { + p = bw2regs_1600; + info->var.xres = info->var.xres_virtual = 1600; + info->var.yres = info->var.yres_virtual = 1280; + *linebytes = 1600 / 8; + } else + p = bw2regs_ecl; + break; + + case BWTWO_SR_ID_MONO: + p = bw2regs_analog; + break; + + case BWTWO_SR_ID_MSYNC: + if (mon == BWTWO_SR_1152_900_76_A || + mon == BWTWO_SR_1152_900_76_B) + p = bw2regs_76hz; + else + p = bw2regs_66hz; + break; + + case BWTWO_SR_ID_NOCONN: + return 0; + + default: + printk(KERN_ERR "bw2: can't handle SR %02x\n", + status); + return -EINVAL; + } + for ( ; *p; p += 2) { + u8 __iomem *regp = &((u8 __iomem *)par->regs)[p[0]]; + sbus_writeb(p[1], regp); + } + return 0; +} + +static int bw2_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct bw2_par *par; + int linebytes, err; + + info = framebuffer_alloc(sizeof(struct bw2_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + par = info->par; + + spin_lock_init(&par->lock); + + info->fix.smem_start = op->resource[0].start; + par->which_io = op->resource[0].flags & IORESOURCE_BITS; + + sbusfb_fill_var(&info->var, dp, 1); + linebytes = of_getintprop_default(dp, "linebytes", + info->var.xres); + + info->var.red.length = info->var.green.length = + info->var.blue.length = info->var.bits_per_pixel; + info->var.red.offset = info->var.green.offset = + info->var.blue.offset = 0; + + par->regs = of_ioremap(&op->resource[0], BWTWO_REGISTER_OFFSET, + sizeof(struct bw2_regs), "bw2 regs"); + if (!par->regs) + goto out_release_fb; + + if (!of_find_property(dp, "width", NULL)) { + err = bw2_do_default_mode(par, info, &linebytes); + if (err) + goto out_unmap_regs; + } + + info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); + + info->flags = FBINFO_DEFAULT; + info->fbops = &bw2_ops; + + info->screen_base = of_ioremap(&op->resource[0], 0, + info->fix.smem_len, "bw2 ram"); + if (!info->screen_base) { + err = -ENOMEM; + goto out_unmap_regs; + } + + bw2_blank(FB_BLANK_UNBLANK, info); + + bw2_init_fix(info, linebytes); + + err = register_framebuffer(info); + if (err < 0) + goto out_unmap_screen; + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: bwtwo at %lx:%lx\n", + dp->full_name, par->which_io, info->fix.smem_start); + + return 0; + +out_unmap_screen: + of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len); + +out_unmap_regs: + of_iounmap(&op->resource[0], par->regs, sizeof(struct bw2_regs)); + +out_release_fb: + framebuffer_release(info); + +out_err: + return err; +} + +static int bw2_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct bw2_par *par = info->par; + + unregister_framebuffer(info); + + of_iounmap(&op->resource[0], par->regs, sizeof(struct bw2_regs)); + of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id bw2_match[] = { + { + .name = "bwtwo", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bw2_match); + +static struct platform_driver bw2_driver = { + .driver = { + .name = "bw2", + .owner = THIS_MODULE, + .of_match_table = bw2_match, + }, + .probe = bw2_probe, + .remove = bw2_remove, +}; + +static int __init bw2_init(void) +{ + if (fb_get_options("bw2fb", NULL)) + return -ENODEV; + + return platform_driver_register(&bw2_driver); +} + +static void __exit bw2_exit(void) +{ + platform_driver_unregister(&bw2_driver); +} + +module_init(bw2_init); +module_exit(bw2_exit); + +MODULE_DESCRIPTION("framebuffer driver for BWTWO chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/c2p.h b/drivers/video/fbdev/c2p.h new file mode 100644 index 000000000000..6c38d40427d8 --- /dev/null +++ b/drivers/video/fbdev/c2p.h @@ -0,0 +1,19 @@ +/* + * Fast C2P (Chunky-to-Planar) Conversion + * + * Copyright (C) 2003-2008 Geert Uytterhoeven + * + * 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/types.h> + +extern void c2p_planar(void *dst, const void *src, u32 dx, u32 dy, u32 width, + u32 height, u32 dst_nextline, u32 dst_nextplane, + u32 src_nextline, u32 bpp); + +extern void c2p_iplan2(void *dst, const void *src, u32 dx, u32 dy, u32 width, + u32 height, u32 dst_nextline, u32 src_nextline, + u32 bpp); diff --git a/drivers/video/fbdev/c2p_core.h b/drivers/video/fbdev/c2p_core.h new file mode 100644 index 000000000000..e1035a865fb9 --- /dev/null +++ b/drivers/video/fbdev/c2p_core.h @@ -0,0 +1,153 @@ +/* + * Fast C2P (Chunky-to-Planar) Conversion + * + * Copyright (C) 2003-2008 Geert Uytterhoeven + * + * NOTES: + * - This code was inspired by Scout's C2P tutorial + * - It assumes to run on a big endian system + * + * 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. + */ + + + /* + * Basic transpose step + */ + +static inline void _transp(u32 d[], unsigned int i1, unsigned int i2, + unsigned int shift, u32 mask) +{ + u32 t = (d[i1] ^ (d[i2] >> shift)) & mask; + + d[i1] ^= t; + d[i2] ^= t << shift; +} + + +extern void c2p_unsupported(void); + +static inline u32 get_mask(unsigned int n) +{ + switch (n) { + case 1: + return 0x55555555; + + case 2: + return 0x33333333; + + case 4: + return 0x0f0f0f0f; + + case 8: + return 0x00ff00ff; + + case 16: + return 0x0000ffff; + } + + c2p_unsupported(); + return 0; +} + + + /* + * Transpose operations on 8 32-bit words + */ + +static inline void transp8(u32 d[], unsigned int n, unsigned int m) +{ + u32 mask = get_mask(n); + + switch (m) { + case 1: + /* First n x 1 block */ + _transp(d, 0, 1, n, mask); + /* Second n x 1 block */ + _transp(d, 2, 3, n, mask); + /* Third n x 1 block */ + _transp(d, 4, 5, n, mask); + /* Fourth n x 1 block */ + _transp(d, 6, 7, n, mask); + return; + + case 2: + /* First n x 2 block */ + _transp(d, 0, 2, n, mask); + _transp(d, 1, 3, n, mask); + /* Second n x 2 block */ + _transp(d, 4, 6, n, mask); + _transp(d, 5, 7, n, mask); + return; + + case 4: + /* Single n x 4 block */ + _transp(d, 0, 4, n, mask); + _transp(d, 1, 5, n, mask); + _transp(d, 2, 6, n, mask); + _transp(d, 3, 7, n, mask); + return; + } + + c2p_unsupported(); +} + + + /* + * Transpose operations on 4 32-bit words + */ + +static inline void transp4(u32 d[], unsigned int n, unsigned int m) +{ + u32 mask = get_mask(n); + + switch (m) { + case 1: + /* First n x 1 block */ + _transp(d, 0, 1, n, mask); + /* Second n x 1 block */ + _transp(d, 2, 3, n, mask); + return; + + case 2: + /* Single n x 2 block */ + _transp(d, 0, 2, n, mask); + _transp(d, 1, 3, n, mask); + return; + } + + c2p_unsupported(); +} + + + /* + * Transpose operations on 4 32-bit words (reverse order) + */ + +static inline void transp4x(u32 d[], unsigned int n, unsigned int m) +{ + u32 mask = get_mask(n); + + switch (m) { + case 2: + /* Single n x 2 block */ + _transp(d, 2, 0, n, mask); + _transp(d, 3, 1, n, mask); + return; + } + + c2p_unsupported(); +} + + + /* + * Compose two values, using a bitmask as decision value + * This is equivalent to (a & mask) | (b & ~mask) + */ + +static inline u32 comp(u32 a, u32 b, u32 mask) +{ + return ((a ^ b) & mask) ^ b; +} diff --git a/drivers/video/fbdev/c2p_iplan2.c b/drivers/video/fbdev/c2p_iplan2.c new file mode 100644 index 000000000000..19156dc6158c --- /dev/null +++ b/drivers/video/fbdev/c2p_iplan2.c @@ -0,0 +1,153 @@ +/* + * Fast C2P (Chunky-to-Planar) Conversion + * + * Copyright (C) 2003-2008 Geert Uytterhoeven + * + * 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/string.h> + +#include <asm/unaligned.h> + +#include "c2p.h" +#include "c2p_core.h" + + + /* + * Perform a full C2P step on 16 8-bit pixels, stored in 4 32-bit words + * containing + * - 16 8-bit chunky pixels on input + * - permutated planar data (2 planes per 32-bit word) on output + */ + +static void c2p_16x8(u32 d[4]) +{ + transp4(d, 8, 2); + transp4(d, 1, 2); + transp4x(d, 16, 2); + transp4x(d, 2, 2); + transp4(d, 4, 1); +} + + + /* + * Array containing the permutation indices of the planar data after c2p + */ + +static const int perm_c2p_16x8[4] = { 1, 3, 0, 2 }; + + + /* + * Store a full block of iplan2 data after c2p conversion + */ + +static inline void store_iplan2(void *dst, u32 bpp, u32 d[4]) +{ + int i; + + for (i = 0; i < bpp/2; i++, dst += 4) + put_unaligned_be32(d[perm_c2p_16x8[i]], dst); +} + + + /* + * Store a partial block of iplan2 data after c2p conversion + */ + +static inline void store_iplan2_masked(void *dst, u32 bpp, u32 d[4], u32 mask) +{ + int i; + + for (i = 0; i < bpp/2; i++, dst += 4) + put_unaligned_be32(comp(d[perm_c2p_16x8[i]], + get_unaligned_be32(dst), mask), + dst); +} + + + /* + * c2p_iplan2 - Copy 8-bit chunky image data to an interleaved planar + * frame buffer with 2 bytes of interleave + * @dst: Starting address of the planar frame buffer + * @dx: Horizontal destination offset (in pixels) + * @dy: Vertical destination offset (in pixels) + * @width: Image width (in pixels) + * @height: Image height (in pixels) + * @dst_nextline: Frame buffer offset to the next line (in bytes) + * @src_nextline: Image offset to the next line (in bytes) + * @bpp: Bits per pixel of the planar frame buffer (2, 4, or 8) + */ + +void c2p_iplan2(void *dst, const void *src, u32 dx, u32 dy, u32 width, + u32 height, u32 dst_nextline, u32 src_nextline, u32 bpp) +{ + union { + u8 pixels[16]; + u32 words[4]; + } d; + u32 dst_idx, first, last, w; + const u8 *c; + void *p; + + dst += dy*dst_nextline+(dx & ~15)*bpp; + dst_idx = dx % 16; + first = 0xffffU >> dst_idx; + first |= first << 16; + last = 0xffffU ^ (0xffffU >> ((dst_idx+width) % 16)); + last |= last << 16; + while (height--) { + c = src; + p = dst; + w = width; + if (dst_idx+width <= 16) { + /* Single destination word */ + first &= last; + memset(d.pixels, 0, sizeof(d)); + memcpy(d.pixels+dst_idx, c, width); + c += width; + c2p_16x8(d.words); + store_iplan2_masked(p, bpp, d.words, first); + p += bpp*2; + } else { + /* Multiple destination words */ + w = width; + /* Leading bits */ + if (dst_idx) { + w = 16 - dst_idx; + memset(d.pixels, 0, dst_idx); + memcpy(d.pixels+dst_idx, c, w); + c += w; + c2p_16x8(d.words); + store_iplan2_masked(p, bpp, d.words, first); + p += bpp*2; + w = width-w; + } + /* Main chunk */ + while (w >= 16) { + memcpy(d.pixels, c, 16); + c += 16; + c2p_16x8(d.words); + store_iplan2(p, bpp, d.words); + p += bpp*2; + w -= 16; + } + /* Trailing bits */ + w %= 16; + if (w > 0) { + memcpy(d.pixels, c, w); + memset(d.pixels+w, 0, 16-w); + c2p_16x8(d.words); + store_iplan2_masked(p, bpp, d.words, last); + } + } + src += src_nextline; + dst += dst_nextline; + } +} +EXPORT_SYMBOL_GPL(c2p_iplan2); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/c2p_planar.c b/drivers/video/fbdev/c2p_planar.c new file mode 100644 index 000000000000..ec7ac8526f06 --- /dev/null +++ b/drivers/video/fbdev/c2p_planar.c @@ -0,0 +1,156 @@ +/* + * Fast C2P (Chunky-to-Planar) Conversion + * + * Copyright (C) 2003-2008 Geert Uytterhoeven + * + * 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/string.h> + +#include <asm/unaligned.h> + +#include "c2p.h" +#include "c2p_core.h" + + + /* + * Perform a full C2P step on 32 8-bit pixels, stored in 8 32-bit words + * containing + * - 32 8-bit chunky pixels on input + * - permutated planar data (1 plane per 32-bit word) on output + */ + +static void c2p_32x8(u32 d[8]) +{ + transp8(d, 16, 4); + transp8(d, 8, 2); + transp8(d, 4, 1); + transp8(d, 2, 4); + transp8(d, 1, 2); +} + + + /* + * Array containing the permutation indices of the planar data after c2p + */ + +static const int perm_c2p_32x8[8] = { 7, 5, 3, 1, 6, 4, 2, 0 }; + + + /* + * Store a full block of planar data after c2p conversion + */ + +static inline void store_planar(void *dst, u32 dst_inc, u32 bpp, u32 d[8]) +{ + int i; + + for (i = 0; i < bpp; i++, dst += dst_inc) + put_unaligned_be32(d[perm_c2p_32x8[i]], dst); +} + + + /* + * Store a partial block of planar data after c2p conversion + */ + +static inline void store_planar_masked(void *dst, u32 dst_inc, u32 bpp, + u32 d[8], u32 mask) +{ + int i; + + for (i = 0; i < bpp; i++, dst += dst_inc) + put_unaligned_be32(comp(d[perm_c2p_32x8[i]], + get_unaligned_be32(dst), mask), + dst); +} + + + /* + * c2p_planar - Copy 8-bit chunky image data to a planar frame buffer + * @dst: Starting address of the planar frame buffer + * @dx: Horizontal destination offset (in pixels) + * @dy: Vertical destination offset (in pixels) + * @width: Image width (in pixels) + * @height: Image height (in pixels) + * @dst_nextline: Frame buffer offset to the next line (in bytes) + * @dst_nextplane: Frame buffer offset to the next plane (in bytes) + * @src_nextline: Image offset to the next line (in bytes) + * @bpp: Bits per pixel of the planar frame buffer (1-8) + */ + +void c2p_planar(void *dst, const void *src, u32 dx, u32 dy, u32 width, + u32 height, u32 dst_nextline, u32 dst_nextplane, + u32 src_nextline, u32 bpp) +{ + union { + u8 pixels[32]; + u32 words[8]; + } d; + u32 dst_idx, first, last, w; + const u8 *c; + void *p; + + dst += dy*dst_nextline+(dx & ~31); + dst_idx = dx % 32; + first = 0xffffffffU >> dst_idx; + last = ~(0xffffffffU >> ((dst_idx+width) % 32)); + while (height--) { + c = src; + p = dst; + w = width; + if (dst_idx+width <= 32) { + /* Single destination word */ + first &= last; + memset(d.pixels, 0, sizeof(d)); + memcpy(d.pixels+dst_idx, c, width); + c += width; + c2p_32x8(d.words); + store_planar_masked(p, dst_nextplane, bpp, d.words, + first); + p += 4; + } else { + /* Multiple destination words */ + w = width; + /* Leading bits */ + if (dst_idx) { + w = 32 - dst_idx; + memset(d.pixels, 0, dst_idx); + memcpy(d.pixels+dst_idx, c, w); + c += w; + c2p_32x8(d.words); + store_planar_masked(p, dst_nextplane, bpp, + d.words, first); + p += 4; + w = width-w; + } + /* Main chunk */ + while (w >= 32) { + memcpy(d.pixels, c, 32); + c += 32; + c2p_32x8(d.words); + store_planar(p, dst_nextplane, bpp, d.words); + p += 4; + w -= 32; + } + /* Trailing bits */ + w %= 32; + if (w > 0) { + memcpy(d.pixels, c, w); + memset(d.pixels+w, 0, 32-w); + c2p_32x8(d.words); + store_planar_masked(p, dst_nextplane, bpp, + d.words, last); + } + } + src += src_nextline; + dst += dst_nextline; + } +} +EXPORT_SYMBOL_GPL(c2p_planar); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/carminefb.c b/drivers/video/fbdev/carminefb.c new file mode 100644 index 000000000000..65f7c15f5fdb --- /dev/null +++ b/drivers/video/fbdev/carminefb.c @@ -0,0 +1,788 @@ +/* + * Frame buffer driver for the Carmine GPU. + * + * The driver configures the GPU as follows + * - FB0 is display 0 with unique memory area + * - FB1 is display 1 with unique memory area + * - both display use 32 bit colors + */ +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include "carminefb.h" +#include "carminefb_regs.h" + +#if !defined(__LITTLE_ENDIAN) && !defined(__BIG_ENDIAN) +#error "The endianness of the target host has not been defined." +#endif + +/* + * The initial video mode can be supplied via two different ways: + * - as a string that is passed to fb_find_mode() (module option fb_mode_str) + * - as an integer that picks the video mode from carmine_modedb[] (module + * option fb_mode) + * + * If nothing is used than the initial video mode will be the + * CARMINEFB_DEFAULT_VIDEO_MODE member of the carmine_modedb[]. + */ +#define CARMINEFB_DEFAULT_VIDEO_MODE 1 + +static unsigned int fb_mode = CARMINEFB_DEFAULT_VIDEO_MODE; +module_param(fb_mode, uint, 0444); +MODULE_PARM_DESC(fb_mode, "Initial video mode as integer."); + +static char *fb_mode_str; +module_param(fb_mode_str, charp, 0444); +MODULE_PARM_DESC(fb_mode_str, "Initial video mode in characters."); + +/* + * Carminefb displays: + * 0b000 None + * 0b001 Display 0 + * 0b010 Display 1 + */ +static int fb_displays = CARMINE_USE_DISPLAY0 | CARMINE_USE_DISPLAY1; +module_param(fb_displays, int, 0444); +MODULE_PARM_DESC(fb_displays, "Bit mode, which displays are used"); + +struct carmine_hw { + void __iomem *v_regs; + void __iomem *screen_mem; + struct fb_info *fb[MAX_DISPLAY]; +}; + +struct carmine_resolution { + u32 htp; + u32 hsp; + u32 hsw; + u32 hdp; + u32 vtr; + u32 vsp; + u32 vsw; + u32 vdp; + u32 disp_mode; +}; + +struct carmine_fb { + void __iomem *display_reg; + void __iomem *screen_base; + u32 smem_offset; + u32 cur_mode; + u32 new_mode; + struct carmine_resolution *res; + u32 pseudo_palette[16]; +}; + +static struct fb_fix_screeninfo carminefb_fix = { + .id = "Carmine", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .accel = FB_ACCEL_NONE, +}; + +static const struct fb_videomode carmine_modedb[] = { + { + .name = "640x480", + .xres = 640, + .yres = 480, + }, { + .name = "800x600", + .xres = 800, + .yres = 600, + }, +}; + +static struct carmine_resolution car_modes[] = { + { + /* 640x480 */ + .htp = 800, + .hsp = 672, + .hsw = 96, + .hdp = 640, + .vtr = 525, + .vsp = 490, + .vsw = 2, + .vdp = 480, + .disp_mode = 0x1400, + }, + { + /* 800x600 */ + .htp = 1060, + .hsp = 864, + .hsw = 72, + .hdp = 800, + .vtr = 628, + .vsp = 601, + .vsw = 2, + .vdp = 600, + .disp_mode = 0x0d00, + } +}; + +static int carmine_find_mode(const struct fb_var_screeninfo *var) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(car_modes); i++) + if (car_modes[i].hdp == var->xres && + car_modes[i].vdp == var->yres) + return i; + return -EINVAL; +} + +static void c_set_disp_reg(const struct carmine_fb *par, + u32 offset, u32 val) +{ + writel(val, par->display_reg + offset); +} + +static u32 c_get_disp_reg(const struct carmine_fb *par, + u32 offset) +{ + return readl(par->display_reg + offset); +} + +static void c_set_hw_reg(const struct carmine_hw *hw, + u32 offset, u32 val) +{ + writel(val, hw->v_regs + offset); +} + +static u32 c_get_hw_reg(const struct carmine_hw *hw, + u32 offset) +{ + return readl(hw->v_regs + offset); +} + +static int carmine_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + if (regno >= 16) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + transp >>= 8; + + ((__be32 *)info->pseudo_palette)[regno] = cpu_to_be32(transp << 24 | + red << 0 | green << 8 | blue << 16); + return 0; +} + +static int carmine_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int ret; + + ret = carmine_find_mode(var); + if (ret < 0) + return ret; + + if (var->grayscale || var->rotate || var->nonstd) + return -EINVAL; + + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + var->bits_per_pixel = 32; + +#ifdef __BIG_ENDIAN + var->transp.offset = 24; + var->red.offset = 0; + var->green.offset = 8; + var->blue.offset = 16; +#else + var->transp.offset = 24; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; +#endif + + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + return 0; +} + +static void carmine_init_display_param(struct carmine_fb *par) +{ + u32 width; + u32 height; + u32 param; + u32 window_size; + u32 soffset = par->smem_offset; + + c_set_disp_reg(par, CARMINE_DISP_REG_C_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_MLMR_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_CURSOR_MODE, + CARMINE_CURSOR0_PRIORITY_MASK | + CARMINE_CURSOR1_PRIORITY_MASK | + CARMINE_CURSOR_CUTZ_MASK); + + /* Set default cursor position */ + c_set_disp_reg(par, CARMINE_DISP_REG_CUR1_POS, 0 << 16 | 0); + c_set_disp_reg(par, CARMINE_DISP_REG_CUR2_POS, 0 << 16 | 0); + + /* Set default display mode */ + c_set_disp_reg(par, CARMINE_DISP_REG_L0_EXT_MODE, CARMINE_WINDOW_MODE | + CARMINE_EXT_CMODE_DIRECT24_RGBA); + c_set_disp_reg(par, CARMINE_DISP_REG_L1_EXT_MODE, + CARMINE_EXT_CMODE_DIRECT24_RGBA); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_EXT_MODE, CARMINE_EXTEND_MODE | + CARMINE_EXT_CMODE_DIRECT24_RGBA); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_EXT_MODE, CARMINE_EXTEND_MODE | + CARMINE_EXT_CMODE_DIRECT24_RGBA); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_EXT_MODE, CARMINE_EXTEND_MODE | + CARMINE_EXT_CMODE_DIRECT24_RGBA); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_EXT_MODE, CARMINE_EXTEND_MODE | + CARMINE_EXT_CMODE_DIRECT24_RGBA); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_EXT_MODE, CARMINE_EXTEND_MODE | + CARMINE_EXT_CMODE_DIRECT24_RGBA); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_EXT_MODE, CARMINE_EXTEND_MODE | + CARMINE_EXT_CMODE_DIRECT24_RGBA); + + /* Set default frame size to layer mode register */ + width = par->res->hdp * 4 / CARMINE_DISP_WIDTH_UNIT; + width = width << CARMINE_DISP_WIDTH_SHIFT; + + height = par->res->vdp - 1; + param = width | height; + + c_set_disp_reg(par, CARMINE_DISP_REG_L0_MODE_W_H, param); + c_set_disp_reg(par, CARMINE_DISP_REG_L1_WIDTH, width); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_MODE_W_H, param); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_MODE_W_H, param); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_MODE_W_H, param); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_MODE_W_H, param); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_MODE_W_H, param); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_MODE_W_H, param); + + /* Set default pos and size */ + window_size = (par->res->vdp - 1) << CARMINE_DISP_WIN_H_SHIFT; + window_size |= par->res->hdp; + + c_set_disp_reg(par, CARMINE_DISP_REG_L0_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L0_WIN_SIZE, window_size); + c_set_disp_reg(par, CARMINE_DISP_REG_L1_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L1_WIN_SIZE, window_size); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_WIN_SIZE, window_size); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_WIN_SIZE, window_size); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_WIN_SIZE, window_size); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_WIN_SIZE, window_size); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_WIN_SIZE, window_size); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_WIN_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_WIN_SIZE, window_size); + + /* Set default origin address */ + c_set_disp_reg(par, CARMINE_DISP_REG_L0_ORG_ADR, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L1_ORG_ADR, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_ORG_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_ORG_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_ORG_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_ORG_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_ORG_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_ORG_ADR1, soffset); + + /* Set default display address */ + c_set_disp_reg(par, CARMINE_DISP_REG_L0_DISP_ADR, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_DISP_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_DISP_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_DISP_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_DISP_ADR1, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_DISP_ADR0, soffset); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_DISP_ADR0, soffset); + + /* Set default display position */ + c_set_disp_reg(par, CARMINE_DISP_REG_L0_DISP_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_DISP_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_DISP_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_DISP_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_DISP_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_DISP_POS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_DISP_POS, 0); + + /* Set default blend mode */ + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L0, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L1, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L2, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L3, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L4, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L5, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L6, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_BLEND_MODE_L7, 0); + + /* default transparency mode */ + c_set_disp_reg(par, CARMINE_DISP_REG_L0_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L1_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L2_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L3_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L4_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L5_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L6_TRANS, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L7_TRANS, 0); + + /* Set default read skip parameter */ + c_set_disp_reg(par, CARMINE_DISP_REG_L0RM, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L2RM, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L3RM, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L4RM, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L5RM, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L6RM, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L7RM, 0); + + c_set_disp_reg(par, CARMINE_DISP_REG_L0PX, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L2PX, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L3PX, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L4PX, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L5PX, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L6PX, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L7PX, 0); + + c_set_disp_reg(par, CARMINE_DISP_REG_L0PY, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L2PY, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L3PY, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L4PY, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L5PY, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L6PY, 0); + c_set_disp_reg(par, CARMINE_DISP_REG_L7PY, 0); +} + +static void set_display_parameters(struct carmine_fb *par) +{ + u32 mode; + u32 hdp, vdp, htp, hsp, hsw, vtr, vsp, vsw; + + /* + * display timing. Parameters are decreased by one because hardware + * spec is 0 to (n - 1) + * */ + hdp = par->res->hdp - 1; + vdp = par->res->vdp - 1; + htp = par->res->htp - 1; + hsp = par->res->hsp - 1; + hsw = par->res->hsw - 1; + vtr = par->res->vtr - 1; + vsp = par->res->vsp - 1; + vsw = par->res->vsw - 1; + + c_set_disp_reg(par, CARMINE_DISP_REG_H_TOTAL, + htp << CARMINE_DISP_HTP_SHIFT); + c_set_disp_reg(par, CARMINE_DISP_REG_H_PERIOD, + (hdp << CARMINE_DISP_HDB_SHIFT) | hdp); + c_set_disp_reg(par, CARMINE_DISP_REG_V_H_W_H_POS, + (vsw << CARMINE_DISP_VSW_SHIFT) | + (hsw << CARMINE_DISP_HSW_SHIFT) | + (hsp)); + c_set_disp_reg(par, CARMINE_DISP_REG_V_TOTAL, + vtr << CARMINE_DISP_VTR_SHIFT); + c_set_disp_reg(par, CARMINE_DISP_REG_V_PERIOD_POS, + (vdp << CARMINE_DISP_VDP_SHIFT) | vsp); + + /* clock */ + mode = c_get_disp_reg(par, CARMINE_DISP_REG_DCM1); + mode = (mode & ~CARMINE_DISP_DCM_MASK) | + (par->res->disp_mode & CARMINE_DISP_DCM_MASK); + /* enable video output and layer 0 */ + mode |= CARMINE_DEN | CARMINE_L0E; + c_set_disp_reg(par, CARMINE_DISP_REG_DCM1, mode); +} + +static int carmine_set_par(struct fb_info *info) +{ + struct carmine_fb *par = info->par; + int ret; + + ret = carmine_find_mode(&info->var); + if (ret < 0) + return ret; + + par->new_mode = ret; + if (par->cur_mode != par->new_mode) { + + par->cur_mode = par->new_mode; + par->res = &car_modes[par->new_mode]; + + carmine_init_display_param(par); + set_display_parameters(par); + } + + info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8; + return 0; +} + +static int init_hardware(struct carmine_hw *hw) +{ + u32 flags; + u32 loops; + u32 ret; + + /* Initialize Carmine */ + /* Sets internal clock */ + c_set_hw_reg(hw, CARMINE_CTL_REG + CARMINE_CTL_REG_CLOCK_ENABLE, + CARMINE_DFLT_IP_CLOCK_ENABLE); + + /* Video signal output is turned off */ + c_set_hw_reg(hw, CARMINE_DISP0_REG + CARMINE_DISP_REG_DCM1, 0); + c_set_hw_reg(hw, CARMINE_DISP1_REG + CARMINE_DISP_REG_DCM1, 0); + + /* Software reset */ + c_set_hw_reg(hw, CARMINE_CTL_REG + CARMINE_CTL_REG_SOFTWARE_RESET, 1); + c_set_hw_reg(hw, CARMINE_CTL_REG + CARMINE_CTL_REG_SOFTWARE_RESET, 0); + + /* I/O mode settings */ + flags = CARMINE_DFLT_IP_DCTL_IO_CONT1 << 16 | + CARMINE_DFLT_IP_DCTL_IO_CONT0; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_IOCONT1_IOCONT0, + flags); + + /* DRAM initial sequence */ + flags = CARMINE_DFLT_IP_DCTL_MODE << 16 | CARMINE_DFLT_IP_DCTL_ADD; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_MODE_ADD, + flags); + + flags = CARMINE_DFLT_IP_DCTL_SET_TIME1 << 16 | + CARMINE_DFLT_IP_DCTL_EMODE; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_SETTIME1_EMODE, + flags); + + flags = CARMINE_DFLT_IP_DCTL_REFRESH << 16 | + CARMINE_DFLT_IP_DCTL_SET_TIME2; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_REFRESH_SETTIME2, + flags); + + flags = CARMINE_DFLT_IP_DCTL_RESERVE2 << 16 | + CARMINE_DFLT_IP_DCTL_FIFO_DEPTH; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_RSV2_RSV1, flags); + + flags = CARMINE_DFLT_IP_DCTL_DDRIF2 << 16 | CARMINE_DFLT_IP_DCTL_DDRIF1; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_DDRIF2_DDRIF1, + flags); + + flags = CARMINE_DFLT_IP_DCTL_RESERVE0 << 16 | + CARMINE_DFLT_IP_DCTL_STATES; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_RSV0_STATES, + flags); + + /* Executes DLL reset */ + if (CARMINE_DCTL_DLL_RESET) { + for (loops = 0; loops < CARMINE_DCTL_INIT_WAIT_LIMIT; loops++) { + + ret = c_get_hw_reg(hw, CARMINE_DCTL_REG + + CARMINE_DCTL_REG_RSV0_STATES); + ret &= CARMINE_DCTL_REG_STATES_MASK; + if (!ret) + break; + + mdelay(CARMINE_DCTL_INIT_WAIT_INTERVAL); + } + + if (loops >= CARMINE_DCTL_INIT_WAIT_LIMIT) { + printk(KERN_ERR "DRAM init failed\n"); + return -EIO; + } + } + + flags = CARMINE_DFLT_IP_DCTL_MODE_AFT_RST << 16 | + CARMINE_DFLT_IP_DCTL_ADD; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_MODE_ADD, flags); + + flags = CARMINE_DFLT_IP_DCTL_RESERVE0 << 16 | + CARMINE_DFLT_IP_DCTL_STATES_AFT_RST; + c_set_hw_reg(hw, CARMINE_DCTL_REG + CARMINE_DCTL_REG_RSV0_STATES, + flags); + + /* Initialize the write back register */ + c_set_hw_reg(hw, CARMINE_WB_REG + CARMINE_WB_REG_WBM, + CARMINE_WB_REG_WBM_DEFAULT); + + /* Initialize the Kottos registers */ + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_VRINTM, 0); + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_VRERRM, 0); + + /* Set DC offsets */ + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_DC_OFFSET_PX, 0); + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_DC_OFFSET_PY, 0); + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_DC_OFFSET_LX, 0); + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_DC_OFFSET_LY, 0); + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_DC_OFFSET_TX, 0); + c_set_hw_reg(hw, CARMINE_GRAPH_REG + CARMINE_GRAPH_REG_DC_OFFSET_TY, 0); + return 0; +} + +static struct fb_ops carminefb_ops = { + .owner = THIS_MODULE, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + + .fb_check_var = carmine_check_var, + .fb_set_par = carmine_set_par, + .fb_setcolreg = carmine_setcolreg, +}; + +static int alloc_carmine_fb(void __iomem *regs, void __iomem *smem_base, + int smem_offset, struct device *device, + struct fb_info **rinfo) +{ + int ret; + struct fb_info *info; + struct carmine_fb *par; + + info = framebuffer_alloc(sizeof *par, device); + if (!info) + return -ENOMEM; + + par = info->par; + par->display_reg = regs; + par->smem_offset = smem_offset; + + info->screen_base = smem_base + smem_offset; + info->screen_size = CARMINE_DISPLAY_MEM; + info->fbops = &carminefb_ops; + + info->fix = carminefb_fix; + info->pseudo_palette = par->pseudo_palette; + info->flags = FBINFO_DEFAULT; + + ret = fb_alloc_cmap(&info->cmap, 256, 1); + if (ret < 0) + goto err_free_fb; + + if (fb_mode >= ARRAY_SIZE(carmine_modedb)) + fb_mode = CARMINEFB_DEFAULT_VIDEO_MODE; + + par->cur_mode = par->new_mode = ~0; + + ret = fb_find_mode(&info->var, info, fb_mode_str, carmine_modedb, + ARRAY_SIZE(carmine_modedb), + &carmine_modedb[fb_mode], 32); + if (!ret || ret == 4) { + ret = -EINVAL; + goto err_dealloc_cmap; + } + + fb_videomode_to_modelist(carmine_modedb, ARRAY_SIZE(carmine_modedb), + &info->modelist); + + ret = register_framebuffer(info); + if (ret < 0) + goto err_dealloc_cmap; + + fb_info(info, "%s frame buffer device\n", info->fix.id); + + *rinfo = info; + return 0; + +err_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); +err_free_fb: + framebuffer_release(info); + return ret; +} + +static void cleanup_fb_device(struct fb_info *info) +{ + if (info) { + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } +} + +static int carminefb_probe(struct pci_dev *dev, const struct pci_device_id *ent) +{ + struct carmine_hw *hw; + struct device *device = &dev->dev; + struct fb_info *info; + int ret; + + ret = pci_enable_device(dev); + if (ret) + return ret; + + ret = -ENOMEM; + hw = kzalloc(sizeof *hw, GFP_KERNEL); + if (!hw) + goto err_enable_pci; + + carminefb_fix.mmio_start = pci_resource_start(dev, CARMINE_CONFIG_BAR); + carminefb_fix.mmio_len = pci_resource_len(dev, CARMINE_CONFIG_BAR); + + if (!request_mem_region(carminefb_fix.mmio_start, + carminefb_fix.mmio_len, + "carminefb regbase")) { + printk(KERN_ERR "carminefb: Can't reserve regbase.\n"); + ret = -EBUSY; + goto err_free_hw; + } + hw->v_regs = ioremap_nocache(carminefb_fix.mmio_start, + carminefb_fix.mmio_len); + if (!hw->v_regs) { + printk(KERN_ERR "carminefb: Can't remap %s register.\n", + carminefb_fix.id); + goto err_free_reg_mmio; + } + + carminefb_fix.smem_start = pci_resource_start(dev, CARMINE_MEMORY_BAR); + carminefb_fix.smem_len = pci_resource_len(dev, CARMINE_MEMORY_BAR); + + /* The memory area tends to be very large (256 MiB). Remap only what + * is required for that largest resolution to avoid remaps at run + * time + */ + if (carminefb_fix.smem_len > CARMINE_TOTAL_DIPLAY_MEM) + carminefb_fix.smem_len = CARMINE_TOTAL_DIPLAY_MEM; + + else if (carminefb_fix.smem_len < CARMINE_TOTAL_DIPLAY_MEM) { + printk(KERN_ERR "carminefb: Memory bar is only %d bytes, %d " + "are required.", carminefb_fix.smem_len, + CARMINE_TOTAL_DIPLAY_MEM); + goto err_unmap_vregs; + } + + if (!request_mem_region(carminefb_fix.smem_start, + carminefb_fix.smem_len, "carminefb smem")) { + printk(KERN_ERR "carminefb: Can't reserve smem.\n"); + goto err_unmap_vregs; + } + + hw->screen_mem = ioremap_nocache(carminefb_fix.smem_start, + carminefb_fix.smem_len); + if (!hw->screen_mem) { + printk(KERN_ERR "carmine: Can't ioremap smem area.\n"); + goto err_reg_smem; + } + + ret = init_hardware(hw); + if (ret) + goto err_unmap_screen; + + info = NULL; + if (fb_displays & CARMINE_USE_DISPLAY0) { + ret = alloc_carmine_fb(hw->v_regs + CARMINE_DISP0_REG, + hw->screen_mem, CARMINE_DISPLAY_MEM * 0, + device, &info); + if (ret) + goto err_deinit_hw; + } + + hw->fb[0] = info; + + info = NULL; + if (fb_displays & CARMINE_USE_DISPLAY1) { + ret = alloc_carmine_fb(hw->v_regs + CARMINE_DISP1_REG, + hw->screen_mem, CARMINE_DISPLAY_MEM * 1, + device, &info); + if (ret) + goto err_cleanup_fb0; + } + + hw->fb[1] = info; + info = NULL; + + pci_set_drvdata(dev, hw); + return 0; + +err_cleanup_fb0: + cleanup_fb_device(hw->fb[0]); +err_deinit_hw: + /* disable clock, etc */ + c_set_hw_reg(hw, CARMINE_CTL_REG + CARMINE_CTL_REG_CLOCK_ENABLE, 0); +err_unmap_screen: + iounmap(hw->screen_mem); +err_reg_smem: + release_mem_region(carminefb_fix.smem_start, carminefb_fix.smem_len); +err_unmap_vregs: + iounmap(hw->v_regs); +err_free_reg_mmio: + release_mem_region(carminefb_fix.mmio_start, carminefb_fix.mmio_len); +err_free_hw: + kfree(hw); +err_enable_pci: + pci_disable_device(dev); + return ret; +} + +static void carminefb_remove(struct pci_dev *dev) +{ + struct carmine_hw *hw = pci_get_drvdata(dev); + struct fb_fix_screeninfo fix; + int i; + + /* in case we use only fb1 and not fb1 */ + if (hw->fb[0]) + fix = hw->fb[0]->fix; + else + fix = hw->fb[1]->fix; + + /* deactivate display(s) and switch clocks */ + c_set_hw_reg(hw, CARMINE_DISP0_REG + CARMINE_DISP_REG_DCM1, 0); + c_set_hw_reg(hw, CARMINE_DISP1_REG + CARMINE_DISP_REG_DCM1, 0); + c_set_hw_reg(hw, CARMINE_CTL_REG + CARMINE_CTL_REG_CLOCK_ENABLE, 0); + + for (i = 0; i < MAX_DISPLAY; i++) + cleanup_fb_device(hw->fb[i]); + + iounmap(hw->screen_mem); + release_mem_region(fix.smem_start, fix.smem_len); + iounmap(hw->v_regs); + release_mem_region(fix.mmio_start, fix.mmio_len); + + pci_disable_device(dev); + kfree(hw); +} + +#define PCI_VENDOR_ID_FUJITU_LIMITED 0x10cf +static struct pci_device_id carmine_devices[] = { +{ + PCI_DEVICE(PCI_VENDOR_ID_FUJITU_LIMITED, 0x202b)}, + {0, 0, 0, 0, 0, 0, 0} +}; + +MODULE_DEVICE_TABLE(pci, carmine_devices); + +static struct pci_driver carmine_pci_driver = { + .name = "carminefb", + .id_table = carmine_devices, + .probe = carminefb_probe, + .remove = carminefb_remove, +}; + +static int __init carminefb_init(void) +{ + if (!(fb_displays & + (CARMINE_USE_DISPLAY0 | CARMINE_USE_DISPLAY1))) { + printk(KERN_ERR "If you disable both displays than you don't " + "need the driver at all\n"); + return -EINVAL; + } + return pci_register_driver(&carmine_pci_driver); +} +module_init(carminefb_init); + +static void __exit carminefb_cleanup(void) +{ + pci_unregister_driver(&carmine_pci_driver); +} +module_exit(carminefb_cleanup); + +MODULE_AUTHOR("Sebastian Siewior <bigeasy@linutronix.de>"); +MODULE_DESCRIPTION("Framebuffer driver for Fujitsu Carmine based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/carminefb.h b/drivers/video/fbdev/carminefb.h new file mode 100644 index 000000000000..05306de0c6b6 --- /dev/null +++ b/drivers/video/fbdev/carminefb.h @@ -0,0 +1,64 @@ +#ifndef CARMINE_CARMINE_H +#define CARMINE_CARMINE_H + +#define CARMINE_MEMORY_BAR 2 +#define CARMINE_CONFIG_BAR 3 + +#define MAX_DISPLAY 2 +#define CARMINE_DISPLAY_MEM (800 * 600 * 4) +#define CARMINE_TOTAL_DIPLAY_MEM (CARMINE_DISPLAY_MEM * MAX_DISPLAY) + +#define CARMINE_USE_DISPLAY0 (1 << 0) +#define CARMINE_USE_DISPLAY1 (1 << 1) + +/* + * This values work on the eval card. Custom boards may use different timings, + * here an example :) + */ + +/* DRAM initialization values */ +#ifdef CONFIG_FB_CARMINE_DRAM_EVAL + +#define CARMINE_DFLT_IP_CLOCK_ENABLE (0x03ff) +#define CARMINE_DFLT_IP_DCTL_ADD (0x05c3) +#define CARMINE_DFLT_IP_DCTL_MODE (0x0121) +#define CARMINE_DFLT_IP_DCTL_EMODE (0x8000) +#define CARMINE_DFLT_IP_DCTL_SET_TIME1 (0x4749) +#define CARMINE_DFLT_IP_DCTL_SET_TIME2 (0x2a22) +#define CARMINE_DFLT_IP_DCTL_REFRESH (0x0042) +#define CARMINE_DFLT_IP_DCTL_STATES (0x0003) +#define CARMINE_DFLT_IP_DCTL_RESERVE0 (0x0020) +#define CARMINE_DFLT_IP_DCTL_FIFO_DEPTH (0x000f) +#define CARMINE_DFLT_IP_DCTL_RESERVE2 (0x0000) +#define CARMINE_DFLT_IP_DCTL_DDRIF1 (0x6646) +#define CARMINE_DFLT_IP_DCTL_DDRIF2 (0x0055) +#define CARMINE_DFLT_IP_DCTL_MODE_AFT_RST (0x0021) +#define CARMINE_DFLT_IP_DCTL_STATES_AFT_RST (0x0002) +#define CARMINE_DFLT_IP_DCTL_IO_CONT0 (0x0555) +#define CARMINE_DFLT_IP_DCTL_IO_CONT1 (0x0555) +#define CARMINE_DCTL_DLL_RESET (1) +#endif + +#ifdef CONFIG_CARMINE_DRAM_CUSTOM + +#define CARMINE_DFLT_IP_CLOCK_ENABLE (0x03ff) +#define CARMINE_DFLT_IP_DCTL_ADD (0x03b2) +#define CARMINE_DFLT_IP_DCTL_MODE (0x0161) +#define CARMINE_DFLT_IP_DCTL_EMODE (0x8000) +#define CARMINE_DFLT_IP_DCTL_SET_TIME1 (0x2628) +#define CARMINE_DFLT_IP_DCTL_SET_TIME2 (0x1a09) +#define CARMINE_DFLT_IP_DCTL_REFRESH (0x00fe) +#define CARMINE_DFLT_IP_DCTL_STATES (0x0003) +#define CARMINE_DFLT_IP_DCTL_RESERVE0 (0x0020) +#define CARMINE_DFLT_IP_DCTL_FIFO_DEPTH (0x000f) +#define CARMINE_DFLT_IP_DCTL_RESERVE2 (0x0000) +#define CARMINE_DFLT_IP_DCTL_DDRIF1 (0x0646) +#define CARMINE_DFLT_IP_DCTL_DDRIF2 (0x55aa) +#define CARMINE_DFLT_IP_DCTL_MODE_AFT_RST (0x0061) +#define CARMINE_DFLT_IP_DCTL_STATES_AFT_RST (0x0002) +#define CARMINE_DFLT_IP_DCTL_IO_CONT0 (0x0555) +#define CARMINE_DFLT_IP_DCTL_IO_CONT1 (0x0555) +#define CARMINE_DCTL_DLL_RESET (1) +#endif + +#endif diff --git a/drivers/video/fbdev/carminefb_regs.h b/drivers/video/fbdev/carminefb_regs.h new file mode 100644 index 000000000000..045215600b73 --- /dev/null +++ b/drivers/video/fbdev/carminefb_regs.h @@ -0,0 +1,159 @@ +#ifndef _CARMINEFB_REGS_H +#define _CARMINEFB_REGS_H + +#define CARMINE_OVERLAY_EXT_MODE (0x00000002) +#define CARMINE_GRAPH_REG (0x00000000) +#define CARMINE_DISP0_REG (0x00100000) +#define CARMINE_DISP1_REG (0x00140000) +#define CARMINE_WB_REG (0x00180000) +#define CARMINE_DCTL_REG (0x00300000) +#define CARMINE_CTL_REG (0x00400000) +#define CARMINE_WINDOW_MODE (0x00000001) +#define CARMINE_EXTEND_MODE (CARMINE_WINDOW_MODE | \ + CARMINE_OVERLAY_EXT_MODE) +#define CARMINE_L0E (1 << 16) +#define CARMINE_L2E (1 << 18) +#define CARMINE_DEN (1 << 31) + +#define CARMINE_EXT_CMODE_DIRECT24_RGBA (0xC0000000) +#define CARMINE_DCTL_REG_MODE_ADD (0x00) +#define CARMINE_DCTL_REG_SETTIME1_EMODE (0x04) +#define CARMINE_DCTL_REG_REFRESH_SETTIME2 (0x08) +#define CARMINE_DCTL_REG_RSV0_STATES (0x0C) +#define CARMINE_DCTL_REG_RSV2_RSV1 (0x10) +#define CARMINE_DCTL_REG_DDRIF2_DDRIF1 (0x14) +#define CARMINE_DCTL_REG_IOCONT1_IOCONT0 (0x24) +#define CARMINE_DCTL_REG_STATES_MASK (0x000F) +#define CARMINE_DCTL_INIT_WAIT_INTERVAL (1) +#define CARMINE_DCTL_INIT_WAIT_LIMIT (5000) +#define CARMINE_WB_REG_WBM_DEFAULT (0x0001c020) +#define CARMINE_DISP_REG_L0RM (0x1880) +#define CARMINE_DISP_REG_L0PX (0x1884) +#define CARMINE_DISP_REG_L0PY (0x1888) +#define CARMINE_DISP_REG_L2RM (0x18A0) +#define CARMINE_DISP_REG_L2PX (0x18A4) +#define CARMINE_DISP_REG_L2PY (0x18A8) +#define CARMINE_DISP_REG_L3RM (0x18B0) +#define CARMINE_DISP_REG_L3PX (0x18B4) +#define CARMINE_DISP_REG_L3PY (0x18B8) +#define CARMINE_DISP_REG_L4RM (0x18C0) +#define CARMINE_DISP_REG_L4PX (0x18C4) +#define CARMINE_DISP_REG_L4PY (0x18C8) +#define CARMINE_DISP_REG_L5RM (0x18D0) +#define CARMINE_DISP_REG_L5PX (0x18D4) +#define CARMINE_DISP_REG_L5PY (0x18D8) +#define CARMINE_DISP_REG_L6RM (0x1924) +#define CARMINE_DISP_REG_L6PX (0x1928) +#define CARMINE_DISP_REG_L6PY (0x192C) +#define CARMINE_DISP_REG_L7RM (0x1964) +#define CARMINE_DISP_REG_L7PX (0x1968) +#define CARMINE_DISP_REG_L7PY (0x196C) +#define CARMINE_WB_REG_WBM (0x0004) +#define CARMINE_DISP_HTP_SHIFT (16) +#define CARMINE_DISP_HDB_SHIFT (16) +#define CARMINE_DISP_HSW_SHIFT (16) +#define CARMINE_DISP_VSW_SHIFT (24) +#define CARMINE_DISP_VTR_SHIFT (16) +#define CARMINE_DISP_VDP_SHIFT (16) +#define CARMINE_CURSOR_CUTZ_MASK (0x00000100) +#define CARMINE_CURSOR0_PRIORITY_MASK (0x00010000) +#define CARMINE_CURSOR1_PRIORITY_MASK (0x00020000) +#define CARMINE_DISP_WIDTH_SHIFT (16) +#define CARMINE_DISP_WIN_H_SHIFT (16) +#define CARMINE_DISP_REG_H_TOTAL (0x0004) +#define CARMINE_DISP_REG_H_PERIOD (0x0008) +#define CARMINE_DISP_REG_V_H_W_H_POS (0x000C) +#define CARMINE_DISP_REG_V_TOTAL (0x0010) +#define CARMINE_DISP_REG_V_PERIOD_POS (0x0014) +#define CARMINE_DISP_REG_L0_MODE_W_H (0x0020) +#define CARMINE_DISP_REG_L0_ORG_ADR (0x0024) +#define CARMINE_DISP_REG_L0_DISP_ADR (0x0028) +#define CARMINE_DISP_REG_L0_DISP_POS (0x002C) +#define CARMINE_DISP_REG_L1_WIDTH (0x0030) +#define CARMINE_DISP_REG_L1_ORG_ADR (0x0034) +#define CARMINE_DISP_REG_L2_MODE_W_H (0x0040) +#define CARMINE_DISP_REG_L2_ORG_ADR1 (0x0044) +#define CARMINE_DISP_REG_L2_DISP_ADR1 (0x0048) +#define CARMINE_DISP_REG_L2_DISP_POS (0x0054) +#define CARMINE_DISP_REG_L3_MODE_W_H (0x0058) +#define CARMINE_DISP_REG_L3_ORG_ADR1 (0x005C) +#define CARMINE_DISP_REG_L3_DISP_ADR1 (0x0060) +#define CARMINE_DISP_REG_L3_DISP_POS (0x006C) +#define CARMINE_DISP_REG_L4_MODE_W_H (0x0070) +#define CARMINE_DISP_REG_L4_ORG_ADR1 (0x0074) +#define CARMINE_DISP_REG_L4_DISP_ADR1 (0x0078) +#define CARMINE_DISP_REG_L4_DISP_POS (0x0084) +#define CARMINE_DISP_REG_L5_MODE_W_H (0x0088) +#define CARMINE_DISP_REG_L5_ORG_ADR1 (0x008C) +#define CARMINE_DISP_REG_L5_DISP_ADR1 (0x0090) +#define CARMINE_DISP_REG_L5_DISP_POS (0x009C) +#define CARMINE_DISP_REG_CURSOR_MODE (0x00A0) +#define CARMINE_DISP_REG_CUR1_POS (0x00A8) +#define CARMINE_DISP_REG_CUR2_POS (0x00B0) +#define CARMINE_DISP_REG_C_TRANS (0x00BC) +#define CARMINE_DISP_REG_MLMR_TRANS (0x00C0) +#define CARMINE_DISP_REG_L0_EXT_MODE (0x0110) +#define CARMINE_DISP_REG_L0_WIN_POS (0x0114) +#define CARMINE_DISP_REG_L0_WIN_SIZE (0x0118) +#define CARMINE_DISP_REG_L1_EXT_MODE (0x0120) +#define CARMINE_DISP_REG_L1_WIN_POS (0x0124) +#define CARMINE_DISP_REG_L1_WIN_SIZE (0x0128) +#define CARMINE_DISP_REG_L2_EXT_MODE (0x0130) +#define CARMINE_DISP_REG_L2_WIN_POS (0x0134) +#define CARMINE_DISP_REG_L2_WIN_SIZE (0x0138) +#define CARMINE_DISP_REG_L3_EXT_MODE (0x0140) +#define CARMINE_DISP_REG_L3_WIN_POS (0x0144) +#define CARMINE_DISP_REG_L3_WIN_SIZE (0x0148) +#define CARMINE_DISP_REG_L4_EXT_MODE (0x0150) +#define CARMINE_DISP_REG_L4_WIN_POS (0x0154) +#define CARMINE_DISP_REG_L4_WIN_SIZE (0x0158) +#define CARMINE_DISP_REG_L5_EXT_MODE (0x0160) +#define CARMINE_DISP_REG_L5_WIN_POS (0x0164) +#define CARMINE_DISP_REG_L5_WIN_SIZE (0x0168) +#define CARMINE_DISP_REG_L6_EXT_MODE (0x1918) +#define CARMINE_DISP_REG_L6_WIN_POS (0x191c) +#define CARMINE_DISP_REG_L6_WIN_SIZE (0x1920) +#define CARMINE_DISP_REG_L7_EXT_MODE (0x1958) +#define CARMINE_DISP_REG_L7_WIN_POS (0x195c) +#define CARMINE_DISP_REG_L7_WIN_SIZE (0x1960) +#define CARMINE_DISP_REG_BLEND_MODE_L0 (0x00B4) +#define CARMINE_DISP_REG_BLEND_MODE_L1 (0x0188) +#define CARMINE_DISP_REG_BLEND_MODE_L2 (0x018C) +#define CARMINE_DISP_REG_BLEND_MODE_L3 (0x0190) +#define CARMINE_DISP_REG_BLEND_MODE_L4 (0x0194) +#define CARMINE_DISP_REG_BLEND_MODE_L5 (0x0198) +#define CARMINE_DISP_REG_BLEND_MODE_L6 (0x1990) +#define CARMINE_DISP_REG_BLEND_MODE_L7 (0x1994) +#define CARMINE_DISP_REG_L0_TRANS (0x01A0) +#define CARMINE_DISP_REG_L1_TRANS (0x01A4) +#define CARMINE_DISP_REG_L2_TRANS (0x01A8) +#define CARMINE_DISP_REG_L3_TRANS (0x01AC) +#define CARMINE_DISP_REG_L4_TRANS (0x01B0) +#define CARMINE_DISP_REG_L5_TRANS (0x01B4) +#define CARMINE_DISP_REG_L6_TRANS (0x1998) +#define CARMINE_DISP_REG_L7_TRANS (0x199c) +#define CARMINE_EXTEND_MODE_MASK (0x00000003) +#define CARMINE_DISP_DCM_MASK (0x0000FFFF) +#define CARMINE_DISP_REG_DCM1 (0x0100) +#define CARMINE_DISP_WIDTH_UNIT (64) +#define CARMINE_DISP_REG_L6_MODE_W_H (0x1900) +#define CARMINE_DISP_REG_L6_ORG_ADR1 (0x1904) +#define CARMINE_DISP_REG_L6_DISP_ADR0 (0x1908) +#define CARMINE_DISP_REG_L6_DISP_POS (0x1914) +#define CARMINE_DISP_REG_L7_MODE_W_H (0x1940) +#define CARMINE_DISP_REG_L7_ORG_ADR1 (0x1944) +#define CARMINE_DISP_REG_L7_DISP_ADR0 (0x1948) +#define CARMINE_DISP_REG_L7_DISP_POS (0x1954) +#define CARMINE_CTL_REG_CLOCK_ENABLE (0x000C) +#define CARMINE_CTL_REG_SOFTWARE_RESET (0x0010) +#define CARMINE_CTL_REG_IST_MASK_ALL (0x07FFFFFF) +#define CARMINE_GRAPH_REG_VRINTM (0x00028064) +#define CARMINE_GRAPH_REG_VRERRM (0x0002806C) +#define CARMINE_GRAPH_REG_DC_OFFSET_PX (0x0004005C) +#define CARMINE_GRAPH_REG_DC_OFFSET_PY (0x00040060) +#define CARMINE_GRAPH_REG_DC_OFFSET_LX (0x00040064) +#define CARMINE_GRAPH_REG_DC_OFFSET_LY (0x00040068) +#define CARMINE_GRAPH_REG_DC_OFFSET_TX (0x0004006C) +#define CARMINE_GRAPH_REG_DC_OFFSET_TY (0x00040070) + +#endif diff --git a/drivers/video/fbdev/cfbcopyarea.c b/drivers/video/fbdev/cfbcopyarea.c new file mode 100644 index 000000000000..bcb57235fcc7 --- /dev/null +++ b/drivers/video/fbdev/cfbcopyarea.c @@ -0,0 +1,434 @@ +/* + * Generic function for frame buffer with packed pixels of any depth. + * + * Copyright (C) 1999-2005 James Simmons <jsimmons@www.infradead.org> + * + * 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. + * + * NOTES: + * + * This is for cfb packed pixels. Iplan and such are incorporated in the + * drivers that need them. + * + * FIXME + * + * Also need to add code to deal with cards endians that are different than + * the native cpu endians. I also need to deal with MSB position in the word. + * + * The two functions or copying forward and backward could be split up like + * the ones for filling, i.e. in aligned and unaligned versions. This would + * help moving some redundant computations and branches out of the loop, too. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <asm/types.h> +#include <asm/io.h> +#include "fb_draw.h" + +#if BITS_PER_LONG == 32 +# define FB_WRITEL fb_writel +# define FB_READL fb_readl +#else +# define FB_WRITEL fb_writeq +# define FB_READL fb_readq +#endif + + /* + * Generic bitwise copy algorithm + */ + +static void +bitcpy(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx, + const unsigned long __iomem *src, unsigned src_idx, int bits, + unsigned n, u32 bswapmask) +{ + unsigned long first, last; + int const shift = dst_idx-src_idx; + +#if 0 + /* + * If you suspect bug in this function, compare it with this simple + * memmove implementation. + */ + fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8, + (char *)src + ((src_idx & (bits - 1))) / 8, n / 8); + return; +#endif + + first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); + last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); + + if (!shift) { + // Same alignment for source and dest + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); + } else { + // Multiple destination words + + // Leading bits + if (first != ~0UL) { + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); + dst++; + src++; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + n -= 8; + } + while (n--) + FB_WRITEL(FB_READL(src++), dst++); + + // Trailing bits + if (last) + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); + } + } else { + /* Different alignment for source and dest */ + unsigned long d0, d1; + int m; + + int const left = shift & (bits - 1); + int const right = -shift & (bits - 1); + + if (dst_idx+n <= bits) { + // Single destination word + if (last) + first &= last; + d0 = FB_READL(src); + d0 = fb_rev_pixels_in_long(d0, bswapmask); + if (shift > 0) { + // Single source word + d0 <<= left; + } else if (src_idx+n <= bits) { + // Single source word + d0 >>= right; + } else { + // 2 source words + d1 = FB_READL(src + 1); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 >> right | d1 << left; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), first), dst); + } else { + // Multiple destination words + /** We must always remember the last value read, because in case + SRC and DST overlap bitwise (e.g. when moving just one pixel in + 1bpp), we always collect one full long for DST and that might + overlap with the current long from SRC. We store this value in + 'd0'. */ + d0 = FB_READL(src++); + d0 = fb_rev_pixels_in_long(d0, bswapmask); + // Leading bits + if (shift > 0) { + // Single source word + d1 = d0; + d0 <<= left; + n -= bits - dst_idx; + } else { + // 2 source words + d1 = FB_READL(src++); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + + d0 = d0 >> right | d1 << left; + n -= bits - dst_idx; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), first), dst); + d0 = d1; + dst++; + + // Main chunk + m = n % bits; + n /= bits; + while ((n >= 4) && !bswapmask) { + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + n -= 4; + } + while (n--) { + d1 = FB_READL(src++); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 >> right | d1 << left; + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(d0, dst++); + d0 = d1; + } + + // Trailing bits + if (m) { + if (m <= bits - right) { + // Single source word + d0 >>= right; + } else { + // 2 source words + d1 = FB_READL(src); + d1 = fb_rev_pixels_in_long(d1, + bswapmask); + d0 = d0 >> right | d1 << left; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), last), dst); + } + } + } +} + + /* + * Generic bitwise copy algorithm, operating backward + */ + +static void +bitcpy_rev(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx, + const unsigned long __iomem *src, unsigned src_idx, int bits, + unsigned n, u32 bswapmask) +{ + unsigned long first, last; + int shift; + +#if 0 + /* + * If you suspect bug in this function, compare it with this simple + * memmove implementation. + */ + fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8, + (char *)src + ((src_idx & (bits - 1))) / 8, n / 8); + return; +#endif + + dst += (dst_idx + n - 1) / bits; + src += (src_idx + n - 1) / bits; + dst_idx = (dst_idx + n - 1) % bits; + src_idx = (src_idx + n - 1) % bits; + + shift = dst_idx-src_idx; + + first = ~fb_shifted_pixels_mask_long(p, (dst_idx + 1) % bits, bswapmask); + last = fb_shifted_pixels_mask_long(p, (bits + dst_idx + 1 - n) % bits, bswapmask); + + if (!shift) { + // Same alignment for source and dest + + if ((unsigned long)dst_idx+1 >= n) { + // Single word + if (first) + last &= first; + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); + } else { + // Multiple destination words + + // Leading bits + if (first) { + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); + dst--; + src--; + n -= dst_idx+1; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + n -= 8; + } + while (n--) + FB_WRITEL(FB_READL(src--), dst--); + + // Trailing bits + if (last != -1UL) + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); + } + } else { + // Different alignment for source and dest + unsigned long d0, d1; + int m; + + int const left = shift & (bits-1); + int const right = -shift & (bits-1); + + if ((unsigned long)dst_idx+1 >= n) { + // Single destination word + if (first) + last &= first; + d0 = FB_READL(src); + if (shift < 0) { + // Single source word + d0 >>= right; + } else if (1+(unsigned long)src_idx >= n) { + // Single source word + d0 <<= left; + } else { + // 2 source words + d1 = FB_READL(src - 1); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 << left | d1 >> right; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), last), dst); + } else { + // Multiple destination words + /** We must always remember the last value read, because in case + SRC and DST overlap bitwise (e.g. when moving just one pixel in + 1bpp), we always collect one full long for DST and that might + overlap with the current long from SRC. We store this value in + 'd0'. */ + + d0 = FB_READL(src--); + d0 = fb_rev_pixels_in_long(d0, bswapmask); + // Leading bits + if (shift < 0) { + // Single source word + d1 = d0; + d0 >>= right; + } else { + // 2 source words + d1 = FB_READL(src--); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 << left | d1 >> right; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), first), dst); + d0 = d1; + dst--; + n -= dst_idx+1; + + // Main chunk + m = n % bits; + n /= bits; + while ((n >= 4) && !bswapmask) { + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + n -= 4; + } + while (n--) { + d1 = FB_READL(src--); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 << left | d1 >> right; + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(d0, dst--); + d0 = d1; + } + + // Trailing bits + if (m) { + if (m <= bits - left) { + // Single source word + d0 <<= left; + } else { + // 2 source words + d1 = FB_READL(src); + d1 = fb_rev_pixels_in_long(d1, + bswapmask); + d0 = d0 << left | d1 >> right; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), last), dst); + } + } + } +} + +void cfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy; + u32 height = area->height, width = area->width; + unsigned long const bits_per_line = p->fix.line_length*8u; + unsigned long __iomem *base = NULL; + int bits = BITS_PER_LONG, bytes = bits >> 3; + unsigned dst_idx = 0, src_idx = 0, rev_copy = 0; + u32 bswapmask = fb_compute_bswapmask(p); + + if (p->state != FBINFO_STATE_RUNNING) + return; + + /* if the beginning of the target area might overlap with the end of + the source area, be have to copy the area reverse. */ + if ((dy == sy && dx > sx) || (dy > sy)) { + dy += height; + sy += height; + rev_copy = 1; + } + + // split the base of the framebuffer into a long-aligned address and the + // index of the first bit + base = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1)); + dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1)); + // add offset of source and target area + dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel; + src_idx += sy*bits_per_line + sx*p->var.bits_per_pixel; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + if (rev_copy) { + while (height--) { + dst_idx -= bits_per_line; + src_idx -= bits_per_line; + bitcpy_rev(p, base + (dst_idx / bits), dst_idx % bits, + base + (src_idx / bits), src_idx % bits, bits, + width*p->var.bits_per_pixel, bswapmask); + } + } else { + while (height--) { + bitcpy(p, base + (dst_idx / bits), dst_idx % bits, + base + (src_idx / bits), src_idx % bits, bits, + width*p->var.bits_per_pixel, bswapmask); + dst_idx += bits_per_line; + src_idx += bits_per_line; + } + } +} + +EXPORT_SYMBOL(cfb_copyarea); + +MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>"); +MODULE_DESCRIPTION("Generic software accelerated copyarea"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/cfbfillrect.c b/drivers/video/fbdev/cfbfillrect.c new file mode 100644 index 000000000000..ba9f58b2a5e8 --- /dev/null +++ b/drivers/video/fbdev/cfbfillrect.c @@ -0,0 +1,371 @@ +/* + * Generic fillrect for frame buffers with packed pixels of any depth. + * + * Copyright (C) 2000 James Simmons (jsimmons@linux-fbdev.org) + * + * 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. + * + * NOTES: + * + * Also need to add code to deal with cards endians that are different than + * the native cpu endians. I also need to deal with MSB position in the word. + * + */ +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <asm/types.h> +#include "fb_draw.h" + +#if BITS_PER_LONG == 32 +# define FB_WRITEL fb_writel +# define FB_READL fb_readl +#else +# define FB_WRITEL fb_writeq +# define FB_READL fb_readq +#endif + + /* + * Aligned pattern fill using 32/64-bit memory accesses + */ + +static void +bitfill_aligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx, + unsigned long pat, unsigned n, int bits, u32 bswapmask) +{ + unsigned long first, last; + + if (!n) + return; + + first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); + last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + } else { + // Multiple destination words + + // Leading bits + if (first!= ~0UL) { + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + dst++; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + n -= 8; + } + while (n--) + FB_WRITEL(pat, dst++); + + // Trailing bits + if (last) + FB_WRITEL(comp(pat, FB_READL(dst), last), dst); + } +} + + + /* + * Unaligned generic pattern fill using 32/64-bit memory accesses + * The pattern must have been expanded to a full 32/64-bit value + * Left/right are the appropriate shifts to convert to the pattern to be + * used for the next 32/64-bit word + */ + +static void +bitfill_unaligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx, + unsigned long pat, int left, int right, unsigned n, int bits) +{ + unsigned long first, last; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + } else { + // Multiple destination words + // Leading bits + if (first) { + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + dst++; + pat = pat << left | pat >> right; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 4) { + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + n -= 4; + } + while (n--) { + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + } + + // Trailing bits + if (last) + FB_WRITEL(comp(pat, FB_READL(dst), last), dst); + } +} + + /* + * Aligned pattern invert using 32/64-bit memory accesses + */ +static void +bitfill_aligned_rev(struct fb_info *p, unsigned long __iomem *dst, + int dst_idx, unsigned long pat, unsigned n, int bits, + u32 bswapmask) +{ + unsigned long val = pat, dat; + unsigned long first, last; + + if (!n) + return; + + first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); + last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ val, dat, first), dst); + } else { + // Multiple destination words + // Leading bits + if (first!=0UL) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ val, dat, first), dst); + dst++; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + n -= 8; + } + while (n--) { + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + } + // Trailing bits + if (last) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ val, dat, last), dst); + } + } +} + + + /* + * Unaligned generic pattern invert using 32/64-bit memory accesses + * The pattern must have been expanded to a full 32/64-bit value + * Left/right are the appropriate shifts to convert to the pattern to be + * used for the next 32/64-bit word + */ + +static void +bitfill_unaligned_rev(struct fb_info *p, unsigned long __iomem *dst, + int dst_idx, unsigned long pat, int left, int right, + unsigned n, int bits) +{ + unsigned long first, last, dat; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ pat, dat, first), dst); + } else { + // Multiple destination words + + // Leading bits + if (first != 0UL) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ pat, dat, first), dst); + dst++; + pat = pat << left | pat >> right; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 4) { + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + n -= 4; + } + while (n--) { + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + } + + // Trailing bits + if (last) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ pat, dat, last), dst); + } + } +} + +void cfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + unsigned long pat, pat2, fg; + unsigned long width = rect->width, height = rect->height; + int bits = BITS_PER_LONG, bytes = bits >> 3; + u32 bpp = p->var.bits_per_pixel; + unsigned long __iomem *dst; + int dst_idx, left; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR ) + fg = ((u32 *) (p->pseudo_palette))[rect->color]; + else + fg = rect->color; + + pat = pixel_to_pat(bpp, fg); + + dst = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1)); + dst_idx = ((unsigned long)p->screen_base & (bytes - 1))*8; + dst_idx += rect->dy*p->fix.line_length*8+rect->dx*bpp; + /* FIXME For now we support 1-32 bpp only */ + left = bits % bpp; + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + if (!left) { + u32 bswapmask = fb_compute_bswapmask(p); + void (*fill_op32)(struct fb_info *p, + unsigned long __iomem *dst, int dst_idx, + unsigned long pat, unsigned n, int bits, + u32 bswapmask) = NULL; + + switch (rect->rop) { + case ROP_XOR: + fill_op32 = bitfill_aligned_rev; + break; + case ROP_COPY: + fill_op32 = bitfill_aligned; + break; + default: + printk( KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n"); + fill_op32 = bitfill_aligned; + break; + } + while (height--) { + dst += dst_idx >> (ffs(bits) - 1); + dst_idx &= (bits - 1); + fill_op32(p, dst, dst_idx, pat, width*bpp, bits, + bswapmask); + dst_idx += p->fix.line_length*8; + } + } else { + int right, r; + void (*fill_op)(struct fb_info *p, unsigned long __iomem *dst, + int dst_idx, unsigned long pat, int left, + int right, unsigned n, int bits) = NULL; +#ifdef __LITTLE_ENDIAN + right = left; + left = bpp - right; +#else + right = bpp - left; +#endif + switch (rect->rop) { + case ROP_XOR: + fill_op = bitfill_unaligned_rev; + break; + case ROP_COPY: + fill_op = bitfill_unaligned; + break; + default: + printk(KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n"); + fill_op = bitfill_unaligned; + break; + } + while (height--) { + dst += dst_idx / bits; + dst_idx &= (bits - 1); + r = dst_idx % bpp; + /* rotate pattern to the correct start position */ + pat2 = le_long_to_cpu(rolx(cpu_to_le_long(pat), r, bpp)); + fill_op(p, dst, dst_idx, pat2, left, right, + width*bpp, bits); + dst_idx += p->fix.line_length*8; + } + } +} + +EXPORT_SYMBOL(cfb_fillrect); + +MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>"); +MODULE_DESCRIPTION("Generic software accelerated fill rectangle"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/cfbimgblt.c b/drivers/video/fbdev/cfbimgblt.c new file mode 100644 index 000000000000..a2bb276a8b24 --- /dev/null +++ b/drivers/video/fbdev/cfbimgblt.c @@ -0,0 +1,313 @@ +/* + * Generic BitBLT function for frame buffer with packed pixels of any depth. + * + * Copyright (C) June 1999 James Simmons + * + * 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. + * + * NOTES: + * + * This function copys a image from system memory to video memory. The + * image can be a bitmap where each 0 represents the background color and + * each 1 represents the foreground color. Great for font handling. It can + * also be a color image. This is determined by image_depth. The color image + * must be laid out exactly in the same format as the framebuffer. Yes I know + * their are cards with hardware that coverts images of various depths to the + * framebuffer depth. But not every card has this. All images must be rounded + * up to the nearest byte. For example a bitmap 12 bits wide must be two + * bytes width. + * + * Tony: + * Incorporate mask tables similar to fbcon-cfb*.c in 2.4 API. This speeds + * up the code significantly. + * + * Code for depths not multiples of BITS_PER_LONG is still kludgy, which is + * still processed a bit at a time. + * + * Also need to add code to deal with cards endians that are different than + * the native cpu endians. I also need to deal with MSB position in the word. + */ +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <asm/types.h> +#include "fb_draw.h" + +#define DEBUG + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt,__func__,## args) +#else +#define DPRINTK(fmt, args...) +#endif + +static const u32 cfb_tab8_be[] = { + 0x00000000,0x000000ff,0x0000ff00,0x0000ffff, + 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff, + 0xff000000,0xff0000ff,0xff00ff00,0xff00ffff, + 0xffff0000,0xffff00ff,0xffffff00,0xffffffff +}; + +static const u32 cfb_tab8_le[] = { + 0x00000000,0xff000000,0x00ff0000,0xffff0000, + 0x0000ff00,0xff00ff00,0x00ffff00,0xffffff00, + 0x000000ff,0xff0000ff,0x00ff00ff,0xffff00ff, + 0x0000ffff,0xff00ffff,0x00ffffff,0xffffffff +}; + +static const u32 cfb_tab16_be[] = { + 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff +}; + +static const u32 cfb_tab16_le[] = { + 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff +}; + +static const u32 cfb_tab32[] = { + 0x00000000, 0xffffffff +}; + +#define FB_WRITEL fb_writel +#define FB_READL fb_readl + +static inline void color_imageblit(const struct fb_image *image, + struct fb_info *p, u8 __iomem *dst1, + u32 start_index, + u32 pitch_index) +{ + /* Draw the penguin */ + u32 __iomem *dst, *dst2; + u32 color = 0, val, shift; + int i, n, bpp = p->var.bits_per_pixel; + u32 null_bits = 32 - bpp; + u32 *palette = (u32 *) p->pseudo_palette; + const u8 *src = image->data; + u32 bswapmask = fb_compute_bswapmask(p); + + dst2 = (u32 __iomem *) dst1; + for (i = image->height; i--; ) { + n = image->width; + dst = (u32 __iomem *) dst1; + shift = 0; + val = 0; + + if (start_index) { + u32 start_mask = ~fb_shifted_pixels_mask_u32(p, + start_index, bswapmask); + val = FB_READL(dst) & start_mask; + shift = start_index; + } + while (n--) { + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR ) + color = palette[*src]; + else + color = *src; + color <<= FB_LEFT_POS(p, bpp); + val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask); + if (shift >= null_bits) { + FB_WRITEL(val, dst++); + + val = (shift == null_bits) ? 0 : + FB_SHIFT_LOW(p, color, 32 - shift); + } + shift += bpp; + shift &= (32 - 1); + src++; + } + if (shift) { + u32 end_mask = fb_shifted_pixels_mask_u32(p, shift, + bswapmask); + + FB_WRITEL((FB_READL(dst) & end_mask) | val, dst); + } + dst1 += p->fix.line_length; + if (pitch_index) { + dst2 += p->fix.line_length; + dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1)); + + start_index += pitch_index; + start_index &= 32 - 1; + } + } +} + +static inline void slow_imageblit(const struct fb_image *image, struct fb_info *p, + u8 __iomem *dst1, u32 fgcolor, + u32 bgcolor, + u32 start_index, + u32 pitch_index) +{ + u32 shift, color = 0, bpp = p->var.bits_per_pixel; + u32 __iomem *dst, *dst2; + u32 val, pitch = p->fix.line_length; + u32 null_bits = 32 - bpp; + u32 spitch = (image->width+7)/8; + const u8 *src = image->data, *s; + u32 i, j, l; + u32 bswapmask = fb_compute_bswapmask(p); + + dst2 = (u32 __iomem *) dst1; + fgcolor <<= FB_LEFT_POS(p, bpp); + bgcolor <<= FB_LEFT_POS(p, bpp); + + for (i = image->height; i--; ) { + shift = val = 0; + l = 8; + j = image->width; + dst = (u32 __iomem *) dst1; + s = src; + + /* write leading bits */ + if (start_index) { + u32 start_mask = ~fb_shifted_pixels_mask_u32(p, + start_index, bswapmask); + val = FB_READL(dst) & start_mask; + shift = start_index; + } + + while (j--) { + l--; + color = (*s & (1 << l)) ? fgcolor : bgcolor; + val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask); + + /* Did the bitshift spill bits to the next long? */ + if (shift >= null_bits) { + FB_WRITEL(val, dst++); + val = (shift == null_bits) ? 0 : + FB_SHIFT_LOW(p, color, 32 - shift); + } + shift += bpp; + shift &= (32 - 1); + if (!l) { l = 8; s++; } + } + + /* write trailing bits */ + if (shift) { + u32 end_mask = fb_shifted_pixels_mask_u32(p, shift, + bswapmask); + + FB_WRITEL((FB_READL(dst) & end_mask) | val, dst); + } + + dst1 += pitch; + src += spitch; + if (pitch_index) { + dst2 += pitch; + dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1)); + start_index += pitch_index; + start_index &= 32 - 1; + } + + } +} + +/* + * fast_imageblit - optimized monochrome color expansion + * + * Only if: bits_per_pixel == 8, 16, or 32 + * image->width is divisible by pixel/dword (ppw); + * fix->line_legth is divisible by 4; + * beginning and end of a scanline is dword aligned + */ +static inline void fast_imageblit(const struct fb_image *image, struct fb_info *p, + u8 __iomem *dst1, u32 fgcolor, + u32 bgcolor) +{ + u32 fgx = fgcolor, bgx = bgcolor, bpp = p->var.bits_per_pixel; + u32 ppw = 32/bpp, spitch = (image->width + 7)/8; + u32 bit_mask, end_mask, eorx, shift; + const char *s = image->data, *src; + u32 __iomem *dst; + const u32 *tab = NULL; + int i, j, k; + + switch (bpp) { + case 8: + tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le; + break; + case 16: + tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le; + break; + case 32: + default: + tab = cfb_tab32; + break; + } + + for (i = ppw-1; i--; ) { + fgx <<= bpp; + bgx <<= bpp; + fgx |= fgcolor; + bgx |= bgcolor; + } + + bit_mask = (1 << ppw) - 1; + eorx = fgx ^ bgx; + k = image->width/ppw; + + for (i = image->height; i--; ) { + dst = (u32 __iomem *) dst1, shift = 8; src = s; + + for (j = k; j--; ) { + shift -= ppw; + end_mask = tab[(*src >> shift) & bit_mask]; + FB_WRITEL((end_mask & eorx)^bgx, dst++); + if (!shift) { shift = 8; src++; } + } + dst1 += p->fix.line_length; + s += spitch; + } +} + +void cfb_imageblit(struct fb_info *p, const struct fb_image *image) +{ + u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0; + u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel; + u32 width = image->width; + u32 dx = image->dx, dy = image->dy; + u8 __iomem *dst1; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + bitstart = (dy * p->fix.line_length * 8) + (dx * bpp); + start_index = bitstart & (32 - 1); + pitch_index = (p->fix.line_length & (bpl - 1)) * 8; + + bitstart /= 8; + bitstart &= ~(bpl - 1); + dst1 = p->screen_base + bitstart; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + if (image->depth == 1) { + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR) { + fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color]; + bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color]; + } else { + fgcolor = image->fg_color; + bgcolor = image->bg_color; + } + + if (32 % bpp == 0 && !start_index && !pitch_index && + ((width & (32/bpp-1)) == 0) && + bpp >= 8 && bpp <= 32) + fast_imageblit(image, p, dst1, fgcolor, bgcolor); + else + slow_imageblit(image, p, dst1, fgcolor, bgcolor, + start_index, pitch_index); + } else + color_imageblit(image, p, dst1, start_index, pitch_index); +} + +EXPORT_SYMBOL(cfb_imageblit); + +MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>"); +MODULE_DESCRIPTION("Generic software accelerated imaging drawing"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/cg14.c b/drivers/video/fbdev/cg14.c new file mode 100644 index 000000000000..c79745b136bb --- /dev/null +++ b/drivers/video/fbdev/cg14.c @@ -0,0 +1,626 @@ +/* cg14.c: CGFOURTEEN frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/uaccess.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int cg14_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); + +static int cg14_mmap(struct fb_info *, struct vm_area_struct *); +static int cg14_ioctl(struct fb_info *, unsigned int, unsigned long); +static int cg14_pan_display(struct fb_var_screeninfo *, struct fb_info *); + +/* + * Frame buffer operations + */ + +static struct fb_ops cg14_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = cg14_setcolreg, + .fb_pan_display = cg14_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = cg14_mmap, + .fb_ioctl = cg14_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + +#define CG14_MCR_INTENABLE_SHIFT 7 +#define CG14_MCR_INTENABLE_MASK 0x80 +#define CG14_MCR_VIDENABLE_SHIFT 6 +#define CG14_MCR_VIDENABLE_MASK 0x40 +#define CG14_MCR_PIXMODE_SHIFT 4 +#define CG14_MCR_PIXMODE_MASK 0x30 +#define CG14_MCR_TMR_SHIFT 2 +#define CG14_MCR_TMR_MASK 0x0c +#define CG14_MCR_TMENABLE_SHIFT 1 +#define CG14_MCR_TMENABLE_MASK 0x02 +#define CG14_MCR_RESET_SHIFT 0 +#define CG14_MCR_RESET_MASK 0x01 +#define CG14_REV_REVISION_SHIFT 4 +#define CG14_REV_REVISION_MASK 0xf0 +#define CG14_REV_IMPL_SHIFT 0 +#define CG14_REV_IMPL_MASK 0x0f +#define CG14_VBR_FRAMEBASE_SHIFT 12 +#define CG14_VBR_FRAMEBASE_MASK 0x00fff000 +#define CG14_VMCR1_SETUP_SHIFT 0 +#define CG14_VMCR1_SETUP_MASK 0x000001ff +#define CG14_VMCR1_VCONFIG_SHIFT 9 +#define CG14_VMCR1_VCONFIG_MASK 0x00000e00 +#define CG14_VMCR2_REFRESH_SHIFT 0 +#define CG14_VMCR2_REFRESH_MASK 0x00000001 +#define CG14_VMCR2_TESTROWCNT_SHIFT 1 +#define CG14_VMCR2_TESTROWCNT_MASK 0x00000002 +#define CG14_VMCR2_FBCONFIG_SHIFT 2 +#define CG14_VMCR2_FBCONFIG_MASK 0x0000000c +#define CG14_VCR_REFRESHREQ_SHIFT 0 +#define CG14_VCR_REFRESHREQ_MASK 0x000003ff +#define CG14_VCR1_REFRESHENA_SHIFT 10 +#define CG14_VCR1_REFRESHENA_MASK 0x00000400 +#define CG14_VCA_CAD_SHIFT 0 +#define CG14_VCA_CAD_MASK 0x000003ff +#define CG14_VCA_VERS_SHIFT 10 +#define CG14_VCA_VERS_MASK 0x00000c00 +#define CG14_VCA_RAMSPEED_SHIFT 12 +#define CG14_VCA_RAMSPEED_MASK 0x00001000 +#define CG14_VCA_8MB_SHIFT 13 +#define CG14_VCA_8MB_MASK 0x00002000 + +#define CG14_MCR_PIXMODE_8 0 +#define CG14_MCR_PIXMODE_16 2 +#define CG14_MCR_PIXMODE_32 3 + +struct cg14_regs{ + u8 mcr; /* Master Control Reg */ + u8 ppr; /* Packed Pixel Reg */ + u8 tms[2]; /* Test Mode Status Regs */ + u8 msr; /* Master Status Reg */ + u8 fsr; /* Fault Status Reg */ + u8 rev; /* Revision & Impl */ + u8 ccr; /* Clock Control Reg */ + u32 tmr; /* Test Mode Read Back */ + u8 mod; /* Monitor Operation Data Reg */ + u8 acr; /* Aux Control */ + u8 xxx0[6]; + u16 hct; /* Hor Counter */ + u16 vct; /* Vert Counter */ + u16 hbs; /* Hor Blank Start */ + u16 hbc; /* Hor Blank Clear */ + u16 hss; /* Hor Sync Start */ + u16 hsc; /* Hor Sync Clear */ + u16 csc; /* Composite Sync Clear */ + u16 vbs; /* Vert Blank Start */ + u16 vbc; /* Vert Blank Clear */ + u16 vss; /* Vert Sync Start */ + u16 vsc; /* Vert Sync Clear */ + u16 xcs; + u16 xcc; + u16 fsa; /* Fault Status Address */ + u16 adr; /* Address Registers */ + u8 xxx1[0xce]; + u8 pcg[0x100]; /* Pixel Clock Generator */ + u32 vbr; /* Frame Base Row */ + u32 vmcr; /* VBC Master Control */ + u32 vcr; /* VBC refresh */ + u32 vca; /* VBC Config */ +}; + +#define CG14_CCR_ENABLE 0x04 +#define CG14_CCR_SELECT 0x02 /* HW/Full screen */ + +struct cg14_cursor { + u32 cpl0[32]; /* Enable plane 0 */ + u32 cpl1[32]; /* Color selection plane */ + u8 ccr; /* Cursor Control Reg */ + u8 xxx0[3]; + u16 cursx; /* Cursor x,y position */ + u16 cursy; /* Cursor x,y position */ + u32 color0; + u32 color1; + u32 xxx1[0x1bc]; + u32 cpl0i[32]; /* Enable plane 0 autoinc */ + u32 cpl1i[32]; /* Color selection autoinc */ +}; + +struct cg14_dac { + u8 addr; /* Address Register */ + u8 xxx0[255]; + u8 glut; /* Gamma table */ + u8 xxx1[255]; + u8 select; /* Register Select */ + u8 xxx2[255]; + u8 mode; /* Mode Register */ +}; + +struct cg14_xlut{ + u8 x_xlut [256]; + u8 x_xlutd [256]; + u8 xxx0[0x600]; + u8 x_xlut_inc [256]; + u8 x_xlutd_inc [256]; +}; + +/* Color look up table (clut) */ +/* Each one of these arrays hold the color lookup table (for 256 + * colors) for each MDI page (I assume then there should be 4 MDI + * pages, I still wonder what they are. I have seen NeXTStep split + * the screen in four parts, while operating in 24 bits mode. Each + * integer holds 4 values: alpha value (transparency channel, thanks + * go to John Stone (johns@umr.edu) from OpenBSD), red, green and blue + * + * I currently use the clut instead of the Xlut + */ +struct cg14_clut { + u32 c_clut [256]; + u32 c_clutd [256]; /* i wonder what the 'd' is for */ + u32 c_clut_inc [256]; + u32 c_clutd_inc [256]; +}; + +#define CG14_MMAP_ENTRIES 16 + +struct cg14_par { + spinlock_t lock; + struct cg14_regs __iomem *regs; + struct cg14_clut __iomem *clut; + struct cg14_cursor __iomem *cursor; + + u32 flags; +#define CG14_FLAG_BLANKED 0x00000001 + + unsigned long iospace; + + struct sbus_mmap_map mmap_map[CG14_MMAP_ENTRIES]; + + int mode; + int ramsize; +}; + +static void __cg14_reset(struct cg14_par *par) +{ + struct cg14_regs __iomem *regs = par->regs; + u8 val; + + val = sbus_readb(®s->mcr); + val &= ~(CG14_MCR_PIXMODE_MASK); + sbus_writeb(val, ®s->mcr); +} + +static int cg14_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct cg14_par *par = (struct cg14_par *) info->par; + unsigned long flags; + + /* We just use this to catch switches out of + * graphics mode. + */ + spin_lock_irqsave(&par->lock, flags); + __cg14_reset(par); + spin_unlock_irqrestore(&par->lock, flags); + + if (var->xoffset || var->yoffset || var->vmode) + return -EINVAL; + return 0; +} + +/** + * cg14_setcolreg - Optional function. Sets a color register. + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int cg14_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct cg14_par *par = (struct cg14_par *) info->par; + struct cg14_clut __iomem *clut = par->clut; + unsigned long flags; + u32 val; + + if (regno >= 256) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + val = (red | (green << 8) | (blue << 16)); + + spin_lock_irqsave(&par->lock, flags); + sbus_writel(val, &clut->c_clut[regno]); + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static int cg14_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct cg14_par *par = (struct cg14_par *) info->par; + + return sbusfb_mmap_helper(par->mmap_map, + info->fix.smem_start, info->fix.smem_len, + par->iospace, vma); +} + +static int cg14_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + struct cg14_par *par = (struct cg14_par *) info->par; + struct cg14_regs __iomem *regs = par->regs; + struct mdi_cfginfo kmdi, __user *mdii; + unsigned long flags; + int cur_mode, mode, ret = 0; + + switch (cmd) { + case MDI_RESET: + spin_lock_irqsave(&par->lock, flags); + __cg14_reset(par); + spin_unlock_irqrestore(&par->lock, flags); + break; + + case MDI_GET_CFGINFO: + memset(&kmdi, 0, sizeof(kmdi)); + + spin_lock_irqsave(&par->lock, flags); + kmdi.mdi_type = FBTYPE_MDICOLOR; + kmdi.mdi_height = info->var.yres; + kmdi.mdi_width = info->var.xres; + kmdi.mdi_mode = par->mode; + kmdi.mdi_pixfreq = 72; /* FIXME */ + kmdi.mdi_size = par->ramsize; + spin_unlock_irqrestore(&par->lock, flags); + + mdii = (struct mdi_cfginfo __user *) arg; + if (copy_to_user(mdii, &kmdi, sizeof(kmdi))) + ret = -EFAULT; + break; + + case MDI_SET_PIXELMODE: + if (get_user(mode, (int __user *) arg)) { + ret = -EFAULT; + break; + } + + spin_lock_irqsave(&par->lock, flags); + cur_mode = sbus_readb(®s->mcr); + cur_mode &= ~CG14_MCR_PIXMODE_MASK; + switch(mode) { + case MDI_32_PIX: + cur_mode |= (CG14_MCR_PIXMODE_32 << + CG14_MCR_PIXMODE_SHIFT); + break; + + case MDI_16_PIX: + cur_mode |= (CG14_MCR_PIXMODE_16 << + CG14_MCR_PIXMODE_SHIFT); + break; + + case MDI_8_PIX: + break; + + default: + ret = -ENOSYS; + break; + } + if (!ret) { + sbus_writeb(cur_mode, ®s->mcr); + par->mode = mode; + } + spin_unlock_irqrestore(&par->lock, flags); + break; + + default: + ret = sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_MDICOLOR, 8, + info->fix.smem_len); + break; + } + + return ret; +} + +/* + * Initialisation + */ + +static void cg14_init_fix(struct fb_info *info, int linebytes, + struct device_node *dp) +{ + const char *name = dp->name; + + strlcpy(info->fix.id, name, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = linebytes; + + info->fix.accel = FB_ACCEL_SUN_CG14; +} + +static struct sbus_mmap_map __cg14_mmap_map[CG14_MMAP_ENTRIES] = { + { + .voff = CG14_REGS, + .poff = 0x80000000, + .size = 0x1000 + }, + { + .voff = CG14_XLUT, + .poff = 0x80003000, + .size = 0x1000 + }, + { + .voff = CG14_CLUT1, + .poff = 0x80004000, + .size = 0x1000 + }, + { + .voff = CG14_CLUT2, + .poff = 0x80005000, + .size = 0x1000 + }, + { + .voff = CG14_CLUT3, + .poff = 0x80006000, + .size = 0x1000 + }, + { + .voff = CG3_MMAP_OFFSET - 0x7000, + .poff = 0x80000000, + .size = 0x7000 + }, + { + .voff = CG3_MMAP_OFFSET, + .poff = 0x00000000, + .size = SBUS_MMAP_FBSIZE(1) + }, + { + .voff = MDI_CURSOR_MAP, + .poff = 0x80001000, + .size = 0x1000 + }, + { + .voff = MDI_CHUNKY_BGR_MAP, + .poff = 0x01000000, + .size = 0x400000 + }, + { + .voff = MDI_PLANAR_X16_MAP, + .poff = 0x02000000, + .size = 0x200000 + }, + { + .voff = MDI_PLANAR_C16_MAP, + .poff = 0x02800000, + .size = 0x200000 + }, + { + .voff = MDI_PLANAR_X32_MAP, + .poff = 0x03000000, + .size = 0x100000 + }, + { + .voff = MDI_PLANAR_B32_MAP, + .poff = 0x03400000, + .size = 0x100000 + }, + { + .voff = MDI_PLANAR_G32_MAP, + .poff = 0x03800000, + .size = 0x100000 + }, + { + .voff = MDI_PLANAR_R32_MAP, + .poff = 0x03c00000, + .size = 0x100000 + }, + { .size = 0 } +}; + +static void cg14_unmap_regs(struct platform_device *op, struct fb_info *info, + struct cg14_par *par) +{ + if (par->regs) + of_iounmap(&op->resource[0], + par->regs, sizeof(struct cg14_regs)); + if (par->clut) + of_iounmap(&op->resource[0], + par->clut, sizeof(struct cg14_clut)); + if (par->cursor) + of_iounmap(&op->resource[0], + par->cursor, sizeof(struct cg14_cursor)); + if (info->screen_base) + of_iounmap(&op->resource[1], + info->screen_base, info->fix.smem_len); +} + +static int cg14_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct cg14_par *par; + int is_8mb, linebytes, i, err; + + info = framebuffer_alloc(sizeof(struct cg14_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + par = info->par; + + spin_lock_init(&par->lock); + + sbusfb_fill_var(&info->var, dp, 8); + info->var.red.length = 8; + info->var.green.length = 8; + info->var.blue.length = 8; + + linebytes = of_getintprop_default(dp, "linebytes", + info->var.xres); + info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); + + if (!strcmp(dp->parent->name, "sbus") || + !strcmp(dp->parent->name, "sbi")) { + info->fix.smem_start = op->resource[0].start; + par->iospace = op->resource[0].flags & IORESOURCE_BITS; + } else { + info->fix.smem_start = op->resource[1].start; + par->iospace = op->resource[0].flags & IORESOURCE_BITS; + } + + par->regs = of_ioremap(&op->resource[0], 0, + sizeof(struct cg14_regs), "cg14 regs"); + par->clut = of_ioremap(&op->resource[0], CG14_CLUT1, + sizeof(struct cg14_clut), "cg14 clut"); + par->cursor = of_ioremap(&op->resource[0], CG14_CURSORREGS, + sizeof(struct cg14_cursor), "cg14 cursor"); + + info->screen_base = of_ioremap(&op->resource[1], 0, + info->fix.smem_len, "cg14 ram"); + + if (!par->regs || !par->clut || !par->cursor || !info->screen_base) + goto out_unmap_regs; + + is_8mb = (((op->resource[1].end - op->resource[1].start) + 1) == + (8 * 1024 * 1024)); + + BUILD_BUG_ON(sizeof(par->mmap_map) != sizeof(__cg14_mmap_map)); + + memcpy(&par->mmap_map, &__cg14_mmap_map, sizeof(par->mmap_map)); + + for (i = 0; i < CG14_MMAP_ENTRIES; i++) { + struct sbus_mmap_map *map = &par->mmap_map[i]; + + if (!map->size) + break; + if (map->poff & 0x80000000) + map->poff = (map->poff & 0x7fffffff) + + (op->resource[0].start - + op->resource[1].start); + if (is_8mb && + map->size >= 0x100000 && + map->size <= 0x400000) + map->size *= 2; + } + + par->mode = MDI_8_PIX; + par->ramsize = (is_8mb ? 0x800000 : 0x400000); + + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + info->fbops = &cg14_ops; + + __cg14_reset(par); + + if (fb_alloc_cmap(&info->cmap, 256, 0)) + goto out_unmap_regs; + + fb_set_cmap(&info->cmap, info); + + cg14_init_fix(info, linebytes, dp); + + err = register_framebuffer(info); + if (err < 0) + goto out_dealloc_cmap; + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: cgfourteen at %lx:%lx, %dMB\n", + dp->full_name, + par->iospace, info->fix.smem_start, + par->ramsize >> 20); + + return 0; + +out_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + +out_unmap_regs: + cg14_unmap_regs(op, info, par); + framebuffer_release(info); + +out_err: + return err; +} + +static int cg14_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct cg14_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + cg14_unmap_regs(op, info, par); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id cg14_match[] = { + { + .name = "cgfourteen", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cg14_match); + +static struct platform_driver cg14_driver = { + .driver = { + .name = "cg14", + .owner = THIS_MODULE, + .of_match_table = cg14_match, + }, + .probe = cg14_probe, + .remove = cg14_remove, +}; + +static int __init cg14_init(void) +{ + if (fb_get_options("cg14fb", NULL)) + return -ENODEV; + + return platform_driver_register(&cg14_driver); +} + +static void __exit cg14_exit(void) +{ + platform_driver_unregister(&cg14_driver); +} + +module_init(cg14_init); +module_exit(cg14_exit); + +MODULE_DESCRIPTION("framebuffer driver for CGfourteen chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/cg3.c b/drivers/video/fbdev/cg3.c new file mode 100644 index 000000000000..64a89d5747ed --- /dev/null +++ b/drivers/video/fbdev/cg3.c @@ -0,0 +1,492 @@ +/* cg3.c: CGTHREE frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int cg3_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); +static int cg3_blank(int, struct fb_info *); + +static int cg3_mmap(struct fb_info *, struct vm_area_struct *); +static int cg3_ioctl(struct fb_info *, unsigned int, unsigned long); + +/* + * Frame buffer operations + */ + +static struct fb_ops cg3_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = cg3_setcolreg, + .fb_blank = cg3_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = cg3_mmap, + .fb_ioctl = cg3_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + + +/* Control Register Constants */ +#define CG3_CR_ENABLE_INTS 0x80 +#define CG3_CR_ENABLE_VIDEO 0x40 +#define CG3_CR_ENABLE_TIMING 0x20 +#define CG3_CR_ENABLE_CURCMP 0x10 +#define CG3_CR_XTAL_MASK 0x0c +#define CG3_CR_DIVISOR_MASK 0x03 + +/* Status Register Constants */ +#define CG3_SR_PENDING_INT 0x80 +#define CG3_SR_RES_MASK 0x70 +#define CG3_SR_1152_900_76_A 0x40 +#define CG3_SR_1152_900_76_B 0x60 +#define CG3_SR_ID_MASK 0x0f +#define CG3_SR_ID_COLOR 0x01 +#define CG3_SR_ID_MONO 0x02 +#define CG3_SR_ID_MONO_ECL 0x03 + +enum cg3_type { + CG3_AT_66HZ = 0, + CG3_AT_76HZ, + CG3_RDI +}; + +struct bt_regs { + u32 addr; + u32 color_map; + u32 control; + u32 cursor; +}; + +struct cg3_regs { + struct bt_regs cmap; + u8 control; + u8 status; + u8 cursor_start; + u8 cursor_end; + u8 h_blank_start; + u8 h_blank_end; + u8 h_sync_start; + u8 h_sync_end; + u8 comp_sync_end; + u8 v_blank_start_high; + u8 v_blank_start_low; + u8 v_blank_end; + u8 v_sync_start; + u8 v_sync_end; + u8 xfer_holdoff_start; + u8 xfer_holdoff_end; +}; + +/* Offset of interesting structures in the OBIO space */ +#define CG3_REGS_OFFSET 0x400000UL +#define CG3_RAM_OFFSET 0x800000UL + +struct cg3_par { + spinlock_t lock; + struct cg3_regs __iomem *regs; + u32 sw_cmap[((256 * 3) + 3) / 4]; + + u32 flags; +#define CG3_FLAG_BLANKED 0x00000001 +#define CG3_FLAG_RDI 0x00000002 + + unsigned long which_io; +}; + +/** + * cg3_setcolreg - Optional function. Sets a color register. + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + * + * The cg3 palette is loaded with 4 color values at each time + * so you end up with: (rgb)(r), (gb)(rg), (b)(rgb), and so on. + * We keep a sw copy of the hw cmap to assist us in this esoteric + * loading procedure. + */ +static int cg3_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct cg3_par *par = (struct cg3_par *) info->par; + struct bt_regs __iomem *bt = &par->regs->cmap; + unsigned long flags; + u32 *p32; + u8 *p8; + int count; + + if (regno >= 256) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + spin_lock_irqsave(&par->lock, flags); + + p8 = (u8 *)par->sw_cmap + (regno * 3); + p8[0] = red; + p8[1] = green; + p8[2] = blue; + +#define D4M3(x) ((((x)>>2)<<1) + ((x)>>2)) /* (x/4)*3 */ +#define D4M4(x) ((x)&~0x3) /* (x/4)*4 */ + + count = 3; + p32 = &par->sw_cmap[D4M3(regno)]; + sbus_writel(D4M4(regno), &bt->addr); + while (count--) + sbus_writel(*p32++, &bt->color_map); + +#undef D4M3 +#undef D4M4 + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +/** + * cg3_blank - Optional function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int cg3_blank(int blank, struct fb_info *info) +{ + struct cg3_par *par = (struct cg3_par *) info->par; + struct cg3_regs __iomem *regs = par->regs; + unsigned long flags; + u8 val; + + spin_lock_irqsave(&par->lock, flags); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + val = sbus_readb(®s->control); + val |= CG3_CR_ENABLE_VIDEO; + sbus_writeb(val, ®s->control); + par->flags &= ~CG3_FLAG_BLANKED; + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + case FB_BLANK_POWERDOWN: /* Poweroff */ + val = sbus_readb(®s->control); + val &= ~CG3_CR_ENABLE_VIDEO; + sbus_writeb(val, ®s->control); + par->flags |= CG3_FLAG_BLANKED; + break; + } + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static struct sbus_mmap_map cg3_mmap_map[] = { + { + .voff = CG3_MMAP_OFFSET, + .poff = CG3_RAM_OFFSET, + .size = SBUS_MMAP_FBSIZE(1) + }, + { .size = 0 } +}; + +static int cg3_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct cg3_par *par = (struct cg3_par *)info->par; + + return sbusfb_mmap_helper(cg3_mmap_map, + info->fix.smem_start, info->fix.smem_len, + par->which_io, + vma); +} + +static int cg3_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + return sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_SUN3COLOR, 8, info->fix.smem_len); +} + +/* + * Initialisation + */ + +static void cg3_init_fix(struct fb_info *info, int linebytes, + struct device_node *dp) +{ + strlcpy(info->fix.id, dp->name, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = linebytes; + + info->fix.accel = FB_ACCEL_SUN_CGTHREE; +} + +static void cg3_rdi_maybe_fixup_var(struct fb_var_screeninfo *var, + struct device_node *dp) +{ + const char *params; + char *p; + int ww, hh; + + params = of_get_property(dp, "params", NULL); + if (params) { + ww = simple_strtoul(params, &p, 10); + if (ww && *p == 'x') { + hh = simple_strtoul(p + 1, &p, 10); + if (hh && *p == '-') { + if (var->xres != ww || + var->yres != hh) { + var->xres = var->xres_virtual = ww; + var->yres = var->yres_virtual = hh; + } + } + } + } +} + +static u8 cg3regvals_66hz[] = { /* 1152 x 900, 66 Hz */ + 0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14, + 0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24, + 0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x20, 0 +}; + +static u8 cg3regvals_76hz[] = { /* 1152 x 900, 76 Hz */ + 0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f, + 0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a, + 0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x24, 0 +}; + +static u8 cg3regvals_rdi[] = { /* 640 x 480, cgRDI */ + 0x14, 0x70, 0x15, 0x20, 0x16, 0x08, 0x17, 0x10, + 0x18, 0x06, 0x19, 0x02, 0x1a, 0x31, 0x1b, 0x51, + 0x1c, 0x06, 0x1d, 0x0c, 0x1e, 0xff, 0x1f, 0x01, + 0x10, 0x22, 0 +}; + +static u8 *cg3_regvals[] = { + cg3regvals_66hz, cg3regvals_76hz, cg3regvals_rdi +}; + +static u_char cg3_dacvals[] = { + 4, 0xff, 5, 0x00, 6, 0x70, 7, 0x00, 0 +}; + +static int cg3_do_default_mode(struct cg3_par *par) +{ + enum cg3_type type; + u8 *p; + + if (par->flags & CG3_FLAG_RDI) + type = CG3_RDI; + else { + u8 status = sbus_readb(&par->regs->status), mon; + if ((status & CG3_SR_ID_MASK) == CG3_SR_ID_COLOR) { + mon = status & CG3_SR_RES_MASK; + if (mon == CG3_SR_1152_900_76_A || + mon == CG3_SR_1152_900_76_B) + type = CG3_AT_76HZ; + else + type = CG3_AT_66HZ; + } else { + printk(KERN_ERR "cgthree: can't handle SR %02x\n", + status); + return -EINVAL; + } + } + + for (p = cg3_regvals[type]; *p; p += 2) { + u8 __iomem *regp = &((u8 __iomem *)par->regs)[p[0]]; + sbus_writeb(p[1], regp); + } + for (p = cg3_dacvals; *p; p += 2) { + u8 __iomem *regp; + + regp = (u8 __iomem *)&par->regs->cmap.addr; + sbus_writeb(p[0], regp); + regp = (u8 __iomem *)&par->regs->cmap.control; + sbus_writeb(p[1], regp); + } + return 0; +} + +static int cg3_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct cg3_par *par; + int linebytes, err; + + info = framebuffer_alloc(sizeof(struct cg3_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + par = info->par; + + spin_lock_init(&par->lock); + + info->fix.smem_start = op->resource[0].start; + par->which_io = op->resource[0].flags & IORESOURCE_BITS; + + sbusfb_fill_var(&info->var, dp, 8); + info->var.red.length = 8; + info->var.green.length = 8; + info->var.blue.length = 8; + if (!strcmp(dp->name, "cgRDI")) + par->flags |= CG3_FLAG_RDI; + if (par->flags & CG3_FLAG_RDI) + cg3_rdi_maybe_fixup_var(&info->var, dp); + + linebytes = of_getintprop_default(dp, "linebytes", + info->var.xres); + info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); + + par->regs = of_ioremap(&op->resource[0], CG3_REGS_OFFSET, + sizeof(struct cg3_regs), "cg3 regs"); + if (!par->regs) + goto out_release_fb; + + info->flags = FBINFO_DEFAULT; + info->fbops = &cg3_ops; + info->screen_base = of_ioremap(&op->resource[0], CG3_RAM_OFFSET, + info->fix.smem_len, "cg3 ram"); + if (!info->screen_base) + goto out_unmap_regs; + + cg3_blank(FB_BLANK_UNBLANK, info); + + if (!of_find_property(dp, "width", NULL)) { + err = cg3_do_default_mode(par); + if (err) + goto out_unmap_screen; + } + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err) + goto out_unmap_screen; + + fb_set_cmap(&info->cmap, info); + + cg3_init_fix(info, linebytes, dp); + + err = register_framebuffer(info); + if (err < 0) + goto out_dealloc_cmap; + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: cg3 at %lx:%lx\n", + dp->full_name, par->which_io, info->fix.smem_start); + + return 0; + +out_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + +out_unmap_screen: + of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len); + +out_unmap_regs: + of_iounmap(&op->resource[0], par->regs, sizeof(struct cg3_regs)); + +out_release_fb: + framebuffer_release(info); + +out_err: + return err; +} + +static int cg3_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct cg3_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + of_iounmap(&op->resource[0], par->regs, sizeof(struct cg3_regs)); + of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id cg3_match[] = { + { + .name = "cgthree", + }, + { + .name = "cgRDI", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cg3_match); + +static struct platform_driver cg3_driver = { + .driver = { + .name = "cg3", + .owner = THIS_MODULE, + .of_match_table = cg3_match, + }, + .probe = cg3_probe, + .remove = cg3_remove, +}; + +static int __init cg3_init(void) +{ + if (fb_get_options("cg3fb", NULL)) + return -ENODEV; + + return platform_driver_register(&cg3_driver); +} + +static void __exit cg3_exit(void) +{ + platform_driver_unregister(&cg3_driver); +} + +module_init(cg3_init); +module_exit(cg3_exit); + +MODULE_DESCRIPTION("framebuffer driver for CGthree chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/cg6.c b/drivers/video/fbdev/cg6.c new file mode 100644 index 000000000000..70781fea092a --- /dev/null +++ b/drivers/video/fbdev/cg6.c @@ -0,0 +1,885 @@ +/* cg6.c: CGSIX (GX, GXplus, TGX) frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int cg6_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); +static int cg6_blank(int, struct fb_info *); + +static void cg6_imageblit(struct fb_info *, const struct fb_image *); +static void cg6_fillrect(struct fb_info *, const struct fb_fillrect *); +static void cg6_copyarea(struct fb_info *info, const struct fb_copyarea *area); +static int cg6_sync(struct fb_info *); +static int cg6_mmap(struct fb_info *, struct vm_area_struct *); +static int cg6_ioctl(struct fb_info *, unsigned int, unsigned long); +static int cg6_pan_display(struct fb_var_screeninfo *, struct fb_info *); + +/* + * Frame buffer operations + */ + +static struct fb_ops cg6_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = cg6_setcolreg, + .fb_blank = cg6_blank, + .fb_pan_display = cg6_pan_display, + .fb_fillrect = cg6_fillrect, + .fb_copyarea = cg6_copyarea, + .fb_imageblit = cg6_imageblit, + .fb_sync = cg6_sync, + .fb_mmap = cg6_mmap, + .fb_ioctl = cg6_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + +/* Offset of interesting structures in the OBIO space */ +/* + * Brooktree is the video dac and is funny to program on the cg6. + * (it's even funnier on the cg3) + * The FBC could be the frame buffer control + * The FHC could is the frame buffer hardware control. + */ +#define CG6_ROM_OFFSET 0x0UL +#define CG6_BROOKTREE_OFFSET 0x200000UL +#define CG6_DHC_OFFSET 0x240000UL +#define CG6_ALT_OFFSET 0x280000UL +#define CG6_FHC_OFFSET 0x300000UL +#define CG6_THC_OFFSET 0x301000UL +#define CG6_FBC_OFFSET 0x700000UL +#define CG6_TEC_OFFSET 0x701000UL +#define CG6_RAM_OFFSET 0x800000UL + +/* FHC definitions */ +#define CG6_FHC_FBID_SHIFT 24 +#define CG6_FHC_FBID_MASK 255 +#define CG6_FHC_REV_SHIFT 20 +#define CG6_FHC_REV_MASK 15 +#define CG6_FHC_FROP_DISABLE (1 << 19) +#define CG6_FHC_ROW_DISABLE (1 << 18) +#define CG6_FHC_SRC_DISABLE (1 << 17) +#define CG6_FHC_DST_DISABLE (1 << 16) +#define CG6_FHC_RESET (1 << 15) +#define CG6_FHC_LITTLE_ENDIAN (1 << 13) +#define CG6_FHC_RES_MASK (3 << 11) +#define CG6_FHC_1024 (0 << 11) +#define CG6_FHC_1152 (1 << 11) +#define CG6_FHC_1280 (2 << 11) +#define CG6_FHC_1600 (3 << 11) +#define CG6_FHC_CPU_MASK (3 << 9) +#define CG6_FHC_CPU_SPARC (0 << 9) +#define CG6_FHC_CPU_68020 (1 << 9) +#define CG6_FHC_CPU_386 (2 << 9) +#define CG6_FHC_TEST (1 << 8) +#define CG6_FHC_TEST_X_SHIFT 4 +#define CG6_FHC_TEST_X_MASK 15 +#define CG6_FHC_TEST_Y_SHIFT 0 +#define CG6_FHC_TEST_Y_MASK 15 + +/* FBC mode definitions */ +#define CG6_FBC_BLIT_IGNORE 0x00000000 +#define CG6_FBC_BLIT_NOSRC 0x00100000 +#define CG6_FBC_BLIT_SRC 0x00200000 +#define CG6_FBC_BLIT_ILLEGAL 0x00300000 +#define CG6_FBC_BLIT_MASK 0x00300000 + +#define CG6_FBC_VBLANK 0x00080000 + +#define CG6_FBC_MODE_IGNORE 0x00000000 +#define CG6_FBC_MODE_COLOR8 0x00020000 +#define CG6_FBC_MODE_COLOR1 0x00040000 +#define CG6_FBC_MODE_HRMONO 0x00060000 +#define CG6_FBC_MODE_MASK 0x00060000 + +#define CG6_FBC_DRAW_IGNORE 0x00000000 +#define CG6_FBC_DRAW_RENDER 0x00008000 +#define CG6_FBC_DRAW_PICK 0x00010000 +#define CG6_FBC_DRAW_ILLEGAL 0x00018000 +#define CG6_FBC_DRAW_MASK 0x00018000 + +#define CG6_FBC_BWRITE0_IGNORE 0x00000000 +#define CG6_FBC_BWRITE0_ENABLE 0x00002000 +#define CG6_FBC_BWRITE0_DISABLE 0x00004000 +#define CG6_FBC_BWRITE0_ILLEGAL 0x00006000 +#define CG6_FBC_BWRITE0_MASK 0x00006000 + +#define CG6_FBC_BWRITE1_IGNORE 0x00000000 +#define CG6_FBC_BWRITE1_ENABLE 0x00000800 +#define CG6_FBC_BWRITE1_DISABLE 0x00001000 +#define CG6_FBC_BWRITE1_ILLEGAL 0x00001800 +#define CG6_FBC_BWRITE1_MASK 0x00001800 + +#define CG6_FBC_BREAD_IGNORE 0x00000000 +#define CG6_FBC_BREAD_0 0x00000200 +#define CG6_FBC_BREAD_1 0x00000400 +#define CG6_FBC_BREAD_ILLEGAL 0x00000600 +#define CG6_FBC_BREAD_MASK 0x00000600 + +#define CG6_FBC_BDISP_IGNORE 0x00000000 +#define CG6_FBC_BDISP_0 0x00000080 +#define CG6_FBC_BDISP_1 0x00000100 +#define CG6_FBC_BDISP_ILLEGAL 0x00000180 +#define CG6_FBC_BDISP_MASK 0x00000180 + +#define CG6_FBC_INDEX_MOD 0x00000040 +#define CG6_FBC_INDEX_MASK 0x00000030 + +/* THC definitions */ +#define CG6_THC_MISC_REV_SHIFT 16 +#define CG6_THC_MISC_REV_MASK 15 +#define CG6_THC_MISC_RESET (1 << 12) +#define CG6_THC_MISC_VIDEO (1 << 10) +#define CG6_THC_MISC_SYNC (1 << 9) +#define CG6_THC_MISC_VSYNC (1 << 8) +#define CG6_THC_MISC_SYNC_ENAB (1 << 7) +#define CG6_THC_MISC_CURS_RES (1 << 6) +#define CG6_THC_MISC_INT_ENAB (1 << 5) +#define CG6_THC_MISC_INT (1 << 4) +#define CG6_THC_MISC_INIT 0x9f +#define CG6_THC_CURSOFF ((65536-32) | ((65536-32) << 16)) + +/* The contents are unknown */ +struct cg6_tec { + int tec_matrix; + int tec_clip; + int tec_vdc; +}; + +struct cg6_thc { + u32 thc_pad0[512]; + u32 thc_hs; /* hsync timing */ + u32 thc_hsdvs; + u32 thc_hd; + u32 thc_vs; /* vsync timing */ + u32 thc_vd; + u32 thc_refresh; + u32 thc_misc; + u32 thc_pad1[56]; + u32 thc_cursxy; /* cursor x,y position (16 bits each) */ + u32 thc_cursmask[32]; /* cursor mask bits */ + u32 thc_cursbits[32]; /* what to show where mask enabled */ +}; + +struct cg6_fbc { + u32 xxx0[1]; + u32 mode; + u32 clip; + u32 xxx1[1]; + u32 s; + u32 draw; + u32 blit; + u32 font; + u32 xxx2[24]; + u32 x0, y0, z0, color0; + u32 x1, y1, z1, color1; + u32 x2, y2, z2, color2; + u32 x3, y3, z3, color3; + u32 offx, offy; + u32 xxx3[2]; + u32 incx, incy; + u32 xxx4[2]; + u32 clipminx, clipminy; + u32 xxx5[2]; + u32 clipmaxx, clipmaxy; + u32 xxx6[2]; + u32 fg; + u32 bg; + u32 alu; + u32 pm; + u32 pixelm; + u32 xxx7[2]; + u32 patalign; + u32 pattern[8]; + u32 xxx8[432]; + u32 apointx, apointy, apointz; + u32 xxx9[1]; + u32 rpointx, rpointy, rpointz; + u32 xxx10[5]; + u32 pointr, pointg, pointb, pointa; + u32 alinex, aliney, alinez; + u32 xxx11[1]; + u32 rlinex, rliney, rlinez; + u32 xxx12[5]; + u32 liner, lineg, lineb, linea; + u32 atrix, atriy, atriz; + u32 xxx13[1]; + u32 rtrix, rtriy, rtriz; + u32 xxx14[5]; + u32 trir, trig, trib, tria; + u32 aquadx, aquady, aquadz; + u32 xxx15[1]; + u32 rquadx, rquady, rquadz; + u32 xxx16[5]; + u32 quadr, quadg, quadb, quada; + u32 arectx, arecty, arectz; + u32 xxx17[1]; + u32 rrectx, rrecty, rrectz; + u32 xxx18[5]; + u32 rectr, rectg, rectb, recta; +}; + +struct bt_regs { + u32 addr; + u32 color_map; + u32 control; + u32 cursor; +}; + +struct cg6_par { + spinlock_t lock; + struct bt_regs __iomem *bt; + struct cg6_fbc __iomem *fbc; + struct cg6_thc __iomem *thc; + struct cg6_tec __iomem *tec; + u32 __iomem *fhc; + + u32 flags; +#define CG6_FLAG_BLANKED 0x00000001 + + unsigned long which_io; +}; + +static int cg6_sync(struct fb_info *info) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + struct cg6_fbc __iomem *fbc = par->fbc; + int limit = 10000; + + do { + if (!(sbus_readl(&fbc->s) & 0x10000000)) + break; + udelay(10); + } while (--limit > 0); + + return 0; +} + +static void cg6_switch_from_graph(struct cg6_par *par) +{ + struct cg6_thc __iomem *thc = par->thc; + unsigned long flags; + + spin_lock_irqsave(&par->lock, flags); + + /* Hide the cursor. */ + sbus_writel(CG6_THC_CURSOFF, &thc->thc_cursxy); + + spin_unlock_irqrestore(&par->lock, flags); +} + +static int cg6_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + + /* We just use this to catch switches out of + * graphics mode. + */ + cg6_switch_from_graph(par); + + if (var->xoffset || var->yoffset || var->vmode) + return -EINVAL; + return 0; +} + +/** + * cg6_fillrect - Draws a rectangle on the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @rect: structure defining the rectagle and operation. + */ +static void cg6_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + struct cg6_fbc __iomem *fbc = par->fbc; + unsigned long flags; + s32 val; + + /* CG6 doesn't handle ROP_XOR */ + + spin_lock_irqsave(&par->lock, flags); + + cg6_sync(info); + + sbus_writel(rect->color, &fbc->fg); + sbus_writel(~(u32)0, &fbc->pixelm); + sbus_writel(0xea80ff00, &fbc->alu); + sbus_writel(0, &fbc->s); + sbus_writel(0, &fbc->clip); + sbus_writel(~(u32)0, &fbc->pm); + sbus_writel(rect->dy, &fbc->arecty); + sbus_writel(rect->dx, &fbc->arectx); + sbus_writel(rect->dy + rect->height, &fbc->arecty); + sbus_writel(rect->dx + rect->width, &fbc->arectx); + do { + val = sbus_readl(&fbc->draw); + } while (val < 0 && (val & 0x20000000)); + spin_unlock_irqrestore(&par->lock, flags); +} + +/** + * cg6_copyarea - Copies one area of the screen to another area. + * + * @info: frame buffer structure that represents a single frame buffer + * @area: Structure providing the data to copy the framebuffer contents + * from one region to another. + * + * This drawing operation copies a rectangular area from one area of the + * screen to another area. + */ +static void cg6_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + struct cg6_fbc __iomem *fbc = par->fbc; + unsigned long flags; + int i; + + spin_lock_irqsave(&par->lock, flags); + + cg6_sync(info); + + sbus_writel(0xff, &fbc->fg); + sbus_writel(0x00, &fbc->bg); + sbus_writel(~0, &fbc->pixelm); + sbus_writel(0xe880cccc, &fbc->alu); + sbus_writel(0, &fbc->s); + sbus_writel(0, &fbc->clip); + + sbus_writel(area->sy, &fbc->y0); + sbus_writel(area->sx, &fbc->x0); + sbus_writel(area->sy + area->height - 1, &fbc->y1); + sbus_writel(area->sx + area->width - 1, &fbc->x1); + sbus_writel(area->dy, &fbc->y2); + sbus_writel(area->dx, &fbc->x2); + sbus_writel(area->dy + area->height - 1, &fbc->y3); + sbus_writel(area->dx + area->width - 1, &fbc->x3); + do { + i = sbus_readl(&fbc->blit); + } while (i < 0 && (i & 0x20000000)); + spin_unlock_irqrestore(&par->lock, flags); +} + +/** + * cg6_imageblit - Copies a image from system memory to the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @image: structure defining the image. + */ +static void cg6_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + struct cg6_fbc __iomem *fbc = par->fbc; + const u8 *data = image->data; + unsigned long flags; + u32 x, y; + int i, width; + + if (image->depth > 1) { + cfb_imageblit(info, image); + return; + } + + spin_lock_irqsave(&par->lock, flags); + + cg6_sync(info); + + sbus_writel(image->fg_color, &fbc->fg); + sbus_writel(image->bg_color, &fbc->bg); + sbus_writel(0x140000, &fbc->mode); + sbus_writel(0xe880fc30, &fbc->alu); + sbus_writel(~(u32)0, &fbc->pixelm); + sbus_writel(0, &fbc->s); + sbus_writel(0, &fbc->clip); + sbus_writel(0xff, &fbc->pm); + sbus_writel(32, &fbc->incx); + sbus_writel(0, &fbc->incy); + + x = image->dx; + y = image->dy; + for (i = 0; i < image->height; i++) { + width = image->width; + + while (width >= 32) { + u32 val; + + sbus_writel(y, &fbc->y0); + sbus_writel(x, &fbc->x0); + sbus_writel(x + 32 - 1, &fbc->x1); + + val = ((u32)data[0] << 24) | + ((u32)data[1] << 16) | + ((u32)data[2] << 8) | + ((u32)data[3] << 0); + sbus_writel(val, &fbc->font); + + data += 4; + x += 32; + width -= 32; + } + if (width) { + u32 val; + + sbus_writel(y, &fbc->y0); + sbus_writel(x, &fbc->x0); + sbus_writel(x + width - 1, &fbc->x1); + if (width <= 8) { + val = (u32) data[0] << 24; + data += 1; + } else if (width <= 16) { + val = ((u32) data[0] << 24) | + ((u32) data[1] << 16); + data += 2; + } else { + val = ((u32) data[0] << 24) | + ((u32) data[1] << 16) | + ((u32) data[2] << 8); + data += 3; + } + sbus_writel(val, &fbc->font); + } + + y += 1; + x = image->dx; + } + + spin_unlock_irqrestore(&par->lock, flags); +} + +/** + * cg6_setcolreg - Sets a color register. + * + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int cg6_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + struct bt_regs __iomem *bt = par->bt; + unsigned long flags; + + if (regno >= 256) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + spin_lock_irqsave(&par->lock, flags); + + sbus_writel((u32)regno << 24, &bt->addr); + sbus_writel((u32)red << 24, &bt->color_map); + sbus_writel((u32)green << 24, &bt->color_map); + sbus_writel((u32)blue << 24, &bt->color_map); + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +/** + * cg6_blank - Blanks the display. + * + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int cg6_blank(int blank, struct fb_info *info) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + struct cg6_thc __iomem *thc = par->thc; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&par->lock, flags); + val = sbus_readl(&thc->thc_misc); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + val |= CG6_THC_MISC_VIDEO; + par->flags &= ~CG6_FLAG_BLANKED; + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + case FB_BLANK_POWERDOWN: /* Poweroff */ + val &= ~CG6_THC_MISC_VIDEO; + par->flags |= CG6_FLAG_BLANKED; + break; + } + + sbus_writel(val, &thc->thc_misc); + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static struct sbus_mmap_map cg6_mmap_map[] = { + { + .voff = CG6_FBC, + .poff = CG6_FBC_OFFSET, + .size = PAGE_SIZE + }, + { + .voff = CG6_TEC, + .poff = CG6_TEC_OFFSET, + .size = PAGE_SIZE + }, + { + .voff = CG6_BTREGS, + .poff = CG6_BROOKTREE_OFFSET, + .size = PAGE_SIZE + }, + { + .voff = CG6_FHC, + .poff = CG6_FHC_OFFSET, + .size = PAGE_SIZE + }, + { + .voff = CG6_THC, + .poff = CG6_THC_OFFSET, + .size = PAGE_SIZE + }, + { + .voff = CG6_ROM, + .poff = CG6_ROM_OFFSET, + .size = 0x10000 + }, + { + .voff = CG6_RAM, + .poff = CG6_RAM_OFFSET, + .size = SBUS_MMAP_FBSIZE(1) + }, + { + .voff = CG6_DHC, + .poff = CG6_DHC_OFFSET, + .size = 0x40000 + }, + { .size = 0 } +}; + +static int cg6_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + + return sbusfb_mmap_helper(cg6_mmap_map, + info->fix.smem_start, info->fix.smem_len, + par->which_io, vma); +} + +static int cg6_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + return sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_SUNFAST_COLOR, 8, info->fix.smem_len); +} + +/* + * Initialisation + */ + +static void cg6_init_fix(struct fb_info *info, int linebytes) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + const char *cg6_cpu_name, *cg6_card_name; + u32 conf; + + conf = sbus_readl(par->fhc); + switch (conf & CG6_FHC_CPU_MASK) { + case CG6_FHC_CPU_SPARC: + cg6_cpu_name = "sparc"; + break; + case CG6_FHC_CPU_68020: + cg6_cpu_name = "68020"; + break; + default: + cg6_cpu_name = "i386"; + break; + } + if (((conf >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK) >= 11) { + if (info->fix.smem_len <= 0x100000) + cg6_card_name = "TGX"; + else + cg6_card_name = "TGX+"; + } else { + if (info->fix.smem_len <= 0x100000) + cg6_card_name = "GX"; + else + cg6_card_name = "GX+"; + } + + sprintf(info->fix.id, "%s %s", cg6_card_name, cg6_cpu_name); + info->fix.id[sizeof(info->fix.id) - 1] = 0; + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = linebytes; + + info->fix.accel = FB_ACCEL_SUN_CGSIX; +} + +/* Initialize Brooktree DAC */ +static void cg6_bt_init(struct cg6_par *par) +{ + struct bt_regs __iomem *bt = par->bt; + + sbus_writel(0x04 << 24, &bt->addr); /* color planes */ + sbus_writel(0xff << 24, &bt->control); + sbus_writel(0x05 << 24, &bt->addr); + sbus_writel(0x00 << 24, &bt->control); + sbus_writel(0x06 << 24, &bt->addr); /* overlay plane */ + sbus_writel(0x73 << 24, &bt->control); + sbus_writel(0x07 << 24, &bt->addr); + sbus_writel(0x00 << 24, &bt->control); +} + +static void cg6_chip_init(struct fb_info *info) +{ + struct cg6_par *par = (struct cg6_par *)info->par; + struct cg6_tec __iomem *tec = par->tec; + struct cg6_fbc __iomem *fbc = par->fbc; + struct cg6_thc __iomem *thc = par->thc; + u32 rev, conf, mode; + int i; + + /* Hide the cursor. */ + sbus_writel(CG6_THC_CURSOFF, &thc->thc_cursxy); + + /* Turn off stuff in the Transform Engine. */ + sbus_writel(0, &tec->tec_matrix); + sbus_writel(0, &tec->tec_clip); + sbus_writel(0, &tec->tec_vdc); + + /* Take care of bugs in old revisions. */ + rev = (sbus_readl(par->fhc) >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK; + if (rev < 5) { + conf = (sbus_readl(par->fhc) & CG6_FHC_RES_MASK) | + CG6_FHC_CPU_68020 | CG6_FHC_TEST | + (11 << CG6_FHC_TEST_X_SHIFT) | + (11 << CG6_FHC_TEST_Y_SHIFT); + if (rev < 2) + conf |= CG6_FHC_DST_DISABLE; + sbus_writel(conf, par->fhc); + } + + /* Set things in the FBC. Bad things appear to happen if we do + * back to back store/loads on the mode register, so copy it + * out instead. */ + mode = sbus_readl(&fbc->mode); + do { + i = sbus_readl(&fbc->s); + } while (i & 0x10000000); + mode &= ~(CG6_FBC_BLIT_MASK | CG6_FBC_MODE_MASK | + CG6_FBC_DRAW_MASK | CG6_FBC_BWRITE0_MASK | + CG6_FBC_BWRITE1_MASK | CG6_FBC_BREAD_MASK | + CG6_FBC_BDISP_MASK); + mode |= (CG6_FBC_BLIT_SRC | CG6_FBC_MODE_COLOR8 | + CG6_FBC_DRAW_RENDER | CG6_FBC_BWRITE0_ENABLE | + CG6_FBC_BWRITE1_DISABLE | CG6_FBC_BREAD_0 | + CG6_FBC_BDISP_0); + sbus_writel(mode, &fbc->mode); + + sbus_writel(0, &fbc->clip); + sbus_writel(0, &fbc->offx); + sbus_writel(0, &fbc->offy); + sbus_writel(0, &fbc->clipminx); + sbus_writel(0, &fbc->clipminy); + sbus_writel(info->var.xres - 1, &fbc->clipmaxx); + sbus_writel(info->var.yres - 1, &fbc->clipmaxy); +} + +static void cg6_unmap_regs(struct platform_device *op, struct fb_info *info, + struct cg6_par *par) +{ + if (par->fbc) + of_iounmap(&op->resource[0], par->fbc, 4096); + if (par->tec) + of_iounmap(&op->resource[0], par->tec, sizeof(struct cg6_tec)); + if (par->thc) + of_iounmap(&op->resource[0], par->thc, sizeof(struct cg6_thc)); + if (par->bt) + of_iounmap(&op->resource[0], par->bt, sizeof(struct bt_regs)); + if (par->fhc) + of_iounmap(&op->resource[0], par->fhc, sizeof(u32)); + + if (info->screen_base) + of_iounmap(&op->resource[0], info->screen_base, + info->fix.smem_len); +} + +static int cg6_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct cg6_par *par; + int linebytes, err; + int dblbuf; + + info = framebuffer_alloc(sizeof(struct cg6_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + par = info->par; + + spin_lock_init(&par->lock); + + info->fix.smem_start = op->resource[0].start; + par->which_io = op->resource[0].flags & IORESOURCE_BITS; + + sbusfb_fill_var(&info->var, dp, 8); + info->var.red.length = 8; + info->var.green.length = 8; + info->var.blue.length = 8; + + linebytes = of_getintprop_default(dp, "linebytes", + info->var.xres); + info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); + + dblbuf = of_getintprop_default(dp, "dblbuf", 0); + if (dblbuf) + info->fix.smem_len *= 4; + + par->fbc = of_ioremap(&op->resource[0], CG6_FBC_OFFSET, + 4096, "cgsix fbc"); + par->tec = of_ioremap(&op->resource[0], CG6_TEC_OFFSET, + sizeof(struct cg6_tec), "cgsix tec"); + par->thc = of_ioremap(&op->resource[0], CG6_THC_OFFSET, + sizeof(struct cg6_thc), "cgsix thc"); + par->bt = of_ioremap(&op->resource[0], CG6_BROOKTREE_OFFSET, + sizeof(struct bt_regs), "cgsix dac"); + par->fhc = of_ioremap(&op->resource[0], CG6_FHC_OFFSET, + sizeof(u32), "cgsix fhc"); + + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT | + FBINFO_READS_FAST; + info->fbops = &cg6_ops; + + info->screen_base = of_ioremap(&op->resource[0], CG6_RAM_OFFSET, + info->fix.smem_len, "cgsix ram"); + if (!par->fbc || !par->tec || !par->thc || + !par->bt || !par->fhc || !info->screen_base) + goto out_unmap_regs; + + info->var.accel_flags = FB_ACCELF_TEXT; + + cg6_bt_init(par); + cg6_chip_init(info); + cg6_blank(FB_BLANK_UNBLANK, info); + + if (fb_alloc_cmap(&info->cmap, 256, 0)) + goto out_unmap_regs; + + fb_set_cmap(&info->cmap, info); + cg6_init_fix(info, linebytes); + + err = register_framebuffer(info); + if (err < 0) + goto out_dealloc_cmap; + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: CGsix [%s] at %lx:%lx\n", + dp->full_name, info->fix.id, + par->which_io, info->fix.smem_start); + + return 0; + +out_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + +out_unmap_regs: + cg6_unmap_regs(op, info, par); + framebuffer_release(info); + +out_err: + return err; +} + +static int cg6_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct cg6_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + cg6_unmap_regs(op, info, par); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id cg6_match[] = { + { + .name = "cgsix", + }, + { + .name = "cgthree+", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cg6_match); + +static struct platform_driver cg6_driver = { + .driver = { + .name = "cg6", + .owner = THIS_MODULE, + .of_match_table = cg6_match, + }, + .probe = cg6_probe, + .remove = cg6_remove, +}; + +static int __init cg6_init(void) +{ + if (fb_get_options("cg6fb", NULL)) + return -ENODEV; + + return platform_driver_register(&cg6_driver); +} + +static void __exit cg6_exit(void) +{ + platform_driver_unregister(&cg6_driver); +} + +module_init(cg6_init); +module_exit(cg6_exit); + +MODULE_DESCRIPTION("framebuffer driver for CGsix chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/chipsfb.c b/drivers/video/fbdev/chipsfb.c new file mode 100644 index 000000000000..206a66b61072 --- /dev/null +++ b/drivers/video/fbdev/chipsfb.c @@ -0,0 +1,519 @@ +/* + * drivers/video/chipsfb.c -- frame buffer device for + * Chips & Technologies 65550 chip. + * + * Copyright (C) 1998-2002 Paul Mackerras + * + * This file is derived from the Powermac "chips" driver: + * Copyright (C) 1997 Fabio Riccardi. + * And from the frame buffer device for Open Firmware-initialized devices: + * Copyright (C) 1997 Geert Uytterhoeven. + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/console.h> +#include <asm/io.h> + +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif + +/* + * Since we access the display with inb/outb to fixed port numbers, + * we can only handle one 6555x chip. -- paulus + */ +#define write_ind(num, val, ap, dp) do { \ + outb((num), (ap)); outb((val), (dp)); \ +} while (0) +#define read_ind(num, var, ap, dp) do { \ + outb((num), (ap)); var = inb((dp)); \ +} while (0) + +/* extension registers */ +#define write_xr(num, val) write_ind(num, val, 0x3d6, 0x3d7) +#define read_xr(num, var) read_ind(num, var, 0x3d6, 0x3d7) +/* flat panel registers */ +#define write_fr(num, val) write_ind(num, val, 0x3d0, 0x3d1) +#define read_fr(num, var) read_ind(num, var, 0x3d0, 0x3d1) +/* CRTC registers */ +#define write_cr(num, val) write_ind(num, val, 0x3d4, 0x3d5) +#define read_cr(num, var) read_ind(num, var, 0x3d4, 0x3d5) +/* graphics registers */ +#define write_gr(num, val) write_ind(num, val, 0x3ce, 0x3cf) +#define read_gr(num, var) read_ind(num, var, 0x3ce, 0x3cf) +/* sequencer registers */ +#define write_sr(num, val) write_ind(num, val, 0x3c4, 0x3c5) +#define read_sr(num, var) read_ind(num, var, 0x3c4, 0x3c5) +/* attribute registers - slightly strange */ +#define write_ar(num, val) do { \ + inb(0x3da); write_ind(num, val, 0x3c0, 0x3c0); \ +} while (0) +#define read_ar(num, var) do { \ + inb(0x3da); read_ind(num, var, 0x3c0, 0x3c1); \ +} while (0) + +/* + * Exported functions + */ +int chips_init(void); + +static int chipsfb_pci_init(struct pci_dev *dp, const struct pci_device_id *); +static int chipsfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int chipsfb_set_par(struct fb_info *info); +static int chipsfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int chipsfb_blank(int blank, struct fb_info *info); + +static struct fb_ops chipsfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = chipsfb_check_var, + .fb_set_par = chipsfb_set_par, + .fb_setcolreg = chipsfb_setcolreg, + .fb_blank = chipsfb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int chipsfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->xres > 800 || var->yres > 600 + || var->xres_virtual > 800 || var->yres_virtual > 600 + || (var->bits_per_pixel != 8 && var->bits_per_pixel != 16) + || var->nonstd + || (var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) + return -EINVAL; + + var->xres = var->xres_virtual = 800; + var->yres = var->yres_virtual = 600; + + return 0; +} + +static int chipsfb_set_par(struct fb_info *info) +{ + if (info->var.bits_per_pixel == 16) { + write_cr(0x13, 200); // Set line length (doublewords) + write_xr(0x81, 0x14); // 15 bit (555) color mode + write_xr(0x82, 0x00); // Disable palettes + write_xr(0x20, 0x10); // 16 bit blitter mode + + info->fix.line_length = 800*2; + info->fix.visual = FB_VISUAL_TRUECOLOR; + + info->var.red.offset = 10; + info->var.green.offset = 5; + info->var.blue.offset = 0; + info->var.red.length = info->var.green.length = + info->var.blue.length = 5; + + } else { + /* p->var.bits_per_pixel == 8 */ + write_cr(0x13, 100); // Set line length (doublewords) + write_xr(0x81, 0x12); // 8 bit color mode + write_xr(0x82, 0x08); // Graphics gamma enable + write_xr(0x20, 0x00); // 8 bit blitter mode + + info->fix.line_length = 800; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->var.red.offset = info->var.green.offset = + info->var.blue.offset = 0; + info->var.red.length = info->var.green.length = + info->var.blue.length = 8; + + } + return 0; +} + +static int chipsfb_blank(int blank, struct fb_info *info) +{ + return 1; /* get fb_blank to set the colormap to all black */ +} + +static int chipsfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno > 255) + return 1; + red >>= 8; + green >>= 8; + blue >>= 8; + outb(regno, 0x3c8); + udelay(1); + outb(red, 0x3c9); + outb(green, 0x3c9); + outb(blue, 0x3c9); + + return 0; +} + +struct chips_init_reg { + unsigned char addr; + unsigned char data; +}; + +static struct chips_init_reg chips_init_sr[] = { + { 0x00, 0x03 }, + { 0x01, 0x01 }, + { 0x02, 0x0f }, + { 0x04, 0x0e } +}; + +static struct chips_init_reg chips_init_gr[] = { + { 0x05, 0x00 }, + { 0x06, 0x0d }, + { 0x08, 0xff } +}; + +static struct chips_init_reg chips_init_ar[] = { + { 0x10, 0x01 }, + { 0x12, 0x0f }, + { 0x13, 0x00 } +}; + +static struct chips_init_reg chips_init_cr[] = { + { 0x00, 0x7f }, + { 0x01, 0x63 }, + { 0x02, 0x63 }, + { 0x03, 0x83 }, + { 0x04, 0x66 }, + { 0x05, 0x10 }, + { 0x06, 0x72 }, + { 0x07, 0x3e }, + { 0x08, 0x00 }, + { 0x09, 0x40 }, + { 0x0c, 0x00 }, + { 0x0d, 0x00 }, + { 0x10, 0x59 }, + { 0x11, 0x0d }, + { 0x12, 0x57 }, + { 0x13, 0x64 }, + { 0x14, 0x00 }, + { 0x15, 0x57 }, + { 0x16, 0x73 }, + { 0x17, 0xe3 }, + { 0x18, 0xff }, + { 0x30, 0x02 }, + { 0x31, 0x02 }, + { 0x32, 0x02 }, + { 0x33, 0x02 }, + { 0x40, 0x00 }, + { 0x41, 0x00 }, + { 0x40, 0x80 } +}; + +static struct chips_init_reg chips_init_fr[] = { + { 0x01, 0x02 }, + { 0x03, 0x08 }, + { 0x04, 0x81 }, + { 0x05, 0x21 }, + { 0x08, 0x0c }, + { 0x0a, 0x74 }, + { 0x0b, 0x11 }, + { 0x10, 0x0c }, + { 0x11, 0xe0 }, + /* { 0x12, 0x40 }, -- 3400 needs 40, 2400 needs 48, no way to tell */ + { 0x20, 0x63 }, + { 0x21, 0x68 }, + { 0x22, 0x19 }, + { 0x23, 0x7f }, + { 0x24, 0x68 }, + { 0x26, 0x00 }, + { 0x27, 0x0f }, + { 0x30, 0x57 }, + { 0x31, 0x58 }, + { 0x32, 0x0d }, + { 0x33, 0x72 }, + { 0x34, 0x02 }, + { 0x35, 0x22 }, + { 0x36, 0x02 }, + { 0x37, 0x00 } +}; + +static struct chips_init_reg chips_init_xr[] = { + { 0xce, 0x00 }, /* set default memory clock */ + { 0xcc, 0x43 }, /* memory clock ratio */ + { 0xcd, 0x18 }, + { 0xce, 0xa1 }, + { 0xc8, 0x84 }, + { 0xc9, 0x0a }, + { 0xca, 0x00 }, + { 0xcb, 0x20 }, + { 0xcf, 0x06 }, + { 0xd0, 0x0e }, + { 0x09, 0x01 }, + { 0x0a, 0x02 }, + { 0x0b, 0x01 }, + { 0x20, 0x00 }, + { 0x40, 0x03 }, + { 0x41, 0x01 }, + { 0x42, 0x00 }, + { 0x80, 0x82 }, + { 0x81, 0x12 }, + { 0x82, 0x08 }, + { 0xa0, 0x00 }, + { 0xa8, 0x00 } +}; + +static void __init chips_hw_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(chips_init_xr); ++i) + write_xr(chips_init_xr[i].addr, chips_init_xr[i].data); + outb(0x29, 0x3c2); /* set misc output reg */ + for (i = 0; i < ARRAY_SIZE(chips_init_sr); ++i) + write_sr(chips_init_sr[i].addr, chips_init_sr[i].data); + for (i = 0; i < ARRAY_SIZE(chips_init_gr); ++i) + write_gr(chips_init_gr[i].addr, chips_init_gr[i].data); + for (i = 0; i < ARRAY_SIZE(chips_init_ar); ++i) + write_ar(chips_init_ar[i].addr, chips_init_ar[i].data); + for (i = 0; i < ARRAY_SIZE(chips_init_cr); ++i) + write_cr(chips_init_cr[i].addr, chips_init_cr[i].data); + for (i = 0; i < ARRAY_SIZE(chips_init_fr); ++i) + write_fr(chips_init_fr[i].addr, chips_init_fr[i].data); +} + +static struct fb_fix_screeninfo chipsfb_fix = { + .id = "C&T 65550", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .accel = FB_ACCEL_NONE, + .line_length = 800, + +// FIXME: Assumes 1MB frame buffer, but 65550 supports 1MB or 2MB. +// * "3500" PowerBook G3 (the original PB G3) has 2MB. +// * 2400 has 1MB composed of 2 Mitsubishi M5M4V4265CTP DRAM chips. +// Motherboard actually supports 2MB -- there are two blank locations +// for a second pair of DRAMs. (Thanks, Apple!) +// * 3400 has 1MB (I think). Don't know if it's expandable. +// -- Tim Seufert + .smem_len = 0x100000, /* 1MB */ +}; + +static struct fb_var_screeninfo chipsfb_var = { + .xres = 800, + .yres = 600, + .xres_virtual = 800, + .yres_virtual = 600, + .bits_per_pixel = 8, + .red = { .length = 8 }, + .green = { .length = 8 }, + .blue = { .length = 8 }, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, + .pixclock = 10000, + .left_margin = 16, + .right_margin = 16, + .upper_margin = 16, + .lower_margin = 16, + .hsync_len = 8, + .vsync_len = 8, +}; + +static void init_chips(struct fb_info *p, unsigned long addr) +{ + memset(p->screen_base, 0, 0x100000); + + p->fix = chipsfb_fix; + p->fix.smem_start = addr; + + p->var = chipsfb_var; + + p->fbops = &chipsfb_ops; + p->flags = FBINFO_DEFAULT; + + fb_alloc_cmap(&p->cmap, 256, 0); + + chips_hw_init(); +} + +static int chipsfb_pci_init(struct pci_dev *dp, const struct pci_device_id *ent) +{ + struct fb_info *p; + unsigned long addr, size; + unsigned short cmd; + int rc = -ENODEV; + + if (pci_enable_device(dp) < 0) { + dev_err(&dp->dev, "Cannot enable PCI device\n"); + goto err_out; + } + + if ((dp->resource[0].flags & IORESOURCE_MEM) == 0) + goto err_disable; + addr = pci_resource_start(dp, 0); + size = pci_resource_len(dp, 0); + if (addr == 0) + goto err_disable; + + p = framebuffer_alloc(0, &dp->dev); + if (p == NULL) { + dev_err(&dp->dev, "Cannot allocate framebuffer structure\n"); + rc = -ENOMEM; + goto err_disable; + } + + if (pci_request_region(dp, 0, "chipsfb") != 0) { + dev_err(&dp->dev, "Cannot request framebuffer\n"); + rc = -EBUSY; + goto err_release_fb; + } + +#ifdef __BIG_ENDIAN + addr += 0x800000; // Use big-endian aperture +#endif + + /* we should use pci_enable_device here, but, + the device doesn't declare its I/O ports in its BARs + so pci_enable_device won't turn on I/O responses */ + pci_read_config_word(dp, PCI_COMMAND, &cmd); + cmd |= 3; /* enable memory and IO space */ + pci_write_config_word(dp, PCI_COMMAND, cmd); + +#ifdef CONFIG_PMAC_BACKLIGHT + /* turn on the backlight */ + mutex_lock(&pmac_backlight_mutex); + if (pmac_backlight) { + pmac_backlight->props.power = FB_BLANK_UNBLANK; + backlight_update_status(pmac_backlight); + } + mutex_unlock(&pmac_backlight_mutex); +#endif /* CONFIG_PMAC_BACKLIGHT */ + +#ifdef CONFIG_PPC + p->screen_base = __ioremap(addr, 0x200000, _PAGE_NO_CACHE); +#else + p->screen_base = ioremap(addr, 0x200000); +#endif + if (p->screen_base == NULL) { + dev_err(&dp->dev, "Cannot map framebuffer\n"); + rc = -ENOMEM; + goto err_release_pci; + } + + pci_set_drvdata(dp, p); + + init_chips(p, addr); + + if (register_framebuffer(p) < 0) { + dev_err(&dp->dev,"C&T 65550 framebuffer failed to register\n"); + goto err_unmap; + } + + dev_info(&dp->dev,"fb%d: Chips 65550 frame buffer" + " (%dK RAM detected)\n", + p->node, p->fix.smem_len / 1024); + + return 0; + + err_unmap: + iounmap(p->screen_base); + err_release_pci: + pci_release_region(dp, 0); + err_release_fb: + framebuffer_release(p); + err_disable: + err_out: + return rc; +} + +static void chipsfb_remove(struct pci_dev *dp) +{ + struct fb_info *p = pci_get_drvdata(dp); + + if (p->screen_base == NULL) + return; + unregister_framebuffer(p); + iounmap(p->screen_base); + p->screen_base = NULL; + pci_release_region(dp, 0); +} + +#ifdef CONFIG_PM +static int chipsfb_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct fb_info *p = pci_get_drvdata(pdev); + + if (state.event == pdev->dev.power.power_state.event) + return 0; + if (!(state.event & PM_EVENT_SLEEP)) + goto done; + + console_lock(); + chipsfb_blank(1, p); + fb_set_suspend(p, 1); + console_unlock(); + done: + pdev->dev.power.power_state = state; + return 0; +} + +static int chipsfb_pci_resume(struct pci_dev *pdev) +{ + struct fb_info *p = pci_get_drvdata(pdev); + + console_lock(); + fb_set_suspend(p, 0); + chipsfb_blank(0, p); + console_unlock(); + + pdev->dev.power.power_state = PMSG_ON; + return 0; +} +#endif /* CONFIG_PM */ + + +static struct pci_device_id chipsfb_pci_tbl[] = { + { PCI_VENDOR_ID_CT, PCI_DEVICE_ID_CT_65550, PCI_ANY_ID, PCI_ANY_ID }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, chipsfb_pci_tbl); + +static struct pci_driver chipsfb_driver = { + .name = "chipsfb", + .id_table = chipsfb_pci_tbl, + .probe = chipsfb_pci_init, + .remove = chipsfb_remove, +#ifdef CONFIG_PM + .suspend = chipsfb_pci_suspend, + .resume = chipsfb_pci_resume, +#endif +}; + +int __init chips_init(void) +{ + if (fb_get_options("chipsfb", NULL)) + return -ENODEV; + + return pci_register_driver(&chipsfb_driver); +} + +module_init(chips_init); + +static void __exit chipsfb_exit(void) +{ + pci_unregister_driver(&chipsfb_driver); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/cirrusfb.c b/drivers/video/fbdev/cirrusfb.c new file mode 100644 index 000000000000..d992aa5eb3f0 --- /dev/null +++ b/drivers/video/fbdev/cirrusfb.c @@ -0,0 +1,2952 @@ +/* + * drivers/video/cirrusfb.c - driver for Cirrus Logic chipsets + * + * Copyright 1999-2001 Jeff Garzik <jgarzik@pobox.com> + * + * Contributors (thanks, all!) + * + * David Eger: + * Overhaul for Linux 2.6 + * + * Jeff Rugen: + * Major contributions; Motorola PowerStack (PPC and PCI) support, + * GD54xx, 1280x1024 mode support, change MCLK based on VCLK. + * + * Geert Uytterhoeven: + * Excellent code review. + * + * Lars Hecking: + * Amiga updates and testing. + * + * Original cirrusfb author: Frank Neumann + * + * Based on retz3fb.c and cirrusfb.c: + * Copyright (C) 1997 Jes Sorensen + * Copyright (C) 1996 Frank Neumann + * + *************************************************************** + * + * Format this code with GNU indent '-kr -i8 -pcs' options. + * + * 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/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <asm/pgtable.h> + +#ifdef CONFIG_ZORRO +#include <linux/zorro.h> +#endif +#ifdef CONFIG_PCI +#include <linux/pci.h> +#endif +#ifdef CONFIG_AMIGA +#include <asm/amigahw.h> +#endif + +#include <video/vga.h> +#include <video/cirrus.h> + +/***************************************************************** + * + * debugging and utility macros + * + */ + +/* disable runtime assertions? */ +/* #define CIRRUSFB_NDEBUG */ + +/* debugging assertions */ +#ifndef CIRRUSFB_NDEBUG +#define assert(expr) \ + if (!(expr)) { \ + printk("Assertion failed! %s,%s,%s,line=%d\n", \ + #expr, __FILE__, __func__, __LINE__); \ + } +#else +#define assert(expr) +#endif + +#define MB_ (1024 * 1024) + +/***************************************************************** + * + * chipset information + * + */ + +/* board types */ +enum cirrus_board { + BT_NONE = 0, + BT_SD64, /* GD5434 */ + BT_PICCOLO, /* GD5426 */ + BT_PICASSO, /* GD5426 or GD5428 */ + BT_SPECTRUM, /* GD5426 or GD5428 */ + BT_PICASSO4, /* GD5446 */ + BT_ALPINE, /* GD543x/4x */ + BT_GD5480, + BT_LAGUNA, /* GD5462/64 */ + BT_LAGUNAB, /* GD5465 */ +}; + +/* + * per-board-type information, used for enumerating and abstracting + * chip-specific information + * NOTE: MUST be in the same order as enum cirrus_board in order to + * use direct indexing on this array + * NOTE: '__initdata' cannot be used as some of this info + * is required at runtime. Maybe separate into an init-only and + * a run-time table? + */ +static const struct cirrusfb_board_info_rec { + char *name; /* ASCII name of chipset */ + long maxclock[5]; /* maximum video clock */ + /* for 1/4bpp, 8bpp 15/16bpp, 24bpp, 32bpp - numbers from xorg code */ + bool init_sr07 : 1; /* init SR07 during init_vgachip() */ + bool init_sr1f : 1; /* write SR1F during init_vgachip() */ + /* construct bit 19 of screen start address */ + bool scrn_start_bit19 : 1; + + /* initial SR07 value, then for each mode */ + unsigned char sr07; + unsigned char sr07_1bpp; + unsigned char sr07_1bpp_mux; + unsigned char sr07_8bpp; + unsigned char sr07_8bpp_mux; + + unsigned char sr1f; /* SR1F VGA initial register value */ +} cirrusfb_board_info[] = { + [BT_SD64] = { + .name = "CL SD64", + .maxclock = { + /* guess */ + /* the SD64/P4 have a higher max. videoclock */ + 135100, 135100, 85500, 85500, 0 + }, + .init_sr07 = true, + .init_sr1f = true, + .scrn_start_bit19 = true, + .sr07 = 0xF0, + .sr07_1bpp = 0xF0, + .sr07_1bpp_mux = 0xF6, + .sr07_8bpp = 0xF1, + .sr07_8bpp_mux = 0xF7, + .sr1f = 0x1E + }, + [BT_PICCOLO] = { + .name = "CL Piccolo", + .maxclock = { + /* guess */ + 90000, 90000, 90000, 90000, 90000 + }, + .init_sr07 = true, + .init_sr1f = true, + .scrn_start_bit19 = false, + .sr07 = 0x80, + .sr07_1bpp = 0x80, + .sr07_8bpp = 0x81, + .sr1f = 0x22 + }, + [BT_PICASSO] = { + .name = "CL Picasso", + .maxclock = { + /* guess */ + 90000, 90000, 90000, 90000, 90000 + }, + .init_sr07 = true, + .init_sr1f = true, + .scrn_start_bit19 = false, + .sr07 = 0x20, + .sr07_1bpp = 0x20, + .sr07_8bpp = 0x21, + .sr1f = 0x22 + }, + [BT_SPECTRUM] = { + .name = "CL Spectrum", + .maxclock = { + /* guess */ + 90000, 90000, 90000, 90000, 90000 + }, + .init_sr07 = true, + .init_sr1f = true, + .scrn_start_bit19 = false, + .sr07 = 0x80, + .sr07_1bpp = 0x80, + .sr07_8bpp = 0x81, + .sr1f = 0x22 + }, + [BT_PICASSO4] = { + .name = "CL Picasso4", + .maxclock = { + 135100, 135100, 85500, 85500, 0 + }, + .init_sr07 = true, + .init_sr1f = false, + .scrn_start_bit19 = true, + .sr07 = 0xA0, + .sr07_1bpp = 0xA0, + .sr07_1bpp_mux = 0xA6, + .sr07_8bpp = 0xA1, + .sr07_8bpp_mux = 0xA7, + .sr1f = 0 + }, + [BT_ALPINE] = { + .name = "CL Alpine", + .maxclock = { + /* for the GD5430. GD5446 can do more... */ + 85500, 85500, 50000, 28500, 0 + }, + .init_sr07 = true, + .init_sr1f = true, + .scrn_start_bit19 = true, + .sr07 = 0xA0, + .sr07_1bpp = 0xA0, + .sr07_1bpp_mux = 0xA6, + .sr07_8bpp = 0xA1, + .sr07_8bpp_mux = 0xA7, + .sr1f = 0x1C + }, + [BT_GD5480] = { + .name = "CL GD5480", + .maxclock = { + 135100, 200000, 200000, 135100, 135100 + }, + .init_sr07 = true, + .init_sr1f = true, + .scrn_start_bit19 = true, + .sr07 = 0x10, + .sr07_1bpp = 0x11, + .sr07_8bpp = 0x11, + .sr1f = 0x1C + }, + [BT_LAGUNA] = { + .name = "CL Laguna", + .maxclock = { + /* taken from X11 code */ + 170000, 170000, 170000, 170000, 135100, + }, + .init_sr07 = false, + .init_sr1f = false, + .scrn_start_bit19 = true, + }, + [BT_LAGUNAB] = { + .name = "CL Laguna AGP", + .maxclock = { + /* taken from X11 code */ + 170000, 250000, 170000, 170000, 135100, + }, + .init_sr07 = false, + .init_sr1f = false, + .scrn_start_bit19 = true, + } +}; + +#ifdef CONFIG_PCI +#define CHIP(id, btype) \ + { PCI_VENDOR_ID_CIRRUS, id, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (btype) } + +static struct pci_device_id cirrusfb_pci_table[] = { + CHIP(PCI_DEVICE_ID_CIRRUS_5436, BT_ALPINE), + CHIP(PCI_DEVICE_ID_CIRRUS_5434_8, BT_SD64), + CHIP(PCI_DEVICE_ID_CIRRUS_5434_4, BT_SD64), + CHIP(PCI_DEVICE_ID_CIRRUS_5430, BT_ALPINE), /* GD-5440 is same id */ + CHIP(PCI_DEVICE_ID_CIRRUS_7543, BT_ALPINE), + CHIP(PCI_DEVICE_ID_CIRRUS_7548, BT_ALPINE), + CHIP(PCI_DEVICE_ID_CIRRUS_5480, BT_GD5480), /* MacPicasso likely */ + CHIP(PCI_DEVICE_ID_CIRRUS_5446, BT_PICASSO4), /* Picasso 4 is 5446 */ + CHIP(PCI_DEVICE_ID_CIRRUS_5462, BT_LAGUNA), /* CL Laguna */ + CHIP(PCI_DEVICE_ID_CIRRUS_5464, BT_LAGUNA), /* CL Laguna 3D */ + CHIP(PCI_DEVICE_ID_CIRRUS_5465, BT_LAGUNAB), /* CL Laguna 3DA*/ + { 0, } +}; +MODULE_DEVICE_TABLE(pci, cirrusfb_pci_table); +#undef CHIP +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_ZORRO +struct zorrocl { + enum cirrus_board type; /* Board type */ + u32 regoffset; /* Offset of registers in first Zorro device */ + u32 ramsize; /* Size of video RAM in first Zorro device */ + /* If zero, use autoprobe on RAM device */ + u32 ramoffset; /* Offset of video RAM in first Zorro device */ + zorro_id ramid; /* Zorro ID of RAM device */ + zorro_id ramid2; /* Zorro ID of optional second RAM device */ +}; + +static const struct zorrocl zcl_sd64 = { + .type = BT_SD64, + .ramid = ZORRO_PROD_HELFRICH_SD64_RAM, +}; + +static const struct zorrocl zcl_piccolo = { + .type = BT_PICCOLO, + .ramid = ZORRO_PROD_HELFRICH_PICCOLO_RAM, +}; + +static const struct zorrocl zcl_picasso = { + .type = BT_PICASSO, + .ramid = ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_RAM, +}; + +static const struct zorrocl zcl_spectrum = { + .type = BT_SPECTRUM, + .ramid = ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_RAM, +}; + +static const struct zorrocl zcl_picasso4_z3 = { + .type = BT_PICASSO4, + .regoffset = 0x00600000, + .ramsize = 4 * MB_, + .ramoffset = 0x01000000, /* 0x02000000 for 64 MiB boards */ +}; + +static const struct zorrocl zcl_picasso4_z2 = { + .type = BT_PICASSO4, + .regoffset = 0x10000, + .ramid = ZORRO_PROD_VILLAGE_TRONIC_PICASSO_IV_Z2_RAM1, + .ramid2 = ZORRO_PROD_VILLAGE_TRONIC_PICASSO_IV_Z2_RAM2, +}; + + +static const struct zorro_device_id cirrusfb_zorro_table[] = { + { + .id = ZORRO_PROD_HELFRICH_SD64_REG, + .driver_data = (unsigned long)&zcl_sd64, + }, { + .id = ZORRO_PROD_HELFRICH_PICCOLO_REG, + .driver_data = (unsigned long)&zcl_piccolo, + }, { + .id = ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_REG, + .driver_data = (unsigned long)&zcl_picasso, + }, { + .id = ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_REG, + .driver_data = (unsigned long)&zcl_spectrum, + }, { + .id = ZORRO_PROD_VILLAGE_TRONIC_PICASSO_IV_Z3, + .driver_data = (unsigned long)&zcl_picasso4_z3, + }, { + .id = ZORRO_PROD_VILLAGE_TRONIC_PICASSO_IV_Z2_REG, + .driver_data = (unsigned long)&zcl_picasso4_z2, + }, + { 0 } +}; +MODULE_DEVICE_TABLE(zorro, cirrusfb_zorro_table); +#endif /* CONFIG_ZORRO */ + +#ifdef CIRRUSFB_DEBUG +enum cirrusfb_dbg_reg_class { + CRT, + SEQ +}; +#endif /* CIRRUSFB_DEBUG */ + +/* info about board */ +struct cirrusfb_info { + u8 __iomem *regbase; + u8 __iomem *laguna_mmio; + enum cirrus_board btype; + unsigned char SFR; /* Shadow of special function register */ + + int multiplexing; + int doubleVCLK; + int blank_mode; + u32 pseudo_palette[16]; + + void (*unmap)(struct fb_info *info); +}; + +static bool noaccel; +static char *mode_option = "640x480@60"; + +/****************************************************************************/ +/**** BEGIN PROTOTYPES ******************************************************/ + +/*--- Interface used by the world ------------------------------------------*/ +static int cirrusfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); + +/*--- Internal routines ----------------------------------------------------*/ +static void init_vgachip(struct fb_info *info); +static void switch_monitor(struct cirrusfb_info *cinfo, int on); +static void WGen(const struct cirrusfb_info *cinfo, + int regnum, unsigned char val); +static unsigned char RGen(const struct cirrusfb_info *cinfo, int regnum); +static void AttrOn(const struct cirrusfb_info *cinfo); +static void WHDR(const struct cirrusfb_info *cinfo, unsigned char val); +static void WSFR(struct cirrusfb_info *cinfo, unsigned char val); +static void WSFR2(struct cirrusfb_info *cinfo, unsigned char val); +static void WClut(struct cirrusfb_info *cinfo, unsigned char regnum, + unsigned char red, unsigned char green, unsigned char blue); +#if 0 +static void RClut(struct cirrusfb_info *cinfo, unsigned char regnum, + unsigned char *red, unsigned char *green, + unsigned char *blue); +#endif +static void cirrusfb_WaitBLT(u8 __iomem *regbase); +static void cirrusfb_BitBLT(u8 __iomem *regbase, int bits_per_pixel, + u_short curx, u_short cury, + u_short destx, u_short desty, + u_short width, u_short height, + u_short line_length); +static void cirrusfb_RectFill(u8 __iomem *regbase, int bits_per_pixel, + u_short x, u_short y, + u_short width, u_short height, + u32 fg_color, u32 bg_color, + u_short line_length, u_char blitmode); + +static void bestclock(long freq, int *nom, int *den, int *div); + +#ifdef CIRRUSFB_DEBUG +static void cirrusfb_dbg_reg_dump(struct fb_info *info, caddr_t regbase); +static void cirrusfb_dbg_print_regs(struct fb_info *info, + caddr_t regbase, + enum cirrusfb_dbg_reg_class reg_class, ...); +#endif /* CIRRUSFB_DEBUG */ + +/*** END PROTOTYPES ********************************************************/ +/*****************************************************************************/ +/*** BEGIN Interface Used by the World ***************************************/ + +static inline int is_laguna(const struct cirrusfb_info *cinfo) +{ + return cinfo->btype == BT_LAGUNA || cinfo->btype == BT_LAGUNAB; +} + +static int opencount; + +/*--- Open /dev/fbx ---------------------------------------------------------*/ +static int cirrusfb_open(struct fb_info *info, int user) +{ + if (opencount++ == 0) + switch_monitor(info->par, 1); + return 0; +} + +/*--- Close /dev/fbx --------------------------------------------------------*/ +static int cirrusfb_release(struct fb_info *info, int user) +{ + if (--opencount == 0) + switch_monitor(info->par, 0); + return 0; +} + +/**** END Interface used by the World *************************************/ +/****************************************************************************/ +/**** BEGIN Hardware specific Routines **************************************/ + +/* Check if the MCLK is not a better clock source */ +static int cirrusfb_check_mclk(struct fb_info *info, long freq) +{ + struct cirrusfb_info *cinfo = info->par; + long mclk = vga_rseq(cinfo->regbase, CL_SEQR1F) & 0x3f; + + /* Read MCLK value */ + mclk = (14318 * mclk) >> 3; + dev_dbg(info->device, "Read MCLK of %ld kHz\n", mclk); + + /* Determine if we should use MCLK instead of VCLK, and if so, what we + * should divide it by to get VCLK + */ + + if (abs(freq - mclk) < 250) { + dev_dbg(info->device, "Using VCLK = MCLK\n"); + return 1; + } else if (abs(freq - (mclk / 2)) < 250) { + dev_dbg(info->device, "Using VCLK = MCLK/2\n"); + return 2; + } + + return 0; +} + +static int cirrusfb_check_pixclock(const struct fb_var_screeninfo *var, + struct fb_info *info) +{ + long freq; + long maxclock; + struct cirrusfb_info *cinfo = info->par; + unsigned maxclockidx = var->bits_per_pixel >> 3; + + /* convert from ps to kHz */ + freq = PICOS2KHZ(var->pixclock); + + dev_dbg(info->device, "desired pixclock: %ld kHz\n", freq); + + maxclock = cirrusfb_board_info[cinfo->btype].maxclock[maxclockidx]; + cinfo->multiplexing = 0; + + /* If the frequency is greater than we can support, we might be able + * to use multiplexing for the video mode */ + if (freq > maxclock) { + dev_err(info->device, + "Frequency greater than maxclock (%ld kHz)\n", + maxclock); + return -EINVAL; + } + /* + * Additional constraint: 8bpp uses DAC clock doubling to allow maximum + * pixel clock + */ + if (var->bits_per_pixel == 8) { + switch (cinfo->btype) { + case BT_ALPINE: + case BT_SD64: + case BT_PICASSO4: + if (freq > 85500) + cinfo->multiplexing = 1; + break; + case BT_GD5480: + if (freq > 135100) + cinfo->multiplexing = 1; + break; + + default: + break; + } + } + + /* If we have a 1MB 5434, we need to put ourselves in a mode where + * the VCLK is double the pixel clock. */ + cinfo->doubleVCLK = 0; + if (cinfo->btype == BT_SD64 && info->fix.smem_len <= MB_ && + var->bits_per_pixel == 16) { + cinfo->doubleVCLK = 1; + } + + return 0; +} + +static int cirrusfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int yres; + /* memory size in pixels */ + unsigned pixels = info->screen_size * 8 / var->bits_per_pixel; + struct cirrusfb_info *cinfo = info->par; + + switch (var->bits_per_pixel) { + case 1: + var->red.offset = 0; + var->red.length = 1; + var->green = var->red; + var->blue = var->red; + break; + + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green = var->red; + var->blue = var->red; + break; + + case 16: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + break; + + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + + default: + dev_dbg(info->device, + "Unsupported bpp size: %d\n", var->bits_per_pixel); + return -EINVAL; + } + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + /* use highest possible virtual resolution */ + if (var->yres_virtual == -1) { + var->yres_virtual = pixels / var->xres_virtual; + + dev_info(info->device, + "virtual resolution set to maximum of %dx%d\n", + var->xres_virtual, var->yres_virtual); + } + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xres_virtual * var->yres_virtual > pixels) { + dev_err(info->device, "mode %dx%dx%d rejected... " + "virtual resolution too high to fit into video memory!\n", + var->xres_virtual, var->yres_virtual, + var->bits_per_pixel); + return -EINVAL; + } + + /* truncate xoffset and yoffset to maximum if too high */ + if (var->xoffset > var->xres_virtual - var->xres) + var->xoffset = var->xres_virtual - var->xres - 1; + if (var->yoffset > var->yres_virtual - var->yres) + var->yoffset = var->yres_virtual - var->yres - 1; + + var->red.msb_right = + var->green.msb_right = + var->blue.msb_right = + var->transp.offset = + var->transp.length = + var->transp.msb_right = 0; + + yres = var->yres; + if (var->vmode & FB_VMODE_DOUBLE) + yres *= 2; + else if (var->vmode & FB_VMODE_INTERLACED) + yres = (yres + 1) / 2; + + if (yres >= 1280) { + dev_err(info->device, "ERROR: VerticalTotal >= 1280; " + "special treatment required! (TODO)\n"); + return -EINVAL; + } + + if (cirrusfb_check_pixclock(var, info)) + return -EINVAL; + + if (!is_laguna(cinfo)) + var->accel_flags = FB_ACCELF_TEXT; + + return 0; +} + +static void cirrusfb_set_mclk_as_source(const struct fb_info *info, int div) +{ + struct cirrusfb_info *cinfo = info->par; + unsigned char old1f, old1e; + + assert(cinfo != NULL); + old1f = vga_rseq(cinfo->regbase, CL_SEQR1F) & ~0x40; + + if (div) { + dev_dbg(info->device, "Set %s as pixclock source.\n", + (div == 2) ? "MCLK/2" : "MCLK"); + old1f |= 0x40; + old1e = vga_rseq(cinfo->regbase, CL_SEQR1E) & ~0x1; + if (div == 2) + old1e |= 1; + + vga_wseq(cinfo->regbase, CL_SEQR1E, old1e); + } + vga_wseq(cinfo->regbase, CL_SEQR1F, old1f); +} + +/************************************************************************* + cirrusfb_set_par_foo() + + actually writes the values for a new video mode into the hardware, +**************************************************************************/ +static int cirrusfb_set_par_foo(struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + struct fb_var_screeninfo *var = &info->var; + u8 __iomem *regbase = cinfo->regbase; + unsigned char tmp; + int pitch; + const struct cirrusfb_board_info_rec *bi; + int hdispend, hsyncstart, hsyncend, htotal; + int yres, vdispend, vsyncstart, vsyncend, vtotal; + long freq; + int nom, den, div; + unsigned int control = 0, format = 0, threshold = 0; + + dev_dbg(info->device, "Requested mode: %dx%dx%d\n", + var->xres, var->yres, var->bits_per_pixel); + + switch (var->bits_per_pixel) { + case 1: + info->fix.line_length = var->xres_virtual / 8; + info->fix.visual = FB_VISUAL_MONO10; + break; + + case 8: + info->fix.line_length = var->xres_virtual; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + + case 16: + case 24: + info->fix.line_length = var->xres_virtual * + var->bits_per_pixel >> 3; + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + } + info->fix.type = FB_TYPE_PACKED_PIXELS; + + init_vgachip(info); + + bi = &cirrusfb_board_info[cinfo->btype]; + + hsyncstart = var->xres + var->right_margin; + hsyncend = hsyncstart + var->hsync_len; + htotal = (hsyncend + var->left_margin) / 8; + hdispend = var->xres / 8; + hsyncstart = hsyncstart / 8; + hsyncend = hsyncend / 8; + + vdispend = var->yres; + vsyncstart = vdispend + var->lower_margin; + vsyncend = vsyncstart + var->vsync_len; + vtotal = vsyncend + var->upper_margin; + + if (var->vmode & FB_VMODE_DOUBLE) { + vdispend *= 2; + vsyncstart *= 2; + vsyncend *= 2; + vtotal *= 2; + } else if (var->vmode & FB_VMODE_INTERLACED) { + vdispend = (vdispend + 1) / 2; + vsyncstart = (vsyncstart + 1) / 2; + vsyncend = (vsyncend + 1) / 2; + vtotal = (vtotal + 1) / 2; + } + yres = vdispend; + if (yres >= 1024) { + vtotal /= 2; + vsyncstart /= 2; + vsyncend /= 2; + vdispend /= 2; + } + + vdispend -= 1; + vsyncstart -= 1; + vsyncend -= 1; + vtotal -= 2; + + if (cinfo->multiplexing) { + htotal /= 2; + hsyncstart /= 2; + hsyncend /= 2; + hdispend /= 2; + } + + htotal -= 5; + hdispend -= 1; + hsyncstart += 1; + hsyncend += 1; + + /* unlock register VGA_CRTC_H_TOTAL..CRT7 */ + vga_wcrt(regbase, VGA_CRTC_V_SYNC_END, 0x20); /* previously: 0x00) */ + + /* if debugging is enabled, all parameters get output before writing */ + dev_dbg(info->device, "CRT0: %d\n", htotal); + vga_wcrt(regbase, VGA_CRTC_H_TOTAL, htotal); + + dev_dbg(info->device, "CRT1: %d\n", hdispend); + vga_wcrt(regbase, VGA_CRTC_H_DISP, hdispend); + + dev_dbg(info->device, "CRT2: %d\n", var->xres / 8); + vga_wcrt(regbase, VGA_CRTC_H_BLANK_START, var->xres / 8); + + /* + 128: Compatible read */ + dev_dbg(info->device, "CRT3: 128+%d\n", (htotal + 5) % 32); + vga_wcrt(regbase, VGA_CRTC_H_BLANK_END, + 128 + ((htotal + 5) % 32)); + + dev_dbg(info->device, "CRT4: %d\n", hsyncstart); + vga_wcrt(regbase, VGA_CRTC_H_SYNC_START, hsyncstart); + + tmp = hsyncend % 32; + if ((htotal + 5) & 32) + tmp += 128; + dev_dbg(info->device, "CRT5: %d\n", tmp); + vga_wcrt(regbase, VGA_CRTC_H_SYNC_END, tmp); + + dev_dbg(info->device, "CRT6: %d\n", vtotal & 0xff); + vga_wcrt(regbase, VGA_CRTC_V_TOTAL, vtotal & 0xff); + + tmp = 16; /* LineCompare bit #9 */ + if (vtotal & 256) + tmp |= 1; + if (vdispend & 256) + tmp |= 2; + if (vsyncstart & 256) + tmp |= 4; + if ((vdispend + 1) & 256) + tmp |= 8; + if (vtotal & 512) + tmp |= 32; + if (vdispend & 512) + tmp |= 64; + if (vsyncstart & 512) + tmp |= 128; + dev_dbg(info->device, "CRT7: %d\n", tmp); + vga_wcrt(regbase, VGA_CRTC_OVERFLOW, tmp); + + tmp = 0x40; /* LineCompare bit #8 */ + if ((vdispend + 1) & 512) + tmp |= 0x20; + if (var->vmode & FB_VMODE_DOUBLE) + tmp |= 0x80; + dev_dbg(info->device, "CRT9: %d\n", tmp); + vga_wcrt(regbase, VGA_CRTC_MAX_SCAN, tmp); + + dev_dbg(info->device, "CRT10: %d\n", vsyncstart & 0xff); + vga_wcrt(regbase, VGA_CRTC_V_SYNC_START, vsyncstart & 0xff); + + dev_dbg(info->device, "CRT11: 64+32+%d\n", vsyncend % 16); + vga_wcrt(regbase, VGA_CRTC_V_SYNC_END, vsyncend % 16 + 64 + 32); + + dev_dbg(info->device, "CRT12: %d\n", vdispend & 0xff); + vga_wcrt(regbase, VGA_CRTC_V_DISP_END, vdispend & 0xff); + + dev_dbg(info->device, "CRT15: %d\n", (vdispend + 1) & 0xff); + vga_wcrt(regbase, VGA_CRTC_V_BLANK_START, (vdispend + 1) & 0xff); + + dev_dbg(info->device, "CRT16: %d\n", vtotal & 0xff); + vga_wcrt(regbase, VGA_CRTC_V_BLANK_END, vtotal & 0xff); + + dev_dbg(info->device, "CRT18: 0xff\n"); + vga_wcrt(regbase, VGA_CRTC_LINE_COMPARE, 0xff); + + tmp = 0; + if (var->vmode & FB_VMODE_INTERLACED) + tmp |= 1; + if ((htotal + 5) & 64) + tmp |= 16; + if ((htotal + 5) & 128) + tmp |= 32; + if (vtotal & 256) + tmp |= 64; + if (vtotal & 512) + tmp |= 128; + + dev_dbg(info->device, "CRT1a: %d\n", tmp); + vga_wcrt(regbase, CL_CRT1A, tmp); + + freq = PICOS2KHZ(var->pixclock); + if (var->bits_per_pixel == 24) + if (cinfo->btype == BT_ALPINE || cinfo->btype == BT_SD64) + freq *= 3; + if (cinfo->multiplexing) + freq /= 2; + if (cinfo->doubleVCLK) + freq *= 2; + + bestclock(freq, &nom, &den, &div); + + dev_dbg(info->device, "VCLK freq: %ld kHz nom: %d den: %d div: %d\n", + freq, nom, den, div); + + /* set VCLK0 */ + /* hardware RefClock: 14.31818 MHz */ + /* formula: VClk = (OSC * N) / (D * (1+P)) */ + /* Example: VClk = (14.31818 * 91) / (23 * (1+1)) = 28.325 MHz */ + + if (cinfo->btype == BT_ALPINE || cinfo->btype == BT_PICASSO4 || + cinfo->btype == BT_SD64) { + /* if freq is close to mclk or mclk/2 select mclk + * as clock source + */ + int divMCLK = cirrusfb_check_mclk(info, freq); + if (divMCLK) + nom = 0; + cirrusfb_set_mclk_as_source(info, divMCLK); + } + if (is_laguna(cinfo)) { + long pcifc = fb_readl(cinfo->laguna_mmio + 0x3fc); + unsigned char tile = fb_readb(cinfo->laguna_mmio + 0x407); + unsigned short tile_control; + + if (cinfo->btype == BT_LAGUNAB) { + tile_control = fb_readw(cinfo->laguna_mmio + 0x2c4); + tile_control &= ~0x80; + fb_writew(tile_control, cinfo->laguna_mmio + 0x2c4); + } + + fb_writel(pcifc | 0x10000000l, cinfo->laguna_mmio + 0x3fc); + fb_writeb(tile & 0x3f, cinfo->laguna_mmio + 0x407); + control = fb_readw(cinfo->laguna_mmio + 0x402); + threshold = fb_readw(cinfo->laguna_mmio + 0xea); + control &= ~0x6800; + format = 0; + threshold &= 0xffc0 & 0x3fbf; + } + if (nom) { + tmp = den << 1; + if (div != 0) + tmp |= 1; + /* 6 bit denom; ONLY 5434!!! (bugged me 10 days) */ + if ((cinfo->btype == BT_SD64) || + (cinfo->btype == BT_ALPINE) || + (cinfo->btype == BT_GD5480)) + tmp |= 0x80; + + /* Laguna chipset has reversed clock registers */ + if (is_laguna(cinfo)) { + vga_wseq(regbase, CL_SEQRE, tmp); + vga_wseq(regbase, CL_SEQR1E, nom); + } else { + vga_wseq(regbase, CL_SEQRE, nom); + vga_wseq(regbase, CL_SEQR1E, tmp); + } + } + + if (yres >= 1024) + /* 1280x1024 */ + vga_wcrt(regbase, VGA_CRTC_MODE, 0xc7); + else + /* mode control: VGA_CRTC_START_HI enable, ROTATE(?), 16bit + * address wrap, no compat. */ + vga_wcrt(regbase, VGA_CRTC_MODE, 0xc3); + + /* don't know if it would hurt to also program this if no interlaced */ + /* mode is used, but I feel better this way.. :-) */ + if (var->vmode & FB_VMODE_INTERLACED) + vga_wcrt(regbase, VGA_CRTC_REGS, htotal / 2); + else + vga_wcrt(regbase, VGA_CRTC_REGS, 0x00); /* interlace control */ + + /* adjust horizontal/vertical sync type (low/high), use VCLK3 */ + /* enable display memory & CRTC I/O address for color mode */ + tmp = 0x03 | 0xc; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + tmp |= 0x40; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + tmp |= 0x80; + WGen(cinfo, VGA_MIS_W, tmp); + + /* text cursor on and start line */ + vga_wcrt(regbase, VGA_CRTC_CURSOR_START, 0); + /* text cursor end line */ + vga_wcrt(regbase, VGA_CRTC_CURSOR_END, 31); + + /****************************************************** + * + * 1 bpp + * + */ + + /* programming for different color depths */ + if (var->bits_per_pixel == 1) { + dev_dbg(info->device, "preparing for 1 bit deep display\n"); + vga_wgfx(regbase, VGA_GFX_MODE, 0); /* mode register */ + + /* SR07 */ + switch (cinfo->btype) { + case BT_SD64: + case BT_PICCOLO: + case BT_PICASSO: + case BT_SPECTRUM: + case BT_PICASSO4: + case BT_ALPINE: + case BT_GD5480: + vga_wseq(regbase, CL_SEQR7, + cinfo->multiplexing ? + bi->sr07_1bpp_mux : bi->sr07_1bpp); + break; + + case BT_LAGUNA: + case BT_LAGUNAB: + vga_wseq(regbase, CL_SEQR7, + vga_rseq(regbase, CL_SEQR7) & ~0x01); + break; + + default: + dev_warn(info->device, "unknown Board\n"); + break; + } + + /* Extended Sequencer Mode */ + switch (cinfo->btype) { + + case BT_PICCOLO: + case BT_SPECTRUM: + /* evtl d0 bei 1 bit? avoid FIFO underruns..? */ + vga_wseq(regbase, CL_SEQRF, 0xb0); + break; + + case BT_PICASSO: + /* ## vorher d0 avoid FIFO underruns..? */ + vga_wseq(regbase, CL_SEQRF, 0xd0); + break; + + case BT_SD64: + case BT_PICASSO4: + case BT_ALPINE: + case BT_GD5480: + case BT_LAGUNA: + case BT_LAGUNAB: + /* do nothing */ + break; + + default: + dev_warn(info->device, "unknown Board\n"); + break; + } + + /* pixel mask: pass-through for first plane */ + WGen(cinfo, VGA_PEL_MSK, 0x01); + if (cinfo->multiplexing) + /* hidden dac reg: 1280x1024 */ + WHDR(cinfo, 0x4a); + else + /* hidden dac: nothing */ + WHDR(cinfo, 0); + /* memory mode: odd/even, ext. memory */ + vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, 0x06); + /* plane mask: only write to first plane */ + vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, 0x01); + } + + /****************************************************** + * + * 8 bpp + * + */ + + else if (var->bits_per_pixel == 8) { + dev_dbg(info->device, "preparing for 8 bit deep display\n"); + switch (cinfo->btype) { + case BT_SD64: + case BT_PICCOLO: + case BT_PICASSO: + case BT_SPECTRUM: + case BT_PICASSO4: + case BT_ALPINE: + case BT_GD5480: + vga_wseq(regbase, CL_SEQR7, + cinfo->multiplexing ? + bi->sr07_8bpp_mux : bi->sr07_8bpp); + break; + + case BT_LAGUNA: + case BT_LAGUNAB: + vga_wseq(regbase, CL_SEQR7, + vga_rseq(regbase, CL_SEQR7) | 0x01); + threshold |= 0x10; + break; + + default: + dev_warn(info->device, "unknown Board\n"); + break; + } + + switch (cinfo->btype) { + case BT_PICCOLO: + case BT_PICASSO: + case BT_SPECTRUM: + /* Fast Page-Mode writes */ + vga_wseq(regbase, CL_SEQRF, 0xb0); + break; + + case BT_PICASSO4: +#ifdef CONFIG_ZORRO + /* ### INCOMPLETE!! */ + vga_wseq(regbase, CL_SEQRF, 0xb8); +#endif + case BT_ALPINE: + case BT_SD64: + case BT_GD5480: + case BT_LAGUNA: + case BT_LAGUNAB: + /* do nothing */ + break; + + default: + dev_warn(info->device, "unknown board\n"); + break; + } + + /* mode register: 256 color mode */ + vga_wgfx(regbase, VGA_GFX_MODE, 64); + if (cinfo->multiplexing) + /* hidden dac reg: 1280x1024 */ + WHDR(cinfo, 0x4a); + else + /* hidden dac: nothing */ + WHDR(cinfo, 0); + } + + /****************************************************** + * + * 16 bpp + * + */ + + else if (var->bits_per_pixel == 16) { + dev_dbg(info->device, "preparing for 16 bit deep display\n"); + switch (cinfo->btype) { + case BT_PICCOLO: + case BT_SPECTRUM: + vga_wseq(regbase, CL_SEQR7, 0x87); + /* Fast Page-Mode writes */ + vga_wseq(regbase, CL_SEQRF, 0xb0); + break; + + case BT_PICASSO: + vga_wseq(regbase, CL_SEQR7, 0x27); + /* Fast Page-Mode writes */ + vga_wseq(regbase, CL_SEQRF, 0xb0); + break; + + case BT_SD64: + case BT_PICASSO4: + case BT_ALPINE: + /* Extended Sequencer Mode: 256c col. mode */ + vga_wseq(regbase, CL_SEQR7, + cinfo->doubleVCLK ? 0xa3 : 0xa7); + break; + + case BT_GD5480: + vga_wseq(regbase, CL_SEQR7, 0x17); + /* We already set SRF and SR1F */ + break; + + case BT_LAGUNA: + case BT_LAGUNAB: + vga_wseq(regbase, CL_SEQR7, + vga_rseq(regbase, CL_SEQR7) & ~0x01); + control |= 0x2000; + format |= 0x1400; + threshold |= 0x10; + break; + + default: + dev_warn(info->device, "unknown Board\n"); + break; + } + + /* mode register: 256 color mode */ + vga_wgfx(regbase, VGA_GFX_MODE, 64); +#ifdef CONFIG_PCI + WHDR(cinfo, cinfo->doubleVCLK ? 0xe1 : 0xc1); +#elif defined(CONFIG_ZORRO) + /* FIXME: CONFIG_PCI and CONFIG_ZORRO may be defined both */ + WHDR(cinfo, 0xa0); /* hidden dac reg: nothing special */ +#endif + } + + /****************************************************** + * + * 24 bpp + * + */ + + else if (var->bits_per_pixel == 24) { + dev_dbg(info->device, "preparing for 24 bit deep display\n"); + switch (cinfo->btype) { + case BT_PICCOLO: + case BT_SPECTRUM: + vga_wseq(regbase, CL_SEQR7, 0x85); + /* Fast Page-Mode writes */ + vga_wseq(regbase, CL_SEQRF, 0xb0); + break; + + case BT_PICASSO: + vga_wseq(regbase, CL_SEQR7, 0x25); + /* Fast Page-Mode writes */ + vga_wseq(regbase, CL_SEQRF, 0xb0); + break; + + case BT_SD64: + case BT_PICASSO4: + case BT_ALPINE: + /* Extended Sequencer Mode: 256c col. mode */ + vga_wseq(regbase, CL_SEQR7, 0xa5); + break; + + case BT_GD5480: + vga_wseq(regbase, CL_SEQR7, 0x15); + /* We already set SRF and SR1F */ + break; + + case BT_LAGUNA: + case BT_LAGUNAB: + vga_wseq(regbase, CL_SEQR7, + vga_rseq(regbase, CL_SEQR7) & ~0x01); + control |= 0x4000; + format |= 0x2400; + threshold |= 0x20; + break; + + default: + dev_warn(info->device, "unknown Board\n"); + break; + } + + /* mode register: 256 color mode */ + vga_wgfx(regbase, VGA_GFX_MODE, 64); + /* hidden dac reg: 8-8-8 mode (24 or 32) */ + WHDR(cinfo, 0xc5); + } + + /****************************************************** + * + * unknown/unsupported bpp + * + */ + + else + dev_err(info->device, + "What's this? requested color depth == %d.\n", + var->bits_per_pixel); + + pitch = info->fix.line_length >> 3; + vga_wcrt(regbase, VGA_CRTC_OFFSET, pitch & 0xff); + tmp = 0x22; + if (pitch & 0x100) + tmp |= 0x10; /* offset overflow bit */ + + /* screen start addr #16-18, fastpagemode cycles */ + vga_wcrt(regbase, CL_CRT1B, tmp); + + /* screen start address bit 19 */ + if (cirrusfb_board_info[cinfo->btype].scrn_start_bit19) + vga_wcrt(regbase, CL_CRT1D, (pitch >> 9) & 1); + + if (is_laguna(cinfo)) { + tmp = 0; + if ((htotal + 5) & 256) + tmp |= 128; + if (hdispend & 256) + tmp |= 64; + if (hsyncstart & 256) + tmp |= 48; + if (vtotal & 1024) + tmp |= 8; + if (vdispend & 1024) + tmp |= 4; + if (vsyncstart & 1024) + tmp |= 3; + + vga_wcrt(regbase, CL_CRT1E, tmp); + dev_dbg(info->device, "CRT1e: %d\n", tmp); + } + + /* pixel panning */ + vga_wattr(regbase, CL_AR33, 0); + + /* [ EGS: SetOffset(); ] */ + /* From SetOffset(): Turn on VideoEnable bit in Attribute controller */ + AttrOn(cinfo); + + if (is_laguna(cinfo)) { + /* no tiles */ + fb_writew(control | 0x1000, cinfo->laguna_mmio + 0x402); + fb_writew(format, cinfo->laguna_mmio + 0xc0); + fb_writew(threshold, cinfo->laguna_mmio + 0xea); + } + /* finally, turn on everything - turn off "FullBandwidth" bit */ + /* also, set "DotClock%2" bit where requested */ + tmp = 0x01; + +/*** FB_VMODE_CLOCK_HALVE in linux/fb.h not defined anymore ? + if (var->vmode & FB_VMODE_CLOCK_HALVE) + tmp |= 0x08; +*/ + + vga_wseq(regbase, VGA_SEQ_CLOCK_MODE, tmp); + dev_dbg(info->device, "CL_SEQR1: %d\n", tmp); + +#ifdef CIRRUSFB_DEBUG + cirrusfb_dbg_reg_dump(info, NULL); +#endif + + return 0; +} + +/* for some reason incomprehensible to me, cirrusfb requires that you write + * the registers twice for the settings to take..grr. -dte */ +static int cirrusfb_set_par(struct fb_info *info) +{ + cirrusfb_set_par_foo(info); + return cirrusfb_set_par_foo(info); +} + +static int cirrusfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + + if (regno > 255) + return -EINVAL; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + + if (regno >= 16) + return 1; + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + + cinfo->pseudo_palette[regno] = v; + return 0; + } + + if (info->var.bits_per_pixel == 8) + WClut(cinfo, regno, red >> 10, green >> 10, blue >> 10); + + return 0; + +} + +/************************************************************************* + cirrusfb_pan_display() + + performs display panning - provided hardware permits this +**************************************************************************/ +static int cirrusfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int xoffset; + unsigned long base; + unsigned char tmp, xpix; + struct cirrusfb_info *cinfo = info->par; + + /* no range checks for xoffset and yoffset, */ + /* as fb_pan_display has already done this */ + if (var->vmode & FB_VMODE_YWRAP) + return -EINVAL; + + xoffset = var->xoffset * info->var.bits_per_pixel / 8; + + base = var->yoffset * info->fix.line_length + xoffset; + + if (info->var.bits_per_pixel == 1) { + /* base is already correct */ + xpix = (unsigned char) (var->xoffset % 8); + } else { + base /= 4; + xpix = (unsigned char) ((xoffset % 4) * 2); + } + + if (!is_laguna(cinfo)) + cirrusfb_WaitBLT(cinfo->regbase); + + /* lower 8 + 8 bits of screen start address */ + vga_wcrt(cinfo->regbase, VGA_CRTC_START_LO, base & 0xff); + vga_wcrt(cinfo->regbase, VGA_CRTC_START_HI, (base >> 8) & 0xff); + + /* 0xf2 is %11110010, exclude tmp bits */ + tmp = vga_rcrt(cinfo->regbase, CL_CRT1B) & 0xf2; + /* construct bits 16, 17 and 18 of screen start address */ + if (base & 0x10000) + tmp |= 0x01; + if (base & 0x20000) + tmp |= 0x04; + if (base & 0x40000) + tmp |= 0x08; + + vga_wcrt(cinfo->regbase, CL_CRT1B, tmp); + + /* construct bit 19 of screen start address */ + if (cirrusfb_board_info[cinfo->btype].scrn_start_bit19) { + tmp = vga_rcrt(cinfo->regbase, CL_CRT1D); + if (is_laguna(cinfo)) + tmp = (tmp & ~0x18) | ((base >> 16) & 0x18); + else + tmp = (tmp & ~0x80) | ((base >> 12) & 0x80); + vga_wcrt(cinfo->regbase, CL_CRT1D, tmp); + } + + /* write pixel panning value to AR33; this does not quite work in 8bpp + * + * ### Piccolo..? Will this work? + */ + if (info->var.bits_per_pixel == 1) + vga_wattr(cinfo->regbase, CL_AR33, xpix); + + return 0; +} + +static int cirrusfb_blank(int blank_mode, struct fb_info *info) +{ + /* + * Blank the screen if blank_mode != 0, else unblank. If blank == NULL + * then the caller blanks by setting the CLUT (Color Look Up Table) + * to all black. Return 0 if blanking succeeded, != 0 if un-/blanking + * failed due to e.g. a video mode which doesn't support it. + * Implements VESA suspend and powerdown modes on hardware that + * supports disabling hsync/vsync: + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ + unsigned char val; + struct cirrusfb_info *cinfo = info->par; + int current_mode = cinfo->blank_mode; + + dev_dbg(info->device, "ENTER, blank mode = %d\n", blank_mode); + + if (info->state != FBINFO_STATE_RUNNING || + current_mode == blank_mode) { + dev_dbg(info->device, "EXIT, returning 0\n"); + return 0; + } + + /* Undo current */ + if (current_mode == FB_BLANK_NORMAL || + current_mode == FB_BLANK_UNBLANK) + /* clear "FullBandwidth" bit */ + val = 0; + else + /* set "FullBandwidth" bit */ + val = 0x20; + + val |= vga_rseq(cinfo->regbase, VGA_SEQ_CLOCK_MODE) & 0xdf; + vga_wseq(cinfo->regbase, VGA_SEQ_CLOCK_MODE, val); + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + val = 0x00; + break; + case FB_BLANK_VSYNC_SUSPEND: + val = 0x04; + break; + case FB_BLANK_HSYNC_SUSPEND: + val = 0x02; + break; + case FB_BLANK_POWERDOWN: + val = 0x06; + break; + default: + dev_dbg(info->device, "EXIT, returning 1\n"); + return 1; + } + + vga_wgfx(cinfo->regbase, CL_GRE, val); + + cinfo->blank_mode = blank_mode; + dev_dbg(info->device, "EXIT, returning 0\n"); + + /* Let fbcon do a soft blank for us */ + return (blank_mode == FB_BLANK_NORMAL) ? 1 : 0; +} + +/**** END Hardware specific Routines **************************************/ +/****************************************************************************/ +/**** BEGIN Internal Routines ***********************************************/ + +static void init_vgachip(struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + const struct cirrusfb_board_info_rec *bi; + + assert(cinfo != NULL); + + bi = &cirrusfb_board_info[cinfo->btype]; + + /* reset board globally */ + switch (cinfo->btype) { + case BT_PICCOLO: + WSFR(cinfo, 0x01); + udelay(500); + WSFR(cinfo, 0x51); + udelay(500); + break; + case BT_PICASSO: + WSFR2(cinfo, 0xff); + udelay(500); + break; + case BT_SD64: + case BT_SPECTRUM: + WSFR(cinfo, 0x1f); + udelay(500); + WSFR(cinfo, 0x4f); + udelay(500); + break; + case BT_PICASSO4: + /* disable flickerfixer */ + vga_wcrt(cinfo->regbase, CL_CRT51, 0x00); + mdelay(100); + /* mode */ + vga_wgfx(cinfo->regbase, CL_GR31, 0x00); + case BT_GD5480: /* fall through */ + /* from Klaus' NetBSD driver: */ + vga_wgfx(cinfo->regbase, CL_GR2F, 0x00); + case BT_ALPINE: /* fall through */ + /* put blitter into 542x compat */ + vga_wgfx(cinfo->regbase, CL_GR33, 0x00); + break; + + case BT_LAGUNA: + case BT_LAGUNAB: + /* Nothing to do to reset the board. */ + break; + + default: + dev_err(info->device, "Warning: Unknown board type\n"); + break; + } + + /* make sure RAM size set by this point */ + assert(info->screen_size > 0); + + /* the P4 is not fully initialized here; I rely on it having been */ + /* inited under AmigaOS already, which seems to work just fine */ + /* (Klaus advised to do it this way) */ + + if (cinfo->btype != BT_PICASSO4) { + WGen(cinfo, CL_VSSM, 0x10); /* EGS: 0x16 */ + WGen(cinfo, CL_POS102, 0x01); + WGen(cinfo, CL_VSSM, 0x08); /* EGS: 0x0e */ + + if (cinfo->btype != BT_SD64) + WGen(cinfo, CL_VSSM2, 0x01); + + /* reset sequencer logic */ + vga_wseq(cinfo->regbase, VGA_SEQ_RESET, 0x03); + + /* FullBandwidth (video off) and 8/9 dot clock */ + vga_wseq(cinfo->regbase, VGA_SEQ_CLOCK_MODE, 0x21); + + /* "magic cookie" - doesn't make any sense to me.. */ +/* vga_wgfx(cinfo->regbase, CL_GRA, 0xce); */ + /* unlock all extension registers */ + vga_wseq(cinfo->regbase, CL_SEQR6, 0x12); + + switch (cinfo->btype) { + case BT_GD5480: + vga_wseq(cinfo->regbase, CL_SEQRF, 0x98); + break; + case BT_ALPINE: + case BT_LAGUNA: + case BT_LAGUNAB: + break; + case BT_SD64: +#ifdef CONFIG_ZORRO + vga_wseq(cinfo->regbase, CL_SEQRF, 0xb8); +#endif + break; + default: + vga_wseq(cinfo->regbase, CL_SEQR16, 0x0f); + vga_wseq(cinfo->regbase, CL_SEQRF, 0xb0); + break; + } + } + /* plane mask: nothing */ + vga_wseq(cinfo->regbase, VGA_SEQ_PLANE_WRITE, 0xff); + /* character map select: doesn't even matter in gx mode */ + vga_wseq(cinfo->regbase, VGA_SEQ_CHARACTER_MAP, 0x00); + /* memory mode: chain4, ext. memory */ + vga_wseq(cinfo->regbase, VGA_SEQ_MEMORY_MODE, 0x0a); + + /* controller-internal base address of video memory */ + if (bi->init_sr07) + vga_wseq(cinfo->regbase, CL_SEQR7, bi->sr07); + + /* vga_wseq(cinfo->regbase, CL_SEQR8, 0x00); */ + /* EEPROM control: shouldn't be necessary to write to this at all.. */ + + /* graphics cursor X position (incomplete; position gives rem. 3 bits */ + vga_wseq(cinfo->regbase, CL_SEQR10, 0x00); + /* graphics cursor Y position (..."... ) */ + vga_wseq(cinfo->regbase, CL_SEQR11, 0x00); + /* graphics cursor attributes */ + vga_wseq(cinfo->regbase, CL_SEQR12, 0x00); + /* graphics cursor pattern address */ + vga_wseq(cinfo->regbase, CL_SEQR13, 0x00); + + /* writing these on a P4 might give problems.. */ + if (cinfo->btype != BT_PICASSO4) { + /* configuration readback and ext. color */ + vga_wseq(cinfo->regbase, CL_SEQR17, 0x00); + /* signature generator */ + vga_wseq(cinfo->regbase, CL_SEQR18, 0x02); + } + + /* Screen A preset row scan: none */ + vga_wcrt(cinfo->regbase, VGA_CRTC_PRESET_ROW, 0x00); + /* Text cursor start: disable text cursor */ + vga_wcrt(cinfo->regbase, VGA_CRTC_CURSOR_START, 0x20); + /* Text cursor end: - */ + vga_wcrt(cinfo->regbase, VGA_CRTC_CURSOR_END, 0x00); + /* text cursor location high: 0 */ + vga_wcrt(cinfo->regbase, VGA_CRTC_CURSOR_HI, 0x00); + /* text cursor location low: 0 */ + vga_wcrt(cinfo->regbase, VGA_CRTC_CURSOR_LO, 0x00); + + /* Underline Row scanline: - */ + vga_wcrt(cinfo->regbase, VGA_CRTC_UNDERLINE, 0x00); + /* ### add 0x40 for text modes with > 30 MHz pixclock */ + /* ext. display controls: ext.adr. wrap */ + vga_wcrt(cinfo->regbase, CL_CRT1B, 0x02); + + /* Set/Reset registers: - */ + vga_wgfx(cinfo->regbase, VGA_GFX_SR_VALUE, 0x00); + /* Set/Reset enable: - */ + vga_wgfx(cinfo->regbase, VGA_GFX_SR_ENABLE, 0x00); + /* Color Compare: - */ + vga_wgfx(cinfo->regbase, VGA_GFX_COMPARE_VALUE, 0x00); + /* Data Rotate: - */ + vga_wgfx(cinfo->regbase, VGA_GFX_DATA_ROTATE, 0x00); + /* Read Map Select: - */ + vga_wgfx(cinfo->regbase, VGA_GFX_PLANE_READ, 0x00); + /* Mode: conf. for 16/4/2 color mode, no odd/even, read/write mode 0 */ + vga_wgfx(cinfo->regbase, VGA_GFX_MODE, 0x00); + /* Miscellaneous: memory map base address, graphics mode */ + vga_wgfx(cinfo->regbase, VGA_GFX_MISC, 0x01); + /* Color Don't care: involve all planes */ + vga_wgfx(cinfo->regbase, VGA_GFX_COMPARE_MASK, 0x0f); + /* Bit Mask: no mask at all */ + vga_wgfx(cinfo->regbase, VGA_GFX_BIT_MASK, 0xff); + + if (cinfo->btype == BT_ALPINE || cinfo->btype == BT_SD64 || + is_laguna(cinfo)) + /* (5434 can't have bit 3 set for bitblt) */ + vga_wgfx(cinfo->regbase, CL_GRB, 0x20); + else + /* Graphics controller mode extensions: finer granularity, + * 8byte data latches + */ + vga_wgfx(cinfo->regbase, CL_GRB, 0x28); + + vga_wgfx(cinfo->regbase, CL_GRC, 0xff); /* Color Key compare: - */ + vga_wgfx(cinfo->regbase, CL_GRD, 0x00); /* Color Key compare mask: - */ + vga_wgfx(cinfo->regbase, CL_GRE, 0x00); /* Miscellaneous control: - */ + /* Background color byte 1: - */ + /* vga_wgfx (cinfo->regbase, CL_GR10, 0x00); */ + /* vga_wgfx (cinfo->regbase, CL_GR11, 0x00); */ + + /* Attribute Controller palette registers: "identity mapping" */ + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE0, 0x00); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE1, 0x01); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE2, 0x02); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE3, 0x03); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE4, 0x04); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE5, 0x05); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE6, 0x06); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE7, 0x07); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE8, 0x08); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTE9, 0x09); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTEA, 0x0a); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTEB, 0x0b); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTEC, 0x0c); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTED, 0x0d); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTEE, 0x0e); + vga_wattr(cinfo->regbase, VGA_ATC_PALETTEF, 0x0f); + + /* Attribute Controller mode: graphics mode */ + vga_wattr(cinfo->regbase, VGA_ATC_MODE, 0x01); + /* Overscan color reg.: reg. 0 */ + vga_wattr(cinfo->regbase, VGA_ATC_OVERSCAN, 0x00); + /* Color Plane enable: Enable all 4 planes */ + vga_wattr(cinfo->regbase, VGA_ATC_PLANE_ENABLE, 0x0f); + /* Color Select: - */ + vga_wattr(cinfo->regbase, VGA_ATC_COLOR_PAGE, 0x00); + + WGen(cinfo, VGA_PEL_MSK, 0xff); /* Pixel mask: no mask */ + + /* BLT Start/status: Blitter reset */ + vga_wgfx(cinfo->regbase, CL_GR31, 0x04); + /* - " - : "end-of-reset" */ + vga_wgfx(cinfo->regbase, CL_GR31, 0x00); + + /* misc... */ + WHDR(cinfo, 0); /* Hidden DAC register: - */ + return; +} + +static void switch_monitor(struct cirrusfb_info *cinfo, int on) +{ +#ifdef CONFIG_ZORRO /* only works on Zorro boards */ + static int IsOn = 0; /* XXX not ok for multiple boards */ + + if (cinfo->btype == BT_PICASSO4) + return; /* nothing to switch */ + if (cinfo->btype == BT_ALPINE) + return; /* nothing to switch */ + if (cinfo->btype == BT_GD5480) + return; /* nothing to switch */ + if (cinfo->btype == BT_PICASSO) { + if ((on && !IsOn) || (!on && IsOn)) + WSFR(cinfo, 0xff); + return; + } + if (on) { + switch (cinfo->btype) { + case BT_SD64: + WSFR(cinfo, cinfo->SFR | 0x21); + break; + case BT_PICCOLO: + WSFR(cinfo, cinfo->SFR | 0x28); + break; + case BT_SPECTRUM: + WSFR(cinfo, 0x6f); + break; + default: /* do nothing */ break; + } + } else { + switch (cinfo->btype) { + case BT_SD64: + WSFR(cinfo, cinfo->SFR & 0xde); + break; + case BT_PICCOLO: + WSFR(cinfo, cinfo->SFR & 0xd7); + break; + case BT_SPECTRUM: + WSFR(cinfo, 0x4f); + break; + default: /* do nothing */ + break; + } + } +#endif /* CONFIG_ZORRO */ +} + +/******************************************/ +/* Linux 2.6-style accelerated functions */ +/******************************************/ + +static int cirrusfb_sync(struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + + if (!is_laguna(cinfo)) { + while (vga_rgfx(cinfo->regbase, CL_GR31) & 0x03) + cpu_relax(); + } + return 0; +} + +static void cirrusfb_fillrect(struct fb_info *info, + const struct fb_fillrect *region) +{ + struct fb_fillrect modded; + int vxres, vyres; + struct cirrusfb_info *cinfo = info->par; + int m = info->var.bits_per_pixel; + u32 color = (info->fix.visual == FB_VISUAL_TRUECOLOR) ? + cinfo->pseudo_palette[region->color] : region->color; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_fillrect(info, region); + return; + } + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + memcpy(&modded, region, sizeof(struct fb_fillrect)); + + if (!modded.width || !modded.height || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + cirrusfb_RectFill(cinfo->regbase, + info->var.bits_per_pixel, + (region->dx * m) / 8, region->dy, + (region->width * m) / 8, region->height, + color, color, + info->fix.line_length, 0x40); +} + +static void cirrusfb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct fb_copyarea modded; + u32 vxres, vyres; + struct cirrusfb_info *cinfo = info->par; + int m = info->var.bits_per_pixel; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_copyarea(info, area); + return; + } + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + memcpy(&modded, area, sizeof(struct fb_copyarea)); + + if (!modded.width || !modded.height || + modded.sx >= vxres || modded.sy >= vyres || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.sx + modded.width > vxres) + modded.width = vxres - modded.sx; + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.sy + modded.height > vyres) + modded.height = vyres - modded.sy; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + cirrusfb_BitBLT(cinfo->regbase, info->var.bits_per_pixel, + (area->sx * m) / 8, area->sy, + (area->dx * m) / 8, area->dy, + (area->width * m) / 8, area->height, + info->fix.line_length); + +} + +static void cirrusfb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct cirrusfb_info *cinfo = info->par; + unsigned char op = (info->var.bits_per_pixel == 24) ? 0xc : 0x4; + + if (info->state != FBINFO_STATE_RUNNING) + return; + /* Alpine/SD64 does not work at 24bpp ??? */ + if (info->flags & FBINFO_HWACCEL_DISABLED || image->depth != 1) + cfb_imageblit(info, image); + else if ((cinfo->btype == BT_ALPINE || cinfo->btype == BT_SD64) && + op == 0xc) + cfb_imageblit(info, image); + else { + unsigned size = ((image->width + 7) >> 3) * image->height; + int m = info->var.bits_per_pixel; + u32 fg, bg; + + if (info->var.bits_per_pixel == 8) { + fg = image->fg_color; + bg = image->bg_color; + } else { + fg = ((u32 *)(info->pseudo_palette))[image->fg_color]; + bg = ((u32 *)(info->pseudo_palette))[image->bg_color]; + } + if (info->var.bits_per_pixel == 24) { + /* clear background first */ + cirrusfb_RectFill(cinfo->regbase, + info->var.bits_per_pixel, + (image->dx * m) / 8, image->dy, + (image->width * m) / 8, + image->height, + bg, bg, + info->fix.line_length, 0x40); + } + cirrusfb_RectFill(cinfo->regbase, + info->var.bits_per_pixel, + (image->dx * m) / 8, image->dy, + (image->width * m) / 8, image->height, + fg, bg, + info->fix.line_length, op); + memcpy(info->screen_base, image->data, size); + } +} + +#ifdef CONFIG_PCI +static int release_io_ports; + +/* Pulled the logic from XFree86 Cirrus driver to get the memory size, + * based on the DRAM bandwidth bit and DRAM bank switching bit. This + * works with 1MB, 2MB and 4MB configurations (which the Motorola boards + * seem to have. */ +static unsigned int cirrusfb_get_memsize(struct fb_info *info, + u8 __iomem *regbase) +{ + unsigned long mem; + struct cirrusfb_info *cinfo = info->par; + + if (is_laguna(cinfo)) { + unsigned char SR14 = vga_rseq(regbase, CL_SEQR14); + + mem = ((SR14 & 7) + 1) << 20; + } else { + unsigned char SRF = vga_rseq(regbase, CL_SEQRF); + switch ((SRF & 0x18)) { + case 0x08: + mem = 512 * 1024; + break; + case 0x10: + mem = 1024 * 1024; + break; + /* 64-bit DRAM data bus width; assume 2MB. + * Also indicates 2MB memory on the 5430. + */ + case 0x18: + mem = 2048 * 1024; + break; + default: + dev_warn(info->device, "Unknown memory size!\n"); + mem = 1024 * 1024; + } + /* If DRAM bank switching is enabled, there must be + * twice as much memory installed. (4MB on the 5434) + */ + if (cinfo->btype != BT_ALPINE && (SRF & 0x80) != 0) + mem *= 2; + } + + /* TODO: Handling of GD5446/5480 (see XF86 sources ...) */ + return mem; +} + +static void get_pci_addrs(const struct pci_dev *pdev, + unsigned long *display, unsigned long *registers) +{ + assert(pdev != NULL); + assert(display != NULL); + assert(registers != NULL); + + *display = 0; + *registers = 0; + + /* This is a best-guess for now */ + + if (pci_resource_flags(pdev, 0) & IORESOURCE_IO) { + *display = pci_resource_start(pdev, 1); + *registers = pci_resource_start(pdev, 0); + } else { + *display = pci_resource_start(pdev, 0); + *registers = pci_resource_start(pdev, 1); + } + + assert(*display != 0); +} + +static void cirrusfb_pci_unmap(struct fb_info *info) +{ + struct pci_dev *pdev = to_pci_dev(info->device); + struct cirrusfb_info *cinfo = info->par; + + if (cinfo->laguna_mmio == NULL) + iounmap(cinfo->laguna_mmio); + iounmap(info->screen_base); +#if 0 /* if system didn't claim this region, we would... */ + release_mem_region(0xA0000, 65535); +#endif + if (release_io_ports) + release_region(0x3C0, 32); + pci_release_regions(pdev); +} +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_ZORRO +static void cirrusfb_zorro_unmap(struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + struct zorro_dev *zdev = to_zorro_dev(info->device); + + if (info->fix.smem_start > 16 * MB_) + iounmap(info->screen_base); + if (info->fix.mmio_start > 16 * MB_) + iounmap(cinfo->regbase); + + zorro_release_device(zdev); +} +#endif /* CONFIG_ZORRO */ + +/* function table of the above functions */ +static struct fb_ops cirrusfb_ops = { + .owner = THIS_MODULE, + .fb_open = cirrusfb_open, + .fb_release = cirrusfb_release, + .fb_setcolreg = cirrusfb_setcolreg, + .fb_check_var = cirrusfb_check_var, + .fb_set_par = cirrusfb_set_par, + .fb_pan_display = cirrusfb_pan_display, + .fb_blank = cirrusfb_blank, + .fb_fillrect = cirrusfb_fillrect, + .fb_copyarea = cirrusfb_copyarea, + .fb_sync = cirrusfb_sync, + .fb_imageblit = cirrusfb_imageblit, +}; + +static int cirrusfb_set_fbinfo(struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + struct fb_var_screeninfo *var = &info->var; + + info->pseudo_palette = cinfo->pseudo_palette; + info->flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_XPAN + | FBINFO_HWACCEL_YPAN + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_IMAGEBLIT + | FBINFO_HWACCEL_COPYAREA; + if (noaccel || is_laguna(cinfo)) { + info->flags |= FBINFO_HWACCEL_DISABLED; + info->fix.accel = FB_ACCEL_NONE; + } else + info->fix.accel = FB_ACCEL_CIRRUS_ALPINE; + + info->fbops = &cirrusfb_ops; + + if (cinfo->btype == BT_GD5480) { + if (var->bits_per_pixel == 16) + info->screen_base += 1 * MB_; + if (var->bits_per_pixel == 32) + info->screen_base += 2 * MB_; + } + + /* Fill fix common fields */ + strlcpy(info->fix.id, cirrusfb_board_info[cinfo->btype].name, + sizeof(info->fix.id)); + + /* monochrome: only 1 memory plane */ + /* 8 bit and above: Use whole memory area */ + info->fix.smem_len = info->screen_size; + if (var->bits_per_pixel == 1) + info->fix.smem_len /= 4; + info->fix.type_aux = 0; + info->fix.xpanstep = 1; + info->fix.ypanstep = 1; + info->fix.ywrapstep = 0; + + /* FIXME: map region at 0xB8000 if available, fill in here */ + info->fix.mmio_len = 0; + + fb_alloc_cmap(&info->cmap, 256, 0); + + return 0; +} + +static int cirrusfb_register(struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + int err; + + /* sanity checks */ + assert(cinfo->btype != BT_NONE); + + /* set all the vital stuff */ + cirrusfb_set_fbinfo(info); + + dev_dbg(info->device, "(RAM start set to: 0x%p)\n", info->screen_base); + + err = fb_find_mode(&info->var, info, mode_option, NULL, 0, NULL, 8); + if (!err) { + dev_dbg(info->device, "wrong initial video mode\n"); + err = -EINVAL; + goto err_dealloc_cmap; + } + + info->var.activate = FB_ACTIVATE_NOW; + + err = cirrusfb_check_var(&info->var, info); + if (err < 0) { + /* should never happen */ + dev_dbg(info->device, + "choking on default var... umm, no good.\n"); + goto err_dealloc_cmap; + } + + err = register_framebuffer(info); + if (err < 0) { + dev_err(info->device, + "could not register fb device; err = %d!\n", err); + goto err_dealloc_cmap; + } + + return 0; + +err_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + return err; +} + +static void cirrusfb_cleanup(struct fb_info *info) +{ + struct cirrusfb_info *cinfo = info->par; + + switch_monitor(cinfo, 0); + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + dev_dbg(info->device, "Framebuffer unregistered\n"); + cinfo->unmap(info); + framebuffer_release(info); +} + +#ifdef CONFIG_PCI +static int cirrusfb_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct cirrusfb_info *cinfo; + struct fb_info *info; + unsigned long board_addr, board_size; + int ret; + + ret = pci_enable_device(pdev); + if (ret < 0) { + printk(KERN_ERR "cirrusfb: Cannot enable PCI device\n"); + goto err_out; + } + + info = framebuffer_alloc(sizeof(struct cirrusfb_info), &pdev->dev); + if (!info) { + printk(KERN_ERR "cirrusfb: could not allocate memory\n"); + ret = -ENOMEM; + goto err_out; + } + + cinfo = info->par; + cinfo->btype = (enum cirrus_board) ent->driver_data; + + dev_dbg(info->device, + " Found PCI device, base address 0 is 0x%Lx, btype set to %d\n", + (unsigned long long)pdev->resource[0].start, cinfo->btype); + dev_dbg(info->device, " base address 1 is 0x%Lx\n", + (unsigned long long)pdev->resource[1].start); + + dev_dbg(info->device, + "Attempt to get PCI info for Cirrus Graphics Card\n"); + get_pci_addrs(pdev, &board_addr, &info->fix.mmio_start); + /* FIXME: this forces VGA. alternatives? */ + cinfo->regbase = NULL; + cinfo->laguna_mmio = ioremap(info->fix.mmio_start, 0x1000); + + dev_dbg(info->device, "Board address: 0x%lx, register address: 0x%lx\n", + board_addr, info->fix.mmio_start); + + board_size = (cinfo->btype == BT_GD5480) ? + 32 * MB_ : cirrusfb_get_memsize(info, cinfo->regbase); + + ret = pci_request_regions(pdev, "cirrusfb"); + if (ret < 0) { + dev_err(info->device, "cannot reserve region 0x%lx, abort\n", + board_addr); + goto err_release_fb; + } +#if 0 /* if the system didn't claim this region, we would... */ + if (!request_mem_region(0xA0000, 65535, "cirrusfb")) { + dev_err(info->device, "cannot reserve region 0x%lx, abort\n", + 0xA0000L); + ret = -EBUSY; + goto err_release_regions; + } +#endif + if (request_region(0x3C0, 32, "cirrusfb")) + release_io_ports = 1; + + info->screen_base = ioremap(board_addr, board_size); + if (!info->screen_base) { + ret = -EIO; + goto err_release_legacy; + } + + info->fix.smem_start = board_addr; + info->screen_size = board_size; + cinfo->unmap = cirrusfb_pci_unmap; + + dev_info(info->device, + "Cirrus Logic chipset on PCI bus, RAM (%lu kB) at 0x%lx\n", + info->screen_size >> 10, board_addr); + pci_set_drvdata(pdev, info); + + ret = cirrusfb_register(info); + if (!ret) + return 0; + + iounmap(info->screen_base); +err_release_legacy: + if (release_io_ports) + release_region(0x3C0, 32); +#if 0 + release_mem_region(0xA0000, 65535); +err_release_regions: +#endif + pci_release_regions(pdev); +err_release_fb: + if (cinfo->laguna_mmio != NULL) + iounmap(cinfo->laguna_mmio); + framebuffer_release(info); +err_out: + return ret; +} + +static void cirrusfb_pci_unregister(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + + cirrusfb_cleanup(info); +} + +static struct pci_driver cirrusfb_pci_driver = { + .name = "cirrusfb", + .id_table = cirrusfb_pci_table, + .probe = cirrusfb_pci_register, + .remove = cirrusfb_pci_unregister, +#ifdef CONFIG_PM +#if 0 + .suspend = cirrusfb_pci_suspend, + .resume = cirrusfb_pci_resume, +#endif +#endif +}; +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_ZORRO +static int cirrusfb_zorro_register(struct zorro_dev *z, + const struct zorro_device_id *ent) +{ + struct fb_info *info; + int error; + const struct zorrocl *zcl; + enum cirrus_board btype; + unsigned long regbase, ramsize, rambase; + struct cirrusfb_info *cinfo; + + info = framebuffer_alloc(sizeof(struct cirrusfb_info), &z->dev); + if (!info) { + printk(KERN_ERR "cirrusfb: could not allocate memory\n"); + return -ENOMEM; + } + + zcl = (const struct zorrocl *)ent->driver_data; + btype = zcl->type; + regbase = zorro_resource_start(z) + zcl->regoffset; + ramsize = zcl->ramsize; + if (ramsize) { + rambase = zorro_resource_start(z) + zcl->ramoffset; + if (zorro_resource_len(z) == 64 * MB_) { + /* Quirk for 64 MiB Picasso IV */ + rambase += zcl->ramoffset; + } + } else { + struct zorro_dev *ram = zorro_find_device(zcl->ramid, NULL); + if (!ram || !zorro_resource_len(ram)) { + dev_err(info->device, "No video RAM found\n"); + error = -ENODEV; + goto err_release_fb; + } + rambase = zorro_resource_start(ram); + ramsize = zorro_resource_len(ram); + if (zcl->ramid2 && + (ram = zorro_find_device(zcl->ramid2, NULL))) { + if (zorro_resource_start(ram) != rambase + ramsize) { + dev_warn(info->device, + "Skipping non-contiguous RAM at %pR\n", + &ram->resource); + } else { + ramsize += zorro_resource_len(ram); + } + } + } + + dev_info(info->device, + "%s board detected, REG at 0x%lx, %lu MiB RAM at 0x%lx\n", + cirrusfb_board_info[btype].name, regbase, ramsize / MB_, + rambase); + + if (!zorro_request_device(z, "cirrusfb")) { + dev_err(info->device, "Cannot reserve %pR\n", &z->resource); + error = -EBUSY; + goto err_release_fb; + } + + cinfo = info->par; + cinfo->btype = btype; + + info->fix.mmio_start = regbase; + cinfo->regbase = regbase > 16 * MB_ ? ioremap(regbase, 64 * 1024) + : ZTWO_VADDR(regbase); + if (!cinfo->regbase) { + dev_err(info->device, "Cannot map registers\n"); + error = -EIO; + goto err_release_dev; + } + + info->fix.smem_start = rambase; + info->screen_size = ramsize; + info->screen_base = rambase > 16 * MB_ ? ioremap(rambase, ramsize) + : ZTWO_VADDR(rambase); + if (!info->screen_base) { + dev_err(info->device, "Cannot map video RAM\n"); + error = -EIO; + goto err_unmap_reg; + } + + cinfo->unmap = cirrusfb_zorro_unmap; + + dev_info(info->device, + "Cirrus Logic chipset on Zorro bus, RAM (%lu MiB) at 0x%lx\n", + ramsize / MB_, rambase); + + /* MCLK select etc. */ + if (cirrusfb_board_info[btype].init_sr1f) + vga_wseq(cinfo->regbase, CL_SEQR1F, + cirrusfb_board_info[btype].sr1f); + + error = cirrusfb_register(info); + if (error) { + dev_err(info->device, "Failed to register device, error %d\n", + error); + goto err_unmap_ram; + } + + zorro_set_drvdata(z, info); + return 0; + +err_unmap_ram: + if (rambase > 16 * MB_) + iounmap(info->screen_base); + +err_unmap_reg: + if (regbase > 16 * MB_) + iounmap(cinfo->regbase); +err_release_dev: + zorro_release_device(z); +err_release_fb: + framebuffer_release(info); + return error; +} + +void cirrusfb_zorro_unregister(struct zorro_dev *z) +{ + struct fb_info *info = zorro_get_drvdata(z); + + cirrusfb_cleanup(info); + zorro_set_drvdata(z, NULL); +} + +static struct zorro_driver cirrusfb_zorro_driver = { + .name = "cirrusfb", + .id_table = cirrusfb_zorro_table, + .probe = cirrusfb_zorro_register, + .remove = cirrusfb_zorro_unregister, +}; +#endif /* CONFIG_ZORRO */ + +#ifndef MODULE +static int __init cirrusfb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + + if (!strcmp(this_opt, "noaccel")) + noaccel = 1; + else if (!strncmp(this_opt, "mode:", 5)) + mode_option = this_opt + 5; + else + mode_option = this_opt; + } + return 0; +} +#endif + + /* + * Modularization + */ + +MODULE_AUTHOR("Copyright 1999,2000 Jeff Garzik <jgarzik@pobox.com>"); +MODULE_DESCRIPTION("Accelerated FBDev driver for Cirrus Logic chips"); +MODULE_LICENSE("GPL"); + +static int __init cirrusfb_init(void) +{ + int error = 0; + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("cirrusfb", &option)) + return -ENODEV; + cirrusfb_setup(option); +#endif + +#ifdef CONFIG_ZORRO + error |= zorro_register_driver(&cirrusfb_zorro_driver); +#endif +#ifdef CONFIG_PCI + error |= pci_register_driver(&cirrusfb_pci_driver); +#endif + return error; +} + +static void __exit cirrusfb_exit(void) +{ +#ifdef CONFIG_PCI + pci_unregister_driver(&cirrusfb_pci_driver); +#endif +#ifdef CONFIG_ZORRO + zorro_unregister_driver(&cirrusfb_zorro_driver); +#endif +} + +module_init(cirrusfb_init); + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Initial video mode e.g. '648x480-8@60'"); +module_param(noaccel, bool, 0); +MODULE_PARM_DESC(noaccel, "Disable acceleration"); + +#ifdef MODULE +module_exit(cirrusfb_exit); +#endif + +/**********************************************************************/ +/* about the following functions - I have used the same names for the */ +/* functions as Markus Wild did in his Retina driver for NetBSD as */ +/* they just made sense for this purpose. Apart from that, I wrote */ +/* these functions myself. */ +/**********************************************************************/ + +/*** WGen() - write into one of the external/general registers ***/ +static void WGen(const struct cirrusfb_info *cinfo, + int regnum, unsigned char val) +{ + unsigned long regofs = 0; + + if (cinfo->btype == BT_PICASSO) { + /* Picasso II specific hack */ +/* if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D || + regnum == CL_VSSM2) */ + if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D) + regofs = 0xfff; + } + + vga_w(cinfo->regbase, regofs + regnum, val); +} + +/*** RGen() - read out one of the external/general registers ***/ +static unsigned char RGen(const struct cirrusfb_info *cinfo, int regnum) +{ + unsigned long regofs = 0; + + if (cinfo->btype == BT_PICASSO) { + /* Picasso II specific hack */ +/* if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D || + regnum == CL_VSSM2) */ + if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D) + regofs = 0xfff; + } + + return vga_r(cinfo->regbase, regofs + regnum); +} + +/*** AttrOn() - turn on VideoEnable for Attribute controller ***/ +static void AttrOn(const struct cirrusfb_info *cinfo) +{ + assert(cinfo != NULL); + + if (vga_rcrt(cinfo->regbase, CL_CRT24) & 0x80) { + /* if we're just in "write value" mode, write back the */ + /* same value as before to not modify anything */ + vga_w(cinfo->regbase, VGA_ATT_IW, + vga_r(cinfo->regbase, VGA_ATT_R)); + } + /* turn on video bit */ +/* vga_w(cinfo->regbase, VGA_ATT_IW, 0x20); */ + vga_w(cinfo->regbase, VGA_ATT_IW, 0x33); + + /* dummy write on Reg0 to be on "write index" mode next time */ + vga_w(cinfo->regbase, VGA_ATT_IW, 0x00); +} + +/*** WHDR() - write into the Hidden DAC register ***/ +/* as the HDR is the only extension register that requires special treatment + * (the other extension registers are accessible just like the "ordinary" + * registers of their functional group) here is a specialized routine for + * accessing the HDR + */ +static void WHDR(const struct cirrusfb_info *cinfo, unsigned char val) +{ + unsigned char dummy; + + if (is_laguna(cinfo)) + return; + if (cinfo->btype == BT_PICASSO) { + /* Klaus' hint for correct access to HDR on some boards */ + /* first write 0 to pixel mask (3c6) */ + WGen(cinfo, VGA_PEL_MSK, 0x00); + udelay(200); + /* next read dummy from pixel address (3c8) */ + dummy = RGen(cinfo, VGA_PEL_IW); + udelay(200); + } + /* now do the usual stuff to access the HDR */ + + dummy = RGen(cinfo, VGA_PEL_MSK); + udelay(200); + dummy = RGen(cinfo, VGA_PEL_MSK); + udelay(200); + dummy = RGen(cinfo, VGA_PEL_MSK); + udelay(200); + dummy = RGen(cinfo, VGA_PEL_MSK); + udelay(200); + + WGen(cinfo, VGA_PEL_MSK, val); + udelay(200); + + if (cinfo->btype == BT_PICASSO) { + /* now first reset HDR access counter */ + dummy = RGen(cinfo, VGA_PEL_IW); + udelay(200); + + /* and at the end, restore the mask value */ + /* ## is this mask always 0xff? */ + WGen(cinfo, VGA_PEL_MSK, 0xff); + udelay(200); + } +} + +/*** WSFR() - write to the "special function register" (SFR) ***/ +static void WSFR(struct cirrusfb_info *cinfo, unsigned char val) +{ +#ifdef CONFIG_ZORRO + assert(cinfo->regbase != NULL); + cinfo->SFR = val; + z_writeb(val, cinfo->regbase + 0x8000); +#endif +} + +/* The Picasso has a second register for switching the monitor bit */ +static void WSFR2(struct cirrusfb_info *cinfo, unsigned char val) +{ +#ifdef CONFIG_ZORRO + /* writing an arbitrary value to this one causes the monitor switcher */ + /* to flip to Amiga display */ + assert(cinfo->regbase != NULL); + cinfo->SFR = val; + z_writeb(val, cinfo->regbase + 0x9000); +#endif +} + +/*** WClut - set CLUT entry (range: 0..63) ***/ +static void WClut(struct cirrusfb_info *cinfo, unsigned char regnum, unsigned char red, + unsigned char green, unsigned char blue) +{ + unsigned int data = VGA_PEL_D; + + /* address write mode register is not translated.. */ + vga_w(cinfo->regbase, VGA_PEL_IW, regnum); + + if (cinfo->btype == BT_PICASSO || cinfo->btype == BT_PICASSO4 || + cinfo->btype == BT_ALPINE || cinfo->btype == BT_GD5480 || + cinfo->btype == BT_SD64 || is_laguna(cinfo)) { + /* but DAC data register IS, at least for Picasso II */ + if (cinfo->btype == BT_PICASSO) + data += 0xfff; + vga_w(cinfo->regbase, data, red); + vga_w(cinfo->regbase, data, green); + vga_w(cinfo->regbase, data, blue); + } else { + vga_w(cinfo->regbase, data, blue); + vga_w(cinfo->regbase, data, green); + vga_w(cinfo->regbase, data, red); + } +} + +#if 0 +/*** RClut - read CLUT entry (range 0..63) ***/ +static void RClut(struct cirrusfb_info *cinfo, unsigned char regnum, unsigned char *red, + unsigned char *green, unsigned char *blue) +{ + unsigned int data = VGA_PEL_D; + + vga_w(cinfo->regbase, VGA_PEL_IR, regnum); + + if (cinfo->btype == BT_PICASSO || cinfo->btype == BT_PICASSO4 || + cinfo->btype == BT_ALPINE || cinfo->btype == BT_GD5480) { + if (cinfo->btype == BT_PICASSO) + data += 0xfff; + *red = vga_r(cinfo->regbase, data); + *green = vga_r(cinfo->regbase, data); + *blue = vga_r(cinfo->regbase, data); + } else { + *blue = vga_r(cinfo->regbase, data); + *green = vga_r(cinfo->regbase, data); + *red = vga_r(cinfo->regbase, data); + } +} +#endif + +/******************************************************************* + cirrusfb_WaitBLT() + + Wait for the BitBLT engine to complete a possible earlier job +*********************************************************************/ + +/* FIXME: use interrupts instead */ +static void cirrusfb_WaitBLT(u8 __iomem *regbase) +{ + while (vga_rgfx(regbase, CL_GR31) & 0x08) + cpu_relax(); +} + +/******************************************************************* + cirrusfb_BitBLT() + + perform accelerated "scrolling" +********************************************************************/ + +static void cirrusfb_set_blitter(u8 __iomem *regbase, + u_short nwidth, u_short nheight, + u_long nsrc, u_long ndest, + u_short bltmode, u_short line_length) + +{ + /* pitch: set to line_length */ + /* dest pitch low */ + vga_wgfx(regbase, CL_GR24, line_length & 0xff); + /* dest pitch hi */ + vga_wgfx(regbase, CL_GR25, line_length >> 8); + /* source pitch low */ + vga_wgfx(regbase, CL_GR26, line_length & 0xff); + /* source pitch hi */ + vga_wgfx(regbase, CL_GR27, line_length >> 8); + + /* BLT width: actual number of pixels - 1 */ + /* BLT width low */ + vga_wgfx(regbase, CL_GR20, nwidth & 0xff); + /* BLT width hi */ + vga_wgfx(regbase, CL_GR21, nwidth >> 8); + + /* BLT height: actual number of lines -1 */ + /* BLT height low */ + vga_wgfx(regbase, CL_GR22, nheight & 0xff); + /* BLT width hi */ + vga_wgfx(regbase, CL_GR23, nheight >> 8); + + /* BLT destination */ + /* BLT dest low */ + vga_wgfx(regbase, CL_GR28, (u_char) (ndest & 0xff)); + /* BLT dest mid */ + vga_wgfx(regbase, CL_GR29, (u_char) (ndest >> 8)); + /* BLT dest hi */ + vga_wgfx(regbase, CL_GR2A, (u_char) (ndest >> 16)); + + /* BLT source */ + /* BLT src low */ + vga_wgfx(regbase, CL_GR2C, (u_char) (nsrc & 0xff)); + /* BLT src mid */ + vga_wgfx(regbase, CL_GR2D, (u_char) (nsrc >> 8)); + /* BLT src hi */ + vga_wgfx(regbase, CL_GR2E, (u_char) (nsrc >> 16)); + + /* BLT mode */ + vga_wgfx(regbase, CL_GR30, bltmode); /* BLT mode */ + + /* BLT ROP: SrcCopy */ + vga_wgfx(regbase, CL_GR32, 0x0d); /* BLT ROP */ + + /* and finally: GO! */ + vga_wgfx(regbase, CL_GR31, 0x02); /* BLT Start/status */ +} + +/******************************************************************* + cirrusfb_BitBLT() + + perform accelerated "scrolling" +********************************************************************/ + +static void cirrusfb_BitBLT(u8 __iomem *regbase, int bits_per_pixel, + u_short curx, u_short cury, + u_short destx, u_short desty, + u_short width, u_short height, + u_short line_length) +{ + u_short nwidth = width - 1; + u_short nheight = height - 1; + u_long nsrc, ndest; + u_char bltmode; + + bltmode = 0x00; + /* if source adr < dest addr, do the Blt backwards */ + if (cury <= desty) { + if (cury == desty) { + /* if src and dest are on the same line, check x */ + if (curx < destx) + bltmode |= 0x01; + } else + bltmode |= 0x01; + } + /* standard case: forward blitting */ + nsrc = (cury * line_length) + curx; + ndest = (desty * line_length) + destx; + if (bltmode) { + /* this means start addresses are at the end, + * counting backwards + */ + nsrc += nheight * line_length + nwidth; + ndest += nheight * line_length + nwidth; + } + + cirrusfb_WaitBLT(regbase); + + cirrusfb_set_blitter(regbase, nwidth, nheight, + nsrc, ndest, bltmode, line_length); +} + +/******************************************************************* + cirrusfb_RectFill() + + perform accelerated rectangle fill +********************************************************************/ + +static void cirrusfb_RectFill(u8 __iomem *regbase, int bits_per_pixel, + u_short x, u_short y, u_short width, u_short height, + u32 fg_color, u32 bg_color, u_short line_length, + u_char blitmode) +{ + u_long ndest = (y * line_length) + x; + u_char op; + + cirrusfb_WaitBLT(regbase); + + /* This is a ColorExpand Blt, using the */ + /* same color for foreground and background */ + vga_wgfx(regbase, VGA_GFX_SR_VALUE, bg_color); + vga_wgfx(regbase, VGA_GFX_SR_ENABLE, fg_color); + + op = 0x80; + if (bits_per_pixel >= 16) { + vga_wgfx(regbase, CL_GR10, bg_color >> 8); + vga_wgfx(regbase, CL_GR11, fg_color >> 8); + op = 0x90; + } + if (bits_per_pixel >= 24) { + vga_wgfx(regbase, CL_GR12, bg_color >> 16); + vga_wgfx(regbase, CL_GR13, fg_color >> 16); + op = 0xa0; + } + if (bits_per_pixel == 32) { + vga_wgfx(regbase, CL_GR14, bg_color >> 24); + vga_wgfx(regbase, CL_GR15, fg_color >> 24); + op = 0xb0; + } + cirrusfb_set_blitter(regbase, width - 1, height - 1, + 0, ndest, op | blitmode, line_length); +} + +/************************************************************************** + * bestclock() - determine closest possible clock lower(?) than the + * desired pixel clock + **************************************************************************/ +static void bestclock(long freq, int *nom, int *den, int *div) +{ + int n, d; + long h, diff; + + assert(nom != NULL); + assert(den != NULL); + assert(div != NULL); + + *nom = 0; + *den = 0; + *div = 0; + + if (freq < 8000) + freq = 8000; + + diff = freq; + + for (n = 32; n < 128; n++) { + int s = 0; + + d = (14318 * n) / freq; + if ((d >= 7) && (d <= 63)) { + int temp = d; + + if (temp > 31) { + s = 1; + temp >>= 1; + } + h = ((14318 * n) / temp) >> s; + h = h > freq ? h - freq : freq - h; + if (h < diff) { + diff = h; + *nom = n; + *den = temp; + *div = s; + } + } + d++; + if ((d >= 7) && (d <= 63)) { + if (d > 31) { + s = 1; + d >>= 1; + } + h = ((14318 * n) / d) >> s; + h = h > freq ? h - freq : freq - h; + if (h < diff) { + diff = h; + *nom = n; + *den = d; + *div = s; + } + } + } +} + +/* ------------------------------------------------------------------------- + * + * debugging functions + * + * ------------------------------------------------------------------------- + */ + +#ifdef CIRRUSFB_DEBUG + +/** + * cirrusfb_dbg_print_regs + * @base: If using newmmio, the newmmio base address, otherwise %NULL + * @reg_class: type of registers to read: %CRT, or %SEQ + * + * DESCRIPTION: + * Dumps the given list of VGA CRTC registers. If @base is %NULL, + * old-style I/O ports are queried for information, otherwise MMIO is + * used at the given @base address to query the information. + */ + +static void cirrusfb_dbg_print_regs(struct fb_info *info, + caddr_t regbase, + enum cirrusfb_dbg_reg_class reg_class, ...) +{ + va_list list; + unsigned char val = 0; + unsigned reg; + char *name; + + va_start(list, reg_class); + + name = va_arg(list, char *); + while (name != NULL) { + reg = va_arg(list, int); + + switch (reg_class) { + case CRT: + val = vga_rcrt(regbase, (unsigned char) reg); + break; + case SEQ: + val = vga_rseq(regbase, (unsigned char) reg); + break; + default: + /* should never occur */ + assert(false); + break; + } + + dev_dbg(info->device, "%8s = 0x%02X\n", name, val); + + name = va_arg(list, char *); + } + + va_end(list); +} + +/** + * cirrusfb_dbg_reg_dump + * @base: If using newmmio, the newmmio base address, otherwise %NULL + * + * DESCRIPTION: + * Dumps a list of interesting VGA and CIRRUSFB registers. If @base is %NULL, + * old-style I/O ports are queried for information, otherwise MMIO is + * used at the given @base address to query the information. + */ + +static void cirrusfb_dbg_reg_dump(struct fb_info *info, caddr_t regbase) +{ + dev_dbg(info->device, "VGA CRTC register dump:\n"); + + cirrusfb_dbg_print_regs(info, regbase, CRT, + "CR00", 0x00, + "CR01", 0x01, + "CR02", 0x02, + "CR03", 0x03, + "CR04", 0x04, + "CR05", 0x05, + "CR06", 0x06, + "CR07", 0x07, + "CR08", 0x08, + "CR09", 0x09, + "CR0A", 0x0A, + "CR0B", 0x0B, + "CR0C", 0x0C, + "CR0D", 0x0D, + "CR0E", 0x0E, + "CR0F", 0x0F, + "CR10", 0x10, + "CR11", 0x11, + "CR12", 0x12, + "CR13", 0x13, + "CR14", 0x14, + "CR15", 0x15, + "CR16", 0x16, + "CR17", 0x17, + "CR18", 0x18, + "CR22", 0x22, + "CR24", 0x24, + "CR26", 0x26, + "CR2D", 0x2D, + "CR2E", 0x2E, + "CR2F", 0x2F, + "CR30", 0x30, + "CR31", 0x31, + "CR32", 0x32, + "CR33", 0x33, + "CR34", 0x34, + "CR35", 0x35, + "CR36", 0x36, + "CR37", 0x37, + "CR38", 0x38, + "CR39", 0x39, + "CR3A", 0x3A, + "CR3B", 0x3B, + "CR3C", 0x3C, + "CR3D", 0x3D, + "CR3E", 0x3E, + "CR3F", 0x3F, + NULL); + + dev_dbg(info->device, "\n"); + + dev_dbg(info->device, "VGA SEQ register dump:\n"); + + cirrusfb_dbg_print_regs(info, regbase, SEQ, + "SR00", 0x00, + "SR01", 0x01, + "SR02", 0x02, + "SR03", 0x03, + "SR04", 0x04, + "SR08", 0x08, + "SR09", 0x09, + "SR0A", 0x0A, + "SR0B", 0x0B, + "SR0D", 0x0D, + "SR10", 0x10, + "SR11", 0x11, + "SR12", 0x12, + "SR13", 0x13, + "SR14", 0x14, + "SR15", 0x15, + "SR16", 0x16, + "SR17", 0x17, + "SR18", 0x18, + "SR19", 0x19, + "SR1A", 0x1A, + "SR1B", 0x1B, + "SR1C", 0x1C, + "SR1D", 0x1D, + "SR1E", 0x1E, + "SR1F", 0x1F, + NULL); + + dev_dbg(info->device, "\n"); +} + +#endif /* CIRRUSFB_DEBUG */ + diff --git a/drivers/video/fbdev/clps711xfb.c b/drivers/video/fbdev/clps711xfb.c new file mode 100644 index 000000000000..f00980607b8f --- /dev/null +++ b/drivers/video/fbdev/clps711xfb.c @@ -0,0 +1,315 @@ +/* + * linux/drivers/video/clps711xfb.c + * + * Copyright (C) 2000-2001 Deep Blue Solutions Ltd. + * + * 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 + * + * Framebuffer driver for the CLPS7111 and EP7212 processors. + */ +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include <mach/hardware.h> +#include <asm/mach-types.h> +#include <linux/uaccess.h> + +struct fb_info *cfb; + +#define CMAP_MAX_SIZE 16 + +/* + * LCD AC Prescale. This comes from the LCD panel manufacturers specifications. + * This determines how many clocks + 1 of CL1 before the M signal toggles. + * The number of lines on the display must not be divisible by this number. + */ +static unsigned int lcd_ac_prescale = 13; + +/* + * Set a single color register. Return != 0 for invalid regno. + */ +static int +clps7111fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + unsigned int level, mask, shift, pal; + + if (regno >= (1 << info->var.bits_per_pixel)) + return 1; + + /* gray = 0.30*R + 0.58*G + 0.11*B */ + level = (red * 77 + green * 151 + blue * 28) >> 20; + + /* + * On an LCD, a high value is dark, while a low value is light. + * So we invert the level. + * + * This isn't true on all machines, so we only do it on EDB7211. + * --rmk + */ + if (machine_is_edb7211()) { + level = 15 - level; + } + + shift = 4 * (regno & 7); + level <<= shift; + mask = 15 << shift; + level &= mask; + + regno = regno < 8 ? PALLSW : PALMSW; + + pal = clps_readl(regno); + pal = (pal & ~mask) | level; + clps_writel(pal, regno); + + return 0; +} + +/* + * Validate the purposed mode. + */ +static int +clps7111fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + var->transp.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + var->red.msb_right = 0; + var->red.offset = 0; + var->red.length = var->bits_per_pixel; + var->green = var->red; + var->blue = var->red; + + if (var->bits_per_pixel > 4) + return -EINVAL; + + return 0; +} + +/* + * Set the hardware state. + */ +static int +clps7111fb_set_par(struct fb_info *info) +{ + unsigned int lcdcon, syscon, pixclock; + + switch (info->var.bits_per_pixel) { + case 1: + info->fix.visual = FB_VISUAL_MONO01; + break; + case 2: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + case 4: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + + info->fix.line_length = info->var.xres_virtual * info->var.bits_per_pixel / 8; + + lcdcon = (info->var.xres_virtual * info->var.yres_virtual * info->var.bits_per_pixel) / 128 - 1; + lcdcon |= ((info->var.xres_virtual / 16) - 1) << 13; + lcdcon |= lcd_ac_prescale << 25; + + /* + * Calculate pixel prescale value from the pixclock. This is: + * 36.864MHz / pixclock_mhz - 1. + * However, pixclock is in picoseconds, so this ends up being: + * 36864000 * pixclock_ps / 10^12 - 1 + * and this will overflow the 32-bit math. We perform this as + * (9 * 4096000 == 36864000): + * pixclock_ps * 9 * (4096000 / 10^12) - 1 + */ + pixclock = 9 * info->var.pixclock / 244140 - 1; + lcdcon |= pixclock << 19; + + if (info->var.bits_per_pixel == 4) + lcdcon |= LCDCON_GSMD; + if (info->var.bits_per_pixel >= 2) + lcdcon |= LCDCON_GSEN; + + /* + * LCDCON must only be changed while the LCD is disabled + */ + syscon = clps_readl(SYSCON1); + clps_writel(syscon & ~SYSCON1_LCDEN, SYSCON1); + clps_writel(lcdcon, LCDCON); + clps_writel(syscon | SYSCON1_LCDEN, SYSCON1); + return 0; +} + +static int clps7111fb_blank(int blank, struct fb_info *info) +{ + /* Enable/Disable LCD controller. */ + if (blank) + clps_writel(clps_readl(SYSCON1) & ~SYSCON1_LCDEN, SYSCON1); + else + clps_writel(clps_readl(SYSCON1) | SYSCON1_LCDEN, SYSCON1); + + return 0; +} + +static struct fb_ops clps7111fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = clps7111fb_check_var, + .fb_set_par = clps7111fb_set_par, + .fb_setcolreg = clps7111fb_setcolreg, + .fb_blank = clps7111fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static void clps711x_guess_lcd_params(struct fb_info *info) +{ + unsigned int lcdcon, syscon, size; + unsigned long phys_base = PAGE_OFFSET; + void *virt_base = (void *)PAGE_OFFSET; + + info->var.xres_virtual = 640; + info->var.yres_virtual = 240; + info->var.bits_per_pixel = 4; + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.pixclock = 93006; /* 10.752MHz pixel clock */ + + /* + * If the LCD controller is already running, decode the values + * in LCDCON to xres/yres/bpp/pixclock/acprescale + */ + syscon = clps_readl(SYSCON1); + if (syscon & SYSCON1_LCDEN) { + lcdcon = clps_readl(LCDCON); + + /* + * Decode GSMD and GSEN bits to bits per pixel + */ + switch (lcdcon & (LCDCON_GSMD | LCDCON_GSEN)) { + case LCDCON_GSMD | LCDCON_GSEN: + info->var.bits_per_pixel = 4; + break; + + case LCDCON_GSEN: + info->var.bits_per_pixel = 2; + break; + + default: + info->var.bits_per_pixel = 1; + break; + } + + /* + * Decode xres/yres + */ + info->var.xres_virtual = (((lcdcon >> 13) & 0x3f) + 1) * 16; + info->var.yres_virtual = (((lcdcon & 0x1fff) + 1) * 128) / + (info->var.xres_virtual * + info->var.bits_per_pixel); + + /* + * Calculate pixclock + */ + info->var.pixclock = (((lcdcon >> 19) & 0x3f) + 1) * 244140 / 9; + + /* + * Grab AC prescale + */ + lcd_ac_prescale = (lcdcon >> 25) & 0x1f; + } + + info->var.xres = info->var.xres_virtual; + info->var.yres = info->var.yres_virtual; + info->var.grayscale = info->var.bits_per_pixel > 1; + + size = info->var.xres * info->var.yres * info->var.bits_per_pixel / 8; + + /* + * Might be worth checking to see if we can use the on-board + * RAM if size here... + * CLPS7110 - no on-board SRAM + * EP7212 - 38400 bytes + */ + if (size <= 38400) { + printk(KERN_INFO "CLPS711xFB: could use on-board SRAM?\n"); + } + + if ((syscon & SYSCON1_LCDEN) == 0) { + /* + * The display isn't running. Ensure that + * the display memory is empty. + */ + memset(virt_base, 0, size); + } + + info->screen_base = virt_base; + info->fix.smem_start = phys_base; + info->fix.smem_len = PAGE_ALIGN(size); + info->fix.type = FB_TYPE_PACKED_PIXELS; +} + +static int clps711x_fb_probe(struct platform_device *pdev) +{ + int err = -ENOMEM; + + if (fb_get_options("clps711xfb", NULL)) + return -ENODEV; + + cfb = kzalloc(sizeof(*cfb), GFP_KERNEL); + if (!cfb) + goto out; + + strcpy(cfb->fix.id, "clps711x"); + + cfb->fbops = &clps7111fb_ops; + cfb->flags = FBINFO_DEFAULT; + + clps711x_guess_lcd_params(cfb); + + fb_alloc_cmap(&cfb->cmap, CMAP_MAX_SIZE, 0); + + err = register_framebuffer(cfb); + +out: return err; +} + +static int clps711x_fb_remove(struct platform_device *pdev) +{ + unregister_framebuffer(cfb); + kfree(cfb); + + return 0; +} + +static struct platform_driver clps711x_fb_driver = { + .driver = { + .name = "video-clps711x", + .owner = THIS_MODULE, + }, + .probe = clps711x_fb_probe, + .remove = clps711x_fb_remove, +}; +module_platform_driver(clps711x_fb_driver); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("CLPS711X framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/cobalt_lcdfb.c b/drivers/video/fbdev/cobalt_lcdfb.c new file mode 100644 index 000000000000..d5533f4db1cf --- /dev/null +++ b/drivers/video/fbdev/cobalt_lcdfb.c @@ -0,0 +1,401 @@ +/* + * Cobalt/SEAD3 LCD frame buffer driver. + * + * Copyright (C) 2008 Yoichi Yuasa <yuasa@linux-mips.org> + * Copyright (C) 2012 MIPS Technologies, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/module.h> + +/* + * Cursor position address + * \X 0 1 2 ... 14 15 + * Y+----+----+----+---+----+----+ + * 0|0x00|0x01|0x02|...|0x0e|0x0f| + * +----+----+----+---+----+----+ + * 1|0x40|0x41|0x42|...|0x4e|0x4f| + * +----+----+----+---+----+----+ + */ +#define LCD_DATA_REG_OFFSET 0x10 +#define LCD_XRES_MAX 16 +#define LCD_YRES_MAX 2 +#define LCD_CHARS_MAX 32 + +#define LCD_CLEAR 0x01 +#define LCD_CURSOR_MOVE_HOME 0x02 +#define LCD_RESET 0x06 +#define LCD_OFF 0x08 +#define LCD_CURSOR_OFF 0x0c +#define LCD_CURSOR_BLINK_OFF 0x0e +#define LCD_CURSOR_ON 0x0f +#define LCD_ON LCD_CURSOR_ON +#define LCD_CURSOR_MOVE_LEFT 0x10 +#define LCD_CURSOR_MOVE_RIGHT 0x14 +#define LCD_DISPLAY_LEFT 0x18 +#define LCD_DISPLAY_RIGHT 0x1c +#define LCD_PRERESET 0x3f /* execute 4 times continuously */ +#define LCD_BUSY 0x80 + +#define LCD_GRAPHIC_MODE 0x40 +#define LCD_TEXT_MODE 0x80 +#define LCD_CUR_POS_MASK 0x7f + +#define LCD_CUR_POS(x) ((x) & LCD_CUR_POS_MASK) +#define LCD_TEXT_POS(x) ((x) | LCD_TEXT_MODE) + +#ifdef CONFIG_MIPS_COBALT +static inline void lcd_write_control(struct fb_info *info, u8 control) +{ + writel((u32)control << 24, info->screen_base); +} + +static inline u8 lcd_read_control(struct fb_info *info) +{ + return readl(info->screen_base) >> 24; +} + +static inline void lcd_write_data(struct fb_info *info, u8 data) +{ + writel((u32)data << 24, info->screen_base + LCD_DATA_REG_OFFSET); +} + +static inline u8 lcd_read_data(struct fb_info *info) +{ + return readl(info->screen_base + LCD_DATA_REG_OFFSET) >> 24; +} +#else + +#define LCD_CTL 0x00 +#define LCD_DATA 0x08 +#define CPLD_STATUS 0x10 +#define CPLD_DATA 0x18 + +static inline void cpld_wait(struct fb_info *info) +{ + do { + } while (readl(info->screen_base + CPLD_STATUS) & 1); +} + +static inline void lcd_write_control(struct fb_info *info, u8 control) +{ + cpld_wait(info); + writel(control, info->screen_base + LCD_CTL); +} + +static inline u8 lcd_read_control(struct fb_info *info) +{ + cpld_wait(info); + readl(info->screen_base + LCD_CTL); + cpld_wait(info); + return readl(info->screen_base + CPLD_DATA) & 0xff; +} + +static inline void lcd_write_data(struct fb_info *info, u8 data) +{ + cpld_wait(info); + writel(data, info->screen_base + LCD_DATA); +} + +static inline u8 lcd_read_data(struct fb_info *info) +{ + cpld_wait(info); + readl(info->screen_base + LCD_DATA); + cpld_wait(info); + return readl(info->screen_base + CPLD_DATA) & 0xff; +} +#endif + +static int lcd_busy_wait(struct fb_info *info) +{ + u8 val = 0; + int timeout = 10, retval = 0; + + do { + val = lcd_read_control(info); + val &= LCD_BUSY; + if (val != LCD_BUSY) + break; + + if (msleep_interruptible(1)) + return -EINTR; + + timeout--; + } while (timeout); + + if (val == LCD_BUSY) + retval = -EBUSY; + + return retval; +} + +static void lcd_clear(struct fb_info *info) +{ + int i; + + for (i = 0; i < 4; i++) { + udelay(150); + + lcd_write_control(info, LCD_PRERESET); + } + + udelay(150); + + lcd_write_control(info, LCD_CLEAR); + + udelay(150); + + lcd_write_control(info, LCD_RESET); +} + +static struct fb_fix_screeninfo cobalt_lcdfb_fix = { + .id = "cobalt-lcd", + .type = FB_TYPE_TEXT, + .type_aux = FB_AUX_TEXT_MDA, + .visual = FB_VISUAL_MONO01, + .line_length = LCD_XRES_MAX, + .accel = FB_ACCEL_NONE, +}; + +static ssize_t cobalt_lcdfb_read(struct fb_info *info, char __user *buf, + size_t count, loff_t *ppos) +{ + char src[LCD_CHARS_MAX]; + unsigned long pos; + int len, retval = 0; + + pos = *ppos; + if (pos >= LCD_CHARS_MAX || count == 0) + return 0; + + if (count > LCD_CHARS_MAX) + count = LCD_CHARS_MAX; + + if (pos + count > LCD_CHARS_MAX) + count = LCD_CHARS_MAX - pos; + + for (len = 0; len < count; len++) { + retval = lcd_busy_wait(info); + if (retval < 0) + break; + + lcd_write_control(info, LCD_TEXT_POS(pos)); + + retval = lcd_busy_wait(info); + if (retval < 0) + break; + + src[len] = lcd_read_data(info); + if (pos == 0x0f) + pos = 0x40; + else + pos++; + } + + if (retval < 0 && signal_pending(current)) + return -ERESTARTSYS; + + if (copy_to_user(buf, src, len)) + return -EFAULT; + + *ppos += len; + + return len; +} + +static ssize_t cobalt_lcdfb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + char dst[LCD_CHARS_MAX]; + unsigned long pos; + int len, retval = 0; + + pos = *ppos; + if (pos >= LCD_CHARS_MAX || count == 0) + return 0; + + if (count > LCD_CHARS_MAX) + count = LCD_CHARS_MAX; + + if (pos + count > LCD_CHARS_MAX) + count = LCD_CHARS_MAX - pos; + + if (copy_from_user(dst, buf, count)) + return -EFAULT; + + for (len = 0; len < count; len++) { + retval = lcd_busy_wait(info); + if (retval < 0) + break; + + lcd_write_control(info, LCD_TEXT_POS(pos)); + + retval = lcd_busy_wait(info); + if (retval < 0) + break; + + lcd_write_data(info, dst[len]); + if (pos == 0x0f) + pos = 0x40; + else + pos++; + } + + if (retval < 0 && signal_pending(current)) + return -ERESTARTSYS; + + *ppos += len; + + return len; +} + +static int cobalt_lcdfb_blank(int blank_mode, struct fb_info *info) +{ + int retval; + + retval = lcd_busy_wait(info); + if (retval < 0) + return retval; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + lcd_write_control(info, LCD_ON); + break; + default: + lcd_write_control(info, LCD_OFF); + break; + } + + return 0; +} + +static int cobalt_lcdfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + u32 x, y; + int retval; + + switch (cursor->set) { + case FB_CUR_SETPOS: + x = cursor->image.dx; + y = cursor->image.dy; + if (x >= LCD_XRES_MAX || y >= LCD_YRES_MAX) + return -EINVAL; + + retval = lcd_busy_wait(info); + if (retval < 0) + return retval; + + lcd_write_control(info, + LCD_TEXT_POS(info->fix.line_length * y + x)); + break; + default: + return -EINVAL; + } + + retval = lcd_busy_wait(info); + if (retval < 0) + return retval; + + if (cursor->enable) + lcd_write_control(info, LCD_CURSOR_ON); + else + lcd_write_control(info, LCD_CURSOR_OFF); + + return 0; +} + +static struct fb_ops cobalt_lcd_fbops = { + .owner = THIS_MODULE, + .fb_read = cobalt_lcdfb_read, + .fb_write = cobalt_lcdfb_write, + .fb_blank = cobalt_lcdfb_blank, + .fb_cursor = cobalt_lcdfb_cursor, +}; + +static int cobalt_lcdfb_probe(struct platform_device *dev) +{ + struct fb_info *info; + struct resource *res; + int retval; + + info = framebuffer_alloc(0, &dev->dev); + if (!info) + return -ENOMEM; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res) { + framebuffer_release(info); + return -EBUSY; + } + + info->screen_size = resource_size(res); + info->screen_base = devm_ioremap(&dev->dev, res->start, + info->screen_size); + info->fbops = &cobalt_lcd_fbops; + info->fix = cobalt_lcdfb_fix; + info->fix.smem_start = res->start; + info->fix.smem_len = info->screen_size; + info->pseudo_palette = NULL; + info->par = NULL; + info->flags = FBINFO_DEFAULT; + + retval = register_framebuffer(info); + if (retval < 0) { + framebuffer_release(info); + return retval; + } + + platform_set_drvdata(dev, info); + + lcd_clear(info); + + fb_info(info, "Cobalt server LCD frame buffer device\n"); + + return 0; +} + +static int cobalt_lcdfb_remove(struct platform_device *dev) +{ + struct fb_info *info; + + info = platform_get_drvdata(dev); + if (info) { + unregister_framebuffer(info); + framebuffer_release(info); + } + + return 0; +} + +static struct platform_driver cobalt_lcdfb_driver = { + .probe = cobalt_lcdfb_probe, + .remove = cobalt_lcdfb_remove, + .driver = { + .name = "cobalt-lcd", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(cobalt_lcdfb_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yoichi Yuasa"); +MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver"); diff --git a/drivers/video/fbdev/controlfb.c b/drivers/video/fbdev/controlfb.c new file mode 100644 index 000000000000..fdadef979238 --- /dev/null +++ b/drivers/video/fbdev/controlfb.c @@ -0,0 +1,1084 @@ +/* + * controlfb.c -- frame buffer device for the PowerMac 'control' display + * + * Created 12 July 1998 by Dan Jacobowitz <dan@debian.org> + * Copyright (C) 1998 Dan Jacobowitz + * Copyright (C) 2001 Takashi Oe + * + * Mmap code by Michel Lanners <mlan@cpu.lu> + * + * Frame buffer structure from: + * drivers/video/chipsfb.c -- frame buffer device for + * Chips & Technologies 65550 chip. + * + * Copyright (C) 1998 Paul Mackerras + * + * This file is derived from the Powermac "chips" driver: + * Copyright (C) 1997 Fabio Riccardi. + * And from the frame buffer device for Open Firmware-initialized devices: + * Copyright (C) 1997 Geert Uytterhoeven. + * + * Hardware information from: + * control.c: Console support for PowerMac "control" display adaptor. + * Copyright (C) 1996 Paul Mackerras + * + * Updated to 2.5 framebuffer API by Ben Herrenschmidt + * <benh@kernel.crashing.org>, Paul Mackerras <paulus@samba.org>, + * and James Simmons <jsimmons@infradead.org>. + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/nvram.h> +#include <linux/adb.h> +#include <linux/cuda.h> +#include <asm/io.h> +#include <asm/prom.h> +#include <asm/pgtable.h> +#include <asm/btext.h> + +#include "macmodes.h" +#include "controlfb.h" + +struct fb_par_control { + int vmode, cmode; + int xres, yres; + int vxres, vyres; + int xoffset, yoffset; + int pitch; + struct control_regvals regvals; + unsigned long sync; + unsigned char ctrl; +}; + +#define DIRTY(z) ((x)->z != (y)->z) +#define DIRTY_CMAP(z) (memcmp(&((x)->z), &((y)->z), sizeof((y)->z))) +static inline int PAR_EQUAL(struct fb_par_control *x, struct fb_par_control *y) +{ + int i, results; + + results = 1; + for (i = 0; i < 3; i++) + results &= !DIRTY(regvals.clock_params[i]); + if (!results) + return 0; + for (i = 0; i < 16; i++) + results &= !DIRTY(regvals.regs[i]); + if (!results) + return 0; + return (!DIRTY(cmode) && !DIRTY(xres) && !DIRTY(yres) + && !DIRTY(vxres) && !DIRTY(vyres)); +} +static inline int VAR_MATCH(struct fb_var_screeninfo *x, struct fb_var_screeninfo *y) +{ + return (!DIRTY(bits_per_pixel) && !DIRTY(xres) + && !DIRTY(yres) && !DIRTY(xres_virtual) + && !DIRTY(yres_virtual) + && !DIRTY_CMAP(red) && !DIRTY_CMAP(green) && !DIRTY_CMAP(blue)); +} + +struct fb_info_control { + struct fb_info info; + struct fb_par_control par; + u32 pseudo_palette[16]; + + struct cmap_regs __iomem *cmap_regs; + unsigned long cmap_regs_phys; + + struct control_regs __iomem *control_regs; + unsigned long control_regs_phys; + unsigned long control_regs_size; + + __u8 __iomem *frame_buffer; + unsigned long frame_buffer_phys; + unsigned long fb_orig_base; + unsigned long fb_orig_size; + + int control_use_bank2; + unsigned long total_vram; + unsigned char vram_attr; +}; + +/* control register access macro */ +#define CNTRL_REG(INFO,REG) (&(((INFO)->control_regs->REG).r)) + + +/******************** Prototypes for exported functions ********************/ +/* + * struct fb_ops + */ +static int controlfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int controlfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int controlfb_blank(int blank_mode, struct fb_info *info); +static int controlfb_mmap(struct fb_info *info, + struct vm_area_struct *vma); +static int controlfb_set_par (struct fb_info *info); +static int controlfb_check_var (struct fb_var_screeninfo *var, struct fb_info *info); + +/******************** Prototypes for internal functions **********************/ + +static void set_control_clock(unsigned char *params); +static int init_control(struct fb_info_control *p); +static void control_set_hardware(struct fb_info_control *p, + struct fb_par_control *par); +static int control_of_init(struct device_node *dp); +static void find_vram_size(struct fb_info_control *p); +static int read_control_sense(struct fb_info_control *p); +static int calc_clock_params(unsigned long clk, unsigned char *param); +static int control_var_to_par(struct fb_var_screeninfo *var, + struct fb_par_control *par, const struct fb_info *fb_info); +static inline void control_par_to_var(struct fb_par_control *par, + struct fb_var_screeninfo *var); +static void control_init_info(struct fb_info *info, struct fb_info_control *p); +static void control_cleanup(void); + + +/************************** Internal variables *******************************/ + +static struct fb_info_control *control_fb; + +static int default_vmode __initdata = VMODE_NVRAM; +static int default_cmode __initdata = CMODE_NVRAM; + + +static struct fb_ops controlfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = controlfb_check_var, + .fb_set_par = controlfb_set_par, + .fb_setcolreg = controlfb_setcolreg, + .fb_pan_display = controlfb_pan_display, + .fb_blank = controlfb_blank, + .fb_mmap = controlfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + + +/******************** The functions for controlfb_ops ********************/ + +#ifdef MODULE +MODULE_LICENSE("GPL"); + +int init_module(void) +{ + struct device_node *dp; + int ret = -ENXIO; + + dp = of_find_node_by_name(NULL, "control"); + if (dp != 0 && !control_of_init(dp)) + ret = 0; + of_node_put(dp); + + return ret; +} + +void cleanup_module(void) +{ + control_cleanup(); +} +#endif + +/* + * Checks a var structure + */ +static int controlfb_check_var (struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct fb_par_control par; + int err; + + err = control_var_to_par(var, &par, info); + if (err) + return err; + control_par_to_var(&par, var); + + return 0; +} + +/* + * Applies current var to display + */ +static int controlfb_set_par (struct fb_info *info) +{ + struct fb_info_control *p = (struct fb_info_control *) info; + struct fb_par_control par; + int err; + + if((err = control_var_to_par(&info->var, &par, info))) { + printk (KERN_ERR "controlfb_set_par: error calling" + " control_var_to_par: %d.\n", err); + return err; + } + + control_set_hardware(p, &par); + + info->fix.visual = (p->par.cmode == CMODE_8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + info->fix.line_length = p->par.pitch; + info->fix.xpanstep = 32 >> p->par.cmode; + info->fix.ypanstep = 1; + + return 0; +} + +/* + * Set screen start address according to var offset values + */ +static inline void set_screen_start(int xoffset, int yoffset, + struct fb_info_control *p) +{ + struct fb_par_control *par = &p->par; + + par->xoffset = xoffset; + par->yoffset = yoffset; + out_le32(CNTRL_REG(p,start_addr), + par->yoffset * par->pitch + (par->xoffset << par->cmode)); +} + + +static int controlfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + unsigned int xoffset, hstep; + struct fb_info_control *p = (struct fb_info_control *)info; + struct fb_par_control *par = &p->par; + + /* + * make sure start addr will be 32-byte aligned + */ + hstep = 0x1f >> par->cmode; + xoffset = (var->xoffset + hstep) & ~hstep; + + if (xoffset+par->xres > par->vxres || + var->yoffset+par->yres > par->vyres) + return -EINVAL; + + set_screen_start(xoffset, var->yoffset, p); + + return 0; +} + + +/* + * Private mmap since we want to have a different caching on the framebuffer + * for controlfb. + * Note there's no locking in here; it's done in fb_mmap() in fbmem.c. + */ +static int controlfb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + unsigned long mmio_pgoff; + unsigned long start; + u32 len; + + start = info->fix.smem_start; + len = info->fix.smem_len; + mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT; + if (vma->vm_pgoff >= mmio_pgoff) { + if (info->var.accel_flags) + return -EINVAL; + vma->vm_pgoff -= mmio_pgoff; + start = info->fix.mmio_start; + len = info->fix.mmio_len; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + } else { + /* framebuffer */ + vma->vm_page_prot = pgprot_cached_wthru(vma->vm_page_prot); + } + + return vm_iomap_memory(vma, start, len); +} + +static int controlfb_blank(int blank_mode, struct fb_info *info) +{ + struct fb_info_control *p = (struct fb_info_control *) info; + unsigned ctrl; + + ctrl = ld_le32(CNTRL_REG(p,ctrl)); + if (blank_mode > 0) + switch (blank_mode) { + case FB_BLANK_VSYNC_SUSPEND: + ctrl &= ~3; + break; + case FB_BLANK_HSYNC_SUSPEND: + ctrl &= ~0x30; + break; + case FB_BLANK_POWERDOWN: + ctrl &= ~0x33; + /* fall through */ + case FB_BLANK_NORMAL: + ctrl |= 0x400; + break; + default: + break; + } + else { + ctrl &= ~0x400; + ctrl |= 0x33; + } + out_le32(CNTRL_REG(p,ctrl), ctrl); + + return 0; +} + +static int controlfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct fb_info_control *p = (struct fb_info_control *) info; + __u8 r, g, b; + + if (regno > 255) + return 1; + + r = red >> 8; + g = green >> 8; + b = blue >> 8; + + out_8(&p->cmap_regs->addr, regno); /* tell clut what addr to fill */ + out_8(&p->cmap_regs->lut, r); /* send one color channel at */ + out_8(&p->cmap_regs->lut, g); /* a time... */ + out_8(&p->cmap_regs->lut, b); + + if (regno < 16) { + int i; + switch (p->par.cmode) { + case CMODE_16: + p->pseudo_palette[regno] = + (regno << 10) | (regno << 5) | regno; + break; + case CMODE_32: + i = (regno << 8) | regno; + p->pseudo_palette[regno] = (i << 16) | i; + break; + } + } + + return 0; +} + + +/******************** End of controlfb_ops implementation ******************/ + + + +static void set_control_clock(unsigned char *params) +{ +#ifdef CONFIG_ADB_CUDA + struct adb_request req; + int i; + + for (i = 0; i < 3; ++i) { + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, + 0x50, i + 1, params[i]); + while (!req.complete) + cuda_poll(); + } +#endif +} + + +/* + * finish off the driver initialization and register + */ +static int __init init_control(struct fb_info_control *p) +{ + int full, sense, vmode, cmode, vyres; + struct fb_var_screeninfo var; + int rc; + + printk(KERN_INFO "controlfb: "); + + full = p->total_vram == 0x400000; + + /* Try to pick a video mode out of NVRAM if we have one. */ +#ifdef CONFIG_NVRAM + if (default_cmode == CMODE_NVRAM) { + cmode = nvram_read_byte(NV_CMODE); + if(cmode < CMODE_8 || cmode > CMODE_32) + cmode = CMODE_8; + } else +#endif + cmode=default_cmode; +#ifdef CONFIG_NVRAM + if (default_vmode == VMODE_NVRAM) { + vmode = nvram_read_byte(NV_VMODE); + if (vmode < 1 || vmode > VMODE_MAX || + control_mac_modes[vmode - 1].m[full] < cmode) { + sense = read_control_sense(p); + printk("Monitor sense value = 0x%x, ", sense); + vmode = mac_map_monitor_sense(sense); + if (control_mac_modes[vmode - 1].m[full] < cmode) + vmode = VMODE_640_480_60; + } + } else +#endif + { + vmode=default_vmode; + if (control_mac_modes[vmode - 1].m[full] < cmode) { + if (cmode > CMODE_8) + cmode--; + else + vmode = VMODE_640_480_60; + } + } + + /* Initialize info structure */ + control_init_info(&p->info, p); + + /* Setup default var */ + if (mac_vmode_to_var(vmode, cmode, &var) < 0) { + /* This shouldn't happen! */ + printk("mac_vmode_to_var(%d, %d,) failed\n", vmode, cmode); +try_again: + vmode = VMODE_640_480_60; + cmode = CMODE_8; + if (mac_vmode_to_var(vmode, cmode, &var) < 0) { + printk(KERN_ERR "controlfb: mac_vmode_to_var() failed\n"); + return -ENXIO; + } + printk(KERN_INFO "controlfb: "); + } + printk("using video mode %d and color mode %d.\n", vmode, cmode); + + vyres = (p->total_vram - CTRLFB_OFF) / (var.xres << cmode); + if (vyres > var.yres) + var.yres_virtual = vyres; + + /* Apply default var */ + var.activate = FB_ACTIVATE_NOW; + rc = fb_set_var(&p->info, &var); + if (rc && (vmode != VMODE_640_480_60 || cmode != CMODE_8)) + goto try_again; + + /* Register with fbdev layer */ + if (register_framebuffer(&p->info) < 0) + return -ENXIO; + + fb_info(&p->info, "control display adapter\n"); + + return 0; +} + +#define RADACAL_WRITE(a,d) \ + out_8(&p->cmap_regs->addr, (a)); \ + out_8(&p->cmap_regs->dat, (d)) + +/* Now how about actually saying, Make it so! */ +/* Some things in here probably don't need to be done each time. */ +static void control_set_hardware(struct fb_info_control *p, struct fb_par_control *par) +{ + struct control_regvals *r; + volatile struct preg __iomem *rp; + int i, cmode; + + if (PAR_EQUAL(&p->par, par)) { + /* + * check if only xoffset or yoffset differs. + * this prevents flickers in typical VT switch case. + */ + if (p->par.xoffset != par->xoffset || + p->par.yoffset != par->yoffset) + set_screen_start(par->xoffset, par->yoffset, p); + + return; + } + + p->par = *par; + cmode = p->par.cmode; + r = &par->regvals; + + /* Turn off display */ + out_le32(CNTRL_REG(p,ctrl), 0x400 | par->ctrl); + + set_control_clock(r->clock_params); + + RADACAL_WRITE(0x20, r->radacal_ctrl); + RADACAL_WRITE(0x21, p->control_use_bank2 ? 0 : 1); + RADACAL_WRITE(0x10, 0); + RADACAL_WRITE(0x11, 0); + + rp = &p->control_regs->vswin; + for (i = 0; i < 16; ++i, ++rp) + out_le32(&rp->r, r->regs[i]); + + out_le32(CNTRL_REG(p,pitch), par->pitch); + out_le32(CNTRL_REG(p,mode), r->mode); + out_le32(CNTRL_REG(p,vram_attr), p->vram_attr); + out_le32(CNTRL_REG(p,start_addr), par->yoffset * par->pitch + + (par->xoffset << cmode)); + out_le32(CNTRL_REG(p,rfrcnt), 0x1e5); + out_le32(CNTRL_REG(p,intr_ena), 0); + + /* Turn on display */ + out_le32(CNTRL_REG(p,ctrl), par->ctrl); + +#ifdef CONFIG_BOOTX_TEXT + btext_update_display(p->frame_buffer_phys + CTRLFB_OFF, + p->par.xres, p->par.yres, + (cmode == CMODE_32? 32: cmode == CMODE_16? 16: 8), + p->par.pitch); +#endif /* CONFIG_BOOTX_TEXT */ +} + + +/* + * Parse user specified options (`video=controlfb:') + */ +static void __init control_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "vmode:", 6)) { + int vmode = simple_strtoul(this_opt+6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX && + control_mac_modes[vmode - 1].m[1] >= 0) + default_vmode = vmode; + } else if (!strncmp(this_opt, "cmode:", 6)) { + int depth = simple_strtoul(this_opt+6, NULL, 0); + switch (depth) { + case CMODE_8: + case CMODE_16: + case CMODE_32: + default_cmode = depth; + break; + case 8: + default_cmode = CMODE_8; + break; + case 15: + case 16: + default_cmode = CMODE_16; + break; + case 24: + case 32: + default_cmode = CMODE_32; + break; + } + } + } +} + +static int __init control_init(void) +{ + struct device_node *dp; + char *option = NULL; + int ret = -ENXIO; + + if (fb_get_options("controlfb", &option)) + return -ENODEV; + control_setup(option); + + dp = of_find_node_by_name(NULL, "control"); + if (dp != 0 && !control_of_init(dp)) + ret = 0; + of_node_put(dp); + + return ret; +} + +module_init(control_init); + +/* Work out which banks of VRAM we have installed. */ +/* danj: I guess the card just ignores writes to nonexistant VRAM... */ + +static void __init find_vram_size(struct fb_info_control *p) +{ + int bank1, bank2; + + /* + * Set VRAM in 2MB (bank 1) mode + * VRAM Bank 2 will be accessible through offset 0x600000 if present + * and VRAM Bank 1 will not respond at that offset even if present + */ + out_le32(CNTRL_REG(p,vram_attr), 0x31); + + out_8(&p->frame_buffer[0x600000], 0xb3); + out_8(&p->frame_buffer[0x600001], 0x71); + asm volatile("eieio; dcbf 0,%0" : : "r" (&p->frame_buffer[0x600000]) + : "memory" ); + mb(); + asm volatile("eieio; dcbi 0,%0" : : "r" (&p->frame_buffer[0x600000]) + : "memory" ); + mb(); + + bank2 = (in_8(&p->frame_buffer[0x600000]) == 0xb3) + && (in_8(&p->frame_buffer[0x600001]) == 0x71); + + /* + * Set VRAM in 2MB (bank 2) mode + * VRAM Bank 1 will be accessible through offset 0x000000 if present + * and VRAM Bank 2 will not respond at that offset even if present + */ + out_le32(CNTRL_REG(p,vram_attr), 0x39); + + out_8(&p->frame_buffer[0], 0x5a); + out_8(&p->frame_buffer[1], 0xc7); + asm volatile("eieio; dcbf 0,%0" : : "r" (&p->frame_buffer[0]) + : "memory" ); + mb(); + asm volatile("eieio; dcbi 0,%0" : : "r" (&p->frame_buffer[0]) + : "memory" ); + mb(); + + bank1 = (in_8(&p->frame_buffer[0]) == 0x5a) + && (in_8(&p->frame_buffer[1]) == 0xc7); + + if (bank2) { + if (!bank1) { + /* + * vram bank 2 only + */ + p->control_use_bank2 = 1; + p->vram_attr = 0x39; + p->frame_buffer += 0x600000; + p->frame_buffer_phys += 0x600000; + } else { + /* + * 4 MB vram + */ + p->vram_attr = 0x51; + } + } else { + /* + * vram bank 1 only + */ + p->vram_attr = 0x31; + } + + p->total_vram = (bank1 + bank2) * 0x200000; + + printk(KERN_INFO "controlfb: VRAM Total = %dMB " + "(%dMB @ bank 1, %dMB @ bank 2)\n", + (bank1 + bank2) << 1, bank1 << 1, bank2 << 1); +} + + +/* + * find "control" and initialize + */ +static int __init control_of_init(struct device_node *dp) +{ + struct fb_info_control *p; + struct resource fb_res, reg_res; + + if (control_fb) { + printk(KERN_ERR "controlfb: only one control is supported\n"); + return -ENXIO; + } + + if (of_pci_address_to_resource(dp, 2, &fb_res) || + of_pci_address_to_resource(dp, 1, ®_res)) { + printk(KERN_ERR "can't get 2 addresses for control\n"); + return -ENXIO; + } + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == 0) + return -ENXIO; + control_fb = p; /* save it for cleanups */ + + /* Map in frame buffer and registers */ + p->fb_orig_base = fb_res.start; + p->fb_orig_size = resource_size(&fb_res); + /* use the big-endian aperture (??) */ + p->frame_buffer_phys = fb_res.start + 0x800000; + p->control_regs_phys = reg_res.start; + p->control_regs_size = resource_size(®_res); + + if (!p->fb_orig_base || + !request_mem_region(p->fb_orig_base,p->fb_orig_size,"controlfb")) { + p->fb_orig_base = 0; + goto error_out; + } + /* map at most 8MB for the frame buffer */ + p->frame_buffer = __ioremap(p->frame_buffer_phys, 0x800000, + _PAGE_WRITETHRU); + + if (!p->control_regs_phys || + !request_mem_region(p->control_regs_phys, p->control_regs_size, + "controlfb regs")) { + p->control_regs_phys = 0; + goto error_out; + } + p->control_regs = ioremap(p->control_regs_phys, p->control_regs_size); + + p->cmap_regs_phys = 0xf301b000; /* XXX not in prom? */ + if (!request_mem_region(p->cmap_regs_phys, 0x1000, "controlfb cmap")) { + p->cmap_regs_phys = 0; + goto error_out; + } + p->cmap_regs = ioremap(p->cmap_regs_phys, 0x1000); + + if (!p->cmap_regs || !p->control_regs || !p->frame_buffer) + goto error_out; + + find_vram_size(p); + if (!p->total_vram) + goto error_out; + + if (init_control(p) < 0) + goto error_out; + + return 0; + +error_out: + control_cleanup(); + return -ENXIO; +} + +/* + * Get the monitor sense value. + * Note that this can be called before calibrate_delay, + * so we can't use udelay. + */ +static int read_control_sense(struct fb_info_control *p) +{ + int sense; + + out_le32(CNTRL_REG(p,mon_sense), 7); /* drive all lines high */ + __delay(200); + out_le32(CNTRL_REG(p,mon_sense), 077); /* turn off drivers */ + __delay(2000); + sense = (in_le32(CNTRL_REG(p,mon_sense)) & 0x1c0) << 2; + + /* drive each sense line low in turn and collect the other 2 */ + out_le32(CNTRL_REG(p,mon_sense), 033); /* drive A low */ + __delay(2000); + sense |= (in_le32(CNTRL_REG(p,mon_sense)) & 0xc0) >> 2; + out_le32(CNTRL_REG(p,mon_sense), 055); /* drive B low */ + __delay(2000); + sense |= ((in_le32(CNTRL_REG(p,mon_sense)) & 0x100) >> 5) + | ((in_le32(CNTRL_REG(p,mon_sense)) & 0x40) >> 4); + out_le32(CNTRL_REG(p,mon_sense), 066); /* drive C low */ + __delay(2000); + sense |= (in_le32(CNTRL_REG(p,mon_sense)) & 0x180) >> 7; + + out_le32(CNTRL_REG(p,mon_sense), 077); /* turn off drivers */ + + return sense; +} + +/********************** Various translation functions **********************/ + +#define CONTROL_PIXCLOCK_BASE 256016 +#define CONTROL_PIXCLOCK_MIN 5000 /* ~ 200 MHz dot clock */ + +/* + * calculate the clock paramaters to be sent to CUDA according to given + * pixclock in pico second. + */ +static int calc_clock_params(unsigned long clk, unsigned char *param) +{ + unsigned long p0, p1, p2, k, l, m, n, min; + + if (clk > (CONTROL_PIXCLOCK_BASE << 3)) + return 1; + + p2 = ((clk << 4) < CONTROL_PIXCLOCK_BASE)? 3: 2; + l = clk << p2; + p0 = 0; + p1 = 0; + for (k = 1, min = l; k < 32; k++) { + unsigned long rem; + + m = CONTROL_PIXCLOCK_BASE * k; + n = m / l; + rem = m % l; + if (n && (n < 128) && rem < min) { + p0 = k; + p1 = n; + min = rem; + } + } + if (!p0 || !p1) + return 1; + + param[0] = p0; + param[1] = p1; + param[2] = p2; + + return 0; +} + + +/* + * This routine takes a user-supplied var, and picks the best vmode/cmode + * from it. + */ + +static int control_var_to_par(struct fb_var_screeninfo *var, + struct fb_par_control *par, const struct fb_info *fb_info) +{ + int cmode, piped_diff, hstep; + unsigned hperiod, hssync, hsblank, hesync, heblank, piped, heq, hlfln, + hserr, vperiod, vssync, vesync, veblank, vsblank, vswin, vewin; + unsigned long pixclock; + struct fb_info_control *p = (struct fb_info_control *) fb_info; + struct control_regvals *r = &par->regvals; + + switch (var->bits_per_pixel) { + case 8: + par->cmode = CMODE_8; + if (p->total_vram > 0x200000) { + r->mode = 3; + r->radacal_ctrl = 0x20; + piped_diff = 13; + } else { + r->mode = 2; + r->radacal_ctrl = 0x10; + piped_diff = 9; + } + break; + case 15: + case 16: + par->cmode = CMODE_16; + if (p->total_vram > 0x200000) { + r->mode = 2; + r->radacal_ctrl = 0x24; + piped_diff = 5; + } else { + r->mode = 1; + r->radacal_ctrl = 0x14; + piped_diff = 3; + } + break; + case 32: + par->cmode = CMODE_32; + if (p->total_vram > 0x200000) { + r->mode = 1; + r->radacal_ctrl = 0x28; + } else { + r->mode = 0; + r->radacal_ctrl = 0x18; + } + piped_diff = 1; + break; + default: + return -EINVAL; + } + + /* + * adjust xres and vxres so that the corresponding memory widths are + * 32-byte aligned + */ + hstep = 31 >> par->cmode; + par->xres = (var->xres + hstep) & ~hstep; + par->vxres = (var->xres_virtual + hstep) & ~hstep; + par->xoffset = (var->xoffset + hstep) & ~hstep; + if (par->vxres < par->xres) + par->vxres = par->xres; + par->pitch = par->vxres << par->cmode; + + par->yres = var->yres; + par->vyres = var->yres_virtual; + par->yoffset = var->yoffset; + if (par->vyres < par->yres) + par->vyres = par->yres; + + par->sync = var->sync; + + if (par->pitch * par->vyres + CTRLFB_OFF > p->total_vram) + return -EINVAL; + + if (par->xoffset + par->xres > par->vxres) + par->xoffset = par->vxres - par->xres; + if (par->yoffset + par->yres > par->vyres) + par->yoffset = par->vyres - par->yres; + + pixclock = (var->pixclock < CONTROL_PIXCLOCK_MIN)? CONTROL_PIXCLOCK_MIN: + var->pixclock; + if (calc_clock_params(pixclock, r->clock_params)) + return -EINVAL; + + hperiod = ((var->left_margin + par->xres + var->right_margin + + var->hsync_len) >> 1) - 2; + hssync = hperiod + 1; + hsblank = hssync - (var->right_margin >> 1); + hesync = (var->hsync_len >> 1) - 1; + heblank = (var->left_margin >> 1) + hesync; + piped = heblank - piped_diff; + heq = var->hsync_len >> 2; + hlfln = (hperiod+2) >> 1; + hserr = hssync-hesync; + vperiod = (var->vsync_len + var->lower_margin + par->yres + + var->upper_margin) << 1; + vssync = vperiod - 2; + vesync = (var->vsync_len << 1) - vperiod + vssync; + veblank = (var->upper_margin << 1) + vesync; + vsblank = vssync - (var->lower_margin << 1); + vswin = (vsblank+vssync) >> 1; + vewin = (vesync+veblank) >> 1; + + r->regs[0] = vswin; + r->regs[1] = vsblank; + r->regs[2] = veblank; + r->regs[3] = vewin; + r->regs[4] = vesync; + r->regs[5] = vssync; + r->regs[6] = vperiod; + r->regs[7] = piped; + r->regs[8] = hperiod; + r->regs[9] = hsblank; + r->regs[10] = heblank; + r->regs[11] = hesync; + r->regs[12] = hssync; + r->regs[13] = heq; + r->regs[14] = hlfln; + r->regs[15] = hserr; + + if (par->xres >= 1280 && par->cmode >= CMODE_16) + par->ctrl = 0x7f; + else + par->ctrl = 0x3b; + + if (mac_var_to_vmode(var, &par->vmode, &cmode)) + par->vmode = 0; + + return 0; +} + + +/* + * Convert hardware data in par to an fb_var_screeninfo + */ + +static void control_par_to_var(struct fb_par_control *par, struct fb_var_screeninfo *var) +{ + struct control_regints *rv; + + rv = (struct control_regints *) par->regvals.regs; + + memset(var, 0, sizeof(*var)); + var->xres = par->xres; + var->yres = par->yres; + var->xres_virtual = par->vxres; + var->yres_virtual = par->vyres; + var->xoffset = par->xoffset; + var->yoffset = par->yoffset; + + switch(par->cmode) { + default: + case CMODE_8: + var->bits_per_pixel = 8; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case CMODE_16: /* RGB 555 */ + var->bits_per_pixel = 16; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case CMODE_32: /* RGB 888 */ + var->bits_per_pixel = 32; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } + var->height = -1; + var->width = -1; + var->vmode = FB_VMODE_NONINTERLACED; + + var->left_margin = (rv->heblank - rv->hesync) << 1; + var->right_margin = (rv->hssync - rv->hsblank) << 1; + var->hsync_len = (rv->hperiod + 2 - rv->hssync + rv->hesync) << 1; + + var->upper_margin = (rv->veblank - rv->vesync) >> 1; + var->lower_margin = (rv->vssync - rv->vsblank) >> 1; + var->vsync_len = (rv->vperiod - rv->vssync + rv->vesync) >> 1; + + var->sync = par->sync; + + /* + * 10^12 * clock_params[0] / (3906400 * clock_params[1] + * * 2^clock_params[2]) + * (10^12 * clock_params[0] / (3906400 * clock_params[1])) + * >> clock_params[2] + */ + /* (255990.17 * clock_params[0] / clock_params[1]) >> clock_params[2] */ + var->pixclock = CONTROL_PIXCLOCK_BASE * par->regvals.clock_params[0]; + var->pixclock /= par->regvals.clock_params[1]; + var->pixclock >>= par->regvals.clock_params[2]; +} + +/* + * Set misc info vars for this driver + */ +static void __init control_init_info(struct fb_info *info, struct fb_info_control *p) +{ + /* Fill fb_info */ + info->par = &p->par; + info->fbops = &controlfb_ops; + info->pseudo_palette = p->pseudo_palette; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + info->screen_base = p->frame_buffer + CTRLFB_OFF; + + fb_alloc_cmap(&info->cmap, 256, 0); + + /* Fill fix common fields */ + strcpy(info->fix.id, "control"); + info->fix.mmio_start = p->control_regs_phys; + info->fix.mmio_len = sizeof(struct control_regs); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.smem_start = p->frame_buffer_phys + CTRLFB_OFF; + info->fix.smem_len = p->total_vram - CTRLFB_OFF; + info->fix.ywrapstep = 0; + info->fix.type_aux = 0; + info->fix.accel = FB_ACCEL_NONE; +} + + +static void control_cleanup(void) +{ + struct fb_info_control *p = control_fb; + + if (!p) + return; + + if (p->cmap_regs) + iounmap(p->cmap_regs); + if (p->control_regs) + iounmap(p->control_regs); + if (p->frame_buffer) { + if (p->control_use_bank2) + p->frame_buffer -= 0x600000; + iounmap(p->frame_buffer); + } + if (p->cmap_regs_phys) + release_mem_region(p->cmap_regs_phys, 0x1000); + if (p->control_regs_phys) + release_mem_region(p->control_regs_phys, p->control_regs_size); + if (p->fb_orig_base) + release_mem_region(p->fb_orig_base, p->fb_orig_size); + kfree(p); +} + + diff --git a/drivers/video/fbdev/controlfb.h b/drivers/video/fbdev/controlfb.h new file mode 100644 index 000000000000..6026c60fc100 --- /dev/null +++ b/drivers/video/fbdev/controlfb.h @@ -0,0 +1,145 @@ +/* + * controlfb_hw.h: Constants of all sorts for controlfb + * + * Copyright (C) 1998 Daniel Jacobowitz <dan@debian.org> + * + * 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. + * + * Based on an awful lot of code, including: + * + * control.c: Console support for PowerMac "control" display adaptor. + * Copyright (C) 1996 Paul Mackerras. + * + * The so far unpublished platinumfb.c + * Copyright (C) 1998 Jon Howell + */ + +/* + * Structure of the registers for the RADACAL colormap device. + */ +struct cmap_regs { + unsigned char addr; /* index for both cmap and misc registers */ + char pad1[15]; + unsigned char crsr; /* cursor palette */ + char pad2[15]; + unsigned char dat; /* RADACAL misc register data */ + char pad3[15]; + unsigned char lut; /* cmap data */ + char pad4[15]; +}; + +/* + * Structure of the registers for the "control" display adaptor. + */ +#define PAD(x) char x[12] + +struct preg { /* padded register */ + unsigned r; + char pad[12]; +}; + +struct control_regs { + struct preg vcount; /* vertical counter */ + /* Vertical parameters are in units of 1/2 scan line */ + struct preg vswin; /* between vsblank and vssync */ + struct preg vsblank; /* vert start blank */ + struct preg veblank; /* vert end blank (display start) */ + struct preg vewin; /* between vesync and veblank */ + struct preg vesync; /* vert end sync */ + struct preg vssync; /* vert start sync */ + struct preg vperiod; /* vert period */ + struct preg piped; /* pipe delay hardware cursor */ + /* Horizontal params are in units of 2 pixels */ + struct preg hperiod; /* horiz period - 2 */ + struct preg hsblank; /* horiz start blank */ + struct preg heblank; /* horiz end blank */ + struct preg hesync; /* horiz end sync */ + struct preg hssync; /* horiz start sync */ + struct preg heq; /* half horiz sync len */ + struct preg hlfln; /* half horiz period */ + struct preg hserr; /* horiz period - horiz sync len */ + struct preg cnttst; + struct preg ctrl; /* display control */ + struct preg start_addr; /* start address: 5 lsbs zero */ + struct preg pitch; /* addrs diff between scan lines */ + struct preg mon_sense; /* monitor sense bits */ + struct preg vram_attr; /* enable vram banks */ + struct preg mode; + struct preg rfrcnt; /* refresh count */ + struct preg intr_ena; /* interrupt enable */ + struct preg intr_stat; /* interrupt status */ + struct preg res[5]; +}; + +struct control_regints { + /* Vertical parameters are in units of 1/2 scan line */ + unsigned vswin; /* between vsblank and vssync */ + unsigned vsblank; /* vert start blank */ + unsigned veblank; /* vert end blank (display start) */ + unsigned vewin; /* between vesync and veblank */ + unsigned vesync; /* vert end sync */ + unsigned vssync; /* vert start sync */ + unsigned vperiod; /* vert period */ + unsigned piped; /* pipe delay hardware cursor */ + /* Horizontal params are in units of 2 pixels */ + /* Except, apparently, for hres > 1024 (or == 1280?) */ + unsigned hperiod; /* horiz period - 2 */ + unsigned hsblank; /* horiz start blank */ + unsigned heblank; /* horiz end blank */ + unsigned hesync; /* horiz end sync */ + unsigned hssync; /* horiz start sync */ + unsigned heq; /* half horiz sync len */ + unsigned hlfln; /* half horiz period */ + unsigned hserr; /* horiz period - horiz sync len */ +}; + +/* + * Dot clock rate is + * 3.9064MHz * 2**clock_params[2] * clock_params[1] / clock_params[0]. + */ +struct control_regvals { + unsigned regs[16]; /* for vswin .. hserr */ + unsigned char mode; + unsigned char radacal_ctrl; + unsigned char clock_params[3]; +}; + +#define CTRLFB_OFF 16 /* position of pixel 0 in frame buffer */ + + +/* + * Best cmode supported by control + */ +struct max_cmodes { + int m[2]; /* 0: 2MB vram, 1: 4MB vram */ +}; + +/* + * Video modes supported by macmodes.c + */ +static struct max_cmodes control_mac_modes[] = { + {{-1,-1}}, /* 512x384, 60Hz interlaced (NTSC) */ + {{-1,-1}}, /* 512x384, 60Hz */ + {{-1,-1}}, /* 640x480, 50Hz interlaced (PAL) */ + {{-1,-1}}, /* 640x480, 60Hz interlaced (NTSC) */ + {{ 2, 2}}, /* 640x480, 60Hz (VGA) */ + {{ 2, 2}}, /* 640x480, 67Hz */ + {{-1,-1}}, /* 640x870, 75Hz (portrait) */ + {{-1,-1}}, /* 768x576, 50Hz (PAL full frame) */ + {{ 2, 2}}, /* 800x600, 56Hz */ + {{ 2, 2}}, /* 800x600, 60Hz */ + {{ 2, 2}}, /* 800x600, 72Hz */ + {{ 2, 2}}, /* 800x600, 75Hz */ + {{ 1, 2}}, /* 832x624, 75Hz */ + {{ 1, 2}}, /* 1024x768, 60Hz */ + {{ 1, 2}}, /* 1024x768, 70Hz (or 72Hz?) */ + {{ 1, 2}}, /* 1024x768, 75Hz (VESA) */ + {{ 1, 2}}, /* 1024x768, 75Hz */ + {{ 1, 2}}, /* 1152x870, 75Hz */ + {{ 0, 1}}, /* 1280x960, 75Hz */ + {{ 0, 1}}, /* 1280x1024, 75Hz */ +}; + diff --git a/drivers/video/fbdev/cyber2000fb.c b/drivers/video/fbdev/cyber2000fb.c new file mode 100644 index 000000000000..b0a950f36970 --- /dev/null +++ b/drivers/video/fbdev/cyber2000fb.c @@ -0,0 +1,1901 @@ +/* + * linux/drivers/video/cyber2000fb.c + * + * Copyright (C) 1998-2002 Russell King + * + * MIPS and 50xx clock support + * Copyright (C) 2001 Bradley D. LaRonde <brad@ltc.com> + * + * 32 bit support, text color and panning fixes for modes != 8 bit + * Copyright (C) 2002 Denis Oliver Kropp <dok@directfb.org> + * + * 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. + * + * Integraphics CyberPro 2000, 2010 and 5000 frame buffer device + * + * Based on cyberfb.c. + * + * Note that we now use the new fbcon fix, var and cmap scheme. We do + * still have to check which console is the currently displayed one + * however, especially for the colourmap stuff. + * + * We also use the new hotplug PCI subsystem. I'm not sure if there + * are any such cards, but I'm erring on the side of caution. We don't + * want to go pop just because someone does have one. + * + * Note that this doesn't work fully in the case of multiple CyberPro + * cards with grabbers. We currently can only attach to the first + * CyberPro card found. + * + * When we're in truecolour mode, we power down the LUT RAM as a power + * saving feature. Also, when we enter any of the powersaving modes + * (except soft blanking) we power down the RAMDACs. This saves about + * 1W, which is roughly 8% of the power consumption of a NetWinder + * (which, incidentally, is about the same saving as a 2.5in hard disk + * entering standby mode.) + */ +#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/pci.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <asm/pgtable.h> + +#ifdef __arm__ +#include <asm/mach-types.h> +#endif + +#include "cyber2000fb.h" + +struct cfb_info { + struct fb_info fb; + struct display_switch *dispsw; + struct display *display; + unsigned char __iomem *region; + unsigned char __iomem *regs; + u_int id; + u_int irq; + int func_use_count; + u_long ref_ps; + + /* + * Clock divisors + */ + u_int divisors[4]; + + struct { + u8 red, green, blue; + } palette[NR_PALETTE]; + + u_char mem_ctl1; + u_char mem_ctl2; + u_char mclk_mult; + u_char mclk_div; + /* + * RAMDAC control register is both of these or'ed together + */ + u_char ramdac_ctrl; + u_char ramdac_powerdown; + + u32 pseudo_palette[16]; + + spinlock_t reg_b0_lock; + +#ifdef CONFIG_FB_CYBER2000_DDC + bool ddc_registered; + struct i2c_adapter ddc_adapter; + struct i2c_algo_bit_data ddc_algo; +#endif + +#ifdef CONFIG_FB_CYBER2000_I2C + struct i2c_adapter i2c_adapter; + struct i2c_algo_bit_data i2c_algo; +#endif +}; + +static char *default_font = "Acorn8x8"; +module_param(default_font, charp, 0); +MODULE_PARM_DESC(default_font, "Default font name"); + +/* + * Our access methods. + */ +#define cyber2000fb_writel(val, reg, cfb) writel(val, (cfb)->regs + (reg)) +#define cyber2000fb_writew(val, reg, cfb) writew(val, (cfb)->regs + (reg)) +#define cyber2000fb_writeb(val, reg, cfb) writeb(val, (cfb)->regs + (reg)) + +#define cyber2000fb_readb(reg, cfb) readb((cfb)->regs + (reg)) + +static inline void +cyber2000_crtcw(unsigned int reg, unsigned int val, struct cfb_info *cfb) +{ + cyber2000fb_writew((reg & 255) | val << 8, 0x3d4, cfb); +} + +static inline void +cyber2000_grphw(unsigned int reg, unsigned int val, struct cfb_info *cfb) +{ + cyber2000fb_writew((reg & 255) | val << 8, 0x3ce, cfb); +} + +static inline unsigned int +cyber2000_grphr(unsigned int reg, struct cfb_info *cfb) +{ + cyber2000fb_writeb(reg, 0x3ce, cfb); + return cyber2000fb_readb(0x3cf, cfb); +} + +static inline void +cyber2000_attrw(unsigned int reg, unsigned int val, struct cfb_info *cfb) +{ + cyber2000fb_readb(0x3da, cfb); + cyber2000fb_writeb(reg, 0x3c0, cfb); + cyber2000fb_readb(0x3c1, cfb); + cyber2000fb_writeb(val, 0x3c0, cfb); +} + +static inline void +cyber2000_seqw(unsigned int reg, unsigned int val, struct cfb_info *cfb) +{ + cyber2000fb_writew((reg & 255) | val << 8, 0x3c4, cfb); +} + +/* -------------------- Hardware specific routines ------------------------- */ + +/* + * Hardware Cyber2000 Acceleration + */ +static void +cyber2000fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + unsigned long dst, col; + + if (!(cfb->fb.var.accel_flags & FB_ACCELF_TEXT)) { + cfb_fillrect(info, rect); + return; + } + + cyber2000fb_writeb(0, CO_REG_CONTROL, cfb); + cyber2000fb_writew(rect->width - 1, CO_REG_PIXWIDTH, cfb); + cyber2000fb_writew(rect->height - 1, CO_REG_PIXHEIGHT, cfb); + + col = rect->color; + if (cfb->fb.var.bits_per_pixel > 8) + col = ((u32 *)cfb->fb.pseudo_palette)[col]; + cyber2000fb_writel(col, CO_REG_FGCOLOUR, cfb); + + dst = rect->dx + rect->dy * cfb->fb.var.xres_virtual; + if (cfb->fb.var.bits_per_pixel == 24) { + cyber2000fb_writeb(dst, CO_REG_X_PHASE, cfb); + dst *= 3; + } + + cyber2000fb_writel(dst, CO_REG_DEST_PTR, cfb); + cyber2000fb_writeb(CO_FG_MIX_SRC, CO_REG_FGMIX, cfb); + cyber2000fb_writew(CO_CMD_L_PATTERN_FGCOL, CO_REG_CMD_L, cfb); + cyber2000fb_writew(CO_CMD_H_BLITTER, CO_REG_CMD_H, cfb); +} + +static void +cyber2000fb_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + unsigned int cmd = CO_CMD_L_PATTERN_FGCOL; + unsigned long src, dst; + + if (!(cfb->fb.var.accel_flags & FB_ACCELF_TEXT)) { + cfb_copyarea(info, region); + return; + } + + cyber2000fb_writeb(0, CO_REG_CONTROL, cfb); + cyber2000fb_writew(region->width - 1, CO_REG_PIXWIDTH, cfb); + cyber2000fb_writew(region->height - 1, CO_REG_PIXHEIGHT, cfb); + + src = region->sx + region->sy * cfb->fb.var.xres_virtual; + dst = region->dx + region->dy * cfb->fb.var.xres_virtual; + + if (region->sx < region->dx) { + src += region->width - 1; + dst += region->width - 1; + cmd |= CO_CMD_L_INC_LEFT; + } + + if (region->sy < region->dy) { + src += (region->height - 1) * cfb->fb.var.xres_virtual; + dst += (region->height - 1) * cfb->fb.var.xres_virtual; + cmd |= CO_CMD_L_INC_UP; + } + + if (cfb->fb.var.bits_per_pixel == 24) { + cyber2000fb_writeb(dst, CO_REG_X_PHASE, cfb); + src *= 3; + dst *= 3; + } + cyber2000fb_writel(src, CO_REG_SRC1_PTR, cfb); + cyber2000fb_writel(dst, CO_REG_DEST_PTR, cfb); + cyber2000fb_writew(CO_FG_MIX_SRC, CO_REG_FGMIX, cfb); + cyber2000fb_writew(cmd, CO_REG_CMD_L, cfb); + cyber2000fb_writew(CO_CMD_H_FGSRCMAP | CO_CMD_H_BLITTER, + CO_REG_CMD_H, cfb); +} + +static void +cyber2000fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + cfb_imageblit(info, image); + return; +} + +static int cyber2000fb_sync(struct fb_info *info) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + int count = 100000; + + if (!(cfb->fb.var.accel_flags & FB_ACCELF_TEXT)) + return 0; + + while (cyber2000fb_readb(CO_REG_CONTROL, cfb) & CO_CTRL_BUSY) { + if (!count--) { + debug_printf("accel_wait timed out\n"); + cyber2000fb_writeb(0, CO_REG_CONTROL, cfb); + break; + } + udelay(1); + } + return 0; +} + +/* + * =========================================================================== + */ + +static inline u32 convert_bitfield(u_int val, struct fb_bitfield *bf) +{ + u_int mask = (1 << bf->length) - 1; + + return (val >> (16 - bf->length) & mask) << bf->offset; +} + +/* + * Set a single color register. Return != 0 for invalid regno. + */ +static int +cyber2000fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + struct fb_var_screeninfo *var = &cfb->fb.var; + u32 pseudo_val; + int ret = 1; + + switch (cfb->fb.fix.visual) { + default: + return 1; + + /* + * Pseudocolour: + * 8 8 + * pixel --/--+--/--> red lut --> red dac + * | 8 + * +--/--> green lut --> green dac + * | 8 + * +--/--> blue lut --> blue dac + */ + case FB_VISUAL_PSEUDOCOLOR: + if (regno >= NR_PALETTE) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + cfb->palette[regno].red = red; + cfb->palette[regno].green = green; + cfb->palette[regno].blue = blue; + + cyber2000fb_writeb(regno, 0x3c8, cfb); + cyber2000fb_writeb(red, 0x3c9, cfb); + cyber2000fb_writeb(green, 0x3c9, cfb); + cyber2000fb_writeb(blue, 0x3c9, cfb); + return 0; + + /* + * Direct colour: + * n rl + * pixel --/--+--/--> red lut --> red dac + * | gl + * +--/--> green lut --> green dac + * | bl + * +--/--> blue lut --> blue dac + * n = bpp, rl = red length, gl = green length, bl = blue length + */ + case FB_VISUAL_DIRECTCOLOR: + red >>= 8; + green >>= 8; + blue >>= 8; + + if (var->green.length == 6 && regno < 64) { + cfb->palette[regno << 2].green = green; + + /* + * The 6 bits of the green component are applied + * to the high 6 bits of the LUT. + */ + cyber2000fb_writeb(regno << 2, 0x3c8, cfb); + cyber2000fb_writeb(cfb->palette[regno >> 1].red, + 0x3c9, cfb); + cyber2000fb_writeb(green, 0x3c9, cfb); + cyber2000fb_writeb(cfb->palette[regno >> 1].blue, + 0x3c9, cfb); + + green = cfb->palette[regno << 3].green; + + ret = 0; + } + + if (var->green.length >= 5 && regno < 32) { + cfb->palette[regno << 3].red = red; + cfb->palette[regno << 3].green = green; + cfb->palette[regno << 3].blue = blue; + + /* + * The 5 bits of each colour component are + * applied to the high 5 bits of the LUT. + */ + cyber2000fb_writeb(regno << 3, 0x3c8, cfb); + cyber2000fb_writeb(red, 0x3c9, cfb); + cyber2000fb_writeb(green, 0x3c9, cfb); + cyber2000fb_writeb(blue, 0x3c9, cfb); + ret = 0; + } + + if (var->green.length == 4 && regno < 16) { + cfb->palette[regno << 4].red = red; + cfb->palette[regno << 4].green = green; + cfb->palette[regno << 4].blue = blue; + + /* + * The 5 bits of each colour component are + * applied to the high 5 bits of the LUT. + */ + cyber2000fb_writeb(regno << 4, 0x3c8, cfb); + cyber2000fb_writeb(red, 0x3c9, cfb); + cyber2000fb_writeb(green, 0x3c9, cfb); + cyber2000fb_writeb(blue, 0x3c9, cfb); + ret = 0; + } + + /* + * Since this is only used for the first 16 colours, we + * don't have to care about overflowing for regno >= 32 + */ + pseudo_val = regno << var->red.offset | + regno << var->green.offset | + regno << var->blue.offset; + break; + + /* + * True colour: + * n rl + * pixel --/--+--/--> red dac + * | gl + * +--/--> green dac + * | bl + * +--/--> blue dac + * n = bpp, rl = red length, gl = green length, bl = blue length + */ + case FB_VISUAL_TRUECOLOR: + pseudo_val = convert_bitfield(transp ^ 0xffff, &var->transp); + pseudo_val |= convert_bitfield(red, &var->red); + pseudo_val |= convert_bitfield(green, &var->green); + pseudo_val |= convert_bitfield(blue, &var->blue); + ret = 0; + break; + } + + /* + * Now set our pseudo palette for the CFB16/24/32 drivers. + */ + if (regno < 16) + ((u32 *)cfb->fb.pseudo_palette)[regno] = pseudo_val; + + return ret; +} + +struct par_info { + /* + * Hardware + */ + u_char clock_mult; + u_char clock_div; + u_char extseqmisc; + u_char co_pixfmt; + u_char crtc_ofl; + u_char crtc[19]; + u_int width; + u_int pitch; + u_int fetch; + + /* + * Other + */ + u_char ramdac; +}; + +static const u_char crtc_idx[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 +}; + +static void cyber2000fb_write_ramdac_ctrl(struct cfb_info *cfb) +{ + unsigned int i; + unsigned int val = cfb->ramdac_ctrl | cfb->ramdac_powerdown; + + cyber2000fb_writeb(0x56, 0x3ce, cfb); + i = cyber2000fb_readb(0x3cf, cfb); + cyber2000fb_writeb(i | 4, 0x3cf, cfb); + cyber2000fb_writeb(val, 0x3c6, cfb); + cyber2000fb_writeb(i, 0x3cf, cfb); + /* prevent card lock-up observed on x86 with CyberPro 2000 */ + cyber2000fb_readb(0x3cf, cfb); +} + +static void cyber2000fb_set_timing(struct cfb_info *cfb, struct par_info *hw) +{ + u_int i; + + /* + * Blank palette + */ + for (i = 0; i < NR_PALETTE; i++) { + cyber2000fb_writeb(i, 0x3c8, cfb); + cyber2000fb_writeb(0, 0x3c9, cfb); + cyber2000fb_writeb(0, 0x3c9, cfb); + cyber2000fb_writeb(0, 0x3c9, cfb); + } + + cyber2000fb_writeb(0xef, 0x3c2, cfb); + cyber2000_crtcw(0x11, 0x0b, cfb); + cyber2000_attrw(0x11, 0x00, cfb); + + cyber2000_seqw(0x00, 0x01, cfb); + cyber2000_seqw(0x01, 0x01, cfb); + cyber2000_seqw(0x02, 0x0f, cfb); + cyber2000_seqw(0x03, 0x00, cfb); + cyber2000_seqw(0x04, 0x0e, cfb); + cyber2000_seqw(0x00, 0x03, cfb); + + for (i = 0; i < sizeof(crtc_idx); i++) + cyber2000_crtcw(crtc_idx[i], hw->crtc[i], cfb); + + for (i = 0x0a; i < 0x10; i++) + cyber2000_crtcw(i, 0, cfb); + + cyber2000_grphw(EXT_CRT_VRTOFL, hw->crtc_ofl, cfb); + cyber2000_grphw(0x00, 0x00, cfb); + cyber2000_grphw(0x01, 0x00, cfb); + cyber2000_grphw(0x02, 0x00, cfb); + cyber2000_grphw(0x03, 0x00, cfb); + cyber2000_grphw(0x04, 0x00, cfb); + cyber2000_grphw(0x05, 0x60, cfb); + cyber2000_grphw(0x06, 0x05, cfb); + cyber2000_grphw(0x07, 0x0f, cfb); + cyber2000_grphw(0x08, 0xff, cfb); + + /* Attribute controller registers */ + for (i = 0; i < 16; i++) + cyber2000_attrw(i, i, cfb); + + cyber2000_attrw(0x10, 0x01, cfb); + cyber2000_attrw(0x11, 0x00, cfb); + cyber2000_attrw(0x12, 0x0f, cfb); + cyber2000_attrw(0x13, 0x00, cfb); + cyber2000_attrw(0x14, 0x00, cfb); + + /* PLL registers */ + spin_lock(&cfb->reg_b0_lock); + cyber2000_grphw(EXT_DCLK_MULT, hw->clock_mult, cfb); + cyber2000_grphw(EXT_DCLK_DIV, hw->clock_div, cfb); + cyber2000_grphw(EXT_MCLK_MULT, cfb->mclk_mult, cfb); + cyber2000_grphw(EXT_MCLK_DIV, cfb->mclk_div, cfb); + cyber2000_grphw(0x90, 0x01, cfb); + cyber2000_grphw(0xb9, 0x80, cfb); + cyber2000_grphw(0xb9, 0x00, cfb); + spin_unlock(&cfb->reg_b0_lock); + + cfb->ramdac_ctrl = hw->ramdac; + cyber2000fb_write_ramdac_ctrl(cfb); + + cyber2000fb_writeb(0x20, 0x3c0, cfb); + cyber2000fb_writeb(0xff, 0x3c6, cfb); + + cyber2000_grphw(0x14, hw->fetch, cfb); + cyber2000_grphw(0x15, ((hw->fetch >> 8) & 0x03) | + ((hw->pitch >> 4) & 0x30), cfb); + cyber2000_grphw(EXT_SEQ_MISC, hw->extseqmisc, cfb); + + /* + * Set up accelerator registers + */ + cyber2000fb_writew(hw->width, CO_REG_SRC_WIDTH, cfb); + cyber2000fb_writew(hw->width, CO_REG_DEST_WIDTH, cfb); + cyber2000fb_writeb(hw->co_pixfmt, CO_REG_PIXFMT, cfb); +} + +static inline int +cyber2000fb_update_start(struct cfb_info *cfb, struct fb_var_screeninfo *var) +{ + u_int base = var->yoffset * var->xres_virtual + var->xoffset; + + base *= var->bits_per_pixel; + + /* + * Convert to bytes and shift two extra bits because DAC + * can only start on 4 byte aligned data. + */ + base >>= 5; + + if (base >= 1 << 20) + return -EINVAL; + + cyber2000_grphw(0x10, base >> 16 | 0x10, cfb); + cyber2000_crtcw(0x0c, base >> 8, cfb); + cyber2000_crtcw(0x0d, base, cfb); + + return 0; +} + +static int +cyber2000fb_decode_crtc(struct par_info *hw, struct cfb_info *cfb, + struct fb_var_screeninfo *var) +{ + u_int Htotal, Hblankend, Hsyncend; + u_int Vtotal, Vdispend, Vblankstart, Vblankend, Vsyncstart, Vsyncend; +#define ENCODE_BIT(v, b1, m, b2) ((((v) >> (b1)) & (m)) << (b2)) + + hw->crtc[13] = hw->pitch; + hw->crtc[17] = 0xe3; + hw->crtc[14] = 0; + hw->crtc[8] = 0; + + Htotal = var->xres + var->right_margin + + var->hsync_len + var->left_margin; + + if (Htotal > 2080) + return -EINVAL; + + hw->crtc[0] = (Htotal >> 3) - 5; + hw->crtc[1] = (var->xres >> 3) - 1; + hw->crtc[2] = var->xres >> 3; + hw->crtc[4] = (var->xres + var->right_margin) >> 3; + + Hblankend = (Htotal - 4 * 8) >> 3; + + hw->crtc[3] = ENCODE_BIT(Hblankend, 0, 0x1f, 0) | + ENCODE_BIT(1, 0, 0x01, 7); + + Hsyncend = (var->xres + var->right_margin + var->hsync_len) >> 3; + + hw->crtc[5] = ENCODE_BIT(Hsyncend, 0, 0x1f, 0) | + ENCODE_BIT(Hblankend, 5, 0x01, 7); + + Vdispend = var->yres - 1; + Vsyncstart = var->yres + var->lower_margin; + Vsyncend = var->yres + var->lower_margin + var->vsync_len; + Vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin - 2; + + if (Vtotal > 2047) + return -EINVAL; + + Vblankstart = var->yres + 6; + Vblankend = Vtotal - 10; + + hw->crtc[6] = Vtotal; + hw->crtc[7] = ENCODE_BIT(Vtotal, 8, 0x01, 0) | + ENCODE_BIT(Vdispend, 8, 0x01, 1) | + ENCODE_BIT(Vsyncstart, 8, 0x01, 2) | + ENCODE_BIT(Vblankstart, 8, 0x01, 3) | + ENCODE_BIT(1, 0, 0x01, 4) | + ENCODE_BIT(Vtotal, 9, 0x01, 5) | + ENCODE_BIT(Vdispend, 9, 0x01, 6) | + ENCODE_BIT(Vsyncstart, 9, 0x01, 7); + hw->crtc[9] = ENCODE_BIT(0, 0, 0x1f, 0) | + ENCODE_BIT(Vblankstart, 9, 0x01, 5) | + ENCODE_BIT(1, 0, 0x01, 6); + hw->crtc[10] = Vsyncstart; + hw->crtc[11] = ENCODE_BIT(Vsyncend, 0, 0x0f, 0) | + ENCODE_BIT(1, 0, 0x01, 7); + hw->crtc[12] = Vdispend; + hw->crtc[15] = Vblankstart; + hw->crtc[16] = Vblankend; + hw->crtc[18] = 0xff; + + /* + * overflow - graphics reg 0x11 + * 0=VTOTAL:10 1=VDEND:10 2=VRSTART:10 3=VBSTART:10 + * 4=LINECOMP:10 5-IVIDEO 6=FIXCNT + */ + hw->crtc_ofl = + ENCODE_BIT(Vtotal, 10, 0x01, 0) | + ENCODE_BIT(Vdispend, 10, 0x01, 1) | + ENCODE_BIT(Vsyncstart, 10, 0x01, 2) | + ENCODE_BIT(Vblankstart, 10, 0x01, 3) | + EXT_CRT_VRTOFL_LINECOMP10; + + /* woody: set the interlaced bit... */ + /* FIXME: what about doublescan? */ + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) + hw->crtc_ofl |= EXT_CRT_VRTOFL_INTERLACE; + + return 0; +} + +/* + * The following was discovered by a good monitor, bit twiddling, theorising + * and but mostly luck. Strangely, it looks like everyone elses' PLL! + * + * Clock registers: + * fclock = fpll / div2 + * fpll = fref * mult / div1 + * where: + * fref = 14.318MHz (69842ps) + * mult = reg0xb0.7:0 + * div1 = (reg0xb1.5:0 + 1) + * div2 = 2^(reg0xb1.7:6) + * fpll should be between 115 and 260 MHz + * (8696ps and 3846ps) + */ +static int +cyber2000fb_decode_clock(struct par_info *hw, struct cfb_info *cfb, + struct fb_var_screeninfo *var) +{ + u_long pll_ps = var->pixclock; + const u_long ref_ps = cfb->ref_ps; + u_int div2, t_div1, best_div1, best_mult; + int best_diff; + int vco; + + /* + * Step 1: + * find div2 such that 115MHz < fpll < 260MHz + * and 0 <= div2 < 4 + */ + for (div2 = 0; div2 < 4; div2++) { + u_long new_pll; + + new_pll = pll_ps / cfb->divisors[div2]; + if (8696 > new_pll && new_pll > 3846) { + pll_ps = new_pll; + break; + } + } + + if (div2 == 4) + return -EINVAL; + + /* + * Step 2: + * Given pll_ps and ref_ps, find: + * pll_ps * 0.995 < pll_ps_calc < pll_ps * 1.005 + * where { 1 < best_div1 < 32, 1 < best_mult < 256 } + * pll_ps_calc = best_div1 / (ref_ps * best_mult) + */ + best_diff = 0x7fffffff; + best_mult = 2; + best_div1 = 32; + for (t_div1 = 2; t_div1 < 32; t_div1 += 1) { + u_int rr, t_mult, t_pll_ps; + int diff; + + /* + * Find the multiplier for this divisor + */ + rr = ref_ps * t_div1; + t_mult = (rr + pll_ps / 2) / pll_ps; + + /* + * Is the multiplier within the correct range? + */ + if (t_mult > 256 || t_mult < 2) + continue; + + /* + * Calculate the actual clock period from this multiplier + * and divisor, and estimate the error. + */ + t_pll_ps = (rr + t_mult / 2) / t_mult; + diff = pll_ps - t_pll_ps; + if (diff < 0) + diff = -diff; + + if (diff < best_diff) { + best_diff = diff; + best_mult = t_mult; + best_div1 = t_div1; + } + + /* + * If we hit an exact value, there is no point in continuing. + */ + if (diff == 0) + break; + } + + /* + * Step 3: + * combine values + */ + hw->clock_mult = best_mult - 1; + hw->clock_div = div2 << 6 | (best_div1 - 1); + + vco = ref_ps * best_div1 / best_mult; + if ((ref_ps == 40690) && (vco < 5556)) + /* Set VFSEL when VCO > 180MHz (5.556 ps). */ + hw->clock_div |= EXT_DCLK_DIV_VFSEL; + + return 0; +} + +/* + * Set the User Defined Part of the Display + */ +static int +cyber2000fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + struct par_info hw; + unsigned int mem; + int err; + + var->transp.msb_right = 0; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + + switch (var->bits_per_pixel) { + case 8: /* PSEUDOCOLOUR, 256 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + case 16:/* DIRECTCOLOUR, 64k or 32k */ + switch (var->green.length) { + case 6: /* RGB565, 64k */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + + default: + case 5: /* RGB555, 32k */ + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + break; + + case 4: /* RGB444, 4k + transparency? */ + var->transp.offset = 12; + var->transp.length = 4; + var->red.offset = 8; + var->red.length = 4; + var->green.offset = 4; + var->green.length = 4; + var->blue.offset = 0; + var->blue.length = 4; + break; + } + break; + + case 24:/* TRUECOLOUR, 16m */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + case 32:/* TRUECOLOUR, 16m */ + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + default: + return -EINVAL; + } + + mem = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel / 8); + if (mem > cfb->fb.fix.smem_len) + var->yres_virtual = cfb->fb.fix.smem_len * 8 / + (var->bits_per_pixel * var->xres_virtual); + + if (var->yres > var->yres_virtual) + var->yres = var->yres_virtual; + if (var->xres > var->xres_virtual) + var->xres = var->xres_virtual; + + err = cyber2000fb_decode_clock(&hw, cfb, var); + if (err) + return err; + + err = cyber2000fb_decode_crtc(&hw, cfb, var); + if (err) + return err; + + return 0; +} + +static int cyber2000fb_set_par(struct fb_info *info) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + struct fb_var_screeninfo *var = &cfb->fb.var; + struct par_info hw; + unsigned int mem; + + hw.width = var->xres_virtual; + hw.ramdac = RAMDAC_VREFEN | RAMDAC_DAC8BIT; + + switch (var->bits_per_pixel) { + case 8: + hw.co_pixfmt = CO_PIXFMT_8BPP; + hw.pitch = hw.width >> 3; + hw.extseqmisc = EXT_SEQ_MISC_8; + break; + + case 16: + hw.co_pixfmt = CO_PIXFMT_16BPP; + hw.pitch = hw.width >> 2; + + switch (var->green.length) { + case 6: /* RGB565, 64k */ + hw.extseqmisc = EXT_SEQ_MISC_16_RGB565; + break; + case 5: /* RGB555, 32k */ + hw.extseqmisc = EXT_SEQ_MISC_16_RGB555; + break; + case 4: /* RGB444, 4k + transparency? */ + hw.extseqmisc = EXT_SEQ_MISC_16_RGB444; + break; + default: + BUG(); + } + break; + + case 24:/* TRUECOLOUR, 16m */ + hw.co_pixfmt = CO_PIXFMT_24BPP; + hw.width *= 3; + hw.pitch = hw.width >> 3; + hw.ramdac |= (RAMDAC_BYPASS | RAMDAC_RAMPWRDN); + hw.extseqmisc = EXT_SEQ_MISC_24_RGB888; + break; + + case 32:/* TRUECOLOUR, 16m */ + hw.co_pixfmt = CO_PIXFMT_32BPP; + hw.pitch = hw.width >> 1; + hw.ramdac |= (RAMDAC_BYPASS | RAMDAC_RAMPWRDN); + hw.extseqmisc = EXT_SEQ_MISC_32; + break; + + default: + BUG(); + } + + /* + * Sigh, this is absolutely disgusting, but caused by + * the way the fbcon developers want to separate out + * the "checking" and the "setting" of the video mode. + * + * If the mode is not suitable for the hardware here, + * we can't prevent it being set by returning an error. + * + * In theory, since NetWinders contain just one VGA card, + * we should never end up hitting this problem. + */ + BUG_ON(cyber2000fb_decode_clock(&hw, cfb, var) != 0); + BUG_ON(cyber2000fb_decode_crtc(&hw, cfb, var) != 0); + + hw.width -= 1; + hw.fetch = hw.pitch; + if (!(cfb->mem_ctl2 & MEM_CTL2_64BIT)) + hw.fetch <<= 1; + hw.fetch += 1; + + cfb->fb.fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; + + /* + * Same here - if the size of the video mode exceeds the + * available RAM, we can't prevent this mode being set. + * + * In theory, since NetWinders contain just one VGA card, + * we should never end up hitting this problem. + */ + mem = cfb->fb.fix.line_length * var->yres_virtual; + BUG_ON(mem > cfb->fb.fix.smem_len); + + /* + * 8bpp displays are always pseudo colour. 16bpp and above + * are direct colour or true colour, depending on whether + * the RAMDAC palettes are bypassed. (Direct colour has + * palettes, true colour does not.) + */ + if (var->bits_per_pixel == 8) + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + else if (hw.ramdac & RAMDAC_BYPASS) + cfb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + else + cfb->fb.fix.visual = FB_VISUAL_DIRECTCOLOR; + + cyber2000fb_set_timing(cfb, &hw); + cyber2000fb_update_start(cfb, var); + + return 0; +} + +/* + * Pan or Wrap the Display + */ +static int +cyber2000fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + + if (cyber2000fb_update_start(cfb, var)) + return -EINVAL; + + cfb->fb.var.xoffset = var->xoffset; + cfb->fb.var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) { + cfb->fb.var.vmode |= FB_VMODE_YWRAP; + } else { + cfb->fb.var.vmode &= ~FB_VMODE_YWRAP; + } + + return 0; +} + +/* + * (Un)Blank the display. + * + * Blank the screen if blank_mode != 0, else unblank. If + * blank == NULL then the caller blanks by setting the CLUT + * (Color Look Up Table) to all black. Return 0 if blanking + * succeeded, != 0 if un-/blanking failed due to e.g. a + * video mode which doesn't support it. Implements VESA + * suspend and powerdown modes on hardware that supports + * disabling hsync/vsync: + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + * + * wms...Enable VESA DMPS compatible powerdown mode + * run "setterm -powersave powerdown" to take advantage + */ +static int cyber2000fb_blank(int blank, struct fb_info *info) +{ + struct cfb_info *cfb = (struct cfb_info *)info; + unsigned int sync = 0; + int i; + + switch (blank) { + case FB_BLANK_POWERDOWN: /* powerdown - both sync lines down */ + sync = EXT_SYNC_CTL_VS_0 | EXT_SYNC_CTL_HS_0; + break; + case FB_BLANK_HSYNC_SUSPEND: /* hsync off */ + sync = EXT_SYNC_CTL_VS_NORMAL | EXT_SYNC_CTL_HS_0; + break; + case FB_BLANK_VSYNC_SUSPEND: /* vsync off */ + sync = EXT_SYNC_CTL_VS_0 | EXT_SYNC_CTL_HS_NORMAL; + break; + case FB_BLANK_NORMAL: /* soft blank */ + default: /* unblank */ + break; + } + + cyber2000_grphw(EXT_SYNC_CTL, sync, cfb); + + if (blank <= 1) { + /* turn on ramdacs */ + cfb->ramdac_powerdown &= ~(RAMDAC_DACPWRDN | RAMDAC_BYPASS | + RAMDAC_RAMPWRDN); + cyber2000fb_write_ramdac_ctrl(cfb); + } + + /* + * Soft blank/unblank the display. + */ + if (blank) { /* soft blank */ + for (i = 0; i < NR_PALETTE; i++) { + cyber2000fb_writeb(i, 0x3c8, cfb); + cyber2000fb_writeb(0, 0x3c9, cfb); + cyber2000fb_writeb(0, 0x3c9, cfb); + cyber2000fb_writeb(0, 0x3c9, cfb); + } + } else { /* unblank */ + for (i = 0; i < NR_PALETTE; i++) { + cyber2000fb_writeb(i, 0x3c8, cfb); + cyber2000fb_writeb(cfb->palette[i].red, 0x3c9, cfb); + cyber2000fb_writeb(cfb->palette[i].green, 0x3c9, cfb); + cyber2000fb_writeb(cfb->palette[i].blue, 0x3c9, cfb); + } + } + + if (blank >= 2) { + /* turn off ramdacs */ + cfb->ramdac_powerdown |= RAMDAC_DACPWRDN | RAMDAC_BYPASS | + RAMDAC_RAMPWRDN; + cyber2000fb_write_ramdac_ctrl(cfb); + } + + return 0; +} + +static struct fb_ops cyber2000fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = cyber2000fb_check_var, + .fb_set_par = cyber2000fb_set_par, + .fb_setcolreg = cyber2000fb_setcolreg, + .fb_blank = cyber2000fb_blank, + .fb_pan_display = cyber2000fb_pan_display, + .fb_fillrect = cyber2000fb_fillrect, + .fb_copyarea = cyber2000fb_copyarea, + .fb_imageblit = cyber2000fb_imageblit, + .fb_sync = cyber2000fb_sync, +}; + +/* + * This is the only "static" reference to the internal data structures + * of this driver. It is here solely at the moment to support the other + * CyberPro modules external to this driver. + */ +static struct cfb_info *int_cfb_info; + +/* + * Enable access to the extended registers + */ +void cyber2000fb_enable_extregs(struct cfb_info *cfb) +{ + cfb->func_use_count += 1; + + if (cfb->func_use_count == 1) { + int old; + + old = cyber2000_grphr(EXT_FUNC_CTL, cfb); + old |= EXT_FUNC_CTL_EXTREGENBL; + cyber2000_grphw(EXT_FUNC_CTL, old, cfb); + } +} +EXPORT_SYMBOL(cyber2000fb_enable_extregs); + +/* + * Disable access to the extended registers + */ +void cyber2000fb_disable_extregs(struct cfb_info *cfb) +{ + if (cfb->func_use_count == 1) { + int old; + + old = cyber2000_grphr(EXT_FUNC_CTL, cfb); + old &= ~EXT_FUNC_CTL_EXTREGENBL; + cyber2000_grphw(EXT_FUNC_CTL, old, cfb); + } + + if (cfb->func_use_count == 0) + printk(KERN_ERR "disable_extregs: count = 0\n"); + else + cfb->func_use_count -= 1; +} +EXPORT_SYMBOL(cyber2000fb_disable_extregs); + +/* + * Attach a capture/tv driver to the core CyberX0X0 driver. + */ +int cyber2000fb_attach(struct cyberpro_info *info, int idx) +{ + if (int_cfb_info != NULL) { + info->dev = int_cfb_info->fb.device; +#ifdef CONFIG_FB_CYBER2000_I2C + info->i2c = &int_cfb_info->i2c_adapter; +#else + info->i2c = NULL; +#endif + info->regs = int_cfb_info->regs; + info->irq = int_cfb_info->irq; + info->fb = int_cfb_info->fb.screen_base; + info->fb_size = int_cfb_info->fb.fix.smem_len; + info->info = int_cfb_info; + + strlcpy(info->dev_name, int_cfb_info->fb.fix.id, + sizeof(info->dev_name)); + } + + return int_cfb_info != NULL; +} +EXPORT_SYMBOL(cyber2000fb_attach); + +/* + * Detach a capture/tv driver from the core CyberX0X0 driver. + */ +void cyber2000fb_detach(int idx) +{ +} +EXPORT_SYMBOL(cyber2000fb_detach); + +#ifdef CONFIG_FB_CYBER2000_DDC + +#define DDC_REG 0xb0 +#define DDC_SCL_OUT (1 << 0) +#define DDC_SDA_OUT (1 << 4) +#define DDC_SCL_IN (1 << 2) +#define DDC_SDA_IN (1 << 6) + +static void cyber2000fb_enable_ddc(struct cfb_info *cfb) +{ + spin_lock(&cfb->reg_b0_lock); + cyber2000fb_writew(0x1bf, 0x3ce, cfb); +} + +static void cyber2000fb_disable_ddc(struct cfb_info *cfb) +{ + cyber2000fb_writew(0x0bf, 0x3ce, cfb); + spin_unlock(&cfb->reg_b0_lock); +} + + +static void cyber2000fb_ddc_setscl(void *data, int val) +{ + struct cfb_info *cfb = data; + unsigned char reg; + + cyber2000fb_enable_ddc(cfb); + reg = cyber2000_grphr(DDC_REG, cfb); + if (!val) /* bit is inverted */ + reg |= DDC_SCL_OUT; + else + reg &= ~DDC_SCL_OUT; + cyber2000_grphw(DDC_REG, reg, cfb); + cyber2000fb_disable_ddc(cfb); +} + +static void cyber2000fb_ddc_setsda(void *data, int val) +{ + struct cfb_info *cfb = data; + unsigned char reg; + + cyber2000fb_enable_ddc(cfb); + reg = cyber2000_grphr(DDC_REG, cfb); + if (!val) /* bit is inverted */ + reg |= DDC_SDA_OUT; + else + reg &= ~DDC_SDA_OUT; + cyber2000_grphw(DDC_REG, reg, cfb); + cyber2000fb_disable_ddc(cfb); +} + +static int cyber2000fb_ddc_getscl(void *data) +{ + struct cfb_info *cfb = data; + int retval; + + cyber2000fb_enable_ddc(cfb); + retval = !!(cyber2000_grphr(DDC_REG, cfb) & DDC_SCL_IN); + cyber2000fb_disable_ddc(cfb); + + return retval; +} + +static int cyber2000fb_ddc_getsda(void *data) +{ + struct cfb_info *cfb = data; + int retval; + + cyber2000fb_enable_ddc(cfb); + retval = !!(cyber2000_grphr(DDC_REG, cfb) & DDC_SDA_IN); + cyber2000fb_disable_ddc(cfb); + + return retval; +} + +static int cyber2000fb_setup_ddc_bus(struct cfb_info *cfb) +{ + strlcpy(cfb->ddc_adapter.name, cfb->fb.fix.id, + sizeof(cfb->ddc_adapter.name)); + cfb->ddc_adapter.owner = THIS_MODULE; + cfb->ddc_adapter.class = I2C_CLASS_DDC; + cfb->ddc_adapter.algo_data = &cfb->ddc_algo; + cfb->ddc_adapter.dev.parent = cfb->fb.device; + cfb->ddc_algo.setsda = cyber2000fb_ddc_setsda; + cfb->ddc_algo.setscl = cyber2000fb_ddc_setscl; + cfb->ddc_algo.getsda = cyber2000fb_ddc_getsda; + cfb->ddc_algo.getscl = cyber2000fb_ddc_getscl; + cfb->ddc_algo.udelay = 10; + cfb->ddc_algo.timeout = 20; + cfb->ddc_algo.data = cfb; + + i2c_set_adapdata(&cfb->ddc_adapter, cfb); + + return i2c_bit_add_bus(&cfb->ddc_adapter); +} +#endif /* CONFIG_FB_CYBER2000_DDC */ + +#ifdef CONFIG_FB_CYBER2000_I2C +static void cyber2000fb_i2c_setsda(void *data, int state) +{ + struct cfb_info *cfb = data; + unsigned int latch2; + + spin_lock(&cfb->reg_b0_lock); + latch2 = cyber2000_grphr(EXT_LATCH2, cfb); + latch2 &= EXT_LATCH2_I2C_CLKEN; + if (state) + latch2 |= EXT_LATCH2_I2C_DATEN; + cyber2000_grphw(EXT_LATCH2, latch2, cfb); + spin_unlock(&cfb->reg_b0_lock); +} + +static void cyber2000fb_i2c_setscl(void *data, int state) +{ + struct cfb_info *cfb = data; + unsigned int latch2; + + spin_lock(&cfb->reg_b0_lock); + latch2 = cyber2000_grphr(EXT_LATCH2, cfb); + latch2 &= EXT_LATCH2_I2C_DATEN; + if (state) + latch2 |= EXT_LATCH2_I2C_CLKEN; + cyber2000_grphw(EXT_LATCH2, latch2, cfb); + spin_unlock(&cfb->reg_b0_lock); +} + +static int cyber2000fb_i2c_getsda(void *data) +{ + struct cfb_info *cfb = data; + int ret; + + spin_lock(&cfb->reg_b0_lock); + ret = !!(cyber2000_grphr(EXT_LATCH2, cfb) & EXT_LATCH2_I2C_DAT); + spin_unlock(&cfb->reg_b0_lock); + + return ret; +} + +static int cyber2000fb_i2c_getscl(void *data) +{ + struct cfb_info *cfb = data; + int ret; + + spin_lock(&cfb->reg_b0_lock); + ret = !!(cyber2000_grphr(EXT_LATCH2, cfb) & EXT_LATCH2_I2C_CLK); + spin_unlock(&cfb->reg_b0_lock); + + return ret; +} + +static int cyber2000fb_i2c_register(struct cfb_info *cfb) +{ + strlcpy(cfb->i2c_adapter.name, cfb->fb.fix.id, + sizeof(cfb->i2c_adapter.name)); + cfb->i2c_adapter.owner = THIS_MODULE; + cfb->i2c_adapter.algo_data = &cfb->i2c_algo; + cfb->i2c_adapter.dev.parent = cfb->fb.device; + cfb->i2c_algo.setsda = cyber2000fb_i2c_setsda; + cfb->i2c_algo.setscl = cyber2000fb_i2c_setscl; + cfb->i2c_algo.getsda = cyber2000fb_i2c_getsda; + cfb->i2c_algo.getscl = cyber2000fb_i2c_getscl; + cfb->i2c_algo.udelay = 5; + cfb->i2c_algo.timeout = msecs_to_jiffies(100); + cfb->i2c_algo.data = cfb; + + return i2c_bit_add_bus(&cfb->i2c_adapter); +} + +static void cyber2000fb_i2c_unregister(struct cfb_info *cfb) +{ + i2c_del_adapter(&cfb->i2c_adapter); +} +#else +#define cyber2000fb_i2c_register(cfb) (0) +#define cyber2000fb_i2c_unregister(cfb) do { } while (0) +#endif + +/* + * These parameters give + * 640x480, hsync 31.5kHz, vsync 60Hz + */ +static struct fb_videomode cyber2000fb_default_mode = { + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 39722, + .left_margin = 56, + .right_margin = 16, + .upper_margin = 34, + .lower_margin = 9, + .hsync_len = 88, + .vsync_len = 2, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED +}; + +static char igs_regs[] = { + EXT_CRT_IRQ, 0, + EXT_CRT_TEST, 0, + EXT_SYNC_CTL, 0, + EXT_SEG_WRITE_PTR, 0, + EXT_SEG_READ_PTR, 0, + EXT_BIU_MISC, EXT_BIU_MISC_LIN_ENABLE | + EXT_BIU_MISC_COP_ENABLE | + EXT_BIU_MISC_COP_BFC, + EXT_FUNC_CTL, 0, + CURS_H_START, 0, + CURS_H_START + 1, 0, + CURS_H_PRESET, 0, + CURS_V_START, 0, + CURS_V_START + 1, 0, + CURS_V_PRESET, 0, + CURS_CTL, 0, + EXT_ATTRIB_CTL, EXT_ATTRIB_CTL_EXT, + EXT_OVERSCAN_RED, 0, + EXT_OVERSCAN_GREEN, 0, + EXT_OVERSCAN_BLUE, 0, + + /* some of these are questionable when we have a BIOS */ + EXT_MEM_CTL0, EXT_MEM_CTL0_7CLK | + EXT_MEM_CTL0_RAS_1 | + EXT_MEM_CTL0_MULTCAS, + EXT_HIDDEN_CTL1, 0x30, + EXT_FIFO_CTL, 0x0b, + EXT_FIFO_CTL + 1, 0x17, + 0x76, 0x00, + EXT_HIDDEN_CTL4, 0xc8 +}; + +/* + * Initialise the CyberPro hardware. On the CyberPro5XXXX, + * ensure that we're using the correct PLL (5XXX's may be + * programmed to use an additional set of PLLs.) + */ +static void cyberpro_init_hw(struct cfb_info *cfb) +{ + int i; + + for (i = 0; i < sizeof(igs_regs); i += 2) + cyber2000_grphw(igs_regs[i], igs_regs[i + 1], cfb); + + if (cfb->id == ID_CYBERPRO_5000) { + unsigned char val; + cyber2000fb_writeb(0xba, 0x3ce, cfb); + val = cyber2000fb_readb(0x3cf, cfb) & 0x80; + cyber2000fb_writeb(val, 0x3cf, cfb); + } +} + +static struct cfb_info *cyberpro_alloc_fb_info(unsigned int id, char *name) +{ + struct cfb_info *cfb; + + cfb = kzalloc(sizeof(struct cfb_info), GFP_KERNEL); + if (!cfb) + return NULL; + + + cfb->id = id; + + if (id == ID_CYBERPRO_5000) + cfb->ref_ps = 40690; /* 24.576 MHz */ + else + cfb->ref_ps = 69842; /* 14.31818 MHz (69841?) */ + + cfb->divisors[0] = 1; + cfb->divisors[1] = 2; + cfb->divisors[2] = 4; + + if (id == ID_CYBERPRO_2000) + cfb->divisors[3] = 8; + else + cfb->divisors[3] = 6; + + strcpy(cfb->fb.fix.id, name); + + cfb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + cfb->fb.fix.type_aux = 0; + cfb->fb.fix.xpanstep = 0; + cfb->fb.fix.ypanstep = 1; + cfb->fb.fix.ywrapstep = 0; + + switch (id) { + case ID_IGA_1682: + cfb->fb.fix.accel = 0; + break; + + case ID_CYBERPRO_2000: + cfb->fb.fix.accel = FB_ACCEL_IGS_CYBER2000; + break; + + case ID_CYBERPRO_2010: + cfb->fb.fix.accel = FB_ACCEL_IGS_CYBER2010; + break; + + case ID_CYBERPRO_5000: + cfb->fb.fix.accel = FB_ACCEL_IGS_CYBER5000; + break; + } + + cfb->fb.var.nonstd = 0; + cfb->fb.var.activate = FB_ACTIVATE_NOW; + cfb->fb.var.height = -1; + cfb->fb.var.width = -1; + cfb->fb.var.accel_flags = FB_ACCELF_TEXT; + + cfb->fb.fbops = &cyber2000fb_ops; + cfb->fb.flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + cfb->fb.pseudo_palette = cfb->pseudo_palette; + + spin_lock_init(&cfb->reg_b0_lock); + + fb_alloc_cmap(&cfb->fb.cmap, NR_PALETTE, 0); + + return cfb; +} + +static void cyberpro_free_fb_info(struct cfb_info *cfb) +{ + if (cfb) { + /* + * Free the colourmap + */ + fb_alloc_cmap(&cfb->fb.cmap, 0, 0); + + kfree(cfb); + } +} + +/* + * Parse Cyber2000fb options. Usage: + * video=cyber2000:font:fontname + */ +#ifndef MODULE +static int cyber2000fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (strncmp(opt, "font:", 5) == 0) { + static char default_font_storage[40]; + + strlcpy(default_font_storage, opt + 5, + sizeof(default_font_storage)); + default_font = default_font_storage; + continue; + } + + printk(KERN_ERR "CyberPro20x0: unknown parameter: %s\n", opt); + } + return 0; +} +#endif /* MODULE */ + +/* + * The CyberPro chips can be placed on many different bus types. + * This probe function is common to all bus types. The bus-specific + * probe function is expected to have: + * - enabled access to the linear memory region + * - memory mapped access to the registers + * - initialised mem_ctl1 and mem_ctl2 appropriately. + */ +static int cyberpro_common_probe(struct cfb_info *cfb) +{ + u_long smem_size; + u_int h_sync, v_sync; + int err; + + cyberpro_init_hw(cfb); + + /* + * Get the video RAM size and width from the VGA register. + * This should have been already initialised by the BIOS, + * but if it's garbage, claim default 1MB VRAM (woody) + */ + cfb->mem_ctl1 = cyber2000_grphr(EXT_MEM_CTL1, cfb); + cfb->mem_ctl2 = cyber2000_grphr(EXT_MEM_CTL2, cfb); + + /* + * Determine the size of the memory. + */ + switch (cfb->mem_ctl2 & MEM_CTL2_SIZE_MASK) { + case MEM_CTL2_SIZE_4MB: + smem_size = 0x00400000; + break; + case MEM_CTL2_SIZE_2MB: + smem_size = 0x00200000; + break; + case MEM_CTL2_SIZE_1MB: + smem_size = 0x00100000; + break; + default: + smem_size = 0x00100000; + break; + } + + cfb->fb.fix.smem_len = smem_size; + cfb->fb.fix.mmio_len = MMIO_SIZE; + cfb->fb.screen_base = cfb->region; + +#ifdef CONFIG_FB_CYBER2000_DDC + if (cyber2000fb_setup_ddc_bus(cfb) == 0) + cfb->ddc_registered = true; +#endif + + err = -EINVAL; + if (!fb_find_mode(&cfb->fb.var, &cfb->fb, NULL, NULL, 0, + &cyber2000fb_default_mode, 8)) { + printk(KERN_ERR "%s: no valid mode found\n", cfb->fb.fix.id); + goto failed; + } + + cfb->fb.var.yres_virtual = cfb->fb.fix.smem_len * 8 / + (cfb->fb.var.bits_per_pixel * cfb->fb.var.xres_virtual); + + if (cfb->fb.var.yres_virtual < cfb->fb.var.yres) + cfb->fb.var.yres_virtual = cfb->fb.var.yres; + +/* fb_set_var(&cfb->fb.var, -1, &cfb->fb); */ + + /* + * Calculate the hsync and vsync frequencies. Note that + * we split the 1e12 constant up so that we can preserve + * the precision and fit the results into 32-bit registers. + * (1953125000 * 512 = 1e12) + */ + h_sync = 1953125000 / cfb->fb.var.pixclock; + h_sync = h_sync * 512 / (cfb->fb.var.xres + cfb->fb.var.left_margin + + cfb->fb.var.right_margin + cfb->fb.var.hsync_len); + v_sync = h_sync / (cfb->fb.var.yres + cfb->fb.var.upper_margin + + cfb->fb.var.lower_margin + cfb->fb.var.vsync_len); + + printk(KERN_INFO "%s: %dKiB VRAM, using %dx%d, %d.%03dkHz, %dHz\n", + cfb->fb.fix.id, cfb->fb.fix.smem_len >> 10, + cfb->fb.var.xres, cfb->fb.var.yres, + h_sync / 1000, h_sync % 1000, v_sync); + + err = cyber2000fb_i2c_register(cfb); + if (err) + goto failed; + + err = register_framebuffer(&cfb->fb); + if (err) + cyber2000fb_i2c_unregister(cfb); + +failed: +#ifdef CONFIG_FB_CYBER2000_DDC + if (err && cfb->ddc_registered) + i2c_del_adapter(&cfb->ddc_adapter); +#endif + return err; +} + +static void cyberpro_common_remove(struct cfb_info *cfb) +{ + unregister_framebuffer(&cfb->fb); +#ifdef CONFIG_FB_CYBER2000_DDC + if (cfb->ddc_registered) + i2c_del_adapter(&cfb->ddc_adapter); +#endif + cyber2000fb_i2c_unregister(cfb); +} + +static void cyberpro_common_resume(struct cfb_info *cfb) +{ + cyberpro_init_hw(cfb); + + /* + * Reprogram the MEM_CTL1 and MEM_CTL2 registers + */ + cyber2000_grphw(EXT_MEM_CTL1, cfb->mem_ctl1, cfb); + cyber2000_grphw(EXT_MEM_CTL2, cfb->mem_ctl2, cfb); + + /* + * Restore the old video mode and the palette. + * We also need to tell fbcon to redraw the console. + */ + cyber2000fb_set_par(&cfb->fb); +} + +/* + * PCI specific support. + */ +#ifdef CONFIG_PCI +/* + * We need to wake up the CyberPro, and make sure its in linear memory + * mode. Unfortunately, this is specific to the platform and card that + * we are running on. + * + * On x86 and ARM, should we be initialising the CyberPro first via the + * IO registers, and then the MMIO registers to catch all cases? Can we + * end up in the situation where the chip is in MMIO mode, but not awake + * on an x86 system? + */ +static int cyberpro_pci_enable_mmio(struct cfb_info *cfb) +{ + unsigned char val; + +#if defined(__sparc_v9__) +#error "You lose, consult DaveM." +#elif defined(__sparc__) + /* + * SPARC does not have an "outb" instruction, so we generate + * I/O cycles storing into a reserved memory space at + * physical address 0x3000000 + */ + unsigned char __iomem *iop; + + iop = ioremap(0x3000000, 0x5000); + if (iop == NULL) { + printk(KERN_ERR "iga5000: cannot map I/O\n"); + return -ENOMEM; + } + + writeb(0x18, iop + 0x46e8); + writeb(0x01, iop + 0x102); + writeb(0x08, iop + 0x46e8); + writeb(EXT_BIU_MISC, iop + 0x3ce); + writeb(EXT_BIU_MISC_LIN_ENABLE, iop + 0x3cf); + + iounmap(iop); +#else + /* + * Most other machine types are "normal", so + * we use the standard IO-based wakeup. + */ + outb(0x18, 0x46e8); + outb(0x01, 0x102); + outb(0x08, 0x46e8); + outb(EXT_BIU_MISC, 0x3ce); + outb(EXT_BIU_MISC_LIN_ENABLE, 0x3cf); +#endif + + /* + * Allow the CyberPro to accept PCI burst accesses + */ + if (cfb->id == ID_CYBERPRO_2010) { + printk(KERN_INFO "%s: NOT enabling PCI bursts\n", + cfb->fb.fix.id); + } else { + val = cyber2000_grphr(EXT_BUS_CTL, cfb); + if (!(val & EXT_BUS_CTL_PCIBURST_WRITE)) { + printk(KERN_INFO "%s: enabling PCI bursts\n", + cfb->fb.fix.id); + + val |= EXT_BUS_CTL_PCIBURST_WRITE; + + if (cfb->id == ID_CYBERPRO_5000) + val |= EXT_BUS_CTL_PCIBURST_READ; + + cyber2000_grphw(EXT_BUS_CTL, val, cfb); + } + } + + return 0; +} + +static int cyberpro_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct cfb_info *cfb; + char name[16]; + int err; + + sprintf(name, "CyberPro%4X", id->device); + + err = pci_enable_device(dev); + if (err) + return err; + + err = -ENOMEM; + cfb = cyberpro_alloc_fb_info(id->driver_data, name); + if (!cfb) + goto failed_release; + + err = pci_request_regions(dev, cfb->fb.fix.id); + if (err) + goto failed_regions; + + cfb->irq = dev->irq; + cfb->region = pci_ioremap_bar(dev, 0); + if (!cfb->region) { + err = -ENOMEM; + goto failed_ioremap; + } + + cfb->regs = cfb->region + MMIO_OFFSET; + cfb->fb.device = &dev->dev; + cfb->fb.fix.mmio_start = pci_resource_start(dev, 0) + MMIO_OFFSET; + cfb->fb.fix.smem_start = pci_resource_start(dev, 0); + + /* + * Bring up the hardware. This is expected to enable access + * to the linear memory region, and allow access to the memory + * mapped registers. Also, mem_ctl1 and mem_ctl2 must be + * initialised. + */ + err = cyberpro_pci_enable_mmio(cfb); + if (err) + goto failed; + + /* + * Use MCLK from BIOS. FIXME: what about hotplug? + */ + cfb->mclk_mult = cyber2000_grphr(EXT_MCLK_MULT, cfb); + cfb->mclk_div = cyber2000_grphr(EXT_MCLK_DIV, cfb); + +#ifdef __arm__ + /* + * MCLK on the NetWinder and the Shark is fixed at 75MHz + */ + if (machine_is_netwinder()) { + cfb->mclk_mult = 0xdb; + cfb->mclk_div = 0x54; + } +#endif + + err = cyberpro_common_probe(cfb); + if (err) + goto failed; + + /* + * Our driver data + */ + pci_set_drvdata(dev, cfb); + if (int_cfb_info == NULL) + int_cfb_info = cfb; + + return 0; + +failed: + iounmap(cfb->region); +failed_ioremap: + pci_release_regions(dev); +failed_regions: + cyberpro_free_fb_info(cfb); +failed_release: + return err; +} + +static void cyberpro_pci_remove(struct pci_dev *dev) +{ + struct cfb_info *cfb = pci_get_drvdata(dev); + + if (cfb) { + cyberpro_common_remove(cfb); + iounmap(cfb->region); + cyberpro_free_fb_info(cfb); + + if (cfb == int_cfb_info) + int_cfb_info = NULL; + + pci_release_regions(dev); + } +} + +static int cyberpro_pci_suspend(struct pci_dev *dev, pm_message_t state) +{ + return 0; +} + +/* + * Re-initialise the CyberPro hardware + */ +static int cyberpro_pci_resume(struct pci_dev *dev) +{ + struct cfb_info *cfb = pci_get_drvdata(dev); + + if (cfb) { + cyberpro_pci_enable_mmio(cfb); + cyberpro_common_resume(cfb); + } + + return 0; +} + +static struct pci_device_id cyberpro_pci_table[] = { +/* Not yet + * { PCI_VENDOR_ID_INTERG, PCI_DEVICE_ID_INTERG_1682, + * PCI_ANY_ID, PCI_ANY_ID, 0, 0, ID_IGA_1682 }, + */ + { PCI_VENDOR_ID_INTERG, PCI_DEVICE_ID_INTERG_2000, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, ID_CYBERPRO_2000 }, + { PCI_VENDOR_ID_INTERG, PCI_DEVICE_ID_INTERG_2010, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, ID_CYBERPRO_2010 }, + { PCI_VENDOR_ID_INTERG, PCI_DEVICE_ID_INTERG_5000, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, ID_CYBERPRO_5000 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, cyberpro_pci_table); + +static struct pci_driver cyberpro_driver = { + .name = "CyberPro", + .probe = cyberpro_pci_probe, + .remove = cyberpro_pci_remove, + .suspend = cyberpro_pci_suspend, + .resume = cyberpro_pci_resume, + .id_table = cyberpro_pci_table +}; +#endif + +/* + * I don't think we can use the "module_init" stuff here because + * the fbcon stuff may not be initialised yet. Hence the #ifdef + * around module_init. + * + * Tony: "module_init" is now required + */ +static int __init cyber2000fb_init(void) +{ + int ret = -1, err; + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("cyber2000fb", &option)) + return -ENODEV; + cyber2000fb_setup(option); +#endif + + err = pci_register_driver(&cyberpro_driver); + if (!err) + ret = 0; + + return ret ? err : 0; +} +module_init(cyber2000fb_init); + +static void __exit cyberpro_exit(void) +{ + pci_unregister_driver(&cyberpro_driver); +} +module_exit(cyberpro_exit); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("CyberPro 2000, 2010 and 5000 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/cyber2000fb.h b/drivers/video/fbdev/cyber2000fb.h new file mode 100644 index 000000000000..bad69102e774 --- /dev/null +++ b/drivers/video/fbdev/cyber2000fb.h @@ -0,0 +1,497 @@ +/* + * linux/drivers/video/cyber2000fb.h + * + * Copyright (C) 1998-2000 Russell King + * + * 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. + * + * Integraphics Cyber2000 frame buffer device + */ + +/* + * Internal CyberPro sizes and offsets. + */ +#define MMIO_OFFSET 0x00800000 +#define MMIO_SIZE 0x000c0000 + +#define NR_PALETTE 256 + +#if defined(DEBUG) && defined(CONFIG_DEBUG_LL) +static void debug_printf(char *fmt, ...) +{ + extern void printascii(const char *); + char buffer[128]; + va_list ap; + + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + va_end(ap); + + printascii(buffer); +} +#else +#define debug_printf(x...) do { } while (0) +#endif + +#define RAMDAC_RAMPWRDN 0x01 +#define RAMDAC_DAC8BIT 0x02 +#define RAMDAC_VREFEN 0x04 +#define RAMDAC_BYPASS 0x10 +#define RAMDAC_DACPWRDN 0x40 + +#define EXT_CRT_VRTOFL 0x11 +#define EXT_CRT_VRTOFL_LINECOMP10 0x10 +#define EXT_CRT_VRTOFL_INTERLACE 0x20 + +#define EXT_CRT_IRQ 0x12 +#define EXT_CRT_IRQ_ENABLE 0x01 +#define EXT_CRT_IRQ_ACT_HIGH 0x04 + +#define EXT_CRT_TEST 0x13 + +#define EXT_SYNC_CTL 0x16 +#define EXT_SYNC_CTL_HS_NORMAL 0x00 +#define EXT_SYNC_CTL_HS_0 0x01 +#define EXT_SYNC_CTL_HS_1 0x02 +#define EXT_SYNC_CTL_HS_HSVS 0x03 +#define EXT_SYNC_CTL_VS_NORMAL 0x00 +#define EXT_SYNC_CTL_VS_0 0x04 +#define EXT_SYNC_CTL_VS_1 0x08 +#define EXT_SYNC_CTL_VS_COMP 0x0c + +#define EXT_BUS_CTL 0x30 +#define EXT_BUS_CTL_LIN_1MB 0x00 +#define EXT_BUS_CTL_LIN_2MB 0x01 +#define EXT_BUS_CTL_LIN_4MB 0x02 +#define EXT_BUS_CTL_ZEROWAIT 0x04 +#define EXT_BUS_CTL_PCIBURST_WRITE 0x20 +#define EXT_BUS_CTL_PCIBURST_READ 0x80 /* CyberPro 5000 only */ + +#define EXT_SEG_WRITE_PTR 0x31 +#define EXT_SEG_READ_PTR 0x32 +#define EXT_BIU_MISC 0x33 +#define EXT_BIU_MISC_LIN_ENABLE 0x01 +#define EXT_BIU_MISC_COP_ENABLE 0x04 +#define EXT_BIU_MISC_COP_BFC 0x08 + +#define EXT_FUNC_CTL 0x3c +#define EXT_FUNC_CTL_EXTREGENBL 0x80 /* enable access to 0xbcxxx */ + +#define PCI_BM_CTL 0x3e +#define PCI_BM_CTL_ENABLE 0x01 /* enable bus-master */ +#define PCI_BM_CTL_BURST 0x02 /* enable burst */ +#define PCI_BM_CTL_BACK2BACK 0x04 /* enable back to back */ +#define PCI_BM_CTL_DUMMY 0x08 /* insert dummy cycle */ + +#define X_V2_VID_MEM_START 0x40 +#define X_V2_VID_SRC_WIDTH 0x43 +#define X_V2_X_START 0x45 +#define X_V2_X_END 0x47 +#define X_V2_Y_START 0x49 +#define X_V2_Y_END 0x4b +#define X_V2_VID_SRC_WIN_WIDTH 0x4d + +#define Y_V2_DDA_X_INC 0x43 +#define Y_V2_DDA_Y_INC 0x47 +#define Y_V2_VID_FIFO_CTL 0x49 +#define Y_V2_VID_FMT 0x4b +#define Y_V2_VID_DISP_CTL1 0x4c +#define Y_V2_VID_FIFO_CTL1 0x4d + +#define J_X2_VID_MEM_START 0x40 +#define J_X2_VID_SRC_WIDTH 0x43 +#define J_X2_X_START 0x47 +#define J_X2_X_END 0x49 +#define J_X2_Y_START 0x4b +#define J_X2_Y_END 0x4d +#define J_X2_VID_SRC_WIN_WIDTH 0x4f + +#define K_X2_DDA_X_INIT 0x40 +#define K_X2_DDA_X_INC 0x42 +#define K_X2_DDA_Y_INIT 0x44 +#define K_X2_DDA_Y_INC 0x46 +#define K_X2_VID_FMT 0x48 +#define K_X2_VID_DISP_CTL1 0x49 + +#define K_CAP_X2_CTL1 0x49 + +#define CURS_H_START 0x50 +#define CURS_H_PRESET 0x52 +#define CURS_V_START 0x53 +#define CURS_V_PRESET 0x55 +#define CURS_CTL 0x56 + +#define EXT_ATTRIB_CTL 0x57 +#define EXT_ATTRIB_CTL_EXT 0x01 + +#define EXT_OVERSCAN_RED 0x58 +#define EXT_OVERSCAN_GREEN 0x59 +#define EXT_OVERSCAN_BLUE 0x5a + +#define CAP_X_START 0x60 +#define CAP_X_END 0x62 +#define CAP_Y_START 0x64 +#define CAP_Y_END 0x66 +#define CAP_DDA_X_INIT 0x68 +#define CAP_DDA_X_INC 0x6a +#define CAP_DDA_Y_INIT 0x6c +#define CAP_DDA_Y_INC 0x6e + +#define EXT_MEM_CTL0 0x70 +#define EXT_MEM_CTL0_7CLK 0x01 +#define EXT_MEM_CTL0_RAS_1 0x02 +#define EXT_MEM_CTL0_RAS2CAS_1 0x04 +#define EXT_MEM_CTL0_MULTCAS 0x08 +#define EXT_MEM_CTL0_ASYM 0x10 +#define EXT_MEM_CTL0_CAS1ON 0x20 +#define EXT_MEM_CTL0_FIFOFLUSH 0x40 +#define EXT_MEM_CTL0_SEQRESET 0x80 + +#define EXT_MEM_CTL1 0x71 +#define EXT_MEM_CTL1_PAR 0x00 +#define EXT_MEM_CTL1_SERPAR 0x01 +#define EXT_MEM_CTL1_SER 0x03 +#define EXT_MEM_CTL1_SYNC 0x04 +#define EXT_MEM_CTL1_VRAM 0x08 +#define EXT_MEM_CTL1_4K_REFRESH 0x10 +#define EXT_MEM_CTL1_256Kx4 0x00 +#define EXT_MEM_CTL1_512Kx8 0x40 +#define EXT_MEM_CTL1_1Mx16 0x60 + +#define EXT_MEM_CTL2 0x72 +#define MEM_CTL2_SIZE_1MB 0x00 +#define MEM_CTL2_SIZE_2MB 0x01 +#define MEM_CTL2_SIZE_4MB 0x02 +#define MEM_CTL2_SIZE_MASK 0x03 +#define MEM_CTL2_64BIT 0x04 + +#define EXT_HIDDEN_CTL1 0x73 + +#define EXT_FIFO_CTL 0x74 + +#define EXT_SEQ_MISC 0x77 +#define EXT_SEQ_MISC_8 0x01 +#define EXT_SEQ_MISC_16_RGB565 0x02 +#define EXT_SEQ_MISC_32 0x03 +#define EXT_SEQ_MISC_24_RGB888 0x04 +#define EXT_SEQ_MISC_16_RGB555 0x06 +#define EXT_SEQ_MISC_8_RGB332 0x09 +#define EXT_SEQ_MISC_16_RGB444 0x0a + +#define EXT_HIDDEN_CTL4 0x7a + +#define CURS_MEM_START 0x7e /* bits 23..12 */ + +#define CAP_PIP_X_START 0x80 +#define CAP_PIP_X_END 0x82 +#define CAP_PIP_Y_START 0x84 +#define CAP_PIP_Y_END 0x86 + +#define EXT_CAP_CTL1 0x88 + +#define EXT_CAP_CTL2 0x89 +#define EXT_CAP_CTL2_ODDFRAMEIRQ 0x01 +#define EXT_CAP_CTL2_ANYFRAMEIRQ 0x02 + +#define BM_CTRL0 0x9c +#define BM_CTRL1 0x9d + +#define EXT_CAP_MODE1 0xa4 +#define EXT_CAP_MODE1_8BIT 0x01 /* enable 8bit capture mode */ +#define EXT_CAP_MODE1_CCIR656 0x02 /* CCIR656 mode */ +#define EXT_CAP_MODE1_IGNOREVGT 0x04 /* ignore VGT */ +#define EXT_CAP_MODE1_ALTFIFO 0x10 /* use alternate FIFO for capture */ +#define EXT_CAP_MODE1_SWAPUV 0x20 /* swap UV bytes */ +#define EXT_CAP_MODE1_MIRRORY 0x40 /* mirror vertically */ +#define EXT_CAP_MODE1_MIRRORX 0x80 /* mirror horizontally */ + +#define EXT_CAP_MODE2 0xa5 +#define EXT_CAP_MODE2_CCIRINVOE 0x01 +#define EXT_CAP_MODE2_CCIRINVVGT 0x02 +#define EXT_CAP_MODE2_CCIRINVHGT 0x04 +#define EXT_CAP_MODE2_CCIRINVDG 0x08 +#define EXT_CAP_MODE2_DATEND 0x10 +#define EXT_CAP_MODE2_CCIRDGH 0x20 +#define EXT_CAP_MODE2_FIXSONY 0x40 +#define EXT_CAP_MODE2_SYNCFREEZE 0x80 + +#define EXT_TV_CTL 0xae + +#define EXT_DCLK_MULT 0xb0 +#define EXT_DCLK_DIV 0xb1 +#define EXT_DCLK_DIV_VFSEL 0x20 +#define EXT_MCLK_MULT 0xb2 +#define EXT_MCLK_DIV 0xb3 + +#define EXT_LATCH1 0xb5 +#define EXT_LATCH1_VAFC_EN 0x01 /* enable VAFC */ + +#define EXT_FEATURE 0xb7 +#define EXT_FEATURE_BUS_MASK 0x07 /* host bus mask */ +#define EXT_FEATURE_BUS_PCI 0x00 +#define EXT_FEATURE_BUS_VL_STD 0x04 +#define EXT_FEATURE_BUS_VL_LINEAR 0x05 +#define EXT_FEATURE_1682 0x20 /* IGS 1682 compatibility */ + +#define EXT_LATCH2 0xb6 +#define EXT_LATCH2_I2C_CLKEN 0x10 +#define EXT_LATCH2_I2C_CLK 0x20 +#define EXT_LATCH2_I2C_DATEN 0x40 +#define EXT_LATCH2_I2C_DAT 0x80 + +#define EXT_XT_CTL 0xbe +#define EXT_XT_CAP16 0x04 +#define EXT_XT_LINEARFB 0x08 +#define EXT_XT_PAL 0x10 + +#define EXT_MEM_START 0xc0 /* ext start address 21 bits */ +#define HOR_PHASE_SHIFT 0xc2 /* high 3 bits */ +#define EXT_SRC_WIDTH 0xc3 /* ext offset phase 10 bits */ +#define EXT_SRC_HEIGHT 0xc4 /* high 6 bits */ +#define EXT_X_START 0xc5 /* ext->screen, 16 bits */ +#define EXT_X_END 0xc7 /* ext->screen, 16 bits */ +#define EXT_Y_START 0xc9 /* ext->screen, 16 bits */ +#define EXT_Y_END 0xcb /* ext->screen, 16 bits */ +#define EXT_SRC_WIN_WIDTH 0xcd /* 8 bits */ +#define EXT_COLOUR_COMPARE 0xce /* 24 bits */ +#define EXT_DDA_X_INIT 0xd1 /* ext->screen 16 bits */ +#define EXT_DDA_X_INC 0xd3 /* ext->screen 16 bits */ +#define EXT_DDA_Y_INIT 0xd5 /* ext->screen 16 bits */ +#define EXT_DDA_Y_INC 0xd7 /* ext->screen 16 bits */ + +#define EXT_VID_FIFO_CTL 0xd9 + +#define EXT_VID_FMT 0xdb +#define EXT_VID_FMT_YUV422 0x00 /* formats - does this cause conversion? */ +#define EXT_VID_FMT_RGB555 0x01 +#define EXT_VID_FMT_RGB565 0x02 +#define EXT_VID_FMT_RGB888_24 0x03 +#define EXT_VID_FMT_RGB888_32 0x04 +#define EXT_VID_FMT_RGB8 0x05 +#define EXT_VID_FMT_RGB4444 0x06 +#define EXT_VID_FMT_RGB8T 0x07 +#define EXT_VID_FMT_DUP_PIX_ZOON 0x08 /* duplicate pixel zoom */ +#define EXT_VID_FMT_MOD_3RD_PIX 0x20 /* modify 3rd duplicated pixel */ +#define EXT_VID_FMT_DBL_H_PIX 0x40 /* double horiz pixels */ +#define EXT_VID_FMT_YUV128 0x80 /* YUV data offset by 128 */ + +#define EXT_VID_DISP_CTL1 0xdc +#define EXT_VID_DISP_CTL1_INTRAM 0x01 /* video pixels go to internal RAM */ +#define EXT_VID_DISP_CTL1_IGNORE_CCOMP 0x02 /* ignore colour compare registers */ +#define EXT_VID_DISP_CTL1_NOCLIP 0x04 /* do not clip to 16235,16240 */ +#define EXT_VID_DISP_CTL1_UV_AVG 0x08 /* U/V data is averaged */ +#define EXT_VID_DISP_CTL1_Y128 0x10 /* Y data offset by 128 (if YUV128 set) */ +#define EXT_VID_DISP_CTL1_VINTERPOL_OFF 0x20 /* disable vertical interpolation */ +#define EXT_VID_DISP_CTL1_FULL_WIN 0x40 /* video out window full */ +#define EXT_VID_DISP_CTL1_ENABLE_WINDOW 0x80 /* enable video window */ + +#define EXT_VID_FIFO_CTL1 0xdd +#define EXT_VID_FIFO_CTL1_OE_HIGH 0x02 +#define EXT_VID_FIFO_CTL1_INTERLEAVE 0x04 /* enable interleaved memory read */ + +#define EXT_ROM_UCB4GH 0xe5 +#define EXT_ROM_UCB4GH_FREEZE 0x02 /* capture frozen */ +#define EXT_ROM_UCB4GH_ODDFRAME 0x04 /* 1 = odd frame captured */ +#define EXT_ROM_UCB4GH_1HL 0x08 /* first horizonal line after VGT falling edge */ +#define EXT_ROM_UCB4GH_ODD 0x10 /* odd frame indicator */ +#define EXT_ROM_UCB4GH_INTSTAT 0x20 /* video interrupt */ + +#define VFAC_CTL1 0xe8 +#define VFAC_CTL1_CAPTURE 0x01 /* capture enable (only when VSYNC high)*/ +#define VFAC_CTL1_VFAC_ENABLE 0x02 /* vfac enable */ +#define VFAC_CTL1_FREEZE_CAPTURE 0x04 /* freeze capture */ +#define VFAC_CTL1_FREEZE_CAPTURE_SYNC 0x08 /* sync freeze capture */ +#define VFAC_CTL1_VALIDFRAME_SRC 0x10 /* select valid frame source */ +#define VFAC_CTL1_PHILIPS 0x40 /* select Philips mode */ +#define VFAC_CTL1_MODVINTERPOLCLK 0x80 /* modify vertical interpolation clocl */ + +#define VFAC_CTL2 0xe9 +#define VFAC_CTL2_INVERT_VIDDATAVALID 0x01 /* invert video data valid */ +#define VFAC_CTL2_INVERT_GRAPHREADY 0x02 /* invert graphic ready output sig */ +#define VFAC_CTL2_INVERT_DATACLK 0x04 /* invert data clock signal */ +#define VFAC_CTL2_INVERT_HSYNC 0x08 /* invert hsync input */ +#define VFAC_CTL2_INVERT_VSYNC 0x10 /* invert vsync input */ +#define VFAC_CTL2_INVERT_FRAME 0x20 /* invert frame odd/even input */ +#define VFAC_CTL2_INVERT_BLANK 0x40 /* invert blank output */ +#define VFAC_CTL2_INVERT_OVSYNC 0x80 /* invert other vsync input */ + +#define VFAC_CTL3 0xea +#define VFAC_CTL3_CAP_LARGE_FIFO 0x01 /* large capture fifo */ +#define VFAC_CTL3_CAP_INTERLACE 0x02 /* capture odd and even fields */ +#define VFAC_CTL3_CAP_HOLD_4NS 0x00 /* hold capture data for 4ns */ +#define VFAC_CTL3_CAP_HOLD_2NS 0x04 /* hold capture data for 2ns */ +#define VFAC_CTL3_CAP_HOLD_6NS 0x08 /* hold capture data for 6ns */ +#define VFAC_CTL3_CAP_HOLD_0NS 0x0c /* hold capture data for 0ns */ +#define VFAC_CTL3_CHROMAKEY 0x20 /* capture data will be chromakeyed */ +#define VFAC_CTL3_CAP_IRQ 0x40 /* enable capture interrupt */ + +#define CAP_MEM_START 0xeb /* 18 bits */ +#define CAP_MAP_WIDTH 0xed /* high 6 bits */ +#define CAP_PITCH 0xee /* 8 bits */ + +#define CAP_CTL_MISC 0xef +#define CAP_CTL_MISC_HDIV 0x01 +#define CAP_CTL_MISC_HDIV4 0x02 +#define CAP_CTL_MISC_ODDEVEN 0x04 +#define CAP_CTL_MISC_HSYNCDIV2 0x08 +#define CAP_CTL_MISC_SYNCTZHIGH 0x10 +#define CAP_CTL_MISC_SYNCTZOR 0x20 +#define CAP_CTL_MISC_DISPUSED 0x80 + +#define REG_BANK 0xfa +#define REG_BANK_X 0x00 +#define REG_BANK_Y 0x01 +#define REG_BANK_W 0x02 +#define REG_BANK_T 0x03 +#define REG_BANK_J 0x04 +#define REG_BANK_K 0x05 + +/* + * Bus-master + */ +#define BM_VID_ADDR_LOW 0xbc040 +#define BM_VID_ADDR_HIGH 0xbc044 +#define BM_ADDRESS_LOW 0xbc080 +#define BM_ADDRESS_HIGH 0xbc084 +#define BM_LENGTH 0xbc088 +#define BM_CONTROL 0xbc08c +#define BM_CONTROL_ENABLE 0x01 /* enable transfer */ +#define BM_CONTROL_IRQEN 0x02 /* enable IRQ at end of transfer */ +#define BM_CONTROL_INIT 0x04 /* initialise status & count */ +#define BM_COUNT 0xbc090 /* read-only */ + +/* + * TV registers + */ +#define TV_VBLANK_EVEN_START 0xbe43c +#define TV_VBLANK_EVEN_END 0xbe440 +#define TV_VBLANK_ODD_START 0xbe444 +#define TV_VBLANK_ODD_END 0xbe448 +#define TV_SYNC_YGAIN 0xbe44c +#define TV_UV_GAIN 0xbe450 +#define TV_PED_UVDET 0xbe454 +#define TV_UV_BURST_AMP 0xbe458 +#define TV_HSYNC_START 0xbe45c +#define TV_HSYNC_END 0xbe460 +#define TV_Y_DELAY1 0xbe464 +#define TV_Y_DELAY2 0xbe468 +#define TV_UV_DELAY1 0xbe46c +#define TV_BURST_START 0xbe470 +#define TV_BURST_END 0xbe474 +#define TV_HBLANK_START 0xbe478 +#define TV_HBLANK_END 0xbe47c +#define TV_PED_EVEN_START 0xbe480 +#define TV_PED_EVEN_END 0xbe484 +#define TV_PED_ODD_START 0xbe488 +#define TV_PED_ODD_END 0xbe48c +#define TV_VSYNC_EVEN_START 0xbe490 +#define TV_VSYNC_EVEN_END 0xbe494 +#define TV_VSYNC_ODD_START 0xbe498 +#define TV_VSYNC_ODD_END 0xbe49c +#define TV_SCFL 0xbe4a0 +#define TV_SCFH 0xbe4a4 +#define TV_SCP 0xbe4a8 +#define TV_DELAYBYPASS 0xbe4b4 +#define TV_EQL_END 0xbe4bc +#define TV_SERR_START 0xbe4c0 +#define TV_SERR_END 0xbe4c4 +#define TV_CTL 0xbe4dc /* reflects a previous register- MVFCLR, MVPCLR etc P241*/ +#define TV_VSYNC_VGA_HS 0xbe4e8 +#define TV_FLICK_XMIN 0xbe514 +#define TV_FLICK_XMAX 0xbe518 +#define TV_FLICK_YMIN 0xbe51c +#define TV_FLICK_YMAX 0xbe520 + +/* + * Graphics Co-processor + */ +#define CO_REG_CONTROL 0xbf011 +#define CO_CTRL_BUSY 0x80 +#define CO_CTRL_CMDFULL 0x04 +#define CO_CTRL_FIFOEMPTY 0x02 +#define CO_CTRL_READY 0x01 + +#define CO_REG_SRC_WIDTH 0xbf018 +#define CO_REG_PIXFMT 0xbf01c +#define CO_PIXFMT_32BPP 0x03 +#define CO_PIXFMT_24BPP 0x02 +#define CO_PIXFMT_16BPP 0x01 +#define CO_PIXFMT_8BPP 0x00 + +#define CO_REG_FGMIX 0xbf048 +#define CO_FG_MIX_ZERO 0x00 +#define CO_FG_MIX_SRC_AND_DST 0x01 +#define CO_FG_MIX_SRC_AND_NDST 0x02 +#define CO_FG_MIX_SRC 0x03 +#define CO_FG_MIX_NSRC_AND_DST 0x04 +#define CO_FG_MIX_DST 0x05 +#define CO_FG_MIX_SRC_XOR_DST 0x06 +#define CO_FG_MIX_SRC_OR_DST 0x07 +#define CO_FG_MIX_NSRC_AND_NDST 0x08 +#define CO_FG_MIX_SRC_XOR_NDST 0x09 +#define CO_FG_MIX_NDST 0x0a +#define CO_FG_MIX_SRC_OR_NDST 0x0b +#define CO_FG_MIX_NSRC 0x0c +#define CO_FG_MIX_NSRC_OR_DST 0x0d +#define CO_FG_MIX_NSRC_OR_NDST 0x0e +#define CO_FG_MIX_ONES 0x0f + +#define CO_REG_FGCOLOUR 0xbf058 +#define CO_REG_BGCOLOUR 0xbf05c +#define CO_REG_PIXWIDTH 0xbf060 +#define CO_REG_PIXHEIGHT 0xbf062 +#define CO_REG_X_PHASE 0xbf078 +#define CO_REG_CMD_L 0xbf07c +#define CO_CMD_L_PATTERN_FGCOL 0x8000 +#define CO_CMD_L_INC_LEFT 0x0004 +#define CO_CMD_L_INC_UP 0x0002 + +#define CO_REG_CMD_H 0xbf07e +#define CO_CMD_H_BGSRCMAP 0x8000 /* otherwise bg colour */ +#define CO_CMD_H_FGSRCMAP 0x2000 /* otherwise fg colour */ +#define CO_CMD_H_BLITTER 0x0800 + +#define CO_REG_SRC1_PTR 0xbf170 +#define CO_REG_SRC2_PTR 0xbf174 +#define CO_REG_DEST_PTR 0xbf178 +#define CO_REG_DEST_WIDTH 0xbf218 + +/* + * Private structure + */ +struct cfb_info; + +struct cyberpro_info { + struct device *dev; + struct i2c_adapter *i2c; + unsigned char __iomem *regs; + char __iomem *fb; + char dev_name[32]; + unsigned int fb_size; + unsigned int chip_id; + unsigned int irq; + + /* + * The following is a pointer to be passed into the + * functions below. The modules outside the main + * cyber2000fb.c driver have no knowledge as to what + * is within this structure. + */ + struct cfb_info *info; +}; + +#define ID_IGA_1682 0 +#define ID_CYBERPRO_2000 1 +#define ID_CYBERPRO_2010 2 +#define ID_CYBERPRO_5000 3 + +/* + * Note! Writing to the Cyber20x0 registers from an interrupt + * routine is definitely a bad idea atm. + */ +int cyber2000fb_attach(struct cyberpro_info *info, int idx); +void cyber2000fb_detach(int idx); +void cyber2000fb_enable_extregs(struct cfb_info *cfb); +void cyber2000fb_disable_extregs(struct cfb_info *cfb); diff --git a/drivers/video/fbdev/da8xx-fb.c b/drivers/video/fbdev/da8xx-fb.c new file mode 100644 index 000000000000..6b23508ff0a5 --- /dev/null +++ b/drivers/video/fbdev/da8xx-fb.c @@ -0,0 +1,1659 @@ +/* + * Copyright (C) 2008-2009 MontaVista Software Inc. + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * Based on the LCD driver for TI Avalanche processors written by + * Ajay Singh and Shalom Hai. + * + * 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/fb.h> +#include <linux/dma-mapping.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/pm_runtime.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/console.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/lcm.h> +#include <video/da8xx-fb.h> +#include <asm/div64.h> + +#define DRIVER_NAME "da8xx_lcdc" + +#define LCD_VERSION_1 1 +#define LCD_VERSION_2 2 + +/* LCD Status Register */ +#define LCD_END_OF_FRAME1 BIT(9) +#define LCD_END_OF_FRAME0 BIT(8) +#define LCD_PL_LOAD_DONE BIT(6) +#define LCD_FIFO_UNDERFLOW BIT(5) +#define LCD_SYNC_LOST BIT(2) +#define LCD_FRAME_DONE BIT(0) + +/* LCD DMA Control Register */ +#define LCD_DMA_BURST_SIZE(x) ((x) << 4) +#define LCD_DMA_BURST_1 0x0 +#define LCD_DMA_BURST_2 0x1 +#define LCD_DMA_BURST_4 0x2 +#define LCD_DMA_BURST_8 0x3 +#define LCD_DMA_BURST_16 0x4 +#define LCD_V1_END_OF_FRAME_INT_ENA BIT(2) +#define LCD_V2_END_OF_FRAME0_INT_ENA BIT(8) +#define LCD_V2_END_OF_FRAME1_INT_ENA BIT(9) +#define LCD_DUAL_FRAME_BUFFER_ENABLE BIT(0) + +/* LCD Control Register */ +#define LCD_CLK_DIVISOR(x) ((x) << 8) +#define LCD_RASTER_MODE 0x01 + +/* LCD Raster Control Register */ +#define LCD_PALETTE_LOAD_MODE(x) ((x) << 20) +#define PALETTE_AND_DATA 0x00 +#define PALETTE_ONLY 0x01 +#define DATA_ONLY 0x02 + +#define LCD_MONO_8BIT_MODE BIT(9) +#define LCD_RASTER_ORDER BIT(8) +#define LCD_TFT_MODE BIT(7) +#define LCD_V1_UNDERFLOW_INT_ENA BIT(6) +#define LCD_V2_UNDERFLOW_INT_ENA BIT(5) +#define LCD_V1_PL_INT_ENA BIT(4) +#define LCD_V2_PL_INT_ENA BIT(6) +#define LCD_MONOCHROME_MODE BIT(1) +#define LCD_RASTER_ENABLE BIT(0) +#define LCD_TFT_ALT_ENABLE BIT(23) +#define LCD_STN_565_ENABLE BIT(24) +#define LCD_V2_DMA_CLK_EN BIT(2) +#define LCD_V2_LIDD_CLK_EN BIT(1) +#define LCD_V2_CORE_CLK_EN BIT(0) +#define LCD_V2_LPP_B10 26 +#define LCD_V2_TFT_24BPP_MODE BIT(25) +#define LCD_V2_TFT_24BPP_UNPACK BIT(26) + +/* LCD Raster Timing 2 Register */ +#define LCD_AC_BIAS_TRANSITIONS_PER_INT(x) ((x) << 16) +#define LCD_AC_BIAS_FREQUENCY(x) ((x) << 8) +#define LCD_SYNC_CTRL BIT(25) +#define LCD_SYNC_EDGE BIT(24) +#define LCD_INVERT_PIXEL_CLOCK BIT(22) +#define LCD_INVERT_LINE_CLOCK BIT(21) +#define LCD_INVERT_FRAME_CLOCK BIT(20) + +/* LCD Block */ +#define LCD_PID_REG 0x0 +#define LCD_CTRL_REG 0x4 +#define LCD_STAT_REG 0x8 +#define LCD_RASTER_CTRL_REG 0x28 +#define LCD_RASTER_TIMING_0_REG 0x2C +#define LCD_RASTER_TIMING_1_REG 0x30 +#define LCD_RASTER_TIMING_2_REG 0x34 +#define LCD_DMA_CTRL_REG 0x40 +#define LCD_DMA_FRM_BUF_BASE_ADDR_0_REG 0x44 +#define LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG 0x48 +#define LCD_DMA_FRM_BUF_BASE_ADDR_1_REG 0x4C +#define LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG 0x50 + +/* Interrupt Registers available only in Version 2 */ +#define LCD_RAW_STAT_REG 0x58 +#define LCD_MASKED_STAT_REG 0x5c +#define LCD_INT_ENABLE_SET_REG 0x60 +#define LCD_INT_ENABLE_CLR_REG 0x64 +#define LCD_END_OF_INT_IND_REG 0x68 + +/* Clock registers available only on Version 2 */ +#define LCD_CLK_ENABLE_REG 0x6c +#define LCD_CLK_RESET_REG 0x70 +#define LCD_CLK_MAIN_RESET BIT(3) + +#define LCD_NUM_BUFFERS 2 + +#define PALETTE_SIZE 256 + +#define CLK_MIN_DIV 2 +#define CLK_MAX_DIV 255 + +static void __iomem *da8xx_fb_reg_base; +static unsigned int lcd_revision; +static irq_handler_t lcdc_irq_handler; +static wait_queue_head_t frame_done_wq; +static int frame_done_flag; + +static unsigned int lcdc_read(unsigned int addr) +{ + return (unsigned int)__raw_readl(da8xx_fb_reg_base + (addr)); +} + +static void lcdc_write(unsigned int val, unsigned int addr) +{ + __raw_writel(val, da8xx_fb_reg_base + (addr)); +} + +struct da8xx_fb_par { + struct device *dev; + resource_size_t p_palette_base; + unsigned char *v_palette_base; + dma_addr_t vram_phys; + unsigned long vram_size; + void *vram_virt; + unsigned int dma_start; + unsigned int dma_end; + struct clk *lcdc_clk; + int irq; + unsigned int palette_sz; + int blank; + wait_queue_head_t vsync_wait; + int vsync_flag; + int vsync_timeout; + spinlock_t lock_for_chan_update; + + /* + * LCDC has 2 ping pong DMA channels, channel 0 + * and channel 1. + */ + unsigned int which_dma_channel_done; +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; +#endif + unsigned int lcdc_clk_rate; + void (*panel_power_ctrl)(int); + u32 pseudo_palette[16]; + struct fb_videomode mode; + struct lcd_ctrl_config cfg; +}; + +static struct fb_var_screeninfo da8xx_fb_var; + +static struct fb_fix_screeninfo da8xx_fb_fix = { + .id = "DA8xx FB Drv", + .type = FB_TYPE_PACKED_PIXELS, + .type_aux = 0, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 0, + .ypanstep = 1, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE +}; + +static struct fb_videomode known_lcd_panels[] = { + /* Sharp LCD035Q3DG01 */ + [0] = { + .name = "Sharp_LCD035Q3DG01", + .xres = 320, + .yres = 240, + .pixclock = KHZ2PICOS(4607), + .left_margin = 6, + .right_margin = 8, + .upper_margin = 2, + .lower_margin = 2, + .hsync_len = 0, + .vsync_len = 0, + .sync = FB_SYNC_CLK_INVERT | + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + }, + /* Sharp LK043T1DG01 */ + [1] = { + .name = "Sharp_LK043T1DG01", + .xres = 480, + .yres = 272, + .pixclock = KHZ2PICOS(7833), + .left_margin = 2, + .right_margin = 2, + .upper_margin = 2, + .lower_margin = 2, + .hsync_len = 41, + .vsync_len = 10, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .flag = 0, + }, + [2] = { + /* Hitachi SP10Q010 */ + .name = "SP10Q010", + .xres = 320, + .yres = 240, + .pixclock = KHZ2PICOS(7833), + .left_margin = 10, + .right_margin = 10, + .upper_margin = 10, + .lower_margin = 10, + .hsync_len = 10, + .vsync_len = 10, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .flag = 0, + }, +}; + +static bool da8xx_fb_is_raster_enabled(void) +{ + return !!(lcdc_read(LCD_RASTER_CTRL_REG) & LCD_RASTER_ENABLE); +} + +/* Enable the Raster Engine of the LCD Controller */ +static void lcd_enable_raster(void) +{ + u32 reg; + + /* Put LCDC in reset for several cycles */ + if (lcd_revision == LCD_VERSION_2) + /* Write 1 to reset LCDC */ + lcdc_write(LCD_CLK_MAIN_RESET, LCD_CLK_RESET_REG); + mdelay(1); + + /* Bring LCDC out of reset */ + if (lcd_revision == LCD_VERSION_2) + lcdc_write(0, LCD_CLK_RESET_REG); + mdelay(1); + + /* Above reset sequence doesnot reset register context */ + reg = lcdc_read(LCD_RASTER_CTRL_REG); + if (!(reg & LCD_RASTER_ENABLE)) + lcdc_write(reg | LCD_RASTER_ENABLE, LCD_RASTER_CTRL_REG); +} + +/* Disable the Raster Engine of the LCD Controller */ +static void lcd_disable_raster(enum da8xx_frame_complete wait_for_frame_done) +{ + u32 reg; + int ret; + + reg = lcdc_read(LCD_RASTER_CTRL_REG); + if (reg & LCD_RASTER_ENABLE) + lcdc_write(reg & ~LCD_RASTER_ENABLE, LCD_RASTER_CTRL_REG); + else + /* return if already disabled */ + return; + + if ((wait_for_frame_done == DA8XX_FRAME_WAIT) && + (lcd_revision == LCD_VERSION_2)) { + frame_done_flag = 0; + ret = wait_event_interruptible_timeout(frame_done_wq, + frame_done_flag != 0, + msecs_to_jiffies(50)); + if (ret == 0) + pr_err("LCD Controller timed out\n"); + } +} + +static void lcd_blit(int load_mode, struct da8xx_fb_par *par) +{ + u32 start; + u32 end; + u32 reg_ras; + u32 reg_dma; + u32 reg_int; + + /* init reg to clear PLM (loading mode) fields */ + reg_ras = lcdc_read(LCD_RASTER_CTRL_REG); + reg_ras &= ~(3 << 20); + + reg_dma = lcdc_read(LCD_DMA_CTRL_REG); + + if (load_mode == LOAD_DATA) { + start = par->dma_start; + end = par->dma_end; + + reg_ras |= LCD_PALETTE_LOAD_MODE(DATA_ONLY); + if (lcd_revision == LCD_VERSION_1) { + reg_dma |= LCD_V1_END_OF_FRAME_INT_ENA; + } else { + reg_int = lcdc_read(LCD_INT_ENABLE_SET_REG) | + LCD_V2_END_OF_FRAME0_INT_ENA | + LCD_V2_END_OF_FRAME1_INT_ENA | + LCD_FRAME_DONE | LCD_SYNC_LOST; + lcdc_write(reg_int, LCD_INT_ENABLE_SET_REG); + } + reg_dma |= LCD_DUAL_FRAME_BUFFER_ENABLE; + + lcdc_write(start, LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + lcdc_write(end, LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + lcdc_write(start, LCD_DMA_FRM_BUF_BASE_ADDR_1_REG); + lcdc_write(end, LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG); + } else if (load_mode == LOAD_PALETTE) { + start = par->p_palette_base; + end = start + par->palette_sz - 1; + + reg_ras |= LCD_PALETTE_LOAD_MODE(PALETTE_ONLY); + + if (lcd_revision == LCD_VERSION_1) { + reg_ras |= LCD_V1_PL_INT_ENA; + } else { + reg_int = lcdc_read(LCD_INT_ENABLE_SET_REG) | + LCD_V2_PL_INT_ENA; + lcdc_write(reg_int, LCD_INT_ENABLE_SET_REG); + } + + lcdc_write(start, LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + lcdc_write(end, LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + } + + lcdc_write(reg_dma, LCD_DMA_CTRL_REG); + lcdc_write(reg_ras, LCD_RASTER_CTRL_REG); + + /* + * The Raster enable bit must be set after all other control fields are + * set. + */ + lcd_enable_raster(); +} + +/* Configure the Burst Size and fifo threhold of DMA */ +static int lcd_cfg_dma(int burst_size, int fifo_th) +{ + u32 reg; + + reg = lcdc_read(LCD_DMA_CTRL_REG) & 0x00000001; + switch (burst_size) { + case 1: + reg |= LCD_DMA_BURST_SIZE(LCD_DMA_BURST_1); + break; + case 2: + reg |= LCD_DMA_BURST_SIZE(LCD_DMA_BURST_2); + break; + case 4: + reg |= LCD_DMA_BURST_SIZE(LCD_DMA_BURST_4); + break; + case 8: + reg |= LCD_DMA_BURST_SIZE(LCD_DMA_BURST_8); + break; + case 16: + default: + reg |= LCD_DMA_BURST_SIZE(LCD_DMA_BURST_16); + break; + } + + reg |= (fifo_th << 8); + + lcdc_write(reg, LCD_DMA_CTRL_REG); + + return 0; +} + +static void lcd_cfg_ac_bias(int period, int transitions_per_int) +{ + u32 reg; + + /* Set the AC Bias Period and Number of Transisitons per Interrupt */ + reg = lcdc_read(LCD_RASTER_TIMING_2_REG) & 0xFFF00000; + reg |= LCD_AC_BIAS_FREQUENCY(period) | + LCD_AC_BIAS_TRANSITIONS_PER_INT(transitions_per_int); + lcdc_write(reg, LCD_RASTER_TIMING_2_REG); +} + +static void lcd_cfg_horizontal_sync(int back_porch, int pulse_width, + int front_porch) +{ + u32 reg; + + reg = lcdc_read(LCD_RASTER_TIMING_0_REG) & 0xf; + reg |= (((back_porch-1) & 0xff) << 24) + | (((front_porch-1) & 0xff) << 16) + | (((pulse_width-1) & 0x3f) << 10); + lcdc_write(reg, LCD_RASTER_TIMING_0_REG); + + /* + * LCDC Version 2 adds some extra bits that increase the allowable + * size of the horizontal timing registers. + * remember that the registers use 0 to represent 1 so all values + * that get set into register need to be decremented by 1 + */ + if (lcd_revision == LCD_VERSION_2) { + /* Mask off the bits we want to change */ + reg = lcdc_read(LCD_RASTER_TIMING_2_REG) & ~0x780000ff; + reg |= ((front_porch-1) & 0x300) >> 8; + reg |= ((back_porch-1) & 0x300) >> 4; + reg |= ((pulse_width-1) & 0x3c0) << 21; + lcdc_write(reg, LCD_RASTER_TIMING_2_REG); + } +} + +static void lcd_cfg_vertical_sync(int back_porch, int pulse_width, + int front_porch) +{ + u32 reg; + + reg = lcdc_read(LCD_RASTER_TIMING_1_REG) & 0x3ff; + reg |= ((back_porch & 0xff) << 24) + | ((front_porch & 0xff) << 16) + | (((pulse_width-1) & 0x3f) << 10); + lcdc_write(reg, LCD_RASTER_TIMING_1_REG); +} + +static int lcd_cfg_display(const struct lcd_ctrl_config *cfg, + struct fb_videomode *panel) +{ + u32 reg; + u32 reg_int; + + reg = lcdc_read(LCD_RASTER_CTRL_REG) & ~(LCD_TFT_MODE | + LCD_MONO_8BIT_MODE | + LCD_MONOCHROME_MODE); + + switch (cfg->panel_shade) { + case MONOCHROME: + reg |= LCD_MONOCHROME_MODE; + if (cfg->mono_8bit_mode) + reg |= LCD_MONO_8BIT_MODE; + break; + case COLOR_ACTIVE: + reg |= LCD_TFT_MODE; + if (cfg->tft_alt_mode) + reg |= LCD_TFT_ALT_ENABLE; + break; + + case COLOR_PASSIVE: + /* AC bias applicable only for Pasive panels */ + lcd_cfg_ac_bias(cfg->ac_bias, cfg->ac_bias_intrpt); + if (cfg->bpp == 12 && cfg->stn_565_mode) + reg |= LCD_STN_565_ENABLE; + break; + + default: + return -EINVAL; + } + + /* enable additional interrupts here */ + if (lcd_revision == LCD_VERSION_1) { + reg |= LCD_V1_UNDERFLOW_INT_ENA; + } else { + reg_int = lcdc_read(LCD_INT_ENABLE_SET_REG) | + LCD_V2_UNDERFLOW_INT_ENA; + lcdc_write(reg_int, LCD_INT_ENABLE_SET_REG); + } + + lcdc_write(reg, LCD_RASTER_CTRL_REG); + + reg = lcdc_read(LCD_RASTER_TIMING_2_REG); + + reg |= LCD_SYNC_CTRL; + + if (cfg->sync_edge) + reg |= LCD_SYNC_EDGE; + else + reg &= ~LCD_SYNC_EDGE; + + if ((panel->sync & FB_SYNC_HOR_HIGH_ACT) == 0) + reg |= LCD_INVERT_LINE_CLOCK; + else + reg &= ~LCD_INVERT_LINE_CLOCK; + + if ((panel->sync & FB_SYNC_VERT_HIGH_ACT) == 0) + reg |= LCD_INVERT_FRAME_CLOCK; + else + reg &= ~LCD_INVERT_FRAME_CLOCK; + + lcdc_write(reg, LCD_RASTER_TIMING_2_REG); + + return 0; +} + +static int lcd_cfg_frame_buffer(struct da8xx_fb_par *par, u32 width, u32 height, + u32 bpp, u32 raster_order) +{ + u32 reg; + + if (bpp > 16 && lcd_revision == LCD_VERSION_1) + return -EINVAL; + + /* Set the Panel Width */ + /* Pixels per line = (PPL + 1)*16 */ + if (lcd_revision == LCD_VERSION_1) { + /* + * 0x3F in bits 4..9 gives max horizontal resolution = 1024 + * pixels. + */ + width &= 0x3f0; + } else { + /* + * 0x7F in bits 4..10 gives max horizontal resolution = 2048 + * pixels. + */ + width &= 0x7f0; + } + + reg = lcdc_read(LCD_RASTER_TIMING_0_REG); + reg &= 0xfffffc00; + if (lcd_revision == LCD_VERSION_1) { + reg |= ((width >> 4) - 1) << 4; + } else { + width = (width >> 4) - 1; + reg |= ((width & 0x3f) << 4) | ((width & 0x40) >> 3); + } + lcdc_write(reg, LCD_RASTER_TIMING_0_REG); + + /* Set the Panel Height */ + /* Set bits 9:0 of Lines Per Pixel */ + reg = lcdc_read(LCD_RASTER_TIMING_1_REG); + reg = ((height - 1) & 0x3ff) | (reg & 0xfffffc00); + lcdc_write(reg, LCD_RASTER_TIMING_1_REG); + + /* Set bit 10 of Lines Per Pixel */ + if (lcd_revision == LCD_VERSION_2) { + reg = lcdc_read(LCD_RASTER_TIMING_2_REG); + reg |= ((height - 1) & 0x400) << 16; + lcdc_write(reg, LCD_RASTER_TIMING_2_REG); + } + + /* Set the Raster Order of the Frame Buffer */ + reg = lcdc_read(LCD_RASTER_CTRL_REG) & ~(1 << 8); + if (raster_order) + reg |= LCD_RASTER_ORDER; + + par->palette_sz = 16 * 2; + + switch (bpp) { + case 1: + case 2: + case 4: + case 16: + break; + case 24: + reg |= LCD_V2_TFT_24BPP_MODE; + break; + case 32: + reg |= LCD_V2_TFT_24BPP_MODE; + reg |= LCD_V2_TFT_24BPP_UNPACK; + break; + case 8: + par->palette_sz = 256 * 2; + break; + + default: + return -EINVAL; + } + + lcdc_write(reg, LCD_RASTER_CTRL_REG); + + return 0; +} + +#define CNVT_TOHW(val, width) ((((val) << (width)) + 0x7FFF - (val)) >> 16) +static int fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct da8xx_fb_par *par = info->par; + unsigned short *palette = (unsigned short *) par->v_palette_base; + u_short pal; + int update_hw = 0; + + if (regno > 255) + return 1; + + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) + return 1; + + if (info->var.bits_per_pixel > 16 && lcd_revision == LCD_VERSION_1) + return -EINVAL; + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + break; + case FB_VISUAL_PSEUDOCOLOR: + switch (info->var.bits_per_pixel) { + case 4: + if (regno > 15) + return -EINVAL; + + if (info->var.grayscale) { + pal = regno; + } else { + red >>= 4; + green >>= 8; + blue >>= 12; + + pal = red & 0x0f00; + pal |= green & 0x00f0; + pal |= blue & 0x000f; + } + if (regno == 0) + pal |= 0x2000; + palette[regno] = pal; + break; + + case 8: + red >>= 4; + green >>= 8; + blue >>= 12; + + pal = (red & 0x0f00); + pal |= (green & 0x00f0); + pal |= (blue & 0x000f); + + if (palette[regno] != pal) { + update_hw = 1; + palette[regno] = pal; + } + break; + } + break; + } + + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + + if (regno > 15) + return -EINVAL; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + + ((u32 *) (info->pseudo_palette))[regno] = v; + if (palette[0] != 0x4000) { + update_hw = 1; + palette[0] = 0x4000; + } + } + + /* Update the palette in the h/w as needed. */ + if (update_hw) + lcd_blit(LOAD_PALETTE, par); + + return 0; +} +#undef CNVT_TOHW + +static void da8xx_fb_lcd_reset(void) +{ + /* DMA has to be disabled */ + lcdc_write(0, LCD_DMA_CTRL_REG); + lcdc_write(0, LCD_RASTER_CTRL_REG); + + if (lcd_revision == LCD_VERSION_2) { + lcdc_write(0, LCD_INT_ENABLE_SET_REG); + /* Write 1 to reset */ + lcdc_write(LCD_CLK_MAIN_RESET, LCD_CLK_RESET_REG); + lcdc_write(0, LCD_CLK_RESET_REG); + } +} + +static int da8xx_fb_config_clk_divider(struct da8xx_fb_par *par, + unsigned lcdc_clk_div, + unsigned lcdc_clk_rate) +{ + int ret; + + if (par->lcdc_clk_rate != lcdc_clk_rate) { + ret = clk_set_rate(par->lcdc_clk, lcdc_clk_rate); + if (IS_ERR_VALUE(ret)) { + dev_err(par->dev, + "unable to set clock rate at %u\n", + lcdc_clk_rate); + return ret; + } + par->lcdc_clk_rate = clk_get_rate(par->lcdc_clk); + } + + /* Configure the LCD clock divisor. */ + lcdc_write(LCD_CLK_DIVISOR(lcdc_clk_div) | + (LCD_RASTER_MODE & 0x1), LCD_CTRL_REG); + + if (lcd_revision == LCD_VERSION_2) + lcdc_write(LCD_V2_DMA_CLK_EN | LCD_V2_LIDD_CLK_EN | + LCD_V2_CORE_CLK_EN, LCD_CLK_ENABLE_REG); + + return 0; +} + +static unsigned int da8xx_fb_calc_clk_divider(struct da8xx_fb_par *par, + unsigned pixclock, + unsigned *lcdc_clk_rate) +{ + unsigned lcdc_clk_div; + + pixclock = PICOS2KHZ(pixclock) * 1000; + + *lcdc_clk_rate = par->lcdc_clk_rate; + + if (pixclock < (*lcdc_clk_rate / CLK_MAX_DIV)) { + *lcdc_clk_rate = clk_round_rate(par->lcdc_clk, + pixclock * CLK_MAX_DIV); + lcdc_clk_div = CLK_MAX_DIV; + } else if (pixclock > (*lcdc_clk_rate / CLK_MIN_DIV)) { + *lcdc_clk_rate = clk_round_rate(par->lcdc_clk, + pixclock * CLK_MIN_DIV); + lcdc_clk_div = CLK_MIN_DIV; + } else { + lcdc_clk_div = *lcdc_clk_rate / pixclock; + } + + return lcdc_clk_div; +} + +static int da8xx_fb_calc_config_clk_divider(struct da8xx_fb_par *par, + struct fb_videomode *mode) +{ + unsigned lcdc_clk_rate; + unsigned lcdc_clk_div = da8xx_fb_calc_clk_divider(par, mode->pixclock, + &lcdc_clk_rate); + + return da8xx_fb_config_clk_divider(par, lcdc_clk_div, lcdc_clk_rate); +} + +static unsigned da8xx_fb_round_clk(struct da8xx_fb_par *par, + unsigned pixclock) +{ + unsigned lcdc_clk_div, lcdc_clk_rate; + + lcdc_clk_div = da8xx_fb_calc_clk_divider(par, pixclock, &lcdc_clk_rate); + return KHZ2PICOS(lcdc_clk_rate / (1000 * lcdc_clk_div)); +} + +static int lcd_init(struct da8xx_fb_par *par, const struct lcd_ctrl_config *cfg, + struct fb_videomode *panel) +{ + u32 bpp; + int ret = 0; + + ret = da8xx_fb_calc_config_clk_divider(par, panel); + if (IS_ERR_VALUE(ret)) { + dev_err(par->dev, "unable to configure clock\n"); + return ret; + } + + if (panel->sync & FB_SYNC_CLK_INVERT) + lcdc_write((lcdc_read(LCD_RASTER_TIMING_2_REG) | + LCD_INVERT_PIXEL_CLOCK), LCD_RASTER_TIMING_2_REG); + else + lcdc_write((lcdc_read(LCD_RASTER_TIMING_2_REG) & + ~LCD_INVERT_PIXEL_CLOCK), LCD_RASTER_TIMING_2_REG); + + /* Configure the DMA burst size and fifo threshold. */ + ret = lcd_cfg_dma(cfg->dma_burst_sz, cfg->fifo_th); + if (ret < 0) + return ret; + + /* Configure the vertical and horizontal sync properties. */ + lcd_cfg_vertical_sync(panel->upper_margin, panel->vsync_len, + panel->lower_margin); + lcd_cfg_horizontal_sync(panel->left_margin, panel->hsync_len, + panel->right_margin); + + /* Configure for disply */ + ret = lcd_cfg_display(cfg, panel); + if (ret < 0) + return ret; + + bpp = cfg->bpp; + + if (bpp == 12) + bpp = 16; + ret = lcd_cfg_frame_buffer(par, (unsigned int)panel->xres, + (unsigned int)panel->yres, bpp, + cfg->raster_order); + if (ret < 0) + return ret; + + /* Configure FDD */ + lcdc_write((lcdc_read(LCD_RASTER_CTRL_REG) & 0xfff00fff) | + (cfg->fdd << 12), LCD_RASTER_CTRL_REG); + + return 0; +} + +/* IRQ handler for version 2 of LCDC */ +static irqreturn_t lcdc_irq_handler_rev02(int irq, void *arg) +{ + struct da8xx_fb_par *par = arg; + u32 stat = lcdc_read(LCD_MASKED_STAT_REG); + + if ((stat & LCD_SYNC_LOST) && (stat & LCD_FIFO_UNDERFLOW)) { + lcd_disable_raster(DA8XX_FRAME_NOWAIT); + lcdc_write(stat, LCD_MASKED_STAT_REG); + lcd_enable_raster(); + } else if (stat & LCD_PL_LOAD_DONE) { + /* + * Must disable raster before changing state of any control bit. + * And also must be disabled before clearing the PL loading + * interrupt via the following write to the status register. If + * this is done after then one gets multiple PL done interrupts. + */ + lcd_disable_raster(DA8XX_FRAME_NOWAIT); + + lcdc_write(stat, LCD_MASKED_STAT_REG); + + /* Disable PL completion interrupt */ + lcdc_write(LCD_V2_PL_INT_ENA, LCD_INT_ENABLE_CLR_REG); + + /* Setup and start data loading mode */ + lcd_blit(LOAD_DATA, par); + } else { + lcdc_write(stat, LCD_MASKED_STAT_REG); + + if (stat & LCD_END_OF_FRAME0) { + par->which_dma_channel_done = 0; + lcdc_write(par->dma_start, + LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + lcdc_write(par->dma_end, + LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + par->vsync_flag = 1; + wake_up_interruptible(&par->vsync_wait); + } + + if (stat & LCD_END_OF_FRAME1) { + par->which_dma_channel_done = 1; + lcdc_write(par->dma_start, + LCD_DMA_FRM_BUF_BASE_ADDR_1_REG); + lcdc_write(par->dma_end, + LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG); + par->vsync_flag = 1; + wake_up_interruptible(&par->vsync_wait); + } + + /* Set only when controller is disabled and at the end of + * active frame + */ + if (stat & BIT(0)) { + frame_done_flag = 1; + wake_up_interruptible(&frame_done_wq); + } + } + + lcdc_write(0, LCD_END_OF_INT_IND_REG); + return IRQ_HANDLED; +} + +/* IRQ handler for version 1 LCDC */ +static irqreturn_t lcdc_irq_handler_rev01(int irq, void *arg) +{ + struct da8xx_fb_par *par = arg; + u32 stat = lcdc_read(LCD_STAT_REG); + u32 reg_ras; + + if ((stat & LCD_SYNC_LOST) && (stat & LCD_FIFO_UNDERFLOW)) { + lcd_disable_raster(DA8XX_FRAME_NOWAIT); + lcdc_write(stat, LCD_STAT_REG); + lcd_enable_raster(); + } else if (stat & LCD_PL_LOAD_DONE) { + /* + * Must disable raster before changing state of any control bit. + * And also must be disabled before clearing the PL loading + * interrupt via the following write to the status register. If + * this is done after then one gets multiple PL done interrupts. + */ + lcd_disable_raster(DA8XX_FRAME_NOWAIT); + + lcdc_write(stat, LCD_STAT_REG); + + /* Disable PL completion inerrupt */ + reg_ras = lcdc_read(LCD_RASTER_CTRL_REG); + reg_ras &= ~LCD_V1_PL_INT_ENA; + lcdc_write(reg_ras, LCD_RASTER_CTRL_REG); + + /* Setup and start data loading mode */ + lcd_blit(LOAD_DATA, par); + } else { + lcdc_write(stat, LCD_STAT_REG); + + if (stat & LCD_END_OF_FRAME0) { + par->which_dma_channel_done = 0; + lcdc_write(par->dma_start, + LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + lcdc_write(par->dma_end, + LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + par->vsync_flag = 1; + wake_up_interruptible(&par->vsync_wait); + } + + if (stat & LCD_END_OF_FRAME1) { + par->which_dma_channel_done = 1; + lcdc_write(par->dma_start, + LCD_DMA_FRM_BUF_BASE_ADDR_1_REG); + lcdc_write(par->dma_end, + LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG); + par->vsync_flag = 1; + wake_up_interruptible(&par->vsync_wait); + } + } + + return IRQ_HANDLED; +} + +static int fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int err = 0; + struct da8xx_fb_par *par = info->par; + int bpp = var->bits_per_pixel >> 3; + unsigned long line_size = var->xres_virtual * bpp; + + if (var->bits_per_pixel > 16 && lcd_revision == LCD_VERSION_1) + return -EINVAL; + + switch (var->bits_per_pixel) { + case 1: + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + var->nonstd = 0; + break; + case 4: + var->red.offset = 0; + var->red.length = 4; + var->green.offset = 0; + var->green.length = 4; + var->blue.offset = 0; + var->blue.length = 4; + var->transp.offset = 0; + var->transp.length = 0; + var->nonstd = FB_NONSTD_REV_PIX_IN_B; + break; + case 16: /* RGB 565 */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + var->nonstd = 0; + break; + case 24: + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->nonstd = 0; + break; + case 32: + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->nonstd = 0; + break; + default: + err = -EINVAL; + } + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + if (line_size * var->yres_virtual > par->vram_size) + var->yres_virtual = par->vram_size / line_size; + + if (var->yres > var->yres_virtual) + var->yres = var->yres_virtual; + + if (var->xres > var->xres_virtual) + var->xres = var->xres_virtual; + + if (var->xres + var->xoffset > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yres + var->yoffset > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + var->pixclock = da8xx_fb_round_clk(par, var->pixclock); + + return err; +} + +#ifdef CONFIG_CPU_FREQ +static int lcd_da8xx_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct da8xx_fb_par *par; + + par = container_of(nb, struct da8xx_fb_par, freq_transition); + if (val == CPUFREQ_POSTCHANGE) { + if (par->lcdc_clk_rate != clk_get_rate(par->lcdc_clk)) { + par->lcdc_clk_rate = clk_get_rate(par->lcdc_clk); + lcd_disable_raster(DA8XX_FRAME_WAIT); + da8xx_fb_calc_config_clk_divider(par, &par->mode); + if (par->blank == FB_BLANK_UNBLANK) + lcd_enable_raster(); + } + } + + return 0; +} + +static int lcd_da8xx_cpufreq_register(struct da8xx_fb_par *par) +{ + par->freq_transition.notifier_call = lcd_da8xx_cpufreq_transition; + + return cpufreq_register_notifier(&par->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +static void lcd_da8xx_cpufreq_deregister(struct da8xx_fb_par *par) +{ + cpufreq_unregister_notifier(&par->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} +#endif + +static int fb_remove(struct platform_device *dev) +{ + struct fb_info *info = dev_get_drvdata(&dev->dev); + + if (info) { + struct da8xx_fb_par *par = info->par; + +#ifdef CONFIG_CPU_FREQ + lcd_da8xx_cpufreq_deregister(par); +#endif + if (par->panel_power_ctrl) + par->panel_power_ctrl(0); + + lcd_disable_raster(DA8XX_FRAME_WAIT); + lcdc_write(0, LCD_RASTER_CTRL_REG); + + /* disable DMA */ + lcdc_write(0, LCD_DMA_CTRL_REG); + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + dma_free_coherent(NULL, PALETTE_SIZE, par->v_palette_base, + par->p_palette_base); + dma_free_coherent(NULL, par->vram_size, par->vram_virt, + par->vram_phys); + pm_runtime_put_sync(&dev->dev); + pm_runtime_disable(&dev->dev); + framebuffer_release(info); + + } + return 0; +} + +/* + * Function to wait for vertical sync which for this LCD peripheral + * translates into waiting for the current raster frame to complete. + */ +static int fb_wait_for_vsync(struct fb_info *info) +{ + struct da8xx_fb_par *par = info->par; + int ret; + + /* + * Set flag to 0 and wait for isr to set to 1. It would seem there is a + * race condition here where the ISR could have occurred just before or + * just after this set. But since we are just coarsely waiting for + * a frame to complete then that's OK. i.e. if the frame completed + * just before this code executed then we have to wait another full + * frame time but there is no way to avoid such a situation. On the + * other hand if the frame completed just after then we don't need + * to wait long at all. Either way we are guaranteed to return to the + * user immediately after a frame completion which is all that is + * required. + */ + par->vsync_flag = 0; + ret = wait_event_interruptible_timeout(par->vsync_wait, + par->vsync_flag != 0, + par->vsync_timeout); + if (ret < 0) + return ret; + if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + +static int fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct lcd_sync_arg sync_arg; + + switch (cmd) { + case FBIOGET_CONTRAST: + case FBIOPUT_CONTRAST: + case FBIGET_BRIGHTNESS: + case FBIPUT_BRIGHTNESS: + case FBIGET_COLOR: + case FBIPUT_COLOR: + return -ENOTTY; + case FBIPUT_HSYNC: + if (copy_from_user(&sync_arg, (char *)arg, + sizeof(struct lcd_sync_arg))) + return -EFAULT; + lcd_cfg_horizontal_sync(sync_arg.back_porch, + sync_arg.pulse_width, + sync_arg.front_porch); + break; + case FBIPUT_VSYNC: + if (copy_from_user(&sync_arg, (char *)arg, + sizeof(struct lcd_sync_arg))) + return -EFAULT; + lcd_cfg_vertical_sync(sync_arg.back_porch, + sync_arg.pulse_width, + sync_arg.front_porch); + break; + case FBIO_WAITFORVSYNC: + return fb_wait_for_vsync(info); + default: + return -EINVAL; + } + return 0; +} + +static int cfb_blank(int blank, struct fb_info *info) +{ + struct da8xx_fb_par *par = info->par; + int ret = 0; + + if (par->blank == blank) + return 0; + + par->blank = blank; + switch (blank) { + case FB_BLANK_UNBLANK: + lcd_enable_raster(); + + if (par->panel_power_ctrl) + par->panel_power_ctrl(1); + break; + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + if (par->panel_power_ctrl) + par->panel_power_ctrl(0); + + lcd_disable_raster(DA8XX_FRAME_WAIT); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* + * Set new x,y offsets in the virtual display for the visible area and switch + * to the new mode. + */ +static int da8xx_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + int ret = 0; + struct fb_var_screeninfo new_var; + struct da8xx_fb_par *par = fbi->par; + struct fb_fix_screeninfo *fix = &fbi->fix; + unsigned int end; + unsigned int start; + unsigned long irq_flags; + + if (var->xoffset != fbi->var.xoffset || + var->yoffset != fbi->var.yoffset) { + memcpy(&new_var, &fbi->var, sizeof(new_var)); + new_var.xoffset = var->xoffset; + new_var.yoffset = var->yoffset; + if (fb_check_var(&new_var, fbi)) + ret = -EINVAL; + else { + memcpy(&fbi->var, &new_var, sizeof(new_var)); + + start = fix->smem_start + + new_var.yoffset * fix->line_length + + new_var.xoffset * fbi->var.bits_per_pixel / 8; + end = start + fbi->var.yres * fix->line_length - 1; + par->dma_start = start; + par->dma_end = end; + spin_lock_irqsave(&par->lock_for_chan_update, + irq_flags); + if (par->which_dma_channel_done == 0) { + lcdc_write(par->dma_start, + LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + lcdc_write(par->dma_end, + LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + } else if (par->which_dma_channel_done == 1) { + lcdc_write(par->dma_start, + LCD_DMA_FRM_BUF_BASE_ADDR_1_REG); + lcdc_write(par->dma_end, + LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG); + } + spin_unlock_irqrestore(&par->lock_for_chan_update, + irq_flags); + } + } + + return ret; +} + +static int da8xxfb_set_par(struct fb_info *info) +{ + struct da8xx_fb_par *par = info->par; + int ret; + bool raster = da8xx_fb_is_raster_enabled(); + + if (raster) + lcd_disable_raster(DA8XX_FRAME_WAIT); + + fb_var_to_videomode(&par->mode, &info->var); + + par->cfg.bpp = info->var.bits_per_pixel; + + info->fix.visual = (par->cfg.bpp <= 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->fix.line_length = (par->mode.xres * par->cfg.bpp) / 8; + + ret = lcd_init(par, &par->cfg, &par->mode); + if (ret < 0) { + dev_err(par->dev, "lcd init failed\n"); + return ret; + } + + par->dma_start = info->fix.smem_start + + info->var.yoffset * info->fix.line_length + + info->var.xoffset * info->var.bits_per_pixel / 8; + par->dma_end = par->dma_start + + info->var.yres * info->fix.line_length - 1; + + lcdc_write(par->dma_start, LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + lcdc_write(par->dma_end, LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + lcdc_write(par->dma_start, LCD_DMA_FRM_BUF_BASE_ADDR_1_REG); + lcdc_write(par->dma_end, LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG); + + if (raster) + lcd_enable_raster(); + + return 0; +} + +static struct fb_ops da8xx_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = fb_check_var, + .fb_set_par = da8xxfb_set_par, + .fb_setcolreg = fb_setcolreg, + .fb_pan_display = da8xx_pan_display, + .fb_ioctl = fb_ioctl, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = cfb_blank, +}; + +static struct fb_videomode *da8xx_fb_get_videomode(struct platform_device *dev) +{ + struct da8xx_lcdc_platform_data *fb_pdata = dev_get_platdata(&dev->dev); + struct fb_videomode *lcdc_info; + int i; + + for (i = 0, lcdc_info = known_lcd_panels; + i < ARRAY_SIZE(known_lcd_panels); i++, lcdc_info++) { + if (strcmp(fb_pdata->type, lcdc_info->name) == 0) + break; + } + + if (i == ARRAY_SIZE(known_lcd_panels)) { + dev_err(&dev->dev, "no panel found\n"); + return NULL; + } + dev_info(&dev->dev, "found %s panel\n", lcdc_info->name); + + return lcdc_info; +} + +static int fb_probe(struct platform_device *device) +{ + struct da8xx_lcdc_platform_data *fb_pdata = + dev_get_platdata(&device->dev); + static struct resource *lcdc_regs; + struct lcd_ctrl_config *lcd_cfg; + struct fb_videomode *lcdc_info; + struct fb_info *da8xx_fb_info; + struct da8xx_fb_par *par; + struct clk *tmp_lcdc_clk; + int ret; + unsigned long ulcm; + + if (fb_pdata == NULL) { + dev_err(&device->dev, "Can not get platform data\n"); + return -ENOENT; + } + + lcdc_info = da8xx_fb_get_videomode(device); + if (lcdc_info == NULL) + return -ENODEV; + + lcdc_regs = platform_get_resource(device, IORESOURCE_MEM, 0); + da8xx_fb_reg_base = devm_ioremap_resource(&device->dev, lcdc_regs); + if (IS_ERR(da8xx_fb_reg_base)) + return PTR_ERR(da8xx_fb_reg_base); + + tmp_lcdc_clk = devm_clk_get(&device->dev, "fck"); + if (IS_ERR(tmp_lcdc_clk)) { + dev_err(&device->dev, "Can not get device clock\n"); + return PTR_ERR(tmp_lcdc_clk); + } + + pm_runtime_enable(&device->dev); + pm_runtime_get_sync(&device->dev); + + /* Determine LCD IP Version */ + switch (lcdc_read(LCD_PID_REG)) { + case 0x4C100102: + lcd_revision = LCD_VERSION_1; + break; + case 0x4F200800: + case 0x4F201000: + lcd_revision = LCD_VERSION_2; + break; + default: + dev_warn(&device->dev, "Unknown PID Reg value 0x%x, " + "defaulting to LCD revision 1\n", + lcdc_read(LCD_PID_REG)); + lcd_revision = LCD_VERSION_1; + break; + } + + lcd_cfg = (struct lcd_ctrl_config *)fb_pdata->controller_data; + + if (!lcd_cfg) { + ret = -EINVAL; + goto err_pm_runtime_disable; + } + + da8xx_fb_info = framebuffer_alloc(sizeof(struct da8xx_fb_par), + &device->dev); + if (!da8xx_fb_info) { + dev_dbg(&device->dev, "Memory allocation failed for fb_info\n"); + ret = -ENOMEM; + goto err_pm_runtime_disable; + } + + par = da8xx_fb_info->par; + par->dev = &device->dev; + par->lcdc_clk = tmp_lcdc_clk; + par->lcdc_clk_rate = clk_get_rate(par->lcdc_clk); + if (fb_pdata->panel_power_ctrl) { + par->panel_power_ctrl = fb_pdata->panel_power_ctrl; + par->panel_power_ctrl(1); + } + + fb_videomode_to_var(&da8xx_fb_var, lcdc_info); + par->cfg = *lcd_cfg; + + da8xx_fb_lcd_reset(); + + /* allocate frame buffer */ + par->vram_size = lcdc_info->xres * lcdc_info->yres * lcd_cfg->bpp; + ulcm = lcm((lcdc_info->xres * lcd_cfg->bpp)/8, PAGE_SIZE); + par->vram_size = roundup(par->vram_size/8, ulcm); + par->vram_size = par->vram_size * LCD_NUM_BUFFERS; + + par->vram_virt = dma_alloc_coherent(NULL, + par->vram_size, + (resource_size_t *) &par->vram_phys, + GFP_KERNEL | GFP_DMA); + if (!par->vram_virt) { + dev_err(&device->dev, + "GLCD: kmalloc for frame buffer failed\n"); + ret = -EINVAL; + goto err_release_fb; + } + + da8xx_fb_info->screen_base = (char __iomem *) par->vram_virt; + da8xx_fb_fix.smem_start = par->vram_phys; + da8xx_fb_fix.smem_len = par->vram_size; + da8xx_fb_fix.line_length = (lcdc_info->xres * lcd_cfg->bpp) / 8; + + par->dma_start = par->vram_phys; + par->dma_end = par->dma_start + lcdc_info->yres * + da8xx_fb_fix.line_length - 1; + + /* allocate palette buffer */ + par->v_palette_base = dma_alloc_coherent(NULL, + PALETTE_SIZE, + (resource_size_t *) + &par->p_palette_base, + GFP_KERNEL | GFP_DMA); + if (!par->v_palette_base) { + dev_err(&device->dev, + "GLCD: kmalloc for palette buffer failed\n"); + ret = -EINVAL; + goto err_release_fb_mem; + } + memset(par->v_palette_base, 0, PALETTE_SIZE); + + par->irq = platform_get_irq(device, 0); + if (par->irq < 0) { + ret = -ENOENT; + goto err_release_pl_mem; + } + + da8xx_fb_var.grayscale = + lcd_cfg->panel_shade == MONOCHROME ? 1 : 0; + da8xx_fb_var.bits_per_pixel = lcd_cfg->bpp; + + /* Initialize fbinfo */ + da8xx_fb_info->flags = FBINFO_FLAG_DEFAULT; + da8xx_fb_info->fix = da8xx_fb_fix; + da8xx_fb_info->var = da8xx_fb_var; + da8xx_fb_info->fbops = &da8xx_fb_ops; + da8xx_fb_info->pseudo_palette = par->pseudo_palette; + da8xx_fb_info->fix.visual = (da8xx_fb_info->var.bits_per_pixel <= 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + + ret = fb_alloc_cmap(&da8xx_fb_info->cmap, PALETTE_SIZE, 0); + if (ret) + goto err_release_pl_mem; + da8xx_fb_info->cmap.len = par->palette_sz; + + /* initialize var_screeninfo */ + da8xx_fb_var.activate = FB_ACTIVATE_FORCE; + fb_set_var(da8xx_fb_info, &da8xx_fb_var); + + dev_set_drvdata(&device->dev, da8xx_fb_info); + + /* initialize the vsync wait queue */ + init_waitqueue_head(&par->vsync_wait); + par->vsync_timeout = HZ / 5; + par->which_dma_channel_done = -1; + spin_lock_init(&par->lock_for_chan_update); + + /* Register the Frame Buffer */ + if (register_framebuffer(da8xx_fb_info) < 0) { + dev_err(&device->dev, + "GLCD: Frame Buffer Registration Failed!\n"); + ret = -EINVAL; + goto err_dealloc_cmap; + } + +#ifdef CONFIG_CPU_FREQ + ret = lcd_da8xx_cpufreq_register(par); + if (ret) { + dev_err(&device->dev, "failed to register cpufreq\n"); + goto err_cpu_freq; + } +#endif + + if (lcd_revision == LCD_VERSION_1) + lcdc_irq_handler = lcdc_irq_handler_rev01; + else { + init_waitqueue_head(&frame_done_wq); + lcdc_irq_handler = lcdc_irq_handler_rev02; + } + + ret = devm_request_irq(&device->dev, par->irq, lcdc_irq_handler, 0, + DRIVER_NAME, par); + if (ret) + goto irq_freq; + return 0; + +irq_freq: +#ifdef CONFIG_CPU_FREQ + lcd_da8xx_cpufreq_deregister(par); +err_cpu_freq: +#endif + unregister_framebuffer(da8xx_fb_info); + +err_dealloc_cmap: + fb_dealloc_cmap(&da8xx_fb_info->cmap); + +err_release_pl_mem: + dma_free_coherent(NULL, PALETTE_SIZE, par->v_palette_base, + par->p_palette_base); + +err_release_fb_mem: + dma_free_coherent(NULL, par->vram_size, par->vram_virt, par->vram_phys); + +err_release_fb: + framebuffer_release(da8xx_fb_info); + +err_pm_runtime_disable: + pm_runtime_put_sync(&device->dev); + pm_runtime_disable(&device->dev); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static struct lcdc_context { + u32 clk_enable; + u32 ctrl; + u32 dma_ctrl; + u32 raster_timing_0; + u32 raster_timing_1; + u32 raster_timing_2; + u32 int_enable_set; + u32 dma_frm_buf_base_addr_0; + u32 dma_frm_buf_ceiling_addr_0; + u32 dma_frm_buf_base_addr_1; + u32 dma_frm_buf_ceiling_addr_1; + u32 raster_ctrl; +} reg_context; + +static void lcd_context_save(void) +{ + if (lcd_revision == LCD_VERSION_2) { + reg_context.clk_enable = lcdc_read(LCD_CLK_ENABLE_REG); + reg_context.int_enable_set = lcdc_read(LCD_INT_ENABLE_SET_REG); + } + + reg_context.ctrl = lcdc_read(LCD_CTRL_REG); + reg_context.dma_ctrl = lcdc_read(LCD_DMA_CTRL_REG); + reg_context.raster_timing_0 = lcdc_read(LCD_RASTER_TIMING_0_REG); + reg_context.raster_timing_1 = lcdc_read(LCD_RASTER_TIMING_1_REG); + reg_context.raster_timing_2 = lcdc_read(LCD_RASTER_TIMING_2_REG); + reg_context.dma_frm_buf_base_addr_0 = + lcdc_read(LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + reg_context.dma_frm_buf_ceiling_addr_0 = + lcdc_read(LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + reg_context.dma_frm_buf_base_addr_1 = + lcdc_read(LCD_DMA_FRM_BUF_BASE_ADDR_1_REG); + reg_context.dma_frm_buf_ceiling_addr_1 = + lcdc_read(LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG); + reg_context.raster_ctrl = lcdc_read(LCD_RASTER_CTRL_REG); + return; +} + +static void lcd_context_restore(void) +{ + if (lcd_revision == LCD_VERSION_2) { + lcdc_write(reg_context.clk_enable, LCD_CLK_ENABLE_REG); + lcdc_write(reg_context.int_enable_set, LCD_INT_ENABLE_SET_REG); + } + + lcdc_write(reg_context.ctrl, LCD_CTRL_REG); + lcdc_write(reg_context.dma_ctrl, LCD_DMA_CTRL_REG); + lcdc_write(reg_context.raster_timing_0, LCD_RASTER_TIMING_0_REG); + lcdc_write(reg_context.raster_timing_1, LCD_RASTER_TIMING_1_REG); + lcdc_write(reg_context.raster_timing_2, LCD_RASTER_TIMING_2_REG); + lcdc_write(reg_context.dma_frm_buf_base_addr_0, + LCD_DMA_FRM_BUF_BASE_ADDR_0_REG); + lcdc_write(reg_context.dma_frm_buf_ceiling_addr_0, + LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG); + lcdc_write(reg_context.dma_frm_buf_base_addr_1, + LCD_DMA_FRM_BUF_BASE_ADDR_1_REG); + lcdc_write(reg_context.dma_frm_buf_ceiling_addr_1, + LCD_DMA_FRM_BUF_CEILING_ADDR_1_REG); + lcdc_write(reg_context.raster_ctrl, LCD_RASTER_CTRL_REG); + return; +} + +static int fb_suspend(struct device *dev) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct da8xx_fb_par *par = info->par; + + console_lock(); + if (par->panel_power_ctrl) + par->panel_power_ctrl(0); + + fb_set_suspend(info, 1); + lcd_disable_raster(DA8XX_FRAME_WAIT); + lcd_context_save(); + pm_runtime_put_sync(dev); + console_unlock(); + + return 0; +} +static int fb_resume(struct device *dev) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct da8xx_fb_par *par = info->par; + + console_lock(); + pm_runtime_get_sync(dev); + lcd_context_restore(); + if (par->blank == FB_BLANK_UNBLANK) { + lcd_enable_raster(); + + if (par->panel_power_ctrl) + par->panel_power_ctrl(1); + } + + fb_set_suspend(info, 0); + console_unlock(); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(fb_pm_ops, fb_suspend, fb_resume); + +static struct platform_driver da8xx_fb_driver = { + .probe = fb_probe, + .remove = fb_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &fb_pm_ops, + }, +}; +module_platform_driver(da8xx_fb_driver); + +MODULE_DESCRIPTION("Framebuffer driver for TI da8xx/omap-l1xx"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/dnfb.c b/drivers/video/fbdev/dnfb.c new file mode 100644 index 000000000000..3526899da61b --- /dev/null +++ b/drivers/video/fbdev/dnfb.c @@ -0,0 +1,303 @@ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <asm/setup.h> +#include <asm/irq.h> +#include <asm/amigahw.h> +#include <asm/amigaints.h> +#include <asm/apollohw.h> +#include <linux/fb.h> +#include <linux/module.h> + +/* apollo video HW definitions */ + +/* + * Control Registers. IOBASE + $x + * + * Note: these are the Memory/IO BASE definitions for a mono card set to the + * alternate address + * + * Control 3A and 3B serve identical functions except that 3A + * deals with control 1 and 3b deals with Color LUT reg. + */ + +#define AP_IOBASE 0x3b0 /* Base address of 1 plane board. */ +#define AP_STATUS isaIO2mem(AP_IOBASE+0) /* Status register. Read */ +#define AP_WRITE_ENABLE isaIO2mem(AP_IOBASE+0) /* Write Enable Register Write */ +#define AP_DEVICE_ID isaIO2mem(AP_IOBASE+1) /* Device ID Register. Read */ +#define AP_ROP_1 isaIO2mem(AP_IOBASE+2) /* Raster Operation reg. Write Word */ +#define AP_DIAG_MEM_REQ isaIO2mem(AP_IOBASE+4) /* Diagnostic Memory Request. Write Word */ +#define AP_CONTROL_0 isaIO2mem(AP_IOBASE+8) /* Control Register 0. Read/Write */ +#define AP_CONTROL_1 isaIO2mem(AP_IOBASE+0xa) /* Control Register 1. Read/Write */ +#define AP_CONTROL_3A isaIO2mem(AP_IOBASE+0xe) /* Control Register 3a. Read/Write */ +#define AP_CONTROL_2 isaIO2mem(AP_IOBASE+0xc) /* Control Register 2. Read/Write */ + + +#define FRAME_BUFFER_START 0x0FA0000 +#define FRAME_BUFFER_LEN 0x40000 + +/* CREG 0 */ +#define VECTOR_MODE 0x40 /* 010x.xxxx */ +#define DBLT_MODE 0x80 /* 100x.xxxx */ +#define NORMAL_MODE 0xE0 /* 111x.xxxx */ +#define SHIFT_BITS 0x1F /* xxx1.1111 */ + /* other bits are Shift value */ + +/* CREG 1 */ +#define AD_BLT 0x80 /* 1xxx.xxxx */ +#define NORMAL 0x80 /* 1xxx.xxxx */ /* What is happening here ?? */ +#define INVERSE 0x00 /* 0xxx.xxxx */ /* Clearing this reverses the screen */ +#define PIX_BLT 0x00 /* 0xxx.xxxx */ + +#define AD_HIBIT 0x40 /* xIxx.xxxx */ + +#define ROP_EN 0x10 /* xxx1.xxxx */ +#define DST_EQ_SRC 0x00 /* xxx0.xxxx */ +#define nRESET_SYNC 0x08 /* xxxx.1xxx */ +#define SYNC_ENAB 0x02 /* xxxx.xx1x */ + +#define BLANK_DISP 0x00 /* xxxx.xxx0 */ +#define ENAB_DISP 0x01 /* xxxx.xxx1 */ + +#define NORM_CREG1 (nRESET_SYNC | SYNC_ENAB | ENAB_DISP) /* no reset sync */ + +/* CREG 2 */ + +/* + * Following 3 defines are common to 1, 4 and 8 plane. + */ + +#define S_DATA_1s 0x00 /* 00xx.xxxx */ /* set source to all 1's -- vector drawing */ +#define S_DATA_PIX 0x40 /* 01xx.xxxx */ /* takes source from ls-bits and replicates over 16 bits */ +#define S_DATA_PLN 0xC0 /* 11xx.xxxx */ /* normal, each data access =16-bits in + one plane of image mem */ + +/* CREG 3A/CREG 3B */ +# define RESET_CREG 0x80 /* 1000.0000 */ + +/* ROP REG - all one nibble */ +/* ********* NOTE : this is used r0,r1,r2,r3 *********** */ +#define ROP(r2,r3,r0,r1) ( (U_SHORT)((r0)|((r1)<<4)|((r2)<<8)|((r3)<<12)) ) +#define DEST_ZERO 0x0 +#define SRC_AND_DEST 0x1 +#define SRC_AND_nDEST 0x2 +#define SRC 0x3 +#define nSRC_AND_DEST 0x4 +#define DEST 0x5 +#define SRC_XOR_DEST 0x6 +#define SRC_OR_DEST 0x7 +#define SRC_NOR_DEST 0x8 +#define SRC_XNOR_DEST 0x9 +#define nDEST 0xA +#define SRC_OR_nDEST 0xB +#define nSRC 0xC +#define nSRC_OR_DEST 0xD +#define SRC_NAND_DEST 0xE +#define DEST_ONE 0xF + +#define SWAP(A) ((A>>8) | ((A&0xff) <<8)) + +/* frame buffer operations */ + +static int dnfb_blank(int blank, struct fb_info *info); +static void dnfb_copyarea(struct fb_info *info, const struct fb_copyarea *area); + +static struct fb_ops dn_fb_ops = { + .owner = THIS_MODULE, + .fb_blank = dnfb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = dnfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +struct fb_var_screeninfo dnfb_var = { + .xres = 1280, + .yres = 1024, + .xres_virtual = 2048, + .yres_virtual = 1024, + .bits_per_pixel = 1, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo dnfb_fix = { + .id = "Apollo Mono", + .smem_start = (FRAME_BUFFER_START + IO_BASE), + .smem_len = FRAME_BUFFER_LEN, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_MONO10, + .line_length = 256, +}; + +static int dnfb_blank(int blank, struct fb_info *info) +{ + if (blank) + out_8(AP_CONTROL_3A, 0x0); + else + out_8(AP_CONTROL_3A, 0x1); + return 0; +} + +static +void dnfb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + + int incr, y_delta, pre_read = 0, x_end, x_word_count; + uint start_mask, end_mask, dest; + ushort *src, dummy; + short i, j; + + incr = (area->dy <= area->sy) ? 1 : -1; + + src = (ushort *)(info->screen_base + area->sy * info->fix.line_length + + (area->sx >> 4)); + dest = area->dy * (info->fix.line_length >> 1) + (area->dx >> 4); + + if (incr > 0) { + y_delta = (info->fix.line_length * 8) - area->sx - area->width; + x_end = area->dx + area->width - 1; + x_word_count = (x_end >> 4) - (area->dx >> 4) + 1; + start_mask = 0xffff0000 >> (area->dx & 0xf); + end_mask = 0x7ffff >> (x_end & 0xf); + out_8(AP_CONTROL_0, + (((area->dx & 0xf) - (area->sx & 0xf)) % 16) | (0x4 << 5)); + if ((area->dx & 0xf) < (area->sx & 0xf)) + pre_read = 1; + } else { + y_delta = -((info->fix.line_length * 8) - area->sx - area->width); + x_end = area->dx - area->width + 1; + x_word_count = (area->dx >> 4) - (x_end >> 4) + 1; + start_mask = 0x7ffff >> (area->dx & 0xf); + end_mask = 0xffff0000 >> (x_end & 0xf); + out_8(AP_CONTROL_0, + ((-((area->sx & 0xf) - (area->dx & 0xf))) % 16) | + (0x4 << 5)); + if ((area->dx & 0xf) > (area->sx & 0xf)) + pre_read = 1; + } + + for (i = 0; i < area->height; i++) { + + out_8(AP_CONTROL_3A, 0xc | (dest >> 16)); + + if (pre_read) { + dummy = *src; + src += incr; + } + + if (x_word_count) { + out_8(AP_WRITE_ENABLE, start_mask); + *src = dest; + src += incr; + dest += incr; + out_8(AP_WRITE_ENABLE, 0); + + for (j = 1; j < (x_word_count - 1); j++) { + *src = dest; + src += incr; + dest += incr; + } + + out_8(AP_WRITE_ENABLE, start_mask); + *src = dest; + dest += incr; + src += incr; + } else { + out_8(AP_WRITE_ENABLE, start_mask | end_mask); + *src = dest; + dest += incr; + src += incr; + } + src += (y_delta / 16); + dest += (y_delta / 16); + } + out_8(AP_CONTROL_0, NORMAL_MODE); +} + +/* + * Initialization + */ + +static int dnfb_probe(struct platform_device *dev) +{ + struct fb_info *info; + int err = 0; + + info = framebuffer_alloc(0, &dev->dev); + if (!info) + return -ENOMEM; + + info->fbops = &dn_fb_ops; + info->fix = dnfb_fix; + info->var = dnfb_var; + info->var.red.length = 1; + info->var.red.offset = 0; + info->var.green = info->var.blue = info->var.red; + info->screen_base = (u_char *) info->fix.smem_start; + + err = fb_alloc_cmap(&info->cmap, 2, 0); + if (err < 0) { + framebuffer_release(info); + return err; + } + + err = register_framebuffer(info); + if (err < 0) { + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + return err; + } + platform_set_drvdata(dev, info); + + /* now we have registered we can safely setup the hardware */ + out_8(AP_CONTROL_3A, RESET_CREG); + out_be16(AP_WRITE_ENABLE, 0x0); + out_8(AP_CONTROL_0, NORMAL_MODE); + out_8(AP_CONTROL_1, (AD_BLT | DST_EQ_SRC | NORM_CREG1)); + out_8(AP_CONTROL_2, S_DATA_PLN); + out_be16(AP_ROP_1, SWAP(0x3)); + + printk("apollo frame buffer alive and kicking !\n"); + return err; +} + +static struct platform_driver dnfb_driver = { + .probe = dnfb_probe, + .driver = { + .name = "dnfb", + }, +}; + +static struct platform_device dnfb_device = { + .name = "dnfb", +}; + +int __init dnfb_init(void) +{ + int ret; + + if (!MACH_IS_APOLLO) + return -ENODEV; + + if (fb_get_options("dnfb", NULL)) + return -ENODEV; + + ret = platform_driver_register(&dnfb_driver); + + if (!ret) { + ret = platform_device_register(&dnfb_device); + if (ret) + platform_driver_unregister(&dnfb_driver); + } + return ret; +} + +module_init(dnfb_init); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/edid.h b/drivers/video/fbdev/edid.h new file mode 100644 index 000000000000..d03a232d90b2 --- /dev/null +++ b/drivers/video/fbdev/edid.h @@ -0,0 +1,138 @@ +/* + * drivers/video/edid.h - EDID/DDC Header + * + * Based on: + * 1. XFree86 4.3.0, edid.h + * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE> + * + * 2. John Fremlin <vii@users.sourceforge.net> and + * Ani Joshi <ajoshi@unixbox.com> + * + * DDC is a Trademark of VESA (Video Electronics Standard Association). + * + * 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. +*/ + +#ifndef __EDID_H__ +#define __EDID_H__ + +#define EDID_LENGTH 0x80 +#define EDID_HEADER 0x00 +#define EDID_HEADER_END 0x07 + +#define ID_MANUFACTURER_NAME 0x08 +#define ID_MANUFACTURER_NAME_END 0x09 +#define ID_MODEL 0x0a + +#define ID_SERIAL_NUMBER 0x0c + +#define MANUFACTURE_WEEK 0x10 +#define MANUFACTURE_YEAR 0x11 + +#define EDID_STRUCT_VERSION 0x12 +#define EDID_STRUCT_REVISION 0x13 + +#define EDID_STRUCT_DISPLAY 0x14 + +#define DPMS_FLAGS 0x18 +#define ESTABLISHED_TIMING_1 0x23 +#define ESTABLISHED_TIMING_2 0x24 +#define MANUFACTURERS_TIMINGS 0x25 + +/* standard timings supported */ +#define STD_TIMING 8 +#define STD_TIMING_DESCRIPTION_SIZE 2 +#define STD_TIMING_DESCRIPTIONS_START 0x26 + +#define DETAILED_TIMING_DESCRIPTIONS_START 0x36 +#define DETAILED_TIMING_DESCRIPTION_SIZE 18 +#define NO_DETAILED_TIMING_DESCRIPTIONS 4 + +#define DETAILED_TIMING_DESCRIPTION_1 0x36 +#define DETAILED_TIMING_DESCRIPTION_2 0x48 +#define DETAILED_TIMING_DESCRIPTION_3 0x5a +#define DETAILED_TIMING_DESCRIPTION_4 0x6c + +#define DESCRIPTOR_DATA 5 + +#define UPPER_NIBBLE( x ) \ + (((128|64|32|16) & (x)) >> 4) + +#define LOWER_NIBBLE( x ) \ + ((1|2|4|8) & (x)) + +#define COMBINE_HI_8LO( hi, lo ) \ + ( (((unsigned)hi) << 8) | (unsigned)lo ) + +#define COMBINE_HI_4LO( hi, lo ) \ + ( (((unsigned)hi) << 4) | (unsigned)lo ) + +#define PIXEL_CLOCK_LO (unsigned)block[ 0 ] +#define PIXEL_CLOCK_HI (unsigned)block[ 1 ] +#define PIXEL_CLOCK (COMBINE_HI_8LO( PIXEL_CLOCK_HI,PIXEL_CLOCK_LO )*10000) +#define H_ACTIVE_LO (unsigned)block[ 2 ] +#define H_BLANKING_LO (unsigned)block[ 3 ] +#define H_ACTIVE_HI UPPER_NIBBLE( (unsigned)block[ 4 ] ) +#define H_ACTIVE COMBINE_HI_8LO( H_ACTIVE_HI, H_ACTIVE_LO ) +#define H_BLANKING_HI LOWER_NIBBLE( (unsigned)block[ 4 ] ) +#define H_BLANKING COMBINE_HI_8LO( H_BLANKING_HI, H_BLANKING_LO ) + +#define V_ACTIVE_LO (unsigned)block[ 5 ] +#define V_BLANKING_LO (unsigned)block[ 6 ] +#define V_ACTIVE_HI UPPER_NIBBLE( (unsigned)block[ 7 ] ) +#define V_ACTIVE COMBINE_HI_8LO( V_ACTIVE_HI, V_ACTIVE_LO ) +#define V_BLANKING_HI LOWER_NIBBLE( (unsigned)block[ 7 ] ) +#define V_BLANKING COMBINE_HI_8LO( V_BLANKING_HI, V_BLANKING_LO ) + +#define H_SYNC_OFFSET_LO (unsigned)block[ 8 ] +#define H_SYNC_WIDTH_LO (unsigned)block[ 9 ] + +#define V_SYNC_OFFSET_LO UPPER_NIBBLE( (unsigned)block[ 10 ] ) +#define V_SYNC_WIDTH_LO LOWER_NIBBLE( (unsigned)block[ 10 ] ) + +#define V_SYNC_WIDTH_HI ((unsigned)block[ 11 ] & (1|2)) +#define V_SYNC_OFFSET_HI (((unsigned)block[ 11 ] & (4|8)) >> 2) + +#define H_SYNC_WIDTH_HI (((unsigned)block[ 11 ] & (16|32)) >> 4) +#define H_SYNC_OFFSET_HI (((unsigned)block[ 11 ] & (64|128)) >> 6) + +#define V_SYNC_WIDTH COMBINE_HI_4LO( V_SYNC_WIDTH_HI, V_SYNC_WIDTH_LO ) +#define V_SYNC_OFFSET COMBINE_HI_4LO( V_SYNC_OFFSET_HI, V_SYNC_OFFSET_LO ) + +#define H_SYNC_WIDTH COMBINE_HI_8LO( H_SYNC_WIDTH_HI, H_SYNC_WIDTH_LO ) +#define H_SYNC_OFFSET COMBINE_HI_8LO( H_SYNC_OFFSET_HI, H_SYNC_OFFSET_LO ) + +#define H_SIZE_LO (unsigned)block[ 12 ] +#define V_SIZE_LO (unsigned)block[ 13 ] + +#define H_SIZE_HI UPPER_NIBBLE( (unsigned)block[ 14 ] ) +#define V_SIZE_HI LOWER_NIBBLE( (unsigned)block[ 14 ] ) + +#define H_SIZE COMBINE_HI_8LO( H_SIZE_HI, H_SIZE_LO ) +#define V_SIZE COMBINE_HI_8LO( V_SIZE_HI, V_SIZE_LO ) + +#define H_BORDER (unsigned)block[ 15 ] +#define V_BORDER (unsigned)block[ 16 ] + +#define FLAGS (unsigned)block[ 17 ] + +#define INTERLACED (FLAGS&128) +#define SYNC_TYPE (FLAGS&3<<3) /* bits 4,3 */ +#define SYNC_SEPARATE (3<<3) +#define HSYNC_POSITIVE (FLAGS & 4) +#define VSYNC_POSITIVE (FLAGS & 2) + +#define V_MIN_RATE block[ 5 ] +#define V_MAX_RATE block[ 6 ] +#define H_MIN_RATE block[ 7 ] +#define H_MAX_RATE block[ 8 ] +#define MAX_PIXEL_CLOCK (((int)block[ 9 ]) * 10) +#define GTF_SUPPORT block[10] + +#define DPMS_ACTIVE_OFF (1 << 5) +#define DPMS_SUSPEND (1 << 6) +#define DPMS_STANDBY (1 << 7) + +#endif /* __EDID_H__ */ diff --git a/drivers/video/fbdev/efifb.c b/drivers/video/fbdev/efifb.c new file mode 100644 index 000000000000..ae9618ff6735 --- /dev/null +++ b/drivers/video/fbdev/efifb.c @@ -0,0 +1,360 @@ +/* + * Framebuffer driver for EFI/UEFI based system + * + * (c) 2006 Edgar Hucek <gimli@dark-green.com> + * Original efi driver written by Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/platform_device.h> +#include <linux/screen_info.h> +#include <linux/dmi.h> +#include <linux/pci.h> +#include <video/vga.h> +#include <asm/sysfb.h> + +static bool request_mem_succeeded = false; + +static struct pci_dev *default_vga; + +static struct fb_var_screeninfo efifb_defined = { + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .right_margin = 32, + .upper_margin = 16, + .lower_margin = 4, + .vsync_len = 4, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo efifb_fix = { + .id = "EFI VGA", + .type = FB_TYPE_PACKED_PIXELS, + .accel = FB_ACCEL_NONE, + .visual = FB_VISUAL_TRUECOLOR, +}; + +static int efifb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= info->cmap.len) + return 1; + + if (regno < 16) { + red >>= 8; + green >>= 8; + blue >>= 8; + ((u32 *)(info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + } + return 0; +} + +static void efifb_destroy(struct fb_info *info) +{ + if (info->screen_base) + iounmap(info->screen_base); + if (request_mem_succeeded) + release_mem_region(info->apertures->ranges[0].base, + info->apertures->ranges[0].size); + fb_dealloc_cmap(&info->cmap); +} + +static struct fb_ops efifb_ops = { + .owner = THIS_MODULE, + .fb_destroy = efifb_destroy, + .fb_setcolreg = efifb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +struct pci_dev *vga_default_device(void) +{ + return default_vga; +} + +EXPORT_SYMBOL_GPL(vga_default_device); + +void vga_set_default_device(struct pci_dev *pdev) +{ + default_vga = pdev; +} + +static int efifb_setup(char *options) +{ + char *this_opt; + int i; + struct pci_dev *dev = NULL; + + if (options && *options) { + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + + for (i = 0; i < M_UNKNOWN; i++) { + if (efifb_dmi_list[i].base != 0 && + !strcmp(this_opt, efifb_dmi_list[i].optname)) { + screen_info.lfb_base = efifb_dmi_list[i].base; + screen_info.lfb_linelength = efifb_dmi_list[i].stride; + screen_info.lfb_width = efifb_dmi_list[i].width; + screen_info.lfb_height = efifb_dmi_list[i].height; + } + } + if (!strncmp(this_opt, "base:", 5)) + screen_info.lfb_base = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "stride:", 7)) + screen_info.lfb_linelength = simple_strtoul(this_opt+7, NULL, 0) * 4; + else if (!strncmp(this_opt, "height:", 7)) + screen_info.lfb_height = simple_strtoul(this_opt+7, NULL, 0); + else if (!strncmp(this_opt, "width:", 6)) + screen_info.lfb_width = simple_strtoul(this_opt+6, NULL, 0); + } + } + + for_each_pci_dev(dev) { + int i; + + if ((dev->class >> 8) != PCI_CLASS_DISPLAY_VGA) + continue; + + for (i=0; i < DEVICE_COUNT_RESOURCE; i++) { + resource_size_t start, end; + + if (!(pci_resource_flags(dev, i) & IORESOURCE_MEM)) + continue; + + start = pci_resource_start(dev, i); + end = pci_resource_end(dev, i); + + if (!start || !end) + continue; + + if (screen_info.lfb_base >= start && + (screen_info.lfb_base + screen_info.lfb_size) < end) + default_vga = dev; + } + } + + return 0; +} + +static int efifb_probe(struct platform_device *dev) +{ + struct fb_info *info; + int err; + unsigned int size_vmode; + unsigned int size_remap; + unsigned int size_total; + char *option = NULL; + + if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI) + return -ENODEV; + + if (fb_get_options("efifb", &option)) + return -ENODEV; + efifb_setup(option); + + /* We don't get linelength from UGA Draw Protocol, only from + * EFI Graphics Protocol. So if it's not in DMI, and it's not + * passed in from the user, we really can't use the framebuffer. + */ + if (!screen_info.lfb_linelength) + return -ENODEV; + + if (!screen_info.lfb_depth) + screen_info.lfb_depth = 32; + if (!screen_info.pages) + screen_info.pages = 1; + if (!screen_info.lfb_base) { + printk(KERN_DEBUG "efifb: invalid framebuffer address\n"); + return -ENODEV; + } + printk(KERN_INFO "efifb: probing for efifb\n"); + + /* just assume they're all unset if any are */ + if (!screen_info.blue_size) { + screen_info.blue_size = 8; + screen_info.blue_pos = 0; + screen_info.green_size = 8; + screen_info.green_pos = 8; + screen_info.red_size = 8; + screen_info.red_pos = 16; + screen_info.rsvd_size = 8; + screen_info.rsvd_pos = 24; + } + + efifb_fix.smem_start = screen_info.lfb_base; + efifb_defined.bits_per_pixel = screen_info.lfb_depth; + efifb_defined.xres = screen_info.lfb_width; + efifb_defined.yres = screen_info.lfb_height; + efifb_fix.line_length = screen_info.lfb_linelength; + + /* size_vmode -- that is the amount of memory needed for the + * used video mode, i.e. the minimum amount of + * memory we need. */ + size_vmode = efifb_defined.yres * efifb_fix.line_length; + + /* size_total -- all video memory we have. Used for + * entries, ressource allocation and bounds + * checking. */ + size_total = screen_info.lfb_size; + if (size_total < size_vmode) + size_total = size_vmode; + + /* size_remap -- the amount of video memory we are going to + * use for efifb. With modern cards it is no + * option to simply use size_total as that + * wastes plenty of kernel address space. */ + size_remap = size_vmode * 2; + if (size_remap > size_total) + size_remap = size_total; + if (size_remap % PAGE_SIZE) + size_remap += PAGE_SIZE - (size_remap % PAGE_SIZE); + efifb_fix.smem_len = size_remap; + + if (request_mem_region(efifb_fix.smem_start, size_remap, "efifb")) { + request_mem_succeeded = true; + } else { + /* We cannot make this fatal. Sometimes this comes from magic + spaces our resource handlers simply don't know about */ + printk(KERN_WARNING + "efifb: cannot reserve video memory at 0x%lx\n", + efifb_fix.smem_start); + } + + info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev); + if (!info) { + printk(KERN_ERR "efifb: cannot allocate framebuffer\n"); + err = -ENOMEM; + goto err_release_mem; + } + platform_set_drvdata(dev, info); + info->pseudo_palette = info->par; + info->par = NULL; + + info->apertures = alloc_apertures(1); + if (!info->apertures) { + err = -ENOMEM; + goto err_release_fb; + } + info->apertures->ranges[0].base = efifb_fix.smem_start; + info->apertures->ranges[0].size = size_remap; + + info->screen_base = ioremap_wc(efifb_fix.smem_start, efifb_fix.smem_len); + if (!info->screen_base) { + printk(KERN_ERR "efifb: abort, cannot ioremap video memory " + "0x%x @ 0x%lx\n", + efifb_fix.smem_len, efifb_fix.smem_start); + err = -EIO; + goto err_release_fb; + } + + printk(KERN_INFO "efifb: framebuffer at 0x%lx, mapped to 0x%p, " + "using %dk, total %dk\n", + efifb_fix.smem_start, info->screen_base, + size_remap/1024, size_total/1024); + printk(KERN_INFO "efifb: mode is %dx%dx%d, linelength=%d, pages=%d\n", + efifb_defined.xres, efifb_defined.yres, + efifb_defined.bits_per_pixel, efifb_fix.line_length, + screen_info.pages); + + efifb_defined.xres_virtual = efifb_defined.xres; + efifb_defined.yres_virtual = efifb_fix.smem_len / + efifb_fix.line_length; + printk(KERN_INFO "efifb: scrolling: redraw\n"); + efifb_defined.yres_virtual = efifb_defined.yres; + + /* some dummy values for timing to make fbset happy */ + efifb_defined.pixclock = 10000000 / efifb_defined.xres * + 1000 / efifb_defined.yres; + efifb_defined.left_margin = (efifb_defined.xres / 8) & 0xf8; + efifb_defined.hsync_len = (efifb_defined.xres / 8) & 0xf8; + + efifb_defined.red.offset = screen_info.red_pos; + efifb_defined.red.length = screen_info.red_size; + efifb_defined.green.offset = screen_info.green_pos; + efifb_defined.green.length = screen_info.green_size; + efifb_defined.blue.offset = screen_info.blue_pos; + efifb_defined.blue.length = screen_info.blue_size; + efifb_defined.transp.offset = screen_info.rsvd_pos; + efifb_defined.transp.length = screen_info.rsvd_size; + + printk(KERN_INFO "efifb: %s: " + "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n", + "Truecolor", + screen_info.rsvd_size, + screen_info.red_size, + screen_info.green_size, + screen_info.blue_size, + screen_info.rsvd_pos, + screen_info.red_pos, + screen_info.green_pos, + screen_info.blue_pos); + + efifb_fix.ypanstep = 0; + efifb_fix.ywrapstep = 0; + + info->fbops = &efifb_ops; + info->var = efifb_defined; + info->fix = efifb_fix; + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE; + + if ((err = fb_alloc_cmap(&info->cmap, 256, 0)) < 0) { + printk(KERN_ERR "efifb: cannot allocate colormap\n"); + goto err_unmap; + } + if ((err = register_framebuffer(info)) < 0) { + printk(KERN_ERR "efifb: cannot register framebuffer\n"); + goto err_fb_dealoc; + } + fb_info(info, "%s frame buffer device\n", info->fix.id); + return 0; + +err_fb_dealoc: + fb_dealloc_cmap(&info->cmap); +err_unmap: + iounmap(info->screen_base); +err_release_fb: + framebuffer_release(info); +err_release_mem: + if (request_mem_succeeded) + release_mem_region(efifb_fix.smem_start, size_total); + return err; +} + +static int efifb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + + unregister_framebuffer(info); + framebuffer_release(info); + + return 0; +} + +static struct platform_driver efifb_driver = { + .driver = { + .name = "efi-framebuffer", + .owner = THIS_MODULE, + }, + .probe = efifb_probe, + .remove = efifb_remove, +}; + +module_platform_driver(efifb_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/ep93xx-fb.c b/drivers/video/fbdev/ep93xx-fb.c new file mode 100644 index 000000000000..35a0f533f1a2 --- /dev/null +++ b/drivers/video/fbdev/ep93xx-fb.c @@ -0,0 +1,634 @@ +/* + * linux/drivers/video/ep93xx-fb.c + * + * Framebuffer support for the EP93xx series. + * + * Copyright (C) 2007 Bluewater Systems Ltd + * Author: Ryan Mallon + * + * Copyright (c) 2009 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * Based on the Cirrus Logic ep93xxfb driver, and various other ep93xxfb + * drivers. + * + * 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. + * + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/fb.h> +#include <linux/io.h> + +#include <linux/platform_data/video-ep93xx.h> + +/* Vertical Frame Timing Registers */ +#define EP93XXFB_VLINES_TOTAL 0x0000 /* SW locked */ +#define EP93XXFB_VSYNC 0x0004 /* SW locked */ +#define EP93XXFB_VACTIVE 0x0008 /* SW locked */ +#define EP93XXFB_VBLANK 0x0228 /* SW locked */ +#define EP93XXFB_VCLK 0x000c /* SW locked */ + +/* Horizontal Frame Timing Registers */ +#define EP93XXFB_HCLKS_TOTAL 0x0010 /* SW locked */ +#define EP93XXFB_HSYNC 0x0014 /* SW locked */ +#define EP93XXFB_HACTIVE 0x0018 /* SW locked */ +#define EP93XXFB_HBLANK 0x022c /* SW locked */ +#define EP93XXFB_HCLK 0x001c /* SW locked */ + +/* Frame Buffer Memory Configuration Registers */ +#define EP93XXFB_SCREEN_PAGE 0x0028 +#define EP93XXFB_SCREEN_HPAGE 0x002c +#define EP93XXFB_SCREEN_LINES 0x0030 +#define EP93XXFB_LINE_LENGTH 0x0034 +#define EP93XXFB_VLINE_STEP 0x0038 +#define EP93XXFB_LINE_CARRY 0x003c /* SW locked */ +#define EP93XXFB_EOL_OFFSET 0x0230 + +/* Other Video Registers */ +#define EP93XXFB_BRIGHTNESS 0x0020 +#define EP93XXFB_ATTRIBS 0x0024 /* SW locked */ +#define EP93XXFB_SWLOCK 0x007c /* SW locked */ +#define EP93XXFB_AC_RATE 0x0214 +#define EP93XXFB_FIFO_LEVEL 0x0234 +#define EP93XXFB_PIXELMODE 0x0054 +#define EP93XXFB_PIXELMODE_32BPP (0x7 << 0) +#define EP93XXFB_PIXELMODE_24BPP (0x6 << 0) +#define EP93XXFB_PIXELMODE_16BPP (0x4 << 0) +#define EP93XXFB_PIXELMODE_8BPP (0x2 << 0) +#define EP93XXFB_PIXELMODE_SHIFT_1P_24B (0x0 << 3) +#define EP93XXFB_PIXELMODE_SHIFT_1P_18B (0x1 << 3) +#define EP93XXFB_PIXELMODE_COLOR_LUT (0x0 << 10) +#define EP93XXFB_PIXELMODE_COLOR_888 (0x4 << 10) +#define EP93XXFB_PIXELMODE_COLOR_555 (0x5 << 10) +#define EP93XXFB_PARL_IF_OUT 0x0058 +#define EP93XXFB_PARL_IF_IN 0x005c + +/* Blink Control Registers */ +#define EP93XXFB_BLINK_RATE 0x0040 +#define EP93XXFB_BLINK_MASK 0x0044 +#define EP93XXFB_BLINK_PATTRN 0x0048 +#define EP93XXFB_PATTRN_MASK 0x004c +#define EP93XXFB_BKGRND_OFFSET 0x0050 + +/* Hardware Cursor Registers */ +#define EP93XXFB_CURSOR_ADR_START 0x0060 +#define EP93XXFB_CURSOR_ADR_RESET 0x0064 +#define EP93XXFB_CURSOR_SIZE 0x0068 +#define EP93XXFB_CURSOR_COLOR1 0x006c +#define EP93XXFB_CURSOR_COLOR2 0x0070 +#define EP93XXFB_CURSOR_BLINK_COLOR1 0x021c +#define EP93XXFB_CURSOR_BLINK_COLOR2 0x0220 +#define EP93XXFB_CURSOR_XY_LOC 0x0074 +#define EP93XXFB_CURSOR_DSCAN_HY_LOC 0x0078 +#define EP93XXFB_CURSOR_BLINK_RATE_CTRL 0x0224 + +/* LUT Registers */ +#define EP93XXFB_GRY_SCL_LUTR 0x0080 +#define EP93XXFB_GRY_SCL_LUTG 0x0280 +#define EP93XXFB_GRY_SCL_LUTB 0x0300 +#define EP93XXFB_LUT_SW_CONTROL 0x0218 +#define EP93XXFB_LUT_SW_CONTROL_SWTCH (1 << 0) +#define EP93XXFB_LUT_SW_CONTROL_SSTAT (1 << 1) +#define EP93XXFB_COLOR_LUT 0x0400 + +/* Video Signature Registers */ +#define EP93XXFB_VID_SIG_RSLT_VAL 0x0200 +#define EP93XXFB_VID_SIG_CTRL 0x0204 +#define EP93XXFB_VSIG 0x0208 +#define EP93XXFB_HSIG 0x020c +#define EP93XXFB_SIG_CLR_STR 0x0210 + +/* Minimum / Maximum resolutions supported */ +#define EP93XXFB_MIN_XRES 64 +#define EP93XXFB_MIN_YRES 64 +#define EP93XXFB_MAX_XRES 1024 +#define EP93XXFB_MAX_YRES 768 + +struct ep93xx_fbi { + struct ep93xxfb_mach_info *mach_info; + struct clk *clk; + struct resource *res; + void __iomem *mmio_base; + unsigned int pseudo_palette[256]; +}; + +static int check_screenpage_bug = 1; +module_param(check_screenpage_bug, int, 0644); +MODULE_PARM_DESC(check_screenpage_bug, + "Check for bit 27 screen page bug. Default = 1"); + +static inline unsigned int ep93xxfb_readl(struct ep93xx_fbi *fbi, + unsigned int off) +{ + return __raw_readl(fbi->mmio_base + off); +} + +static inline void ep93xxfb_writel(struct ep93xx_fbi *fbi, + unsigned int val, unsigned int off) +{ + __raw_writel(val, fbi->mmio_base + off); +} + +/* + * Write to one of the locked raster registers. + */ +static inline void ep93xxfb_out_locked(struct ep93xx_fbi *fbi, + unsigned int val, unsigned int reg) +{ + /* + * We don't need a lock or delay here since the raster register + * block will remain unlocked until the next access. + */ + ep93xxfb_writel(fbi, 0xaa, EP93XXFB_SWLOCK); + ep93xxfb_writel(fbi, val, reg); +} + +static void ep93xxfb_set_video_attribs(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int attribs; + + attribs = EP93XXFB_ENABLE; + attribs |= fbi->mach_info->flags; + ep93xxfb_out_locked(fbi, attribs, EP93XXFB_ATTRIBS); +} + +static int ep93xxfb_set_pixelmode(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int val; + + info->var.transp.offset = 0; + info->var.transp.length = 0; + + switch (info->var.bits_per_pixel) { + case 8: + val = EP93XXFB_PIXELMODE_8BPP | EP93XXFB_PIXELMODE_COLOR_LUT | + EP93XXFB_PIXELMODE_SHIFT_1P_18B; + + info->var.red.offset = 0; + info->var.red.length = 8; + info->var.green.offset = 0; + info->var.green.length = 8; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + + case 16: + val = EP93XXFB_PIXELMODE_16BPP | EP93XXFB_PIXELMODE_COLOR_555 | + EP93XXFB_PIXELMODE_SHIFT_1P_18B; + + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + + case 24: + val = EP93XXFB_PIXELMODE_24BPP | EP93XXFB_PIXELMODE_COLOR_888 | + EP93XXFB_PIXELMODE_SHIFT_1P_24B; + + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + + case 32: + val = EP93XXFB_PIXELMODE_32BPP | EP93XXFB_PIXELMODE_COLOR_888 | + EP93XXFB_PIXELMODE_SHIFT_1P_24B; + + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + + default: + return -EINVAL; + } + + ep93xxfb_writel(fbi, val, EP93XXFB_PIXELMODE); + return 0; +} + +static void ep93xxfb_set_timing(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int vlines_total, hclks_total, start, stop; + + vlines_total = info->var.yres + info->var.upper_margin + + info->var.lower_margin + info->var.vsync_len - 1; + + hclks_total = info->var.xres + info->var.left_margin + + info->var.right_margin + info->var.hsync_len - 1; + + ep93xxfb_out_locked(fbi, vlines_total, EP93XXFB_VLINES_TOTAL); + ep93xxfb_out_locked(fbi, hclks_total, EP93XXFB_HCLKS_TOTAL); + + start = vlines_total; + stop = vlines_total - info->var.vsync_len; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VSYNC); + + start = vlines_total - info->var.vsync_len - info->var.upper_margin; + stop = info->var.lower_margin - 1; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VBLANK); + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VACTIVE); + + start = vlines_total; + stop = vlines_total + 1; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VCLK); + + start = hclks_total; + stop = hclks_total - info->var.hsync_len; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HSYNC); + + start = hclks_total - info->var.hsync_len - info->var.left_margin; + stop = info->var.right_margin - 1; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HBLANK); + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HACTIVE); + + start = hclks_total; + stop = hclks_total; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HCLK); + + ep93xxfb_out_locked(fbi, 0x0, EP93XXFB_LINE_CARRY); +} + +static int ep93xxfb_set_par(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + + clk_set_rate(fbi->clk, 1000 * PICOS2KHZ(info->var.pixclock)); + + ep93xxfb_set_timing(info); + + info->fix.line_length = info->var.xres_virtual * + info->var.bits_per_pixel / 8; + + ep93xxfb_writel(fbi, info->fix.smem_start, EP93XXFB_SCREEN_PAGE); + ep93xxfb_writel(fbi, info->var.yres - 1, EP93XXFB_SCREEN_LINES); + ep93xxfb_writel(fbi, ((info->var.xres * info->var.bits_per_pixel) + / 32) - 1, EP93XXFB_LINE_LENGTH); + ep93xxfb_writel(fbi, info->fix.line_length / 4, EP93XXFB_VLINE_STEP); + ep93xxfb_set_video_attribs(info); + return 0; +} + +static int ep93xxfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int err; + + err = ep93xxfb_set_pixelmode(info); + if (err) + return err; + + var->xres = max_t(unsigned int, var->xres, EP93XXFB_MIN_XRES); + var->xres = min_t(unsigned int, var->xres, EP93XXFB_MAX_XRES); + var->xres_virtual = max(var->xres_virtual, var->xres); + + var->yres = max_t(unsigned int, var->yres, EP93XXFB_MIN_YRES); + var->yres = min_t(unsigned int, var->yres, EP93XXFB_MAX_YRES); + var->yres_virtual = max(var->yres_virtual, var->yres); + + return 0; +} + +static int ep93xxfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned int offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset < info->fix.smem_len) { + return dma_mmap_writecombine(info->dev, vma, info->screen_base, + info->fix.smem_start, + info->fix.smem_len); + } + + return -EINVAL; +} + +static int ep93xxfb_blank(int blank_mode, struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int attribs = ep93xxfb_readl(fbi, EP93XXFB_ATTRIBS); + + if (blank_mode) { + if (fbi->mach_info->blank) + fbi->mach_info->blank(blank_mode, info); + ep93xxfb_out_locked(fbi, attribs & ~EP93XXFB_ENABLE, + EP93XXFB_ATTRIBS); + clk_disable(fbi->clk); + } else { + clk_enable(fbi->clk); + ep93xxfb_out_locked(fbi, attribs | EP93XXFB_ENABLE, + EP93XXFB_ATTRIBS); + if (fbi->mach_info->blank) + fbi->mach_info->blank(blank_mode, info); + } + + return 0; +} + +static inline int ep93xxfb_convert_color(int val, int width) +{ + return ((val << width) + 0x7fff - val) >> 16; +} + +static int ep93xxfb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int *pal = info->pseudo_palette; + unsigned int ctrl, i, rgb, lut_current, lut_stat; + + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + if (regno > 255) + return 1; + rgb = ((red & 0xff00) << 8) | (green & 0xff00) | + ((blue & 0xff00) >> 8); + + pal[regno] = rgb; + ep93xxfb_writel(fbi, rgb, (EP93XXFB_COLOR_LUT + (regno << 2))); + ctrl = ep93xxfb_readl(fbi, EP93XXFB_LUT_SW_CONTROL); + lut_stat = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SSTAT); + lut_current = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SWTCH); + + if (lut_stat == lut_current) { + for (i = 0; i < 256; i++) { + ep93xxfb_writel(fbi, pal[i], + EP93XXFB_COLOR_LUT + (i << 2)); + } + + ep93xxfb_writel(fbi, + ctrl ^ EP93XXFB_LUT_SW_CONTROL_SWTCH, + EP93XXFB_LUT_SW_CONTROL); + } + break; + + case FB_VISUAL_TRUECOLOR: + if (regno > 16) + return 1; + + red = ep93xxfb_convert_color(red, info->var.red.length); + green = ep93xxfb_convert_color(green, info->var.green.length); + blue = ep93xxfb_convert_color(blue, info->var.blue.length); + transp = ep93xxfb_convert_color(transp, + info->var.transp.length); + + pal[regno] = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + break; + + default: + return 1; + } + + return 0; +} + +static struct fb_ops ep93xxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = ep93xxfb_check_var, + .fb_set_par = ep93xxfb_set_par, + .fb_blank = ep93xxfb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_setcolreg = ep93xxfb_setcolreg, + .fb_mmap = ep93xxfb_mmap, +}; + +static int ep93xxfb_calc_fbsize(struct ep93xxfb_mach_info *mach_info) +{ + int i, fb_size = 0; + + if (mach_info->num_modes == EP93XXFB_USE_MODEDB) { + fb_size = EP93XXFB_MAX_XRES * EP93XXFB_MAX_YRES * + mach_info->bpp / 8; + } else { + for (i = 0; i < mach_info->num_modes; i++) { + const struct fb_videomode *mode; + int size; + + mode = &mach_info->modes[i]; + size = mode->xres * mode->yres * mach_info->bpp / 8; + if (size > fb_size) + fb_size = size; + } + } + + return fb_size; +} + +static int ep93xxfb_alloc_videomem(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + char __iomem *virt_addr; + dma_addr_t phys_addr; + unsigned int fb_size; + + fb_size = ep93xxfb_calc_fbsize(fbi->mach_info); + virt_addr = dma_alloc_writecombine(info->dev, fb_size, + &phys_addr, GFP_KERNEL); + if (!virt_addr) + return -ENOMEM; + + /* + * There is a bug in the ep93xx framebuffer which causes problems + * if bit 27 of the physical address is set. + * See: http://marc.info/?l=linux-arm-kernel&m=110061245502000&w=2 + * There does not seem to be any official errata for this, but I + * have confirmed the problem exists on my hardware (ep9315) at + * least. + */ + if (check_screenpage_bug && phys_addr & (1 << 27)) { + dev_err(info->dev, "ep93xx framebuffer bug. phys addr (0x%x) " + "has bit 27 set: cannot init framebuffer\n", + phys_addr); + + dma_free_coherent(info->dev, fb_size, virt_addr, phys_addr); + return -ENOMEM; + } + + info->fix.smem_start = phys_addr; + info->fix.smem_len = fb_size; + info->screen_base = virt_addr; + + return 0; +} + +static void ep93xxfb_dealloc_videomem(struct fb_info *info) +{ + if (info->screen_base) + dma_free_coherent(info->dev, info->fix.smem_len, + info->screen_base, info->fix.smem_start); +} + +static int ep93xxfb_probe(struct platform_device *pdev) +{ + struct ep93xxfb_mach_info *mach_info = dev_get_platdata(&pdev->dev); + struct fb_info *info; + struct ep93xx_fbi *fbi; + struct resource *res; + char *video_mode; + int err; + + if (!mach_info) + return -EINVAL; + + info = framebuffer_alloc(sizeof(struct ep93xx_fbi), &pdev->dev); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + platform_set_drvdata(pdev, info); + fbi = info->par; + fbi->mach_info = mach_info; + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err) + goto failed_cmap; + + err = ep93xxfb_alloc_videomem(info); + if (err) + goto failed_videomem; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + err = -ENXIO; + goto failed_resource; + } + + /* + * FIXME - We don't do a request_mem_region here because we are + * sharing the register space with the backlight driver (see + * drivers/video/backlight/ep93xx_bl.c) and doing so will cause + * the second loaded driver to return -EBUSY. + * + * NOTE: No locking is required; the backlight does not touch + * any of the framebuffer registers. + */ + fbi->res = res; + fbi->mmio_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!fbi->mmio_base) { + err = -ENXIO; + goto failed_resource; + } + + strcpy(info->fix.id, pdev->name); + info->fbops = &ep93xxfb_ops; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.accel = FB_ACCEL_NONE; + info->var.activate = FB_ACTIVATE_NOW; + info->var.vmode = FB_VMODE_NONINTERLACED; + info->flags = FBINFO_DEFAULT; + info->node = -1; + info->state = FBINFO_STATE_RUNNING; + info->pseudo_palette = &fbi->pseudo_palette; + + fb_get_options("ep93xx-fb", &video_mode); + err = fb_find_mode(&info->var, info, video_mode, + fbi->mach_info->modes, fbi->mach_info->num_modes, + fbi->mach_info->default_mode, fbi->mach_info->bpp); + if (err == 0) { + dev_err(info->dev, "No suitable video mode found\n"); + err = -EINVAL; + goto failed_resource; + } + + if (mach_info->setup) { + err = mach_info->setup(pdev); + if (err) + goto failed_resource; + } + + err = ep93xxfb_check_var(&info->var, info); + if (err) + goto failed_check; + + fbi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(fbi->clk)) { + err = PTR_ERR(fbi->clk); + fbi->clk = NULL; + goto failed_check; + } + + ep93xxfb_set_par(info); + clk_enable(fbi->clk); + + err = register_framebuffer(info); + if (err) + goto failed_check; + + dev_info(info->dev, "registered. Mode = %dx%d-%d\n", + info->var.xres, info->var.yres, info->var.bits_per_pixel); + return 0; + +failed_check: + if (fbi->mach_info->teardown) + fbi->mach_info->teardown(pdev); +failed_resource: + ep93xxfb_dealloc_videomem(info); +failed_videomem: + fb_dealloc_cmap(&info->cmap); +failed_cmap: + kfree(info); + + return err; +} + +static int ep93xxfb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct ep93xx_fbi *fbi = info->par; + + unregister_framebuffer(info); + clk_disable(fbi->clk); + ep93xxfb_dealloc_videomem(info); + fb_dealloc_cmap(&info->cmap); + + if (fbi->mach_info->teardown) + fbi->mach_info->teardown(pdev); + + kfree(info); + + return 0; +} + +static struct platform_driver ep93xxfb_driver = { + .probe = ep93xxfb_probe, + .remove = ep93xxfb_remove, + .driver = { + .name = "ep93xx-fb", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(ep93xxfb_driver); + +MODULE_DESCRIPTION("EP93XX Framebuffer Driver"); +MODULE_ALIAS("platform:ep93xx-fb"); +MODULE_AUTHOR("Ryan Mallon, " + "H Hartley Sweeten <hsweeten@visionengravers.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/exynos/Kconfig b/drivers/video/fbdev/exynos/Kconfig new file mode 100644 index 000000000000..fcf2d48ac6d1 --- /dev/null +++ b/drivers/video/fbdev/exynos/Kconfig @@ -0,0 +1,32 @@ +# +# Exynos Video configuration +# + +menuconfig EXYNOS_VIDEO + bool "Exynos Video driver support" + help + This enables support for EXYNOS Video device. + +if EXYNOS_VIDEO + +# +# MIPI DSI driver +# + +config EXYNOS_MIPI_DSI + bool "EXYNOS MIPI DSI driver support." + depends on ARCH_S5PV210 || ARCH_EXYNOS + select GENERIC_PHY + help + This enables support for MIPI-DSI device. + +config EXYNOS_LCD_S6E8AX0 + bool "S6E8AX0 MIPI AMOLED LCD Driver" + depends on EXYNOS_MIPI_DSI && BACKLIGHT_CLASS_DEVICE + depends on (LCD_CLASS_DEVICE = y) + default n + help + If you have an S6E8AX0 MIPI AMOLED LCD Panel, say Y to enable its + LCD control driver. + +endif # EXYNOS_VIDEO diff --git a/drivers/video/fbdev/exynos/Makefile b/drivers/video/fbdev/exynos/Makefile new file mode 100644 index 000000000000..b5b1bd228abb --- /dev/null +++ b/drivers/video/fbdev/exynos/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the exynos video drivers. +# + +obj-$(CONFIG_EXYNOS_MIPI_DSI) += exynos_mipi_dsi.o exynos_mipi_dsi_common.o \ + exynos_mipi_dsi_lowlevel.o +obj-$(CONFIG_EXYNOS_LCD_S6E8AX0) += s6e8ax0.o diff --git a/drivers/video/fbdev/exynos/exynos_mipi_dsi.c b/drivers/video/fbdev/exynos/exynos_mipi_dsi.c new file mode 100644 index 000000000000..cee9602f9a7b --- /dev/null +++ b/drivers/video/fbdev/exynos/exynos_mipi_dsi.c @@ -0,0 +1,574 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi.c + * + * Samsung SoC MIPI-DSIM driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * + * 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. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/ctype.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/memory.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/notifier.h> +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/err.h> + +#include <video/exynos_mipi_dsim.h> + +#include "exynos_mipi_dsi_common.h" +#include "exynos_mipi_dsi_lowlevel.h" + +struct mipi_dsim_ddi { + int bus_id; + struct list_head list; + struct mipi_dsim_lcd_device *dsim_lcd_dev; + struct mipi_dsim_lcd_driver *dsim_lcd_drv; +}; + +static LIST_HEAD(dsim_ddi_list); + +static DEFINE_MUTEX(mipi_dsim_lock); + +static struct mipi_dsim_platform_data *to_dsim_plat(struct platform_device + *pdev) +{ + return pdev->dev.platform_data; +} + +static struct regulator_bulk_data supplies[] = { + { .supply = "vdd11", }, + { .supply = "vdd18", }, +}; + +static int exynos_mipi_regulator_enable(struct mipi_dsim_device *dsim) +{ + int ret; + + mutex_lock(&dsim->lock); + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + mutex_unlock(&dsim->lock); + + return ret; +} + +static int exynos_mipi_regulator_disable(struct mipi_dsim_device *dsim) +{ + int ret; + + mutex_lock(&dsim->lock); + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + mutex_unlock(&dsim->lock); + + return ret; +} + +/* update all register settings to MIPI DSI controller. */ +static void exynos_mipi_update_cfg(struct mipi_dsim_device *dsim) +{ + /* + * data from Display controller(FIMD) is not transferred in video mode + * but in case of command mode, all settings is not updated to + * registers. + */ + exynos_mipi_dsi_stand_by(dsim, 0); + + exynos_mipi_dsi_init_dsim(dsim); + exynos_mipi_dsi_init_link(dsim); + + exynos_mipi_dsi_set_hs_enable(dsim); + + /* set display timing. */ + exynos_mipi_dsi_set_display_mode(dsim, dsim->dsim_config); + + exynos_mipi_dsi_init_interrupt(dsim); + + /* + * data from Display controller(FIMD) is transferred in video mode + * but in case of command mode, all settings are updated to registers. + */ + exynos_mipi_dsi_stand_by(dsim, 1); +} + +static int exynos_mipi_dsi_early_blank_mode(struct mipi_dsim_device *dsim, + int power) +{ + struct mipi_dsim_lcd_driver *client_drv = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + switch (power) { + case FB_BLANK_POWERDOWN: + if (dsim->suspended) + return 0; + + if (client_drv && client_drv->suspend) + client_drv->suspend(client_dev); + + clk_disable(dsim->clock); + + exynos_mipi_regulator_disable(dsim); + + dsim->suspended = true; + + break; + default: + break; + } + + return 0; +} + +static int exynos_mipi_dsi_blank_mode(struct mipi_dsim_device *dsim, int power) +{ + struct mipi_dsim_lcd_driver *client_drv = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + switch (power) { + case FB_BLANK_UNBLANK: + if (!dsim->suspended) + return 0; + + /* lcd panel power on. */ + if (client_drv && client_drv->power_on) + client_drv->power_on(client_dev, 1); + + exynos_mipi_regulator_enable(dsim); + + /* enable MIPI-DSI PHY. */ + phy_power_on(dsim->phy); + + clk_enable(dsim->clock); + + exynos_mipi_update_cfg(dsim); + + /* set lcd panel sequence commands. */ + if (client_drv && client_drv->set_sequence) + client_drv->set_sequence(client_dev); + + dsim->suspended = false; + + break; + case FB_BLANK_NORMAL: + /* TODO. */ + break; + default: + break; + } + + return 0; +} + +int exynos_mipi_dsi_register_lcd_device(struct mipi_dsim_lcd_device *lcd_dev) +{ + struct mipi_dsim_ddi *dsim_ddi; + + if (!lcd_dev->name) { + pr_err("dsim_lcd_device name is NULL.\n"); + return -EFAULT; + } + + dsim_ddi = kzalloc(sizeof(struct mipi_dsim_ddi), GFP_KERNEL); + if (!dsim_ddi) { + pr_err("failed to allocate dsim_ddi object.\n"); + return -ENOMEM; + } + + dsim_ddi->dsim_lcd_dev = lcd_dev; + + mutex_lock(&mipi_dsim_lock); + list_add_tail(&dsim_ddi->list, &dsim_ddi_list); + mutex_unlock(&mipi_dsim_lock); + + return 0; +} + +static struct mipi_dsim_ddi *exynos_mipi_dsi_find_lcd_device( + struct mipi_dsim_lcd_driver *lcd_drv) +{ + struct mipi_dsim_ddi *dsim_ddi, *next; + struct mipi_dsim_lcd_device *lcd_dev; + + mutex_lock(&mipi_dsim_lock); + + list_for_each_entry_safe(dsim_ddi, next, &dsim_ddi_list, list) { + if (!dsim_ddi) + goto out; + + lcd_dev = dsim_ddi->dsim_lcd_dev; + if (!lcd_dev) + continue; + + if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0) { + /** + * bus_id would be used to identify + * connected bus. + */ + dsim_ddi->bus_id = lcd_dev->bus_id; + mutex_unlock(&mipi_dsim_lock); + + return dsim_ddi; + } + + list_del(&dsim_ddi->list); + kfree(dsim_ddi); + } + +out: + mutex_unlock(&mipi_dsim_lock); + + return NULL; +} + +int exynos_mipi_dsi_register_lcd_driver(struct mipi_dsim_lcd_driver *lcd_drv) +{ + struct mipi_dsim_ddi *dsim_ddi; + + if (!lcd_drv->name) { + pr_err("dsim_lcd_driver name is NULL.\n"); + return -EFAULT; + } + + dsim_ddi = exynos_mipi_dsi_find_lcd_device(lcd_drv); + if (!dsim_ddi) { + pr_err("mipi_dsim_ddi object not found.\n"); + return -EFAULT; + } + + dsim_ddi->dsim_lcd_drv = lcd_drv; + + pr_info("registered panel driver(%s) to mipi-dsi driver.\n", + lcd_drv->name); + + return 0; + +} + +static struct mipi_dsim_ddi *exynos_mipi_dsi_bind_lcd_ddi( + struct mipi_dsim_device *dsim, + const char *name) +{ + struct mipi_dsim_ddi *dsim_ddi, *next; + struct mipi_dsim_lcd_driver *lcd_drv; + struct mipi_dsim_lcd_device *lcd_dev; + int ret; + + mutex_lock(&dsim->lock); + + list_for_each_entry_safe(dsim_ddi, next, &dsim_ddi_list, list) { + lcd_drv = dsim_ddi->dsim_lcd_drv; + lcd_dev = dsim_ddi->dsim_lcd_dev; + if (!lcd_drv || !lcd_dev || + (dsim->id != dsim_ddi->bus_id)) + continue; + + dev_dbg(dsim->dev, "lcd_drv->id = %d, lcd_dev->id = %d\n", + lcd_drv->id, lcd_dev->id); + dev_dbg(dsim->dev, "lcd_dev->bus_id = %d, dsim->id = %d\n", + lcd_dev->bus_id, dsim->id); + + if ((strcmp(lcd_drv->name, name) == 0)) { + lcd_dev->master = dsim; + + lcd_dev->dev.parent = dsim->dev; + dev_set_name(&lcd_dev->dev, "%s", lcd_drv->name); + + ret = device_register(&lcd_dev->dev); + if (ret < 0) { + dev_err(dsim->dev, + "can't register %s, status %d\n", + dev_name(&lcd_dev->dev), ret); + mutex_unlock(&dsim->lock); + + return NULL; + } + + dsim->dsim_lcd_dev = lcd_dev; + dsim->dsim_lcd_drv = lcd_drv; + + mutex_unlock(&dsim->lock); + + return dsim_ddi; + } + } + + mutex_unlock(&dsim->lock); + + return NULL; +} + +/* define MIPI-DSI Master operations. */ +static struct mipi_dsim_master_ops master_ops = { + .cmd_read = exynos_mipi_dsi_rd_data, + .cmd_write = exynos_mipi_dsi_wr_data, + .get_dsim_frame_done = exynos_mipi_dsi_get_frame_done_status, + .clear_dsim_frame_done = exynos_mipi_dsi_clear_frame_done, + .set_early_blank_mode = exynos_mipi_dsi_early_blank_mode, + .set_blank_mode = exynos_mipi_dsi_blank_mode, +}; + +static int exynos_mipi_dsi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mipi_dsim_device *dsim; + struct mipi_dsim_config *dsim_config; + struct mipi_dsim_platform_data *dsim_pd; + struct mipi_dsim_ddi *dsim_ddi; + int ret = -EINVAL; + + dsim = devm_kzalloc(&pdev->dev, sizeof(struct mipi_dsim_device), + GFP_KERNEL); + if (!dsim) { + dev_err(&pdev->dev, "failed to allocate dsim object.\n"); + return -ENOMEM; + } + + dsim->pd = to_dsim_plat(pdev); + dsim->dev = &pdev->dev; + dsim->id = pdev->id; + + /* get mipi_dsim_platform_data. */ + dsim_pd = (struct mipi_dsim_platform_data *)dsim->pd; + if (dsim_pd == NULL) { + dev_err(&pdev->dev, "failed to get platform data for dsim.\n"); + return -EINVAL; + } + /* get mipi_dsim_config. */ + dsim_config = dsim_pd->dsim_config; + if (dsim_config == NULL) { + dev_err(&pdev->dev, "failed to get dsim config data.\n"); + return -EINVAL; + } + + dsim->dsim_config = dsim_config; + dsim->master_ops = &master_ops; + + mutex_init(&dsim->lock); + + ret = devm_regulator_bulk_get(&pdev->dev, ARRAY_SIZE(supplies), + supplies); + if (ret) { + dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + dsim->phy = devm_phy_get(&pdev->dev, "dsim"); + if (IS_ERR(dsim->phy)) + return PTR_ERR(dsim->phy); + + dsim->clock = devm_clk_get(&pdev->dev, "dsim0"); + if (IS_ERR(dsim->clock)) { + dev_err(&pdev->dev, "failed to get dsim clock source\n"); + return -ENODEV; + } + + clk_enable(dsim->clock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dsim->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dsim->reg_base)) { + ret = PTR_ERR(dsim->reg_base); + goto error; + } + + mutex_init(&dsim->lock); + + /* bind lcd ddi matched with panel name. */ + dsim_ddi = exynos_mipi_dsi_bind_lcd_ddi(dsim, dsim_pd->lcd_panel_name); + if (!dsim_ddi) { + dev_err(&pdev->dev, "mipi_dsim_ddi object not found.\n"); + ret = -EINVAL; + goto error; + } + + dsim->irq = platform_get_irq(pdev, 0); + if (IS_ERR_VALUE(dsim->irq)) { + dev_err(&pdev->dev, "failed to request dsim irq resource\n"); + ret = -EINVAL; + goto error; + } + + init_completion(&dsim_wr_comp); + init_completion(&dsim_rd_comp); + platform_set_drvdata(pdev, dsim); + + ret = devm_request_irq(&pdev->dev, dsim->irq, + exynos_mipi_dsi_interrupt_handler, + IRQF_SHARED, dev_name(&pdev->dev), dsim); + if (ret != 0) { + dev_err(&pdev->dev, "failed to request dsim irq\n"); + ret = -EINVAL; + goto error; + } + + /* enable interrupts */ + exynos_mipi_dsi_init_interrupt(dsim); + + /* initialize mipi-dsi client(lcd panel). */ + if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->probe) + dsim_ddi->dsim_lcd_drv->probe(dsim_ddi->dsim_lcd_dev); + + /* in case mipi-dsi has been enabled by bootloader */ + if (dsim_pd->enabled) { + exynos_mipi_regulator_enable(dsim); + goto done; + } + + /* lcd panel power on. */ + if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->power_on) + dsim_ddi->dsim_lcd_drv->power_on(dsim_ddi->dsim_lcd_dev, 1); + + exynos_mipi_regulator_enable(dsim); + + /* enable MIPI-DSI PHY. */ + phy_power_on(dsim->phy); + + exynos_mipi_update_cfg(dsim); + + /* set lcd panel sequence commands. */ + if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->set_sequence) + dsim_ddi->dsim_lcd_drv->set_sequence(dsim_ddi->dsim_lcd_dev); + + dsim->suspended = false; + +done: + platform_set_drvdata(pdev, dsim); + + dev_dbg(&pdev->dev, "%s() completed successfully (%s mode)\n", __func__, + dsim_config->e_interface == DSIM_COMMAND ? "CPU" : "RGB"); + + return 0; + +error: + clk_disable(dsim->clock); + return ret; +} + +static int exynos_mipi_dsi_remove(struct platform_device *pdev) +{ + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); + struct mipi_dsim_ddi *dsim_ddi, *next; + struct mipi_dsim_lcd_driver *dsim_lcd_drv; + + clk_disable(dsim->clock); + + list_for_each_entry_safe(dsim_ddi, next, &dsim_ddi_list, list) { + if (dsim_ddi) { + if (dsim->id != dsim_ddi->bus_id) + continue; + + dsim_lcd_drv = dsim_ddi->dsim_lcd_drv; + + if (dsim_lcd_drv->remove) + dsim_lcd_drv->remove(dsim_ddi->dsim_lcd_dev); + + kfree(dsim_ddi); + } + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_mipi_dsi_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); + struct mipi_dsim_lcd_driver *client_drv = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + disable_irq(dsim->irq); + + if (dsim->suspended) + return 0; + + if (client_drv && client_drv->suspend) + client_drv->suspend(client_dev); + + /* disable MIPI-DSI PHY. */ + phy_power_off(dsim->phy); + + clk_disable(dsim->clock); + + exynos_mipi_regulator_disable(dsim); + + dsim->suspended = true; + + return 0; +} + +static int exynos_mipi_dsi_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); + struct mipi_dsim_lcd_driver *client_drv = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + enable_irq(dsim->irq); + + if (!dsim->suspended) + return 0; + + /* lcd panel power on. */ + if (client_drv && client_drv->power_on) + client_drv->power_on(client_dev, 1); + + exynos_mipi_regulator_enable(dsim); + + /* enable MIPI-DSI PHY. */ + phy_power_on(dsim->phy); + + clk_enable(dsim->clock); + + exynos_mipi_update_cfg(dsim); + + /* set lcd panel sequence commands. */ + if (client_drv && client_drv->set_sequence) + client_drv->set_sequence(client_dev); + + dsim->suspended = false; + + return 0; +} +#endif + +static const struct dev_pm_ops exynos_mipi_dsi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_mipi_dsi_suspend, exynos_mipi_dsi_resume) +}; + +static struct platform_driver exynos_mipi_dsi_driver = { + .probe = exynos_mipi_dsi_probe, + .remove = exynos_mipi_dsi_remove, + .driver = { + .name = "exynos-mipi-dsim", + .owner = THIS_MODULE, + .pm = &exynos_mipi_dsi_pm_ops, + }, +}; + +module_platform_driver(exynos_mipi_dsi_driver); + +MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>"); +MODULE_DESCRIPTION("Samusung SoC MIPI-DSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/exynos/exynos_mipi_dsi_common.c b/drivers/video/fbdev/exynos/exynos_mipi_dsi_common.c new file mode 100644 index 000000000000..85edabfdef5a --- /dev/null +++ b/drivers/video/fbdev/exynos/exynos_mipi_dsi_common.c @@ -0,0 +1,880 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi_common.c + * + * Samsung SoC MIPI-DSI common driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * + * 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. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/ctype.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/memory.h> +#include <linux/delay.h> +#include <linux/irqreturn.h> +#include <linux/kthread.h> + +#include <video/mipi_display.h> +#include <video/exynos_mipi_dsim.h> + +#include "exynos_mipi_dsi_regs.h" +#include "exynos_mipi_dsi_lowlevel.h" +#include "exynos_mipi_dsi_common.h" + +#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250) +#define MIPI_RX_FIFO_READ_DONE 0x30800002 +#define MIPI_MAX_RX_FIFO 20 +#define MHZ (1000 * 1000) +#define FIN_HZ (24 * MHZ) + +#define DFIN_PLL_MIN_HZ (6 * MHZ) +#define DFIN_PLL_MAX_HZ (12 * MHZ) + +#define DFVCO_MIN_HZ (500 * MHZ) +#define DFVCO_MAX_HZ (1000 * MHZ) + +#define TRY_GET_FIFO_TIMEOUT (5000 * 2) +#define TRY_FIFO_CLEAR (10) + +/* MIPI-DSIM status types. */ +enum { + DSIM_STATE_INIT, /* should be initialized. */ + DSIM_STATE_STOP, /* CPU and LCDC are LP mode. */ + DSIM_STATE_HSCLKEN, /* HS clock was enabled. */ + DSIM_STATE_ULPS +}; + +/* define DSI lane types. */ +enum { + DSIM_LANE_CLOCK = (1 << 0), + DSIM_LANE_DATA0 = (1 << 1), + DSIM_LANE_DATA1 = (1 << 2), + DSIM_LANE_DATA2 = (1 << 3), + DSIM_LANE_DATA3 = (1 << 4) +}; + +static unsigned int dpll_table[15] = { + 100, 120, 170, 220, 270, + 320, 390, 450, 510, 560, + 640, 690, 770, 870, 950 +}; + +irqreturn_t exynos_mipi_dsi_interrupt_handler(int irq, void *dev_id) +{ + struct mipi_dsim_device *dsim = dev_id; + unsigned int intsrc, intmsk; + + intsrc = exynos_mipi_dsi_read_interrupt(dsim); + intmsk = exynos_mipi_dsi_read_interrupt_mask(dsim); + intmsk = ~intmsk & intsrc; + + if (intsrc & INTMSK_RX_DONE) { + complete(&dsim_rd_comp); + dev_dbg(dsim->dev, "MIPI INTMSK_RX_DONE\n"); + } + if (intsrc & INTMSK_FIFO_EMPTY) { + complete(&dsim_wr_comp); + dev_dbg(dsim->dev, "MIPI INTMSK_FIFO_EMPTY\n"); + } + + exynos_mipi_dsi_clear_interrupt(dsim, intmsk); + + return IRQ_HANDLED; +} + +/* + * write long packet to mipi dsi slave + * @dsim: mipi dsim device structure. + * @data0: packet data to send. + * @data1: size of packet data + */ +static void exynos_mipi_dsi_long_data_wr(struct mipi_dsim_device *dsim, + const unsigned char *data0, unsigned int data_size) +{ + unsigned int data_cnt = 0, payload = 0; + + /* in case that data count is more then 4 */ + for (data_cnt = 0; data_cnt < data_size; data_cnt += 4) { + /* + * after sending 4bytes per one time, + * send remainder data less then 4. + */ + if ((data_size - data_cnt) < 4) { + if ((data_size - data_cnt) == 3) { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8 | + data0[data_cnt + 2] << 16; + dev_dbg(dsim->dev, "count = 3 payload = %x, %x %x %x\n", + payload, data0[data_cnt], + data0[data_cnt + 1], + data0[data_cnt + 2]); + } else if ((data_size - data_cnt) == 2) { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8; + dev_dbg(dsim->dev, + "count = 2 payload = %x, %x %x\n", payload, + data0[data_cnt], + data0[data_cnt + 1]); + } else if ((data_size - data_cnt) == 1) { + payload = data0[data_cnt]; + } + + exynos_mipi_dsi_wr_tx_data(dsim, payload); + /* send 4bytes per one time. */ + } else { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8 | + data0[data_cnt + 2] << 16 | + data0[data_cnt + 3] << 24; + + dev_dbg(dsim->dev, + "count = 4 payload = %x, %x %x %x %x\n", + payload, *(u8 *)(data0 + data_cnt), + data0[data_cnt + 1], + data0[data_cnt + 2], + data0[data_cnt + 3]); + + exynos_mipi_dsi_wr_tx_data(dsim, payload); + } + } +} + +int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id, + const unsigned char *data0, unsigned int data_size) +{ + unsigned int check_rx_ack = 0; + + if (dsim->state == DSIM_STATE_ULPS) { + dev_err(dsim->dev, "state is ULPS.\n"); + + return -EINVAL; + } + + /* FIXME!!! why does it need this delay? */ + msleep(20); + + mutex_lock(&dsim->lock); + + switch (data_id) { + /* short packet types of packet types for command. */ + case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM: + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE: + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0[0], data0[1]); + if (check_rx_ack) { + /* process response func should be implemented */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + + /* general command */ + case MIPI_DSI_COLOR_MODE_OFF: + case MIPI_DSI_COLOR_MODE_ON: + case MIPI_DSI_SHUTDOWN_PERIPHERAL: + case MIPI_DSI_TURN_ON_PERIPHERAL: + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0[0], data0[1]); + if (check_rx_ack) { + /* process response func should be implemented. */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + + /* packet types for video data */ + case MIPI_DSI_V_SYNC_START: + case MIPI_DSI_V_SYNC_END: + case MIPI_DSI_H_SYNC_START: + case MIPI_DSI_H_SYNC_END: + case MIPI_DSI_END_OF_TRANSMISSION: + mutex_unlock(&dsim->lock); + return 0; + + /* long packet type and null packet */ + case MIPI_DSI_NULL_PACKET: + case MIPI_DSI_BLANKING_PACKET: + mutex_unlock(&dsim->lock); + return 0; + case MIPI_DSI_GENERIC_LONG_WRITE: + case MIPI_DSI_DCS_LONG_WRITE: + { + unsigned int size, payload = 0; + reinit_completion(&dsim_wr_comp); + + size = data_size * 4; + + /* if data count is less then 4, then send 3bytes data. */ + if (data_size < 4) { + payload = data0[0] | + data0[1] << 8 | + data0[2] << 16; + + exynos_mipi_dsi_wr_tx_data(dsim, payload); + + dev_dbg(dsim->dev, "count = %d payload = %x,%x %x %x\n", + data_size, payload, data0[0], + data0[1], data0[2]); + + /* in case that data count is more then 4 */ + } else + exynos_mipi_dsi_long_data_wr(dsim, data0, data_size); + + /* put data into header fifo */ + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data_size & 0xff, + (data_size & 0xff00) >> 8); + + if (!wait_for_completion_interruptible_timeout(&dsim_wr_comp, + MIPI_FIFO_TIMEOUT)) { + dev_warn(dsim->dev, "command write timeout.\n"); + mutex_unlock(&dsim->lock); + return -EAGAIN; + } + + if (check_rx_ack) { + /* process response func should be implemented. */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + } + + /* packet typo for video data */ + case MIPI_DSI_PACKED_PIXEL_STREAM_16: + case MIPI_DSI_PACKED_PIXEL_STREAM_18: + case MIPI_DSI_PIXEL_STREAM_3BYTE_18: + case MIPI_DSI_PACKED_PIXEL_STREAM_24: + if (check_rx_ack) { + /* process response func should be implemented. */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + default: + dev_warn(dsim->dev, + "data id %x is not supported current DSI spec.\n", + data_id); + + mutex_unlock(&dsim->lock); + return -EINVAL; + } +} + +static unsigned int exynos_mipi_dsi_long_data_rd(struct mipi_dsim_device *dsim, + unsigned int req_size, unsigned int rx_data, u8 *rx_buf) +{ + unsigned int rcv_pkt, i, j; + u16 rxsize; + + /* for long packet */ + rxsize = (u16)((rx_data & 0x00ffff00) >> 8); + dev_dbg(dsim->dev, "mipi dsi rx size : %d\n", rxsize); + if (rxsize != req_size) { + dev_dbg(dsim->dev, + "received size mismatch received: %d, requested: %d\n", + rxsize, req_size); + goto err; + } + + for (i = 0; i < (rxsize >> 2); i++) { + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + dev_dbg(dsim->dev, "received pkt : %08x\n", rcv_pkt); + for (j = 0; j < 4; j++) { + rx_buf[(i * 4) + j] = + (u8)(rcv_pkt >> (j * 8)) & 0xff; + dev_dbg(dsim->dev, "received value : %02x\n", + (rcv_pkt >> (j * 8)) & 0xff); + } + } + if (rxsize % 4) { + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + dev_dbg(dsim->dev, "received pkt : %08x\n", rcv_pkt); + for (j = 0; j < (rxsize % 4); j++) { + rx_buf[(i * 4) + j] = + (u8)(rcv_pkt >> (j * 8)) & 0xff; + dev_dbg(dsim->dev, "received value : %02x\n", + (rcv_pkt >> (j * 8)) & 0xff); + } + } + + return rxsize; + +err: + return -EINVAL; +} + +static unsigned int exynos_mipi_dsi_response_size(unsigned int req_size) +{ + switch (req_size) { + case 1: + return MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE; + case 2: + return MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE; + default: + return MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE; + } +} + +int exynos_mipi_dsi_rd_data(struct mipi_dsim_device *dsim, unsigned int data_id, + unsigned int data0, unsigned int req_size, u8 *rx_buf) +{ + unsigned int rx_data, rcv_pkt, i; + u8 response = 0; + u16 rxsize; + + if (dsim->state == DSIM_STATE_ULPS) { + dev_err(dsim->dev, "state is ULPS.\n"); + + return -EINVAL; + } + + /* FIXME!!! */ + msleep(20); + + mutex_lock(&dsim->lock); + reinit_completion(&dsim_rd_comp); + exynos_mipi_dsi_rd_tx_header(dsim, + MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, req_size); + + response = exynos_mipi_dsi_response_size(req_size); + + switch (data_id) { + case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM: + case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM: + case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM: + case MIPI_DSI_DCS_READ: + exynos_mipi_dsi_rd_tx_header(dsim, + data_id, data0); + /* process response func should be implemented. */ + break; + default: + dev_warn(dsim->dev, + "data id %x is not supported current DSI spec.\n", + data_id); + + mutex_unlock(&dsim->lock); + return -EINVAL; + } + + if (!wait_for_completion_interruptible_timeout(&dsim_rd_comp, + MIPI_FIFO_TIMEOUT)) { + pr_err("RX done interrupt timeout\n"); + mutex_unlock(&dsim->lock); + return 0; + } + + msleep(20); + + rx_data = exynos_mipi_dsi_rd_rx_fifo(dsim); + + if ((u8)(rx_data & 0xff) != response) { + printk(KERN_ERR + "mipi dsi wrong response rx_data : %x, response:%x\n", + rx_data, response); + goto clear_rx_fifo; + } + + if (req_size <= 2) { + /* for short packet */ + for (i = 0; i < req_size; i++) + rx_buf[i] = (rx_data >> (8 + (i * 8))) & 0xff; + rxsize = req_size; + } else { + /* for long packet */ + rxsize = exynos_mipi_dsi_long_data_rd(dsim, req_size, rx_data, + rx_buf); + if (rxsize != req_size) + goto clear_rx_fifo; + } + + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + + msleep(20); + + if (rcv_pkt != MIPI_RX_FIFO_READ_DONE) { + dev_info(dsim->dev, + "Can't found RX FIFO READ DONE FLAG : %x\n", rcv_pkt); + goto clear_rx_fifo; + } + + mutex_unlock(&dsim->lock); + + return rxsize; + +clear_rx_fifo: + i = 0; + while (1) { + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + if ((rcv_pkt == MIPI_RX_FIFO_READ_DONE) + || (i > MIPI_MAX_RX_FIFO)) + break; + dev_dbg(dsim->dev, + "mipi dsi clear rx fifo : %08x\n", rcv_pkt); + i++; + } + dev_info(dsim->dev, + "mipi dsi rx done count : %d, rcv_pkt : %08x\n", i, rcv_pkt); + + mutex_unlock(&dsim->lock); + + return 0; +} + +static int exynos_mipi_dsi_pll_on(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + int sw_timeout; + + if (enable) { + sw_timeout = 1000; + + exynos_mipi_dsi_enable_pll(dsim, 1); + while (1) { + sw_timeout--; + if (exynos_mipi_dsi_is_pll_stable(dsim)) + return 0; + if (sw_timeout == 0) + return -EINVAL; + } + } else + exynos_mipi_dsi_enable_pll(dsim, 0); + + return 0; +} + +static unsigned long exynos_mipi_dsi_change_pll(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler) +{ + unsigned long dfin_pll, dfvco, dpll_out; + unsigned int i, freq_band = 0xf; + + dfin_pll = (FIN_HZ / pre_divider); + + /****************************************************** + * Serial Clock(=ByteClk X 8) FreqBand[3:0] * + ****************************************************** + * ~ 99.99 MHz 0000 + * 100 ~ 119.99 MHz 0001 + * 120 ~ 159.99 MHz 0010 + * 160 ~ 199.99 MHz 0011 + * 200 ~ 239.99 MHz 0100 + * 140 ~ 319.99 MHz 0101 + * 320 ~ 389.99 MHz 0110 + * 390 ~ 449.99 MHz 0111 + * 450 ~ 509.99 MHz 1000 + * 510 ~ 559.99 MHz 1001 + * 560 ~ 639.99 MHz 1010 + * 640 ~ 689.99 MHz 1011 + * 690 ~ 769.99 MHz 1100 + * 770 ~ 869.99 MHz 1101 + * 870 ~ 949.99 MHz 1110 + * 950 ~ 1000 MHz 1111 + ******************************************************/ + if (dfin_pll < DFIN_PLL_MIN_HZ || dfin_pll > DFIN_PLL_MAX_HZ) { + dev_warn(dsim->dev, "fin_pll range should be 6MHz ~ 12MHz\n"); + exynos_mipi_dsi_enable_afc(dsim, 0, 0); + } else { + if (dfin_pll < 7 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x1); + else if (dfin_pll < 8 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x0); + else if (dfin_pll < 9 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x3); + else if (dfin_pll < 10 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x2); + else if (dfin_pll < 11 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x5); + else + exynos_mipi_dsi_enable_afc(dsim, 1, 0x4); + } + + dfvco = dfin_pll * main_divider; + dev_dbg(dsim->dev, "dfvco = %lu, dfin_pll = %lu, main_divider = %d\n", + dfvco, dfin_pll, main_divider); + if (dfvco < DFVCO_MIN_HZ || dfvco > DFVCO_MAX_HZ) + dev_warn(dsim->dev, "fvco range should be 500MHz ~ 1000MHz\n"); + + dpll_out = dfvco / (1 << scaler); + dev_dbg(dsim->dev, "dpll_out = %lu, dfvco = %lu, scaler = %d\n", + dpll_out, dfvco, scaler); + + for (i = 0; i < ARRAY_SIZE(dpll_table); i++) { + if (dpll_out < dpll_table[i] * MHZ) { + freq_band = i; + break; + } + } + + dev_dbg(dsim->dev, "freq_band = %d\n", freq_band); + + exynos_mipi_dsi_pll_freq(dsim, pre_divider, main_divider, scaler); + + exynos_mipi_dsi_hs_zero_ctrl(dsim, 0); + exynos_mipi_dsi_prep_ctrl(dsim, 0); + + /* Freq Band */ + exynos_mipi_dsi_pll_freq_band(dsim, freq_band); + + /* Stable time */ + exynos_mipi_dsi_pll_stable_time(dsim, dsim->dsim_config->pll_stable_time); + + /* Enable PLL */ + dev_dbg(dsim->dev, "FOUT of mipi dphy pll is %luMHz\n", + (dpll_out / MHZ)); + + return dpll_out; +} + +static int exynos_mipi_dsi_set_clock(struct mipi_dsim_device *dsim, + unsigned int byte_clk_sel, unsigned int enable) +{ + unsigned int esc_div; + unsigned long esc_clk_error_rate; + unsigned long hs_clk = 0, byte_clk = 0, escape_clk = 0; + + if (enable) { + dsim->e_clk_src = byte_clk_sel; + + /* Escape mode clock and byte clock source */ + exynos_mipi_dsi_set_byte_clock_src(dsim, byte_clk_sel); + + /* DPHY, DSIM Link : D-PHY clock out */ + if (byte_clk_sel == DSIM_PLL_OUT_DIV8) { + hs_clk = exynos_mipi_dsi_change_pll(dsim, + dsim->dsim_config->p, dsim->dsim_config->m, + dsim->dsim_config->s); + if (hs_clk == 0) { + dev_err(dsim->dev, + "failed to get hs clock.\n"); + return -EINVAL; + } + + byte_clk = hs_clk / 8; + exynos_mipi_dsi_enable_pll_bypass(dsim, 0); + exynos_mipi_dsi_pll_on(dsim, 1); + /* DPHY : D-PHY clock out, DSIM link : external clock out */ + } else if (byte_clk_sel == DSIM_EXT_CLK_DIV8) { + dev_warn(dsim->dev, "this project is not support\n"); + dev_warn(dsim->dev, + "external clock source for MIPI DSIM.\n"); + } else if (byte_clk_sel == DSIM_EXT_CLK_BYPASS) { + dev_warn(dsim->dev, "this project is not support\n"); + dev_warn(dsim->dev, + "external clock source for MIPI DSIM\n"); + } + + /* escape clock divider */ + esc_div = byte_clk / (dsim->dsim_config->esc_clk); + dev_dbg(dsim->dev, + "esc_div = %d, byte_clk = %lu, esc_clk = %lu\n", + esc_div, byte_clk, dsim->dsim_config->esc_clk); + if ((byte_clk / esc_div) >= (20 * MHZ) || + (byte_clk / esc_div) > + dsim->dsim_config->esc_clk) + esc_div += 1; + + escape_clk = byte_clk / esc_div; + dev_dbg(dsim->dev, + "escape_clk = %lu, byte_clk = %lu, esc_div = %d\n", + escape_clk, byte_clk, esc_div); + + /* enable escape clock. */ + exynos_mipi_dsi_enable_byte_clock(dsim, 1); + + /* enable byte clk and escape clock */ + exynos_mipi_dsi_set_esc_clk_prs(dsim, 1, esc_div); + /* escape clock on lane */ + exynos_mipi_dsi_enable_esc_clk_on_lane(dsim, + (DSIM_LANE_CLOCK | dsim->data_lane), 1); + + dev_dbg(dsim->dev, "byte clock is %luMHz\n", + (byte_clk / MHZ)); + dev_dbg(dsim->dev, "escape clock that user's need is %lu\n", + (dsim->dsim_config->esc_clk / MHZ)); + dev_dbg(dsim->dev, "escape clock divider is %x\n", esc_div); + dev_dbg(dsim->dev, "escape clock is %luMHz\n", + ((byte_clk / esc_div) / MHZ)); + + if ((byte_clk / esc_div) > escape_clk) { + esc_clk_error_rate = escape_clk / + (byte_clk / esc_div); + dev_warn(dsim->dev, "error rate is %lu over.\n", + (esc_clk_error_rate / 100)); + } else if ((byte_clk / esc_div) < (escape_clk)) { + esc_clk_error_rate = (byte_clk / esc_div) / + escape_clk; + dev_warn(dsim->dev, "error rate is %lu under.\n", + (esc_clk_error_rate / 100)); + } + } else { + exynos_mipi_dsi_enable_esc_clk_on_lane(dsim, + (DSIM_LANE_CLOCK | dsim->data_lane), 0); + exynos_mipi_dsi_set_esc_clk_prs(dsim, 0, 0); + + /* disable escape clock. */ + exynos_mipi_dsi_enable_byte_clock(dsim, 0); + + if (byte_clk_sel == DSIM_PLL_OUT_DIV8) + exynos_mipi_dsi_pll_on(dsim, 0); + } + + return 0; +} + +int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim) +{ + dsim->state = DSIM_STATE_INIT; + + switch (dsim->dsim_config->e_no_data_lane) { + case DSIM_DATA_LANE_1: + dsim->data_lane = DSIM_LANE_DATA0; + break; + case DSIM_DATA_LANE_2: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1; + break; + case DSIM_DATA_LANE_3: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 | + DSIM_LANE_DATA2; + break; + case DSIM_DATA_LANE_4: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 | + DSIM_LANE_DATA2 | DSIM_LANE_DATA3; + break; + default: + dev_info(dsim->dev, "data lane is invalid.\n"); + return -EINVAL; + } + + exynos_mipi_dsi_sw_reset(dsim); + exynos_mipi_dsi_func_reset(dsim); + + exynos_mipi_dsi_dp_dn_swap(dsim, 0); + + return 0; +} + +void exynos_mipi_dsi_init_interrupt(struct mipi_dsim_device *dsim) +{ + unsigned int src = 0; + + src = (INTSRC_SFR_FIFO_EMPTY | INTSRC_RX_DATA_DONE); + exynos_mipi_dsi_set_interrupt(dsim, src, 1); + + src = 0; + src = ~(INTMSK_RX_DONE | INTMSK_FIFO_EMPTY); + exynos_mipi_dsi_set_interrupt_mask(dsim, src, 1); +} + +int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + /* enable only frame done interrupt */ + exynos_mipi_dsi_set_interrupt_mask(dsim, INTMSK_FRAME_DONE, enable); + + return 0; +} + +void exynos_mipi_dsi_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + + /* consider Main display and Sub display. */ + + exynos_mipi_dsi_set_main_stand_by(dsim, enable); +} + +int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config) +{ + struct mipi_dsim_platform_data *dsim_pd; + struct fb_videomode *timing; + + dsim_pd = (struct mipi_dsim_platform_data *)dsim->pd; + timing = (struct fb_videomode *)dsim_pd->lcd_panel_info; + + /* in case of VIDEO MODE (RGB INTERFACE), it sets polarities. */ + if (dsim_config->e_interface == (u32) DSIM_VIDEO) { + if (dsim_config->auto_vertical_cnt == 0) { + exynos_mipi_dsi_set_main_disp_vporch(dsim, + dsim_config->cmd_allow, + timing->lower_margin, + timing->upper_margin); + exynos_mipi_dsi_set_main_disp_hporch(dsim, + timing->right_margin, + timing->left_margin); + exynos_mipi_dsi_set_main_disp_sync_area(dsim, + timing->vsync_len, + timing->hsync_len); + } + } + + exynos_mipi_dsi_set_main_disp_resol(dsim, timing->xres, + timing->yres); + + exynos_mipi_dsi_display_config(dsim, dsim_config); + + dev_info(dsim->dev, "lcd panel ==> width = %d, height = %d\n", + timing->xres, timing->yres); + + return 0; +} + +int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim) +{ + unsigned int time_out = 100; + + switch (dsim->state) { + case DSIM_STATE_INIT: + exynos_mipi_dsi_init_fifo_pointer(dsim, 0x1f); + + /* dsi configuration */ + exynos_mipi_dsi_init_config(dsim); + exynos_mipi_dsi_enable_lane(dsim, DSIM_LANE_CLOCK, 1); + exynos_mipi_dsi_enable_lane(dsim, dsim->data_lane, 1); + + /* set clock configuration */ + exynos_mipi_dsi_set_clock(dsim, dsim->dsim_config->e_byte_clk, 1); + + /* check clock and data lane state are stop state */ + while (!(exynos_mipi_dsi_is_lane_state(dsim))) { + time_out--; + if (time_out == 0) { + dev_err(dsim->dev, + "DSI Master is not stop state.\n"); + dev_err(dsim->dev, + "Check initialization process\n"); + + return -EINVAL; + } + } + if (time_out != 0) { + dev_info(dsim->dev, + "DSI Master driver has been completed.\n"); + dev_info(dsim->dev, "DSI Master state is stop state\n"); + } + + dsim->state = DSIM_STATE_STOP; + + /* BTA sequence counters */ + exynos_mipi_dsi_set_stop_state_counter(dsim, + dsim->dsim_config->stop_holding_cnt); + exynos_mipi_dsi_set_bta_timeout(dsim, + dsim->dsim_config->bta_timeout); + exynos_mipi_dsi_set_lpdr_timeout(dsim, + dsim->dsim_config->rx_timeout); + + return 0; + default: + dev_info(dsim->dev, "DSI Master is already init.\n"); + return 0; + } + + return 0; +} + +int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim) +{ + if (dsim->state != DSIM_STATE_STOP) { + dev_warn(dsim->dev, "DSIM is not in stop state.\n"); + return 0; + } + + if (dsim->e_clk_src == DSIM_EXT_CLK_BYPASS) { + dev_warn(dsim->dev, "clock source is external bypass.\n"); + return 0; + } + + dsim->state = DSIM_STATE_HSCLKEN; + + /* set LCDC and CPU transfer mode to HS. */ + exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0); + exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0); + exynos_mipi_dsi_enable_hs_clock(dsim, 1); + + return 0; +} + +int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int mode) +{ + if (mode) { + if (dsim->state != DSIM_STATE_HSCLKEN) { + dev_err(dsim->dev, "HS Clock lane is not enabled.\n"); + return -EINVAL; + } + + exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0); + } else { + if (dsim->state == DSIM_STATE_INIT || dsim->state == + DSIM_STATE_ULPS) { + dev_err(dsim->dev, + "DSI Master is not STOP or HSDT state.\n"); + return -EINVAL; + } + + exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0); + } + + return 0; +} + +int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim) +{ + return _exynos_mipi_dsi_get_frame_done_status(dsim); +} + +int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim) +{ + _exynos_mipi_dsi_clear_frame_done(dsim); + + return 0; +} + +int exynos_mipi_dsi_fifo_clear(struct mipi_dsim_device *dsim, + unsigned int val) +{ + int try = TRY_FIFO_CLEAR; + + exynos_mipi_dsi_sw_reset_release(dsim); + exynos_mipi_dsi_func_reset(dsim); + + do { + if (exynos_mipi_dsi_get_sw_reset_release(dsim)) { + exynos_mipi_dsi_init_interrupt(dsim); + dev_dbg(dsim->dev, "reset release done.\n"); + return 0; + } + } while (--try); + + dev_err(dsim->dev, "failed to clear dsim fifo.\n"); + return -EAGAIN; +} + +MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>"); +MODULE_DESCRIPTION("Samusung SoC MIPI-DSI common driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/exynos/exynos_mipi_dsi_common.h b/drivers/video/fbdev/exynos/exynos_mipi_dsi_common.h new file mode 100644 index 000000000000..412552274df3 --- /dev/null +++ b/drivers/video/fbdev/exynos/exynos_mipi_dsi_common.h @@ -0,0 +1,46 @@ +/* linux/drivers/video/exynos_mipi_dsi_common.h + * + * Header file for Samsung SoC MIPI-DSI common driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae <inki.dae@samsung.com> + * Donghwa Lee <dh09.lee@samsung.com> + * + * 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. +*/ + +#ifndef _EXYNOS_MIPI_DSI_COMMON_H +#define _EXYNOS_MIPI_DSI_COMMON_H + +static DECLARE_COMPLETION(dsim_rd_comp); +static DECLARE_COMPLETION(dsim_wr_comp); + +int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id, + const unsigned char *data0, unsigned int data_size); +int exynos_mipi_dsi_rd_data(struct mipi_dsim_device *dsim, unsigned int data_id, + unsigned int data0, unsigned int req_size, u8 *rx_buf); +irqreturn_t exynos_mipi_dsi_interrupt_handler(int irq, void *dev_id); +void exynos_mipi_dsi_init_interrupt(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable); +int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_info); +int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int mode); +int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim, + unsigned int enable); +int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim); + +extern struct fb_info *registered_fb[FB_MAX] __read_mostly; + +int exynos_mipi_dsi_fifo_clear(struct mipi_dsim_device *dsim, + unsigned int val); + +#endif /* _EXYNOS_MIPI_DSI_COMMON_H */ diff --git a/drivers/video/fbdev/exynos/exynos_mipi_dsi_lowlevel.c b/drivers/video/fbdev/exynos/exynos_mipi_dsi_lowlevel.c new file mode 100644 index 000000000000..c148d06540c1 --- /dev/null +++ b/drivers/video/fbdev/exynos/exynos_mipi_dsi_lowlevel.c @@ -0,0 +1,618 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c + * + * Samsung SoC MIPI-DSI lowlevel driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * + * 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. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/ctype.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +#include <video/exynos_mipi_dsim.h> + +#include "exynos_mipi_dsi_regs.h" +#include "exynos_mipi_dsi_lowlevel.h" + +void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_SWRST); + + reg |= DSIM_FUNCRST; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_SWRST); +} + +void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_SWRST); + + reg |= DSIM_SWRST; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_SWRST); +} + +void exynos_mipi_dsi_sw_reset_release(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + reg |= INTSRC_SW_RST_RELEASE; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +int exynos_mipi_dsi_get_sw_reset_release(struct mipi_dsim_device *dsim) +{ + return (readl(dsim->reg_base + EXYNOS_DSIM_INTSRC)) & + INTSRC_SW_RST_RELEASE; +} + +unsigned int exynos_mipi_dsi_read_interrupt_mask(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_INTMSK); + + return reg; +} + +void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim, + unsigned int mode, unsigned int mask) +{ + unsigned int reg = 0; + + if (mask) + reg |= mode; + else + reg &= ~mode; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTMSK); +} + +void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim, + unsigned int cfg) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_FIFOCTRL); + + writel(reg & ~(cfg), dsim->reg_base + EXYNOS_DSIM_FIFOCTRL); + mdelay(10); + reg |= cfg; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_FIFOCTRL); +} + +/* + * this function set PLL P, M and S value in D-PHY + */ +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value) +{ + writel(DSIM_AFC_CTL(value), dsim->reg_base + EXYNOS_DSIM_PHYACCHR); +} + +void exynos_mipi_dsi_set_main_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_MDRESOL); + + reg &= ~DSIM_MAIN_STAND_BY; + + if (enable) + reg |= DSIM_MAIN_STAND_BY; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MDRESOL); +} + +void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim, + unsigned int width_resol, unsigned int height_resol) +{ + unsigned int reg; + + /* standby should be set after configuration so set to not ready*/ + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MDRESOL)) & + ~(DSIM_MAIN_STAND_BY); + writel(reg, dsim->reg_base + EXYNOS_DSIM_MDRESOL); + + reg &= ~((0x7ff << 16) | (0x7ff << 0)); + reg |= DSIM_MAIN_VRESOL(height_resol) | DSIM_MAIN_HRESOL(width_resol); + + reg |= DSIM_MAIN_STAND_BY; + writel(reg, dsim->reg_base + EXYNOS_DSIM_MDRESOL); +} + +void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim, + unsigned int cmd_allow, unsigned int vfront, unsigned int vback) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MVPORCH)) & + ~((DSIM_CMD_ALLOW_MASK) | (DSIM_STABLE_VFP_MASK) | + (DSIM_MAIN_VBP_MASK)); + + reg |= (DSIM_CMD_ALLOW_SHIFT(cmd_allow & 0xf) | + DSIM_STABLE_VFP_SHIFT(vfront & 0x7ff) | + DSIM_MAIN_VBP_SHIFT(vback & 0x7ff)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MVPORCH); +} + +void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim, + unsigned int front, unsigned int back) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MHPORCH)) & + ~((DSIM_MAIN_HFP_MASK) | (DSIM_MAIN_HBP_MASK)); + + reg |= DSIM_MAIN_HFP_SHIFT(front) | DSIM_MAIN_HBP_SHIFT(back); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MHPORCH); +} + +void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MSYNC)) & + ~((DSIM_MAIN_VSA_MASK) | (DSIM_MAIN_HSA_MASK)); + + reg |= (DSIM_MAIN_VSA_SHIFT(vert & 0x3ff) | + DSIM_MAIN_HSA_SHIFT(hori)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MSYNC); +} + +void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_SDRESOL)) & + ~(DSIM_SUB_STANDY_MASK); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_SDRESOL); + + reg &= ~(DSIM_SUB_VRESOL_MASK) | ~(DSIM_SUB_HRESOL_MASK); + reg |= (DSIM_SUB_VRESOL_SHIFT(vert & 0x7ff) | + DSIM_SUB_HRESOL_SHIFT(hori & 0x7ff)); + writel(reg, dsim->reg_base + EXYNOS_DSIM_SDRESOL); + + reg |= DSIM_SUB_STANDY_SHIFT(1); + writel(reg, dsim->reg_base + EXYNOS_DSIM_SDRESOL); +} + +void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim) +{ + struct mipi_dsim_config *dsim_config = dsim->dsim_config; + + unsigned int cfg = (readl(dsim->reg_base + EXYNOS_DSIM_CONFIG)) & + ~((1 << 28) | (0x1f << 20) | (0x3 << 5)); + + cfg = ((DSIM_AUTO_FLUSH(dsim_config->auto_flush)) | + (DSIM_EOT_DISABLE(dsim_config->eot_disable)) | + (DSIM_AUTO_MODE_SHIFT(dsim_config->auto_vertical_cnt)) | + (DSIM_HSE_MODE_SHIFT(dsim_config->hse)) | + (DSIM_HFP_MODE_SHIFT(dsim_config->hfp)) | + (DSIM_HBP_MODE_SHIFT(dsim_config->hbp)) | + (DSIM_HSA_MODE_SHIFT(dsim_config->hsa)) | + (DSIM_NUM_OF_DATALANE_SHIFT(dsim_config->e_no_data_lane))); + + writel(cfg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + +void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config) +{ + u32 reg = (readl(dsim->reg_base + EXYNOS_DSIM_CONFIG)) & + ~((0x3 << 26) | (1 << 25) | (0x3 << 18) | (0x7 << 12) | + (0x3 << 16) | (0x7 << 8)); + + if (dsim_config->e_interface == DSIM_VIDEO) + reg |= (1 << 25); + else if (dsim_config->e_interface == DSIM_COMMAND) + reg &= ~(1 << 25); + else { + dev_err(dsim->dev, "unknown lcd type.\n"); + return; + } + + /* main lcd */ + reg |= ((u8) (dsim_config->e_burst_mode) & 0x3) << 26 | + ((u8) (dsim_config->e_virtual_ch) & 0x3) << 18 | + ((u8) (dsim_config->e_pixel_format) & 0x7) << 12; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + +void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim, unsigned int lane, + unsigned int enable) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_CONFIG); + + if (enable) + reg |= DSIM_LANE_ENx(lane); + else + reg &= ~DSIM_LANE_ENx(lane); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + + +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count) +{ + unsigned int cfg; + + /* get the data lane number. */ + cfg = DSIM_NUM_OF_DATALANE_SHIFT(count); + + writel(cfg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + +void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim, unsigned int enable, + unsigned int afc_code) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_PHYACCHR); + + if (enable) { + reg |= (1 << 14); + reg &= ~(0x7 << 5); + reg |= (afc_code & 0x7) << 5; + } else + reg &= ~(1 << 14); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PHYACCHR); +} + +void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_PLL_BYPASS_SHIFT(0x1)); + + reg |= DSIM_PLL_BYPASS_SHIFT(enable); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_set_pll_pms(struct mipi_dsim_device *dsim, unsigned int p, + unsigned int m, unsigned int s) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL); + + reg |= ((p & 0x3f) << 13) | ((m & 0x1ff) << 4) | ((s & 0x7) << 1); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim, + unsigned int freq_band) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(DSIM_FREQ_BAND_SHIFT(0x1f)); + + reg |= DSIM_FREQ_BAND_SHIFT(freq_band & 0x1f); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(0x7ffff << 1); + + reg |= (pre_divider & 0x3f) << 13 | (main_divider & 0x1ff) << 4 | + (scaler & 0x7) << 1; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim, + unsigned int lock_time) +{ + writel(lock_time, dsim->reg_base + EXYNOS_DSIM_PLLTMR); +} + +void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim, unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(DSIM_PLL_EN_SHIFT(0x1)); + + reg |= DSIM_PLL_EN_SHIFT(enable & 0x1); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim, + unsigned int src) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_BYTE_CLK_SRC_SHIFT(0x3)); + + reg |= (DSIM_BYTE_CLK_SRC_SHIFT(src)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_BYTE_CLKEN_SHIFT(0x1)); + + reg |= DSIM_BYTE_CLKEN_SHIFT(enable); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int prs_val) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_ESC_CLKEN_SHIFT(0x1) | 0xffff); + + reg |= DSIM_ESC_CLKEN_SHIFT(enable); + if (enable) + reg |= prs_val; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim, + unsigned int lane_sel, unsigned int enable) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL); + + if (enable) + reg |= DSIM_LANE_ESC_CLKEN(lane_sel); + else + + reg &= ~DSIM_LANE_ESC_CLKEN(lane_sel); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE)) & + ~(DSIM_FORCE_STOP_STATE_SHIFT(0x1)); + + reg |= (DSIM_FORCE_STOP_STATE_SHIFT(enable & 0x1)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_STATUS); + + /** + * check clock and data lane states. + * if MIPI-DSI controller was enabled at bootloader then + * TX_READY_HS_CLK is enabled otherwise STOP_STATE_CLK. + * so it should be checked for two case. + */ + if ((reg & DSIM_STOP_STATE_DAT(0xf)) && + ((reg & DSIM_STOP_STATE_CLK) || + (reg & DSIM_TX_READY_HS_CLK))) + return 1; + + return 0; +} + +void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim, + unsigned int cnt_val) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE)) & + ~(DSIM_STOP_STATE_CNT_SHIFT(0x7ff)); + + reg |= (DSIM_STOP_STATE_CNT_SHIFT(cnt_val & 0x7ff)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_TIMEOUT)) & + ~(DSIM_BTA_TOUT_SHIFT(0xff)); + + reg |= (DSIM_BTA_TOUT_SHIFT(timeout)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_TIMEOUT); +} + +void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_TIMEOUT)) & + ~(DSIM_LPDR_TOUT_SHIFT(0xffff)); + + reg |= (DSIM_LPDR_TOUT_SHIFT(timeout)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_TIMEOUT); +} + +void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE); + + reg &= ~DSIM_CMD_LPDT_LP; + + if (lp) + reg |= DSIM_CMD_LPDT_LP; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE); + + reg &= ~DSIM_TX_LPDT_LP; + + if (lp) + reg |= DSIM_TX_LPDT_LP; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_TX_REQUEST_HSCLK_SHIFT(0x1)); + + reg |= DSIM_TX_REQUEST_HSCLK_SHIFT(enable); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim, + unsigned int swap_en) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_PHYACCHR1); + + reg &= ~(0x3 << 0); + reg |= (swap_en & 0x3) << 0; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PHYACCHR1); +} + +void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim, + unsigned int hs_zero) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(0xf << 28); + + reg |= ((hs_zero & 0xf) << 28); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim, unsigned int prep) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(0x7 << 20); + + reg |= ((prep & 0x7) << 20); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +unsigned int exynos_mipi_dsi_read_interrupt(struct mipi_dsim_device *dsim) +{ + return readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim, + unsigned int src) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + reg |= src; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +void exynos_mipi_dsi_set_interrupt(struct mipi_dsim_device *dsim, + unsigned int src, unsigned int enable) +{ + unsigned int reg = 0; + + if (enable) + reg |= src; + else + reg &= ~src; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_STATUS); + + return reg & (1 << 31) ? 1 : 0; +} + +unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim) +{ + return readl(dsim->reg_base + EXYNOS_DSIM_FIFOCTRL) & ~(0x1f); +} + +void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim, + unsigned int di, unsigned int data0, unsigned int data1) +{ + unsigned int reg = (data1 << 16) | (data0 << 8) | ((di & 0x3f) << 0); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PKTHDR); +} + +void exynos_mipi_dsi_rd_tx_header(struct mipi_dsim_device *dsim, + unsigned int di, unsigned int data0) +{ + unsigned int reg = (data0 << 8) | (di << 0); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PKTHDR); +} + +unsigned int exynos_mipi_dsi_rd_rx_fifo(struct mipi_dsim_device *dsim) +{ + return readl(dsim->reg_base + EXYNOS_DSIM_RXFIFO); +} + +unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + return (reg & INTSRC_FRAME_DONE) ? 1 : 0; +} + +void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + writel(reg | INTSRC_FRAME_DONE, dsim->reg_base + + EXYNOS_DSIM_INTSRC); +} + +void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim, + unsigned int tx_data) +{ + writel(tx_data, dsim->reg_base + EXYNOS_DSIM_PAYLOAD); +} diff --git a/drivers/video/fbdev/exynos/exynos_mipi_dsi_lowlevel.h b/drivers/video/fbdev/exynos/exynos_mipi_dsi_lowlevel.h new file mode 100644 index 000000000000..85460701c7ea --- /dev/null +++ b/drivers/video/fbdev/exynos/exynos_mipi_dsi_lowlevel.h @@ -0,0 +1,112 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h + * + * Header file for Samsung SoC MIPI-DSI lowlevel driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae <inki.dae@samsung.com> + * Donghwa Lee <dh09.lee@samsung.com> + * + * 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. +*/ + +#ifndef _EXYNOS_MIPI_DSI_LOWLEVEL_H +#define _EXYNOS_MIPI_DSI_LOWLEVEL_H + +void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_sw_reset_release(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_get_sw_reset_release(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim, + unsigned int mode, unsigned int mask); +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count); +void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim, + unsigned int cfg); +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value); +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value); +void exynos_mipi_dsi_set_main_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim, + unsigned int width_resol, unsigned int height_resol); +void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim, + unsigned int cmd_allow, unsigned int vfront, unsigned int vback); +void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim, + unsigned int front, unsigned int back); +void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori); +void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori); +void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config); +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count); +void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim, unsigned int lane, + unsigned int enable); +void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim, unsigned int enable, + unsigned int afc_code); +void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_pll_pms(struct mipi_dsim_device *dsim, unsigned int p, + unsigned int m, unsigned int s); +void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim, + unsigned int freq_band); +void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler); +void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim, + unsigned int lock_time); +void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim, + unsigned int src); +void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int prs_val); +void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim, + unsigned int lane_sel, unsigned int enable); +void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim, + unsigned int enable); +unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim, + unsigned int cnt_val); +void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout); +void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout); +void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp); +void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp); +void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim, + unsigned int swap_en); +void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim, + unsigned int hs_zero); +void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim, unsigned int prep); +unsigned int exynos_mipi_dsi_read_interrupt(struct mipi_dsim_device *dsim); +unsigned int exynos_mipi_dsi_read_interrupt_mask(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim, + unsigned int src); +void exynos_mipi_dsi_set_interrupt(struct mipi_dsim_device *dsim, + unsigned int src, unsigned int enable); +unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim); +unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim); +unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim); +void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim, unsigned int di, + unsigned int data0, unsigned int data1); +void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim, + unsigned int tx_data); +void exynos_mipi_dsi_rd_tx_header(struct mipi_dsim_device *dsim, + unsigned int data0, unsigned int data1); +unsigned int exynos_mipi_dsi_rd_rx_fifo(struct mipi_dsim_device *dsim); + +#endif /* _EXYNOS_MIPI_DSI_LOWLEVEL_H */ diff --git a/drivers/video/fbdev/exynos/exynos_mipi_dsi_regs.h b/drivers/video/fbdev/exynos/exynos_mipi_dsi_regs.h new file mode 100644 index 000000000000..4227106d3fd0 --- /dev/null +++ b/drivers/video/fbdev/exynos/exynos_mipi_dsi_regs.h @@ -0,0 +1,149 @@ +/* linux/driver/video/exynos/exynos_mipi_dsi_regs.h + * + * Register definition file for Samsung MIPI-DSIM driver + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae <inki.dae@samsung.com> + * Donghwa Lee <dh09.lee@samsung.com> + * + * 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. +*/ + +#ifndef _EXYNOS_MIPI_DSI_REGS_H +#define _EXYNOS_MIPI_DSI_REGS_H + +#define EXYNOS_DSIM_STATUS 0x0 /* Status register */ +#define EXYNOS_DSIM_SWRST 0x4 /* Software reset register */ +#define EXYNOS_DSIM_CLKCTRL 0x8 /* Clock control register */ +#define EXYNOS_DSIM_TIMEOUT 0xc /* Time out register */ +#define EXYNOS_DSIM_CONFIG 0x10 /* Configuration register */ +#define EXYNOS_DSIM_ESCMODE 0x14 /* Escape mode register */ + +/* Main display image resolution register */ +#define EXYNOS_DSIM_MDRESOL 0x18 +#define EXYNOS_DSIM_MVPORCH 0x1c /* Main display Vporch register */ +#define EXYNOS_DSIM_MHPORCH 0x20 /* Main display Hporch register */ +#define EXYNOS_DSIM_MSYNC 0x24 /* Main display sync area register */ + +/* Sub display image resolution register */ +#define EXYNOS_DSIM_SDRESOL 0x28 +#define EXYNOS_DSIM_INTSRC 0x2c /* Interrupt source register */ +#define EXYNOS_DSIM_INTMSK 0x30 /* Interrupt mask register */ +#define EXYNOS_DSIM_PKTHDR 0x34 /* Packet Header FIFO register */ +#define EXYNOS_DSIM_PAYLOAD 0x38 /* Payload FIFO register */ +#define EXYNOS_DSIM_RXFIFO 0x3c /* Read FIFO register */ +#define EXYNOS_DSIM_FIFOTHLD 0x40 /* FIFO threshold level register */ +#define EXYNOS_DSIM_FIFOCTRL 0x44 /* FIFO status and control register */ + +/* FIFO memory AC characteristic register */ +#define EXYNOS_DSIM_PLLCTRL 0x4c /* PLL control register */ +#define EXYNOS_DSIM_PLLTMR 0x50 /* PLL timer register */ +#define EXYNOS_DSIM_PHYACCHR 0x54 /* D-PHY AC characteristic register */ +#define EXYNOS_DSIM_PHYACCHR1 0x58 /* D-PHY AC characteristic register1 */ + +/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) + +/* DSIM_SWRST */ +#define DSIM_FUNCRST (1 << 16) +#define DSIM_SWRST (1 << 0) + +/* EXYNOS_DSIM_TIMEOUT */ +#define DSIM_LPDR_TOUT_SHIFT(x) ((x) << 0) +#define DSIM_BTA_TOUT_SHIFT(x) ((x) << 16) + +/* EXYNOS_DSIM_CLKCTRL */ +#define DSIM_LANE_ESC_CLKEN(x) (((x) & 0x1f) << 19) +#define DSIM_BYTE_CLKEN_SHIFT(x) ((x) << 24) +#define DSIM_BYTE_CLK_SRC_SHIFT(x) ((x) << 25) +#define DSIM_PLL_BYPASS_SHIFT(x) ((x) << 27) +#define DSIM_ESC_CLKEN_SHIFT(x) ((x) << 28) +#define DSIM_TX_REQUEST_HSCLK_SHIFT(x) ((x) << 31) + +/* EXYNOS_DSIM_CONFIG */ +#define DSIM_LANE_ENx(x) (((x) & 0x1f) << 0) +#define DSIM_NUM_OF_DATALANE_SHIFT(x) ((x) << 5) +#define DSIM_HSA_MODE_SHIFT(x) ((x) << 20) +#define DSIM_HBP_MODE_SHIFT(x) ((x) << 21) +#define DSIM_HFP_MODE_SHIFT(x) ((x) << 22) +#define DSIM_HSE_MODE_SHIFT(x) ((x) << 23) +#define DSIM_AUTO_MODE_SHIFT(x) ((x) << 24) +#define DSIM_EOT_DISABLE(x) ((x) << 28) +#define DSIM_AUTO_FLUSH(x) ((x) << 29) + +#define DSIM_NUM_OF_DATA_LANE(x) ((x) << DSIM_NUM_OF_DATALANE_SHIFT) + +/* EXYNOS_DSIM_ESCMODE */ +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_STOP_STATE_SHIFT(x) ((x) << 20) +#define DSIM_STOP_STATE_CNT_SHIFT(x) ((x) << 21) + +/* EXYNOS_DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x) (((x) & 0x7ff) << 16) +#define DSIM_MAIN_HRESOL(x) (((x) & 0X7ff) << 0) + +/* EXYNOS_DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW_SHIFT(x) ((x) << 28) +#define DSIM_STABLE_VFP_SHIFT(x) ((x) << 16) +#define DSIM_MAIN_VBP_SHIFT(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0) + +/* EXYNOS_DSIM_MHPORCH */ +#define DSIM_MAIN_HFP_SHIFT(x) ((x) << 16) +#define DSIM_MAIN_HBP_SHIFT(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0) + +/* EXYNOS_DSIM_MSYNC */ +#define DSIM_MAIN_VSA_SHIFT(x) ((x) << 22) +#define DSIM_MAIN_HSA_SHIFT(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0) + +/* EXYNOS_DSIM_SDRESOL */ +#define DSIM_SUB_STANDY_SHIFT(x) ((x) << 31) +#define DSIM_SUB_VRESOL_SHIFT(x) ((x) << 16) +#define DSIM_SUB_HRESOL_SHIFT(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0) + +/* EXYNOS_DSIM_INTSRC */ +#define INTSRC_PLL_STABLE (1 << 31) +#define INTSRC_SW_RST_RELEASE (1 << 30) +#define INTSRC_SFR_FIFO_EMPTY (1 << 29) +#define INTSRC_FRAME_DONE (1 << 24) +#define INTSRC_RX_DATA_DONE (1 << 18) + +/* EXYNOS_DSIM_INTMSK */ +#define INTMSK_FIFO_EMPTY (1 << 29) +#define INTMSK_BTA (1 << 25) +#define INTMSK_FRAME_DONE (1 << 24) +#define INTMSK_RX_TIMEOUT (1 << 21) +#define INTMSK_BTA_TIMEOUT (1 << 20) +#define INTMSK_RX_DONE (1 << 18) +#define INTMSK_RX_TE (1 << 17) +#define INTMSK_RX_ACK (1 << 16) +#define INTMSK_RX_ECC_ERR (1 << 15) +#define INTMSK_RX_CRC_ERR (1 << 14) + +/* EXYNOS_DSIM_FIFOCTRL */ +#define SFR_HEADER_EMPTY (1 << 22) + +/* EXYNOS_DSIM_PHYACCHR */ +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5) + +/* EXYNOS_DSIM_PLLCTRL */ +#define DSIM_PLL_EN_SHIFT(x) ((x) << 23) +#define DSIM_FREQ_BAND_SHIFT(x) ((x) << 24) + +#endif /* _EXYNOS_MIPI_DSI_REGS_H */ diff --git a/drivers/video/fbdev/exynos/s6e8ax0.c b/drivers/video/fbdev/exynos/s6e8ax0.c new file mode 100644 index 000000000000..29e70ed3f154 --- /dev/null +++ b/drivers/video/fbdev/exynos/s6e8ax0.c @@ -0,0 +1,898 @@ +/* linux/drivers/video/exynos/s6e8ax0.c + * + * MIPI-DSI based s6e8ax0 AMOLED lcd 4.65 inch panel driver. + * + * Inki Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * + * 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. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/ctype.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/lcd.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> +#include <video/exynos_mipi_dsim.h> + +#define LDI_MTP_LENGTH 24 +#define DSIM_PM_STABLE_TIME 10 +#define MIN_BRIGHTNESS 0 +#define MAX_BRIGHTNESS 24 +#define GAMMA_TABLE_COUNT 26 + +#define POWER_IS_ON(pwr) ((pwr) == FB_BLANK_UNBLANK) +#define POWER_IS_OFF(pwr) ((pwr) == FB_BLANK_POWERDOWN) +#define POWER_IS_NRM(pwr) ((pwr) == FB_BLANK_NORMAL) + +#define lcd_to_master(a) (a->dsim_dev->master) +#define lcd_to_master_ops(a) ((lcd_to_master(a))->master_ops) + +enum { + DSIM_NONE_STATE = 0, + DSIM_RESUME_COMPLETE = 1, + DSIM_FRAME_DONE = 2, +}; + +struct s6e8ax0 { + struct device *dev; + unsigned int power; + unsigned int id; + unsigned int gamma; + unsigned int acl_enable; + unsigned int cur_acl; + + struct lcd_device *ld; + struct backlight_device *bd; + + struct mipi_dsim_lcd_device *dsim_dev; + struct lcd_platform_data *ddi_pd; + struct mutex lock; + bool enabled; +}; + + +static struct regulator_bulk_data supplies[] = { + { .supply = "vdd3", }, + { .supply = "vci", }, +}; + +static void s6e8ax0_regulator_enable(struct s6e8ax0 *lcd) +{ + int ret = 0; + struct lcd_platform_data *pd = NULL; + + pd = lcd->ddi_pd; + mutex_lock(&lcd->lock); + if (!lcd->enabled) { + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + if (ret) + goto out; + + lcd->enabled = true; + } + msleep(pd->power_on_delay); +out: + mutex_unlock(&lcd->lock); +} + +static void s6e8ax0_regulator_disable(struct s6e8ax0 *lcd) +{ + int ret = 0; + + mutex_lock(&lcd->lock); + if (lcd->enabled) { + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + if (ret) + goto out; + + lcd->enabled = false; + } +out: + mutex_unlock(&lcd->lock); +} + +static const unsigned char s6e8ax0_22_gamma_30[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xf5, 0x00, 0xff, 0xad, 0xaf, + 0xbA, 0xc3, 0xd8, 0xc5, 0x9f, 0xc6, 0x9e, 0xc1, 0xdc, 0xc0, + 0x00, 0x61, 0x00, 0x5a, 0x00, 0x74, +}; + +static const unsigned char s6e8ax0_22_gamma_50[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xe8, 0x1f, 0xf7, 0xad, 0xc0, + 0xb5, 0xc4, 0xdc, 0xc4, 0x9e, 0xc6, 0x9c, 0xbb, 0xd8, 0xbb, + 0x00, 0x70, 0x00, 0x68, 0x00, 0x86, +}; + +static const unsigned char s6e8ax0_22_gamma_60[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xde, 0x1f, 0xef, 0xad, 0xc4, + 0xb3, 0xc3, 0xdd, 0xc4, 0x9e, 0xc6, 0x9c, 0xbc, 0xd6, 0xba, + 0x00, 0x75, 0x00, 0x6e, 0x00, 0x8d, +}; + +static const unsigned char s6e8ax0_22_gamma_70[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xd8, 0x1f, 0xe7, 0xaf, 0xc8, + 0xb4, 0xc4, 0xdd, 0xc3, 0x9d, 0xc6, 0x9c, 0xbb, 0xd6, 0xb9, + 0x00, 0x7a, 0x00, 0x72, 0x00, 0x93, +}; + +static const unsigned char s6e8ax0_22_gamma_80[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xc9, 0x1f, 0xde, 0xae, 0xc9, + 0xb1, 0xc3, 0xdd, 0xc2, 0x9d, 0xc5, 0x9b, 0xbc, 0xd6, 0xbb, + 0x00, 0x7f, 0x00, 0x77, 0x00, 0x99, +}; + +static const unsigned char s6e8ax0_22_gamma_90[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xc7, 0x1f, 0xd9, 0xb0, 0xcc, + 0xb2, 0xc3, 0xdc, 0xc1, 0x9c, 0xc6, 0x9c, 0xbc, 0xd4, 0xb9, + 0x00, 0x83, 0x00, 0x7b, 0x00, 0x9e, +}; + +static const unsigned char s6e8ax0_22_gamma_100[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xbd, 0x80, 0xcd, 0xba, 0xce, + 0xb3, 0xc4, 0xde, 0xc3, 0x9c, 0xc4, 0x9, 0xb8, 0xd3, 0xb6, + 0x00, 0x88, 0x00, 0x80, 0x00, 0xa5, +}; + +static const unsigned char s6e8ax0_22_gamma_120[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb9, 0x95, 0xc8, 0xb1, 0xcf, + 0xb2, 0xc6, 0xdf, 0xc5, 0x9b, 0xc3, 0x99, 0xb6, 0xd2, 0xb6, + 0x00, 0x8f, 0x00, 0x86, 0x00, 0xac, +}; + +static const unsigned char s6e8ax0_22_gamma_130[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb7, 0xa0, 0xc7, 0xb1, 0xd0, + 0xb2, 0xc4, 0xdd, 0xc3, 0x9a, 0xc3, 0x98, 0xb6, 0xd0, 0xb4, + 0x00, 0x92, 0x00, 0x8a, 0x00, 0xb1, +}; + +static const unsigned char s6e8ax0_22_gamma_140[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb7, 0xa0, 0xc5, 0xb2, 0xd0, + 0xb3, 0xc3, 0xde, 0xc3, 0x9b, 0xc2, 0x98, 0xb6, 0xd0, 0xb4, + 0x00, 0x95, 0x00, 0x8d, 0x00, 0xb5, +}; + +static const unsigned char s6e8ax0_22_gamma_150[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xa0, 0xc2, 0xb2, 0xd0, + 0xb2, 0xc1, 0xdd, 0xc2, 0x9b, 0xc2, 0x98, 0xb4, 0xcf, 0xb1, + 0x00, 0x99, 0x00, 0x90, 0x00, 0xba, +}; + +static const unsigned char s6e8ax0_22_gamma_160[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xa5, 0xbf, 0xb0, 0xd0, + 0xb1, 0xc3, 0xde, 0xc2, 0x99, 0xc1, 0x97, 0xb4, 0xce, 0xb1, + 0x00, 0x9c, 0x00, 0x93, 0x00, 0xbe, +}; + +static const unsigned char s6e8ax0_22_gamma_170[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb5, 0xbf, 0xb1, 0xd1, + 0xb1, 0xc3, 0xde, 0xc3, 0x99, 0xc0, 0x96, 0xb4, 0xce, 0xb1, + 0x00, 0x9f, 0x00, 0x96, 0x00, 0xc2, +}; + +static const unsigned char s6e8ax0_22_gamma_180[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb7, 0xbe, 0xb3, 0xd2, + 0xb3, 0xc3, 0xde, 0xc2, 0x97, 0xbf, 0x95, 0xb4, 0xcd, 0xb1, + 0x00, 0xa2, 0x00, 0x99, 0x00, 0xc5, +}; + +static const unsigned char s6e8ax0_22_gamma_190[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb9, 0xbe, 0xb2, 0xd2, + 0xb2, 0xc3, 0xdd, 0xc3, 0x98, 0xbf, 0x95, 0xb2, 0xcc, 0xaf, + 0x00, 0xa5, 0x00, 0x9c, 0x00, 0xc9, +}; + +static const unsigned char s6e8ax0_22_gamma_200[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb9, 0xbc, 0xb2, 0xd2, + 0xb1, 0xc4, 0xdd, 0xc3, 0x97, 0xbe, 0x95, 0xb1, 0xcb, 0xae, + 0x00, 0xa8, 0x00, 0x9f, 0x00, 0xcd, +}; + +static const unsigned char s6e8ax0_22_gamma_210[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc1, 0xbd, 0xb1, 0xd1, + 0xb1, 0xc2, 0xde, 0xc2, 0x97, 0xbe, 0x94, 0xB0, 0xc9, 0xad, + 0x00, 0xae, 0x00, 0xa4, 0x00, 0xd4, +}; + +static const unsigned char s6e8ax0_22_gamma_220[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc7, 0xbd, 0xb1, 0xd1, + 0xb1, 0xc2, 0xdd, 0xc2, 0x97, 0xbd, 0x94, 0xb0, 0xc9, 0xad, + 0x00, 0xad, 0x00, 0xa2, 0x00, 0xd3, +}; + +static const unsigned char s6e8ax0_22_gamma_230[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc3, 0xbd, 0xb2, 0xd1, + 0xb1, 0xc3, 0xdd, 0xc1, 0x96, 0xbd, 0x94, 0xb0, 0xc9, 0xad, + 0x00, 0xb0, 0x00, 0xa7, 0x00, 0xd7, +}; + +static const unsigned char s6e8ax0_22_gamma_240[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xcb, 0xbd, 0xb1, 0xd2, + 0xb1, 0xc3, 0xdD, 0xc2, 0x95, 0xbd, 0x93, 0xaf, 0xc8, 0xab, + 0x00, 0xb3, 0x00, 0xa9, 0x00, 0xdb, +}; + +static const unsigned char s6e8ax0_22_gamma_250[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xcc, 0xbe, 0xb0, 0xd2, + 0xb0, 0xc3, 0xdD, 0xc2, 0x94, 0xbc, 0x92, 0xae, 0xc8, 0xab, + 0x00, 0xb6, 0x00, 0xab, 0x00, 0xde, +}; + +static const unsigned char s6e8ax0_22_gamma_260[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xd0, 0xbe, 0xaf, 0xd1, + 0xaf, 0xc2, 0xdd, 0xc1, 0x96, 0xbc, 0x93, 0xaf, 0xc8, 0xac, + 0x00, 0xb7, 0x00, 0xad, 0x00, 0xe0, +}; + +static const unsigned char s6e8ax0_22_gamma_270[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb2, 0xcF, 0xbd, 0xb0, 0xd2, + 0xaf, 0xc2, 0xdc, 0xc1, 0x95, 0xbd, 0x93, 0xae, 0xc6, 0xaa, + 0x00, 0xba, 0x00, 0xb0, 0x00, 0xe4, +}; + +static const unsigned char s6e8ax0_22_gamma_280[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb2, 0xd0, 0xbd, 0xaf, 0xd0, + 0xad, 0xc4, 0xdd, 0xc3, 0x95, 0xbd, 0x93, 0xac, 0xc5, 0xa9, + 0x00, 0xbd, 0x00, 0xb2, 0x00, 0xe7, +}; + +static const unsigned char s6e8ax0_22_gamma_300[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb5, 0xd3, 0xbd, 0xb1, 0xd2, + 0xb0, 0xc0, 0xdc, 0xc0, 0x94, 0xba, 0x91, 0xac, 0xc5, 0xa9, + 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xed, +}; + +static const unsigned char *s6e8ax0_22_gamma_table[] = { + s6e8ax0_22_gamma_30, + s6e8ax0_22_gamma_50, + s6e8ax0_22_gamma_60, + s6e8ax0_22_gamma_70, + s6e8ax0_22_gamma_80, + s6e8ax0_22_gamma_90, + s6e8ax0_22_gamma_100, + s6e8ax0_22_gamma_120, + s6e8ax0_22_gamma_130, + s6e8ax0_22_gamma_140, + s6e8ax0_22_gamma_150, + s6e8ax0_22_gamma_160, + s6e8ax0_22_gamma_170, + s6e8ax0_22_gamma_180, + s6e8ax0_22_gamma_190, + s6e8ax0_22_gamma_200, + s6e8ax0_22_gamma_210, + s6e8ax0_22_gamma_220, + s6e8ax0_22_gamma_230, + s6e8ax0_22_gamma_240, + s6e8ax0_22_gamma_250, + s6e8ax0_22_gamma_260, + s6e8ax0_22_gamma_270, + s6e8ax0_22_gamma_280, + s6e8ax0_22_gamma_300, +}; + +static void s6e8ax0_panel_cond(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + static const unsigned char data_to_send[] = { + 0xf8, 0x3d, 0x35, 0x00, 0x00, 0x00, 0x93, 0x00, 0x3c, 0x7d, + 0x08, 0x27, 0x7d, 0x3f, 0x00, 0x00, 0x00, 0x20, 0x04, 0x08, + 0x6e, 0x00, 0x00, 0x00, 0x02, 0x08, 0x08, 0x23, 0x23, 0xc0, + 0xc8, 0x08, 0x48, 0xc1, 0x00, 0xc1, 0xff, 0xff, 0xc8 + }; + static const unsigned char data_to_send_panel_reverse[] = { + 0xf8, 0x19, 0x35, 0x00, 0x00, 0x00, 0x93, 0x00, 0x3c, 0x7d, + 0x08, 0x27, 0x7d, 0x3f, 0x00, 0x00, 0x00, 0x20, 0x04, 0x08, + 0x6e, 0x00, 0x00, 0x00, 0x02, 0x08, 0x08, 0x23, 0x23, 0xc0, + 0xc1, 0x01, 0x41, 0xc1, 0x00, 0xc1, 0xf6, 0xf6, 0xc1 + }; + + if (lcd->dsim_dev->panel_reverse) + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send_panel_reverse, + ARRAY_SIZE(data_to_send_panel_reverse)); + else + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_cond(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf2, 0x80, 0x03, 0x0d + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +/* Gamma 2.2 Setting (200cd, 7500K, 10MPCD) */ +static void s6e8ax0_gamma_cond(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + unsigned int gamma = lcd->bd->props.brightness; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + s6e8ax0_22_gamma_table[gamma], + GAMMA_TABLE_COUNT); +} + +static void s6e8ax0_gamma_update(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf7, 0x03 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond1(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xd1, 0xfe, 0x80, 0x00, 0x01, 0x0b, 0x00, 0x00, 0x40, + 0x0d, 0x00, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond2(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xff, 0x44, 0x44, 0xc0, + 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond3(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe1, 0x10, 0x1c, 0x17, 0x08, 0x1d + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond4(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe2, 0xed, 0x07, 0xc3, 0x13, 0x0d, 0x03 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond5(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf4, 0xcf, 0x0a, 0x12, 0x10, 0x19, 0x33, 0x02 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} +static void s6e8ax0_etc_cond6(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe3, 0x40 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond7(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe4, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_elvss_set(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xb1, 0x04, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_elvss_nvm_set(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xd9, 0x5c, 0x20, 0x0c, 0x0f, 0x41, 0x00, 0x10, 0x11, + 0x12, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x80, 0xcb, 0xed, + 0x64, 0xaf + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_sleep_in(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x10, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_sleep_out(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x11, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_on(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x29, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_off(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x28, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_apply_level2_key(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf0, 0x5a, 0x5a + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_acl_on(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xc0, 0x01 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_acl_off(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xc0, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +/* Full white 50% reducing setting */ +static void s6e8ax0_acl_ctrl_set(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + /* Full white 50% reducing setting */ + static const unsigned char cutoff_50[] = { + 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf, + 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2a, 0x31, 0x38, + 0x3f, 0x46 + }; + /* Full white 45% reducing setting */ + static const unsigned char cutoff_45[] = { + 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf, + 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x0d, 0x13, 0x19, 0x1f, 0x25, 0x2b, 0x31, + 0x37, 0x3d + }; + /* Full white 40% reducing setting */ + static const unsigned char cutoff_40[] = { + 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf, + 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x06, 0x0c, 0x11, 0x16, 0x1c, 0x21, 0x26, 0x2b, + 0x31, 0x36 + }; + + if (lcd->acl_enable) { + if (lcd->cur_acl == 0) { + if (lcd->gamma == 0 || lcd->gamma == 1) { + s6e8ax0_acl_off(lcd); + dev_dbg(&lcd->ld->dev, + "cur_acl=%d\n", lcd->cur_acl); + } else + s6e8ax0_acl_on(lcd); + } + switch (lcd->gamma) { + case 0: /* 30cd */ + s6e8ax0_acl_off(lcd); + lcd->cur_acl = 0; + break; + case 1 ... 3: /* 50cd ~ 90cd */ + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_LONG_WRITE, + cutoff_40, + ARRAY_SIZE(cutoff_40)); + lcd->cur_acl = 40; + break; + case 4 ... 7: /* 120cd ~ 210cd */ + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_LONG_WRITE, + cutoff_45, + ARRAY_SIZE(cutoff_45)); + lcd->cur_acl = 45; + break; + case 8 ... 10: /* 220cd ~ 300cd */ + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_LONG_WRITE, + cutoff_50, + ARRAY_SIZE(cutoff_50)); + lcd->cur_acl = 50; + break; + default: + break; + } + } else { + s6e8ax0_acl_off(lcd); + lcd->cur_acl = 0; + dev_dbg(&lcd->ld->dev, "cur_acl = %d\n", lcd->cur_acl); + } +} + +static void s6e8ax0_read_id(struct s6e8ax0 *lcd, u8 *mtp_id) +{ + unsigned int ret; + unsigned int addr = 0xd1; /* MTP ID */ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ret = ops->cmd_read(lcd_to_master(lcd), + MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM, + addr, 3, mtp_id); +} + +static int s6e8ax0_panel_init(struct s6e8ax0 *lcd) +{ + s6e8ax0_apply_level2_key(lcd); + s6e8ax0_sleep_out(lcd); + msleep(1); + s6e8ax0_panel_cond(lcd); + s6e8ax0_display_cond(lcd); + s6e8ax0_gamma_cond(lcd); + s6e8ax0_gamma_update(lcd); + + s6e8ax0_etc_cond1(lcd); + s6e8ax0_etc_cond2(lcd); + s6e8ax0_etc_cond3(lcd); + s6e8ax0_etc_cond4(lcd); + s6e8ax0_etc_cond5(lcd); + s6e8ax0_etc_cond6(lcd); + s6e8ax0_etc_cond7(lcd); + + s6e8ax0_elvss_nvm_set(lcd); + s6e8ax0_elvss_set(lcd); + + s6e8ax0_acl_ctrl_set(lcd); + s6e8ax0_acl_on(lcd); + + /* if ID3 value is not 33h, branch private elvss mode */ + msleep(lcd->ddi_pd->power_on_delay); + + return 0; +} + +static int s6e8ax0_update_gamma_ctrl(struct s6e8ax0 *lcd, int brightness) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + s6e8ax0_22_gamma_table[brightness], + ARRAY_SIZE(s6e8ax0_22_gamma_table)); + + /* update gamma table. */ + s6e8ax0_gamma_update(lcd); + lcd->gamma = brightness; + + return 0; +} + +static int s6e8ax0_gamma_ctrl(struct s6e8ax0 *lcd, int gamma) +{ + s6e8ax0_update_gamma_ctrl(lcd, gamma); + + return 0; +} + +static int s6e8ax0_set_power(struct lcd_device *ld, int power) +{ + struct s6e8ax0 *lcd = lcd_get_data(ld); + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + int ret = 0; + + if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && + power != FB_BLANK_NORMAL) { + dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); + return -EINVAL; + } + + if ((power == FB_BLANK_UNBLANK) && ops->set_blank_mode) { + /* LCD power on */ + if ((POWER_IS_ON(power) && POWER_IS_OFF(lcd->power)) + || (POWER_IS_ON(power) && POWER_IS_NRM(lcd->power))) { + ret = ops->set_blank_mode(lcd_to_master(lcd), power); + if (!ret && lcd->power != power) + lcd->power = power; + } + } else if ((power == FB_BLANK_POWERDOWN) && ops->set_early_blank_mode) { + /* LCD power off */ + if ((POWER_IS_OFF(power) && POWER_IS_ON(lcd->power)) || + (POWER_IS_ON(lcd->power) && POWER_IS_NRM(power))) { + ret = ops->set_early_blank_mode(lcd_to_master(lcd), + power); + if (!ret && lcd->power != power) + lcd->power = power; + } + } + + return ret; +} + +static int s6e8ax0_get_power(struct lcd_device *ld) +{ + struct s6e8ax0 *lcd = lcd_get_data(ld); + + return lcd->power; +} + +static int s6e8ax0_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static int s6e8ax0_set_brightness(struct backlight_device *bd) +{ + int ret = 0, brightness = bd->props.brightness; + struct s6e8ax0 *lcd = bl_get_data(bd); + + if (brightness < MIN_BRIGHTNESS || + brightness > bd->props.max_brightness) { + dev_err(lcd->dev, "lcd brightness should be %d to %d.\n", + MIN_BRIGHTNESS, MAX_BRIGHTNESS); + return -EINVAL; + } + + ret = s6e8ax0_gamma_ctrl(lcd, brightness); + if (ret) { + dev_err(&bd->dev, "lcd brightness setting failed.\n"); + return -EIO; + } + + return ret; +} + +static struct lcd_ops s6e8ax0_lcd_ops = { + .set_power = s6e8ax0_set_power, + .get_power = s6e8ax0_get_power, +}; + +static const struct backlight_ops s6e8ax0_backlight_ops = { + .get_brightness = s6e8ax0_get_brightness, + .update_status = s6e8ax0_set_brightness, +}; + +static void s6e8ax0_power_on(struct mipi_dsim_lcd_device *dsim_dev, int power) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + msleep(lcd->ddi_pd->power_on_delay); + + /* lcd power on */ + if (power) + s6e8ax0_regulator_enable(lcd); + else + s6e8ax0_regulator_disable(lcd); + + msleep(lcd->ddi_pd->reset_delay); + + /* lcd reset */ + if (lcd->ddi_pd->reset) + lcd->ddi_pd->reset(lcd->ld); + msleep(5); +} + +static void s6e8ax0_set_sequence(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6e8ax0_panel_init(lcd); + s6e8ax0_display_on(lcd); + + lcd->power = FB_BLANK_UNBLANK; +} + +static int s6e8ax0_probe(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd; + int ret; + u8 mtp_id[3] = {0, }; + + lcd = devm_kzalloc(&dsim_dev->dev, sizeof(struct s6e8ax0), GFP_KERNEL); + if (!lcd) { + dev_err(&dsim_dev->dev, "failed to allocate s6e8ax0 structure.\n"); + return -ENOMEM; + } + + lcd->dsim_dev = dsim_dev; + lcd->ddi_pd = (struct lcd_platform_data *)dsim_dev->platform_data; + lcd->dev = &dsim_dev->dev; + + mutex_init(&lcd->lock); + + ret = devm_regulator_bulk_get(lcd->dev, ARRAY_SIZE(supplies), supplies); + if (ret) { + dev_err(lcd->dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + lcd->ld = devm_lcd_device_register(lcd->dev, "s6e8ax0", lcd->dev, lcd, + &s6e8ax0_lcd_ops); + if (IS_ERR(lcd->ld)) { + dev_err(lcd->dev, "failed to register lcd ops.\n"); + return PTR_ERR(lcd->ld); + } + + lcd->bd = devm_backlight_device_register(lcd->dev, "s6e8ax0-bl", + lcd->dev, lcd, &s6e8ax0_backlight_ops, NULL); + if (IS_ERR(lcd->bd)) { + dev_err(lcd->dev, "failed to register backlight ops.\n"); + return PTR_ERR(lcd->bd); + } + + lcd->bd->props.max_brightness = MAX_BRIGHTNESS; + lcd->bd->props.brightness = MAX_BRIGHTNESS; + + s6e8ax0_read_id(lcd, mtp_id); + if (mtp_id[0] == 0x00) + dev_err(lcd->dev, "read id failed\n"); + + dev_info(lcd->dev, "Read ID : %x, %x, %x\n", + mtp_id[0], mtp_id[1], mtp_id[2]); + + if (mtp_id[2] == 0x33) + dev_info(lcd->dev, + "ID-3 is 0xff does not support dynamic elvss\n"); + else + dev_info(lcd->dev, + "ID-3 is 0x%x support dynamic elvss\n", mtp_id[2]); + + lcd->acl_enable = 1; + lcd->cur_acl = 0; + + dev_set_drvdata(&dsim_dev->dev, lcd); + + dev_dbg(lcd->dev, "probed s6e8ax0 panel driver.\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int s6e8ax0_suspend(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6e8ax0_sleep_in(lcd); + msleep(lcd->ddi_pd->power_off_delay); + s6e8ax0_display_off(lcd); + + s6e8ax0_regulator_disable(lcd); + + return 0; +} + +static int s6e8ax0_resume(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6e8ax0_sleep_out(lcd); + msleep(lcd->ddi_pd->power_on_delay); + + s6e8ax0_regulator_enable(lcd); + s6e8ax0_set_sequence(dsim_dev); + + return 0; +} +#else +#define s6e8ax0_suspend NULL +#define s6e8ax0_resume NULL +#endif + +static struct mipi_dsim_lcd_driver s6e8ax0_dsim_ddi_driver = { + .name = "s6e8ax0", + .id = -1, + + .power_on = s6e8ax0_power_on, + .set_sequence = s6e8ax0_set_sequence, + .probe = s6e8ax0_probe, + .suspend = s6e8ax0_suspend, + .resume = s6e8ax0_resume, +}; + +static int s6e8ax0_init(void) +{ + exynos_mipi_dsi_register_lcd_driver(&s6e8ax0_dsim_ddi_driver); + + return 0; +} + +static void s6e8ax0_exit(void) +{ + return; +} + +module_init(s6e8ax0_init); +module_exit(s6e8ax0_exit); + +MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); +MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); +MODULE_DESCRIPTION("MIPI-DSI based s6e8ax0 AMOLED LCD Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/fb-puv3.c b/drivers/video/fbdev/fb-puv3.c new file mode 100644 index 000000000000..6db9ebd042a3 --- /dev/null +++ b/drivers/video/fbdev/fb-puv3.c @@ -0,0 +1,838 @@ +/* + * Frame Buffer Driver for PKUnity-v3 Unigfx + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/console.h> + +#include <asm/sizes.h> +#include <mach/hardware.h> + +/* Platform_data reserved for unifb registers. */ +#define UNIFB_REGS_NUM 10 +/* RAM reserved for the frame buffer. */ +#define UNIFB_MEMSIZE (SZ_4M) /* 4 MB for 1024*768*32b */ + +/* + * cause UNIGFX don not have EDID + * all the modes are organized as follow + */ +static const struct fb_videomode unifb_modes[] = { + /* 0 640x480-60 VESA */ + { "640x480@60", 60, 640, 480, 25175000, 48, 16, 34, 10, 96, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1 640x480-75 VESA */ + { "640x480@75", 75, 640, 480, 31500000, 120, 16, 18, 1, 64, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 2 800x600-60 VESA */ + { "800x600@60", 60, 800, 600, 40000000, 88, 40, 26, 1, 128, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 3 800x600-75 VESA */ + { "800x600@75", 75, 800, 600, 49500000, 160, 16, 23, 1, 80, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 4 1024x768-60 VESA */ + { "1024x768@60", 60, 1024, 768, 65000000, 160, 24, 34, 3, 136, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 5 1024x768-75 VESA */ + { "1024x768@75", 75, 1024, 768, 78750000, 176, 16, 30, 1, 96, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 6 1280x960-60 VESA */ + { "1280x960@60", 60, 1280, 960, 108000000, 312, 96, 38, 1, 112, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 7 1440x900-60 VESA */ + { "1440x900@60", 60, 1440, 900, 106500000, 232, 80, 30, 3, 152, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 8 FIXME 9 1024x600-60 VESA UNTESTED */ + { "1024x600@60", 60, 1024, 600, 50650000, 160, 24, 26, 1, 136, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 9 FIXME 10 1024x600-75 VESA UNTESTED */ + { "1024x600@75", 75, 1024, 600, 61500000, 176, 16, 23, 1, 96, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 10 FIXME 11 1366x768-60 VESA UNTESTED */ + { "1366x768@60", 60, 1366, 768, 85500000, 256, 58, 18, 1, 112, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, +}; + +static struct fb_var_screeninfo unifb_default = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 16, + .red = { 11, 5, 0 }, + .green = { 5, 6, 0 }, + .blue = { 0, 5, 0 }, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .pixclock = 25175000, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo unifb_fix = { + .id = "UNIGFX FB", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 1, + .accel = FB_ACCEL_NONE, +}; + +static void unifb_sync(struct fb_info *info) +{ + /* TODO: may, this can be replaced by interrupt */ + int cnt; + + for (cnt = 0; cnt < 0x10000000; cnt++) { + if (readl(UGE_COMMAND) & 0x1000000) + return; + } + + if (cnt > 0x8000000) + dev_warn(info->device, "Warning: UniGFX GE time out ...\n"); +} + +static void unifb_prim_fillrect(struct fb_info *info, + const struct fb_fillrect *region) +{ + int awidth = region->width; + int aheight = region->height; + int m_iBpp = info->var.bits_per_pixel; + int screen_width = info->var.xres; + int src_sel = 1; /* from fg_color */ + int pat_sel = 1; + int src_x0 = 0; + int dst_x0 = region->dx; + int src_y0 = 0; + int dst_y0 = region->dy; + int rop_alpha_sel = 0; + int rop_alpha_code = 0xCC; + int x_dir = 1; + int y_dir = 1; + int alpha_r = 0; + int alpha_sel = 0; + int dst_pitch = screen_width * (m_iBpp / 8); + int dst_offset = dst_y0 * dst_pitch + dst_x0 * (m_iBpp / 8); + int src_pitch = screen_width * (m_iBpp / 8); + int src_offset = src_y0 * src_pitch + src_x0 * (m_iBpp / 8); + unsigned int command = 0; + int clip_region = 0; + int clip_en = 0; + int tp_en = 0; + int fg_color = 0; + int bottom = info->var.yres - 1; + int right = info->var.xres - 1; + int top = 0; + + bottom = (bottom << 16) | right; + command = (rop_alpha_sel << 26) | (pat_sel << 18) | (src_sel << 16) + | (x_dir << 20) | (y_dir << 21) | (command << 24) + | (clip_region << 23) | (clip_en << 22) | (tp_en << 27); + src_pitch = (dst_pitch << 16) | src_pitch; + awidth = awidth | (aheight << 16); + alpha_r = ((rop_alpha_code & 0xff) << 8) | (alpha_r & 0xff) + | (alpha_sel << 16); + src_x0 = (src_x0 & 0x1fff) | ((src_y0 & 0x1fff) << 16); + dst_x0 = (dst_x0 & 0x1fff) | ((dst_y0 & 0x1fff) << 16); + fg_color = region->color; + + unifb_sync(info); + + writel(((u32 *)(info->pseudo_palette))[fg_color], UGE_FCOLOR); + writel(0, UGE_BCOLOR); + writel(src_pitch, UGE_PITCH); + writel(src_offset, UGE_SRCSTART); + writel(dst_offset, UGE_DSTSTART); + writel(awidth, UGE_WIDHEIGHT); + writel(top, UGE_CLIP0); + writel(bottom, UGE_CLIP1); + writel(alpha_r, UGE_ROPALPHA); + writel(src_x0, UGE_SRCXY); + writel(dst_x0, UGE_DSTXY); + writel(command, UGE_COMMAND); +} + +static void unifb_fillrect(struct fb_info *info, + const struct fb_fillrect *region) +{ + struct fb_fillrect modded; + int vxres, vyres; + + if (info->flags & FBINFO_HWACCEL_DISABLED) { + sys_fillrect(info, region); + return; + } + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + memcpy(&modded, region, sizeof(struct fb_fillrect)); + + if (!modded.width || !modded.height || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + unifb_prim_fillrect(info, &modded); +} + +static void unifb_prim_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + int awidth = area->width; + int aheight = area->height; + int m_iBpp = info->var.bits_per_pixel; + int screen_width = info->var.xres; + int src_sel = 2; /* from mem */ + int pat_sel = 0; + int src_x0 = area->sx; + int dst_x0 = area->dx; + int src_y0 = area->sy; + int dst_y0 = area->dy; + + int rop_alpha_sel = 0; + int rop_alpha_code = 0xCC; + int x_dir = 1; + int y_dir = 1; + + int alpha_r = 0; + int alpha_sel = 0; + int dst_pitch = screen_width * (m_iBpp / 8); + int dst_offset = dst_y0 * dst_pitch + dst_x0 * (m_iBpp / 8); + int src_pitch = screen_width * (m_iBpp / 8); + int src_offset = src_y0 * src_pitch + src_x0 * (m_iBpp / 8); + unsigned int command = 0; + int clip_region = 0; + int clip_en = 1; + int tp_en = 0; + int top = 0; + int bottom = info->var.yres; + int right = info->var.xres; + int fg_color = 0; + int bg_color = 0; + + if (src_x0 < 0) + src_x0 = 0; + if (src_y0 < 0) + src_y0 = 0; + + if (src_y0 - dst_y0 > 0) { + y_dir = 1; + } else { + y_dir = 0; + src_offset = (src_y0 + aheight) * src_pitch + + src_x0 * (m_iBpp / 8); + dst_offset = (dst_y0 + aheight) * dst_pitch + + dst_x0 * (m_iBpp / 8); + src_y0 += aheight; + dst_y0 += aheight; + } + + command = (rop_alpha_sel << 26) | (pat_sel << 18) | (src_sel << 16) | + (x_dir << 20) | (y_dir << 21) | (command << 24) | + (clip_region << 23) | (clip_en << 22) | (tp_en << 27); + src_pitch = (dst_pitch << 16) | src_pitch; + awidth = awidth | (aheight << 16); + alpha_r = ((rop_alpha_code & 0xff) << 8) | (alpha_r & 0xff) | + (alpha_sel << 16); + src_x0 = (src_x0 & 0x1fff) | ((src_y0 & 0x1fff) << 16); + dst_x0 = (dst_x0 & 0x1fff) | ((dst_y0 & 0x1fff) << 16); + bottom = (bottom << 16) | right; + + unifb_sync(info); + + writel(src_pitch, UGE_PITCH); + writel(src_offset, UGE_SRCSTART); + writel(dst_offset, UGE_DSTSTART); + writel(awidth, UGE_WIDHEIGHT); + writel(top, UGE_CLIP0); + writel(bottom, UGE_CLIP1); + writel(bg_color, UGE_BCOLOR); + writel(fg_color, UGE_FCOLOR); + writel(alpha_r, UGE_ROPALPHA); + writel(src_x0, UGE_SRCXY); + writel(dst_x0, UGE_DSTXY); + writel(command, UGE_COMMAND); +} + +static void unifb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct fb_copyarea modded; + u32 vxres, vyres; + modded.sx = area->sx; + modded.sy = area->sy; + modded.dx = area->dx; + modded.dy = area->dy; + modded.width = area->width; + modded.height = area->height; + + if (info->flags & FBINFO_HWACCEL_DISABLED) { + sys_copyarea(info, area); + return; + } + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if (!modded.width || !modded.height || + modded.sx >= vxres || modded.sy >= vyres || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.sx + modded.width > vxres) + modded.width = vxres - modded.sx; + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.sy + modded.height > vyres) + modded.height = vyres - modded.sy; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + unifb_prim_copyarea(info, &modded); +} + +static void unifb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + sys_imageblit(info, image); +} + +static u_long get_line_length(int xres_virtual, int bpp) +{ + u_long length; + + length = xres_virtual * bpp; + length = (length + 31) & ~31; + length >>= 3; + return length; +} + +/* + * Setting the video mode has been split into two parts. + * First part, xxxfb_check_var, must not write anything + * to hardware, it should only verify and adjust var. + * This means it doesn't alter par but it does use hardware + * data from it to check this var. + */ +static int unifb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + u_long line_length; + + /* + * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal! + * as FB_VMODE_SMOOTH_XPAN is only used internally + */ + + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = info->var.xoffset; + var->yoffset = info->var.yoffset; + } + + /* + * Some very basic checks + */ + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + if (var->bits_per_pixel <= 1) + var->bits_per_pixel = 1; + else 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 <= 24) + var->bits_per_pixel = 24; + else if (var->bits_per_pixel <= 32) + var->bits_per_pixel = 32; + else + return -EINVAL; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + /* + * Memory limit + */ + line_length = + get_line_length(var->xres_virtual, var->bits_per_pixel); + if (line_length * var->yres_virtual > UNIFB_MEMSIZE) + return -ENOMEM; + + /* + * Now that we checked it we alter var. The reason being is that the + * video mode passed in might not work but slight changes to it might + * make it work. This way we let the user know what is acceptable. + */ + switch (var->bits_per_pixel) { + case 1: + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGBA 5551 */ + if (var->transp.length) { + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 10; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + } else { /* RGB 565 */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + break; + case 24: /* RGB 888 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: /* RGBA 8888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + return 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + */ +static int unifb_set_par(struct fb_info *info) +{ + int hTotal, vTotal, hSyncStart, hSyncEnd, vSyncStart, vSyncEnd; + int format; + +#ifdef CONFIG_PUV3_PM + struct clk *clk_vga; + u32 pixclk = 0; + int i; + + for (i = 0; i <= 10; i++) { + if (info->var.xres == unifb_modes[i].xres + && info->var.yres == unifb_modes[i].yres + && info->var.upper_margin == unifb_modes[i].upper_margin + && info->var.lower_margin == unifb_modes[i].lower_margin + && info->var.left_margin == unifb_modes[i].left_margin + && info->var.right_margin == unifb_modes[i].right_margin + && info->var.hsync_len == unifb_modes[i].hsync_len + && info->var.vsync_len == unifb_modes[i].vsync_len) { + pixclk = unifb_modes[i].pixclock; + break; + } + } + + /* set clock rate */ + clk_vga = clk_get(info->device, "VGA_CLK"); + if (clk_vga == ERR_PTR(-ENOENT)) + return -ENOENT; + + if (pixclk != 0) { + if (clk_set_rate(clk_vga, pixclk)) { /* set clock failed */ + info->fix = unifb_fix; + info->var = unifb_default; + if (clk_set_rate(clk_vga, unifb_default.pixclock)) + return -EINVAL; + } + } +#endif + + info->fix.line_length = get_line_length(info->var.xres_virtual, + info->var.bits_per_pixel); + + hSyncStart = info->var.xres + info->var.right_margin; + hSyncEnd = hSyncStart + info->var.hsync_len; + hTotal = hSyncEnd + info->var.left_margin; + + vSyncStart = info->var.yres + info->var.lower_margin; + vSyncEnd = vSyncStart + info->var.vsync_len; + vTotal = vSyncEnd + info->var.upper_margin; + + switch (info->var.bits_per_pixel) { + case 8: + format = UDE_CFG_DST8; + break; + case 16: + format = UDE_CFG_DST16; + break; + case 24: + format = UDE_CFG_DST24; + break; + case 32: + format = UDE_CFG_DST32; + break; + default: + return -EINVAL; + } + + writel(info->fix.smem_start, UDE_FSA); + writel(info->var.yres, UDE_LS); + writel(get_line_length(info->var.xres, + info->var.bits_per_pixel) >> 3, UDE_PS); + /* >> 3 for hardware required. */ + writel((hTotal << 16) | (info->var.xres), UDE_HAT); + writel(((hTotal - 1) << 16) | (info->var.xres - 1), UDE_HBT); + writel(((hSyncEnd - 1) << 16) | (hSyncStart - 1), UDE_HST); + writel((vTotal << 16) | (info->var.yres), UDE_VAT); + writel(((vTotal - 1) << 16) | (info->var.yres - 1), UDE_VBT); + writel(((vSyncEnd - 1) << 16) | (vSyncStart - 1), UDE_VST); + writel(UDE_CFG_GDEN_ENABLE | UDE_CFG_TIMEUP_ENABLE + | format | 0xC0000001, UDE_CFG); + + return 0; +} + +/* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ +static int unifb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = + (red * 77 + green * 151 + blue * 28) >> 8; + } + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + + if (regno >= 16) + return 1; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + switch (info->var.bits_per_pixel) { + case 8: + break; + case 16: + case 24: + case 32: + ((u32 *) (info->pseudo_palette))[regno] = v; + break; + default: + return 1; + } + return 0; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ +static int unifb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->vmode & FB_VMODE_YWRAP) { + if (var->yoffset < 0 + || var->yoffset >= info->var.yres_virtual + || var->xoffset) + return -EINVAL; + } else { + if (var->xoffset + info->var.xres > info->var.xres_virtual || + var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + } + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + return 0; +} + +int unifb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return vm_iomap_memory(vma, info->fix.smem_start, info->fix.smem_len); +} + +static struct fb_ops unifb_ops = { + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_check_var = unifb_check_var, + .fb_set_par = unifb_set_par, + .fb_setcolreg = unifb_setcolreg, + .fb_pan_display = unifb_pan_display, + .fb_fillrect = unifb_fillrect, + .fb_copyarea = unifb_copyarea, + .fb_imageblit = unifb_imageblit, + .fb_mmap = unifb_mmap, +}; + +/* + * Initialisation + */ +static int unifb_probe(struct platform_device *dev) +{ + struct fb_info *info; + u32 unifb_regs[UNIFB_REGS_NUM]; + int retval = -ENOMEM; + struct resource *iomem; + void *videomemory; + + videomemory = (void *)__get_free_pages(GFP_KERNEL | __GFP_COMP, + get_order(UNIFB_MEMSIZE)); + if (!videomemory) + goto err; + + memset(videomemory, 0, UNIFB_MEMSIZE); + + unifb_fix.smem_start = virt_to_phys(videomemory); + unifb_fix.smem_len = UNIFB_MEMSIZE; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + unifb_fix.mmio_start = iomem->start; + + info = framebuffer_alloc(sizeof(u32)*256, &dev->dev); + if (!info) + goto err; + + info->screen_base = (char __iomem *)videomemory; + info->fbops = &unifb_ops; + + retval = fb_find_mode(&info->var, info, NULL, + unifb_modes, 10, &unifb_modes[0], 16); + + if (!retval || (retval == 4)) + info->var = unifb_default; + + info->fix = unifb_fix; + info->pseudo_palette = info->par; + info->par = NULL; + info->flags = FBINFO_FLAG_DEFAULT; +#ifdef FB_ACCEL_PUV3_UNIGFX + info->fix.accel = FB_ACCEL_PUV3_UNIGFX; +#endif + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) + goto err1; + + retval = register_framebuffer(info); + if (retval < 0) + goto err2; + platform_set_drvdata(dev, info); + platform_device_add_data(dev, unifb_regs, sizeof(u32) * UNIFB_REGS_NUM); + + fb_info(info, "Virtual frame buffer device, using %dM of video memory\n", + UNIFB_MEMSIZE >> 20); + return 0; +err2: + fb_dealloc_cmap(&info->cmap); +err1: + framebuffer_release(info); +err: + return retval; +} + +static int unifb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + return 0; +} + +#ifdef CONFIG_PM +static int unifb_resume(struct platform_device *dev) +{ + int rc = 0; + u32 *unifb_regs = dev->dev.platform_data; + + if (dev->dev.power.power_state.event == PM_EVENT_ON) + return 0; + + console_lock(); + + if (dev->dev.power.power_state.event == PM_EVENT_SUSPEND) { + writel(unifb_regs[0], UDE_FSA); + writel(unifb_regs[1], UDE_LS); + writel(unifb_regs[2], UDE_PS); + writel(unifb_regs[3], UDE_HAT); + writel(unifb_regs[4], UDE_HBT); + writel(unifb_regs[5], UDE_HST); + writel(unifb_regs[6], UDE_VAT); + writel(unifb_regs[7], UDE_VBT); + writel(unifb_regs[8], UDE_VST); + writel(unifb_regs[9], UDE_CFG); + } + dev->dev.power.power_state = PMSG_ON; + + console_unlock(); + + return rc; +} + +static int unifb_suspend(struct platform_device *dev, pm_message_t mesg) +{ + u32 *unifb_regs = dev->dev.platform_data; + + unifb_regs[0] = readl(UDE_FSA); + unifb_regs[1] = readl(UDE_LS); + unifb_regs[2] = readl(UDE_PS); + unifb_regs[3] = readl(UDE_HAT); + unifb_regs[4] = readl(UDE_HBT); + unifb_regs[5] = readl(UDE_HST); + unifb_regs[6] = readl(UDE_VAT); + unifb_regs[7] = readl(UDE_VBT); + unifb_regs[8] = readl(UDE_VST); + unifb_regs[9] = readl(UDE_CFG); + + if (mesg.event == dev->dev.power.power_state.event) + return 0; + + switch (mesg.event) { + case PM_EVENT_FREEZE: /* about to take snapshot */ + case PM_EVENT_PRETHAW: /* before restoring snapshot */ + goto done; + } + + console_lock(); + + /* do nothing... */ + + console_unlock(); + +done: + dev->dev.power.power_state = mesg; + + return 0; +} +#else +#define unifb_resume NULL +#define unifb_suspend NULL +#endif + +static struct platform_driver unifb_driver = { + .probe = unifb_probe, + .remove = unifb_remove, + .resume = unifb_resume, + .suspend = unifb_suspend, + .driver = { + .name = "PKUnity-v3-UNIGFX", + }, +}; + +static int __init unifb_init(void) +{ +#ifndef MODULE + if (fb_get_options("unifb", NULL)) + return -ENODEV; +#endif + + return platform_driver_register(&unifb_driver); +} + +module_init(unifb_init); + +static void __exit unifb_exit(void) +{ + platform_driver_unregister(&unifb_driver); +} + +module_exit(unifb_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/fb_ddc.c b/drivers/video/fbdev/fb_ddc.c new file mode 100644 index 000000000000..2b106f046fde --- /dev/null +++ b/drivers/video/fbdev/fb_ddc.c @@ -0,0 +1,119 @@ +/* + * drivers/video/fb_ddc.c - DDC/EDID read support. + * + * Copyright (C) 2006 Dennis Munsie <dmunsie@cecropia.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/delay.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/i2c-algo-bit.h> +#include <linux/slab.h> + +#include "edid.h" + +#define DDC_ADDR 0x50 + +static unsigned char *fb_do_probe_ddc_edid(struct i2c_adapter *adapter) +{ + unsigned char start = 0x0; + unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL); + struct i2c_msg msgs[] = { + { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = buf, + } + }; + + if (!buf) { + dev_warn(&adapter->dev, "unable to allocate memory for EDID " + "block.\n"); + return NULL; + } + + if (i2c_transfer(adapter, msgs, 2) == 2) + return buf; + + dev_warn(&adapter->dev, "unable to read EDID block.\n"); + kfree(buf); + return NULL; +} + +unsigned char *fb_ddc_read(struct i2c_adapter *adapter) +{ + struct i2c_algo_bit_data *algo_data = adapter->algo_data; + unsigned char *edid = NULL; + int i, j; + + algo_data->setscl(algo_data->data, 1); + + for (i = 0; i < 3; i++) { + /* For some old monitors we need the + * following process to initialize/stop DDC + */ + algo_data->setsda(algo_data->data, 1); + msleep(13); + + algo_data->setscl(algo_data->data, 1); + for (j = 0; j < 5; j++) { + msleep(10); + if (algo_data->getscl(algo_data->data)) + break; + } + if (j == 5) + continue; + + algo_data->setsda(algo_data->data, 0); + msleep(15); + algo_data->setscl(algo_data->data, 0); + msleep(15); + algo_data->setsda(algo_data->data, 1); + msleep(15); + + /* Do the real work */ + edid = fb_do_probe_ddc_edid(adapter); + algo_data->setsda(algo_data->data, 0); + algo_data->setscl(algo_data->data, 0); + msleep(15); + + algo_data->setscl(algo_data->data, 1); + for (j = 0; j < 10; j++) { + msleep(10); + if (algo_data->getscl(algo_data->data)) + break; + } + + algo_data->setsda(algo_data->data, 1); + msleep(15); + algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 0); + if (edid) + break; + } + /* Release the DDC lines when done or the Apple Cinema HD display + * will switch off + */ + algo_data->setsda(algo_data->data, 1); + algo_data->setscl(algo_data->data, 1); + + adapter->class |= I2C_CLASS_DDC; + return edid; +} + +EXPORT_SYMBOL_GPL(fb_ddc_read); + +MODULE_AUTHOR("Dennis Munsie <dmunsie@cecropia.com>"); +MODULE_DESCRIPTION("DDC/EDID reading support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/fb_defio.c b/drivers/video/fbdev/fb_defio.c new file mode 100644 index 000000000000..900aa4ecd617 --- /dev/null +++ b/drivers/video/fbdev/fb_defio.c @@ -0,0 +1,245 @@ +/* + * linux/drivers/video/fb_defio.c + * + * Copyright (C) 2006 Jaya Kumar + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/list.h> + +/* to support deferred IO */ +#include <linux/rmap.h> +#include <linux/pagemap.h> + +static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs) +{ + void *screen_base = (void __force *) info->screen_base; + struct page *page; + + if (is_vmalloc_addr(screen_base + offs)) + page = vmalloc_to_page(screen_base + offs); + else + page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT); + + return page; +} + +/* this is to find and return the vmalloc-ed fb pages */ +static int fb_deferred_io_fault(struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + unsigned long offset; + struct page *page; + struct fb_info *info = vma->vm_private_data; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= info->fix.smem_len) + return VM_FAULT_SIGBUS; + + page = fb_deferred_io_page(info, offset); + if (!page) + return VM_FAULT_SIGBUS; + + get_page(page); + + if (vma->vm_file) + page->mapping = vma->vm_file->f_mapping; + else + printk(KERN_ERR "no mapping available\n"); + + BUG_ON(!page->mapping); + page->index = vmf->pgoff; + + vmf->page = page; + return 0; +} + +int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync) +{ + struct fb_info *info = file->private_data; + struct inode *inode = file_inode(file); + int err = filemap_write_and_wait_range(inode->i_mapping, start, end); + if (err) + return err; + + /* Skip if deferred io is compiled-in but disabled on this fbdev */ + if (!info->fbdefio) + return 0; + + mutex_lock(&inode->i_mutex); + /* Kill off the delayed work */ + cancel_delayed_work_sync(&info->deferred_work); + + /* Run it immediately */ + err = schedule_delayed_work(&info->deferred_work, 0); + mutex_unlock(&inode->i_mutex); + return err; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_fsync); + +/* vm_ops->page_mkwrite handler */ +static int fb_deferred_io_mkwrite(struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + struct page *page = vmf->page; + struct fb_info *info = vma->vm_private_data; + struct fb_deferred_io *fbdefio = info->fbdefio; + struct page *cur; + + /* this is a callback we get when userspace first tries to + write to the page. we schedule a workqueue. that workqueue + will eventually mkclean the touched pages and execute the + deferred framebuffer IO. then if userspace touches a page + again, we repeat the same scheme */ + + file_update_time(vma->vm_file); + + /* protect against the workqueue changing the page list */ + mutex_lock(&fbdefio->lock); + + /* first write in this cycle, notify the driver */ + if (fbdefio->first_io && list_empty(&fbdefio->pagelist)) + fbdefio->first_io(info); + + /* + * We want the page to remain locked from ->page_mkwrite until + * the PTE is marked dirty to avoid page_mkclean() being called + * before the PTE is updated, which would leave the page ignored + * by defio. + * Do this by locking the page here and informing the caller + * about it with VM_FAULT_LOCKED. + */ + lock_page(page); + + /* we loop through the pagelist before adding in order + to keep the pagelist sorted */ + list_for_each_entry(cur, &fbdefio->pagelist, lru) { + /* this check is to catch the case where a new + process could start writing to the same page + through a new pte. this new access can cause the + mkwrite even when the original ps's pte is marked + writable */ + if (unlikely(cur == page)) + goto page_already_added; + else if (cur->index > page->index) + break; + } + + list_add_tail(&page->lru, &cur->lru); + +page_already_added: + mutex_unlock(&fbdefio->lock); + + /* come back after delay to process the deferred IO */ + schedule_delayed_work(&info->deferred_work, fbdefio->delay); + return VM_FAULT_LOCKED; +} + +static const struct vm_operations_struct fb_deferred_io_vm_ops = { + .fault = fb_deferred_io_fault, + .page_mkwrite = fb_deferred_io_mkwrite, +}; + +static int fb_deferred_io_set_page_dirty(struct page *page) +{ + if (!PageDirty(page)) + SetPageDirty(page); + return 0; +} + +static const struct address_space_operations fb_deferred_io_aops = { + .set_page_dirty = fb_deferred_io_set_page_dirty, +}; + +static int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + vma->vm_ops = &fb_deferred_io_vm_ops; + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + if (!(info->flags & FBINFO_VIRTFB)) + vma->vm_flags |= VM_IO; + vma->vm_private_data = info; + return 0; +} + +/* workqueue callback */ +static void fb_deferred_io_work(struct work_struct *work) +{ + struct fb_info *info = container_of(work, struct fb_info, + deferred_work.work); + struct list_head *node, *next; + struct page *cur; + struct fb_deferred_io *fbdefio = info->fbdefio; + + /* here we mkclean the pages, then do all deferred IO */ + mutex_lock(&fbdefio->lock); + list_for_each_entry(cur, &fbdefio->pagelist, lru) { + lock_page(cur); + page_mkclean(cur); + unlock_page(cur); + } + + /* driver's callback with pagelist */ + fbdefio->deferred_io(info, &fbdefio->pagelist); + + /* clear the list */ + list_for_each_safe(node, next, &fbdefio->pagelist) { + list_del(node); + } + mutex_unlock(&fbdefio->lock); +} + +void fb_deferred_io_init(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + + BUG_ON(!fbdefio); + mutex_init(&fbdefio->lock); + info->fbops->fb_mmap = fb_deferred_io_mmap; + INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); + INIT_LIST_HEAD(&fbdefio->pagelist); + if (fbdefio->delay == 0) /* set a default of 1 s */ + fbdefio->delay = HZ; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_init); + +void fb_deferred_io_open(struct fb_info *info, + struct inode *inode, + struct file *file) +{ + file->f_mapping->a_ops = &fb_deferred_io_aops; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_open); + +void fb_deferred_io_cleanup(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct page *page; + int i; + + BUG_ON(!fbdefio); + cancel_delayed_work_sync(&info->deferred_work); + + /* clear out the mapping that we setup */ + for (i = 0 ; i < info->fix.smem_len; i += PAGE_SIZE) { + page = fb_deferred_io_page(info, i); + page->mapping = NULL; + } + + info->fbops->fb_mmap = NULL; + mutex_destroy(&fbdefio->lock); +} +EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/fb_draw.h b/drivers/video/fbdev/fb_draw.h new file mode 100644 index 000000000000..624ee115f129 --- /dev/null +++ b/drivers/video/fbdev/fb_draw.h @@ -0,0 +1,186 @@ +#ifndef _FB_DRAW_H +#define _FB_DRAW_H + +#include <asm/types.h> +#include <linux/fb.h> +#include <linux/bug.h> + + /* + * Compose two values, using a bitmask as decision value + * This is equivalent to (a & mask) | (b & ~mask) + */ + +static inline unsigned long +comp(unsigned long a, unsigned long b, unsigned long mask) +{ + return ((a ^ b) & mask) ^ b; +} + + /* + * Create a pattern with the given pixel's color + */ + +#if BITS_PER_LONG == 64 +static inline unsigned long +pixel_to_pat( u32 bpp, u32 pixel) +{ + switch (bpp) { + case 1: + return 0xfffffffffffffffful*pixel; + case 2: + return 0x5555555555555555ul*pixel; + case 4: + return 0x1111111111111111ul*pixel; + case 8: + return 0x0101010101010101ul*pixel; + case 12: + return 0x1001001001001001ul*pixel; + case 16: + return 0x0001000100010001ul*pixel; + case 24: + return 0x0001000001000001ul*pixel; + case 32: + return 0x0000000100000001ul*pixel; + default: + WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp); + return 0; + } +} +#else +static inline unsigned long +pixel_to_pat( u32 bpp, u32 pixel) +{ + switch (bpp) { + case 1: + return 0xfffffffful*pixel; + case 2: + return 0x55555555ul*pixel; + case 4: + return 0x11111111ul*pixel; + case 8: + return 0x01010101ul*pixel; + case 12: + return 0x01001001ul*pixel; + case 16: + return 0x00010001ul*pixel; + case 24: + return 0x01000001ul*pixel; + case 32: + return 0x00000001ul*pixel; + default: + WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp); + return 0; + } +} +#endif + +#ifdef CONFIG_FB_CFB_REV_PIXELS_IN_BYTE +#if BITS_PER_LONG == 64 +#define REV_PIXELS_MASK1 0x5555555555555555ul +#define REV_PIXELS_MASK2 0x3333333333333333ul +#define REV_PIXELS_MASK4 0x0f0f0f0f0f0f0f0ful +#else +#define REV_PIXELS_MASK1 0x55555555ul +#define REV_PIXELS_MASK2 0x33333333ul +#define REV_PIXELS_MASK4 0x0f0f0f0ful +#endif + +static inline unsigned long fb_rev_pixels_in_long(unsigned long val, + u32 bswapmask) +{ + if (bswapmask & 1) + val = comp(val >> 1, val << 1, REV_PIXELS_MASK1); + if (bswapmask & 2) + val = comp(val >> 2, val << 2, REV_PIXELS_MASK2); + if (bswapmask & 3) + val = comp(val >> 4, val << 4, REV_PIXELS_MASK4); + return val; +} + +static inline u32 fb_shifted_pixels_mask_u32(struct fb_info *p, u32 index, + u32 bswapmask) +{ + u32 mask; + + if (!bswapmask) { + mask = FB_SHIFT_HIGH(p, ~(u32)0, index); + } else { + mask = 0xff << FB_LEFT_POS(p, 8); + mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask; + mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask)); +#if defined(__i386__) || defined(__x86_64__) + /* Shift argument is limited to 0 - 31 on x86 based CPU's */ + if(index + bswapmask < 32) +#endif + mask |= FB_SHIFT_HIGH(p, ~(u32)0, + (index + bswapmask) & ~(bswapmask)); + } + return mask; +} + +static inline unsigned long fb_shifted_pixels_mask_long(struct fb_info *p, + u32 index, + u32 bswapmask) +{ + unsigned long mask; + + if (!bswapmask) { + mask = FB_SHIFT_HIGH(p, ~0UL, index); + } else { + mask = 0xff << FB_LEFT_POS(p, 8); + mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask; + mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask)); +#if defined(__i386__) || defined(__x86_64__) + /* Shift argument is limited to 0 - 31 on x86 based CPU's */ + if(index + bswapmask < BITS_PER_LONG) +#endif + mask |= FB_SHIFT_HIGH(p, ~0UL, + (index + bswapmask) & ~(bswapmask)); + } + return mask; +} + + +static inline u32 fb_compute_bswapmask(struct fb_info *info) +{ + u32 bswapmask = 0; + unsigned bpp = info->var.bits_per_pixel; + + if ((bpp < 8) && (info->var.nonstd & FB_NONSTD_REV_PIX_IN_B)) { + /* + * Reversed order of pixel layout in bytes + * works only for 1, 2 and 4 bpp + */ + bswapmask = 7 - bpp + 1; + } + return bswapmask; +} + +#else /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */ + +static inline unsigned long fb_rev_pixels_in_long(unsigned long val, + u32 bswapmask) +{ + return val; +} + +#define fb_shifted_pixels_mask_u32(p, i, b) FB_SHIFT_HIGH((p), ~(u32)0, (i)) +#define fb_shifted_pixels_mask_long(p, i, b) FB_SHIFT_HIGH((p), ~0UL, (i)) +#define fb_compute_bswapmask(...) 0 + +#endif /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */ + +#define cpu_to_le_long _cpu_to_le_long(BITS_PER_LONG) +#define _cpu_to_le_long(x) __cpu_to_le_long(x) +#define __cpu_to_le_long(x) cpu_to_le##x + +#define le_long_to_cpu _le_long_to_cpu(BITS_PER_LONG) +#define _le_long_to_cpu(x) __le_long_to_cpu(x) +#define __le_long_to_cpu(x) le##x##_to_cpu + +static inline unsigned long rolx(unsigned long word, unsigned int shift, unsigned int x) +{ + return (word << shift) | (word >> (x - shift)); +} + +#endif /* FB_DRAW_H */ diff --git a/drivers/video/fbdev/fb_notify.c b/drivers/video/fbdev/fb_notify.c new file mode 100644 index 000000000000..74c2da528884 --- /dev/null +++ b/drivers/video/fbdev/fb_notify.c @@ -0,0 +1,47 @@ +/* + * linux/drivers/video/fb_notify.c + * + * Copyright (C) 2006 Antonino Daplas <adaplas@pol.net> + * + * 2001 - Documented with DocBook + * - Brad Douglas <brad@neruo.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/fb.h> +#include <linux/notifier.h> +#include <linux/export.h> + +static BLOCKING_NOTIFIER_HEAD(fb_notifier_list); + +/** + * fb_register_client - register a client notifier + * @nb: notifier block to callback on events + */ +int fb_register_client(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&fb_notifier_list, nb); +} +EXPORT_SYMBOL(fb_register_client); + +/** + * fb_unregister_client - unregister a client notifier + * @nb: notifier block to callback on events + */ +int fb_unregister_client(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&fb_notifier_list, nb); +} +EXPORT_SYMBOL(fb_unregister_client); + +/** + * fb_notifier_call_chain - notify clients of fb_events + * + */ +int fb_notifier_call_chain(unsigned long val, void *v) +{ + return blocking_notifier_call_chain(&fb_notifier_list, val, v); +} +EXPORT_SYMBOL_GPL(fb_notifier_call_chain); diff --git a/drivers/video/fbdev/fb_sys_fops.c b/drivers/video/fbdev/fb_sys_fops.c new file mode 100644 index 000000000000..ff275d7f3eaf --- /dev/null +++ b/drivers/video/fbdev/fb_sys_fops.c @@ -0,0 +1,104 @@ +/* + * linux/drivers/video/fb_sys_read.c - Generic file operations where + * framebuffer is in system RAM + * + * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net> + * + * 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/fb.h> +#include <linux/module.h> +#include <linux/uaccess.h> + +ssize_t fb_sys_read(struct fb_info *info, char __user *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *src; + int err = 0; + unsigned long total_size; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + total_size = info->screen_size; + + if (total_size == 0) + total_size = info->fix.smem_len; + + if (p >= total_size) + return 0; + + if (count >= total_size) + count = total_size; + + if (count + p > total_size) + count = total_size - p; + + src = (void __force *)(info->screen_base + p); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + if (copy_to_user(buf, src, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + return (err) ? err : count; +} +EXPORT_SYMBOL_GPL(fb_sys_read); + +ssize_t fb_sys_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + void *dst; + int err = 0; + unsigned long total_size; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + total_size = info->screen_size; + + if (total_size == 0) + total_size = info->fix.smem_len; + + if (p > total_size) + return -EFBIG; + + if (count > total_size) { + err = -EFBIG; + count = total_size; + } + + if (count + p > total_size) { + if (!err) + err = -ENOSPC; + + count = total_size - p; + } + + dst = (void __force *) (info->screen_base + p); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + if (copy_from_user(dst, buf, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + return (err) ? err : count; +} +EXPORT_SYMBOL_GPL(fb_sys_write); + +MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); +MODULE_DESCRIPTION("Generic file read (fb in system RAM)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/fbcmap.c b/drivers/video/fbdev/fbcmap.c new file mode 100644 index 000000000000..f89245b8ba8e --- /dev/null +++ b/drivers/video/fbdev/fbcmap.c @@ -0,0 +1,362 @@ +/* + * linux/drivers/video/fbcmap.c -- Colormap handling for frame buffer devices + * + * Created 15 Jun 1997 by Geert Uytterhoeven + * + * 2001 - Documented with DocBook + * - Brad Douglas <brad@neruo.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/string.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +static u16 red2[] __read_mostly = { + 0x0000, 0xaaaa +}; +static u16 green2[] __read_mostly = { + 0x0000, 0xaaaa +}; +static u16 blue2[] __read_mostly = { + 0x0000, 0xaaaa +}; + +static u16 red4[] __read_mostly = { + 0x0000, 0xaaaa, 0x5555, 0xffff +}; +static u16 green4[] __read_mostly = { + 0x0000, 0xaaaa, 0x5555, 0xffff +}; +static u16 blue4[] __read_mostly = { + 0x0000, 0xaaaa, 0x5555, 0xffff +}; + +static u16 red8[] __read_mostly = { + 0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa +}; +static u16 green8[] __read_mostly = { + 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa +}; +static u16 blue8[] __read_mostly = { + 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa +}; + +static u16 red16[] __read_mostly = { + 0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, + 0x5555, 0x5555, 0x5555, 0x5555, 0xffff, 0xffff, 0xffff, 0xffff +}; +static u16 green16[] __read_mostly = { + 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa, + 0x5555, 0x5555, 0xffff, 0xffff, 0x5555, 0x5555, 0xffff, 0xffff +}; +static u16 blue16[] __read_mostly = { + 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, + 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff +}; + +static const struct fb_cmap default_2_colors = { + .len=2, .red=red2, .green=green2, .blue=blue2 +}; +static const struct fb_cmap default_8_colors = { + .len=8, .red=red8, .green=green8, .blue=blue8 +}; +static const struct fb_cmap default_4_colors = { + .len=4, .red=red4, .green=green4, .blue=blue4 +}; +static const struct fb_cmap default_16_colors = { + .len=16, .red=red16, .green=green16, .blue=blue16 +}; + + + +/** + * fb_alloc_cmap - allocate a colormap + * @cmap: frame buffer colormap structure + * @len: length of @cmap + * @transp: boolean, 1 if there is transparency, 0 otherwise + * @flags: flags for kmalloc memory allocation + * + * Allocates memory for a colormap @cmap. @len is the + * number of entries in the palette. + * + * Returns negative errno on error, or zero on success. + * + */ + +int fb_alloc_cmap_gfp(struct fb_cmap *cmap, int len, int transp, gfp_t flags) +{ + int size = len * sizeof(u16); + int ret = -ENOMEM; + + if (cmap->len != len) { + fb_dealloc_cmap(cmap); + if (!len) + return 0; + + cmap->red = kmalloc(size, flags); + if (!cmap->red) + goto fail; + cmap->green = kmalloc(size, flags); + if (!cmap->green) + goto fail; + cmap->blue = kmalloc(size, flags); + if (!cmap->blue) + goto fail; + if (transp) { + cmap->transp = kmalloc(size, flags); + if (!cmap->transp) + goto fail; + } else { + cmap->transp = NULL; + } + } + cmap->start = 0; + cmap->len = len; + ret = fb_copy_cmap(fb_default_cmap(len), cmap); + if (ret) + goto fail; + return 0; + +fail: + fb_dealloc_cmap(cmap); + return ret; +} + +int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) +{ + return fb_alloc_cmap_gfp(cmap, len, transp, GFP_ATOMIC); +} + +/** + * fb_dealloc_cmap - deallocate a colormap + * @cmap: frame buffer colormap structure + * + * Deallocates a colormap that was previously allocated with + * fb_alloc_cmap(). + * + */ + +void fb_dealloc_cmap(struct fb_cmap *cmap) +{ + kfree(cmap->red); + kfree(cmap->green); + kfree(cmap->blue); + kfree(cmap->transp); + + cmap->red = cmap->green = cmap->blue = cmap->transp = NULL; + cmap->len = 0; +} + +/** + * fb_copy_cmap - copy a colormap + * @from: frame buffer colormap structure + * @to: frame buffer colormap structure + * + * Copy contents of colormap from @from to @to. + */ + +int fb_copy_cmap(const struct fb_cmap *from, struct fb_cmap *to) +{ + int tooff = 0, fromoff = 0; + int size; + + if (to->start > from->start) + fromoff = to->start - from->start; + else + tooff = from->start - to->start; + size = to->len - tooff; + if (size > (int) (from->len - fromoff)) + size = from->len - fromoff; + if (size <= 0) + return -EINVAL; + size *= sizeof(u16); + + memcpy(to->red+tooff, from->red+fromoff, size); + memcpy(to->green+tooff, from->green+fromoff, size); + memcpy(to->blue+tooff, from->blue+fromoff, size); + if (from->transp && to->transp) + memcpy(to->transp+tooff, from->transp+fromoff, size); + return 0; +} + +int fb_cmap_to_user(const struct fb_cmap *from, struct fb_cmap_user *to) +{ + int tooff = 0, fromoff = 0; + int size; + + if (to->start > from->start) + fromoff = to->start - from->start; + else + tooff = from->start - to->start; + size = to->len - tooff; + if (size > (int) (from->len - fromoff)) + size = from->len - fromoff; + if (size <= 0) + return -EINVAL; + size *= sizeof(u16); + + if (copy_to_user(to->red+tooff, from->red+fromoff, size)) + return -EFAULT; + if (copy_to_user(to->green+tooff, from->green+fromoff, size)) + return -EFAULT; + if (copy_to_user(to->blue+tooff, from->blue+fromoff, size)) + return -EFAULT; + if (from->transp && to->transp) + if (copy_to_user(to->transp+tooff, from->transp+fromoff, size)) + return -EFAULT; + return 0; +} + +/** + * fb_set_cmap - set the colormap + * @cmap: frame buffer colormap structure + * @info: frame buffer info structure + * + * Sets the colormap @cmap for a screen of device @info. + * + * Returns negative errno on error, or zero on success. + * + */ + +int fb_set_cmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int i, start, rc = 0; + u16 *red, *green, *blue, *transp; + u_int hred, hgreen, hblue, htransp = 0xffff; + + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + start = cmap->start; + + if (start < 0 || (!info->fbops->fb_setcolreg && + !info->fbops->fb_setcmap)) + return -EINVAL; + if (info->fbops->fb_setcmap) { + rc = info->fbops->fb_setcmap(cmap, info); + } else { + for (i = 0; i < cmap->len; i++) { + hred = *red++; + hgreen = *green++; + hblue = *blue++; + if (transp) + htransp = *transp++; + if (info->fbops->fb_setcolreg(start++, + hred, hgreen, hblue, + htransp, info)) + break; + } + } + if (rc == 0) + fb_copy_cmap(cmap, &info->cmap); + + return rc; +} + +int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) +{ + int rc, size = cmap->len * sizeof(u16); + struct fb_cmap umap; + + if (size < 0 || size < cmap->len) + return -E2BIG; + + memset(&umap, 0, sizeof(struct fb_cmap)); + rc = fb_alloc_cmap_gfp(&umap, cmap->len, cmap->transp != NULL, + GFP_KERNEL); + if (rc) + return rc; + if (copy_from_user(umap.red, cmap->red, size) || + copy_from_user(umap.green, cmap->green, size) || + copy_from_user(umap.blue, cmap->blue, size) || + (cmap->transp && copy_from_user(umap.transp, cmap->transp, size))) { + rc = -EFAULT; + goto out; + } + umap.start = cmap->start; + if (!lock_fb_info(info)) { + rc = -ENODEV; + goto out; + } + + rc = fb_set_cmap(&umap, info); + unlock_fb_info(info); +out: + fb_dealloc_cmap(&umap); + return rc; +} + +/** + * fb_default_cmap - get default colormap + * @len: size of palette for a depth + * + * Gets the default colormap for a specific screen depth. @len + * is the size of the palette for a particular screen depth. + * + * Returns pointer to a frame buffer colormap structure. + * + */ + +const struct fb_cmap *fb_default_cmap(int len) +{ + if (len <= 2) + return &default_2_colors; + if (len <= 4) + return &default_4_colors; + if (len <= 8) + return &default_8_colors; + return &default_16_colors; +} + + +/** + * fb_invert_cmaps - invert all defaults colormaps + * + * Invert all default colormaps. + * + */ + +void fb_invert_cmaps(void) +{ + u_int i; + + for (i = 0; i < ARRAY_SIZE(red2); i++) { + red2[i] = ~red2[i]; + green2[i] = ~green2[i]; + blue2[i] = ~blue2[i]; + } + for (i = 0; i < ARRAY_SIZE(red4); i++) { + red4[i] = ~red4[i]; + green4[i] = ~green4[i]; + blue4[i] = ~blue4[i]; + } + for (i = 0; i < ARRAY_SIZE(red8); i++) { + red8[i] = ~red8[i]; + green8[i] = ~green8[i]; + blue8[i] = ~blue8[i]; + } + for (i = 0; i < ARRAY_SIZE(red16); i++) { + red16[i] = ~red16[i]; + green16[i] = ~green16[i]; + blue16[i] = ~blue16[i]; + } +} + + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(fb_alloc_cmap); +EXPORT_SYMBOL(fb_dealloc_cmap); +EXPORT_SYMBOL(fb_copy_cmap); +EXPORT_SYMBOL(fb_set_cmap); +EXPORT_SYMBOL(fb_default_cmap); +EXPORT_SYMBOL(fb_invert_cmaps); diff --git a/drivers/video/fbdev/fbcvt.c b/drivers/video/fbdev/fbcvt.c new file mode 100644 index 000000000000..7cb715dfc0e1 --- /dev/null +++ b/drivers/video/fbdev/fbcvt.c @@ -0,0 +1,379 @@ +/* + * linux/drivers/video/fbcvt.c - VESA(TM) Coordinated Video Timings + * + * Copyright (C) 2005 Antonino Daplas <adaplas@pol.net> + * + * Based from the VESA(TM) Coordinated Video Timing Generator by + * Graham Loveridge April 9, 2003 available at + * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls + * + * 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/fb.h> +#include <linux/slab.h> + +#define FB_CVT_CELLSIZE 8 +#define FB_CVT_GTF_C 40 +#define FB_CVT_GTF_J 20 +#define FB_CVT_GTF_K 128 +#define FB_CVT_GTF_M 600 +#define FB_CVT_MIN_VSYNC_BP 550 +#define FB_CVT_MIN_VPORCH 3 +#define FB_CVT_MIN_BPORCH 6 + +#define FB_CVT_RB_MIN_VBLANK 460 +#define FB_CVT_RB_HBLANK 160 +#define FB_CVT_RB_V_FPORCH 3 + +#define FB_CVT_FLAG_REDUCED_BLANK 1 +#define FB_CVT_FLAG_MARGINS 2 +#define FB_CVT_FLAG_INTERLACED 4 + +struct fb_cvt_data { + u32 xres; + u32 yres; + u32 refresh; + u32 f_refresh; + u32 pixclock; + u32 hperiod; + u32 hblank; + u32 hfreq; + u32 htotal; + u32 vtotal; + u32 vsync; + u32 hsync; + u32 h_front_porch; + u32 h_back_porch; + u32 v_front_porch; + u32 v_back_porch; + u32 h_margin; + u32 v_margin; + u32 interlace; + u32 aspect_ratio; + u32 active_pixels; + u32 flags; + u32 status; +}; + +static const unsigned char fb_cvt_vbi_tab[] = { + 4, /* 4:3 */ + 5, /* 16:9 */ + 6, /* 16:10 */ + 7, /* 5:4 */ + 7, /* 15:9 */ + 8, /* reserved */ + 9, /* reserved */ + 10 /* custom */ +}; + +/* returns hperiod * 1000 */ +static u32 fb_cvt_hperiod(struct fb_cvt_data *cvt) +{ + u32 num = 1000000000/cvt->f_refresh; + u32 den; + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) { + num -= FB_CVT_RB_MIN_VBLANK * 1000; + den = 2 * (cvt->yres/cvt->interlace + 2 * cvt->v_margin); + } else { + num -= FB_CVT_MIN_VSYNC_BP * 1000; + den = 2 * (cvt->yres/cvt->interlace + cvt->v_margin * 2 + + FB_CVT_MIN_VPORCH + cvt->interlace/2); + } + + return 2 * (num/den); +} + +/* returns ideal duty cycle * 1000 */ +static u32 fb_cvt_ideal_duty_cycle(struct fb_cvt_data *cvt) +{ + u32 c_prime = (FB_CVT_GTF_C - FB_CVT_GTF_J) * + (FB_CVT_GTF_K) + 256 * FB_CVT_GTF_J; + u32 m_prime = (FB_CVT_GTF_K * FB_CVT_GTF_M); + u32 h_period_est = cvt->hperiod; + + return (1000 * c_prime - ((m_prime * h_period_est)/1000))/256; +} + +static u32 fb_cvt_hblank(struct fb_cvt_data *cvt) +{ + u32 hblank = 0; + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) + hblank = FB_CVT_RB_HBLANK; + else { + u32 ideal_duty_cycle = fb_cvt_ideal_duty_cycle(cvt); + u32 active_pixels = cvt->active_pixels; + + if (ideal_duty_cycle < 20000) + hblank = (active_pixels * 20000)/ + (100000 - 20000); + else { + hblank = (active_pixels * ideal_duty_cycle)/ + (100000 - ideal_duty_cycle); + } + } + + hblank &= ~((2 * FB_CVT_CELLSIZE) - 1); + + return hblank; +} + +static u32 fb_cvt_hsync(struct fb_cvt_data *cvt) +{ + u32 hsync; + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) + hsync = 32; + else + hsync = (FB_CVT_CELLSIZE * cvt->htotal)/100; + + hsync &= ~(FB_CVT_CELLSIZE - 1); + return hsync; +} + +static u32 fb_cvt_vbi_lines(struct fb_cvt_data *cvt) +{ + u32 vbi_lines, min_vbi_lines, act_vbi_lines; + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) { + vbi_lines = (1000 * FB_CVT_RB_MIN_VBLANK)/cvt->hperiod + 1; + min_vbi_lines = FB_CVT_RB_V_FPORCH + cvt->vsync + + FB_CVT_MIN_BPORCH; + + } else { + vbi_lines = (FB_CVT_MIN_VSYNC_BP * 1000)/cvt->hperiod + 1 + + FB_CVT_MIN_VPORCH; + min_vbi_lines = cvt->vsync + FB_CVT_MIN_BPORCH + + FB_CVT_MIN_VPORCH; + } + + if (vbi_lines < min_vbi_lines) + act_vbi_lines = min_vbi_lines; + else + act_vbi_lines = vbi_lines; + + return act_vbi_lines; +} + +static u32 fb_cvt_vtotal(struct fb_cvt_data *cvt) +{ + u32 vtotal = cvt->yres/cvt->interlace; + + vtotal += 2 * cvt->v_margin + cvt->interlace/2 + fb_cvt_vbi_lines(cvt); + vtotal |= cvt->interlace/2; + + return vtotal; +} + +static u32 fb_cvt_pixclock(struct fb_cvt_data *cvt) +{ + u32 pixclock; + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) + pixclock = (cvt->f_refresh * cvt->vtotal * cvt->htotal)/1000; + else + pixclock = (cvt->htotal * 1000000)/cvt->hperiod; + + pixclock /= 250; + pixclock *= 250; + pixclock *= 1000; + + return pixclock; +} + +static u32 fb_cvt_aspect_ratio(struct fb_cvt_data *cvt) +{ + u32 xres = cvt->xres; + u32 yres = cvt->yres; + u32 aspect = -1; + + if (xres == (yres * 4)/3 && !((yres * 4) % 3)) + aspect = 0; + else if (xres == (yres * 16)/9 && !((yres * 16) % 9)) + aspect = 1; + else if (xres == (yres * 16)/10 && !((yres * 16) % 10)) + aspect = 2; + else if (xres == (yres * 5)/4 && !((yres * 5) % 4)) + aspect = 3; + else if (xres == (yres * 15)/9 && !((yres * 15) % 9)) + aspect = 4; + else { + printk(KERN_INFO "fbcvt: Aspect ratio not CVT " + "standard\n"); + aspect = 7; + cvt->status = 1; + } + + return aspect; +} + +static void fb_cvt_print_name(struct fb_cvt_data *cvt) +{ + u32 pixcount, pixcount_mod; + int cnt = 255, offset = 0, read = 0; + u8 *buf = kzalloc(256, GFP_KERNEL); + + if (!buf) + return; + + pixcount = (cvt->xres * (cvt->yres/cvt->interlace))/1000000; + pixcount_mod = (cvt->xres * (cvt->yres/cvt->interlace)) % 1000000; + pixcount_mod /= 1000; + + read = snprintf(buf+offset, cnt, "fbcvt: %dx%d@%d: CVT Name - ", + cvt->xres, cvt->yres, cvt->refresh); + offset += read; + cnt -= read; + + if (cvt->status) + snprintf(buf+offset, cnt, "Not a CVT standard - %d.%03d Mega " + "Pixel Image\n", pixcount, pixcount_mod); + else { + if (pixcount) { + read = snprintf(buf+offset, cnt, "%d", pixcount); + cnt -= read; + offset += read; + } + + read = snprintf(buf+offset, cnt, ".%03dM", pixcount_mod); + cnt -= read; + offset += read; + + if (cvt->aspect_ratio == 0) + read = snprintf(buf+offset, cnt, "3"); + else if (cvt->aspect_ratio == 3) + read = snprintf(buf+offset, cnt, "4"); + else if (cvt->aspect_ratio == 1 || cvt->aspect_ratio == 4) + read = snprintf(buf+offset, cnt, "9"); + else if (cvt->aspect_ratio == 2) + read = snprintf(buf+offset, cnt, "A"); + else + read = 0; + cnt -= read; + offset += read; + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) { + read = snprintf(buf+offset, cnt, "-R"); + cnt -= read; + offset += read; + } + } + + printk(KERN_INFO "%s\n", buf); + kfree(buf); +} + +static void fb_cvt_convert_to_mode(struct fb_cvt_data *cvt, + struct fb_videomode *mode) +{ + mode->refresh = cvt->f_refresh; + mode->pixclock = KHZ2PICOS(cvt->pixclock/1000); + mode->left_margin = cvt->h_back_porch; + mode->right_margin = cvt->h_front_porch; + mode->hsync_len = cvt->hsync; + mode->upper_margin = cvt->v_back_porch; + mode->lower_margin = cvt->v_front_porch; + mode->vsync_len = cvt->vsync; + + mode->sync &= ~(FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT); + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + else + mode->sync |= FB_SYNC_VERT_HIGH_ACT; +} + +/* + * fb_find_mode_cvt - calculate mode using VESA(TM) CVT + * @mode: pointer to fb_videomode; xres, yres, refresh and vmode must be + * pre-filled with the desired values + * @margins: add margin to calculation (1.8% of xres and yres) + * @rb: compute with reduced blanking (for flatpanels) + * + * RETURNS: + * 0 for success + * @mode is filled with computed values. If interlaced, the refresh field + * will be filled with the field rate (2x the frame rate) + * + * DESCRIPTION: + * Computes video timings using VESA(TM) Coordinated Video Timings + */ +int fb_find_mode_cvt(struct fb_videomode *mode, int margins, int rb) +{ + struct fb_cvt_data cvt; + + memset(&cvt, 0, sizeof(cvt)); + + if (margins) + cvt.flags |= FB_CVT_FLAG_MARGINS; + + if (rb) + cvt.flags |= FB_CVT_FLAG_REDUCED_BLANK; + + if (mode->vmode & FB_VMODE_INTERLACED) + cvt.flags |= FB_CVT_FLAG_INTERLACED; + + cvt.xres = mode->xres; + cvt.yres = mode->yres; + cvt.refresh = mode->refresh; + cvt.f_refresh = cvt.refresh; + cvt.interlace = 1; + + if (!cvt.xres || !cvt.yres || !cvt.refresh) { + printk(KERN_INFO "fbcvt: Invalid input parameters\n"); + return 1; + } + + if (!(cvt.refresh == 50 || cvt.refresh == 60 || cvt.refresh == 70 || + cvt.refresh == 85)) { + printk(KERN_INFO "fbcvt: Refresh rate not CVT " + "standard\n"); + cvt.status = 1; + } + + cvt.xres &= ~(FB_CVT_CELLSIZE - 1); + + if (cvt.flags & FB_CVT_FLAG_INTERLACED) { + cvt.interlace = 2; + cvt.f_refresh *= 2; + } + + if (cvt.flags & FB_CVT_FLAG_REDUCED_BLANK) { + if (cvt.refresh != 60) { + printk(KERN_INFO "fbcvt: 60Hz refresh rate " + "advised for reduced blanking\n"); + cvt.status = 1; + } + } + + if (cvt.flags & FB_CVT_FLAG_MARGINS) { + cvt.h_margin = (cvt.xres * 18)/1000; + cvt.h_margin &= ~(FB_CVT_CELLSIZE - 1); + cvt.v_margin = ((cvt.yres/cvt.interlace)* 18)/1000; + } + + cvt.aspect_ratio = fb_cvt_aspect_ratio(&cvt); + cvt.active_pixels = cvt.xres + 2 * cvt.h_margin; + cvt.hperiod = fb_cvt_hperiod(&cvt); + cvt.vsync = fb_cvt_vbi_tab[cvt.aspect_ratio]; + cvt.vtotal = fb_cvt_vtotal(&cvt); + cvt.hblank = fb_cvt_hblank(&cvt); + cvt.htotal = cvt.active_pixels + cvt.hblank; + cvt.hsync = fb_cvt_hsync(&cvt); + cvt.pixclock = fb_cvt_pixclock(&cvt); + cvt.hfreq = cvt.pixclock/cvt.htotal; + cvt.h_back_porch = cvt.hblank/2 + cvt.h_margin; + cvt.h_front_porch = cvt.hblank - cvt.hsync - cvt.h_back_porch + + 2 * cvt.h_margin; + cvt.v_back_porch = 3 + cvt.v_margin; + cvt.v_front_porch = cvt.vtotal - cvt.yres/cvt.interlace - + cvt.v_back_porch - cvt.vsync; + fb_cvt_print_name(&cvt); + fb_cvt_convert_to_mode(&cvt, mode); + + return 0; +} diff --git a/drivers/video/fbdev/fbmem.c b/drivers/video/fbdev/fbmem.c new file mode 100644 index 000000000000..b6d5008f361f --- /dev/null +++ b/drivers/video/fbdev/fbmem.c @@ -0,0 +1,2002 @@ +/* + * linux/drivers/video/fbmem.c + * + * Copyright (C) 1994 Martin Schaller + * + * 2001 - Documented with DocBook + * - Brad Douglas <brad@neruo.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/compat.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/vt.h> +#include <linux/init.h> +#include <linux/linux_logo.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/console.h> +#include <linux/kmod.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/efi.h> +#include <linux/fb.h> + +#include <asm/fb.h> + + + /* + * Frame buffer device initialization and setup routines + */ + +#define FBPIXMAPSIZE (1024 * 8) + +static DEFINE_MUTEX(registration_lock); + +struct fb_info *registered_fb[FB_MAX] __read_mostly; +EXPORT_SYMBOL(registered_fb); + +int num_registered_fb __read_mostly; +EXPORT_SYMBOL(num_registered_fb); + +static struct fb_info *get_fb_info(unsigned int idx) +{ + struct fb_info *fb_info; + + if (idx >= FB_MAX) + return ERR_PTR(-ENODEV); + + mutex_lock(®istration_lock); + fb_info = registered_fb[idx]; + if (fb_info) + atomic_inc(&fb_info->count); + mutex_unlock(®istration_lock); + + return fb_info; +} + +static void put_fb_info(struct fb_info *fb_info) +{ + if (!atomic_dec_and_test(&fb_info->count)) + return; + if (fb_info->fbops->fb_destroy) + fb_info->fbops->fb_destroy(fb_info); +} + +int lock_fb_info(struct fb_info *info) +{ + mutex_lock(&info->lock); + if (!info->fbops) { + mutex_unlock(&info->lock); + return 0; + } + return 1; +} +EXPORT_SYMBOL(lock_fb_info); + +/* + * Helpers + */ + +int fb_get_color_depth(struct fb_var_screeninfo *var, + struct fb_fix_screeninfo *fix) +{ + int depth = 0; + + if (fix->visual == FB_VISUAL_MONO01 || + fix->visual == FB_VISUAL_MONO10) + depth = 1; + else { + if (var->green.length == var->blue.length && + var->green.length == var->red.length && + var->green.offset == var->blue.offset && + var->green.offset == var->red.offset) + depth = var->green.length; + else + depth = var->green.length + var->red.length + + var->blue.length; + } + + return depth; +} +EXPORT_SYMBOL(fb_get_color_depth); + +/* + * Data padding functions. + */ +void fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 s_pitch, u32 height) +{ + __fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, height); +} +EXPORT_SYMBOL(fb_pad_aligned_buffer); + +void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height, + u32 shift_high, u32 shift_low, u32 mod) +{ + u8 mask = (u8) (0xfff << shift_high), tmp; + int i, j; + + for (i = height; i--; ) { + for (j = 0; j < idx; j++) { + tmp = dst[j]; + tmp &= mask; + tmp |= *src >> shift_low; + dst[j] = tmp; + tmp = *src << shift_high; + dst[j+1] = tmp; + src++; + } + tmp = dst[idx]; + tmp &= mask; + tmp |= *src >> shift_low; + dst[idx] = tmp; + if (shift_high < mod) { + tmp = *src << shift_high; + dst[idx+1] = tmp; + } + src++; + dst += d_pitch; + } +} +EXPORT_SYMBOL(fb_pad_unaligned_buffer); + +/* + * we need to lock this section since fb_cursor + * may use fb_imageblit() + */ +char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size) +{ + u32 align = buf->buf_align - 1, offset; + char *addr = buf->addr; + + /* If IO mapped, we need to sync before access, no sharing of + * the pixmap is done + */ + if (buf->flags & FB_PIXMAP_IO) { + if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC)) + info->fbops->fb_sync(info); + return addr; + } + + /* See if we fit in the remaining pixmap space */ + offset = buf->offset + align; + offset &= ~align; + if (offset + size > buf->size) { + /* We do not fit. In order to be able to re-use the buffer, + * we must ensure no asynchronous DMA'ing or whatever operation + * is in progress, we sync for that. + */ + if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC)) + info->fbops->fb_sync(info); + offset = 0; + } + buf->offset = offset + size; + addr += offset; + + return addr; +} +EXPORT_SYMBOL(fb_get_buffer_offset); + +#ifdef CONFIG_LOGO + +static inline unsigned safe_shift(unsigned d, int n) +{ + return n < 0 ? d >> -n : d << n; +} + +static void fb_set_logocmap(struct fb_info *info, + const struct linux_logo *logo) +{ + struct fb_cmap palette_cmap; + u16 palette_green[16]; + u16 palette_blue[16]; + u16 palette_red[16]; + int i, j, n; + const unsigned char *clut = logo->clut; + + palette_cmap.start = 0; + palette_cmap.len = 16; + palette_cmap.red = palette_red; + palette_cmap.green = palette_green; + palette_cmap.blue = palette_blue; + palette_cmap.transp = NULL; + + for (i = 0; i < logo->clutsize; i += n) { + n = logo->clutsize - i; + /* palette_cmap provides space for only 16 colors at once */ + if (n > 16) + n = 16; + palette_cmap.start = 32 + i; + palette_cmap.len = n; + for (j = 0; j < n; ++j) { + palette_cmap.red[j] = clut[0] << 8 | clut[0]; + palette_cmap.green[j] = clut[1] << 8 | clut[1]; + palette_cmap.blue[j] = clut[2] << 8 | clut[2]; + clut += 3; + } + fb_set_cmap(&palette_cmap, info); + } +} + +static void fb_set_logo_truepalette(struct fb_info *info, + const struct linux_logo *logo, + u32 *palette) +{ + static const unsigned char mask[] = { 0,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff }; + unsigned char redmask, greenmask, bluemask; + int redshift, greenshift, blueshift; + int i; + const unsigned char *clut = logo->clut; + + /* + * We have to create a temporary palette since console palette is only + * 16 colors long. + */ + /* Bug: Doesn't obey msb_right ... (who needs that?) */ + redmask = mask[info->var.red.length < 8 ? info->var.red.length : 8]; + greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8]; + bluemask = mask[info->var.blue.length < 8 ? info->var.blue.length : 8]; + redshift = info->var.red.offset - (8 - info->var.red.length); + greenshift = info->var.green.offset - (8 - info->var.green.length); + blueshift = info->var.blue.offset - (8 - info->var.blue.length); + + for ( i = 0; i < logo->clutsize; i++) { + palette[i+32] = (safe_shift((clut[0] & redmask), redshift) | + safe_shift((clut[1] & greenmask), greenshift) | + safe_shift((clut[2] & bluemask), blueshift)); + clut += 3; + } +} + +static void fb_set_logo_directpalette(struct fb_info *info, + const struct linux_logo *logo, + u32 *palette) +{ + int redshift, greenshift, blueshift; + int i; + + redshift = info->var.red.offset; + greenshift = info->var.green.offset; + blueshift = info->var.blue.offset; + + for (i = 32; i < 32 + logo->clutsize; i++) + palette[i] = i << redshift | i << greenshift | i << blueshift; +} + +static void fb_set_logo(struct fb_info *info, + const struct linux_logo *logo, u8 *dst, + int depth) +{ + int i, j, k; + const u8 *src = logo->data; + u8 xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0; + u8 fg = 1, d; + + switch (fb_get_color_depth(&info->var, &info->fix)) { + case 1: + fg = 1; + break; + case 2: + fg = 3; + break; + default: + fg = 7; + break; + } + + if (info->fix.visual == FB_VISUAL_MONO01 || + info->fix.visual == FB_VISUAL_MONO10) + fg = ~((u8) (0xfff << info->var.green.length)); + + switch (depth) { + case 4: + for (i = 0; i < logo->height; i++) + for (j = 0; j < logo->width; src++) { + *dst++ = *src >> 4; + j++; + if (j < logo->width) { + *dst++ = *src & 0x0f; + j++; + } + } + break; + case 1: + for (i = 0; i < logo->height; i++) { + for (j = 0; j < logo->width; src++) { + d = *src ^ xor; + for (k = 7; k >= 0; k--) { + *dst++ = ((d >> k) & 1) ? fg : 0; + j++; + } + } + } + break; + } +} + +/* + * Three (3) kinds of logo maps exist. linux_logo_clut224 (>16 colors), + * linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors). Depending on + * the visual format and color depth of the framebuffer, the DAC, the + * pseudo_palette, and the logo data will be adjusted accordingly. + * + * Case 1 - linux_logo_clut224: + * Color exceeds the number of console colors (16), thus we set the hardware DAC + * using fb_set_cmap() appropriately. The "needs_cmapreset" flag will be set. + * + * For visuals that require color info from the pseudo_palette, we also construct + * one for temporary use. The "needs_directpalette" or "needs_truepalette" flags + * will be set. + * + * Case 2 - linux_logo_vga16: + * The number of colors just matches the console colors, thus there is no need + * to set the DAC or the pseudo_palette. However, the bitmap is packed, ie, + * each byte contains color information for two pixels (upper and lower nibble). + * To be consistent with fb_imageblit() usage, we therefore separate the two + * nibbles into separate bytes. The "depth" flag will be set to 4. + * + * Case 3 - linux_logo_mono: + * This is similar with Case 2. Each byte contains information for 8 pixels. + * We isolate each bit and expand each into a byte. The "depth" flag will + * be set to 1. + */ +static struct logo_data { + int depth; + int needs_directpalette; + int needs_truepalette; + int needs_cmapreset; + const struct linux_logo *logo; +} fb_logo __read_mostly; + +static void fb_rotate_logo_ud(const u8 *in, u8 *out, u32 width, u32 height) +{ + u32 size = width * height, i; + + out += size - 1; + + for (i = size; i--; ) + *out-- = *in++; +} + +static void fb_rotate_logo_cw(const u8 *in, u8 *out, u32 width, u32 height) +{ + int i, j, h = height - 1; + + for (i = 0; i < height; i++) + for (j = 0; j < width; j++) + out[height * j + h - i] = *in++; +} + +static void fb_rotate_logo_ccw(const u8 *in, u8 *out, u32 width, u32 height) +{ + int i, j, w = width - 1; + + for (i = 0; i < height; i++) + for (j = 0; j < width; j++) + out[height * (w - j) + i] = *in++; +} + +static void fb_rotate_logo(struct fb_info *info, u8 *dst, + struct fb_image *image, int rotate) +{ + u32 tmp; + + if (rotate == FB_ROTATE_UD) { + fb_rotate_logo_ud(image->data, dst, image->width, + image->height); + image->dx = info->var.xres - image->width - image->dx; + image->dy = info->var.yres - image->height - image->dy; + } else if (rotate == FB_ROTATE_CW) { + fb_rotate_logo_cw(image->data, dst, image->width, + image->height); + tmp = image->width; + image->width = image->height; + image->height = tmp; + tmp = image->dy; + image->dy = image->dx; + image->dx = info->var.xres - image->width - tmp; + } else if (rotate == FB_ROTATE_CCW) { + fb_rotate_logo_ccw(image->data, dst, image->width, + image->height); + tmp = image->width; + image->width = image->height; + image->height = tmp; + tmp = image->dx; + image->dx = image->dy; + image->dy = info->var.yres - image->height - tmp; + } + + image->data = dst; +} + +static void fb_do_show_logo(struct fb_info *info, struct fb_image *image, + int rotate, unsigned int num) +{ + unsigned int x; + + if (rotate == FB_ROTATE_UR) { + for (x = 0; + x < num && image->dx + image->width <= info->var.xres; + x++) { + info->fbops->fb_imageblit(info, image); + image->dx += image->width + 8; + } + } else if (rotate == FB_ROTATE_UD) { + for (x = 0; x < num && image->dx >= 0; x++) { + info->fbops->fb_imageblit(info, image); + image->dx -= image->width + 8; + } + } else if (rotate == FB_ROTATE_CW) { + for (x = 0; + x < num && image->dy + image->height <= info->var.yres; + x++) { + info->fbops->fb_imageblit(info, image); + image->dy += image->height + 8; + } + } else if (rotate == FB_ROTATE_CCW) { + for (x = 0; x < num && image->dy >= 0; x++) { + info->fbops->fb_imageblit(info, image); + image->dy -= image->height + 8; + } + } +} + +static int fb_show_logo_line(struct fb_info *info, int rotate, + const struct linux_logo *logo, int y, + unsigned int n) +{ + u32 *palette = NULL, *saved_pseudo_palette = NULL; + unsigned char *logo_new = NULL, *logo_rotate = NULL; + struct fb_image image; + + /* Return if the frame buffer is not mapped or suspended */ + if (logo == NULL || info->state != FBINFO_STATE_RUNNING || + info->flags & FBINFO_MODULE) + return 0; + + image.depth = 8; + image.data = logo->data; + + if (fb_logo.needs_cmapreset) + fb_set_logocmap(info, logo); + + if (fb_logo.needs_truepalette || + fb_logo.needs_directpalette) { + palette = kmalloc(256 * 4, GFP_KERNEL); + if (palette == NULL) + return 0; + + if (fb_logo.needs_truepalette) + fb_set_logo_truepalette(info, logo, palette); + else + fb_set_logo_directpalette(info, logo, palette); + + saved_pseudo_palette = info->pseudo_palette; + info->pseudo_palette = palette; + } + + if (fb_logo.depth <= 4) { + logo_new = kmalloc(logo->width * logo->height, GFP_KERNEL); + if (logo_new == NULL) { + kfree(palette); + if (saved_pseudo_palette) + info->pseudo_palette = saved_pseudo_palette; + return 0; + } + image.data = logo_new; + fb_set_logo(info, logo, logo_new, fb_logo.depth); + } + + image.dx = 0; + image.dy = y; + image.width = logo->width; + image.height = logo->height; + + if (rotate) { + logo_rotate = kmalloc(logo->width * + logo->height, GFP_KERNEL); + if (logo_rotate) + fb_rotate_logo(info, logo_rotate, &image, rotate); + } + + fb_do_show_logo(info, &image, rotate, n); + + kfree(palette); + if (saved_pseudo_palette != NULL) + info->pseudo_palette = saved_pseudo_palette; + kfree(logo_new); + kfree(logo_rotate); + return logo->height; +} + + +#ifdef CONFIG_FB_LOGO_EXTRA + +#define FB_LOGO_EX_NUM_MAX 10 +static struct logo_data_extra { + const struct linux_logo *logo; + unsigned int n; +} fb_logo_ex[FB_LOGO_EX_NUM_MAX]; +static unsigned int fb_logo_ex_num; + +void fb_append_extra_logo(const struct linux_logo *logo, unsigned int n) +{ + if (!n || fb_logo_ex_num == FB_LOGO_EX_NUM_MAX) + return; + + fb_logo_ex[fb_logo_ex_num].logo = logo; + fb_logo_ex[fb_logo_ex_num].n = n; + fb_logo_ex_num++; +} + +static int fb_prepare_extra_logos(struct fb_info *info, unsigned int height, + unsigned int yres) +{ + unsigned int i; + + /* FIXME: logo_ex supports only truecolor fb. */ + if (info->fix.visual != FB_VISUAL_TRUECOLOR) + fb_logo_ex_num = 0; + + for (i = 0; i < fb_logo_ex_num; i++) { + if (fb_logo_ex[i].logo->type != fb_logo.logo->type) { + fb_logo_ex[i].logo = NULL; + continue; + } + height += fb_logo_ex[i].logo->height; + if (height > yres) { + height -= fb_logo_ex[i].logo->height; + fb_logo_ex_num = i; + break; + } + } + return height; +} + +static int fb_show_extra_logos(struct fb_info *info, int y, int rotate) +{ + unsigned int i; + + for (i = 0; i < fb_logo_ex_num; i++) + y += fb_show_logo_line(info, rotate, + fb_logo_ex[i].logo, y, fb_logo_ex[i].n); + + return y; +} + +#else /* !CONFIG_FB_LOGO_EXTRA */ + +static inline int fb_prepare_extra_logos(struct fb_info *info, + unsigned int height, + unsigned int yres) +{ + return height; +} + +static inline int fb_show_extra_logos(struct fb_info *info, int y, int rotate) +{ + return y; +} + +#endif /* CONFIG_FB_LOGO_EXTRA */ + + +int fb_prepare_logo(struct fb_info *info, int rotate) +{ + int depth = fb_get_color_depth(&info->var, &info->fix); + unsigned int yres; + + memset(&fb_logo, 0, sizeof(struct logo_data)); + + if (info->flags & FBINFO_MISC_TILEBLITTING || + info->flags & FBINFO_MODULE) + return 0; + + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + depth = info->var.blue.length; + if (info->var.red.length < depth) + depth = info->var.red.length; + if (info->var.green.length < depth) + depth = info->var.green.length; + } + + if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) { + /* assume console colormap */ + depth = 4; + } + + /* Return if no suitable logo was found */ + fb_logo.logo = fb_find_logo(depth); + + if (!fb_logo.logo) { + return 0; + } + + if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD) + yres = info->var.yres; + else + yres = info->var.xres; + + if (fb_logo.logo->height > yres) { + fb_logo.logo = NULL; + return 0; + } + + /* What depth we asked for might be different from what we get */ + if (fb_logo.logo->type == LINUX_LOGO_CLUT224) + fb_logo.depth = 8; + else if (fb_logo.logo->type == LINUX_LOGO_VGA16) + fb_logo.depth = 4; + else + fb_logo.depth = 1; + + + if (fb_logo.depth > 4 && depth > 4) { + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + fb_logo.needs_truepalette = 1; + break; + case FB_VISUAL_DIRECTCOLOR: + fb_logo.needs_directpalette = 1; + fb_logo.needs_cmapreset = 1; + break; + case FB_VISUAL_PSEUDOCOLOR: + fb_logo.needs_cmapreset = 1; + break; + } + } + + return fb_prepare_extra_logos(info, fb_logo.logo->height, yres); +} + +int fb_show_logo(struct fb_info *info, int rotate) +{ + int y; + + y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, + num_online_cpus()); + y = fb_show_extra_logos(info, y, rotate); + + return y; +} +#else +int fb_prepare_logo(struct fb_info *info, int rotate) { return 0; } +int fb_show_logo(struct fb_info *info, int rotate) { return 0; } +#endif /* CONFIG_LOGO */ +EXPORT_SYMBOL(fb_show_logo); + +static void *fb_seq_start(struct seq_file *m, loff_t *pos) +{ + mutex_lock(®istration_lock); + return (*pos < FB_MAX) ? pos : NULL; +} + +static void *fb_seq_next(struct seq_file *m, void *v, loff_t *pos) +{ + (*pos)++; + return (*pos < FB_MAX) ? pos : NULL; +} + +static void fb_seq_stop(struct seq_file *m, void *v) +{ + mutex_unlock(®istration_lock); +} + +static int fb_seq_show(struct seq_file *m, void *v) +{ + int i = *(loff_t *)v; + struct fb_info *fi = registered_fb[i]; + + if (fi) + seq_printf(m, "%d %s\n", fi->node, fi->fix.id); + return 0; +} + +static const struct seq_operations proc_fb_seq_ops = { + .start = fb_seq_start, + .next = fb_seq_next, + .stop = fb_seq_stop, + .show = fb_seq_show, +}; + +static int proc_fb_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_fb_seq_ops); +} + +static const struct file_operations fb_proc_fops = { + .owner = THIS_MODULE, + .open = proc_fb_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/* + * We hold a reference to the fb_info in file->private_data, + * but if the current registered fb has changed, we don't + * actually want to use it. + * + * So look up the fb_info using the inode minor number, + * and just verify it against the reference we have. + */ +static struct fb_info *file_fb_info(struct file *file) +{ + struct inode *inode = file_inode(file); + int fbidx = iminor(inode); + struct fb_info *info = registered_fb[fbidx]; + + if (info != file->private_data) + info = NULL; + return info; +} + +static ssize_t +fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + struct fb_info *info = file_fb_info(file); + u8 *buffer, *dst; + u8 __iomem *src; + int c, cnt = 0, err = 0; + unsigned long total_size; + + if (!info || ! info->screen_base) + return -ENODEV; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + if (info->fbops->fb_read) + return info->fbops->fb_read(info, buf, count, ppos); + + total_size = info->screen_size; + + if (total_size == 0) + total_size = info->fix.smem_len; + + if (p >= total_size) + return 0; + + if (count >= total_size) + count = total_size; + + if (count + p > total_size) + count = total_size - p; + + buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, + GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + src = (u8 __iomem *) (info->screen_base + p); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + while (count) { + c = (count > PAGE_SIZE) ? PAGE_SIZE : count; + dst = buffer; + fb_memcpy_fromfb(dst, src, c); + dst += c; + src += c; + + if (copy_to_user(buf, buffer, c)) { + err = -EFAULT; + break; + } + *ppos += c; + buf += c; + cnt += c; + count -= c; + } + + kfree(buffer); + + return (err) ? err : cnt; +} + +static ssize_t +fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + struct fb_info *info = file_fb_info(file); + u8 *buffer, *src; + u8 __iomem *dst; + int c, cnt = 0, err = 0; + unsigned long total_size; + + if (!info || !info->screen_base) + return -ENODEV; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + if (info->fbops->fb_write) + return info->fbops->fb_write(info, buf, count, ppos); + + total_size = info->screen_size; + + if (total_size == 0) + total_size = info->fix.smem_len; + + if (p > total_size) + return -EFBIG; + + if (count > total_size) { + err = -EFBIG; + count = total_size; + } + + if (count + p > total_size) { + if (!err) + err = -ENOSPC; + + count = total_size - p; + } + + buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, + GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + dst = (u8 __iomem *) (info->screen_base + p); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + while (count) { + c = (count > PAGE_SIZE) ? PAGE_SIZE : count; + src = buffer; + + if (copy_from_user(src, buf, c)) { + err = -EFAULT; + break; + } + + fb_memcpy_tofb(dst, src, c); + dst += c; + src += c; + *ppos += c; + buf += c; + cnt += c; + count -= c; + } + + kfree(buffer); + + return (cnt) ? cnt : err; +} + +int +fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var) +{ + struct fb_fix_screeninfo *fix = &info->fix; + unsigned int yres = info->var.yres; + int err = 0; + + if (var->yoffset > 0) { + if (var->vmode & FB_VMODE_YWRAP) { + if (!fix->ywrapstep || (var->yoffset % fix->ywrapstep)) + err = -EINVAL; + else + yres = 0; + } else if (!fix->ypanstep || (var->yoffset % fix->ypanstep)) + err = -EINVAL; + } + + if (var->xoffset > 0 && (!fix->xpanstep || + (var->xoffset % fix->xpanstep))) + err = -EINVAL; + + if (err || !info->fbops->fb_pan_display || + var->yoffset > info->var.yres_virtual - yres || + var->xoffset > info->var.xres_virtual - info->var.xres) + return -EINVAL; + + if ((err = info->fbops->fb_pan_display(var, info))) + return err; + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + return 0; +} +EXPORT_SYMBOL(fb_pan_display); + +static int fb_check_caps(struct fb_info *info, struct fb_var_screeninfo *var, + u32 activate) +{ + struct fb_event event; + struct fb_blit_caps caps, fbcaps; + int err = 0; + + memset(&caps, 0, sizeof(caps)); + memset(&fbcaps, 0, sizeof(fbcaps)); + caps.flags = (activate & FB_ACTIVATE_ALL) ? 1 : 0; + event.info = info; + event.data = ∩︀ + fb_notifier_call_chain(FB_EVENT_GET_REQ, &event); + info->fbops->fb_get_caps(info, &fbcaps, var); + + if (((fbcaps.x ^ caps.x) & caps.x) || + ((fbcaps.y ^ caps.y) & caps.y) || + (fbcaps.len < caps.len)) + err = -EINVAL; + + return err; +} + +int +fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var) +{ + int flags = info->flags; + int ret = 0; + + if (var->activate & FB_ACTIVATE_INV_MODE) { + struct fb_videomode mode1, mode2; + + fb_var_to_videomode(&mode1, var); + fb_var_to_videomode(&mode2, &info->var); + /* make sure we don't delete the videomode of current var */ + ret = fb_mode_is_equal(&mode1, &mode2); + + if (!ret) { + struct fb_event event; + + event.info = info; + event.data = &mode1; + ret = fb_notifier_call_chain(FB_EVENT_MODE_DELETE, &event); + } + + if (!ret) + fb_delete_videomode(&mode1, &info->modelist); + + + ret = (ret) ? -EINVAL : 0; + goto done; + } + + if ((var->activate & FB_ACTIVATE_FORCE) || + memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) { + u32 activate = var->activate; + + /* When using FOURCC mode, make sure the red, green, blue and + * transp fields are set to 0. + */ + if ((info->fix.capabilities & FB_CAP_FOURCC) && + var->grayscale > 1) { + if (var->red.offset || var->green.offset || + var->blue.offset || var->transp.offset || + var->red.length || var->green.length || + var->blue.length || var->transp.length || + var->red.msb_right || var->green.msb_right || + var->blue.msb_right || var->transp.msb_right) + return -EINVAL; + } + + if (!info->fbops->fb_check_var) { + *var = info->var; + goto done; + } + + ret = info->fbops->fb_check_var(var, info); + + if (ret) + goto done; + + if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) { + struct fb_var_screeninfo old_var; + struct fb_videomode mode; + + if (info->fbops->fb_get_caps) { + ret = fb_check_caps(info, var, activate); + + if (ret) + goto done; + } + + old_var = info->var; + info->var = *var; + + if (info->fbops->fb_set_par) { + ret = info->fbops->fb_set_par(info); + + if (ret) { + info->var = old_var; + printk(KERN_WARNING "detected " + "fb_set_par error, " + "error code: %d\n", ret); + goto done; + } + } + + fb_pan_display(info, &info->var); + fb_set_cmap(&info->cmap, info); + fb_var_to_videomode(&mode, &info->var); + + if (info->modelist.prev && info->modelist.next && + !list_empty(&info->modelist)) + ret = fb_add_videomode(&mode, &info->modelist); + + if (!ret && (flags & FBINFO_MISC_USEREVENT)) { + struct fb_event event; + int evnt = (activate & FB_ACTIVATE_ALL) ? + FB_EVENT_MODE_CHANGE_ALL : + FB_EVENT_MODE_CHANGE; + + info->flags &= ~FBINFO_MISC_USEREVENT; + event.info = info; + event.data = &mode; + fb_notifier_call_chain(evnt, &event); + } + } + } + + done: + return ret; +} +EXPORT_SYMBOL(fb_set_var); + +int +fb_blank(struct fb_info *info, int blank) +{ + struct fb_event event; + int ret = -EINVAL, early_ret; + + if (blank > FB_BLANK_POWERDOWN) + blank = FB_BLANK_POWERDOWN; + + event.info = info; + event.data = ␣ + + early_ret = fb_notifier_call_chain(FB_EARLY_EVENT_BLANK, &event); + + if (info->fbops->fb_blank) + ret = info->fbops->fb_blank(blank, info); + + if (!ret) + fb_notifier_call_chain(FB_EVENT_BLANK, &event); + else { + /* + * if fb_blank is failed then revert effects of + * the early blank event. + */ + if (!early_ret) + fb_notifier_call_chain(FB_R_EARLY_EVENT_BLANK, &event); + } + + return ret; +} +EXPORT_SYMBOL(fb_blank); + +static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct fb_ops *fb; + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + struct fb_con2fbmap con2fb; + struct fb_cmap cmap_from; + struct fb_cmap_user cmap; + struct fb_event event; + void __user *argp = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case FBIOGET_VSCREENINFO: + if (!lock_fb_info(info)) + return -ENODEV; + var = info->var; + unlock_fb_info(info); + + ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0; + break; + case FBIOPUT_VSCREENINFO: + if (copy_from_user(&var, argp, sizeof(var))) + return -EFAULT; + console_lock(); + if (!lock_fb_info(info)) { + console_unlock(); + return -ENODEV; + } + info->flags |= FBINFO_MISC_USEREVENT; + ret = fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + unlock_fb_info(info); + console_unlock(); + if (!ret && copy_to_user(argp, &var, sizeof(var))) + ret = -EFAULT; + break; + case FBIOGET_FSCREENINFO: + if (!lock_fb_info(info)) + return -ENODEV; + fix = info->fix; + unlock_fb_info(info); + + ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0; + break; + case FBIOPUTCMAP: + if (copy_from_user(&cmap, argp, sizeof(cmap))) + return -EFAULT; + ret = fb_set_user_cmap(&cmap, info); + break; + case FBIOGETCMAP: + if (copy_from_user(&cmap, argp, sizeof(cmap))) + return -EFAULT; + if (!lock_fb_info(info)) + return -ENODEV; + cmap_from = info->cmap; + unlock_fb_info(info); + ret = fb_cmap_to_user(&cmap_from, &cmap); + break; + case FBIOPAN_DISPLAY: + if (copy_from_user(&var, argp, sizeof(var))) + return -EFAULT; + console_lock(); + if (!lock_fb_info(info)) { + console_unlock(); + return -ENODEV; + } + ret = fb_pan_display(info, &var); + unlock_fb_info(info); + console_unlock(); + if (ret == 0 && copy_to_user(argp, &var, sizeof(var))) + return -EFAULT; + break; + case FBIO_CURSOR: + ret = -EINVAL; + break; + case FBIOGET_CON2FBMAP: + if (copy_from_user(&con2fb, argp, sizeof(con2fb))) + return -EFAULT; + if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) + return -EINVAL; + con2fb.framebuffer = -1; + event.data = &con2fb; + if (!lock_fb_info(info)) + return -ENODEV; + event.info = info; + fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event); + unlock_fb_info(info); + ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0; + break; + case FBIOPUT_CON2FBMAP: + if (copy_from_user(&con2fb, argp, sizeof(con2fb))) + return -EFAULT; + if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) + return -EINVAL; + if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX) + return -EINVAL; + if (!registered_fb[con2fb.framebuffer]) + request_module("fb%d", con2fb.framebuffer); + if (!registered_fb[con2fb.framebuffer]) { + ret = -EINVAL; + break; + } + event.data = &con2fb; + console_lock(); + if (!lock_fb_info(info)) { + console_unlock(); + return -ENODEV; + } + event.info = info; + ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event); + unlock_fb_info(info); + console_unlock(); + break; + case FBIOBLANK: + console_lock(); + if (!lock_fb_info(info)) { + console_unlock(); + return -ENODEV; + } + info->flags |= FBINFO_MISC_USEREVENT; + ret = fb_blank(info, arg); + info->flags &= ~FBINFO_MISC_USEREVENT; + unlock_fb_info(info); + console_unlock(); + break; + default: + if (!lock_fb_info(info)) + return -ENODEV; + fb = info->fbops; + if (fb->fb_ioctl) + ret = fb->fb_ioctl(info, cmd, arg); + else + ret = -ENOTTY; + unlock_fb_info(info); + } + return ret; +} + +static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct fb_info *info = file_fb_info(file); + + if (!info) + return -ENODEV; + return do_fb_ioctl(info, cmd, arg); +} + +#ifdef CONFIG_COMPAT +struct fb_fix_screeninfo32 { + char id[16]; + compat_caddr_t smem_start; + u32 smem_len; + u32 type; + u32 type_aux; + u32 visual; + u16 xpanstep; + u16 ypanstep; + u16 ywrapstep; + u32 line_length; + compat_caddr_t mmio_start; + u32 mmio_len; + u32 accel; + u16 reserved[3]; +}; + +struct fb_cmap32 { + u32 start; + u32 len; + compat_caddr_t red; + compat_caddr_t green; + compat_caddr_t blue; + compat_caddr_t transp; +}; + +static int fb_getput_cmap(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct fb_cmap_user __user *cmap; + struct fb_cmap32 __user *cmap32; + __u32 data; + int err; + + cmap = compat_alloc_user_space(sizeof(*cmap)); + cmap32 = compat_ptr(arg); + + if (copy_in_user(&cmap->start, &cmap32->start, 2 * sizeof(__u32))) + return -EFAULT; + + if (get_user(data, &cmap32->red) || + put_user(compat_ptr(data), &cmap->red) || + get_user(data, &cmap32->green) || + put_user(compat_ptr(data), &cmap->green) || + get_user(data, &cmap32->blue) || + put_user(compat_ptr(data), &cmap->blue) || + get_user(data, &cmap32->transp) || + put_user(compat_ptr(data), &cmap->transp)) + return -EFAULT; + + err = do_fb_ioctl(info, cmd, (unsigned long) cmap); + + if (!err) { + if (copy_in_user(&cmap32->start, + &cmap->start, + 2 * sizeof(__u32))) + err = -EFAULT; + } + return err; +} + +static int do_fscreeninfo_to_user(struct fb_fix_screeninfo *fix, + struct fb_fix_screeninfo32 __user *fix32) +{ + __u32 data; + int err; + + err = copy_to_user(&fix32->id, &fix->id, sizeof(fix32->id)); + + data = (__u32) (unsigned long) fix->smem_start; + err |= put_user(data, &fix32->smem_start); + + err |= put_user(fix->smem_len, &fix32->smem_len); + err |= put_user(fix->type, &fix32->type); + err |= put_user(fix->type_aux, &fix32->type_aux); + err |= put_user(fix->visual, &fix32->visual); + err |= put_user(fix->xpanstep, &fix32->xpanstep); + err |= put_user(fix->ypanstep, &fix32->ypanstep); + err |= put_user(fix->ywrapstep, &fix32->ywrapstep); + err |= put_user(fix->line_length, &fix32->line_length); + + data = (__u32) (unsigned long) fix->mmio_start; + err |= put_user(data, &fix32->mmio_start); + + err |= put_user(fix->mmio_len, &fix32->mmio_len); + err |= put_user(fix->accel, &fix32->accel); + err |= copy_to_user(fix32->reserved, fix->reserved, + sizeof(fix->reserved)); + + if (err) + return -EFAULT; + return 0; +} + +static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + mm_segment_t old_fs; + struct fb_fix_screeninfo fix; + struct fb_fix_screeninfo32 __user *fix32; + int err; + + fix32 = compat_ptr(arg); + + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = do_fb_ioctl(info, cmd, (unsigned long) &fix); + set_fs(old_fs); + + if (!err) + err = do_fscreeninfo_to_user(&fix, fix32); + + return err; +} + +static long fb_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct fb_info *info = file_fb_info(file); + struct fb_ops *fb; + long ret = -ENOIOCTLCMD; + + if (!info) + return -ENODEV; + fb = info->fbops; + switch(cmd) { + case FBIOGET_VSCREENINFO: + case FBIOPUT_VSCREENINFO: + case FBIOPAN_DISPLAY: + case FBIOGET_CON2FBMAP: + case FBIOPUT_CON2FBMAP: + arg = (unsigned long) compat_ptr(arg); + case FBIOBLANK: + ret = do_fb_ioctl(info, cmd, arg); + break; + + case FBIOGET_FSCREENINFO: + ret = fb_get_fscreeninfo(info, cmd, arg); + break; + + case FBIOGETCMAP: + case FBIOPUTCMAP: + ret = fb_getput_cmap(info, cmd, arg); + break; + + default: + if (fb->fb_compat_ioctl) + ret = fb->fb_compat_ioctl(info, cmd, arg); + break; + } + return ret; +} +#endif + +static int +fb_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct fb_info *info = file_fb_info(file); + struct fb_ops *fb; + unsigned long mmio_pgoff; + unsigned long start; + u32 len; + + if (!info) + return -ENODEV; + fb = info->fbops; + if (!fb) + return -ENODEV; + mutex_lock(&info->mm_lock); + if (fb->fb_mmap) { + int res; + res = fb->fb_mmap(info, vma); + mutex_unlock(&info->mm_lock); + return res; + } + + /* + * Ugh. This can be either the frame buffer mapping, or + * if pgoff points past it, the mmio mapping. + */ + start = info->fix.smem_start; + len = info->fix.smem_len; + mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT; + if (vma->vm_pgoff >= mmio_pgoff) { + if (info->var.accel_flags) { + mutex_unlock(&info->mm_lock); + return -EINVAL; + } + + vma->vm_pgoff -= mmio_pgoff; + start = info->fix.mmio_start; + len = info->fix.mmio_len; + } + mutex_unlock(&info->mm_lock); + + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + fb_pgprotect(file, vma, start); + + return vm_iomap_memory(vma, start, len); +} + +static int +fb_open(struct inode *inode, struct file *file) +__acquires(&info->lock) +__releases(&info->lock) +{ + int fbidx = iminor(inode); + struct fb_info *info; + int res = 0; + + info = get_fb_info(fbidx); + if (!info) { + request_module("fb%d", fbidx); + info = get_fb_info(fbidx); + if (!info) + return -ENODEV; + } + if (IS_ERR(info)) + return PTR_ERR(info); + + mutex_lock(&info->lock); + if (!try_module_get(info->fbops->owner)) { + res = -ENODEV; + goto out; + } + file->private_data = info; + if (info->fbops->fb_open) { + res = info->fbops->fb_open(info,1); + if (res) + module_put(info->fbops->owner); + } +#ifdef CONFIG_FB_DEFERRED_IO + if (info->fbdefio) + fb_deferred_io_open(info, inode, file); +#endif +out: + mutex_unlock(&info->lock); + if (res) + put_fb_info(info); + return res; +} + +static int +fb_release(struct inode *inode, struct file *file) +__acquires(&info->lock) +__releases(&info->lock) +{ + struct fb_info * const info = file->private_data; + + mutex_lock(&info->lock); + if (info->fbops->fb_release) + info->fbops->fb_release(info,1); + module_put(info->fbops->owner); + mutex_unlock(&info->lock); + put_fb_info(info); + return 0; +} + +static const struct file_operations fb_fops = { + .owner = THIS_MODULE, + .read = fb_read, + .write = fb_write, + .unlocked_ioctl = fb_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = fb_compat_ioctl, +#endif + .mmap = fb_mmap, + .open = fb_open, + .release = fb_release, +#ifdef HAVE_ARCH_FB_UNMAPPED_AREA + .get_unmapped_area = get_fb_unmapped_area, +#endif +#ifdef CONFIG_FB_DEFERRED_IO + .fsync = fb_deferred_io_fsync, +#endif + .llseek = default_llseek, +}; + +struct class *fb_class; +EXPORT_SYMBOL(fb_class); + +static int fb_check_foreignness(struct fb_info *fi) +{ + const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN; + + fi->flags &= ~FBINFO_FOREIGN_ENDIAN; + +#ifdef __BIG_ENDIAN + fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH; +#else + fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0; +#endif /* __BIG_ENDIAN */ + + if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) { + pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to " + "support this framebuffer\n", fi->fix.id); + return -ENOSYS; + } else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) { + pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to " + "support this framebuffer\n", fi->fix.id); + return -ENOSYS; + } + + return 0; +} + +static bool apertures_overlap(struct aperture *gen, struct aperture *hw) +{ + /* is the generic aperture base the same as the HW one */ + if (gen->base == hw->base) + return true; + /* is the generic aperture base inside the hw base->hw base+size */ + if (gen->base > hw->base && gen->base < hw->base + hw->size) + return true; + return false; +} + +static bool fb_do_apertures_overlap(struct apertures_struct *gena, + struct apertures_struct *hwa) +{ + int i, j; + if (!hwa || !gena) + return false; + + for (i = 0; i < hwa->count; ++i) { + struct aperture *h = &hwa->ranges[i]; + for (j = 0; j < gena->count; ++j) { + struct aperture *g = &gena->ranges[j]; + printk(KERN_DEBUG "checking generic (%llx %llx) vs hw (%llx %llx)\n", + (unsigned long long)g->base, + (unsigned long long)g->size, + (unsigned long long)h->base, + (unsigned long long)h->size); + if (apertures_overlap(g, h)) + return true; + } + } + + return false; +} + +static int do_unregister_framebuffer(struct fb_info *fb_info); + +#define VGA_FB_PHYS 0xA0000 +static int do_remove_conflicting_framebuffers(struct apertures_struct *a, + const char *name, bool primary) +{ + int i, ret; + + /* check all firmware fbs and kick off if the base addr overlaps */ + for (i = 0 ; i < FB_MAX; i++) { + struct apertures_struct *gen_aper; + if (!registered_fb[i]) + continue; + + if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE)) + continue; + + gen_aper = registered_fb[i]->apertures; + if (fb_do_apertures_overlap(gen_aper, a) || + (primary && gen_aper && gen_aper->count && + gen_aper->ranges[0].base == VGA_FB_PHYS)) { + + printk(KERN_INFO "fb: switching to %s from %s\n", + name, registered_fb[i]->fix.id); + ret = do_unregister_framebuffer(registered_fb[i]); + if (ret) + return ret; + } + } + + return 0; +} + +static int do_register_framebuffer(struct fb_info *fb_info) +{ + int i, ret; + struct fb_event event; + struct fb_videomode mode; + + if (fb_check_foreignness(fb_info)) + return -ENOSYS; + + ret = do_remove_conflicting_framebuffers(fb_info->apertures, + fb_info->fix.id, + fb_is_primary_device(fb_info)); + if (ret) + return ret; + + if (num_registered_fb == FB_MAX) + return -ENXIO; + + num_registered_fb++; + for (i = 0 ; i < FB_MAX; i++) + if (!registered_fb[i]) + break; + fb_info->node = i; + atomic_set(&fb_info->count, 1); + mutex_init(&fb_info->lock); + mutex_init(&fb_info->mm_lock); + + fb_info->dev = device_create(fb_class, fb_info->device, + MKDEV(FB_MAJOR, i), NULL, "fb%d", i); + if (IS_ERR(fb_info->dev)) { + /* Not fatal */ + printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev)); + fb_info->dev = NULL; + } else + fb_init_device(fb_info); + + if (fb_info->pixmap.addr == NULL) { + fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); + if (fb_info->pixmap.addr) { + fb_info->pixmap.size = FBPIXMAPSIZE; + fb_info->pixmap.buf_align = 1; + fb_info->pixmap.scan_align = 1; + fb_info->pixmap.access_align = 32; + fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; + } + } + fb_info->pixmap.offset = 0; + + if (!fb_info->pixmap.blit_x) + fb_info->pixmap.blit_x = ~(u32)0; + + if (!fb_info->pixmap.blit_y) + fb_info->pixmap.blit_y = ~(u32)0; + + if (!fb_info->modelist.prev || !fb_info->modelist.next) + INIT_LIST_HEAD(&fb_info->modelist); + + if (fb_info->skip_vt_switch) + pm_vt_switch_required(fb_info->dev, false); + else + pm_vt_switch_required(fb_info->dev, true); + + fb_var_to_videomode(&mode, &fb_info->var); + fb_add_videomode(&mode, &fb_info->modelist); + registered_fb[i] = fb_info; + + event.info = fb_info; + console_lock(); + if (!lock_fb_info(fb_info)) { + console_unlock(); + return -ENODEV; + } + + fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); + unlock_fb_info(fb_info); + console_unlock(); + return 0; +} + +static int do_unregister_framebuffer(struct fb_info *fb_info) +{ + struct fb_event event; + int i, ret = 0; + + i = fb_info->node; + if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info) + return -EINVAL; + + console_lock(); + if (!lock_fb_info(fb_info)) { + console_unlock(); + return -ENODEV; + } + + event.info = fb_info; + ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event); + unlock_fb_info(fb_info); + console_unlock(); + + if (ret) + return -EINVAL; + + pm_vt_switch_unregister(fb_info->dev); + + unlink_framebuffer(fb_info); + if (fb_info->pixmap.addr && + (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) + kfree(fb_info->pixmap.addr); + fb_destroy_modelist(&fb_info->modelist); + registered_fb[i] = NULL; + num_registered_fb--; + fb_cleanup_device(fb_info); + event.info = fb_info; + console_lock(); + fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); + console_unlock(); + + /* this may free fb info */ + put_fb_info(fb_info); + return 0; +} + +int unlink_framebuffer(struct fb_info *fb_info) +{ + int i; + + i = fb_info->node; + if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info) + return -EINVAL; + + if (fb_info->dev) { + device_destroy(fb_class, MKDEV(FB_MAJOR, i)); + fb_info->dev = NULL; + } + return 0; +} +EXPORT_SYMBOL(unlink_framebuffer); + +int remove_conflicting_framebuffers(struct apertures_struct *a, + const char *name, bool primary) +{ + int ret; + + mutex_lock(®istration_lock); + ret = do_remove_conflicting_framebuffers(a, name, primary); + mutex_unlock(®istration_lock); + + return ret; +} +EXPORT_SYMBOL(remove_conflicting_framebuffers); + +/** + * register_framebuffer - registers a frame buffer device + * @fb_info: frame buffer info structure + * + * Registers a frame buffer device @fb_info. + * + * Returns negative errno on error, or zero for success. + * + */ +int +register_framebuffer(struct fb_info *fb_info) +{ + int ret; + + mutex_lock(®istration_lock); + ret = do_register_framebuffer(fb_info); + mutex_unlock(®istration_lock); + + return ret; +} +EXPORT_SYMBOL(register_framebuffer); + +/** + * unregister_framebuffer - releases a frame buffer device + * @fb_info: frame buffer info structure + * + * Unregisters a frame buffer device @fb_info. + * + * Returns negative errno on error, or zero for success. + * + * This function will also notify the framebuffer console + * to release the driver. + * + * This is meant to be called within a driver's module_exit() + * function. If this is called outside module_exit(), ensure + * that the driver implements fb_open() and fb_release() to + * check that no processes are using the device. + */ +int +unregister_framebuffer(struct fb_info *fb_info) +{ + int ret; + + mutex_lock(®istration_lock); + ret = do_unregister_framebuffer(fb_info); + mutex_unlock(®istration_lock); + + return ret; +} +EXPORT_SYMBOL(unregister_framebuffer); + +/** + * fb_set_suspend - low level driver signals suspend + * @info: framebuffer affected + * @state: 0 = resuming, !=0 = suspending + * + * This is meant to be used by low level drivers to + * signal suspend/resume to the core & clients. + * It must be called with the console semaphore held + */ +void fb_set_suspend(struct fb_info *info, int state) +{ + struct fb_event event; + + event.info = info; + if (state) { + fb_notifier_call_chain(FB_EVENT_SUSPEND, &event); + info->state = FBINFO_STATE_SUSPENDED; + } else { + info->state = FBINFO_STATE_RUNNING; + fb_notifier_call_chain(FB_EVENT_RESUME, &event); + } +} +EXPORT_SYMBOL(fb_set_suspend); + +/** + * fbmem_init - init frame buffer subsystem + * + * Initialize the frame buffer subsystem. + * + * NOTE: This function is _only_ to be called by drivers/char/mem.c. + * + */ + +static int __init +fbmem_init(void) +{ + proc_create("fb", 0, NULL, &fb_proc_fops); + + if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) + printk("unable to get major %d for fb devs\n", FB_MAJOR); + + fb_class = class_create(THIS_MODULE, "graphics"); + if (IS_ERR(fb_class)) { + printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); + fb_class = NULL; + } + return 0; +} + +#ifdef MODULE +module_init(fbmem_init); +static void __exit +fbmem_exit(void) +{ + remove_proc_entry("fb", NULL); + class_destroy(fb_class); + unregister_chrdev(FB_MAJOR, "fb"); +} + +module_exit(fbmem_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Framebuffer base"); +#else +subsys_initcall(fbmem_init); +#endif + +int fb_new_modelist(struct fb_info *info) +{ + struct fb_event event; + struct fb_var_screeninfo var = info->var; + struct list_head *pos, *n; + struct fb_modelist *modelist; + struct fb_videomode *m, mode; + int err = 1; + + list_for_each_safe(pos, n, &info->modelist) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + fb_videomode_to_var(&var, m); + var.activate = FB_ACTIVATE_TEST; + err = fb_set_var(info, &var); + fb_var_to_videomode(&mode, &var); + if (err || !fb_mode_is_equal(m, &mode)) { + list_del(pos); + kfree(pos); + } + } + + err = 1; + + if (!list_empty(&info->modelist)) { + event.info = info; + err = fb_notifier_call_chain(FB_EVENT_NEW_MODELIST, &event); + } + + return err; +} + +static char *video_options[FB_MAX] __read_mostly; +static int ofonly __read_mostly; + +/** + * fb_get_options - get kernel boot parameters + * @name: framebuffer name as it would appear in + * the boot parameter line + * (video=<name>:<options>) + * @option: the option will be stored here + * + * NOTE: Needed to maintain backwards compatibility + */ +int fb_get_options(const char *name, char **option) +{ + char *opt, *options = NULL; + int retval = 0; + int name_len = strlen(name), i; + + if (name_len && ofonly && strncmp(name, "offb", 4)) + retval = 1; + + if (name_len && !retval) { + for (i = 0; i < FB_MAX; i++) { + if (video_options[i] == NULL) + continue; + if (!video_options[i][0]) + continue; + opt = video_options[i]; + if (!strncmp(name, opt, name_len) && + opt[name_len] == ':') + options = opt + name_len + 1; + } + } + /* No match, pass global option */ + if (!options && option && fb_mode_option) + options = kstrdup(fb_mode_option, GFP_KERNEL); + if (options && !strncmp(options, "off", 3)) + retval = 1; + + if (option) + *option = options; + + return retval; +} +EXPORT_SYMBOL(fb_get_options); + +#ifndef MODULE +/** + * video_setup - process command line options + * @options: string of options + * + * Process command line options for frame buffer subsystem. + * + * NOTE: This function is a __setup and __init function. + * It only stores the options. Drivers have to call + * fb_get_options() as necessary. + * + * Returns zero. + * + */ +static int __init video_setup(char *options) +{ + int i, global = 0; + + if (!options || !*options) + global = 1; + + if (!global && !strncmp(options, "ofonly", 6)) { + ofonly = 1; + global = 1; + } + + if (!global && !strchr(options, ':')) { + fb_mode_option = options; + global = 1; + } + + if (!global) { + for (i = 0; i < FB_MAX; i++) { + if (video_options[i] == NULL) { + video_options[i] = options; + break; + } + + } + } + + return 1; +} +__setup("video=", video_setup); +#endif + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/fbmon.c b/drivers/video/fbdev/fbmon.c new file mode 100644 index 000000000000..6103fa6fb54f --- /dev/null +++ b/drivers/video/fbdev/fbmon.c @@ -0,0 +1,1592 @@ +/* + * linux/drivers/video/fbmon.c + * + * Copyright (C) 2002 James Simmons <jsimmons@users.sf.net> + * + * Credits: + * + * The EDID Parser is a conglomeration from the following sources: + * + * 1. SciTech SNAP Graphics Architecture + * Copyright (C) 1991-2002 SciTech Software, Inc. All rights reserved. + * + * 2. XFree86 4.3.0, interpret_edid.c + * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE> + * + * 3. John Fremlin <vii@users.sourceforge.net> and + * Ani Joshi <ajoshi@unixbox.com> + * + * Generalized Timing Formula is derived from: + * + * GTF Spreadsheet by Andy Morrish (1/5/97) + * available at http://www.vesa.org + * + * 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/fb.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <video/edid.h> +#include <video/of_videomode.h> +#include <video/videomode.h> +#ifdef CONFIG_PPC_OF +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#endif +#include "edid.h" + +/* + * EDID parser + */ + +#undef DEBUG /* define this for verbose EDID parsing output */ + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(fmt,## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#define FBMON_FIX_HEADER 1 +#define FBMON_FIX_INPUT 2 +#define FBMON_FIX_TIMINGS 3 + +#ifdef CONFIG_FB_MODE_HELPERS +struct broken_edid { + u8 manufacturer[4]; + u32 model; + u32 fix; +}; + +static const struct broken_edid brokendb[] = { + /* DEC FR-PCXAV-YZ */ + { + .manufacturer = "DEC", + .model = 0x073a, + .fix = FBMON_FIX_HEADER, + }, + /* ViewSonic PF775a */ + { + .manufacturer = "VSC", + .model = 0x5a44, + .fix = FBMON_FIX_INPUT, + }, + /* Sharp UXGA? */ + { + .manufacturer = "SHP", + .model = 0x138e, + .fix = FBMON_FIX_TIMINGS, + }, +}; + +static const unsigned char edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00 +}; + +static void copy_string(unsigned char *c, unsigned char *s) +{ + int i; + c = c + 5; + for (i = 0; (i < 13 && *c != 0x0A); i++) + *(s++) = *(c++); + *s = 0; + while (i-- && (*--s == 0x20)) *s = 0; +} + +static int edid_is_serial_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xff) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_ascii_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xfe) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_limits_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xfd) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_monitor_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xfc) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_timing_block(unsigned char *block) +{ + if ((block[0] != 0x00) || (block[1] != 0x00) || + (block[2] != 0x00) || (block[4] != 0x00)) + return 1; + else + return 0; +} + +static int check_edid(unsigned char *edid) +{ + unsigned char *block = edid + ID_MANUFACTURER_NAME, manufacturer[4]; + unsigned char *b; + u32 model; + int i, fix = 0, ret = 0; + + manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@'; + manufacturer[1] = ((block[0] & 0x03) << 3) + + ((block[1] & 0xe0) >> 5) + '@'; + manufacturer[2] = (block[1] & 0x1f) + '@'; + manufacturer[3] = 0; + model = block[2] + (block[3] << 8); + + for (i = 0; i < ARRAY_SIZE(brokendb); i++) { + if (!strncmp(manufacturer, brokendb[i].manufacturer, 4) && + brokendb[i].model == model) { + fix = brokendb[i].fix; + break; + } + } + + switch (fix) { + case FBMON_FIX_HEADER: + for (i = 0; i < 8; i++) { + if (edid[i] != edid_v1_header[i]) { + ret = fix; + break; + } + } + break; + case FBMON_FIX_INPUT: + b = edid + EDID_STRUCT_DISPLAY; + /* Only if display is GTF capable will + the input type be reset to analog */ + if (b[4] & 0x01 && b[0] & 0x80) + ret = fix; + break; + case FBMON_FIX_TIMINGS: + b = edid + DETAILED_TIMING_DESCRIPTIONS_START; + ret = fix; + + for (i = 0; i < 4; i++) { + if (edid_is_limits_block(b)) { + ret = 0; + break; + } + + b += DETAILED_TIMING_DESCRIPTION_SIZE; + } + + break; + } + + if (ret) + printk("fbmon: The EDID Block of " + "Manufacturer: %s Model: 0x%x is known to " + "be broken,\n", manufacturer, model); + + return ret; +} + +static void fix_edid(unsigned char *edid, int fix) +{ + int i; + unsigned char *b, csum = 0; + + switch (fix) { + case FBMON_FIX_HEADER: + printk("fbmon: trying a header reconstruct\n"); + memcpy(edid, edid_v1_header, 8); + break; + case FBMON_FIX_INPUT: + printk("fbmon: trying to fix input type\n"); + b = edid + EDID_STRUCT_DISPLAY; + b[0] &= ~0x80; + edid[127] += 0x80; + break; + case FBMON_FIX_TIMINGS: + printk("fbmon: trying to fix monitor timings\n"); + b = edid + DETAILED_TIMING_DESCRIPTIONS_START; + for (i = 0; i < 4; i++) { + if (!(edid_is_serial_block(b) || + edid_is_ascii_block(b) || + edid_is_monitor_block(b) || + edid_is_timing_block(b))) { + b[0] = 0x00; + b[1] = 0x00; + b[2] = 0x00; + b[3] = 0xfd; + b[4] = 0x00; + b[5] = 60; /* vfmin */ + b[6] = 60; /* vfmax */ + b[7] = 30; /* hfmin */ + b[8] = 75; /* hfmax */ + b[9] = 17; /* pixclock - 170 MHz*/ + b[10] = 0; /* GTF */ + break; + } + + b += DETAILED_TIMING_DESCRIPTION_SIZE; + } + + for (i = 0; i < EDID_LENGTH - 1; i++) + csum += edid[i]; + + edid[127] = 256 - csum; + break; + } +} + +static int edid_checksum(unsigned char *edid) +{ + unsigned char csum = 0, all_null = 0; + int i, err = 0, fix = check_edid(edid); + + if (fix) + fix_edid(edid, fix); + + for (i = 0; i < EDID_LENGTH; i++) { + csum += edid[i]; + all_null |= edid[i]; + } + + if (csum == 0x00 && all_null) { + /* checksum passed, everything's good */ + err = 1; + } + + return err; +} + +static int edid_check_header(unsigned char *edid) +{ + int i, err = 1, fix = check_edid(edid); + + if (fix) + fix_edid(edid, fix); + + for (i = 0; i < 8; i++) { + if (edid[i] != edid_v1_header[i]) + err = 0; + } + + return err; +} + +static void parse_vendor_block(unsigned char *block, struct fb_monspecs *specs) +{ + specs->manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@'; + specs->manufacturer[1] = ((block[0] & 0x03) << 3) + + ((block[1] & 0xe0) >> 5) + '@'; + specs->manufacturer[2] = (block[1] & 0x1f) + '@'; + specs->manufacturer[3] = 0; + specs->model = block[2] + (block[3] << 8); + specs->serial = block[4] + (block[5] << 8) + + (block[6] << 16) + (block[7] << 24); + specs->year = block[9] + 1990; + specs->week = block[8]; + DPRINTK(" Manufacturer: %s\n", specs->manufacturer); + DPRINTK(" Model: %x\n", specs->model); + DPRINTK(" Serial#: %u\n", specs->serial); + DPRINTK(" Year: %u Week %u\n", specs->year, specs->week); +} + +static void get_dpms_capabilities(unsigned char flags, + struct fb_monspecs *specs) +{ + specs->dpms = 0; + if (flags & DPMS_ACTIVE_OFF) + specs->dpms |= FB_DPMS_ACTIVE_OFF; + if (flags & DPMS_SUSPEND) + specs->dpms |= FB_DPMS_SUSPEND; + if (flags & DPMS_STANDBY) + specs->dpms |= FB_DPMS_STANDBY; + DPRINTK(" DPMS: Active %s, Suspend %s, Standby %s\n", + (flags & DPMS_ACTIVE_OFF) ? "yes" : "no", + (flags & DPMS_SUSPEND) ? "yes" : "no", + (flags & DPMS_STANDBY) ? "yes" : "no"); +} + +static void get_chroma(unsigned char *block, struct fb_monspecs *specs) +{ + int tmp; + + DPRINTK(" Chroma\n"); + /* Chromaticity data */ + tmp = ((block[5] & (3 << 6)) >> 6) | (block[0x7] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.redx = tmp/1024; + DPRINTK(" RedX: 0.%03d ", specs->chroma.redx); + + tmp = ((block[5] & (3 << 4)) >> 4) | (block[0x8] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.redy = tmp/1024; + DPRINTK("RedY: 0.%03d\n", specs->chroma.redy); + + tmp = ((block[5] & (3 << 2)) >> 2) | (block[0x9] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.greenx = tmp/1024; + DPRINTK(" GreenX: 0.%03d ", specs->chroma.greenx); + + tmp = (block[5] & 3) | (block[0xa] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.greeny = tmp/1024; + DPRINTK("GreenY: 0.%03d\n", specs->chroma.greeny); + + tmp = ((block[6] & (3 << 6)) >> 6) | (block[0xb] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.bluex = tmp/1024; + DPRINTK(" BlueX: 0.%03d ", specs->chroma.bluex); + + tmp = ((block[6] & (3 << 4)) >> 4) | (block[0xc] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.bluey = tmp/1024; + DPRINTK("BlueY: 0.%03d\n", specs->chroma.bluey); + + tmp = ((block[6] & (3 << 2)) >> 2) | (block[0xd] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.whitex = tmp/1024; + DPRINTK(" WhiteX: 0.%03d ", specs->chroma.whitex); + + tmp = (block[6] & 3) | (block[0xe] << 2); + tmp *= 1000; + tmp += 512; + specs->chroma.whitey = tmp/1024; + DPRINTK("WhiteY: 0.%03d\n", specs->chroma.whitey); +} + +static void calc_mode_timings(int xres, int yres, int refresh, + struct fb_videomode *mode) +{ + struct fb_var_screeninfo *var; + + var = kzalloc(sizeof(struct fb_var_screeninfo), GFP_KERNEL); + + if (var) { + var->xres = xres; + var->yres = yres; + fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, + refresh, var, NULL); + mode->xres = xres; + mode->yres = yres; + mode->pixclock = var->pixclock; + mode->refresh = refresh; + mode->left_margin = var->left_margin; + mode->right_margin = var->right_margin; + mode->upper_margin = var->upper_margin; + mode->lower_margin = var->lower_margin; + mode->hsync_len = var->hsync_len; + mode->vsync_len = var->vsync_len; + mode->vmode = 0; + mode->sync = 0; + kfree(var); + } +} + +static int get_est_timing(unsigned char *block, struct fb_videomode *mode) +{ + int num = 0; + unsigned char c; + + c = block[0]; + if (c&0x80) { + calc_mode_timings(720, 400, 70, &mode[num]); + mode[num++].flag = FB_MODE_IS_CALCULATED; + DPRINTK(" 720x400@70Hz\n"); + } + if (c&0x40) { + calc_mode_timings(720, 400, 88, &mode[num]); + mode[num++].flag = FB_MODE_IS_CALCULATED; + DPRINTK(" 720x400@88Hz\n"); + } + if (c&0x20) { + mode[num++] = vesa_modes[3]; + DPRINTK(" 640x480@60Hz\n"); + } + if (c&0x10) { + calc_mode_timings(640, 480, 67, &mode[num]); + mode[num++].flag = FB_MODE_IS_CALCULATED; + DPRINTK(" 640x480@67Hz\n"); + } + if (c&0x08) { + mode[num++] = vesa_modes[4]; + DPRINTK(" 640x480@72Hz\n"); + } + if (c&0x04) { + mode[num++] = vesa_modes[5]; + DPRINTK(" 640x480@75Hz\n"); + } + if (c&0x02) { + mode[num++] = vesa_modes[7]; + DPRINTK(" 800x600@56Hz\n"); + } + if (c&0x01) { + mode[num++] = vesa_modes[8]; + DPRINTK(" 800x600@60Hz\n"); + } + + c = block[1]; + if (c&0x80) { + mode[num++] = vesa_modes[9]; + DPRINTK(" 800x600@72Hz\n"); + } + if (c&0x40) { + mode[num++] = vesa_modes[10]; + DPRINTK(" 800x600@75Hz\n"); + } + if (c&0x20) { + calc_mode_timings(832, 624, 75, &mode[num]); + mode[num++].flag = FB_MODE_IS_CALCULATED; + DPRINTK(" 832x624@75Hz\n"); + } + if (c&0x10) { + mode[num++] = vesa_modes[12]; + DPRINTK(" 1024x768@87Hz Interlaced\n"); + } + if (c&0x08) { + mode[num++] = vesa_modes[13]; + DPRINTK(" 1024x768@60Hz\n"); + } + if (c&0x04) { + mode[num++] = vesa_modes[14]; + DPRINTK(" 1024x768@70Hz\n"); + } + if (c&0x02) { + mode[num++] = vesa_modes[15]; + DPRINTK(" 1024x768@75Hz\n"); + } + if (c&0x01) { + mode[num++] = vesa_modes[21]; + DPRINTK(" 1280x1024@75Hz\n"); + } + c = block[2]; + if (c&0x80) { + mode[num++] = vesa_modes[17]; + DPRINTK(" 1152x870@75Hz\n"); + } + DPRINTK(" Manufacturer's mask: %x\n",c&0x7F); + return num; +} + +static int get_std_timing(unsigned char *block, struct fb_videomode *mode, + int ver, int rev) +{ + int xres, yres = 0, refresh, ratio, i; + + xres = (block[0] + 31) * 8; + if (xres <= 256) + return 0; + + ratio = (block[1] & 0xc0) >> 6; + switch (ratio) { + case 0: + /* in EDID 1.3 the meaning of 0 changed to 16:10 (prior 1:1) */ + if (ver < 1 || (ver == 1 && rev < 3)) + yres = xres; + else + yres = (xres * 10)/16; + break; + case 1: + yres = (xres * 3)/4; + break; + case 2: + yres = (xres * 4)/5; + break; + case 3: + yres = (xres * 9)/16; + break; + } + refresh = (block[1] & 0x3f) + 60; + + DPRINTK(" %dx%d@%dHz\n", xres, yres, refresh); + for (i = 0; i < VESA_MODEDB_SIZE; i++) { + if (vesa_modes[i].xres == xres && + vesa_modes[i].yres == yres && + vesa_modes[i].refresh == refresh) { + *mode = vesa_modes[i]; + mode->flag |= FB_MODE_IS_STANDARD; + return 1; + } + } + calc_mode_timings(xres, yres, refresh, mode); + return 1; +} + +static int get_dst_timing(unsigned char *block, + struct fb_videomode *mode, int ver, int rev) +{ + int j, num = 0; + + for (j = 0; j < 6; j++, block += STD_TIMING_DESCRIPTION_SIZE) + num += get_std_timing(block, &mode[num], ver, rev); + + return num; +} + +static void get_detailed_timing(unsigned char *block, + struct fb_videomode *mode) +{ + mode->xres = H_ACTIVE; + mode->yres = V_ACTIVE; + mode->pixclock = PIXEL_CLOCK; + mode->pixclock /= 1000; + mode->pixclock = KHZ2PICOS(mode->pixclock); + mode->right_margin = H_SYNC_OFFSET; + mode->left_margin = (H_ACTIVE + H_BLANKING) - + (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); + mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - + V_SYNC_WIDTH; + mode->lower_margin = V_SYNC_OFFSET; + mode->hsync_len = H_SYNC_WIDTH; + mode->vsync_len = V_SYNC_WIDTH; + if (HSYNC_POSITIVE) + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (VSYNC_POSITIVE) + mode->sync |= FB_SYNC_VERT_HIGH_ACT; + mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * + (V_ACTIVE + V_BLANKING)); + if (INTERLACED) { + mode->yres *= 2; + mode->upper_margin *= 2; + mode->lower_margin *= 2; + mode->vsync_len *= 2; + mode->vmode |= FB_VMODE_INTERLACED; + } + mode->flag = FB_MODE_IS_DETAILED; + + DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000); + DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, + H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); + DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, + V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); + DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-", + (VSYNC_POSITIVE) ? "+" : "-"); +} + +/** + * fb_create_modedb - create video mode database + * @edid: EDID data + * @dbsize: database size + * + * RETURNS: struct fb_videomode, @dbsize contains length of database + * + * DESCRIPTION: + * This function builds a mode database using the contents of the EDID + * data + */ +static struct fb_videomode *fb_create_modedb(unsigned char *edid, int *dbsize) +{ + struct fb_videomode *mode, *m; + unsigned char *block; + int num = 0, i, first = 1; + int ver, rev; + + ver = edid[EDID_STRUCT_VERSION]; + rev = edid[EDID_STRUCT_REVISION]; + + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return NULL; + + if (edid == NULL || !edid_checksum(edid) || + !edid_check_header(edid)) { + kfree(mode); + return NULL; + } + + *dbsize = 0; + + DPRINTK(" Detailed Timings\n"); + block = edid + DETAILED_TIMING_DESCRIPTIONS_START; + for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { + if (!(block[0] == 0x00 && block[1] == 0x00)) { + get_detailed_timing(block, &mode[num]); + if (first) { + mode[num].flag |= FB_MODE_IS_FIRST; + first = 0; + } + num++; + } + } + + DPRINTK(" Supported VESA Modes\n"); + block = edid + ESTABLISHED_TIMING_1; + num += get_est_timing(block, &mode[num]); + + DPRINTK(" Standard Timings\n"); + block = edid + STD_TIMING_DESCRIPTIONS_START; + for (i = 0; i < STD_TIMING; i++, block += STD_TIMING_DESCRIPTION_SIZE) + num += get_std_timing(block, &mode[num], ver, rev); + + block = edid + DETAILED_TIMING_DESCRIPTIONS_START; + for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { + if (block[0] == 0x00 && block[1] == 0x00 && block[3] == 0xfa) + num += get_dst_timing(block + 5, &mode[num], ver, rev); + } + + /* Yikes, EDID data is totally useless */ + if (!num) { + kfree(mode); + return NULL; + } + + *dbsize = num; + m = kmalloc(num * sizeof(struct fb_videomode), GFP_KERNEL); + if (!m) + return mode; + memmove(m, mode, num * sizeof(struct fb_videomode)); + kfree(mode); + return m; +} + +/** + * fb_destroy_modedb - destroys mode database + * @modedb: mode database to destroy + * + * DESCRIPTION: + * Destroy mode database created by fb_create_modedb + */ +void fb_destroy_modedb(struct fb_videomode *modedb) +{ + kfree(modedb); +} + +static int fb_get_monitor_limits(unsigned char *edid, struct fb_monspecs *specs) +{ + int i, retval = 1; + unsigned char *block; + + block = edid + DETAILED_TIMING_DESCRIPTIONS_START; + + DPRINTK(" Monitor Operating Limits: "); + + for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { + if (edid_is_limits_block(block)) { + specs->hfmin = H_MIN_RATE * 1000; + specs->hfmax = H_MAX_RATE * 1000; + specs->vfmin = V_MIN_RATE; + specs->vfmax = V_MAX_RATE; + specs->dclkmax = MAX_PIXEL_CLOCK * 1000000; + specs->gtf = (GTF_SUPPORT) ? 1 : 0; + retval = 0; + DPRINTK("From EDID\n"); + break; + } + } + + /* estimate monitor limits based on modes supported */ + if (retval) { + struct fb_videomode *modes, *mode; + int num_modes, hz, hscan, pixclock; + int vtotal, htotal; + + modes = fb_create_modedb(edid, &num_modes); + if (!modes) { + DPRINTK("None Available\n"); + return 1; + } + + retval = 0; + for (i = 0; i < num_modes; i++) { + mode = &modes[i]; + pixclock = PICOS2KHZ(modes[i].pixclock) * 1000; + htotal = mode->xres + mode->right_margin + mode->hsync_len + + mode->left_margin; + vtotal = mode->yres + mode->lower_margin + mode->vsync_len + + mode->upper_margin; + + if (mode->vmode & FB_VMODE_INTERLACED) + vtotal /= 2; + + if (mode->vmode & FB_VMODE_DOUBLE) + vtotal *= 2; + + hscan = (pixclock + htotal / 2) / htotal; + hscan = (hscan + 500) / 1000 * 1000; + hz = (hscan + vtotal / 2) / vtotal; + + if (specs->dclkmax == 0 || specs->dclkmax < pixclock) + specs->dclkmax = pixclock; + + if (specs->dclkmin == 0 || specs->dclkmin > pixclock) + specs->dclkmin = pixclock; + + if (specs->hfmax == 0 || specs->hfmax < hscan) + specs->hfmax = hscan; + + if (specs->hfmin == 0 || specs->hfmin > hscan) + specs->hfmin = hscan; + + if (specs->vfmax == 0 || specs->vfmax < hz) + specs->vfmax = hz; + + if (specs->vfmin == 0 || specs->vfmin > hz) + specs->vfmin = hz; + } + DPRINTK("Extrapolated\n"); + fb_destroy_modedb(modes); + } + DPRINTK(" H: %d-%dKHz V: %d-%dHz DCLK: %dMHz\n", + specs->hfmin/1000, specs->hfmax/1000, specs->vfmin, + specs->vfmax, specs->dclkmax/1000000); + return retval; +} + +static void get_monspecs(unsigned char *edid, struct fb_monspecs *specs) +{ + unsigned char c, *block; + + block = edid + EDID_STRUCT_DISPLAY; + + fb_get_monitor_limits(edid, specs); + + c = block[0] & 0x80; + specs->input = 0; + if (c) { + specs->input |= FB_DISP_DDI; + DPRINTK(" Digital Display Input"); + } else { + DPRINTK(" Analog Display Input: Input Voltage - "); + switch ((block[0] & 0x60) >> 5) { + case 0: + DPRINTK("0.700V/0.300V"); + specs->input |= FB_DISP_ANA_700_300; + break; + case 1: + DPRINTK("0.714V/0.286V"); + specs->input |= FB_DISP_ANA_714_286; + break; + case 2: + DPRINTK("1.000V/0.400V"); + specs->input |= FB_DISP_ANA_1000_400; + break; + case 3: + DPRINTK("0.700V/0.000V"); + specs->input |= FB_DISP_ANA_700_000; + break; + } + } + DPRINTK("\n Sync: "); + c = block[0] & 0x10; + if (c) + DPRINTK(" Configurable signal level\n"); + c = block[0] & 0x0f; + specs->signal = 0; + if (c & 0x10) { + DPRINTK("Blank to Blank "); + specs->signal |= FB_SIGNAL_BLANK_BLANK; + } + if (c & 0x08) { + DPRINTK("Separate "); + specs->signal |= FB_SIGNAL_SEPARATE; + } + if (c & 0x04) { + DPRINTK("Composite "); + specs->signal |= FB_SIGNAL_COMPOSITE; + } + if (c & 0x02) { + DPRINTK("Sync on Green "); + specs->signal |= FB_SIGNAL_SYNC_ON_GREEN; + } + if (c & 0x01) { + DPRINTK("Serration on "); + specs->signal |= FB_SIGNAL_SERRATION_ON; + } + DPRINTK("\n"); + specs->max_x = block[1]; + specs->max_y = block[2]; + DPRINTK(" Max H-size in cm: "); + if (specs->max_x) + DPRINTK("%d\n", specs->max_x); + else + DPRINTK("variable\n"); + DPRINTK(" Max V-size in cm: "); + if (specs->max_y) + DPRINTK("%d\n", specs->max_y); + else + DPRINTK("variable\n"); + + c = block[3]; + specs->gamma = c+100; + DPRINTK(" Gamma: "); + DPRINTK("%d.%d\n", specs->gamma/100, specs->gamma % 100); + + get_dpms_capabilities(block[4], specs); + + switch ((block[4] & 0x18) >> 3) { + case 0: + DPRINTK(" Monochrome/Grayscale\n"); + specs->input |= FB_DISP_MONO; + break; + case 1: + DPRINTK(" RGB Color Display\n"); + specs->input |= FB_DISP_RGB; + break; + case 2: + DPRINTK(" Non-RGB Multicolor Display\n"); + specs->input |= FB_DISP_MULTI; + break; + default: + DPRINTK(" Unknown\n"); + specs->input |= FB_DISP_UNKNOWN; + break; + } + + get_chroma(block, specs); + + specs->misc = 0; + c = block[4] & 0x7; + if (c & 0x04) { + DPRINTK(" Default color format is primary\n"); + specs->misc |= FB_MISC_PRIM_COLOR; + } + if (c & 0x02) { + DPRINTK(" First DETAILED Timing is preferred\n"); + specs->misc |= FB_MISC_1ST_DETAIL; + } + if (c & 0x01) { + printk(" Display is GTF capable\n"); + specs->gtf = 1; + } +} + +int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var) +{ + int i; + unsigned char *block; + + if (edid == NULL || var == NULL) + return 1; + + if (!(edid_checksum(edid))) + return 1; + + if (!(edid_check_header(edid))) + return 1; + + block = edid + DETAILED_TIMING_DESCRIPTIONS_START; + + for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { + if (edid_is_timing_block(block)) { + var->xres = var->xres_virtual = H_ACTIVE; + var->yres = var->yres_virtual = V_ACTIVE; + var->height = var->width = 0; + var->right_margin = H_SYNC_OFFSET; + var->left_margin = (H_ACTIVE + H_BLANKING) - + (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); + var->upper_margin = V_BLANKING - V_SYNC_OFFSET - + V_SYNC_WIDTH; + var->lower_margin = V_SYNC_OFFSET; + var->hsync_len = H_SYNC_WIDTH; + var->vsync_len = V_SYNC_WIDTH; + var->pixclock = PIXEL_CLOCK; + var->pixclock /= 1000; + var->pixclock = KHZ2PICOS(var->pixclock); + + if (HSYNC_POSITIVE) + var->sync |= FB_SYNC_HOR_HIGH_ACT; + if (VSYNC_POSITIVE) + var->sync |= FB_SYNC_VERT_HIGH_ACT; + return 0; + } + } + return 1; +} + +void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) +{ + unsigned char *block; + int i, found = 0; + + if (edid == NULL) + return; + + if (!(edid_checksum(edid))) + return; + + if (!(edid_check_header(edid))) + return; + + memset(specs, 0, sizeof(struct fb_monspecs)); + + specs->version = edid[EDID_STRUCT_VERSION]; + specs->revision = edid[EDID_STRUCT_REVISION]; + + DPRINTK("========================================\n"); + DPRINTK("Display Information (EDID)\n"); + DPRINTK("========================================\n"); + DPRINTK(" EDID Version %d.%d\n", (int) specs->version, + (int) specs->revision); + + parse_vendor_block(edid + ID_MANUFACTURER_NAME, specs); + + block = edid + DETAILED_TIMING_DESCRIPTIONS_START; + for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { + if (edid_is_serial_block(block)) { + copy_string(block, specs->serial_no); + DPRINTK(" Serial Number: %s\n", specs->serial_no); + } else if (edid_is_ascii_block(block)) { + copy_string(block, specs->ascii); + DPRINTK(" ASCII Block: %s\n", specs->ascii); + } else if (edid_is_monitor_block(block)) { + copy_string(block, specs->monitor); + DPRINTK(" Monitor Name: %s\n", specs->monitor); + } + } + + DPRINTK(" Display Characteristics:\n"); + get_monspecs(edid, specs); + + specs->modedb = fb_create_modedb(edid, &specs->modedb_len); + + /* + * Workaround for buggy EDIDs that sets that the first + * detailed timing is preferred but has not detailed + * timing specified + */ + for (i = 0; i < specs->modedb_len; i++) { + if (specs->modedb[i].flag & FB_MODE_IS_DETAILED) { + found = 1; + break; + } + } + + if (!found) + specs->misc &= ~FB_MISC_1ST_DETAIL; + + DPRINTK("========================================\n"); +} + +/** + * fb_edid_add_monspecs() - add monitor video modes from E-EDID data + * @edid: 128 byte array with an E-EDID block + * @spacs: monitor specs to be extended + */ +void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) +{ + unsigned char *block; + struct fb_videomode *m; + int num = 0, i; + u8 svd[64], edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE]; + u8 pos = 4, svd_n = 0; + + if (!edid) + return; + + if (!edid_checksum(edid)) + return; + + if (edid[0] != 0x2 || + edid[2] < 4 || edid[2] > 128 - DETAILED_TIMING_DESCRIPTION_SIZE) + return; + + DPRINTK(" Short Video Descriptors\n"); + + while (pos < edid[2]) { + u8 len = edid[pos] & 0x1f, type = (edid[pos] >> 5) & 7; + pr_debug("Data block %u of %u bytes\n", type, len); + if (type == 2) + for (i = pos; i < pos + len; i++) { + u8 idx = edid[pos + i] & 0x7f; + svd[svd_n++] = idx; + pr_debug("N%sative mode #%d\n", + edid[pos + i] & 0x80 ? "" : "on-n", idx); + } + pos += len + 1; + } + + block = edid + edid[2]; + + DPRINTK(" Extended Detailed Timings\n"); + + for (i = 0; i < (128 - edid[2]) / DETAILED_TIMING_DESCRIPTION_SIZE; + i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) + if (PIXEL_CLOCK) + edt[num++] = block - edid; + + /* Yikes, EDID data is totally useless */ + if (!(num + svd_n)) + return; + + m = kzalloc((specs->modedb_len + num + svd_n) * + sizeof(struct fb_videomode), GFP_KERNEL); + + if (!m) + return; + + memcpy(m, specs->modedb, specs->modedb_len * sizeof(struct fb_videomode)); + + for (i = specs->modedb_len; i < specs->modedb_len + num; i++) { + get_detailed_timing(edid + edt[i - specs->modedb_len], &m[i]); + if (i == specs->modedb_len) + m[i].flag |= FB_MODE_IS_FIRST; + pr_debug("Adding %ux%u@%u\n", m[i].xres, m[i].yres, m[i].refresh); + } + + for (i = specs->modedb_len + num; i < specs->modedb_len + num + svd_n; i++) { + int idx = svd[i - specs->modedb_len - num]; + if (!idx || idx > 63) { + pr_warning("Reserved SVD code %d\n", idx); + } else if (idx > ARRAY_SIZE(cea_modes) || !cea_modes[idx].xres) { + pr_warning("Unimplemented SVD code %d\n", idx); + } else { + memcpy(&m[i], cea_modes + idx, sizeof(m[i])); + pr_debug("Adding SVD #%d: %ux%u@%u\n", idx, + m[i].xres, m[i].yres, m[i].refresh); + } + } + + kfree(specs->modedb); + specs->modedb = m; + specs->modedb_len = specs->modedb_len + num + svd_n; +} + +/* + * VESA Generalized Timing Formula (GTF) + */ + +#define FLYBACK 550 +#define V_FRONTPORCH 1 +#define H_OFFSET 40 +#define H_SCALEFACTOR 20 +#define H_BLANKSCALE 128 +#define H_GRADIENT 600 +#define C_VAL 30 +#define M_VAL 300 + +struct __fb_timings { + u32 dclk; + u32 hfreq; + u32 vfreq; + u32 hactive; + u32 vactive; + u32 hblank; + u32 vblank; + u32 htotal; + u32 vtotal; +}; + +/** + * fb_get_vblank - get vertical blank time + * @hfreq: horizontal freq + * + * DESCRIPTION: + * vblank = right_margin + vsync_len + left_margin + * + * given: right_margin = 1 (V_FRONTPORCH) + * vsync_len = 3 + * flyback = 550 + * + * flyback * hfreq + * left_margin = --------------- - vsync_len + * 1000000 + */ +static u32 fb_get_vblank(u32 hfreq) +{ + u32 vblank; + + vblank = (hfreq * FLYBACK)/1000; + vblank = (vblank + 500)/1000; + return (vblank + V_FRONTPORCH); +} + +/** + * fb_get_hblank_by_freq - get horizontal blank time given hfreq + * @hfreq: horizontal freq + * @xres: horizontal resolution in pixels + * + * DESCRIPTION: + * + * xres * duty_cycle + * hblank = ------------------ + * 100 - duty_cycle + * + * duty cycle = percent of htotal assigned to inactive display + * duty cycle = C - (M/Hfreq) + * + * where: C = ((offset - scale factor) * blank_scale) + * -------------------------------------- + scale factor + * 256 + * M = blank_scale * gradient + * + */ +static u32 fb_get_hblank_by_hfreq(u32 hfreq, u32 xres) +{ + u32 c_val, m_val, duty_cycle, hblank; + + c_val = (((H_OFFSET - H_SCALEFACTOR) * H_BLANKSCALE)/256 + + H_SCALEFACTOR) * 1000; + m_val = (H_BLANKSCALE * H_GRADIENT)/256; + m_val = (m_val * 1000000)/hfreq; + duty_cycle = c_val - m_val; + hblank = (xres * duty_cycle)/(100000 - duty_cycle); + return (hblank); +} + +/** + * fb_get_hblank_by_dclk - get horizontal blank time given pixelclock + * @dclk: pixelclock in Hz + * @xres: horizontal resolution in pixels + * + * DESCRIPTION: + * + * xres * duty_cycle + * hblank = ------------------ + * 100 - duty_cycle + * + * duty cycle = percent of htotal assigned to inactive display + * duty cycle = C - (M * h_period) + * + * where: h_period = SQRT(100 - C + (0.4 * xres * M)/dclk) + C - 100 + * ----------------------------------------------- + * 2 * M + * M = 300; + * C = 30; + + */ +static u32 fb_get_hblank_by_dclk(u32 dclk, u32 xres) +{ + u32 duty_cycle, h_period, hblank; + + dclk /= 1000; + h_period = 100 - C_VAL; + h_period *= h_period; + h_period += (M_VAL * xres * 2 * 1000)/(5 * dclk); + h_period *= 10000; + + h_period = int_sqrt(h_period); + h_period -= (100 - C_VAL) * 100; + h_period *= 1000; + h_period /= 2 * M_VAL; + + duty_cycle = C_VAL * 1000 - (M_VAL * h_period)/100; + hblank = (xres * duty_cycle)/(100000 - duty_cycle) + 8; + hblank &= ~15; + return (hblank); +} + +/** + * fb_get_hfreq - estimate hsync + * @vfreq: vertical refresh rate + * @yres: vertical resolution + * + * DESCRIPTION: + * + * (yres + front_port) * vfreq * 1000000 + * hfreq = ------------------------------------- + * (1000000 - (vfreq * FLYBACK) + * + */ + +static u32 fb_get_hfreq(u32 vfreq, u32 yres) +{ + u32 divisor, hfreq; + + divisor = (1000000 - (vfreq * FLYBACK))/1000; + hfreq = (yres + V_FRONTPORCH) * vfreq * 1000; + return (hfreq/divisor); +} + +static void fb_timings_vfreq(struct __fb_timings *timings) +{ + timings->hfreq = fb_get_hfreq(timings->vfreq, timings->vactive); + timings->vblank = fb_get_vblank(timings->hfreq); + timings->vtotal = timings->vactive + timings->vblank; + timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq, + timings->hactive); + timings->htotal = timings->hactive + timings->hblank; + timings->dclk = timings->htotal * timings->hfreq; +} + +static void fb_timings_hfreq(struct __fb_timings *timings) +{ + timings->vblank = fb_get_vblank(timings->hfreq); + timings->vtotal = timings->vactive + timings->vblank; + timings->vfreq = timings->hfreq/timings->vtotal; + timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq, + timings->hactive); + timings->htotal = timings->hactive + timings->hblank; + timings->dclk = timings->htotal * timings->hfreq; +} + +static void fb_timings_dclk(struct __fb_timings *timings) +{ + timings->hblank = fb_get_hblank_by_dclk(timings->dclk, + timings->hactive); + timings->htotal = timings->hactive + timings->hblank; + timings->hfreq = timings->dclk/timings->htotal; + timings->vblank = fb_get_vblank(timings->hfreq); + timings->vtotal = timings->vactive + timings->vblank; + timings->vfreq = timings->hfreq/timings->vtotal; +} + +/* + * fb_get_mode - calculates video mode using VESA GTF + * @flags: if: 0 - maximize vertical refresh rate + * 1 - vrefresh-driven calculation; + * 2 - hscan-driven calculation; + * 3 - pixelclock-driven calculation; + * @val: depending on @flags, ignored, vrefresh, hsync or pixelclock + * @var: pointer to fb_var_screeninfo + * @info: pointer to fb_info + * + * DESCRIPTION: + * Calculates video mode based on monitor specs using VESA GTF. + * The GTF is best for VESA GTF compliant monitors but is + * specifically formulated to work for older monitors as well. + * + * If @flag==0, the function will attempt to maximize the + * refresh rate. Otherwise, it will calculate timings based on + * the flag and accompanying value. + * + * If FB_IGNOREMON bit is set in @flags, monitor specs will be + * ignored and @var will be filled with the calculated timings. + * + * All calculations are based on the VESA GTF Spreadsheet + * available at VESA's public ftp (http://www.vesa.org). + * + * NOTES: + * The timings generated by the GTF will be different from VESA + * DMT. It might be a good idea to keep a table of standard + * VESA modes as well. The GTF may also not work for some displays, + * such as, and especially, analog TV. + * + * REQUIRES: + * A valid info->monspecs, otherwise 'safe numbers' will be used. + */ +int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct __fb_timings *timings; + u32 interlace = 1, dscan = 1; + u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax, err = 0; + + + timings = kzalloc(sizeof(struct __fb_timings), GFP_KERNEL); + + if (!timings) + return -ENOMEM; + + /* + * If monspecs are invalid, use values that are enough + * for 640x480@60 + */ + if (!info || !info->monspecs.hfmax || !info->monspecs.vfmax || + !info->monspecs.dclkmax || + info->monspecs.hfmax < info->monspecs.hfmin || + info->monspecs.vfmax < info->monspecs.vfmin || + info->monspecs.dclkmax < info->monspecs.dclkmin) { + hfmin = 29000; hfmax = 30000; + vfmin = 60; vfmax = 60; + dclkmin = 0; dclkmax = 25000000; + } else { + hfmin = info->monspecs.hfmin; + hfmax = info->monspecs.hfmax; + vfmin = info->monspecs.vfmin; + vfmax = info->monspecs.vfmax; + dclkmin = info->monspecs.dclkmin; + dclkmax = info->monspecs.dclkmax; + } + + timings->hactive = var->xres; + timings->vactive = var->yres; + if (var->vmode & FB_VMODE_INTERLACED) { + timings->vactive /= 2; + interlace = 2; + } + if (var->vmode & FB_VMODE_DOUBLE) { + timings->vactive *= 2; + dscan = 2; + } + + switch (flags & ~FB_IGNOREMON) { + case FB_MAXTIMINGS: /* maximize refresh rate */ + timings->hfreq = hfmax; + fb_timings_hfreq(timings); + if (timings->vfreq > vfmax) { + timings->vfreq = vfmax; + fb_timings_vfreq(timings); + } + if (timings->dclk > dclkmax) { + timings->dclk = dclkmax; + fb_timings_dclk(timings); + } + break; + case FB_VSYNCTIMINGS: /* vrefresh driven */ + timings->vfreq = val; + fb_timings_vfreq(timings); + break; + case FB_HSYNCTIMINGS: /* hsync driven */ + timings->hfreq = val; + fb_timings_hfreq(timings); + break; + case FB_DCLKTIMINGS: /* pixelclock driven */ + timings->dclk = PICOS2KHZ(val) * 1000; + fb_timings_dclk(timings); + break; + default: + err = -EINVAL; + + } + + if (err || (!(flags & FB_IGNOREMON) && + (timings->vfreq < vfmin || timings->vfreq > vfmax || + timings->hfreq < hfmin || timings->hfreq > hfmax || + timings->dclk < dclkmin || timings->dclk > dclkmax))) { + err = -EINVAL; + } else { + var->pixclock = KHZ2PICOS(timings->dclk/1000); + var->hsync_len = (timings->htotal * 8)/100; + var->right_margin = (timings->hblank/2) - var->hsync_len; + var->left_margin = timings->hblank - var->right_margin - + var->hsync_len; + var->vsync_len = (3 * interlace)/dscan; + var->lower_margin = (1 * interlace)/dscan; + var->upper_margin = (timings->vblank * interlace)/dscan - + (var->vsync_len + var->lower_margin); + } + + kfree(timings); + return err; +} + +#ifdef CONFIG_VIDEOMODE_HELPERS +int fb_videomode_from_videomode(const struct videomode *vm, + struct fb_videomode *fbmode) +{ + unsigned int htotal, vtotal; + + fbmode->xres = vm->hactive; + fbmode->left_margin = vm->hback_porch; + fbmode->right_margin = vm->hfront_porch; + fbmode->hsync_len = vm->hsync_len; + + fbmode->yres = vm->vactive; + fbmode->upper_margin = vm->vback_porch; + fbmode->lower_margin = vm->vfront_porch; + fbmode->vsync_len = vm->vsync_len; + + /* prevent division by zero in KHZ2PICOS macro */ + fbmode->pixclock = vm->pixelclock ? + KHZ2PICOS(vm->pixelclock / 1000) : 0; + + fbmode->sync = 0; + fbmode->vmode = 0; + if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) + fbmode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) + fbmode->sync |= FB_SYNC_VERT_HIGH_ACT; + if (vm->flags & DISPLAY_FLAGS_INTERLACED) + fbmode->vmode |= FB_VMODE_INTERLACED; + if (vm->flags & DISPLAY_FLAGS_DOUBLESCAN) + fbmode->vmode |= FB_VMODE_DOUBLE; + fbmode->flag = 0; + + htotal = vm->hactive + vm->hfront_porch + vm->hback_porch + + vm->hsync_len; + vtotal = vm->vactive + vm->vfront_porch + vm->vback_porch + + vm->vsync_len; + /* prevent division by zero */ + if (htotal && vtotal) { + fbmode->refresh = vm->pixelclock / (htotal * vtotal); + /* a mode must have htotal and vtotal != 0 or it is invalid */ + } else { + fbmode->refresh = 0; + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fb_videomode_from_videomode); + +#ifdef CONFIG_OF +static inline void dump_fb_videomode(const struct fb_videomode *m) +{ + pr_debug("fb_videomode = %ux%u@%uHz (%ukHz) %u %u %u %u %u %u %u %u %u\n", + m->xres, m->yres, m->refresh, m->pixclock, m->left_margin, + m->right_margin, m->upper_margin, m->lower_margin, + m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); +} + +/** + * of_get_fb_videomode - get a fb_videomode from devicetree + * @np: device_node with the timing specification + * @fb: will be set to the return value + * @index: index into the list of display timings in devicetree + * + * DESCRIPTION: + * This function is expensive and should only be used, if only one mode is to be + * read from DT. To get multiple modes start with of_get_display_timings ond + * work with that instead. + */ +int of_get_fb_videomode(struct device_node *np, struct fb_videomode *fb, + int index) +{ + struct videomode vm; + int ret; + + ret = of_get_videomode(np, &vm, index); + if (ret) + return ret; + + fb_videomode_from_videomode(&vm, fb); + + pr_debug("%s: got %dx%d display mode from %s\n", + of_node_full_name(np), vm.hactive, vm.vactive, np->name); + dump_fb_videomode(fb); + + return 0; +} +EXPORT_SYMBOL_GPL(of_get_fb_videomode); +#endif /* CONFIG_OF */ +#endif /* CONFIG_VIDEOMODE_HELPERS */ + +#else +int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var) +{ + return 1; +} +void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) +{ + specs = NULL; +} +void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) +{ +} +void fb_destroy_modedb(struct fb_videomode *modedb) +{ +} +int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, + struct fb_info *info) +{ + return -EINVAL; +} +#endif /* CONFIG_FB_MODE_HELPERS */ + +/* + * fb_validate_mode - validates var against monitor capabilities + * @var: pointer to fb_var_screeninfo + * @info: pointer to fb_info + * + * DESCRIPTION: + * Validates video mode against monitor capabilities specified in + * info->monspecs. + * + * REQUIRES: + * A valid info->monspecs. + */ +int fb_validate_mode(const struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 hfreq, vfreq, htotal, vtotal, pixclock; + u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax; + + /* + * If monspecs are invalid, use values that are enough + * for 640x480@60 + */ + if (!info->monspecs.hfmax || !info->monspecs.vfmax || + !info->monspecs.dclkmax || + info->monspecs.hfmax < info->monspecs.hfmin || + info->monspecs.vfmax < info->monspecs.vfmin || + info->monspecs.dclkmax < info->monspecs.dclkmin) { + hfmin = 29000; hfmax = 30000; + vfmin = 60; vfmax = 60; + dclkmin = 0; dclkmax = 25000000; + } else { + hfmin = info->monspecs.hfmin; + hfmax = info->monspecs.hfmax; + vfmin = info->monspecs.vfmin; + vfmax = info->monspecs.vfmax; + dclkmin = info->monspecs.dclkmin; + dclkmax = info->monspecs.dclkmax; + } + + if (!var->pixclock) + return -EINVAL; + pixclock = PICOS2KHZ(var->pixclock) * 1000; + + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + + if (var->vmode & FB_VMODE_INTERLACED) + vtotal /= 2; + if (var->vmode & FB_VMODE_DOUBLE) + vtotal *= 2; + + hfreq = pixclock/htotal; + hfreq = (hfreq + 500) / 1000 * 1000; + + vfreq = hfreq/vtotal; + + return (vfreq < vfmin || vfreq > vfmax || + hfreq < hfmin || hfreq > hfmax || + pixclock < dclkmin || pixclock > dclkmax) ? + -EINVAL : 0; +} + +#if defined(CONFIG_FIRMWARE_EDID) && defined(CONFIG_X86) + +/* + * We need to ensure that the EDID block is only returned for + * the primary graphics adapter. + */ + +const unsigned char *fb_firmware_edid(struct device *device) +{ + struct pci_dev *dev = NULL; + struct resource *res = NULL; + unsigned char *edid = NULL; + + if (device) + dev = to_pci_dev(device); + + if (dev) + res = &dev->resource[PCI_ROM_RESOURCE]; + + if (res && res->flags & IORESOURCE_ROM_SHADOW) + edid = edid_info.dummy; + + return edid; +} +#else +const unsigned char *fb_firmware_edid(struct device *device) +{ + return NULL; +} +#endif +EXPORT_SYMBOL(fb_firmware_edid); + +EXPORT_SYMBOL(fb_parse_edid); +EXPORT_SYMBOL(fb_edid_to_monspecs); +EXPORT_SYMBOL(fb_edid_add_monspecs); +EXPORT_SYMBOL(fb_get_mode); +EXPORT_SYMBOL(fb_validate_mode); +EXPORT_SYMBOL(fb_destroy_modedb); diff --git a/drivers/video/fbdev/fbsysfs.c b/drivers/video/fbdev/fbsysfs.c new file mode 100644 index 000000000000..53444ac19fe0 --- /dev/null +++ b/drivers/video/fbdev/fbsysfs.c @@ -0,0 +1,586 @@ +/* + * fbsysfs.c - framebuffer device class and attributes + * + * Copyright (c) 2004 James Simmons <jsimmons@infradead.org> + * + * 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. + */ + +/* + * Note: currently there's only stubs for framebuffer_alloc and + * framebuffer_release here. The reson for that is that until all drivers + * are converted to use it a sysfsification will open OOPSable races. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/module.h> + +#define FB_SYSFS_FLAG_ATTR 1 + +/** + * framebuffer_alloc - creates a new frame buffer info structure + * + * @size: size of driver private data, can be zero + * @dev: pointer to the device for this fb, this can be NULL + * + * Creates a new frame buffer info structure. Also reserves @size bytes + * for driver private data (info->par). info->par (if any) will be + * aligned to sizeof(long). + * + * Returns the new structure, or NULL if an error occurred. + * + */ +struct fb_info *framebuffer_alloc(size_t size, struct device *dev) +{ +#define BYTES_PER_LONG (BITS_PER_LONG/8) +#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG)) + int fb_info_size = sizeof(struct fb_info); + struct fb_info *info; + char *p; + + if (size) + fb_info_size += PADDING; + + p = kzalloc(fb_info_size + size, GFP_KERNEL); + + if (!p) + return NULL; + + info = (struct fb_info *) p; + + if (size) + info->par = p + fb_info_size; + + info->device = dev; + +#ifdef CONFIG_FB_BACKLIGHT + mutex_init(&info->bl_curve_mutex); +#endif + + return info; +#undef PADDING +#undef BYTES_PER_LONG +} +EXPORT_SYMBOL(framebuffer_alloc); + +/** + * framebuffer_release - marks the structure available for freeing + * + * @info: frame buffer info structure + * + * Drop the reference count of the device embedded in the + * framebuffer info structure. + * + */ +void framebuffer_release(struct fb_info *info) +{ + if (!info) + return; + kfree(info->apertures); + kfree(info); +} +EXPORT_SYMBOL(framebuffer_release); + +static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var) +{ + int err; + + var->activate |= FB_ACTIVATE_FORCE; + console_lock(); + fb_info->flags |= FBINFO_MISC_USEREVENT; + err = fb_set_var(fb_info, var); + fb_info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + if (err) + return err; + return 0; +} + +static int mode_string(char *buf, unsigned int offset, + const struct fb_videomode *mode) +{ + char m = 'U'; + char v = 'p'; + + if (mode->flag & FB_MODE_IS_DETAILED) + m = 'D'; + if (mode->flag & FB_MODE_IS_VESA) + m = 'V'; + if (mode->flag & FB_MODE_IS_STANDARD) + m = 'S'; + + if (mode->vmode & FB_VMODE_INTERLACED) + v = 'i'; + if (mode->vmode & FB_VMODE_DOUBLE) + v = 'd'; + + return snprintf(&buf[offset], PAGE_SIZE - offset, "%c:%dx%d%c-%d\n", + m, mode->xres, mode->yres, v, mode->refresh); +} + +static ssize_t store_mode(struct device *device, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + char mstr[100]; + struct fb_var_screeninfo var; + struct fb_modelist *modelist; + struct fb_videomode *mode; + struct list_head *pos; + size_t i; + int err; + + memset(&var, 0, sizeof(var)); + + list_for_each(pos, &fb_info->modelist) { + modelist = list_entry(pos, struct fb_modelist, list); + mode = &modelist->mode; + i = mode_string(mstr, 0, mode); + if (strncmp(mstr, buf, max(count, i)) == 0) { + + var = fb_info->var; + fb_videomode_to_var(&var, mode); + if ((err = activate(fb_info, &var))) + return err; + fb_info->mode = mode; + return count; + } + } + return -EINVAL; +} + +static ssize_t show_mode(struct device *device, struct device_attribute *attr, + char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + + if (!fb_info->mode) + return 0; + + return mode_string(buf, 0, fb_info->mode); +} + +static ssize_t store_modes(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + LIST_HEAD(old_list); + int i = count / sizeof(struct fb_videomode); + + if (i * sizeof(struct fb_videomode) != count) + return -EINVAL; + + console_lock(); + if (!lock_fb_info(fb_info)) { + console_unlock(); + return -ENODEV; + } + + list_splice(&fb_info->modelist, &old_list); + fb_videomode_to_modelist((const struct fb_videomode *)buf, i, + &fb_info->modelist); + if (fb_new_modelist(fb_info)) { + fb_destroy_modelist(&fb_info->modelist); + list_splice(&old_list, &fb_info->modelist); + } else + fb_destroy_modelist(&old_list); + + unlock_fb_info(fb_info); + console_unlock(); + + return 0; +} + +static ssize_t show_modes(struct device *device, struct device_attribute *attr, + char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + unsigned int i; + struct list_head *pos; + struct fb_modelist *modelist; + const struct fb_videomode *mode; + + i = 0; + list_for_each(pos, &fb_info->modelist) { + modelist = list_entry(pos, struct fb_modelist, list); + mode = &modelist->mode; + i += mode_string(buf, i, mode); + } + return i; +} + +static ssize_t store_bpp(struct device *device, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fb_var_screeninfo var; + char ** last = NULL; + int err; + + var = fb_info->var; + var.bits_per_pixel = simple_strtoul(buf, last, 0); + if ((err = activate(fb_info, &var))) + return err; + return count; +} + +static ssize_t show_bpp(struct device *device, struct device_attribute *attr, + char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.bits_per_pixel); +} + +static ssize_t store_rotate(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fb_var_screeninfo var; + char **last = NULL; + int err; + + var = fb_info->var; + var.rotate = simple_strtoul(buf, last, 0); + + if ((err = activate(fb_info, &var))) + return err; + + return count; +} + + +static ssize_t show_rotate(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + + return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.rotate); +} + +static ssize_t store_virtual(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fb_var_screeninfo var; + char *last = NULL; + int err; + + var = fb_info->var; + var.xres_virtual = simple_strtoul(buf, &last, 0); + last++; + if (last - buf >= count) + return -EINVAL; + var.yres_virtual = simple_strtoul(last, &last, 0); + + if ((err = activate(fb_info, &var))) + return err; + return count; +} + +static ssize_t show_virtual(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + return snprintf(buf, PAGE_SIZE, "%d,%d\n", fb_info->var.xres_virtual, + fb_info->var.yres_virtual); +} + +static ssize_t show_stride(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->fix.line_length); +} + +static ssize_t store_blank(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + char *last = NULL; + int err; + + console_lock(); + fb_info->flags |= FBINFO_MISC_USEREVENT; + err = fb_blank(fb_info, simple_strtoul(buf, &last, 0)); + fb_info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + if (err < 0) + return err; + return count; +} + +static ssize_t show_blank(struct device *device, + struct device_attribute *attr, char *buf) +{ +// struct fb_info *fb_info = dev_get_drvdata(device); + return 0; +} + +static ssize_t store_console(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ +// struct fb_info *fb_info = dev_get_drvdata(device); + return 0; +} + +static ssize_t show_console(struct device *device, + struct device_attribute *attr, char *buf) +{ +// struct fb_info *fb_info = dev_get_drvdata(device); + return 0; +} + +static ssize_t store_cursor(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ +// struct fb_info *fb_info = dev_get_drvdata(device); + return 0; +} + +static ssize_t show_cursor(struct device *device, + struct device_attribute *attr, char *buf) +{ +// struct fb_info *fb_info = dev_get_drvdata(device); + return 0; +} + +static ssize_t store_pan(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fb_var_screeninfo var; + char *last = NULL; + int err; + + var = fb_info->var; + var.xoffset = simple_strtoul(buf, &last, 0); + last++; + if (last - buf >= count) + return -EINVAL; + var.yoffset = simple_strtoul(last, &last, 0); + + console_lock(); + err = fb_pan_display(fb_info, &var); + console_unlock(); + + if (err < 0) + return err; + return count; +} + +static ssize_t show_pan(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + return snprintf(buf, PAGE_SIZE, "%d,%d\n", fb_info->var.xoffset, + fb_info->var.yoffset); +} + +static ssize_t show_name(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + + return snprintf(buf, PAGE_SIZE, "%s\n", fb_info->fix.id); +} + +static ssize_t store_fbstate(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + u32 state; + char *last = NULL; + + state = simple_strtoul(buf, &last, 0); + + console_lock(); + if (!lock_fb_info(fb_info)) { + console_unlock(); + return -ENODEV; + } + + fb_set_suspend(fb_info, (int)state); + + unlock_fb_info(fb_info); + console_unlock(); + + return count; +} + +static ssize_t show_fbstate(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->state); +} + +#ifdef CONFIG_FB_BACKLIGHT +static ssize_t store_bl_curve(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + u8 tmp_curve[FB_BACKLIGHT_LEVELS]; + unsigned int i; + + /* Some drivers don't use framebuffer_alloc(), but those also + * don't have backlights. + */ + if (!fb_info || !fb_info->bl_dev) + return -ENODEV; + + if (count != (FB_BACKLIGHT_LEVELS / 8 * 24)) + return -EINVAL; + + for (i = 0; i < (FB_BACKLIGHT_LEVELS / 8); ++i) + if (sscanf(&buf[i * 24], + "%2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx\n", + &tmp_curve[i * 8 + 0], + &tmp_curve[i * 8 + 1], + &tmp_curve[i * 8 + 2], + &tmp_curve[i * 8 + 3], + &tmp_curve[i * 8 + 4], + &tmp_curve[i * 8 + 5], + &tmp_curve[i * 8 + 6], + &tmp_curve[i * 8 + 7]) != 8) + return -EINVAL; + + /* If there has been an error in the input data, we won't + * reach this loop. + */ + mutex_lock(&fb_info->bl_curve_mutex); + for (i = 0; i < FB_BACKLIGHT_LEVELS; ++i) + fb_info->bl_curve[i] = tmp_curve[i]; + mutex_unlock(&fb_info->bl_curve_mutex); + + return count; +} + +static ssize_t show_bl_curve(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + ssize_t len = 0; + unsigned int i; + + /* Some drivers don't use framebuffer_alloc(), but those also + * don't have backlights. + */ + if (!fb_info || !fb_info->bl_dev) + return -ENODEV; + + mutex_lock(&fb_info->bl_curve_mutex); + for (i = 0; i < FB_BACKLIGHT_LEVELS; i += 8) + len += snprintf(&buf[len], PAGE_SIZE, + "%02x %02x %02x %02x %02x %02x %02x %02x\n", + fb_info->bl_curve[i + 0], + fb_info->bl_curve[i + 1], + fb_info->bl_curve[i + 2], + fb_info->bl_curve[i + 3], + fb_info->bl_curve[i + 4], + fb_info->bl_curve[i + 5], + fb_info->bl_curve[i + 6], + fb_info->bl_curve[i + 7]); + mutex_unlock(&fb_info->bl_curve_mutex); + + return len; +} +#endif + +/* When cmap is added back in it should be a binary attribute + * not a text one. Consideration should also be given to converting + * fbdev to use configfs instead of sysfs */ +static struct device_attribute device_attrs[] = { + __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp), + __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank), + __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console), + __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor), + __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode), + __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes), + __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan), + __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual), + __ATTR(name, S_IRUGO, show_name, NULL), + __ATTR(stride, S_IRUGO, show_stride, NULL), + __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate), + __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate), +#ifdef CONFIG_FB_BACKLIGHT + __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve), +#endif +}; + +int fb_init_device(struct fb_info *fb_info) +{ + int i, error = 0; + + dev_set_drvdata(fb_info->dev, fb_info); + + fb_info->class_flag |= FB_SYSFS_FLAG_ATTR; + + for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { + error = device_create_file(fb_info->dev, &device_attrs[i]); + + if (error) + break; + } + + if (error) { + while (--i >= 0) + device_remove_file(fb_info->dev, &device_attrs[i]); + fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR; + } + + return 0; +} + +void fb_cleanup_device(struct fb_info *fb_info) +{ + unsigned int i; + + if (fb_info->class_flag & FB_SYSFS_FLAG_ATTR) { + for (i = 0; i < ARRAY_SIZE(device_attrs); i++) + device_remove_file(fb_info->dev, &device_attrs[i]); + + fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR; + } +} + +#ifdef CONFIG_FB_BACKLIGHT +/* This function generates a linear backlight curve + * + * 0: off + * 1-7: min + * 8-127: linear from min to max + */ +void fb_bl_default_curve(struct fb_info *fb_info, u8 off, u8 min, u8 max) +{ + unsigned int i, flat, count, range = (max - min); + + mutex_lock(&fb_info->bl_curve_mutex); + + fb_info->bl_curve[0] = off; + + for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat) + fb_info->bl_curve[flat] = min; + + count = FB_BACKLIGHT_LEVELS * 15 / 16; + for (i = 0; i < count; ++i) + fb_info->bl_curve[flat + i] = min + (range * (i + 1) / count); + + mutex_unlock(&fb_info->bl_curve_mutex); +} +EXPORT_SYMBOL_GPL(fb_bl_default_curve); +#endif diff --git a/drivers/video/fbdev/ffb.c b/drivers/video/fbdev/ffb.c new file mode 100644 index 000000000000..4c4ffa61ae26 --- /dev/null +++ b/drivers/video/fbdev/ffb.c @@ -0,0 +1,1081 @@ +/* ffb.c: Creator/Elite3D frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 1997,1998,1999 Jakub Jelinek (jj@ultra.linux.cz) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/timer.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/upa.h> +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int ffb_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); +static int ffb_blank(int, struct fb_info *); + +static void ffb_imageblit(struct fb_info *, const struct fb_image *); +static void ffb_fillrect(struct fb_info *, const struct fb_fillrect *); +static void ffb_copyarea(struct fb_info *, const struct fb_copyarea *); +static int ffb_sync(struct fb_info *); +static int ffb_mmap(struct fb_info *, struct vm_area_struct *); +static int ffb_ioctl(struct fb_info *, unsigned int, unsigned long); +static int ffb_pan_display(struct fb_var_screeninfo *, struct fb_info *); + +/* + * Frame buffer operations + */ + +static struct fb_ops ffb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = ffb_setcolreg, + .fb_blank = ffb_blank, + .fb_pan_display = ffb_pan_display, + .fb_fillrect = ffb_fillrect, + .fb_copyarea = ffb_copyarea, + .fb_imageblit = ffb_imageblit, + .fb_sync = ffb_sync, + .fb_mmap = ffb_mmap, + .fb_ioctl = ffb_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + +/* Register layout and definitions */ +#define FFB_SFB8R_VOFF 0x00000000 +#define FFB_SFB8G_VOFF 0x00400000 +#define FFB_SFB8B_VOFF 0x00800000 +#define FFB_SFB8X_VOFF 0x00c00000 +#define FFB_SFB32_VOFF 0x01000000 +#define FFB_SFB64_VOFF 0x02000000 +#define FFB_FBC_REGS_VOFF 0x04000000 +#define FFB_BM_FBC_REGS_VOFF 0x04002000 +#define FFB_DFB8R_VOFF 0x04004000 +#define FFB_DFB8G_VOFF 0x04404000 +#define FFB_DFB8B_VOFF 0x04804000 +#define FFB_DFB8X_VOFF 0x04c04000 +#define FFB_DFB24_VOFF 0x05004000 +#define FFB_DFB32_VOFF 0x06004000 +#define FFB_DFB422A_VOFF 0x07004000 /* DFB 422 mode write to A */ +#define FFB_DFB422AD_VOFF 0x07804000 /* DFB 422 mode with line doubling */ +#define FFB_DFB24B_VOFF 0x08004000 /* DFB 24bit mode write to B */ +#define FFB_DFB422B_VOFF 0x09004000 /* DFB 422 mode write to B */ +#define FFB_DFB422BD_VOFF 0x09804000 /* DFB 422 mode with line doubling */ +#define FFB_SFB16Z_VOFF 0x0a004000 /* 16bit mode Z planes */ +#define FFB_SFB8Z_VOFF 0x0a404000 /* 8bit mode Z planes */ +#define FFB_SFB422_VOFF 0x0ac04000 /* SFB 422 mode write to A/B */ +#define FFB_SFB422D_VOFF 0x0b404000 /* SFB 422 mode with line doubling */ +#define FFB_FBC_KREGS_VOFF 0x0bc04000 +#define FFB_DAC_VOFF 0x0bc06000 +#define FFB_PROM_VOFF 0x0bc08000 +#define FFB_EXP_VOFF 0x0bc18000 + +#define FFB_SFB8R_POFF 0x04000000UL +#define FFB_SFB8G_POFF 0x04400000UL +#define FFB_SFB8B_POFF 0x04800000UL +#define FFB_SFB8X_POFF 0x04c00000UL +#define FFB_SFB32_POFF 0x05000000UL +#define FFB_SFB64_POFF 0x06000000UL +#define FFB_FBC_REGS_POFF 0x00600000UL +#define FFB_BM_FBC_REGS_POFF 0x00600000UL +#define FFB_DFB8R_POFF 0x01000000UL +#define FFB_DFB8G_POFF 0x01400000UL +#define FFB_DFB8B_POFF 0x01800000UL +#define FFB_DFB8X_POFF 0x01c00000UL +#define FFB_DFB24_POFF 0x02000000UL +#define FFB_DFB32_POFF 0x03000000UL +#define FFB_FBC_KREGS_POFF 0x00610000UL +#define FFB_DAC_POFF 0x00400000UL +#define FFB_PROM_POFF 0x00000000UL +#define FFB_EXP_POFF 0x00200000UL +#define FFB_DFB422A_POFF 0x09000000UL +#define FFB_DFB422AD_POFF 0x09800000UL +#define FFB_DFB24B_POFF 0x0a000000UL +#define FFB_DFB422B_POFF 0x0b000000UL +#define FFB_DFB422BD_POFF 0x0b800000UL +#define FFB_SFB16Z_POFF 0x0c800000UL +#define FFB_SFB8Z_POFF 0x0c000000UL +#define FFB_SFB422_POFF 0x0d000000UL +#define FFB_SFB422D_POFF 0x0d800000UL + +/* Draw operations */ +#define FFB_DRAWOP_DOT 0x00 +#define FFB_DRAWOP_AADOT 0x01 +#define FFB_DRAWOP_BRLINECAP 0x02 +#define FFB_DRAWOP_BRLINEOPEN 0x03 +#define FFB_DRAWOP_DDLINE 0x04 +#define FFB_DRAWOP_AALINE 0x05 +#define FFB_DRAWOP_TRIANGLE 0x06 +#define FFB_DRAWOP_POLYGON 0x07 +#define FFB_DRAWOP_RECTANGLE 0x08 +#define FFB_DRAWOP_FASTFILL 0x09 +#define FFB_DRAWOP_BCOPY 0x0a +#define FFB_DRAWOP_VSCROLL 0x0b + +/* Pixel processor control */ +/* Force WID */ +#define FFB_PPC_FW_DISABLE 0x800000 +#define FFB_PPC_FW_ENABLE 0xc00000 +/* Auxiliary clip */ +#define FFB_PPC_ACE_DISABLE 0x040000 +#define FFB_PPC_ACE_AUX_SUB 0x080000 +#define FFB_PPC_ACE_AUX_ADD 0x0c0000 +/* Depth cue */ +#define FFB_PPC_DCE_DISABLE 0x020000 +#define FFB_PPC_DCE_ENABLE 0x030000 +/* Alpha blend */ +#define FFB_PPC_ABE_DISABLE 0x008000 +#define FFB_PPC_ABE_ENABLE 0x00c000 +/* View clip */ +#define FFB_PPC_VCE_DISABLE 0x001000 +#define FFB_PPC_VCE_2D 0x002000 +#define FFB_PPC_VCE_3D 0x003000 +/* Area pattern */ +#define FFB_PPC_APE_DISABLE 0x000800 +#define FFB_PPC_APE_ENABLE 0x000c00 +/* Transparent background */ +#define FFB_PPC_TBE_OPAQUE 0x000200 +#define FFB_PPC_TBE_TRANSPARENT 0x000300 +/* Z source */ +#define FFB_PPC_ZS_VAR 0x000080 +#define FFB_PPC_ZS_CONST 0x0000c0 +/* Y source */ +#define FFB_PPC_YS_VAR 0x000020 +#define FFB_PPC_YS_CONST 0x000030 +/* X source */ +#define FFB_PPC_XS_WID 0x000004 +#define FFB_PPC_XS_VAR 0x000008 +#define FFB_PPC_XS_CONST 0x00000c +/* Color (BGR) source */ +#define FFB_PPC_CS_VAR 0x000002 +#define FFB_PPC_CS_CONST 0x000003 + +#define FFB_ROP_NEW 0x83 +#define FFB_ROP_OLD 0x85 +#define FFB_ROP_NEW_XOR_OLD 0x86 + +#define FFB_UCSR_FIFO_MASK 0x00000fff +#define FFB_UCSR_FB_BUSY 0x01000000 +#define FFB_UCSR_RP_BUSY 0x02000000 +#define FFB_UCSR_ALL_BUSY (FFB_UCSR_RP_BUSY|FFB_UCSR_FB_BUSY) +#define FFB_UCSR_READ_ERR 0x40000000 +#define FFB_UCSR_FIFO_OVFL 0x80000000 +#define FFB_UCSR_ALL_ERRORS (FFB_UCSR_READ_ERR|FFB_UCSR_FIFO_OVFL) + +struct ffb_fbc { + /* Next vertex registers */ + u32 xxx1[3]; + u32 alpha; + u32 red; + u32 green; + u32 blue; + u32 depth; + u32 y; + u32 x; + u32 xxx2[2]; + u32 ryf; + u32 rxf; + u32 xxx3[2]; + + u32 dmyf; + u32 dmxf; + u32 xxx4[2]; + u32 ebyi; + u32 ebxi; + u32 xxx5[2]; + u32 by; + u32 bx; + u32 dy; + u32 dx; + u32 bh; + u32 bw; + u32 xxx6[2]; + + u32 xxx7[32]; + + /* Setup unit vertex state register */ + u32 suvtx; + u32 xxx8[63]; + + /* Control registers */ + u32 ppc; + u32 wid; + u32 fg; + u32 bg; + u32 consty; + u32 constz; + u32 xclip; + u32 dcss; + u32 vclipmin; + u32 vclipmax; + u32 vclipzmin; + u32 vclipzmax; + u32 dcsf; + u32 dcsb; + u32 dczf; + u32 dczb; + + u32 xxx9; + u32 blendc; + u32 blendc1; + u32 blendc2; + u32 fbramitc; + u32 fbc; + u32 rop; + u32 cmp; + u32 matchab; + u32 matchc; + u32 magnab; + u32 magnc; + u32 fbcfg0; + u32 fbcfg1; + u32 fbcfg2; + u32 fbcfg3; + + u32 ppcfg; + u32 pick; + u32 fillmode; + u32 fbramwac; + u32 pmask; + u32 xpmask; + u32 ypmask; + u32 zpmask; + u32 clip0min; + u32 clip0max; + u32 clip1min; + u32 clip1max; + u32 clip2min; + u32 clip2max; + u32 clip3min; + u32 clip3max; + + /* New 3dRAM III support regs */ + u32 rawblend2; + u32 rawpreblend; + u32 rawstencil; + u32 rawstencilctl; + u32 threedram1; + u32 threedram2; + u32 passin; + u32 rawclrdepth; + u32 rawpmask; + u32 rawcsrc; + u32 rawmatch; + u32 rawmagn; + u32 rawropblend; + u32 rawcmp; + u32 rawwac; + u32 fbramid; + + u32 drawop; + u32 xxx10[2]; + u32 fontlpat; + u32 xxx11; + u32 fontxy; + u32 fontw; + u32 fontinc; + u32 font; + u32 xxx12[3]; + u32 blend2; + u32 preblend; + u32 stencil; + u32 stencilctl; + + u32 xxx13[4]; + u32 dcss1; + u32 dcss2; + u32 dcss3; + u32 widpmask; + u32 dcs2; + u32 dcs3; + u32 dcs4; + u32 xxx14; + u32 dcd2; + u32 dcd3; + u32 dcd4; + u32 xxx15; + + u32 pattern[32]; + + u32 xxx16[256]; + + u32 devid; + u32 xxx17[63]; + + u32 ucsr; + u32 xxx18[31]; + + u32 mer; +}; + +struct ffb_dac { + u32 type; + u32 value; + u32 type2; + u32 value2; +}; + +#define FFB_DAC_UCTRL 0x1001 /* User Control */ +#define FFB_DAC_UCTRL_MANREV 0x00000f00 /* 4-bit Manufacturing Revision */ +#define FFB_DAC_UCTRL_MANREV_SHIFT 8 +#define FFB_DAC_TGEN 0x6000 /* Timing Generator */ +#define FFB_DAC_TGEN_VIDE 0x00000001 /* Video Enable */ +#define FFB_DAC_DID 0x8000 /* Device Identification */ +#define FFB_DAC_DID_PNUM 0x0ffff000 /* Device Part Number */ +#define FFB_DAC_DID_PNUM_SHIFT 12 +#define FFB_DAC_DID_REV 0xf0000000 /* Device Revision */ +#define FFB_DAC_DID_REV_SHIFT 28 + +#define FFB_DAC_CUR_CTRL 0x100 +#define FFB_DAC_CUR_CTRL_P0 0x00000001 +#define FFB_DAC_CUR_CTRL_P1 0x00000002 + +struct ffb_par { + spinlock_t lock; + struct ffb_fbc __iomem *fbc; + struct ffb_dac __iomem *dac; + + u32 flags; +#define FFB_FLAG_AFB 0x00000001 /* AFB m3 or m6 */ +#define FFB_FLAG_BLANKED 0x00000002 /* screen is blanked */ +#define FFB_FLAG_INVCURSOR 0x00000004 /* DAC has inverted cursor logic */ + + u32 fg_cache __attribute__((aligned (8))); + u32 bg_cache; + u32 rop_cache; + + int fifo_cache; + + unsigned long physbase; + unsigned long fbsize; + + int board_type; + + u32 pseudo_palette[16]; +}; + +static void FFBFifo(struct ffb_par *par, int n) +{ + struct ffb_fbc __iomem *fbc; + int cache = par->fifo_cache; + + if (cache - n < 0) { + fbc = par->fbc; + do { + cache = (upa_readl(&fbc->ucsr) & FFB_UCSR_FIFO_MASK); + cache -= 8; + } while (cache - n < 0); + } + par->fifo_cache = cache - n; +} + +static void FFBWait(struct ffb_par *par) +{ + struct ffb_fbc __iomem *fbc; + int limit = 10000; + + fbc = par->fbc; + do { + if ((upa_readl(&fbc->ucsr) & FFB_UCSR_ALL_BUSY) == 0) + break; + if ((upa_readl(&fbc->ucsr) & FFB_UCSR_ALL_ERRORS) != 0) { + upa_writel(FFB_UCSR_ALL_ERRORS, &fbc->ucsr); + } + udelay(10); + } while (--limit > 0); +} + +static int ffb_sync(struct fb_info *p) +{ + struct ffb_par *par = (struct ffb_par *)p->par; + + FFBWait(par); + return 0; +} + +static __inline__ void ffb_rop(struct ffb_par *par, u32 rop) +{ + if (par->rop_cache != rop) { + FFBFifo(par, 1); + upa_writel(rop, &par->fbc->rop); + par->rop_cache = rop; + } +} + +static void ffb_switch_from_graph(struct ffb_par *par) +{ + struct ffb_fbc __iomem *fbc = par->fbc; + struct ffb_dac __iomem *dac = par->dac; + unsigned long flags; + + spin_lock_irqsave(&par->lock, flags); + FFBWait(par); + par->fifo_cache = 0; + FFBFifo(par, 7); + upa_writel(FFB_PPC_VCE_DISABLE | FFB_PPC_TBE_OPAQUE | + FFB_PPC_APE_DISABLE | FFB_PPC_CS_CONST, + &fbc->ppc); + upa_writel(0x2000707f, &fbc->fbc); + upa_writel(par->rop_cache, &fbc->rop); + upa_writel(0xffffffff, &fbc->pmask); + upa_writel((1 << 16) | (0 << 0), &fbc->fontinc); + upa_writel(par->fg_cache, &fbc->fg); + upa_writel(par->bg_cache, &fbc->bg); + FFBWait(par); + + /* Disable cursor. */ + upa_writel(FFB_DAC_CUR_CTRL, &dac->type2); + if (par->flags & FFB_FLAG_INVCURSOR) + upa_writel(0, &dac->value2); + else + upa_writel((FFB_DAC_CUR_CTRL_P0 | + FFB_DAC_CUR_CTRL_P1), &dac->value2); + + spin_unlock_irqrestore(&par->lock, flags); +} + +static int ffb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + + /* We just use this to catch switches out of + * graphics mode. + */ + ffb_switch_from_graph(par); + + if (var->xoffset || var->yoffset || var->vmode) + return -EINVAL; + return 0; +} + +/** + * ffb_fillrect - Draws a rectangle on the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @rect: structure defining the rectagle and operation. + */ +static void ffb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + struct ffb_fbc __iomem *fbc = par->fbc; + unsigned long flags; + u32 fg; + + BUG_ON(rect->rop != ROP_COPY && rect->rop != ROP_XOR); + + fg = ((u32 *)info->pseudo_palette)[rect->color]; + + spin_lock_irqsave(&par->lock, flags); + + if (fg != par->fg_cache) { + FFBFifo(par, 1); + upa_writel(fg, &fbc->fg); + par->fg_cache = fg; + } + + ffb_rop(par, rect->rop == ROP_COPY ? + FFB_ROP_NEW : + FFB_ROP_NEW_XOR_OLD); + + FFBFifo(par, 5); + upa_writel(FFB_DRAWOP_RECTANGLE, &fbc->drawop); + upa_writel(rect->dy, &fbc->by); + upa_writel(rect->dx, &fbc->bx); + upa_writel(rect->height, &fbc->bh); + upa_writel(rect->width, &fbc->bw); + + spin_unlock_irqrestore(&par->lock, flags); +} + +/** + * ffb_copyarea - Copies on area of the screen to another area. + * + * @info: frame buffer structure that represents a single frame buffer + * @area: structure defining the source and destination. + */ + +static void ffb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + struct ffb_fbc __iomem *fbc = par->fbc; + unsigned long flags; + + if (area->dx != area->sx || + area->dy == area->sy) { + cfb_copyarea(info, area); + return; + } + + spin_lock_irqsave(&par->lock, flags); + + ffb_rop(par, FFB_ROP_OLD); + + FFBFifo(par, 7); + upa_writel(FFB_DRAWOP_VSCROLL, &fbc->drawop); + upa_writel(area->sy, &fbc->by); + upa_writel(area->sx, &fbc->bx); + upa_writel(area->dy, &fbc->dy); + upa_writel(area->dx, &fbc->dx); + upa_writel(area->height, &fbc->bh); + upa_writel(area->width, &fbc->bw); + + spin_unlock_irqrestore(&par->lock, flags); +} + +/** + * ffb_imageblit - Copies a image from system memory to the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @image: structure defining the image. + */ +static void ffb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + struct ffb_fbc __iomem *fbc = par->fbc; + const u8 *data = image->data; + unsigned long flags; + u32 fg, bg, xy; + u64 fgbg; + int i, width, stride; + + if (image->depth > 1) { + cfb_imageblit(info, image); + return; + } + + fg = ((u32 *)info->pseudo_palette)[image->fg_color]; + bg = ((u32 *)info->pseudo_palette)[image->bg_color]; + fgbg = ((u64) fg << 32) | (u64) bg; + xy = (image->dy << 16) | image->dx; + width = image->width; + stride = ((width + 7) >> 3); + + spin_lock_irqsave(&par->lock, flags); + + if (fgbg != *(u64 *)&par->fg_cache) { + FFBFifo(par, 2); + upa_writeq(fgbg, &fbc->fg); + *(u64 *)&par->fg_cache = fgbg; + } + + if (width >= 32) { + FFBFifo(par, 1); + upa_writel(32, &fbc->fontw); + } + + while (width >= 32) { + const u8 *next_data = data + 4; + + FFBFifo(par, 1); + upa_writel(xy, &fbc->fontxy); + xy += (32 << 0); + + for (i = 0; i < image->height; i++) { + u32 val = (((u32)data[0] << 24) | + ((u32)data[1] << 16) | + ((u32)data[2] << 8) | + ((u32)data[3] << 0)); + FFBFifo(par, 1); + upa_writel(val, &fbc->font); + + data += stride; + } + + data = next_data; + width -= 32; + } + + if (width) { + FFBFifo(par, 2); + upa_writel(width, &fbc->fontw); + upa_writel(xy, &fbc->fontxy); + + for (i = 0; i < image->height; i++) { + u32 val = (((u32)data[0] << 24) | + ((u32)data[1] << 16) | + ((u32)data[2] << 8) | + ((u32)data[3] << 0)); + FFBFifo(par, 1); + upa_writel(val, &fbc->font); + + data += stride; + } + } + + spin_unlock_irqrestore(&par->lock, flags); +} + +static void ffb_fixup_var_rgb(struct fb_var_screeninfo *var) +{ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; +} + +/** + * ffb_setcolreg - Sets a color register. + * + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int ffb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + u32 value; + + if (regno >= 16) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + value = (blue << 16) | (green << 8) | red; + ((u32 *)info->pseudo_palette)[regno] = value; + + return 0; +} + +/** + * ffb_blank - Optional function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int ffb_blank(int blank, struct fb_info *info) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + struct ffb_dac __iomem *dac = par->dac; + unsigned long flags; + u32 val; + int i; + + spin_lock_irqsave(&par->lock, flags); + + FFBWait(par); + + upa_writel(FFB_DAC_TGEN, &dac->type); + val = upa_readl(&dac->value); + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + val |= FFB_DAC_TGEN_VIDE; + par->flags &= ~FFB_FLAG_BLANKED; + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + case FB_BLANK_POWERDOWN: /* Poweroff */ + val &= ~FFB_DAC_TGEN_VIDE; + par->flags |= FFB_FLAG_BLANKED; + break; + } + upa_writel(FFB_DAC_TGEN, &dac->type); + upa_writel(val, &dac->value); + for (i = 0; i < 10; i++) { + upa_writel(FFB_DAC_TGEN, &dac->type); + upa_readl(&dac->value); + } + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static struct sbus_mmap_map ffb_mmap_map[] = { + { + .voff = FFB_SFB8R_VOFF, + .poff = FFB_SFB8R_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_SFB8G_VOFF, + .poff = FFB_SFB8G_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_SFB8B_VOFF, + .poff = FFB_SFB8B_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_SFB8X_VOFF, + .poff = FFB_SFB8X_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_SFB32_VOFF, + .poff = FFB_SFB32_POFF, + .size = 0x1000000 + }, + { + .voff = FFB_SFB64_VOFF, + .poff = FFB_SFB64_POFF, + .size = 0x2000000 + }, + { + .voff = FFB_FBC_REGS_VOFF, + .poff = FFB_FBC_REGS_POFF, + .size = 0x0002000 + }, + { + .voff = FFB_BM_FBC_REGS_VOFF, + .poff = FFB_BM_FBC_REGS_POFF, + .size = 0x0002000 + }, + { + .voff = FFB_DFB8R_VOFF, + .poff = FFB_DFB8R_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_DFB8G_VOFF, + .poff = FFB_DFB8G_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_DFB8B_VOFF, + .poff = FFB_DFB8B_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_DFB8X_VOFF, + .poff = FFB_DFB8X_POFF, + .size = 0x0400000 + }, + { + .voff = FFB_DFB24_VOFF, + .poff = FFB_DFB24_POFF, + .size = 0x1000000 + }, + { + .voff = FFB_DFB32_VOFF, + .poff = FFB_DFB32_POFF, + .size = 0x1000000 + }, + { + .voff = FFB_FBC_KREGS_VOFF, + .poff = FFB_FBC_KREGS_POFF, + .size = 0x0002000 + }, + { + .voff = FFB_DAC_VOFF, + .poff = FFB_DAC_POFF, + .size = 0x0002000 + }, + { + .voff = FFB_PROM_VOFF, + .poff = FFB_PROM_POFF, + .size = 0x0010000 + }, + { + .voff = FFB_EXP_VOFF, + .poff = FFB_EXP_POFF, + .size = 0x0002000 + }, + { + .voff = FFB_DFB422A_VOFF, + .poff = FFB_DFB422A_POFF, + .size = 0x0800000 + }, + { + .voff = FFB_DFB422AD_VOFF, + .poff = FFB_DFB422AD_POFF, + .size = 0x0800000 + }, + { + .voff = FFB_DFB24B_VOFF, + .poff = FFB_DFB24B_POFF, + .size = 0x1000000 + }, + { + .voff = FFB_DFB422B_VOFF, + .poff = FFB_DFB422B_POFF, + .size = 0x0800000 + }, + { + .voff = FFB_DFB422BD_VOFF, + .poff = FFB_DFB422BD_POFF, + .size = 0x0800000 + }, + { + .voff = FFB_SFB16Z_VOFF, + .poff = FFB_SFB16Z_POFF, + .size = 0x0800000 + }, + { + .voff = FFB_SFB8Z_VOFF, + .poff = FFB_SFB8Z_POFF, + .size = 0x0800000 + }, + { + .voff = FFB_SFB422_VOFF, + .poff = FFB_SFB422_POFF, + .size = 0x0800000 + }, + { + .voff = FFB_SFB422D_VOFF, + .poff = FFB_SFB422D_POFF, + .size = 0x0800000 + }, + { .size = 0 } +}; + +static int ffb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + + return sbusfb_mmap_helper(ffb_mmap_map, + par->physbase, par->fbsize, + 0, vma); +} + +static int ffb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + + return sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_CREATOR, 24, par->fbsize); +} + +/* + * Initialisation + */ + +static void ffb_init_fix(struct fb_info *info) +{ + struct ffb_par *par = (struct ffb_par *)info->par; + const char *ffb_type_name; + + if (!(par->flags & FFB_FLAG_AFB)) { + if ((par->board_type & 0x7) == 0x3) + ffb_type_name = "Creator 3D"; + else + ffb_type_name = "Creator"; + } else + ffb_type_name = "Elite 3D"; + + strlcpy(info->fix.id, ffb_type_name, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + + /* Framebuffer length is the same regardless of resolution. */ + info->fix.line_length = 8192; + + info->fix.accel = FB_ACCEL_SUN_CREATOR; +} + +static int ffb_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct ffb_fbc __iomem *fbc; + struct ffb_dac __iomem *dac; + struct fb_info *info; + struct ffb_par *par; + u32 dac_pnum, dac_rev, dac_mrev; + int err; + + info = framebuffer_alloc(sizeof(struct ffb_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + + par = info->par; + + spin_lock_init(&par->lock); + par->fbc = of_ioremap(&op->resource[2], 0, + sizeof(struct ffb_fbc), "ffb fbc"); + if (!par->fbc) + goto out_release_fb; + + par->dac = of_ioremap(&op->resource[1], 0, + sizeof(struct ffb_dac), "ffb dac"); + if (!par->dac) + goto out_unmap_fbc; + + par->rop_cache = FFB_ROP_NEW; + par->physbase = op->resource[0].start; + + /* Don't mention copyarea, so SCROLL_REDRAW is always + * used. It is the fastest on this chip. + */ + info->flags = (FBINFO_DEFAULT | + /* FBINFO_HWACCEL_COPYAREA | */ + FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_IMAGEBLIT); + + info->fbops = &ffb_ops; + + info->screen_base = (char *) par->physbase + FFB_DFB24_POFF; + info->pseudo_palette = par->pseudo_palette; + + sbusfb_fill_var(&info->var, dp, 32); + par->fbsize = PAGE_ALIGN(info->var.xres * info->var.yres * 4); + ffb_fixup_var_rgb(&info->var); + + info->var.accel_flags = FB_ACCELF_TEXT; + + if (!strcmp(dp->name, "SUNW,afb")) + par->flags |= FFB_FLAG_AFB; + + par->board_type = of_getintprop_default(dp, "board_type", 0); + + fbc = par->fbc; + if ((upa_readl(&fbc->ucsr) & FFB_UCSR_ALL_ERRORS) != 0) + upa_writel(FFB_UCSR_ALL_ERRORS, &fbc->ucsr); + + dac = par->dac; + upa_writel(FFB_DAC_DID, &dac->type); + dac_pnum = upa_readl(&dac->value); + dac_rev = (dac_pnum & FFB_DAC_DID_REV) >> FFB_DAC_DID_REV_SHIFT; + dac_pnum = (dac_pnum & FFB_DAC_DID_PNUM) >> FFB_DAC_DID_PNUM_SHIFT; + + upa_writel(FFB_DAC_UCTRL, &dac->type); + dac_mrev = upa_readl(&dac->value); + dac_mrev = (dac_mrev & FFB_DAC_UCTRL_MANREV) >> + FFB_DAC_UCTRL_MANREV_SHIFT; + + /* Elite3D has different DAC revision numbering, and no DAC revisions + * have the reversed meaning of cursor enable. Otherwise, Pacifica 1 + * ramdacs with manufacturing revision less than 3 have inverted + * cursor logic. We identify Pacifica 1 as not Pacifica 2, the + * latter having a part number value of 0x236e. + */ + if ((par->flags & FFB_FLAG_AFB) || dac_pnum == 0x236e) { + par->flags &= ~FFB_FLAG_INVCURSOR; + } else { + if (dac_mrev < 3) + par->flags |= FFB_FLAG_INVCURSOR; + } + + ffb_switch_from_graph(par); + + /* Unblank it just to be sure. When there are multiple + * FFB/AFB cards in the system, or it is not the OBP + * chosen console, it will have video outputs off in + * the DAC. + */ + ffb_blank(FB_BLANK_UNBLANK, info); + + if (fb_alloc_cmap(&info->cmap, 256, 0)) + goto out_unmap_dac; + + ffb_init_fix(info); + + err = register_framebuffer(info); + if (err < 0) + goto out_dealloc_cmap; + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: %s at %016lx, type %d, " + "DAC pnum[%x] rev[%d] manuf_rev[%d]\n", + dp->full_name, + ((par->flags & FFB_FLAG_AFB) ? "AFB" : "FFB"), + par->physbase, par->board_type, + dac_pnum, dac_rev, dac_mrev); + + return 0; + +out_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + +out_unmap_dac: + of_iounmap(&op->resource[1], par->dac, sizeof(struct ffb_dac)); + +out_unmap_fbc: + of_iounmap(&op->resource[2], par->fbc, sizeof(struct ffb_fbc)); + +out_release_fb: + framebuffer_release(info); + +out_err: + return err; +} + +static int ffb_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct ffb_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + of_iounmap(&op->resource[2], par->fbc, sizeof(struct ffb_fbc)); + of_iounmap(&op->resource[1], par->dac, sizeof(struct ffb_dac)); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id ffb_match[] = { + { + .name = "SUNW,ffb", + }, + { + .name = "SUNW,afb", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ffb_match); + +static struct platform_driver ffb_driver = { + .driver = { + .name = "ffb", + .owner = THIS_MODULE, + .of_match_table = ffb_match, + }, + .probe = ffb_probe, + .remove = ffb_remove, +}; + +static int __init ffb_init(void) +{ + if (fb_get_options("ffb", NULL)) + return -ENODEV; + + return platform_driver_register(&ffb_driver); +} + +static void __exit ffb_exit(void) +{ + platform_driver_unregister(&ffb_driver); +} + +module_init(ffb_init); +module_exit(ffb_exit); + +MODULE_DESCRIPTION("framebuffer driver for Creator/Elite3D chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/fm2fb.c b/drivers/video/fbdev/fm2fb.c new file mode 100644 index 000000000000..e69d47af9932 --- /dev/null +++ b/drivers/video/fbdev/fm2fb.c @@ -0,0 +1,323 @@ +/* + * linux/drivers/video/fm2fb.c -- BSC FrameMaster II/Rainbow II frame buffer + * device + * + * Copyright (C) 1998 Steffen A. Mork (linux-dev@morknet.de) + * Copyright (C) 1999 Geert Uytterhoeven + * + * Written for 2.0.x by Steffen A. Mork + * Ported to 2.1.x by Geert Uytterhoeven + * Ported to new api by James Simmons + * + * 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/mm.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/zorro.h> +#include <asm/io.h> + +/* + * Some technical notes: + * + * The BSC FrameMaster II (or Rainbow II) is a simple very dumb + * frame buffer which allows to display 24 bit true color images. + * Each pixel is 32 bit width so it's very easy to maintain the + * frame buffer. One long word has the following layout: + * AARRGGBB which means: AA the alpha channel byte, RR the red + * channel, GG the green channel and BB the blue channel. + * + * The FrameMaster II supports the following video modes. + * - PAL/NTSC + * - interlaced/non interlaced + * - composite sync/sync/sync over green + * + * The resolution is to the following both ones: + * - 768x576 (PAL) + * - 768x480 (NTSC) + * + * This means that pixel access per line is fixed due to the + * fixed line width. In case of maximal resolution the frame + * buffer needs an amount of memory of 1.769.472 bytes which + * is near to 2 MByte (the allocated address space of Zorro2). + * The memory is channel interleaved. That means every channel + * owns four VRAMs. Unfortunately most FrameMasters II are + * not assembled with memory for the alpha channel. In this + * case it could be possible to add the frame buffer into the + * normal memory pool. + * + * At relative address 0x1ffff8 of the frame buffers base address + * there exists a control register with the number of + * four control bits. They have the following meaning: + * bit value meaning + * + * 0 1 0=interlaced/1=non interlaced + * 1 2 0=video out disabled/1=video out enabled + * 2 4 0=normal mode as jumpered via JP8/1=complement mode + * 3 8 0=read onboard ROM/1 normal operation (required) + * + * As mentioned above there are several jumper. I think there + * is not very much information about the FrameMaster II in + * the world so I add these information for completeness. + * + * JP1 interlace selection (1-2 non interlaced/2-3 interlaced) + * JP2 wait state creation (leave as is!) + * JP3 wait state creation (leave as is!) + * JP4 modulate composite sync on green output (1-2 composite + * sync on green channel/2-3 normal composite sync) + * JP5 create test signal, shorting this jumper will create + * a white screen + * JP6 sync creation (1-2 composite sync/2-3 H-sync output) + * JP8 video mode (1-2 PAL/2-3 NTSC) + * + * With the following jumpering table you can connect the + * FrameMaster II to a normal TV via SCART connector: + * JP1: 2-3 + * JP4: 2-3 + * JP6: 2-3 + * JP8: 1-2 (means PAL for Europe) + * + * NOTE: + * There is no other possibility to change the video timings + * except the interlaced/non interlaced, sync control and the + * video mode PAL (50 Hz)/NTSC (60 Hz). Inside this + * FrameMaster II driver are assumed values to avoid anomalies + * to a future X server. Except the pixel clock is really + * constant at 30 MHz. + * + * 9 pin female video connector: + * + * 1 analog red 0.7 Vss + * 2 analog green 0.7 Vss + * 3 analog blue 0.7 Vss + * 4 H-sync TTL + * 5 V-sync TTL + * 6 ground + * 7 ground + * 8 ground + * 9 ground + * + * Some performance notes: + * The FrameMaster II was not designed to display a console + * this driver would do! It was designed to display still true + * color images. Imagine: When scroll up a text line there + * must copied ca. 1.7 MBytes to another place inside this + * frame buffer. This means 1.7 MByte read and 1.7 MByte write + * over the slow 16 bit wide Zorro2 bus! A scroll of one + * line needs 1 second so do not expect to much from this + * driver - he is at the limit! + * + */ + +/* + * definitions + */ + +#define FRAMEMASTER_SIZE 0x200000 +#define FRAMEMASTER_REG 0x1ffff8 + +#define FRAMEMASTER_NOLACE 1 +#define FRAMEMASTER_ENABLE 2 +#define FRAMEMASTER_COMPL 4 +#define FRAMEMASTER_ROM 8 + +static volatile unsigned char *fm2fb_reg; + +static struct fb_fix_screeninfo fb_fix = { + .smem_len = FRAMEMASTER_REG, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .line_length = (768 << 2), + .mmio_len = (8), + .accel = FB_ACCEL_NONE, +}; + +static int fm2fb_mode = -1; + +#define FM2FB_MODE_PAL 0 +#define FM2FB_MODE_NTSC 1 + +static struct fb_var_screeninfo fb_var_modes[] = { + { + /* 768 x 576, 32 bpp (PAL) */ + 768, 576, 768, 576, 0, 0, 32, 0, + { 16, 8, 0 }, { 8, 8, 0 }, { 0, 8, 0 }, { 24, 8, 0 }, + 0, FB_ACTIVATE_NOW, -1, -1, FB_ACCEL_NONE, + 33333, 10, 102, 10, 5, 80, 34, FB_SYNC_COMP_HIGH_ACT, 0 + }, { + /* 768 x 480, 32 bpp (NTSC - not supported yet */ + 768, 480, 768, 480, 0, 0, 32, 0, + { 16, 8, 0 }, { 8, 8, 0 }, { 0, 8, 0 }, { 24, 8, 0 }, + 0, FB_ACTIVATE_NOW, -1, -1, FB_ACCEL_NONE, + 33333, 10, 102, 10, 5, 80, 34, FB_SYNC_COMP_HIGH_ACT, 0 + } +}; + + /* + * Interface used by the world + */ + +static int fm2fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int fm2fb_blank(int blank, struct fb_info *info); + +static struct fb_ops fm2fb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = fm2fb_setcolreg, + .fb_blank = fm2fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + + /* + * Blank the display. + */ +static int fm2fb_blank(int blank, struct fb_info *info) +{ + unsigned char t = FRAMEMASTER_ROM; + + if (!blank) + t |= FRAMEMASTER_ENABLE | FRAMEMASTER_NOLACE; + fm2fb_reg[0] = t; + return 0; +} + + /* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ +static int fm2fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno < 16) { + red >>= 8; + green >>= 8; + blue >>= 8; + + ((u32*)(info->pseudo_palette))[regno] = (red << 16) | + (green << 8) | blue; + } + + return 0; +} + + /* + * Initialisation + */ + +static int fm2fb_probe(struct zorro_dev *z, const struct zorro_device_id *id); + +static struct zorro_device_id fm2fb_devices[] = { + { ZORRO_PROD_BSC_FRAMEMASTER_II }, + { ZORRO_PROD_HELFRICH_RAINBOW_II }, + { 0 } +}; +MODULE_DEVICE_TABLE(zorro, fm2fb_devices); + +static struct zorro_driver fm2fb_driver = { + .name = "fm2fb", + .id_table = fm2fb_devices, + .probe = fm2fb_probe, +}; + +static int fm2fb_probe(struct zorro_dev *z, const struct zorro_device_id *id) +{ + struct fb_info *info; + unsigned long *ptr; + int is_fm; + int x, y; + + is_fm = z->id == ZORRO_PROD_BSC_FRAMEMASTER_II; + + if (!zorro_request_device(z,"fm2fb")) + return -ENXIO; + + info = framebuffer_alloc(16 * sizeof(u32), &z->dev); + if (!info) { + zorro_release_device(z); + return -ENOMEM; + } + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + framebuffer_release(info); + zorro_release_device(z); + return -ENOMEM; + } + + /* assigning memory to kernel space */ + fb_fix.smem_start = zorro_resource_start(z); + info->screen_base = ioremap(fb_fix.smem_start, FRAMEMASTER_SIZE); + fb_fix.mmio_start = fb_fix.smem_start + FRAMEMASTER_REG; + fm2fb_reg = (unsigned char *)(info->screen_base+FRAMEMASTER_REG); + + strcpy(fb_fix.id, is_fm ? "FrameMaster II" : "Rainbow II"); + + /* make EBU color bars on display */ + ptr = (unsigned long *)fb_fix.smem_start; + for (y = 0; y < 576; y++) { + for (x = 0; x < 96; x++) *ptr++ = 0xffffff;/* white */ + for (x = 0; x < 96; x++) *ptr++ = 0xffff00;/* yellow */ + for (x = 0; x < 96; x++) *ptr++ = 0x00ffff;/* cyan */ + for (x = 0; x < 96; x++) *ptr++ = 0x00ff00;/* green */ + for (x = 0; x < 96; x++) *ptr++ = 0xff00ff;/* magenta */ + for (x = 0; x < 96; x++) *ptr++ = 0xff0000;/* red */ + for (x = 0; x < 96; x++) *ptr++ = 0x0000ff;/* blue */ + for (x = 0; x < 96; x++) *ptr++ = 0x000000;/* black */ + } + fm2fb_blank(0, info); + + if (fm2fb_mode == -1) + fm2fb_mode = FM2FB_MODE_PAL; + + info->fbops = &fm2fb_ops; + info->var = fb_var_modes[fm2fb_mode]; + info->pseudo_palette = info->par; + info->par = NULL; + info->fix = fb_fix; + info->flags = FBINFO_DEFAULT; + + if (register_framebuffer(info) < 0) { + fb_dealloc_cmap(&info->cmap); + iounmap(info->screen_base); + framebuffer_release(info); + zorro_release_device(z); + return -EINVAL; + } + fb_info(info, "%s frame buffer device\n", fb_fix.id); + return 0; +} + +int __init fm2fb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "pal", 3)) + fm2fb_mode = FM2FB_MODE_PAL; + else if (!strncmp(this_opt, "ntsc", 4)) + fm2fb_mode = FM2FB_MODE_NTSC; + } + return 0; +} + +int __init fm2fb_init(void) +{ + char *option = NULL; + + if (fb_get_options("fm2fb", &option)) + return -ENODEV; + fm2fb_setup(option); + return zorro_register_driver(&fm2fb_driver); +} + +module_init(fm2fb_init); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/fsl-diu-fb.c b/drivers/video/fbdev/fsl-diu-fb.c new file mode 100644 index 000000000000..e8758b9c3bcc --- /dev/null +++ b/drivers/video/fbdev/fsl-diu-fb.c @@ -0,0 +1,1994 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Freescale DIU Frame Buffer device driver + * + * Authors: Hongjun Chen <hong-jun.chen@freescale.com> + * Paul Widmer <paul.widmer@freescale.com> + * Srikanth Srinivasan <srikanth.srinivasan@freescale.com> + * York Sun <yorksun@freescale.com> + * + * Based on imxfb.c Copyright (C) 2004 S.Hauer, Pengutronix + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/vmalloc.h> +#include <linux/spinlock.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <sysdev/fsl_soc.h> +#include <linux/fsl-diu-fb.h> +#include "edid.h" + +#define NUM_AOIS 5 /* 1 for plane 0, 2 for planes 1 & 2 each */ + +/* HW cursor parameters */ +#define MAX_CURS 32 + +/* INT_STATUS/INT_MASK field descriptions */ +#define INT_VSYNC 0x01 /* Vsync interrupt */ +#define INT_VSYNC_WB 0x02 /* Vsync interrupt for write back operation */ +#define INT_UNDRUN 0x04 /* Under run exception interrupt */ +#define INT_PARERR 0x08 /* Display parameters error interrupt */ +#define INT_LS_BF_VS 0x10 /* Lines before vsync. interrupt */ + +/* + * List of supported video modes + * + * The first entry is the default video mode. The remain entries are in + * order if increasing resolution and frequency. The 320x240-60 mode is + * the initial AOI for the second and third planes. + */ +static struct fb_videomode fsl_diu_mode_db[] = { + { + .refresh = 60, + .xres = 1024, + .yres = 768, + .pixclock = 15385, + .left_margin = 160, + .right_margin = 24, + .upper_margin = 29, + .lower_margin = 3, + .hsync_len = 136, + .vsync_len = 6, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 320, + .yres = 240, + .pixclock = 79440, + .left_margin = 16, + .right_margin = 16, + .upper_margin = 16, + .lower_margin = 5, + .hsync_len = 48, + .vsync_len = 1, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 39722, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 72, + .xres = 640, + .yres = 480, + .pixclock = 32052, + .left_margin = 128, + .right_margin = 24, + .upper_margin = 28, + .lower_margin = 9, + .hsync_len = 40, + .vsync_len = 3, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 75, + .xres = 640, + .yres = 480, + .pixclock = 31747, + .left_margin = 120, + .right_margin = 16, + .upper_margin = 16, + .lower_margin = 1, + .hsync_len = 64, + .vsync_len = 3, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 90, + .xres = 640, + .yres = 480, + .pixclock = 25057, + .left_margin = 120, + .right_margin = 32, + .upper_margin = 14, + .lower_margin = 25, + .hsync_len = 40, + .vsync_len = 14, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 100, + .xres = 640, + .yres = 480, + .pixclock = 22272, + .left_margin = 48, + .right_margin = 32, + .upper_margin = 17, + .lower_margin = 22, + .hsync_len = 128, + .vsync_len = 12, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 800, + .yres = 480, + .pixclock = 33805, + .left_margin = 96, + .right_margin = 24, + .upper_margin = 10, + .lower_margin = 3, + .hsync_len = 72, + .vsync_len = 7, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 800, + .yres = 600, + .pixclock = 25000, + .left_margin = 88, + .right_margin = 40, + .upper_margin = 23, + .lower_margin = 1, + .hsync_len = 128, + .vsync_len = 4, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 854, + .yres = 480, + .pixclock = 31518, + .left_margin = 104, + .right_margin = 16, + .upper_margin = 13, + .lower_margin = 1, + .hsync_len = 88, + .vsync_len = 3, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 70, + .xres = 1024, + .yres = 768, + .pixclock = 16886, + .left_margin = 3, + .right_margin = 3, + .upper_margin = 2, + .lower_margin = 2, + .hsync_len = 40, + .vsync_len = 18, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 75, + .xres = 1024, + .yres = 768, + .pixclock = 15009, + .left_margin = 3, + .right_margin = 3, + .upper_margin = 2, + .lower_margin = 2, + .hsync_len = 80, + .vsync_len = 32, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 1280, + .yres = 480, + .pixclock = 18939, + .left_margin = 353, + .right_margin = 47, + .upper_margin = 39, + .lower_margin = 4, + .hsync_len = 8, + .vsync_len = 2, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 1280, + .yres = 720, + .pixclock = 13426, + .left_margin = 192, + .right_margin = 64, + .upper_margin = 22, + .lower_margin = 1, + .hsync_len = 136, + .vsync_len = 3, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 1280, + .yres = 1024, + .pixclock = 9375, + .left_margin = 38, + .right_margin = 128, + .upper_margin = 2, + .lower_margin = 7, + .hsync_len = 216, + .vsync_len = 37, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 70, + .xres = 1280, + .yres = 1024, + .pixclock = 9380, + .left_margin = 6, + .right_margin = 6, + .upper_margin = 4, + .lower_margin = 4, + .hsync_len = 60, + .vsync_len = 94, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 75, + .xres = 1280, + .yres = 1024, + .pixclock = 9380, + .left_margin = 6, + .right_margin = 6, + .upper_margin = 4, + .lower_margin = 4, + .hsync_len = 60, + .vsync_len = 15, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .refresh = 60, + .xres = 1920, + .yres = 1080, + .pixclock = 5787, + .left_margin = 328, + .right_margin = 120, + .upper_margin = 34, + .lower_margin = 1, + .hsync_len = 208, + .vsync_len = 3, + .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, +}; + +static char *fb_mode; +static unsigned long default_bpp = 32; +static enum fsl_diu_monitor_port monitor_port; +static char *monitor_string; + +#if defined(CONFIG_NOT_COHERENT_CACHE) +static u8 *coherence_data; +static size_t coherence_data_size; +static unsigned int d_cache_line_size; +#endif + +static DEFINE_SPINLOCK(diu_lock); + +enum mfb_index { + PLANE0 = 0, /* Plane 0, only one AOI that fills the screen */ + PLANE1_AOI0, /* Plane 1, first AOI */ + PLANE1_AOI1, /* Plane 1, second AOI */ + PLANE2_AOI0, /* Plane 2, first AOI */ + PLANE2_AOI1, /* Plane 2, second AOI */ +}; + +struct mfb_info { + enum mfb_index index; + char *id; + int registered; + unsigned long pseudo_palette[16]; + struct diu_ad *ad; + unsigned char g_alpha; + unsigned int count; + int x_aoi_d; /* aoi display x offset to physical screen */ + int y_aoi_d; /* aoi display y offset to physical screen */ + struct fsl_diu_data *parent; +}; + +/** + * struct fsl_diu_data - per-DIU data structure + * @dma_addr: DMA address of this structure + * @fsl_diu_info: fb_info objects, one per AOI + * @dev_attr: sysfs structure + * @irq: IRQ + * @monitor_port: the monitor port this DIU is connected to + * @diu_reg: pointer to the DIU hardware registers + * @reg_lock: spinlock for register access + * @dummy_aoi: video buffer for the 4x4 32-bit dummy AOI + * dummy_ad: DIU Area Descriptor for the dummy AOI + * @ad[]: Area Descriptors for each real AOI + * @gamma: gamma color table + * @cursor: hardware cursor data + * + * This data structure must be allocated with 32-byte alignment, so that the + * internal fields can be aligned properly. + */ +struct fsl_diu_data { + dma_addr_t dma_addr; + struct fb_info fsl_diu_info[NUM_AOIS]; + struct mfb_info mfb[NUM_AOIS]; + struct device_attribute dev_attr; + unsigned int irq; + enum fsl_diu_monitor_port monitor_port; + struct diu __iomem *diu_reg; + spinlock_t reg_lock; + u8 dummy_aoi[4 * 4 * 4]; + struct diu_ad dummy_ad __aligned(8); + struct diu_ad ad[NUM_AOIS] __aligned(8); + u8 gamma[256 * 3] __aligned(32); + /* It's easier to parse the cursor data as little-endian */ + __le16 cursor[MAX_CURS * MAX_CURS] __aligned(32); + /* Blank cursor data -- used to hide the cursor */ + __le16 blank_cursor[MAX_CURS * MAX_CURS] __aligned(32); + uint8_t edid_data[EDID_LENGTH]; + bool has_edid; +} __aligned(32); + +/* Determine the DMA address of a member of the fsl_diu_data structure */ +#define DMA_ADDR(p, f) ((p)->dma_addr + offsetof(struct fsl_diu_data, f)) + +static struct mfb_info mfb_template[] = { + { + .index = PLANE0, + .id = "Panel0", + .registered = 0, + .count = 0, + .x_aoi_d = 0, + .y_aoi_d = 0, + }, + { + .index = PLANE1_AOI0, + .id = "Panel1 AOI0", + .registered = 0, + .g_alpha = 0xff, + .count = 0, + .x_aoi_d = 0, + .y_aoi_d = 0, + }, + { + .index = PLANE1_AOI1, + .id = "Panel1 AOI1", + .registered = 0, + .g_alpha = 0xff, + .count = 0, + .x_aoi_d = 0, + .y_aoi_d = 480, + }, + { + .index = PLANE2_AOI0, + .id = "Panel2 AOI0", + .registered = 0, + .g_alpha = 0xff, + .count = 0, + .x_aoi_d = 640, + .y_aoi_d = 0, + }, + { + .index = PLANE2_AOI1, + .id = "Panel2 AOI1", + .registered = 0, + .g_alpha = 0xff, + .count = 0, + .x_aoi_d = 640, + .y_aoi_d = 480, + }, +}; + +#ifdef DEBUG +static void __attribute__ ((unused)) fsl_diu_dump(struct diu __iomem *hw) +{ + mb(); + pr_debug("DIU: desc=%08x,%08x,%08x, gamma=%08x pallete=%08x " + "cursor=%08x curs_pos=%08x diu_mode=%08x bgnd=%08x " + "disp_size=%08x hsyn_para=%08x vsyn_para=%08x syn_pol=%08x " + "thresholds=%08x int_mask=%08x plut=%08x\n", + hw->desc[0], hw->desc[1], hw->desc[2], hw->gamma, + hw->pallete, hw->cursor, hw->curs_pos, hw->diu_mode, + hw->bgnd, hw->disp_size, hw->hsyn_para, hw->vsyn_para, + hw->syn_pol, hw->thresholds, hw->int_mask, hw->plut); + rmb(); +} +#endif + +/** + * fsl_diu_name_to_port - convert a port name to a monitor port enum + * + * Takes the name of a monitor port ("dvi", "lvds", or "dlvds") and returns + * the enum fsl_diu_monitor_port that corresponds to that string. + * + * For compatibility with older versions, a number ("0", "1", or "2") is also + * supported. + * + * If the string is unknown, DVI is assumed. + * + * If the particular port is not supported by the platform, another port + * (platform-specific) is chosen instead. + */ +static enum fsl_diu_monitor_port fsl_diu_name_to_port(const char *s) +{ + enum fsl_diu_monitor_port port = FSL_DIU_PORT_DVI; + unsigned long val; + + if (s) { + if (!kstrtoul(s, 10, &val) && (val <= 2)) + port = (enum fsl_diu_monitor_port) val; + else if (strncmp(s, "lvds", 4) == 0) + port = FSL_DIU_PORT_LVDS; + else if (strncmp(s, "dlvds", 5) == 0) + port = FSL_DIU_PORT_DLVDS; + } + + return diu_ops.valid_monitor_port(port); +} + +/* + * Workaround for failed writing desc register of planes. + * Needed with MPC5121 DIU rev 2.0 silicon. + */ +void wr_reg_wa(u32 *reg, u32 val) +{ + do { + out_be32(reg, val); + } while (in_be32(reg) != val); +} + +static void fsl_diu_enable_panel(struct fb_info *info) +{ + struct mfb_info *pmfbi, *cmfbi, *mfbi = info->par; + struct diu_ad *ad = mfbi->ad; + struct fsl_diu_data *data = mfbi->parent; + struct diu __iomem *hw = data->diu_reg; + + switch (mfbi->index) { + case PLANE0: + wr_reg_wa(&hw->desc[0], ad->paddr); + break; + case PLANE1_AOI0: + cmfbi = &data->mfb[2]; + if (hw->desc[1] != ad->paddr) { /* AOI0 closed */ + if (cmfbi->count > 0) /* AOI1 open */ + ad->next_ad = + cpu_to_le32(cmfbi->ad->paddr); + else + ad->next_ad = 0; + wr_reg_wa(&hw->desc[1], ad->paddr); + } + break; + case PLANE2_AOI0: + cmfbi = &data->mfb[4]; + if (hw->desc[2] != ad->paddr) { /* AOI0 closed */ + if (cmfbi->count > 0) /* AOI1 open */ + ad->next_ad = + cpu_to_le32(cmfbi->ad->paddr); + else + ad->next_ad = 0; + wr_reg_wa(&hw->desc[2], ad->paddr); + } + break; + case PLANE1_AOI1: + pmfbi = &data->mfb[1]; + ad->next_ad = 0; + if (hw->desc[1] == data->dummy_ad.paddr) + wr_reg_wa(&hw->desc[1], ad->paddr); + else /* AOI0 open */ + pmfbi->ad->next_ad = cpu_to_le32(ad->paddr); + break; + case PLANE2_AOI1: + pmfbi = &data->mfb[3]; + ad->next_ad = 0; + if (hw->desc[2] == data->dummy_ad.paddr) + wr_reg_wa(&hw->desc[2], ad->paddr); + else /* AOI0 was open */ + pmfbi->ad->next_ad = cpu_to_le32(ad->paddr); + break; + } +} + +static void fsl_diu_disable_panel(struct fb_info *info) +{ + struct mfb_info *pmfbi, *cmfbi, *mfbi = info->par; + struct diu_ad *ad = mfbi->ad; + struct fsl_diu_data *data = mfbi->parent; + struct diu __iomem *hw = data->diu_reg; + + switch (mfbi->index) { + case PLANE0: + wr_reg_wa(&hw->desc[0], 0); + break; + case PLANE1_AOI0: + cmfbi = &data->mfb[2]; + if (cmfbi->count > 0) /* AOI1 is open */ + wr_reg_wa(&hw->desc[1], cmfbi->ad->paddr); + /* move AOI1 to the first */ + else /* AOI1 was closed */ + wr_reg_wa(&hw->desc[1], data->dummy_ad.paddr); + /* close AOI 0 */ + break; + case PLANE2_AOI0: + cmfbi = &data->mfb[4]; + if (cmfbi->count > 0) /* AOI1 is open */ + wr_reg_wa(&hw->desc[2], cmfbi->ad->paddr); + /* move AOI1 to the first */ + else /* AOI1 was closed */ + wr_reg_wa(&hw->desc[2], data->dummy_ad.paddr); + /* close AOI 0 */ + break; + case PLANE1_AOI1: + pmfbi = &data->mfb[1]; + if (hw->desc[1] != ad->paddr) { + /* AOI1 is not the first in the chain */ + if (pmfbi->count > 0) + /* AOI0 is open, must be the first */ + pmfbi->ad->next_ad = 0; + } else /* AOI1 is the first in the chain */ + wr_reg_wa(&hw->desc[1], data->dummy_ad.paddr); + /* close AOI 1 */ + break; + case PLANE2_AOI1: + pmfbi = &data->mfb[3]; + if (hw->desc[2] != ad->paddr) { + /* AOI1 is not the first in the chain */ + if (pmfbi->count > 0) + /* AOI0 is open, must be the first */ + pmfbi->ad->next_ad = 0; + } else /* AOI1 is the first in the chain */ + wr_reg_wa(&hw->desc[2], data->dummy_ad.paddr); + /* close AOI 1 */ + break; + } +} + +static void enable_lcdc(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + struct diu __iomem *hw = data->diu_reg; + + out_be32(&hw->diu_mode, MFB_MODE1); +} + +static void disable_lcdc(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + struct diu __iomem *hw = data->diu_reg; + + out_be32(&hw->diu_mode, 0); +} + +static void adjust_aoi_size_position(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mfb_info *lower_aoi_mfbi, *upper_aoi_mfbi, *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + int available_height, upper_aoi_bottom; + enum mfb_index index = mfbi->index; + int lower_aoi_is_open, upper_aoi_is_open; + __u32 base_plane_width, base_plane_height, upper_aoi_height; + + base_plane_width = data->fsl_diu_info[0].var.xres; + base_plane_height = data->fsl_diu_info[0].var.yres; + + if (mfbi->x_aoi_d < 0) + mfbi->x_aoi_d = 0; + if (mfbi->y_aoi_d < 0) + mfbi->y_aoi_d = 0; + switch (index) { + case PLANE0: + if (mfbi->x_aoi_d != 0) + mfbi->x_aoi_d = 0; + if (mfbi->y_aoi_d != 0) + mfbi->y_aoi_d = 0; + break; + case PLANE1_AOI0: + case PLANE2_AOI0: + lower_aoi_mfbi = data->fsl_diu_info[index+1].par; + lower_aoi_is_open = lower_aoi_mfbi->count > 0 ? 1 : 0; + if (var->xres > base_plane_width) + var->xres = base_plane_width; + if ((mfbi->x_aoi_d + var->xres) > base_plane_width) + mfbi->x_aoi_d = base_plane_width - var->xres; + + if (lower_aoi_is_open) + available_height = lower_aoi_mfbi->y_aoi_d; + else + available_height = base_plane_height; + if (var->yres > available_height) + var->yres = available_height; + if ((mfbi->y_aoi_d + var->yres) > available_height) + mfbi->y_aoi_d = available_height - var->yres; + break; + case PLANE1_AOI1: + case PLANE2_AOI1: + upper_aoi_mfbi = data->fsl_diu_info[index-1].par; + upper_aoi_height = data->fsl_diu_info[index-1].var.yres; + upper_aoi_bottom = upper_aoi_mfbi->y_aoi_d + upper_aoi_height; + upper_aoi_is_open = upper_aoi_mfbi->count > 0 ? 1 : 0; + if (var->xres > base_plane_width) + var->xres = base_plane_width; + if ((mfbi->x_aoi_d + var->xres) > base_plane_width) + mfbi->x_aoi_d = base_plane_width - var->xres; + if (mfbi->y_aoi_d < 0) + mfbi->y_aoi_d = 0; + if (upper_aoi_is_open) { + if (mfbi->y_aoi_d < upper_aoi_bottom) + mfbi->y_aoi_d = upper_aoi_bottom; + available_height = base_plane_height + - upper_aoi_bottom; + } else + available_height = base_plane_height; + if (var->yres > available_height) + var->yres = available_height; + if ((mfbi->y_aoi_d + var->yres) > base_plane_height) + mfbi->y_aoi_d = base_plane_height - var->yres; + break; + } +} +/* + * Checks to see if the hardware supports the state requested by var passed + * in. This function does not alter the hardware state! If the var passed in + * is slightly off by what the hardware can support then we alter the var + * PASSED in to what we can do. If the hardware doesn't support mode change + * a -EINVAL will be returned by the upper layers. + */ +static int fsl_diu_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xoffset < 0) + var->xoffset = 0; + + if (var->yoffset < 0) + var->yoffset = 0; + + if (var->xoffset + info->var.xres > info->var.xres_virtual) + var->xoffset = info->var.xres_virtual - info->var.xres; + + if (var->yoffset + info->var.yres > info->var.yres_virtual) + var->yoffset = info->var.yres_virtual - info->var.yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 16; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* Copy nonstd field to/from sync for fbset usage */ + var->sync |= var->nonstd; + var->nonstd |= var->sync; + + adjust_aoi_size_position(var, info); + return 0; +} + +static void set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mfb_info *mfbi = info->par; + + strncpy(fix->id, mfbi->id, sizeof(fix->id)); + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; +} + +static void update_lcdc(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct mfb_info *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + struct diu __iomem *hw; + int i, j; + u8 *gamma_table_base; + + u32 temp; + + hw = data->diu_reg; + + if (diu_ops.set_monitor_port) + diu_ops.set_monitor_port(data->monitor_port); + gamma_table_base = data->gamma; + + /* Prep for DIU init - gamma table, cursor table */ + + for (i = 0; i <= 2; i++) + for (j = 0; j <= 255; j++) + *gamma_table_base++ = j; + + if (diu_ops.set_gamma_table) + diu_ops.set_gamma_table(data->monitor_port, data->gamma); + + disable_lcdc(info); + + /* Program DIU registers */ + + out_be32(&hw->gamma, DMA_ADDR(data, gamma)); + + out_be32(&hw->bgnd, 0x007F7F7F); /* Set background to grey */ + out_be32(&hw->disp_size, (var->yres << 16) | var->xres); + + /* Horizontal and vertical configuration register */ + temp = var->left_margin << 22 | /* BP_H */ + var->hsync_len << 11 | /* PW_H */ + var->right_margin; /* FP_H */ + + out_be32(&hw->hsyn_para, temp); + + temp = var->upper_margin << 22 | /* BP_V */ + var->vsync_len << 11 | /* PW_V */ + var->lower_margin; /* FP_V */ + + out_be32(&hw->vsyn_para, temp); + + diu_ops.set_pixel_clock(var->pixclock); + +#ifndef CONFIG_PPC_MPC512x + /* + * The PLUT register is defined differently on the MPC5121 than it + * is on other SOCs. Unfortunately, there's no documentation that + * explains how it's supposed to be programmed, so for now, we leave + * it at the default value on the MPC5121. + * + * For other SOCs, program it for the highest priority, which will + * reduce the chance of underrun. Technically, we should scale the + * priority to match the screen resolution, but doing that properly + * requires delicate fine-tuning for each use-case. + */ + out_be32(&hw->plut, 0x01F5F666); +#endif + + /* Enable the DIU */ + enable_lcdc(info); +} + +static int map_video_memory(struct fb_info *info) +{ + u32 smem_len = info->fix.line_length * info->var.yres_virtual; + void *p; + + p = alloc_pages_exact(smem_len, GFP_DMA | __GFP_ZERO); + if (!p) { + dev_err(info->dev, "unable to allocate fb memory\n"); + return -ENOMEM; + } + mutex_lock(&info->mm_lock); + info->screen_base = p; + info->fix.smem_start = virt_to_phys(info->screen_base); + info->fix.smem_len = smem_len; + mutex_unlock(&info->mm_lock); + info->screen_size = info->fix.smem_len; + + return 0; +} + +static void unmap_video_memory(struct fb_info *info) +{ + void *p = info->screen_base; + size_t l = info->fix.smem_len; + + mutex_lock(&info->mm_lock); + info->screen_base = NULL; + info->fix.smem_start = 0; + info->fix.smem_len = 0; + mutex_unlock(&info->mm_lock); + + if (p) + free_pages_exact(p, l); +} + +/* + * Using the fb_var_screeninfo in fb_info we set the aoi of this + * particular framebuffer. It is a light version of fsl_diu_set_par. + */ +static int fsl_diu_set_aoi(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct mfb_info *mfbi = info->par; + struct diu_ad *ad = mfbi->ad; + + /* AOI should not be greater than display size */ + ad->offset_xyi = cpu_to_le32((var->yoffset << 16) | var->xoffset); + ad->offset_xyd = cpu_to_le32((mfbi->y_aoi_d << 16) | mfbi->x_aoi_d); + return 0; +} + +/** + * fsl_diu_get_pixel_format: return the pixel format for a given color depth + * + * The pixel format is a 32-bit value that determine which bits in each + * pixel are to be used for each color. This is the default function used + * if the platform does not define its own version. + */ +static u32 fsl_diu_get_pixel_format(unsigned int bits_per_pixel) +{ +#define PF_BYTE_F 0x10000000 +#define PF_ALPHA_C_MASK 0x0E000000 +#define PF_ALPHA_C_SHIFT 25 +#define PF_BLUE_C_MASK 0x01800000 +#define PF_BLUE_C_SHIFT 23 +#define PF_GREEN_C_MASK 0x00600000 +#define PF_GREEN_C_SHIFT 21 +#define PF_RED_C_MASK 0x00180000 +#define PF_RED_C_SHIFT 19 +#define PF_PALETTE 0x00040000 +#define PF_PIXEL_S_MASK 0x00030000 +#define PF_PIXEL_S_SHIFT 16 +#define PF_COMP_3_MASK 0x0000F000 +#define PF_COMP_3_SHIFT 12 +#define PF_COMP_2_MASK 0x00000F00 +#define PF_COMP_2_SHIFT 8 +#define PF_COMP_1_MASK 0x000000F0 +#define PF_COMP_1_SHIFT 4 +#define PF_COMP_0_MASK 0x0000000F +#define PF_COMP_0_SHIFT 0 + +#define MAKE_PF(alpha, red, green, blue, size, c0, c1, c2, c3) \ + cpu_to_le32(PF_BYTE_F | (alpha << PF_ALPHA_C_SHIFT) | \ + (blue << PF_BLUE_C_SHIFT) | (green << PF_GREEN_C_SHIFT) | \ + (red << PF_RED_C_SHIFT) | (c3 << PF_COMP_3_SHIFT) | \ + (c2 << PF_COMP_2_SHIFT) | (c1 << PF_COMP_1_SHIFT) | \ + (c0 << PF_COMP_0_SHIFT) | (size << PF_PIXEL_S_SHIFT)) + + switch (bits_per_pixel) { + case 32: + /* 0x88883316 */ + return MAKE_PF(3, 2, 1, 0, 3, 8, 8, 8, 8); + case 24: + /* 0x88082219 */ + return MAKE_PF(4, 0, 1, 2, 2, 8, 8, 8, 0); + case 16: + /* 0x65053118 */ + return MAKE_PF(4, 2, 1, 0, 1, 5, 6, 5, 0); + default: + pr_err("fsl-diu: unsupported color depth %u\n", bits_per_pixel); + return 0; + } +} + +/* + * Copies a cursor image from user space to the proper place in driver + * memory so that the hardware can display the cursor image. + * + * Cursor data is represented as a sequence of 'width' bits packed into bytes. + * That is, the first 8 bits are in the first byte, the second 8 bits in the + * second byte, and so on. Therefore, the each row of the cursor is (width + + * 7) / 8 bytes of 'data' + * + * The DIU only supports cursors up to 32x32 (MAX_CURS). We reject cursors + * larger than this, so we already know that 'width' <= 32. Therefore, we can + * simplify our code by using a 32-bit big-endian integer ("line") to read in + * a single line of pixels, and only look at the top 'width' bits of that + * integer. + * + * This could result in an unaligned 32-bit read. For example, if the cursor + * is 24x24, then the first three bytes of 'image' contain the pixel data for + * the top line of the cursor. We do a 32-bit read of 'image', but we look + * only at the top 24 bits. Then we increment 'image' by 3 bytes. The next + * read is unaligned. The only problem is that we might read past the end of + * 'image' by 1-3 bytes, but that should not cause any problems. + */ +static void fsl_diu_load_cursor_image(struct fb_info *info, + const void *image, uint16_t bg, uint16_t fg, + unsigned int width, unsigned int height) +{ + struct mfb_info *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + __le16 *cursor = data->cursor; + __le16 _fg = cpu_to_le16(fg); + __le16 _bg = cpu_to_le16(bg); + unsigned int h, w; + + for (h = 0; h < height; h++) { + uint32_t mask = 1 << 31; + uint32_t line = be32_to_cpup(image); + + for (w = 0; w < width; w++) { + cursor[w] = (line & mask) ? _fg : _bg; + mask >>= 1; + } + + cursor += MAX_CURS; + image += DIV_ROUND_UP(width, 8); + } +} + +/* + * Set a hardware cursor. The image data for the cursor is passed via the + * fb_cursor object. + */ +static int fsl_diu_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct mfb_info *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + struct diu __iomem *hw = data->diu_reg; + + if (cursor->image.width > MAX_CURS || cursor->image.height > MAX_CURS) + return -EINVAL; + + /* The cursor size has changed */ + if (cursor->set & FB_CUR_SETSIZE) { + /* + * The DIU cursor is a fixed size, so when we get this + * message, instead of resizing the cursor, we just clear + * all the image data, in expectation of new data. However, + * in tests this control does not appear to be normally + * called. + */ + memset(data->cursor, 0, sizeof(data->cursor)); + } + + /* The cursor position has changed (cursor->image.dx|dy) */ + if (cursor->set & FB_CUR_SETPOS) { + uint32_t xx, yy; + + yy = (cursor->image.dy - info->var.yoffset) & 0x7ff; + xx = (cursor->image.dx - info->var.xoffset) & 0x7ff; + + out_be32(&hw->curs_pos, yy << 16 | xx); + } + + /* + * FB_CUR_SETIMAGE - the cursor image has changed + * FB_CUR_SETCMAP - the cursor colors has changed + * FB_CUR_SETSHAPE - the cursor bitmask has changed + */ + if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETCMAP | FB_CUR_SETIMAGE)) { + unsigned int image_size = + DIV_ROUND_UP(cursor->image.width, 8) * cursor->image.height; + unsigned int image_words = + DIV_ROUND_UP(image_size, sizeof(uint32_t)); + unsigned int bg_idx = cursor->image.bg_color; + unsigned int fg_idx = cursor->image.fg_color; + uint8_t buffer[image_size]; + uint32_t *image, *source, *mask; + uint16_t fg, bg; + unsigned int i; + + if (info->state != FBINFO_STATE_RUNNING) + return 0; + + /* + * Determine the size of the cursor image data. Normally, + * it's 8x16. + */ + image_size = DIV_ROUND_UP(cursor->image.width, 8) * + cursor->image.height; + + bg = ((info->cmap.red[bg_idx] & 0xf8) << 7) | + ((info->cmap.green[bg_idx] & 0xf8) << 2) | + ((info->cmap.blue[bg_idx] & 0xf8) >> 3) | + 1 << 15; + + fg = ((info->cmap.red[fg_idx] & 0xf8) << 7) | + ((info->cmap.green[fg_idx] & 0xf8) << 2) | + ((info->cmap.blue[fg_idx] & 0xf8) >> 3) | + 1 << 15; + + /* Use 32-bit operations on the data to improve performance */ + image = (uint32_t *)buffer; + source = (uint32_t *)cursor->image.data; + mask = (uint32_t *)cursor->mask; + + if (cursor->rop == ROP_XOR) + for (i = 0; i < image_words; i++) + image[i] = source[i] ^ mask[i]; + else + for (i = 0; i < image_words; i++) + image[i] = source[i] & mask[i]; + + fsl_diu_load_cursor_image(info, image, bg, fg, + cursor->image.width, cursor->image.height); + } + + /* + * Show or hide the cursor. The cursor data is always stored in the + * 'cursor' memory block, and the actual cursor position is always in + * the DIU's CURS_POS register. To hide the cursor, we redirect the + * CURSOR register to a blank cursor. The show the cursor, we + * redirect the CURSOR register to the real cursor data. + */ + if (cursor->enable) + out_be32(&hw->cursor, DMA_ADDR(data, cursor)); + else + out_be32(&hw->cursor, DMA_ADDR(data, blank_cursor)); + + return 0; +} + +/* + * Using the fb_var_screeninfo in fb_info we set the resolution of this + * particular framebuffer. This function alters the fb_fix_screeninfo stored + * in fb_info. It does not alter var in fb_info since we are using that + * data. This means we depend on the data in var inside fb_info to be + * supported by the hardware. fsl_diu_check_var is always called before + * fsl_diu_set_par to ensure this. + */ +static int fsl_diu_set_par(struct fb_info *info) +{ + unsigned long len; + struct fb_var_screeninfo *var = &info->var; + struct mfb_info *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + struct diu_ad *ad = mfbi->ad; + struct diu __iomem *hw; + + hw = data->diu_reg; + + set_fix(info); + + len = info->var.yres_virtual * info->fix.line_length; + /* Alloc & dealloc each time resolution/bpp change */ + if (len != info->fix.smem_len) { + if (info->fix.smem_start) + unmap_video_memory(info); + + /* Memory allocation for framebuffer */ + if (map_video_memory(info)) { + dev_err(info->dev, "unable to allocate fb memory 1\n"); + return -ENOMEM; + } + } + + if (diu_ops.get_pixel_format) + ad->pix_fmt = diu_ops.get_pixel_format(data->monitor_port, + var->bits_per_pixel); + else + ad->pix_fmt = fsl_diu_get_pixel_format(var->bits_per_pixel); + + ad->addr = cpu_to_le32(info->fix.smem_start); + ad->src_size_g_alpha = cpu_to_le32((var->yres_virtual << 12) | + var->xres_virtual) | mfbi->g_alpha; + /* AOI should not be greater than display size */ + ad->aoi_size = cpu_to_le32((var->yres << 16) | var->xres); + ad->offset_xyi = cpu_to_le32((var->yoffset << 16) | var->xoffset); + ad->offset_xyd = cpu_to_le32((mfbi->y_aoi_d << 16) | mfbi->x_aoi_d); + + /* Disable chroma keying function */ + ad->ckmax_r = 0; + ad->ckmax_g = 0; + ad->ckmax_b = 0; + + ad->ckmin_r = 255; + ad->ckmin_g = 255; + ad->ckmin_b = 255; + + if (mfbi->index == PLANE0) + update_lcdc(info); + return 0; +} + +static inline __u32 CNVT_TOHW(__u32 val, __u32 width) +{ + return ((val << width) + 0x7FFF - val) >> 16; +} + +/* + * Set a single color register. The values supplied have a 16 bit magnitude + * which needs to be scaled in this function for the hardware. Things to take + * into consideration are how many color registers, if any, are supported with + * the current color visual. With truecolor mode no color palettes are + * supported. Here a pseudo palette is created which we store the value in + * pseudo_palette in struct fb_info. For pseudocolor mode we have a limited + * color palette. + */ +static int fsl_diu_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + u32 v; + + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + pal[regno] = v; + ret = 0; + } + break; + } + + return ret; +} + +/* + * Pan (or wrap, depending on the `vmode' field) the display using the + * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values + * don't fit, return -EINVAL. + */ +static int fsl_diu_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + if (var->xoffset < 0 || var->yoffset < 0 + || var->xoffset + info->var.xres > info->var.xres_virtual + || var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + fsl_diu_set_aoi(info); + + return 0; +} + +static int fsl_diu_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct mfb_info *mfbi = info->par; + struct diu_ad *ad = mfbi->ad; + struct mfb_chroma_key ck; + unsigned char global_alpha; + struct aoi_display_offset aoi_d; + __u32 pix_fmt; + void __user *buf = (void __user *)arg; + + if (!arg) + return -EINVAL; + + dev_dbg(info->dev, "ioctl %08x (dir=%s%s type=%u nr=%u size=%u)\n", cmd, + _IOC_DIR(cmd) & _IOC_READ ? "R" : "", + _IOC_DIR(cmd) & _IOC_WRITE ? "W" : "", + _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd)); + + switch (cmd) { + case MFB_SET_PIXFMT_OLD: + dev_warn(info->dev, + "MFB_SET_PIXFMT value of 0x%08x is deprecated.\n", + MFB_SET_PIXFMT_OLD); + case MFB_SET_PIXFMT: + if (copy_from_user(&pix_fmt, buf, sizeof(pix_fmt))) + return -EFAULT; + ad->pix_fmt = pix_fmt; + break; + case MFB_GET_PIXFMT_OLD: + dev_warn(info->dev, + "MFB_GET_PIXFMT value of 0x%08x is deprecated.\n", + MFB_GET_PIXFMT_OLD); + case MFB_GET_PIXFMT: + pix_fmt = ad->pix_fmt; + if (copy_to_user(buf, &pix_fmt, sizeof(pix_fmt))) + return -EFAULT; + break; + case MFB_SET_AOID: + if (copy_from_user(&aoi_d, buf, sizeof(aoi_d))) + return -EFAULT; + mfbi->x_aoi_d = aoi_d.x_aoi_d; + mfbi->y_aoi_d = aoi_d.y_aoi_d; + fsl_diu_check_var(&info->var, info); + fsl_diu_set_aoi(info); + break; + case MFB_GET_AOID: + aoi_d.x_aoi_d = mfbi->x_aoi_d; + aoi_d.y_aoi_d = mfbi->y_aoi_d; + if (copy_to_user(buf, &aoi_d, sizeof(aoi_d))) + return -EFAULT; + break; + case MFB_GET_ALPHA: + global_alpha = mfbi->g_alpha; + if (copy_to_user(buf, &global_alpha, sizeof(global_alpha))) + return -EFAULT; + break; + case MFB_SET_ALPHA: + /* set panel information */ + if (copy_from_user(&global_alpha, buf, sizeof(global_alpha))) + return -EFAULT; + ad->src_size_g_alpha = (ad->src_size_g_alpha & (~0xff)) | + (global_alpha & 0xff); + mfbi->g_alpha = global_alpha; + break; + case MFB_SET_CHROMA_KEY: + /* set panel winformation */ + if (copy_from_user(&ck, buf, sizeof(ck))) + return -EFAULT; + + if (ck.enable && + (ck.red_max < ck.red_min || + ck.green_max < ck.green_min || + ck.blue_max < ck.blue_min)) + return -EINVAL; + + if (!ck.enable) { + ad->ckmax_r = 0; + ad->ckmax_g = 0; + ad->ckmax_b = 0; + ad->ckmin_r = 255; + ad->ckmin_g = 255; + ad->ckmin_b = 255; + } else { + ad->ckmax_r = ck.red_max; + ad->ckmax_g = ck.green_max; + ad->ckmax_b = ck.blue_max; + ad->ckmin_r = ck.red_min; + ad->ckmin_g = ck.green_min; + ad->ckmin_b = ck.blue_min; + } + break; +#ifdef CONFIG_PPC_MPC512x + case MFB_SET_GAMMA: { + struct fsl_diu_data *data = mfbi->parent; + + if (copy_from_user(data->gamma, buf, sizeof(data->gamma))) + return -EFAULT; + setbits32(&data->diu_reg->gamma, 0); /* Force table reload */ + break; + } + case MFB_GET_GAMMA: { + struct fsl_diu_data *data = mfbi->parent; + + if (copy_to_user(buf, data->gamma, sizeof(data->gamma))) + return -EFAULT; + break; + } +#endif + default: + dev_err(info->dev, "unknown ioctl command (0x%08X)\n", cmd); + return -ENOIOCTLCMD; + } + + return 0; +} + +static inline void fsl_diu_enable_interrupts(struct fsl_diu_data *data) +{ + u32 int_mask = INT_UNDRUN; /* enable underrun detection */ + + if (IS_ENABLED(CONFIG_NOT_COHERENT_CACHE)) + int_mask |= INT_VSYNC; /* enable vertical sync */ + + clrbits32(&data->diu_reg->int_mask, int_mask); +} + +/* turn on fb if count == 1 + */ +static int fsl_diu_open(struct fb_info *info, int user) +{ + struct mfb_info *mfbi = info->par; + int res = 0; + + /* free boot splash memory on first /dev/fb0 open */ + if ((mfbi->index == PLANE0) && diu_ops.release_bootmem) + diu_ops.release_bootmem(); + + spin_lock(&diu_lock); + mfbi->count++; + if (mfbi->count == 1) { + fsl_diu_check_var(&info->var, info); + res = fsl_diu_set_par(info); + if (res < 0) + mfbi->count--; + else { + fsl_diu_enable_interrupts(mfbi->parent); + fsl_diu_enable_panel(info); + } + } + + spin_unlock(&diu_lock); + return res; +} + +/* turn off fb if count == 0 + */ +static int fsl_diu_release(struct fb_info *info, int user) +{ + struct mfb_info *mfbi = info->par; + int res = 0; + + spin_lock(&diu_lock); + mfbi->count--; + if (mfbi->count == 0) { + struct fsl_diu_data *data = mfbi->parent; + bool disable = true; + int i; + + /* Disable interrupts only if all AOIs are closed */ + for (i = 0; i < NUM_AOIS; i++) { + struct mfb_info *mi = data->fsl_diu_info[i].par; + + if (mi->count) + disable = false; + } + if (disable) + out_be32(&data->diu_reg->int_mask, 0xffffffff); + fsl_diu_disable_panel(info); + } + + spin_unlock(&diu_lock); + return res; +} + +static struct fb_ops fsl_diu_ops = { + .owner = THIS_MODULE, + .fb_check_var = fsl_diu_check_var, + .fb_set_par = fsl_diu_set_par, + .fb_setcolreg = fsl_diu_setcolreg, + .fb_pan_display = fsl_diu_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_ioctl = fsl_diu_ioctl, + .fb_open = fsl_diu_open, + .fb_release = fsl_diu_release, + .fb_cursor = fsl_diu_cursor, +}; + +static int install_fb(struct fb_info *info) +{ + int rc; + struct mfb_info *mfbi = info->par; + struct fsl_diu_data *data = mfbi->parent; + const char *aoi_mode, *init_aoi_mode = "320x240"; + struct fb_videomode *db = fsl_diu_mode_db; + unsigned int dbsize = ARRAY_SIZE(fsl_diu_mode_db); + int has_default_mode = 1; + + info->var.activate = FB_ACTIVATE_NOW; + info->fbops = &fsl_diu_ops; + info->flags = FBINFO_DEFAULT | FBINFO_VIRTFB | FBINFO_PARTIAL_PAN_OK | + FBINFO_READS_FAST; + info->pseudo_palette = mfbi->pseudo_palette; + + rc = fb_alloc_cmap(&info->cmap, 16, 0); + if (rc) + return rc; + + if (mfbi->index == PLANE0) { + if (data->has_edid) { + /* Now build modedb from EDID */ + fb_edid_to_monspecs(data->edid_data, &info->monspecs); + fb_videomode_to_modelist(info->monspecs.modedb, + info->monspecs.modedb_len, + &info->modelist); + db = info->monspecs.modedb; + dbsize = info->monspecs.modedb_len; + } + aoi_mode = fb_mode; + } else { + aoi_mode = init_aoi_mode; + } + rc = fb_find_mode(&info->var, info, aoi_mode, db, dbsize, NULL, + default_bpp); + if (!rc) { + /* + * For plane 0 we continue and look into + * driver's internal modedb. + */ + if ((mfbi->index == PLANE0) && data->has_edid) + has_default_mode = 0; + else + return -EINVAL; + } + + if (!has_default_mode) { + rc = fb_find_mode(&info->var, info, aoi_mode, fsl_diu_mode_db, + ARRAY_SIZE(fsl_diu_mode_db), NULL, default_bpp); + if (rc) + has_default_mode = 1; + } + + /* Still not found, use preferred mode from database if any */ + if (!has_default_mode && info->monspecs.modedb) { + struct fb_monspecs *specs = &info->monspecs; + struct fb_videomode *modedb = &specs->modedb[0]; + + /* + * Get preferred timing. If not found, + * first mode in database will be used. + */ + if (specs->misc & FB_MISC_1ST_DETAIL) { + int i; + + for (i = 0; i < specs->modedb_len; i++) { + if (specs->modedb[i].flag & FB_MODE_IS_FIRST) { + modedb = &specs->modedb[i]; + break; + } + } + } + + info->var.bits_per_pixel = default_bpp; + fb_videomode_to_var(&info->var, modedb); + } + + if (fsl_diu_check_var(&info->var, info)) { + dev_err(info->dev, "fsl_diu_check_var failed\n"); + unmap_video_memory(info); + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + if (register_framebuffer(info) < 0) { + dev_err(info->dev, "register_framebuffer failed\n"); + unmap_video_memory(info); + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + mfbi->registered = 1; + dev_info(info->dev, "%s registered successfully\n", mfbi->id); + + return 0; +} + +static void uninstall_fb(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + + if (!mfbi->registered) + return; + + unregister_framebuffer(info); + unmap_video_memory(info); + if (&info->cmap) + fb_dealloc_cmap(&info->cmap); + + mfbi->registered = 0; +} + +static irqreturn_t fsl_diu_isr(int irq, void *dev_id) +{ + struct diu __iomem *hw = dev_id; + uint32_t status = in_be32(&hw->int_status); + + if (status) { + /* This is the workaround for underrun */ + if (status & INT_UNDRUN) { + out_be32(&hw->diu_mode, 0); + udelay(1); + out_be32(&hw->diu_mode, 1); + } +#if defined(CONFIG_NOT_COHERENT_CACHE) + else if (status & INT_VSYNC) { + unsigned int i; + + for (i = 0; i < coherence_data_size; + i += d_cache_line_size) + __asm__ __volatile__ ( + "dcbz 0, %[input]" + ::[input]"r"(&coherence_data[i])); + } +#endif + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ +static int fsl_diu_suspend(struct platform_device *ofdev, pm_message_t state) +{ + struct fsl_diu_data *data; + + data = dev_get_drvdata(&ofdev->dev); + disable_lcdc(data->fsl_diu_info); + + return 0; +} + +static int fsl_diu_resume(struct platform_device *ofdev) +{ + struct fsl_diu_data *data; + + data = dev_get_drvdata(&ofdev->dev); + enable_lcdc(data->fsl_diu_info); + + return 0; +} + +#else +#define fsl_diu_suspend NULL +#define fsl_diu_resume NULL +#endif /* CONFIG_PM */ + +static ssize_t store_monitor(struct device *device, + struct device_attribute *attr, const char *buf, size_t count) +{ + enum fsl_diu_monitor_port old_monitor_port; + struct fsl_diu_data *data = + container_of(attr, struct fsl_diu_data, dev_attr); + + old_monitor_port = data->monitor_port; + data->monitor_port = fsl_diu_name_to_port(buf); + + if (old_monitor_port != data->monitor_port) { + /* All AOIs need adjust pixel format + * fsl_diu_set_par only change the pixsel format here + * unlikely to fail. */ + unsigned int i; + + for (i=0; i < NUM_AOIS; i++) + fsl_diu_set_par(&data->fsl_diu_info[i]); + } + return count; +} + +static ssize_t show_monitor(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fsl_diu_data *data = + container_of(attr, struct fsl_diu_data, dev_attr); + + switch (data->monitor_port) { + case FSL_DIU_PORT_DVI: + return sprintf(buf, "DVI\n"); + case FSL_DIU_PORT_LVDS: + return sprintf(buf, "Single-link LVDS\n"); + case FSL_DIU_PORT_DLVDS: + return sprintf(buf, "Dual-link LVDS\n"); + } + + return 0; +} + +static int fsl_diu_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mfb_info *mfbi; + struct fsl_diu_data *data; + dma_addr_t dma_addr; /* DMA addr of fsl_diu_data struct */ + const void *prop; + unsigned int i; + int ret; + + data = dmam_alloc_coherent(&pdev->dev, sizeof(struct fsl_diu_data), + &dma_addr, GFP_DMA | __GFP_ZERO); + if (!data) + return -ENOMEM; + data->dma_addr = dma_addr; + + /* + * dma_alloc_coherent() uses a page allocator, so the address is + * always page-aligned. We need the memory to be 32-byte aligned, + * so that's good. However, if one day the allocator changes, we + * need to catch that. It's not worth the effort to handle unaligned + * alloctions now because it's highly unlikely to ever be a problem. + */ + if ((unsigned long)data & 31) { + dev_err(&pdev->dev, "misaligned allocation"); + ret = -ENOMEM; + goto error; + } + + spin_lock_init(&data->reg_lock); + + for (i = 0; i < NUM_AOIS; i++) { + struct fb_info *info = &data->fsl_diu_info[i]; + + info->device = &pdev->dev; + info->par = &data->mfb[i]; + + /* + * We store the physical address of the AD in the reserved + * 'paddr' field of the AD itself. + */ + data->ad[i].paddr = DMA_ADDR(data, ad[i]); + + info->fix.smem_start = 0; + + /* Initialize the AOI data structure */ + mfbi = info->par; + memcpy(mfbi, &mfb_template[i], sizeof(struct mfb_info)); + mfbi->parent = data; + mfbi->ad = &data->ad[i]; + } + + /* Get the EDID data from the device tree, if present */ + prop = of_get_property(np, "edid", &ret); + if (prop && ret == EDID_LENGTH) { + memcpy(data->edid_data, prop, EDID_LENGTH); + data->has_edid = true; + } + + data->diu_reg = of_iomap(np, 0); + if (!data->diu_reg) { + dev_err(&pdev->dev, "cannot map DIU registers\n"); + ret = -EFAULT; + goto error; + } + + /* Get the IRQ of the DIU */ + data->irq = irq_of_parse_and_map(np, 0); + + if (!data->irq) { + dev_err(&pdev->dev, "could not get DIU IRQ\n"); + ret = -EINVAL; + goto error; + } + data->monitor_port = monitor_port; + + /* Initialize the dummy Area Descriptor */ + data->dummy_ad.addr = cpu_to_le32(DMA_ADDR(data, dummy_aoi)); + data->dummy_ad.pix_fmt = 0x88882317; + data->dummy_ad.src_size_g_alpha = cpu_to_le32((4 << 12) | 4); + data->dummy_ad.aoi_size = cpu_to_le32((4 << 16) | 2); + data->dummy_ad.offset_xyi = 0; + data->dummy_ad.offset_xyd = 0; + data->dummy_ad.next_ad = 0; + data->dummy_ad.paddr = DMA_ADDR(data, dummy_ad); + + /* + * Let DIU continue to display splash screen if it was pre-initialized + * by the bootloader; otherwise, clear the display. + */ + if (in_be32(&data->diu_reg->diu_mode) == MFB_MODE0) + out_be32(&data->diu_reg->desc[0], 0); + + out_be32(&data->diu_reg->desc[1], data->dummy_ad.paddr); + out_be32(&data->diu_reg->desc[2], data->dummy_ad.paddr); + + /* + * Older versions of U-Boot leave interrupts enabled, so disable + * all of them and clear the status register. + */ + out_be32(&data->diu_reg->int_mask, 0xffffffff); + in_be32(&data->diu_reg->int_status); + + ret = request_irq(data->irq, fsl_diu_isr, 0, "fsl-diu-fb", + data->diu_reg); + if (ret) { + dev_err(&pdev->dev, "could not claim irq\n"); + goto error; + } + + for (i = 0; i < NUM_AOIS; i++) { + ret = install_fb(&data->fsl_diu_info[i]); + if (ret) { + dev_err(&pdev->dev, "could not register fb %d\n", i); + free_irq(data->irq, data->diu_reg); + goto error; + } + } + + sysfs_attr_init(&data->dev_attr.attr); + data->dev_attr.attr.name = "monitor"; + data->dev_attr.attr.mode = S_IRUGO|S_IWUSR; + data->dev_attr.show = show_monitor; + data->dev_attr.store = store_monitor; + ret = device_create_file(&pdev->dev, &data->dev_attr); + if (ret) { + dev_err(&pdev->dev, "could not create sysfs file %s\n", + data->dev_attr.attr.name); + } + + dev_set_drvdata(&pdev->dev, data); + return 0; + +error: + for (i = 0; i < NUM_AOIS; i++) + uninstall_fb(&data->fsl_diu_info[i]); + + iounmap(data->diu_reg); + + return ret; +} + +static int fsl_diu_remove(struct platform_device *pdev) +{ + struct fsl_diu_data *data; + int i; + + data = dev_get_drvdata(&pdev->dev); + disable_lcdc(&data->fsl_diu_info[0]); + + free_irq(data->irq, data->diu_reg); + + for (i = 0; i < NUM_AOIS; i++) + uninstall_fb(&data->fsl_diu_info[i]); + + iounmap(data->diu_reg); + + return 0; +} + +#ifndef MODULE +static int __init fsl_diu_setup(char *options) +{ + char *opt; + unsigned long val; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "monitor=", 8)) { + monitor_port = fsl_diu_name_to_port(opt + 8); + } else if (!strncmp(opt, "bpp=", 4)) { + if (!kstrtoul(opt + 4, 10, &val)) + default_bpp = val; + } else + fb_mode = opt; + } + + return 0; +} +#endif + +static struct of_device_id fsl_diu_match[] = { +#ifdef CONFIG_PPC_MPC512x + { + .compatible = "fsl,mpc5121-diu", + }, +#endif + { + .compatible = "fsl,diu", + }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_diu_match); + +static struct platform_driver fsl_diu_driver = { + .driver = { + .name = "fsl-diu-fb", + .owner = THIS_MODULE, + .of_match_table = fsl_diu_match, + }, + .probe = fsl_diu_probe, + .remove = fsl_diu_remove, + .suspend = fsl_diu_suspend, + .resume = fsl_diu_resume, +}; + +static int __init fsl_diu_init(void) +{ +#ifdef CONFIG_NOT_COHERENT_CACHE + struct device_node *np; + const u32 *prop; +#endif + int ret; +#ifndef MODULE + char *option; + + /* + * For kernel boot options (in 'video=xxxfb:<options>' format) + */ + if (fb_get_options("fslfb", &option)) + return -ENODEV; + fsl_diu_setup(option); +#else + monitor_port = fsl_diu_name_to_port(monitor_string); +#endif + pr_info("Freescale Display Interface Unit (DIU) framebuffer driver\n"); + +#ifdef CONFIG_NOT_COHERENT_CACHE + np = of_find_node_by_type(NULL, "cpu"); + if (!np) { + pr_err("fsl-diu-fb: can't find 'cpu' device node\n"); + return -ENODEV; + } + + prop = of_get_property(np, "d-cache-size", NULL); + if (prop == NULL) { + pr_err("fsl-diu-fb: missing 'd-cache-size' property' " + "in 'cpu' node\n"); + of_node_put(np); + return -ENODEV; + } + + /* + * Freescale PLRU requires 13/8 times the cache size to do a proper + * displacement flush + */ + coherence_data_size = be32_to_cpup(prop) * 13; + coherence_data_size /= 8; + + pr_debug("fsl-diu-fb: coherence data size is %zu bytes\n", + coherence_data_size); + + prop = of_get_property(np, "d-cache-line-size", NULL); + if (prop == NULL) { + pr_err("fsl-diu-fb: missing 'd-cache-line-size' property' " + "in 'cpu' node\n"); + of_node_put(np); + return -ENODEV; + } + d_cache_line_size = be32_to_cpup(prop); + + pr_debug("fsl-diu-fb: cache lines size is %u bytes\n", + d_cache_line_size); + + of_node_put(np); + coherence_data = vmalloc(coherence_data_size); + if (!coherence_data) { + pr_err("fsl-diu-fb: could not allocate coherence data " + "(size=%zu)\n", coherence_data_size); + return -ENOMEM; + } + +#endif + + ret = platform_driver_register(&fsl_diu_driver); + if (ret) { + pr_err("fsl-diu-fb: failed to register platform driver\n"); +#if defined(CONFIG_NOT_COHERENT_CACHE) + vfree(coherence_data); +#endif + } + return ret; +} + +static void __exit fsl_diu_exit(void) +{ + platform_driver_unregister(&fsl_diu_driver); +#if defined(CONFIG_NOT_COHERENT_CACHE) + vfree(coherence_data); +#endif +} + +module_init(fsl_diu_init); +module_exit(fsl_diu_exit); + +MODULE_AUTHOR("York Sun <yorksun@freescale.com>"); +MODULE_DESCRIPTION("Freescale DIU framebuffer driver"); +MODULE_LICENSE("GPL"); + +module_param_named(mode, fb_mode, charp, 0); +MODULE_PARM_DESC(mode, + "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); +module_param_named(bpp, default_bpp, ulong, 0); +MODULE_PARM_DESC(bpp, "Specify bit-per-pixel if not specified in 'mode'"); +module_param_named(monitor, monitor_string, charp, 0); +MODULE_PARM_DESC(monitor, "Specify the monitor port " + "(\"dvi\", \"lvds\", or \"dlvds\") if supported by the platform"); + diff --git a/drivers/video/fbdev/g364fb.c b/drivers/video/fbdev/g364fb.c new file mode 100644 index 000000000000..223896cc5f7d --- /dev/null +++ b/drivers/video/fbdev/g364fb.c @@ -0,0 +1,255 @@ +/* $Id: g364fb.c,v 1.3 1998/08/28 22:43:00 tsbogend Exp $ + * + * linux/drivers/video/g364fb.c -- Mips Magnum frame buffer device + * + * (C) 1998 Thomas Bogendoerfer + * + * This driver is based on tgafb.c + * + * Copyright (C) 1997 Geert Uytterhoeven + * Copyright (C) 1995 Jay Estabrook + * + * 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/console.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/jazz.h> + +/* + * Various defines for the G364 + */ +#define G364_MEM_BASE 0xe4400000 +#define G364_PORT_BASE 0xe4000000 +#define ID_REG 0xe4000000 /* Read only */ +#define BOOT_REG 0xe4080000 +#define TIMING_REG 0xe4080108 /* to 0x080170 - DON'T TOUCH! */ +#define DISPLAY_REG 0xe4080118 +#define VDISPLAY_REG 0xe4080150 +#define MASK_REG 0xe4080200 +#define CTLA_REG 0xe4080300 +#define CURS_TOGGLE 0x800000 +#define BIT_PER_PIX 0x700000 /* bits 22 to 20 of Control A */ +#define DELAY_SAMPLE 0x080000 +#define PORT_INTER 0x040000 +#define PIX_PIPE_DEL 0x030000 /* bits 17 and 16 of Control A */ +#define PIX_PIPE_DEL2 0x008000 /* same as above - don't ask me why */ +#define TR_CYCLE_TOG 0x004000 +#define VRAM_ADR_INC 0x003000 /* bits 13 and 12 of Control A */ +#define BLANK_OFF 0x000800 +#define FORCE_BLANK 0x000400 +#define BLK_FUN_SWTCH 0x000200 +#define BLANK_IO 0x000100 +#define BLANK_LEVEL 0x000080 +#define A_VID_FORM 0x000040 +#define D_SYNC_FORM 0x000020 +#define FRAME_FLY_PAT 0x000010 +#define OP_MODE 0x000008 +#define INTL_STAND 0x000004 +#define SCRN_FORM 0x000002 +#define ENABLE_VTG 0x000001 +#define TOP_REG 0xe4080400 +#define CURS_PAL_REG 0xe4080508 /* to 0x080518 */ +#define CHKSUM_REG 0xe4080600 /* to 0x080610 - unused */ +#define CURS_POS_REG 0xe4080638 +#define CLR_PAL_REG 0xe4080800 /* to 0x080ff8 */ +#define CURS_PAT_REG 0xe4081000 /* to 0x081ff8 */ +#define MON_ID_REG 0xe4100000 /* unused */ +#define RESET_REG 0xe4180000 /* Write only */ + +static struct fb_info fb_info; + +static struct fb_fix_screeninfo fb_fix __initdata = { + .id = "G364 8plane", + .smem_start = 0x40000000, /* physical address */ + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .ypanstep = 1, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo fb_var __initdata = { + .bits_per_pixel = 8, + .red = { 0, 8, 0 }, + .green = { 0, 8, 0 }, + .blue = { 0, 8, 0 }, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .pixclock = 39722, + .left_margin = 40, + .right_margin = 24, + .upper_margin = 32, + .lower_margin = 11, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +/* + * Interface used by the world + */ +int g364fb_init(void); + +static int g364fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int g364fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *info); +static int g364fb_cursor(struct fb_info *info, struct fb_cursor *cursor); +static int g364fb_blank(int blank, struct fb_info *info); + +static struct fb_ops g364fb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = g364fb_setcolreg, + .fb_pan_display = g364fb_pan_display, + .fb_blank = g364fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = g364fb_cursor, +}; + +int g364fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + + switch (cursor->enable) { + case CM_ERASE: + *(unsigned int *) CTLA_REG |= CURS_TOGGLE; + break; + + case CM_MOVE: + case CM_DRAW: + *(unsigned int *) CTLA_REG &= ~CURS_TOGGLE; + *(unsigned int *) CURS_POS_REG = + ((x * fontwidth(p)) << 12) | ((y * fontheight(p)) - + info->var.yoffset); + break; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ +static int g364fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->xoffset || + var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + *(unsigned int *) TOP_REG = var->yoffset * info->var.xres; + return 0; +} + +/* + * Blank the display. + */ +static int g364fb_blank(int blank, struct fb_info *info) +{ + if (blank) + *(unsigned int *) CTLA_REG |= FORCE_BLANK; + else + *(unsigned int *) CTLA_REG &= ~FORCE_BLANK; + return 0; +} + +/* + * Set a single color register. Return != 0 for invalid regno. + */ +static int g364fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + volatile unsigned int *ptr = (volatile unsigned int *) CLR_PAL_REG; + + if (regno > 255) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + ptr[regno << 1] = (red << 16) | (green << 8) | blue; + + return 0; +} + +/* + * Initialisation + */ +int __init g364fb_init(void) +{ + volatile unsigned int *pal_ptr = + (volatile unsigned int *) CLR_PAL_REG; + volatile unsigned int *curs_pal_ptr = + (volatile unsigned int *) CURS_PAL_REG; + int mem, i, j; + + if (fb_get_options("g364fb", NULL)) + return -ENODEV; + + /* TBD: G364 detection */ + + /* get the resolution set by ARC console */ + *(volatile unsigned int *) CTLA_REG &= ~ENABLE_VTG; + fb_var.xres = + (*((volatile unsigned int *) DISPLAY_REG) & 0x00ffffff) * 4; + fb_var.yres = + (*((volatile unsigned int *) VDISPLAY_REG) & 0x00ffffff) / 2; + *(volatile unsigned int *) CTLA_REG |= ENABLE_VTG; + + /* setup cursor */ + curs_pal_ptr[0] |= 0x00ffffff; + curs_pal_ptr[2] |= 0x00ffffff; + curs_pal_ptr[4] |= 0x00ffffff; + + /* + * first set the whole cursor to transparent + */ + for (i = 0; i < 512; i++) + *(unsigned short *) (CURS_PAT_REG + i * 8) = 0; + + /* + * switch the last two lines to cursor palette 3 + * we assume here, that FONTSIZE_X is 8 + */ + *(unsigned short *) (CURS_PAT_REG + 14 * 64) = 0xffff; + *(unsigned short *) (CURS_PAT_REG + 15 * 64) = 0xffff; + fb_var.xres_virtual = fbvar.xres; + fb_fix.line_length = (xres / 8) * fb_var.bits_per_pixel; + fb_fix.smem_start = 0x40000000; /* physical address */ + /* get size of video memory; this is special for the JAZZ hardware */ + mem = (r4030_read_reg32(JAZZ_R4030_CONFIG) >> 8) & 3; + fb_fix.smem_len = (1 << (mem * 2)) * 512 * 1024; + fb_var.yres_virtual = fb_fix.smem_len / fb_var.xres; + + fb_info.fbops = &g364fb_ops; + fb_info.screen_base = (char *) G364_MEM_BASE; /* virtual kernel address */ + fb_info.var = fb_var; + fb_info.fix = fb_fix; + fb_info.flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + fb_alloc_cmap(&fb_info.cmap, 255, 0); + + if (register_framebuffer(&fb_info) < 0) + return -EINVAL; + return 0; +} + +module_init(g364fb_init); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/gbefb.c b/drivers/video/fbdev/gbefb.c new file mode 100644 index 000000000000..3ec65a878ac8 --- /dev/null +++ b/drivers/video/fbdev/gbefb.c @@ -0,0 +1,1309 @@ +/* + * SGI GBE frame buffer driver + * + * Copyright (C) 1999 Silicon Graphics, Inc. - Jeffrey Newquist + * Copyright (C) 2002 Vivien Chappelier <vivien.chappelier@linux-mips.org> + * + * 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/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/gfp.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/io.h> + +#ifdef CONFIG_X86 +#include <asm/mtrr.h> +#endif +#ifdef CONFIG_MIPS +#include <asm/addrspace.h> +#endif +#include <asm/byteorder.h> +#include <asm/tlbflush.h> + +#include <video/gbe.h> + +static struct sgi_gbe *gbe; + +struct gbefb_par { + struct fb_var_screeninfo var; + struct gbe_timing_info timing; + int valid; +}; + +#ifdef CONFIG_SGI_IP32 +#define GBE_BASE 0x16000000 /* SGI O2 */ +#endif + +/* macro for fastest write-though access to the framebuffer */ +#ifdef CONFIG_MIPS +#ifdef CONFIG_CPU_R10000 +#define pgprot_fb(_prot) (((_prot) & (~_CACHE_MASK)) | _CACHE_UNCACHED_ACCELERATED) +#else +#define pgprot_fb(_prot) (((_prot) & (~_CACHE_MASK)) | _CACHE_CACHABLE_NO_WA) +#endif +#endif +#ifdef CONFIG_X86 +#define pgprot_fb(_prot) ((_prot) | _PAGE_PCD) +#endif + +/* + * RAM we reserve for the frame buffer. This defines the maximum screen + * size + */ +#if CONFIG_FB_GBE_MEM > 8 +#error GBE Framebuffer cannot use more than 8MB of memory +#endif + +#define TILE_SHIFT 16 +#define TILE_SIZE (1 << TILE_SHIFT) +#define TILE_MASK (TILE_SIZE - 1) + +static unsigned int gbe_mem_size = CONFIG_FB_GBE_MEM * 1024*1024; +static void *gbe_mem; +static dma_addr_t gbe_dma_addr; +static unsigned long gbe_mem_phys; + +static struct { + uint16_t *cpu; + dma_addr_t dma; +} gbe_tiles; + +static int gbe_revision; + +static int ypan, ywrap; + +static uint32_t pseudo_palette[16]; +static uint32_t gbe_cmap[256]; +static int gbe_turned_on; /* 0 turned off, 1 turned on */ + +static char *mode_option = NULL; + +/* default CRT mode */ +static struct fb_var_screeninfo default_var_CRT = { + /* 640x480, 60 Hz, Non-Interlaced (25.175 MHz dotclock) */ + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .xoffset = 0, + .yoffset = 0, + .bits_per_pixel = 8, + .grayscale = 0, + .red = { 0, 8, 0 }, + .green = { 0, 8, 0 }, + .blue = { 0, 8, 0 }, + .transp = { 0, 0, 0 }, + .nonstd = 0, + .activate = 0, + .height = -1, + .width = -1, + .accel_flags = 0, + .pixclock = 39722, /* picoseconds */ + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, +}; + +/* default LCD mode */ +static struct fb_var_screeninfo default_var_LCD = { + /* 1600x1024, 8 bpp */ + .xres = 1600, + .yres = 1024, + .xres_virtual = 1600, + .yres_virtual = 1024, + .xoffset = 0, + .yoffset = 0, + .bits_per_pixel = 8, + .grayscale = 0, + .red = { 0, 8, 0 }, + .green = { 0, 8, 0 }, + .blue = { 0, 8, 0 }, + .transp = { 0, 0, 0 }, + .nonstd = 0, + .activate = 0, + .height = -1, + .width = -1, + .accel_flags = 0, + .pixclock = 9353, + .left_margin = 20, + .right_margin = 30, + .upper_margin = 37, + .lower_margin = 3, + .hsync_len = 20, + .vsync_len = 3, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED +}; + +/* default modedb mode */ +/* 640x480, 60 Hz, Non-Interlaced (25.172 MHz dotclock) */ +static struct fb_videomode default_mode_CRT = { + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 39722, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, +}; +/* 1600x1024 SGI flatpanel 1600sw */ +static struct fb_videomode default_mode_LCD = { + /* 1600x1024, 8 bpp */ + .xres = 1600, + .yres = 1024, + .pixclock = 9353, + .left_margin = 20, + .right_margin = 30, + .upper_margin = 37, + .lower_margin = 3, + .hsync_len = 20, + .vsync_len = 3, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_videomode *default_mode = &default_mode_CRT; +static struct fb_var_screeninfo *default_var = &default_var_CRT; + +static int flat_panel_enabled = 0; + +static void gbe_reset(void) +{ + /* Turn on dotclock PLL */ + gbe->ctrlstat = 0x300aa000; +} + + +/* + * Function: gbe_turn_off + * Parameters: (None) + * Description: This should turn off the monitor and gbe. This is used + * when switching between the serial console and the graphics + * console. + */ + +static void gbe_turn_off(void) +{ + int i; + unsigned int val, x, y, vpixen_off; + + gbe_turned_on = 0; + + /* check if pixel counter is on */ + val = gbe->vt_xy; + if (GET_GBE_FIELD(VT_XY, FREEZE, val) == 1) + return; + + /* turn off DMA */ + val = gbe->ovr_control; + SET_GBE_FIELD(OVR_CONTROL, OVR_DMA_ENABLE, val, 0); + gbe->ovr_control = val; + udelay(1000); + val = gbe->frm_control; + SET_GBE_FIELD(FRM_CONTROL, FRM_DMA_ENABLE, val, 0); + gbe->frm_control = val; + udelay(1000); + val = gbe->did_control; + SET_GBE_FIELD(DID_CONTROL, DID_DMA_ENABLE, val, 0); + gbe->did_control = val; + udelay(1000); + + /* We have to wait through two vertical retrace periods before + * the pixel DMA is turned off for sure. */ + for (i = 0; i < 10000; i++) { + val = gbe->frm_inhwctrl; + if (GET_GBE_FIELD(FRM_INHWCTRL, FRM_DMA_ENABLE, val)) { + udelay(10); + } else { + val = gbe->ovr_inhwctrl; + if (GET_GBE_FIELD(OVR_INHWCTRL, OVR_DMA_ENABLE, val)) { + udelay(10); + } else { + val = gbe->did_inhwctrl; + if (GET_GBE_FIELD(DID_INHWCTRL, DID_DMA_ENABLE, val)) { + udelay(10); + } else + break; + } + } + } + if (i == 10000) + printk(KERN_ERR "gbefb: turn off DMA timed out\n"); + + /* wait for vpixen_off */ + val = gbe->vt_vpixen; + vpixen_off = GET_GBE_FIELD(VT_VPIXEN, VPIXEN_OFF, val); + + for (i = 0; i < 100000; i++) { + val = gbe->vt_xy; + x = GET_GBE_FIELD(VT_XY, X, val); + y = GET_GBE_FIELD(VT_XY, Y, val); + if (y < vpixen_off) + break; + udelay(1); + } + if (i == 100000) + printk(KERN_ERR + "gbefb: wait for vpixen_off timed out\n"); + for (i = 0; i < 10000; i++) { + val = gbe->vt_xy; + x = GET_GBE_FIELD(VT_XY, X, val); + y = GET_GBE_FIELD(VT_XY, Y, val); + if (y > vpixen_off) + break; + udelay(1); + } + if (i == 10000) + printk(KERN_ERR "gbefb: wait for vpixen_off timed out\n"); + + /* turn off pixel counter */ + val = 0; + SET_GBE_FIELD(VT_XY, FREEZE, val, 1); + gbe->vt_xy = val; + udelay(10000); + for (i = 0; i < 10000; i++) { + val = gbe->vt_xy; + if (GET_GBE_FIELD(VT_XY, FREEZE, val) != 1) + udelay(10); + else + break; + } + if (i == 10000) + printk(KERN_ERR "gbefb: turn off pixel clock timed out\n"); + + /* turn off dot clock */ + val = gbe->dotclock; + SET_GBE_FIELD(DOTCLK, RUN, val, 0); + gbe->dotclock = val; + udelay(10000); + for (i = 0; i < 10000; i++) { + val = gbe->dotclock; + if (GET_GBE_FIELD(DOTCLK, RUN, val)) + udelay(10); + else + break; + } + if (i == 10000) + printk(KERN_ERR "gbefb: turn off dotclock timed out\n"); + + /* reset the frame DMA FIFO */ + val = gbe->frm_size_tile; + SET_GBE_FIELD(FRM_SIZE_TILE, FRM_FIFO_RESET, val, 1); + gbe->frm_size_tile = val; + SET_GBE_FIELD(FRM_SIZE_TILE, FRM_FIFO_RESET, val, 0); + gbe->frm_size_tile = val; +} + +static void gbe_turn_on(void) +{ + unsigned int val, i; + + /* + * Check if pixel counter is off, for unknown reason this + * code hangs Visual Workstations + */ + if (gbe_revision < 2) { + val = gbe->vt_xy; + if (GET_GBE_FIELD(VT_XY, FREEZE, val) == 0) + return; + } + + /* turn on dot clock */ + val = gbe->dotclock; + SET_GBE_FIELD(DOTCLK, RUN, val, 1); + gbe->dotclock = val; + udelay(10000); + for (i = 0; i < 10000; i++) { + val = gbe->dotclock; + if (GET_GBE_FIELD(DOTCLK, RUN, val) != 1) + udelay(10); + else + break; + } + if (i == 10000) + printk(KERN_ERR "gbefb: turn on dotclock timed out\n"); + + /* turn on pixel counter */ + val = 0; + SET_GBE_FIELD(VT_XY, FREEZE, val, 0); + gbe->vt_xy = val; + udelay(10000); + for (i = 0; i < 10000; i++) { + val = gbe->vt_xy; + if (GET_GBE_FIELD(VT_XY, FREEZE, val)) + udelay(10); + else + break; + } + if (i == 10000) + printk(KERN_ERR "gbefb: turn on pixel clock timed out\n"); + + /* turn on DMA */ + val = gbe->frm_control; + SET_GBE_FIELD(FRM_CONTROL, FRM_DMA_ENABLE, val, 1); + gbe->frm_control = val; + udelay(1000); + for (i = 0; i < 10000; i++) { + val = gbe->frm_inhwctrl; + if (GET_GBE_FIELD(FRM_INHWCTRL, FRM_DMA_ENABLE, val) != 1) + udelay(10); + else + break; + } + if (i == 10000) + printk(KERN_ERR "gbefb: turn on DMA timed out\n"); + + gbe_turned_on = 1; +} + +static void gbe_loadcmap(void) +{ + int i, j; + + for (i = 0; i < 256; i++) { + for (j = 0; j < 1000 && gbe->cm_fifo >= 63; j++) + udelay(10); + if (j == 1000) + printk(KERN_ERR "gbefb: cmap FIFO timeout\n"); + + gbe->cmap[i] = gbe_cmap[i]; + } +} + +/* + * Blank the display. + */ +static int gbefb_blank(int blank, struct fb_info *info) +{ + /* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ + switch (blank) { + case FB_BLANK_UNBLANK: /* unblank */ + gbe_turn_on(); + gbe_loadcmap(); + break; + + case FB_BLANK_NORMAL: /* blank */ + gbe_turn_off(); + break; + + default: + /* Nothing */ + break; + } + return 0; +} + +/* + * Setup flatpanel related registers. + */ +static void gbefb_setup_flatpanel(struct gbe_timing_info *timing) +{ + int fp_wid, fp_hgt, fp_vbs, fp_vbe; + u32 outputVal = 0; + + SET_GBE_FIELD(VT_FLAGS, HDRV_INVERT, outputVal, + (timing->flags & FB_SYNC_HOR_HIGH_ACT) ? 0 : 1); + SET_GBE_FIELD(VT_FLAGS, VDRV_INVERT, outputVal, + (timing->flags & FB_SYNC_VERT_HIGH_ACT) ? 0 : 1); + gbe->vt_flags = outputVal; + + /* Turn on the flat panel */ + fp_wid = 1600; + fp_hgt = 1024; + fp_vbs = 0; + fp_vbe = 1600; + timing->pll_m = 4; + timing->pll_n = 1; + timing->pll_p = 0; + + outputVal = 0; + SET_GBE_FIELD(FP_DE, ON, outputVal, fp_vbs); + SET_GBE_FIELD(FP_DE, OFF, outputVal, fp_vbe); + gbe->fp_de = outputVal; + outputVal = 0; + SET_GBE_FIELD(FP_HDRV, OFF, outputVal, fp_wid); + gbe->fp_hdrv = outputVal; + outputVal = 0; + SET_GBE_FIELD(FP_VDRV, ON, outputVal, 1); + SET_GBE_FIELD(FP_VDRV, OFF, outputVal, fp_hgt + 1); + gbe->fp_vdrv = outputVal; +} + +struct gbe_pll_info { + int clock_rate; + int fvco_min; + int fvco_max; +}; + +static struct gbe_pll_info gbe_pll_table[2] = { + { 20, 80, 220 }, + { 27, 80, 220 }, +}; + +static int compute_gbe_timing(struct fb_var_screeninfo *var, + struct gbe_timing_info *timing) +{ + int pll_m, pll_n, pll_p, error, best_m, best_n, best_p, best_error; + int pixclock; + struct gbe_pll_info *gbe_pll; + + if (gbe_revision < 2) + gbe_pll = &gbe_pll_table[0]; + else + gbe_pll = &gbe_pll_table[1]; + + /* Determine valid resolution and timing + * GBE crystal runs at 20Mhz or 27Mhz + * pll_m, pll_n, pll_p define the following frequencies + * fvco = pll_m * 20Mhz / pll_n + * fout = fvco / (2**pll_p) */ + best_error = 1000000000; + best_n = best_m = best_p = 0; + for (pll_p = 0; pll_p < 4; pll_p++) + for (pll_m = 1; pll_m < 256; pll_m++) + for (pll_n = 1; pll_n < 64; pll_n++) { + pixclock = (1000000 / gbe_pll->clock_rate) * + (pll_n << pll_p) / pll_m; + + error = var->pixclock - pixclock; + + if (error < 0) + error = -error; + + if (error < best_error && + pll_m / pll_n > + gbe_pll->fvco_min / gbe_pll->clock_rate && + pll_m / pll_n < + gbe_pll->fvco_max / gbe_pll->clock_rate) { + best_error = error; + best_m = pll_m; + best_n = pll_n; + best_p = pll_p; + } + } + + if (!best_n || !best_m) + return -EINVAL; /* Resolution to high */ + + pixclock = (1000000 / gbe_pll->clock_rate) * + (best_n << best_p) / best_m; + + /* set video timing information */ + if (timing) { + timing->width = var->xres; + timing->height = var->yres; + timing->pll_m = best_m; + timing->pll_n = best_n; + timing->pll_p = best_p; + timing->cfreq = gbe_pll->clock_rate * 1000 * timing->pll_m / + (timing->pll_n << timing->pll_p); + timing->htotal = var->left_margin + var->xres + + var->right_margin + var->hsync_len; + timing->vtotal = var->upper_margin + var->yres + + var->lower_margin + var->vsync_len; + timing->fields_sec = 1000 * timing->cfreq / timing->htotal * + 1000 / timing->vtotal; + timing->hblank_start = var->xres; + timing->vblank_start = var->yres; + timing->hblank_end = timing->htotal; + timing->hsync_start = var->xres + var->right_margin + 1; + timing->hsync_end = timing->hsync_start + var->hsync_len; + timing->vblank_end = timing->vtotal; + timing->vsync_start = var->yres + var->lower_margin + 1; + timing->vsync_end = timing->vsync_start + var->vsync_len; + } + + return pixclock; +} + +static void gbe_set_timing_info(struct gbe_timing_info *timing) +{ + int temp; + unsigned int val; + + /* setup dot clock PLL */ + val = 0; + SET_GBE_FIELD(DOTCLK, M, val, timing->pll_m - 1); + SET_GBE_FIELD(DOTCLK, N, val, timing->pll_n - 1); + SET_GBE_FIELD(DOTCLK, P, val, timing->pll_p); + SET_GBE_FIELD(DOTCLK, RUN, val, 0); /* do not start yet */ + gbe->dotclock = val; + udelay(10000); + + /* setup pixel counter */ + val = 0; + SET_GBE_FIELD(VT_XYMAX, MAXX, val, timing->htotal); + SET_GBE_FIELD(VT_XYMAX, MAXY, val, timing->vtotal); + gbe->vt_xymax = val; + + /* setup video timing signals */ + val = 0; + SET_GBE_FIELD(VT_VSYNC, VSYNC_ON, val, timing->vsync_start); + SET_GBE_FIELD(VT_VSYNC, VSYNC_OFF, val, timing->vsync_end); + gbe->vt_vsync = val; + val = 0; + SET_GBE_FIELD(VT_HSYNC, HSYNC_ON, val, timing->hsync_start); + SET_GBE_FIELD(VT_HSYNC, HSYNC_OFF, val, timing->hsync_end); + gbe->vt_hsync = val; + val = 0; + SET_GBE_FIELD(VT_VBLANK, VBLANK_ON, val, timing->vblank_start); + SET_GBE_FIELD(VT_VBLANK, VBLANK_OFF, val, timing->vblank_end); + gbe->vt_vblank = val; + val = 0; + SET_GBE_FIELD(VT_HBLANK, HBLANK_ON, val, + timing->hblank_start - 5); + SET_GBE_FIELD(VT_HBLANK, HBLANK_OFF, val, + timing->hblank_end - 3); + gbe->vt_hblank = val; + + /* setup internal timing signals */ + val = 0; + SET_GBE_FIELD(VT_VCMAP, VCMAP_ON, val, timing->vblank_start); + SET_GBE_FIELD(VT_VCMAP, VCMAP_OFF, val, timing->vblank_end); + gbe->vt_vcmap = val; + val = 0; + SET_GBE_FIELD(VT_HCMAP, HCMAP_ON, val, timing->hblank_start); + SET_GBE_FIELD(VT_HCMAP, HCMAP_OFF, val, timing->hblank_end); + gbe->vt_hcmap = val; + + val = 0; + temp = timing->vblank_start - timing->vblank_end - 1; + if (temp > 0) + temp = -temp; + + if (flat_panel_enabled) + gbefb_setup_flatpanel(timing); + + SET_GBE_FIELD(DID_START_XY, DID_STARTY, val, (u32) temp); + if (timing->hblank_end >= 20) + SET_GBE_FIELD(DID_START_XY, DID_STARTX, val, + timing->hblank_end - 20); + else + SET_GBE_FIELD(DID_START_XY, DID_STARTX, val, + timing->htotal - (20 - timing->hblank_end)); + gbe->did_start_xy = val; + + val = 0; + SET_GBE_FIELD(CRS_START_XY, CRS_STARTY, val, (u32) (temp + 1)); + if (timing->hblank_end >= GBE_CRS_MAGIC) + SET_GBE_FIELD(CRS_START_XY, CRS_STARTX, val, + timing->hblank_end - GBE_CRS_MAGIC); + else + SET_GBE_FIELD(CRS_START_XY, CRS_STARTX, val, + timing->htotal - (GBE_CRS_MAGIC - + timing->hblank_end)); + gbe->crs_start_xy = val; + + val = 0; + SET_GBE_FIELD(VC_START_XY, VC_STARTY, val, (u32) temp); + SET_GBE_FIELD(VC_START_XY, VC_STARTX, val, timing->hblank_end - 4); + gbe->vc_start_xy = val; + + val = 0; + temp = timing->hblank_end - GBE_PIXEN_MAGIC_ON; + if (temp < 0) + temp += timing->htotal; /* allow blank to wrap around */ + + SET_GBE_FIELD(VT_HPIXEN, HPIXEN_ON, val, temp); + SET_GBE_FIELD(VT_HPIXEN, HPIXEN_OFF, val, + ((temp + timing->width - + GBE_PIXEN_MAGIC_OFF) % timing->htotal)); + gbe->vt_hpixen = val; + + val = 0; + SET_GBE_FIELD(VT_VPIXEN, VPIXEN_ON, val, timing->vblank_end); + SET_GBE_FIELD(VT_VPIXEN, VPIXEN_OFF, val, timing->vblank_start); + gbe->vt_vpixen = val; + + /* turn off sync on green */ + val = 0; + SET_GBE_FIELD(VT_FLAGS, SYNC_LOW, val, 1); + gbe->vt_flags = val; +} + +/* + * Set the hardware according to 'par'. + */ + +static int gbefb_set_par(struct fb_info *info) +{ + int i; + unsigned int val; + int wholeTilesX, partTilesX, maxPixelsPerTileX; + int height_pix; + int xpmax, ypmax; /* Monitor resolution */ + int bytesPerPixel; /* Bytes per pixel */ + struct gbefb_par *par = (struct gbefb_par *) info->par; + + compute_gbe_timing(&info->var, &par->timing); + + bytesPerPixel = info->var.bits_per_pixel / 8; + info->fix.line_length = info->var.xres_virtual * bytesPerPixel; + xpmax = par->timing.width; + ypmax = par->timing.height; + + /* turn off GBE */ + gbe_turn_off(); + + /* set timing info */ + gbe_set_timing_info(&par->timing); + + /* initialize DIDs */ + val = 0; + switch (bytesPerPixel) { + case 1: + SET_GBE_FIELD(WID, TYP, val, GBE_CMODE_I8); + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + case 2: + SET_GBE_FIELD(WID, TYP, val, GBE_CMODE_ARGB5); + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + case 4: + SET_GBE_FIELD(WID, TYP, val, GBE_CMODE_RGB8); + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + } + SET_GBE_FIELD(WID, BUF, val, GBE_BMODE_BOTH); + + for (i = 0; i < 32; i++) + gbe->mode_regs[i] = val; + + /* Initialize interrupts */ + gbe->vt_intr01 = 0xffffffff; + gbe->vt_intr23 = 0xffffffff; + + /* HACK: + The GBE hardware uses a tiled memory to screen mapping. Tiles are + blocks of 512x128, 256x128 or 128x128 pixels, respectively for 8bit, + 16bit and 32 bit modes (64 kB). They cover the screen with partial + tiles on the right and/or bottom of the screen if needed. + For example in 640x480 8 bit mode the mapping is: + + <-------- 640 -----> + <---- 512 ----><128|384 offscreen> + ^ ^ + | 128 [tile 0] [tile 1] + | v + ^ + 4 128 [tile 2] [tile 3] + 8 v + 0 ^ + 128 [tile 4] [tile 5] + | v + | ^ + v 96 [tile 6] [tile 7] + 32 offscreen + + Tiles have the advantage that they can be allocated individually in + memory. However, this mapping is not linear at all, which is not + really convenient. In order to support linear addressing, the GBE + DMA hardware is fooled into thinking the screen is only one tile + large and but has a greater height, so that the DMA transfer covers + the same region. + Tiles are still allocated as independent chunks of 64KB of + continuous physical memory and remapped so that the kernel sees the + framebuffer as a continuous virtual memory. The GBE tile table is + set up so that each tile references one of these 64k blocks: + + GBE -> tile list framebuffer TLB <------------ CPU + [ tile 0 ] -> [ 64KB ] <- [ 16x 4KB page entries ] ^ + ... ... ... linear virtual FB + [ tile n ] -> [ 64KB ] <- [ 16x 4KB page entries ] v + + + The GBE hardware is then told that the buffer is 512*tweaked_height, + with tweaked_height = real_width*real_height/pixels_per_tile. + Thus the GBE hardware will scan the first tile, filing the first 64k + covered region of the screen, and then will proceed to the next + tile, until the whole screen is covered. + + Here is what would happen at 640x480 8bit: + + normal tiling linear + ^ 11111111111111112222 11111111111111111111 ^ + 128 11111111111111112222 11111111111111111111 102 lines + 11111111111111112222 11111111111111111111 v + V 11111111111111112222 11111111222222222222 + 33333333333333334444 22222222222222222222 + 33333333333333334444 22222222222222222222 + < 512 > < 256 > 102*640+256 = 64k + + NOTE: The only mode for which this is not working is 800x600 8bit, + as 800*600/512 = 937.5 which is not integer and thus causes + flickering. + I guess this is not so important as one can use 640x480 8bit or + 800x600 16bit anyway. + */ + + /* Tell gbe about the tiles table location */ + /* tile_ptr -> [ tile 1 ] -> FB mem */ + /* [ tile 2 ] -> FB mem */ + /* ... */ + val = 0; + SET_GBE_FIELD(FRM_CONTROL, FRM_TILE_PTR, val, gbe_tiles.dma >> 9); + SET_GBE_FIELD(FRM_CONTROL, FRM_DMA_ENABLE, val, 0); /* do not start */ + SET_GBE_FIELD(FRM_CONTROL, FRM_LINEAR, val, 0); + gbe->frm_control = val; + + maxPixelsPerTileX = 512 / bytesPerPixel; + wholeTilesX = 1; + partTilesX = 0; + + /* Initialize the framebuffer */ + val = 0; + SET_GBE_FIELD(FRM_SIZE_TILE, FRM_WIDTH_TILE, val, wholeTilesX); + SET_GBE_FIELD(FRM_SIZE_TILE, FRM_RHS, val, partTilesX); + + switch (bytesPerPixel) { + case 1: + SET_GBE_FIELD(FRM_SIZE_TILE, FRM_DEPTH, val, + GBE_FRM_DEPTH_8); + break; + case 2: + SET_GBE_FIELD(FRM_SIZE_TILE, FRM_DEPTH, val, + GBE_FRM_DEPTH_16); + break; + case 4: + SET_GBE_FIELD(FRM_SIZE_TILE, FRM_DEPTH, val, + GBE_FRM_DEPTH_32); + break; + } + gbe->frm_size_tile = val; + + /* compute tweaked height */ + height_pix = xpmax * ypmax / maxPixelsPerTileX; + + val = 0; + SET_GBE_FIELD(FRM_SIZE_PIXEL, FB_HEIGHT_PIX, val, height_pix); + gbe->frm_size_pixel = val; + + /* turn off DID and overlay DMA */ + gbe->did_control = 0; + gbe->ovr_width_tile = 0; + + /* Turn off mouse cursor */ + gbe->crs_ctl = 0; + + /* Turn on GBE */ + gbe_turn_on(); + + /* Initialize the gamma map */ + udelay(10); + for (i = 0; i < 256; i++) + gbe->gmap[i] = (i << 24) | (i << 16) | (i << 8); + + /* Initialize the color map */ + for (i = 0; i < 256; i++) + gbe_cmap[i] = (i << 8) | (i << 16) | (i << 24); + + gbe_loadcmap(); + + return 0; +} + +static void gbefb_encode_fix(struct fb_fix_screeninfo *fix, + struct fb_var_screeninfo *var) +{ + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + strcpy(fix->id, "SGI GBE"); + fix->smem_start = (unsigned long) gbe_mem; + fix->smem_len = gbe_mem_size; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + fix->accel = FB_ACCEL_NONE; + switch (var->bits_per_pixel) { + case 8: + fix->visual = FB_VISUAL_PSEUDOCOLOR; + break; + default: + fix->visual = FB_VISUAL_TRUECOLOR; + break; + } + fix->ywrapstep = 0; + fix->xpanstep = 0; + fix->ypanstep = 0; + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + fix->mmio_start = GBE_BASE; + fix->mmio_len = sizeof(struct sgi_gbe); +} + +/* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ + +static int gbefb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + int i; + + if (regno > 255) + return 1; + red >>= 8; + green >>= 8; + blue >>= 8; + + if (info->var.bits_per_pixel <= 8) { + gbe_cmap[regno] = (red << 24) | (green << 16) | (blue << 8); + if (gbe_turned_on) { + /* wait for the color map FIFO to have a free entry */ + for (i = 0; i < 1000 && gbe->cm_fifo >= 63; i++) + udelay(10); + if (i == 1000) { + printk(KERN_ERR "gbefb: cmap FIFO timeout\n"); + return 1; + } + gbe->cmap[regno] = gbe_cmap[regno]; + } + } else if (regno < 16) { + switch (info->var.bits_per_pixel) { + case 15: + case 16: + red >>= 3; + green >>= 3; + blue >>= 3; + pseudo_palette[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + break; + case 32: + pseudo_palette[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + break; + } + } + + return 0; +} + +/* + * Check video mode validity, eventually modify var to best match. + */ +static int gbefb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + unsigned int line_length; + struct gbe_timing_info timing; + int ret; + + /* Limit bpp to 8, 16, and 32 */ + 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; + else + return -EINVAL; + + /* Check the mode can be mapped linearly with the tile table trick. */ + /* This requires width x height x bytes/pixel be a multiple of 512 */ + if ((var->xres * var->yres * var->bits_per_pixel) & 4095) + return -EINVAL; + + var->grayscale = 0; /* No grayscale for now */ + + ret = compute_gbe_timing(var, &timing); + var->pixclock = ret; + if (ret < 0) + return -EINVAL; + + /* Adjust virtual resolution, if necessary */ + if (var->xres > var->xres_virtual || (!ywrap && !ypan)) + var->xres_virtual = var->xres; + if (var->yres > var->yres_virtual || (!ywrap && !ypan)) + var->yres_virtual = var->yres; + + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = info->var.xoffset; + var->yoffset = info->var.yoffset; + } + + /* No grayscale for now */ + var->grayscale = 0; + + /* Memory limit */ + line_length = var->xres_virtual * var->bits_per_pixel / 8; + if (line_length * var->yres_virtual > gbe_mem_size) + return -ENOMEM; /* Virtual resolution too high */ + + switch (var->bits_per_pixel) { + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGB 1555 */ + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: /* RGB 8888 */ + var->red.offset = 24; + var->red.length = 8; + var->green.offset = 16; + var->green.length = 8; + var->blue.offset = 8; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 8; + break; + } + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + var->left_margin = timing.htotal - timing.hsync_end; + var->right_margin = timing.hsync_start - timing.width; + var->upper_margin = timing.vtotal - timing.vsync_end; + var->lower_margin = timing.vsync_start - timing.height; + var->hsync_len = timing.hsync_end - timing.hsync_start; + var->vsync_len = timing.vsync_end - timing.vsync_start; + + return 0; +} + +static int gbefb_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; + unsigned long addr; + unsigned long phys_addr, phys_size; + u16 *tile; + + /* check range */ + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + if (size > gbe_mem_size) + return -EINVAL; + if (offset > gbe_mem_size - size) + return -EINVAL; + + /* remap using the fastest write-through mode on architecture */ + /* try not polluting the cache when possible */ + pgprot_val(vma->vm_page_prot) = + pgprot_fb(pgprot_val(vma->vm_page_prot)); + + /* VM_IO | VM_DONTEXPAND | VM_DONTDUMP are set by remap_pfn_range() */ + + /* look for the starting tile */ + tile = &gbe_tiles.cpu[offset >> TILE_SHIFT]; + addr = vma->vm_start; + offset &= TILE_MASK; + + /* remap each tile separately */ + do { + phys_addr = (((unsigned long) (*tile)) << TILE_SHIFT) + offset; + if ((offset + size) < TILE_SIZE) + phys_size = size; + else + phys_size = TILE_SIZE - offset; + + if (remap_pfn_range(vma, addr, phys_addr >> PAGE_SHIFT, + phys_size, vma->vm_page_prot)) + return -EAGAIN; + + offset = 0; + size -= phys_size; + addr += phys_size; + tile++; + } while (size); + + return 0; +} + +static struct fb_ops gbefb_ops = { + .owner = THIS_MODULE, + .fb_check_var = gbefb_check_var, + .fb_set_par = gbefb_set_par, + .fb_setcolreg = gbefb_setcolreg, + .fb_mmap = gbefb_mmap, + .fb_blank = gbefb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* + * sysfs + */ + +static ssize_t gbefb_show_memsize(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gbe_mem_size); +} + +static DEVICE_ATTR(size, S_IRUGO, gbefb_show_memsize, NULL); + +static ssize_t gbefb_show_rev(struct device *device, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gbe_revision); +} + +static DEVICE_ATTR(revision, S_IRUGO, gbefb_show_rev, NULL); + +static void gbefb_remove_sysfs(struct device *dev) +{ + device_remove_file(dev, &dev_attr_size); + device_remove_file(dev, &dev_attr_revision); +} + +static void gbefb_create_sysfs(struct device *dev) +{ + device_create_file(dev, &dev_attr_size); + device_create_file(dev, &dev_attr_revision); +} + +/* + * Initialization + */ + +static int gbefb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "monitor:", 8)) { + if (!strncmp(this_opt + 8, "crt", 3)) { + flat_panel_enabled = 0; + default_var = &default_var_CRT; + default_mode = &default_mode_CRT; + } else if (!strncmp(this_opt + 8, "1600sw", 6) || + !strncmp(this_opt + 8, "lcd", 3)) { + flat_panel_enabled = 1; + default_var = &default_var_LCD; + default_mode = &default_mode_LCD; + } + } else if (!strncmp(this_opt, "mem:", 4)) { + gbe_mem_size = memparse(this_opt + 4, &this_opt); + if (gbe_mem_size > CONFIG_FB_GBE_MEM * 1024 * 1024) + gbe_mem_size = CONFIG_FB_GBE_MEM * 1024 * 1024; + if (gbe_mem_size < TILE_SIZE) + gbe_mem_size = TILE_SIZE; + } else + mode_option = this_opt; + } + return 0; +} + +static int gbefb_probe(struct platform_device *p_dev) +{ + int i, ret = 0; + struct fb_info *info; + struct gbefb_par *par; +#ifndef MODULE + char *options = NULL; +#endif + + info = framebuffer_alloc(sizeof(struct gbefb_par), &p_dev->dev); + if (!info) + return -ENOMEM; + +#ifndef MODULE + if (fb_get_options("gbefb", &options)) { + ret = -ENODEV; + goto out_release_framebuffer; + } + gbefb_setup(options); +#endif + + if (!request_mem_region(GBE_BASE, sizeof(struct sgi_gbe), "GBE")) { + printk(KERN_ERR "gbefb: couldn't reserve mmio region\n"); + ret = -EBUSY; + goto out_release_framebuffer; + } + + gbe = (struct sgi_gbe *) devm_ioremap(&p_dev->dev, GBE_BASE, + sizeof(struct sgi_gbe)); + if (!gbe) { + printk(KERN_ERR "gbefb: couldn't map mmio region\n"); + ret = -ENXIO; + goto out_release_mem_region; + } + gbe_revision = gbe->ctrlstat & 15; + + gbe_tiles.cpu = + dma_alloc_coherent(NULL, GBE_TLB_SIZE * sizeof(uint16_t), + &gbe_tiles.dma, GFP_KERNEL); + if (!gbe_tiles.cpu) { + printk(KERN_ERR "gbefb: couldn't allocate tiles table\n"); + ret = -ENOMEM; + goto out_release_mem_region; + } + + if (gbe_mem_phys) { + /* memory was allocated at boot time */ + gbe_mem = devm_ioremap_nocache(&p_dev->dev, gbe_mem_phys, + gbe_mem_size); + if (!gbe_mem) { + printk(KERN_ERR "gbefb: couldn't map framebuffer\n"); + ret = -ENOMEM; + goto out_tiles_free; + } + + gbe_dma_addr = 0; + } else { + /* try to allocate memory with the classical allocator + * this has high chance to fail on low memory machines */ + gbe_mem = dma_alloc_coherent(NULL, gbe_mem_size, &gbe_dma_addr, + GFP_KERNEL); + if (!gbe_mem) { + printk(KERN_ERR "gbefb: couldn't allocate framebuffer memory\n"); + ret = -ENOMEM; + goto out_tiles_free; + } + + gbe_mem_phys = (unsigned long) gbe_dma_addr; + } + +#ifdef CONFIG_X86 + mtrr_add(gbe_mem_phys, gbe_mem_size, MTRR_TYPE_WRCOMB, 1); +#endif + + /* map framebuffer memory into tiles table */ + for (i = 0; i < (gbe_mem_size >> TILE_SHIFT); i++) + gbe_tiles.cpu[i] = (gbe_mem_phys >> TILE_SHIFT) + i; + + info->fbops = &gbefb_ops; + info->pseudo_palette = pseudo_palette; + info->flags = FBINFO_DEFAULT; + info->screen_base = gbe_mem; + fb_alloc_cmap(&info->cmap, 256, 0); + + /* reset GBE */ + gbe_reset(); + + par = info->par; + /* turn on default video mode */ + if (fb_find_mode(&par->var, info, mode_option, NULL, 0, + default_mode, 8) == 0) + par->var = *default_var; + info->var = par->var; + gbefb_check_var(&par->var, info); + gbefb_encode_fix(&info->fix, &info->var); + + if (register_framebuffer(info) < 0) { + printk(KERN_ERR "gbefb: couldn't register framebuffer\n"); + ret = -ENXIO; + goto out_gbe_unmap; + } + + platform_set_drvdata(p_dev, info); + gbefb_create_sysfs(&p_dev->dev); + + fb_info(info, "%s rev %d @ 0x%08x using %dkB memory\n", + info->fix.id, gbe_revision, (unsigned)GBE_BASE, + gbe_mem_size >> 10); + + return 0; + +out_gbe_unmap: + if (gbe_dma_addr) + dma_free_coherent(NULL, gbe_mem_size, gbe_mem, gbe_mem_phys); +out_tiles_free: + dma_free_coherent(NULL, GBE_TLB_SIZE * sizeof(uint16_t), + (void *)gbe_tiles.cpu, gbe_tiles.dma); +out_release_mem_region: + release_mem_region(GBE_BASE, sizeof(struct sgi_gbe)); +out_release_framebuffer: + framebuffer_release(info); + + return ret; +} + +static int gbefb_remove(struct platform_device* p_dev) +{ + struct fb_info *info = platform_get_drvdata(p_dev); + + unregister_framebuffer(info); + gbe_turn_off(); + if (gbe_dma_addr) + dma_free_coherent(NULL, gbe_mem_size, gbe_mem, gbe_mem_phys); + dma_free_coherent(NULL, GBE_TLB_SIZE * sizeof(uint16_t), + (void *)gbe_tiles.cpu, gbe_tiles.dma); + release_mem_region(GBE_BASE, sizeof(struct sgi_gbe)); + gbefb_remove_sysfs(&p_dev->dev); + framebuffer_release(info); + + return 0; +} + +static struct platform_driver gbefb_driver = { + .probe = gbefb_probe, + .remove = gbefb_remove, + .driver = { + .name = "gbefb", + }, +}; + +static struct platform_device *gbefb_device; + +static int __init gbefb_init(void) +{ + int ret = platform_driver_register(&gbefb_driver); + if (!ret) { + gbefb_device = platform_device_alloc("gbefb", 0); + if (gbefb_device) { + ret = platform_device_add(gbefb_device); + } else { + ret = -ENOMEM; + } + if (ret) { + platform_device_put(gbefb_device); + platform_driver_unregister(&gbefb_driver); + } + } + return ret; +} + +static void __exit gbefb_exit(void) +{ + platform_device_unregister(gbefb_device); + platform_driver_unregister(&gbefb_driver); +} + +module_init(gbefb_init); +module_exit(gbefb_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/geode/Kconfig b/drivers/video/fbdev/geode/Kconfig new file mode 100644 index 000000000000..1e8555284786 --- /dev/null +++ b/drivers/video/fbdev/geode/Kconfig @@ -0,0 +1,54 @@ +# +# Geode family framebuffer configuration +# +config FB_GEODE + bool "AMD Geode family framebuffer support" + depends on FB && PCI && (X86_32 || (X86 && COMPILE_TEST)) + ---help--- + Say 'Y' here to allow you to select framebuffer drivers for + the AMD Geode family of processors. + +config FB_GEODE_LX + tristate "AMD Geode LX framebuffer support" + depends on FB && FB_GEODE + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Framebuffer driver for the display controller integrated into the + AMD Geode LX processors. + + To compile this driver as a module, choose M here: the module will + be called lxfb. + + If unsure, say N. + +config FB_GEODE_GX + tristate "AMD Geode GX framebuffer support" + depends on FB && FB_GEODE + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Framebuffer driver for the display controller integrated into the + AMD Geode GX processors. + + To compile this driver as a module, choose M here: the module will be + called gxfb. + + If unsure, say N. + +config FB_GEODE_GX1 + tristate "AMD Geode GX1 framebuffer support" + depends on FB && FB_GEODE + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Framebuffer driver for the display controller integrated into the + AMD Geode GX1 processor. + + To compile this driver as a module, choose M here: the module will be + called gx1fb. + + If unsure, say N. diff --git a/drivers/video/fbdev/geode/Makefile b/drivers/video/fbdev/geode/Makefile new file mode 100644 index 000000000000..5c98da126883 --- /dev/null +++ b/drivers/video/fbdev/geode/Makefile @@ -0,0 +1,9 @@ +# Makefile for the Geode family framebuffer drivers + +obj-$(CONFIG_FB_GEODE_GX1) += gx1fb.o +obj-$(CONFIG_FB_GEODE_GX) += gxfb.o +obj-$(CONFIG_FB_GEODE_LX) += lxfb.o + +gx1fb-objs := gx1fb_core.o display_gx1.o video_cs5530.o +gxfb-objs := gxfb_core.o display_gx.o video_gx.o suspend_gx.o +lxfb-objs := lxfb_core.o lxfb_ops.o diff --git a/drivers/video/fbdev/geode/display_gx.c b/drivers/video/fbdev/geode/display_gx.c new file mode 100644 index 000000000000..f0af911a096d --- /dev/null +++ b/drivers/video/fbdev/geode/display_gx.c @@ -0,0 +1,184 @@ +/* + * Geode GX display controller. + * + * Copyright (C) 2005 Arcom Control Systems Ltd. + * + * Portions from AMD's original 2.4 driver: + * Copyright (C) 2004 Advanced Micro Devices, 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. + */ +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/div64.h> +#include <asm/delay.h> +#include <linux/cs5535.h> + +#include "gxfb.h" + +unsigned int gx_frame_buffer_size(void) +{ + unsigned int val; + + if (!cs5535_has_vsa2()) { + uint32_t hi, lo; + + /* The number of pages is (PMAX - PMIN)+1 */ + rdmsr(MSR_GLIU_P2D_RO0, lo, hi); + + /* PMAX */ + val = ((hi & 0xff) << 12) | ((lo & 0xfff00000) >> 20); + /* PMIN */ + val -= (lo & 0x000fffff); + val += 1; + + /* The page size is 4k */ + return (val << 12); + } + + /* FB size can be obtained from the VSA II */ + /* Virtual register class = 0x02 */ + /* VG_MEM_SIZE(512Kb units) = 0x00 */ + + outw(VSA_VR_UNLOCK, VSA_VRC_INDEX); + outw(VSA_VR_MEM_SIZE, VSA_VRC_INDEX); + + val = (unsigned int)(inw(VSA_VRC_DATA)) & 0xFFl; + return (val << 19); +} + +int gx_line_delta(int xres, int bpp) +{ + /* Must be a multiple of 8 bytes. */ + return (xres * (bpp >> 3) + 7) & ~0x7; +} + +void gx_set_mode(struct fb_info *info) +{ + struct gxfb_par *par = info->par; + u32 gcfg, dcfg; + int hactive, hblankstart, hsyncstart, hsyncend, hblankend, htotal; + int vactive, vblankstart, vsyncstart, vsyncend, vblankend, vtotal; + + /* Unlock the display controller registers. */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + + gcfg = read_dc(par, DC_GENERAL_CFG); + dcfg = read_dc(par, DC_DISPLAY_CFG); + + /* Disable the timing generator. */ + dcfg &= ~DC_DISPLAY_CFG_TGEN; + write_dc(par, DC_DISPLAY_CFG, dcfg); + + /* Wait for pending memory requests before disabling the FIFO load. */ + udelay(100); + + /* Disable FIFO load and compression. */ + gcfg &= ~(DC_GENERAL_CFG_DFLE | DC_GENERAL_CFG_CMPE | + DC_GENERAL_CFG_DECE); + write_dc(par, DC_GENERAL_CFG, gcfg); + + /* Setup DCLK and its divisor. */ + gx_set_dclk_frequency(info); + + /* + * Setup new mode. + */ + + /* Clear all unused feature bits. */ + gcfg &= DC_GENERAL_CFG_YUVM | DC_GENERAL_CFG_VDSE; + dcfg = 0; + + /* Set FIFO priority (default 6/5) and enable. */ + /* FIXME: increase fifo priority for 1280x1024 and higher modes? */ + gcfg |= (6 << DC_GENERAL_CFG_DFHPEL_SHIFT) | + (5 << DC_GENERAL_CFG_DFHPSL_SHIFT) | DC_GENERAL_CFG_DFLE; + + /* Framebuffer start offset. */ + write_dc(par, DC_FB_ST_OFFSET, 0); + + /* Line delta and line buffer length. */ + write_dc(par, DC_GFX_PITCH, info->fix.line_length >> 3); + write_dc(par, DC_LINE_SIZE, + ((info->var.xres * info->var.bits_per_pixel/8) >> 3) + 2); + + + /* Enable graphics and video data and unmask address lines. */ + dcfg |= DC_DISPLAY_CFG_GDEN | DC_DISPLAY_CFG_VDEN | + DC_DISPLAY_CFG_A20M | DC_DISPLAY_CFG_A18M; + + /* Set pixel format. */ + switch (info->var.bits_per_pixel) { + case 8: + dcfg |= DC_DISPLAY_CFG_DISP_MODE_8BPP; + break; + case 16: + dcfg |= DC_DISPLAY_CFG_DISP_MODE_16BPP; + break; + case 32: + dcfg |= DC_DISPLAY_CFG_DISP_MODE_24BPP; + dcfg |= DC_DISPLAY_CFG_PALB; + break; + } + + /* Enable timing generator. */ + dcfg |= DC_DISPLAY_CFG_TGEN; + + /* Horizontal and vertical timings. */ + hactive = info->var.xres; + hblankstart = hactive; + hsyncstart = hblankstart + info->var.right_margin; + hsyncend = hsyncstart + info->var.hsync_len; + hblankend = hsyncend + info->var.left_margin; + htotal = hblankend; + + vactive = info->var.yres; + vblankstart = vactive; + vsyncstart = vblankstart + info->var.lower_margin; + vsyncend = vsyncstart + info->var.vsync_len; + vblankend = vsyncend + info->var.upper_margin; + vtotal = vblankend; + + write_dc(par, DC_H_ACTIVE_TIMING, (hactive - 1) | + ((htotal - 1) << 16)); + write_dc(par, DC_H_BLANK_TIMING, (hblankstart - 1) | + ((hblankend - 1) << 16)); + write_dc(par, DC_H_SYNC_TIMING, (hsyncstart - 1) | + ((hsyncend - 1) << 16)); + + write_dc(par, DC_V_ACTIVE_TIMING, (vactive - 1) | + ((vtotal - 1) << 16)); + write_dc(par, DC_V_BLANK_TIMING, (vblankstart - 1) | + ((vblankend - 1) << 16)); + write_dc(par, DC_V_SYNC_TIMING, (vsyncstart - 1) | + ((vsyncend - 1) << 16)); + + /* Write final register values. */ + write_dc(par, DC_DISPLAY_CFG, dcfg); + write_dc(par, DC_GENERAL_CFG, gcfg); + + gx_configure_display(info); + + /* Relock display controller registers */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_LOCK); +} + +void gx_set_hw_palette_reg(struct fb_info *info, unsigned regno, + unsigned red, unsigned green, unsigned blue) +{ + struct gxfb_par *par = info->par; + int val; + + /* Hardware palette is in RGB 8-8-8 format. */ + val = (red << 8) & 0xff0000; + val |= (green) & 0x00ff00; + val |= (blue >> 8) & 0x0000ff; + + write_dc(par, DC_PAL_ADDRESS, regno); + write_dc(par, DC_PAL_DATA, val); +} diff --git a/drivers/video/fbdev/geode/display_gx1.c b/drivers/video/fbdev/geode/display_gx1.c new file mode 100644 index 000000000000..926d53eeb549 --- /dev/null +++ b/drivers/video/fbdev/geode/display_gx1.c @@ -0,0 +1,214 @@ +/* + * drivers/video/geode/display_gx1.c + * -- Geode GX1 display controller + * + * Copyright (C) 2005 Arcom Control Systems Ltd. + * + * Based on AMD's original 2.4 driver: + * Copyright (C) 2004 Advanced Micro Devices, 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. + */ +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/div64.h> +#include <asm/delay.h> + +#include "geodefb.h" +#include "display_gx1.h" + +static DEFINE_SPINLOCK(gx1_conf_reg_lock); + +static u8 gx1_read_conf_reg(u8 reg) +{ + u8 val, ccr3; + unsigned long flags; + + spin_lock_irqsave(&gx1_conf_reg_lock, flags); + + outb(CONFIG_CCR3, 0x22); + ccr3 = inb(0x23); + outb(CONFIG_CCR3, 0x22); + outb(ccr3 | CONFIG_CCR3_MAPEN, 0x23); + outb(reg, 0x22); + val = inb(0x23); + outb(CONFIG_CCR3, 0x22); + outb(ccr3, 0x23); + + spin_unlock_irqrestore(&gx1_conf_reg_lock, flags); + + return val; +} + +unsigned gx1_gx_base(void) +{ + return (gx1_read_conf_reg(CONFIG_GCR) & 0x03) << 30; +} + +int gx1_frame_buffer_size(void) +{ + void __iomem *mc_regs; + u32 bank_cfg; + int d; + unsigned dram_size = 0, fb_base; + + mc_regs = ioremap(gx1_gx_base() + 0x8400, 0x100); + if (!mc_regs) + return -ENOMEM; + + + /* Calculate the total size of both DIMM0 and DIMM1. */ + bank_cfg = readl(mc_regs + MC_BANK_CFG); + + for (d = 0; d < 2; d++) { + if ((bank_cfg & MC_BCFG_DIMM0_PG_SZ_MASK) != MC_BCFG_DIMM0_PG_SZ_NO_DIMM) + dram_size += 0x400000 << ((bank_cfg & MC_BCFG_DIMM0_SZ_MASK) >> 8); + bank_cfg >>= 16; /* look at DIMM1 next */ + } + + fb_base = (readl(mc_regs + MC_GBASE_ADD) & MC_GADD_GBADD_MASK) << 19; + + iounmap(mc_regs); + + return dram_size - fb_base; +} + +static void gx1_set_mode(struct fb_info *info) +{ + struct geodefb_par *par = info->par; + u32 gcfg, tcfg, ocfg, dclk_div, val; + int hactive, hblankstart, hsyncstart, hsyncend, hblankend, htotal; + int vactive, vblankstart, vsyncstart, vsyncend, vblankend, vtotal; + + /* Unlock the display controller registers. */ + readl(par->dc_regs + DC_UNLOCK); + writel(DC_UNLOCK_CODE, par->dc_regs + DC_UNLOCK); + + gcfg = readl(par->dc_regs + DC_GENERAL_CFG); + tcfg = readl(par->dc_regs + DC_TIMING_CFG); + + /* Blank the display and disable the timing generator. */ + tcfg &= ~(DC_TCFG_BLKE | DC_TCFG_TGEN); + writel(tcfg, par->dc_regs + DC_TIMING_CFG); + + /* Wait for pending memory requests before disabling the FIFO load. */ + udelay(100); + + /* Disable FIFO load and compression. */ + gcfg &= ~(DC_GCFG_DFLE | DC_GCFG_CMPE | DC_GCFG_DECE); + writel(gcfg, par->dc_regs + DC_GENERAL_CFG); + + /* Setup DCLK and its divisor. */ + gcfg &= ~DC_GCFG_DCLK_MASK; + writel(gcfg, par->dc_regs + DC_GENERAL_CFG); + + par->vid_ops->set_dclk(info); + + dclk_div = DC_GCFG_DCLK_DIV_1; /* FIXME: may need to divide DCLK by 2 sometimes? */ + gcfg |= dclk_div; + writel(gcfg, par->dc_regs + DC_GENERAL_CFG); + + /* Wait for the clock generatation to settle. This is needed since + * some of the register writes that follow require that clock to be + * present. */ + udelay(1000); /* FIXME: seems a little long */ + + /* + * Setup new mode. + */ + + /* Clear all unused feature bits. */ + gcfg = DC_GCFG_VRDY | dclk_div; + + /* Set FIFO priority (default 6/5) and enable. */ + /* FIXME: increase fifo priority for 1280x1024 modes? */ + gcfg |= (6 << DC_GCFG_DFHPEL_POS) | (5 << DC_GCFG_DFHPSL_POS) | DC_GCFG_DFLE; + + /* FIXME: Set pixel and line double bits if necessary. */ + + /* Framebuffer start offset. */ + writel(0, par->dc_regs + DC_FB_ST_OFFSET); + + /* Line delta and line buffer length. */ + writel(info->fix.line_length >> 2, par->dc_regs + DC_LINE_DELTA); + writel(((info->var.xres * info->var.bits_per_pixel/8) >> 3) + 2, + par->dc_regs + DC_BUF_SIZE); + + /* Output configuration. Enable panel data, set pixel format. */ + ocfg = DC_OCFG_PCKE | DC_OCFG_PDEL | DC_OCFG_PDEH; + if (info->var.bits_per_pixel == 8) ocfg |= DC_OCFG_8BPP; + + /* Enable timing generator, sync and FP data. */ + tcfg = DC_TCFG_FPPE | DC_TCFG_HSYE | DC_TCFG_VSYE | DC_TCFG_BLKE + | DC_TCFG_TGEN; + + /* Horizontal and vertical timings. */ + hactive = info->var.xres; + hblankstart = hactive; + hsyncstart = hblankstart + info->var.right_margin; + hsyncend = hsyncstart + info->var.hsync_len; + hblankend = hsyncend + info->var.left_margin; + htotal = hblankend; + + vactive = info->var.yres; + vblankstart = vactive; + vsyncstart = vblankstart + info->var.lower_margin; + vsyncend = vsyncstart + info->var.vsync_len; + vblankend = vsyncend + info->var.upper_margin; + vtotal = vblankend; + + val = (hactive - 1) | ((htotal - 1) << 16); + writel(val, par->dc_regs + DC_H_TIMING_1); + val = (hblankstart - 1) | ((hblankend - 1) << 16); + writel(val, par->dc_regs + DC_H_TIMING_2); + val = (hsyncstart - 1) | ((hsyncend - 1) << 16); + writel(val, par->dc_regs + DC_H_TIMING_3); + writel(val, par->dc_regs + DC_FP_H_TIMING); + val = (vactive - 1) | ((vtotal - 1) << 16); + writel(val, par->dc_regs + DC_V_TIMING_1); + val = (vblankstart - 1) | ((vblankend - 1) << 16); + writel(val, par->dc_regs + DC_V_TIMING_2); + val = (vsyncstart - 1) | ((vsyncend - 1) << 16); + writel(val, par->dc_regs + DC_V_TIMING_3); + val = (vsyncstart - 2) | ((vsyncend - 2) << 16); + writel(val, par->dc_regs + DC_FP_V_TIMING); + + /* Write final register values. */ + writel(ocfg, par->dc_regs + DC_OUTPUT_CFG); + writel(tcfg, par->dc_regs + DC_TIMING_CFG); + udelay(1000); /* delay after TIMING_CFG. FIXME: perhaps a little long */ + writel(gcfg, par->dc_regs + DC_GENERAL_CFG); + + par->vid_ops->configure_display(info); + + /* Relock display controller registers */ + writel(0, par->dc_regs + DC_UNLOCK); + + /* FIXME: write line_length and bpp to Graphics Pipeline GP_BLT_STATUS + * register. */ +} + +static void gx1_set_hw_palette_reg(struct fb_info *info, unsigned regno, + unsigned red, unsigned green, unsigned blue) +{ + struct geodefb_par *par = info->par; + int val; + + /* Hardware palette is in RGB 6-6-6 format. */ + val = (red << 2) & 0x3f000; + val |= (green >> 4) & 0x00fc0; + val |= (blue >> 10) & 0x0003f; + + writel(regno, par->dc_regs + DC_PAL_ADDRESS); + writel(val, par->dc_regs + DC_PAL_DATA); +} + +struct geode_dc_ops gx1_dc_ops = { + .set_mode = gx1_set_mode, + .set_palette_reg = gx1_set_hw_palette_reg, +}; diff --git a/drivers/video/fbdev/geode/display_gx1.h b/drivers/video/fbdev/geode/display_gx1.h new file mode 100644 index 000000000000..671c05558c79 --- /dev/null +++ b/drivers/video/fbdev/geode/display_gx1.h @@ -0,0 +1,154 @@ +/* + * drivers/video/geode/display_gx1.h + * -- Geode GX1 display controller + * + * Copyright (C) 2005 Arcom Control Systems Ltd. + * + * Based on AMD's original 2.4 driver: + * Copyright (C) 2004 Advanced Micro Devices, 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. + */ +#ifndef __DISPLAY_GX1_H__ +#define __DISPLAY_GX1_H__ + +unsigned gx1_gx_base(void); +int gx1_frame_buffer_size(void); + +extern struct geode_dc_ops gx1_dc_ops; + +/* GX1 configuration I/O registers */ + +#define CONFIG_CCR3 0xc3 +# define CONFIG_CCR3_MAPEN 0x10 +#define CONFIG_GCR 0xb8 + +/* Memory controller registers */ + +#define MC_BANK_CFG 0x08 +# define MC_BCFG_DIMM0_SZ_MASK 0x00000700 +# define MC_BCFG_DIMM0_PG_SZ_MASK 0x00000070 +# define MC_BCFG_DIMM0_PG_SZ_NO_DIMM 0x00000070 + +#define MC_GBASE_ADD 0x14 +# define MC_GADD_GBADD_MASK 0x000003ff + +/* Display controller registers */ + +#define DC_PAL_ADDRESS 0x70 +#define DC_PAL_DATA 0x74 + +#define DC_UNLOCK 0x00 +# define DC_UNLOCK_CODE 0x00004758 + +#define DC_GENERAL_CFG 0x04 +# define DC_GCFG_DFLE 0x00000001 +# define DC_GCFG_CURE 0x00000002 +# define DC_GCFG_VCLK_DIV 0x00000004 +# define DC_GCFG_PLNO 0x00000004 +# define DC_GCFG_PPC 0x00000008 +# define DC_GCFG_CMPE 0x00000010 +# define DC_GCFG_DECE 0x00000020 +# define DC_GCFG_DCLK_MASK 0x000000C0 +# define DC_GCFG_DCLK_DIV_1 0x00000080 +# define DC_GCFG_DFHPSL_MASK 0x00000F00 +# define DC_GCFG_DFHPSL_POS 8 +# define DC_GCFG_DFHPEL_MASK 0x0000F000 +# define DC_GCFG_DFHPEL_POS 12 +# define DC_GCFG_CIM_MASK 0x00030000 +# define DC_GCFG_CIM_POS 16 +# define DC_GCFG_FDTY 0x00040000 +# define DC_GCFG_RTPM 0x00080000 +# define DC_GCFG_DAC_RS_MASK 0x00700000 +# define DC_GCFG_DAC_RS_POS 20 +# define DC_GCFG_CKWR 0x00800000 +# define DC_GCFG_LDBL 0x01000000 +# define DC_GCFG_DIAG 0x02000000 +# define DC_GCFG_CH4S 0x04000000 +# define DC_GCFG_SSLC 0x08000000 +# define DC_GCFG_VIDE 0x10000000 +# define DC_GCFG_VRDY 0x20000000 +# define DC_GCFG_DPCK 0x40000000 +# define DC_GCFG_DDCK 0x80000000 + +#define DC_TIMING_CFG 0x08 +# define DC_TCFG_FPPE 0x00000001 +# define DC_TCFG_HSYE 0x00000002 +# define DC_TCFG_VSYE 0x00000004 +# define DC_TCFG_BLKE 0x00000008 +# define DC_TCFG_DDCK 0x00000010 +# define DC_TCFG_TGEN 0x00000020 +# define DC_TCFG_VIEN 0x00000040 +# define DC_TCFG_BLNK 0x00000080 +# define DC_TCFG_CHSP 0x00000100 +# define DC_TCFG_CVSP 0x00000200 +# define DC_TCFG_FHSP 0x00000400 +# define DC_TCFG_FVSP 0x00000800 +# define DC_TCFG_FCEN 0x00001000 +# define DC_TCFG_CDCE 0x00002000 +# define DC_TCFG_PLNR 0x00002000 +# define DC_TCFG_INTL 0x00004000 +# define DC_TCFG_PXDB 0x00008000 +# define DC_TCFG_BKRT 0x00010000 +# define DC_TCFG_PSD_MASK 0x000E0000 +# define DC_TCFG_PSD_POS 17 +# define DC_TCFG_DDCI 0x08000000 +# define DC_TCFG_SENS 0x10000000 +# define DC_TCFG_DNA 0x20000000 +# define DC_TCFG_VNA 0x40000000 +# define DC_TCFG_VINT 0x80000000 + +#define DC_OUTPUT_CFG 0x0C +# define DC_OCFG_8BPP 0x00000001 +# define DC_OCFG_555 0x00000002 +# define DC_OCFG_PCKE 0x00000004 +# define DC_OCFG_FRME 0x00000008 +# define DC_OCFG_DITE 0x00000010 +# define DC_OCFG_2PXE 0x00000020 +# define DC_OCFG_2XCK 0x00000040 +# define DC_OCFG_2IND 0x00000080 +# define DC_OCFG_34ADD 0x00000100 +# define DC_OCFG_FRMS 0x00000200 +# define DC_OCFG_CKSL 0x00000400 +# define DC_OCFG_PRMP 0x00000800 +# define DC_OCFG_PDEL 0x00001000 +# define DC_OCFG_PDEH 0x00002000 +# define DC_OCFG_CFRW 0x00004000 +# define DC_OCFG_DIAG 0x00008000 + +#define DC_FB_ST_OFFSET 0x10 +#define DC_CB_ST_OFFSET 0x14 +#define DC_CURS_ST_OFFSET 0x18 +#define DC_ICON_ST_OFFSET 0x1C +#define DC_VID_ST_OFFSET 0x20 +#define DC_LINE_DELTA 0x24 +#define DC_BUF_SIZE 0x28 + +#define DC_H_TIMING_1 0x30 +#define DC_H_TIMING_2 0x34 +#define DC_H_TIMING_3 0x38 +#define DC_FP_H_TIMING 0x3C + +#define DC_V_TIMING_1 0x40 +#define DC_V_TIMING_2 0x44 +#define DC_V_TIMING_3 0x48 +#define DC_FP_V_TIMING 0x4C + +#define DC_CURSOR_X 0x50 +#define DC_ICON_X 0x54 +#define DC_V_LINE_CNT 0x54 +#define DC_CURSOR_Y 0x58 +#define DC_ICON_Y 0x5C +#define DC_SS_LINE_CMP 0x5C +#define DC_CURSOR_COLOR 0x60 +#define DC_ICON_COLOR 0x64 +#define DC_BORDER_COLOR 0x68 +#define DC_PAL_ADDRESS 0x70 +#define DC_PAL_DATA 0x74 +#define DC_DFIFO_DIAG 0x78 +#define DC_CFIFO_DIAG 0x7C + +#endif /* !__DISPLAY_GX1_H__ */ diff --git a/drivers/video/fbdev/geode/geodefb.h b/drivers/video/fbdev/geode/geodefb.h new file mode 100644 index 000000000000..ae04820e0c57 --- /dev/null +++ b/drivers/video/fbdev/geode/geodefb.h @@ -0,0 +1,38 @@ +/* + * drivers/video/geode/geodefb.h + * -- Geode framebuffer driver + * + * Copyright (C) 2005 Arcom Control Systems Ltd. + * + * 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. + */ +#ifndef __GEODEFB_H__ +#define __GEODEFB_H__ + +struct geodefb_info; + +struct geode_dc_ops { + void (*set_mode)(struct fb_info *); + void (*set_palette_reg)(struct fb_info *, unsigned, unsigned, unsigned, unsigned); +}; + +struct geode_vid_ops { + void (*set_dclk)(struct fb_info *); + void (*configure_display)(struct fb_info *); + int (*blank_display)(struct fb_info *, int blank_mode); +}; + +struct geodefb_par { + int enable_crt; + int panel_x; /* dimensions of an attached flat panel, non-zero => enable panel */ + int panel_y; + void __iomem *dc_regs; + void __iomem *vid_regs; + struct geode_dc_ops *dc_ops; + struct geode_vid_ops *vid_ops; +}; + +#endif /* !__GEODEFB_H__ */ diff --git a/drivers/video/fbdev/geode/gx1fb_core.c b/drivers/video/fbdev/geode/gx1fb_core.c new file mode 100644 index 000000000000..2794ba11f332 --- /dev/null +++ b/drivers/video/fbdev/geode/gx1fb_core.c @@ -0,0 +1,476 @@ +/* + * drivers/video/geode/gx1fb_core.c + * -- Geode GX1 framebuffer driver + * + * Copyright (C) 2005 Arcom Control Systems Ltd. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> + +#include "geodefb.h" +#include "display_gx1.h" +#include "video_cs5530.h" + +static char mode_option[32] = "640x480-16@60"; +static int crt_option = 1; +static char panel_option[32] = ""; + +/* Modes relevant to the GX1 (taken from modedb.c) */ +static const struct fb_videomode gx1_modedb[] = { + /* 640x480-60 VESA */ + { NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 640x480-75 VESA */ + { NULL, 75, 640, 480, 31746, 120, 16, 16, 01, 64, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 640x480-85 VESA */ + { NULL, 85, 640, 480, 27777, 80, 56, 25, 01, 56, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 800x600-60 VESA */ + { NULL, 60, 800, 600, 25000, 88, 40, 23, 01, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 800x600-75 VESA */ + { NULL, 75, 800, 600, 20202, 160, 16, 21, 01, 80, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 800x600-85 VESA */ + { NULL, 85, 800, 600, 17761, 152, 32, 27, 01, 64, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1024x768-60 VESA */ + { NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1024x768-75 VESA */ + { NULL, 75, 1024, 768, 12690, 176, 16, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1024x768-85 VESA */ + { NULL, 85, 1024, 768, 10582, 208, 48, 36, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x960-60 VESA */ + { NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x960-85 VESA */ + { NULL, 85, 1280, 960, 6734, 224, 64, 47, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x1024-60 VESA */ + { NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x1024-75 VESA */ + { NULL, 75, 1280, 1024, 7407, 248, 16, 38, 1, 144, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x1024-85 VESA */ + { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, +}; + +static int gx1_line_delta(int xres, int bpp) +{ + int line_delta = xres * (bpp >> 3); + + if (line_delta > 2048) + line_delta = 4096; + else if (line_delta > 1024) + line_delta = 2048; + else + line_delta = 1024; + return line_delta; +} + +static int gx1fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct geodefb_par *par = info->par; + + /* Maximum resolution is 1280x1024. */ + if (var->xres > 1280 || var->yres > 1024) + return -EINVAL; + + if (par->panel_x && (var->xres > par->panel_x || var->yres > par->panel_y)) + return -EINVAL; + + /* Only 16 bpp and 8 bpp is supported by the hardware. */ + if (var->bits_per_pixel == 16) { + var->red.offset = 11; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 0; var->blue.length = 5; + var->transp.offset = 0; var->transp.length = 0; + } else if (var->bits_per_pixel == 8) { + var->red.offset = 0; var->red.length = 8; + var->green.offset = 0; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + } else + return -EINVAL; + + /* Enough video memory? */ + if (gx1_line_delta(var->xres, var->bits_per_pixel) * var->yres > info->fix.smem_len) + return -EINVAL; + + /* FIXME: Check timing parameters here? */ + + return 0; +} + +static int gx1fb_set_par(struct fb_info *info) +{ + struct geodefb_par *par = info->par; + + if (info->var.bits_per_pixel == 16) + info->fix.visual = FB_VISUAL_TRUECOLOR; + else + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = gx1_line_delta(info->var.xres, info->var.bits_per_pixel); + + par->dc_ops->set_mode(info); + + return 0; +} + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int gx1fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct geodefb_par *par = info->par; + + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 *pal = info->pseudo_palette; + u32 v; + + if (regno >= 16) + return -EINVAL; + + v = chan_to_field(red, &info->var.red); + v |= chan_to_field(green, &info->var.green); + v |= chan_to_field(blue, &info->var.blue); + + pal[regno] = v; + } else { + if (regno >= 256) + return -EINVAL; + + par->dc_ops->set_palette_reg(info, regno, red, green, blue); + } + + return 0; +} + +static int gx1fb_blank(int blank_mode, struct fb_info *info) +{ + struct geodefb_par *par = info->par; + + return par->vid_ops->blank_display(info, blank_mode); +} + +static int gx1fb_map_video_memory(struct fb_info *info, struct pci_dev *dev) +{ + struct geodefb_par *par = info->par; + unsigned gx_base; + int fb_len; + int ret; + + gx_base = gx1_gx_base(); + if (!gx_base) + return -ENODEV; + + ret = pci_enable_device(dev); + if (ret < 0) + return ret; + + ret = pci_request_region(dev, 0, "gx1fb (video)"); + if (ret < 0) + return ret; + par->vid_regs = pci_ioremap_bar(dev, 0); + if (!par->vid_regs) + return -ENOMEM; + + if (!request_mem_region(gx_base + 0x8300, 0x100, "gx1fb (display controller)")) + return -EBUSY; + par->dc_regs = ioremap(gx_base + 0x8300, 0x100); + if (!par->dc_regs) + return -ENOMEM; + + if ((fb_len = gx1_frame_buffer_size()) < 0) + return -ENOMEM; + info->fix.smem_start = gx_base + 0x800000; + info->fix.smem_len = fb_len; + info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); + if (!info->screen_base) + return -ENOMEM; + + dev_info(&dev->dev, "%d Kibyte of video memory at 0x%lx\n", + info->fix.smem_len / 1024, info->fix.smem_start); + + return 0; +} + +static int parse_panel_option(struct fb_info *info) +{ + struct geodefb_par *par = info->par; + + if (strcmp(panel_option, "") != 0) { + int x, y; + char *s; + x = simple_strtol(panel_option, &s, 10); + if (!x) + return -EINVAL; + y = simple_strtol(s + 1, NULL, 10); + if (!y) + return -EINVAL; + par->panel_x = x; + par->panel_y = y; + } + return 0; +} + +static struct fb_ops gx1fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = gx1fb_check_var, + .fb_set_par = gx1fb_set_par, + .fb_setcolreg = gx1fb_setcolreg, + .fb_blank = gx1fb_blank, + /* No HW acceleration for now. */ + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_info *gx1fb_init_fbinfo(struct device *dev) +{ + struct geodefb_par *par; + struct fb_info *info; + + /* Alloc enough space for the pseudo palette. */ + info = framebuffer_alloc(sizeof(struct geodefb_par) + sizeof(u32) * 16, dev); + if (!info) + return NULL; + + par = info->par; + + strcpy(info->fix.id, "GX1"); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.accel = FB_ACCEL_NONE; + + info->var.nonstd = 0; + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.accel_flags = 0; + info->var.vmode = FB_VMODE_NONINTERLACED; + + info->fbops = &gx1fb_ops; + info->flags = FBINFO_DEFAULT; + info->node = -1; + + info->pseudo_palette = (void *)par + sizeof(struct geodefb_par); + + info->var.grayscale = 0; + + /* CRT and panel options */ + par->enable_crt = crt_option; + if (parse_panel_option(info) < 0) + printk(KERN_WARNING "gx1fb: invalid 'panel' option -- disabling flat panel\n"); + if (!par->panel_x) + par->enable_crt = 1; /* fall back to CRT if no panel is specified */ + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + framebuffer_release(info); + return NULL; + } + return info; +} + +static int gx1fb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct geodefb_par *par; + struct fb_info *info; + int ret; + + info = gx1fb_init_fbinfo(&pdev->dev); + if (!info) + return -ENOMEM; + par = info->par; + + /* GX1 display controller and CS5530 video device */ + par->dc_ops = &gx1_dc_ops; + par->vid_ops = &cs5530_vid_ops; + + if ((ret = gx1fb_map_video_memory(info, pdev)) < 0) { + dev_err(&pdev->dev, "failed to map frame buffer or controller registers\n"); + goto err; + } + + ret = fb_find_mode(&info->var, info, mode_option, + gx1_modedb, ARRAY_SIZE(gx1_modedb), NULL, 16); + if (ret == 0 || ret == 4) { + dev_err(&pdev->dev, "could not find valid video mode\n"); + ret = -EINVAL; + goto err; + } + + /* Clear the frame buffer of garbage. */ + memset_io(info->screen_base, 0, info->fix.smem_len); + + gx1fb_check_var(&info->var, info); + gx1fb_set_par(info); + + if (register_framebuffer(info) < 0) { + ret = -EINVAL; + goto err; + } + pci_set_drvdata(pdev, info); + fb_info(info, "%s frame buffer device\n", info->fix.id); + return 0; + + err: + if (info->screen_base) { + iounmap(info->screen_base); + pci_release_region(pdev, 0); + } + if (par->vid_regs) { + iounmap(par->vid_regs); + pci_release_region(pdev, 1); + } + if (par->dc_regs) { + iounmap(par->dc_regs); + release_mem_region(gx1_gx_base() + 0x8300, 0x100); + } + + if (info) { + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + + return ret; +} + +static void gx1fb_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct geodefb_par *par = info->par; + + unregister_framebuffer(info); + + iounmap((void __iomem *)info->screen_base); + pci_release_region(pdev, 0); + + iounmap(par->vid_regs); + pci_release_region(pdev, 1); + + iounmap(par->dc_regs); + release_mem_region(gx1_gx_base() + 0x8300, 0x100); + + fb_dealloc_cmap(&info->cmap); + + framebuffer_release(info); +} + +#ifndef MODULE +static void __init gx1fb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return; + + while ((this_opt = strsep(&options, ","))) { + if (!*this_opt) + continue; + + if (!strncmp(this_opt, "mode:", 5)) + strlcpy(mode_option, this_opt + 5, sizeof(mode_option)); + else if (!strncmp(this_opt, "crt:", 4)) + crt_option = !!simple_strtoul(this_opt + 4, NULL, 0); + else if (!strncmp(this_opt, "panel:", 6)) + strlcpy(panel_option, this_opt + 6, sizeof(panel_option)); + else + strlcpy(mode_option, this_opt, sizeof(mode_option)); + } +} +#endif + +static struct pci_device_id gx1fb_id_table[] = { + { PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_VIDEO, + PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, + 0xff0000, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, gx1fb_id_table); + +static struct pci_driver gx1fb_driver = { + .name = "gx1fb", + .id_table = gx1fb_id_table, + .probe = gx1fb_probe, + .remove = gx1fb_remove, +}; + +static int __init gx1fb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("gx1fb", &option)) + return -ENODEV; + gx1fb_setup(option); +#endif + return pci_register_driver(&gx1fb_driver); +} + +static void gx1fb_cleanup(void) +{ + pci_unregister_driver(&gx1fb_driver); +} + +module_init(gx1fb_init); +module_exit(gx1fb_cleanup); + +module_param_string(mode, mode_option, sizeof(mode_option), 0444); +MODULE_PARM_DESC(mode, "video mode (<x>x<y>[-<bpp>][@<refr>])"); + +module_param_named(crt, crt_option, int, 0444); +MODULE_PARM_DESC(crt, "enable CRT output. 0 = off, 1 = on (default)"); + +module_param_string(panel, panel_option, sizeof(panel_option), 0444); +MODULE_PARM_DESC(panel, "size of attached flat panel (<x>x<y>)"); + +MODULE_DESCRIPTION("framebuffer driver for the AMD Geode GX1"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/geode/gxfb.h b/drivers/video/fbdev/geode/gxfb.h new file mode 100644 index 000000000000..d19e9378b0c0 --- /dev/null +++ b/drivers/video/fbdev/geode/gxfb.h @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2008 Andres Salomon <dilinger@debian.org> + * + * Geode GX2 header information + * + * 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. + */ +#ifndef _GXFB_H_ +#define _GXFB_H_ + +#include <linux/io.h> + +#define GP_REG_COUNT (0x50 / 4) +#define DC_REG_COUNT (0x90 / 4) +#define VP_REG_COUNT (0x138 / 8) +#define FP_REG_COUNT (0x68 / 8) + +#define DC_PAL_COUNT 0x104 + +struct gxfb_par { + int enable_crt; + void __iomem *dc_regs; + void __iomem *vid_regs; + void __iomem *gp_regs; +#ifdef CONFIG_PM + int powered_down; + + /* register state, for power management functionality */ + struct { + uint64_t padsel; + uint64_t dotpll; + } msr; + + uint32_t gp[GP_REG_COUNT]; + uint32_t dc[DC_REG_COUNT]; + uint64_t vp[VP_REG_COUNT]; + uint64_t fp[FP_REG_COUNT]; + + uint32_t pal[DC_PAL_COUNT]; +#endif +}; + +unsigned int gx_frame_buffer_size(void); +int gx_line_delta(int xres, int bpp); +void gx_set_mode(struct fb_info *info); +void gx_set_hw_palette_reg(struct fb_info *info, unsigned regno, + unsigned red, unsigned green, unsigned blue); + +void gx_set_dclk_frequency(struct fb_info *info); +void gx_configure_display(struct fb_info *info); +int gx_blank_display(struct fb_info *info, int blank_mode); + +#ifdef CONFIG_PM +int gx_powerdown(struct fb_info *info); +int gx_powerup(struct fb_info *info); +#endif + + +/* Graphics Processor registers (table 6-23 from the data book) */ +enum gp_registers { + GP_DST_OFFSET = 0, + GP_SRC_OFFSET, + GP_STRIDE, + GP_WID_HEIGHT, + + GP_SRC_COLOR_FG, + GP_SRC_COLOR_BG, + GP_PAT_COLOR_0, + GP_PAT_COLOR_1, + + GP_PAT_COLOR_2, + GP_PAT_COLOR_3, + GP_PAT_COLOR_4, + GP_PAT_COLOR_5, + + GP_PAT_DATA_0, + GP_PAT_DATA_1, + GP_RASTER_MODE, + GP_VECTOR_MODE, + + GP_BLT_MODE, + GP_BLT_STATUS, + GP_HST_SRC, + GP_BASE_OFFSET, /* 0x4c */ +}; + +#define GP_BLT_STATUS_BLT_PENDING (1 << 2) +#define GP_BLT_STATUS_BLT_BUSY (1 << 0) + + +/* Display Controller registers (table 6-38 from the data book) */ +enum dc_registers { + DC_UNLOCK = 0, + DC_GENERAL_CFG, + DC_DISPLAY_CFG, + DC_RSVD_0, + + DC_FB_ST_OFFSET, + DC_CB_ST_OFFSET, + DC_CURS_ST_OFFSET, + DC_ICON_ST_OFFSET, + + DC_VID_Y_ST_OFFSET, + DC_VID_U_ST_OFFSET, + DC_VID_V_ST_OFFSET, + DC_RSVD_1, + + DC_LINE_SIZE, + DC_GFX_PITCH, + DC_VID_YUV_PITCH, + DC_RSVD_2, + + DC_H_ACTIVE_TIMING, + DC_H_BLANK_TIMING, + DC_H_SYNC_TIMING, + DC_RSVD_3, + + DC_V_ACTIVE_TIMING, + DC_V_BLANK_TIMING, + DC_V_SYNC_TIMING, + DC_RSVD_4, + + DC_CURSOR_X, + DC_CURSOR_Y, + DC_ICON_X, + DC_LINE_CNT, + + DC_PAL_ADDRESS, + DC_PAL_DATA, + DC_DFIFO_DIAG, + DC_CFIFO_DIAG, + + DC_VID_DS_DELTA, + DC_GLIU0_MEM_OFFSET, + DC_RSVD_5, + DC_DV_ACC, /* 0x8c */ +}; + +#define DC_UNLOCK_LOCK 0x00000000 +#define DC_UNLOCK_UNLOCK 0x00004758 /* magic value */ + +#define DC_GENERAL_CFG_YUVM (1 << 20) +#define DC_GENERAL_CFG_VDSE (1 << 19) +#define DC_GENERAL_CFG_DFHPEL_SHIFT 12 +#define DC_GENERAL_CFG_DFHPSL_SHIFT 8 +#define DC_GENERAL_CFG_DECE (1 << 6) +#define DC_GENERAL_CFG_CMPE (1 << 5) +#define DC_GENERAL_CFG_VIDE (1 << 3) +#define DC_GENERAL_CFG_ICNE (1 << 2) +#define DC_GENERAL_CFG_CURE (1 << 1) +#define DC_GENERAL_CFG_DFLE (1 << 0) + +#define DC_DISPLAY_CFG_A20M (1 << 31) +#define DC_DISPLAY_CFG_A18M (1 << 30) +#define DC_DISPLAY_CFG_PALB (1 << 25) +#define DC_DISPLAY_CFG_DISP_MODE_24BPP (1 << 9) +#define DC_DISPLAY_CFG_DISP_MODE_16BPP (1 << 8) +#define DC_DISPLAY_CFG_DISP_MODE_8BPP (0) +#define DC_DISPLAY_CFG_VDEN (1 << 4) +#define DC_DISPLAY_CFG_GDEN (1 << 3) +#define DC_DISPLAY_CFG_TGEN (1 << 0) + + +/* + * Video Processor registers (table 6-54). + * There is space for 64 bit values, but we never use more than the + * lower 32 bits. The actual register save/restore code only bothers + * to restore those 32 bits. + */ +enum vp_registers { + VP_VCFG = 0, + VP_DCFG, + + VP_VX, + VP_VY, + + VP_VS, + VP_VCK, + + VP_VCM, + VP_GAR, + + VP_GDR, + VP_RSVD_0, + + VP_MISC, + VP_CCS, + + VP_RSVD_1, + VP_RSVD_2, + + VP_RSVD_3, + VP_VDC, + + VP_VCO, + VP_CRC, + + VP_CRC32, + VP_VDE, + + VP_CCK, + VP_CCM, + + VP_CC1, + VP_CC2, + + VP_A1X, + VP_A1Y, + + VP_A1C, + VP_A1T, + + VP_A2X, + VP_A2Y, + + VP_A2C, + VP_A2T, + + VP_A3X, + VP_A3Y, + + VP_A3C, + VP_A3T, + + VP_VRR, + VP_AWT, + + VP_VTM, /* 0x130 */ +}; + +#define VP_VCFG_VID_EN (1 << 0) + +#define VP_DCFG_DAC_VREF (1 << 26) +#define VP_DCFG_GV_GAM (1 << 21) +#define VP_DCFG_VG_CK (1 << 20) +#define VP_DCFG_CRT_SYNC_SKW_DEFAULT (1 << 16) +#define VP_DCFG_CRT_SYNC_SKW ((1 << 14) | (1 << 15) | (1 << 16)) +#define VP_DCFG_CRT_VSYNC_POL (1 << 9) +#define VP_DCFG_CRT_HSYNC_POL (1 << 8) +#define VP_DCFG_FP_DATA_EN (1 << 7) /* undocumented */ +#define VP_DCFG_FP_PWR_EN (1 << 6) /* undocumented */ +#define VP_DCFG_DAC_BL_EN (1 << 3) +#define VP_DCFG_VSYNC_EN (1 << 2) +#define VP_DCFG_HSYNC_EN (1 << 1) +#define VP_DCFG_CRT_EN (1 << 0) + +#define VP_MISC_GAM_EN (1 << 0) +#define VP_MISC_DACPWRDN (1 << 10) +#define VP_MISC_APWRDN (1 << 11) + + +/* + * Flat Panel registers (table 6-55). + * Also 64 bit registers; see above note about 32-bit handling. + */ + +/* we're actually in the VP register space, starting at address 0x400 */ +#define VP_FP_START 0x400 + +enum fp_registers { + FP_PT1 = 0, + FP_PT2, + + FP_PM, + FP_DFC, + + FP_BLFSR, + FP_RLFSR, + + FP_FMI, + FP_FMD, + + FP_RSVD_0, + FP_DCA, + + FP_DMD, + FP_CRC, + + FP_FBB, /* 0x460 */ +}; + +#define FP_PT1_VSIZE_SHIFT 16 /* undocumented? */ +#define FP_PT1_VSIZE_MASK 0x7FF0000 /* undocumented? */ + +#define FP_PT2_HSP (1 << 22) +#define FP_PT2_VSP (1 << 23) + +#define FP_PM_P (1 << 24) /* panel power on */ +#define FP_PM_PANEL_PWR_UP (1 << 3) /* r/o */ +#define FP_PM_PANEL_PWR_DOWN (1 << 2) /* r/o */ +#define FP_PM_PANEL_OFF (1 << 1) /* r/o */ +#define FP_PM_PANEL_ON (1 << 0) /* r/o */ + +#define FP_DFC_NFI ((1 << 4) | (1 << 5) | (1 << 6)) + + +/* register access functions */ + +static inline uint32_t read_gp(struct gxfb_par *par, int reg) +{ + return readl(par->gp_regs + 4*reg); +} + +static inline void write_gp(struct gxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->gp_regs + 4*reg); +} + +static inline uint32_t read_dc(struct gxfb_par *par, int reg) +{ + return readl(par->dc_regs + 4*reg); +} + +static inline void write_dc(struct gxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->dc_regs + 4*reg); +} + +static inline uint32_t read_vp(struct gxfb_par *par, int reg) +{ + return readl(par->vid_regs + 8*reg); +} + +static inline void write_vp(struct gxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->vid_regs + 8*reg); +} + +static inline uint32_t read_fp(struct gxfb_par *par, int reg) +{ + return readl(par->vid_regs + 8*reg + VP_FP_START); +} + +static inline void write_fp(struct gxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->vid_regs + 8*reg + VP_FP_START); +} + + +/* MSRs are defined in linux/cs5535.h; their bitfields are here */ + +#define MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3 (1 << 3) +#define MSR_GLCP_SYS_RSTPLL_DOTPREMULT2 (1 << 2) +#define MSR_GLCP_SYS_RSTPLL_DOTPREDIV2 (1 << 1) + +#define MSR_GLCP_DOTPLL_LOCK (1 << 25) /* r/o */ +#define MSR_GLCP_DOTPLL_BYPASS (1 << 15) +#define MSR_GLCP_DOTPLL_DOTRESET (1 << 0) + +#define MSR_GX_MSR_PADSEL_MASK 0x3FFFFFFF /* undocumented? */ +#define MSR_GX_MSR_PADSEL_TFT 0x1FFFFFFF /* undocumented? */ + +#define MSR_GX_GLD_MSR_CONFIG_FP (1 << 3) + +#endif diff --git a/drivers/video/fbdev/geode/gxfb_core.c b/drivers/video/fbdev/geode/gxfb_core.c new file mode 100644 index 000000000000..1790f14bab15 --- /dev/null +++ b/drivers/video/fbdev/geode/gxfb_core.c @@ -0,0 +1,547 @@ +/* + * Geode GX framebuffer driver. + * + * Copyright (C) 2006 Arcom Control Systems Ltd. + * + * 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 driver assumes that the BIOS has created a virtual PCI device header + * for the video device. The PCI header is assumed to contain the following + * BARs: + * + * BAR0 - framebuffer memory + * BAR1 - graphics processor registers + * BAR2 - display controller registers + * BAR3 - video processor and flat panel control registers. + * + * 16 MiB of framebuffer memory is assumed to be available. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/suspend.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/cs5535.h> + +#include "gxfb.h" + +static char *mode_option; +static int vram; +static int vt_switch; + +/* Modes relevant to the GX (taken from modedb.c) */ +static struct fb_videomode gx_modedb[] = { + /* 640x480-60 VESA */ + { NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 640x480-75 VESA */ + { NULL, 75, 640, 480, 31746, 120, 16, 16, 01, 64, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 640x480-85 VESA */ + { NULL, 85, 640, 480, 27777, 80, 56, 25, 01, 56, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 800x600-60 VESA */ + { NULL, 60, 800, 600, 25000, 88, 40, 23, 01, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 800x600-75 VESA */ + { NULL, 75, 800, 600, 20202, 160, 16, 21, 01, 80, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 800x600-85 VESA */ + { NULL, 85, 800, 600, 17761, 152, 32, 27, 01, 64, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1024x768-60 VESA */ + { NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1024x768-75 VESA */ + { NULL, 75, 1024, 768, 12690, 176, 16, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1024x768-85 VESA */ + { NULL, 85, 1024, 768, 10582, 208, 48, 36, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x960-60 VESA */ + { NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x960-85 VESA */ + { NULL, 85, 1280, 960, 6734, 224, 64, 47, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x1024-60 VESA */ + { NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x1024-75 VESA */ + { NULL, 75, 1280, 1024, 7407, 248, 16, 38, 1, 144, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1280x1024-85 VESA */ + { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1600x1200-60 VESA */ + { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1600x1200-75 VESA */ + { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1600x1200-85 VESA */ + { NULL, 85, 1600, 1200, 4357, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, +}; + +#ifdef CONFIG_OLPC +#include <asm/olpc.h> + +static struct fb_videomode gx_dcon_modedb[] = { + /* The only mode the DCON has is 1200x900 */ + { NULL, 50, 1200, 900, 17460, 24, 8, 4, 5, 8, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 } +}; + +static void get_modedb(struct fb_videomode **modedb, unsigned int *size) +{ + if (olpc_has_dcon()) { + *modedb = (struct fb_videomode *) gx_dcon_modedb; + *size = ARRAY_SIZE(gx_dcon_modedb); + } else { + *modedb = (struct fb_videomode *) gx_modedb; + *size = ARRAY_SIZE(gx_modedb); + } +} + +#else +static void get_modedb(struct fb_videomode **modedb, unsigned int *size) +{ + *modedb = (struct fb_videomode *) gx_modedb; + *size = ARRAY_SIZE(gx_modedb); +} +#endif + +static int gxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + if (var->xres > 1600 || var->yres > 1200) + return -EINVAL; + if ((var->xres > 1280 || var->yres > 1024) && var->bits_per_pixel > 16) + return -EINVAL; + + if (var->bits_per_pixel == 32) { + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + } else if (var->bits_per_pixel == 16) { + var->red.offset = 11; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 0; var->blue.length = 5; + } else if (var->bits_per_pixel == 8) { + var->red.offset = 0; var->red.length = 8; + var->green.offset = 0; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + } else + return -EINVAL; + var->transp.offset = 0; var->transp.length = 0; + + /* Enough video memory? */ + if (gx_line_delta(var->xres, var->bits_per_pixel) * var->yres > info->fix.smem_len) + return -EINVAL; + + /* FIXME: Check timing parameters here? */ + + return 0; +} + +static int gxfb_set_par(struct fb_info *info) +{ + if (info->var.bits_per_pixel > 8) + info->fix.visual = FB_VISUAL_TRUECOLOR; + else + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = gx_line_delta(info->var.xres, info->var.bits_per_pixel); + + gx_set_mode(info); + + return 0; +} + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int gxfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 *pal = info->pseudo_palette; + u32 v; + + if (regno >= 16) + return -EINVAL; + + v = chan_to_field(red, &info->var.red); + v |= chan_to_field(green, &info->var.green); + v |= chan_to_field(blue, &info->var.blue); + + pal[regno] = v; + } else { + if (regno >= 256) + return -EINVAL; + + gx_set_hw_palette_reg(info, regno, red, green, blue); + } + + return 0; +} + +static int gxfb_blank(int blank_mode, struct fb_info *info) +{ + return gx_blank_display(info, blank_mode); +} + +static int gxfb_map_video_memory(struct fb_info *info, struct pci_dev *dev) +{ + struct gxfb_par *par = info->par; + int ret; + + ret = pci_enable_device(dev); + if (ret < 0) + return ret; + + ret = pci_request_region(dev, 3, "gxfb (video processor)"); + if (ret < 0) + return ret; + par->vid_regs = pci_ioremap_bar(dev, 3); + if (!par->vid_regs) + return -ENOMEM; + + ret = pci_request_region(dev, 2, "gxfb (display controller)"); + if (ret < 0) + return ret; + par->dc_regs = pci_ioremap_bar(dev, 2); + if (!par->dc_regs) + return -ENOMEM; + + ret = pci_request_region(dev, 1, "gxfb (graphics processor)"); + if (ret < 0) + return ret; + par->gp_regs = pci_ioremap_bar(dev, 1); + + if (!par->gp_regs) + return -ENOMEM; + + ret = pci_request_region(dev, 0, "gxfb (framebuffer)"); + if (ret < 0) + return ret; + + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = vram ? vram : gx_frame_buffer_size(); + info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); + if (!info->screen_base) + return -ENOMEM; + + /* Set the 16MiB aligned base address of the graphics memory region + * in the display controller */ + + write_dc(par, DC_GLIU0_MEM_OFFSET, info->fix.smem_start & 0xFF000000); + + dev_info(&dev->dev, "%d KiB of video memory at 0x%lx\n", + info->fix.smem_len / 1024, info->fix.smem_start); + + return 0; +} + +static struct fb_ops gxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = gxfb_check_var, + .fb_set_par = gxfb_set_par, + .fb_setcolreg = gxfb_setcolreg, + .fb_blank = gxfb_blank, + /* No HW acceleration for now. */ + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_info *gxfb_init_fbinfo(struct device *dev) +{ + struct gxfb_par *par; + struct fb_info *info; + + /* Alloc enough space for the pseudo palette. */ + info = framebuffer_alloc(sizeof(struct gxfb_par) + sizeof(u32) * 16, + dev); + if (!info) + return NULL; + + par = info->par; + + strcpy(info->fix.id, "Geode GX"); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.accel = FB_ACCEL_NONE; + + info->var.nonstd = 0; + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.accel_flags = 0; + info->var.vmode = FB_VMODE_NONINTERLACED; + + info->fbops = &gxfb_ops; + info->flags = FBINFO_DEFAULT; + info->node = -1; + + info->pseudo_palette = (void *)par + sizeof(struct gxfb_par); + + info->var.grayscale = 0; + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + framebuffer_release(info); + return NULL; + } + + return info; +} + +#ifdef CONFIG_PM +static int gxfb_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(pdev); + + if (state.event == PM_EVENT_SUSPEND) { + console_lock(); + gx_powerdown(info); + fb_set_suspend(info, 1); + console_unlock(); + } + + /* there's no point in setting PCI states; we emulate PCI, so + * we don't end up getting power savings anyways */ + + return 0; +} + +static int gxfb_resume(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + int ret; + + console_lock(); + ret = gx_powerup(info); + if (ret) { + printk(KERN_ERR "gxfb: power up failed!\n"); + return ret; + } + + fb_set_suspend(info, 0); + console_unlock(); + return 0; +} +#endif + +static int gxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct gxfb_par *par; + struct fb_info *info; + int ret; + unsigned long val; + + struct fb_videomode *modedb_ptr; + unsigned int modedb_size; + + info = gxfb_init_fbinfo(&pdev->dev); + if (!info) + return -ENOMEM; + par = info->par; + + if ((ret = gxfb_map_video_memory(info, pdev)) < 0) { + dev_err(&pdev->dev, "failed to map frame buffer or controller registers\n"); + goto err; + } + + /* Figure out if this is a TFT or CRT part */ + + rdmsrl(MSR_GX_GLD_MSR_CONFIG, val); + + if ((val & MSR_GX_GLD_MSR_CONFIG_FP) == MSR_GX_GLD_MSR_CONFIG_FP) + par->enable_crt = 0; + else + par->enable_crt = 1; + + get_modedb(&modedb_ptr, &modedb_size); + ret = fb_find_mode(&info->var, info, mode_option, + modedb_ptr, modedb_size, NULL, 16); + if (ret == 0 || ret == 4) { + dev_err(&pdev->dev, "could not find valid video mode\n"); + ret = -EINVAL; + goto err; + } + + + /* Clear the frame buffer of garbage. */ + memset_io(info->screen_base, 0, info->fix.smem_len); + + gxfb_check_var(&info->var, info); + gxfb_set_par(info); + + pm_set_vt_switch(vt_switch); + + if (register_framebuffer(info) < 0) { + ret = -EINVAL; + goto err; + } + pci_set_drvdata(pdev, info); + fb_info(info, "%s frame buffer device\n", info->fix.id); + return 0; + + err: + if (info->screen_base) { + iounmap(info->screen_base); + pci_release_region(pdev, 0); + } + if (par->vid_regs) { + iounmap(par->vid_regs); + pci_release_region(pdev, 3); + } + if (par->dc_regs) { + iounmap(par->dc_regs); + pci_release_region(pdev, 2); + } + if (par->gp_regs) { + iounmap(par->gp_regs); + pci_release_region(pdev, 1); + } + + if (info) { + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + return ret; +} + +static void gxfb_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct gxfb_par *par = info->par; + + unregister_framebuffer(info); + + iounmap((void __iomem *)info->screen_base); + pci_release_region(pdev, 0); + + iounmap(par->vid_regs); + pci_release_region(pdev, 3); + + iounmap(par->dc_regs); + pci_release_region(pdev, 2); + + iounmap(par->gp_regs); + pci_release_region(pdev, 1); + + fb_dealloc_cmap(&info->cmap); + + framebuffer_release(info); +} + +static struct pci_device_id gxfb_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_GX_VIDEO) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, gxfb_id_table); + +static struct pci_driver gxfb_driver = { + .name = "gxfb", + .id_table = gxfb_id_table, + .probe = gxfb_probe, + .remove = gxfb_remove, +#ifdef CONFIG_PM + .suspend = gxfb_suspend, + .resume = gxfb_resume, +#endif +}; + +#ifndef MODULE +static int __init gxfb_setup(char *options) +{ + + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + mode_option = opt; + } + + return 0; +} +#endif + +static int __init gxfb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("gxfb", &option)) + return -ENODEV; + + gxfb_setup(option); +#endif + return pci_register_driver(&gxfb_driver); +} + +static void __exit gxfb_cleanup(void) +{ + pci_unregister_driver(&gxfb_driver); +} + +module_init(gxfb_init); +module_exit(gxfb_cleanup); + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "video mode (<x>x<y>[-<bpp>][@<refr>])"); + +module_param(vram, int, 0); +MODULE_PARM_DESC(vram, "video memory size"); + +module_param(vt_switch, int, 0); +MODULE_PARM_DESC(vt_switch, "enable VT switch during suspend/resume"); + +MODULE_DESCRIPTION("Framebuffer driver for the AMD Geode GX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/geode/lxfb.h b/drivers/video/fbdev/geode/lxfb.h new file mode 100644 index 000000000000..cfcd8090f313 --- /dev/null +++ b/drivers/video/fbdev/geode/lxfb.h @@ -0,0 +1,452 @@ +/* Geode LX framebuffer driver + * + * Copyright (C) 2006-2007, Advanced Micro Devices,Inc. + * Copyright (c) 2008 Andres Salomon <dilinger@debian.org> + * + * 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. + */ +#ifndef _LXFB_H_ +#define _LXFB_H_ + +#include <linux/fb.h> + +#define GP_REG_COUNT (0x7c / 4) +#define DC_REG_COUNT (0xf0 / 4) +#define VP_REG_COUNT (0x158 / 8) +#define FP_REG_COUNT (0x60 / 8) + +#define DC_PAL_COUNT 0x104 +#define DC_HFILT_COUNT 0x100 +#define DC_VFILT_COUNT 0x100 +#define VP_COEFF_SIZE 0x1000 +#define VP_PAL_COUNT 0x100 + +#define OUTPUT_CRT 0x01 +#define OUTPUT_PANEL 0x02 + +struct lxfb_par { + int output; + + void __iomem *gp_regs; + void __iomem *dc_regs; + void __iomem *vp_regs; +#ifdef CONFIG_PM + int powered_down; + + /* register state, for power mgmt functionality */ + struct { + uint64_t padsel; + uint64_t dotpll; + uint64_t dfglcfg; + uint64_t dcspare; + } msr; + + uint32_t gp[GP_REG_COUNT]; + uint32_t dc[DC_REG_COUNT]; + uint64_t vp[VP_REG_COUNT]; + uint64_t fp[FP_REG_COUNT]; + + uint32_t dc_pal[DC_PAL_COUNT]; + uint32_t vp_pal[VP_PAL_COUNT]; + uint32_t hcoeff[DC_HFILT_COUNT * 2]; + uint32_t vcoeff[DC_VFILT_COUNT]; + uint32_t vp_coeff[VP_COEFF_SIZE / 4]; +#endif +}; + +static inline unsigned int lx_get_pitch(unsigned int xres, int bpp) +{ + return (((xres * (bpp >> 3)) + 7) & ~7); +} + +void lx_set_mode(struct fb_info *); +unsigned int lx_framebuffer_size(void); +int lx_blank_display(struct fb_info *, int); +void lx_set_palette_reg(struct fb_info *, unsigned int, unsigned int, + unsigned int, unsigned int); + +#ifdef CONFIG_PM +int lx_powerdown(struct fb_info *info); +int lx_powerup(struct fb_info *info); +#endif + + +/* Graphics Processor registers (table 6-29 from the data book) */ +enum gp_registers { + GP_DST_OFFSET = 0, + GP_SRC_OFFSET, + GP_STRIDE, + GP_WID_HEIGHT, + + GP_SRC_COLOR_FG, + GP_SRC_COLOR_BG, + GP_PAT_COLOR_0, + GP_PAT_COLOR_1, + + GP_PAT_COLOR_2, + GP_PAT_COLOR_3, + GP_PAT_COLOR_4, + GP_PAT_COLOR_5, + + GP_PAT_DATA_0, + GP_PAT_DATA_1, + GP_RASTER_MODE, + GP_VECTOR_MODE, + + GP_BLT_MODE, + GP_BLT_STATUS, + GP_HST_SRC, + GP_BASE_OFFSET, + + GP_CMD_TOP, + GP_CMD_BOT, + GP_CMD_READ, + GP_CMD_WRITE, + + GP_CH3_OFFSET, + GP_CH3_MODE_STR, + GP_CH3_WIDHI, + GP_CH3_HSRC, + + GP_LUT_INDEX, + GP_LUT_DATA, + GP_INT_CNTRL, /* 0x78 */ +}; + +#define GP_BLT_STATUS_CE (1 << 4) /* cmd buf empty */ +#define GP_BLT_STATUS_PB (1 << 0) /* primitive busy */ + + +/* Display Controller registers (table 6-47 from the data book) */ +enum dc_registers { + DC_UNLOCK = 0, + DC_GENERAL_CFG, + DC_DISPLAY_CFG, + DC_ARB_CFG, + + DC_FB_ST_OFFSET, + DC_CB_ST_OFFSET, + DC_CURS_ST_OFFSET, + DC_RSVD_0, + + DC_VID_Y_ST_OFFSET, + DC_VID_U_ST_OFFSET, + DC_VID_V_ST_OFFSET, + DC_DV_TOP, + + DC_LINE_SIZE, + DC_GFX_PITCH, + DC_VID_YUV_PITCH, + DC_RSVD_1, + + DC_H_ACTIVE_TIMING, + DC_H_BLANK_TIMING, + DC_H_SYNC_TIMING, + DC_RSVD_2, + + DC_V_ACTIVE_TIMING, + DC_V_BLANK_TIMING, + DC_V_SYNC_TIMING, + DC_FB_ACTIVE, + + DC_CURSOR_X, + DC_CURSOR_Y, + DC_RSVD_3, + DC_LINE_CNT, + + DC_PAL_ADDRESS, + DC_PAL_DATA, + DC_DFIFO_DIAG, + DC_CFIFO_DIAG, + + DC_VID_DS_DELTA, + DC_GLIU0_MEM_OFFSET, + DC_DV_CTL, + DC_DV_ACCESS, + + DC_GFX_SCALE, + DC_IRQ_FILT_CTL, + DC_FILT_COEFF1, + DC_FILT_COEFF2, + + DC_VBI_EVEN_CTL, + DC_VBI_ODD_CTL, + DC_VBI_HOR, + DC_VBI_LN_ODD, + + DC_VBI_LN_EVEN, + DC_VBI_PITCH, + DC_CLR_KEY, + DC_CLR_KEY_MASK, + + DC_CLR_KEY_X, + DC_CLR_KEY_Y, + DC_IRQ, + DC_RSVD_4, + + DC_RSVD_5, + DC_GENLK_CTL, + DC_VID_EVEN_Y_ST_OFFSET, + DC_VID_EVEN_U_ST_OFFSET, + + DC_VID_EVEN_V_ST_OFFSET, + DC_V_ACTIVE_EVEN_TIMING, + DC_V_BLANK_EVEN_TIMING, + DC_V_SYNC_EVEN_TIMING, /* 0xec */ +}; + +#define DC_UNLOCK_LOCK 0x00000000 +#define DC_UNLOCK_UNLOCK 0x00004758 /* magic value */ + +#define DC_GENERAL_CFG_FDTY (1 << 17) +#define DC_GENERAL_CFG_DFHPEL_SHIFT (12) +#define DC_GENERAL_CFG_DFHPSL_SHIFT (8) +#define DC_GENERAL_CFG_VGAE (1 << 7) +#define DC_GENERAL_CFG_DECE (1 << 6) +#define DC_GENERAL_CFG_CMPE (1 << 5) +#define DC_GENERAL_CFG_VIDE (1 << 3) +#define DC_GENERAL_CFG_DFLE (1 << 0) + +#define DC_DISPLAY_CFG_VISL (1 << 27) +#define DC_DISPLAY_CFG_PALB (1 << 25) +#define DC_DISPLAY_CFG_DCEN (1 << 24) +#define DC_DISPLAY_CFG_DISP_MODE_24BPP (1 << 9) +#define DC_DISPLAY_CFG_DISP_MODE_16BPP (1 << 8) +#define DC_DISPLAY_CFG_DISP_MODE_8BPP (0) +#define DC_DISPLAY_CFG_TRUP (1 << 6) +#define DC_DISPLAY_CFG_VDEN (1 << 4) +#define DC_DISPLAY_CFG_GDEN (1 << 3) +#define DC_DISPLAY_CFG_TGEN (1 << 0) + +#define DC_DV_TOP_DV_TOP_EN (1 << 0) + +#define DC_DV_CTL_DV_LINE_SIZE ((1 << 10) | (1 << 11)) +#define DC_DV_CTL_DV_LINE_SIZE_1K (0) +#define DC_DV_CTL_DV_LINE_SIZE_2K (1 << 10) +#define DC_DV_CTL_DV_LINE_SIZE_4K (1 << 11) +#define DC_DV_CTL_DV_LINE_SIZE_8K ((1 << 10) | (1 << 11)) +#define DC_DV_CTL_CLEAR_DV_RAM (1 << 0) + +#define DC_IRQ_FILT_CTL_H_FILT_SEL (1 << 10) + +#define DC_CLR_KEY_CLR_KEY_EN (1 << 24) + +#define DC_IRQ_VIP_VSYNC_IRQ_STATUS (1 << 21) /* undocumented? */ +#define DC_IRQ_STATUS (1 << 20) /* undocumented? */ +#define DC_IRQ_VIP_VSYNC_LOSS_IRQ_MASK (1 << 1) +#define DC_IRQ_MASK (1 << 0) + +#define DC_GENLK_CTL_FLICK_SEL_MASK (0x0F << 28) +#define DC_GENLK_CTL_ALPHA_FLICK_EN (1 << 25) +#define DC_GENLK_CTL_FLICK_EN (1 << 24) +#define DC_GENLK_CTL_GENLK_EN (1 << 18) + + +/* + * Video Processor registers (table 6-71). + * There is space for 64 bit values, but we never use more than the + * lower 32 bits. The actual register save/restore code only bothers + * to restore those 32 bits. + */ +enum vp_registers { + VP_VCFG = 0, + VP_DCFG, + + VP_VX, + VP_VY, + + VP_SCL, + VP_VCK, + + VP_VCM, + VP_PAR, + + VP_PDR, + VP_SLR, + + VP_MISC, + VP_CCS, + + VP_VYS, + VP_VXS, + + VP_RSVD_0, + VP_VDC, + + VP_RSVD_1, + VP_CRC, + + VP_CRC32, + VP_VDE, + + VP_CCK, + VP_CCM, + + VP_CC1, + VP_CC2, + + VP_A1X, + VP_A1Y, + + VP_A1C, + VP_A1T, + + VP_A2X, + VP_A2Y, + + VP_A2C, + VP_A2T, + + VP_A3X, + VP_A3Y, + + VP_A3C, + VP_A3T, + + VP_VRR, + VP_AWT, + + VP_VTM, + VP_VYE, + + VP_A1YE, + VP_A2YE, + + VP_A3YE, /* 0x150 */ + + VP_VCR = 0x1000, /* 0x1000 - 0x1fff */ +}; + +#define VP_VCFG_VID_EN (1 << 0) + +#define VP_DCFG_GV_GAM (1 << 21) +#define VP_DCFG_PWR_SEQ_DELAY ((1 << 17) | (1 << 18) | (1 << 19)) +#define VP_DCFG_PWR_SEQ_DELAY_DEFAULT (1 << 19) /* undocumented */ +#define VP_DCFG_CRT_SYNC_SKW ((1 << 14) | (1 << 15) | (1 << 16)) +#define VP_DCFG_CRT_SYNC_SKW_DEFAULT (1 << 16) +#define VP_DCFG_CRT_VSYNC_POL (1 << 9) +#define VP_DCFG_CRT_HSYNC_POL (1 << 8) +#define VP_DCFG_DAC_BL_EN (1 << 3) +#define VP_DCFG_VSYNC_EN (1 << 2) +#define VP_DCFG_HSYNC_EN (1 << 1) +#define VP_DCFG_CRT_EN (1 << 0) + +#define VP_MISC_APWRDN (1 << 11) +#define VP_MISC_DACPWRDN (1 << 10) +#define VP_MISC_BYP_BOTH (1 << 0) + + +/* + * Flat Panel registers (table 6-71). + * Also 64 bit registers; see above note about 32-bit handling. + */ + +/* we're actually in the VP register space, starting at address 0x400 */ +#define VP_FP_START 0x400 + +enum fp_registers { + FP_PT1 = 0, + FP_PT2, + + FP_PM, + FP_DFC, + + FP_RSVD_0, + FP_RSVD_1, + + FP_RSVD_2, + FP_RSVD_3, + + FP_RSVD_4, + FP_DCA, + + FP_DMD, + FP_CRC, /* 0x458 */ +}; + +#define FP_PT2_HSP (1 << 22) +#define FP_PT2_VSP (1 << 23) +#define FP_PT2_SCRC (1 << 27) /* shfclk free */ + +#define FP_PM_P (1 << 24) /* panel power ctl */ +#define FP_PM_PANEL_PWR_UP (1 << 3) /* r/o */ +#define FP_PM_PANEL_PWR_DOWN (1 << 2) /* r/o */ +#define FP_PM_PANEL_OFF (1 << 1) /* r/o */ +#define FP_PM_PANEL_ON (1 << 0) /* r/o */ + +#define FP_DFC_BC ((1 << 4) | (1 << 5) | (1 << 6)) + + +/* register access functions */ + +static inline uint32_t read_gp(struct lxfb_par *par, int reg) +{ + return readl(par->gp_regs + 4*reg); +} + +static inline void write_gp(struct lxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->gp_regs + 4*reg); +} + +static inline uint32_t read_dc(struct lxfb_par *par, int reg) +{ + return readl(par->dc_regs + 4*reg); +} + +static inline void write_dc(struct lxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->dc_regs + 4*reg); +} + +static inline uint32_t read_vp(struct lxfb_par *par, int reg) +{ + return readl(par->vp_regs + 8*reg); +} + +static inline void write_vp(struct lxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->vp_regs + 8*reg); +} + +static inline uint32_t read_fp(struct lxfb_par *par, int reg) +{ + return readl(par->vp_regs + 8*reg + VP_FP_START); +} + +static inline void write_fp(struct lxfb_par *par, int reg, uint32_t val) +{ + writel(val, par->vp_regs + 8*reg + VP_FP_START); +} + + +/* MSRs are defined in linux/cs5535.h; their bitfields are here */ + +#define MSR_GLCP_DOTPLL_LOCK (1 << 25) /* r/o */ +#define MSR_GLCP_DOTPLL_HALFPIX (1 << 24) +#define MSR_GLCP_DOTPLL_BYPASS (1 << 15) +#define MSR_GLCP_DOTPLL_DOTRESET (1 << 0) + +/* note: this is actually the VP's GLD_MSR_CONFIG */ +#define MSR_LX_GLD_MSR_CONFIG_FMT ((1 << 3) | (1 << 4) | (1 << 5)) +#define MSR_LX_GLD_MSR_CONFIG_FMT_FP (1 << 3) +#define MSR_LX_GLD_MSR_CONFIG_FMT_CRT (0) +#define MSR_LX_GLD_MSR_CONFIG_FPC (1 << 15) /* FP *and* CRT */ + +#define MSR_LX_MSR_PADSEL_TFT_SEL_LOW 0xDFFFFFFF /* ??? */ +#define MSR_LX_MSR_PADSEL_TFT_SEL_HIGH 0x0000003F /* ??? */ + +#define MSR_LX_SPARE_MSR_DIS_CFIFO_HGO (1 << 11) /* undocumented */ +#define MSR_LX_SPARE_MSR_VFIFO_ARB_SEL (1 << 10) /* undocumented */ +#define MSR_LX_SPARE_MSR_WM_LPEN_OVRD (1 << 9) /* undocumented */ +#define MSR_LX_SPARE_MSR_LOAD_WM_LPEN_M (1 << 8) /* undocumented */ +#define MSR_LX_SPARE_MSR_DIS_INIT_V_PRI (1 << 7) /* undocumented */ +#define MSR_LX_SPARE_MSR_DIS_VIFO_WM (1 << 6) +#define MSR_LX_SPARE_MSR_DIS_CWD_CHECK (1 << 5) /* undocumented */ +#define MSR_LX_SPARE_MSR_PIX8_PAN_FIX (1 << 4) /* undocumented */ +#define MSR_LX_SPARE_MSR_FIRST_REQ_MASK (1 << 1) /* undocumented */ + +#endif diff --git a/drivers/video/fbdev/geode/lxfb_core.c b/drivers/video/fbdev/geode/lxfb_core.c new file mode 100644 index 000000000000..9e1d19d673a1 --- /dev/null +++ b/drivers/video/fbdev/geode/lxfb_core.c @@ -0,0 +1,683 @@ +/* + * Geode LX framebuffer driver. + * + * Copyright (C) 2007 Advanced Micro Devices, Inc. + * Built from gxfb (which is Copyright (C) 2006 Arcom Control Systems Ltd.) + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/console.h> +#include <linux/mm.h> +#include <linux/suspend.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/uaccess.h> + +#include "lxfb.h" + +static char *mode_option; +static int noclear, nopanel, nocrt; +static int vram; +static int vt_switch; + +/* Most of these modes are sorted in ascending order, but + * since the first entry in this table is the "default" mode, + * we try to make it something sane - 640x480-60 is sane + */ + +static struct fb_videomode geode_modedb[] = { + /* 640x480-60 */ + { NULL, 60, 640, 480, 39682, 48, 8, 25, 2, 88, 2, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 640x400-70 */ + { NULL, 70, 640, 400, 39770, 40, 8, 28, 5, 96, 2, + FB_SYNC_HOR_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 640x480-70 */ + { NULL, 70, 640, 480, 35014, 88, 24, 15, 2, 64, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 640x480-72 */ + { NULL, 72, 640, 480, 32102, 120, 16, 20, 1, 40, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 640x480-75 */ + { NULL, 75, 640, 480, 31746, 120, 16, 16, 1, 64, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 640x480-85 */ + { NULL, 85, 640, 480, 27780, 80, 56, 25, 1, 56, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 640x480-90 */ + { NULL, 90, 640, 480, 26392, 96, 32, 22, 1, 64, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 640x480-100 */ + { NULL, 100, 640, 480, 23167, 104, 40, 25, 1, 64, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 640x480-60 */ + { NULL, 60, 640, 480, 39682, 48, 16, 25, 10, 88, 2, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-56 */ + { NULL, 56, 800, 600, 27901, 128, 24, 22, 1, 72, 2, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-60 */ + { NULL, 60, 800, 600, 25131, 72, 32, 23, 1, 136, 4, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-70 */ + { NULL, 70, 800, 600, 21873, 120, 40, 21, 4, 80, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-72 */ + { NULL, 72, 800, 600, 20052, 64, 56, 23, 37, 120, 6, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-75 */ + { NULL, 75, 800, 600, 20202, 160, 16, 21, 1, 80, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-85 */ + { NULL, 85, 800, 600, 17790, 152, 32, 27, 1, 64, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-90 */ + { NULL, 90, 800, 600, 16648, 128, 40, 28, 1, 88, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-100 */ + { NULL, 100, 800, 600, 14667, 136, 48, 27, 1, 88, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 800x600-60 */ + { NULL, 60, 800, 600, 25131, 88, 40, 23, 1, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-60 */ + { NULL, 60, 1024, 768, 15385, 160, 24, 29, 3, 136, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-70 */ + { NULL, 70, 1024, 768, 13346, 144, 24, 29, 3, 136, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-72 */ + { NULL, 72, 1024, 768, 12702, 168, 56, 29, 4, 112, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-75 */ + { NULL, 75, 1024, 768, 12703, 176, 16, 28, 1, 96, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-85 */ + { NULL, 85, 1024, 768, 10581, 208, 48, 36, 1, 96, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-90 */ + { NULL, 90, 1024, 768, 9981, 176, 64, 37, 1, 112, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-100 */ + { NULL, 100, 1024, 768, 8825, 184, 72, 42, 1, 112, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1024x768-60 */ + { NULL, 60, 1024, 768, 15385, 160, 24, 29, 3, 136, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-60 */ + { NULL, 60, 1152, 864, 12251, 184, 64, 27, 1, 120, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-70 */ + { NULL, 70, 1152, 864, 10254, 192, 72, 32, 8, 120, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-72 */ + { NULL, 72, 1152, 864, 9866, 200, 72, 33, 7, 128, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-75 */ + { NULL, 75, 1152, 864, 9259, 256, 64, 32, 1, 128, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-85 */ + { NULL, 85, 1152, 864, 8357, 200, 72, 37, 3, 128, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-90 */ + { NULL, 90, 1152, 864, 7719, 208, 80, 42, 9, 128, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-100 */ + { NULL, 100, 1152, 864, 6947, 208, 80, 48, 3, 128, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1152x864-60 */ + { NULL, 60, 1152, 864, 12251, 184, 64, 27, 1, 120, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-60 */ + { NULL, 60, 1280, 1024, 9262, 248, 48, 38, 1, 112, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-70 */ + { NULL, 70, 1280, 1024, 7719, 224, 88, 38, 6, 136, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-72 */ + { NULL, 72, 1280, 1024, 7490, 224, 88, 39, 7, 136, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-75 */ + { NULL, 75, 1280, 1024, 7409, 248, 16, 38, 1, 144, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-85 */ + { NULL, 85, 1280, 1024, 6351, 224, 64, 44, 1, 160, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-90 */ + { NULL, 90, 1280, 1024, 5791, 240, 96, 51, 12, 144, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-100 */ + { NULL, 100, 1280, 1024, 5212, 240, 96, 57, 6, 144, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1280x1024-60 */ + { NULL, 60, 1280, 1024, 9262, 248, 48, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-60 */ + { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-70 */ + { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-72 */ + { NULL, 72, 1600, 1200, 5053, 288, 112, 47, 13, 176, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-75 */ + { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-85 */ + { NULL, 85, 1600, 1200, 4357, 304, 64, 46, 1, 192, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-90 */ + { NULL, 90, 1600, 1200, 3981, 304, 128, 60, 1, 176, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-100 */ + { NULL, 100, 1600, 1200, 3563, 304, 128, 67, 1, 176, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1600x1200-60 */ + { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 1920x1440-60 */ + { NULL, 60, 1920, 1440, 4273, 344, 128, 56, 1, 208, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1920x1440-70 */ + { NULL, 70, 1920, 1440, 3593, 360, 152, 55, 8, 208, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1920x1440-72 */ + { NULL, 72, 1920, 1440, 3472, 360, 152, 68, 4, 208, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1920x1440-75 */ + { NULL, 75, 1920, 1440, 3367, 352, 144, 56, 1, 224, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 1920x1440-85 */ + { NULL, 85, 1920, 1440, 2929, 368, 152, 68, 1, 216, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, +}; + +#ifdef CONFIG_OLPC +#include <asm/olpc.h> + +static struct fb_videomode olpc_dcon_modedb[] = { + /* The only mode the DCON has is 1200x900 */ + { NULL, 50, 1200, 900, 17460, 24, 8, 4, 5, 8, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 } +}; + +static void get_modedb(struct fb_videomode **modedb, unsigned int *size) +{ + if (olpc_has_dcon()) { + *modedb = (struct fb_videomode *) olpc_dcon_modedb; + *size = ARRAY_SIZE(olpc_dcon_modedb); + } else { + *modedb = (struct fb_videomode *) geode_modedb; + *size = ARRAY_SIZE(geode_modedb); + } +} + +#else +static void get_modedb(struct fb_videomode **modedb, unsigned int *size) +{ + *modedb = (struct fb_videomode *) geode_modedb; + *size = ARRAY_SIZE(geode_modedb); +} +#endif + +static int lxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + if (var->xres > 1920 || var->yres > 1440) + return -EINVAL; + + if (var->bits_per_pixel == 32) { + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + } else if (var->bits_per_pixel == 16) { + var->red.offset = 11; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 0; var->blue.length = 5; + } else if (var->bits_per_pixel == 8) { + var->red.offset = 0; var->red.length = 8; + var->green.offset = 0; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + } else + return -EINVAL; + + var->transp.offset = 0; var->transp.length = 0; + + /* Enough video memory? */ + if ((lx_get_pitch(var->xres, var->bits_per_pixel) * var->yres) + > info->fix.smem_len) + return -EINVAL; + + return 0; +} + +static int lxfb_set_par(struct fb_info *info) +{ + if (info->var.bits_per_pixel > 8) + info->fix.visual = FB_VISUAL_TRUECOLOR; + else + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = lx_get_pitch(info->var.xres, + info->var.bits_per_pixel); + + lx_set_mode(info); + return 0; +} + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int lxfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 *pal = info->pseudo_palette; + u32 v; + + if (regno >= 16) + return -EINVAL; + + v = chan_to_field(red, &info->var.red); + v |= chan_to_field(green, &info->var.green); + v |= chan_to_field(blue, &info->var.blue); + + pal[regno] = v; + } else { + if (regno >= 256) + return -EINVAL; + + lx_set_palette_reg(info, regno, red, green, blue); + } + + return 0; +} + +static int lxfb_blank(int blank_mode, struct fb_info *info) +{ + return lx_blank_display(info, blank_mode); +} + + +static int lxfb_map_video_memory(struct fb_info *info, struct pci_dev *dev) +{ + struct lxfb_par *par = info->par; + int ret; + + ret = pci_enable_device(dev); + + if (ret) + return ret; + + ret = pci_request_region(dev, 0, "lxfb-framebuffer"); + + if (ret) + return ret; + + ret = pci_request_region(dev, 1, "lxfb-gp"); + + if (ret) + return ret; + + ret = pci_request_region(dev, 2, "lxfb-vg"); + + if (ret) + return ret; + + ret = pci_request_region(dev, 3, "lxfb-vp"); + + if (ret) + return ret; + + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = vram ? vram : lx_framebuffer_size(); + + info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); + + ret = -ENOMEM; + + if (info->screen_base == NULL) + return ret; + + par->gp_regs = pci_ioremap_bar(dev, 1); + + if (par->gp_regs == NULL) + return ret; + + par->dc_regs = pci_ioremap_bar(dev, 2); + + if (par->dc_regs == NULL) + return ret; + + par->vp_regs = pci_ioremap_bar(dev, 3); + + if (par->vp_regs == NULL) + return ret; + + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + write_dc(par, DC_GLIU0_MEM_OFFSET, info->fix.smem_start & 0xFF000000); + write_dc(par, DC_UNLOCK, DC_UNLOCK_LOCK); + + dev_info(&dev->dev, "%d KB of video memory at 0x%lx\n", + info->fix.smem_len / 1024, info->fix.smem_start); + + return 0; +} + +static struct fb_ops lxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = lxfb_check_var, + .fb_set_par = lxfb_set_par, + .fb_setcolreg = lxfb_setcolreg, + .fb_blank = lxfb_blank, + /* No HW acceleration for now. */ + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_info *lxfb_init_fbinfo(struct device *dev) +{ + struct lxfb_par *par; + struct fb_info *info; + + /* Alloc enough space for the pseudo palette. */ + info = framebuffer_alloc(sizeof(struct lxfb_par) + sizeof(u32) * 16, + dev); + if (!info) + return NULL; + + par = info->par; + + strcpy(info->fix.id, "Geode LX"); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.accel = FB_ACCEL_NONE; + + info->var.nonstd = 0; + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.accel_flags = 0; + info->var.vmode = FB_VMODE_NONINTERLACED; + + info->fbops = &lxfb_ops; + info->flags = FBINFO_DEFAULT; + info->node = -1; + + info->pseudo_palette = (void *)par + sizeof(struct lxfb_par); + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + framebuffer_release(info); + return NULL; + } + + info->var.grayscale = 0; + + return info; +} + +#ifdef CONFIG_PM +static int lxfb_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(pdev); + + if (state.event == PM_EVENT_SUSPEND) { + console_lock(); + lx_powerdown(info); + fb_set_suspend(info, 1); + console_unlock(); + } + + /* there's no point in setting PCI states; we emulate PCI, so + * we don't end up getting power savings anyways */ + + return 0; +} + +static int lxfb_resume(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + int ret; + + console_lock(); + ret = lx_powerup(info); + if (ret) { + printk(KERN_ERR "lxfb: power up failed!\n"); + return ret; + } + + fb_set_suspend(info, 0); + console_unlock(); + return 0; +} +#else +#define lxfb_suspend NULL +#define lxfb_resume NULL +#endif + +static int lxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct lxfb_par *par; + struct fb_info *info; + int ret; + + struct fb_videomode *modedb_ptr; + unsigned int modedb_size; + + info = lxfb_init_fbinfo(&pdev->dev); + + if (info == NULL) + return -ENOMEM; + + par = info->par; + + ret = lxfb_map_video_memory(info, pdev); + + if (ret < 0) { + dev_err(&pdev->dev, + "failed to map frame buffer or controller registers\n"); + goto err; + } + + /* Set up the desired outputs */ + + par->output = 0; + par->output |= (nopanel) ? 0 : OUTPUT_PANEL; + par->output |= (nocrt) ? 0 : OUTPUT_CRT; + + /* Set up the mode database */ + + get_modedb(&modedb_ptr, &modedb_size); + ret = fb_find_mode(&info->var, info, mode_option, + modedb_ptr, modedb_size, NULL, 16); + + if (ret == 0 || ret == 4) { + dev_err(&pdev->dev, "could not find valid video mode\n"); + ret = -EINVAL; + goto err; + } + + /* Clear the screen of garbage, unless noclear was specified, + * in which case we assume the user knows what he is doing */ + + if (!noclear) + memset_io(info->screen_base, 0, info->fix.smem_len); + + /* Set the mode */ + + lxfb_check_var(&info->var, info); + lxfb_set_par(info); + + pm_set_vt_switch(vt_switch); + + if (register_framebuffer(info) < 0) { + ret = -EINVAL; + goto err; + } + pci_set_drvdata(pdev, info); + fb_info(info, "%s frame buffer device\n", info->fix.id); + + return 0; + +err: + if (info->screen_base) { + iounmap(info->screen_base); + pci_release_region(pdev, 0); + } + if (par->gp_regs) { + iounmap(par->gp_regs); + pci_release_region(pdev, 1); + } + if (par->dc_regs) { + iounmap(par->dc_regs); + pci_release_region(pdev, 2); + } + if (par->vp_regs) { + iounmap(par->vp_regs); + pci_release_region(pdev, 3); + } + + if (info) { + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + + return ret; +} + +static void lxfb_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct lxfb_par *par = info->par; + + unregister_framebuffer(info); + + iounmap(info->screen_base); + pci_release_region(pdev, 0); + + iounmap(par->gp_regs); + pci_release_region(pdev, 1); + + iounmap(par->dc_regs); + pci_release_region(pdev, 2); + + iounmap(par->vp_regs); + pci_release_region(pdev, 3); + + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + +static struct pci_device_id lxfb_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LX_VIDEO) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, lxfb_id_table); + +static struct pci_driver lxfb_driver = { + .name = "lxfb", + .id_table = lxfb_id_table, + .probe = lxfb_probe, + .remove = lxfb_remove, + .suspend = lxfb_suspend, + .resume = lxfb_resume, +}; + +#ifndef MODULE +static int __init lxfb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strcmp(opt, "noclear")) + noclear = 1; + else if (!strcmp(opt, "nopanel")) + nopanel = 1; + else if (!strcmp(opt, "nocrt")) + nocrt = 1; + else + mode_option = opt; + } + + return 0; +} +#endif + +static int __init lxfb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("lxfb", &option)) + return -ENODEV; + + lxfb_setup(option); +#endif + return pci_register_driver(&lxfb_driver); +} +static void __exit lxfb_cleanup(void) +{ + pci_unregister_driver(&lxfb_driver); +} + +module_init(lxfb_init); +module_exit(lxfb_cleanup); + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "video mode (<x>x<y>[-<bpp>][@<refr>])"); + +module_param(vram, int, 0); +MODULE_PARM_DESC(vram, "video memory size"); + +module_param(vt_switch, int, 0); +MODULE_PARM_DESC(vt_switch, "enable VT switch during suspend/resume"); + +MODULE_DESCRIPTION("Framebuffer driver for the AMD Geode LX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/geode/lxfb_ops.c b/drivers/video/fbdev/geode/lxfb_ops.c new file mode 100644 index 000000000000..79e9abc72b83 --- /dev/null +++ b/drivers/video/fbdev/geode/lxfb_ops.c @@ -0,0 +1,845 @@ +/* Geode LX framebuffer driver + * + * Copyright (C) 2006-2007, Advanced Micro Devices,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. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/cs5535.h> + +#include "lxfb.h" + +/* TODO + * Support panel scaling + * Add acceleration + * Add support for interlacing (TV out) + * Support compression + */ + +/* This is the complete list of PLL frequencies that we can set - + * we will choose the closest match to the incoming clock. + * freq is the frequency of the dotclock * 1000 (for example, + * 24823 = 24.983 Mhz). + * pllval is the corresponding PLL value +*/ + +static const struct { + unsigned int pllval; + unsigned int freq; +} pll_table[] = { + { 0x000131AC, 6231 }, + { 0x0001215D, 6294 }, + { 0x00011087, 6750 }, + { 0x0001216C, 7081 }, + { 0x0001218D, 7140 }, + { 0x000110C9, 7800 }, + { 0x00013147, 7875 }, + { 0x000110A7, 8258 }, + { 0x00012159, 8778 }, + { 0x00014249, 8875 }, + { 0x00010057, 9000 }, + { 0x0001219A, 9472 }, + { 0x00012158, 9792 }, + { 0x00010045, 10000 }, + { 0x00010089, 10791 }, + { 0x000110E7, 11225 }, + { 0x00012136, 11430 }, + { 0x00013207, 12375 }, + { 0x00012187, 12500 }, + { 0x00014286, 14063 }, + { 0x000110E5, 15016 }, + { 0x00014214, 16250 }, + { 0x00011105, 17045 }, + { 0x000131E4, 18563 }, + { 0x00013183, 18750 }, + { 0x00014284, 19688 }, + { 0x00011104, 20400 }, + { 0x00016363, 23625 }, + { 0x000031AC, 24923 }, + { 0x0000215D, 25175 }, + { 0x00001087, 27000 }, + { 0x0000216C, 28322 }, + { 0x0000218D, 28560 }, + { 0x000010C9, 31200 }, + { 0x00003147, 31500 }, + { 0x000010A7, 33032 }, + { 0x00002159, 35112 }, + { 0x00004249, 35500 }, + { 0x00000057, 36000 }, + { 0x0000219A, 37889 }, + { 0x00002158, 39168 }, + { 0x00000045, 40000 }, + { 0x00000089, 43163 }, + { 0x000010E7, 44900 }, + { 0x00002136, 45720 }, + { 0x00003207, 49500 }, + { 0x00002187, 50000 }, + { 0x00004286, 56250 }, + { 0x000010E5, 60065 }, + { 0x00004214, 65000 }, + { 0x00001105, 68179 }, + { 0x000031E4, 74250 }, + { 0x00003183, 75000 }, + { 0x00004284, 78750 }, + { 0x00001104, 81600 }, + { 0x00006363, 94500 }, + { 0x00005303, 97520 }, + { 0x00002183, 100187 }, + { 0x00002122, 101420 }, + { 0x00001081, 108000 }, + { 0x00006201, 113310 }, + { 0x00000041, 119650 }, + { 0x000041A1, 129600 }, + { 0x00002182, 133500 }, + { 0x000041B1, 135000 }, + { 0x00000051, 144000 }, + { 0x000041E1, 148500 }, + { 0x000062D1, 157500 }, + { 0x000031A1, 162000 }, + { 0x00000061, 169203 }, + { 0x00004231, 172800 }, + { 0x00002151, 175500 }, + { 0x000052E1, 189000 }, + { 0x00000071, 192000 }, + { 0x00003201, 198000 }, + { 0x00004291, 202500 }, + { 0x00001101, 204750 }, + { 0x00007481, 218250 }, + { 0x00004170, 229500 }, + { 0x00006210, 234000 }, + { 0x00003140, 251182 }, + { 0x00006250, 261000 }, + { 0x000041C0, 278400 }, + { 0x00005220, 280640 }, + { 0x00000050, 288000 }, + { 0x000041E0, 297000 }, + { 0x00002130, 320207 } +}; + + +static void lx_set_dotpll(u32 pllval) +{ + u32 dotpll_lo, dotpll_hi; + int i; + + rdmsr(MSR_GLCP_DOTPLL, dotpll_lo, dotpll_hi); + + if ((dotpll_lo & MSR_GLCP_DOTPLL_LOCK) && (dotpll_hi == pllval)) + return; + + dotpll_hi = pllval; + dotpll_lo &= ~(MSR_GLCP_DOTPLL_BYPASS | MSR_GLCP_DOTPLL_HALFPIX); + dotpll_lo |= MSR_GLCP_DOTPLL_DOTRESET; + + wrmsr(MSR_GLCP_DOTPLL, dotpll_lo, dotpll_hi); + + /* Wait 100us for the PLL to lock */ + + udelay(100); + + /* Now, loop for the lock bit */ + + for (i = 0; i < 1000; i++) { + rdmsr(MSR_GLCP_DOTPLL, dotpll_lo, dotpll_hi); + if (dotpll_lo & MSR_GLCP_DOTPLL_LOCK) + break; + } + + /* Clear the reset bit */ + + dotpll_lo &= ~MSR_GLCP_DOTPLL_DOTRESET; + wrmsr(MSR_GLCP_DOTPLL, dotpll_lo, dotpll_hi); +} + +/* Set the clock based on the frequency specified by the current mode */ + +static void lx_set_clock(struct fb_info *info) +{ + unsigned int diff, min, best = 0; + unsigned int freq, i; + + freq = (unsigned int) (1000000000 / info->var.pixclock); + + min = abs(pll_table[0].freq - freq); + + for (i = 0; i < ARRAY_SIZE(pll_table); i++) { + diff = abs(pll_table[i].freq - freq); + if (diff < min) { + min = diff; + best = i; + } + } + + lx_set_dotpll(pll_table[best].pllval & 0x00017FFF); +} + +static void lx_graphics_disable(struct fb_info *info) +{ + struct lxfb_par *par = info->par; + unsigned int val, gcfg; + + /* Note: This assumes that the video is in a quitet state */ + + write_vp(par, VP_A1T, 0); + write_vp(par, VP_A2T, 0); + write_vp(par, VP_A3T, 0); + + /* Turn off the VGA and video enable */ + val = read_dc(par, DC_GENERAL_CFG) & ~(DC_GENERAL_CFG_VGAE | + DC_GENERAL_CFG_VIDE); + + write_dc(par, DC_GENERAL_CFG, val); + + val = read_vp(par, VP_VCFG) & ~VP_VCFG_VID_EN; + write_vp(par, VP_VCFG, val); + + write_dc(par, DC_IRQ, DC_IRQ_MASK | DC_IRQ_VIP_VSYNC_LOSS_IRQ_MASK | + DC_IRQ_STATUS | DC_IRQ_VIP_VSYNC_IRQ_STATUS); + + val = read_dc(par, DC_GENLK_CTL) & ~DC_GENLK_CTL_GENLK_EN; + write_dc(par, DC_GENLK_CTL, val); + + val = read_dc(par, DC_CLR_KEY); + write_dc(par, DC_CLR_KEY, val & ~DC_CLR_KEY_CLR_KEY_EN); + + /* turn off the panel */ + write_fp(par, FP_PM, read_fp(par, FP_PM) & ~FP_PM_P); + + val = read_vp(par, VP_MISC) | VP_MISC_DACPWRDN; + write_vp(par, VP_MISC, val); + + /* Turn off the display */ + + val = read_vp(par, VP_DCFG); + write_vp(par, VP_DCFG, val & ~(VP_DCFG_CRT_EN | VP_DCFG_HSYNC_EN | + VP_DCFG_VSYNC_EN | VP_DCFG_DAC_BL_EN)); + + gcfg = read_dc(par, DC_GENERAL_CFG); + gcfg &= ~(DC_GENERAL_CFG_CMPE | DC_GENERAL_CFG_DECE); + write_dc(par, DC_GENERAL_CFG, gcfg); + + /* Turn off the TGEN */ + val = read_dc(par, DC_DISPLAY_CFG); + val &= ~DC_DISPLAY_CFG_TGEN; + write_dc(par, DC_DISPLAY_CFG, val); + + /* Wait 1000 usecs to ensure that the TGEN is clear */ + udelay(1000); + + /* Turn off the FIFO loader */ + + gcfg &= ~DC_GENERAL_CFG_DFLE; + write_dc(par, DC_GENERAL_CFG, gcfg); + + /* Lastly, wait for the GP to go idle */ + + do { + val = read_gp(par, GP_BLT_STATUS); + } while ((val & GP_BLT_STATUS_PB) || !(val & GP_BLT_STATUS_CE)); +} + +static void lx_graphics_enable(struct fb_info *info) +{ + struct lxfb_par *par = info->par; + u32 temp, config; + + /* Set the video request register */ + write_vp(par, VP_VRR, 0); + + /* Set up the polarities */ + + config = read_vp(par, VP_DCFG); + + config &= ~(VP_DCFG_CRT_SYNC_SKW | VP_DCFG_PWR_SEQ_DELAY | + VP_DCFG_CRT_HSYNC_POL | VP_DCFG_CRT_VSYNC_POL); + + config |= (VP_DCFG_CRT_SYNC_SKW_DEFAULT | VP_DCFG_PWR_SEQ_DELAY_DEFAULT + | VP_DCFG_GV_GAM); + + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) + config |= VP_DCFG_CRT_HSYNC_POL; + + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) + config |= VP_DCFG_CRT_VSYNC_POL; + + if (par->output & OUTPUT_PANEL) { + u32 msrlo, msrhi; + + write_fp(par, FP_PT1, 0); + temp = FP_PT2_SCRC; + + if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) + temp |= FP_PT2_HSP; + + if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) + temp |= FP_PT2_VSP; + + write_fp(par, FP_PT2, temp); + write_fp(par, FP_DFC, FP_DFC_BC); + + msrlo = MSR_LX_MSR_PADSEL_TFT_SEL_LOW; + msrhi = MSR_LX_MSR_PADSEL_TFT_SEL_HIGH; + + wrmsr(MSR_LX_MSR_PADSEL, msrlo, msrhi); + } + + if (par->output & OUTPUT_CRT) { + config |= VP_DCFG_CRT_EN | VP_DCFG_HSYNC_EN | + VP_DCFG_VSYNC_EN | VP_DCFG_DAC_BL_EN; + } + + write_vp(par, VP_DCFG, config); + + /* Turn the CRT dacs back on */ + + if (par->output & OUTPUT_CRT) { + temp = read_vp(par, VP_MISC); + temp &= ~(VP_MISC_DACPWRDN | VP_MISC_APWRDN); + write_vp(par, VP_MISC, temp); + } + + /* Turn the panel on (if it isn't already) */ + if (par->output & OUTPUT_PANEL) + write_fp(par, FP_PM, read_fp(par, FP_PM) | FP_PM_P); +} + +unsigned int lx_framebuffer_size(void) +{ + unsigned int val; + + if (!cs5535_has_vsa2()) { + uint32_t hi, lo; + + /* The number of pages is (PMAX - PMIN)+1 */ + rdmsr(MSR_GLIU_P2D_RO0, lo, hi); + + /* PMAX */ + val = ((hi & 0xff) << 12) | ((lo & 0xfff00000) >> 20); + /* PMIN */ + val -= (lo & 0x000fffff); + val += 1; + + /* The page size is 4k */ + return (val << 12); + } + + /* The frame buffer size is reported by a VSM in VSA II */ + /* Virtual Register Class = 0x02 */ + /* VG_MEM_SIZE (1MB units) = 0x00 */ + + outw(VSA_VR_UNLOCK, VSA_VRC_INDEX); + outw(VSA_VR_MEM_SIZE, VSA_VRC_INDEX); + + val = (unsigned int)(inw(VSA_VRC_DATA)) & 0xFE; + return (val << 20); +} + +void lx_set_mode(struct fb_info *info) +{ + struct lxfb_par *par = info->par; + u64 msrval; + + unsigned int max, dv, val, size; + + unsigned int gcfg, dcfg; + int hactive, hblankstart, hsyncstart, hsyncend, hblankend, htotal; + int vactive, vblankstart, vsyncstart, vsyncend, vblankend, vtotal; + + /* Unlock the DC registers */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + + lx_graphics_disable(info); + + lx_set_clock(info); + + /* Set output mode */ + + rdmsrl(MSR_LX_GLD_MSR_CONFIG, msrval); + msrval &= ~MSR_LX_GLD_MSR_CONFIG_FMT; + + if (par->output & OUTPUT_PANEL) { + msrval |= MSR_LX_GLD_MSR_CONFIG_FMT_FP; + + if (par->output & OUTPUT_CRT) + msrval |= MSR_LX_GLD_MSR_CONFIG_FPC; + else + msrval &= ~MSR_LX_GLD_MSR_CONFIG_FPC; + } else + msrval |= MSR_LX_GLD_MSR_CONFIG_FMT_CRT; + + wrmsrl(MSR_LX_GLD_MSR_CONFIG, msrval); + + /* Clear the various buffers */ + /* FIXME: Adjust for panning here */ + + write_dc(par, DC_FB_ST_OFFSET, 0); + write_dc(par, DC_CB_ST_OFFSET, 0); + write_dc(par, DC_CURS_ST_OFFSET, 0); + + /* FIXME: Add support for interlacing */ + /* FIXME: Add support for scaling */ + + val = read_dc(par, DC_GENLK_CTL); + val &= ~(DC_GENLK_CTL_ALPHA_FLICK_EN | DC_GENLK_CTL_FLICK_EN | + DC_GENLK_CTL_FLICK_SEL_MASK); + + /* Default scaling params */ + + write_dc(par, DC_GFX_SCALE, (0x4000 << 16) | 0x4000); + write_dc(par, DC_IRQ_FILT_CTL, 0); + write_dc(par, DC_GENLK_CTL, val); + + /* FIXME: Support compression */ + + if (info->fix.line_length > 4096) + dv = DC_DV_CTL_DV_LINE_SIZE_8K; + else if (info->fix.line_length > 2048) + dv = DC_DV_CTL_DV_LINE_SIZE_4K; + else if (info->fix.line_length > 1024) + dv = DC_DV_CTL_DV_LINE_SIZE_2K; + else + dv = DC_DV_CTL_DV_LINE_SIZE_1K; + + max = info->fix.line_length * info->var.yres; + max = (max + 0x3FF) & 0xFFFFFC00; + + write_dc(par, DC_DV_TOP, max | DC_DV_TOP_DV_TOP_EN); + + val = read_dc(par, DC_DV_CTL) & ~DC_DV_CTL_DV_LINE_SIZE; + write_dc(par, DC_DV_CTL, val | dv); + + size = info->var.xres * (info->var.bits_per_pixel >> 3); + + write_dc(par, DC_GFX_PITCH, info->fix.line_length >> 3); + write_dc(par, DC_LINE_SIZE, (size + 7) >> 3); + + /* Set default watermark values */ + + rdmsrl(MSR_LX_SPARE_MSR, msrval); + + msrval &= ~(MSR_LX_SPARE_MSR_DIS_CFIFO_HGO + | MSR_LX_SPARE_MSR_VFIFO_ARB_SEL + | MSR_LX_SPARE_MSR_LOAD_WM_LPEN_M + | MSR_LX_SPARE_MSR_WM_LPEN_OVRD); + msrval |= MSR_LX_SPARE_MSR_DIS_VIFO_WM | + MSR_LX_SPARE_MSR_DIS_INIT_V_PRI; + wrmsrl(MSR_LX_SPARE_MSR, msrval); + + gcfg = DC_GENERAL_CFG_DFLE; /* Display fifo enable */ + gcfg |= (0x6 << DC_GENERAL_CFG_DFHPSL_SHIFT) | /* default priority */ + (0xb << DC_GENERAL_CFG_DFHPEL_SHIFT); + gcfg |= DC_GENERAL_CFG_FDTY; /* Set the frame dirty mode */ + + dcfg = DC_DISPLAY_CFG_VDEN; /* Enable video data */ + dcfg |= DC_DISPLAY_CFG_GDEN; /* Enable graphics */ + dcfg |= DC_DISPLAY_CFG_TGEN; /* Turn on the timing generator */ + dcfg |= DC_DISPLAY_CFG_TRUP; /* Update timings immediately */ + dcfg |= DC_DISPLAY_CFG_PALB; /* Palette bypass in > 8 bpp modes */ + dcfg |= DC_DISPLAY_CFG_VISL; + dcfg |= DC_DISPLAY_CFG_DCEN; /* Always center the display */ + + /* Set the current BPP mode */ + + switch (info->var.bits_per_pixel) { + case 8: + dcfg |= DC_DISPLAY_CFG_DISP_MODE_8BPP; + break; + + case 16: + dcfg |= DC_DISPLAY_CFG_DISP_MODE_16BPP; + break; + + case 32: + case 24: + dcfg |= DC_DISPLAY_CFG_DISP_MODE_24BPP; + break; + } + + /* Now - set up the timings */ + + hactive = info->var.xres; + hblankstart = hactive; + hsyncstart = hblankstart + info->var.right_margin; + hsyncend = hsyncstart + info->var.hsync_len; + hblankend = hsyncend + info->var.left_margin; + htotal = hblankend; + + vactive = info->var.yres; + vblankstart = vactive; + vsyncstart = vblankstart + info->var.lower_margin; + vsyncend = vsyncstart + info->var.vsync_len; + vblankend = vsyncend + info->var.upper_margin; + vtotal = vblankend; + + write_dc(par, DC_H_ACTIVE_TIMING, (hactive - 1) | ((htotal - 1) << 16)); + write_dc(par, DC_H_BLANK_TIMING, + (hblankstart - 1) | ((hblankend - 1) << 16)); + write_dc(par, DC_H_SYNC_TIMING, + (hsyncstart - 1) | ((hsyncend - 1) << 16)); + + write_dc(par, DC_V_ACTIVE_TIMING, (vactive - 1) | ((vtotal - 1) << 16)); + write_dc(par, DC_V_BLANK_TIMING, + (vblankstart - 1) | ((vblankend - 1) << 16)); + write_dc(par, DC_V_SYNC_TIMING, + (vsyncstart - 1) | ((vsyncend - 1) << 16)); + + write_dc(par, DC_FB_ACTIVE, + (info->var.xres - 1) << 16 | (info->var.yres - 1)); + + /* And re-enable the graphics output */ + lx_graphics_enable(info); + + /* Write the two main configuration registers */ + write_dc(par, DC_DISPLAY_CFG, dcfg); + write_dc(par, DC_ARB_CFG, 0); + write_dc(par, DC_GENERAL_CFG, gcfg); + + /* Lock the DC registers */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_LOCK); +} + +void lx_set_palette_reg(struct fb_info *info, unsigned regno, + unsigned red, unsigned green, unsigned blue) +{ + struct lxfb_par *par = info->par; + int val; + + /* Hardware palette is in RGB 8-8-8 format. */ + + val = (red << 8) & 0xff0000; + val |= (green) & 0x00ff00; + val |= (blue >> 8) & 0x0000ff; + + write_dc(par, DC_PAL_ADDRESS, regno); + write_dc(par, DC_PAL_DATA, val); +} + +int lx_blank_display(struct fb_info *info, int blank_mode) +{ + struct lxfb_par *par = info->par; + u32 dcfg, misc, fp_pm; + int blank, hsync, vsync; + + /* CRT power saving modes. */ + switch (blank_mode) { + case FB_BLANK_UNBLANK: + blank = 0; hsync = 1; vsync = 1; + break; + case FB_BLANK_NORMAL: + blank = 1; hsync = 1; vsync = 1; + break; + case FB_BLANK_VSYNC_SUSPEND: + blank = 1; hsync = 1; vsync = 0; + break; + case FB_BLANK_HSYNC_SUSPEND: + blank = 1; hsync = 0; vsync = 1; + break; + case FB_BLANK_POWERDOWN: + blank = 1; hsync = 0; vsync = 0; + break; + default: + return -EINVAL; + } + + dcfg = read_vp(par, VP_DCFG); + dcfg &= ~(VP_DCFG_DAC_BL_EN | VP_DCFG_HSYNC_EN | VP_DCFG_VSYNC_EN | + VP_DCFG_CRT_EN); + if (!blank) + dcfg |= VP_DCFG_DAC_BL_EN | VP_DCFG_CRT_EN; + if (hsync) + dcfg |= VP_DCFG_HSYNC_EN; + if (vsync) + dcfg |= VP_DCFG_VSYNC_EN; + + write_vp(par, VP_DCFG, dcfg); + + misc = read_vp(par, VP_MISC); + + if (vsync && hsync) + misc &= ~VP_MISC_DACPWRDN; + else + misc |= VP_MISC_DACPWRDN; + + write_vp(par, VP_MISC, misc); + + /* Power on/off flat panel */ + + if (par->output & OUTPUT_PANEL) { + fp_pm = read_fp(par, FP_PM); + if (blank_mode == FB_BLANK_POWERDOWN) + fp_pm &= ~FP_PM_P; + else + fp_pm |= FP_PM_P; + write_fp(par, FP_PM, fp_pm); + } + + return 0; +} + +#ifdef CONFIG_PM + +static void lx_save_regs(struct lxfb_par *par) +{ + uint32_t filt; + int i; + + /* wait for the BLT engine to stop being busy */ + do { + i = read_gp(par, GP_BLT_STATUS); + } while ((i & GP_BLT_STATUS_PB) || !(i & GP_BLT_STATUS_CE)); + + /* save MSRs */ + rdmsrl(MSR_LX_MSR_PADSEL, par->msr.padsel); + rdmsrl(MSR_GLCP_DOTPLL, par->msr.dotpll); + rdmsrl(MSR_LX_GLD_MSR_CONFIG, par->msr.dfglcfg); + rdmsrl(MSR_LX_SPARE_MSR, par->msr.dcspare); + + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + + /* save registers */ + memcpy(par->gp, par->gp_regs, sizeof(par->gp)); + memcpy(par->dc, par->dc_regs, sizeof(par->dc)); + memcpy(par->vp, par->vp_regs, sizeof(par->vp)); + memcpy(par->fp, par->vp_regs + VP_FP_START, sizeof(par->fp)); + + /* save the display controller palette */ + write_dc(par, DC_PAL_ADDRESS, 0); + for (i = 0; i < ARRAY_SIZE(par->dc_pal); i++) + par->dc_pal[i] = read_dc(par, DC_PAL_DATA); + + /* save the video processor palette */ + write_vp(par, VP_PAR, 0); + for (i = 0; i < ARRAY_SIZE(par->vp_pal); i++) + par->vp_pal[i] = read_vp(par, VP_PDR); + + /* save the horizontal filter coefficients */ + filt = par->dc[DC_IRQ_FILT_CTL] | DC_IRQ_FILT_CTL_H_FILT_SEL; + for (i = 0; i < ARRAY_SIZE(par->hcoeff); i += 2) { + write_dc(par, DC_IRQ_FILT_CTL, (filt & 0xffffff00) | i); + par->hcoeff[i] = read_dc(par, DC_FILT_COEFF1); + par->hcoeff[i + 1] = read_dc(par, DC_FILT_COEFF2); + } + + /* save the vertical filter coefficients */ + filt &= ~DC_IRQ_FILT_CTL_H_FILT_SEL; + for (i = 0; i < ARRAY_SIZE(par->vcoeff); i++) { + write_dc(par, DC_IRQ_FILT_CTL, (filt & 0xffffff00) | i); + par->vcoeff[i] = read_dc(par, DC_FILT_COEFF1); + } + + /* save video coeff ram */ + memcpy(par->vp_coeff, par->vp_regs + VP_VCR, sizeof(par->vp_coeff)); +} + +static void lx_restore_gfx_proc(struct lxfb_par *par) +{ + int i; + + /* a bunch of registers require GP_RASTER_MODE to be set first */ + write_gp(par, GP_RASTER_MODE, par->gp[GP_RASTER_MODE]); + + for (i = 0; i < ARRAY_SIZE(par->gp); i++) { + switch (i) { + case GP_RASTER_MODE: + case GP_VECTOR_MODE: + case GP_BLT_MODE: + case GP_BLT_STATUS: + case GP_HST_SRC: + /* FIXME: restore LUT data */ + case GP_LUT_INDEX: + case GP_LUT_DATA: + /* don't restore these registers */ + break; + + default: + write_gp(par, i, par->gp[i]); + } + } +} + +static void lx_restore_display_ctlr(struct lxfb_par *par) +{ + uint32_t filt; + int i; + + wrmsrl(MSR_LX_SPARE_MSR, par->msr.dcspare); + + for (i = 0; i < ARRAY_SIZE(par->dc); i++) { + switch (i) { + case DC_UNLOCK: + /* unlock the DC; runs first */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + break; + + case DC_GENERAL_CFG: + case DC_DISPLAY_CFG: + /* disable all while restoring */ + write_dc(par, i, 0); + break; + + case DC_DV_CTL: + /* set all ram to dirty */ + write_dc(par, i, par->dc[i] | DC_DV_CTL_CLEAR_DV_RAM); + + case DC_RSVD_1: + case DC_RSVD_2: + case DC_RSVD_3: + case DC_LINE_CNT: + case DC_PAL_ADDRESS: + case DC_PAL_DATA: + case DC_DFIFO_DIAG: + case DC_CFIFO_DIAG: + case DC_FILT_COEFF1: + case DC_FILT_COEFF2: + case DC_RSVD_4: + case DC_RSVD_5: + /* don't restore these registers */ + break; + + default: + write_dc(par, i, par->dc[i]); + } + } + + /* restore the palette */ + write_dc(par, DC_PAL_ADDRESS, 0); + for (i = 0; i < ARRAY_SIZE(par->dc_pal); i++) + write_dc(par, DC_PAL_DATA, par->dc_pal[i]); + + /* restore the horizontal filter coefficients */ + filt = par->dc[DC_IRQ_FILT_CTL] | DC_IRQ_FILT_CTL_H_FILT_SEL; + for (i = 0; i < ARRAY_SIZE(par->hcoeff); i += 2) { + write_dc(par, DC_IRQ_FILT_CTL, (filt & 0xffffff00) | i); + write_dc(par, DC_FILT_COEFF1, par->hcoeff[i]); + write_dc(par, DC_FILT_COEFF2, par->hcoeff[i + 1]); + } + + /* restore the vertical filter coefficients */ + filt &= ~DC_IRQ_FILT_CTL_H_FILT_SEL; + for (i = 0; i < ARRAY_SIZE(par->vcoeff); i++) { + write_dc(par, DC_IRQ_FILT_CTL, (filt & 0xffffff00) | i); + write_dc(par, DC_FILT_COEFF1, par->vcoeff[i]); + } +} + +static void lx_restore_video_proc(struct lxfb_par *par) +{ + int i; + + wrmsrl(MSR_LX_GLD_MSR_CONFIG, par->msr.dfglcfg); + wrmsrl(MSR_LX_MSR_PADSEL, par->msr.padsel); + + for (i = 0; i < ARRAY_SIZE(par->vp); i++) { + switch (i) { + case VP_VCFG: + case VP_DCFG: + case VP_PAR: + case VP_PDR: + case VP_CCS: + case VP_RSVD_0: + /* case VP_VDC: */ /* why should this not be restored? */ + case VP_RSVD_1: + case VP_CRC32: + /* don't restore these registers */ + break; + + default: + write_vp(par, i, par->vp[i]); + } + } + + /* restore video processor palette */ + write_vp(par, VP_PAR, 0); + for (i = 0; i < ARRAY_SIZE(par->vp_pal); i++) + write_vp(par, VP_PDR, par->vp_pal[i]); + + /* restore video coeff ram */ + memcpy(par->vp_regs + VP_VCR, par->vp_coeff, sizeof(par->vp_coeff)); +} + +static void lx_restore_regs(struct lxfb_par *par) +{ + int i; + + lx_set_dotpll((u32) (par->msr.dotpll >> 32)); + lx_restore_gfx_proc(par); + lx_restore_display_ctlr(par); + lx_restore_video_proc(par); + + /* Flat Panel */ + for (i = 0; i < ARRAY_SIZE(par->fp); i++) { + switch (i) { + case FP_PM: + case FP_RSVD_0: + case FP_RSVD_1: + case FP_RSVD_2: + case FP_RSVD_3: + case FP_RSVD_4: + /* don't restore these registers */ + break; + + default: + write_fp(par, i, par->fp[i]); + } + } + + /* control the panel */ + if (par->fp[FP_PM] & FP_PM_P) { + /* power on the panel if not already power{ed,ing} on */ + if (!(read_fp(par, FP_PM) & + (FP_PM_PANEL_ON|FP_PM_PANEL_PWR_UP))) + write_fp(par, FP_PM, par->fp[FP_PM]); + } else { + /* power down the panel if not already power{ed,ing} down */ + if (!(read_fp(par, FP_PM) & + (FP_PM_PANEL_OFF|FP_PM_PANEL_PWR_DOWN))) + write_fp(par, FP_PM, par->fp[FP_PM]); + } + + /* turn everything on */ + write_vp(par, VP_VCFG, par->vp[VP_VCFG]); + write_vp(par, VP_DCFG, par->vp[VP_DCFG]); + write_dc(par, DC_DISPLAY_CFG, par->dc[DC_DISPLAY_CFG]); + /* do this last; it will enable the FIFO load */ + write_dc(par, DC_GENERAL_CFG, par->dc[DC_GENERAL_CFG]); + + /* lock the door behind us */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_LOCK); +} + +int lx_powerdown(struct fb_info *info) +{ + struct lxfb_par *par = info->par; + + if (par->powered_down) + return 0; + + lx_save_regs(par); + lx_graphics_disable(info); + + par->powered_down = 1; + return 0; +} + +int lx_powerup(struct fb_info *info) +{ + struct lxfb_par *par = info->par; + + if (!par->powered_down) + return 0; + + lx_restore_regs(par); + + par->powered_down = 0; + return 0; +} + +#endif diff --git a/drivers/video/fbdev/geode/suspend_gx.c b/drivers/video/fbdev/geode/suspend_gx.c new file mode 100644 index 000000000000..1bb043d70c64 --- /dev/null +++ b/drivers/video/fbdev/geode/suspend_gx.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2007 Advanced Micro Devices, Inc. + * Copyright (C) 2008 Andres Salomon <dilinger@debian.org> + * + * 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. + */ +#include <linux/fb.h> +#include <asm/io.h> +#include <asm/msr.h> +#include <linux/cs5535.h> +#include <asm/delay.h> + +#include "gxfb.h" + +#ifdef CONFIG_PM + +static void gx_save_regs(struct gxfb_par *par) +{ + int i; + + /* wait for the BLT engine to stop being busy */ + do { + i = read_gp(par, GP_BLT_STATUS); + } while (i & (GP_BLT_STATUS_BLT_PENDING | GP_BLT_STATUS_BLT_BUSY)); + + /* save MSRs */ + rdmsrl(MSR_GX_MSR_PADSEL, par->msr.padsel); + rdmsrl(MSR_GLCP_DOTPLL, par->msr.dotpll); + + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + + /* save registers */ + memcpy(par->gp, par->gp_regs, sizeof(par->gp)); + memcpy(par->dc, par->dc_regs, sizeof(par->dc)); + memcpy(par->vp, par->vid_regs, sizeof(par->vp)); + memcpy(par->fp, par->vid_regs + VP_FP_START, sizeof(par->fp)); + + /* save the palette */ + write_dc(par, DC_PAL_ADDRESS, 0); + for (i = 0; i < ARRAY_SIZE(par->pal); i++) + par->pal[i] = read_dc(par, DC_PAL_DATA); +} + +static void gx_set_dotpll(uint32_t dotpll_hi) +{ + uint32_t dotpll_lo; + int i; + + rdmsrl(MSR_GLCP_DOTPLL, dotpll_lo); + dotpll_lo |= MSR_GLCP_DOTPLL_DOTRESET; + dotpll_lo &= ~MSR_GLCP_DOTPLL_BYPASS; + wrmsr(MSR_GLCP_DOTPLL, dotpll_lo, dotpll_hi); + + /* wait for the PLL to lock */ + for (i = 0; i < 200; i++) { + rdmsrl(MSR_GLCP_DOTPLL, dotpll_lo); + if (dotpll_lo & MSR_GLCP_DOTPLL_LOCK) + break; + udelay(1); + } + + /* PLL set, unlock */ + dotpll_lo &= ~MSR_GLCP_DOTPLL_DOTRESET; + wrmsr(MSR_GLCP_DOTPLL, dotpll_lo, dotpll_hi); +} + +static void gx_restore_gfx_proc(struct gxfb_par *par) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(par->gp); i++) { + switch (i) { + case GP_VECTOR_MODE: + case GP_BLT_MODE: + case GP_BLT_STATUS: + case GP_HST_SRC: + /* don't restore these registers */ + break; + default: + write_gp(par, i, par->gp[i]); + } + } +} + +static void gx_restore_display_ctlr(struct gxfb_par *par) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(par->dc); i++) { + switch (i) { + case DC_UNLOCK: + /* unlock the DC; runs first */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + break; + + case DC_GENERAL_CFG: + /* write without the enables */ + write_dc(par, i, par->dc[i] & ~(DC_GENERAL_CFG_VIDE | + DC_GENERAL_CFG_ICNE | + DC_GENERAL_CFG_CURE | + DC_GENERAL_CFG_DFLE)); + break; + + case DC_DISPLAY_CFG: + /* write without the enables */ + write_dc(par, i, par->dc[i] & ~(DC_DISPLAY_CFG_VDEN | + DC_DISPLAY_CFG_GDEN | + DC_DISPLAY_CFG_TGEN)); + break; + + case DC_RSVD_0: + case DC_RSVD_1: + case DC_RSVD_2: + case DC_RSVD_3: + case DC_RSVD_4: + case DC_LINE_CNT: + case DC_PAL_ADDRESS: + case DC_PAL_DATA: + case DC_DFIFO_DIAG: + case DC_CFIFO_DIAG: + case DC_RSVD_5: + /* don't restore these registers */ + break; + default: + write_dc(par, i, par->dc[i]); + } + } + + /* restore the palette */ + write_dc(par, DC_PAL_ADDRESS, 0); + for (i = 0; i < ARRAY_SIZE(par->pal); i++) + write_dc(par, DC_PAL_DATA, par->pal[i]); +} + +static void gx_restore_video_proc(struct gxfb_par *par) +{ + int i; + + wrmsrl(MSR_GX_MSR_PADSEL, par->msr.padsel); + + for (i = 0; i < ARRAY_SIZE(par->vp); i++) { + switch (i) { + case VP_VCFG: + /* don't enable video yet */ + write_vp(par, i, par->vp[i] & ~VP_VCFG_VID_EN); + break; + + case VP_DCFG: + /* don't enable CRT yet */ + write_vp(par, i, par->vp[i] & + ~(VP_DCFG_DAC_BL_EN | VP_DCFG_VSYNC_EN | + VP_DCFG_HSYNC_EN | VP_DCFG_CRT_EN)); + break; + + case VP_GAR: + case VP_GDR: + case VP_RSVD_0: + case VP_RSVD_1: + case VP_RSVD_2: + case VP_RSVD_3: + case VP_CRC32: + case VP_AWT: + case VP_VTM: + /* don't restore these registers */ + break; + default: + write_vp(par, i, par->vp[i]); + } + } +} + +static void gx_restore_regs(struct gxfb_par *par) +{ + int i; + + gx_set_dotpll((uint32_t) (par->msr.dotpll >> 32)); + gx_restore_gfx_proc(par); + gx_restore_display_ctlr(par); + gx_restore_video_proc(par); + + /* Flat Panel */ + for (i = 0; i < ARRAY_SIZE(par->fp); i++) { + if (i != FP_PM && i != FP_RSVD_0) + write_fp(par, i, par->fp[i]); + } +} + +static void gx_disable_graphics(struct gxfb_par *par) +{ + /* shut down the engine */ + write_vp(par, VP_VCFG, par->vp[VP_VCFG] & ~VP_VCFG_VID_EN); + write_vp(par, VP_DCFG, par->vp[VP_DCFG] & ~(VP_DCFG_DAC_BL_EN | + VP_DCFG_VSYNC_EN | VP_DCFG_HSYNC_EN | VP_DCFG_CRT_EN)); + + /* turn off the flat panel */ + write_fp(par, FP_PM, par->fp[FP_PM] & ~FP_PM_P); + + + /* turn off display */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK); + write_dc(par, DC_GENERAL_CFG, par->dc[DC_GENERAL_CFG] & + ~(DC_GENERAL_CFG_VIDE | DC_GENERAL_CFG_ICNE | + DC_GENERAL_CFG_CURE | DC_GENERAL_CFG_DFLE)); + write_dc(par, DC_DISPLAY_CFG, par->dc[DC_DISPLAY_CFG] & + ~(DC_DISPLAY_CFG_VDEN | DC_DISPLAY_CFG_GDEN | + DC_DISPLAY_CFG_TGEN)); + write_dc(par, DC_UNLOCK, DC_UNLOCK_LOCK); +} + +static void gx_enable_graphics(struct gxfb_par *par) +{ + uint32_t fp; + + fp = read_fp(par, FP_PM); + if (par->fp[FP_PM] & FP_PM_P) { + /* power on the panel if not already power{ed,ing} on */ + if (!(fp & (FP_PM_PANEL_ON|FP_PM_PANEL_PWR_UP))) + write_fp(par, FP_PM, par->fp[FP_PM]); + } else { + /* power down the panel if not already power{ed,ing} down */ + if (!(fp & (FP_PM_PANEL_OFF|FP_PM_PANEL_PWR_DOWN))) + write_fp(par, FP_PM, par->fp[FP_PM]); + } + + /* turn everything on */ + write_vp(par, VP_VCFG, par->vp[VP_VCFG]); + write_vp(par, VP_DCFG, par->vp[VP_DCFG]); + write_dc(par, DC_DISPLAY_CFG, par->dc[DC_DISPLAY_CFG]); + /* do this last; it will enable the FIFO load */ + write_dc(par, DC_GENERAL_CFG, par->dc[DC_GENERAL_CFG]); + + /* lock the door behind us */ + write_dc(par, DC_UNLOCK, DC_UNLOCK_LOCK); +} + +int gx_powerdown(struct fb_info *info) +{ + struct gxfb_par *par = info->par; + + if (par->powered_down) + return 0; + + gx_save_regs(par); + gx_disable_graphics(par); + + par->powered_down = 1; + return 0; +} + +int gx_powerup(struct fb_info *info) +{ + struct gxfb_par *par = info->par; + + if (!par->powered_down) + return 0; + + gx_restore_regs(par); + gx_enable_graphics(par); + + par->powered_down = 0; + return 0; +} + +#endif diff --git a/drivers/video/fbdev/geode/video_cs5530.c b/drivers/video/fbdev/geode/video_cs5530.c new file mode 100644 index 000000000000..649c3943d431 --- /dev/null +++ b/drivers/video/fbdev/geode/video_cs5530.c @@ -0,0 +1,193 @@ +/* + * drivers/video/geode/video_cs5530.c + * -- CS5530 video device + * + * Copyright (C) 2005 Arcom Control Systems Ltd. + * + * Based on AMD's original 2.4 driver: + * Copyright (C) 2004 Advanced Micro Devices, 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. + */ +#include <linux/fb.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/delay.h> + +#include "geodefb.h" +#include "video_cs5530.h" + +/* + * CS5530 PLL table. This maps pixclocks to the appropriate PLL register + * value. + */ +struct cs5530_pll_entry { + long pixclock; /* ps */ + u32 pll_value; +}; + +static const struct cs5530_pll_entry cs5530_pll_table[] = { + { 39721, 0x31C45801, }, /* 25.1750 MHz */ + { 35308, 0x20E36802, }, /* 28.3220 */ + { 31746, 0x33915801, }, /* 31.5000 */ + { 27777, 0x31EC4801, }, /* 36.0000 */ + { 26666, 0x21E22801, }, /* 37.5000 */ + { 25000, 0x33088801, }, /* 40.0000 */ + { 22271, 0x33E22801, }, /* 44.9000 */ + { 20202, 0x336C4801, }, /* 49.5000 */ + { 20000, 0x23088801, }, /* 50.0000 */ + { 19860, 0x23088801, }, /* 50.3500 */ + { 18518, 0x3708A801, }, /* 54.0000 */ + { 17777, 0x23E36802, }, /* 56.2500 */ + { 17733, 0x23E36802, }, /* 56.3916 */ + { 17653, 0x23E36802, }, /* 56.6444 */ + { 16949, 0x37C45801, }, /* 59.0000 */ + { 15873, 0x23EC4801, }, /* 63.0000 */ + { 15384, 0x37911801, }, /* 65.0000 */ + { 14814, 0x37963803, }, /* 67.5000 */ + { 14124, 0x37058803, }, /* 70.8000 */ + { 13888, 0x3710C805, }, /* 72.0000 */ + { 13333, 0x37E22801, }, /* 75.0000 */ + { 12698, 0x27915801, }, /* 78.7500 */ + { 12500, 0x37D8D802, }, /* 80.0000 */ + { 11135, 0x27588802, }, /* 89.8000 */ + { 10582, 0x27EC4802, }, /* 94.5000 */ + { 10101, 0x27AC6803, }, /* 99.0000 */ + { 10000, 0x27088801, }, /* 100.0000 */ + { 9259, 0x2710C805, }, /* 108.0000 */ + { 8888, 0x27E36802, }, /* 112.5000 */ + { 7692, 0x27C58803, }, /* 130.0000 */ + { 7407, 0x27316803, }, /* 135.0000 */ + { 6349, 0x2F915801, }, /* 157.5000 */ + { 6172, 0x2F08A801, }, /* 162.0000 */ + { 5714, 0x2FB11802, }, /* 175.0000 */ + { 5291, 0x2FEC4802, }, /* 189.0000 */ + { 4950, 0x2F963803, }, /* 202.0000 */ + { 4310, 0x2FB1B802, }, /* 232.0000 */ +}; + +static void cs5530_set_dclk_frequency(struct fb_info *info) +{ + struct geodefb_par *par = info->par; + int i; + u32 value; + long min, diff; + + /* Search the table for the closest pixclock. */ + value = cs5530_pll_table[0].pll_value; + min = cs5530_pll_table[0].pixclock - info->var.pixclock; + if (min < 0) min = -min; + for (i = 1; i < ARRAY_SIZE(cs5530_pll_table); i++) { + diff = cs5530_pll_table[i].pixclock - info->var.pixclock; + if (diff < 0L) diff = -diff; + if (diff < min) { + min = diff; + value = cs5530_pll_table[i].pll_value; + } + } + + writel(value, par->vid_regs + CS5530_DOT_CLK_CONFIG); + writel(value | 0x80000100, par->vid_regs + CS5530_DOT_CLK_CONFIG); /* set reset and bypass */ + udelay(500); /* wait for PLL to settle */ + writel(value & 0x7FFFFFFF, par->vid_regs + CS5530_DOT_CLK_CONFIG); /* clear reset */ + writel(value & 0x7FFFFEFF, par->vid_regs + CS5530_DOT_CLK_CONFIG); /* clear bypass */ +} + +static void cs5530_configure_display(struct fb_info *info) +{ + struct geodefb_par *par = info->par; + u32 dcfg; + + dcfg = readl(par->vid_regs + CS5530_DISPLAY_CONFIG); + + /* Clear bits from existing mode. */ + dcfg &= ~(CS5530_DCFG_CRT_SYNC_SKW_MASK | CS5530_DCFG_PWR_SEQ_DLY_MASK + | CS5530_DCFG_CRT_HSYNC_POL | CS5530_DCFG_CRT_VSYNC_POL + | CS5530_DCFG_FP_PWR_EN | CS5530_DCFG_FP_DATA_EN + | CS5530_DCFG_DAC_PWR_EN | CS5530_DCFG_VSYNC_EN + | CS5530_DCFG_HSYNC_EN); + + /* Set default sync skew and power sequence delays. */ + dcfg |= (CS5530_DCFG_CRT_SYNC_SKW_INIT | CS5530_DCFG_PWR_SEQ_DLY_INIT + | CS5530_DCFG_GV_PAL_BYP); + + /* Enable DACs, hsync and vsync for CRTs */ + if (par->enable_crt) { + dcfg |= CS5530_DCFG_DAC_PWR_EN; + dcfg |= CS5530_DCFG_HSYNC_EN | CS5530_DCFG_VSYNC_EN; + } + /* Enable panel power and data if using a flat panel. */ + if (par->panel_x > 0) { + dcfg |= CS5530_DCFG_FP_PWR_EN; + dcfg |= CS5530_DCFG_FP_DATA_EN; + } + + /* Sync polarities. */ + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) + dcfg |= CS5530_DCFG_CRT_HSYNC_POL; + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) + dcfg |= CS5530_DCFG_CRT_VSYNC_POL; + + writel(dcfg, par->vid_regs + CS5530_DISPLAY_CONFIG); +} + +static int cs5530_blank_display(struct fb_info *info, int blank_mode) +{ + struct geodefb_par *par = info->par; + u32 dcfg; + int blank, hsync, vsync; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + blank = 0; hsync = 1; vsync = 1; + break; + case FB_BLANK_NORMAL: + blank = 1; hsync = 1; vsync = 1; + break; + case FB_BLANK_VSYNC_SUSPEND: + blank = 1; hsync = 1; vsync = 0; + break; + case FB_BLANK_HSYNC_SUSPEND: + blank = 1; hsync = 0; vsync = 1; + break; + case FB_BLANK_POWERDOWN: + blank = 1; hsync = 0; vsync = 0; + break; + default: + return -EINVAL; + } + + dcfg = readl(par->vid_regs + CS5530_DISPLAY_CONFIG); + + dcfg &= ~(CS5530_DCFG_DAC_BL_EN | CS5530_DCFG_DAC_PWR_EN + | CS5530_DCFG_HSYNC_EN | CS5530_DCFG_VSYNC_EN + | CS5530_DCFG_FP_DATA_EN | CS5530_DCFG_FP_PWR_EN); + + if (par->enable_crt) { + if (!blank) + dcfg |= CS5530_DCFG_DAC_BL_EN | CS5530_DCFG_DAC_PWR_EN; + if (hsync) + dcfg |= CS5530_DCFG_HSYNC_EN; + if (vsync) + dcfg |= CS5530_DCFG_VSYNC_EN; + } + if (par->panel_x > 0) { + if (!blank) + dcfg |= CS5530_DCFG_FP_DATA_EN; + if (hsync && vsync) + dcfg |= CS5530_DCFG_FP_PWR_EN; + } + + writel(dcfg, par->vid_regs + CS5530_DISPLAY_CONFIG); + + return 0; +} + +struct geode_vid_ops cs5530_vid_ops = { + .set_dclk = cs5530_set_dclk_frequency, + .configure_display = cs5530_configure_display, + .blank_display = cs5530_blank_display, +}; diff --git a/drivers/video/fbdev/geode/video_cs5530.h b/drivers/video/fbdev/geode/video_cs5530.h new file mode 100644 index 000000000000..56cecca7f1ce --- /dev/null +++ b/drivers/video/fbdev/geode/video_cs5530.h @@ -0,0 +1,75 @@ +/* + * drivers/video/geode/video_cs5530.h + * -- CS5530 video device + * + * Copyright (C) 2005 Arcom Control Systems Ltd. + * + * Based on AMD's original 2.4 driver: + * Copyright (C) 2004 Advanced Micro Devices, 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. + */ +#ifndef __VIDEO_CS5530_H__ +#define __VIDEO_CS5530_H__ + +extern struct geode_vid_ops cs5530_vid_ops; + +/* CS5530 Video device registers */ + +#define CS5530_VIDEO_CONFIG 0x0000 +# define CS5530_VCFG_VID_EN 0x00000001 +# define CS5530_VCFG_VID_REG_UPDATE 0x00000002 +# define CS5530_VCFG_VID_INP_FORMAT 0x0000000C +# define CS5530_VCFG_8_BIT_4_2_0 0x00000004 +# define CS5530_VCFG_16_BIT_4_2_0 0x00000008 +# define CS5530_VCFG_GV_SEL 0x00000010 +# define CS5530_VCFG_CSC_BYPASS 0x00000020 +# define CS5530_VCFG_X_FILTER_EN 0x00000040 +# define CS5530_VCFG_Y_FILTER_EN 0x00000080 +# define CS5530_VCFG_LINE_SIZE_LOWER_MASK 0x0000FF00 +# define CS5530_VCFG_INIT_READ_MASK 0x01FF0000 +# define CS5530_VCFG_EARLY_VID_RDY 0x02000000 +# define CS5530_VCFG_LINE_SIZE_UPPER 0x08000000 +# define CS5530_VCFG_4_2_0_MODE 0x10000000 +# define CS5530_VCFG_16_BIT_EN 0x20000000 +# define CS5530_VCFG_HIGH_SPD_INT 0x40000000 + +#define CS5530_DISPLAY_CONFIG 0x0004 +# define CS5530_DCFG_DIS_EN 0x00000001 +# define CS5530_DCFG_HSYNC_EN 0x00000002 +# define CS5530_DCFG_VSYNC_EN 0x00000004 +# define CS5530_DCFG_DAC_BL_EN 0x00000008 +# define CS5530_DCFG_DAC_PWR_EN 0x00000020 +# define CS5530_DCFG_FP_PWR_EN 0x00000040 +# define CS5530_DCFG_FP_DATA_EN 0x00000080 +# define CS5530_DCFG_CRT_HSYNC_POL 0x00000100 +# define CS5530_DCFG_CRT_VSYNC_POL 0x00000200 +# define CS5530_DCFG_FP_HSYNC_POL 0x00000400 +# define CS5530_DCFG_FP_VSYNC_POL 0x00000800 +# define CS5530_DCFG_XGA_FP 0x00001000 +# define CS5530_DCFG_FP_DITH_EN 0x00002000 +# define CS5530_DCFG_CRT_SYNC_SKW_MASK 0x0001C000 +# define CS5530_DCFG_CRT_SYNC_SKW_INIT 0x00010000 +# define CS5530_DCFG_PWR_SEQ_DLY_MASK 0x000E0000 +# define CS5530_DCFG_PWR_SEQ_DLY_INIT 0x00080000 +# define CS5530_DCFG_VG_CK 0x00100000 +# define CS5530_DCFG_GV_PAL_BYP 0x00200000 +# define CS5530_DCFG_DDC_SCL 0x00400000 +# define CS5530_DCFG_DDC_SDA 0x00800000 +# define CS5530_DCFG_DDC_OE 0x01000000 +# define CS5530_DCFG_16_BIT_EN 0x02000000 + +#define CS5530_VIDEO_X_POS 0x0008 +#define CS5530_VIDEO_Y_POS 0x000C +#define CS5530_VIDEO_SCALE 0x0010 +#define CS5530_VIDEO_COLOR_KEY 0x0014 +#define CS5530_VIDEO_COLOR_MASK 0x0018 +#define CS5530_PALETTE_ADDRESS 0x001C +#define CS5530_PALETTE_DATA 0x0020 +#define CS5530_DOT_CLK_CONFIG 0x0024 +#define CS5530_CRCSIG_TFT_TV 0x0028 + +#endif /* !__VIDEO_CS5530_H__ */ diff --git a/drivers/video/fbdev/geode/video_gx.c b/drivers/video/fbdev/geode/video_gx.c new file mode 100644 index 000000000000..6082f653c68a --- /dev/null +++ b/drivers/video/fbdev/geode/video_gx.c @@ -0,0 +1,349 @@ +/* + * Geode GX video processor device. + * + * Copyright (C) 2006 Arcom Control Systems Ltd. + * + * Portions from AMD's original 2.4 driver: + * Copyright (C) 2004 Advanced Micro Devices, 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. + */ +#include <linux/fb.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/msr.h> +#include <linux/cs5535.h> + +#include "gxfb.h" + + +/* + * Tables of register settings for various DOTCLKs. + */ +struct gx_pll_entry { + long pixclock; /* ps */ + u32 sys_rstpll_bits; + u32 dotpll_value; +}; + +#define POSTDIV3 ((u32)MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3) +#define PREMULT2 ((u32)MSR_GLCP_SYS_RSTPLL_DOTPREMULT2) +#define PREDIV2 ((u32)MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3) + +static const struct gx_pll_entry gx_pll_table_48MHz[] = { + { 40123, POSTDIV3, 0x00000BF2 }, /* 24.9230 */ + { 39721, 0, 0x00000037 }, /* 25.1750 */ + { 35308, POSTDIV3|PREMULT2, 0x00000B1A }, /* 28.3220 */ + { 31746, POSTDIV3, 0x000002D2 }, /* 31.5000 */ + { 27777, POSTDIV3|PREMULT2, 0x00000FE2 }, /* 36.0000 */ + { 26666, POSTDIV3, 0x0000057A }, /* 37.5000 */ + { 25000, POSTDIV3, 0x0000030A }, /* 40.0000 */ + { 22271, 0, 0x00000063 }, /* 44.9000 */ + { 20202, 0, 0x0000054B }, /* 49.5000 */ + { 20000, 0, 0x0000026E }, /* 50.0000 */ + { 19860, PREMULT2, 0x00000037 }, /* 50.3500 */ + { 18518, POSTDIV3|PREMULT2, 0x00000B0D }, /* 54.0000 */ + { 17777, 0, 0x00000577 }, /* 56.2500 */ + { 17733, 0, 0x000007F7 }, /* 56.3916 */ + { 17653, 0, 0x0000057B }, /* 56.6444 */ + { 16949, PREMULT2, 0x00000707 }, /* 59.0000 */ + { 15873, POSTDIV3|PREMULT2, 0x00000B39 }, /* 63.0000 */ + { 15384, POSTDIV3|PREMULT2, 0x00000B45 }, /* 65.0000 */ + { 14814, POSTDIV3|PREMULT2, 0x00000FC1 }, /* 67.5000 */ + { 14124, POSTDIV3, 0x00000561 }, /* 70.8000 */ + { 13888, POSTDIV3, 0x000007E1 }, /* 72.0000 */ + { 13426, PREMULT2, 0x00000F4A }, /* 74.4810 */ + { 13333, 0, 0x00000052 }, /* 75.0000 */ + { 12698, 0, 0x00000056 }, /* 78.7500 */ + { 12500, POSTDIV3|PREMULT2, 0x00000709 }, /* 80.0000 */ + { 11135, PREMULT2, 0x00000262 }, /* 89.8000 */ + { 10582, 0, 0x000002D2 }, /* 94.5000 */ + { 10101, PREMULT2, 0x00000B4A }, /* 99.0000 */ + { 10000, PREMULT2, 0x00000036 }, /* 100.0000 */ + { 9259, 0, 0x000007E2 }, /* 108.0000 */ + { 8888, 0, 0x000007F6 }, /* 112.5000 */ + { 7692, POSTDIV3|PREMULT2, 0x00000FB0 }, /* 130.0000 */ + { 7407, POSTDIV3|PREMULT2, 0x00000B50 }, /* 135.0000 */ + { 6349, 0, 0x00000055 }, /* 157.5000 */ + { 6172, 0, 0x000009C1 }, /* 162.0000 */ + { 5787, PREMULT2, 0x0000002D }, /* 172.798 */ + { 5698, 0, 0x000002C1 }, /* 175.5000 */ + { 5291, 0, 0x000002D1 }, /* 189.0000 */ + { 4938, 0, 0x00000551 }, /* 202.5000 */ + { 4357, 0, 0x0000057D }, /* 229.5000 */ +}; + +static const struct gx_pll_entry gx_pll_table_14MHz[] = { + { 39721, 0, 0x00000037 }, /* 25.1750 */ + { 35308, 0, 0x00000B7B }, /* 28.3220 */ + { 31746, 0, 0x000004D3 }, /* 31.5000 */ + { 27777, 0, 0x00000BE3 }, /* 36.0000 */ + { 26666, 0, 0x0000074F }, /* 37.5000 */ + { 25000, 0, 0x0000050B }, /* 40.0000 */ + { 22271, 0, 0x00000063 }, /* 44.9000 */ + { 20202, 0, 0x0000054B }, /* 49.5000 */ + { 20000, 0, 0x0000026E }, /* 50.0000 */ + { 19860, 0, 0x000007C3 }, /* 50.3500 */ + { 18518, 0, 0x000007E3 }, /* 54.0000 */ + { 17777, 0, 0x00000577 }, /* 56.2500 */ + { 17733, 0, 0x000002FB }, /* 56.3916 */ + { 17653, 0, 0x0000057B }, /* 56.6444 */ + { 16949, 0, 0x0000058B }, /* 59.0000 */ + { 15873, 0, 0x0000095E }, /* 63.0000 */ + { 15384, 0, 0x0000096A }, /* 65.0000 */ + { 14814, 0, 0x00000BC2 }, /* 67.5000 */ + { 14124, 0, 0x0000098A }, /* 70.8000 */ + { 13888, 0, 0x00000BE2 }, /* 72.0000 */ + { 13333, 0, 0x00000052 }, /* 75.0000 */ + { 12698, 0, 0x00000056 }, /* 78.7500 */ + { 12500, 0, 0x0000050A }, /* 80.0000 */ + { 11135, 0, 0x0000078E }, /* 89.8000 */ + { 10582, 0, 0x000002D2 }, /* 94.5000 */ + { 10101, 0, 0x000011F6 }, /* 99.0000 */ + { 10000, 0, 0x0000054E }, /* 100.0000 */ + { 9259, 0, 0x000007E2 }, /* 108.0000 */ + { 8888, 0, 0x000002FA }, /* 112.5000 */ + { 7692, 0, 0x00000BB1 }, /* 130.0000 */ + { 7407, 0, 0x00000975 }, /* 135.0000 */ + { 6349, 0, 0x00000055 }, /* 157.5000 */ + { 6172, 0, 0x000009C1 }, /* 162.0000 */ + { 5698, 0, 0x000002C1 }, /* 175.5000 */ + { 5291, 0, 0x00000539 }, /* 189.0000 */ + { 4938, 0, 0x00000551 }, /* 202.5000 */ + { 4357, 0, 0x0000057D }, /* 229.5000 */ +}; + +void gx_set_dclk_frequency(struct fb_info *info) +{ + const struct gx_pll_entry *pll_table; + int pll_table_len; + int i, best_i; + long min, diff; + u64 dotpll, sys_rstpll; + int timeout = 1000; + + /* Rev. 1 Geode GXs use a 14 MHz reference clock instead of 48 MHz. */ + if (cpu_data(0).x86_mask == 1) { + pll_table = gx_pll_table_14MHz; + pll_table_len = ARRAY_SIZE(gx_pll_table_14MHz); + } else { + pll_table = gx_pll_table_48MHz; + pll_table_len = ARRAY_SIZE(gx_pll_table_48MHz); + } + + /* Search the table for the closest pixclock. */ + best_i = 0; + min = abs(pll_table[0].pixclock - info->var.pixclock); + for (i = 1; i < pll_table_len; i++) { + diff = abs(pll_table[i].pixclock - info->var.pixclock); + if (diff < min) { + min = diff; + best_i = i; + } + } + + rdmsrl(MSR_GLCP_SYS_RSTPLL, sys_rstpll); + rdmsrl(MSR_GLCP_DOTPLL, dotpll); + + /* Program new M, N and P. */ + dotpll &= 0x00000000ffffffffull; + dotpll |= (u64)pll_table[best_i].dotpll_value << 32; + dotpll |= MSR_GLCP_DOTPLL_DOTRESET; + dotpll &= ~MSR_GLCP_DOTPLL_BYPASS; + + wrmsrl(MSR_GLCP_DOTPLL, dotpll); + + /* Program dividers. */ + sys_rstpll &= ~( MSR_GLCP_SYS_RSTPLL_DOTPREDIV2 + | MSR_GLCP_SYS_RSTPLL_DOTPREMULT2 + | MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3 ); + sys_rstpll |= pll_table[best_i].sys_rstpll_bits; + + wrmsrl(MSR_GLCP_SYS_RSTPLL, sys_rstpll); + + /* Clear reset bit to start PLL. */ + dotpll &= ~(MSR_GLCP_DOTPLL_DOTRESET); + wrmsrl(MSR_GLCP_DOTPLL, dotpll); + + /* Wait for LOCK bit. */ + do { + rdmsrl(MSR_GLCP_DOTPLL, dotpll); + } while (timeout-- && !(dotpll & MSR_GLCP_DOTPLL_LOCK)); +} + +static void +gx_configure_tft(struct fb_info *info) +{ + struct gxfb_par *par = info->par; + unsigned long val; + unsigned long fp; + + /* Set up the DF pad select MSR */ + + rdmsrl(MSR_GX_MSR_PADSEL, val); + val &= ~MSR_GX_MSR_PADSEL_MASK; + val |= MSR_GX_MSR_PADSEL_TFT; + wrmsrl(MSR_GX_MSR_PADSEL, val); + + /* Turn off the panel */ + + fp = read_fp(par, FP_PM); + fp &= ~FP_PM_P; + write_fp(par, FP_PM, fp); + + /* Set timing 1 */ + + fp = read_fp(par, FP_PT1); + fp &= FP_PT1_VSIZE_MASK; + fp |= info->var.yres << FP_PT1_VSIZE_SHIFT; + write_fp(par, FP_PT1, fp); + + /* Timing 2 */ + /* Set bits that are always on for TFT */ + + fp = 0x0F100000; + + /* Configure sync polarity */ + + if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) + fp |= FP_PT2_VSP; + + if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) + fp |= FP_PT2_HSP; + + write_fp(par, FP_PT2, fp); + + /* Set the dither control */ + write_fp(par, FP_DFC, FP_DFC_NFI); + + /* Enable the FP data and power (in case the BIOS didn't) */ + + fp = read_vp(par, VP_DCFG); + fp |= VP_DCFG_FP_PWR_EN | VP_DCFG_FP_DATA_EN; + write_vp(par, VP_DCFG, fp); + + /* Unblank the panel */ + + fp = read_fp(par, FP_PM); + fp |= FP_PM_P; + write_fp(par, FP_PM, fp); +} + +void gx_configure_display(struct fb_info *info) +{ + struct gxfb_par *par = info->par; + u32 dcfg, misc; + + /* Write the display configuration */ + dcfg = read_vp(par, VP_DCFG); + + /* Disable hsync and vsync */ + dcfg &= ~(VP_DCFG_VSYNC_EN | VP_DCFG_HSYNC_EN); + write_vp(par, VP_DCFG, dcfg); + + /* Clear bits from existing mode. */ + dcfg &= ~(VP_DCFG_CRT_SYNC_SKW + | VP_DCFG_CRT_HSYNC_POL | VP_DCFG_CRT_VSYNC_POL + | VP_DCFG_VSYNC_EN | VP_DCFG_HSYNC_EN); + + /* Set default sync skew. */ + dcfg |= VP_DCFG_CRT_SYNC_SKW_DEFAULT; + + /* Enable hsync and vsync. */ + dcfg |= VP_DCFG_HSYNC_EN | VP_DCFG_VSYNC_EN; + + misc = read_vp(par, VP_MISC); + + /* Disable gamma correction */ + misc |= VP_MISC_GAM_EN; + + if (par->enable_crt) { + + /* Power up the CRT DACs */ + misc &= ~(VP_MISC_APWRDN | VP_MISC_DACPWRDN); + write_vp(par, VP_MISC, misc); + + /* Only change the sync polarities if we are running + * in CRT mode. The FP polarities will be handled in + * gxfb_configure_tft */ + if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) + dcfg |= VP_DCFG_CRT_HSYNC_POL; + if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) + dcfg |= VP_DCFG_CRT_VSYNC_POL; + } else { + /* Power down the CRT DACs if in FP mode */ + misc |= (VP_MISC_APWRDN | VP_MISC_DACPWRDN); + write_vp(par, VP_MISC, misc); + } + + /* Enable the display logic */ + /* Set up the DACS to blank normally */ + + dcfg |= VP_DCFG_CRT_EN | VP_DCFG_DAC_BL_EN; + + /* Enable the external DAC VREF? */ + + write_vp(par, VP_DCFG, dcfg); + + /* Set up the flat panel (if it is enabled) */ + + if (par->enable_crt == 0) + gx_configure_tft(info); +} + +int gx_blank_display(struct fb_info *info, int blank_mode) +{ + struct gxfb_par *par = info->par; + u32 dcfg, fp_pm; + int blank, hsync, vsync, crt; + + /* CRT power saving modes. */ + switch (blank_mode) { + case FB_BLANK_UNBLANK: + blank = 0; hsync = 1; vsync = 1; crt = 1; + break; + case FB_BLANK_NORMAL: + blank = 1; hsync = 1; vsync = 1; crt = 1; + break; + case FB_BLANK_VSYNC_SUSPEND: + blank = 1; hsync = 1; vsync = 0; crt = 1; + break; + case FB_BLANK_HSYNC_SUSPEND: + blank = 1; hsync = 0; vsync = 1; crt = 1; + break; + case FB_BLANK_POWERDOWN: + blank = 1; hsync = 0; vsync = 0; crt = 0; + break; + default: + return -EINVAL; + } + dcfg = read_vp(par, VP_DCFG); + dcfg &= ~(VP_DCFG_DAC_BL_EN | VP_DCFG_HSYNC_EN | VP_DCFG_VSYNC_EN | + VP_DCFG_CRT_EN); + if (!blank) + dcfg |= VP_DCFG_DAC_BL_EN; + if (hsync) + dcfg |= VP_DCFG_HSYNC_EN; + if (vsync) + dcfg |= VP_DCFG_VSYNC_EN; + if (crt) + dcfg |= VP_DCFG_CRT_EN; + write_vp(par, VP_DCFG, dcfg); + + /* Power on/off flat panel. */ + + if (par->enable_crt == 0) { + fp_pm = read_fp(par, FP_PM); + if (blank_mode == FB_BLANK_POWERDOWN) + fp_pm &= ~FP_PM_P; + else + fp_pm |= FP_PM_P; + write_fp(par, FP_PM, fp_pm); + } + + return 0; +} diff --git a/drivers/video/fbdev/goldfishfb.c b/drivers/video/fbdev/goldfishfb.c new file mode 100644 index 000000000000..7f6c9e6cfc6c --- /dev/null +++ b/drivers/video/fbdev/goldfishfb.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (C) 2012 Intel, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/dma-mapping.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/interrupt.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> + +enum { + FB_GET_WIDTH = 0x00, + FB_GET_HEIGHT = 0x04, + FB_INT_STATUS = 0x08, + FB_INT_ENABLE = 0x0c, + FB_SET_BASE = 0x10, + FB_SET_ROTATION = 0x14, + FB_SET_BLANK = 0x18, + FB_GET_PHYS_WIDTH = 0x1c, + FB_GET_PHYS_HEIGHT = 0x20, + + FB_INT_VSYNC = 1U << 0, + FB_INT_BASE_UPDATE_DONE = 1U << 1 +}; + +struct goldfish_fb { + void __iomem *reg_base; + int irq; + spinlock_t lock; + wait_queue_head_t wait; + int base_update_count; + int rotation; + struct fb_info fb; + u32 cmap[16]; +}; + +static irqreturn_t goldfish_fb_interrupt(int irq, void *dev_id) +{ + unsigned long irq_flags; + struct goldfish_fb *fb = dev_id; + u32 status; + + spin_lock_irqsave(&fb->lock, irq_flags); + status = readl(fb->reg_base + FB_INT_STATUS); + if (status & FB_INT_BASE_UPDATE_DONE) { + fb->base_update_count++; + wake_up(&fb->wait); + } + spin_unlock_irqrestore(&fb->lock, irq_flags); + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static inline u32 convert_bitfield(int val, struct fb_bitfield *bf) +{ + unsigned int mask = (1 << bf->length) - 1; + + return (val >> (16 - bf->length) & mask) << bf->offset; +} + +static int +goldfish_fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, + unsigned int blue, unsigned int transp, struct fb_info *info) +{ + struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb); + + if (regno < 16) { + fb->cmap[regno] = convert_bitfield(transp, &fb->fb.var.transp) | + convert_bitfield(blue, &fb->fb.var.blue) | + convert_bitfield(green, &fb->fb.var.green) | + convert_bitfield(red, &fb->fb.var.red); + return 0; + } else { + return 1; + } +} + +static int goldfish_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if ((var->rotate & 1) != (info->var.rotate & 1)) { + if ((var->xres != info->var.yres) || + (var->yres != info->var.xres) || + (var->xres_virtual != info->var.yres) || + (var->yres_virtual > info->var.xres * 2) || + (var->yres_virtual < info->var.xres)) { + return -EINVAL; + } + } else { + if ((var->xres != info->var.xres) || + (var->yres != info->var.yres) || + (var->xres_virtual != info->var.xres) || + (var->yres_virtual > info->var.yres * 2) || + (var->yres_virtual < info->var.yres)) { + return -EINVAL; + } + } + if ((var->xoffset != info->var.xoffset) || + (var->bits_per_pixel != info->var.bits_per_pixel) || + (var->grayscale != info->var.grayscale)) { + return -EINVAL; + } + return 0; +} + +static int goldfish_fb_set_par(struct fb_info *info) +{ + struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb); + if (fb->rotation != fb->fb.var.rotate) { + info->fix.line_length = info->var.xres * 2; + fb->rotation = fb->fb.var.rotate; + writel(fb->rotation, fb->reg_base + FB_SET_ROTATION); + } + return 0; +} + + +static int goldfish_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + unsigned long irq_flags; + int base_update_count; + struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb); + + spin_lock_irqsave(&fb->lock, irq_flags); + base_update_count = fb->base_update_count; + writel(fb->fb.fix.smem_start + fb->fb.var.xres * 2 * var->yoffset, + fb->reg_base + FB_SET_BASE); + spin_unlock_irqrestore(&fb->lock, irq_flags); + wait_event_timeout(fb->wait, + fb->base_update_count != base_update_count, HZ / 15); + if (fb->base_update_count == base_update_count) + pr_err("goldfish_fb_pan_display: timeout waiting for base update\n"); + return 0; +} + +static int goldfish_fb_blank(int blank, struct fb_info *info) +{ + struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb); + switch (blank) { + case FB_BLANK_NORMAL: + writel(1, fb->reg_base + FB_SET_BLANK); + break; + case FB_BLANK_UNBLANK: + writel(0, fb->reg_base + FB_SET_BLANK); + break; + } + return 0; +} + +static struct fb_ops goldfish_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = goldfish_fb_check_var, + .fb_set_par = goldfish_fb_set_par, + .fb_setcolreg = goldfish_fb_setcolreg, + .fb_pan_display = goldfish_fb_pan_display, + .fb_blank = goldfish_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + + +static int goldfish_fb_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct goldfish_fb *fb; + size_t framesize; + u32 width, height; + dma_addr_t fbpaddr; + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (fb == NULL) { + ret = -ENOMEM; + goto err_fb_alloc_failed; + } + spin_lock_init(&fb->lock); + init_waitqueue_head(&fb->wait); + platform_set_drvdata(pdev, fb); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + ret = -ENODEV; + goto err_no_io_base; + } + fb->reg_base = ioremap(r->start, PAGE_SIZE); + if (fb->reg_base == NULL) { + ret = -ENOMEM; + goto err_no_io_base; + } + + fb->irq = platform_get_irq(pdev, 0); + if (fb->irq <= 0) { + ret = -ENODEV; + goto err_no_irq; + } + + width = readl(fb->reg_base + FB_GET_WIDTH); + height = readl(fb->reg_base + FB_GET_HEIGHT); + + fb->fb.fbops = &goldfish_fb_ops; + fb->fb.flags = FBINFO_FLAG_DEFAULT; + fb->fb.pseudo_palette = fb->cmap; + fb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + fb->fb.fix.line_length = width * 2; + fb->fb.fix.accel = FB_ACCEL_NONE; + fb->fb.fix.ypanstep = 1; + + fb->fb.var.xres = width; + fb->fb.var.yres = height; + fb->fb.var.xres_virtual = width; + fb->fb.var.yres_virtual = height * 2; + fb->fb.var.bits_per_pixel = 16; + fb->fb.var.activate = FB_ACTIVATE_NOW; + fb->fb.var.height = readl(fb->reg_base + FB_GET_PHYS_HEIGHT); + fb->fb.var.width = readl(fb->reg_base + FB_GET_PHYS_WIDTH); + fb->fb.var.pixclock = 10000; + + fb->fb.var.red.offset = 11; + fb->fb.var.red.length = 5; + fb->fb.var.green.offset = 5; + fb->fb.var.green.length = 6; + fb->fb.var.blue.offset = 0; + fb->fb.var.blue.length = 5; + + framesize = width * height * 2 * 2; + fb->fb.screen_base = (char __force __iomem *)dma_alloc_coherent( + &pdev->dev, framesize, + &fbpaddr, GFP_KERNEL); + pr_debug("allocating frame buffer %d * %d, got %p\n", + width, height, fb->fb.screen_base); + if (fb->fb.screen_base == NULL) { + ret = -ENOMEM; + goto err_alloc_screen_base_failed; + } + fb->fb.fix.smem_start = fbpaddr; + fb->fb.fix.smem_len = framesize; + + ret = fb_set_var(&fb->fb, &fb->fb.var); + if (ret) + goto err_fb_set_var_failed; + + ret = request_irq(fb->irq, goldfish_fb_interrupt, IRQF_SHARED, + pdev->name, fb); + if (ret) + goto err_request_irq_failed; + + writel(FB_INT_BASE_UPDATE_DONE, fb->reg_base + FB_INT_ENABLE); + goldfish_fb_pan_display(&fb->fb.var, &fb->fb); /* updates base */ + + ret = register_framebuffer(&fb->fb); + if (ret) + goto err_register_framebuffer_failed; + return 0; + +err_register_framebuffer_failed: + free_irq(fb->irq, fb); +err_request_irq_failed: +err_fb_set_var_failed: + dma_free_coherent(&pdev->dev, framesize, + (void *)fb->fb.screen_base, + fb->fb.fix.smem_start); +err_alloc_screen_base_failed: +err_no_irq: + iounmap(fb->reg_base); +err_no_io_base: + kfree(fb); +err_fb_alloc_failed: + return ret; +} + +static int goldfish_fb_remove(struct platform_device *pdev) +{ + size_t framesize; + struct goldfish_fb *fb = platform_get_drvdata(pdev); + + framesize = fb->fb.var.xres_virtual * fb->fb.var.yres_virtual * 2; + unregister_framebuffer(&fb->fb); + free_irq(fb->irq, fb); + + dma_free_coherent(&pdev->dev, framesize, (void *)fb->fb.screen_base, + fb->fb.fix.smem_start); + iounmap(fb->reg_base); + return 0; +} + + +static struct platform_driver goldfish_fb_driver = { + .probe = goldfish_fb_probe, + .remove = goldfish_fb_remove, + .driver = { + .name = "goldfish_fb" + } +}; + +module_platform_driver(goldfish_fb_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/grvga.c b/drivers/video/fbdev/grvga.c new file mode 100644 index 000000000000..c078701f15f6 --- /dev/null +++ b/drivers/video/fbdev/grvga.c @@ -0,0 +1,562 @@ +/* + * Driver for Aeroflex Gaisler SVGACTRL framebuffer device. + * + * 2011 (c) Aeroflex Gaisler AB + * + * Full documentation of the core can be found here: + * http://www.gaisler.com/products/grlib/grip.pdf + * + * 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. + * + * Contributors: Kristoffer Glembo <kristoffer@gaisler.com> + * + */ + +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/of_platform.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/io.h> + +struct grvga_regs { + u32 status; /* 0x00 */ + u32 video_length; /* 0x04 */ + u32 front_porch; /* 0x08 */ + u32 sync_length; /* 0x0C */ + u32 line_length; /* 0x10 */ + u32 fb_pos; /* 0x14 */ + u32 clk_vector[4]; /* 0x18 */ + u32 clut; /* 0x20 */ +}; + +struct grvga_par { + struct grvga_regs *regs; + u32 color_palette[16]; /* 16 entry pseudo palette used by fbcon in true color mode */ + int clk_sel; + int fb_alloced; /* = 1 if framebuffer is allocated in main memory */ +}; + + +static const struct fb_videomode grvga_modedb[] = { + { + /* 640x480 @ 60 Hz */ + NULL, 60, 640, 480, 40000, 48, 16, 39, 11, 96, 2, + 0, FB_VMODE_NONINTERLACED + }, { + /* 800x600 @ 60 Hz */ + NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4, + 0, FB_VMODE_NONINTERLACED + }, { + /* 800x600 @ 72 Hz */ + NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6, + 0, FB_VMODE_NONINTERLACED + }, { + /* 1024x768 @ 60 Hz */ + NULL, 60, 1024, 768, 15385, 160, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED + } + }; + +static struct fb_fix_screeninfo grvga_fix = { + .id = "AG SVGACTRL", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 0, + .ypanstep = 1, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static int grvga_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct grvga_par *par = info->par; + int i; + + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + 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 <= 24) + var->bits_per_pixel = 24; + else if (var->bits_per_pixel <= 32) + var->bits_per_pixel = 32; + else + return -EINVAL; + + var->xres_virtual = var->xres; + var->yres_virtual = 2*var->yres; + + if (info->fix.smem_len) { + if ((var->yres_virtual*var->xres_virtual*var->bits_per_pixel/8) > info->fix.smem_len) + return -ENOMEM; + } + + /* Which clocks that are available can be read out in these registers */ + for (i = 0; i <= 3 ; i++) { + if (var->pixclock == par->regs->clk_vector[i]) + break; + } + if (i <= 3) + par->clk_sel = i; + else + return -EINVAL; + + switch (info->var.bits_per_pixel) { + case 8: + var->red = (struct fb_bitfield) {0, 8, 0}; /* offset, length, msb-right */ + var->green = (struct fb_bitfield) {0, 8, 0}; + var->blue = (struct fb_bitfield) {0, 8, 0}; + var->transp = (struct fb_bitfield) {0, 0, 0}; + break; + case 16: + var->red = (struct fb_bitfield) {11, 5, 0}; + var->green = (struct fb_bitfield) {5, 6, 0}; + var->blue = (struct fb_bitfield) {0, 5, 0}; + var->transp = (struct fb_bitfield) {0, 0, 0}; + break; + case 24: + case 32: + var->red = (struct fb_bitfield) {16, 8, 0}; + var->green = (struct fb_bitfield) {8, 8, 0}; + var->blue = (struct fb_bitfield) {0, 8, 0}; + var->transp = (struct fb_bitfield) {24, 8, 0}; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int grvga_set_par(struct fb_info *info) +{ + + u32 func = 0; + struct grvga_par *par = info->par; + + __raw_writel(((info->var.yres - 1) << 16) | (info->var.xres - 1), + &par->regs->video_length); + + __raw_writel((info->var.lower_margin << 16) | (info->var.right_margin), + &par->regs->front_porch); + + __raw_writel((info->var.vsync_len << 16) | (info->var.hsync_len), + &par->regs->sync_length); + + __raw_writel(((info->var.yres + info->var.lower_margin + info->var.upper_margin + info->var.vsync_len - 1) << 16) | + (info->var.xres + info->var.right_margin + info->var.left_margin + info->var.hsync_len - 1), + &par->regs->line_length); + + switch (info->var.bits_per_pixel) { + case 8: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + func = 1; + break; + case 16: + info->fix.visual = FB_VISUAL_TRUECOLOR; + func = 2; + break; + case 24: + case 32: + info->fix.visual = FB_VISUAL_TRUECOLOR; + func = 3; + break; + default: + return -EINVAL; + } + + __raw_writel((par->clk_sel << 6) | (func << 4) | 1, + &par->regs->status); + + info->fix.line_length = (info->var.xres_virtual*info->var.bits_per_pixel)/8; + return 0; +} + +static int grvga_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) +{ + struct grvga_par *par; + par = info->par; + + if (regno >= 256) /* Size of CLUT */ + return -EINVAL; + + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + +#undef CNVT_TOHW + + /* In PSEUDOCOLOR we use the hardware CLUT */ + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) + __raw_writel((regno << 24) | (red << 16) | (green << 8) | blue, + &par->regs->clut); + + /* Truecolor uses the pseudo palette */ + else if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + if (regno >= 16) + return -EINVAL; + + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + ((u32 *) (info->pseudo_palette))[regno] = v; + } + return 0; +} + +static int grvga_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct grvga_par *par = info->par; + struct fb_fix_screeninfo *fix = &info->fix; + u32 base_addr; + + if (var->xoffset != 0) + return -EINVAL; + + base_addr = fix->smem_start + (var->yoffset * fix->line_length); + base_addr &= ~3UL; + + /* Set framebuffer base address */ + __raw_writel(base_addr, + &par->regs->fb_pos); + + return 0; +} + +static struct fb_ops grvga_ops = { + .owner = THIS_MODULE, + .fb_check_var = grvga_check_var, + .fb_set_par = grvga_set_par, + .fb_setcolreg = grvga_setcolreg, + .fb_pan_display = grvga_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit +}; + +static int grvga_parse_custom(char *options, + struct fb_var_screeninfo *screendata) +{ + char *this_opt; + int count = 0; + if (!options || !*options) + return -1; + + while ((this_opt = strsep(&options, " ")) != NULL) { + if (!*this_opt) + continue; + + switch (count) { + case 0: + screendata->pixclock = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 1: + screendata->xres = screendata->xres_virtual = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 2: + screendata->right_margin = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 3: + screendata->hsync_len = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 4: + screendata->left_margin = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 5: + screendata->yres = screendata->yres_virtual = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 6: + screendata->lower_margin = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 7: + screendata->vsync_len = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 8: + screendata->upper_margin = simple_strtoul(this_opt, NULL, 0); + count++; + break; + case 9: + screendata->bits_per_pixel = simple_strtoul(this_opt, NULL, 0); + count++; + break; + default: + return -1; + } + } + screendata->activate = FB_ACTIVATE_NOW; + screendata->vmode = FB_VMODE_NONINTERLACED; + return 0; +} + +static int grvga_probe(struct platform_device *dev) +{ + struct fb_info *info; + int retval = -ENOMEM; + unsigned long virtual_start; + unsigned long grvga_fix_addr = 0; + unsigned long physical_start = 0; + unsigned long grvga_mem_size = 0; + struct grvga_par *par = NULL; + char *options = NULL, *mode_opt = NULL; + + info = framebuffer_alloc(sizeof(struct grvga_par), &dev->dev); + if (!info) { + dev_err(&dev->dev, "framebuffer_alloc failed\n"); + return -ENOMEM; + } + + /* Expecting: "grvga: modestring, [addr:<framebuffer physical address>], [size:<framebuffer size>] + * + * If modestring is custom:<custom mode string> we parse the string which then contains all videoparameters + * If address is left out, we allocate memory, + * if size is left out we only allocate enough to support the given mode. + */ + if (fb_get_options("grvga", &options)) { + retval = -ENODEV; + goto free_fb; + } + + if (!options || !*options) + options = "640x480-8@60"; + + while (1) { + char *this_opt = strsep(&options, ","); + + if (!this_opt) + break; + + if (!strncmp(this_opt, "custom", 6)) { + if (grvga_parse_custom(this_opt, &info->var) < 0) { + dev_err(&dev->dev, "Failed to parse custom mode (%s).\n", this_opt); + retval = -EINVAL; + goto free_fb; + } + } else if (!strncmp(this_opt, "addr", 4)) + grvga_fix_addr = simple_strtoul(this_opt + 5, NULL, 16); + else if (!strncmp(this_opt, "size", 4)) + grvga_mem_size = simple_strtoul(this_opt + 5, NULL, 0); + else + mode_opt = this_opt; + } + + par = info->par; + info->fbops = &grvga_ops; + info->fix = grvga_fix; + info->pseudo_palette = par->color_palette; + info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | FBINFO_HWACCEL_YPAN; + info->fix.smem_len = grvga_mem_size; + + if (!devm_request_mem_region(&dev->dev, dev->resource[0].start, + resource_size(&dev->resource[0]), "grlib-svgactrl regs")) { + dev_err(&dev->dev, "registers already mapped\n"); + retval = -EBUSY; + goto free_fb; + } + + par->regs = of_ioremap(&dev->resource[0], 0, + resource_size(&dev->resource[0]), + "grlib-svgactrl regs"); + + if (!par->regs) { + dev_err(&dev->dev, "failed to map registers\n"); + retval = -ENOMEM; + goto free_fb; + } + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) { + dev_err(&dev->dev, "failed to allocate mem with fb_alloc_cmap\n"); + retval = -ENOMEM; + goto unmap_regs; + } + + if (mode_opt) { + retval = fb_find_mode(&info->var, info, mode_opt, + grvga_modedb, sizeof(grvga_modedb), &grvga_modedb[0], 8); + if (!retval || retval == 4) { + retval = -EINVAL; + goto dealloc_cmap; + } + } + + if (!grvga_mem_size) + grvga_mem_size = info->var.xres_virtual * info->var.yres_virtual * info->var.bits_per_pixel/8; + + if (grvga_fix_addr) { + /* Got framebuffer base address from argument list */ + + physical_start = grvga_fix_addr; + + if (!devm_request_mem_region(&dev->dev, physical_start, + grvga_mem_size, dev->name)) { + dev_err(&dev->dev, "failed to request memory region\n"); + retval = -ENOMEM; + goto dealloc_cmap; + } + + virtual_start = (unsigned long) ioremap(physical_start, grvga_mem_size); + + if (!virtual_start) { + dev_err(&dev->dev, "error mapping framebuffer memory\n"); + retval = -ENOMEM; + goto dealloc_cmap; + } + } else { /* Allocate frambuffer memory */ + + unsigned long page; + + virtual_start = (unsigned long) __get_free_pages(GFP_DMA, + get_order(grvga_mem_size)); + if (!virtual_start) { + dev_err(&dev->dev, + "unable to allocate framebuffer memory (%lu bytes)\n", + grvga_mem_size); + retval = -ENOMEM; + goto dealloc_cmap; + } + + physical_start = dma_map_single(&dev->dev, (void *)virtual_start, grvga_mem_size, DMA_TO_DEVICE); + + /* Set page reserved so that mmap will work. This is necessary + * since we'll be remapping normal memory. + */ + for (page = virtual_start; + page < PAGE_ALIGN(virtual_start + grvga_mem_size); + page += PAGE_SIZE) { + SetPageReserved(virt_to_page(page)); + } + + par->fb_alloced = 1; + } + + memset((unsigned long *) virtual_start, 0, grvga_mem_size); + + info->screen_base = (char __iomem *) virtual_start; + info->fix.smem_start = physical_start; + info->fix.smem_len = grvga_mem_size; + + dev_set_drvdata(&dev->dev, info); + + dev_info(&dev->dev, + "Aeroflex Gaisler framebuffer device (fb%d), %dx%d-%d, using %luK of video memory @ %p\n", + info->node, info->var.xres, info->var.yres, info->var.bits_per_pixel, + grvga_mem_size >> 10, info->screen_base); + + retval = register_framebuffer(info); + if (retval < 0) { + dev_err(&dev->dev, "failed to register framebuffer\n"); + goto free_mem; + } + + __raw_writel(physical_start, &par->regs->fb_pos); + __raw_writel(__raw_readl(&par->regs->status) | 1, /* Enable framebuffer */ + &par->regs->status); + + return 0; + +free_mem: + if (grvga_fix_addr) + iounmap((void *)virtual_start); + else + kfree((void *)virtual_start); +dealloc_cmap: + fb_dealloc_cmap(&info->cmap); +unmap_regs: + of_iounmap(&dev->resource[0], par->regs, + resource_size(&dev->resource[0])); +free_fb: + framebuffer_release(info); + + return retval; +} + +static int grvga_remove(struct platform_device *device) +{ + struct fb_info *info = dev_get_drvdata(&device->dev); + struct grvga_par *par = info->par; + + if (info) { + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + of_iounmap(&device->resource[0], par->regs, + resource_size(&device->resource[0])); + + if (!par->fb_alloced) + iounmap(info->screen_base); + else + kfree((void *)info->screen_base); + + framebuffer_release(info); + } + + return 0; +} + +static struct of_device_id svgactrl_of_match[] = { + { + .name = "GAISLER_SVGACTRL", + }, + { + .name = "01_063", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, svgactrl_of_match); + +static struct platform_driver grvga_driver = { + .driver = { + .name = "grlib-svgactrl", + .owner = THIS_MODULE, + .of_match_table = svgactrl_of_match, + }, + .probe = grvga_probe, + .remove = grvga_remove, +}; + +module_platform_driver(grvga_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Aeroflex Gaisler"); +MODULE_DESCRIPTION("Aeroflex Gaisler framebuffer device driver"); diff --git a/drivers/video/fbdev/gxt4500.c b/drivers/video/fbdev/gxt4500.c new file mode 100644 index 000000000000..135d78a02588 --- /dev/null +++ b/drivers/video/fbdev/gxt4500.c @@ -0,0 +1,783 @@ +/* + * Frame buffer device for IBM GXT4500P/6500P and GXT4000P/6000P + * display adaptors + * + * Copyright (C) 2006 Paul Mackerras, IBM Corp. <paulus@samba.org> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/delay.h> +#include <linux/string.h> + +#define PCI_DEVICE_ID_IBM_GXT4500P 0x21c +#define PCI_DEVICE_ID_IBM_GXT6500P 0x21b +#define PCI_DEVICE_ID_IBM_GXT4000P 0x16e +#define PCI_DEVICE_ID_IBM_GXT6000P 0x170 + +/* GXT4500P registers */ + +/* Registers in PCI config space */ +#define CFG_ENDIAN0 0x40 + +/* Misc control/status registers */ +#define STATUS 0x1000 +#define CTRL_REG0 0x1004 +#define CR0_HALT_DMA 0x4 +#define CR0_RASTER_RESET 0x8 +#define CR0_GEOM_RESET 0x10 +#define CR0_MEM_CTRLER_RESET 0x20 + +/* Framebuffer control registers */ +#define FB_AB_CTRL 0x1100 +#define FB_CD_CTRL 0x1104 +#define FB_WID_CTRL 0x1108 +#define FB_Z_CTRL 0x110c +#define FB_VGA_CTRL 0x1110 +#define REFRESH_AB_CTRL 0x1114 +#define REFRESH_CD_CTRL 0x1118 +#define FB_OVL_CTRL 0x111c +#define FB_CTRL_TYPE 0x80000000 +#define FB_CTRL_WIDTH_MASK 0x007f0000 +#define FB_CTRL_WIDTH_SHIFT 16 +#define FB_CTRL_START_SEG_MASK 0x00003fff + +#define REFRESH_START 0x1098 +#define REFRESH_SIZE 0x109c + +/* "Direct" framebuffer access registers */ +#define DFA_FB_A 0x11e0 +#define DFA_FB_B 0x11e4 +#define DFA_FB_C 0x11e8 +#define DFA_FB_D 0x11ec +#define DFA_FB_ENABLE 0x80000000 +#define DFA_FB_BASE_MASK 0x03f00000 +#define DFA_FB_STRIDE_1k 0x00000000 +#define DFA_FB_STRIDE_2k 0x00000010 +#define DFA_FB_STRIDE_4k 0x00000020 +#define DFA_PIX_8BIT 0x00000000 +#define DFA_PIX_16BIT_565 0x00000001 +#define DFA_PIX_16BIT_1555 0x00000002 +#define DFA_PIX_24BIT 0x00000004 +#define DFA_PIX_32BIT 0x00000005 + +/* maps DFA_PIX_* to pixel size in bytes */ +static const unsigned char pixsize[] = { + 1, 2, 2, 2, 4, 4 +}; + +/* Display timing generator registers */ +#define DTG_CONTROL 0x1900 +#define DTG_CTL_SCREEN_REFRESH 2 +#define DTG_CTL_ENABLE 1 +#define DTG_HORIZ_EXTENT 0x1904 +#define DTG_HORIZ_DISPLAY 0x1908 +#define DTG_HSYNC_START 0x190c +#define DTG_HSYNC_END 0x1910 +#define DTG_HSYNC_END_COMP 0x1914 +#define DTG_VERT_EXTENT 0x1918 +#define DTG_VERT_DISPLAY 0x191c +#define DTG_VSYNC_START 0x1920 +#define DTG_VSYNC_END 0x1924 +#define DTG_VERT_SHORT 0x1928 + +/* PLL/RAMDAC registers */ +#define DISP_CTL 0x402c +#define DISP_CTL_OFF 2 +#define SYNC_CTL 0x4034 +#define SYNC_CTL_SYNC_ON_RGB 1 +#define SYNC_CTL_SYNC_OFF 2 +#define SYNC_CTL_HSYNC_INV 8 +#define SYNC_CTL_VSYNC_INV 0x10 +#define SYNC_CTL_HSYNC_OFF 0x20 +#define SYNC_CTL_VSYNC_OFF 0x40 + +#define PLL_M 0x4040 +#define PLL_N 0x4044 +#define PLL_POSTDIV 0x4048 +#define PLL_C 0x404c + +/* Hardware cursor */ +#define CURSOR_X 0x4078 +#define CURSOR_Y 0x407c +#define CURSOR_HOTSPOT 0x4080 +#define CURSOR_MODE 0x4084 +#define CURSOR_MODE_OFF 0 +#define CURSOR_MODE_4BPP 1 +#define CURSOR_PIXMAP 0x5000 +#define CURSOR_CMAP 0x7400 + +/* Window attribute table */ +#define WAT_FMT 0x4100 +#define WAT_FMT_24BIT 0 +#define WAT_FMT_16BIT_565 1 +#define WAT_FMT_16BIT_1555 2 +#define WAT_FMT_32BIT 3 /* 0 vs. 3 is a guess */ +#define WAT_FMT_8BIT_332 9 +#define WAT_FMT_8BIT 0xa +#define WAT_FMT_NO_CMAP 4 /* ORd in to other values */ +#define WAT_CMAP_OFFSET 0x4104 /* 4-bit value gets << 6 */ +#define WAT_CTRL 0x4108 +#define WAT_CTRL_SEL_B 1 /* select B buffer if 1 */ +#define WAT_CTRL_NO_INC 2 +#define WAT_GAMMA_CTRL 0x410c +#define WAT_GAMMA_DISABLE 1 /* disables gamma cmap */ +#define WAT_OVL_CTRL 0x430c /* controls overlay */ + +/* Indexed by DFA_PIX_* values */ +static const unsigned char watfmt[] = { + WAT_FMT_8BIT, WAT_FMT_16BIT_565, WAT_FMT_16BIT_1555, 0, + WAT_FMT_24BIT, WAT_FMT_32BIT +}; + +/* Colormap array; 1k entries of 4 bytes each */ +#define CMAP 0x6000 + +#define readreg(par, reg) readl((par)->regs + (reg)) +#define writereg(par, reg, val) writel((val), (par)->regs + (reg)) + +struct gxt4500_par { + void __iomem *regs; + + int pixfmt; /* pixel format, see DFA_PIX_* values */ + + /* PLL parameters */ + int refclk_ps; /* ref clock period in picoseconds */ + int pll_m; /* ref clock divisor */ + int pll_n; /* VCO divisor */ + int pll_pd1; /* first post-divisor */ + int pll_pd2; /* second post-divisor */ + + u32 pseudo_palette[16]; /* used in color blits */ +}; + +/* mode requested by user */ +static char *mode_option; + +/* default mode: 1280x1024 @ 60 Hz, 8 bpp */ +static const struct fb_videomode defaultmode = { + .refresh = 60, + .xres = 1280, + .yres = 1024, + .pixclock = 9295, + .left_margin = 248, + .right_margin = 48, + .upper_margin = 38, + .lower_margin = 1, + .hsync_len = 112, + .vsync_len = 3, + .vmode = FB_VMODE_NONINTERLACED +}; + +/* List of supported cards */ +enum gxt_cards { + GXT4500P, + GXT6500P, + GXT4000P, + GXT6000P +}; + +/* Card-specific information */ +static const struct cardinfo { + int refclk_ps; /* period of PLL reference clock in ps */ + const char *cardname; +} cardinfo[] = { + [GXT4500P] = { .refclk_ps = 9259, .cardname = "IBM GXT4500P" }, + [GXT6500P] = { .refclk_ps = 9259, .cardname = "IBM GXT6500P" }, + [GXT4000P] = { .refclk_ps = 40000, .cardname = "IBM GXT4000P" }, + [GXT6000P] = { .refclk_ps = 40000, .cardname = "IBM GXT6000P" }, +}; + +/* + * The refclk and VCO dividers appear to use a linear feedback shift + * register, which gets reloaded when it reaches a terminal value, at + * which point the divider output is toggled. Thus one can obtain + * whatever divisor is required by putting the appropriate value into + * the reload register. For a divisor of N, one puts the value from + * the LFSR sequence that comes N-1 places before the terminal value + * into the reload register. + */ + +static const unsigned char mdivtab[] = { +/* 1 */ 0x3f, 0x00, 0x20, 0x10, 0x28, 0x14, 0x2a, 0x15, 0x0a, +/* 10 */ 0x25, 0x32, 0x19, 0x0c, 0x26, 0x13, 0x09, 0x04, 0x22, 0x11, +/* 20 */ 0x08, 0x24, 0x12, 0x29, 0x34, 0x1a, 0x2d, 0x36, 0x1b, 0x0d, +/* 30 */ 0x06, 0x23, 0x31, 0x38, 0x1c, 0x2e, 0x17, 0x0b, 0x05, 0x02, +/* 40 */ 0x21, 0x30, 0x18, 0x2c, 0x16, 0x2b, 0x35, 0x3a, 0x1d, 0x0e, +/* 50 */ 0x27, 0x33, 0x39, 0x3c, 0x1e, 0x2f, 0x37, 0x3b, 0x3d, 0x3e, +/* 60 */ 0x1f, 0x0f, 0x07, 0x03, 0x01, +}; + +static const unsigned char ndivtab[] = { +/* 2 */ 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0x78, 0xbc, 0x5e, +/* 10 */ 0x2f, 0x17, 0x0b, 0x85, 0xc2, 0xe1, 0x70, 0x38, 0x9c, 0x4e, +/* 20 */ 0xa7, 0xd3, 0xe9, 0xf4, 0xfa, 0xfd, 0xfe, 0x7f, 0xbf, 0xdf, +/* 30 */ 0xef, 0x77, 0x3b, 0x1d, 0x8e, 0xc7, 0xe3, 0x71, 0xb8, 0xdc, +/* 40 */ 0x6e, 0xb7, 0x5b, 0x2d, 0x16, 0x8b, 0xc5, 0xe2, 0xf1, 0xf8, +/* 50 */ 0xfc, 0x7e, 0x3f, 0x9f, 0xcf, 0x67, 0xb3, 0xd9, 0x6c, 0xb6, +/* 60 */ 0xdb, 0x6d, 0x36, 0x9b, 0x4d, 0x26, 0x13, 0x89, 0xc4, 0x62, +/* 70 */ 0xb1, 0xd8, 0xec, 0xf6, 0xfb, 0x7d, 0xbe, 0x5f, 0xaf, 0x57, +/* 80 */ 0x2b, 0x95, 0x4a, 0x25, 0x92, 0x49, 0xa4, 0x52, 0x29, 0x94, +/* 90 */ 0xca, 0x65, 0xb2, 0x59, 0x2c, 0x96, 0xcb, 0xe5, 0xf2, 0x79, +/* 100 */ 0x3c, 0x1e, 0x0f, 0x07, 0x83, 0x41, 0x20, 0x90, 0x48, 0x24, +/* 110 */ 0x12, 0x09, 0x84, 0x42, 0xa1, 0x50, 0x28, 0x14, 0x8a, 0x45, +/* 120 */ 0xa2, 0xd1, 0xe8, 0x74, 0xba, 0xdd, 0xee, 0xf7, 0x7b, 0x3d, +/* 130 */ 0x9e, 0x4f, 0x27, 0x93, 0xc9, 0xe4, 0x72, 0x39, 0x1c, 0x0e, +/* 140 */ 0x87, 0xc3, 0x61, 0x30, 0x18, 0x8c, 0xc6, 0x63, 0x31, 0x98, +/* 150 */ 0xcc, 0xe6, 0x73, 0xb9, 0x5c, 0x2e, 0x97, 0x4b, 0xa5, 0xd2, +/* 160 */ 0x69, +}; + +static int calc_pll(int period_ps, struct gxt4500_par *par) +{ + int m, n, pdiv1, pdiv2, postdiv; + int pll_period, best_error, t, intf; + + /* only deal with range 5MHz - 300MHz */ + if (period_ps < 3333 || period_ps > 200000) + return -1; + + best_error = 1000000; + for (pdiv1 = 1; pdiv1 <= 8; ++pdiv1) { + for (pdiv2 = 1; pdiv2 <= pdiv1; ++pdiv2) { + postdiv = pdiv1 * pdiv2; + pll_period = DIV_ROUND_UP(period_ps, postdiv); + /* keep pll in range 350..600 MHz */ + if (pll_period < 1666 || pll_period > 2857) + continue; + for (m = 1; m <= 64; ++m) { + intf = m * par->refclk_ps; + if (intf > 500000) + break; + n = intf * postdiv / period_ps; + if (n < 3 || n > 160) + continue; + t = par->refclk_ps * m * postdiv / n; + t -= period_ps; + if (t >= 0 && t < best_error) { + par->pll_m = m; + par->pll_n = n; + par->pll_pd1 = pdiv1; + par->pll_pd2 = pdiv2; + best_error = t; + } + } + } + } + if (best_error == 1000000) + return -1; + return 0; +} + +static int calc_pixclock(struct gxt4500_par *par) +{ + return par->refclk_ps * par->pll_m * par->pll_pd1 * par->pll_pd2 + / par->pll_n; +} + +static int gxt4500_var_to_par(struct fb_var_screeninfo *var, + struct gxt4500_par *par) +{ + if (var->xres + var->xoffset > var->xres_virtual || + var->yres + var->yoffset > var->yres_virtual || + var->xres_virtual > 4096) + return -EINVAL; + if ((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) + return -EINVAL; + + if (calc_pll(var->pixclock, par) < 0) + return -EINVAL; + + switch (var->bits_per_pixel) { + case 32: + if (var->transp.length) + par->pixfmt = DFA_PIX_32BIT; + else + par->pixfmt = DFA_PIX_24BIT; + break; + case 24: + par->pixfmt = DFA_PIX_24BIT; + break; + case 16: + if (var->green.length == 5) + par->pixfmt = DFA_PIX_16BIT_1555; + else + par->pixfmt = DFA_PIX_16BIT_565; + break; + case 8: + par->pixfmt = DFA_PIX_8BIT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct fb_bitfield eightbits = {0, 8}; +static const struct fb_bitfield nobits = {0, 0}; + +static void gxt4500_unpack_pixfmt(struct fb_var_screeninfo *var, + int pixfmt) +{ + var->bits_per_pixel = pixsize[pixfmt] * 8; + var->red = eightbits; + var->green = eightbits; + var->blue = eightbits; + var->transp = nobits; + + switch (pixfmt) { + case DFA_PIX_16BIT_565: + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + break; + case DFA_PIX_16BIT_1555: + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + var->transp.length = 1; + break; + case DFA_PIX_32BIT: + var->transp.length = 8; + break; + } + if (pixfmt != DFA_PIX_8BIT) { + var->green.offset = var->red.length; + var->blue.offset = var->green.offset + var->green.length; + if (var->transp.length) + var->transp.offset = + var->blue.offset + var->blue.length; + } +} + +static int gxt4500_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct gxt4500_par par; + int err; + + par = *(struct gxt4500_par *)info->par; + err = gxt4500_var_to_par(var, &par); + if (!err) { + var->pixclock = calc_pixclock(&par); + gxt4500_unpack_pixfmt(var, par.pixfmt); + } + return err; +} + +static int gxt4500_set_par(struct fb_info *info) +{ + struct gxt4500_par *par = info->par; + struct fb_var_screeninfo *var = &info->var; + int err; + u32 ctrlreg, tmp; + unsigned int dfa_ctl, pixfmt, stride; + unsigned int wid_tiles, i; + unsigned int prefetch_pix, htot; + struct gxt4500_par save_par; + + save_par = *par; + err = gxt4500_var_to_par(var, par); + if (err) { + *par = save_par; + return err; + } + + /* turn off DTG for now */ + ctrlreg = readreg(par, DTG_CONTROL); + ctrlreg &= ~(DTG_CTL_ENABLE | DTG_CTL_SCREEN_REFRESH); + writereg(par, DTG_CONTROL, ctrlreg); + + /* set PLL registers */ + tmp = readreg(par, PLL_C) & ~0x7f; + if (par->pll_n < 38) + tmp |= 0x29; + if (par->pll_n < 69) + tmp |= 0x35; + else if (par->pll_n < 100) + tmp |= 0x76; + else + tmp |= 0x7e; + writereg(par, PLL_C, tmp); + writereg(par, PLL_M, mdivtab[par->pll_m - 1]); + writereg(par, PLL_N, ndivtab[par->pll_n - 2]); + tmp = ((8 - par->pll_pd2) << 3) | (8 - par->pll_pd1); + if (par->pll_pd1 == 8 || par->pll_pd2 == 8) { + /* work around erratum */ + writereg(par, PLL_POSTDIV, tmp | 0x9); + udelay(1); + } + writereg(par, PLL_POSTDIV, tmp); + msleep(20); + + /* turn off hardware cursor */ + writereg(par, CURSOR_MODE, CURSOR_MODE_OFF); + + /* reset raster engine */ + writereg(par, CTRL_REG0, CR0_RASTER_RESET | (CR0_RASTER_RESET << 16)); + udelay(10); + writereg(par, CTRL_REG0, CR0_RASTER_RESET << 16); + + /* set display timing generator registers */ + htot = var->xres + var->left_margin + var->right_margin + + var->hsync_len; + writereg(par, DTG_HORIZ_EXTENT, htot - 1); + writereg(par, DTG_HORIZ_DISPLAY, var->xres - 1); + writereg(par, DTG_HSYNC_START, var->xres + var->right_margin - 1); + writereg(par, DTG_HSYNC_END, + var->xres + var->right_margin + var->hsync_len - 1); + writereg(par, DTG_HSYNC_END_COMP, + var->xres + var->right_margin + var->hsync_len - 1); + writereg(par, DTG_VERT_EXTENT, + var->yres + var->upper_margin + var->lower_margin + + var->vsync_len - 1); + writereg(par, DTG_VERT_DISPLAY, var->yres - 1); + writereg(par, DTG_VSYNC_START, var->yres + var->lower_margin - 1); + writereg(par, DTG_VSYNC_END, + var->yres + var->lower_margin + var->vsync_len - 1); + prefetch_pix = 3300000 / var->pixclock; + if (prefetch_pix >= htot) + prefetch_pix = htot - 1; + writereg(par, DTG_VERT_SHORT, htot - prefetch_pix - 1); + ctrlreg |= DTG_CTL_ENABLE | DTG_CTL_SCREEN_REFRESH; + writereg(par, DTG_CONTROL, ctrlreg); + + /* calculate stride in DFA aperture */ + if (var->xres_virtual > 2048) { + stride = 4096; + dfa_ctl = DFA_FB_STRIDE_4k; + } else if (var->xres_virtual > 1024) { + stride = 2048; + dfa_ctl = DFA_FB_STRIDE_2k; + } else { + stride = 1024; + dfa_ctl = DFA_FB_STRIDE_1k; + } + + /* Set up framebuffer definition */ + wid_tiles = (var->xres_virtual + 63) >> 6; + + /* XXX add proper FB allocation here someday */ + writereg(par, FB_AB_CTRL, FB_CTRL_TYPE | (wid_tiles << 16) | 0); + writereg(par, REFRESH_AB_CTRL, FB_CTRL_TYPE | (wid_tiles << 16) | 0); + writereg(par, FB_CD_CTRL, FB_CTRL_TYPE | (wid_tiles << 16) | 0); + writereg(par, REFRESH_CD_CTRL, FB_CTRL_TYPE | (wid_tiles << 16) | 0); + writereg(par, REFRESH_START, (var->xoffset << 16) | var->yoffset); + writereg(par, REFRESH_SIZE, (var->xres << 16) | var->yres); + + /* Set up framebuffer access by CPU */ + + pixfmt = par->pixfmt; + dfa_ctl |= DFA_FB_ENABLE | pixfmt; + writereg(par, DFA_FB_A, dfa_ctl); + + /* + * Set up window attribute table. + * We set all WAT entries the same so it doesn't matter what the + * window ID (WID) plane contains. + */ + for (i = 0; i < 32; ++i) { + writereg(par, WAT_FMT + (i << 4), watfmt[pixfmt]); + writereg(par, WAT_CMAP_OFFSET + (i << 4), 0); + writereg(par, WAT_CTRL + (i << 4), 0); + writereg(par, WAT_GAMMA_CTRL + (i << 4), WAT_GAMMA_DISABLE); + } + + /* Set sync polarity etc. */ + ctrlreg = readreg(par, SYNC_CTL) & + ~(SYNC_CTL_SYNC_ON_RGB | SYNC_CTL_HSYNC_INV | + SYNC_CTL_VSYNC_INV); + if (var->sync & FB_SYNC_ON_GREEN) + ctrlreg |= SYNC_CTL_SYNC_ON_RGB; + if (!(var->sync & FB_SYNC_HOR_HIGH_ACT)) + ctrlreg |= SYNC_CTL_HSYNC_INV; + if (!(var->sync & FB_SYNC_VERT_HIGH_ACT)) + ctrlreg |= SYNC_CTL_VSYNC_INV; + writereg(par, SYNC_CTL, ctrlreg); + + info->fix.line_length = stride * pixsize[pixfmt]; + info->fix.visual = (pixfmt == DFA_PIX_8BIT)? FB_VISUAL_PSEUDOCOLOR: + FB_VISUAL_DIRECTCOLOR; + + return 0; +} + +static int gxt4500_setcolreg(unsigned int reg, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + u32 cmap_entry; + struct gxt4500_par *par = info->par; + + if (reg > 1023) + return 1; + cmap_entry = ((transp & 0xff00) << 16) | ((red & 0xff00) << 8) | + (green & 0xff00) | (blue >> 8); + writereg(par, CMAP + reg * 4, cmap_entry); + + if (reg < 16 && par->pixfmt != DFA_PIX_8BIT) { + u32 *pal = info->pseudo_palette; + u32 val = reg; + switch (par->pixfmt) { + case DFA_PIX_16BIT_565: + val |= (reg << 11) | (reg << 6); + break; + case DFA_PIX_16BIT_1555: + val |= (reg << 10) | (reg << 5); + break; + case DFA_PIX_32BIT: + val |= (reg << 24); + /* fall through */ + case DFA_PIX_24BIT: + val |= (reg << 16) | (reg << 8); + break; + } + pal[reg] = val; + } + + return 0; +} + +static int gxt4500_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct gxt4500_par *par = info->par; + + if (var->xoffset & 7) + return -EINVAL; + if (var->xoffset + info->var.xres > info->var.xres_virtual || + var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + writereg(par, REFRESH_START, (var->xoffset << 16) | var->yoffset); + return 0; +} + +static int gxt4500_blank(int blank, struct fb_info *info) +{ + struct gxt4500_par *par = info->par; + int ctrl, dctl; + + ctrl = readreg(par, SYNC_CTL); + ctrl &= ~(SYNC_CTL_SYNC_OFF | SYNC_CTL_HSYNC_OFF | SYNC_CTL_VSYNC_OFF); + dctl = readreg(par, DISP_CTL); + dctl |= DISP_CTL_OFF; + switch (blank) { + case FB_BLANK_UNBLANK: + dctl &= ~DISP_CTL_OFF; + break; + case FB_BLANK_POWERDOWN: + ctrl |= SYNC_CTL_SYNC_OFF; + break; + case FB_BLANK_HSYNC_SUSPEND: + ctrl |= SYNC_CTL_HSYNC_OFF; + break; + case FB_BLANK_VSYNC_SUSPEND: + ctrl |= SYNC_CTL_VSYNC_OFF; + break; + default: ; + } + writereg(par, SYNC_CTL, ctrl); + writereg(par, DISP_CTL, dctl); + + return 0; +} + +static const struct fb_fix_screeninfo gxt4500_fix = { + .id = "IBM GXT4500P", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 8, + .ypanstep = 1, + .mmio_len = 0x20000, +}; + +static struct fb_ops gxt4500_ops = { + .owner = THIS_MODULE, + .fb_check_var = gxt4500_check_var, + .fb_set_par = gxt4500_set_par, + .fb_setcolreg = gxt4500_setcolreg, + .fb_pan_display = gxt4500_pan_display, + .fb_blank = gxt4500_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* PCI functions */ +static int gxt4500_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int err; + unsigned long reg_phys, fb_phys; + struct gxt4500_par *par; + struct fb_info *info; + struct fb_var_screeninfo var; + enum gxt_cards cardtype; + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "gxt4500: cannot enable PCI device: %d\n", + err); + return err; + } + + reg_phys = pci_resource_start(pdev, 0); + if (!request_mem_region(reg_phys, pci_resource_len(pdev, 0), + "gxt4500 regs")) { + dev_err(&pdev->dev, "gxt4500: cannot get registers\n"); + goto err_nodev; + } + + fb_phys = pci_resource_start(pdev, 1); + if (!request_mem_region(fb_phys, pci_resource_len(pdev, 1), + "gxt4500 FB")) { + dev_err(&pdev->dev, "gxt4500: cannot get framebuffer\n"); + goto err_free_regs; + } + + info = framebuffer_alloc(sizeof(struct gxt4500_par), &pdev->dev); + if (!info) { + dev_err(&pdev->dev, "gxt4500: cannot alloc FB info record\n"); + goto err_free_fb; + } + par = info->par; + cardtype = ent->driver_data; + par->refclk_ps = cardinfo[cardtype].refclk_ps; + info->fix = gxt4500_fix; + strlcpy(info->fix.id, cardinfo[cardtype].cardname, + sizeof(info->fix.id)); + info->pseudo_palette = par->pseudo_palette; + + info->fix.mmio_start = reg_phys; + par->regs = pci_ioremap_bar(pdev, 0); + if (!par->regs) { + dev_err(&pdev->dev, "gxt4500: cannot map registers\n"); + goto err_free_all; + } + + info->fix.smem_start = fb_phys; + info->fix.smem_len = pci_resource_len(pdev, 1); + info->screen_base = pci_ioremap_bar(pdev, 1); + if (!info->screen_base) { + dev_err(&pdev->dev, "gxt4500: cannot map framebuffer\n"); + goto err_unmap_regs; + } + + pci_set_drvdata(pdev, info); + + /* Set byte-swapping for DFA aperture for all pixel sizes */ + pci_write_config_dword(pdev, CFG_ENDIAN0, 0x333300); + + info->fbops = &gxt4500_ops; + info->flags = FBINFO_FLAG_DEFAULT; + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err) { + dev_err(&pdev->dev, "gxt4500: cannot allocate cmap\n"); + goto err_unmap_all; + } + + gxt4500_blank(FB_BLANK_UNBLANK, info); + + if (!fb_find_mode(&var, info, mode_option, NULL, 0, &defaultmode, 8)) { + dev_err(&pdev->dev, "gxt4500: cannot find valid video mode\n"); + goto err_free_cmap; + } + info->var = var; + if (gxt4500_set_par(info)) { + printk(KERN_ERR "gxt4500: cannot set video mode\n"); + goto err_free_cmap; + } + + if (register_framebuffer(info) < 0) { + dev_err(&pdev->dev, "gxt4500: cannot register framebuffer\n"); + goto err_free_cmap; + } + fb_info(info, "%s frame buffer device\n", info->fix.id); + + return 0; + + err_free_cmap: + fb_dealloc_cmap(&info->cmap); + err_unmap_all: + iounmap(info->screen_base); + err_unmap_regs: + iounmap(par->regs); + err_free_all: + framebuffer_release(info); + err_free_fb: + release_mem_region(fb_phys, pci_resource_len(pdev, 1)); + err_free_regs: + release_mem_region(reg_phys, pci_resource_len(pdev, 0)); + err_nodev: + return -ENODEV; +} + +static void gxt4500_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct gxt4500_par *par; + + if (!info) + return; + par = info->par; + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + iounmap(par->regs); + iounmap(info->screen_base); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + release_mem_region(pci_resource_start(pdev, 1), + pci_resource_len(pdev, 1)); + framebuffer_release(info); +} + +/* supported chipsets */ +static const struct pci_device_id gxt4500_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_IBM, PCI_DEVICE_ID_IBM_GXT4500P), + .driver_data = GXT4500P }, + { PCI_DEVICE(PCI_VENDOR_ID_IBM, PCI_DEVICE_ID_IBM_GXT6500P), + .driver_data = GXT6500P }, + { PCI_DEVICE(PCI_VENDOR_ID_IBM, PCI_DEVICE_ID_IBM_GXT4000P), + .driver_data = GXT4000P }, + { PCI_DEVICE(PCI_VENDOR_ID_IBM, PCI_DEVICE_ID_IBM_GXT6000P), + .driver_data = GXT6000P }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, gxt4500_pci_tbl); + +static struct pci_driver gxt4500_driver = { + .name = "gxt4500", + .id_table = gxt4500_pci_tbl, + .probe = gxt4500_probe, + .remove = gxt4500_remove, +}; + +static int gxt4500_init(void) +{ +#ifndef MODULE + if (fb_get_options("gxt4500", &mode_option)) + return -ENODEV; +#endif + + return pci_register_driver(&gxt4500_driver); +} +module_init(gxt4500_init); + +static void __exit gxt4500_exit(void) +{ + pci_unregister_driver(&gxt4500_driver); +} +module_exit(gxt4500_exit); + +MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>"); +MODULE_DESCRIPTION("FBDev driver for IBM GXT4500P/6500P and GXT4000P/6000P"); +MODULE_LICENSE("GPL"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\""); diff --git a/drivers/video/fbdev/hecubafb.c b/drivers/video/fbdev/hecubafb.c new file mode 100644 index 000000000000..f64120ec9192 --- /dev/null +++ b/drivers/video/fbdev/hecubafb.c @@ -0,0 +1,311 @@ +/* + * linux/drivers/video/hecubafb.c -- FB driver for Hecuba/Apollo controller + * + * Copyright (C) 2006, Jaya Kumar + * This work was sponsored by CIS(M) Sdn Bhd + * + * 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. + * + * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. + * This work was possible because of apollo display code from E-Ink's website + * http://support.eink.com/community + * All information used to write this code is from public material made + * available by E-Ink on its support site. Some commands such as 0xA4 + * were found by looping through cmd=0x00 thru 0xFF and supplying random + * values. There are other commands that the display is capable of, + * beyond the 5 used here but they are more complex. + * + * This driver is written to be used with the Hecuba display architecture. + * The actual display chip is called Apollo and the interface electronics + * it needs is called Hecuba. + * + * It is intended to be architecture independent. A board specific driver + * must be used to perform all the physical IO interactions. An example + * is provided as n411.c + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/uaccess.h> + +#include <video/hecubafb.h> + +/* Display specific information */ +#define DPY_W 600 +#define DPY_H 800 + +static struct fb_fix_screeninfo hecubafb_fix = { + .id = "hecubafb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_MONO01, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .line_length = DPY_W, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo hecubafb_var = { + .xres = DPY_W, + .yres = DPY_H, + .xres_virtual = DPY_W, + .yres_virtual = DPY_H, + .bits_per_pixel = 1, + .nonstd = 1, +}; + +/* main hecubafb functions */ + +static void apollo_send_data(struct hecubafb_par *par, unsigned char data) +{ + /* set data */ + par->board->set_data(par, data); + + /* set DS low */ + par->board->set_ctl(par, HCB_DS_BIT, 0); + + /* wait for ack */ + par->board->wait_for_ack(par, 0); + + /* set DS hi */ + par->board->set_ctl(par, HCB_DS_BIT, 1); + + /* wait for ack to clear */ + par->board->wait_for_ack(par, 1); +} + +static void apollo_send_command(struct hecubafb_par *par, unsigned char data) +{ + /* command so set CD to high */ + par->board->set_ctl(par, HCB_CD_BIT, 1); + + /* actually strobe with command */ + apollo_send_data(par, data); + + /* clear CD back to low */ + par->board->set_ctl(par, HCB_CD_BIT, 0); +} + +static void hecubafb_dpy_update(struct hecubafb_par *par) +{ + int i; + unsigned char *buf = (unsigned char __force *)par->info->screen_base; + + apollo_send_command(par, APOLLO_START_NEW_IMG); + + for (i=0; i < (DPY_W*DPY_H/8); i++) { + apollo_send_data(par, *(buf++)); + } + + apollo_send_command(par, APOLLO_STOP_IMG_DATA); + apollo_send_command(par, APOLLO_DISPLAY_IMG); +} + +/* this is called back from the deferred io workqueue */ +static void hecubafb_dpy_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + hecubafb_dpy_update(info->par); +} + +static void hecubafb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct hecubafb_par *par = info->par; + + sys_fillrect(info, rect); + + hecubafb_dpy_update(par); +} + +static void hecubafb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct hecubafb_par *par = info->par; + + sys_copyarea(info, area); + + hecubafb_dpy_update(par); +} + +static void hecubafb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct hecubafb_par *par = info->par; + + sys_imageblit(info, image); + + hecubafb_dpy_update(par); +} + +/* + * this is the slow path from userspace. they can seek and write to + * the fb. it's inefficient to do anything less than a full screen draw + */ +static ssize_t hecubafb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct hecubafb_par *par = info->par; + unsigned long p = *ppos; + void *dst; + int err = 0; + unsigned long total_size; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + total_size = info->fix.smem_len; + + if (p > total_size) + return -EFBIG; + + if (count > total_size) { + err = -EFBIG; + count = total_size; + } + + if (count + p > total_size) { + if (!err) + err = -ENOSPC; + + count = total_size - p; + } + + dst = (void __force *) (info->screen_base + p); + + if (copy_from_user(dst, buf, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + hecubafb_dpy_update(par); + + return (err) ? err : count; +} + +static struct fb_ops hecubafb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = hecubafb_write, + .fb_fillrect = hecubafb_fillrect, + .fb_copyarea = hecubafb_copyarea, + .fb_imageblit = hecubafb_imageblit, +}; + +static struct fb_deferred_io hecubafb_defio = { + .delay = HZ, + .deferred_io = hecubafb_dpy_deferred_io, +}; + +static int hecubafb_probe(struct platform_device *dev) +{ + struct fb_info *info; + struct hecuba_board *board; + int retval = -ENOMEM; + int videomemorysize; + unsigned char *videomemory; + struct hecubafb_par *par; + + /* pick up board specific routines */ + board = dev->dev.platform_data; + if (!board) + return -EINVAL; + + /* try to count device specific driver, if can't, platform recalls */ + if (!try_module_get(board->owner)) + return -ENODEV; + + videomemorysize = (DPY_W*DPY_H)/8; + + videomemory = vzalloc(videomemorysize); + if (!videomemory) + goto err_videomem_alloc; + + info = framebuffer_alloc(sizeof(struct hecubafb_par), &dev->dev); + if (!info) + goto err_fballoc; + + info->screen_base = (char __force __iomem *)videomemory; + info->fbops = &hecubafb_ops; + + info->var = hecubafb_var; + info->fix = hecubafb_fix; + info->fix.smem_len = videomemorysize; + par = info->par; + par->info = info; + par->board = board; + par->send_command = apollo_send_command; + par->send_data = apollo_send_data; + + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + + info->fbdefio = &hecubafb_defio; + fb_deferred_io_init(info); + + retval = register_framebuffer(info); + if (retval < 0) + goto err_fbreg; + platform_set_drvdata(dev, info); + + fb_info(info, "Hecuba frame buffer device, using %dK of video memory\n", + videomemorysize >> 10); + + /* this inits the dpy */ + retval = par->board->init(par); + if (retval < 0) + goto err_fbreg; + + return 0; +err_fbreg: + framebuffer_release(info); +err_fballoc: + vfree(videomemory); +err_videomem_alloc: + module_put(board->owner); + return retval; +} + +static int hecubafb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + struct hecubafb_par *par = info->par; + fb_deferred_io_cleanup(info); + unregister_framebuffer(info); + vfree((void __force *)info->screen_base); + if (par->board->remove) + par->board->remove(par); + module_put(par->board->owner); + framebuffer_release(info); + } + return 0; +} + +static struct platform_driver hecubafb_driver = { + .probe = hecubafb_probe, + .remove = hecubafb_remove, + .driver = { + .owner = THIS_MODULE, + .name = "hecubafb", + }, +}; +module_platform_driver(hecubafb_driver); + +MODULE_DESCRIPTION("fbdev driver for Hecuba/Apollo controller"); +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/hgafb.c b/drivers/video/fbdev/hgafb.c new file mode 100644 index 000000000000..5ff9fe2116a4 --- /dev/null +++ b/drivers/video/fbdev/hgafb.c @@ -0,0 +1,667 @@ +/* + * linux/drivers/video/hgafb.c -- Hercules graphics adaptor frame buffer device + * + * Created 25 Nov 1999 by Ferenc Bakonyi (fero@drama.obuda.kando.hu) + * Based on skeletonfb.c by Geert Uytterhoeven and + * mdacon.c by Andrew Apted + * + * History: + * + * - Revision 0.1.8 (23 Oct 2002): Ported to new framebuffer api. + * + * - Revision 0.1.7 (23 Jan 2001): fix crash resulting from MDA only cards + * being detected as Hercules. (Paul G.) + * - Revision 0.1.6 (17 Aug 2000): new style structs + * documentation + * - Revision 0.1.5 (13 Mar 2000): spinlocks instead of saveflags();cli();etc + * minor fixes + * - Revision 0.1.4 (24 Jan 2000): fixed a bug in hga_card_detect() for + * HGA-only systems + * - Revision 0.1.3 (22 Jan 2000): modified for the new fb_info structure + * screen is cleared after rmmod + * virtual resolutions + * module parameter 'nologo={0|1}' + * the most important: boot logo :) + * - Revision 0.1.0 (6 Dec 1999): faster scrolling and minor fixes + * - First release (25 Nov 1999) + * + * 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/spinlock.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <asm/io.h> +#include <asm/vga.h> + +#if 0 +#define DPRINTK(args...) printk(KERN_DEBUG __FILE__": " ##args) +#else +#define DPRINTK(args...) +#endif + +#if 0 +#define CHKINFO(ret) if (info != &fb_info) { printk(KERN_DEBUG __FILE__": This should never happen, line:%d \n", __LINE__); return ret; } +#else +#define CHKINFO(ret) +#endif + +/* Description of the hardware layout */ + +static void __iomem *hga_vram; /* Base of video memory */ +static unsigned long hga_vram_len; /* Size of video memory */ + +#define HGA_ROWADDR(row) ((row%4)*8192 + (row>>2)*90) +#define HGA_TXT 0 +#define HGA_GFX 1 + +static inline u8 __iomem * rowaddr(struct fb_info *info, u_int row) +{ + return info->screen_base + HGA_ROWADDR(row); +} + +static int hga_mode = -1; /* 0 = txt, 1 = gfx mode */ + +static enum { TYPE_HERC, TYPE_HERCPLUS, TYPE_HERCCOLOR } hga_type; +static char *hga_type_name; + +#define HGA_INDEX_PORT 0x3b4 /* Register select port */ +#define HGA_VALUE_PORT 0x3b5 /* Register value port */ +#define HGA_MODE_PORT 0x3b8 /* Mode control port */ +#define HGA_STATUS_PORT 0x3ba /* Status and Config port */ +#define HGA_GFX_PORT 0x3bf /* Graphics control port */ + +/* HGA register values */ + +#define HGA_CURSOR_BLINKING 0x00 +#define HGA_CURSOR_OFF 0x20 +#define HGA_CURSOR_SLOWBLINK 0x60 + +#define HGA_MODE_GRAPHICS 0x02 +#define HGA_MODE_VIDEO_EN 0x08 +#define HGA_MODE_BLINK_EN 0x20 +#define HGA_MODE_GFX_PAGE1 0x80 + +#define HGA_STATUS_HSYNC 0x01 +#define HGA_STATUS_VSYNC 0x80 +#define HGA_STATUS_VIDEO 0x08 + +#define HGA_CONFIG_COL132 0x08 +#define HGA_GFX_MODE_EN 0x01 +#define HGA_GFX_PAGE_EN 0x02 + +/* Global locks */ + +static DEFINE_SPINLOCK(hga_reg_lock); + +/* Framebuffer driver structures */ + +static struct fb_var_screeninfo hga_default_var = { + .xres = 720, + .yres = 348, + .xres_virtual = 720, + .yres_virtual = 348, + .bits_per_pixel = 1, + .red = {0, 1, 0}, + .green = {0, 1, 0}, + .blue = {0, 1, 0}, + .transp = {0, 0, 0}, + .height = -1, + .width = -1, +}; + +static struct fb_fix_screeninfo hga_fix = { + .id = "HGA", + .type = FB_TYPE_PACKED_PIXELS, /* (not sure) */ + .visual = FB_VISUAL_MONO10, + .xpanstep = 8, + .ypanstep = 8, + .line_length = 90, + .accel = FB_ACCEL_NONE +}; + +/* Don't assume that tty1 will be the initial current console. */ +static int release_io_port = 0; +static int release_io_ports = 0; +static bool nologo = 0; + +/* ------------------------------------------------------------------------- + * + * Low level hardware functions + * + * ------------------------------------------------------------------------- */ + +static void write_hga_b(unsigned int val, unsigned char reg) +{ + outb_p(reg, HGA_INDEX_PORT); + outb_p(val, HGA_VALUE_PORT); +} + +static void write_hga_w(unsigned int val, unsigned char reg) +{ + outb_p(reg, HGA_INDEX_PORT); outb_p(val >> 8, HGA_VALUE_PORT); + outb_p(reg+1, HGA_INDEX_PORT); outb_p(val & 0xff, HGA_VALUE_PORT); +} + +static int test_hga_b(unsigned char val, unsigned char reg) +{ + outb_p(reg, HGA_INDEX_PORT); + outb (val, HGA_VALUE_PORT); + udelay(20); val = (inb_p(HGA_VALUE_PORT) == val); + return val; +} + +static void hga_clear_screen(void) +{ + unsigned char fillchar = 0xbf; /* magic */ + unsigned long flags; + + spin_lock_irqsave(&hga_reg_lock, flags); + if (hga_mode == HGA_TXT) + fillchar = ' '; + else if (hga_mode == HGA_GFX) + fillchar = 0x00; + spin_unlock_irqrestore(&hga_reg_lock, flags); + if (fillchar != 0xbf) + memset_io(hga_vram, fillchar, hga_vram_len); +} + +static void hga_txt_mode(void) +{ + unsigned long flags; + + spin_lock_irqsave(&hga_reg_lock, flags); + outb_p(HGA_MODE_VIDEO_EN | HGA_MODE_BLINK_EN, HGA_MODE_PORT); + outb_p(0x00, HGA_GFX_PORT); + outb_p(0x00, HGA_STATUS_PORT); + + write_hga_b(0x61, 0x00); /* horizontal total */ + write_hga_b(0x50, 0x01); /* horizontal displayed */ + write_hga_b(0x52, 0x02); /* horizontal sync pos */ + write_hga_b(0x0f, 0x03); /* horizontal sync width */ + + write_hga_b(0x19, 0x04); /* vertical total */ + write_hga_b(0x06, 0x05); /* vertical total adjust */ + write_hga_b(0x19, 0x06); /* vertical displayed */ + write_hga_b(0x19, 0x07); /* vertical sync pos */ + + write_hga_b(0x02, 0x08); /* interlace mode */ + write_hga_b(0x0d, 0x09); /* maximum scanline */ + write_hga_b(0x0c, 0x0a); /* cursor start */ + write_hga_b(0x0d, 0x0b); /* cursor end */ + + write_hga_w(0x0000, 0x0c); /* start address */ + write_hga_w(0x0000, 0x0e); /* cursor location */ + + hga_mode = HGA_TXT; + spin_unlock_irqrestore(&hga_reg_lock, flags); +} + +static void hga_gfx_mode(void) +{ + unsigned long flags; + + spin_lock_irqsave(&hga_reg_lock, flags); + outb_p(0x00, HGA_STATUS_PORT); + outb_p(HGA_GFX_MODE_EN, HGA_GFX_PORT); + outb_p(HGA_MODE_VIDEO_EN | HGA_MODE_GRAPHICS, HGA_MODE_PORT); + + write_hga_b(0x35, 0x00); /* horizontal total */ + write_hga_b(0x2d, 0x01); /* horizontal displayed */ + write_hga_b(0x2e, 0x02); /* horizontal sync pos */ + write_hga_b(0x07, 0x03); /* horizontal sync width */ + + write_hga_b(0x5b, 0x04); /* vertical total */ + write_hga_b(0x02, 0x05); /* vertical total adjust */ + write_hga_b(0x57, 0x06); /* vertical displayed */ + write_hga_b(0x57, 0x07); /* vertical sync pos */ + + write_hga_b(0x02, 0x08); /* interlace mode */ + write_hga_b(0x03, 0x09); /* maximum scanline */ + write_hga_b(0x00, 0x0a); /* cursor start */ + write_hga_b(0x00, 0x0b); /* cursor end */ + + write_hga_w(0x0000, 0x0c); /* start address */ + write_hga_w(0x0000, 0x0e); /* cursor location */ + + hga_mode = HGA_GFX; + spin_unlock_irqrestore(&hga_reg_lock, flags); +} + +static void hga_show_logo(struct fb_info *info) +{ +/* + void __iomem *dest = hga_vram; + char *logo = linux_logo_bw; + int x, y; + + for (y = 134; y < 134 + 80 ; y++) * this needs some cleanup * + for (x = 0; x < 10 ; x++) + writeb(~*(logo++),(dest + HGA_ROWADDR(y) + x + 40)); +*/ +} + +static void hga_pan(unsigned int xoffset, unsigned int yoffset) +{ + unsigned int base; + unsigned long flags; + + base = (yoffset / 8) * 90 + xoffset; + spin_lock_irqsave(&hga_reg_lock, flags); + write_hga_w(base, 0x0c); /* start address */ + spin_unlock_irqrestore(&hga_reg_lock, flags); + DPRINTK("hga_pan: base:%d\n", base); +} + +static void hga_blank(int blank_mode) +{ + unsigned long flags; + + spin_lock_irqsave(&hga_reg_lock, flags); + if (blank_mode) { + outb_p(0x00, HGA_MODE_PORT); /* disable video */ + } else { + outb_p(HGA_MODE_VIDEO_EN | HGA_MODE_GRAPHICS, HGA_MODE_PORT); + } + spin_unlock_irqrestore(&hga_reg_lock, flags); +} + +static int hga_card_detect(void) +{ + int count = 0; + void __iomem *p, *q; + unsigned short p_save, q_save; + + hga_vram_len = 0x08000; + + hga_vram = ioremap(0xb0000, hga_vram_len); + + if (request_region(0x3b0, 12, "hgafb")) + release_io_ports = 1; + if (request_region(0x3bf, 1, "hgafb")) + release_io_port = 1; + + /* do a memory check */ + + p = hga_vram; + q = hga_vram + 0x01000; + + p_save = readw(p); q_save = readw(q); + + writew(0xaa55, p); if (readw(p) == 0xaa55) count++; + writew(0x55aa, p); if (readw(p) == 0x55aa) count++; + writew(p_save, p); + + if (count != 2) + goto error; + + /* Ok, there is definitely a card registering at the correct + * memory location, so now we do an I/O port test. + */ + + if (!test_hga_b(0x66, 0x0f)) /* cursor low register */ + goto error; + + if (!test_hga_b(0x99, 0x0f)) /* cursor low register */ + goto error; + + /* See if the card is a Hercules, by checking whether the vsync + * bit of the status register is changing. This test lasts for + * approximately 1/10th of a second. + */ + + p_save = q_save = inb_p(HGA_STATUS_PORT) & HGA_STATUS_VSYNC; + + for (count=0; count < 50000 && p_save == q_save; count++) { + q_save = inb(HGA_STATUS_PORT) & HGA_STATUS_VSYNC; + udelay(2); + } + + if (p_save == q_save) + goto error; + + switch (inb_p(HGA_STATUS_PORT) & 0x70) { + case 0x10: + hga_type = TYPE_HERCPLUS; + hga_type_name = "HerculesPlus"; + break; + case 0x50: + hga_type = TYPE_HERCCOLOR; + hga_type_name = "HerculesColor"; + break; + default: + hga_type = TYPE_HERC; + hga_type_name = "Hercules"; + break; + } + return 1; +error: + if (release_io_ports) + release_region(0x3b0, 12); + if (release_io_port) + release_region(0x3bf, 1); + return 0; +} + +/** + * hgafb_open - open the framebuffer device + * @info:pointer to fb_info object containing info for current hga board + * @int:open by console system or userland. + */ + +static int hgafb_open(struct fb_info *info, int init) +{ + hga_gfx_mode(); + hga_clear_screen(); + if (!nologo) hga_show_logo(info); + return 0; +} + +/** + * hgafb_open - open the framebuffer device + * @info:pointer to fb_info object containing info for current hga board + * @int:open by console system or userland. + */ + +static int hgafb_release(struct fb_info *info, int init) +{ + hga_txt_mode(); + hga_clear_screen(); + return 0; +} + +/** + * hgafb_setcolreg - set color registers + * @regno:register index to set + * @red:red value, unused + * @green:green value, unused + * @blue:blue value, unused + * @transp:transparency value, unused + * @info:unused + * + * This callback function is used to set the color registers of a HGA + * board. Since we have only two fixed colors only @regno is checked. + * A zero is returned on success and 1 for failure. + */ + +static int hgafb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno > 1) + return 1; + return 0; +} + +/** + * hga_pan_display - pan or wrap the display + * @var:contains new xoffset, yoffset and vmode values + * @info:pointer to fb_info object containing info for current hga board + * + * This function looks only at xoffset, yoffset and the %FB_VMODE_YWRAP + * flag in @var. If input parameters are correct it calls hga_pan() to + * program the hardware. @info->var is updated to the new values. + * A zero is returned on success and %-EINVAL for failure. + */ + +static int hgafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->vmode & FB_VMODE_YWRAP) { + if (var->yoffset < 0 || + var->yoffset >= info->var.yres_virtual || + var->xoffset) + return -EINVAL; + } else { + if (var->xoffset + info->var.xres > info->var.xres_virtual + || var->yoffset + info->var.yres > info->var.yres_virtual + || var->yoffset % 8) + return -EINVAL; + } + + hga_pan(var->xoffset, var->yoffset); + return 0; +} + +/** + * hgafb_blank - (un)blank the screen + * @blank_mode:blanking method to use + * @info:unused + * + * Blank the screen if blank_mode != 0, else unblank. + * Implements VESA suspend and powerdown modes on hardware that supports + * disabling hsync/vsync: + * @blank_mode == 2 means suspend vsync, + * @blank_mode == 3 means suspend hsync, + * @blank_mode == 4 means powerdown. + */ + +static int hgafb_blank(int blank_mode, struct fb_info *info) +{ + hga_blank(blank_mode); + return 0; +} + +/* + * Accel functions + */ +static void hgafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + u_int rows, y; + u8 __iomem *dest; + + y = rect->dy; + + for (rows = rect->height; rows--; y++) { + dest = rowaddr(info, y) + (rect->dx >> 3); + switch (rect->rop) { + case ROP_COPY: + memset_io(dest, rect->color, (rect->width >> 3)); + break; + case ROP_XOR: + fb_writeb(~(fb_readb(dest)), dest); + break; + } + } +} + +static void hgafb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + u_int rows, y1, y2; + u8 __iomem *src; + u8 __iomem *dest; + + if (area->dy <= area->sy) { + y1 = area->sy; + y2 = area->dy; + + for (rows = area->height; rows--; ) { + src = rowaddr(info, y1) + (area->sx >> 3); + dest = rowaddr(info, y2) + (area->dx >> 3); + memmove(dest, src, (area->width >> 3)); + y1++; + y2++; + } + } else { + y1 = area->sy + area->height - 1; + y2 = area->dy + area->height - 1; + + for (rows = area->height; rows--;) { + src = rowaddr(info, y1) + (area->sx >> 3); + dest = rowaddr(info, y2) + (area->dx >> 3); + memmove(dest, src, (area->width >> 3)); + y1--; + y2--; + } + } +} + +static void hgafb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + u8 __iomem *dest; + u8 *cdat = (u8 *) image->data; + u_int rows, y = image->dy; + u_int x; + u8 d; + + for (rows = image->height; rows--; y++) { + for (x = 0; x < image->width; x+= 8) { + d = *cdat++; + dest = rowaddr(info, y) + ((image->dx + x)>> 3); + fb_writeb(d, dest); + } + } +} + +static struct fb_ops hgafb_ops = { + .owner = THIS_MODULE, + .fb_open = hgafb_open, + .fb_release = hgafb_release, + .fb_setcolreg = hgafb_setcolreg, + .fb_pan_display = hgafb_pan_display, + .fb_blank = hgafb_blank, + .fb_fillrect = hgafb_fillrect, + .fb_copyarea = hgafb_copyarea, + .fb_imageblit = hgafb_imageblit, +}; + +/* ------------------------------------------------------------------------- * + * + * Functions in fb_info + * + * ------------------------------------------------------------------------- */ + +/* ------------------------------------------------------------------------- */ + + /* + * Initialization + */ + +static int hgafb_probe(struct platform_device *pdev) +{ + struct fb_info *info; + + if (! hga_card_detect()) { + printk(KERN_INFO "hgafb: HGA card not detected.\n"); + if (hga_vram) + iounmap(hga_vram); + return -EINVAL; + } + + printk(KERN_INFO "hgafb: %s with %ldK of memory detected.\n", + hga_type_name, hga_vram_len/1024); + + info = framebuffer_alloc(0, &pdev->dev); + if (!info) { + iounmap(hga_vram); + return -ENOMEM; + } + + hga_fix.smem_start = (unsigned long)hga_vram; + hga_fix.smem_len = hga_vram_len; + + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + info->var = hga_default_var; + info->fix = hga_fix; + info->monspecs.hfmin = 0; + info->monspecs.hfmax = 0; + info->monspecs.vfmin = 10000; + info->monspecs.vfmax = 10000; + info->monspecs.dpms = 0; + info->fbops = &hgafb_ops; + info->screen_base = hga_vram; + + if (register_framebuffer(info) < 0) { + framebuffer_release(info); + iounmap(hga_vram); + return -EINVAL; + } + + fb_info(info, "%s frame buffer device\n", info->fix.id); + platform_set_drvdata(pdev, info); + return 0; +} + +static int hgafb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + + hga_txt_mode(); + hga_clear_screen(); + + if (info) { + unregister_framebuffer(info); + framebuffer_release(info); + } + + iounmap(hga_vram); + + if (release_io_ports) + release_region(0x3b0, 12); + + if (release_io_port) + release_region(0x3bf, 1); + + return 0; +} + +static struct platform_driver hgafb_driver = { + .probe = hgafb_probe, + .remove = hgafb_remove, + .driver = { + .name = "hgafb", + }, +}; + +static struct platform_device *hgafb_device; + +static int __init hgafb_init(void) +{ + int ret; + + if (fb_get_options("hgafb", NULL)) + return -ENODEV; + + ret = platform_driver_register(&hgafb_driver); + + if (!ret) { + hgafb_device = platform_device_register_simple("hgafb", 0, NULL, 0); + + if (IS_ERR(hgafb_device)) { + platform_driver_unregister(&hgafb_driver); + ret = PTR_ERR(hgafb_device); + } + } + + return ret; +} + +static void __exit hgafb_exit(void) +{ + platform_device_unregister(hgafb_device); + platform_driver_unregister(&hgafb_driver); +} + +/* ------------------------------------------------------------------------- + * + * Modularization + * + * ------------------------------------------------------------------------- */ + +MODULE_AUTHOR("Ferenc Bakonyi (fero@drama.obuda.kando.hu)"); +MODULE_DESCRIPTION("FBDev driver for Hercules Graphics Adaptor"); +MODULE_LICENSE("GPL"); + +module_param(nologo, bool, 0); +MODULE_PARM_DESC(nologo, "Disables startup logo if != 0 (default=0)"); +module_init(hgafb_init); +module_exit(hgafb_exit); diff --git a/drivers/video/fbdev/hitfb.c b/drivers/video/fbdev/hitfb.c new file mode 100644 index 000000000000..a648d5186c6e --- /dev/null +++ b/drivers/video/fbdev/hitfb.c @@ -0,0 +1,500 @@ +/* + * linux/drivers/video/hitfb.c -- Hitachi LCD frame buffer device + * + * (C) 1999 Mihai Spatar + * (C) 2000 YAEGASHI Takeshi + * (C) 2003, 2004 Paul Mundt + * (C) 2003, 2004, 2006 Andriy Skulysh + * + * 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/delay.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fb.h> + +#include <asm/machvec.h> +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/hd64461.h> +#include <cpu/dac.h> + +#define WIDTH 640 + +static struct fb_var_screeninfo hitfb_var = { + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo hitfb_fix = { + .id = "Hitachi HD64461", + .type = FB_TYPE_PACKED_PIXELS, + .accel = FB_ACCEL_NONE, +}; + +static inline void hitfb_accel_wait(void) +{ + while (fb_readw(HD64461_GRCFGR) & HD64461_GRCFGR_ACCSTATUS) ; +} + +static inline void hitfb_accel_start(int truecolor) +{ + if (truecolor) { + fb_writew(6, HD64461_GRCFGR); + } else { + fb_writew(7, HD64461_GRCFGR); + } +} + +static inline void hitfb_accel_set_dest(int truecolor, u16 dx, u16 dy, + u16 width, u16 height) +{ + u32 saddr = WIDTH * dy + dx; + if (truecolor) + saddr <<= 1; + + fb_writew(width-1, HD64461_BBTDWR); + fb_writew(height-1, HD64461_BBTDHR); + + fb_writew(saddr & 0xffff, HD64461_BBTDSARL); + fb_writew(saddr >> 16, HD64461_BBTDSARH); + +} + +static inline void hitfb_accel_bitblt(int truecolor, u16 sx, u16 sy, u16 dx, + u16 dy, u16 width, u16 height, u16 rop, + u32 mask_addr) +{ + u32 saddr, daddr; + u32 maddr = 0; + + height--; + width--; + fb_writew(rop, HD64461_BBTROPR); + if ((sy < dy) || ((sy == dy) && (sx <= dx))) { + saddr = WIDTH * (sy + height) + sx + width; + daddr = WIDTH * (dy + height) + dx + width; + if (mask_addr) { + if (truecolor) + maddr = ((width >> 3) + 1) * (height + 1) - 1; + else + maddr = + (((width >> 4) + 1) * (height + 1) - 1) * 2; + + fb_writew((1 << 5) | 1, HD64461_BBTMDR); + } else + fb_writew(1, HD64461_BBTMDR); + } else { + saddr = WIDTH * sy + sx; + daddr = WIDTH * dy + dx; + if (mask_addr) { + fb_writew((1 << 5), HD64461_BBTMDR); + } else { + fb_writew(0, HD64461_BBTMDR); + } + } + if (truecolor) { + saddr <<= 1; + daddr <<= 1; + } + fb_writew(width, HD64461_BBTDWR); + fb_writew(height, HD64461_BBTDHR); + fb_writew(saddr & 0xffff, HD64461_BBTSSARL); + fb_writew(saddr >> 16, HD64461_BBTSSARH); + fb_writew(daddr & 0xffff, HD64461_BBTDSARL); + fb_writew(daddr >> 16, HD64461_BBTDSARH); + if (mask_addr) { + maddr += mask_addr; + fb_writew(maddr & 0xffff, HD64461_BBTMARL); + fb_writew(maddr >> 16, HD64461_BBTMARH); + } + hitfb_accel_start(truecolor); +} + +static void hitfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + if (rect->rop != ROP_COPY) + cfb_fillrect(p, rect); + else { + hitfb_accel_wait(); + fb_writew(0x00f0, HD64461_BBTROPR); + fb_writew(16, HD64461_BBTMDR); + + if (p->var.bits_per_pixel == 16) { + fb_writew(((u32 *) (p->pseudo_palette))[rect->color], + HD64461_GRSCR); + hitfb_accel_set_dest(1, rect->dx, rect->dy, rect->width, + rect->height); + hitfb_accel_start(1); + } else { + fb_writew(rect->color, HD64461_GRSCR); + hitfb_accel_set_dest(0, rect->dx, rect->dy, rect->width, + rect->height); + hitfb_accel_start(0); + } + } +} + +static void hitfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + hitfb_accel_wait(); + hitfb_accel_bitblt(p->var.bits_per_pixel == 16, area->sx, area->sy, + area->dx, area->dy, area->width, area->height, + 0x00cc, 0); +} + +static int hitfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int xoffset = var->xoffset; + int yoffset = var->yoffset; + + if (xoffset != 0) + return -EINVAL; + + fb_writew((yoffset*info->fix.line_length)>>10, HD64461_LCDCBAR); + + return 0; +} + +int hitfb_blank(int blank_mode, struct fb_info *info) +{ + unsigned short v; + + if (blank_mode) { + v = fb_readw(HD64461_LDR1); + v &= ~HD64461_LDR1_DON; + fb_writew(v, HD64461_LDR1); + + v = fb_readw(HD64461_LCDCCR); + v |= HD64461_LCDCCR_MOFF; + fb_writew(v, HD64461_LCDCCR); + + v = fb_readw(HD64461_STBCR); + v |= HD64461_STBCR_SLCDST; + fb_writew(v, HD64461_STBCR); + } else { + v = fb_readw(HD64461_STBCR); + v &= ~HD64461_STBCR_SLCDST; + fb_writew(v, HD64461_STBCR); + + v = fb_readw(HD64461_LCDCCR); + v &= ~(HD64461_LCDCCR_MOFF | HD64461_LCDCCR_STREQ); + fb_writew(v, HD64461_LCDCCR); + + do { + v = fb_readw(HD64461_LCDCCR); + } while(v&HD64461_LCDCCR_STBACK); + + v = fb_readw(HD64461_LDR1); + v |= HD64461_LDR1_DON; + fb_writew(v, HD64461_LDR1); + } + return 0; +} + +static int hitfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + if (regno >= 256) + return 1; + + switch (info->var.bits_per_pixel) { + case 8: + fb_writew(regno << 8, HD64461_CPTWAR); + fb_writew(red >> 10, HD64461_CPTWDR); + fb_writew(green >> 10, HD64461_CPTWDR); + fb_writew(blue >> 10, HD64461_CPTWDR); + break; + case 16: + if (regno >= 16) + return 1; + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800)) | + ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11); + break; + } + return 0; +} + +static int hitfb_sync(struct fb_info *info) +{ + hitfb_accel_wait(); + + return 0; +} + +static int hitfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int maxy; + + var->xres = info->var.xres; + var->xres_virtual = info->var.xres; + var->yres = info->var.yres; + + if ((var->bits_per_pixel != 8) && (var->bits_per_pixel != 16)) + var->bits_per_pixel = info->var.bits_per_pixel; + + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + maxy = info->fix.smem_len / var->xres; + + if (var->bits_per_pixel == 16) + maxy /= 2; + + if (var->yres_virtual > maxy) + var->yres_virtual = maxy; + + var->xoffset = 0; + var->yoffset = 0; + + switch (var->bits_per_pixel) { + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGB 565 */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + } + + return 0; +} + +static int hitfb_set_par(struct fb_info *info) +{ + unsigned short ldr3; + + switch (info->var.bits_per_pixel) { + case 8: + info->fix.line_length = info->var.xres; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.ypanstep = 16; + break; + case 16: + info->fix.line_length = info->var.xres*2; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.ypanstep = 8; + break; + } + + fb_writew(info->fix.line_length, HD64461_LCDCLOR); + ldr3 = fb_readw(HD64461_LDR3); + ldr3 &= ~15; + ldr3 |= (info->var.bits_per_pixel == 8) ? 4 : 8; + fb_writew(ldr3, HD64461_LDR3); + return 0; +} + +static struct fb_ops hitfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = hitfb_check_var, + .fb_set_par = hitfb_set_par, + .fb_setcolreg = hitfb_setcolreg, + .fb_blank = hitfb_blank, + .fb_sync = hitfb_sync, + .fb_pan_display = hitfb_pan_display, + .fb_fillrect = hitfb_fillrect, + .fb_copyarea = hitfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int hitfb_probe(struct platform_device *dev) +{ + unsigned short lcdclor, ldr3, ldvndr; + struct fb_info *info; + int ret; + + if (fb_get_options("hitfb", NULL)) + return -ENODEV; + + hitfb_fix.mmio_start = HD64461_IO_OFFSET(0x1000); + hitfb_fix.mmio_len = 0x1000; + hitfb_fix.smem_start = HD64461_IO_OFFSET(0x02000000); + hitfb_fix.smem_len = 512 * 1024; + + lcdclor = fb_readw(HD64461_LCDCLOR); + ldvndr = fb_readw(HD64461_LDVNDR); + ldr3 = fb_readw(HD64461_LDR3); + + switch (ldr3 & 15) { + default: + case 4: + hitfb_var.bits_per_pixel = 8; + hitfb_var.xres = lcdclor; + break; + case 8: + hitfb_var.bits_per_pixel = 16; + hitfb_var.xres = lcdclor / 2; + break; + } + hitfb_fix.line_length = lcdclor; + hitfb_fix.visual = (hitfb_var.bits_per_pixel == 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + hitfb_var.yres = ldvndr + 1; + hitfb_var.xres_virtual = hitfb_var.xres; + hitfb_var.yres_virtual = hitfb_fix.smem_len / lcdclor; + switch (hitfb_var.bits_per_pixel) { + case 8: + hitfb_var.red.offset = 0; + hitfb_var.red.length = 8; + hitfb_var.green.offset = 0; + hitfb_var.green.length = 8; + hitfb_var.blue.offset = 0; + hitfb_var.blue.length = 8; + hitfb_var.transp.offset = 0; + hitfb_var.transp.length = 0; + break; + case 16: /* RGB 565 */ + hitfb_var.red.offset = 11; + hitfb_var.red.length = 5; + hitfb_var.green.offset = 5; + hitfb_var.green.length = 6; + hitfb_var.blue.offset = 0; + hitfb_var.blue.length = 5; + hitfb_var.transp.offset = 0; + hitfb_var.transp.length = 0; + break; + } + + info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev); + if (unlikely(!info)) + return -ENOMEM; + + info->fbops = &hitfb_ops; + info->var = hitfb_var; + info->fix = hitfb_fix; + info->pseudo_palette = info->par; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_COPYAREA; + + info->screen_base = (void *)hitfb_fix.smem_start; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (unlikely(ret < 0)) + goto err_fb; + + ret = register_framebuffer(info); + if (unlikely(ret < 0)) + goto err; + + platform_set_drvdata(dev, info); + + fb_info(info, "%s frame buffer device\n", info->fix.id); + + return 0; + +err: + fb_dealloc_cmap(&info->cmap); +err_fb: + framebuffer_release(info); + return ret; +} + +static int hitfb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + + return 0; +} + +static int hitfb_suspend(struct device *dev) +{ + u16 v; + + hitfb_blank(1,0); + v = fb_readw(HD64461_STBCR); + v |= HD64461_STBCR_SLCKE_IST; + fb_writew(v, HD64461_STBCR); + + return 0; +} + +static int hitfb_resume(struct device *dev) +{ + u16 v; + + v = fb_readw(HD64461_STBCR); + v &= ~HD64461_STBCR_SLCKE_OST; + msleep(100); + v = fb_readw(HD64461_STBCR); + v &= ~HD64461_STBCR_SLCKE_IST; + fb_writew(v, HD64461_STBCR); + hitfb_blank(0,0); + + return 0; +} + +static const struct dev_pm_ops hitfb_dev_pm_ops = { + .suspend = hitfb_suspend, + .resume = hitfb_resume, +}; + +static struct platform_driver hitfb_driver = { + .probe = hitfb_probe, + .remove = hitfb_remove, + .driver = { + .name = "hitfb", + .owner = THIS_MODULE, + .pm = &hitfb_dev_pm_ops, + }, +}; + +static struct platform_device hitfb_device = { + .name = "hitfb", + .id = -1, +}; + +static int __init hitfb_init(void) +{ + int ret; + + ret = platform_driver_register(&hitfb_driver); + if (!ret) { + ret = platform_device_register(&hitfb_device); + if (ret) + platform_driver_unregister(&hitfb_driver); + } + return ret; +} + + +static void __exit hitfb_exit(void) +{ + platform_device_unregister(&hitfb_device); + platform_driver_unregister(&hitfb_driver); +} + +module_init(hitfb_init); +module_exit(hitfb_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/hpfb.c b/drivers/video/fbdev/hpfb.c new file mode 100644 index 000000000000..a1b7e5fa9b09 --- /dev/null +++ b/drivers/video/fbdev/hpfb.c @@ -0,0 +1,429 @@ +/* + * HP300 Topcat framebuffer support (derived from macfb of all things) + * Phil Blundell <philb@gnu.org> 1998 + * DIO-II, colour map and Catseye support by + * Kars de Jong <jongk@linux-m68k.org>, May 2004. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/dio.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +static struct fb_info fb_info = { + .fix = { + .id = "HP300 ", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .accel = FB_ACCEL_NONE, + } +}; + +static unsigned long fb_regs; +static unsigned char fb_bitmask; + +#define TC_NBLANK 0x4080 +#define TC_WEN 0x4088 +#define TC_REN 0x408c +#define TC_FBEN 0x4090 +#define TC_PRR 0x40ea + +/* These defines match the X window system */ +#define RR_CLEAR 0x0 +#define RR_COPY 0x3 +#define RR_NOOP 0x5 +#define RR_XOR 0x6 +#define RR_INVERT 0xa +#define RR_COPYINVERTED 0xc +#define RR_SET 0xf + +/* blitter regs */ +#define BUSY 0x4044 +#define WMRR 0x40ef +#define SOURCE_X 0x40f2 +#define SOURCE_Y 0x40f6 +#define DEST_X 0x40fa +#define DEST_Y 0x40fe +#define WHEIGHT 0x4106 +#define WWIDTH 0x4102 +#define WMOVE 0x409c + +static struct fb_var_screeninfo hpfb_defined = { + .red = { + .length = 8, + }, + .green = { + .length = 8, + }, + .blue = { + .length = 8, + }, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static int hpfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + /* use MSBs */ + unsigned char _red =red>>8; + unsigned char _green=green>>8; + unsigned char _blue =blue>>8; + unsigned char _regno=regno; + + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= info->cmap.len) + return 1; + + while (in_be16(fb_regs + 0x6002) & 0x4) udelay(1); + + out_be16(fb_regs + 0x60ba, 0xff); + + out_be16(fb_regs + 0x60b2, _red); + out_be16(fb_regs + 0x60b4, _green); + out_be16(fb_regs + 0x60b6, _blue); + out_be16(fb_regs + 0x60b8, ~_regno); + out_be16(fb_regs + 0x60f0, 0xff); + + udelay(100); + + while (in_be16(fb_regs + 0x6002) & 0x4) udelay(1); + out_be16(fb_regs + 0x60b2, 0); + out_be16(fb_regs + 0x60b4, 0); + out_be16(fb_regs + 0x60b6, 0); + out_be16(fb_regs + 0x60b8, 0); + + return 0; +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ + +static int hpfb_blank(int blank, struct fb_info *info) +{ + out_8(fb_regs + TC_NBLANK, (blank ? 0x00 : fb_bitmask)); + + return 0; +} + +static void topcat_blit(int x0, int y0, int x1, int y1, int w, int h, int rr) +{ + if (rr >= 0) { + while (in_8(fb_regs + BUSY) & fb_bitmask) + ; + } + out_8(fb_regs + TC_FBEN, fb_bitmask); + if (rr >= 0) { + out_8(fb_regs + TC_WEN, fb_bitmask); + out_8(fb_regs + WMRR, rr); + } + out_be16(fb_regs + SOURCE_X, x0); + out_be16(fb_regs + SOURCE_Y, y0); + out_be16(fb_regs + DEST_X, x1); + out_be16(fb_regs + DEST_Y, y1); + out_be16(fb_regs + WWIDTH, w); + out_be16(fb_regs + WHEIGHT, h); + out_8(fb_regs + WMOVE, fb_bitmask); +} + +static void hpfb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + topcat_blit(area->sx, area->sy, area->dx, area->dy, area->width, area->height, RR_COPY); +} + +static void hpfb_fillrect(struct fb_info *p, const struct fb_fillrect *region) +{ + u8 clr; + + clr = region->color & 0xff; + + while (in_8(fb_regs + BUSY) & fb_bitmask) + ; + + /* Foreground */ + out_8(fb_regs + TC_WEN, fb_bitmask & clr); + out_8(fb_regs + WMRR, (region->rop == ROP_COPY ? RR_SET : RR_INVERT)); + + /* Background */ + out_8(fb_regs + TC_WEN, fb_bitmask & ~clr); + out_8(fb_regs + WMRR, (region->rop == ROP_COPY ? RR_CLEAR : RR_NOOP)); + + topcat_blit(region->dx, region->dy, region->dx, region->dy, region->width, region->height, -1); +} + +static int hpfb_sync(struct fb_info *info) +{ + /* + * Since we also access the framebuffer directly, we have to wait + * until the block mover is finished + */ + while (in_8(fb_regs + BUSY) & fb_bitmask) + ; + + out_8(fb_regs + TC_WEN, fb_bitmask); + out_8(fb_regs + TC_PRR, RR_COPY); + out_8(fb_regs + TC_FBEN, fb_bitmask); + + return 0; +} + +static struct fb_ops hpfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = hpfb_setcolreg, + .fb_blank = hpfb_blank, + .fb_fillrect = hpfb_fillrect, + .fb_copyarea = hpfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_sync = hpfb_sync, +}; + +/* Common to all HP framebuffers */ +#define HPFB_FBWMSB 0x05 /* Frame buffer width */ +#define HPFB_FBWLSB 0x07 +#define HPFB_FBHMSB 0x09 /* Frame buffer height */ +#define HPFB_FBHLSB 0x0b +#define HPFB_DWMSB 0x0d /* Display width */ +#define HPFB_DWLSB 0x0f +#define HPFB_DHMSB 0x11 /* Display height */ +#define HPFB_DHLSB 0x13 +#define HPFB_NUMPLANES 0x5b /* Number of colour planes */ +#define HPFB_FBOMSB 0x5d /* Frame buffer offset */ +#define HPFB_FBOLSB 0x5f + +static int hpfb_init_one(unsigned long phys_base, unsigned long virt_base) +{ + unsigned long fboff, fb_width, fb_height, fb_start; + int ret; + + fb_regs = virt_base; + fboff = (in_8(fb_regs + HPFB_FBOMSB) << 8) | in_8(fb_regs + HPFB_FBOLSB); + + fb_info.fix.smem_start = (in_8(fb_regs + fboff) << 16); + + if (phys_base >= DIOII_BASE) { + fb_info.fix.smem_start += phys_base; + } + + if (DIO_SECID(fb_regs) != DIO_ID2_TOPCAT) { + /* This is the magic incantation the HP X server uses to make Catseye boards work. */ + while (in_be16(fb_regs+0x4800) & 1) + ; + out_be16(fb_regs+0x4800, 0); /* Catseye status */ + out_be16(fb_regs+0x4510, 0); /* VB */ + out_be16(fb_regs+0x4512, 0); /* TCNTRL */ + out_be16(fb_regs+0x4514, 0); /* ACNTRL */ + out_be16(fb_regs+0x4516, 0); /* PNCNTRL */ + out_be16(fb_regs+0x4206, 0x90); /* RUG Command/Status */ + out_be16(fb_regs+0x60a2, 0); /* Overlay Mask */ + out_be16(fb_regs+0x60bc, 0); /* Ram Select */ + } + + /* + * Fill in the available video resolution + */ + fb_width = (in_8(fb_regs + HPFB_FBWMSB) << 8) | in_8(fb_regs + HPFB_FBWLSB); + fb_info.fix.line_length = fb_width; + fb_height = (in_8(fb_regs + HPFB_FBHMSB) << 8) | in_8(fb_regs + HPFB_FBHLSB); + fb_info.fix.smem_len = fb_width * fb_height; + fb_start = (unsigned long)ioremap_writethrough(fb_info.fix.smem_start, + fb_info.fix.smem_len); + hpfb_defined.xres = (in_8(fb_regs + HPFB_DWMSB) << 8) | in_8(fb_regs + HPFB_DWLSB); + hpfb_defined.yres = (in_8(fb_regs + HPFB_DHMSB) << 8) | in_8(fb_regs + HPFB_DHLSB); + hpfb_defined.xres_virtual = hpfb_defined.xres; + hpfb_defined.yres_virtual = hpfb_defined.yres; + hpfb_defined.bits_per_pixel = in_8(fb_regs + HPFB_NUMPLANES); + + printk(KERN_INFO "hpfb: framebuffer at 0x%lx, mapped to 0x%lx, size %dk\n", + fb_info.fix.smem_start, fb_start, fb_info.fix.smem_len/1024); + printk(KERN_INFO "hpfb: mode is %dx%dx%d, linelength=%d\n", + hpfb_defined.xres, hpfb_defined.yres, hpfb_defined.bits_per_pixel, fb_info.fix.line_length); + + /* + * Give the hardware a bit of a prod and work out how many bits per + * pixel are supported. + */ + out_8(fb_regs + TC_WEN, 0xff); + out_8(fb_regs + TC_PRR, RR_COPY); + out_8(fb_regs + TC_FBEN, 0xff); + out_8(fb_start, 0xff); + fb_bitmask = in_8(fb_start); + out_8(fb_start, 0); + + /* + * Enable reading/writing of all the planes. + */ + out_8(fb_regs + TC_WEN, fb_bitmask); + out_8(fb_regs + TC_PRR, RR_COPY); + out_8(fb_regs + TC_REN, fb_bitmask); + out_8(fb_regs + TC_FBEN, fb_bitmask); + + /* + * Clear the screen. + */ + topcat_blit(0, 0, 0, 0, fb_width, fb_height, RR_CLEAR); + + /* + * Let there be consoles.. + */ + if (DIO_SECID(fb_regs) == DIO_ID2_TOPCAT) + strcat(fb_info.fix.id, "Topcat"); + else + strcat(fb_info.fix.id, "Catseye"); + fb_info.fbops = &hpfb_ops; + fb_info.flags = FBINFO_DEFAULT; + fb_info.var = hpfb_defined; + fb_info.screen_base = (char *)fb_start; + + ret = fb_alloc_cmap(&fb_info.cmap, 1 << hpfb_defined.bits_per_pixel, 0); + if (ret < 0) + goto unmap_screen_base; + + ret = register_framebuffer(&fb_info); + if (ret < 0) + goto dealloc_cmap; + + fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id); + + return 0; + +dealloc_cmap: + fb_dealloc_cmap(&fb_info.cmap); + +unmap_screen_base: + if (fb_info.screen_base) { + iounmap(fb_info.screen_base); + fb_info.screen_base = NULL; + } + + return ret; +} + +/* + * Check that the secondary ID indicates that we have some hope of working with this + * framebuffer. The catseye boards are pretty much like topcats and we can muddle through. + */ + +#define topcat_sid_ok(x) (((x) == DIO_ID2_LRCATSEYE) || ((x) == DIO_ID2_HRCCATSEYE) \ + || ((x) == DIO_ID2_HRMCATSEYE) || ((x) == DIO_ID2_TOPCAT)) + +/* + * Initialise the framebuffer + */ +static int hpfb_dio_probe(struct dio_dev *d, const struct dio_device_id *ent) +{ + unsigned long paddr, vaddr; + + paddr = d->resource.start; + if (!request_mem_region(d->resource.start, resource_size(&d->resource), d->name)) + return -EBUSY; + + if (d->scode >= DIOII_SCBASE) { + vaddr = (unsigned long)ioremap(paddr, resource_size(&d->resource)); + } else { + vaddr = paddr + DIO_VIRADDRBASE; + } + printk(KERN_INFO "Topcat found at DIO select code %d " + "(secondary id %02x)\n", d->scode, (d->id >> 8) & 0xff); + if (hpfb_init_one(paddr, vaddr)) { + if (d->scode >= DIOII_SCBASE) + iounmap((void *)vaddr); + return -ENOMEM; + } + return 0; +} + +static void hpfb_remove_one(struct dio_dev *d) +{ + unregister_framebuffer(&fb_info); + if (d->scode >= DIOII_SCBASE) + iounmap((void *)fb_regs); + release_mem_region(d->resource.start, resource_size(&d->resource)); + fb_dealloc_cmap(&fb_info.cmap); + if (fb_info.screen_base) + iounmap(fb_info.screen_base); +} + +static struct dio_device_id hpfb_dio_tbl[] = { + { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_LRCATSEYE) }, + { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRCCATSEYE) }, + { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRMCATSEYE) }, + { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_TOPCAT) }, + { 0 } +}; + +static struct dio_driver hpfb_driver = { + .name = "hpfb", + .id_table = hpfb_dio_tbl, + .probe = hpfb_dio_probe, + .remove = hpfb_remove_one, +}; + +int __init hpfb_init(void) +{ + unsigned int sid; + mm_segment_t fs; + unsigned char i; + int err; + + /* Topcats can be on the internal IO bus or real DIO devices. + * The internal variant sits at 0x560000; it has primary + * and secondary ID registers just like the DIO version. + * So we merge the two detection routines. + * + * Perhaps this #define should be in a global header file: + * I believe it's common to all internal fbs, not just topcat. + */ +#define INTFBVADDR 0xf0560000 +#define INTFBPADDR 0x560000 + + if (!MACH_IS_HP300) + return -ENODEV; + + if (fb_get_options("hpfb", NULL)) + return -ENODEV; + + err = dio_register_driver(&hpfb_driver); + if (err) + return err; + + fs = get_fs(); + set_fs(KERNEL_DS); + err = get_user(i, (unsigned char *)INTFBVADDR + DIO_IDOFF); + set_fs(fs); + + if (!err && (i == DIO_ID_FBUFFER) && topcat_sid_ok(sid = DIO_SECID(INTFBVADDR))) { + if (!request_mem_region(INTFBPADDR, DIO_DEVSIZE, "Internal Topcat")) + return -EBUSY; + printk(KERN_INFO "Internal Topcat found (secondary id %02x)\n", sid); + if (hpfb_init_one(INTFBPADDR, INTFBVADDR)) { + return -ENOMEM; + } + } + return 0; +} + +void __exit hpfb_cleanup_module(void) +{ + dio_unregister_driver(&hpfb_driver); +} + +module_init(hpfb_init); +module_exit(hpfb_cleanup_module); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c new file mode 100644 index 000000000000..e23392ec5af3 --- /dev/null +++ b/drivers/video/fbdev/hyperv_fb.c @@ -0,0 +1,907 @@ +/* + * Copyright (c) 2012, Microsoft Corporation. + * + * Author: + * Haiyang Zhang <haiyangz@microsoft.com> + * + * 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. + * + * 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +/* + * Hyper-V Synthetic Video Frame Buffer Driver + * + * This is the driver for the Hyper-V Synthetic Video, which supports + * screen resolution up to Full HD 1920x1080 with 32 bit color on Windows + * Server 2012, and 1600x1200 with 16 bit color on Windows Server 2008 R2 + * or earlier. + * + * It also solves the double mouse cursor issue of the emulated video mode. + * + * The default screen resolution is 1152x864, which may be changed by a + * kernel parameter: + * video=hyperv_fb:<width>x<height> + * For example: video=hyperv_fb:1280x1024 + * + * Portrait orientation is also supported: + * For example: video=hyperv_fb:864x1152 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/completion.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/efi.h> + +#include <linux/hyperv.h> + + +/* Hyper-V Synthetic Video Protocol definitions and structures */ +#define MAX_VMBUS_PKT_SIZE 0x4000 + +#define SYNTHVID_VERSION(major, minor) ((minor) << 16 | (major)) +#define SYNTHVID_VERSION_WIN7 SYNTHVID_VERSION(3, 0) +#define SYNTHVID_VERSION_WIN8 SYNTHVID_VERSION(3, 2) + +#define SYNTHVID_DEPTH_WIN7 16 +#define SYNTHVID_DEPTH_WIN8 32 + +#define SYNTHVID_FB_SIZE_WIN7 (4 * 1024 * 1024) +#define SYNTHVID_WIDTH_MAX_WIN7 1600 +#define SYNTHVID_HEIGHT_MAX_WIN7 1200 + +#define SYNTHVID_FB_SIZE_WIN8 (8 * 1024 * 1024) + +#define PCI_VENDOR_ID_MICROSOFT 0x1414 +#define PCI_DEVICE_ID_HYPERV_VIDEO 0x5353 + + +enum pipe_msg_type { + PIPE_MSG_INVALID, + PIPE_MSG_DATA, + PIPE_MSG_MAX +}; + +struct pipe_msg_hdr { + u32 type; + u32 size; /* size of message after this field */ +} __packed; + + +enum synthvid_msg_type { + SYNTHVID_ERROR = 0, + SYNTHVID_VERSION_REQUEST = 1, + SYNTHVID_VERSION_RESPONSE = 2, + SYNTHVID_VRAM_LOCATION = 3, + SYNTHVID_VRAM_LOCATION_ACK = 4, + SYNTHVID_SITUATION_UPDATE = 5, + SYNTHVID_SITUATION_UPDATE_ACK = 6, + SYNTHVID_POINTER_POSITION = 7, + SYNTHVID_POINTER_SHAPE = 8, + SYNTHVID_FEATURE_CHANGE = 9, + SYNTHVID_DIRT = 10, + + SYNTHVID_MAX = 11 +}; + +struct synthvid_msg_hdr { + u32 type; + u32 size; /* size of this header + payload after this field*/ +} __packed; + + +struct synthvid_version_req { + u32 version; +} __packed; + +struct synthvid_version_resp { + u32 version; + u8 is_accepted; + u8 max_video_outputs; +} __packed; + +struct synthvid_vram_location { + u64 user_ctx; + u8 is_vram_gpa_specified; + u64 vram_gpa; +} __packed; + +struct synthvid_vram_location_ack { + u64 user_ctx; +} __packed; + +struct video_output_situation { + u8 active; + u32 vram_offset; + u8 depth_bits; + u32 width_pixels; + u32 height_pixels; + u32 pitch_bytes; +} __packed; + +struct synthvid_situation_update { + u64 user_ctx; + u8 video_output_count; + struct video_output_situation video_output[1]; +} __packed; + +struct synthvid_situation_update_ack { + u64 user_ctx; +} __packed; + +struct synthvid_pointer_position { + u8 is_visible; + u8 video_output; + s32 image_x; + s32 image_y; +} __packed; + + +#define CURSOR_MAX_X 96 +#define CURSOR_MAX_Y 96 +#define CURSOR_ARGB_PIXEL_SIZE 4 +#define CURSOR_MAX_SIZE (CURSOR_MAX_X * CURSOR_MAX_Y * CURSOR_ARGB_PIXEL_SIZE) +#define CURSOR_COMPLETE (-1) + +struct synthvid_pointer_shape { + u8 part_idx; + u8 is_argb; + u32 width; /* CURSOR_MAX_X at most */ + u32 height; /* CURSOR_MAX_Y at most */ + u32 hot_x; /* hotspot relative to upper-left of pointer image */ + u32 hot_y; + u8 data[4]; +} __packed; + +struct synthvid_feature_change { + u8 is_dirt_needed; + u8 is_ptr_pos_needed; + u8 is_ptr_shape_needed; + u8 is_situ_needed; +} __packed; + +struct rect { + s32 x1, y1; /* top left corner */ + s32 x2, y2; /* bottom right corner, exclusive */ +} __packed; + +struct synthvid_dirt { + u8 video_output; + u8 dirt_count; + struct rect rect[1]; +} __packed; + +struct synthvid_msg { + struct pipe_msg_hdr pipe_hdr; + struct synthvid_msg_hdr vid_hdr; + union { + struct synthvid_version_req ver_req; + struct synthvid_version_resp ver_resp; + struct synthvid_vram_location vram; + struct synthvid_vram_location_ack vram_ack; + struct synthvid_situation_update situ; + struct synthvid_situation_update_ack situ_ack; + struct synthvid_pointer_position ptr_pos; + struct synthvid_pointer_shape ptr_shape; + struct synthvid_feature_change feature_chg; + struct synthvid_dirt dirt; + }; +} __packed; + + + +/* FB driver definitions and structures */ +#define HVFB_WIDTH 1152 /* default screen width */ +#define HVFB_HEIGHT 864 /* default screen height */ +#define HVFB_WIDTH_MIN 640 +#define HVFB_HEIGHT_MIN 480 + +#define RING_BUFSIZE (256 * 1024) +#define VSP_TIMEOUT (10 * HZ) +#define HVFB_UPDATE_DELAY (HZ / 20) + +struct hvfb_par { + struct fb_info *info; + struct resource mem; + bool fb_ready; /* fb device is ready */ + struct completion wait; + u32 synthvid_version; + + struct delayed_work dwork; + bool update; + + u32 pseudo_palette[16]; + u8 init_buf[MAX_VMBUS_PKT_SIZE]; + u8 recv_buf[MAX_VMBUS_PKT_SIZE]; +}; + +static uint screen_width = HVFB_WIDTH; +static uint screen_height = HVFB_HEIGHT; +static uint screen_depth; +static uint screen_fb_size; + +/* Send message to Hyper-V host */ +static inline int synthvid_send(struct hv_device *hdev, + struct synthvid_msg *msg) +{ + static atomic64_t request_id = ATOMIC64_INIT(0); + int ret; + + msg->pipe_hdr.type = PIPE_MSG_DATA; + msg->pipe_hdr.size = msg->vid_hdr.size; + + ret = vmbus_sendpacket(hdev->channel, msg, + msg->vid_hdr.size + sizeof(struct pipe_msg_hdr), + atomic64_inc_return(&request_id), + VM_PKT_DATA_INBAND, 0); + + if (ret) + pr_err("Unable to send packet via vmbus\n"); + + return ret; +} + + +/* Send screen resolution info to host */ +static int synthvid_send_situ(struct hv_device *hdev) +{ + struct fb_info *info = hv_get_drvdata(hdev); + struct synthvid_msg msg; + + if (!info) + return -ENODEV; + + memset(&msg, 0, sizeof(struct synthvid_msg)); + + msg.vid_hdr.type = SYNTHVID_SITUATION_UPDATE; + msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) + + sizeof(struct synthvid_situation_update); + msg.situ.user_ctx = 0; + msg.situ.video_output_count = 1; + msg.situ.video_output[0].active = 1; + msg.situ.video_output[0].vram_offset = 0; + msg.situ.video_output[0].depth_bits = info->var.bits_per_pixel; + msg.situ.video_output[0].width_pixels = info->var.xres; + msg.situ.video_output[0].height_pixels = info->var.yres; + msg.situ.video_output[0].pitch_bytes = info->fix.line_length; + + synthvid_send(hdev, &msg); + + return 0; +} + +/* Send mouse pointer info to host */ +static int synthvid_send_ptr(struct hv_device *hdev) +{ + struct synthvid_msg msg; + + memset(&msg, 0, sizeof(struct synthvid_msg)); + msg.vid_hdr.type = SYNTHVID_POINTER_POSITION; + msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) + + sizeof(struct synthvid_pointer_position); + msg.ptr_pos.is_visible = 1; + msg.ptr_pos.video_output = 0; + msg.ptr_pos.image_x = 0; + msg.ptr_pos.image_y = 0; + synthvid_send(hdev, &msg); + + memset(&msg, 0, sizeof(struct synthvid_msg)); + msg.vid_hdr.type = SYNTHVID_POINTER_SHAPE; + msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) + + sizeof(struct synthvid_pointer_shape); + msg.ptr_shape.part_idx = CURSOR_COMPLETE; + msg.ptr_shape.is_argb = 1; + msg.ptr_shape.width = 1; + msg.ptr_shape.height = 1; + msg.ptr_shape.hot_x = 0; + msg.ptr_shape.hot_y = 0; + msg.ptr_shape.data[0] = 0; + msg.ptr_shape.data[1] = 1; + msg.ptr_shape.data[2] = 1; + msg.ptr_shape.data[3] = 1; + synthvid_send(hdev, &msg); + + return 0; +} + +/* Send updated screen area (dirty rectangle) location to host */ +static int synthvid_update(struct fb_info *info) +{ + struct hv_device *hdev = device_to_hv_device(info->device); + struct synthvid_msg msg; + + memset(&msg, 0, sizeof(struct synthvid_msg)); + + msg.vid_hdr.type = SYNTHVID_DIRT; + msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) + + sizeof(struct synthvid_dirt); + msg.dirt.video_output = 0; + msg.dirt.dirt_count = 1; + msg.dirt.rect[0].x1 = 0; + msg.dirt.rect[0].y1 = 0; + msg.dirt.rect[0].x2 = info->var.xres; + msg.dirt.rect[0].y2 = info->var.yres; + + synthvid_send(hdev, &msg); + + return 0; +} + + +/* + * Actions on received messages from host: + * Complete the wait event. + * Or, reply with screen and cursor info. + */ +static void synthvid_recv_sub(struct hv_device *hdev) +{ + struct fb_info *info = hv_get_drvdata(hdev); + struct hvfb_par *par; + struct synthvid_msg *msg; + + if (!info) + return; + + par = info->par; + msg = (struct synthvid_msg *)par->recv_buf; + + /* Complete the wait event */ + if (msg->vid_hdr.type == SYNTHVID_VERSION_RESPONSE || + msg->vid_hdr.type == SYNTHVID_VRAM_LOCATION_ACK) { + memcpy(par->init_buf, msg, MAX_VMBUS_PKT_SIZE); + complete(&par->wait); + return; + } + + /* Reply with screen and cursor info */ + if (msg->vid_hdr.type == SYNTHVID_FEATURE_CHANGE) { + if (par->fb_ready) { + synthvid_send_ptr(hdev); + synthvid_send_situ(hdev); + } + + par->update = msg->feature_chg.is_dirt_needed; + if (par->update) + schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY); + } +} + +/* Receive callback for messages from the host */ +static void synthvid_receive(void *ctx) +{ + struct hv_device *hdev = ctx; + struct fb_info *info = hv_get_drvdata(hdev); + struct hvfb_par *par; + struct synthvid_msg *recv_buf; + u32 bytes_recvd; + u64 req_id; + int ret; + + if (!info) + return; + + par = info->par; + recv_buf = (struct synthvid_msg *)par->recv_buf; + + do { + ret = vmbus_recvpacket(hdev->channel, recv_buf, + MAX_VMBUS_PKT_SIZE, + &bytes_recvd, &req_id); + if (bytes_recvd > 0 && + recv_buf->pipe_hdr.type == PIPE_MSG_DATA) + synthvid_recv_sub(hdev); + } while (bytes_recvd > 0 && ret == 0); +} + +/* Check synthetic video protocol version with the host */ +static int synthvid_negotiate_ver(struct hv_device *hdev, u32 ver) +{ + struct fb_info *info = hv_get_drvdata(hdev); + struct hvfb_par *par = info->par; + struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf; + int t, ret = 0; + + memset(msg, 0, sizeof(struct synthvid_msg)); + msg->vid_hdr.type = SYNTHVID_VERSION_REQUEST; + msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) + + sizeof(struct synthvid_version_req); + msg->ver_req.version = ver; + synthvid_send(hdev, msg); + + t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT); + if (!t) { + pr_err("Time out on waiting version response\n"); + ret = -ETIMEDOUT; + goto out; + } + if (!msg->ver_resp.is_accepted) { + ret = -ENODEV; + goto out; + } + + par->synthvid_version = ver; + +out: + return ret; +} + +/* Connect to VSP (Virtual Service Provider) on host */ +static int synthvid_connect_vsp(struct hv_device *hdev) +{ + struct fb_info *info = hv_get_drvdata(hdev); + struct hvfb_par *par = info->par; + int ret; + + ret = vmbus_open(hdev->channel, RING_BUFSIZE, RING_BUFSIZE, + NULL, 0, synthvid_receive, hdev); + if (ret) { + pr_err("Unable to open vmbus channel\n"); + return ret; + } + + /* Negotiate the protocol version with host */ + if (vmbus_proto_version == VERSION_WS2008 || + vmbus_proto_version == VERSION_WIN7) + ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7); + else + ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN8); + + if (ret) { + pr_err("Synthetic video device version not accepted\n"); + goto error; + } + + if (par->synthvid_version == SYNTHVID_VERSION_WIN7) + screen_depth = SYNTHVID_DEPTH_WIN7; + else + screen_depth = SYNTHVID_DEPTH_WIN8; + + screen_fb_size = hdev->channel->offermsg.offer. + mmio_megabytes * 1024 * 1024; + + return 0; + +error: + vmbus_close(hdev->channel); + return ret; +} + +/* Send VRAM and Situation messages to the host */ +static int synthvid_send_config(struct hv_device *hdev) +{ + struct fb_info *info = hv_get_drvdata(hdev); + struct hvfb_par *par = info->par; + struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf; + int t, ret = 0; + + /* Send VRAM location */ + memset(msg, 0, sizeof(struct synthvid_msg)); + msg->vid_hdr.type = SYNTHVID_VRAM_LOCATION; + msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) + + sizeof(struct synthvid_vram_location); + msg->vram.user_ctx = msg->vram.vram_gpa = info->fix.smem_start; + msg->vram.is_vram_gpa_specified = 1; + synthvid_send(hdev, msg); + + t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT); + if (!t) { + pr_err("Time out on waiting vram location ack\n"); + ret = -ETIMEDOUT; + goto out; + } + if (msg->vram_ack.user_ctx != info->fix.smem_start) { + pr_err("Unable to set VRAM location\n"); + ret = -ENODEV; + goto out; + } + + /* Send pointer and situation update */ + synthvid_send_ptr(hdev); + synthvid_send_situ(hdev); + +out: + return ret; +} + + +/* + * Delayed work callback: + * It is called at HVFB_UPDATE_DELAY or longer time interval to process + * screen updates. It is re-scheduled if further update is necessary. + */ +static void hvfb_update_work(struct work_struct *w) +{ + struct hvfb_par *par = container_of(w, struct hvfb_par, dwork.work); + struct fb_info *info = par->info; + + if (par->fb_ready) + synthvid_update(info); + + if (par->update) + schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY); +} + + +/* Framebuffer operation handlers */ + +static int hvfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + if (var->xres < HVFB_WIDTH_MIN || var->yres < HVFB_HEIGHT_MIN || + var->xres > screen_width || var->yres > screen_height || + var->bits_per_pixel != screen_depth) + return -EINVAL; + + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + return 0; +} + +static int hvfb_set_par(struct fb_info *info) +{ + struct hv_device *hdev = device_to_hv_device(info->device); + + return synthvid_send_situ(hdev); +} + + +static inline u32 chan_to_field(u32 chan, struct fb_bitfield *bf) +{ + return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset; +} + +static int hvfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + u32 *pal = info->pseudo_palette; + + if (regno > 15) + return -EINVAL; + + pal[regno] = chan_to_field(red, &info->var.red) + | chan_to_field(green, &info->var.green) + | chan_to_field(blue, &info->var.blue) + | chan_to_field(transp, &info->var.transp); + + return 0; +} + +static int hvfb_blank(int blank, struct fb_info *info) +{ + return 1; /* get fb_blank to set the colormap to all black */ +} + +static struct fb_ops hvfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = hvfb_check_var, + .fb_set_par = hvfb_set_par, + .fb_setcolreg = hvfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = hvfb_blank, +}; + + +/* Get options from kernel paramenter "video=" */ +static void hvfb_get_option(struct fb_info *info) +{ + struct hvfb_par *par = info->par; + char *opt = NULL, *p; + uint x = 0, y = 0; + + if (fb_get_options(KBUILD_MODNAME, &opt) || !opt || !*opt) + return; + + p = strsep(&opt, "x"); + if (!*p || kstrtouint(p, 0, &x) || + !opt || !*opt || kstrtouint(opt, 0, &y)) { + pr_err("Screen option is invalid: skipped\n"); + return; + } + + if (x < HVFB_WIDTH_MIN || y < HVFB_HEIGHT_MIN || + (par->synthvid_version == SYNTHVID_VERSION_WIN8 && + x * y * screen_depth / 8 > SYNTHVID_FB_SIZE_WIN8) || + (par->synthvid_version == SYNTHVID_VERSION_WIN7 && + (x > SYNTHVID_WIDTH_MAX_WIN7 || y > SYNTHVID_HEIGHT_MAX_WIN7))) { + pr_err("Screen resolution option is out of range: skipped\n"); + return; + } + + screen_width = x; + screen_height = y; + return; +} + + +/* Get framebuffer memory from Hyper-V video pci space */ +static int hvfb_getmem(struct fb_info *info) +{ + struct hvfb_par *par = info->par; + struct pci_dev *pdev = NULL; + void __iomem *fb_virt; + int gen2vm = efi_enabled(EFI_BOOT); + int ret; + + par->mem.name = KBUILD_MODNAME; + par->mem.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + if (gen2vm) { + ret = allocate_resource(&hyperv_mmio, &par->mem, + screen_fb_size, + 0, -1, + screen_fb_size, + NULL, NULL); + if (ret != 0) { + pr_err("Unable to allocate framebuffer memory\n"); + return -ENODEV; + } + } else { + pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT, + PCI_DEVICE_ID_HYPERV_VIDEO, NULL); + if (!pdev) { + pr_err("Unable to find PCI Hyper-V video\n"); + return -ENODEV; + } + + if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) || + pci_resource_len(pdev, 0) < screen_fb_size) + goto err1; + + par->mem.end = pci_resource_end(pdev, 0); + par->mem.start = par->mem.end - screen_fb_size + 1; + ret = request_resource(&pdev->resource[0], &par->mem); + if (ret != 0) { + pr_err("Unable to request framebuffer memory\n"); + goto err1; + } + } + + fb_virt = ioremap(par->mem.start, screen_fb_size); + if (!fb_virt) + goto err2; + + info->apertures = alloc_apertures(1); + if (!info->apertures) + goto err3; + + if (gen2vm) { + info->apertures->ranges[0].base = screen_info.lfb_base; + info->apertures->ranges[0].size = screen_info.lfb_size; + remove_conflicting_framebuffers(info->apertures, + KBUILD_MODNAME, false); + } else { + info->apertures->ranges[0].base = pci_resource_start(pdev, 0); + info->apertures->ranges[0].size = pci_resource_len(pdev, 0); + } + + info->fix.smem_start = par->mem.start; + info->fix.smem_len = screen_fb_size; + info->screen_base = fb_virt; + info->screen_size = screen_fb_size; + + if (!gen2vm) + pci_dev_put(pdev); + + return 0; + +err3: + iounmap(fb_virt); +err2: + release_resource(&par->mem); +err1: + if (!gen2vm) + pci_dev_put(pdev); + + return -ENOMEM; +} + +/* Release the framebuffer */ +static void hvfb_putmem(struct fb_info *info) +{ + struct hvfb_par *par = info->par; + + iounmap(info->screen_base); + release_resource(&par->mem); +} + + +static int hvfb_probe(struct hv_device *hdev, + const struct hv_vmbus_device_id *dev_id) +{ + struct fb_info *info; + struct hvfb_par *par; + int ret; + + info = framebuffer_alloc(sizeof(struct hvfb_par), &hdev->device); + if (!info) { + pr_err("No memory for framebuffer info\n"); + return -ENOMEM; + } + + par = info->par; + par->info = info; + par->fb_ready = false; + init_completion(&par->wait); + INIT_DELAYED_WORK(&par->dwork, hvfb_update_work); + + /* Connect to VSP */ + hv_set_drvdata(hdev, info); + ret = synthvid_connect_vsp(hdev); + if (ret) { + pr_err("Unable to connect to VSP\n"); + goto error1; + } + + ret = hvfb_getmem(info); + if (ret) { + pr_err("No memory for framebuffer\n"); + goto error2; + } + + hvfb_get_option(info); + pr_info("Screen resolution: %dx%d, Color depth: %d\n", + screen_width, screen_height, screen_depth); + + + /* Set up fb_info */ + info->flags = FBINFO_DEFAULT; + + info->var.xres_virtual = info->var.xres = screen_width; + info->var.yres_virtual = info->var.yres = screen_height; + info->var.bits_per_pixel = screen_depth; + + if (info->var.bits_per_pixel == 16) { + info->var.red = (struct fb_bitfield){11, 5, 0}; + info->var.green = (struct fb_bitfield){5, 6, 0}; + info->var.blue = (struct fb_bitfield){0, 5, 0}; + info->var.transp = (struct fb_bitfield){0, 0, 0}; + } else { + info->var.red = (struct fb_bitfield){16, 8, 0}; + info->var.green = (struct fb_bitfield){8, 8, 0}; + info->var.blue = (struct fb_bitfield){0, 8, 0}; + info->var.transp = (struct fb_bitfield){24, 8, 0}; + } + + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.vmode = FB_VMODE_NONINTERLACED; + + strcpy(info->fix.id, KBUILD_MODNAME); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = screen_width * screen_depth / 8; + info->fix.accel = FB_ACCEL_NONE; + + info->fbops = &hvfb_ops; + info->pseudo_palette = par->pseudo_palette; + + /* Send config to host */ + ret = synthvid_send_config(hdev); + if (ret) + goto error; + + ret = register_framebuffer(info); + if (ret) { + pr_err("Unable to register framebuffer\n"); + goto error; + } + + par->fb_ready = true; + + return 0; + +error: + hvfb_putmem(info); +error2: + vmbus_close(hdev->channel); +error1: + cancel_delayed_work_sync(&par->dwork); + hv_set_drvdata(hdev, NULL); + framebuffer_release(info); + return ret; +} + + +static int hvfb_remove(struct hv_device *hdev) +{ + struct fb_info *info = hv_get_drvdata(hdev); + struct hvfb_par *par = info->par; + + par->update = false; + par->fb_ready = false; + + unregister_framebuffer(info); + cancel_delayed_work_sync(&par->dwork); + + vmbus_close(hdev->channel); + hv_set_drvdata(hdev, NULL); + + hvfb_putmem(info); + framebuffer_release(info); + + return 0; +} + + +static DEFINE_PCI_DEVICE_TABLE(pci_stub_id_table) = { + { + .vendor = PCI_VENDOR_ID_MICROSOFT, + .device = PCI_DEVICE_ID_HYPERV_VIDEO, + }, + { /* end of list */ } +}; + +static const struct hv_vmbus_device_id id_table[] = { + /* Synthetic Video Device GUID */ + {HV_SYNTHVID_GUID}, + {} +}; + +MODULE_DEVICE_TABLE(pci, pci_stub_id_table); +MODULE_DEVICE_TABLE(vmbus, id_table); + +static struct hv_driver hvfb_drv = { + .name = KBUILD_MODNAME, + .id_table = id_table, + .probe = hvfb_probe, + .remove = hvfb_remove, +}; + +static int hvfb_pci_stub_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + return 0; +} + +static void hvfb_pci_stub_remove(struct pci_dev *pdev) +{ +} + +static struct pci_driver hvfb_pci_stub_driver = { + .name = KBUILD_MODNAME, + .id_table = pci_stub_id_table, + .probe = hvfb_pci_stub_probe, + .remove = hvfb_pci_stub_remove, +}; + +static int __init hvfb_drv_init(void) +{ + int ret; + + ret = vmbus_driver_register(&hvfb_drv); + if (ret != 0) + return ret; + + ret = pci_register_driver(&hvfb_pci_stub_driver); + if (ret != 0) { + vmbus_driver_unregister(&hvfb_drv); + return ret; + } + + return 0; +} + +static void __exit hvfb_drv_exit(void) +{ + pci_unregister_driver(&hvfb_pci_stub_driver); + vmbus_driver_unregister(&hvfb_drv); +} + +module_init(hvfb_drv_init); +module_exit(hvfb_drv_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic Video Frame Buffer Driver"); diff --git a/drivers/video/fbdev/i740_reg.h b/drivers/video/fbdev/i740_reg.h new file mode 100644 index 000000000000..91bac76549d7 --- /dev/null +++ b/drivers/video/fbdev/i740_reg.h @@ -0,0 +1,309 @@ +/************************************************************************** + +Copyright 1998-1999 Precision Insight, Inc., Cedar Park, Texas. +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sub license, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. +IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +**************************************************************************/ + +/* + * Authors: + * Kevin E. Martin <kevin@precisioninsight.com> + */ + +/* I/O register offsets */ +#define SRX VGA_SEQ_I +#define GRX VGA_GFX_I +#define ARX VGA_ATT_IW +#define XRX 0x3D6 +#define MRX 0x3D2 + +/* VGA Color Palette Registers */ +#define DACMASK 0x3C6 +#define DACSTATE 0x3C7 +#define DACRX 0x3C7 +#define DACWX 0x3C8 +#define DACDATA 0x3C9 + +/* CRT Controller Registers (CRX) */ +#define START_ADDR_HI 0x0C +#define START_ADDR_LO 0x0D +#define VERT_SYNC_END 0x11 +#define EXT_VERT_TOTAL 0x30 +#define EXT_VERT_DISPLAY 0x31 +#define EXT_VERT_SYNC_START 0x32 +#define EXT_VERT_BLANK_START 0x33 +#define EXT_HORIZ_TOTAL 0x35 +#define EXT_HORIZ_BLANK 0x39 +#define EXT_START_ADDR 0x40 +#define EXT_START_ADDR_ENABLE 0x80 +#define EXT_OFFSET 0x41 +#define EXT_START_ADDR_HI 0x42 +#define INTERLACE_CNTL 0x70 +#define INTERLACE_ENABLE 0x80 +#define INTERLACE_DISABLE 0x00 + +/* Miscellaneous Output Register */ +#define MSR_R 0x3CC +#define MSR_W 0x3C2 +#define IO_ADDR_SELECT 0x01 + +#define MDA_BASE 0x3B0 +#define CGA_BASE 0x3D0 + +/* System Configuration Extension Registers (XRX) */ +#define IO_CTNL 0x09 +#define EXTENDED_ATTR_CNTL 0x02 +#define EXTENDED_CRTC_CNTL 0x01 + +#define ADDRESS_MAPPING 0x0A +#define PACKED_MODE_ENABLE 0x04 +#define LINEAR_MODE_ENABLE 0x02 +#define PAGE_MAPPING_ENABLE 0x01 + +#define BITBLT_CNTL 0x20 +#define COLEXP_MODE 0x30 +#define COLEXP_8BPP 0x00 +#define COLEXP_16BPP 0x10 +#define COLEXP_24BPP 0x20 +#define COLEXP_RESERVED 0x30 +#define CHIP_RESET 0x02 +#define BITBLT_STATUS 0x01 + +#define DISPLAY_CNTL 0x40 +#define VGA_WRAP_MODE 0x02 +#define VGA_WRAP_AT_256KB 0x00 +#define VGA_NO_WRAP 0x02 +#define GUI_MODE 0x01 +#define STANDARD_VGA_MODE 0x00 +#define HIRES_MODE 0x01 + +#define DRAM_ROW_TYPE 0x50 +#define DRAM_ROW_0 0x07 +#define DRAM_ROW_0_SDRAM 0x00 +#define DRAM_ROW_0_EMPTY 0x07 +#define DRAM_ROW_1 0x38 +#define DRAM_ROW_1_SDRAM 0x00 +#define DRAM_ROW_1_EMPTY 0x38 +#define DRAM_ROW_CNTL_LO 0x51 +#define DRAM_CAS_LATENCY 0x10 +#define DRAM_RAS_TIMING 0x08 +#define DRAM_RAS_PRECHARGE 0x04 +#define DRAM_ROW_CNTL_HI 0x52 +#define DRAM_EXT_CNTL 0x53 +#define DRAM_REFRESH_RATE 0x03 +#define DRAM_REFRESH_DISABLE 0x00 +#define DRAM_REFRESH_60HZ 0x01 +#define DRAM_REFRESH_FAST_TEST 0x02 +#define DRAM_REFRESH_RESERVED 0x03 +#define DRAM_TIMING 0x54 +#define DRAM_ROW_BNDRY_0 0x55 +#define DRAM_ROW_BNDRY_1 0x56 + +#define DPMS_SYNC_SELECT 0x61 +#define VSYNC_CNTL 0x08 +#define VSYNC_ON 0x00 +#define VSYNC_OFF 0x08 +#define HSYNC_CNTL 0x02 +#define HSYNC_ON 0x00 +#define HSYNC_OFF 0x02 + +#define PIXPIPE_CONFIG_0 0x80 +#define DAC_8_BIT 0x80 +#define DAC_6_BIT 0x00 +#define HW_CURSOR_ENABLE 0x10 +#define EXTENDED_PALETTE 0x01 + +#define PIXPIPE_CONFIG_1 0x81 +#define DISPLAY_COLOR_MODE 0x0F +#define DISPLAY_VGA_MODE 0x00 +#define DISPLAY_8BPP_MODE 0x02 +#define DISPLAY_15BPP_MODE 0x04 +#define DISPLAY_16BPP_MODE 0x05 +#define DISPLAY_24BPP_MODE 0x06 +#define DISPLAY_32BPP_MODE 0x07 + +#define PIXPIPE_CONFIG_2 0x82 +#define DISPLAY_GAMMA_ENABLE 0x08 +#define DISPLAY_GAMMA_DISABLE 0x00 +#define OVERLAY_GAMMA_ENABLE 0x04 +#define OVERLAY_GAMMA_DISABLE 0x00 + +#define CURSOR_CONTROL 0xA0 +#define CURSOR_ORIGIN_SCREEN 0x00 +#define CURSOR_ORIGIN_DISPLAY 0x10 +#define CURSOR_MODE 0x07 +#define CURSOR_MODE_DISABLE 0x00 +#define CURSOR_MODE_32_4C_AX 0x01 +#define CURSOR_MODE_128_2C 0x02 +#define CURSOR_MODE_128_1C 0x03 +#define CURSOR_MODE_64_3C 0x04 +#define CURSOR_MODE_64_4C_AX 0x05 +#define CURSOR_MODE_64_4C 0x06 +#define CURSOR_MODE_RESERVED 0x07 +#define CURSOR_BASEADDR_LO 0xA2 +#define CURSOR_BASEADDR_HI 0xA3 +#define CURSOR_X_LO 0xA4 +#define CURSOR_X_HI 0xA5 +#define CURSOR_X_POS 0x00 +#define CURSOR_X_NEG 0x80 +#define CURSOR_Y_LO 0xA6 +#define CURSOR_Y_HI 0xA7 +#define CURSOR_Y_POS 0x00 +#define CURSOR_Y_NEG 0x80 + +#define VCLK2_VCO_M 0xC8 +#define VCLK2_VCO_N 0xC9 +#define VCLK2_VCO_MN_MSBS 0xCA +#define VCO_N_MSBS 0x30 +#define VCO_M_MSBS 0x03 +#define VCLK2_VCO_DIV_SEL 0xCB +#define POST_DIV_SELECT 0x70 +#define POST_DIV_1 0x00 +#define POST_DIV_2 0x10 +#define POST_DIV_4 0x20 +#define POST_DIV_8 0x30 +#define POST_DIV_16 0x40 +#define POST_DIV_32 0x50 +#define VCO_LOOP_DIV_BY_4M 0x00 +#define VCO_LOOP_DIV_BY_16M 0x04 +#define REF_CLK_DIV_BY_5 0x02 +#define REF_DIV_4 0x00 +#define REF_DIV_1 0x01 + +#define PLL_CNTL 0xCE +#define PLL_MEMCLK_SEL 0x03 +#define PLL_MEMCLK__66667KHZ 0x00 +#define PLL_MEMCLK__75000KHZ 0x01 +#define PLL_MEMCLK__88889KHZ 0x02 +#define PLL_MEMCLK_100000KHZ 0x03 + +/* Multimedia Extension Registers (MRX) */ +#define ACQ_CNTL_1 0x02 +#define ACQ_CNTL_2 0x03 +#define FRAME_CAP_MODE 0x01 +#define CONT_CAP_MODE 0x00 +#define SINGLE_CAP_MODE 0x01 +#define ACQ_CNTL_3 0x04 +#define COL_KEY_CNTL_1 0x3C +#define BLANK_DISP_OVERLAY 0x20 + +/* FIFOs */ +#define LP_FIFO 0x1000 +#define HP_FIFO 0x2000 +#define INSTPNT 0x3040 +#define LP_FIFO_COUNT 0x3040 +#define HP_FIFO_COUNT 0x3041 + +/* FIFO Commands */ +#define CLIENT 0xE0000000 +#define CLIENT_2D 0x60000000 + +/* Command Parser Mode Register */ +#define COMPARS 0x3038 +#define TWO_D_INST_DISABLE 0x08 +#define THREE_D_INST_DISABLE 0x04 +#define STATE_VAR_UPDATE_DISABLE 0x02 +#define PAL_STIP_DISABLE 0x01 + +/* Interrupt Control Registers */ +#define IER 0x3030 +#define IIR 0x3032 +#define IMR 0x3034 +#define ISR 0x3036 +#define VMIINTB_EVENT 0x2000 +#define GPIO4_INT 0x1000 +#define DISP_FLIP_EVENT 0x0800 +#define DVD_PORT_DMA 0x0400 +#define DISP_VBLANK 0x0200 +#define FIFO_EMPTY_DMA_DONE 0x0100 +#define INST_PARSER_ERROR 0x0080 +#define USER_DEFINED 0x0040 +#define BREAKPOINT 0x0020 +#define DISP_HORIZ_COUNT 0x0010 +#define DISP_VSYNC 0x0008 +#define CAPTURE_HORIZ_COUNT 0x0004 +#define CAPTURE_VSYNC 0x0002 +#define THREE_D_PIPE_FLUSHED 0x0001 + +/* FIFO Watermark and Burst Length Control Register */ +#define FWATER_BLC 0x00006000 +#define LMI_BURST_LENGTH 0x7F000000 +#define LMI_FIFO_WATERMARK 0x003F0000 +#define AGP_BURST_LENGTH 0x00007F00 +#define AGP_FIFO_WATERMARK 0x0000003F + +/* BitBLT Registers */ +#define SRC_DST_PITCH 0x00040000 +#define DST_PITCH 0x1FFF0000 +#define SRC_PITCH 0x00001FFF +#define COLEXP_BG_COLOR 0x00040004 +#define COLEXP_FG_COLOR 0x00040008 +#define MONO_SRC_CNTL 0x0004000C +#define MONO_USE_COLEXP 0x00000000 +#define MONO_USE_SRCEXP 0x08000000 +#define MONO_DATA_ALIGN 0x07000000 +#define MONO_BIT_ALIGN 0x01000000 +#define MONO_BYTE_ALIGN 0x02000000 +#define MONO_WORD_ALIGN 0x03000000 +#define MONO_DWORD_ALIGN 0x04000000 +#define MONO_QWORD_ALIGN 0x05000000 +#define MONO_SRC_INIT_DSCRD 0x003F0000 +#define MONO_SRC_RIGHT_CLIP 0x00003F00 +#define MONO_SRC_LEFT_CLIP 0x0000003F +#define BITBLT_CONTROL 0x00040010 +#define BLTR_STATUS 0x80000000 +#define DYN_DEPTH 0x03000000 +#define DYN_DEPTH_8BPP 0x00000000 +#define DYN_DEPTH_16BPP 0x01000000 +#define DYN_DEPTH_24BPP 0x02000000 +#define DYN_DEPTH_32BPP 0x03000000 /* Unimplemented on the i740 */ +#define DYN_DEPTH_ENABLE 0x00800000 +#define PAT_VERT_ALIGN 0x00700000 +#define SOLID_PAT_SELECT 0x00080000 +#define PAT_IS_IN_COLOR 0x00000000 +#define PAT_IS_MONO 0x00040000 +#define MONO_PAT_TRANSP 0x00020000 +#define COLOR_TRANSP_ROP 0x00000000 +#define COLOR_TRANSP_DST 0x00008000 +#define COLOR_TRANSP_EQ 0x00000000 +#define COLOR_TRANSP_NOT_EQ 0x00010000 +#define COLOR_TRANSP_ENABLE 0x00004000 +#define MONO_SRC_TRANSP 0x00002000 +#define SRC_IS_IN_COLOR 0x00000000 +#define SRC_IS_MONO 0x00001000 +#define SRC_USE_SRC_ADDR 0x00000000 +#define SRC_USE_BLTDATA 0x00000400 +#define BLT_TOP_TO_BOT 0x00000000 +#define BLT_BOT_TO_TOP 0x00000200 +#define BLT_LEFT_TO_RIGHT 0x00000000 +#define BLT_RIGHT_TO_LEFT 0x00000100 +#define BLT_ROP 0x000000FF +#define BLT_PAT_ADDR 0x00040014 +#define BLT_SRC_ADDR 0x00040018 +#define BLT_DST_ADDR 0x0004001C +#define BLT_DST_H_W 0x00040020 +#define BLT_DST_HEIGHT 0x1FFF0000 +#define BLT_DST_WIDTH 0x00001FFF +#define SRCEXP_BG_COLOR 0x00040024 +#define SRCEXP_FG_COLOR 0x00040028 +#define BLTDATA 0x00050000 diff --git a/drivers/video/fbdev/i740fb.c b/drivers/video/fbdev/i740fb.c new file mode 100644 index 000000000000..ca7c9df193b0 --- /dev/null +++ b/drivers/video/fbdev/i740fb.c @@ -0,0 +1,1333 @@ +/* + * i740fb - framebuffer driver for Intel740 + * Copyright (c) 2011 Ondrej Zary + * + * Based on old i740fb driver (c) 2001-2002 Andrey Ulanov <drey@rt.mipt.ru> + * which was partially based on: + * VGA 16-color framebuffer driver (c) 1999 Ben Pfaff <pfaffben@debian.org> + * and Petr Vandrovec <VANDROVE@vc.cvut.cz> + * i740 driver from XFree86 (c) 1998-1999 Precision Insight, Inc., Cedar Park, + * Texas. + * i740fb by Patrick LERDA, v0.9 + */ + +#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> +#include <linux/pci_ids.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/console.h> +#include <video/vga.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include "i740_reg.h" + +static char *mode_option; + +#ifdef CONFIG_MTRR +static int mtrr = 1; +#endif + +struct i740fb_par { + unsigned char __iomem *regs; + bool has_sgram; +#ifdef CONFIG_MTRR + int mtrr_reg; +#endif + bool ddc_registered; + struct i2c_adapter ddc_adapter; + struct i2c_algo_bit_data ddc_algo; + u32 pseudo_palette[16]; + struct mutex open_lock; + unsigned int ref_count; + + u8 crtc[VGA_CRT_C]; + u8 atc[VGA_ATT_C]; + u8 gdc[VGA_GFX_C]; + u8 seq[VGA_SEQ_C]; + u8 misc; + u8 vss; + + /* i740 specific registers */ + u8 display_cntl; + u8 pixelpipe_cfg0; + u8 pixelpipe_cfg1; + u8 pixelpipe_cfg2; + u8 video_clk2_m; + u8 video_clk2_n; + u8 video_clk2_mn_msbs; + u8 video_clk2_div_sel; + u8 pll_cntl; + u8 address_mapping; + u8 io_cntl; + u8 bitblt_cntl; + u8 ext_vert_total; + u8 ext_vert_disp_end; + u8 ext_vert_sync_start; + u8 ext_vert_blank_start; + u8 ext_horiz_total; + u8 ext_horiz_blank; + u8 ext_offset; + u8 interlace_cntl; + u32 lmi_fifo_watermark; + u8 ext_start_addr; + u8 ext_start_addr_hi; +}; + +#define DACSPEED8 203 +#define DACSPEED16 163 +#define DACSPEED24_SG 136 +#define DACSPEED24_SD 128 +#define DACSPEED32 86 + +static struct fb_fix_screeninfo i740fb_fix = { + .id = "i740fb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 8, + .ypanstep = 1, + .accel = FB_ACCEL_NONE, +}; + +static inline void i740outb(struct i740fb_par *par, u16 port, u8 val) +{ + vga_mm_w(par->regs, port, val); +} +static inline u8 i740inb(struct i740fb_par *par, u16 port) +{ + return vga_mm_r(par->regs, port); +} +static inline void i740outreg(struct i740fb_par *par, u16 port, u8 reg, u8 val) +{ + vga_mm_w_fast(par->regs, port, reg, val); +} +static inline u8 i740inreg(struct i740fb_par *par, u16 port, u8 reg) +{ + vga_mm_w(par->regs, port, reg); + return vga_mm_r(par->regs, port+1); +} +static inline void i740outreg_mask(struct i740fb_par *par, u16 port, u8 reg, + u8 val, u8 mask) +{ + vga_mm_w_fast(par->regs, port, reg, (val & mask) + | (i740inreg(par, port, reg) & ~mask)); +} + +#define REG_DDC_DRIVE 0x62 +#define REG_DDC_STATE 0x63 +#define DDC_SCL (1 << 3) +#define DDC_SDA (1 << 2) + +static void i740fb_ddc_setscl(void *data, int val) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, DDC_SCL, DDC_SCL); + i740outreg_mask(par, XRX, REG_DDC_STATE, val ? DDC_SCL : 0, DDC_SCL); +} + +static void i740fb_ddc_setsda(void *data, int val) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, DDC_SDA, DDC_SDA); + i740outreg_mask(par, XRX, REG_DDC_STATE, val ? DDC_SDA : 0, DDC_SDA); +} + +static int i740fb_ddc_getscl(void *data) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, 0, DDC_SCL); + + return !!(i740inreg(par, XRX, REG_DDC_STATE) & DDC_SCL); +} + +static int i740fb_ddc_getsda(void *data) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, 0, DDC_SDA); + + return !!(i740inreg(par, XRX, REG_DDC_STATE) & DDC_SDA); +} + +static int i740fb_setup_ddc_bus(struct fb_info *info) +{ + struct i740fb_par *par = info->par; + + strlcpy(par->ddc_adapter.name, info->fix.id, + sizeof(par->ddc_adapter.name)); + par->ddc_adapter.owner = THIS_MODULE; + par->ddc_adapter.class = I2C_CLASS_DDC; + par->ddc_adapter.algo_data = &par->ddc_algo; + par->ddc_adapter.dev.parent = info->device; + par->ddc_algo.setsda = i740fb_ddc_setsda; + par->ddc_algo.setscl = i740fb_ddc_setscl; + par->ddc_algo.getsda = i740fb_ddc_getsda; + par->ddc_algo.getscl = i740fb_ddc_getscl; + par->ddc_algo.udelay = 10; + par->ddc_algo.timeout = 20; + par->ddc_algo.data = par; + + i2c_set_adapdata(&par->ddc_adapter, par); + + return i2c_bit_add_bus(&par->ddc_adapter); +} + +static int i740fb_open(struct fb_info *info, int user) +{ + struct i740fb_par *par = info->par; + + mutex_lock(&(par->open_lock)); + par->ref_count++; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +static int i740fb_release(struct fb_info *info, int user) +{ + struct i740fb_par *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + fb_err(info, "release called with zero refcount\n"); + mutex_unlock(&(par->open_lock)); + return -EINVAL; + } + + par->ref_count--; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +static u32 i740_calc_fifo(struct i740fb_par *par, u32 freq, int bpp) +{ + /* + * Would like to calculate these values automatically, but a generic + * algorithm does not seem possible. Note: These FIFO water mark + * values were tested on several cards and seem to eliminate the + * all of the snow and vertical banding, but fine adjustments will + * probably be required for other cards. + */ + + u32 wm; + + switch (bpp) { + case 8: + if (freq > 200) + wm = 0x18120000; + else if (freq > 175) + wm = 0x16110000; + else if (freq > 135) + wm = 0x120E0000; + else + wm = 0x100D0000; + break; + case 15: + case 16: + if (par->has_sgram) { + if (freq > 140) + wm = 0x2C1D0000; + else if (freq > 120) + wm = 0x2C180000; + else if (freq > 100) + wm = 0x24160000; + else if (freq > 90) + wm = 0x18120000; + else if (freq > 50) + wm = 0x16110000; + else if (freq > 32) + wm = 0x13100000; + else + wm = 0x120E0000; + } else { + if (freq > 160) + wm = 0x28200000; + else if (freq > 140) + wm = 0x2A1E0000; + else if (freq > 130) + wm = 0x2B1A0000; + else if (freq > 120) + wm = 0x2C180000; + else if (freq > 100) + wm = 0x24180000; + else if (freq > 90) + wm = 0x18120000; + else if (freq > 50) + wm = 0x16110000; + else if (freq > 32) + wm = 0x13100000; + else + wm = 0x120E0000; + } + break; + case 24: + if (par->has_sgram) { + if (freq > 130) + wm = 0x31200000; + else if (freq > 120) + wm = 0x2E200000; + else if (freq > 100) + wm = 0x2C1D0000; + else if (freq > 80) + wm = 0x25180000; + else if (freq > 64) + wm = 0x24160000; + else if (freq > 49) + wm = 0x18120000; + else if (freq > 32) + wm = 0x16110000; + else + wm = 0x13100000; + } else { + if (freq > 120) + wm = 0x311F0000; + else if (freq > 100) + wm = 0x2C1D0000; + else if (freq > 80) + wm = 0x25180000; + else if (freq > 64) + wm = 0x24160000; + else if (freq > 49) + wm = 0x18120000; + else if (freq > 32) + wm = 0x16110000; + else + wm = 0x13100000; + } + break; + case 32: + if (par->has_sgram) { + if (freq > 80) + wm = 0x2A200000; + else if (freq > 60) + wm = 0x281A0000; + else if (freq > 49) + wm = 0x25180000; + else if (freq > 32) + wm = 0x18120000; + else + wm = 0x16110000; + } else { + if (freq > 80) + wm = 0x29200000; + else if (freq > 60) + wm = 0x281A0000; + else if (freq > 49) + wm = 0x25180000; + else if (freq > 32) + wm = 0x18120000; + else + wm = 0x16110000; + } + break; + } + + return wm; +} + +/* clock calculation from i740fb by Patrick LERDA */ + +#define I740_RFREQ 1000000 +#define TARGET_MAX_N 30 +#define I740_FFIX (1 << 8) +#define I740_RFREQ_FIX (I740_RFREQ / I740_FFIX) +#define I740_REF_FREQ (6667 * I740_FFIX / 100) /* 66.67 MHz */ +#define I740_MAX_VCO_FREQ (450 * I740_FFIX) /* 450 MHz */ + +static void i740_calc_vclk(u32 freq, struct i740fb_par *par) +{ + const u32 err_max = freq / (200 * I740_RFREQ / I740_FFIX); + const u32 err_target = freq / (1000 * I740_RFREQ / I740_FFIX); + u32 err_best = 512 * I740_FFIX; + u32 f_err, f_vco; + int m_best = 0, n_best = 0, p_best = 0, d_best = 0; + int m, n; + + p_best = min(15, ilog2(I740_MAX_VCO_FREQ / (freq / I740_RFREQ_FIX))); + d_best = 0; + f_vco = (freq * (1 << p_best)) / I740_RFREQ_FIX; + freq = freq / I740_RFREQ_FIX; + + n = 2; + do { + n++; + m = ((f_vco * n) / I740_REF_FREQ + 2) / 4; + + if (m < 3) + m = 3; + + { + u32 f_out = (((m * I740_REF_FREQ * (4 << 2 * d_best)) + / n) + ((1 << p_best) / 2)) / (1 << p_best); + + f_err = (freq - f_out); + + if (abs(f_err) < err_max) { + m_best = m; + n_best = n; + err_best = f_err; + } + } + } while ((abs(f_err) >= err_target) && + ((n <= TARGET_MAX_N) || (abs(err_best) > err_max))); + + if (abs(f_err) < err_target) { + m_best = m; + n_best = n; + } + + par->video_clk2_m = (m_best - 2) & 0xFF; + par->video_clk2_n = (n_best - 2) & 0xFF; + par->video_clk2_mn_msbs = ((((n_best - 2) >> 4) & VCO_N_MSBS) + | (((m_best - 2) >> 8) & VCO_M_MSBS)); + par->video_clk2_div_sel = + ((p_best << 4) | (d_best ? 4 : 0) | REF_DIV_1); +} + +static int i740fb_decode_var(const struct fb_var_screeninfo *var, + struct i740fb_par *par, struct fb_info *info) +{ + /* + * Get the video params out of 'var'. + * If a value doesn't fit, round it up, if it's too big, return -EINVAL. + */ + + u32 xres, right, hslen, left, xtotal; + u32 yres, lower, vslen, upper, ytotal; + u32 vxres, xoffset, vyres, yoffset; + u32 bpp, base, dacspeed24, mem; + u8 r7; + int i; + + dev_dbg(info->device, "decode_var: xres: %i, yres: %i, xres_v: %i, xres_v: %i\n", + var->xres, var->yres, var->xres_virtual, var->xres_virtual); + dev_dbg(info->device, " xoff: %i, yoff: %i, bpp: %i, graysc: %i\n", + var->xoffset, var->yoffset, var->bits_per_pixel, + var->grayscale); + dev_dbg(info->device, " activate: %i, nonstd: %i, vmode: %i\n", + var->activate, var->nonstd, var->vmode); + dev_dbg(info->device, " pixclock: %i, hsynclen:%i, vsynclen:%i\n", + var->pixclock, var->hsync_len, var->vsync_len); + dev_dbg(info->device, " left: %i, right: %i, up:%i, lower:%i\n", + var->left_margin, var->right_margin, var->upper_margin, + var->lower_margin); + + + bpp = var->bits_per_pixel; + switch (bpp) { + case 1 ... 8: + bpp = 8; + if ((1000000 / var->pixclock) > DACSPEED8) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 8bpp)\n", + 1000000 / var->pixclock, DACSPEED8); + return -EINVAL; + } + break; + case 9 ... 15: + bpp = 15; + case 16: + if ((1000000 / var->pixclock) > DACSPEED16) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 15/16bpp)\n", + 1000000 / var->pixclock, DACSPEED16); + return -EINVAL; + } + break; + case 17 ... 24: + bpp = 24; + dacspeed24 = par->has_sgram ? DACSPEED24_SG : DACSPEED24_SD; + if ((1000000 / var->pixclock) > dacspeed24) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 24bpp)\n", + 1000000 / var->pixclock, dacspeed24); + return -EINVAL; + } + break; + case 25 ... 32: + bpp = 32; + if ((1000000 / var->pixclock) > DACSPEED32) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 32bpp)\n", + 1000000 / var->pixclock, DACSPEED32); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + xres = ALIGN(var->xres, 8); + vxres = ALIGN(var->xres_virtual, 16); + if (vxres < xres) + vxres = xres; + + xoffset = ALIGN(var->xoffset, 8); + if (xres + xoffset > vxres) + xoffset = vxres - xres; + + left = ALIGN(var->left_margin, 8); + right = ALIGN(var->right_margin, 8); + hslen = ALIGN(var->hsync_len, 8); + + yres = var->yres; + vyres = var->yres_virtual; + if (yres > vyres) + vyres = yres; + + yoffset = var->yoffset; + if (yres + yoffset > vyres) + yoffset = vyres - yres; + + lower = var->lower_margin; + vslen = var->vsync_len; + upper = var->upper_margin; + + mem = vxres * vyres * ((bpp + 1) / 8); + if (mem > info->screen_size) { + dev_err(info->device, "not enough video memory (%d KB requested, %ld KB available)\n", + mem >> 10, info->screen_size >> 10); + return -ENOMEM; + } + + if (yoffset + yres > vyres) + yoffset = vyres - yres; + + xtotal = xres + right + hslen + left; + ytotal = yres + lower + vslen + upper; + + par->crtc[VGA_CRTC_H_TOTAL] = (xtotal >> 3) - 5; + par->crtc[VGA_CRTC_H_DISP] = (xres >> 3) - 1; + par->crtc[VGA_CRTC_H_BLANK_START] = ((xres + right) >> 3) - 1; + par->crtc[VGA_CRTC_H_SYNC_START] = (xres + right) >> 3; + par->crtc[VGA_CRTC_H_SYNC_END] = (((xres + right + hslen) >> 3) & 0x1F) + | ((((xres + right + hslen) >> 3) & 0x20) << 2); + par->crtc[VGA_CRTC_H_BLANK_END] = ((xres + right + hslen) >> 3 & 0x1F) + | 0x80; + + par->crtc[VGA_CRTC_V_TOTAL] = ytotal - 2; + + r7 = 0x10; /* disable linecompare */ + if (ytotal & 0x100) + r7 |= 0x01; + if (ytotal & 0x200) + r7 |= 0x20; + + par->crtc[VGA_CRTC_PRESET_ROW] = 0; + par->crtc[VGA_CRTC_MAX_SCAN] = 0x40; /* 1 scanline, no linecmp */ + if (var->vmode & FB_VMODE_DOUBLE) + par->crtc[VGA_CRTC_MAX_SCAN] |= 0x80; + par->crtc[VGA_CRTC_CURSOR_START] = 0x00; + par->crtc[VGA_CRTC_CURSOR_END] = 0x00; + par->crtc[VGA_CRTC_CURSOR_HI] = 0x00; + par->crtc[VGA_CRTC_CURSOR_LO] = 0x00; + par->crtc[VGA_CRTC_V_DISP_END] = yres-1; + if ((yres-1) & 0x100) + r7 |= 0x02; + if ((yres-1) & 0x200) + r7 |= 0x40; + + par->crtc[VGA_CRTC_V_BLANK_START] = yres + lower - 1; + par->crtc[VGA_CRTC_V_SYNC_START] = yres + lower - 1; + if ((yres + lower - 1) & 0x100) + r7 |= 0x0C; + if ((yres + lower - 1) & 0x200) { + par->crtc[VGA_CRTC_MAX_SCAN] |= 0x20; + r7 |= 0x80; + } + + /* disabled IRQ */ + par->crtc[VGA_CRTC_V_SYNC_END] = + ((yres + lower - 1 + vslen) & 0x0F) & ~0x10; + /* 0x7F for VGA, but some SVGA chips require all 8 bits to be set */ + par->crtc[VGA_CRTC_V_BLANK_END] = (yres + lower - 1 + vslen) & 0xFF; + + par->crtc[VGA_CRTC_UNDERLINE] = 0x00; + par->crtc[VGA_CRTC_MODE] = 0xC3 ; + par->crtc[VGA_CRTC_LINE_COMPARE] = 0xFF; + par->crtc[VGA_CRTC_OVERFLOW] = r7; + + par->vss = 0x00; /* 3DA */ + + for (i = 0x00; i < 0x10; i++) + par->atc[i] = i; + par->atc[VGA_ATC_MODE] = 0x81; + par->atc[VGA_ATC_OVERSCAN] = 0x00; /* 0 for EGA, 0xFF for VGA */ + par->atc[VGA_ATC_PLANE_ENABLE] = 0x0F; + par->atc[VGA_ATC_COLOR_PAGE] = 0x00; + + par->misc = 0xC3; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + par->misc &= ~0x40; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + par->misc &= ~0x80; + + par->seq[VGA_SEQ_CLOCK_MODE] = 0x01; + par->seq[VGA_SEQ_PLANE_WRITE] = 0x0F; + par->seq[VGA_SEQ_CHARACTER_MAP] = 0x00; + par->seq[VGA_SEQ_MEMORY_MODE] = 0x06; + + par->gdc[VGA_GFX_SR_VALUE] = 0x00; + par->gdc[VGA_GFX_SR_ENABLE] = 0x00; + par->gdc[VGA_GFX_COMPARE_VALUE] = 0x00; + par->gdc[VGA_GFX_DATA_ROTATE] = 0x00; + par->gdc[VGA_GFX_PLANE_READ] = 0; + par->gdc[VGA_GFX_MODE] = 0x02; + par->gdc[VGA_GFX_MISC] = 0x05; + par->gdc[VGA_GFX_COMPARE_MASK] = 0x0F; + par->gdc[VGA_GFX_BIT_MASK] = 0xFF; + + base = (yoffset * vxres + (xoffset & ~7)) >> 2; + switch (bpp) { + case 8: + par->crtc[VGA_CRTC_OFFSET] = vxres >> 3; + par->ext_offset = vxres >> 11; + par->pixelpipe_cfg1 = DISPLAY_8BPP_MODE; + par->bitblt_cntl = COLEXP_8BPP; + break; + case 15: /* 0rrrrrgg gggbbbbb */ + case 16: /* rrrrrggg gggbbbbb */ + par->pixelpipe_cfg1 = (var->green.length == 6) ? + DISPLAY_16BPP_MODE : DISPLAY_15BPP_MODE; + par->crtc[VGA_CRTC_OFFSET] = vxres >> 2; + par->ext_offset = vxres >> 10; + par->bitblt_cntl = COLEXP_16BPP; + base *= 2; + break; + case 24: + par->crtc[VGA_CRTC_OFFSET] = (vxres * 3) >> 3; + par->ext_offset = (vxres * 3) >> 11; + par->pixelpipe_cfg1 = DISPLAY_24BPP_MODE; + par->bitblt_cntl = COLEXP_24BPP; + base &= 0xFFFFFFFE; /* ...ignore the last bit. */ + base *= 3; + break; + case 32: + par->crtc[VGA_CRTC_OFFSET] = vxres >> 1; + par->ext_offset = vxres >> 9; + par->pixelpipe_cfg1 = DISPLAY_32BPP_MODE; + par->bitblt_cntl = COLEXP_RESERVED; /* Unimplemented on i740 */ + base *= 4; + break; + } + + par->crtc[VGA_CRTC_START_LO] = base & 0x000000FF; + par->crtc[VGA_CRTC_START_HI] = (base & 0x0000FF00) >> 8; + par->ext_start_addr = + ((base & 0x003F0000) >> 16) | EXT_START_ADDR_ENABLE; + par->ext_start_addr_hi = (base & 0x3FC00000) >> 22; + + par->pixelpipe_cfg0 = DAC_8_BIT; + + par->pixelpipe_cfg2 = DISPLAY_GAMMA_ENABLE | OVERLAY_GAMMA_ENABLE; + par->io_cntl = EXTENDED_CRTC_CNTL; + par->address_mapping = LINEAR_MODE_ENABLE | PAGE_MAPPING_ENABLE; + par->display_cntl = HIRES_MODE; + + /* Set the MCLK freq */ + par->pll_cntl = PLL_MEMCLK_100000KHZ; /* 100 MHz -- use as default */ + + /* Calculate the extended CRTC regs */ + par->ext_vert_total = (ytotal - 2) >> 8; + par->ext_vert_disp_end = (yres - 1) >> 8; + par->ext_vert_sync_start = (yres + lower) >> 8; + par->ext_vert_blank_start = (yres + lower) >> 8; + par->ext_horiz_total = ((xtotal >> 3) - 5) >> 8; + par->ext_horiz_blank = (((xres + right) >> 3) & 0x40) >> 6; + + par->interlace_cntl = INTERLACE_DISABLE; + + /* Set the overscan color to 0. (NOTE: This only affects >8bpp mode) */ + par->atc[VGA_ATC_OVERSCAN] = 0; + + /* Calculate VCLK that most closely matches the requested dot clock */ + i740_calc_vclk((((u32)1e9) / var->pixclock) * (u32)(1e3), par); + + /* Since we program the clocks ourselves, always use VCLK2. */ + par->misc |= 0x0C; + + /* Calculate the FIFO Watermark and Burst Length. */ + par->lmi_fifo_watermark = + i740_calc_fifo(par, 1000000 / var->pixclock, bpp); + + return 0; +} + +static int i740fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + switch (var->bits_per_pixel) { + case 8: + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + break; + case 16: + switch (var->green.length) { + default: + case 5: + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case 6: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = var->blue.length = 5; + break; + } + break; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + break; + case 32: + var->transp.offset = 24; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->transp.length = 8; + var->red.length = var->green.length = var->blue.length = 8; + break; + default: + return -EINVAL; + } + + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + if (info->monspecs.hfmax && info->monspecs.vfmax && + info->monspecs.dclkmax && fb_validate_mode(var, info) < 0) + return -EINVAL; + + return 0; +} + +static void vga_protect(struct i740fb_par *par) +{ + /* disable the display */ + i740outreg_mask(par, VGA_SEQ_I, VGA_SEQ_CLOCK_MODE, 0x20, 0x20); + + i740inb(par, 0x3DA); + i740outb(par, VGA_ATT_W, 0x00); /* enable palette access */ +} + +static void vga_unprotect(struct i740fb_par *par) +{ + /* reenable display */ + i740outreg_mask(par, VGA_SEQ_I, VGA_SEQ_CLOCK_MODE, 0, 0x20); + + i740inb(par, 0x3DA); + i740outb(par, VGA_ATT_W, 0x20); /* disable palette access */ +} + +static int i740fb_set_par(struct fb_info *info) +{ + struct i740fb_par *par = info->par; + u32 itemp; + int i; + + i = i740fb_decode_var(&info->var, par, info); + if (i) + return i; + + memset(info->screen_base, 0, info->screen_size); + + vga_protect(par); + + i740outreg(par, XRX, DRAM_EXT_CNTL, DRAM_REFRESH_DISABLE); + + mdelay(1); + + i740outreg(par, XRX, VCLK2_VCO_M, par->video_clk2_m); + i740outreg(par, XRX, VCLK2_VCO_N, par->video_clk2_n); + i740outreg(par, XRX, VCLK2_VCO_MN_MSBS, par->video_clk2_mn_msbs); + i740outreg(par, XRX, VCLK2_VCO_DIV_SEL, par->video_clk2_div_sel); + + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_0, + par->pixelpipe_cfg0 & DAC_8_BIT, 0x80); + + i740inb(par, 0x3DA); + i740outb(par, 0x3C0, 0x00); + + /* update misc output register */ + i740outb(par, VGA_MIS_W, par->misc | 0x01); + + /* synchronous reset on */ + i740outreg(par, VGA_SEQ_I, VGA_SEQ_RESET, 0x01); + /* write sequencer registers */ + i740outreg(par, VGA_SEQ_I, VGA_SEQ_CLOCK_MODE, + par->seq[VGA_SEQ_CLOCK_MODE] | 0x20); + for (i = 2; i < VGA_SEQ_C; i++) + i740outreg(par, VGA_SEQ_I, i, par->seq[i]); + + /* synchronous reset off */ + i740outreg(par, VGA_SEQ_I, VGA_SEQ_RESET, 0x03); + + /* deprotect CRT registers 0-7 */ + i740outreg(par, VGA_CRT_IC, VGA_CRTC_V_SYNC_END, + par->crtc[VGA_CRTC_V_SYNC_END]); + + /* write CRT registers */ + for (i = 0; i < VGA_CRT_C; i++) + i740outreg(par, VGA_CRT_IC, i, par->crtc[i]); + + /* write graphics controller registers */ + for (i = 0; i < VGA_GFX_C; i++) + i740outreg(par, VGA_GFX_I, i, par->gdc[i]); + + /* write attribute controller registers */ + for (i = 0; i < VGA_ATT_C; i++) { + i740inb(par, VGA_IS1_RC); /* reset flip-flop */ + i740outb(par, VGA_ATT_IW, i); + i740outb(par, VGA_ATT_IW, par->atc[i]); + } + + i740inb(par, VGA_IS1_RC); + i740outb(par, VGA_ATT_IW, 0x20); + + i740outreg(par, VGA_CRT_IC, EXT_VERT_TOTAL, par->ext_vert_total); + i740outreg(par, VGA_CRT_IC, EXT_VERT_DISPLAY, par->ext_vert_disp_end); + i740outreg(par, VGA_CRT_IC, EXT_VERT_SYNC_START, + par->ext_vert_sync_start); + i740outreg(par, VGA_CRT_IC, EXT_VERT_BLANK_START, + par->ext_vert_blank_start); + i740outreg(par, VGA_CRT_IC, EXT_HORIZ_TOTAL, par->ext_horiz_total); + i740outreg(par, VGA_CRT_IC, EXT_HORIZ_BLANK, par->ext_horiz_blank); + i740outreg(par, VGA_CRT_IC, EXT_OFFSET, par->ext_offset); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR_HI, par->ext_start_addr_hi); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR, par->ext_start_addr); + + i740outreg_mask(par, VGA_CRT_IC, INTERLACE_CNTL, + par->interlace_cntl, INTERLACE_ENABLE); + i740outreg_mask(par, XRX, ADDRESS_MAPPING, par->address_mapping, 0x1F); + i740outreg_mask(par, XRX, BITBLT_CNTL, par->bitblt_cntl, COLEXP_MODE); + i740outreg_mask(par, XRX, DISPLAY_CNTL, + par->display_cntl, VGA_WRAP_MODE | GUI_MODE); + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_0, par->pixelpipe_cfg0, 0x9B); + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_2, par->pixelpipe_cfg2, 0x0C); + + i740outreg(par, XRX, PLL_CNTL, par->pll_cntl); + + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_1, + par->pixelpipe_cfg1, DISPLAY_COLOR_MODE); + + itemp = readl(par->regs + FWATER_BLC); + itemp &= ~(LMI_BURST_LENGTH | LMI_FIFO_WATERMARK); + itemp |= par->lmi_fifo_watermark; + writel(itemp, par->regs + FWATER_BLC); + + i740outreg(par, XRX, DRAM_EXT_CNTL, DRAM_REFRESH_60HZ); + + i740outreg_mask(par, MRX, COL_KEY_CNTL_1, 0, BLANK_DISP_OVERLAY); + i740outreg_mask(par, XRX, IO_CTNL, + par->io_cntl, EXTENDED_ATTR_CNTL | EXTENDED_CRTC_CNTL); + + if (par->pixelpipe_cfg1 != DISPLAY_8BPP_MODE) { + i740outb(par, VGA_PEL_MSK, 0xFF); + i740outb(par, VGA_PEL_IW, 0x00); + for (i = 0; i < 256; i++) { + itemp = (par->pixelpipe_cfg0 & DAC_8_BIT) ? i : i >> 2; + i740outb(par, VGA_PEL_D, itemp); + i740outb(par, VGA_PEL_D, itemp); + i740outb(par, VGA_PEL_D, itemp); + } + } + + /* Wait for screen to stabilize. */ + mdelay(50); + vga_unprotect(par); + + info->fix.line_length = + info->var.xres_virtual * info->var.bits_per_pixel / 8; + if (info->var.bits_per_pixel == 8) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + return 0; +} + +static int i740fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + u32 r, g, b; + + dev_dbg(info->device, "setcolreg: regno: %i, red=%d, green=%d, blue=%d, transp=%d, bpp=%d\n", + regno, red, green, blue, transp, info->var.bits_per_pixel); + + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + if (regno >= 256) + return -EINVAL; + i740outb(info->par, VGA_PEL_IW, regno); + i740outb(info->par, VGA_PEL_D, red >> 8); + i740outb(info->par, VGA_PEL_D, green >> 8); + i740outb(info->par, VGA_PEL_D, blue >> 8); + break; + case FB_VISUAL_TRUECOLOR: + if (regno >= 16) + return -EINVAL; + r = (red >> (16 - info->var.red.length)) + << info->var.red.offset; + b = (blue >> (16 - info->var.blue.length)) + << info->var.blue.offset; + g = (green >> (16 - info->var.green.length)) + << info->var.green.offset; + ((u32 *) info->pseudo_palette)[regno] = r | g | b; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int i740fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct i740fb_par *par = info->par; + u32 base = (var->yoffset * info->var.xres_virtual + + (var->xoffset & ~7)) >> 2; + + dev_dbg(info->device, "pan_display: xoffset: %i yoffset: %i base: %i\n", + var->xoffset, var->yoffset, base); + + switch (info->var.bits_per_pixel) { + case 8: + break; + case 15: + case 16: + base *= 2; + break; + case 24: + /* + * The last bit does not seem to have any effect on the start + * address register in 24bpp mode, so... + */ + base &= 0xFFFFFFFE; /* ...ignore the last bit. */ + base *= 3; + break; + case 32: + base *= 4; + break; + } + + par->crtc[VGA_CRTC_START_LO] = base & 0x000000FF; + par->crtc[VGA_CRTC_START_HI] = (base & 0x0000FF00) >> 8; + par->ext_start_addr_hi = (base & 0x3FC00000) >> 22; + par->ext_start_addr = + ((base & 0x003F0000) >> 16) | EXT_START_ADDR_ENABLE; + + i740outreg(par, VGA_CRT_IC, VGA_CRTC_START_LO, base & 0x000000FF); + i740outreg(par, VGA_CRT_IC, VGA_CRTC_START_HI, + (base & 0x0000FF00) >> 8); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR_HI, + (base & 0x3FC00000) >> 22); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR, + ((base & 0x003F0000) >> 16) | EXT_START_ADDR_ENABLE); + + return 0; +} + +static int i740fb_blank(int blank_mode, struct fb_info *info) +{ + struct i740fb_par *par = info->par; + + unsigned char SEQ01; + int DPMSSyncSelect; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + SEQ01 = 0x00; + DPMSSyncSelect = HSYNC_ON | VSYNC_ON; + break; + case FB_BLANK_VSYNC_SUSPEND: + SEQ01 = 0x20; + DPMSSyncSelect = HSYNC_ON | VSYNC_OFF; + break; + case FB_BLANK_HSYNC_SUSPEND: + SEQ01 = 0x20; + DPMSSyncSelect = HSYNC_OFF | VSYNC_ON; + break; + case FB_BLANK_POWERDOWN: + SEQ01 = 0x20; + DPMSSyncSelect = HSYNC_OFF | VSYNC_OFF; + break; + default: + return -EINVAL; + } + /* Turn the screen on/off */ + i740outb(par, SRX, 0x01); + SEQ01 |= i740inb(par, SRX + 1) & ~0x20; + i740outb(par, SRX, 0x01); + i740outb(par, SRX + 1, SEQ01); + + /* Set the DPMS mode */ + i740outreg(par, XRX, DPMS_SYNC_SELECT, DPMSSyncSelect); + + /* Let fbcon do a soft blank for us */ + return (blank_mode == FB_BLANK_NORMAL) ? 1 : 0; +} + +static struct fb_ops i740fb_ops = { + .owner = THIS_MODULE, + .fb_open = i740fb_open, + .fb_release = i740fb_release, + .fb_check_var = i740fb_check_var, + .fb_set_par = i740fb_set_par, + .fb_setcolreg = i740fb_setcolreg, + .fb_blank = i740fb_blank, + .fb_pan_display = i740fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* ------------------------------------------------------------------------- */ + +static int i740fb_probe(struct pci_dev *dev, const struct pci_device_id *ent) +{ + struct fb_info *info; + struct i740fb_par *par; + int ret, tmp; + bool found = false; + u8 *edid; + + info = framebuffer_alloc(sizeof(struct i740fb_par), &(dev->dev)); + if (!info) { + dev_err(&(dev->dev), "cannot allocate framebuffer\n"); + return -ENOMEM; + } + + par = info->par; + mutex_init(&par->open_lock); + + info->var.activate = FB_ACTIVATE_NOW; + info->var.bits_per_pixel = 8; + info->fbops = &i740fb_ops; + info->pseudo_palette = par->pseudo_palette; + + ret = pci_enable_device(dev); + if (ret) { + dev_err(info->device, "cannot enable PCI device\n"); + goto err_enable_device; + } + + ret = pci_request_regions(dev, info->fix.id); + if (ret) { + dev_err(info->device, "error requesting regions\n"); + goto err_request_regions; + } + + info->screen_base = pci_ioremap_bar(dev, 0); + if (!info->screen_base) { + dev_err(info->device, "error remapping base\n"); + ret = -ENOMEM; + goto err_ioremap_1; + } + + par->regs = pci_ioremap_bar(dev, 1); + if (!par->regs) { + dev_err(info->device, "error remapping MMIO\n"); + ret = -ENOMEM; + goto err_ioremap_2; + } + + /* detect memory size */ + if ((i740inreg(par, XRX, DRAM_ROW_TYPE) & DRAM_ROW_1) + == DRAM_ROW_1_SDRAM) + i740outb(par, XRX, DRAM_ROW_BNDRY_1); + else + i740outb(par, XRX, DRAM_ROW_BNDRY_0); + info->screen_size = i740inb(par, XRX + 1) * 1024 * 1024; + /* detect memory type */ + tmp = i740inreg(par, XRX, DRAM_ROW_CNTL_LO); + par->has_sgram = !((tmp & DRAM_RAS_TIMING) || + (tmp & DRAM_RAS_PRECHARGE)); + + fb_info(info, "Intel740 on %s, %ld KB %s\n", + pci_name(dev), info->screen_size >> 10, + par->has_sgram ? "SGRAM" : "SDRAM"); + + info->fix = i740fb_fix; + info->fix.mmio_start = pci_resource_start(dev, 1); + info->fix.mmio_len = pci_resource_len(dev, 1); + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = info->screen_size; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + if (i740fb_setup_ddc_bus(info) == 0) { + par->ddc_registered = true; + edid = fb_ddc_read(&par->ddc_adapter); + if (edid) { + fb_edid_to_monspecs(edid, &info->monspecs); + kfree(edid); + if (!info->monspecs.modedb) + dev_err(info->device, + "error getting mode database\n"); + else { + const struct fb_videomode *m; + + fb_videomode_to_modelist( + info->monspecs.modedb, + info->monspecs.modedb_len, + &info->modelist); + m = fb_find_best_display(&info->monspecs, + &info->modelist); + if (m) { + fb_videomode_to_var(&info->var, m); + /* fill all other info->var's fields */ + if (!i740fb_check_var(&info->var, info)) + found = true; + } + } + } + } + + if (!mode_option && !found) + mode_option = "640x480-8@60"; + + if (mode_option) { + ret = fb_find_mode(&info->var, info, mode_option, + info->monspecs.modedb, + info->monspecs.modedb_len, + NULL, info->var.bits_per_pixel); + if (!ret || ret == 4) { + dev_err(info->device, "mode %s not found\n", + mode_option); + ret = -EINVAL; + } + } + + fb_destroy_modedb(info->monspecs.modedb); + info->monspecs.modedb = NULL; + + /* maximize virtual vertical size for fast scrolling */ + info->var.yres_virtual = info->fix.smem_len * 8 / + (info->var.bits_per_pixel * info->var.xres_virtual); + + if (ret == -EINVAL) + goto err_find_mode; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) { + dev_err(info->device, "cannot allocate colormap\n"); + goto err_alloc_cmap; + } + + ret = register_framebuffer(info); + if (ret) { + dev_err(info->device, "error registering framebuffer\n"); + goto err_reg_framebuffer; + } + + fb_info(info, "%s frame buffer device\n", info->fix.id); + pci_set_drvdata(dev, info); +#ifdef CONFIG_MTRR + if (mtrr) { + par->mtrr_reg = -1; + par->mtrr_reg = mtrr_add(info->fix.smem_start, + info->fix.smem_len, MTRR_TYPE_WRCOMB, 1); + } +#endif + return 0; + +err_reg_framebuffer: + fb_dealloc_cmap(&info->cmap); +err_alloc_cmap: +err_find_mode: + if (par->ddc_registered) + i2c_del_adapter(&par->ddc_adapter); + pci_iounmap(dev, par->regs); +err_ioremap_2: + pci_iounmap(dev, info->screen_base); +err_ioremap_1: + pci_release_regions(dev); +err_request_regions: +/* pci_disable_device(dev); */ +err_enable_device: + framebuffer_release(info); + return ret; +} + +static void i740fb_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + + if (info) { + struct i740fb_par *par = info->par; + +#ifdef CONFIG_MTRR + if (par->mtrr_reg >= 0) { + mtrr_del(par->mtrr_reg, 0, 0); + par->mtrr_reg = -1; + } +#endif + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + if (par->ddc_registered) + i2c_del_adapter(&par->ddc_adapter); + pci_iounmap(dev, par->regs); + pci_iounmap(dev, info->screen_base); + pci_release_regions(dev); +/* pci_disable_device(dev); */ + framebuffer_release(info); + } +} + +#ifdef CONFIG_PM +static int i740fb_suspend(struct pci_dev *dev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct i740fb_par *par = info->par; + + /* don't disable console during hibernation and wakeup from it */ + if (state.event == PM_EVENT_FREEZE || state.event == PM_EVENT_PRETHAW) + return 0; + + console_lock(); + mutex_lock(&(par->open_lock)); + + /* do nothing if framebuffer is not active */ + if (par->ref_count == 0) { + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; + } + + fb_set_suspend(info, 1); + + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, state)); + + mutex_unlock(&(par->open_lock)); + console_unlock(); + + return 0; +} + +static int i740fb_resume(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct i740fb_par *par = info->par; + + console_lock(); + mutex_lock(&(par->open_lock)); + + if (par->ref_count == 0) + goto fail; + + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + if (pci_enable_device(dev)) + goto fail; + + i740fb_set_par(info); + fb_set_suspend(info, 0); + +fail: + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; +} +#else +#define i740fb_suspend NULL +#define i740fb_resume NULL +#endif /* CONFIG_PM */ + +#define I740_ID_PCI 0x00d1 +#define I740_ID_AGP 0x7800 + +static DEFINE_PCI_DEVICE_TABLE(i740fb_id_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, I740_ID_PCI) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, I740_ID_AGP) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, i740fb_id_table); + +static struct pci_driver i740fb_driver = { + .name = "i740fb", + .id_table = i740fb_id_table, + .probe = i740fb_probe, + .remove = i740fb_remove, + .suspend = i740fb_suspend, + .resume = i740fb_resume, +}; + +#ifndef MODULE +static int __init i740fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; +#ifdef CONFIG_MTRR + else if (!strncmp(opt, "mtrr:", 5)) + mtrr = simple_strtoul(opt + 5, NULL, 0); +#endif + else + mode_option = opt; + } + + return 0; +} +#endif + +static int __init i740fb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("i740fb", &option)) + return -ENODEV; + i740fb_setup(option); +#endif + + return pci_register_driver(&i740fb_driver); +} + +static void __exit i740fb_exit(void) +{ + pci_unregister_driver(&i740fb_driver); +} + +module_init(i740fb_init); +module_exit(i740fb_exit); + +MODULE_AUTHOR("(c) 2011 Ondrej Zary <linux@rainbow-software.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("fbdev driver for Intel740"); + +module_param(mode_option, charp, 0444); +MODULE_PARM_DESC(mode_option, "Default video mode ('640x480-8@60', etc)"); + +#ifdef CONFIG_MTRR +module_param(mtrr, int, 0444); +MODULE_PARM_DESC(mtrr, "Enable write-combining with MTRR (1=enable, 0=disable, default=1)"); +#endif diff --git a/drivers/video/fbdev/i810/Makefile b/drivers/video/fbdev/i810/Makefile new file mode 100644 index 000000000000..96e08c8ded97 --- /dev/null +++ b/drivers/video/fbdev/i810/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for the Intel 810/815 framebuffer driver +# + +obj-$(CONFIG_FB_I810) += i810fb.o + +i810fb-objs := i810_main.o i810_accel.o + +ifdef CONFIG_FB_I810_GTF +i810fb-objs += i810_gtf.o +else +i810fb-objs += i810_dvt.o +endif + +ifdef CONFIG_FB_I810_I2C +i810fb-objs += i810-i2c.o +endif diff --git a/drivers/video/fbdev/i810/i810-i2c.c b/drivers/video/fbdev/i810/i810-i2c.c new file mode 100644 index 000000000000..7db17d0d8a8c --- /dev/null +++ b/drivers/video/fbdev/i810/i810-i2c.c @@ -0,0 +1,175 @@ + /*-*- linux-c -*- + * linux/drivers/video/i810-i2c.c -- Intel 810/815 I2C support + * + * Copyright (C) 2004 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * 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/delay.h> +#include <linux/gfp.h> +#include <linux/pci.h> +#include <linux/fb.h> +#include "i810.h" +#include "i810_regs.h" +#include "i810_main.h" +#include "../edid.h" + +/* bit locations in the registers */ +#define SCL_DIR_MASK 0x0001 +#define SCL_DIR 0x0002 +#define SCL_VAL_MASK 0x0004 +#define SCL_VAL_OUT 0x0008 +#define SCL_VAL_IN 0x0010 +#define SDA_DIR_MASK 0x0100 +#define SDA_DIR 0x0200 +#define SDA_VAL_MASK 0x0400 +#define SDA_VAL_OUT 0x0800 +#define SDA_VAL_IN 0x1000 + +#define DEBUG /* define this for verbose EDID parsing output */ + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(fmt,## args) +#else +#define DPRINTK(fmt, args...) +#endif + +static void i810i2c_setscl(void *data, int state) +{ + struct i810fb_i2c_chan *chan = data; + struct i810fb_par *par = chan->par; + u8 __iomem *mmio = par->mmio_start_virtual; + + if (state) + i810_writel(mmio, chan->ddc_base, SCL_DIR_MASK | SCL_VAL_MASK); + else + i810_writel(mmio, chan->ddc_base, SCL_DIR | SCL_DIR_MASK | SCL_VAL_MASK); + i810_readl(mmio, chan->ddc_base); /* flush posted write */ +} + +static void i810i2c_setsda(void *data, int state) +{ + struct i810fb_i2c_chan *chan = data; + struct i810fb_par *par = chan->par; + u8 __iomem *mmio = par->mmio_start_virtual; + + if (state) + i810_writel(mmio, chan->ddc_base, SDA_DIR_MASK | SDA_VAL_MASK); + else + i810_writel(mmio, chan->ddc_base, SDA_DIR | SDA_DIR_MASK | SDA_VAL_MASK); + i810_readl(mmio, chan->ddc_base); /* flush posted write */ +} + +static int i810i2c_getscl(void *data) +{ + struct i810fb_i2c_chan *chan = data; + struct i810fb_par *par = chan->par; + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_writel(mmio, chan->ddc_base, SCL_DIR_MASK); + i810_writel(mmio, chan->ddc_base, 0); + return ((i810_readl(mmio, chan->ddc_base) & SCL_VAL_IN) != 0); +} + +static int i810i2c_getsda(void *data) +{ + struct i810fb_i2c_chan *chan = data; + struct i810fb_par *par = chan->par; + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_writel(mmio, chan->ddc_base, SDA_DIR_MASK); + i810_writel(mmio, chan->ddc_base, 0); + return ((i810_readl(mmio, chan->ddc_base) & SDA_VAL_IN) != 0); +} + +static int i810_setup_i2c_bus(struct i810fb_i2c_chan *chan, const char *name) +{ + int rc; + + strcpy(chan->adapter.name, name); + chan->adapter.owner = THIS_MODULE; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = &chan->par->dev->dev; + chan->algo.setsda = i810i2c_setsda; + chan->algo.setscl = i810i2c_setscl; + chan->algo.getsda = i810i2c_getsda; + chan->algo.getscl = i810i2c_getscl; + chan->algo.udelay = 10; + chan->algo.timeout = (HZ/2); + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + /* Raise SCL and SDA */ + chan->algo.setsda(chan, 1); + chan->algo.setscl(chan, 1); + udelay(20); + + rc = i2c_bit_add_bus(&chan->adapter); + + if (rc == 0) + dev_dbg(&chan->par->dev->dev, "I2C bus %s registered.\n",name); + else { + dev_warn(&chan->par->dev->dev, "Failed to register I2C bus " + "%s.\n", name); + chan->par = NULL; + } + + return rc; +} + +void i810_create_i2c_busses(struct i810fb_par *par) +{ + par->chan[0].par = par; + par->chan[1].par = par; + par->chan[2].par = par; + + par->chan[0].ddc_base = GPIOA; + i810_setup_i2c_bus(&par->chan[0], "I810-DDC"); + par->chan[1].ddc_base = GPIOB; + i810_setup_i2c_bus(&par->chan[1], "I810-I2C"); + par->chan[2].ddc_base = GPIOC; + i810_setup_i2c_bus(&par->chan[2], "I810-GPIOC"); +} + +void i810_delete_i2c_busses(struct i810fb_par *par) +{ + if (par->chan[0].par) + i2c_del_adapter(&par->chan[0].adapter); + par->chan[0].par = NULL; + + if (par->chan[1].par) + i2c_del_adapter(&par->chan[1].adapter); + par->chan[1].par = NULL; + + if (par->chan[2].par) + i2c_del_adapter(&par->chan[2].adapter); + par->chan[2].par = NULL; +} + +int i810_probe_i2c_connector(struct fb_info *info, u8 **out_edid, int conn) +{ + struct i810fb_par *par = info->par; + u8 *edid = NULL; + + DPRINTK("i810-i2c: Probe DDC%i Bus\n", conn+1); + if (conn < par->ddc_num) { + edid = fb_ddc_read(&par->chan[conn].adapter); + } else { + const u8 *e = fb_firmware_edid(info->device); + + if (e != NULL) { + DPRINTK("i810-i2c: Getting EDID from BIOS\n"); + edid = kmemdup(e, EDID_LENGTH, GFP_KERNEL); + } + } + + *out_edid = edid; + + return (edid) ? 0 : 1; +} diff --git a/drivers/video/fbdev/i810/i810.h b/drivers/video/fbdev/i810/i810.h new file mode 100644 index 000000000000..1414b73ac55b --- /dev/null +++ b/drivers/video/fbdev/i810/i810.h @@ -0,0 +1,299 @@ +/*-*- linux-c -*- + * linux/drivers/video/i810.h -- Intel 810 General Definitions/Declarations + * + * Copyright (C) 2001 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * + * 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. + */ + +#ifndef __I810_H__ +#define __I810_H__ + +#include <linux/list.h> +#include <linux/agp_backend.h> +#include <linux/fb.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <video/vga.h> + +/* Fence */ +#define TILEWALK_X (0 << 12) +#define TILEWALK_Y (1 << 12) + +/* Raster ops */ +#define COLOR_COPY_ROP 0xF0 +#define PAT_COPY_ROP 0xCC +#define CLEAR_ROP 0x00 +#define WHITE_ROP 0xFF +#define INVERT_ROP 0x55 +#define XOR_ROP 0x5A + +/* 2D Engine definitions */ +#define SOLIDPATTERN 0x80000000 +#define NONSOLID 0x00000000 +#define BPP8 (0 << 24) +#define BPP16 (1 << 24) +#define BPP24 (2 << 24) + +#define PIXCONF8 (2 << 16) +#define PIXCONF15 (4 << 16) +#define PIXCONF16 (5 << 16) +#define PIXCONF24 (6 << 16) +#define PIXCONF32 (7 << 16) + +#define DYN_COLOR_EN (1 << 26) +#define DYN_COLOR_DIS (0 << 26) +#define INCREMENT 0x00000000 +#define DECREMENT (0x01 << 30) +#define ARB_ON 0x00000001 +#define ARB_OFF 0x00000000 +#define SYNC_FLIP 0x00000000 +#define ASYNC_FLIP 0x00000040 +#define OPTYPE_MASK 0xE0000000 +#define PARSER_MASK 0x001F8000 +#define D2_MASK 0x001FC000 /* 2D mask */ + +/* Instruction type */ +/* There are more but pertains to 3D */ +#define PARSER 0x00000000 +#define BLIT (0x02 << 29) +#define RENDER (0x03 << 29) + +/* Parser */ +#define NOP 0x00 /* No operation, padding */ +#define BP_INT (0x01 << 23) /* Breakpoint interrupt */ +#define USR_INT (0x02 << 23) /* User interrupt */ +#define WAIT_FOR_EVNT (0x03 << 23) /* Wait for event */ +#define FLUSH (0x04 << 23) +#define CONTEXT_SEL (0x05 << 23) +#define REPORT_HEAD (0x07 << 23) +#define ARB_ON_OFF (0x08 << 23) +#define OVERLAY_FLIP (0x11 << 23) +#define LOAD_SCAN_INC (0x12 << 23) +#define LOAD_SCAN_EX (0x13 << 23) +#define FRONT_BUFFER (0x14 << 23) +#define DEST_BUFFER (0x15 << 23) +#define Z_BUFFER (0x16 << 23) + +#define STORE_DWORD_IMM (0x20 << 23) +#define STORE_DWORD_IDX (0x21 << 23) +#define BATCH_BUFFER (0x30 << 23) + +/* Blit */ +#define SETUP_BLIT 0x00 +#define SETUP_MONO_PATTERN_SL_BLT (0x10 << 22) +#define PIXEL_BLT (0x20 << 22) +#define SCANLINE_BLT (0x21 << 22) +#define TEXT_BLT (0x22 << 22) +#define TEXT_IMM_BLT (0x30 << 22) +#define COLOR_BLT (0x40 << 22) +#define MONO_PAT_BLIT (0x42 << 22) +#define SOURCE_COPY_BLIT (0x43 << 22) +#define MONO_SOURCE_COPY_BLIT (0x44 << 22) +#define SOURCE_COPY_IMMEDIATE (0x60 << 22) +#define MONO_SOURCE_COPY_IMMEDIATE (0x61 << 22) + +#define VERSION_MAJOR 0 +#define VERSION_MINOR 9 +#define VERSION_TEENIE 0 +#define BRANCH_VERSION "" + + +/* mvo: intel i815 */ +#ifndef PCI_DEVICE_ID_INTEL_82815_100 + #define PCI_DEVICE_ID_INTEL_82815_100 0x1102 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82815_NOAGP + #define PCI_DEVICE_ID_INTEL_82815_NOAGP 0x1112 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82815_FULL_CTRL + #define PCI_DEVICE_ID_INTEL_82815_FULL_CTRL 0x1130 +#endif + +/* General Defines */ +#define I810_PAGESIZE 4096 +#define MAX_DMA_SIZE (1024 * 4096) +#define SAREA_SIZE 4096 +#define PCI_I810_MISCC 0x72 +#define MMIO_SIZE (512*1024) +#define GTT_SIZE (16*1024) +#define RINGBUFFER_SIZE (64*1024) +#define CURSOR_SIZE 4096 +#define OFF 0 +#define ON 1 +#define MAX_KEY 256 +#define WAIT_COUNT 10000000 +#define IRING_PAD 8 +#define FONTDATAMAX 8192 +/* Masks (AND ops) and OR's */ +#define FB_START_MASK (0x3f << (32 - 6)) +#define MMIO_ADDR_MASK (0x1FFF << (32 - 13)) +#define FREQ_MASK (1 << 4) +#define SCR_OFF 0x20 +#define DRAM_ON 0x08 +#define DRAM_OFF 0xE7 +#define PG_ENABLE_MASK 0x01 +#define RING_SIZE_MASK (RINGBUFFER_SIZE - 1) + +/* defines for restoring registers partially */ +#define ADDR_MAP_MASK (0x07 << 5) +#define DISP_CTRL ~0 +#define PIXCONF_0 (0x64 << 8) +#define PIXCONF_2 (0xF3 << 24) +#define PIXCONF_1 (0xF0 << 16) +#define MN_MASK 0x3FF03FF +#define P_OR (0x7 << 4) +#define DAC_BIT (1 << 16) +#define INTERLACE_BIT (1 << 7) +#define IER_MASK (3 << 13) +#define IMR_MASK (3 << 13) + +/* Power Management */ +#define DPMS_MASK 0xF0000 +#define POWERON 0x00000 +#define STANDBY 0x20000 +#define SUSPEND 0x80000 +#define POWERDOWN 0xA0000 +#define EMR_MASK ~0x3F +#define FW_BLC_MASK ~(0x3F|(7 << 8)|(0x3F << 12)|(7 << 20)) + +/* Ringbuffer */ +#define RBUFFER_START_MASK 0xFFFFF000 +#define RBUFFER_SIZE_MASK 0x001FF000 +#define RBUFFER_HEAD_MASK 0x001FFFFC +#define RBUFFER_TAIL_MASK 0x001FFFF8 + +/* Video Timings */ +#define REF_FREQ 24000000 +#define TARGET_N_MAX 30 + +#define MAX_PIXELCLOCK 230000000 +#define MIN_PIXELCLOCK 15000000 +#define VFMAX 60 +#define VFMIN 60 +#define HFMAX 30000 +#define HFMIN 29000 + +/* Cursor */ +#define CURSOR_ENABLE_MASK 0x1000 +#define CURSOR_MODE_64_TRANS 4 +#define CURSOR_MODE_64_XOR 5 +#define CURSOR_MODE_64_3C 6 +#define COORD_INACTIVE 0 +#define COORD_ACTIVE (1 << 4) +#define EXTENDED_PALETTE 1 + +/* AGP Memory Types*/ +#define AGP_NORMAL_MEMORY 0 +#define AGP_DCACHE_MEMORY 1 +#define AGP_PHYSICAL_MEMORY 2 + +/* Allocated resource Flags */ +#define FRAMEBUFFER_REQ 1 +#define MMIO_REQ 2 +#define PCI_DEVICE_ENABLED 4 +#define HAS_FONTCACHE 8 + +/* driver flags */ +#define HAS_MTRR 1 +#define HAS_ACCELERATION 2 +#define ALWAYS_SYNC 4 +#define LOCKUP 8 + +struct gtt_data { + struct agp_memory *i810_fb_memory; + struct agp_memory *i810_cursor_memory; +}; + +struct mode_registers { + u32 pixclock, M, N, P; + u8 cr00, cr01, cr02, cr03; + u8 cr04, cr05, cr06, cr07; + u8 cr09, cr10, cr11, cr12; + u8 cr13, cr15, cr16, cr30; + u8 cr31, cr32, cr33, cr35, cr39; + u32 bpp8_100, bpp16_100; + u32 bpp24_100, bpp8_133; + u32 bpp16_133, bpp24_133; + u8 msr; +}; + +struct heap_data { + unsigned long physical; + __u8 __iomem *virtual; + u32 offset; + u32 size; +}; + +struct state_registers { + u32 dclk_1d, dclk_2d, dclk_0ds; + u32 pixconf, fw_blc, pgtbl_ctl; + u32 fence0, hws_pga, dplystas; + u16 bltcntl, hwstam, ier, iir, imr; + u8 cr00, cr01, cr02, cr03, cr04; + u8 cr05, cr06, cr07, cr08, cr09; + u8 cr10, cr11, cr12, cr13, cr14; + u8 cr15, cr16, cr17, cr80, gr10; + u8 cr30, cr31, cr32, cr33, cr35; + u8 cr39, cr41, cr70, sr01, msr; +}; + +struct i810fb_par; + +struct i810fb_i2c_chan { + struct i810fb_par *par; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo; + unsigned long ddc_base; +}; + +struct i810fb_par { + struct mode_registers regs; + struct state_registers hw_state; + struct gtt_data i810_gtt; + struct fb_ops i810fb_ops; + struct pci_dev *dev; + struct heap_data aperture; + struct heap_data fb; + struct heap_data iring; + struct heap_data cursor_heap; + struct vgastate state; + struct i810fb_i2c_chan chan[3]; + struct mutex open_lock; + unsigned int use_count; + u32 pseudo_palette[16]; + unsigned long mmio_start_phys; + u8 __iomem *mmio_start_virtual; + u8 *edid; + u32 pitch; + u32 pixconf; + u32 watermark; + u32 mem_freq; + u32 res_flags; + u32 dev_flags; + u32 cur_tail; + u32 depth; + u32 blit_bpp; + u32 ovract; + u32 cur_state; + u32 ddc_num; + int mtrr_reg; + u16 bltcntl; + u8 interlace; +}; + +/* + * Register I/O + */ +#define i810_readb(where, mmio) readb(mmio + where) +#define i810_readw(where, mmio) readw(mmio + where) +#define i810_readl(where, mmio) readl(mmio + where) +#define i810_writeb(where, mmio, val) writeb(val, mmio + where) +#define i810_writew(where, mmio, val) writew(val, mmio + where) +#define i810_writel(where, mmio, val) writel(val, mmio + where) + +#endif /* __I810_H__ */ diff --git a/drivers/video/fbdev/i810/i810_accel.c b/drivers/video/fbdev/i810/i810_accel.c new file mode 100644 index 000000000000..7672d2ea9b35 --- /dev/null +++ b/drivers/video/fbdev/i810/i810_accel.c @@ -0,0 +1,456 @@ +/*-*- linux-c -*- + * linux/drivers/video/i810_accel.c -- Hardware Acceleration + * + * Copyright (C) 2001 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * 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/kernel.h> +#include <linux/string.h> +#include <linux/fb.h> + +#include "i810_regs.h" +#include "i810.h" +#include "i810_main.h" + +static u32 i810fb_rop[] = { + COLOR_COPY_ROP, /* ROP_COPY */ + XOR_ROP /* ROP_XOR */ +}; + +/* Macros */ +#define PUT_RING(n) { \ + i810_writel(par->cur_tail, par->iring.virtual, n); \ + par->cur_tail += 4; \ + par->cur_tail &= RING_SIZE_MASK; \ +} + +extern void flush_cache(void); + +/************************************************************/ + +/* BLT Engine Routines */ +static inline void i810_report_error(u8 __iomem *mmio) +{ + printk("IIR : 0x%04x\n" + "EIR : 0x%04x\n" + "PGTBL_ER: 0x%04x\n" + "IPEIR : 0x%04x\n" + "IPEHR : 0x%04x\n", + i810_readw(IIR, mmio), + i810_readb(EIR, mmio), + i810_readl(PGTBL_ER, mmio), + i810_readl(IPEIR, mmio), + i810_readl(IPEHR, mmio)); +} + +/** + * wait_for_space - check ring buffer free space + * @space: amount of ringbuffer space needed in bytes + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * The function waits until a free space from the ringbuffer + * is available + */ +static inline int wait_for_space(struct fb_info *info, u32 space) +{ + struct i810fb_par *par = info->par; + u32 head, count = WAIT_COUNT, tail; + u8 __iomem *mmio = par->mmio_start_virtual; + + tail = par->cur_tail; + while (count--) { + head = i810_readl(IRING + 4, mmio) & RBUFFER_HEAD_MASK; + if ((tail == head) || + (tail > head && + (par->iring.size - tail + head) >= space) || + (tail < head && (head - tail) >= space)) { + return 0; + } + } + printk("ringbuffer lockup!!!\n"); + i810_report_error(mmio); + par->dev_flags |= LOCKUP; + info->pixmap.scan_align = 1; + return 1; +} + +/** + * wait_for_engine_idle - waits for all hardware engines to finish + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * This waits for lring(0), iring(1), and batch(3), etc to finish and + * waits until ringbuffer is empty. + */ +static inline int wait_for_engine_idle(struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u8 __iomem *mmio = par->mmio_start_virtual; + int count = WAIT_COUNT; + + if (wait_for_space(info, par->iring.size)) /* flush */ + return 1; + + while((i810_readw(INSTDONE, mmio) & 0x7B) != 0x7B && --count); + if (count) return 0; + + printk("accel engine lockup!!!\n"); + printk("INSTDONE: 0x%04x\n", i810_readl(INSTDONE, mmio)); + i810_report_error(mmio); + par->dev_flags |= LOCKUP; + info->pixmap.scan_align = 1; + return 1; +} + +/* begin_iring - prepares the ringbuffer + * @space: length of sequence in dwords + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Checks/waits for sufficient space in ringbuffer of size + * space. Returns the tail of the buffer + */ +static inline u32 begin_iring(struct fb_info *info, u32 space) +{ + struct i810fb_par *par = info->par; + + if (par->dev_flags & ALWAYS_SYNC) + wait_for_engine_idle(info); + return wait_for_space(info, space); +} + +/** + * end_iring - advances the buffer + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * This advances the tail of the ringbuffer, effectively + * beginning the execution of the graphics instruction sequence. + */ +static inline void end_iring(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_writel(IRING, mmio, par->cur_tail); +} + +/** + * source_copy_blit - BLIT transfer operation + * @dwidth: width of rectangular graphics data + * @dheight: height of rectangular graphics data + * @dpitch: bytes per line of destination buffer + * @xdir: direction of copy (left to right or right to left) + * @src: address of first pixel to read from + * @dest: address of first pixel to write to + * @from: source address + * @where: destination address + * @rop: raster operation + * @blit_bpp: pixel format which can be different from the + * framebuffer's pixelformat + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * This is a BLIT operation typically used when doing + * a 'Copy and Paste' + */ +static inline void source_copy_blit(int dwidth, int dheight, int dpitch, + int xdir, int src, int dest, int rop, + int blit_bpp, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + + if (begin_iring(info, 24 + IRING_PAD)) return; + + PUT_RING(BLIT | SOURCE_COPY_BLIT | 4); + PUT_RING(xdir | rop << 16 | dpitch | DYN_COLOR_EN | blit_bpp); + PUT_RING(dheight << 16 | dwidth); + PUT_RING(dest); + PUT_RING(dpitch); + PUT_RING(src); + + end_iring(par); +} + +/** + * color_blit - solid color BLIT operation + * @width: width of destination + * @height: height of destination + * @pitch: pixels per line of the buffer + * @dest: address of first pixel to write to + * @where: destination + * @rop: raster operation + * @what: color to transfer + * @blit_bpp: pixel format which can be different from the + * framebuffer's pixelformat + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * A BLIT operation which can be used for color fill/rectangular fill + */ +static inline void color_blit(int width, int height, int pitch, int dest, + int rop, int what, int blit_bpp, + struct fb_info *info) +{ + struct i810fb_par *par = info->par; + + if (begin_iring(info, 24 + IRING_PAD)) return; + + PUT_RING(BLIT | COLOR_BLT | 3); + PUT_RING(rop << 16 | pitch | SOLIDPATTERN | DYN_COLOR_EN | blit_bpp); + PUT_RING(height << 16 | width); + PUT_RING(dest); + PUT_RING(what); + PUT_RING(NOP); + + end_iring(par); +} + +/** + * mono_src_copy_imm_blit - color expand from system memory to framebuffer + * @dwidth: width of destination + * @dheight: height of destination + * @dpitch: pixels per line of the buffer + * @dsize: size of bitmap in double words + * @dest: address of first byte of pixel; + * @rop: raster operation + * @blit_bpp: pixelformat to use which can be different from the + * framebuffer's pixelformat + * @src: address of image data + * @bg: backgound color + * @fg: forground color + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * A color expand operation where the source data is placed in the + * ringbuffer itself. Useful for drawing text. + * + * REQUIREMENT: + * The end of a scanline must be padded to the next word. + */ +static inline void mono_src_copy_imm_blit(int dwidth, int dheight, int dpitch, + int dsize, int blit_bpp, int rop, + int dest, const u32 *src, int bg, + int fg, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + + if (begin_iring(info, 24 + (dsize << 2) + IRING_PAD)) return; + + PUT_RING(BLIT | MONO_SOURCE_COPY_IMMEDIATE | (4 + dsize)); + PUT_RING(DYN_COLOR_EN | blit_bpp | rop << 16 | dpitch); + PUT_RING(dheight << 16 | dwidth); + PUT_RING(dest); + PUT_RING(bg); + PUT_RING(fg); + while (dsize--) + PUT_RING(*src++); + + end_iring(par); +} + +static inline void load_front(int offset, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + + if (begin_iring(info, 8 + IRING_PAD)) return; + + PUT_RING(PARSER | FLUSH); + PUT_RING(NOP); + + end_iring(par); + + if (begin_iring(info, 8 + IRING_PAD)) return; + + PUT_RING(PARSER | FRONT_BUFFER | ((par->pitch >> 3) << 8)); + PUT_RING((par->fb.offset << 12) + offset); + + end_iring(par); +} + +/** + * i810fb_iring_enable - enables/disables the ringbuffer + * @mode: enable or disable + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Enables or disables the ringbuffer, effectively enabling or + * disabling the instruction/acceleration engine. + */ +static inline void i810fb_iring_enable(struct i810fb_par *par, u32 mode) +{ + u32 tmp; + u8 __iomem *mmio = par->mmio_start_virtual; + + tmp = i810_readl(IRING + 12, mmio); + if (mode == OFF) + tmp &= ~1; + else + tmp |= 1; + flush_cache(); + i810_writel(IRING + 12, mmio, tmp); +} + +void i810fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct i810fb_par *par = info->par; + u32 dx, dy, width, height, dest, rop = 0, color = 0; + + if (!info->var.accel_flags || par->dev_flags & LOCKUP || + par->depth == 4) { + cfb_fillrect(info, rect); + return; + } + + if (par->depth == 1) + color = rect->color; + else + color = ((u32 *) (info->pseudo_palette))[rect->color]; + + rop = i810fb_rop[rect->rop]; + + dx = rect->dx * par->depth; + width = rect->width * par->depth; + dy = rect->dy; + height = rect->height; + + dest = info->fix.smem_start + (dy * info->fix.line_length) + dx; + color_blit(width, height, info->fix.line_length, dest, rop, color, + par->blit_bpp, info); +} + +void i810fb_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct i810fb_par *par = info->par; + u32 sx, sy, dx, dy, pitch, width, height, src, dest, xdir; + + if (!info->var.accel_flags || par->dev_flags & LOCKUP || + par->depth == 4) { + cfb_copyarea(info, region); + return; + } + + dx = region->dx * par->depth; + sx = region->sx * par->depth; + width = region->width * par->depth; + sy = region->sy; + dy = region->dy; + height = region->height; + + if (dx <= sx) { + xdir = INCREMENT; + } + else { + xdir = DECREMENT; + sx += width - 1; + dx += width - 1; + } + if (dy <= sy) { + pitch = info->fix.line_length; + } + else { + pitch = (-(info->fix.line_length)) & 0xFFFF; + sy += height - 1; + dy += height - 1; + } + src = info->fix.smem_start + (sy * info->fix.line_length) + sx; + dest = info->fix.smem_start + (dy * info->fix.line_length) + dx; + + source_copy_blit(width, height, pitch, xdir, src, dest, + PAT_COPY_ROP, par->blit_bpp, info); +} + +void i810fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct i810fb_par *par = info->par; + u32 fg = 0, bg = 0, size, dst; + + if (!info->var.accel_flags || par->dev_flags & LOCKUP || + par->depth == 4 || image->depth != 1) { + cfb_imageblit(info, image); + return; + } + + switch (info->var.bits_per_pixel) { + case 8: + fg = image->fg_color; + bg = image->bg_color; + break; + case 16: + case 24: + fg = ((u32 *)(info->pseudo_palette))[image->fg_color]; + bg = ((u32 *)(info->pseudo_palette))[image->bg_color]; + break; + } + + dst = info->fix.smem_start + (image->dy * info->fix.line_length) + + (image->dx * par->depth); + + size = (image->width+7)/8 + 1; + size &= ~1; + size *= image->height; + size += 7; + size &= ~7; + mono_src_copy_imm_blit(image->width * par->depth, + image->height, info->fix.line_length, + size/4, par->blit_bpp, + PAT_COPY_ROP, dst, (u32 *) image->data, + bg, fg, info); +} + +int i810fb_sync(struct fb_info *info) +{ + struct i810fb_par *par = info->par; + + if (!info->var.accel_flags || par->dev_flags & LOCKUP) + return 0; + + return wait_for_engine_idle(info); +} + +void i810fb_load_front(u32 offset, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u8 __iomem *mmio = par->mmio_start_virtual; + + if (!info->var.accel_flags || par->dev_flags & LOCKUP) + i810_writel(DPLYBASE, mmio, par->fb.physical + offset); + else + load_front(offset, info); +} + +/** + * i810fb_init_ringbuffer - initialize the ringbuffer + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Initializes the ringbuffer by telling the device the + * size and location of the ringbuffer. It also sets + * the head and tail pointers = 0 + */ +void i810fb_init_ringbuffer(struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u32 tmp1, tmp2; + u8 __iomem *mmio = par->mmio_start_virtual; + + wait_for_engine_idle(info); + i810fb_iring_enable(par, OFF); + i810_writel(IRING, mmio, 0); + i810_writel(IRING + 4, mmio, 0); + par->cur_tail = 0; + + tmp2 = i810_readl(IRING + 8, mmio) & ~RBUFFER_START_MASK; + tmp1 = par->iring.physical; + i810_writel(IRING + 8, mmio, tmp2 | tmp1); + + tmp1 = i810_readl(IRING + 12, mmio); + tmp1 &= ~RBUFFER_SIZE_MASK; + tmp2 = (par->iring.size - I810_PAGESIZE) & RBUFFER_SIZE_MASK; + i810_writel(IRING + 12, mmio, tmp1 | tmp2); + i810fb_iring_enable(par, ON); +} diff --git a/drivers/video/fbdev/i810/i810_dvt.c b/drivers/video/fbdev/i810/i810_dvt.c new file mode 100644 index 000000000000..b4b3670667ab --- /dev/null +++ b/drivers/video/fbdev/i810/i810_dvt.c @@ -0,0 +1,312 @@ +/*-*- linux-c -*- + * linux/drivers/video/i810_dvt.c -- Intel 810 Discrete Video Timings (Intel) + * + * Copyright (C) 2001 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * + * 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/kernel.h> + +#include "i810_regs.h" +#include "i810.h" + +struct mode_registers std_modes[] = { + /* 640x480 @ 60Hz */ + { 25000, 0x0013, 0x0003, 0x40, 0x5F, 0x4F, 0x50, 0x82, 0x51, 0x9D, + 0x0B, 0x10, 0x40, 0xE9, 0x0B, 0xDF, 0x50, 0xE7, 0x04, 0x02, + 0x01, 0x01, 0x01, 0x00, 0x01, 0x22002000, 0x22004000, 0x22006000, + 0x22002000, 0x22004000, 0x22006000, 0xC0 }, + + /* 640x480 @ 70Hz */ + { 28000, 0x0053, 0x0010, 0x40, 0x61, 0x4F, 0x4F, 0x85, 0x52, 0x9A, + 0xF2, 0x10, 0x40, 0xE0, 0x03, 0xDF, 0x50, 0xDF, 0xF3, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x01, 0x22002000, 0x22004000, 0x22005000, + 0x22002000, 0x22004000, 0x22005000, 0xC0 }, + + /* 640x480 @ 72Hz */ + { 31000, 0x0013, 0x0002, 0x40, 0x63, 0x4F, 0x4F, 0x87, 0x52, 0x97, + 0x06, 0x0F, 0x40, 0xE8, 0x0B, 0xDF, 0x50, 0xDF, 0x07, 0x02, + 0x01, 0x01, 0x01, 0x00, 0x01, 0x22003000, 0x22005000, 0x22007000, + 0x22003000, 0x22005000, 0x22007000, 0xC0 }, + + /* 640x480 @ 75Hz */ + { 31000, 0x0013, 0x0002, 0x40, 0x64, 0x4F, 0x4F, 0x88, 0x51, 0x99, + 0xF2, 0x10, 0x40, 0xE0, 0x03, 0xDF, 0x50, 0xDF, 0xF3, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x01, 0x22003000, 0x22005000, 0x22007000, + 0x22003000, 0x22005000, 0x22007000, 0xC0 }, + + /* 640x480 @ 85Hz */ + { 36000, 0x0010, 0x0001, 0x40, 0x63, 0x4F, 0x4F, 0x87, 0x56, 0x9D, + 0xFB, 0x10, 0x40, 0xE0, 0x03, 0xDF, 0x50, 0xDF, 0xFC, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x01, 0x22003000, 0x22005000, 0x22107000, + 0x22003000, 0x22005000, 0x22107000, 0xC0 }, + + /* 800x600 @ 56Hz */ + { 36000, 0x0010, 0x0001, 0x40, 0x7B, 0x63, 0x63, 0x9F, 0x66, 0x8F, + 0x6F, 0x10, 0x40, 0x58, 0x0A, 0x57, 0xC8, 0x57, 0x70, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x01, 0x22003000, 0x22005000, 0x22107000, + 0x22003000, 0x22005000, 0x22107000, 0x00 }, + + /* 800x600 @ 60Hz */ + { 40000, 0x0008, 0x0001, 0x30, 0x7F, 0x63, 0x63, 0x83, 0x68, 0x18, + 0x72, 0x10, 0x40, 0x58, 0x0C, 0x57, 0xC8, 0x57, 0x73, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x22003000, 0x22006000, 0x22108000, + 0x22003000, 0x22006000, 0x22108000, 0x00 }, + + /* 800x600 @ 70Hz */ + { 45000, 0x0054, 0x0015, 0x30, 0x7D, 0x63, 0x63, 0x81, 0x68, 0x12, + 0x6f, 0x10, 0x40, 0x58, 0x0b, 0x57, 0x64, 0x57, 0x70, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x22004000, 0x22007000, 0x2210A000, + 0x22004000, 0x22007000, 0x2210A000, 0x00 }, + + /* 800x600 @ 72Hz */ + { 50000, 0x0017, 0x0004, 0x30, 0x7D, 0x63, 0x63, 0x81, 0x6A, 0x19, + 0x98, 0x10, 0x40, 0x7C, 0x02, 0x57, 0xC8, 0x57, 0x99, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x22004000, 0x22007000, 0x2210A000, + 0x22004000, 0x22007000, 0x2210A000, 0x00 }, + + /* 800x600 @ 75Hz */ + { 49000, 0x001F, 0x0006, 0x30, 0x7F, 0x63, 0x63, 0x83, 0x65, 0x0F, + 0x6F, 0x10, 0x40, 0x58, 0x0B, 0x57, 0xC8, 0x57, 0x70, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x22004000, 0x22007000, 0x2210B000, + 0x22004000, 0x22007000, 0x2210B000, 0x00 }, + + /* 800x600 @ 85Hz */ + { 56000, 0x0049, 0x000E, 0x30, 0x7E, 0x63, 0x63, 0x82, 0x67, 0x0F, + 0x75, 0x10, 0x40, 0x58, 0x0B, 0x57, 0xC8, 0x57, 0x76, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x22004000, 0x22108000, 0x2210b000, + 0x22004000, 0x22108000, 0x2210b000, 0x00 }, + + /* 1024x768 @ 60Hz */ + { 65000, 0x003F, 0x000A, 0x30, 0xA3, 0x7F, 0x7F, 0x87, 0x83, 0x94, + 0x24, 0x10, 0x40, 0x02, 0x08, 0xFF, 0x80, 0xFF, 0x25, 0x03, + 0x02, 0x03, 0x02, 0x00, 0x00, 0x22005000, 0x22109000, 0x2220D000, + 0x22005000, 0x22109000, 0x2220D000, 0xC0 }, + + /* 1024x768 @ 70Hz */ + { 75000, 0x0017, 0x0002, 0x30, 0xA1, 0x7F, 0x7F, 0x85, 0x82, 0x93, + 0x24, 0x10, 0x40, 0x02, 0x08, 0xFF, 0x80, 0xFF, 0x25, 0x03, + 0x02, 0x03, 0x02, 0x00, 0x00, 0x22005000, 0x2210A000, 0x2220F000, + 0x22005000, 0x2210A000, 0x2220F000, 0xC0 }, + + /* 1024x768 @ 75Hz */ + { 78000, 0x0050, 0x0017, 0x20, 0x9F, 0x7F, 0x7F, 0x83, 0x81, 0x8D, + 0x1E, 0x10, 0x40, 0x00, 0x03, 0xFF, 0x80, 0xFF, 0x1F, 0x03, + 0x02, 0x03, 0x02, 0x00, 0x00, 0x22006000, 0x2210B000, 0x22210000, + 0x22006000, 0x2210B000, 0x22210000, 0x00 }, + + /* 1024x768 @ 85Hz */ + { 94000, 0x003D, 0x000E, 0x20, 0xA7, 0x7F, 0x7F, 0x8B, 0x85, 0x91, + 0x26, 0x10, 0x40, 0x00, 0x03, 0xFF, 0x80, 0xFF, 0x27, 0x03, + 0x02, 0x03, 0x02, 0x00, 0x00, 0x22007000, 0x2220E000, 0x22212000, + 0x22007000, 0x2220E000, 0x22212000, 0x00 }, + + /* 1152x864 @ 60Hz */ + { 80000, 0x0008, 0x0001, 0x20, 0xB3, 0x8F, 0x8F, 0x97, 0x93, 0x9f, + 0x87, 0x10, 0x40, 0x60, 0x03, 0x5F, 0x90, 0x5f, 0x88, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x00, 0x2220C000, 0x22210000, 0x22415000, + 0x2220C000, 0x22210000, 0x22415000, 0x00 }, + + /* 1152x864 @ 70Hz */ + { 96000, 0x000a, 0x0001, 0x20, 0xbb, 0x8F, 0x8F, 0x9f, 0x98, 0x87, + 0x82, 0x10, 0x40, 0x60, 0x03, 0x5F, 0x90, 0x5F, 0x83, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x00, 0x22107000, 0x22210000, 0x22415000, + 0x22107000, 0x22210000, 0x22415000, 0x00 }, + + /* 1152x864 @ 72Hz */ + { 99000, 0x001f, 0x0006, 0x20, 0xbb, 0x8F, 0x8F, 0x9f, 0x98, 0x87, + 0x83, 0x10, 0x40, 0x60, 0x03, 0x5F, 0x90, 0x5F, 0x84, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x00, 0x22107000, 0x22210000, 0x22415000, + 0x22107000, 0x22210000, 0x22415000, 0x00 }, + + /* 1152x864 @ 75Hz */ + { 108000, 0x0010, 0x0002, 0x20, 0xC3, 0x8F, 0x8F, 0x87, 0x97, 0x07, + 0x82, 0x10, 0x40, 0x60, 0x03, 0x5F, 0x90, 0x5F, 0x83, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x01, 0x22107000, 0x22210000, 0x22415000, + 0x22107000, 0x22210000, 0x22415000, 0x00 }, + + /* 1152x864 @ 85Hz */ + { 121000, 0x006D, 0x0014, 0x20, 0xc0, 0x8F, 0x8F, 0x84, 0x97, 0x07, + 0x93, 0x10, 0x40, 0x60, 0x03, 0x5F, 0x90, 0x5F, 0x94, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x01, 0x2220C000, 0x22210000, 0x22415000, + 0x2220C000, 0x22210000, 0x22415000, 0x0 }, + + /* 1280x960 @ 60Hz */ + { 108000, 0x0010, 0x0002, 0x20, 0xDC, 0x9F, 0x9F, 0x80, 0xAB, 0x99, + 0xE6, 0x10, 0x40, 0xC0, 0x03, 0xBF, 0xA0, 0xBF, 0xE7, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x01, 0x2210A000, 0x22210000, 0x22415000, + 0x2210A000, 0x22210000, 0x22415000, 0x00 }, + + /* 1280x960 @ 75Hz */ + { 129000, 0x0029, 0x0006, 0x20, 0xD3, 0x9F, 0x9F, 0x97, 0xaa, 0x1b, + 0xE8, 0x10, 0x40, 0xC0, 0x03, 0xBF, 0xA0, 0xBF, 0xE9, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x01, 0x2210A000, 0x22210000, 0x2241B000, + 0x2210A000, 0x22210000, 0x2241B000, 0x00 }, + + /* 1280x960 @ 85Hz */ + { 148000, 0x0042, 0x0009, 0x20, 0xD3, 0x9F, 0x9F, 0x97, 0xA7, 0x1B, + 0xF1, 0x10, 0x40, 0xC0, 0x03, 0xBF, 0xA0, 0xBF, 0xF2, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x01, 0x2210A000, 0x22220000, 0x2241D000, + 0x2210A000, 0x22220000, 0x2241D000, 0x00 }, + + /* 1600x1200 @ 60Hz */ + { 162000, 0x0019, 0x0006, 0x10, 0x09, 0xC7, 0xC7, 0x8D, 0xcf, 0x07, + 0xE0, 0x10, 0x40, 0xB0, 0x03, 0xAF, 0xC8, 0xAF, 0xE1, 0x04, + 0x04, 0x04, 0x04, 0x01, 0x00, 0x2210b000, 0x22416000, 0x44419000, + 0x2210b000, 0x22416000, 0x44419000, 0x00 }, + + /* 1600x1200 @ 65 Hz */ + { 175000, 0x005d, 0x0018, 0x10, 0x09, 0xC7, 0xC7, 0x8D, 0xcf, 0x07, + 0xE0, 0x10, 0x40, 0xB0, 0x03, 0xAF, 0xC8, 0xAF, 0xE1, 0x04, + 0x04, 0x04, 0x04, 0x01, 0x00, 0x2210c000, 0x22416000, 0x44419000, + 0x2210c000, 0x22416000, 0x44419000, 0x00 }, + + /* 1600x1200 @ 70 Hz */ + { 189000, 0x003D, 0x000e, 0x10, 0x09, 0xC7, 0xC7, 0x8d, 0xcf, 0x07, + 0xE0, 0x10, 0x40, 0xb0, 0x03, 0xAF, 0xC8, 0xaf, 0xE1, 0x04, + 0x04, 0x04, 0x04, 0x01, 0x00, 0x2220e000, 0x22416000, 0x44419000, + 0x2220e000, 0x22416000, 0x44419000, 0x00 }, + + /* 1600x1200 @ 72 Hz */ + { 195000, 0x003f, 0x000e, 0x10, 0x0b, 0xC7, 0xC7, 0x8f, 0xd5, 0x0b, + 0xE1, 0x10, 0x40, 0xb0, 0x03, 0xAF, 0xC8, 0xaf, 0xe2, 0x04, 0x04, + 0x04, 0x04, 0x01, 0x00, 0x2220e000, 0x22416000, 0x44419000, + 0x2220e000, 0x22416000, 0x44419000, 0x00 }, + + /* 1600x1200 @ 75 Hz */ + { 202000, 0x0024, 0x0007, 0x10, 0x09, 0xC7, 0xC7, 0x8d, 0xcf, 0x07, + 0xE0, 0x10, 0x40, 0xb0, 0x03, 0xAF, 0xC8, 0xaf, 0xE1, 0x04, 0x04, + 0x04, 0x04, 0x01, 0x00, 0x2220e000, 0x22416000, 0x44419000, + 0x2220e000, 0x22416000, 0x44419000, 0x00 }, + + /* 1600x1200 @ 85 Hz */ + { 229000, 0x0029, 0x0007, 0x10, 0x09, 0xC7, 0xC7, 0x8d, 0xcf, 0x07, + 0xE0, 0x10, 0x40, 0xb0, 0x03, 0xAF, 0xC8, 0xaf, 0xE1, 0x04, 0x04, + 0x04, 0x04, 0x01, 0x00, 0x22210000, 0x22416000, 0x0, + 0x22210000, 0x22416000, 0x0, 0x00 }, +}; + +void round_off_xres(u32 *xres) +{ + if (*xres <= 640) + *xres = 640; + else if (*xres <= 800) + *xres = 800; + else if (*xres <= 1024) + *xres = 1024; + else if (*xres <= 1152) + *xres = 1152; + else if (*xres <= 1280) + *xres = 1280; + else + *xres = 1600; +} + +inline void round_off_yres(u32 *xres, u32 *yres) +{ + *yres = (*xres * 3) >> 2; +} + +static int i810fb_find_best_mode(u32 xres, u32 yres, u32 pixclock) +{ + u32 diff = 0, diff_best = 0xFFFFFFFF, i = 0, i_best = 0; + u8 hfl = (u8) ((xres >> 3) - 1); + + for (i = 0; i < ARRAY_SIZE(std_modes); i++) { + if (std_modes[i].cr01 == hfl) { + if (std_modes[i].pixclock <= pixclock) + diff = pixclock - std_modes[i].pixclock; + if (diff < diff_best) { + i_best = i; + diff_best = diff; + } + } + } + return i_best; +} + +void i810fb_encode_registers(const struct fb_var_screeninfo *var, + struct i810fb_par *par, u32 xres, u32 yres) +{ + u32 i_best = i810fb_find_best_mode(xres, yres, par->regs.pixclock); + + par->regs = std_modes[i_best]; + + /* overlay */ + par->ovract = ((xres + var->right_margin + var->hsync_len + + var->left_margin - 32) | ((xres - 32) << 16)); +} + +void i810fb_fill_var_timings(struct fb_var_screeninfo *var) +{ + u32 total, xres, yres; + u32 mode, pixclock; + + xres = var->xres; + yres = var->yres; + + pixclock = 1000000000 / var->pixclock; + mode = i810fb_find_best_mode(xres, yres, pixclock); + + total = (std_modes[mode].cr00 | (std_modes[mode].cr35 & 1) << 8) + 3; + total <<= 3; + + var->pixclock = 1000000000 / std_modes[mode].pixclock; + var->right_margin = (std_modes[mode].cr04 << 3) - xres; + var->hsync_len = ((std_modes[mode].cr05 & 0x1F) - + (std_modes[mode].cr04 & 0x1F)) << 3; + var->left_margin = (total - (xres + var->right_margin + + var->hsync_len)); + var->sync = FB_SYNC_ON_GREEN; + if (~(std_modes[mode].msr & (1 << 6))) + var->sync |= FB_SYNC_HOR_HIGH_ACT; + if (~(std_modes[mode].msr & (1 << 7))) + var->sync |= FB_SYNC_VERT_HIGH_ACT; + + total = (std_modes[mode].cr06 | (std_modes[mode].cr30 & 0xF) << 8) + 2; + var->lower_margin = (std_modes[mode].cr10 | + (std_modes[mode].cr32 & 0x0F) << 8) - yres; + var->vsync_len = (std_modes[mode].cr11 & 0x0F) - + (var->lower_margin & 0x0F); + var->upper_margin = total - (yres + var->lower_margin + var->vsync_len); +} + +u32 i810_get_watermark(struct fb_var_screeninfo *var, + struct i810fb_par *par) +{ + struct mode_registers *params = &par->regs; + u32 wmark = 0; + + if (par->mem_freq == 100) { + switch (var->bits_per_pixel) { + case 8: + wmark = params->bpp8_100; + break; + case 16: + wmark = params->bpp16_100; + break; + case 24: + case 32: + wmark = params->bpp24_100; + } + } else { + switch (var->bits_per_pixel) { + case 8: + wmark = params->bpp8_133; + break; + case 16: + wmark = params->bpp16_133; + break; + case 24: + case 32: + wmark = params->bpp24_133; + } + } + return wmark; +} + diff --git a/drivers/video/fbdev/i810/i810_gtf.c b/drivers/video/fbdev/i810/i810_gtf.c new file mode 100644 index 000000000000..9743d51e7f8c --- /dev/null +++ b/drivers/video/fbdev/i810/i810_gtf.c @@ -0,0 +1,276 @@ +/*-*- linux-c -*- + * linux/drivers/video/i810_main.h -- Intel 810 Non-discrete Video Timings + * (VESA GTF) + * + * Copyright (C) 2001 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * + * 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/kernel.h> + +#include "i810_regs.h" +#include "i810.h" +#include "i810_main.h" + +/* + * FIFO and Watermark tables - based almost wholly on i810_wmark.c in + * XFree86 v4.03 by Precision Insight. Slightly modified for integer + * operation, instead of float + */ + +struct wm_info { + u32 freq; + u32 wm; +}; + +static struct wm_info i810_wm_8_100[] = { + { 15, 0x0070c000 }, { 19, 0x0070c000 }, { 25, 0x22003000 }, + { 28, 0x22003000 }, { 31, 0x22003000 }, { 36, 0x22007000 }, + { 40, 0x22007000 }, { 45, 0x22007000 }, { 49, 0x22008000 }, + { 50, 0x22008000 }, { 56, 0x22008000 }, { 65, 0x22008000 }, + { 75, 0x22008000 }, { 78, 0x22008000 }, { 80, 0x22008000 }, + { 94, 0x22008000 }, { 96, 0x22107000 }, { 99, 0x22107000 }, + { 108, 0x22107000 }, { 121, 0x22107000 }, { 128, 0x22107000 }, + { 132, 0x22109000 }, { 135, 0x22109000 }, { 157, 0x2210b000 }, + { 162, 0x2210b000 }, { 175, 0x2210b000 }, { 189, 0x2220e000 }, + { 195, 0x2220e000 }, { 202, 0x2220e000 }, { 204, 0x2220e000 }, + { 218, 0x2220f000 }, { 229, 0x22210000 }, { 234, 0x22210000 }, +}; + +static struct wm_info i810_wm_16_100[] = { + { 15, 0x0070c000 }, { 19, 0x0020c000 }, { 25, 0x22006000 }, + { 28, 0x22006000 }, { 31, 0x22007000 }, { 36, 0x22007000 }, + { 40, 0x22007000 }, { 45, 0x22007000 }, { 49, 0x22009000 }, + { 50, 0x22009000 }, { 56, 0x22108000 }, { 65, 0x2210e000 }, + { 75, 0x2210e000 }, { 78, 0x2210e000 }, { 80, 0x22210000 }, + { 94, 0x22210000 }, { 96, 0x22210000 }, { 99, 0x22210000 }, + { 108, 0x22210000 }, { 121, 0x22210000 }, { 128, 0x22210000 }, + { 132, 0x22314000 }, { 135, 0x22314000 }, { 157, 0x22415000 }, + { 162, 0x22416000 }, { 175, 0x22416000 }, { 189, 0x22416000 }, + { 195, 0x22416000 }, { 202, 0x22416000 }, { 204, 0x22416000 }, + { 218, 0x22416000 }, { 229, 0x22416000 }, +}; + +static struct wm_info i810_wm_24_100[] = { + { 15, 0x0020c000 }, { 19, 0x0040c000 }, { 25, 0x22009000 }, + { 28, 0x22009000 }, { 31, 0x2200a000 }, { 36, 0x2210c000 }, + { 40, 0x2210c000 }, { 45, 0x2210c000 }, { 49, 0x22111000 }, + { 50, 0x22111000 }, { 56, 0x22111000 }, { 65, 0x22214000 }, + { 75, 0x22214000 }, { 78, 0x22215000 }, { 80, 0x22216000 }, + { 94, 0x22218000 }, { 96, 0x22418000 }, { 99, 0x22418000 }, + { 108, 0x22418000 }, { 121, 0x22418000 }, { 128, 0x22419000 }, + { 132, 0x22519000 }, { 135, 0x4441d000 }, { 157, 0x44419000 }, + { 162, 0x44419000 }, { 175, 0x44419000 }, { 189, 0x44419000 }, + { 195, 0x44419000 }, { 202, 0x44419000 }, { 204, 0x44419000 }, +}; + +static struct wm_info i810_wm_8_133[] = { + { 15, 0x0070c000 }, { 19, 0x0070c000 }, { 25, 0x22003000 }, + { 28, 0x22003000 }, { 31, 0x22003000 }, { 36, 0x22007000 }, + { 40, 0x22007000 }, { 45, 0x22007000 }, { 49, 0x22008000 }, + { 50, 0x22008000 }, { 56, 0x22008000 }, { 65, 0x22008000 }, + { 75, 0x22008000 }, { 78, 0x22008000 }, { 80, 0x22008000 }, + { 94, 0x22008000 }, { 96, 0x22107000 }, { 99, 0x22107000 }, + { 108, 0x22107000 }, { 121, 0x22107000 }, { 128, 0x22107000 }, + { 132, 0x22109000 }, { 135, 0x22109000 }, { 157, 0x2210b000 }, + { 162, 0x2210b000 }, { 175, 0x2210b000 }, { 189, 0x2220e000 }, + { 195, 0x2220e000 }, { 202, 0x2220e000 }, { 204, 0x2220e000 }, + { 218, 0x2220f000 }, { 229, 0x22210000 }, { 234, 0x22210000 }, +}; + +static struct wm_info i810_wm_16_133[] = { + { 15, 0x0020c000 }, { 19, 0x0020c000 }, { 25, 0x22006000 }, + { 28, 0x22006000 }, { 31, 0x22007000 }, { 36, 0x22007000 }, + { 40, 0x22007000 }, { 45, 0x22007000 }, { 49, 0x22009000 }, + { 50, 0x22009000 }, { 56, 0x22108000 }, { 65, 0x2210e000 }, + { 75, 0x2210e000 }, { 78, 0x2210e000 }, { 80, 0x22210000 }, + { 94, 0x22210000 }, { 96, 0x22210000 }, { 99, 0x22210000 }, + { 108, 0x22210000 }, { 121, 0x22210000 }, { 128, 0x22210000 }, + { 132, 0x22314000 }, { 135, 0x22314000 }, { 157, 0x22415000 }, + { 162, 0x22416000 }, { 175, 0x22416000 }, { 189, 0x22416000 }, + { 195, 0x22416000 }, { 202, 0x22416000 }, { 204, 0x22416000 }, + { 218, 0x22416000 }, { 229, 0x22416000 }, +}; + +static struct wm_info i810_wm_24_133[] = { + { 15, 0x0020c000 }, { 19, 0x00408000 }, { 25, 0x22009000 }, + { 28, 0x22009000 }, { 31, 0x2200a000 }, { 36, 0x2210c000 }, + { 40, 0x2210c000 }, { 45, 0x2210c000 }, { 49, 0x22111000 }, + { 50, 0x22111000 }, { 56, 0x22111000 }, { 65, 0x22214000 }, + { 75, 0x22214000 }, { 78, 0x22215000 }, { 80, 0x22216000 }, + { 94, 0x22218000 }, { 96, 0x22418000 }, { 99, 0x22418000 }, + { 108, 0x22418000 }, { 121, 0x22418000 }, { 128, 0x22419000 }, + { 132, 0x22519000 }, { 135, 0x4441d000 }, { 157, 0x44419000 }, + { 162, 0x44419000 }, { 175, 0x44419000 }, { 189, 0x44419000 }, + { 195, 0x44419000 }, { 202, 0x44419000 }, { 204, 0x44419000 }, +}; + +void round_off_xres(u32 *xres) { } +void round_off_yres(u32 *xres, u32 *yres) { } + +/** + * i810fb_encode_registers - encode @var to hardware register values + * @var: pointer to var structure + * @par: pointer to hardware par structure + * + * DESCRIPTION: + * Timing values in @var will be converted to appropriate + * register values of @par. + */ +void i810fb_encode_registers(const struct fb_var_screeninfo *var, + struct i810fb_par *par, u32 xres, u32 yres) +{ + int n, blank_s, blank_e; + u8 __iomem *mmio = par->mmio_start_virtual; + u8 msr = 0; + + /* Horizontal */ + /* htotal */ + n = ((xres + var->right_margin + var->hsync_len + + var->left_margin) >> 3) - 5; + par->regs.cr00 = (u8) n; + par->regs.cr35 = (u8) ((n >> 8) & 1); + + /* xres */ + par->regs.cr01 = (u8) ((xres >> 3) - 1); + + /* hblank */ + blank_e = (xres + var->right_margin + var->hsync_len + + var->left_margin) >> 3; + blank_e--; + blank_s = blank_e - 127; + if (blank_s < (xres >> 3)) + blank_s = xres >> 3; + par->regs.cr02 = (u8) blank_s; + par->regs.cr03 = (u8) (blank_e & 0x1F); + par->regs.cr05 = (u8) ((blank_e & (1 << 5)) << 2); + par->regs.cr39 = (u8) ((blank_e >> 6) & 1); + + /* hsync */ + par->regs.cr04 = (u8) ((xres + var->right_margin) >> 3); + par->regs.cr05 |= (u8) (((xres + var->right_margin + + var->hsync_len) >> 3) & 0x1F); + + /* Vertical */ + /* vtotal */ + n = yres + var->lower_margin + var->vsync_len + var->upper_margin - 2; + par->regs.cr06 = (u8) (n & 0xFF); + par->regs.cr30 = (u8) ((n >> 8) & 0x0F); + + /* vsync */ + n = yres + var->lower_margin; + par->regs.cr10 = (u8) (n & 0xFF); + par->regs.cr32 = (u8) ((n >> 8) & 0x0F); + par->regs.cr11 = i810_readb(CR11, mmio) & ~0x0F; + par->regs.cr11 |= (u8) ((yres + var->lower_margin + + var->vsync_len) & 0x0F); + + /* yres */ + n = yres - 1; + par->regs.cr12 = (u8) (n & 0xFF); + par->regs.cr31 = (u8) ((n >> 8) & 0x0F); + + /* vblank */ + blank_e = yres + var->lower_margin + var->vsync_len + + var->upper_margin; + blank_e--; + blank_s = blank_e - 127; + if (blank_s < yres) + blank_s = yres; + par->regs.cr15 = (u8) (blank_s & 0xFF); + par->regs.cr33 = (u8) ((blank_s >> 8) & 0x0F); + par->regs.cr16 = (u8) (blank_e & 0xFF); + par->regs.cr09 = 0; + + /* sync polarity */ + if (!(var->sync & FB_SYNC_HOR_HIGH_ACT)) + msr |= 1 << 6; + if (!(var->sync & FB_SYNC_VERT_HIGH_ACT)) + msr |= 1 << 7; + par->regs.msr = msr; + + /* interlace */ + if (var->vmode & FB_VMODE_INTERLACED) + par->interlace = (1 << 7) | ((u8) (var->yres >> 4)); + else + par->interlace = 0; + + if (var->vmode & FB_VMODE_DOUBLE) + par->regs.cr09 |= 1 << 7; + + /* overlay */ + par->ovract = ((var->xres + var->right_margin + var->hsync_len + + var->left_margin - 32) | ((var->xres - 32) << 16)); +} + +void i810fb_fill_var_timings(struct fb_var_screeninfo *var) { } + +/** + * i810_get_watermark - gets watermark + * @var: pointer to fb_var_screeninfo + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Gets the required watermark based on + * pixelclock and RAMBUS frequency. + * + * RETURNS: + * watermark + */ +u32 i810_get_watermark(const struct fb_var_screeninfo *var, + struct i810fb_par *par) +{ + struct wm_info *wmark = NULL; + u32 i, size = 0, pixclock, wm_best = 0, min, diff; + + if (par->mem_freq == 100) { + switch (var->bits_per_pixel) { + case 8: + wmark = i810_wm_8_100; + size = ARRAY_SIZE(i810_wm_8_100); + break; + case 16: + wmark = i810_wm_16_100; + size = ARRAY_SIZE(i810_wm_16_100); + break; + case 24: + case 32: + wmark = i810_wm_24_100; + size = ARRAY_SIZE(i810_wm_24_100); + } + } else { + switch(var->bits_per_pixel) { + case 8: + wmark = i810_wm_8_133; + size = ARRAY_SIZE(i810_wm_8_133); + break; + case 16: + wmark = i810_wm_16_133; + size = ARRAY_SIZE(i810_wm_16_133); + break; + case 24: + case 32: + wmark = i810_wm_24_133; + size = ARRAY_SIZE(i810_wm_24_133); + } + } + + pixclock = 1000000/var->pixclock; + min = ~0; + for (i = 0; i < size; i++) { + if (pixclock <= wmark[i].freq) + diff = wmark[i].freq - pixclock; + else + diff = pixclock - wmark[i].freq; + if (diff < min) { + wm_best = wmark[i].wm; + min = diff; + } + } + return wm_best; +} + diff --git a/drivers/video/fbdev/i810/i810_main.c b/drivers/video/fbdev/i810/i810_main.c new file mode 100644 index 000000000000..bb674e431741 --- /dev/null +++ b/drivers/video/fbdev/i810/i810_main.c @@ -0,0 +1,2218 @@ + /*-*- linux-c -*- + * linux/drivers/video/i810_main.c -- Intel 810 frame buffer device + * + * Copyright (C) 2001 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * Contributors: + * Michael Vogt <mvogt@acm.org> - added support for Intel 815 chipsets + * and enabling the power-on state of + * external VGA connectors for + * secondary displays + * + * Fredrik Andersson <krueger@shell.linux.se> - alpha testing of + * the VESA GTF + * + * Brad Corrion <bcorrion@web-co.com> - alpha testing of customized + * timings support + * + * The code framework is a modification of vfb.c by Geert Uytterhoeven. + * DotClock and PLL calculations are partly based on i810_driver.c + * in xfree86 v4.0.3 by Precision Insight. + * Watermark calculation and tables are based on i810_wmark.c + * in xfre86 v4.0.3 by Precision Insight. Slight modifications + * only to allow for integer operations instead of floating point. + * + * 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/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/resource.h> +#include <linux/unistd.h> +#include <linux/console.h> + +#include <asm/io.h> +#include <asm/div64.h> +#include <asm/page.h> + +#include "i810_regs.h" +#include "i810.h" +#include "i810_main.h" + +/* + * voffset - framebuffer offset in MiB from aperture start address. In order for + * the driver to work with X, we must try to use memory holes left untouched by X. The + * following table lists where X's different surfaces start at. + * + * --------------------------------------------- + * : : 64 MiB : 32 MiB : + * ---------------------------------------------- + * : FrontBuffer : 0 : 0 : + * : DepthBuffer : 48 : 16 : + * : BackBuffer : 56 : 24 : + * ---------------------------------------------- + * + * So for chipsets with 64 MiB Aperture sizes, 32 MiB for v_offset is okay, allowing up to + * 15 + 1 MiB of Framebuffer memory. For 32 MiB Aperture sizes, a v_offset of 8 MiB should + * work, allowing 7 + 1 MiB of Framebuffer memory. + * Note, the size of the hole may change depending on how much memory you allocate to X, + * and how the memory is split up between these surfaces. + * + * Note: Anytime the DepthBuffer or FrontBuffer is overlapped, X would still run but with + * DRI disabled. But if the Frontbuffer is overlapped, X will fail to load. + * + * Experiment with v_offset to find out which works best for you. + */ +static u32 v_offset_default; /* For 32 MiB Aper size, 8 should be the default */ +static u32 voffset; + +static int i810fb_cursor(struct fb_info *info, struct fb_cursor *cursor); +static int i810fb_init_pci(struct pci_dev *dev, + const struct pci_device_id *entry); +static void __exit i810fb_remove_pci(struct pci_dev *dev); +static int i810fb_resume(struct pci_dev *dev); +static int i810fb_suspend(struct pci_dev *dev, pm_message_t state); + +/* Chipset Specific Functions */ +static int i810fb_set_par (struct fb_info *info); +static int i810fb_getcolreg (u8 regno, u8 *red, u8 *green, u8 *blue, + u8 *transp, struct fb_info *info); +static int i810fb_setcolreg (unsigned regno, unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info); +static int i810fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); +static int i810fb_blank (int blank_mode, struct fb_info *info); + +/* Initialization */ +static void i810fb_release_resource (struct fb_info *info, struct i810fb_par *par); + +/* PCI */ +static const char * const i810_pci_list[] = { + "Intel(R) 810 Framebuffer Device" , + "Intel(R) 810-DC100 Framebuffer Device" , + "Intel(R) 810E Framebuffer Device" , + "Intel(R) 815 (Internal Graphics 100Mhz FSB) Framebuffer Device" , + "Intel(R) 815 (Internal Graphics only) Framebuffer Device" , + "Intel(R) 815 (Internal Graphics with AGP) Framebuffer Device" +}; + +static struct pci_device_id i810fb_pci_tbl[] = { + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810E_IG, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 2 }, + /* mvo: added i815 PCI-ID */ + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_100, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 3 }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_NOAGP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 4 }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 5 }, + { 0 }, +}; + +static struct pci_driver i810fb_driver = { + .name = "i810fb", + .id_table = i810fb_pci_tbl, + .probe = i810fb_init_pci, + .remove = __exit_p(i810fb_remove_pci), + .suspend = i810fb_suspend, + .resume = i810fb_resume, +}; + +static char *mode_option = NULL; +static int vram = 4; +static int bpp = 8; +static bool mtrr; +static bool accel; +static int hsync1; +static int hsync2; +static int vsync1; +static int vsync2; +static int xres; +static int yres; +static int vyres; +static bool sync; +static bool extvga; +static bool dcolor; +static bool ddc3; + +/*------------------------------------------------------------*/ + +/************************************************************** + * Hardware Low Level Routines * + **************************************************************/ + +/** + * i810_screen_off - turns off/on display + * @mmio: address of register space + * @mode: on or off + * + * DESCRIPTION: + * Blanks/unblanks the display + */ +static void i810_screen_off(u8 __iomem *mmio, u8 mode) +{ + u32 count = WAIT_COUNT; + u8 val; + + i810_writeb(SR_INDEX, mmio, SR01); + val = i810_readb(SR_DATA, mmio); + val = (mode == OFF) ? val | SCR_OFF : + val & ~SCR_OFF; + + while((i810_readw(DISP_SL, mmio) & 0xFFF) && count--); + i810_writeb(SR_INDEX, mmio, SR01); + i810_writeb(SR_DATA, mmio, val); +} + +/** + * i810_dram_off - turns off/on dram refresh + * @mmio: address of register space + * @mode: on or off + * + * DESCRIPTION: + * Turns off DRAM refresh. Must be off for only 2 vsyncs + * before data becomes corrupt + */ +static void i810_dram_off(u8 __iomem *mmio, u8 mode) +{ + u8 val; + + val = i810_readb(DRAMCH, mmio); + val &= DRAM_OFF; + val = (mode == OFF) ? val : val | DRAM_ON; + i810_writeb(DRAMCH, mmio, val); +} + +/** + * i810_protect_regs - allows rw/ro mode of certain VGA registers + * @mmio: address of register space + * @mode: protect/unprotect + * + * DESCRIPTION: + * The IBM VGA standard allows protection of certain VGA registers. + * This will protect or unprotect them. + */ +static void i810_protect_regs(u8 __iomem *mmio, int mode) +{ + u8 reg; + + i810_writeb(CR_INDEX_CGA, mmio, CR11); + reg = i810_readb(CR_DATA_CGA, mmio); + reg = (mode == OFF) ? reg & ~0x80 : + reg | 0x80; + + i810_writeb(CR_INDEX_CGA, mmio, CR11); + i810_writeb(CR_DATA_CGA, mmio, reg); +} + +/** + * i810_load_pll - loads values for the hardware PLL clock + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Loads the P, M, and N registers. + */ +static void i810_load_pll(struct i810fb_par *par) +{ + u32 tmp1, tmp2; + u8 __iomem *mmio = par->mmio_start_virtual; + + tmp1 = par->regs.M | par->regs.N << 16; + tmp2 = i810_readl(DCLK_2D, mmio); + tmp2 &= ~MN_MASK; + i810_writel(DCLK_2D, mmio, tmp1 | tmp2); + + tmp1 = par->regs.P; + tmp2 = i810_readl(DCLK_0DS, mmio); + tmp2 &= ~(P_OR << 16); + i810_writel(DCLK_0DS, mmio, (tmp1 << 16) | tmp2); + + i810_writeb(MSR_WRITE, mmio, par->regs.msr | 0xC8 | 1); + +} + +/** + * i810_load_vga - load standard VGA registers + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Load values to VGA registers + */ +static void i810_load_vga(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + + /* interlace */ + i810_writeb(CR_INDEX_CGA, mmio, CR70); + i810_writeb(CR_DATA_CGA, mmio, par->interlace); + + i810_writeb(CR_INDEX_CGA, mmio, CR00); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr00); + i810_writeb(CR_INDEX_CGA, mmio, CR01); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr01); + i810_writeb(CR_INDEX_CGA, mmio, CR02); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr02); + i810_writeb(CR_INDEX_CGA, mmio, CR03); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr03); + i810_writeb(CR_INDEX_CGA, mmio, CR04); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr04); + i810_writeb(CR_INDEX_CGA, mmio, CR05); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr05); + i810_writeb(CR_INDEX_CGA, mmio, CR06); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr06); + i810_writeb(CR_INDEX_CGA, mmio, CR09); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr09); + i810_writeb(CR_INDEX_CGA, mmio, CR10); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr10); + i810_writeb(CR_INDEX_CGA, mmio, CR11); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr11); + i810_writeb(CR_INDEX_CGA, mmio, CR12); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr12); + i810_writeb(CR_INDEX_CGA, mmio, CR15); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr15); + i810_writeb(CR_INDEX_CGA, mmio, CR16); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr16); +} + +/** + * i810_load_vgax - load extended VGA registers + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Load values to extended VGA registers + */ +static void i810_load_vgax(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_writeb(CR_INDEX_CGA, mmio, CR30); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr30); + i810_writeb(CR_INDEX_CGA, mmio, CR31); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr31); + i810_writeb(CR_INDEX_CGA, mmio, CR32); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr32); + i810_writeb(CR_INDEX_CGA, mmio, CR33); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr33); + i810_writeb(CR_INDEX_CGA, mmio, CR35); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr35); + i810_writeb(CR_INDEX_CGA, mmio, CR39); + i810_writeb(CR_DATA_CGA, mmio, par->regs.cr39); +} + +/** + * i810_load_2d - load grahics registers + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Load values to graphics registers + */ +static void i810_load_2d(struct i810fb_par *par) +{ + u32 tmp; + u8 tmp8; + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_writel(FW_BLC, mmio, par->watermark); + tmp = i810_readl(PIXCONF, mmio); + tmp |= 1 | 1 << 20; + i810_writel(PIXCONF, mmio, tmp); + + i810_writel(OVRACT, mmio, par->ovract); + + i810_writeb(GR_INDEX, mmio, GR10); + tmp8 = i810_readb(GR_DATA, mmio); + tmp8 |= 2; + i810_writeb(GR_INDEX, mmio, GR10); + i810_writeb(GR_DATA, mmio, tmp8); +} + +/** + * i810_hires - enables high resolution mode + * @mmio: address of register space + */ +static void i810_hires(u8 __iomem *mmio) +{ + u8 val; + + i810_writeb(CR_INDEX_CGA, mmio, CR80); + val = i810_readb(CR_DATA_CGA, mmio); + i810_writeb(CR_INDEX_CGA, mmio, CR80); + i810_writeb(CR_DATA_CGA, mmio, val | 1); + /* Stop LCD displays from flickering */ + i810_writel(MEM_MODE, mmio, i810_readl(MEM_MODE, mmio) | 4); +} + +/** + * i810_load_pitch - loads the characters per line of the display + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Loads the characters per line + */ +static void i810_load_pitch(struct i810fb_par *par) +{ + u32 tmp, pitch; + u8 val; + u8 __iomem *mmio = par->mmio_start_virtual; + + pitch = par->pitch >> 3; + i810_writeb(SR_INDEX, mmio, SR01); + val = i810_readb(SR_DATA, mmio); + val &= 0xE0; + val |= 1 | 1 << 2; + i810_writeb(SR_INDEX, mmio, SR01); + i810_writeb(SR_DATA, mmio, val); + + tmp = pitch & 0xFF; + i810_writeb(CR_INDEX_CGA, mmio, CR13); + i810_writeb(CR_DATA_CGA, mmio, (u8) tmp); + + tmp = pitch >> 8; + i810_writeb(CR_INDEX_CGA, mmio, CR41); + val = i810_readb(CR_DATA_CGA, mmio) & ~0x0F; + i810_writeb(CR_INDEX_CGA, mmio, CR41); + i810_writeb(CR_DATA_CGA, mmio, (u8) tmp | val); +} + +/** + * i810_load_color - loads the color depth of the display + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Loads the color depth of the display and the graphics engine + */ +static void i810_load_color(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + u32 reg1; + u16 reg2; + + reg1 = i810_readl(PIXCONF, mmio) & ~(0xF0000 | 1 << 27); + reg2 = i810_readw(BLTCNTL, mmio) & ~0x30; + + reg1 |= 0x8000 | par->pixconf; + reg2 |= par->bltcntl; + i810_writel(PIXCONF, mmio, reg1); + i810_writew(BLTCNTL, mmio, reg2); +} + +/** + * i810_load_regs - loads all registers for the mode + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Loads registers + */ +static void i810_load_regs(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_screen_off(mmio, OFF); + i810_protect_regs(mmio, OFF); + i810_dram_off(mmio, OFF); + i810_load_pll(par); + i810_load_vga(par); + i810_load_vgax(par); + i810_dram_off(mmio, ON); + i810_load_2d(par); + i810_hires(mmio); + i810_screen_off(mmio, ON); + i810_protect_regs(mmio, ON); + i810_load_color(par); + i810_load_pitch(par); +} + +static void i810_write_dac(u8 regno, u8 red, u8 green, u8 blue, + u8 __iomem *mmio) +{ + i810_writeb(CLUT_INDEX_WRITE, mmio, regno); + i810_writeb(CLUT_DATA, mmio, red); + i810_writeb(CLUT_DATA, mmio, green); + i810_writeb(CLUT_DATA, mmio, blue); +} + +static void i810_read_dac(u8 regno, u8 *red, u8 *green, u8 *blue, + u8 __iomem *mmio) +{ + i810_writeb(CLUT_INDEX_READ, mmio, regno); + *red = i810_readb(CLUT_DATA, mmio); + *green = i810_readb(CLUT_DATA, mmio); + *blue = i810_readb(CLUT_DATA, mmio); +} + +/************************************************************ + * VGA State Restore * + ************************************************************/ +static void i810_restore_pll(struct i810fb_par *par) +{ + u32 tmp1, tmp2; + u8 __iomem *mmio = par->mmio_start_virtual; + + tmp1 = par->hw_state.dclk_2d; + tmp2 = i810_readl(DCLK_2D, mmio); + tmp1 &= ~MN_MASK; + tmp2 &= MN_MASK; + i810_writel(DCLK_2D, mmio, tmp1 | tmp2); + + tmp1 = par->hw_state.dclk_1d; + tmp2 = i810_readl(DCLK_1D, mmio); + tmp1 &= ~MN_MASK; + tmp2 &= MN_MASK; + i810_writel(DCLK_1D, mmio, tmp1 | tmp2); + + i810_writel(DCLK_0DS, mmio, par->hw_state.dclk_0ds); +} + +static void i810_restore_dac(struct i810fb_par *par) +{ + u32 tmp1, tmp2; + u8 __iomem *mmio = par->mmio_start_virtual; + + tmp1 = par->hw_state.pixconf; + tmp2 = i810_readl(PIXCONF, mmio); + tmp1 &= DAC_BIT; + tmp2 &= ~DAC_BIT; + i810_writel(PIXCONF, mmio, tmp1 | tmp2); +} + +static void i810_restore_vgax(struct i810fb_par *par) +{ + u8 i, j; + u8 __iomem *mmio = par->mmio_start_virtual; + + for (i = 0; i < 4; i++) { + i810_writeb(CR_INDEX_CGA, mmio, CR30+i); + i810_writeb(CR_DATA_CGA, mmio, *(&(par->hw_state.cr30) + i)); + } + i810_writeb(CR_INDEX_CGA, mmio, CR35); + i810_writeb(CR_DATA_CGA, mmio, par->hw_state.cr35); + i810_writeb(CR_INDEX_CGA, mmio, CR39); + i810_writeb(CR_DATA_CGA, mmio, par->hw_state.cr39); + i810_writeb(CR_INDEX_CGA, mmio, CR41); + i810_writeb(CR_DATA_CGA, mmio, par->hw_state.cr39); + + /*restore interlace*/ + i810_writeb(CR_INDEX_CGA, mmio, CR70); + i = par->hw_state.cr70; + i &= INTERLACE_BIT; + j = i810_readb(CR_DATA_CGA, mmio); + i810_writeb(CR_INDEX_CGA, mmio, CR70); + i810_writeb(CR_DATA_CGA, mmio, j | i); + + i810_writeb(CR_INDEX_CGA, mmio, CR80); + i810_writeb(CR_DATA_CGA, mmio, par->hw_state.cr80); + i810_writeb(MSR_WRITE, mmio, par->hw_state.msr); + i810_writeb(SR_INDEX, mmio, SR01); + i = (par->hw_state.sr01) & ~0xE0 ; + j = i810_readb(SR_DATA, mmio) & 0xE0; + i810_writeb(SR_INDEX, mmio, SR01); + i810_writeb(SR_DATA, mmio, i | j); +} + +static void i810_restore_vga(struct i810fb_par *par) +{ + u8 i; + u8 __iomem *mmio = par->mmio_start_virtual; + + for (i = 0; i < 10; i++) { + i810_writeb(CR_INDEX_CGA, mmio, CR00 + i); + i810_writeb(CR_DATA_CGA, mmio, *((&par->hw_state.cr00) + i)); + } + for (i = 0; i < 8; i++) { + i810_writeb(CR_INDEX_CGA, mmio, CR10 + i); + i810_writeb(CR_DATA_CGA, mmio, *((&par->hw_state.cr10) + i)); + } +} + +static void i810_restore_addr_map(struct i810fb_par *par) +{ + u8 tmp; + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_writeb(GR_INDEX, mmio, GR10); + tmp = i810_readb(GR_DATA, mmio); + tmp &= ADDR_MAP_MASK; + tmp |= par->hw_state.gr10; + i810_writeb(GR_INDEX, mmio, GR10); + i810_writeb(GR_DATA, mmio, tmp); +} + +static void i810_restore_2d(struct i810fb_par *par) +{ + u32 tmp_long; + u16 tmp_word; + u8 __iomem *mmio = par->mmio_start_virtual; + + tmp_word = i810_readw(BLTCNTL, mmio); + tmp_word &= ~(3 << 4); + tmp_word |= par->hw_state.bltcntl; + i810_writew(BLTCNTL, mmio, tmp_word); + + i810_dram_off(mmio, OFF); + i810_writel(PIXCONF, mmio, par->hw_state.pixconf); + i810_dram_off(mmio, ON); + + tmp_word = i810_readw(HWSTAM, mmio); + tmp_word &= 3 << 13; + tmp_word |= par->hw_state.hwstam; + i810_writew(HWSTAM, mmio, tmp_word); + + tmp_long = i810_readl(FW_BLC, mmio); + tmp_long &= FW_BLC_MASK; + tmp_long |= par->hw_state.fw_blc; + i810_writel(FW_BLC, mmio, tmp_long); + + i810_writel(HWS_PGA, mmio, par->hw_state.hws_pga); + i810_writew(IER, mmio, par->hw_state.ier); + i810_writew(IMR, mmio, par->hw_state.imr); + i810_writel(DPLYSTAS, mmio, par->hw_state.dplystas); +} + +static void i810_restore_vga_state(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_screen_off(mmio, OFF); + i810_protect_regs(mmio, OFF); + i810_dram_off(mmio, OFF); + i810_restore_pll(par); + i810_restore_dac(par); + i810_restore_vga(par); + i810_restore_vgax(par); + i810_restore_addr_map(par); + i810_dram_off(mmio, ON); + i810_restore_2d(par); + i810_screen_off(mmio, ON); + i810_protect_regs(mmio, ON); +} + +/*********************************************************************** + * VGA State Save * + ***********************************************************************/ + +static void i810_save_vgax(struct i810fb_par *par) +{ + u8 i; + u8 __iomem *mmio = par->mmio_start_virtual; + + for (i = 0; i < 4; i++) { + i810_writeb(CR_INDEX_CGA, mmio, CR30 + i); + *(&(par->hw_state.cr30) + i) = i810_readb(CR_DATA_CGA, mmio); + } + i810_writeb(CR_INDEX_CGA, mmio, CR35); + par->hw_state.cr35 = i810_readb(CR_DATA_CGA, mmio); + i810_writeb(CR_INDEX_CGA, mmio, CR39); + par->hw_state.cr39 = i810_readb(CR_DATA_CGA, mmio); + i810_writeb(CR_INDEX_CGA, mmio, CR41); + par->hw_state.cr41 = i810_readb(CR_DATA_CGA, mmio); + i810_writeb(CR_INDEX_CGA, mmio, CR70); + par->hw_state.cr70 = i810_readb(CR_DATA_CGA, mmio); + par->hw_state.msr = i810_readb(MSR_READ, mmio); + i810_writeb(CR_INDEX_CGA, mmio, CR80); + par->hw_state.cr80 = i810_readb(CR_DATA_CGA, mmio); + i810_writeb(SR_INDEX, mmio, SR01); + par->hw_state.sr01 = i810_readb(SR_DATA, mmio); +} + +static void i810_save_vga(struct i810fb_par *par) +{ + u8 i; + u8 __iomem *mmio = par->mmio_start_virtual; + + for (i = 0; i < 10; i++) { + i810_writeb(CR_INDEX_CGA, mmio, CR00 + i); + *((&par->hw_state.cr00) + i) = i810_readb(CR_DATA_CGA, mmio); + } + for (i = 0; i < 8; i++) { + i810_writeb(CR_INDEX_CGA, mmio, CR10 + i); + *((&par->hw_state.cr10) + i) = i810_readb(CR_DATA_CGA, mmio); + } +} + +static void i810_save_2d(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + + par->hw_state.dclk_2d = i810_readl(DCLK_2D, mmio); + par->hw_state.dclk_1d = i810_readl(DCLK_1D, mmio); + par->hw_state.dclk_0ds = i810_readl(DCLK_0DS, mmio); + par->hw_state.pixconf = i810_readl(PIXCONF, mmio); + par->hw_state.fw_blc = i810_readl(FW_BLC, mmio); + par->hw_state.bltcntl = i810_readw(BLTCNTL, mmio); + par->hw_state.hwstam = i810_readw(HWSTAM, mmio); + par->hw_state.hws_pga = i810_readl(HWS_PGA, mmio); + par->hw_state.ier = i810_readw(IER, mmio); + par->hw_state.imr = i810_readw(IMR, mmio); + par->hw_state.dplystas = i810_readl(DPLYSTAS, mmio); +} + +static void i810_save_vga_state(struct i810fb_par *par) +{ + i810_save_vga(par); + i810_save_vgax(par); + i810_save_2d(par); +} + +/************************************************************ + * Helpers * + ************************************************************/ +/** + * get_line_length - calculates buffer pitch in bytes + * @par: pointer to i810fb_par structure + * @xres_virtual: virtual resolution of the frame + * @bpp: bits per pixel + * + * DESCRIPTION: + * Calculates buffer pitch in bytes. + */ +static u32 get_line_length(struct i810fb_par *par, int xres_virtual, int bpp) +{ + u32 length; + + length = xres_virtual*bpp; + length = (length+31)&-32; + length >>= 3; + return length; +} + +/** + * i810_calc_dclk - calculates the P, M, and N values of a pixelclock value + * @freq: target pixelclock in picoseconds + * @m: where to write M register + * @n: where to write N register + * @p: where to write P register + * + * DESCRIPTION: + * Based on the formula Freq_actual = (4*M*Freq_ref)/(N^P) + * Repeatedly computes the Freq until the actual Freq is equal to + * the target Freq or until the loop count is zero. In the latter + * case, the actual frequency nearest the target will be used. + */ +static void i810_calc_dclk(u32 freq, u32 *m, u32 *n, u32 *p) +{ + u32 m_reg, n_reg, p_divisor, n_target_max; + u32 m_target, n_target, p_target, n_best, m_best, mod; + u32 f_out, target_freq, diff = 0, mod_min, diff_min; + + diff_min = mod_min = 0xFFFFFFFF; + n_best = m_best = m_target = f_out = 0; + + target_freq = freq; + n_target_max = 30; + + /* + * find P such that target freq is 16x reference freq (Hz). + */ + p_divisor = 1; + p_target = 0; + while(!((1000000 * p_divisor)/(16 * 24 * target_freq)) && + p_divisor <= 32) { + p_divisor <<= 1; + p_target++; + } + + n_reg = m_reg = n_target = 3; + while (diff_min && mod_min && (n_target < n_target_max)) { + f_out = (p_divisor * n_reg * 1000000)/(4 * 24 * m_reg); + mod = (p_divisor * n_reg * 1000000) % (4 * 24 * m_reg); + m_target = m_reg; + n_target = n_reg; + if (f_out <= target_freq) { + n_reg++; + diff = target_freq - f_out; + } else { + m_reg++; + diff = f_out - target_freq; + } + + if (diff_min > diff) { + diff_min = diff; + n_best = n_target; + m_best = m_target; + } + + if (!diff && mod_min > mod) { + mod_min = mod; + n_best = n_target; + m_best = m_target; + } + } + if (m) *m = (m_best - 2) & 0x3FF; + if (n) *n = (n_best - 2) & 0x3FF; + if (p) *p = (p_target << 4); +} + +/************************************************************* + * Hardware Cursor Routines * + *************************************************************/ + +/** + * i810_enable_cursor - show or hide the hardware cursor + * @mmio: address of register space + * @mode: show (1) or hide (0) + * + * Description: + * Shows or hides the hardware cursor + */ +static void i810_enable_cursor(u8 __iomem *mmio, int mode) +{ + u32 temp; + + temp = i810_readl(PIXCONF, mmio); + temp = (mode == ON) ? temp | CURSOR_ENABLE_MASK : + temp & ~CURSOR_ENABLE_MASK; + + i810_writel(PIXCONF, mmio, temp); +} + +static void i810_reset_cursor_image(struct i810fb_par *par) +{ + u8 __iomem *addr = par->cursor_heap.virtual; + int i, j; + + for (i = 64; i--; ) { + for (j = 0; j < 8; j++) { + i810_writeb(j, addr, 0xff); + i810_writeb(j+8, addr, 0x00); + } + addr +=16; + } +} + +static void i810_load_cursor_image(int width, int height, u8 *data, + struct i810fb_par *par) +{ + u8 __iomem *addr = par->cursor_heap.virtual; + int i, j, w = width/8; + int mod = width % 8, t_mask, d_mask; + + t_mask = 0xff >> mod; + d_mask = ~(0xff >> mod); + for (i = height; i--; ) { + for (j = 0; j < w; j++) { + i810_writeb(j+0, addr, 0x00); + i810_writeb(j+8, addr, *data++); + } + if (mod) { + i810_writeb(j+0, addr, t_mask); + i810_writeb(j+8, addr, *data++ & d_mask); + } + addr += 16; + } +} + +static void i810_load_cursor_colors(int fg, int bg, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u8 __iomem *mmio = par->mmio_start_virtual; + u8 red, green, blue, trans, temp; + + i810fb_getcolreg(bg, &red, &green, &blue, &trans, info); + + temp = i810_readb(PIXCONF1, mmio); + i810_writeb(PIXCONF1, mmio, temp | EXTENDED_PALETTE); + + i810_write_dac(4, red, green, blue, mmio); + + i810_writeb(PIXCONF1, mmio, temp); + + i810fb_getcolreg(fg, &red, &green, &blue, &trans, info); + temp = i810_readb(PIXCONF1, mmio); + i810_writeb(PIXCONF1, mmio, temp | EXTENDED_PALETTE); + + i810_write_dac(5, red, green, blue, mmio); + + i810_writeb(PIXCONF1, mmio, temp); +} + +/** + * i810_init_cursor - initializes the cursor + * @par: pointer to i810fb_par structure + * + * DESCRIPTION: + * Initializes the cursor registers + */ +static void i810_init_cursor(struct i810fb_par *par) +{ + u8 __iomem *mmio = par->mmio_start_virtual; + + i810_enable_cursor(mmio, OFF); + i810_writel(CURBASE, mmio, par->cursor_heap.physical); + i810_writew(CURCNTR, mmio, COORD_ACTIVE | CURSOR_MODE_64_XOR); +} + +/********************************************************************* + * Framebuffer hook helpers * + *********************************************************************/ +/** + * i810_round_off - Round off values to capability of hardware + * @var: pointer to fb_var_screeninfo structure + * + * DESCRIPTION: + * @var contains user-defined information for the mode to be set. + * This will try modify those values to ones nearest the + * capability of the hardware + */ +static void i810_round_off(struct fb_var_screeninfo *var) +{ + u32 xres, yres, vxres, vyres; + + /* + * Presently supports only these configurations + */ + + xres = var->xres; + yres = var->yres; + vxres = var->xres_virtual; + vyres = var->yres_virtual; + + var->bits_per_pixel += 7; + var->bits_per_pixel &= ~7; + + if (var->bits_per_pixel < 8) + var->bits_per_pixel = 8; + if (var->bits_per_pixel > 32) + var->bits_per_pixel = 32; + + round_off_xres(&xres); + if (xres < 40) + xres = 40; + if (xres > 2048) + xres = 2048; + xres = (xres + 7) & ~7; + + if (vxres < xres) + vxres = xres; + + round_off_yres(&xres, &yres); + if (yres < 1) + yres = 1; + if (yres >= 2048) + yres = 2048; + + if (vyres < yres) + vyres = yres; + + if (var->bits_per_pixel == 32) + var->accel_flags = 0; + + /* round of horizontal timings to nearest 8 pixels */ + var->left_margin = (var->left_margin + 4) & ~7; + var->right_margin = (var->right_margin + 4) & ~7; + var->hsync_len = (var->hsync_len + 4) & ~7; + + if (var->vmode & FB_VMODE_INTERLACED) { + if (!((yres + var->upper_margin + var->vsync_len + + var->lower_margin) & 1)) + var->upper_margin++; + } + + var->xres = xres; + var->yres = yres; + var->xres_virtual = vxres; + var->yres_virtual = vyres; +} + +/** + * set_color_bitfields - sets rgba fields + * @var: pointer to fb_var_screeninfo + * + * DESCRIPTION: + * The length, offset and ordering for each color field + * (red, green, blue) will be set as specified + * by the hardware + */ +static void set_color_bitfields(struct fb_var_screeninfo *var) +{ + switch (var->bits_per_pixel) { + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: + var->green.length = (var->green.length == 5) ? 5 : 6; + var->red.length = 5; + var->blue.length = 5; + var->transp.length = 6 - var->green.length; + var->blue.offset = 0; + var->green.offset = 5; + var->red.offset = 5 + var->green.length; + var->transp.offset = (5 + var->red.offset) & 15; + break; + case 24: /* RGB 888 */ + case 32: /* RGBA 8888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.length = var->bits_per_pixel - 24; + var->transp.offset = (var->transp.length) ? 24 : 0; + break; + } + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; +} + +/** + * i810_check_params - check if contents in var are valid + * @var: pointer to fb_var_screeninfo + * @info: pointer to fb_info + * + * DESCRIPTION: + * This will check if the framebuffer size is sufficient + * for the current mode and if the user's monitor has the + * required specifications to display the current mode. + */ +static int i810_check_params(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct i810fb_par *par = info->par; + int line_length, vidmem, mode_valid = 0, retval = 0; + u32 vyres = var->yres_virtual, vxres = var->xres_virtual; + + /* + * Memory limit + */ + line_length = get_line_length(par, vxres, var->bits_per_pixel); + vidmem = line_length*vyres; + + if (vidmem > par->fb.size) { + vyres = par->fb.size/line_length; + if (vyres < var->yres) { + vyres = info->var.yres; + vxres = par->fb.size/vyres; + vxres /= var->bits_per_pixel >> 3; + line_length = get_line_length(par, vxres, + var->bits_per_pixel); + vidmem = line_length * info->var.yres; + if (vxres < var->xres) { + printk("i810fb: required video memory, " + "%d bytes, for %dx%d-%d (virtual) " + "is out of range\n", + vidmem, vxres, vyres, + var->bits_per_pixel); + return -ENOMEM; + } + } + } + + var->xres_virtual = vxres; + var->yres_virtual = vyres; + + /* + * Monitor limit + */ + switch (var->bits_per_pixel) { + case 8: + info->monspecs.dclkmax = 234000000; + break; + case 16: + info->monspecs.dclkmax = 229000000; + break; + case 24: + case 32: + info->monspecs.dclkmax = 204000000; + break; + } + + info->monspecs.dclkmin = 15000000; + + if (!fb_validate_mode(var, info)) + mode_valid = 1; + +#ifdef CONFIG_FB_I810_I2C + if (!mode_valid && info->monspecs.gtf && + !fb_get_mode(FB_MAXTIMINGS, 0, var, info)) + mode_valid = 1; + + if (!mode_valid && info->monspecs.modedb_len) { + const struct fb_videomode *mode; + + mode = fb_find_best_mode(var, &info->modelist); + if (mode) { + fb_videomode_to_var(var, mode); + mode_valid = 1; + } + } +#endif + if (!mode_valid && info->monspecs.modedb_len == 0) { + if (fb_get_mode(FB_MAXTIMINGS, 0, var, info)) { + int default_sync = (info->monspecs.hfmin-HFMIN) + |(info->monspecs.hfmax-HFMAX) + |(info->monspecs.vfmin-VFMIN) + |(info->monspecs.vfmax-VFMAX); + printk("i810fb: invalid video mode%s\n", + default_sync ? "" : ". Specifying " + "vsyncN/hsyncN parameters may help"); + retval = -EINVAL; + } + } + + return retval; +} + +/** + * encode_fix - fill up fb_fix_screeninfo structure + * @fix: pointer to fb_fix_screeninfo + * @info: pointer to fb_info + * + * DESCRIPTION: + * This will set up parameters that are unmodifiable by the user. + */ +static int encode_fix(struct fb_fix_screeninfo *fix, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + + strcpy(fix->id, "I810"); + mutex_lock(&info->mm_lock); + fix->smem_start = par->fb.physical; + fix->smem_len = par->fb.size; + mutex_unlock(&info->mm_lock); + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + fix->xpanstep = 8; + fix->ypanstep = 1; + + switch (info->var.bits_per_pixel) { + case 8: + fix->visual = FB_VISUAL_PSEUDOCOLOR; + break; + case 16: + case 24: + case 32: + if (info->var.nonstd) + fix->visual = FB_VISUAL_DIRECTCOLOR; + else + fix->visual = FB_VISUAL_TRUECOLOR; + break; + default: + return -EINVAL; + } + fix->ywrapstep = 0; + fix->line_length = par->pitch; + fix->mmio_start = par->mmio_start_phys; + fix->mmio_len = MMIO_SIZE; + fix->accel = FB_ACCEL_I810; + + return 0; +} + +/** + * decode_var - modify par according to contents of var + * @var: pointer to fb_var_screeninfo + * @par: pointer to i810fb_par + * + * DESCRIPTION: + * Based on the contents of @var, @par will be dynamically filled up. + * @par contains all information necessary to modify the hardware. +*/ +static void decode_var(const struct fb_var_screeninfo *var, + struct i810fb_par *par) +{ + u32 xres, yres, vxres, vyres; + + xres = var->xres; + yres = var->yres; + vxres = var->xres_virtual; + vyres = var->yres_virtual; + + switch (var->bits_per_pixel) { + case 8: + par->pixconf = PIXCONF8; + par->bltcntl = 0; + par->depth = 1; + par->blit_bpp = BPP8; + break; + case 16: + if (var->green.length == 5) + par->pixconf = PIXCONF15; + else + par->pixconf = PIXCONF16; + par->bltcntl = 16; + par->depth = 2; + par->blit_bpp = BPP16; + break; + case 24: + par->pixconf = PIXCONF24; + par->bltcntl = 32; + par->depth = 3; + par->blit_bpp = BPP24; + break; + case 32: + par->pixconf = PIXCONF32; + par->bltcntl = 0; + par->depth = 4; + par->blit_bpp = 3 << 24; + break; + } + if (var->nonstd && var->bits_per_pixel != 8) + par->pixconf |= 1 << 27; + + i810_calc_dclk(var->pixclock, &par->regs.M, + &par->regs.N, &par->regs.P); + i810fb_encode_registers(var, par, xres, yres); + + par->watermark = i810_get_watermark(var, par); + par->pitch = get_line_length(par, vxres, var->bits_per_pixel); +} + +/** + * i810fb_getcolreg - gets red, green and blue values of the hardware DAC + * @regno: DAC index + * @red: red + * @green: green + * @blue: blue + * @transp: transparency (alpha) + * @info: pointer to fb_info + * + * DESCRIPTION: + * Gets the red, green and blue values of the hardware DAC as pointed by @regno + * and writes them to @red, @green and @blue respectively + */ +static int i810fb_getcolreg(u8 regno, u8 *red, u8 *green, u8 *blue, + u8 *transp, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u8 __iomem *mmio = par->mmio_start_virtual; + u8 temp; + + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + if ((info->var.green.length == 5 && regno > 31) || + (info->var.green.length == 6 && regno > 63)) + return 1; + } + + temp = i810_readb(PIXCONF1, mmio); + i810_writeb(PIXCONF1, mmio, temp & ~EXTENDED_PALETTE); + + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR && + info->var.green.length == 5) + i810_read_dac(regno * 8, red, green, blue, mmio); + + else if (info->fix.visual == FB_VISUAL_DIRECTCOLOR && + info->var.green.length == 6) { + u8 tmp; + + i810_read_dac(regno * 8, red, &tmp, blue, mmio); + i810_read_dac(regno * 4, &tmp, green, &tmp, mmio); + } + else + i810_read_dac(regno, red, green, blue, mmio); + + *transp = 0; + i810_writeb(PIXCONF1, mmio, temp); + + return 0; +} + +/****************************************************************** + * Framebuffer device-specific hooks * + ******************************************************************/ + +static int i810fb_open(struct fb_info *info, int user) +{ + struct i810fb_par *par = info->par; + + mutex_lock(&par->open_lock); + if (par->use_count == 0) { + memset(&par->state, 0, sizeof(struct vgastate)); + par->state.flags = VGA_SAVE_CMAP; + par->state.vgabase = par->mmio_start_virtual; + save_vga(&par->state); + + i810_save_vga_state(par); + } + + par->use_count++; + mutex_unlock(&par->open_lock); + + return 0; +} + +static int i810fb_release(struct fb_info *info, int user) +{ + struct i810fb_par *par = info->par; + + mutex_lock(&par->open_lock); + if (par->use_count == 0) { + mutex_unlock(&par->open_lock); + return -EINVAL; + } + + if (par->use_count == 1) { + i810_restore_vga_state(par); + restore_vga(&par->state); + } + + par->use_count--; + mutex_unlock(&par->open_lock); + + return 0; +} + + +static int i810fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u8 __iomem *mmio = par->mmio_start_virtual; + u8 temp; + int i; + + if (regno > 255) return 1; + + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + if ((info->var.green.length == 5 && regno > 31) || + (info->var.green.length == 6 && regno > 63)) + return 1; + } + + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + temp = i810_readb(PIXCONF1, mmio); + i810_writeb(PIXCONF1, mmio, temp & ~EXTENDED_PALETTE); + + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR && + info->var.green.length == 5) { + for (i = 0; i < 8; i++) + i810_write_dac((u8) (regno * 8) + i, (u8) red, + (u8) green, (u8) blue, mmio); + } else if (info->fix.visual == FB_VISUAL_DIRECTCOLOR && + info->var.green.length == 6) { + u8 r, g, b; + + if (regno < 32) { + for (i = 0; i < 8; i++) + i810_write_dac((u8) (regno * 8) + i, + (u8) red, (u8) green, + (u8) blue, mmio); + } + i810_read_dac((u8) (regno*4), &r, &g, &b, mmio); + for (i = 0; i < 4; i++) + i810_write_dac((u8) (regno*4) + i, r, (u8) green, + b, mmio); + } else if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) { + i810_write_dac((u8) regno, (u8) red, (u8) green, + (u8) blue, mmio); + } + + i810_writeb(PIXCONF1, mmio, temp); + + if (regno < 16) { + switch (info->var.bits_per_pixel) { + case 16: + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + if (info->var.green.length == 5) + ((u32 *)info->pseudo_palette)[regno] = + (regno << 10) | (regno << 5) | + regno; + else + ((u32 *)info->pseudo_palette)[regno] = + (regno << 11) | (regno << 5) | + regno; + } else { + if (info->var.green.length == 5) { + /* RGB 555 */ + ((u32 *)info->pseudo_palette)[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + } else { + /* RGB 565 */ + ((u32 *)info->pseudo_palette)[regno] = + (red & 0xf800) | + ((green & 0xf800) >> 5) | + ((blue & 0xf800) >> 11); + } + } + break; + case 24: /* RGB 888 */ + case 32: /* RGBA 8888 */ + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) + ((u32 *)info->pseudo_palette)[regno] = + (regno << 16) | (regno << 8) | + regno; + else + ((u32 *)info->pseudo_palette)[regno] = + ((red & 0xff00) << 8) | + (green & 0xff00) | + ((blue & 0xff00) >> 8); + break; + } + } + return 0; +} + +static int i810fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u32 total; + + total = var->xoffset * par->depth + + var->yoffset * info->fix.line_length; + i810fb_load_front(total, info); + + return 0; +} + +static int i810fb_blank (int blank_mode, struct fb_info *info) +{ + struct i810fb_par *par = info->par; + u8 __iomem *mmio = par->mmio_start_virtual; + int mode = 0, pwr, scr_off = 0; + + pwr = i810_readl(PWR_CLKC, mmio); + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + mode = POWERON; + pwr |= 1; + scr_off = ON; + break; + case FB_BLANK_NORMAL: + mode = POWERON; + pwr |= 1; + scr_off = OFF; + break; + case FB_BLANK_VSYNC_SUSPEND: + mode = STANDBY; + pwr |= 1; + scr_off = OFF; + break; + case FB_BLANK_HSYNC_SUSPEND: + mode = SUSPEND; + pwr |= 1; + scr_off = OFF; + break; + case FB_BLANK_POWERDOWN: + mode = POWERDOWN; + pwr &= ~1; + scr_off = OFF; + break; + default: + return -EINVAL; + } + + i810_screen_off(mmio, scr_off); + i810_writel(HVSYNC, mmio, mode); + i810_writel(PWR_CLKC, mmio, pwr); + + return 0; +} + +static int i810fb_set_par(struct fb_info *info) +{ + struct i810fb_par *par = info->par; + + decode_var(&info->var, par); + i810_load_regs(par); + i810_init_cursor(par); + encode_fix(&info->fix, info); + + if (info->var.accel_flags && !(par->dev_flags & LOCKUP)) { + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_IMAGEBLIT; + info->pixmap.scan_align = 2; + } else { + info->pixmap.scan_align = 1; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + } + return 0; +} + +static int i810fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int err; + + if (IS_DVT) { + var->vmode &= ~FB_VMODE_MASK; + var->vmode |= FB_VMODE_NONINTERLACED; + } + if (var->vmode & FB_VMODE_DOUBLE) { + var->vmode &= ~FB_VMODE_MASK; + var->vmode |= FB_VMODE_NONINTERLACED; + } + + i810_round_off(var); + if ((err = i810_check_params(var, info))) + return err; + + i810fb_fill_var_timings(var); + set_color_bitfields(var); + return 0; +} + +static int i810fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct i810fb_par *par = info->par; + u8 __iomem *mmio = par->mmio_start_virtual; + + if (par->dev_flags & LOCKUP) + return -ENXIO; + + if (cursor->image.width > 64 || cursor->image.height > 64) + return -ENXIO; + + if ((i810_readl(CURBASE, mmio) & 0xf) != par->cursor_heap.physical) { + i810_init_cursor(par); + cursor->set |= FB_CUR_SETALL; + } + + i810_enable_cursor(mmio, OFF); + + if (cursor->set & FB_CUR_SETPOS) { + u32 tmp; + + tmp = (cursor->image.dx - info->var.xoffset) & 0xffff; + tmp |= (cursor->image.dy - info->var.yoffset) << 16; + i810_writel(CURPOS, mmio, tmp); + } + + if (cursor->set & FB_CUR_SETSIZE) + i810_reset_cursor_image(par); + + if (cursor->set & FB_CUR_SETCMAP) + i810_load_cursor_colors(cursor->image.fg_color, + cursor->image.bg_color, + info); + + if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETIMAGE)) { + int size = ((cursor->image.width + 7) >> 3) * + cursor->image.height; + int i; + u8 *data = kmalloc(64 * 8, GFP_ATOMIC); + + if (data == NULL) + return -ENOMEM; + + switch (cursor->rop) { + case ROP_XOR: + for (i = 0; i < size; i++) + data[i] = cursor->image.data[i] ^ cursor->mask[i]; + break; + case ROP_COPY: + default: + for (i = 0; i < size; i++) + data[i] = cursor->image.data[i] & cursor->mask[i]; + break; + } + + i810_load_cursor_image(cursor->image.width, + cursor->image.height, data, + par); + kfree(data); + } + + if (cursor->enable) + i810_enable_cursor(mmio, ON); + + return 0; +} + +static struct fb_ops i810fb_ops = { + .owner = THIS_MODULE, + .fb_open = i810fb_open, + .fb_release = i810fb_release, + .fb_check_var = i810fb_check_var, + .fb_set_par = i810fb_set_par, + .fb_setcolreg = i810fb_setcolreg, + .fb_blank = i810fb_blank, + .fb_pan_display = i810fb_pan_display, + .fb_fillrect = i810fb_fillrect, + .fb_copyarea = i810fb_copyarea, + .fb_imageblit = i810fb_imageblit, + .fb_cursor = i810fb_cursor, + .fb_sync = i810fb_sync, +}; + +/*********************************************************************** + * Power Management * + ***********************************************************************/ +static int i810fb_suspend(struct pci_dev *dev, pm_message_t mesg) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct i810fb_par *par = info->par; + + par->cur_state = mesg.event; + + switch (mesg.event) { + case PM_EVENT_FREEZE: + case PM_EVENT_PRETHAW: + dev->dev.power.power_state = mesg; + return 0; + } + + console_lock(); + fb_set_suspend(info, 1); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + i810fb_blank(FB_BLANK_POWERDOWN, info); + agp_unbind_memory(par->i810_gtt.i810_fb_memory); + agp_unbind_memory(par->i810_gtt.i810_cursor_memory); + + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, mesg)); + console_unlock(); + + return 0; +} + +static int i810fb_resume(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct i810fb_par *par = info->par; + int cur_state = par->cur_state; + + par->cur_state = PM_EVENT_ON; + + if (cur_state == PM_EVENT_FREEZE) { + pci_set_power_state(dev, PCI_D0); + return 0; + } + + console_lock(); + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + + if (pci_enable_device(dev)) + goto fail; + + pci_set_master(dev); + agp_bind_memory(par->i810_gtt.i810_fb_memory, + par->fb.offset); + agp_bind_memory(par->i810_gtt.i810_cursor_memory, + par->cursor_heap.offset); + i810fb_set_par(info); + fb_set_suspend (info, 0); + info->fbops->fb_blank(VESA_NO_BLANKING, info); +fail: + console_unlock(); + return 0; +} +/*********************************************************************** + * AGP resource allocation * + ***********************************************************************/ + +static void i810_fix_pointers(struct i810fb_par *par) +{ + par->fb.physical = par->aperture.physical+(par->fb.offset << 12); + par->fb.virtual = par->aperture.virtual+(par->fb.offset << 12); + par->iring.physical = par->aperture.physical + + (par->iring.offset << 12); + par->iring.virtual = par->aperture.virtual + + (par->iring.offset << 12); + par->cursor_heap.virtual = par->aperture.virtual+ + (par->cursor_heap.offset << 12); +} + +static void i810_fix_offsets(struct i810fb_par *par) +{ + if (vram + 1 > par->aperture.size >> 20) + vram = (par->aperture.size >> 20) - 1; + if (v_offset_default > (par->aperture.size >> 20)) + v_offset_default = (par->aperture.size >> 20); + if (vram + v_offset_default + 1 > par->aperture.size >> 20) + v_offset_default = (par->aperture.size >> 20) - (vram + 1); + + par->fb.size = vram << 20; + par->fb.offset = v_offset_default << 20; + par->fb.offset >>= 12; + + par->iring.offset = par->fb.offset + (par->fb.size >> 12); + par->iring.size = RINGBUFFER_SIZE; + + par->cursor_heap.offset = par->iring.offset + (RINGBUFFER_SIZE >> 12); + par->cursor_heap.size = 4096; +} + +static int i810_alloc_agp_mem(struct fb_info *info) +{ + struct i810fb_par *par = info->par; + int size; + struct agp_bridge_data *bridge; + + i810_fix_offsets(par); + size = par->fb.size + par->iring.size; + + if (!(bridge = agp_backend_acquire(par->dev))) { + printk("i810fb_alloc_fbmem: cannot acquire agpgart\n"); + return -ENODEV; + } + if (!(par->i810_gtt.i810_fb_memory = + agp_allocate_memory(bridge, size >> 12, AGP_NORMAL_MEMORY))) { + printk("i810fb_alloc_fbmem: can't allocate framebuffer " + "memory\n"); + agp_backend_release(bridge); + return -ENOMEM; + } + if (agp_bind_memory(par->i810_gtt.i810_fb_memory, + par->fb.offset)) { + printk("i810fb_alloc_fbmem: can't bind framebuffer memory\n"); + agp_backend_release(bridge); + return -EBUSY; + } + + if (!(par->i810_gtt.i810_cursor_memory = + agp_allocate_memory(bridge, par->cursor_heap.size >> 12, + AGP_PHYSICAL_MEMORY))) { + printk("i810fb_alloc_cursormem: can't allocate" + "cursor memory\n"); + agp_backend_release(bridge); + return -ENOMEM; + } + if (agp_bind_memory(par->i810_gtt.i810_cursor_memory, + par->cursor_heap.offset)) { + printk("i810fb_alloc_cursormem: cannot bind cursor memory\n"); + agp_backend_release(bridge); + return -EBUSY; + } + + par->cursor_heap.physical = par->i810_gtt.i810_cursor_memory->physical; + + i810_fix_pointers(par); + + agp_backend_release(bridge); + + return 0; +} + +/*************************************************************** + * Initialization * + ***************************************************************/ + +/** + * i810_init_monspecs + * @info: pointer to device specific info structure + * + * DESCRIPTION: + * Sets the user monitor's horizontal and vertical + * frequency limits + */ +static void i810_init_monspecs(struct fb_info *info) +{ + if (!hsync1) + hsync1 = HFMIN; + if (!hsync2) + hsync2 = HFMAX; + if (!info->monspecs.hfmax) + info->monspecs.hfmax = hsync2; + if (!info->monspecs.hfmin) + info->monspecs.hfmin = hsync1; + if (hsync2 < hsync1) + info->monspecs.hfmin = hsync2; + + if (!vsync1) + vsync1 = VFMIN; + if (!vsync2) + vsync2 = VFMAX; + if (IS_DVT && vsync1 < 60) + vsync1 = 60; + if (!info->monspecs.vfmax) + info->monspecs.vfmax = vsync2; + if (!info->monspecs.vfmin) + info->monspecs.vfmin = vsync1; + if (vsync2 < vsync1) + info->monspecs.vfmin = vsync2; +} + +/** + * i810_init_defaults - initializes default values to use + * @par: pointer to i810fb_par structure + * @info: pointer to current fb_info structure + */ +static void i810_init_defaults(struct i810fb_par *par, struct fb_info *info) +{ + mutex_init(&par->open_lock); + + if (voffset) + v_offset_default = voffset; + else if (par->aperture.size > 32 * 1024 * 1024) + v_offset_default = 16; + else + v_offset_default = 8; + + if (!vram) + vram = 1; + + if (accel) + par->dev_flags |= HAS_ACCELERATION; + + if (sync) + par->dev_flags |= ALWAYS_SYNC; + + par->ddc_num = (ddc3 ? 3 : 2); + + if (bpp < 8) + bpp = 8; + + par->i810fb_ops = i810fb_ops; + + if (xres) + info->var.xres = xres; + else + info->var.xres = 640; + + if (yres) + info->var.yres = yres; + else + info->var.yres = 480; + + if (!vyres) + vyres = (vram << 20)/(info->var.xres*bpp >> 3); + + info->var.yres_virtual = vyres; + info->var.bits_per_pixel = bpp; + + if (dcolor) + info->var.nonstd = 1; + + if (par->dev_flags & HAS_ACCELERATION) + info->var.accel_flags = 1; + + i810_init_monspecs(info); +} + +/** + * i810_init_device - initialize device + * @par: pointer to i810fb_par structure + */ +static void i810_init_device(struct i810fb_par *par) +{ + u8 reg; + u8 __iomem *mmio = par->mmio_start_virtual; + + if (mtrr) set_mtrr(par); + + i810_init_cursor(par); + + /* mvo: enable external vga-connector (for laptops) */ + if (extvga) { + i810_writel(HVSYNC, mmio, 0); + i810_writel(PWR_CLKC, mmio, 3); + } + + pci_read_config_byte(par->dev, 0x50, ®); + reg &= FREQ_MASK; + par->mem_freq = (reg) ? 133 : 100; + +} + +static int i810_allocate_pci_resource(struct i810fb_par *par, + const struct pci_device_id *entry) +{ + int err; + + if ((err = pci_enable_device(par->dev))) { + printk("i810fb_init: cannot enable device\n"); + return err; + } + par->res_flags |= PCI_DEVICE_ENABLED; + + if (pci_resource_len(par->dev, 0) > 512 * 1024) { + par->aperture.physical = pci_resource_start(par->dev, 0); + par->aperture.size = pci_resource_len(par->dev, 0); + par->mmio_start_phys = pci_resource_start(par->dev, 1); + } else { + par->aperture.physical = pci_resource_start(par->dev, 1); + par->aperture.size = pci_resource_len(par->dev, 1); + par->mmio_start_phys = pci_resource_start(par->dev, 0); + } + if (!par->aperture.size) { + printk("i810fb_init: device is disabled\n"); + return -ENOMEM; + } + + if (!request_mem_region(par->aperture.physical, + par->aperture.size, + i810_pci_list[entry->driver_data])) { + printk("i810fb_init: cannot request framebuffer region\n"); + return -ENODEV; + } + par->res_flags |= FRAMEBUFFER_REQ; + + par->aperture.virtual = ioremap_nocache(par->aperture.physical, + par->aperture.size); + if (!par->aperture.virtual) { + printk("i810fb_init: cannot remap framebuffer region\n"); + return -ENODEV; + } + + if (!request_mem_region(par->mmio_start_phys, + MMIO_SIZE, + i810_pci_list[entry->driver_data])) { + printk("i810fb_init: cannot request mmio region\n"); + return -ENODEV; + } + par->res_flags |= MMIO_REQ; + + par->mmio_start_virtual = ioremap_nocache(par->mmio_start_phys, + MMIO_SIZE); + if (!par->mmio_start_virtual) { + printk("i810fb_init: cannot remap mmio region\n"); + return -ENODEV; + } + + return 0; +} + +static void i810fb_find_init_mode(struct fb_info *info) +{ + struct fb_videomode mode; + struct fb_var_screeninfo var; + struct fb_monspecs *specs = &info->monspecs; + int found = 0; +#ifdef CONFIG_FB_I810_I2C + int i; + int err = 1; + struct i810fb_par *par = info->par; +#endif + + INIT_LIST_HEAD(&info->modelist); + memset(&mode, 0, sizeof(struct fb_videomode)); + var = info->var; +#ifdef CONFIG_FB_I810_I2C + i810_create_i2c_busses(par); + + for (i = 0; i < par->ddc_num + 1; i++) { + err = i810_probe_i2c_connector(info, &par->edid, i); + if (!err) + break; + } + + if (!err) + printk("i810fb_init_pci: DDC probe successful\n"); + + fb_edid_to_monspecs(par->edid, specs); + + if (specs->modedb == NULL) + printk("i810fb_init_pci: Unable to get Mode Database\n"); + + fb_videomode_to_modelist(specs->modedb, specs->modedb_len, + &info->modelist); + if (specs->modedb != NULL) { + const struct fb_videomode *m; + + if (xres && yres) { + if ((m = fb_find_best_mode(&var, &info->modelist))) { + mode = *m; + found = 1; + } + } + + if (!found) { + m = fb_find_best_display(&info->monspecs, &info->modelist); + mode = *m; + found = 1; + } + + fb_videomode_to_var(&var, &mode); + } +#endif + if (mode_option) + fb_find_mode(&var, info, mode_option, specs->modedb, + specs->modedb_len, (found) ? &mode : NULL, + info->var.bits_per_pixel); + + info->var = var; + fb_destroy_modedb(specs->modedb); + specs->modedb = NULL; +} + +#ifndef MODULE +static int i810fb_setup(char *options) +{ + char *this_opt, *suffix = NULL; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "mtrr", 4)) + mtrr = 1; + else if (!strncmp(this_opt, "accel", 5)) + accel = 1; + else if (!strncmp(this_opt, "extvga", 6)) + extvga = 1; + else if (!strncmp(this_opt, "sync", 4)) + sync = 1; + else if (!strncmp(this_opt, "vram:", 5)) + vram = (simple_strtoul(this_opt+5, NULL, 0)); + else if (!strncmp(this_opt, "voffset:", 8)) + voffset = (simple_strtoul(this_opt+8, NULL, 0)); + else if (!strncmp(this_opt, "xres:", 5)) + xres = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "yres:", 5)) + yres = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "vyres:", 6)) + vyres = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "bpp:", 4)) + bpp = simple_strtoul(this_opt+4, NULL, 0); + else if (!strncmp(this_opt, "hsync1:", 7)) { + hsync1 = simple_strtoul(this_opt+7, &suffix, 0); + if (strncmp(suffix, "H", 1)) + hsync1 *= 1000; + } else if (!strncmp(this_opt, "hsync2:", 7)) { + hsync2 = simple_strtoul(this_opt+7, &suffix, 0); + if (strncmp(suffix, "H", 1)) + hsync2 *= 1000; + } else if (!strncmp(this_opt, "vsync1:", 7)) + vsync1 = simple_strtoul(this_opt+7, NULL, 0); + else if (!strncmp(this_opt, "vsync2:", 7)) + vsync2 = simple_strtoul(this_opt+7, NULL, 0); + else if (!strncmp(this_opt, "dcolor", 6)) + dcolor = 1; + else if (!strncmp(this_opt, "ddc3", 4)) + ddc3 = true; + else + mode_option = this_opt; + } + return 0; +} +#endif + +static int i810fb_init_pci(struct pci_dev *dev, + const struct pci_device_id *entry) +{ + struct fb_info *info; + struct i810fb_par *par = NULL; + struct fb_videomode mode; + int err = -1, vfreq, hfreq, pixclock; + + info = framebuffer_alloc(sizeof(struct i810fb_par), &dev->dev); + if (!info) + return -ENOMEM; + + par = info->par; + par->dev = dev; + + if (!(info->pixmap.addr = kzalloc(8*1024, GFP_KERNEL))) { + i810fb_release_resource(info, par); + return -ENOMEM; + } + info->pixmap.size = 8*1024; + info->pixmap.buf_align = 8; + info->pixmap.access_align = 32; + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + if ((err = i810_allocate_pci_resource(par, entry))) { + i810fb_release_resource(info, par); + return err; + } + + i810_init_defaults(par, info); + + if ((err = i810_alloc_agp_mem(info))) { + i810fb_release_resource(info, par); + return err; + } + + i810_init_device(par); + + info->screen_base = par->fb.virtual; + info->fbops = &par->i810fb_ops; + info->pseudo_palette = par->pseudo_palette; + fb_alloc_cmap(&info->cmap, 256, 0); + i810fb_find_init_mode(info); + + if ((err = info->fbops->fb_check_var(&info->var, info))) { + i810fb_release_resource(info, par); + return err; + } + + fb_var_to_videomode(&mode, &info->var); + fb_add_videomode(&mode, &info->modelist); + + i810fb_init_ringbuffer(info); + err = register_framebuffer(info); + + if (err < 0) { + i810fb_release_resource(info, par); + printk("i810fb_init: cannot register framebuffer device\n"); + return err; + } + + pci_set_drvdata(dev, info); + pixclock = 1000000000/(info->var.pixclock); + pixclock *= 1000; + hfreq = pixclock/(info->var.xres + info->var.left_margin + + info->var.hsync_len + info->var.right_margin); + vfreq = hfreq/(info->var.yres + info->var.upper_margin + + info->var.vsync_len + info->var.lower_margin); + + printk("I810FB: fb%d : %s v%d.%d.%d%s\n" + "I810FB: Video RAM : %dK\n" + "I810FB: Monitor : H: %d-%d KHz V: %d-%d Hz\n" + "I810FB: Mode : %dx%d-%dbpp@%dHz\n", + info->node, + i810_pci_list[entry->driver_data], + VERSION_MAJOR, VERSION_MINOR, VERSION_TEENIE, BRANCH_VERSION, + (int) par->fb.size>>10, info->monspecs.hfmin/1000, + info->monspecs.hfmax/1000, info->monspecs.vfmin, + info->monspecs.vfmax, info->var.xres, + info->var.yres, info->var.bits_per_pixel, vfreq); + return 0; +} + +/*************************************************************** + * De-initialization * + ***************************************************************/ + +static void i810fb_release_resource(struct fb_info *info, + struct i810fb_par *par) +{ + struct gtt_data *gtt = &par->i810_gtt; + unset_mtrr(par); + + i810_delete_i2c_busses(par); + + if (par->i810_gtt.i810_cursor_memory) + agp_free_memory(gtt->i810_cursor_memory); + if (par->i810_gtt.i810_fb_memory) + agp_free_memory(gtt->i810_fb_memory); + + if (par->mmio_start_virtual) + iounmap(par->mmio_start_virtual); + if (par->aperture.virtual) + iounmap(par->aperture.virtual); + kfree(par->edid); + if (par->res_flags & FRAMEBUFFER_REQ) + release_mem_region(par->aperture.physical, + par->aperture.size); + if (par->res_flags & MMIO_REQ) + release_mem_region(par->mmio_start_phys, MMIO_SIZE); + + framebuffer_release(info); + +} + +static void __exit i810fb_remove_pci(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct i810fb_par *par = info->par; + + unregister_framebuffer(info); + i810fb_release_resource(info, par); + printk("cleanup_module: unloaded i810 framebuffer device\n"); +} + +#ifndef MODULE +static int i810fb_init(void) +{ + char *option = NULL; + + if (fb_get_options("i810fb", &option)) + return -ENODEV; + i810fb_setup(option); + + return pci_register_driver(&i810fb_driver); +} +#endif + +/********************************************************************* + * Modularization * + *********************************************************************/ + +#ifdef MODULE + +static int i810fb_init(void) +{ + hsync1 *= 1000; + hsync2 *= 1000; + + return pci_register_driver(&i810fb_driver); +} + +module_param(vram, int, 0); +MODULE_PARM_DESC(vram, "System RAM to allocate to framebuffer in MiB" + " (default=4)"); +module_param(voffset, int, 0); +MODULE_PARM_DESC(voffset, "at what offset to place start of framebuffer " + "memory (0 to maximum aperture size), in MiB (default = 48)"); +module_param(bpp, int, 0); +MODULE_PARM_DESC(bpp, "Color depth for display in bits per pixel" + " (default = 8)"); +module_param(xres, int, 0); +MODULE_PARM_DESC(xres, "Horizontal resolution in pixels (default = 640)"); +module_param(yres, int, 0); +MODULE_PARM_DESC(yres, "Vertical resolution in scanlines (default = 480)"); +module_param(vyres,int, 0); +MODULE_PARM_DESC(vyres, "Virtual vertical resolution in scanlines" + " (default = 480)"); +module_param(hsync1, int, 0); +MODULE_PARM_DESC(hsync1, "Minimum horizontal frequency of monitor in KHz" + " (default = 29)"); +module_param(hsync2, int, 0); +MODULE_PARM_DESC(hsync2, "Maximum horizontal frequency of monitor in KHz" + " (default = 30)"); +module_param(vsync1, int, 0); +MODULE_PARM_DESC(vsync1, "Minimum vertical frequency of monitor in Hz" + " (default = 50)"); +module_param(vsync2, int, 0); +MODULE_PARM_DESC(vsync2, "Maximum vertical frequency of monitor in Hz" + " (default = 60)"); +module_param(accel, bool, 0); +MODULE_PARM_DESC(accel, "Use Acceleration (BLIT) engine (default = 0)"); +module_param(mtrr, bool, 0); +MODULE_PARM_DESC(mtrr, "Use MTRR (default = 0)"); +module_param(extvga, bool, 0); +MODULE_PARM_DESC(extvga, "Enable external VGA connector (default = 0)"); +module_param(sync, bool, 0); +MODULE_PARM_DESC(sync, "wait for accel engine to finish drawing" + " (default = 0)"); +module_param(dcolor, bool, 0); +MODULE_PARM_DESC(dcolor, "use DirectColor visuals" + " (default = 0 = TrueColor)"); +module_param(ddc3, bool, 0); +MODULE_PARM_DESC(ddc3, "Probe DDC bus 3 (default = 0 = no)"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Specify initial video mode"); + +MODULE_AUTHOR("Tony A. Daplas"); +MODULE_DESCRIPTION("Framebuffer device for the Intel 810/815 and" + " compatible cards"); +MODULE_LICENSE("GPL"); + +static void __exit i810fb_exit(void) +{ + pci_unregister_driver(&i810fb_driver); +} +module_exit(i810fb_exit); + +#endif /* MODULE */ + +module_init(i810fb_init); diff --git a/drivers/video/fbdev/i810/i810_main.h b/drivers/video/fbdev/i810/i810_main.h new file mode 100644 index 000000000000..a25afaa534ba --- /dev/null +++ b/drivers/video/fbdev/i810/i810_main.h @@ -0,0 +1,95 @@ +/*-*- linux-c -*- + * linux/drivers/video/i810fb_main.h -- Intel 810 frame buffer device + * main header file + * + * Copyright (C) 2001 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * + * 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. + */ + +#ifndef __I810_MAIN_H__ +#define __I810_MAIN_H__ + +/* Video Timings */ +extern void round_off_xres (u32 *xres); +extern void round_off_yres (u32 *xres, u32 *yres); +extern u32 i810_get_watermark (const struct fb_var_screeninfo *var, + struct i810fb_par *par); +extern void i810fb_encode_registers(const struct fb_var_screeninfo *var, + struct i810fb_par *par, u32 xres, u32 yres); +extern void i810fb_fill_var_timings(struct fb_var_screeninfo *var); + +/* Accelerated Functions */ +extern void i810fb_fillrect (struct fb_info *p, + const struct fb_fillrect *rect); +extern void i810fb_copyarea (struct fb_info *p, + const struct fb_copyarea *region); +extern void i810fb_imageblit(struct fb_info *p, const struct fb_image *image); +extern int i810fb_sync (struct fb_info *p); + +extern void i810fb_init_ringbuffer(struct fb_info *info); +extern void i810fb_load_front (u32 offset, struct fb_info *info); + +#ifdef CONFIG_FB_I810_I2C +/* I2C */ +extern int i810_probe_i2c_connector(struct fb_info *info, u8 **out_edid, + int conn); +extern void i810_create_i2c_busses(struct i810fb_par *par); +extern void i810_delete_i2c_busses(struct i810fb_par *par); +#else +static inline int i810_probe_i2c_connector(struct fb_info *info, u8 **out_edid, + int conn) +{ + return 1; +} +static inline void i810_create_i2c_busses(struct i810fb_par *par) { } +static inline void i810_delete_i2c_busses(struct i810fb_par *par) { } +#endif + +/* Conditionals */ +#ifdef CONFIG_X86 +static inline void flush_cache(void) +{ + asm volatile ("wbinvd":::"memory"); +} +#else +#define flush_cache() do { } while(0) +#endif + +#ifdef CONFIG_MTRR + +#include <asm/mtrr.h> + +static inline void set_mtrr(struct i810fb_par *par) +{ + par->mtrr_reg = mtrr_add((u32) par->aperture.physical, + par->aperture.size, MTRR_TYPE_WRCOMB, 1); + if (par->mtrr_reg < 0) { + printk(KERN_ERR "set_mtrr: unable to set MTRR\n"); + return; + } + par->dev_flags |= HAS_MTRR; +} +static inline void unset_mtrr(struct i810fb_par *par) +{ + if (par->dev_flags & HAS_MTRR) + mtrr_del(par->mtrr_reg, (u32) par->aperture.physical, + par->aperture.size); +} +#else +#define set_mtrr(x) printk("set_mtrr: MTRR is disabled in the kernel\n") + +#define unset_mtrr(x) do { } while (0) +#endif /* CONFIG_MTRR */ + +#ifdef CONFIG_FB_I810_GTF +#define IS_DVT (0) +#else +#define IS_DVT (1) +#endif + +#endif /* __I810_MAIN_H__ */ diff --git a/drivers/video/fbdev/i810/i810_regs.h b/drivers/video/fbdev/i810/i810_regs.h new file mode 100644 index 000000000000..91c6bd9d0d0d --- /dev/null +++ b/drivers/video/fbdev/i810/i810_regs.h @@ -0,0 +1,275 @@ +/*-*- linux-c -*- + * linux/drivers/video/i810_regs.h -- Intel 810/815 Register List + * + * Copyright (C) 2001 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * + * 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. + */ + + +/* + * Intel 810 Chipset Family PRM 15 3.1 + * GC Register Memory Address Map + * + * Based on: + * Intel (R) 810 Chipset Family + * Programmer s Reference Manual + * November 1999 + * Revision 1.0 + * Order Number: 298026-001 R + * + * All GC registers are memory-mapped. In addition, the VGA and extended VGA registers + * are I/O mapped. + */ + +#ifndef __I810_REGS_H__ +#define __I810_REGS_H__ + +/* Instruction and Interrupt Control Registers (01000h 02FFFh) */ +#define FENCE 0x02000 +#define PGTBL_CTL 0x02020 +#define PGTBL_ER 0x02024 +#define LRING 0x02030 +#define IRING 0x02040 +#define HWS_PGA 0x02080 +#define IPEIR 0x02088 +#define IPEHR 0x0208C +#define INSTDONE 0x02090 +#define NOPID 0x02094 +#define HWSTAM 0x02098 +#define IER 0x020A0 +#define IIR 0x020A4 +#define IMR 0x020A8 +#define ISR 0x020AC +#define EIR 0x020B0 +#define EMR 0x020B4 +#define ESR 0x020B8 +#define INSTPM 0x020C0 +#define INSTPS 0x020C4 +#define BBP_PTR 0x020C8 +#define ABB_SRT 0x020CC +#define ABB_END 0x020D0 +#define DMA_FADD 0x020D4 +#define FW_BLC 0x020D8 +#define MEM_MODE 0x020DC + +/* Memory Control Registers (03000h 03FFFh) */ +#define DRT 0x03000 +#define DRAMCL 0x03001 +#define DRAMCH 0x03002 + + +/* Span Cursor Registers (04000h 04FFFh) */ +#define UI_SC_CTL 0x04008 + +/* I/O Control Registers (05000h 05FFFh) */ +#define HVSYNC 0x05000 +#define GPIOA 0x05010 +#define GPIOB 0x05014 +#define GPIOC 0x0501C + +/* Clock Control and Power Management Registers (06000h 06FFFh) */ +#define DCLK_0D 0x06000 +#define DCLK_1D 0x06004 +#define DCLK_2D 0x06008 +#define LCD_CLKD 0x0600C +#define DCLK_0DS 0x06010 +#define PWR_CLKC 0x06014 + +/* Graphics Translation Table Range Definition (10000h 1FFFFh) */ +#define GTT 0x10000 + +/* Overlay Registers (30000h 03FFFFh) */ +#define OVOADDR 0x30000 +#define DOVOSTA 0x30008 +#define GAMMA 0x30010 +#define OBUF_0Y 0x30100 +#define OBUF_1Y 0x30104 +#define OBUF_0U 0x30108 +#define OBUF_0V 0x3010C +#define OBUF_1U 0x30110 +#define OBUF_1V 0x30114 +#define OVOSTRIDE 0x30118 +#define YRGB_VPH 0x3011C +#define UV_VPH 0x30120 +#define HORZ_PH 0x30124 +#define INIT_PH 0x30128 +#define DWINPOS 0x3012C +#define DWINSZ 0x30130 +#define SWID 0x30134 +#define SWIDQW 0x30138 +#define SHEIGHT 0x3013F +#define YRGBSCALE 0x30140 +#define UVSCALE 0x30144 +#define OVOCLRCO 0x30148 +#define OVOCLRC1 0x3014C +#define DCLRKV 0x30150 +#define DLCRKM 0x30154 +#define SCLRKVH 0x30158 +#define SCLRKVL 0x3015C +#define SCLRKM 0x30160 +#define OVOCONF 0x30164 +#define OVOCMD 0x30168 +#define AWINPOS 0x30170 +#define AWINZ 0x30174 + +/* BLT Engine Status (40000h 4FFFFh) (Software Debug) */ +#define BR00 0x40000 +#define BRO1 0x40004 +#define BR02 0x40008 +#define BR03 0x4000C +#define BR04 0x40010 +#define BR05 0x40014 +#define BR06 0x40018 +#define BR07 0x4001C +#define BR08 0x40020 +#define BR09 0x40024 +#define BR10 0x40028 +#define BR11 0x4002C +#define BR12 0x40030 +#define BR13 0x40034 +#define BR14 0x40038 +#define BR15 0x4003C +#define BR16 0x40040 +#define BR17 0x40044 +#define BR18 0x40048 +#define BR19 0x4004C +#define SSLADD 0x40074 +#define DSLH 0x40078 +#define DSLRADD 0x4007C + + +/* LCD/TV-Out and HW DVD Registers (60000h 6FFFFh) */ +/* LCD/TV-Out */ +#define HTOTAL 0x60000 +#define HBLANK 0x60004 +#define HSYNC 0x60008 +#define VTOTAL 0x6000C +#define VBLANK 0x60010 +#define VSYNC 0x60014 +#define LCDTV_C 0x60018 +#define OVRACT 0x6001C +#define BCLRPAT 0x60020 + +/* Display and Cursor Control Registers (70000h 7FFFFh) */ +#define DISP_SL 0x70000 +#define DISP_SLC 0x70004 +#define PIXCONF 0x70008 +#define PIXCONF1 0x70009 +#define BLTCNTL 0x7000C +#define SWF 0x70014 +#define DPLYBASE 0x70020 +#define DPLYSTAS 0x70024 +#define CURCNTR 0x70080 +#define CURBASE 0x70084 +#define CURPOS 0x70088 + + +/* VGA Registers */ + +/* SMRAM Registers */ +#define SMRAM 0x10 + +/* Graphics Control Registers */ +#define GR_INDEX 0x3CE +#define GR_DATA 0x3CF + +#define GR10 0x10 +#define GR11 0x11 + +/* CRT Controller Registers */ +#define CR_INDEX_MDA 0x3B4 +#define CR_INDEX_CGA 0x3D4 +#define CR_DATA_MDA 0x3B5 +#define CR_DATA_CGA 0x3D5 + +#define CR30 0x30 +#define CR31 0x31 +#define CR32 0x32 +#define CR33 0x33 +#define CR35 0x35 +#define CR39 0x39 +#define CR40 0x40 +#define CR41 0x41 +#define CR42 0x42 +#define CR70 0x70 +#define CR80 0x80 +#define CR81 0x82 + +/* Extended VGA Registers */ + +/* General Control and Status Registers */ +#define ST00 0x3C2 +#define ST01_MDA 0x3BA +#define ST01_CGA 0x3DA +#define FRC_READ 0x3CA +#define FRC_WRITE_MDA 0x3BA +#define FRC_WRITE_CGA 0x3DA +#define MSR_READ 0x3CC +#define MSR_WRITE 0x3C2 + +/* Sequencer Registers */ +#define SR_INDEX 0x3C4 +#define SR_DATA 0x3C5 + +#define SR01 0x01 +#define SR02 0x02 +#define SR03 0x03 +#define SR04 0x04 +#define SR07 0x07 + +/* Graphics Controller Registers */ +#define GR00 0x00 +#define GR01 0x01 +#define GR02 0x02 +#define GR03 0x03 +#define GR04 0x04 +#define GR05 0x05 +#define GR06 0x06 +#define GR07 0x07 +#define GR08 0x08 + +/* Attribute Controller Registers */ +#define ATTR_WRITE 0x3C0 +#define ATTR_READ 0x3C1 + +/* VGA Color Palette Registers */ + +/* CLUT */ +#define CLUT_DATA 0x3C9 /* DACDATA */ +#define CLUT_INDEX_READ 0x3C7 /* DACRX */ +#define CLUT_INDEX_WRITE 0x3C8 /* DACWX */ +#define DACMASK 0x3C6 + +/* CRT Controller Registers */ +#define CR00 0x00 +#define CR01 0x01 +#define CR02 0x02 +#define CR03 0x03 +#define CR04 0x04 +#define CR05 0x05 +#define CR06 0x06 +#define CR07 0x07 +#define CR08 0x08 +#define CR09 0x09 +#define CR0A 0x0A +#define CR0B 0x0B +#define CR0C 0x0C +#define CR0D 0x0D +#define CR0E 0x0E +#define CR0F 0x0F +#define CR10 0x10 +#define CR11 0x11 +#define CR12 0x12 +#define CR13 0x13 +#define CR14 0x14 +#define CR15 0x15 +#define CR16 0x16 +#define CR17 0x17 +#define CR18 0x18 + +#endif /* __I810_REGS_H__ */ diff --git a/drivers/video/fbdev/igafb.c b/drivers/video/fbdev/igafb.c new file mode 100644 index 000000000000..486f18897414 --- /dev/null +++ b/drivers/video/fbdev/igafb.c @@ -0,0 +1,579 @@ +/* + * linux/drivers/video/igafb.c -- Frame buffer device for IGA 1682 + * + * Copyright (C) 1998 Vladimir Roganov and Gleb Raiko + * + * This driver is partly based on the Frame buffer device for ATI Mach64 + * and partially on VESA-related code. + * + * Copyright (C) 1997-1998 Geert Uytterhoeven + * Copyright (C) 1998 Bernd Harries + * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be) + * + * 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: + Despite of IGA Card has advanced graphic acceleration, + initial version is almost dummy and does not support it. + Support for video modes and acceleration must be added + together with accelerated X-Windows driver implementation. + + Most important thing at this moment is that we have working + JavaEngine1 console & X with new console interface. + +******************************************************************************/ + +#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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/nvram.h> + +#include <asm/io.h> + +#ifdef CONFIG_SPARC +#include <asm/prom.h> +#include <asm/pcic.h> +#endif + +#include <video/iga.h> + +struct pci_mmap_map { + unsigned long voff; + unsigned long poff; + unsigned long size; + unsigned long prot_flag; + unsigned long prot_mask; +}; + +struct iga_par { + struct pci_mmap_map *mmap_map; + unsigned long frame_buffer_phys; + unsigned long io_base; +}; + +struct fb_info fb_info; + +struct fb_fix_screeninfo igafb_fix __initdata = { + .id = "IGA 1682", + .type = FB_TYPE_PACKED_PIXELS, + .mmio_len = 1000 +}; + +struct fb_var_screeninfo default_var = { + /* 640x480, 60 Hz, Non-Interlaced (25.175 MHz dotclock) */ + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 8, + .red = {0, 8, 0 }, + .green = {0, 8, 0 }, + .blue = {0, 8, 0 }, + .height = -1, + .width = -1, + .accel_flags = FB_ACCEL_NONE, + .pixclock = 39722, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED +}; + +#ifdef CONFIG_SPARC +struct fb_var_screeninfo default_var_1024x768 __initdata = { + /* 1024x768, 75 Hz, Non-Interlaced (78.75 MHz dotclock) */ + .xres = 1024, + .yres = 768, + .xres_virtual = 1024, + .yres_virtual = 768, + .bits_per_pixel = 8, + .red = {0, 8, 0 }, + .green = {0, 8, 0 }, + .blue = {0, 8, 0 }, + .height = -1, + .width = -1, + .accel_flags = FB_ACCEL_NONE, + .pixclock = 12699, + .left_margin = 176, + .right_margin = 16, + .upper_margin = 28, + .lower_margin = 1, + .hsync_len = 96, + .vsync_len = 3, + .vmode = FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED +}; + +struct fb_var_screeninfo default_var_1152x900 __initdata = { + /* 1152x900, 76 Hz, Non-Interlaced (110.0 MHz dotclock) */ + .xres = 1152, + .yres = 900, + .xres_virtual = 1152, + .yres_virtual = 900, + .bits_per_pixel = 8, + .red = { 0, 8, 0 }, + .green = { 0, 8, 0 }, + .blue = { 0, 8, 0 }, + .height = -1, + .width = -1, + .accel_flags = FB_ACCEL_NONE, + .pixclock = 9091, + .left_margin = 234, + .right_margin = 24, + .upper_margin = 34, + .lower_margin = 3, + .hsync_len = 100, + .vsync_len = 3, + .vmode = FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED +}; + +struct fb_var_screeninfo default_var_1280x1024 __initdata = { + /* 1280x1024, 75 Hz, Non-Interlaced (135.00 MHz dotclock) */ + .xres = 1280, + .yres = 1024, + .xres_virtual = 1280, + .yres_virtual = 1024, + .bits_per_pixel = 8, + .red = {0, 8, 0 }, + .green = {0, 8, 0 }, + .blue = {0, 8, 0 }, + .height = -1, + .width = -1, + .accel_flags = 0, + .pixclock = 7408, + .left_margin = 248, + .right_margin = 16, + .upper_margin = 38, + .lower_margin = 1, + .hsync_len = 144, + .vsync_len = 3, + .vmode = FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED +}; + +/* + * Memory-mapped I/O functions for Sparc PCI + * + * On sparc we happen to access I/O with memory mapped functions too. + */ +#define pci_inb(par, reg) readb(par->io_base+(reg)) +#define pci_outb(par, val, reg) writeb(val, par->io_base+(reg)) + +static inline unsigned int iga_inb(struct iga_par *par, unsigned int reg, + unsigned int idx) +{ + pci_outb(par, idx, reg); + return pci_inb(par, reg + 1); +} + +static inline void iga_outb(struct iga_par *par, unsigned char val, + unsigned int reg, unsigned int idx ) +{ + pci_outb(par, idx, reg); + pci_outb(par, val, reg+1); +} + +#endif /* CONFIG_SPARC */ + +/* + * Very important functionality for the JavaEngine1 computer: + * make screen border black (usign special IGA registers) + */ +static void iga_blank_border(struct iga_par *par) +{ + int i; +#if 0 + /* + * PROM does this for us, so keep this code as a reminder + * about required read from 0x3DA and writing of 0x20 in the end. + */ + (void) pci_inb(par, 0x3DA); /* required for every access */ + pci_outb(par, IGA_IDX_VGA_OVERSCAN, IGA_ATTR_CTL); + (void) pci_inb(par, IGA_ATTR_CTL+1); + pci_outb(par, 0x38, IGA_ATTR_CTL); + pci_outb(par, 0x20, IGA_ATTR_CTL); /* re-enable visual */ +#endif + /* + * This does not work as it was designed because the overscan + * color is looked up in the palette. Therefore, under X11 + * overscan changes color. + */ + for (i=0; i < 3; i++) + iga_outb(par, 0, IGA_EXT_CNTRL, IGA_IDX_OVERSCAN_COLOR + i); +} + +#ifdef CONFIG_SPARC +static int igafb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + struct iga_par *par = (struct iga_par *)info->par; + unsigned int size, page, map_size = 0; + unsigned long map_offset = 0; + int i; + + if (!par->mmap_map) + return -ENXIO; + + size = vma->vm_end - vma->vm_start; + + /* Each page, see which map applies */ + for (page = 0; page < size; ) { + map_size = 0; + for (i = 0; par->mmap_map[i].size; i++) { + unsigned long start = par->mmap_map[i].voff; + unsigned long end = start + par->mmap_map[i].size; + unsigned long offset = (vma->vm_pgoff << PAGE_SHIFT) + page; + + if (start > offset) + continue; + if (offset >= end) + continue; + + map_size = par->mmap_map[i].size - (offset - start); + map_offset = par->mmap_map[i].poff + (offset - start); + break; + } + if (!map_size) { + page += PAGE_SIZE; + continue; + } + if (page + map_size > size) + map_size = size - page; + + pgprot_val(vma->vm_page_prot) &= ~(par->mmap_map[i].prot_mask); + pgprot_val(vma->vm_page_prot) |= par->mmap_map[i].prot_flag; + + if (remap_pfn_range(vma, vma->vm_start + page, + map_offset >> PAGE_SHIFT, map_size, vma->vm_page_prot)) + return -EAGAIN; + + page += map_size; + } + + if (!map_size) + return -EINVAL; + + vma->vm_flags |= VM_IO; + return 0; +} +#endif /* CONFIG_SPARC */ + +static int igafb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + struct iga_par *par = (struct iga_par *)info->par; + + if (regno >= info->cmap.len) + return 1; + + pci_outb(par, regno, DAC_W_INDEX); + pci_outb(par, red, DAC_DATA); + pci_outb(par, green, DAC_DATA); + pci_outb(par, blue, DAC_DATA); + + if (regno < 16) { + switch (info->var.bits_per_pixel) { + case 16: + ((u16*)(info->pseudo_palette))[regno] = + (regno << 10) | (regno << 5) | regno; + break; + case 24: + ((u32*)(info->pseudo_palette))[regno] = + (regno << 16) | (regno << 8) | regno; + break; + case 32: + { int i; + i = (regno << 8) | regno; + ((u32*)(info->pseudo_palette))[regno] = (i << 16) | i; + } + break; + } + } + return 0; +} + +/* + * Framebuffer option structure + */ +static struct fb_ops igafb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = igafb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +#ifdef CONFIG_SPARC + .fb_mmap = igafb_mmap, +#endif +}; + +static int __init iga_init(struct fb_info *info, struct iga_par *par) +{ + char vramsz = iga_inb(par, IGA_EXT_CNTRL, IGA_IDX_EXT_BUS_CNTL) + & MEM_SIZE_ALIAS; + int video_cmap_len; + + switch (vramsz) { + case MEM_SIZE_1M: + info->fix.smem_len = 0x100000; + break; + case MEM_SIZE_2M: + info->fix.smem_len = 0x200000; + break; + case MEM_SIZE_4M: + case MEM_SIZE_RESERVED: + info->fix.smem_len = 0x400000; + break; + } + + if (info->var.bits_per_pixel > 8) + video_cmap_len = 16; + else + video_cmap_len = 256; + + info->fbops = &igafb_ops; + info->flags = FBINFO_DEFAULT; + + fb_alloc_cmap(&info->cmap, video_cmap_len, 0); + + if (register_framebuffer(info) < 0) + return 0; + + fb_info(info, "%s frame buffer device at 0x%08lx [%dMB VRAM]\n", + info->fix.id, par->frame_buffer_phys, info->fix.smem_len >> 20); + + iga_blank_border(par); + return 1; +} + +static int __init igafb_init(void) +{ + struct fb_info *info; + struct pci_dev *pdev; + struct iga_par *par; + unsigned long addr; + int size, iga2000 = 0; + + if (fb_get_options("igafb", NULL)) + return -ENODEV; + + pdev = pci_get_device(PCI_VENDOR_ID_INTERG, + PCI_DEVICE_ID_INTERG_1682, 0); + if (pdev == NULL) { + /* + * XXX We tried to use cyber2000fb.c for IGS 2000. + * But it does not initialize the chip in JavaStation-E, alas. + */ + pdev = pci_get_device(PCI_VENDOR_ID_INTERG, 0x2000, 0); + if(pdev == NULL) { + return -ENXIO; + } + iga2000 = 1; + } + /* We leak a reference here but as it cannot be unloaded this is + fine. If you write unload code remember to free it in unload */ + + size = sizeof(struct iga_par) + sizeof(u32)*16; + + info = framebuffer_alloc(size, &pdev->dev); + if (!info) { + printk("igafb_init: can't alloc fb_info\n"); + pci_dev_put(pdev); + return -ENOMEM; + } + + par = info->par; + + if ((addr = pdev->resource[0].start) == 0) { + printk("igafb_init: no memory start\n"); + kfree(info); + pci_dev_put(pdev); + return -ENXIO; + } + + if ((info->screen_base = ioremap(addr, 1024*1024*2)) == 0) { + printk("igafb_init: can't remap %lx[2M]\n", addr); + kfree(info); + pci_dev_put(pdev); + return -ENXIO; + } + + par->frame_buffer_phys = addr & PCI_BASE_ADDRESS_MEM_MASK; + +#ifdef CONFIG_SPARC + /* + * The following is sparc specific and this is why: + * + * IGS2000 has its I/O memory mapped and we want + * to generate memory cycles on PCI, e.g. do ioremap(), + * then readb/writeb() as in Documentation/io-mapping.txt. + * + * IGS1682 is more traditional, it responds to PCI I/O + * cycles, so we want to access it with inb()/outb(). + * + * On sparc, PCIC converts CPU memory access within + * phys window 0x3000xxxx into PCI I/O cycles. Therefore + * we may use readb/writeb to access them with IGS1682. + * + * We do not take io_base_phys from resource[n].start + * on IGS1682 because that chip is BROKEN. It does not + * have a base register for I/O. We just "know" what its + * I/O addresses are. + */ + if (iga2000) { + igafb_fix.mmio_start = par->frame_buffer_phys | 0x00800000; + } else { + igafb_fix.mmio_start = 0x30000000; /* XXX */ + } + if ((par->io_base = (int) ioremap(igafb_fix.mmio_start, igafb_fix.smem_len)) == 0) { + printk("igafb_init: can't remap %lx[4K]\n", igafb_fix.mmio_start); + iounmap((void *)info->screen_base); + kfree(info); + pci_dev_put(pdev); + return -ENXIO; + } + + /* + * Figure mmap addresses from PCI config space. + * We need two regions: for video memory and for I/O ports. + * Later one can add region for video coprocessor registers. + * However, mmap routine loops until size != 0, so we put + * one additional region with size == 0. + */ + + par->mmap_map = kzalloc(4 * sizeof(*par->mmap_map), GFP_ATOMIC); + if (!par->mmap_map) { + printk("igafb_init: can't alloc mmap_map\n"); + iounmap((void *)par->io_base); + iounmap(info->screen_base); + kfree(info); + pci_dev_put(pdev); + return -ENOMEM; + } + + /* + * Set default vmode and cmode from PROM properties. + */ + { + struct device_node *dp = pci_device_to_OF_node(pdev); + int node = dp->node; + int width = prom_getintdefault(node, "width", 1024); + int height = prom_getintdefault(node, "height", 768); + int depth = prom_getintdefault(node, "depth", 8); + switch (width) { + case 1024: + if (height == 768) + default_var = default_var_1024x768; + break; + case 1152: + if (height == 900) + default_var = default_var_1152x900; + break; + case 1280: + if (height == 1024) + default_var = default_var_1280x1024; + break; + default: + break; + } + + switch (depth) { + case 8: + default_var.bits_per_pixel = 8; + break; + case 16: + default_var.bits_per_pixel = 16; + break; + case 24: + default_var.bits_per_pixel = 24; + break; + case 32: + default_var.bits_per_pixel = 32; + break; + default: + break; + } + } + +#endif + igafb_fix.smem_start = (unsigned long) info->screen_base; + igafb_fix.line_length = default_var.xres*(default_var.bits_per_pixel/8); + igafb_fix.visual = default_var.bits_per_pixel <= 8 ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + + info->var = default_var; + info->fix = igafb_fix; + info->pseudo_palette = (void *)(par + 1); + + if (!iga_init(info, par)) { + iounmap((void *)par->io_base); + iounmap(info->screen_base); + kfree(par->mmap_map); + kfree(info); + return -ENODEV; + } + +#ifdef CONFIG_SPARC + /* + * Add /dev/fb mmap values. + */ + + /* First region is for video memory */ + par->mmap_map[0].voff = 0x0; + par->mmap_map[0].poff = par->frame_buffer_phys & PAGE_MASK; + par->mmap_map[0].size = info->fix.smem_len & PAGE_MASK; + par->mmap_map[0].prot_mask = SRMMU_CACHE; + par->mmap_map[0].prot_flag = SRMMU_WRITE; + + /* Second region is for I/O ports */ + par->mmap_map[1].voff = par->frame_buffer_phys & PAGE_MASK; + par->mmap_map[1].poff = info->fix.smem_start & PAGE_MASK; + par->mmap_map[1].size = PAGE_SIZE * 2; /* X wants 2 pages */ + par->mmap_map[1].prot_mask = SRMMU_CACHE; + par->mmap_map[1].prot_flag = SRMMU_WRITE; +#endif /* CONFIG_SPARC */ + + return 0; +} + +static int __init igafb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + } + return 0; +} + +module_init(igafb_init); +MODULE_LICENSE("GPL"); +static struct pci_device_id igafb_pci_tbl[] = { + { PCI_VENDOR_ID_INTERG, PCI_DEVICE_ID_INTERG_1682, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { } +}; + +MODULE_DEVICE_TABLE(pci, igafb_pci_tbl); diff --git a/drivers/video/fbdev/imsttfb.c b/drivers/video/fbdev/imsttfb.c new file mode 100644 index 000000000000..aae10ce74f14 --- /dev/null +++ b/drivers/video/fbdev/imsttfb.c @@ -0,0 +1,1626 @@ +/* + * drivers/video/imsttfb.c -- frame buffer device for IMS TwinTurbo + * + * This file is derived from the powermac console "imstt" driver: + * Copyright (C) 1997 Sigurdur Asgeirsson + * With additional hacking by Jeffrey Kuskin (jsk@mojave.stanford.edu) + * Modified by Danilo Beuche 1998 + * Some register values added by Damien Doligez, INRIA Rocquencourt + * Various cleanups by Paul Mundt (lethal@chaoticdreams.org) + * + * This file was written by Ryan Nielsen (ran@krazynet.com) + * Most of the frame buffer device stuff was copied from atyfb.c + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <asm/io.h> +#include <linux/uaccess.h> + +#if defined(CONFIG_PPC) +#include <linux/nvram.h> +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#include "macmodes.h" +#endif + +#ifndef __powerpc__ +#define eieio() /* Enforce In-order Execution of I/O */ +#endif + +/* TwinTurbo (Cosmo) registers */ +enum { + S1SA = 0, /* 0x00 */ + S2SA = 1, /* 0x04 */ + SP = 2, /* 0x08 */ + DSA = 3, /* 0x0C */ + CNT = 4, /* 0x10 */ + DP_OCTL = 5, /* 0x14 */ + CLR = 6, /* 0x18 */ + BI = 8, /* 0x20 */ + MBC = 9, /* 0x24 */ + BLTCTL = 10, /* 0x28 */ + + /* Scan Timing Generator Registers */ + HES = 12, /* 0x30 */ + HEB = 13, /* 0x34 */ + HSB = 14, /* 0x38 */ + HT = 15, /* 0x3C */ + VES = 16, /* 0x40 */ + VEB = 17, /* 0x44 */ + VSB = 18, /* 0x48 */ + VT = 19, /* 0x4C */ + HCIV = 20, /* 0x50 */ + VCIV = 21, /* 0x54 */ + TCDR = 22, /* 0x58 */ + VIL = 23, /* 0x5C */ + STGCTL = 24, /* 0x60 */ + + /* Screen Refresh Generator Registers */ + SSR = 25, /* 0x64 */ + HRIR = 26, /* 0x68 */ + SPR = 27, /* 0x6C */ + CMR = 28, /* 0x70 */ + SRGCTL = 29, /* 0x74 */ + + /* RAM Refresh Generator Registers */ + RRCIV = 30, /* 0x78 */ + RRSC = 31, /* 0x7C */ + RRCR = 34, /* 0x88 */ + + /* System Registers */ + GIOE = 32, /* 0x80 */ + GIO = 33, /* 0x84 */ + SCR = 35, /* 0x8C */ + SSTATUS = 36, /* 0x90 */ + PRC = 37, /* 0x94 */ + +#if 0 + /* PCI Registers */ + DVID = 0x00000000L, + SC = 0x00000004L, + CCR = 0x00000008L, + OG = 0x0000000CL, + BARM = 0x00000010L, + BARER = 0x00000030L, +#endif +}; + +/* IBM 624 RAMDAC Direct Registers */ +enum { + PADDRW = 0x00, + PDATA = 0x04, + PPMASK = 0x08, + PADDRR = 0x0c, + PIDXLO = 0x10, + PIDXHI = 0x14, + PIDXDATA= 0x18, + PIDXCTL = 0x1c +}; + +/* IBM 624 RAMDAC Indirect Registers */ +enum { + CLKCTL = 0x02, /* (0x01) Miscellaneous Clock Control */ + SYNCCTL = 0x03, /* (0x00) Sync Control */ + HSYNCPOS = 0x04, /* (0x00) Horizontal Sync Position */ + PWRMNGMT = 0x05, /* (0x00) Power Management */ + DACOP = 0x06, /* (0x02) DAC Operation */ + PALETCTL = 0x07, /* (0x00) Palette Control */ + SYSCLKCTL = 0x08, /* (0x01) System Clock Control */ + PIXFMT = 0x0a, /* () Pixel Format [bpp >> 3 + 2] */ + BPP8 = 0x0b, /* () 8 Bits/Pixel Control */ + BPP16 = 0x0c, /* () 16 Bits/Pixel Control [bit 1=1 for 565] */ + BPP24 = 0x0d, /* () 24 Bits/Pixel Control */ + BPP32 = 0x0e, /* () 32 Bits/Pixel Control */ + PIXCTL1 = 0x10, /* (0x05) Pixel PLL Control 1 */ + PIXCTL2 = 0x11, /* (0x00) Pixel PLL Control 2 */ + SYSCLKN = 0x15, /* () System Clock N (System PLL Reference Divider) */ + SYSCLKM = 0x16, /* () System Clock M (System PLL VCO Divider) */ + SYSCLKP = 0x17, /* () System Clock P */ + SYSCLKC = 0x18, /* () System Clock C */ + /* + * Dot clock rate is 20MHz * (m + 1) / ((n + 1) * (p ? 2 * p : 1) + * c is charge pump bias which depends on the VCO frequency + */ + PIXM0 = 0x20, /* () Pixel M 0 */ + PIXN0 = 0x21, /* () Pixel N 0 */ + PIXP0 = 0x22, /* () Pixel P 0 */ + PIXC0 = 0x23, /* () Pixel C 0 */ + CURSCTL = 0x30, /* (0x00) Cursor Control */ + CURSXLO = 0x31, /* () Cursor X position, low 8 bits */ + CURSXHI = 0x32, /* () Cursor X position, high 8 bits */ + CURSYLO = 0x33, /* () Cursor Y position, low 8 bits */ + CURSYHI = 0x34, /* () Cursor Y position, high 8 bits */ + CURSHOTX = 0x35, /* () Cursor Hot Spot X */ + CURSHOTY = 0x36, /* () Cursor Hot Spot Y */ + CURSACCTL = 0x37, /* () Advanced Cursor Control Enable */ + CURSACATTR = 0x38, /* () Advanced Cursor Attribute */ + CURS1R = 0x40, /* () Cursor 1 Red */ + CURS1G = 0x41, /* () Cursor 1 Green */ + CURS1B = 0x42, /* () Cursor 1 Blue */ + CURS2R = 0x43, /* () Cursor 2 Red */ + CURS2G = 0x44, /* () Cursor 2 Green */ + CURS2B = 0x45, /* () Cursor 2 Blue */ + CURS3R = 0x46, /* () Cursor 3 Red */ + CURS3G = 0x47, /* () Cursor 3 Green */ + CURS3B = 0x48, /* () Cursor 3 Blue */ + BORDR = 0x60, /* () Border Color Red */ + BORDG = 0x61, /* () Border Color Green */ + BORDB = 0x62, /* () Border Color Blue */ + MISCTL1 = 0x70, /* (0x00) Miscellaneous Control 1 */ + MISCTL2 = 0x71, /* (0x00) Miscellaneous Control 2 */ + MISCTL3 = 0x72, /* (0x00) Miscellaneous Control 3 */ + KEYCTL = 0x78 /* (0x00) Key Control/DB Operation */ +}; + +/* TI TVP 3030 RAMDAC Direct Registers */ +enum { + TVPADDRW = 0x00, /* 0 Palette/Cursor RAM Write Address/Index */ + TVPPDATA = 0x04, /* 1 Palette Data RAM Data */ + TVPPMASK = 0x08, /* 2 Pixel Read-Mask */ + TVPPADRR = 0x0c, /* 3 Palette/Cursor RAM Read Address */ + TVPCADRW = 0x10, /* 4 Cursor/Overscan Color Write Address */ + TVPCDATA = 0x14, /* 5 Cursor/Overscan Color Data */ + /* 6 reserved */ + TVPCADRR = 0x1c, /* 7 Cursor/Overscan Color Read Address */ + /* 8 reserved */ + TVPDCCTL = 0x24, /* 9 Direct Cursor Control */ + TVPIDATA = 0x28, /* 10 Index Data */ + TVPCRDAT = 0x2c, /* 11 Cursor RAM Data */ + TVPCXPOL = 0x30, /* 12 Cursor-Position X LSB */ + TVPCXPOH = 0x34, /* 13 Cursor-Position X MSB */ + TVPCYPOL = 0x38, /* 14 Cursor-Position Y LSB */ + TVPCYPOH = 0x3c, /* 15 Cursor-Position Y MSB */ +}; + +/* TI TVP 3030 RAMDAC Indirect Registers */ +enum { + TVPIRREV = 0x01, /* Silicon Revision [RO] */ + TVPIRICC = 0x06, /* Indirect Cursor Control (0x00) */ + TVPIRBRC = 0x07, /* Byte Router Control (0xe4) */ + TVPIRLAC = 0x0f, /* Latch Control (0x06) */ + TVPIRTCC = 0x18, /* True Color Control (0x80) */ + TVPIRMXC = 0x19, /* Multiplex Control (0x98) */ + TVPIRCLS = 0x1a, /* Clock Selection (0x07) */ + TVPIRPPG = 0x1c, /* Palette Page (0x00) */ + TVPIRGEC = 0x1d, /* General Control (0x00) */ + TVPIRMIC = 0x1e, /* Miscellaneous Control (0x00) */ + TVPIRPLA = 0x2c, /* PLL Address */ + TVPIRPPD = 0x2d, /* Pixel Clock PLL Data */ + TVPIRMPD = 0x2e, /* Memory Clock PLL Data */ + TVPIRLPD = 0x2f, /* Loop Clock PLL Data */ + TVPIRCKL = 0x30, /* Color-Key Overlay Low */ + TVPIRCKH = 0x31, /* Color-Key Overlay High */ + TVPIRCRL = 0x32, /* Color-Key Red Low */ + TVPIRCRH = 0x33, /* Color-Key Red High */ + TVPIRCGL = 0x34, /* Color-Key Green Low */ + TVPIRCGH = 0x35, /* Color-Key Green High */ + TVPIRCBL = 0x36, /* Color-Key Blue Low */ + TVPIRCBH = 0x37, /* Color-Key Blue High */ + TVPIRCKC = 0x38, /* Color-Key Control (0x00) */ + TVPIRMLC = 0x39, /* MCLK/Loop Clock Control (0x18) */ + TVPIRSEN = 0x3a, /* Sense Test (0x00) */ + TVPIRTMD = 0x3b, /* Test Mode Data */ + TVPIRRML = 0x3c, /* CRC Remainder LSB [RO] */ + TVPIRRMM = 0x3d, /* CRC Remainder MSB [RO] */ + TVPIRRMS = 0x3e, /* CRC Bit Select [WO] */ + TVPIRDID = 0x3f, /* Device ID [RO] (0x30) */ + TVPIRRES = 0xff /* Software Reset [WO] */ +}; + +struct initvalues { + __u8 addr, value; +}; + +static struct initvalues ibm_initregs[] = { + { CLKCTL, 0x21 }, + { SYNCCTL, 0x00 }, + { HSYNCPOS, 0x00 }, + { PWRMNGMT, 0x00 }, + { DACOP, 0x02 }, + { PALETCTL, 0x00 }, + { SYSCLKCTL, 0x01 }, + + /* + * Note that colors in X are correct only if all video data is + * passed through the palette in the DAC. That is, "indirect + * color" must be configured. This is the case for the IBM DAC + * used in the 2MB and 4MB cards, at least. + */ + { BPP8, 0x00 }, + { BPP16, 0x01 }, + { BPP24, 0x00 }, + { BPP32, 0x00 }, + + { PIXCTL1, 0x05 }, + { PIXCTL2, 0x00 }, + { SYSCLKN, 0x08 }, + { SYSCLKM, 0x4f }, + { SYSCLKP, 0x00 }, + { SYSCLKC, 0x00 }, + { CURSCTL, 0x00 }, + { CURSACCTL, 0x01 }, + { CURSACATTR, 0xa8 }, + { CURS1R, 0xff }, + { CURS1G, 0xff }, + { CURS1B, 0xff }, + { CURS2R, 0xff }, + { CURS2G, 0xff }, + { CURS2B, 0xff }, + { CURS3R, 0xff }, + { CURS3G, 0xff }, + { CURS3B, 0xff }, + { BORDR, 0xff }, + { BORDG, 0xff }, + { BORDB, 0xff }, + { MISCTL1, 0x01 }, + { MISCTL2, 0x45 }, + { MISCTL3, 0x00 }, + { KEYCTL, 0x00 } +}; + +static struct initvalues tvp_initregs[] = { + { TVPIRICC, 0x00 }, + { TVPIRBRC, 0xe4 }, + { TVPIRLAC, 0x06 }, + { TVPIRTCC, 0x80 }, + { TVPIRMXC, 0x4d }, + { TVPIRCLS, 0x05 }, + { TVPIRPPG, 0x00 }, + { TVPIRGEC, 0x00 }, + { TVPIRMIC, 0x08 }, + { TVPIRCKL, 0xff }, + { TVPIRCKH, 0xff }, + { TVPIRCRL, 0xff }, + { TVPIRCRH, 0xff }, + { TVPIRCGL, 0xff }, + { TVPIRCGH, 0xff }, + { TVPIRCBL, 0xff }, + { TVPIRCBH, 0xff }, + { TVPIRCKC, 0x00 }, + { TVPIRPLA, 0x00 }, + { TVPIRPPD, 0xc0 }, + { TVPIRPPD, 0xd5 }, + { TVPIRPPD, 0xea }, + { TVPIRPLA, 0x00 }, + { TVPIRMPD, 0xb9 }, + { TVPIRMPD, 0x3a }, + { TVPIRMPD, 0xb1 }, + { TVPIRPLA, 0x00 }, + { TVPIRLPD, 0xc1 }, + { TVPIRLPD, 0x3d }, + { TVPIRLPD, 0xf3 }, +}; + +struct imstt_regvals { + __u32 pitch; + __u16 hes, heb, hsb, ht, ves, veb, vsb, vt, vil; + __u8 pclk_m, pclk_n, pclk_p; + /* Values of the tvp which change depending on colormode x resolution */ + __u8 mlc[3]; /* Memory Loop Config 0x39 */ + __u8 lckl_p[3]; /* P value of LCKL PLL */ +}; + +struct imstt_par { + struct imstt_regvals init; + __u32 __iomem *dc_regs; + unsigned long cmap_regs_phys; + __u8 *cmap_regs; + __u32 ramdac; + __u32 palette[16]; +}; + +enum { + IBM = 0, + TVP = 1 +}; + +#define USE_NV_MODES 1 +#define INIT_BPP 8 +#define INIT_XRES 640 +#define INIT_YRES 480 + +static int inverse = 0; +static char fontname[40] __initdata = { 0 }; +#if defined(CONFIG_PPC) +static signed char init_vmode = -1, init_cmode = -1; +#endif + +static struct imstt_regvals tvp_reg_init_2 = { + 512, + 0x0002, 0x0006, 0x0026, 0x0028, 0x0003, 0x0016, 0x0196, 0x0197, 0x0196, + 0xec, 0x2a, 0xf3, + { 0x3c, 0x3b, 0x39 }, { 0xf3, 0xf3, 0xf3 } +}; + +static struct imstt_regvals tvp_reg_init_6 = { + 640, + 0x0004, 0x0009, 0x0031, 0x0036, 0x0003, 0x002a, 0x020a, 0x020d, 0x020a, + 0xef, 0x2e, 0xb2, + { 0x39, 0x39, 0x38 }, { 0xf3, 0xf3, 0xf3 } +}; + +static struct imstt_regvals tvp_reg_init_12 = { + 800, + 0x0005, 0x000e, 0x0040, 0x0042, 0x0003, 0x018, 0x270, 0x271, 0x270, + 0xf6, 0x2e, 0xf2, + { 0x3a, 0x39, 0x38 }, { 0xf3, 0xf3, 0xf3 } +}; + +static struct imstt_regvals tvp_reg_init_13 = { + 832, + 0x0004, 0x0011, 0x0045, 0x0048, 0x0003, 0x002a, 0x029a, 0x029b, 0x0000, + 0xfe, 0x3e, 0xf1, + { 0x39, 0x38, 0x38 }, { 0xf3, 0xf3, 0xf2 } +}; + +static struct imstt_regvals tvp_reg_init_17 = { + 1024, + 0x0006, 0x0210, 0x0250, 0x0053, 0x1003, 0x0021, 0x0321, 0x0324, 0x0000, + 0xfc, 0x3a, 0xf1, + { 0x39, 0x38, 0x38 }, { 0xf3, 0xf3, 0xf2 } +}; + +static struct imstt_regvals tvp_reg_init_18 = { + 1152, + 0x0009, 0x0011, 0x059, 0x5b, 0x0003, 0x0031, 0x0397, 0x039a, 0x0000, + 0xfd, 0x3a, 0xf1, + { 0x39, 0x38, 0x38 }, { 0xf3, 0xf3, 0xf2 } +}; + +static struct imstt_regvals tvp_reg_init_19 = { + 1280, + 0x0009, 0x0016, 0x0066, 0x0069, 0x0003, 0x0027, 0x03e7, 0x03e8, 0x03e7, + 0xf7, 0x36, 0xf0, + { 0x38, 0x38, 0x38 }, { 0xf3, 0xf2, 0xf1 } +}; + +static struct imstt_regvals tvp_reg_init_20 = { + 1280, + 0x0009, 0x0018, 0x0068, 0x006a, 0x0003, 0x0029, 0x0429, 0x042a, 0x0000, + 0xf0, 0x2d, 0xf0, + { 0x38, 0x38, 0x38 }, { 0xf3, 0xf2, 0xf1 } +}; + +/* + * PCI driver prototypes + */ +static int imsttfb_probe(struct pci_dev *pdev, const struct pci_device_id *ent); +static void imsttfb_remove(struct pci_dev *pdev); + +/* + * Register access + */ +static inline u32 read_reg_le32(volatile u32 __iomem *base, int regindex) +{ +#ifdef __powerpc__ + return in_le32(base + regindex); +#else + return readl(base + regindex); +#endif +} + +static inline void write_reg_le32(volatile u32 __iomem *base, int regindex, u32 val) +{ +#ifdef __powerpc__ + out_le32(base + regindex, val); +#else + writel(val, base + regindex); +#endif +} + +static __u32 +getclkMHz(struct imstt_par *par) +{ + __u32 clk_m, clk_n, clk_p; + + clk_m = par->init.pclk_m; + clk_n = par->init.pclk_n; + clk_p = par->init.pclk_p; + + return 20 * (clk_m + 1) / ((clk_n + 1) * (clk_p ? 2 * clk_p : 1)); +} + +static void +setclkMHz(struct imstt_par *par, __u32 MHz) +{ + __u32 clk_m, clk_n, x, stage, spilled; + + clk_m = clk_n = 0; + stage = spilled = 0; + for (;;) { + switch (stage) { + case 0: + clk_m++; + break; + case 1: + clk_n++; + break; + } + x = 20 * (clk_m + 1) / (clk_n + 1); + if (x == MHz) + break; + if (x > MHz) { + spilled = 1; + stage = 1; + } else if (spilled && x < MHz) { + stage = 0; + } + } + + par->init.pclk_m = clk_m; + par->init.pclk_n = clk_n; + par->init.pclk_p = 0; +} + +static struct imstt_regvals * +compute_imstt_regvals_ibm(struct imstt_par *par, int xres, int yres) +{ + struct imstt_regvals *init = &par->init; + __u32 MHz, hes, heb, veb, htp, vtp; + + switch (xres) { + case 640: + hes = 0x0008; heb = 0x0012; veb = 0x002a; htp = 10; vtp = 2; + MHz = 30 /* .25 */ ; + break; + case 832: + hes = 0x0005; heb = 0x0020; veb = 0x0028; htp = 8; vtp = 3; + MHz = 57 /* .27_ */ ; + break; + case 1024: + hes = 0x000a; heb = 0x001c; veb = 0x0020; htp = 8; vtp = 3; + MHz = 80; + break; + case 1152: + hes = 0x0012; heb = 0x0022; veb = 0x0031; htp = 4; vtp = 3; + MHz = 101 /* .6_ */ ; + break; + case 1280: + hes = 0x0012; heb = 0x002f; veb = 0x0029; htp = 4; vtp = 1; + MHz = yres == 960 ? 126 : 135; + break; + case 1600: + hes = 0x0018; heb = 0x0040; veb = 0x002a; htp = 4; vtp = 3; + MHz = 200; + break; + default: + return NULL; + } + + setclkMHz(par, MHz); + + init->hes = hes; + init->heb = heb; + init->hsb = init->heb + (xres >> 3); + init->ht = init->hsb + htp; + init->ves = 0x0003; + init->veb = veb; + init->vsb = init->veb + yres; + init->vt = init->vsb + vtp; + init->vil = init->vsb; + + init->pitch = xres; + return init; +} + +static struct imstt_regvals * +compute_imstt_regvals_tvp(struct imstt_par *par, int xres, int yres) +{ + struct imstt_regvals *init; + + switch (xres) { + case 512: + init = &tvp_reg_init_2; + break; + case 640: + init = &tvp_reg_init_6; + break; + case 800: + init = &tvp_reg_init_12; + break; + case 832: + init = &tvp_reg_init_13; + break; + case 1024: + init = &tvp_reg_init_17; + break; + case 1152: + init = &tvp_reg_init_18; + break; + case 1280: + init = yres == 960 ? &tvp_reg_init_19 : &tvp_reg_init_20; + break; + default: + return NULL; + } + par->init = *init; + return init; +} + +static struct imstt_regvals * +compute_imstt_regvals (struct imstt_par *par, u_int xres, u_int yres) +{ + if (par->ramdac == IBM) + return compute_imstt_regvals_ibm(par, xres, yres); + else + return compute_imstt_regvals_tvp(par, xres, yres); +} + +static void +set_imstt_regvals_ibm (struct imstt_par *par, u_int bpp) +{ + struct imstt_regvals *init = &par->init; + __u8 pformat = (bpp >> 3) + 2; + + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = PIXM0; eieio(); + par->cmap_regs[PIDXDATA] = init->pclk_m;eieio(); + par->cmap_regs[PIDXLO] = PIXN0; eieio(); + par->cmap_regs[PIDXDATA] = init->pclk_n;eieio(); + par->cmap_regs[PIDXLO] = PIXP0; eieio(); + par->cmap_regs[PIDXDATA] = init->pclk_p;eieio(); + par->cmap_regs[PIDXLO] = PIXC0; eieio(); + par->cmap_regs[PIDXDATA] = 0x02; eieio(); + + par->cmap_regs[PIDXLO] = PIXFMT; eieio(); + par->cmap_regs[PIDXDATA] = pformat; eieio(); +} + +static void +set_imstt_regvals_tvp (struct imstt_par *par, u_int bpp) +{ + struct imstt_regvals *init = &par->init; + __u8 tcc, mxc, lckl_n, mic; + __u8 mlc, lckl_p; + + switch (bpp) { + default: + case 8: + tcc = 0x80; + mxc = 0x4d; + lckl_n = 0xc1; + mlc = init->mlc[0]; + lckl_p = init->lckl_p[0]; + break; + case 16: + tcc = 0x44; + mxc = 0x55; + lckl_n = 0xe1; + mlc = init->mlc[1]; + lckl_p = init->lckl_p[1]; + break; + case 24: + tcc = 0x5e; + mxc = 0x5d; + lckl_n = 0xf1; + mlc = init->mlc[2]; + lckl_p = init->lckl_p[2]; + break; + case 32: + tcc = 0x46; + mxc = 0x5d; + lckl_n = 0xf1; + mlc = init->mlc[2]; + lckl_p = init->lckl_p[2]; + break; + } + mic = 0x08; + + par->cmap_regs[TVPADDRW] = TVPIRPLA; eieio(); + par->cmap_regs[TVPIDATA] = 0x00; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRPPD; eieio(); + par->cmap_regs[TVPIDATA] = init->pclk_m; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRPPD; eieio(); + par->cmap_regs[TVPIDATA] = init->pclk_n; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRPPD; eieio(); + par->cmap_regs[TVPIDATA] = init->pclk_p; eieio(); + + par->cmap_regs[TVPADDRW] = TVPIRTCC; eieio(); + par->cmap_regs[TVPIDATA] = tcc; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRMXC; eieio(); + par->cmap_regs[TVPIDATA] = mxc; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRMIC; eieio(); + par->cmap_regs[TVPIDATA] = mic; eieio(); + + par->cmap_regs[TVPADDRW] = TVPIRPLA; eieio(); + par->cmap_regs[TVPIDATA] = 0x00; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRLPD; eieio(); + par->cmap_regs[TVPIDATA] = lckl_n; eieio(); + + par->cmap_regs[TVPADDRW] = TVPIRPLA; eieio(); + par->cmap_regs[TVPIDATA] = 0x15; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRMLC; eieio(); + par->cmap_regs[TVPIDATA] = mlc; eieio(); + + par->cmap_regs[TVPADDRW] = TVPIRPLA; eieio(); + par->cmap_regs[TVPIDATA] = 0x2a; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRLPD; eieio(); + par->cmap_regs[TVPIDATA] = lckl_p; eieio(); +} + +static void +set_imstt_regvals (struct fb_info *info, u_int bpp) +{ + struct imstt_par *par = info->par; + struct imstt_regvals *init = &par->init; + __u32 ctl, pitch, byteswap, scr; + + if (par->ramdac == IBM) + set_imstt_regvals_ibm(par, bpp); + else + set_imstt_regvals_tvp(par, bpp); + + /* + * From what I (jsk) can gather poking around with MacsBug, + * bits 8 and 9 in the SCR register control endianness + * correction (byte swapping). These bits must be set according + * to the color depth as follows: + * Color depth Bit 9 Bit 8 + * ========== ===== ===== + * 8bpp 0 0 + * 16bpp 0 1 + * 32bpp 1 1 + */ + switch (bpp) { + default: + case 8: + ctl = 0x17b1; + pitch = init->pitch >> 2; + byteswap = 0x000; + break; + case 16: + ctl = 0x17b3; + pitch = init->pitch >> 1; + byteswap = 0x100; + break; + case 24: + ctl = 0x17b9; + pitch = init->pitch - (init->pitch >> 2); + byteswap = 0x200; + break; + case 32: + ctl = 0x17b5; + pitch = init->pitch; + byteswap = 0x300; + break; + } + if (par->ramdac == TVP) + ctl -= 0x30; + + write_reg_le32(par->dc_regs, HES, init->hes); + write_reg_le32(par->dc_regs, HEB, init->heb); + write_reg_le32(par->dc_regs, HSB, init->hsb); + write_reg_le32(par->dc_regs, HT, init->ht); + write_reg_le32(par->dc_regs, VES, init->ves); + write_reg_le32(par->dc_regs, VEB, init->veb); + write_reg_le32(par->dc_regs, VSB, init->vsb); + write_reg_le32(par->dc_regs, VT, init->vt); + write_reg_le32(par->dc_regs, VIL, init->vil); + write_reg_le32(par->dc_regs, HCIV, 1); + write_reg_le32(par->dc_regs, VCIV, 1); + write_reg_le32(par->dc_regs, TCDR, 4); + write_reg_le32(par->dc_regs, RRCIV, 1); + write_reg_le32(par->dc_regs, RRSC, 0x980); + write_reg_le32(par->dc_regs, RRCR, 0x11); + + if (par->ramdac == IBM) { + write_reg_le32(par->dc_regs, HRIR, 0x0100); + write_reg_le32(par->dc_regs, CMR, 0x00ff); + write_reg_le32(par->dc_regs, SRGCTL, 0x0073); + } else { + write_reg_le32(par->dc_regs, HRIR, 0x0200); + write_reg_le32(par->dc_regs, CMR, 0x01ff); + write_reg_le32(par->dc_regs, SRGCTL, 0x0003); + } + + switch (info->fix.smem_len) { + case 0x200000: + scr = 0x059d | byteswap; + break; + /* case 0x400000: + case 0x800000: */ + default: + pitch >>= 1; + scr = 0x150dd | byteswap; + break; + } + + write_reg_le32(par->dc_regs, SCR, scr); + write_reg_le32(par->dc_regs, SPR, pitch); + write_reg_le32(par->dc_regs, STGCTL, ctl); +} + +static inline void +set_offset (struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct imstt_par *par = info->par; + __u32 off = var->yoffset * (info->fix.line_length >> 3) + + ((var->xoffset * (info->var.bits_per_pixel >> 3)) >> 3); + write_reg_le32(par->dc_regs, SSR, off); +} + +static inline void +set_555 (struct imstt_par *par) +{ + if (par->ramdac == IBM) { + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = BPP16; eieio(); + par->cmap_regs[PIDXDATA] = 0x01; eieio(); + } else { + par->cmap_regs[TVPADDRW] = TVPIRTCC; eieio(); + par->cmap_regs[TVPIDATA] = 0x44; eieio(); + } +} + +static inline void +set_565 (struct imstt_par *par) +{ + if (par->ramdac == IBM) { + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = BPP16; eieio(); + par->cmap_regs[PIDXDATA] = 0x03; eieio(); + } else { + par->cmap_regs[TVPADDRW] = TVPIRTCC; eieio(); + par->cmap_regs[TVPIDATA] = 0x45; eieio(); + } +} + +static int +imsttfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + if ((var->bits_per_pixel != 8 && var->bits_per_pixel != 16 + && var->bits_per_pixel != 24 && var->bits_per_pixel != 32) + || var->xres_virtual < var->xres || var->yres_virtual < var->yres + || var->nonstd + || (var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) + return -EINVAL; + + if ((var->xres * var->yres) * (var->bits_per_pixel >> 3) > info->fix.smem_len + || (var->xres_virtual * var->yres_virtual) * (var->bits_per_pixel >> 3) > info->fix.smem_len) + return -EINVAL; + + switch (var->bits_per_pixel) { + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGB 555 or 565 */ + if (var->green.length != 6) + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + if (var->green.length != 6) + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 24: /* RGB 888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: /* RGBA 8888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } + + if (var->yres == var->yres_virtual) { + __u32 vram = (info->fix.smem_len - (PAGE_SIZE << 2)); + var->yres_virtual = ((vram << 3) / var->bits_per_pixel) / var->xres_virtual; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + } + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + var->height = -1; + var->width = -1; + var->vmode = FB_VMODE_NONINTERLACED; + var->left_margin = var->right_margin = 16; + var->upper_margin = var->lower_margin = 16; + var->hsync_len = var->vsync_len = 8; + return 0; +} + +static int +imsttfb_set_par(struct fb_info *info) +{ + struct imstt_par *par = info->par; + + if (!compute_imstt_regvals(par, info->var.xres, info->var.yres)) + return -EINVAL; + + if (info->var.green.length == 6) + set_565(par); + else + set_555(par); + set_imstt_regvals(info, info->var.bits_per_pixel); + info->var.pixclock = 1000000 / getclkMHz(par); + return 0; +} + +static int +imsttfb_setcolreg (u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct imstt_par *par = info->par; + u_int bpp = info->var.bits_per_pixel; + + if (regno > 255) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + /* PADDRW/PDATA are the same as TVPPADDRW/TVPPDATA */ + if (0 && bpp == 16) /* screws up X */ + par->cmap_regs[PADDRW] = regno << 3; + else + par->cmap_regs[PADDRW] = regno; + eieio(); + + par->cmap_regs[PDATA] = red; eieio(); + par->cmap_regs[PDATA] = green; eieio(); + par->cmap_regs[PDATA] = blue; eieio(); + + if (regno < 16) + switch (bpp) { + case 16: + par->palette[regno] = + (regno << (info->var.green.length == + 5 ? 10 : 11)) | (regno << 5) | regno; + break; + case 24: + par->palette[regno] = + (regno << 16) | (regno << 8) | regno; + break; + case 32: { + int i = (regno << 8) | regno; + par->palette[regno] = (i << 16) |i; + break; + } + } + return 0; +} + +static int +imsttfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + if (var->xoffset + info->var.xres > info->var.xres_virtual + || var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + set_offset(var, info); + return 0; +} + +static int +imsttfb_blank(int blank, struct fb_info *info) +{ + struct imstt_par *par = info->par; + __u32 ctrl; + + ctrl = read_reg_le32(par->dc_regs, STGCTL); + if (blank > 0) { + switch (blank) { + case FB_BLANK_NORMAL: + case FB_BLANK_POWERDOWN: + ctrl &= ~0x00000380; + if (par->ramdac == IBM) { + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = MISCTL2; eieio(); + par->cmap_regs[PIDXDATA] = 0x55; eieio(); + par->cmap_regs[PIDXLO] = MISCTL1; eieio(); + par->cmap_regs[PIDXDATA] = 0x11; eieio(); + par->cmap_regs[PIDXLO] = SYNCCTL; eieio(); + par->cmap_regs[PIDXDATA] = 0x0f; eieio(); + par->cmap_regs[PIDXLO] = PWRMNGMT; eieio(); + par->cmap_regs[PIDXDATA] = 0x1f; eieio(); + par->cmap_regs[PIDXLO] = CLKCTL; eieio(); + par->cmap_regs[PIDXDATA] = 0xc0; + } + break; + case FB_BLANK_VSYNC_SUSPEND: + ctrl &= ~0x00000020; + break; + case FB_BLANK_HSYNC_SUSPEND: + ctrl &= ~0x00000010; + break; + } + } else { + if (par->ramdac == IBM) { + ctrl |= 0x000017b0; + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = CLKCTL; eieio(); + par->cmap_regs[PIDXDATA] = 0x01; eieio(); + par->cmap_regs[PIDXLO] = PWRMNGMT; eieio(); + par->cmap_regs[PIDXDATA] = 0x00; eieio(); + par->cmap_regs[PIDXLO] = SYNCCTL; eieio(); + par->cmap_regs[PIDXDATA] = 0x00; eieio(); + par->cmap_regs[PIDXLO] = MISCTL1; eieio(); + par->cmap_regs[PIDXDATA] = 0x01; eieio(); + par->cmap_regs[PIDXLO] = MISCTL2; eieio(); + par->cmap_regs[PIDXDATA] = 0x45; eieio(); + } else + ctrl |= 0x00001780; + } + write_reg_le32(par->dc_regs, STGCTL, ctrl); + return 0; +} + +static void +imsttfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct imstt_par *par = info->par; + __u32 Bpp, line_pitch, bgc, dx, dy, width, height; + + bgc = rect->color; + bgc |= (bgc << 8); + bgc |= (bgc << 16); + + Bpp = info->var.bits_per_pixel >> 3, + line_pitch = info->fix.line_length; + + dy = rect->dy * line_pitch; + dx = rect->dx * Bpp; + height = rect->height; + height--; + width = rect->width * Bpp; + width--; + + if (rect->rop == ROP_COPY) { + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x80); + write_reg_le32(par->dc_regs, DSA, dy + dx); + write_reg_le32(par->dc_regs, CNT, (height << 16) | width); + write_reg_le32(par->dc_regs, DP_OCTL, line_pitch); + write_reg_le32(par->dc_regs, BI, 0xffffffff); + write_reg_le32(par->dc_regs, MBC, 0xffffffff); + write_reg_le32(par->dc_regs, CLR, bgc); + write_reg_le32(par->dc_regs, BLTCTL, 0x840); /* 0x200000 */ + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x80); + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x40); + } else { + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x80); + write_reg_le32(par->dc_regs, DSA, dy + dx); + write_reg_le32(par->dc_regs, S1SA, dy + dx); + write_reg_le32(par->dc_regs, CNT, (height << 16) | width); + write_reg_le32(par->dc_regs, DP_OCTL, line_pitch); + write_reg_le32(par->dc_regs, SP, line_pitch); + write_reg_le32(par->dc_regs, BLTCTL, 0x40005); + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x80); + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x40); + } +} + +static void +imsttfb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct imstt_par *par = info->par; + __u32 Bpp, line_pitch, fb_offset_old, fb_offset_new, sp, dp_octl; + __u32 cnt, bltctl, sx, sy, dx, dy, height, width; + + Bpp = info->var.bits_per_pixel >> 3, + + sx = area->sx * Bpp; + sy = area->sy; + dx = area->dx * Bpp; + dy = area->dy; + height = area->height; + height--; + width = area->width * Bpp; + width--; + + line_pitch = info->fix.line_length; + bltctl = 0x05; + sp = line_pitch << 16; + cnt = height << 16; + + if (sy < dy) { + sy += height; + dy += height; + sp |= -(line_pitch) & 0xffff; + dp_octl = -(line_pitch) & 0xffff; + } else { + sp |= line_pitch; + dp_octl = line_pitch; + } + if (sx < dx) { + sx += width; + dx += width; + bltctl |= 0x80; + cnt |= -(width) & 0xffff; + } else { + cnt |= width; + } + fb_offset_old = sy * line_pitch + sx; + fb_offset_new = dy * line_pitch + dx; + + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x80); + write_reg_le32(par->dc_regs, S1SA, fb_offset_old); + write_reg_le32(par->dc_regs, SP, sp); + write_reg_le32(par->dc_regs, DSA, fb_offset_new); + write_reg_le32(par->dc_regs, CNT, cnt); + write_reg_le32(par->dc_regs, DP_OCTL, dp_octl); + write_reg_le32(par->dc_regs, BLTCTL, bltctl); + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x80); + while(read_reg_le32(par->dc_regs, SSTATUS) & 0x40); +} + +#if 0 +static int +imsttfb_load_cursor_image(struct imstt_par *par, int width, int height, __u8 fgc) +{ + u_int x, y; + + if (width > 32 || height > 32) + return -EINVAL; + + if (par->ramdac == IBM) { + par->cmap_regs[PIDXHI] = 1; eieio(); + for (x = 0; x < 0x100; x++) { + par->cmap_regs[PIDXLO] = x; eieio(); + par->cmap_regs[PIDXDATA] = 0x00; eieio(); + } + par->cmap_regs[PIDXHI] = 1; eieio(); + for (y = 0; y < height; y++) + for (x = 0; x < width >> 2; x++) { + par->cmap_regs[PIDXLO] = x + y * 8; eieio(); + par->cmap_regs[PIDXDATA] = 0xff; eieio(); + } + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = CURS1R; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS1G; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS1B; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS2R; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS2G; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS2B; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS3R; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS3G; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + par->cmap_regs[PIDXLO] = CURS3B; eieio(); + par->cmap_regs[PIDXDATA] = fgc; eieio(); + } else { + par->cmap_regs[TVPADDRW] = TVPIRICC; eieio(); + par->cmap_regs[TVPIDATA] &= 0x03; eieio(); + par->cmap_regs[TVPADDRW] = 0; eieio(); + for (x = 0; x < 0x200; x++) { + par->cmap_regs[TVPCRDAT] = 0x00; eieio(); + } + for (x = 0; x < 0x200; x++) { + par->cmap_regs[TVPCRDAT] = 0xff; eieio(); + } + par->cmap_regs[TVPADDRW] = TVPIRICC; eieio(); + par->cmap_regs[TVPIDATA] &= 0x03; eieio(); + for (y = 0; y < height; y++) + for (x = 0; x < width >> 3; x++) { + par->cmap_regs[TVPADDRW] = x + y * 8; eieio(); + par->cmap_regs[TVPCRDAT] = 0xff; eieio(); + } + par->cmap_regs[TVPADDRW] = TVPIRICC; eieio(); + par->cmap_regs[TVPIDATA] |= 0x08; eieio(); + for (y = 0; y < height; y++) + for (x = 0; x < width >> 3; x++) { + par->cmap_regs[TVPADDRW] = x + y * 8; eieio(); + par->cmap_regs[TVPCRDAT] = 0xff; eieio(); + } + par->cmap_regs[TVPCADRW] = 0x00; eieio(); + for (x = 0; x < 12; x++) { + par->cmap_regs[TVPCDATA] = fgc; + eieio(); + } + } + return 1; +} + +static void +imstt_set_cursor(struct imstt_par *par, struct fb_image *d, int on) +{ + if (par->ramdac == IBM) { + par->cmap_regs[PIDXHI] = 0; eieio(); + if (!on) { + par->cmap_regs[PIDXLO] = CURSCTL; eieio(); + par->cmap_regs[PIDXDATA] = 0x00; eieio(); + } else { + par->cmap_regs[PIDXLO] = CURSXHI; eieio(); + par->cmap_regs[PIDXDATA] = d->dx >> 8; eieio(); + par->cmap_regs[PIDXLO] = CURSXLO; eieio(); + par->cmap_regs[PIDXDATA] = d->dx & 0xff;eieio(); + par->cmap_regs[PIDXLO] = CURSYHI; eieio(); + par->cmap_regs[PIDXDATA] = d->dy >> 8; eieio(); + par->cmap_regs[PIDXLO] = CURSYLO; eieio(); + par->cmap_regs[PIDXDATA] = d->dy & 0xff;eieio(); + par->cmap_regs[PIDXLO] = CURSCTL; eieio(); + par->cmap_regs[PIDXDATA] = 0x02; eieio(); + } + } else { + if (!on) { + par->cmap_regs[TVPADDRW] = TVPIRICC; eieio(); + par->cmap_regs[TVPIDATA] = 0x00; eieio(); + } else { + __u16 x = d->dx + 0x40, y = d->dy + 0x40; + + par->cmap_regs[TVPCXPOH] = x >> 8; eieio(); + par->cmap_regs[TVPCXPOL] = x & 0xff; eieio(); + par->cmap_regs[TVPCYPOH] = y >> 8; eieio(); + par->cmap_regs[TVPCYPOL] = y & 0xff; eieio(); + par->cmap_regs[TVPADDRW] = TVPIRICC; eieio(); + par->cmap_regs[TVPIDATA] = 0x02; eieio(); + } + } +} + +static int +imsttfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct imstt_par *par = info->par; + u32 flags = cursor->set, fg, bg, xx, yy; + + if (cursor->dest == NULL && cursor->rop == ROP_XOR) + return 1; + + imstt_set_cursor(info, cursor, 0); + + if (flags & FB_CUR_SETPOS) { + xx = cursor->image.dx - info->var.xoffset; + yy = cursor->image.dy - info->var.yoffset; + } + + if (flags & FB_CUR_SETSIZE) { + } + + if (flags & (FB_CUR_SETSHAPE | FB_CUR_SETCMAP)) { + int fg_idx = cursor->image.fg_color; + int width = (cursor->image.width+7)/8; + u8 *dat = (u8 *) cursor->image.data; + u8 *dst = (u8 *) cursor->dest; + u8 *msk = (u8 *) cursor->mask; + + switch (cursor->rop) { + case ROP_XOR: + for (i = 0; i < cursor->image.height; i++) { + for (j = 0; j < width; j++) { + d_idx = i * MAX_CURS/8 + j; + data[d_idx] = byte_rev[dat[s_idx] ^ + dst[s_idx]]; + mask[d_idx] = byte_rev[msk[s_idx]]; + s_idx++; + } + } + break; + case ROP_COPY: + default: + for (i = 0; i < cursor->image.height; i++) { + for (j = 0; j < width; j++) { + d_idx = i * MAX_CURS/8 + j; + data[d_idx] = byte_rev[dat[s_idx]]; + mask[d_idx] = byte_rev[msk[s_idx]]; + s_idx++; + } + } + break; + } + + fg = ((info->cmap.red[fg_idx] & 0xf8) << 7) | + ((info->cmap.green[fg_idx] & 0xf8) << 2) | + ((info->cmap.blue[fg_idx] & 0xf8) >> 3) | 1 << 15; + + imsttfb_load_cursor_image(par, xx, yy, fgc); + } + if (cursor->enable) + imstt_set_cursor(info, cursor, 1); + return 0; +} +#endif + +#define FBIMSTT_SETREG 0x545401 +#define FBIMSTT_GETREG 0x545402 +#define FBIMSTT_SETCMAPREG 0x545403 +#define FBIMSTT_GETCMAPREG 0x545404 +#define FBIMSTT_SETIDXREG 0x545405 +#define FBIMSTT_GETIDXREG 0x545406 + +static int +imsttfb_ioctl(struct fb_info *info, u_int cmd, u_long arg) +{ + struct imstt_par *par = info->par; + void __user *argp = (void __user *)arg; + __u32 reg[2]; + __u8 idx[2]; + + switch (cmd) { + case FBIMSTT_SETREG: + if (copy_from_user(reg, argp, 8) || reg[0] > (0x1000 - sizeof(reg[0])) / sizeof(reg[0])) + return -EFAULT; + write_reg_le32(par->dc_regs, reg[0], reg[1]); + return 0; + case FBIMSTT_GETREG: + if (copy_from_user(reg, argp, 4) || reg[0] > (0x1000 - sizeof(reg[0])) / sizeof(reg[0])) + return -EFAULT; + reg[1] = read_reg_le32(par->dc_regs, reg[0]); + if (copy_to_user((void __user *)(arg + 4), ®[1], 4)) + return -EFAULT; + return 0; + case FBIMSTT_SETCMAPREG: + if (copy_from_user(reg, argp, 8) || reg[0] > (0x1000 - sizeof(reg[0])) / sizeof(reg[0])) + return -EFAULT; + write_reg_le32(((u_int __iomem *)par->cmap_regs), reg[0], reg[1]); + return 0; + case FBIMSTT_GETCMAPREG: + if (copy_from_user(reg, argp, 4) || reg[0] > (0x1000 - sizeof(reg[0])) / sizeof(reg[0])) + return -EFAULT; + reg[1] = read_reg_le32(((u_int __iomem *)par->cmap_regs), reg[0]); + if (copy_to_user((void __user *)(arg + 4), ®[1], 4)) + return -EFAULT; + return 0; + case FBIMSTT_SETIDXREG: + if (copy_from_user(idx, argp, 2)) + return -EFAULT; + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = idx[0]; eieio(); + par->cmap_regs[PIDXDATA] = idx[1]; eieio(); + return 0; + case FBIMSTT_GETIDXREG: + if (copy_from_user(idx, argp, 1)) + return -EFAULT; + par->cmap_regs[PIDXHI] = 0; eieio(); + par->cmap_regs[PIDXLO] = idx[0]; eieio(); + idx[1] = par->cmap_regs[PIDXDATA]; + if (copy_to_user((void __user *)(arg + 1), &idx[1], 1)) + return -EFAULT; + return 0; + default: + return -ENOIOCTLCMD; + } +} + +static struct pci_device_id imsttfb_pci_tbl[] = { + { PCI_VENDOR_ID_IMS, PCI_DEVICE_ID_IMS_TT128, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, IBM }, + { PCI_VENDOR_ID_IMS, PCI_DEVICE_ID_IMS_TT3D, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, TVP }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, imsttfb_pci_tbl); + +static struct pci_driver imsttfb_pci_driver = { + .name = "imsttfb", + .id_table = imsttfb_pci_tbl, + .probe = imsttfb_probe, + .remove = imsttfb_remove, +}; + +static struct fb_ops imsttfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = imsttfb_check_var, + .fb_set_par = imsttfb_set_par, + .fb_setcolreg = imsttfb_setcolreg, + .fb_pan_display = imsttfb_pan_display, + .fb_blank = imsttfb_blank, + .fb_fillrect = imsttfb_fillrect, + .fb_copyarea = imsttfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_ioctl = imsttfb_ioctl, +}; + +static void init_imstt(struct fb_info *info) +{ + struct imstt_par *par = info->par; + __u32 i, tmp, *ip, *end; + + tmp = read_reg_le32(par->dc_regs, PRC); + if (par->ramdac == IBM) + info->fix.smem_len = (tmp & 0x0004) ? 0x400000 : 0x200000; + else + info->fix.smem_len = 0x800000; + + ip = (__u32 *)info->screen_base; + end = (__u32 *)(info->screen_base + info->fix.smem_len); + while (ip < end) + *ip++ = 0; + + /* initialize the card */ + tmp = read_reg_le32(par->dc_regs, STGCTL); + write_reg_le32(par->dc_regs, STGCTL, tmp & ~0x1); + write_reg_le32(par->dc_regs, SSR, 0); + + /* set default values for DAC registers */ + if (par->ramdac == IBM) { + par->cmap_regs[PPMASK] = 0xff; + eieio(); + par->cmap_regs[PIDXHI] = 0; + eieio(); + for (i = 0; i < ARRAY_SIZE(ibm_initregs); i++) { + par->cmap_regs[PIDXLO] = ibm_initregs[i].addr; + eieio(); + par->cmap_regs[PIDXDATA] = ibm_initregs[i].value; + eieio(); + } + } else { + for (i = 0; i < ARRAY_SIZE(tvp_initregs); i++) { + par->cmap_regs[TVPADDRW] = tvp_initregs[i].addr; + eieio(); + par->cmap_regs[TVPIDATA] = tvp_initregs[i].value; + eieio(); + } + } + +#if USE_NV_MODES && defined(CONFIG_PPC32) + { + int vmode = init_vmode, cmode = init_cmode; + + if (vmode == -1) { + vmode = nvram_read_byte(NV_VMODE); + if (vmode <= 0 || vmode > VMODE_MAX) + vmode = VMODE_640_480_67; + } + if (cmode == -1) { + cmode = nvram_read_byte(NV_CMODE); + if (cmode < CMODE_8 || cmode > CMODE_32) + cmode = CMODE_8; + } + if (mac_vmode_to_var(vmode, cmode, &info->var)) { + info->var.xres = info->var.xres_virtual = INIT_XRES; + info->var.yres = info->var.yres_virtual = INIT_YRES; + info->var.bits_per_pixel = INIT_BPP; + } + } +#else + info->var.xres = info->var.xres_virtual = INIT_XRES; + info->var.yres = info->var.yres_virtual = INIT_YRES; + info->var.bits_per_pixel = INIT_BPP; +#endif + + if ((info->var.xres * info->var.yres) * (info->var.bits_per_pixel >> 3) > info->fix.smem_len + || !(compute_imstt_regvals(par, info->var.xres, info->var.yres))) { + printk("imsttfb: %ux%ux%u not supported\n", info->var.xres, info->var.yres, info->var.bits_per_pixel); + framebuffer_release(info); + return; + } + + sprintf(info->fix.id, "IMS TT (%s)", par->ramdac == IBM ? "IBM" : "TVP"); + info->fix.mmio_len = 0x1000; + info->fix.accel = FB_ACCEL_IMS_TWINTURBO; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = info->var.bits_per_pixel == 8 ? FB_VISUAL_PSEUDOCOLOR + : FB_VISUAL_DIRECTCOLOR; + info->fix.line_length = info->var.xres * (info->var.bits_per_pixel >> 3); + info->fix.xpanstep = 8; + info->fix.ypanstep = 1; + info->fix.ywrapstep = 0; + + info->var.accel_flags = FB_ACCELF_TEXT; + +// if (par->ramdac == IBM) +// imstt_cursor_init(info); + if (info->var.green.length == 6) + set_565(par); + else + set_555(par); + set_imstt_regvals(info, info->var.bits_per_pixel); + + info->var.pixclock = 1000000 / getclkMHz(par); + + info->fbops = &imsttfb_ops; + info->flags = FBINFO_DEFAULT | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_YPAN; + + fb_alloc_cmap(&info->cmap, 0, 0); + + if (register_framebuffer(info) < 0) { + framebuffer_release(info); + return; + } + + tmp = (read_reg_le32(par->dc_regs, SSTATUS) & 0x0f00) >> 8; + fb_info(info, "%s frame buffer; %uMB vram; chip version %u\n", + info->fix.id, info->fix.smem_len >> 20, tmp); +} + +static int imsttfb_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + unsigned long addr, size; + struct imstt_par *par; + struct fb_info *info; +#ifdef CONFIG_PPC_OF + struct device_node *dp; + + dp = pci_device_to_OF_node(pdev); + if(dp) + printk(KERN_INFO "%s: OF name %s\n",__func__, dp->name); + else + printk(KERN_ERR "imsttfb: no OF node for pci device\n"); +#endif /* CONFIG_PPC_OF */ + + info = framebuffer_alloc(sizeof(struct imstt_par), &pdev->dev); + + if (!info) { + printk(KERN_ERR "imsttfb: Can't allocate memory\n"); + return -ENOMEM; + } + + par = info->par; + + addr = pci_resource_start (pdev, 0); + size = pci_resource_len (pdev, 0); + + if (!request_mem_region(addr, size, "imsttfb")) { + printk(KERN_ERR "imsttfb: Can't reserve memory region\n"); + framebuffer_release(info); + return -ENODEV; + } + + switch (pdev->device) { + case PCI_DEVICE_ID_IMS_TT128: /* IMS,tt128mbA */ + par->ramdac = IBM; +#ifdef CONFIG_PPC_OF + if (dp && ((strcmp(dp->name, "IMS,tt128mb8") == 0) || + (strcmp(dp->name, "IMS,tt128mb8A") == 0))) + par->ramdac = TVP; +#endif /* CONFIG_PPC_OF */ + break; + case PCI_DEVICE_ID_IMS_TT3D: /* IMS,tt3d */ + par->ramdac = TVP; + break; + default: + printk(KERN_INFO "imsttfb: Device 0x%x unknown, " + "contact maintainer.\n", pdev->device); + release_mem_region(addr, size); + framebuffer_release(info); + return -ENODEV; + } + + info->fix.smem_start = addr; + info->screen_base = (__u8 *)ioremap(addr, par->ramdac == IBM ? + 0x400000 : 0x800000); + info->fix.mmio_start = addr + 0x800000; + par->dc_regs = ioremap(addr + 0x800000, 0x1000); + par->cmap_regs_phys = addr + 0x840000; + par->cmap_regs = (__u8 *)ioremap(addr + 0x840000, 0x1000); + info->pseudo_palette = par->palette; + init_imstt(info); + + pci_set_drvdata(pdev, info); + return 0; +} + +static void imsttfb_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct imstt_par *par = info->par; + int size = pci_resource_len(pdev, 0); + + unregister_framebuffer(info); + iounmap(par->cmap_regs); + iounmap(par->dc_regs); + iounmap(info->screen_base); + release_mem_region(info->fix.smem_start, size); + framebuffer_release(info); +} + +#ifndef MODULE +static int __init +imsttfb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "font:", 5)) { + char *p; + int i; + + p = this_opt + 5; + for (i = 0; i < sizeof(fontname) - 1; i++) + if (!*p || *p == ' ' || *p == ',') + break; + memcpy(fontname, this_opt + 5, i); + fontname[i] = 0; + } else if (!strncmp(this_opt, "inverse", 7)) { + inverse = 1; + fb_invert_cmaps(); + } +#if defined(CONFIG_PPC) + else if (!strncmp(this_opt, "vmode:", 6)) { + int vmode = simple_strtoul(this_opt+6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX) + init_vmode = vmode; + } else if (!strncmp(this_opt, "cmode:", 6)) { + int cmode = simple_strtoul(this_opt+6, NULL, 0); + switch (cmode) { + case CMODE_8: + case 8: + init_cmode = CMODE_8; + break; + case CMODE_16: + case 15: + case 16: + init_cmode = CMODE_16; + break; + case CMODE_32: + case 24: + case 32: + init_cmode = CMODE_32; + break; + } + } +#endif + } + return 0; +} + +#endif /* MODULE */ + +static int __init imsttfb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("imsttfb", &option)) + return -ENODEV; + + imsttfb_setup(option); +#endif + return pci_register_driver(&imsttfb_pci_driver); +} + +static void __exit imsttfb_exit(void) +{ + pci_unregister_driver(&imsttfb_pci_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(imsttfb_init); +module_exit(imsttfb_exit); + diff --git a/drivers/video/fbdev/imxfb.c b/drivers/video/fbdev/imxfb.c new file mode 100644 index 000000000000..f6e621684953 --- /dev/null +++ b/drivers/video/fbdev/imxfb.c @@ -0,0 +1,1075 @@ +/* + * Freescale i.MX Frame Buffer device driver + * + * Copyright (C) 2004 Sascha Hauer, Pengutronix + * Based on acornfb.c Copyright (C) Russell King. + * + * 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. + * + * Please direct your questions and comments on this driver to the following + * email address: + * + * linux-arm-kernel@lists.arm.linux.org.uk + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/cpufreq.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/lcd.h> +#include <linux/math64.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <linux/regulator/consumer.h> + +#include <video/of_display_timing.h> +#include <video/of_videomode.h> +#include <video/videomode.h> + +#include <linux/platform_data/video-imxfb.h> + +/* + * Complain if VAR is out of range. + */ +#define DEBUG_VAR 1 + +#define DRIVER_NAME "imx-fb" + +#define LCDC_SSA 0x00 + +#define LCDC_SIZE 0x04 +#define SIZE_XMAX(x) ((((x) >> 4) & 0x3f) << 20) + +#define YMAX_MASK_IMX1 0x1ff +#define YMAX_MASK_IMX21 0x3ff + +#define LCDC_VPW 0x08 +#define VPW_VPW(x) ((x) & 0x3ff) + +#define LCDC_CPOS 0x0C +#define CPOS_CC1 (1<<31) +#define CPOS_CC0 (1<<30) +#define CPOS_OP (1<<28) +#define CPOS_CXP(x) (((x) & 3ff) << 16) + +#define LCDC_LCWHB 0x10 +#define LCWHB_BK_EN (1<<31) +#define LCWHB_CW(w) (((w) & 0x1f) << 24) +#define LCWHB_CH(h) (((h) & 0x1f) << 16) +#define LCWHB_BD(x) ((x) & 0xff) + +#define LCDC_LCHCC 0x14 + +#define LCDC_PCR 0x18 + +#define LCDC_HCR 0x1C +#define HCR_H_WIDTH(x) (((x) & 0x3f) << 26) +#define HCR_H_WAIT_1(x) (((x) & 0xff) << 8) +#define HCR_H_WAIT_2(x) ((x) & 0xff) + +#define LCDC_VCR 0x20 +#define VCR_V_WIDTH(x) (((x) & 0x3f) << 26) +#define VCR_V_WAIT_1(x) (((x) & 0xff) << 8) +#define VCR_V_WAIT_2(x) ((x) & 0xff) + +#define LCDC_POS 0x24 +#define POS_POS(x) ((x) & 1f) + +#define LCDC_LSCR1 0x28 +/* bit fields in imxfb.h */ + +#define LCDC_PWMR 0x2C +/* bit fields in imxfb.h */ + +#define LCDC_DMACR 0x30 +/* bit fields in imxfb.h */ + +#define LCDC_RMCR 0x34 + +#define RMCR_LCDC_EN_MX1 (1<<1) + +#define RMCR_SELF_REF (1<<0) + +#define LCDC_LCDICR 0x38 +#define LCDICR_INT_SYN (1<<2) +#define LCDICR_INT_CON (1) + +#define LCDC_LCDISR 0x40 +#define LCDISR_UDR_ERR (1<<3) +#define LCDISR_ERR_RES (1<<2) +#define LCDISR_EOF (1<<1) +#define LCDISR_BOF (1<<0) + +#define IMXFB_LSCR1_DEFAULT 0x00120300 + +/* Used fb-mode. Can be set on kernel command line, therefore file-static. */ +static const char *fb_mode; + +/* + * These are the bitfields for each + * display depth that we support. + */ +struct imxfb_rgb { + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; +}; + +enum imxfb_type { + IMX1_FB, + IMX21_FB, +}; + +struct imxfb_info { + struct platform_device *pdev; + void __iomem *regs; + struct clk *clk_ipg; + struct clk *clk_ahb; + struct clk *clk_per; + enum imxfb_type devtype; + bool enabled; + + /* + * These are the addresses we mapped + * the framebuffer memory region to. + */ + dma_addr_t map_dma; + u_int map_size; + + u_int palette_size; + + dma_addr_t dbar1; + dma_addr_t dbar2; + + u_int pcr; + u_int pwmr; + u_int lscr1; + u_int dmacr; + bool cmap_inverse; + bool cmap_static; + + struct imx_fb_videomode *mode; + int num_modes; + + struct regulator *lcd_pwr; +}; + +static struct platform_device_id imxfb_devtype[] = { + { + .name = "imx1-fb", + .driver_data = IMX1_FB, + }, { + .name = "imx21-fb", + .driver_data = IMX21_FB, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, imxfb_devtype); + +static struct of_device_id imxfb_of_dev_id[] = { + { + .compatible = "fsl,imx1-fb", + .data = &imxfb_devtype[IMX1_FB], + }, { + .compatible = "fsl,imx21-fb", + .data = &imxfb_devtype[IMX21_FB], + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, imxfb_of_dev_id); + +static inline int is_imx1_fb(struct imxfb_info *fbi) +{ + return fbi->devtype == IMX1_FB; +} + +#define IMX_NAME "IMX" + +/* + * Minimum X and Y resolutions + */ +#define MIN_XRES 64 +#define MIN_YRES 64 + +/* Actually this really is 18bit support, the lowest 2 bits of each colour + * are unused in hardware. We claim to have 24bit support to make software + * like X work, which does not support 18bit. + */ +static struct imxfb_rgb def_rgb_18 = { + .red = {.offset = 16, .length = 8,}, + .green = {.offset = 8, .length = 8,}, + .blue = {.offset = 0, .length = 8,}, + .transp = {.offset = 0, .length = 0,}, +}; + +static struct imxfb_rgb def_rgb_16_tft = { + .red = {.offset = 11, .length = 5,}, + .green = {.offset = 5, .length = 6,}, + .blue = {.offset = 0, .length = 5,}, + .transp = {.offset = 0, .length = 0,}, +}; + +static struct imxfb_rgb def_rgb_16_stn = { + .red = {.offset = 8, .length = 4,}, + .green = {.offset = 4, .length = 4,}, + .blue = {.offset = 0, .length = 4,}, + .transp = {.offset = 0, .length = 0,}, +}; + +static struct imxfb_rgb def_rgb_8 = { + .red = {.offset = 0, .length = 8,}, + .green = {.offset = 0, .length = 8,}, + .blue = {.offset = 0, .length = 8,}, + .transp = {.offset = 0, .length = 0,}, +}; + +static int imxfb_activate_var(struct fb_var_screeninfo *var, + struct fb_info *info); + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int imxfb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + struct imxfb_info *fbi = info->par; + u_int val, ret = 1; + +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + if (regno < fbi->palette_size) { + val = (CNVT_TOHW(red, 4) << 8) | + (CNVT_TOHW(green,4) << 4) | + CNVT_TOHW(blue, 4); + + writel(val, fbi->regs + 0x800 + (regno << 2)); + ret = 0; + } + return ret; +} + +static int imxfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + struct imxfb_info *fbi = info->par; + unsigned int val; + int ret = 1; + + /* + * If inverse mode was selected, invert all the colours + * rather than the register number. The register number + * is what you poke into the framebuffer to produce the + * colour you requested. + */ + if (fbi->cmap_inverse) { + red = 0xffff - red; + green = 0xffff - green; + blue = 0xffff - blue; + } + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no mater what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 12 or 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + ret = imxfb_setpalettereg(regno, red, green, blue, trans, info); + break; + } + + return ret; +} + +static const struct imx_fb_videomode *imxfb_find_mode(struct imxfb_info *fbi) +{ + struct imx_fb_videomode *m; + int i; + + if (!fb_mode) + return &fbi->mode[0]; + + for (i = 0, m = &fbi->mode[0]; i < fbi->num_modes; i++, m++) { + if (!strcmp(m->mode.name, fb_mode)) + return m; + } + return NULL; +} + +/* + * imxfb_check_var(): + * Round up in the following order: bits_per_pixel, xres, + * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale, + * bitfields, horizontal timing, vertical timing. + */ +static int imxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct imxfb_info *fbi = info->par; + struct imxfb_rgb *rgb; + const struct imx_fb_videomode *imxfb_mode; + unsigned long lcd_clk; + unsigned long long tmp; + u32 pcr = 0; + + if (var->xres < MIN_XRES) + var->xres = MIN_XRES; + if (var->yres < MIN_YRES) + var->yres = MIN_YRES; + + imxfb_mode = imxfb_find_mode(fbi); + if (!imxfb_mode) + return -EINVAL; + + var->xres = imxfb_mode->mode.xres; + var->yres = imxfb_mode->mode.yres; + var->bits_per_pixel = imxfb_mode->bpp; + var->pixclock = imxfb_mode->mode.pixclock; + var->hsync_len = imxfb_mode->mode.hsync_len; + var->left_margin = imxfb_mode->mode.left_margin; + var->right_margin = imxfb_mode->mode.right_margin; + var->vsync_len = imxfb_mode->mode.vsync_len; + var->upper_margin = imxfb_mode->mode.upper_margin; + var->lower_margin = imxfb_mode->mode.lower_margin; + var->sync = imxfb_mode->mode.sync; + var->xres_virtual = max(var->xres_virtual, var->xres); + var->yres_virtual = max(var->yres_virtual, var->yres); + + pr_debug("var->bits_per_pixel=%d\n", var->bits_per_pixel); + + lcd_clk = clk_get_rate(fbi->clk_per); + + tmp = var->pixclock * (unsigned long long)lcd_clk; + + do_div(tmp, 1000000); + + if (do_div(tmp, 1000000) > 500000) + tmp++; + + pcr = (unsigned int)tmp; + + if (--pcr > 0x3F) { + pcr = 0x3F; + printk(KERN_WARNING "Must limit pixel clock to %luHz\n", + lcd_clk / pcr); + } + + switch (var->bits_per_pixel) { + case 32: + pcr |= PCR_BPIX_18; + rgb = &def_rgb_18; + break; + case 16: + default: + if (is_imx1_fb(fbi)) + pcr |= PCR_BPIX_12; + else + pcr |= PCR_BPIX_16; + + if (imxfb_mode->pcr & PCR_TFT) + rgb = &def_rgb_16_tft; + else + rgb = &def_rgb_16_stn; + break; + case 8: + pcr |= PCR_BPIX_8; + rgb = &def_rgb_8; + break; + } + + /* add sync polarities */ + pcr |= imxfb_mode->pcr & ~(0x3f | (7 << 25)); + + fbi->pcr = pcr; + + /* + * Copy the RGB parameters for this display + * from the machine specific parameters. + */ + var->red = rgb->red; + var->green = rgb->green; + var->blue = rgb->blue; + var->transp = rgb->transp; + + pr_debug("RGBT length = %d:%d:%d:%d\n", + var->red.length, var->green.length, var->blue.length, + var->transp.length); + + pr_debug("RGBT offset = %d:%d:%d:%d\n", + var->red.offset, var->green.offset, var->blue.offset, + var->transp.offset); + + return 0; +} + +/* + * imxfb_set_par(): + * Set the user defined part of the display for the specified console + */ +static int imxfb_set_par(struct fb_info *info) +{ + struct imxfb_info *fbi = info->par; + struct fb_var_screeninfo *var = &info->var; + + if (var->bits_per_pixel == 16 || var->bits_per_pixel == 32) + info->fix.visual = FB_VISUAL_TRUECOLOR; + else if (!fbi->cmap_static) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else { + /* + * Some people have weird ideas about wanting static + * pseudocolor maps. I suspect their user space + * applications are broken. + */ + info->fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + } + + info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; + fbi->palette_size = var->bits_per_pixel == 8 ? 256 : 16; + + imxfb_activate_var(var, info); + + return 0; +} + +static void imxfb_enable_controller(struct imxfb_info *fbi) +{ + + if (fbi->enabled) + return; + + pr_debug("Enabling LCD controller\n"); + + writel(fbi->map_dma, fbi->regs + LCDC_SSA); + + /* panning offset 0 (0 pixel offset) */ + writel(0x00000000, fbi->regs + LCDC_POS); + + /* disable hardware cursor */ + writel(readl(fbi->regs + LCDC_CPOS) & ~(CPOS_CC0 | CPOS_CC1), + fbi->regs + LCDC_CPOS); + + /* + * RMCR_LCDC_EN_MX1 is present on i.MX1 only, but doesn't hurt + * on other SoCs + */ + writel(RMCR_LCDC_EN_MX1, fbi->regs + LCDC_RMCR); + + clk_prepare_enable(fbi->clk_ipg); + clk_prepare_enable(fbi->clk_ahb); + clk_prepare_enable(fbi->clk_per); + fbi->enabled = true; +} + +static void imxfb_disable_controller(struct imxfb_info *fbi) +{ + if (!fbi->enabled) + return; + + pr_debug("Disabling LCD controller\n"); + + clk_disable_unprepare(fbi->clk_per); + clk_disable_unprepare(fbi->clk_ipg); + clk_disable_unprepare(fbi->clk_ahb); + fbi->enabled = false; + + writel(0, fbi->regs + LCDC_RMCR); +} + +static int imxfb_blank(int blank, struct fb_info *info) +{ + struct imxfb_info *fbi = info->par; + + pr_debug("imxfb_blank: blank=%d\n", blank); + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + imxfb_disable_controller(fbi); + break; + + case FB_BLANK_UNBLANK: + imxfb_enable_controller(fbi); + break; + } + return 0; +} + +static struct fb_ops imxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = imxfb_check_var, + .fb_set_par = imxfb_set_par, + .fb_setcolreg = imxfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = imxfb_blank, +}; + +/* + * imxfb_activate_var(): + * Configures LCD Controller based on entries in var parameter. Settings are + * only written to the controller if changes were made. + */ +static int imxfb_activate_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct imxfb_info *fbi = info->par; + u32 ymax_mask = is_imx1_fb(fbi) ? YMAX_MASK_IMX1 : YMAX_MASK_IMX21; + + pr_debug("var: xres=%d hslen=%d lm=%d rm=%d\n", + var->xres, var->hsync_len, + var->left_margin, var->right_margin); + pr_debug("var: yres=%d vslen=%d um=%d bm=%d\n", + var->yres, var->vsync_len, + var->upper_margin, var->lower_margin); + +#if DEBUG_VAR + if (var->xres < 16 || var->xres > 1024) + printk(KERN_ERR "%s: invalid xres %d\n", + info->fix.id, var->xres); + if (var->hsync_len < 1 || var->hsync_len > 64) + printk(KERN_ERR "%s: invalid hsync_len %d\n", + info->fix.id, var->hsync_len); + if (var->left_margin > 255) + printk(KERN_ERR "%s: invalid left_margin %d\n", + info->fix.id, var->left_margin); + if (var->right_margin > 255) + printk(KERN_ERR "%s: invalid right_margin %d\n", + info->fix.id, var->right_margin); + if (var->yres < 1 || var->yres > ymax_mask) + printk(KERN_ERR "%s: invalid yres %d\n", + info->fix.id, var->yres); + if (var->vsync_len > 100) + printk(KERN_ERR "%s: invalid vsync_len %d\n", + info->fix.id, var->vsync_len); + if (var->upper_margin > 63) + printk(KERN_ERR "%s: invalid upper_margin %d\n", + info->fix.id, var->upper_margin); + if (var->lower_margin > 255) + printk(KERN_ERR "%s: invalid lower_margin %d\n", + info->fix.id, var->lower_margin); +#endif + + /* physical screen start address */ + writel(VPW_VPW(var->xres * var->bits_per_pixel / 8 / 4), + fbi->regs + LCDC_VPW); + + writel(HCR_H_WIDTH(var->hsync_len - 1) | + HCR_H_WAIT_1(var->right_margin - 1) | + HCR_H_WAIT_2(var->left_margin - 3), + fbi->regs + LCDC_HCR); + + writel(VCR_V_WIDTH(var->vsync_len) | + VCR_V_WAIT_1(var->lower_margin) | + VCR_V_WAIT_2(var->upper_margin), + fbi->regs + LCDC_VCR); + + writel(SIZE_XMAX(var->xres) | (var->yres & ymax_mask), + fbi->regs + LCDC_SIZE); + + writel(fbi->pcr, fbi->regs + LCDC_PCR); + if (fbi->pwmr) + writel(fbi->pwmr, fbi->regs + LCDC_PWMR); + writel(fbi->lscr1, fbi->regs + LCDC_LSCR1); + + /* dmacr = 0 is no valid value, as we need DMA control marks. */ + if (fbi->dmacr) + writel(fbi->dmacr, fbi->regs + LCDC_DMACR); + + return 0; +} + +static int imxfb_init_fbinfo(struct platform_device *pdev) +{ + struct imx_fb_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct fb_info *info = dev_get_drvdata(&pdev->dev); + struct imxfb_info *fbi = info->par; + struct device_node *np; + + pr_debug("%s\n",__func__); + + info->pseudo_palette = kmalloc(sizeof(u32) * 16, GFP_KERNEL); + if (!info->pseudo_palette) + return -ENOMEM; + + memset(fbi, 0, sizeof(struct imxfb_info)); + + fbi->devtype = pdev->id_entry->driver_data; + + strlcpy(info->fix.id, IMX_NAME, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.accel = FB_ACCEL_NONE; + + info->var.nonstd = 0; + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.accel_flags = 0; + info->var.vmode = FB_VMODE_NONINTERLACED; + + info->fbops = &imxfb_ops; + info->flags = FBINFO_FLAG_DEFAULT | + FBINFO_READS_FAST; + if (pdata) { + fbi->lscr1 = pdata->lscr1; + fbi->dmacr = pdata->dmacr; + fbi->pwmr = pdata->pwmr; + } else { + np = pdev->dev.of_node; + info->var.grayscale = of_property_read_bool(np, + "cmap-greyscale"); + fbi->cmap_inverse = of_property_read_bool(np, "cmap-inverse"); + fbi->cmap_static = of_property_read_bool(np, "cmap-static"); + + fbi->lscr1 = IMXFB_LSCR1_DEFAULT; + + of_property_read_u32(np, "fsl,lpccr", &fbi->pwmr); + + of_property_read_u32(np, "fsl,lscr1", &fbi->lscr1); + + of_property_read_u32(np, "fsl,dmacr", &fbi->dmacr); + } + + return 0; +} + +static int imxfb_of_read_mode(struct device *dev, struct device_node *np, + struct imx_fb_videomode *imxfb_mode) +{ + int ret; + struct fb_videomode *of_mode = &imxfb_mode->mode; + u32 bpp; + u32 pcr; + + ret = of_property_read_string(np, "model", &of_mode->name); + if (ret) + of_mode->name = NULL; + + ret = of_get_fb_videomode(np, of_mode, OF_USE_NATIVE_MODE); + if (ret) { + dev_err(dev, "Failed to get videomode from DT\n"); + return ret; + } + + ret = of_property_read_u32(np, "bits-per-pixel", &bpp); + ret |= of_property_read_u32(np, "fsl,pcr", &pcr); + + if (ret) { + dev_err(dev, "Failed to read bpp and pcr from DT\n"); + return -EINVAL; + } + + if (bpp < 1 || bpp > 255) { + dev_err(dev, "Bits per pixel have to be between 1 and 255\n"); + return -EINVAL; + } + + imxfb_mode->bpp = bpp; + imxfb_mode->pcr = pcr; + + return 0; +} + +static int imxfb_lcd_check_fb(struct lcd_device *lcddev, struct fb_info *fi) +{ + struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); + + if (!fi || fi->par == fbi) + return 1; + + return 0; +} + +static int imxfb_lcd_get_contrast(struct lcd_device *lcddev) +{ + struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); + + return fbi->pwmr & 0xff; +} + +static int imxfb_lcd_set_contrast(struct lcd_device *lcddev, int contrast) +{ + struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); + + if (fbi->pwmr && fbi->enabled) { + if (contrast > 255) + contrast = 255; + else if (contrast < 0) + contrast = 0; + + fbi->pwmr &= ~0xff; + fbi->pwmr |= contrast; + + writel(fbi->pwmr, fbi->regs + LCDC_PWMR); + } + + return 0; +} + +static int imxfb_lcd_get_power(struct lcd_device *lcddev) +{ + struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); + + if (!IS_ERR(fbi->lcd_pwr)) + return regulator_is_enabled(fbi->lcd_pwr); + + return 1; +} + +static int imxfb_lcd_set_power(struct lcd_device *lcddev, int power) +{ + struct imxfb_info *fbi = dev_get_drvdata(&lcddev->dev); + + if (!IS_ERR(fbi->lcd_pwr)) { + if (power) + return regulator_enable(fbi->lcd_pwr); + else + return regulator_disable(fbi->lcd_pwr); + } + + return 0; +} + +static struct lcd_ops imxfb_lcd_ops = { + .check_fb = imxfb_lcd_check_fb, + .get_contrast = imxfb_lcd_get_contrast, + .set_contrast = imxfb_lcd_set_contrast, + .get_power = imxfb_lcd_get_power, + .set_power = imxfb_lcd_set_power, +}; + +static int imxfb_setup(void) +{ + char *opt, *options = NULL; + + if (fb_get_options("imxfb", &options)) + return -ENODEV; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + else + fb_mode = opt; + } + + return 0; +} + +static int imxfb_probe(struct platform_device *pdev) +{ + struct imxfb_info *fbi; + struct lcd_device *lcd; + struct fb_info *info; + struct imx_fb_platform_data *pdata; + struct resource *res; + struct imx_fb_videomode *m; + const struct of_device_id *of_id; + int ret, i; + int bytes_per_pixel; + + dev_info(&pdev->dev, "i.MX Framebuffer driver\n"); + + ret = imxfb_setup(); + if (ret < 0) + return ret; + + of_id = of_match_device(imxfb_of_dev_id, &pdev->dev); + if (of_id) + pdev->id_entry = of_id->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pdata = dev_get_platdata(&pdev->dev); + + info = framebuffer_alloc(sizeof(struct imxfb_info), &pdev->dev); + if (!info) + return -ENOMEM; + + fbi = info->par; + + platform_set_drvdata(pdev, info); + + ret = imxfb_init_fbinfo(pdev); + if (ret < 0) + goto failed_init; + + if (pdata) { + if (!fb_mode) + fb_mode = pdata->mode[0].mode.name; + + fbi->mode = pdata->mode; + fbi->num_modes = pdata->num_modes; + } else { + struct device_node *display_np; + fb_mode = NULL; + + display_np = of_parse_phandle(pdev->dev.of_node, "display", 0); + if (!display_np) { + dev_err(&pdev->dev, "No display defined in devicetree\n"); + ret = -EINVAL; + goto failed_of_parse; + } + + /* + * imxfb does not support more modes, we choose only the native + * mode. + */ + fbi->num_modes = 1; + + fbi->mode = devm_kzalloc(&pdev->dev, + sizeof(struct imx_fb_videomode), GFP_KERNEL); + if (!fbi->mode) { + ret = -ENOMEM; + goto failed_of_parse; + } + + ret = imxfb_of_read_mode(&pdev->dev, display_np, fbi->mode); + if (ret) + goto failed_of_parse; + } + + /* Calculate maximum bytes used per pixel. In most cases this should + * be the same as m->bpp/8 */ + m = &fbi->mode[0]; + bytes_per_pixel = (m->bpp + 7) / 8; + for (i = 0; i < fbi->num_modes; i++, m++) + info->fix.smem_len = max_t(size_t, info->fix.smem_len, + m->mode.xres * m->mode.yres * bytes_per_pixel); + + res = request_mem_region(res->start, resource_size(res), + DRIVER_NAME); + if (!res) { + ret = -EBUSY; + goto failed_req; + } + + fbi->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(fbi->clk_ipg)) { + ret = PTR_ERR(fbi->clk_ipg); + goto failed_getclock; + } + + fbi->clk_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(fbi->clk_ahb)) { + ret = PTR_ERR(fbi->clk_ahb); + goto failed_getclock; + } + + fbi->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(fbi->clk_per)) { + ret = PTR_ERR(fbi->clk_per); + goto failed_getclock; + } + + fbi->regs = ioremap(res->start, resource_size(res)); + if (fbi->regs == NULL) { + dev_err(&pdev->dev, "Cannot map frame buffer registers\n"); + ret = -ENOMEM; + goto failed_ioremap; + } + + fbi->map_size = PAGE_ALIGN(info->fix.smem_len); + info->screen_base = dma_alloc_writecombine(&pdev->dev, fbi->map_size, + &fbi->map_dma, GFP_KERNEL); + + if (!info->screen_base) { + dev_err(&pdev->dev, "Failed to allocate video RAM: %d\n", ret); + ret = -ENOMEM; + goto failed_map; + } + + info->fix.smem_start = fbi->map_dma; + + if (pdata && pdata->init) { + ret = pdata->init(fbi->pdev); + if (ret) + goto failed_platform_init; + } + + + INIT_LIST_HEAD(&info->modelist); + for (i = 0; i < fbi->num_modes; i++) + fb_add_videomode(&fbi->mode[i].mode, &info->modelist); + + /* + * This makes sure that our colour bitfield + * descriptors are correctly initialised. + */ + imxfb_check_var(&info->var, info); + + ret = fb_alloc_cmap(&info->cmap, 1 << info->var.bits_per_pixel, 0); + if (ret < 0) + goto failed_cmap; + + imxfb_set_par(info); + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register framebuffer\n"); + goto failed_register; + } + + fbi->lcd_pwr = devm_regulator_get(&pdev->dev, "lcd"); + if (IS_ERR(fbi->lcd_pwr) && (PTR_ERR(fbi->lcd_pwr) == -EPROBE_DEFER)) { + ret = -EPROBE_DEFER; + goto failed_lcd; + } + + lcd = devm_lcd_device_register(&pdev->dev, "imxfb-lcd", &pdev->dev, fbi, + &imxfb_lcd_ops); + if (IS_ERR(lcd)) { + ret = PTR_ERR(lcd); + goto failed_lcd; + } + + lcd->props.max_contrast = 0xff; + + imxfb_enable_controller(fbi); + fbi->pdev = pdev; + + return 0; + +failed_lcd: + unregister_framebuffer(info); + +failed_register: + fb_dealloc_cmap(&info->cmap); +failed_cmap: + if (pdata && pdata->exit) + pdata->exit(fbi->pdev); +failed_platform_init: + dma_free_writecombine(&pdev->dev, fbi->map_size, info->screen_base, + fbi->map_dma); +failed_map: + iounmap(fbi->regs); +failed_ioremap: +failed_getclock: + release_mem_region(res->start, resource_size(res)); +failed_req: +failed_of_parse: + kfree(info->pseudo_palette); +failed_init: + framebuffer_release(info); + return ret; +} + +static int imxfb_remove(struct platform_device *pdev) +{ + struct imx_fb_platform_data *pdata; + struct fb_info *info = platform_get_drvdata(pdev); + struct imxfb_info *fbi = info->par; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + imxfb_disable_controller(fbi); + + unregister_framebuffer(info); + + pdata = dev_get_platdata(&pdev->dev); + if (pdata && pdata->exit) + pdata->exit(fbi->pdev); + + fb_dealloc_cmap(&info->cmap); + kfree(info->pseudo_palette); + framebuffer_release(info); + + dma_free_writecombine(&pdev->dev, fbi->map_size, info->screen_base, + fbi->map_dma); + + iounmap(fbi->regs); + release_mem_region(res->start, resource_size(res)); + + return 0; +} + +static int __maybe_unused imxfb_suspend(struct device *dev) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct imxfb_info *fbi = info->par; + + imxfb_disable_controller(fbi); + + return 0; +} + +static int __maybe_unused imxfb_resume(struct device *dev) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct imxfb_info *fbi = info->par; + + imxfb_enable_controller(fbi); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(imxfb_pm_ops, imxfb_suspend, imxfb_resume); + +static struct platform_driver imxfb_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = imxfb_of_dev_id, + .owner = THIS_MODULE, + .pm = &imxfb_pm_ops, + }, + .probe = imxfb_probe, + .remove = imxfb_remove, + .id_table = imxfb_devtype, +}; +module_platform_driver(imxfb_driver); + +MODULE_DESCRIPTION("Freescale i.MX framebuffer driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/intelfb/Makefile b/drivers/video/fbdev/intelfb/Makefile new file mode 100644 index 000000000000..f7d631ebee8e --- /dev/null +++ b/drivers/video/fbdev/intelfb/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_FB_INTEL) += intelfb.o + +intelfb-y := intelfbdrv.o intelfbhw.o +intelfb-$(CONFIG_FB_INTEL_I2C) += intelfb_i2c.o +intelfb-objs := $(intelfb-y) + +ccflags-$(CONFIG_FB_INTEL_DEBUG) := -DDEBUG -DREGDUMP diff --git a/drivers/video/fbdev/intelfb/intelfb.h b/drivers/video/fbdev/intelfb/intelfb.h new file mode 100644 index 000000000000..6b51175629c7 --- /dev/null +++ b/drivers/video/fbdev/intelfb/intelfb.h @@ -0,0 +1,383 @@ +#ifndef _INTELFB_H +#define _INTELFB_H + +/* $DHD: intelfb/intelfb.h,v 1.40 2003/06/27 15:06:25 dawes Exp $ */ + +#include <linux/agp_backend.h> +#include <linux/fb.h> + +#ifdef CONFIG_FB_INTEL_I2C +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#endif + +/*** Version/name ***/ +#define INTELFB_VERSION "0.9.6" +#define INTELFB_MODULE_NAME "intelfb" +#define SUPPORTED_CHIPSETS "830M/845G/852GM/855GM/865G/915G/915GM/945G/945GM/945GME/965G/965GM" + + +/*** Debug/feature defines ***/ + +#ifndef DEBUG +#define DEBUG 0 +#endif + +#ifndef VERBOSE +#define VERBOSE 0 +#endif + +#ifndef REGDUMP +#define REGDUMP 0 +#endif + +#ifndef DETECT_VGA_CLASS_ONLY +#define DETECT_VGA_CLASS_ONLY 1 +#endif + +#ifndef ALLOCATE_FOR_PANNING +#define ALLOCATE_FOR_PANNING 1 +#endif + +#ifndef PREFERRED_MODE +#define PREFERRED_MODE "1024x768-32@70" +#endif + +/*** hw-related values ***/ + +/* Resource Allocation */ +#define INTELFB_FB_ACQUIRED 1 +#define INTELFB_MMIO_ACQUIRED 2 + +/* PCI ids for supported devices */ +#define PCI_DEVICE_ID_INTEL_830M 0x3577 +#define PCI_DEVICE_ID_INTEL_845G 0x2562 +#define PCI_DEVICE_ID_INTEL_85XGM 0x3582 +#define PCI_DEVICE_ID_INTEL_854 0x358E +#define PCI_DEVICE_ID_INTEL_865G 0x2572 +#define PCI_DEVICE_ID_INTEL_915G 0x2582 +#define PCI_DEVICE_ID_INTEL_915GM 0x2592 +#define PCI_DEVICE_ID_INTEL_945G 0x2772 +#define PCI_DEVICE_ID_INTEL_945GM 0x27A2 +#define PCI_DEVICE_ID_INTEL_945GME 0x27AE +#define PCI_DEVICE_ID_INTEL_965G 0x29A2 +#define PCI_DEVICE_ID_INTEL_965GM 0x2A02 + +/* Size of MMIO region */ +#define INTEL_REG_SIZE 0x80000 + +#define STRIDE_ALIGNMENT 16 +#define STRIDE_ALIGNMENT_I9XX 64 + +#define PALETTE_8_ENTRIES 256 + + +/*** Macros ***/ + +/* basic arithmetic */ +#define KB(x) ((x) * 1024) +#define MB(x) ((x) * 1024 * 1024) +#define BtoKB(x) ((x) / 1024) +#define BtoMB(x) ((x) / 1024 / 1024) + +#define GTT_PAGE_SIZE KB(4) + +#define ROUND_UP_TO(x, y) (((x) + (y) - 1) / (y) * (y)) +#define ROUND_DOWN_TO(x, y) ((x) / (y) * (y)) +#define ROUND_UP_TO_PAGE(x) ROUND_UP_TO((x), GTT_PAGE_SIZE) +#define ROUND_DOWN_TO_PAGE(x) ROUND_DOWN_TO((x), GTT_PAGE_SIZE) + +/* messages */ +#define PFX INTELFB_MODULE_NAME ": " + +#define ERR_MSG(fmt, args...) printk(KERN_ERR PFX fmt, ## args) +#define WRN_MSG(fmt, args...) printk(KERN_WARNING PFX fmt, ## args) +#define NOT_MSG(fmt, args...) printk(KERN_NOTICE PFX fmt, ## args) +#define INF_MSG(fmt, args...) printk(KERN_INFO PFX fmt, ## args) +#if DEBUG +#define DBG_MSG(fmt, args...) printk(KERN_DEBUG PFX fmt, ## args) +#else +#define DBG_MSG(fmt, args...) while (0) printk(fmt, ## args) +#endif + +/* get commonly used pointers */ +#define GET_DINFO(info) (info)->par + +/* misc macros */ +#define ACCEL(d, i) \ + ((d)->accel && !(d)->ring_lockup && \ + ((i)->var.accel_flags & FB_ACCELF_TEXT)) + +/*#define NOACCEL_CHIPSET(d) \ + ((d)->chipset != INTEL_865G)*/ +#define NOACCEL_CHIPSET(d) \ + (0) + +#define FIXED_MODE(d) ((d)->fixed_mode) + +/*** Driver parameters ***/ + +#define RINGBUFFER_SIZE KB(64) +#define HW_CURSOR_SIZE KB(4) + +/* Intel agpgart driver */ +#define AGP_PHYSICAL_MEMORY 2 + +/* store information about an Ixxx DVO */ +/* The i830->i865 use multiple DVOs with multiple i2cs */ +/* the i915, i945 have a single sDVO i2c bus - which is different */ +#define MAX_OUTPUTS 6 + +/* these are outputs from the chip - integrated only + external chips are via DVO or SDVO output */ +#define INTELFB_OUTPUT_UNUSED 0 +#define INTELFB_OUTPUT_ANALOG 1 +#define INTELFB_OUTPUT_DVO 2 +#define INTELFB_OUTPUT_SDVO 3 +#define INTELFB_OUTPUT_LVDS 4 +#define INTELFB_OUTPUT_TVOUT 5 + +#define INTELFB_DVO_CHIP_NONE 0 +#define INTELFB_DVO_CHIP_LVDS 1 +#define INTELFB_DVO_CHIP_TMDS 2 +#define INTELFB_DVO_CHIP_TVOUT 4 + +#define INTELFB_OUTPUT_PIPE_NC 0 +#define INTELFB_OUTPUT_PIPE_A 1 +#define INTELFB_OUTPUT_PIPE_B 2 + +/*** Data Types ***/ + +/* supported chipsets */ +enum intel_chips { + INTEL_830M, + INTEL_845G, + INTEL_85XGM, + INTEL_852GM, + INTEL_852GME, + INTEL_854, + INTEL_855GM, + INTEL_855GME, + INTEL_865G, + INTEL_915G, + INTEL_915GM, + INTEL_945G, + INTEL_945GM, + INTEL_945GME, + INTEL_965G, + INTEL_965GM, +}; + +struct intelfb_hwstate { + u32 vga0_divisor; + u32 vga1_divisor; + u32 vga_pd; + u32 dpll_a; + u32 dpll_b; + u32 fpa0; + u32 fpa1; + u32 fpb0; + u32 fpb1; + u32 palette_a[PALETTE_8_ENTRIES]; + u32 palette_b[PALETTE_8_ENTRIES]; + u32 htotal_a; + u32 hblank_a; + u32 hsync_a; + u32 vtotal_a; + u32 vblank_a; + u32 vsync_a; + u32 src_size_a; + u32 bclrpat_a; + u32 htotal_b; + u32 hblank_b; + u32 hsync_b; + u32 vtotal_b; + u32 vblank_b; + u32 vsync_b; + u32 src_size_b; + u32 bclrpat_b; + u32 adpa; + u32 dvoa; + u32 dvob; + u32 dvoc; + u32 dvoa_srcdim; + u32 dvob_srcdim; + u32 dvoc_srcdim; + u32 lvds; + u32 pipe_a_conf; + u32 pipe_b_conf; + u32 disp_arb; + u32 cursor_a_control; + u32 cursor_b_control; + u32 cursor_a_base; + u32 cursor_b_base; + u32 cursor_size; + u32 disp_a_ctrl; + u32 disp_b_ctrl; + u32 disp_a_base; + u32 disp_b_base; + u32 cursor_a_palette[4]; + u32 cursor_b_palette[4]; + u32 disp_a_stride; + u32 disp_b_stride; + u32 vgacntrl; + u32 add_id; + u32 swf0x[7]; + u32 swf1x[7]; + u32 swf3x[3]; + u32 fence[8]; + u32 instpm; + u32 mem_mode; + u32 fw_blc_0; + u32 fw_blc_1; + u16 hwstam; + u16 ier; + u16 iir; + u16 imr; +}; + +struct intelfb_heap_data { + u32 physical; + u8 __iomem *virtual; + u32 offset; /* in GATT pages */ + u32 size; /* in bytes */ +}; + +#ifdef CONFIG_FB_INTEL_I2C +struct intelfb_i2c_chan { + struct intelfb_info *dinfo; + u32 reg; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo; +}; +#endif + +struct intelfb_output_rec { + int type; + int pipe; + int flags; + +#ifdef CONFIG_FB_INTEL_I2C + struct intelfb_i2c_chan i2c_bus; + struct intelfb_i2c_chan ddc_bus; +#endif +}; + +struct intelfb_vsync { + wait_queue_head_t wait; + unsigned int count; + int pan_display; + u32 pan_offset; +}; + +struct intelfb_info { + struct fb_info *info; + struct fb_ops *fbops; + struct pci_dev *pdev; + + struct intelfb_hwstate save_state; + + /* agpgart structs */ + struct agp_memory *gtt_fb_mem; /* use all stolen memory or vram */ + struct agp_memory *gtt_ring_mem; /* ring buffer */ + struct agp_memory *gtt_cursor_mem; /* hw cursor */ + + /* use a gart reserved fb mem */ + u8 fbmem_gart; + + /* mtrr support */ + int mtrr_reg; + u32 has_mtrr; + + /* heap data */ + struct intelfb_heap_data aperture; + struct intelfb_heap_data fb; + struct intelfb_heap_data ring; + struct intelfb_heap_data cursor; + + /* mmio regs */ + u32 mmio_base_phys; + u8 __iomem *mmio_base; + + /* fb start offset (in bytes) */ + u32 fb_start; + + /* ring buffer */ + u32 ring_head; + u32 ring_tail; + u32 ring_tail_mask; + u32 ring_space; + u32 ring_lockup; + + /* palette */ + u32 pseudo_palette[16]; + + /* chip info */ + int pci_chipset; + int chipset; + const char *name; + int mobile; + + /* current mode */ + int bpp, depth; + u32 visual; + int xres, yres, pitch; + int pixclock; + + /* current pipe */ + int pipe; + + /* some flags */ + int accel; + int hwcursor; + int fixed_mode; + int ring_active; + int flag; + unsigned long irq_flags; + int open; + + /* vsync */ + struct intelfb_vsync vsync; + spinlock_t int_lock; + + /* hw cursor */ + int cursor_on; + int cursor_blanked; + u8 cursor_src[64]; + + /* initial parameters */ + int initial_vga; + struct fb_var_screeninfo initial_var; + u32 initial_fb_base; + u32 initial_video_ram; + u32 initial_pitch; + + /* driver registered */ + int registered; + + /* index into plls */ + int pll_index; + + /* outputs */ + int num_outputs; + struct intelfb_output_rec output[MAX_OUTPUTS]; +}; + +#define IS_I9XX(dinfo) (((dinfo)->chipset == INTEL_915G) || \ + ((dinfo)->chipset == INTEL_915GM) || \ + ((dinfo)->chipset == INTEL_945G) || \ + ((dinfo)->chipset == INTEL_945GM) || \ + ((dinfo)->chipset == INTEL_945GME) || \ + ((dinfo)->chipset == INTEL_965G) || \ + ((dinfo)->chipset == INTEL_965GM)) + +/*** function prototypes ***/ + +extern int intelfb_var_to_depth(const struct fb_var_screeninfo *var); + +#ifdef CONFIG_FB_INTEL_I2C +extern void intelfb_create_i2c_busses(struct intelfb_info *dinfo); +extern void intelfb_delete_i2c_busses(struct intelfb_info *dinfo); +#endif + +#endif /* _INTELFB_H */ diff --git a/drivers/video/fbdev/intelfb/intelfb_i2c.c b/drivers/video/fbdev/intelfb/intelfb_i2c.c new file mode 100644 index 000000000000..3300bd31d9d7 --- /dev/null +++ b/drivers/video/fbdev/intelfb/intelfb_i2c.c @@ -0,0 +1,209 @@ +/************************************************************************** + + Copyright 2006 Dave Airlie <airlied@linux.ie> + +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +on the rights to use, copy, modify, merge, publish, distribute, sub +license, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL +THE COPYRIGHT HOLDERS AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +**************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/fb.h> + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <asm/io.h> + +#include "intelfb.h" +#include "intelfbhw.h" + +/* bit locations in the registers */ +#define SCL_DIR_MASK 0x0001 +#define SCL_DIR 0x0002 +#define SCL_VAL_MASK 0x0004 +#define SCL_VAL_OUT 0x0008 +#define SCL_VAL_IN 0x0010 +#define SDA_DIR_MASK 0x0100 +#define SDA_DIR 0x0200 +#define SDA_VAL_MASK 0x0400 +#define SDA_VAL_OUT 0x0800 +#define SDA_VAL_IN 0x1000 + +static void intelfb_gpio_setscl(void *data, int state) +{ + struct intelfb_i2c_chan *chan = data; + struct intelfb_info *dinfo = chan->dinfo; + u32 val; + + OUTREG(chan->reg, (state ? SCL_VAL_OUT : 0) | + SCL_DIR | SCL_DIR_MASK | SCL_VAL_MASK); + val = INREG(chan->reg); +} + +static void intelfb_gpio_setsda(void *data, int state) +{ + struct intelfb_i2c_chan *chan = data; + struct intelfb_info *dinfo = chan->dinfo; + u32 val; + + OUTREG(chan->reg, (state ? SDA_VAL_OUT : 0) | + SDA_DIR | SDA_DIR_MASK | SDA_VAL_MASK); + val = INREG(chan->reg); +} + +static int intelfb_gpio_getscl(void *data) +{ + struct intelfb_i2c_chan *chan = data; + struct intelfb_info *dinfo = chan->dinfo; + u32 val; + + OUTREG(chan->reg, SCL_DIR_MASK); + OUTREG(chan->reg, 0); + val = INREG(chan->reg); + return ((val & SCL_VAL_IN) != 0); +} + +static int intelfb_gpio_getsda(void *data) +{ + struct intelfb_i2c_chan *chan = data; + struct intelfb_info *dinfo = chan->dinfo; + u32 val; + + OUTREG(chan->reg, SDA_DIR_MASK); + OUTREG(chan->reg, 0); + val = INREG(chan->reg); + return ((val & SDA_VAL_IN) != 0); +} + +static int intelfb_setup_i2c_bus(struct intelfb_info *dinfo, + struct intelfb_i2c_chan *chan, + const u32 reg, const char *name, + int class) +{ + int rc; + + chan->dinfo = dinfo; + chan->reg = reg; + snprintf(chan->adapter.name, sizeof(chan->adapter.name), + "intelfb %s", name); + chan->adapter.class = class; + chan->adapter.owner = THIS_MODULE; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = &chan->dinfo->pdev->dev; + chan->algo.setsda = intelfb_gpio_setsda; + chan->algo.setscl = intelfb_gpio_setscl; + chan->algo.getsda = intelfb_gpio_getsda; + chan->algo.getscl = intelfb_gpio_getscl; + chan->algo.udelay = 40; + chan->algo.timeout = 20; + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + /* Raise SCL and SDA */ + intelfb_gpio_setsda(chan, 1); + intelfb_gpio_setscl(chan, 1); + udelay(20); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + DBG_MSG("I2C bus %s registered.\n", name); + else + WRN_MSG("Failed to register I2C bus %s.\n", name); + return rc; +} + +void intelfb_create_i2c_busses(struct intelfb_info *dinfo) +{ + int i = 0; + + /* everyone has at least a single analog output */ + dinfo->num_outputs = 1; + dinfo->output[i].type = INTELFB_OUTPUT_ANALOG; + + /* setup the DDC bus for analog output */ + intelfb_setup_i2c_bus(dinfo, &dinfo->output[i].ddc_bus, GPIOA, + "CRTDDC_A", I2C_CLASS_DDC); + i++; + + /* need to add the output busses for each device + - this function is very incomplete + - i915GM has LVDS and TVOUT for example + */ + switch(dinfo->chipset) { + case INTEL_830M: + case INTEL_845G: + case INTEL_854: + case INTEL_855GM: + case INTEL_865G: + dinfo->output[i].type = INTELFB_OUTPUT_DVO; + intelfb_setup_i2c_bus(dinfo, &dinfo->output[i].ddc_bus, + GPIOD, "DVODDC_D", I2C_CLASS_DDC); + intelfb_setup_i2c_bus(dinfo, &dinfo->output[i].i2c_bus, + GPIOE, "DVOI2C_E", 0); + i++; + break; + case INTEL_915G: + case INTEL_915GM: + /* has some LVDS + tv-out */ + case INTEL_945G: + case INTEL_945GM: + case INTEL_945GME: + case INTEL_965G: + case INTEL_965GM: + /* SDVO ports have a single control bus - 2 devices */ + dinfo->output[i].type = INTELFB_OUTPUT_SDVO; + intelfb_setup_i2c_bus(dinfo, &dinfo->output[i].i2c_bus, + GPIOE, "SDVOCTRL_E", 0); + /* TODO: initialize the SDVO */ + /* I830SDVOInit(pScrn, i, DVOB); */ + i++; + + /* set up SDVOC */ + dinfo->output[i].type = INTELFB_OUTPUT_SDVO; + dinfo->output[i].i2c_bus = dinfo->output[i - 1].i2c_bus; + /* TODO: initialize the SDVO */ + /* I830SDVOInit(pScrn, i, DVOC); */ + i++; + break; + } + dinfo->num_outputs = i; +} + +void intelfb_delete_i2c_busses(struct intelfb_info *dinfo) +{ + int i; + + for (i = 0; i < MAX_OUTPUTS; i++) { + if (dinfo->output[i].i2c_bus.dinfo) { + i2c_del_adapter(&dinfo->output[i].i2c_bus.adapter); + dinfo->output[i].i2c_bus.dinfo = NULL; + } + if (dinfo->output[i].ddc_bus.dinfo) { + i2c_del_adapter(&dinfo->output[i].ddc_bus.adapter); + dinfo->output[i].ddc_bus.dinfo = NULL; + } + } +} diff --git a/drivers/video/fbdev/intelfb/intelfbdrv.c b/drivers/video/fbdev/intelfb/intelfbdrv.c new file mode 100644 index 000000000000..b847d530471a --- /dev/null +++ b/drivers/video/fbdev/intelfb/intelfbdrv.c @@ -0,0 +1,1704 @@ +/* + * intelfb + * + * Linux framebuffer driver for Intel(R) 830M/845G/852GM/855GM/865G/915G/915GM/ + * 945G/945GM/945GME/965G/965GM integrated graphics chips. + * + * Copyright © 2002, 2003 David Dawes <dawes@xfree86.org> + * 2004 Sylvain Meyer + * 2006 David Airlie + * + * This driver consists of two parts. The first part (intelfbdrv.c) provides + * the basic fbdev interfaces, is derived in part from the radeonfb and + * vesafb drivers, and is covered by the GPL. The second part (intelfbhw.c) + * provides the code to program the hardware. Most of it is derived from + * the i810/i830 XFree86 driver. The HW-specific code is covered here + * under a dual license (GPL and MIT/XFree86 license). + * + * Author: David Dawes + * + */ + +/* $DHD: intelfb/intelfbdrv.c,v 1.20 2003/06/27 15:17:40 dawes Exp $ */ + +/* + * Changes: + * 01/2003 - Initial driver (0.1.0), no mode switching, no acceleration. + * This initial version is a basic core that works a lot like + * the vesafb driver. It must be built-in to the kernel, + * and the initial video mode must be set with vga=XXX at + * boot time. (David Dawes) + * + * 01/2003 - Version 0.2.0: Mode switching added, colormap support + * implemented, Y panning, and soft screen blanking implemented. + * No acceleration yet. (David Dawes) + * + * 01/2003 - Version 0.3.0: fbcon acceleration support added. Module + * option handling added. (David Dawes) + * + * 01/2003 - Version 0.4.0: fbcon HW cursor support added. (David Dawes) + * + * 01/2003 - Version 0.4.1: Add auto-generation of built-in modes. + * (David Dawes) + * + * 02/2003 - Version 0.4.2: Add check for active non-CRT devices, and + * mode validation checks. (David Dawes) + * + * 02/2003 - Version 0.4.3: Check when the VC is in graphics mode so that + * acceleration is disabled while an XFree86 server is running. + * (David Dawes) + * + * 02/2003 - Version 0.4.4: Monitor DPMS support. (David Dawes) + * + * 02/2003 - Version 0.4.5: Basic XFree86 + fbdev working. (David Dawes) + * + * 02/2003 - Version 0.5.0: Modify to work with the 2.5.32 kernel as well + * as 2.4.x kernels. (David Dawes) + * + * 02/2003 - Version 0.6.0: Split out HW-specifics into a separate file. + * (David Dawes) + * + * 02/2003 - Version 0.7.0: Test on 852GM/855GM. Acceleration and HW + * cursor are disabled on this platform. (David Dawes) + * + * 02/2003 - Version 0.7.1: Test on 845G. Acceleration is disabled + * on this platform. (David Dawes) + * + * 02/2003 - Version 0.7.2: Test on 830M. Acceleration and HW + * cursor are disabled on this platform. (David Dawes) + * + * 02/2003 - Version 0.7.3: Fix 8-bit modes for mobile platforms + * (David Dawes) + * + * 02/2003 - Version 0.7.4: Add checks for FB and FBCON_HAS_CFB* configured + * in the kernel, and add mode bpp verification and default + * bpp selection based on which FBCON_HAS_CFB* are configured. + * (David Dawes) + * + * 02/2003 - Version 0.7.5: Add basic package/install scripts based on the + * DRI packaging scripts. (David Dawes) + * + * 04/2003 - Version 0.7.6: Fix typo that affects builds with SMP-enabled + * kernels. (David Dawes, reported by Anupam). + * + * 06/2003 - Version 0.7.7: + * Fix Makefile.kernel build problem (Tsutomu Yasuda). + * Fix mis-placed #endif (2.4.21 kernel). + * + * 09/2004 - Version 0.9.0 - by Sylvain Meyer + * Port to linux 2.6 kernel fbdev + * Fix HW accel and HW cursor on i845G + * Use of agpgart for fb memory reservation + * Add mtrr support + * + * 10/2004 - Version 0.9.1 + * Use module_param instead of old MODULE_PARM + * Some cleanup + * + * 11/2004 - Version 0.9.2 + * Add vram option to reserve more memory than stolen by BIOS + * Fix intelfbhw_pan_display typo + * Add __initdata annotations + * + * 04/2008 - Version 0.9.5 + * Add support for 965G/965GM. (Maik Broemme <mbroemme@plusserver.de>) + * + * 08/2008 - Version 0.9.6 + * Add support for 945GME. (Phil Endecott <spam_from_intelfb@chezphil.org>) + */ + +#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/ioport.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/screen_info.h> + +#include <asm/io.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include "intelfb.h" +#include "intelfbhw.h" +#include "../edid.h" + +static void get_initial_mode(struct intelfb_info *dinfo); +static void update_dinfo(struct intelfb_info *dinfo, + struct fb_var_screeninfo *var); +static int intelfb_open(struct fb_info *info, int user); +static int intelfb_release(struct fb_info *info, int user); +static int intelfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int intelfb_set_par(struct fb_info *info); +static int intelfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info); + +static int intelfb_blank(int blank, struct fb_info *info); +static int intelfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); + +static void intelfb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect); +static void intelfb_copyarea(struct fb_info *info, + const struct fb_copyarea *region); +static void intelfb_imageblit(struct fb_info *info, + const struct fb_image *image); +static int intelfb_cursor(struct fb_info *info, + struct fb_cursor *cursor); + +static int intelfb_sync(struct fb_info *info); + +static int intelfb_ioctl(struct fb_info *info, + unsigned int cmd, unsigned long arg); + +static int intelfb_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent); +static void intelfb_pci_unregister(struct pci_dev *pdev); +static int intelfb_set_fbinfo(struct intelfb_info *dinfo); + +/* + * Limiting the class to PCI_CLASS_DISPLAY_VGA prevents function 1 of the + * mobile chipsets from being registered. + */ +#if DETECT_VGA_CLASS_ONLY +#define INTELFB_CLASS_MASK ~0 << 8 +#else +#define INTELFB_CLASS_MASK 0 +#endif + +static struct pci_device_id intelfb_pci_table[] = { + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_830M, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_830M }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_845G, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_845G }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_85XGM, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_85XGM }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_865G, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_865G }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_854, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_854 }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_915G, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_915G }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_915GM, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_915GM }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_945G, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_945G }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_945GM, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_945GM }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_945GME, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_945GME }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_965G, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_965G }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_965GM, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, INTELFB_CLASS_MASK, INTEL_965GM }, + { 0, } +}; + +/* Global data */ +static int num_registered = 0; + +/* fb ops */ +static struct fb_ops intel_fb_ops = { + .owner = THIS_MODULE, + .fb_open = intelfb_open, + .fb_release = intelfb_release, + .fb_check_var = intelfb_check_var, + .fb_set_par = intelfb_set_par, + .fb_setcolreg = intelfb_setcolreg, + .fb_blank = intelfb_blank, + .fb_pan_display = intelfb_pan_display, + .fb_fillrect = intelfb_fillrect, + .fb_copyarea = intelfb_copyarea, + .fb_imageblit = intelfb_imageblit, + .fb_cursor = intelfb_cursor, + .fb_sync = intelfb_sync, + .fb_ioctl = intelfb_ioctl +}; + +/* PCI driver module table */ +static struct pci_driver intelfb_driver = { + .name = "intelfb", + .id_table = intelfb_pci_table, + .probe = intelfb_pci_register, + .remove = intelfb_pci_unregister, +}; + +/* Module description/parameters */ +MODULE_AUTHOR("David Dawes <dawes@tungstengraphics.com>, " + "Sylvain Meyer <sylvain.meyer@worldonline.fr>"); +MODULE_DESCRIPTION("Framebuffer driver for Intel(R) " SUPPORTED_CHIPSETS + " chipsets"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DEVICE_TABLE(pci, intelfb_pci_table); + +static bool accel = 1; +static int vram = 4; +static bool hwcursor = 0; +static bool mtrr = 1; +static bool fixed = 0; +static bool noinit = 0; +static bool noregister = 0; +static bool probeonly = 0; +static bool idonly = 0; +static int bailearly = 0; +static int voffset = 48; +static char *mode = NULL; + +module_param(accel, bool, S_IRUGO); +MODULE_PARM_DESC(accel, "Enable hardware acceleration"); +module_param(vram, int, S_IRUGO); +MODULE_PARM_DESC(vram, "System RAM to allocate to framebuffer in MiB"); +module_param(voffset, int, S_IRUGO); +MODULE_PARM_DESC(voffset, "Offset of framebuffer in MiB"); +module_param(hwcursor, bool, S_IRUGO); +MODULE_PARM_DESC(hwcursor, "Enable HW cursor"); +module_param(mtrr, bool, S_IRUGO); +MODULE_PARM_DESC(mtrr, "Enable MTRR support"); +module_param(fixed, bool, S_IRUGO); +MODULE_PARM_DESC(fixed, "Disable mode switching"); +module_param(noinit, bool, 0); +MODULE_PARM_DESC(noinit, "Don't initialise graphics mode when loading"); +module_param(noregister, bool, 0); +MODULE_PARM_DESC(noregister, "Don't register, just probe and exit (debug)"); +module_param(probeonly, bool, 0); +MODULE_PARM_DESC(probeonly, "Do a minimal probe (debug)"); +module_param(idonly, bool, 0); +MODULE_PARM_DESC(idonly, "Just identify without doing anything else (debug)"); +module_param(bailearly, int, 0); +MODULE_PARM_DESC(bailearly, "Bail out early, depending on value (debug)"); +module_param(mode, charp, S_IRUGO); +MODULE_PARM_DESC(mode, + "Initial video mode \"<xres>x<yres>[-<depth>][@<refresh>]\""); + +#ifndef MODULE +#define OPT_EQUAL(opt, name) (!strncmp(opt, name, strlen(name))) +#define OPT_INTVAL(opt, name) simple_strtoul(opt + strlen(name) + 1, NULL, 0) +#define OPT_STRVAL(opt, name) (opt + strlen(name)) + +static __inline__ char * get_opt_string(const char *this_opt, const char *name) +{ + const char *p; + int i; + char *ret; + + p = OPT_STRVAL(this_opt, name); + i = 0; + while (p[i] && p[i] != ' ' && p[i] != ',') + i++; + ret = kmalloc(i + 1, GFP_KERNEL); + if (ret) { + strncpy(ret, p, i); + ret[i] = '\0'; + } + return ret; +} + +static __inline__ int get_opt_int(const char *this_opt, const char *name, + int *ret) +{ + if (!ret) + return 0; + + if (!OPT_EQUAL(this_opt, name)) + return 0; + + *ret = OPT_INTVAL(this_opt, name); + return 1; +} + +static __inline__ int get_opt_bool(const char *this_opt, const char *name, + int *ret) +{ + if (!ret) + return 0; + + if (OPT_EQUAL(this_opt, name)) { + if (this_opt[strlen(name)] == '=') + *ret = simple_strtoul(this_opt + strlen(name) + 1, + NULL, 0); + else + *ret = 1; + } else { + if (OPT_EQUAL(this_opt, "no") && OPT_EQUAL(this_opt + 2, name)) + *ret = 0; + else + return 0; + } + return 1; +} + +static int __init intelfb_setup(char *options) +{ + char *this_opt; + + DBG_MSG("intelfb_setup\n"); + + if (!options || !*options) { + DBG_MSG("no options\n"); + return 0; + } else + DBG_MSG("options: %s\n", options); + + /* + * These are the built-in options analogous to the module parameters + * defined above. + * + * The syntax is: + * + * video=intelfb:[mode][,<param>=<val>] ... + * + * e.g., + * + * video=intelfb:1024x768-16@75,accel=0 + */ + + while ((this_opt = strsep(&options, ","))) { + if (!*this_opt) + continue; + if (get_opt_bool(this_opt, "accel", &accel)) + ; + else if (get_opt_int(this_opt, "vram", &vram)) + ; + else if (get_opt_bool(this_opt, "hwcursor", &hwcursor)) + ; + else if (get_opt_bool(this_opt, "mtrr", &mtrr)) + ; + else if (get_opt_bool(this_opt, "fixed", &fixed)) + ; + else if (get_opt_bool(this_opt, "init", &noinit)) + noinit = !noinit; + else if (OPT_EQUAL(this_opt, "mode=")) + mode = get_opt_string(this_opt, "mode="); + else + mode = this_opt; + } + + return 0; +} + +#endif + +static int __init intelfb_init(void) +{ +#ifndef MODULE + char *option = NULL; +#endif + + DBG_MSG("intelfb_init\n"); + + INF_MSG("Framebuffer driver for " + "Intel(R) " SUPPORTED_CHIPSETS " chipsets\n"); + INF_MSG("Version " INTELFB_VERSION "\n"); + + if (idonly) + return -ENODEV; + +#ifndef MODULE + if (fb_get_options("intelfb", &option)) + return -ENODEV; + intelfb_setup(option); +#endif + + return pci_register_driver(&intelfb_driver); +} + +static void __exit intelfb_exit(void) +{ + DBG_MSG("intelfb_exit\n"); + pci_unregister_driver(&intelfb_driver); +} + +module_init(intelfb_init); +module_exit(intelfb_exit); + +/*************************************************************** + * mtrr support functions * + ***************************************************************/ + +#ifdef CONFIG_MTRR +static inline void set_mtrr(struct intelfb_info *dinfo) +{ + dinfo->mtrr_reg = mtrr_add(dinfo->aperture.physical, + dinfo->aperture.size, MTRR_TYPE_WRCOMB, 1); + if (dinfo->mtrr_reg < 0) { + ERR_MSG("unable to set MTRR\n"); + return; + } + dinfo->has_mtrr = 1; +} +static inline void unset_mtrr(struct intelfb_info *dinfo) +{ + if (dinfo->has_mtrr) + mtrr_del(dinfo->mtrr_reg, dinfo->aperture.physical, + dinfo->aperture.size); +} +#else +#define set_mtrr(x) WRN_MSG("MTRR is disabled in the kernel\n") + +#define unset_mtrr(x) do { } while (0) +#endif /* CONFIG_MTRR */ + +/*************************************************************** + * driver init / cleanup * + ***************************************************************/ + +static void cleanup(struct intelfb_info *dinfo) +{ + DBG_MSG("cleanup\n"); + + if (!dinfo) + return; + + intelfbhw_disable_irq(dinfo); + + fb_dealloc_cmap(&dinfo->info->cmap); + kfree(dinfo->info->pixmap.addr); + + if (dinfo->registered) + unregister_framebuffer(dinfo->info); + + unset_mtrr(dinfo); + + if (dinfo->fbmem_gart && dinfo->gtt_fb_mem) { + agp_unbind_memory(dinfo->gtt_fb_mem); + agp_free_memory(dinfo->gtt_fb_mem); + } + if (dinfo->gtt_cursor_mem) { + agp_unbind_memory(dinfo->gtt_cursor_mem); + agp_free_memory(dinfo->gtt_cursor_mem); + } + if (dinfo->gtt_ring_mem) { + agp_unbind_memory(dinfo->gtt_ring_mem); + agp_free_memory(dinfo->gtt_ring_mem); + } + +#ifdef CONFIG_FB_INTEL_I2C + /* un-register I2C bus */ + intelfb_delete_i2c_busses(dinfo); +#endif + + if (dinfo->mmio_base) + iounmap((void __iomem *)dinfo->mmio_base); + if (dinfo->aperture.virtual) + iounmap((void __iomem *)dinfo->aperture.virtual); + + if (dinfo->flag & INTELFB_MMIO_ACQUIRED) + release_mem_region(dinfo->mmio_base_phys, INTEL_REG_SIZE); + if (dinfo->flag & INTELFB_FB_ACQUIRED) + release_mem_region(dinfo->aperture.physical, + dinfo->aperture.size); + framebuffer_release(dinfo->info); +} + +#define bailout(dinfo) do { \ + DBG_MSG("bailout\n"); \ + cleanup(dinfo); \ + INF_MSG("Not going to register framebuffer, exiting...\n"); \ + return -ENODEV; \ +} while (0) + + +static int intelfb_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct fb_info *info; + struct intelfb_info *dinfo; + int i, err, dvo; + int aperture_size, stolen_size; + struct agp_kern_info gtt_info; + int agp_memtype; + const char *s; + struct agp_bridge_data *bridge; + int aperture_bar = 0; + int mmio_bar = 1; + int offset; + + DBG_MSG("intelfb_pci_register\n"); + + num_registered++; + if (num_registered != 1) { + ERR_MSG("Attempted to register %d devices " + "(should be only 1).\n", num_registered); + return -ENODEV; + } + + info = framebuffer_alloc(sizeof(struct intelfb_info), &pdev->dev); + if (!info) { + ERR_MSG("Could not allocate memory for intelfb_info.\n"); + return -ENODEV; + } + if (fb_alloc_cmap(&info->cmap, 256, 1) < 0) { + ERR_MSG("Could not allocate cmap for intelfb_info.\n"); + goto err_out_cmap; + } + + dinfo = info->par; + dinfo->info = info; + dinfo->fbops = &intel_fb_ops; + dinfo->pdev = pdev; + + /* Reserve pixmap space. */ + info->pixmap.addr = kzalloc(64 * 1024, GFP_KERNEL); + if (info->pixmap.addr == NULL) { + ERR_MSG("Cannot reserve pixmap memory.\n"); + goto err_out_pixmap; + } + + /* set early this option because it could be changed by tv encoder + driver */ + dinfo->fixed_mode = fixed; + + /* Enable device. */ + if ((err = pci_enable_device(pdev))) { + ERR_MSG("Cannot enable device.\n"); + cleanup(dinfo); + return -ENODEV; + } + + /* Set base addresses. */ + if ((ent->device == PCI_DEVICE_ID_INTEL_915G) || + (ent->device == PCI_DEVICE_ID_INTEL_915GM) || + (ent->device == PCI_DEVICE_ID_INTEL_945G) || + (ent->device == PCI_DEVICE_ID_INTEL_945GM) || + (ent->device == PCI_DEVICE_ID_INTEL_945GME) || + (ent->device == PCI_DEVICE_ID_INTEL_965G) || + (ent->device == PCI_DEVICE_ID_INTEL_965GM)) { + + aperture_bar = 2; + mmio_bar = 0; + } + dinfo->aperture.physical = pci_resource_start(pdev, aperture_bar); + dinfo->aperture.size = pci_resource_len(pdev, aperture_bar); + dinfo->mmio_base_phys = pci_resource_start(pdev, mmio_bar); + DBG_MSG("fb aperture: 0x%llx/0x%llx, MMIO region: 0x%llx/0x%llx\n", + (unsigned long long)pci_resource_start(pdev, aperture_bar), + (unsigned long long)pci_resource_len(pdev, aperture_bar), + (unsigned long long)pci_resource_start(pdev, mmio_bar), + (unsigned long long)pci_resource_len(pdev, mmio_bar)); + + /* Reserve the fb and MMIO regions */ + if (!request_mem_region(dinfo->aperture.physical, dinfo->aperture.size, + INTELFB_MODULE_NAME)) { + ERR_MSG("Cannot reserve FB region.\n"); + cleanup(dinfo); + return -ENODEV; + } + + dinfo->flag |= INTELFB_FB_ACQUIRED; + + if (!request_mem_region(dinfo->mmio_base_phys, + INTEL_REG_SIZE, + INTELFB_MODULE_NAME)) { + ERR_MSG("Cannot reserve MMIO region.\n"); + cleanup(dinfo); + return -ENODEV; + } + + dinfo->flag |= INTELFB_MMIO_ACQUIRED; + + /* Get the chipset info. */ + dinfo->pci_chipset = pdev->device; + + if (intelfbhw_get_chipset(pdev, dinfo)) { + cleanup(dinfo); + return -ENODEV; + } + + if (intelfbhw_get_memory(pdev, &aperture_size,&stolen_size)) { + cleanup(dinfo); + return -ENODEV; + } + + INF_MSG("%02x:%02x.%d: %s, aperture size %dMB, " + "stolen memory %dkB\n", + pdev->bus->number, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), dinfo->name, + BtoMB(aperture_size), BtoKB(stolen_size)); + + /* Set these from the options. */ + dinfo->accel = accel; + dinfo->hwcursor = hwcursor; + + if (NOACCEL_CHIPSET(dinfo) && dinfo->accel == 1) { + INF_MSG("Acceleration is not supported for the %s chipset.\n", + dinfo->name); + dinfo->accel = 0; + } + + /* Framebuffer parameters - Use all the stolen memory if >= vram */ + if (ROUND_UP_TO_PAGE(stolen_size) >= MB(vram)) { + dinfo->fb.size = ROUND_UP_TO_PAGE(stolen_size); + dinfo->fbmem_gart = 0; + } else { + dinfo->fb.size = MB(vram); + dinfo->fbmem_gart = 1; + } + + /* Allocate space for the ring buffer and HW cursor if enabled. */ + if (dinfo->accel) { + dinfo->ring.size = RINGBUFFER_SIZE; + dinfo->ring_tail_mask = dinfo->ring.size - 1; + } + if (dinfo->hwcursor) + dinfo->cursor.size = HW_CURSOR_SIZE; + + /* Use agpgart to manage the GATT */ + if (!(bridge = agp_backend_acquire(pdev))) { + ERR_MSG("cannot acquire agp\n"); + cleanup(dinfo); + return -ENODEV; + } + + /* get the current gatt info */ + if (agp_copy_info(bridge, >t_info)) { + ERR_MSG("cannot get agp info\n"); + agp_backend_release(bridge); + cleanup(dinfo); + return -ENODEV; + } + + if (MB(voffset) < stolen_size) + offset = (stolen_size >> 12); + else + offset = ROUND_UP_TO_PAGE(MB(voffset))/GTT_PAGE_SIZE; + + /* set the mem offsets - set them after the already used pages */ + if (dinfo->accel) + dinfo->ring.offset = offset + gtt_info.current_memory; + if (dinfo->hwcursor) + dinfo->cursor.offset = offset + + + gtt_info.current_memory + (dinfo->ring.size >> 12); + if (dinfo->fbmem_gart) + dinfo->fb.offset = offset + + + gtt_info.current_memory + (dinfo->ring.size >> 12) + + (dinfo->cursor.size >> 12); + + /* Allocate memories (which aren't stolen) */ + /* Map the fb and MMIO regions */ + /* ioremap only up to the end of used aperture */ + dinfo->aperture.virtual = (u8 __iomem *)ioremap_nocache + (dinfo->aperture.physical, ((offset + dinfo->fb.offset) << 12) + + dinfo->fb.size); + if (!dinfo->aperture.virtual) { + ERR_MSG("Cannot remap FB region.\n"); + agp_backend_release(bridge); + cleanup(dinfo); + return -ENODEV; + } + + dinfo->mmio_base = + (u8 __iomem *)ioremap_nocache(dinfo->mmio_base_phys, + INTEL_REG_SIZE); + if (!dinfo->mmio_base) { + ERR_MSG("Cannot remap MMIO region.\n"); + agp_backend_release(bridge); + cleanup(dinfo); + return -ENODEV; + } + + if (dinfo->accel) { + if (!(dinfo->gtt_ring_mem = + agp_allocate_memory(bridge, dinfo->ring.size >> 12, + AGP_NORMAL_MEMORY))) { + ERR_MSG("cannot allocate ring buffer memory\n"); + agp_backend_release(bridge); + cleanup(dinfo); + return -ENOMEM; + } + if (agp_bind_memory(dinfo->gtt_ring_mem, + dinfo->ring.offset)) { + ERR_MSG("cannot bind ring buffer memory\n"); + agp_backend_release(bridge); + cleanup(dinfo); + return -EBUSY; + } + dinfo->ring.physical = dinfo->aperture.physical + + (dinfo->ring.offset << 12); + dinfo->ring.virtual = dinfo->aperture.virtual + + (dinfo->ring.offset << 12); + dinfo->ring_head = 0; + } + if (dinfo->hwcursor) { + agp_memtype = dinfo->mobile ? AGP_PHYSICAL_MEMORY + : AGP_NORMAL_MEMORY; + if (!(dinfo->gtt_cursor_mem = + agp_allocate_memory(bridge, dinfo->cursor.size >> 12, + agp_memtype))) { + ERR_MSG("cannot allocate cursor memory\n"); + agp_backend_release(bridge); + cleanup(dinfo); + return -ENOMEM; + } + if (agp_bind_memory(dinfo->gtt_cursor_mem, + dinfo->cursor.offset)) { + ERR_MSG("cannot bind cursor memory\n"); + agp_backend_release(bridge); + cleanup(dinfo); + return -EBUSY; + } + if (dinfo->mobile) + dinfo->cursor.physical + = dinfo->gtt_cursor_mem->physical; + else + dinfo->cursor.physical = dinfo->aperture.physical + + (dinfo->cursor.offset << 12); + dinfo->cursor.virtual = dinfo->aperture.virtual + + (dinfo->cursor.offset << 12); + } + if (dinfo->fbmem_gart) { + if (!(dinfo->gtt_fb_mem = + agp_allocate_memory(bridge, dinfo->fb.size >> 12, + AGP_NORMAL_MEMORY))) { + WRN_MSG("cannot allocate framebuffer memory - use " + "the stolen one\n"); + dinfo->fbmem_gart = 0; + } + if (agp_bind_memory(dinfo->gtt_fb_mem, + dinfo->fb.offset)) { + WRN_MSG("cannot bind framebuffer memory - use " + "the stolen one\n"); + dinfo->fbmem_gart = 0; + } + } + + /* update framebuffer memory parameters */ + if (!dinfo->fbmem_gart) + dinfo->fb.offset = 0; /* starts at offset 0 */ + dinfo->fb.physical = dinfo->aperture.physical + + (dinfo->fb.offset << 12); + dinfo->fb.virtual = dinfo->aperture.virtual + (dinfo->fb.offset << 12); + dinfo->fb_start = dinfo->fb.offset << 12; + + /* release agpgart */ + agp_backend_release(bridge); + + if (mtrr) + set_mtrr(dinfo); + + DBG_MSG("fb: 0x%x(+ 0x%x)/0x%x (0x%p)\n", + dinfo->fb.physical, dinfo->fb.offset, dinfo->fb.size, + dinfo->fb.virtual); + DBG_MSG("MMIO: 0x%x/0x%x (0x%p)\n", + dinfo->mmio_base_phys, INTEL_REG_SIZE, + dinfo->mmio_base); + DBG_MSG("ring buffer: 0x%x/0x%x (0x%p)\n", + dinfo->ring.physical, dinfo->ring.size, + dinfo->ring.virtual); + DBG_MSG("HW cursor: 0x%x/0x%x (0x%p) (offset 0x%x) (phys 0x%x)\n", + dinfo->cursor.physical, dinfo->cursor.size, + dinfo->cursor.virtual, dinfo->cursor.offset, + dinfo->cursor.physical); + + DBG_MSG("options: vram = %d, accel = %d, hwcursor = %d, fixed = %d, " + "noinit = %d\n", vram, accel, hwcursor, fixed, noinit); + DBG_MSG("options: mode = \"%s\"\n", mode ? mode : ""); + + if (probeonly) + bailout(dinfo); + + /* + * Check if the LVDS port or any DVO ports are enabled. If so, + * don't allow mode switching + */ + dvo = intelfbhw_check_non_crt(dinfo); + if (dvo) { + dinfo->fixed_mode = 1; + WRN_MSG("Non-CRT device is enabled ( "); + i = 0; + while (dvo) { + if (dvo & 1) { + s = intelfbhw_dvo_to_string(1 << i); + if (s) + printk("%s ", s); + } + dvo >>= 1; + ++i; + } + printk("). Disabling mode switching.\n"); + } + + if (bailearly == 1) + bailout(dinfo); + + if (FIXED_MODE(dinfo) && + screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) { + ERR_MSG("Video mode must be programmed at boot time.\n"); + cleanup(dinfo); + return -ENODEV; + } + + if (bailearly == 2) + bailout(dinfo); + + /* Initialise dinfo and related data. */ + /* If an initial mode was programmed at boot time, get its details. */ + if (screen_info.orig_video_isVGA == VIDEO_TYPE_VLFB) + get_initial_mode(dinfo); + + if (bailearly == 3) + bailout(dinfo); + + if (FIXED_MODE(dinfo)) /* remap fb address */ + update_dinfo(dinfo, &dinfo->initial_var); + + if (bailearly == 4) + bailout(dinfo); + + + if (intelfb_set_fbinfo(dinfo)) { + cleanup(dinfo); + return -ENODEV; + } + + if (bailearly == 5) + bailout(dinfo); + +#ifdef CONFIG_FB_INTEL_I2C + /* register I2C bus */ + intelfb_create_i2c_busses(dinfo); +#endif + + if (bailearly == 6) + bailout(dinfo); + + pci_set_drvdata(pdev, dinfo); + + /* Save the initial register state. */ + i = intelfbhw_read_hw_state(dinfo, &dinfo->save_state, + bailearly > 6 ? bailearly - 6 : 0); + if (i != 0) { + DBG_MSG("intelfbhw_read_hw_state returned %d\n", i); + bailout(dinfo); + } + + intelfbhw_print_hw_state(dinfo, &dinfo->save_state); + + if (bailearly == 18) + bailout(dinfo); + + /* read active pipe */ + dinfo->pipe = intelfbhw_active_pipe(&dinfo->save_state); + + /* Cursor initialisation */ + if (dinfo->hwcursor) { + intelfbhw_cursor_init(dinfo); + intelfbhw_cursor_reset(dinfo); + } + + if (bailearly == 19) + bailout(dinfo); + + /* 2d acceleration init */ + if (dinfo->accel) + intelfbhw_2d_start(dinfo); + + if (bailearly == 20) + bailout(dinfo); + + if (noregister) + bailout(dinfo); + + if (register_framebuffer(dinfo->info) < 0) { + ERR_MSG("Cannot register framebuffer.\n"); + cleanup(dinfo); + return -ENODEV; + } + + dinfo->registered = 1; + dinfo->open = 0; + + init_waitqueue_head(&dinfo->vsync.wait); + spin_lock_init(&dinfo->int_lock); + dinfo->irq_flags = 0; + dinfo->vsync.pan_display = 0; + dinfo->vsync.pan_offset = 0; + + return 0; + +err_out_pixmap: + fb_dealloc_cmap(&info->cmap); +err_out_cmap: + framebuffer_release(info); + return -ENODEV; +} + +static void intelfb_pci_unregister(struct pci_dev *pdev) +{ + struct intelfb_info *dinfo = pci_get_drvdata(pdev); + + DBG_MSG("intelfb_pci_unregister\n"); + + if (!dinfo) + return; + + cleanup(dinfo); +} + +/*************************************************************** + * helper functions * + ***************************************************************/ + +int __inline__ intelfb_var_to_depth(const struct fb_var_screeninfo *var) +{ + DBG_MSG("intelfb_var_to_depth: bpp: %d, green.length is %d\n", + var->bits_per_pixel, var->green.length); + + switch (var->bits_per_pixel) { + case 16: + return (var->green.length == 6) ? 16 : 15; + case 32: + return 24; + default: + return var->bits_per_pixel; + } +} + + +static __inline__ int var_to_refresh(const struct fb_var_screeninfo *var) +{ + int xtot = var->xres + var->left_margin + var->right_margin + + var->hsync_len; + int ytot = var->yres + var->upper_margin + var->lower_margin + + var->vsync_len; + + return (1000000000 / var->pixclock * 1000 + 500) / xtot / ytot; +} + +/*************************************************************** + * Various intialisation functions * + ***************************************************************/ + +static void get_initial_mode(struct intelfb_info *dinfo) +{ + struct fb_var_screeninfo *var; + int xtot, ytot; + + DBG_MSG("get_initial_mode\n"); + + dinfo->initial_vga = 1; + dinfo->initial_fb_base = screen_info.lfb_base; + dinfo->initial_video_ram = screen_info.lfb_size * KB(64); + dinfo->initial_pitch = screen_info.lfb_linelength; + + var = &dinfo->initial_var; + memset(var, 0, sizeof(*var)); + var->xres = screen_info.lfb_width; + var->yres = screen_info.lfb_height; + var->bits_per_pixel = screen_info.lfb_depth; + switch (screen_info.lfb_depth) { + case 15: + var->bits_per_pixel = 16; + break; + case 24: + var->bits_per_pixel = 32; + break; + } + + DBG_MSG("Initial info: FB is 0x%x/0x%x (%d kByte)\n", + dinfo->initial_fb_base, dinfo->initial_video_ram, + BtoKB(dinfo->initial_video_ram)); + + DBG_MSG("Initial info: mode is %dx%d-%d (%d)\n", + var->xres, var->yres, var->bits_per_pixel, + dinfo->initial_pitch); + + /* Dummy timing values (assume 60Hz) */ + var->left_margin = (var->xres / 8) & 0xf8; + var->right_margin = 32; + var->upper_margin = 16; + var->lower_margin = 4; + var->hsync_len = (var->xres / 8) & 0xf8; + var->vsync_len = 4; + + xtot = var->xres + var->left_margin + + var->right_margin + var->hsync_len; + ytot = var->yres + var->upper_margin + + var->lower_margin + var->vsync_len; + var->pixclock = 10000000 / xtot * 1000 / ytot * 100 / 60; + + var->height = -1; + var->width = -1; + + if (var->bits_per_pixel > 8) { + var->red.offset = screen_info.red_pos; + var->red.length = screen_info.red_size; + var->green.offset = screen_info.green_pos; + var->green.length = screen_info.green_size; + var->blue.offset = screen_info.blue_pos; + var->blue.length = screen_info.blue_size; + var->transp.offset = screen_info.rsvd_pos; + var->transp.length = screen_info.rsvd_size; + } else { + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + } +} + +static int intelfb_init_var(struct intelfb_info *dinfo) +{ + struct fb_var_screeninfo *var; + int msrc = 0; + + DBG_MSG("intelfb_init_var\n"); + + var = &dinfo->info->var; + if (FIXED_MODE(dinfo)) { + memcpy(var, &dinfo->initial_var, + sizeof(struct fb_var_screeninfo)); + msrc = 5; + } else { + const u8 *edid_s = fb_firmware_edid(&dinfo->pdev->dev); + u8 *edid_d = NULL; + + if (edid_s) { + edid_d = kmemdup(edid_s, EDID_LENGTH, GFP_KERNEL); + + if (edid_d) { + fb_edid_to_monspecs(edid_d, + &dinfo->info->monspecs); + kfree(edid_d); + } + } + + if (mode) { + printk("intelfb: Looking for mode in private " + "database\n"); + msrc = fb_find_mode(var, dinfo->info, mode, + dinfo->info->monspecs.modedb, + dinfo->info->monspecs.modedb_len, + NULL, 0); + + if (msrc && msrc > 1) { + printk("intelfb: No mode in private database, " + "intelfb: looking for mode in global " + "database "); + msrc = fb_find_mode(var, dinfo->info, mode, + NULL, 0, NULL, 0); + + if (msrc) + msrc |= 8; + } + + } + + if (!msrc) + msrc = fb_find_mode(var, dinfo->info, PREFERRED_MODE, + NULL, 0, NULL, 0); + } + + if (!msrc) { + ERR_MSG("Cannot find a suitable video mode.\n"); + return 1; + } + + INF_MSG("Initial video mode is %dx%d-%d@%d.\n", var->xres, var->yres, + var->bits_per_pixel, var_to_refresh(var)); + + DBG_MSG("Initial video mode is from %d.\n", msrc); + +#if ALLOCATE_FOR_PANNING + /* Allow use of half of the video ram for panning */ + var->xres_virtual = var->xres; + var->yres_virtual = + dinfo->fb.size / 2 / (var->bits_per_pixel * var->xres); + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; +#else + var->yres_virtual = var->yres; +#endif + + if (dinfo->accel) + var->accel_flags |= FB_ACCELF_TEXT; + else + var->accel_flags &= ~FB_ACCELF_TEXT; + + return 0; +} + +static int intelfb_set_fbinfo(struct intelfb_info *dinfo) +{ + struct fb_info *info = dinfo->info; + + DBG_MSG("intelfb_set_fbinfo\n"); + + info->flags = FBINFO_FLAG_DEFAULT; + info->fbops = &intel_fb_ops; + info->pseudo_palette = dinfo->pseudo_palette; + + info->pixmap.size = 64*1024; + info->pixmap.buf_align = 8; + info->pixmap.access_align = 32; + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + if (intelfb_init_var(dinfo)) + return 1; + + info->pixmap.scan_align = 1; + strcpy(info->fix.id, dinfo->name); + info->fix.smem_start = dinfo->fb.physical; + info->fix.smem_len = dinfo->fb.size; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 8; + info->fix.ypanstep = 1; + info->fix.ywrapstep = 0; + info->fix.mmio_start = dinfo->mmio_base_phys; + info->fix.mmio_len = INTEL_REG_SIZE; + info->fix.accel = FB_ACCEL_I830; + update_dinfo(dinfo, &info->var); + + return 0; +} + +/* Update dinfo to match the active video mode. */ +static void update_dinfo(struct intelfb_info *dinfo, + struct fb_var_screeninfo *var) +{ + DBG_MSG("update_dinfo\n"); + + dinfo->bpp = var->bits_per_pixel; + dinfo->depth = intelfb_var_to_depth(var); + dinfo->xres = var->xres; + dinfo->yres = var->xres; + dinfo->pixclock = var->pixclock; + + dinfo->info->fix.visual = dinfo->visual; + dinfo->info->fix.line_length = dinfo->pitch; + + switch (dinfo->bpp) { + case 8: + dinfo->visual = FB_VISUAL_PSEUDOCOLOR; + dinfo->pitch = var->xres_virtual; + break; + case 16: + dinfo->visual = FB_VISUAL_TRUECOLOR; + dinfo->pitch = var->xres_virtual * 2; + break; + case 32: + dinfo->visual = FB_VISUAL_TRUECOLOR; + dinfo->pitch = var->xres_virtual * 4; + break; + } + + /* Make sure the line length is a aligned correctly. */ + if (IS_I9XX(dinfo)) + dinfo->pitch = ROUND_UP_TO(dinfo->pitch, STRIDE_ALIGNMENT_I9XX); + else + dinfo->pitch = ROUND_UP_TO(dinfo->pitch, STRIDE_ALIGNMENT); + + if (FIXED_MODE(dinfo)) + dinfo->pitch = dinfo->initial_pitch; + + dinfo->info->screen_base = (char __iomem *)dinfo->fb.virtual; + dinfo->info->fix.line_length = dinfo->pitch; + dinfo->info->fix.visual = dinfo->visual; +} + +/* fbops functions */ + +/*************************************************************** + * fbdev interface * + ***************************************************************/ + +static int intelfb_open(struct fb_info *info, int user) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + + if (user) + dinfo->open++; + + return 0; +} + +static int intelfb_release(struct fb_info *info, int user) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + + if (user) { + dinfo->open--; + msleep(1); + if (!dinfo->open) + intelfbhw_disable_irq(dinfo); + } + + return 0; +} + +static int intelfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int change_var = 0; + struct fb_var_screeninfo v; + struct intelfb_info *dinfo; + static int first = 1; + int i; + /* Good pitches to allow tiling. Don't care about pitches < 1024. */ + static const int pitches[] = { + 128 * 8, + 128 * 16, + 128 * 32, + 128 * 64, + 0 + }; + + DBG_MSG("intelfb_check_var: accel_flags is %d\n", var->accel_flags); + + dinfo = GET_DINFO(info); + + /* update the pitch */ + if (intelfbhw_validate_mode(dinfo, var) != 0) + return -EINVAL; + + v = *var; + + for (i = 0; pitches[i] != 0; i++) { + if (pitches[i] >= v.xres_virtual) { + v.xres_virtual = pitches[i]; + break; + } + } + + /* Check for a supported bpp. */ + if (v.bits_per_pixel <= 8) + v.bits_per_pixel = 8; + else if (v.bits_per_pixel <= 16) { + if (v.bits_per_pixel == 16) + v.green.length = 6; + v.bits_per_pixel = 16; + } else if (v.bits_per_pixel <= 32) + v.bits_per_pixel = 32; + else + return -EINVAL; + + change_var = ((info->var.xres != var->xres) || + (info->var.yres != var->yres) || + (info->var.xres_virtual != var->xres_virtual) || + (info->var.yres_virtual != var->yres_virtual) || + (info->var.bits_per_pixel != var->bits_per_pixel) || + memcmp(&info->var.red, &var->red, sizeof(var->red)) || + memcmp(&info->var.green, &var->green, + sizeof(var->green)) || + memcmp(&info->var.blue, &var->blue, sizeof(var->blue))); + + if (FIXED_MODE(dinfo) && + (change_var || + var->yres_virtual > dinfo->initial_var.yres_virtual || + var->yres_virtual < dinfo->initial_var.yres || + var->xoffset || var->nonstd)) { + if (first) { + ERR_MSG("Changing the video mode is not supported.\n"); + first = 0; + } + return -EINVAL; + } + + switch (intelfb_var_to_depth(&v)) { + case 8: + v.red.offset = v.green.offset = v.blue.offset = 0; + v.red.length = v.green.length = v.blue.length = 8; + v.transp.offset = v.transp.length = 0; + break; + case 15: + v.red.offset = 10; + v.green.offset = 5; + v.blue.offset = 0; + v.red.length = v.green.length = v.blue.length = 5; + v.transp.offset = v.transp.length = 0; + break; + case 16: + v.red.offset = 11; + v.green.offset = 5; + v.blue.offset = 0; + v.red.length = 5; + v.green.length = 6; + v.blue.length = 5; + v.transp.offset = v.transp.length = 0; + break; + case 24: + v.red.offset = 16; + v.green.offset = 8; + v.blue.offset = 0; + v.red.length = v.green.length = v.blue.length = 8; + v.transp.offset = v.transp.length = 0; + break; + case 32: + v.red.offset = 16; + v.green.offset = 8; + v.blue.offset = 0; + v.red.length = v.green.length = v.blue.length = 8; + v.transp.offset = 24; + v.transp.length = 8; + break; + } + + if (v.xoffset < 0) + v.xoffset = 0; + if (v.yoffset < 0) + v.yoffset = 0; + + if (v.xoffset > v.xres_virtual - v.xres) + v.xoffset = v.xres_virtual - v.xres; + if (v.yoffset > v.yres_virtual - v.yres) + v.yoffset = v.yres_virtual - v.yres; + + v.red.msb_right = v.green.msb_right = v.blue.msb_right = + v.transp.msb_right = 0; + + *var = v; + + return 0; +} + +static int intelfb_set_par(struct fb_info *info) +{ + struct intelfb_hwstate *hw; + struct intelfb_info *dinfo = GET_DINFO(info); + + if (FIXED_MODE(dinfo)) { + ERR_MSG("Changing the video mode is not supported.\n"); + return -EINVAL; + } + + hw = kmalloc(sizeof(*hw), GFP_ATOMIC); + if (!hw) + return -ENOMEM; + + DBG_MSG("intelfb_set_par (%dx%d-%d)\n", info->var.xres, + info->var.yres, info->var.bits_per_pixel); + + /* + * Disable VCO prior to timing register change. + */ + OUTREG(DPLL_A, INREG(DPLL_A) & ~DPLL_VCO_ENABLE); + + intelfb_blank(FB_BLANK_POWERDOWN, info); + + if (ACCEL(dinfo, info)) + intelfbhw_2d_stop(dinfo); + + memcpy(hw, &dinfo->save_state, sizeof(*hw)); + if (intelfbhw_mode_to_hw(dinfo, hw, &info->var)) + goto invalid_mode; + if (intelfbhw_program_mode(dinfo, hw, 0)) + goto invalid_mode; + +#if REGDUMP > 0 + intelfbhw_read_hw_state(dinfo, hw, 0); + intelfbhw_print_hw_state(dinfo, hw); +#endif + + update_dinfo(dinfo, &info->var); + + if (ACCEL(dinfo, info)) + intelfbhw_2d_start(dinfo); + + intelfb_pan_display(&info->var, info); + + intelfb_blank(FB_BLANK_UNBLANK, info); + + if (ACCEL(dinfo, info)) { + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_IMAGEBLIT; + } else + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + kfree(hw); + return 0; +invalid_mode: + kfree(hw); + return -EINVAL; +} + +static int intelfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + +#if VERBOSE > 0 + DBG_MSG("intelfb_setcolreg: regno %d, depth %d\n", regno, dinfo->depth); +#endif + + if (regno > 255) + return 1; + + if (dinfo->depth == 8) { + red >>= 8; + green >>= 8; + blue >>= 8; + + intelfbhw_setcolreg(dinfo, regno, red, green, blue, + transp); + } + + if (regno < 16) { + switch (dinfo->depth) { + case 15: + dinfo->pseudo_palette[regno] = ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + break; + case 16: + dinfo->pseudo_palette[regno] = (red & 0xf800) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + case 24: + dinfo->pseudo_palette[regno] = ((red & 0xff00) << 8) | + (green & 0xff00) | + ((blue & 0xff00) >> 8); + break; + } + } + + return 0; +} + +static int intelfb_blank(int blank, struct fb_info *info) +{ + intelfbhw_do_blank(blank, info); + return 0; +} + +static int intelfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + intelfbhw_pan_display(var, info); + return 0; +} + +/* When/if we have our own ioctls. */ +static int intelfb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + struct intelfb_info *dinfo = GET_DINFO(info); + u32 pipe = 0; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + if (get_user(pipe, (__u32 __user *)arg)) + return -EFAULT; + + retval = intelfbhw_wait_for_vsync(dinfo, pipe); + break; + default: + break; + } + + return retval; +} + +static void intelfb_fillrect (struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + u32 rop, color; + +#if VERBOSE > 0 + DBG_MSG("intelfb_fillrect\n"); +#endif + + if (!ACCEL(dinfo, info) || dinfo->depth == 4) { + cfb_fillrect(info, rect); + return; + } + + if (rect->rop == ROP_COPY) + rop = PAT_ROP_GXCOPY; + else /* ROP_XOR */ + rop = PAT_ROP_GXXOR; + + if (dinfo->depth != 8) + color = dinfo->pseudo_palette[rect->color]; + else + color = rect->color; + + intelfbhw_do_fillrect(dinfo, rect->dx, rect->dy, + rect->width, rect->height, color, + dinfo->pitch, info->var.bits_per_pixel, + rop); +} + +static void intelfb_copyarea(struct fb_info *info, + const struct fb_copyarea *region) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + +#if VERBOSE > 0 + DBG_MSG("intelfb_copyarea\n"); +#endif + + if (!ACCEL(dinfo, info) || dinfo->depth == 4) { + cfb_copyarea(info, region); + return; + } + + intelfbhw_do_bitblt(dinfo, region->sx, region->sy, region->dx, + region->dy, region->width, region->height, + dinfo->pitch, info->var.bits_per_pixel); +} + +static void intelfb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + u32 fgcolor, bgcolor; + +#if VERBOSE > 0 + DBG_MSG("intelfb_imageblit\n"); +#endif + + if (!ACCEL(dinfo, info) || dinfo->depth == 4 + || image->depth != 1) { + cfb_imageblit(info, image); + return; + } + + if (dinfo->depth != 8) { + fgcolor = dinfo->pseudo_palette[image->fg_color]; + bgcolor = dinfo->pseudo_palette[image->bg_color]; + } else { + fgcolor = image->fg_color; + bgcolor = image->bg_color; + } + + if (!intelfbhw_do_drawglyph(dinfo, fgcolor, bgcolor, image->width, + image->height, image->data, + image->dx, image->dy, + dinfo->pitch, info->var.bits_per_pixel)) { + cfb_imageblit(info, image); + return; + } +} + +static int intelfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + u32 physical; +#if VERBOSE > 0 + DBG_MSG("intelfb_cursor\n"); +#endif + + if (!dinfo->hwcursor) + return -ENODEV; + + intelfbhw_cursor_hide(dinfo); + + /* If XFree killed the cursor - restore it */ + physical = (dinfo->mobile || IS_I9XX(dinfo)) ? dinfo->cursor.physical : + (dinfo->cursor.offset << 12); + + if (INREG(CURSOR_A_BASEADDR) != physical) { + u32 fg, bg; + + DBG_MSG("the cursor was killed - restore it !!\n"); + DBG_MSG("size %d, %d pos %d, %d\n", + cursor->image.width, cursor->image.height, + cursor->image.dx, cursor->image.dy); + + intelfbhw_cursor_init(dinfo); + intelfbhw_cursor_reset(dinfo); + intelfbhw_cursor_setpos(dinfo, cursor->image.dx, + cursor->image.dy); + + if (dinfo->depth != 8) { + fg =dinfo->pseudo_palette[cursor->image.fg_color]; + bg =dinfo->pseudo_palette[cursor->image.bg_color]; + } else { + fg = cursor->image.fg_color; + bg = cursor->image.bg_color; + } + intelfbhw_cursor_setcolor(dinfo, bg, fg); + intelfbhw_cursor_load(dinfo, cursor->image.width, + cursor->image.height, + dinfo->cursor_src); + + if (cursor->enable) + intelfbhw_cursor_show(dinfo); + return 0; + } + + if (cursor->set & FB_CUR_SETPOS) { + u32 dx, dy; + + dx = cursor->image.dx - info->var.xoffset; + dy = cursor->image.dy - info->var.yoffset; + + intelfbhw_cursor_setpos(dinfo, dx, dy); + } + + if (cursor->set & FB_CUR_SETSIZE) { + if (cursor->image.width > 64 || cursor->image.height > 64) + return -ENXIO; + + intelfbhw_cursor_reset(dinfo); + } + + if (cursor->set & FB_CUR_SETCMAP) { + u32 fg, bg; + + if (dinfo->depth != 8) { + fg = dinfo->pseudo_palette[cursor->image.fg_color]; + bg = dinfo->pseudo_palette[cursor->image.bg_color]; + } else { + fg = cursor->image.fg_color; + bg = cursor->image.bg_color; + } + + intelfbhw_cursor_setcolor(dinfo, bg, fg); + } + + if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETIMAGE)) { + u32 s_pitch = (ROUND_UP_TO(cursor->image.width, 8) / 8); + u32 size = s_pitch * cursor->image.height; + u8 *dat = (u8 *) cursor->image.data; + u8 *msk = (u8 *) cursor->mask; + u8 src[64]; + u32 i; + + if (cursor->image.depth != 1) + return -ENXIO; + + switch (cursor->rop) { + case ROP_XOR: + for (i = 0; i < size; i++) + src[i] = dat[i] ^ msk[i]; + break; + case ROP_COPY: + default: + for (i = 0; i < size; i++) + src[i] = dat[i] & msk[i]; + break; + } + + /* save the bitmap to restore it when XFree will + make the cursor dirty */ + memcpy(dinfo->cursor_src, src, size); + + intelfbhw_cursor_load(dinfo, cursor->image.width, + cursor->image.height, src); + } + + if (cursor->enable) + intelfbhw_cursor_show(dinfo); + + return 0; +} + +static int intelfb_sync(struct fb_info *info) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + +#if VERBOSE > 0 + DBG_MSG("intelfb_sync\n"); +#endif + + if (dinfo->ring_lockup) + return 0; + + intelfbhw_do_sync(dinfo); + return 0; +} + diff --git a/drivers/video/fbdev/intelfb/intelfbhw.c b/drivers/video/fbdev/intelfb/intelfbhw.c new file mode 100644 index 000000000000..fbad61da359f --- /dev/null +++ b/drivers/video/fbdev/intelfb/intelfbhw.c @@ -0,0 +1,2121 @@ +/* + * intelfb + * + * Linux framebuffer driver for Intel(R) 865G integrated graphics chips. + * + * Copyright © 2002, 2003 David Dawes <dawes@xfree86.org> + * 2004 Sylvain Meyer + * + * This driver consists of two parts. The first part (intelfbdrv.c) provides + * the basic fbdev interfaces, is derived in part from the radeonfb and + * vesafb drivers, and is covered by the GPL. The second part (intelfbhw.c) + * provides the code to program the hardware. Most of it is derived from + * the i810/i830 XFree86 driver. The HW-specific code is covered here + * under a dual license (GPL and MIT/XFree86 license). + * + * Author: David Dawes + * + */ + +/* $DHD: intelfb/intelfbhw.c,v 1.9 2003/06/27 15:06:25 dawes Exp $ */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/interrupt.h> + +#include <asm/io.h> + +#include "intelfb.h" +#include "intelfbhw.h" + +struct pll_min_max { + int min_m, max_m, min_m1, max_m1; + int min_m2, max_m2, min_n, max_n; + int min_p, max_p, min_p1, max_p1; + int min_vco, max_vco, p_transition_clk, ref_clk; + int p_inc_lo, p_inc_hi; +}; + +#define PLLS_I8xx 0 +#define PLLS_I9xx 1 +#define PLLS_MAX 2 + +static struct pll_min_max plls[PLLS_MAX] = { + { 108, 140, 18, 26, + 6, 16, 3, 16, + 4, 128, 0, 31, + 930000, 1400000, 165000, 48000, + 4, 2 }, /* I8xx */ + + { 75, 120, 10, 20, + 5, 9, 4, 7, + 5, 80, 1, 8, + 1400000, 2800000, 200000, 96000, + 10, 5 } /* I9xx */ +}; + +int intelfbhw_get_chipset(struct pci_dev *pdev, struct intelfb_info *dinfo) +{ + u32 tmp; + if (!pdev || !dinfo) + return 1; + + switch (pdev->device) { + case PCI_DEVICE_ID_INTEL_830M: + dinfo->name = "Intel(R) 830M"; + dinfo->chipset = INTEL_830M; + dinfo->mobile = 1; + dinfo->pll_index = PLLS_I8xx; + return 0; + case PCI_DEVICE_ID_INTEL_845G: + dinfo->name = "Intel(R) 845G"; + dinfo->chipset = INTEL_845G; + dinfo->mobile = 0; + dinfo->pll_index = PLLS_I8xx; + return 0; + case PCI_DEVICE_ID_INTEL_854: + dinfo->mobile = 1; + dinfo->name = "Intel(R) 854"; + dinfo->chipset = INTEL_854; + return 0; + case PCI_DEVICE_ID_INTEL_85XGM: + tmp = 0; + dinfo->mobile = 1; + dinfo->pll_index = PLLS_I8xx; + pci_read_config_dword(pdev, INTEL_85X_CAPID, &tmp); + switch ((tmp >> INTEL_85X_VARIANT_SHIFT) & + INTEL_85X_VARIANT_MASK) { + case INTEL_VAR_855GME: + dinfo->name = "Intel(R) 855GME"; + dinfo->chipset = INTEL_855GME; + return 0; + case INTEL_VAR_855GM: + dinfo->name = "Intel(R) 855GM"; + dinfo->chipset = INTEL_855GM; + return 0; + case INTEL_VAR_852GME: + dinfo->name = "Intel(R) 852GME"; + dinfo->chipset = INTEL_852GME; + return 0; + case INTEL_VAR_852GM: + dinfo->name = "Intel(R) 852GM"; + dinfo->chipset = INTEL_852GM; + return 0; + default: + dinfo->name = "Intel(R) 852GM/855GM"; + dinfo->chipset = INTEL_85XGM; + return 0; + } + break; + case PCI_DEVICE_ID_INTEL_865G: + dinfo->name = "Intel(R) 865G"; + dinfo->chipset = INTEL_865G; + dinfo->mobile = 0; + dinfo->pll_index = PLLS_I8xx; + return 0; + case PCI_DEVICE_ID_INTEL_915G: + dinfo->name = "Intel(R) 915G"; + dinfo->chipset = INTEL_915G; + dinfo->mobile = 0; + dinfo->pll_index = PLLS_I9xx; + return 0; + case PCI_DEVICE_ID_INTEL_915GM: + dinfo->name = "Intel(R) 915GM"; + dinfo->chipset = INTEL_915GM; + dinfo->mobile = 1; + dinfo->pll_index = PLLS_I9xx; + return 0; + case PCI_DEVICE_ID_INTEL_945G: + dinfo->name = "Intel(R) 945G"; + dinfo->chipset = INTEL_945G; + dinfo->mobile = 0; + dinfo->pll_index = PLLS_I9xx; + return 0; + case PCI_DEVICE_ID_INTEL_945GM: + dinfo->name = "Intel(R) 945GM"; + dinfo->chipset = INTEL_945GM; + dinfo->mobile = 1; + dinfo->pll_index = PLLS_I9xx; + return 0; + case PCI_DEVICE_ID_INTEL_945GME: + dinfo->name = "Intel(R) 945GME"; + dinfo->chipset = INTEL_945GME; + dinfo->mobile = 1; + dinfo->pll_index = PLLS_I9xx; + return 0; + case PCI_DEVICE_ID_INTEL_965G: + dinfo->name = "Intel(R) 965G"; + dinfo->chipset = INTEL_965G; + dinfo->mobile = 0; + dinfo->pll_index = PLLS_I9xx; + return 0; + case PCI_DEVICE_ID_INTEL_965GM: + dinfo->name = "Intel(R) 965GM"; + dinfo->chipset = INTEL_965GM; + dinfo->mobile = 1; + dinfo->pll_index = PLLS_I9xx; + return 0; + default: + return 1; + } +} + +int intelfbhw_get_memory(struct pci_dev *pdev, int *aperture_size, + int *stolen_size) +{ + struct pci_dev *bridge_dev; + u16 tmp; + int stolen_overhead; + + if (!pdev || !aperture_size || !stolen_size) + return 1; + + /* Find the bridge device. It is always 0:0.0 */ + if (!(bridge_dev = pci_get_bus_and_slot(0, PCI_DEVFN(0, 0)))) { + ERR_MSG("cannot find bridge device\n"); + return 1; + } + + /* Get the fb aperture size and "stolen" memory amount. */ + tmp = 0; + pci_read_config_word(bridge_dev, INTEL_GMCH_CTRL, &tmp); + pci_dev_put(bridge_dev); + + switch (pdev->device) { + case PCI_DEVICE_ID_INTEL_915G: + case PCI_DEVICE_ID_INTEL_915GM: + case PCI_DEVICE_ID_INTEL_945G: + case PCI_DEVICE_ID_INTEL_945GM: + case PCI_DEVICE_ID_INTEL_945GME: + case PCI_DEVICE_ID_INTEL_965G: + case PCI_DEVICE_ID_INTEL_965GM: + /* 915, 945 and 965 chipsets support a 256MB aperture. + Aperture size is determined by inspected the + base address of the aperture. */ + if (pci_resource_start(pdev, 2) & 0x08000000) + *aperture_size = MB(128); + else + *aperture_size = MB(256); + break; + default: + if ((tmp & INTEL_GMCH_MEM_MASK) == INTEL_GMCH_MEM_64M) + *aperture_size = MB(64); + else + *aperture_size = MB(128); + break; + } + + /* Stolen memory size is reduced by the GTT and the popup. + GTT is 1K per MB of aperture size, and popup is 4K. */ + stolen_overhead = (*aperture_size / MB(1)) + 4; + switch(pdev->device) { + case PCI_DEVICE_ID_INTEL_830M: + case PCI_DEVICE_ID_INTEL_845G: + switch (tmp & INTEL_830_GMCH_GMS_MASK) { + case INTEL_830_GMCH_GMS_STOLEN_512: + *stolen_size = KB(512) - KB(stolen_overhead); + return 0; + case INTEL_830_GMCH_GMS_STOLEN_1024: + *stolen_size = MB(1) - KB(stolen_overhead); + return 0; + case INTEL_830_GMCH_GMS_STOLEN_8192: + *stolen_size = MB(8) - KB(stolen_overhead); + return 0; + case INTEL_830_GMCH_GMS_LOCAL: + ERR_MSG("only local memory found\n"); + return 1; + case INTEL_830_GMCH_GMS_DISABLED: + ERR_MSG("video memory is disabled\n"); + return 1; + default: + ERR_MSG("unexpected GMCH_GMS value: 0x%02x\n", + tmp & INTEL_830_GMCH_GMS_MASK); + return 1; + } + break; + default: + switch (tmp & INTEL_855_GMCH_GMS_MASK) { + case INTEL_855_GMCH_GMS_STOLEN_1M: + *stolen_size = MB(1) - KB(stolen_overhead); + return 0; + case INTEL_855_GMCH_GMS_STOLEN_4M: + *stolen_size = MB(4) - KB(stolen_overhead); + return 0; + case INTEL_855_GMCH_GMS_STOLEN_8M: + *stolen_size = MB(8) - KB(stolen_overhead); + return 0; + case INTEL_855_GMCH_GMS_STOLEN_16M: + *stolen_size = MB(16) - KB(stolen_overhead); + return 0; + case INTEL_855_GMCH_GMS_STOLEN_32M: + *stolen_size = MB(32) - KB(stolen_overhead); + return 0; + case INTEL_915G_GMCH_GMS_STOLEN_48M: + *stolen_size = MB(48) - KB(stolen_overhead); + return 0; + case INTEL_915G_GMCH_GMS_STOLEN_64M: + *stolen_size = MB(64) - KB(stolen_overhead); + return 0; + case INTEL_855_GMCH_GMS_DISABLED: + ERR_MSG("video memory is disabled\n"); + return 0; + default: + ERR_MSG("unexpected GMCH_GMS value: 0x%02x\n", + tmp & INTEL_855_GMCH_GMS_MASK); + return 1; + } + } +} + +int intelfbhw_check_non_crt(struct intelfb_info *dinfo) +{ + int dvo = 0; + + if (INREG(LVDS) & PORT_ENABLE) + dvo |= LVDS_PORT; + if (INREG(DVOA) & PORT_ENABLE) + dvo |= DVOA_PORT; + if (INREG(DVOB) & PORT_ENABLE) + dvo |= DVOB_PORT; + if (INREG(DVOC) & PORT_ENABLE) + dvo |= DVOC_PORT; + + return dvo; +} + +const char * intelfbhw_dvo_to_string(int dvo) +{ + if (dvo & DVOA_PORT) + return "DVO port A"; + else if (dvo & DVOB_PORT) + return "DVO port B"; + else if (dvo & DVOC_PORT) + return "DVO port C"; + else if (dvo & LVDS_PORT) + return "LVDS port"; + else + return NULL; +} + + +int intelfbhw_validate_mode(struct intelfb_info *dinfo, + struct fb_var_screeninfo *var) +{ + int bytes_per_pixel; + int tmp; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_validate_mode\n"); +#endif + + bytes_per_pixel = var->bits_per_pixel / 8; + if (bytes_per_pixel == 3) + bytes_per_pixel = 4; + + /* Check if enough video memory. */ + tmp = var->yres_virtual * var->xres_virtual * bytes_per_pixel; + if (tmp > dinfo->fb.size) { + WRN_MSG("Not enough video ram for mode " + "(%d KByte vs %d KByte).\n", + BtoKB(tmp), BtoKB(dinfo->fb.size)); + return 1; + } + + /* Check if x/y limits are OK. */ + if (var->xres - 1 > HACTIVE_MASK) { + WRN_MSG("X resolution too large (%d vs %d).\n", + var->xres, HACTIVE_MASK + 1); + return 1; + } + if (var->yres - 1 > VACTIVE_MASK) { + WRN_MSG("Y resolution too large (%d vs %d).\n", + var->yres, VACTIVE_MASK + 1); + return 1; + } + if (var->xres < 4) { + WRN_MSG("X resolution too small (%d vs 4).\n", var->xres); + return 1; + } + if (var->yres < 4) { + WRN_MSG("Y resolution too small (%d vs 4).\n", var->yres); + return 1; + } + + /* Check for doublescan modes. */ + if (var->vmode & FB_VMODE_DOUBLE) { + WRN_MSG("Mode is double-scan.\n"); + return 1; + } + + if ((var->vmode & FB_VMODE_INTERLACED) && (var->yres & 1)) { + WRN_MSG("Odd number of lines in interlaced mode\n"); + return 1; + } + + /* Check if clock is OK. */ + tmp = 1000000000 / var->pixclock; + if (tmp < MIN_CLOCK) { + WRN_MSG("Pixel clock is too low (%d MHz vs %d MHz).\n", + (tmp + 500) / 1000, MIN_CLOCK / 1000); + return 1; + } + if (tmp > MAX_CLOCK) { + WRN_MSG("Pixel clock is too high (%d MHz vs %d MHz).\n", + (tmp + 500) / 1000, MAX_CLOCK / 1000); + return 1; + } + + return 0; +} + +int intelfbhw_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + u32 offset, xoffset, yoffset; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_pan_display\n"); +#endif + + xoffset = ROUND_DOWN_TO(var->xoffset, 8); + yoffset = var->yoffset; + + if ((xoffset + info->var.xres > info->var.xres_virtual) || + (yoffset + info->var.yres > info->var.yres_virtual)) + return -EINVAL; + + offset = (yoffset * dinfo->pitch) + + (xoffset * info->var.bits_per_pixel) / 8; + + offset += dinfo->fb.offset << 12; + + dinfo->vsync.pan_offset = offset; + if ((var->activate & FB_ACTIVATE_VBL) && + !intelfbhw_enable_irq(dinfo)) + dinfo->vsync.pan_display = 1; + else { + dinfo->vsync.pan_display = 0; + OUTREG(DSPABASE, offset); + } + + return 0; +} + +/* Blank the screen. */ +void intelfbhw_do_blank(int blank, struct fb_info *info) +{ + struct intelfb_info *dinfo = GET_DINFO(info); + u32 tmp; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_do_blank: blank is %d\n", blank); +#endif + + /* Turn plane A on or off */ + tmp = INREG(DSPACNTR); + if (blank) + tmp &= ~DISPPLANE_PLANE_ENABLE; + else + tmp |= DISPPLANE_PLANE_ENABLE; + OUTREG(DSPACNTR, tmp); + /* Flush */ + tmp = INREG(DSPABASE); + OUTREG(DSPABASE, tmp); + + /* Turn off/on the HW cursor */ +#if VERBOSE > 0 + DBG_MSG("cursor_on is %d\n", dinfo->cursor_on); +#endif + if (dinfo->cursor_on) { + if (blank) + intelfbhw_cursor_hide(dinfo); + else + intelfbhw_cursor_show(dinfo); + dinfo->cursor_on = 1; + } + dinfo->cursor_blanked = blank; + + /* Set DPMS level */ + tmp = INREG(ADPA) & ~ADPA_DPMS_CONTROL_MASK; + switch (blank) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + tmp |= ADPA_DPMS_D0; + break; + case FB_BLANK_VSYNC_SUSPEND: + tmp |= ADPA_DPMS_D1; + break; + case FB_BLANK_HSYNC_SUSPEND: + tmp |= ADPA_DPMS_D2; + break; + case FB_BLANK_POWERDOWN: + tmp |= ADPA_DPMS_D3; + break; + } + OUTREG(ADPA, tmp); + + return; +} + + +/* Check which pipe is connected to an active display plane. */ +int intelfbhw_active_pipe(const struct intelfb_hwstate *hw) +{ + int pipe = -1; + + /* keep old default behaviour - prefer PIPE_A */ + if (hw->disp_b_ctrl & DISPPLANE_PLANE_ENABLE) { + pipe = (hw->disp_b_ctrl >> DISPPLANE_SEL_PIPE_SHIFT); + pipe &= PIPE_MASK; + if (unlikely(pipe == PIPE_A)) + return PIPE_A; + } + if (hw->disp_a_ctrl & DISPPLANE_PLANE_ENABLE) { + pipe = (hw->disp_a_ctrl >> DISPPLANE_SEL_PIPE_SHIFT); + pipe &= PIPE_MASK; + if (likely(pipe == PIPE_A)) + return PIPE_A; + } + /* Impossible that no pipe is selected - return PIPE_A */ + WARN_ON(pipe == -1); + if (unlikely(pipe == -1)) + pipe = PIPE_A; + + return pipe; +} + +void intelfbhw_setcolreg(struct intelfb_info *dinfo, unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp) +{ + u32 palette_reg = (dinfo->pipe == PIPE_A) ? + PALETTE_A : PALETTE_B; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_setcolreg: %d: (%d, %d, %d)\n", + regno, red, green, blue); +#endif + + OUTREG(palette_reg + (regno << 2), + (red << PALETTE_8_RED_SHIFT) | + (green << PALETTE_8_GREEN_SHIFT) | + (blue << PALETTE_8_BLUE_SHIFT)); +} + + +int intelfbhw_read_hw_state(struct intelfb_info *dinfo, + struct intelfb_hwstate *hw, int flag) +{ + int i; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_read_hw_state\n"); +#endif + + if (!hw || !dinfo) + return -1; + + /* Read in as much of the HW state as possible. */ + hw->vga0_divisor = INREG(VGA0_DIVISOR); + hw->vga1_divisor = INREG(VGA1_DIVISOR); + hw->vga_pd = INREG(VGAPD); + hw->dpll_a = INREG(DPLL_A); + hw->dpll_b = INREG(DPLL_B); + hw->fpa0 = INREG(FPA0); + hw->fpa1 = INREG(FPA1); + hw->fpb0 = INREG(FPB0); + hw->fpb1 = INREG(FPB1); + + if (flag == 1) + return flag; + +#if 0 + /* This seems to be a problem with the 852GM/855GM */ + for (i = 0; i < PALETTE_8_ENTRIES; i++) { + hw->palette_a[i] = INREG(PALETTE_A + (i << 2)); + hw->palette_b[i] = INREG(PALETTE_B + (i << 2)); + } +#endif + + if (flag == 2) + return flag; + + hw->htotal_a = INREG(HTOTAL_A); + hw->hblank_a = INREG(HBLANK_A); + hw->hsync_a = INREG(HSYNC_A); + hw->vtotal_a = INREG(VTOTAL_A); + hw->vblank_a = INREG(VBLANK_A); + hw->vsync_a = INREG(VSYNC_A); + hw->src_size_a = INREG(SRC_SIZE_A); + hw->bclrpat_a = INREG(BCLRPAT_A); + hw->htotal_b = INREG(HTOTAL_B); + hw->hblank_b = INREG(HBLANK_B); + hw->hsync_b = INREG(HSYNC_B); + hw->vtotal_b = INREG(VTOTAL_B); + hw->vblank_b = INREG(VBLANK_B); + hw->vsync_b = INREG(VSYNC_B); + hw->src_size_b = INREG(SRC_SIZE_B); + hw->bclrpat_b = INREG(BCLRPAT_B); + + if (flag == 3) + return flag; + + hw->adpa = INREG(ADPA); + hw->dvoa = INREG(DVOA); + hw->dvob = INREG(DVOB); + hw->dvoc = INREG(DVOC); + hw->dvoa_srcdim = INREG(DVOA_SRCDIM); + hw->dvob_srcdim = INREG(DVOB_SRCDIM); + hw->dvoc_srcdim = INREG(DVOC_SRCDIM); + hw->lvds = INREG(LVDS); + + if (flag == 4) + return flag; + + hw->pipe_a_conf = INREG(PIPEACONF); + hw->pipe_b_conf = INREG(PIPEBCONF); + hw->disp_arb = INREG(DISPARB); + + if (flag == 5) + return flag; + + hw->cursor_a_control = INREG(CURSOR_A_CONTROL); + hw->cursor_b_control = INREG(CURSOR_B_CONTROL); + hw->cursor_a_base = INREG(CURSOR_A_BASEADDR); + hw->cursor_b_base = INREG(CURSOR_B_BASEADDR); + + if (flag == 6) + return flag; + + for (i = 0; i < 4; i++) { + hw->cursor_a_palette[i] = INREG(CURSOR_A_PALETTE0 + (i << 2)); + hw->cursor_b_palette[i] = INREG(CURSOR_B_PALETTE0 + (i << 2)); + } + + if (flag == 7) + return flag; + + hw->cursor_size = INREG(CURSOR_SIZE); + + if (flag == 8) + return flag; + + hw->disp_a_ctrl = INREG(DSPACNTR); + hw->disp_b_ctrl = INREG(DSPBCNTR); + hw->disp_a_base = INREG(DSPABASE); + hw->disp_b_base = INREG(DSPBBASE); + hw->disp_a_stride = INREG(DSPASTRIDE); + hw->disp_b_stride = INREG(DSPBSTRIDE); + + if (flag == 9) + return flag; + + hw->vgacntrl = INREG(VGACNTRL); + + if (flag == 10) + return flag; + + hw->add_id = INREG(ADD_ID); + + if (flag == 11) + return flag; + + for (i = 0; i < 7; i++) { + hw->swf0x[i] = INREG(SWF00 + (i << 2)); + hw->swf1x[i] = INREG(SWF10 + (i << 2)); + if (i < 3) + hw->swf3x[i] = INREG(SWF30 + (i << 2)); + } + + for (i = 0; i < 8; i++) + hw->fence[i] = INREG(FENCE + (i << 2)); + + hw->instpm = INREG(INSTPM); + hw->mem_mode = INREG(MEM_MODE); + hw->fw_blc_0 = INREG(FW_BLC_0); + hw->fw_blc_1 = INREG(FW_BLC_1); + + hw->hwstam = INREG16(HWSTAM); + hw->ier = INREG16(IER); + hw->iir = INREG16(IIR); + hw->imr = INREG16(IMR); + + return 0; +} + + +static int calc_vclock3(int index, int m, int n, int p) +{ + if (p == 0 || n == 0) + return 0; + return plls[index].ref_clk * m / n / p; +} + +static int calc_vclock(int index, int m1, int m2, int n, int p1, int p2, + int lvds) +{ + struct pll_min_max *pll = &plls[index]; + u32 m, vco, p; + + m = (5 * (m1 + 2)) + (m2 + 2); + n += 2; + vco = pll->ref_clk * m / n; + + if (index == PLLS_I8xx) + p = ((p1 + 2) * (1 << (p2 + 1))); + else + p = ((p1) * (p2 ? 5 : 10)); + return vco / p; +} + +#if REGDUMP +static void intelfbhw_get_p1p2(struct intelfb_info *dinfo, int dpll, + int *o_p1, int *o_p2) +{ + int p1, p2; + + if (IS_I9XX(dinfo)) { + if (dpll & DPLL_P1_FORCE_DIV2) + p1 = 1; + else + p1 = (dpll >> DPLL_P1_SHIFT) & 0xff; + + p1 = ffs(p1); + + p2 = (dpll >> DPLL_I9XX_P2_SHIFT) & DPLL_P2_MASK; + } else { + if (dpll & DPLL_P1_FORCE_DIV2) + p1 = 0; + else + p1 = (dpll >> DPLL_P1_SHIFT) & DPLL_P1_MASK; + p2 = (dpll >> DPLL_P2_SHIFT) & DPLL_P2_MASK; + } + + *o_p1 = p1; + *o_p2 = p2; +} +#endif + + +void intelfbhw_print_hw_state(struct intelfb_info *dinfo, + struct intelfb_hwstate *hw) +{ +#if REGDUMP + int i, m1, m2, n, p1, p2; + int index = dinfo->pll_index; + DBG_MSG("intelfbhw_print_hw_state\n"); + + if (!hw) + return; + /* Read in as much of the HW state as possible. */ + printk("hw state dump start\n"); + printk(" VGA0_DIVISOR: 0x%08x\n", hw->vga0_divisor); + printk(" VGA1_DIVISOR: 0x%08x\n", hw->vga1_divisor); + printk(" VGAPD: 0x%08x\n", hw->vga_pd); + n = (hw->vga0_divisor >> FP_N_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m1 = (hw->vga0_divisor >> FP_M1_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m2 = (hw->vga0_divisor >> FP_M2_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + + intelfbhw_get_p1p2(dinfo, hw->vga_pd, &p1, &p2); + + printk(" VGA0: (m1, m2, n, p1, p2) = (%d, %d, %d, %d, %d)\n", + m1, m2, n, p1, p2); + printk(" VGA0: clock is %d\n", + calc_vclock(index, m1, m2, n, p1, p2, 0)); + + n = (hw->vga1_divisor >> FP_N_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m1 = (hw->vga1_divisor >> FP_M1_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m2 = (hw->vga1_divisor >> FP_M2_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + + intelfbhw_get_p1p2(dinfo, hw->vga_pd, &p1, &p2); + printk(" VGA1: (m1, m2, n, p1, p2) = (%d, %d, %d, %d, %d)\n", + m1, m2, n, p1, p2); + printk(" VGA1: clock is %d\n", + calc_vclock(index, m1, m2, n, p1, p2, 0)); + + printk(" DPLL_A: 0x%08x\n", hw->dpll_a); + printk(" DPLL_B: 0x%08x\n", hw->dpll_b); + printk(" FPA0: 0x%08x\n", hw->fpa0); + printk(" FPA1: 0x%08x\n", hw->fpa1); + printk(" FPB0: 0x%08x\n", hw->fpb0); + printk(" FPB1: 0x%08x\n", hw->fpb1); + + n = (hw->fpa0 >> FP_N_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m1 = (hw->fpa0 >> FP_M1_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m2 = (hw->fpa0 >> FP_M2_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + + intelfbhw_get_p1p2(dinfo, hw->dpll_a, &p1, &p2); + + printk(" PLLA0: (m1, m2, n, p1, p2) = (%d, %d, %d, %d, %d)\n", + m1, m2, n, p1, p2); + printk(" PLLA0: clock is %d\n", + calc_vclock(index, m1, m2, n, p1, p2, 0)); + + n = (hw->fpa1 >> FP_N_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m1 = (hw->fpa1 >> FP_M1_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + m2 = (hw->fpa1 >> FP_M2_DIVISOR_SHIFT) & FP_DIVISOR_MASK; + + intelfbhw_get_p1p2(dinfo, hw->dpll_a, &p1, &p2); + + printk(" PLLA1: (m1, m2, n, p1, p2) = (%d, %d, %d, %d, %d)\n", + m1, m2, n, p1, p2); + printk(" PLLA1: clock is %d\n", + calc_vclock(index, m1, m2, n, p1, p2, 0)); + +#if 0 + printk(" PALETTE_A:\n"); + for (i = 0; i < PALETTE_8_ENTRIES) + printk(" %3d: 0x%08x\n", i, hw->palette_a[i]); + printk(" PALETTE_B:\n"); + for (i = 0; i < PALETTE_8_ENTRIES) + printk(" %3d: 0x%08x\n", i, hw->palette_b[i]); +#endif + + printk(" HTOTAL_A: 0x%08x\n", hw->htotal_a); + printk(" HBLANK_A: 0x%08x\n", hw->hblank_a); + printk(" HSYNC_A: 0x%08x\n", hw->hsync_a); + printk(" VTOTAL_A: 0x%08x\n", hw->vtotal_a); + printk(" VBLANK_A: 0x%08x\n", hw->vblank_a); + printk(" VSYNC_A: 0x%08x\n", hw->vsync_a); + printk(" SRC_SIZE_A: 0x%08x\n", hw->src_size_a); + printk(" BCLRPAT_A: 0x%08x\n", hw->bclrpat_a); + printk(" HTOTAL_B: 0x%08x\n", hw->htotal_b); + printk(" HBLANK_B: 0x%08x\n", hw->hblank_b); + printk(" HSYNC_B: 0x%08x\n", hw->hsync_b); + printk(" VTOTAL_B: 0x%08x\n", hw->vtotal_b); + printk(" VBLANK_B: 0x%08x\n", hw->vblank_b); + printk(" VSYNC_B: 0x%08x\n", hw->vsync_b); + printk(" SRC_SIZE_B: 0x%08x\n", hw->src_size_b); + printk(" BCLRPAT_B: 0x%08x\n", hw->bclrpat_b); + + printk(" ADPA: 0x%08x\n", hw->adpa); + printk(" DVOA: 0x%08x\n", hw->dvoa); + printk(" DVOB: 0x%08x\n", hw->dvob); + printk(" DVOC: 0x%08x\n", hw->dvoc); + printk(" DVOA_SRCDIM: 0x%08x\n", hw->dvoa_srcdim); + printk(" DVOB_SRCDIM: 0x%08x\n", hw->dvob_srcdim); + printk(" DVOC_SRCDIM: 0x%08x\n", hw->dvoc_srcdim); + printk(" LVDS: 0x%08x\n", hw->lvds); + + printk(" PIPEACONF: 0x%08x\n", hw->pipe_a_conf); + printk(" PIPEBCONF: 0x%08x\n", hw->pipe_b_conf); + printk(" DISPARB: 0x%08x\n", hw->disp_arb); + + printk(" CURSOR_A_CONTROL: 0x%08x\n", hw->cursor_a_control); + printk(" CURSOR_B_CONTROL: 0x%08x\n", hw->cursor_b_control); + printk(" CURSOR_A_BASEADDR: 0x%08x\n", hw->cursor_a_base); + printk(" CURSOR_B_BASEADDR: 0x%08x\n", hw->cursor_b_base); + + printk(" CURSOR_A_PALETTE: "); + for (i = 0; i < 4; i++) { + printk("0x%08x", hw->cursor_a_palette[i]); + if (i < 3) + printk(", "); + } + printk("\n"); + printk(" CURSOR_B_PALETTE: "); + for (i = 0; i < 4; i++) { + printk("0x%08x", hw->cursor_b_palette[i]); + if (i < 3) + printk(", "); + } + printk("\n"); + + printk(" CURSOR_SIZE: 0x%08x\n", hw->cursor_size); + + printk(" DSPACNTR: 0x%08x\n", hw->disp_a_ctrl); + printk(" DSPBCNTR: 0x%08x\n", hw->disp_b_ctrl); + printk(" DSPABASE: 0x%08x\n", hw->disp_a_base); + printk(" DSPBBASE: 0x%08x\n", hw->disp_b_base); + printk(" DSPASTRIDE: 0x%08x\n", hw->disp_a_stride); + printk(" DSPBSTRIDE: 0x%08x\n", hw->disp_b_stride); + + printk(" VGACNTRL: 0x%08x\n", hw->vgacntrl); + printk(" ADD_ID: 0x%08x\n", hw->add_id); + + for (i = 0; i < 7; i++) { + printk(" SWF0%d 0x%08x\n", i, + hw->swf0x[i]); + } + for (i = 0; i < 7; i++) { + printk(" SWF1%d 0x%08x\n", i, + hw->swf1x[i]); + } + for (i = 0; i < 3; i++) { + printk(" SWF3%d 0x%08x\n", i, + hw->swf3x[i]); + } + for (i = 0; i < 8; i++) + printk(" FENCE%d 0x%08x\n", i, + hw->fence[i]); + + printk(" INSTPM 0x%08x\n", hw->instpm); + printk(" MEM_MODE 0x%08x\n", hw->mem_mode); + printk(" FW_BLC_0 0x%08x\n", hw->fw_blc_0); + printk(" FW_BLC_1 0x%08x\n", hw->fw_blc_1); + + printk(" HWSTAM 0x%04x\n", hw->hwstam); + printk(" IER 0x%04x\n", hw->ier); + printk(" IIR 0x%04x\n", hw->iir); + printk(" IMR 0x%04x\n", hw->imr); + printk("hw state dump end\n"); +#endif +} + + + +/* Split the M parameter into M1 and M2. */ +static int splitm(int index, unsigned int m, unsigned int *retm1, + unsigned int *retm2) +{ + int m1, m2; + int testm; + struct pll_min_max *pll = &plls[index]; + + /* no point optimising too much - brute force m */ + for (m1 = pll->min_m1; m1 < pll->max_m1 + 1; m1++) { + for (m2 = pll->min_m2; m2 < pll->max_m2 + 1; m2++) { + testm = (5 * (m1 + 2)) + (m2 + 2); + if (testm == m) { + *retm1 = (unsigned int)m1; + *retm2 = (unsigned int)m2; + return 0; + } + } + } + return 1; +} + +/* Split the P parameter into P1 and P2. */ +static int splitp(int index, unsigned int p, unsigned int *retp1, + unsigned int *retp2) +{ + int p1, p2; + struct pll_min_max *pll = &plls[index]; + + if (index == PLLS_I9xx) { + p2 = (p % 10) ? 1 : 0; + + p1 = p / (p2 ? 5 : 10); + + *retp1 = (unsigned int)p1; + *retp2 = (unsigned int)p2; + return 0; + } + + if (p % 4 == 0) + p2 = 1; + else + p2 = 0; + p1 = (p / (1 << (p2 + 1))) - 2; + if (p % 4 == 0 && p1 < pll->min_p1) { + p2 = 0; + p1 = (p / (1 << (p2 + 1))) - 2; + } + if (p1 < pll->min_p1 || p1 > pll->max_p1 || + (p1 + 2) * (1 << (p2 + 1)) != p) { + return 1; + } else { + *retp1 = (unsigned int)p1; + *retp2 = (unsigned int)p2; + return 0; + } +} + +static int calc_pll_params(int index, int clock, u32 *retm1, u32 *retm2, + u32 *retn, u32 *retp1, u32 *retp2, u32 *retclock) +{ + u32 m1, m2, n, p1, p2, n1, testm; + u32 f_vco, p, p_best = 0, m, f_out = 0; + u32 err_max, err_target, err_best = 10000000; + u32 n_best = 0, m_best = 0, f_best, f_err; + u32 p_min, p_max, p_inc, div_max; + struct pll_min_max *pll = &plls[index]; + + /* Accept 0.5% difference, but aim for 0.1% */ + err_max = 5 * clock / 1000; + err_target = clock / 1000; + + DBG_MSG("Clock is %d\n", clock); + + div_max = pll->max_vco / clock; + + p_inc = (clock <= pll->p_transition_clk) ? pll->p_inc_lo : pll->p_inc_hi; + p_min = p_inc; + p_max = ROUND_DOWN_TO(div_max, p_inc); + if (p_min < pll->min_p) + p_min = pll->min_p; + if (p_max > pll->max_p) + p_max = pll->max_p; + + DBG_MSG("p range is %d-%d (%d)\n", p_min, p_max, p_inc); + + p = p_min; + do { + if (splitp(index, p, &p1, &p2)) { + WRN_MSG("cannot split p = %d\n", p); + p += p_inc; + continue; + } + n = pll->min_n; + f_vco = clock * p; + + do { + m = ROUND_UP_TO(f_vco * n, pll->ref_clk) / pll->ref_clk; + if (m < pll->min_m) + m = pll->min_m + 1; + if (m > pll->max_m) + m = pll->max_m - 1; + for (testm = m - 1; testm <= m; testm++) { + f_out = calc_vclock3(index, testm, n, p); + if (splitm(index, testm, &m1, &m2)) { + WRN_MSG("cannot split m = %d\n", + testm); + continue; + } + if (clock > f_out) + f_err = clock - f_out; + else/* slightly bias the error for bigger clocks */ + f_err = f_out - clock + 1; + + if (f_err < err_best) { + m_best = testm; + n_best = n; + p_best = p; + f_best = f_out; + err_best = f_err; + } + } + n++; + } while ((n <= pll->max_n) && (f_out >= clock)); + p += p_inc; + } while ((p <= p_max)); + + if (!m_best) { + WRN_MSG("cannot find parameters for clock %d\n", clock); + return 1; + } + m = m_best; + n = n_best; + p = p_best; + splitm(index, m, &m1, &m2); + splitp(index, p, &p1, &p2); + n1 = n - 2; + + DBG_MSG("m, n, p: %d (%d,%d), %d (%d), %d (%d,%d), " + "f: %d (%d), VCO: %d\n", + m, m1, m2, n, n1, p, p1, p2, + calc_vclock3(index, m, n, p), + calc_vclock(index, m1, m2, n1, p1, p2, 0), + calc_vclock3(index, m, n, p) * p); + *retm1 = m1; + *retm2 = m2; + *retn = n1; + *retp1 = p1; + *retp2 = p2; + *retclock = calc_vclock(index, m1, m2, n1, p1, p2, 0); + + return 0; +} + +static __inline__ int check_overflow(u32 value, u32 limit, + const char *description) +{ + if (value > limit) { + WRN_MSG("%s value %d exceeds limit %d\n", + description, value, limit); + return 1; + } + return 0; +} + +/* It is assumed that hw is filled in with the initial state information. */ +int intelfbhw_mode_to_hw(struct intelfb_info *dinfo, + struct intelfb_hwstate *hw, + struct fb_var_screeninfo *var) +{ + int pipe = intelfbhw_active_pipe(hw); + u32 *dpll, *fp0, *fp1; + u32 m1, m2, n, p1, p2, clock_target, clock; + u32 hsync_start, hsync_end, hblank_start, hblank_end, htotal, hactive; + u32 vsync_start, vsync_end, vblank_start, vblank_end, vtotal, vactive; + u32 vsync_pol, hsync_pol; + u32 *vs, *vb, *vt, *hs, *hb, *ht, *ss, *pipe_conf; + u32 stride_alignment; + + DBG_MSG("intelfbhw_mode_to_hw\n"); + + /* Disable VGA */ + hw->vgacntrl |= VGA_DISABLE; + + /* Set which pipe's registers will be set. */ + if (pipe == PIPE_B) { + dpll = &hw->dpll_b; + fp0 = &hw->fpb0; + fp1 = &hw->fpb1; + hs = &hw->hsync_b; + hb = &hw->hblank_b; + ht = &hw->htotal_b; + vs = &hw->vsync_b; + vb = &hw->vblank_b; + vt = &hw->vtotal_b; + ss = &hw->src_size_b; + pipe_conf = &hw->pipe_b_conf; + } else { + dpll = &hw->dpll_a; + fp0 = &hw->fpa0; + fp1 = &hw->fpa1; + hs = &hw->hsync_a; + hb = &hw->hblank_a; + ht = &hw->htotal_a; + vs = &hw->vsync_a; + vb = &hw->vblank_a; + vt = &hw->vtotal_a; + ss = &hw->src_size_a; + pipe_conf = &hw->pipe_a_conf; + } + + /* Use ADPA register for sync control. */ + hw->adpa &= ~ADPA_USE_VGA_HVPOLARITY; + + /* sync polarity */ + hsync_pol = (var->sync & FB_SYNC_HOR_HIGH_ACT) ? + ADPA_SYNC_ACTIVE_HIGH : ADPA_SYNC_ACTIVE_LOW; + vsync_pol = (var->sync & FB_SYNC_VERT_HIGH_ACT) ? + ADPA_SYNC_ACTIVE_HIGH : ADPA_SYNC_ACTIVE_LOW; + hw->adpa &= ~((ADPA_SYNC_ACTIVE_MASK << ADPA_VSYNC_ACTIVE_SHIFT) | + (ADPA_SYNC_ACTIVE_MASK << ADPA_HSYNC_ACTIVE_SHIFT)); + hw->adpa |= (hsync_pol << ADPA_HSYNC_ACTIVE_SHIFT) | + (vsync_pol << ADPA_VSYNC_ACTIVE_SHIFT); + + /* Connect correct pipe to the analog port DAC */ + hw->adpa &= ~(PIPE_MASK << ADPA_PIPE_SELECT_SHIFT); + hw->adpa |= (pipe << ADPA_PIPE_SELECT_SHIFT); + + /* Set DPMS state to D0 (on) */ + hw->adpa &= ~ADPA_DPMS_CONTROL_MASK; + hw->adpa |= ADPA_DPMS_D0; + + hw->adpa |= ADPA_DAC_ENABLE; + + *dpll |= (DPLL_VCO_ENABLE | DPLL_VGA_MODE_DISABLE); + *dpll &= ~(DPLL_RATE_SELECT_MASK | DPLL_REFERENCE_SELECT_MASK); + *dpll |= (DPLL_REFERENCE_DEFAULT | DPLL_RATE_SELECT_FP0); + + /* Desired clock in kHz */ + clock_target = 1000000000 / var->pixclock; + + if (calc_pll_params(dinfo->pll_index, clock_target, &m1, &m2, + &n, &p1, &p2, &clock)) { + WRN_MSG("calc_pll_params failed\n"); + return 1; + } + + /* Check for overflow. */ + if (check_overflow(p1, DPLL_P1_MASK, "PLL P1 parameter")) + return 1; + if (check_overflow(p2, DPLL_P2_MASK, "PLL P2 parameter")) + return 1; + if (check_overflow(m1, FP_DIVISOR_MASK, "PLL M1 parameter")) + return 1; + if (check_overflow(m2, FP_DIVISOR_MASK, "PLL M2 parameter")) + return 1; + if (check_overflow(n, FP_DIVISOR_MASK, "PLL N parameter")) + return 1; + + *dpll &= ~DPLL_P1_FORCE_DIV2; + *dpll &= ~((DPLL_P2_MASK << DPLL_P2_SHIFT) | + (DPLL_P1_MASK << DPLL_P1_SHIFT)); + + if (IS_I9XX(dinfo)) { + *dpll |= (p2 << DPLL_I9XX_P2_SHIFT); + *dpll |= (1 << (p1 - 1)) << DPLL_P1_SHIFT; + } else + *dpll |= (p2 << DPLL_P2_SHIFT) | (p1 << DPLL_P1_SHIFT); + + *fp0 = (n << FP_N_DIVISOR_SHIFT) | + (m1 << FP_M1_DIVISOR_SHIFT) | + (m2 << FP_M2_DIVISOR_SHIFT); + *fp1 = *fp0; + + hw->dvob &= ~PORT_ENABLE; + hw->dvoc &= ~PORT_ENABLE; + + /* Use display plane A. */ + hw->disp_a_ctrl |= DISPPLANE_PLANE_ENABLE; + hw->disp_a_ctrl &= ~DISPPLANE_GAMMA_ENABLE; + hw->disp_a_ctrl &= ~DISPPLANE_PIXFORMAT_MASK; + switch (intelfb_var_to_depth(var)) { + case 8: + hw->disp_a_ctrl |= DISPPLANE_8BPP | DISPPLANE_GAMMA_ENABLE; + break; + case 15: + hw->disp_a_ctrl |= DISPPLANE_15_16BPP; + break; + case 16: + hw->disp_a_ctrl |= DISPPLANE_16BPP; + break; + case 24: + hw->disp_a_ctrl |= DISPPLANE_32BPP_NO_ALPHA; + break; + } + hw->disp_a_ctrl &= ~(PIPE_MASK << DISPPLANE_SEL_PIPE_SHIFT); + hw->disp_a_ctrl |= (pipe << DISPPLANE_SEL_PIPE_SHIFT); + + /* Set CRTC registers. */ + hactive = var->xres; + hsync_start = hactive + var->right_margin; + hsync_end = hsync_start + var->hsync_len; + htotal = hsync_end + var->left_margin; + hblank_start = hactive; + hblank_end = htotal; + + DBG_MSG("H: act %d, ss %d, se %d, tot %d bs %d, be %d\n", + hactive, hsync_start, hsync_end, htotal, hblank_start, + hblank_end); + + vactive = var->yres; + if (var->vmode & FB_VMODE_INTERLACED) + vactive--; /* the chip adds 2 halflines automatically */ + vsync_start = vactive + var->lower_margin; + vsync_end = vsync_start + var->vsync_len; + vtotal = vsync_end + var->upper_margin; + vblank_start = vactive; + vblank_end = vtotal; + vblank_end = vsync_end + 1; + + DBG_MSG("V: act %d, ss %d, se %d, tot %d bs %d, be %d\n", + vactive, vsync_start, vsync_end, vtotal, vblank_start, + vblank_end); + + /* Adjust for register values, and check for overflow. */ + hactive--; + if (check_overflow(hactive, HACTIVE_MASK, "CRTC hactive")) + return 1; + hsync_start--; + if (check_overflow(hsync_start, HSYNCSTART_MASK, "CRTC hsync_start")) + return 1; + hsync_end--; + if (check_overflow(hsync_end, HSYNCEND_MASK, "CRTC hsync_end")) + return 1; + htotal--; + if (check_overflow(htotal, HTOTAL_MASK, "CRTC htotal")) + return 1; + hblank_start--; + if (check_overflow(hblank_start, HBLANKSTART_MASK, "CRTC hblank_start")) + return 1; + hblank_end--; + if (check_overflow(hblank_end, HBLANKEND_MASK, "CRTC hblank_end")) + return 1; + + vactive--; + if (check_overflow(vactive, VACTIVE_MASK, "CRTC vactive")) + return 1; + vsync_start--; + if (check_overflow(vsync_start, VSYNCSTART_MASK, "CRTC vsync_start")) + return 1; + vsync_end--; + if (check_overflow(vsync_end, VSYNCEND_MASK, "CRTC vsync_end")) + return 1; + vtotal--; + if (check_overflow(vtotal, VTOTAL_MASK, "CRTC vtotal")) + return 1; + vblank_start--; + if (check_overflow(vblank_start, VBLANKSTART_MASK, "CRTC vblank_start")) + return 1; + vblank_end--; + if (check_overflow(vblank_end, VBLANKEND_MASK, "CRTC vblank_end")) + return 1; + + *ht = (htotal << HTOTAL_SHIFT) | (hactive << HACTIVE_SHIFT); + *hb = (hblank_start << HBLANKSTART_SHIFT) | + (hblank_end << HSYNCEND_SHIFT); + *hs = (hsync_start << HSYNCSTART_SHIFT) | (hsync_end << HSYNCEND_SHIFT); + + *vt = (vtotal << VTOTAL_SHIFT) | (vactive << VACTIVE_SHIFT); + *vb = (vblank_start << VBLANKSTART_SHIFT) | + (vblank_end << VSYNCEND_SHIFT); + *vs = (vsync_start << VSYNCSTART_SHIFT) | (vsync_end << VSYNCEND_SHIFT); + *ss = (hactive << SRC_SIZE_HORIZ_SHIFT) | + (vactive << SRC_SIZE_VERT_SHIFT); + + hw->disp_a_stride = dinfo->pitch; + DBG_MSG("pitch is %d\n", hw->disp_a_stride); + + hw->disp_a_base = hw->disp_a_stride * var->yoffset + + var->xoffset * var->bits_per_pixel / 8; + + hw->disp_a_base += dinfo->fb.offset << 12; + + /* Check stride alignment. */ + stride_alignment = IS_I9XX(dinfo) ? STRIDE_ALIGNMENT_I9XX : + STRIDE_ALIGNMENT; + if (hw->disp_a_stride % stride_alignment != 0) { + WRN_MSG("display stride %d has bad alignment %d\n", + hw->disp_a_stride, stride_alignment); + return 1; + } + + /* Set the palette to 8-bit mode. */ + *pipe_conf &= ~PIPECONF_GAMMA; + + if (var->vmode & FB_VMODE_INTERLACED) + *pipe_conf |= PIPECONF_INTERLACE_W_FIELD_INDICATION; + else + *pipe_conf &= ~PIPECONF_INTERLACE_MASK; + + return 0; +} + +/* Program a (non-VGA) video mode. */ +int intelfbhw_program_mode(struct intelfb_info *dinfo, + const struct intelfb_hwstate *hw, int blank) +{ + u32 tmp; + const u32 *dpll, *fp0, *fp1, *pipe_conf; + const u32 *hs, *ht, *hb, *vs, *vt, *vb, *ss; + u32 dpll_reg, fp0_reg, fp1_reg, pipe_conf_reg, pipe_stat_reg; + u32 hsync_reg, htotal_reg, hblank_reg; + u32 vsync_reg, vtotal_reg, vblank_reg; + u32 src_size_reg; + u32 count, tmp_val[3]; + + /* Assume single pipe */ + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_program_mode\n"); +#endif + + /* Disable VGA */ + tmp = INREG(VGACNTRL); + tmp |= VGA_DISABLE; + OUTREG(VGACNTRL, tmp); + + dinfo->pipe = intelfbhw_active_pipe(hw); + + if (dinfo->pipe == PIPE_B) { + dpll = &hw->dpll_b; + fp0 = &hw->fpb0; + fp1 = &hw->fpb1; + pipe_conf = &hw->pipe_b_conf; + hs = &hw->hsync_b; + hb = &hw->hblank_b; + ht = &hw->htotal_b; + vs = &hw->vsync_b; + vb = &hw->vblank_b; + vt = &hw->vtotal_b; + ss = &hw->src_size_b; + dpll_reg = DPLL_B; + fp0_reg = FPB0; + fp1_reg = FPB1; + pipe_conf_reg = PIPEBCONF; + pipe_stat_reg = PIPEBSTAT; + hsync_reg = HSYNC_B; + htotal_reg = HTOTAL_B; + hblank_reg = HBLANK_B; + vsync_reg = VSYNC_B; + vtotal_reg = VTOTAL_B; + vblank_reg = VBLANK_B; + src_size_reg = SRC_SIZE_B; + } else { + dpll = &hw->dpll_a; + fp0 = &hw->fpa0; + fp1 = &hw->fpa1; + pipe_conf = &hw->pipe_a_conf; + hs = &hw->hsync_a; + hb = &hw->hblank_a; + ht = &hw->htotal_a; + vs = &hw->vsync_a; + vb = &hw->vblank_a; + vt = &hw->vtotal_a; + ss = &hw->src_size_a; + dpll_reg = DPLL_A; + fp0_reg = FPA0; + fp1_reg = FPA1; + pipe_conf_reg = PIPEACONF; + pipe_stat_reg = PIPEASTAT; + hsync_reg = HSYNC_A; + htotal_reg = HTOTAL_A; + hblank_reg = HBLANK_A; + vsync_reg = VSYNC_A; + vtotal_reg = VTOTAL_A; + vblank_reg = VBLANK_A; + src_size_reg = SRC_SIZE_A; + } + + /* turn off pipe */ + tmp = INREG(pipe_conf_reg); + tmp &= ~PIPECONF_ENABLE; + OUTREG(pipe_conf_reg, tmp); + + count = 0; + do { + tmp_val[count % 3] = INREG(PIPEA_DSL); + if ((tmp_val[0] == tmp_val[1]) && (tmp_val[1] == tmp_val[2])) + break; + count++; + udelay(1); + if (count % 200 == 0) { + tmp = INREG(pipe_conf_reg); + tmp &= ~PIPECONF_ENABLE; + OUTREG(pipe_conf_reg, tmp); + } + } while (count < 2000); + + OUTREG(ADPA, INREG(ADPA) & ~ADPA_DAC_ENABLE); + + /* Disable planes A and B. */ + tmp = INREG(DSPACNTR); + tmp &= ~DISPPLANE_PLANE_ENABLE; + OUTREG(DSPACNTR, tmp); + tmp = INREG(DSPBCNTR); + tmp &= ~DISPPLANE_PLANE_ENABLE; + OUTREG(DSPBCNTR, tmp); + + /* Wait for vblank. For now, just wait for a 50Hz cycle (20ms)) */ + mdelay(20); + + OUTREG(DVOB, INREG(DVOB) & ~PORT_ENABLE); + OUTREG(DVOC, INREG(DVOC) & ~PORT_ENABLE); + OUTREG(ADPA, INREG(ADPA) & ~ADPA_DAC_ENABLE); + + /* Disable Sync */ + tmp = INREG(ADPA); + tmp &= ~ADPA_DPMS_CONTROL_MASK; + tmp |= ADPA_DPMS_D3; + OUTREG(ADPA, tmp); + + /* do some funky magic - xyzzy */ + OUTREG(0x61204, 0xabcd0000); + + /* turn off PLL */ + tmp = INREG(dpll_reg); + tmp &= ~DPLL_VCO_ENABLE; + OUTREG(dpll_reg, tmp); + + /* Set PLL parameters */ + OUTREG(fp0_reg, *fp0); + OUTREG(fp1_reg, *fp1); + + /* Enable PLL */ + OUTREG(dpll_reg, *dpll); + + /* Set DVOs B/C */ + OUTREG(DVOB, hw->dvob); + OUTREG(DVOC, hw->dvoc); + + /* undo funky magic */ + OUTREG(0x61204, 0x00000000); + + /* Set ADPA */ + OUTREG(ADPA, INREG(ADPA) | ADPA_DAC_ENABLE); + OUTREG(ADPA, (hw->adpa & ~(ADPA_DPMS_CONTROL_MASK)) | ADPA_DPMS_D3); + + /* Set pipe parameters */ + OUTREG(hsync_reg, *hs); + OUTREG(hblank_reg, *hb); + OUTREG(htotal_reg, *ht); + OUTREG(vsync_reg, *vs); + OUTREG(vblank_reg, *vb); + OUTREG(vtotal_reg, *vt); + OUTREG(src_size_reg, *ss); + + switch (dinfo->info->var.vmode & (FB_VMODE_INTERLACED | + FB_VMODE_ODD_FLD_FIRST)) { + case FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST: + OUTREG(pipe_stat_reg, 0xFFFF | PIPESTAT_FLD_EVT_ODD_EN); + break; + case FB_VMODE_INTERLACED: /* even lines first */ + OUTREG(pipe_stat_reg, 0xFFFF | PIPESTAT_FLD_EVT_EVEN_EN); + break; + default: /* non-interlaced */ + OUTREG(pipe_stat_reg, 0xFFFF); /* clear all status bits only */ + } + /* Enable pipe */ + OUTREG(pipe_conf_reg, *pipe_conf | PIPECONF_ENABLE); + + /* Enable sync */ + tmp = INREG(ADPA); + tmp &= ~ADPA_DPMS_CONTROL_MASK; + tmp |= ADPA_DPMS_D0; + OUTREG(ADPA, tmp); + + /* setup display plane */ + if (dinfo->pdev->device == PCI_DEVICE_ID_INTEL_830M) { + /* + * i830M errata: the display plane must be enabled + * to allow writes to the other bits in the plane + * control register. + */ + tmp = INREG(DSPACNTR); + if ((tmp & DISPPLANE_PLANE_ENABLE) != DISPPLANE_PLANE_ENABLE) { + tmp |= DISPPLANE_PLANE_ENABLE; + OUTREG(DSPACNTR, tmp); + OUTREG(DSPACNTR, + hw->disp_a_ctrl|DISPPLANE_PLANE_ENABLE); + mdelay(1); + } + } + + OUTREG(DSPACNTR, hw->disp_a_ctrl & ~DISPPLANE_PLANE_ENABLE); + OUTREG(DSPASTRIDE, hw->disp_a_stride); + OUTREG(DSPABASE, hw->disp_a_base); + + /* Enable plane */ + if (!blank) { + tmp = INREG(DSPACNTR); + tmp |= DISPPLANE_PLANE_ENABLE; + OUTREG(DSPACNTR, tmp); + OUTREG(DSPABASE, hw->disp_a_base); + } + + return 0; +} + +/* forward declarations */ +static void refresh_ring(struct intelfb_info *dinfo); +static void reset_state(struct intelfb_info *dinfo); +static void do_flush(struct intelfb_info *dinfo); + +static u32 get_ring_space(struct intelfb_info *dinfo) +{ + u32 ring_space; + + if (dinfo->ring_tail >= dinfo->ring_head) + ring_space = dinfo->ring.size - + (dinfo->ring_tail - dinfo->ring_head); + else + ring_space = dinfo->ring_head - dinfo->ring_tail; + + if (ring_space > RING_MIN_FREE) + ring_space -= RING_MIN_FREE; + else + ring_space = 0; + + return ring_space; +} + +static int wait_ring(struct intelfb_info *dinfo, int n) +{ + int i = 0; + unsigned long end; + u32 last_head = INREG(PRI_RING_HEAD) & RING_HEAD_MASK; + +#if VERBOSE > 0 + DBG_MSG("wait_ring: %d\n", n); +#endif + + end = jiffies + (HZ * 3); + while (dinfo->ring_space < n) { + dinfo->ring_head = INREG(PRI_RING_HEAD) & RING_HEAD_MASK; + dinfo->ring_space = get_ring_space(dinfo); + + if (dinfo->ring_head != last_head) { + end = jiffies + (HZ * 3); + last_head = dinfo->ring_head; + } + i++; + if (time_before(end, jiffies)) { + if (!i) { + /* Try again */ + reset_state(dinfo); + refresh_ring(dinfo); + do_flush(dinfo); + end = jiffies + (HZ * 3); + i = 1; + } else { + WRN_MSG("ring buffer : space: %d wanted %d\n", + dinfo->ring_space, n); + WRN_MSG("lockup - turning off hardware " + "acceleration\n"); + dinfo->ring_lockup = 1; + break; + } + } + udelay(1); + } + return i; +} + +static void do_flush(struct intelfb_info *dinfo) +{ + START_RING(2); + OUT_RING(MI_FLUSH | MI_WRITE_DIRTY_STATE | MI_INVALIDATE_MAP_CACHE); + OUT_RING(MI_NOOP); + ADVANCE_RING(); +} + +void intelfbhw_do_sync(struct intelfb_info *dinfo) +{ +#if VERBOSE > 0 + DBG_MSG("intelfbhw_do_sync\n"); +#endif + + if (!dinfo->accel) + return; + + /* + * Send a flush, then wait until the ring is empty. This is what + * the XFree86 driver does, and actually it doesn't seem a lot worse + * than the recommended method (both have problems). + */ + do_flush(dinfo); + wait_ring(dinfo, dinfo->ring.size - RING_MIN_FREE); + dinfo->ring_space = dinfo->ring.size - RING_MIN_FREE; +} + +static void refresh_ring(struct intelfb_info *dinfo) +{ +#if VERBOSE > 0 + DBG_MSG("refresh_ring\n"); +#endif + + dinfo->ring_head = INREG(PRI_RING_HEAD) & RING_HEAD_MASK; + dinfo->ring_tail = INREG(PRI_RING_TAIL) & RING_TAIL_MASK; + dinfo->ring_space = get_ring_space(dinfo); +} + +static void reset_state(struct intelfb_info *dinfo) +{ + int i; + u32 tmp; + +#if VERBOSE > 0 + DBG_MSG("reset_state\n"); +#endif + + for (i = 0; i < FENCE_NUM; i++) + OUTREG(FENCE + (i << 2), 0); + + /* Flush the ring buffer if it's enabled. */ + tmp = INREG(PRI_RING_LENGTH); + if (tmp & RING_ENABLE) { +#if VERBOSE > 0 + DBG_MSG("reset_state: ring was enabled\n"); +#endif + refresh_ring(dinfo); + intelfbhw_do_sync(dinfo); + DO_RING_IDLE(); + } + + OUTREG(PRI_RING_LENGTH, 0); + OUTREG(PRI_RING_HEAD, 0); + OUTREG(PRI_RING_TAIL, 0); + OUTREG(PRI_RING_START, 0); +} + +/* Stop the 2D engine, and turn off the ring buffer. */ +void intelfbhw_2d_stop(struct intelfb_info *dinfo) +{ +#if VERBOSE > 0 + DBG_MSG("intelfbhw_2d_stop: accel: %d, ring_active: %d\n", + dinfo->accel, dinfo->ring_active); +#endif + + if (!dinfo->accel) + return; + + dinfo->ring_active = 0; + reset_state(dinfo); +} + +/* + * Enable the ring buffer, and initialise the 2D engine. + * It is assumed that the graphics engine has been stopped by previously + * calling intelfb_2d_stop(). + */ +void intelfbhw_2d_start(struct intelfb_info *dinfo) +{ +#if VERBOSE > 0 + DBG_MSG("intelfbhw_2d_start: accel: %d, ring_active: %d\n", + dinfo->accel, dinfo->ring_active); +#endif + + if (!dinfo->accel) + return; + + /* Initialise the primary ring buffer. */ + OUTREG(PRI_RING_LENGTH, 0); + OUTREG(PRI_RING_TAIL, 0); + OUTREG(PRI_RING_HEAD, 0); + + OUTREG(PRI_RING_START, dinfo->ring.physical & RING_START_MASK); + OUTREG(PRI_RING_LENGTH, + ((dinfo->ring.size - GTT_PAGE_SIZE) & RING_LENGTH_MASK) | + RING_NO_REPORT | RING_ENABLE); + refresh_ring(dinfo); + dinfo->ring_active = 1; +} + +/* 2D fillrect (solid fill or invert) */ +void intelfbhw_do_fillrect(struct intelfb_info *dinfo, u32 x, u32 y, u32 w, + u32 h, u32 color, u32 pitch, u32 bpp, u32 rop) +{ + u32 br00, br09, br13, br14, br16; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_do_fillrect: (%d,%d) %dx%d, c 0x%06x, p %d bpp %d, " + "rop 0x%02x\n", x, y, w, h, color, pitch, bpp, rop); +#endif + + br00 = COLOR_BLT_CMD; + br09 = dinfo->fb_start + (y * pitch + x * (bpp / 8)); + br13 = (rop << ROP_SHIFT) | pitch; + br14 = (h << HEIGHT_SHIFT) | ((w * (bpp / 8)) << WIDTH_SHIFT); + br16 = color; + + switch (bpp) { + case 8: + br13 |= COLOR_DEPTH_8; + break; + case 16: + br13 |= COLOR_DEPTH_16; + break; + case 32: + br13 |= COLOR_DEPTH_32; + br00 |= WRITE_ALPHA | WRITE_RGB; + break; + } + + START_RING(6); + OUT_RING(br00); + OUT_RING(br13); + OUT_RING(br14); + OUT_RING(br09); + OUT_RING(br16); + OUT_RING(MI_NOOP); + ADVANCE_RING(); + +#if VERBOSE > 0 + DBG_MSG("ring = 0x%08x, 0x%08x (%d)\n", dinfo->ring_head, + dinfo->ring_tail, dinfo->ring_space); +#endif +} + +void +intelfbhw_do_bitblt(struct intelfb_info *dinfo, u32 curx, u32 cury, + u32 dstx, u32 dsty, u32 w, u32 h, u32 pitch, u32 bpp) +{ + u32 br00, br09, br11, br12, br13, br22, br23, br26; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_do_bitblt: (%d,%d)->(%d,%d) %dx%d, p %d bpp %d\n", + curx, cury, dstx, dsty, w, h, pitch, bpp); +#endif + + br00 = XY_SRC_COPY_BLT_CMD; + br09 = dinfo->fb_start; + br11 = (pitch << PITCH_SHIFT); + br12 = dinfo->fb_start; + br13 = (SRC_ROP_GXCOPY << ROP_SHIFT) | (pitch << PITCH_SHIFT); + br22 = (dstx << WIDTH_SHIFT) | (dsty << HEIGHT_SHIFT); + br23 = ((dstx + w) << WIDTH_SHIFT) | + ((dsty + h) << HEIGHT_SHIFT); + br26 = (curx << WIDTH_SHIFT) | (cury << HEIGHT_SHIFT); + + switch (bpp) { + case 8: + br13 |= COLOR_DEPTH_8; + break; + case 16: + br13 |= COLOR_DEPTH_16; + break; + case 32: + br13 |= COLOR_DEPTH_32; + br00 |= WRITE_ALPHA | WRITE_RGB; + break; + } + + START_RING(8); + OUT_RING(br00); + OUT_RING(br13); + OUT_RING(br22); + OUT_RING(br23); + OUT_RING(br09); + OUT_RING(br26); + OUT_RING(br11); + OUT_RING(br12); + ADVANCE_RING(); +} + +int intelfbhw_do_drawglyph(struct intelfb_info *dinfo, u32 fg, u32 bg, u32 w, + u32 h, const u8* cdat, u32 x, u32 y, u32 pitch, + u32 bpp) +{ + int nbytes, ndwords, pad, tmp; + u32 br00, br09, br13, br18, br19, br22, br23; + int dat, ix, iy, iw; + int i, j; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_do_drawglyph: (%d,%d) %dx%d\n", x, y, w, h); +#endif + + /* size in bytes of a padded scanline */ + nbytes = ROUND_UP_TO(w, 16) / 8; + + /* Total bytes of padded scanline data to write out. */ + nbytes = nbytes * h; + + /* + * Check if the glyph data exceeds the immediate mode limit. + * It would take a large font (1K pixels) to hit this limit. + */ + if (nbytes > MAX_MONO_IMM_SIZE) + return 0; + + /* Src data is packaged a dword (32-bit) at a time. */ + ndwords = ROUND_UP_TO(nbytes, 4) / 4; + + /* + * Ring has to be padded to a quad word. But because the command starts + with 7 bytes, pad only if there is an even number of ndwords + */ + pad = !(ndwords % 2); + + tmp = (XY_MONO_SRC_IMM_BLT_CMD & DW_LENGTH_MASK) + ndwords; + br00 = (XY_MONO_SRC_IMM_BLT_CMD & ~DW_LENGTH_MASK) | tmp; + br09 = dinfo->fb_start; + br13 = (SRC_ROP_GXCOPY << ROP_SHIFT) | (pitch << PITCH_SHIFT); + br18 = bg; + br19 = fg; + br22 = (x << WIDTH_SHIFT) | (y << HEIGHT_SHIFT); + br23 = ((x + w) << WIDTH_SHIFT) | ((y + h) << HEIGHT_SHIFT); + + switch (bpp) { + case 8: + br13 |= COLOR_DEPTH_8; + break; + case 16: + br13 |= COLOR_DEPTH_16; + break; + case 32: + br13 |= COLOR_DEPTH_32; + br00 |= WRITE_ALPHA | WRITE_RGB; + break; + } + + START_RING(8 + ndwords); + OUT_RING(br00); + OUT_RING(br13); + OUT_RING(br22); + OUT_RING(br23); + OUT_RING(br09); + OUT_RING(br18); + OUT_RING(br19); + ix = iy = 0; + iw = ROUND_UP_TO(w, 8) / 8; + while (ndwords--) { + dat = 0; + for (j = 0; j < 2; ++j) { + for (i = 0; i < 2; ++i) { + if (ix != iw || i == 0) + dat |= cdat[iy*iw + ix++] << (i+j*2)*8; + } + if (ix == iw && iy != (h-1)) { + ix = 0; + ++iy; + } + } + OUT_RING(dat); + } + if (pad) + OUT_RING(MI_NOOP); + ADVANCE_RING(); + + return 1; +} + +/* HW cursor functions. */ +void intelfbhw_cursor_init(struct intelfb_info *dinfo) +{ + u32 tmp; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_cursor_init\n"); +#endif + + if (dinfo->mobile || IS_I9XX(dinfo)) { + if (!dinfo->cursor.physical) + return; + tmp = INREG(CURSOR_A_CONTROL); + tmp &= ~(CURSOR_MODE_MASK | CURSOR_MOBILE_GAMMA_ENABLE | + CURSOR_MEM_TYPE_LOCAL | + (1 << CURSOR_PIPE_SELECT_SHIFT)); + tmp |= CURSOR_MODE_DISABLE; + OUTREG(CURSOR_A_CONTROL, tmp); + OUTREG(CURSOR_A_BASEADDR, dinfo->cursor.physical); + } else { + tmp = INREG(CURSOR_CONTROL); + tmp &= ~(CURSOR_FORMAT_MASK | CURSOR_GAMMA_ENABLE | + CURSOR_ENABLE | CURSOR_STRIDE_MASK); + tmp = CURSOR_FORMAT_3C; + OUTREG(CURSOR_CONTROL, tmp); + OUTREG(CURSOR_A_BASEADDR, dinfo->cursor.offset << 12); + tmp = (64 << CURSOR_SIZE_H_SHIFT) | + (64 << CURSOR_SIZE_V_SHIFT); + OUTREG(CURSOR_SIZE, tmp); + } +} + +void intelfbhw_cursor_hide(struct intelfb_info *dinfo) +{ + u32 tmp; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_cursor_hide\n"); +#endif + + dinfo->cursor_on = 0; + if (dinfo->mobile || IS_I9XX(dinfo)) { + if (!dinfo->cursor.physical) + return; + tmp = INREG(CURSOR_A_CONTROL); + tmp &= ~CURSOR_MODE_MASK; + tmp |= CURSOR_MODE_DISABLE; + OUTREG(CURSOR_A_CONTROL, tmp); + /* Flush changes */ + OUTREG(CURSOR_A_BASEADDR, dinfo->cursor.physical); + } else { + tmp = INREG(CURSOR_CONTROL); + tmp &= ~CURSOR_ENABLE; + OUTREG(CURSOR_CONTROL, tmp); + } +} + +void intelfbhw_cursor_show(struct intelfb_info *dinfo) +{ + u32 tmp; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_cursor_show\n"); +#endif + + dinfo->cursor_on = 1; + + if (dinfo->cursor_blanked) + return; + + if (dinfo->mobile || IS_I9XX(dinfo)) { + if (!dinfo->cursor.physical) + return; + tmp = INREG(CURSOR_A_CONTROL); + tmp &= ~CURSOR_MODE_MASK; + tmp |= CURSOR_MODE_64_4C_AX; + OUTREG(CURSOR_A_CONTROL, tmp); + /* Flush changes */ + OUTREG(CURSOR_A_BASEADDR, dinfo->cursor.physical); + } else { + tmp = INREG(CURSOR_CONTROL); + tmp |= CURSOR_ENABLE; + OUTREG(CURSOR_CONTROL, tmp); + } +} + +void intelfbhw_cursor_setpos(struct intelfb_info *dinfo, int x, int y) +{ + u32 tmp; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_cursor_setpos: (%d, %d)\n", x, y); +#endif + + /* + * Sets the position. The coordinates are assumed to already + * have any offset adjusted. Assume that the cursor is never + * completely off-screen, and that x, y are always >= 0. + */ + + tmp = ((x & CURSOR_POS_MASK) << CURSOR_X_SHIFT) | + ((y & CURSOR_POS_MASK) << CURSOR_Y_SHIFT); + OUTREG(CURSOR_A_POSITION, tmp); + + if (IS_I9XX(dinfo)) + OUTREG(CURSOR_A_BASEADDR, dinfo->cursor.physical); +} + +void intelfbhw_cursor_setcolor(struct intelfb_info *dinfo, u32 bg, u32 fg) +{ +#if VERBOSE > 0 + DBG_MSG("intelfbhw_cursor_setcolor\n"); +#endif + + OUTREG(CURSOR_A_PALETTE0, bg & CURSOR_PALETTE_MASK); + OUTREG(CURSOR_A_PALETTE1, fg & CURSOR_PALETTE_MASK); + OUTREG(CURSOR_A_PALETTE2, fg & CURSOR_PALETTE_MASK); + OUTREG(CURSOR_A_PALETTE3, bg & CURSOR_PALETTE_MASK); +} + +void intelfbhw_cursor_load(struct intelfb_info *dinfo, int width, int height, + u8 *data) +{ + u8 __iomem *addr = (u8 __iomem *)dinfo->cursor.virtual; + int i, j, w = width / 8; + int mod = width % 8, t_mask, d_mask; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_cursor_load\n"); +#endif + + if (!dinfo->cursor.virtual) + return; + + t_mask = 0xff >> mod; + d_mask = ~(0xff >> mod); + for (i = height; i--; ) { + for (j = 0; j < w; j++) { + writeb(0x00, addr + j); + writeb(*(data++), addr + j+8); + } + if (mod) { + writeb(t_mask, addr + j); + writeb(*(data++) & d_mask, addr + j+8); + } + addr += 16; + } +} + +void intelfbhw_cursor_reset(struct intelfb_info *dinfo) +{ + u8 __iomem *addr = (u8 __iomem *)dinfo->cursor.virtual; + int i, j; + +#if VERBOSE > 0 + DBG_MSG("intelfbhw_cursor_reset\n"); +#endif + + if (!dinfo->cursor.virtual) + return; + + for (i = 64; i--; ) { + for (j = 0; j < 8; j++) { + writeb(0xff, addr + j+0); + writeb(0x00, addr + j+8); + } + addr += 16; + } +} + +static irqreturn_t intelfbhw_irq(int irq, void *dev_id) +{ + u16 tmp; + struct intelfb_info *dinfo = dev_id; + + spin_lock(&dinfo->int_lock); + + tmp = INREG16(IIR); + if (dinfo->info->var.vmode & FB_VMODE_INTERLACED) + tmp &= PIPE_A_EVENT_INTERRUPT; + else + tmp &= VSYNC_PIPE_A_INTERRUPT; /* non-interlaced */ + + if (tmp == 0) { + spin_unlock(&dinfo->int_lock); + return IRQ_RETVAL(0); /* not us */ + } + + /* clear status bits 0-15 ASAP and don't touch bits 16-31 */ + OUTREG(PIPEASTAT, INREG(PIPEASTAT)); + + OUTREG16(IIR, tmp); + if (dinfo->vsync.pan_display) { + dinfo->vsync.pan_display = 0; + OUTREG(DSPABASE, dinfo->vsync.pan_offset); + } + + dinfo->vsync.count++; + wake_up_interruptible(&dinfo->vsync.wait); + + spin_unlock(&dinfo->int_lock); + + return IRQ_RETVAL(1); +} + +int intelfbhw_enable_irq(struct intelfb_info *dinfo) +{ + u16 tmp; + if (!test_and_set_bit(0, &dinfo->irq_flags)) { + if (request_irq(dinfo->pdev->irq, intelfbhw_irq, IRQF_SHARED, + "intelfb", dinfo)) { + clear_bit(0, &dinfo->irq_flags); + return -EINVAL; + } + + spin_lock_irq(&dinfo->int_lock); + OUTREG16(HWSTAM, 0xfffe); /* i830 DRM uses ffff */ + OUTREG16(IMR, 0); + } else + spin_lock_irq(&dinfo->int_lock); + + if (dinfo->info->var.vmode & FB_VMODE_INTERLACED) + tmp = PIPE_A_EVENT_INTERRUPT; + else + tmp = VSYNC_PIPE_A_INTERRUPT; /* non-interlaced */ + if (tmp != INREG16(IER)) { + DBG_MSG("changing IER to 0x%X\n", tmp); + OUTREG16(IER, tmp); + } + + spin_unlock_irq(&dinfo->int_lock); + return 0; +} + +void intelfbhw_disable_irq(struct intelfb_info *dinfo) +{ + if (test_and_clear_bit(0, &dinfo->irq_flags)) { + if (dinfo->vsync.pan_display) { + dinfo->vsync.pan_display = 0; + OUTREG(DSPABASE, dinfo->vsync.pan_offset); + } + spin_lock_irq(&dinfo->int_lock); + OUTREG16(HWSTAM, 0xffff); + OUTREG16(IMR, 0xffff); + OUTREG16(IER, 0x0); + + OUTREG16(IIR, INREG16(IIR)); /* clear IRQ requests */ + spin_unlock_irq(&dinfo->int_lock); + + free_irq(dinfo->pdev->irq, dinfo); + } +} + +int intelfbhw_wait_for_vsync(struct intelfb_info *dinfo, u32 pipe) +{ + struct intelfb_vsync *vsync; + unsigned int count; + int ret; + + switch (pipe) { + case 0: + vsync = &dinfo->vsync; + break; + default: + return -ENODEV; + } + + ret = intelfbhw_enable_irq(dinfo); + if (ret) + return ret; + + count = vsync->count; + ret = wait_event_interruptible_timeout(vsync->wait, + count != vsync->count, HZ / 10); + if (ret < 0) + return ret; + if (ret == 0) { + DBG_MSG("wait_for_vsync timed out!\n"); + return -ETIMEDOUT; + } + + return 0; +} diff --git a/drivers/video/fbdev/intelfb/intelfbhw.h b/drivers/video/fbdev/intelfb/intelfbhw.h new file mode 100644 index 000000000000..216ca20f259f --- /dev/null +++ b/drivers/video/fbdev/intelfb/intelfbhw.h @@ -0,0 +1,609 @@ +#ifndef _INTELFBHW_H +#define _INTELFBHW_H + +/* $DHD: intelfb/intelfbhw.h,v 1.5 2003/06/27 15:06:25 dawes Exp $ */ + + +/*** HW-specific data ***/ + +/* Information about the 852GM/855GM variants */ +#define INTEL_85X_CAPID 0x44 +#define INTEL_85X_VARIANT_MASK 0x7 +#define INTEL_85X_VARIANT_SHIFT 5 +#define INTEL_VAR_855GME 0x0 +#define INTEL_VAR_855GM 0x4 +#define INTEL_VAR_852GME 0x2 +#define INTEL_VAR_852GM 0x5 + +/* Information about DVO/LVDS Ports */ +#define DVOA_PORT 0x1 +#define DVOB_PORT 0x2 +#define DVOC_PORT 0x4 +#define LVDS_PORT 0x8 + +/* + * The Bridge device's PCI config space has information about the + * fb aperture size and the amount of pre-reserved memory. + */ +#define INTEL_GMCH_CTRL 0x52 +#define INTEL_GMCH_ENABLED 0x4 +#define INTEL_GMCH_MEM_MASK 0x1 +#define INTEL_GMCH_MEM_64M 0x1 +#define INTEL_GMCH_MEM_128M 0 + +#define INTEL_830_GMCH_GMS_MASK (0x7 << 4) +#define INTEL_830_GMCH_GMS_DISABLED (0x0 << 4) +#define INTEL_830_GMCH_GMS_LOCAL (0x1 << 4) +#define INTEL_830_GMCH_GMS_STOLEN_512 (0x2 << 4) +#define INTEL_830_GMCH_GMS_STOLEN_1024 (0x3 << 4) +#define INTEL_830_GMCH_GMS_STOLEN_8192 (0x4 << 4) + +#define INTEL_855_GMCH_GMS_MASK (0x7 << 4) +#define INTEL_855_GMCH_GMS_DISABLED (0x0 << 4) +#define INTEL_855_GMCH_GMS_STOLEN_1M (0x1 << 4) +#define INTEL_855_GMCH_GMS_STOLEN_4M (0x2 << 4) +#define INTEL_855_GMCH_GMS_STOLEN_8M (0x3 << 4) +#define INTEL_855_GMCH_GMS_STOLEN_16M (0x4 << 4) +#define INTEL_855_GMCH_GMS_STOLEN_32M (0x5 << 4) + +#define INTEL_915G_GMCH_GMS_STOLEN_48M (0x6 << 4) +#define INTEL_915G_GMCH_GMS_STOLEN_64M (0x7 << 4) + +/* HW registers */ + +/* Fence registers */ +#define FENCE 0x2000 +#define FENCE_NUM 8 + +/* Primary ring buffer */ +#define PRI_RING_TAIL 0x2030 +#define RING_TAIL_MASK 0x001ffff8 +#define RING_INUSE 0x1 + +#define PRI_RING_HEAD 0x2034 +#define RING_HEAD_WRAP_MASK 0x7ff +#define RING_HEAD_WRAP_SHIFT 21 +#define RING_HEAD_MASK 0x001ffffc + +#define PRI_RING_START 0x2038 +#define RING_START_MASK 0xfffff000 + +#define PRI_RING_LENGTH 0x203c +#define RING_LENGTH_MASK 0x001ff000 +#define RING_REPORT_MASK (0x3 << 1) +#define RING_NO_REPORT (0x0 << 1) +#define RING_REPORT_64K (0x1 << 1) +#define RING_REPORT_4K (0x2 << 1) +#define RING_REPORT_128K (0x3 << 1) +#define RING_ENABLE 0x1 + +/* + * Tail can't wrap to any closer than RING_MIN_FREE bytes of the head, + * and the last RING_MIN_FREE bytes need to be padded with MI_NOOP + */ +#define RING_MIN_FREE 64 + +#define IPEHR 0x2088 + +#define INSTDONE 0x2090 +#define PRI_RING_EMPTY 1 + +#define HWSTAM 0x2098 +#define IER 0x20A0 +#define IIR 0x20A4 +#define IMR 0x20A8 +#define VSYNC_PIPE_A_INTERRUPT (1 << 7) +#define PIPE_A_EVENT_INTERRUPT (1 << 6) +#define VSYNC_PIPE_B_INTERRUPT (1 << 5) +#define PIPE_B_EVENT_INTERRUPT (1 << 4) +#define HOST_PORT_EVENT_INTERRUPT (1 << 3) +#define CAPTURE_EVENT_INTERRUPT (1 << 2) +#define USER_DEFINED_INTERRUPT (1 << 1) +#define BREAKPOINT_INTERRUPT 1 + +#define INSTPM 0x20c0 +#define SYNC_FLUSH_ENABLE (1 << 5) + +#define INSTPS 0x20c4 + +#define MEM_MODE 0x20cc + +#define MASK_SHIFT 16 + +#define FW_BLC_0 0x20d8 +#define FW_DISPA_WM_SHIFT 0 +#define FW_DISPA_WM_MASK 0x3f +#define FW_DISPA_BL_SHIFT 8 +#define FW_DISPA_BL_MASK 0xf +#define FW_DISPB_WM_SHIFT 16 +#define FW_DISPB_WM_MASK 0x1f +#define FW_DISPB_BL_SHIFT 24 +#define FW_DISPB_BL_MASK 0x7 + +#define FW_BLC_1 0x20dc +#define FW_DISPC_WM_SHIFT 0 +#define FW_DISPC_WM_MASK 0x1f +#define FW_DISPC_BL_SHIFT 8 +#define FW_DISPC_BL_MASK 0x7 + +#define GPIOA 0x5010 +#define GPIOB 0x5014 +#define GPIOC 0x5018 /* this may be external DDC on i830 */ +#define GPIOD 0x501C /* this is DVO DDC */ +#define GPIOE 0x5020 /* this is DVO i2C */ +#define GPIOF 0x5024 + +/* PLL registers */ +#define VGA0_DIVISOR 0x06000 +#define VGA1_DIVISOR 0x06004 +#define VGAPD 0x06010 +#define VGAPD_0_P1_SHIFT 0 +#define VGAPD_0_P1_FORCE_DIV2 (1 << 5) +#define VGAPD_0_P2_SHIFT 7 +#define VGAPD_1_P1_SHIFT 8 +#define VGAPD_1_P1_FORCE_DIV2 (1 << 13) +#define VGAPD_1_P2_SHIFT 15 + +#define DPLL_A 0x06014 +#define DPLL_B 0x06018 +#define DPLL_VCO_ENABLE (1 << 31) +#define DPLL_2X_CLOCK_ENABLE (1 << 30) +#define DPLL_SYNCLOCK_ENABLE (1 << 29) +#define DPLL_VGA_MODE_DISABLE (1 << 28) +#define DPLL_P2_MASK 1 +#define DPLL_P2_SHIFT 23 +#define DPLL_I9XX_P2_SHIFT 24 +#define DPLL_P1_FORCE_DIV2 (1 << 21) +#define DPLL_P1_MASK 0x1f +#define DPLL_P1_SHIFT 16 +#define DPLL_REFERENCE_SELECT_MASK (0x3 << 13) +#define DPLL_REFERENCE_DEFAULT (0x0 << 13) +#define DPLL_REFERENCE_TVCLK (0x2 << 13) +#define DPLL_RATE_SELECT_MASK (1 << 8) +#define DPLL_RATE_SELECT_FP0 (0 << 8) +#define DPLL_RATE_SELECT_FP1 (1 << 8) + +#define FPA0 0x06040 +#define FPA1 0x06044 +#define FPB0 0x06048 +#define FPB1 0x0604c +#define FP_DIVISOR_MASK 0x3f +#define FP_N_DIVISOR_SHIFT 16 +#define FP_M1_DIVISOR_SHIFT 8 +#define FP_M2_DIVISOR_SHIFT 0 + +/* PLL parameters (these are for 852GM/855GM/865G, check earlier chips). */ +/* Clock values are in units of kHz */ +#define PLL_REFCLK 48000 +#define MIN_CLOCK 25000 +#define MAX_CLOCK 350000 + +/* Two pipes */ +#define PIPE_A 0 +#define PIPE_B 1 +#define PIPE_MASK 1 + +/* palette registers */ +#define PALETTE_A 0x0a000 +#define PALETTE_B 0x0a800 +#ifndef PALETTE_8_ENTRIES +#define PALETTE_8_ENTRIES 256 +#endif +#define PALETTE_8_SIZE (PALETTE_8_ENTRIES * 4) +#define PALETTE_10_ENTRIES 128 +#define PALETTE_10_SIZE (PALETTE_10_ENTRIES * 8) +#define PALETTE_8_MASK 0xff +#define PALETTE_8_RED_SHIFT 16 +#define PALETTE_8_GREEN_SHIFT 8 +#define PALETTE_8_BLUE_SHIFT 0 + +/* CRTC registers */ +#define HTOTAL_A 0x60000 +#define HBLANK_A 0x60004 +#define HSYNC_A 0x60008 +#define VTOTAL_A 0x6000c +#define VBLANK_A 0x60010 +#define VSYNC_A 0x60014 +#define SRC_SIZE_A 0x6001c +#define BCLRPAT_A 0x60020 + +#define HTOTAL_B 0x61000 +#define HBLANK_B 0x61004 +#define HSYNC_B 0x61008 +#define VTOTAL_B 0x6100c +#define VBLANK_B 0x61010 +#define VSYNC_B 0x61014 +#define SRC_SIZE_B 0x6101c +#define BCLRPAT_B 0x61020 + +#define HTOTAL_MASK 0xfff +#define HTOTAL_SHIFT 16 +#define HACTIVE_MASK 0x7ff +#define HACTIVE_SHIFT 0 +#define HBLANKEND_MASK 0xfff +#define HBLANKEND_SHIFT 16 +#define HBLANKSTART_MASK 0xfff +#define HBLANKSTART_SHIFT 0 +#define HSYNCEND_MASK 0xfff +#define HSYNCEND_SHIFT 16 +#define HSYNCSTART_MASK 0xfff +#define HSYNCSTART_SHIFT 0 +#define VTOTAL_MASK 0xfff +#define VTOTAL_SHIFT 16 +#define VACTIVE_MASK 0x7ff +#define VACTIVE_SHIFT 0 +#define VBLANKEND_MASK 0xfff +#define VBLANKEND_SHIFT 16 +#define VBLANKSTART_MASK 0xfff +#define VBLANKSTART_SHIFT 0 +#define VSYNCEND_MASK 0xfff +#define VSYNCEND_SHIFT 16 +#define VSYNCSTART_MASK 0xfff +#define VSYNCSTART_SHIFT 0 +#define SRC_SIZE_HORIZ_MASK 0x7ff +#define SRC_SIZE_HORIZ_SHIFT 16 +#define SRC_SIZE_VERT_MASK 0x7ff +#define SRC_SIZE_VERT_SHIFT 0 + +#define ADPA 0x61100 +#define ADPA_DAC_ENABLE (1 << 31) +#define ADPA_DAC_DISABLE 0 +#define ADPA_PIPE_SELECT_SHIFT 30 +#define ADPA_USE_VGA_HVPOLARITY (1 << 15) +#define ADPA_SETS_HVPOLARITY 0 +#define ADPA_DPMS_CONTROL_MASK (0x3 << 10) +#define ADPA_DPMS_D0 (0x0 << 10) +#define ADPA_DPMS_D2 (0x1 << 10) +#define ADPA_DPMS_D1 (0x2 << 10) +#define ADPA_DPMS_D3 (0x3 << 10) +#define ADPA_VSYNC_ACTIVE_SHIFT 4 +#define ADPA_HSYNC_ACTIVE_SHIFT 3 +#define ADPA_SYNC_ACTIVE_MASK 1 +#define ADPA_SYNC_ACTIVE_HIGH 1 +#define ADPA_SYNC_ACTIVE_LOW 0 + +#define DVOA 0x61120 +#define DVOB 0x61140 +#define DVOC 0x61160 +#define LVDS 0x61180 +#define PORT_ENABLE (1 << 31) +#define PORT_PIPE_SELECT_SHIFT 30 +#define PORT_TV_FLAGS_MASK 0xFF +#define PORT_TV_FLAGS 0xC4 /* ripped from my BIOS + to understand and correct */ + +#define DVOA_SRCDIM 0x61124 +#define DVOB_SRCDIM 0x61144 +#define DVOC_SRCDIM 0x61164 + +#define PIPEA_DSL 0x70000 +#define PIPEB_DSL 0x71000 +#define PIPEACONF 0x70008 +#define PIPEBCONF 0x71008 +#define PIPEASTAT 0x70024 /* bits 0-15 are "write 1 to clear" */ +#define PIPEBSTAT 0x71024 + +#define PIPECONF_ENABLE (1 << 31) +#define PIPECONF_DISABLE 0 +#define PIPECONF_DOUBLE_WIDE (1 << 30) +#define PIPECONF_SINGLE_WIDE 0 +#define PIPECONF_LOCKED (1 << 25) +#define PIPECONF_UNLOCKED 0 +#define PIPECONF_GAMMA (1 << 24) +#define PIPECONF_PALETTE 0 +#define PIPECONF_PROGRESSIVE (0 << 21) +#define PIPECONF_INTERLACE_W_FIELD_INDICATION (6 << 21) +#define PIPECONF_INTERLACE_FIELD_0_ONLY (7 << 21) +#define PIPECONF_INTERLACE_MASK (7 << 21) + +/* enable bits, write 1 to enable */ +#define PIPESTAT_FIFO_UNDERRUN (1 << 31) +#define PIPESTAT_CRC_ERROR_EN (1 << 29) +#define PIPESTAT_CRC_DONE_EN (1 << 28) +#define PIPESTAT_HOTPLUG_EN (1 << 26) +#define PIPESTAT_VERTICAL_SYNC_EN (1 << 25) +#define PIPESTAT_DISPLINE_COMP_EN (1 << 24) +#define PIPESTAT_FLD_EVT_ODD_EN (1 << 21) +#define PIPESTAT_FLD_EVT_EVEN_EN (1 << 20) +#define PIPESTAT_TV_HOTPLUG_EN (1 << 18) +#define PIPESTAT_VBLANK_EN (1 << 17) +#define PIPESTAT_OVL_UPDATE_EN (1 << 16) +/* status bits, write 1 to clear */ +#define PIPESTAT_HOTPLUG_STATE (1 << 15) +#define PIPESTAT_CRC_ERROR (1 << 13) +#define PIPESTAT_CRC_DONE (1 << 12) +#define PIPESTAT_HOTPLUG (1 << 10) +#define PIPESTAT_VSYNC (1 << 9) +#define PIPESTAT_DISPLINE_COMP (1 << 8) +#define PIPESTAT_FLD_EVT_ODD (1 << 5) +#define PIPESTAT_FLD_EVT_EVEN (1 << 4) +#define PIPESTAT_TV_HOTPLUG (1 << 2) +#define PIPESTAT_VBLANK (1 << 1) +#define PIPESTAT_OVL_UPDATE (1 << 0) + +#define DISPARB 0x70030 +#define DISPARB_AEND_MASK 0x1ff +#define DISPARB_AEND_SHIFT 0 +#define DISPARB_BEND_MASK 0x3ff +#define DISPARB_BEND_SHIFT 9 + +/* Desktop HW cursor */ +#define CURSOR_CONTROL 0x70080 +#define CURSOR_ENABLE (1 << 31) +#define CURSOR_GAMMA_ENABLE (1 << 30) +#define CURSOR_STRIDE_MASK (0x3 << 28) +#define CURSOR_STRIDE_256 (0x0 << 28) +#define CURSOR_STRIDE_512 (0x1 << 28) +#define CURSOR_STRIDE_1K (0x2 << 28) +#define CURSOR_STRIDE_2K (0x3 << 28) +#define CURSOR_FORMAT_MASK (0x7 << 24) +#define CURSOR_FORMAT_2C (0x0 << 24) +#define CURSOR_FORMAT_3C (0x1 << 24) +#define CURSOR_FORMAT_4C (0x2 << 24) +#define CURSOR_FORMAT_ARGB (0x4 << 24) +#define CURSOR_FORMAT_XRGB (0x5 << 24) + +/* Mobile HW cursor (and i810) */ +#define CURSOR_A_CONTROL CURSOR_CONTROL +#define CURSOR_B_CONTROL 0x700c0 +#define CURSOR_MODE_MASK 0x27 +#define CURSOR_MODE_DISABLE 0 +#define CURSOR_MODE_64_3C 0x04 +#define CURSOR_MODE_64_4C_AX 0x05 +#define CURSOR_MODE_64_4C 0x06 +#define CURSOR_MODE_64_32B_AX 0x07 +#define CURSOR_MODE_64_ARGB_AX 0x27 +#define CURSOR_PIPE_SELECT_SHIFT 28 +#define CURSOR_MOBILE_GAMMA_ENABLE (1 << 26) +#define CURSOR_MEM_TYPE_LOCAL (1 << 25) + +/* All platforms (desktop has no pipe B) */ +#define CURSOR_A_BASEADDR 0x70084 +#define CURSOR_B_BASEADDR 0x700c4 +#define CURSOR_BASE_MASK 0xffffff00 + +#define CURSOR_A_POSITION 0x70088 +#define CURSOR_B_POSITION 0x700c8 +#define CURSOR_POS_SIGN (1 << 15) +#define CURSOR_POS_MASK 0x7ff +#define CURSOR_X_SHIFT 0 +#define CURSOR_Y_SHIFT 16 + +#define CURSOR_A_PALETTE0 0x70090 +#define CURSOR_A_PALETTE1 0x70094 +#define CURSOR_A_PALETTE2 0x70098 +#define CURSOR_A_PALETTE3 0x7009c +#define CURSOR_B_PALETTE0 0x700d0 +#define CURSOR_B_PALETTE1 0x700d4 +#define CURSOR_B_PALETTE2 0x700d8 +#define CURSOR_B_PALETTE3 0x700dc +#define CURSOR_COLOR_MASK 0xff +#define CURSOR_RED_SHIFT 16 +#define CURSOR_GREEN_SHIFT 8 +#define CURSOR_BLUE_SHIFT 0 +#define CURSOR_PALETTE_MASK 0xffffff + +/* Desktop only */ +#define CURSOR_SIZE 0x700a0 +#define CURSOR_SIZE_MASK 0x3ff +#define CURSOR_SIZE_H_SHIFT 0 +#define CURSOR_SIZE_V_SHIFT 12 + +#define DSPACNTR 0x70180 +#define DSPBCNTR 0x71180 +#define DISPPLANE_PLANE_ENABLE (1 << 31) +#define DISPPLANE_PLANE_DISABLE 0 +#define DISPPLANE_GAMMA_ENABLE (1<<30) +#define DISPPLANE_GAMMA_DISABLE 0 +#define DISPPLANE_PIXFORMAT_MASK (0xf<<26) +#define DISPPLANE_8BPP (0x2<<26) +#define DISPPLANE_15_16BPP (0x4<<26) +#define DISPPLANE_16BPP (0x5<<26) +#define DISPPLANE_32BPP_NO_ALPHA (0x6<<26) +#define DISPPLANE_32BPP (0x7<<26) +#define DISPPLANE_STEREO_ENABLE (1<<25) +#define DISPPLANE_STEREO_DISABLE 0 +#define DISPPLANE_SEL_PIPE_SHIFT 24 +#define DISPPLANE_SRC_KEY_ENABLE (1<<22) +#define DISPPLANE_SRC_KEY_DISABLE 0 +#define DISPPLANE_LINE_DOUBLE (1<<20) +#define DISPPLANE_NO_LINE_DOUBLE 0 +#define DISPPLANE_STEREO_POLARITY_FIRST 0 +#define DISPPLANE_STEREO_POLARITY_SECOND (1<<18) +/* plane B only */ +#define DISPPLANE_ALPHA_TRANS_ENABLE (1<<15) +#define DISPPLANE_ALPHA_TRANS_DISABLE 0 +#define DISPPLANE_SPRITE_ABOVE_DISPLAYA 0 +#define DISPPLANE_SPRITE_ABOVE_OVERLAY 1 + +#define DSPABASE 0x70184 +#define DSPASTRIDE 0x70188 + +#define DSPBBASE 0x71184 +#define DSPBSTRIDE 0x71188 + +#define VGACNTRL 0x71400 +#define VGA_DISABLE (1 << 31) +#define VGA_ENABLE 0 +#define VGA_PIPE_SELECT_SHIFT 29 +#define VGA_PALETTE_READ_SELECT 23 +#define VGA_PALETTE_A_WRITE_DISABLE (1 << 22) +#define VGA_PALETTE_B_WRITE_DISABLE (1 << 21) +#define VGA_LEGACY_PALETTE (1 << 20) +#define VGA_6BIT_DAC 0 +#define VGA_8BIT_DAC (1 << 20) + +#define ADD_ID 0x71408 +#define ADD_ID_MASK 0xff + +/* BIOS scratch area registers (830M and 845G). */ +#define SWF0 0x71410 +#define SWF1 0x71414 +#define SWF2 0x71418 +#define SWF3 0x7141c +#define SWF4 0x71420 +#define SWF5 0x71424 +#define SWF6 0x71428 + +/* BIOS scratch area registers (852GM, 855GM, 865G). */ +#define SWF00 0x70410 +#define SWF01 0x70414 +#define SWF02 0x70418 +#define SWF03 0x7041c +#define SWF04 0x70420 +#define SWF05 0x70424 +#define SWF06 0x70428 + +#define SWF10 SWF0 +#define SWF11 SWF1 +#define SWF12 SWF2 +#define SWF13 SWF3 +#define SWF14 SWF4 +#define SWF15 SWF5 +#define SWF16 SWF6 + +#define SWF30 0x72414 +#define SWF31 0x72418 +#define SWF32 0x7241c + +/* Memory Commands */ +#define MI_NOOP (0x00 << 23) +#define MI_NOOP_WRITE_ID (1 << 22) +#define MI_NOOP_ID_MASK ((1 << 22) - 1) + +#define MI_FLUSH (0x04 << 23) +#define MI_WRITE_DIRTY_STATE (1 << 4) +#define MI_END_SCENE (1 << 3) +#define MI_INHIBIT_RENDER_CACHE_FLUSH (1 << 2) +#define MI_INVALIDATE_MAP_CACHE (1 << 0) + +#define MI_STORE_DWORD_IMM ((0x20 << 23) | 1) + +/* 2D Commands */ +#define COLOR_BLT_CMD ((2 << 29) | (0x40 << 22) | 3) +#define XY_COLOR_BLT_CMD ((2 << 29) | (0x50 << 22) | 4) +#define XY_SETUP_CLIP_BLT_CMD ((2 << 29) | (0x03 << 22) | 1) +#define XY_SRC_COPY_BLT_CMD ((2 << 29) | (0x53 << 22) | 6) +#define SRC_COPY_BLT_CMD ((2 << 29) | (0x43 << 22) | 4) +#define XY_MONO_PAT_BLT_CMD ((2 << 29) | (0x52 << 22) | 7) +#define XY_MONO_SRC_BLT_CMD ((2 << 29) | (0x54 << 22) | 6) +#define XY_MONO_SRC_IMM_BLT_CMD ((2 << 29) | (0x71 << 22) | 5) +#define TXT_IMM_BLT_CMD ((2 << 29) | (0x30 << 22) | 2) +#define SETUP_BLT_CMD ((2 << 29) | (0x00 << 22) | 6) + +#define DW_LENGTH_MASK 0xff + +#define WRITE_ALPHA (1 << 21) +#define WRITE_RGB (1 << 20) +#define VERT_SEED (3 << 8) +#define HORIZ_SEED (3 << 12) + +#define COLOR_DEPTH_8 (0 << 24) +#define COLOR_DEPTH_16 (1 << 24) +#define COLOR_DEPTH_32 (3 << 24) + +#define SRC_ROP_GXCOPY 0xcc +#define SRC_ROP_GXXOR 0x66 + +#define PAT_ROP_GXCOPY 0xf0 +#define PAT_ROP_GXXOR 0x5a + +#define PITCH_SHIFT 0 +#define ROP_SHIFT 16 +#define WIDTH_SHIFT 0 +#define HEIGHT_SHIFT 16 + +/* in bytes */ +#define MAX_MONO_IMM_SIZE 128 + + +/*** Macros ***/ + +/* I/O macros */ +#define INREG8(addr) readb((u8 __iomem *)(dinfo->mmio_base + (addr))) +#define INREG16(addr) readw((u16 __iomem *)(dinfo->mmio_base + (addr))) +#define INREG(addr) readl((u32 __iomem *)(dinfo->mmio_base + (addr))) +#define OUTREG8(addr, val) writeb((val),(u8 __iomem *)(dinfo->mmio_base + \ + (addr))) +#define OUTREG16(addr, val) writew((val),(u16 __iomem *)(dinfo->mmio_base + \ + (addr))) +#define OUTREG(addr, val) writel((val),(u32 __iomem *)(dinfo->mmio_base + \ + (addr))) + +/* Ring buffer macros */ +#define OUT_RING(n) do { \ + writel((n), (u32 __iomem *)(dinfo->ring.virtual + dinfo->ring_tail));\ + dinfo->ring_tail += 4; \ + dinfo->ring_tail &= dinfo->ring_tail_mask; \ +} while (0) + +#define START_RING(n) do { \ + if (dinfo->ring_space < (n) * 4) \ + wait_ring(dinfo,(n) * 4); \ + dinfo->ring_space -= (n) * 4; \ +} while (0) + +#define ADVANCE_RING() do { \ + OUTREG(PRI_RING_TAIL, dinfo->ring_tail); \ +} while (0) + +#define DO_RING_IDLE() do { \ + u32 head, tail; \ + do { \ + head = INREG(PRI_RING_HEAD) & RING_HEAD_MASK; \ + tail = INREG(PRI_RING_TAIL) & RING_TAIL_MASK; \ + udelay(10); \ + } while (head != tail); \ +} while (0) + + +/* function protoypes */ +extern int intelfbhw_get_chipset(struct pci_dev *pdev, struct intelfb_info *dinfo); +extern int intelfbhw_get_memory(struct pci_dev *pdev, int *aperture_size, + int *stolen_size); +extern int intelfbhw_check_non_crt(struct intelfb_info *dinfo); +extern const char *intelfbhw_dvo_to_string(int dvo); +extern int intelfbhw_validate_mode(struct intelfb_info *dinfo, + struct fb_var_screeninfo *var); +extern int intelfbhw_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +extern void intelfbhw_do_blank(int blank, struct fb_info *info); +extern void intelfbhw_setcolreg(struct intelfb_info *dinfo, unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp); +extern int intelfbhw_read_hw_state(struct intelfb_info *dinfo, + struct intelfb_hwstate *hw, int flag); +extern void intelfbhw_print_hw_state(struct intelfb_info *dinfo, + struct intelfb_hwstate *hw); +extern int intelfbhw_mode_to_hw(struct intelfb_info *dinfo, + struct intelfb_hwstate *hw, + struct fb_var_screeninfo *var); +extern int intelfbhw_program_mode(struct intelfb_info *dinfo, + const struct intelfb_hwstate *hw, int blank); +extern void intelfbhw_do_sync(struct intelfb_info *dinfo); +extern void intelfbhw_2d_stop(struct intelfb_info *dinfo); +extern void intelfbhw_2d_start(struct intelfb_info *dinfo); +extern void intelfbhw_do_fillrect(struct intelfb_info *dinfo, u32 x, u32 y, + u32 w, u32 h, u32 color, u32 pitch, u32 bpp, + u32 rop); +extern void intelfbhw_do_bitblt(struct intelfb_info *dinfo, u32 curx, u32 cury, + u32 dstx, u32 dsty, u32 w, u32 h, u32 pitch, + u32 bpp); +extern int intelfbhw_do_drawglyph(struct intelfb_info *dinfo, u32 fg, u32 bg, + u32 w, u32 h, const u8* cdat, u32 x, u32 y, + u32 pitch, u32 bpp); +extern void intelfbhw_cursor_init(struct intelfb_info *dinfo); +extern void intelfbhw_cursor_hide(struct intelfb_info *dinfo); +extern void intelfbhw_cursor_show(struct intelfb_info *dinfo); +extern void intelfbhw_cursor_setpos(struct intelfb_info *dinfo, int x, int y); +extern void intelfbhw_cursor_setcolor(struct intelfb_info *dinfo, u32 bg, + u32 fg); +extern void intelfbhw_cursor_load(struct intelfb_info *dinfo, int width, + int height, u8 *data); +extern void intelfbhw_cursor_reset(struct intelfb_info *dinfo); +extern int intelfbhw_enable_irq(struct intelfb_info *dinfo); +extern void intelfbhw_disable_irq(struct intelfb_info *dinfo); +extern int intelfbhw_wait_for_vsync(struct intelfb_info *dinfo, u32 pipe); +extern int intelfbhw_active_pipe(const struct intelfb_hwstate *hw); + +#endif /* _INTELFBHW_H */ diff --git a/drivers/video/fbdev/jz4740_fb.c b/drivers/video/fbdev/jz4740_fb.c new file mode 100644 index 000000000000..87790e9644d0 --- /dev/null +++ b/drivers/video/fbdev/jz4740_fb.c @@ -0,0 +1,806 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SoC LCD framebuffer driver + * + * 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. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <linux/clk.h> +#include <linux/delay.h> + +#include <linux/console.h> +#include <linux/fb.h> + +#include <linux/dma-mapping.h> + +#include <asm/mach-jz4740/jz4740_fb.h> +#include <asm/mach-jz4740/gpio.h> + +#define JZ_REG_LCD_CFG 0x00 +#define JZ_REG_LCD_VSYNC 0x04 +#define JZ_REG_LCD_HSYNC 0x08 +#define JZ_REG_LCD_VAT 0x0C +#define JZ_REG_LCD_DAH 0x10 +#define JZ_REG_LCD_DAV 0x14 +#define JZ_REG_LCD_PS 0x18 +#define JZ_REG_LCD_CLS 0x1C +#define JZ_REG_LCD_SPL 0x20 +#define JZ_REG_LCD_REV 0x24 +#define JZ_REG_LCD_CTRL 0x30 +#define JZ_REG_LCD_STATE 0x34 +#define JZ_REG_LCD_IID 0x38 +#define JZ_REG_LCD_DA0 0x40 +#define JZ_REG_LCD_SA0 0x44 +#define JZ_REG_LCD_FID0 0x48 +#define JZ_REG_LCD_CMD0 0x4C +#define JZ_REG_LCD_DA1 0x50 +#define JZ_REG_LCD_SA1 0x54 +#define JZ_REG_LCD_FID1 0x58 +#define JZ_REG_LCD_CMD1 0x5C + +#define JZ_LCD_CFG_SLCD BIT(31) +#define JZ_LCD_CFG_PS_DISABLE BIT(23) +#define JZ_LCD_CFG_CLS_DISABLE BIT(22) +#define JZ_LCD_CFG_SPL_DISABLE BIT(21) +#define JZ_LCD_CFG_REV_DISABLE BIT(20) +#define JZ_LCD_CFG_HSYNCM BIT(19) +#define JZ_LCD_CFG_PCLKM BIT(18) +#define JZ_LCD_CFG_INV BIT(17) +#define JZ_LCD_CFG_SYNC_DIR BIT(16) +#define JZ_LCD_CFG_PS_POLARITY BIT(15) +#define JZ_LCD_CFG_CLS_POLARITY BIT(14) +#define JZ_LCD_CFG_SPL_POLARITY BIT(13) +#define JZ_LCD_CFG_REV_POLARITY BIT(12) +#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(11) +#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10) +#define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9) +#define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8) +#define JZ_LCD_CFG_18_BIT BIT(7) +#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4)) +#define JZ_LCD_CFG_MODE_MASK 0xf + +#define JZ_LCD_CTRL_BURST_4 (0x0 << 28) +#define JZ_LCD_CTRL_BURST_8 (0x1 << 28) +#define JZ_LCD_CTRL_BURST_16 (0x2 << 28) +#define JZ_LCD_CTRL_RGB555 BIT(27) +#define JZ_LCD_CTRL_OFUP BIT(26) +#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24) +#define JZ_LCD_CTRL_FRC_GRAYSCALE_4 (0x1 << 24) +#define JZ_LCD_CTRL_FRC_GRAYSCALE_2 (0x2 << 24) +#define JZ_LCD_CTRL_PDD_MASK (0xff << 16) +#define JZ_LCD_CTRL_EOF_IRQ BIT(13) +#define JZ_LCD_CTRL_SOF_IRQ BIT(12) +#define JZ_LCD_CTRL_OFU_IRQ BIT(11) +#define JZ_LCD_CTRL_IFU0_IRQ BIT(10) +#define JZ_LCD_CTRL_IFU1_IRQ BIT(9) +#define JZ_LCD_CTRL_DD_IRQ BIT(8) +#define JZ_LCD_CTRL_QDD_IRQ BIT(7) +#define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6) +#define JZ_LCD_CTRL_LSB_FISRT BIT(5) +#define JZ_LCD_CTRL_DISABLE BIT(4) +#define JZ_LCD_CTRL_ENABLE BIT(3) +#define JZ_LCD_CTRL_BPP_1 0x0 +#define JZ_LCD_CTRL_BPP_2 0x1 +#define JZ_LCD_CTRL_BPP_4 0x2 +#define JZ_LCD_CTRL_BPP_8 0x3 +#define JZ_LCD_CTRL_BPP_15_16 0x4 +#define JZ_LCD_CTRL_BPP_18_24 0x5 + +#define JZ_LCD_CMD_SOF_IRQ BIT(31) +#define JZ_LCD_CMD_EOF_IRQ BIT(30) +#define JZ_LCD_CMD_ENABLE_PAL BIT(28) + +#define JZ_LCD_SYNC_MASK 0x3ff + +#define JZ_LCD_STATE_DISABLED BIT(0) + +struct jzfb_framedesc { + uint32_t next; + uint32_t addr; + uint32_t id; + uint32_t cmd; +} __packed; + +struct jzfb { + struct fb_info *fb; + struct platform_device *pdev; + void __iomem *base; + struct resource *mem; + struct jz4740_fb_platform_data *pdata; + + size_t vidmem_size; + void *vidmem; + dma_addr_t vidmem_phys; + struct jzfb_framedesc *framedesc; + dma_addr_t framedesc_phys; + + struct clk *ldclk; + struct clk *lpclk; + + unsigned is_enabled:1; + struct mutex lock; + + uint32_t pseudo_palette[16]; +}; + +static const struct fb_fix_screeninfo jzfb_fix = { + .id = "JZ4740 FB", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static const struct jz_gpio_bulk_request jz_lcd_ctrl_pins[] = { + JZ_GPIO_BULK_PIN(LCD_PCLK), + JZ_GPIO_BULK_PIN(LCD_HSYNC), + JZ_GPIO_BULK_PIN(LCD_VSYNC), + JZ_GPIO_BULK_PIN(LCD_DE), + JZ_GPIO_BULK_PIN(LCD_PS), + JZ_GPIO_BULK_PIN(LCD_REV), + JZ_GPIO_BULK_PIN(LCD_CLS), + JZ_GPIO_BULK_PIN(LCD_SPL), +}; + +static const struct jz_gpio_bulk_request jz_lcd_data_pins[] = { + JZ_GPIO_BULK_PIN(LCD_DATA0), + JZ_GPIO_BULK_PIN(LCD_DATA1), + JZ_GPIO_BULK_PIN(LCD_DATA2), + JZ_GPIO_BULK_PIN(LCD_DATA3), + JZ_GPIO_BULK_PIN(LCD_DATA4), + JZ_GPIO_BULK_PIN(LCD_DATA5), + JZ_GPIO_BULK_PIN(LCD_DATA6), + JZ_GPIO_BULK_PIN(LCD_DATA7), + JZ_GPIO_BULK_PIN(LCD_DATA8), + JZ_GPIO_BULK_PIN(LCD_DATA9), + JZ_GPIO_BULK_PIN(LCD_DATA10), + JZ_GPIO_BULK_PIN(LCD_DATA11), + JZ_GPIO_BULK_PIN(LCD_DATA12), + JZ_GPIO_BULK_PIN(LCD_DATA13), + JZ_GPIO_BULK_PIN(LCD_DATA14), + JZ_GPIO_BULK_PIN(LCD_DATA15), + JZ_GPIO_BULK_PIN(LCD_DATA16), + JZ_GPIO_BULK_PIN(LCD_DATA17), +}; + +static unsigned int jzfb_num_ctrl_pins(struct jzfb *jzfb) +{ + unsigned int num; + + switch (jzfb->pdata->lcd_type) { + case JZ_LCD_TYPE_GENERIC_16_BIT: + num = 4; + break; + case JZ_LCD_TYPE_GENERIC_18_BIT: + num = 4; + break; + case JZ_LCD_TYPE_8BIT_SERIAL: + num = 3; + break; + case JZ_LCD_TYPE_SPECIAL_TFT_1: + case JZ_LCD_TYPE_SPECIAL_TFT_2: + case JZ_LCD_TYPE_SPECIAL_TFT_3: + num = 8; + break; + default: + num = 0; + break; + } + return num; +} + +static unsigned int jzfb_num_data_pins(struct jzfb *jzfb) +{ + unsigned int num; + + switch (jzfb->pdata->lcd_type) { + case JZ_LCD_TYPE_GENERIC_16_BIT: + num = 16; + break; + case JZ_LCD_TYPE_GENERIC_18_BIT: + num = 18; + break; + case JZ_LCD_TYPE_8BIT_SERIAL: + num = 8; + break; + case JZ_LCD_TYPE_SPECIAL_TFT_1: + case JZ_LCD_TYPE_SPECIAL_TFT_2: + case JZ_LCD_TYPE_SPECIAL_TFT_3: + if (jzfb->pdata->bpp == 18) + num = 18; + else + num = 16; + break; + default: + num = 0; + break; + } + return num; +} + +/* Based on CNVT_TOHW macro from skeletonfb.c */ +static inline uint32_t jzfb_convert_color_to_hw(unsigned val, + struct fb_bitfield *bf) +{ + return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset; +} + +static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *fb) +{ + uint32_t color; + + if (regno >= 16) + return -EINVAL; + + color = jzfb_convert_color_to_hw(red, &fb->var.red); + color |= jzfb_convert_color_to_hw(green, &fb->var.green); + color |= jzfb_convert_color_to_hw(blue, &fb->var.blue); + color |= jzfb_convert_color_to_hw(transp, &fb->var.transp); + + ((uint32_t *)(fb->pseudo_palette))[regno] = color; + + return 0; +} + +static int jzfb_get_controller_bpp(struct jzfb *jzfb) +{ + switch (jzfb->pdata->bpp) { + case 18: + case 24: + return 32; + case 15: + return 16; + default: + return jzfb->pdata->bpp; + } +} + +static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb, + struct fb_var_screeninfo *var) +{ + size_t i; + struct fb_videomode *mode = jzfb->pdata->modes; + + for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) { + if (mode->xres == var->xres && mode->yres == var->yres) + return mode; + } + + return NULL; +} + +static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb) +{ + struct jzfb *jzfb = fb->par; + struct fb_videomode *mode; + + if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) && + var->bits_per_pixel != jzfb->pdata->bpp) + return -EINVAL; + + mode = jzfb_get_mode(jzfb, var); + if (mode == NULL) + return -EINVAL; + + fb_videomode_to_var(var, mode); + + switch (jzfb->pdata->bpp) { + case 8: + break; + case 15: + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 6; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + break; + case 16: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + case 18: + var->red.offset = 16; + var->red.length = 6; + var->green.offset = 8; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 6; + var->bits_per_pixel = 32; + break; + case 32: + case 24: + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->bits_per_pixel = 32; + break; + default: + break; + } + + return 0; +} + +static int jzfb_set_par(struct fb_info *info) +{ + struct jzfb *jzfb = info->par; + struct jz4740_fb_platform_data *pdata = jzfb->pdata; + struct fb_var_screeninfo *var = &info->var; + struct fb_videomode *mode; + uint16_t hds, vds; + uint16_t hde, vde; + uint16_t ht, vt; + uint32_t ctrl; + uint32_t cfg; + unsigned long rate; + + mode = jzfb_get_mode(jzfb, var); + if (mode == NULL) + return -EINVAL; + + if (mode == info->mode) + return 0; + + info->mode = mode; + + hds = mode->hsync_len + mode->left_margin; + hde = hds + mode->xres; + ht = hde + mode->right_margin; + + vds = mode->vsync_len + mode->upper_margin; + vde = vds + mode->yres; + vt = vde + mode->lower_margin; + + ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16; + + switch (pdata->bpp) { + case 1: + ctrl |= JZ_LCD_CTRL_BPP_1; + break; + case 2: + ctrl |= JZ_LCD_CTRL_BPP_2; + break; + case 4: + ctrl |= JZ_LCD_CTRL_BPP_4; + break; + case 8: + ctrl |= JZ_LCD_CTRL_BPP_8; + break; + case 15: + ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */ + case 16: + ctrl |= JZ_LCD_CTRL_BPP_15_16; + break; + case 18: + case 24: + case 32: + ctrl |= JZ_LCD_CTRL_BPP_18_24; + break; + default: + break; + } + + cfg = pdata->lcd_type & 0xf; + + if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) + cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW; + + if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) + cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW; + + if (pdata->pixclk_falling_edge) + cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE; + + if (pdata->date_enable_active_low) + cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW; + + if (pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT) + cfg |= JZ_LCD_CFG_18_BIT; + + if (mode->pixclock) { + rate = PICOS2KHZ(mode->pixclock) * 1000; + mode->refresh = rate / vt / ht; + } else { + if (pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL) + rate = mode->refresh * (vt + 2 * mode->xres) * ht; + else + rate = mode->refresh * vt * ht; + + mode->pixclock = KHZ2PICOS(rate / 1000); + } + + mutex_lock(&jzfb->lock); + if (!jzfb->is_enabled) + clk_enable(jzfb->ldclk); + else + ctrl |= JZ_LCD_CTRL_ENABLE; + + switch (pdata->lcd_type) { + case JZ_LCD_TYPE_SPECIAL_TFT_1: + case JZ_LCD_TYPE_SPECIAL_TFT_2: + case JZ_LCD_TYPE_SPECIAL_TFT_3: + writel(pdata->special_tft_config.spl, jzfb->base + JZ_REG_LCD_SPL); + writel(pdata->special_tft_config.cls, jzfb->base + JZ_REG_LCD_CLS); + writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_PS); + writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_REV); + break; + default: + cfg |= JZ_LCD_CFG_PS_DISABLE; + cfg |= JZ_LCD_CFG_CLS_DISABLE; + cfg |= JZ_LCD_CFG_SPL_DISABLE; + cfg |= JZ_LCD_CFG_REV_DISABLE; + break; + } + + writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC); + writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC); + + writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT); + + writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH); + writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV); + + writel(cfg, jzfb->base + JZ_REG_LCD_CFG); + + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); + + if (!jzfb->is_enabled) + clk_disable_unprepare(jzfb->ldclk); + + mutex_unlock(&jzfb->lock); + + clk_set_rate(jzfb->lpclk, rate); + clk_set_rate(jzfb->ldclk, rate * 3); + + return 0; +} + +static void jzfb_enable(struct jzfb *jzfb) +{ + uint32_t ctrl; + + clk_prepare_enable(jzfb->ldclk); + + jz_gpio_bulk_resume(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); + jz_gpio_bulk_resume(jz_lcd_data_pins, jzfb_num_data_pins(jzfb)); + + writel(0, jzfb->base + JZ_REG_LCD_STATE); + + writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0); + + ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); + ctrl |= JZ_LCD_CTRL_ENABLE; + ctrl &= ~JZ_LCD_CTRL_DISABLE; + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); +} + +static void jzfb_disable(struct jzfb *jzfb) +{ + uint32_t ctrl; + + ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); + ctrl |= JZ_LCD_CTRL_DISABLE; + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); + do { + ctrl = readl(jzfb->base + JZ_REG_LCD_STATE); + } while (!(ctrl & JZ_LCD_STATE_DISABLED)); + + jz_gpio_bulk_suspend(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); + jz_gpio_bulk_suspend(jz_lcd_data_pins, jzfb_num_data_pins(jzfb)); + + clk_disable_unprepare(jzfb->ldclk); +} + +static int jzfb_blank(int blank_mode, struct fb_info *info) +{ + struct jzfb *jzfb = info->par; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + mutex_lock(&jzfb->lock); + if (jzfb->is_enabled) { + mutex_unlock(&jzfb->lock); + return 0; + } + + jzfb_enable(jzfb); + jzfb->is_enabled = 1; + + mutex_unlock(&jzfb->lock); + break; + default: + mutex_lock(&jzfb->lock); + if (!jzfb->is_enabled) { + mutex_unlock(&jzfb->lock); + return 0; + } + + jzfb_disable(jzfb); + jzfb->is_enabled = 0; + + mutex_unlock(&jzfb->lock); + break; + } + + return 0; +} + +static int jzfb_alloc_devmem(struct jzfb *jzfb) +{ + int max_videosize = 0; + struct fb_videomode *mode = jzfb->pdata->modes; + void *page; + int i; + + for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) { + if (max_videosize < mode->xres * mode->yres) + max_videosize = mode->xres * mode->yres; + } + + max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3; + + jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev, + sizeof(*jzfb->framedesc), + &jzfb->framedesc_phys, GFP_KERNEL); + + if (!jzfb->framedesc) + return -ENOMEM; + + jzfb->vidmem_size = PAGE_ALIGN(max_videosize); + jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev, + jzfb->vidmem_size, + &jzfb->vidmem_phys, GFP_KERNEL); + + if (!jzfb->vidmem) + goto err_free_framedesc; + + for (page = jzfb->vidmem; + page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size); + page += PAGE_SIZE) { + SetPageReserved(virt_to_page(page)); + } + + jzfb->framedesc->next = jzfb->framedesc_phys; + jzfb->framedesc->addr = jzfb->vidmem_phys; + jzfb->framedesc->id = 0xdeafbead; + jzfb->framedesc->cmd = 0; + jzfb->framedesc->cmd |= max_videosize / 4; + + return 0; + +err_free_framedesc: + dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), + jzfb->framedesc, jzfb->framedesc_phys); + return -ENOMEM; +} + +static void jzfb_free_devmem(struct jzfb *jzfb) +{ + dma_free_coherent(&jzfb->pdev->dev, jzfb->vidmem_size, + jzfb->vidmem, jzfb->vidmem_phys); + dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), + jzfb->framedesc, jzfb->framedesc_phys); +} + +static struct fb_ops jzfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = jzfb_check_var, + .fb_set_par = jzfb_set_par, + .fb_blank = jzfb_blank, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_setcolreg = jzfb_setcolreg, +}; + +static int jzfb_probe(struct platform_device *pdev) +{ + int ret; + struct jzfb *jzfb; + struct fb_info *fb; + struct jz4740_fb_platform_data *pdata = pdev->dev.platform_data; + struct resource *mem; + + if (!pdata) { + dev_err(&pdev->dev, "Missing platform data\n"); + return -ENXIO; + } + + fb = framebuffer_alloc(sizeof(struct jzfb), &pdev->dev); + if (!fb) { + dev_err(&pdev->dev, "Failed to allocate framebuffer device\n"); + return -ENOMEM; + } + + fb->fbops = &jzfb_ops; + fb->flags = FBINFO_DEFAULT; + + jzfb = fb->par; + jzfb->pdev = pdev; + jzfb->pdata = pdata; + + jzfb->ldclk = devm_clk_get(&pdev->dev, "lcd"); + if (IS_ERR(jzfb->ldclk)) { + ret = PTR_ERR(jzfb->ldclk); + dev_err(&pdev->dev, "Failed to get lcd clock: %d\n", ret); + goto err_framebuffer_release; + } + + jzfb->lpclk = devm_clk_get(&pdev->dev, "lcd_pclk"); + if (IS_ERR(jzfb->lpclk)) { + ret = PTR_ERR(jzfb->lpclk); + dev_err(&pdev->dev, "Failed to get lcd pixel clock: %d\n", ret); + goto err_framebuffer_release; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + jzfb->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(jzfb->base)) { + ret = PTR_ERR(jzfb->base); + goto err_framebuffer_release; + } + + platform_set_drvdata(pdev, jzfb); + + mutex_init(&jzfb->lock); + + fb_videomode_to_modelist(pdata->modes, pdata->num_modes, + &fb->modelist); + fb_videomode_to_var(&fb->var, pdata->modes); + fb->var.bits_per_pixel = pdata->bpp; + jzfb_check_var(&fb->var, fb); + + ret = jzfb_alloc_devmem(jzfb); + if (ret) { + dev_err(&pdev->dev, "Failed to allocate video memory\n"); + goto err_framebuffer_release; + } + + fb->fix = jzfb_fix; + fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8; + fb->fix.mmio_start = mem->start; + fb->fix.mmio_len = resource_size(mem); + fb->fix.smem_start = jzfb->vidmem_phys; + fb->fix.smem_len = fb->fix.line_length * fb->var.yres; + fb->screen_base = jzfb->vidmem; + fb->pseudo_palette = jzfb->pseudo_palette; + + fb_alloc_cmap(&fb->cmap, 256, 0); + + clk_prepare_enable(jzfb->ldclk); + jzfb->is_enabled = 1; + + writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0); + + fb->mode = NULL; + jzfb_set_par(fb); + + jz_gpio_bulk_request(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); + jz_gpio_bulk_request(jz_lcd_data_pins, jzfb_num_data_pins(jzfb)); + + ret = register_framebuffer(fb); + if (ret) { + dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret); + goto err_free_devmem; + } + + jzfb->fb = fb; + + return 0; + +err_free_devmem: + jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); + jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb)); + + fb_dealloc_cmap(&fb->cmap); + jzfb_free_devmem(jzfb); +err_framebuffer_release: + framebuffer_release(fb); + return ret; +} + +static int jzfb_remove(struct platform_device *pdev) +{ + struct jzfb *jzfb = platform_get_drvdata(pdev); + + jzfb_blank(FB_BLANK_POWERDOWN, jzfb->fb); + + jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); + jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb)); + + fb_dealloc_cmap(&jzfb->fb->cmap); + jzfb_free_devmem(jzfb); + + framebuffer_release(jzfb->fb); + + return 0; +} + +#ifdef CONFIG_PM + +static int jzfb_suspend(struct device *dev) +{ + struct jzfb *jzfb = dev_get_drvdata(dev); + + console_lock(); + fb_set_suspend(jzfb->fb, 1); + console_unlock(); + + mutex_lock(&jzfb->lock); + if (jzfb->is_enabled) + jzfb_disable(jzfb); + mutex_unlock(&jzfb->lock); + + return 0; +} + +static int jzfb_resume(struct device *dev) +{ + struct jzfb *jzfb = dev_get_drvdata(dev); + clk_prepare_enable(jzfb->ldclk); + + mutex_lock(&jzfb->lock); + if (jzfb->is_enabled) + jzfb_enable(jzfb); + mutex_unlock(&jzfb->lock); + + console_lock(); + fb_set_suspend(jzfb->fb, 0); + console_unlock(); + + return 0; +} + +static const struct dev_pm_ops jzfb_pm_ops = { + .suspend = jzfb_suspend, + .resume = jzfb_resume, + .poweroff = jzfb_suspend, + .restore = jzfb_resume, +}; + +#define JZFB_PM_OPS (&jzfb_pm_ops) + +#else +#define JZFB_PM_OPS NULL +#endif + +static struct platform_driver jzfb_driver = { + .probe = jzfb_probe, + .remove = jzfb_remove, + .driver = { + .name = "jz4740-fb", + .pm = JZFB_PM_OPS, + }, +}; +module_platform_driver(jzfb_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver"); +MODULE_ALIAS("platform:jz4740-fb"); diff --git a/drivers/video/fbdev/kyro/Makefile b/drivers/video/fbdev/kyro/Makefile new file mode 100644 index 000000000000..2fd66f551bae --- /dev/null +++ b/drivers/video/fbdev/kyro/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the Kyro framebuffer driver +# + +obj-$(CONFIG_FB_KYRO) += kyrofb.o + +kyrofb-objs := STG4000Ramdac.o STG4000VTG.o STG4000OverlayDevice.o \ + STG4000InitDevice.o fbdev.o diff --git a/drivers/video/fbdev/kyro/STG4000InitDevice.c b/drivers/video/fbdev/kyro/STG4000InitDevice.c new file mode 100644 index 000000000000..1d3f2080aa6f --- /dev/null +++ b/drivers/video/fbdev/kyro/STG4000InitDevice.c @@ -0,0 +1,326 @@ +/* + * linux/drivers/video/kyro/STG4000InitDevice.c + * + * Copyright (C) 2000 Imagination Technologies Ltd + * Copyright (C) 2002 STMicroelectronics + * + * 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/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> + +#include "STG4000Reg.h" +#include "STG4000Interface.h" + +/* SDRAM fixed settings */ +#define SDRAM_CFG_0 0x49A1 +#define SDRAM_CFG_1 0xA732 +#define SDRAM_CFG_2 0x31 +#define SDRAM_ARB_CFG 0xA0 +#define SDRAM_REFRESH 0x20 + +/* Reset values */ +#define PMX2_SOFTRESET_DAC_RST 0x0001 +#define PMX2_SOFTRESET_C1_RST 0x0004 +#define PMX2_SOFTRESET_C2_RST 0x0008 +#define PMX2_SOFTRESET_3D_RST 0x0010 +#define PMX2_SOFTRESET_VIDIN_RST 0x0020 +#define PMX2_SOFTRESET_TLB_RST 0x0040 +#define PMX2_SOFTRESET_SD_RST 0x0080 +#define PMX2_SOFTRESET_VGA_RST 0x0100 +#define PMX2_SOFTRESET_ROM_RST 0x0200 /* reserved bit, do not reset */ +#define PMX2_SOFTRESET_TA_RST 0x0400 +#define PMX2_SOFTRESET_REG_RST 0x4000 +#define PMX2_SOFTRESET_ALL 0x7fff + +/* Core clock freq */ +#define CORE_PLL_FREQ 1000000 + +/* Reference Clock freq */ +#define REF_FREQ 14318 + +/* PCI Registers */ +static u16 CorePllControl = 0x70; + +#define PCI_CONFIG_SUBSYS_ID 0x2e + +/* Misc */ +#define CORE_PLL_MODE_REG_0_7 3 +#define CORE_PLL_MODE_REG_8_15 2 +#define CORE_PLL_MODE_CONFIG_REG 1 +#define DAC_PLL_CONFIG_REG 0 + +#define STG_MAX_VCO 500000 +#define STG_MIN_VCO 100000 + +/* PLL Clock */ +#define STG4K3_PLL_SCALER 8 /* scale numbers by 2^8 for fixed point calc */ +#define STG4K3_PLL_MIN_R 2 /* Minimum multiplier */ +#define STG4K3_PLL_MAX_R 33 /* Max */ +#define STG4K3_PLL_MIN_F 2 /* Minimum divisor */ +#define STG4K3_PLL_MAX_F 513 /* Max */ +#define STG4K3_PLL_MIN_OD 0 /* Min output divider (shift) */ +#define STG4K3_PLL_MAX_OD 2 /* Max */ +#define STG4K3_PLL_MIN_VCO_SC (100000000 >> STG4K3_PLL_SCALER) /* Min VCO rate */ +#define STG4K3_PLL_MAX_VCO_SC (500000000 >> STG4K3_PLL_SCALER) /* Max VCO rate */ +#define STG4K3_PLL_MINR_VCO_SC (100000000 >> STG4K3_PLL_SCALER) /* Min VCO rate (restricted) */ +#define STG4K3_PLL_MAXR_VCO_SC (500000000 >> STG4K3_PLL_SCALER) /* Max VCO rate (restricted) */ +#define STG4K3_PLL_MINR_VCO 100000000 /* Min VCO rate (restricted) */ +#define STG4K3_PLL_MAX_VCO 500000000 /* Max VCO rate */ +#define STG4K3_PLL_MAXR_VCO 500000000 /* Max VCO rate (restricted) */ + +#define OS_DELAY(X) \ +{ \ +volatile u32 i,count=0; \ + for(i=0;i<X;i++) count++; \ +} + +static u32 InitSDRAMRegisters(volatile STG4000REG __iomem *pSTGReg, + u32 dwSubSysID, u32 dwRevID) +{ + u32 adwSDRAMArgCfg0[] = { 0xa0, 0x80, 0xa0, 0xa0, 0xa0 }; + u32 adwSDRAMCfg1[] = { 0x8732, 0x8732, 0xa732, 0xa732, 0x8732 }; + u32 adwSDRAMCfg2[] = { 0x87d2, 0x87d2, 0xa7d2, 0x87d2, 0xa7d2 }; + u32 adwSDRAMRsh[] = { 36, 39, 40 }; + u32 adwChipSpeed[] = { 110, 120, 125 }; + u32 dwMemTypeIdx; + u32 dwChipSpeedIdx; + + /* Get memory tpye and chip speed indexs from the SubSysDevID */ + dwMemTypeIdx = (dwSubSysID & 0x70) >> 4; + dwChipSpeedIdx = (dwSubSysID & 0x180) >> 7; + + if (dwMemTypeIdx > 4 || dwChipSpeedIdx > 2) + return 0; + + /* Program SD-RAM interface */ + STG_WRITE_REG(SDRAMArbiterConf, adwSDRAMArgCfg0[dwMemTypeIdx]); + if (dwRevID < 5) { + STG_WRITE_REG(SDRAMConf0, 0x49A1); + STG_WRITE_REG(SDRAMConf1, adwSDRAMCfg1[dwMemTypeIdx]); + } else { + STG_WRITE_REG(SDRAMConf0, 0x4DF1); + STG_WRITE_REG(SDRAMConf1, adwSDRAMCfg2[dwMemTypeIdx]); + } + + STG_WRITE_REG(SDRAMConf2, 0x31); + STG_WRITE_REG(SDRAMRefresh, adwSDRAMRsh[dwChipSpeedIdx]); + + return adwChipSpeed[dwChipSpeedIdx] * 10000; +} + +u32 ProgramClock(u32 refClock, + u32 coreClock, + u32 * FOut, u32 * ROut, u32 * POut) +{ + u32 R = 0, F = 0, OD = 0, ODIndex = 0; + u32 ulBestR = 0, ulBestF = 0, ulBestOD = 0; + u32 ulBestVCO = 0, ulBestClk = 0, ulBestScore = 0; + u32 ulScore, ulPhaseScore, ulVcoScore; + u32 ulTmp = 0, ulVCO; + u32 ulScaleClockReq, ulMinClock, ulMaxClock; + u32 ODValues[] = { 1, 2, 0 }; + + /* Translate clock in Hz */ + coreClock *= 100; /* in Hz */ + refClock *= 1000; /* in Hz */ + + /* Work out acceptable clock + * The method calculates ~ +- 0.4% (1/256) + */ + ulMinClock = coreClock - (coreClock >> 8); + ulMaxClock = coreClock + (coreClock >> 8); + + /* Scale clock required for use in calculations */ + ulScaleClockReq = coreClock >> STG4K3_PLL_SCALER; + + /* Iterate through post divider values */ + for (ODIndex = 0; ODIndex < 3; ODIndex++) { + OD = ODValues[ODIndex]; + R = STG4K3_PLL_MIN_R; + + /* loop for pre-divider from min to max */ + while (R <= STG4K3_PLL_MAX_R) { + /* estimate required feedback multiplier */ + ulTmp = R * (ulScaleClockReq << OD); + + /* F = ClkRequired * R * (2^OD) / Fref */ + F = (u32)(ulTmp / (refClock >> STG4K3_PLL_SCALER)); + + /* compensate for accuracy */ + if (F > STG4K3_PLL_MIN_F) + F--; + + + /* + * We should be close to our target frequency (if it's + * achievable with current OD & R) let's iterate + * through F for best fit + */ + while ((F >= STG4K3_PLL_MIN_F) && + (F <= STG4K3_PLL_MAX_F)) { + /* Calc VCO at full accuracy */ + ulVCO = refClock / R; + ulVCO = F * ulVCO; + + /* + * Check it's within restricted VCO range + * unless of course the desired frequency is + * above the restricted range, then test + * against VCO limit + */ + if ((ulVCO >= STG4K3_PLL_MINR_VCO) && + ((ulVCO <= STG4K3_PLL_MAXR_VCO) || + ((coreClock > STG4K3_PLL_MAXR_VCO) + && (ulVCO <= STG4K3_PLL_MAX_VCO)))) { + ulTmp = (ulVCO >> OD); /* Clock = VCO / (2^OD) */ + + /* Is this clock good enough? */ + if ((ulTmp >= ulMinClock) + && (ulTmp <= ulMaxClock)) { + ulPhaseScore = (((refClock / R) - (refClock / STG4K3_PLL_MAX_R))) / ((refClock - (refClock / STG4K3_PLL_MAX_R)) >> 10); + + ulVcoScore = ((ulVCO - STG4K3_PLL_MINR_VCO)) / ((STG4K3_PLL_MAXR_VCO - STG4K3_PLL_MINR_VCO) >> 10); + ulScore = ulPhaseScore + ulVcoScore; + + if (!ulBestScore) { + ulBestVCO = ulVCO; + ulBestOD = OD; + ulBestF = F; + ulBestR = R; + ulBestClk = ulTmp; + ulBestScore = + ulScore; + } + /* is this better, ( aim for highest Score) */ + /*-------------------------------------------------------------------------- + Here we want to use a scoring system which will take account of both the + value at the phase comparater and the VCO output + to do this we will use a cumulative score between the two + The way this ends up is that we choose the first value in the loop anyway + but we shall keep this code in case new restrictions come into play + --------------------------------------------------------------------------*/ + if ((ulScore >= ulBestScore) && (OD > 0)) { + ulBestVCO = ulVCO; + ulBestOD = OD; + ulBestF = F; + ulBestR = R; + ulBestClk = ulTmp; + ulBestScore = + ulScore; + } + } + } + F++; + } + R++; + } + } + + /* + did we find anything? + Then return RFOD + */ + if (ulBestScore) { + *ROut = ulBestR; + *FOut = ulBestF; + + if ((ulBestOD == 2) || (ulBestOD == 3)) { + *POut = 3; + } else + *POut = ulBestOD; + + } + + return (ulBestClk); +} + +int SetCoreClockPLL(volatile STG4000REG __iomem *pSTGReg, struct pci_dev *pDev) +{ + u32 F, R, P; + u16 core_pll = 0, sub; + u32 ulCoreClock; + u32 tmp; + u32 ulChipSpeed; + + STG_WRITE_REG(IntMask, 0xFFFF); + + /* Disable Primary Core Thread0 */ + tmp = STG_READ_REG(Thread0Enable); + CLEAR_BIT(0); + STG_WRITE_REG(Thread0Enable, tmp); + + /* Disable Primary Core Thread1 */ + tmp = STG_READ_REG(Thread1Enable); + CLEAR_BIT(0); + STG_WRITE_REG(Thread1Enable, tmp); + + STG_WRITE_REG(SoftwareReset, + PMX2_SOFTRESET_REG_RST | PMX2_SOFTRESET_ROM_RST); + STG_WRITE_REG(SoftwareReset, + PMX2_SOFTRESET_REG_RST | PMX2_SOFTRESET_TA_RST | + PMX2_SOFTRESET_ROM_RST); + + /* Need to play around to reset TA */ + STG_WRITE_REG(TAConfiguration, 0); + STG_WRITE_REG(SoftwareReset, + PMX2_SOFTRESET_REG_RST | PMX2_SOFTRESET_ROM_RST); + STG_WRITE_REG(SoftwareReset, + PMX2_SOFTRESET_REG_RST | PMX2_SOFTRESET_TA_RST | + PMX2_SOFTRESET_ROM_RST); + + pci_read_config_word(pDev, PCI_CONFIG_SUBSYS_ID, &sub); + + ulChipSpeed = InitSDRAMRegisters(pSTGReg, (u32)sub, + (u32)pDev->revision); + + if (ulChipSpeed == 0) + return -EINVAL; + + ulCoreClock = ProgramClock(REF_FREQ, CORE_PLL_FREQ, &F, &R, &P); + + core_pll |= ((P) | ((F - 2) << 2) | ((R - 2) << 11)); + + /* Set Core PLL Control to Core PLL Mode */ + + /* Send bits 0:7 of the Core PLL Mode register */ + tmp = ((CORE_PLL_MODE_REG_0_7 << 8) | (core_pll & 0x00FF)); + pci_write_config_word(pDev, CorePllControl, tmp); + /* Without some delay between the PCI config writes the clock does + not reliably set when the code is compiled -O3 + */ + OS_DELAY(1000000); + + tmp |= SET_BIT(14); + pci_write_config_word(pDev, CorePllControl, tmp); + OS_DELAY(1000000); + + /* Send bits 8:15 of the Core PLL Mode register */ + tmp = + ((CORE_PLL_MODE_REG_8_15 << 8) | ((core_pll & 0xFF00) >> 8)); + pci_write_config_word(pDev, CorePllControl, tmp); + OS_DELAY(1000000); + + tmp |= SET_BIT(14); + pci_write_config_word(pDev, CorePllControl, tmp); + OS_DELAY(1000000); + + STG_WRITE_REG(SoftwareReset, PMX2_SOFTRESET_ALL); + +#if 0 + /* Enable Primary Core Thread0 */ + tmp = ((STG_READ_REG(Thread0Enable)) | SET_BIT(0)); + STG_WRITE_REG(Thread0Enable, tmp); + + /* Enable Primary Core Thread1 */ + tmp = ((STG_READ_REG(Thread1Enable)) | SET_BIT(0)); + STG_WRITE_REG(Thread1Enable, tmp); +#endif + + return 0; +} diff --git a/drivers/video/fbdev/kyro/STG4000Interface.h b/drivers/video/fbdev/kyro/STG4000Interface.h new file mode 100644 index 000000000000..b7c83d5dfb13 --- /dev/null +++ b/drivers/video/fbdev/kyro/STG4000Interface.h @@ -0,0 +1,61 @@ +/* + * linux/drivers/video/kyro/STG4000Interface.h + * + * Copyright (C) 2002 STMicroelectronics + * + * 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. + */ + +#ifndef _STG4000INTERFACE_H +#define _STG4000INTERFACE_H + +#include <linux/pci.h> +#include <video/kyro.h> + +/* + * Ramdac Setup + */ +extern int InitialiseRamdac(volatile STG4000REG __iomem *pSTGReg, u32 displayDepth, + u32 displayWidth, u32 displayHeight, + s32 HSyncPolarity, s32 VSyncPolarity, + u32 *pixelClock); + +extern void DisableRamdacOutput(volatile STG4000REG __iomem *pSTGReg); +extern void EnableRamdacOutput(volatile STG4000REG __iomem *pSTGReg); + +/* + * Timing generator setup + */ +extern void DisableVGA(volatile STG4000REG __iomem *pSTGReg); +extern void StopVTG(volatile STG4000REG __iomem *pSTGReg); +extern void StartVTG(volatile STG4000REG __iomem *pSTGReg); +extern void SetupVTG(volatile STG4000REG __iomem *pSTGReg, + const struct kyrofb_info * pTiming); + +extern u32 ProgramClock(u32 refClock, u32 coreClock, u32 *FOut, u32 *ROut, u32 *POut); +extern int SetCoreClockPLL(volatile STG4000REG __iomem *pSTGReg, struct pci_dev *pDev); + +/* + * Overlay setup + */ +extern void ResetOverlayRegisters(volatile STG4000REG __iomem *pSTGReg); + +extern int CreateOverlaySurface(volatile STG4000REG __iomem *pSTGReg, + u32 ulWidth, u32 ulHeight, + int bLinear, + u32 ulOverlayOffset, + u32 * retStride, u32 * retUVStride); + +extern int SetOverlayBlendMode(volatile STG4000REG __iomem *pSTGReg, + OVRL_BLEND_MODE mode, + u32 ulAlpha, u32 ulColorKey); + +extern int SetOverlayViewPort(volatile STG4000REG __iomem *pSTGReg, + u32 left, u32 top, + u32 right, u32 bottom); + +extern void EnableOverlayPlane(volatile STG4000REG __iomem *pSTGReg); + +#endif /* _STG4000INTERFACE_H */ diff --git a/drivers/video/fbdev/kyro/STG4000OverlayDevice.c b/drivers/video/fbdev/kyro/STG4000OverlayDevice.c new file mode 100644 index 000000000000..0aeeaa10708b --- /dev/null +++ b/drivers/video/fbdev/kyro/STG4000OverlayDevice.c @@ -0,0 +1,601 @@ +/* + * linux/drivers/video/kyro/STG4000OverlayDevice.c + * + * Copyright (C) 2000 Imagination Technologies Ltd + * Copyright (C) 2002 STMicroelectronics + * + * 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/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> + +#include "STG4000Reg.h" +#include "STG4000Interface.h" + +/* HW Defines */ + +#define STG4000_NO_SCALING 0x800 +#define STG4000_NO_DECIMATION 0xFFFFFFFF + +/* Primary surface */ +#define STG4000_PRIM_NUM_PIX 5 +#define STG4000_PRIM_ALIGN 4 +#define STG4000_PRIM_ADDR_BITS 20 + +#define STG4000_PRIM_MIN_WIDTH 640 +#define STG4000_PRIM_MAX_WIDTH 1600 +#define STG4000_PRIM_MIN_HEIGHT 480 +#define STG4000_PRIM_MAX_HEIGHT 1200 + +/* Overlay surface */ +#define STG4000_OVRL_NUM_PIX 4 +#define STG4000_OVRL_ALIGN 2 +#define STG4000_OVRL_ADDR_BITS 20 +#define STG4000_OVRL_NUM_MODES 5 + +#define STG4000_OVRL_MIN_WIDTH 0 +#define STG4000_OVRL_MAX_WIDTH 720 +#define STG4000_OVRL_MIN_HEIGHT 0 +#define STG4000_OVRL_MAX_HEIGHT 576 + +/* Decimation and Scaling */ +static u32 adwDecim8[33] = { + 0xffffffff, 0xfffeffff, 0xffdffbff, 0xfefefeff, 0xfdf7efbf, + 0xfbdf7bdf, 0xf7bbddef, 0xeeeeeeef, 0xeeddbb77, 0xedb76db7, + 0xdb6db6db, 0xdb5b5b5b, 0xdab5ad6b, 0xd5ab55ab, 0xd555aaab, + 0xaaaaaaab, 0xaaaa5555, 0xaa952a55, 0xa94a5295, 0xa5252525, + 0xa4924925, 0x92491249, 0x91224489, 0x91111111, 0x90884211, + 0x88410821, 0x88102041, 0x81010101, 0x80800801, 0x80010001, + 0x80000001, 0x00000001, 0x00000000 +}; + +typedef struct _OVRL_SRC_DEST { + /*clipped on-screen pixel position of overlay */ + u32 ulDstX1; + u32 ulDstY1; + u32 ulDstX2; + u32 ulDstY2; + + /*clipped pixel pos of source data within buffer thses need to be 128 bit word aligned */ + u32 ulSrcX1; + u32 ulSrcY1; + u32 ulSrcX2; + u32 ulSrcY2; + + /* on-screen pixel position of overlay */ + s32 lDstX1; + s32 lDstY1; + s32 lDstX2; + s32 lDstY2; +} OVRL_SRC_DEST; + +static u32 ovlWidth, ovlHeight, ovlStride; +static int ovlLinear; + +void ResetOverlayRegisters(volatile STG4000REG __iomem *pSTGReg) +{ + u32 tmp; + + /* Set Overlay address to default */ + tmp = STG_READ_REG(DACOverlayAddr); + CLEAR_BITS_FRM_TO(0, 20); + CLEAR_BIT(31); + STG_WRITE_REG(DACOverlayAddr, tmp); + + /* Set Overlay U address */ + tmp = STG_READ_REG(DACOverlayUAddr); + CLEAR_BITS_FRM_TO(0, 20); + STG_WRITE_REG(DACOverlayUAddr, tmp); + + /* Set Overlay V address */ + tmp = STG_READ_REG(DACOverlayVAddr); + CLEAR_BITS_FRM_TO(0, 20); + STG_WRITE_REG(DACOverlayVAddr, tmp); + + /* Set Overlay Size */ + tmp = STG_READ_REG(DACOverlaySize); + CLEAR_BITS_FRM_TO(0, 10); + CLEAR_BITS_FRM_TO(12, 31); + STG_WRITE_REG(DACOverlaySize, tmp); + + /* Set Overlay Vt Decimation */ + tmp = STG4000_NO_DECIMATION; + STG_WRITE_REG(DACOverlayVtDec, tmp); + + /* Set Overlay format to default value */ + tmp = STG_READ_REG(DACPixelFormat); + CLEAR_BITS_FRM_TO(4, 7); + CLEAR_BITS_FRM_TO(16, 22); + STG_WRITE_REG(DACPixelFormat, tmp); + + /* Set Vertical scaling to default */ + tmp = STG_READ_REG(DACVerticalScal); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 22); + tmp |= STG4000_NO_SCALING; /* Set to no scaling */ + STG_WRITE_REG(DACVerticalScal, tmp); + + /* Set Horizontal Scaling to default */ + tmp = STG_READ_REG(DACHorizontalScal); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 17); + tmp |= STG4000_NO_SCALING; /* Set to no scaling */ + STG_WRITE_REG(DACHorizontalScal, tmp); + + /* Set Blend mode to Alpha Blend */ + /* ????? SG 08/11/2001 Surely this isn't the alpha blend mode, + hopefully its overwrite + */ + tmp = STG_READ_REG(DACBlendCtrl); + CLEAR_BITS_FRM_TO(0, 30); + tmp = (GRAPHICS_MODE << 28); + STG_WRITE_REG(DACBlendCtrl, tmp); + +} + +int CreateOverlaySurface(volatile STG4000REG __iomem *pSTGReg, + u32 inWidth, + u32 inHeight, + int bLinear, + u32 ulOverlayOffset, + u32 * retStride, u32 * retUVStride) +{ + u32 tmp; + u32 ulStride; + + if (inWidth > STG4000_OVRL_MAX_WIDTH || + inHeight > STG4000_OVRL_MAX_HEIGHT) { + return -EINVAL; + } + + /* Stride in 16 byte words - 16Bpp */ + if (bLinear) { + /* Format is 16bits so num 16 byte words is width/8 */ + if ((inWidth & 0x7) == 0) { /* inWidth % 8 */ + ulStride = (inWidth / 8); + } else { + /* Round up to next 16byte boundary */ + ulStride = ((inWidth + 8) / 8); + } + } else { + /* Y component is 8bits so num 16 byte words is width/16 */ + if ((inWidth & 0xf) == 0) { /* inWidth % 16 */ + ulStride = (inWidth / 16); + } else { + /* Round up to next 16byte boundary */ + ulStride = ((inWidth + 16) / 16); + } + } + + + /* Set Overlay address and Format mode */ + tmp = STG_READ_REG(DACOverlayAddr); + CLEAR_BITS_FRM_TO(0, 20); + if (bLinear) { + CLEAR_BIT(31); /* Overlay format to Linear */ + } else { + tmp |= SET_BIT(31); /* Overlay format to Planer */ + } + + /* Only bits 24:4 of the Overlay address */ + tmp |= (ulOverlayOffset >> 4); + STG_WRITE_REG(DACOverlayAddr, tmp); + + if (!bLinear) { + u32 uvSize = + (inWidth & 0x1) ? (inWidth + 1 / 2) : (inWidth / 2); + u32 uvStride; + u32 ulOffset; + /* Y component is 8bits so num 32 byte words is width/32 */ + if ((uvSize & 0xf) == 0) { /* inWidth % 16 */ + uvStride = (uvSize / 16); + } else { + /* Round up to next 32byte boundary */ + uvStride = ((uvSize + 16) / 16); + } + + ulOffset = ulOverlayOffset + (inHeight * (ulStride * 16)); + /* Align U,V data to 32byte boundary */ + if ((ulOffset & 0x1f) != 0) + ulOffset = (ulOffset + 32L) & 0xffffffE0L; + + tmp = STG_READ_REG(DACOverlayUAddr); + CLEAR_BITS_FRM_TO(0, 20); + tmp |= (ulOffset >> 4); + STG_WRITE_REG(DACOverlayUAddr, tmp); + + ulOffset += (inHeight / 2) * (uvStride * 16); + /* Align U,V data to 32byte boundary */ + if ((ulOffset & 0x1f) != 0) + ulOffset = (ulOffset + 32L) & 0xffffffE0L; + + tmp = STG_READ_REG(DACOverlayVAddr); + CLEAR_BITS_FRM_TO(0, 20); + tmp |= (ulOffset >> 4); + STG_WRITE_REG(DACOverlayVAddr, tmp); + + *retUVStride = uvStride * 16; + } + + + /* Set Overlay YUV pixel format + * Make sure that LUT not used - ?????? + */ + tmp = STG_READ_REG(DACPixelFormat); + /* Only support Planer or UYVY linear formats */ + CLEAR_BITS_FRM_TO(4, 9); + STG_WRITE_REG(DACPixelFormat, tmp); + + ovlWidth = inWidth; + ovlHeight = inHeight; + ovlStride = ulStride; + ovlLinear = bLinear; + *retStride = ulStride << 4; /* In bytes */ + + return 0; +} + +int SetOverlayBlendMode(volatile STG4000REG __iomem *pSTGReg, + OVRL_BLEND_MODE mode, + u32 ulAlpha, u32 ulColorKey) +{ + u32 tmp; + + tmp = STG_READ_REG(DACBlendCtrl); + CLEAR_BITS_FRM_TO(28, 30); + tmp |= (mode << 28); + + switch (mode) { + case COLOR_KEY: + CLEAR_BITS_FRM_TO(0, 23); + tmp |= (ulColorKey & 0x00FFFFFF); + break; + + case GLOBAL_ALPHA: + CLEAR_BITS_FRM_TO(24, 27); + tmp |= ((ulAlpha & 0xF) << 24); + break; + + case CK_PIXEL_ALPHA: + CLEAR_BITS_FRM_TO(0, 23); + tmp |= (ulColorKey & 0x00FFFFFF); + break; + + case CK_GLOBAL_ALPHA: + CLEAR_BITS_FRM_TO(0, 23); + tmp |= (ulColorKey & 0x00FFFFFF); + CLEAR_BITS_FRM_TO(24, 27); + tmp |= ((ulAlpha & 0xF) << 24); + break; + + case GRAPHICS_MODE: + case PER_PIXEL_ALPHA: + break; + + default: + return -EINVAL; + } + + STG_WRITE_REG(DACBlendCtrl, tmp); + + return 0; +} + +void EnableOverlayPlane(volatile STG4000REG __iomem *pSTGReg) +{ + u32 tmp; + /* Enable Overlay */ + tmp = STG_READ_REG(DACPixelFormat); + tmp |= SET_BIT(7); + STG_WRITE_REG(DACPixelFormat, tmp); + + /* Set video stream control */ + tmp = STG_READ_REG(DACStreamCtrl); + tmp |= SET_BIT(1); /* video stream */ + STG_WRITE_REG(DACStreamCtrl, tmp); +} + +static u32 Overlap(u32 ulBits, u32 ulPattern) +{ + u32 ulCount = 0; + + while (ulBits) { + if (!(ulPattern & 1)) + ulCount++; + ulBits--; + ulPattern = ulPattern >> 1; + } + + return ulCount; + +} + +int SetOverlayViewPort(volatile STG4000REG __iomem *pSTGReg, + u32 left, u32 top, + u32 right, u32 bottom) +{ + OVRL_SRC_DEST srcDest; + + u32 ulSrcTop, ulSrcBottom; + u32 ulSrc, ulDest; + u32 ulFxScale, ulFxOffset; + u32 ulHeight, ulWidth; + u32 ulPattern; + u32 ulDecimate, ulDecimated; + u32 ulApplied; + u32 ulDacXScale, ulDacYScale; + u32 ulScale; + u32 ulLeft, ulRight; + u32 ulSrcLeft, ulSrcRight; + u32 ulScaleLeft, ulScaleRight; + u32 ulhDecim; + u32 ulsVal; + u32 ulVertDecFactor; + int bResult; + u32 ulClipOff = 0; + u32 ulBits = 0; + u32 ulsAdd = 0; + u32 tmp, ulStride; + u32 ulExcessPixels, ulClip, ulExtraLines; + + + srcDest.ulSrcX1 = 0; + srcDest.ulSrcY1 = 0; + srcDest.ulSrcX2 = ovlWidth - 1; + srcDest.ulSrcY2 = ovlHeight - 1; + + srcDest.ulDstX1 = left; + srcDest.ulDstY1 = top; + srcDest.ulDstX2 = right; + srcDest.ulDstY2 = bottom; + + srcDest.lDstX1 = srcDest.ulDstX1; + srcDest.lDstY1 = srcDest.ulDstY1; + srcDest.lDstX2 = srcDest.ulDstX2; + srcDest.lDstY2 = srcDest.ulDstY2; + + /************* Vertical decimation/scaling ******************/ + + /* Get Src Top and Bottom */ + ulSrcTop = srcDest.ulSrcY1; + ulSrcBottom = srcDest.ulSrcY2; + + ulSrc = ulSrcBottom - ulSrcTop; + ulDest = srcDest.lDstY2 - srcDest.lDstY1; /* on-screen overlay */ + + if (ulSrc <= 1) + return -EINVAL; + + /* First work out the position we are to display as offset from the + * source of the buffer + */ + ulFxScale = (ulDest << 11) / ulSrc; /* fixed point scale factor */ + ulFxOffset = (srcDest.lDstY2 - srcDest.ulDstY2) << 11; + + ulSrcBottom = ulSrcBottom - (ulFxOffset / ulFxScale); + ulSrc = ulSrcBottom - ulSrcTop; + ulHeight = ulSrc; + + ulDest = srcDest.ulDstY2 - (srcDest.ulDstY1 - 1); + ulPattern = adwDecim8[ulBits]; + + /* At this point ulSrc represents the input decimator */ + if (ulSrc > ulDest) { + ulDecimate = ulSrc - ulDest; + ulBits = 0; + ulApplied = ulSrc / 32; + + while (((ulBits * ulApplied) + + Overlap((ulSrc % 32), + adwDecim8[ulBits])) < ulDecimate) + ulBits++; + + ulPattern = adwDecim8[ulBits]; + ulDecimated = + (ulBits * ulApplied) + Overlap((ulSrc % 32), + ulPattern); + ulSrc = ulSrc - ulDecimated; /* the number number of lines that will go into the scaler */ + } + + if (ulBits && (ulBits != 32)) { + ulVertDecFactor = (63 - ulBits) / (32 - ulBits); /* vertical decimation factor scaled up to nearest integer */ + } else { + ulVertDecFactor = 1; + } + + ulDacYScale = ((ulSrc - 1) * 2048) / (ulDest + 1); + + tmp = STG_READ_REG(DACOverlayVtDec); /* Decimation */ + CLEAR_BITS_FRM_TO(0, 31); + tmp = ulPattern; + STG_WRITE_REG(DACOverlayVtDec, tmp); + + /***************** Horizontal decimation/scaling ***************************/ + + /* + * Now we handle the horizontal case, this is a simplified version of + * the vertical case in that we decimate by factors of 2. as we are + * working in words we should always be able to decimate by these + * factors. as we always have to have a buffer which is aligned to a + * whole number of 128 bit words, we must align the left side to the + * lowest to the next lowest 128 bit boundary, and the right hand edge + * to the next largets boundary, (in a similar way to how we didi it in + * PMX1) as the left and right hand edges are aligned to these + * boundaries normally this only becomes an issue when we are chopping + * of one of the sides We shall work out vertical stuff first + */ + ulSrc = srcDest.ulSrcX2 - srcDest.ulSrcX1; + ulDest = srcDest.lDstX2 - srcDest.lDstX1; +#ifdef _OLDCODE + ulLeft = srcDest.ulDstX1; + ulRight = srcDest.ulDstX2; +#else + if (srcDest.ulDstX1 > 2) { + ulLeft = srcDest.ulDstX1 + 2; + ulRight = srcDest.ulDstX2 + 1; + } else { + ulLeft = srcDest.ulDstX1; + ulRight = srcDest.ulDstX2 + 1; + } +#endif + /* first work out the position we are to display as offset from the source of the buffer */ + bResult = 1; + + do { + if (ulDest == 0) + return -EINVAL; + + /* source pixels per dest pixel <<11 */ + ulFxScale = ((ulSrc - 1) << 11) / (ulDest); + + /* then number of destination pixels out we are */ + ulFxOffset = ulFxScale * ((srcDest.ulDstX1 - srcDest.lDstX1) + ulClipOff); + ulFxOffset >>= 11; + + /* this replaces the code which was making a decision as to use either ulFxOffset or ulSrcX1 */ + ulSrcLeft = srcDest.ulSrcX1 + ulFxOffset; + + /* then number of destination pixels out we are */ + ulFxOffset = ulFxScale * (srcDest.lDstX2 - srcDest.ulDstX2); + ulFxOffset >>= 11; + + ulSrcRight = srcDest.ulSrcX2 - ulFxOffset; + + /* + * we must align these to our 128 bit boundaries. we shall + * round down the pixel pos to the nearest 8 pixels. + */ + ulScaleLeft = ulSrcLeft; + ulScaleRight = ulSrcRight; + + /* shift fxscale until it is in the range of the scaler */ + ulhDecim = 0; + ulScale = (((ulSrcRight - ulSrcLeft) - 1) << (11 - ulhDecim)) / (ulRight - ulLeft + 2); + + while (ulScale > 0x800) { + ulhDecim++; + ulScale = (((ulSrcRight - ulSrcLeft) - 1) << (11 - ulhDecim)) / (ulRight - ulLeft + 2); + } + + /* + * to try and get the best values We first try and use + * src/dwdest for the scale factor, then we move onto src-1 + * + * we want to check to see if we will need to clip data, if so + * then we should clip our source so that we don't need to + */ + if (!ovlLinear) { + ulSrcLeft &= ~0x1f; + + /* + * we must align the right hand edge to the next 32 + * pixel` boundary, must be on a 256 boundary so u, and + * v are 128 bit aligned + */ + ulSrcRight = (ulSrcRight + 0x1f) & ~0x1f; + } else { + ulSrcLeft &= ~0x7; + + /* + * we must align the right hand edge to the next + * 8pixel` boundary + */ + ulSrcRight = (ulSrcRight + 0x7) & ~0x7; + } + + /* this is the input size line store needs to cope with */ + ulWidth = ulSrcRight - ulSrcLeft; + + /* + * use unclipped value to work out scale factror this is the + * scale factor we want we shall now work out the horizonal + * decimation and scaling + */ + ulsVal = ((ulWidth / 8) >> ulhDecim); + + if ((ulWidth != (ulsVal << ulhDecim) * 8)) + ulsAdd = 1; + + /* input pixels to scaler; */ + ulSrc = ulWidth >> ulhDecim; + + if (ulSrc <= 2) + return -EINVAL; + + ulExcessPixels = ((((ulScaleLeft - ulSrcLeft)) << (11 - ulhDecim)) / ulScale); + + ulClip = (ulSrc << 11) / ulScale; + ulClip -= (ulRight - ulLeft); + ulClip += ulExcessPixels; + + if (ulClip) + ulClip--; + + /* We may need to do more here if we really have a HW rev < 5 */ + } while (!bResult); + + ulExtraLines = (1 << ulhDecim) * ulVertDecFactor; + ulExtraLines += 64; + ulHeight += ulExtraLines; + + ulDacXScale = ulScale; + + + tmp = STG_READ_REG(DACVerticalScal); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 22); /* Vertical Scaling */ + + /* Calculate new output line stride, this is always the number of 422 + words in the line buffer, so it doesn't matter if the + mode is 420. Then set the vertical scale register. + */ + ulStride = (ulWidth >> (ulhDecim + 3)) + ulsAdd; + tmp |= ((ulStride << 16) | (ulDacYScale)); /* DAC_LS_CTRL = stride */ + STG_WRITE_REG(DACVerticalScal, tmp); + + /* Now set up the overlay size using the modified width and height + from decimate and scaling calculations + */ + tmp = STG_READ_REG(DACOverlaySize); + CLEAR_BITS_FRM_TO(0, 10); + CLEAR_BITS_FRM_TO(12, 31); + + if (ovlLinear) { + tmp |= + (ovlStride | ((ulHeight + 1) << 12) | + (((ulWidth / 8) - 1) << 23)); + } else { + tmp |= + (ovlStride | ((ulHeight + 1) << 12) | + (((ulWidth / 32) - 1) << 23)); + } + + STG_WRITE_REG(DACOverlaySize, tmp); + + /* Set Video Window Start */ + tmp = ((ulLeft << 16)) | (srcDest.ulDstY1); + STG_WRITE_REG(DACVidWinStart, tmp); + + /* Set Video Window End */ + tmp = ((ulRight) << 16) | (srcDest.ulDstY2); + STG_WRITE_REG(DACVidWinEnd, tmp); + + /* Finally set up the rest of the overlay regs in the order + done in the IMG driver + */ + tmp = STG_READ_REG(DACPixelFormat); + tmp = ((ulExcessPixels << 16) | tmp) & 0x7fffffff; + STG_WRITE_REG(DACPixelFormat, tmp); + + tmp = STG_READ_REG(DACHorizontalScal); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 17); + tmp |= ((ulhDecim << 16) | (ulDacXScale)); + STG_WRITE_REG(DACHorizontalScal, tmp); + + return 0; +} diff --git a/drivers/video/fbdev/kyro/STG4000Ramdac.c b/drivers/video/fbdev/kyro/STG4000Ramdac.c new file mode 100644 index 000000000000..e6ad037e4396 --- /dev/null +++ b/drivers/video/fbdev/kyro/STG4000Ramdac.c @@ -0,0 +1,163 @@ +/* + * linux/drivers/video/kyro/STG4000Ramdac.c + * + * Copyright (C) 2002 STMicroelectronics + * + * 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/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <video/kyro.h> + +#include "STG4000Reg.h" +#include "STG4000Interface.h" + +static u32 STG_PIXEL_BUS_WIDTH = 128; /* 128 bit bus width */ +static u32 REF_CLOCK = 14318; + +int InitialiseRamdac(volatile STG4000REG __iomem * pSTGReg, + u32 displayDepth, + u32 displayWidth, + u32 displayHeight, + s32 HSyncPolarity, + s32 VSyncPolarity, u32 * pixelClock) +{ + u32 tmp = 0; + u32 F = 0, R = 0, P = 0; + u32 stride = 0; + u32 ulPdiv = 0; + u32 physicalPixelDepth = 0; + /* Make sure DAC is in Reset */ + tmp = STG_READ_REG(SoftwareReset); + + if (tmp & 0x1) { + CLEAR_BIT(1); + STG_WRITE_REG(SoftwareReset, tmp); + } + + /* Set Pixel Format */ + tmp = STG_READ_REG(DACPixelFormat); + CLEAR_BITS_FRM_TO(0, 2); + + /* Set LUT not used from 16bpp to 32 bpp ??? */ + CLEAR_BITS_FRM_TO(8, 9); + + switch (displayDepth) { + case 16: + { + physicalPixelDepth = 16; + tmp |= _16BPP; + break; + } + case 32: + { + /* Set for 32 bits per pixel */ + physicalPixelDepth = 32; + tmp |= _32BPP; + break; + } + default: + return -EINVAL; + } + + STG_WRITE_REG(DACPixelFormat, tmp); + + /* Workout Bus transfer bandwidth according to pixel format */ + ulPdiv = STG_PIXEL_BUS_WIDTH / physicalPixelDepth; + + /* Get Screen Stride in pixels */ + stride = displayWidth; + + /* Set Primary size info */ + tmp = STG_READ_REG(DACPrimSize); + CLEAR_BITS_FRM_TO(0, 10); + CLEAR_BITS_FRM_TO(12, 31); + tmp |= + ((((displayHeight - 1) << 12) | (((displayWidth / ulPdiv) - + 1) << 23)) + | (stride / ulPdiv)); + STG_WRITE_REG(DACPrimSize, tmp); + + + /* Set Pixel Clock */ + *pixelClock = ProgramClock(REF_CLOCK, *pixelClock, &F, &R, &P); + + /* Set DAC PLL Mode */ + tmp = STG_READ_REG(DACPLLMode); + CLEAR_BITS_FRM_TO(0, 15); + /* tmp |= ((P-1) | ((F-2) << 2) | ((R-2) << 11)); */ + tmp |= ((P) | ((F - 2) << 2) | ((R - 2) << 11)); + STG_WRITE_REG(DACPLLMode, tmp); + + /* Set Prim Address */ + tmp = STG_READ_REG(DACPrimAddress); + CLEAR_BITS_FRM_TO(0, 20); + CLEAR_BITS_FRM_TO(20, 31); + STG_WRITE_REG(DACPrimAddress, tmp); + + /* Set Cursor details with HW Cursor disabled */ + tmp = STG_READ_REG(DACCursorCtrl); + tmp &= ~SET_BIT(31); + STG_WRITE_REG(DACCursorCtrl, tmp); + + tmp = STG_READ_REG(DACCursorAddr); + CLEAR_BITS_FRM_TO(0, 20); + STG_WRITE_REG(DACCursorAddr, tmp); + + /* Set Video Window */ + tmp = STG_READ_REG(DACVidWinStart); + CLEAR_BITS_FRM_TO(0, 10); + CLEAR_BITS_FRM_TO(16, 26); + STG_WRITE_REG(DACVidWinStart, tmp); + + tmp = STG_READ_REG(DACVidWinEnd); + CLEAR_BITS_FRM_TO(0, 10); + CLEAR_BITS_FRM_TO(16, 26); + STG_WRITE_REG(DACVidWinEnd, tmp); + + /* Set DAC Border Color to default */ + tmp = STG_READ_REG(DACBorderColor); + CLEAR_BITS_FRM_TO(0, 23); + STG_WRITE_REG(DACBorderColor, tmp); + + /* Set Graphics and Overlay Burst Control */ + STG_WRITE_REG(DACBurstCtrl, 0x0404); + + /* Set CRC Trigger to default */ + tmp = STG_READ_REG(DACCrcTrigger); + CLEAR_BIT(0); + STG_WRITE_REG(DACCrcTrigger, tmp); + + /* Set Video Port Control to default */ + tmp = STG_READ_REG(DigVidPortCtrl); + CLEAR_BIT(8); + CLEAR_BITS_FRM_TO(16, 27); + CLEAR_BITS_FRM_TO(1, 3); + CLEAR_BITS_FRM_TO(10, 11); + STG_WRITE_REG(DigVidPortCtrl, tmp); + + return 0; +} + +/* Ramdac control, turning output to the screen on and off */ +void DisableRamdacOutput(volatile STG4000REG __iomem * pSTGReg) +{ + u32 tmp; + + /* Disable DAC for Graphics Stream Control */ + tmp = (STG_READ_REG(DACStreamCtrl)) & ~SET_BIT(0); + STG_WRITE_REG(DACStreamCtrl, tmp); +} + +void EnableRamdacOutput(volatile STG4000REG __iomem * pSTGReg) +{ + u32 tmp; + + /* Enable DAC for Graphics Stream Control */ + tmp = (STG_READ_REG(DACStreamCtrl)) | SET_BIT(0); + STG_WRITE_REG(DACStreamCtrl, tmp); +} diff --git a/drivers/video/fbdev/kyro/STG4000Reg.h b/drivers/video/fbdev/kyro/STG4000Reg.h new file mode 100644 index 000000000000..50f4670e9252 --- /dev/null +++ b/drivers/video/fbdev/kyro/STG4000Reg.h @@ -0,0 +1,283 @@ +/* + * linux/drivers/video/kyro/STG4000Reg.h + * + * Copyright (C) 2002 STMicroelectronics + * + * 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. + */ + +#ifndef _STG4000REG_H +#define _STG4000REG_H + +#define DWFILL unsigned long :32 +#define WFILL unsigned short :16 + +/* + * Macros that access memory mapped card registers in PCI space + * Add an appropriate section for your OS or processor architecture. + */ +#if defined(__KERNEL__) +#include <asm/page.h> +#include <asm/io.h> +#define STG_WRITE_REG(reg,data) (writel(data,&pSTGReg->reg)) +#define STG_READ_REG(reg) (readl(&pSTGReg->reg)) +#else +#define STG_WRITE_REG(reg,data) (pSTGReg->reg = data) +#define STG_READ_REG(reg) (pSTGReg->reg) +#endif /* __KERNEL__ */ + +#define SET_BIT(n) (1<<(n)) +#define CLEAR_BIT(n) (tmp &= ~(1<<n)) +#define CLEAR_BITS_FRM_TO(frm, to) \ +{\ +int i; \ + for(i = frm; i<= to; i++) \ + { \ + tmp &= ~(1<<i); \ + } \ +} + +#define CLEAR_BIT_2(n) (usTemp &= ~(1<<n)) +#define CLEAR_BITS_FRM_TO_2(frm, to) \ +{\ +int i; \ + for(i = frm; i<= to; i++) \ + { \ + usTemp &= ~(1<<i); \ + } \ +} + +/* LUT select */ +typedef enum _LUT_USES { + NO_LUT = 0, RESERVED, GRAPHICS, OVERLAY +} LUT_USES; + +/* Primary surface pixel format select */ +typedef enum _PIXEL_FORMAT { + _8BPP = 0, _15BPP, _16BPP, _24BPP, _32BPP +} PIXEL_FORMAT; + +/* Overlay blending mode select */ +typedef enum _BLEND_MODE { + GRAPHICS_MODE = 0, COLOR_KEY, PER_PIXEL_ALPHA, GLOBAL_ALPHA, + CK_PIXEL_ALPHA, CK_GLOBAL_ALPHA +} OVRL_BLEND_MODE; + +/* Overlay Pixel format select */ +typedef enum _OVRL_PIX_FORMAT { + UYVY, VYUY, YUYV, YVYU +} OVRL_PIX_FORMAT; + +/* Register Table */ +typedef struct { + /* 0h */ + volatile u32 Thread0Enable; /* 0x0000 */ + volatile u32 Thread1Enable; /* 0x0004 */ + volatile u32 Thread0Recover; /* 0x0008 */ + volatile u32 Thread1Recover; /* 0x000C */ + volatile u32 Thread0Step; /* 0x0010 */ + volatile u32 Thread1Step; /* 0x0014 */ + volatile u32 VideoInStatus; /* 0x0018 */ + volatile u32 Core2InSignStart; /* 0x001C */ + volatile u32 Core1ResetVector; /* 0x0020 */ + volatile u32 Core1ROMOffset; /* 0x0024 */ + volatile u32 Core1ArbiterPriority; /* 0x0028 */ + volatile u32 VideoInControl; /* 0x002C */ + volatile u32 VideoInReg0CtrlA; /* 0x0030 */ + volatile u32 VideoInReg0CtrlB; /* 0x0034 */ + volatile u32 VideoInReg1CtrlA; /* 0x0038 */ + volatile u32 VideoInReg1CtrlB; /* 0x003C */ + volatile u32 Thread0Kicker; /* 0x0040 */ + volatile u32 Core2InputSign; /* 0x0044 */ + volatile u32 Thread0ProgCtr; /* 0x0048 */ + volatile u32 Thread1ProgCtr; /* 0x004C */ + volatile u32 Thread1Kicker; /* 0x0050 */ + volatile u32 GPRegister1; /* 0x0054 */ + volatile u32 GPRegister2; /* 0x0058 */ + volatile u32 GPRegister3; /* 0x005C */ + volatile u32 GPRegister4; /* 0x0060 */ + volatile u32 SerialIntA; /* 0x0064 */ + + volatile u32 Fill0[6]; /* GAP 0x0068 - 0x007C */ + + volatile u32 SoftwareReset; /* 0x0080 */ + volatile u32 SerialIntB; /* 0x0084 */ + + volatile u32 Fill1[37]; /* GAP 0x0088 - 0x011C */ + + volatile u32 ROMELQV; /* 0x011C */ + volatile u32 WLWH; /* 0x0120 */ + volatile u32 ROMELWL; /* 0x0124 */ + + volatile u32 dwFill_1; /* GAP 0x0128 */ + + volatile u32 IntStatus; /* 0x012C */ + volatile u32 IntMask; /* 0x0130 */ + volatile u32 IntClear; /* 0x0134 */ + + volatile u32 Fill2[6]; /* GAP 0x0138 - 0x014C */ + + volatile u32 ROMGPIOA; /* 0x0150 */ + volatile u32 ROMGPIOB; /* 0x0154 */ + volatile u32 ROMGPIOC; /* 0x0158 */ + volatile u32 ROMGPIOD; /* 0x015C */ + + volatile u32 Fill3[2]; /* GAP 0x0160 - 0x0168 */ + + volatile u32 AGPIntID; /* 0x0168 */ + volatile u32 AGPIntClassCode; /* 0x016C */ + volatile u32 AGPIntBIST; /* 0x0170 */ + volatile u32 AGPIntSSID; /* 0x0174 */ + volatile u32 AGPIntPMCSR; /* 0x0178 */ + volatile u32 VGAFrameBufBase; /* 0x017C */ + volatile u32 VGANotify; /* 0x0180 */ + volatile u32 DACPLLMode; /* 0x0184 */ + volatile u32 Core1VideoClockDiv; /* 0x0188 */ + volatile u32 AGPIntStat; /* 0x018C */ + + /* + volatile u32 Fill4[0x0400/4 - 0x0190/4]; //GAP 0x0190 - 0x0400 + volatile u32 Fill5[0x05FC/4 - 0x0400/4]; //GAP 0x0400 - 0x05FC Fog Table + volatile u32 Fill6[0x0604/4 - 0x0600/4]; //GAP 0x0600 - 0x0604 + volatile u32 Fill7[0x0680/4 - 0x0608/4]; //GAP 0x0608 - 0x0680 + volatile u32 Fill8[0x07FC/4 - 0x0684/4]; //GAP 0x0684 - 0x07FC + */ + volatile u32 Fill4[412]; /* 0x0190 - 0x07FC */ + + volatile u32 TACtrlStreamBase; /* 0x0800 */ + volatile u32 TAObjDataBase; /* 0x0804 */ + volatile u32 TAPtrDataBase; /* 0x0808 */ + volatile u32 TARegionDataBase; /* 0x080C */ + volatile u32 TATailPtrBase; /* 0x0810 */ + volatile u32 TAPtrRegionSize; /* 0x0814 */ + volatile u32 TAConfiguration; /* 0x0818 */ + volatile u32 TAObjDataStartAddr; /* 0x081C */ + volatile u32 TAObjDataEndAddr; /* 0x0820 */ + volatile u32 TAXScreenClip; /* 0x0824 */ + volatile u32 TAYScreenClip; /* 0x0828 */ + volatile u32 TARHWClamp; /* 0x082C */ + volatile u32 TARHWCompare; /* 0x0830 */ + volatile u32 TAStart; /* 0x0834 */ + volatile u32 TAObjReStart; /* 0x0838 */ + volatile u32 TAPtrReStart; /* 0x083C */ + volatile u32 TAStatus1; /* 0x0840 */ + volatile u32 TAStatus2; /* 0x0844 */ + volatile u32 TAIntStatus; /* 0x0848 */ + volatile u32 TAIntMask; /* 0x084C */ + + volatile u32 Fill5[235]; /* GAP 0x0850 - 0x0BF8 */ + + volatile u32 TextureAddrThresh; /* 0x0BFC */ + volatile u32 Core1Translation; /* 0x0C00 */ + volatile u32 TextureAddrReMap; /* 0x0C04 */ + volatile u32 RenderOutAGPRemap; /* 0x0C08 */ + volatile u32 _3DRegionReadTrans; /* 0x0C0C */ + volatile u32 _3DPtrReadTrans; /* 0x0C10 */ + volatile u32 _3DParamReadTrans; /* 0x0C14 */ + volatile u32 _3DRegionReadThresh; /* 0x0C18 */ + volatile u32 _3DPtrReadThresh; /* 0x0C1C */ + volatile u32 _3DParamReadThresh; /* 0x0C20 */ + volatile u32 _3DRegionReadAGPRemap; /* 0x0C24 */ + volatile u32 _3DPtrReadAGPRemap; /* 0x0C28 */ + volatile u32 _3DParamReadAGPRemap; /* 0x0C2C */ + volatile u32 ZBufferAGPRemap; /* 0x0C30 */ + volatile u32 TAIndexAGPRemap; /* 0x0C34 */ + volatile u32 TAVertexAGPRemap; /* 0x0C38 */ + volatile u32 TAUVAddrTrans; /* 0x0C3C */ + volatile u32 TATailPtrCacheTrans; /* 0x0C40 */ + volatile u32 TAParamWriteTrans; /* 0x0C44 */ + volatile u32 TAPtrWriteTrans; /* 0x0C48 */ + volatile u32 TAParamWriteThresh; /* 0x0C4C */ + volatile u32 TAPtrWriteThresh; /* 0x0C50 */ + volatile u32 TATailPtrCacheAGPRe; /* 0x0C54 */ + volatile u32 TAParamWriteAGPRe; /* 0x0C58 */ + volatile u32 TAPtrWriteAGPRe; /* 0x0C5C */ + volatile u32 SDRAMArbiterConf; /* 0x0C60 */ + volatile u32 SDRAMConf0; /* 0x0C64 */ + volatile u32 SDRAMConf1; /* 0x0C68 */ + volatile u32 SDRAMConf2; /* 0x0C6C */ + volatile u32 SDRAMRefresh; /* 0x0C70 */ + volatile u32 SDRAMPowerStat; /* 0x0C74 */ + + volatile u32 Fill6[2]; /* GAP 0x0C78 - 0x0C7C */ + + volatile u32 RAMBistData; /* 0x0C80 */ + volatile u32 RAMBistCtrl; /* 0x0C84 */ + volatile u32 FIFOBistKey; /* 0x0C88 */ + volatile u32 RAMBistResult; /* 0x0C8C */ + volatile u32 FIFOBistResult; /* 0x0C90 */ + + /* + volatile u32 Fill11[0x0CBC/4 - 0x0C94/4]; //GAP 0x0C94 - 0x0CBC + volatile u32 Fill12[0x0CD0/4 - 0x0CC0/4]; //GAP 0x0CC0 - 0x0CD0 3DRegisters + */ + + volatile u32 Fill7[16]; /* 0x0c94 - 0x0cd0 */ + + volatile u32 SDRAMAddrSign; /* 0x0CD4 */ + volatile u32 SDRAMDataSign; /* 0x0CD8 */ + volatile u32 SDRAMSignConf; /* 0x0CDC */ + + /* DWFILL; //GAP 0x0CE0 */ + volatile u32 dwFill_2; + + volatile u32 ISPSignature; /* 0x0CE4 */ + + volatile u32 Fill8[454]; /*GAP 0x0CE8 - 0x13FC */ + + volatile u32 DACPrimAddress; /* 0x1400 */ + volatile u32 DACPrimSize; /* 0x1404 */ + volatile u32 DACCursorAddr; /* 0x1408 */ + volatile u32 DACCursorCtrl; /* 0x140C */ + volatile u32 DACOverlayAddr; /* 0x1410 */ + volatile u32 DACOverlayUAddr; /* 0x1414 */ + volatile u32 DACOverlayVAddr; /* 0x1418 */ + volatile u32 DACOverlaySize; /* 0x141C */ + volatile u32 DACOverlayVtDec; /* 0x1420 */ + + volatile u32 Fill9[9]; /* GAP 0x1424 - 0x1444 */ + + volatile u32 DACVerticalScal; /* 0x1448 */ + volatile u32 DACPixelFormat; /* 0x144C */ + volatile u32 DACHorizontalScal; /* 0x1450 */ + volatile u32 DACVidWinStart; /* 0x1454 */ + volatile u32 DACVidWinEnd; /* 0x1458 */ + volatile u32 DACBlendCtrl; /* 0x145C */ + volatile u32 DACHorTim1; /* 0x1460 */ + volatile u32 DACHorTim2; /* 0x1464 */ + volatile u32 DACHorTim3; /* 0x1468 */ + volatile u32 DACVerTim1; /* 0x146C */ + volatile u32 DACVerTim2; /* 0x1470 */ + volatile u32 DACVerTim3; /* 0x1474 */ + volatile u32 DACBorderColor; /* 0x1478 */ + volatile u32 DACSyncCtrl; /* 0x147C */ + volatile u32 DACStreamCtrl; /* 0x1480 */ + volatile u32 DACLUTAddress; /* 0x1484 */ + volatile u32 DACLUTData; /* 0x1488 */ + volatile u32 DACBurstCtrl; /* 0x148C */ + volatile u32 DACCrcTrigger; /* 0x1490 */ + volatile u32 DACCrcDone; /* 0x1494 */ + volatile u32 DACCrcResult1; /* 0x1498 */ + volatile u32 DACCrcResult2; /* 0x149C */ + volatile u32 DACLinecount; /* 0x14A0 */ + + volatile u32 Fill10[151]; /*GAP 0x14A4 - 0x16FC */ + + volatile u32 DigVidPortCtrl; /* 0x1700 */ + volatile u32 DigVidPortStat; /* 0x1704 */ + + /* + volatile u32 Fill11[0x1FFC/4 - 0x1708/4]; //GAP 0x1708 - 0x1FFC + volatile u32 Fill17[0x3000/4 - 0x2FFC/4]; //GAP 0x2000 - 0x2FFC ALUT + */ + + volatile u32 Fill11[1598]; + + /* DWFILL; //GAP 0x3000 ALUT 256MB offset */ + volatile u32 Fill_3; + +} STG4000REG; + +#endif /* _STG4000REG_H */ diff --git a/drivers/video/fbdev/kyro/STG4000VTG.c b/drivers/video/fbdev/kyro/STG4000VTG.c new file mode 100644 index 000000000000..bd389709d234 --- /dev/null +++ b/drivers/video/fbdev/kyro/STG4000VTG.c @@ -0,0 +1,170 @@ +/* + * linux/drivers/video/kyro/STG4000VTG.c + * + * Copyright (C) 2002 STMicroelectronics + * + * 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/types.h> +#include <video/kyro.h> + +#include "STG4000Reg.h" +#include "STG4000Interface.h" + +void DisableVGA(volatile STG4000REG __iomem *pSTGReg) +{ + u32 tmp; + volatile u32 count = 0, i; + + /* Reset the VGA registers */ + tmp = STG_READ_REG(SoftwareReset); + CLEAR_BIT(8); + STG_WRITE_REG(SoftwareReset, tmp); + + /* Just for Delay */ + for (i = 0; i < 1000; i++) { + count++; + } + + /* Pull-out the VGA registers from reset */ + tmp = STG_READ_REG(SoftwareReset); + tmp |= SET_BIT(8); + STG_WRITE_REG(SoftwareReset, tmp); +} + +void StopVTG(volatile STG4000REG __iomem *pSTGReg) +{ + u32 tmp = 0; + + /* Stop Ver and Hor Sync Generator */ + tmp = (STG_READ_REG(DACSyncCtrl)) | SET_BIT(0) | SET_BIT(2); + CLEAR_BIT(31); + STG_WRITE_REG(DACSyncCtrl, tmp); +} + +void StartVTG(volatile STG4000REG __iomem *pSTGReg) +{ + u32 tmp = 0; + + /* Start Ver and Hor Sync Generator */ + tmp = ((STG_READ_REG(DACSyncCtrl)) | SET_BIT(31)); + CLEAR_BIT(0); + CLEAR_BIT(2); + STG_WRITE_REG(DACSyncCtrl, tmp); +} + +void SetupVTG(volatile STG4000REG __iomem *pSTGReg, + const struct kyrofb_info * pTiming) +{ + u32 tmp = 0; + u32 margins = 0; + u32 ulBorder; + u32 xRes = pTiming->XRES; + u32 yRes = pTiming->YRES; + + /* Horizontal */ + u32 HAddrTime, HRightBorder, HLeftBorder; + u32 HBackPorcStrt, HFrontPorchStrt, HTotal, + HLeftBorderStrt, HRightBorderStrt, HDisplayStrt; + + /* Vertical */ + u32 VDisplayStrt, VBottomBorder, VTopBorder; + u32 VBackPorchStrt, VTotal, VTopBorderStrt, + VFrontPorchStrt, VBottomBorderStrt, VAddrTime; + + /* Need to calculate the right border */ + if ((xRes == 640) && (yRes == 480)) { + if ((pTiming->VFREQ == 60) || (pTiming->VFREQ == 72)) { + margins = 8; + } + } + + /* Work out the Border */ + ulBorder = + (pTiming->HTot - + (pTiming->HST + (pTiming->HBP - margins) + xRes + + (pTiming->HFP - margins))) >> 1; + + /* Border the same for Vertical and Horizontal */ + VBottomBorder = HLeftBorder = VTopBorder = HRightBorder = ulBorder; + + /************ Get Timing values for Horizontal ******************/ + HAddrTime = xRes; + HBackPorcStrt = pTiming->HST; + HTotal = pTiming->HTot; + HDisplayStrt = + pTiming->HST + (pTiming->HBP - margins) + HLeftBorder; + HLeftBorderStrt = HDisplayStrt - HLeftBorder; + HFrontPorchStrt = + pTiming->HST + (pTiming->HBP - margins) + HLeftBorder + + HAddrTime + HRightBorder; + HRightBorderStrt = HFrontPorchStrt - HRightBorder; + + /************ Get Timing values for Vertical ******************/ + VAddrTime = yRes; + VBackPorchStrt = pTiming->VST; + VTotal = pTiming->VTot; + VDisplayStrt = + pTiming->VST + (pTiming->VBP - margins) + VTopBorder; + VTopBorderStrt = VDisplayStrt - VTopBorder; + VFrontPorchStrt = + pTiming->VST + (pTiming->VBP - margins) + VTopBorder + + VAddrTime + VBottomBorder; + VBottomBorderStrt = VFrontPorchStrt - VBottomBorder; + + /* Set Hor Timing 1, 2, 3 */ + tmp = STG_READ_REG(DACHorTim1); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 27); + tmp |= (HTotal) | (HBackPorcStrt << 16); + STG_WRITE_REG(DACHorTim1, tmp); + + tmp = STG_READ_REG(DACHorTim2); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 27); + tmp |= (HDisplayStrt << 16) | HLeftBorderStrt; + STG_WRITE_REG(DACHorTim2, tmp); + + tmp = STG_READ_REG(DACHorTim3); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 27); + tmp |= (HFrontPorchStrt << 16) | HRightBorderStrt; + STG_WRITE_REG(DACHorTim3, tmp); + + /* Set Ver Timing 1, 2, 3 */ + tmp = STG_READ_REG(DACVerTim1); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 27); + tmp |= (VBackPorchStrt << 16) | (VTotal); + STG_WRITE_REG(DACVerTim1, tmp); + + tmp = STG_READ_REG(DACVerTim2); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 27); + tmp |= (VDisplayStrt << 16) | VTopBorderStrt; + STG_WRITE_REG(DACVerTim2, tmp); + + tmp = STG_READ_REG(DACVerTim3); + CLEAR_BITS_FRM_TO(0, 11); + CLEAR_BITS_FRM_TO(16, 27); + tmp |= (VFrontPorchStrt << 16) | VBottomBorderStrt; + STG_WRITE_REG(DACVerTim3, tmp); + + /* Set Verical and Horizontal Polarity */ + tmp = STG_READ_REG(DACSyncCtrl) | SET_BIT(3) | SET_BIT(1); + + if ((pTiming->HSP > 0) && (pTiming->VSP < 0)) { /* +hsync -vsync */ + tmp &= ~0x8; + } else if ((pTiming->HSP < 0) && (pTiming->VSP > 0)) { /* -hsync +vsync */ + tmp &= ~0x2; + } else if ((pTiming->HSP < 0) && (pTiming->VSP < 0)) { /* -hsync -vsync */ + tmp &= ~0xA; + } else if ((pTiming->HSP > 0) && (pTiming->VSP > 0)) { /* +hsync -vsync */ + tmp &= ~0x0; + } + + STG_WRITE_REG(DACSyncCtrl, tmp); +} diff --git a/drivers/video/fbdev/kyro/fbdev.c b/drivers/video/fbdev/kyro/fbdev.c new file mode 100644 index 000000000000..65041e15fd59 --- /dev/null +++ b/drivers/video/fbdev/kyro/fbdev.c @@ -0,0 +1,808 @@ +/* + * linux/drivers/video/kyro/fbdev.c + * + * Copyright (C) 2002 STMicroelectronics + * Copyright (C) 2003, 2004 Paul Mundt + * + * 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/types.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/ioctl.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <asm/io.h> +#include <linux/uaccess.h> +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include <video/kyro.h> + +#include "STG4000Reg.h" +#include "STG4000Interface.h" + +/* + * PCI Definitions + */ +#define PCI_VENDOR_ID_ST 0x104a +#define PCI_DEVICE_ID_STG4000 0x0010 + +#define KHZ2PICOS(a) (1000000000UL/(a)) + +/****************************************************************************/ +static struct fb_fix_screeninfo kyro_fix = { + .id = "ST Kyro", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo kyro_var = { + /* 640x480, 16bpp @ 60 Hz */ + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 16, + .red = { 11, 5, 0 }, + .green = { 5, 6, 0 }, + .blue = { 0, 5, 0 }, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .pixclock = KHZ2PICOS(25175), + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +typedef struct { + STG4000REG __iomem *pSTGReg; /* Virtual address of PCI register region */ + u32 ulNextFreeVidMem; /* Offset from start of vid mem to next free region */ + u32 ulOverlayOffset; /* Offset from start of vid mem to overlay */ + u32 ulOverlayStride; /* Interleaved YUV and 422 mode Y stride */ + u32 ulOverlayUVStride; /* 422 mode U & V stride */ +} device_info_t; + +/* global graphics card info structure (one per card) */ +static device_info_t deviceInfo; + +static char *mode_option = NULL; +static int nopan = 0; +static int nowrap = 1; +#ifdef CONFIG_MTRR +static int nomtrr = 0; +#endif + +/* PCI driver prototypes */ +static int kyrofb_probe(struct pci_dev *pdev, const struct pci_device_id *ent); +static void kyrofb_remove(struct pci_dev *pdev); + +static struct fb_videomode kyro_modedb[] = { + { + /* 640x350 @ 85Hz */ + NULL, 85, 640, 350, KHZ2PICOS(31500), + 96, 32, 60, 32, 64, 3, + FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 640x400 @ 85Hz */ + NULL, 85, 640, 400, KHZ2PICOS(31500), + 96, 32, 41, 1, 64, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 720x400 @ 85Hz */ + NULL, 85, 720, 400, KHZ2PICOS(35500), + 108, 36, 42, 1, 72, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 640x480 @ 60Hz */ + NULL, 60, 640, 480, KHZ2PICOS(25175), + 48, 16, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED + }, { + /* 640x480 @ 72Hz */ + NULL, 72, 640, 480, KHZ2PICOS(31500), + 128, 24, 28, 9, 40, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 640x480 @ 75Hz */ + NULL, 75, 640, 480, KHZ2PICOS(31500), + 120, 16, 16, 1, 64, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 640x480 @ 85Hz */ + NULL, 85, 640, 480, KHZ2PICOS(36000), + 80, 56, 25, 1, 56, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 800x600 @ 56Hz */ + NULL, 56, 800, 600, KHZ2PICOS(36000), + 128, 24, 22, 1, 72, 2, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 800x600 @ 60Hz */ + NULL, 60, 800, 600, KHZ2PICOS(40000), + 88, 40, 23, 1, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 800x600 @ 72Hz */ + NULL, 72, 800, 600, KHZ2PICOS(50000), + 64, 56, 23, 37, 120, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 800x600 @ 75Hz */ + NULL, 75, 800, 600, KHZ2PICOS(49500), + 160, 16, 21, 1, 80, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 800x600 @ 85Hz */ + NULL, 85, 800, 600, KHZ2PICOS(56250), + 152, 32, 27, 1, 64, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1024x768 @ 60Hz */ + NULL, 60, 1024, 768, KHZ2PICOS(65000), + 160, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED + }, { + /* 1024x768 @ 70Hz */ + NULL, 70, 1024, 768, KHZ2PICOS(75000), + 144, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED + }, { + /* 1024x768 @ 75Hz */ + NULL, 75, 1024, 768, KHZ2PICOS(78750), + 176, 16, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1024x768 @ 85Hz */ + NULL, 85, 1024, 768, KHZ2PICOS(94500), + 208, 48, 36, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1152x864 @ 75Hz */ + NULL, 75, 1152, 864, KHZ2PICOS(108000), + 256, 64, 32, 1, 128, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1280x960 @ 60Hz */ + NULL, 60, 1280, 960, KHZ2PICOS(108000), + 312, 96, 36, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1280x960 @ 85Hz */ + NULL, 85, 1280, 960, KHZ2PICOS(148500), + 224, 64, 47, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1280x1024 @ 60Hz */ + NULL, 60, 1280, 1024, KHZ2PICOS(108000), + 248, 48, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1280x1024 @ 75Hz */ + NULL, 75, 1280, 1024, KHZ2PICOS(135000), + 248, 16, 38, 1, 144, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1280x1024 @ 85Hz */ + NULL, 85, 1280, 1024, KHZ2PICOS(157500), + 224, 64, 44, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1600x1200 @ 60Hz */ + NULL, 60, 1600, 1200, KHZ2PICOS(162000), + 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1600x1200 @ 65Hz */ + NULL, 65, 1600, 1200, KHZ2PICOS(175500), + 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1600x1200 @ 70Hz */ + NULL, 70, 1600, 1200, KHZ2PICOS(189000), + 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1600x1200 @ 75Hz */ + NULL, 75, 1600, 1200, KHZ2PICOS(202500), + 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1600x1200 @ 85Hz */ + NULL, 85, 1600, 1200, KHZ2PICOS(229500), + 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1792x1344 @ 60Hz */ + NULL, 60, 1792, 1344, KHZ2PICOS(204750), + 328, 128, 46, 1, 200, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1792x1344 @ 75Hz */ + NULL, 75, 1792, 1344, KHZ2PICOS(261000), + 352, 96, 69, 1, 216, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1856x1392 @ 60Hz */ + NULL, 60, 1856, 1392, KHZ2PICOS(218250), + 352, 96, 43, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1856x1392 @ 75Hz */ + NULL, 75, 1856, 1392, KHZ2PICOS(288000), + 352, 128, 104, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1920x1440 @ 60Hz */ + NULL, 60, 1920, 1440, KHZ2PICOS(234000), + 344, 128, 56, 1, 208, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1920x1440 @ 75Hz */ + NULL, 75, 1920, 1440, KHZ2PICOS(297000), + 352, 144, 56, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, +}; +#define NUM_TOTAL_MODES ARRAY_SIZE(kyro_modedb) + +/* + * This needs to be kept ordered corresponding to kyro_modedb. + */ +enum { + VMODE_640_350_85, + VMODE_640_400_85, + VMODE_720_400_85, + VMODE_640_480_60, + VMODE_640_480_72, + VMODE_640_480_75, + VMODE_640_480_85, + VMODE_800_600_56, + VMODE_800_600_60, + VMODE_800_600_72, + VMODE_800_600_75, + VMODE_800_600_85, + VMODE_1024_768_60, + VMODE_1024_768_70, + VMODE_1024_768_75, + VMODE_1024_768_85, + VMODE_1152_864_75, + VMODE_1280_960_60, + VMODE_1280_960_85, + VMODE_1280_1024_60, + VMODE_1280_1024_75, + VMODE_1280_1024_85, + VMODE_1600_1200_60, + VMODE_1600_1200_65, + VMODE_1600_1200_70, + VMODE_1600_1200_75, + VMODE_1600_1200_85, + VMODE_1792_1344_60, + VMODE_1792_1344_75, + VMODE_1856_1392_60, + VMODE_1856_1392_75, + VMODE_1920_1440_60, + VMODE_1920_1440_75, +}; + +/* Accessors */ +static int kyro_dev_video_mode_set(struct fb_info *info) +{ + struct kyrofb_info *par = info->par; + + /* Turn off display */ + StopVTG(deviceInfo.pSTGReg); + DisableRamdacOutput(deviceInfo.pSTGReg); + + /* Bring us out of VGA and into Hi-Res mode, if not already. */ + DisableVGA(deviceInfo.pSTGReg); + + if (InitialiseRamdac(deviceInfo.pSTGReg, + info->var.bits_per_pixel, + info->var.xres, info->var.yres, + par->HSP, par->VSP, &par->PIXCLK) < 0) + return -EINVAL; + + SetupVTG(deviceInfo.pSTGReg, par); + + ResetOverlayRegisters(deviceInfo.pSTGReg); + + /* Turn on display in new mode */ + EnableRamdacOutput(deviceInfo.pSTGReg); + StartVTG(deviceInfo.pSTGReg); + + deviceInfo.ulNextFreeVidMem = info->var.xres * info->var.yres * + info->var.bits_per_pixel; + deviceInfo.ulOverlayOffset = 0; + + return 0; +} + +static int kyro_dev_overlay_create(u32 ulWidth, + u32 ulHeight, int bLinear) +{ + u32 offset; + u32 stride, uvStride; + + if (deviceInfo.ulOverlayOffset != 0) + /* + * Can only create one overlay without resetting the card or + * changing display mode + */ + return -EINVAL; + + ResetOverlayRegisters(deviceInfo.pSTGReg); + + /* Overlays are addressed in multiples of 16bytes or 32bytes, so make + * sure the start offset is on an appropriate boundary. + */ + offset = deviceInfo.ulNextFreeVidMem; + if ((offset & 0x1f) != 0) { + offset = (offset + 32L) & 0xffffffE0L; + } + + if (CreateOverlaySurface(deviceInfo.pSTGReg, ulWidth, ulHeight, + bLinear, offset, &stride, &uvStride) < 0) + return -EINVAL; + + deviceInfo.ulOverlayOffset = offset; + deviceInfo.ulOverlayStride = stride; + deviceInfo.ulOverlayUVStride = uvStride; + deviceInfo.ulNextFreeVidMem = offset + (ulHeight * stride) + (ulHeight * 2 * uvStride); + + SetOverlayBlendMode(deviceInfo.pSTGReg, GLOBAL_ALPHA, 0xf, 0x0); + + return 0; +} + +static int kyro_dev_overlay_viewport_set(u32 x, u32 y, u32 ulWidth, u32 ulHeight) +{ + if (deviceInfo.ulOverlayOffset == 0) + /* probably haven't called CreateOverlay yet */ + return -EINVAL; + + /* Stop Ramdac Output */ + DisableRamdacOutput(deviceInfo.pSTGReg); + + SetOverlayViewPort(deviceInfo.pSTGReg, + x, y, x + ulWidth - 1, y + ulHeight - 1); + + EnableOverlayPlane(deviceInfo.pSTGReg); + /* Start Ramdac Output */ + EnableRamdacOutput(deviceInfo.pSTGReg); + + return 0; +} + +static inline unsigned long get_line_length(int x, int bpp) +{ + return (unsigned long)((((x*bpp)+31)&~31) >> 3); +} + +static int kyrofb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct kyrofb_info *par = info->par; + + if (var->bits_per_pixel != 16 && var->bits_per_pixel != 32) { + printk(KERN_WARNING "kyrofb: depth not supported: %u\n", var->bits_per_pixel); + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.length = 5; + break; + case 32: + var->transp.offset = 24; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + break; + } + + /* Height/Width of picture in mm */ + var->height = var->width = -1; + + /* Timing information. All values are in picoseconds */ + + /* par->PIXCLK is in 100Hz units. Convert to picoseconds - + * ensuring we do not exceed 32 bit precision + */ + /* + * XXX: Enabling this really screws over the pixclock value when we + * read it back with fbset. As such, leaving this commented out appears + * to do the right thing (at least for now) .. bearing in mind that we + * have infact already done the KHZ2PICOS conversion in both the modedb + * and kyro_var. -- PFM. + */ +// var->pixclock = 1000000000 / (par->PIXCLK / 10); + + /* the header file claims we should use picoseconds + * - nobody else does though, the all use pixels and lines + * of h and v sizes. Both options here. + */ + + /* + * If we're being called by __fb_try_mode(), then we don't want to + * override any of the var settings that we've already parsed + * from our modedb. -- PFM. + */ + if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_TEST) + return 0; + + var->left_margin = par->HBP; + var->hsync_len = par->HST; + var->right_margin = par->HFP; + + var->upper_margin = par->VBP; + var->vsync_len = par->VST; + var->lower_margin = par->VFP; + + if (par->HSP == 1) + var->sync |= FB_SYNC_HOR_HIGH_ACT; + if (par->VSP == 1) + var->sync |= FB_SYNC_VERT_HIGH_ACT; + + return 0; +} + +static int kyrofb_set_par(struct fb_info *info) +{ + struct kyrofb_info *par = info->par; + unsigned long lineclock; + unsigned long frameclock; + + /* Actual resolution */ + par->XRES = info->var.xres; + par->YRES = info->var.yres; + + /* pixel depth */ + par->PIXDEPTH = info->var.bits_per_pixel; + + /* Refresh rate */ + /* time for a line in ns */ + lineclock = (info->var.pixclock * (info->var.xres + + info->var.right_margin + + info->var.hsync_len + + info->var.left_margin)) / 1000; + + + /* time for a frame in ns (precision in 32bpp) */ + frameclock = lineclock * (info->var.yres + + info->var.lower_margin + + info->var.vsync_len + + info->var.upper_margin); + + /* Calculate refresh rate and horrizontal clocks */ + par->VFREQ = (1000000000 + (frameclock / 2)) / frameclock; + par->HCLK = (1000000000 + (lineclock / 2)) / lineclock; + par->PIXCLK = ((1000000000 + (info->var.pixclock / 2)) + / info->var.pixclock) * 10; + + /* calculate horizontal timings */ + par->HFP = info->var.right_margin; + par->HST = info->var.hsync_len; + par->HBP = info->var.left_margin; + par->HTot = par->XRES + par->HBP + par->HST + par->HFP; + + /* calculate vertical timings */ + par->VFP = info->var.lower_margin; + par->VST = info->var.vsync_len; + par->VBP = info->var.upper_margin; + par->VTot = par->YRES + par->VBP + par->VST + par->VFP; + + par->HSP = (info->var.sync & FB_SYNC_HOR_HIGH_ACT) ? 1 : 0; + par->VSP = (info->var.sync & FB_SYNC_VERT_HIGH_ACT) ? 1 : 0; + + kyro_dev_video_mode_set(info); + + /* length of a line in bytes */ + info->fix.line_length = get_line_length(par->XRES, par->PIXDEPTH); + info->fix.visual = FB_VISUAL_TRUECOLOR; + + return 0; +} + +static int kyrofb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + struct kyrofb_info *par = info->par; + + if (regno > 255) + return 1; /* Invalid register */ + + if (regno < 16) { + switch (info->var.bits_per_pixel) { + case 16: + par->palette[regno] = + (red & 0xf800) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + case 32: + red >>= 8; green >>= 8; blue >>= 8; transp >>= 8; + par->palette[regno] = + (transp << 24) | (red << 16) | (green << 8) | blue; + break; + } + } + + return 0; +} + +#ifndef MODULE +static int __init kyrofb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ","))) { + if (!*this_opt) + continue; + if (strcmp(this_opt, "nopan") == 0) { + nopan = 1; + } else if (strcmp(this_opt, "nowrap") == 0) { + nowrap = 1; +#ifdef CONFIG_MTRR + } else if (strcmp(this_opt, "nomtrr") == 0) { + nomtrr = 1; +#endif + } else { + mode_option = this_opt; + } + } + + return 0; +} +#endif + +static int kyrofb_ioctl(struct fb_info *info, + unsigned int cmd, unsigned long arg) +{ + overlay_create ol_create; + overlay_viewport_set ol_viewport_set; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case KYRO_IOCTL_OVERLAY_CREATE: + if (copy_from_user(&ol_create, argp, sizeof(overlay_create))) + return -EFAULT; + + if (kyro_dev_overlay_create(ol_create.ulWidth, + ol_create.ulHeight, 0) < 0) { + printk(KERN_ERR "Kyro FB: failed to create overlay surface.\n"); + + return -EINVAL; + } + break; + case KYRO_IOCTL_OVERLAY_VIEWPORT_SET: + if (copy_from_user(&ol_viewport_set, argp, + sizeof(overlay_viewport_set))) + return -EFAULT; + + if (kyro_dev_overlay_viewport_set(ol_viewport_set.xOrgin, + ol_viewport_set.yOrgin, + ol_viewport_set.xSize, + ol_viewport_set.ySize) != 0) + { + printk(KERN_ERR "Kyro FB: failed to create overlay viewport.\n"); + return -EINVAL; + } + break; + case KYRO_IOCTL_SET_VIDEO_MODE: + { + printk(KERN_ERR "Kyro FB: KYRO_IOCTL_SET_VIDEO_MODE is" + "obsolete, use the appropriate fb_ioctl()" + "command instead.\n"); + return -EINVAL; + } + case KYRO_IOCTL_UVSTRIDE: + if (copy_to_user(argp, &deviceInfo.ulOverlayUVStride, sizeof(deviceInfo.ulOverlayUVStride))) + return -EFAULT; + break; + case KYRO_IOCTL_STRIDE: + if (copy_to_user(argp, &deviceInfo.ulOverlayStride, sizeof(deviceInfo.ulOverlayStride))) + return -EFAULT; + break; + case KYRO_IOCTL_OVERLAY_OFFSET: + if (copy_to_user(argp, &deviceInfo.ulOverlayOffset, sizeof(deviceInfo.ulOverlayOffset))) + return -EFAULT; + break; + } + + return 0; +} + +static struct pci_device_id kyrofb_pci_tbl[] = { + { PCI_VENDOR_ID_ST, PCI_DEVICE_ID_STG4000, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, kyrofb_pci_tbl); + +static struct pci_driver kyrofb_pci_driver = { + .name = "kyrofb", + .id_table = kyrofb_pci_tbl, + .probe = kyrofb_probe, + .remove = kyrofb_remove, +}; + +static struct fb_ops kyrofb_ops = { + .owner = THIS_MODULE, + .fb_check_var = kyrofb_check_var, + .fb_set_par = kyrofb_set_par, + .fb_setcolreg = kyrofb_setcolreg, + .fb_ioctl = kyrofb_ioctl, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int kyrofb_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct fb_info *info; + struct kyrofb_info *currentpar; + unsigned long size; + int err; + + if ((err = pci_enable_device(pdev))) { + printk(KERN_WARNING "kyrofb: Can't enable pdev: %d\n", err); + return err; + } + + info = framebuffer_alloc(sizeof(struct kyrofb_info), &pdev->dev); + if (!info) + return -ENOMEM; + + currentpar = info->par; + + kyro_fix.smem_start = pci_resource_start(pdev, 0); + kyro_fix.smem_len = pci_resource_len(pdev, 0); + kyro_fix.mmio_start = pci_resource_start(pdev, 1); + kyro_fix.mmio_len = pci_resource_len(pdev, 1); + + currentpar->regbase = deviceInfo.pSTGReg = + ioremap_nocache(kyro_fix.mmio_start, kyro_fix.mmio_len); + + info->screen_base = ioremap_nocache(kyro_fix.smem_start, + kyro_fix.smem_len); + +#ifdef CONFIG_MTRR + if (!nomtrr) + currentpar->mtrr_handle = + mtrr_add(kyro_fix.smem_start, + kyro_fix.smem_len, + MTRR_TYPE_WRCOMB, 1); +#endif + + kyro_fix.ypanstep = nopan ? 0 : 1; + kyro_fix.ywrapstep = nowrap ? 0 : 1; + + info->fbops = &kyrofb_ops; + info->fix = kyro_fix; + info->pseudo_palette = currentpar->palette; + info->flags = FBINFO_DEFAULT; + + SetCoreClockPLL(deviceInfo.pSTGReg, pdev); + + deviceInfo.ulNextFreeVidMem = 0; + deviceInfo.ulOverlayOffset = 0; + + /* This should give a reasonable default video mode */ + if (!fb_find_mode(&info->var, info, mode_option, kyro_modedb, + NUM_TOTAL_MODES, &kyro_modedb[VMODE_1024_768_75], 32)) + info->var = kyro_var; + + fb_alloc_cmap(&info->cmap, 256, 0); + + kyrofb_set_par(info); + kyrofb_check_var(&info->var, info); + + size = get_line_length(info->var.xres_virtual, + info->var.bits_per_pixel); + size *= info->var.yres_virtual; + + fb_memset(info->screen_base, 0, size); + + if (register_framebuffer(info) < 0) + goto out_unmap; + + fb_info(info, "%s frame buffer device, at %dx%d@%d using %ldk/%ldk of VRAM\n", + info->fix.id, + info->var.xres, info->var.yres, info->var.bits_per_pixel, + size >> 10, (unsigned long)info->fix.smem_len >> 10); + + pci_set_drvdata(pdev, info); + + return 0; + +out_unmap: + iounmap(currentpar->regbase); + iounmap(info->screen_base); + framebuffer_release(info); + + return -EINVAL; +} + +static void kyrofb_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct kyrofb_info *par = info->par; + + /* Reset the board */ + StopVTG(deviceInfo.pSTGReg); + DisableRamdacOutput(deviceInfo.pSTGReg); + + /* Sync up the PLL */ + SetCoreClockPLL(deviceInfo.pSTGReg, pdev); + + deviceInfo.ulNextFreeVidMem = 0; + deviceInfo.ulOverlayOffset = 0; + + iounmap(info->screen_base); + iounmap(par->regbase); + +#ifdef CONFIG_MTRR + if (par->mtrr_handle) + mtrr_del(par->mtrr_handle, + info->fix.smem_start, + info->fix.smem_len); +#endif + + unregister_framebuffer(info); + framebuffer_release(info); +} + +static int __init kyrofb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("kyrofb", &option)) + return -ENODEV; + kyrofb_setup(option); +#endif + return pci_register_driver(&kyrofb_pci_driver); +} + +static void __exit kyrofb_exit(void) +{ + pci_unregister_driver(&kyrofb_pci_driver); +} + +module_init(kyrofb_init); + +#ifdef MODULE +module_exit(kyrofb_exit); +#endif + +MODULE_AUTHOR("STMicroelectronics; Paul Mundt <lethal@linux-sh.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/leo.c b/drivers/video/fbdev/leo.c new file mode 100644 index 000000000000..2c7f7d479fe2 --- /dev/null +++ b/drivers/video/fbdev/leo.c @@ -0,0 +1,691 @@ +/* leo.c: LEO frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996-1999 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1997 Michal Rehacek (Michal.Rehacek@st.mff.cuni.cz) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/of_device.h> +#include <linux/io.h> + +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int leo_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); +static int leo_blank(int, struct fb_info *); + +static int leo_mmap(struct fb_info *, struct vm_area_struct *); +static int leo_ioctl(struct fb_info *, unsigned int, unsigned long); +static int leo_pan_display(struct fb_var_screeninfo *, struct fb_info *); + +/* + * Frame buffer operations + */ + +static struct fb_ops leo_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = leo_setcolreg, + .fb_blank = leo_blank, + .fb_pan_display = leo_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = leo_mmap, + .fb_ioctl = leo_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + +#define LEO_OFF_LC_SS0_KRN 0x00200000UL +#define LEO_OFF_LC_SS0_USR 0x00201000UL +#define LEO_OFF_LC_SS1_KRN 0x01200000UL +#define LEO_OFF_LC_SS1_USR 0x01201000UL +#define LEO_OFF_LD_SS0 0x00400000UL +#define LEO_OFF_LD_SS1 0x01400000UL +#define LEO_OFF_LD_GBL 0x00401000UL +#define LEO_OFF_LX_KRN 0x00600000UL +#define LEO_OFF_LX_CURSOR 0x00601000UL +#define LEO_OFF_SS0 0x00800000UL +#define LEO_OFF_SS1 0x01800000UL +#define LEO_OFF_UNK 0x00602000UL +#define LEO_OFF_UNK2 0x00000000UL + +#define LEO_CUR_ENABLE 0x00000080 +#define LEO_CUR_UPDATE 0x00000030 +#define LEO_CUR_PROGRESS 0x00000006 +#define LEO_CUR_UPDATECMAP 0x00000003 + +#define LEO_CUR_TYPE_MASK 0x00000000 +#define LEO_CUR_TYPE_IMAGE 0x00000020 +#define LEO_CUR_TYPE_CMAP 0x00000050 + +struct leo_cursor { + u8 xxx0[16]; + u32 cur_type; + u32 cur_misc; + u32 cur_cursxy; + u32 cur_data; +}; + +#define LEO_KRN_TYPE_CLUT0 0x00001000 +#define LEO_KRN_TYPE_CLUT1 0x00001001 +#define LEO_KRN_TYPE_CLUT2 0x00001002 +#define LEO_KRN_TYPE_WID 0x00001003 +#define LEO_KRN_TYPE_UNK 0x00001006 +#define LEO_KRN_TYPE_VIDEO 0x00002003 +#define LEO_KRN_TYPE_CLUTDATA 0x00004000 +#define LEO_KRN_CSR_ENABLE 0x00000008 +#define LEO_KRN_CSR_PROGRESS 0x00000004 +#define LEO_KRN_CSR_UNK 0x00000002 +#define LEO_KRN_CSR_UNK2 0x00000001 + +struct leo_lx_krn { + u32 krn_type; + u32 krn_csr; + u32 krn_value; +}; + +struct leo_lc_ss0_krn { + u32 misc; + u8 xxx0[0x800-4]; + u32 rev; +}; + +struct leo_lc_ss0_usr { + u32 csr; + u32 addrspace; + u32 fontmsk; + u32 fontt; + u32 extent; + u32 src; + u32 dst; + u32 copy; + u32 fill; +}; + +struct leo_lc_ss1_krn { + u8 unknown; +}; + +struct leo_lc_ss1_usr { + u8 unknown; +}; + +struct leo_ld_ss0 { + u8 xxx0[0xe00]; + u32 csr; + u32 wid; + u32 wmask; + u32 widclip; + u32 vclipmin; + u32 vclipmax; + u32 pickmin; /* SS1 only */ + u32 pickmax; /* SS1 only */ + u32 fg; + u32 bg; + u32 src; /* Copy/Scroll (SS0 only) */ + u32 dst; /* Copy/Scroll/Fill (SS0 only) */ + u32 extent; /* Copy/Scroll/Fill size (SS0 only) */ + u32 xxx1[3]; + u32 setsem; /* SS1 only */ + u32 clrsem; /* SS1 only */ + u32 clrpick; /* SS1 only */ + u32 clrdat; /* SS1 only */ + u32 alpha; /* SS1 only */ + u8 xxx2[0x2c]; + u32 winbg; + u32 planemask; + u32 rop; + u32 z; + u32 dczf; /* SS1 only */ + u32 dczb; /* SS1 only */ + u32 dcs; /* SS1 only */ + u32 dczs; /* SS1 only */ + u32 pickfb; /* SS1 only */ + u32 pickbb; /* SS1 only */ + u32 dcfc; /* SS1 only */ + u32 forcecol; /* SS1 only */ + u32 door[8]; /* SS1 only */ + u32 pick[5]; /* SS1 only */ +}; + +#define LEO_SS1_MISC_ENABLE 0x00000001 +#define LEO_SS1_MISC_STEREO 0x00000002 +struct leo_ld_ss1 { + u8 xxx0[0xef4]; + u32 ss1_misc; +}; + +struct leo_ld_gbl { + u8 unknown; +}; + +struct leo_par { + spinlock_t lock; + struct leo_lx_krn __iomem *lx_krn; + struct leo_lc_ss0_usr __iomem *lc_ss0_usr; + struct leo_ld_ss0 __iomem *ld_ss0; + struct leo_ld_ss1 __iomem *ld_ss1; + struct leo_cursor __iomem *cursor; + u32 extent; + u32 clut_data[256]; + + u32 flags; +#define LEO_FLAG_BLANKED 0x00000001 + + unsigned long which_io; +}; + +static void leo_wait(struct leo_lx_krn __iomem *lx_krn) +{ + int i; + + for (i = 0; + (sbus_readl(&lx_krn->krn_csr) & LEO_KRN_CSR_PROGRESS) && + i < 300000; + i++) + udelay(1); /* Busy wait at most 0.3 sec */ + return; +} + +static void leo_switch_from_graph(struct fb_info *info) +{ + struct leo_par *par = (struct leo_par *) info->par; + struct leo_ld_ss0 __iomem *ss = par->ld_ss0; + struct leo_cursor __iomem *cursor = par->cursor; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&par->lock, flags); + + par->extent = ((info->var.xres - 1) | + ((info->var.yres - 1) << 16)); + + sbus_writel(0xffffffff, &ss->wid); + sbus_writel(0xffff, &ss->wmask); + sbus_writel(0, &ss->vclipmin); + sbus_writel(par->extent, &ss->vclipmax); + sbus_writel(0, &ss->fg); + sbus_writel(0xff000000, &ss->planemask); + sbus_writel(0x310850, &ss->rop); + sbus_writel(0, &ss->widclip); + sbus_writel((info->var.xres-1) | ((info->var.yres-1) << 11), + &par->lc_ss0_usr->extent); + sbus_writel(4, &par->lc_ss0_usr->addrspace); + sbus_writel(0x80000000, &par->lc_ss0_usr->fill); + sbus_writel(0, &par->lc_ss0_usr->fontt); + do { + val = sbus_readl(&par->lc_ss0_usr->csr); + } while (val & 0x20000000); + + /* setup screen buffer for cfb_* functions */ + sbus_writel(1, &ss->wid); + sbus_writel(0x00ffffff, &ss->planemask); + sbus_writel(0x310b90, &ss->rop); + sbus_writel(0, &par->lc_ss0_usr->addrspace); + + /* hide cursor */ + sbus_writel(sbus_readl(&cursor->cur_misc) & ~LEO_CUR_ENABLE, &cursor->cur_misc); + + spin_unlock_irqrestore(&par->lock, flags); +} + +static int leo_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + /* We just use this to catch switches out of + * graphics mode. + */ + leo_switch_from_graph(info); + + if (var->xoffset || var->yoffset || var->vmode) + return -EINVAL; + return 0; +} + +/** + * leo_setcolreg - Optional function. Sets a color register. + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int leo_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct leo_par *par = (struct leo_par *) info->par; + struct leo_lx_krn __iomem *lx_krn = par->lx_krn; + unsigned long flags; + u32 val; + int i; + + if (regno >= 256) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + par->clut_data[regno] = red | (green << 8) | (blue << 16); + + spin_lock_irqsave(&par->lock, flags); + + leo_wait(lx_krn); + + sbus_writel(LEO_KRN_TYPE_CLUTDATA, &lx_krn->krn_type); + for (i = 0; i < 256; i++) + sbus_writel(par->clut_data[i], &lx_krn->krn_value); + sbus_writel(LEO_KRN_TYPE_CLUT0, &lx_krn->krn_type); + + val = sbus_readl(&lx_krn->krn_csr); + val |= (LEO_KRN_CSR_UNK | LEO_KRN_CSR_UNK2); + sbus_writel(val, &lx_krn->krn_csr); + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +/** + * leo_blank - Optional function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int leo_blank(int blank, struct fb_info *info) +{ + struct leo_par *par = (struct leo_par *) info->par; + struct leo_lx_krn __iomem *lx_krn = par->lx_krn; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&par->lock, flags); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + val = sbus_readl(&lx_krn->krn_csr); + val |= LEO_KRN_CSR_ENABLE; + sbus_writel(val, &lx_krn->krn_csr); + par->flags &= ~LEO_FLAG_BLANKED; + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + case FB_BLANK_POWERDOWN: /* Poweroff */ + val = sbus_readl(&lx_krn->krn_csr); + val &= ~LEO_KRN_CSR_ENABLE; + sbus_writel(val, &lx_krn->krn_csr); + par->flags |= LEO_FLAG_BLANKED; + break; + } + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static struct sbus_mmap_map leo_mmap_map[] = { + { + .voff = LEO_SS0_MAP, + .poff = LEO_OFF_SS0, + .size = 0x800000 + }, + { + .voff = LEO_LC_SS0_USR_MAP, + .poff = LEO_OFF_LC_SS0_USR, + .size = 0x1000 + }, + { + .voff = LEO_LD_SS0_MAP, + .poff = LEO_OFF_LD_SS0, + .size = 0x1000 + }, + { + .voff = LEO_LX_CURSOR_MAP, + .poff = LEO_OFF_LX_CURSOR, + .size = 0x1000 + }, + { + .voff = LEO_SS1_MAP, + .poff = LEO_OFF_SS1, + .size = 0x800000 + }, + { + .voff = LEO_LC_SS1_USR_MAP, + .poff = LEO_OFF_LC_SS1_USR, + .size = 0x1000 + }, + { + .voff = LEO_LD_SS1_MAP, + .poff = LEO_OFF_LD_SS1, + .size = 0x1000 + }, + { + .voff = LEO_UNK_MAP, + .poff = LEO_OFF_UNK, + .size = 0x1000 + }, + { + .voff = LEO_LX_KRN_MAP, + .poff = LEO_OFF_LX_KRN, + .size = 0x1000 + }, + { + .voff = LEO_LC_SS0_KRN_MAP, + .poff = LEO_OFF_LC_SS0_KRN, + .size = 0x1000 + }, + { + .voff = LEO_LC_SS1_KRN_MAP, + .poff = LEO_OFF_LC_SS1_KRN, + .size = 0x1000 + }, + { + .voff = LEO_LD_GBL_MAP, + .poff = LEO_OFF_LD_GBL, + .size = 0x1000 + }, + { + .voff = LEO_UNK2_MAP, + .poff = LEO_OFF_UNK2, + .size = 0x100000 + }, + { .size = 0 } +}; + +static int leo_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct leo_par *par = (struct leo_par *)info->par; + + return sbusfb_mmap_helper(leo_mmap_map, + info->fix.smem_start, info->fix.smem_len, + par->which_io, vma); +} + +static int leo_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + return sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_SUNLEO, 32, info->fix.smem_len); +} + +/* + * Initialisation + */ + +static void +leo_init_fix(struct fb_info *info, struct device_node *dp) +{ + strlcpy(info->fix.id, dp->name, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + + info->fix.line_length = 8192; + + info->fix.accel = FB_ACCEL_SUN_LEO; +} + +static void leo_wid_put(struct fb_info *info, struct fb_wid_list *wl) +{ + struct leo_par *par = (struct leo_par *) info->par; + struct leo_lx_krn __iomem *lx_krn = par->lx_krn; + struct fb_wid_item *wi; + unsigned long flags; + u32 val; + int i, j; + + spin_lock_irqsave(&par->lock, flags); + + leo_wait(lx_krn); + + for (i = 0, wi = wl->wl_list; i < wl->wl_count; i++, wi++) { + switch (wi->wi_type) { + case FB_WID_DBL_8: + j = (wi->wi_index & 0xf) + 0x40; + break; + + case FB_WID_DBL_24: + j = wi->wi_index & 0x3f; + break; + + default: + continue; + } + sbus_writel(0x5800 + j, &lx_krn->krn_type); + sbus_writel(wi->wi_values[0], &lx_krn->krn_value); + } + sbus_writel(LEO_KRN_TYPE_WID, &lx_krn->krn_type); + + val = sbus_readl(&lx_krn->krn_csr); + val |= (LEO_KRN_CSR_UNK | LEO_KRN_CSR_UNK2); + sbus_writel(val, &lx_krn->krn_csr); + + spin_unlock_irqrestore(&par->lock, flags); +} + +static void leo_init_wids(struct fb_info *info) +{ + struct fb_wid_item wi; + struct fb_wid_list wl; + + wl.wl_count = 1; + wl.wl_list = &wi; + wi.wi_type = FB_WID_DBL_8; + wi.wi_index = 0; + wi.wi_values [0] = 0x2c0; + leo_wid_put(info, &wl); + wi.wi_index = 1; + wi.wi_values [0] = 0x30; + leo_wid_put(info, &wl); + wi.wi_index = 2; + wi.wi_values [0] = 0x20; + leo_wid_put(info, &wl); + wi.wi_type = FB_WID_DBL_24; + wi.wi_index = 1; + wi.wi_values [0] = 0x30; + leo_wid_put(info, &wl); +} + +static void leo_init_hw(struct fb_info *info) +{ + struct leo_par *par = (struct leo_par *) info->par; + u32 val; + + val = sbus_readl(&par->ld_ss1->ss1_misc); + val |= LEO_SS1_MISC_ENABLE; + sbus_writel(val, &par->ld_ss1->ss1_misc); + + leo_switch_from_graph(info); +} + +static void leo_fixup_var_rgb(struct fb_var_screeninfo *var) +{ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; +} + +static void leo_unmap_regs(struct platform_device *op, struct fb_info *info, + struct leo_par *par) +{ + if (par->lc_ss0_usr) + of_iounmap(&op->resource[0], par->lc_ss0_usr, 0x1000); + if (par->ld_ss0) + of_iounmap(&op->resource[0], par->ld_ss0, 0x1000); + if (par->ld_ss1) + of_iounmap(&op->resource[0], par->ld_ss1, 0x1000); + if (par->lx_krn) + of_iounmap(&op->resource[0], par->lx_krn, 0x1000); + if (par->cursor) + of_iounmap(&op->resource[0], + par->cursor, sizeof(struct leo_cursor)); + if (info->screen_base) + of_iounmap(&op->resource[0], info->screen_base, 0x800000); +} + +static int leo_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct leo_par *par; + int linebytes, err; + + info = framebuffer_alloc(sizeof(struct leo_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + par = info->par; + + spin_lock_init(&par->lock); + + info->fix.smem_start = op->resource[0].start; + par->which_io = op->resource[0].flags & IORESOURCE_BITS; + + sbusfb_fill_var(&info->var, dp, 32); + leo_fixup_var_rgb(&info->var); + + linebytes = of_getintprop_default(dp, "linebytes", + info->var.xres); + info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); + + par->lc_ss0_usr = + of_ioremap(&op->resource[0], LEO_OFF_LC_SS0_USR, + 0x1000, "leolc ss0usr"); + par->ld_ss0 = + of_ioremap(&op->resource[0], LEO_OFF_LD_SS0, + 0x1000, "leold ss0"); + par->ld_ss1 = + of_ioremap(&op->resource[0], LEO_OFF_LD_SS1, + 0x1000, "leold ss1"); + par->lx_krn = + of_ioremap(&op->resource[0], LEO_OFF_LX_KRN, + 0x1000, "leolx krn"); + par->cursor = + of_ioremap(&op->resource[0], LEO_OFF_LX_CURSOR, + sizeof(struct leo_cursor), "leolx cursor"); + info->screen_base = + of_ioremap(&op->resource[0], LEO_OFF_SS0, + 0x800000, "leo ram"); + if (!par->lc_ss0_usr || + !par->ld_ss0 || + !par->ld_ss1 || + !par->lx_krn || + !par->cursor || + !info->screen_base) + goto out_unmap_regs; + + info->flags = FBINFO_DEFAULT; + info->fbops = &leo_ops; + info->pseudo_palette = par->clut_data; + + leo_init_wids(info); + leo_init_hw(info); + + leo_blank(FB_BLANK_UNBLANK, info); + + if (fb_alloc_cmap(&info->cmap, 256, 0)) + goto out_unmap_regs; + + leo_init_fix(info, dp); + + err = register_framebuffer(info); + if (err < 0) + goto out_dealloc_cmap; + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: leo at %lx:%lx\n", + dp->full_name, + par->which_io, info->fix.smem_start); + + return 0; + +out_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + +out_unmap_regs: + leo_unmap_regs(op, info, par); + framebuffer_release(info); + +out_err: + return err; +} + +static int leo_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct leo_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + leo_unmap_regs(op, info, par); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id leo_match[] = { + { + .name = "SUNW,leo", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, leo_match); + +static struct platform_driver leo_driver = { + .driver = { + .name = "leo", + .owner = THIS_MODULE, + .of_match_table = leo_match, + }, + .probe = leo_probe, + .remove = leo_remove, +}; + +static int __init leo_init(void) +{ + if (fb_get_options("leofb", NULL)) + return -ENODEV; + + return platform_driver_register(&leo_driver); +} + +static void __exit leo_exit(void) +{ + platform_driver_unregister(&leo_driver); +} + +module_init(leo_init); +module_exit(leo_exit); + +MODULE_DESCRIPTION("framebuffer driver for LEO chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/macfb.c b/drivers/video/fbdev/macfb.c new file mode 100644 index 000000000000..cda7587cbc86 --- /dev/null +++ b/drivers/video/fbdev/macfb.c @@ -0,0 +1,928 @@ +/* + * macfb.c: Generic framebuffer for Macs whose colourmaps/modes we + * don't know how to set. + * + * (c) 1999 David Huggins-Daines <dhd@debian.org> + * + * Primarily based on vesafb.c, by Gerd Knorr + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * Also uses information and code from: + * + * The original macfb.c from Linux/mac68k 2.0, by Alan Cox, Juergen + * Mellinger, Mikael Forselius, Michael Schmitz, and others. + * + * valkyriefb.c, by Martin Costabel, Kevin Schoedel, Barry Nathan, Dan + * Jacobowitz, Paul Mackerras, Fabio Riccardi, and Geert Uytterhoeven. + * + * The VideoToolbox "Bugs" web page at + * http://rajsky.psych.nyu.edu/Tips/VideoBugs.html + * + * This code is free software. You may copy, modify, and distribute + * it subject to the terms and conditions of the GNU General Public + * License, version 2, or any later version, at your convenience. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/nubus.h> +#include <linux/init.h> +#include <linux/fb.h> + +#include <asm/setup.h> +#include <asm/macintosh.h> +#include <asm/io.h> + +/* Common DAC base address for the LC, RBV, Valkyrie, and IIvx */ +#define DAC_BASE 0x50f24000 + +/* Some addresses for the DAFB */ +#define DAFB_BASE 0xf9800200 + +/* Address for the built-in Civic framebuffer in Quadra AVs */ +#define CIVIC_BASE 0x50f30800 + +/* GSC (Gray Scale Controller) base address */ +#define GSC_BASE 0x50F20000 + +/* CSC (Color Screen Controller) base address */ +#define CSC_BASE 0x50F20000 + +static int (*macfb_setpalette)(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info); + +static struct { + unsigned char addr; + unsigned char lut; +} __iomem *v8_brazil_cmap_regs; + +static struct { + unsigned char addr; + char pad1[3]; /* word aligned */ + unsigned char lut; + char pad2[3]; /* word aligned */ + unsigned char cntl; /* a guess as to purpose */ +} __iomem *rbv_cmap_regs; + +static struct { + unsigned long reset; + unsigned long pad1[3]; + unsigned char pad2[3]; + unsigned char lut; +} __iomem *dafb_cmap_regs; + +static struct { + unsigned char addr; /* OFFSET: 0x00 */ + unsigned char pad1[15]; + unsigned char lut; /* OFFSET: 0x10 */ + unsigned char pad2[15]; + unsigned char status; /* OFFSET: 0x20 */ + unsigned char pad3[7]; + unsigned long vbl_addr; /* OFFSET: 0x28 */ + unsigned int status2; /* OFFSET: 0x2C */ +} __iomem *civic_cmap_regs; + +static struct { + char pad1[0x40]; + unsigned char clut_waddr; /* 0x40 */ + char pad2; + unsigned char clut_data; /* 0x42 */ + char pad3[0x3]; + unsigned char clut_raddr; /* 0x46 */ +} __iomem *csc_cmap_regs; + +/* The registers in these structs are in NuBus slot space */ +struct mdc_cmap_regs { + char pad1[0x200200]; + unsigned char addr; + char pad2[6]; + unsigned char lut; +}; + +struct toby_cmap_regs { + char pad1[0x90018]; + unsigned char lut; /* TFBClutWDataReg, offset 0x90018 */ + char pad2[3]; + unsigned char addr; /* TFBClutAddrReg, offset 0x9001C */ +}; + +struct jet_cmap_regs { + char pad1[0xe0e000]; + unsigned char addr; + unsigned char lut; +}; + +#define PIXEL_TO_MM(a) (((a)*10)/28) /* width in mm at 72 dpi */ + +static struct fb_var_screeninfo macfb_defined = { + .bits_per_pixel = 8, + .activate = FB_ACTIVATE_NOW, + .width = -1, + .height = -1, + .right_margin = 32, + .upper_margin = 16, + .lower_margin = 4, + .vsync_len = 4, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo macfb_fix = { + .type = FB_TYPE_PACKED_PIXELS, + .accel = FB_ACCEL_NONE, +}; + +static void *slot_addr; +static struct fb_info fb_info; +static u32 pseudo_palette[16]; +static int inverse; +static int vidtest; + +/* + * Unlike the Valkyrie, the DAFB cannot set individual colormap + * registers. Therefore, we do what the MacOS driver does (no + * kidding!) and simply set them one by one until we hit the one we + * want. + */ +static int dafb_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + static int lastreg = -1; + unsigned long flags; + + local_irq_save(flags); + + /* + * fbdev will set an entire colourmap, but X won't. Hopefully + * this should accommodate both of them + */ + if (regno != lastreg + 1) { + int i; + + /* Stab in the dark trying to reset the CLUT pointer */ + nubus_writel(0, &dafb_cmap_regs->reset); + nop(); + + /* Loop until we get to the register we want */ + for (i = 0; i < regno; i++) { + nubus_writeb(info->cmap.red[i] >> 8, + &dafb_cmap_regs->lut); + nop(); + nubus_writeb(info->cmap.green[i] >> 8, + &dafb_cmap_regs->lut); + nop(); + nubus_writeb(info->cmap.blue[i] >> 8, + &dafb_cmap_regs->lut); + nop(); + } + } + + nubus_writeb(red, &dafb_cmap_regs->lut); + nop(); + nubus_writeb(green, &dafb_cmap_regs->lut); + nop(); + nubus_writeb(blue, &dafb_cmap_regs->lut); + + local_irq_restore(flags); + lastreg = regno; + return 0; +} + +/* V8 and Brazil seem to use the same DAC. Sonora does as well. */ +static int v8_brazil_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + unsigned int bpp = info->var.bits_per_pixel; + unsigned long flags; + + if (bpp > 8) + return 1; /* failsafe */ + + local_irq_save(flags); + + /* On these chips, the CLUT register numbers are spread out + * across the register space. Thus: + * In 8bpp, all regnos are valid. + * In 4bpp, the regnos are 0x0f, 0x1f, 0x2f, etc, etc + * In 2bpp, the regnos are 0x3f, 0x7f, 0xbf, 0xff + */ + regno = (regno << (8 - bpp)) | (0xFF >> bpp); + nubus_writeb(regno, &v8_brazil_cmap_regs->addr); + nop(); + + /* send one color channel at a time */ + nubus_writeb(red, &v8_brazil_cmap_regs->lut); + nop(); + nubus_writeb(green, &v8_brazil_cmap_regs->lut); + nop(); + nubus_writeb(blue, &v8_brazil_cmap_regs->lut); + + local_irq_restore(flags); + return 0; +} + +/* RAM-Based Video */ +static int rbv_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + unsigned long flags; + + if (info->var.bits_per_pixel > 8) + return 1; /* failsafe */ + + local_irq_save(flags); + + /* From the VideoToolbox driver. Seems to be saying that + * regno #254 and #255 are the important ones for 1-bit color, + * regno #252-255 are the important ones for 2-bit color, etc. + */ + regno += 256 - (1 << info->var.bits_per_pixel); + + /* reset clut? (VideoToolbox sez "not necessary") */ + nubus_writeb(0xFF, &rbv_cmap_regs->cntl); + nop(); + + /* tell clut which address to use. */ + nubus_writeb(regno, &rbv_cmap_regs->addr); + nop(); + + /* send one color channel at a time. */ + nubus_writeb(red, &rbv_cmap_regs->lut); + nop(); + nubus_writeb(green, &rbv_cmap_regs->lut); + nop(); + nubus_writeb(blue, &rbv_cmap_regs->lut); + + local_irq_restore(flags); + return 0; +} + +/* Macintosh Display Card (8*24) */ +static int mdc_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + struct mdc_cmap_regs *cmap_regs = slot_addr; + unsigned long flags; + + local_irq_save(flags); + + /* the nop's are there to order writes. */ + nubus_writeb(regno, &cmap_regs->addr); + nop(); + nubus_writeb(red, &cmap_regs->lut); + nop(); + nubus_writeb(green, &cmap_regs->lut); + nop(); + nubus_writeb(blue, &cmap_regs->lut); + + local_irq_restore(flags); + return 0; +} + +/* Toby frame buffer */ +static int toby_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + struct toby_cmap_regs *cmap_regs = slot_addr; + unsigned int bpp = info->var.bits_per_pixel; + unsigned long flags; + + red = ~red; + green = ~green; + blue = ~blue; + regno = (regno << (8 - bpp)) | (0xFF >> bpp); + + local_irq_save(flags); + + nubus_writeb(regno, &cmap_regs->addr); + nop(); + nubus_writeb(red, &cmap_regs->lut); + nop(); + nubus_writeb(green, &cmap_regs->lut); + nop(); + nubus_writeb(blue, &cmap_regs->lut); + + local_irq_restore(flags); + return 0; +} + +/* Jet frame buffer */ +static int jet_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + struct jet_cmap_regs *cmap_regs = slot_addr; + unsigned long flags; + + local_irq_save(flags); + + nubus_writeb(regno, &cmap_regs->addr); + nop(); + nubus_writeb(red, &cmap_regs->lut); + nop(); + nubus_writeb(green, &cmap_regs->lut); + nop(); + nubus_writeb(blue, &cmap_regs->lut); + + local_irq_restore(flags); + return 0; +} + +/* + * Civic framebuffer -- Quadra AV built-in video. A chip + * called Sebastian holds the actual color palettes, and + * apparently, there are two different banks of 512K RAM + * which can act as separate framebuffers for doing video + * input and viewing the screen at the same time! The 840AV + * Can add another 1MB RAM to give the two framebuffers + * 1MB RAM apiece. + */ +static int civic_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + unsigned long flags; + int clut_status; + + if (info->var.bits_per_pixel > 8) + return 1; /* failsafe */ + + local_irq_save(flags); + + /* Set the register address */ + nubus_writeb(regno, &civic_cmap_regs->addr); + nop(); + + /* + * Grab a status word and do some checking; + * Then finally write the clut! + */ + clut_status = nubus_readb(&civic_cmap_regs->status2); + + if ((clut_status & 0x0008) == 0) + { +#if 0 + if ((clut_status & 0x000D) != 0) + { + nubus_writeb(0x00, &civic_cmap_regs->lut); + nop(); + nubus_writeb(0x00, &civic_cmap_regs->lut); + nop(); + } +#endif + + nubus_writeb(red, &civic_cmap_regs->lut); + nop(); + nubus_writeb(green, &civic_cmap_regs->lut); + nop(); + nubus_writeb(blue, &civic_cmap_regs->lut); + nop(); + nubus_writeb(0x00, &civic_cmap_regs->lut); + } + else + { + unsigned char junk; + + junk = nubus_readb(&civic_cmap_regs->lut); + nop(); + junk = nubus_readb(&civic_cmap_regs->lut); + nop(); + junk = nubus_readb(&civic_cmap_regs->lut); + nop(); + junk = nubus_readb(&civic_cmap_regs->lut); + nop(); + + if ((clut_status & 0x000D) != 0) + { + nubus_writeb(0x00, &civic_cmap_regs->lut); + nop(); + nubus_writeb(0x00, &civic_cmap_regs->lut); + nop(); + } + + nubus_writeb(red, &civic_cmap_regs->lut); + nop(); + nubus_writeb(green, &civic_cmap_regs->lut); + nop(); + nubus_writeb(blue, &civic_cmap_regs->lut); + nop(); + nubus_writeb(junk, &civic_cmap_regs->lut); + } + + local_irq_restore(flags); + return 0; +} + +/* + * The CSC is the framebuffer on the PowerBook 190 series + * (and the 5300 too, but that's a PowerMac). This function + * brought to you in part by the ECSC driver for MkLinux. + */ +static int csc_setpalette(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + struct fb_info *info) +{ + unsigned long flags; + + local_irq_save(flags); + + udelay(1); /* mklinux on PB 5300 waits for 260 ns */ + nubus_writeb(regno, &csc_cmap_regs->clut_waddr); + nubus_writeb(red, &csc_cmap_regs->clut_data); + nubus_writeb(green, &csc_cmap_regs->clut_data); + nubus_writeb(blue, &csc_cmap_regs->clut_data); + + local_irq_restore(flags); + return 0; +} + +static int macfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *fb_info) +{ + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). + * Return non-zero for invalid regno. + */ + + if (regno >= fb_info->cmap.len) + return 1; + + if (fb_info->var.bits_per_pixel <= 8) { + switch (fb_info->var.bits_per_pixel) { + case 1: + /* We shouldn't get here */ + break; + case 2: + case 4: + case 8: + if (macfb_setpalette) + macfb_setpalette(regno, red >> 8, green >> 8, + blue >> 8, fb_info); + else + return 1; + break; + } + } else if (regno < 16) { + switch (fb_info->var.bits_per_pixel) { + case 16: + if (fb_info->var.red.offset == 10) { + /* 1:5:5:5 */ + ((u32*) (fb_info->pseudo_palette))[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11) | + ((transp != 0) << 15); + } else { + /* 0:5:6:5 */ + ((u32*) (fb_info->pseudo_palette))[regno] = + ((red & 0xf800) >> 0) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + } + break; + /* + * 24-bit colour almost doesn't exist on 68k Macs -- + * http://support.apple.com/kb/TA28634 (Old Article: 10992) + */ + case 24: + case 32: + red >>= 8; + green >>= 8; + blue >>= 8; + ((u32 *)(fb_info->pseudo_palette))[regno] = + (red << fb_info->var.red.offset) | + (green << fb_info->var.green.offset) | + (blue << fb_info->var.blue.offset); + break; + } + } + + return 0; +} + +static struct fb_ops macfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = macfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static void __init macfb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + + if (!strcmp(this_opt, "inverse")) + inverse = 1; + else + if (!strcmp(this_opt, "vidtest")) + vidtest = 1; /* enable experimental CLUT code */ + } +} + +static void __init iounmap_macfb(void) +{ + if (dafb_cmap_regs) + iounmap(dafb_cmap_regs); + if (v8_brazil_cmap_regs) + iounmap(v8_brazil_cmap_regs); + if (rbv_cmap_regs) + iounmap(rbv_cmap_regs); + if (civic_cmap_regs) + iounmap(civic_cmap_regs); + if (csc_cmap_regs) + iounmap(csc_cmap_regs); +} + +static int __init macfb_init(void) +{ + int video_cmap_len, video_is_nubus = 0; + struct nubus_dev* ndev = NULL; + char *option = NULL; + int err; + + if (fb_get_options("macfb", &option)) + return -ENODEV; + macfb_setup(option); + + if (!MACH_IS_MAC) + return -ENODEV; + + if (mac_bi_data.id == MAC_MODEL_Q630 || + mac_bi_data.id == MAC_MODEL_P588) + return -ENODEV; /* See valkyriefb.c */ + + macfb_defined.xres = mac_bi_data.dimensions & 0xFFFF; + macfb_defined.yres = mac_bi_data.dimensions >> 16; + macfb_defined.bits_per_pixel = mac_bi_data.videodepth; + + macfb_fix.line_length = mac_bi_data.videorow; + macfb_fix.smem_len = macfb_fix.line_length * macfb_defined.yres; + /* Note: physical address (since 2.1.127) */ + macfb_fix.smem_start = mac_bi_data.videoaddr; + + /* + * This is actually redundant with the initial mappings. + * However, there are some non-obvious aspects to the way + * those mappings are set up, so this is in fact the safest + * way to ensure that this driver will work on every possible Mac + */ + fb_info.screen_base = ioremap(mac_bi_data.videoaddr, + macfb_fix.smem_len); + if (!fb_info.screen_base) + return -ENODEV; + + pr_info("macfb: framebuffer at 0x%08lx, mapped to 0x%p, size %dk\n", + macfb_fix.smem_start, fb_info.screen_base, + macfb_fix.smem_len / 1024); + pr_info("macfb: mode is %dx%dx%d, linelength=%d\n", + macfb_defined.xres, macfb_defined.yres, + macfb_defined.bits_per_pixel, macfb_fix.line_length); + + /* Fill in the available video resolution */ + macfb_defined.xres_virtual = macfb_defined.xres; + macfb_defined.yres_virtual = macfb_defined.yres; + macfb_defined.height = PIXEL_TO_MM(macfb_defined.yres); + macfb_defined.width = PIXEL_TO_MM(macfb_defined.xres); + + /* Some dummy values for timing to make fbset happy */ + macfb_defined.pixclock = 10000000 / macfb_defined.xres * + 1000 / macfb_defined.yres; + macfb_defined.left_margin = (macfb_defined.xres / 8) & 0xf8; + macfb_defined.hsync_len = (macfb_defined.xres / 8) & 0xf8; + + switch (macfb_defined.bits_per_pixel) { + case 1: + macfb_defined.red.length = macfb_defined.bits_per_pixel; + macfb_defined.green.length = macfb_defined.bits_per_pixel; + macfb_defined.blue.length = macfb_defined.bits_per_pixel; + video_cmap_len = 2; + macfb_fix.visual = FB_VISUAL_MONO01; + break; + case 2: + case 4: + case 8: + macfb_defined.red.length = macfb_defined.bits_per_pixel; + macfb_defined.green.length = macfb_defined.bits_per_pixel; + macfb_defined.blue.length = macfb_defined.bits_per_pixel; + video_cmap_len = 1 << macfb_defined.bits_per_pixel; + macfb_fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + case 16: + macfb_defined.transp.offset = 15; + macfb_defined.transp.length = 1; + macfb_defined.red.offset = 10; + macfb_defined.red.length = 5; + macfb_defined.green.offset = 5; + macfb_defined.green.length = 5; + macfb_defined.blue.offset = 0; + macfb_defined.blue.length = 5; + video_cmap_len = 16; + /* + * Should actually be FB_VISUAL_DIRECTCOLOR, but this + * works too + */ + macfb_fix.visual = FB_VISUAL_TRUECOLOR; + break; + case 24: + case 32: + macfb_defined.red.offset = 16; + macfb_defined.red.length = 8; + macfb_defined.green.offset = 8; + macfb_defined.green.length = 8; + macfb_defined.blue.offset = 0; + macfb_defined.blue.length = 8; + video_cmap_len = 16; + macfb_fix.visual = FB_VISUAL_TRUECOLOR; + break; + default: + pr_err("macfb: unknown or unsupported bit depth: %d\n", + macfb_defined.bits_per_pixel); + err = -EINVAL; + goto fail_unmap; + } + + /* + * We take a wild guess that if the video physical address is + * in nubus slot space, that the nubus card is driving video. + * Penguin really ought to tell us whether we are using internal + * video or not. + * Hopefully we only find one of them. Otherwise our NuBus + * code is really broken :-) + */ + + while ((ndev = nubus_find_type(NUBUS_CAT_DISPLAY, + NUBUS_TYPE_VIDEO, ndev))) + { + unsigned long base = ndev->board->slot_addr; + + if (mac_bi_data.videoaddr < base || + mac_bi_data.videoaddr - base > 0xFFFFFF) + continue; + + video_is_nubus = 1; + slot_addr = (unsigned char *)base; + + switch(ndev->dr_hw) { + case NUBUS_DRHW_APPLE_MDC: + strcpy(macfb_fix.id, "Mac Disp. Card"); + macfb_setpalette = mdc_setpalette; + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + case NUBUS_DRHW_APPLE_TFB: + strcpy(macfb_fix.id, "Toby"); + macfb_setpalette = toby_setpalette; + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + case NUBUS_DRHW_APPLE_JET: + strcpy(macfb_fix.id, "Jet"); + macfb_setpalette = jet_setpalette; + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + default: + strcpy(macfb_fix.id, "Generic NuBus"); + break; + } + } + + /* If it's not a NuBus card, it must be internal video */ + if (!video_is_nubus) + switch (mac_bi_data.id) { + /* + * DAFB Quadras + * Note: these first four have the v7 DAFB, which is + * known to be rather unlike the ones used in the + * other models + */ + case MAC_MODEL_P475: + case MAC_MODEL_P475F: + case MAC_MODEL_P575: + case MAC_MODEL_Q605: + + case MAC_MODEL_Q800: + case MAC_MODEL_Q650: + case MAC_MODEL_Q610: + case MAC_MODEL_C650: + case MAC_MODEL_C610: + case MAC_MODEL_Q700: + case MAC_MODEL_Q900: + case MAC_MODEL_Q950: + strcpy(macfb_fix.id, "DAFB"); + macfb_setpalette = dafb_setpalette; + dafb_cmap_regs = ioremap(DAFB_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + + /* + * LC II uses the V8 framebuffer + */ + case MAC_MODEL_LCII: + strcpy(macfb_fix.id, "V8"); + macfb_setpalette = v8_brazil_setpalette; + v8_brazil_cmap_regs = ioremap(DAC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + + /* + * IIvi, IIvx use the "Brazil" framebuffer (which is + * very much like the V8, it seems, and probably uses + * the same DAC) + */ + case MAC_MODEL_IIVI: + case MAC_MODEL_IIVX: + case MAC_MODEL_P600: + strcpy(macfb_fix.id, "Brazil"); + macfb_setpalette = v8_brazil_setpalette; + v8_brazil_cmap_regs = ioremap(DAC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + + /* + * LC III (and friends) use the Sonora framebuffer + * Incidentally this is also used in the non-AV models + * of the x100 PowerMacs + * These do in fact seem to use the same DAC interface + * as the LC II. + */ + case MAC_MODEL_LCIII: + case MAC_MODEL_P520: + case MAC_MODEL_P550: + case MAC_MODEL_P460: + strcpy(macfb_fix.id, "Sonora"); + macfb_setpalette = v8_brazil_setpalette; + v8_brazil_cmap_regs = ioremap(DAC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + + /* + * IIci and IIsi use the infamous RBV chip + * (the IIsi is just a rebadged and crippled + * IIci in a different case, BTW) + */ + case MAC_MODEL_IICI: + case MAC_MODEL_IISI: + strcpy(macfb_fix.id, "RBV"); + macfb_setpalette = rbv_setpalette; + rbv_cmap_regs = ioremap(DAC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + + /* + * AVs use the Civic framebuffer + */ + case MAC_MODEL_Q840: + case MAC_MODEL_C660: + strcpy(macfb_fix.id, "Civic"); + macfb_setpalette = civic_setpalette; + civic_cmap_regs = ioremap(CIVIC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + + + /* + * Assorted weirdos + * We think this may be like the LC II + */ + case MAC_MODEL_LC: + strcpy(macfb_fix.id, "LC"); + if (vidtest) { + macfb_setpalette = v8_brazil_setpalette; + v8_brazil_cmap_regs = + ioremap(DAC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + } + break; + + /* + * We think this may be like the LC II + */ + case MAC_MODEL_CCL: + strcpy(macfb_fix.id, "Color Classic"); + if (vidtest) { + macfb_setpalette = v8_brazil_setpalette; + v8_brazil_cmap_regs = + ioremap(DAC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + } + break; + + /* + * And we *do* mean "weirdos" + */ + case MAC_MODEL_TV: + strcpy(macfb_fix.id, "Mac TV"); + break; + + /* + * These don't have colour, so no need to worry + */ + case MAC_MODEL_SE30: + case MAC_MODEL_CLII: + strcpy(macfb_fix.id, "Monochrome"); + break; + + /* + * Powerbooks are particularly difficult. Many of + * them have separate framebuffers for external and + * internal video, which is admittedly pretty cool, + * but will be a bit of a headache to support here. + * Also, many of them are grayscale, and we don't + * really support that. + */ + + /* + * Slot 0 ROM says TIM. No external video. B&W. + */ + case MAC_MODEL_PB140: + case MAC_MODEL_PB145: + case MAC_MODEL_PB170: + strcpy(macfb_fix.id, "DDC"); + break; + + /* + * Internal is GSC, External (if present) is ViSC + */ + case MAC_MODEL_PB150: /* no external video */ + case MAC_MODEL_PB160: + case MAC_MODEL_PB165: + case MAC_MODEL_PB180: + case MAC_MODEL_PB210: + case MAC_MODEL_PB230: + strcpy(macfb_fix.id, "GSC"); + break; + + /* + * Internal is TIM, External is ViSC + */ + case MAC_MODEL_PB165C: + case MAC_MODEL_PB180C: + strcpy(macfb_fix.id, "TIM"); + break; + + /* + * Internal is CSC, External is Keystone+Ariel. + */ + case MAC_MODEL_PB190: /* external video is optional */ + case MAC_MODEL_PB520: + case MAC_MODEL_PB250: + case MAC_MODEL_PB270C: + case MAC_MODEL_PB280: + case MAC_MODEL_PB280C: + strcpy(macfb_fix.id, "CSC"); + macfb_setpalette = csc_setpalette; + csc_cmap_regs = ioremap(CSC_BASE, 0x1000); + macfb_defined.activate = FB_ACTIVATE_NOW; + break; + + default: + strcpy(macfb_fix.id, "Unknown"); + break; + } + + fb_info.fbops = &macfb_ops; + fb_info.var = macfb_defined; + fb_info.fix = macfb_fix; + fb_info.pseudo_palette = pseudo_palette; + fb_info.flags = FBINFO_DEFAULT; + + err = fb_alloc_cmap(&fb_info.cmap, video_cmap_len, 0); + if (err) + goto fail_unmap; + + err = register_framebuffer(&fb_info); + if (err) + goto fail_dealloc; + + fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id); + + return 0; + +fail_dealloc: + fb_dealloc_cmap(&fb_info.cmap); +fail_unmap: + iounmap(fb_info.screen_base); + iounmap_macfb(); + return err; +} + +module_init(macfb_init); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/macmodes.c b/drivers/video/fbdev/macmodes.c new file mode 100644 index 000000000000..af86c081d2be --- /dev/null +++ b/drivers/video/fbdev/macmodes.c @@ -0,0 +1,414 @@ +/* + * linux/drivers/video/macmodes.c -- Standard MacOS video modes + * + * Copyright (C) 1998 Geert Uytterhoeven + * + * 2000 - Removal of OpenFirmware dependencies by: + * - Ani Joshi + * - Brad Douglas <brad@neruo.com> + * + * 2001 - Documented with DocBook + * - Brad Douglas <brad@neruo.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/errno.h> +#include <linux/fb.h> +#include <linux/string.h> +#include <linux/module.h> + +#include "macmodes.h" + + /* + * MacOS video mode definitions + * + * Order IS important! If you change these, don't forget to update + * mac_modes[] below! + */ + +#define DEFAULT_MODEDB_INDEX 0 + +static const struct fb_videomode mac_modedb[] = { + { + /* 512x384, 60Hz, Non-Interlaced (15.67 MHz dot clock) */ + "mac2", 60, 512, 384, 63828, 80, 16, 19, 1, 32, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 640x480, 60 Hz, Non-Interlaced (25.175 MHz dotclock) */ + "mac5", 60, 640, 480, 39722, 32, 32, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED + }, { + /* 640x480, 67Hz, Non-Interlaced (30.0 MHz dotclock) */ + "mac6", 67, 640, 480, 33334, 80, 80, 39, 3, 64, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 640x870, 75Hz (portrait), Non-Interlaced (57.28 MHz dot clock) */ + "mac7", 75, 640, 870, 17457, 80, 32, 42, 3, 80, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 800x600, 56 Hz, Non-Interlaced (36.00 MHz dotclock) */ + "mac9", 56, 800, 600, 27778, 112, 40, 22, 1, 72, 2, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 800x600, 60 Hz, Non-Interlaced (40.00 MHz dotclock) */ + "mac10", 60, 800, 600, 25000, 72, 56, 23, 1, 128, 4, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 800x600, 72 Hz, Non-Interlaced (50.00 MHz dotclock) */ + "mac11", 72, 800, 600, 20000, 48, 72, 23, 37, 120, 6, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 800x600, 75 Hz, Non-Interlaced (49.50 MHz dotclock) */ + "mac12", 75, 800, 600, 20203, 144, 32, 21, 1, 80, 3, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 832x624, 75Hz, Non-Interlaced (57.6 MHz dotclock) */ + "mac13", 75, 832, 624, 17362, 208, 48, 39, 1, 64, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 1024x768, 60 Hz, Non-Interlaced (65.00 MHz dotclock) */ + "mac14", 60, 1024, 768, 15385, 144, 40, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED + }, { + /* 1024x768, 72 Hz, Non-Interlaced (75.00 MHz dotclock) */ + "mac15", 72, 1024, 768, 13334, 128, 40, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED + }, { + /* 1024x768, 75 Hz, Non-Interlaced (78.75 MHz dotclock) */ + "mac16", 75, 1024, 768, 12699, 176, 16, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1024x768, 75 Hz, Non-Interlaced (78.75 MHz dotclock) */ + "mac17", 75, 1024, 768, 12699, 160, 32, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1152x870, 75 Hz, Non-Interlaced (100.0 MHz dotclock) */ + "mac18", 75, 1152, 870, 10000, 128, 48, 39, 3, 128, 3, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1280x960, 75 Hz, Non-Interlaced (126.00 MHz dotclock) */ + "mac19", 75, 1280, 960, 7937, 224, 32, 36, 1, 144, 3, + 0, FB_VMODE_NONINTERLACED + }, { + /* 1280x1024, 75 Hz, Non-Interlaced (135.00 MHz dotclock) */ + "mac20", 75, 1280, 1024, 7408, 232, 64, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1152x768, 60 Hz, Titanium PowerBook */ + "mac21", 60, 1152, 768, 15386, 158, 26, 29, 3, 136, 6, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + }, { + /* 1600x1024, 60 Hz, Non-Interlaced (112.27 MHz dotclock) */ + "mac22", 60, 1600, 1024, 8908, 88, 104, 1, 10, 16, 1, + FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED + } + +#if 0 + /* Anyone who has timings for these? */ + { + /* VMODE_512_384_60I: 512x384, 60Hz, Interlaced (NTSC) */ + "mac1", 60, 512, 384, pixclock, left, right, upper, lower, hslen, vslen, + sync, FB_VMODE_INTERLACED + }, { + /* VMODE_640_480_50I: 640x480, 50Hz, Interlaced (PAL) */ + "mac3", 50, 640, 480, pixclock, left, right, upper, lower, hslen, vslen, + sync, FB_VMODE_INTERLACED + }, { + /* VMODE_640_480_60I: 640x480, 60Hz, Interlaced (NTSC) */ + "mac4", 60, 640, 480, pixclock, left, right, upper, lower, hslen, vslen, + sync, FB_VMODE_INTERLACED + }, { + /* VMODE_768_576_50I: 768x576, 50Hz (PAL full frame), Interlaced */ + "mac8", 50, 768, 576, pixclock, left, right, upper, lower, hslen, vslen, + sync, FB_VMODE_INTERLACED + }, +#endif +}; + + + /* + * Mapping between MacOS video mode numbers and video mode definitions + * + * These MUST be ordered in + * - increasing resolution + * - decreasing pixel clock period + */ + +static const struct mode_map { + int vmode; + const struct fb_videomode *mode; +} mac_modes[] = { + /* 512x384 */ + { VMODE_512_384_60, &mac_modedb[0] }, + /* 640x480 */ + { VMODE_640_480_60, &mac_modedb[1] }, + { VMODE_640_480_67, &mac_modedb[2] }, + /* 640x870 */ + { VMODE_640_870_75P, &mac_modedb[3] }, + /* 800x600 */ + { VMODE_800_600_56, &mac_modedb[4] }, + { VMODE_800_600_60, &mac_modedb[5] }, + { VMODE_800_600_75, &mac_modedb[7] }, + { VMODE_800_600_72, &mac_modedb[6] }, + /* 832x624 */ + { VMODE_832_624_75, &mac_modedb[8] }, + /* 1024x768 */ + { VMODE_1024_768_60, &mac_modedb[9] }, + { VMODE_1024_768_70, &mac_modedb[10] }, + { VMODE_1024_768_75V, &mac_modedb[11] }, + { VMODE_1024_768_75, &mac_modedb[12] }, + /* 1152x768 */ + { VMODE_1152_768_60, &mac_modedb[16] }, + /* 1152x870 */ + { VMODE_1152_870_75, &mac_modedb[13] }, + /* 1280x960 */ + { VMODE_1280_960_75, &mac_modedb[14] }, + /* 1280x1024 */ + { VMODE_1280_1024_75, &mac_modedb[15] }, + /* 1600x1024 */ + { VMODE_1600_1024_60, &mac_modedb[17] }, + { -1, NULL } +}; + + + /* + * Mapping between monitor sense values and MacOS video mode numbers + */ + +static const struct monitor_map { + int sense; + int vmode; +} mac_monitors[] = { + { 0x000, VMODE_1280_1024_75 }, /* 21" RGB */ + { 0x114, VMODE_640_870_75P }, /* Portrait Monochrome */ + { 0x221, VMODE_512_384_60 }, /* 12" RGB*/ + { 0x331, VMODE_1280_1024_75 }, /* 21" RGB (Radius) */ + { 0x334, VMODE_1280_1024_75 }, /* 21" mono (Radius) */ + { 0x335, VMODE_1280_1024_75 }, /* 21" mono */ + { 0x40A, VMODE_640_480_60I }, /* NTSC */ + { 0x51E, VMODE_640_870_75P }, /* Portrait RGB */ + { 0x603, VMODE_832_624_75 }, /* 12"-16" multiscan */ + { 0x60b, VMODE_1024_768_70 }, /* 13"-19" multiscan */ + { 0x623, VMODE_1152_870_75 }, /* 13"-21" multiscan */ + { 0x62b, VMODE_640_480_67 }, /* 13"/14" RGB */ + { 0x700, VMODE_640_480_50I }, /* PAL */ + { 0x714, VMODE_640_480_60I }, /* NTSC */ + { 0x717, VMODE_800_600_75 }, /* VGA */ + { 0x72d, VMODE_832_624_75 }, /* 16" RGB (Goldfish) */ + { 0x730, VMODE_768_576_50I }, /* PAL (Alternate) */ + { 0x73a, VMODE_1152_870_75 }, /* 3rd party 19" */ + { 0x73f, VMODE_640_480_67 }, /* no sense lines connected at all */ + { 0xBEEF, VMODE_1600_1024_60 }, /* 22" Apple Cinema Display */ + { -1, VMODE_640_480_60 }, /* catch-all, must be last */ +}; + +/** + * mac_vmode_to_var - converts vmode/cmode pair to var structure + * @vmode: MacOS video mode + * @cmode: MacOS color mode + * @var: frame buffer video mode structure + * + * Converts a MacOS vmode/cmode pair to a frame buffer video + * mode structure. + * + * Returns negative errno on error, or zero for success. + * + */ + +int mac_vmode_to_var(int vmode, int cmode, struct fb_var_screeninfo *var) +{ + const struct fb_videomode *mode = NULL; + const struct mode_map *map; + + for (map = mac_modes; map->vmode != -1; map++) + if (map->vmode == vmode) { + mode = map->mode; + break; + } + if (!mode) + return -EINVAL; + + memset(var, 0, sizeof(struct fb_var_screeninfo)); + switch (cmode) { + case CMODE_8: + var->bits_per_pixel = 8; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + case CMODE_16: + var->bits_per_pixel = 16; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + break; + + case CMODE_32: + var->bits_per_pixel = 32; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + + default: + return -EINVAL; + } + var->xres = mode->xres; + var->yres = mode->yres; + var->xres_virtual = mode->xres; + var->yres_virtual = mode->yres; + var->height = -1; + var->width = -1; + var->pixclock = mode->pixclock; + var->left_margin = mode->left_margin; + var->right_margin = mode->right_margin; + var->upper_margin = mode->upper_margin; + var->lower_margin = mode->lower_margin; + var->hsync_len = mode->hsync_len; + var->vsync_len = mode->vsync_len; + var->sync = mode->sync; + var->vmode = mode->vmode; + return 0; +} +EXPORT_SYMBOL(mac_vmode_to_var); + +/** + * mac_var_to_vmode - convert var structure to MacOS vmode/cmode pair + * @var: frame buffer video mode structure + * @vmode: MacOS video mode + * @cmode: MacOS color mode + * + * Converts a frame buffer video mode structure to a MacOS + * vmode/cmode pair. + * + * Returns negative errno on error, or zero for success. + * + */ + +int mac_var_to_vmode(const struct fb_var_screeninfo *var, int *vmode, + int *cmode) +{ + const struct mode_map *map; + + if (var->bits_per_pixel <= 8) + *cmode = CMODE_8; + else if (var->bits_per_pixel <= 16) + *cmode = CMODE_16; + else if (var->bits_per_pixel <= 32) + *cmode = CMODE_32; + else + return -EINVAL; + + /* + * Find the mac_mode with a matching resolution or failing that, the + * closest larger resolution. Skip modes with a shorter pixel clock period. + */ + for (map = mac_modes; map->vmode != -1; map++) { + const struct fb_videomode *mode = map->mode; + + if (var->xres > mode->xres || var->yres > mode->yres) + continue; + if (var->xres_virtual > mode->xres || var->yres_virtual > mode->yres) + continue; + if (var->pixclock > mode->pixclock) + continue; + if ((var->vmode & FB_VMODE_MASK) != mode->vmode) + continue; + *vmode = map->vmode; + + /* + * Having found a good resolution, find the matching pixel clock + * or failing that, the closest longer pixel clock period. + */ + map++; + while (map->vmode != -1) { + const struct fb_videomode *clk_mode = map->mode; + + if (mode->xres != clk_mode->xres || mode->yres != clk_mode->yres) + break; + if (var->pixclock > mode->pixclock) + break; + if (mode->vmode != clk_mode->vmode) + continue; + *vmode = map->vmode; + map++; + } + return 0; + } + return -EINVAL; +} + +/** + * mac_map_monitor_sense - Convert monitor sense to vmode + * @sense: Macintosh monitor sense number + * + * Converts a Macintosh monitor sense number to a MacOS + * vmode number. + * + * Returns MacOS vmode video mode number. + * + */ + +int mac_map_monitor_sense(int sense) +{ + const struct monitor_map *map; + + for (map = mac_monitors; map->sense != -1; map++) + if (map->sense == sense) + break; + return map->vmode; +} +EXPORT_SYMBOL(mac_map_monitor_sense); + +/** + * mac_find_mode - find a video mode + * @var: frame buffer user defined part of display + * @info: frame buffer info structure + * @mode_option: video mode name (see mac_modedb[]) + * @default_bpp: default color depth in bits per pixel + * + * Finds a suitable video mode. Tries to set mode specified + * by @mode_option. If the name of the wanted mode begins with + * 'mac', the Mac video mode database will be used, otherwise it + * will fall back to the standard video mode database. + * + * Note: Function marked as __init and can only be used during + * system boot. + * + * Returns error code from fb_find_mode (see fb_find_mode + * function). + * + */ + +int mac_find_mode(struct fb_var_screeninfo *var, struct fb_info *info, + const char *mode_option, unsigned int default_bpp) +{ + const struct fb_videomode *db = NULL; + unsigned int dbsize = 0; + + if (mode_option && !strncmp(mode_option, "mac", 3)) { + mode_option += 3; + db = mac_modedb; + dbsize = ARRAY_SIZE(mac_modedb); + } + return fb_find_mode(var, info, mode_option, db, dbsize, + &mac_modedb[DEFAULT_MODEDB_INDEX], default_bpp); +} +EXPORT_SYMBOL(mac_find_mode); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/macmodes.h b/drivers/video/fbdev/macmodes.h new file mode 100644 index 000000000000..b86ba08aac9e --- /dev/null +++ b/drivers/video/fbdev/macmodes.h @@ -0,0 +1,71 @@ +/* + * linux/drivers/video/macmodes.h -- Standard MacOS video modes + * + * Copyright (C) 1998 Geert Uytterhoeven + * + * 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. + */ + +#ifndef _VIDEO_MACMODES_H +#define _VIDEO_MACMODES_H + + /* + * Video mode values. + * These are supposed to be the same as the values that Apple uses in + * MacOS. + */ + +#define VMODE_NVRAM 0 +#define VMODE_512_384_60I 1 /* 512x384, 60Hz interlaced (NTSC) */ +#define VMODE_512_384_60 2 /* 512x384, 60Hz */ +#define VMODE_640_480_50I 3 /* 640x480, 50Hz interlaced (PAL) */ +#define VMODE_640_480_60I 4 /* 640x480, 60Hz interlaced (NTSC) */ +#define VMODE_640_480_60 5 /* 640x480, 60Hz (VGA) */ +#define VMODE_640_480_67 6 /* 640x480, 67Hz */ +#define VMODE_640_870_75P 7 /* 640x870, 75Hz (portrait) */ +#define VMODE_768_576_50I 8 /* 768x576, 50Hz (PAL full frame) */ +#define VMODE_800_600_56 9 /* 800x600, 56Hz */ +#define VMODE_800_600_60 10 /* 800x600, 60Hz */ +#define VMODE_800_600_72 11 /* 800x600, 72Hz */ +#define VMODE_800_600_75 12 /* 800x600, 75Hz */ +#define VMODE_832_624_75 13 /* 832x624, 75Hz */ +#define VMODE_1024_768_60 14 /* 1024x768, 60Hz */ +#define VMODE_1024_768_70 15 /* 1024x768, 70Hz (or 72Hz?) */ +#define VMODE_1024_768_75V 16 /* 1024x768, 75Hz (VESA) */ +#define VMODE_1024_768_75 17 /* 1024x768, 75Hz */ +#define VMODE_1152_870_75 18 /* 1152x870, 75Hz */ +#define VMODE_1280_960_75 19 /* 1280x960, 75Hz */ +#define VMODE_1280_1024_75 20 /* 1280x1024, 75Hz */ +#define VMODE_1152_768_60 21 /* 1152x768, 60Hz Titanium PowerBook */ +#define VMODE_1600_1024_60 22 /* 1600x1024, 60Hz 22" Cinema Display */ +#define VMODE_MAX 22 +#define VMODE_CHOOSE 99 + +#define CMODE_NVRAM -1 +#define CMODE_CHOOSE -2 +#define CMODE_8 0 /* 8 bits/pixel */ +#define CMODE_16 1 /* 16 (actually 15) bits/pixel */ +#define CMODE_32 2 /* 32 (actually 24) bits/pixel */ + + +extern int mac_vmode_to_var(int vmode, int cmode, + struct fb_var_screeninfo *var); +extern int mac_var_to_vmode(const struct fb_var_screeninfo *var, int *vmode, + int *cmode); +extern int mac_map_monitor_sense(int sense); +extern int mac_find_mode(struct fb_var_screeninfo *var, + struct fb_info *info, + const char *mode_option, + unsigned int default_bpp); + + + /* + * Addresses in NVRAM where video mode and pixel size are stored. + */ + +#define NV_VMODE 0x140f +#define NV_CMODE 0x1410 + +#endif /* _VIDEO_MACMODES_H */ diff --git a/drivers/video/fbdev/matrox/Makefile b/drivers/video/fbdev/matrox/Makefile new file mode 100644 index 000000000000..f9c00ebe2530 --- /dev/null +++ b/drivers/video/fbdev/matrox/Makefile @@ -0,0 +1,11 @@ +# Makefile for the Linux video drivers. +# 5 Aug 1999, James Simmons, <mailto:jsimmons@edgeglobal.com> +# Rewritten to use lists instead of if-statements. + +# Each configuration option enables a list of files. + +my-obj-$(CONFIG_FB_MATROX_G) += g450_pll.o matroxfb_g450.o matroxfb_crtc2.o + +obj-$(CONFIG_FB_MATROX) += matroxfb_base.o matroxfb_accel.o matroxfb_DAC1064.o matroxfb_Ti3026.o matroxfb_misc.o $(my-obj-y) +obj-$(CONFIG_FB_MATROX_I2C) += i2c-matroxfb.o +obj-$(CONFIG_FB_MATROX_MAVEN) += matroxfb_maven.o matroxfb_crtc2.o diff --git a/drivers/video/fbdev/matrox/g450_pll.c b/drivers/video/fbdev/matrox/g450_pll.c new file mode 100644 index 000000000000..c15f8a57498e --- /dev/null +++ b/drivers/video/fbdev/matrox/g450_pll.c @@ -0,0 +1,539 @@ +/* + * + * Hardware accelerated Matrox PCI cards - G450/G550 PLL control. + * + * (c) 2001-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.64 2002/06/10 + * + * 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 "g450_pll.h" +#include "matroxfb_DAC1064.h" + +static inline unsigned int g450_vco2f(unsigned char p, unsigned int fvco) { + return (p & 0x40) ? fvco : fvco >> ((p & 3) + 1); +} + +static inline unsigned int g450_f2vco(unsigned char p, unsigned int fin) { + return (p & 0x40) ? fin : fin << ((p & 3) + 1); +} + +static unsigned int g450_mnp2vco(const struct matrox_fb_info *minfo, + unsigned int mnp) +{ + unsigned int m, n; + + m = ((mnp >> 16) & 0x0FF) + 1; + n = ((mnp >> 7) & 0x1FE) + 4; + return (minfo->features.pll.ref_freq * n + (m >> 1)) / m; +} + +unsigned int g450_mnp2f(const struct matrox_fb_info *minfo, unsigned int mnp) +{ + return g450_vco2f(mnp, g450_mnp2vco(minfo, mnp)); +} + +static inline unsigned int pll_freq_delta(unsigned int f1, unsigned int f2) { + if (f2 < f1) { + f2 = f1 - f2; + } else { + f2 = f2 - f1; + } + return f2; +} + +#define NO_MORE_MNP 0x01FFFFFF +#define G450_MNP_FREQBITS (0xFFFFFF43) /* do not mask high byte so we'll catch NO_MORE_MNP */ + +static unsigned int g450_nextpll(const struct matrox_fb_info *minfo, + const struct matrox_pll_limits *pi, + unsigned int *fvco, unsigned int mnp) +{ + unsigned int m, n, p; + unsigned int tvco = *fvco; + + m = (mnp >> 16) & 0xFF; + p = mnp & 0xFF; + + do { + if (m == 0 || m == 0xFF) { + if (m == 0) { + if (p & 0x40) { + return NO_MORE_MNP; + } + if (p & 3) { + p--; + } else { + p = 0x40; + } + tvco >>= 1; + if (tvco < pi->vcomin) { + return NO_MORE_MNP; + } + *fvco = tvco; + } + + p &= 0x43; + if (tvco < 550000) { +/* p |= 0x00; */ + } else if (tvco < 700000) { + p |= 0x08; + } else if (tvco < 1000000) { + p |= 0x10; + } else if (tvco < 1150000) { + p |= 0x18; + } else { + p |= 0x20; + } + m = 9; + } else { + m--; + } + n = ((tvco * (m+1) + minfo->features.pll.ref_freq) / (minfo->features.pll.ref_freq * 2)) - 2; + } while (n < 0x03 || n > 0x7A); + return (m << 16) | (n << 8) | p; +} + +static unsigned int g450_firstpll(const struct matrox_fb_info *minfo, + const struct matrox_pll_limits *pi, + unsigned int *vco, unsigned int fout) +{ + unsigned int p; + unsigned int vcomax; + + vcomax = pi->vcomax; + if (fout > (vcomax / 2)) { + if (fout > vcomax) { + *vco = vcomax; + } else { + *vco = fout; + } + p = 0x40; + } else { + unsigned int tvco; + + p = 3; + tvco = g450_f2vco(p, fout); + while (p && (tvco > vcomax)) { + p--; + tvco >>= 1; + } + if (tvco < pi->vcomin) { + tvco = pi->vcomin; + } + *vco = tvco; + } + return g450_nextpll(minfo, pi, vco, 0xFF0000 | p); +} + +static inline unsigned int g450_setpll(const struct matrox_fb_info *minfo, + unsigned int mnp, unsigned int pll) +{ + switch (pll) { + case M_PIXEL_PLL_A: + matroxfb_DAC_out(minfo, M1064_XPIXPLLAM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XPIXPLLAN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XPIXPLLAP, mnp); + return M1064_XPIXPLLSTAT; + + case M_PIXEL_PLL_B: + matroxfb_DAC_out(minfo, M1064_XPIXPLLBM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XPIXPLLBN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XPIXPLLBP, mnp); + return M1064_XPIXPLLSTAT; + + case M_PIXEL_PLL_C: + matroxfb_DAC_out(minfo, M1064_XPIXPLLCM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XPIXPLLCN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XPIXPLLCP, mnp); + return M1064_XPIXPLLSTAT; + + case M_SYSTEM_PLL: + matroxfb_DAC_out(minfo, DAC1064_XSYSPLLM, mnp >> 16); + matroxfb_DAC_out(minfo, DAC1064_XSYSPLLN, mnp >> 8); + matroxfb_DAC_out(minfo, DAC1064_XSYSPLLP, mnp); + return DAC1064_XSYSPLLSTAT; + + case M_VIDEO_PLL: + matroxfb_DAC_out(minfo, M1064_XVIDPLLM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XVIDPLLN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XVIDPLLP, mnp); + return M1064_XVIDPLLSTAT; + } + return 0; +} + +static inline unsigned int g450_cmppll(const struct matrox_fb_info *minfo, + unsigned int mnp, unsigned int pll) +{ + unsigned char m = mnp >> 16; + unsigned char n = mnp >> 8; + unsigned char p = mnp; + + switch (pll) { + case M_PIXEL_PLL_A: + return (matroxfb_DAC_in(minfo, M1064_XPIXPLLAM) != m || + matroxfb_DAC_in(minfo, M1064_XPIXPLLAN) != n || + matroxfb_DAC_in(minfo, M1064_XPIXPLLAP) != p); + + case M_PIXEL_PLL_B: + return (matroxfb_DAC_in(minfo, M1064_XPIXPLLBM) != m || + matroxfb_DAC_in(minfo, M1064_XPIXPLLBN) != n || + matroxfb_DAC_in(minfo, M1064_XPIXPLLBP) != p); + + case M_PIXEL_PLL_C: + return (matroxfb_DAC_in(minfo, M1064_XPIXPLLCM) != m || + matroxfb_DAC_in(minfo, M1064_XPIXPLLCN) != n || + matroxfb_DAC_in(minfo, M1064_XPIXPLLCP) != p); + + case M_SYSTEM_PLL: + return (matroxfb_DAC_in(minfo, DAC1064_XSYSPLLM) != m || + matroxfb_DAC_in(minfo, DAC1064_XSYSPLLN) != n || + matroxfb_DAC_in(minfo, DAC1064_XSYSPLLP) != p); + + case M_VIDEO_PLL: + return (matroxfb_DAC_in(minfo, M1064_XVIDPLLM) != m || + matroxfb_DAC_in(minfo, M1064_XVIDPLLN) != n || + matroxfb_DAC_in(minfo, M1064_XVIDPLLP) != p); + } + return 1; +} + +static inline int g450_isplllocked(const struct matrox_fb_info *minfo, + unsigned int regidx) +{ + unsigned int j; + + for (j = 0; j < 1000; j++) { + if (matroxfb_DAC_in(minfo, regidx) & 0x40) { + unsigned int r = 0; + int i; + + for (i = 0; i < 100; i++) { + r += matroxfb_DAC_in(minfo, regidx) & 0x40; + } + return r >= (90 * 0x40); + } + /* udelay(1)... but DAC_in is much slower... */ + } + return 0; +} + +static int g450_testpll(const struct matrox_fb_info *minfo, unsigned int mnp, + unsigned int pll) +{ + return g450_isplllocked(minfo, g450_setpll(minfo, mnp, pll)); +} + +static void updatehwstate_clk(struct matrox_hw_state* hw, unsigned int mnp, unsigned int pll) { + switch (pll) { + case M_SYSTEM_PLL: + hw->DACclk[3] = mnp >> 16; + hw->DACclk[4] = mnp >> 8; + hw->DACclk[5] = mnp; + break; + } +} + +void matroxfb_g450_setpll_cond(struct matrox_fb_info *minfo, unsigned int mnp, + unsigned int pll) +{ + if (g450_cmppll(minfo, mnp, pll)) { + g450_setpll(minfo, mnp, pll); + } +} + +static inline unsigned int g450_findworkingpll(struct matrox_fb_info *minfo, + unsigned int pll, + unsigned int *mnparray, + unsigned int mnpcount) +{ + unsigned int found = 0; + unsigned int idx; + unsigned int mnpfound = mnparray[0]; + + for (idx = 0; idx < mnpcount; idx++) { + unsigned int sarray[3]; + unsigned int *sptr; + { + unsigned int mnp; + + sptr = sarray; + mnp = mnparray[idx]; + if (mnp & 0x38) { + *sptr++ = mnp - 8; + } + if ((mnp & 0x38) != 0x38) { + *sptr++ = mnp + 8; + } + *sptr = mnp; + } + while (sptr >= sarray) { + unsigned int mnp = *sptr--; + + if (g450_testpll(minfo, mnp - 0x0300, pll) && + g450_testpll(minfo, mnp + 0x0300, pll) && + g450_testpll(minfo, mnp - 0x0200, pll) && + g450_testpll(minfo, mnp + 0x0200, pll) && + g450_testpll(minfo, mnp - 0x0100, pll) && + g450_testpll(minfo, mnp + 0x0100, pll)) { + if (g450_testpll(minfo, mnp, pll)) { + return mnp; + } + } else if (!found && g450_testpll(minfo, mnp, pll)) { + mnpfound = mnp; + found = 1; + } + } + } + g450_setpll(minfo, mnpfound, pll); + return mnpfound; +} + +static void g450_addcache(struct matrox_pll_cache* ci, unsigned int mnp_key, unsigned int mnp_value) { + if (++ci->valid > ARRAY_SIZE(ci->data)) { + ci->valid = ARRAY_SIZE(ci->data); + } + memmove(ci->data + 1, ci->data, (ci->valid - 1) * sizeof(*ci->data)); + ci->data[0].mnp_key = mnp_key & G450_MNP_FREQBITS; + ci->data[0].mnp_value = mnp_value; +} + +static int g450_checkcache(struct matrox_fb_info *minfo, + struct matrox_pll_cache *ci, unsigned int mnp_key) +{ + unsigned int i; + + mnp_key &= G450_MNP_FREQBITS; + for (i = 0; i < ci->valid; i++) { + if (ci->data[i].mnp_key == mnp_key) { + unsigned int mnp; + + mnp = ci->data[i].mnp_value; + if (i) { + memmove(ci->data + 1, ci->data, i * sizeof(*ci->data)); + ci->data[0].mnp_key = mnp_key; + ci->data[0].mnp_value = mnp; + } + return mnp; + } + } + return NO_MORE_MNP; +} + +static int __g450_setclk(struct matrox_fb_info *minfo, unsigned int fout, + unsigned int pll, unsigned int *mnparray, + unsigned int *deltaarray) +{ + unsigned int mnpcount; + unsigned int pixel_vco; + const struct matrox_pll_limits* pi; + struct matrox_pll_cache* ci; + + pixel_vco = 0; + switch (pll) { + case M_PIXEL_PLL_A: + case M_PIXEL_PLL_B: + case M_PIXEL_PLL_C: + { + u_int8_t tmp, xpwrctrl; + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + + xpwrctrl = matroxfb_DAC_in(minfo, M1064_XPWRCTRL); + matroxfb_DAC_out(minfo, M1064_XPWRCTRL, xpwrctrl & ~M1064_XPWRCTRL_PANELPDN); + mga_outb(M_SEQ_INDEX, M_SEQ1); + mga_outb(M_SEQ_DATA, mga_inb(M_SEQ_DATA) | M_SEQ1_SCROFF); + tmp = matroxfb_DAC_in(minfo, M1064_XPIXCLKCTRL); + tmp |= M1064_XPIXCLKCTRL_DIS; + if (!(tmp & M1064_XPIXCLKCTRL_PLL_UP)) { + tmp |= M1064_XPIXCLKCTRL_PLL_UP; + } + matroxfb_DAC_out(minfo, M1064_XPIXCLKCTRL, tmp); + /* DVI PLL preferred for frequencies up to + panel link max, standard PLL otherwise */ + if (fout >= minfo->max_pixel_clock_panellink) + tmp = 0; + else tmp = + M1064_XDVICLKCTRL_DVIDATAPATHSEL | + M1064_XDVICLKCTRL_C1DVICLKSEL | + M1064_XDVICLKCTRL_C1DVICLKEN | + M1064_XDVICLKCTRL_DVILOOPCTL | + M1064_XDVICLKCTRL_P1LOOPBWDTCTL; + /* Setting this breaks PC systems so don't do it */ + /* matroxfb_DAC_out(minfo, M1064_XDVICLKCTRL, tmp); */ + matroxfb_DAC_out(minfo, M1064_XPWRCTRL, + xpwrctrl); + + matroxfb_DAC_unlock_irqrestore(flags); + } + { + u_int8_t misc; + + misc = mga_inb(M_MISC_REG_READ) & ~0x0C; + switch (pll) { + case M_PIXEL_PLL_A: + break; + case M_PIXEL_PLL_B: + misc |= 0x04; + break; + default: + misc |= 0x0C; + break; + } + mga_outb(M_MISC_REG, misc); + } + pi = &minfo->limits.pixel; + ci = &minfo->cache.pixel; + break; + case M_SYSTEM_PLL: + { + u_int32_t opt; + + pci_read_config_dword(minfo->pcidev, PCI_OPTION_REG, &opt); + if (!(opt & 0x20)) { + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, opt | 0x20); + } + } + pi = &minfo->limits.system; + ci = &minfo->cache.system; + break; + case M_VIDEO_PLL: + { + u_int8_t tmp; + unsigned int mnp; + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + tmp = matroxfb_DAC_in(minfo, M1064_XPWRCTRL); + if (!(tmp & 2)) { + matroxfb_DAC_out(minfo, M1064_XPWRCTRL, tmp | 2); + } + + mnp = matroxfb_DAC_in(minfo, M1064_XPIXPLLCM) << 16; + mnp |= matroxfb_DAC_in(minfo, M1064_XPIXPLLCN) << 8; + pixel_vco = g450_mnp2vco(minfo, mnp); + matroxfb_DAC_unlock_irqrestore(flags); + } + pi = &minfo->limits.video; + ci = &minfo->cache.video; + break; + default: + return -EINVAL; + } + + mnpcount = 0; + { + unsigned int mnp; + unsigned int xvco; + + for (mnp = g450_firstpll(minfo, pi, &xvco, fout); mnp != NO_MORE_MNP; mnp = g450_nextpll(minfo, pi, &xvco, mnp)) { + unsigned int idx; + unsigned int vco; + unsigned int delta; + + vco = g450_mnp2vco(minfo, mnp); +#if 0 + if (pll == M_VIDEO_PLL) { + unsigned int big, small; + + if (vco < pixel_vco) { + small = vco; + big = pixel_vco; + } else { + small = pixel_vco; + big = vco; + } + while (big > small) { + big >>= 1; + } + if (big == small) { + continue; + } + } +#endif + delta = pll_freq_delta(fout, g450_vco2f(mnp, vco)); + for (idx = mnpcount; idx > 0; idx--) { + /* == is important; due to nextpll algorithm we get + sorted equally good frequencies from lower VCO + frequency to higher - with <= lowest wins, while + with < highest one wins */ + if (delta <= deltaarray[idx-1]) { + /* all else being equal except VCO, + * choose VCO not near (within 1/16th or so) VCOmin + * (freqs near VCOmin aren't as stable) + */ + if (delta == deltaarray[idx-1] + && vco != g450_mnp2vco(minfo, mnparray[idx-1]) + && vco < (pi->vcomin * 17 / 16)) { + break; + } + mnparray[idx] = mnparray[idx-1]; + deltaarray[idx] = deltaarray[idx-1]; + } else { + break; + } + } + mnparray[idx] = mnp; + deltaarray[idx] = delta; + mnpcount++; + } + } + /* VideoPLL and PixelPLL matched: do nothing... In all other cases we should get at least one frequency */ + if (!mnpcount) { + return -EBUSY; + } + { + unsigned long flags; + unsigned int mnp; + + matroxfb_DAC_lock_irqsave(flags); + mnp = g450_checkcache(minfo, ci, mnparray[0]); + if (mnp != NO_MORE_MNP) { + matroxfb_g450_setpll_cond(minfo, mnp, pll); + } else { + mnp = g450_findworkingpll(minfo, pll, mnparray, mnpcount); + g450_addcache(ci, mnparray[0], mnp); + } + updatehwstate_clk(&minfo->hw, mnp, pll); + matroxfb_DAC_unlock_irqrestore(flags); + return mnp; + } +} + +/* It must be greater than number of possible PLL values. + * Currently there is 5(p) * 10(m) = 50 possible values. */ +#define MNP_TABLE_SIZE 64 + +int matroxfb_g450_setclk(struct matrox_fb_info *minfo, unsigned int fout, + unsigned int pll) +{ + unsigned int* arr; + + arr = kmalloc(sizeof(*arr) * MNP_TABLE_SIZE * 2, GFP_KERNEL); + if (arr) { + int r; + + r = __g450_setclk(minfo, fout, pll, arr, arr + MNP_TABLE_SIZE); + kfree(arr); + return r; + } + return -ENOMEM; +} + +EXPORT_SYMBOL(matroxfb_g450_setclk); +EXPORT_SYMBOL(g450_mnp2f); +EXPORT_SYMBOL(matroxfb_g450_setpll_cond); + +MODULE_AUTHOR("(c) 2001-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G450/G550 PLL driver"); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/g450_pll.h b/drivers/video/fbdev/matrox/g450_pll.h new file mode 100644 index 000000000000..aac615d18440 --- /dev/null +++ b/drivers/video/fbdev/matrox/g450_pll.h @@ -0,0 +1,12 @@ +#ifndef __G450_PLL_H__ +#define __G450_PLL_H__ + +#include "matroxfb_base.h" + +int matroxfb_g450_setclk(struct matrox_fb_info *minfo, unsigned int fout, + unsigned int pll); +unsigned int g450_mnp2f(const struct matrox_fb_info *minfo, unsigned int mnp); +void matroxfb_g450_setpll_cond(struct matrox_fb_info *minfo, unsigned int mnp, + unsigned int pll); + +#endif /* __G450_PLL_H__ */ diff --git a/drivers/video/fbdev/matrox/i2c-matroxfb.c b/drivers/video/fbdev/matrox/i2c-matroxfb.c new file mode 100644 index 000000000000..0fb280ead3dc --- /dev/null +++ b/drivers/video/fbdev/matrox/i2c-matroxfb.c @@ -0,0 +1,238 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Version: 1.64 2002/06/10 + * + * See matroxfb_base.c for contributors. + * + */ + +#include "matroxfb_base.h" +#include "matroxfb_maven.h" +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/i2c-algo-bit.h> + +/* MGA-TVO I2C for G200, G400 */ +#define MAT_CLK 0x20 +#define MAT_DATA 0x10 +/* primary head DDC for Mystique(?), G100, G200, G400 */ +#define DDC1_CLK 0x08 +#define DDC1_DATA 0x02 +/* primary head DDC for Millennium, Millennium II */ +#define DDC1B_CLK 0x10 +#define DDC1B_DATA 0x04 +/* secondary head DDC for G400 */ +#define DDC2_CLK 0x04 +#define DDC2_DATA 0x01 + +/******************************************************/ + +struct matroxfb_dh_maven_info { + struct i2c_bit_adapter maven; + struct i2c_bit_adapter ddc1; + struct i2c_bit_adapter ddc2; +}; + +static int matroxfb_read_gpio(struct matrox_fb_info* minfo) { + unsigned long flags; + int v; + + matroxfb_DAC_lock_irqsave(flags); + v = matroxfb_DAC_in(minfo, DAC_XGENIODATA); + matroxfb_DAC_unlock_irqrestore(flags); + return v; +} + +static void matroxfb_set_gpio(struct matrox_fb_info* minfo, int mask, int val) { + unsigned long flags; + int v; + + matroxfb_DAC_lock_irqsave(flags); + v = (matroxfb_DAC_in(minfo, DAC_XGENIOCTRL) & mask) | val; + matroxfb_DAC_out(minfo, DAC_XGENIOCTRL, v); + /* We must reset GENIODATA very often... XFree plays with this register */ + matroxfb_DAC_out(minfo, DAC_XGENIODATA, 0x00); + matroxfb_DAC_unlock_irqrestore(flags); +} + +/* software I2C functions */ +static inline void matroxfb_i2c_set(struct matrox_fb_info* minfo, int mask, int state) { + if (state) + state = 0; + else + state = mask; + matroxfb_set_gpio(minfo, ~mask, state); +} + +static void matroxfb_gpio_setsda(void* data, int state) { + struct i2c_bit_adapter* b = data; + matroxfb_i2c_set(b->minfo, b->mask.data, state); +} + +static void matroxfb_gpio_setscl(void* data, int state) { + struct i2c_bit_adapter* b = data; + matroxfb_i2c_set(b->minfo, b->mask.clock, state); +} + +static int matroxfb_gpio_getsda(void* data) { + struct i2c_bit_adapter* b = data; + return (matroxfb_read_gpio(b->minfo) & b->mask.data) ? 1 : 0; +} + +static int matroxfb_gpio_getscl(void* data) { + struct i2c_bit_adapter* b = data; + return (matroxfb_read_gpio(b->minfo) & b->mask.clock) ? 1 : 0; +} + +static const struct i2c_algo_bit_data matrox_i2c_algo_template = +{ + .setsda = matroxfb_gpio_setsda, + .setscl = matroxfb_gpio_setscl, + .getsda = matroxfb_gpio_getsda, + .getscl = matroxfb_gpio_getscl, + .udelay = 10, + .timeout = 100, +}; + +static int i2c_bus_reg(struct i2c_bit_adapter* b, struct matrox_fb_info* minfo, + unsigned int data, unsigned int clock, const char *name, + int class) +{ + int err; + + b->minfo = minfo; + b->mask.data = data; + b->mask.clock = clock; + b->adapter.owner = THIS_MODULE; + snprintf(b->adapter.name, sizeof(b->adapter.name), name, + minfo->fbcon.node); + i2c_set_adapdata(&b->adapter, b); + b->adapter.class = class; + b->adapter.algo_data = &b->bac; + b->adapter.dev.parent = &minfo->pcidev->dev; + b->bac = matrox_i2c_algo_template; + b->bac.data = b; + err = i2c_bit_add_bus(&b->adapter); + b->initialized = !err; + return err; +} + +static void i2c_bit_bus_del(struct i2c_bit_adapter* b) { + if (b->initialized) { + i2c_del_adapter(&b->adapter); + b->initialized = 0; + } +} + +static inline void i2c_maven_done(struct matroxfb_dh_maven_info* minfo2) { + i2c_bit_bus_del(&minfo2->maven); +} + +static inline void i2c_ddc1_done(struct matroxfb_dh_maven_info* minfo2) { + i2c_bit_bus_del(&minfo2->ddc1); +} + +static inline void i2c_ddc2_done(struct matroxfb_dh_maven_info* minfo2) { + i2c_bit_bus_del(&minfo2->ddc2); +} + +static void* i2c_matroxfb_probe(struct matrox_fb_info* minfo) { + int err; + unsigned long flags; + struct matroxfb_dh_maven_info* m2info; + + m2info = kzalloc(sizeof(*m2info), GFP_KERNEL); + if (!m2info) + return NULL; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, DAC_XGENIODATA, 0xFF); + matroxfb_DAC_out(minfo, DAC_XGENIOCTRL, 0x00); + matroxfb_DAC_unlock_irqrestore(flags); + + switch (minfo->chip) { + case MGA_2064: + case MGA_2164: + err = i2c_bus_reg(&m2info->ddc1, minfo, + DDC1B_DATA, DDC1B_CLK, + "DDC:fb%u #0", I2C_CLASS_DDC); + break; + default: + err = i2c_bus_reg(&m2info->ddc1, minfo, + DDC1_DATA, DDC1_CLK, + "DDC:fb%u #0", I2C_CLASS_DDC); + break; + } + if (err) + goto fail_ddc1; + if (minfo->devflags.dualhead) { + err = i2c_bus_reg(&m2info->ddc2, minfo, + DDC2_DATA, DDC2_CLK, + "DDC:fb%u #1", I2C_CLASS_DDC); + if (err == -ENODEV) { + printk(KERN_INFO "i2c-matroxfb: VGA->TV plug detected, DDC unavailable.\n"); + } else if (err) + printk(KERN_INFO "i2c-matroxfb: Could not register secondary output i2c bus. Continuing anyway.\n"); + /* Register maven bus even on G450/G550 */ + err = i2c_bus_reg(&m2info->maven, minfo, + MAT_DATA, MAT_CLK, "MAVEN:fb%u", 0); + if (err) + printk(KERN_INFO "i2c-matroxfb: Could not register Maven i2c bus. Continuing anyway.\n"); + else { + struct i2c_board_info maven_info = { + I2C_BOARD_INFO("maven", 0x1b), + }; + unsigned short const addr_list[2] = { + 0x1b, I2C_CLIENT_END + }; + + i2c_new_probed_device(&m2info->maven.adapter, + &maven_info, addr_list, NULL); + } + } + return m2info; +fail_ddc1:; + kfree(m2info); + printk(KERN_ERR "i2c-matroxfb: Could not register primary adapter DDC bus.\n"); + return NULL; +} + +static void i2c_matroxfb_remove(struct matrox_fb_info* minfo, void* data) { + struct matroxfb_dh_maven_info* m2info = data; + + i2c_maven_done(m2info); + i2c_ddc2_done(m2info); + i2c_ddc1_done(m2info); + kfree(m2info); +} + +static struct matroxfb_driver i2c_matroxfb = { + .node = LIST_HEAD_INIT(i2c_matroxfb.node), + .name = "i2c-matroxfb", + .probe = i2c_matroxfb_probe, + .remove = i2c_matroxfb_remove, +}; + +static int __init i2c_matroxfb_init(void) { + if (matroxfb_register_driver(&i2c_matroxfb)) { + printk(KERN_ERR "i2c-matroxfb: failed to register driver\n"); + return -ENXIO; + } + return 0; +} + +static void __exit i2c_matroxfb_exit(void) { + matroxfb_unregister_driver(&i2c_matroxfb); +} + +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Support module providing I2C buses present on Matrox videocards"); + +module_init(i2c_matroxfb_init); +module_exit(i2c_matroxfb_exit); +/* no __setup required */ +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_DAC1064.c b/drivers/video/fbdev/matrox/matroxfb_DAC1064.c new file mode 100644 index 000000000000..a01147fdf270 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_DAC1064.c @@ -0,0 +1,1107 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * See matroxfb_base.c for contributors. + * + */ + + +#include "matroxfb_DAC1064.h" +#include "matroxfb_misc.h" +#include "matroxfb_accel.h" +#include "g450_pll.h" +#include <linux/matroxfb.h> + +#ifdef NEED_DAC1064 +#define outDAC1064 matroxfb_DAC_out +#define inDAC1064 matroxfb_DAC_in + +#define DAC1064_OPT_SCLK_PCI 0x00 +#define DAC1064_OPT_SCLK_PLL 0x01 +#define DAC1064_OPT_SCLK_EXT 0x02 +#define DAC1064_OPT_SCLK_MASK 0x03 +#define DAC1064_OPT_GDIV1 0x04 /* maybe it is GDIV2 on G100 ?! */ +#define DAC1064_OPT_GDIV3 0x00 +#define DAC1064_OPT_MDIV1 0x08 +#define DAC1064_OPT_MDIV2 0x00 +#define DAC1064_OPT_RESERVED 0x10 + +static void DAC1064_calcclock(const struct matrox_fb_info *minfo, + unsigned int freq, unsigned int fmax, + unsigned int *in, unsigned int *feed, + unsigned int *post) +{ + unsigned int fvco; + unsigned int p; + + DBG(__func__) + + /* only for devices older than G450 */ + + fvco = PLL_calcclock(minfo, freq, fmax, in, feed, &p); + + p = (1 << p) - 1; + if (fvco <= 100000) + ; + else if (fvco <= 140000) + p |= 0x08; + else if (fvco <= 180000) + p |= 0x10; + else + p |= 0x18; + *post = p; +} + +/* they must be in POS order */ +static const unsigned char MGA1064_DAC_regs[] = { + M1064_XCURADDL, M1064_XCURADDH, M1064_XCURCTRL, + M1064_XCURCOL0RED, M1064_XCURCOL0GREEN, M1064_XCURCOL0BLUE, + M1064_XCURCOL1RED, M1064_XCURCOL1GREEN, M1064_XCURCOL1BLUE, + M1064_XCURCOL2RED, M1064_XCURCOL2GREEN, M1064_XCURCOL2BLUE, + DAC1064_XVREFCTRL, M1064_XMULCTRL, M1064_XPIXCLKCTRL, M1064_XGENCTRL, + M1064_XMISCCTRL, + M1064_XGENIOCTRL, M1064_XGENIODATA, M1064_XZOOMCTRL, M1064_XSENSETEST, + M1064_XCRCBITSEL, + M1064_XCOLKEYMASKL, M1064_XCOLKEYMASKH, M1064_XCOLKEYL, M1064_XCOLKEYH }; + +static const unsigned char MGA1064_DAC[] = { + 0x00, 0x00, M1064_XCURCTRL_DIS, + 0x00, 0x00, 0x00, /* black */ + 0xFF, 0xFF, 0xFF, /* white */ + 0xFF, 0x00, 0x00, /* red */ + 0x00, 0, + M1064_XPIXCLKCTRL_PLL_UP | M1064_XPIXCLKCTRL_EN | M1064_XPIXCLKCTRL_SRC_PLL, + M1064_XGENCTRL_VS_0 | M1064_XGENCTRL_ALPHA_DIS | M1064_XGENCTRL_BLACK_0IRE | M1064_XGENCTRL_NO_SYNC_ON_GREEN, + M1064_XMISCCTRL_DAC_8BIT, + 0x00, 0x00, M1064_XZOOMCTRL_1, M1064_XSENSETEST_BCOMP | M1064_XSENSETEST_GCOMP | M1064_XSENSETEST_RCOMP | M1064_XSENSETEST_PDOWN, + 0x00, + 0x00, 0x00, 0xFF, 0xFF}; + +static void DAC1064_setpclk(struct matrox_fb_info *minfo, unsigned long fout) +{ + unsigned int m, n, p; + + DBG(__func__) + + DAC1064_calcclock(minfo, fout, minfo->max_pixel_clock, &m, &n, &p); + minfo->hw.DACclk[0] = m; + minfo->hw.DACclk[1] = n; + minfo->hw.DACclk[2] = p; +} + +static void DAC1064_setmclk(struct matrox_fb_info *minfo, int oscinfo, + unsigned long fmem) +{ + u_int32_t mx; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (minfo->devflags.noinit) { + /* read MCLK and give up... */ + hw->DACclk[3] = inDAC1064(minfo, DAC1064_XSYSPLLM); + hw->DACclk[4] = inDAC1064(minfo, DAC1064_XSYSPLLN); + hw->DACclk[5] = inDAC1064(minfo, DAC1064_XSYSPLLP); + return; + } + mx = hw->MXoptionReg | 0x00000004; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + mx &= ~0x000000BB; + if (oscinfo & DAC1064_OPT_GDIV1) + mx |= 0x00000008; + if (oscinfo & DAC1064_OPT_MDIV1) + mx |= 0x00000010; + if (oscinfo & DAC1064_OPT_RESERVED) + mx |= 0x00000080; + if ((oscinfo & DAC1064_OPT_SCLK_MASK) == DAC1064_OPT_SCLK_PLL) { + /* select PCI clock until we have setup oscilator... */ + int clk; + unsigned int m, n, p; + + /* powerup system PLL, select PCI clock */ + mx |= 0x00000020; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + mx &= ~0x00000004; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + + /* !!! you must not access device if MCLK is not running !!! + Doing so cause immediate PCI lockup :-( Maybe they should + generate ABORT or I/O (parity...) error and Linux should + recover from this... (kill driver/process). But world is not + perfect... */ + /* (bit 2 of PCI_OPTION_REG must be 0... and bits 0,1 must not + select PLL... because of PLL can be stopped at this time) */ + DAC1064_calcclock(minfo, fmem, minfo->max_pixel_clock, &m, &n, &p); + outDAC1064(minfo, DAC1064_XSYSPLLM, hw->DACclk[3] = m); + outDAC1064(minfo, DAC1064_XSYSPLLN, hw->DACclk[4] = n); + outDAC1064(minfo, DAC1064_XSYSPLLP, hw->DACclk[5] = p); + for (clk = 65536; clk; --clk) { + if (inDAC1064(minfo, DAC1064_XSYSPLLSTAT) & 0x40) + break; + } + if (!clk) + printk(KERN_ERR "matroxfb: aiee, SYSPLL not locked\n"); + /* select PLL */ + mx |= 0x00000005; + } else { + /* select specified system clock source */ + mx |= oscinfo & DAC1064_OPT_SCLK_MASK; + } + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + mx &= ~0x00000004; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + hw->MXoptionReg = mx; +} + +#ifdef CONFIG_FB_MATROX_G +static void g450_set_plls(struct matrox_fb_info *minfo) +{ + u_int32_t c2_ctl; + unsigned int pxc; + struct matrox_hw_state *hw = &minfo->hw; + int pixelmnp; + int videomnp; + + c2_ctl = hw->crtc2.ctl & ~0x4007; /* Clear PLL + enable for CRTC2 */ + c2_ctl |= 0x0001; /* Enable CRTC2 */ + hw->DACreg[POS1064_XPWRCTRL] &= ~0x02; /* Stop VIDEO PLL */ + pixelmnp = minfo->crtc1.mnp; + videomnp = minfo->crtc2.mnp; + if (videomnp < 0) { + c2_ctl &= ~0x0001; /* Disable CRTC2 */ + hw->DACreg[POS1064_XPWRCTRL] &= ~0x10; /* Powerdown CRTC2 */ + } else if (minfo->crtc2.pixclock == minfo->features.pll.ref_freq) { + c2_ctl |= 0x4002; /* Use reference directly */ + } else if (videomnp == pixelmnp) { + c2_ctl |= 0x0004; /* Use pixel PLL */ + } else { + if (0 == ((videomnp ^ pixelmnp) & 0xFFFFFF00)) { + /* PIXEL and VIDEO PLL must not use same frequency. We modify N + of PIXEL PLL in such case because of VIDEO PLL may be source + of TVO clocks, and chroma subcarrier is derived from its + pixel clocks */ + pixelmnp += 0x000100; + } + c2_ctl |= 0x0006; /* Use video PLL */ + hw->DACreg[POS1064_XPWRCTRL] |= 0x02; + + outDAC1064(minfo, M1064_XPWRCTRL, hw->DACreg[POS1064_XPWRCTRL]); + matroxfb_g450_setpll_cond(minfo, videomnp, M_VIDEO_PLL); + } + + hw->DACreg[POS1064_XPIXCLKCTRL] &= ~M1064_XPIXCLKCTRL_PLL_UP; + if (pixelmnp >= 0) { + hw->DACreg[POS1064_XPIXCLKCTRL] |= M1064_XPIXCLKCTRL_PLL_UP; + + outDAC1064(minfo, M1064_XPIXCLKCTRL, hw->DACreg[POS1064_XPIXCLKCTRL]); + matroxfb_g450_setpll_cond(minfo, pixelmnp, M_PIXEL_PLL_C); + } + if (c2_ctl != hw->crtc2.ctl) { + hw->crtc2.ctl = c2_ctl; + mga_outl(0x3C10, c2_ctl); + } + + pxc = minfo->crtc1.pixclock; + if (pxc == 0 || minfo->outputs[2].src == MATROXFB_SRC_CRTC2) { + pxc = minfo->crtc2.pixclock; + } + if (minfo->chip == MGA_G550) { + if (pxc < 45000) { + hw->DACreg[POS1064_XPANMODE] = 0x00; /* 0-50 */ + } else if (pxc < 55000) { + hw->DACreg[POS1064_XPANMODE] = 0x08; /* 34-62 */ + } else if (pxc < 70000) { + hw->DACreg[POS1064_XPANMODE] = 0x10; /* 42-78 */ + } else if (pxc < 85000) { + hw->DACreg[POS1064_XPANMODE] = 0x18; /* 62-92 */ + } else if (pxc < 100000) { + hw->DACreg[POS1064_XPANMODE] = 0x20; /* 74-108 */ + } else if (pxc < 115000) { + hw->DACreg[POS1064_XPANMODE] = 0x28; /* 94-122 */ + } else if (pxc < 125000) { + hw->DACreg[POS1064_XPANMODE] = 0x30; /* 108-132 */ + } else { + hw->DACreg[POS1064_XPANMODE] = 0x38; /* 120-168 */ + } + } else { + /* G450 */ + if (pxc < 45000) { + hw->DACreg[POS1064_XPANMODE] = 0x00; /* 0-54 */ + } else if (pxc < 65000) { + hw->DACreg[POS1064_XPANMODE] = 0x08; /* 38-70 */ + } else if (pxc < 85000) { + hw->DACreg[POS1064_XPANMODE] = 0x10; /* 56-96 */ + } else if (pxc < 105000) { + hw->DACreg[POS1064_XPANMODE] = 0x18; /* 80-114 */ + } else if (pxc < 135000) { + hw->DACreg[POS1064_XPANMODE] = 0x20; /* 102-144 */ + } else if (pxc < 160000) { + hw->DACreg[POS1064_XPANMODE] = 0x28; /* 132-166 */ + } else if (pxc < 175000) { + hw->DACreg[POS1064_XPANMODE] = 0x30; /* 154-182 */ + } else { + hw->DACreg[POS1064_XPANMODE] = 0x38; /* 170-204 */ + } + } +} +#endif + +void DAC1064_global_init(struct matrox_fb_info *minfo) +{ + struct matrox_hw_state *hw = &minfo->hw; + + hw->DACreg[POS1064_XMISCCTRL] &= M1064_XMISCCTRL_DAC_WIDTHMASK; + hw->DACreg[POS1064_XMISCCTRL] |= M1064_XMISCCTRL_LUT_EN; + hw->DACreg[POS1064_XPIXCLKCTRL] = M1064_XPIXCLKCTRL_PLL_UP | M1064_XPIXCLKCTRL_EN | M1064_XPIXCLKCTRL_SRC_PLL; +#ifdef CONFIG_FB_MATROX_G + if (minfo->devflags.g450dac) { + hw->DACreg[POS1064_XPWRCTRL] = 0x1F; /* powerup everything */ + hw->DACreg[POS1064_XOUTPUTCONN] = 0x00; /* disable outputs */ + hw->DACreg[POS1064_XMISCCTRL] |= M1064_XMISCCTRL_DAC_EN; + switch (minfo->outputs[0].src) { + case MATROXFB_SRC_CRTC1: + case MATROXFB_SRC_CRTC2: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x01; /* enable output; CRTC1/2 selection is in CRTC2 ctl */ + break; + case MATROXFB_SRC_NONE: + hw->DACreg[POS1064_XMISCCTRL] &= ~M1064_XMISCCTRL_DAC_EN; + break; + } + switch (minfo->outputs[1].src) { + case MATROXFB_SRC_CRTC1: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x04; + break; + case MATROXFB_SRC_CRTC2: + if (minfo->outputs[1].mode == MATROXFB_OUTPUT_MODE_MONITOR) { + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x08; + } else { + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x0C; + } + break; + case MATROXFB_SRC_NONE: + hw->DACreg[POS1064_XPWRCTRL] &= ~0x01; /* Poweroff DAC2 */ + break; + } + switch (minfo->outputs[2].src) { + case MATROXFB_SRC_CRTC1: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x20; + break; + case MATROXFB_SRC_CRTC2: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x40; + break; + case MATROXFB_SRC_NONE: +#if 0 + /* HELP! If we boot without DFP connected to DVI, we can + poweroff TMDS. But if we boot with DFP connected, + TMDS generated clocks are used instead of ALL pixclocks + available... If someone knows which register + handles it, please reveal this secret to me... */ + hw->DACreg[POS1064_XPWRCTRL] &= ~0x04; /* Poweroff TMDS */ +#endif + break; + } + /* Now set timming related variables... */ + g450_set_plls(minfo); + } else +#endif + { + if (minfo->outputs[1].src == MATROXFB_SRC_CRTC1) { + hw->DACreg[POS1064_XPIXCLKCTRL] = M1064_XPIXCLKCTRL_PLL_UP | M1064_XPIXCLKCTRL_EN | M1064_XPIXCLKCTRL_SRC_EXT; + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_MAFC | G400_XMISCCTRL_VDO_MAFC12; + } else if (minfo->outputs[1].src == MATROXFB_SRC_CRTC2) { + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_MAFC | G400_XMISCCTRL_VDO_C2_MAFC12; + } else if (minfo->outputs[2].src == MATROXFB_SRC_CRTC1) + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_PANELLINK | G400_XMISCCTRL_VDO_MAFC12; + else + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_DIS; + + if (minfo->outputs[0].src != MATROXFB_SRC_NONE) + hw->DACreg[POS1064_XMISCCTRL] |= M1064_XMISCCTRL_DAC_EN; + } +} + +void DAC1064_global_restore(struct matrox_fb_info *minfo) +{ + struct matrox_hw_state *hw = &minfo->hw; + + outDAC1064(minfo, M1064_XPIXCLKCTRL, hw->DACreg[POS1064_XPIXCLKCTRL]); + outDAC1064(minfo, M1064_XMISCCTRL, hw->DACreg[POS1064_XMISCCTRL]); + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) { + outDAC1064(minfo, 0x20, 0x04); + outDAC1064(minfo, 0x1F, minfo->devflags.dfp_type); + if (minfo->devflags.g450dac) { + outDAC1064(minfo, M1064_XSYNCCTRL, 0xCC); + outDAC1064(minfo, M1064_XPWRCTRL, hw->DACreg[POS1064_XPWRCTRL]); + outDAC1064(minfo, M1064_XPANMODE, hw->DACreg[POS1064_XPANMODE]); + outDAC1064(minfo, M1064_XOUTPUTCONN, hw->DACreg[POS1064_XOUTPUTCONN]); + } + } +} + +static int DAC1064_init_1(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + memcpy(hw->DACreg, MGA1064_DAC, sizeof(MGA1064_DAC_regs)); + switch (minfo->fbcon.var.bits_per_pixel) { + /* case 4: not supported by MGA1064 DAC */ + case 8: + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_8BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + case 16: + if (minfo->fbcon.var.green.length == 5) + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_15BPP_1BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + else + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_16BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + case 24: + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_24BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + case 32: + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_32BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + default: + return 1; /* unsupported depth */ + } + hw->DACreg[POS1064_XVREFCTRL] = minfo->features.DAC1064.xvrefctrl; + hw->DACreg[POS1064_XGENCTRL] &= ~M1064_XGENCTRL_SYNC_ON_GREEN_MASK; + hw->DACreg[POS1064_XGENCTRL] |= (m->sync & FB_SYNC_ON_GREEN)?M1064_XGENCTRL_SYNC_ON_GREEN:M1064_XGENCTRL_NO_SYNC_ON_GREEN; + hw->DACreg[POS1064_XCURADDL] = 0; + hw->DACreg[POS1064_XCURADDH] = 0; + + DAC1064_global_init(minfo); + return 0; +} + +static int DAC1064_init_2(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (minfo->fbcon.var.bits_per_pixel > 16) { /* 256 entries */ + int i; + + for (i = 0; i < 256; i++) { + hw->DACpal[i * 3 + 0] = i; + hw->DACpal[i * 3 + 1] = i; + hw->DACpal[i * 3 + 2] = i; + } + } else if (minfo->fbcon.var.bits_per_pixel > 8) { + if (minfo->fbcon.var.green.length == 5) { /* 0..31, 128..159 */ + int i; + + for (i = 0; i < 32; i++) { + /* with p15 == 0 */ + hw->DACpal[i * 3 + 0] = i << 3; + hw->DACpal[i * 3 + 1] = i << 3; + hw->DACpal[i * 3 + 2] = i << 3; + /* with p15 == 1 */ + hw->DACpal[(i + 128) * 3 + 0] = i << 3; + hw->DACpal[(i + 128) * 3 + 1] = i << 3; + hw->DACpal[(i + 128) * 3 + 2] = i << 3; + } + } else { + int i; + + for (i = 0; i < 64; i++) { /* 0..63 */ + hw->DACpal[i * 3 + 0] = i << 3; + hw->DACpal[i * 3 + 1] = i << 2; + hw->DACpal[i * 3 + 2] = i << 3; + } + } + } else { + memset(hw->DACpal, 0, 768); + } + return 0; +} + +static void DAC1064_restore_1(struct matrox_fb_info *minfo) +{ + struct matrox_hw_state *hw = &minfo->hw; + + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + if ((inDAC1064(minfo, DAC1064_XSYSPLLM) != hw->DACclk[3]) || + (inDAC1064(minfo, DAC1064_XSYSPLLN) != hw->DACclk[4]) || + (inDAC1064(minfo, DAC1064_XSYSPLLP) != hw->DACclk[5])) { + outDAC1064(minfo, DAC1064_XSYSPLLM, hw->DACclk[3]); + outDAC1064(minfo, DAC1064_XSYSPLLN, hw->DACclk[4]); + outDAC1064(minfo, DAC1064_XSYSPLLP, hw->DACclk[5]); + } + { + unsigned int i; + + for (i = 0; i < sizeof(MGA1064_DAC_regs); i++) { + if ((i != POS1064_XPIXCLKCTRL) && (i != POS1064_XMISCCTRL)) + outDAC1064(minfo, MGA1064_DAC_regs[i], hw->DACreg[i]); + } + } + + DAC1064_global_restore(minfo); + + CRITEND +}; + +static void DAC1064_restore_2(struct matrox_fb_info *minfo) +{ +#ifdef DEBUG + unsigned int i; +#endif + + DBG(__func__) + +#ifdef DEBUG + dprintk(KERN_DEBUG "DAC1064regs "); + for (i = 0; i < sizeof(MGA1064_DAC_regs); i++) { + dprintk("R%02X=%02X ", MGA1064_DAC_regs[i], minfo->hw.DACreg[i]); + if ((i & 0x7) == 0x7) dprintk(KERN_DEBUG "continuing... "); + } + dprintk(KERN_DEBUG "DAC1064clk "); + for (i = 0; i < 6; i++) + dprintk("C%02X=%02X ", i, minfo->hw.DACclk[i]); + dprintk("\n"); +#endif +} + +static int m1064_compute(void* out, struct my_timming* m) { +#define minfo ((struct matrox_fb_info*)out) + { + int i; + int tmout; + CRITFLAGS + + DAC1064_setpclk(minfo, m->pixclock); + + CRITBEGIN + + for (i = 0; i < 3; i++) + outDAC1064(minfo, M1064_XPIXPLLCM + i, minfo->hw.DACclk[i]); + for (tmout = 500000; tmout; tmout--) { + if (inDAC1064(minfo, M1064_XPIXPLLSTAT) & 0x40) + break; + udelay(10); + } + + CRITEND + + if (!tmout) + printk(KERN_ERR "matroxfb: Pixel PLL not locked after 5 secs\n"); + } +#undef minfo + return 0; +} + +static struct matrox_altout m1064 = { + .name = "Primary output", + .compute = m1064_compute, +}; + +#ifdef CONFIG_FB_MATROX_G +static int g450_compute(void* out, struct my_timming* m) { +#define minfo ((struct matrox_fb_info*)out) + if (m->mnp < 0) { + m->mnp = matroxfb_g450_setclk(minfo, m->pixclock, (m->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL); + if (m->mnp >= 0) { + m->pixclock = g450_mnp2f(minfo, m->mnp); + } + } +#undef minfo + return 0; +} + +static struct matrox_altout g450out = { + .name = "Primary output", + .compute = g450_compute, +}; +#endif + +#endif /* NEED_DAC1064 */ + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static int MGA1064_init(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (DAC1064_init_1(minfo, m)) return 1; + if (matroxfb_vgaHWinit(minfo, m)) return 1; + + hw->MiscOutReg = 0xCB; + if (m->sync & FB_SYNC_HOR_HIGH_ACT) + hw->MiscOutReg &= ~0x40; + if (m->sync & FB_SYNC_VERT_HIGH_ACT) + hw->MiscOutReg &= ~0x80; + if (m->sync & FB_SYNC_COMP_HIGH_ACT) /* should be only FB_SYNC_COMP */ + hw->CRTCEXT[3] |= 0x40; + + if (DAC1064_init_2(minfo, m)) return 1; + return 0; +} +#endif + +#ifdef CONFIG_FB_MATROX_G +static int MGAG100_init(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (DAC1064_init_1(minfo, m)) return 1; + hw->MXoptionReg &= ~0x2000; + if (matroxfb_vgaHWinit(minfo, m)) return 1; + + hw->MiscOutReg = 0xEF; + if (m->sync & FB_SYNC_HOR_HIGH_ACT) + hw->MiscOutReg &= ~0x40; + if (m->sync & FB_SYNC_VERT_HIGH_ACT) + hw->MiscOutReg &= ~0x80; + if (m->sync & FB_SYNC_COMP_HIGH_ACT) /* should be only FB_SYNC_COMP */ + hw->CRTCEXT[3] |= 0x40; + + if (DAC1064_init_2(minfo, m)) return 1; + return 0; +} +#endif /* G */ + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static void MGA1064_ramdac_init(struct matrox_fb_info *minfo) +{ + + DBG(__func__) + + /* minfo->features.DAC1064.vco_freq_min = 120000; */ + minfo->features.pll.vco_freq_min = 62000; + minfo->features.pll.ref_freq = 14318; + minfo->features.pll.feed_div_min = 100; + minfo->features.pll.feed_div_max = 127; + minfo->features.pll.in_div_min = 1; + minfo->features.pll.in_div_max = 31; + minfo->features.pll.post_shift_max = 3; + minfo->features.DAC1064.xvrefctrl = DAC1064_XVREFCTRL_EXTERNAL; + /* maybe cmdline MCLK= ?, doc says gclk=44MHz, mclk=66MHz... it was 55/83 with old values */ + DAC1064_setmclk(minfo, DAC1064_OPT_MDIV2 | DAC1064_OPT_GDIV3 | DAC1064_OPT_SCLK_PLL, 133333); +} +#endif + +#ifdef CONFIG_FB_MATROX_G +/* BIOS environ */ +static int x7AF4 = 0x10; /* flags, maybe 0x10 = SDRAM, 0x00 = SGRAM??? */ + /* G100 wants 0x10, G200 SGRAM does not care... */ +#if 0 +static int def50 = 0; /* reg50, & 0x0F, & 0x3000 (only 0x0000, 0x1000, 0x2000 (0x3000 disallowed and treated as 0) */ +#endif + +static void MGAG100_progPixClock(const struct matrox_fb_info *minfo, int flags, + int m, int n, int p) +{ + int reg; + int selClk; + int clk; + + DBG(__func__) + + outDAC1064(minfo, M1064_XPIXCLKCTRL, inDAC1064(minfo, M1064_XPIXCLKCTRL) | M1064_XPIXCLKCTRL_DIS | + M1064_XPIXCLKCTRL_PLL_UP); + switch (flags & 3) { + case 0: reg = M1064_XPIXPLLAM; break; + case 1: reg = M1064_XPIXPLLBM; break; + default: reg = M1064_XPIXPLLCM; break; + } + outDAC1064(minfo, reg++, m); + outDAC1064(minfo, reg++, n); + outDAC1064(minfo, reg, p); + selClk = mga_inb(M_MISC_REG_READ) & ~0xC; + /* there should be flags & 0x03 & case 0/1/else */ + /* and we should first select source and after that we should wait for PLL */ + /* and we are waiting for PLL with oscilator disabled... Is it right? */ + switch (flags & 0x03) { + case 0x00: break; + case 0x01: selClk |= 4; break; + default: selClk |= 0x0C; break; + } + mga_outb(M_MISC_REG, selClk); + for (clk = 500000; clk; clk--) { + if (inDAC1064(minfo, M1064_XPIXPLLSTAT) & 0x40) + break; + udelay(10); + } + if (!clk) + printk(KERN_ERR "matroxfb: Pixel PLL%c not locked after usual time\n", (reg-M1064_XPIXPLLAM-2)/4 + 'A'); + selClk = inDAC1064(minfo, M1064_XPIXCLKCTRL) & ~M1064_XPIXCLKCTRL_SRC_MASK; + switch (flags & 0x0C) { + case 0x00: selClk |= M1064_XPIXCLKCTRL_SRC_PCI; break; + case 0x04: selClk |= M1064_XPIXCLKCTRL_SRC_PLL; break; + default: selClk |= M1064_XPIXCLKCTRL_SRC_EXT; break; + } + outDAC1064(minfo, M1064_XPIXCLKCTRL, selClk); + outDAC1064(minfo, M1064_XPIXCLKCTRL, inDAC1064(minfo, M1064_XPIXCLKCTRL) & ~M1064_XPIXCLKCTRL_DIS); +} + +static void MGAG100_setPixClock(const struct matrox_fb_info *minfo, int flags, + int freq) +{ + unsigned int m, n, p; + + DBG(__func__) + + DAC1064_calcclock(minfo, freq, minfo->max_pixel_clock, &m, &n, &p); + MGAG100_progPixClock(minfo, flags, m, n, p); +} +#endif + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static int MGA1064_preinit(struct matrox_fb_info *minfo) +{ + static const int vxres_mystique[] = { 512, 640, 768, 800, 832, 960, + 1024, 1152, 1280, 1600, 1664, 1920, + 2048, 0}; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + /* minfo->capable.cfb4 = 0; ... preinitialized by 0 */ + minfo->capable.text = 1; + minfo->capable.vxres = vxres_mystique; + + minfo->outputs[0].output = &m1064; + minfo->outputs[0].src = minfo->outputs[0].default_src; + minfo->outputs[0].data = minfo; + minfo->outputs[0].mode = MATROXFB_OUTPUT_MODE_MONITOR; + + if (minfo->devflags.noinit) + return 0; /* do not modify settings */ + hw->MXoptionReg &= 0xC0000100; + hw->MXoptionReg |= 0x00094E20; + if (minfo->devflags.novga) + hw->MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + hw->MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + hw->MXoptionReg |= 0x20000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + mga_setr(M_SEQ_INDEX, 0x01, 0x20); + mga_outl(M_CTLWTST, 0x00000000); + udelay(200); + mga_outl(M_MACCESS, 0x00008000); + udelay(100); + mga_outl(M_MACCESS, 0x0000C000); + return 0; +} + +static void MGA1064_reset(struct matrox_fb_info *minfo) +{ + + DBG(__func__); + + MGA1064_ramdac_init(minfo); +} +#endif + +#ifdef CONFIG_FB_MATROX_G +static void g450_mclk_init(struct matrox_fb_info *minfo) +{ + /* switch all clocks to PCI source */ + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg | 4); + pci_write_config_dword(minfo->pcidev, PCI_OPTION3_REG, minfo->values.reg.opt3 & ~0x00300C03); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + if (((minfo->values.reg.opt3 & 0x000003) == 0x000003) || + ((minfo->values.reg.opt3 & 0x000C00) == 0x000C00) || + ((minfo->values.reg.opt3 & 0x300000) == 0x300000)) { + matroxfb_g450_setclk(minfo, minfo->values.pll.video, M_VIDEO_PLL); + } else { + unsigned long flags; + unsigned int pwr; + + matroxfb_DAC_lock_irqsave(flags); + pwr = inDAC1064(minfo, M1064_XPWRCTRL) & ~0x02; + outDAC1064(minfo, M1064_XPWRCTRL, pwr); + matroxfb_DAC_unlock_irqrestore(flags); + } + matroxfb_g450_setclk(minfo, minfo->values.pll.system, M_SYSTEM_PLL); + + /* switch clocks to their real PLL source(s) */ + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg | 4); + pci_write_config_dword(minfo->pcidev, PCI_OPTION3_REG, minfo->values.reg.opt3); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + +} + +static void g450_memory_init(struct matrox_fb_info *minfo) +{ + /* disable memory refresh */ + minfo->hw.MXoptionReg &= ~0x001F8000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* set memory interface parameters */ + minfo->hw.MXoptionReg &= ~0x00207E00; + minfo->hw.MXoptionReg |= 0x00207E00 & minfo->values.reg.opt; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, minfo->values.reg.opt2); + + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + + /* first set up memory interface with disabled memory interface clocks */ + pci_write_config_dword(minfo->pcidev, PCI_MEMMISC_REG, minfo->values.reg.memmisc & ~0x80000000U); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + mga_outl(M_MACCESS, minfo->values.reg.maccess); + /* start memory clocks */ + pci_write_config_dword(minfo->pcidev, PCI_MEMMISC_REG, minfo->values.reg.memmisc | 0x80000000U); + + udelay(200); + + if (minfo->values.memory.ddr && (!minfo->values.memory.emrswen || !minfo->values.memory.dll)) { + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk & ~0x1000); + } + mga_outl(M_MACCESS, minfo->values.reg.maccess | 0x8000); + + udelay(200); + + minfo->hw.MXoptionReg |= 0x001F8000 & minfo->values.reg.opt; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* value is written to memory chips only if old != new */ + mga_outl(M_PLNWT, 0); + mga_outl(M_PLNWT, ~0); + + if (minfo->values.reg.mctlwtst != minfo->values.reg.mctlwtst_core) { + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst_core); + } + +} + +static void g450_preinit(struct matrox_fb_info *minfo) +{ + u_int32_t c2ctl; + u_int8_t curctl; + u_int8_t c1ctl; + + /* minfo->hw.MXoptionReg = minfo->values.reg.opt; */ + minfo->hw.MXoptionReg &= 0xC0000100; + minfo->hw.MXoptionReg |= 0x00000020; + if (minfo->devflags.novga) + minfo->hw.MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + minfo->hw.MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + minfo->hw.MXoptionReg |= 0x20000000; + minfo->hw.MXoptionReg |= minfo->values.reg.opt & 0x03400040; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* Init system clocks */ + + /* stop crtc2 */ + c2ctl = mga_inl(M_C2CTL); + mga_outl(M_C2CTL, c2ctl & ~1); + /* stop cursor */ + curctl = inDAC1064(minfo, M1064_XCURCTRL); + outDAC1064(minfo, M1064_XCURCTRL, 0); + /* stop crtc1 */ + c1ctl = mga_readr(M_SEQ_INDEX, 1); + mga_setr(M_SEQ_INDEX, 1, c1ctl | 0x20); + + g450_mclk_init(minfo); + g450_memory_init(minfo); + + /* set legacy VGA clock sources for DOSEmu or VMware... */ + matroxfb_g450_setclk(minfo, 25175, M_PIXEL_PLL_A); + matroxfb_g450_setclk(minfo, 28322, M_PIXEL_PLL_B); + + /* restore crtc1 */ + mga_setr(M_SEQ_INDEX, 1, c1ctl); + + /* restore cursor */ + outDAC1064(minfo, M1064_XCURCTRL, curctl); + + /* restore crtc2 */ + mga_outl(M_C2CTL, c2ctl); + + return; +} + +static int MGAG100_preinit(struct matrox_fb_info *minfo) +{ + static const int vxres_g100[] = { 512, 640, 768, 800, 832, 960, + 1024, 1152, 1280, 1600, 1664, 1920, + 2048, 0}; + struct matrox_hw_state *hw = &minfo->hw; + + u_int32_t reg50; +#if 0 + u_int32_t q; +#endif + + DBG(__func__) + + /* there are some instabilities if in_div > 19 && vco < 61000 */ + if (minfo->devflags.g450dac) { + minfo->features.pll.vco_freq_min = 130000; /* my sample: >118 */ + } else { + minfo->features.pll.vco_freq_min = 62000; + } + if (!minfo->features.pll.ref_freq) { + minfo->features.pll.ref_freq = 27000; + } + minfo->features.pll.feed_div_min = 7; + minfo->features.pll.feed_div_max = 127; + minfo->features.pll.in_div_min = 1; + minfo->features.pll.in_div_max = 31; + minfo->features.pll.post_shift_max = 3; + minfo->features.DAC1064.xvrefctrl = DAC1064_XVREFCTRL_G100_DEFAULT; + /* minfo->capable.cfb4 = 0; ... preinitialized by 0 */ + minfo->capable.text = 1; + minfo->capable.vxres = vxres_g100; + minfo->capable.plnwt = minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG100 + ? minfo->devflags.sgram : 1; + + if (minfo->devflags.g450dac) { + minfo->outputs[0].output = &g450out; + } else { + minfo->outputs[0].output = &m1064; + } + minfo->outputs[0].src = minfo->outputs[0].default_src; + minfo->outputs[0].data = minfo; + minfo->outputs[0].mode = MATROXFB_OUTPUT_MODE_MONITOR; + + if (minfo->devflags.g450dac) { + /* we must do this always, BIOS does not do it for us + and accelerator dies without it */ + mga_outl(0x1C0C, 0); + } + if (minfo->devflags.noinit) + return 0; + if (minfo->devflags.g450dac) { + g450_preinit(minfo); + return 0; + } + hw->MXoptionReg &= 0xC0000100; + hw->MXoptionReg |= 0x00000020; + if (minfo->devflags.novga) + hw->MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + hw->MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + hw->MXoptionReg |= 0x20000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + DAC1064_setmclk(minfo, DAC1064_OPT_MDIV2 | DAC1064_OPT_GDIV3 | DAC1064_OPT_SCLK_PCI, 133333); + + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG100) { + pci_read_config_dword(minfo->pcidev, PCI_OPTION2_REG, ®50); + reg50 &= ~0x3000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + + hw->MXoptionReg |= 0x1080; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + udelay(100); + mga_outb(0x1C05, 0x00); + mga_outb(0x1C05, 0x80); + udelay(100); + mga_outb(0x1C05, 0x40); + mga_outb(0x1C05, 0xC0); + udelay(100); + reg50 &= ~0xFF; + reg50 |= 0x07; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + /* it should help with G100 */ + mga_outb(M_GRAPHICS_INDEX, 6); + mga_outb(M_GRAPHICS_DATA, (mga_inb(M_GRAPHICS_DATA) & 3) | 4); + mga_setr(M_EXTVGA_INDEX, 0x03, 0x81); + mga_setr(M_EXTVGA_INDEX, 0x04, 0x00); + mga_writeb(minfo->video.vbase, 0x0000, 0xAA); + mga_writeb(minfo->video.vbase, 0x0800, 0x55); + mga_writeb(minfo->video.vbase, 0x4000, 0x55); +#if 0 + if (mga_readb(minfo->video.vbase, 0x0000) != 0xAA) { + hw->MXoptionReg &= ~0x1000; + } +#endif + hw->MXoptionReg |= 0x00078020; + } else if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG200) { + pci_read_config_dword(minfo->pcidev, PCI_OPTION2_REG, ®50); + reg50 &= ~0x3000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + + if (minfo->devflags.memtype == -1) + hw->MXoptionReg |= minfo->values.reg.opt & 0x1C00; + else + hw->MXoptionReg |= (minfo->devflags.memtype & 7) << 10; + if (minfo->devflags.sgram) + hw->MXoptionReg |= 0x4000; + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + udelay(200); + mga_outl(M_MACCESS, 0x00000000); + mga_outl(M_MACCESS, 0x00008000); + udelay(100); + mga_outw(M_MEMRDBK, minfo->values.reg.memrdbk); + hw->MXoptionReg |= 0x00078020; + } else { + pci_read_config_dword(minfo->pcidev, PCI_OPTION2_REG, ®50); + reg50 &= ~0x00000100; + reg50 |= 0x00000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + + if (minfo->devflags.memtype == -1) + hw->MXoptionReg |= minfo->values.reg.opt & 0x1C00; + else + hw->MXoptionReg |= (minfo->devflags.memtype & 7) << 10; + if (minfo->devflags.sgram) + hw->MXoptionReg |= 0x4000; + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + udelay(200); + mga_outl(M_MACCESS, 0x00000000); + mga_outl(M_MACCESS, 0x00008000); + udelay(100); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + hw->MXoptionReg |= 0x00040020; + } + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + return 0; +} + +static void MGAG100_reset(struct matrox_fb_info *minfo) +{ + u_int8_t b; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + { +#ifdef G100_BROKEN_IBM_82351 + u_int32_t d; + + find 1014/22 (IBM/82351); /* if found and bridging Matrox, do some strange stuff */ + pci_read_config_byte(ibm, PCI_SECONDARY_BUS, &b); + if (b == minfo->pcidev->bus->number) { + pci_write_config_byte(ibm, PCI_COMMAND+1, 0); /* disable back-to-back & SERR */ + pci_write_config_byte(ibm, 0x41, 0xF4); /* ??? */ + pci_write_config_byte(ibm, PCI_IO_BASE, 0xF0); /* ??? */ + pci_write_config_byte(ibm, PCI_IO_LIMIT, 0x00); /* ??? */ + } +#endif + if (!minfo->devflags.noinit) { + if (x7AF4 & 8) { + hw->MXoptionReg |= 0x40; /* FIXME... */ + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + } + mga_setr(M_EXTVGA_INDEX, 0x06, 0x00); + } + } + if (minfo->devflags.g450dac) { + /* either leave MCLK as is... or they were set in preinit */ + hw->DACclk[3] = inDAC1064(minfo, DAC1064_XSYSPLLM); + hw->DACclk[4] = inDAC1064(minfo, DAC1064_XSYSPLLN); + hw->DACclk[5] = inDAC1064(minfo, DAC1064_XSYSPLLP); + } else { + DAC1064_setmclk(minfo, DAC1064_OPT_RESERVED | DAC1064_OPT_MDIV2 | DAC1064_OPT_GDIV1 | DAC1064_OPT_SCLK_PLL, 133333); + } + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) { + if (minfo->devflags.dfp_type == -1) { + minfo->devflags.dfp_type = inDAC1064(minfo, 0x1F); + } + } + if (minfo->devflags.noinit) + return; + if (minfo->devflags.g450dac) { + } else { + MGAG100_setPixClock(minfo, 4, 25175); + MGAG100_setPixClock(minfo, 5, 28322); + if (x7AF4 & 0x10) { + b = inDAC1064(minfo, M1064_XGENIODATA) & ~1; + outDAC1064(minfo, M1064_XGENIODATA, b); + b = inDAC1064(minfo, M1064_XGENIOCTRL) | 1; + outDAC1064(minfo, M1064_XGENIOCTRL, b); + } + } +} +#endif + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static void MGA1064_restore(struct matrox_fb_info *minfo) +{ + int i; + struct matrox_hw_state *hw = &minfo->hw; + + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + mga_outb(M_IEN, 0x00); + mga_outb(M_CACHEFLUSH, 0x00); + + CRITEND + + DAC1064_restore_1(minfo); + matroxfb_vgaHWrestore(minfo); + minfo->crtc1.panpos = -1; + for (i = 0; i < 6; i++) + mga_setr(M_EXTVGA_INDEX, i, hw->CRTCEXT[i]); + DAC1064_restore_2(minfo); +} +#endif + +#ifdef CONFIG_FB_MATROX_G +static void MGAG100_restore(struct matrox_fb_info *minfo) +{ + int i; + struct matrox_hw_state *hw = &minfo->hw; + + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + CRITEND + + DAC1064_restore_1(minfo); + matroxfb_vgaHWrestore(minfo); + if (minfo->devflags.support32MB) + mga_setr(M_EXTVGA_INDEX, 8, hw->CRTCEXT[8]); + minfo->crtc1.panpos = -1; + for (i = 0; i < 6; i++) + mga_setr(M_EXTVGA_INDEX, i, hw->CRTCEXT[i]); + DAC1064_restore_2(minfo); +} +#endif + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +struct matrox_switch matrox_mystique = { + MGA1064_preinit, MGA1064_reset, MGA1064_init, MGA1064_restore, +}; +EXPORT_SYMBOL(matrox_mystique); +#endif + +#ifdef CONFIG_FB_MATROX_G +struct matrox_switch matrox_G100 = { + MGAG100_preinit, MGAG100_reset, MGAG100_init, MGAG100_restore, +}; +EXPORT_SYMBOL(matrox_G100); +#endif + +#ifdef NEED_DAC1064 +EXPORT_SYMBOL(DAC1064_global_init); +EXPORT_SYMBOL(DAC1064_global_restore); +#endif +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_DAC1064.h b/drivers/video/fbdev/matrox/matroxfb_DAC1064.h new file mode 100644 index 000000000000..1e6e45b57b78 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_DAC1064.h @@ -0,0 +1,179 @@ +#ifndef __MATROXFB_DAC1064_H__ +#define __MATROXFB_DAC1064_H__ + + +#include "matroxfb_base.h" + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +extern struct matrox_switch matrox_mystique; +#endif +#ifdef CONFIG_FB_MATROX_G +extern struct matrox_switch matrox_G100; +#endif +#ifdef NEED_DAC1064 +void DAC1064_global_init(struct matrox_fb_info *minfo); +void DAC1064_global_restore(struct matrox_fb_info *minfo); +#endif + +#define M1064_INDEX 0x00 +#define M1064_PALWRADD 0x00 +#define M1064_PALDATA 0x01 +#define M1064_PIXRDMSK 0x02 +#define M1064_PALRDADD 0x03 +#define M1064_X_DATAREG 0x0A +#define M1064_CURPOSXL 0x0C /* can be accessed as DWORD */ +#define M1064_CURPOSXH 0x0D +#define M1064_CURPOSYL 0x0E +#define M1064_CURPOSYH 0x0F + +#define M1064_XCURADDL 0x04 +#define M1064_XCURADDH 0x05 +#define M1064_XCURCTRL 0x06 +#define M1064_XCURCTRL_DIS 0x00 /* transparent, transparent, transparent, transparent */ +#define M1064_XCURCTRL_3COLOR 0x01 /* transparent, 0, 1, 2 */ +#define M1064_XCURCTRL_XGA 0x02 /* 0, 1, transparent, complement */ +#define M1064_XCURCTRL_XWIN 0x03 /* transparent, transparent, 0, 1 */ + /* drive DVI by standard(0)/DVI(1) PLL */ + /* if set(1), C?DVICLKEN and C?DVICLKSEL must be set(1) */ +#define M1064_XDVICLKCTRL_DVIDATAPATHSEL 0x01 + /* drive CRTC1 by standard(0)/DVI(1) PLL */ +#define M1064_XDVICLKCTRL_C1DVICLKSEL 0x02 + /* drive CRTC2 by standard(0)/DVI(1) PLL */ +#define M1064_XDVICLKCTRL_C2DVICLKSEL 0x04 + /* pixel clock allowed to(0)/blocked from(1) driving CRTC1 */ +#define M1064_XDVICLKCTRL_C1DVICLKEN 0x08 + /* DVI PLL loop filter bandwidth selection bits */ +#define M1064_XDVICLKCTRL_DVILOOPCTL 0x30 + /* CRTC2 pixel clock allowed to(0)/blocked from(1) driving CRTC2 */ +#define M1064_XDVICLKCTRL_C2DVICLKEN 0x40 + /* P1PLL loop filter bandwidth selection */ +#define M1064_XDVICLKCTRL_P1LOOPBWDTCTL 0x80 +#define M1064_XCURCOL0RED 0x08 +#define M1064_XCURCOL0GREEN 0x09 +#define M1064_XCURCOL0BLUE 0x0A +#define M1064_XCURCOL1RED 0x0C +#define M1064_XCURCOL1GREEN 0x0D +#define M1064_XCURCOL1BLUE 0x0E +#define M1064_XDVICLKCTRL 0x0F +#define M1064_XCURCOL2RED 0x10 +#define M1064_XCURCOL2GREEN 0x11 +#define M1064_XCURCOL2BLUE 0x12 +#define DAC1064_XVREFCTRL 0x18 +#define DAC1064_XVREFCTRL_INTERNAL 0x3F +#define DAC1064_XVREFCTRL_EXTERNAL 0x00 +#define DAC1064_XVREFCTRL_G100_DEFAULT 0x03 +#define M1064_XMULCTRL 0x19 +#define M1064_XMULCTRL_DEPTH_8BPP 0x00 /* 8 bpp paletized */ +#define M1064_XMULCTRL_DEPTH_15BPP_1BPP 0x01 /* 15 bpp paletized + 1 bpp overlay */ +#define M1064_XMULCTRL_DEPTH_16BPP 0x02 /* 16 bpp paletized */ +#define M1064_XMULCTRL_DEPTH_24BPP 0x03 /* 24 bpp paletized */ +#define M1064_XMULCTRL_DEPTH_24BPP_8BPP 0x04 /* 24 bpp direct + 8 bpp overlay paletized */ +#define M1064_XMULCTRL_2G8V16 0x05 /* 15 bpp video direct, half xres, 8bpp paletized */ +#define M1064_XMULCTRL_G16V16 0x06 /* 15 bpp video, 15bpp graphics, one of them paletized */ +#define M1064_XMULCTRL_DEPTH_32BPP 0x07 /* 24 bpp paletized + 8 bpp unused */ +#define M1064_XMULCTRL_GRAPHICS_PALETIZED 0x00 +#define M1064_XMULCTRL_VIDEO_PALETIZED 0x08 +#define M1064_XPIXCLKCTRL 0x1A +#define M1064_XPIXCLKCTRL_SRC_PCI 0x00 +#define M1064_XPIXCLKCTRL_SRC_PLL 0x01 +#define M1064_XPIXCLKCTRL_SRC_EXT 0x02 +#define M1064_XPIXCLKCTRL_SRC_SYS 0x03 /* G200/G400 */ +#define M1064_XPIXCLKCTRL_SRC_PLL2 0x03 /* G450 */ +#define M1064_XPIXCLKCTRL_SRC_MASK 0x03 +#define M1064_XPIXCLKCTRL_EN 0x00 +#define M1064_XPIXCLKCTRL_DIS 0x04 +#define M1064_XPIXCLKCTRL_PLL_DOWN 0x00 +#define M1064_XPIXCLKCTRL_PLL_UP 0x08 +#define M1064_XGENCTRL 0x1D +#define M1064_XGENCTRL_VS_0 0x00 +#define M1064_XGENCTRL_VS_1 0x01 +#define M1064_XGENCTRL_ALPHA_DIS 0x00 +#define M1064_XGENCTRL_ALPHA_EN 0x02 +#define M1064_XGENCTRL_BLACK_0IRE 0x00 +#define M1064_XGENCTRL_BLACK_75IRE 0x10 +#define M1064_XGENCTRL_SYNC_ON_GREEN 0x00 +#define M1064_XGENCTRL_NO_SYNC_ON_GREEN 0x20 +#define M1064_XGENCTRL_SYNC_ON_GREEN_MASK 0x20 +#define M1064_XMISCCTRL 0x1E +#define M1064_XMISCCTRL_DAC_DIS 0x00 +#define M1064_XMISCCTRL_DAC_EN 0x01 +#define M1064_XMISCCTRL_MFC_VGA 0x00 +#define M1064_XMISCCTRL_MFC_MAFC 0x02 +#define M1064_XMISCCTRL_MFC_DIS 0x06 +#define GX00_XMISCCTRL_MFC_MAFC 0x02 +#define GX00_XMISCCTRL_MFC_PANELLINK 0x04 +#define GX00_XMISCCTRL_MFC_DIS 0x06 +#define GX00_XMISCCTRL_MFC_MASK 0x06 +#define M1064_XMISCCTRL_DAC_6BIT 0x00 +#define M1064_XMISCCTRL_DAC_8BIT 0x08 +#define M1064_XMISCCTRL_DAC_WIDTHMASK 0x08 +#define M1064_XMISCCTRL_LUT_DIS 0x00 +#define M1064_XMISCCTRL_LUT_EN 0x10 +#define G400_XMISCCTRL_VDO_MAFC12 0x00 +#define G400_XMISCCTRL_VDO_BYPASS656 0x40 +#define G400_XMISCCTRL_VDO_C2_MAFC12 0x80 +#define G400_XMISCCTRL_VDO_C2_BYPASS656 0xC0 +#define G400_XMISCCTRL_VDO_MASK 0xE0 +#define M1064_XGENIOCTRL 0x2A +#define M1064_XGENIODATA 0x2B +#define DAC1064_XSYSPLLM 0x2C +#define DAC1064_XSYSPLLN 0x2D +#define DAC1064_XSYSPLLP 0x2E +#define DAC1064_XSYSPLLSTAT 0x2F +#define M1064_XZOOMCTRL 0x38 +#define M1064_XZOOMCTRL_1 0x00 +#define M1064_XZOOMCTRL_2 0x01 +#define M1064_XZOOMCTRL_4 0x03 +#define M1064_XSENSETEST 0x3A +#define M1064_XSENSETEST_BCOMP 0x01 +#define M1064_XSENSETEST_GCOMP 0x02 +#define M1064_XSENSETEST_RCOMP 0x04 +#define M1064_XSENSETEST_PDOWN 0x00 +#define M1064_XSENSETEST_PUP 0x80 +#define M1064_XCRCREML 0x3C +#define M1064_XCRCREMH 0x3D +#define M1064_XCRCBITSEL 0x3E +#define M1064_XCOLKEYMASKL 0x40 +#define M1064_XCOLKEYMASKH 0x41 +#define M1064_XCOLKEYL 0x42 +#define M1064_XCOLKEYH 0x43 +#define M1064_XPIXPLLAM 0x44 +#define M1064_XPIXPLLAN 0x45 +#define M1064_XPIXPLLAP 0x46 +#define M1064_XPIXPLLBM 0x48 +#define M1064_XPIXPLLBN 0x49 +#define M1064_XPIXPLLBP 0x4A +#define M1064_XPIXPLLCM 0x4C +#define M1064_XPIXPLLCN 0x4D +#define M1064_XPIXPLLCP 0x4E +#define M1064_XPIXPLLSTAT 0x4F + +#define M1064_XTVO_IDX 0x87 +#define M1064_XTVO_DATA 0x88 + +#define M1064_XOUTPUTCONN 0x8A +#define M1064_XSYNCCTRL 0x8B +#define M1064_XVIDPLLSTAT 0x8C +#define M1064_XVIDPLLP 0x8D +#define M1064_XVIDPLLM 0x8E +#define M1064_XVIDPLLN 0x8F + +#define M1064_XPWRCTRL 0xA0 +#define M1064_XPWRCTRL_PANELPDN 0x04 + +#define M1064_XPANMODE 0xA2 + +enum POS1064 { + POS1064_XCURADDL=0, POS1064_XCURADDH, POS1064_XCURCTRL, + POS1064_XCURCOL0RED, POS1064_XCURCOL0GREEN, POS1064_XCURCOL0BLUE, + POS1064_XCURCOL1RED, POS1064_XCURCOL1GREEN, POS1064_XCURCOL1BLUE, + POS1064_XCURCOL2RED, POS1064_XCURCOL2GREEN, POS1064_XCURCOL2BLUE, + POS1064_XVREFCTRL, POS1064_XMULCTRL, POS1064_XPIXCLKCTRL, POS1064_XGENCTRL, + POS1064_XMISCCTRL, + POS1064_XGENIOCTRL, POS1064_XGENIODATA, POS1064_XZOOMCTRL, POS1064_XSENSETEST, + POS1064_XCRCBITSEL, + POS1064_XCOLKEYMASKL, POS1064_XCOLKEYMASKH, POS1064_XCOLKEYL, POS1064_XCOLKEYH, + POS1064_XOUTPUTCONN, POS1064_XPANMODE, POS1064_XPWRCTRL }; + + +#endif /* __MATROXFB_DAC1064_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_Ti3026.c b/drivers/video/fbdev/matrox/matroxfb_Ti3026.c new file mode 100644 index 000000000000..195ad7cac1ba --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_Ti3026.c @@ -0,0 +1,745 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + + +#include "matroxfb_Ti3026.h" +#include "matroxfb_misc.h" +#include "matroxfb_accel.h" +#include <linux/matroxfb.h> + +#ifdef CONFIG_FB_MATROX_MILLENIUM +#define outTi3026 matroxfb_DAC_out +#define inTi3026 matroxfb_DAC_in + +#define TVP3026_INDEX 0x00 +#define TVP3026_PALWRADD 0x00 +#define TVP3026_PALDATA 0x01 +#define TVP3026_PIXRDMSK 0x02 +#define TVP3026_PALRDADD 0x03 +#define TVP3026_CURCOLWRADD 0x04 +#define TVP3026_CLOVERSCAN 0x00 +#define TVP3026_CLCOLOR0 0x01 +#define TVP3026_CLCOLOR1 0x02 +#define TVP3026_CLCOLOR2 0x03 +#define TVP3026_CURCOLDATA 0x05 +#define TVP3026_CURCOLRDADD 0x07 +#define TVP3026_CURCTRL 0x09 +#define TVP3026_X_DATAREG 0x0A +#define TVP3026_CURRAMDATA 0x0B +#define TVP3026_CURPOSXL 0x0C +#define TVP3026_CURPOSXH 0x0D +#define TVP3026_CURPOSYL 0x0E +#define TVP3026_CURPOSYH 0x0F + +#define TVP3026_XSILICONREV 0x01 +#define TVP3026_XCURCTRL 0x06 +#define TVP3026_XCURCTRL_DIS 0x00 /* transparent, transparent, transparent, transparent */ +#define TVP3026_XCURCTRL_3COLOR 0x01 /* transparent, 0, 1, 2 */ +#define TVP3026_XCURCTRL_XGA 0x02 /* 0, 1, transparent, complement */ +#define TVP3026_XCURCTRL_XWIN 0x03 /* transparent, transparent, 0, 1 */ +#define TVP3026_XCURCTRL_BLANK2048 0x00 +#define TVP3026_XCURCTRL_BLANK4096 0x10 +#define TVP3026_XCURCTRL_INTERLACED 0x20 +#define TVP3026_XCURCTRL_ODD 0x00 /* ext.signal ODD/\EVEN */ +#define TVP3026_XCURCTRL_EVEN 0x40 /* ext.signal EVEN/\ODD */ +#define TVP3026_XCURCTRL_INDIRECT 0x00 +#define TVP3026_XCURCTRL_DIRECT 0x80 +#define TVP3026_XLATCHCTRL 0x0F +#define TVP3026_XLATCHCTRL_1_1 0x06 +#define TVP3026_XLATCHCTRL_2_1 0x07 +#define TVP3026_XLATCHCTRL_4_1 0x06 +#define TVP3026_XLATCHCTRL_8_1 0x06 +#define TVP3026_XLATCHCTRL_16_1 0x06 +#define TVP3026A_XLATCHCTRL_4_3 0x06 /* ??? do not understand... but it works... !!! */ +#define TVP3026A_XLATCHCTRL_8_3 0x07 +#define TVP3026B_XLATCHCTRL_4_3 0x08 +#define TVP3026B_XLATCHCTRL_8_3 0x06 /* ??? do not understand... but it works... !!! */ +#define TVP3026_XTRUECOLORCTRL 0x18 +#define TVP3026_XTRUECOLORCTRL_VRAM_SHIFT_ACCEL 0x00 +#define TVP3026_XTRUECOLORCTRL_VRAM_SHIFT_TVP 0x20 +#define TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR 0x80 +#define TVP3026_XTRUECOLORCTRL_TRUECOLOR 0x40 /* paletized */ +#define TVP3026_XTRUECOLORCTRL_DIRECTCOLOR 0x00 +#define TVP3026_XTRUECOLORCTRL_24_ALTERNATE 0x08 /* 5:4/5:2 instead of 4:3/8:3 */ +#define TVP3026_XTRUECOLORCTRL_RGB_888 0x16 /* 4:3/8:3 (or 5:4/5:2) */ +#define TVP3026_XTRUECOLORCTRL_BGR_888 0x17 +#define TVP3026_XTRUECOLORCTRL_ORGB_8888 0x06 +#define TVP3026_XTRUECOLORCTRL_BGRO_8888 0x07 +#define TVP3026_XTRUECOLORCTRL_RGB_565 0x05 +#define TVP3026_XTRUECOLORCTRL_ORGB_1555 0x04 +#define TVP3026_XTRUECOLORCTRL_RGB_664 0x03 +#define TVP3026_XTRUECOLORCTRL_RGBO_4444 0x01 +#define TVP3026_XMUXCTRL 0x19 +#define TVP3026_XMUXCTRL_MEMORY_8BIT 0x01 /* - */ +#define TVP3026_XMUXCTRL_MEMORY_16BIT 0x02 /* - */ +#define TVP3026_XMUXCTRL_MEMORY_32BIT 0x03 /* 2MB RAM, 512K * 4 */ +#define TVP3026_XMUXCTRL_MEMORY_64BIT 0x04 /* >2MB RAM, 512K * 8 & more */ +#define TVP3026_XMUXCTRL_PIXEL_4BIT 0x40 /* L0,H0,L1,H1... */ +#define TVP3026_XMUXCTRL_PIXEL_4BIT_SWAPPED 0x60 /* H0,L0,H1,L1... */ +#define TVP3026_XMUXCTRL_PIXEL_8BIT 0x48 +#define TVP3026_XMUXCTRL_PIXEL_16BIT 0x50 +#define TVP3026_XMUXCTRL_PIXEL_32BIT 0x58 +#define TVP3026_XMUXCTRL_VGA 0x98 /* VGA MEMORY, 8BIT PIXEL */ +#define TVP3026_XCLKCTRL 0x1A +#define TVP3026_XCLKCTRL_DIV1 0x00 +#define TVP3026_XCLKCTRL_DIV2 0x10 +#define TVP3026_XCLKCTRL_DIV4 0x20 +#define TVP3026_XCLKCTRL_DIV8 0x30 +#define TVP3026_XCLKCTRL_DIV16 0x40 +#define TVP3026_XCLKCTRL_DIV32 0x50 +#define TVP3026_XCLKCTRL_DIV64 0x60 +#define TVP3026_XCLKCTRL_CLKSTOPPED 0x70 +#define TVP3026_XCLKCTRL_SRC_CLK0 0x00 +#define TVP3026_XCLKCTRL_SRC_CLK1 0x01 +#define TVP3026_XCLKCTRL_SRC_CLK2 0x02 /* CLK2 is TTL source*/ +#define TVP3026_XCLKCTRL_SRC_NCLK2 0x03 /* not CLK2 is TTL source */ +#define TVP3026_XCLKCTRL_SRC_ECLK2 0x04 /* CLK2 and not CLK2 is ECL source */ +#define TVP3026_XCLKCTRL_SRC_PLL 0x05 +#define TVP3026_XCLKCTRL_SRC_DIS 0x06 /* disable & poweroff internal clock */ +#define TVP3026_XCLKCTRL_SRC_CLK0VGA 0x07 +#define TVP3026_XPALETTEPAGE 0x1C +#define TVP3026_XGENCTRL 0x1D +#define TVP3026_XGENCTRL_HSYNC_POS 0x00 +#define TVP3026_XGENCTRL_HSYNC_NEG 0x01 +#define TVP3026_XGENCTRL_VSYNC_POS 0x00 +#define TVP3026_XGENCTRL_VSYNC_NEG 0x02 +#define TVP3026_XGENCTRL_LITTLE_ENDIAN 0x00 +#define TVP3026_XGENCTRL_BIG_ENDIAN 0x08 +#define TVP3026_XGENCTRL_BLACK_0IRE 0x00 +#define TVP3026_XGENCTRL_BLACK_75IRE 0x10 +#define TVP3026_XGENCTRL_NO_SYNC_ON_GREEN 0x00 +#define TVP3026_XGENCTRL_SYNC_ON_GREEN 0x20 +#define TVP3026_XGENCTRL_OVERSCAN_DIS 0x00 +#define TVP3026_XGENCTRL_OVERSCAN_EN 0x40 +#define TVP3026_XMISCCTRL 0x1E +#define TVP3026_XMISCCTRL_DAC_PUP 0x00 +#define TVP3026_XMISCCTRL_DAC_PDOWN 0x01 +#define TVP3026_XMISCCTRL_DAC_EXT 0x00 /* or 8, bit 3 is ignored */ +#define TVP3026_XMISCCTRL_DAC_6BIT 0x04 +#define TVP3026_XMISCCTRL_DAC_8BIT 0x0C +#define TVP3026_XMISCCTRL_PSEL_DIS 0x00 +#define TVP3026_XMISCCTRL_PSEL_EN 0x10 +#define TVP3026_XMISCCTRL_PSEL_LOW 0x00 /* PSEL high selects directcolor */ +#define TVP3026_XMISCCTRL_PSEL_HIGH 0x20 /* PSEL high selects truecolor or pseudocolor */ +#define TVP3026_XGENIOCTRL 0x2A +#define TVP3026_XGENIODATA 0x2B +#define TVP3026_XPLLADDR 0x2C +#define TVP3026_XPLLADDR_X(LOOP,MCLK,PIX) (((LOOP)<<4) | ((MCLK)<<2) | (PIX)) +#define TVP3026_XPLLDATA_N 0x00 +#define TVP3026_XPLLDATA_M 0x01 +#define TVP3026_XPLLDATA_P 0x02 +#define TVP3026_XPLLDATA_STAT 0x03 +#define TVP3026_XPIXPLLDATA 0x2D +#define TVP3026_XMEMPLLDATA 0x2E +#define TVP3026_XLOOPPLLDATA 0x2F +#define TVP3026_XCOLKEYOVRMIN 0x30 +#define TVP3026_XCOLKEYOVRMAX 0x31 +#define TVP3026_XCOLKEYREDMIN 0x32 +#define TVP3026_XCOLKEYREDMAX 0x33 +#define TVP3026_XCOLKEYGREENMIN 0x34 +#define TVP3026_XCOLKEYGREENMAX 0x35 +#define TVP3026_XCOLKEYBLUEMIN 0x36 +#define TVP3026_XCOLKEYBLUEMAX 0x37 +#define TVP3026_XCOLKEYCTRL 0x38 +#define TVP3026_XCOLKEYCTRL_OVR_EN 0x01 +#define TVP3026_XCOLKEYCTRL_RED_EN 0x02 +#define TVP3026_XCOLKEYCTRL_GREEN_EN 0x04 +#define TVP3026_XCOLKEYCTRL_BLUE_EN 0x08 +#define TVP3026_XCOLKEYCTRL_NEGATE 0x10 +#define TVP3026_XCOLKEYCTRL_ZOOM1 0x00 +#define TVP3026_XCOLKEYCTRL_ZOOM2 0x20 +#define TVP3026_XCOLKEYCTRL_ZOOM4 0x40 +#define TVP3026_XCOLKEYCTRL_ZOOM8 0x60 +#define TVP3026_XCOLKEYCTRL_ZOOM16 0x80 +#define TVP3026_XCOLKEYCTRL_ZOOM32 0xA0 +#define TVP3026_XMEMPLLCTRL 0x39 +#define TVP3026_XMEMPLLCTRL_DIV(X) (((X)-1)>>1) /* 2,4,6,8,10,12,14,16, division applied to LOOP PLL after divide by 2^P */ +#define TVP3026_XMEMPLLCTRL_STROBEMKC4 0x08 +#define TVP3026_XMEMPLLCTRL_MCLK_DOTCLOCK 0x00 /* MKC4 */ +#define TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL 0x10 /* MKC4 */ +#define TVP3026_XMEMPLLCTRL_RCLK_PIXPLL 0x00 +#define TVP3026_XMEMPLLCTRL_RCLK_LOOPPLL 0x20 +#define TVP3026_XMEMPLLCTRL_RCLK_DOTDIVN 0x40 /* dot clock divided by loop pclk N prescaler */ +#define TVP3026_XSENSETEST 0x3A +#define TVP3026_XTESTMODEDATA 0x3B +#define TVP3026_XCRCREML 0x3C +#define TVP3026_XCRCREMH 0x3D +#define TVP3026_XCRCBITSEL 0x3E +#define TVP3026_XID 0x3F + +static const unsigned char DACseq[] = +{ TVP3026_XLATCHCTRL, TVP3026_XTRUECOLORCTRL, + TVP3026_XMUXCTRL, TVP3026_XCLKCTRL, + TVP3026_XPALETTEPAGE, + TVP3026_XGENCTRL, + TVP3026_XMISCCTRL, + TVP3026_XGENIOCTRL, + TVP3026_XGENIODATA, + TVP3026_XCOLKEYOVRMIN, TVP3026_XCOLKEYOVRMAX, TVP3026_XCOLKEYREDMIN, TVP3026_XCOLKEYREDMAX, + TVP3026_XCOLKEYGREENMIN, TVP3026_XCOLKEYGREENMAX, TVP3026_XCOLKEYBLUEMIN, TVP3026_XCOLKEYBLUEMAX, + TVP3026_XCOLKEYCTRL, + TVP3026_XMEMPLLCTRL, TVP3026_XSENSETEST, TVP3026_XCURCTRL }; + +#define POS3026_XLATCHCTRL 0 +#define POS3026_XTRUECOLORCTRL 1 +#define POS3026_XMUXCTRL 2 +#define POS3026_XCLKCTRL 3 +#define POS3026_XGENCTRL 5 +#define POS3026_XMISCCTRL 6 +#define POS3026_XMEMPLLCTRL 18 +#define POS3026_XCURCTRL 20 + +static const unsigned char MGADACbpp32[] = +{ TVP3026_XLATCHCTRL_2_1, TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_ORGB_8888, + 0x00, TVP3026_XCLKCTRL_DIV1 | TVP3026_XCLKCTRL_SRC_PLL, + 0x00, + TVP3026_XGENCTRL_HSYNC_POS | TVP3026_XGENCTRL_VSYNC_POS | TVP3026_XGENCTRL_LITTLE_ENDIAN | TVP3026_XGENCTRL_BLACK_0IRE | TVP3026_XGENCTRL_NO_SYNC_ON_GREEN | TVP3026_XGENCTRL_OVERSCAN_DIS, + TVP3026_XMISCCTRL_DAC_PUP | TVP3026_XMISCCTRL_DAC_8BIT | TVP3026_XMISCCTRL_PSEL_DIS | TVP3026_XMISCCTRL_PSEL_HIGH, + 0x00, + 0x1E, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + TVP3026_XCOLKEYCTRL_ZOOM1, + 0x00, 0x00, TVP3026_XCURCTRL_DIS }; + +static int Ti3026_calcclock(const struct matrox_fb_info *minfo, + unsigned int freq, unsigned int fmax, int *in, + int *feed, int *post) +{ + unsigned int fvco; + unsigned int lin, lfeed, lpost; + + DBG(__func__) + + fvco = PLL_calcclock(minfo, freq, fmax, &lin, &lfeed, &lpost); + fvco >>= (*post = lpost); + *in = 64 - lin; + *feed = 64 - lfeed; + return fvco; +} + +static int Ti3026_setpclk(struct matrox_fb_info *minfo, int clk) +{ + unsigned int f_pll; + unsigned int pixfeed, pixin, pixpost; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + f_pll = Ti3026_calcclock(minfo, clk, minfo->max_pixel_clock, &pixin, &pixfeed, &pixpost); + + hw->DACclk[0] = pixin | 0xC0; + hw->DACclk[1] = pixfeed; + hw->DACclk[2] = pixpost | 0xB0; + + { + unsigned int loopfeed, loopin, looppost, loopdiv, z; + unsigned int Bpp; + + Bpp = minfo->curr.final_bppShift; + + if (minfo->fbcon.var.bits_per_pixel == 24) { + loopfeed = 3; /* set lm to any possible value */ + loopin = 3 * 32 / Bpp; + } else { + loopfeed = 4; + loopin = 4 * 32 / Bpp; + } + z = (110000 * loopin) / (f_pll * loopfeed); + loopdiv = 0; /* div 2 */ + if (z < 2) + looppost = 0; + else if (z < 4) + looppost = 1; + else if (z < 8) + looppost = 2; + else { + looppost = 3; + loopdiv = z/16; + } + if (minfo->fbcon.var.bits_per_pixel == 24) { + hw->DACclk[3] = ((65 - loopin) & 0x3F) | 0xC0; + hw->DACclk[4] = (65 - loopfeed) | 0x80; + if (minfo->accel.ramdac_rev > 0x20) { + if (isInterleave(minfo)) + hw->DACreg[POS3026_XLATCHCTRL] = TVP3026B_XLATCHCTRL_8_3; + else { + hw->DACclk[4] &= ~0xC0; + hw->DACreg[POS3026_XLATCHCTRL] = TVP3026B_XLATCHCTRL_4_3; + } + } else { + if (isInterleave(minfo)) + ; /* default... */ + else { + hw->DACclk[4] ^= 0xC0; /* change from 0x80 to 0x40 */ + hw->DACreg[POS3026_XLATCHCTRL] = TVP3026A_XLATCHCTRL_4_3; + } + } + hw->DACclk[5] = looppost | 0xF8; + if (minfo->devflags.mga_24bpp_fix) + hw->DACclk[5] ^= 0x40; + } else { + hw->DACclk[3] = ((65 - loopin) & 0x3F) | 0xC0; + hw->DACclk[4] = 65 - loopfeed; + hw->DACclk[5] = looppost | 0xF0; + } + hw->DACreg[POS3026_XMEMPLLCTRL] = loopdiv | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL | TVP3026_XMEMPLLCTRL_RCLK_LOOPPLL; + } + return 0; +} + +static int Ti3026_init(struct matrox_fb_info *minfo, struct my_timming *m) +{ + u_int8_t muxctrl = isInterleave(minfo) ? TVP3026_XMUXCTRL_MEMORY_64BIT : TVP3026_XMUXCTRL_MEMORY_32BIT; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + memcpy(hw->DACreg, MGADACbpp32, sizeof(hw->DACreg)); + switch (minfo->fbcon.var.bits_per_pixel) { + case 4: hw->DACreg[POS3026_XLATCHCTRL] = TVP3026_XLATCHCTRL_16_1; /* or _8_1, they are same */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR; + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_4BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV8; + hw->DACreg[POS3026_XMISCCTRL] = TVP3026_XMISCCTRL_DAC_PUP | TVP3026_XMISCCTRL_DAC_8BIT | TVP3026_XMISCCTRL_PSEL_DIS | TVP3026_XMISCCTRL_PSEL_LOW; + break; + case 8: hw->DACreg[POS3026_XLATCHCTRL] = TVP3026_XLATCHCTRL_8_1; /* or _4_1, they are same */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR; + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_8BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV4; + hw->DACreg[POS3026_XMISCCTRL] = TVP3026_XMISCCTRL_DAC_PUP | TVP3026_XMISCCTRL_DAC_8BIT | TVP3026_XMISCCTRL_PSEL_DIS | TVP3026_XMISCCTRL_PSEL_LOW; + break; + case 16: + /* XLATCHCTRL should be _4_1 / _2_1... Why is not? (_2_1 is used every time) */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = (minfo->fbcon.var.green.length == 5) ? (TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_ORGB_1555) : (TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_RGB_565); + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_16BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV2; + break; + case 24: + /* XLATCHCTRL is: for (A) use _4_3 (?_8_3 is same? TBD), for (B) it is set in setpclk */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_RGB_888; + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_32BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV4; + break; + case 32: + /* XLATCHCTRL should be _2_1 / _1_1... Why is not? (_2_1 is used every time) */ + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_32BIT; + break; + default: + return 1; /* TODO: failed */ + } + if (matroxfb_vgaHWinit(minfo, m)) return 1; + + /* set SYNC */ + hw->MiscOutReg = 0xCB; + if (m->sync & FB_SYNC_HOR_HIGH_ACT) + hw->DACreg[POS3026_XGENCTRL] |= TVP3026_XGENCTRL_HSYNC_NEG; + if (m->sync & FB_SYNC_VERT_HIGH_ACT) + hw->DACreg[POS3026_XGENCTRL] |= TVP3026_XGENCTRL_VSYNC_NEG; + if (m->sync & FB_SYNC_ON_GREEN) + hw->DACreg[POS3026_XGENCTRL] |= TVP3026_XGENCTRL_SYNC_ON_GREEN; + + /* set DELAY */ + if (minfo->video.len < 0x400000) + hw->CRTCEXT[3] |= 0x08; + else if (minfo->video.len > 0x400000) + hw->CRTCEXT[3] |= 0x10; + + /* set HWCURSOR */ + if (m->interlaced) { + hw->DACreg[POS3026_XCURCTRL] |= TVP3026_XCURCTRL_INTERLACED; + } + if (m->HTotal >= 1536) + hw->DACreg[POS3026_XCURCTRL] |= TVP3026_XCURCTRL_BLANK4096; + + /* set interleaving */ + hw->MXoptionReg &= ~0x00001000; + if (isInterleave(minfo)) hw->MXoptionReg |= 0x00001000; + + /* set DAC */ + Ti3026_setpclk(minfo, m->pixclock); + return 0; +} + +static void ti3026_setMCLK(struct matrox_fb_info *minfo, int fout) +{ + unsigned int f_pll; + unsigned int pclk_m, pclk_n, pclk_p; + unsigned int mclk_m, mclk_n, mclk_p; + unsigned int rfhcnt, mclk_ctl; + int tmout; + + DBG(__func__) + + f_pll = Ti3026_calcclock(minfo, fout, minfo->max_pixel_clock, &mclk_n, &mclk_m, &mclk_p); + + /* save pclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFC); + pclk_n = inTi3026(minfo, TVP3026_XPIXPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0xFD); + pclk_m = inTi3026(minfo, TVP3026_XPIXPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0xFE); + pclk_p = inTi3026(minfo, TVP3026_XPIXPLLDATA); + + /* stop pclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFE); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0x00); + + /* set pclk to new mclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFC); + outTi3026(minfo, TVP3026_XPIXPLLDATA, mclk_n | 0xC0); + outTi3026(minfo, TVP3026_XPIXPLLDATA, mclk_m); + outTi3026(minfo, TVP3026_XPIXPLLDATA, mclk_p | 0xB0); + + /* wait for PLL to lock */ + for (tmout = 500000; tmout; tmout--) { + if (inTi3026(minfo, TVP3026_XPIXPLLDATA) & 0x40) + break; + udelay(10); + } + if (!tmout) + printk(KERN_ERR "matroxfb: Temporary pixel PLL not locked after 5 secs\n"); + + /* output pclk on mclk pin */ + mclk_ctl = inTi3026(minfo, TVP3026_XMEMPLLCTRL); + outTi3026(minfo, TVP3026_XMEMPLLCTRL, mclk_ctl & 0xE7); + outTi3026(minfo, TVP3026_XMEMPLLCTRL, (mclk_ctl & 0xE7) | TVP3026_XMEMPLLCTRL_STROBEMKC4); + + /* stop MCLK */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFB); + outTi3026(minfo, TVP3026_XMEMPLLDATA, 0x00); + + /* set mclk to new freq */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xF3); + outTi3026(minfo, TVP3026_XMEMPLLDATA, mclk_n | 0xC0); + outTi3026(minfo, TVP3026_XMEMPLLDATA, mclk_m); + outTi3026(minfo, TVP3026_XMEMPLLDATA, mclk_p | 0xB0); + + /* wait for PLL to lock */ + for (tmout = 500000; tmout; tmout--) { + if (inTi3026(minfo, TVP3026_XMEMPLLDATA) & 0x40) + break; + udelay(10); + } + if (!tmout) + printk(KERN_ERR "matroxfb: Memory PLL not locked after 5 secs\n"); + + f_pll = f_pll * 333 / (10000 << mclk_p); + if (isMilleniumII(minfo)) { + rfhcnt = (f_pll - 128) / 256; + if (rfhcnt > 15) + rfhcnt = 15; + } else { + rfhcnt = (f_pll - 64) / 128; + if (rfhcnt > 15) + rfhcnt = 0; + } + minfo->hw.MXoptionReg = (minfo->hw.MXoptionReg & ~0x000F0000) | (rfhcnt << 16); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* output MCLK to MCLK pin */ + outTi3026(minfo, TVP3026_XMEMPLLCTRL, (mclk_ctl & 0xE7) | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL); + outTi3026(minfo, TVP3026_XMEMPLLCTRL, (mclk_ctl ) | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL | TVP3026_XMEMPLLCTRL_STROBEMKC4); + + /* stop PCLK */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFE); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0x00); + + /* restore pclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFC); + outTi3026(minfo, TVP3026_XPIXPLLDATA, pclk_n); + outTi3026(minfo, TVP3026_XPIXPLLDATA, pclk_m); + outTi3026(minfo, TVP3026_XPIXPLLDATA, pclk_p); + + /* wait for PLL to lock */ + for (tmout = 500000; tmout; tmout--) { + if (inTi3026(minfo, TVP3026_XPIXPLLDATA) & 0x40) + break; + udelay(10); + } + if (!tmout) + printk(KERN_ERR "matroxfb: Pixel PLL not locked after 5 secs\n"); +} + +static void ti3026_ramdac_init(struct matrox_fb_info *minfo) +{ + DBG(__func__) + + minfo->features.pll.vco_freq_min = 110000; + minfo->features.pll.ref_freq = 114545; + minfo->features.pll.feed_div_min = 2; + minfo->features.pll.feed_div_max = 24; + minfo->features.pll.in_div_min = 2; + minfo->features.pll.in_div_max = 63; + minfo->features.pll.post_shift_max = 3; + if (minfo->devflags.noinit) + return; + ti3026_setMCLK(minfo, 60000); +} + +static void Ti3026_restore(struct matrox_fb_info *minfo) +{ + int i; + unsigned char progdac[6]; + struct matrox_hw_state *hw = &minfo->hw; + CRITFLAGS + + DBG(__func__) + +#ifdef DEBUG + dprintk(KERN_INFO "EXTVGA regs: "); + for (i = 0; i < 6; i++) + dprintk("%02X:", hw->CRTCEXT[i]); + dprintk("\n"); +#endif + + CRITBEGIN + + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + + CRITEND + + matroxfb_vgaHWrestore(minfo); + + CRITBEGIN + + minfo->crtc1.panpos = -1; + for (i = 0; i < 6; i++) + mga_setr(M_EXTVGA_INDEX, i, hw->CRTCEXT[i]); + + for (i = 0; i < 21; i++) { + outTi3026(minfo, DACseq[i], hw->DACreg[i]); + } + + outTi3026(minfo, TVP3026_XPLLADDR, 0x00); + progdac[0] = inTi3026(minfo, TVP3026_XPIXPLLDATA); + progdac[3] = inTi3026(minfo, TVP3026_XLOOPPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0x15); + progdac[1] = inTi3026(minfo, TVP3026_XPIXPLLDATA); + progdac[4] = inTi3026(minfo, TVP3026_XLOOPPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0x2A); + progdac[2] = inTi3026(minfo, TVP3026_XPIXPLLDATA); + progdac[5] = inTi3026(minfo, TVP3026_XLOOPPLLDATA); + + CRITEND + if (memcmp(hw->DACclk, progdac, 6)) { + /* agrhh... setting up PLL is very slow on Millennium... */ + /* Mystique PLL is locked in few ms, but Millennium PLL lock takes about 0.15 s... */ + /* Maybe even we should call schedule() ? */ + + CRITBEGIN + outTi3026(minfo, TVP3026_XCLKCTRL, hw->DACreg[POS3026_XCLKCTRL]); + outTi3026(minfo, TVP3026_XPLLADDR, 0x2A); + outTi3026(minfo, TVP3026_XLOOPPLLDATA, 0); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0); + + outTi3026(minfo, TVP3026_XPLLADDR, 0x00); + for (i = 0; i < 3; i++) + outTi3026(minfo, TVP3026_XPIXPLLDATA, hw->DACclk[i]); + /* wait for PLL only if PLL clock requested (always for PowerMode, never for VGA) */ + if (hw->MiscOutReg & 0x08) { + int tmout; + outTi3026(minfo, TVP3026_XPLLADDR, 0x3F); + for (tmout = 500000; tmout; --tmout) { + if (inTi3026(minfo, TVP3026_XPIXPLLDATA) & 0x40) + break; + udelay(10); + } + + CRITEND + + if (!tmout) + printk(KERN_ERR "matroxfb: Pixel PLL not locked after 5 secs\n"); + else + dprintk(KERN_INFO "PixelPLL: %d\n", 500000-tmout); + CRITBEGIN + } + outTi3026(minfo, TVP3026_XMEMPLLCTRL, hw->DACreg[POS3026_XMEMPLLCTRL]); + outTi3026(minfo, TVP3026_XPLLADDR, 0x00); + for (i = 3; i < 6; i++) + outTi3026(minfo, TVP3026_XLOOPPLLDATA, hw->DACclk[i]); + CRITEND + if ((hw->MiscOutReg & 0x08) && ((hw->DACclk[5] & 0x80) == 0x80)) { + int tmout; + + CRITBEGIN + outTi3026(minfo, TVP3026_XPLLADDR, 0x3F); + for (tmout = 500000; tmout; --tmout) { + if (inTi3026(minfo, TVP3026_XLOOPPLLDATA) & 0x40) + break; + udelay(10); + } + CRITEND + if (!tmout) + printk(KERN_ERR "matroxfb: Loop PLL not locked after 5 secs\n"); + else + dprintk(KERN_INFO "LoopPLL: %d\n", 500000-tmout); + } + } + +#ifdef DEBUG + dprintk(KERN_DEBUG "3026DACregs "); + for (i = 0; i < 21; i++) { + dprintk("R%02X=%02X ", DACseq[i], hw->DACreg[i]); + if ((i & 0x7) == 0x7) dprintk(KERN_DEBUG "continuing... "); + } + dprintk(KERN_DEBUG "DACclk "); + for (i = 0; i < 6; i++) + dprintk("C%02X=%02X ", i, hw->DACclk[i]); + dprintk("\n"); +#endif +} + +static void Ti3026_reset(struct matrox_fb_info *minfo) +{ + DBG(__func__) + + ti3026_ramdac_init(minfo); +} + +static struct matrox_altout ti3026_output = { + .name = "Primary output", +}; + +static int Ti3026_preinit(struct matrox_fb_info *minfo) +{ + static const int vxres_mill2[] = { 512, 640, 768, 800, 832, 960, + 1024, 1152, 1280, 1600, 1664, 1920, + 2048, 0}; + static const int vxres_mill1[] = { 640, 768, 800, 960, + 1024, 1152, 1280, 1600, 1920, + 2048, 0}; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + minfo->millenium = 1; + minfo->milleniumII = (minfo->pcidev->device != PCI_DEVICE_ID_MATROX_MIL); + minfo->capable.cfb4 = 1; + minfo->capable.text = 1; /* isMilleniumII(minfo); */ + minfo->capable.vxres = isMilleniumII(minfo) ? vxres_mill2 : vxres_mill1; + + minfo->outputs[0].data = minfo; + minfo->outputs[0].output = &ti3026_output; + minfo->outputs[0].src = minfo->outputs[0].default_src; + minfo->outputs[0].mode = MATROXFB_OUTPUT_MODE_MONITOR; + + if (minfo->devflags.noinit) + return 0; + /* preserve VGA I/O, BIOS and PPC */ + hw->MXoptionReg &= 0xC0000100; + hw->MXoptionReg |= 0x002C0000; + if (minfo->devflags.novga) + hw->MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + hw->MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + hw->MXoptionReg |= 0x20000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + + minfo->accel.ramdac_rev = inTi3026(minfo, TVP3026_XSILICONREV); + + outTi3026(minfo, TVP3026_XCLKCTRL, TVP3026_XCLKCTRL_SRC_CLK0VGA | TVP3026_XCLKCTRL_CLKSTOPPED); + outTi3026(minfo, TVP3026_XTRUECOLORCTRL, TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR); + outTi3026(minfo, TVP3026_XMUXCTRL, TVP3026_XMUXCTRL_VGA); + + outTi3026(minfo, TVP3026_XPLLADDR, 0x2A); + outTi3026(minfo, TVP3026_XLOOPPLLDATA, 0x00); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0x00); + + mga_outb(M_MISC_REG, 0x67); + + outTi3026(minfo, TVP3026_XMEMPLLCTRL, TVP3026_XMEMPLLCTRL_STROBEMKC4 | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL); + + mga_outl(M_RESET, 1); + udelay(250); + mga_outl(M_RESET, 0); + udelay(250); + mga_outl(M_MACCESS, 0x00008000); + udelay(10); + return 0; +} + +struct matrox_switch matrox_millennium = { + Ti3026_preinit, Ti3026_reset, Ti3026_init, Ti3026_restore +}; +EXPORT_SYMBOL(matrox_millennium); +#endif +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_Ti3026.h b/drivers/video/fbdev/matrox/matroxfb_Ti3026.h new file mode 100644 index 000000000000..27872aaa0a17 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_Ti3026.h @@ -0,0 +1,11 @@ +#ifndef __MATROXFB_TI3026_H__ +#define __MATROXFB_TI3026_H__ + + +#include "matroxfb_base.h" + +#ifdef CONFIG_FB_MATROX_MILLENIUM +extern struct matrox_switch matrox_millennium; +#endif + +#endif /* __MATROXFB_TI3026_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_accel.c b/drivers/video/fbdev/matrox/matroxfb_accel.c new file mode 100644 index 000000000000..0d5cb85d071a --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_accel.c @@ -0,0 +1,519 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + +#include "matroxfb_accel.h" +#include "matroxfb_DAC1064.h" +#include "matroxfb_Ti3026.h" +#include "matroxfb_misc.h" + +#define curr_ydstorg(x) ((x)->curr.ydstorg.pixels) + +#define mga_ydstlen(y,l) mga_outl(M_YDSTLEN | M_EXEC, ((y) << 16) | (l)) + +static inline void matrox_cfb4_pal(u_int32_t* pal) { + unsigned int i; + + for (i = 0; i < 16; i++) { + pal[i] = i * 0x11111111U; + } +} + +static inline void matrox_cfb8_pal(u_int32_t* pal) { + unsigned int i; + + for (i = 0; i < 16; i++) { + pal[i] = i * 0x01010101U; + } +} + +static void matroxfb_copyarea(struct fb_info* info, const struct fb_copyarea* area); +static void matroxfb_fillrect(struct fb_info* info, const struct fb_fillrect* rect); +static void matroxfb_imageblit(struct fb_info* info, const struct fb_image* image); +static void matroxfb_cfb4_fillrect(struct fb_info* info, const struct fb_fillrect* rect); +static void matroxfb_cfb4_copyarea(struct fb_info* info, const struct fb_copyarea* area); + +void matrox_cfbX_init(struct matrox_fb_info *minfo) +{ + u_int32_t maccess; + u_int32_t mpitch; + u_int32_t mopmode; + int accel; + + DBG(__func__) + + mpitch = minfo->fbcon.var.xres_virtual; + + minfo->fbops.fb_copyarea = cfb_copyarea; + minfo->fbops.fb_fillrect = cfb_fillrect; + minfo->fbops.fb_imageblit = cfb_imageblit; + minfo->fbops.fb_cursor = NULL; + + accel = (minfo->fbcon.var.accel_flags & FB_ACCELF_TEXT) == FB_ACCELF_TEXT; + + switch (minfo->fbcon.var.bits_per_pixel) { + case 4: maccess = 0x00000000; /* accelerate as 8bpp video */ + mpitch = (mpitch >> 1) | 0x8000; /* disable linearization */ + mopmode = M_OPMODE_4BPP; + matrox_cfb4_pal(minfo->cmap); + if (accel && !(mpitch & 1)) { + minfo->fbops.fb_copyarea = matroxfb_cfb4_copyarea; + minfo->fbops.fb_fillrect = matroxfb_cfb4_fillrect; + } + break; + case 8: maccess = 0x00000000; + mopmode = M_OPMODE_8BPP; + matrox_cfb8_pal(minfo->cmap); + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + case 16: if (minfo->fbcon.var.green.length == 5) + maccess = 0xC0000001; + else + maccess = 0x40000001; + mopmode = M_OPMODE_16BPP; + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + case 24: maccess = 0x00000003; + mopmode = M_OPMODE_24BPP; + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + case 32: maccess = 0x00000002; + mopmode = M_OPMODE_32BPP; + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + default: maccess = 0x00000000; + mopmode = 0x00000000; + break; /* turn off acceleration!!! */ + } + mga_fifo(8); + mga_outl(M_PITCH, mpitch); + mga_outl(M_YDSTORG, curr_ydstorg(minfo)); + if (minfo->capable.plnwt) + mga_outl(M_PLNWT, -1); + if (minfo->capable.srcorg) { + mga_outl(M_SRCORG, 0); + mga_outl(M_DSTORG, 0); + } + mga_outl(M_OPMODE, mopmode); + mga_outl(M_CXBNDRY, 0xFFFF0000); + mga_outl(M_YTOP, 0); + mga_outl(M_YBOT, 0x01FFFFFF); + mga_outl(M_MACCESS, maccess); + minfo->accel.m_dwg_rect = M_DWG_TRAP | M_DWG_SOLID | M_DWG_ARZERO | M_DWG_SGNZERO | M_DWG_SHIFTZERO; + if (isMilleniumII(minfo)) minfo->accel.m_dwg_rect |= M_DWG_TRANSC; + minfo->accel.m_opmode = mopmode; + minfo->accel.m_access = maccess; + minfo->accel.m_pitch = mpitch; +} + +EXPORT_SYMBOL(matrox_cfbX_init); + +static void matrox_accel_restore_maccess(struct matrox_fb_info *minfo) +{ + mga_outl(M_MACCESS, minfo->accel.m_access); + mga_outl(M_PITCH, minfo->accel.m_pitch); +} + +static void matrox_accel_bmove(struct matrox_fb_info *minfo, int vxres, int sy, + int sx, int dy, int dx, int height, int width) +{ + int start, end; + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + if ((dy < sy) || ((dy == sy) && (dx <= sx))) { + mga_fifo(4); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_SGNZERO | + M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_AR5, vxres); + width--; + start = sy*vxres+sx+curr_ydstorg(minfo); + end = start+width; + } else { + mga_fifo(5); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_SGN, 5); + mga_outl(M_AR5, -vxres); + width--; + end = (sy+height-1)*vxres+sx+curr_ydstorg(minfo); + start = end+width; + dy += height-1; + } + mga_fifo(6); + matrox_accel_restore_maccess(minfo); + mga_outl(M_AR0, end); + mga_outl(M_AR3, start); + mga_outl(M_FXBNDRY, ((dx+width)<<16) | dx); + mga_ydstlen(dy, height); + WaitTillIdle(); + + CRITEND +} + +static void matrox_accel_bmove_lin(struct matrox_fb_info *minfo, int vxres, + int sy, int sx, int dy, int dx, int height, + int width) +{ + int start, end; + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + if ((dy < sy) || ((dy == sy) && (dx <= sx))) { + mga_fifo(4); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_SGNZERO | + M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_AR5, vxres); + width--; + start = sy*vxres+sx+curr_ydstorg(minfo); + end = start+width; + } else { + mga_fifo(5); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_SGN, 5); + mga_outl(M_AR5, -vxres); + width--; + end = (sy+height-1)*vxres+sx+curr_ydstorg(minfo); + start = end+width; + dy += height-1; + } + mga_fifo(7); + matrox_accel_restore_maccess(minfo); + mga_outl(M_AR0, end); + mga_outl(M_AR3, start); + mga_outl(M_FXBNDRY, ((dx+width)<<16) | dx); + mga_outl(M_YDST, dy*vxres >> 5); + mga_outl(M_LEN | M_EXEC, height); + WaitTillIdle(); + + CRITEND +} + +static void matroxfb_cfb4_copyarea(struct fb_info* info, const struct fb_copyarea* area) { + struct matrox_fb_info *minfo = info2minfo(info); + + if ((area->sx | area->dx | area->width) & 1) + cfb_copyarea(info, area); + else + matrox_accel_bmove_lin(minfo, minfo->fbcon.var.xres_virtual >> 1, area->sy, area->sx >> 1, area->dy, area->dx >> 1, area->height, area->width >> 1); +} + +static void matroxfb_copyarea(struct fb_info* info, const struct fb_copyarea* area) { + struct matrox_fb_info *minfo = info2minfo(info); + + matrox_accel_bmove(minfo, minfo->fbcon.var.xres_virtual, area->sy, area->sx, area->dy, area->dx, area->height, area->width); +} + +static void matroxfb_accel_clear(struct matrox_fb_info *minfo, u_int32_t color, + int sy, int sx, int height, int width) +{ + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + mga_fifo(7); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, minfo->accel.m_dwg_rect | M_DWG_REPLACE); + mga_outl(M_FCOL, color); + mga_outl(M_FXBNDRY, ((sx + width) << 16) | sx); + mga_ydstlen(sy, height); + WaitTillIdle(); + + CRITEND +} + +static void matroxfb_fillrect(struct fb_info* info, const struct fb_fillrect* rect) { + struct matrox_fb_info *minfo = info2minfo(info); + + switch (rect->rop) { + case ROP_COPY: + matroxfb_accel_clear(minfo, ((u_int32_t *)info->pseudo_palette)[rect->color], rect->dy, rect->dx, rect->height, rect->width); + break; + } +} + +static void matroxfb_cfb4_clear(struct matrox_fb_info *minfo, u_int32_t bgx, + int sy, int sx, int height, int width) +{ + int whattodo; + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + whattodo = 0; + if (sx & 1) { + sx ++; + if (!width) return; + width --; + whattodo = 1; + } + if (width & 1) { + whattodo |= 2; + } + width >>= 1; + sx >>= 1; + if (width) { + mga_fifo(7); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, minfo->accel.m_dwg_rect | M_DWG_REPLACE2); + mga_outl(M_FCOL, bgx); + mga_outl(M_FXBNDRY, ((sx + width) << 16) | sx); + mga_outl(M_YDST, sy * minfo->fbcon.var.xres_virtual >> 6); + mga_outl(M_LEN | M_EXEC, height); + WaitTillIdle(); + } + if (whattodo) { + u_int32_t step = minfo->fbcon.var.xres_virtual >> 1; + vaddr_t vbase = minfo->video.vbase; + if (whattodo & 1) { + unsigned int uaddr = sy * step + sx - 1; + u_int32_t loop; + u_int8_t bgx2 = bgx & 0xF0; + for (loop = height; loop > 0; loop --) { + mga_writeb(vbase, uaddr, (mga_readb(vbase, uaddr) & 0x0F) | bgx2); + uaddr += step; + } + } + if (whattodo & 2) { + unsigned int uaddr = sy * step + sx + width; + u_int32_t loop; + u_int8_t bgx2 = bgx & 0x0F; + for (loop = height; loop > 0; loop --) { + mga_writeb(vbase, uaddr, (mga_readb(vbase, uaddr) & 0xF0) | bgx2); + uaddr += step; + } + } + } + + CRITEND +} + +static void matroxfb_cfb4_fillrect(struct fb_info* info, const struct fb_fillrect* rect) { + struct matrox_fb_info *minfo = info2minfo(info); + + switch (rect->rop) { + case ROP_COPY: + matroxfb_cfb4_clear(minfo, ((u_int32_t *)info->pseudo_palette)[rect->color], rect->dy, rect->dx, rect->height, rect->width); + break; + } +} + +static void matroxfb_1bpp_imageblit(struct matrox_fb_info *minfo, u_int32_t fgx, + u_int32_t bgx, const u_int8_t *chardata, + int width, int height, int yy, int xx) +{ + u_int32_t step; + u_int32_t ydstlen; + u_int32_t xlen; + u_int32_t ar0; + u_int32_t charcell; + u_int32_t fxbndry; + vaddr_t mmio; + int easy; + CRITFLAGS + + DBG_HEAVY(__func__); + + step = (width + 7) >> 3; + charcell = height * step; + xlen = (charcell + 3) & ~3; + ydstlen = (yy << 16) | height; + if (width == step << 3) { + ar0 = height * width - 1; + easy = 1; + } else { + ar0 = width - 1; + easy = 0; + } + + CRITBEGIN + + mga_fifo(5); + matrox_accel_restore_maccess(minfo); + if (easy) + mga_outl(M_DWGCTL, M_DWG_ILOAD | M_DWG_SGNZERO | M_DWG_SHIFTZERO | M_DWG_BMONOWF | M_DWG_LINEAR | M_DWG_REPLACE); + else + mga_outl(M_DWGCTL, M_DWG_ILOAD | M_DWG_SGNZERO | M_DWG_SHIFTZERO | M_DWG_BMONOWF | M_DWG_REPLACE); + mga_outl(M_FCOL, fgx); + mga_outl(M_BCOL, bgx); + fxbndry = ((xx + width - 1) << 16) | xx; + mmio = minfo->mmio.vbase; + + mga_fifo(8); + matrox_accel_restore_maccess(minfo); + mga_writel(mmio, M_FXBNDRY, fxbndry); + mga_writel(mmio, M_AR0, ar0); + mga_writel(mmio, M_AR3, 0); + if (easy) { + mga_writel(mmio, M_YDSTLEN | M_EXEC, ydstlen); + mga_memcpy_toio(mmio, chardata, xlen); + } else { + mga_writel(mmio, M_AR5, 0); + mga_writel(mmio, M_YDSTLEN | M_EXEC, ydstlen); + if ((step & 3) == 0) { + /* Great. Source has 32bit aligned lines, so we can feed them + directly to the accelerator. */ + mga_memcpy_toio(mmio, chardata, charcell); + } else if (step == 1) { + /* Special case for 1..8bit widths */ + while (height--) { +#if defined(__BIG_ENDIAN) + fb_writel((*chardata) << 24, mmio.vaddr); +#else + fb_writel(*chardata, mmio.vaddr); +#endif + chardata++; + } + } else if (step == 2) { + /* Special case for 9..15bit widths */ + while (height--) { +#if defined(__BIG_ENDIAN) + fb_writel((*(u_int16_t*)chardata) << 16, mmio.vaddr); +#else + fb_writel(*(u_int16_t*)chardata, mmio.vaddr); +#endif + chardata += 2; + } + } else { + /* Tell... well, why bother... */ + while (height--) { + size_t i; + + for (i = 0; i < step; i += 4) { + /* Hope that there are at least three readable bytes beyond the end of bitmap */ + fb_writel(get_unaligned((u_int32_t*)(chardata + i)),mmio.vaddr); + } + chardata += step; + } + } + } + WaitTillIdle(); + CRITEND +} + + +static void matroxfb_imageblit(struct fb_info* info, const struct fb_image* image) { + struct matrox_fb_info *minfo = info2minfo(info); + + DBG_HEAVY(__func__); + + if (image->depth == 1) { + u_int32_t fgx, bgx; + + fgx = ((u_int32_t*)info->pseudo_palette)[image->fg_color]; + bgx = ((u_int32_t*)info->pseudo_palette)[image->bg_color]; + matroxfb_1bpp_imageblit(minfo, fgx, bgx, image->data, image->width, image->height, image->dy, image->dx); + } else { + /* Danger! image->depth is useless: logo painting code always + passes framebuffer color depth here, although logo data are + always 8bpp and info->pseudo_palette is changed to contain + logo palette to be used (but only for true/direct-color... sic...). + So do it completely in software... */ + cfb_imageblit(info, image); + } +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_accel.h b/drivers/video/fbdev/matrox/matroxfb_accel.h new file mode 100644 index 000000000000..1e418e62c22d --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_accel.h @@ -0,0 +1,8 @@ +#ifndef __MATROXFB_ACCEL_H__ +#define __MATROXFB_ACCEL_H__ + +#include "matroxfb_base.h" + +void matrox_cfbX_init(struct matrox_fb_info *minfo); + +#endif diff --git a/drivers/video/fbdev/matrox/matroxfb_base.c b/drivers/video/fbdev/matrox/matroxfb_base.c new file mode 100644 index 000000000000..7116c5309c7d --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_base.c @@ -0,0 +1,2584 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * "Samuel Hocevar" <sam@via.ecp.fr> + * Fixes + * + * "Anton Altaparmakov" <AntonA@bigfoot.com> + * G400 MAX/non-MAX distinction + * + * "Ken Aaker" <kdaaker@rchland.vnet.ibm.com> + * memtype extension (needed for GXT130P RS/6000 adapter) + * + * "Uns Lider" <unslider@miranda.org> + * G100 PLNWT fixes + * + * "Denis Zaitsev" <zzz@cd-club.ru> + * Fixes + * + * "Mike Pieper" <mike@pieper-family.de> + * TVOut enhandcements, V4L2 control interface. + * + * "Diego Biurrun" <diego@biurrun.de> + * DFP testing + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + +#include <linux/version.h> + +#include "matroxfb_base.h" +#include "matroxfb_misc.h" +#include "matroxfb_accel.h" +#include "matroxfb_DAC1064.h" +#include "matroxfb_Ti3026.h" +#include "matroxfb_maven.h" +#include "matroxfb_crtc2.h" +#include "matroxfb_g450.h" +#include <linux/matroxfb.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#ifdef CONFIG_PPC_PMAC +#include <asm/machdep.h> +unsigned char nvram_read_byte(int); +static int default_vmode = VMODE_NVRAM; +static int default_cmode = CMODE_NVRAM; +#endif + +static void matroxfb_unregister_device(struct matrox_fb_info* minfo); + +/* --------------------------------------------------------------------- */ + +/* + * card parameters + */ + +/* --------------------------------------------------------------------- */ + +static struct fb_var_screeninfo vesafb_defined = { + 640,480,640,480,/* W,H, W, H (virtual) load xres,xres_virtual*/ + 0,0, /* virtual -> visible no offset */ + 8, /* depth -> load bits_per_pixel */ + 0, /* greyscale ? */ + {0,0,0}, /* R */ + {0,0,0}, /* G */ + {0,0,0}, /* B */ + {0,0,0}, /* transparency */ + 0, /* standard pixel format */ + FB_ACTIVATE_NOW, + -1,-1, + FB_ACCELF_TEXT, /* accel flags */ + 39721L,48L,16L,33L,10L, + 96L,2L,~0, /* No sync info */ + FB_VMODE_NONINTERLACED, +}; + + + +/* --------------------------------------------------------------------- */ +static void update_crtc2(struct matrox_fb_info *minfo, unsigned int pos) +{ + struct matroxfb_dh_fb_info *info = minfo->crtc2.info; + + /* Make sure that displays are compatible */ + if (info && (info->fbcon.var.bits_per_pixel == minfo->fbcon.var.bits_per_pixel) + && (info->fbcon.var.xres_virtual == minfo->fbcon.var.xres_virtual) + && (info->fbcon.var.green.length == minfo->fbcon.var.green.length) + ) { + switch (minfo->fbcon.var.bits_per_pixel) { + case 16: + case 32: + pos = pos * 8; + if (info->interlaced) { + mga_outl(0x3C2C, pos); + mga_outl(0x3C28, pos + minfo->fbcon.var.xres_virtual * minfo->fbcon.var.bits_per_pixel / 8); + } else { + mga_outl(0x3C28, pos); + } + break; + } + } +} + +static void matroxfb_crtc1_panpos(struct matrox_fb_info *minfo) +{ + if (minfo->crtc1.panpos >= 0) { + unsigned long flags; + int panpos; + + matroxfb_DAC_lock_irqsave(flags); + panpos = minfo->crtc1.panpos; + if (panpos >= 0) { + unsigned int extvga_reg; + + minfo->crtc1.panpos = -1; /* No update pending anymore */ + extvga_reg = mga_inb(M_EXTVGA_INDEX); + mga_setr(M_EXTVGA_INDEX, 0x00, panpos); + if (extvga_reg != 0x00) { + mga_outb(M_EXTVGA_INDEX, extvga_reg); + } + } + matroxfb_DAC_unlock_irqrestore(flags); + } +} + +static irqreturn_t matrox_irq(int irq, void *dev_id) +{ + u_int32_t status; + int handled = 0; + struct matrox_fb_info *minfo = dev_id; + + status = mga_inl(M_STATUS); + + if (status & 0x20) { + mga_outl(M_ICLEAR, 0x20); + minfo->crtc1.vsync.cnt++; + matroxfb_crtc1_panpos(minfo); + wake_up_interruptible(&minfo->crtc1.vsync.wait); + handled = 1; + } + if (status & 0x200) { + mga_outl(M_ICLEAR, 0x200); + minfo->crtc2.vsync.cnt++; + wake_up_interruptible(&minfo->crtc2.vsync.wait); + handled = 1; + } + return IRQ_RETVAL(handled); +} + +int matroxfb_enable_irq(struct matrox_fb_info *minfo, int reenable) +{ + u_int32_t bm; + + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) + bm = 0x220; + else + bm = 0x020; + + if (!test_and_set_bit(0, &minfo->irq_flags)) { + if (request_irq(minfo->pcidev->irq, matrox_irq, + IRQF_SHARED, "matroxfb", minfo)) { + clear_bit(0, &minfo->irq_flags); + return -EINVAL; + } + /* Clear any pending field interrupts */ + mga_outl(M_ICLEAR, bm); + mga_outl(M_IEN, mga_inl(M_IEN) | bm); + } else if (reenable) { + u_int32_t ien; + + ien = mga_inl(M_IEN); + if ((ien & bm) != bm) { + printk(KERN_DEBUG "matroxfb: someone disabled IRQ [%08X]\n", ien); + mga_outl(M_IEN, ien | bm); + } + } + return 0; +} + +static void matroxfb_disable_irq(struct matrox_fb_info *minfo) +{ + if (test_and_clear_bit(0, &minfo->irq_flags)) { + /* Flush pending pan-at-vbl request... */ + matroxfb_crtc1_panpos(minfo); + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) + mga_outl(M_IEN, mga_inl(M_IEN) & ~0x220); + else + mga_outl(M_IEN, mga_inl(M_IEN) & ~0x20); + free_irq(minfo->pcidev->irq, minfo); + } +} + +int matroxfb_wait_for_sync(struct matrox_fb_info *minfo, u_int32_t crtc) +{ + struct matrox_vsync *vs; + unsigned int cnt; + int ret; + + switch (crtc) { + case 0: + vs = &minfo->crtc1.vsync; + break; + case 1: + if (minfo->devflags.accelerator != FB_ACCEL_MATROX_MGAG400) { + return -ENODEV; + } + vs = &minfo->crtc2.vsync; + break; + default: + return -ENODEV; + } + ret = matroxfb_enable_irq(minfo, 0); + if (ret) { + return ret; + } + + cnt = vs->cnt; + ret = wait_event_interruptible_timeout(vs->wait, cnt != vs->cnt, HZ/10); + if (ret < 0) { + return ret; + } + if (ret == 0) { + matroxfb_enable_irq(minfo, 1); + return -ETIMEDOUT; + } + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void matrox_pan_var(struct matrox_fb_info *minfo, + struct fb_var_screeninfo *var) +{ + unsigned int pos; + unsigned short p0, p1, p2; + unsigned int p3; + int vbl; + unsigned long flags; + + CRITFLAGS + + DBG(__func__) + + if (minfo->dead) + return; + + minfo->fbcon.var.xoffset = var->xoffset; + minfo->fbcon.var.yoffset = var->yoffset; + pos = (minfo->fbcon.var.yoffset * minfo->fbcon.var.xres_virtual + minfo->fbcon.var.xoffset) * minfo->curr.final_bppShift / 32; + pos += minfo->curr.ydstorg.chunks; + p0 = minfo->hw.CRTC[0x0D] = pos & 0xFF; + p1 = minfo->hw.CRTC[0x0C] = (pos & 0xFF00) >> 8; + p2 = minfo->hw.CRTCEXT[0] = (minfo->hw.CRTCEXT[0] & 0xB0) | ((pos >> 16) & 0x0F) | ((pos >> 14) & 0x40); + p3 = minfo->hw.CRTCEXT[8] = pos >> 21; + + /* FB_ACTIVATE_VBL and we can acquire interrupts? Honor FB_ACTIVATE_VBL then... */ + vbl = (var->activate & FB_ACTIVATE_VBL) && (matroxfb_enable_irq(minfo, 0) == 0); + + CRITBEGIN + + matroxfb_DAC_lock_irqsave(flags); + mga_setr(M_CRTC_INDEX, 0x0D, p0); + mga_setr(M_CRTC_INDEX, 0x0C, p1); + if (minfo->devflags.support32MB) + mga_setr(M_EXTVGA_INDEX, 0x08, p3); + if (vbl) { + minfo->crtc1.panpos = p2; + } else { + /* Abort any pending change */ + minfo->crtc1.panpos = -1; + mga_setr(M_EXTVGA_INDEX, 0x00, p2); + } + matroxfb_DAC_unlock_irqrestore(flags); + + update_crtc2(minfo, pos); + + CRITEND +} + +static void matroxfb_remove(struct matrox_fb_info *minfo, int dummy) +{ + /* Currently we are holding big kernel lock on all dead & usecount updates. + * Destroy everything after all users release it. Especially do not unregister + * framebuffer and iounmap memory, neither fbmem nor fbcon-cfb* does not check + * for device unplugged when in use. + * In future we should point mmio.vbase & video.vbase somewhere where we can + * write data without causing too much damage... + */ + + minfo->dead = 1; + if (minfo->usecount) { + /* destroy it later */ + return; + } + matroxfb_unregister_device(minfo); + unregister_framebuffer(&minfo->fbcon); + matroxfb_g450_shutdown(minfo); +#ifdef CONFIG_MTRR + if (minfo->mtrr.vram_valid) + mtrr_del(minfo->mtrr.vram, minfo->video.base, minfo->video.len); +#endif + mga_iounmap(minfo->mmio.vbase); + mga_iounmap(minfo->video.vbase); + release_mem_region(minfo->video.base, minfo->video.len_maximum); + release_mem_region(minfo->mmio.base, 16384); + kfree(minfo); +} + + /* + * Open/Release the frame buffer device + */ + +static int matroxfb_open(struct fb_info *info, int user) +{ + struct matrox_fb_info *minfo = info2minfo(info); + + DBG_LOOP(__func__) + + if (minfo->dead) { + return -ENXIO; + } + minfo->usecount++; + if (user) { + minfo->userusecount++; + } + return(0); +} + +static int matroxfb_release(struct fb_info *info, int user) +{ + struct matrox_fb_info *minfo = info2minfo(info); + + DBG_LOOP(__func__) + + if (user) { + if (0 == --minfo->userusecount) { + matroxfb_disable_irq(minfo); + } + } + if (!(--minfo->usecount) && minfo->dead) { + matroxfb_remove(minfo, 0); + } + return(0); +} + +static int matroxfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info* info) { + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + matrox_pan_var(minfo, var); + return 0; +} + +static int matroxfb_get_final_bppShift(const struct matrox_fb_info *minfo, + int bpp) +{ + int bppshft2; + + DBG(__func__) + + bppshft2 = bpp; + if (!bppshft2) { + return 8; + } + if (isInterleave(minfo)) + bppshft2 >>= 1; + if (minfo->devflags.video64bits) + bppshft2 >>= 1; + return bppshft2; +} + +static int matroxfb_test_and_set_rounding(const struct matrox_fb_info *minfo, + int xres, int bpp) +{ + int over; + int rounding; + + DBG(__func__) + + switch (bpp) { + case 0: return xres; + case 4: rounding = 128; + break; + case 8: rounding = 64; /* doc says 64; 32 is OK for G400 */ + break; + case 16: rounding = 32; + break; + case 24: rounding = 64; /* doc says 64; 32 is OK for G400 */ + break; + default: rounding = 16; + /* on G400, 16 really does not work */ + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) + rounding = 32; + break; + } + if (isInterleave(minfo)) { + rounding *= 2; + } + over = xres % rounding; + if (over) + xres += rounding-over; + return xres; +} + +static int matroxfb_pitch_adjust(const struct matrox_fb_info *minfo, int xres, + int bpp) +{ + const int* width; + int xres_new; + + DBG(__func__) + + if (!bpp) return xres; + + width = minfo->capable.vxres; + + if (minfo->devflags.precise_width) { + while (*width) { + if ((*width >= xres) && (matroxfb_test_and_set_rounding(minfo, *width, bpp) == *width)) { + break; + } + width++; + } + xres_new = *width; + } else { + xres_new = matroxfb_test_and_set_rounding(minfo, xres, bpp); + } + return xres_new; +} + +static int matroxfb_get_cmap_len(struct fb_var_screeninfo *var) { + + DBG(__func__) + + switch (var->bits_per_pixel) { + case 4: + return 16; /* pseudocolor... 16 entries HW palette */ + case 8: + return 256; /* pseudocolor... 256 entries HW palette */ + case 16: + return 16; /* directcolor... 16 entries SW palette */ + /* Mystique: truecolor, 16 entries SW palette, HW palette hardwired into 1:1 mapping */ + case 24: + return 16; /* directcolor... 16 entries SW palette */ + /* Mystique: truecolor, 16 entries SW palette, HW palette hardwired into 1:1 mapping */ + case 32: + return 16; /* directcolor... 16 entries SW palette */ + /* Mystique: truecolor, 16 entries SW palette, HW palette hardwired into 1:1 mapping */ + } + return 16; /* return something reasonable... or panic()? */ +} + +static int matroxfb_decode_var(const struct matrox_fb_info *minfo, + struct fb_var_screeninfo *var, int *visual, + int *video_cmap_len, unsigned int* ydstorg) +{ + struct RGBT { + unsigned char bpp; + struct { + unsigned char offset, + length; + } red, + green, + blue, + transp; + signed char visual; + }; + static const struct RGBT table[]= { + { 8,{ 0,8},{0,8},{0,8},{ 0,0},MX_VISUAL_PSEUDOCOLOR}, + {15,{10,5},{5,5},{0,5},{15,1},MX_VISUAL_DIRECTCOLOR}, + {16,{11,5},{5,6},{0,5},{ 0,0},MX_VISUAL_DIRECTCOLOR}, + {24,{16,8},{8,8},{0,8},{ 0,0},MX_VISUAL_DIRECTCOLOR}, + {32,{16,8},{8,8},{0,8},{24,8},MX_VISUAL_DIRECTCOLOR} + }; + struct RGBT const *rgbt; + unsigned int bpp = var->bits_per_pixel; + unsigned int vramlen; + unsigned int memlen; + + DBG(__func__) + + switch (bpp) { + case 4: if (!minfo->capable.cfb4) return -EINVAL; + break; + case 8: break; + case 16: break; + case 24: break; + case 32: break; + default: return -EINVAL; + } + *ydstorg = 0; + vramlen = minfo->video.len_usable; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + var->xres_virtual = matroxfb_pitch_adjust(minfo, var->xres_virtual, bpp); + memlen = var->xres_virtual * bpp * var->yres_virtual / 8; + if (memlen > vramlen) { + var->yres_virtual = vramlen * 8 / (var->xres_virtual * bpp); + memlen = var->xres_virtual * bpp * var->yres_virtual / 8; + } + /* There is hardware bug that no line can cross 4MB boundary */ + /* give up for CFB24, it is impossible to easy workaround it */ + /* for other try to do something */ + if (!minfo->capable.cross4MB && (memlen > 0x400000)) { + if (bpp == 24) { + /* sorry */ + } else { + unsigned int linelen; + unsigned int m1 = linelen = var->xres_virtual * bpp / 8; + unsigned int m2 = PAGE_SIZE; /* or 128 if you do not need PAGE ALIGNED address */ + unsigned int max_yres; + + while (m1) { + int t; + + while (m2 >= m1) m2 -= m1; + t = m1; + m1 = m2; + m2 = t; + } + m2 = linelen * PAGE_SIZE / m2; + *ydstorg = m2 = 0x400000 % m2; + max_yres = (vramlen - m2) / linelen; + if (var->yres_virtual > max_yres) + var->yres_virtual = max_yres; + } + } + /* YDSTLEN contains only signed 16bit value */ + if (var->yres_virtual > 32767) + var->yres_virtual = 32767; + /* we must round yres/xres down, we already rounded y/xres_virtual up + if it was possible. We should return -EINVAL, but I disagree */ + if (var->yres_virtual < var->yres) + var->yres = var->yres_virtual; + if (var->xres_virtual < var->xres) + var->xres = var->xres_virtual; + if (var->xoffset + var->xres > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yoffset + var->yres > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + if (bpp == 16 && var->green.length == 5) { + bpp--; /* an artificial value - 15 */ + } + + for (rgbt = table; rgbt->bpp < bpp; rgbt++); +#define SETCLR(clr)\ + var->clr.offset = rgbt->clr.offset;\ + var->clr.length = rgbt->clr.length + SETCLR(red); + SETCLR(green); + SETCLR(blue); + SETCLR(transp); +#undef SETCLR + *visual = rgbt->visual; + + if (bpp > 8) + dprintk("matroxfb: truecolor: " + "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n", + var->transp.length, var->red.length, var->green.length, var->blue.length, + var->transp.offset, var->red.offset, var->green.offset, var->blue.offset); + + *video_cmap_len = matroxfb_get_cmap_len(var); + dprintk(KERN_INFO "requested %d*%d/%dbpp (%d*%d)\n", var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual); + return 0; +} + +static int matroxfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *fb_info) +{ + struct matrox_fb_info* minfo = container_of(fb_info, struct matrox_fb_info, fbcon); + + DBG(__func__) + + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= minfo->curr.cmap_len) + return 1; + + if (minfo->fbcon.var.grayscale) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + red = CNVT_TOHW(red, minfo->fbcon.var.red.length); + green = CNVT_TOHW(green, minfo->fbcon.var.green.length); + blue = CNVT_TOHW(blue, minfo->fbcon.var.blue.length); + transp = CNVT_TOHW(transp, minfo->fbcon.var.transp.length); + + switch (minfo->fbcon.var.bits_per_pixel) { + case 4: + case 8: + mga_outb(M_DAC_REG, regno); + mga_outb(M_DAC_VAL, red); + mga_outb(M_DAC_VAL, green); + mga_outb(M_DAC_VAL, blue); + break; + case 16: + if (regno >= 16) + break; + { + u_int16_t col = + (red << minfo->fbcon.var.red.offset) | + (green << minfo->fbcon.var.green.offset) | + (blue << minfo->fbcon.var.blue.offset) | + (transp << minfo->fbcon.var.transp.offset); /* for 1:5:5:5 */ + minfo->cmap[regno] = col | (col << 16); + } + break; + case 24: + case 32: + if (regno >= 16) + break; + minfo->cmap[regno] = + (red << minfo->fbcon.var.red.offset) | + (green << minfo->fbcon.var.green.offset) | + (blue << minfo->fbcon.var.blue.offset) | + (transp << minfo->fbcon.var.transp.offset); /* 8:8:8:8 */ + break; + } + return 0; +} + +static void matroxfb_init_fix(struct matrox_fb_info *minfo) +{ + struct fb_fix_screeninfo *fix = &minfo->fbcon.fix; + DBG(__func__) + + strcpy(fix->id,"MATROX"); + + fix->xpanstep = 8; /* 8 for 8bpp, 4 for 16bpp, 2 for 32bpp */ + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->mmio_start = minfo->mmio.base; + fix->mmio_len = minfo->mmio.len; + fix->accel = minfo->devflags.accelerator; +} + +static void matroxfb_update_fix(struct matrox_fb_info *minfo) +{ + struct fb_fix_screeninfo *fix = &minfo->fbcon.fix; + DBG(__func__) + + mutex_lock(&minfo->fbcon.mm_lock); + fix->smem_start = minfo->video.base + minfo->curr.ydstorg.bytes; + fix->smem_len = minfo->video.len_usable - minfo->curr.ydstorg.bytes; + mutex_unlock(&minfo->fbcon.mm_lock); +} + +static int matroxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int err; + int visual; + int cmap_len; + unsigned int ydstorg; + struct matrox_fb_info *minfo = info2minfo(info); + + if (minfo->dead) { + return -ENXIO; + } + if ((err = matroxfb_decode_var(minfo, var, &visual, &cmap_len, &ydstorg)) != 0) + return err; + return 0; +} + +static int matroxfb_set_par(struct fb_info *info) +{ + int err; + int visual; + int cmap_len; + unsigned int ydstorg; + struct fb_var_screeninfo *var; + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + if (minfo->dead) { + return -ENXIO; + } + + var = &info->var; + if ((err = matroxfb_decode_var(minfo, var, &visual, &cmap_len, &ydstorg)) != 0) + return err; + minfo->fbcon.screen_base = vaddr_va(minfo->video.vbase) + ydstorg; + matroxfb_update_fix(minfo); + minfo->fbcon.fix.visual = visual; + minfo->fbcon.fix.type = FB_TYPE_PACKED_PIXELS; + minfo->fbcon.fix.type_aux = 0; + minfo->fbcon.fix.line_length = (var->xres_virtual * var->bits_per_pixel) >> 3; + { + unsigned int pos; + + minfo->curr.cmap_len = cmap_len; + ydstorg += minfo->devflags.ydstorg; + minfo->curr.ydstorg.bytes = ydstorg; + minfo->curr.ydstorg.chunks = ydstorg >> (isInterleave(minfo) ? 3 : 2); + if (var->bits_per_pixel == 4) + minfo->curr.ydstorg.pixels = ydstorg; + else + minfo->curr.ydstorg.pixels = (ydstorg * 8) / var->bits_per_pixel; + minfo->curr.final_bppShift = matroxfb_get_final_bppShift(minfo, var->bits_per_pixel); + { struct my_timming mt; + struct matrox_hw_state* hw; + int out; + + matroxfb_var2my(var, &mt); + mt.crtc = MATROXFB_SRC_CRTC1; + /* CRTC1 delays */ + switch (var->bits_per_pixel) { + case 0: mt.delay = 31 + 0; break; + case 16: mt.delay = 21 + 8; break; + case 24: mt.delay = 17 + 8; break; + case 32: mt.delay = 16 + 8; break; + default: mt.delay = 31 + 8; break; + } + + hw = &minfo->hw; + + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC1 && + minfo->outputs[out].output->compute) { + minfo->outputs[out].output->compute(minfo->outputs[out].data, &mt); + } + } + up_read(&minfo->altout.lock); + minfo->crtc1.pixclock = mt.pixclock; + minfo->crtc1.mnp = mt.mnp; + minfo->hw_switch->init(minfo, &mt); + pos = (var->yoffset * var->xres_virtual + var->xoffset) * minfo->curr.final_bppShift / 32; + pos += minfo->curr.ydstorg.chunks; + + hw->CRTC[0x0D] = pos & 0xFF; + hw->CRTC[0x0C] = (pos & 0xFF00) >> 8; + hw->CRTCEXT[0] = (hw->CRTCEXT[0] & 0xF0) | ((pos >> 16) & 0x0F) | ((pos >> 14) & 0x40); + hw->CRTCEXT[8] = pos >> 21; + minfo->hw_switch->restore(minfo); + update_crtc2(minfo, pos); + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC1 && + minfo->outputs[out].output->program) { + minfo->outputs[out].output->program(minfo->outputs[out].data); + } + } + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC1 && + minfo->outputs[out].output->start) { + minfo->outputs[out].output->start(minfo->outputs[out].data); + } + } + up_read(&minfo->altout.lock); + matrox_cfbX_init(minfo); + } + } + minfo->initialized = 1; + return 0; +} + +static int matroxfb_get_vblank(struct matrox_fb_info *minfo, + struct fb_vblank *vblank) +{ + unsigned int sts1; + + matroxfb_enable_irq(minfo, 0); + memset(vblank, 0, sizeof(*vblank)); + vblank->flags = FB_VBLANK_HAVE_VCOUNT | FB_VBLANK_HAVE_VSYNC | + FB_VBLANK_HAVE_VBLANK | FB_VBLANK_HAVE_HBLANK; + sts1 = mga_inb(M_INSTS1); + vblank->vcount = mga_inl(M_VCOUNT); + /* BTW, on my PIII/450 with G400, reading M_INSTS1 + byte makes this call about 12% slower (1.70 vs. 2.05 us + per ioctl()) */ + if (sts1 & 1) + vblank->flags |= FB_VBLANK_HBLANKING; + if (sts1 & 8) + vblank->flags |= FB_VBLANK_VSYNCING; + if (vblank->vcount >= minfo->fbcon.var.yres) + vblank->flags |= FB_VBLANK_VBLANKING; + if (test_bit(0, &minfo->irq_flags)) { + vblank->flags |= FB_VBLANK_HAVE_COUNT; + /* Only one writer, aligned int value... + it should work without lock and without atomic_t */ + vblank->count = minfo->crtc1.vsync.cnt; + } + return 0; +} + +static struct matrox_altout panellink_output = { + .name = "Panellink output", +}; + +static int matroxfb_ioctl(struct fb_info *info, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + if (minfo->dead) { + return -ENXIO; + } + + switch (cmd) { + case FBIOGET_VBLANK: + { + struct fb_vblank vblank; + int err; + + err = matroxfb_get_vblank(minfo, &vblank); + if (err) + return err; + if (copy_to_user(argp, &vblank, sizeof(vblank))) + return -EFAULT; + return 0; + } + case FBIO_WAITFORVSYNC: + { + u_int32_t crt; + + if (get_user(crt, (u_int32_t __user *)arg)) + return -EFAULT; + + return matroxfb_wait_for_sync(minfo, crt); + } + case MATROXFB_SET_OUTPUT_MODE: + { + struct matroxioc_output_mode mom; + struct matrox_altout *oproc; + int val; + + if (copy_from_user(&mom, argp, sizeof(mom))) + return -EFAULT; + if (mom.output >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + down_read(&minfo->altout.lock); + oproc = minfo->outputs[mom.output].output; + if (!oproc) { + val = -ENXIO; + } else if (!oproc->verifymode) { + if (mom.mode == MATROXFB_OUTPUT_MODE_MONITOR) { + val = 0; + } else { + val = -EINVAL; + } + } else { + val = oproc->verifymode(minfo->outputs[mom.output].data, mom.mode); + } + if (!val) { + if (minfo->outputs[mom.output].mode != mom.mode) { + minfo->outputs[mom.output].mode = mom.mode; + val = 1; + } + } + up_read(&minfo->altout.lock); + if (val != 1) + return val; + switch (minfo->outputs[mom.output].src) { + case MATROXFB_SRC_CRTC1: + matroxfb_set_par(info); + break; + case MATROXFB_SRC_CRTC2: + { + struct matroxfb_dh_fb_info* crtc2; + + down_read(&minfo->crtc2.lock); + crtc2 = minfo->crtc2.info; + if (crtc2) + crtc2->fbcon.fbops->fb_set_par(&crtc2->fbcon); + up_read(&minfo->crtc2.lock); + } + break; + } + return 0; + } + case MATROXFB_GET_OUTPUT_MODE: + { + struct matroxioc_output_mode mom; + struct matrox_altout *oproc; + int val; + + if (copy_from_user(&mom, argp, sizeof(mom))) + return -EFAULT; + if (mom.output >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + down_read(&minfo->altout.lock); + oproc = minfo->outputs[mom.output].output; + if (!oproc) { + val = -ENXIO; + } else { + mom.mode = minfo->outputs[mom.output].mode; + val = 0; + } + up_read(&minfo->altout.lock); + if (val) + return val; + if (copy_to_user(argp, &mom, sizeof(mom))) + return -EFAULT; + return 0; + } + case MATROXFB_SET_OUTPUT_CONNECTION: + { + u_int32_t tmp; + int i; + int changes; + + if (copy_from_user(&tmp, argp, sizeof(tmp))) + return -EFAULT; + for (i = 0; i < 32; i++) { + if (tmp & (1 << i)) { + if (i >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + if (!minfo->outputs[i].output) + return -ENXIO; + switch (minfo->outputs[i].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC1: + break; + default: + return -EBUSY; + } + } + } + if (minfo->devflags.panellink) { + if (tmp & MATROXFB_OUTPUT_CONN_DFP) { + if (tmp & MATROXFB_OUTPUT_CONN_SECONDARY) + return -EINVAL; + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].src == MATROXFB_SRC_CRTC2) { + return -EBUSY; + } + } + } + } + changes = 0; + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (tmp & (1 << i)) { + if (minfo->outputs[i].src != MATROXFB_SRC_CRTC1) { + changes = 1; + minfo->outputs[i].src = MATROXFB_SRC_CRTC1; + } + } else if (minfo->outputs[i].src == MATROXFB_SRC_CRTC1) { + changes = 1; + minfo->outputs[i].src = MATROXFB_SRC_NONE; + } + } + if (!changes) + return 0; + matroxfb_set_par(info); + return 0; + } + case MATROXFB_GET_OUTPUT_CONNECTION: + { + u_int32_t conn = 0; + int i; + + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].src == MATROXFB_SRC_CRTC1) { + conn |= 1 << i; + } + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case MATROXFB_GET_AVAILABLE_OUTPUTS: + { + u_int32_t conn = 0; + int i; + + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].output) { + switch (minfo->outputs[i].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC1: + conn |= 1 << i; + break; + } + } + } + if (minfo->devflags.panellink) { + if (conn & MATROXFB_OUTPUT_CONN_DFP) + conn &= ~MATROXFB_OUTPUT_CONN_SECONDARY; + if (conn & MATROXFB_OUTPUT_CONN_SECONDARY) + conn &= ~MATROXFB_OUTPUT_CONN_DFP; + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case MATROXFB_GET_ALL_OUTPUTS: + { + u_int32_t conn = 0; + int i; + + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].output) { + conn |= 1 << i; + } + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case VIDIOC_QUERYCAP: + { + struct v4l2_capability r; + + memset(&r, 0, sizeof(r)); + strcpy(r.driver, "matroxfb"); + strcpy(r.card, "Matrox"); + sprintf(r.bus_info, "PCI:%s", pci_name(minfo->pcidev)); + r.version = KERNEL_VERSION(1,0,0); + r.capabilities = V4L2_CAP_VIDEO_OUTPUT; + if (copy_to_user(argp, &r, sizeof(r))) + return -EFAULT; + return 0; + + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl qctrl; + int err; + + if (copy_from_user(&qctrl, argp, sizeof(qctrl))) + return -EFAULT; + + down_read(&minfo->altout.lock); + if (!minfo->outputs[1].output) { + err = -ENXIO; + } else if (minfo->outputs[1].output->getqueryctrl) { + err = minfo->outputs[1].output->getqueryctrl(minfo->outputs[1].data, &qctrl); + } else { + err = -EINVAL; + } + up_read(&minfo->altout.lock); + if (err >= 0 && + copy_to_user(argp, &qctrl, sizeof(qctrl))) + return -EFAULT; + return err; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control ctrl; + int err; + + if (copy_from_user(&ctrl, argp, sizeof(ctrl))) + return -EFAULT; + + down_read(&minfo->altout.lock); + if (!minfo->outputs[1].output) { + err = -ENXIO; + } else if (minfo->outputs[1].output->getctrl) { + err = minfo->outputs[1].output->getctrl(minfo->outputs[1].data, &ctrl); + } else { + err = -EINVAL; + } + up_read(&minfo->altout.lock); + if (err >= 0 && + copy_to_user(argp, &ctrl, sizeof(ctrl))) + return -EFAULT; + return err; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control ctrl; + int err; + + if (copy_from_user(&ctrl, argp, sizeof(ctrl))) + return -EFAULT; + + down_read(&minfo->altout.lock); + if (!minfo->outputs[1].output) { + err = -ENXIO; + } else if (minfo->outputs[1].output->setctrl) { + err = minfo->outputs[1].output->setctrl(minfo->outputs[1].data, &ctrl); + } else { + err = -EINVAL; + } + up_read(&minfo->altout.lock); + return err; + } + } + return -ENOTTY; +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ + +static int matroxfb_blank(int blank, struct fb_info *info) +{ + int seq; + int crtc; + CRITFLAGS + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + if (minfo->dead) + return 1; + + switch (blank) { + case FB_BLANK_NORMAL: seq = 0x20; crtc = 0x00; break; /* works ??? */ + case FB_BLANK_VSYNC_SUSPEND: seq = 0x20; crtc = 0x10; break; + case FB_BLANK_HSYNC_SUSPEND: seq = 0x20; crtc = 0x20; break; + case FB_BLANK_POWERDOWN: seq = 0x20; crtc = 0x30; break; + default: seq = 0x00; crtc = 0x00; break; + } + + CRITBEGIN + + mga_outb(M_SEQ_INDEX, 1); + mga_outb(M_SEQ_DATA, (mga_inb(M_SEQ_DATA) & ~0x20) | seq); + mga_outb(M_EXTVGA_INDEX, 1); + mga_outb(M_EXTVGA_DATA, (mga_inb(M_EXTVGA_DATA) & ~0x30) | crtc); + + CRITEND + return 0; +} + +static struct fb_ops matroxfb_ops = { + .owner = THIS_MODULE, + .fb_open = matroxfb_open, + .fb_release = matroxfb_release, + .fb_check_var = matroxfb_check_var, + .fb_set_par = matroxfb_set_par, + .fb_setcolreg = matroxfb_setcolreg, + .fb_pan_display =matroxfb_pan_display, + .fb_blank = matroxfb_blank, + .fb_ioctl = matroxfb_ioctl, +/* .fb_fillrect = <set by matrox_cfbX_init>, */ +/* .fb_copyarea = <set by matrox_cfbX_init>, */ +/* .fb_imageblit = <set by matrox_cfbX_init>, */ +/* .fb_cursor = <set by matrox_cfbX_init>, */ +}; + +#define RSDepth(X) (((X) >> 8) & 0x0F) +#define RS8bpp 0x1 +#define RS15bpp 0x2 +#define RS16bpp 0x3 +#define RS32bpp 0x4 +#define RS4bpp 0x5 +#define RS24bpp 0x6 +#define RSText 0x7 +#define RSText8 0x8 +/* 9-F */ +static struct { struct fb_bitfield red, green, blue, transp; int bits_per_pixel; } colors[] = { + { { 0, 8, 0}, { 0, 8, 0}, { 0, 8, 0}, { 0, 0, 0}, 8 }, + { { 10, 5, 0}, { 5, 5, 0}, { 0, 5, 0}, { 15, 1, 0}, 16 }, + { { 11, 5, 0}, { 5, 6, 0}, { 0, 5, 0}, { 0, 0, 0}, 16 }, + { { 16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 24, 8, 0}, 32 }, + { { 0, 8, 0}, { 0, 8, 0}, { 0, 8, 0}, { 0, 0, 0}, 4 }, + { { 16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 0, 0, 0}, 24 }, + { { 0, 6, 0}, { 0, 6, 0}, { 0, 6, 0}, { 0, 0, 0}, 0 }, /* textmode with (default) VGA8x16 */ + { { 0, 6, 0}, { 0, 6, 0}, { 0, 6, 0}, { 0, 0, 0}, 0 }, /* textmode hardwired to VGA8x8 */ +}; + +/* initialized by setup, see explanation at end of file (search for MODULE_PARM_DESC) */ +static unsigned int mem; /* "matroxfb:mem:xxxxxM" */ +static int option_precise_width = 1; /* cannot be changed, option_precise_width==0 must imply noaccel */ +static int inv24; /* "matroxfb:inv24" */ +static int cross4MB = -1; /* "matroxfb:cross4MB" */ +static int disabled; /* "matroxfb:disabled" */ +static int noaccel; /* "matroxfb:noaccel" */ +static int nopan; /* "matroxfb:nopan" */ +static int no_pci_retry; /* "matroxfb:nopciretry" */ +static int novga; /* "matroxfb:novga" */ +static int nobios; /* "matroxfb:nobios" */ +static int noinit = 1; /* "matroxfb:init" */ +static int inverse; /* "matroxfb:inverse" */ +static int sgram; /* "matroxfb:sgram" */ +#ifdef CONFIG_MTRR +static int mtrr = 1; /* "matroxfb:nomtrr" */ +#endif +static int grayscale; /* "matroxfb:grayscale" */ +static int dev = -1; /* "matroxfb:dev:xxxxx" */ +static unsigned int vesa = ~0; /* "matroxfb:vesa:xxxxx" */ +static int depth = -1; /* "matroxfb:depth:xxxxx" */ +static unsigned int xres; /* "matroxfb:xres:xxxxx" */ +static unsigned int yres; /* "matroxfb:yres:xxxxx" */ +static unsigned int upper = ~0; /* "matroxfb:upper:xxxxx" */ +static unsigned int lower = ~0; /* "matroxfb:lower:xxxxx" */ +static unsigned int vslen; /* "matroxfb:vslen:xxxxx" */ +static unsigned int left = ~0; /* "matroxfb:left:xxxxx" */ +static unsigned int right = ~0; /* "matroxfb:right:xxxxx" */ +static unsigned int hslen; /* "matroxfb:hslen:xxxxx" */ +static unsigned int pixclock; /* "matroxfb:pixclock:xxxxx" */ +static int sync = -1; /* "matroxfb:sync:xxxxx" */ +static unsigned int fv; /* "matroxfb:fv:xxxxx" */ +static unsigned int fh; /* "matroxfb:fh:xxxxxk" */ +static unsigned int maxclk; /* "matroxfb:maxclk:xxxxM" */ +static int dfp; /* "matroxfb:dfp */ +static int dfp_type = -1; /* "matroxfb:dfp:xxx */ +static int memtype = -1; /* "matroxfb:memtype:xxx" */ +static char outputs[8]; /* "matroxfb:outputs:xxx" */ + +#ifndef MODULE +static char videomode[64]; /* "matroxfb:mode:xxxxx" or "matroxfb:xxxxx" */ +#endif + +static int matroxfb_getmemory(struct matrox_fb_info *minfo, + unsigned int maxSize, unsigned int *realSize) +{ + vaddr_t vm; + unsigned int offs; + unsigned int offs2; + unsigned char orig; + unsigned char bytes[32]; + unsigned char* tmp; + + DBG(__func__) + + vm = minfo->video.vbase; + maxSize &= ~0x1FFFFF; /* must be X*2MB (really it must be 2 or X*4MB) */ + /* at least 2MB */ + if (maxSize < 0x0200000) return 0; + if (maxSize > 0x2000000) maxSize = 0x2000000; + + mga_outb(M_EXTVGA_INDEX, 0x03); + orig = mga_inb(M_EXTVGA_DATA); + mga_outb(M_EXTVGA_DATA, orig | 0x80); + + tmp = bytes; + for (offs = 0x100000; offs < maxSize; offs += 0x200000) + *tmp++ = mga_readb(vm, offs); + for (offs = 0x100000; offs < maxSize; offs += 0x200000) + mga_writeb(vm, offs, 0x02); + mga_outb(M_CACHEFLUSH, 0x00); + for (offs = 0x100000; offs < maxSize; offs += 0x200000) { + if (mga_readb(vm, offs) != 0x02) + break; + mga_writeb(vm, offs, mga_readb(vm, offs) - 0x02); + if (mga_readb(vm, offs)) + break; + } + tmp = bytes; + for (offs2 = 0x100000; offs2 < maxSize; offs2 += 0x200000) + mga_writeb(vm, offs2, *tmp++); + + mga_outb(M_EXTVGA_INDEX, 0x03); + mga_outb(M_EXTVGA_DATA, orig); + + *realSize = offs - 0x100000; +#ifdef CONFIG_FB_MATROX_MILLENIUM + minfo->interleave = !(!isMillenium(minfo) || ((offs - 0x100000) & 0x3FFFFF)); +#endif + return 1; +} + +struct video_board { + int maxvram; + int maxdisplayable; + int accelID; + struct matrox_switch* lowlevel; + }; +#ifdef CONFIG_FB_MATROX_MILLENIUM +static struct video_board vbMillennium = {0x0800000, 0x0800000, FB_ACCEL_MATROX_MGA2064W, &matrox_millennium}; +static struct video_board vbMillennium2 = {0x1000000, 0x0800000, FB_ACCEL_MATROX_MGA2164W, &matrox_millennium}; +static struct video_board vbMillennium2A = {0x1000000, 0x0800000, FB_ACCEL_MATROX_MGA2164W_AGP, &matrox_millennium}; +#endif /* CONFIG_FB_MATROX_MILLENIUM */ +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static struct video_board vbMystique = {0x0800000, 0x0800000, FB_ACCEL_MATROX_MGA1064SG, &matrox_mystique}; +#endif /* CONFIG_FB_MATROX_MYSTIQUE */ +#ifdef CONFIG_FB_MATROX_G +static struct video_board vbG100 = {0x0800000, 0x0800000, FB_ACCEL_MATROX_MGAG100, &matrox_G100}; +static struct video_board vbG200 = {0x1000000, 0x1000000, FB_ACCEL_MATROX_MGAG200, &matrox_G100}; +/* from doc it looks like that accelerator can draw only to low 16MB :-( Direct accesses & displaying are OK for + whole 32MB */ +static struct video_board vbG400 = {0x2000000, 0x1000000, FB_ACCEL_MATROX_MGAG400, &matrox_G100}; +#endif + +#define DEVF_VIDEO64BIT 0x0001 +#define DEVF_SWAPS 0x0002 +#define DEVF_SRCORG 0x0004 +#define DEVF_DUALHEAD 0x0008 +#define DEVF_CROSS4MB 0x0010 +#define DEVF_TEXT4B 0x0020 +/* #define DEVF_recycled 0x0040 */ +/* #define DEVF_recycled 0x0080 */ +#define DEVF_SUPPORT32MB 0x0100 +#define DEVF_ANY_VXRES 0x0200 +#define DEVF_TEXT16B 0x0400 +#define DEVF_CRTC2 0x0800 +#define DEVF_MAVEN_CAPABLE 0x1000 +#define DEVF_PANELLINK_CAPABLE 0x2000 +#define DEVF_G450DAC 0x4000 + +#define DEVF_GCORE (DEVF_VIDEO64BIT | DEVF_SWAPS | DEVF_CROSS4MB) +#define DEVF_G2CORE (DEVF_GCORE | DEVF_ANY_VXRES | DEVF_MAVEN_CAPABLE | DEVF_PANELLINK_CAPABLE | DEVF_SRCORG | DEVF_DUALHEAD) +#define DEVF_G100 (DEVF_GCORE) /* no doc, no vxres... */ +#define DEVF_G200 (DEVF_G2CORE) +#define DEVF_G400 (DEVF_G2CORE | DEVF_SUPPORT32MB | DEVF_TEXT16B | DEVF_CRTC2) +/* if you'll find how to drive DFP... */ +#define DEVF_G450 (DEVF_GCORE | DEVF_ANY_VXRES | DEVF_SUPPORT32MB | DEVF_TEXT16B | DEVF_CRTC2 | DEVF_G450DAC | DEVF_SRCORG | DEVF_DUALHEAD) +#define DEVF_G550 (DEVF_G450) + +static struct board { + unsigned short vendor, device, rev, svid, sid; + unsigned int flags; + unsigned int maxclk; + enum mga_chip chip; + struct video_board* base; + const char* name; + } dev_list[] = { +#ifdef CONFIG_FB_MATROX_MILLENIUM + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL, 0xFF, + 0, 0, + DEVF_TEXT4B, + 230000, + MGA_2064, + &vbMillennium, + "Millennium (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL_2, 0xFF, + 0, 0, + DEVF_SWAPS, + 220000, + MGA_2164, + &vbMillennium2, + "Millennium II (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL_2_AGP, 0xFF, + 0, 0, + DEVF_SWAPS, + 250000, + MGA_2164, + &vbMillennium2A, + "Millennium II (AGP)"}, +#endif +#ifdef CONFIG_FB_MATROX_MYSTIQUE + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS, 0x02, + 0, 0, + DEVF_VIDEO64BIT | DEVF_CROSS4MB, + 180000, + MGA_1064, + &vbMystique, + "Mystique (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS, 0xFF, + 0, 0, + DEVF_VIDEO64BIT | DEVF_SWAPS | DEVF_CROSS4MB, + 220000, + MGA_1164, + &vbMystique, + "Mystique 220 (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS_AGP, 0x02, + 0, 0, + DEVF_VIDEO64BIT | DEVF_CROSS4MB, + 180000, + MGA_1064, + &vbMystique, + "Mystique (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS_AGP, 0xFF, + 0, 0, + DEVF_VIDEO64BIT | DEVF_SWAPS | DEVF_CROSS4MB, + 220000, + MGA_1164, + &vbMystique, + "Mystique 220 (AGP)"}, +#endif +#ifdef CONFIG_FB_MATROX_G + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G100_MM, 0xFF, + 0, 0, + DEVF_G100, + 230000, + MGA_G100, + &vbG100, + "MGA-G100 (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G100_AGP, 0xFF, + 0, 0, + DEVF_G100, + 230000, + MGA_G100, + &vbG100, + "MGA-G100 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_PCI, 0xFF, + 0, 0, + DEVF_G200, + 250000, + MGA_G200, + &vbG200, + "MGA-G200 (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_GENERIC, + DEVF_G200, + 220000, + MGA_G200, + &vbG200, + "MGA-G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MYSTIQUE_G200_AGP, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "Mystique G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MILLENIUM_G200_AGP, + DEVF_G200, + 250000, + MGA_G200, + &vbG200, + "Millennium G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MARVEL_G200_AGP, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "Marvel G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_SIEMENS_NIXDORF, PCI_SS_ID_SIEMENS_MGA_G200_AGP, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "MGA-G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + 0, 0, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400, 0x80, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MILLENNIUM_G400_MAX_AGP, + DEVF_G400, + 360000, + MGA_G400, + &vbG400, + "Millennium G400 MAX (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400, 0x80, + 0, 0, + DEVF_G400, + 300000, + MGA_G400, + &vbG400, + "G400 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400, 0xFF, + 0, 0, + DEVF_G450, + 360000, + MGA_G450, + &vbG400, + "G450"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G550, 0xFF, + 0, 0, + DEVF_G550, + 360000, + MGA_G550, + &vbG400, + "G550"}, +#endif + {0, 0, 0xFF, + 0, 0, + 0, + 0, + 0, + NULL, + NULL}}; + +#ifndef MODULE +static struct fb_videomode defaultmode = { + /* 640x480 @ 60Hz, 31.5 kHz */ + NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2, + 0, FB_VMODE_NONINTERLACED +}; +#endif /* !MODULE */ + +static int hotplug = 0; + +static void setDefaultOutputs(struct matrox_fb_info *minfo) +{ + unsigned int i; + const char* ptr; + + minfo->outputs[0].default_src = MATROXFB_SRC_CRTC1; + if (minfo->devflags.g450dac) { + minfo->outputs[1].default_src = MATROXFB_SRC_CRTC1; + minfo->outputs[2].default_src = MATROXFB_SRC_CRTC1; + } else if (dfp) { + minfo->outputs[2].default_src = MATROXFB_SRC_CRTC1; + } + ptr = outputs; + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + char c = *ptr++; + + if (c == 0) { + break; + } + if (c == '0') { + minfo->outputs[i].default_src = MATROXFB_SRC_NONE; + } else if (c == '1') { + minfo->outputs[i].default_src = MATROXFB_SRC_CRTC1; + } else if (c == '2' && minfo->devflags.crtc2) { + minfo->outputs[i].default_src = MATROXFB_SRC_CRTC2; + } else { + printk(KERN_ERR "matroxfb: Unknown outputs setting\n"); + break; + } + } + /* Nullify this option for subsequent adapters */ + outputs[0] = 0; +} + +static int initMatrox2(struct matrox_fb_info *minfo, struct board *b) +{ + unsigned long ctrlptr_phys = 0; + unsigned long video_base_phys = 0; + unsigned int memsize; + int err; + + static struct pci_device_id intel_82437[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437) }, + { }, + }; + + DBG(__func__) + + /* set default values... */ + vesafb_defined.accel_flags = FB_ACCELF_TEXT; + + minfo->hw_switch = b->base->lowlevel; + minfo->devflags.accelerator = b->base->accelID; + minfo->max_pixel_clock = b->maxclk; + + printk(KERN_INFO "matroxfb: Matrox %s detected\n", b->name); + minfo->capable.plnwt = 1; + minfo->chip = b->chip; + minfo->capable.srcorg = b->flags & DEVF_SRCORG; + minfo->devflags.video64bits = b->flags & DEVF_VIDEO64BIT; + if (b->flags & DEVF_TEXT4B) { + minfo->devflags.vgastep = 4; + minfo->devflags.textmode = 4; + minfo->devflags.text_type_aux = FB_AUX_TEXT_MGA_STEP16; + } else if (b->flags & DEVF_TEXT16B) { + minfo->devflags.vgastep = 16; + minfo->devflags.textmode = 1; + minfo->devflags.text_type_aux = FB_AUX_TEXT_MGA_STEP16; + } else { + minfo->devflags.vgastep = 8; + minfo->devflags.textmode = 1; + minfo->devflags.text_type_aux = FB_AUX_TEXT_MGA_STEP8; + } + minfo->devflags.support32MB = (b->flags & DEVF_SUPPORT32MB) != 0; + minfo->devflags.precise_width = !(b->flags & DEVF_ANY_VXRES); + minfo->devflags.crtc2 = (b->flags & DEVF_CRTC2) != 0; + minfo->devflags.maven_capable = (b->flags & DEVF_MAVEN_CAPABLE) != 0; + minfo->devflags.dualhead = (b->flags & DEVF_DUALHEAD) != 0; + minfo->devflags.dfp_type = dfp_type; + minfo->devflags.g450dac = (b->flags & DEVF_G450DAC) != 0; + minfo->devflags.textstep = minfo->devflags.vgastep * minfo->devflags.textmode; + minfo->devflags.textvram = 65536 / minfo->devflags.textmode; + setDefaultOutputs(minfo); + if (b->flags & DEVF_PANELLINK_CAPABLE) { + minfo->outputs[2].data = minfo; + minfo->outputs[2].output = &panellink_output; + minfo->outputs[2].src = minfo->outputs[2].default_src; + minfo->outputs[2].mode = MATROXFB_OUTPUT_MODE_MONITOR; + minfo->devflags.panellink = 1; + } + + if (minfo->capable.cross4MB < 0) + minfo->capable.cross4MB = b->flags & DEVF_CROSS4MB; + if (b->flags & DEVF_SWAPS) { + ctrlptr_phys = pci_resource_start(minfo->pcidev, 1); + video_base_phys = pci_resource_start(minfo->pcidev, 0); + minfo->devflags.fbResource = PCI_BASE_ADDRESS_0; + } else { + ctrlptr_phys = pci_resource_start(minfo->pcidev, 0); + video_base_phys = pci_resource_start(minfo->pcidev, 1); + minfo->devflags.fbResource = PCI_BASE_ADDRESS_1; + } + err = -EINVAL; + if (!ctrlptr_phys) { + printk(KERN_ERR "matroxfb: control registers are not available, matroxfb disabled\n"); + goto fail; + } + if (!video_base_phys) { + printk(KERN_ERR "matroxfb: video RAM is not available in PCI address space, matroxfb disabled\n"); + goto fail; + } + memsize = b->base->maxvram; + if (!request_mem_region(ctrlptr_phys, 16384, "matroxfb MMIO")) { + goto fail; + } + if (!request_mem_region(video_base_phys, memsize, "matroxfb FB")) { + goto failCtrlMR; + } + minfo->video.len_maximum = memsize; + /* convert mem (autodetect k, M) */ + if (mem < 1024) mem *= 1024; + if (mem < 0x00100000) mem *= 1024; + + if (mem && (mem < memsize)) + memsize = mem; + err = -ENOMEM; + if (mga_ioremap(ctrlptr_phys, 16384, MGA_IOREMAP_MMIO, &minfo->mmio.vbase)) { + printk(KERN_ERR "matroxfb: cannot ioremap(%lX, 16384), matroxfb disabled\n", ctrlptr_phys); + goto failVideoMR; + } + minfo->mmio.base = ctrlptr_phys; + minfo->mmio.len = 16384; + minfo->video.base = video_base_phys; + if (mga_ioremap(video_base_phys, memsize, MGA_IOREMAP_FB, &minfo->video.vbase)) { + printk(KERN_ERR "matroxfb: cannot ioremap(%lX, %d), matroxfb disabled\n", + video_base_phys, memsize); + goto failCtrlIO; + } + { + u_int32_t cmd; + u_int32_t mga_option; + + pci_read_config_dword(minfo->pcidev, PCI_OPTION_REG, &mga_option); + pci_read_config_dword(minfo->pcidev, PCI_COMMAND, &cmd); + mga_option &= 0x7FFFFFFF; /* clear BIG_ENDIAN */ + mga_option |= MX_OPTION_BSWAP; + /* disable palette snooping */ + cmd &= ~PCI_COMMAND_VGA_PALETTE; + if (pci_dev_present(intel_82437)) { + if (!(mga_option & 0x20000000) && !minfo->devflags.nopciretry) { + printk(KERN_WARNING "matroxfb: Disabling PCI retries due to i82437 present\n"); + } + mga_option |= 0x20000000; + minfo->devflags.nopciretry = 1; + } + pci_write_config_dword(minfo->pcidev, PCI_COMMAND, cmd); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mga_option); + minfo->hw.MXoptionReg = mga_option; + + /* select non-DMA memory for PCI_MGA_DATA, otherwise dump of PCI cfg space can lock PCI bus */ + /* maybe preinit() candidate, but it is same... for all devices... at this time... */ + pci_write_config_dword(minfo->pcidev, PCI_MGA_INDEX, 0x00003C00); + } + + err = -ENXIO; + matroxfb_read_pins(minfo); + if (minfo->hw_switch->preinit(minfo)) { + goto failVideoIO; + } + + err = -ENOMEM; + if (!matroxfb_getmemory(minfo, memsize, &minfo->video.len) || !minfo->video.len) { + printk(KERN_ERR "matroxfb: cannot determine memory size\n"); + goto failVideoIO; + } + minfo->devflags.ydstorg = 0; + + minfo->video.base = video_base_phys; + minfo->video.len_usable = minfo->video.len; + if (minfo->video.len_usable > b->base->maxdisplayable) + minfo->video.len_usable = b->base->maxdisplayable; +#ifdef CONFIG_MTRR + if (mtrr) { + minfo->mtrr.vram = mtrr_add(video_base_phys, minfo->video.len, MTRR_TYPE_WRCOMB, 1); + minfo->mtrr.vram_valid = 1; + printk(KERN_INFO "matroxfb: MTRR's turned on\n"); + } +#endif /* CONFIG_MTRR */ + + if (!minfo->devflags.novga) + request_region(0x3C0, 32, "matrox"); + matroxfb_g450_connect(minfo); + minfo->hw_switch->reset(minfo); + + minfo->fbcon.monspecs.hfmin = 0; + minfo->fbcon.monspecs.hfmax = fh; + minfo->fbcon.monspecs.vfmin = 0; + minfo->fbcon.monspecs.vfmax = fv; + minfo->fbcon.monspecs.dpms = 0; /* TBD */ + + /* static settings */ + vesafb_defined.red = colors[depth-1].red; + vesafb_defined.green = colors[depth-1].green; + vesafb_defined.blue = colors[depth-1].blue; + vesafb_defined.bits_per_pixel = colors[depth-1].bits_per_pixel; + vesafb_defined.grayscale = grayscale; + vesafb_defined.vmode = 0; + if (noaccel) + vesafb_defined.accel_flags &= ~FB_ACCELF_TEXT; + + minfo->fbops = matroxfb_ops; + minfo->fbcon.fbops = &minfo->fbops; + minfo->fbcon.pseudo_palette = minfo->cmap; + /* after __init time we are like module... no logo */ + minfo->fbcon.flags = hotplug ? FBINFO_FLAG_MODULE : FBINFO_FLAG_DEFAULT; + minfo->fbcon.flags |= FBINFO_PARTIAL_PAN_OK | /* Prefer panning for scroll under MC viewer/edit */ + FBINFO_HWACCEL_COPYAREA | /* We have hw-assisted bmove */ + FBINFO_HWACCEL_FILLRECT | /* And fillrect */ + FBINFO_HWACCEL_IMAGEBLIT | /* And imageblit */ + FBINFO_HWACCEL_XPAN | /* And we support both horizontal */ + FBINFO_HWACCEL_YPAN | /* And vertical panning */ + FBINFO_READS_FAST; + minfo->video.len_usable &= PAGE_MASK; + fb_alloc_cmap(&minfo->fbcon.cmap, 256, 1); + +#ifndef MODULE + /* mode database is marked __init!!! */ + if (!hotplug) { + fb_find_mode(&vesafb_defined, &minfo->fbcon, videomode[0] ? videomode : NULL, + NULL, 0, &defaultmode, vesafb_defined.bits_per_pixel); + } +#endif /* !MODULE */ + + /* mode modifiers */ + if (hslen) + vesafb_defined.hsync_len = hslen; + if (vslen) + vesafb_defined.vsync_len = vslen; + if (left != ~0) + vesafb_defined.left_margin = left; + if (right != ~0) + vesafb_defined.right_margin = right; + if (upper != ~0) + vesafb_defined.upper_margin = upper; + if (lower != ~0) + vesafb_defined.lower_margin = lower; + if (xres) + vesafb_defined.xres = xres; + if (yres) + vesafb_defined.yres = yres; + if (sync != -1) + vesafb_defined.sync = sync; + else if (vesafb_defined.sync == ~0) { + vesafb_defined.sync = 0; + if (yres < 400) + vesafb_defined.sync |= FB_SYNC_HOR_HIGH_ACT; + else if (yres < 480) + vesafb_defined.sync |= FB_SYNC_VERT_HIGH_ACT; + } + + /* fv, fh, maxclk limits was specified */ + { + unsigned int tmp; + + if (fv) { + tmp = fv * (vesafb_defined.upper_margin + vesafb_defined.yres + + vesafb_defined.lower_margin + vesafb_defined.vsync_len); + if ((tmp < fh) || (fh == 0)) fh = tmp; + } + if (fh) { + tmp = fh * (vesafb_defined.left_margin + vesafb_defined.xres + + vesafb_defined.right_margin + vesafb_defined.hsync_len); + if ((tmp < maxclk) || (maxclk == 0)) maxclk = tmp; + } + tmp = (maxclk + 499) / 500; + if (tmp) { + tmp = (2000000000 + tmp) / tmp; + if (tmp > pixclock) pixclock = tmp; + } + } + if (pixclock) { + if (pixclock < 2000) /* > 500MHz */ + pixclock = 4000; /* 250MHz */ + if (pixclock > 1000000) + pixclock = 1000000; /* 1MHz */ + vesafb_defined.pixclock = pixclock; + } + + /* FIXME: Where to move this?! */ +#if defined(CONFIG_PPC_PMAC) +#ifndef MODULE + if (machine_is(powermac)) { + struct fb_var_screeninfo var; + if (default_vmode <= 0 || default_vmode > VMODE_MAX) + default_vmode = VMODE_640_480_60; +#ifdef CONFIG_NVRAM + if (default_cmode == CMODE_NVRAM) + default_cmode = nvram_read_byte(NV_CMODE); +#endif + if (default_cmode < CMODE_8 || default_cmode > CMODE_32) + default_cmode = CMODE_8; + if (!mac_vmode_to_var(default_vmode, default_cmode, &var)) { + var.accel_flags = vesafb_defined.accel_flags; + var.xoffset = var.yoffset = 0; + /* Note: mac_vmode_to_var() does not set all parameters */ + vesafb_defined = var; + } + } +#endif /* !MODULE */ +#endif /* CONFIG_PPC_PMAC */ + vesafb_defined.xres_virtual = vesafb_defined.xres; + if (nopan) { + vesafb_defined.yres_virtual = vesafb_defined.yres; + } else { + vesafb_defined.yres_virtual = 65536; /* large enough to be INF, but small enough + to yres_virtual * xres_virtual < 2^32 */ + } + matroxfb_init_fix(minfo); + minfo->fbcon.screen_base = vaddr_va(minfo->video.vbase); + /* Normalize values (namely yres_virtual) */ + matroxfb_check_var(&vesafb_defined, &minfo->fbcon); + /* And put it into "current" var. Do NOT program hardware yet, or we'll not take over + * vgacon correctly. fbcon_startup will call fb_set_par for us, WITHOUT check_var, + * and unfortunately it will do it BEFORE vgacon contents is saved, so it won't work + * anyway. But we at least tried... */ + minfo->fbcon.var = vesafb_defined; + err = -EINVAL; + + printk(KERN_INFO "matroxfb: %dx%dx%dbpp (virtual: %dx%d)\n", + vesafb_defined.xres, vesafb_defined.yres, vesafb_defined.bits_per_pixel, + vesafb_defined.xres_virtual, vesafb_defined.yres_virtual); + printk(KERN_INFO "matroxfb: framebuffer at 0x%lX, mapped to 0x%p, size %d\n", + minfo->video.base, vaddr_va(minfo->video.vbase), minfo->video.len); + +/* We do not have to set currcon to 0... register_framebuffer do it for us on first console + * and we do not want currcon == 0 for subsequent framebuffers */ + + minfo->fbcon.device = &minfo->pcidev->dev; + if (register_framebuffer(&minfo->fbcon) < 0) { + goto failVideoIO; + } + fb_info(&minfo->fbcon, "%s frame buffer device\n", minfo->fbcon.fix.id); + + /* there is no console on this fb... but we have to initialize hardware + * until someone tells me what is proper thing to do */ + if (!minfo->initialized) { + fb_info(&minfo->fbcon, "initializing hardware\n"); + /* We have to use FB_ACTIVATE_FORCE, as we had to put vesafb_defined to the fbcon.var + * already before, so register_framebuffer works correctly. */ + vesafb_defined.activate |= FB_ACTIVATE_FORCE; + fb_set_var(&minfo->fbcon, &vesafb_defined); + } + + return 0; +failVideoIO:; + matroxfb_g450_shutdown(minfo); + mga_iounmap(minfo->video.vbase); +failCtrlIO:; + mga_iounmap(minfo->mmio.vbase); +failVideoMR:; + release_mem_region(video_base_phys, minfo->video.len_maximum); +failCtrlMR:; + release_mem_region(ctrlptr_phys, 16384); +fail:; + return err; +} + +static LIST_HEAD(matroxfb_list); +static LIST_HEAD(matroxfb_driver_list); + +#define matroxfb_l(x) list_entry(x, struct matrox_fb_info, next_fb) +#define matroxfb_driver_l(x) list_entry(x, struct matroxfb_driver, node) +int matroxfb_register_driver(struct matroxfb_driver* drv) { + struct matrox_fb_info* minfo; + + list_add(&drv->node, &matroxfb_driver_list); + for (minfo = matroxfb_l(matroxfb_list.next); + minfo != matroxfb_l(&matroxfb_list); + minfo = matroxfb_l(minfo->next_fb.next)) { + void* p; + + if (minfo->drivers_count == MATROXFB_MAX_FB_DRIVERS) + continue; + p = drv->probe(minfo); + if (p) { + minfo->drivers_data[minfo->drivers_count] = p; + minfo->drivers[minfo->drivers_count++] = drv; + } + } + return 0; +} + +void matroxfb_unregister_driver(struct matroxfb_driver* drv) { + struct matrox_fb_info* minfo; + + list_del(&drv->node); + for (minfo = matroxfb_l(matroxfb_list.next); + minfo != matroxfb_l(&matroxfb_list); + minfo = matroxfb_l(minfo->next_fb.next)) { + int i; + + for (i = 0; i < minfo->drivers_count; ) { + if (minfo->drivers[i] == drv) { + if (drv && drv->remove) + drv->remove(minfo, minfo->drivers_data[i]); + minfo->drivers[i] = minfo->drivers[--minfo->drivers_count]; + minfo->drivers_data[i] = minfo->drivers_data[minfo->drivers_count]; + } else + i++; + } + } +} + +static void matroxfb_register_device(struct matrox_fb_info* minfo) { + struct matroxfb_driver* drv; + int i = 0; + list_add(&minfo->next_fb, &matroxfb_list); + for (drv = matroxfb_driver_l(matroxfb_driver_list.next); + drv != matroxfb_driver_l(&matroxfb_driver_list); + drv = matroxfb_driver_l(drv->node.next)) { + if (drv && drv->probe) { + void *p = drv->probe(minfo); + if (p) { + minfo->drivers_data[i] = p; + minfo->drivers[i++] = drv; + if (i == MATROXFB_MAX_FB_DRIVERS) + break; + } + } + } + minfo->drivers_count = i; +} + +static void matroxfb_unregister_device(struct matrox_fb_info* minfo) { + int i; + + list_del(&minfo->next_fb); + for (i = 0; i < minfo->drivers_count; i++) { + struct matroxfb_driver* drv = minfo->drivers[i]; + + if (drv && drv->remove) + drv->remove(minfo, minfo->drivers_data[i]); + } +} + +static int matroxfb_probe(struct pci_dev* pdev, const struct pci_device_id* dummy) { + struct board* b; + u_int16_t svid; + u_int16_t sid; + struct matrox_fb_info* minfo; + int err; + u_int32_t cmd; + DBG(__func__) + + svid = pdev->subsystem_vendor; + sid = pdev->subsystem_device; + for (b = dev_list; b->vendor; b++) { + if ((b->vendor != pdev->vendor) || (b->device != pdev->device) || (b->rev < pdev->revision)) continue; + if (b->svid) + if ((b->svid != svid) || (b->sid != sid)) continue; + break; + } + /* not match... */ + if (!b->vendor) + return -ENODEV; + if (dev > 0) { + /* not requested one... */ + dev--; + return -ENODEV; + } + pci_read_config_dword(pdev, PCI_COMMAND, &cmd); + if (pci_enable_device(pdev)) { + return -1; + } + + minfo = kzalloc(sizeof(*minfo), GFP_KERNEL); + if (!minfo) + return -1; + + minfo->pcidev = pdev; + minfo->dead = 0; + minfo->usecount = 0; + minfo->userusecount = 0; + + pci_set_drvdata(pdev, minfo); + /* DEVFLAGS */ + minfo->devflags.memtype = memtype; + if (memtype != -1) + noinit = 0; + if (cmd & PCI_COMMAND_MEMORY) { + minfo->devflags.novga = novga; + minfo->devflags.nobios = nobios; + minfo->devflags.noinit = noinit; + /* subsequent heads always needs initialization and must not enable BIOS */ + novga = 1; + nobios = 1; + noinit = 0; + } else { + minfo->devflags.novga = 1; + minfo->devflags.nobios = 1; + minfo->devflags.noinit = 0; + } + + minfo->devflags.nopciretry = no_pci_retry; + minfo->devflags.mga_24bpp_fix = inv24; + minfo->devflags.precise_width = option_precise_width; + minfo->devflags.sgram = sgram; + minfo->capable.cross4MB = cross4MB; + + spin_lock_init(&minfo->lock.DAC); + spin_lock_init(&minfo->lock.accel); + init_rwsem(&minfo->crtc2.lock); + init_rwsem(&minfo->altout.lock); + mutex_init(&minfo->fbcon.mm_lock); + minfo->irq_flags = 0; + init_waitqueue_head(&minfo->crtc1.vsync.wait); + init_waitqueue_head(&minfo->crtc2.vsync.wait); + minfo->crtc1.panpos = -1; + + err = initMatrox2(minfo, b); + if (!err) { + matroxfb_register_device(minfo); + return 0; + } + kfree(minfo); + return -1; +} + +static void pci_remove_matrox(struct pci_dev* pdev) { + struct matrox_fb_info* minfo; + + minfo = pci_get_drvdata(pdev); + matroxfb_remove(minfo, 1); +} + +static struct pci_device_id matroxfb_devices[] = { +#ifdef CONFIG_FB_MATROX_MILLENIUM + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL_2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL_2_AGP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, +#endif +#ifdef CONFIG_FB_MATROX_MYSTIQUE + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, +#endif +#ifdef CONFIG_FB_MATROX_G + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G100_MM, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G100_AGP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_PCI, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G550, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, +#endif + {0, 0, + 0, 0, 0, 0, 0} +}; + +MODULE_DEVICE_TABLE(pci, matroxfb_devices); + + +static struct pci_driver matroxfb_driver = { + .name = "matroxfb", + .id_table = matroxfb_devices, + .probe = matroxfb_probe, + .remove = pci_remove_matrox, +}; + +/* **************************** init-time only **************************** */ + +#define RSResolution(X) ((X) & 0x0F) +#define RS640x400 1 +#define RS640x480 2 +#define RS800x600 3 +#define RS1024x768 4 +#define RS1280x1024 5 +#define RS1600x1200 6 +#define RS768x576 7 +#define RS960x720 8 +#define RS1152x864 9 +#define RS1408x1056 10 +#define RS640x350 11 +#define RS1056x344 12 /* 132 x 43 text */ +#define RS1056x400 13 /* 132 x 50 text */ +#define RS1056x480 14 /* 132 x 60 text */ +#define RSNoxNo 15 +/* 10-FF */ +static struct { int xres, yres, left, right, upper, lower, hslen, vslen, vfreq; } timmings[] __initdata = { + { 640, 400, 48, 16, 39, 8, 96, 2, 70 }, + { 640, 480, 48, 16, 33, 10, 96, 2, 60 }, + { 800, 600, 144, 24, 28, 8, 112, 6, 60 }, + { 1024, 768, 160, 32, 30, 4, 128, 4, 60 }, + { 1280, 1024, 224, 32, 32, 4, 136, 4, 60 }, + { 1600, 1200, 272, 48, 32, 5, 152, 5, 60 }, + { 768, 576, 144, 16, 28, 6, 112, 4, 60 }, + { 960, 720, 144, 24, 28, 8, 112, 4, 60 }, + { 1152, 864, 192, 32, 30, 4, 128, 4, 60 }, + { 1408, 1056, 256, 40, 32, 5, 144, 5, 60 }, + { 640, 350, 48, 16, 39, 8, 96, 2, 70 }, + { 1056, 344, 96, 24, 59, 44, 160, 2, 70 }, + { 1056, 400, 96, 24, 39, 8, 160, 2, 70 }, + { 1056, 480, 96, 24, 36, 12, 160, 3, 60 }, + { 0, 0, ~0, ~0, ~0, ~0, 0, 0, 0 } +}; + +#define RSCreate(X,Y) ((X) | ((Y) << 8)) +static struct { unsigned int vesa; unsigned int info; } *RSptr, vesamap[] __initdata = { +/* default must be first */ + { ~0, RSCreate(RSNoxNo, RS8bpp ) }, + { 0x101, RSCreate(RS640x480, RS8bpp ) }, + { 0x100, RSCreate(RS640x400, RS8bpp ) }, + { 0x180, RSCreate(RS768x576, RS8bpp ) }, + { 0x103, RSCreate(RS800x600, RS8bpp ) }, + { 0x188, RSCreate(RS960x720, RS8bpp ) }, + { 0x105, RSCreate(RS1024x768, RS8bpp ) }, + { 0x190, RSCreate(RS1152x864, RS8bpp ) }, + { 0x107, RSCreate(RS1280x1024, RS8bpp ) }, + { 0x198, RSCreate(RS1408x1056, RS8bpp ) }, + { 0x11C, RSCreate(RS1600x1200, RS8bpp ) }, + { 0x110, RSCreate(RS640x480, RS15bpp) }, + { 0x181, RSCreate(RS768x576, RS15bpp) }, + { 0x113, RSCreate(RS800x600, RS15bpp) }, + { 0x189, RSCreate(RS960x720, RS15bpp) }, + { 0x116, RSCreate(RS1024x768, RS15bpp) }, + { 0x191, RSCreate(RS1152x864, RS15bpp) }, + { 0x119, RSCreate(RS1280x1024, RS15bpp) }, + { 0x199, RSCreate(RS1408x1056, RS15bpp) }, + { 0x11D, RSCreate(RS1600x1200, RS15bpp) }, + { 0x111, RSCreate(RS640x480, RS16bpp) }, + { 0x182, RSCreate(RS768x576, RS16bpp) }, + { 0x114, RSCreate(RS800x600, RS16bpp) }, + { 0x18A, RSCreate(RS960x720, RS16bpp) }, + { 0x117, RSCreate(RS1024x768, RS16bpp) }, + { 0x192, RSCreate(RS1152x864, RS16bpp) }, + { 0x11A, RSCreate(RS1280x1024, RS16bpp) }, + { 0x19A, RSCreate(RS1408x1056, RS16bpp) }, + { 0x11E, RSCreate(RS1600x1200, RS16bpp) }, + { 0x1B2, RSCreate(RS640x480, RS24bpp) }, + { 0x184, RSCreate(RS768x576, RS24bpp) }, + { 0x1B5, RSCreate(RS800x600, RS24bpp) }, + { 0x18C, RSCreate(RS960x720, RS24bpp) }, + { 0x1B8, RSCreate(RS1024x768, RS24bpp) }, + { 0x194, RSCreate(RS1152x864, RS24bpp) }, + { 0x1BB, RSCreate(RS1280x1024, RS24bpp) }, + { 0x19C, RSCreate(RS1408x1056, RS24bpp) }, + { 0x1BF, RSCreate(RS1600x1200, RS24bpp) }, + { 0x112, RSCreate(RS640x480, RS32bpp) }, + { 0x183, RSCreate(RS768x576, RS32bpp) }, + { 0x115, RSCreate(RS800x600, RS32bpp) }, + { 0x18B, RSCreate(RS960x720, RS32bpp) }, + { 0x118, RSCreate(RS1024x768, RS32bpp) }, + { 0x193, RSCreate(RS1152x864, RS32bpp) }, + { 0x11B, RSCreate(RS1280x1024, RS32bpp) }, + { 0x19B, RSCreate(RS1408x1056, RS32bpp) }, + { 0x11F, RSCreate(RS1600x1200, RS32bpp) }, + { 0x010, RSCreate(RS640x350, RS4bpp ) }, + { 0x012, RSCreate(RS640x480, RS4bpp ) }, + { 0x102, RSCreate(RS800x600, RS4bpp ) }, + { 0x104, RSCreate(RS1024x768, RS4bpp ) }, + { 0x106, RSCreate(RS1280x1024, RS4bpp ) }, + { 0, 0 }}; + +static void __init matroxfb_init_params(void) { + /* fh from kHz to Hz */ + if (fh < 1000) + fh *= 1000; /* 1kHz minimum */ + /* maxclk */ + if (maxclk < 1000) maxclk *= 1000; /* kHz -> Hz, MHz -> kHz */ + if (maxclk < 1000000) maxclk *= 1000; /* kHz -> Hz, 1MHz minimum */ + /* fix VESA number */ + if (vesa != ~0) + vesa &= 0x1DFF; /* mask out clearscreen, acceleration and so on */ + + /* static settings */ + for (RSptr = vesamap; RSptr->vesa; RSptr++) { + if (RSptr->vesa == vesa) break; + } + if (!RSptr->vesa) { + printk(KERN_ERR "Invalid vesa mode 0x%04X\n", vesa); + RSptr = vesamap; + } + { + int res = RSResolution(RSptr->info)-1; + if (left == ~0) + left = timmings[res].left; + if (!xres) + xres = timmings[res].xres; + if (right == ~0) + right = timmings[res].right; + if (!hslen) + hslen = timmings[res].hslen; + if (upper == ~0) + upper = timmings[res].upper; + if (!yres) + yres = timmings[res].yres; + if (lower == ~0) + lower = timmings[res].lower; + if (!vslen) + vslen = timmings[res].vslen; + if (!(fv||fh||maxclk||pixclock)) + fv = timmings[res].vfreq; + if (depth == -1) + depth = RSDepth(RSptr->info); + } +} + +static int __init matrox_init(void) { + int err; + + matroxfb_init_params(); + err = pci_register_driver(&matroxfb_driver); + dev = -1; /* accept all new devices... */ + return err; +} + +/* **************************** exit-time only **************************** */ + +static void __exit matrox_done(void) { + pci_unregister_driver(&matroxfb_driver); +} + +#ifndef MODULE + +/* ************************* init in-kernel code ************************** */ + +static int __init matroxfb_setup(char *options) { + char *this_opt; + + DBG(__func__) + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + + dprintk("matroxfb_setup: option %s\n", this_opt); + + if (!strncmp(this_opt, "dev:", 4)) + dev = simple_strtoul(this_opt+4, NULL, 0); + else if (!strncmp(this_opt, "depth:", 6)) { + switch (simple_strtoul(this_opt+6, NULL, 0)) { + case 0: depth = RSText; break; + case 4: depth = RS4bpp; break; + case 8: depth = RS8bpp; break; + case 15:depth = RS15bpp; break; + case 16:depth = RS16bpp; break; + case 24:depth = RS24bpp; break; + case 32:depth = RS32bpp; break; + default: + printk(KERN_ERR "matroxfb: unsupported color depth\n"); + } + } else if (!strncmp(this_opt, "xres:", 5)) + xres = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "yres:", 5)) + yres = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "vslen:", 6)) + vslen = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "hslen:", 6)) + hslen = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "left:", 5)) + left = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "right:", 6)) + right = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "upper:", 6)) + upper = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "lower:", 6)) + lower = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "pixclock:", 9)) + pixclock = simple_strtoul(this_opt+9, NULL, 0); + else if (!strncmp(this_opt, "sync:", 5)) + sync = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "vesa:", 5)) + vesa = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "maxclk:", 7)) + maxclk = simple_strtoul(this_opt+7, NULL, 0); + else if (!strncmp(this_opt, "fh:", 3)) + fh = simple_strtoul(this_opt+3, NULL, 0); + else if (!strncmp(this_opt, "fv:", 3)) + fv = simple_strtoul(this_opt+3, NULL, 0); + else if (!strncmp(this_opt, "mem:", 4)) + mem = simple_strtoul(this_opt+4, NULL, 0); + else if (!strncmp(this_opt, "mode:", 5)) + strlcpy(videomode, this_opt+5, sizeof(videomode)); + else if (!strncmp(this_opt, "outputs:", 8)) + strlcpy(outputs, this_opt+8, sizeof(outputs)); + else if (!strncmp(this_opt, "dfp:", 4)) { + dfp_type = simple_strtoul(this_opt+4, NULL, 0); + dfp = 1; + } +#ifdef CONFIG_PPC_PMAC + else if (!strncmp(this_opt, "vmode:", 6)) { + unsigned int vmode = simple_strtoul(this_opt+6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX) + default_vmode = vmode; + } else if (!strncmp(this_opt, "cmode:", 6)) { + unsigned int cmode = simple_strtoul(this_opt+6, NULL, 0); + switch (cmode) { + case 0: + case 8: + default_cmode = CMODE_8; + break; + case 15: + case 16: + default_cmode = CMODE_16; + break; + case 24: + case 32: + default_cmode = CMODE_32; + break; + } + } +#endif + else if (!strcmp(this_opt, "disabled")) /* nodisabled does not exist */ + disabled = 1; + else if (!strcmp(this_opt, "enabled")) /* noenabled does not exist */ + disabled = 0; + else if (!strcmp(this_opt, "sgram")) /* nosgram == sdram */ + sgram = 1; + else if (!strcmp(this_opt, "sdram")) + sgram = 0; + else if (!strncmp(this_opt, "memtype:", 8)) + memtype = simple_strtoul(this_opt+8, NULL, 0); + else { + int value = 1; + + if (!strncmp(this_opt, "no", 2)) { + value = 0; + this_opt += 2; + } + if (! strcmp(this_opt, "inverse")) + inverse = value; + else if (!strcmp(this_opt, "accel")) + noaccel = !value; + else if (!strcmp(this_opt, "pan")) + nopan = !value; + else if (!strcmp(this_opt, "pciretry")) + no_pci_retry = !value; + else if (!strcmp(this_opt, "vga")) + novga = !value; + else if (!strcmp(this_opt, "bios")) + nobios = !value; + else if (!strcmp(this_opt, "init")) + noinit = !value; +#ifdef CONFIG_MTRR + else if (!strcmp(this_opt, "mtrr")) + mtrr = value; +#endif + else if (!strcmp(this_opt, "inv24")) + inv24 = value; + else if (!strcmp(this_opt, "cross4MB")) + cross4MB = value; + else if (!strcmp(this_opt, "grayscale")) + grayscale = value; + else if (!strcmp(this_opt, "dfp")) + dfp = value; + else { + strlcpy(videomode, this_opt, sizeof(videomode)); + } + } + } + return 0; +} + +static int __initdata initialized = 0; + +static int __init matroxfb_init(void) +{ + char *option = NULL; + int err = 0; + + DBG(__func__) + + if (fb_get_options("matroxfb", &option)) + return -ENODEV; + matroxfb_setup(option); + + if (disabled) + return -ENXIO; + if (!initialized) { + initialized = 1; + err = matrox_init(); + } + hotplug = 1; + /* never return failure, user can hotplug matrox later... */ + return err; +} + +module_init(matroxfb_init); + +#else + +/* *************************** init module code **************************** */ + +MODULE_AUTHOR("(c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Accelerated FBDev driver for Matrox Millennium/Mystique/G100/G200/G400/G450/G550"); +MODULE_LICENSE("GPL"); + +module_param(mem, int, 0); +MODULE_PARM_DESC(mem, "Size of available memory in MB, KB or B (2,4,8,12,16MB, default=autodetect)"); +module_param(disabled, int, 0); +MODULE_PARM_DESC(disabled, "Disabled (0 or 1=disabled) (default=0)"); +module_param(noaccel, int, 0); +MODULE_PARM_DESC(noaccel, "Do not use accelerating engine (0 or 1=disabled) (default=0)"); +module_param(nopan, int, 0); +MODULE_PARM_DESC(nopan, "Disable pan on startup (0 or 1=disabled) (default=0)"); +module_param(no_pci_retry, int, 0); +MODULE_PARM_DESC(no_pci_retry, "PCI retries enabled (0 or 1=disabled) (default=0)"); +module_param(novga, int, 0); +MODULE_PARM_DESC(novga, "VGA I/O (0x3C0-0x3DF) disabled (0 or 1=disabled) (default=0)"); +module_param(nobios, int, 0); +MODULE_PARM_DESC(nobios, "Disables ROM BIOS (0 or 1=disabled) (default=do not change BIOS state)"); +module_param(noinit, int, 0); +MODULE_PARM_DESC(noinit, "Disables W/SG/SD-RAM and bus interface initialization (0 or 1=do not initialize) (default=0)"); +module_param(memtype, int, 0); +MODULE_PARM_DESC(memtype, "Memory type for G200/G400 (see Documentation/fb/matroxfb.txt for explanation) (default=3 for G200, 0 for G400)"); +#ifdef CONFIG_MTRR +module_param(mtrr, int, 0); +MODULE_PARM_DESC(mtrr, "This speeds up video memory accesses (0=disabled or 1) (default=1)"); +#endif +module_param(sgram, int, 0); +MODULE_PARM_DESC(sgram, "Indicates that G100/G200/G400 has SGRAM memory (0=SDRAM, 1=SGRAM) (default=0)"); +module_param(inv24, int, 0); +MODULE_PARM_DESC(inv24, "Inverts clock polarity for 24bpp and loop frequency > 100MHz (default=do not invert polarity)"); +module_param(inverse, int, 0); +MODULE_PARM_DESC(inverse, "Inverse (0 or 1) (default=0)"); +module_param(dev, int, 0); +MODULE_PARM_DESC(dev, "Multihead support, attach to device ID (0..N) (default=all working)"); +module_param(vesa, int, 0); +MODULE_PARM_DESC(vesa, "Startup videomode (0x000-0x1FF) (default=0x101)"); +module_param(xres, int, 0); +MODULE_PARM_DESC(xres, "Horizontal resolution (px), overrides xres from vesa (default=vesa)"); +module_param(yres, int, 0); +MODULE_PARM_DESC(yres, "Vertical resolution (scans), overrides yres from vesa (default=vesa)"); +module_param(upper, int, 0); +MODULE_PARM_DESC(upper, "Upper blank space (scans), overrides upper from vesa (default=vesa)"); +module_param(lower, int, 0); +MODULE_PARM_DESC(lower, "Lower blank space (scans), overrides lower from vesa (default=vesa)"); +module_param(vslen, int, 0); +MODULE_PARM_DESC(vslen, "Vertical sync length (scans), overrides lower from vesa (default=vesa)"); +module_param(left, int, 0); +MODULE_PARM_DESC(left, "Left blank space (px), overrides left from vesa (default=vesa)"); +module_param(right, int, 0); +MODULE_PARM_DESC(right, "Right blank space (px), overrides right from vesa (default=vesa)"); +module_param(hslen, int, 0); +MODULE_PARM_DESC(hslen, "Horizontal sync length (px), overrides hslen from vesa (default=vesa)"); +module_param(pixclock, int, 0); +MODULE_PARM_DESC(pixclock, "Pixelclock (ns), overrides pixclock from vesa (default=vesa)"); +module_param(sync, int, 0); +MODULE_PARM_DESC(sync, "Sync polarity, overrides sync from vesa (default=vesa)"); +module_param(depth, int, 0); +MODULE_PARM_DESC(depth, "Color depth (0=text,8,15,16,24,32) (default=vesa)"); +module_param(maxclk, int, 0); +MODULE_PARM_DESC(maxclk, "Startup maximal clock, 0-999MHz, 1000-999999kHz, 1000000-INF Hz"); +module_param(fh, int, 0); +MODULE_PARM_DESC(fh, "Startup horizontal frequency, 0-999kHz, 1000-INF Hz"); +module_param(fv, int, 0); +MODULE_PARM_DESC(fv, "Startup vertical frequency, 0-INF Hz\n" +"You should specify \"fv:max_monitor_vsync,fh:max_monitor_hsync,maxclk:max_monitor_dotclock\""); +module_param(grayscale, int, 0); +MODULE_PARM_DESC(grayscale, "Sets display into grayscale. Works perfectly with paletized videomode (4, 8bpp), some limitations apply to 16, 24 and 32bpp videomodes (default=nograyscale)"); +module_param(cross4MB, int, 0); +MODULE_PARM_DESC(cross4MB, "Specifies that 4MB boundary can be in middle of line. (default=autodetected)"); +module_param(dfp, int, 0); +MODULE_PARM_DESC(dfp, "Specifies whether to use digital flat panel interface of G200/G400 (0 or 1) (default=0)"); +module_param(dfp_type, int, 0); +MODULE_PARM_DESC(dfp_type, "Specifies DFP interface type (0 to 255) (default=read from hardware)"); +module_param_string(outputs, outputs, sizeof(outputs), 0); +MODULE_PARM_DESC(outputs, "Specifies which CRTC is mapped to which output (string of up to three letters, consisting of 0 (disabled), 1 (CRTC1), 2 (CRTC2)) (default=111 for Gx50, 101 for G200/G400 with DFP, and 100 for all other devices)"); +#ifdef CONFIG_PPC_PMAC +module_param_named(vmode, default_vmode, int, 0); +MODULE_PARM_DESC(vmode, "Specify the vmode mode number that should be used (640x480 default)"); +module_param_named(cmode, default_cmode, int, 0); +MODULE_PARM_DESC(cmode, "Specify the video depth that should be used (8bit default)"); +#endif + +int __init init_module(void){ + + DBG(__func__) + + if (disabled) + return -ENXIO; + + if (depth == 0) + depth = RSText; + else if (depth == 4) + depth = RS4bpp; + else if (depth == 8) + depth = RS8bpp; + else if (depth == 15) + depth = RS15bpp; + else if (depth == 16) + depth = RS16bpp; + else if (depth == 24) + depth = RS24bpp; + else if (depth == 32) + depth = RS32bpp; + else if (depth != -1) { + printk(KERN_ERR "matroxfb: depth %d is not supported, using default\n", depth); + depth = -1; + } + matrox_init(); + /* never return failure; user can hotplug matrox later... */ + return 0; +} +#endif /* MODULE */ + +module_exit(matrox_done); +EXPORT_SYMBOL(matroxfb_register_driver); +EXPORT_SYMBOL(matroxfb_unregister_driver); +EXPORT_SYMBOL(matroxfb_wait_for_sync); +EXPORT_SYMBOL(matroxfb_enable_irq); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/drivers/video/fbdev/matrox/matroxfb_base.h b/drivers/video/fbdev/matrox/matroxfb_base.h new file mode 100644 index 000000000000..556d96ce40bf --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_base.h @@ -0,0 +1,735 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + */ +#ifndef __MATROXFB_H__ +#define __MATROXFB_H__ + +/* general, but fairly heavy, debugging */ +#undef MATROXFB_DEBUG + +/* heavy debugging: */ +/* -- logs putc[s], so every time a char is displayed, it's logged */ +#undef MATROXFB_DEBUG_HEAVY + +/* This one _could_ cause infinite loops */ +/* It _does_ cause lots and lots of messages during idle loops */ +#undef MATROXFB_DEBUG_LOOP + +/* Debug register calls, too? */ +#undef MATROXFB_DEBUG_REG + +/* Guard accelerator accesses with spin_lock_irqsave... */ +#undef MATROXFB_USE_SPINLOCKS + +#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/console.h> +#include <linux/selection.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/kd.h> + +#include <asm/io.h> +#include <asm/unaligned.h> +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#if defined(CONFIG_PPC_PMAC) +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#include "../macmodes.h" +#endif + +#ifdef MATROXFB_DEBUG + +#define DEBUG +#define DBG(x) printk(KERN_DEBUG "matroxfb: %s\n", (x)); + +#ifdef MATROXFB_DEBUG_HEAVY +#define DBG_HEAVY(x) DBG(x) +#else /* MATROXFB_DEBUG_HEAVY */ +#define DBG_HEAVY(x) /* DBG_HEAVY */ +#endif /* MATROXFB_DEBUG_HEAVY */ + +#ifdef MATROXFB_DEBUG_LOOP +#define DBG_LOOP(x) DBG(x) +#else /* MATROXFB_DEBUG_LOOP */ +#define DBG_LOOP(x) /* DBG_LOOP */ +#endif /* MATROXFB_DEBUG_LOOP */ + +#ifdef MATROXFB_DEBUG_REG +#define DBG_REG(x) DBG(x) +#else /* MATROXFB_DEBUG_REG */ +#define DBG_REG(x) /* DBG_REG */ +#endif /* MATROXFB_DEBUG_REG */ + +#else /* MATROXFB_DEBUG */ + +#define DBG(x) /* DBG */ +#define DBG_HEAVY(x) /* DBG_HEAVY */ +#define DBG_REG(x) /* DBG_REG */ +#define DBG_LOOP(x) /* DBG_LOOP */ + +#endif /* MATROXFB_DEBUG */ + +#ifdef DEBUG +#define dprintk(X...) printk(X) +#else +#define dprintk(X...) +#endif + +#ifndef PCI_SS_VENDOR_ID_SIEMENS_NIXDORF +#define PCI_SS_VENDOR_ID_SIEMENS_NIXDORF 0x110A +#endif +#ifndef PCI_SS_VENDOR_ID_MATROX +#define PCI_SS_VENDOR_ID_MATROX PCI_VENDOR_ID_MATROX +#endif + +#ifndef PCI_SS_ID_MATROX_PRODUCTIVA_G100_AGP +#define PCI_SS_ID_MATROX_GENERIC 0xFF00 +#define PCI_SS_ID_MATROX_PRODUCTIVA_G100_AGP 0xFF01 +#define PCI_SS_ID_MATROX_MYSTIQUE_G200_AGP 0xFF02 +#define PCI_SS_ID_MATROX_MILLENIUM_G200_AGP 0xFF03 +#define PCI_SS_ID_MATROX_MARVEL_G200_AGP 0xFF04 +#define PCI_SS_ID_MATROX_MGA_G100_PCI 0xFF05 +#define PCI_SS_ID_MATROX_MGA_G100_AGP 0x1001 +#define PCI_SS_ID_MATROX_MILLENNIUM_G400_MAX_AGP 0x2179 +#define PCI_SS_ID_SIEMENS_MGA_G100_AGP 0x001E /* 30 */ +#define PCI_SS_ID_SIEMENS_MGA_G200_AGP 0x0032 /* 50 */ +#endif + +#define MX_VISUAL_TRUECOLOR FB_VISUAL_DIRECTCOLOR +#define MX_VISUAL_DIRECTCOLOR FB_VISUAL_TRUECOLOR +#define MX_VISUAL_PSEUDOCOLOR FB_VISUAL_PSEUDOCOLOR + +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + +/* G-series and Mystique have (almost) same DAC */ +#undef NEED_DAC1064 +#if defined(CONFIG_FB_MATROX_MYSTIQUE) || defined(CONFIG_FB_MATROX_G) +#define NEED_DAC1064 1 +#endif + +typedef struct { + void __iomem* vaddr; +} vaddr_t; + +static inline unsigned int mga_readb(vaddr_t va, unsigned int offs) { + return readb(va.vaddr + offs); +} + +static inline void mga_writeb(vaddr_t va, unsigned int offs, u_int8_t value) { + writeb(value, va.vaddr + offs); +} + +static inline void mga_writew(vaddr_t va, unsigned int offs, u_int16_t value) { + writew(value, va.vaddr + offs); +} + +static inline u_int32_t mga_readl(vaddr_t va, unsigned int offs) { + return readl(va.vaddr + offs); +} + +static inline void mga_writel(vaddr_t va, unsigned int offs, u_int32_t value) { + writel(value, va.vaddr + offs); +} + +static inline void mga_memcpy_toio(vaddr_t va, const void* src, int len) { +#if defined(__alpha__) || defined(__i386__) || defined(__x86_64__) + /* + * iowrite32_rep works for us if: + * (1) Copies data as 32bit quantities, not byte after byte, + * (2) Performs LE ordered stores, and + * (3) It copes with unaligned source (destination is guaranteed to be page + * aligned and length is guaranteed to be multiple of 4). + */ + iowrite32_rep(va.vaddr, src, len >> 2); +#else + u_int32_t __iomem* addr = va.vaddr; + + if ((unsigned long)src & 3) { + while (len >= 4) { + fb_writel(get_unaligned((u32 *)src), addr); + addr++; + len -= 4; + src += 4; + } + } else { + while (len >= 4) { + fb_writel(*(u32 *)src, addr); + addr++; + len -= 4; + src += 4; + } + } +#endif +} + +static inline void vaddr_add(vaddr_t* va, unsigned long offs) { + va->vaddr += offs; +} + +static inline void __iomem* vaddr_va(vaddr_t va) { + return va.vaddr; +} + +#define MGA_IOREMAP_NORMAL 0 +#define MGA_IOREMAP_NOCACHE 1 + +#define MGA_IOREMAP_FB MGA_IOREMAP_NOCACHE +#define MGA_IOREMAP_MMIO MGA_IOREMAP_NOCACHE +static inline int mga_ioremap(unsigned long phys, unsigned long size, int flags, vaddr_t* virt) { + if (flags & MGA_IOREMAP_NOCACHE) + virt->vaddr = ioremap_nocache(phys, size); + else + virt->vaddr = ioremap(phys, size); + return (virt->vaddr == NULL); /* 0, !0... 0, error_code in future */ +} + +static inline void mga_iounmap(vaddr_t va) { + iounmap(va.vaddr); +} + +struct my_timming { + unsigned int pixclock; + int mnp; + unsigned int crtc; + unsigned int HDisplay; + unsigned int HSyncStart; + unsigned int HSyncEnd; + unsigned int HTotal; + unsigned int VDisplay; + unsigned int VSyncStart; + unsigned int VSyncEnd; + unsigned int VTotal; + unsigned int sync; + int dblscan; + int interlaced; + unsigned int delay; /* CRTC delay */ +}; + +enum { M_SYSTEM_PLL, M_PIXEL_PLL_A, M_PIXEL_PLL_B, M_PIXEL_PLL_C, M_VIDEO_PLL }; + +struct matrox_pll_cache { + unsigned int valid; + struct { + unsigned int mnp_key; + unsigned int mnp_value; + } data[4]; +}; + +struct matrox_pll_limits { + unsigned int vcomin; + unsigned int vcomax; +}; + +struct matrox_pll_features { + unsigned int vco_freq_min; + unsigned int ref_freq; + unsigned int feed_div_min; + unsigned int feed_div_max; + unsigned int in_div_min; + unsigned int in_div_max; + unsigned int post_shift_max; +}; + +struct matroxfb_par +{ + unsigned int final_bppShift; + unsigned int cmap_len; + struct { + unsigned int bytes; + unsigned int pixels; + unsigned int chunks; + } ydstorg; +}; + +struct matrox_fb_info; + +struct matrox_DAC1064_features { + u_int8_t xvrefctrl; + u_int8_t xmiscctrl; +}; + +/* current hardware status */ +struct mavenregs { + u_int8_t regs[256]; + int mode; + int vlines; + int xtal; + int fv; + + u_int16_t htotal; + u_int16_t hcorr; +}; + +struct matrox_crtc2 { + u_int32_t ctl; +}; + +struct matrox_hw_state { + u_int32_t MXoptionReg; + unsigned char DACclk[6]; + unsigned char DACreg[80]; + unsigned char MiscOutReg; + unsigned char DACpal[768]; + unsigned char CRTC[25]; + unsigned char CRTCEXT[9]; + unsigned char SEQ[5]; + /* unused for MGA mode, but who knows... */ + unsigned char GCTL[9]; + /* unused for MGA mode, but who knows... */ + unsigned char ATTR[21]; + + /* TVOut only */ + struct mavenregs maven; + + struct matrox_crtc2 crtc2; +}; + +struct matrox_accel_data { +#ifdef CONFIG_FB_MATROX_MILLENIUM + unsigned char ramdac_rev; +#endif + u_int32_t m_dwg_rect; + u_int32_t m_opmode; + u_int32_t m_access; + u_int32_t m_pitch; +}; + +struct v4l2_queryctrl; +struct v4l2_control; + +struct matrox_altout { + const char *name; + int (*compute)(void* altout_dev, struct my_timming* input); + int (*program)(void* altout_dev); + int (*start)(void* altout_dev); + int (*verifymode)(void* altout_dev, u_int32_t mode); + int (*getqueryctrl)(void* altout_dev, + struct v4l2_queryctrl* ctrl); + int (*getctrl)(void* altout_dev, + struct v4l2_control* ctrl); + int (*setctrl)(void* altout_dev, + struct v4l2_control* ctrl); +}; + +#define MATROXFB_SRC_NONE 0 +#define MATROXFB_SRC_CRTC1 1 +#define MATROXFB_SRC_CRTC2 2 + +enum mga_chip { MGA_2064, MGA_2164, MGA_1064, MGA_1164, MGA_G100, MGA_G200, MGA_G400, MGA_G450, MGA_G550 }; + +struct matrox_bios { + unsigned int bios_valid : 1; + unsigned int pins_len; + unsigned char pins[128]; + struct { + unsigned char vMaj, vMin, vRev; + } version; + struct { + unsigned char state, tvout; + } output; +}; + +struct matrox_switch; +struct matroxfb_driver; +struct matroxfb_dh_fb_info; + +struct matrox_vsync { + wait_queue_head_t wait; + unsigned int cnt; +}; + +struct matrox_fb_info { + struct fb_info fbcon; + + struct list_head next_fb; + + int dead; + int initialized; + unsigned int usecount; + + unsigned int userusecount; + unsigned long irq_flags; + + struct matroxfb_par curr; + struct matrox_hw_state hw; + + struct matrox_accel_data accel; + + struct pci_dev* pcidev; + + struct { + struct matrox_vsync vsync; + unsigned int pixclock; + int mnp; + int panpos; + } crtc1; + struct { + struct matrox_vsync vsync; + unsigned int pixclock; + int mnp; + struct matroxfb_dh_fb_info* info; + struct rw_semaphore lock; + } crtc2; + struct { + struct rw_semaphore lock; + struct { + int brightness, contrast, saturation, hue, gamma; + int testout, deflicker; + } tvo_params; + } altout; +#define MATROXFB_MAX_OUTPUTS 3 + struct { + unsigned int src; + struct matrox_altout* output; + void* data; + unsigned int mode; + unsigned int default_src; + } outputs[MATROXFB_MAX_OUTPUTS]; + +#define MATROXFB_MAX_FB_DRIVERS 5 + struct matroxfb_driver* (drivers[MATROXFB_MAX_FB_DRIVERS]); + void* (drivers_data[MATROXFB_MAX_FB_DRIVERS]); + unsigned int drivers_count; + + struct { + unsigned long base; /* physical */ + vaddr_t vbase; /* CPU view */ + unsigned int len; + unsigned int len_usable; + unsigned int len_maximum; + } video; + + struct { + unsigned long base; /* physical */ + vaddr_t vbase; /* CPU view */ + unsigned int len; + } mmio; + + unsigned int max_pixel_clock; + unsigned int max_pixel_clock_panellink; + + struct matrox_switch* hw_switch; + + struct { + struct matrox_pll_features pll; + struct matrox_DAC1064_features DAC1064; + } features; + struct { + spinlock_t DAC; + spinlock_t accel; + } lock; + + enum mga_chip chip; + + int interleave; + int millenium; + int milleniumII; + struct { + int cfb4; + const int* vxres; + int cross4MB; + int text; + int plnwt; + int srcorg; + } capable; +#ifdef CONFIG_MTRR + struct { + int vram; + int vram_valid; + } mtrr; +#endif + struct { + int precise_width; + int mga_24bpp_fix; + int novga; + int nobios; + int nopciretry; + int noinit; + int sgram; + int support32MB; + + int accelerator; + int text_type_aux; + int video64bits; + int crtc2; + int maven_capable; + unsigned int vgastep; + unsigned int textmode; + unsigned int textstep; + unsigned int textvram; /* character cells */ + unsigned int ydstorg; /* offset in bytes from video start to usable memory */ + /* 0 except for 6MB Millenium */ + int memtype; + int g450dac; + int dfp_type; + int panellink; /* G400 DFP possible (not G450/G550) */ + int dualhead; + unsigned int fbResource; + } devflags; + struct fb_ops fbops; + struct matrox_bios bios; + struct { + struct matrox_pll_limits pixel; + struct matrox_pll_limits system; + struct matrox_pll_limits video; + } limits; + struct { + struct matrox_pll_cache pixel; + struct matrox_pll_cache system; + struct matrox_pll_cache video; + } cache; + struct { + struct { + unsigned int video; + unsigned int system; + } pll; + struct { + u_int32_t opt; + u_int32_t opt2; + u_int32_t opt3; + u_int32_t mctlwtst; + u_int32_t mctlwtst_core; + u_int32_t memmisc; + u_int32_t memrdbk; + u_int32_t maccess; + } reg; + struct { + unsigned int ddr:1, + emrswen:1, + dll:1; + } memory; + } values; + u_int32_t cmap[16]; +}; + +#define info2minfo(info) container_of(info, struct matrox_fb_info, fbcon) + +struct matrox_switch { + int (*preinit)(struct matrox_fb_info *minfo); + void (*reset)(struct matrox_fb_info *minfo); + int (*init)(struct matrox_fb_info *minfo, struct my_timming*); + void (*restore)(struct matrox_fb_info *minfo); +}; + +struct matroxfb_driver { + struct list_head node; + char* name; + void* (*probe)(struct matrox_fb_info* info); + void (*remove)(struct matrox_fb_info* info, void* data); +}; + +int matroxfb_register_driver(struct matroxfb_driver* drv); +void matroxfb_unregister_driver(struct matroxfb_driver* drv); + +#define PCI_OPTION_REG 0x40 +#define PCI_OPTION_ENABLE_ROM 0x40000000 + +#define PCI_MGA_INDEX 0x44 +#define PCI_MGA_DATA 0x48 +#define PCI_OPTION2_REG 0x50 +#define PCI_OPTION3_REG 0x54 +#define PCI_MEMMISC_REG 0x58 + +#define M_DWGCTL 0x1C00 +#define M_MACCESS 0x1C04 +#define M_CTLWTST 0x1C08 + +#define M_PLNWT 0x1C1C + +#define M_BCOL 0x1C20 +#define M_FCOL 0x1C24 + +#define M_SGN 0x1C58 +#define M_LEN 0x1C5C +#define M_AR0 0x1C60 +#define M_AR1 0x1C64 +#define M_AR2 0x1C68 +#define M_AR3 0x1C6C +#define M_AR4 0x1C70 +#define M_AR5 0x1C74 +#define M_AR6 0x1C78 + +#define M_CXBNDRY 0x1C80 +#define M_FXBNDRY 0x1C84 +#define M_YDSTLEN 0x1C88 +#define M_PITCH 0x1C8C +#define M_YDST 0x1C90 +#define M_YDSTORG 0x1C94 +#define M_YTOP 0x1C98 +#define M_YBOT 0x1C9C + +/* mystique only */ +#define M_CACHEFLUSH 0x1FFF + +#define M_EXEC 0x0100 + +#define M_DWG_TRAP 0x04 +#define M_DWG_BITBLT 0x08 +#define M_DWG_ILOAD 0x09 + +#define M_DWG_LINEAR 0x0080 +#define M_DWG_SOLID 0x0800 +#define M_DWG_ARZERO 0x1000 +#define M_DWG_SGNZERO 0x2000 +#define M_DWG_SHIFTZERO 0x4000 + +#define M_DWG_REPLACE 0x000C0000 +#define M_DWG_REPLACE2 (M_DWG_REPLACE | 0x40) +#define M_DWG_XOR 0x00060010 + +#define M_DWG_BFCOL 0x04000000 +#define M_DWG_BMONOWF 0x08000000 + +#define M_DWG_TRANSC 0x40000000 + +#define M_FIFOSTATUS 0x1E10 +#define M_STATUS 0x1E14 +#define M_ICLEAR 0x1E18 +#define M_IEN 0x1E1C + +#define M_VCOUNT 0x1E20 + +#define M_RESET 0x1E40 +#define M_MEMRDBK 0x1E44 + +#define M_AGP2PLL 0x1E4C + +#define M_OPMODE 0x1E54 +#define M_OPMODE_DMA_GEN_WRITE 0x00 +#define M_OPMODE_DMA_BLIT 0x04 +#define M_OPMODE_DMA_VECTOR_WRITE 0x08 +#define M_OPMODE_DMA_LE 0x0000 /* little endian - no transformation */ +#define M_OPMODE_DMA_BE_8BPP 0x0000 +#define M_OPMODE_DMA_BE_16BPP 0x0100 +#define M_OPMODE_DMA_BE_32BPP 0x0200 +#define M_OPMODE_DIR_LE 0x000000 /* little endian - no transformation */ +#define M_OPMODE_DIR_BE_8BPP 0x000000 +#define M_OPMODE_DIR_BE_16BPP 0x010000 +#define M_OPMODE_DIR_BE_32BPP 0x020000 + +#define M_ATTR_INDEX 0x1FC0 +#define M_ATTR_DATA 0x1FC1 + +#define M_MISC_REG 0x1FC2 +#define M_3C2_RD 0x1FC2 + +#define M_SEQ_INDEX 0x1FC4 +#define M_SEQ_DATA 0x1FC5 +#define M_SEQ1 0x01 +#define M_SEQ1_SCROFF 0x20 + +#define M_MISC_REG_READ 0x1FCC + +#define M_GRAPHICS_INDEX 0x1FCE +#define M_GRAPHICS_DATA 0x1FCF + +#define M_CRTC_INDEX 0x1FD4 + +#define M_ATTR_RESET 0x1FDA +#define M_3DA_WR 0x1FDA +#define M_INSTS1 0x1FDA + +#define M_EXTVGA_INDEX 0x1FDE +#define M_EXTVGA_DATA 0x1FDF + +/* G200 only */ +#define M_SRCORG 0x2CB4 +#define M_DSTORG 0x2CB8 + +#define M_RAMDAC_BASE 0x3C00 + +/* fortunately, same on TVP3026 and MGA1064 */ +#define M_DAC_REG (M_RAMDAC_BASE+0) +#define M_DAC_VAL (M_RAMDAC_BASE+1) +#define M_PALETTE_MASK (M_RAMDAC_BASE+2) + +#define M_X_INDEX 0x00 +#define M_X_DATAREG 0x0A + +#define DAC_XGENIOCTRL 0x2A +#define DAC_XGENIODATA 0x2B + +#define M_C2CTL 0x3C10 + +#define MX_OPTION_BSWAP 0x00000000 + +#ifdef __LITTLE_ENDIAN +#define M_OPMODE_4BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_8BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_16BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_24BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_32BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#else +#ifdef __BIG_ENDIAN +#define M_OPMODE_4BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) /* TODO */ +#define M_OPMODE_8BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_8BPP | M_OPMODE_DMA_BLIT) +#define M_OPMODE_16BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_16BPP | M_OPMODE_DMA_BLIT) +#define M_OPMODE_24BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_8BPP | M_OPMODE_DMA_BLIT) /* TODO, ?32 */ +#define M_OPMODE_32BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_32BPP | M_OPMODE_DMA_BLIT) +#else +#error "Byte ordering have to be defined. Cannot continue." +#endif +#endif + +#define mga_inb(addr) mga_readb(minfo->mmio.vbase, (addr)) +#define mga_inl(addr) mga_readl(minfo->mmio.vbase, (addr)) +#define mga_outb(addr,val) mga_writeb(minfo->mmio.vbase, (addr), (val)) +#define mga_outw(addr,val) mga_writew(minfo->mmio.vbase, (addr), (val)) +#define mga_outl(addr,val) mga_writel(minfo->mmio.vbase, (addr), (val)) +#define mga_readr(port,idx) (mga_outb((port),(idx)), mga_inb((port)+1)) +#define mga_setr(addr,port,val) mga_outw(addr, ((val)<<8) | (port)) + +#define mga_fifo(n) do {} while ((mga_inl(M_FIFOSTATUS) & 0xFF) < (n)) + +#define WaitTillIdle() do {} while (mga_inl(M_STATUS) & 0x10000) + +/* code speedup */ +#ifdef CONFIG_FB_MATROX_MILLENIUM +#define isInterleave(x) (x->interleave) +#define isMillenium(x) (x->millenium) +#define isMilleniumII(x) (x->milleniumII) +#else +#define isInterleave(x) (0) +#define isMillenium(x) (0) +#define isMilleniumII(x) (0) +#endif + +#define matroxfb_DAC_lock() spin_lock(&minfo->lock.DAC) +#define matroxfb_DAC_unlock() spin_unlock(&minfo->lock.DAC) +#define matroxfb_DAC_lock_irqsave(flags) spin_lock_irqsave(&minfo->lock.DAC, flags) +#define matroxfb_DAC_unlock_irqrestore(flags) spin_unlock_irqrestore(&minfo->lock.DAC, flags) +extern void matroxfb_DAC_out(const struct matrox_fb_info *minfo, int reg, + int val); +extern int matroxfb_DAC_in(const struct matrox_fb_info *minfo, int reg); +extern void matroxfb_var2my(struct fb_var_screeninfo* fvsi, struct my_timming* mt); +extern int matroxfb_wait_for_sync(struct matrox_fb_info *minfo, u_int32_t crtc); +extern int matroxfb_enable_irq(struct matrox_fb_info *minfo, int reenable); + +#ifdef MATROXFB_USE_SPINLOCKS +#define CRITBEGIN spin_lock_irqsave(&minfo->lock.accel, critflags); +#define CRITEND spin_unlock_irqrestore(&minfo->lock.accel, critflags); +#define CRITFLAGS unsigned long critflags; +#else +#define CRITBEGIN +#define CRITEND +#define CRITFLAGS +#endif + +#endif /* __MATROXFB_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_crtc2.c b/drivers/video/fbdev/matrox/matroxfb_crtc2.c new file mode 100644 index 000000000000..02796a4317a9 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_crtc2.c @@ -0,0 +1,739 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + */ + +#include "matroxfb_maven.h" +#include "matroxfb_crtc2.h" +#include "matroxfb_misc.h" +#include "matroxfb_DAC1064.h" +#include <linux/matroxfb.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +/* **************************************************** */ + +static int mem = 8192; + +module_param(mem, int, 0); +MODULE_PARM_DESC(mem, "Memory size reserved for dualhead (default=8MB)"); + +/* **************************************************** */ + +static int matroxfb_dh_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info* info) { + u_int32_t col; +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + + if (regno >= 16) + return 1; + if (m2info->fbcon.var.grayscale) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + red = CNVT_TOHW(red, m2info->fbcon.var.red.length); + green = CNVT_TOHW(green, m2info->fbcon.var.green.length); + blue = CNVT_TOHW(blue, m2info->fbcon.var.blue.length); + transp = CNVT_TOHW(transp, m2info->fbcon.var.transp.length); + + col = (red << m2info->fbcon.var.red.offset) | + (green << m2info->fbcon.var.green.offset) | + (blue << m2info->fbcon.var.blue.offset) | + (transp << m2info->fbcon.var.transp.offset); + + switch (m2info->fbcon.var.bits_per_pixel) { + case 16: + m2info->cmap[regno] = col | (col << 16); + break; + case 32: + m2info->cmap[regno] = col; + break; + } + return 0; +#undef m2info +} + +static void matroxfb_dh_restore(struct matroxfb_dh_fb_info* m2info, + struct my_timming* mt, + int mode, + unsigned int pos) { + u_int32_t tmp; + u_int32_t datactl; + struct matrox_fb_info *minfo = m2info->primary_dev; + + switch (mode) { + case 15: + tmp = 0x00200000; + break; + case 16: + tmp = 0x00400000; + break; +/* case 32: */ + default: + tmp = 0x00800000; + break; + } + tmp |= 0x00000001; /* enable CRTC2 */ + datactl = 0; + if (minfo->outputs[1].src == MATROXFB_SRC_CRTC2) { + if (minfo->devflags.g450dac) { + tmp |= 0x00000006; /* source from secondary pixel PLL */ + /* no vidrst when in monitor mode */ + if (minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { + tmp |= 0xC0001000; /* Enable H/V vidrst */ + } + } else { + tmp |= 0x00000002; /* source from VDOCLK */ + tmp |= 0xC0000000; /* enable vvidrst & hvidrst */ + /* MGA TVO is our clock source */ + } + } else if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { + tmp |= 0x00000004; /* source from pixclock */ + /* PIXPLL is our clock source */ + } + if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { + tmp |= 0x00100000; /* connect CRTC2 to DAC */ + } + if (mt->interlaced) { + tmp |= 0x02000000; /* interlaced, second field is bigger, as G450 apparently ignores it */ + mt->VDisplay >>= 1; + mt->VSyncStart >>= 1; + mt->VSyncEnd >>= 1; + mt->VTotal >>= 1; + } + if ((mt->HTotal & 7) == 2) { + datactl |= 0x00000010; + mt->HTotal &= ~7; + } + tmp |= 0x10000000; /* 0x10000000 is VIDRST polarity */ + mga_outl(0x3C14, ((mt->HDisplay - 8) << 16) | (mt->HTotal - 8)); + mga_outl(0x3C18, ((mt->HSyncEnd - 8) << 16) | (mt->HSyncStart - 8)); + mga_outl(0x3C1C, ((mt->VDisplay - 1) << 16) | (mt->VTotal - 1)); + mga_outl(0x3C20, ((mt->VSyncEnd - 1) << 16) | (mt->VSyncStart - 1)); + mga_outl(0x3C24, ((mt->VSyncStart) << 16) | (mt->HSyncStart)); /* preload */ + { + u_int32_t linelen = m2info->fbcon.var.xres_virtual * (m2info->fbcon.var.bits_per_pixel >> 3); + if (tmp & 0x02000000) { + /* field #0 is smaller, so... */ + mga_outl(0x3C2C, pos); /* field #1 vmemory start */ + mga_outl(0x3C28, pos + linelen); /* field #0 vmemory start */ + linelen <<= 1; + m2info->interlaced = 1; + } else { + mga_outl(0x3C28, pos); /* vmemory start */ + m2info->interlaced = 0; + } + mga_outl(0x3C40, linelen); + } + mga_outl(0x3C4C, datactl); /* data control */ + if (tmp & 0x02000000) { + int i; + + mga_outl(0x3C10, tmp & ~0x02000000); + for (i = 0; i < 2; i++) { + unsigned int nl; + unsigned int lastl = 0; + + while ((nl = mga_inl(0x3C48) & 0xFFF) >= lastl) { + lastl = nl; + } + } + } + mga_outl(0x3C10, tmp); + minfo->hw.crtc2.ctl = tmp; + + tmp = mt->VDisplay << 16; /* line compare */ + if (mt->sync & FB_SYNC_HOR_HIGH_ACT) + tmp |= 0x00000100; + if (mt->sync & FB_SYNC_VERT_HIGH_ACT) + tmp |= 0x00000200; + mga_outl(0x3C44, tmp); +} + +static void matroxfb_dh_disable(struct matroxfb_dh_fb_info* m2info) { + struct matrox_fb_info *minfo = m2info->primary_dev; + + mga_outl(0x3C10, 0x00000004); /* disable CRTC2, CRTC1->DAC1, PLL as clock source */ + minfo->hw.crtc2.ctl = 0x00000004; +} + +static void matroxfb_dh_pan_var(struct matroxfb_dh_fb_info* m2info, + struct fb_var_screeninfo* var) { + unsigned int pos; + unsigned int linelen; + unsigned int pixelsize; + struct matrox_fb_info *minfo = m2info->primary_dev; + + m2info->fbcon.var.xoffset = var->xoffset; + m2info->fbcon.var.yoffset = var->yoffset; + pixelsize = m2info->fbcon.var.bits_per_pixel >> 3; + linelen = m2info->fbcon.var.xres_virtual * pixelsize; + pos = m2info->fbcon.var.yoffset * linelen + m2info->fbcon.var.xoffset * pixelsize; + pos += m2info->video.offbase; + if (m2info->interlaced) { + mga_outl(0x3C2C, pos); + mga_outl(0x3C28, pos + linelen); + } else { + mga_outl(0x3C28, pos); + } +} + +static int matroxfb_dh_decode_var(struct matroxfb_dh_fb_info* m2info, + struct fb_var_screeninfo* var, + int *visual, + int *video_cmap_len, + int *mode) { + unsigned int mask; + unsigned int memlen; + unsigned int vramlen; + + switch (var->bits_per_pixel) { + case 16: mask = 0x1F; + break; + case 32: mask = 0x0F; + break; + default: return -EINVAL; + } + vramlen = m2info->video.len_usable; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + var->xres_virtual = (var->xres_virtual + mask) & ~mask; + if (var->yres_virtual > 32767) + return -EINVAL; + memlen = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel >> 3); + if (memlen > vramlen) + return -EINVAL; + if (var->xoffset + var->xres > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yoffset + var->yres > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + var->xres &= ~7; + var->left_margin &= ~7; + var->right_margin &= ~7; + var->hsync_len &= ~7; + + *mode = var->bits_per_pixel; + if (var->bits_per_pixel == 16) { + if (var->green.length == 5) { + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + *mode = 15; + } else { + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + } else { + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + } + *visual = FB_VISUAL_TRUECOLOR; + *video_cmap_len = 16; + return 0; +} + +static int matroxfb_dh_open(struct fb_info* info, int user) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + struct matrox_fb_info *minfo = m2info->primary_dev; + + if (minfo) { + int err; + + if (minfo->dead) { + return -ENXIO; + } + err = minfo->fbops.fb_open(&minfo->fbcon, user); + if (err) { + return err; + } + } + return 0; +#undef m2info +} + +static int matroxfb_dh_release(struct fb_info* info, int user) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + int err = 0; + struct matrox_fb_info *minfo = m2info->primary_dev; + + if (minfo) { + err = minfo->fbops.fb_release(&minfo->fbcon, user); + } + return err; +#undef m2info +} + +/* + * This function is called before the register_framebuffer so + * no locking is needed. + */ +static void matroxfb_dh_init_fix(struct matroxfb_dh_fb_info *m2info) +{ + struct fb_fix_screeninfo *fix = &m2info->fbcon.fix; + + strcpy(fix->id, "MATROX DH"); + + fix->smem_start = m2info->video.base; + fix->smem_len = m2info->video.len_usable; + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->xpanstep = 8; /* TBD */ + fix->mmio_start = m2info->mmio.base; + fix->mmio_len = m2info->mmio.len; + fix->accel = 0; /* no accel... */ +} + +static int matroxfb_dh_check_var(struct fb_var_screeninfo* var, struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + int visual; + int cmap_len; + int mode; + + return matroxfb_dh_decode_var(m2info, var, &visual, &cmap_len, &mode); +#undef m2info +} + +static int matroxfb_dh_set_par(struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + int visual; + int cmap_len; + int mode; + int err; + struct fb_var_screeninfo* var = &info->var; + struct matrox_fb_info *minfo = m2info->primary_dev; + + if ((err = matroxfb_dh_decode_var(m2info, var, &visual, &cmap_len, &mode)) != 0) + return err; + /* cmap */ + { + m2info->fbcon.screen_base = vaddr_va(m2info->video.vbase); + m2info->fbcon.fix.visual = visual; + m2info->fbcon.fix.type = FB_TYPE_PACKED_PIXELS; + m2info->fbcon.fix.type_aux = 0; + m2info->fbcon.fix.line_length = (var->xres_virtual * var->bits_per_pixel) >> 3; + } + { + struct my_timming mt; + unsigned int pos; + int out; + int cnt; + + matroxfb_var2my(&m2info->fbcon.var, &mt); + mt.crtc = MATROXFB_SRC_CRTC2; + /* CRTC2 delay */ + mt.delay = 34; + + pos = (m2info->fbcon.var.yoffset * m2info->fbcon.var.xres_virtual + m2info->fbcon.var.xoffset) * m2info->fbcon.var.bits_per_pixel >> 3; + pos += m2info->video.offbase; + cnt = 0; + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { + cnt++; + if (minfo->outputs[out].output->compute) { + minfo->outputs[out].output->compute(minfo->outputs[out].data, &mt); + } + } + } + minfo->crtc2.pixclock = mt.pixclock; + minfo->crtc2.mnp = mt.mnp; + up_read(&minfo->altout.lock); + if (cnt) { + matroxfb_dh_restore(m2info, &mt, mode, pos); + } else { + matroxfb_dh_disable(m2info); + } + DAC1064_global_init(minfo); + DAC1064_global_restore(minfo); + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && + minfo->outputs[out].output->program) { + minfo->outputs[out].output->program(minfo->outputs[out].data); + } + } + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && + minfo->outputs[out].output->start) { + minfo->outputs[out].output->start(minfo->outputs[out].data); + } + } + up_read(&minfo->altout.lock); + } + m2info->initialized = 1; + return 0; +#undef m2info +} + +static int matroxfb_dh_pan_display(struct fb_var_screeninfo* var, struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + matroxfb_dh_pan_var(m2info, var); + return 0; +#undef m2info +} + +static int matroxfb_dh_get_vblank(const struct matroxfb_dh_fb_info* m2info, struct fb_vblank* vblank) { + struct matrox_fb_info *minfo = m2info->primary_dev; + + matroxfb_enable_irq(minfo, 0); + memset(vblank, 0, sizeof(*vblank)); + vblank->flags = FB_VBLANK_HAVE_VCOUNT | FB_VBLANK_HAVE_VBLANK; + /* mask out reserved bits + field number (odd/even) */ + vblank->vcount = mga_inl(0x3C48) & 0x000007FF; + /* compatibility stuff */ + if (vblank->vcount >= m2info->fbcon.var.yres) + vblank->flags |= FB_VBLANK_VBLANKING; + if (test_bit(0, &minfo->irq_flags)) { + vblank->flags |= FB_VBLANK_HAVE_COUNT; + /* Only one writer, aligned int value... + it should work without lock and without atomic_t */ + vblank->count = minfo->crtc2.vsync.cnt; + } + return 0; +} + +static int matroxfb_dh_ioctl(struct fb_info *info, + unsigned int cmd, + unsigned long arg) +{ +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + struct matrox_fb_info *minfo = m2info->primary_dev; + + DBG(__func__) + + switch (cmd) { + case FBIOGET_VBLANK: + { + struct fb_vblank vblank; + int err; + + err = matroxfb_dh_get_vblank(m2info, &vblank); + if (err) + return err; + if (copy_to_user((void __user *)arg, &vblank, sizeof(vblank))) + return -EFAULT; + return 0; + } + case FBIO_WAITFORVSYNC: + { + u_int32_t crt; + + if (get_user(crt, (u_int32_t __user *)arg)) + return -EFAULT; + + if (crt != 0) + return -ENODEV; + return matroxfb_wait_for_sync(minfo, 1); + } + case MATROXFB_SET_OUTPUT_MODE: + case MATROXFB_GET_OUTPUT_MODE: + case MATROXFB_GET_ALL_OUTPUTS: + { + return minfo->fbcon.fbops->fb_ioctl(&minfo->fbcon, cmd, arg); + } + case MATROXFB_SET_OUTPUT_CONNECTION: + { + u_int32_t tmp; + int out; + int changes; + + if (get_user(tmp, (u_int32_t __user *)arg)) + return -EFAULT; + for (out = 0; out < 32; out++) { + if (tmp & (1 << out)) { + if (out >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + if (!minfo->outputs[out].output) + return -ENXIO; + switch (minfo->outputs[out].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC2: + break; + default: + return -EBUSY; + } + } + } + if (minfo->devflags.panellink) { + if (tmp & MATROXFB_OUTPUT_CONN_DFP) + return -EINVAL; + if ((minfo->outputs[2].src == MATROXFB_SRC_CRTC1) && tmp) + return -EBUSY; + } + changes = 0; + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (tmp & (1 << out)) { + if (minfo->outputs[out].src != MATROXFB_SRC_CRTC2) { + changes = 1; + minfo->outputs[out].src = MATROXFB_SRC_CRTC2; + } + } else if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { + changes = 1; + minfo->outputs[out].src = MATROXFB_SRC_NONE; + } + } + if (!changes) + return 0; + matroxfb_dh_set_par(info); + return 0; + } + case MATROXFB_GET_OUTPUT_CONNECTION: + { + u_int32_t conn = 0; + int out; + + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { + conn |= 1 << out; + } + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case MATROXFB_GET_AVAILABLE_OUTPUTS: + { + u_int32_t tmp = 0; + int out; + + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].output) { + switch (minfo->outputs[out].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC2: + tmp |= 1 << out; + break; + } + } + } + if (minfo->devflags.panellink) { + tmp &= ~MATROXFB_OUTPUT_CONN_DFP; + if (minfo->outputs[2].src == MATROXFB_SRC_CRTC1) { + tmp = 0; + } + } + if (put_user(tmp, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + } + return -ENOTTY; +#undef m2info +} + +static int matroxfb_dh_blank(int blank, struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + switch (blank) { + case 1: + case 2: + case 3: + case 4: + default:; + } + /* do something... */ + return 0; +#undef m2info +} + +static struct fb_ops matroxfb_dh_ops = { + .owner = THIS_MODULE, + .fb_open = matroxfb_dh_open, + .fb_release = matroxfb_dh_release, + .fb_check_var = matroxfb_dh_check_var, + .fb_set_par = matroxfb_dh_set_par, + .fb_setcolreg = matroxfb_dh_setcolreg, + .fb_pan_display =matroxfb_dh_pan_display, + .fb_blank = matroxfb_dh_blank, + .fb_ioctl = matroxfb_dh_ioctl, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_var_screeninfo matroxfb_dh_defined = { + 640,480,640,480,/* W,H, virtual W,H */ + 0,0, /* offset */ + 32, /* depth */ + 0, /* gray */ + {0,0,0}, /* R */ + {0,0,0}, /* G */ + {0,0,0}, /* B */ + {0,0,0}, /* alpha */ + 0, /* nonstd */ + FB_ACTIVATE_NOW, + -1,-1, /* display size */ + 0, /* accel flags */ + 39721L,48L,16L,33L,10L, + 96L,2,0, /* no sync info */ + FB_VMODE_NONINTERLACED, +}; + +static int matroxfb_dh_regit(const struct matrox_fb_info *minfo, + struct matroxfb_dh_fb_info *m2info) +{ +#define minfo (m2info->primary_dev) + void* oldcrtc2; + + m2info->fbcon.fbops = &matroxfb_dh_ops; + m2info->fbcon.flags = FBINFO_FLAG_DEFAULT; + m2info->fbcon.flags |= FBINFO_HWACCEL_XPAN | + FBINFO_HWACCEL_YPAN; + m2info->fbcon.pseudo_palette = m2info->cmap; + fb_alloc_cmap(&m2info->fbcon.cmap, 256, 1); + + if (mem < 64) + mem *= 1024; + if (mem < 64*1024) + mem *= 1024; + mem &= ~0x00000FFF; /* PAGE_MASK? */ + if (minfo->video.len_usable + mem <= minfo->video.len) + m2info->video.offbase = minfo->video.len - mem; + else if (minfo->video.len < mem) { + return -ENOMEM; + } else { /* check yres on first head... */ + m2info->video.borrowed = mem; + minfo->video.len_usable -= mem; + m2info->video.offbase = minfo->video.len_usable; + } + m2info->video.base = minfo->video.base + m2info->video.offbase; + m2info->video.len = m2info->video.len_usable = m2info->video.len_maximum = mem; + m2info->video.vbase.vaddr = vaddr_va(minfo->video.vbase) + m2info->video.offbase; + m2info->mmio.base = minfo->mmio.base; + m2info->mmio.vbase = minfo->mmio.vbase; + m2info->mmio.len = minfo->mmio.len; + + matroxfb_dh_init_fix(m2info); + if (register_framebuffer(&m2info->fbcon)) { + return -ENXIO; + } + if (!m2info->initialized) + fb_set_var(&m2info->fbcon, &matroxfb_dh_defined); + down_write(&minfo->crtc2.lock); + oldcrtc2 = minfo->crtc2.info; + minfo->crtc2.info = m2info; + up_write(&minfo->crtc2.lock); + if (oldcrtc2) { + printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 already present: %p\n", + oldcrtc2); + } + return 0; +#undef minfo +} + +/* ************************** */ + +static int matroxfb_dh_registerfb(struct matroxfb_dh_fb_info* m2info) { +#define minfo (m2info->primary_dev) + if (matroxfb_dh_regit(minfo, m2info)) { + printk(KERN_ERR "matroxfb_crtc2: secondary head failed to register\n"); + return -1; + } + printk(KERN_INFO "matroxfb_crtc2: secondary head of fb%u was registered as fb%u\n", + minfo->fbcon.node, m2info->fbcon.node); + m2info->fbcon_registered = 1; + return 0; +#undef minfo +} + +static void matroxfb_dh_deregisterfb(struct matroxfb_dh_fb_info* m2info) { +#define minfo (m2info->primary_dev) + if (m2info->fbcon_registered) { + int id; + struct matroxfb_dh_fb_info* crtc2; + + down_write(&minfo->crtc2.lock); + crtc2 = minfo->crtc2.info; + if (crtc2 == m2info) + minfo->crtc2.info = NULL; + up_write(&minfo->crtc2.lock); + if (crtc2 != m2info) { + printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 mismatch at unload: %p != %p\n", + crtc2, m2info); + printk(KERN_ERR "matroxfb_crtc2: Expect kernel crash after module unload.\n"); + return; + } + id = m2info->fbcon.node; + unregister_framebuffer(&m2info->fbcon); + /* return memory back to primary head */ + minfo->video.len_usable += m2info->video.borrowed; + printk(KERN_INFO "matroxfb_crtc2: fb%u unregistered\n", id); + m2info->fbcon_registered = 0; + } +#undef minfo +} + +static void* matroxfb_crtc2_probe(struct matrox_fb_info* minfo) { + struct matroxfb_dh_fb_info* m2info; + + /* hardware is CRTC2 incapable... */ + if (!minfo->devflags.crtc2) + return NULL; + m2info = kzalloc(sizeof(*m2info), GFP_KERNEL); + if (!m2info) { + printk(KERN_ERR "matroxfb_crtc2: Not enough memory for CRTC2 control structs\n"); + return NULL; + } + m2info->primary_dev = minfo; + if (matroxfb_dh_registerfb(m2info)) { + kfree(m2info); + printk(KERN_ERR "matroxfb_crtc2: CRTC2 framebuffer failed to register\n"); + return NULL; + } + return m2info; +} + +static void matroxfb_crtc2_remove(struct matrox_fb_info* minfo, void* crtc2) { + matroxfb_dh_deregisterfb(crtc2); + kfree(crtc2); +} + +static struct matroxfb_driver crtc2 = { + .name = "Matrox G400 CRTC2", + .probe = matroxfb_crtc2_probe, + .remove = matroxfb_crtc2_remove }; + +static int matroxfb_crtc2_init(void) { + if (fb_get_options("matrox_crtc2fb", NULL)) + return -ENODEV; + + matroxfb_register_driver(&crtc2); + return 0; +} + +static void matroxfb_crtc2_exit(void) { + matroxfb_unregister_driver(&crtc2); +} + +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G400 CRTC2 driver"); +MODULE_LICENSE("GPL"); +module_init(matroxfb_crtc2_init); +module_exit(matroxfb_crtc2_exit); +/* we do not have __setup() yet */ diff --git a/drivers/video/fbdev/matrox/matroxfb_crtc2.h b/drivers/video/fbdev/matrox/matroxfb_crtc2.h new file mode 100644 index 000000000000..1005582e843e --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_crtc2.h @@ -0,0 +1,34 @@ +#ifndef __MATROXFB_CRTC2_H__ +#define __MATROXFB_CRTC2_H__ + +#include <linux/ioctl.h> +#include "matroxfb_base.h" + +struct matroxfb_dh_fb_info { + struct fb_info fbcon; + int fbcon_registered; + int initialized; + + struct matrox_fb_info* primary_dev; + + struct { + unsigned long base; /* physical */ + vaddr_t vbase; /* virtual */ + unsigned int len; + unsigned int len_usable; + unsigned int len_maximum; + unsigned int offbase; + unsigned int borrowed; + } video; + struct { + unsigned long base; + vaddr_t vbase; + unsigned int len; + } mmio; + + unsigned int interlaced:1; + + u_int32_t cmap[16]; +}; + +#endif /* __MATROXFB_CRTC2_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_g450.c b/drivers/video/fbdev/matrox/matroxfb_g450.c new file mode 100644 index 000000000000..cff0546ea6fd --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_g450.c @@ -0,0 +1,640 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * See matroxfb_base.c for contributors. + * + */ + +#include "matroxfb_base.h" +#include "matroxfb_misc.h" +#include "matroxfb_DAC1064.h" +#include "g450_pll.h" +#include <linux/matroxfb.h> +#include <asm/div64.h> + +#include "matroxfb_g450.h" + +/* Definition of the various controls */ +struct mctl { + struct v4l2_queryctrl desc; + size_t control; +}; + +#define BLMIN 0xF3 +#define WLMAX 0x3FF + +static const struct mctl g450_controls[] = +{ { { V4L2_CID_BRIGHTNESS, V4L2_CTRL_TYPE_INTEGER, + "brightness", + 0, WLMAX-BLMIN, 1, 370-BLMIN, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.brightness) }, + { { V4L2_CID_CONTRAST, V4L2_CTRL_TYPE_INTEGER, + "contrast", + 0, 1023, 1, 127, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.contrast) }, + { { V4L2_CID_SATURATION, V4L2_CTRL_TYPE_INTEGER, + "saturation", + 0, 255, 1, 165, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.saturation) }, + { { V4L2_CID_HUE, V4L2_CTRL_TYPE_INTEGER, + "hue", + 0, 255, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.hue) }, + { { MATROXFB_CID_TESTOUT, V4L2_CTRL_TYPE_BOOLEAN, + "test output", + 0, 1, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.testout) }, +}; + +#define G450CTRLS ARRAY_SIZE(g450_controls) + +/* Return: positive number: id found + -EINVAL: id not found, return failure + -ENOENT: id not found, create fake disabled control */ +static int get_ctrl_id(__u32 v4l2_id) { + int i; + + for (i = 0; i < G450CTRLS; i++) { + if (v4l2_id < g450_controls[i].desc.id) { + if (g450_controls[i].desc.id == 0x08000000) { + return -EINVAL; + } + return -ENOENT; + } + if (v4l2_id == g450_controls[i].desc.id) { + return i; + } + } + return -EINVAL; +} + +static inline int *get_ctrl_ptr(struct matrox_fb_info *minfo, unsigned int idx) +{ + return (int*)((char*)minfo + g450_controls[idx].control); +} + +static void tvo_fill_defaults(struct matrox_fb_info *minfo) +{ + unsigned int i; + + for (i = 0; i < G450CTRLS; i++) { + *get_ctrl_ptr(minfo, i) = g450_controls[i].desc.default_value; + } +} + +static int cve2_get_reg(struct matrox_fb_info *minfo, int reg) +{ + unsigned long flags; + int val; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, 0x87, reg); + val = matroxfb_DAC_in(minfo, 0x88); + matroxfb_DAC_unlock_irqrestore(flags); + return val; +} + +static void cve2_set_reg(struct matrox_fb_info *minfo, int reg, int val) +{ + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, 0x87, reg); + matroxfb_DAC_out(minfo, 0x88, val); + matroxfb_DAC_unlock_irqrestore(flags); +} + +static void cve2_set_reg10(struct matrox_fb_info *minfo, int reg, int val) +{ + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, 0x87, reg); + matroxfb_DAC_out(minfo, 0x88, val >> 2); + matroxfb_DAC_out(minfo, 0x87, reg + 1); + matroxfb_DAC_out(minfo, 0x88, val & 3); + matroxfb_DAC_unlock_irqrestore(flags); +} + +static void g450_compute_bwlevel(const struct matrox_fb_info *minfo, int *bl, + int *wl) +{ + const int b = minfo->altout.tvo_params.brightness + BLMIN; + const int c = minfo->altout.tvo_params.contrast; + + *bl = max(b - c, BLMIN); + *wl = min(b + c, WLMAX); +} + +static int g450_query_ctrl(void* md, struct v4l2_queryctrl *p) { + int i; + + i = get_ctrl_id(p->id); + if (i >= 0) { + *p = g450_controls[i].desc; + return 0; + } + if (i == -ENOENT) { + static const struct v4l2_queryctrl disctrl = + { .flags = V4L2_CTRL_FLAG_DISABLED }; + + i = p->id; + *p = disctrl; + p->id = i; + sprintf(p->name, "Ctrl #%08X", i); + return 0; + } + return -EINVAL; +} + +static int g450_set_ctrl(void* md, struct v4l2_control *p) { + int i; + struct matrox_fb_info *minfo = md; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + + /* + * Check if changed. + */ + if (p->value == *get_ctrl_ptr(minfo, i)) return 0; + + /* + * Check limits. + */ + if (p->value > g450_controls[i].desc.maximum) return -EINVAL; + if (p->value < g450_controls[i].desc.minimum) return -EINVAL; + + /* + * Store new value. + */ + *get_ctrl_ptr(minfo, i) = p->value; + + switch (p->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + { + int blacklevel, whitelevel; + g450_compute_bwlevel(minfo, &blacklevel, &whitelevel); + cve2_set_reg10(minfo, 0x0e, blacklevel); + cve2_set_reg10(minfo, 0x1e, whitelevel); + } + break; + case V4L2_CID_SATURATION: + cve2_set_reg(minfo, 0x20, p->value); + cve2_set_reg(minfo, 0x22, p->value); + break; + case V4L2_CID_HUE: + cve2_set_reg(minfo, 0x25, p->value); + break; + case MATROXFB_CID_TESTOUT: + { + unsigned char val = cve2_get_reg(minfo, 0x05); + if (p->value) val |= 0x02; + else val &= ~0x02; + cve2_set_reg(minfo, 0x05, val); + } + break; + } + + + return 0; +} + +static int g450_get_ctrl(void* md, struct v4l2_control *p) { + int i; + struct matrox_fb_info *minfo = md; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + p->value = *get_ctrl_ptr(minfo, i); + return 0; +} + +struct output_desc { + unsigned int h_vis; + unsigned int h_f_porch; + unsigned int h_sync; + unsigned int h_b_porch; + unsigned long long int chromasc; + unsigned int burst; + unsigned int v_total; +}; + +static void computeRegs(struct matrox_fb_info *minfo, struct mavenregs *r, + struct my_timming *mt, const struct output_desc *outd) +{ + u_int32_t chromasc; + u_int32_t hlen; + u_int32_t hsl; + u_int32_t hbp; + u_int32_t hfp; + u_int32_t hvis; + unsigned int pixclock; + unsigned long long piic; + int mnp; + int over; + + r->regs[0x80] = 0x03; /* | 0x40 for SCART */ + + hvis = ((mt->HDisplay << 1) + 3) & ~3; + + if (hvis >= 2048) { + hvis = 2044; + } + + piic = 1000000000ULL * hvis; + do_div(piic, outd->h_vis); + + dprintk(KERN_DEBUG "Want %u kHz pixclock\n", (unsigned int)piic); + + mnp = matroxfb_g450_setclk(minfo, piic, M_VIDEO_PLL); + + mt->mnp = mnp; + mt->pixclock = g450_mnp2f(minfo, mnp); + + dprintk(KERN_DEBUG "MNP=%08X\n", mnp); + + pixclock = 1000000000U / mt->pixclock; + + dprintk(KERN_DEBUG "Got %u ps pixclock\n", pixclock); + + piic = outd->chromasc; + do_div(piic, mt->pixclock); + chromasc = piic; + + dprintk(KERN_DEBUG "Chroma is %08X\n", chromasc); + + r->regs[0] = piic >> 24; + r->regs[1] = piic >> 16; + r->regs[2] = piic >> 8; + r->regs[3] = piic >> 0; + hbp = (((outd->h_b_porch + pixclock) / pixclock)) & ~1; + hfp = (((outd->h_f_porch + pixclock) / pixclock)) & ~1; + hsl = (((outd->h_sync + pixclock) / pixclock)) & ~1; + hlen = hvis + hfp + hsl + hbp; + over = hlen & 0x0F; + + dprintk(KERN_DEBUG "WL: vis=%u, hf=%u, hs=%u, hb=%u, total=%u\n", hvis, hfp, hsl, hbp, hlen); + + if (over) { + hfp -= over; + hlen -= over; + if (over <= 2) { + } else if (over < 10) { + hfp += 4; + hlen += 4; + } else { + hfp += 16; + hlen += 16; + } + } + + /* maybe cve2 has requirement 800 < hlen < 1184 */ + r->regs[0x08] = hsl; + r->regs[0x09] = (outd->burst + pixclock - 1) / pixclock; /* burst length */ + r->regs[0x0A] = hbp; + r->regs[0x2C] = hfp; + r->regs[0x31] = hvis / 8; + r->regs[0x32] = hvis & 7; + + dprintk(KERN_DEBUG "PG: vis=%04X, hf=%02X, hs=%02X, hb=%02X, total=%04X\n", hvis, hfp, hsl, hbp, hlen); + + r->regs[0x84] = 1; /* x sync point */ + r->regs[0x85] = 0; + hvis = hvis >> 1; + hlen = hlen >> 1; + + dprintk(KERN_DEBUG "hlen=%u hvis=%u\n", hlen, hvis); + + mt->interlaced = 1; + + mt->HDisplay = hvis & ~7; + mt->HSyncStart = mt->HDisplay + 8; + mt->HSyncEnd = (hlen & ~7) - 8; + mt->HTotal = hlen; + + { + int upper; + unsigned int vtotal; + unsigned int vsyncend; + unsigned int vdisplay; + + vtotal = mt->VTotal; + vsyncend = mt->VSyncEnd; + vdisplay = mt->VDisplay; + if (vtotal < outd->v_total) { + unsigned int yovr = outd->v_total - vtotal; + + vsyncend += yovr >> 1; + } else if (vtotal > outd->v_total) { + vdisplay = outd->v_total - 4; + vsyncend = outd->v_total; + } + upper = (outd->v_total - vsyncend) >> 1; /* in field lines */ + r->regs[0x17] = outd->v_total / 4; + r->regs[0x18] = outd->v_total & 3; + r->regs[0x33] = upper - 1; /* upper blanking */ + r->regs[0x82] = upper; /* y sync point */ + r->regs[0x83] = upper >> 8; + + mt->VDisplay = vdisplay; + mt->VSyncStart = outd->v_total - 2; + mt->VSyncEnd = outd->v_total; + mt->VTotal = outd->v_total; + } +} + +static void cve2_init_TVdata(int norm, struct mavenregs* data, const struct output_desc** outd) { + static const struct output_desc paloutd = { + .h_vis = 52148148, // ps + .h_f_porch = 1407407, // ps + .h_sync = 4666667, // ps + .h_b_porch = 5777778, // ps + .chromasc = 19042247534182ULL, // 4433618.750 Hz + .burst = 2518518, // ps + .v_total = 625, + }; + static const struct output_desc ntscoutd = { + .h_vis = 52888889, // ps + .h_f_porch = 1333333, // ps + .h_sync = 4666667, // ps + .h_b_porch = 4666667, // ps + .chromasc = 15374030659475ULL, // 3579545.454 Hz + .burst = 2418418, // ps + .v_total = 525, // lines + }; + + static const struct mavenregs palregs = { { + 0x2A, 0x09, 0x8A, 0xCB, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* test */ + 0xF9, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x44, /* 09 */ + 0x9C, /* 0A */ + 0x2E, /* 0B */ + 0x21, /* 0C */ + 0x00, /* ? not written */ +// 0x3F, 0x03, /* 0E-0F */ + 0x3C, 0x03, + 0x3C, 0x03, /* 10-11 */ + 0x1A, /* 12 */ + 0x2A, /* 13 */ + 0x1C, 0x3D, 0x14, /* 14-16 */ + 0x9C, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0xFE, /* 1A */ + 0x7E, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ +// 0x89, 0x03, /* 1E-1F */ + 0xAD, 0x03, +// 0x72, /* 20 */ + 0xA5, + 0x07, /* 21 */ +// 0x72, /* 22 */ + 0xA5, + 0x00, /* 23 */ + 0x00, /* 24 */ + 0x00, /* 25 */ + 0x08, /* 26 */ + 0x04, /* 27 */ + 0x00, /* 28 */ + 0x1A, /* 29 */ + 0x55, 0x01, /* 2A-2B */ + 0x26, /* 2C */ + 0x07, 0x7E, /* 2D-2E */ + 0x02, 0x54, /* 2F-30 */ + 0xB0, 0x00, /* 31-32 */ + 0x14, /* 33 */ + 0x49, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x22, /* 39 */ + 0x02, /* 3A */ + 0x22, /* 3B */ + 0x3F, 0x03, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* 3F not written */ + } }; + static struct mavenregs ntscregs = { { + 0x21, 0xF0, 0x7C, 0x1F, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* test */ + 0xF9, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x43, /* 09 */ + 0x7E, /* 0A */ + 0x3D, /* 0B */ + 0x00, /* 0C */ + 0x00, /* ? not written */ + 0x41, 0x00, /* 0E-0F */ + 0x3C, 0x00, /* 10-11 */ + 0x17, /* 12 */ + 0x21, /* 13 */ + 0x1B, 0x1B, 0x24, /* 14-16 */ + 0x83, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0x0F, /* 1A */ + 0x0F, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ + //0x89, 0x02, /* 1E-1F */ + 0xC0, 0x02, /* 1E-1F */ + //0x5F, /* 20 */ + 0x9C, /* 20 */ + 0x04, /* 21 */ + //0x5F, /* 22 */ + 0x9C, /* 22 */ + 0x01, /* 23 */ + 0x02, /* 24 */ + 0x00, /* 25 */ + 0x0A, /* 26 */ + 0x05, /* 27 */ + 0x00, /* 28 */ + 0x10, /* 29 */ + 0xFF, 0x03, /* 2A-2B */ + 0x24, /* 2C */ + 0x0F, 0x78, /* 2D-2E */ + 0x00, 0x00, /* 2F-30 */ + 0xB2, 0x04, /* 31-32 */ + 0x14, /* 33 */ + 0x02, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x15, /* 39 */ + 0x05, /* 3A */ + 0x3B, /* 3B */ + 0x3C, 0x00, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* never written */ + } }; + + if (norm == MATROXFB_OUTPUT_MODE_PAL) { + *data = palregs; + *outd = &paloutd; + } else { + *data = ntscregs; + *outd = &ntscoutd; + } + return; +} + +#define LR(x) cve2_set_reg(minfo, (x), m->regs[(x)]) +static void cve2_init_TV(struct matrox_fb_info *minfo, + const struct mavenregs *m) +{ + int i; + + LR(0x80); + LR(0x82); LR(0x83); + LR(0x84); LR(0x85); + + cve2_set_reg(minfo, 0x3E, 0x01); + + for (i = 0; i < 0x3E; i++) { + LR(i); + } + cve2_set_reg(minfo, 0x3E, 0x00); +} + +static int matroxfb_g450_compute(void* md, struct my_timming* mt) { + struct matrox_fb_info *minfo = md; + + dprintk(KERN_DEBUG "Computing, mode=%u\n", minfo->outputs[1].mode); + + if (mt->crtc == MATROXFB_SRC_CRTC2 && + minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { + const struct output_desc* outd; + + cve2_init_TVdata(minfo->outputs[1].mode, &minfo->hw.maven, &outd); + { + int blacklevel, whitelevel; + g450_compute_bwlevel(minfo, &blacklevel, &whitelevel); + minfo->hw.maven.regs[0x0E] = blacklevel >> 2; + minfo->hw.maven.regs[0x0F] = blacklevel & 3; + minfo->hw.maven.regs[0x1E] = whitelevel >> 2; + minfo->hw.maven.regs[0x1F] = whitelevel & 3; + + minfo->hw.maven.regs[0x20] = + minfo->hw.maven.regs[0x22] = minfo->altout.tvo_params.saturation; + + minfo->hw.maven.regs[0x25] = minfo->altout.tvo_params.hue; + + if (minfo->altout.tvo_params.testout) { + minfo->hw.maven.regs[0x05] |= 0x02; + } + } + computeRegs(minfo, &minfo->hw.maven, mt, outd); + } else if (mt->mnp < 0) { + /* We must program clocks before CRTC2, otherwise interlaced mode + startup may fail */ + mt->mnp = matroxfb_g450_setclk(minfo, mt->pixclock, (mt->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL); + mt->pixclock = g450_mnp2f(minfo, mt->mnp); + } + dprintk(KERN_DEBUG "Pixclock = %u\n", mt->pixclock); + return 0; +} + +static int matroxfb_g450_program(void* md) { + struct matrox_fb_info *minfo = md; + + if (minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { + cve2_init_TV(minfo, &minfo->hw.maven); + } + return 0; +} + +static int matroxfb_g450_verify_mode(void* md, u_int32_t arg) { + switch (arg) { + case MATROXFB_OUTPUT_MODE_PAL: + case MATROXFB_OUTPUT_MODE_NTSC: + case MATROXFB_OUTPUT_MODE_MONITOR: + return 0; + } + return -EINVAL; +} + +static int g450_dvi_compute(void* md, struct my_timming* mt) { + struct matrox_fb_info *minfo = md; + + if (mt->mnp < 0) { + mt->mnp = matroxfb_g450_setclk(minfo, mt->pixclock, (mt->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL); + mt->pixclock = g450_mnp2f(minfo, mt->mnp); + } + return 0; +} + +static struct matrox_altout matroxfb_g450_altout = { + .name = "Secondary output", + .compute = matroxfb_g450_compute, + .program = matroxfb_g450_program, + .verifymode = matroxfb_g450_verify_mode, + .getqueryctrl = g450_query_ctrl, + .getctrl = g450_get_ctrl, + .setctrl = g450_set_ctrl, +}; + +static struct matrox_altout matroxfb_g450_dvi = { + .name = "DVI output", + .compute = g450_dvi_compute, +}; + +void matroxfb_g450_connect(struct matrox_fb_info *minfo) +{ + if (minfo->devflags.g450dac) { + down_write(&minfo->altout.lock); + tvo_fill_defaults(minfo); + minfo->outputs[1].src = minfo->outputs[1].default_src; + minfo->outputs[1].data = minfo; + minfo->outputs[1].output = &matroxfb_g450_altout; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + minfo->outputs[2].src = minfo->outputs[2].default_src; + minfo->outputs[2].data = minfo; + minfo->outputs[2].output = &matroxfb_g450_dvi; + minfo->outputs[2].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + } +} + +void matroxfb_g450_shutdown(struct matrox_fb_info *minfo) +{ + if (minfo->devflags.g450dac) { + down_write(&minfo->altout.lock); + minfo->outputs[1].src = MATROXFB_SRC_NONE; + minfo->outputs[1].output = NULL; + minfo->outputs[1].data = NULL; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + minfo->outputs[2].src = MATROXFB_SRC_NONE; + minfo->outputs[2].output = NULL; + minfo->outputs[2].data = NULL; + minfo->outputs[2].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + } +} + +EXPORT_SYMBOL(matroxfb_g450_connect); +EXPORT_SYMBOL(matroxfb_g450_shutdown); + +MODULE_AUTHOR("(c) 2000-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G450/G550 output driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_g450.h b/drivers/video/fbdev/matrox/matroxfb_g450.h new file mode 100644 index 000000000000..3a3e654444b8 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_g450.h @@ -0,0 +1,14 @@ +#ifndef __MATROXFB_G450_H__ +#define __MATROXFB_G450_H__ + +#include "matroxfb_base.h" + +#ifdef CONFIG_FB_MATROX_G +void matroxfb_g450_connect(struct matrox_fb_info *minfo); +void matroxfb_g450_shutdown(struct matrox_fb_info *minfo); +#else +static inline void matroxfb_g450_connect(struct matrox_fb_info *minfo) { }; +static inline void matroxfb_g450_shutdown(struct matrox_fb_info *minfo) { }; +#endif + +#endif /* __MATROXFB_G450_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_maven.c b/drivers/video/fbdev/matrox/matroxfb_maven.c new file mode 100644 index 000000000000..ee41a0f276b2 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_maven.c @@ -0,0 +1,1301 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * See matroxfb_base.c for contributors. + * + */ + +#include "matroxfb_maven.h" +#include "matroxfb_misc.h" +#include "matroxfb_DAC1064.h" +#include <linux/i2c.h> +#include <linux/matroxfb.h> +#include <linux/slab.h> +#include <asm/div64.h> + +#define MGATVO_B 1 +#define MGATVO_C 2 + +static const struct maven_gamma { + unsigned char reg83; + unsigned char reg84; + unsigned char reg85; + unsigned char reg86; + unsigned char reg87; + unsigned char reg88; + unsigned char reg89; + unsigned char reg8a; + unsigned char reg8b; +} maven_gamma[] = { + { 131, 57, 223, 15, 117, 212, 251, 91, 156}, + { 133, 61, 128, 63, 180, 147, 195, 100, 180}, + { 131, 19, 63, 31, 50, 66, 171, 64, 176}, + { 0, 0, 0, 31, 16, 16, 16, 100, 200}, + { 8, 23, 47, 73, 147, 244, 220, 80, 195}, + { 22, 43, 64, 80, 147, 115, 58, 85, 168}, + { 34, 60, 80, 214, 147, 212, 188, 85, 167}, + { 45, 77, 96, 216, 147, 99, 91, 85, 159}, + { 56, 76, 112, 107, 147, 212, 148, 64, 144}, + { 65, 91, 128, 137, 147, 196, 17, 69, 148}, + { 72, 104, 136, 138, 147, 180, 245, 73, 147}, + { 87, 116, 143, 126, 16, 83, 229, 77, 144}, + { 95, 119, 152, 254, 244, 83, 221, 77, 151}, + { 100, 129, 159, 156, 244, 148, 197, 77, 160}, + { 105, 141, 167, 247, 244, 132, 181, 84, 166}, + { 105, 147, 168, 247, 244, 245, 181, 90, 170}, + { 120, 153, 175, 248, 212, 229, 165, 90, 180}, + { 119, 156, 176, 248, 244, 229, 84, 74, 160}, + { 119, 158, 183, 248, 244, 229, 149, 78, 165} +}; + +/* Definition of the various controls */ +struct mctl { + struct v4l2_queryctrl desc; + size_t control; +}; + +#define BLMIN 0x0FF +#define WLMAX 0x3FF + +static const struct mctl maven_controls[] = +{ { { V4L2_CID_BRIGHTNESS, V4L2_CTRL_TYPE_INTEGER, + "brightness", + 0, WLMAX - BLMIN, 1, 379 - BLMIN, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.brightness) }, + { { V4L2_CID_CONTRAST, V4L2_CTRL_TYPE_INTEGER, + "contrast", + 0, 1023, 1, 127, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.contrast) }, + { { V4L2_CID_SATURATION, V4L2_CTRL_TYPE_INTEGER, + "saturation", + 0, 255, 1, 155, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.saturation) }, + { { V4L2_CID_HUE, V4L2_CTRL_TYPE_INTEGER, + "hue", + 0, 255, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.hue) }, + { { V4L2_CID_GAMMA, V4L2_CTRL_TYPE_INTEGER, + "gamma", + 0, ARRAY_SIZE(maven_gamma) - 1, 1, 3, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.gamma) }, + { { MATROXFB_CID_TESTOUT, V4L2_CTRL_TYPE_BOOLEAN, + "test output", + 0, 1, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.testout) }, + { { MATROXFB_CID_DEFLICKER, V4L2_CTRL_TYPE_INTEGER, + "deflicker mode", + 0, 2, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.deflicker) }, + +}; + +#define MAVCTRLS ARRAY_SIZE(maven_controls) + +/* Return: positive number: id found + -EINVAL: id not found, return failure + -ENOENT: id not found, create fake disabled control */ +static int get_ctrl_id(__u32 v4l2_id) { + int i; + + for (i = 0; i < MAVCTRLS; i++) { + if (v4l2_id < maven_controls[i].desc.id) { + if (maven_controls[i].desc.id == 0x08000000) { + return -EINVAL; + } + return -ENOENT; + } + if (v4l2_id == maven_controls[i].desc.id) { + return i; + } + } + return -EINVAL; +} + +struct maven_data { + struct matrox_fb_info* primary_head; + struct i2c_client *client; + int version; +}; + +static int* get_ctrl_ptr(struct maven_data* md, int idx) { + return (int*)((char*)(md->primary_head) + maven_controls[idx].control); +} + +static int maven_get_reg(struct i2c_client* c, char reg) { + char dst; + struct i2c_msg msgs[] = { + { + .addr = c->addr, + .flags = I2C_M_REV_DIR_ADDR, + .len = sizeof(reg), + .buf = ® + }, + { + .addr = c->addr, + .flags = I2C_M_RD | I2C_M_NOSTART, + .len = sizeof(dst), + .buf = &dst + } + }; + s32 err; + + err = i2c_transfer(c->adapter, msgs, 2); + if (err < 0) + printk(KERN_INFO "ReadReg(%d) failed\n", reg); + return dst & 0xFF; +} + +static int maven_set_reg(struct i2c_client* c, int reg, int val) { + s32 err; + + err = i2c_smbus_write_byte_data(c, reg, val); + if (err) + printk(KERN_INFO "WriteReg(%d) failed\n", reg); + return err; +} + +static int maven_set_reg_pair(struct i2c_client* c, int reg, int val) { + s32 err; + + err = i2c_smbus_write_word_data(c, reg, val); + if (err) + printk(KERN_INFO "WriteRegPair(%d) failed\n", reg); + return err; +} + +static const struct matrox_pll_features maven_pll = { + 50000, + 27000, + 4, 127, + 2, 31, + 3 +}; + +struct matrox_pll_features2 { + unsigned int vco_freq_min; + unsigned int vco_freq_max; + unsigned int feed_div_min; + unsigned int feed_div_max; + unsigned int in_div_min; + unsigned int in_div_max; + unsigned int post_shift_max; +}; + +struct matrox_pll_ctl { + unsigned int ref_freq; + unsigned int den; +}; + +static const struct matrox_pll_features2 maven1000_pll = { + 50000000, + 300000000, + 5, 128, + 3, 32, + 3 +}; + +static const struct matrox_pll_ctl maven_PAL = { + 540000, + 50 +}; + +static const struct matrox_pll_ctl maven_NTSC = { + 450450, /* 27027000/60 == 27000000/59.94005994 */ + 60 +}; + +static int matroxfb_PLL_mavenclock(const struct matrox_pll_features2* pll, + const struct matrox_pll_ctl* ctl, + unsigned int htotal, unsigned int vtotal, + unsigned int* in, unsigned int* feed, unsigned int* post, + unsigned int* h2) { + unsigned int besth2 = 0; + unsigned int fxtal = ctl->ref_freq; + unsigned int fmin = pll->vco_freq_min / ctl->den; + unsigned int fwant; + unsigned int p; + unsigned int scrlen; + unsigned int fmax; + + DBG(__func__) + + scrlen = htotal * (vtotal - 1); + fwant = htotal * vtotal; + fmax = pll->vco_freq_max / ctl->den; + + dprintk(KERN_DEBUG "want: %u, xtal: %u, h: %u, v: %u, fmax: %u\n", + fwant, fxtal, htotal, vtotal, fmax); + for (p = 1; p <= pll->post_shift_max; p++) { + if (fwant * 2 > fmax) + break; + fwant *= 2; + } + if (fwant > fmax) + return 0; + for (; p-- > 0; fwant >>= 1) { + unsigned int m; + + if (fwant < fmin) break; + for (m = pll->in_div_min; m <= pll->in_div_max; m++) { + unsigned int n; + unsigned int dvd; + unsigned int ln; + + n = (fwant * m) / fxtal; + if (n < pll->feed_div_min) + continue; + if (n > pll->feed_div_max) + break; + + ln = fxtal * n; + dvd = m << p; + + if (ln % dvd) + continue; + ln = ln / dvd; + + if (ln < scrlen + 2) + continue; + ln = ln - scrlen; + if (ln > htotal) + continue; + dprintk(KERN_DEBUG "Match: %u / %u / %u / %u\n", n, m, p, ln); + if (ln > besth2) { + dprintk(KERN_DEBUG "Better...\n"); + *h2 = besth2 = ln; + *post = p; + *in = m; + *feed = n; + } + } + } + + /* if h2/post/in/feed have not been assigned, return zero (error) */ + if (besth2 < 2) + return 0; + + dprintk(KERN_ERR "clk: %02X %02X %02X %d %d\n", *in, *feed, *post, fxtal, fwant); + return fxtal * (*feed) / (*in) * ctl->den; +} + +static int matroxfb_mavenclock(const struct matrox_pll_ctl *ctl, + unsigned int htotal, unsigned int vtotal, + unsigned int* in, unsigned int* feed, unsigned int* post, + unsigned int* htotal2) { + unsigned int fvco; + unsigned int uninitialized_var(p); + + fvco = matroxfb_PLL_mavenclock(&maven1000_pll, ctl, htotal, vtotal, in, feed, &p, htotal2); + if (!fvco) + return -EINVAL; + p = (1 << p) - 1; + if (fvco <= 100000000) + ; + else if (fvco <= 140000000) + p |= 0x08; + else if (fvco <= 180000000) + p |= 0x10; + else + p |= 0x18; + *post = p; + return 0; +} + +static void DAC1064_calcclock(unsigned int freq, unsigned int fmax, + unsigned int* in, unsigned int* feed, unsigned int* post) { + unsigned int fvco; + unsigned int p; + + fvco = matroxfb_PLL_calcclock(&maven_pll, freq, fmax, in, feed, &p); + p = (1 << p) - 1; + if (fvco <= 100000) + ; + else if (fvco <= 140000) + p |= 0x08; + else if (fvco <= 180000) + p |= 0x10; + else + p |= 0x18; + *post = p; + return; +} + +static unsigned char maven_compute_deflicker (const struct maven_data* md) { + unsigned char df; + + df = (md->version == MGATVO_B?0x40:0x00); + switch (md->primary_head->altout.tvo_params.deflicker) { + case 0: +/* df |= 0x00; */ + break; + case 1: + df |= 0xB1; + break; + case 2: + df |= 0xA2; + break; + } + return df; +} + +static void maven_compute_bwlevel (const struct maven_data* md, + int *bl, int *wl) { + const int b = md->primary_head->altout.tvo_params.brightness + BLMIN; + const int c = md->primary_head->altout.tvo_params.contrast; + + *bl = max(b - c, BLMIN); + *wl = min(b + c, WLMAX); +} + +static const struct maven_gamma* maven_compute_gamma (const struct maven_data* md) { + return maven_gamma + md->primary_head->altout.tvo_params.gamma; +} + + +static void maven_init_TVdata(const struct maven_data* md, struct mavenregs* data) { + static struct mavenregs palregs = { { + 0x2A, 0x09, 0x8A, 0xCB, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* ? not written */ + 0x00, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x44, /* 09 */ + 0x9C, /* 0A */ + 0x2E, /* 0B */ + 0x21, /* 0C */ + 0x00, /* ? not written */ + 0x3F, 0x03, /* 0E-0F */ + 0x3F, 0x03, /* 10-11 */ + 0x1A, /* 12 */ + 0x2A, /* 13 */ + 0x1C, 0x3D, 0x14, /* 14-16 */ + 0x9C, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0xFE, /* 1A */ + 0x7E, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ + 0x89, 0x03, /* 1E-1F */ + 0x72, /* 20 */ + 0x07, /* 21 */ + 0x72, /* 22 */ + 0x00, /* 23 */ + 0x00, /* 24 */ + 0x00, /* 25 */ + 0x08, /* 26 */ + 0x04, /* 27 */ + 0x00, /* 28 */ + 0x1A, /* 29 */ + 0x55, 0x01, /* 2A-2B */ + 0x26, /* 2C */ + 0x07, 0x7E, /* 2D-2E */ + 0x02, 0x54, /* 2F-30 */ + 0xB0, 0x00, /* 31-32 */ + 0x14, /* 33 */ + 0x49, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x22, /* 39 */ + 0x02, /* 3A */ + 0x22, /* 3B */ + 0x3F, 0x03, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* 3F not written */ + }, MATROXFB_OUTPUT_MODE_PAL, 625, 50 }; + static struct mavenregs ntscregs = { { + 0x21, 0xF0, 0x7C, 0x1F, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* ? not written */ + 0x00, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x43, /* 09 */ + 0x7E, /* 0A */ + 0x3D, /* 0B */ + 0x00, /* 0C */ + 0x00, /* ? not written */ + 0x41, 0x00, /* 0E-0F */ + 0x3C, 0x00, /* 10-11 */ + 0x17, /* 12 */ + 0x21, /* 13 */ + 0x1B, 0x1B, 0x24, /* 14-16 */ + 0x83, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0x0F, /* 1A */ + 0x0F, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ + 0x89, 0x02, /* 1E-1F */ + 0x5F, /* 20 */ + 0x04, /* 21 */ + 0x5F, /* 22 */ + 0x01, /* 23 */ + 0x02, /* 24 */ + 0x00, /* 25 */ + 0x0A, /* 26 */ + 0x05, /* 27 */ + 0x00, /* 28 */ + 0x10, /* 29 */ + 0xFF, 0x03, /* 2A-2B */ + 0x24, /* 2C */ + 0x0F, 0x78, /* 2D-2E */ + 0x00, 0x00, /* 2F-30 */ + 0xB2, 0x04, /* 31-32 */ + 0x14, /* 33 */ + 0x02, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x15, /* 39 */ + 0x05, /* 3A */ + 0x3B, /* 3B */ + 0x3C, 0x00, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* never written */ + }, MATROXFB_OUTPUT_MODE_NTSC, 525, 60 }; + struct matrox_fb_info *minfo = md->primary_head; + + if (minfo->outputs[1].mode == MATROXFB_OUTPUT_MODE_PAL) + *data = palregs; + else + *data = ntscregs; + + /* Set deflicker */ + data->regs[0x93] = maven_compute_deflicker(md); + + /* set gamma */ + { + const struct maven_gamma* g; + g = maven_compute_gamma(md); + data->regs[0x83] = g->reg83; + data->regs[0x84] = g->reg84; + data->regs[0x85] = g->reg85; + data->regs[0x86] = g->reg86; + data->regs[0x87] = g->reg87; + data->regs[0x88] = g->reg88; + data->regs[0x89] = g->reg89; + data->regs[0x8A] = g->reg8a; + data->regs[0x8B] = g->reg8b; + } + + /* Set contrast / brightness */ + { + int bl, wl; + maven_compute_bwlevel (md, &bl, &wl); + data->regs[0x0e] = bl >> 2; + data->regs[0x0f] = bl & 3; + data->regs[0x1e] = wl >> 2; + data->regs[0x1f] = wl & 3; + } + + /* Set saturation */ + { + data->regs[0x20] = + data->regs[0x22] = minfo->altout.tvo_params.saturation; + } + + /* Set HUE */ + data->regs[0x25] = minfo->altout.tvo_params.hue; + return; +} + +#define LR(x) maven_set_reg(c, (x), m->regs[(x)]) +#define LRP(x) maven_set_reg_pair(c, (x), m->regs[(x)] | (m->regs[(x)+1] << 8)) +static void maven_init_TV(struct i2c_client* c, const struct mavenregs* m) { + int val; + + + maven_set_reg(c, 0x3E, 0x01); + maven_get_reg(c, 0x82); /* fetch oscillator state? */ + maven_set_reg(c, 0x8C, 0x00); + maven_get_reg(c, 0x94); /* get 0x82 */ + maven_set_reg(c, 0x94, 0xA2); + /* xmiscctrl */ + + maven_set_reg_pair(c, 0x8E, 0x1EFF); + maven_set_reg(c, 0xC6, 0x01); + + /* removed code... */ + + maven_get_reg(c, 0x06); + maven_set_reg(c, 0x06, 0xF9); /* or read |= 0xF0 ? */ + + /* removed code here... */ + + /* real code begins here? */ + /* chroma subcarrier */ + LR(0x00); LR(0x01); LR(0x02); LR(0x03); + + LR(0x04); + + LR(0x2C); + LR(0x08); + LR(0x0A); + LR(0x09); + LR(0x29); + LRP(0x31); + LRP(0x17); + LR(0x0B); + LR(0x0C); + if (m->mode == MATROXFB_OUTPUT_MODE_PAL) { + maven_set_reg(c, 0x35, 0x10); /* ... */ + } else { + maven_set_reg(c, 0x35, 0x0F); /* ... */ + } + + LRP(0x10); + + LRP(0x0E); + LRP(0x1E); + + LR(0x20); /* saturation #1 */ + LR(0x22); /* saturation #2 */ + LR(0x25); /* hue */ + LR(0x34); + LR(0x33); + LR(0x19); + LR(0x12); + LR(0x3B); + LR(0x13); + LR(0x39); + LR(0x1D); + LR(0x3A); + LR(0x24); + LR(0x14); + LR(0x15); + LR(0x16); + LRP(0x2D); + LRP(0x2F); + LR(0x1A); + LR(0x1B); + LR(0x1C); + LR(0x23); + LR(0x26); + LR(0x28); + LR(0x27); + LR(0x21); + LRP(0x2A); + if (m->mode == MATROXFB_OUTPUT_MODE_PAL) + maven_set_reg(c, 0x35, 0x1D); /* ... */ + else + maven_set_reg(c, 0x35, 0x1C); + + LRP(0x3C); + LR(0x37); + LR(0x38); + maven_set_reg(c, 0xB3, 0x01); + + maven_get_reg(c, 0xB0); /* read 0x80 */ + maven_set_reg(c, 0xB0, 0x08); /* ugh... */ + maven_get_reg(c, 0xB9); /* read 0x7C */ + maven_set_reg(c, 0xB9, 0x78); + maven_get_reg(c, 0xBF); /* read 0x00 */ + maven_set_reg(c, 0xBF, 0x02); + maven_get_reg(c, 0x94); /* read 0x82 */ + maven_set_reg(c, 0x94, 0xB3); + + LR(0x80); /* 04 1A 91 or 05 21 91 */ + LR(0x81); + LR(0x82); + + maven_set_reg(c, 0x8C, 0x20); + maven_get_reg(c, 0x8D); + maven_set_reg(c, 0x8D, 0x10); + + LR(0x90); /* 4D 50 52 or 4E 05 45 */ + LR(0x91); + LR(0x92); + + LRP(0x9A); /* 0049 or 004F */ + LRP(0x9C); /* 0004 or 0004 */ + LRP(0x9E); /* 0458 or 045E */ + LRP(0xA0); /* 05DA or 051B */ + LRP(0xA2); /* 00CC or 00CF */ + LRP(0xA4); /* 007D or 007F */ + LRP(0xA6); /* 007C or 007E */ + LRP(0xA8); /* 03CB or 03CE */ + LRP(0x98); /* 0000 or 0000 */ + LRP(0xAE); /* 0044 or 003A */ + LRP(0x96); /* 05DA or 051B */ + LRP(0xAA); /* 04BC or 046A */ + LRP(0xAC); /* 004D or 004E */ + + LR(0xBE); + LR(0xC2); + + maven_get_reg(c, 0x8D); + maven_set_reg(c, 0x8D, 0x04); + + LR(0x20); /* saturation #1 */ + LR(0x22); /* saturation #2 */ + LR(0x93); /* whoops */ + LR(0x20); /* oh, saturation #1 again */ + LR(0x22); /* oh, saturation #2 again */ + LR(0x25); /* hue */ + LRP(0x0E); + LRP(0x1E); + LRP(0x0E); /* problems with memory? */ + LRP(0x1E); /* yes, matrox must have problems in memory area... */ + + /* load gamma correction stuff */ + LR(0x83); + LR(0x84); + LR(0x85); + LR(0x86); + LR(0x87); + LR(0x88); + LR(0x89); + LR(0x8A); + LR(0x8B); + + val = maven_get_reg(c, 0x8D); + val &= 0x14; /* 0x10 or anything ored with it */ + maven_set_reg(c, 0x8D, val); + + LR(0x33); + LR(0x19); + LR(0x12); + LR(0x3B); + LR(0x13); + LR(0x39); + LR(0x1D); + LR(0x3A); + LR(0x24); + LR(0x14); + LR(0x15); + LR(0x16); + LRP(0x2D); + LRP(0x2F); + LR(0x1A); + LR(0x1B); + LR(0x1C); + LR(0x23); + LR(0x26); + LR(0x28); + LR(0x27); + LR(0x21); + LRP(0x2A); + if (m->mode == MATROXFB_OUTPUT_MODE_PAL) + maven_set_reg(c, 0x35, 0x1D); + else + maven_set_reg(c, 0x35, 0x1C); + LRP(0x3C); + LR(0x37); + LR(0x38); + + maven_get_reg(c, 0xB0); + LR(0xB0); /* output mode */ + LR(0x90); + LR(0xBE); + LR(0xC2); + + LRP(0x9A); + LRP(0xA2); + LRP(0x9E); + LRP(0xA6); + LRP(0xAA); + LRP(0xAC); + maven_set_reg(c, 0x3E, 0x00); + maven_set_reg(c, 0x95, 0x20); +} + +static int maven_find_exact_clocks(unsigned int ht, unsigned int vt, + struct mavenregs* m) { + unsigned int x; + unsigned int err = ~0; + + /* 1:1 */ + m->regs[0x80] = 0x0F; + m->regs[0x81] = 0x07; + m->regs[0x82] = 0x81; + + for (x = 0; x < 8; x++) { + unsigned int c; + unsigned int uninitialized_var(a), uninitialized_var(b), + uninitialized_var(h2); + unsigned int h = ht + 2 + x; + + if (!matroxfb_mavenclock((m->mode == MATROXFB_OUTPUT_MODE_PAL) ? &maven_PAL : &maven_NTSC, h, vt, &a, &b, &c, &h2)) { + unsigned int diff = h - h2; + + if (diff < err) { + err = diff; + m->regs[0x80] = a - 1; + m->regs[0x81] = b - 1; + m->regs[0x82] = c | 0x80; + m->hcorr = h2 - 2; + m->htotal = h - 2; + } + } + } + return err != ~0U; +} + +static inline int maven_compute_timming(struct maven_data* md, + struct my_timming* mt, + struct mavenregs* m) { + unsigned int tmpi; + unsigned int a, bv, c; + struct matrox_fb_info *minfo = md->primary_head; + + m->mode = minfo->outputs[1].mode; + if (m->mode != MATROXFB_OUTPUT_MODE_MONITOR) { + unsigned int lmargin; + unsigned int umargin; + unsigned int vslen; + unsigned int hcrt; + unsigned int slen; + + maven_init_TVdata(md, m); + + if (maven_find_exact_clocks(mt->HTotal, mt->VTotal, m) == 0) + return -EINVAL; + + lmargin = mt->HTotal - mt->HSyncEnd; + slen = mt->HSyncEnd - mt->HSyncStart; + hcrt = mt->HTotal - slen - mt->delay; + umargin = mt->VTotal - mt->VSyncEnd; + vslen = mt->VSyncEnd - mt->VSyncStart; + + if (m->hcorr < mt->HTotal) + hcrt += m->hcorr; + if (hcrt > mt->HTotal) + hcrt -= mt->HTotal; + if (hcrt + 2 > mt->HTotal) + hcrt = 0; /* or issue warning? */ + + /* last (first? middle?) line in picture can have different length */ + /* hlen - 2 */ + m->regs[0x96] = m->hcorr; + m->regs[0x97] = m->hcorr >> 8; + /* ... */ + m->regs[0x98] = 0x00; m->regs[0x99] = 0x00; + /* hblanking end */ + m->regs[0x9A] = lmargin; /* 100% */ + m->regs[0x9B] = lmargin >> 8; /* 100% */ + /* who knows */ + m->regs[0x9C] = 0x04; + m->regs[0x9D] = 0x00; + /* htotal - 2 */ + m->regs[0xA0] = m->htotal; + m->regs[0xA1] = m->htotal >> 8; + /* vblanking end */ + m->regs[0xA2] = mt->VTotal - mt->VSyncStart - 1; /* stop vblanking */ + m->regs[0xA3] = (mt->VTotal - mt->VSyncStart - 1) >> 8; + /* something end... [A6]+1..[A8] */ + if (md->version == MGATVO_B) { + m->regs[0xA4] = 0x04; + m->regs[0xA5] = 0x00; + } else { + m->regs[0xA4] = 0x01; + m->regs[0xA5] = 0x00; + } + /* something start... 0..[A4]-1 */ + m->regs[0xA6] = 0x00; + m->regs[0xA7] = 0x00; + /* vertical line count - 1 */ + m->regs[0xA8] = mt->VTotal - 1; + m->regs[0xA9] = (mt->VTotal - 1) >> 8; + /* horizontal vidrst pos */ + m->regs[0xAA] = hcrt; /* 0 <= hcrt <= htotal - 2 */ + m->regs[0xAB] = hcrt >> 8; + /* vertical vidrst pos */ + m->regs[0xAC] = mt->VTotal - 2; + m->regs[0xAD] = (mt->VTotal - 2) >> 8; + /* moves picture up/down and so on... */ + m->regs[0xAE] = 0x01; /* Fix this... 0..VTotal */ + m->regs[0xAF] = 0x00; + { + int hdec; + int hlen; + unsigned int ibmin = 4 + lmargin + mt->HDisplay; + unsigned int ib; + int i; + + /* Verify! */ + /* Where 94208 came from? */ + if (mt->HTotal) + hdec = 94208 / (mt->HTotal); + else + hdec = 0x81; + if (hdec > 0x81) + hdec = 0x81; + if (hdec < 0x41) + hdec = 0x41; + hdec--; + hlen = 98304 - 128 - ((lmargin + mt->HDisplay - 8) * hdec); + if (hlen < 0) + hlen = 0; + hlen = hlen >> 8; + if (hlen > 0xFF) + hlen = 0xFF; + /* Now we have to compute input buffer length. + If you want any picture, it must be between + 4 + lmargin + xres + and + 94208 / hdec + If you want perfect picture even on the top + of screen, it must be also + 0x3C0000 * i / hdec + Q - R / hdec + where + R Qmin Qmax + 0x07000 0x5AE 0x5BF + 0x08000 0x5CF 0x5FF + 0x0C000 0x653 0x67F + 0x10000 0x6F8 0x6FF + */ + i = 1; + do { + ib = ((0x3C0000 * i - 0x8000)/ hdec + 0x05E7) >> 8; + i++; + } while (ib < ibmin); + if (ib >= m->htotal + 2) { + ib = ibmin; + } + + m->regs[0x90] = hdec; /* < 0x40 || > 0x80 is bad... 0x80 is questionable */ + m->regs[0xC2] = hlen; + /* 'valid' input line length */ + m->regs[0x9E] = ib; + m->regs[0x9F] = ib >> 8; + } + { + int vdec; + int vlen; + +#define MATROX_USE64BIT_DIVIDE + if (mt->VTotal) { +#ifdef MATROX_USE64BIT_DIVIDE + u64 f1; + u32 a; + u32 b; + + a = m->vlines * (m->htotal + 2); + b = (mt->VTotal - 1) * (m->htotal + 2) + m->hcorr + 2; + + f1 = ((u64)a) << 15; /* *32768 */ + do_div(f1, b); + vdec = f1; +#else + vdec = m->vlines * 32768 / mt->VTotal; +#endif + } else + vdec = 0x8000; + if (vdec > 0x8000) + vdec = 0x8000; + vlen = (vslen + umargin + mt->VDisplay) * vdec; + vlen = (vlen >> 16) - 146; /* FIXME: 146?! */ + if (vlen < 0) + vlen = 0; + if (vlen > 0xFF) + vlen = 0xFF; + vdec--; + m->regs[0x91] = vdec; + m->regs[0x92] = vdec >> 8; + m->regs[0xBE] = vlen; + } + m->regs[0xB0] = 0x08; /* output: SVideo/Composite */ + return 0; + } + + DAC1064_calcclock(mt->pixclock, 450000, &a, &bv, &c); + m->regs[0x80] = a; + m->regs[0x81] = bv; + m->regs[0x82] = c | 0x80; + + m->regs[0xB3] = 0x01; + m->regs[0x94] = 0xB2; + + /* htotal... */ + m->regs[0x96] = mt->HTotal; + m->regs[0x97] = mt->HTotal >> 8; + /* ?? */ + m->regs[0x98] = 0x00; + m->regs[0x99] = 0x00; + /* hsync len */ + tmpi = mt->HSyncEnd - mt->HSyncStart; + m->regs[0x9A] = tmpi; + m->regs[0x9B] = tmpi >> 8; + /* hblank end */ + tmpi = mt->HTotal - mt->HSyncStart; + m->regs[0x9C] = tmpi; + m->regs[0x9D] = tmpi >> 8; + /* hblank start */ + tmpi += mt->HDisplay; + m->regs[0x9E] = tmpi; + m->regs[0x9F] = tmpi >> 8; + /* htotal + 1 */ + tmpi = mt->HTotal + 1; + m->regs[0xA0] = tmpi; + m->regs[0xA1] = tmpi >> 8; + /* vsync?! */ + tmpi = mt->VSyncEnd - mt->VSyncStart - 1; + m->regs[0xA2] = tmpi; + m->regs[0xA3] = tmpi >> 8; + /* ignored? */ + tmpi = mt->VTotal - mt->VSyncStart; + m->regs[0xA4] = tmpi; + m->regs[0xA5] = tmpi >> 8; + /* ignored? */ + tmpi = mt->VTotal - 1; + m->regs[0xA6] = tmpi; + m->regs[0xA7] = tmpi >> 8; + /* vtotal - 1 */ + m->regs[0xA8] = tmpi; + m->regs[0xA9] = tmpi >> 8; + /* hor vidrst */ + tmpi = mt->HTotal - mt->delay; + m->regs[0xAA] = tmpi; + m->regs[0xAB] = tmpi >> 8; + /* vert vidrst */ + tmpi = mt->VTotal - 2; + m->regs[0xAC] = tmpi; + m->regs[0xAD] = tmpi >> 8; + /* ignored? */ + m->regs[0xAE] = 0x00; + m->regs[0xAF] = 0x00; + + m->regs[0xB0] = 0x03; /* output: monitor */ + m->regs[0xB1] = 0xA0; /* ??? */ + m->regs[0x8C] = 0x20; /* must be set... */ + m->regs[0x8D] = 0x04; /* defaults to 0x10: test signal */ + m->regs[0xB9] = 0x1A; /* defaults to 0x2C: too bright */ + m->regs[0xBF] = 0x22; /* makes picture stable */ + + return 0; +} + +static int maven_program_timming(struct maven_data* md, + const struct mavenregs* m) { + struct i2c_client *c = md->client; + + if (m->mode == MATROXFB_OUTPUT_MODE_MONITOR) { + LR(0x80); + LR(0x81); + LR(0x82); + + LR(0xB3); + LR(0x94); + + LRP(0x96); + LRP(0x98); + LRP(0x9A); + LRP(0x9C); + LRP(0x9E); + LRP(0xA0); + LRP(0xA2); + LRP(0xA4); + LRP(0xA6); + LRP(0xA8); + LRP(0xAA); + LRP(0xAC); + LRP(0xAE); + + LR(0xB0); /* output: monitor */ + LR(0xB1); /* ??? */ + LR(0x8C); /* must be set... */ + LR(0x8D); /* defaults to 0x10: test signal */ + LR(0xB9); /* defaults to 0x2C: too bright */ + LR(0xBF); /* makes picture stable */ + } else { + maven_init_TV(c, m); + } + return 0; +} + +static inline int maven_resync(struct maven_data* md) { + struct i2c_client *c = md->client; + maven_set_reg(c, 0x95, 0x20); /* start whole thing */ + return 0; +} + +static int maven_get_queryctrl (struct maven_data* md, + struct v4l2_queryctrl *p) { + int i; + + i = get_ctrl_id(p->id); + if (i >= 0) { + *p = maven_controls[i].desc; + return 0; + } + if (i == -ENOENT) { + static const struct v4l2_queryctrl disctrl = + { .flags = V4L2_CTRL_FLAG_DISABLED }; + + i = p->id; + *p = disctrl; + p->id = i; + sprintf(p->name, "Ctrl #%08X", i); + return 0; + } + return -EINVAL; +} + +static int maven_set_control (struct maven_data* md, + struct v4l2_control *p) { + int i; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + + /* + * Check if changed. + */ + if (p->value == *get_ctrl_ptr(md, i)) return 0; + + /* + * Check limits. + */ + if (p->value > maven_controls[i].desc.maximum) return -EINVAL; + if (p->value < maven_controls[i].desc.minimum) return -EINVAL; + + /* + * Store new value. + */ + *get_ctrl_ptr(md, i) = p->value; + + switch (p->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + { + int blacklevel, whitelevel; + maven_compute_bwlevel(md, &blacklevel, &whitelevel); + blacklevel = (blacklevel >> 2) | ((blacklevel & 3) << 8); + whitelevel = (whitelevel >> 2) | ((whitelevel & 3) << 8); + maven_set_reg_pair(md->client, 0x0e, blacklevel); + maven_set_reg_pair(md->client, 0x1e, whitelevel); + } + break; + case V4L2_CID_SATURATION: + { + maven_set_reg(md->client, 0x20, p->value); + maven_set_reg(md->client, 0x22, p->value); + } + break; + case V4L2_CID_HUE: + { + maven_set_reg(md->client, 0x25, p->value); + } + break; + case V4L2_CID_GAMMA: + { + const struct maven_gamma* g; + g = maven_compute_gamma(md); + maven_set_reg(md->client, 0x83, g->reg83); + maven_set_reg(md->client, 0x84, g->reg84); + maven_set_reg(md->client, 0x85, g->reg85); + maven_set_reg(md->client, 0x86, g->reg86); + maven_set_reg(md->client, 0x87, g->reg87); + maven_set_reg(md->client, 0x88, g->reg88); + maven_set_reg(md->client, 0x89, g->reg89); + maven_set_reg(md->client, 0x8a, g->reg8a); + maven_set_reg(md->client, 0x8b, g->reg8b); + } + break; + case MATROXFB_CID_TESTOUT: + { + unsigned char val + = maven_get_reg(md->client, 0x8d); + if (p->value) val |= 0x10; + else val &= ~0x10; + maven_set_reg(md->client, 0x8d, val); + } + break; + case MATROXFB_CID_DEFLICKER: + { + maven_set_reg(md->client, 0x93, maven_compute_deflicker(md)); + } + break; + } + + + return 0; +} + +static int maven_get_control (struct maven_data* md, + struct v4l2_control *p) { + int i; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + p->value = *get_ctrl_ptr(md, i); + return 0; +} + +/******************************************************/ + +static int maven_out_compute(void* md, struct my_timming* mt) { +#define mdinfo ((struct maven_data*)md) +#define minfo (mdinfo->primary_head) + return maven_compute_timming(md, mt, &minfo->hw.maven); +#undef minfo +#undef mdinfo +} + +static int maven_out_program(void* md) { +#define mdinfo ((struct maven_data*)md) +#define minfo (mdinfo->primary_head) + return maven_program_timming(md, &minfo->hw.maven); +#undef minfo +#undef mdinfo +} + +static int maven_out_start(void* md) { + return maven_resync(md); +} + +static int maven_out_verify_mode(void* md, u_int32_t arg) { + switch (arg) { + case MATROXFB_OUTPUT_MODE_PAL: + case MATROXFB_OUTPUT_MODE_NTSC: + case MATROXFB_OUTPUT_MODE_MONITOR: + return 0; + } + return -EINVAL; +} + +static int maven_out_get_queryctrl(void* md, struct v4l2_queryctrl* p) { + return maven_get_queryctrl(md, p); +} + +static int maven_out_get_ctrl(void* md, struct v4l2_control* p) { + return maven_get_control(md, p); +} + +static int maven_out_set_ctrl(void* md, struct v4l2_control* p) { + return maven_set_control(md, p); +} + +static struct matrox_altout maven_altout = { + .name = "Secondary output", + .compute = maven_out_compute, + .program = maven_out_program, + .start = maven_out_start, + .verifymode = maven_out_verify_mode, + .getqueryctrl = maven_out_get_queryctrl, + .getctrl = maven_out_get_ctrl, + .setctrl = maven_out_set_ctrl, +}; + +static int maven_init_client(struct i2c_client* clnt) { + struct maven_data* md = i2c_get_clientdata(clnt); + struct matrox_fb_info *minfo = container_of(clnt->adapter, + struct i2c_bit_adapter, + adapter)->minfo; + + md->primary_head = minfo; + md->client = clnt; + down_write(&minfo->altout.lock); + minfo->outputs[1].output = &maven_altout; + minfo->outputs[1].src = minfo->outputs[1].default_src; + minfo->outputs[1].data = md; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + if (maven_get_reg(clnt, 0xB2) < 0x14) { + md->version = MGATVO_B; + /* Tweak some things for this old chip */ + } else { + md->version = MGATVO_C; + } + /* + * Set all parameters to its initial values. + */ + { + unsigned int i; + + for (i = 0; i < MAVCTRLS; ++i) { + *get_ctrl_ptr(md, i) = maven_controls[i].desc.default_value; + } + } + + return 0; +} + +static int maven_shutdown_client(struct i2c_client* clnt) { + struct maven_data* md = i2c_get_clientdata(clnt); + + if (md->primary_head) { + struct matrox_fb_info *minfo = md->primary_head; + + down_write(&minfo->altout.lock); + minfo->outputs[1].src = MATROXFB_SRC_NONE; + minfo->outputs[1].output = NULL; + minfo->outputs[1].data = NULL; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + md->primary_head = NULL; + } + return 0; +} + +static int maven_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + int err = -ENODEV; + struct maven_data* data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_WORD_DATA | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_NOSTART | + I2C_FUNC_PROTOCOL_MANGLING)) + goto ERROR0; + if (!(data = kzalloc(sizeof(*data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + i2c_set_clientdata(client, data); + err = maven_init_client(client); + if (err) + goto ERROR4; + return 0; +ERROR4:; + kfree(data); +ERROR0:; + return err; +} + +static int maven_remove(struct i2c_client *client) +{ + maven_shutdown_client(client); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id maven_id[] = { + { "maven", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, maven_id); + +static struct i2c_driver maven_driver={ + .driver = { + .name = "maven", + }, + .probe = maven_probe, + .remove = maven_remove, + .id_table = maven_id, +}; + +module_i2c_driver(maven_driver); +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G200/G400 Matrox MGA-TVO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_maven.h b/drivers/video/fbdev/matrox/matroxfb_maven.h new file mode 100644 index 000000000000..99eddec9f30c --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_maven.h @@ -0,0 +1,20 @@ +#ifndef __MATROXFB_MAVEN_H__ +#define __MATROXFB_MAVEN_H__ + +#include <linux/ioctl.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include "matroxfb_base.h" + +struct i2c_bit_adapter { + struct i2c_adapter adapter; + int initialized; + struct i2c_algo_bit_data bac; + struct matrox_fb_info* minfo; + struct { + unsigned int data; + unsigned int clock; + } mask; +}; + +#endif /* __MATROXFB_MAVEN_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_misc.c b/drivers/video/fbdev/matrox/matroxfb_misc.c new file mode 100644 index 000000000000..9948ca2a3046 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_misc.c @@ -0,0 +1,815 @@ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * "David C. Hansen" <haveblue@us.ibm.com> + * Fixes + * + * "Ian Romanick" <idr@us.ibm.com> + * Find PInS data in BIOS on PowerPC systems. + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + + +#include "matroxfb_misc.h" +#include <linux/interrupt.h> +#include <linux/matroxfb.h> + +void matroxfb_DAC_out(const struct matrox_fb_info *minfo, int reg, int val) +{ + DBG_REG(__func__) + mga_outb(M_RAMDAC_BASE+M_X_INDEX, reg); + mga_outb(M_RAMDAC_BASE+M_X_DATAREG, val); +} + +int matroxfb_DAC_in(const struct matrox_fb_info *minfo, int reg) +{ + DBG_REG(__func__) + mga_outb(M_RAMDAC_BASE+M_X_INDEX, reg); + return mga_inb(M_RAMDAC_BASE+M_X_DATAREG); +} + +void matroxfb_var2my(struct fb_var_screeninfo* var, struct my_timming* mt) { + unsigned int pixclock = var->pixclock; + + DBG(__func__) + + if (!pixclock) pixclock = 10000; /* 10ns = 100MHz */ + mt->pixclock = 1000000000 / pixclock; + if (mt->pixclock < 1) mt->pixclock = 1; + mt->mnp = -1; + mt->dblscan = var->vmode & FB_VMODE_DOUBLE; + mt->interlaced = var->vmode & FB_VMODE_INTERLACED; + mt->HDisplay = var->xres; + mt->HSyncStart = mt->HDisplay + var->right_margin; + mt->HSyncEnd = mt->HSyncStart + var->hsync_len; + mt->HTotal = mt->HSyncEnd + var->left_margin; + mt->VDisplay = var->yres; + mt->VSyncStart = mt->VDisplay + var->lower_margin; + mt->VSyncEnd = mt->VSyncStart + var->vsync_len; + mt->VTotal = mt->VSyncEnd + var->upper_margin; + mt->sync = var->sync; +} + +int matroxfb_PLL_calcclock(const struct matrox_pll_features* pll, unsigned int freq, unsigned int fmax, + unsigned int* in, unsigned int* feed, unsigned int* post) { + unsigned int bestdiff = ~0; + unsigned int bestvco = 0; + unsigned int fxtal = pll->ref_freq; + unsigned int fwant; + unsigned int p; + + DBG(__func__) + + fwant = freq; + +#ifdef DEBUG + printk(KERN_ERR "post_shift_max: %d\n", pll->post_shift_max); + printk(KERN_ERR "ref_freq: %d\n", pll->ref_freq); + printk(KERN_ERR "freq: %d\n", freq); + printk(KERN_ERR "vco_freq_min: %d\n", pll->vco_freq_min); + printk(KERN_ERR "in_div_min: %d\n", pll->in_div_min); + printk(KERN_ERR "in_div_max: %d\n", pll->in_div_max); + printk(KERN_ERR "feed_div_min: %d\n", pll->feed_div_min); + printk(KERN_ERR "feed_div_max: %d\n", pll->feed_div_max); + printk(KERN_ERR "fmax: %d\n", fmax); +#endif + for (p = 1; p <= pll->post_shift_max; p++) { + if (fwant * 2 > fmax) + break; + fwant *= 2; + } + if (fwant < pll->vco_freq_min) fwant = pll->vco_freq_min; + if (fwant > fmax) fwant = fmax; + for (; p-- > 0; fwant >>= 1, bestdiff >>= 1) { + unsigned int m; + + if (fwant < pll->vco_freq_min) break; + for (m = pll->in_div_min; m <= pll->in_div_max; m++) { + unsigned int diff, fvco; + unsigned int n; + + n = (fwant * (m + 1) + (fxtal >> 1)) / fxtal - 1; + if (n > pll->feed_div_max) + break; + if (n < pll->feed_div_min) + n = pll->feed_div_min; + fvco = (fxtal * (n + 1)) / (m + 1); + if (fvco < fwant) + diff = fwant - fvco; + else + diff = fvco - fwant; + if (diff < bestdiff) { + bestdiff = diff; + *post = p; + *in = m; + *feed = n; + bestvco = fvco; + } + } + } + dprintk(KERN_ERR "clk: %02X %02X %02X %d %d %d\n", *in, *feed, *post, fxtal, bestvco, fwant); + return bestvco; +} + +int matroxfb_vgaHWinit(struct matrox_fb_info *minfo, struct my_timming *m) +{ + unsigned int hd, hs, he, hbe, ht; + unsigned int vd, vs, ve, vt, lc; + unsigned int wd; + unsigned int divider; + int i; + struct matrox_hw_state * const hw = &minfo->hw; + + DBG(__func__) + + hw->SEQ[0] = 0x00; + hw->SEQ[1] = 0x01; /* or 0x09 */ + hw->SEQ[2] = 0x0F; /* bitplanes */ + hw->SEQ[3] = 0x00; + hw->SEQ[4] = 0x0E; + /* CRTC 0..7, 9, 16..19, 21, 22 are reprogrammed by Matrox Millennium code... Hope that by MGA1064 too */ + if (m->dblscan) { + m->VTotal <<= 1; + m->VDisplay <<= 1; + m->VSyncStart <<= 1; + m->VSyncEnd <<= 1; + } + if (m->interlaced) { + m->VTotal >>= 1; + m->VDisplay >>= 1; + m->VSyncStart >>= 1; + m->VSyncEnd >>= 1; + } + + /* GCTL is ignored when not using 0xA0000 aperture */ + hw->GCTL[0] = 0x00; + hw->GCTL[1] = 0x00; + hw->GCTL[2] = 0x00; + hw->GCTL[3] = 0x00; + hw->GCTL[4] = 0x00; + hw->GCTL[5] = 0x40; + hw->GCTL[6] = 0x05; + hw->GCTL[7] = 0x0F; + hw->GCTL[8] = 0xFF; + + /* Whole ATTR is ignored in PowerGraphics mode */ + for (i = 0; i < 16; i++) + hw->ATTR[i] = i; + hw->ATTR[16] = 0x41; + hw->ATTR[17] = 0xFF; + hw->ATTR[18] = 0x0F; + hw->ATTR[19] = 0x00; + hw->ATTR[20] = 0x00; + + hd = m->HDisplay >> 3; + hs = m->HSyncStart >> 3; + he = m->HSyncEnd >> 3; + ht = m->HTotal >> 3; + /* standard timmings are in 8pixels, but for interleaved we cannot */ + /* do it for 4bpp (because of (4bpp >> 1(interleaved))/4 == 0) */ + /* using 16 or more pixels per unit can save us */ + divider = minfo->curr.final_bppShift; + while (divider & 3) { + hd >>= 1; + hs >>= 1; + he >>= 1; + ht >>= 1; + divider <<= 1; + } + divider = divider / 4; + /* divider can be from 1 to 8 */ + while (divider > 8) { + hd <<= 1; + hs <<= 1; + he <<= 1; + ht <<= 1; + divider >>= 1; + } + hd = hd - 1; + hs = hs - 1; + he = he - 1; + ht = ht - 1; + vd = m->VDisplay - 1; + vs = m->VSyncStart - 1; + ve = m->VSyncEnd - 1; + vt = m->VTotal - 2; + lc = vd; + /* G200 cannot work with (ht & 7) == 6 */ + if (((ht & 0x07) == 0x06) || ((ht & 0x0F) == 0x04)) + ht++; + hbe = ht; + wd = minfo->fbcon.var.xres_virtual * minfo->curr.final_bppShift / 64; + + hw->CRTCEXT[0] = 0; + hw->CRTCEXT[5] = 0; + if (m->interlaced) { + hw->CRTCEXT[0] = 0x80; + hw->CRTCEXT[5] = (hs + he - ht) >> 1; + if (!m->dblscan) + wd <<= 1; + vt &= ~1; + } + hw->CRTCEXT[0] |= (wd & 0x300) >> 4; + hw->CRTCEXT[1] = (((ht - 4) & 0x100) >> 8) | + ((hd & 0x100) >> 7) | /* blanking */ + ((hs & 0x100) >> 6) | /* sync start */ + (hbe & 0x040); /* end hor. blanking */ + /* FIXME: Enable vidrst only on G400, and only if TV-out is used */ + if (minfo->outputs[1].src == MATROXFB_SRC_CRTC1) + hw->CRTCEXT[1] |= 0x88; /* enable horizontal and vertical vidrst */ + hw->CRTCEXT[2] = ((vt & 0xC00) >> 10) | + ((vd & 0x400) >> 8) | /* disp end */ + ((vd & 0xC00) >> 7) | /* vblanking start */ + ((vs & 0xC00) >> 5) | + ((lc & 0x400) >> 3); + hw->CRTCEXT[3] = (divider - 1) | 0x80; + hw->CRTCEXT[4] = 0; + + hw->CRTC[0] = ht-4; + hw->CRTC[1] = hd; + hw->CRTC[2] = hd; + hw->CRTC[3] = (hbe & 0x1F) | 0x80; + hw->CRTC[4] = hs; + hw->CRTC[5] = ((hbe & 0x20) << 2) | (he & 0x1F); + hw->CRTC[6] = vt & 0xFF; + hw->CRTC[7] = ((vt & 0x100) >> 8) | + ((vd & 0x100) >> 7) | + ((vs & 0x100) >> 6) | + ((vd & 0x100) >> 5) | + ((lc & 0x100) >> 4) | + ((vt & 0x200) >> 4) | + ((vd & 0x200) >> 3) | + ((vs & 0x200) >> 2); + hw->CRTC[8] = 0x00; + hw->CRTC[9] = ((vd & 0x200) >> 4) | + ((lc & 0x200) >> 3); + if (m->dblscan && !m->interlaced) + hw->CRTC[9] |= 0x80; + for (i = 10; i < 16; i++) + hw->CRTC[i] = 0x00; + hw->CRTC[16] = vs /* & 0xFF */; + hw->CRTC[17] = (ve & 0x0F) | 0x20; + hw->CRTC[18] = vd /* & 0xFF */; + hw->CRTC[19] = wd /* & 0xFF */; + hw->CRTC[20] = 0x00; + hw->CRTC[21] = vd /* & 0xFF */; + hw->CRTC[22] = (vt + 1) /* & 0xFF */; + hw->CRTC[23] = 0xC3; + hw->CRTC[24] = lc; + return 0; +}; + +void matroxfb_vgaHWrestore(struct matrox_fb_info *minfo) +{ + int i; + struct matrox_hw_state * const hw = &minfo->hw; + CRITFLAGS + + DBG(__func__) + + dprintk(KERN_INFO "MiscOutReg: %02X\n", hw->MiscOutReg); + dprintk(KERN_INFO "SEQ regs: "); + for (i = 0; i < 5; i++) + dprintk("%02X:", hw->SEQ[i]); + dprintk("\n"); + dprintk(KERN_INFO "GDC regs: "); + for (i = 0; i < 9; i++) + dprintk("%02X:", hw->GCTL[i]); + dprintk("\n"); + dprintk(KERN_INFO "CRTC regs: "); + for (i = 0; i < 25; i++) + dprintk("%02X:", hw->CRTC[i]); + dprintk("\n"); + dprintk(KERN_INFO "ATTR regs: "); + for (i = 0; i < 21; i++) + dprintk("%02X:", hw->ATTR[i]); + dprintk("\n"); + + CRITBEGIN + + mga_inb(M_ATTR_RESET); + mga_outb(M_ATTR_INDEX, 0); + mga_outb(M_MISC_REG, hw->MiscOutReg); + for (i = 1; i < 5; i++) + mga_setr(M_SEQ_INDEX, i, hw->SEQ[i]); + mga_setr(M_CRTC_INDEX, 17, hw->CRTC[17] & 0x7F); + for (i = 0; i < 25; i++) + mga_setr(M_CRTC_INDEX, i, hw->CRTC[i]); + for (i = 0; i < 9; i++) + mga_setr(M_GRAPHICS_INDEX, i, hw->GCTL[i]); + for (i = 0; i < 21; i++) { + mga_inb(M_ATTR_RESET); + mga_outb(M_ATTR_INDEX, i); + mga_outb(M_ATTR_INDEX, hw->ATTR[i]); + } + mga_outb(M_PALETTE_MASK, 0xFF); + mga_outb(M_DAC_REG, 0x00); + for (i = 0; i < 768; i++) + mga_outb(M_DAC_VAL, hw->DACpal[i]); + mga_inb(M_ATTR_RESET); + mga_outb(M_ATTR_INDEX, 0x20); + + CRITEND +} + +static void get_pins(unsigned char __iomem* pins, struct matrox_bios* bd) { + unsigned int b0 = readb(pins); + + if (b0 == 0x2E && readb(pins+1) == 0x41) { + unsigned int pins_len = readb(pins+2); + unsigned int i; + unsigned char cksum; + unsigned char* dst = bd->pins; + + if (pins_len < 3 || pins_len > 128) { + return; + } + *dst++ = 0x2E; + *dst++ = 0x41; + *dst++ = pins_len; + cksum = 0x2E + 0x41 + pins_len; + for (i = 3; i < pins_len; i++) { + cksum += *dst++ = readb(pins+i); + } + if (cksum) { + return; + } + bd->pins_len = pins_len; + } else if (b0 == 0x40 && readb(pins+1) == 0x00) { + unsigned int i; + unsigned char* dst = bd->pins; + + *dst++ = 0x40; + *dst++ = 0; + for (i = 2; i < 0x40; i++) { + *dst++ = readb(pins+i); + } + bd->pins_len = 0x40; + } +} + +static void get_bios_version(unsigned char __iomem * vbios, struct matrox_bios* bd) { + unsigned int pcir_offset; + + pcir_offset = readb(vbios + 24) | (readb(vbios + 25) << 8); + if (pcir_offset >= 26 && pcir_offset < 0xFFE0 && + readb(vbios + pcir_offset ) == 'P' && + readb(vbios + pcir_offset + 1) == 'C' && + readb(vbios + pcir_offset + 2) == 'I' && + readb(vbios + pcir_offset + 3) == 'R') { + unsigned char h; + + h = readb(vbios + pcir_offset + 0x12); + bd->version.vMaj = (h >> 4) & 0xF; + bd->version.vMin = h & 0xF; + bd->version.vRev = readb(vbios + pcir_offset + 0x13); + } else { + unsigned char h; + + h = readb(vbios + 5); + bd->version.vMaj = (h >> 4) & 0xF; + bd->version.vMin = h & 0xF; + bd->version.vRev = 0; + } +} + +static void get_bios_output(unsigned char __iomem* vbios, struct matrox_bios* bd) { + unsigned char b; + + b = readb(vbios + 0x7FF1); + if (b == 0xFF) { + b = 0; + } + bd->output.state = b; +} + +static void get_bios_tvout(unsigned char __iomem* vbios, struct matrox_bios* bd) { + unsigned int i; + + /* Check for 'IBM .*(V....TVO' string - it means TVO BIOS */ + bd->output.tvout = 0; + if (readb(vbios + 0x1D) != 'I' || + readb(vbios + 0x1E) != 'B' || + readb(vbios + 0x1F) != 'M' || + readb(vbios + 0x20) != ' ') { + return; + } + for (i = 0x2D; i < 0x2D + 128; i++) { + unsigned char b = readb(vbios + i); + + if (b == '(' && readb(vbios + i + 1) == 'V') { + if (readb(vbios + i + 6) == 'T' && + readb(vbios + i + 7) == 'V' && + readb(vbios + i + 8) == 'O') { + bd->output.tvout = 1; + } + return; + } + if (b == 0) + break; + } +} + +static void parse_bios(unsigned char __iomem* vbios, struct matrox_bios* bd) { + unsigned int pins_offset; + + if (readb(vbios) != 0x55 || readb(vbios + 1) != 0xAA) { + return; + } + bd->bios_valid = 1; + get_bios_version(vbios, bd); + get_bios_output(vbios, bd); + get_bios_tvout(vbios, bd); +#if defined(__powerpc__) + /* On PowerPC cards, the PInS offset isn't stored at the end of the + * BIOS image. Instead, you must search the entire BIOS image for + * the magic PInS signature. + * + * This actually applies to all OpenFirmware base cards. Since these + * cards could be put in a MIPS or SPARC system, should the condition + * be something different? + */ + for ( pins_offset = 0 ; pins_offset <= 0xFF80 ; pins_offset++ ) { + unsigned char header[3]; + + header[0] = readb(vbios + pins_offset); + header[1] = readb(vbios + pins_offset + 1); + header[2] = readb(vbios + pins_offset + 2); + if ( (header[0] == 0x2E) && (header[1] == 0x41) + && ((header[2] == 0x40) || (header[2] == 0x80)) ) { + printk(KERN_INFO "PInS data found at offset %u\n", + pins_offset); + get_pins(vbios + pins_offset, bd); + break; + } + } +#else + pins_offset = readb(vbios + 0x7FFC) | (readb(vbios + 0x7FFD) << 8); + if (pins_offset <= 0xFF80) { + get_pins(vbios + pins_offset, bd); + } +#endif +} + +static int parse_pins1(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + unsigned int maxdac; + + switch (bd->pins[22]) { + case 0: maxdac = 175000; break; + case 1: maxdac = 220000; break; + default: maxdac = 240000; break; + } + if (get_unaligned_le16(bd->pins + 24)) { + maxdac = get_unaligned_le16(bd->pins + 24) * 10; + } + minfo->limits.pixel.vcomax = maxdac; + minfo->values.pll.system = get_unaligned_le16(bd->pins + 28) ? + get_unaligned_le16(bd->pins + 28) * 10 : 50000; + /* ignore 4MB, 8MB, module clocks */ + minfo->features.pll.ref_freq = 14318; + minfo->values.reg.mctlwtst = 0x00030101; + return 0; +} + +static void default_pins1(struct matrox_fb_info *minfo) +{ + /* Millennium */ + minfo->limits.pixel.vcomax = 220000; + minfo->values.pll.system = 50000; + minfo->features.pll.ref_freq = 14318; + minfo->values.reg.mctlwtst = 0x00030101; +} + +static int parse_pins2(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = (bd->pins[41] == 0xFF) ? 230000 : ((bd->pins[41] + 100) * 1000); + minfo->values.reg.mctlwtst = ((bd->pins[51] & 0x01) ? 0x00000001 : 0) | + ((bd->pins[51] & 0x02) ? 0x00000100 : 0) | + ((bd->pins[51] & 0x04) ? 0x00010000 : 0) | + ((bd->pins[51] & 0x08) ? 0x00020000 : 0); + minfo->values.pll.system = (bd->pins[43] == 0xFF) ? 50000 : ((bd->pins[43] + 100) * 1000); + minfo->features.pll.ref_freq = 14318; + return 0; +} + +static void default_pins2(struct matrox_fb_info *minfo) +{ + /* Millennium II, Mystique */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = 230000; + minfo->values.reg.mctlwtst = 0x00030101; + minfo->values.pll.system = 50000; + minfo->features.pll.ref_freq = 14318; +} + +static int parse_pins3(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = (bd->pins[36] == 0xFF) ? 230000 : ((bd->pins[36] + 100) * 1000); + minfo->values.reg.mctlwtst = get_unaligned_le32(bd->pins + 48) == 0xFFFFFFFF ? + 0x01250A21 : get_unaligned_le32(bd->pins + 48); + /* memory config */ + minfo->values.reg.memrdbk = ((bd->pins[57] << 21) & 0x1E000000) | + ((bd->pins[57] << 22) & 0x00C00000) | + ((bd->pins[56] << 1) & 0x000001E0) | + ( bd->pins[56] & 0x0000000F); + minfo->values.reg.opt = (bd->pins[54] & 7) << 10; + minfo->values.reg.opt2 = bd->pins[58] << 12; + minfo->features.pll.ref_freq = (bd->pins[52] & 0x20) ? 14318 : 27000; + return 0; +} + +static void default_pins3(struct matrox_fb_info *minfo) +{ + /* G100, G200 */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = 230000; + minfo->values.reg.mctlwtst = 0x01250A21; + minfo->values.reg.memrdbk = 0x00000000; + minfo->values.reg.opt = 0x00000C00; + minfo->values.reg.opt2 = 0x00000000; + minfo->features.pll.ref_freq = 27000; +} + +static int parse_pins4(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + minfo->limits.pixel.vcomax = (bd->pins[ 39] == 0xFF) ? 230000 : bd->pins[ 39] * 4000; + minfo->limits.system.vcomax = (bd->pins[ 38] == 0xFF) ? minfo->limits.pixel.vcomax : bd->pins[ 38] * 4000; + minfo->values.reg.mctlwtst = get_unaligned_le32(bd->pins + 71); + minfo->values.reg.memrdbk = ((bd->pins[87] << 21) & 0x1E000000) | + ((bd->pins[87] << 22) & 0x00C00000) | + ((bd->pins[86] << 1) & 0x000001E0) | + ( bd->pins[86] & 0x0000000F); + minfo->values.reg.opt = ((bd->pins[53] << 15) & 0x00400000) | + ((bd->pins[53] << 22) & 0x10000000) | + ((bd->pins[53] << 7) & 0x00001C00); + minfo->values.reg.opt3 = get_unaligned_le32(bd->pins + 67); + minfo->values.pll.system = (bd->pins[ 65] == 0xFF) ? 200000 : bd->pins[ 65] * 4000; + minfo->features.pll.ref_freq = (bd->pins[ 92] & 0x01) ? 14318 : 27000; + return 0; +} + +static void default_pins4(struct matrox_fb_info *minfo) +{ + /* G400 */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = 252000; + minfo->values.reg.mctlwtst = 0x04A450A1; + minfo->values.reg.memrdbk = 0x000000E7; + minfo->values.reg.opt = 0x10000400; + minfo->values.reg.opt3 = 0x0190A419; + minfo->values.pll.system = 200000; + minfo->features.pll.ref_freq = 27000; +} + +static int parse_pins5(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + unsigned int mult; + + mult = bd->pins[4]?8000:6000; + + minfo->limits.pixel.vcomax = (bd->pins[ 38] == 0xFF) ? 600000 : bd->pins[ 38] * mult; + minfo->limits.system.vcomax = (bd->pins[ 36] == 0xFF) ? minfo->limits.pixel.vcomax : bd->pins[ 36] * mult; + minfo->limits.video.vcomax = (bd->pins[ 37] == 0xFF) ? minfo->limits.system.vcomax : bd->pins[ 37] * mult; + minfo->limits.pixel.vcomin = (bd->pins[123] == 0xFF) ? 256000 : bd->pins[123] * mult; + minfo->limits.system.vcomin = (bd->pins[121] == 0xFF) ? minfo->limits.pixel.vcomin : bd->pins[121] * mult; + minfo->limits.video.vcomin = (bd->pins[122] == 0xFF) ? minfo->limits.system.vcomin : bd->pins[122] * mult; + minfo->values.pll.system = + minfo->values.pll.video = (bd->pins[ 92] == 0xFF) ? 284000 : bd->pins[ 92] * 4000; + minfo->values.reg.opt = get_unaligned_le32(bd->pins + 48); + minfo->values.reg.opt2 = get_unaligned_le32(bd->pins + 52); + minfo->values.reg.opt3 = get_unaligned_le32(bd->pins + 94); + minfo->values.reg.mctlwtst = get_unaligned_le32(bd->pins + 98); + minfo->values.reg.memmisc = get_unaligned_le32(bd->pins + 102); + minfo->values.reg.memrdbk = get_unaligned_le32(bd->pins + 106); + minfo->features.pll.ref_freq = (bd->pins[110] & 0x01) ? 14318 : 27000; + minfo->values.memory.ddr = (bd->pins[114] & 0x60) == 0x20; + minfo->values.memory.dll = (bd->pins[115] & 0x02) != 0; + minfo->values.memory.emrswen = (bd->pins[115] & 0x01) != 0; + minfo->values.reg.maccess = minfo->values.memory.emrswen ? 0x00004000 : 0x00000000; + if (bd->pins[115] & 4) { + minfo->values.reg.mctlwtst_core = minfo->values.reg.mctlwtst; + } else { + u_int32_t wtst_xlat[] = { 0, 1, 5, 6, 7, 5, 2, 3 }; + minfo->values.reg.mctlwtst_core = (minfo->values.reg.mctlwtst & ~7) | + wtst_xlat[minfo->values.reg.mctlwtst & 7]; + } + minfo->max_pixel_clock_panellink = bd->pins[47] * 4000; + return 0; +} + +static void default_pins5(struct matrox_fb_info *minfo) +{ + /* Mine 16MB G450 with SDRAM DDR */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = + minfo->limits.video.vcomax = 600000; + minfo->limits.pixel.vcomin = + minfo->limits.system.vcomin = + minfo->limits.video.vcomin = 256000; + minfo->values.pll.system = + minfo->values.pll.video = 284000; + minfo->values.reg.opt = 0x404A1160; + minfo->values.reg.opt2 = 0x0000AC00; + minfo->values.reg.opt3 = 0x0090A409; + minfo->values.reg.mctlwtst_core = + minfo->values.reg.mctlwtst = 0x0C81462B; + minfo->values.reg.memmisc = 0x80000004; + minfo->values.reg.memrdbk = 0x01001103; + minfo->features.pll.ref_freq = 27000; + minfo->values.memory.ddr = 1; + minfo->values.memory.dll = 1; + minfo->values.memory.emrswen = 1; + minfo->values.reg.maccess = 0x00004000; +} + +static int matroxfb_set_limits(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + unsigned int pins_version; + static const unsigned int pinslen[] = { 64, 64, 64, 128, 128 }; + + switch (minfo->chip) { + case MGA_2064: default_pins1(minfo); break; + case MGA_2164: + case MGA_1064: + case MGA_1164: default_pins2(minfo); break; + case MGA_G100: + case MGA_G200: default_pins3(minfo); break; + case MGA_G400: default_pins4(minfo); break; + case MGA_G450: + case MGA_G550: default_pins5(minfo); break; + } + if (!bd->bios_valid) { + printk(KERN_INFO "matroxfb: Your Matrox device does not have BIOS\n"); + return -1; + } + if (bd->pins_len < 64) { + printk(KERN_INFO "matroxfb: BIOS on your Matrox device does not contain powerup info\n"); + return -1; + } + if (bd->pins[0] == 0x2E && bd->pins[1] == 0x41) { + pins_version = bd->pins[5]; + if (pins_version < 2 || pins_version > 5) { + printk(KERN_INFO "matroxfb: Unknown version (%u) of powerup info\n", pins_version); + return -1; + } + } else { + pins_version = 1; + } + if (bd->pins_len != pinslen[pins_version - 1]) { + printk(KERN_INFO "matroxfb: Invalid powerup info\n"); + return -1; + } + switch (pins_version) { + case 1: + return parse_pins1(minfo, bd); + case 2: + return parse_pins2(minfo, bd); + case 3: + return parse_pins3(minfo, bd); + case 4: + return parse_pins4(minfo, bd); + case 5: + return parse_pins5(minfo, bd); + default: + printk(KERN_DEBUG "matroxfb: Powerup info version %u is not yet supported\n", pins_version); + return -1; + } +} + +void matroxfb_read_pins(struct matrox_fb_info *minfo) +{ + u32 opt; + u32 biosbase; + u32 fbbase; + struct pci_dev *pdev = minfo->pcidev; + + memset(&minfo->bios, 0, sizeof(minfo->bios)); + pci_read_config_dword(pdev, PCI_OPTION_REG, &opt); + pci_write_config_dword(pdev, PCI_OPTION_REG, opt | PCI_OPTION_ENABLE_ROM); + pci_read_config_dword(pdev, PCI_ROM_ADDRESS, &biosbase); + pci_read_config_dword(pdev, minfo->devflags.fbResource, &fbbase); + pci_write_config_dword(pdev, PCI_ROM_ADDRESS, (fbbase & PCI_ROM_ADDRESS_MASK) | PCI_ROM_ADDRESS_ENABLE); + parse_bios(vaddr_va(minfo->video.vbase), &minfo->bios); + pci_write_config_dword(pdev, PCI_ROM_ADDRESS, biosbase); + pci_write_config_dword(pdev, PCI_OPTION_REG, opt); +#ifdef CONFIG_X86 + if (!minfo->bios.bios_valid) { + unsigned char __iomem* b; + + b = ioremap(0x000C0000, 65536); + if (!b) { + printk(KERN_INFO "matroxfb: Unable to map legacy BIOS\n"); + } else { + unsigned int ven = readb(b+0x64+0) | (readb(b+0x64+1) << 8); + unsigned int dev = readb(b+0x64+2) | (readb(b+0x64+3) << 8); + + if (ven != pdev->vendor || dev != pdev->device) { + printk(KERN_INFO "matroxfb: Legacy BIOS is for %04X:%04X, while this device is %04X:%04X\n", + ven, dev, pdev->vendor, pdev->device); + } else { + parse_bios(b, &minfo->bios); + } + iounmap(b); + } + } +#endif + matroxfb_set_limits(minfo, &minfo->bios); + printk(KERN_INFO "PInS memtype = %u\n", + (minfo->values.reg.opt & 0x1C00) >> 10); +} + +EXPORT_SYMBOL(matroxfb_DAC_in); +EXPORT_SYMBOL(matroxfb_DAC_out); +EXPORT_SYMBOL(matroxfb_var2my); +EXPORT_SYMBOL(matroxfb_PLL_calcclock); +EXPORT_SYMBOL(matroxfb_vgaHWinit); /* DAC1064, Ti3026 */ +EXPORT_SYMBOL(matroxfb_vgaHWrestore); /* DAC1064, Ti3026 */ +EXPORT_SYMBOL(matroxfb_read_pins); + +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Miscellaneous support for Matrox video cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_misc.h b/drivers/video/fbdev/matrox/matroxfb_misc.h new file mode 100644 index 000000000000..351c823f1f74 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_misc.h @@ -0,0 +1,21 @@ +#ifndef __MATROXFB_MISC_H__ +#define __MATROXFB_MISC_H__ + +#include "matroxfb_base.h" + +/* also for modules */ +int matroxfb_PLL_calcclock(const struct matrox_pll_features* pll, unsigned int freq, unsigned int fmax, + unsigned int* in, unsigned int* feed, unsigned int* post); +static inline int PLL_calcclock(const struct matrox_fb_info *minfo, + unsigned int freq, unsigned int fmax, + unsigned int *in, unsigned int *feed, + unsigned int *post) +{ + return matroxfb_PLL_calcclock(&minfo->features.pll, freq, fmax, in, feed, post); +} + +int matroxfb_vgaHWinit(struct matrox_fb_info *minfo, struct my_timming* m); +void matroxfb_vgaHWrestore(struct matrox_fb_info *minfo); +void matroxfb_read_pins(struct matrox_fb_info *minfo); + +#endif /* __MATROXFB_MISC_H__ */ diff --git a/drivers/video/fbdev/maxinefb.c b/drivers/video/fbdev/maxinefb.c new file mode 100644 index 000000000000..5cf52d3c8e75 --- /dev/null +++ b/drivers/video/fbdev/maxinefb.c @@ -0,0 +1,177 @@ +/* + * linux/drivers/video/maxinefb.c + * + * DECstation 5000/xx onboard framebuffer support ... derived from: + * "HP300 Topcat framebuffer support (derived from macfb of all things) + * Phil Blundell <philb@gnu.org> 1998", the original code can be + * found in the file hpfb.c in the same directory. + * + * DECstation related code Copyright (C) 1999,2000,2001 by + * Michael Engel <engel@unix-ag.org> and + * Karsten Merker <merker@linuxtag.org>. + * 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. + * + */ + +/* + * Changes: + * 2001/01/27 removed debugging and testing code, fixed fb_ops + * initialization which had caused a crash before, + * general cleanup, first official release (KM) + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <video/maxinefb.h> + +/* bootinfo.h defines the machine type values, needed when checking */ +/* whether are really running on a maxine, KM */ +#include <asm/bootinfo.h> + +static struct fb_info fb_info; + +static struct fb_var_screeninfo maxinefb_defined = { + .xres = 1024, + .yres = 768, + .xres_virtual = 1024, + .yres_virtual = 768, + .bits_per_pixel =8, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo maxinefb_fix = { + .id = "Maxine", + .smem_len = (1024*768), + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .line_length = 1024, +}; + +/* Handle the funny Inmos RamDAC/video controller ... */ + +void maxinefb_ims332_write_register(int regno, register unsigned int val) +{ + register unsigned char *regs = (char *) MAXINEFB_IMS332_ADDRESS; + unsigned char *wptr; + + wptr = regs + 0xa0000 + (regno << 4); + *((volatile unsigned int *) (regs)) = (val >> 8) & 0xff00; + *((volatile unsigned short *) (wptr)) = val; +} + +unsigned int maxinefb_ims332_read_register(int regno) +{ + register unsigned char *regs = (char *) MAXINEFB_IMS332_ADDRESS; + unsigned char *rptr; + register unsigned int j, k; + + rptr = regs + 0x80000 + (regno << 4); + j = *((volatile unsigned short *) rptr); + k = *((volatile unsigned short *) regs); + + return (j & 0xffff) | ((k & 0xff00) << 8); +} + +/* Set the palette */ +static int maxinefb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + /* value to be written into the palette reg. */ + unsigned long hw_colorvalue = 0; + + if (regno > 255) + return 1; + + red >>= 8; /* The cmap fields are 16 bits */ + green >>= 8; /* wide, but the harware colormap */ + blue >>= 8; /* registers are only 8 bits wide */ + + hw_colorvalue = (blue << 16) + (green << 8) + (red); + + maxinefb_ims332_write_register(IMS332_REG_COLOR_PALETTE + regno, + hw_colorvalue); + return 0; +} + +static struct fb_ops maxinefb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = maxinefb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +int __init maxinefb_init(void) +{ + unsigned long fboff; + unsigned long fb_start; + int i; + + if (fb_get_options("maxinefb", NULL)) + return -ENODEV; + + /* Validate we're on the proper machine type */ + if (mips_machtype != MACH_DS5000_XX) { + return -EINVAL; + } + + printk(KERN_INFO "Maxinefb: Personal DECstation detected\n"); + printk(KERN_INFO "Maxinefb: initializing onboard framebuffer\n"); + + /* Framebuffer display memory base address */ + fb_start = DS5000_xx_ONBOARD_FBMEM_START; + + /* Clear screen */ + for (fboff = fb_start; fboff < fb_start + 0x1ffff; fboff++) + *(volatile unsigned char *)fboff = 0x0; + + maxinefb_fix.smem_start = fb_start; + + /* erase hardware cursor */ + for (i = 0; i < 512; i++) { + maxinefb_ims332_write_register(IMS332_REG_CURSOR_RAM + i, + 0); + /* + if (i&0x8 == 0) + maxinefb_ims332_write_register (IMS332_REG_CURSOR_RAM + i, 0x0f); + else + maxinefb_ims332_write_register (IMS332_REG_CURSOR_RAM + i, 0xf0); + */ + } + + fb_info.fbops = &maxinefb_ops; + fb_info.screen_base = (char *)maxinefb_fix.smem_start; + fb_info.var = maxinefb_defined; + fb_info.fix = maxinefb_fix; + fb_info.flags = FBINFO_DEFAULT; + + fb_alloc_cmap(&fb_info.cmap, 256, 0); + + if (register_framebuffer(&fb_info) < 0) + return 1; + return 0; +} + +static void __exit maxinefb_exit(void) +{ + unregister_framebuffer(&fb_info); +} + +#ifdef MODULE +MODULE_LICENSE("GPL"); +#endif +module_init(maxinefb_init); +module_exit(maxinefb_exit); + diff --git a/drivers/video/fbdev/mb862xx/Makefile b/drivers/video/fbdev/mb862xx/Makefile new file mode 100644 index 000000000000..5707ed0e31a7 --- /dev/null +++ b/drivers/video/fbdev/mb862xx/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the MB862xx framebuffer driver +# + +obj-$(CONFIG_FB_MB862XX) += mb862xxfb.o + +mb862xxfb-y := mb862xxfbdrv.o mb862xxfb_accel.o +mb862xxfb-$(CONFIG_FB_MB862XX_I2C) += mb862xx-i2c.o diff --git a/drivers/video/fbdev/mb862xx/mb862xx-i2c.c b/drivers/video/fbdev/mb862xx/mb862xx-i2c.c new file mode 100644 index 000000000000..c87e17afb3e2 --- /dev/null +++ b/drivers/video/fbdev/mb862xx/mb862xx-i2c.c @@ -0,0 +1,179 @@ +/* + * Coral-P(A)/Lime I2C adapter driver + * + * (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de> + * + * 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. + * + */ + +#include <linux/fb.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/export.h> + +#include "mb862xxfb.h" +#include "mb862xx_reg.h" + +static int mb862xx_i2c_wait_event(struct i2c_adapter *adap) +{ + struct mb862xxfb_par *par = adap->algo_data; + u32 reg; + + do { + udelay(10); + reg = inreg(i2c, GC_I2C_BCR); + if (reg & (I2C_INT | I2C_BER)) + break; + } while (1); + + return (reg & I2C_BER) ? 0 : 1; +} + +static int mb862xx_i2c_do_address(struct i2c_adapter *adap, int addr) +{ + struct mb862xxfb_par *par = adap->algo_data; + + outreg(i2c, GC_I2C_DAR, addr); + outreg(i2c, GC_I2C_CCR, I2C_CLOCK_AND_ENABLE); + outreg(i2c, GC_I2C_BCR, par->i2c_rs ? I2C_REPEATED_START : I2C_START); + if (!mb862xx_i2c_wait_event(adap)) + return -EIO; + par->i2c_rs = !(inreg(i2c, GC_I2C_BSR) & I2C_LRB); + return par->i2c_rs; +} + +static int mb862xx_i2c_write_byte(struct i2c_adapter *adap, u8 byte) +{ + struct mb862xxfb_par *par = adap->algo_data; + + outreg(i2c, GC_I2C_DAR, byte); + outreg(i2c, GC_I2C_BCR, I2C_START); + if (!mb862xx_i2c_wait_event(adap)) + return -EIO; + return !(inreg(i2c, GC_I2C_BSR) & I2C_LRB); +} + +static int mb862xx_i2c_read_byte(struct i2c_adapter *adap, u8 *byte, int last) +{ + struct mb862xxfb_par *par = adap->algo_data; + + outreg(i2c, GC_I2C_BCR, I2C_START | (last ? 0 : I2C_ACK)); + if (!mb862xx_i2c_wait_event(adap)) + return 0; + *byte = inreg(i2c, GC_I2C_DAR); + return 1; +} + +static void mb862xx_i2c_stop(struct i2c_adapter *adap) +{ + struct mb862xxfb_par *par = adap->algo_data; + + outreg(i2c, GC_I2C_BCR, I2C_STOP); + outreg(i2c, GC_I2C_CCR, I2C_DISABLE); + par->i2c_rs = 0; +} + +static int mb862xx_i2c_read(struct i2c_adapter *adap, struct i2c_msg *m) +{ + int i, ret = 0; + int last = m->len - 1; + + for (i = 0; i < m->len; i++) { + if (!mb862xx_i2c_read_byte(adap, &m->buf[i], i == last)) { + ret = -EIO; + break; + } + } + return ret; +} + +static int mb862xx_i2c_write(struct i2c_adapter *adap, struct i2c_msg *m) +{ + int i, ret = 0; + + for (i = 0; i < m->len; i++) { + if (!mb862xx_i2c_write_byte(adap, m->buf[i])) { + ret = -EIO; + break; + } + } + return ret; +} + +static int mb862xx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct mb862xxfb_par *par = adap->algo_data; + struct i2c_msg *m; + int addr; + int i = 0, err = 0; + + dev_dbg(par->dev, "%s: %d msgs\n", __func__, num); + + for (i = 0; i < num; i++) { + m = &msgs[i]; + if (!m->len) { + dev_dbg(par->dev, "%s: null msgs\n", __func__); + continue; + } + addr = m->addr; + if (m->flags & I2C_M_RD) + addr |= 1; + + err = mb862xx_i2c_do_address(adap, addr); + if (err < 0) + break; + if (m->flags & I2C_M_RD) + err = mb862xx_i2c_read(adap, m); + else + err = mb862xx_i2c_write(adap, m); + } + + if (i) + mb862xx_i2c_stop(adap); + + return (err < 0) ? err : i; +} + +static u32 mb862xx_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_BYTE_DATA; +} + +static const struct i2c_algorithm mb862xx_algo = { + .master_xfer = mb862xx_xfer, + .functionality = mb862xx_func, +}; + +static struct i2c_adapter mb862xx_i2c_adapter = { + .name = "MB862xx I2C adapter", + .algo = &mb862xx_algo, + .owner = THIS_MODULE, +}; + +int mb862xx_i2c_init(struct mb862xxfb_par *par) +{ + int ret; + + mb862xx_i2c_adapter.algo_data = par; + par->adap = &mb862xx_i2c_adapter; + + ret = i2c_add_adapter(par->adap); + if (ret < 0) { + dev_err(par->dev, "failed to add %s\n", + mb862xx_i2c_adapter.name); + } + return ret; +} + +void mb862xx_i2c_exit(struct mb862xxfb_par *par) +{ + if (par->adap) { + i2c_del_adapter(par->adap); + par->adap = NULL; + } +} diff --git a/drivers/video/fbdev/mb862xx/mb862xx_reg.h b/drivers/video/fbdev/mb862xx/mb862xx_reg.h new file mode 100644 index 000000000000..9df48b8edc94 --- /dev/null +++ b/drivers/video/fbdev/mb862xx/mb862xx_reg.h @@ -0,0 +1,188 @@ +/* + * Fujitsu MB862xx Graphics Controller Registers/Bits + */ + +#ifndef _MB862XX_REG_H +#define _MB862XX_REG_H + +#define MB862XX_MMIO_BASE 0x01fc0000 +#define MB862XX_MMIO_HIGH_BASE 0x03fc0000 +#define MB862XX_I2C_BASE 0x0000c000 +#define MB862XX_DISP_BASE 0x00010000 +#define MB862XX_CAP_BASE 0x00018000 +#define MB862XX_DRAW_BASE 0x00030000 +#define MB862XX_GEO_BASE 0x00038000 +#define MB862XX_PIO_BASE 0x00038000 +#define MB862XX_MMIO_SIZE 0x40000 + +/* Host interface/pio registers */ +#define GC_IST 0x00000020 +#define GC_IMASK 0x00000024 +#define GC_SRST 0x0000002c +#define GC_CCF 0x00000038 +#define GC_RSW 0x0000005c +#define GC_CID 0x000000f0 +#define GC_REVISION 0x00000084 + +#define GC_CCF_CGE_100 0x00000000 +#define GC_CCF_CGE_133 0x00040000 +#define GC_CCF_CGE_166 0x00080000 +#define GC_CCF_COT_100 0x00000000 +#define GC_CCF_COT_133 0x00010000 +#define GC_CID_CNAME_MSK 0x0000ff00 +#define GC_CID_VERSION_MSK 0x000000ff + +/* define enabled interrupts hereby */ +#define GC_INT_EN 0x00000000 + +/* Memory interface mode register */ +#define GC_MMR 0x0000fffc + +/* Display Controller registers */ +#define GC_DCM0 0x00000000 +#define GC_HTP 0x00000004 +#define GC_HDB_HDP 0x00000008 +#define GC_VSW_HSW_HSP 0x0000000c +#define GC_VTR 0x00000010 +#define GC_VDP_VSP 0x00000014 +#define GC_WY_WX 0x00000018 +#define GC_WH_WW 0x0000001c +#define GC_L0M 0x00000020 +#define GC_L0OA0 0x00000024 +#define GC_L0DA0 0x00000028 +#define GC_L0DY_L0DX 0x0000002c +#define GC_L1M 0x00000030 +#define GC_L1DA 0x00000034 +#define GC_DCM1 0x00000100 +#define GC_L0EM 0x00000110 +#define GC_L0WY_L0WX 0x00000114 +#define GC_L0WH_L0WW 0x00000118 +#define GC_L1EM 0x00000120 +#define GC_L1WY_L1WX 0x00000124 +#define GC_L1WH_L1WW 0x00000128 +#define GC_DLS 0x00000180 +#define GC_DCM2 0x00000104 +#define GC_DCM3 0x00000108 +#define GC_CPM_CUTC 0x000000a0 +#define GC_CUOA0 0x000000a4 +#define GC_CUY0_CUX0 0x000000a8 +#define GC_CUOA1 0x000000ac +#define GC_CUY1_CUX1 0x000000b0 +#define GC_L0PAL0 0x00000400 + +#define GC_CPM_CEN0 0x00100000 +#define GC_CPM_CEN1 0x00200000 +#define GC_DCM1_DEN 0x80000000 +#define GC_DCM1_L1E 0x00020000 +#define GC_L1M_16 0x80000000 +#define GC_L1M_YC 0x40000000 +#define GC_L1M_CS 0x20000000 + +#define GC_DCM01_ESY 0x00000004 +#define GC_DCM01_SC 0x00003f00 +#define GC_DCM01_RESV 0x00004000 +#define GC_DCM01_CKS 0x00008000 +#define GC_DCM01_L0E 0x00010000 +#define GC_DCM01_DEN 0x80000000 +#define GC_L0M_L0C_8 0x00000000 +#define GC_L0M_L0C_16 0x80000000 +#define GC_L0EM_L0EC_24 0x40000000 +#define GC_L0M_L0W_UNIT 64 +#define GC_L1EM_DM 0x02000000 + +#define GC_DISP_REFCLK_400 400 + +/* I2C */ +#define GC_I2C_BSR 0x00000000 /* BSR */ +#define GC_I2C_BCR 0x00000004 /* BCR */ +#define GC_I2C_CCR 0x00000008 /* CCR */ +#define GC_I2C_ADR 0x0000000C /* ADR */ +#define GC_I2C_DAR 0x00000010 /* DAR */ + +#define I2C_DISABLE 0x00000000 +#define I2C_STOP 0x00000000 +#define I2C_START 0x00000010 +#define I2C_REPEATED_START 0x00000030 +#define I2C_CLOCK_AND_ENABLE 0x0000003f +#define I2C_READY 0x01 +#define I2C_INT 0x01 +#define I2C_INTE 0x02 +#define I2C_ACK 0x08 +#define I2C_BER 0x80 +#define I2C_BEIE 0x40 +#define I2C_TRX 0x80 +#define I2C_LRB 0x10 + +/* Capture registers and bits */ +#define GC_CAP_VCM 0x00000000 +#define GC_CAP_CSC 0x00000004 +#define GC_CAP_VCS 0x00000008 +#define GC_CAP_CBM 0x00000010 +#define GC_CAP_CBOA 0x00000014 +#define GC_CAP_CBLA 0x00000018 +#define GC_CAP_IMG_START 0x0000001C +#define GC_CAP_IMG_END 0x00000020 +#define GC_CAP_CMSS 0x00000048 +#define GC_CAP_CMDS 0x0000004C + +#define GC_VCM_VIE 0x80000000 +#define GC_VCM_CM 0x03000000 +#define GC_VCM_VS_PAL 0x00000002 +#define GC_CBM_OO 0x80000000 +#define GC_CBM_HRV 0x00000010 +#define GC_CBM_CBST 0x00000001 + +/* Carmine specific */ +#define MB86297_DRAW_BASE 0x00020000 +#define MB86297_DISP0_BASE 0x00100000 +#define MB86297_DISP1_BASE 0x00140000 +#define MB86297_WRBACK_BASE 0x00180000 +#define MB86297_CAP0_BASE 0x00200000 +#define MB86297_CAP1_BASE 0x00280000 +#define MB86297_DRAMCTRL_BASE 0x00300000 +#define MB86297_CTRL_BASE 0x00400000 +#define MB86297_I2C_BASE 0x00500000 + +#define GC_CTRL_STATUS 0x00000000 +#define GC_CTRL_INT_MASK 0x00000004 +#define GC_CTRL_CLK_ENABLE 0x0000000c +#define GC_CTRL_SOFT_RST 0x00000010 + +#define GC_CTRL_CLK_EN_DRAM 0x00000001 +#define GC_CTRL_CLK_EN_2D3D 0x00000002 +#define GC_CTRL_CLK_EN_DISP0 0x00000020 +#define GC_CTRL_CLK_EN_DISP1 0x00000040 + +#define GC_2D3D_REV 0x000004b4 +#define GC_RE_REVISION 0x24240200 + +/* define enabled interrupts hereby */ +#define GC_CARMINE_INT_EN 0x00000004 + +/* DRAM controller */ +#define GC_DCTL_MODE_ADD 0x00000000 +#define GC_DCTL_SETTIME1_EMODE 0x00000004 +#define GC_DCTL_REFRESH_SETTIME2 0x00000008 +#define GC_DCTL_RSV0_STATES 0x0000000C +#define GC_DCTL_RSV2_RSV1 0x00000010 +#define GC_DCTL_DDRIF2_DDRIF1 0x00000014 +#define GC_DCTL_IOCONT1_IOCONT0 0x00000024 + +#define GC_DCTL_STATES_MSK 0x0000000f +#define GC_DCTL_INIT_WAIT_CNT 3000 +#define GC_DCTL_INIT_WAIT_INTERVAL 1 + +/* DRAM ctrl values for Carmine PCI Eval. board */ +#define GC_EVB_DCTL_MODE_ADD 0x012105c3 +#define GC_EVB_DCTL_MODE_ADD_AFT_RST 0x002105c3 +#define GC_EVB_DCTL_SETTIME1_EMODE 0x47498000 +#define GC_EVB_DCTL_REFRESH_SETTIME2 0x00422a22 +#define GC_EVB_DCTL_RSV0_STATES 0x00200003 +#define GC_EVB_DCTL_RSV0_STATES_AFT_RST 0x00200002 +#define GC_EVB_DCTL_RSV2_RSV1 0x0000000f +#define GC_EVB_DCTL_DDRIF2_DDRIF1 0x00556646 +#define GC_EVB_DCTL_IOCONT1_IOCONT0 0x05550555 + +#define GC_DISP_REFCLK_533 533 + +#endif diff --git a/drivers/video/fbdev/mb862xx/mb862xxfb.h b/drivers/video/fbdev/mb862xx/mb862xxfb.h new file mode 100644 index 000000000000..8550630c1e01 --- /dev/null +++ b/drivers/video/fbdev/mb862xx/mb862xxfb.h @@ -0,0 +1,121 @@ +#ifndef __MB862XX_H__ +#define __MB862XX_H__ + +struct mb862xx_l1_cfg { + unsigned short sx; + unsigned short sy; + unsigned short sw; + unsigned short sh; + unsigned short dx; + unsigned short dy; + unsigned short dw; + unsigned short dh; + int mirror; +}; + +#define MB862XX_BASE 'M' +#define MB862XX_L1_GET_CFG _IOR(MB862XX_BASE, 0, struct mb862xx_l1_cfg*) +#define MB862XX_L1_SET_CFG _IOW(MB862XX_BASE, 1, struct mb862xx_l1_cfg*) +#define MB862XX_L1_ENABLE _IOW(MB862XX_BASE, 2, int) +#define MB862XX_L1_CAP_CTL _IOW(MB862XX_BASE, 3, int) + +#ifdef __KERNEL__ + +#define PCI_VENDOR_ID_FUJITSU_LIMITED 0x10cf +#define PCI_DEVICE_ID_FUJITSU_CORALP 0x2019 +#define PCI_DEVICE_ID_FUJITSU_CORALPA 0x201e +#define PCI_DEVICE_ID_FUJITSU_CARMINE 0x202b + +#define GC_MMR_CORALP_EVB_VAL 0x11d7fa13 + +enum gdctype { + BT_NONE, + BT_LIME, + BT_MINT, + BT_CORAL, + BT_CORALP, + BT_CARMINE, +}; + +struct mb862xx_gc_mode { + struct fb_videomode def_mode; /* mode of connected display */ + unsigned int def_bpp; /* default depth */ + unsigned long max_vram; /* connected SDRAM size */ + unsigned long ccf; /* gdc clk */ + unsigned long mmr; /* memory mode for SDRAM */ +}; + +/* private data */ +struct mb862xxfb_par { + struct fb_info *info; /* fb info head */ + struct device *dev; + struct pci_dev *pdev; + struct resource *res; /* framebuffer/mmio resource */ + + resource_size_t fb_base_phys; /* fb base, 36-bit PPC440EPx */ + resource_size_t mmio_base_phys; /* io base addr */ + void __iomem *fb_base; /* remapped framebuffer */ + void __iomem *mmio_base; /* remapped registers */ + size_t mapped_vram; /* length of remapped vram */ + size_t mmio_len; /* length of register region */ + unsigned long cap_buf; /* capture buffers offset */ + size_t cap_len; /* length of capture buffers */ + + void __iomem *host; /* relocatable reg. bases */ + void __iomem *i2c; + void __iomem *disp; + void __iomem *disp1; + void __iomem *cap; + void __iomem *cap1; + void __iomem *draw; + void __iomem *geo; + void __iomem *pio; + void __iomem *ctrl; + void __iomem *dram_ctrl; + void __iomem *wrback; + + unsigned int irq; + unsigned int type; /* GDC type */ + unsigned int refclk; /* disp. reference clock */ + struct mb862xx_gc_mode *gc_mode; /* GDC mode init data */ + int pre_init; /* don't init display if 1 */ + struct i2c_adapter *adap; /* GDC I2C bus adapter */ + int i2c_rs; + + struct mb862xx_l1_cfg l1_cfg; + int l1_stride; + + u32 pseudo_palette[16]; +}; + +extern void mb862xxfb_init_accel(struct fb_info *info, int xres); +#ifdef CONFIG_FB_MB862XX_I2C +extern int mb862xx_i2c_init(struct mb862xxfb_par *par); +extern void mb862xx_i2c_exit(struct mb862xxfb_par *par); +#else +static inline int mb862xx_i2c_init(struct mb862xxfb_par *par) { return 0; } +static inline void mb862xx_i2c_exit(struct mb862xxfb_par *par) { } +#endif + +#if defined(CONFIG_FB_MB862XX_LIME) && defined(CONFIG_FB_MB862XX_PCI_GDC) +#error "Select Lime GDC or CoralP/Carmine support, but not both together" +#endif +#if defined(CONFIG_FB_MB862XX_LIME) +#define gdc_read __raw_readl +#define gdc_write __raw_writel +#else +#define gdc_read readl +#define gdc_write writel +#endif + +#define inreg(type, off) \ + gdc_read((par->type + (off))) + +#define outreg(type, off, val) \ + gdc_write((val), (par->type + (off))) + +#define pack(a, b) (((a) << 16) | (b)) + +#endif /* __KERNEL__ */ + +#endif diff --git a/drivers/video/fbdev/mb862xx/mb862xxfb_accel.c b/drivers/video/fbdev/mb862xx/mb862xxfb_accel.c new file mode 100644 index 000000000000..fe92eed6da70 --- /dev/null +++ b/drivers/video/fbdev/mb862xx/mb862xxfb_accel.c @@ -0,0 +1,335 @@ +/* + * drivers/mb862xx/mb862xxfb_accel.c + * + * Fujitsu Carmine/Coral-P(A)/Lime framebuffer driver acceleration support + * + * (C) 2007 Alexander Shishkin <virtuoso@slind.org> + * (C) 2009 Valentin Sitdikov <v.sitdikov@gmail.com> + * (C) 2009 Siemens AG + * + * 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. + * + */ +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#if defined(CONFIG_OF) +#include <linux/of_platform.h> +#endif +#include "mb862xxfb.h" +#include "mb862xx_reg.h" +#include "mb862xxfb_accel.h" + +static void mb862xxfb_write_fifo(u32 count, u32 *data, struct fb_info *info) +{ + struct mb862xxfb_par *par = info->par; + static u32 free; + + u32 total = 0; + while (total < count) { + if (free) { + outreg(geo, GDC_GEO_REG_INPUT_FIFO, data[total]); + total++; + free--; + } else { + free = (u32) inreg(draw, GDC_REG_FIFO_COUNT); + } + } +} + +static void mb86290fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + __u32 cmd[6]; + + cmd[0] = (GDC_TYPE_SETREGISTER << 24) | (1 << 16) | GDC_REG_MODE_BITMAP; + /* Set raster operation */ + cmd[1] = (2 << 7) | (GDC_ROP_COPY << 9); + cmd[2] = GDC_TYPE_BLTCOPYP << 24; + + if (area->sx >= area->dx && area->sy >= area->dy) + cmd[2] |= GDC_CMD_BLTCOPY_TOP_LEFT << 16; + else if (area->sx >= area->dx && area->sy <= area->dy) + cmd[2] |= GDC_CMD_BLTCOPY_BOTTOM_LEFT << 16; + else if (area->sx <= area->dx && area->sy >= area->dy) + cmd[2] |= GDC_CMD_BLTCOPY_TOP_RIGHT << 16; + else + cmd[2] |= GDC_CMD_BLTCOPY_BOTTOM_RIGHT << 16; + + cmd[3] = (area->sy << 16) | area->sx; + cmd[4] = (area->dy << 16) | area->dx; + cmd[5] = (area->height << 16) | area->width; + mb862xxfb_write_fifo(6, cmd, info); +} + +/* + * Fill in the cmd array /GDC FIFO commands/ to draw a 1bit image. + * Make sure cmd has enough room! + */ +static void mb86290fb_imageblit1(u32 *cmd, u16 step, u16 dx, u16 dy, + u16 width, u16 height, u32 fgcolor, + u32 bgcolor, const struct fb_image *image, + struct fb_info *info) +{ + int i; + unsigned const char *line; + u16 bytes; + + /* set colors and raster operation regs */ + cmd[0] = (GDC_TYPE_SETREGISTER << 24) | (1 << 16) | GDC_REG_MODE_BITMAP; + /* Set raster operation */ + cmd[1] = (2 << 7) | (GDC_ROP_COPY << 9); + cmd[2] = + (GDC_TYPE_SETCOLORREGISTER << 24) | (GDC_CMD_BODY_FORE_COLOR << 16); + cmd[3] = fgcolor; + cmd[4] = + (GDC_TYPE_SETCOLORREGISTER << 24) | (GDC_CMD_BODY_BACK_COLOR << 16); + cmd[5] = bgcolor; + + i = 0; + line = image->data; + bytes = (image->width + 7) >> 3; + + /* and the image */ + cmd[6] = (GDC_TYPE_DRAWBITMAPP << 24) | + (GDC_CMD_BITMAP << 16) | (2 + (step * height)); + cmd[7] = (dy << 16) | dx; + cmd[8] = (height << 16) | width; + + while (i < height) { + memcpy(&cmd[9 + i * step], line, step << 2); +#ifdef __LITTLE_ENDIAN + { + int k = 0; + for (k = 0; k < step; k++) + cmd[9 + i * step + k] = + cpu_to_be32(cmd[9 + i * step + k]); + } +#endif + line += bytes; + i++; + } +} + +/* + * Fill in the cmd array /GDC FIFO commands/ to draw a 8bit image. + * Make sure cmd has enough room! + */ +static void mb86290fb_imageblit8(u32 *cmd, u16 step, u16 dx, u16 dy, + u16 width, u16 height, u32 fgcolor, + u32 bgcolor, const struct fb_image *image, + struct fb_info *info) +{ + int i, j; + unsigned const char *line, *ptr; + u16 bytes; + + cmd[0] = (GDC_TYPE_DRAWBITMAPP << 24) | + (GDC_CMD_BLT_DRAW << 16) | (2 + (height * step)); + cmd[1] = (dy << 16) | dx; + cmd[2] = (height << 16) | width; + + i = 0; + line = ptr = image->data; + bytes = image->width; + + while (i < height) { + ptr = line; + for (j = 0; j < step; j++) { + cmd[3 + i * step + j] = + (((u32 *) (info->pseudo_palette))[*ptr]) & 0xffff; + ptr++; + cmd[3 + i * step + j] |= + ((((u32 *) (info-> + pseudo_palette))[*ptr]) & 0xffff) << 16; + ptr++; + } + + line += bytes; + i++; + } +} + +/* + * Fill in the cmd array /GDC FIFO commands/ to draw a 16bit image. + * Make sure cmd has enough room! + */ +static void mb86290fb_imageblit16(u32 *cmd, u16 step, u16 dx, u16 dy, + u16 width, u16 height, u32 fgcolor, + u32 bgcolor, const struct fb_image *image, + struct fb_info *info) +{ + int i; + unsigned const char *line; + u16 bytes; + + i = 0; + line = image->data; + bytes = image->width << 1; + + cmd[0] = (GDC_TYPE_DRAWBITMAPP << 24) | + (GDC_CMD_BLT_DRAW << 16) | (2 + step * height); + cmd[1] = (dy << 16) | dx; + cmd[2] = (height << 16) | width; + + while (i < height) { + memcpy(&cmd[3 + i * step], line, step); + line += bytes; + i++; + } +} + +static void mb86290fb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + int mdr; + u32 *cmd = NULL; + void (*cmdfn) (u32 *, u16, u16, u16, u16, u16, u32, u32, + const struct fb_image *, struct fb_info *) = NULL; + u32 cmdlen; + u32 fgcolor = 0, bgcolor = 0; + u16 step; + + u16 width = image->width, height = image->height; + u16 dx = image->dx, dy = image->dy; + int x2, y2, vxres, vyres; + + mdr = (GDC_ROP_COPY << 9); + x2 = image->dx + image->width; + y2 = image->dy + image->height; + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + x2 = min(x2, vxres); + y2 = min(y2, vyres); + width = x2 - dx; + height = y2 - dy; + + switch (image->depth) { + case 1: + step = (width + 31) >> 5; + cmdlen = 9 + height * step; + cmdfn = mb86290fb_imageblit1; + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + fgcolor = + ((u32 *) (info->pseudo_palette))[image->fg_color]; + bgcolor = + ((u32 *) (info->pseudo_palette))[image->bg_color]; + } else { + fgcolor = image->fg_color; + bgcolor = image->bg_color; + } + + break; + + case 8: + step = (width + 1) >> 1; + cmdlen = 3 + height * step; + cmdfn = mb86290fb_imageblit8; + break; + + case 16: + step = (width + 1) >> 1; + cmdlen = 3 + height * step; + cmdfn = mb86290fb_imageblit16; + break; + + default: + cfb_imageblit(info, image); + return; + } + + cmd = kmalloc(cmdlen * 4, GFP_DMA); + if (!cmd) + return cfb_imageblit(info, image); + cmdfn(cmd, step, dx, dy, width, height, fgcolor, bgcolor, image, info); + mb862xxfb_write_fifo(cmdlen, cmd, info); + kfree(cmd); +} + +static void mb86290fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + + u32 x2, y2, vxres, vyres, height, width, fg; + u32 cmd[7]; + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if (!rect->width || !rect->height || rect->dx > vxres + || rect->dy > vyres) + return; + + /* We could use hardware clipping but on many cards you get around + * hardware clipping by writing to framebuffer directly. */ + x2 = rect->dx + rect->width; + y2 = rect->dy + rect->height; + x2 = min(x2, vxres); + y2 = min(y2, vyres); + width = x2 - rect->dx; + height = y2 - rect->dy; + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) + fg = ((u32 *) (info->pseudo_palette))[rect->color]; + else + fg = rect->color; + + switch (rect->rop) { + + case ROP_XOR: + /* Set raster operation */ + cmd[1] = (2 << 7) | (GDC_ROP_XOR << 9); + break; + + case ROP_COPY: + /* Set raster operation */ + cmd[1] = (2 << 7) | (GDC_ROP_COPY << 9); + break; + + } + + cmd[0] = (GDC_TYPE_SETREGISTER << 24) | (1 << 16) | GDC_REG_MODE_BITMAP; + /* cmd[1] set earlier */ + cmd[2] = + (GDC_TYPE_SETCOLORREGISTER << 24) | (GDC_CMD_BODY_FORE_COLOR << 16); + cmd[3] = fg; + cmd[4] = (GDC_TYPE_DRAWRECTP << 24) | (GDC_CMD_BLT_FILL << 16); + cmd[5] = (rect->dy << 16) | (rect->dx); + cmd[6] = (height << 16) | width; + + mb862xxfb_write_fifo(7, cmd, info); +} + +void mb862xxfb_init_accel(struct fb_info *info, int xres) +{ + struct mb862xxfb_par *par = info->par; + + if (info->var.bits_per_pixel == 32) { + info->fbops->fb_fillrect = cfb_fillrect; + info->fbops->fb_copyarea = cfb_copyarea; + info->fbops->fb_imageblit = cfb_imageblit; + } else { + outreg(disp, GC_L0EM, 3); + info->fbops->fb_fillrect = mb86290fb_fillrect; + info->fbops->fb_copyarea = mb86290fb_copyarea; + info->fbops->fb_imageblit = mb86290fb_imageblit; + } + outreg(draw, GDC_REG_DRAW_BASE, 0); + outreg(draw, GDC_REG_MODE_MISC, 0x8000); + outreg(draw, GDC_REG_X_RESOLUTION, xres); + + info->flags |= + FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_IMAGEBLIT; + info->fix.accel = 0xff; /*FIXME: add right define */ +} +EXPORT_SYMBOL(mb862xxfb_init_accel); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/mb862xx/mb862xxfb_accel.h b/drivers/video/fbdev/mb862xx/mb862xxfb_accel.h new file mode 100644 index 000000000000..96a2dfef0f60 --- /dev/null +++ b/drivers/video/fbdev/mb862xx/mb862xxfb_accel.h @@ -0,0 +1,203 @@ +#ifndef __MB826XXFB_ACCEL_H__ +#define __MB826XXFB_ACCEL_H__ + +/* registers */ +#define GDC_GEO_REG_INPUT_FIFO 0x00000400L + +/* Special Registers */ +#define GDC_REG_CTRL 0x00000400L +#define GDC_REG_FIFO_STATUS 0x00000404L +#define GDC_REG_FIFO_COUNT 0x00000408L +#define GDC_REG_SETUP_STATUS 0x0000040CL +#define GDC_REG_DDA_STATUS 0x00000410L +#define GDC_REG_ENGINE_STATUS 0x00000414L +#define GDC_REG_ERROR_STATUS 0x00000418L +#define GDC_REG_MODE_MISC 0x00000420L /* MDR0 */ +#define GDC_REG_MODE_LINE 0x00000424L /* MDR1 */ +#define GDC_REG_MODE_POLYGON 0x00000428L /* MDR2 */ +#define GDC_REG_MODE_TEXTURE 0x0000042CL /* MDR3 */ +#define GDC_REG_MODE_BITMAP 0x00000430L /* MDR4 */ +#define GDC_REG_MODE_EXTENSION 0x0000043CL /* MDR7 */ + +/* Configuration Registers */ +#define GDC_REG_DRAW_BASE 0x00000440L +#define GDC_REG_X_RESOLUTION 0x00000444L +#define GDC_REG_Z_BASE 0x00000448L +#define GDC_REG_TEXTURE_BASE 0x0000044CL +#define GDC_REG_POLYGON_FLAG_BASE 0x00000450L +#define GDC_REG_CLIP_XMIN 0x00000454L +#define GDC_REG_CLIP_XMAX 0x00000458L +#define GDC_REG_CLIP_YMIN 0x0000045CL +#define GDC_REG_CLIP_YMAX 0x00000460L +#define GDC_REG_TEXURE_SIZE 0x00000464L +#define GDC_REG_TILE_SIZE 0x00000468L +#define GDC_REG_TEX_BUF_OFFSET 0x0000046CL + +/* for MB86293 or later */ +#define GDC_REG_ALPHA_MAP_BASE 0x00000474L /* ABR */ + +/* Constant Registers */ +#define GDC_REG_FOREGROUND_COLOR 0x00000480L +#define GDC_REG_BACKGROUND_COLOR 0x00000484L +#define GDC_REG_ALPHA 0x00000488L +#define GDC_REG_LINE_PATTERN 0x0000048CL +#define GDC_REG_TEX_BORDER_COLOR 0x00000494L +#define GDC_REG_LINE_PATTERN_OFFSET 0x000003E0L + +/* Coomand Code */ +#define GDC_CMD_PIXEL 0x00000000L +#define GDC_CMD_PIXEL_Z 0x00000001L + +#define GDC_CMD_X_VECTOR 0x00000020L +#define GDC_CMD_Y_VECTOR 0x00000021L +#define GDC_CMD_X_VECTOR_NOEND 0x00000022L +#define GDC_CMD_Y_VECTOR_NOEND 0x00000023L +#define GDC_CMD_X_VECTOR_BLPO 0x00000024L +#define GDC_CMD_Y_VECTOR_BLPO 0x00000025L +#define GDC_CMD_X_VECTOR_NOEND_BLPO 0x00000026L +#define GDC_CMD_Y_VECTOR_NOEND_BLPO 0x00000027L +#define GDC_CMD_AA_X_VECTOR 0x00000028L +#define GDC_CMD_AA_Y_VECTOR 0x00000029L +#define GDC_CMD_AA_X_VECTOR_NOEND 0x0000002AL +#define GDC_CMD_AA_Y_VECTOR_NOEND 0x0000002BL +#define GDC_CMD_AA_X_VECTOR_BLPO 0x0000002CL +#define GDC_CMD_AA_Y_VECTOR_BLPO 0x0000002DL +#define GDC_CMD_AA_X_VECTOR_NOEND_BLPO 0x0000002EL +#define GDC_CMD_AA_Y_VECTOR_NOEND_BLPO 0x0000002FL + +#define GDC_CMD_0_VECTOR 0x00000030L +#define GDC_CMD_1_VECTOR 0x00000031L +#define GDC_CMD_0_VECTOR_NOEND 0x00000032L +#define GDC_CMD_1_VECTOR_NOEND 0x00000033L +#define GDC_CMD_0_VECTOR_BLPO 0x00000034L +#define GDC_CMD_1_VECTOR_BLPO 0x00000035L +#define GDC_CMD_0_VECTOR_NOEND_BLPO 0x00000036L +#define GDC_CMD_1_VECTOR_NOEND_BLPO 0x00000037L +#define GDC_CMD_AA_0_VECTOR 0x00000038L +#define GDC_CMD_AA_1_VECTOR 0x00000039L +#define GDC_CMD_AA_0_VECTOR_NOEND 0x0000003AL +#define GDC_CMD_AA_1_VECTOR_NOEND 0x0000003BL +#define GDC_CMD_AA_0_VECTOR_BLPO 0x0000003CL +#define GDC_CMD_AA_1_VECTOR_BLPO 0x0000003DL +#define GDC_CMD_AA_0_VECTOR_NOEND_BLPO 0x0000003EL +#define GDC_CMD_AA_1_VECTOR_NOEND_BLPO 0x0000003FL + +#define GDC_CMD_BLT_FILL 0x00000041L +#define GDC_CMD_BLT_DRAW 0x00000042L +#define GDC_CMD_BITMAP 0x00000043L +#define GDC_CMD_BLTCOPY_TOP_LEFT 0x00000044L +#define GDC_CMD_BLTCOPY_TOP_RIGHT 0x00000045L +#define GDC_CMD_BLTCOPY_BOTTOM_LEFT 0x00000046L +#define GDC_CMD_BLTCOPY_BOTTOM_RIGHT 0x00000047L +#define GDC_CMD_LOAD_TEXTURE 0x00000048L +#define GDC_CMD_LOAD_TILE 0x00000049L + +#define GDC_CMD_TRAP_RIGHT 0x00000060L +#define GDC_CMD_TRAP_LEFT 0x00000061L +#define GDC_CMD_TRIANGLE_FAN 0x00000062L +#define GDC_CMD_FLAG_TRIANGLE_FAN 0x00000063L + +#define GDC_CMD_FLUSH_FB 0x000000C1L +#define GDC_CMD_FLUSH_Z 0x000000C2L + +#define GDC_CMD_POLYGON_BEGIN 0x000000E0L +#define GDC_CMD_POLYGON_END 0x000000E1L +#define GDC_CMD_CLEAR_POLY_FLAG 0x000000E2L +#define GDC_CMD_NORMAL 0x000000FFL + +#define GDC_CMD_VECTOR_BLPO_FLAG 0x00040000L +#define GDC_CMD_FAST_VECTOR_BLPO_FLAG 0x00000004L + +/* for MB86293 or later */ +#define GDC_CMD_MDR1 0x00000000L +#define GDC_CMD_MDR1S 0x00000002L +#define GDC_CMD_MDR1B 0x00000004L +#define GDC_CMD_MDR2 0x00000001L +#define GDC_CMD_MDR2S 0x00000003L +#define GDC_CMD_MDR2TL 0x00000007L +#define GDC_CMD_GMDR1E 0x00000010L +#define GDC_CMD_GMDR2E 0x00000020L +#define GDC_CMD_OVERLAP_SHADOW_XY 0x00000000L +#define GDC_CMD_OVERLAP_SHADOW_XY_COMPOSITION 0x00000001L +#define GDC_CMD_OVERLAP_Z_PACKED_ONBS 0x00000007L +#define GDC_CMD_OVERLAP_Z_ORIGIN 0x00000000L +#define GDC_CMD_OVERLAP_Z_NON_TOPLEFT 0x00000001L +#define GDC_CMD_OVERLAP_Z_BORDER 0x00000002L +#define GDC_CMD_OVERLAP_Z_SHADOW 0x00000003L +#define GDC_CMD_BLTCOPY_ALT_ALPHA 0x00000000L /* Reserverd */ +#define GDC_CMD_DC_LOGOUT 0x00000000L /* Reserverd */ +#define GDC_CMD_BODY_FORE_COLOR 0x00000000L +#define GDC_CMD_BODY_BACK_COLOR 0x00000001L +#define GDC_CMD_SHADOW_FORE_COLOR 0x00000002L +#define GDC_CMD_SHADOW_BACK_COLOR 0x00000003L +#define GDC_CMD_BORDER_FORE_COLOR 0x00000004L +#define GDC_CMD_BORDER_BACK_COLOR 0x00000005L + +/* Type Code Table */ +#define GDC_TYPE_G_NOP 0x00000020L +#define GDC_TYPE_G_BEGIN 0x00000021L +#define GDC_TYPE_G_BEGINCONT 0x00000022L +#define GDC_TYPE_G_END 0x00000023L +#define GDC_TYPE_G_VERTEX 0x00000030L +#define GDC_TYPE_G_VERTEXLOG 0x00000032L +#define GDC_TYPE_G_VERTEXNOPLOG 0x00000033L +#define GDC_TYPE_G_INIT 0x00000040L +#define GDC_TYPE_G_VIEWPORT 0x00000041L +#define GDC_TYPE_G_DEPTHRANGE 0x00000042L +#define GDC_TYPE_G_LOADMATRIX 0x00000043L +#define GDC_TYPE_G_VIEWVOLUMEXYCLIP 0x00000044L +#define GDC_TYPE_G_VIEWVOLUMEZCLIP 0x00000045L +#define GDC_TYPE_G_VIEWVOLUMEWCLIP 0x00000046L +#define GDC_TYPE_SETLVERTEX2I 0x00000072L +#define GDC_TYPE_SETLVERTEX2IP 0x00000073L +#define GDC_TYPE_SETMODEREGISTER 0x000000C0L +#define GDC_TYPE_SETGMODEREGISTER 0x000000C1L +#define GDC_TYPE_OVERLAPXYOFFT 0x000000C8L +#define GDC_TYPE_OVERLAPZOFFT 0x000000C9L +#define GDC_TYPE_DC_LOGOUTADDR 0x000000CCL +#define GDC_TYPE_SETCOLORREGISTER 0x000000CEL +#define GDC_TYPE_G_BEGINE 0x000000E1L +#define GDC_TYPE_G_BEGINCONTE 0x000000E2L +#define GDC_TYPE_G_ENDE 0x000000E3L +#define GDC_TYPE_DRAWPIXEL 0x00000000L +#define GDC_TYPE_DRAWPIXELZ 0x00000001L +#define GDC_TYPE_DRAWLINE 0x00000002L +#define GDC_TYPE_DRAWLINE2I 0x00000003L +#define GDC_TYPE_DRAWLINE2IP 0x00000004L +#define GDC_TYPE_DRAWTRAP 0x00000005L +#define GDC_TYPE_DRAWVERTEX2I 0x00000006L +#define GDC_TYPE_DRAWVERTEX2IP 0x00000007L +#define GDC_TYPE_DRAWRECTP 0x00000009L +#define GDC_TYPE_DRAWBITMAPP 0x0000000BL +#define GDC_TYPE_BLTCOPYP 0x0000000DL +#define GDC_TYPE_BLTCOPYALTERNATEP 0x0000000FL +#define GDC_TYPE_LOADTEXTUREP 0x00000011L +#define GDC_TYPE_BLTTEXTUREP 0x00000013L +#define GDC_TYPE_BLTCOPYALTALPHABLENDP 0x0000001FL +#define GDC_TYPE_SETVERTEX2I 0x00000070L +#define GDC_TYPE_SETVERTEX2IP 0x00000071L +#define GDC_TYPE_DRAW 0x000000F0L +#define GDC_TYPE_SETREGISTER 0x000000F1L +#define GDC_TYPE_SYNC 0x000000FCL +#define GDC_TYPE_INTERRUPT 0x000000FDL +#define GDC_TYPE_NOP 0x0 + +/* Raster operation */ +#define GDC_ROP_CLEAR 0x0000 +#define GDC_ROP_AND 0x0001 +#define GDC_ROP_AND_REVERSE 0x0002 +#define GDC_ROP_COPY 0x0003 +#define GDC_ROP_AND_INVERTED 0x0004 +#define GDC_ROP_NOP 0x0005 +#define GDC_ROP_XOR 0x0006 +#define GDC_ROP_OR 0x0007 +#define GDC_ROP_NOR 0x0008 +#define GDC_ROP_EQUIV 0x0009 +#define GDC_ROP_INVERT 0x000A +#define GDC_ROP_OR_REVERSE 0x000B +#define GDC_ROP_COPY_INVERTED 0x000C +#define GDC_ROP_OR_INVERTED 0x000D +#define GDC_ROP_NAND 0x000E +#define GDC_ROP_SET 0x000F + +#endif diff --git a/drivers/video/fbdev/mb862xx/mb862xxfbdrv.c b/drivers/video/fbdev/mb862xx/mb862xxfbdrv.c new file mode 100644 index 000000000000..0cd4c3318511 --- /dev/null +++ b/drivers/video/fbdev/mb862xx/mb862xxfbdrv.c @@ -0,0 +1,1206 @@ +/* + * 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/uaccess.h> +#include <linux/module.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 0x2000000 +#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(info->var.yres_virtual, info->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; +} + +static int mb862xxfb_ioctl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg) +{ + struct mb862xxfb_par *par = fbi->par; + struct mb862xx_l1_cfg *l1_cfg = &par->l1_cfg; + void __user *argp = (void __user *)arg; + int *enable; + u32 l1em = 0; + + switch (cmd) { + case MB862XX_L1_GET_CFG: + if (copy_to_user(argp, l1_cfg, sizeof(*l1_cfg))) + return -EFAULT; + break; + case MB862XX_L1_SET_CFG: + if (copy_from_user(l1_cfg, argp, sizeof(*l1_cfg))) + return -EFAULT; + if (l1_cfg->dh == 0 || l1_cfg->dw == 0) + return -EINVAL; + if ((l1_cfg->sw >= l1_cfg->dw) && (l1_cfg->sh >= l1_cfg->dh)) { + /* downscaling */ + outreg(cap, GC_CAP_CSC, + pack((l1_cfg->sh << 11) / l1_cfg->dh, + (l1_cfg->sw << 11) / l1_cfg->dw)); + l1em = inreg(disp, GC_L1EM); + l1em &= ~GC_L1EM_DM; + } else if ((l1_cfg->sw <= l1_cfg->dw) && + (l1_cfg->sh <= l1_cfg->dh)) { + /* upscaling */ + outreg(cap, GC_CAP_CSC, + pack((l1_cfg->sh << 11) / l1_cfg->dh, + (l1_cfg->sw << 11) / l1_cfg->dw)); + outreg(cap, GC_CAP_CMSS, + pack(l1_cfg->sw >> 1, l1_cfg->sh)); + outreg(cap, GC_CAP_CMDS, + pack(l1_cfg->dw >> 1, l1_cfg->dh)); + l1em = inreg(disp, GC_L1EM); + l1em |= GC_L1EM_DM; + } + + if (l1_cfg->mirror) { + outreg(cap, GC_CAP_CBM, + inreg(cap, GC_CAP_CBM) | GC_CBM_HRV); + l1em |= l1_cfg->dw * 2 - 8; + } else { + outreg(cap, GC_CAP_CBM, + inreg(cap, GC_CAP_CBM) & ~GC_CBM_HRV); + l1em &= 0xffff0000; + } + outreg(disp, GC_L1EM, l1em); + break; + case MB862XX_L1_ENABLE: + enable = (int *)arg; + if (*enable) { + outreg(disp, GC_L1DA, par->cap_buf); + outreg(cap, GC_CAP_IMG_START, + pack(l1_cfg->sy >> 1, l1_cfg->sx)); + outreg(cap, GC_CAP_IMG_END, + pack(l1_cfg->sh, l1_cfg->sw)); + outreg(disp, GC_L1M, GC_L1M_16 | GC_L1M_YC | GC_L1M_CS | + (par->l1_stride << 16)); + outreg(disp, GC_L1WY_L1WX, + pack(l1_cfg->dy, l1_cfg->dx)); + outreg(disp, GC_L1WH_L1WW, + pack(l1_cfg->dh - 1, l1_cfg->dw)); + outreg(disp, GC_DLS, 1); + outreg(cap, GC_CAP_VCM, + GC_VCM_VIE | GC_VCM_CM | GC_VCM_VS_PAL); + outreg(disp, GC_DCM1, inreg(disp, GC_DCM1) | + GC_DCM1_DEN | GC_DCM1_L1E); + } else { + outreg(cap, GC_CAP_VCM, + inreg(cap, GC_CAP_VCM) & ~GC_VCM_VIE); + outreg(disp, GC_DCM1, + inreg(disp, GC_DCM1) & ~GC_DCM1_L1E); + } + break; + case MB862XX_L1_CAP_CTL: + enable = (int *)arg; + if (*enable) { + outreg(cap, GC_CAP_VCM, + inreg(cap, GC_CAP_VCM) | GC_VCM_VIE); + } else { + outreg(cap, GC_CAP_VCM, + inreg(cap, GC_CAP_VCM) & ~GC_VCM_VIE); + } + break; + default: + return -EINVAL; + } + 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, + .fb_ioctl = mb862xxfb_ioctl, +}; + +/* 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; + int stride; + + 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.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; + fbi->fix.smem_len = fbi->fix.line_length * fbi->var.yres_virtual; + + /* + * reserve space for capture buffers and two cursors + * at the end of vram: 720x576 * 2 * 2.2 + 64x64 * 16. + */ + par->cap_buf = par->mapped_vram - 0x1bd800 - 0x10000; + par->cap_len = 0x1bd800; + par->l1_cfg.sx = 0; + par->l1_cfg.sy = 0; + par->l1_cfg.sw = 720; + par->l1_cfg.sh = 576; + par->l1_cfg.dx = 0; + par->l1_cfg.dy = 0; + par->l1_cfg.dw = 720; + par->l1_cfg.dh = 576; + stride = par->l1_cfg.sw * (fbi->var.bits_per_pixel / 8); + par->l1_stride = stride / 64 + ((stride % 64) ? 1 : 0); + outreg(cap, GC_CAP_CBM, GC_CBM_OO | GC_CBM_CBST | + (par->l1_stride << 16)); + outreg(cap, GC_CAP_CBOA, par->cap_buf); + outreg(cap, GC_CAP_CBLA, par->cap_buf + par->cap_len); + 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); + +static 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 of_platform_mb862xx_probe(struct platform_device *ofdev) +{ + struct device_node *np = ofdev->dev.of_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 = resource_size(&res); + 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, 0, + 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: + framebuffer_release(info); + return ret; +} + +static int of_platform_mb862xx_remove(struct platform_device *ofdev) +{ + struct fb_info *fbi = dev_get_drvdata(&ofdev->dev); + struct mb862xxfb_par *par = fbi->par; + resource_size_t res_size = resource_size(par->res); + 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); + + release_mem_region(par->res->start, res_size); + framebuffer_release(fbi); + return 0; +} + +/* + * common types + */ +static struct of_device_id 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 platform_driver of_platform_mb862xxfb_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_platform_mb862xx_tbl, + }, + .probe = of_platform_mb862xx_probe, + .remove = 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; + + if (par->mapped_vram >= 0x2000000) { + /* relocate gdc registers space */ + writel(1, par->fb_base + MB862XX_MMIO_BASE + GC_RSW); + udelay(1); /* wait at least 20 bus cycles */ + } + + ver = inreg(host, GC_CID); + cn = (ver & GC_CID_CNAME_MSK) >> 8; + ver = ver & GC_CID_VERSION_MSK; + if (cn == 3) { + unsigned long reg; + + dev_info(par->dev, "Fujitsu Coral-%s GDC Rev.%d found\n",\ + (ver == 6) ? "P" : (ver == 8) ? "PA" : "?", + par->pdev->revision); + reg = inreg(disp, GC_DCM1); + if (reg & GC_DCM01_DEN && reg & GC_DCM01_L0E) + par->pre_init = 1; + + if (!par->pre_init) { + 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; + } + + mb862xx_i2c_init(par); + 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[] = { + /* 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 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; + if (par->mapped_vram >= 0x2000000) { + par->mmio_base_phys = par->fb_base_phys + + MB862XX_MMIO_HIGH_BASE; + } else { + 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 */ + ret = -EIO; + 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"); + ret = -EIO; + 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); + + ret = mb862xx_pci_gdc_init(par); + if (ret) + goto io_unmap; + + ret = request_irq(par->irq, mb862xx_intr, IRQF_SHARED, + DRV_NAME, (void *)par); + if (ret) { + 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 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); + } + + mb862xx_i2c_exit(par); + + device_remove_file(&pdev->dev, &dev_attr_dispregs); + + 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 = mb862xx_pci_remove, +}; +#endif + +static int mb862xxfb_init(void) +{ + int ret = -ENODEV; + +#if defined(CONFIG_FB_MB862XX_LIME) + ret = platform_driver_register(&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) + platform_driver_unregister(&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"); diff --git a/drivers/video/fbdev/mbx/Makefile b/drivers/video/fbdev/mbx/Makefile new file mode 100644 index 000000000000..16c1165cf9c7 --- /dev/null +++ b/drivers/video/fbdev/mbx/Makefile @@ -0,0 +1,4 @@ +# Makefile for the 2700G controller driver. + +obj-$(CONFIG_FB_MBX) += mbxfb.o +obj-$(CONFIG_FB_MBX_DEBUG) += mbxfbdebugfs.o diff --git a/drivers/video/fbdev/mbx/mbxdebugfs.c b/drivers/video/fbdev/mbx/mbxdebugfs.c new file mode 100644 index 000000000000..4449f249b0e7 --- /dev/null +++ b/drivers/video/fbdev/mbx/mbxdebugfs.c @@ -0,0 +1,251 @@ +#include <linux/debugfs.h> +#include <linux/slab.h> + +#define BIG_BUFFER_SIZE (1024) + +static char big_buffer[BIG_BUFFER_SIZE]; + +struct mbxfb_debugfs_data { + struct dentry *dir; + struct dentry *sysconf; + struct dentry *clock; + struct dentry *display; + struct dentry *gsctl; + struct dentry *sdram; + struct dentry *misc; +}; + +static int open_file_generic(struct inode *inode, struct file *file) +{ + file->private_data = inode->u.generic_ip; + return 0; +} + +static ssize_t write_file_dummy(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return count; +} + +static ssize_t sysconf_read_file(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char * s = big_buffer; + + s += sprintf(s, "SYSCFG = %08x\n", readl(SYSCFG)); + s += sprintf(s, "PFBASE = %08x\n", readl(PFBASE)); + s += sprintf(s, "PFCEIL = %08x\n", readl(PFCEIL)); + s += sprintf(s, "POLLFLAG = %08x\n", readl(POLLFLAG)); + s += sprintf(s, "SYSRST = %08x\n", readl(SYSRST)); + + return simple_read_from_buffer(userbuf, count, ppos, + big_buffer, s-big_buffer); +} + + +static ssize_t gsctl_read_file(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char * s = big_buffer; + + s += sprintf(s, "GSCTRL = %08x\n", readl(GSCTRL)); + s += sprintf(s, "VSCTRL = %08x\n", readl(VSCTRL)); + s += sprintf(s, "GBBASE = %08x\n", readl(GBBASE)); + s += sprintf(s, "VBBASE = %08x\n", readl(VBBASE)); + s += sprintf(s, "GDRCTRL = %08x\n", readl(GDRCTRL)); + s += sprintf(s, "VCMSK = %08x\n", readl(VCMSK)); + s += sprintf(s, "GSCADR = %08x\n", readl(GSCADR)); + s += sprintf(s, "VSCADR = %08x\n", readl(VSCADR)); + s += sprintf(s, "VUBASE = %08x\n", readl(VUBASE)); + s += sprintf(s, "VVBASE = %08x\n", readl(VVBASE)); + s += sprintf(s, "GSADR = %08x\n", readl(GSADR)); + s += sprintf(s, "VSADR = %08x\n", readl(VSADR)); + s += sprintf(s, "HCCTRL = %08x\n", readl(HCCTRL)); + s += sprintf(s, "HCSIZE = %08x\n", readl(HCSIZE)); + s += sprintf(s, "HCPOS = %08x\n", readl(HCPOS)); + s += sprintf(s, "HCBADR = %08x\n", readl(HCBADR)); + s += sprintf(s, "HCCKMSK = %08x\n", readl(HCCKMSK)); + s += sprintf(s, "GPLUT = %08x\n", readl(GPLUT)); + + return simple_read_from_buffer(userbuf, count, ppos, + big_buffer, s-big_buffer); +} + +static ssize_t display_read_file(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char * s = big_buffer; + + s += sprintf(s, "DSCTRL = %08x\n", readl(DSCTRL)); + s += sprintf(s, "DHT01 = %08x\n", readl(DHT01)); + s += sprintf(s, "DHT02 = %08x\n", readl(DHT02)); + s += sprintf(s, "DHT03 = %08x\n", readl(DHT03)); + s += sprintf(s, "DVT01 = %08x\n", readl(DVT01)); + s += sprintf(s, "DVT02 = %08x\n", readl(DVT02)); + s += sprintf(s, "DVT03 = %08x\n", readl(DVT03)); + s += sprintf(s, "DBCOL = %08x\n", readl(DBCOL)); + s += sprintf(s, "BGCOLOR = %08x\n", readl(BGCOLOR)); + s += sprintf(s, "DINTRS = %08x\n", readl(DINTRS)); + s += sprintf(s, "DINTRE = %08x\n", readl(DINTRE)); + s += sprintf(s, "DINTRCNT = %08x\n", readl(DINTRCNT)); + s += sprintf(s, "DSIG = %08x\n", readl(DSIG)); + s += sprintf(s, "DMCTRL = %08x\n", readl(DMCTRL)); + s += sprintf(s, "CLIPCTRL = %08x\n", readl(CLIPCTRL)); + s += sprintf(s, "SPOCTRL = %08x\n", readl(SPOCTRL)); + s += sprintf(s, "SVCTRL = %08x\n", readl(SVCTRL)); + s += sprintf(s, "DLSTS = %08x\n", readl(DLSTS)); + s += sprintf(s, "DLLCTRL = %08x\n", readl(DLLCTRL)); + s += sprintf(s, "DVLNUM = %08x\n", readl(DVLNUM)); + s += sprintf(s, "DUCTRL = %08x\n", readl(DUCTRL)); + s += sprintf(s, "DVECTRL = %08x\n", readl(DVECTRL)); + s += sprintf(s, "DHDET = %08x\n", readl(DHDET)); + s += sprintf(s, "DVDET = %08x\n", readl(DVDET)); + s += sprintf(s, "DODMSK = %08x\n", readl(DODMSK)); + s += sprintf(s, "CSC01 = %08x\n", readl(CSC01)); + s += sprintf(s, "CSC02 = %08x\n", readl(CSC02)); + s += sprintf(s, "CSC03 = %08x\n", readl(CSC03)); + s += sprintf(s, "CSC04 = %08x\n", readl(CSC04)); + s += sprintf(s, "CSC05 = %08x\n", readl(CSC05)); + + return simple_read_from_buffer(userbuf, count, ppos, + big_buffer, s-big_buffer); +} + +static ssize_t clock_read_file(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char * s = big_buffer; + + s += sprintf(s, "SYSCLKSRC = %08x\n", readl(SYSCLKSRC)); + s += sprintf(s, "PIXCLKSRC = %08x\n", readl(PIXCLKSRC)); + s += sprintf(s, "CLKSLEEP = %08x\n", readl(CLKSLEEP)); + s += sprintf(s, "COREPLL = %08x\n", readl(COREPLL)); + s += sprintf(s, "DISPPLL = %08x\n", readl(DISPPLL)); + s += sprintf(s, "PLLSTAT = %08x\n", readl(PLLSTAT)); + s += sprintf(s, "VOVRCLK = %08x\n", readl(VOVRCLK)); + s += sprintf(s, "PIXCLK = %08x\n", readl(PIXCLK)); + s += sprintf(s, "MEMCLK = %08x\n", readl(MEMCLK)); + s += sprintf(s, "M24CLK = %08x\n", readl(M24CLK)); + s += sprintf(s, "MBXCLK = %08x\n", readl(MBXCLK)); + s += sprintf(s, "SDCLK = %08x\n", readl(SDCLK)); + s += sprintf(s, "PIXCLKDIV = %08x\n", readl(PIXCLKDIV)); + + return simple_read_from_buffer(userbuf, count, ppos, + big_buffer, s-big_buffer); +} + +static ssize_t sdram_read_file(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char * s = big_buffer; + + s += sprintf(s, "LMRST = %08x\n", readl(LMRST)); + s += sprintf(s, "LMCFG = %08x\n", readl(LMCFG)); + s += sprintf(s, "LMPWR = %08x\n", readl(LMPWR)); + s += sprintf(s, "LMPWRSTAT = %08x\n", readl(LMPWRSTAT)); + s += sprintf(s, "LMCEMR = %08x\n", readl(LMCEMR)); + s += sprintf(s, "LMTYPE = %08x\n", readl(LMTYPE)); + s += sprintf(s, "LMTIM = %08x\n", readl(LMTIM)); + s += sprintf(s, "LMREFRESH = %08x\n", readl(LMREFRESH)); + s += sprintf(s, "LMPROTMIN = %08x\n", readl(LMPROTMIN)); + s += sprintf(s, "LMPROTMAX = %08x\n", readl(LMPROTMAX)); + s += sprintf(s, "LMPROTCFG = %08x\n", readl(LMPROTCFG)); + s += sprintf(s, "LMPROTERR = %08x\n", readl(LMPROTERR)); + + return simple_read_from_buffer(userbuf, count, ppos, + big_buffer, s-big_buffer); +} + +static ssize_t misc_read_file(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char * s = big_buffer; + + s += sprintf(s, "LCD_CONFIG = %08x\n", readl(LCD_CONFIG)); + s += sprintf(s, "ODFBPWR = %08x\n", readl(ODFBPWR)); + s += sprintf(s, "ODFBSTAT = %08x\n", readl(ODFBSTAT)); + s += sprintf(s, "ID = %08x\n", readl(ID)); + + return simple_read_from_buffer(userbuf, count, ppos, + big_buffer, s-big_buffer); +} + + +static const struct file_operations sysconf_fops = { + .read = sysconf_read_file, + .write = write_file_dummy, + .open = open_file_generic, + .llseek = default_llseek, +}; + +static const struct file_operations clock_fops = { + .read = clock_read_file, + .write = write_file_dummy, + .open = open_file_generic, + .llseek = default_llseek, +}; + +static const struct file_operations display_fops = { + .read = display_read_file, + .write = write_file_dummy, + .open = open_file_generic, + .llseek = default_llseek, +}; + +static const struct file_operations gsctl_fops = { + .read = gsctl_read_file, + .write = write_file_dummy, + .open = open_file_generic, + .llseek = default_llseek, +}; + +static const struct file_operations sdram_fops = { + .read = sdram_read_file, + .write = write_file_dummy, + .open = open_file_generic, + .llseek = default_llseek, +}; + +static const struct file_operations misc_fops = { + .read = misc_read_file, + .write = write_file_dummy, + .open = open_file_generic, + .llseek = default_llseek, +}; + +static void mbxfb_debugfs_init(struct fb_info *fbi) +{ + struct mbxfb_info *mfbi = fbi->par; + struct mbxfb_debugfs_data *dbg; + + dbg = kzalloc(sizeof(struct mbxfb_debugfs_data), GFP_KERNEL); + mfbi->debugfs_data = dbg; + + dbg->dir = debugfs_create_dir("mbxfb", NULL); + dbg->sysconf = debugfs_create_file("sysconf", 0444, dbg->dir, + fbi, &sysconf_fops); + dbg->clock = debugfs_create_file("clock", 0444, dbg->dir, + fbi, &clock_fops); + dbg->display = debugfs_create_file("display", 0444, dbg->dir, + fbi, &display_fops); + dbg->gsctl = debugfs_create_file("gsctl", 0444, dbg->dir, + fbi, &gsctl_fops); + dbg->sdram = debugfs_create_file("sdram", 0444, dbg->dir, + fbi, &sdram_fops); + dbg->misc = debugfs_create_file("misc", 0444, dbg->dir, + fbi, &misc_fops); +} + +static void mbxfb_debugfs_remove(struct fb_info *fbi) +{ + struct mbxfb_info *mfbi = fbi->par; + struct mbxfb_debugfs_data *dbg = mfbi->debugfs_data; + + debugfs_remove(dbg->misc); + debugfs_remove(dbg->sdram); + debugfs_remove(dbg->gsctl); + debugfs_remove(dbg->display); + debugfs_remove(dbg->clock); + debugfs_remove(dbg->sysconf); + debugfs_remove(dbg->dir); +} diff --git a/drivers/video/fbdev/mbx/mbxfb.c b/drivers/video/fbdev/mbx/mbxfb.c new file mode 100644 index 000000000000..f0a5392f5fd3 --- /dev/null +++ b/drivers/video/fbdev/mbx/mbxfb.c @@ -0,0 +1,1053 @@ +/* + * linux/drivers/video/mbx/mbxfb.c + * + * Copyright (C) 2006-2007 8D Technologies inc + * Raphael Assenat <raph@8d.com> + * - Added video overlay support + * - Various improvements + * + * Copyright (C) 2006 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * - Creation of driver + * + * Based on pxafb.c + * + * 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. + * + * Intel 2700G (Marathon) Graphics Accelerator Frame Buffer Driver + * + */ + +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include <video/mbxfb.h> + +#include "regs.h" +#include "reg_bits.h" + +static void __iomem *virt_base_2700; + +#define write_reg(val, reg) do { writel((val), (reg)); } while(0) + +/* Without this delay, the graphics appears somehow scaled and + * there is a lot of jitter in scanlines. This delay is probably + * needed only after setting some specific register(s) somewhere, + * not all over the place... */ +#define write_reg_dly(val, reg) do { writel((val), reg); udelay(1000); } while(0) + +#define MIN_XRES 16 +#define MIN_YRES 16 +#define MAX_XRES 2048 +#define MAX_YRES 2048 + +#define MAX_PALETTES 16 + +/* FIXME: take care of different chip revisions with different sizes + of ODFB */ +#define MEMORY_OFFSET 0x60000 + +struct mbxfb_info { + struct device *dev; + + struct resource *fb_res; + struct resource *fb_req; + + struct resource *reg_res; + struct resource *reg_req; + + void __iomem *fb_virt_addr; + unsigned long fb_phys_addr; + + void __iomem *reg_virt_addr; + unsigned long reg_phys_addr; + + int (*platform_probe) (struct fb_info * fb); + int (*platform_remove) (struct fb_info * fb); + + u32 pseudo_palette[MAX_PALETTES]; +#ifdef CONFIG_FB_MBX_DEBUG + void *debugfs_data; +#endif + +}; + +static struct fb_var_screeninfo mbxfb_default = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 16, + .red = {11, 5, 0}, + .green = {5, 6, 0}, + .blue = {0, 5, 0}, + .activate = FB_ACTIVATE_TEST, + .height = -1, + .width = -1, + .pixclock = 40000, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, +}; + +static struct fb_fix_screeninfo mbxfb_fix = { + .id = "MBX", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +struct pixclock_div { + u8 m; + u8 n; + u8 p; +}; + +static unsigned int mbxfb_get_pixclock(unsigned int pixclock_ps, + struct pixclock_div *div) +{ + u8 m, n, p; + unsigned int err = 0; + unsigned int min_err = ~0x0; + unsigned int clk; + unsigned int best_clk = 0; + unsigned int ref_clk = 13000; /* FIXME: take from platform data */ + unsigned int pixclock; + + /* convert pixclock to KHz */ + pixclock = PICOS2KHZ(pixclock_ps); + + /* PLL output freq = (ref_clk * M) / (N * 2^P) + * + * M: 1 to 63 + * N: 1 to 7 + * P: 0 to 7 + */ + + /* RAPH: When N==1, the resulting pixel clock appears to + * get divided by 2. Preventing N=1 by starting the following + * loop at 2 prevents this. Is this a bug with my chip + * revision or something I dont understand? */ + for (m = 1; m < 64; m++) { + for (n = 2; n < 8; n++) { + for (p = 0; p < 8; p++) { + clk = (ref_clk * m) / (n * (1 << p)); + err = (clk > pixclock) ? (clk - pixclock) : + (pixclock - clk); + if (err < min_err) { + min_err = err; + best_clk = clk; + div->m = m; + div->n = n; + div->p = p; + } + } + } + } + return KHZ2PICOS(best_clk); +} + +static int mbxfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + u32 val, ret = 1; + + if (regno < MAX_PALETTES) { + u32 *pal = info->pseudo_palette; + + val = (red & 0xf800) | ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + pal[regno] = val; + ret = 0; + } + + return ret; +} + +static int mbxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct pixclock_div div; + + var->pixclock = mbxfb_get_pixclock(var->pixclock, &div); + + if (var->xres < MIN_XRES) + var->xres = MIN_XRES; + if (var->yres < MIN_YRES) + var->yres = MIN_YRES; + if (var->xres > MAX_XRES) + return -EINVAL; + if (var->yres > MAX_YRES) + return -EINVAL; + var->xres_virtual = max(var->xres_virtual, var->xres); + var->yres_virtual = max(var->yres_virtual, var->yres); + + switch (var->bits_per_pixel) { + /* 8 bits-per-pixel is not supported yet */ + case 8: + return -EINVAL; + case 16: + var->green.length = (var->green.length == 5) ? 5 : 6; + var->red.length = 5; + var->blue.length = 5; + var->transp.length = 6 - var->green.length; + var->blue.offset = 0; + var->green.offset = 5; + var->red.offset = 5 + var->green.length; + var->transp.offset = (5 + var->red.offset) & 15; + break; + case 24: /* RGB 888 */ + case 32: /* RGBA 8888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.length = var->bits_per_pixel - 24; + var->transp.offset = (var->transp.length) ? 24 : 0; + break; + } + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + return 0; +} + +static int mbxfb_set_par(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct pixclock_div div; + ushort hbps, ht, hfps, has; + ushort vbps, vt, vfps, vas; + u32 gsctrl = readl(GSCTRL); + u32 gsadr = readl(GSADR); + + info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; + + /* setup color mode */ + gsctrl &= ~(FMsk(GSCTRL_GPIXFMT)); + /* FIXME: add *WORKING* support for 8-bits per color */ + if (info->var.bits_per_pixel == 8) { + return -EINVAL; + } else { + fb_dealloc_cmap(&info->cmap); + gsctrl &= ~GSCTRL_LUT_EN; + + info->fix.visual = FB_VISUAL_TRUECOLOR; + switch (info->var.bits_per_pixel) { + case 16: + if (info->var.green.length == 5) + gsctrl |= GSCTRL_GPIXFMT_ARGB1555; + else + gsctrl |= GSCTRL_GPIXFMT_RGB565; + break; + case 24: + gsctrl |= GSCTRL_GPIXFMT_RGB888; + break; + case 32: + gsctrl |= GSCTRL_GPIXFMT_ARGB8888; + break; + } + } + + /* setup resolution */ + gsctrl &= ~(FMsk(GSCTRL_GSWIDTH) | FMsk(GSCTRL_GSHEIGHT)); + gsctrl |= Gsctrl_Width(info->var.xres) | + Gsctrl_Height(info->var.yres); + write_reg_dly(gsctrl, GSCTRL); + + gsadr &= ~(FMsk(GSADR_SRCSTRIDE)); + gsadr |= Gsadr_Srcstride(info->var.xres * info->var.bits_per_pixel / + (8 * 16) - 1); + write_reg_dly(gsadr, GSADR); + + /* setup timings */ + var->pixclock = mbxfb_get_pixclock(info->var.pixclock, &div); + + write_reg_dly((Disp_Pll_M(div.m) | Disp_Pll_N(div.n) | + Disp_Pll_P(div.p) | DISP_PLL_EN), DISPPLL); + + hbps = var->hsync_len; + has = hbps + var->left_margin; + hfps = has + var->xres; + ht = hfps + var->right_margin; + + vbps = var->vsync_len; + vas = vbps + var->upper_margin; + vfps = vas + var->yres; + vt = vfps + var->lower_margin; + + write_reg_dly((Dht01_Hbps(hbps) | Dht01_Ht(ht)), DHT01); + write_reg_dly((Dht02_Hlbs(has) | Dht02_Has(has)), DHT02); + write_reg_dly((Dht03_Hfps(hfps) | Dht03_Hrbs(hfps)), DHT03); + write_reg_dly((Dhdet_Hdes(has) | Dhdet_Hdef(hfps)), DHDET); + + write_reg_dly((Dvt01_Vbps(vbps) | Dvt01_Vt(vt)), DVT01); + write_reg_dly((Dvt02_Vtbs(vas) | Dvt02_Vas(vas)), DVT02); + write_reg_dly((Dvt03_Vfps(vfps) | Dvt03_Vbbs(vfps)), DVT03); + write_reg_dly((Dvdet_Vdes(vas) | Dvdet_Vdef(vfps)), DVDET); + write_reg_dly((Dvectrl_Vevent(vfps) | Dvectrl_Vfetch(vbps)), DVECTRL); + + write_reg_dly((readl(DSCTRL) | DSCTRL_SYNCGEN_EN), DSCTRL); + + write_reg_dly(DINTRE_VEVENT0_EN, DINTRE); + + return 0; +} + +static int mbxfb_blank(int blank, struct fb_info *info) +{ + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + write_reg_dly((readl(DSCTRL) & ~DSCTRL_SYNCGEN_EN), DSCTRL); + write_reg_dly((readl(PIXCLK) & ~PIXCLK_EN), PIXCLK); + write_reg_dly((readl(VOVRCLK) & ~VOVRCLK_EN), VOVRCLK); + break; + case FB_BLANK_UNBLANK: + write_reg_dly((readl(DSCTRL) | DSCTRL_SYNCGEN_EN), DSCTRL); + write_reg_dly((readl(PIXCLK) | PIXCLK_EN), PIXCLK); + break; + } + return 0; +} + +static int mbxfb_setupOverlay(struct mbxfb_overlaySetup *set) +{ + u32 vsctrl, vscadr, vsadr; + u32 sssize, spoctrl, shctrl; + u32 vubase, vvbase; + u32 vovrclk; + + if (set->scaled_width==0 || set->scaled_height==0) + return -EINVAL; + + /* read registers which have reserved bits + * so we can write them back as-is. */ + vovrclk = readl(VOVRCLK); + vsctrl = readl(VSCTRL); + vscadr = readl(VSCADR); + vubase = readl(VUBASE); + vvbase = readl(VVBASE); + shctrl = readl(SHCTRL); + + spoctrl = readl(SPOCTRL); + sssize = readl(SSSIZE); + + vsctrl &= ~( FMsk(VSCTRL_VSWIDTH) | + FMsk(VSCTRL_VSHEIGHT) | + FMsk(VSCTRL_VPIXFMT) | + VSCTRL_GAMMA_EN | VSCTRL_CSC_EN | + VSCTRL_COSITED ); + vsctrl |= Vsctrl_Width(set->width) | Vsctrl_Height(set->height) | + VSCTRL_CSC_EN; + + vscadr &= ~(VSCADR_STR_EN | FMsk(VSCADR_VBASE_ADR) ); + vubase &= ~(VUBASE_UVHALFSTR | FMsk(VUBASE_UBASE_ADR)); + vvbase &= ~(FMsk(VVBASE_VBASE_ADR)); + + switch (set->fmt) { + case MBXFB_FMT_YUV16: + vsctrl |= VSCTRL_VPIXFMT_YUV12; + + set->Y_stride = ((set->width) + 0xf ) & ~0xf; + break; + case MBXFB_FMT_YUV12: + vsctrl |= VSCTRL_VPIXFMT_YUV12; + + set->Y_stride = ((set->width) + 0xf ) & ~0xf; + vubase |= VUBASE_UVHALFSTR; + + break; + case MBXFB_FMT_UY0VY1: + vsctrl |= VSCTRL_VPIXFMT_UY0VY1; + set->Y_stride = (set->width*2 + 0xf ) & ~0xf; + break; + case MBXFB_FMT_VY0UY1: + vsctrl |= VSCTRL_VPIXFMT_VY0UY1; + set->Y_stride = (set->width*2 + 0xf ) & ~0xf; + break; + case MBXFB_FMT_Y0UY1V: + vsctrl |= VSCTRL_VPIXFMT_Y0UY1V; + set->Y_stride = (set->width*2 + 0xf ) & ~0xf; + break; + case MBXFB_FMT_Y0VY1U: + vsctrl |= VSCTRL_VPIXFMT_Y0VY1U; + set->Y_stride = (set->width*2 + 0xf ) & ~0xf; + break; + default: + return -EINVAL; + } + + /* VSCTRL has the bits which sets the Video Pixel Format. + * When passing from a packed to planar format, + * if we write VSCTRL first, VVBASE and VUBASE would + * be zero if we would not set them here. (And then, + * the chips hangs and only a reset seems to fix it). + * + * If course, the values calculated here have no meaning + * for packed formats. + */ + set->UV_stride = ((set->width/2) + 0x7 ) & ~0x7; + set->U_offset = set->height * set->Y_stride; + set->V_offset = set->U_offset + + set->height * set->UV_stride; + vubase |= Vubase_Ubase_Adr( + (0x60000 + set->mem_offset + set->U_offset)>>3); + vvbase |= Vvbase_Vbase_Adr( + (0x60000 + set->mem_offset + set->V_offset)>>3); + + + vscadr |= Vscadr_Vbase_Adr((0x60000 + set->mem_offset)>>4); + + if (set->enable) + vscadr |= VSCADR_STR_EN; + + + vsadr = Vsadr_Srcstride((set->Y_stride)/16-1) | + Vsadr_Xstart(set->x) | Vsadr_Ystart(set->y); + + sssize &= ~(FMsk(SSSIZE_SC_WIDTH) | FMsk(SSSIZE_SC_HEIGHT)); + sssize = Sssize_Sc_Width(set->scaled_width-1) | + Sssize_Sc_Height(set->scaled_height-1); + + spoctrl &= ~(SPOCTRL_H_SC_BP | SPOCTRL_V_SC_BP | + SPOCTRL_HV_SC_OR | SPOCTRL_VS_UR_C | + FMsk(SPOCTRL_VPITCH)); + spoctrl |= Spoctrl_Vpitch((set->height<<11)/set->scaled_height); + + /* Bypass horiz/vert scaler when same size */ + if (set->scaled_width == set->width) + spoctrl |= SPOCTRL_H_SC_BP; + if (set->scaled_height == set->height) + spoctrl |= SPOCTRL_V_SC_BP; + + shctrl &= ~(FMsk(SHCTRL_HPITCH) | SHCTRL_HDECIM); + shctrl |= Shctrl_Hpitch((set->width<<11)/set->scaled_width); + + /* Video plane registers */ + write_reg(vsctrl, VSCTRL); + write_reg(vscadr, VSCADR); + write_reg(vubase, VUBASE); + write_reg(vvbase, VVBASE); + write_reg(vsadr, VSADR); + + /* Video scaler registers */ + write_reg(sssize, SSSIZE); + write_reg(spoctrl, SPOCTRL); + write_reg(shctrl, SHCTRL); + + /* Clock */ + if (set->enable) + vovrclk |= 1; + else + vovrclk &= ~1; + + write_reg(vovrclk, VOVRCLK); + + return 0; +} + +static int mbxfb_ioctl_planeorder(struct mbxfb_planeorder *porder) +{ + unsigned long gscadr, vscadr; + + if (porder->bottom == porder->top) + return -EINVAL; + + gscadr = readl(GSCADR); + vscadr = readl(VSCADR); + + gscadr &= ~(FMsk(GSCADR_BLEND_POS)); + vscadr &= ~(FMsk(VSCADR_BLEND_POS)); + + switch (porder->bottom) { + case MBXFB_PLANE_GRAPHICS: + gscadr |= GSCADR_BLEND_GFX; + break; + case MBXFB_PLANE_VIDEO: + vscadr |= VSCADR_BLEND_GFX; + break; + default: + return -EINVAL; + } + + switch (porder->top) { + case MBXFB_PLANE_GRAPHICS: + gscadr |= GSCADR_BLEND_VID; + break; + case MBXFB_PLANE_VIDEO: + vscadr |= GSCADR_BLEND_VID; + break; + default: + return -EINVAL; + } + + write_reg_dly(vscadr, VSCADR); + write_reg_dly(gscadr, GSCADR); + + return 0; + +} + +static int mbxfb_ioctl_alphactl(struct mbxfb_alphaCtl *alpha) +{ + unsigned long vscadr, vbbase, vcmsk; + unsigned long gscadr, gbbase, gdrctrl; + + vbbase = Vbbase_Glalpha(alpha->overlay_global_alpha) | + Vbbase_Colkey(alpha->overlay_colorkey); + + gbbase = Gbbase_Glalpha(alpha->graphics_global_alpha) | + Gbbase_Colkey(alpha->graphics_colorkey); + + vcmsk = readl(VCMSK); + vcmsk &= ~(FMsk(VCMSK_COLKEY_M)); + vcmsk |= Vcmsk_colkey_m(alpha->overlay_colorkey_mask); + + gdrctrl = readl(GDRCTRL); + gdrctrl &= ~(FMsk(GDRCTRL_COLKEYM)); + gdrctrl |= Gdrctrl_Colkeym(alpha->graphics_colorkey_mask); + + vscadr = readl(VSCADR); + vscadr &= ~(FMsk(VSCADR_BLEND_M) | VSCADR_COLKEYSRC | VSCADR_COLKEY_EN); + + gscadr = readl(GSCADR); + gscadr &= ~(FMsk(GSCADR_BLEND_M) | GSCADR_COLKEY_EN | GSCADR_COLKEYSRC); + + switch (alpha->overlay_colorkey_mode) { + case MBXFB_COLORKEY_DISABLED: + break; + case MBXFB_COLORKEY_PREVIOUS: + vscadr |= VSCADR_COLKEY_EN; + break; + case MBXFB_COLORKEY_CURRENT: + vscadr |= VSCADR_COLKEY_EN | VSCADR_COLKEYSRC; + break; + default: + return -EINVAL; + } + + switch (alpha->overlay_blend_mode) { + case MBXFB_ALPHABLEND_NONE: + vscadr |= VSCADR_BLEND_NONE; + break; + case MBXFB_ALPHABLEND_GLOBAL: + vscadr |= VSCADR_BLEND_GLOB; + break; + case MBXFB_ALPHABLEND_PIXEL: + vscadr |= VSCADR_BLEND_PIX; + break; + default: + return -EINVAL; + } + + switch (alpha->graphics_colorkey_mode) { + case MBXFB_COLORKEY_DISABLED: + break; + case MBXFB_COLORKEY_PREVIOUS: + gscadr |= GSCADR_COLKEY_EN; + break; + case MBXFB_COLORKEY_CURRENT: + gscadr |= GSCADR_COLKEY_EN | GSCADR_COLKEYSRC; + break; + default: + return -EINVAL; + } + + switch (alpha->graphics_blend_mode) { + case MBXFB_ALPHABLEND_NONE: + gscadr |= GSCADR_BLEND_NONE; + break; + case MBXFB_ALPHABLEND_GLOBAL: + gscadr |= GSCADR_BLEND_GLOB; + break; + case MBXFB_ALPHABLEND_PIXEL: + gscadr |= GSCADR_BLEND_PIX; + break; + default: + return -EINVAL; + } + + write_reg_dly(vbbase, VBBASE); + write_reg_dly(gbbase, GBBASE); + write_reg_dly(vcmsk, VCMSK); + write_reg_dly(gdrctrl, GDRCTRL); + write_reg_dly(gscadr, GSCADR); + write_reg_dly(vscadr, VSCADR); + + return 0; +} + +static int mbxfb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct mbxfb_overlaySetup setup; + struct mbxfb_planeorder porder; + struct mbxfb_alphaCtl alpha; + struct mbxfb_reg reg; + int res; + __u32 tmp; + + switch (cmd) + { + case MBXFB_IOCX_OVERLAY: + if (copy_from_user(&setup, (void __user*)arg, + sizeof(struct mbxfb_overlaySetup))) + return -EFAULT; + + res = mbxfb_setupOverlay(&setup); + if (res) + return res; + + if (copy_to_user((void __user*)arg, &setup, + sizeof(struct mbxfb_overlaySetup))) + return -EFAULT; + + return 0; + + case MBXFB_IOCS_PLANEORDER: + if (copy_from_user(&porder, (void __user*)arg, + sizeof(struct mbxfb_planeorder))) + return -EFAULT; + + return mbxfb_ioctl_planeorder(&porder); + + case MBXFB_IOCS_ALPHA: + if (copy_from_user(&alpha, (void __user*)arg, + sizeof(struct mbxfb_alphaCtl))) + return -EFAULT; + + return mbxfb_ioctl_alphactl(&alpha); + + case MBXFB_IOCS_REG: + if (copy_from_user(®, (void __user*)arg, + sizeof(struct mbxfb_reg))) + return -EFAULT; + + if (reg.addr >= 0x10000) /* regs are from 0x3fe0000 to 0x3feffff */ + return -EINVAL; + + tmp = readl(virt_base_2700 + reg.addr); + tmp &= ~reg.mask; + tmp |= reg.val & reg.mask; + writel(tmp, virt_base_2700 + reg.addr); + + return 0; + case MBXFB_IOCX_REG: + if (copy_from_user(®, (void __user*)arg, + sizeof(struct mbxfb_reg))) + return -EFAULT; + + if (reg.addr >= 0x10000) /* regs are from 0x3fe0000 to 0x3feffff */ + return -EINVAL; + reg.val = readl(virt_base_2700 + reg.addr); + + if (copy_to_user((void __user*)arg, ®, + sizeof(struct mbxfb_reg))) + return -EFAULT; + + return 0; + } + return -EINVAL; +} + +static struct fb_ops mbxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mbxfb_check_var, + .fb_set_par = mbxfb_set_par, + .fb_setcolreg = mbxfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mbxfb_blank, + .fb_ioctl = mbxfb_ioctl, +}; + +/* + Enable external SDRAM controller. Assume that all clocks are active + by now. +*/ +static void setup_memc(struct fb_info *fbi) +{ + unsigned long tmp; + int i; + + /* FIXME: use platform specific parameters */ + /* setup SDRAM controller */ + write_reg_dly((LMCFG_LMC_DS | LMCFG_LMC_TS | LMCFG_LMD_TS | + LMCFG_LMA_TS), + LMCFG); + + write_reg_dly(LMPWR_MC_PWR_ACT, LMPWR); + + /* setup SDRAM timings */ + write_reg_dly((Lmtim_Tras(7) | Lmtim_Trp(3) | Lmtim_Trcd(3) | + Lmtim_Trc(9) | Lmtim_Tdpl(2)), + LMTIM); + /* setup SDRAM refresh rate */ + write_reg_dly(0xc2b, LMREFRESH); + /* setup SDRAM type parameters */ + write_reg_dly((LMTYPE_CASLAT_3 | LMTYPE_BKSZ_2 | LMTYPE_ROWSZ_11 | + LMTYPE_COLSZ_8), + LMTYPE); + /* enable memory controller */ + write_reg_dly(LMPWR_MC_PWR_ACT, LMPWR); + /* perform dummy reads */ + for ( i = 0; i < 16; i++ ) { + tmp = readl(fbi->screen_base); + } +} + +static void enable_clocks(struct fb_info *fbi) +{ + /* enable clocks */ + write_reg_dly(SYSCLKSRC_PLL_2, SYSCLKSRC); + write_reg_dly(PIXCLKSRC_PLL_1, PIXCLKSRC); + write_reg_dly(0x00000000, CLKSLEEP); + + /* PLL output = (Frefclk * M) / (N * 2^P ) + * + * M: 0x17, N: 0x3, P: 0x0 == 100 Mhz! + * M: 0xb, N: 0x1, P: 0x1 == 71 Mhz + * */ + write_reg_dly((Core_Pll_M(0xb) | Core_Pll_N(0x1) | Core_Pll_P(0x1) | + CORE_PLL_EN), + COREPLL); + + write_reg_dly((Disp_Pll_M(0x1b) | Disp_Pll_N(0x7) | Disp_Pll_P(0x1) | + DISP_PLL_EN), + DISPPLL); + + write_reg_dly(0x00000000, VOVRCLK); + write_reg_dly(PIXCLK_EN, PIXCLK); + write_reg_dly(MEMCLK_EN, MEMCLK); + write_reg_dly(0x00000001, M24CLK); + write_reg_dly(0x00000001, MBXCLK); + write_reg_dly(SDCLK_EN, SDCLK); + write_reg_dly(0x00000001, PIXCLKDIV); +} + +static void setup_graphics(struct fb_info *fbi) +{ + unsigned long gsctrl; + unsigned long vscadr; + + gsctrl = GSCTRL_GAMMA_EN | Gsctrl_Width(fbi->var.xres) | + Gsctrl_Height(fbi->var.yres); + switch (fbi->var.bits_per_pixel) { + case 16: + if (fbi->var.green.length == 5) + gsctrl |= GSCTRL_GPIXFMT_ARGB1555; + else + gsctrl |= GSCTRL_GPIXFMT_RGB565; + break; + case 24: + gsctrl |= GSCTRL_GPIXFMT_RGB888; + break; + case 32: + gsctrl |= GSCTRL_GPIXFMT_ARGB8888; + break; + } + + write_reg_dly(gsctrl, GSCTRL); + write_reg_dly(0x00000000, GBBASE); + write_reg_dly(0x00ffffff, GDRCTRL); + write_reg_dly((GSCADR_STR_EN | Gscadr_Gbase_Adr(0x6000)), GSCADR); + write_reg_dly(0x00000000, GPLUT); + + vscadr = readl(VSCADR); + vscadr &= ~(FMsk(VSCADR_BLEND_POS) | FMsk(VSCADR_BLEND_M)); + vscadr |= VSCADR_BLEND_VID | VSCADR_BLEND_NONE; + write_reg_dly(vscadr, VSCADR); +} + +static void setup_display(struct fb_info *fbi) +{ + unsigned long dsctrl = 0; + + dsctrl = DSCTRL_BLNK_POL; + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + dsctrl |= DSCTRL_HS_POL; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + dsctrl |= DSCTRL_VS_POL; + write_reg_dly(dsctrl, DSCTRL); + write_reg_dly(0xd0303010, DMCTRL); + write_reg_dly((readl(DSCTRL) | DSCTRL_SYNCGEN_EN), DSCTRL); +} + +static void enable_controller(struct fb_info *fbi) +{ + u32 svctrl, shctrl; + + write_reg_dly(SYSRST_RST, SYSRST); + + /* setup a timeout, raise drive strength */ + write_reg_dly(0xffffff0c, SYSCFG); + + enable_clocks(fbi); + setup_memc(fbi); + setup_graphics(fbi); + setup_display(fbi); + + shctrl = readl(SHCTRL); + shctrl &= ~(FMsk(SHCTRL_HINITIAL)); + shctrl |= Shctrl_Hinitial(4<<11); + writel(shctrl, SHCTRL); + + svctrl = Svctrl_Initial1(1<<10) | Svctrl_Initial2(1<<10); + writel(svctrl, SVCTRL); + + writel(SPOCTRL_H_SC_BP | SPOCTRL_V_SC_BP | SPOCTRL_VORDER_4TAP + , SPOCTRL); + + /* Those coefficients are good for scaling up. For scaling + * down, the application has to calculate them. */ + write_reg(0xff000100, VSCOEFF0); + write_reg(0xfdfcfdfe, VSCOEFF1); + write_reg(0x170d0500, VSCOEFF2); + write_reg(0x3d372d22, VSCOEFF3); + write_reg(0x00000040, VSCOEFF4); + + write_reg(0xff010100, HSCOEFF0); + write_reg(0x00000000, HSCOEFF1); + write_reg(0x02010000, HSCOEFF2); + write_reg(0x01020302, HSCOEFF3); + write_reg(0xf9fbfe00, HSCOEFF4); + write_reg(0xfbf7f6f7, HSCOEFF5); + write_reg(0x1c110700, HSCOEFF6); + write_reg(0x3e393127, HSCOEFF7); + write_reg(0x00000040, HSCOEFF8); + +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ +static int mbxfb_suspend(struct platform_device *dev, pm_message_t state) +{ + /* make frame buffer memory enter self-refresh mode */ + write_reg_dly(LMPWR_MC_PWR_SRM, LMPWR); + while (readl(LMPWRSTAT) != LMPWRSTAT_MC_PWR_SRM) + ; /* empty statement */ + + /* reset the device, since it's initial state is 'mostly sleeping' */ + write_reg_dly(SYSRST_RST, SYSRST); + return 0; +} + +static int mbxfb_resume(struct platform_device *dev) +{ + struct fb_info *fbi = platform_get_drvdata(dev); + + enable_clocks(fbi); +/* setup_graphics(fbi); */ +/* setup_display(fbi); */ + + write_reg_dly((readl(DSCTRL) | DSCTRL_SYNCGEN_EN), DSCTRL); + return 0; +} +#else +#define mbxfb_suspend NULL +#define mbxfb_resume NULL +#endif + +/* debugfs entries */ +#ifndef CONFIG_FB_MBX_DEBUG +#define mbxfb_debugfs_init(x) do {} while(0) +#define mbxfb_debugfs_remove(x) do {} while(0) +#endif + +#define res_size(_r) (((_r)->end - (_r)->start) + 1) + +static int mbxfb_probe(struct platform_device *dev) +{ + int ret; + struct fb_info *fbi; + struct mbxfb_info *mfbi; + struct mbxfb_platform_data *pdata; + + dev_dbg(&dev->dev, "mbxfb_probe\n"); + + pdata = dev_get_platdata(&dev->dev); + if (!pdata) { + dev_err(&dev->dev, "platform data is required\n"); + return -EINVAL; + } + + fbi = framebuffer_alloc(sizeof(struct mbxfb_info), &dev->dev); + if (fbi == NULL) { + dev_err(&dev->dev, "framebuffer_alloc failed\n"); + return -ENOMEM; + } + + mfbi = fbi->par; + fbi->pseudo_palette = mfbi->pseudo_palette; + + + if (pdata->probe) + mfbi->platform_probe = pdata->probe; + if (pdata->remove) + mfbi->platform_remove = pdata->remove; + + mfbi->fb_res = platform_get_resource(dev, IORESOURCE_MEM, 0); + mfbi->reg_res = platform_get_resource(dev, IORESOURCE_MEM, 1); + + if (!mfbi->fb_res || !mfbi->reg_res) { + dev_err(&dev->dev, "no resources found\n"); + ret = -ENODEV; + goto err1; + } + + mfbi->fb_req = request_mem_region(mfbi->fb_res->start, + res_size(mfbi->fb_res), dev->name); + if (mfbi->fb_req == NULL) { + dev_err(&dev->dev, "failed to claim framebuffer memory\n"); + ret = -EINVAL; + goto err1; + } + mfbi->fb_phys_addr = mfbi->fb_res->start; + + mfbi->reg_req = request_mem_region(mfbi->reg_res->start, + res_size(mfbi->reg_res), dev->name); + if (mfbi->reg_req == NULL) { + dev_err(&dev->dev, "failed to claim Marathon registers\n"); + ret = -EINVAL; + goto err2; + } + mfbi->reg_phys_addr = mfbi->reg_res->start; + + mfbi->reg_virt_addr = devm_ioremap_nocache(&dev->dev, + mfbi->reg_phys_addr, + res_size(mfbi->reg_req)); + if (!mfbi->reg_virt_addr) { + dev_err(&dev->dev, "failed to ioremap Marathon registers\n"); + ret = -EINVAL; + goto err3; + } + virt_base_2700 = mfbi->reg_virt_addr; + + mfbi->fb_virt_addr = devm_ioremap_nocache(&dev->dev, mfbi->fb_phys_addr, + res_size(mfbi->fb_req)); + if (!mfbi->fb_virt_addr) { + dev_err(&dev->dev, "failed to ioremap frame buffer\n"); + ret = -EINVAL; + goto err3; + } + + fbi->screen_base = (char __iomem *)(mfbi->fb_virt_addr + 0x60000); + fbi->screen_size = pdata->memsize; + fbi->fbops = &mbxfb_ops; + + fbi->var = mbxfb_default; + fbi->fix = mbxfb_fix; + fbi->fix.smem_start = mfbi->fb_phys_addr + 0x60000; + fbi->fix.smem_len = pdata->memsize; + fbi->fix.line_length = mbxfb_default.xres_virtual * + mbxfb_default.bits_per_pixel / 8; + + ret = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (ret < 0) { + dev_err(&dev->dev, "fb_alloc_cmap failed\n"); + ret = -EINVAL; + goto err3; + } + + platform_set_drvdata(dev, fbi); + + fb_info(fbi, "mbx frame buffer device\n"); + + if (mfbi->platform_probe) + mfbi->platform_probe(fbi); + + enable_controller(fbi); + + mbxfb_debugfs_init(fbi); + + ret = register_framebuffer(fbi); + if (ret < 0) { + dev_err(&dev->dev, "register_framebuffer failed\n"); + ret = -EINVAL; + goto err6; + } + + return 0; + +err6: + fb_dealloc_cmap(&fbi->cmap); +err3: + release_mem_region(mfbi->reg_res->start, res_size(mfbi->reg_res)); +err2: + release_mem_region(mfbi->fb_res->start, res_size(mfbi->fb_res)); +err1: + framebuffer_release(fbi); + + return ret; +} + +static int mbxfb_remove(struct platform_device *dev) +{ + struct fb_info *fbi = platform_get_drvdata(dev); + + write_reg_dly(SYSRST_RST, SYSRST); + + mbxfb_debugfs_remove(fbi); + + if (fbi) { + struct mbxfb_info *mfbi = fbi->par; + + unregister_framebuffer(fbi); + if (mfbi) { + if (mfbi->platform_remove) + mfbi->platform_remove(fbi); + + + if (mfbi->reg_req) + release_mem_region(mfbi->reg_req->start, + res_size(mfbi->reg_req)); + if (mfbi->fb_req) + release_mem_region(mfbi->fb_req->start, + res_size(mfbi->fb_req)); + } + framebuffer_release(fbi); + } + + return 0; +} + +static struct platform_driver mbxfb_driver = { + .probe = mbxfb_probe, + .remove = mbxfb_remove, + .suspend = mbxfb_suspend, + .resume = mbxfb_resume, + .driver = { + .name = "mbx-fb", + }, +}; + +module_platform_driver(mbxfb_driver); + +MODULE_DESCRIPTION("loadable framebuffer driver for Marathon device"); +MODULE_AUTHOR("Mike Rapoport, Compulab"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mbx/reg_bits.h b/drivers/video/fbdev/mbx/reg_bits.h new file mode 100644 index 000000000000..5f14b4befd71 --- /dev/null +++ b/drivers/video/fbdev/mbx/reg_bits.h @@ -0,0 +1,613 @@ +#ifndef __REG_BITS_2700G_ +#define __REG_BITS_2700G_ + +/* use defines from asm-arm/arch-pxa/bitfields.h for bit fields access */ +#define UData(Data) ((unsigned long) (Data)) +#define Fld(Size, Shft) (((Size) << 16) + (Shft)) +#define FSize(Field) ((Field) >> 16) +#define FShft(Field) ((Field) & 0x0000FFFF) +#define FMsk(Field) (((UData (1) << FSize (Field)) - 1) << FShft (Field)) +#define FAlnMsk(Field) ((UData (1) << FSize (Field)) - 1) +#define F1stBit(Field) (UData (1) << FShft (Field)) + +#define SYSRST_RST (1 << 0) + +/* SYSCLKSRC - SYSCLK Source Control Register */ +#define SYSCLKSRC_SEL Fld(2,0) +#define SYSCLKSRC_REF ((0x0) << FShft(SYSCLKSRC_SEL)) +#define SYSCLKSRC_PLL_1 ((0x1) << FShft(SYSCLKSRC_SEL)) +#define SYSCLKSRC_PLL_2 ((0x2) << FShft(SYSCLKSRC_SEL)) + +/* PIXCLKSRC - PIXCLK Source Control Register */ +#define PIXCLKSRC_SEL Fld(2,0) +#define PIXCLKSRC_REF ((0x0) << FShft(PIXCLKSRC_SEL)) +#define PIXCLKSRC_PLL_1 ((0x1) << FShft(PIXCLKSRC_SEL)) +#define PIXCLKSRC_PLL_2 ((0x2) << FShft(PIXCLKSRC_SEL)) + +/* Clock Disable Register */ +#define CLKSLEEP_SLP (1 << 0) + +/* Core PLL Control Register */ +#define CORE_PLL_M Fld(6,7) +#define Core_Pll_M(x) ((x) << FShft(CORE_PLL_M)) +#define CORE_PLL_N Fld(3,4) +#define Core_Pll_N(x) ((x) << FShft(CORE_PLL_N)) +#define CORE_PLL_P Fld(3,1) +#define Core_Pll_P(x) ((x) << FShft(CORE_PLL_P)) +#define CORE_PLL_EN (1 << 0) + +/* Display PLL Control Register */ +#define DISP_PLL_M Fld(6,7) +#define Disp_Pll_M(x) ((x) << FShft(DISP_PLL_M)) +#define DISP_PLL_N Fld(3,4) +#define Disp_Pll_N(x) ((x) << FShft(DISP_PLL_N)) +#define DISP_PLL_P Fld(3,1) +#define Disp_Pll_P(x) ((x) << FShft(DISP_PLL_P)) +#define DISP_PLL_EN (1 << 0) + +/* PLL status register */ +#define PLLSTAT_CORE_PLL_LOST_L (1 << 3) +#define PLLSTAT_CORE_PLL_LSTS (1 << 2) +#define PLLSTAT_DISP_PLL_LOST_L (1 << 1) +#define PLLSTAT_DISP_PLL_LSTS (1 << 0) + +/* Video and scale clock control register */ +#define VOVRCLK_EN (1 << 0) + +/* Pixel clock control register */ +#define PIXCLK_EN (1 << 0) + +/* Memory clock control register */ +#define MEMCLK_EN (1 << 0) + +/* MBX clock control register */ +#define MBXCLK_DIV Fld(2,2) +#define MBXCLK_DIV_1 ((0x0) << FShft(MBXCLK_DIV)) +#define MBXCLK_DIV_2 ((0x1) << FShft(MBXCLK_DIV)) +#define MBXCLK_DIV_3 ((0x2) << FShft(MBXCLK_DIV)) +#define MBXCLK_DIV_4 ((0x3) << FShft(MBXCLK_DIV)) +#define MBXCLK_EN Fld(2,0) +#define MBXCLK_EN_NONE ((0x0) << FShft(MBXCLK_EN)) +#define MBXCLK_EN_2D ((0x1) << FShft(MBXCLK_EN)) +#define MBXCLK_EN_BOTH ((0x2) << FShft(MBXCLK_EN)) + +/* M24 clock control register */ +#define M24CLK_DIV Fld(2,1) +#define M24CLK_DIV_1 ((0x0) << FShft(M24CLK_DIV)) +#define M24CLK_DIV_2 ((0x1) << FShft(M24CLK_DIV)) +#define M24CLK_DIV_3 ((0x2) << FShft(M24CLK_DIV)) +#define M24CLK_DIV_4 ((0x3) << FShft(M24CLK_DIV)) +#define M24CLK_EN (1 << 0) + +/* SDRAM clock control register */ +#define SDCLK_EN (1 << 0) + +/* PixClk Divisor Register */ +#define PIXCLKDIV_PD Fld(9,0) +#define Pixclkdiv_Pd(x) ((x) << FShft(PIXCLKDIV_PD)) + +/* LCD Config control register */ +#define LCDCFG_IN_FMT Fld(3,28) +#define Lcdcfg_In_Fmt(x) ((x) << FShft(LCDCFG_IN_FMT)) +#define LCDCFG_LCD1DEN_POL (1 << 27) +#define LCDCFG_LCD1FCLK_POL (1 << 26) +#define LCDCFG_LCD1LCLK_POL (1 << 25) +#define LCDCFG_LCD1D_POL (1 << 24) +#define LCDCFG_LCD2DEN_POL (1 << 23) +#define LCDCFG_LCD2FCLK_POL (1 << 22) +#define LCDCFG_LCD2LCLK_POL (1 << 21) +#define LCDCFG_LCD2D_POL (1 << 20) +#define LCDCFG_LCD1_TS (1 << 19) +#define LCDCFG_LCD1D_DS (1 << 18) +#define LCDCFG_LCD1C_DS (1 << 17) +#define LCDCFG_LCD1_IS_IN (1 << 16) +#define LCDCFG_LCD2_TS (1 << 3) +#define LCDCFG_LCD2D_DS (1 << 2) +#define LCDCFG_LCD2C_DS (1 << 1) +#define LCDCFG_LCD2_IS_IN (1 << 0) + +/* On-Die Frame Buffer Power Control Register */ +#define ODFBPWR_SLOW (1 << 2) +#define ODFBPWR_MODE Fld(2,0) +#define ODFBPWR_MODE_ACT ((0x0) << FShft(ODFBPWR_MODE)) +#define ODFBPWR_MODE_ACT_LP ((0x1) << FShft(ODFBPWR_MODE)) +#define ODFBPWR_MODE_SLEEP ((0x2) << FShft(ODFBPWR_MODE)) +#define ODFBPWR_MODE_SHUTD ((0x3) << FShft(ODFBPWR_MODE)) + +/* On-Die Frame Buffer Power State Status Register */ +#define ODFBSTAT_ACT (1 << 2) +#define ODFBSTAT_SLP (1 << 1) +#define ODFBSTAT_SDN (1 << 0) + +/* LMRST - Local Memory (SDRAM) Reset */ +#define LMRST_MC_RST (1 << 0) + +/* LMCFG - Local Memory (SDRAM) Configuration Register */ +#define LMCFG_LMC_DS (1 << 5) +#define LMCFG_LMD_DS (1 << 4) +#define LMCFG_LMA_DS (1 << 3) +#define LMCFG_LMC_TS (1 << 2) +#define LMCFG_LMD_TS (1 << 1) +#define LMCFG_LMA_TS (1 << 0) + +/* LMPWR - Local Memory (SDRAM) Power Control Register */ +#define LMPWR_MC_PWR_CNT Fld(2,0) +#define LMPWR_MC_PWR_ACT ((0x0) << FShft(LMPWR_MC_PWR_CNT)) /* Active */ +#define LMPWR_MC_PWR_SRM ((0x1) << FShft(LMPWR_MC_PWR_CNT)) /* Self-refresh */ +#define LMPWR_MC_PWR_DPD ((0x3) << FShft(LMPWR_MC_PWR_CNT)) /* deep power down */ + +/* LMPWRSTAT - Local Memory (SDRAM) Power Status Register */ +#define LMPWRSTAT_MC_PWR_CNT Fld(2,0) +#define LMPWRSTAT_MC_PWR_ACT ((0x0) << FShft(LMPWRSTAT_MC_PWR_CNT)) /* Active */ +#define LMPWRSTAT_MC_PWR_SRM ((0x1) << FShft(LMPWRSTAT_MC_PWR_CNT)) /* Self-refresh */ +#define LMPWRSTAT_MC_PWR_DPD ((0x3) << FShft(LMPWRSTAT_MC_PWR_CNT)) /* deep power down */ + +/* LMTYPE - Local Memory (SDRAM) Type Register */ +#define LMTYPE_CASLAT Fld(3,10) +#define LMTYPE_CASLAT_1 ((0x1) << FShft(LMTYPE_CASLAT)) +#define LMTYPE_CASLAT_2 ((0x2) << FShft(LMTYPE_CASLAT)) +#define LMTYPE_CASLAT_3 ((0x3) << FShft(LMTYPE_CASLAT)) +#define LMTYPE_BKSZ Fld(2,8) +#define LMTYPE_BKSZ_1 ((0x1) << FShft(LMTYPE_BKSZ)) +#define LMTYPE_BKSZ_2 ((0x2) << FShft(LMTYPE_BKSZ)) +#define LMTYPE_ROWSZ Fld(4,4) +#define LMTYPE_ROWSZ_11 ((0xb) << FShft(LMTYPE_ROWSZ)) +#define LMTYPE_ROWSZ_12 ((0xc) << FShft(LMTYPE_ROWSZ)) +#define LMTYPE_ROWSZ_13 ((0xd) << FShft(LMTYPE_ROWSZ)) +#define LMTYPE_COLSZ Fld(4,0) +#define LMTYPE_COLSZ_7 ((0x7) << FShft(LMTYPE_COLSZ)) +#define LMTYPE_COLSZ_8 ((0x8) << FShft(LMTYPE_COLSZ)) +#define LMTYPE_COLSZ_9 ((0x9) << FShft(LMTYPE_COLSZ)) +#define LMTYPE_COLSZ_10 ((0xa) << FShft(LMTYPE_COLSZ)) +#define LMTYPE_COLSZ_11 ((0xb) << FShft(LMTYPE_COLSZ)) +#define LMTYPE_COLSZ_12 ((0xc) << FShft(LMTYPE_COLSZ)) + +/* LMTIM - Local Memory (SDRAM) Timing Register */ +#define LMTIM_TRAS Fld(4,16) +#define Lmtim_Tras(x) ((x) << FShft(LMTIM_TRAS)) +#define LMTIM_TRP Fld(4,12) +#define Lmtim_Trp(x) ((x) << FShft(LMTIM_TRP)) +#define LMTIM_TRCD Fld(4,8) +#define Lmtim_Trcd(x) ((x) << FShft(LMTIM_TRCD)) +#define LMTIM_TRC Fld(4,4) +#define Lmtim_Trc(x) ((x) << FShft(LMTIM_TRC)) +#define LMTIM_TDPL Fld(4,0) +#define Lmtim_Tdpl(x) ((x) << FShft(LMTIM_TDPL)) + +/* LMREFRESH - Local Memory (SDRAM) tREF Control Register */ +#define LMREFRESH_TREF Fld(2,0) +#define Lmrefresh_Tref(x) ((x) << FShft(LMREFRESH_TREF)) + +/* GSCTRL - Graphics surface control register */ +#define GSCTRL_LUT_EN (1 << 31) +#define GSCTRL_GPIXFMT Fld(4,27) +#define GSCTRL_GPIXFMT_INDEXED ((0x0) << FShft(GSCTRL_GPIXFMT)) +#define GSCTRL_GPIXFMT_ARGB4444 ((0x4) << FShft(GSCTRL_GPIXFMT)) +#define GSCTRL_GPIXFMT_ARGB1555 ((0x5) << FShft(GSCTRL_GPIXFMT)) +#define GSCTRL_GPIXFMT_RGB888 ((0x6) << FShft(GSCTRL_GPIXFMT)) +#define GSCTRL_GPIXFMT_RGB565 ((0x7) << FShft(GSCTRL_GPIXFMT)) +#define GSCTRL_GPIXFMT_ARGB8888 ((0x8) << FShft(GSCTRL_GPIXFMT)) +#define GSCTRL_GAMMA_EN (1 << 26) + +#define GSCTRL_GSWIDTH Fld(11,11) +#define Gsctrl_Width(Pixel) /* Display Width [1..2048 pix.] */ \ + (((Pixel) - 1) << FShft(GSCTRL_GSWIDTH)) + +#define GSCTRL_GSHEIGHT Fld(11,0) +#define Gsctrl_Height(Pixel) /* Display Height [1..2048 pix.] */ \ + (((Pixel) - 1) << FShft(GSCTRL_GSHEIGHT)) + +/* GBBASE fileds */ +#define GBBASE_GLALPHA Fld(8,24) +#define Gbbase_Glalpha(x) ((x) << FShft(GBBASE_GLALPHA)) + +#define GBBASE_COLKEY Fld(24,0) +#define Gbbase_Colkey(x) ((x) << FShft(GBBASE_COLKEY)) + +/* GDRCTRL fields */ +#define GDRCTRL_PIXDBL (1 << 31) +#define GDRCTRL_PIXHLV (1 << 30) +#define GDRCTRL_LNDBL (1 << 29) +#define GDRCTRL_LNHLV (1 << 28) +#define GDRCTRL_COLKEYM Fld(24,0) +#define Gdrctrl_Colkeym(x) ((x) << FShft(GDRCTRL_COLKEYM)) + +/* GSCADR graphics stream control address register fields */ +#define GSCADR_STR_EN (1 << 31) +#define GSCADR_COLKEY_EN (1 << 30) +#define GSCADR_COLKEYSRC (1 << 29) +#define GSCADR_BLEND_M Fld(2,27) +#define GSCADR_BLEND_NONE ((0x0) << FShft(GSCADR_BLEND_M)) +#define GSCADR_BLEND_INV ((0x1) << FShft(GSCADR_BLEND_M)) +#define GSCADR_BLEND_GLOB ((0x2) << FShft(GSCADR_BLEND_M)) +#define GSCADR_BLEND_PIX ((0x3) << FShft(GSCADR_BLEND_M)) +#define GSCADR_BLEND_POS Fld(2,24) +#define GSCADR_BLEND_GFX ((0x0) << FShft(GSCADR_BLEND_POS)) +#define GSCADR_BLEND_VID ((0x1) << FShft(GSCADR_BLEND_POS)) +#define GSCADR_BLEND_CUR ((0x2) << FShft(GSCADR_BLEND_POS)) +#define GSCADR_GBASE_ADR Fld(23,0) +#define Gscadr_Gbase_Adr(x) ((x) << FShft(GSCADR_GBASE_ADR)) + +/* GSADR graphics stride address register fields */ +#define GSADR_SRCSTRIDE Fld(10,22) +#define Gsadr_Srcstride(x) ((x) << FShft(GSADR_SRCSTRIDE)) +#define GSADR_XSTART Fld(11,11) +#define Gsadr_Xstart(x) ((x) << FShft(GSADR_XSTART)) +#define GSADR_YSTART Fld(11,0) +#define Gsadr_Ystart(y) ((y) << FShft(GSADR_YSTART)) + +/* GPLUT graphics palette register fields */ +#define GPLUT_LUTADR Fld(8,24) +#define Gplut_Lutadr(x) ((x) << FShft(GPLUT_LUTADR)) +#define GPLUT_LUTDATA Fld(24,0) +#define Gplut_Lutdata(x) ((x) << FShft(GPLUT_LUTDATA)) + +/* VSCTRL - Video Surface Control Register */ +#define VSCTRL_VPIXFMT Fld(4,27) +#define VSCTRL_VPIXFMT_YUV12 ((0x9) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_UY0VY1 ((0xc) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_VY0UY1 ((0xd) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_Y0UY1V ((0xe) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_Y0VY1U ((0xf) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_GAMMA_EN (1 << 26) +#define VSCTRL_CSC_EN (1 << 25) +#define VSCTRL_COSITED (1 << 22) +#define VSCTRL_VSWIDTH Fld(11,11) +#define Vsctrl_Width(Pixels) /* Video Width [1-2048] */ \ + (((Pixels) - 1) << FShft(VSCTRL_VSWIDTH)) +#define VSCTRL_VSHEIGHT Fld(11,0) +#define Vsctrl_Height(Pixels) /* Video Height [1-2048] */ \ + (((Pixels) - 1) << FShft(VSCTRL_VSHEIGHT)) + +/* VBBASE - Video Blending Base Register */ +#define VBBASE_GLALPHA Fld(8,24) +#define Vbbase_Glalpha(x) ((x) << FShft(VBBASE_GLALPHA)) + +#define VBBASE_COLKEY Fld(24,0) +#define Vbbase_Colkey(x) ((x) << FShft(VBBASE_COLKEY)) + +/* VCMSK - Video Color Key Mask Register */ +#define VCMSK_COLKEY_M Fld(24,0) +#define Vcmsk_colkey_m(x) ((x) << FShft(VCMSK_COLKEY_M)) + +/* VSCADR - Video Stream Control Rddress Register */ +#define VSCADR_STR_EN (1 << 31) +#define VSCADR_COLKEY_EN (1 << 30) +#define VSCADR_COLKEYSRC (1 << 29) +#define VSCADR_BLEND_M Fld(2,27) +#define VSCADR_BLEND_NONE ((0x0) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_INV ((0x1) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_GLOB ((0x2) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_PIX ((0x3) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_POS Fld(2,24) +#define VSCADR_BLEND_GFX ((0x0) << FShft(VSCADR_BLEND_POS)) +#define VSCADR_BLEND_VID ((0x1) << FShft(VSCADR_BLEND_POS)) +#define VSCADR_BLEND_CUR ((0x2) << FShft(VSCADR_BLEND_POS)) +#define VSCADR_VBASE_ADR Fld(23,0) +#define Vscadr_Vbase_Adr(x) ((x) << FShft(VSCADR_VBASE_ADR)) + +/* VUBASE - Video U Base Register */ +#define VUBASE_UVHALFSTR (1 << 31) +#define VUBASE_UBASE_ADR Fld(24,0) +#define Vubase_Ubase_Adr(x) ((x) << FShft(VUBASE_UBASE_ADR)) + +/* VVBASE - Video V Base Register */ +#define VVBASE_VBASE_ADR Fld(24,0) +#define Vvbase_Vbase_Adr(x) ((x) << FShft(VVBASE_VBASE_ADR)) + +/* VSADR - Video Stride Address Register */ +#define VSADR_SRCSTRIDE Fld(10,22) +#define Vsadr_Srcstride(x) ((x) << FShft(VSADR_SRCSTRIDE)) +#define VSADR_XSTART Fld(11,11) +#define Vsadr_Xstart(x) ((x) << FShft(VSADR_XSTART)) +#define VSADR_YSTART Fld(11,0) +#define Vsadr_Ystart(x) ((x) << FShft(VSADR_YSTART)) + +/* VSCTRL - Video Surface Control Register */ +#define VSCTRL_VPIXFMT Fld(4,27) +#define VSCTRL_VPIXFMT_YUV12 ((0x9) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_UY0VY1 ((0xc) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_VY0UY1 ((0xd) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_Y0UY1V ((0xe) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_VPIXFMT_Y0VY1U ((0xf) << FShft(VSCTRL_VPIXFMT)) +#define VSCTRL_GAMMA_EN (1 << 26) +#define VSCTRL_CSC_EN (1 << 25) +#define VSCTRL_COSITED (1 << 22) +#define VSCTRL_VSWIDTH Fld(11,11) +#define Vsctrl_Width(Pixels) /* Video Width [1-2048] */ \ + (((Pixels) - 1) << FShft(VSCTRL_VSWIDTH)) +#define VSCTRL_VSHEIGHT Fld(11,0) +#define Vsctrl_Height(Pixels) /* Video Height [1-2048] */ \ + (((Pixels) - 1) << FShft(VSCTRL_VSHEIGHT)) + +/* VBBASE - Video Blending Base Register */ +#define VBBASE_GLALPHA Fld(8,24) +#define Vbbase_Glalpha(x) ((x) << FShft(VBBASE_GLALPHA)) + +#define VBBASE_COLKEY Fld(24,0) +#define Vbbase_Colkey(x) ((x) << FShft(VBBASE_COLKEY)) + +/* VCMSK - Video Color Key Mask Register */ +#define VCMSK_COLKEY_M Fld(24,0) +#define Vcmsk_colkey_m(x) ((x) << FShft(VCMSK_COLKEY_M)) + +/* VSCADR - Video Stream Control Rddress Register */ +#define VSCADR_STR_EN (1 << 31) +#define VSCADR_COLKEY_EN (1 << 30) +#define VSCADR_COLKEYSRC (1 << 29) +#define VSCADR_BLEND_M Fld(2,27) +#define VSCADR_BLEND_NONE ((0x0) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_INV ((0x1) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_GLOB ((0x2) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_PIX ((0x3) << FShft(VSCADR_BLEND_M)) +#define VSCADR_BLEND_POS Fld(2,24) +#define VSCADR_BLEND_GFX ((0x0) << FShft(VSCADR_BLEND_POS)) +#define VSCADR_BLEND_VID ((0x1) << FShft(VSCADR_BLEND_POS)) +#define VSCADR_BLEND_CUR ((0x2) << FShft(VSCADR_BLEND_POS)) +#define VSCADR_VBASE_ADR Fld(23,0) +#define Vscadr_Vbase_Adr(x) ((x) << FShft(VSCADR_VBASE_ADR)) + +/* VUBASE - Video U Base Register */ +#define VUBASE_UVHALFSTR (1 << 31) +#define VUBASE_UBASE_ADR Fld(24,0) +#define Vubase_Ubase_Adr(x) ((x) << FShft(VUBASE_UBASE_ADR)) + +/* VVBASE - Video V Base Register */ +#define VVBASE_VBASE_ADR Fld(24,0) +#define Vvbase_Vbase_Adr(x) ((x) << FShft(VVBASE_VBASE_ADR)) + +/* VSADR - Video Stride Address Register */ +#define VSADR_SRCSTRIDE Fld(10,22) +#define Vsadr_Srcstride(x) ((x) << FShft(VSADR_SRCSTRIDE)) +#define VSADR_XSTART Fld(11,11) +#define Vsadr_Xstart(x) ((x) << FShft(VSADR_XSTART)) +#define VSADR_YSTART Fld(11,0) +#define Vsadr_Ystart(x) ((x) << FShft(VSADR_YSTART)) + +/* HCCTRL - Hardware Cursor Register fields */ +#define HCCTRL_CUR_EN (1 << 31) +#define HCCTRL_COLKEY_EN (1 << 29) +#define HCCTRL_COLKEYSRC (1 << 28) +#define HCCTRL_BLEND_M Fld(2,26) +#define HCCTRL_BLEND_NONE ((0x0) << FShft(HCCTRL_BLEND_M)) +#define HCCTRL_BLEND_INV ((0x1) << FShft(HCCTRL_BLEND_M)) +#define HCCTRL_BLEND_GLOB ((0x2) << FShft(HCCTRL_BLEND_M)) +#define HCCTRL_BLEND_PIX ((0x3) << FShft(HCCTRL_BLEND_M)) +#define HCCTRL_CPIXFMT Fld(3,23) +#define HCCTRL_CPIXFMT_RGB332 ((0x3) << FShft(HCCTRL_CPIXFMT)) +#define HCCTRL_CPIXFMT_ARGB4444 ((0x4) << FShft(HCCTRL_CPIXFMT)) +#define HCCTRL_CPIXFMT_ARGB1555 ((0x5) << FShft(HCCTRL_CPIXFMT)) +#define HCCTRL_CBASE_ADR Fld(23,0) +#define Hcctrl_Cbase_Adr(x) ((x) << FShft(HCCTRL_CBASE_ADR)) + +/* HCSIZE Hardware Cursor Size Register fields */ +#define HCSIZE_BLEND_POS Fld(2,29) +#define HCSIZE_BLEND_GFX ((0x0) << FShft(HCSIZE_BLEND_POS)) +#define HCSIZE_BLEND_VID ((0x1) << FShft(HCSIZE_BLEND_POS)) +#define HCSIZE_BLEND_CUR ((0x2) << FShft(HCSIZE_BLEND_POS)) +#define HCSIZE_CWIDTH Fld(3,16) +#define Hcsize_Cwidth(x) ((x) << FShft(HCSIZE_CWIDTH)) +#define HCSIZE_CHEIGHT Fld(3,0) +#define Hcsize_Cheight(x) ((x) << FShft(HCSIZE_CHEIGHT)) + +/* HCPOS Hardware Cursor Position Register fields */ +#define HCPOS_SWITCHSRC (1 << 30) +#define HCPOS_CURBLINK Fld(6,24) +#define Hcpos_Curblink(x) ((x) << FShft(HCPOS_CURBLINK)) +#define HCPOS_XSTART Fld(12,12) +#define Hcpos_Xstart(x) ((x) << FShft(HCPOS_XSTART)) +#define HCPOS_YSTART Fld(12,0) +#define Hcpos_Ystart(y) ((y) << FShft(HCPOS_YSTART)) + +/* HCBADR Hardware Cursor Blend Address Register */ +#define HCBADR_GLALPHA Fld(8,24) +#define Hcbadr_Glalpha(x) ((x) << FShft(HCBADR_GLALPHA)) +#define HCBADR_COLKEY Fld(24,0) +#define Hcbadr_Colkey(x) ((x) << FShft(HCBADR_COLKEY)) + +/* HCCKMSK - Hardware Cursor Color Key Mask Register */ +#define HCCKMSK_COLKEY_M Fld(24,0) +#define Hcckmsk_Colkey_M(x) ((x) << FShft(HCCKMSK_COLKEY_M)) + +/* DSCTRL - Display sync control register */ +#define DSCTRL_SYNCGEN_EN (1 << 31) +#define DSCTRL_DPL_RST (1 << 29) +#define DSCTRL_PWRDN_M (1 << 28) +#define DSCTRL_UPDSYNCCNT (1 << 26) +#define DSCTRL_UPDINTCNT (1 << 25) +#define DSCTRL_UPDCNT (1 << 24) +#define DSCTRL_UPDWAIT Fld(4,16) +#define Dsctrl_Updwait(x) ((x) << FShft(DSCTRL_UPDWAIT)) +#define DSCTRL_CLKPOL (1 << 11) +#define DSCTRL_CSYNC_EN (1 << 10) +#define DSCTRL_VS_SLAVE (1 << 7) +#define DSCTRL_HS_SLAVE (1 << 6) +#define DSCTRL_BLNK_POL (1 << 5) +#define DSCTRL_BLNK_DIS (1 << 4) +#define DSCTRL_VS_POL (1 << 3) +#define DSCTRL_VS_DIS (1 << 2) +#define DSCTRL_HS_POL (1 << 1) +#define DSCTRL_HS_DIS (1 << 0) + +/* DHT01 - Display horizontal timing register 01 */ +#define DHT01_HBPS Fld(12,16) +#define Dht01_Hbps(x) ((x) << FShft(DHT01_HBPS)) +#define DHT01_HT Fld(12,0) +#define Dht01_Ht(x) ((x) << FShft(DHT01_HT)) + +/* DHT02 - Display horizontal timing register 02 */ +#define DHT02_HAS Fld(12,16) +#define Dht02_Has(x) ((x) << FShft(DHT02_HAS)) +#define DHT02_HLBS Fld(12,0) +#define Dht02_Hlbs(x) ((x) << FShft(DHT02_HLBS)) + +/* DHT03 - Display horizontal timing register 03 */ +#define DHT03_HFPS Fld(12,16) +#define Dht03_Hfps(x) ((x) << FShft(DHT03_HFPS)) +#define DHT03_HRBS Fld(12,0) +#define Dht03_Hrbs(x) ((x) << FShft(DHT03_HRBS)) + +/* DVT01 - Display vertical timing register 01 */ +#define DVT01_VBPS Fld(12,16) +#define Dvt01_Vbps(x) ((x) << FShft(DVT01_VBPS)) +#define DVT01_VT Fld(12,0) +#define Dvt01_Vt(x) ((x) << FShft(DVT01_VT)) + +/* DVT02 - Display vertical timing register 02 */ +#define DVT02_VAS Fld(12,16) +#define Dvt02_Vas(x) ((x) << FShft(DVT02_VAS)) +#define DVT02_VTBS Fld(12,0) +#define Dvt02_Vtbs(x) ((x) << FShft(DVT02_VTBS)) + +/* DVT03 - Display vertical timing register 03 */ +#define DVT03_VFPS Fld(12,16) +#define Dvt03_Vfps(x) ((x) << FShft(DVT03_VFPS)) +#define DVT03_VBBS Fld(12,0) +#define Dvt03_Vbbs(x) ((x) << FShft(DVT03_VBBS)) + +/* DVECTRL - display vertical event control register */ +#define DVECTRL_VEVENT Fld(12,16) +#define Dvectrl_Vevent(x) ((x) << FShft(DVECTRL_VEVENT)) +#define DVECTRL_VFETCH Fld(12,0) +#define Dvectrl_Vfetch(x) ((x) << FShft(DVECTRL_VFETCH)) + +/* DHDET - display horizontal DE timing register */ +#define DHDET_HDES Fld(12,16) +#define Dhdet_Hdes(x) ((x) << FShft(DHDET_HDES)) +#define DHDET_HDEF Fld(12,0) +#define Dhdet_Hdef(x) ((x) << FShft(DHDET_HDEF)) + +/* DVDET - display vertical DE timing register */ +#define DVDET_VDES Fld(12,16) +#define Dvdet_Vdes(x) ((x) << FShft(DVDET_VDES)) +#define DVDET_VDEF Fld(12,0) +#define Dvdet_Vdef(x) ((x) << FShft(DVDET_VDEF)) + +/* DODMSK - display output data mask register */ +#define DODMSK_MASK_LVL (1 << 31) +#define DODMSK_BLNK_LVL (1 << 30) +#define DODMSK_MASK_B Fld(8,16) +#define Dodmsk_Mask_B(x) ((x) << FShft(DODMSK_MASK_B)) +#define DODMSK_MASK_G Fld(8,8) +#define Dodmsk_Mask_G(x) ((x) << FShft(DODMSK_MASK_G)) +#define DODMSK_MASK_R Fld(8,0) +#define Dodmsk_Mask_R(x) ((x) << FShft(DODMSK_MASK_R)) + +/* DBCOL - display border color control register */ +#define DBCOL_BORDCOL Fld(24,0) +#define Dbcol_Bordcol(x) ((x) << FShft(DBCOL_BORDCOL)) + +/* DVLNUM - display vertical line number register */ +#define DVLNUM_VLINE Fld(12,0) +#define Dvlnum_Vline(x) ((x) << FShft(DVLNUM_VLINE)) + +/* DMCTRL - Display Memory Control Register */ +#define DMCTRL_MEM_REF Fld(2,30) +#define DMCTRL_MEM_REF_ACT ((0x0) << FShft(DMCTRL_MEM_REF)) +#define DMCTRL_MEM_REF_HB ((0x1) << FShft(DMCTRL_MEM_REF)) +#define DMCTRL_MEM_REF_VB ((0x2) << FShft(DMCTRL_MEM_REF)) +#define DMCTRL_MEM_REF_BOTH ((0x3) << FShft(DMCTRL_MEM_REF)) +#define DMCTRL_UV_THRHLD Fld(6,24) +#define Dmctrl_Uv_Thrhld(x) ((x) << FShft(DMCTRL_UV_THRHLD)) +#define DMCTRL_V_THRHLD Fld(7,16) +#define Dmctrl_V_Thrhld(x) ((x) << FShft(DMCTRL_V_THRHLD)) +#define DMCTRL_D_THRHLD Fld(7,8) +#define Dmctrl_D_Thrhld(x) ((x) << FShft(DMCTRL_D_THRHLD)) +#define DMCTRL_BURSTLEN Fld(6,0) +#define Dmctrl_Burstlen(x) ((x) << FShft(DMCTRL_BURSTLEN)) + +/* DINTRS - Display Interrupt Status Register */ +#define DINTRS_CUR_OR_S (1 << 18) +#define DINTRS_STR2_OR_S (1 << 17) +#define DINTRS_STR1_OR_S (1 << 16) +#define DINTRS_CUR_UR_S (1 << 6) +#define DINTRS_STR2_UR_S (1 << 5) +#define DINTRS_STR1_UR_S (1 << 4) +#define DINTRS_VEVENT1_S (1 << 3) +#define DINTRS_VEVENT0_S (1 << 2) +#define DINTRS_HBLNK1_S (1 << 1) +#define DINTRS_HBLNK0_S (1 << 0) + +/* DINTRE - Display Interrupt Enable Register */ +#define DINTRE_CUR_OR_EN (1 << 18) +#define DINTRE_STR2_OR_EN (1 << 17) +#define DINTRE_STR1_OR_EN (1 << 16) +#define DINTRE_CUR_UR_EN (1 << 6) +#define DINTRE_STR2_UR_EN (1 << 5) +#define DINTRE_STR1_UR_EN (1 << 4) +#define DINTRE_VEVENT1_EN (1 << 3) +#define DINTRE_VEVENT0_EN (1 << 2) +#define DINTRE_HBLNK1_EN (1 << 1) +#define DINTRE_HBLNK0_EN (1 << 0) + +/* DINTRS - Display Interrupt Status Register */ +#define DINTRS_CUR_OR_S (1 << 18) +#define DINTRS_STR2_OR_S (1 << 17) +#define DINTRS_STR1_OR_S (1 << 16) +#define DINTRS_CUR_UR_S (1 << 6) +#define DINTRS_STR2_UR_S (1 << 5) +#define DINTRS_STR1_UR_S (1 << 4) +#define DINTRS_VEVENT1_S (1 << 3) +#define DINTRS_VEVENT0_S (1 << 2) +#define DINTRS_HBLNK1_S (1 << 1) +#define DINTRS_HBLNK0_S (1 << 0) + +/* DINTRE - Display Interrupt Enable Register */ +#define DINTRE_CUR_OR_EN (1 << 18) +#define DINTRE_STR2_OR_EN (1 << 17) +#define DINTRE_STR1_OR_EN (1 << 16) +#define DINTRE_CUR_UR_EN (1 << 6) +#define DINTRE_STR2_UR_EN (1 << 5) +#define DINTRE_STR1_UR_EN (1 << 4) +#define DINTRE_VEVENT1_EN (1 << 3) +#define DINTRE_VEVENT0_EN (1 << 2) +#define DINTRE_HBLNK1_EN (1 << 1) +#define DINTRE_HBLNK0_EN (1 << 0) + + +/* DLSTS - display load status register */ +#define DLSTS_RLD_ADONE (1 << 23) +/* #define DLSTS_RLD_ADOUT Fld(23,0) */ + +/* DLLCTRL - display list load control register */ +#define DLLCTRL_RLD_ADRLN Fld(8,24) +#define Dllctrl_Rld_Adrln(x) ((x) << FShft(DLLCTRL_RLD_ADRLN)) + +/* CLIPCTRL - Clipping Control Register */ +#define CLIPCTRL_HSKIP Fld(11,16) +#define Clipctrl_Hskip ((x) << FShft(CLIPCTRL_HSKIP)) +#define CLIPCTRL_VSKIP Fld(11,0) +#define Clipctrl_Vskip ((x) << FShft(CLIPCTRL_VSKIP)) + +/* SPOCTRL - Scale Pitch/Order Control Register */ +#define SPOCTRL_H_SC_BP (1 << 31) +#define SPOCTRL_V_SC_BP (1 << 30) +#define SPOCTRL_HV_SC_OR (1 << 29) +#define SPOCTRL_VS_UR_C (1 << 27) +#define SPOCTRL_VORDER Fld(2,16) +#define SPOCTRL_VORDER_1TAP ((0x0) << FShft(SPOCTRL_VORDER)) +#define SPOCTRL_VORDER_2TAP ((0x1) << FShft(SPOCTRL_VORDER)) +#define SPOCTRL_VORDER_4TAP ((0x3) << FShft(SPOCTRL_VORDER)) +#define SPOCTRL_VPITCH Fld(16,0) +#define Spoctrl_Vpitch(x) ((x) << FShft(SPOCTRL_VPITCH)) + +/* SVCTRL - Scale Vertical Control Register */ +#define SVCTRL_INITIAL1 Fld(16,16) +#define Svctrl_Initial1(x) ((x) << FShft(SVCTRL_INITIAL1)) +#define SVCTRL_INITIAL2 Fld(16,0) +#define Svctrl_Initial2(x) ((x) << FShft(SVCTRL_INITIAL2)) + +/* SHCTRL - Scale Horizontal Control Register */ +#define SHCTRL_HINITIAL Fld(16,16) +#define Shctrl_Hinitial(x) ((x) << FShft(SHCTRL_HINITIAL)) +#define SHCTRL_HDECIM (1 << 15) +#define SHCTRL_HPITCH Fld(15,0) +#define Shctrl_Hpitch(x) ((x) << FShft(SHCTRL_HPITCH)) + +/* SSSIZE - Scale Surface Size Register */ +#define SSSIZE_SC_WIDTH Fld(11,16) +#define Sssize_Sc_Width(x) ((x) << FShft(SSSIZE_SC_WIDTH)) +#define SSSIZE_SC_HEIGHT Fld(11,0) +#define Sssize_Sc_Height(x) ((x) << FShft(SSSIZE_SC_HEIGHT)) + +#endif /* __REG_BITS_2700G_ */ diff --git a/drivers/video/fbdev/mbx/regs.h b/drivers/video/fbdev/mbx/regs.h new file mode 100644 index 000000000000..063099d48839 --- /dev/null +++ b/drivers/video/fbdev/mbx/regs.h @@ -0,0 +1,195 @@ +#ifndef __REGS_2700G_ +#define __REGS_2700G_ + +/* extern unsigned long virt_base_2700; */ +/* #define __REG_2700G(x) (*(volatile unsigned long*)((x)+virt_base_2700)) */ +#define __REG_2700G(x) ((x)+virt_base_2700) + +/* System Configuration Registers (0x0000_0000 0x0000_0010) */ +#define SYSCFG __REG_2700G(0x00000000) +#define PFBASE __REG_2700G(0x00000004) +#define PFCEIL __REG_2700G(0x00000008) +#define POLLFLAG __REG_2700G(0x0000000c) +#define SYSRST __REG_2700G(0x00000010) + +/* Interrupt Control Registers (0x0000_0014 0x0000_002F) */ +#define NINTPW __REG_2700G(0x00000014) +#define MINTENABLE __REG_2700G(0x00000018) +#define MINTSTAT __REG_2700G(0x0000001c) +#define SINTENABLE __REG_2700G(0x00000020) +#define SINTSTAT __REG_2700G(0x00000024) +#define SINTCLR __REG_2700G(0x00000028) + +/* Clock Control Registers (0x0000_002C 0x0000_005F) */ +#define SYSCLKSRC __REG_2700G(0x0000002c) +#define PIXCLKSRC __REG_2700G(0x00000030) +#define CLKSLEEP __REG_2700G(0x00000034) +#define COREPLL __REG_2700G(0x00000038) +#define DISPPLL __REG_2700G(0x0000003c) +#define PLLSTAT __REG_2700G(0x00000040) +#define VOVRCLK __REG_2700G(0x00000044) +#define PIXCLK __REG_2700G(0x00000048) +#define MEMCLK __REG_2700G(0x0000004c) +#define M24CLK __REG_2700G(0x00000050) +#define MBXCLK __REG_2700G(0x00000054) +#define SDCLK __REG_2700G(0x00000058) +#define PIXCLKDIV __REG_2700G(0x0000005c) + +/* LCD Port Control Register (0x0000_0060 0x0000_006F) */ +#define LCD_CONFIG __REG_2700G(0x00000060) + +/* On-Die Frame Buffer Registers (0x0000_0064 0x0000_006B) */ +#define ODFBPWR __REG_2700G(0x00000064) +#define ODFBSTAT __REG_2700G(0x00000068) + +/* GPIO Registers (0x0000_006C 0x0000_007F) */ +#define GPIOCGF __REG_2700G(0x0000006c) +#define GPIOHI __REG_2700G(0x00000070) +#define GPIOLO __REG_2700G(0x00000074) +#define GPIOSTAT __REG_2700G(0x00000078) + +/* Pulse Width Modulator (PWM) Registers (0x0000_0200 0x0000_02FF) */ +#define PWMRST __REG_2700G(0x00000200) +#define PWMCFG __REG_2700G(0x00000204) +#define PWM0DIV __REG_2700G(0x00000210) +#define PWM0DUTY __REG_2700G(0x00000214) +#define PWM0PER __REG_2700G(0x00000218) +#define PWM1DIV __REG_2700G(0x00000220) +#define PWM1DUTY __REG_2700G(0x00000224) +#define PWM1PER __REG_2700G(0x00000228) + +/* Identification (ID) Registers (0x0000_0300 0x0000_0FFF) */ +#define ID __REG_2700G(0x00000FF0) + +/* Local Memory (SDRAM) Interface Registers (0x0000_1000 0x0000_1FFF) */ +#define LMRST __REG_2700G(0x00001000) +#define LMCFG __REG_2700G(0x00001004) +#define LMPWR __REG_2700G(0x00001008) +#define LMPWRSTAT __REG_2700G(0x0000100c) +#define LMCEMR __REG_2700G(0x00001010) +#define LMTYPE __REG_2700G(0x00001014) +#define LMTIM __REG_2700G(0x00001018) +#define LMREFRESH __REG_2700G(0x0000101c) +#define LMPROTMIN __REG_2700G(0x00001020) +#define LMPROTMAX __REG_2700G(0x00001024) +#define LMPROTCFG __REG_2700G(0x00001028) +#define LMPROTERR __REG_2700G(0x0000102c) + +/* Plane Controller Registers (0x0000_2000 0x0000_2FFF) */ +#define GSCTRL __REG_2700G(0x00002000) +#define VSCTRL __REG_2700G(0x00002004) +#define GBBASE __REG_2700G(0x00002020) +#define VBBASE __REG_2700G(0x00002024) +#define GDRCTRL __REG_2700G(0x00002040) +#define VCMSK __REG_2700G(0x00002044) +#define GSCADR __REG_2700G(0x00002060) +#define VSCADR __REG_2700G(0x00002064) +#define VUBASE __REG_2700G(0x00002084) +#define VVBASE __REG_2700G(0x000020a4) +#define GSADR __REG_2700G(0x000020c0) +#define VSADR __REG_2700G(0x000020c4) +#define HCCTRL __REG_2700G(0x00002100) +#define HCSIZE __REG_2700G(0x00002110) +#define HCPOS __REG_2700G(0x00002120) +#define HCBADR __REG_2700G(0x00002130) +#define HCCKMSK __REG_2700G(0x00002140) +#define GPLUT __REG_2700G(0x00002150) +#define DSCTRL __REG_2700G(0x00002154) +#define DHT01 __REG_2700G(0x00002158) +#define DHT02 __REG_2700G(0x0000215c) +#define DHT03 __REG_2700G(0x00002160) +#define DVT01 __REG_2700G(0x00002164) +#define DVT02 __REG_2700G(0x00002168) +#define DVT03 __REG_2700G(0x0000216c) +#define DBCOL __REG_2700G(0x00002170) +#define BGCOLOR __REG_2700G(0x00002174) +#define DINTRS __REG_2700G(0x00002178) +#define DINTRE __REG_2700G(0x0000217c) +#define DINTRCNT __REG_2700G(0x00002180) +#define DSIG __REG_2700G(0x00002184) +#define DMCTRL __REG_2700G(0x00002188) +#define CLIPCTRL __REG_2700G(0x0000218c) +#define SPOCTRL __REG_2700G(0x00002190) +#define SVCTRL __REG_2700G(0x00002194) + +/* 0x0000_2198 */ +/* 0x0000_21A8 VSCOEFF[0:4] Video Scalar Vertical Coefficient [0:4] 4.14.5 */ +#define VSCOEFF0 __REG_2700G(0x00002198) +#define VSCOEFF1 __REG_2700G(0x0000219c) +#define VSCOEFF2 __REG_2700G(0x000021a0) +#define VSCOEFF3 __REG_2700G(0x000021a4) +#define VSCOEFF4 __REG_2700G(0x000021a8) + +#define SHCTRL __REG_2700G(0x000021b0) + +/* 0x0000_21B4 */ +/* 0x0000_21D4 HSCOEFF[0:8] Video Scalar Horizontal Coefficient [0:8] 4.14.7 */ +#define HSCOEFF0 __REG_2700G(0x000021b4) +#define HSCOEFF1 __REG_2700G(0x000021b8) +#define HSCOEFF2 __REG_2700G(0x000021bc) +#define HSCOEFF3 __REG_2700G(0x000021c0) +#define HSCOEFF4 __REG_2700G(0x000021c4) +#define HSCOEFF5 __REG_2700G(0x000021c8) +#define HSCOEFF6 __REG_2700G(0x000021cc) +#define HSCOEFF7 __REG_2700G(0x000021d0) +#define HSCOEFF8 __REG_2700G(0x000021d4) + +#define SSSIZE __REG_2700G(0x000021D8) + +/* 0x0000_2200 */ +/* 0x0000_2240 VIDGAM[0:16] Video Gamma LUT Index [0:16] 4.15.2 */ +#define VIDGAM0 __REG_2700G(0x00002200) +#define VIDGAM1 __REG_2700G(0x00002204) +#define VIDGAM2 __REG_2700G(0x00002208) +#define VIDGAM3 __REG_2700G(0x0000220c) +#define VIDGAM4 __REG_2700G(0x00002210) +#define VIDGAM5 __REG_2700G(0x00002214) +#define VIDGAM6 __REG_2700G(0x00002218) +#define VIDGAM7 __REG_2700G(0x0000221c) +#define VIDGAM8 __REG_2700G(0x00002220) +#define VIDGAM9 __REG_2700G(0x00002224) +#define VIDGAM10 __REG_2700G(0x00002228) +#define VIDGAM11 __REG_2700G(0x0000222c) +#define VIDGAM12 __REG_2700G(0x00002230) +#define VIDGAM13 __REG_2700G(0x00002234) +#define VIDGAM14 __REG_2700G(0x00002238) +#define VIDGAM15 __REG_2700G(0x0000223c) +#define VIDGAM16 __REG_2700G(0x00002240) + +/* 0x0000_2250 */ +/* 0x0000_2290 GFXGAM[0:16] Graphics Gamma LUT Index [0:16] 4.15.3 */ +#define GFXGAM0 __REG_2700G(0x00002250) +#define GFXGAM1 __REG_2700G(0x00002254) +#define GFXGAM2 __REG_2700G(0x00002258) +#define GFXGAM3 __REG_2700G(0x0000225c) +#define GFXGAM4 __REG_2700G(0x00002260) +#define GFXGAM5 __REG_2700G(0x00002264) +#define GFXGAM6 __REG_2700G(0x00002268) +#define GFXGAM7 __REG_2700G(0x0000226c) +#define GFXGAM8 __REG_2700G(0x00002270) +#define GFXGAM9 __REG_2700G(0x00002274) +#define GFXGAM10 __REG_2700G(0x00002278) +#define GFXGAM11 __REG_2700G(0x0000227c) +#define GFXGAM12 __REG_2700G(0x00002280) +#define GFXGAM13 __REG_2700G(0x00002284) +#define GFXGAM14 __REG_2700G(0x00002288) +#define GFXGAM15 __REG_2700G(0x0000228c) +#define GFXGAM16 __REG_2700G(0x00002290) + +#define DLSTS __REG_2700G(0x00002300) +#define DLLCTRL __REG_2700G(0x00002304) +#define DVLNUM __REG_2700G(0x00002308) +#define DUCTRL __REG_2700G(0x0000230c) +#define DVECTRL __REG_2700G(0x00002310) +#define DHDET __REG_2700G(0x00002314) +#define DVDET __REG_2700G(0x00002318) +#define DODMSK __REG_2700G(0x0000231c) +#define CSC01 __REG_2700G(0x00002330) +#define CSC02 __REG_2700G(0x00002334) +#define CSC03 __REG_2700G(0x00002338) +#define CSC04 __REG_2700G(0x0000233c) +#define CSC05 __REG_2700G(0x00002340) + +#define FB_MEMORY_START __REG_2700G(0x00060000) + +#endif /* __REGS_2700G_ */ diff --git a/drivers/video/fbdev/metronomefb.c b/drivers/video/fbdev/metronomefb.c new file mode 100644 index 000000000000..195cc2db4c2c --- /dev/null +++ b/drivers/video/fbdev/metronomefb.c @@ -0,0 +1,780 @@ +/* + * linux/drivers/video/metronomefb.c -- FB driver for Metronome controller + * + * Copyright (C) 2008, Jaya Kumar + * + * 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. + * + * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. + * + * This work was made possible by help and equipment support from E-Ink + * Corporation. http://www.eink.com/ + * + * This driver is written to be used with the Metronome display controller. + * It is intended to be architecture independent. A board specific driver + * must be used to perform all the physical IO interactions. An example + * is provided as am200epd.c + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/firmware.h> +#include <linux/dma-mapping.h> +#include <linux/uaccess.h> +#include <linux/irq.h> + +#include <video/metronomefb.h> + +#include <asm/unaligned.h> + +/* Display specific information */ +#define DPY_W 832 +#define DPY_H 622 + +static int user_wfm_size; + +/* frame differs from image. frame includes non-visible pixels */ +struct epd_frame { + int fw; /* frame width */ + int fh; /* frame height */ + u16 config[4]; + int wfm_size; +}; + +static struct epd_frame epd_frame_table[] = { + { + .fw = 832, + .fh = 622, + .config = { + 15 /* sdlew */ + | 2 << 8 /* sdosz */ + | 0 << 11 /* sdor */ + | 0 << 12 /* sdces */ + | 0 << 15, /* sdcer */ + 42 /* gdspl */ + | 1 << 8 /* gdr1 */ + | 1 << 9 /* sdshr */ + | 0 << 15, /* gdspp */ + 18 /* gdspw */ + | 0 << 15, /* dispc */ + 599 /* vdlc */ + | 0 << 11 /* dsi */ + | 0 << 12, /* dsic */ + }, + .wfm_size = 47001, + }, + { + .fw = 1088, + .fh = 791, + .config = { + 0x0104, + 0x031f, + 0x0088, + 0x02ff, + }, + .wfm_size = 46770, + }, + { + .fw = 1200, + .fh = 842, + .config = { + 0x0101, + 0x030e, + 0x0012, + 0x0280, + }, + .wfm_size = 46770, + }, +}; + +static struct fb_fix_screeninfo metronomefb_fix = { + .id = "metronomefb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_STATIC_PSEUDOCOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .line_length = DPY_W, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo metronomefb_var = { + .xres = DPY_W, + .yres = DPY_H, + .xres_virtual = DPY_W, + .yres_virtual = DPY_H, + .bits_per_pixel = 8, + .grayscale = 1, + .nonstd = 1, + .red = { 4, 3, 0 }, + .green = { 0, 0, 0 }, + .blue = { 0, 0, 0 }, + .transp = { 0, 0, 0 }, +}; + +/* the waveform structure that is coming from userspace firmware */ +struct waveform_hdr { + u8 stuff[32]; + + u8 wmta[3]; + u8 fvsn; + + u8 luts; + u8 mc; + u8 trc; + u8 stuff3; + + u8 endb; + u8 swtb; + u8 stuff2a[2]; + + u8 stuff2b[3]; + u8 wfm_cs; +} __attribute__ ((packed)); + +/* main metronomefb functions */ +static u8 calc_cksum(int start, int end, u8 *mem) +{ + u8 tmp = 0; + int i; + + for (i = start; i < end; i++) + tmp += mem[i]; + + return tmp; +} + +static u16 calc_img_cksum(u16 *start, int length) +{ + u16 tmp = 0; + + while (length--) + tmp += *start++; + + return tmp; +} + +/* here we decode the incoming waveform file and populate metromem */ +static int load_waveform(u8 *mem, size_t size, int m, int t, + struct metronomefb_par *par) +{ + int tta; + int wmta; + int trn = 0; + int i; + unsigned char v; + u8 cksum; + int cksum_idx; + int wfm_idx, owfm_idx; + int mem_idx = 0; + struct waveform_hdr *wfm_hdr; + u8 *metromem = par->metromem_wfm; + struct device *dev = par->info->dev; + + if (user_wfm_size) + epd_frame_table[par->dt].wfm_size = user_wfm_size; + + if (size != epd_frame_table[par->dt].wfm_size) { + dev_err(dev, "Error: unexpected size %Zd != %d\n", size, + epd_frame_table[par->dt].wfm_size); + return -EINVAL; + } + + wfm_hdr = (struct waveform_hdr *) mem; + + if (wfm_hdr->fvsn != 1) { + dev_err(dev, "Error: bad fvsn %x\n", wfm_hdr->fvsn); + return -EINVAL; + } + if (wfm_hdr->luts != 0) { + dev_err(dev, "Error: bad luts %x\n", wfm_hdr->luts); + return -EINVAL; + } + cksum = calc_cksum(32, 47, mem); + if (cksum != wfm_hdr->wfm_cs) { + dev_err(dev, "Error: bad cksum %x != %x\n", cksum, + wfm_hdr->wfm_cs); + return -EINVAL; + } + wfm_hdr->mc += 1; + wfm_hdr->trc += 1; + for (i = 0; i < 5; i++) { + if (*(wfm_hdr->stuff2a + i) != 0) { + dev_err(dev, "Error: unexpected value in padding\n"); + return -EINVAL; + } + } + + /* calculating trn. trn is something used to index into + the waveform. presumably selecting the right one for the + desired temperature. it works out the offset of the first + v that exceeds the specified temperature */ + if ((sizeof(*wfm_hdr) + wfm_hdr->trc) > size) + return -EINVAL; + + for (i = sizeof(*wfm_hdr); i <= sizeof(*wfm_hdr) + wfm_hdr->trc; i++) { + if (mem[i] > t) { + trn = i - sizeof(*wfm_hdr) - 1; + break; + } + } + + /* check temperature range table checksum */ + cksum_idx = sizeof(*wfm_hdr) + wfm_hdr->trc + 1; + if (cksum_idx > size) + return -EINVAL; + cksum = calc_cksum(sizeof(*wfm_hdr), cksum_idx, mem); + if (cksum != mem[cksum_idx]) { + dev_err(dev, "Error: bad temperature range table cksum" + " %x != %x\n", cksum, mem[cksum_idx]); + return -EINVAL; + } + + /* check waveform mode table address checksum */ + wmta = get_unaligned_le32(wfm_hdr->wmta) & 0x00FFFFFF; + cksum_idx = wmta + m*4 + 3; + if (cksum_idx > size) + return -EINVAL; + cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem); + if (cksum != mem[cksum_idx]) { + dev_err(dev, "Error: bad mode table address cksum" + " %x != %x\n", cksum, mem[cksum_idx]); + return -EINVAL; + } + + /* check waveform temperature table address checksum */ + tta = get_unaligned_le32(mem + wmta + m * 4) & 0x00FFFFFF; + cksum_idx = tta + trn*4 + 3; + if (cksum_idx > size) + return -EINVAL; + cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem); + if (cksum != mem[cksum_idx]) { + dev_err(dev, "Error: bad temperature table address cksum" + " %x != %x\n", cksum, mem[cksum_idx]); + return -EINVAL; + } + + /* here we do the real work of putting the waveform into the + metromem buffer. this does runlength decoding of the waveform */ + wfm_idx = get_unaligned_le32(mem + tta + trn * 4) & 0x00FFFFFF; + owfm_idx = wfm_idx; + if (wfm_idx > size) + return -EINVAL; + while (wfm_idx < size) { + unsigned char rl; + v = mem[wfm_idx++]; + if (v == wfm_hdr->swtb) { + while (((v = mem[wfm_idx++]) != wfm_hdr->swtb) && + wfm_idx < size) + metromem[mem_idx++] = v; + + continue; + } + + if (v == wfm_hdr->endb) + break; + + rl = mem[wfm_idx++]; + for (i = 0; i <= rl; i++) + metromem[mem_idx++] = v; + } + + cksum_idx = wfm_idx; + if (cksum_idx > size) + return -EINVAL; + cksum = calc_cksum(owfm_idx, cksum_idx, mem); + if (cksum != mem[cksum_idx]) { + dev_err(dev, "Error: bad waveform data cksum" + " %x != %x\n", cksum, mem[cksum_idx]); + return -EINVAL; + } + par->frame_count = (mem_idx/64); + + return 0; +} + +static int metronome_display_cmd(struct metronomefb_par *par) +{ + int i; + u16 cs; + u16 opcode; + static u8 borderval; + + /* setup display command + we can't immediately set the opcode since the controller + will try parse the command before we've set it all up + so we just set cs here and set the opcode at the end */ + + if (par->metromem_cmd->opcode == 0xCC40) + opcode = cs = 0xCC41; + else + opcode = cs = 0xCC40; + + /* set the args ( 2 bytes ) for display */ + i = 0; + par->metromem_cmd->args[i] = 1 << 3 /* border update */ + | ((borderval++ % 4) & 0x0F) << 4 + | (par->frame_count - 1) << 8; + cs += par->metromem_cmd->args[i++]; + + /* the rest are 0 */ + memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2); + + par->metromem_cmd->csum = cs; + par->metromem_cmd->opcode = opcode; /* display cmd */ + + return par->board->met_wait_event_intr(par); +} + +static int metronome_powerup_cmd(struct metronomefb_par *par) +{ + int i; + u16 cs; + + /* setup power up command */ + par->metromem_cmd->opcode = 0x1234; /* pwr up pseudo cmd */ + cs = par->metromem_cmd->opcode; + + /* set pwr1,2,3 to 1024 */ + for (i = 0; i < 3; i++) { + par->metromem_cmd->args[i] = 1024; + cs += par->metromem_cmd->args[i]; + } + + /* the rest are 0 */ + memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2); + + par->metromem_cmd->csum = cs; + + msleep(1); + par->board->set_rst(par, 1); + + msleep(1); + par->board->set_stdby(par, 1); + + return par->board->met_wait_event(par); +} + +static int metronome_config_cmd(struct metronomefb_par *par) +{ + /* setup config command + we can't immediately set the opcode since the controller + will try parse the command before we've set it all up */ + + memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config, + sizeof(epd_frame_table[par->dt].config)); + /* the rest are 0 */ + memset((u8 *) (par->metromem_cmd->args + 4), 0, (32-4)*2); + + par->metromem_cmd->csum = 0xCC10; + par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4); + par->metromem_cmd->opcode = 0xCC10; /* config cmd */ + + return par->board->met_wait_event(par); +} + +static int metronome_init_cmd(struct metronomefb_par *par) +{ + int i; + u16 cs; + + /* setup init command + we can't immediately set the opcode since the controller + will try parse the command before we've set it all up + so we just set cs here and set the opcode at the end */ + + cs = 0xCC20; + + /* set the args ( 2 bytes ) for init */ + i = 0; + par->metromem_cmd->args[i] = 0; + cs += par->metromem_cmd->args[i++]; + + /* the rest are 0 */ + memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2); + + par->metromem_cmd->csum = cs; + par->metromem_cmd->opcode = 0xCC20; /* init cmd */ + + return par->board->met_wait_event(par); +} + +static int metronome_init_regs(struct metronomefb_par *par) +{ + int res; + + res = par->board->setup_io(par); + if (res) + return res; + + res = metronome_powerup_cmd(par); + if (res) + return res; + + res = metronome_config_cmd(par); + if (res) + return res; + + res = metronome_init_cmd(par); + + return res; +} + +static void metronomefb_dpy_update(struct metronomefb_par *par) +{ + int fbsize; + u16 cksum; + unsigned char *buf = (unsigned char __force *)par->info->screen_base; + + fbsize = par->info->fix.smem_len; + /* copy from vm to metromem */ + memcpy(par->metromem_img, buf, fbsize); + + cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2); + *((u16 *)(par->metromem_img) + fbsize/2) = cksum; + metronome_display_cmd(par); +} + +static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index) +{ + int i; + u16 csum = 0; + u16 *buf = (u16 __force *)(par->info->screen_base + index); + u16 *img = (u16 *)(par->metromem_img + index); + + /* swizzle from vm to metromem and recalc cksum at the same time*/ + for (i = 0; i < PAGE_SIZE/2; i++) { + *(img + i) = (buf[i] << 5) & 0xE0E0; + csum += *(img + i); + } + return csum; +} + +/* this is called back from the deferred io workqueue */ +static void metronomefb_dpy_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + u16 cksum; + struct page *cur; + struct fb_deferred_io *fbdefio = info->fbdefio; + struct metronomefb_par *par = info->par; + + /* walk the written page list and swizzle the data */ + list_for_each_entry(cur, &fbdefio->pagelist, lru) { + cksum = metronomefb_dpy_update_page(par, + (cur->index << PAGE_SHIFT)); + par->metromem_img_csum -= par->csum_table[cur->index]; + par->csum_table[cur->index] = cksum; + par->metromem_img_csum += cksum; + } + + metronome_display_cmd(par); +} + +static void metronomefb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct metronomefb_par *par = info->par; + + sys_fillrect(info, rect); + metronomefb_dpy_update(par); +} + +static void metronomefb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct metronomefb_par *par = info->par; + + sys_copyarea(info, area); + metronomefb_dpy_update(par); +} + +static void metronomefb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct metronomefb_par *par = info->par; + + sys_imageblit(info, image); + metronomefb_dpy_update(par); +} + +/* + * this is the slow path from userspace. they can seek and write to + * the fb. it is based on fb_sys_write + */ +static ssize_t metronomefb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct metronomefb_par *par = info->par; + unsigned long p = *ppos; + void *dst; + int err = 0; + unsigned long total_size; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + total_size = info->fix.smem_len; + + if (p > total_size) + return -EFBIG; + + if (count > total_size) { + err = -EFBIG; + count = total_size; + } + + if (count + p > total_size) { + if (!err) + err = -ENOSPC; + + count = total_size - p; + } + + dst = (void __force *)(info->screen_base + p); + + if (copy_from_user(dst, buf, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + metronomefb_dpy_update(par); + + return (err) ? err : count; +} + +static struct fb_ops metronomefb_ops = { + .owner = THIS_MODULE, + .fb_write = metronomefb_write, + .fb_fillrect = metronomefb_fillrect, + .fb_copyarea = metronomefb_copyarea, + .fb_imageblit = metronomefb_imageblit, +}; + +static struct fb_deferred_io metronomefb_defio = { + .delay = HZ, + .deferred_io = metronomefb_dpy_deferred_io, +}; + +static int metronomefb_probe(struct platform_device *dev) +{ + struct fb_info *info; + struct metronome_board *board; + int retval = -ENOMEM; + int videomemorysize; + unsigned char *videomemory; + struct metronomefb_par *par; + const struct firmware *fw_entry; + int i; + int panel_type; + int fw, fh; + int epd_dt_index; + + /* pick up board specific routines */ + board = dev->dev.platform_data; + if (!board) + return -EINVAL; + + /* try to count device specific driver, if can't, platform recalls */ + if (!try_module_get(board->owner)) + return -ENODEV; + + info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev); + if (!info) + goto err; + + /* we have two blocks of memory. + info->screen_base which is vm, and is the fb used by apps. + par->metromem which is physically contiguous memory and + contains the display controller commands, waveform, + processed image data and padding. this is the data pulled + by the device's LCD controller and pushed to Metronome. + the metromem memory is allocated by the board driver and + is provided to us */ + + panel_type = board->get_panel_type(); + switch (panel_type) { + case 6: + epd_dt_index = 0; + break; + case 8: + epd_dt_index = 1; + break; + case 97: + epd_dt_index = 2; + break; + default: + dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n"); + epd_dt_index = 0; + break; + } + + fw = epd_frame_table[epd_dt_index].fw; + fh = epd_frame_table[epd_dt_index].fh; + + /* we need to add a spare page because our csum caching scheme walks + * to the end of the page */ + videomemorysize = PAGE_SIZE + (fw * fh); + videomemory = vzalloc(videomemorysize); + if (!videomemory) + goto err_fb_rel; + + info->screen_base = (char __force __iomem *)videomemory; + info->fbops = &metronomefb_ops; + + metronomefb_fix.line_length = fw; + metronomefb_var.xres = fw; + metronomefb_var.yres = fh; + metronomefb_var.xres_virtual = fw; + metronomefb_var.yres_virtual = fh; + info->var = metronomefb_var; + info->fix = metronomefb_fix; + info->fix.smem_len = videomemorysize; + par = info->par; + par->info = info; + par->board = board; + par->dt = epd_dt_index; + init_waitqueue_head(&par->waitq); + + /* this table caches per page csum values. */ + par->csum_table = vmalloc(videomemorysize/PAGE_SIZE); + if (!par->csum_table) + goto err_vfree; + + /* the physical framebuffer that we use is setup by + * the platform device driver. It will provide us + * with cmd, wfm and image memory in a contiguous area. */ + retval = board->setup_fb(par); + if (retval) { + dev_err(&dev->dev, "Failed to setup fb\n"); + goto err_csum_table; + } + + /* after this point we should have a framebuffer */ + if ((!par->metromem_wfm) || (!par->metromem_img) || + (!par->metromem_dma)) { + dev_err(&dev->dev, "fb access failure\n"); + retval = -EINVAL; + goto err_csum_table; + } + + info->fix.smem_start = par->metromem_dma; + + /* load the waveform in. assume mode 3, temp 31 for now + a) request the waveform file from userspace + b) process waveform and decode into metromem */ + retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev); + if (retval < 0) { + dev_err(&dev->dev, "Failed to get waveform\n"); + goto err_csum_table; + } + + retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31, + par); + release_firmware(fw_entry); + if (retval < 0) { + dev_err(&dev->dev, "Failed processing waveform\n"); + goto err_csum_table; + } + + retval = board->setup_irq(info); + if (retval) + goto err_csum_table; + + retval = metronome_init_regs(par); + if (retval < 0) + goto err_free_irq; + + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + + info->fbdefio = &metronomefb_defio; + fb_deferred_io_init(info); + + retval = fb_alloc_cmap(&info->cmap, 8, 0); + if (retval < 0) { + dev_err(&dev->dev, "Failed to allocate colormap\n"); + goto err_free_irq; + } + + /* set cmap */ + for (i = 0; i < 8; i++) + info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16; + memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8); + memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8); + + retval = register_framebuffer(info); + if (retval < 0) + goto err_cmap; + + platform_set_drvdata(dev, info); + + dev_dbg(&dev->dev, + "fb%d: Metronome frame buffer device, using %dK of video" + " memory\n", info->node, videomemorysize >> 10); + + return 0; + +err_cmap: + fb_dealloc_cmap(&info->cmap); +err_free_irq: + board->cleanup(par); +err_csum_table: + vfree(par->csum_table); +err_vfree: + vfree(videomemory); +err_fb_rel: + framebuffer_release(info); +err: + module_put(board->owner); + return retval; +} + +static int metronomefb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + struct metronomefb_par *par = info->par; + + unregister_framebuffer(info); + fb_deferred_io_cleanup(info); + fb_dealloc_cmap(&info->cmap); + par->board->cleanup(par); + vfree(par->csum_table); + vfree((void __force *)info->screen_base); + module_put(par->board->owner); + dev_dbg(&dev->dev, "calling release\n"); + framebuffer_release(info); + } + return 0; +} + +static struct platform_driver metronomefb_driver = { + .probe = metronomefb_probe, + .remove = metronomefb_remove, + .driver = { + .owner = THIS_MODULE, + .name = "metronomefb", + }, +}; +module_platform_driver(metronomefb_driver); + +module_param(user_wfm_size, uint, 0); +MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size"); + +MODULE_DESCRIPTION("fbdev driver for Metronome controller"); +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mmp/Kconfig b/drivers/video/fbdev/mmp/Kconfig new file mode 100644 index 000000000000..d4a4ffc24749 --- /dev/null +++ b/drivers/video/fbdev/mmp/Kconfig @@ -0,0 +1,11 @@ +menuconfig MMP_DISP + tristate "Marvell MMP Display Subsystem support" + depends on CPU_PXA910 || CPU_MMP2 || CPU_MMP3 || CPU_PXA988 + help + Marvell Display Subsystem support. + +if MMP_DISP +source "drivers/video/fbdev/mmp/hw/Kconfig" +source "drivers/video/fbdev/mmp/panel/Kconfig" +source "drivers/video/fbdev/mmp/fb/Kconfig" +endif diff --git a/drivers/video/fbdev/mmp/Makefile b/drivers/video/fbdev/mmp/Makefile new file mode 100644 index 000000000000..a014cb358bf8 --- /dev/null +++ b/drivers/video/fbdev/mmp/Makefile @@ -0,0 +1 @@ +obj-y += core.o hw/ panel/ fb/ diff --git a/drivers/video/fbdev/mmp/core.c b/drivers/video/fbdev/mmp/core.c new file mode 100644 index 000000000000..b563b920f159 --- /dev/null +++ b/drivers/video/fbdev/mmp/core.c @@ -0,0 +1,251 @@ +/* + * linux/drivers/video/mmp/common.c + * This driver is a common framework for Marvell Display Controller + * + * Copyright (C) 2012 Marvell Technology Group Ltd. + * Authors: Zhou Zhu <zzhu3@marvell.com> + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/export.h> +#include <video/mmp_disp.h> + +static struct mmp_overlay *path_get_overlay(struct mmp_path *path, + int overlay_id) +{ + if (path && overlay_id < path->overlay_num) + return &path->overlays[overlay_id]; + return NULL; +} + +static int path_check_status(struct mmp_path *path) +{ + int i; + for (i = 0; i < path->overlay_num; i++) + if (path->overlays[i].status) + return 1; + + return 0; +} + +/* + * Get modelist write pointer of modelist. + * It also returns modelist number + * this function fetches modelist from phy/panel: + * for HDMI/parallel or dsi to hdmi cases, get from phy + * or get from panel + */ +static int path_get_modelist(struct mmp_path *path, + struct mmp_mode **modelist) +{ + BUG_ON(!path || !modelist); + + if (path->panel && path->panel->get_modelist) + return path->panel->get_modelist(path->panel, modelist); + + return 0; +} + +/* + * panel list is used to pair panel/path when path/panel registered + * path list is used for both buffer driver and platdriver + * plat driver do path register/unregister + * panel driver do panel register/unregister + * buffer driver get registered path + */ +static LIST_HEAD(panel_list); +static LIST_HEAD(path_list); +static DEFINE_MUTEX(disp_lock); + +/* + * mmp_register_panel - register panel to panel_list and connect to path + * @p: panel to be registered + * + * this function provides interface for panel drivers to register panel + * to panel_list and connect to path which matchs panel->plat_path_name. + * no error returns when no matching path is found as path register after + * panel register is permitted. + */ +void mmp_register_panel(struct mmp_panel *panel) +{ + struct mmp_path *path; + + mutex_lock(&disp_lock); + + /* add */ + list_add_tail(&panel->node, &panel_list); + + /* try to register to path */ + list_for_each_entry(path, &path_list, node) { + if (!strcmp(panel->plat_path_name, path->name)) { + dev_info(panel->dev, "connect to path %s\n", + path->name); + path->panel = panel; + break; + } + } + + mutex_unlock(&disp_lock); +} +EXPORT_SYMBOL_GPL(mmp_register_panel); + +/* + * mmp_unregister_panel - unregister panel from panel_list and disconnect + * @p: panel to be unregistered + * + * this function provides interface for panel drivers to unregister panel + * from panel_list and disconnect from path. + */ +void mmp_unregister_panel(struct mmp_panel *panel) +{ + struct mmp_path *path; + + mutex_lock(&disp_lock); + list_del(&panel->node); + + list_for_each_entry(path, &path_list, node) { + if (path->panel && path->panel == panel) { + dev_info(panel->dev, "disconnect from path %s\n", + path->name); + path->panel = NULL; + break; + } + } + mutex_unlock(&disp_lock); +} +EXPORT_SYMBOL_GPL(mmp_unregister_panel); + +/* + * mmp_get_path - get path by name + * @p: path name + * + * this function checks path name in path_list and return matching path + * return NULL if no matching path + */ +struct mmp_path *mmp_get_path(const char *name) +{ + struct mmp_path *path; + int found = 0; + + mutex_lock(&disp_lock); + list_for_each_entry(path, &path_list, node) { + if (!strcmp(name, path->name)) { + found = 1; + break; + } + } + mutex_unlock(&disp_lock); + + return found ? path : NULL; +} +EXPORT_SYMBOL_GPL(mmp_get_path); + +/* + * mmp_register_path - init and register path by path_info + * @p: path info provided by display controller + * + * this function init by path info and register path to path_list + * this function also try to connect path with panel by name + */ +struct mmp_path *mmp_register_path(struct mmp_path_info *info) +{ + int i; + size_t size; + struct mmp_path *path = NULL; + struct mmp_panel *panel; + + size = sizeof(struct mmp_path) + + sizeof(struct mmp_overlay) * info->overlay_num; + path = kzalloc(size, GFP_KERNEL); + if (!path) + return NULL; + + /* path set */ + mutex_init(&path->access_ok); + path->dev = info->dev; + path->id = info->id; + path->name = info->name; + path->output_type = info->output_type; + path->overlay_num = info->overlay_num; + path->plat_data = info->plat_data; + path->ops.set_mode = info->set_mode; + + mutex_lock(&disp_lock); + /* get panel */ + list_for_each_entry(panel, &panel_list, node) { + if (!strcmp(info->name, panel->plat_path_name)) { + dev_info(path->dev, "get panel %s\n", panel->name); + path->panel = panel; + break; + } + } + + dev_info(path->dev, "register %s, overlay_num %d\n", + path->name, path->overlay_num); + + /* default op set: if already set by driver, never cover it */ + if (!path->ops.check_status) + path->ops.check_status = path_check_status; + if (!path->ops.get_overlay) + path->ops.get_overlay = path_get_overlay; + if (!path->ops.get_modelist) + path->ops.get_modelist = path_get_modelist; + + /* step3: init overlays */ + for (i = 0; i < path->overlay_num; i++) { + path->overlays[i].path = path; + path->overlays[i].id = i; + mutex_init(&path->overlays[i].access_ok); + path->overlays[i].ops = info->overlay_ops; + } + + /* add to pathlist */ + list_add_tail(&path->node, &path_list); + + mutex_unlock(&disp_lock); + return path; +} +EXPORT_SYMBOL_GPL(mmp_register_path); + +/* + * mmp_unregister_path - unregister and destory path + * @p: path to be destoried. + * + * this function registers path and destorys it. + */ +void mmp_unregister_path(struct mmp_path *path) +{ + int i; + + if (!path) + return; + + mutex_lock(&disp_lock); + /* del from pathlist */ + list_del(&path->node); + + /* deinit overlays */ + for (i = 0; i < path->overlay_num; i++) + mutex_destroy(&path->overlays[i].access_ok); + + mutex_destroy(&path->access_ok); + + kfree(path); + mutex_unlock(&disp_lock); +} +EXPORT_SYMBOL_GPL(mmp_unregister_path); diff --git a/drivers/video/fbdev/mmp/fb/Kconfig b/drivers/video/fbdev/mmp/fb/Kconfig new file mode 100644 index 000000000000..9b0141f105f5 --- /dev/null +++ b/drivers/video/fbdev/mmp/fb/Kconfig @@ -0,0 +1,13 @@ +if MMP_DISP + +config MMP_FB + bool "fb driver for Marvell MMP Display Subsystem" + depends on FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + default y + help + fb driver for Marvell MMP Display Subsystem + +endif diff --git a/drivers/video/fbdev/mmp/fb/Makefile b/drivers/video/fbdev/mmp/fb/Makefile new file mode 100644 index 000000000000..709fd1f76abe --- /dev/null +++ b/drivers/video/fbdev/mmp/fb/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MMP_FB) += mmpfb.o diff --git a/drivers/video/fbdev/mmp/fb/mmpfb.c b/drivers/video/fbdev/mmp/fb/mmpfb.c new file mode 100644 index 000000000000..7ab31eb76a8c --- /dev/null +++ b/drivers/video/fbdev/mmp/fb/mmpfb.c @@ -0,0 +1,694 @@ +/* + * linux/drivers/video/mmp/fb/mmpfb.c + * Framebuffer driver for Marvell Display controller. + * + * Copyright (C) 2012 Marvell Technology Group Ltd. + * Authors: Zhou Zhu <zzhu3@marvell.com> + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include "mmpfb.h" + +static int var_to_pixfmt(struct fb_var_screeninfo *var) +{ + /* + * Pseudocolor mode? + */ + if (var->bits_per_pixel == 8) + return PIXFMT_PSEUDOCOLOR; + + /* + * Check for YUV422PLANAR. + */ + if (var->bits_per_pixel == 16 && var->red.length == 8 && + var->green.length == 4 && var->blue.length == 4) { + if (var->green.offset >= var->blue.offset) + return PIXFMT_YUV422P; + else + return PIXFMT_YVU422P; + } + + /* + * Check for YUV420PLANAR. + */ + if (var->bits_per_pixel == 12 && var->red.length == 8 && + var->green.length == 2 && var->blue.length == 2) { + if (var->green.offset >= var->blue.offset) + return PIXFMT_YUV420P; + else + return PIXFMT_YVU420P; + } + + /* + * Check for YUV422PACK. + */ + if (var->bits_per_pixel == 16 && var->red.length == 16 && + var->green.length == 16 && var->blue.length == 16) { + if (var->red.offset == 0) + return PIXFMT_YUYV; + else if (var->green.offset >= var->blue.offset) + return PIXFMT_UYVY; + else + return PIXFMT_VYUY; + } + + /* + * Check for 565/1555. + */ + if (var->bits_per_pixel == 16 && var->red.length <= 5 && + var->green.length <= 6 && var->blue.length <= 5) { + if (var->transp.length == 0) { + if (var->red.offset >= var->blue.offset) + return PIXFMT_RGB565; + else + return PIXFMT_BGR565; + } + } + + /* + * Check for 888/A888. + */ + if (var->bits_per_pixel <= 32 && var->red.length <= 8 && + var->green.length <= 8 && var->blue.length <= 8) { + if (var->bits_per_pixel == 24 && var->transp.length == 0) { + if (var->red.offset >= var->blue.offset) + return PIXFMT_RGB888PACK; + else + return PIXFMT_BGR888PACK; + } + + if (var->bits_per_pixel == 32 && var->transp.offset == 24) { + if (var->red.offset >= var->blue.offset) + return PIXFMT_RGBA888; + else + return PIXFMT_BGRA888; + } else { + if (var->red.offset >= var->blue.offset) + return PIXFMT_RGB888UNPACK; + else + return PIXFMT_BGR888UNPACK; + } + + /* fall through */ + } + + return -EINVAL; +} + +static void pixfmt_to_var(struct fb_var_screeninfo *var, int pix_fmt) +{ + switch (pix_fmt) { + case PIXFMT_RGB565: + var->bits_per_pixel = 16; + var->red.offset = 11; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 0; var->blue.length = 5; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_BGR565: + var->bits_per_pixel = 16; + var->red.offset = 0; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 11; var->blue.length = 5; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_RGB888UNPACK: + var->bits_per_pixel = 32; + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_BGR888UNPACK: + var->bits_per_pixel = 32; + var->red.offset = 0; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 16; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_RGBA888: + var->bits_per_pixel = 32; + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 24; var->transp.length = 8; + break; + case PIXFMT_BGRA888: + var->bits_per_pixel = 32; + var->red.offset = 0; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 16; var->blue.length = 8; + var->transp.offset = 24; var->transp.length = 8; + break; + case PIXFMT_RGB888PACK: + var->bits_per_pixel = 24; + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_BGR888PACK: + var->bits_per_pixel = 24; + var->red.offset = 0; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 16; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_YUV420P: + var->bits_per_pixel = 12; + var->red.offset = 4; var->red.length = 8; + var->green.offset = 2; var->green.length = 2; + var->blue.offset = 0; var->blue.length = 2; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_YVU420P: + var->bits_per_pixel = 12; + var->red.offset = 4; var->red.length = 8; + var->green.offset = 0; var->green.length = 2; + var->blue.offset = 2; var->blue.length = 2; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_YUV422P: + var->bits_per_pixel = 16; + var->red.offset = 8; var->red.length = 8; + var->green.offset = 4; var->green.length = 4; + var->blue.offset = 0; var->blue.length = 4; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_YVU422P: + var->bits_per_pixel = 16; + var->red.offset = 8; var->red.length = 8; + var->green.offset = 0; var->green.length = 4; + var->blue.offset = 4; var->blue.length = 4; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_UYVY: + var->bits_per_pixel = 16; + var->red.offset = 8; var->red.length = 16; + var->green.offset = 4; var->green.length = 16; + var->blue.offset = 0; var->blue.length = 16; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_VYUY: + var->bits_per_pixel = 16; + var->red.offset = 8; var->red.length = 16; + var->green.offset = 0; var->green.length = 16; + var->blue.offset = 4; var->blue.length = 16; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_YUYV: + var->bits_per_pixel = 16; + var->red.offset = 0; var->red.length = 16; + var->green.offset = 4; var->green.length = 16; + var->blue.offset = 8; var->blue.length = 16; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIXFMT_PSEUDOCOLOR: + var->bits_per_pixel = 8; + var->red.offset = 0; var->red.length = 8; + var->green.offset = 0; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + } +} + +/* + * fb framework has its limitation: + * 1. input color/output color is not seprated + * 2. fb_videomode not include output color + * so for fb usage, we keep a output format which is not changed + * then it's added for mmpmode + */ +static void fbmode_to_mmpmode(struct mmp_mode *mode, + struct fb_videomode *videomode, int output_fmt) +{ + u64 div_result = 1000000000000ll; + mode->name = videomode->name; + mode->refresh = videomode->refresh; + mode->xres = videomode->xres; + mode->yres = videomode->yres; + + do_div(div_result, videomode->pixclock); + mode->pixclock_freq = (u32)div_result; + + mode->left_margin = videomode->left_margin; + mode->right_margin = videomode->right_margin; + mode->upper_margin = videomode->upper_margin; + mode->lower_margin = videomode->lower_margin; + mode->hsync_len = videomode->hsync_len; + mode->vsync_len = videomode->vsync_len; + mode->hsync_invert = !!(videomode->sync & FB_SYNC_HOR_HIGH_ACT); + mode->vsync_invert = !!(videomode->sync & FB_SYNC_VERT_HIGH_ACT); + /* no defined flag in fb, use vmode>>3*/ + mode->invert_pixclock = !!(videomode->vmode & 8); + mode->pix_fmt_out = output_fmt; +} + +static void mmpmode_to_fbmode(struct fb_videomode *videomode, + struct mmp_mode *mode) +{ + u64 div_result = 1000000000000ll; + + videomode->name = mode->name; + videomode->refresh = mode->refresh; + videomode->xres = mode->xres; + videomode->yres = mode->yres; + + do_div(div_result, mode->pixclock_freq); + videomode->pixclock = (u32)div_result; + + videomode->left_margin = mode->left_margin; + videomode->right_margin = mode->right_margin; + videomode->upper_margin = mode->upper_margin; + videomode->lower_margin = mode->lower_margin; + videomode->hsync_len = mode->hsync_len; + videomode->vsync_len = mode->vsync_len; + videomode->sync = (mode->hsync_invert ? FB_SYNC_HOR_HIGH_ACT : 0) + | (mode->vsync_invert ? FB_SYNC_VERT_HIGH_ACT : 0); + videomode->vmode = mode->invert_pixclock ? 8 : 0; +} + +static int mmpfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mmpfb_info *fbi = info->par; + + if (var->bits_per_pixel == 8) + return -EINVAL; + /* + * Basic geometry sanity checks. + */ + if (var->xoffset + var->xres > var->xres_virtual) + return -EINVAL; + if (var->yoffset + var->yres > var->yres_virtual) + return -EINVAL; + + /* + * Check size of framebuffer. + */ + if (var->xres_virtual * var->yres_virtual * + (var->bits_per_pixel >> 3) > fbi->fb_size) + return -EINVAL; + + return 0; +} + +static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) +{ + return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset; +} + +static u32 to_rgb(u16 red, u16 green, u16 blue) +{ + red >>= 8; + green >>= 8; + blue >>= 8; + + return (red << 16) | (green << 8) | blue; +} + +static int mmpfb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int trans, struct fb_info *info) +{ + struct mmpfb_info *fbi = info->par; + u32 val; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR && 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); + fbi->pseudo_palette[regno] = val; + } + + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR && regno < 256) { + val = to_rgb(red, green, blue); + /* TODO */ + } + + return 0; +} + +static int mmpfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mmpfb_info *fbi = info->par; + struct mmp_addr addr; + + memset(&addr, 0, sizeof(addr)); + addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset) + * var->bits_per_pixel / 8 + fbi->fb_start_dma; + mmp_overlay_set_addr(fbi->overlay, &addr); + + return 0; +} + +static int var_update(struct fb_info *info) +{ + struct mmpfb_info *fbi = info->par; + struct fb_var_screeninfo *var = &info->var; + struct fb_videomode *m; + int pix_fmt; + + /* set pix_fmt */ + pix_fmt = var_to_pixfmt(var); + if (pix_fmt < 0) + return -EINVAL; + pixfmt_to_var(var, pix_fmt); + fbi->pix_fmt = pix_fmt; + + /* set var according to best video mode*/ + m = (struct fb_videomode *)fb_match_mode(var, &info->modelist); + if (!m) { + dev_err(fbi->dev, "set par: no match mode, use best mode\n"); + m = (struct fb_videomode *)fb_find_best_mode(var, + &info->modelist); + fb_videomode_to_var(var, m); + } + memcpy(&fbi->mode, m, sizeof(struct fb_videomode)); + + /* fix to 2* yres */ + var->yres_virtual = var->yres * 2; + info->fix.visual = (pix_fmt == PIXFMT_PSEUDOCOLOR) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; + info->fix.ypanstep = var->yres; + return 0; +} + +static void mmpfb_set_win(struct fb_info *info) +{ + struct mmpfb_info *fbi = info->par; + struct fb_var_screeninfo *var = &info->var; + struct mmp_win win; + u32 stride; + + memset(&win, 0, sizeof(win)); + win.xsrc = win.xdst = fbi->mode.xres; + win.ysrc = win.ydst = fbi->mode.yres; + win.pix_fmt = fbi->pix_fmt; + stride = pixfmt_to_stride(win.pix_fmt); + win.pitch[0] = var->xres_virtual * stride; + win.pitch[1] = win.pitch[2] = + (stride == 1) ? (var->xres_virtual >> 1) : 0; + mmp_overlay_set_win(fbi->overlay, &win); +} + +static int mmpfb_set_par(struct fb_info *info) +{ + struct mmpfb_info *fbi = info->par; + struct fb_var_screeninfo *var = &info->var; + struct mmp_addr addr; + struct mmp_mode mode; + int ret; + + ret = var_update(info); + if (ret != 0) + return ret; + + /* set window/path according to new videomode */ + fbmode_to_mmpmode(&mode, &fbi->mode, fbi->output_fmt); + mmp_path_set_mode(fbi->path, &mode); + + /* set window related info */ + mmpfb_set_win(info); + + /* set address always */ + memset(&addr, 0, sizeof(addr)); + addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset) + * var->bits_per_pixel / 8 + fbi->fb_start_dma; + mmp_overlay_set_addr(fbi->overlay, &addr); + + return 0; +} + +static void mmpfb_power(struct mmpfb_info *fbi, int power) +{ + struct mmp_addr addr; + struct fb_var_screeninfo *var = &fbi->fb_info->var; + + /* for power on, always set address/window again */ + if (power) { + /* set window related info */ + mmpfb_set_win(fbi->fb_info); + + /* set address always */ + memset(&addr, 0, sizeof(addr)); + addr.phys[0] = fbi->fb_start_dma + + (var->yoffset * var->xres_virtual + var->xoffset) + * var->bits_per_pixel / 8; + mmp_overlay_set_addr(fbi->overlay, &addr); + } + mmp_overlay_set_onoff(fbi->overlay, power); +} + +static int mmpfb_blank(int blank, struct fb_info *info) +{ + struct mmpfb_info *fbi = info->par; + + mmpfb_power(fbi, (blank == FB_BLANK_UNBLANK)); + + return 0; +} + +static struct fb_ops mmpfb_ops = { + .owner = THIS_MODULE, + .fb_blank = mmpfb_blank, + .fb_check_var = mmpfb_check_var, + .fb_set_par = mmpfb_set_par, + .fb_setcolreg = mmpfb_setcolreg, + .fb_pan_display = mmpfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int modes_setup(struct mmpfb_info *fbi) +{ + struct fb_videomode *videomodes; + struct mmp_mode *mmp_modes; + struct fb_info *info = fbi->fb_info; + int videomode_num, i; + + /* get videomodes from path */ + videomode_num = mmp_path_get_modelist(fbi->path, &mmp_modes); + if (!videomode_num) { + dev_warn(fbi->dev, "can't get videomode num\n"); + return 0; + } + /* put videomode list to info structure */ + videomodes = kzalloc(sizeof(struct fb_videomode) * videomode_num, + GFP_KERNEL); + if (!videomodes) { + dev_err(fbi->dev, "can't malloc video modes\n"); + return -ENOMEM; + } + for (i = 0; i < videomode_num; i++) + mmpmode_to_fbmode(&videomodes[i], &mmp_modes[i]); + fb_videomode_to_modelist(videomodes, videomode_num, &info->modelist); + + /* set videomode[0] as default mode */ + memcpy(&fbi->mode, &videomodes[0], sizeof(struct fb_videomode)); + fbi->output_fmt = mmp_modes[0].pix_fmt_out; + fb_videomode_to_var(&info->var, &fbi->mode); + mmp_path_set_mode(fbi->path, &mmp_modes[0]); + + kfree(videomodes); + return videomode_num; +} + +static int fb_info_setup(struct fb_info *info, + struct mmpfb_info *fbi) +{ + int ret = 0; + /* Initialise static fb parameters.*/ + info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | + FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; + info->node = -1; + strcpy(info->fix.id, fbi->name); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = info->var.yres; + info->fix.ywrapstep = 0; + info->fix.accel = FB_ACCEL_NONE; + info->fix.smem_start = fbi->fb_start_dma; + info->fix.smem_len = fbi->fb_size; + info->fix.visual = (fbi->pix_fmt == PIXFMT_PSEUDOCOLOR) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.xres_virtual * + info->var.bits_per_pixel / 8; + info->fbops = &mmpfb_ops; + info->pseudo_palette = fbi->pseudo_palette; + info->screen_base = fbi->fb_start; + info->screen_size = fbi->fb_size; + + /* For FB framework: Allocate color map and Register framebuffer*/ + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) + ret = -ENOMEM; + + return ret; +} + +static void fb_info_clear(struct fb_info *info) +{ + fb_dealloc_cmap(&info->cmap); +} + +static int mmpfb_probe(struct platform_device *pdev) +{ + struct mmp_buffer_driver_mach_info *mi; + struct fb_info *info = 0; + struct mmpfb_info *fbi = 0; + int ret, modes_num; + + mi = pdev->dev.platform_data; + if (mi == NULL) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -EINVAL; + } + + /* initialize fb */ + info = framebuffer_alloc(sizeof(struct mmpfb_info), &pdev->dev); + if (info == NULL) + return -ENOMEM; + fbi = info->par; + if (!fbi) { + ret = -EINVAL; + goto failed; + } + + /* init fb */ + fbi->fb_info = info; + platform_set_drvdata(pdev, fbi); + fbi->dev = &pdev->dev; + fbi->name = mi->name; + fbi->pix_fmt = mi->default_pixfmt; + pixfmt_to_var(&info->var, fbi->pix_fmt); + mutex_init(&fbi->access_ok); + + /* get display path by name */ + fbi->path = mmp_get_path(mi->path_name); + if (!fbi->path) { + dev_err(&pdev->dev, "can't get the path %s\n", mi->path_name); + ret = -EINVAL; + goto failed_destroy_mutex; + } + + dev_info(fbi->dev, "path %s get\n", fbi->path->name); + + /* get overlay */ + fbi->overlay = mmp_path_get_overlay(fbi->path, mi->overlay_id); + if (!fbi->overlay) { + ret = -EINVAL; + goto failed_destroy_mutex; + } + /* set fetch used */ + mmp_overlay_set_fetch(fbi->overlay, mi->dmafetch_id); + + modes_num = modes_setup(fbi); + if (modes_num < 0) { + ret = modes_num; + goto failed_destroy_mutex; + } + + /* + * if get modes success, means not hotplug panels, use caculated buffer + * or use default size + */ + if (modes_num > 0) { + /* fix to 2* yres */ + info->var.yres_virtual = info->var.yres * 2; + + /* Allocate framebuffer memory: size = modes xy *4 */ + fbi->fb_size = info->var.xres_virtual * info->var.yres_virtual + * info->var.bits_per_pixel / 8; + } else { + fbi->fb_size = MMPFB_DEFAULT_SIZE; + } + + fbi->fb_start = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), + &fbi->fb_start_dma, GFP_KERNEL); + if (fbi->fb_start == NULL) { + dev_err(&pdev->dev, "can't alloc framebuffer\n"); + ret = -ENOMEM; + goto failed_destroy_mutex; + } + memset(fbi->fb_start, 0, fbi->fb_size); + dev_info(fbi->dev, "fb %dk allocated\n", fbi->fb_size/1024); + + /* fb power on */ + if (modes_num > 0) + mmpfb_power(fbi, 1); + + ret = fb_info_setup(info, fbi); + if (ret < 0) + goto failed_free_buff; + + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register fb: %d\n", ret); + ret = -ENXIO; + goto failed_clear_info; + } + + dev_info(fbi->dev, "loaded to /dev/fb%d <%s>.\n", + info->node, info->fix.id); + +#ifdef CONFIG_LOGO + if (fbi->fb_start) { + fb_prepare_logo(info, 0); + fb_show_logo(info, 0); + } +#endif + + return 0; + +failed_clear_info: + fb_info_clear(info); +failed_free_buff: + dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), fbi->fb_start, + fbi->fb_start_dma); +failed_destroy_mutex: + mutex_destroy(&fbi->access_ok); +failed: + dev_err(fbi->dev, "mmp-fb: frame buffer device init failed\n"); + + framebuffer_release(info); + + return ret; +} + +static struct platform_driver mmpfb_driver = { + .driver = { + .name = "mmp-fb", + .owner = THIS_MODULE, + }, + .probe = mmpfb_probe, +}; + +static int mmpfb_init(void) +{ + return platform_driver_register(&mmpfb_driver); +} +module_init(mmpfb_init); + +MODULE_AUTHOR("Zhou Zhu <zhou.zhu@marvell.com>"); +MODULE_DESCRIPTION("Framebuffer driver for Marvell displays"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mmp/fb/mmpfb.h b/drivers/video/fbdev/mmp/fb/mmpfb.h new file mode 100644 index 000000000000..88c23c10a9ec --- /dev/null +++ b/drivers/video/fbdev/mmp/fb/mmpfb.h @@ -0,0 +1,54 @@ +/* + * linux/drivers/video/mmp/fb/mmpfb.h + * Framebuffer driver for Marvell Display controller. + * + * Copyright (C) 2012 Marvell Technology Group Ltd. + * Authors: Zhou Zhu <zzhu3@marvell.com> + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _MMP_FB_H_ +#define _MMP_FB_H_ + +#include <video/mmp_disp.h> +#include <linux/fb.h> + +/* LCD controller private state. */ +struct mmpfb_info { + struct device *dev; + int id; + const char *name; + + struct fb_info *fb_info; + /* basicaly videomode is for output */ + struct fb_videomode mode; + int pix_fmt; + + void *fb_start; + int fb_size; + dma_addr_t fb_start_dma; + + struct mmp_overlay *overlay; + struct mmp_path *path; + + struct mutex access_ok; + + unsigned int pseudo_palette[16]; + int output_fmt; +}; + +#define MMPFB_DEFAULT_SIZE (PAGE_ALIGN(1920 * 1080 * 4 * 2)) +#endif /* _MMP_FB_H_ */ diff --git a/drivers/video/fbdev/mmp/hw/Kconfig b/drivers/video/fbdev/mmp/hw/Kconfig new file mode 100644 index 000000000000..02f109a20cd0 --- /dev/null +++ b/drivers/video/fbdev/mmp/hw/Kconfig @@ -0,0 +1,20 @@ +if MMP_DISP + +config MMP_DISP_CONTROLLER + bool "mmp display controller hw support" + depends on CPU_PXA910 || CPU_MMP2 || CPU_MMP3 || CPU_PXA988 + default n + help + Marvell MMP display hw controller support + this controller is used on Marvell PXA910, + MMP2, MMP3, PXA988 chips + +config MMP_DISP_SPI + bool "mmp display controller spi port" + depends on MMP_DISP_CONTROLLER && SPI_MASTER + default y + help + Marvell MMP display hw controller spi port support + will register as a spi master for panel usage + +endif diff --git a/drivers/video/fbdev/mmp/hw/Makefile b/drivers/video/fbdev/mmp/hw/Makefile new file mode 100644 index 000000000000..0000a714fedf --- /dev/null +++ b/drivers/video/fbdev/mmp/hw/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MMP_DISP_CONTROLLER) += mmp_ctrl.o +obj-$(CONFIG_MMP_DISP_SPI) += mmp_spi.o diff --git a/drivers/video/fbdev/mmp/hw/mmp_ctrl.c b/drivers/video/fbdev/mmp/hw/mmp_ctrl.c new file mode 100644 index 000000000000..8621a9f2bdcc --- /dev/null +++ b/drivers/video/fbdev/mmp/hw/mmp_ctrl.c @@ -0,0 +1,588 @@ +/* + * linux/drivers/video/mmp/hw/mmp_ctrl.c + * Marvell MMP series Display Controller support + * + * Copyright (C) 2012 Marvell Technology Group Ltd. + * Authors: Guoqing Li <ligq@marvell.com> + * Lisa Du <cldu@marvell.com> + * Zhou Zhu <zzhu3@marvell.com> + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/vmalloc.h> +#include <linux/uaccess.h> +#include <linux/kthread.h> +#include <linux/io.h> + +#include "mmp_ctrl.h" + +static irqreturn_t ctrl_handle_irq(int irq, void *dev_id) +{ + struct mmphw_ctrl *ctrl = (struct mmphw_ctrl *)dev_id; + u32 isr, imask, tmp; + + isr = readl_relaxed(ctrl->reg_base + SPU_IRQ_ISR); + imask = readl_relaxed(ctrl->reg_base + SPU_IRQ_ENA); + + do { + /* clear clock only */ + tmp = readl_relaxed(ctrl->reg_base + SPU_IRQ_ISR); + if (tmp & isr) + writel_relaxed(~isr, ctrl->reg_base + SPU_IRQ_ISR); + } while ((isr = readl_relaxed(ctrl->reg_base + SPU_IRQ_ISR)) & imask); + + return IRQ_HANDLED; +} + +static u32 fmt_to_reg(struct mmp_overlay *overlay, int pix_fmt) +{ + u32 rbswap = 0, uvswap = 0, yuvswap = 0, + csc_en = 0, val = 0, + vid = overlay_is_vid(overlay); + + switch (pix_fmt) { + case PIXFMT_RGB565: + case PIXFMT_RGB1555: + case PIXFMT_RGB888PACK: + case PIXFMT_RGB888UNPACK: + case PIXFMT_RGBA888: + rbswap = 1; + break; + case PIXFMT_VYUY: + case PIXFMT_YVU422P: + case PIXFMT_YVU420P: + uvswap = 1; + break; + case PIXFMT_YUYV: + yuvswap = 1; + break; + default: + break; + } + + switch (pix_fmt) { + case PIXFMT_RGB565: + case PIXFMT_BGR565: + break; + case PIXFMT_RGB1555: + case PIXFMT_BGR1555: + val = 0x1; + break; + case PIXFMT_RGB888PACK: + case PIXFMT_BGR888PACK: + val = 0x2; + break; + case PIXFMT_RGB888UNPACK: + case PIXFMT_BGR888UNPACK: + val = 0x3; + break; + case PIXFMT_RGBA888: + case PIXFMT_BGRA888: + val = 0x4; + break; + case PIXFMT_UYVY: + case PIXFMT_VYUY: + case PIXFMT_YUYV: + val = 0x5; + csc_en = 1; + break; + case PIXFMT_YUV422P: + case PIXFMT_YVU422P: + val = 0x6; + csc_en = 1; + break; + case PIXFMT_YUV420P: + case PIXFMT_YVU420P: + val = 0x7; + csc_en = 1; + break; + default: + break; + } + + return (dma_palette(0) | dma_fmt(vid, val) | + dma_swaprb(vid, rbswap) | dma_swapuv(vid, uvswap) | + dma_swapyuv(vid, yuvswap) | dma_csc(vid, csc_en)); +} + +static void dmafetch_set_fmt(struct mmp_overlay *overlay) +{ + u32 tmp; + struct mmp_path *path = overlay->path; + tmp = readl_relaxed(ctrl_regs(path) + dma_ctrl(0, path->id)); + tmp &= ~dma_mask(overlay_is_vid(overlay)); + tmp |= fmt_to_reg(overlay, overlay->win.pix_fmt); + writel_relaxed(tmp, ctrl_regs(path) + dma_ctrl(0, path->id)); +} + +static void overlay_set_win(struct mmp_overlay *overlay, struct mmp_win *win) +{ + struct lcd_regs *regs = path_regs(overlay->path); + + /* assert win supported */ + memcpy(&overlay->win, win, sizeof(struct mmp_win)); + + mutex_lock(&overlay->access_ok); + + if (overlay_is_vid(overlay)) { + writel_relaxed(win->pitch[0], ®s->v_pitch_yc); + writel_relaxed(win->pitch[2] << 16 | + win->pitch[1], ®s->v_pitch_uv); + + writel_relaxed((win->ysrc << 16) | win->xsrc, ®s->v_size); + writel_relaxed((win->ydst << 16) | win->xdst, ®s->v_size_z); + writel_relaxed(win->ypos << 16 | win->xpos, ®s->v_start); + } else { + writel_relaxed(win->pitch[0], ®s->g_pitch); + + writel_relaxed((win->ysrc << 16) | win->xsrc, ®s->g_size); + writel_relaxed((win->ydst << 16) | win->xdst, ®s->g_size_z); + writel_relaxed(win->ypos << 16 | win->xpos, ®s->g_start); + } + + dmafetch_set_fmt(overlay); + mutex_unlock(&overlay->access_ok); +} + +static void dmafetch_onoff(struct mmp_overlay *overlay, int on) +{ + u32 mask = overlay_is_vid(overlay) ? CFG_DMA_ENA_MASK : + CFG_GRA_ENA_MASK; + u32 enable = overlay_is_vid(overlay) ? CFG_DMA_ENA(1) : CFG_GRA_ENA(1); + u32 tmp; + struct mmp_path *path = overlay->path; + + mutex_lock(&overlay->access_ok); + tmp = readl_relaxed(ctrl_regs(path) + dma_ctrl(0, path->id)); + tmp &= ~mask; + tmp |= (on ? enable : 0); + writel(tmp, ctrl_regs(path) + dma_ctrl(0, path->id)); + mutex_unlock(&overlay->access_ok); +} + +static void path_enabledisable(struct mmp_path *path, int on) +{ + u32 tmp; + mutex_lock(&path->access_ok); + tmp = readl_relaxed(ctrl_regs(path) + LCD_SCLK(path)); + if (on) + tmp &= ~SCLK_DISABLE; + else + tmp |= SCLK_DISABLE; + writel_relaxed(tmp, ctrl_regs(path) + LCD_SCLK(path)); + mutex_unlock(&path->access_ok); +} + +static void path_onoff(struct mmp_path *path, int on) +{ + if (path->status == on) { + dev_info(path->dev, "path %s is already %s\n", + path->name, stat_name(path->status)); + return; + } + + if (on) { + path_enabledisable(path, 1); + + if (path->panel && path->panel->set_onoff) + path->panel->set_onoff(path->panel, 1); + } else { + if (path->panel && path->panel->set_onoff) + path->panel->set_onoff(path->panel, 0); + + path_enabledisable(path, 0); + } + path->status = on; +} + +static void overlay_set_onoff(struct mmp_overlay *overlay, int on) +{ + if (overlay->status == on) { + dev_info(overlay_to_ctrl(overlay)->dev, "overlay %s is already %s\n", + overlay->path->name, stat_name(overlay->status)); + return; + } + overlay->status = on; + dmafetch_onoff(overlay, on); + if (overlay->path->ops.check_status(overlay->path) + != overlay->path->status) + path_onoff(overlay->path, on); +} + +static void overlay_set_fetch(struct mmp_overlay *overlay, int fetch_id) +{ + overlay->dmafetch_id = fetch_id; +} + +static int overlay_set_addr(struct mmp_overlay *overlay, struct mmp_addr *addr) +{ + struct lcd_regs *regs = path_regs(overlay->path); + + /* FIXME: assert addr supported */ + memcpy(&overlay->addr, addr, sizeof(struct mmp_addr)); + + if (overlay_is_vid(overlay)) { + writel_relaxed(addr->phys[0], ®s->v_y0); + writel_relaxed(addr->phys[1], ®s->v_u0); + writel_relaxed(addr->phys[2], ®s->v_v0); + } else + writel_relaxed(addr->phys[0], ®s->g_0); + + return overlay->addr.phys[0]; +} + +static void path_set_mode(struct mmp_path *path, struct mmp_mode *mode) +{ + struct lcd_regs *regs = path_regs(path); + u32 total_x, total_y, vsync_ctrl, tmp, sclk_src, sclk_div, + link_config = path_to_path_plat(path)->link_config, + dsi_rbswap = path_to_path_plat(path)->link_config; + + /* FIXME: assert videomode supported */ + memcpy(&path->mode, mode, sizeof(struct mmp_mode)); + + mutex_lock(&path->access_ok); + + /* polarity of timing signals */ + tmp = readl_relaxed(ctrl_regs(path) + intf_ctrl(path->id)) & 0x1; + tmp |= mode->vsync_invert ? 0 : 0x8; + tmp |= mode->hsync_invert ? 0 : 0x4; + tmp |= link_config & CFG_DUMBMODE_MASK; + tmp |= CFG_DUMB_ENA(1); + writel_relaxed(tmp, ctrl_regs(path) + intf_ctrl(path->id)); + + /* interface rb_swap setting */ + tmp = readl_relaxed(ctrl_regs(path) + intf_rbswap_ctrl(path->id)) & + (~(CFG_INTFRBSWAP_MASK)); + tmp |= dsi_rbswap & CFG_INTFRBSWAP_MASK; + writel_relaxed(tmp, ctrl_regs(path) + intf_rbswap_ctrl(path->id)); + + writel_relaxed((mode->yres << 16) | mode->xres, ®s->screen_active); + writel_relaxed((mode->left_margin << 16) | mode->right_margin, + ®s->screen_h_porch); + writel_relaxed((mode->upper_margin << 16) | mode->lower_margin, + ®s->screen_v_porch); + total_x = mode->xres + mode->left_margin + mode->right_margin + + mode->hsync_len; + total_y = mode->yres + mode->upper_margin + mode->lower_margin + + mode->vsync_len; + writel_relaxed((total_y << 16) | total_x, ®s->screen_size); + + /* vsync ctrl */ + if (path->output_type == PATH_OUT_DSI) + vsync_ctrl = 0x01330133; + else + vsync_ctrl = ((mode->xres + mode->right_margin) << 16) + | (mode->xres + mode->right_margin); + writel_relaxed(vsync_ctrl, ®s->vsync_ctrl); + + /* set pixclock div */ + sclk_src = clk_get_rate(path_to_ctrl(path)->clk); + sclk_div = sclk_src / mode->pixclock_freq; + if (sclk_div * mode->pixclock_freq < sclk_src) + sclk_div++; + + dev_info(path->dev, "%s sclk_src %d sclk_div 0x%x pclk %d\n", + __func__, sclk_src, sclk_div, mode->pixclock_freq); + + tmp = readl_relaxed(ctrl_regs(path) + LCD_SCLK(path)); + tmp &= ~CLK_INT_DIV_MASK; + tmp |= sclk_div; + writel_relaxed(tmp, ctrl_regs(path) + LCD_SCLK(path)); + + mutex_unlock(&path->access_ok); +} + +static struct mmp_overlay_ops mmphw_overlay_ops = { + .set_fetch = overlay_set_fetch, + .set_onoff = overlay_set_onoff, + .set_win = overlay_set_win, + .set_addr = overlay_set_addr, +}; + +static void ctrl_set_default(struct mmphw_ctrl *ctrl) +{ + u32 tmp, irq_mask; + + /* + * LCD Global control(LCD_TOP_CTRL) should be configed before + * any other LCD registers read/write, or there maybe issues. + */ + tmp = readl_relaxed(ctrl->reg_base + LCD_TOP_CTRL); + tmp |= 0xfff0; + writel_relaxed(tmp, ctrl->reg_base + LCD_TOP_CTRL); + + + /* disable all interrupts */ + irq_mask = path_imasks(0) | err_imask(0) | + path_imasks(1) | err_imask(1); + tmp = readl_relaxed(ctrl->reg_base + SPU_IRQ_ENA); + tmp &= ~irq_mask; + tmp |= irq_mask; + writel_relaxed(tmp, ctrl->reg_base + SPU_IRQ_ENA); +} + +static void path_set_default(struct mmp_path *path) +{ + struct lcd_regs *regs = path_regs(path); + u32 dma_ctrl1, mask, tmp, path_config; + + path_config = path_to_path_plat(path)->path_config; + + /* Configure IOPAD: should be parallel only */ + if (PATH_OUT_PARALLEL == path->output_type) { + mask = CFG_IOPADMODE_MASK | CFG_BURST_MASK | CFG_BOUNDARY_MASK; + tmp = readl_relaxed(ctrl_regs(path) + SPU_IOPAD_CONTROL); + tmp &= ~mask; + tmp |= path_config; + writel_relaxed(tmp, ctrl_regs(path) + SPU_IOPAD_CONTROL); + } + + /* Select path clock source */ + tmp = readl_relaxed(ctrl_regs(path) + LCD_SCLK(path)); + tmp &= ~SCLK_SRC_SEL_MASK; + tmp |= path_config; + writel_relaxed(tmp, ctrl_regs(path) + LCD_SCLK(path)); + + /* + * Configure default bits: vsync triggers DMA, + * power save enable, configure alpha registers to + * display 100% graphics, and set pixel command. + */ + dma_ctrl1 = 0x2032ff81; + + dma_ctrl1 |= CFG_VSYNC_INV_MASK; + writel_relaxed(dma_ctrl1, ctrl_regs(path) + dma_ctrl(1, path->id)); + + /* Configure default register values */ + writel_relaxed(0x00000000, ®s->blank_color); + writel_relaxed(0x00000000, ®s->g_1); + writel_relaxed(0x00000000, ®s->g_start); + + /* + * 1.enable multiple burst request in DMA AXI + * bus arbiter for faster read if not tv path; + * 2.enable horizontal smooth filter; + */ + mask = CFG_GRA_HSMOOTH_MASK | CFG_DMA_HSMOOTH_MASK | CFG_ARBFAST_ENA(1); + tmp = readl_relaxed(ctrl_regs(path) + dma_ctrl(0, path->id)); + tmp |= mask; + if (PATH_TV == path->id) + tmp &= ~CFG_ARBFAST_ENA(1); + writel_relaxed(tmp, ctrl_regs(path) + dma_ctrl(0, path->id)); +} + +static int path_init(struct mmphw_path_plat *path_plat, + struct mmp_mach_path_config *config) +{ + struct mmphw_ctrl *ctrl = path_plat->ctrl; + struct mmp_path_info *path_info; + struct mmp_path *path = NULL; + + dev_info(ctrl->dev, "%s: %s\n", __func__, config->name); + + /* init driver data */ + path_info = kzalloc(sizeof(struct mmp_path_info), GFP_KERNEL); + if (!path_info) { + dev_err(ctrl->dev, "%s: unable to alloc path_info for %s\n", + __func__, config->name); + return 0; + } + path_info->name = config->name; + path_info->id = path_plat->id; + path_info->dev = ctrl->dev; + path_info->overlay_num = config->overlay_num; + path_info->overlay_ops = &mmphw_overlay_ops; + path_info->set_mode = path_set_mode; + path_info->plat_data = path_plat; + + /* create/register platform device */ + path = mmp_register_path(path_info); + if (!path) { + kfree(path_info); + return 0; + } + path_plat->path = path; + path_plat->path_config = config->path_config; + path_plat->link_config = config->link_config; + path_plat->dsi_rbswap = config->dsi_rbswap; + path_set_default(path); + + kfree(path_info); + return 1; +} + +static void path_deinit(struct mmphw_path_plat *path_plat) +{ + if (!path_plat) + return; + + if (path_plat->path) + mmp_unregister_path(path_plat->path); +} + +static int mmphw_probe(struct platform_device *pdev) +{ + struct mmp_mach_plat_info *mi; + struct resource *res; + int ret, i, size, irq; + struct mmphw_path_plat *path_plat; + struct mmphw_ctrl *ctrl = NULL; + + /* get resources from platform data */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "%s: no IO memory defined\n", __func__); + ret = -ENOENT; + goto failed; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "%s: no IRQ defined\n", __func__); + ret = -ENOENT; + goto failed; + } + + /* get configs from platform data */ + mi = pdev->dev.platform_data; + if (mi == NULL || !mi->path_num || !mi->paths) { + dev_err(&pdev->dev, "%s: no platform data defined\n", __func__); + ret = -EINVAL; + goto failed; + } + + /* allocate */ + size = sizeof(struct mmphw_ctrl) + sizeof(struct mmphw_path_plat) * + mi->path_num; + ctrl = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!ctrl) { + ret = -ENOMEM; + goto failed; + } + + ctrl->name = mi->name; + ctrl->path_num = mi->path_num; + ctrl->dev = &pdev->dev; + ctrl->irq = irq; + platform_set_drvdata(pdev, ctrl); + mutex_init(&ctrl->access_ok); + + /* map registers.*/ + if (!devm_request_mem_region(ctrl->dev, res->start, + resource_size(res), ctrl->name)) { + dev_err(ctrl->dev, + "can't request region for resource %pR\n", res); + ret = -EINVAL; + goto failed; + } + + ctrl->reg_base = devm_ioremap_nocache(ctrl->dev, + res->start, resource_size(res)); + if (ctrl->reg_base == NULL) { + dev_err(ctrl->dev, "%s: res %x - %x map failed\n", __func__, + res->start, res->end); + ret = -ENOMEM; + goto failed; + } + + /* request irq */ + ret = devm_request_irq(ctrl->dev, ctrl->irq, ctrl_handle_irq, + IRQF_SHARED, "lcd_controller", ctrl); + if (ret < 0) { + dev_err(ctrl->dev, "%s unable to request IRQ %d\n", + __func__, ctrl->irq); + ret = -ENXIO; + goto failed; + } + + /* get clock */ + ctrl->clk = devm_clk_get(ctrl->dev, mi->clk_name); + if (IS_ERR(ctrl->clk)) { + dev_err(ctrl->dev, "unable to get clk %s\n", mi->clk_name); + ret = -ENOENT; + goto failed; + } + clk_prepare_enable(ctrl->clk); + + /* init global regs */ + ctrl_set_default(ctrl); + + /* init pathes from machine info and register them */ + for (i = 0; i < ctrl->path_num; i++) { + /* get from config and machine info */ + path_plat = &ctrl->path_plats[i]; + path_plat->id = i; + path_plat->ctrl = ctrl; + + /* path init */ + if (!path_init(path_plat, &mi->paths[i])) { + ret = -EINVAL; + goto failed_path_init; + } + } + +#ifdef CONFIG_MMP_DISP_SPI + ret = lcd_spi_register(ctrl); + if (ret < 0) + goto failed_path_init; +#endif + + dev_info(ctrl->dev, "device init done\n"); + + return 0; + +failed_path_init: + for (i = 0; i < ctrl->path_num; i++) { + path_plat = &ctrl->path_plats[i]; + path_deinit(path_plat); + } + + clk_disable_unprepare(ctrl->clk); +failed: + dev_err(&pdev->dev, "device init failed\n"); + + return ret; +} + +static struct platform_driver mmphw_driver = { + .driver = { + .name = "mmp-disp", + .owner = THIS_MODULE, + }, + .probe = mmphw_probe, +}; + +static int mmphw_init(void) +{ + return platform_driver_register(&mmphw_driver); +} +module_init(mmphw_init); + +MODULE_AUTHOR("Li Guoqing<ligq@marvell.com>"); +MODULE_DESCRIPTION("Framebuffer driver for mmp"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/mmp/hw/mmp_ctrl.h b/drivers/video/fbdev/mmp/hw/mmp_ctrl.h new file mode 100644 index 000000000000..53301cfdb1ae --- /dev/null +++ b/drivers/video/fbdev/mmp/hw/mmp_ctrl.h @@ -0,0 +1,1502 @@ +/* + * drivers/video/mmp/hw/mmp_ctrl.h + * + * + * Copyright (C) 2012 Marvell Technology Group Ltd. + * Authors: Guoqing Li <ligq@marvell.com> + * Lisa Du <cldu@marvell.com> + * Zhou Zhu <zzhu3@marvell.com> + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _MMP_CTRL_H_ +#define _MMP_CTRL_H_ + +#include <video/mmp_disp.h> + +/* ------------< LCD register >------------ */ +struct lcd_regs { +/* TV patch register for MMP2 */ +/* 32 bit TV Video Frame0 Y Starting Address */ +#define LCD_TVD_START_ADDR_Y0 (0x0000) +/* 32 bit TV Video Frame0 U Starting Address */ +#define LCD_TVD_START_ADDR_U0 (0x0004) +/* 32 bit TV Video Frame0 V Starting Address */ +#define LCD_TVD_START_ADDR_V0 (0x0008) +/* 32 bit TV Video Frame0 Command Starting Address */ +#define LCD_TVD_START_ADDR_C0 (0x000C) +/* 32 bit TV Video Frame1 Y Starting Address Register*/ +#define LCD_TVD_START_ADDR_Y1 (0x0010) +/* 32 bit TV Video Frame1 U Starting Address Register*/ +#define LCD_TVD_START_ADDR_U1 (0x0014) +/* 32 bit TV Video Frame1 V Starting Address Register*/ +#define LCD_TVD_START_ADDR_V1 (0x0018) +/* 32 bit TV Video Frame1 Command Starting Address Register*/ +#define LCD_TVD_START_ADDR_C1 (0x001C) +/* 32 bit TV Video Y andC Line Length(Pitch)Register*/ +#define LCD_TVD_PITCH_YC (0x0020) +/* 32 bit TV Video U andV Line Length(Pitch)Register*/ +#define LCD_TVD_PITCH_UV (0x0024) +/* 32 bit TV Video Starting Point on Screen Register*/ +#define LCD_TVD_OVSA_HPXL_VLN (0x0028) +/* 32 bit TV Video Source Size Register*/ +#define LCD_TVD_HPXL_VLN (0x002C) +/* 32 bit TV Video Destination Size (After Zooming)Register*/ +#define LCD_TVDZM_HPXL_VLN (0x0030) + u32 v_y0; + u32 v_u0; + u32 v_v0; + u32 v_c0; + u32 v_y1; + u32 v_u1; + u32 v_v1; + u32 v_c1; + u32 v_pitch_yc; /* Video Y and C Line Length (Pitch) */ + u32 v_pitch_uv; /* Video U and V Line Length (Pitch) */ + u32 v_start; /* Video Starting Point on Screen */ + u32 v_size; /* Video Source Size */ + u32 v_size_z; /* Video Destination Size (After Zooming) */ + +/* 32 bit TV Graphic Frame 0 Starting Address Register*/ +#define LCD_TVG_START_ADDR0 (0x0034) +/* 32 bit TV Graphic Frame 1 Starting Address Register*/ +#define LCD_TVG_START_ADDR1 (0x0038) +/* 32 bit TV Graphic Line Length(Pitch)Register*/ +#define LCD_TVG_PITCH (0x003C) +/* 32 bit TV Graphic Starting Point on Screen Register*/ +#define LCD_TVG_OVSA_HPXL_VLN (0x0040) +/* 32 bit TV Graphic Source Size Register*/ +#define LCD_TVG_HPXL_VLN (0x0044) +/* 32 bit TV Graphic Destination size (after Zooming)Register*/ +#define LCD_TVGZM_HPXL_VLN (0x0048) + u32 g_0; /* Graphic Frame 0/1 Starting Address */ + u32 g_1; + u32 g_pitch; /* Graphic Line Length (Pitch) */ + u32 g_start; /* Graphic Starting Point on Screen */ + u32 g_size; /* Graphic Source Size */ + u32 g_size_z; /* Graphic Destination Size (After Zooming) */ + +/* 32 bit TV Hardware Cursor Starting Point on screen Register*/ +#define LCD_TVC_OVSA_HPXL_VLN (0x004C) +/* 32 bit TV Hardware Cursor Size Register */ +#define LCD_TVC_HPXL_VLN (0x0050) + u32 hc_start; /* Hardware Cursor */ + u32 hc_size; /* Hardware Cursor */ + +/* 32 bit TV Total Screen Size Register*/ +#define LCD_TV_V_H_TOTAL (0x0054) +/* 32 bit TV Screen Active Size Register*/ +#define LCD_TV_V_H_ACTIVE (0x0058) +/* 32 bit TV Screen Horizontal Porch Register*/ +#define LCD_TV_H_PORCH (0x005C) +/* 32 bit TV Screen Vertical Porch Register*/ +#define LCD_TV_V_PORCH (0x0060) + u32 screen_size; /* Screen Total Size */ + u32 screen_active; /* Screen Active Size */ + u32 screen_h_porch; /* Screen Horizontal Porch */ + u32 screen_v_porch; /* Screen Vertical Porch */ + +/* 32 bit TV Screen Blank Color Register*/ +#define LCD_TV_BLANKCOLOR (0x0064) +/* 32 bit TV Hardware Cursor Color1 Register*/ +#define LCD_TV_ALPHA_COLOR1 (0x0068) +/* 32 bit TV Hardware Cursor Color2 Register*/ +#define LCD_TV_ALPHA_COLOR2 (0x006C) + u32 blank_color; /* Screen Blank Color */ + u32 hc_Alpha_color1; /* Hardware Cursor Color1 */ + u32 hc_Alpha_color2; /* Hardware Cursor Color2 */ + +/* 32 bit TV Video Y Color Key Control*/ +#define LCD_TV_COLORKEY_Y (0x0070) +/* 32 bit TV Video U Color Key Control*/ +#define LCD_TV_COLORKEY_U (0x0074) +/* 32 bit TV Video V Color Key Control*/ +#define LCD_TV_COLORKEY_V (0x0078) + u32 v_colorkey_y; /* Video Y Color Key Control */ + u32 v_colorkey_u; /* Video U Color Key Control */ + u32 v_colorkey_v; /* Video V Color Key Control */ + +/* 32 bit TV VSYNC PulsePixel Edge Control Register*/ +#define LCD_TV_SEPXLCNT (0x007C) + u32 vsync_ctrl; /* VSYNC PulsePixel Edge Control */ +}; + +#define intf_ctrl(id) ((id) ? (((id) & 1) ? LCD_TVIF_CTRL : \ + LCD_DUMB2_CTRL) : LCD_SPU_DUMB_CTRL) +#define dma_ctrl0(id) ((id) ? (((id) & 1) ? LCD_TV_CTRL0 : \ + LCD_PN2_CTRL0) : LCD_SPU_DMA_CTRL0) +#define dma_ctrl1(id) ((id) ? (((id) & 1) ? LCD_TV_CTRL1 : \ + LCD_PN2_CTRL1) : LCD_SPU_DMA_CTRL1) +#define dma_ctrl(ctrl1, id) (ctrl1 ? dma_ctrl1(id) : dma_ctrl0(id)) + +/* 32 bit TV Path DMA Control 0*/ +#define LCD_TV_CTRL0 (0x0080) +/* 32 bit TV Path DMA Control 1*/ +#define LCD_TV_CTRL1 (0x0084) +/* 32 bit TV Path Video Contrast*/ +#define LCD_TV_CONTRAST (0x0088) +/* 32 bit TV Path Video Saturation*/ +#define LCD_TV_SATURATION (0x008C) +/* 32 bit TV Path Video Hue Adjust*/ +#define LCD_TV_CBSH_HUE (0x0090) +/* 32 bit TV Path TVIF Control Register */ +#define LCD_TVIF_CTRL (0x0094) +#define TV_VBLNK_VALID_EN (1 << 12) + +/* 32 bit TV Path I/O Pad Control*/ +#define LCD_TVIOPAD_CTRL (0x0098) +/* 32 bit TV Path Cloc Divider */ +#define LCD_TCLK_DIV (0x009C) + +#define LCD_SCLK(path) ((PATH_PN == path->id) ? LCD_CFG_SCLK_DIV :\ + ((PATH_TV == path->id) ? LCD_TCLK_DIV : LCD_PN2_SCLK_DIV)) +#define intf_rbswap_ctrl(id) ((id) ? (((id) & 1) ? LCD_TVIF_CTRL : \ + PN2_IOPAD_CONTROL) : LCD_TOP_CTRL) + +/* dither configure */ +#ifdef CONFIG_CPU_PXA988 +#define LCD_DITHER_CTRL (0x01EC) +#else +#define LCD_DITHER_CTRL (0x00A0) +#endif + +#define DITHER_TBL_INDEX_SEL(s) ((s) << 16) +#define DITHER_MODE2(m) ((m) << 12) +#define DITHER_MODE2_SHIFT (12) +#define DITHER_4X8_EN2 (1 << 9) +#define DITHER_4X8_EN2_SHIFT (9) +#define DITHER_EN2 (1 << 8) +#define DITHER_MODE1(m) ((m) << 4) +#define DITHER_MODE1_SHIFT (4) +#define DITHER_4X8_EN1 (1 << 1) +#define DITHER_4X8_EN1_SHIFT (1) +#define DITHER_EN1 (1) + +/* dither table data was fixed by video bpp of input and output*/ +#ifdef CONFIG_CPU_PXA988 +#define DITHER_TB_4X4_INDEX0 (0x6e4ca280) +#define DITHER_TB_4X4_INDEX1 (0x5d7f91b3) +#define DITHER_TB_4X8_INDEX0 (0xb391a280) +#define DITHER_TB_4X8_INDEX1 (0x7f5d6e4c) +#define DITHER_TB_4X8_INDEX2 (0x80a291b3) +#define DITHER_TB_4X8_INDEX3 (0x4c6e5d7f) +#define LCD_DITHER_TBL_DATA (0x01F0) +#else +#define DITHER_TB_4X4_INDEX0 (0x3b19f7d5) +#define DITHER_TB_4X4_INDEX1 (0x082ac4e6) +#define DITHER_TB_4X8_INDEX0 (0xf7d508e6) +#define DITHER_TB_4X8_INDEX1 (0x3b194c2a) +#define DITHER_TB_4X8_INDEX2 (0xc4e6d5f7) +#define DITHER_TB_4X8_INDEX3 (0x082a193b) +#define LCD_DITHER_TBL_DATA (0x00A4) +#endif + +/* Video Frame 0&1 start address registers */ +#define LCD_SPU_DMA_START_ADDR_Y0 0x00C0 +#define LCD_SPU_DMA_START_ADDR_U0 0x00C4 +#define LCD_SPU_DMA_START_ADDR_V0 0x00C8 +#define LCD_CFG_DMA_START_ADDR_0 0x00CC /* Cmd address */ +#define LCD_SPU_DMA_START_ADDR_Y1 0x00D0 +#define LCD_SPU_DMA_START_ADDR_U1 0x00D4 +#define LCD_SPU_DMA_START_ADDR_V1 0x00D8 +#define LCD_CFG_DMA_START_ADDR_1 0x00DC /* Cmd address */ + +/* YC & UV Pitch */ +#define LCD_SPU_DMA_PITCH_YC 0x00E0 +#define SPU_DMA_PITCH_C(c) ((c)<<16) +#define SPU_DMA_PITCH_Y(y) (y) +#define LCD_SPU_DMA_PITCH_UV 0x00E4 +#define SPU_DMA_PITCH_V(v) ((v)<<16) +#define SPU_DMA_PITCH_U(u) (u) + +/* Video Starting Point on Screen Register */ +#define LCD_SPUT_DMA_OVSA_HPXL_VLN 0x00E8 +#define CFG_DMA_OVSA_VLN(y) ((y)<<16) /* 0~0xfff */ +#define CFG_DMA_OVSA_HPXL(x) (x) /* 0~0xfff */ + +/* Video Size Register */ +#define LCD_SPU_DMA_HPXL_VLN 0x00EC +#define CFG_DMA_VLN(y) ((y)<<16) +#define CFG_DMA_HPXL(x) (x) + +/* Video Size After zooming Register */ +#define LCD_SPU_DZM_HPXL_VLN 0x00F0 +#define CFG_DZM_VLN(y) ((y)<<16) +#define CFG_DZM_HPXL(x) (x) + +/* Graphic Frame 0&1 Starting Address Register */ +#define LCD_CFG_GRA_START_ADDR0 0x00F4 +#define LCD_CFG_GRA_START_ADDR1 0x00F8 + +/* Graphic Frame Pitch */ +#define LCD_CFG_GRA_PITCH 0x00FC + +/* Graphic Starting Point on Screen Register */ +#define LCD_SPU_GRA_OVSA_HPXL_VLN 0x0100 +#define CFG_GRA_OVSA_VLN(y) ((y)<<16) +#define CFG_GRA_OVSA_HPXL(x) (x) + +/* Graphic Size Register */ +#define LCD_SPU_GRA_HPXL_VLN 0x0104 +#define CFG_GRA_VLN(y) ((y)<<16) +#define CFG_GRA_HPXL(x) (x) + +/* Graphic Size after Zooming Register */ +#define LCD_SPU_GZM_HPXL_VLN 0x0108 +#define CFG_GZM_VLN(y) ((y)<<16) +#define CFG_GZM_HPXL(x) (x) + +/* HW Cursor Starting Point on Screen Register */ +#define LCD_SPU_HWC_OVSA_HPXL_VLN 0x010C +#define CFG_HWC_OVSA_VLN(y) ((y)<<16) +#define CFG_HWC_OVSA_HPXL(x) (x) + +/* HW Cursor Size */ +#define LCD_SPU_HWC_HPXL_VLN 0x0110 +#define CFG_HWC_VLN(y) ((y)<<16) +#define CFG_HWC_HPXL(x) (x) + +/* Total Screen Size Register */ +#define LCD_SPUT_V_H_TOTAL 0x0114 +#define CFG_V_TOTAL(y) ((y)<<16) +#define CFG_H_TOTAL(x) (x) + +/* Total Screen Active Size Register */ +#define LCD_SPU_V_H_ACTIVE 0x0118 +#define CFG_V_ACTIVE(y) ((y)<<16) +#define CFG_H_ACTIVE(x) (x) + +/* Screen H&V Porch Register */ +#define LCD_SPU_H_PORCH 0x011C +#define CFG_H_BACK_PORCH(b) ((b)<<16) +#define CFG_H_FRONT_PORCH(f) (f) +#define LCD_SPU_V_PORCH 0x0120 +#define CFG_V_BACK_PORCH(b) ((b)<<16) +#define CFG_V_FRONT_PORCH(f) (f) + +/* Screen Blank Color Register */ +#define LCD_SPU_BLANKCOLOR 0x0124 +#define CFG_BLANKCOLOR_MASK 0x00FFFFFF +#define CFG_BLANKCOLOR_R_MASK 0x000000FF +#define CFG_BLANKCOLOR_G_MASK 0x0000FF00 +#define CFG_BLANKCOLOR_B_MASK 0x00FF0000 + +/* HW Cursor Color 1&2 Register */ +#define LCD_SPU_ALPHA_COLOR1 0x0128 +#define CFG_HWC_COLOR1 0x00FFFFFF +#define CFG_HWC_COLOR1_R(red) ((red)<<16) +#define CFG_HWC_COLOR1_G(green) ((green)<<8) +#define CFG_HWC_COLOR1_B(blue) (blue) +#define CFG_HWC_COLOR1_R_MASK 0x000000FF +#define CFG_HWC_COLOR1_G_MASK 0x0000FF00 +#define CFG_HWC_COLOR1_B_MASK 0x00FF0000 +#define LCD_SPU_ALPHA_COLOR2 0x012C +#define CFG_HWC_COLOR2 0x00FFFFFF +#define CFG_HWC_COLOR2_R_MASK 0x000000FF +#define CFG_HWC_COLOR2_G_MASK 0x0000FF00 +#define CFG_HWC_COLOR2_B_MASK 0x00FF0000 + +/* Video YUV Color Key Control */ +#define LCD_SPU_COLORKEY_Y 0x0130 +#define CFG_CKEY_Y2(y2) ((y2)<<24) +#define CFG_CKEY_Y2_MASK 0xFF000000 +#define CFG_CKEY_Y1(y1) ((y1)<<16) +#define CFG_CKEY_Y1_MASK 0x00FF0000 +#define CFG_CKEY_Y(y) ((y)<<8) +#define CFG_CKEY_Y_MASK 0x0000FF00 +#define CFG_ALPHA_Y(y) (y) +#define CFG_ALPHA_Y_MASK 0x000000FF +#define LCD_SPU_COLORKEY_U 0x0134 +#define CFG_CKEY_U2(u2) ((u2)<<24) +#define CFG_CKEY_U2_MASK 0xFF000000 +#define CFG_CKEY_U1(u1) ((u1)<<16) +#define CFG_CKEY_U1_MASK 0x00FF0000 +#define CFG_CKEY_U(u) ((u)<<8) +#define CFG_CKEY_U_MASK 0x0000FF00 +#define CFG_ALPHA_U(u) (u) +#define CFG_ALPHA_U_MASK 0x000000FF +#define LCD_SPU_COLORKEY_V 0x0138 +#define CFG_CKEY_V2(v2) ((v2)<<24) +#define CFG_CKEY_V2_MASK 0xFF000000 +#define CFG_CKEY_V1(v1) ((v1)<<16) +#define CFG_CKEY_V1_MASK 0x00FF0000 +#define CFG_CKEY_V(v) ((v)<<8) +#define CFG_CKEY_V_MASK 0x0000FF00 +#define CFG_ALPHA_V(v) (v) +#define CFG_ALPHA_V_MASK 0x000000FF + +/* Graphics/Video DMA color key enable bits in LCD_TV_CTRL1 */ +#define CFG_CKEY_GRA 0x2 +#define CFG_CKEY_DMA 0x1 + +/* Interlace mode enable bits in LCD_TV_CTRL1 */ +#define CFG_TV_INTERLACE_EN (1 << 22) +#define CFG_TV_NIB (1 << 0) + +#define LCD_PN_SEPXLCNT 0x013c /* MMP2 */ + +/* SPI Read Data Register */ +#define LCD_SPU_SPI_RXDATA 0x0140 + +/* Smart Panel Read Data Register */ +#define LCD_SPU_ISA_RSDATA 0x0144 +#define ISA_RXDATA_16BIT_1_DATA_MASK 0x000000FF +#define ISA_RXDATA_16BIT_2_DATA_MASK 0x0000FF00 +#define ISA_RXDATA_16BIT_3_DATA_MASK 0x00FF0000 +#define ISA_RXDATA_16BIT_4_DATA_MASK 0xFF000000 +#define ISA_RXDATA_32BIT_1_DATA_MASK 0x00FFFFFF + +#define LCD_SPU_DBG_ISA (0x0148) /* TTC */ +#define LCD_SPU_DMAVLD_YC (0x014C) +#define LCD_SPU_DMAVLD_UV (0x0150) +#define LCD_SPU_DMAVLD_UVSPU_GRAVLD (0x0154) + +#define LCD_READ_IOPAD (0x0148) /* MMP2*/ +#define LCD_DMAVLD_YC (0x014C) +#define LCD_DMAVLD_UV (0x0150) +#define LCD_TVGGRAVLD_HLEN (0x0154) + +/* HWC SRAM Read Data Register */ +#define LCD_SPU_HWC_RDDAT 0x0158 + +/* Gamma Table SRAM Read Data Register */ +#define LCD_SPU_GAMMA_RDDAT 0x015c +#define CFG_GAMMA_RDDAT_MASK 0x000000FF + +/* Palette Table SRAM Read Data Register */ +#define LCD_SPU_PALETTE_RDDAT 0x0160 +#define CFG_PALETTE_RDDAT_MASK 0x00FFFFFF + +#define LCD_SPU_DBG_DMATOP (0x0164) /* TTC */ +#define LCD_SPU_DBG_GRATOP (0x0168) +#define LCD_SPU_DBG_TXCTRL (0x016C) +#define LCD_SPU_DBG_SLVTOP (0x0170) +#define LCD_SPU_DBG_MUXTOP (0x0174) + +#define LCD_SLV_DBG (0x0164) /* MMP2 */ +#define LCD_TVDVLD_YC (0x0168) +#define LCD_TVDVLD_UV (0x016C) +#define LCD_TVC_RDDAT (0x0170) +#define LCD_TV_GAMMA_RDDAT (0x0174) + +/* I/O Pads Input Read Only Register */ +#define LCD_SPU_IOPAD_IN 0x0178 +#define CFG_IOPAD_IN_MASK 0x0FFFFFFF + +#define LCD_TV_PALETTE_RDDAT (0x0178) /* MMP2 */ + +/* Reserved Read Only Registers */ +#define LCD_CFG_RDREG5F 0x017C +#define IRE_FRAME_CNT_MASK 0x000000C0 +#define IPE_FRAME_CNT_MASK 0x00000030 +#define GRA_FRAME_CNT_MASK 0x0000000C /* Graphic */ +#define DMA_FRAME_CNT_MASK 0x00000003 /* Video */ + +#define LCD_FRAME_CNT (0x017C) /* MMP2 */ + +/* SPI Control Register. */ +#define LCD_SPU_SPI_CTRL 0x0180 +#define CFG_SCLKCNT(div) ((div)<<24) /* 0xFF~0x2 */ +#define CFG_SCLKCNT_MASK 0xFF000000 +#define CFG_RXBITS(rx) (((rx) - 1)<<16) /* 0x1F~0x1 */ +#define CFG_RXBITS_MASK 0x00FF0000 +#define CFG_TXBITS(tx) (((tx) - 1)<<8) /* 0x1F~0x1 */ +#define CFG_TXBITS_MASK 0x0000FF00 +#define CFG_CLKINV(clk) ((clk)<<7) +#define CFG_CLKINV_MASK 0x00000080 +#define CFG_KEEPXFER(transfer) ((transfer)<<6) +#define CFG_KEEPXFER_MASK 0x00000040 +#define CFG_RXBITSTO0(rx) ((rx)<<5) +#define CFG_RXBITSTO0_MASK 0x00000020 +#define CFG_TXBITSTO0(tx) ((tx)<<4) +#define CFG_TXBITSTO0_MASK 0x00000010 +#define CFG_SPI_ENA(spi) ((spi)<<3) +#define CFG_SPI_ENA_MASK 0x00000008 +#define CFG_SPI_SEL(spi) ((spi)<<2) +#define CFG_SPI_SEL_MASK 0x00000004 +#define CFG_SPI_3W4WB(wire) ((wire)<<1) +#define CFG_SPI_3W4WB_MASK 0x00000002 +#define CFG_SPI_START(start) (start) +#define CFG_SPI_START_MASK 0x00000001 + +/* SPI Tx Data Register */ +#define LCD_SPU_SPI_TXDATA 0x0184 + +/* + 1. Smart Pannel 8-bit Bus Control Register. + 2. AHB Slave Path Data Port Register +*/ +#define LCD_SPU_SMPN_CTRL 0x0188 + +/* DMA Control 0 Register */ +#define LCD_SPU_DMA_CTRL0 0x0190 +#define CFG_NOBLENDING(nb) ((nb)<<31) +#define CFG_NOBLENDING_MASK 0x80000000 +#define CFG_GAMMA_ENA(gn) ((gn)<<30) +#define CFG_GAMMA_ENA_MASK 0x40000000 +#define CFG_CBSH_ENA(cn) ((cn)<<29) +#define CFG_CBSH_ENA_MASK 0x20000000 +#define CFG_PALETTE_ENA(pn) ((pn)<<28) +#define CFG_PALETTE_ENA_MASK 0x10000000 +#define CFG_ARBFAST_ENA(an) ((an)<<27) +#define CFG_ARBFAST_ENA_MASK 0x08000000 +#define CFG_HWC_1BITMOD(mode) ((mode)<<26) +#define CFG_HWC_1BITMOD_MASK 0x04000000 +#define CFG_HWC_1BITENA(mn) ((mn)<<25) +#define CFG_HWC_1BITENA_MASK 0x02000000 +#define CFG_HWC_ENA(cn) ((cn)<<24) +#define CFG_HWC_ENA_MASK 0x01000000 +#define CFG_DMAFORMAT(dmaformat) ((dmaformat)<<20) +#define CFG_DMAFORMAT_MASK 0x00F00000 +#define CFG_GRAFORMAT(graformat) ((graformat)<<16) +#define CFG_GRAFORMAT_MASK 0x000F0000 +/* for graphic part */ +#define CFG_GRA_FTOGGLE(toggle) ((toggle)<<15) +#define CFG_GRA_FTOGGLE_MASK 0x00008000 +#define CFG_GRA_HSMOOTH(smooth) ((smooth)<<14) +#define CFG_GRA_HSMOOTH_MASK 0x00004000 +#define CFG_GRA_TSTMODE(test) ((test)<<13) +#define CFG_GRA_TSTMODE_MASK 0x00002000 +#define CFG_GRA_SWAPRB(swap) ((swap)<<12) +#define CFG_GRA_SWAPRB_MASK 0x00001000 +#define CFG_GRA_SWAPUV(swap) ((swap)<<11) +#define CFG_GRA_SWAPUV_MASK 0x00000800 +#define CFG_GRA_SWAPYU(swap) ((swap)<<10) +#define CFG_GRA_SWAPYU_MASK 0x00000400 +#define CFG_GRA_SWAP_MASK 0x00001C00 +#define CFG_YUV2RGB_GRA(cvrt) ((cvrt)<<9) +#define CFG_YUV2RGB_GRA_MASK 0x00000200 +#define CFG_GRA_ENA(gra) ((gra)<<8) +#define CFG_GRA_ENA_MASK 0x00000100 +#define dma0_gfx_masks (CFG_GRAFORMAT_MASK | CFG_GRA_FTOGGLE_MASK | \ + CFG_GRA_HSMOOTH_MASK | CFG_GRA_TSTMODE_MASK | CFG_GRA_SWAP_MASK | \ + CFG_YUV2RGB_GRA_MASK | CFG_GRA_ENA_MASK) +/* for video part */ +#define CFG_DMA_FTOGGLE(toggle) ((toggle)<<7) +#define CFG_DMA_FTOGGLE_MASK 0x00000080 +#define CFG_DMA_HSMOOTH(smooth) ((smooth)<<6) +#define CFG_DMA_HSMOOTH_MASK 0x00000040 +#define CFG_DMA_TSTMODE(test) ((test)<<5) +#define CFG_DMA_TSTMODE_MASK 0x00000020 +#define CFG_DMA_SWAPRB(swap) ((swap)<<4) +#define CFG_DMA_SWAPRB_MASK 0x00000010 +#define CFG_DMA_SWAPUV(swap) ((swap)<<3) +#define CFG_DMA_SWAPUV_MASK 0x00000008 +#define CFG_DMA_SWAPYU(swap) ((swap)<<2) +#define CFG_DMA_SWAPYU_MASK 0x00000004 +#define CFG_DMA_SWAP_MASK 0x0000001C +#define CFG_YUV2RGB_DMA(cvrt) ((cvrt)<<1) +#define CFG_YUV2RGB_DMA_MASK 0x00000002 +#define CFG_DMA_ENA(video) (video) +#define CFG_DMA_ENA_MASK 0x00000001 +#define dma0_vid_masks (CFG_DMAFORMAT_MASK | CFG_DMA_FTOGGLE_MASK | \ + CFG_DMA_HSMOOTH_MASK | CFG_DMA_TSTMODE_MASK | CFG_DMA_SWAP_MASK | \ + CFG_YUV2RGB_DMA_MASK | CFG_DMA_ENA_MASK) +#define dma_palette(val) ((val ? 1 : 0) << 28) +#define dma_fmt(vid, val) ((val & 0xf) << ((vid) ? 20 : 16)) +#define dma_swaprb(vid, val) ((val ? 1 : 0) << ((vid) ? 4 : 12)) +#define dma_swapuv(vid, val) ((val ? 1 : 0) << ((vid) ? 3 : 11)) +#define dma_swapyuv(vid, val) ((val ? 1 : 0) << ((vid) ? 2 : 10)) +#define dma_csc(vid, val) ((val ? 1 : 0) << ((vid) ? 1 : 9)) +#define dma_hsmooth(vid, val) ((val ? 1 : 0) << ((vid) ? 6 : 14)) +#define dma_mask(vid) (dma_palette(1) | dma_fmt(vid, 0xf) | dma_csc(vid, 1) \ + | dma_swaprb(vid, 1) | dma_swapuv(vid, 1) | dma_swapyuv(vid, 1)) + +/* DMA Control 1 Register */ +#define LCD_SPU_DMA_CTRL1 0x0194 +#define CFG_FRAME_TRIG(trig) ((trig)<<31) +#define CFG_FRAME_TRIG_MASK 0x80000000 +#define CFG_VSYNC_TRIG(trig) ((trig)<<28) +#define CFG_VSYNC_TRIG_MASK 0x70000000 +#define CFG_VSYNC_INV(inv) ((inv)<<27) +#define CFG_VSYNC_INV_MASK 0x08000000 +#define CFG_COLOR_KEY_MODE(cmode) ((cmode)<<24) +#define CFG_COLOR_KEY_MASK 0x07000000 +#define CFG_CARRY(carry) ((carry)<<23) +#define CFG_CARRY_MASK 0x00800000 +#define CFG_LNBUF_ENA(lnbuf) ((lnbuf)<<22) +#define CFG_LNBUF_ENA_MASK 0x00400000 +#define CFG_GATED_ENA(gated) ((gated)<<21) +#define CFG_GATED_ENA_MASK 0x00200000 +#define CFG_PWRDN_ENA(power) ((power)<<20) +#define CFG_PWRDN_ENA_MASK 0x00100000 +#define CFG_DSCALE(dscale) ((dscale)<<18) +#define CFG_DSCALE_MASK 0x000C0000 +#define CFG_ALPHA_MODE(amode) ((amode)<<16) +#define CFG_ALPHA_MODE_MASK 0x00030000 +#define CFG_ALPHA(alpha) ((alpha)<<8) +#define CFG_ALPHA_MASK 0x0000FF00 +#define CFG_PXLCMD(pxlcmd) (pxlcmd) +#define CFG_PXLCMD_MASK 0x000000FF + +/* SRAM Control Register */ +#define LCD_SPU_SRAM_CTRL 0x0198 +#define CFG_SRAM_INIT_WR_RD(mode) ((mode)<<14) +#define CFG_SRAM_INIT_WR_RD_MASK 0x0000C000 +#define CFG_SRAM_ADDR_LCDID(id) ((id)<<8) +#define CFG_SRAM_ADDR_LCDID_MASK 0x00000F00 +#define CFG_SRAM_ADDR(addr) (addr) +#define CFG_SRAM_ADDR_MASK 0x000000FF + +/* SRAM Write Data Register */ +#define LCD_SPU_SRAM_WRDAT 0x019C + +/* SRAM RTC/WTC Control Register */ +#define LCD_SPU_SRAM_PARA0 0x01A0 + +/* SRAM Power Down Control Register */ +#define LCD_SPU_SRAM_PARA1 0x01A4 +#define CFG_CSB_256x32(hwc) ((hwc)<<15) /* HWC */ +#define CFG_CSB_256x32_MASK 0x00008000 +#define CFG_CSB_256x24(palette) ((palette)<<14) /* Palette */ +#define CFG_CSB_256x24_MASK 0x00004000 +#define CFG_CSB_256x8(gamma) ((gamma)<<13) /* Gamma */ +#define CFG_CSB_256x8_MASK 0x00002000 +#define CFG_PDWN256x32(pdwn) ((pdwn)<<7) /* HWC */ +#define CFG_PDWN256x32_MASK 0x00000080 +#define CFG_PDWN256x24(pdwn) ((pdwn)<<6) /* Palette */ +#define CFG_PDWN256x24_MASK 0x00000040 +#define CFG_PDWN256x8(pdwn) ((pdwn)<<5) /* Gamma */ +#define CFG_PDWN256x8_MASK 0x00000020 +#define CFG_PDWN32x32(pdwn) ((pdwn)<<3) +#define CFG_PDWN32x32_MASK 0x00000008 +#define CFG_PDWN16x66(pdwn) ((pdwn)<<2) +#define CFG_PDWN16x66_MASK 0x00000004 +#define CFG_PDWN32x66(pdwn) ((pdwn)<<1) +#define CFG_PDWN32x66_MASK 0x00000002 +#define CFG_PDWN64x66(pdwn) (pdwn) +#define CFG_PDWN64x66_MASK 0x00000001 + +/* Smart or Dumb Panel Clock Divider */ +#define LCD_CFG_SCLK_DIV 0x01A8 +#define SCLK_SRC_SEL(src) ((src)<<31) +#define SCLK_SRC_SEL_MASK 0x80000000 +#define SCLK_DISABLE (1<<28) +#define CLK_FRACDIV(frac) ((frac)<<16) +#define CLK_FRACDIV_MASK 0x0FFF0000 +#define DSI1_BITCLK_DIV(div) (div<<8) +#define DSI1_BITCLK_DIV_MASK 0x00000F00 +#define CLK_INT_DIV(div) (div) +#define CLK_INT_DIV_MASK 0x000000FF + +/* Video Contrast Register */ +#define LCD_SPU_CONTRAST 0x01AC +#define CFG_BRIGHTNESS(bright) ((bright)<<16) +#define CFG_BRIGHTNESS_MASK 0xFFFF0000 +#define CFG_CONTRAST(contrast) (contrast) +#define CFG_CONTRAST_MASK 0x0000FFFF + +/* Video Saturation Register */ +#define LCD_SPU_SATURATION 0x01B0 +#define CFG_C_MULTS(mult) ((mult)<<16) +#define CFG_C_MULTS_MASK 0xFFFF0000 +#define CFG_SATURATION(sat) (sat) +#define CFG_SATURATION_MASK 0x0000FFFF + +/* Video Hue Adjust Register */ +#define LCD_SPU_CBSH_HUE 0x01B4 +#define CFG_SIN0(sin0) ((sin0)<<16) +#define CFG_SIN0_MASK 0xFFFF0000 +#define CFG_COS0(con0) (con0) +#define CFG_COS0_MASK 0x0000FFFF + +/* Dump LCD Panel Control Register */ +#define LCD_SPU_DUMB_CTRL 0x01B8 +#define CFG_DUMBMODE(mode) ((mode)<<28) +#define CFG_DUMBMODE_MASK 0xF0000000 +#define CFG_INTFRBSWAP(mode) ((mode)<<24) +#define CFG_INTFRBSWAP_MASK 0x0F000000 +#define CFG_LCDGPIO_O(data) ((data)<<20) +#define CFG_LCDGPIO_O_MASK 0x0FF00000 +#define CFG_LCDGPIO_ENA(gpio) ((gpio)<<12) +#define CFG_LCDGPIO_ENA_MASK 0x000FF000 +#define CFG_BIAS_OUT(bias) ((bias)<<8) +#define CFG_BIAS_OUT_MASK 0x00000100 +#define CFG_REVERSE_RGB(RGB) ((RGB)<<7) +#define CFG_REVERSE_RGB_MASK 0x00000080 +#define CFG_INV_COMPBLANK(blank) ((blank)<<6) +#define CFG_INV_COMPBLANK_MASK 0x00000040 +#define CFG_INV_COMPSYNC(sync) ((sync)<<5) +#define CFG_INV_COMPSYNC_MASK 0x00000020 +#define CFG_INV_HENA(hena) ((hena)<<4) +#define CFG_INV_HENA_MASK 0x00000010 +#define CFG_INV_VSYNC(vsync) ((vsync)<<3) +#define CFG_INV_VSYNC_MASK 0x00000008 +#define CFG_INV_HSYNC(hsync) ((hsync)<<2) +#define CFG_INV_HSYNC_MASK 0x00000004 +#define CFG_INV_PCLK(pclk) ((pclk)<<1) +#define CFG_INV_PCLK_MASK 0x00000002 +#define CFG_DUMB_ENA(dumb) (dumb) +#define CFG_DUMB_ENA_MASK 0x00000001 + +/* LCD I/O Pads Control Register */ +#define SPU_IOPAD_CONTROL 0x01BC +#define CFG_GRA_VM_ENA(vm) ((vm)<<15) +#define CFG_GRA_VM_ENA_MASK 0x00008000 +#define CFG_DMA_VM_ENA(vm) ((vm)<<13) +#define CFG_DMA_VM_ENA_MASK 0x00002000 +#define CFG_CMD_VM_ENA(vm) ((vm)<<12) +#define CFG_CMD_VM_ENA_MASK 0x00001000 +#define CFG_CSC(csc) ((csc)<<8) +#define CFG_CSC_MASK 0x00000300 +#define CFG_BOUNDARY(size) ((size)<<5) +#define CFG_BOUNDARY_MASK 0x00000020 +#define CFG_BURST(len) ((len)<<4) +#define CFG_BURST_MASK 0x00000010 +#define CFG_IOPADMODE(iopad) (iopad) +#define CFG_IOPADMODE_MASK 0x0000000F + +/* LCD Interrupt Control Register */ +#define SPU_IRQ_ENA 0x01C0 +#define DMA_FRAME_IRQ0_ENA(irq) ((irq)<<31) +#define DMA_FRAME_IRQ0_ENA_MASK 0x80000000 +#define DMA_FRAME_IRQ1_ENA(irq) ((irq)<<30) +#define DMA_FRAME_IRQ1_ENA_MASK 0x40000000 +#define DMA_FF_UNDERFLOW_ENA(ff) ((ff)<<29) +#define DMA_FF_UNDERFLOW_ENA_MASK 0x20000000 +#define AXI_BUS_ERROR_IRQ_ENA(irq) ((irq)<<28) +#define AXI_BUS_ERROR_IRQ_ENA_MASK 0x10000000 +#define GRA_FRAME_IRQ0_ENA(irq) ((irq)<<27) +#define GRA_FRAME_IRQ0_ENA_MASK 0x08000000 +#define GRA_FRAME_IRQ1_ENA(irq) ((irq)<<26) +#define GRA_FRAME_IRQ1_ENA_MASK 0x04000000 +#define GRA_FF_UNDERFLOW_ENA(ff) ((ff)<<25) +#define GRA_FF_UNDERFLOW_ENA_MASK 0x02000000 +#define VSYNC_IRQ_ENA(vsync_irq) ((vsync_irq)<<23) +#define VSYNC_IRQ_ENA_MASK 0x00800000 +#define DUMB_FRAMEDONE_ENA(fdone) ((fdone)<<22) +#define DUMB_FRAMEDONE_ENA_MASK 0x00400000 +#define TWC_FRAMEDONE_ENA(fdone) ((fdone)<<21) +#define TWC_FRAMEDONE_ENA_MASK 0x00200000 +#define HWC_FRAMEDONE_ENA(fdone) ((fdone)<<20) +#define HWC_FRAMEDONE_ENA_MASK 0x00100000 +#define SLV_IRQ_ENA(irq) ((irq)<<19) +#define SLV_IRQ_ENA_MASK 0x00080000 +#define SPI_IRQ_ENA(irq) ((irq)<<18) +#define SPI_IRQ_ENA_MASK 0x00040000 +#define PWRDN_IRQ_ENA(irq) ((irq)<<17) +#define PWRDN_IRQ_ENA_MASK 0x00020000 +#define AXI_LATENCY_TOO_LONG_IRQ_ENA(irq) ((irq)<<16) +#define AXI_LATENCY_TOO_LONG_IRQ_ENA_MASK 0x00010000 +#define CLEAN_SPU_IRQ_ISR(irq) (irq) +#define CLEAN_SPU_IRQ_ISR_MASK 0x0000FFFF +#define TV_DMA_FRAME_IRQ0_ENA(irq) ((irq)<<15) +#define TV_DMA_FRAME_IRQ0_ENA_MASK 0x00008000 +#define TV_DMA_FRAME_IRQ1_ENA(irq) ((irq)<<14) +#define TV_DMA_FRAME_IRQ1_ENA_MASK 0x00004000 +#define TV_DMA_FF_UNDERFLOW_ENA(unerrun) ((unerrun)<<13) +#define TV_DMA_FF_UNDERFLOW_ENA_MASK 0x00002000 +#define TVSYNC_IRQ_ENA(irq) ((irq)<<12) +#define TVSYNC_IRQ_ENA_MASK 0x00001000 +#define TV_FRAME_IRQ0_ENA(irq) ((irq)<<11) +#define TV_FRAME_IRQ0_ENA_MASK 0x00000800 +#define TV_FRAME_IRQ1_ENA(irq) ((irq)<<10) +#define TV_FRAME_IRQ1_ENA_MASK 0x00000400 +#define TV_GRA_FF_UNDERFLOW_ENA(unerrun) ((unerrun)<<9) +#define TV_GRA_FF_UNDERFLOW_ENA_MASK 0x00000200 +#define TV_FRAMEDONE_ENA(irq) ((irq)<<8) +#define TV_FRAMEDONE_ENA_MASK 0x00000100 + +/* FIXME - JUST GUESS */ +#define PN2_DMA_FRAME_IRQ0_ENA(irq) ((irq)<<7) +#define PN2_DMA_FRAME_IRQ0_ENA_MASK 0x00000080 +#define PN2_DMA_FRAME_IRQ1_ENA(irq) ((irq)<<6) +#define PN2_DMA_FRAME_IRQ1_ENA_MASK 0x00000040 +#define PN2_DMA_FF_UNDERFLOW_ENA(ff) ((ff)<<5) +#define PN2_DMA_FF_UNDERFLOW_ENA_MASK 0x00000020 +#define PN2_GRA_FRAME_IRQ0_ENA(irq) ((irq)<<3) +#define PN2_GRA_FRAME_IRQ0_ENA_MASK 0x00000008 +#define PN2_GRA_FRAME_IRQ1_ENA(irq) ((irq)<<2) +#define PN2_GRA_FRAME_IRQ1_ENA_MASK 0x04000004 +#define PN2_GRA_FF_UNDERFLOW_ENA(ff) ((ff)<<1) +#define PN2_GRA_FF_UNDERFLOW_ENA_MASK 0x00000002 +#define PN2_VSYNC_IRQ_ENA(irq) ((irq)<<0) +#define PN2_SYNC_IRQ_ENA_MASK 0x00000001 + +#define gf0_imask(id) ((id) ? (((id) & 1) ? TV_FRAME_IRQ0_ENA_MASK \ + : PN2_GRA_FRAME_IRQ0_ENA_MASK) : GRA_FRAME_IRQ0_ENA_MASK) +#define gf1_imask(id) ((id) ? (((id) & 1) ? TV_FRAME_IRQ1_ENA_MASK \ + : PN2_GRA_FRAME_IRQ1_ENA_MASK) : GRA_FRAME_IRQ1_ENA_MASK) +#define vsync_imask(id) ((id) ? (((id) & 1) ? TVSYNC_IRQ_ENA_MASK \ + : PN2_SYNC_IRQ_ENA_MASK) : VSYNC_IRQ_ENA_MASK) +#define vsync_imasks (vsync_imask(0) | vsync_imask(1)) + +#define display_done_imask(id) ((id) ? (((id) & 1) ? TV_FRAMEDONE_ENA_MASK\ + : (PN2_DMA_FRAME_IRQ0_ENA_MASK | PN2_DMA_FRAME_IRQ1_ENA_MASK))\ + : DUMB_FRAMEDONE_ENA_MASK) + +#define display_done_imasks (display_done_imask(0) | display_done_imask(1)) + +#define vf0_imask(id) ((id) ? (((id) & 1) ? TV_DMA_FRAME_IRQ0_ENA_MASK \ + : PN2_DMA_FRAME_IRQ0_ENA_MASK) : DMA_FRAME_IRQ0_ENA_MASK) +#define vf1_imask(id) ((id) ? (((id) & 1) ? TV_DMA_FRAME_IRQ1_ENA_MASK \ + : PN2_DMA_FRAME_IRQ1_ENA_MASK) : DMA_FRAME_IRQ1_ENA_MASK) + +#define gfx_imasks (gf0_imask(0) | gf1_imask(0) | gf0_imask(1) | \ + gf1_imask(1)) +#define vid_imasks (vf0_imask(0) | vf1_imask(0) | vf0_imask(1) | \ + vf1_imask(1)) +#define vid_imask(id) (display_done_imask(id)) + +#define pn1_imasks (gf0_imask(0) | gf1_imask(0) | vsync_imask(0) | \ + display_done_imask(0) | vf0_imask(0) | vf1_imask(0)) +#define tv_imasks (gf0_imask(1) | gf1_imask(1) | vsync_imask(1) | \ + display_done_imask(1) | vf0_imask(1) | vf1_imask(1)) +#define path_imasks(id) ((id) ? (tv_imasks) : (pn1_imasks)) + +/* error indications */ +#define vid_udflow_imask(id) ((id) ? (((id) & 1) ? \ + (TV_DMA_FF_UNDERFLOW_ENA_MASK) : (PN2_DMA_FF_UNDERFLOW_ENA_MASK)) : \ + (DMA_FF_UNDERFLOW_ENA_MASK)) +#define gfx_udflow_imask(id) ((id) ? (((id) & 1) ? \ + (TV_GRA_FF_UNDERFLOW_ENA_MASK) : (PN2_GRA_FF_UNDERFLOW_ENA_MASK)) : \ + (GRA_FF_UNDERFLOW_ENA_MASK)) + +#define err_imask(id) (vid_udflow_imask(id) | gfx_udflow_imask(id) | \ + AXI_BUS_ERROR_IRQ_ENA_MASK | AXI_LATENCY_TOO_LONG_IRQ_ENA_MASK) +#define err_imasks (err_imask(0) | err_imask(1) | err_imask(2)) +/* LCD Interrupt Status Register */ +#define SPU_IRQ_ISR 0x01C4 +#define DMA_FRAME_IRQ0(irq) ((irq)<<31) +#define DMA_FRAME_IRQ0_MASK 0x80000000 +#define DMA_FRAME_IRQ1(irq) ((irq)<<30) +#define DMA_FRAME_IRQ1_MASK 0x40000000 +#define DMA_FF_UNDERFLOW(ff) ((ff)<<29) +#define DMA_FF_UNDERFLOW_MASK 0x20000000 +#define AXI_BUS_ERROR_IRQ(irq) ((irq)<<28) +#define AXI_BUS_ERROR_IRQ_MASK 0x10000000 +#define GRA_FRAME_IRQ0(irq) ((irq)<<27) +#define GRA_FRAME_IRQ0_MASK 0x08000000 +#define GRA_FRAME_IRQ1(irq) ((irq)<<26) +#define GRA_FRAME_IRQ1_MASK 0x04000000 +#define GRA_FF_UNDERFLOW(ff) ((ff)<<25) +#define GRA_FF_UNDERFLOW_MASK 0x02000000 +#define VSYNC_IRQ(vsync_irq) ((vsync_irq)<<23) +#define VSYNC_IRQ_MASK 0x00800000 +#define DUMB_FRAMEDONE(fdone) ((fdone)<<22) +#define DUMB_FRAMEDONE_MASK 0x00400000 +#define TWC_FRAMEDONE(fdone) ((fdone)<<21) +#define TWC_FRAMEDONE_MASK 0x00200000 +#define HWC_FRAMEDONE(fdone) ((fdone)<<20) +#define HWC_FRAMEDONE_MASK 0x00100000 +#define SLV_IRQ(irq) ((irq)<<19) +#define SLV_IRQ_MASK 0x00080000 +#define SPI_IRQ(irq) ((irq)<<18) +#define SPI_IRQ_MASK 0x00040000 +#define PWRDN_IRQ(irq) ((irq)<<17) +#define PWRDN_IRQ_MASK 0x00020000 +#define AXI_LATENCY_TOO_LONGR_IRQ(irq) ((irq)<<16) +#define AXI_LATENCY_TOO_LONGR_IRQ_MASK 0x00010000 +#define TV_DMA_FRAME_IRQ0(irq) ((irq)<<15) +#define TV_DMA_FRAME_IRQ0_MASK 0x00008000 +#define TV_DMA_FRAME_IRQ1(irq) ((irq)<<14) +#define TV_DMA_FRAME_IRQ1_MASK 0x00004000 +#define TV_DMA_FF_UNDERFLOW(unerrun) ((unerrun)<<13) +#define TV_DMA_FF_UNDERFLOW_MASK 0x00002000 +#define TVSYNC_IRQ(irq) ((irq)<<12) +#define TVSYNC_IRQ_MASK 0x00001000 +#define TV_FRAME_IRQ0(irq) ((irq)<<11) +#define TV_FRAME_IRQ0_MASK 0x00000800 +#define TV_FRAME_IRQ1(irq) ((irq)<<10) +#define TV_FRAME_IRQ1_MASK 0x00000400 +#define TV_GRA_FF_UNDERFLOW(unerrun) ((unerrun)<<9) +#define TV_GRA_FF_UNDERFLOW_MASK 0x00000200 +#define PN2_DMA_FRAME_IRQ0(irq) ((irq)<<7) +#define PN2_DMA_FRAME_IRQ0_MASK 0x00000080 +#define PN2_DMA_FRAME_IRQ1(irq) ((irq)<<6) +#define PN2_DMA_FRAME_IRQ1_MASK 0x00000040 +#define PN2_DMA_FF_UNDERFLOW(ff) ((ff)<<5) +#define PN2_DMA_FF_UNDERFLOW_MASK 0x00000020 +#define PN2_GRA_FRAME_IRQ0(irq) ((irq)<<3) +#define PN2_GRA_FRAME_IRQ0_MASK 0x00000008 +#define PN2_GRA_FRAME_IRQ1(irq) ((irq)<<2) +#define PN2_GRA_FRAME_IRQ1_MASK 0x04000004 +#define PN2_GRA_FF_UNDERFLOW(ff) ((ff)<<1) +#define PN2_GRA_FF_UNDERFLOW_MASK 0x00000002 +#define PN2_VSYNC_IRQ(irq) ((irq)<<0) +#define PN2_SYNC_IRQ_MASK 0x00000001 + +/* LCD FIFO Depth register */ +#define LCD_FIFO_DEPTH 0x01c8 +#define VIDEO_FIFO(fi) ((fi) << 0) +#define VIDEO_FIFO_MASK 0x00000003 +#define GRAPHIC_FIFO(fi) ((fi) << 2) +#define GRAPHIC_FIFO_MASK 0x0000000c + +/* read-only */ +#define DMA_FRAME_IRQ0_LEVEL_MASK 0x00008000 +#define DMA_FRAME_IRQ1_LEVEL_MASK 0x00004000 +#define DMA_FRAME_CNT_ISR_MASK 0x00003000 +#define GRA_FRAME_IRQ0_LEVEL_MASK 0x00000800 +#define GRA_FRAME_IRQ1_LEVEL_MASK 0x00000400 +#define GRA_FRAME_CNT_ISR_MASK 0x00000300 +#define VSYNC_IRQ_LEVEL_MASK 0x00000080 +#define DUMB_FRAMEDONE_LEVEL_MASK 0x00000040 +#define TWC_FRAMEDONE_LEVEL_MASK 0x00000020 +#define HWC_FRAMEDONE_LEVEL_MASK 0x00000010 +#define SLV_FF_EMPTY_MASK 0x00000008 +#define DMA_FF_ALLEMPTY_MASK 0x00000004 +#define GRA_FF_ALLEMPTY_MASK 0x00000002 +#define PWRDN_IRQ_LEVEL_MASK 0x00000001 + +/* 32 bit LCD Interrupt Reset Status*/ +#define SPU_IRQ_RSR (0x01C8) +/* 32 bit Panel Path Graphic Partial Display Horizontal Control Register*/ +#define LCD_GRA_CUTHPXL (0x01CC) +/* 32 bit Panel Path Graphic Partial Display Vertical Control Register*/ +#define LCD_GRA_CUTVLN (0x01D0) +/* 32 bit TV Path Graphic Partial Display Horizontal Control Register*/ +#define LCD_TVG_CUTHPXL (0x01D4) +/* 32 bit TV Path Graphic Partial Display Vertical Control Register*/ +#define LCD_TVG_CUTVLN (0x01D8) +/* 32 bit LCD Global Control Register*/ +#define LCD_TOP_CTRL (0x01DC) +/* 32 bit LCD SQU Line Buffer Control Register 1*/ +#define LCD_SQULN1_CTRL (0x01E0) +/* 32 bit LCD SQU Line Buffer Control Register 2*/ +#define LCD_SQULN2_CTRL (0x01E4) +#define squln_ctrl(id) ((id) ? (((id) & 1) ? LCD_SQULN2_CTRL : \ + LCD_PN2_SQULN1_CTRL) : LCD_SQULN1_CTRL) + +/* 32 bit LCD Mixed Overlay Control Register */ +#define LCD_AFA_ALL2ONE (0x01E8) + +#define LCD_PN2_SCLK_DIV (0x01EC) +#define LCD_PN2_TCLK_DIV (0x01F0) +#define LCD_LVDS_SCLK_DIV_WR (0x01F4) +#define LCD_LVDS_SCLK_DIV_RD (0x01FC) +#define PN2_LCD_DMA_START_ADDR_Y0 (0x0200) +#define PN2_LCD_DMA_START_ADDR_U0 (0x0204) +#define PN2_LCD_DMA_START_ADDR_V0 (0x0208) +#define PN2_LCD_DMA_START_ADDR_C0 (0x020C) +#define PN2_LCD_DMA_START_ADDR_Y1 (0x0210) +#define PN2_LCD_DMA_START_ADDR_U1 (0x0214) +#define PN2_LCD_DMA_START_ADDR_V1 (0x0218) +#define PN2_LCD_DMA_START_ADDR_C1 (0x021C) +#define PN2_LCD_DMA_PITCH_YC (0x0220) +#define PN2_LCD_DMA_PITCH_UV (0x0224) +#define PN2_LCD_DMA_OVSA_HPXL_VLN (0x0228) +#define PN2_LCD_DMA_HPXL_VLN (0x022C) +#define PN2_LCD_DMAZM_HPXL_VLN (0x0230) +#define PN2_LCD_GRA_START_ADDR0 (0x0234) +#define PN2_LCD_GRA_START_ADDR1 (0x0238) +#define PN2_LCD_GRA_PITCH (0x023C) +#define PN2_LCD_GRA_OVSA_HPXL_VLN (0x0240) +#define PN2_LCD_GRA_HPXL_VLN (0x0244) +#define PN2_LCD_GRAZM_HPXL_VLN (0x0248) +#define PN2_LCD_HWC_OVSA_HPXL_VLN (0x024C) +#define PN2_LCD_HWC_HPXL_VLN (0x0250) +#define LCD_PN2_V_H_TOTAL (0x0254) +#define LCD_PN2_V_H_ACTIVE (0x0258) +#define LCD_PN2_H_PORCH (0x025C) +#define LCD_PN2_V_PORCH (0x0260) +#define LCD_PN2_BLANKCOLOR (0x0264) +#define LCD_PN2_ALPHA_COLOR1 (0x0268) +#define LCD_PN2_ALPHA_COLOR2 (0x026C) +#define LCD_PN2_COLORKEY_Y (0x0270) +#define LCD_PN2_COLORKEY_U (0x0274) +#define LCD_PN2_COLORKEY_V (0x0278) +#define LCD_PN2_SEPXLCNT (0x027C) +#define LCD_TV_V_H_TOTAL_FLD (0x0280) +#define LCD_TV_V_PORCH_FLD (0x0284) +#define LCD_TV_SEPXLCNT_FLD (0x0288) + +#define LCD_2ND_ALPHA (0x0294) +#define LCD_PN2_CONTRAST (0x0298) +#define LCD_PN2_SATURATION (0x029c) +#define LCD_PN2_CBSH_HUE (0x02a0) +#define LCD_TIMING_EXT (0x02C0) +#define LCD_PN2_LAYER_ALPHA_SEL1 (0x02c4) +#define LCD_PN2_CTRL0 (0x02C8) +#define TV_LAYER_ALPHA_SEL1 (0x02cc) +#define LCD_SMPN2_CTRL (0x02D0) +#define LCD_IO_OVERL_MAP_CTRL (0x02D4) +#define LCD_DUMB2_CTRL (0x02d8) +#define LCD_PN2_CTRL1 (0x02DC) +#define PN2_IOPAD_CONTROL (0x02E0) +#define LCD_PN2_SQULN1_CTRL (0x02E4) +#define PN2_LCD_GRA_CUTHPXL (0x02e8) +#define PN2_LCD_GRA_CUTVLN (0x02ec) +#define LCD_PN2_SQULN2_CTRL (0x02F0) +#define ALL_LAYER_ALPHA_SEL (0x02F4) + +/* pxa988 has different MASTER_CTRL from MMP3/MMP2 */ +#ifdef CONFIG_CPU_PXA988 +#define TIMING_MASTER_CONTROL (0x01F4) +#define MASTER_ENH(id) (1 << ((id) + 5)) +#define MASTER_ENV(id) (1 << ((id) + 6)) +#else +#define TIMING_MASTER_CONTROL (0x02F8) +#define MASTER_ENH(id) (1 << (id)) +#define MASTER_ENV(id) (1 << ((id) + 4)) +#endif + +#define DSI_START_SEL_SHIFT(id) (((id) << 1) + 8) +#define timing_master_config(path, dsi_id, lcd_id) \ + (MASTER_ENH(path) | MASTER_ENV(path) | \ + (((lcd_id) + ((dsi_id) << 1)) << DSI_START_SEL_SHIFT(path))) + +#define LCD_2ND_BLD_CTL (0x02Fc) +#define LVDS_SRC_MASK (3 << 30) +#define LVDS_SRC_SHIFT (30) +#define LVDS_FMT_MASK (1 << 28) +#define LVDS_FMT_SHIFT (28) + +#define CLK_SCLK (1 << 0) +#define CLK_LVDS_RD (1 << 1) +#define CLK_LVDS_WR (1 << 2) + +#define gra_partdisp_ctrl_hor(id) ((id) ? (((id) & 1) ? \ + LCD_TVG_CUTHPXL : PN2_LCD_GRA_CUTHPXL) : LCD_GRA_CUTHPXL) +#define gra_partdisp_ctrl_ver(id) ((id) ? (((id) & 1) ? \ + LCD_TVG_CUTVLN : PN2_LCD_GRA_CUTVLN) : LCD_GRA_CUTVLN) + +/* + * defined for Configure Dumb Mode + * defined for Configure Dumb Mode + * DUMB LCD Panel bit[31:28] + */ +#define DUMB16_RGB565_0 0x0 +#define DUMB16_RGB565_1 0x1 +#define DUMB18_RGB666_0 0x2 +#define DUMB18_RGB666_1 0x3 +#define DUMB12_RGB444_0 0x4 +#define DUMB12_RGB444_1 0x5 +#define DUMB24_RGB888_0 0x6 +#define DUMB_BLANK 0x7 + +/* + * defined for Configure I/O Pin Allocation Mode + * LCD LCD I/O Pads control register bit[3:0] + */ +#define IOPAD_DUMB24 0x0 +#define IOPAD_DUMB18SPI 0x1 +#define IOPAD_DUMB18GPIO 0x2 +#define IOPAD_DUMB16SPI 0x3 +#define IOPAD_DUMB16GPIO 0x4 +#define IOPAD_DUMB12 0x5 +#define IOPAD_SMART18SPI 0x6 +#define IOPAD_SMART16SPI 0x7 +#define IOPAD_SMART8BOTH 0x8 +#define IOPAD_DUMB18_SMART8 0x9 +#define IOPAD_DUMB16_SMART8SPI 0xa +#define IOPAD_DUMB16_SMART8GPIO 0xb +#define IOPAD_DUMB16_DUMB16 0xc +#define IOPAD_SMART8_SMART8 0xc + +/* + *defined for indicating boundary and cycle burst length + */ +#define CFG_BOUNDARY_1KB (1<<5) +#define CFG_BOUNDARY_4KB (0<<5) +#define CFG_CYC_BURST_LEN16 (1<<4) +#define CFG_CYC_BURST_LEN8 (0<<4) + +/* SRAM ID */ +#define SRAMID_GAMMA_YR 0x0 +#define SRAMID_GAMMA_UG 0x1 +#define SRAMID_GAMMA_VB 0x2 +#define SRAMID_PALATTE 0x3 +#define SRAMID_HWC 0xf + +/* SRAM INIT Read/Write */ +#define SRAMID_INIT_READ 0x0 +#define SRAMID_INIT_WRITE 0x2 +#define SRAMID_INIT_DEFAULT 0x3 + +/* + * defined VSYNC selection mode for DMA control 1 register + * DMA1 bit[30:28] + */ +#define VMODE_SMPN 0x0 +#define VMODE_SMPNIRQ 0x1 +#define VMODE_DUMB 0x2 +#define VMODE_IPE 0x3 +#define VMODE_IRE 0x4 + +/* + * defined Configure Alpha and Alpha mode for DMA control 1 register + * DMA1 bit[15:08](alpha) / bit[17:16](alpha mode) + */ +/* ALPHA mode */ +#define MODE_ALPHA_DMA 0x0 +#define MODE_ALPHA_GRA 0x1 +#define MODE_ALPHA_CFG 0x2 + +/* alpha value */ +#define ALPHA_NOGRAPHIC 0xFF /* all video, no graphic */ +#define ALPHA_NOVIDEO 0x00 /* all graphic, no video */ +#define ALPHA_GRAPHNVIDEO 0x0F /* Selects graphic & video */ + +/* + * defined Pixel Command for DMA control 1 register + * DMA1 bit[07:00] + */ +#define PIXEL_CMD 0x81 + +/* DSI */ +/* DSI1 - 4 Lane Controller base */ +#define DSI1_REGS_PHYSICAL_BASE 0xD420B800 +/* DSI2 - 3 Lane Controller base */ +#define DSI2_REGS_PHYSICAL_BASE 0xD420BA00 + +/* DSI Controller Registers */ +struct dsi_lcd_regs { +#define DSI_LCD1_CTRL_0 0x100 /* DSI Active Panel 1 Control register 0 */ +#define DSI_LCD1_CTRL_1 0x104 /* DSI Active Panel 1 Control register 1 */ + u32 ctrl0; + u32 ctrl1; + u32 reserved1[2]; + +#define DSI_LCD1_TIMING_0 0x110 /* Timing register 0 */ +#define DSI_LCD1_TIMING_1 0x114 /* Timing register 1 */ +#define DSI_LCD1_TIMING_2 0x118 /* Timing register 2 */ +#define DSI_LCD1_TIMING_3 0x11C /* Timing register 3 */ +#define DSI_LCD1_WC_0 0x120 /* Word Count register 0 */ +#define DSI_LCD1_WC_1 0x124 /* Word Count register 1 */ +#define DSI_LCD1_WC_2 0x128 /* Word Count register 2 */ + u32 timing0; + u32 timing1; + u32 timing2; + u32 timing3; + u32 wc0; + u32 wc1; + u32 wc2; + u32 reserved2[1]; + u32 slot_cnt0; + u32 slot_cnt1; + u32 reserved3[2]; + u32 status_0; + u32 status_1; + u32 status_2; + u32 status_3; + u32 status_4; +}; + +struct dsi_regs { +#define DSI_CTRL_0 0x000 /* DSI control register 0 */ +#define DSI_CTRL_1 0x004 /* DSI control register 1 */ + u32 ctrl0; + u32 ctrl1; + u32 reserved1[2]; + u32 irq_status; + u32 irq_mask; + u32 reserved2[2]; + +#define DSI_CPU_CMD_0 0x020 /* DSI CPU packet command register 0 */ +#define DSI_CPU_CMD_1 0x024 /* DSU CPU Packet Command Register 1 */ +#define DSI_CPU_CMD_3 0x02C /* DSU CPU Packet Command Register 3 */ +#define DSI_CPU_WDAT_0 0x030 /* DSI CUP */ + u32 cmd0; + u32 cmd1; + u32 cmd2; + u32 cmd3; + u32 dat0; + u32 status0; + u32 status1; + u32 status2; + u32 status3; + u32 status4; + u32 reserved3[2]; + + u32 smt_cmd; + u32 smt_ctrl0; + u32 smt_ctrl1; + u32 reserved4[1]; + + u32 rx0_status; + +/* Rx Packet Header - data from slave device */ +#define DSI_RX_PKT_HDR_0 0x064 + u32 rx0_header; + u32 rx1_status; + u32 rx1_header; + u32 rx_ctrl; + u32 rx_ctrl1; + u32 rx2_status; + u32 rx2_header; + u32 reserved5[1]; + + u32 phy_ctrl1; +#define DSI_PHY_CTRL_2 0x088 /* DSI DPHI Control Register 2 */ +#define DSI_PHY_CTRL_3 0x08C /* DPHY Control Register 3 */ + u32 phy_ctrl2; + u32 phy_ctrl3; + u32 phy_status0; + u32 phy_status1; + u32 reserved6[5]; + u32 phy_status2; + +#define DSI_PHY_RCOMP_0 0x0B0 /* DPHY Rcomp Control Register */ + u32 phy_rcomp0; + u32 reserved7[3]; +#define DSI_PHY_TIME_0 0x0C0 /* DPHY Timing Control Register 0 */ +#define DSI_PHY_TIME_1 0x0C4 /* DPHY Timing Control Register 1 */ +#define DSI_PHY_TIME_2 0x0C8 /* DPHY Timing Control Register 2 */ +#define DSI_PHY_TIME_3 0x0CC /* DPHY Timing Control Register 3 */ +#define DSI_PHY_TIME_4 0x0D0 /* DPHY Timing Control Register 4 */ +#define DSI_PHY_TIME_5 0x0D4 /* DPHY Timing Control Register 5 */ + u32 phy_timing0; + u32 phy_timing1; + u32 phy_timing2; + u32 phy_timing3; + u32 phy_code_0; + u32 phy_code_1; + u32 reserved8[2]; + u32 mem_ctrl; + u32 tx_timer; + u32 rx_timer; + u32 turn_timer; + u32 reserved9[4]; + +#define DSI_LCD1_CTRL_0 0x100 /* DSI Active Panel 1 Control register 0 */ +#define DSI_LCD1_CTRL_1 0x104 /* DSI Active Panel 1 Control register 1 */ +#define DSI_LCD1_TIMING_0 0x110 /* Timing register 0 */ +#define DSI_LCD1_TIMING_1 0x114 /* Timing register 1 */ +#define DSI_LCD1_TIMING_2 0x118 /* Timing register 2 */ +#define DSI_LCD1_TIMING_3 0x11C /* Timing register 3 */ +#define DSI_LCD1_WC_0 0x120 /* Word Count register 0 */ +#define DSI_LCD1_WC_1 0x124 /* Word Count register 1 */ +#define DSI_LCD1_WC_2 0x128 /* Word Count register 2 */ + struct dsi_lcd_regs lcd1; + u32 reserved10[11]; + struct dsi_lcd_regs lcd2; +}; + +#define DSI_LCD2_CTRL_0 0x180 /* DSI Active Panel 2 Control register 0 */ +#define DSI_LCD2_CTRL_1 0x184 /* DSI Active Panel 2 Control register 1 */ +#define DSI_LCD2_TIMING_0 0x190 /* Timing register 0 */ +#define DSI_LCD2_TIMING_1 0x194 /* Timing register 1 */ +#define DSI_LCD2_TIMING_2 0x198 /* Timing register 2 */ +#define DSI_LCD2_TIMING_3 0x19C /* Timing register 3 */ +#define DSI_LCD2_WC_0 0x1A0 /* Word Count register 0 */ +#define DSI_LCD2_WC_1 0x1A4 /* Word Count register 1 */ +#define DSI_LCD2_WC_2 0x1A8 /* Word Count register 2 */ + +/* DSI_CTRL_0 0x0000 DSI Control Register 0 */ +#define DSI_CTRL_0_CFG_SOFT_RST (1<<31) +#define DSI_CTRL_0_CFG_SOFT_RST_REG (1<<30) +#define DSI_CTRL_0_CFG_LCD1_TX_EN (1<<8) +#define DSI_CTRL_0_CFG_LCD1_SLV (1<<4) +#define DSI_CTRL_0_CFG_LCD1_EN (1<<0) + +/* DSI_CTRL_1 0x0004 DSI Control Register 1 */ +#define DSI_CTRL_1_CFG_EOTP (1<<8) +#define DSI_CTRL_1_CFG_RSVD (2<<4) +#define DSI_CTRL_1_CFG_LCD2_VCH_NO_MASK (3<<2) +#define DSI_CTRL_1_CFG_LCD2_VCH_NO_SHIFT 2 +#define DSI_CTRL_1_CFG_LCD1_VCH_NO_MASK (3<<0) +#define DSI_CTRL_1_CFG_LCD1_VCH_NO_SHIFT 0 + +/* DSI_LCD1_CTRL_1 0x0104 DSI Active Panel 1 Control Register 1 */ +/* LCD 1 Vsync Reset Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_VSYNC_RST_EN (1<<31) +/* LCD 1 2K Pixel Buffer Mode Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_M2K_EN (1<<30) +/* Bit(s) DSI_LCD1_CTRL_1_RSRV_29_23 reserved */ +/* Long Blanking Packet Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_HLP_PKT_EN (1<<22) +/* Extra Long Blanking Packet Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_HEX_PKT_EN (1<<21) +/* Front Porch Packet Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_HFP_PKT_EN (1<<20) +/* hact Packet Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_HACT_PKT_EN (1<<19) +/* Back Porch Packet Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_HBP_PKT_EN (1<<18) +/* hse Packet Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_HSE_PKT_EN (1<<17) +/* hsa Packet Enable */ +#define DSI_LCD1_CTRL_1_CFG_L1_HSA_PKT_EN (1<<16) +/* All Item Enable after Pixel Data */ +#define DSI_LCD1_CTRL_1_CFG_L1_ALL_SLOT_EN (1<<15) +/* Extra Long Packet Enable after Pixel Data */ +#define DSI_LCD1_CTRL_1_CFG_L1_HEX_SLOT_EN (1<<14) +/* Bit(s) DSI_LCD1_CTRL_1_RSRV_13_11 reserved */ +/* Turn Around Bus at Last h Line */ +#define DSI_LCD1_CTRL_1_CFG_L1_LAST_LINE_TURN (1<<10) +/* Go to Low Power Every Frame */ +#define DSI_LCD1_CTRL_1_CFG_L1_LPM_FRAME_EN (1<<9) +/* Go to Low Power Every Line */ +#define DSI_LCD1_CTRL_1_CFG_L1_LPM_LINE_EN (1<<8) +/* Bit(s) DSI_LCD1_CTRL_1_RSRV_7_4 reserved */ +/* DSI Transmission Mode for LCD 1 */ +#define DSI_LCD1_CTRL_1_CFG_L1_BURST_MODE_SHIFT 2 +#define DSI_LCD1_CTRL_1_CFG_L1_BURST_MODE_MASK (3<<2) +/* LCD 1 Input Data RGB Mode for LCD 1 */ +#define DSI_LCD2_CTRL_1_CFG_L1_RGB_TYPE_SHIFT 0 +#define DSI_LCD2_CTRL_1_CFG_L1_RGB_TYPE_MASK (3<<2) + +/* DSI_PHY_CTRL_2 0x0088 DPHY Control Register 2 */ +/* Bit(s) DSI_PHY_CTRL_2_RSRV_31_12 reserved */ +/* DPHY LP Receiver Enable */ +#define DSI_PHY_CTRL_2_CFG_CSR_LANE_RESC_EN_MASK (0xf<<8) +#define DSI_PHY_CTRL_2_CFG_CSR_LANE_RESC_EN_SHIFT 8 +/* DPHY Data Lane Enable */ +#define DSI_PHY_CTRL_2_CFG_CSR_LANE_EN_MASK (0xf<<4) +#define DSI_PHY_CTRL_2_CFG_CSR_LANE_EN_SHIFT 4 +/* DPHY Bus Turn Around */ +#define DSI_PHY_CTRL_2_CFG_CSR_LANE_TURN_MASK (0xf) +#define DSI_PHY_CTRL_2_CFG_CSR_LANE_TURN_SHIFT 0 + +/* DSI_CPU_CMD_1 0x0024 DSI CPU Packet Command Register 1 */ +/* Bit(s) DSI_CPU_CMD_1_RSRV_31_24 reserved */ +/* LPDT TX Enable */ +#define DSI_CPU_CMD_1_CFG_TXLP_LPDT_MASK (0xf<<20) +#define DSI_CPU_CMD_1_CFG_TXLP_LPDT_SHIFT 20 +/* ULPS TX Enable */ +#define DSI_CPU_CMD_1_CFG_TXLP_ULPS_MASK (0xf<<16) +#define DSI_CPU_CMD_1_CFG_TXLP_ULPS_SHIFT 16 +/* Low Power TX Trigger Code */ +#define DSI_CPU_CMD_1_CFG_TXLP_TRIGGER_CODE_MASK (0xffff) +#define DSI_CPU_CMD_1_CFG_TXLP_TRIGGER_CODE_SHIFT 0 + +/* DSI_PHY_TIME_0 0x00c0 DPHY Timing Control Register 0 */ +/* Length of HS Exit Period in tx_clk_esc Cycles */ +#define DSI_PHY_TIME_0_CFG_CSR_TIME_HS_EXIT_MASK (0xff<<24) +#define DSI_PHY_TIME_0_CFG_CSR_TIME_HS_EXIT_SHIFT 24 +/* DPHY HS Trail Period Length */ +#define DSI_PHY_TIME_0_CFG_CSR_TIME_HS_TRAIL_MASK (0xff<<16) +#define DSI_PHY_TIME_0_CFG_CSR_TIME_HS_TRAIL_SHIFT 16 +/* DPHY HS Zero State Length */ +#define DSI_PHY_TIME_0_CDG_CSR_TIME_HS_ZERO_MASK (0xff<<8) +#define DSI_PHY_TIME_0_CDG_CSR_TIME_HS_ZERO_SHIFT 8 +/* DPHY HS Prepare State Length */ +#define DSI_PHY_TIME_0_CFG_CSR_TIME_HS_PREP_MASK (0xff) +#define DSI_PHY_TIME_0_CFG_CSR_TIME_HS_PREP_SHIFT 0 + +/* DSI_PHY_TIME_1 0x00c4 DPHY Timing Control Register 1 */ +/* Time to Drive LP-00 by New Transmitter */ +#define DSI_PHY_TIME_1_CFG_CSR_TIME_TA_GET_MASK (0xff<<24) +#define DSI_PHY_TIME_1_CFG_CSR_TIME_TA_GET_SHIFT 24 +/* Time to Drive LP-00 after Turn Request */ +#define DSI_PHY_TIME_1_CFG_CSR_TIME_TA_GO_MASK (0xff<<16) +#define DSI_PHY_TIME_1_CFG_CSR_TIME_TA_GO_SHIFT 16 +/* DPHY HS Wakeup Period Length */ +#define DSI_PHY_TIME_1_CFG_CSR_TIME_WAKEUP_MASK (0xffff) +#define DSI_PHY_TIME_1_CFG_CSR_TIME_WAKEUP_SHIFT 0 + +/* DSI_PHY_TIME_2 0x00c8 DPHY Timing Control Register 2 */ +/* DPHY CLK Exit Period Length */ +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_EXIT_MASK (0xff<<24) +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_EXIT_SHIFT 24 +/* DPHY CLK Trail Period Length */ +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_TRAIL_MASK (0xff<<16) +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_TRAIL_SHIFT 16 +/* DPHY CLK Zero State Length */ +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_ZERO_MASK (0xff<<8) +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_ZERO_SHIFT 8 +/* DPHY CLK LP Length */ +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_LPX_MASK (0xff) +#define DSI_PHY_TIME_2_CFG_CSR_TIME_CK_LPX_SHIFT 0 + +/* DSI_PHY_TIME_3 0x00cc DPHY Timing Control Register 3 */ +/* Bit(s) DSI_PHY_TIME_3_RSRV_31_16 reserved */ +/* DPHY LP Length */ +#define DSI_PHY_TIME_3_CFG_CSR_TIME_LPX_MASK (0xff<<8) +#define DSI_PHY_TIME_3_CFG_CSR_TIME_LPX_SHIFT 8 +/* DPHY HS req to rdy Length */ +#define DSI_PHY_TIME_3_CFG_CSR_TIME_REQRDY_MASK (0xff) +#define DSI_PHY_TIME_3_CFG_CSR_TIME_REQRDY_SHIFT 0 + +/* + * DSI timings + * PXA988 has diffrent ESC CLK with MMP2/MMP3 + * it will be used in dsi_set_dphy() in pxa688_phy.c + * as low power mode clock. + */ +#ifdef CONFIG_CPU_PXA988 +#define DSI_ESC_CLK 52 /* Unit: Mhz */ +#define DSI_ESC_CLK_T 19 /* Unit: ns */ +#else +#define DSI_ESC_CLK 66 /* Unit: Mhz */ +#define DSI_ESC_CLK_T 15 /* Unit: ns */ +#endif + +/* LVDS */ +/* LVDS_PHY_CTRL */ +#define LVDS_PHY_CTL 0x2A4 +#define LVDS_PLL_LOCK (1 << 31) +#define LVDS_PHY_EXT_MASK (7 << 28) +#define LVDS_PHY_EXT_SHIFT (28) +#define LVDS_CLK_PHASE_MASK (0x7f << 16) +#define LVDS_CLK_PHASE_SHIFT (16) +#define LVDS_SSC_RESET_EXT (1 << 13) +#define LVDS_SSC_MODE_DOWN_SPREAD (1 << 12) +#define LVDS_SSC_EN (1 << 11) +#define LVDS_PU_PLL (1 << 10) +#define LVDS_PU_TX (1 << 9) +#define LVDS_PU_IVREF (1 << 8) +#define LVDS_CLK_SEL (1 << 7) +#define LVDS_CLK_SEL_LVDS_PCLK (1 << 7) +#define LVDS_PD_CH_MASK (0x3f << 1) +#define LVDS_PD_CH(ch) ((ch) << 1) +#define LVDS_RST (1 << 0) + +#define LVDS_PHY_CTL_EXT 0x2A8 + +/* LVDS_PHY_CTRL_EXT1 */ +#define LVDS_SSC_RNGE_MASK (0x7ff << 16) +#define LVDS_SSC_RNGE_SHIFT (16) +#define LVDS_RESERVE_IN_MASK (0xf << 12) +#define LVDS_RESERVE_IN_SHIFT (12) +#define LVDS_TEST_MON_MASK (0x7 << 8) +#define LVDS_TEST_MON_SHIFT (8) +#define LVDS_POL_SWAP_MASK (0x3f << 0) +#define LVDS_POL_SWAP_SHIFT (0) + +/* LVDS_PHY_CTRL_EXT2 */ +#define LVDS_TX_DIF_AMP_MASK (0xf << 24) +#define LVDS_TX_DIF_AMP_SHIFT (24) +#define LVDS_TX_DIF_CM_MASK (0x3 << 22) +#define LVDS_TX_DIF_CM_SHIFT (22) +#define LVDS_SELLV_TXCLK_MASK (0x1f << 16) +#define LVDS_SELLV_TXCLK_SHIFT (16) +#define LVDS_TX_CMFB_EN (0x1 << 15) +#define LVDS_TX_TERM_EN (0x1 << 14) +#define LVDS_SELLV_TXDATA_MASK (0x1f << 8) +#define LVDS_SELLV_TXDATA_SHIFT (8) +#define LVDS_SELLV_OP7_MASK (0x3 << 6) +#define LVDS_SELLV_OP7_SHIFT (6) +#define LVDS_SELLV_OP6_MASK (0x3 << 4) +#define LVDS_SELLV_OP6_SHIFT (4) +#define LVDS_SELLV_OP9_MASK (0x3 << 2) +#define LVDS_SELLV_OP9_SHIFT (2) +#define LVDS_STRESSTST_EN (0x1 << 0) + +/* LVDS_PHY_CTRL_EXT3 */ +#define LVDS_KVCO_MASK (0xf << 28) +#define LVDS_KVCO_SHIFT (28) +#define LVDS_CTUNE_MASK (0x3 << 26) +#define LVDS_CTUNE_SHIFT (26) +#define LVDS_VREG_IVREF_MASK (0x3 << 24) +#define LVDS_VREG_IVREF_SHIFT (24) +#define LVDS_VDDL_MASK (0xf << 20) +#define LVDS_VDDL_SHIFT (20) +#define LVDS_VDDM_MASK (0x3 << 18) +#define LVDS_VDDM_SHIFT (18) +#define LVDS_FBDIV_MASK (0xf << 8) +#define LVDS_FBDIV_SHIFT (8) +#define LVDS_REFDIV_MASK (0x7f << 0) +#define LVDS_REFDIV_SHIFT (0) + +/* LVDS_PHY_CTRL_EXT4 */ +#define LVDS_SSC_FREQ_DIV_MASK (0xffff << 16) +#define LVDS_SSC_FREQ_DIV_SHIFT (16) +#define LVDS_INTPI_MASK (0xf << 12) +#define LVDS_INTPI_SHIFT (12) +#define LVDS_VCODIV_SEL_SE_MASK (0xf << 8) +#define LVDS_VCODIV_SEL_SE_SHIFT (8) +#define LVDS_RESET_INTP_EXT (0x1 << 7) +#define LVDS_VCO_VRNG_MASK (0x7 << 4) +#define LVDS_VCO_VRNG_SHIFT (4) +#define LVDS_PI_EN (0x1 << 3) +#define LVDS_ICP_MASK (0x7 << 0) +#define LVDS_ICP_SHIFT (0) + +/* LVDS_PHY_CTRL_EXT5 */ +#define LVDS_FREQ_OFFSET_MASK (0x1ffff << 15) +#define LVDS_FREQ_OFFSET_SHIFT (15) +#define LVDS_FREQ_OFFSET_VALID (0x1 << 2) +#define LVDS_FREQ_OFFSET_MODE_CK_DIV4_OUT (0x1 << 1) +#define LVDS_FREQ_OFFSET_MODE_EN (0x1 << 0) + +enum { + PATH_PN = 0, + PATH_TV, + PATH_P2, +}; + +/* + * mmp path describes part of mmp path related info: + * which is hiden in display driver and not exported to buffer driver + */ +struct mmphw_ctrl; +struct mmphw_path_plat { + int id; + struct mmphw_ctrl *ctrl; + struct mmp_path *path; + u32 path_config; + u32 link_config; + u32 dsi_rbswap; +}; + +/* mmp ctrl describes mmp controller related info */ +struct mmphw_ctrl { + /* platform related, get from config */ + const char *name; + int irq; + void *reg_base; + struct clk *clk; + + /* sys info */ + struct device *dev; + + /* state */ + int open_count; + int status; + struct mutex access_ok; + + /*pathes*/ + int path_num; + struct mmphw_path_plat path_plats[0]; +}; + +static inline int overlay_is_vid(struct mmp_overlay *overlay) +{ + return overlay->dmafetch_id & 1; +} + +static inline struct mmphw_path_plat *path_to_path_plat(struct mmp_path *path) +{ + return (struct mmphw_path_plat *)path->plat_data; +} + +static inline struct mmphw_ctrl *path_to_ctrl(struct mmp_path *path) +{ + return path_to_path_plat(path)->ctrl; +} + +static inline struct mmphw_ctrl *overlay_to_ctrl(struct mmp_overlay *overlay) +{ + return path_to_ctrl(overlay->path); +} + +static inline void *ctrl_regs(struct mmp_path *path) +{ + return path_to_ctrl(path)->reg_base; +} + +/* path regs, for regs symmetrical for both pathes */ +static inline struct lcd_regs *path_regs(struct mmp_path *path) +{ + if (path->id == PATH_PN) + return (struct lcd_regs *)(ctrl_regs(path) + 0xc0); + else if (path->id == PATH_TV) + return (struct lcd_regs *)ctrl_regs(path); + else if (path->id == PATH_P2) + return (struct lcd_regs *)(ctrl_regs(path) + 0x200); + else { + dev_err(path->dev, "path id %d invalid\n", path->id); + BUG_ON(1); + return NULL; + } +} + +#ifdef CONFIG_MMP_DISP_SPI +extern int lcd_spi_register(struct mmphw_ctrl *ctrl); +#endif +#endif /* _MMP_CTRL_H_ */ diff --git a/drivers/video/fbdev/mmp/hw/mmp_spi.c b/drivers/video/fbdev/mmp/hw/mmp_spi.c new file mode 100644 index 000000000000..e62ca7bf0d5e --- /dev/null +++ b/drivers/video/fbdev/mmp/hw/mmp_spi.c @@ -0,0 +1,180 @@ +/* + * linux/drivers/video/mmp/hw/mmp_spi.c + * using the spi in LCD controler for commands send + * + * Copyright (C) 2012 Marvell Technology Group Ltd. + * Authors: Guoqing Li <ligq@marvell.com> + * Lisa Du <cldu@marvell.com> + * Zhou Zhu <zzhu3@marvell.com> + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/spi/spi.h> +#include "mmp_ctrl.h" + +/** + * spi_write - write command to the SPI port + * @data: can be 8/16/32-bit, MSB justified data to write. + * @len: data length. + * + * Wait bus transfer complete IRQ. + * The caller is expected to perform the necessary locking. + * + * Returns: + * %-ETIMEDOUT timeout occurred + * 0 success + */ +static inline int lcd_spi_write(struct spi_device *spi, u32 data) +{ + int timeout = 100000, isr, ret = 0; + u32 tmp; + void *reg_base = + *(void **)spi_master_get_devdata(spi->master); + + /* clear ISR */ + writel_relaxed(~SPI_IRQ_MASK, reg_base + SPU_IRQ_ISR); + + switch (spi->bits_per_word) { + case 8: + writel_relaxed((u8)data, reg_base + LCD_SPU_SPI_TXDATA); + break; + case 16: + writel_relaxed((u16)data, reg_base + LCD_SPU_SPI_TXDATA); + break; + case 32: + writel_relaxed((u32)data, reg_base + LCD_SPU_SPI_TXDATA); + break; + default: + dev_err(&spi->dev, "Wrong spi bit length\n"); + } + + /* SPI start to send command */ + tmp = readl_relaxed(reg_base + LCD_SPU_SPI_CTRL); + tmp &= ~CFG_SPI_START_MASK; + tmp |= CFG_SPI_START(1); + writel(tmp, reg_base + LCD_SPU_SPI_CTRL); + + isr = readl_relaxed(reg_base + SPU_IRQ_ISR); + while (!(isr & SPI_IRQ_ENA_MASK)) { + udelay(100); + isr = readl_relaxed(reg_base + SPU_IRQ_ISR); + if (!--timeout) { + ret = -ETIMEDOUT; + dev_err(&spi->dev, "spi cmd send time out\n"); + break; + } + } + + tmp = readl_relaxed(reg_base + LCD_SPU_SPI_CTRL); + tmp &= ~CFG_SPI_START_MASK; + tmp |= CFG_SPI_START(0); + writel_relaxed(tmp, reg_base + LCD_SPU_SPI_CTRL); + + writel_relaxed(~SPI_IRQ_MASK, reg_base + SPU_IRQ_ISR); + + return ret; +} + +static int lcd_spi_setup(struct spi_device *spi) +{ + void *reg_base = + *(void **)spi_master_get_devdata(spi->master); + u32 tmp; + + tmp = CFG_SCLKCNT(16) | + CFG_TXBITS(spi->bits_per_word) | + CFG_SPI_SEL(1) | CFG_SPI_ENA(1) | + CFG_SPI_3W4WB(1); + writel(tmp, reg_base + LCD_SPU_SPI_CTRL); + + /* + * After set mode it need a time to pull up the spi singals, + * or it would cause the wrong waveform when send spi command, + * especially on pxa910h + */ + tmp = readl_relaxed(reg_base + SPU_IOPAD_CONTROL); + if ((tmp & CFG_IOPADMODE_MASK) != IOPAD_DUMB18SPI) + writel_relaxed(IOPAD_DUMB18SPI | + (tmp & ~CFG_IOPADMODE_MASK), + reg_base + SPU_IOPAD_CONTROL); + udelay(20); + return 0; +} + +static int lcd_spi_one_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct spi_transfer *t; + int i; + + list_for_each_entry(t, &m->transfers, transfer_list) { + switch (spi->bits_per_word) { + case 8: + for (i = 0; i < t->len; i++) + lcd_spi_write(spi, ((u8 *)t->tx_buf)[i]); + break; + case 16: + for (i = 0; i < t->len/2; i++) + lcd_spi_write(spi, ((u16 *)t->tx_buf)[i]); + break; + case 32: + for (i = 0; i < t->len/4; i++) + lcd_spi_write(spi, ((u32 *)t->tx_buf)[i]); + break; + default: + dev_err(&spi->dev, "Wrong spi bit length\n"); + } + } + + m->status = 0; + if (m->complete) + m->complete(m->context); + return 0; +} + +int lcd_spi_register(struct mmphw_ctrl *ctrl) +{ + struct spi_master *master; + void **p_regbase; + int err; + + master = spi_alloc_master(ctrl->dev, sizeof(void *)); + if (!master) { + dev_err(ctrl->dev, "unable to allocate SPI master\n"); + return -ENOMEM; + } + p_regbase = spi_master_get_devdata(master); + *p_regbase = ctrl->reg_base; + + /* set bus num to 5 to avoid conflict with other spi hosts */ + master->bus_num = 5; + master->num_chipselect = 1; + master->setup = lcd_spi_setup; + master->transfer = lcd_spi_one_transfer; + + err = spi_register_master(master); + if (err < 0) { + dev_err(ctrl->dev, "unable to register SPI master\n"); + spi_master_put(master); + return err; + } + + dev_info(&master->dev, "registered\n"); + + return 0; +} diff --git a/drivers/video/fbdev/mmp/panel/Kconfig b/drivers/video/fbdev/mmp/panel/Kconfig new file mode 100644 index 000000000000..4b2c4f457b11 --- /dev/null +++ b/drivers/video/fbdev/mmp/panel/Kconfig @@ -0,0 +1,6 @@ +config MMP_PANEL_TPOHVGA + bool "tpohvga panel TJ032MD01BW support" + depends on SPI_MASTER + default n + help + tpohvga panel support diff --git a/drivers/video/fbdev/mmp/panel/Makefile b/drivers/video/fbdev/mmp/panel/Makefile new file mode 100644 index 000000000000..2f91611c7e5e --- /dev/null +++ b/drivers/video/fbdev/mmp/panel/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MMP_PANEL_TPOHVGA) += tpo_tj032md01bw.o diff --git a/drivers/video/fbdev/mmp/panel/tpo_tj032md01bw.c b/drivers/video/fbdev/mmp/panel/tpo_tj032md01bw.c new file mode 100644 index 000000000000..998978b08f5e --- /dev/null +++ b/drivers/video/fbdev/mmp/panel/tpo_tj032md01bw.c @@ -0,0 +1,186 @@ +/* + * linux/drivers/video/mmp/panel/tpo_tj032md01bw.c + * active panel using spi interface to do init + * + * Copyright (C) 2012 Marvell Technology Group Ltd. + * Authors: Guoqing Li <ligq@marvell.com> + * Lisa Du <cldu@marvell.com> + * Zhou Zhu <zzhu3@marvell.com> + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spi/spi.h> +#include <video/mmp_disp.h> + +static u16 init[] = { + 0x0801, + 0x0800, + 0x0200, + 0x0304, + 0x040e, + 0x0903, + 0x0b18, + 0x0c53, + 0x0d01, + 0x0ee0, + 0x0f01, + 0x1058, + 0x201e, + 0x210a, + 0x220a, + 0x231e, + 0x2400, + 0x2532, + 0x2600, + 0x27ac, + 0x2904, + 0x2aa2, + 0x2b45, + 0x2c45, + 0x2d15, + 0x2e5a, + 0x2fff, + 0x306b, + 0x310d, + 0x3248, + 0x3382, + 0x34bd, + 0x35e7, + 0x3618, + 0x3794, + 0x3801, + 0x395d, + 0x3aae, + 0x3bff, + 0x07c9, +}; + +static u16 poweroff[] = { + 0x07d9, +}; + +struct tpohvga_plat_data { + void (*plat_onoff)(int status); + struct spi_device *spi; +}; + +static void tpohvga_onoff(struct mmp_panel *panel, int status) +{ + struct tpohvga_plat_data *plat = panel->plat_data; + int ret; + + if (status) { + plat->plat_onoff(1); + + ret = spi_write(plat->spi, init, sizeof(init)); + if (ret < 0) + dev_warn(panel->dev, "init cmd failed(%d)\n", ret); + } else { + ret = spi_write(plat->spi, poweroff, sizeof(poweroff)); + if (ret < 0) + dev_warn(panel->dev, "poweroff cmd failed(%d)\n", ret); + + plat->plat_onoff(0); + } +} + +static struct mmp_mode mmp_modes_tpohvga[] = { + [0] = { + .pixclock_freq = 10394400, + .refresh = 60, + .xres = 320, + .yres = 480, + .hsync_len = 10, + .left_margin = 15, + .right_margin = 10, + .vsync_len = 2, + .upper_margin = 4, + .lower_margin = 2, + .invert_pixclock = 1, + .pix_fmt_out = PIXFMT_RGB565, + }, +}; + +static int tpohvga_get_modelist(struct mmp_panel *panel, + struct mmp_mode **modelist) +{ + *modelist = mmp_modes_tpohvga; + return 1; +} + +static struct mmp_panel panel_tpohvga = { + .name = "tpohvga", + .panel_type = PANELTYPE_ACTIVE, + .get_modelist = tpohvga_get_modelist, + .set_onoff = tpohvga_onoff, +}; + +static int tpohvga_probe(struct spi_device *spi) +{ + struct mmp_mach_panel_info *mi; + int ret; + struct tpohvga_plat_data *plat_data; + + /* get configs from platform data */ + mi = spi->dev.platform_data; + if (mi == NULL) { + dev_err(&spi->dev, "%s: no platform data defined\n", __func__); + return -EINVAL; + } + + /* setup spi related info */ + spi->bits_per_word = 16; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "spi setup failed %d", ret); + return ret; + } + + plat_data = kzalloc(sizeof(*plat_data), GFP_KERNEL); + if (plat_data == NULL) + return -ENOMEM; + + plat_data->spi = spi; + plat_data->plat_onoff = mi->plat_set_onoff; + panel_tpohvga.plat_data = plat_data; + panel_tpohvga.plat_path_name = mi->plat_path_name; + panel_tpohvga.dev = &spi->dev; + + mmp_register_panel(&panel_tpohvga); + + return 0; +} + +static struct spi_driver panel_tpohvga_driver = { + .driver = { + .name = "tpo-hvga", + .owner = THIS_MODULE, + }, + .probe = tpohvga_probe, +}; +module_spi_driver(panel_tpohvga_driver); + +MODULE_AUTHOR("Lisa Du<cldu@marvell.com>"); +MODULE_DESCRIPTION("Panel driver for tpohvga"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/modedb.c b/drivers/video/fbdev/modedb.c new file mode 100644 index 000000000000..a9a907c440d7 --- /dev/null +++ b/drivers/video/fbdev/modedb.c @@ -0,0 +1,1137 @@ +/* + * linux/drivers/video/modedb.c -- Standard video mode database management + * + * Copyright (C) 1999 Geert Uytterhoeven + * + * 2001 - Documented with DocBook + * - Brad Douglas <brad@neruo.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/slab.h> +#include <linux/fb.h> +#include <linux/kernel.h> + +#undef DEBUG + +#define name_matches(v, s, l) \ + ((v).name && !strncmp((s), (v).name, (l)) && strlen((v).name) == (l)) +#define res_matches(v, x, y) \ + ((v).xres == (x) && (v).yres == (y)) + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk("modedb %s: " fmt, __func__ , ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +const char *fb_mode_option; +EXPORT_SYMBOL_GPL(fb_mode_option); + +/* + * Standard video mode definitions (taken from XFree86) + */ + +static const struct fb_videomode modedb[] = { + + /* 640x400 @ 70 Hz, 31.5 kHz hsync */ + { NULL, 70, 640, 400, 39721, 40, 24, 39, 9, 96, 2, 0, + FB_VMODE_NONINTERLACED }, + + /* 640x480 @ 60 Hz, 31.5 kHz hsync */ + { NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2, 0, + FB_VMODE_NONINTERLACED }, + + /* 800x600 @ 56 Hz, 35.15 kHz hsync */ + { NULL, 56, 800, 600, 27777, 128, 24, 22, 1, 72, 2, 0, + FB_VMODE_NONINTERLACED }, + + /* 1024x768 @ 87 Hz interlaced, 35.5 kHz hsync */ + { NULL, 87, 1024, 768, 22271, 56, 24, 33, 8, 160, 8, 0, + FB_VMODE_INTERLACED }, + + /* 640x400 @ 85 Hz, 37.86 kHz hsync */ + { NULL, 85, 640, 400, 31746, 96, 32, 41, 1, 64, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED }, + + /* 640x480 @ 72 Hz, 36.5 kHz hsync */ + { NULL, 72, 640, 480, 31746, 144, 40, 30, 8, 40, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 640x480 @ 75 Hz, 37.50 kHz hsync */ + { NULL, 75, 640, 480, 31746, 120, 16, 16, 1, 64, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 800x600 @ 60 Hz, 37.8 kHz hsync */ + { NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 640x480 @ 85 Hz, 43.27 kHz hsync */ + { NULL, 85, 640, 480, 27777, 80, 56, 25, 1, 56, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 1152x864 @ 89 Hz interlaced, 44 kHz hsync */ + { NULL, 89, 1152, 864, 15384, 96, 16, 110, 1, 216, 10, 0, + FB_VMODE_INTERLACED }, + /* 800x600 @ 72 Hz, 48.0 kHz hsync */ + { NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1024x768 @ 60 Hz, 48.4 kHz hsync */ + { NULL, 60, 1024, 768, 15384, 168, 8, 29, 3, 144, 6, 0, + FB_VMODE_NONINTERLACED }, + + /* 640x480 @ 100 Hz, 53.01 kHz hsync */ + { NULL, 100, 640, 480, 21834, 96, 32, 36, 8, 96, 6, 0, + FB_VMODE_NONINTERLACED }, + + /* 1152x864 @ 60 Hz, 53.5 kHz hsync */ + { NULL, 60, 1152, 864, 11123, 208, 64, 16, 4, 256, 8, 0, + FB_VMODE_NONINTERLACED }, + + /* 800x600 @ 85 Hz, 55.84 kHz hsync */ + { NULL, 85, 800, 600, 16460, 160, 64, 36, 16, 64, 5, 0, + FB_VMODE_NONINTERLACED }, + + /* 1024x768 @ 70 Hz, 56.5 kHz hsync */ + { NULL, 70, 1024, 768, 13333, 144, 24, 29, 3, 136, 6, 0, + FB_VMODE_NONINTERLACED }, + + /* 1280x1024 @ 87 Hz interlaced, 51 kHz hsync */ + { NULL, 87, 1280, 1024, 12500, 56, 16, 128, 1, 216, 12, 0, + FB_VMODE_INTERLACED }, + + /* 800x600 @ 100 Hz, 64.02 kHz hsync */ + { NULL, 100, 800, 600, 14357, 160, 64, 30, 4, 64, 6, 0, + FB_VMODE_NONINTERLACED }, + + /* 1024x768 @ 76 Hz, 62.5 kHz hsync */ + { NULL, 76, 1024, 768, 11764, 208, 8, 36, 16, 120, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 1152x864 @ 70 Hz, 62.4 kHz hsync */ + { NULL, 70, 1152, 864, 10869, 106, 56, 20, 1, 160, 10, 0, + FB_VMODE_NONINTERLACED }, + + /* 1280x1024 @ 61 Hz, 64.2 kHz hsync */ + { NULL, 61, 1280, 1024, 9090, 200, 48, 26, 1, 184, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 1400x1050 @ 60Hz, 63.9 kHz hsync */ + { NULL, 60, 1400, 1050, 9259, 136, 40, 13, 1, 112, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 1400x1050 @ 75,107 Hz, 82,392 kHz +hsync +vsync*/ + { NULL, 75, 1400, 1050, 7190, 120, 56, 23, 10, 112, 13, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1400x1050 @ 60 Hz, ? kHz +hsync +vsync*/ + { NULL, 60, 1400, 1050, 9259, 128, 40, 12, 0, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1024x768 @ 85 Hz, 70.24 kHz hsync */ + { NULL, 85, 1024, 768, 10111, 192, 32, 34, 14, 160, 6, 0, + FB_VMODE_NONINTERLACED }, + + /* 1152x864 @ 78 Hz, 70.8 kHz hsync */ + { NULL, 78, 1152, 864, 9090, 228, 88, 32, 0, 84, 12, 0, + FB_VMODE_NONINTERLACED }, + + /* 1280x1024 @ 70 Hz, 74.59 kHz hsync */ + { NULL, 70, 1280, 1024, 7905, 224, 32, 28, 8, 160, 8, 0, + FB_VMODE_NONINTERLACED }, + + /* 1600x1200 @ 60Hz, 75.00 kHz hsync */ + { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1152x864 @ 84 Hz, 76.0 kHz hsync */ + { NULL, 84, 1152, 864, 7407, 184, 312, 32, 0, 128, 12, 0, + FB_VMODE_NONINTERLACED }, + + /* 1280x1024 @ 74 Hz, 78.85 kHz hsync */ + { NULL, 74, 1280, 1024, 7407, 256, 32, 34, 3, 144, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 1024x768 @ 100Hz, 80.21 kHz hsync */ + { NULL, 100, 1024, 768, 8658, 192, 32, 21, 3, 192, 10, 0, + FB_VMODE_NONINTERLACED }, + + /* 1280x1024 @ 76 Hz, 81.13 kHz hsync */ + { NULL, 76, 1280, 1024, 7407, 248, 32, 34, 3, 104, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 1600x1200 @ 70 Hz, 87.50 kHz hsync */ + { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 1152x864 @ 100 Hz, 89.62 kHz hsync */ + { NULL, 100, 1152, 864, 7264, 224, 32, 17, 2, 128, 19, 0, + FB_VMODE_NONINTERLACED }, + + /* 1280x1024 @ 85 Hz, 91.15 kHz hsync */ + { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1600x1200 @ 75 Hz, 93.75 kHz hsync */ + { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1680x1050 @ 60 Hz, 65.191 kHz hsync */ + { NULL, 60, 1680, 1050, 6848, 280, 104, 30, 3, 176, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1600x1200 @ 85 Hz, 105.77 kHz hsync */ + { NULL, 85, 1600, 1200, 4545, 272, 16, 37, 4, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1280x1024 @ 100 Hz, 107.16 kHz hsync */ + { NULL, 100, 1280, 1024, 5502, 256, 32, 26, 7, 128, 15, 0, + FB_VMODE_NONINTERLACED }, + + /* 1800x1440 @ 64Hz, 96.15 kHz hsync */ + { NULL, 64, 1800, 1440, 4347, 304, 96, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1800x1440 @ 70Hz, 104.52 kHz hsync */ + { NULL, 70, 1800, 1440, 4000, 304, 96, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 512x384 @ 78 Hz, 31.50 kHz hsync */ + { NULL, 78, 512, 384, 49603, 48, 16, 16, 1, 64, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 512x384 @ 85 Hz, 34.38 kHz hsync */ + { NULL, 85, 512, 384, 45454, 48, 16, 16, 1, 64, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 320x200 @ 70 Hz, 31.5 kHz hsync, 8:5 aspect ratio */ + { NULL, 70, 320, 200, 79440, 16, 16, 20, 4, 48, 1, 0, + FB_VMODE_DOUBLE }, + + /* 320x240 @ 60 Hz, 31.5 kHz hsync, 4:3 aspect ratio */ + { NULL, 60, 320, 240, 79440, 16, 16, 16, 5, 48, 1, 0, + FB_VMODE_DOUBLE }, + + /* 320x240 @ 72 Hz, 36.5 kHz hsync */ + { NULL, 72, 320, 240, 63492, 16, 16, 16, 4, 48, 2, 0, + FB_VMODE_DOUBLE }, + + /* 400x300 @ 56 Hz, 35.2 kHz hsync, 4:3 aspect ratio */ + { NULL, 56, 400, 300, 55555, 64, 16, 10, 1, 32, 1, 0, + FB_VMODE_DOUBLE }, + + /* 400x300 @ 60 Hz, 37.8 kHz hsync */ + { NULL, 60, 400, 300, 50000, 48, 16, 11, 1, 64, 2, 0, + FB_VMODE_DOUBLE }, + + /* 400x300 @ 72 Hz, 48.0 kHz hsync */ + { NULL, 72, 400, 300, 40000, 32, 24, 11, 19, 64, 3, 0, + FB_VMODE_DOUBLE }, + + /* 480x300 @ 56 Hz, 35.2 kHz hsync, 8:5 aspect ratio */ + { NULL, 56, 480, 300, 46176, 80, 16, 10, 1, 40, 1, 0, + FB_VMODE_DOUBLE }, + + /* 480x300 @ 60 Hz, 37.8 kHz hsync */ + { NULL, 60, 480, 300, 41858, 56, 16, 11, 1, 80, 2, 0, + FB_VMODE_DOUBLE }, + + /* 480x300 @ 63 Hz, 39.6 kHz hsync */ + { NULL, 63, 480, 300, 40000, 56, 16, 11, 1, 80, 2, 0, + FB_VMODE_DOUBLE }, + + /* 480x300 @ 72 Hz, 48.0 kHz hsync */ + { NULL, 72, 480, 300, 33386, 40, 24, 11, 19, 80, 3, 0, + FB_VMODE_DOUBLE }, + + /* 1920x1200 @ 60 Hz, 74.5 Khz hsync */ + { NULL, 60, 1920, 1200, 5177, 128, 336, 1, 38, 208, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1152x768, 60 Hz, PowerBook G4 Titanium I and II */ + { NULL, 60, 1152, 768, 14047, 158, 26, 29, 3, 136, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 1366x768, 60 Hz, 47.403 kHz hsync, WXGA 16:9 aspect ratio */ + { NULL, 60, 1366, 768, 13806, 120, 10, 14, 3, 32, 5, 0, + FB_VMODE_NONINTERLACED }, + + /* 1280x800, 60 Hz, 47.403 kHz hsync, WXGA 16:10 aspect ratio */ + { NULL, 60, 1280, 800, 12048, 200, 64, 24, 1, 136, 3, 0, + FB_VMODE_NONINTERLACED }, + + /* 720x576i @ 50 Hz, 15.625 kHz hsync (PAL RGB) */ + { NULL, 50, 720, 576, 74074, 64, 16, 39, 5, 64, 5, 0, + FB_VMODE_INTERLACED }, + + /* 800x520i @ 50 Hz, 15.625 kHz hsync (PAL RGB) */ + { NULL, 50, 800, 520, 58823, 144, 64, 72, 28, 80, 5, 0, + FB_VMODE_INTERLACED }, + + /* 864x480 @ 60 Hz, 35.15 kHz hsync */ + { NULL, 60, 864, 480, 27777, 1, 1, 1, 1, 0, 0, + 0, FB_VMODE_NONINTERLACED }, +}; + +#ifdef CONFIG_FB_MODE_HELPERS +const struct fb_videomode cea_modes[64] = { + /* #1: 640x480p@59.94/60Hz */ + [1] = { + NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #3: 720x480p@59.94/60Hz */ + [3] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #5: 1920x1080i@59.94/60Hz */ + [5] = { + NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, 0, + }, + /* #7: 720(1440)x480iH@59.94/60Hz */ + [7] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_INTERLACED, 0, + }, + /* #9: 720(1440)x240pH@59.94/60Hz */ + [9] = { + NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #18: 720x576pH@50Hz */ + [18] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #19: 1280x720p@50Hz */ + [19] = { + NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0, + }, + /* #20: 1920x1080i@50Hz */ + [20] = { + NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, 0, + }, + /* #32: 1920x1080p@23.98/24Hz */ + [32] = { + NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0, + }, + /* #35: (2880)x480p4x@59.94/60Hz */ + [35] = { + NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0, + FB_VMODE_NONINTERLACED, 0, + }, +}; + +const struct fb_videomode vesa_modes[] = { + /* 0 640x350-85 VESA */ + { NULL, 85, 640, 350, 31746, 96, 32, 60, 32, 64, 3, + FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA}, + /* 1 640x400-85 VESA */ + { NULL, 85, 640, 400, 31746, 96, 32, 41, 01, 64, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 2 720x400-85 VESA */ + { NULL, 85, 721, 400, 28169, 108, 36, 42, 01, 72, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 3 640x480-60 VESA */ + { NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 4 640x480-72 VESA */ + { NULL, 72, 640, 480, 31746, 128, 24, 29, 9, 40, 2, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 5 640x480-75 VESA */ + { NULL, 75, 640, 480, 31746, 120, 16, 16, 01, 64, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 6 640x480-85 VESA */ + { NULL, 85, 640, 480, 27777, 80, 56, 25, 01, 56, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 7 800x600-56 VESA */ + { NULL, 56, 800, 600, 27777, 128, 24, 22, 01, 72, 2, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 8 800x600-60 VESA */ + { NULL, 60, 800, 600, 25000, 88, 40, 23, 01, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 9 800x600-72 VESA */ + { NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 10 800x600-75 VESA */ + { NULL, 75, 800, 600, 20202, 160, 16, 21, 01, 80, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 11 800x600-85 VESA */ + { NULL, 85, 800, 600, 17761, 152, 32, 27, 01, 64, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 12 1024x768i-43 VESA */ + { NULL, 43, 1024, 768, 22271, 56, 8, 41, 0, 176, 8, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, FB_MODE_IS_VESA }, + /* 13 1024x768-60 VESA */ + { NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 14 1024x768-70 VESA */ + { NULL, 70, 1024, 768, 13333, 144, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 15 1024x768-75 VESA */ + { NULL, 75, 1024, 768, 12690, 176, 16, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 16 1024x768-85 VESA */ + { NULL, 85, 1024, 768, 10582, 208, 48, 36, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 17 1152x864-75 VESA */ + { NULL, 75, 1152, 864, 9259, 256, 64, 32, 1, 128, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 18 1280x960-60 VESA */ + { NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 19 1280x960-85 VESA */ + { NULL, 85, 1280, 960, 6734, 224, 64, 47, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 20 1280x1024-60 VESA */ + { NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 21 1280x1024-75 VESA */ + { NULL, 75, 1280, 1024, 7407, 248, 16, 38, 1, 144, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 22 1280x1024-85 VESA */ + { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 23 1600x1200-60 VESA */ + { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 24 1600x1200-65 VESA */ + { NULL, 65, 1600, 1200, 5698, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 25 1600x1200-70 VESA */ + { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 26 1600x1200-75 VESA */ + { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 27 1600x1200-85 VESA */ + { NULL, 85, 1600, 1200, 4357, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 28 1792x1344-60 VESA */ + { NULL, 60, 1792, 1344, 4882, 328, 128, 46, 1, 200, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 29 1792x1344-75 VESA */ + { NULL, 75, 1792, 1344, 3831, 352, 96, 69, 1, 216, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 30 1856x1392-60 VESA */ + { NULL, 60, 1856, 1392, 4580, 352, 96, 43, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 31 1856x1392-75 VESA */ + { NULL, 75, 1856, 1392, 3472, 352, 128, 104, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 32 1920x1440-60 VESA */ + { NULL, 60, 1920, 1440, 4273, 344, 128, 56, 1, 200, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 33 1920x1440-75 VESA */ + { NULL, 75, 1920, 1440, 3367, 352, 144, 56, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, +}; +EXPORT_SYMBOL(vesa_modes); +#endif /* CONFIG_FB_MODE_HELPERS */ + +/** + * fb_try_mode - test a video mode + * @var: frame buffer user defined part of display + * @info: frame buffer info structure + * @mode: frame buffer video mode structure + * @bpp: color depth in bits per pixel + * + * Tries a video mode to test it's validity for device @info. + * + * Returns 1 on success. + * + */ + +static int fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info, + const struct fb_videomode *mode, unsigned int bpp) +{ + int err = 0; + + DPRINTK("Trying mode %s %dx%d-%d@%d\n", + mode->name ? mode->name : "noname", + mode->xres, mode->yres, bpp, mode->refresh); + var->xres = mode->xres; + var->yres = mode->yres; + var->xres_virtual = mode->xres; + var->yres_virtual = mode->yres; + var->xoffset = 0; + var->yoffset = 0; + var->bits_per_pixel = bpp; + var->activate |= FB_ACTIVATE_TEST; + var->pixclock = mode->pixclock; + var->left_margin = mode->left_margin; + var->right_margin = mode->right_margin; + var->upper_margin = mode->upper_margin; + var->lower_margin = mode->lower_margin; + var->hsync_len = mode->hsync_len; + var->vsync_len = mode->vsync_len; + var->sync = mode->sync; + var->vmode = mode->vmode; + if (info->fbops->fb_check_var) + err = info->fbops->fb_check_var(var, info); + var->activate &= ~FB_ACTIVATE_TEST; + return err; +} + +/** + * fb_find_mode - finds a valid video mode + * @var: frame buffer user defined part of display + * @info: frame buffer info structure + * @mode_option: string video mode to find + * @db: video mode database + * @dbsize: size of @db + * @default_mode: default video mode to fall back to + * @default_bpp: default color depth in bits per pixel + * + * Finds a suitable video mode, starting with the specified mode + * in @mode_option with fallback to @default_mode. If + * @default_mode fails, all modes in the video mode database will + * be tried. + * + * Valid mode specifiers for @mode_option: + * + * <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m] or + * <name>[-<bpp>][@<refresh>] + * + * with <xres>, <yres>, <bpp> and <refresh> decimal numbers and + * <name> a string. + * + * If 'M' is present after yres (and before refresh/bpp if present), + * the function will compute the timings using VESA(tm) Coordinated + * Video Timings (CVT). If 'R' is present after 'M', will compute with + * reduced blanking (for flatpanels). If 'i' is present, compute + * interlaced mode. If 'm' is present, add margins equal to 1.8% + * of xres rounded down to 8 pixels, and 1.8% of yres. The char + * 'i' and 'm' must be after 'M' and 'R'. Example: + * + * 1024x768MR-8@60m - Reduced blank with margins at 60Hz. + * + * NOTE: The passed struct @var is _not_ cleared! This allows you + * to supply values for e.g. the grayscale and accel_flags fields. + * + * Returns zero for failure, 1 if using specified @mode_option, + * 2 if using specified @mode_option with an ignored refresh rate, + * 3 if default mode is used, 4 if fall back to any valid mode. + * + */ + +int fb_find_mode(struct fb_var_screeninfo *var, + struct fb_info *info, const char *mode_option, + const struct fb_videomode *db, unsigned int dbsize, + const struct fb_videomode *default_mode, + unsigned int default_bpp) +{ + int i; + + /* Set up defaults */ + if (!db) { + db = modedb; + dbsize = ARRAY_SIZE(modedb); + } + + if (!default_mode) + default_mode = &db[0]; + + if (!default_bpp) + default_bpp = 8; + + /* Did the user specify a video mode? */ + if (!mode_option) + mode_option = fb_mode_option; + if (mode_option) { + const char *name = mode_option; + unsigned int namelen = strlen(name); + int res_specified = 0, bpp_specified = 0, refresh_specified = 0; + unsigned int xres = 0, yres = 0, bpp = default_bpp, refresh = 0; + int yres_specified = 0, cvt = 0, rb = 0, interlace = 0; + int margins = 0; + u32 best, diff, tdiff; + + for (i = namelen-1; i >= 0; i--) { + switch (name[i]) { + case '@': + namelen = i; + if (!refresh_specified && !bpp_specified && + !yres_specified) { + refresh = simple_strtol(&name[i+1], NULL, + 10); + refresh_specified = 1; + if (cvt || rb) + cvt = 0; + } else + goto done; + break; + case '-': + namelen = i; + if (!bpp_specified && !yres_specified) { + bpp = simple_strtol(&name[i+1], NULL, + 10); + bpp_specified = 1; + if (cvt || rb) + cvt = 0; + } else + goto done; + break; + case 'x': + if (!yres_specified) { + yres = simple_strtol(&name[i+1], NULL, + 10); + yres_specified = 1; + } else + goto done; + break; + case '0' ... '9': + break; + case 'M': + if (!yres_specified) + cvt = 1; + break; + case 'R': + if (!cvt) + rb = 1; + break; + case 'm': + if (!cvt) + margins = 1; + break; + case 'i': + if (!cvt) + interlace = 1; + break; + default: + goto done; + } + } + if (i < 0 && yres_specified) { + xres = simple_strtol(name, NULL, 10); + res_specified = 1; + } +done: + if (cvt) { + struct fb_videomode cvt_mode; + int ret; + + DPRINTK("CVT mode %dx%d@%dHz%s%s%s\n", xres, yres, + (refresh) ? refresh : 60, + (rb) ? " reduced blanking" : "", + (margins) ? " with margins" : "", + (interlace) ? " interlaced" : ""); + + memset(&cvt_mode, 0, sizeof(cvt_mode)); + cvt_mode.xres = xres; + cvt_mode.yres = yres; + cvt_mode.refresh = (refresh) ? refresh : 60; + + if (interlace) + cvt_mode.vmode |= FB_VMODE_INTERLACED; + else + cvt_mode.vmode &= ~FB_VMODE_INTERLACED; + + ret = fb_find_mode_cvt(&cvt_mode, margins, rb); + + if (!ret && !fb_try_mode(var, info, &cvt_mode, bpp)) { + DPRINTK("modedb CVT: CVT mode ok\n"); + return 1; + } + + DPRINTK("CVT mode invalid, getting mode from database\n"); + } + + DPRINTK("Trying specified video mode%s %ix%i\n", + refresh_specified ? "" : " (ignoring refresh rate)", + xres, yres); + + if (!refresh_specified) { + /* + * If the caller has provided a custom mode database and + * a valid monspecs structure, we look for the mode with + * the highest refresh rate. Otherwise we play it safe + * it and try to find a mode with a refresh rate closest + * to the standard 60 Hz. + */ + if (db != modedb && + info->monspecs.vfmin && info->monspecs.vfmax && + info->monspecs.hfmin && info->monspecs.hfmax && + info->monspecs.dclkmax) { + refresh = 1000; + } else { + refresh = 60; + } + } + + diff = -1; + best = -1; + for (i = 0; i < dbsize; i++) { + if ((name_matches(db[i], name, namelen) || + (res_specified && res_matches(db[i], xres, yres))) && + !fb_try_mode(var, info, &db[i], bpp)) { + if (refresh_specified && db[i].refresh == refresh) + return 1; + + if (abs(db[i].refresh - refresh) < diff) { + diff = abs(db[i].refresh - refresh); + best = i; + } + } + } + if (best != -1) { + fb_try_mode(var, info, &db[best], bpp); + return (refresh_specified) ? 2 : 1; + } + + diff = 2 * (xres + yres); + best = -1; + DPRINTK("Trying best-fit modes\n"); + for (i = 0; i < dbsize; i++) { + DPRINTK("Trying %ix%i\n", db[i].xres, db[i].yres); + if (!fb_try_mode(var, info, &db[i], bpp)) { + tdiff = abs(db[i].xres - xres) + + abs(db[i].yres - yres); + + /* + * Penalize modes with resolutions smaller + * than requested. + */ + if (xres > db[i].xres || yres > db[i].yres) + tdiff += xres + yres; + + if (diff > tdiff) { + diff = tdiff; + best = i; + } + } + } + if (best != -1) { + fb_try_mode(var, info, &db[best], bpp); + return 5; + } + } + + DPRINTK("Trying default video mode\n"); + if (!fb_try_mode(var, info, default_mode, default_bpp)) + return 3; + + DPRINTK("Trying all modes\n"); + for (i = 0; i < dbsize; i++) + if (!fb_try_mode(var, info, &db[i], default_bpp)) + return 4; + + DPRINTK("No valid mode found\n"); + return 0; +} + +/** + * fb_var_to_videomode - convert fb_var_screeninfo to fb_videomode + * @mode: pointer to struct fb_videomode + * @var: pointer to struct fb_var_screeninfo + */ +void fb_var_to_videomode(struct fb_videomode *mode, + const struct fb_var_screeninfo *var) +{ + u32 pixclock, hfreq, htotal, vtotal; + + mode->name = NULL; + mode->xres = var->xres; + mode->yres = var->yres; + mode->pixclock = var->pixclock; + mode->hsync_len = var->hsync_len; + mode->vsync_len = var->vsync_len; + mode->left_margin = var->left_margin; + mode->right_margin = var->right_margin; + mode->upper_margin = var->upper_margin; + mode->lower_margin = var->lower_margin; + mode->sync = var->sync; + mode->vmode = var->vmode & FB_VMODE_MASK; + mode->flag = FB_MODE_IS_FROM_VAR; + mode->refresh = 0; + + if (!var->pixclock) + return; + + pixclock = PICOS2KHZ(var->pixclock) * 1000; + + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + + if (var->vmode & FB_VMODE_INTERLACED) + vtotal /= 2; + if (var->vmode & FB_VMODE_DOUBLE) + vtotal *= 2; + + hfreq = pixclock/htotal; + mode->refresh = hfreq/vtotal; +} + +/** + * fb_videomode_to_var - convert fb_videomode to fb_var_screeninfo + * @var: pointer to struct fb_var_screeninfo + * @mode: pointer to struct fb_videomode + */ +void fb_videomode_to_var(struct fb_var_screeninfo *var, + const struct fb_videomode *mode) +{ + var->xres = mode->xres; + var->yres = mode->yres; + var->xres_virtual = mode->xres; + var->yres_virtual = mode->yres; + var->xoffset = 0; + var->yoffset = 0; + var->pixclock = mode->pixclock; + var->left_margin = mode->left_margin; + var->right_margin = mode->right_margin; + var->upper_margin = mode->upper_margin; + var->lower_margin = mode->lower_margin; + var->hsync_len = mode->hsync_len; + var->vsync_len = mode->vsync_len; + var->sync = mode->sync; + var->vmode = mode->vmode & FB_VMODE_MASK; +} + +/** + * fb_mode_is_equal - compare 2 videomodes + * @mode1: first videomode + * @mode2: second videomode + * + * RETURNS: + * 1 if equal, 0 if not + */ +int fb_mode_is_equal(const struct fb_videomode *mode1, + const struct fb_videomode *mode2) +{ + return (mode1->xres == mode2->xres && + mode1->yres == mode2->yres && + mode1->pixclock == mode2->pixclock && + mode1->hsync_len == mode2->hsync_len && + mode1->vsync_len == mode2->vsync_len && + mode1->left_margin == mode2->left_margin && + mode1->right_margin == mode2->right_margin && + mode1->upper_margin == mode2->upper_margin && + mode1->lower_margin == mode2->lower_margin && + mode1->sync == mode2->sync && + mode1->vmode == mode2->vmode); +} + +/** + * fb_find_best_mode - find best matching videomode + * @var: pointer to struct fb_var_screeninfo + * @head: pointer to struct list_head of modelist + * + * RETURNS: + * struct fb_videomode, NULL if none found + * + * IMPORTANT: + * This function assumes that all modelist entries in + * info->modelist are valid. + * + * NOTES: + * Finds best matching videomode which has an equal or greater dimension than + * var->xres and var->yres. If more than 1 videomode is found, will return + * the videomode with the highest refresh rate + */ +const struct fb_videomode *fb_find_best_mode(const struct fb_var_screeninfo *var, + struct list_head *head) +{ + struct list_head *pos; + struct fb_modelist *modelist; + struct fb_videomode *mode, *best = NULL; + u32 diff = -1; + + list_for_each(pos, head) { + u32 d; + + modelist = list_entry(pos, struct fb_modelist, list); + mode = &modelist->mode; + + if (mode->xres >= var->xres && mode->yres >= var->yres) { + d = (mode->xres - var->xres) + + (mode->yres - var->yres); + if (diff > d) { + diff = d; + best = mode; + } else if (diff == d && best && + mode->refresh > best->refresh) + best = mode; + } + } + return best; +} + +/** + * fb_find_nearest_mode - find closest videomode + * + * @mode: pointer to struct fb_videomode + * @head: pointer to modelist + * + * Finds best matching videomode, smaller or greater in dimension. + * If more than 1 videomode is found, will return the videomode with + * the closest refresh rate. + */ +const struct fb_videomode *fb_find_nearest_mode(const struct fb_videomode *mode, + struct list_head *head) +{ + struct list_head *pos; + struct fb_modelist *modelist; + struct fb_videomode *cmode, *best = NULL; + u32 diff = -1, diff_refresh = -1; + + list_for_each(pos, head) { + u32 d; + + modelist = list_entry(pos, struct fb_modelist, list); + cmode = &modelist->mode; + + d = abs(cmode->xres - mode->xres) + + abs(cmode->yres - mode->yres); + if (diff > d) { + diff = d; + diff_refresh = abs(cmode->refresh - mode->refresh); + best = cmode; + } else if (diff == d) { + d = abs(cmode->refresh - mode->refresh); + if (diff_refresh > d) { + diff_refresh = d; + best = cmode; + } + } + } + + return best; +} + +/** + * fb_match_mode - find a videomode which exactly matches the timings in var + * @var: pointer to struct fb_var_screeninfo + * @head: pointer to struct list_head of modelist + * + * RETURNS: + * struct fb_videomode, NULL if none found + */ +const struct fb_videomode *fb_match_mode(const struct fb_var_screeninfo *var, + struct list_head *head) +{ + struct list_head *pos; + struct fb_modelist *modelist; + struct fb_videomode *m, mode; + + fb_var_to_videomode(&mode, var); + list_for_each(pos, head) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + if (fb_mode_is_equal(m, &mode)) + return m; + } + return NULL; +} + +/** + * fb_add_videomode - adds videomode entry to modelist + * @mode: videomode to add + * @head: struct list_head of modelist + * + * NOTES: + * Will only add unmatched mode entries + */ +int fb_add_videomode(const struct fb_videomode *mode, struct list_head *head) +{ + struct list_head *pos; + struct fb_modelist *modelist; + struct fb_videomode *m; + int found = 0; + + list_for_each(pos, head) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + if (fb_mode_is_equal(m, mode)) { + found = 1; + break; + } + } + if (!found) { + modelist = kmalloc(sizeof(struct fb_modelist), + GFP_KERNEL); + + if (!modelist) + return -ENOMEM; + modelist->mode = *mode; + list_add(&modelist->list, head); + } + return 0; +} + +/** + * fb_delete_videomode - removed videomode entry from modelist + * @mode: videomode to remove + * @head: struct list_head of modelist + * + * NOTES: + * Will remove all matching mode entries + */ +void fb_delete_videomode(const struct fb_videomode *mode, + struct list_head *head) +{ + struct list_head *pos, *n; + struct fb_modelist *modelist; + struct fb_videomode *m; + + list_for_each_safe(pos, n, head) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + if (fb_mode_is_equal(m, mode)) { + list_del(pos); + kfree(pos); + } + } +} + +/** + * fb_destroy_modelist - destroy modelist + * @head: struct list_head of modelist + */ +void fb_destroy_modelist(struct list_head *head) +{ + struct list_head *pos, *n; + + list_for_each_safe(pos, n, head) { + list_del(pos); + kfree(pos); + } +} +EXPORT_SYMBOL_GPL(fb_destroy_modelist); + +/** + * fb_videomode_to_modelist - convert mode array to mode list + * @modedb: array of struct fb_videomode + * @num: number of entries in array + * @head: struct list_head of modelist + */ +void fb_videomode_to_modelist(const struct fb_videomode *modedb, int num, + struct list_head *head) +{ + int i; + + INIT_LIST_HEAD(head); + + for (i = 0; i < num; i++) { + if (fb_add_videomode(&modedb[i], head)) + return; + } +} + +const struct fb_videomode *fb_find_best_display(const struct fb_monspecs *specs, + struct list_head *head) +{ + struct list_head *pos; + struct fb_modelist *modelist; + const struct fb_videomode *m, *m1 = NULL, *md = NULL, *best = NULL; + int first = 0; + + if (!head->prev || !head->next || list_empty(head)) + goto finished; + + /* get the first detailed mode and the very first mode */ + list_for_each(pos, head) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + + if (!first) { + m1 = m; + first = 1; + } + + if (m->flag & FB_MODE_IS_FIRST) { + md = m; + break; + } + } + + /* first detailed timing is preferred */ + if (specs->misc & FB_MISC_1ST_DETAIL) { + best = md; + goto finished; + } + + /* find best mode based on display width and height */ + if (specs->max_x && specs->max_y) { + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(struct fb_var_screeninfo)); + var.xres = (specs->max_x * 7200)/254; + var.yres = (specs->max_y * 7200)/254; + m = fb_find_best_mode(&var, head); + if (m) { + best = m; + goto finished; + } + } + + /* use first detailed mode */ + if (md) { + best = md; + goto finished; + } + + /* last resort, use the very first mode */ + best = m1; +finished: + return best; +} +EXPORT_SYMBOL(fb_find_best_display); + +EXPORT_SYMBOL(fb_videomode_to_var); +EXPORT_SYMBOL(fb_var_to_videomode); +EXPORT_SYMBOL(fb_mode_is_equal); +EXPORT_SYMBOL(fb_add_videomode); +EXPORT_SYMBOL(fb_match_mode); +EXPORT_SYMBOL(fb_find_best_mode); +EXPORT_SYMBOL(fb_find_nearest_mode); +EXPORT_SYMBOL(fb_videomode_to_modelist); +EXPORT_SYMBOL(fb_find_mode); +EXPORT_SYMBOL(fb_find_mode_cvt); diff --git a/drivers/video/fbdev/msm/Makefile b/drivers/video/fbdev/msm/Makefile new file mode 100644 index 000000000000..802d6ae523fb --- /dev/null +++ b/drivers/video/fbdev/msm/Makefile @@ -0,0 +1,19 @@ + +# core framebuffer +# +obj-y := msm_fb.o + +# MDP DMA/PPP engine +# +obj-y += mdp.o mdp_scale_tables.o mdp_ppp.o + +# MDDI interface +# +obj-y += mddi.o + +# MDDI client/panel drivers +# +obj-y += mddi_client_dummy.o +obj-y += mddi_client_toshiba.o +obj-y += mddi_client_nt35399.o + diff --git a/drivers/video/fbdev/msm/mddi.c b/drivers/video/fbdev/msm/mddi.c new file mode 100644 index 000000000000..e0f8011a3c4b --- /dev/null +++ b/drivers/video/fbdev/msm/mddi.c @@ -0,0 +1,821 @@ +/* + * MSM MDDI Transport + * + * Copyright (C) 2007 Google Incorporated + * Copyright (C) 2007 QUALCOMM Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/gfp.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/platform_data/video-msm_fb.h> +#include "mddi_hw.h" + +#define FLAG_DISABLE_HIBERNATION 0x0001 +#define FLAG_HAVE_CAPS 0x0002 +#define FLAG_HAS_VSYNC_IRQ 0x0004 +#define FLAG_HAVE_STATUS 0x0008 + +#define CMD_GET_CLIENT_CAP 0x0601 +#define CMD_GET_CLIENT_STATUS 0x0602 + +union mddi_rev { + unsigned char raw[MDDI_REV_BUFFER_SIZE]; + struct mddi_rev_packet hdr; + struct mddi_client_status status; + struct mddi_client_caps caps; + struct mddi_register_access reg; +}; + +struct reg_read_info { + struct completion done; + uint32_t reg; + uint32_t status; + uint32_t result; +}; + +struct mddi_info { + uint16_t flags; + uint16_t version; + char __iomem *base; + int irq; + struct clk *clk; + struct msm_mddi_client_data client_data; + + /* buffer for rev encap packets */ + void *rev_data; + dma_addr_t rev_addr; + struct mddi_llentry *reg_write_data; + dma_addr_t reg_write_addr; + struct mddi_llentry *reg_read_data; + dma_addr_t reg_read_addr; + size_t rev_data_curr; + + spinlock_t int_lock; + uint32_t int_enable; + uint32_t got_int; + wait_queue_head_t int_wait; + + struct mutex reg_write_lock; + struct mutex reg_read_lock; + struct reg_read_info *reg_read; + + struct mddi_client_caps caps; + struct mddi_client_status status; + + void (*power_client)(struct msm_mddi_client_data *, int); + + /* client device published to bind us to the + * appropriate mddi_client driver + */ + char client_name[20]; + + struct platform_device client_pdev; +}; + +static void mddi_init_rev_encap(struct mddi_info *mddi); + +#define mddi_readl(r) readl(mddi->base + (MDDI_##r)) +#define mddi_writel(v, r) writel((v), mddi->base + (MDDI_##r)) + +void mddi_activate_link(struct msm_mddi_client_data *cdata) +{ + struct mddi_info *mddi = container_of(cdata, struct mddi_info, + client_data); + + mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); +} + +static void mddi_handle_link_list_done(struct mddi_info *mddi) +{ +} + +static void mddi_reset_rev_encap_ptr(struct mddi_info *mddi) +{ + printk(KERN_INFO "mddi: resetting rev ptr\n"); + mddi->rev_data_curr = 0; + mddi_writel(mddi->rev_addr, REV_PTR); + mddi_writel(mddi->rev_addr, REV_PTR); + mddi_writel(MDDI_CMD_FORCE_NEW_REV_PTR, CMD); +} + +static void mddi_handle_rev_data(struct mddi_info *mddi, union mddi_rev *rev) +{ + int i; + struct reg_read_info *ri; + + if ((rev->hdr.length <= MDDI_REV_BUFFER_SIZE - 2) && + (rev->hdr.length >= sizeof(struct mddi_rev_packet) - 2)) { + + switch (rev->hdr.type) { + case TYPE_CLIENT_CAPS: + memcpy(&mddi->caps, &rev->caps, + sizeof(struct mddi_client_caps)); + mddi->flags |= FLAG_HAVE_CAPS; + wake_up(&mddi->int_wait); + break; + case TYPE_CLIENT_STATUS: + memcpy(&mddi->status, &rev->status, + sizeof(struct mddi_client_status)); + mddi->flags |= FLAG_HAVE_STATUS; + wake_up(&mddi->int_wait); + break; + case TYPE_REGISTER_ACCESS: + ri = mddi->reg_read; + if (ri == 0) { + printk(KERN_INFO "rev: got reg %x = %x without " + " pending read\n", + rev->reg.register_address, + rev->reg.register_data_list); + break; + } + if (ri->reg != rev->reg.register_address) { + printk(KERN_INFO "rev: got reg %x = %x for " + "wrong register, expected " + "%x\n", + rev->reg.register_address, + rev->reg.register_data_list, ri->reg); + break; + } + mddi->reg_read = NULL; + ri->status = 0; + ri->result = rev->reg.register_data_list; + complete(&ri->done); + break; + default: + printk(KERN_INFO "rev: unknown reverse packet: " + "len=%04x type=%04x CURR_REV_PTR=%x\n", + rev->hdr.length, rev->hdr.type, + mddi_readl(CURR_REV_PTR)); + for (i = 0; i < rev->hdr.length + 2; i++) { + if ((i % 16) == 0) + printk(KERN_INFO "\n"); + printk(KERN_INFO " %02x", rev->raw[i]); + } + printk(KERN_INFO "\n"); + mddi_reset_rev_encap_ptr(mddi); + } + } else { + printk(KERN_INFO "bad rev length, %d, CURR_REV_PTR %x\n", + rev->hdr.length, mddi_readl(CURR_REV_PTR)); + mddi_reset_rev_encap_ptr(mddi); + } +} + +static void mddi_wait_interrupt(struct mddi_info *mddi, uint32_t intmask); + +static void mddi_handle_rev_data_avail(struct mddi_info *mddi) +{ + uint32_t rev_data_count; + uint32_t rev_crc_err_count; + struct reg_read_info *ri; + size_t prev_offset; + uint16_t length; + + union mddi_rev *crev = mddi->rev_data + mddi->rev_data_curr; + + /* clear the interrupt */ + mddi_writel(MDDI_INT_REV_DATA_AVAIL, INT); + rev_data_count = mddi_readl(REV_PKT_CNT); + rev_crc_err_count = mddi_readl(REV_CRC_ERR); + if (rev_data_count > 1) + printk(KERN_INFO "rev_data_count %d\n", rev_data_count); + + if (rev_crc_err_count) { + printk(KERN_INFO "rev_crc_err_count %d, INT %x\n", + rev_crc_err_count, mddi_readl(INT)); + ri = mddi->reg_read; + if (ri == 0) { + printk(KERN_INFO "rev: got crc error without pending " + "read\n"); + } else { + mddi->reg_read = NULL; + ri->status = -EIO; + ri->result = -1; + complete(&ri->done); + } + } + + if (rev_data_count == 0) + return; + + prev_offset = mddi->rev_data_curr; + + length = *((uint8_t *)mddi->rev_data + mddi->rev_data_curr); + mddi->rev_data_curr++; + if (mddi->rev_data_curr == MDDI_REV_BUFFER_SIZE) + mddi->rev_data_curr = 0; + length += *((uint8_t *)mddi->rev_data + mddi->rev_data_curr) << 8; + mddi->rev_data_curr += 1 + length; + if (mddi->rev_data_curr >= MDDI_REV_BUFFER_SIZE) + mddi->rev_data_curr = + mddi->rev_data_curr % MDDI_REV_BUFFER_SIZE; + + if (length > MDDI_REV_BUFFER_SIZE - 2) { + printk(KERN_INFO "mddi: rev data length greater than buffer" + "size\n"); + mddi_reset_rev_encap_ptr(mddi); + return; + } + + if (prev_offset + 2 + length >= MDDI_REV_BUFFER_SIZE) { + union mddi_rev tmprev; + size_t rem = MDDI_REV_BUFFER_SIZE - prev_offset; + memcpy(&tmprev.raw[0], mddi->rev_data + prev_offset, rem); + memcpy(&tmprev.raw[rem], mddi->rev_data, 2 + length - rem); + mddi_handle_rev_data(mddi, &tmprev); + } else { + mddi_handle_rev_data(mddi, crev); + } + + if (prev_offset < MDDI_REV_BUFFER_SIZE / 2 && + mddi->rev_data_curr >= MDDI_REV_BUFFER_SIZE / 2) { + mddi_writel(mddi->rev_addr, REV_PTR); + } +} + +static irqreturn_t mddi_isr(int irq, void *data) +{ + struct msm_mddi_client_data *cdata = data; + struct mddi_info *mddi = container_of(cdata, struct mddi_info, + client_data); + uint32_t active, status; + + spin_lock(&mddi->int_lock); + + active = mddi_readl(INT); + status = mddi_readl(STAT); + + mddi_writel(active, INT); + + /* ignore any interrupts we have disabled */ + active &= mddi->int_enable; + + mddi->got_int |= active; + wake_up(&mddi->int_wait); + + if (active & MDDI_INT_PRI_LINK_LIST_DONE) { + mddi->int_enable &= (~MDDI_INT_PRI_LINK_LIST_DONE); + mddi_handle_link_list_done(mddi); + } + if (active & MDDI_INT_REV_DATA_AVAIL) + mddi_handle_rev_data_avail(mddi); + + if (active & ~MDDI_INT_NEED_CLEAR) + mddi->int_enable &= ~(active & ~MDDI_INT_NEED_CLEAR); + + if (active & MDDI_INT_LINK_ACTIVE) { + mddi->int_enable &= (~MDDI_INT_LINK_ACTIVE); + mddi->int_enable |= MDDI_INT_IN_HIBERNATION; + } + + if (active & MDDI_INT_IN_HIBERNATION) { + mddi->int_enable &= (~MDDI_INT_IN_HIBERNATION); + mddi->int_enable |= MDDI_INT_LINK_ACTIVE; + } + + mddi_writel(mddi->int_enable, INTEN); + spin_unlock(&mddi->int_lock); + + return IRQ_HANDLED; +} + +static long mddi_wait_interrupt_timeout(struct mddi_info *mddi, + uint32_t intmask, int timeout) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&mddi->int_lock, irq_flags); + mddi->got_int &= ~intmask; + mddi->int_enable |= intmask; + mddi_writel(mddi->int_enable, INTEN); + spin_unlock_irqrestore(&mddi->int_lock, irq_flags); + return wait_event_timeout(mddi->int_wait, mddi->got_int & intmask, + timeout); +} + +static void mddi_wait_interrupt(struct mddi_info *mddi, uint32_t intmask) +{ + if (mddi_wait_interrupt_timeout(mddi, intmask, HZ/10) == 0) + printk(KERN_INFO "mddi_wait_interrupt %d, timeout " + "waiting for %x, INT = %x, STAT = %x gotint = %x\n", + current->pid, intmask, mddi_readl(INT), mddi_readl(STAT), + mddi->got_int); +} + +static void mddi_init_rev_encap(struct mddi_info *mddi) +{ + memset(mddi->rev_data, 0xee, MDDI_REV_BUFFER_SIZE); + mddi_writel(mddi->rev_addr, REV_PTR); + mddi_writel(MDDI_CMD_FORCE_NEW_REV_PTR, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); +} + +void mddi_set_auto_hibernate(struct msm_mddi_client_data *cdata, int on) +{ + struct mddi_info *mddi = container_of(cdata, struct mddi_info, + client_data); + mddi_writel(MDDI_CMD_POWERDOWN, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_IN_HIBERNATION); + mddi_writel(MDDI_CMD_HIBERNATE | !!on, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); +} + + +static uint16_t mddi_init_registers(struct mddi_info *mddi) +{ + mddi_writel(0x0001, VERSION); + mddi_writel(MDDI_HOST_BYTES_PER_SUBFRAME, BPS); + mddi_writel(0x0003, SPM); /* subframes per media */ + mddi_writel(0x0005, TA1_LEN); + mddi_writel(MDDI_HOST_TA2_LEN, TA2_LEN); + mddi_writel(0x0096, DRIVE_HI); + /* 0x32 normal, 0x50 for Toshiba display */ + mddi_writel(0x0050, DRIVE_LO); + mddi_writel(0x003C, DISP_WAKE); /* wakeup counter */ + mddi_writel(MDDI_HOST_REV_RATE_DIV, REV_RATE_DIV); + + mddi_writel(MDDI_REV_BUFFER_SIZE, REV_SIZE); + mddi_writel(MDDI_MAX_REV_PKT_SIZE, REV_ENCAP_SZ); + + /* disable periodic rev encap */ + mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + + if (mddi_readl(PAD_CTL) == 0) { + /* If we are turning on band gap, need to wait 5us before + * turning on the rest of the PAD */ + mddi_writel(0x08000, PAD_CTL); + udelay(5); + } + + /* Recommendation from PAD hw team */ + mddi_writel(0xa850f, PAD_CTL); + + + /* Need an even number for counts */ + mddi_writel(0x60006, DRIVER_START_CNT); + + mddi_set_auto_hibernate(&mddi->client_data, 0); + + mddi_writel(MDDI_CMD_DISP_IGNORE, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + + mddi_init_rev_encap(mddi); + return mddi_readl(CORE_VER) & 0xffff; +} + +static void mddi_suspend(struct msm_mddi_client_data *cdata) +{ + struct mddi_info *mddi = container_of(cdata, struct mddi_info, + client_data); + /* turn off the client */ + if (mddi->power_client) + mddi->power_client(&mddi->client_data, 0); + /* turn off the link */ + mddi_writel(MDDI_CMD_RESET, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + /* turn off the clock */ + clk_disable(mddi->clk); +} + +static void mddi_resume(struct msm_mddi_client_data *cdata) +{ + struct mddi_info *mddi = container_of(cdata, struct mddi_info, + client_data); + mddi_set_auto_hibernate(&mddi->client_data, 0); + /* turn on the client */ + if (mddi->power_client) + mddi->power_client(&mddi->client_data, 1); + /* turn on the clock */ + clk_enable(mddi->clk); + /* set up the local registers */ + mddi->rev_data_curr = 0; + mddi_init_registers(mddi); + mddi_writel(mddi->int_enable, INTEN); + mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); + mddi_writel(MDDI_CMD_SEND_RTD, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + mddi_set_auto_hibernate(&mddi->client_data, 1); +} + +static int mddi_get_client_caps(struct mddi_info *mddi) +{ + int i, j; + + /* clear any stale interrupts */ + mddi_writel(0xffffffff, INT); + + mddi->int_enable = MDDI_INT_LINK_ACTIVE | + MDDI_INT_IN_HIBERNATION | + MDDI_INT_PRI_LINK_LIST_DONE | + MDDI_INT_REV_DATA_AVAIL | + MDDI_INT_REV_OVERFLOW | + MDDI_INT_REV_OVERWRITE | + MDDI_INT_RTD_FAILURE; + mddi_writel(mddi->int_enable, INTEN); + + mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + + for (j = 0; j < 3; j++) { + /* the toshiba vga panel does not respond to get + * caps unless you SEND_RTD, but the first SEND_RTD + * will fail... + */ + for (i = 0; i < 4; i++) { + uint32_t stat; + + mddi_writel(MDDI_CMD_SEND_RTD, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + stat = mddi_readl(STAT); + printk(KERN_INFO "mddi cmd send rtd: int %x, stat %x, " + "rtd val %x\n", mddi_readl(INT), stat, + mddi_readl(RTD_VAL)); + if ((stat & MDDI_STAT_RTD_MEAS_FAIL) == 0) + break; + msleep(1); + } + + mddi_writel(CMD_GET_CLIENT_CAP, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + wait_event_timeout(mddi->int_wait, mddi->flags & FLAG_HAVE_CAPS, + HZ / 100); + + if (mddi->flags & FLAG_HAVE_CAPS) + break; + printk(KERN_INFO "mddi_init, timeout waiting for caps\n"); + } + return mddi->flags & FLAG_HAVE_CAPS; +} + +/* link must be active when this is called */ +int mddi_check_status(struct mddi_info *mddi) +{ + int ret = -1, retry = 3; + mutex_lock(&mddi->reg_read_lock); + mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 1, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + + do { + mddi->flags &= ~FLAG_HAVE_STATUS; + mddi_writel(CMD_GET_CLIENT_STATUS, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + wait_event_timeout(mddi->int_wait, + mddi->flags & FLAG_HAVE_STATUS, + HZ / 100); + + if (mddi->flags & FLAG_HAVE_STATUS) { + if (mddi->status.crc_error_count) + printk(KERN_INFO "mddi status: crc_error " + "count: %d\n", + mddi->status.crc_error_count); + else + ret = 0; + break; + } else + printk(KERN_INFO "mddi status: failed to get client " + "status\n"); + mddi_writel(MDDI_CMD_SEND_RTD, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + } while (--retry); + + mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 0, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + mutex_unlock(&mddi->reg_read_lock); + return ret; +} + + +void mddi_remote_write(struct msm_mddi_client_data *cdata, uint32_t val, + uint32_t reg) +{ + struct mddi_info *mddi = container_of(cdata, struct mddi_info, + client_data); + struct mddi_llentry *ll; + struct mddi_register_access *ra; + + mutex_lock(&mddi->reg_write_lock); + + ll = mddi->reg_write_data; + + ra = &(ll->u.r); + ra->length = 14 + 4; + ra->type = TYPE_REGISTER_ACCESS; + ra->client_id = 0; + ra->read_write_info = MDDI_WRITE | 1; + ra->crc16 = 0; + + ra->register_address = reg; + ra->register_data_list = val; + + ll->flags = 1; + ll->header_count = 14; + ll->data_count = 4; + ll->data = mddi->reg_write_addr + offsetof(struct mddi_llentry, + u.r.register_data_list); + ll->next = 0; + ll->reserved = 0; + + mddi_writel(mddi->reg_write_addr, PRI_PTR); + + mddi_wait_interrupt(mddi, MDDI_INT_PRI_LINK_LIST_DONE); + mutex_unlock(&mddi->reg_write_lock); +} + +uint32_t mddi_remote_read(struct msm_mddi_client_data *cdata, uint32_t reg) +{ + struct mddi_info *mddi = container_of(cdata, struct mddi_info, + client_data); + struct mddi_llentry *ll; + struct mddi_register_access *ra; + struct reg_read_info ri; + unsigned s; + int retry_count = 2; + unsigned long irq_flags; + + mutex_lock(&mddi->reg_read_lock); + + ll = mddi->reg_read_data; + + ra = &(ll->u.r); + ra->length = 14; + ra->type = TYPE_REGISTER_ACCESS; + ra->client_id = 0; + ra->read_write_info = MDDI_READ | 1; + ra->crc16 = 0; + + ra->register_address = reg; + + ll->flags = 0x11; + ll->header_count = 14; + ll->data_count = 0; + ll->data = 0; + ll->next = 0; + ll->reserved = 0; + + s = mddi_readl(STAT); + + ri.reg = reg; + ri.status = -1; + + do { + init_completion(&ri.done); + mddi->reg_read = &ri; + mddi_writel(mddi->reg_read_addr, PRI_PTR); + + mddi_wait_interrupt(mddi, MDDI_INT_PRI_LINK_LIST_DONE); + + /* Enable Periodic Reverse Encapsulation. */ + mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 1, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + if (wait_for_completion_timeout(&ri.done, HZ/10) == 0 && + !ri.done.done) { + printk(KERN_INFO "mddi_remote_read(%x) timeout " + "(%d %d %d)\n", + reg, ri.status, ri.result, ri.done.done); + spin_lock_irqsave(&mddi->int_lock, irq_flags); + mddi->reg_read = NULL; + spin_unlock_irqrestore(&mddi->int_lock, irq_flags); + ri.status = -1; + ri.result = -1; + } + if (ri.status == 0) + break; + + mddi_writel(MDDI_CMD_SEND_RTD, CMD); + mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + printk(KERN_INFO "mddi_remote_read: failed, sent " + "MDDI_CMD_SEND_RTD: int %x, stat %x, rtd val %x " + "curr_rev_ptr %x\n", mddi_readl(INT), mddi_readl(STAT), + mddi_readl(RTD_VAL), mddi_readl(CURR_REV_PTR)); + } while (retry_count-- > 0); + /* Disable Periodic Reverse Encapsulation. */ + mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 0, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + mddi->reg_read = NULL; + mutex_unlock(&mddi->reg_read_lock); + return ri.result; +} + +static struct mddi_info mddi_info[2]; + +static int mddi_clk_setup(struct platform_device *pdev, struct mddi_info *mddi, + unsigned long clk_rate) +{ + int ret; + + /* set up the clocks */ + mddi->clk = clk_get(&pdev->dev, "mddi_clk"); + if (IS_ERR(mddi->clk)) { + printk(KERN_INFO "mddi: failed to get clock\n"); + return PTR_ERR(mddi->clk); + } + ret = clk_enable(mddi->clk); + if (ret) + goto fail; + ret = clk_set_rate(mddi->clk, clk_rate); + if (ret) + goto fail; + return 0; + +fail: + clk_put(mddi->clk); + return ret; +} + +static int __init mddi_rev_data_setup(struct mddi_info *mddi) +{ + void *dma; + dma_addr_t dma_addr; + + /* set up dma buffer */ + dma = dma_alloc_coherent(NULL, 0x1000, &dma_addr, GFP_KERNEL); + if (dma == 0) + return -ENOMEM; + mddi->rev_data = dma; + mddi->rev_data_curr = 0; + mddi->rev_addr = dma_addr; + mddi->reg_write_data = dma + MDDI_REV_BUFFER_SIZE; + mddi->reg_write_addr = dma_addr + MDDI_REV_BUFFER_SIZE; + mddi->reg_read_data = mddi->reg_write_data + 1; + mddi->reg_read_addr = mddi->reg_write_addr + + sizeof(*mddi->reg_write_data); + return 0; +} + +static int mddi_probe(struct platform_device *pdev) +{ + struct msm_mddi_platform_data *pdata = pdev->dev.platform_data; + struct mddi_info *mddi = &mddi_info[pdev->id]; + struct resource *resource; + int ret, i; + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + printk(KERN_ERR "mddi: no associated mem resource!\n"); + return -ENOMEM; + } + mddi->base = ioremap(resource->start, resource_size(resource)); + if (!mddi->base) { + printk(KERN_ERR "mddi: failed to remap base!\n"); + ret = -EINVAL; + goto error_ioremap; + } + resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!resource) { + printk(KERN_ERR "mddi: no associated irq resource!\n"); + ret = -EINVAL; + goto error_get_irq_resource; + } + mddi->irq = resource->start; + printk(KERN_INFO "mddi: init() base=0x%p irq=%d\n", mddi->base, + mddi->irq); + mddi->power_client = pdata->power_client; + + mutex_init(&mddi->reg_write_lock); + mutex_init(&mddi->reg_read_lock); + spin_lock_init(&mddi->int_lock); + init_waitqueue_head(&mddi->int_wait); + + ret = mddi_clk_setup(pdev, mddi, pdata->clk_rate); + if (ret) { + printk(KERN_ERR "mddi: failed to setup clock!\n"); + goto error_clk_setup; + } + + ret = mddi_rev_data_setup(mddi); + if (ret) { + printk(KERN_ERR "mddi: failed to setup rev data!\n"); + goto error_rev_data; + } + + mddi->int_enable = 0; + mddi_writel(mddi->int_enable, INTEN); + ret = request_irq(mddi->irq, mddi_isr, 0, "mddi", + &mddi->client_data); + if (ret) { + printk(KERN_ERR "mddi: failed to request enable irq!\n"); + goto error_request_irq; + } + + /* turn on the mddi client bridge chip */ + if (mddi->power_client) + mddi->power_client(&mddi->client_data, 1); + + /* initialize the mddi registers */ + mddi_set_auto_hibernate(&mddi->client_data, 0); + mddi_writel(MDDI_CMD_RESET, CMD); + mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + mddi->version = mddi_init_registers(mddi); + if (mddi->version < 0x20) { + printk(KERN_ERR "mddi: unsupported version 0x%x\n", + mddi->version); + ret = -ENODEV; + goto error_mddi_version; + } + + /* read the capabilities off the client */ + if (!mddi_get_client_caps(mddi)) { + printk(KERN_INFO "mddi: no client found\n"); + /* power down the panel */ + mddi_writel(MDDI_CMD_POWERDOWN, CMD); + printk(KERN_INFO "mddi powerdown: stat %x\n", mddi_readl(STAT)); + msleep(100); + printk(KERN_INFO "mddi powerdown: stat %x\n", mddi_readl(STAT)); + return 0; + } + mddi_set_auto_hibernate(&mddi->client_data, 1); + + if (mddi->caps.Mfr_Name == 0 && mddi->caps.Product_Code == 0) + pdata->fixup(&mddi->caps.Mfr_Name, &mddi->caps.Product_Code); + + mddi->client_pdev.id = 0; + for (i = 0; i < pdata->num_clients; i++) { + if (pdata->client_platform_data[i].product_id == + (mddi->caps.Mfr_Name << 16 | mddi->caps.Product_Code)) { + mddi->client_data.private_client_data = + pdata->client_platform_data[i].client_data; + mddi->client_pdev.name = + pdata->client_platform_data[i].name; + mddi->client_pdev.id = + pdata->client_platform_data[i].id; + /* XXX: possibly set clock */ + break; + } + } + + if (i >= pdata->num_clients) + mddi->client_pdev.name = "mddi_c_dummy"; + printk(KERN_INFO "mddi: registering panel %s\n", + mddi->client_pdev.name); + + mddi->client_data.suspend = mddi_suspend; + mddi->client_data.resume = mddi_resume; + mddi->client_data.activate_link = mddi_activate_link; + mddi->client_data.remote_write = mddi_remote_write; + mddi->client_data.remote_read = mddi_remote_read; + mddi->client_data.auto_hibernate = mddi_set_auto_hibernate; + mddi->client_data.fb_resource = pdata->fb_resource; + if (pdev->id == 0) + mddi->client_data.interface_type = MSM_MDDI_PMDH_INTERFACE; + else if (pdev->id == 1) + mddi->client_data.interface_type = MSM_MDDI_EMDH_INTERFACE; + else { + printk(KERN_ERR "mddi: can not determine interface %d!\n", + pdev->id); + ret = -EINVAL; + goto error_mddi_interface; + } + + mddi->client_pdev.dev.platform_data = &mddi->client_data; + printk(KERN_INFO "mddi: publish: %s\n", mddi->client_name); + platform_device_register(&mddi->client_pdev); + return 0; + +error_mddi_interface: +error_mddi_version: + free_irq(mddi->irq, 0); +error_request_irq: + dma_free_coherent(NULL, 0x1000, mddi->rev_data, mddi->rev_addr); +error_rev_data: +error_clk_setup: +error_get_irq_resource: + iounmap(mddi->base); +error_ioremap: + + printk(KERN_INFO "mddi: mddi_init() failed (%d)\n", ret); + return ret; +} + + +static struct platform_driver mddi_driver = { + .probe = mddi_probe, + .driver = { .name = "msm_mddi" }, +}; + +static int __init _mddi_init(void) +{ + return platform_driver_register(&mddi_driver); +} + +module_init(_mddi_init); diff --git a/drivers/video/fbdev/msm/mddi_client_dummy.c b/drivers/video/fbdev/msm/mddi_client_dummy.c new file mode 100644 index 000000000000..f1b0dfcc9717 --- /dev/null +++ b/drivers/video/fbdev/msm/mddi_client_dummy.c @@ -0,0 +1,98 @@ +/* drivers/video/msm_fb/mddi_client_dummy.c + * + * Support for "dummy" mddi client devices which require no + * special initialization code. + * + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> + +#include <linux/platform_data/video-msm_fb.h> + +struct panel_info { + struct platform_device pdev; + struct msm_panel_data panel_data; +}; + +static int mddi_dummy_suspend(struct msm_panel_data *panel_data) +{ + return 0; +} + +static int mddi_dummy_resume(struct msm_panel_data *panel_data) +{ + return 0; +} + +static int mddi_dummy_blank(struct msm_panel_data *panel_data) +{ + return 0; +} + +static int mddi_dummy_unblank(struct msm_panel_data *panel_data) +{ + return 0; +} + +static int mddi_dummy_probe(struct platform_device *pdev) +{ + struct msm_mddi_client_data *client_data = pdev->dev.platform_data; + struct panel_info *panel = + kzalloc(sizeof(struct panel_info), GFP_KERNEL); + int ret; + if (!panel) + return -ENOMEM; + platform_set_drvdata(pdev, panel); + panel->panel_data.suspend = mddi_dummy_suspend; + panel->panel_data.resume = mddi_dummy_resume; + panel->panel_data.blank = mddi_dummy_blank; + panel->panel_data.unblank = mddi_dummy_unblank; + panel->panel_data.caps = MSMFB_CAP_PARTIAL_UPDATES; + panel->pdev.name = "msm_panel"; + panel->pdev.id = pdev->id; + platform_device_add_resources(&panel->pdev, + client_data->fb_resource, 1); + panel->panel_data.fb_data = client_data->private_client_data; + panel->pdev.dev.platform_data = &panel->panel_data; + ret = platform_device_register(&panel->pdev); + if (ret) { + kfree(panel); + return ret; + } + return 0; +} + +static int mddi_dummy_remove(struct platform_device *pdev) +{ + struct panel_info *panel = platform_get_drvdata(pdev); + kfree(panel); + return 0; +} + +static struct platform_driver mddi_client_dummy = { + .probe = mddi_dummy_probe, + .remove = mddi_dummy_remove, + .driver = { .name = "mddi_c_dummy" }, +}; + +static int __init mddi_client_dummy_init(void) +{ + platform_driver_register(&mddi_client_dummy); + return 0; +} + +module_init(mddi_client_dummy_init); + diff --git a/drivers/video/fbdev/msm/mddi_client_nt35399.c b/drivers/video/fbdev/msm/mddi_client_nt35399.c new file mode 100644 index 000000000000..f96df32e5509 --- /dev/null +++ b/drivers/video/fbdev/msm/mddi_client_nt35399.c @@ -0,0 +1,252 @@ +/* drivers/video/msm_fb/mddi_client_nt35399.c + * + * Support for Novatek NT35399 MDDI client of Sapphire + * + * Copyright (C) 2008 HTC Incorporated + * Author: Solomon Chiu (solomon_chiu@htc.com) + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/platform_data/video-msm_fb.h> + +static DECLARE_WAIT_QUEUE_HEAD(nt35399_vsync_wait); + +struct panel_info { + struct msm_mddi_client_data *client_data; + struct platform_device pdev; + struct msm_panel_data panel_data; + struct msmfb_callback *fb_callback; + struct work_struct panel_work; + struct workqueue_struct *fb_wq; + int nt35399_got_int; +}; + +static void +nt35399_request_vsync(struct msm_panel_data *panel_data, + struct msmfb_callback *callback) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + panel->fb_callback = callback; + if (panel->nt35399_got_int) { + panel->nt35399_got_int = 0; + client_data->activate_link(client_data); /* clears interrupt */ + } +} + +static void nt35399_wait_vsync(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + if (panel->nt35399_got_int) { + panel->nt35399_got_int = 0; + client_data->activate_link(client_data); /* clears interrupt */ + } + + if (wait_event_timeout(nt35399_vsync_wait, panel->nt35399_got_int, + HZ/2) == 0) + printk(KERN_ERR "timeout waiting for VSYNC\n"); + + panel->nt35399_got_int = 0; + /* interrupt clears when screen dma starts */ +} + +static int nt35399_suspend(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + int ret; + + ret = bridge_data->uninit(bridge_data, client_data); + if (ret) { + printk(KERN_INFO "mddi nt35399 client: non zero return from " + "uninit\n"); + return ret; + } + client_data->suspend(client_data); + return 0; +} + +static int nt35399_resume(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + int ret; + + client_data->resume(client_data); + ret = bridge_data->init(bridge_data, client_data); + if (ret) + return ret; + return 0; +} + +static int nt35399_blank(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + + return bridge_data->blank(bridge_data, client_data); +} + +static int nt35399_unblank(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + + return bridge_data->unblank(bridge_data, client_data); +} + +irqreturn_t nt35399_vsync_interrupt(int irq, void *data) +{ + struct panel_info *panel = data; + + panel->nt35399_got_int = 1; + + if (panel->fb_callback) { + panel->fb_callback->func(panel->fb_callback); + panel->fb_callback = NULL; + } + + wake_up(&nt35399_vsync_wait); + + return IRQ_HANDLED; +} + +static int setup_vsync(struct panel_info *panel, int init) +{ + int ret; + int gpio = 97; + unsigned int irq; + + if (!init) { + ret = 0; + goto uninit; + } + ret = gpio_request_one(gpio, GPIOF_IN, "vsync"); + if (ret) + goto err_request_gpio_failed; + + ret = irq = gpio_to_irq(gpio); + if (ret < 0) + goto err_get_irq_num_failed; + + ret = request_irq(irq, nt35399_vsync_interrupt, IRQF_TRIGGER_RISING, + "vsync", panel); + if (ret) + goto err_request_irq_failed; + + printk(KERN_INFO "vsync on gpio %d now %d\n", + gpio, gpio_get_value(gpio)); + return 0; + +uninit: + free_irq(gpio_to_irq(gpio), panel->client_data); +err_request_irq_failed: +err_get_irq_num_failed: + gpio_free(gpio); +err_request_gpio_failed: + return ret; +} + +static int mddi_nt35399_probe(struct platform_device *pdev) +{ + struct msm_mddi_client_data *client_data = pdev->dev.platform_data; + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + + int ret; + + struct panel_info *panel = devm_kzalloc(&pdev->dev, + sizeof(struct panel_info), + GFP_KERNEL); + + printk(KERN_DEBUG "%s: enter.\n", __func__); + + if (!panel) + return -ENOMEM; + platform_set_drvdata(pdev, panel); + + ret = setup_vsync(panel, 1); + if (ret) { + dev_err(&pdev->dev, "mddi_nt35399_setup_vsync failed\n"); + return ret; + } + + panel->client_data = client_data; + panel->panel_data.suspend = nt35399_suspend; + panel->panel_data.resume = nt35399_resume; + panel->panel_data.wait_vsync = nt35399_wait_vsync; + panel->panel_data.request_vsync = nt35399_request_vsync; + panel->panel_data.blank = nt35399_blank; + panel->panel_data.unblank = nt35399_unblank; + panel->panel_data.fb_data = &bridge_data->fb_data; + panel->panel_data.caps = 0; + + panel->pdev.name = "msm_panel"; + panel->pdev.id = pdev->id; + panel->pdev.resource = client_data->fb_resource; + panel->pdev.num_resources = 1; + panel->pdev.dev.platform_data = &panel->panel_data; + + if (bridge_data->init) + bridge_data->init(bridge_data, client_data); + + platform_device_register(&panel->pdev); + + return 0; +} + +static int mddi_nt35399_remove(struct platform_device *pdev) +{ + struct panel_info *panel = platform_get_drvdata(pdev); + + setup_vsync(panel, 0); + return 0; +} + +static struct platform_driver mddi_client_0bda_8a47 = { + .probe = mddi_nt35399_probe, + .remove = mddi_nt35399_remove, + .driver = { .name = "mddi_c_0bda_8a47" }, +}; + +static int __init mddi_client_nt35399_init(void) +{ + return platform_driver_register(&mddi_client_0bda_8a47); +} + +module_init(mddi_client_nt35399_init); + diff --git a/drivers/video/fbdev/msm/mddi_client_toshiba.c b/drivers/video/fbdev/msm/mddi_client_toshiba.c new file mode 100644 index 000000000000..061d7dfebbf3 --- /dev/null +++ b/drivers/video/fbdev/msm/mddi_client_toshiba.c @@ -0,0 +1,280 @@ +/* drivers/video/msm_fb/mddi_client_toshiba.c + * + * Support for Toshiba TC358720XBG mddi client devices which require no + * special initialization code. + * + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/platform_data/video-msm_fb.h> + + +#define LCD_CONTROL_BLOCK_BASE 0x110000 +#define CMN (LCD_CONTROL_BLOCK_BASE|0x10) +#define INTFLG (LCD_CONTROL_BLOCK_BASE|0x18) +#define HCYCLE (LCD_CONTROL_BLOCK_BASE|0x34) +#define HDE_START (LCD_CONTROL_BLOCK_BASE|0x3C) +#define VPOS (LCD_CONTROL_BLOCK_BASE|0xC0) +#define MPLFBUF (LCD_CONTROL_BLOCK_BASE|0x20) +#define WAKEUP (LCD_CONTROL_BLOCK_BASE|0x54) +#define WSYN_DLY (LCD_CONTROL_BLOCK_BASE|0x58) +#define REGENB (LCD_CONTROL_BLOCK_BASE|0x5C) + +#define BASE5 0x150000 +#define BASE6 0x160000 +#define BASE7 0x170000 + +#define GPIOIEV (BASE5 + 0x10) +#define GPIOIE (BASE5 + 0x14) +#define GPIORIS (BASE5 + 0x18) +#define GPIOMIS (BASE5 + 0x1C) +#define GPIOIC (BASE5 + 0x20) + +#define INTMASK (BASE6 + 0x0C) +#define INTMASK_VWAKEOUT (1U << 0) +#define INTMASK_VWAKEOUT_ACTIVE_LOW (1U << 8) +#define GPIOSEL (BASE7 + 0x00) +#define GPIOSEL_VWAKEINT (1U << 0) + +static DECLARE_WAIT_QUEUE_HEAD(toshiba_vsync_wait); + +struct panel_info { + struct msm_mddi_client_data *client_data; + struct platform_device pdev; + struct msm_panel_data panel_data; + struct msmfb_callback *toshiba_callback; + int toshiba_got_int; +}; + + +static void toshiba_request_vsync(struct msm_panel_data *panel_data, + struct msmfb_callback *callback) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + panel->toshiba_callback = callback; + if (panel->toshiba_got_int) { + panel->toshiba_got_int = 0; + client_data->activate_link(client_data); + } +} + +static void toshiba_clear_vsync(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + client_data->activate_link(client_data); +} + +static void toshiba_wait_vsync(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + if (panel->toshiba_got_int) { + panel->toshiba_got_int = 0; + client_data->activate_link(client_data); /* clears interrupt */ + } + if (wait_event_timeout(toshiba_vsync_wait, panel->toshiba_got_int, + HZ/2) == 0) + printk(KERN_ERR "timeout waiting for VSYNC\n"); + panel->toshiba_got_int = 0; + /* interrupt clears when screen dma starts */ +} + +static int toshiba_suspend(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + int ret; + + ret = bridge_data->uninit(bridge_data, client_data); + if (ret) { + printk(KERN_INFO "mddi toshiba client: non zero return from " + "uninit\n"); + return ret; + } + client_data->suspend(client_data); + return 0; +} + +static int toshiba_resume(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + int ret; + + client_data->resume(client_data); + ret = bridge_data->init(bridge_data, client_data); + if (ret) + return ret; + return 0; +} + +static int toshiba_blank(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + + return bridge_data->blank(bridge_data, client_data); +} + +static int toshiba_unblank(struct msm_panel_data *panel_data) +{ + struct panel_info *panel = container_of(panel_data, struct panel_info, + panel_data); + struct msm_mddi_client_data *client_data = panel->client_data; + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + + return bridge_data->unblank(bridge_data, client_data); +} + +irqreturn_t toshiba_vsync_interrupt(int irq, void *data) +{ + struct panel_info *panel = data; + + panel->toshiba_got_int = 1; + if (panel->toshiba_callback) { + panel->toshiba_callback->func(panel->toshiba_callback); + panel->toshiba_callback = 0; + } + wake_up(&toshiba_vsync_wait); + return IRQ_HANDLED; +} + +static int setup_vsync(struct panel_info *panel, + int init) +{ + int ret; + int gpio = 97; + unsigned int irq; + + if (!init) { + ret = 0; + goto uninit; + } + ret = gpio_request_one(gpio, GPIOF_IN, "vsync"); + if (ret) + goto err_request_gpio_failed; + + ret = irq = gpio_to_irq(gpio); + if (ret < 0) + goto err_get_irq_num_failed; + + ret = request_irq(irq, toshiba_vsync_interrupt, IRQF_TRIGGER_RISING, + "vsync", panel); + if (ret) + goto err_request_irq_failed; + printk(KERN_INFO "vsync on gpio %d now %d\n", + gpio, gpio_get_value(gpio)); + return 0; + +uninit: + free_irq(gpio_to_irq(gpio), panel); +err_request_irq_failed: +err_get_irq_num_failed: + gpio_free(gpio); +err_request_gpio_failed: + return ret; +} + +static int mddi_toshiba_probe(struct platform_device *pdev) +{ + int ret; + struct msm_mddi_client_data *client_data = pdev->dev.platform_data; + struct msm_mddi_bridge_platform_data *bridge_data = + client_data->private_client_data; + struct panel_info *panel = + kzalloc(sizeof(struct panel_info), GFP_KERNEL); + if (!panel) + return -ENOMEM; + platform_set_drvdata(pdev, panel); + + /* mddi_remote_write(mddi, 0, WAKEUP); */ + client_data->remote_write(client_data, GPIOSEL_VWAKEINT, GPIOSEL); + client_data->remote_write(client_data, INTMASK_VWAKEOUT, INTMASK); + + ret = setup_vsync(panel, 1); + if (ret) { + dev_err(&pdev->dev, "mddi_bridge_setup_vsync failed\n"); + return ret; + } + + panel->client_data = client_data; + panel->panel_data.suspend = toshiba_suspend; + panel->panel_data.resume = toshiba_resume; + panel->panel_data.wait_vsync = toshiba_wait_vsync; + panel->panel_data.request_vsync = toshiba_request_vsync; + panel->panel_data.clear_vsync = toshiba_clear_vsync; + panel->panel_data.blank = toshiba_blank; + panel->panel_data.unblank = toshiba_unblank; + panel->panel_data.fb_data = &bridge_data->fb_data; + panel->panel_data.caps = MSMFB_CAP_PARTIAL_UPDATES; + + panel->pdev.name = "msm_panel"; + panel->pdev.id = pdev->id; + panel->pdev.resource = client_data->fb_resource; + panel->pdev.num_resources = 1; + panel->pdev.dev.platform_data = &panel->panel_data; + bridge_data->init(bridge_data, client_data); + platform_device_register(&panel->pdev); + + return 0; +} + +static int mddi_toshiba_remove(struct platform_device *pdev) +{ + struct panel_info *panel = platform_get_drvdata(pdev); + + setup_vsync(panel, 0); + kfree(panel); + return 0; +} + +static struct platform_driver mddi_client_d263_0000 = { + .probe = mddi_toshiba_probe, + .remove = mddi_toshiba_remove, + .driver = { .name = "mddi_c_d263_0000" }, +}; + +static int __init mddi_client_toshiba_init(void) +{ + platform_driver_register(&mddi_client_d263_0000); + return 0; +} + +module_init(mddi_client_toshiba_init); + diff --git a/drivers/video/fbdev/msm/mddi_hw.h b/drivers/video/fbdev/msm/mddi_hw.h new file mode 100644 index 000000000000..45cc01fc1e7f --- /dev/null +++ b/drivers/video/fbdev/msm/mddi_hw.h @@ -0,0 +1,305 @@ +/* drivers/video/msm_fb/mddi_hw.h + * + * MSM MDDI Hardware Registers and Structures + * + * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef _MDDI_HW_H_ +#define _MDDI_HW_H_ + +#include <linux/types.h> + +#define MDDI_CMD 0x0000 +#define MDDI_VERSION 0x0004 +#define MDDI_PRI_PTR 0x0008 +#define MDDI_SEC_PTR 0x000c +#define MDDI_BPS 0x0010 +#define MDDI_SPM 0x0014 +#define MDDI_INT 0x0018 +#define MDDI_INTEN 0x001c +#define MDDI_REV_PTR 0x0020 +#define MDDI_REV_SIZE 0x0024 +#define MDDI_STAT 0x0028 +#define MDDI_REV_RATE_DIV 0x002c +#define MDDI_REV_CRC_ERR 0x0030 +#define MDDI_TA1_LEN 0x0034 +#define MDDI_TA2_LEN 0x0038 +#define MDDI_TEST_BUS 0x003c +#define MDDI_TEST 0x0040 +#define MDDI_REV_PKT_CNT 0x0044 +#define MDDI_DRIVE_HI 0x0048 +#define MDDI_DRIVE_LO 0x004c +#define MDDI_DISP_WAKE 0x0050 +#define MDDI_REV_ENCAP_SZ 0x0054 +#define MDDI_RTD_VAL 0x0058 +#define MDDI_PAD_CTL 0x0068 +#define MDDI_DRIVER_START_CNT 0x006c +#define MDDI_NEXT_PRI_PTR 0x0070 +#define MDDI_NEXT_SEC_PTR 0x0074 +#define MDDI_MISR_CTL 0x0078 +#define MDDI_MISR_DATA 0x007c +#define MDDI_SF_CNT 0x0080 +#define MDDI_MF_CNT 0x0084 +#define MDDI_CURR_REV_PTR 0x0088 +#define MDDI_CORE_VER 0x008c + +#define MDDI_INT_PRI_PTR_READ 0x0001 +#define MDDI_INT_SEC_PTR_READ 0x0002 +#define MDDI_INT_REV_DATA_AVAIL 0x0004 +#define MDDI_INT_DISP_REQ 0x0008 +#define MDDI_INT_PRI_UNDERFLOW 0x0010 +#define MDDI_INT_SEC_UNDERFLOW 0x0020 +#define MDDI_INT_REV_OVERFLOW 0x0040 +#define MDDI_INT_CRC_ERROR 0x0080 +#define MDDI_INT_MDDI_IN 0x0100 +#define MDDI_INT_PRI_OVERWRITE 0x0200 +#define MDDI_INT_SEC_OVERWRITE 0x0400 +#define MDDI_INT_REV_OVERWRITE 0x0800 +#define MDDI_INT_DMA_FAILURE 0x1000 +#define MDDI_INT_LINK_ACTIVE 0x2000 +#define MDDI_INT_IN_HIBERNATION 0x4000 +#define MDDI_INT_PRI_LINK_LIST_DONE 0x8000 +#define MDDI_INT_SEC_LINK_LIST_DONE 0x10000 +#define MDDI_INT_NO_CMD_PKTS_PEND 0x20000 +#define MDDI_INT_RTD_FAILURE 0x40000 +#define MDDI_INT_REV_PKT_RECEIVED 0x80000 +#define MDDI_INT_REV_PKTS_AVAIL 0x100000 + +#define MDDI_INT_NEED_CLEAR ( \ + MDDI_INT_REV_DATA_AVAIL | \ + MDDI_INT_PRI_UNDERFLOW | \ + MDDI_INT_SEC_UNDERFLOW | \ + MDDI_INT_REV_OVERFLOW | \ + MDDI_INT_CRC_ERROR | \ + MDDI_INT_REV_PKT_RECEIVED) + + +#define MDDI_STAT_LINK_ACTIVE 0x0001 +#define MDDI_STAT_NEW_REV_PTR 0x0002 +#define MDDI_STAT_NEW_PRI_PTR 0x0004 +#define MDDI_STAT_NEW_SEC_PTR 0x0008 +#define MDDI_STAT_IN_HIBERNATION 0x0010 +#define MDDI_STAT_PRI_LINK_LIST_DONE 0x0020 +#define MDDI_STAT_SEC_LINK_LIST_DONE 0x0040 +#define MDDI_STAT_PENDING_TIMING_PKT 0x0080 +#define MDDI_STAT_PENDING_REV_ENCAP 0x0100 +#define MDDI_STAT_PENDING_POWERDOWN 0x0200 +#define MDDI_STAT_RTD_MEAS_FAIL 0x0800 +#define MDDI_STAT_CLIENT_WAKEUP_REQ 0x1000 + + +#define MDDI_CMD_POWERDOWN 0x0100 +#define MDDI_CMD_POWERUP 0x0200 +#define MDDI_CMD_HIBERNATE 0x0300 +#define MDDI_CMD_RESET 0x0400 +#define MDDI_CMD_DISP_IGNORE 0x0501 +#define MDDI_CMD_DISP_LISTEN 0x0500 +#define MDDI_CMD_SEND_REV_ENCAP 0x0600 +#define MDDI_CMD_GET_CLIENT_CAP 0x0601 +#define MDDI_CMD_GET_CLIENT_STATUS 0x0602 +#define MDDI_CMD_SEND_RTD 0x0700 +#define MDDI_CMD_LINK_ACTIVE 0x0900 +#define MDDI_CMD_PERIODIC_REV_ENCAP 0x0A00 +#define MDDI_CMD_FORCE_NEW_REV_PTR 0x0C00 + + + +#define MDDI_VIDEO_REV_PKT_SIZE 0x40 +#define MDDI_CLIENT_CAPABILITY_REV_PKT_SIZE 0x60 +#define MDDI_MAX_REV_PKT_SIZE 0x60 + +/* #define MDDI_REV_BUFFER_SIZE 128 */ +#define MDDI_REV_BUFFER_SIZE (MDDI_MAX_REV_PKT_SIZE * 4) + +/* MDP sends 256 pixel packets, so lower value hibernates more without + * significantly increasing latency of waiting for next subframe */ +#define MDDI_HOST_BYTES_PER_SUBFRAME 0x3C00 +#define MDDI_HOST_TA2_LEN 0x000c +#define MDDI_HOST_REV_RATE_DIV 0x0002 + + +struct __attribute__((packed)) mddi_rev_packet { + uint16_t length; + uint16_t type; + uint16_t client_id; +}; + +struct __attribute__((packed)) mddi_client_status { + uint16_t length; + uint16_t type; + uint16_t client_id; + uint16_t reverse_link_request; /* bytes needed in rev encap message */ + uint8_t crc_error_count; + uint8_t capability_change; + uint16_t graphics_busy_flags; + uint16_t crc16; +}; + +struct __attribute__((packed)) mddi_client_caps { + uint16_t length; /* length, exclusive of this field */ + uint16_t type; /* 66 */ + uint16_t client_id; + + uint16_t Protocol_Version; + uint16_t Minimum_Protocol_Version; + uint16_t Data_Rate_Capability; + uint8_t Interface_Type_Capability; + uint8_t Number_of_Alt_Displays; + uint16_t PostCal_Data_Rate; + uint16_t Bitmap_Width; + uint16_t Bitmap_Height; + uint16_t Display_Window_Width; + uint16_t Display_Window_Height; + uint32_t Color_Map_Size; + uint16_t Color_Map_RGB_Width; + uint16_t RGB_Capability; + uint8_t Monochrome_Capability; + uint8_t Reserved_1; + uint16_t Y_Cb_Cr_Capability; + uint16_t Bayer_Capability; + uint16_t Alpha_Cursor_Image_Planes; + uint32_t Client_Feature_Capability_Indicators; + uint8_t Maximum_Video_Frame_Rate_Capability; + uint8_t Minimum_Video_Frame_Rate_Capability; + uint16_t Minimum_Sub_frame_Rate; + uint16_t Audio_Buffer_Depth; + uint16_t Audio_Channel_Capability; + uint16_t Audio_Sample_Rate_Capability; + uint8_t Audio_Sample_Resolution; + uint8_t Mic_Audio_Sample_Resolution; + uint16_t Mic_Sample_Rate_Capability; + uint8_t Keyboard_Data_Format; + uint8_t pointing_device_data_format; + uint16_t content_protection_type; + uint16_t Mfr_Name; + uint16_t Product_Code; + uint16_t Reserved_3; + uint32_t Serial_Number; + uint8_t Week_of_Manufacture; + uint8_t Year_of_Manufacture; + + uint16_t crc16; +} mddi_client_capability_type; + + +struct __attribute__((packed)) mddi_video_stream { + uint16_t length; + uint16_t type; /* 16 */ + uint16_t client_id; /* 0 */ + + uint16_t video_data_format_descriptor; +/* format of each pixel in the Pixel Data in the present stream in the + * present packet. + * If bits [15:13] = 000 monochrome + * If bits [15:13] = 001 color pixels (palette). + * If bits [15:13] = 010 color pixels in raw RGB + * If bits [15:13] = 011 data in 4:2:2 Y Cb Cr format + * If bits [15:13] = 100 Bayer pixels + */ + + uint16_t pixel_data_attributes; +/* interpreted as follows: + * Bits [1:0] = 11 pixel data is displayed to both eyes + * Bits [1:0] = 10 pixel data is routed to the left eye only. + * Bits [1:0] = 01 pixel data is routed to the right eye only. + * Bits [1:0] = 00 pixel data is routed to the alternate display. + * Bit 2 is 0 Pixel Data is in the standard progressive format. + * Bit 2 is 1 Pixel Data is in interlace format. + * Bit 3 is 0 Pixel Data is in the standard progressive format. + * Bit 3 is 1 Pixel Data is in alternate pixel format. + * Bit 4 is 0 Pixel Data is to or from the display frame buffer. + * Bit 4 is 1 Pixel Data is to or from the camera. + * Bit 5 is 0 pixel data contains the next consecutive row of pixels. + * Bit 5 is 1 X Left Edge, Y Top Edge, X Right Edge, Y Bottom Edge, + * X Start, and Y Start parameters are not defined and + * shall be ignored by the client. + * Bits [7:6] = 01 Pixel data is written to the offline image buffer. + * Bits [7:6] = 00 Pixel data is written to the buffer to refresh display. + * Bits [7:6] = 11 Pixel data is written to all image buffers. + * Bits [7:6] = 10 Invalid. Reserved for future use. + * Bits 8 through 11 alternate display number. + * Bits 12 through 14 are reserved for future use and shall be set to zero. + * Bit 15 is 1 the row of pixels is the last row of pixels in a frame. + */ + + uint16_t x_left_edge; + uint16_t y_top_edge; + /* X,Y coordinate of the top left edge of the screen window */ + + uint16_t x_right_edge; + uint16_t y_bottom_edge; + /* X,Y coordinate of the bottom right edge of the window being + * updated. */ + + uint16_t x_start; + uint16_t y_start; + /* (X Start, Y Start) is the first pixel in the Pixel Data field + * below. */ + + uint16_t pixel_count; + /* number of pixels in the Pixel Data field below. */ + + uint16_t parameter_CRC; + /* 16-bit CRC of all bytes from the Packet Length to the Pixel Count. */ + + uint16_t reserved; + /* 16-bit variable to make structure align on 4 byte boundary */ +}; + +#define TYPE_VIDEO_STREAM 16 +#define TYPE_CLIENT_CAPS 66 +#define TYPE_REGISTER_ACCESS 146 +#define TYPE_CLIENT_STATUS 70 + +struct __attribute__((packed)) mddi_register_access { + uint16_t length; + uint16_t type; /* 146 */ + uint16_t client_id; + + uint16_t read_write_info; + /* Bits 13:0 a 14-bit unsigned integer that specifies the number of + * 32-bit Register Data List items to be transferred in the + * Register Data List field. + * Bits[15:14] = 00 Write to register(s); + * Bits[15:14] = 10 Read from register(s); + * Bits[15:14] = 11 Response to a Read. + * Bits[15:14] = 01 this value is reserved for future use. */ +#define MDDI_WRITE (0 << 14) +#define MDDI_READ (2 << 14) +#define MDDI_READ_RESP (3 << 14) + + uint32_t register_address; + /* the register address that is to be written to or read from. */ + + uint16_t crc16; + + uint32_t register_data_list; + /* list of 4-byte register data values for/from client registers */ +}; + +struct __attribute__((packed)) mddi_llentry { + uint16_t flags; + uint16_t header_count; + uint16_t data_count; + dma_addr_t data; /* 32 bit */ + struct mddi_llentry *next; + uint16_t reserved; + union { + struct mddi_video_stream v; + struct mddi_register_access r; + uint32_t _[12]; + } u; +}; + +#endif diff --git a/drivers/video/fbdev/msm/mdp.c b/drivers/video/fbdev/msm/mdp.c new file mode 100644 index 000000000000..113c7876c855 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp.c @@ -0,0 +1,520 @@ +/* drivers/video/msm_fb/mdp.c + * + * MSM MDP Interface (used by framebuffer core) + * + * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/fb.h> +#include <linux/msm_mdp.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/clk.h> +#include <linux/file.h> +#include <linux/major.h> +#include <linux/slab.h> + +#include <linux/platform_data/video-msm_fb.h> +#include <linux/platform_device.h> +#include <linux/export.h> + +#include "mdp_hw.h" + +struct class *mdp_class; + +#define MDP_CMD_DEBUG_ACCESS_BASE (0x10000) + +static uint16_t mdp_default_ccs[] = { + 0x254, 0x000, 0x331, 0x254, 0xF38, 0xE61, 0x254, 0x409, 0x000, + 0x010, 0x080, 0x080 +}; + +static DECLARE_WAIT_QUEUE_HEAD(mdp_dma2_waitqueue); +static DECLARE_WAIT_QUEUE_HEAD(mdp_ppp_waitqueue); +static struct msmfb_callback *dma_callback; +static struct clk *clk; +static unsigned int mdp_irq_mask; +static DEFINE_SPINLOCK(mdp_lock); +DEFINE_MUTEX(mdp_mutex); + +static int enable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +{ + unsigned long irq_flags; + int ret = 0; + + BUG_ON(!mask); + + spin_lock_irqsave(&mdp_lock, irq_flags); + /* if the mask bits are already set return an error, this interrupt + * is already enabled */ + if (mdp_irq_mask & mask) { + printk(KERN_ERR "mdp irq already on already on %x %x\n", + mdp_irq_mask, mask); + ret = -1; + } + /* if the mdp irq is not already enabled enable it */ + if (!mdp_irq_mask) { + if (clk) + clk_enable(clk); + enable_irq(mdp->irq); + } + + /* update the irq mask to reflect the fact that the interrupt is + * enabled */ + mdp_irq_mask |= mask; + spin_unlock_irqrestore(&mdp_lock, irq_flags); + return ret; +} + +static int locked_disable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +{ + /* this interrupt is already disabled! */ + if (!(mdp_irq_mask & mask)) { + printk(KERN_ERR "mdp irq already off %x %x\n", + mdp_irq_mask, mask); + return -1; + } + /* update the irq mask to reflect the fact that the interrupt is + * disabled */ + mdp_irq_mask &= ~(mask); + /* if no one is waiting on the interrupt, disable it */ + if (!mdp_irq_mask) { + disable_irq_nosync(mdp->irq); + if (clk) + clk_disable(clk); + } + return 0; +} + +static int disable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +{ + unsigned long irq_flags; + int ret; + + spin_lock_irqsave(&mdp_lock, irq_flags); + ret = locked_disable_mdp_irq(mdp, mask); + spin_unlock_irqrestore(&mdp_lock, irq_flags); + return ret; +} + +static irqreturn_t mdp_isr(int irq, void *data) +{ + uint32_t status; + unsigned long irq_flags; + struct mdp_info *mdp = data; + + spin_lock_irqsave(&mdp_lock, irq_flags); + + status = mdp_readl(mdp, MDP_INTR_STATUS); + mdp_writel(mdp, status, MDP_INTR_CLEAR); + + status &= mdp_irq_mask; + if (status & DL0_DMA2_TERM_DONE) { + if (dma_callback) { + dma_callback->func(dma_callback); + dma_callback = NULL; + } + wake_up(&mdp_dma2_waitqueue); + } + + if (status & DL0_ROI_DONE) + wake_up(&mdp_ppp_waitqueue); + + if (status) + locked_disable_mdp_irq(mdp, status); + + spin_unlock_irqrestore(&mdp_lock, irq_flags); + return IRQ_HANDLED; +} + +static uint32_t mdp_check_mask(uint32_t mask) +{ + uint32_t ret; + unsigned long irq_flags; + + spin_lock_irqsave(&mdp_lock, irq_flags); + ret = mdp_irq_mask & mask; + spin_unlock_irqrestore(&mdp_lock, irq_flags); + return ret; +} + +static int mdp_wait(struct mdp_info *mdp, uint32_t mask, wait_queue_head_t *wq) +{ + int ret = 0; + unsigned long irq_flags; + + wait_event_timeout(*wq, !mdp_check_mask(mask), HZ); + + spin_lock_irqsave(&mdp_lock, irq_flags); + if (mdp_irq_mask & mask) { + locked_disable_mdp_irq(mdp, mask); + printk(KERN_WARNING "timeout waiting for mdp to complete %x\n", + mask); + ret = -ETIMEDOUT; + } + spin_unlock_irqrestore(&mdp_lock, irq_flags); + + return ret; +} + +void mdp_dma_wait(struct mdp_device *mdp_dev) +{ +#define MDP_MAX_TIMEOUTS 20 + static int timeout_count; + struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + + if (mdp_wait(mdp, DL0_DMA2_TERM_DONE, &mdp_dma2_waitqueue) == -ETIMEDOUT) + timeout_count++; + else + timeout_count = 0; + + if (timeout_count > MDP_MAX_TIMEOUTS) { + printk(KERN_ERR "mdp: dma failed %d times, somethings wrong!\n", + MDP_MAX_TIMEOUTS); + BUG(); + } +} + +static int mdp_ppp_wait(struct mdp_info *mdp) +{ + return mdp_wait(mdp, DL0_ROI_DONE, &mdp_ppp_waitqueue); +} + +void mdp_dma_to_mddi(struct mdp_info *mdp, uint32_t addr, uint32_t stride, + uint32_t width, uint32_t height, uint32_t x, uint32_t y, + struct msmfb_callback *callback) +{ + uint32_t dma2_cfg; + uint16_t ld_param = 0; /* 0=PRIM, 1=SECD, 2=EXT */ + + if (enable_mdp_irq(mdp, DL0_DMA2_TERM_DONE)) { + printk(KERN_ERR "mdp_dma_to_mddi: busy\n"); + return; + } + + dma_callback = callback; + + dma2_cfg = DMA_PACK_TIGHT | + DMA_PACK_ALIGN_LSB | + DMA_PACK_PATTERN_RGB | + DMA_OUT_SEL_AHB | + DMA_IBUF_NONCONTIGUOUS; + + dma2_cfg |= DMA_IBUF_FORMAT_RGB565; + + dma2_cfg |= DMA_OUT_SEL_MDDI; + + dma2_cfg |= DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY; + + dma2_cfg |= DMA_DITHER_EN; + + /* setup size, address, and stride */ + mdp_writel(mdp, (height << 16) | (width), + MDP_CMD_DEBUG_ACCESS_BASE + 0x0184); + mdp_writel(mdp, addr, MDP_CMD_DEBUG_ACCESS_BASE + 0x0188); + mdp_writel(mdp, stride, MDP_CMD_DEBUG_ACCESS_BASE + 0x018C); + + /* 666 18BPP */ + dma2_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + + /* set y & x offset and MDDI transaction parameters */ + mdp_writel(mdp, (y << 16) | (x), MDP_CMD_DEBUG_ACCESS_BASE + 0x0194); + mdp_writel(mdp, ld_param, MDP_CMD_DEBUG_ACCESS_BASE + 0x01a0); + mdp_writel(mdp, (MDDI_VDO_PACKET_DESC << 16) | MDDI_VDO_PACKET_PRIM, + MDP_CMD_DEBUG_ACCESS_BASE + 0x01a4); + + mdp_writel(mdp, dma2_cfg, MDP_CMD_DEBUG_ACCESS_BASE + 0x0180); + + /* start DMA2 */ + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0044); +} + +void mdp_dma(struct mdp_device *mdp_dev, uint32_t addr, uint32_t stride, + uint32_t width, uint32_t height, uint32_t x, uint32_t y, + struct msmfb_callback *callback, int interface) +{ + struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + + if (interface == MSM_MDDI_PMDH_INTERFACE) { + mdp_dma_to_mddi(mdp, addr, stride, width, height, x, y, + callback); + } +} + +int get_img(struct mdp_img *img, struct fb_info *info, + unsigned long *start, unsigned long *len, + struct file **filep) +{ + int ret = 0; + struct fd f = fdget(img->memory_id); + if (f.file == NULL) + return -1; + + if (MAJOR(file_inode(f.file)->i_rdev) == FB_MAJOR) { + *start = info->fix.smem_start; + *len = info->fix.smem_len; + } else + ret = -1; + fdput(f); + + return ret; +} + +void put_img(struct file *src_file, struct file *dst_file) +{ +} + +int mdp_blit(struct mdp_device *mdp_dev, struct fb_info *fb, + struct mdp_blit_req *req) +{ + int ret; + unsigned long src_start = 0, src_len = 0, dst_start = 0, dst_len = 0; + struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + struct file *src_file = 0, *dst_file = 0; + + /* WORKAROUND FOR HARDWARE BUG IN BG TILE FETCH */ + if (unlikely(req->src_rect.h == 0 || + req->src_rect.w == 0)) { + printk(KERN_ERR "mpd_ppp: src img of zero size!\n"); + return -EINVAL; + } + if (unlikely(req->dst_rect.h == 0 || + req->dst_rect.w == 0)) + return -EINVAL; + + /* do this first so that if this fails, the caller can always + * safely call put_img */ + if (unlikely(get_img(&req->src, fb, &src_start, &src_len, &src_file))) { + printk(KERN_ERR "mpd_ppp: could not retrieve src image from " + "memory\n"); + return -EINVAL; + } + + if (unlikely(get_img(&req->dst, fb, &dst_start, &dst_len, &dst_file))) { + printk(KERN_ERR "mpd_ppp: could not retrieve dst image from " + "memory\n"); + return -EINVAL; + } + mutex_lock(&mdp_mutex); + + /* transp_masking unimplemented */ + req->transp_mask = MDP_TRANSP_NOP; + if (unlikely((req->transp_mask != MDP_TRANSP_NOP || + req->alpha != MDP_ALPHA_NOP || + HAS_ALPHA(req->src.format)) && + (req->flags & MDP_ROT_90 && + req->dst_rect.w <= 16 && req->dst_rect.h >= 16))) { + int i; + unsigned int tiles = req->dst_rect.h / 16; + unsigned int remainder = req->dst_rect.h % 16; + req->src_rect.w = 16*req->src_rect.w / req->dst_rect.h; + req->dst_rect.h = 16; + for (i = 0; i < tiles; i++) { + enable_mdp_irq(mdp, DL0_ROI_DONE); + ret = mdp_ppp_blit(mdp, req, src_file, src_start, + src_len, dst_file, dst_start, + dst_len); + if (ret) + goto err_bad_blit; + ret = mdp_ppp_wait(mdp); + if (ret) + goto err_wait_failed; + req->dst_rect.y += 16; + req->src_rect.x += req->src_rect.w; + } + if (!remainder) + goto end; + req->src_rect.w = remainder*req->src_rect.w / req->dst_rect.h; + req->dst_rect.h = remainder; + } + enable_mdp_irq(mdp, DL0_ROI_DONE); + ret = mdp_ppp_blit(mdp, req, src_file, src_start, src_len, dst_file, + dst_start, + dst_len); + if (ret) + goto err_bad_blit; + ret = mdp_ppp_wait(mdp); + if (ret) + goto err_wait_failed; +end: + put_img(src_file, dst_file); + mutex_unlock(&mdp_mutex); + return 0; +err_bad_blit: + disable_mdp_irq(mdp, DL0_ROI_DONE); +err_wait_failed: + put_img(src_file, dst_file); + mutex_unlock(&mdp_mutex); + return ret; +} + +void mdp_set_grp_disp(struct mdp_device *mdp_dev, unsigned disp_id) +{ + struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + + disp_id &= 0xf; + mdp_writel(mdp, disp_id, MDP_FULL_BYPASS_WORD43); +} + +int register_mdp_client(struct class_interface *cint) +{ + if (!mdp_class) { + pr_err("mdp: no mdp_class when registering mdp client\n"); + return -ENODEV; + } + cint->class = mdp_class; + return class_interface_register(cint); +} + +#include "mdp_csc_table.h" +#include "mdp_scale_tables.h" + +int mdp_probe(struct platform_device *pdev) +{ + struct resource *resource; + int ret; + int n; + struct mdp_info *mdp; + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + pr_err("mdp: can not get mdp mem resource!\n"); + return -ENOMEM; + } + + mdp = kzalloc(sizeof(struct mdp_info), GFP_KERNEL); + if (!mdp) + return -ENOMEM; + + mdp->irq = platform_get_irq(pdev, 0); + if (mdp->irq < 0) { + pr_err("mdp: can not get mdp irq\n"); + ret = mdp->irq; + goto error_get_irq; + } + + mdp->base = ioremap(resource->start, resource_size(resource)); + if (mdp->base == 0) { + printk(KERN_ERR "msmfb: cannot allocate mdp regs!\n"); + ret = -ENOMEM; + goto error_ioremap; + } + + mdp->mdp_dev.dma = mdp_dma; + mdp->mdp_dev.dma_wait = mdp_dma_wait; + mdp->mdp_dev.blit = mdp_blit; + mdp->mdp_dev.set_grp_disp = mdp_set_grp_disp; + + clk = clk_get(&pdev->dev, "mdp_clk"); + if (IS_ERR(clk)) { + printk(KERN_INFO "mdp: failed to get mdp clk"); + ret = PTR_ERR(clk); + goto error_get_clk; + } + + ret = request_irq(mdp->irq, mdp_isr, 0, "msm_mdp", mdp); + if (ret) + goto error_request_irq; + disable_irq(mdp->irq); + mdp_irq_mask = 0; + + /* debug interface write access */ + mdp_writel(mdp, 1, 0x60); + + mdp_writel(mdp, MDP_ANY_INTR_MASK, MDP_INTR_ENABLE); + mdp_writel(mdp, 1, MDP_EBI2_PORTMAP_MODE); + + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01f8); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01fc); + + for (n = 0; n < ARRAY_SIZE(csc_table); n++) + mdp_writel(mdp, csc_table[n].val, csc_table[n].reg); + + /* clear up unused fg/main registers */ + /* comp.plane 2&3 ystride */ + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0120); + + /* unpacked pattern */ + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x012c); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0130); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0134); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0158); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x015c); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0160); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0170); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0174); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x017c); + + /* comp.plane 2 & 3 */ + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0114); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0118); + + /* clear unused bg registers */ + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01c8); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01d0); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01dc); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01e0); + mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01e4); + + for (n = 0; n < ARRAY_SIZE(mdp_upscale_table); n++) + mdp_writel(mdp, mdp_upscale_table[n].val, + mdp_upscale_table[n].reg); + + for (n = 0; n < 9; n++) + mdp_writel(mdp, mdp_default_ccs[n], 0x40440 + 4 * n); + mdp_writel(mdp, mdp_default_ccs[9], 0x40500 + 4 * 0); + mdp_writel(mdp, mdp_default_ccs[10], 0x40500 + 4 * 0); + mdp_writel(mdp, mdp_default_ccs[11], 0x40500 + 4 * 0); + + /* register mdp device */ + mdp->mdp_dev.dev.parent = &pdev->dev; + mdp->mdp_dev.dev.class = mdp_class; + dev_set_name(&mdp->mdp_dev.dev, "mdp%d", pdev->id); + + /* if you can remove the platform device you'd have to implement + * this: + mdp_dev.release = mdp_class; */ + + ret = device_register(&mdp->mdp_dev.dev); + if (ret) + goto error_device_register; + return 0; + +error_device_register: + free_irq(mdp->irq, mdp); +error_request_irq: +error_get_clk: + iounmap(mdp->base); +error_get_irq: +error_ioremap: + kfree(mdp); + return ret; +} + +static struct platform_driver msm_mdp_driver = { + .probe = mdp_probe, + .driver = {.name = "msm_mdp"}, +}; + +static int __init mdp_init(void) +{ + mdp_class = class_create(THIS_MODULE, "msm_mdp"); + if (IS_ERR(mdp_class)) { + printk(KERN_ERR "Error creating mdp class\n"); + return PTR_ERR(mdp_class); + } + return platform_driver_register(&msm_mdp_driver); +} + +subsys_initcall(mdp_init); diff --git a/drivers/video/fbdev/msm/mdp_csc_table.h b/drivers/video/fbdev/msm/mdp_csc_table.h new file mode 100644 index 000000000000..d1cde30ead52 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp_csc_table.h @@ -0,0 +1,582 @@ +/* drivers/video/msm_fb/mdp_csc_table.h + * + * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +static struct { + uint32_t reg; + uint32_t val; +} csc_table[] = { + { 0x40400, 0x83 }, + { 0x40404, 0x102 }, + { 0x40408, 0x32 }, + { 0x4040c, 0xffffffb5 }, + { 0x40410, 0xffffff6c }, + { 0x40414, 0xe1 }, + { 0x40418, 0xe1 }, + { 0x4041c, 0xffffff45 }, + { 0x40420, 0xffffffdc }, + { 0x40440, 0x254 }, + { 0x40444, 0x0 }, + { 0x40448, 0x331 }, + { 0x4044c, 0x254 }, + { 0x40450, 0xffffff38 }, + { 0x40454, 0xfffffe61 }, + { 0x40458, 0x254 }, + { 0x4045c, 0x409 }, + { 0x40460, 0x0 }, + { 0x40480, 0x5d }, + { 0x40484, 0x13a }, + { 0x40488, 0x20 }, + { 0x4048c, 0xffffffcd }, + { 0x40490, 0xffffff54 }, + { 0x40494, 0xe1 }, + { 0x40498, 0xe1 }, + { 0x4049c, 0xffffff35 }, + { 0x404a0, 0xffffffec }, + { 0x404c0, 0x254 }, + { 0x404c4, 0x0 }, + { 0x404c8, 0x396 }, + { 0x404cc, 0x254 }, + { 0x404d0, 0xffffff94 }, + { 0x404d4, 0xfffffef0 }, + { 0x404d8, 0x254 }, + { 0x404dc, 0x43a }, + { 0x404e0, 0x0 }, + { 0x40500, 0x10 }, + { 0x40504, 0x80 }, + { 0x40508, 0x80 }, + { 0x40540, 0x10 }, + { 0x40544, 0x80 }, + { 0x40548, 0x80 }, + { 0x40580, 0x10 }, + { 0x40584, 0xeb }, + { 0x40588, 0x10 }, + { 0x4058c, 0xf0 }, + { 0x405c0, 0x10 }, + { 0x405c4, 0xeb }, + { 0x405c8, 0x10 }, + { 0x405cc, 0xf0 }, + { 0x40800, 0x0 }, + { 0x40804, 0x151515 }, + { 0x40808, 0x1d1d1d }, + { 0x4080c, 0x232323 }, + { 0x40810, 0x272727 }, + { 0x40814, 0x2b2b2b }, + { 0x40818, 0x2f2f2f }, + { 0x4081c, 0x333333 }, + { 0x40820, 0x363636 }, + { 0x40824, 0x393939 }, + { 0x40828, 0x3b3b3b }, + { 0x4082c, 0x3e3e3e }, + { 0x40830, 0x404040 }, + { 0x40834, 0x434343 }, + { 0x40838, 0x454545 }, + { 0x4083c, 0x474747 }, + { 0x40840, 0x494949 }, + { 0x40844, 0x4b4b4b }, + { 0x40848, 0x4d4d4d }, + { 0x4084c, 0x4f4f4f }, + { 0x40850, 0x515151 }, + { 0x40854, 0x535353 }, + { 0x40858, 0x555555 }, + { 0x4085c, 0x565656 }, + { 0x40860, 0x585858 }, + { 0x40864, 0x5a5a5a }, + { 0x40868, 0x5b5b5b }, + { 0x4086c, 0x5d5d5d }, + { 0x40870, 0x5e5e5e }, + { 0x40874, 0x606060 }, + { 0x40878, 0x616161 }, + { 0x4087c, 0x636363 }, + { 0x40880, 0x646464 }, + { 0x40884, 0x666666 }, + { 0x40888, 0x676767 }, + { 0x4088c, 0x686868 }, + { 0x40890, 0x6a6a6a }, + { 0x40894, 0x6b6b6b }, + { 0x40898, 0x6c6c6c }, + { 0x4089c, 0x6e6e6e }, + { 0x408a0, 0x6f6f6f }, + { 0x408a4, 0x707070 }, + { 0x408a8, 0x717171 }, + { 0x408ac, 0x727272 }, + { 0x408b0, 0x747474 }, + { 0x408b4, 0x757575 }, + { 0x408b8, 0x767676 }, + { 0x408bc, 0x777777 }, + { 0x408c0, 0x787878 }, + { 0x408c4, 0x797979 }, + { 0x408c8, 0x7a7a7a }, + { 0x408cc, 0x7c7c7c }, + { 0x408d0, 0x7d7d7d }, + { 0x408d4, 0x7e7e7e }, + { 0x408d8, 0x7f7f7f }, + { 0x408dc, 0x808080 }, + { 0x408e0, 0x818181 }, + { 0x408e4, 0x828282 }, + { 0x408e8, 0x838383 }, + { 0x408ec, 0x848484 }, + { 0x408f0, 0x858585 }, + { 0x408f4, 0x868686 }, + { 0x408f8, 0x878787 }, + { 0x408fc, 0x888888 }, + { 0x40900, 0x898989 }, + { 0x40904, 0x8a8a8a }, + { 0x40908, 0x8b8b8b }, + { 0x4090c, 0x8c8c8c }, + { 0x40910, 0x8d8d8d }, + { 0x40914, 0x8e8e8e }, + { 0x40918, 0x8f8f8f }, + { 0x4091c, 0x8f8f8f }, + { 0x40920, 0x909090 }, + { 0x40924, 0x919191 }, + { 0x40928, 0x929292 }, + { 0x4092c, 0x939393 }, + { 0x40930, 0x949494 }, + { 0x40934, 0x959595 }, + { 0x40938, 0x969696 }, + { 0x4093c, 0x969696 }, + { 0x40940, 0x979797 }, + { 0x40944, 0x989898 }, + { 0x40948, 0x999999 }, + { 0x4094c, 0x9a9a9a }, + { 0x40950, 0x9b9b9b }, + { 0x40954, 0x9c9c9c }, + { 0x40958, 0x9c9c9c }, + { 0x4095c, 0x9d9d9d }, + { 0x40960, 0x9e9e9e }, + { 0x40964, 0x9f9f9f }, + { 0x40968, 0xa0a0a0 }, + { 0x4096c, 0xa0a0a0 }, + { 0x40970, 0xa1a1a1 }, + { 0x40974, 0xa2a2a2 }, + { 0x40978, 0xa3a3a3 }, + { 0x4097c, 0xa4a4a4 }, + { 0x40980, 0xa4a4a4 }, + { 0x40984, 0xa5a5a5 }, + { 0x40988, 0xa6a6a6 }, + { 0x4098c, 0xa7a7a7 }, + { 0x40990, 0xa7a7a7 }, + { 0x40994, 0xa8a8a8 }, + { 0x40998, 0xa9a9a9 }, + { 0x4099c, 0xaaaaaa }, + { 0x409a0, 0xaaaaaa }, + { 0x409a4, 0xababab }, + { 0x409a8, 0xacacac }, + { 0x409ac, 0xadadad }, + { 0x409b0, 0xadadad }, + { 0x409b4, 0xaeaeae }, + { 0x409b8, 0xafafaf }, + { 0x409bc, 0xafafaf }, + { 0x409c0, 0xb0b0b0 }, + { 0x409c4, 0xb1b1b1 }, + { 0x409c8, 0xb2b2b2 }, + { 0x409cc, 0xb2b2b2 }, + { 0x409d0, 0xb3b3b3 }, + { 0x409d4, 0xb4b4b4 }, + { 0x409d8, 0xb4b4b4 }, + { 0x409dc, 0xb5b5b5 }, + { 0x409e0, 0xb6b6b6 }, + { 0x409e4, 0xb6b6b6 }, + { 0x409e8, 0xb7b7b7 }, + { 0x409ec, 0xb8b8b8 }, + { 0x409f0, 0xb8b8b8 }, + { 0x409f4, 0xb9b9b9 }, + { 0x409f8, 0xbababa }, + { 0x409fc, 0xbababa }, + { 0x40a00, 0xbbbbbb }, + { 0x40a04, 0xbcbcbc }, + { 0x40a08, 0xbcbcbc }, + { 0x40a0c, 0xbdbdbd }, + { 0x40a10, 0xbebebe }, + { 0x40a14, 0xbebebe }, + { 0x40a18, 0xbfbfbf }, + { 0x40a1c, 0xc0c0c0 }, + { 0x40a20, 0xc0c0c0 }, + { 0x40a24, 0xc1c1c1 }, + { 0x40a28, 0xc1c1c1 }, + { 0x40a2c, 0xc2c2c2 }, + { 0x40a30, 0xc3c3c3 }, + { 0x40a34, 0xc3c3c3 }, + { 0x40a38, 0xc4c4c4 }, + { 0x40a3c, 0xc5c5c5 }, + { 0x40a40, 0xc5c5c5 }, + { 0x40a44, 0xc6c6c6 }, + { 0x40a48, 0xc6c6c6 }, + { 0x40a4c, 0xc7c7c7 }, + { 0x40a50, 0xc8c8c8 }, + { 0x40a54, 0xc8c8c8 }, + { 0x40a58, 0xc9c9c9 }, + { 0x40a5c, 0xc9c9c9 }, + { 0x40a60, 0xcacaca }, + { 0x40a64, 0xcbcbcb }, + { 0x40a68, 0xcbcbcb }, + { 0x40a6c, 0xcccccc }, + { 0x40a70, 0xcccccc }, + { 0x40a74, 0xcdcdcd }, + { 0x40a78, 0xcecece }, + { 0x40a7c, 0xcecece }, + { 0x40a80, 0xcfcfcf }, + { 0x40a84, 0xcfcfcf }, + { 0x40a88, 0xd0d0d0 }, + { 0x40a8c, 0xd0d0d0 }, + { 0x40a90, 0xd1d1d1 }, + { 0x40a94, 0xd2d2d2 }, + { 0x40a98, 0xd2d2d2 }, + { 0x40a9c, 0xd3d3d3 }, + { 0x40aa0, 0xd3d3d3 }, + { 0x40aa4, 0xd4d4d4 }, + { 0x40aa8, 0xd4d4d4 }, + { 0x40aac, 0xd5d5d5 }, + { 0x40ab0, 0xd6d6d6 }, + { 0x40ab4, 0xd6d6d6 }, + { 0x40ab8, 0xd7d7d7 }, + { 0x40abc, 0xd7d7d7 }, + { 0x40ac0, 0xd8d8d8 }, + { 0x40ac4, 0xd8d8d8 }, + { 0x40ac8, 0xd9d9d9 }, + { 0x40acc, 0xd9d9d9 }, + { 0x40ad0, 0xdadada }, + { 0x40ad4, 0xdbdbdb }, + { 0x40ad8, 0xdbdbdb }, + { 0x40adc, 0xdcdcdc }, + { 0x40ae0, 0xdcdcdc }, + { 0x40ae4, 0xdddddd }, + { 0x40ae8, 0xdddddd }, + { 0x40aec, 0xdedede }, + { 0x40af0, 0xdedede }, + { 0x40af4, 0xdfdfdf }, + { 0x40af8, 0xdfdfdf }, + { 0x40afc, 0xe0e0e0 }, + { 0x40b00, 0xe0e0e0 }, + { 0x40b04, 0xe1e1e1 }, + { 0x40b08, 0xe1e1e1 }, + { 0x40b0c, 0xe2e2e2 }, + { 0x40b10, 0xe3e3e3 }, + { 0x40b14, 0xe3e3e3 }, + { 0x40b18, 0xe4e4e4 }, + { 0x40b1c, 0xe4e4e4 }, + { 0x40b20, 0xe5e5e5 }, + { 0x40b24, 0xe5e5e5 }, + { 0x40b28, 0xe6e6e6 }, + { 0x40b2c, 0xe6e6e6 }, + { 0x40b30, 0xe7e7e7 }, + { 0x40b34, 0xe7e7e7 }, + { 0x40b38, 0xe8e8e8 }, + { 0x40b3c, 0xe8e8e8 }, + { 0x40b40, 0xe9e9e9 }, + { 0x40b44, 0xe9e9e9 }, + { 0x40b48, 0xeaeaea }, + { 0x40b4c, 0xeaeaea }, + { 0x40b50, 0xebebeb }, + { 0x40b54, 0xebebeb }, + { 0x40b58, 0xececec }, + { 0x40b5c, 0xececec }, + { 0x40b60, 0xededed }, + { 0x40b64, 0xededed }, + { 0x40b68, 0xeeeeee }, + { 0x40b6c, 0xeeeeee }, + { 0x40b70, 0xefefef }, + { 0x40b74, 0xefefef }, + { 0x40b78, 0xf0f0f0 }, + { 0x40b7c, 0xf0f0f0 }, + { 0x40b80, 0xf1f1f1 }, + { 0x40b84, 0xf1f1f1 }, + { 0x40b88, 0xf2f2f2 }, + { 0x40b8c, 0xf2f2f2 }, + { 0x40b90, 0xf2f2f2 }, + { 0x40b94, 0xf3f3f3 }, + { 0x40b98, 0xf3f3f3 }, + { 0x40b9c, 0xf4f4f4 }, + { 0x40ba0, 0xf4f4f4 }, + { 0x40ba4, 0xf5f5f5 }, + { 0x40ba8, 0xf5f5f5 }, + { 0x40bac, 0xf6f6f6 }, + { 0x40bb0, 0xf6f6f6 }, + { 0x40bb4, 0xf7f7f7 }, + { 0x40bb8, 0xf7f7f7 }, + { 0x40bbc, 0xf8f8f8 }, + { 0x40bc0, 0xf8f8f8 }, + { 0x40bc4, 0xf9f9f9 }, + { 0x40bc8, 0xf9f9f9 }, + { 0x40bcc, 0xfafafa }, + { 0x40bd0, 0xfafafa }, + { 0x40bd4, 0xfafafa }, + { 0x40bd8, 0xfbfbfb }, + { 0x40bdc, 0xfbfbfb }, + { 0x40be0, 0xfcfcfc }, + { 0x40be4, 0xfcfcfc }, + { 0x40be8, 0xfdfdfd }, + { 0x40bec, 0xfdfdfd }, + { 0x40bf0, 0xfefefe }, + { 0x40bf4, 0xfefefe }, + { 0x40bf8, 0xffffff }, + { 0x40bfc, 0xffffff }, + { 0x40c00, 0x0 }, + { 0x40c04, 0x0 }, + { 0x40c08, 0x0 }, + { 0x40c0c, 0x0 }, + { 0x40c10, 0x0 }, + { 0x40c14, 0x0 }, + { 0x40c18, 0x0 }, + { 0x40c1c, 0x0 }, + { 0x40c20, 0x0 }, + { 0x40c24, 0x0 }, + { 0x40c28, 0x0 }, + { 0x40c2c, 0x0 }, + { 0x40c30, 0x0 }, + { 0x40c34, 0x0 }, + { 0x40c38, 0x0 }, + { 0x40c3c, 0x0 }, + { 0x40c40, 0x10101 }, + { 0x40c44, 0x10101 }, + { 0x40c48, 0x10101 }, + { 0x40c4c, 0x10101 }, + { 0x40c50, 0x10101 }, + { 0x40c54, 0x10101 }, + { 0x40c58, 0x10101 }, + { 0x40c5c, 0x10101 }, + { 0x40c60, 0x10101 }, + { 0x40c64, 0x10101 }, + { 0x40c68, 0x20202 }, + { 0x40c6c, 0x20202 }, + { 0x40c70, 0x20202 }, + { 0x40c74, 0x20202 }, + { 0x40c78, 0x20202 }, + { 0x40c7c, 0x20202 }, + { 0x40c80, 0x30303 }, + { 0x40c84, 0x30303 }, + { 0x40c88, 0x30303 }, + { 0x40c8c, 0x30303 }, + { 0x40c90, 0x30303 }, + { 0x40c94, 0x40404 }, + { 0x40c98, 0x40404 }, + { 0x40c9c, 0x40404 }, + { 0x40ca0, 0x40404 }, + { 0x40ca4, 0x40404 }, + { 0x40ca8, 0x50505 }, + { 0x40cac, 0x50505 }, + { 0x40cb0, 0x50505 }, + { 0x40cb4, 0x50505 }, + { 0x40cb8, 0x60606 }, + { 0x40cbc, 0x60606 }, + { 0x40cc0, 0x60606 }, + { 0x40cc4, 0x70707 }, + { 0x40cc8, 0x70707 }, + { 0x40ccc, 0x70707 }, + { 0x40cd0, 0x70707 }, + { 0x40cd4, 0x80808 }, + { 0x40cd8, 0x80808 }, + { 0x40cdc, 0x80808 }, + { 0x40ce0, 0x90909 }, + { 0x40ce4, 0x90909 }, + { 0x40ce8, 0xa0a0a }, + { 0x40cec, 0xa0a0a }, + { 0x40cf0, 0xa0a0a }, + { 0x40cf4, 0xb0b0b }, + { 0x40cf8, 0xb0b0b }, + { 0x40cfc, 0xb0b0b }, + { 0x40d00, 0xc0c0c }, + { 0x40d04, 0xc0c0c }, + { 0x40d08, 0xd0d0d }, + { 0x40d0c, 0xd0d0d }, + { 0x40d10, 0xe0e0e }, + { 0x40d14, 0xe0e0e }, + { 0x40d18, 0xe0e0e }, + { 0x40d1c, 0xf0f0f }, + { 0x40d20, 0xf0f0f }, + { 0x40d24, 0x101010 }, + { 0x40d28, 0x101010 }, + { 0x40d2c, 0x111111 }, + { 0x40d30, 0x111111 }, + { 0x40d34, 0x121212 }, + { 0x40d38, 0x121212 }, + { 0x40d3c, 0x131313 }, + { 0x40d40, 0x131313 }, + { 0x40d44, 0x141414 }, + { 0x40d48, 0x151515 }, + { 0x40d4c, 0x151515 }, + { 0x40d50, 0x161616 }, + { 0x40d54, 0x161616 }, + { 0x40d58, 0x171717 }, + { 0x40d5c, 0x171717 }, + { 0x40d60, 0x181818 }, + { 0x40d64, 0x191919 }, + { 0x40d68, 0x191919 }, + { 0x40d6c, 0x1a1a1a }, + { 0x40d70, 0x1b1b1b }, + { 0x40d74, 0x1b1b1b }, + { 0x40d78, 0x1c1c1c }, + { 0x40d7c, 0x1c1c1c }, + { 0x40d80, 0x1d1d1d }, + { 0x40d84, 0x1e1e1e }, + { 0x40d88, 0x1f1f1f }, + { 0x40d8c, 0x1f1f1f }, + { 0x40d90, 0x202020 }, + { 0x40d94, 0x212121 }, + { 0x40d98, 0x212121 }, + { 0x40d9c, 0x222222 }, + { 0x40da0, 0x232323 }, + { 0x40da4, 0x242424 }, + { 0x40da8, 0x242424 }, + { 0x40dac, 0x252525 }, + { 0x40db0, 0x262626 }, + { 0x40db4, 0x272727 }, + { 0x40db8, 0x272727 }, + { 0x40dbc, 0x282828 }, + { 0x40dc0, 0x292929 }, + { 0x40dc4, 0x2a2a2a }, + { 0x40dc8, 0x2b2b2b }, + { 0x40dcc, 0x2c2c2c }, + { 0x40dd0, 0x2c2c2c }, + { 0x40dd4, 0x2d2d2d }, + { 0x40dd8, 0x2e2e2e }, + { 0x40ddc, 0x2f2f2f }, + { 0x40de0, 0x303030 }, + { 0x40de4, 0x313131 }, + { 0x40de8, 0x323232 }, + { 0x40dec, 0x333333 }, + { 0x40df0, 0x333333 }, + { 0x40df4, 0x343434 }, + { 0x40df8, 0x353535 }, + { 0x40dfc, 0x363636 }, + { 0x40e00, 0x373737 }, + { 0x40e04, 0x383838 }, + { 0x40e08, 0x393939 }, + { 0x40e0c, 0x3a3a3a }, + { 0x40e10, 0x3b3b3b }, + { 0x40e14, 0x3c3c3c }, + { 0x40e18, 0x3d3d3d }, + { 0x40e1c, 0x3e3e3e }, + { 0x40e20, 0x3f3f3f }, + { 0x40e24, 0x404040 }, + { 0x40e28, 0x414141 }, + { 0x40e2c, 0x424242 }, + { 0x40e30, 0x434343 }, + { 0x40e34, 0x444444 }, + { 0x40e38, 0x464646 }, + { 0x40e3c, 0x474747 }, + { 0x40e40, 0x484848 }, + { 0x40e44, 0x494949 }, + { 0x40e48, 0x4a4a4a }, + { 0x40e4c, 0x4b4b4b }, + { 0x40e50, 0x4c4c4c }, + { 0x40e54, 0x4d4d4d }, + { 0x40e58, 0x4f4f4f }, + { 0x40e5c, 0x505050 }, + { 0x40e60, 0x515151 }, + { 0x40e64, 0x525252 }, + { 0x40e68, 0x535353 }, + { 0x40e6c, 0x545454 }, + { 0x40e70, 0x565656 }, + { 0x40e74, 0x575757 }, + { 0x40e78, 0x585858 }, + { 0x40e7c, 0x595959 }, + { 0x40e80, 0x5b5b5b }, + { 0x40e84, 0x5c5c5c }, + { 0x40e88, 0x5d5d5d }, + { 0x40e8c, 0x5e5e5e }, + { 0x40e90, 0x606060 }, + { 0x40e94, 0x616161 }, + { 0x40e98, 0x626262 }, + { 0x40e9c, 0x646464 }, + { 0x40ea0, 0x656565 }, + { 0x40ea4, 0x666666 }, + { 0x40ea8, 0x686868 }, + { 0x40eac, 0x696969 }, + { 0x40eb0, 0x6a6a6a }, + { 0x40eb4, 0x6c6c6c }, + { 0x40eb8, 0x6d6d6d }, + { 0x40ebc, 0x6f6f6f }, + { 0x40ec0, 0x707070 }, + { 0x40ec4, 0x717171 }, + { 0x40ec8, 0x737373 }, + { 0x40ecc, 0x747474 }, + { 0x40ed0, 0x767676 }, + { 0x40ed4, 0x777777 }, + { 0x40ed8, 0x797979 }, + { 0x40edc, 0x7a7a7a }, + { 0x40ee0, 0x7c7c7c }, + { 0x40ee4, 0x7d7d7d }, + { 0x40ee8, 0x7f7f7f }, + { 0x40eec, 0x808080 }, + { 0x40ef0, 0x828282 }, + { 0x40ef4, 0x838383 }, + { 0x40ef8, 0x858585 }, + { 0x40efc, 0x868686 }, + { 0x40f00, 0x888888 }, + { 0x40f04, 0x898989 }, + { 0x40f08, 0x8b8b8b }, + { 0x40f0c, 0x8d8d8d }, + { 0x40f10, 0x8e8e8e }, + { 0x40f14, 0x909090 }, + { 0x40f18, 0x919191 }, + { 0x40f1c, 0x939393 }, + { 0x40f20, 0x959595 }, + { 0x40f24, 0x969696 }, + { 0x40f28, 0x989898 }, + { 0x40f2c, 0x9a9a9a }, + { 0x40f30, 0x9b9b9b }, + { 0x40f34, 0x9d9d9d }, + { 0x40f38, 0x9f9f9f }, + { 0x40f3c, 0xa1a1a1 }, + { 0x40f40, 0xa2a2a2 }, + { 0x40f44, 0xa4a4a4 }, + { 0x40f48, 0xa6a6a6 }, + { 0x40f4c, 0xa7a7a7 }, + { 0x40f50, 0xa9a9a9 }, + { 0x40f54, 0xababab }, + { 0x40f58, 0xadadad }, + { 0x40f5c, 0xafafaf }, + { 0x40f60, 0xb0b0b0 }, + { 0x40f64, 0xb2b2b2 }, + { 0x40f68, 0xb4b4b4 }, + { 0x40f6c, 0xb6b6b6 }, + { 0x40f70, 0xb8b8b8 }, + { 0x40f74, 0xbababa }, + { 0x40f78, 0xbbbbbb }, + { 0x40f7c, 0xbdbdbd }, + { 0x40f80, 0xbfbfbf }, + { 0x40f84, 0xc1c1c1 }, + { 0x40f88, 0xc3c3c3 }, + { 0x40f8c, 0xc5c5c5 }, + { 0x40f90, 0xc7c7c7 }, + { 0x40f94, 0xc9c9c9 }, + { 0x40f98, 0xcbcbcb }, + { 0x40f9c, 0xcdcdcd }, + { 0x40fa0, 0xcfcfcf }, + { 0x40fa4, 0xd1d1d1 }, + { 0x40fa8, 0xd3d3d3 }, + { 0x40fac, 0xd5d5d5 }, + { 0x40fb0, 0xd7d7d7 }, + { 0x40fb4, 0xd9d9d9 }, + { 0x40fb8, 0xdbdbdb }, + { 0x40fbc, 0xdddddd }, + { 0x40fc0, 0xdfdfdf }, + { 0x40fc4, 0xe1e1e1 }, + { 0x40fc8, 0xe3e3e3 }, + { 0x40fcc, 0xe5e5e5 }, + { 0x40fd0, 0xe7e7e7 }, + { 0x40fd4, 0xe9e9e9 }, + { 0x40fd8, 0xebebeb }, + { 0x40fdc, 0xeeeeee }, + { 0x40fe0, 0xf0f0f0 }, + { 0x40fe4, 0xf2f2f2 }, + { 0x40fe8, 0xf4f4f4 }, + { 0x40fec, 0xf6f6f6 }, + { 0x40ff0, 0xf8f8f8 }, + { 0x40ff4, 0xfbfbfb }, + { 0x40ff8, 0xfdfdfd }, + { 0x40ffc, 0xffffff }, +}; diff --git a/drivers/video/fbdev/msm/mdp_hw.h b/drivers/video/fbdev/msm/mdp_hw.h new file mode 100644 index 000000000000..35848d741001 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp_hw.h @@ -0,0 +1,627 @@ +/* drivers/video/msm_fb/mdp_hw.h + * + * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ +#ifndef _MDP_HW_H_ +#define _MDP_HW_H_ + +#include <linux/platform_data/video-msm_fb.h> + +struct mdp_info { + struct mdp_device mdp_dev; + char * __iomem base; + int irq; +}; +struct mdp_blit_req; +struct mdp_device; +int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, + struct file *src_file, unsigned long src_start, + unsigned long src_len, struct file *dst_file, + unsigned long dst_start, unsigned long dst_len); +#define mdp_writel(mdp, value, offset) writel(value, mdp->base + offset) +#define mdp_readl(mdp, offset) readl(mdp->base + offset) + +#define MDP_SYNC_CONFIG_0 (0x00000) +#define MDP_SYNC_CONFIG_1 (0x00004) +#define MDP_SYNC_CONFIG_2 (0x00008) +#define MDP_SYNC_STATUS_0 (0x0000c) +#define MDP_SYNC_STATUS_1 (0x00010) +#define MDP_SYNC_STATUS_2 (0x00014) +#define MDP_SYNC_THRESH_0 (0x00018) +#define MDP_SYNC_THRESH_1 (0x0001c) +#define MDP_INTR_ENABLE (0x00020) +#define MDP_INTR_STATUS (0x00024) +#define MDP_INTR_CLEAR (0x00028) +#define MDP_DISPLAY0_START (0x00030) +#define MDP_DISPLAY1_START (0x00034) +#define MDP_DISPLAY_STATUS (0x00038) +#define MDP_EBI2_LCD0 (0x0003c) +#define MDP_EBI2_LCD1 (0x00040) +#define MDP_DISPLAY0_ADDR (0x00054) +#define MDP_DISPLAY1_ADDR (0x00058) +#define MDP_EBI2_PORTMAP_MODE (0x0005c) +#define MDP_MODE (0x00060) +#define MDP_TV_OUT_STATUS (0x00064) +#define MDP_HW_VERSION (0x00070) +#define MDP_SW_RESET (0x00074) +#define MDP_AXI_ERROR_MASTER_STOP (0x00078) +#define MDP_SEL_CLK_OR_HCLK_TEST_BUS (0x0007c) +#define MDP_PRIMARY_VSYNC_OUT_CTRL (0x00080) +#define MDP_SECONDARY_VSYNC_OUT_CTRL (0x00084) +#define MDP_EXTERNAL_VSYNC_OUT_CTRL (0x00088) +#define MDP_VSYNC_CTRL (0x0008c) +#define MDP_CGC_EN (0x00100) +#define MDP_CMD_STATUS (0x10008) +#define MDP_PROFILE_EN (0x10010) +#define MDP_PROFILE_COUNT (0x10014) +#define MDP_DMA_START (0x10044) +#define MDP_FULL_BYPASS_WORD0 (0x10100) +#define MDP_FULL_BYPASS_WORD1 (0x10104) +#define MDP_COMMAND_CONFIG (0x10104) +#define MDP_FULL_BYPASS_WORD2 (0x10108) +#define MDP_FULL_BYPASS_WORD3 (0x1010c) +#define MDP_FULL_BYPASS_WORD4 (0x10110) +#define MDP_FULL_BYPASS_WORD6 (0x10118) +#define MDP_FULL_BYPASS_WORD7 (0x1011c) +#define MDP_FULL_BYPASS_WORD8 (0x10120) +#define MDP_FULL_BYPASS_WORD9 (0x10124) +#define MDP_PPP_SOURCE_CONFIG (0x10124) +#define MDP_FULL_BYPASS_WORD10 (0x10128) +#define MDP_FULL_BYPASS_WORD11 (0x1012c) +#define MDP_FULL_BYPASS_WORD12 (0x10130) +#define MDP_FULL_BYPASS_WORD13 (0x10134) +#define MDP_FULL_BYPASS_WORD14 (0x10138) +#define MDP_PPP_OPERATION_CONFIG (0x10138) +#define MDP_FULL_BYPASS_WORD15 (0x1013c) +#define MDP_FULL_BYPASS_WORD16 (0x10140) +#define MDP_FULL_BYPASS_WORD17 (0x10144) +#define MDP_FULL_BYPASS_WORD18 (0x10148) +#define MDP_FULL_BYPASS_WORD19 (0x1014c) +#define MDP_FULL_BYPASS_WORD20 (0x10150) +#define MDP_PPP_DESTINATION_CONFIG (0x10150) +#define MDP_FULL_BYPASS_WORD21 (0x10154) +#define MDP_FULL_BYPASS_WORD22 (0x10158) +#define MDP_FULL_BYPASS_WORD23 (0x1015c) +#define MDP_FULL_BYPASS_WORD24 (0x10160) +#define MDP_FULL_BYPASS_WORD25 (0x10164) +#define MDP_FULL_BYPASS_WORD26 (0x10168) +#define MDP_FULL_BYPASS_WORD27 (0x1016c) +#define MDP_FULL_BYPASS_WORD29 (0x10174) +#define MDP_FULL_BYPASS_WORD30 (0x10178) +#define MDP_FULL_BYPASS_WORD31 (0x1017c) +#define MDP_FULL_BYPASS_WORD32 (0x10180) +#define MDP_DMA_CONFIG (0x10180) +#define MDP_FULL_BYPASS_WORD33 (0x10184) +#define MDP_FULL_BYPASS_WORD34 (0x10188) +#define MDP_FULL_BYPASS_WORD35 (0x1018c) +#define MDP_FULL_BYPASS_WORD37 (0x10194) +#define MDP_FULL_BYPASS_WORD39 (0x1019c) +#define MDP_FULL_BYPASS_WORD40 (0x101a0) +#define MDP_FULL_BYPASS_WORD41 (0x101a4) +#define MDP_FULL_BYPASS_WORD43 (0x101ac) +#define MDP_FULL_BYPASS_WORD46 (0x101b8) +#define MDP_FULL_BYPASS_WORD47 (0x101bc) +#define MDP_FULL_BYPASS_WORD48 (0x101c0) +#define MDP_FULL_BYPASS_WORD49 (0x101c4) +#define MDP_FULL_BYPASS_WORD50 (0x101c8) +#define MDP_FULL_BYPASS_WORD51 (0x101cc) +#define MDP_FULL_BYPASS_WORD52 (0x101d0) +#define MDP_FULL_BYPASS_WORD53 (0x101d4) +#define MDP_FULL_BYPASS_WORD54 (0x101d8) +#define MDP_FULL_BYPASS_WORD55 (0x101dc) +#define MDP_FULL_BYPASS_WORD56 (0x101e0) +#define MDP_FULL_BYPASS_WORD57 (0x101e4) +#define MDP_FULL_BYPASS_WORD58 (0x101e8) +#define MDP_FULL_BYPASS_WORD59 (0x101ec) +#define MDP_FULL_BYPASS_WORD60 (0x101f0) +#define MDP_VSYNC_THRESHOLD (0x101f0) +#define MDP_FULL_BYPASS_WORD61 (0x101f4) +#define MDP_FULL_BYPASS_WORD62 (0x101f8) +#define MDP_FULL_BYPASS_WORD63 (0x101fc) +#define MDP_TFETCH_TEST_MODE (0x20004) +#define MDP_TFETCH_STATUS (0x20008) +#define MDP_TFETCH_TILE_COUNT (0x20010) +#define MDP_TFETCH_FETCH_COUNT (0x20014) +#define MDP_TFETCH_CONSTANT_COLOR (0x20040) +#define MDP_CSC_BYPASS (0x40004) +#define MDP_SCALE_COEFF_LSB (0x5fffc) +#define MDP_TV_OUT_CTL (0xc0000) +#define MDP_TV_OUT_FIR_COEFF (0xc0004) +#define MDP_TV_OUT_BUF_ADDR (0xc0008) +#define MDP_TV_OUT_CC_DATA (0xc000c) +#define MDP_TV_OUT_SOBEL (0xc0010) +#define MDP_TV_OUT_Y_CLAMP (0xc0018) +#define MDP_TV_OUT_CB_CLAMP (0xc001c) +#define MDP_TV_OUT_CR_CLAMP (0xc0020) +#define MDP_TEST_MODE_CLK (0xd0000) +#define MDP_TEST_MISR_RESET_CLK (0xd0004) +#define MDP_TEST_EXPORT_MISR_CLK (0xd0008) +#define MDP_TEST_MISR_CURR_VAL_CLK (0xd000c) +#define MDP_TEST_MODE_HCLK (0xd0100) +#define MDP_TEST_MISR_RESET_HCLK (0xd0104) +#define MDP_TEST_EXPORT_MISR_HCLK (0xd0108) +#define MDP_TEST_MISR_CURR_VAL_HCLK (0xd010c) +#define MDP_TEST_MODE_DCLK (0xd0200) +#define MDP_TEST_MISR_RESET_DCLK (0xd0204) +#define MDP_TEST_EXPORT_MISR_DCLK (0xd0208) +#define MDP_TEST_MISR_CURR_VAL_DCLK (0xd020c) +#define MDP_TEST_CAPTURED_DCLK (0xd0210) +#define MDP_TEST_MISR_CAPT_VAL_DCLK (0xd0214) +#define MDP_LCDC_CTL (0xe0000) +#define MDP_LCDC_HSYNC_CTL (0xe0004) +#define MDP_LCDC_VSYNC_CTL (0xe0008) +#define MDP_LCDC_ACTIVE_HCTL (0xe000c) +#define MDP_LCDC_ACTIVE_VCTL (0xe0010) +#define MDP_LCDC_BORDER_CLR (0xe0014) +#define MDP_LCDC_H_BLANK (0xe0018) +#define MDP_LCDC_V_BLANK (0xe001c) +#define MDP_LCDC_UNDERFLOW_CLR (0xe0020) +#define MDP_LCDC_HSYNC_SKEW (0xe0024) +#define MDP_LCDC_TEST_CTL (0xe0028) +#define MDP_LCDC_LINE_IRQ (0xe002c) +#define MDP_LCDC_CTL_POLARITY (0xe0030) +#define MDP_LCDC_DMA_CONFIG (0xe1000) +#define MDP_LCDC_DMA_SIZE (0xe1004) +#define MDP_LCDC_DMA_IBUF_ADDR (0xe1008) +#define MDP_LCDC_DMA_IBUF_Y_STRIDE (0xe100c) + + +#define MDP_DMA2_TERM 0x1 +#define MDP_DMA3_TERM 0x2 +#define MDP_PPP_TERM 0x3 + +/* MDP_INTR_ENABLE */ +#define DL0_ROI_DONE (1<<0) +#define DL1_ROI_DONE (1<<1) +#define DL0_DMA2_TERM_DONE (1<<2) +#define DL1_DMA2_TERM_DONE (1<<3) +#define DL0_PPP_TERM_DONE (1<<4) +#define DL1_PPP_TERM_DONE (1<<5) +#define TV_OUT_DMA3_DONE (1<<6) +#define TV_ENC_UNDERRUN (1<<7) +#define DL0_FETCH_DONE (1<<11) +#define DL1_FETCH_DONE (1<<12) + +#define MDP_PPP_BUSY_STATUS (DL0_ROI_DONE| \ + DL1_ROI_DONE| \ + DL0_PPP_TERM_DONE| \ + DL1_PPP_TERM_DONE) + +#define MDP_ANY_INTR_MASK (DL0_ROI_DONE| \ + DL1_ROI_DONE| \ + DL0_DMA2_TERM_DONE| \ + DL1_DMA2_TERM_DONE| \ + DL0_PPP_TERM_DONE| \ + DL1_PPP_TERM_DONE| \ + DL0_FETCH_DONE| \ + DL1_FETCH_DONE| \ + TV_ENC_UNDERRUN) + +#define MDP_TOP_LUMA 16 +#define MDP_TOP_CHROMA 0 +#define MDP_BOTTOM_LUMA 19 +#define MDP_BOTTOM_CHROMA 3 +#define MDP_LEFT_LUMA 22 +#define MDP_LEFT_CHROMA 6 +#define MDP_RIGHT_LUMA 25 +#define MDP_RIGHT_CHROMA 9 + +#define CLR_G 0x0 +#define CLR_B 0x1 +#define CLR_R 0x2 +#define CLR_ALPHA 0x3 + +#define CLR_Y CLR_G +#define CLR_CB CLR_B +#define CLR_CR CLR_R + +/* from lsb to msb */ +#define MDP_GET_PACK_PATTERN(a, x, y, z, bit) \ + (((a)<<(bit*3))|((x)<<(bit*2))|((y)<<bit)|(z)) + +/* MDP_SYNC_CONFIG_0/1/2 */ +#define MDP_SYNCFG_HGT_LOC 22 +#define MDP_SYNCFG_VSYNC_EXT_EN (1<<21) +#define MDP_SYNCFG_VSYNC_INT_EN (1<<20) + +/* MDP_SYNC_THRESH_0 */ +#define MDP_PRIM_BELOW_LOC 0 +#define MDP_PRIM_ABOVE_LOC 8 + +/* MDP_{PRIMARY,SECONDARY,EXTERNAL}_VSYNC_OUT_CRL */ +#define VSYNC_PULSE_EN (1<<31) +#define VSYNC_PULSE_INV (1<<30) + +/* MDP_VSYNC_CTRL */ +#define DISP0_VSYNC_MAP_VSYNC0 0 +#define DISP0_VSYNC_MAP_VSYNC1 (1<<0) +#define DISP0_VSYNC_MAP_VSYNC2 ((1<<0)|(1<<1)) + +#define DISP1_VSYNC_MAP_VSYNC0 0 +#define DISP1_VSYNC_MAP_VSYNC1 (1<<2) +#define DISP1_VSYNC_MAP_VSYNC2 ((1<<2)|(1<<3)) + +#define PRIMARY_LCD_SYNC_EN (1<<4) +#define PRIMARY_LCD_SYNC_DISABLE 0 + +#define SECONDARY_LCD_SYNC_EN (1<<5) +#define SECONDARY_LCD_SYNC_DISABLE 0 + +#define EXTERNAL_LCD_SYNC_EN (1<<6) +#define EXTERNAL_LCD_SYNC_DISABLE 0 + +/* MDP_VSYNC_THRESHOLD / MDP_FULL_BYPASS_WORD60 */ +#define VSYNC_THRESHOLD_ABOVE_LOC 0 +#define VSYNC_THRESHOLD_BELOW_LOC 16 +#define VSYNC_ANTI_TEAR_EN (1<<31) + +/* MDP_COMMAND_CONFIG / MDP_FULL_BYPASS_WORD1 */ +#define MDP_CMD_DBGBUS_EN (1<<0) + +/* MDP_PPP_SOURCE_CONFIG / MDP_FULL_BYPASS_WORD9&53 */ +#define PPP_SRC_C0G_8BIT ((1<<1)|(1<<0)) +#define PPP_SRC_C1B_8BIT ((1<<3)|(1<<2)) +#define PPP_SRC_C2R_8BIT ((1<<5)|(1<<4)) +#define PPP_SRC_C3A_8BIT ((1<<7)|(1<<6)) + +#define PPP_SRC_C0G_6BIT (1<<1) +#define PPP_SRC_C1B_6BIT (1<<3) +#define PPP_SRC_C2R_6BIT (1<<5) + +#define PPP_SRC_C0G_5BIT (1<<0) +#define PPP_SRC_C1B_5BIT (1<<2) +#define PPP_SRC_C2R_5BIT (1<<4) + +#define PPP_SRC_C3ALPHA_EN (1<<8) + +#define PPP_SRC_BPP_1BYTES 0 +#define PPP_SRC_BPP_2BYTES (1<<9) +#define PPP_SRC_BPP_3BYTES (1<<10) +#define PPP_SRC_BPP_4BYTES ((1<<10)|(1<<9)) + +#define PPP_SRC_BPP_ROI_ODD_X (1<<11) +#define PPP_SRC_BPP_ROI_ODD_Y (1<<12) +#define PPP_SRC_INTERLVD_2COMPONENTS (1<<13) +#define PPP_SRC_INTERLVD_3COMPONENTS (1<<14) +#define PPP_SRC_INTERLVD_4COMPONENTS ((1<<14)|(1<<13)) + + +/* RGB666 unpack format +** TIGHT means R6+G6+B6 together +** LOOSE means R6+2 +G6+2+ B6+2 (with MSB) +** or 2+R6 +2+G6 +2+B6 (with LSB) +*/ +#define PPP_SRC_PACK_TIGHT (1<<17) +#define PPP_SRC_PACK_LOOSE 0 +#define PPP_SRC_PACK_ALIGN_LSB 0 +#define PPP_SRC_PACK_ALIGN_MSB (1<<18) + +#define PPP_SRC_PLANE_INTERLVD 0 +#define PPP_SRC_PLANE_PSEUDOPLNR (1<<20) + +#define PPP_SRC_WMV9_MODE (1<<21) + +/* MDP_PPP_OPERATION_CONFIG / MDP_FULL_BYPASS_WORD14 */ +#define PPP_OP_SCALE_X_ON (1<<0) +#define PPP_OP_SCALE_Y_ON (1<<1) + +#define PPP_OP_CONVERT_RGB2YCBCR 0 +#define PPP_OP_CONVERT_YCBCR2RGB (1<<2) +#define PPP_OP_CONVERT_ON (1<<3) + +#define PPP_OP_CONVERT_MATRIX_PRIMARY 0 +#define PPP_OP_CONVERT_MATRIX_SECONDARY (1<<4) + +#define PPP_OP_LUT_C0_ON (1<<5) +#define PPP_OP_LUT_C1_ON (1<<6) +#define PPP_OP_LUT_C2_ON (1<<7) + +/* rotate or blend enable */ +#define PPP_OP_ROT_ON (1<<8) + +#define PPP_OP_ROT_90 (1<<9) +#define PPP_OP_FLIP_LR (1<<10) +#define PPP_OP_FLIP_UD (1<<11) + +#define PPP_OP_BLEND_ON (1<<12) + +#define PPP_OP_BLEND_SRCPIXEL_ALPHA 0 +#define PPP_OP_BLEND_DSTPIXEL_ALPHA (1<<13) +#define PPP_OP_BLEND_CONSTANT_ALPHA (1<<14) +#define PPP_OP_BLEND_SRCPIXEL_TRANSP ((1<<13)|(1<<14)) + +#define PPP_OP_BLEND_ALPHA_BLEND_NORMAL 0 +#define PPP_OP_BLEND_ALPHA_BLEND_REVERSE (1<<15) + +#define PPP_OP_DITHER_EN (1<<16) + +#define PPP_OP_COLOR_SPACE_RGB 0 +#define PPP_OP_COLOR_SPACE_YCBCR (1<<17) + +#define PPP_OP_SRC_CHROMA_RGB 0 +#define PPP_OP_SRC_CHROMA_H2V1 (1<<18) +#define PPP_OP_SRC_CHROMA_H1V2 (1<<19) +#define PPP_OP_SRC_CHROMA_420 ((1<<18)|(1<<19)) +#define PPP_OP_SRC_CHROMA_COSITE 0 +#define PPP_OP_SRC_CHROMA_OFFSITE (1<<20) + +#define PPP_OP_DST_CHROMA_RGB 0 +#define PPP_OP_DST_CHROMA_H2V1 (1<<21) +#define PPP_OP_DST_CHROMA_H1V2 (1<<22) +#define PPP_OP_DST_CHROMA_420 ((1<<21)|(1<<22)) +#define PPP_OP_DST_CHROMA_COSITE 0 +#define PPP_OP_DST_CHROMA_OFFSITE (1<<23) + +#define PPP_BLEND_ALPHA_TRANSP (1<<24) + +#define PPP_OP_BG_CHROMA_RGB 0 +#define PPP_OP_BG_CHROMA_H2V1 (1<<25) +#define PPP_OP_BG_CHROMA_H1V2 (1<<26) +#define PPP_OP_BG_CHROMA_420 ((1<<25)|(1<<26)) +#define PPP_OP_BG_CHROMA_SITE_COSITE 0 +#define PPP_OP_BG_CHROMA_SITE_OFFSITE (1<<27) + +/* MDP_PPP_DESTINATION_CONFIG / MDP_FULL_BYPASS_WORD20 */ +#define PPP_DST_C0G_8BIT ((1<<0)|(1<<1)) +#define PPP_DST_C1B_8BIT ((1<<3)|(1<<2)) +#define PPP_DST_C2R_8BIT ((1<<5)|(1<<4)) +#define PPP_DST_C3A_8BIT ((1<<7)|(1<<6)) + +#define PPP_DST_C0G_6BIT (1<<1) +#define PPP_DST_C1B_6BIT (1<<3) +#define PPP_DST_C2R_6BIT (1<<5) + +#define PPP_DST_C0G_5BIT (1<<0) +#define PPP_DST_C1B_5BIT (1<<2) +#define PPP_DST_C2R_5BIT (1<<4) + +#define PPP_DST_C3A_8BIT ((1<<7)|(1<<6)) +#define PPP_DST_C3ALPHA_EN (1<<8) + +#define PPP_DST_INTERLVD_2COMPONENTS (1<<9) +#define PPP_DST_INTERLVD_3COMPONENTS (1<<10) +#define PPP_DST_INTERLVD_4COMPONENTS ((1<<10)|(1<<9)) +#define PPP_DST_INTERLVD_6COMPONENTS ((1<<11)|(1<<9)) + +#define PPP_DST_PACK_LOOSE 0 +#define PPP_DST_PACK_TIGHT (1<<13) +#define PPP_DST_PACK_ALIGN_LSB 0 +#define PPP_DST_PACK_ALIGN_MSB (1<<14) + +#define PPP_DST_OUT_SEL_AXI 0 +#define PPP_DST_OUT_SEL_MDDI (1<<15) + +#define PPP_DST_BPP_2BYTES (1<<16) +#define PPP_DST_BPP_3BYTES (1<<17) +#define PPP_DST_BPP_4BYTES ((1<<17)|(1<<16)) + +#define PPP_DST_PLANE_INTERLVD 0 +#define PPP_DST_PLANE_PLANAR (1<<18) +#define PPP_DST_PLANE_PSEUDOPLNR (1<<19) + +#define PPP_DST_TO_TV (1<<20) + +#define PPP_DST_MDDI_PRIMARY 0 +#define PPP_DST_MDDI_SECONDARY (1<<21) +#define PPP_DST_MDDI_EXTERNAL (1<<22) + +/* image configurations by image type */ +#define PPP_CFG_MDP_RGB_565(dir) (PPP_##dir##_C2R_5BIT | \ + PPP_##dir##_C0G_6BIT | \ + PPP_##dir##_C1B_5BIT | \ + PPP_##dir##_BPP_2BYTES | \ + PPP_##dir##_INTERLVD_3COMPONENTS | \ + PPP_##dir##_PACK_TIGHT | \ + PPP_##dir##_PACK_ALIGN_LSB | \ + PPP_##dir##_PLANE_INTERLVD) + +#define PPP_CFG_MDP_RGB_888(dir) (PPP_##dir##_C2R_8BIT | \ + PPP_##dir##_C0G_8BIT | \ + PPP_##dir##_C1B_8BIT | \ + PPP_##dir##_BPP_3BYTES | \ + PPP_##dir##_INTERLVD_3COMPONENTS | \ + PPP_##dir##_PACK_TIGHT | \ + PPP_##dir##_PACK_ALIGN_LSB | \ + PPP_##dir##_PLANE_INTERLVD) + +#define PPP_CFG_MDP_ARGB_8888(dir) (PPP_##dir##_C2R_8BIT | \ + PPP_##dir##_C0G_8BIT | \ + PPP_##dir##_C1B_8BIT | \ + PPP_##dir##_C3A_8BIT | \ + PPP_##dir##_C3ALPHA_EN | \ + PPP_##dir##_BPP_4BYTES | \ + PPP_##dir##_INTERLVD_4COMPONENTS | \ + PPP_##dir##_PACK_TIGHT | \ + PPP_##dir##_PACK_ALIGN_LSB | \ + PPP_##dir##_PLANE_INTERLVD) + +#define PPP_CFG_MDP_XRGB_8888(dir) PPP_CFG_MDP_ARGB_8888(dir) +#define PPP_CFG_MDP_RGBA_8888(dir) PPP_CFG_MDP_ARGB_8888(dir) +#define PPP_CFG_MDP_BGRA_8888(dir) PPP_CFG_MDP_ARGB_8888(dir) +#define PPP_CFG_MDP_RGBX_8888(dir) PPP_CFG_MDP_ARGB_8888(dir) + +#define PPP_CFG_MDP_Y_CBCR_H2V2(dir) (PPP_##dir##_C2R_8BIT | \ + PPP_##dir##_C0G_8BIT | \ + PPP_##dir##_C1B_8BIT | \ + PPP_##dir##_C3A_8BIT | \ + PPP_##dir##_BPP_2BYTES | \ + PPP_##dir##_INTERLVD_2COMPONENTS | \ + PPP_##dir##_PACK_TIGHT | \ + PPP_##dir##_PACK_ALIGN_LSB | \ + PPP_##dir##_PLANE_PSEUDOPLNR) + +#define PPP_CFG_MDP_Y_CRCB_H2V2(dir) PPP_CFG_MDP_Y_CBCR_H2V2(dir) + +#define PPP_CFG_MDP_YCRYCB_H2V1(dir) (PPP_##dir##_C2R_8BIT | \ + PPP_##dir##_C0G_8BIT | \ + PPP_##dir##_C1B_8BIT | \ + PPP_##dir##_C3A_8BIT | \ + PPP_##dir##_BPP_2BYTES | \ + PPP_##dir##_INTERLVD_4COMPONENTS | \ + PPP_##dir##_PACK_TIGHT | \ + PPP_##dir##_PACK_ALIGN_LSB |\ + PPP_##dir##_PLANE_INTERLVD) + +#define PPP_CFG_MDP_Y_CBCR_H2V1(dir) (PPP_##dir##_C2R_8BIT | \ + PPP_##dir##_C0G_8BIT | \ + PPP_##dir##_C1B_8BIT | \ + PPP_##dir##_C3A_8BIT | \ + PPP_##dir##_BPP_2BYTES | \ + PPP_##dir##_INTERLVD_2COMPONENTS | \ + PPP_##dir##_PACK_TIGHT | \ + PPP_##dir##_PACK_ALIGN_LSB | \ + PPP_##dir##_PLANE_PSEUDOPLNR) + +#define PPP_CFG_MDP_Y_CRCB_H2V1(dir) PPP_CFG_MDP_Y_CBCR_H2V1(dir) + +#define PPP_PACK_PATTERN_MDP_RGB_565 \ + MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8) +#define PPP_PACK_PATTERN_MDP_RGB_888 PPP_PACK_PATTERN_MDP_RGB_565 +#define PPP_PACK_PATTERN_MDP_XRGB_8888 \ + MDP_GET_PACK_PATTERN(CLR_B, CLR_G, CLR_R, CLR_ALPHA, 8) +#define PPP_PACK_PATTERN_MDP_ARGB_8888 PPP_PACK_PATTERN_MDP_XRGB_8888 +#define PPP_PACK_PATTERN_MDP_RGBA_8888 \ + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, CLR_R, 8) +#define PPP_PACK_PATTERN_MDP_BGRA_8888 \ + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, 8) +#define PPP_PACK_PATTERN_MDP_RGBX_8888 \ + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, CLR_R, 8) +#define PPP_PACK_PATTERN_MDP_Y_CBCR_H2V1 \ + MDP_GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8) +#define PPP_PACK_PATTERN_MDP_Y_CBCR_H2V2 PPP_PACK_PATTERN_MDP_Y_CBCR_H2V1 +#define PPP_PACK_PATTERN_MDP_Y_CRCB_H2V1 \ + MDP_GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8) +#define PPP_PACK_PATTERN_MDP_Y_CRCB_H2V2 PPP_PACK_PATTERN_MDP_Y_CRCB_H2V1 +#define PPP_PACK_PATTERN_MDP_YCRYCB_H2V1 \ + MDP_GET_PACK_PATTERN(CLR_Y, CLR_R, CLR_Y, CLR_B, 8) + +#define PPP_CHROMA_SAMP_MDP_RGB_565(dir) PPP_OP_##dir##_CHROMA_RGB +#define PPP_CHROMA_SAMP_MDP_RGB_888(dir) PPP_OP_##dir##_CHROMA_RGB +#define PPP_CHROMA_SAMP_MDP_XRGB_8888(dir) PPP_OP_##dir##_CHROMA_RGB +#define PPP_CHROMA_SAMP_MDP_ARGB_8888(dir) PPP_OP_##dir##_CHROMA_RGB +#define PPP_CHROMA_SAMP_MDP_RGBA_8888(dir) PPP_OP_##dir##_CHROMA_RGB +#define PPP_CHROMA_SAMP_MDP_BGRA_8888(dir) PPP_OP_##dir##_CHROMA_RGB +#define PPP_CHROMA_SAMP_MDP_RGBX_8888(dir) PPP_OP_##dir##_CHROMA_RGB +#define PPP_CHROMA_SAMP_MDP_Y_CBCR_H2V1(dir) PPP_OP_##dir##_CHROMA_H2V1 +#define PPP_CHROMA_SAMP_MDP_Y_CBCR_H2V2(dir) PPP_OP_##dir##_CHROMA_420 +#define PPP_CHROMA_SAMP_MDP_Y_CRCB_H2V1(dir) PPP_OP_##dir##_CHROMA_H2V1 +#define PPP_CHROMA_SAMP_MDP_Y_CRCB_H2V2(dir) PPP_OP_##dir##_CHROMA_420 +#define PPP_CHROMA_SAMP_MDP_YCRYCB_H2V1(dir) PPP_OP_##dir##_CHROMA_H2V1 + +/* Helpful array generation macros */ +#define PPP_ARRAY0(name) \ + [MDP_RGB_565] = PPP_##name##_MDP_RGB_565,\ + [MDP_RGB_888] = PPP_##name##_MDP_RGB_888,\ + [MDP_XRGB_8888] = PPP_##name##_MDP_XRGB_8888,\ + [MDP_ARGB_8888] = PPP_##name##_MDP_ARGB_8888,\ + [MDP_RGBA_8888] = PPP_##name##_MDP_RGBA_8888,\ + [MDP_BGRA_8888] = PPP_##name##_MDP_BGRA_8888,\ + [MDP_RGBX_8888] = PPP_##name##_MDP_RGBX_8888,\ + [MDP_Y_CBCR_H2V1] = PPP_##name##_MDP_Y_CBCR_H2V1,\ + [MDP_Y_CBCR_H2V2] = PPP_##name##_MDP_Y_CBCR_H2V2,\ + [MDP_Y_CRCB_H2V1] = PPP_##name##_MDP_Y_CRCB_H2V1,\ + [MDP_Y_CRCB_H2V2] = PPP_##name##_MDP_Y_CRCB_H2V2,\ + [MDP_YCRYCB_H2V1] = PPP_##name##_MDP_YCRYCB_H2V1 + +#define PPP_ARRAY1(name, dir) \ + [MDP_RGB_565] = PPP_##name##_MDP_RGB_565(dir),\ + [MDP_RGB_888] = PPP_##name##_MDP_RGB_888(dir),\ + [MDP_XRGB_8888] = PPP_##name##_MDP_XRGB_8888(dir),\ + [MDP_ARGB_8888] = PPP_##name##_MDP_ARGB_8888(dir),\ + [MDP_RGBA_8888] = PPP_##name##_MDP_RGBA_8888(dir),\ + [MDP_BGRA_8888] = PPP_##name##_MDP_BGRA_8888(dir),\ + [MDP_RGBX_8888] = PPP_##name##_MDP_RGBX_8888(dir),\ + [MDP_Y_CBCR_H2V1] = PPP_##name##_MDP_Y_CBCR_H2V1(dir),\ + [MDP_Y_CBCR_H2V2] = PPP_##name##_MDP_Y_CBCR_H2V2(dir),\ + [MDP_Y_CRCB_H2V1] = PPP_##name##_MDP_Y_CRCB_H2V1(dir),\ + [MDP_Y_CRCB_H2V2] = PPP_##name##_MDP_Y_CRCB_H2V2(dir),\ + [MDP_YCRYCB_H2V1] = PPP_##name##_MDP_YCRYCB_H2V1(dir) + +#define IS_YCRCB(img) ((img == MDP_Y_CRCB_H2V2) | (img == MDP_Y_CBCR_H2V2) | \ + (img == MDP_Y_CRCB_H2V1) | (img == MDP_Y_CBCR_H2V1) | \ + (img == MDP_YCRYCB_H2V1)) +#define IS_RGB(img) ((img == MDP_RGB_565) | (img == MDP_RGB_888) | \ + (img == MDP_ARGB_8888) | (img == MDP_RGBA_8888) | \ + (img == MDP_XRGB_8888) | (img == MDP_BGRA_8888) | \ + (img == MDP_RGBX_8888)) +#define HAS_ALPHA(img) ((img == MDP_ARGB_8888) | (img == MDP_RGBA_8888) | \ + (img == MDP_BGRA_8888)) + +#define IS_PSEUDOPLNR(img) ((img == MDP_Y_CRCB_H2V2) | \ + (img == MDP_Y_CBCR_H2V2) | \ + (img == MDP_Y_CRCB_H2V1) | \ + (img == MDP_Y_CBCR_H2V1)) + +/* Mappings from addr to purpose */ +#define PPP_ADDR_SRC_ROI MDP_FULL_BYPASS_WORD2 +#define PPP_ADDR_SRC0 MDP_FULL_BYPASS_WORD3 +#define PPP_ADDR_SRC1 MDP_FULL_BYPASS_WORD4 +#define PPP_ADDR_SRC_YSTRIDE MDP_FULL_BYPASS_WORD7 +#define PPP_ADDR_SRC_CFG MDP_FULL_BYPASS_WORD9 +#define PPP_ADDR_SRC_PACK_PATTERN MDP_FULL_BYPASS_WORD10 +#define PPP_ADDR_OPERATION MDP_FULL_BYPASS_WORD14 +#define PPP_ADDR_PHASEX_INIT MDP_FULL_BYPASS_WORD15 +#define PPP_ADDR_PHASEY_INIT MDP_FULL_BYPASS_WORD16 +#define PPP_ADDR_PHASEX_STEP MDP_FULL_BYPASS_WORD17 +#define PPP_ADDR_PHASEY_STEP MDP_FULL_BYPASS_WORD18 +#define PPP_ADDR_ALPHA_TRANSP MDP_FULL_BYPASS_WORD19 +#define PPP_ADDR_DST_CFG MDP_FULL_BYPASS_WORD20 +#define PPP_ADDR_DST_PACK_PATTERN MDP_FULL_BYPASS_WORD21 +#define PPP_ADDR_DST_ROI MDP_FULL_BYPASS_WORD25 +#define PPP_ADDR_DST0 MDP_FULL_BYPASS_WORD26 +#define PPP_ADDR_DST1 MDP_FULL_BYPASS_WORD27 +#define PPP_ADDR_DST_YSTRIDE MDP_FULL_BYPASS_WORD30 +#define PPP_ADDR_EDGE MDP_FULL_BYPASS_WORD46 +#define PPP_ADDR_BG0 MDP_FULL_BYPASS_WORD48 +#define PPP_ADDR_BG1 MDP_FULL_BYPASS_WORD49 +#define PPP_ADDR_BG_YSTRIDE MDP_FULL_BYPASS_WORD51 +#define PPP_ADDR_BG_CFG MDP_FULL_BYPASS_WORD53 +#define PPP_ADDR_BG_PACK_PATTERN MDP_FULL_BYPASS_WORD54 + +/* MDP_DMA_CONFIG / MDP_FULL_BYPASS_WORD32 */ +#define DMA_DSTC0G_6BITS (1<<1) +#define DMA_DSTC1B_6BITS (1<<3) +#define DMA_DSTC2R_6BITS (1<<5) +#define DMA_DSTC0G_5BITS (1<<0) +#define DMA_DSTC1B_5BITS (1<<2) +#define DMA_DSTC2R_5BITS (1<<4) + +#define DMA_PACK_TIGHT (1<<6) +#define DMA_PACK_LOOSE 0 +#define DMA_PACK_ALIGN_LSB 0 +#define DMA_PACK_ALIGN_MSB (1<<7) +#define DMA_PACK_PATTERN_RGB \ + (MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 2)<<8) + +#define DMA_OUT_SEL_AHB 0 +#define DMA_OUT_SEL_MDDI (1<<14) +#define DMA_AHBM_LCD_SEL_PRIMARY 0 +#define DMA_AHBM_LCD_SEL_SECONDARY (1<<15) +#define DMA_IBUF_C3ALPHA_EN (1<<16) +#define DMA_DITHER_EN (1<<17) + +#define DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY 0 +#define DMA_MDDI_DMAOUT_LCD_SEL_SECONDARY (1<<18) +#define DMA_MDDI_DMAOUT_LCD_SEL_EXTERNAL (1<<19) + +#define DMA_IBUF_FORMAT_RGB565 (1<<20) +#define DMA_IBUF_FORMAT_RGB888_OR_ARGB8888 0 + +#define DMA_IBUF_NONCONTIGUOUS (1<<21) + +/* MDDI REGISTER ? */ +#define MDDI_VDO_PACKET_DESC 0x5666 +#define MDDI_VDO_PACKET_PRIM 0xC3 +#define MDDI_VDO_PACKET_SECD 0xC0 + +#endif diff --git a/drivers/video/fbdev/msm/mdp_ppp.c b/drivers/video/fbdev/msm/mdp_ppp.c new file mode 100644 index 000000000000..be6079cdfbb6 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp_ppp.c @@ -0,0 +1,731 @@ +/* drivers/video/msm/mdp_ppp.c + * + * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ +#include <linux/fb.h> +#include <linux/file.h> +#include <linux/delay.h> +#include <linux/msm_mdp.h> +#include <linux/platform_data/video-msm_fb.h> + +#include "mdp_hw.h" +#include "mdp_scale_tables.h" + +#define DLOG(x...) do {} while (0) + +#define MDP_DOWNSCALE_BLUR (MDP_DOWNSCALE_MAX + 1) +static int downscale_y_table = MDP_DOWNSCALE_MAX; +static int downscale_x_table = MDP_DOWNSCALE_MAX; + +struct mdp_regs { + uint32_t src0; + uint32_t src1; + uint32_t dst0; + uint32_t dst1; + uint32_t src_cfg; + uint32_t dst_cfg; + uint32_t src_pack; + uint32_t dst_pack; + uint32_t src_rect; + uint32_t dst_rect; + uint32_t src_ystride; + uint32_t dst_ystride; + uint32_t op; + uint32_t src_bpp; + uint32_t dst_bpp; + uint32_t edge; + uint32_t phasex_init; + uint32_t phasey_init; + uint32_t phasex_step; + uint32_t phasey_step; +}; + +static uint32_t pack_pattern[] = { + PPP_ARRAY0(PACK_PATTERN) +}; + +static uint32_t src_img_cfg[] = { + PPP_ARRAY1(CFG, SRC) +}; + +static uint32_t dst_img_cfg[] = { + PPP_ARRAY1(CFG, DST) +}; + +static uint32_t bytes_per_pixel[] = { + [MDP_RGB_565] = 2, + [MDP_RGB_888] = 3, + [MDP_XRGB_8888] = 4, + [MDP_ARGB_8888] = 4, + [MDP_RGBA_8888] = 4, + [MDP_BGRA_8888] = 4, + [MDP_RGBX_8888] = 4, + [MDP_Y_CBCR_H2V1] = 1, + [MDP_Y_CBCR_H2V2] = 1, + [MDP_Y_CRCB_H2V1] = 1, + [MDP_Y_CRCB_H2V2] = 1, + [MDP_YCRYCB_H2V1] = 2 +}; + +static uint32_t dst_op_chroma[] = { + PPP_ARRAY1(CHROMA_SAMP, DST) +}; + +static uint32_t src_op_chroma[] = { + PPP_ARRAY1(CHROMA_SAMP, SRC) +}; + +static uint32_t bg_op_chroma[] = { + PPP_ARRAY1(CHROMA_SAMP, BG) +}; + +static void rotate_dst_addr_x(struct mdp_blit_req *req, struct mdp_regs *regs) +{ + regs->dst0 += (req->dst_rect.w - + min((uint32_t)16, req->dst_rect.w)) * regs->dst_bpp; + regs->dst1 += (req->dst_rect.w - + min((uint32_t)16, req->dst_rect.w)) * regs->dst_bpp; +} + +static void rotate_dst_addr_y(struct mdp_blit_req *req, struct mdp_regs *regs) +{ + regs->dst0 += (req->dst_rect.h - + min((uint32_t)16, req->dst_rect.h)) * + regs->dst_ystride; + regs->dst1 += (req->dst_rect.h - + min((uint32_t)16, req->dst_rect.h)) * + regs->dst_ystride; +} + +static void blit_rotate(struct mdp_blit_req *req, + struct mdp_regs *regs) +{ + if (req->flags == MDP_ROT_NOP) + return; + + regs->op |= PPP_OP_ROT_ON; + if ((req->flags & MDP_ROT_90 || req->flags & MDP_FLIP_LR) && + !(req->flags & MDP_ROT_90 && req->flags & MDP_FLIP_LR)) + rotate_dst_addr_x(req, regs); + if (req->flags & MDP_ROT_90) + regs->op |= PPP_OP_ROT_90; + if (req->flags & MDP_FLIP_UD) { + regs->op |= PPP_OP_FLIP_UD; + rotate_dst_addr_y(req, regs); + } + if (req->flags & MDP_FLIP_LR) + regs->op |= PPP_OP_FLIP_LR; +} + +static void blit_convert(struct mdp_blit_req *req, struct mdp_regs *regs) +{ + if (req->src.format == req->dst.format) + return; + if (IS_RGB(req->src.format) && IS_YCRCB(req->dst.format)) { + regs->op |= PPP_OP_CONVERT_RGB2YCBCR | PPP_OP_CONVERT_ON; + } else if (IS_YCRCB(req->src.format) && IS_RGB(req->dst.format)) { + regs->op |= PPP_OP_CONVERT_YCBCR2RGB | PPP_OP_CONVERT_ON; + if (req->dst.format == MDP_RGB_565) + regs->op |= PPP_OP_CONVERT_MATRIX_SECONDARY; + } +} + +#define GET_BIT_RANGE(value, high, low) \ + (((1 << (high - low + 1)) - 1) & (value >> low)) +static uint32_t transp_convert(struct mdp_blit_req *req) +{ + uint32_t transp = 0; + if (req->src.format == MDP_RGB_565) { + /* pad each value to 8 bits by copying the high bits into the + * low end, convert RGB to RBG by switching low 2 components */ + transp |= ((GET_BIT_RANGE(req->transp_mask, 15, 11) << 3) | + (GET_BIT_RANGE(req->transp_mask, 15, 13))) << 16; + + transp |= ((GET_BIT_RANGE(req->transp_mask, 4, 0) << 3) | + (GET_BIT_RANGE(req->transp_mask, 4, 2))) << 8; + + transp |= (GET_BIT_RANGE(req->transp_mask, 10, 5) << 2) | + (GET_BIT_RANGE(req->transp_mask, 10, 9)); + } else { + /* convert RGB to RBG */ + transp |= (GET_BIT_RANGE(req->transp_mask, 15, 8)) | + (GET_BIT_RANGE(req->transp_mask, 23, 16) << 16) | + (GET_BIT_RANGE(req->transp_mask, 7, 0) << 8); + } + return transp; +} +#undef GET_BIT_RANGE + +static void blit_blend(struct mdp_blit_req *req, struct mdp_regs *regs) +{ + /* TRANSP BLEND */ + if (req->transp_mask != MDP_TRANSP_NOP) { + req->transp_mask = transp_convert(req); + if (req->alpha != MDP_ALPHA_NOP) { + /* use blended transparancy mode + * pixel = (src == transp) ? dst : blend + * blend is combo of blend_eq_sel and + * blend_alpha_sel */ + regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | + PPP_OP_BLEND_ALPHA_BLEND_NORMAL | + PPP_OP_BLEND_CONSTANT_ALPHA | + PPP_BLEND_ALPHA_TRANSP; + } else { + /* simple transparancy mode + * pixel = (src == transp) ? dst : src */ + regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | + PPP_OP_BLEND_SRCPIXEL_TRANSP; + } + } + + req->alpha &= 0xff; + /* ALPHA BLEND */ + if (HAS_ALPHA(req->src.format)) { + regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | + PPP_OP_BLEND_SRCPIXEL_ALPHA; + } else if (req->alpha < MDP_ALPHA_NOP) { + /* just blend by alpha */ + regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | + PPP_OP_BLEND_ALPHA_BLEND_NORMAL | + PPP_OP_BLEND_CONSTANT_ALPHA; + } + + regs->op |= bg_op_chroma[req->dst.format]; +} + +#define ONE_HALF (1LL << 32) +#define ONE (1LL << 33) +#define TWO (2LL << 33) +#define THREE (3LL << 33) +#define FRAC_MASK (ONE - 1) +#define INT_MASK (~FRAC_MASK) + +static int scale_params(uint32_t dim_in, uint32_t dim_out, uint32_t origin, + uint32_t *phase_init, uint32_t *phase_step) +{ + /* to improve precicsion calculations are done in U31.33 and converted + * to U3.29 at the end */ + int64_t k1, k2, k3, k4, tmp; + uint64_t n, d, os, os_p, od, od_p, oreq; + unsigned rpa = 0; + int64_t ip64, delta; + + if (dim_out % 3 == 0) + rpa = !(dim_in % (dim_out / 3)); + + n = ((uint64_t)dim_out) << 34; + d = dim_in; + if (!d) + return -1; + do_div(n, d); + k3 = (n + 1) >> 1; + if ((k3 >> 4) < (1LL << 27) || (k3 >> 4) > (1LL << 31)) { + DLOG("crap bad scale\n"); + return -1; + } + n = ((uint64_t)dim_in) << 34; + d = (uint64_t)dim_out; + if (!d) + return -1; + do_div(n, d); + k1 = (n + 1) >> 1; + k2 = (k1 - ONE) >> 1; + + *phase_init = (int)(k2 >> 4); + k4 = (k3 - ONE) >> 1; + + if (rpa) { + os = ((uint64_t)origin << 33) - ONE_HALF; + tmp = (dim_out * os) + ONE_HALF; + if (!dim_in) + return -1; + do_div(tmp, dim_in); + od = tmp - ONE_HALF; + } else { + os = ((uint64_t)origin << 1) - 1; + od = (((k3 * os) >> 1) + k4); + } + + od_p = od & INT_MASK; + if (od_p != od) + od_p += ONE; + + if (rpa) { + tmp = (dim_in * od_p) + ONE_HALF; + if (!dim_in) + return -1; + do_div(tmp, dim_in); + os_p = tmp - ONE_HALF; + } else { + os_p = ((k1 * (od_p >> 33)) + k2); + } + + oreq = (os_p & INT_MASK) - ONE; + + ip64 = os_p - oreq; + delta = ((int64_t)(origin) << 33) - oreq; + ip64 -= delta; + /* limit to valid range before the left shift */ + delta = (ip64 & (1LL << 63)) ? 4 : -4; + delta <<= 33; + while (abs((int)(ip64 >> 33)) > 4) + ip64 += delta; + *phase_init = (int)(ip64 >> 4); + *phase_step = (uint32_t)(k1 >> 4); + return 0; +} + +static void load_scale_table(const struct mdp_info *mdp, + struct mdp_table_entry *table, int len) +{ + int i; + for (i = 0; i < len; i++) + mdp_writel(mdp, table[i].val, table[i].reg); +} + +enum { +IMG_LEFT, +IMG_RIGHT, +IMG_TOP, +IMG_BOTTOM, +}; + +static void get_edge_info(uint32_t src, uint32_t src_coord, uint32_t dst, + uint32_t *interp1, uint32_t *interp2, + uint32_t *repeat1, uint32_t *repeat2) { + if (src > 3 * dst) { + *interp1 = 0; + *interp2 = src - 1; + *repeat1 = 0; + *repeat2 = 0; + } else if (src == 3 * dst) { + *interp1 = 0; + *interp2 = src; + *repeat1 = 0; + *repeat2 = 1; + } else if (src > dst && src < 3 * dst) { + *interp1 = -1; + *interp2 = src; + *repeat1 = 1; + *repeat2 = 1; + } else if (src == dst) { + *interp1 = -1; + *interp2 = src + 1; + *repeat1 = 1; + *repeat2 = 2; + } else { + *interp1 = -2; + *interp2 = src + 1; + *repeat1 = 2; + *repeat2 = 2; + } + *interp1 += src_coord; + *interp2 += src_coord; +} + +static int get_edge_cond(struct mdp_blit_req *req, struct mdp_regs *regs) +{ + int32_t luma_interp[4]; + int32_t luma_repeat[4]; + int32_t chroma_interp[4]; + int32_t chroma_bound[4]; + int32_t chroma_repeat[4]; + uint32_t dst_w, dst_h; + + memset(&luma_interp, 0, sizeof(int32_t) * 4); + memset(&luma_repeat, 0, sizeof(int32_t) * 4); + memset(&chroma_interp, 0, sizeof(int32_t) * 4); + memset(&chroma_bound, 0, sizeof(int32_t) * 4); + memset(&chroma_repeat, 0, sizeof(int32_t) * 4); + regs->edge = 0; + + if (req->flags & MDP_ROT_90) { + dst_w = req->dst_rect.h; + dst_h = req->dst_rect.w; + } else { + dst_w = req->dst_rect.w; + dst_h = req->dst_rect.h; + } + + if (regs->op & (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON)) { + get_edge_info(req->src_rect.h, req->src_rect.y, dst_h, + &luma_interp[IMG_TOP], &luma_interp[IMG_BOTTOM], + &luma_repeat[IMG_TOP], &luma_repeat[IMG_BOTTOM]); + get_edge_info(req->src_rect.w, req->src_rect.x, dst_w, + &luma_interp[IMG_LEFT], &luma_interp[IMG_RIGHT], + &luma_repeat[IMG_LEFT], &luma_repeat[IMG_RIGHT]); + } else { + luma_interp[IMG_LEFT] = req->src_rect.x; + luma_interp[IMG_RIGHT] = req->src_rect.x + req->src_rect.w - 1; + luma_interp[IMG_TOP] = req->src_rect.y; + luma_interp[IMG_BOTTOM] = req->src_rect.y + req->src_rect.h - 1; + luma_repeat[IMG_LEFT] = 0; + luma_repeat[IMG_TOP] = 0; + luma_repeat[IMG_RIGHT] = 0; + luma_repeat[IMG_BOTTOM] = 0; + } + + chroma_interp[IMG_LEFT] = luma_interp[IMG_LEFT]; + chroma_interp[IMG_RIGHT] = luma_interp[IMG_RIGHT]; + chroma_interp[IMG_TOP] = luma_interp[IMG_TOP]; + chroma_interp[IMG_BOTTOM] = luma_interp[IMG_BOTTOM]; + + chroma_bound[IMG_LEFT] = req->src_rect.x; + chroma_bound[IMG_RIGHT] = req->src_rect.x + req->src_rect.w - 1; + chroma_bound[IMG_TOP] = req->src_rect.y; + chroma_bound[IMG_BOTTOM] = req->src_rect.y + req->src_rect.h - 1; + + if (IS_YCRCB(req->src.format)) { + chroma_interp[IMG_LEFT] = chroma_interp[IMG_LEFT] >> 1; + chroma_interp[IMG_RIGHT] = (chroma_interp[IMG_RIGHT] + 1) >> 1; + + chroma_bound[IMG_LEFT] = chroma_bound[IMG_LEFT] >> 1; + chroma_bound[IMG_RIGHT] = chroma_bound[IMG_RIGHT] >> 1; + } + + if (req->src.format == MDP_Y_CBCR_H2V2 || + req->src.format == MDP_Y_CRCB_H2V2) { + chroma_interp[IMG_TOP] = (chroma_interp[IMG_TOP] - 1) >> 1; + chroma_interp[IMG_BOTTOM] = (chroma_interp[IMG_BOTTOM] + 1) + >> 1; + chroma_bound[IMG_TOP] = (chroma_bound[IMG_TOP] + 1) >> 1; + chroma_bound[IMG_BOTTOM] = chroma_bound[IMG_BOTTOM] >> 1; + } + + chroma_repeat[IMG_LEFT] = chroma_bound[IMG_LEFT] - + chroma_interp[IMG_LEFT]; + chroma_repeat[IMG_RIGHT] = chroma_interp[IMG_RIGHT] - + chroma_bound[IMG_RIGHT]; + chroma_repeat[IMG_TOP] = chroma_bound[IMG_TOP] - + chroma_interp[IMG_TOP]; + chroma_repeat[IMG_BOTTOM] = chroma_interp[IMG_BOTTOM] - + chroma_bound[IMG_BOTTOM]; + + if (chroma_repeat[IMG_LEFT] < 0 || chroma_repeat[IMG_LEFT] > 3 || + chroma_repeat[IMG_RIGHT] < 0 || chroma_repeat[IMG_RIGHT] > 3 || + chroma_repeat[IMG_TOP] < 0 || chroma_repeat[IMG_TOP] > 3 || + chroma_repeat[IMG_BOTTOM] < 0 || chroma_repeat[IMG_BOTTOM] > 3 || + luma_repeat[IMG_LEFT] < 0 || luma_repeat[IMG_LEFT] > 3 || + luma_repeat[IMG_RIGHT] < 0 || luma_repeat[IMG_RIGHT] > 3 || + luma_repeat[IMG_TOP] < 0 || luma_repeat[IMG_TOP] > 3 || + luma_repeat[IMG_BOTTOM] < 0 || luma_repeat[IMG_BOTTOM] > 3) + return -1; + + regs->edge |= (chroma_repeat[IMG_LEFT] & 3) << MDP_LEFT_CHROMA; + regs->edge |= (chroma_repeat[IMG_RIGHT] & 3) << MDP_RIGHT_CHROMA; + regs->edge |= (chroma_repeat[IMG_TOP] & 3) << MDP_TOP_CHROMA; + regs->edge |= (chroma_repeat[IMG_BOTTOM] & 3) << MDP_BOTTOM_CHROMA; + regs->edge |= (luma_repeat[IMG_LEFT] & 3) << MDP_LEFT_LUMA; + regs->edge |= (luma_repeat[IMG_RIGHT] & 3) << MDP_RIGHT_LUMA; + regs->edge |= (luma_repeat[IMG_TOP] & 3) << MDP_TOP_LUMA; + regs->edge |= (luma_repeat[IMG_BOTTOM] & 3) << MDP_BOTTOM_LUMA; + return 0; +} + +static int blit_scale(const struct mdp_info *mdp, struct mdp_blit_req *req, + struct mdp_regs *regs) +{ + uint32_t phase_init_x, phase_init_y, phase_step_x, phase_step_y; + uint32_t scale_factor_x, scale_factor_y; + uint32_t downscale; + uint32_t dst_w, dst_h; + + if (req->flags & MDP_ROT_90) { + dst_w = req->dst_rect.h; + dst_h = req->dst_rect.w; + } else { + dst_w = req->dst_rect.w; + dst_h = req->dst_rect.h; + } + if ((req->src_rect.w == dst_w) && (req->src_rect.h == dst_h) && + !(req->flags & MDP_BLUR)) { + regs->phasex_init = 0; + regs->phasey_init = 0; + regs->phasex_step = 0; + regs->phasey_step = 0; + return 0; + } + + if (scale_params(req->src_rect.w, dst_w, 1, &phase_init_x, + &phase_step_x) || + scale_params(req->src_rect.h, dst_h, 1, &phase_init_y, + &phase_step_y)) + return -1; + + scale_factor_x = (dst_w * 10) / req->src_rect.w; + scale_factor_y = (dst_h * 10) / req->src_rect.h; + + if (scale_factor_x > 8) + downscale = MDP_DOWNSCALE_PT8TO1; + else if (scale_factor_x > 6) + downscale = MDP_DOWNSCALE_PT6TOPT8; + else if (scale_factor_x > 4) + downscale = MDP_DOWNSCALE_PT4TOPT6; + else + downscale = MDP_DOWNSCALE_PT2TOPT4; + if (downscale != downscale_x_table) { + load_scale_table(mdp, mdp_downscale_x_table[downscale], 64); + downscale_x_table = downscale; + } + + if (scale_factor_y > 8) + downscale = MDP_DOWNSCALE_PT8TO1; + else if (scale_factor_y > 6) + downscale = MDP_DOWNSCALE_PT6TOPT8; + else if (scale_factor_y > 4) + downscale = MDP_DOWNSCALE_PT4TOPT6; + else + downscale = MDP_DOWNSCALE_PT2TOPT4; + if (downscale != downscale_y_table) { + load_scale_table(mdp, mdp_downscale_y_table[downscale], 64); + downscale_y_table = downscale; + } + + regs->phasex_init = phase_init_x; + regs->phasey_init = phase_init_y; + regs->phasex_step = phase_step_x; + regs->phasey_step = phase_step_y; + regs->op |= (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON); + return 0; + +} + +static void blit_blur(const struct mdp_info *mdp, struct mdp_blit_req *req, + struct mdp_regs *regs) +{ + if (!(req->flags & MDP_BLUR)) + return; + + if (!(downscale_x_table == MDP_DOWNSCALE_BLUR && + downscale_y_table == MDP_DOWNSCALE_BLUR)) { + load_scale_table(mdp, mdp_gaussian_blur_table, 128); + downscale_x_table = MDP_DOWNSCALE_BLUR; + downscale_y_table = MDP_DOWNSCALE_BLUR; + } + + regs->op |= (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON); +} + + +#define IMG_LEN(rect_h, w, rect_w, bpp) (((rect_h) * w) * bpp) + +#define Y_TO_CRCB_RATIO(format) \ + ((format == MDP_Y_CBCR_H2V2 || format == MDP_Y_CRCB_H2V2) ? 2 :\ + (format == MDP_Y_CBCR_H2V1 || format == MDP_Y_CRCB_H2V1) ? 1 : 1) + +static void get_len(struct mdp_img *img, struct mdp_rect *rect, uint32_t bpp, + uint32_t *len0, uint32_t *len1) +{ + *len0 = IMG_LEN(rect->h, img->width, rect->w, bpp); + if (IS_PSEUDOPLNR(img->format)) + *len1 = *len0/Y_TO_CRCB_RATIO(img->format); + else + *len1 = 0; +} + +static int valid_src_dst(unsigned long src_start, unsigned long src_len, + unsigned long dst_start, unsigned long dst_len, + struct mdp_blit_req *req, struct mdp_regs *regs) +{ + unsigned long src_min_ok = src_start; + unsigned long src_max_ok = src_start + src_len; + unsigned long dst_min_ok = dst_start; + unsigned long dst_max_ok = dst_start + dst_len; + uint32_t src0_len, src1_len, dst0_len, dst1_len; + get_len(&req->src, &req->src_rect, regs->src_bpp, &src0_len, + &src1_len); + get_len(&req->dst, &req->dst_rect, regs->dst_bpp, &dst0_len, + &dst1_len); + + if (regs->src0 < src_min_ok || regs->src0 > src_max_ok || + regs->src0 + src0_len > src_max_ok) { + DLOG("invalid_src %x %x %lx %lx\n", regs->src0, + src0_len, src_min_ok, src_max_ok); + return 0; + } + if (regs->src_cfg & PPP_SRC_PLANE_PSEUDOPLNR) { + if (regs->src1 < src_min_ok || regs->src1 > src_max_ok || + regs->src1 + src1_len > src_max_ok) { + DLOG("invalid_src1"); + return 0; + } + } + if (regs->dst0 < dst_min_ok || regs->dst0 > dst_max_ok || + regs->dst0 + dst0_len > dst_max_ok) { + DLOG("invalid_dst"); + return 0; + } + if (regs->dst_cfg & PPP_SRC_PLANE_PSEUDOPLNR) { + if (regs->dst1 < dst_min_ok || regs->dst1 > dst_max_ok || + regs->dst1 + dst1_len > dst_max_ok) { + DLOG("invalid_dst1"); + return 0; + } + } + return 1; +} + + +static void flush_imgs(struct mdp_blit_req *req, struct mdp_regs *regs, + struct file *src_file, struct file *dst_file) +{ +} + +static void get_chroma_addr(struct mdp_img *img, struct mdp_rect *rect, + uint32_t base, uint32_t bpp, uint32_t cfg, + uint32_t *addr, uint32_t *ystride) +{ + uint32_t compress_v = Y_TO_CRCB_RATIO(img->format); + uint32_t compress_h = 2; + uint32_t offset; + + if (IS_PSEUDOPLNR(img->format)) { + offset = (rect->x / compress_h) * compress_h; + offset += rect->y == 0 ? 0 : + ((rect->y + 1) / compress_v) * img->width; + *addr = base + (img->width * img->height * bpp); + *addr += offset * bpp; + *ystride |= *ystride << 16; + } else { + *addr = 0; + } +} + +static int send_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, + struct mdp_regs *regs, struct file *src_file, + struct file *dst_file) +{ + mdp_writel(mdp, 1, 0x060); + mdp_writel(mdp, regs->src_rect, PPP_ADDR_SRC_ROI); + mdp_writel(mdp, regs->src0, PPP_ADDR_SRC0); + mdp_writel(mdp, regs->src1, PPP_ADDR_SRC1); + mdp_writel(mdp, regs->src_ystride, PPP_ADDR_SRC_YSTRIDE); + mdp_writel(mdp, regs->src_cfg, PPP_ADDR_SRC_CFG); + mdp_writel(mdp, regs->src_pack, PPP_ADDR_SRC_PACK_PATTERN); + + mdp_writel(mdp, regs->op, PPP_ADDR_OPERATION); + mdp_writel(mdp, regs->phasex_init, PPP_ADDR_PHASEX_INIT); + mdp_writel(mdp, regs->phasey_init, PPP_ADDR_PHASEY_INIT); + mdp_writel(mdp, regs->phasex_step, PPP_ADDR_PHASEX_STEP); + mdp_writel(mdp, regs->phasey_step, PPP_ADDR_PHASEY_STEP); + + mdp_writel(mdp, (req->alpha << 24) | (req->transp_mask & 0xffffff), + PPP_ADDR_ALPHA_TRANSP); + + mdp_writel(mdp, regs->dst_cfg, PPP_ADDR_DST_CFG); + mdp_writel(mdp, regs->dst_pack, PPP_ADDR_DST_PACK_PATTERN); + mdp_writel(mdp, regs->dst_rect, PPP_ADDR_DST_ROI); + mdp_writel(mdp, regs->dst0, PPP_ADDR_DST0); + mdp_writel(mdp, regs->dst1, PPP_ADDR_DST1); + mdp_writel(mdp, regs->dst_ystride, PPP_ADDR_DST_YSTRIDE); + + mdp_writel(mdp, regs->edge, PPP_ADDR_EDGE); + if (regs->op & PPP_OP_BLEND_ON) { + mdp_writel(mdp, regs->dst0, PPP_ADDR_BG0); + mdp_writel(mdp, regs->dst1, PPP_ADDR_BG1); + mdp_writel(mdp, regs->dst_ystride, PPP_ADDR_BG_YSTRIDE); + mdp_writel(mdp, src_img_cfg[req->dst.format], PPP_ADDR_BG_CFG); + mdp_writel(mdp, pack_pattern[req->dst.format], + PPP_ADDR_BG_PACK_PATTERN); + } + flush_imgs(req, regs, src_file, dst_file); + mdp_writel(mdp, 0x1000, MDP_DISPLAY0_START); + return 0; +} + +int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, + struct file *src_file, unsigned long src_start, unsigned long src_len, + struct file *dst_file, unsigned long dst_start, unsigned long dst_len) +{ + struct mdp_regs regs = {0}; + + if (unlikely(req->src.format >= MDP_IMGTYPE_LIMIT || + req->dst.format >= MDP_IMGTYPE_LIMIT)) { + printk(KERN_ERR "mpd_ppp: img is of wrong format\n"); + return -EINVAL; + } + + if (unlikely(req->src_rect.x > req->src.width || + req->src_rect.y > req->src.height || + req->dst_rect.x > req->dst.width || + req->dst_rect.y > req->dst.height)) { + printk(KERN_ERR "mpd_ppp: img rect is outside of img!\n"); + return -EINVAL; + } + + /* set the src image configuration */ + regs.src_cfg = src_img_cfg[req->src.format]; + regs.src_cfg |= (req->src_rect.x & 0x1) ? PPP_SRC_BPP_ROI_ODD_X : 0; + regs.src_cfg |= (req->src_rect.y & 0x1) ? PPP_SRC_BPP_ROI_ODD_Y : 0; + regs.src_rect = (req->src_rect.h << 16) | req->src_rect.w; + regs.src_pack = pack_pattern[req->src.format]; + + /* set the dest image configuration */ + regs.dst_cfg = dst_img_cfg[req->dst.format] | PPP_DST_OUT_SEL_AXI; + regs.dst_rect = (req->dst_rect.h << 16) | req->dst_rect.w; + regs.dst_pack = pack_pattern[req->dst.format]; + + /* set src, bpp, start pixel and ystride */ + regs.src_bpp = bytes_per_pixel[req->src.format]; + regs.src0 = src_start + req->src.offset; + regs.src_ystride = req->src.width * regs.src_bpp; + get_chroma_addr(&req->src, &req->src_rect, regs.src0, regs.src_bpp, + regs.src_cfg, ®s.src1, ®s.src_ystride); + regs.src0 += (req->src_rect.x + (req->src_rect.y * req->src.width)) * + regs.src_bpp; + + /* set dst, bpp, start pixel and ystride */ + regs.dst_bpp = bytes_per_pixel[req->dst.format]; + regs.dst0 = dst_start + req->dst.offset; + regs.dst_ystride = req->dst.width * regs.dst_bpp; + get_chroma_addr(&req->dst, &req->dst_rect, regs.dst0, regs.dst_bpp, + regs.dst_cfg, ®s.dst1, ®s.dst_ystride); + regs.dst0 += (req->dst_rect.x + (req->dst_rect.y * req->dst.width)) * + regs.dst_bpp; + + if (!valid_src_dst(src_start, src_len, dst_start, dst_len, req, + ®s)) { + printk(KERN_ERR "mpd_ppp: final src or dst location is " + "invalid, are you trying to make an image too large " + "or to place it outside the screen?\n"); + return -EINVAL; + } + + /* set up operation register */ + regs.op = 0; + blit_rotate(req, ®s); + blit_convert(req, ®s); + if (req->flags & MDP_DITHER) + regs.op |= PPP_OP_DITHER_EN; + blit_blend(req, ®s); + if (blit_scale(mdp, req, ®s)) { + printk(KERN_ERR "mpd_ppp: error computing scale for img.\n"); + return -EINVAL; + } + blit_blur(mdp, req, ®s); + regs.op |= dst_op_chroma[req->dst.format] | + src_op_chroma[req->src.format]; + + /* if the image is YCRYCB, the x and w must be even */ + if (unlikely(req->src.format == MDP_YCRYCB_H2V1)) { + req->src_rect.x = req->src_rect.x & (~0x1); + req->src_rect.w = req->src_rect.w & (~0x1); + req->dst_rect.x = req->dst_rect.x & (~0x1); + req->dst_rect.w = req->dst_rect.w & (~0x1); + } + if (get_edge_cond(req, ®s)) + return -EINVAL; + + send_blit(mdp, req, ®s, src_file, dst_file); + return 0; +} diff --git a/drivers/video/fbdev/msm/mdp_scale_tables.c b/drivers/video/fbdev/msm/mdp_scale_tables.c new file mode 100644 index 000000000000..604783b2e17c --- /dev/null +++ b/drivers/video/fbdev/msm/mdp_scale_tables.c @@ -0,0 +1,766 @@ +/* drivers/video/msm_fb/mdp_scale_tables.c + * + * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include "mdp_scale_tables.h" +#include "mdp_hw.h" + +struct mdp_table_entry mdp_upscale_table[] = { + { 0x5fffc, 0x0 }, + { 0x50200, 0x7fc00000 }, + { 0x5fffc, 0xff80000d }, + { 0x50204, 0x7ec003f9 }, + { 0x5fffc, 0xfec0001c }, + { 0x50208, 0x7d4003f3 }, + { 0x5fffc, 0xfe40002b }, + { 0x5020c, 0x7b8003ed }, + { 0x5fffc, 0xfd80003c }, + { 0x50210, 0x794003e8 }, + { 0x5fffc, 0xfcc0004d }, + { 0x50214, 0x76c003e4 }, + { 0x5fffc, 0xfc40005f }, + { 0x50218, 0x73c003e0 }, + { 0x5fffc, 0xfb800071 }, + { 0x5021c, 0x708003de }, + { 0x5fffc, 0xfac00085 }, + { 0x50220, 0x6d0003db }, + { 0x5fffc, 0xfa000098 }, + { 0x50224, 0x698003d9 }, + { 0x5fffc, 0xf98000ac }, + { 0x50228, 0x654003d8 }, + { 0x5fffc, 0xf8c000c1 }, + { 0x5022c, 0x610003d7 }, + { 0x5fffc, 0xf84000d5 }, + { 0x50230, 0x5c8003d7 }, + { 0x5fffc, 0xf7c000e9 }, + { 0x50234, 0x580003d7 }, + { 0x5fffc, 0xf74000fd }, + { 0x50238, 0x534003d8 }, + { 0x5fffc, 0xf6c00112 }, + { 0x5023c, 0x4e8003d8 }, + { 0x5fffc, 0xf6800126 }, + { 0x50240, 0x494003da }, + { 0x5fffc, 0xf600013a }, + { 0x50244, 0x448003db }, + { 0x5fffc, 0xf600014d }, + { 0x50248, 0x3f4003dd }, + { 0x5fffc, 0xf5c00160 }, + { 0x5024c, 0x3a4003df }, + { 0x5fffc, 0xf5c00172 }, + { 0x50250, 0x354003e1 }, + { 0x5fffc, 0xf5c00184 }, + { 0x50254, 0x304003e3 }, + { 0x5fffc, 0xf6000195 }, + { 0x50258, 0x2b0003e6 }, + { 0x5fffc, 0xf64001a6 }, + { 0x5025c, 0x260003e8 }, + { 0x5fffc, 0xf6c001b4 }, + { 0x50260, 0x214003eb }, + { 0x5fffc, 0xf78001c2 }, + { 0x50264, 0x1c4003ee }, + { 0x5fffc, 0xf80001cf }, + { 0x50268, 0x17c003f1 }, + { 0x5fffc, 0xf90001db }, + { 0x5026c, 0x134003f3 }, + { 0x5fffc, 0xfa0001e5 }, + { 0x50270, 0xf0003f6 }, + { 0x5fffc, 0xfb4001ee }, + { 0x50274, 0xac003f9 }, + { 0x5fffc, 0xfcc001f5 }, + { 0x50278, 0x70003fb }, + { 0x5fffc, 0xfe4001fb }, + { 0x5027c, 0x34003fe }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT2TOPT4[] = { + { 0x5fffc, 0x740008c }, + { 0x50280, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50284, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50288, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5028c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50290, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50294, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50298, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5029c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x502a0, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x502a4, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x502a8, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x502ac, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x502b0, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x502b4, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x502b8, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x502bc, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x502c0, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x502c4, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x502c8, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x502cc, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x502d0, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x502d4, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x502d8, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x502dc, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x502e0, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x502e4, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x502e8, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x502ec, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x502f0, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x502f4, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x502f8, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x502fc, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT4TOPT6[] = { + { 0x5fffc, 0x740008c }, + { 0x50280, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50284, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50288, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5028c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50290, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50294, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50298, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5029c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x502a0, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x502a4, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x502a8, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x502ac, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x502b0, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x502b4, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x502b8, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x502bc, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x502c0, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x502c4, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x502c8, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x502cc, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x502d0, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x502d4, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x502d8, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x502dc, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x502e0, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x502e4, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x502e8, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x502ec, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x502f0, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x502f4, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x502f8, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x502fc, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT6TOPT8[] = { + { 0x5fffc, 0xfe000070 }, + { 0x50280, 0x4bc00068 }, + { 0x5fffc, 0xfe000078 }, + { 0x50284, 0x4bc00060 }, + { 0x5fffc, 0xfe000080 }, + { 0x50288, 0x4b800059 }, + { 0x5fffc, 0xfe000089 }, + { 0x5028c, 0x4b000052 }, + { 0x5fffc, 0xfe400091 }, + { 0x50290, 0x4a80004b }, + { 0x5fffc, 0xfe40009a }, + { 0x50294, 0x4a000044 }, + { 0x5fffc, 0xfe8000a3 }, + { 0x50298, 0x4940003d }, + { 0x5fffc, 0xfec000ac }, + { 0x5029c, 0x48400037 }, + { 0x5fffc, 0xff0000b4 }, + { 0x502a0, 0x47800031 }, + { 0x5fffc, 0xff8000bd }, + { 0x502a4, 0x4640002b }, + { 0x5fffc, 0xc5 }, + { 0x502a8, 0x45000026 }, + { 0x5fffc, 0x8000ce }, + { 0x502ac, 0x43800021 }, + { 0x5fffc, 0x10000d6 }, + { 0x502b0, 0x4240001c }, + { 0x5fffc, 0x18000df }, + { 0x502b4, 0x40800018 }, + { 0x5fffc, 0x24000e6 }, + { 0x502b8, 0x3f000014 }, + { 0x5fffc, 0x30000ee }, + { 0x502bc, 0x3d400010 }, + { 0x5fffc, 0x40000f5 }, + { 0x502c0, 0x3b80000c }, + { 0x5fffc, 0x50000fc }, + { 0x502c4, 0x39800009 }, + { 0x5fffc, 0x6000102 }, + { 0x502c8, 0x37c00006 }, + { 0x5fffc, 0x7000109 }, + { 0x502cc, 0x35800004 }, + { 0x5fffc, 0x840010e }, + { 0x502d0, 0x33800002 }, + { 0x5fffc, 0x9800114 }, + { 0x502d4, 0x31400000 }, + { 0x5fffc, 0xac00119 }, + { 0x502d8, 0x2f4003fe }, + { 0x5fffc, 0xc40011e }, + { 0x502dc, 0x2d0003fc }, + { 0x5fffc, 0xdc00121 }, + { 0x502e0, 0x2b0003fb }, + { 0x5fffc, 0xf400125 }, + { 0x502e4, 0x28c003fa }, + { 0x5fffc, 0x11000128 }, + { 0x502e8, 0x268003f9 }, + { 0x5fffc, 0x12c0012a }, + { 0x502ec, 0x244003f9 }, + { 0x5fffc, 0x1480012c }, + { 0x502f0, 0x224003f8 }, + { 0x5fffc, 0x1640012e }, + { 0x502f4, 0x200003f8 }, + { 0x5fffc, 0x1800012f }, + { 0x502f8, 0x1e0003f8 }, + { 0x5fffc, 0x1a00012f }, + { 0x502fc, 0x1c0003f8 }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT8TO1[] = { + { 0x5fffc, 0x0 }, + { 0x50280, 0x7fc00000 }, + { 0x5fffc, 0xff80000d }, + { 0x50284, 0x7ec003f9 }, + { 0x5fffc, 0xfec0001c }, + { 0x50288, 0x7d4003f3 }, + { 0x5fffc, 0xfe40002b }, + { 0x5028c, 0x7b8003ed }, + { 0x5fffc, 0xfd80003c }, + { 0x50290, 0x794003e8 }, + { 0x5fffc, 0xfcc0004d }, + { 0x50294, 0x76c003e4 }, + { 0x5fffc, 0xfc40005f }, + { 0x50298, 0x73c003e0 }, + { 0x5fffc, 0xfb800071 }, + { 0x5029c, 0x708003de }, + { 0x5fffc, 0xfac00085 }, + { 0x502a0, 0x6d0003db }, + { 0x5fffc, 0xfa000098 }, + { 0x502a4, 0x698003d9 }, + { 0x5fffc, 0xf98000ac }, + { 0x502a8, 0x654003d8 }, + { 0x5fffc, 0xf8c000c1 }, + { 0x502ac, 0x610003d7 }, + { 0x5fffc, 0xf84000d5 }, + { 0x502b0, 0x5c8003d7 }, + { 0x5fffc, 0xf7c000e9 }, + { 0x502b4, 0x580003d7 }, + { 0x5fffc, 0xf74000fd }, + { 0x502b8, 0x534003d8 }, + { 0x5fffc, 0xf6c00112 }, + { 0x502bc, 0x4e8003d8 }, + { 0x5fffc, 0xf6800126 }, + { 0x502c0, 0x494003da }, + { 0x5fffc, 0xf600013a }, + { 0x502c4, 0x448003db }, + { 0x5fffc, 0xf600014d }, + { 0x502c8, 0x3f4003dd }, + { 0x5fffc, 0xf5c00160 }, + { 0x502cc, 0x3a4003df }, + { 0x5fffc, 0xf5c00172 }, + { 0x502d0, 0x354003e1 }, + { 0x5fffc, 0xf5c00184 }, + { 0x502d4, 0x304003e3 }, + { 0x5fffc, 0xf6000195 }, + { 0x502d8, 0x2b0003e6 }, + { 0x5fffc, 0xf64001a6 }, + { 0x502dc, 0x260003e8 }, + { 0x5fffc, 0xf6c001b4 }, + { 0x502e0, 0x214003eb }, + { 0x5fffc, 0xf78001c2 }, + { 0x502e4, 0x1c4003ee }, + { 0x5fffc, 0xf80001cf }, + { 0x502e8, 0x17c003f1 }, + { 0x5fffc, 0xf90001db }, + { 0x502ec, 0x134003f3 }, + { 0x5fffc, 0xfa0001e5 }, + { 0x502f0, 0xf0003f6 }, + { 0x5fffc, 0xfb4001ee }, + { 0x502f4, 0xac003f9 }, + { 0x5fffc, 0xfcc001f5 }, + { 0x502f8, 0x70003fb }, + { 0x5fffc, 0xfe4001fb }, + { 0x502fc, 0x34003fe }, +}; + +struct mdp_table_entry *mdp_downscale_x_table[MDP_DOWNSCALE_MAX] = { + [MDP_DOWNSCALE_PT2TOPT4] = mdp_downscale_x_table_PT2TOPT4, + [MDP_DOWNSCALE_PT4TOPT6] = mdp_downscale_x_table_PT4TOPT6, + [MDP_DOWNSCALE_PT6TOPT8] = mdp_downscale_x_table_PT6TOPT8, + [MDP_DOWNSCALE_PT8TO1] = mdp_downscale_x_table_PT8TO1, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT2TOPT4[] = { + { 0x5fffc, 0x740008c }, + { 0x50300, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50304, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50308, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5030c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50310, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50314, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50318, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5031c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x50320, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x50324, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x50328, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x5032c, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x50330, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x50334, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x50338, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x5033c, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x50340, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x50344, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x50348, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x5034c, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x50350, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x50354, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x50358, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x5035c, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x50360, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x50364, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x50368, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x5036c, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x50370, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x50374, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x50378, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x5037c, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT4TOPT6[] = { + { 0x5fffc, 0x740008c }, + { 0x50300, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50304, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50308, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5030c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50310, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50314, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50318, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5031c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x50320, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x50324, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x50328, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x5032c, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x50330, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x50334, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x50338, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x5033c, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x50340, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x50344, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x50348, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x5034c, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x50350, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x50354, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x50358, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x5035c, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x50360, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x50364, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x50368, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x5036c, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x50370, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x50374, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x50378, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x5037c, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT6TOPT8[] = { + { 0x5fffc, 0xfe000070 }, + { 0x50300, 0x4bc00068 }, + { 0x5fffc, 0xfe000078 }, + { 0x50304, 0x4bc00060 }, + { 0x5fffc, 0xfe000080 }, + { 0x50308, 0x4b800059 }, + { 0x5fffc, 0xfe000089 }, + { 0x5030c, 0x4b000052 }, + { 0x5fffc, 0xfe400091 }, + { 0x50310, 0x4a80004b }, + { 0x5fffc, 0xfe40009a }, + { 0x50314, 0x4a000044 }, + { 0x5fffc, 0xfe8000a3 }, + { 0x50318, 0x4940003d }, + { 0x5fffc, 0xfec000ac }, + { 0x5031c, 0x48400037 }, + { 0x5fffc, 0xff0000b4 }, + { 0x50320, 0x47800031 }, + { 0x5fffc, 0xff8000bd }, + { 0x50324, 0x4640002b }, + { 0x5fffc, 0xc5 }, + { 0x50328, 0x45000026 }, + { 0x5fffc, 0x8000ce }, + { 0x5032c, 0x43800021 }, + { 0x5fffc, 0x10000d6 }, + { 0x50330, 0x4240001c }, + { 0x5fffc, 0x18000df }, + { 0x50334, 0x40800018 }, + { 0x5fffc, 0x24000e6 }, + { 0x50338, 0x3f000014 }, + { 0x5fffc, 0x30000ee }, + { 0x5033c, 0x3d400010 }, + { 0x5fffc, 0x40000f5 }, + { 0x50340, 0x3b80000c }, + { 0x5fffc, 0x50000fc }, + { 0x50344, 0x39800009 }, + { 0x5fffc, 0x6000102 }, + { 0x50348, 0x37c00006 }, + { 0x5fffc, 0x7000109 }, + { 0x5034c, 0x35800004 }, + { 0x5fffc, 0x840010e }, + { 0x50350, 0x33800002 }, + { 0x5fffc, 0x9800114 }, + { 0x50354, 0x31400000 }, + { 0x5fffc, 0xac00119 }, + { 0x50358, 0x2f4003fe }, + { 0x5fffc, 0xc40011e }, + { 0x5035c, 0x2d0003fc }, + { 0x5fffc, 0xdc00121 }, + { 0x50360, 0x2b0003fb }, + { 0x5fffc, 0xf400125 }, + { 0x50364, 0x28c003fa }, + { 0x5fffc, 0x11000128 }, + { 0x50368, 0x268003f9 }, + { 0x5fffc, 0x12c0012a }, + { 0x5036c, 0x244003f9 }, + { 0x5fffc, 0x1480012c }, + { 0x50370, 0x224003f8 }, + { 0x5fffc, 0x1640012e }, + { 0x50374, 0x200003f8 }, + { 0x5fffc, 0x1800012f }, + { 0x50378, 0x1e0003f8 }, + { 0x5fffc, 0x1a00012f }, + { 0x5037c, 0x1c0003f8 }, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT8TO1[] = { + { 0x5fffc, 0x0 }, + { 0x50300, 0x7fc00000 }, + { 0x5fffc, 0xff80000d }, + { 0x50304, 0x7ec003f9 }, + { 0x5fffc, 0xfec0001c }, + { 0x50308, 0x7d4003f3 }, + { 0x5fffc, 0xfe40002b }, + { 0x5030c, 0x7b8003ed }, + { 0x5fffc, 0xfd80003c }, + { 0x50310, 0x794003e8 }, + { 0x5fffc, 0xfcc0004d }, + { 0x50314, 0x76c003e4 }, + { 0x5fffc, 0xfc40005f }, + { 0x50318, 0x73c003e0 }, + { 0x5fffc, 0xfb800071 }, + { 0x5031c, 0x708003de }, + { 0x5fffc, 0xfac00085 }, + { 0x50320, 0x6d0003db }, + { 0x5fffc, 0xfa000098 }, + { 0x50324, 0x698003d9 }, + { 0x5fffc, 0xf98000ac }, + { 0x50328, 0x654003d8 }, + { 0x5fffc, 0xf8c000c1 }, + { 0x5032c, 0x610003d7 }, + { 0x5fffc, 0xf84000d5 }, + { 0x50330, 0x5c8003d7 }, + { 0x5fffc, 0xf7c000e9 }, + { 0x50334, 0x580003d7 }, + { 0x5fffc, 0xf74000fd }, + { 0x50338, 0x534003d8 }, + { 0x5fffc, 0xf6c00112 }, + { 0x5033c, 0x4e8003d8 }, + { 0x5fffc, 0xf6800126 }, + { 0x50340, 0x494003da }, + { 0x5fffc, 0xf600013a }, + { 0x50344, 0x448003db }, + { 0x5fffc, 0xf600014d }, + { 0x50348, 0x3f4003dd }, + { 0x5fffc, 0xf5c00160 }, + { 0x5034c, 0x3a4003df }, + { 0x5fffc, 0xf5c00172 }, + { 0x50350, 0x354003e1 }, + { 0x5fffc, 0xf5c00184 }, + { 0x50354, 0x304003e3 }, + { 0x5fffc, 0xf6000195 }, + { 0x50358, 0x2b0003e6 }, + { 0x5fffc, 0xf64001a6 }, + { 0x5035c, 0x260003e8 }, + { 0x5fffc, 0xf6c001b4 }, + { 0x50360, 0x214003eb }, + { 0x5fffc, 0xf78001c2 }, + { 0x50364, 0x1c4003ee }, + { 0x5fffc, 0xf80001cf }, + { 0x50368, 0x17c003f1 }, + { 0x5fffc, 0xf90001db }, + { 0x5036c, 0x134003f3 }, + { 0x5fffc, 0xfa0001e5 }, + { 0x50370, 0xf0003f6 }, + { 0x5fffc, 0xfb4001ee }, + { 0x50374, 0xac003f9 }, + { 0x5fffc, 0xfcc001f5 }, + { 0x50378, 0x70003fb }, + { 0x5fffc, 0xfe4001fb }, + { 0x5037c, 0x34003fe }, +}; + +struct mdp_table_entry *mdp_downscale_y_table[MDP_DOWNSCALE_MAX] = { + [MDP_DOWNSCALE_PT2TOPT4] = mdp_downscale_y_table_PT2TOPT4, + [MDP_DOWNSCALE_PT4TOPT6] = mdp_downscale_y_table_PT4TOPT6, + [MDP_DOWNSCALE_PT6TOPT8] = mdp_downscale_y_table_PT6TOPT8, + [MDP_DOWNSCALE_PT8TO1] = mdp_downscale_y_table_PT8TO1, +}; + +struct mdp_table_entry mdp_gaussian_blur_table[] = { + /* max variance */ + { 0x5fffc, 0x20000080 }, + { 0x50280, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50284, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50288, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5028c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50290, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50294, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50298, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5029c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502ac, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502bc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502cc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502dc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502ec, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502fc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50300, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50304, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50308, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5030c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50310, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50314, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50318, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5031c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50320, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50324, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50328, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5032c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50330, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50334, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50338, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5033c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50340, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50344, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50348, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5034c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50350, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50354, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50358, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5035c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50360, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50364, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50368, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5036c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50370, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50374, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50378, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5037c, 0x20000080 }, +}; diff --git a/drivers/video/fbdev/msm/mdp_scale_tables.h b/drivers/video/fbdev/msm/mdp_scale_tables.h new file mode 100644 index 000000000000..34077b1af603 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp_scale_tables.h @@ -0,0 +1,38 @@ +/* drivers/video/msm_fb/mdp_scale_tables.h + * + * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ +#ifndef _MDP_SCALE_TABLES_H_ +#define _MDP_SCALE_TABLES_H_ + +#include <linux/types.h> +struct mdp_table_entry { + uint32_t reg; + uint32_t val; +}; + +extern struct mdp_table_entry mdp_upscale_table[64]; + +enum { + MDP_DOWNSCALE_PT2TOPT4, + MDP_DOWNSCALE_PT4TOPT6, + MDP_DOWNSCALE_PT6TOPT8, + MDP_DOWNSCALE_PT8TO1, + MDP_DOWNSCALE_MAX, +}; + +extern struct mdp_table_entry *mdp_downscale_x_table[MDP_DOWNSCALE_MAX]; +extern struct mdp_table_entry *mdp_downscale_y_table[MDP_DOWNSCALE_MAX]; +extern struct mdp_table_entry mdp_gaussian_blur_table[]; + +#endif diff --git a/drivers/video/fbdev/msm/msm_fb.c b/drivers/video/fbdev/msm/msm_fb.c new file mode 100644 index 000000000000..1374803fbcd9 --- /dev/null +++ b/drivers/video/fbdev/msm/msm_fb.c @@ -0,0 +1,638 @@ +/* drivers/video/msm/msm_fb.c + * + * Core MSM framebuffer driver. + * + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/freezer.h> +#include <linux/wait.h> +#include <linux/msm_mdp.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/platform_data/video-msm_fb.h> +#include <linux/workqueue.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/dma-mapping.h> + +#define PRINT_FPS 0 +#define PRINT_BLIT_TIME 0 + +#define SLEEPING 0x4 +#define UPDATING 0x3 +#define FULL_UPDATE_DONE 0x2 +#define WAKING 0x1 +#define AWAKE 0x0 + +#define NONE 0 +#define SUSPEND_RESUME 0x1 +#define FPS 0x2 +#define BLIT_TIME 0x4 +#define SHOW_UPDATES 0x8 + +#define DLOG(mask, fmt, args...) \ +do { \ + if (msmfb_debug_mask & mask) \ + printk(KERN_INFO "msmfb: "fmt, ##args); \ +} while (0) + +static int msmfb_debug_mask; +module_param_named(msmfb_debug_mask, msmfb_debug_mask, int, + S_IRUGO | S_IWUSR | S_IWGRP); + +struct mdp_device *mdp; + +struct msmfb_info { + struct fb_info *fb; + struct msm_panel_data *panel; + int xres; + int yres; + unsigned output_format; + unsigned yoffset; + unsigned frame_requested; + unsigned frame_done; + int sleeping; + unsigned update_frame; + struct { + int left; + int top; + int eright; /* exclusive */ + int ebottom; /* exclusive */ + } update_info; + char *black; + + spinlock_t update_lock; + struct mutex panel_init_lock; + wait_queue_head_t frame_wq; + struct work_struct resume_work; + struct msmfb_callback dma_callback; + struct msmfb_callback vsync_callback; + struct hrtimer fake_vsync; + ktime_t vsync_request_time; +}; + +static int msmfb_open(struct fb_info *info, int user) +{ + return 0; +} + +static int msmfb_release(struct fb_info *info, int user) +{ + return 0; +} + +/* Called from dma interrupt handler, must not sleep */ +static void msmfb_handle_dma_interrupt(struct msmfb_callback *callback) +{ + unsigned long irq_flags; + struct msmfb_info *msmfb = container_of(callback, struct msmfb_info, + dma_callback); + + spin_lock_irqsave(&msmfb->update_lock, irq_flags); + msmfb->frame_done = msmfb->frame_requested; + if (msmfb->sleeping == UPDATING && + msmfb->frame_done == msmfb->update_frame) { + DLOG(SUSPEND_RESUME, "full update completed\n"); + schedule_work(&msmfb->resume_work); + } + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + wake_up(&msmfb->frame_wq); +} + +static int msmfb_start_dma(struct msmfb_info *msmfb) +{ + uint32_t x, y, w, h; + unsigned addr; + unsigned long irq_flags; + uint32_t yoffset; + s64 time_since_request; + struct msm_panel_data *panel = msmfb->panel; + + spin_lock_irqsave(&msmfb->update_lock, irq_flags); + time_since_request = ktime_to_ns(ktime_sub(ktime_get(), + msmfb->vsync_request_time)); + if (time_since_request > 20 * NSEC_PER_MSEC) { + uint32_t us; + us = do_div(time_since_request, NSEC_PER_MSEC) / NSEC_PER_USEC; + printk(KERN_WARNING "msmfb_start_dma %lld.%03u ms after vsync " + "request\n", time_since_request, us); + } + if (msmfb->frame_done == msmfb->frame_requested) { + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + return -1; + } + if (msmfb->sleeping == SLEEPING) { + DLOG(SUSPEND_RESUME, "tried to start dma while asleep\n"); + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + return -1; + } + x = msmfb->update_info.left; + y = msmfb->update_info.top; + w = msmfb->update_info.eright - x; + h = msmfb->update_info.ebottom - y; + yoffset = msmfb->yoffset; + msmfb->update_info.left = msmfb->xres + 1; + msmfb->update_info.top = msmfb->yres + 1; + msmfb->update_info.eright = 0; + msmfb->update_info.ebottom = 0; + if (unlikely(w > msmfb->xres || h > msmfb->yres || + w == 0 || h == 0)) { + printk(KERN_INFO "invalid update: %d %d %d " + "%d\n", x, y, w, h); + msmfb->frame_done = msmfb->frame_requested; + goto error; + } + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + + addr = ((msmfb->xres * (yoffset + y) + x) * 2); + mdp->dma(mdp, addr + msmfb->fb->fix.smem_start, + msmfb->xres * 2, w, h, x, y, &msmfb->dma_callback, + panel->interface_type); + return 0; +error: + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + /* some clients need to clear their vsync interrupt */ + if (panel->clear_vsync) + panel->clear_vsync(panel); + wake_up(&msmfb->frame_wq); + return 0; +} + +/* Called from esync interrupt handler, must not sleep */ +static void msmfb_handle_vsync_interrupt(struct msmfb_callback *callback) +{ + struct msmfb_info *msmfb = container_of(callback, struct msmfb_info, + vsync_callback); + msmfb_start_dma(msmfb); +} + +static enum hrtimer_restart msmfb_fake_vsync(struct hrtimer *timer) +{ + struct msmfb_info *msmfb = container_of(timer, struct msmfb_info, + fake_vsync); + msmfb_start_dma(msmfb); + return HRTIMER_NORESTART; +} + +static void msmfb_pan_update(struct fb_info *info, uint32_t left, uint32_t top, + uint32_t eright, uint32_t ebottom, + uint32_t yoffset, int pan_display) +{ + struct msmfb_info *msmfb = info->par; + struct msm_panel_data *panel = msmfb->panel; + unsigned long irq_flags; + int sleeping; + int retry = 1; + + DLOG(SHOW_UPDATES, "update %d %d %d %d %d %d\n", + left, top, eright, ebottom, yoffset, pan_display); +restart: + spin_lock_irqsave(&msmfb->update_lock, irq_flags); + + /* if we are sleeping, on a pan_display wait 10ms (to throttle back + * drawing otherwise return */ + if (msmfb->sleeping == SLEEPING) { + DLOG(SUSPEND_RESUME, "drawing while asleep\n"); + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + if (pan_display) + wait_event_interruptible_timeout(msmfb->frame_wq, + msmfb->sleeping != SLEEPING, HZ/10); + return; + } + + sleeping = msmfb->sleeping; + /* on a full update, if the last frame has not completed, wait for it */ + if ((pan_display && msmfb->frame_requested != msmfb->frame_done) || + sleeping == UPDATING) { + int ret; + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + ret = wait_event_interruptible_timeout(msmfb->frame_wq, + msmfb->frame_done == msmfb->frame_requested && + msmfb->sleeping != UPDATING, 5 * HZ); + if (ret <= 0 && (msmfb->frame_requested != msmfb->frame_done || + msmfb->sleeping == UPDATING)) { + if (retry && panel->request_vsync && + (sleeping == AWAKE)) { + panel->request_vsync(panel, + &msmfb->vsync_callback); + retry = 0; + printk(KERN_WARNING "msmfb_pan_display timeout " + "rerequest vsync\n"); + } else { + printk(KERN_WARNING "msmfb_pan_display timeout " + "waiting for frame start, %d %d\n", + msmfb->frame_requested, + msmfb->frame_done); + return; + } + } + goto restart; + } + + + msmfb->frame_requested++; + /* if necessary, update the y offset, if this is the + * first full update on resume, set the sleeping state */ + if (pan_display) { + msmfb->yoffset = yoffset; + if (left == 0 && top == 0 && eright == info->var.xres && + ebottom == info->var.yres) { + if (sleeping == WAKING) { + msmfb->update_frame = msmfb->frame_requested; + DLOG(SUSPEND_RESUME, "full update starting\n"); + msmfb->sleeping = UPDATING; + } + } + } + + /* set the update request */ + if (left < msmfb->update_info.left) + msmfb->update_info.left = left; + if (top < msmfb->update_info.top) + msmfb->update_info.top = top; + if (eright > msmfb->update_info.eright) + msmfb->update_info.eright = eright; + if (ebottom > msmfb->update_info.ebottom) + msmfb->update_info.ebottom = ebottom; + DLOG(SHOW_UPDATES, "update queued %d %d %d %d %d\n", + msmfb->update_info.left, msmfb->update_info.top, + msmfb->update_info.eright, msmfb->update_info.ebottom, + msmfb->yoffset); + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + + /* if the panel is all the way on wait for vsync, otherwise sleep + * for 16 ms (long enough for the dma to panel) and then begin dma */ + msmfb->vsync_request_time = ktime_get(); + if (panel->request_vsync && (sleeping == AWAKE)) { + panel->request_vsync(panel, &msmfb->vsync_callback); + } else { + if (!hrtimer_active(&msmfb->fake_vsync)) { + hrtimer_start(&msmfb->fake_vsync, + ktime_set(0, NSEC_PER_SEC/60), + HRTIMER_MODE_REL); + } + } +} + +static void msmfb_update(struct fb_info *info, uint32_t left, uint32_t top, + uint32_t eright, uint32_t ebottom) +{ + msmfb_pan_update(info, left, top, eright, ebottom, 0, 0); +} + +static void power_on_panel(struct work_struct *work) +{ + struct msmfb_info *msmfb = + container_of(work, struct msmfb_info, resume_work); + struct msm_panel_data *panel = msmfb->panel; + unsigned long irq_flags; + + mutex_lock(&msmfb->panel_init_lock); + DLOG(SUSPEND_RESUME, "turning on panel\n"); + if (msmfb->sleeping == UPDATING) { + if (panel->unblank(panel)) { + printk(KERN_INFO "msmfb: panel unblank failed," + "not starting drawing\n"); + goto error; + } + spin_lock_irqsave(&msmfb->update_lock, irq_flags); + msmfb->sleeping = AWAKE; + wake_up(&msmfb->frame_wq); + spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + } +error: + mutex_unlock(&msmfb->panel_init_lock); +} + + +static int msmfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + if ((var->xres != info->var.xres) || + (var->yres != info->var.yres) || + (var->xres_virtual != info->var.xres_virtual) || + (var->yres_virtual != info->var.yres_virtual) || + (var->xoffset != info->var.xoffset) || + (var->bits_per_pixel != info->var.bits_per_pixel) || + (var->grayscale != info->var.grayscale)) + return -EINVAL; + return 0; +} + +int msmfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct msmfb_info *msmfb = info->par; + struct msm_panel_data *panel = msmfb->panel; + + /* "UPDT" */ + if ((panel->caps & MSMFB_CAP_PARTIAL_UPDATES) && + (var->reserved[0] == 0x54445055)) { + msmfb_pan_update(info, var->reserved[1] & 0xffff, + var->reserved[1] >> 16, + var->reserved[2] & 0xffff, + var->reserved[2] >> 16, var->yoffset, 1); + } else { + msmfb_pan_update(info, 0, 0, info->var.xres, info->var.yres, + var->yoffset, 1); + } + return 0; +} + +static void msmfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + cfb_fillrect(p, rect); + msmfb_update(p, rect->dx, rect->dy, rect->dx + rect->width, + rect->dy + rect->height); +} + +static void msmfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + cfb_copyarea(p, area); + msmfb_update(p, area->dx, area->dy, area->dx + area->width, + area->dy + area->height); +} + +static void msmfb_imageblit(struct fb_info *p, const struct fb_image *image) +{ + cfb_imageblit(p, image); + msmfb_update(p, image->dx, image->dy, image->dx + image->width, + image->dy + image->height); +} + + +static int msmfb_blit(struct fb_info *info, + void __user *p) +{ + struct mdp_blit_req req; + struct mdp_blit_req_list req_list; + int i; + int ret; + + if (copy_from_user(&req_list, p, sizeof(req_list))) + return -EFAULT; + + for (i = 0; i < req_list.count; i++) { + struct mdp_blit_req_list *list = + (struct mdp_blit_req_list *)p; + if (copy_from_user(&req, &list->req[i], sizeof(req))) + return -EFAULT; + ret = mdp->blit(mdp, info, &req); + if (ret) + return ret; + } + return 0; +} + + +DEFINE_MUTEX(mdp_ppp_lock); + +static int msmfb_ioctl(struct fb_info *p, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret; + + switch (cmd) { + case MSMFB_GRP_DISP: + mdp->set_grp_disp(mdp, arg); + break; + case MSMFB_BLIT: + ret = msmfb_blit(p, argp); + if (ret) + return ret; + break; + default: + printk(KERN_INFO "msmfb unknown ioctl: %d\n", cmd); + return -EINVAL; + } + return 0; +} + +static struct fb_ops msmfb_ops = { + .owner = THIS_MODULE, + .fb_open = msmfb_open, + .fb_release = msmfb_release, + .fb_check_var = msmfb_check_var, + .fb_pan_display = msmfb_pan_display, + .fb_fillrect = msmfb_fillrect, + .fb_copyarea = msmfb_copyarea, + .fb_imageblit = msmfb_imageblit, + .fb_ioctl = msmfb_ioctl, +}; + +static unsigned PP[16]; + + + +#define BITS_PER_PIXEL 16 + +static void setup_fb_info(struct msmfb_info *msmfb) +{ + struct fb_info *fb_info = msmfb->fb; + int r; + + /* finish setting up the fb_info struct */ + strncpy(fb_info->fix.id, "msmfb", 16); + fb_info->fix.ypanstep = 1; + + fb_info->fbops = &msmfb_ops; + fb_info->flags = FBINFO_DEFAULT; + + fb_info->fix.type = FB_TYPE_PACKED_PIXELS; + fb_info->fix.visual = FB_VISUAL_TRUECOLOR; + fb_info->fix.line_length = msmfb->xres * 2; + + fb_info->var.xres = msmfb->xres; + fb_info->var.yres = msmfb->yres; + fb_info->var.width = msmfb->panel->fb_data->width; + fb_info->var.height = msmfb->panel->fb_data->height; + fb_info->var.xres_virtual = msmfb->xres; + fb_info->var.yres_virtual = msmfb->yres * 2; + fb_info->var.bits_per_pixel = BITS_PER_PIXEL; + fb_info->var.accel_flags = 0; + + fb_info->var.yoffset = 0; + + if (msmfb->panel->caps & MSMFB_CAP_PARTIAL_UPDATES) { + /* + * Set the param in the fixed screen, so userspace can't + * change it. This will be used to check for the + * capability. + */ + fb_info->fix.reserved[0] = 0x5444; + fb_info->fix.reserved[1] = 0x5055; + + /* + * This preloads the value so that if userspace doesn't + * change it, it will be a full update + */ + fb_info->var.reserved[0] = 0x54445055; + fb_info->var.reserved[1] = 0; + fb_info->var.reserved[2] = (uint16_t)msmfb->xres | + ((uint32_t)msmfb->yres << 16); + } + + fb_info->var.red.offset = 11; + fb_info->var.red.length = 5; + fb_info->var.red.msb_right = 0; + fb_info->var.green.offset = 5; + fb_info->var.green.length = 6; + fb_info->var.green.msb_right = 0; + fb_info->var.blue.offset = 0; + fb_info->var.blue.length = 5; + fb_info->var.blue.msb_right = 0; + + r = fb_alloc_cmap(&fb_info->cmap, 16, 0); + fb_info->pseudo_palette = PP; + + PP[0] = 0; + for (r = 1; r < 16; r++) + PP[r] = 0xffffffff; +} + +static int setup_fbmem(struct msmfb_info *msmfb, struct platform_device *pdev) +{ + struct fb_info *fb = msmfb->fb; + struct resource *resource; + unsigned long size = msmfb->xres * msmfb->yres * + (BITS_PER_PIXEL >> 3) * 2; + unsigned char *fbram; + + /* board file might have attached a resource describing an fb */ + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) + return -EINVAL; + + /* check the resource is large enough to fit the fb */ + if (resource->end - resource->start < size) { + printk(KERN_ERR "allocated resource is too small for " + "fb\n"); + return -ENOMEM; + } + fb->fix.smem_start = resource->start; + fb->fix.smem_len = resource_size(resource); + fbram = ioremap(resource->start, resource_size(resource)); + if (fbram == NULL) { + printk(KERN_ERR "msmfb: cannot allocate fbram!\n"); + return -ENOMEM; + } + fb->screen_base = fbram; + return 0; +} + +static int msmfb_probe(struct platform_device *pdev) +{ + struct fb_info *fb; + struct msmfb_info *msmfb; + struct msm_panel_data *panel = pdev->dev.platform_data; + int ret; + + if (!panel) { + pr_err("msmfb_probe: no platform data\n"); + return -EINVAL; + } + if (!panel->fb_data) { + pr_err("msmfb_probe: no fb_data\n"); + return -EINVAL; + } + + fb = framebuffer_alloc(sizeof(struct msmfb_info), &pdev->dev); + if (!fb) + return -ENOMEM; + msmfb = fb->par; + msmfb->fb = fb; + msmfb->panel = panel; + msmfb->xres = panel->fb_data->xres; + msmfb->yres = panel->fb_data->yres; + + ret = setup_fbmem(msmfb, pdev); + if (ret) + goto error_setup_fbmem; + + setup_fb_info(msmfb); + + spin_lock_init(&msmfb->update_lock); + mutex_init(&msmfb->panel_init_lock); + init_waitqueue_head(&msmfb->frame_wq); + INIT_WORK(&msmfb->resume_work, power_on_panel); + msmfb->black = kzalloc(msmfb->fb->var.bits_per_pixel*msmfb->xres, + GFP_KERNEL); + + printk(KERN_INFO "msmfb_probe() installing %d x %d panel\n", + msmfb->xres, msmfb->yres); + + msmfb->dma_callback.func = msmfb_handle_dma_interrupt; + msmfb->vsync_callback.func = msmfb_handle_vsync_interrupt; + hrtimer_init(&msmfb->fake_vsync, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + + msmfb->fake_vsync.function = msmfb_fake_vsync; + + ret = register_framebuffer(fb); + if (ret) + goto error_register_framebuffer; + + msmfb->sleeping = WAKING; + + return 0; + +error_register_framebuffer: + iounmap(fb->screen_base); +error_setup_fbmem: + framebuffer_release(msmfb->fb); + return ret; +} + +static struct platform_driver msm_panel_driver = { + /* need to write remove */ + .probe = msmfb_probe, + .driver = {.name = "msm_panel"}, +}; + + +static int msmfb_add_mdp_device(struct device *dev, + struct class_interface *class_intf) +{ + /* might need locking if mulitple mdp devices */ + if (mdp) + return 0; + mdp = container_of(dev, struct mdp_device, dev); + return platform_driver_register(&msm_panel_driver); +} + +static void msmfb_remove_mdp_device(struct device *dev, + struct class_interface *class_intf) +{ + /* might need locking if mulitple mdp devices */ + if (dev != &mdp->dev) + return; + platform_driver_unregister(&msm_panel_driver); + mdp = NULL; +} + +static struct class_interface msm_fb_interface = { + .add_dev = &msmfb_add_mdp_device, + .remove_dev = &msmfb_remove_mdp_device, +}; + +static int __init msmfb_init(void) +{ + return register_mdp_client(&msm_fb_interface); +} + +module_init(msmfb_init); diff --git a/drivers/video/fbdev/mx3fb.c b/drivers/video/fbdev/mx3fb.c new file mode 100644 index 000000000000..142e860fb527 --- /dev/null +++ b/drivers/video/fbdev/mx3fb.c @@ -0,0 +1,1630 @@ +/* + * Copyright (C) 2008 + * Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.de> + * + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/console.h> +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/dma/ipu-dma.h> + +#include <linux/platform_data/dma-imx.h> +#include <linux/platform_data/video-mx3fb.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#define MX3FB_NAME "mx3_sdc_fb" + +#define MX3FB_REG_OFFSET 0xB4 + +/* SDC Registers */ +#define SDC_COM_CONF (0xB4 - MX3FB_REG_OFFSET) +#define SDC_GW_CTRL (0xB8 - MX3FB_REG_OFFSET) +#define SDC_FG_POS (0xBC - MX3FB_REG_OFFSET) +#define SDC_BG_POS (0xC0 - MX3FB_REG_OFFSET) +#define SDC_CUR_POS (0xC4 - MX3FB_REG_OFFSET) +#define SDC_PWM_CTRL (0xC8 - MX3FB_REG_OFFSET) +#define SDC_CUR_MAP (0xCC - MX3FB_REG_OFFSET) +#define SDC_HOR_CONF (0xD0 - MX3FB_REG_OFFSET) +#define SDC_VER_CONF (0xD4 - MX3FB_REG_OFFSET) +#define SDC_SHARP_CONF_1 (0xD8 - MX3FB_REG_OFFSET) +#define SDC_SHARP_CONF_2 (0xDC - MX3FB_REG_OFFSET) + +/* Register bits */ +#define SDC_COM_TFT_COLOR 0x00000001UL +#define SDC_COM_FG_EN 0x00000010UL +#define SDC_COM_GWSEL 0x00000020UL +#define SDC_COM_GLB_A 0x00000040UL +#define SDC_COM_KEY_COLOR_G 0x00000080UL +#define SDC_COM_BG_EN 0x00000200UL +#define SDC_COM_SHARP 0x00001000UL + +#define SDC_V_SYNC_WIDTH_L 0x00000001UL + +/* Display Interface registers */ +#define DI_DISP_IF_CONF (0x0124 - MX3FB_REG_OFFSET) +#define DI_DISP_SIG_POL (0x0128 - MX3FB_REG_OFFSET) +#define DI_SER_DISP1_CONF (0x012C - MX3FB_REG_OFFSET) +#define DI_SER_DISP2_CONF (0x0130 - MX3FB_REG_OFFSET) +#define DI_HSP_CLK_PER (0x0134 - MX3FB_REG_OFFSET) +#define DI_DISP0_TIME_CONF_1 (0x0138 - MX3FB_REG_OFFSET) +#define DI_DISP0_TIME_CONF_2 (0x013C - MX3FB_REG_OFFSET) +#define DI_DISP0_TIME_CONF_3 (0x0140 - MX3FB_REG_OFFSET) +#define DI_DISP1_TIME_CONF_1 (0x0144 - MX3FB_REG_OFFSET) +#define DI_DISP1_TIME_CONF_2 (0x0148 - MX3FB_REG_OFFSET) +#define DI_DISP1_TIME_CONF_3 (0x014C - MX3FB_REG_OFFSET) +#define DI_DISP2_TIME_CONF_1 (0x0150 - MX3FB_REG_OFFSET) +#define DI_DISP2_TIME_CONF_2 (0x0154 - MX3FB_REG_OFFSET) +#define DI_DISP2_TIME_CONF_3 (0x0158 - MX3FB_REG_OFFSET) +#define DI_DISP3_TIME_CONF (0x015C - MX3FB_REG_OFFSET) +#define DI_DISP0_DB0_MAP (0x0160 - MX3FB_REG_OFFSET) +#define DI_DISP0_DB1_MAP (0x0164 - MX3FB_REG_OFFSET) +#define DI_DISP0_DB2_MAP (0x0168 - MX3FB_REG_OFFSET) +#define DI_DISP0_CB0_MAP (0x016C - MX3FB_REG_OFFSET) +#define DI_DISP0_CB1_MAP (0x0170 - MX3FB_REG_OFFSET) +#define DI_DISP0_CB2_MAP (0x0174 - MX3FB_REG_OFFSET) +#define DI_DISP1_DB0_MAP (0x0178 - MX3FB_REG_OFFSET) +#define DI_DISP1_DB1_MAP (0x017C - MX3FB_REG_OFFSET) +#define DI_DISP1_DB2_MAP (0x0180 - MX3FB_REG_OFFSET) +#define DI_DISP1_CB0_MAP (0x0184 - MX3FB_REG_OFFSET) +#define DI_DISP1_CB1_MAP (0x0188 - MX3FB_REG_OFFSET) +#define DI_DISP1_CB2_MAP (0x018C - MX3FB_REG_OFFSET) +#define DI_DISP2_DB0_MAP (0x0190 - MX3FB_REG_OFFSET) +#define DI_DISP2_DB1_MAP (0x0194 - MX3FB_REG_OFFSET) +#define DI_DISP2_DB2_MAP (0x0198 - MX3FB_REG_OFFSET) +#define DI_DISP2_CB0_MAP (0x019C - MX3FB_REG_OFFSET) +#define DI_DISP2_CB1_MAP (0x01A0 - MX3FB_REG_OFFSET) +#define DI_DISP2_CB2_MAP (0x01A4 - MX3FB_REG_OFFSET) +#define DI_DISP3_B0_MAP (0x01A8 - MX3FB_REG_OFFSET) +#define DI_DISP3_B1_MAP (0x01AC - MX3FB_REG_OFFSET) +#define DI_DISP3_B2_MAP (0x01B0 - MX3FB_REG_OFFSET) +#define DI_DISP_ACC_CC (0x01B4 - MX3FB_REG_OFFSET) +#define DI_DISP_LLA_CONF (0x01B8 - MX3FB_REG_OFFSET) +#define DI_DISP_LLA_DATA (0x01BC - MX3FB_REG_OFFSET) + +/* DI_DISP_SIG_POL bits */ +#define DI_D3_VSYNC_POL_SHIFT 28 +#define DI_D3_HSYNC_POL_SHIFT 27 +#define DI_D3_DRDY_SHARP_POL_SHIFT 26 +#define DI_D3_CLK_POL_SHIFT 25 +#define DI_D3_DATA_POL_SHIFT 24 + +/* DI_DISP_IF_CONF bits */ +#define DI_D3_CLK_IDLE_SHIFT 26 +#define DI_D3_CLK_SEL_SHIFT 25 +#define DI_D3_DATAMSK_SHIFT 24 + +enum ipu_panel { + IPU_PANEL_SHARP_TFT, + IPU_PANEL_TFT, +}; + +struct ipu_di_signal_cfg { + unsigned datamask_en:1; + unsigned clksel_en:1; + unsigned clkidle_en:1; + unsigned data_pol:1; /* true = inverted */ + unsigned clk_pol:1; /* true = rising edge */ + unsigned enable_pol:1; + unsigned Hsync_pol:1; /* true = active high */ + unsigned Vsync_pol:1; +}; + +static const struct fb_videomode mx3fb_modedb[] = { + { + /* 240x320 @ 60 Hz */ + .name = "Sharp-QVGA", + .refresh = 60, + .xres = 240, + .yres = 320, + .pixclock = 185925, + .left_margin = 9, + .right_margin = 16, + .upper_margin = 7, + .lower_margin = 9, + .hsync_len = 1, + .vsync_len = 1, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT | + FB_SYNC_CLK_IDLE_EN, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, + }, { + /* 240x33 @ 60 Hz */ + .name = "Sharp-CLI", + .refresh = 60, + .xres = 240, + .yres = 33, + .pixclock = 185925, + .left_margin = 9, + .right_margin = 16, + .upper_margin = 7, + .lower_margin = 9 + 287, + .hsync_len = 1, + .vsync_len = 1, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT | + FB_SYNC_CLK_IDLE_EN, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, + }, { + /* 640x480 @ 60 Hz */ + .name = "NEC-VGA", + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 38255, + .left_margin = 144, + .right_margin = 0, + .upper_margin = 34, + .lower_margin = 40, + .hsync_len = 1, + .vsync_len = 1, + .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_OE_ACT_HIGH, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, + }, { + /* NTSC TV output */ + .name = "TV-NTSC", + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 37538, + .left_margin = 38, + .right_margin = 858 - 640 - 38 - 3, + .upper_margin = 36, + .lower_margin = 518 - 480 - 36 - 1, + .hsync_len = 3, + .vsync_len = 1, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, + }, { + /* PAL TV output */ + .name = "TV-PAL", + .refresh = 50, + .xres = 640, + .yres = 480, + .pixclock = 37538, + .left_margin = 38, + .right_margin = 960 - 640 - 38 - 32, + .upper_margin = 32, + .lower_margin = 555 - 480 - 32 - 3, + .hsync_len = 32, + .vsync_len = 3, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, + }, { + /* TV output VGA mode, 640x480 @ 65 Hz */ + .name = "TV-VGA", + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 40574, + .left_margin = 35, + .right_margin = 45, + .upper_margin = 9, + .lower_margin = 1, + .hsync_len = 46, + .vsync_len = 5, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, + .flag = 0, + }, +}; + +struct mx3fb_data { + struct fb_info *fbi; + int backlight_level; + void __iomem *reg_base; + spinlock_t lock; + struct device *dev; + + uint32_t h_start_width; + uint32_t v_start_width; + enum disp_data_mapping disp_data_fmt; +}; + +struct dma_chan_request { + struct mx3fb_data *mx3fb; + enum ipu_channel id; +}; + +/* MX3 specific framebuffer information. */ +struct mx3fb_info { + int blank; + enum ipu_channel ipu_ch; + uint32_t cur_ipu_buf; + + u32 pseudo_palette[16]; + + struct completion flip_cmpl; + struct mutex mutex; /* Protects fb-ops */ + struct mx3fb_data *mx3fb; + struct idmac_channel *idmac_channel; + struct dma_async_tx_descriptor *txd; + dma_cookie_t cookie; + struct scatterlist sg[2]; + + struct fb_var_screeninfo cur_var; /* current var info */ +}; + +static void mx3fb_dma_done(void *); + +/* Used fb-mode and bpp. Can be set on kernel command line, therefore file-static. */ +static const char *fb_mode; +static unsigned long default_bpp = 16; + +static u32 mx3fb_read_reg(struct mx3fb_data *mx3fb, unsigned long reg) +{ + return __raw_readl(mx3fb->reg_base + reg); +} + +static void mx3fb_write_reg(struct mx3fb_data *mx3fb, u32 value, unsigned long reg) +{ + __raw_writel(value, mx3fb->reg_base + reg); +} + +struct di_mapping { + uint32_t b0, b1, b2; +}; + +static const struct di_mapping di_mappings[] = { + [IPU_DISP_DATA_MAPPING_RGB666] = { 0x0005000f, 0x000b000f, 0x0011000f }, + [IPU_DISP_DATA_MAPPING_RGB565] = { 0x0004003f, 0x000a000f, 0x000f003f }, + [IPU_DISP_DATA_MAPPING_RGB888] = { 0x00070000, 0x000f0000, 0x00170000 }, +}; + +static void sdc_fb_init(struct mx3fb_info *fbi) +{ + struct mx3fb_data *mx3fb = fbi->mx3fb; + uint32_t reg; + + reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF); + + mx3fb_write_reg(mx3fb, reg | SDC_COM_BG_EN, SDC_COM_CONF); +} + +/* Returns enabled flag before uninit */ +static uint32_t sdc_fb_uninit(struct mx3fb_info *fbi) +{ + struct mx3fb_data *mx3fb = fbi->mx3fb; + uint32_t reg; + + reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF); + + mx3fb_write_reg(mx3fb, reg & ~SDC_COM_BG_EN, SDC_COM_CONF); + + return reg & SDC_COM_BG_EN; +} + +static void sdc_enable_channel(struct mx3fb_info *mx3_fbi) +{ + struct mx3fb_data *mx3fb = mx3_fbi->mx3fb; + struct idmac_channel *ichan = mx3_fbi->idmac_channel; + struct dma_chan *dma_chan = &ichan->dma_chan; + unsigned long flags; + dma_cookie_t cookie; + + if (mx3_fbi->txd) + dev_dbg(mx3fb->dev, "mx3fbi %p, desc %p, sg %p\n", mx3_fbi, + to_tx_desc(mx3_fbi->txd), to_tx_desc(mx3_fbi->txd)->sg); + else + dev_dbg(mx3fb->dev, "mx3fbi %p, txd = NULL\n", mx3_fbi); + + /* This enables the channel */ + if (mx3_fbi->cookie < 0) { + mx3_fbi->txd = dmaengine_prep_slave_sg(dma_chan, + &mx3_fbi->sg[0], 1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); + if (!mx3_fbi->txd) { + dev_err(mx3fb->dev, "Cannot allocate descriptor on %d\n", + dma_chan->chan_id); + return; + } + + mx3_fbi->txd->callback_param = mx3_fbi->txd; + mx3_fbi->txd->callback = mx3fb_dma_done; + + cookie = mx3_fbi->txd->tx_submit(mx3_fbi->txd); + dev_dbg(mx3fb->dev, "%d: Submit %p #%d [%c]\n", __LINE__, + mx3_fbi->txd, cookie, list_empty(&ichan->queue) ? '-' : '+'); + } else { + if (!mx3_fbi->txd || !mx3_fbi->txd->tx_submit) { + dev_err(mx3fb->dev, "Cannot enable channel %d\n", + dma_chan->chan_id); + return; + } + + /* Just re-activate the same buffer */ + dma_async_issue_pending(dma_chan); + cookie = mx3_fbi->cookie; + dev_dbg(mx3fb->dev, "%d: Re-submit %p #%d [%c]\n", __LINE__, + mx3_fbi->txd, cookie, list_empty(&ichan->queue) ? '-' : '+'); + } + + if (cookie >= 0) { + spin_lock_irqsave(&mx3fb->lock, flags); + sdc_fb_init(mx3_fbi); + mx3_fbi->cookie = cookie; + spin_unlock_irqrestore(&mx3fb->lock, flags); + } + + /* + * Attention! Without this msleep the channel keeps generating + * interrupts. Next sdc_set_brightness() is going to be called + * from mx3fb_blank(). + */ + msleep(2); +} + +static void sdc_disable_channel(struct mx3fb_info *mx3_fbi) +{ + struct mx3fb_data *mx3fb = mx3_fbi->mx3fb; + uint32_t enabled; + unsigned long flags; + + if (mx3_fbi->txd == NULL) + return; + + spin_lock_irqsave(&mx3fb->lock, flags); + + enabled = sdc_fb_uninit(mx3_fbi); + + spin_unlock_irqrestore(&mx3fb->lock, flags); + + mx3_fbi->txd->chan->device->device_control(mx3_fbi->txd->chan, + DMA_TERMINATE_ALL, 0); + mx3_fbi->txd = NULL; + mx3_fbi->cookie = -EINVAL; +} + +/** + * sdc_set_window_pos() - set window position of the respective plane. + * @mx3fb: mx3fb context. + * @channel: IPU DMAC channel ID. + * @x_pos: X coordinate relative to the top left corner to place window at. + * @y_pos: Y coordinate relative to the top left corner to place window at. + * @return: 0 on success or negative error code on failure. + */ +static int sdc_set_window_pos(struct mx3fb_data *mx3fb, enum ipu_channel channel, + int16_t x_pos, int16_t y_pos) +{ + if (channel != IDMAC_SDC_0) + return -EINVAL; + + x_pos += mx3fb->h_start_width; + y_pos += mx3fb->v_start_width; + + mx3fb_write_reg(mx3fb, (x_pos << 16) | y_pos, SDC_BG_POS); + return 0; +} + +/** + * sdc_init_panel() - initialize a synchronous LCD panel. + * @mx3fb: mx3fb context. + * @panel: panel type. + * @pixel_clk: desired pixel clock frequency in Hz. + * @width: width of panel in pixels. + * @height: height of panel in pixels. + * @h_start_width: number of pixel clocks between the HSYNC signal pulse + * and the start of valid data. + * @h_sync_width: width of the HSYNC signal in units of pixel clocks. + * @h_end_width: number of pixel clocks between the end of valid data + * and the HSYNC signal for next line. + * @v_start_width: number of lines between the VSYNC signal pulse and the + * start of valid data. + * @v_sync_width: width of the VSYNC signal in units of lines + * @v_end_width: number of lines between the end of valid data and the + * VSYNC signal for next frame. + * @sig: bitfield of signal polarities for LCD interface. + * @return: 0 on success or negative error code on failure. + */ +static int sdc_init_panel(struct mx3fb_data *mx3fb, enum ipu_panel panel, + uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + struct ipu_di_signal_cfg sig) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t old_conf; + uint32_t div; + struct clk *ipu_clk; + const struct di_mapping *map; + + dev_dbg(mx3fb->dev, "panel size = %d x %d", width, height); + + if (v_sync_width == 0 || h_sync_width == 0) + return -EINVAL; + + /* Init panel size and blanking periods */ + reg = ((uint32_t) (h_sync_width - 1) << 26) | + ((uint32_t) (width + h_start_width + h_end_width - 1) << 16); + mx3fb_write_reg(mx3fb, reg, SDC_HOR_CONF); + +#ifdef DEBUG + printk(KERN_CONT " hor_conf %x,", reg); +#endif + + reg = ((uint32_t) (v_sync_width - 1) << 26) | SDC_V_SYNC_WIDTH_L | + ((uint32_t) (height + v_start_width + v_end_width - 1) << 16); + mx3fb_write_reg(mx3fb, reg, SDC_VER_CONF); + +#ifdef DEBUG + printk(KERN_CONT " ver_conf %x\n", reg); +#endif + + mx3fb->h_start_width = h_start_width; + mx3fb->v_start_width = v_start_width; + + switch (panel) { + case IPU_PANEL_SHARP_TFT: + mx3fb_write_reg(mx3fb, 0x00FD0102L, SDC_SHARP_CONF_1); + mx3fb_write_reg(mx3fb, 0x00F500F4L, SDC_SHARP_CONF_2); + mx3fb_write_reg(mx3fb, SDC_COM_SHARP | SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + case IPU_PANEL_TFT: + mx3fb_write_reg(mx3fb, SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + default: + return -EINVAL; + } + + /* Init clocking */ + + /* + * Calculate divider: fractional part is 4 bits so simply multiple by + * 2^4 to get fractional part, as long as we stay under ~250MHz and on + * i.MX31 it (HSP_CLK) is <= 178MHz. Currently 128.267MHz + */ + ipu_clk = clk_get(mx3fb->dev, NULL); + if (!IS_ERR(ipu_clk)) { + div = clk_get_rate(ipu_clk) * 16 / pixel_clk; + clk_put(ipu_clk); + } else { + div = 0; + } + + if (div < 0x40) { /* Divider less than 4 */ + dev_dbg(mx3fb->dev, + "InitPanel() - Pixel clock divider less than 4\n"); + div = 0x40; + } + + dev_dbg(mx3fb->dev, "pixel clk = %u, divider %u.%u\n", + pixel_clk, div >> 4, (div & 7) * 125); + + spin_lock_irqsave(&mx3fb->lock, lock_flags); + + /* + * DISP3_IF_CLK_DOWN_WR is half the divider value and 2 fraction bits + * fewer. Subtract 1 extra from DISP3_IF_CLK_DOWN_WR based on timing + * debug. DISP3_IF_CLK_UP_WR is 0 + */ + mx3fb_write_reg(mx3fb, (((div / 8) - 1) << 22) | div, DI_DISP3_TIME_CONF); + + /* DI settings */ + old_conf = mx3fb_read_reg(mx3fb, DI_DISP_IF_CONF) & 0x78FFFFFF; + old_conf |= sig.datamask_en << DI_D3_DATAMSK_SHIFT | + sig.clksel_en << DI_D3_CLK_SEL_SHIFT | + sig.clkidle_en << DI_D3_CLK_IDLE_SHIFT; + mx3fb_write_reg(mx3fb, old_conf, DI_DISP_IF_CONF); + + old_conf = mx3fb_read_reg(mx3fb, DI_DISP_SIG_POL) & 0xE0FFFFFF; + old_conf |= sig.data_pol << DI_D3_DATA_POL_SHIFT | + sig.clk_pol << DI_D3_CLK_POL_SHIFT | + sig.enable_pol << DI_D3_DRDY_SHARP_POL_SHIFT | + sig.Hsync_pol << DI_D3_HSYNC_POL_SHIFT | + sig.Vsync_pol << DI_D3_VSYNC_POL_SHIFT; + mx3fb_write_reg(mx3fb, old_conf, DI_DISP_SIG_POL); + + map = &di_mappings[mx3fb->disp_data_fmt]; + mx3fb_write_reg(mx3fb, map->b0, DI_DISP3_B0_MAP); + mx3fb_write_reg(mx3fb, map->b1, DI_DISP3_B1_MAP); + mx3fb_write_reg(mx3fb, map->b2, DI_DISP3_B2_MAP); + + spin_unlock_irqrestore(&mx3fb->lock, lock_flags); + + dev_dbg(mx3fb->dev, "DI_DISP_IF_CONF = 0x%08X\n", + mx3fb_read_reg(mx3fb, DI_DISP_IF_CONF)); + dev_dbg(mx3fb->dev, "DI_DISP_SIG_POL = 0x%08X\n", + mx3fb_read_reg(mx3fb, DI_DISP_SIG_POL)); + dev_dbg(mx3fb->dev, "DI_DISP3_TIME_CONF = 0x%08X\n", + mx3fb_read_reg(mx3fb, DI_DISP3_TIME_CONF)); + + return 0; +} + +/** + * sdc_set_color_key() - set the transparent color key for SDC graphic plane. + * @mx3fb: mx3fb context. + * @channel: IPU DMAC channel ID. + * @enable: boolean to enable or disable color keyl. + * @color_key: 24-bit RGB color to use as transparent color key. + * @return: 0 on success or negative error code on failure. + */ +static int sdc_set_color_key(struct mx3fb_data *mx3fb, enum ipu_channel channel, + bool enable, uint32_t color_key) +{ + uint32_t reg, sdc_conf; + unsigned long lock_flags; + + spin_lock_irqsave(&mx3fb->lock, lock_flags); + + sdc_conf = mx3fb_read_reg(mx3fb, SDC_COM_CONF); + if (channel == IDMAC_SDC_0) + sdc_conf &= ~SDC_COM_GWSEL; + else + sdc_conf |= SDC_COM_GWSEL; + + if (enable) { + reg = mx3fb_read_reg(mx3fb, SDC_GW_CTRL) & 0xFF000000L; + mx3fb_write_reg(mx3fb, reg | (color_key & 0x00FFFFFFL), + SDC_GW_CTRL); + + sdc_conf |= SDC_COM_KEY_COLOR_G; + } else { + sdc_conf &= ~SDC_COM_KEY_COLOR_G; + } + mx3fb_write_reg(mx3fb, sdc_conf, SDC_COM_CONF); + + spin_unlock_irqrestore(&mx3fb->lock, lock_flags); + + return 0; +} + +/** + * sdc_set_global_alpha() - set global alpha blending modes. + * @mx3fb: mx3fb context. + * @enable: boolean to enable or disable global alpha blending. If disabled, + * per pixel blending is used. + * @alpha: global alpha value. + * @return: 0 on success or negative error code on failure. + */ +static int sdc_set_global_alpha(struct mx3fb_data *mx3fb, bool enable, uint8_t alpha) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&mx3fb->lock, lock_flags); + + if (enable) { + reg = mx3fb_read_reg(mx3fb, SDC_GW_CTRL) & 0x00FFFFFFL; + mx3fb_write_reg(mx3fb, reg | ((uint32_t) alpha << 24), SDC_GW_CTRL); + + reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF); + mx3fb_write_reg(mx3fb, reg | SDC_COM_GLB_A, SDC_COM_CONF); + } else { + reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF); + mx3fb_write_reg(mx3fb, reg & ~SDC_COM_GLB_A, SDC_COM_CONF); + } + + spin_unlock_irqrestore(&mx3fb->lock, lock_flags); + + return 0; +} + +static void sdc_set_brightness(struct mx3fb_data *mx3fb, uint8_t value) +{ + dev_dbg(mx3fb->dev, "%s: value = %d\n", __func__, value); + /* This might be board-specific */ + mx3fb_write_reg(mx3fb, 0x03000000UL | value << 16, SDC_PWM_CTRL); + return; +} + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +static int mx3fb_blank(int blank, struct fb_info *fbi); +static int mx3fb_map_video_memory(struct fb_info *fbi, unsigned int mem_len, + bool lock); +static int mx3fb_unmap_video_memory(struct fb_info *fbi); + +/** + * mx3fb_set_fix() - set fixed framebuffer parameters from variable settings. + * @info: framebuffer information pointer + * @return: 0 on success or negative error code on failure. + */ +static int mx3fb_set_fix(struct fb_info *fbi) +{ + struct fb_fix_screeninfo *fix = &fbi->fix; + struct fb_var_screeninfo *var = &fbi->var; + + strncpy(fix->id, "DISP3 BG", 8); + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static void mx3fb_dma_done(void *arg) +{ + struct idmac_tx_desc *tx_desc = to_tx_desc(arg); + struct dma_chan *chan = tx_desc->txd.chan; + struct idmac_channel *ichannel = to_idmac_chan(chan); + struct mx3fb_data *mx3fb = ichannel->client; + struct mx3fb_info *mx3_fbi = mx3fb->fbi->par; + + dev_dbg(mx3fb->dev, "irq %d callback\n", ichannel->eof_irq); + + /* We only need one interrupt, it will be re-enabled as needed */ + disable_irq_nosync(ichannel->eof_irq); + + complete(&mx3_fbi->flip_cmpl); +} + +static bool mx3fb_must_set_par(struct fb_info *fbi) +{ + struct mx3fb_info *mx3_fbi = fbi->par; + struct fb_var_screeninfo old_var = mx3_fbi->cur_var; + struct fb_var_screeninfo new_var = fbi->var; + + if ((fbi->var.activate & FB_ACTIVATE_FORCE) && + (fbi->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) + return true; + + /* + * Ignore xoffset and yoffset update, + * because pan display handles this case. + */ + old_var.xoffset = new_var.xoffset; + old_var.yoffset = new_var.yoffset; + + return !!memcmp(&old_var, &new_var, sizeof(struct fb_var_screeninfo)); +} + +static int __set_par(struct fb_info *fbi, bool lock) +{ + u32 mem_len, cur_xoffset, cur_yoffset; + struct ipu_di_signal_cfg sig_cfg; + enum ipu_panel mode = IPU_PANEL_TFT; + struct mx3fb_info *mx3_fbi = fbi->par; + struct mx3fb_data *mx3fb = mx3_fbi->mx3fb; + struct idmac_channel *ichan = mx3_fbi->idmac_channel; + struct idmac_video_param *video = &ichan->params.video; + struct scatterlist *sg = mx3_fbi->sg; + + /* Total cleanup */ + if (mx3_fbi->txd) + sdc_disable_channel(mx3_fbi); + + mx3fb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (mem_len > fbi->fix.smem_len) { + if (fbi->fix.smem_start) + mx3fb_unmap_video_memory(fbi); + + if (mx3fb_map_video_memory(fbi, mem_len, lock) < 0) + return -ENOMEM; + } + + sg_init_table(&sg[0], 1); + sg_init_table(&sg[1], 1); + + sg_dma_address(&sg[0]) = fbi->fix.smem_start; + sg_set_page(&sg[0], virt_to_page(fbi->screen_base), + fbi->fix.smem_len, + offset_in_page(fbi->screen_base)); + + if (mx3_fbi->ipu_ch == IDMAC_SDC_0) { + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_INVERT) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (fbi->var.sync & FB_SYNC_OE_ACT_HIGH) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + if (fbi->var.sync & FB_SYNC_CLK_SEL_EN) + sig_cfg.clksel_en = true; + if (fbi->var.sync & FB_SYNC_SHARP_MODE) + mode = IPU_PANEL_SHARP_TFT; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (sdc_init_panel(mx3fb, mode, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin + + fbi->var.hsync_len, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin + + fbi->var.vsync_len, sig_cfg) != 0) { + dev_err(fbi->device, + "mx3fb: Error initializing panel.\n"); + return -EINVAL; + } + } + + sdc_set_window_pos(mx3fb, mx3_fbi->ipu_ch, 0, 0); + + mx3_fbi->cur_ipu_buf = 0; + + video->out_pixel_fmt = bpp_to_pixfmt(fbi->var.bits_per_pixel); + video->out_width = fbi->var.xres; + video->out_height = fbi->var.yres; + video->out_stride = fbi->var.xres_virtual; + + if (mx3_fbi->blank == FB_BLANK_UNBLANK) { + sdc_enable_channel(mx3_fbi); + /* + * sg[0] points to fb smem_start address + * and is actually active in controller. + */ + mx3_fbi->cur_var.xoffset = 0; + mx3_fbi->cur_var.yoffset = 0; + } + + /* + * Preserve xoffset and yoffest in case they are + * inactive in controller as fb is blanked. + */ + cur_xoffset = mx3_fbi->cur_var.xoffset; + cur_yoffset = mx3_fbi->cur_var.yoffset; + mx3_fbi->cur_var = fbi->var; + mx3_fbi->cur_var.xoffset = cur_xoffset; + mx3_fbi->cur_var.yoffset = cur_yoffset; + + return 0; +} + +/** + * mx3fb_set_par() - set framebuffer parameters and change the operating mode. + * @fbi: framebuffer information pointer. + * @return: 0 on success or negative error code on failure. + */ +static int mx3fb_set_par(struct fb_info *fbi) +{ + struct mx3fb_info *mx3_fbi = fbi->par; + struct mx3fb_data *mx3fb = mx3_fbi->mx3fb; + struct idmac_channel *ichan = mx3_fbi->idmac_channel; + int ret; + + dev_dbg(mx3fb->dev, "%s [%c]\n", __func__, list_empty(&ichan->queue) ? '-' : '+'); + + mutex_lock(&mx3_fbi->mutex); + + ret = mx3fb_must_set_par(fbi) ? __set_par(fbi, true) : 0; + + mutex_unlock(&mx3_fbi->mutex); + + return ret; +} + +/** + * mx3fb_check_var() - check and adjust framebuffer variable parameters. + * @var: framebuffer variable parameters + * @fbi: framebuffer information pointer + */ +static int mx3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + struct mx3fb_info *mx3_fbi = fbi->par; + u32 vtotal; + u32 htotal; + + dev_dbg(fbi->device, "%s\n", __func__); + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(fbi->device, "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* Preserve sync flags */ + var->sync |= mx3_fbi->cur_var.sync; + mx3_fbi->cur_var.sync |= var->sync; + + return 0; +} + +static u32 chan_to_field(unsigned int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mx3fb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int trans, struct fb_info *fbi) +{ + struct mx3fb_info *mx3_fbi = fbi->par; + u32 val; + int ret = 1; + + dev_dbg(fbi->device, "%s, regno = %u\n", __func__, regno); + + mutex_lock(&mx3_fbi->mutex); + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = chan_to_field(red, &fbi->var.red); + val |= chan_to_field(green, &fbi->var.green); + val |= chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + mutex_unlock(&mx3_fbi->mutex); + + return ret; +} + +static void __blank(int blank, struct fb_info *fbi) +{ + struct mx3fb_info *mx3_fbi = fbi->par; + struct mx3fb_data *mx3fb = mx3_fbi->mx3fb; + int was_blank = mx3_fbi->blank; + + mx3_fbi->blank = blank; + + /* Attention! + * Do not call sdc_disable_channel() for a channel that is disabled + * already! This will result in a kernel NULL pointer dereference + * (mx3_fbi->txd is NULL). Hide the fact, that all blank modes are + * handled equally by this driver. + */ + if (blank > FB_BLANK_UNBLANK && was_blank > FB_BLANK_UNBLANK) + return; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + sdc_set_brightness(mx3fb, 0); + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + /* Give LCD time to update - enough for 50 and 60 Hz */ + msleep(25); + sdc_disable_channel(mx3_fbi); + break; + case FB_BLANK_UNBLANK: + sdc_enable_channel(mx3_fbi); + sdc_set_brightness(mx3fb, mx3fb->backlight_level); + break; + } +} + +/** + * mx3fb_blank() - blank the display. + */ +static int mx3fb_blank(int blank, struct fb_info *fbi) +{ + struct mx3fb_info *mx3_fbi = fbi->par; + + dev_dbg(fbi->device, "%s, blank = %d, base %p, len %u\n", __func__, + blank, fbi->screen_base, fbi->fix.smem_len); + + if (mx3_fbi->blank == blank) + return 0; + + mutex_lock(&mx3_fbi->mutex); + __blank(blank, fbi); + mutex_unlock(&mx3_fbi->mutex); + + return 0; +} + +/** + * mx3fb_pan_display() - pan or wrap the display + * @var: variable screen buffer information. + * @info: framebuffer information pointer. + * + * We look only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ +static int mx3fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct mx3fb_info *mx3_fbi = fbi->par; + u32 y_bottom; + unsigned long base; + off_t offset; + dma_cookie_t cookie; + struct scatterlist *sg = mx3_fbi->sg; + struct dma_chan *dma_chan = &mx3_fbi->idmac_channel->dma_chan; + struct dma_async_tx_descriptor *txd; + int ret; + + dev_dbg(fbi->device, "%s [%c]\n", __func__, + list_empty(&mx3_fbi->idmac_channel->queue) ? '-' : '+'); + + if (var->xoffset > 0) { + dev_dbg(fbi->device, "x panning not supported\n"); + return -EINVAL; + } + + if (mx3_fbi->cur_var.xoffset == var->xoffset && + mx3_fbi->cur_var.yoffset == var->yoffset) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += fbi->var.yres; + + if (y_bottom > fbi->var.yres_virtual) + return -EINVAL; + + mutex_lock(&mx3_fbi->mutex); + + offset = var->yoffset * fbi->fix.line_length + + var->xoffset * (fbi->var.bits_per_pixel / 8); + base = fbi->fix.smem_start + offset; + + dev_dbg(fbi->device, "Updating SDC BG buf %d address=0x%08lX\n", + mx3_fbi->cur_ipu_buf, base); + + /* + * We enable the End of Frame interrupt, which will free a tx-descriptor, + * which we will need for the next device_prep_slave_sg(). The + * IRQ-handler will disable the IRQ again. + */ + init_completion(&mx3_fbi->flip_cmpl); + enable_irq(mx3_fbi->idmac_channel->eof_irq); + + ret = wait_for_completion_timeout(&mx3_fbi->flip_cmpl, HZ / 10); + if (ret <= 0) { + mutex_unlock(&mx3_fbi->mutex); + dev_info(fbi->device, "Panning failed due to %s\n", ret < 0 ? + "user interrupt" : "timeout"); + disable_irq(mx3_fbi->idmac_channel->eof_irq); + return ret ? : -ETIMEDOUT; + } + + mx3_fbi->cur_ipu_buf = !mx3_fbi->cur_ipu_buf; + + sg_dma_address(&sg[mx3_fbi->cur_ipu_buf]) = base; + sg_set_page(&sg[mx3_fbi->cur_ipu_buf], + virt_to_page(fbi->screen_base + offset), fbi->fix.smem_len, + offset_in_page(fbi->screen_base + offset)); + + if (mx3_fbi->txd) + async_tx_ack(mx3_fbi->txd); + + txd = dmaengine_prep_slave_sg(dma_chan, sg + + mx3_fbi->cur_ipu_buf, 1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); + if (!txd) { + dev_err(fbi->device, + "Error preparing a DMA transaction descriptor.\n"); + mutex_unlock(&mx3_fbi->mutex); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = mx3fb_dma_done; + + /* + * Emulate original mx3fb behaviour: each new call to idmac_tx_submit() + * should switch to another buffer + */ + cookie = txd->tx_submit(txd); + dev_dbg(fbi->device, "%d: Submit %p #%d\n", __LINE__, txd, cookie); + if (cookie < 0) { + dev_err(fbi->device, + "Error updating SDC buf %d to address=0x%08lX\n", + mx3_fbi->cur_ipu_buf, base); + mutex_unlock(&mx3_fbi->mutex); + return -EIO; + } + + mx3_fbi->txd = txd; + + fbi->var.xoffset = var->xoffset; + fbi->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + fbi->var.vmode |= FB_VMODE_YWRAP; + else + fbi->var.vmode &= ~FB_VMODE_YWRAP; + + mx3_fbi->cur_var = fbi->var; + + mutex_unlock(&mx3_fbi->mutex); + + dev_dbg(fbi->device, "Update complete\n"); + + return 0; +} + +/* + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mx3fb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mx3fb_set_par, + .fb_check_var = mx3fb_check_var, + .fb_setcolreg = mx3fb_setcolreg, + .fb_pan_display = mx3fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mx3fb_blank, +}; + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mx3fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mx3fb_data *mx3fb = platform_get_drvdata(pdev); + struct mx3fb_info *mx3_fbi = mx3fb->fbi->par; + + console_lock(); + fb_set_suspend(mx3fb->fbi, 1); + console_unlock(); + + if (mx3_fbi->blank == FB_BLANK_UNBLANK) { + sdc_disable_channel(mx3_fbi); + sdc_set_brightness(mx3fb, 0); + + } + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mx3fb_resume(struct platform_device *pdev) +{ + struct mx3fb_data *mx3fb = platform_get_drvdata(pdev); + struct mx3fb_info *mx3_fbi = mx3fb->fbi->par; + + if (mx3_fbi->blank == FB_BLANK_UNBLANK) { + sdc_enable_channel(mx3_fbi); + sdc_set_brightness(mx3fb, mx3fb->backlight_level); + } + + console_lock(); + fb_set_suspend(mx3fb->fbi, 0); + console_unlock(); + + return 0; +} +#else +#define mx3fb_suspend NULL +#define mx3fb_resume NULL +#endif + +/* + * Main framebuffer functions + */ + +/** + * mx3fb_map_video_memory() - allocates the DRAM memory for the frame buffer. + * @fbi: framebuffer information pointer + * @mem_len: length of mapped memory + * @lock: do not lock during initialisation + * @return: Error code indicating success or failure + * + * This buffer is remapped into a non-cached, non-buffered, memory region to + * allow palette and pixel writes to occur without flushing the cache. Once this + * area is remapped, all virtual memory access to the video memory should occur + * at the new region. + */ +static int mx3fb_map_video_memory(struct fb_info *fbi, unsigned int mem_len, + bool lock) +{ + int retval = 0; + dma_addr_t addr; + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + mem_len, + &addr, GFP_DMA | GFP_KERNEL); + + if (!fbi->screen_base) { + dev_err(fbi->device, "Cannot allocate %u bytes framebuffer memory\n", + mem_len); + retval = -EBUSY; + goto err0; + } + + if (lock) + mutex_lock(&fbi->mm_lock); + fbi->fix.smem_start = addr; + fbi->fix.smem_len = mem_len; + if (lock) + mutex_unlock(&fbi->mm_lock); + + dev_dbg(fbi->device, "allocated fb @ p=0x%08x, v=0x%p, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->screen_base, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; + +err0: + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + fbi->screen_base = NULL; + return retval; +} + +/** + * mx3fb_unmap_video_memory() - de-allocate frame buffer memory. + * @fbi: framebuffer information pointer + * @return: error code indicating success or failure + */ +static int mx3fb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + + fbi->screen_base = NULL; + mutex_lock(&fbi->mm_lock); + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + mutex_unlock(&fbi->mm_lock); + return 0; +} + +/** + * mx3fb_init_fbinfo() - initialize framebuffer information object. + * @return: initialized framebuffer structure. + */ +static struct fb_info *mx3fb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mx3fb_info *mx3fbi; + int ret; + + /* Allocate sufficient memory for the fb structure */ + fbi = framebuffer_alloc(sizeof(struct mx3fb_info), dev); + if (!fbi) + return NULL; + + mx3fbi = fbi->par; + mx3fbi->cookie = -EINVAL; + mx3fbi->cur_ipu_buf = 0; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mx3fbi->pseudo_palette; + + mutex_init(&mx3fbi->mutex); + + /* Allocate colormap */ + ret = fb_alloc_cmap(&fbi->cmap, 16, 0); + if (ret < 0) { + framebuffer_release(fbi); + return NULL; + } + + return fbi; +} + +static int init_fb_chan(struct mx3fb_data *mx3fb, struct idmac_channel *ichan) +{ + struct device *dev = mx3fb->dev; + struct mx3fb_platform_data *mx3fb_pdata = dev_get_platdata(dev); + const char *name = mx3fb_pdata->name; + unsigned int irq; + struct fb_info *fbi; + struct mx3fb_info *mx3fbi; + const struct fb_videomode *mode; + int ret, num_modes; + + if (mx3fb_pdata->disp_data_fmt >= ARRAY_SIZE(di_mappings)) { + dev_err(dev, "Illegal display data format %d\n", + mx3fb_pdata->disp_data_fmt); + return -EINVAL; + } + + ichan->client = mx3fb; + irq = ichan->eof_irq; + + if (ichan->dma_chan.chan_id != IDMAC_SDC_0) + return -EINVAL; + + fbi = mx3fb_init_fbinfo(dev, &mx3fb_ops); + if (!fbi) + return -ENOMEM; + + if (!fb_mode) + fb_mode = name; + + if (!fb_mode) { + ret = -EINVAL; + goto emode; + } + + if (mx3fb_pdata->mode && mx3fb_pdata->num_modes) { + mode = mx3fb_pdata->mode; + num_modes = mx3fb_pdata->num_modes; + } else { + mode = mx3fb_modedb; + num_modes = ARRAY_SIZE(mx3fb_modedb); + } + + if (!fb_find_mode(&fbi->var, fbi, fb_mode, mode, + num_modes, NULL, default_bpp)) { + ret = -EBUSY; + goto emode; + } + + fb_videomode_to_modelist(mode, num_modes, &fbi->modelist); + + /* Default Y virtual size is 2x panel size */ + fbi->var.yres_virtual = fbi->var.yres * 2; + + mx3fb->fbi = fbi; + + /* set Display Interface clock period */ + mx3fb_write_reg(mx3fb, 0x00100010L, DI_HSP_CLK_PER); + /* Might need to trigger HSP clock change - see 44.3.3.8.5 */ + + sdc_set_brightness(mx3fb, 255); + sdc_set_global_alpha(mx3fb, true, 0xFF); + sdc_set_color_key(mx3fb, IDMAC_SDC_0, false, 0); + + mx3fbi = fbi->par; + mx3fbi->idmac_channel = ichan; + mx3fbi->ipu_ch = ichan->dma_chan.chan_id; + mx3fbi->mx3fb = mx3fb; + mx3fbi->blank = FB_BLANK_NORMAL; + + mx3fb->disp_data_fmt = mx3fb_pdata->disp_data_fmt; + + init_completion(&mx3fbi->flip_cmpl); + disable_irq(ichan->eof_irq); + dev_dbg(mx3fb->dev, "disabling irq %d\n", ichan->eof_irq); + ret = __set_par(fbi, false); + if (ret < 0) + goto esetpar; + + __blank(FB_BLANK_UNBLANK, fbi); + + dev_info(dev, "registered, using mode %s\n", fb_mode); + + ret = register_framebuffer(fbi); + if (ret < 0) + goto erfb; + + return 0; + +erfb: +esetpar: +emode: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + + return ret; +} + +static bool chan_filter(struct dma_chan *chan, void *arg) +{ + struct dma_chan_request *rq = arg; + struct device *dev; + struct mx3fb_platform_data *mx3fb_pdata; + + if (!imx_dma_is_ipu(chan)) + return false; + + if (!rq) + return false; + + dev = rq->mx3fb->dev; + mx3fb_pdata = dev_get_platdata(dev); + + return rq->id == chan->chan_id && + mx3fb_pdata->dma_dev == chan->device->dev; +} + +static void release_fbi(struct fb_info *fbi) +{ + mx3fb_unmap_video_memory(fbi); + + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); +} + +static int mx3fb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + struct resource *sdc_reg; + struct mx3fb_data *mx3fb; + dma_cap_mask_t mask; + struct dma_chan *chan; + struct dma_chan_request rq; + + /* + * Display Interface (DI) and Synchronous Display Controller (SDC) + * registers + */ + sdc_reg = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!sdc_reg) + return -EINVAL; + + mx3fb = kzalloc(sizeof(*mx3fb), GFP_KERNEL); + if (!mx3fb) + return -ENOMEM; + + spin_lock_init(&mx3fb->lock); + + mx3fb->reg_base = ioremap(sdc_reg->start, resource_size(sdc_reg)); + if (!mx3fb->reg_base) { + ret = -ENOMEM; + goto eremap; + } + + pr_debug("Remapped %pR at %p\n", sdc_reg, mx3fb->reg_base); + + /* IDMAC interface */ + dmaengine_get(); + + mx3fb->dev = dev; + platform_set_drvdata(pdev, mx3fb); + + rq.mx3fb = mx3fb; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + rq.id = IDMAC_SDC_0; + chan = dma_request_channel(mask, chan_filter, &rq); + if (!chan) { + ret = -EBUSY; + goto ersdc0; + } + + mx3fb->backlight_level = 255; + + ret = init_fb_chan(mx3fb, to_idmac_chan(chan)); + if (ret < 0) + goto eisdc0; + + return 0; + +eisdc0: + dma_release_channel(chan); +ersdc0: + dmaengine_put(); + iounmap(mx3fb->reg_base); +eremap: + kfree(mx3fb); + dev_err(dev, "mx3fb: failed to register fb\n"); + return ret; +} + +static int mx3fb_remove(struct platform_device *dev) +{ + struct mx3fb_data *mx3fb = platform_get_drvdata(dev); + struct fb_info *fbi = mx3fb->fbi; + struct mx3fb_info *mx3_fbi = fbi->par; + struct dma_chan *chan; + + chan = &mx3_fbi->idmac_channel->dma_chan; + release_fbi(fbi); + + dma_release_channel(chan); + dmaengine_put(); + + iounmap(mx3fb->reg_base); + kfree(mx3fb); + return 0; +} + +static struct platform_driver mx3fb_driver = { + .driver = { + .name = MX3FB_NAME, + .owner = THIS_MODULE, + }, + .probe = mx3fb_probe, + .remove = mx3fb_remove, + .suspend = mx3fb_suspend, + .resume = mx3fb_resume, +}; + +/* + * Parse user specified options (`video=mx3fb:') + * example: + * video=mx3fb:bpp=16 + */ +static int __init mx3fb_setup(void) +{ +#ifndef MODULE + char *opt, *options = NULL; + + if (fb_get_options("mx3fb", &options)) + return -ENODEV; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } +#endif + + return 0; +} + +static int __init mx3fb_init(void) +{ + int ret = mx3fb_setup(); + + if (ret < 0) + return ret; + + ret = platform_driver_register(&mx3fb_driver); + return ret; +} + +static void __exit mx3fb_exit(void) +{ + platform_driver_unregister(&mx3fb_driver); +} + +module_init(mx3fb_init); +module_exit(mx3fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX3 framebuffer driver"); +MODULE_ALIAS("platform:" MX3FB_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/mxsfb.c b/drivers/video/fbdev/mxsfb.c new file mode 100644 index 000000000000..accf48a2cce4 --- /dev/null +++ b/drivers/video/fbdev/mxsfb.c @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2010 Juergen Beisert, Pengutronix + * + * This code is based on: + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + * + * 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. + */ + +#define DRIVER_NAME "mxsfb" + +/** + * @file + * @brief LCDIF driver for i.MX23 and i.MX28 + * + * The LCDIF support four modes of operation + * - MPU interface (to drive smart displays) -> not supported yet + * - VSYNC interface (like MPU interface plus Vsync) -> not supported yet + * - Dotclock interface (to drive LC displays with RGB data and sync signals) + * - DVI (to drive ITU-R BT656) -> not supported yet + * + * This driver depends on a correct setup of the pins used for this purpose + * (platform specific). + * + * For the developer: Don't forget to set the data bus width to the display + * in the imx_fb_videomode structure. You will else end up with ugly colours. + * If you fight against jitter you can vary the clock delay. This is a feature + * of the i.MX28 and you can vary it between 2 ns ... 8 ns in 2 ns steps. Give + * the required value in the imx_fb_videomode structure. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/fb.h> +#include <linux/regulator/consumer.h> +#include <video/of_display_timing.h> +#include <video/of_videomode.h> +#include <video/videomode.h> + +#define REG_SET 4 +#define REG_CLR 8 + +#define LCDC_CTRL 0x00 +#define LCDC_CTRL1 0x10 +#define LCDC_V4_CTRL2 0x20 +#define LCDC_V3_TRANSFER_COUNT 0x20 +#define LCDC_V4_TRANSFER_COUNT 0x30 +#define LCDC_V4_CUR_BUF 0x40 +#define LCDC_V4_NEXT_BUF 0x50 +#define LCDC_V3_CUR_BUF 0x30 +#define LCDC_V3_NEXT_BUF 0x40 +#define LCDC_TIMING 0x60 +#define LCDC_VDCTRL0 0x70 +#define LCDC_VDCTRL1 0x80 +#define LCDC_VDCTRL2 0x90 +#define LCDC_VDCTRL3 0xa0 +#define LCDC_VDCTRL4 0xb0 +#define LCDC_DVICTRL0 0xc0 +#define LCDC_DVICTRL1 0xd0 +#define LCDC_DVICTRL2 0xe0 +#define LCDC_DVICTRL3 0xf0 +#define LCDC_DVICTRL4 0x100 +#define LCDC_V4_DATA 0x180 +#define LCDC_V3_DATA 0x1b0 +#define LCDC_V4_DEBUG0 0x1d0 +#define LCDC_V3_DEBUG0 0x1f0 + +#define CTRL_SFTRST (1 << 31) +#define CTRL_CLKGATE (1 << 30) +#define CTRL_BYPASS_COUNT (1 << 19) +#define CTRL_VSYNC_MODE (1 << 18) +#define CTRL_DOTCLK_MODE (1 << 17) +#define CTRL_DATA_SELECT (1 << 16) +#define CTRL_SET_BUS_WIDTH(x) (((x) & 0x3) << 10) +#define CTRL_GET_BUS_WIDTH(x) (((x) >> 10) & 0x3) +#define CTRL_SET_WORD_LENGTH(x) (((x) & 0x3) << 8) +#define CTRL_GET_WORD_LENGTH(x) (((x) >> 8) & 0x3) +#define CTRL_MASTER (1 << 5) +#define CTRL_DF16 (1 << 3) +#define CTRL_DF18 (1 << 2) +#define CTRL_DF24 (1 << 1) +#define CTRL_RUN (1 << 0) + +#define CTRL1_FIFO_CLEAR (1 << 21) +#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16) +#define CTRL1_GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf) + +#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16) +#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff) +#define TRANSFER_COUNT_SET_HCOUNT(x) ((x) & 0xffff) +#define TRANSFER_COUNT_GET_HCOUNT(x) ((x) & 0xffff) + + +#define VDCTRL0_ENABLE_PRESENT (1 << 28) +#define VDCTRL0_VSYNC_ACT_HIGH (1 << 27) +#define VDCTRL0_HSYNC_ACT_HIGH (1 << 26) +#define VDCTRL0_DOTCLK_ACT_FALLING (1 << 25) +#define VDCTRL0_ENABLE_ACT_HIGH (1 << 24) +#define VDCTRL0_VSYNC_PERIOD_UNIT (1 << 21) +#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT (1 << 20) +#define VDCTRL0_HALF_LINE (1 << 19) +#define VDCTRL0_HALF_LINE_MODE (1 << 18) +#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) +#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) + +#define VDCTRL2_SET_HSYNC_PERIOD(x) ((x) & 0x3ffff) +#define VDCTRL2_GET_HSYNC_PERIOD(x) ((x) & 0x3ffff) + +#define VDCTRL3_MUX_SYNC_SIGNALS (1 << 29) +#define VDCTRL3_VSYNC_ONLY (1 << 28) +#define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16) +#define GET_HOR_WAIT_CNT(x) (((x) >> 16) & 0xfff) +#define SET_VERT_WAIT_CNT(x) ((x) & 0xffff) +#define GET_VERT_WAIT_CNT(x) ((x) & 0xffff) + +#define VDCTRL4_SET_DOTCLK_DLY(x) (((x) & 0x7) << 29) /* v4 only */ +#define VDCTRL4_GET_DOTCLK_DLY(x) (((x) >> 29) & 0x7) /* v4 only */ +#define VDCTRL4_SYNC_SIGNALS_ON (1 << 18) +#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff) + +#define DEBUG0_HSYNC (1 < 26) +#define DEBUG0_VSYNC (1 < 25) + +#define MIN_XRES 120 +#define MIN_YRES 120 + +#define RED 0 +#define GREEN 1 +#define BLUE 2 +#define TRANSP 3 + +#define STMLCDIF_8BIT 1 /** pixel data bus to the display is of 8 bit width */ +#define STMLCDIF_16BIT 0 /** pixel data bus to the display is of 16 bit width */ +#define STMLCDIF_18BIT 2 /** pixel data bus to the display is of 18 bit width */ +#define STMLCDIF_24BIT 3 /** pixel data bus to the display is of 24 bit width */ + +#define MXSFB_SYNC_DATA_ENABLE_HIGH_ACT (1 << 6) +#define MXSFB_SYNC_DOTCLK_FALLING_ACT (1 << 7) /* negtive edge sampling */ + +enum mxsfb_devtype { + MXSFB_V3, + MXSFB_V4, +}; + +/* CPU dependent register offsets */ +struct mxsfb_devdata { + unsigned transfer_count; + unsigned cur_buf; + unsigned next_buf; + unsigned debug0; + unsigned hs_wdth_mask; + unsigned hs_wdth_shift; + unsigned ipversion; +}; + +struct mxsfb_info { + struct fb_info fb_info; + struct platform_device *pdev; + struct clk *clk; + void __iomem *base; /* registers */ + unsigned allocated_size; + int enabled; + unsigned ld_intf_width; + unsigned dotclk_delay; + const struct mxsfb_devdata *devdata; + u32 sync; + struct regulator *reg_lcd; +}; + +#define mxsfb_is_v3(host) (host->devdata->ipversion == 3) +#define mxsfb_is_v4(host) (host->devdata->ipversion == 4) + +static const struct mxsfb_devdata mxsfb_devdata[] = { + [MXSFB_V3] = { + .transfer_count = LCDC_V3_TRANSFER_COUNT, + .cur_buf = LCDC_V3_CUR_BUF, + .next_buf = LCDC_V3_NEXT_BUF, + .debug0 = LCDC_V3_DEBUG0, + .hs_wdth_mask = 0xff, + .hs_wdth_shift = 24, + .ipversion = 3, + }, + [MXSFB_V4] = { + .transfer_count = LCDC_V4_TRANSFER_COUNT, + .cur_buf = LCDC_V4_CUR_BUF, + .next_buf = LCDC_V4_NEXT_BUF, + .debug0 = LCDC_V4_DEBUG0, + .hs_wdth_mask = 0x3fff, + .hs_wdth_shift = 18, + .ipversion = 4, + }, +}; + +#define to_imxfb_host(x) (container_of(x, struct mxsfb_info, fb_info)) + +/* mask and shift depends on architecture */ +static inline u32 set_hsync_pulse_width(struct mxsfb_info *host, unsigned val) +{ + return (val & host->devdata->hs_wdth_mask) << + host->devdata->hs_wdth_shift; +} + +static inline u32 get_hsync_pulse_width(struct mxsfb_info *host, unsigned val) +{ + return (val >> host->devdata->hs_wdth_shift) & + host->devdata->hs_wdth_mask; +} + +static const struct fb_bitfield def_rgb565[] = { + [RED] = { + .offset = 11, + .length = 5, + }, + [GREEN] = { + .offset = 5, + .length = 6, + }, + [BLUE] = { + .offset = 0, + .length = 5, + }, + [TRANSP] = { /* no support for transparency */ + .length = 0, + } +}; + +static const struct fb_bitfield def_rgb888[] = { + [RED] = { + .offset = 16, + .length = 8, + }, + [GREEN] = { + .offset = 8, + .length = 8, + }, + [BLUE] = { + .offset = 0, + .length = 8, + }, + [TRANSP] = { /* no support for transparency */ + .length = 0, + } +}; + +static inline unsigned chan_to_field(unsigned chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxsfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + const struct fb_bitfield *rgb = NULL; + + if (var->xres < MIN_XRES) + var->xres = MIN_XRES; + if (var->yres < MIN_YRES) + var->yres = MIN_YRES; + + var->xres_virtual = var->xres; + + var->yres_virtual = var->yres; + + switch (var->bits_per_pixel) { + case 16: + /* always expect RGB 565 */ + rgb = def_rgb565; + break; + case 32: + switch (host->ld_intf_width) { + case STMLCDIF_8BIT: + pr_debug("Unsupported LCD bus width mapping\n"); + break; + case STMLCDIF_16BIT: + case STMLCDIF_18BIT: + case STMLCDIF_24BIT: + /* real 24 bit */ + rgb = def_rgb888; + break; + } + break; + default: + pr_err("Unsupported colour depth: %u\n", var->bits_per_pixel); + return -EINVAL; + } + + /* + * Copy the RGB parameters for this display + * from the machine specific parameters. + */ + var->red = rgb[RED]; + var->green = rgb[GREEN]; + var->blue = rgb[BLUE]; + var->transp = rgb[TRANSP]; + + return 0; +} + +static void mxsfb_enable_controller(struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + u32 reg; + int ret; + + dev_dbg(&host->pdev->dev, "%s\n", __func__); + + if (host->reg_lcd) { + ret = regulator_enable(host->reg_lcd); + if (ret) { + dev_err(&host->pdev->dev, + "lcd regulator enable failed: %d\n", ret); + return; + } + } + + clk_prepare_enable(host->clk); + clk_set_rate(host->clk, PICOS2KHZ(fb_info->var.pixclock) * 1000U); + + /* if it was disabled, re-enable the mode again */ + writel(CTRL_DOTCLK_MODE, host->base + LCDC_CTRL + REG_SET); + + /* enable the SYNC signals first, then the DMA engine */ + reg = readl(host->base + LCDC_VDCTRL4); + reg |= VDCTRL4_SYNC_SIGNALS_ON; + writel(reg, host->base + LCDC_VDCTRL4); + + writel(CTRL_RUN, host->base + LCDC_CTRL + REG_SET); + + host->enabled = 1; +} + +static void mxsfb_disable_controller(struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + unsigned loop; + u32 reg; + int ret; + + dev_dbg(&host->pdev->dev, "%s\n", __func__); + + /* + * Even if we disable the controller here, it will still continue + * until its FIFOs are running out of data + */ + writel(CTRL_DOTCLK_MODE, host->base + LCDC_CTRL + REG_CLR); + + loop = 1000; + while (loop) { + reg = readl(host->base + LCDC_CTRL); + if (!(reg & CTRL_RUN)) + break; + loop--; + } + + reg = readl(host->base + LCDC_VDCTRL4); + writel(reg & ~VDCTRL4_SYNC_SIGNALS_ON, host->base + LCDC_VDCTRL4); + + clk_disable_unprepare(host->clk); + + host->enabled = 0; + + if (host->reg_lcd) { + ret = regulator_disable(host->reg_lcd); + if (ret) + dev_err(&host->pdev->dev, + "lcd regulator disable failed: %d\n", ret); + } +} + +static int mxsfb_set_par(struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + u32 ctrl, vdctrl0, vdctrl4; + int line_size, fb_size; + int reenable = 0; + + line_size = fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3); + fb_size = fb_info->var.yres_virtual * line_size; + + if (fb_size > fb_info->fix.smem_len) + return -ENOMEM; + + fb_info->fix.line_length = line_size; + + /* + * It seems, you can't re-program the controller if it is still running. + * This may lead into shifted pictures (FIFO issue?). + * So, first stop the controller and drain its FIFOs + */ + if (host->enabled) { + reenable = 1; + mxsfb_disable_controller(fb_info); + } + + /* clear the FIFOs */ + writel(CTRL1_FIFO_CLEAR, host->base + LCDC_CTRL1 + REG_SET); + + ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER | + CTRL_SET_BUS_WIDTH(host->ld_intf_width); + + switch (fb_info->var.bits_per_pixel) { + case 16: + dev_dbg(&host->pdev->dev, "Setting up RGB565 mode\n"); + ctrl |= CTRL_SET_WORD_LENGTH(0); + writel(CTRL1_SET_BYTE_PACKAGING(0xf), host->base + LCDC_CTRL1); + break; + case 32: + dev_dbg(&host->pdev->dev, "Setting up RGB888/666 mode\n"); + ctrl |= CTRL_SET_WORD_LENGTH(3); + switch (host->ld_intf_width) { + case STMLCDIF_8BIT: + dev_err(&host->pdev->dev, + "Unsupported LCD bus width mapping\n"); + return -EINVAL; + case STMLCDIF_16BIT: + case STMLCDIF_18BIT: + case STMLCDIF_24BIT: + /* real 24 bit */ + break; + } + /* do not use packed pixels = one pixel per word instead */ + writel(CTRL1_SET_BYTE_PACKAGING(0x7), host->base + LCDC_CTRL1); + break; + default: + dev_err(&host->pdev->dev, "Unhandled color depth of %u\n", + fb_info->var.bits_per_pixel); + return -EINVAL; + } + + writel(ctrl, host->base + LCDC_CTRL); + + writel(TRANSFER_COUNT_SET_VCOUNT(fb_info->var.yres) | + TRANSFER_COUNT_SET_HCOUNT(fb_info->var.xres), + host->base + host->devdata->transfer_count); + + vdctrl0 = VDCTRL0_ENABLE_PRESENT | /* always in DOTCLOCK mode */ + VDCTRL0_VSYNC_PERIOD_UNIT | + VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | + VDCTRL0_SET_VSYNC_PULSE_WIDTH(fb_info->var.vsync_len); + if (fb_info->var.sync & FB_SYNC_HOR_HIGH_ACT) + vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; + if (fb_info->var.sync & FB_SYNC_VERT_HIGH_ACT) + vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; + if (host->sync & MXSFB_SYNC_DATA_ENABLE_HIGH_ACT) + vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; + if (host->sync & MXSFB_SYNC_DOTCLK_FALLING_ACT) + vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING; + + writel(vdctrl0, host->base + LCDC_VDCTRL0); + + /* frame length in lines */ + writel(fb_info->var.upper_margin + fb_info->var.vsync_len + + fb_info->var.lower_margin + fb_info->var.yres, + host->base + LCDC_VDCTRL1); + + /* line length in units of clocks or pixels */ + writel(set_hsync_pulse_width(host, fb_info->var.hsync_len) | + VDCTRL2_SET_HSYNC_PERIOD(fb_info->var.left_margin + + fb_info->var.hsync_len + fb_info->var.right_margin + + fb_info->var.xres), + host->base + LCDC_VDCTRL2); + + writel(SET_HOR_WAIT_CNT(fb_info->var.left_margin + + fb_info->var.hsync_len) | + SET_VERT_WAIT_CNT(fb_info->var.upper_margin + + fb_info->var.vsync_len), + host->base + LCDC_VDCTRL3); + + vdctrl4 = SET_DOTCLK_H_VALID_DATA_CNT(fb_info->var.xres); + if (mxsfb_is_v4(host)) + vdctrl4 |= VDCTRL4_SET_DOTCLK_DLY(host->dotclk_delay); + writel(vdctrl4, host->base + LCDC_VDCTRL4); + + writel(fb_info->fix.smem_start + + fb_info->fix.line_length * fb_info->var.yoffset, + host->base + host->devdata->next_buf); + + if (reenable) + mxsfb_enable_controller(fb_info); + + return 0; +} + +static int mxsfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *fb_info) +{ + unsigned int val; + int ret = -EINVAL; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fb_info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + switch (fb_info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 12 or 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fb_info->pseudo_palette; + + val = chan_to_field(red, &fb_info->var.red); + val |= chan_to_field(green, &fb_info->var.green); + val |= chan_to_field(blue, &fb_info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static int mxsfb_blank(int blank, struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + if (host->enabled) + mxsfb_disable_controller(fb_info); + break; + + case FB_BLANK_UNBLANK: + if (!host->enabled) + mxsfb_enable_controller(fb_info); + break; + } + return 0; +} + +static int mxsfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + unsigned offset; + + if (var->xoffset != 0) + return -EINVAL; + + offset = fb_info->fix.line_length * var->yoffset; + + /* update on next VSYNC */ + writel(fb_info->fix.smem_start + offset, + host->base + host->devdata->next_buf); + + return 0; +} + +static struct fb_ops mxsfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxsfb_check_var, + .fb_set_par = mxsfb_set_par, + .fb_setcolreg = mxsfb_setcolreg, + .fb_blank = mxsfb_blank, + .fb_pan_display = mxsfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int mxsfb_restore_mode(struct mxsfb_info *host, + struct fb_videomode *vmode) +{ + struct fb_info *fb_info = &host->fb_info; + unsigned line_count; + unsigned period; + unsigned long pa, fbsize; + int bits_per_pixel, ofs; + u32 transfer_count, vdctrl0, vdctrl2, vdctrl3, vdctrl4, ctrl; + + /* Only restore the mode when the controller is running */ + ctrl = readl(host->base + LCDC_CTRL); + if (!(ctrl & CTRL_RUN)) + return -EINVAL; + + vdctrl0 = readl(host->base + LCDC_VDCTRL0); + vdctrl2 = readl(host->base + LCDC_VDCTRL2); + vdctrl3 = readl(host->base + LCDC_VDCTRL3); + vdctrl4 = readl(host->base + LCDC_VDCTRL4); + + transfer_count = readl(host->base + host->devdata->transfer_count); + + vmode->xres = TRANSFER_COUNT_GET_HCOUNT(transfer_count); + vmode->yres = TRANSFER_COUNT_GET_VCOUNT(transfer_count); + + switch (CTRL_GET_WORD_LENGTH(ctrl)) { + case 0: + bits_per_pixel = 16; + break; + case 3: + bits_per_pixel = 32; + break; + case 1: + default: + return -EINVAL; + } + + fb_info->var.bits_per_pixel = bits_per_pixel; + + vmode->pixclock = KHZ2PICOS(clk_get_rate(host->clk) / 1000U); + vmode->hsync_len = get_hsync_pulse_width(host, vdctrl2); + vmode->left_margin = GET_HOR_WAIT_CNT(vdctrl3) - vmode->hsync_len; + vmode->right_margin = VDCTRL2_GET_HSYNC_PERIOD(vdctrl2) - + vmode->hsync_len - vmode->left_margin - vmode->xres; + vmode->vsync_len = VDCTRL0_GET_VSYNC_PULSE_WIDTH(vdctrl0); + period = readl(host->base + LCDC_VDCTRL1); + vmode->upper_margin = GET_VERT_WAIT_CNT(vdctrl3) - vmode->vsync_len; + vmode->lower_margin = period - vmode->vsync_len - + vmode->upper_margin - vmode->yres; + + vmode->vmode = FB_VMODE_NONINTERLACED; + + vmode->sync = 0; + if (vdctrl0 & VDCTRL0_HSYNC_ACT_HIGH) + vmode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (vdctrl0 & VDCTRL0_VSYNC_ACT_HIGH) + vmode->sync |= FB_SYNC_VERT_HIGH_ACT; + + pr_debug("Reconstructed video mode:\n"); + pr_debug("%dx%d, hsync: %u left: %u, right: %u, vsync: %u, upper: %u, lower: %u\n", + vmode->xres, vmode->yres, vmode->hsync_len, vmode->left_margin, + vmode->right_margin, vmode->vsync_len, vmode->upper_margin, + vmode->lower_margin); + pr_debug("pixclk: %ldkHz\n", PICOS2KHZ(vmode->pixclock)); + + host->ld_intf_width = CTRL_GET_BUS_WIDTH(ctrl); + host->dotclk_delay = VDCTRL4_GET_DOTCLK_DLY(vdctrl4); + + fb_info->fix.line_length = vmode->xres * (bits_per_pixel >> 3); + + pa = readl(host->base + host->devdata->cur_buf); + fbsize = fb_info->fix.line_length * vmode->yres; + if (pa < fb_info->fix.smem_start) + return -EINVAL; + if (pa + fbsize > fb_info->fix.smem_start + fb_info->fix.smem_len) + return -EINVAL; + ofs = pa - fb_info->fix.smem_start; + if (ofs) { + memmove(fb_info->screen_base, fb_info->screen_base + ofs, fbsize); + writel(fb_info->fix.smem_start, host->base + host->devdata->next_buf); + } + + line_count = fb_info->fix.smem_len / fb_info->fix.line_length; + fb_info->fix.ypanstep = 1; + + clk_prepare_enable(host->clk); + host->enabled = 1; + + return 0; +} + +static int mxsfb_init_fbinfo_dt(struct mxsfb_info *host, + struct fb_videomode *vmode) +{ + struct fb_info *fb_info = &host->fb_info; + struct fb_var_screeninfo *var = &fb_info->var; + struct device *dev = &host->pdev->dev; + struct device_node *np = host->pdev->dev.of_node; + struct device_node *display_np; + struct videomode vm; + u32 width; + int ret; + + display_np = of_parse_phandle(np, "display", 0); + if (!display_np) { + dev_err(dev, "failed to find display phandle\n"); + return -ENOENT; + } + + ret = of_property_read_u32(display_np, "bus-width", &width); + if (ret < 0) { + dev_err(dev, "failed to get property bus-width\n"); + goto put_display_node; + } + + switch (width) { + case 8: + host->ld_intf_width = STMLCDIF_8BIT; + break; + case 16: + host->ld_intf_width = STMLCDIF_16BIT; + break; + case 18: + host->ld_intf_width = STMLCDIF_18BIT; + break; + case 24: + host->ld_intf_width = STMLCDIF_24BIT; + break; + default: + dev_err(dev, "invalid bus-width value\n"); + ret = -EINVAL; + goto put_display_node; + } + + ret = of_property_read_u32(display_np, "bits-per-pixel", + &var->bits_per_pixel); + if (ret < 0) { + dev_err(dev, "failed to get property bits-per-pixel\n"); + goto put_display_node; + } + + ret = of_get_videomode(display_np, &vm, OF_USE_NATIVE_MODE); + if (ret) { + dev_err(dev, "failed to get videomode from DT\n"); + goto put_display_node; + } + + ret = fb_videomode_from_videomode(&vm, vmode); + if (ret < 0) + goto put_display_node; + + if (vm.flags & DISPLAY_FLAGS_DE_HIGH) + host->sync |= MXSFB_SYNC_DATA_ENABLE_HIGH_ACT; + if (vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) + host->sync |= MXSFB_SYNC_DOTCLK_FALLING_ACT; + +put_display_node: + of_node_put(display_np); + return ret; +} + +static int mxsfb_init_fbinfo(struct mxsfb_info *host, + struct fb_videomode *vmode) +{ + int ret; + struct fb_info *fb_info = &host->fb_info; + struct fb_var_screeninfo *var = &fb_info->var; + dma_addr_t fb_phys; + void *fb_virt; + unsigned fb_size; + + fb_info->fbops = &mxsfb_ops; + fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST; + strlcpy(fb_info->fix.id, "mxs", sizeof(fb_info->fix.id)); + fb_info->fix.type = FB_TYPE_PACKED_PIXELS; + fb_info->fix.ypanstep = 1; + fb_info->fix.visual = FB_VISUAL_TRUECOLOR, + fb_info->fix.accel = FB_ACCEL_NONE; + + ret = mxsfb_init_fbinfo_dt(host, vmode); + if (ret) + return ret; + + var->nonstd = 0; + var->activate = FB_ACTIVATE_NOW; + var->accel_flags = 0; + var->vmode = FB_VMODE_NONINTERLACED; + + /* Memory allocation for framebuffer */ + fb_size = SZ_2M; + fb_virt = alloc_pages_exact(fb_size, GFP_DMA); + if (!fb_virt) + return -ENOMEM; + + fb_phys = virt_to_phys(fb_virt); + + fb_info->fix.smem_start = fb_phys; + fb_info->screen_base = fb_virt; + fb_info->screen_size = fb_info->fix.smem_len = fb_size; + + if (mxsfb_restore_mode(host, vmode)) + memset(fb_virt, 0, fb_size); + + return 0; +} + +static void mxsfb_free_videomem(struct mxsfb_info *host) +{ + struct fb_info *fb_info = &host->fb_info; + + free_pages_exact(fb_info->screen_base, fb_info->fix.smem_len); +} + +static struct platform_device_id mxsfb_devtype[] = { + { + .name = "imx23-fb", + .driver_data = MXSFB_V3, + }, { + .name = "imx28-fb", + .driver_data = MXSFB_V4, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, mxsfb_devtype); + +static const struct of_device_id mxsfb_dt_ids[] = { + { .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], }, + { .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxsfb_dt_ids); + +static int mxsfb_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(mxsfb_dt_ids, &pdev->dev); + struct resource *res; + struct mxsfb_info *host; + struct fb_info *fb_info; + struct fb_videomode *mode; + int ret; + + if (of_id) + pdev->id_entry = of_id->data; + + fb_info = framebuffer_alloc(sizeof(struct mxsfb_info), &pdev->dev); + if (!fb_info) { + dev_err(&pdev->dev, "Failed to allocate fbdev\n"); + return -ENOMEM; + } + + mode = devm_kzalloc(&pdev->dev, sizeof(struct fb_videomode), + GFP_KERNEL); + if (mode == NULL) + return -ENOMEM; + + host = to_imxfb_host(fb_info); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->base)) { + ret = PTR_ERR(host->base); + goto fb_release; + } + + host->pdev = pdev; + platform_set_drvdata(pdev, host); + + host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data]; + + host->clk = devm_clk_get(&host->pdev->dev, NULL); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + goto fb_release; + } + + host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd"); + if (IS_ERR(host->reg_lcd)) + host->reg_lcd = NULL; + + fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, + GFP_KERNEL); + if (!fb_info->pseudo_palette) { + ret = -ENOMEM; + goto fb_release; + } + + ret = mxsfb_init_fbinfo(host, mode); + if (ret != 0) + goto fb_release; + + fb_videomode_to_var(&fb_info->var, mode); + + /* init the color fields */ + mxsfb_check_var(&fb_info->var, fb_info); + + platform_set_drvdata(pdev, fb_info); + + ret = register_framebuffer(fb_info); + if (ret != 0) { + dev_err(&pdev->dev,"Failed to register framebuffer\n"); + goto fb_destroy; + } + + if (!host->enabled) { + writel(0, host->base + LCDC_CTRL); + mxsfb_set_par(fb_info); + mxsfb_enable_controller(fb_info); + } + + dev_info(&pdev->dev, "initialized\n"); + + return 0; + +fb_destroy: + if (host->enabled) + clk_disable_unprepare(host->clk); +fb_release: + framebuffer_release(fb_info); + + return ret; +} + +static int mxsfb_remove(struct platform_device *pdev) +{ + struct fb_info *fb_info = platform_get_drvdata(pdev); + struct mxsfb_info *host = to_imxfb_host(fb_info); + + if (host->enabled) + mxsfb_disable_controller(fb_info); + + unregister_framebuffer(fb_info); + mxsfb_free_videomem(host); + + framebuffer_release(fb_info); + + return 0; +} + +static void mxsfb_shutdown(struct platform_device *pdev) +{ + struct fb_info *fb_info = platform_get_drvdata(pdev); + struct mxsfb_info *host = to_imxfb_host(fb_info); + + /* + * Force stop the LCD controller as keeping it running during reboot + * might interfere with the BootROM's boot mode pads sampling. + */ + writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR); +} + +static struct platform_driver mxsfb_driver = { + .probe = mxsfb_probe, + .remove = mxsfb_remove, + .shutdown = mxsfb_shutdown, + .id_table = mxsfb_devtype, + .driver = { + .name = DRIVER_NAME, + .of_match_table = mxsfb_dt_ids, + }, +}; + +module_platform_driver(mxsfb_driver); + +MODULE_DESCRIPTION("Freescale mxs framebuffer driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/n411.c b/drivers/video/fbdev/n411.c new file mode 100644 index 000000000000..935830fea7b6 --- /dev/null +++ b/drivers/video/fbdev/n411.c @@ -0,0 +1,202 @@ +/* + * linux/drivers/video/n411.c -- Platform device for N411 EPD kit + * + * Copyright (C) 2008, Jaya Kumar + * + * 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. + * + * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. + * + * This driver is written to be used with the Hecuba display controller + * board, and tested with the EInk 800x600 display in 1 bit mode. + * The interface between Hecuba and the host is TTL based GPIO. The + * GPIO requirements are 8 writable data lines and 6 lines for control. + * Only 4 of the controls are actually used here but 6 for future use. + * The driver requires the IO addresses for data and control GPIO at + * load time. It is also possible to use this display with a standard + * PC parallel port. + * + * General notes: + * - User must set dio_addr=0xIOADDR cio_addr=0xIOADDR c2io_addr=0xIOADDR + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <linux/irq.h> + +#include <video/hecubafb.h> + +static unsigned long dio_addr; +static unsigned long cio_addr; +static unsigned long c2io_addr; +static unsigned long splashval; +static unsigned int nosplash; +static unsigned char ctl; + +static void n411_set_ctl(struct hecubafb_par *par, unsigned char bit, unsigned + char state) +{ + switch (bit) { + case HCB_CD_BIT: + if (state) + ctl &= ~(HCB_CD_BIT); + else + ctl |= HCB_CD_BIT; + break; + case HCB_DS_BIT: + if (state) + ctl &= ~(HCB_DS_BIT); + else + ctl |= HCB_DS_BIT; + break; + } + outb(ctl, cio_addr); +} + +static unsigned char n411_get_ctl(struct hecubafb_par *par) +{ + return inb(c2io_addr); +} + +static void n411_set_data(struct hecubafb_par *par, unsigned char value) +{ + outb(value, dio_addr); +} + +static void n411_wait_for_ack(struct hecubafb_par *par, int clear) +{ + int timeout; + unsigned char tmp; + + timeout = 500; + do { + tmp = n411_get_ctl(par); + if ((tmp & HCB_ACK_BIT) && (!clear)) + return; + else if (!(tmp & HCB_ACK_BIT) && (clear)) + return; + udelay(1); + } while (timeout--); + printk(KERN_ERR "timed out waiting for ack\n"); +} + +static int n411_init_control(struct hecubafb_par *par) +{ + unsigned char tmp; + /* for init, we want the following setup to be set: + WUP = lo + ACK = hi + DS = hi + RW = hi + CD = lo + */ + + /* write WUP to lo, DS to hi, RW to hi, CD to lo */ + ctl = HCB_WUP_BIT | HCB_RW_BIT | HCB_CD_BIT ; + n411_set_ctl(par, HCB_DS_BIT, 1); + + /* check ACK is not lo */ + tmp = n411_get_ctl(par); + if (tmp & HCB_ACK_BIT) { + printk(KERN_ERR "Fail because ACK is already low\n"); + return -ENXIO; + } + + return 0; +} + + +static int n411_init_board(struct hecubafb_par *par) +{ + int retval; + + retval = n411_init_control(par); + if (retval) + return retval; + + par->send_command(par, APOLLO_INIT_DISPLAY); + par->send_data(par, 0x81); + + /* have to wait while display resets */ + udelay(1000); + + /* if we were told to splash the screen, we just clear it */ + if (!nosplash) { + par->send_command(par, APOLLO_ERASE_DISPLAY); + par->send_data(par, splashval); + } + + return 0; +} + +static struct hecuba_board n411_board = { + .owner = THIS_MODULE, + .init = n411_init_board, + .set_ctl = n411_set_ctl, + .set_data = n411_set_data, + .wait_for_ack = n411_wait_for_ack, +}; + +static struct platform_device *n411_device; +static int __init n411_init(void) +{ + int ret; + if (!dio_addr || !cio_addr || !c2io_addr) { + printk(KERN_WARNING "no IO addresses supplied\n"); + return -EINVAL; + } + + /* request our platform independent driver */ + request_module("hecubafb"); + + n411_device = platform_device_alloc("hecubafb", -1); + if (!n411_device) + return -ENOMEM; + + platform_device_add_data(n411_device, &n411_board, sizeof(n411_board)); + + /* this _add binds hecubafb to n411. hecubafb refcounts n411 */ + ret = platform_device_add(n411_device); + + if (ret) + platform_device_put(n411_device); + + return ret; + +} + +static void __exit n411_exit(void) +{ + platform_device_unregister(n411_device); +} + +module_init(n411_init); +module_exit(n411_exit); + +module_param(nosplash, uint, 0); +MODULE_PARM_DESC(nosplash, "Disable doing the splash screen"); +module_param(dio_addr, ulong, 0); +MODULE_PARM_DESC(dio_addr, "IO address for data, eg: 0x480"); +module_param(cio_addr, ulong, 0); +MODULE_PARM_DESC(cio_addr, "IO address for control, eg: 0x400"); +module_param(c2io_addr, ulong, 0); +MODULE_PARM_DESC(c2io_addr, "IO address for secondary control, eg: 0x408"); +module_param(splashval, ulong, 0); +MODULE_PARM_DESC(splashval, "Splash pattern: 0x00 is black, 0x01 is white"); + +MODULE_DESCRIPTION("board driver for n411 hecuba/apollo epd kit"); +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/neofb.c b/drivers/video/fbdev/neofb.c new file mode 100644 index 000000000000..44f99a60bb9b --- /dev/null +++ b/drivers/video/fbdev/neofb.c @@ -0,0 +1,2247 @@ +/* + * linux/drivers/video/neofb.c -- NeoMagic Framebuffer Driver + * + * Copyright (c) 2001-2002 Denis Oliver Kropp <dok@directfb.org> + * + * + * Card specific code is based on XFree86's neomagic driver. + * Framebuffer framework code is based on code of cyber2000fb. + * + * 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. + * + * + * 0.4.1 + * - Cosmetic changes (dok) + * + * 0.4 + * - Toshiba Libretto support, allow modes larger than LCD size if + * LCD is disabled, keep BIOS settings if internal/external display + * haven't been enabled explicitly + * (Thomas J. Moore <dark@mama.indstate.edu>) + * + * 0.3.3 + * - Porting over to new fbdev api. (jsimmons) + * + * 0.3.2 + * - got rid of all floating point (dok) + * + * 0.3.1 + * - added module license (dok) + * + * 0.3 + * - hardware accelerated clear and move for 2200 and above (dok) + * - maximum allowed dotclock is handled now (dok) + * + * 0.2.1 + * - correct panning after X usage (dok) + * - added module and kernel parameters (dok) + * - no stretching if external display is enabled (dok) + * + * 0.2 + * - initial version (dok) + * + * + * TODO + * - ioctl for internal/external switching + * - blanking + * - 32bit depth support, maybe impossible + * - disable pan-on-sync, need specs + * + * BUGS + * - white margin on bootup like with tdfxfb (colormap problem?) + * + */ + +#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/pci.h> +#include <linux/init.h> +#ifdef CONFIG_TOSHIBA +#include <linux/toshiba.h> +#endif + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/pgtable.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include <video/vga.h> +#include <video/neomagic.h> + +#define NEOFB_VERSION "0.4.2" + +/* --------------------------------------------------------------------- */ + +static bool internal; +static bool external; +static bool libretto; +static bool nostretch; +static bool nopciburst; +static char *mode_option = NULL; + +#ifdef MODULE + +MODULE_AUTHOR("(c) 2001-2002 Denis Oliver Kropp <dok@convergence.de>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("FBDev driver for NeoMagic PCI Chips"); +module_param(internal, bool, 0); +MODULE_PARM_DESC(internal, "Enable output on internal LCD Display."); +module_param(external, bool, 0); +MODULE_PARM_DESC(external, "Enable output on external CRT."); +module_param(libretto, bool, 0); +MODULE_PARM_DESC(libretto, "Force Libretto 100/110 800x480 LCD."); +module_param(nostretch, bool, 0); +MODULE_PARM_DESC(nostretch, + "Disable stretching of modes smaller than LCD."); +module_param(nopciburst, bool, 0); +MODULE_PARM_DESC(nopciburst, "Disable PCI burst mode."); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Preferred video mode ('640x480-8@60', etc)"); + +#endif + + +/* --------------------------------------------------------------------- */ + +static biosMode bios8[] = { + {320, 240, 0x40}, + {300, 400, 0x42}, + {640, 400, 0x20}, + {640, 480, 0x21}, + {800, 600, 0x23}, + {1024, 768, 0x25}, +}; + +static biosMode bios16[] = { + {320, 200, 0x2e}, + {320, 240, 0x41}, + {300, 400, 0x43}, + {640, 480, 0x31}, + {800, 600, 0x34}, + {1024, 768, 0x37}, +}; + +static biosMode bios24[] = { + {640, 480, 0x32}, + {800, 600, 0x35}, + {1024, 768, 0x38} +}; + +#ifdef NO_32BIT_SUPPORT_YET +/* FIXME: guessed values, wrong */ +static biosMode bios32[] = { + {640, 480, 0x33}, + {800, 600, 0x36}, + {1024, 768, 0x39} +}; +#endif + +static inline void write_le32(int regindex, u32 val, const struct neofb_par *par) +{ + writel(val, par->neo2200 + par->cursorOff + regindex); +} + +static int neoFindMode(int xres, int yres, int depth) +{ + int xres_s; + int i, size; + biosMode *mode; + + switch (depth) { + case 8: + size = ARRAY_SIZE(bios8); + mode = bios8; + break; + case 16: + size = ARRAY_SIZE(bios16); + mode = bios16; + break; + case 24: + size = ARRAY_SIZE(bios24); + mode = bios24; + break; +#ifdef NO_32BIT_SUPPORT_YET + case 32: + size = ARRAY_SIZE(bios32); + mode = bios32; + break; +#endif + default: + return 0; + } + + for (i = 0; i < size; i++) { + if (xres <= mode[i].x_res) { + xres_s = mode[i].x_res; + for (; i < size; i++) { + if (mode[i].x_res != xres_s) + return mode[i - 1].mode; + if (yres <= mode[i].y_res) + return mode[i].mode; + } + } + } + return mode[size - 1].mode; +} + +/* + * neoCalcVCLK -- + * + * Determine the closest clock frequency to the one requested. + */ +#define MAX_N 127 +#define MAX_D 31 +#define MAX_F 1 + +static void neoCalcVCLK(const struct fb_info *info, + struct neofb_par *par, long freq) +{ + int n, d, f; + int n_best = 0, d_best = 0, f_best = 0; + long f_best_diff = 0x7ffff; + + for (f = 0; f <= MAX_F; f++) + for (d = 0; d <= MAX_D; d++) + for (n = 0; n <= MAX_N; n++) { + long f_out; + long f_diff; + + f_out = ((14318 * (n + 1)) / (d + 1)) >> f; + f_diff = abs(f_out - freq); + if (f_diff <= f_best_diff) { + f_best_diff = f_diff; + n_best = n; + d_best = d; + f_best = f; + } + if (f_out > freq) + break; + } + + if (info->fix.accel == FB_ACCEL_NEOMAGIC_NM2200 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2230 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2360 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2380) { + /* NOT_DONE: We are trying the full range of the 2200 clock. + We should be able to try n up to 2047 */ + par->VCLK3NumeratorLow = n_best; + par->VCLK3NumeratorHigh = (f_best << 7); + } else + par->VCLK3NumeratorLow = n_best | (f_best << 7); + + par->VCLK3Denominator = d_best; + +#ifdef NEOFB_DEBUG + printk(KERN_DEBUG "neoVCLK: f:%ld NumLow=%d NumHi=%d Den=%d Df=%ld\n", + freq, + par->VCLK3NumeratorLow, + par->VCLK3NumeratorHigh, + par->VCLK3Denominator, f_best_diff); +#endif +} + +/* + * vgaHWInit -- + * Handle the initialization, etc. of a screen. + * Return FALSE on failure. + */ + +static int vgaHWInit(const struct fb_var_screeninfo *var, + struct neofb_par *par) +{ + int hsync_end = var->xres + var->right_margin + var->hsync_len; + int htotal = (hsync_end + var->left_margin) >> 3; + int vsync_start = var->yres + var->lower_margin; + int vsync_end = vsync_start + var->vsync_len; + int vtotal = vsync_end + var->upper_margin; + + par->MiscOutReg = 0x23; + + if (!(var->sync & FB_SYNC_HOR_HIGH_ACT)) + par->MiscOutReg |= 0x40; + + if (!(var->sync & FB_SYNC_VERT_HIGH_ACT)) + par->MiscOutReg |= 0x80; + + /* + * Time Sequencer + */ + par->Sequencer[0] = 0x00; + par->Sequencer[1] = 0x01; + par->Sequencer[2] = 0x0F; + par->Sequencer[3] = 0x00; /* Font select */ + par->Sequencer[4] = 0x0E; /* Misc */ + + /* + * CRTC Controller + */ + par->CRTC[0] = htotal - 5; + par->CRTC[1] = (var->xres >> 3) - 1; + par->CRTC[2] = (var->xres >> 3) - 1; + par->CRTC[3] = ((htotal - 1) & 0x1F) | 0x80; + par->CRTC[4] = ((var->xres + var->right_margin) >> 3); + par->CRTC[5] = (((htotal - 1) & 0x20) << 2) + | (((hsync_end >> 3)) & 0x1F); + par->CRTC[6] = (vtotal - 2) & 0xFF; + par->CRTC[7] = (((vtotal - 2) & 0x100) >> 8) + | (((var->yres - 1) & 0x100) >> 7) + | ((vsync_start & 0x100) >> 6) + | (((var->yres - 1) & 0x100) >> 5) + | 0x10 | (((vtotal - 2) & 0x200) >> 4) + | (((var->yres - 1) & 0x200) >> 3) + | ((vsync_start & 0x200) >> 2); + par->CRTC[8] = 0x00; + par->CRTC[9] = (((var->yres - 1) & 0x200) >> 4) | 0x40; + + if (var->vmode & FB_VMODE_DOUBLE) + par->CRTC[9] |= 0x80; + + par->CRTC[10] = 0x00; + par->CRTC[11] = 0x00; + par->CRTC[12] = 0x00; + par->CRTC[13] = 0x00; + par->CRTC[14] = 0x00; + par->CRTC[15] = 0x00; + par->CRTC[16] = vsync_start & 0xFF; + par->CRTC[17] = (vsync_end & 0x0F) | 0x20; + par->CRTC[18] = (var->yres - 1) & 0xFF; + par->CRTC[19] = var->xres_virtual >> 4; + par->CRTC[20] = 0x00; + par->CRTC[21] = (var->yres - 1) & 0xFF; + par->CRTC[22] = (vtotal - 1) & 0xFF; + par->CRTC[23] = 0xC3; + par->CRTC[24] = 0xFF; + + /* + * are these unnecessary? + * vgaHWHBlankKGA(mode, regp, 0, KGA_FIX_OVERSCAN | KGA_ENABLE_ON_ZERO); + * vgaHWVBlankKGA(mode, regp, 0, KGA_FIX_OVERSCAN | KGA_ENABLE_ON_ZERO); + */ + + /* + * Graphics Display Controller + */ + par->Graphics[0] = 0x00; + par->Graphics[1] = 0x00; + par->Graphics[2] = 0x00; + par->Graphics[3] = 0x00; + par->Graphics[4] = 0x00; + par->Graphics[5] = 0x40; + par->Graphics[6] = 0x05; /* only map 64k VGA memory !!!! */ + par->Graphics[7] = 0x0F; + par->Graphics[8] = 0xFF; + + + par->Attribute[0] = 0x00; /* standard colormap translation */ + par->Attribute[1] = 0x01; + par->Attribute[2] = 0x02; + par->Attribute[3] = 0x03; + par->Attribute[4] = 0x04; + par->Attribute[5] = 0x05; + par->Attribute[6] = 0x06; + par->Attribute[7] = 0x07; + par->Attribute[8] = 0x08; + par->Attribute[9] = 0x09; + par->Attribute[10] = 0x0A; + par->Attribute[11] = 0x0B; + par->Attribute[12] = 0x0C; + par->Attribute[13] = 0x0D; + par->Attribute[14] = 0x0E; + par->Attribute[15] = 0x0F; + par->Attribute[16] = 0x41; + par->Attribute[17] = 0xFF; + par->Attribute[18] = 0x0F; + par->Attribute[19] = 0x00; + par->Attribute[20] = 0x00; + return 0; +} + +static void vgaHWLock(struct vgastate *state) +{ + /* Protect CRTC[0-7] */ + vga_wcrt(state->vgabase, 0x11, vga_rcrt(state->vgabase, 0x11) | 0x80); +} + +static void vgaHWUnlock(void) +{ + /* Unprotect CRTC[0-7] */ + vga_wcrt(NULL, 0x11, vga_rcrt(NULL, 0x11) & ~0x80); +} + +static void neoLock(struct vgastate *state) +{ + vga_wgfx(state->vgabase, 0x09, 0x00); + vgaHWLock(state); +} + +static void neoUnlock(void) +{ + vgaHWUnlock(); + vga_wgfx(NULL, 0x09, 0x26); +} + +/* + * VGA Palette management + */ +static int paletteEnabled = 0; + +static inline void VGAenablePalette(void) +{ + vga_r(NULL, VGA_IS1_RC); + vga_w(NULL, VGA_ATT_W, 0x00); + paletteEnabled = 1; +} + +static inline void VGAdisablePalette(void) +{ + vga_r(NULL, VGA_IS1_RC); + vga_w(NULL, VGA_ATT_W, 0x20); + paletteEnabled = 0; +} + +static inline void VGAwATTR(u8 index, u8 value) +{ + if (paletteEnabled) + index &= ~0x20; + else + index |= 0x20; + + vga_r(NULL, VGA_IS1_RC); + vga_wattr(NULL, index, value); +} + +static void vgaHWProtect(int on) +{ + unsigned char tmp; + + tmp = vga_rseq(NULL, 0x01); + if (on) { + /* + * Turn off screen and disable sequencer. + */ + vga_wseq(NULL, 0x00, 0x01); /* Synchronous Reset */ + vga_wseq(NULL, 0x01, tmp | 0x20); /* disable the display */ + + VGAenablePalette(); + } else { + /* + * Reenable sequencer, then turn on screen. + */ + vga_wseq(NULL, 0x01, tmp & ~0x20); /* reenable display */ + vga_wseq(NULL, 0x00, 0x03); /* clear synchronousreset */ + + VGAdisablePalette(); + } +} + +static void vgaHWRestore(const struct fb_info *info, + const struct neofb_par *par) +{ + int i; + + vga_w(NULL, VGA_MIS_W, par->MiscOutReg); + + for (i = 1; i < 5; i++) + vga_wseq(NULL, i, par->Sequencer[i]); + + /* Ensure CRTC registers 0-7 are unlocked by clearing bit 7 or CRTC[17] */ + vga_wcrt(NULL, 17, par->CRTC[17] & ~0x80); + + for (i = 0; i < 25; i++) + vga_wcrt(NULL, i, par->CRTC[i]); + + for (i = 0; i < 9; i++) + vga_wgfx(NULL, i, par->Graphics[i]); + + VGAenablePalette(); + + for (i = 0; i < 21; i++) + VGAwATTR(i, par->Attribute[i]); + + VGAdisablePalette(); +} + + +/* -------------------- Hardware specific routines ------------------------- */ + +/* + * Hardware Acceleration for Neo2200+ + */ +static inline int neo2200_sync(struct fb_info *info) +{ + struct neofb_par *par = info->par; + + while (readl(&par->neo2200->bltStat) & 1) + cpu_relax(); + return 0; +} + +static inline void neo2200_wait_fifo(struct fb_info *info, + int requested_fifo_space) +{ + // ndev->neo.waitfifo_calls++; + // ndev->neo.waitfifo_sum += requested_fifo_space; + + /* FIXME: does not work + if (neo_fifo_space < requested_fifo_space) + { + neo_fifo_waitcycles++; + + while (1) + { + neo_fifo_space = (neo2200->bltStat >> 8); + if (neo_fifo_space >= requested_fifo_space) + break; + } + } + else + { + neo_fifo_cache_hits++; + } + + neo_fifo_space -= requested_fifo_space; + */ + + neo2200_sync(info); +} + +static inline void neo2200_accel_init(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct neofb_par *par = info->par; + Neo2200 __iomem *neo2200 = par->neo2200; + u32 bltMod, pitch; + + neo2200_sync(info); + + switch (var->bits_per_pixel) { + case 8: + bltMod = NEO_MODE1_DEPTH8; + pitch = var->xres_virtual; + break; + case 15: + case 16: + bltMod = NEO_MODE1_DEPTH16; + pitch = var->xres_virtual * 2; + break; + case 24: + bltMod = NEO_MODE1_DEPTH24; + pitch = var->xres_virtual * 3; + break; + default: + printk(KERN_ERR + "neofb: neo2200_accel_init: unexpected bits per pixel!\n"); + return; + } + + writel(bltMod << 16, &neo2200->bltStat); + writel((pitch << 16) | pitch, &neo2200->pitch); +} + +/* --------------------------------------------------------------------- */ + +static int +neofb_open(struct fb_info *info, int user) +{ + struct neofb_par *par = info->par; + + if (!par->ref_count) { + memset(&par->state, 0, sizeof(struct vgastate)); + par->state.flags = VGA_SAVE_MODE | VGA_SAVE_FONTS; + save_vga(&par->state); + } + par->ref_count++; + + return 0; +} + +static int +neofb_release(struct fb_info *info, int user) +{ + struct neofb_par *par = info->par; + + if (!par->ref_count) + return -EINVAL; + + if (par->ref_count == 1) { + restore_vga(&par->state); + } + par->ref_count--; + + return 0; +} + +static int +neofb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct neofb_par *par = info->par; + int memlen, vramlen; + int mode_ok = 0; + + DBG("neofb_check_var"); + + if (PICOS2KHZ(var->pixclock) > par->maxClock) + return -EINVAL; + + /* Is the mode larger than the LCD panel? */ + if (par->internal_display && + ((var->xres > par->NeoPanelWidth) || + (var->yres > par->NeoPanelHeight))) { + printk(KERN_INFO + "Mode (%dx%d) larger than the LCD panel (%dx%d)\n", + var->xres, var->yres, par->NeoPanelWidth, + par->NeoPanelHeight); + return -EINVAL; + } + + /* Is the mode one of the acceptable sizes? */ + if (!par->internal_display) + mode_ok = 1; + else { + switch (var->xres) { + case 1280: + if (var->yres == 1024) + mode_ok = 1; + break; + case 1024: + if (var->yres == 768) + mode_ok = 1; + break; + case 800: + if (var->yres == (par->libretto ? 480 : 600)) + mode_ok = 1; + break; + case 640: + if (var->yres == 480) + mode_ok = 1; + break; + } + } + + if (!mode_ok) { + printk(KERN_INFO + "Mode (%dx%d) won't display properly on LCD\n", + var->xres, var->yres); + return -EINVAL; + } + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + var->transp.offset = 0; + var->transp.length = 0; + switch (var->bits_per_pixel) { + case 8: /* PSEUDOCOLOUR, 256 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + case 16: /* DIRECTCOLOUR, 64k */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + + case 24: /* TRUECOLOUR, 16m */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + +#ifdef NO_32BIT_SUPPORT_YET + case 32: /* TRUECOLOUR, 16m */ + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; +#endif + default: + printk(KERN_WARNING "neofb: no support for %dbpp\n", + var->bits_per_pixel); + return -EINVAL; + } + + vramlen = info->fix.smem_len; + if (vramlen > 4 * 1024 * 1024) + vramlen = 4 * 1024 * 1024; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + memlen = var->xres_virtual * var->bits_per_pixel * var->yres_virtual >> 3; + + if (memlen > vramlen) { + var->yres_virtual = vramlen * 8 / (var->xres_virtual * + var->bits_per_pixel); + memlen = var->xres_virtual * var->bits_per_pixel * + var->yres_virtual / 8; + } + + /* we must round yres/xres down, we already rounded y/xres_virtual up + if it was possible. We should return -EINVAL, but I disagree */ + if (var->yres_virtual < var->yres) + var->yres = var->yres_virtual; + if (var->xoffset + var->xres > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yoffset + var->yres > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + var->nonstd = 0; + var->height = -1; + var->width = -1; + + if (var->bits_per_pixel >= 24 || !par->neo2200) + var->accel_flags &= ~FB_ACCELF_TEXT; + return 0; +} + +static int neofb_set_par(struct fb_info *info) +{ + struct neofb_par *par = info->par; + unsigned char temp; + int i, clock_hi = 0; + int lcd_stretch; + int hoffset, voffset; + int vsync_start, vtotal; + + DBG("neofb_set_par"); + + neoUnlock(); + + vgaHWProtect(1); /* Blank the screen */ + + vsync_start = info->var.yres + info->var.lower_margin; + vtotal = vsync_start + info->var.vsync_len + info->var.upper_margin; + + /* + * This will allocate the datastructure and initialize all of the + * generic VGA registers. + */ + + if (vgaHWInit(&info->var, par)) + return -EINVAL; + + /* + * The default value assigned by vgaHW.c is 0x41, but this does + * not work for NeoMagic. + */ + par->Attribute[16] = 0x01; + + switch (info->var.bits_per_pixel) { + case 8: + par->CRTC[0x13] = info->var.xres_virtual >> 3; + par->ExtCRTOffset = info->var.xres_virtual >> 11; + par->ExtColorModeSelect = 0x11; + break; + case 16: + par->CRTC[0x13] = info->var.xres_virtual >> 2; + par->ExtCRTOffset = info->var.xres_virtual >> 10; + par->ExtColorModeSelect = 0x13; + break; + case 24: + par->CRTC[0x13] = (info->var.xres_virtual * 3) >> 3; + par->ExtCRTOffset = (info->var.xres_virtual * 3) >> 11; + par->ExtColorModeSelect = 0x14; + break; +#ifdef NO_32BIT_SUPPORT_YET + case 32: /* FIXME: guessed values */ + par->CRTC[0x13] = info->var.xres_virtual >> 1; + par->ExtCRTOffset = info->var.xres_virtual >> 9; + par->ExtColorModeSelect = 0x15; + break; +#endif + default: + break; + } + + par->ExtCRTDispAddr = 0x10; + + /* Vertical Extension */ + par->VerticalExt = (((vtotal - 2) & 0x400) >> 10) + | (((info->var.yres - 1) & 0x400) >> 9) + | (((vsync_start) & 0x400) >> 8) + | (((vsync_start) & 0x400) >> 7); + + /* Fast write bursts on unless disabled. */ + if (par->pci_burst) + par->SysIfaceCntl1 = 0x30; + else + par->SysIfaceCntl1 = 0x00; + + par->SysIfaceCntl2 = 0xc0; /* VESA Bios sets this to 0x80! */ + + /* Initialize: by default, we want display config register to be read */ + par->PanelDispCntlRegRead = 1; + + /* Enable any user specified display devices. */ + par->PanelDispCntlReg1 = 0x00; + if (par->internal_display) + par->PanelDispCntlReg1 |= 0x02; + if (par->external_display) + par->PanelDispCntlReg1 |= 0x01; + + /* If the user did not specify any display devices, then... */ + if (par->PanelDispCntlReg1 == 0x00) { + /* Default to internal (i.e., LCD) only. */ + par->PanelDispCntlReg1 = vga_rgfx(NULL, 0x20) & 0x03; + } + + /* If we are using a fixed mode, then tell the chip we are. */ + switch (info->var.xres) { + case 1280: + par->PanelDispCntlReg1 |= 0x60; + break; + case 1024: + par->PanelDispCntlReg1 |= 0x40; + break; + case 800: + par->PanelDispCntlReg1 |= 0x20; + break; + case 640: + default: + break; + } + + /* Setup shadow register locking. */ + switch (par->PanelDispCntlReg1 & 0x03) { + case 0x01: /* External CRT only mode: */ + par->GeneralLockReg = 0x00; + /* We need to program the VCLK for external display only mode. */ + par->ProgramVCLK = 1; + break; + case 0x02: /* Internal LCD only mode: */ + case 0x03: /* Simultaneous internal/external (LCD/CRT) mode: */ + par->GeneralLockReg = 0x01; + /* Don't program the VCLK when using the LCD. */ + par->ProgramVCLK = 0; + break; + } + + /* + * If the screen is to be stretched, turn on stretching for the + * various modes. + * + * OPTION_LCD_STRETCH means stretching should be turned off! + */ + par->PanelDispCntlReg2 = 0x00; + par->PanelDispCntlReg3 = 0x00; + + if (par->lcd_stretch && (par->PanelDispCntlReg1 == 0x02) && /* LCD only */ + (info->var.xres != par->NeoPanelWidth)) { + switch (info->var.xres) { + case 320: /* Needs testing. KEM -- 24 May 98 */ + case 400: /* Needs testing. KEM -- 24 May 98 */ + case 640: + case 800: + case 1024: + lcd_stretch = 1; + par->PanelDispCntlReg2 |= 0xC6; + break; + default: + lcd_stretch = 0; + /* No stretching in these modes. */ + } + } else + lcd_stretch = 0; + + /* + * If the screen is to be centerd, turn on the centering for the + * various modes. + */ + par->PanelVertCenterReg1 = 0x00; + par->PanelVertCenterReg2 = 0x00; + par->PanelVertCenterReg3 = 0x00; + par->PanelVertCenterReg4 = 0x00; + par->PanelVertCenterReg5 = 0x00; + par->PanelHorizCenterReg1 = 0x00; + par->PanelHorizCenterReg2 = 0x00; + par->PanelHorizCenterReg3 = 0x00; + par->PanelHorizCenterReg4 = 0x00; + par->PanelHorizCenterReg5 = 0x00; + + + if (par->PanelDispCntlReg1 & 0x02) { + if (info->var.xres == par->NeoPanelWidth) { + /* + * No centering required when the requested display width + * equals the panel width. + */ + } else { + par->PanelDispCntlReg2 |= 0x01; + par->PanelDispCntlReg3 |= 0x10; + + /* Calculate the horizontal and vertical offsets. */ + if (!lcd_stretch) { + hoffset = + ((par->NeoPanelWidth - + info->var.xres) >> 4) - 1; + voffset = + ((par->NeoPanelHeight - + info->var.yres) >> 1) - 2; + } else { + /* Stretched modes cannot be centered. */ + hoffset = 0; + voffset = 0; + } + + switch (info->var.xres) { + case 320: /* Needs testing. KEM -- 24 May 98 */ + par->PanelHorizCenterReg3 = hoffset; + par->PanelVertCenterReg2 = voffset; + break; + case 400: /* Needs testing. KEM -- 24 May 98 */ + par->PanelHorizCenterReg4 = hoffset; + par->PanelVertCenterReg1 = voffset; + break; + case 640: + par->PanelHorizCenterReg1 = hoffset; + par->PanelVertCenterReg3 = voffset; + break; + case 800: + par->PanelHorizCenterReg2 = hoffset; + par->PanelVertCenterReg4 = voffset; + break; + case 1024: + par->PanelHorizCenterReg5 = hoffset; + par->PanelVertCenterReg5 = voffset; + break; + case 1280: + default: + /* No centering in these modes. */ + break; + } + } + } + + par->biosMode = + neoFindMode(info->var.xres, info->var.yres, + info->var.bits_per_pixel); + + /* + * Calculate the VCLK that most closely matches the requested dot + * clock. + */ + neoCalcVCLK(info, par, PICOS2KHZ(info->var.pixclock)); + + /* Since we program the clocks ourselves, always use VCLK3. */ + par->MiscOutReg |= 0x0C; + + /* alread unlocked above */ + /* BOGUS vga_wgfx(NULL, 0x09, 0x26); */ + + /* don't know what this is, but it's 0 from bootup anyway */ + vga_wgfx(NULL, 0x15, 0x00); + + /* was set to 0x01 by my bios in text and vesa modes */ + vga_wgfx(NULL, 0x0A, par->GeneralLockReg); + + /* + * The color mode needs to be set before calling vgaHWRestore + * to ensure the DAC is initialized properly. + * + * NOTE: Make sure we don't change bits make sure we don't change + * any reserved bits. + */ + temp = vga_rgfx(NULL, 0x90); + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2070: + temp &= 0xF0; /* Save bits 7:4 */ + temp |= (par->ExtColorModeSelect & ~0xF0); + break; + case FB_ACCEL_NEOMAGIC_NM2090: + case FB_ACCEL_NEOMAGIC_NM2093: + case FB_ACCEL_NEOMAGIC_NM2097: + case FB_ACCEL_NEOMAGIC_NM2160: + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + temp &= 0x70; /* Save bits 6:4 */ + temp |= (par->ExtColorModeSelect & ~0x70); + break; + } + + vga_wgfx(NULL, 0x90, temp); + + /* + * In some rare cases a lockup might occur if we don't delay + * here. (Reported by Miles Lane) + */ + //mdelay(200); + + /* + * Disable horizontal and vertical graphics and text expansions so + * that vgaHWRestore works properly. + */ + temp = vga_rgfx(NULL, 0x25); + temp &= 0x39; + vga_wgfx(NULL, 0x25, temp); + + /* + * Sleep for 200ms to make sure that the two operations above have + * had time to take effect. + */ + mdelay(200); + + /* + * This function handles restoring the generic VGA registers. */ + vgaHWRestore(info, par); + + /* linear colormap for non palettized modes */ + switch (info->var.bits_per_pixel) { + case 8: + /* PseudoColor, 256 */ + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + case 16: + /* TrueColor, 64k */ + info->fix.visual = FB_VISUAL_TRUECOLOR; + + for (i = 0; i < 64; i++) { + outb(i, 0x3c8); + + outb(i << 1, 0x3c9); + outb(i, 0x3c9); + outb(i << 1, 0x3c9); + } + break; + case 24: +#ifdef NO_32BIT_SUPPORT_YET + case 32: +#endif + /* TrueColor, 16m */ + info->fix.visual = FB_VISUAL_TRUECOLOR; + + for (i = 0; i < 256; i++) { + outb(i, 0x3c8); + + outb(i, 0x3c9); + outb(i, 0x3c9); + outb(i, 0x3c9); + } + break; + } + + vga_wgfx(NULL, 0x0E, par->ExtCRTDispAddr); + vga_wgfx(NULL, 0x0F, par->ExtCRTOffset); + temp = vga_rgfx(NULL, 0x10); + temp &= 0x0F; /* Save bits 3:0 */ + temp |= (par->SysIfaceCntl1 & ~0x0F); /* VESA Bios sets bit 1! */ + vga_wgfx(NULL, 0x10, temp); + + vga_wgfx(NULL, 0x11, par->SysIfaceCntl2); + vga_wgfx(NULL, 0x15, 0 /*par->SingleAddrPage */ ); + vga_wgfx(NULL, 0x16, 0 /*par->DualAddrPage */ ); + + temp = vga_rgfx(NULL, 0x20); + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2070: + temp &= 0xFC; /* Save bits 7:2 */ + temp |= (par->PanelDispCntlReg1 & ~0xFC); + break; + case FB_ACCEL_NEOMAGIC_NM2090: + case FB_ACCEL_NEOMAGIC_NM2093: + case FB_ACCEL_NEOMAGIC_NM2097: + case FB_ACCEL_NEOMAGIC_NM2160: + temp &= 0xDC; /* Save bits 7:6,4:2 */ + temp |= (par->PanelDispCntlReg1 & ~0xDC); + break; + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + temp &= 0x98; /* Save bits 7,4:3 */ + temp |= (par->PanelDispCntlReg1 & ~0x98); + break; + } + vga_wgfx(NULL, 0x20, temp); + + temp = vga_rgfx(NULL, 0x25); + temp &= 0x38; /* Save bits 5:3 */ + temp |= (par->PanelDispCntlReg2 & ~0x38); + vga_wgfx(NULL, 0x25, temp); + + if (info->fix.accel != FB_ACCEL_NEOMAGIC_NM2070) { + temp = vga_rgfx(NULL, 0x30); + temp &= 0xEF; /* Save bits 7:5 and bits 3:0 */ + temp |= (par->PanelDispCntlReg3 & ~0xEF); + vga_wgfx(NULL, 0x30, temp); + } + + vga_wgfx(NULL, 0x28, par->PanelVertCenterReg1); + vga_wgfx(NULL, 0x29, par->PanelVertCenterReg2); + vga_wgfx(NULL, 0x2a, par->PanelVertCenterReg3); + + if (info->fix.accel != FB_ACCEL_NEOMAGIC_NM2070) { + vga_wgfx(NULL, 0x32, par->PanelVertCenterReg4); + vga_wgfx(NULL, 0x33, par->PanelHorizCenterReg1); + vga_wgfx(NULL, 0x34, par->PanelHorizCenterReg2); + vga_wgfx(NULL, 0x35, par->PanelHorizCenterReg3); + } + + if (info->fix.accel == FB_ACCEL_NEOMAGIC_NM2160) + vga_wgfx(NULL, 0x36, par->PanelHorizCenterReg4); + + if (info->fix.accel == FB_ACCEL_NEOMAGIC_NM2200 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2230 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2360 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2380) { + vga_wgfx(NULL, 0x36, par->PanelHorizCenterReg4); + vga_wgfx(NULL, 0x37, par->PanelVertCenterReg5); + vga_wgfx(NULL, 0x38, par->PanelHorizCenterReg5); + + clock_hi = 1; + } + + /* Program VCLK3 if needed. */ + if (par->ProgramVCLK && ((vga_rgfx(NULL, 0x9B) != par->VCLK3NumeratorLow) + || (vga_rgfx(NULL, 0x9F) != par->VCLK3Denominator) + || (clock_hi && ((vga_rgfx(NULL, 0x8F) & ~0x0f) + != (par->VCLK3NumeratorHigh & + ~0x0F))))) { + vga_wgfx(NULL, 0x9B, par->VCLK3NumeratorLow); + if (clock_hi) { + temp = vga_rgfx(NULL, 0x8F); + temp &= 0x0F; /* Save bits 3:0 */ + temp |= (par->VCLK3NumeratorHigh & ~0x0F); + vga_wgfx(NULL, 0x8F, temp); + } + vga_wgfx(NULL, 0x9F, par->VCLK3Denominator); + } + + if (par->biosMode) + vga_wcrt(NULL, 0x23, par->biosMode); + + vga_wgfx(NULL, 0x93, 0xc0); /* Gives 5x faster framebuffer writes !!! */ + + /* Program vertical extension register */ + if (info->fix.accel == FB_ACCEL_NEOMAGIC_NM2200 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2230 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2360 || + info->fix.accel == FB_ACCEL_NEOMAGIC_NM2380) { + vga_wcrt(NULL, 0x70, par->VerticalExt); + } + + vgaHWProtect(0); /* Turn on screen */ + + /* Calling this also locks offset registers required in update_start */ + neoLock(&par->state); + + info->fix.line_length = + info->var.xres_virtual * (info->var.bits_per_pixel >> 3); + + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + neo2200_accel_init(info, &info->var); + break; + default: + break; + } + return 0; +} + +/* + * Pan or Wrap the Display + */ +static int neofb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct neofb_par *par = info->par; + struct vgastate *state = &par->state; + int oldExtCRTDispAddr; + int Base; + + DBG("neofb_update_start"); + + Base = (var->yoffset * info->var.xres_virtual + var->xoffset) >> 2; + Base *= (info->var.bits_per_pixel + 7) / 8; + + neoUnlock(); + + /* + * These are the generic starting address registers. + */ + vga_wcrt(state->vgabase, 0x0C, (Base & 0x00FF00) >> 8); + vga_wcrt(state->vgabase, 0x0D, (Base & 0x00FF)); + + /* + * Make sure we don't clobber some other bits that might already + * have been set. NOTE: NM2200 has a writable bit 3, but it shouldn't + * be needed. + */ + oldExtCRTDispAddr = vga_rgfx(NULL, 0x0E); + vga_wgfx(state->vgabase, 0x0E, (((Base >> 16) & 0x0f) | (oldExtCRTDispAddr & 0xf0))); + + neoLock(state); + + return 0; +} + +static int neofb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *fb) +{ + if (regno >= fb->cmap.len || regno > 255) + return -EINVAL; + + if (fb->var.bits_per_pixel <= 8) { + outb(regno, 0x3c8); + + outb(red >> 10, 0x3c9); + outb(green >> 10, 0x3c9); + outb(blue >> 10, 0x3c9); + } else if (regno < 16) { + switch (fb->var.bits_per_pixel) { + case 16: + ((u32 *) fb->pseudo_palette)[regno] = + ((red & 0xf800)) | ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + case 24: + ((u32 *) fb->pseudo_palette)[regno] = + ((red & 0xff00) << 8) | ((green & 0xff00)) | + ((blue & 0xff00) >> 8); + break; +#ifdef NO_32BIT_SUPPORT_YET + case 32: + ((u32 *) fb->pseudo_palette)[regno] = + ((transp & 0xff00) << 16) | ((red & 0xff00) << 8) | + ((green & 0xff00)) | ((blue & 0xff00) >> 8); + break; +#endif + default: + return 1; + } + } + + return 0; +} + +/* + * (Un)Blank the display. + */ +static int neofb_blank(int blank_mode, struct fb_info *info) +{ + /* + * Blank the screen if blank_mode != 0, else unblank. + * Return 0 if blanking succeeded, != 0 if un-/blanking failed due to + * e.g. a video mode which doesn't support it. Implements VESA suspend + * and powerdown modes for monitors, and backlight control on LCDs. + * blank_mode == 0: unblanked (backlight on) + * blank_mode == 1: blank (backlight on) + * blank_mode == 2: suspend vsync (backlight off) + * blank_mode == 3: suspend hsync (backlight off) + * blank_mode == 4: powerdown (backlight off) + * + * wms...Enable VESA DPMS compatible powerdown mode + * run "setterm -powersave powerdown" to take advantage + */ + struct neofb_par *par = info->par; + int seqflags, lcdflags, dpmsflags, reg, tmpdisp; + + /* + * Read back the register bits related to display configuration. They might + * have been changed underneath the driver via Fn key stroke. + */ + neoUnlock(); + tmpdisp = vga_rgfx(NULL, 0x20) & 0x03; + neoLock(&par->state); + + /* In case we blank the screen, we want to store the possibly new + * configuration in the driver. During un-blank, we re-apply this setting, + * since the LCD bit will be cleared in order to switch off the backlight. + */ + if (par->PanelDispCntlRegRead) { + par->PanelDispCntlReg1 = tmpdisp; + } + par->PanelDispCntlRegRead = !blank_mode; + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: /* powerdown - both sync lines down */ + seqflags = VGA_SR01_SCREEN_OFF; /* Disable sequencer */ + lcdflags = 0; /* LCD off */ + dpmsflags = NEO_GR01_SUPPRESS_HSYNC | + NEO_GR01_SUPPRESS_VSYNC; +#ifdef CONFIG_TOSHIBA + /* Do we still need this ? */ + /* attempt to turn off backlight on toshiba; also turns off external */ + { + SMMRegisters regs; + + regs.eax = 0xff00; /* HCI_SET */ + regs.ebx = 0x0002; /* HCI_BACKLIGHT */ + regs.ecx = 0x0000; /* HCI_DISABLE */ + tosh_smm(®s); + } +#endif + break; + case FB_BLANK_HSYNC_SUSPEND: /* hsync off */ + seqflags = VGA_SR01_SCREEN_OFF; /* Disable sequencer */ + lcdflags = 0; /* LCD off */ + dpmsflags = NEO_GR01_SUPPRESS_HSYNC; + break; + case FB_BLANK_VSYNC_SUSPEND: /* vsync off */ + seqflags = VGA_SR01_SCREEN_OFF; /* Disable sequencer */ + lcdflags = 0; /* LCD off */ + dpmsflags = NEO_GR01_SUPPRESS_VSYNC; + break; + case FB_BLANK_NORMAL: /* just blank screen (backlight stays on) */ + seqflags = VGA_SR01_SCREEN_OFF; /* Disable sequencer */ + /* + * During a blank operation with the LID shut, we might store "LCD off" + * by mistake. Due to timing issues, the BIOS may switch the lights + * back on, and we turn it back off once we "unblank". + * + * So here is an attempt to implement ">=" - if we are in the process + * of unblanking, and the LCD bit is unset in the driver but set in the + * register, we must keep it. + */ + lcdflags = ((par->PanelDispCntlReg1 | tmpdisp) & 0x02); /* LCD normal */ + dpmsflags = 0x00; /* no hsync/vsync suppression */ + break; + case FB_BLANK_UNBLANK: /* unblank */ + seqflags = 0; /* Enable sequencer */ + lcdflags = ((par->PanelDispCntlReg1 | tmpdisp) & 0x02); /* LCD normal */ + dpmsflags = 0x00; /* no hsync/vsync suppression */ +#ifdef CONFIG_TOSHIBA + /* Do we still need this ? */ + /* attempt to re-enable backlight/external on toshiba */ + { + SMMRegisters regs; + + regs.eax = 0xff00; /* HCI_SET */ + regs.ebx = 0x0002; /* HCI_BACKLIGHT */ + regs.ecx = 0x0001; /* HCI_ENABLE */ + tosh_smm(®s); + } +#endif + break; + default: /* Anything else we don't understand; return 1 to tell + * fb_blank we didn't aactually do anything */ + return 1; + } + + neoUnlock(); + reg = (vga_rseq(NULL, 0x01) & ~0x20) | seqflags; + vga_wseq(NULL, 0x01, reg); + reg = (vga_rgfx(NULL, 0x20) & ~0x02) | lcdflags; + vga_wgfx(NULL, 0x20, reg); + reg = (vga_rgfx(NULL, 0x01) & ~0xF0) | 0x80 | dpmsflags; + vga_wgfx(NULL, 0x01, reg); + neoLock(&par->state); + return 0; +} + +static void +neo2200_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct neofb_par *par = info->par; + u_long dst, rop; + + dst = rect->dx + rect->dy * info->var.xres_virtual; + rop = rect->rop ? 0x060000 : 0x0c0000; + + neo2200_wait_fifo(info, 4); + + /* set blt control */ + writel(NEO_BC3_FIFO_EN | + NEO_BC0_SRC_IS_FG | NEO_BC3_SKIP_MAPPING | + // NEO_BC3_DST_XY_ADDR | + // NEO_BC3_SRC_XY_ADDR | + rop, &par->neo2200->bltCntl); + + switch (info->var.bits_per_pixel) { + case 8: + writel(rect->color, &par->neo2200->fgColor); + break; + case 16: + case 24: + writel(((u32 *) (info->pseudo_palette))[rect->color], + &par->neo2200->fgColor); + break; + } + + writel(dst * ((info->var.bits_per_pixel + 7) >> 3), + &par->neo2200->dstStart); + writel((rect->height << 16) | (rect->width & 0xffff), + &par->neo2200->xyExt); +} + +static void +neo2200_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + u32 sx = area->sx, sy = area->sy, dx = area->dx, dy = area->dy; + struct neofb_par *par = info->par; + u_long src, dst, bltCntl; + + bltCntl = NEO_BC3_FIFO_EN | NEO_BC3_SKIP_MAPPING | 0x0C0000; + + if ((dy > sy) || ((dy == sy) && (dx > sx))) { + /* Start with the lower right corner */ + sy += (area->height - 1); + dy += (area->height - 1); + sx += (area->width - 1); + dx += (area->width - 1); + + bltCntl |= NEO_BC0_X_DEC | NEO_BC0_DST_Y_DEC | NEO_BC0_SRC_Y_DEC; + } + + src = sx * (info->var.bits_per_pixel >> 3) + sy*info->fix.line_length; + dst = dx * (info->var.bits_per_pixel >> 3) + dy*info->fix.line_length; + + neo2200_wait_fifo(info, 4); + + /* set blt control */ + writel(bltCntl, &par->neo2200->bltCntl); + + writel(src, &par->neo2200->srcStart); + writel(dst, &par->neo2200->dstStart); + writel((area->height << 16) | (area->width & 0xffff), + &par->neo2200->xyExt); +} + +static void +neo2200_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct neofb_par *par = info->par; + int s_pitch = (image->width * image->depth + 7) >> 3; + int scan_align = info->pixmap.scan_align - 1; + int buf_align = info->pixmap.buf_align - 1; + int bltCntl_flags, d_pitch, data_len; + + // The data is padded for the hardware + d_pitch = (s_pitch + scan_align) & ~scan_align; + data_len = ((d_pitch * image->height) + buf_align) & ~buf_align; + + neo2200_sync(info); + + if (image->depth == 1) { + if (info->var.bits_per_pixel == 24 && image->width < 16) { + /* FIXME. There is a bug with accelerated color-expanded + * transfers in 24 bit mode if the image being transferred + * is less than 16 bits wide. This is due to insufficient + * padding when writing the image. We need to adjust + * struct fb_pixmap. Not yet done. */ + cfb_imageblit(info, image); + return; + } + bltCntl_flags = NEO_BC0_SRC_MONO; + } else if (image->depth == info->var.bits_per_pixel) { + bltCntl_flags = 0; + } else { + /* We don't currently support hardware acceleration if image + * depth is different from display */ + cfb_imageblit(info, image); + return; + } + + switch (info->var.bits_per_pixel) { + case 8: + writel(image->fg_color, &par->neo2200->fgColor); + writel(image->bg_color, &par->neo2200->bgColor); + break; + case 16: + case 24: + writel(((u32 *) (info->pseudo_palette))[image->fg_color], + &par->neo2200->fgColor); + writel(((u32 *) (info->pseudo_palette))[image->bg_color], + &par->neo2200->bgColor); + break; + } + + writel(NEO_BC0_SYS_TO_VID | + NEO_BC3_SKIP_MAPPING | bltCntl_flags | + // NEO_BC3_DST_XY_ADDR | + 0x0c0000, &par->neo2200->bltCntl); + + writel(0, &par->neo2200->srcStart); +// par->neo2200->dstStart = (image->dy << 16) | (image->dx & 0xffff); + writel(((image->dx & 0xffff) * (info->var.bits_per_pixel >> 3) + + image->dy * info->fix.line_length), &par->neo2200->dstStart); + writel((image->height << 16) | (image->width & 0xffff), + &par->neo2200->xyExt); + + memcpy_toio(par->mmio_vbase + 0x100000, image->data, data_len); +} + +static void +neofb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + neo2200_fillrect(info, rect); + break; + default: + cfb_fillrect(info, rect); + break; + } +} + +static void +neofb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + neo2200_copyarea(info, area); + break; + default: + cfb_copyarea(info, area); + break; + } +} + +static void +neofb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + neo2200_imageblit(info, image); + break; + default: + cfb_imageblit(info, image); + break; + } +} + +static int +neofb_sync(struct fb_info *info) +{ + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + neo2200_sync(info); + break; + default: + break; + } + return 0; +} + +/* +static void +neofb_draw_cursor(struct fb_info *info, u8 *dst, u8 *src, unsigned int width) +{ + //memset_io(info->sprite.addr, 0xff, 1); +} + +static int +neofb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct neofb_par *par = (struct neofb_par *) info->par; + + * Disable cursor * + write_le32(NEOREG_CURSCNTL, ~NEO_CURS_ENABLE, par); + + if (cursor->set & FB_CUR_SETPOS) { + u32 x = cursor->image.dx; + u32 y = cursor->image.dy; + + info->cursor.image.dx = x; + info->cursor.image.dy = y; + write_le32(NEOREG_CURSX, x, par); + write_le32(NEOREG_CURSY, y, par); + } + + if (cursor->set & FB_CUR_SETSIZE) { + info->cursor.image.height = cursor->image.height; + info->cursor.image.width = cursor->image.width; + } + + if (cursor->set & FB_CUR_SETHOT) + info->cursor.hot = cursor->hot; + + if (cursor->set & FB_CUR_SETCMAP) { + if (cursor->image.depth == 1) { + u32 fg = cursor->image.fg_color; + u32 bg = cursor->image.bg_color; + + info->cursor.image.fg_color = fg; + info->cursor.image.bg_color = bg; + + fg = ((fg & 0xff0000) >> 16) | ((fg & 0xff) << 16) | (fg & 0xff00); + bg = ((bg & 0xff0000) >> 16) | ((bg & 0xff) << 16) | (bg & 0xff00); + write_le32(NEOREG_CURSFGCOLOR, fg, par); + write_le32(NEOREG_CURSBGCOLOR, bg, par); + } + } + + if (cursor->set & FB_CUR_SETSHAPE) + fb_load_cursor_image(info); + + if (info->cursor.enable) + write_le32(NEOREG_CURSCNTL, NEO_CURS_ENABLE, par); + return 0; +} +*/ + +static struct fb_ops neofb_ops = { + .owner = THIS_MODULE, + .fb_open = neofb_open, + .fb_release = neofb_release, + .fb_check_var = neofb_check_var, + .fb_set_par = neofb_set_par, + .fb_setcolreg = neofb_setcolreg, + .fb_pan_display = neofb_pan_display, + .fb_blank = neofb_blank, + .fb_sync = neofb_sync, + .fb_fillrect = neofb_fillrect, + .fb_copyarea = neofb_copyarea, + .fb_imageblit = neofb_imageblit, +}; + +/* --------------------------------------------------------------------- */ + +static struct fb_videomode mode800x480 = { + .xres = 800, + .yres = 480, + .pixclock = 25000, + .left_margin = 88, + .right_margin = 40, + .upper_margin = 23, + .lower_margin = 1, + .hsync_len = 128, + .vsync_len = 4, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED +}; + +static int neo_map_mmio(struct fb_info *info, struct pci_dev *dev) +{ + struct neofb_par *par = info->par; + + DBG("neo_map_mmio"); + + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2070: + info->fix.mmio_start = pci_resource_start(dev, 0)+ + 0x100000; + break; + case FB_ACCEL_NEOMAGIC_NM2090: + case FB_ACCEL_NEOMAGIC_NM2093: + info->fix.mmio_start = pci_resource_start(dev, 0)+ + 0x200000; + break; + case FB_ACCEL_NEOMAGIC_NM2160: + case FB_ACCEL_NEOMAGIC_NM2097: + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + info->fix.mmio_start = pci_resource_start(dev, 1); + break; + default: + info->fix.mmio_start = pci_resource_start(dev, 0); + } + info->fix.mmio_len = MMIO_SIZE; + + if (!request_mem_region + (info->fix.mmio_start, MMIO_SIZE, "memory mapped I/O")) { + printk("neofb: memory mapped IO in use\n"); + return -EBUSY; + } + + par->mmio_vbase = ioremap(info->fix.mmio_start, MMIO_SIZE); + if (!par->mmio_vbase) { + printk("neofb: unable to map memory mapped IO\n"); + release_mem_region(info->fix.mmio_start, + info->fix.mmio_len); + return -ENOMEM; + } else + printk(KERN_INFO "neofb: mapped io at %p\n", + par->mmio_vbase); + return 0; +} + +static void neo_unmap_mmio(struct fb_info *info) +{ + struct neofb_par *par = info->par; + + DBG("neo_unmap_mmio"); + + iounmap(par->mmio_vbase); + par->mmio_vbase = NULL; + + release_mem_region(info->fix.mmio_start, + info->fix.mmio_len); +} + +static int neo_map_video(struct fb_info *info, struct pci_dev *dev, + int video_len) +{ + //unsigned long addr; + + DBG("neo_map_video"); + + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = video_len; + + if (!request_mem_region(info->fix.smem_start, info->fix.smem_len, + "frame buffer")) { + printk("neofb: frame buffer in use\n"); + return -EBUSY; + } + + info->screen_base = + ioremap(info->fix.smem_start, info->fix.smem_len); + if (!info->screen_base) { + printk("neofb: unable to map screen memory\n"); + release_mem_region(info->fix.smem_start, + info->fix.smem_len); + return -ENOMEM; + } else + printk(KERN_INFO "neofb: mapped framebuffer at %p\n", + info->screen_base); + +#ifdef CONFIG_MTRR + ((struct neofb_par *)(info->par))->mtrr = + mtrr_add(info->fix.smem_start, pci_resource_len(dev, 0), + MTRR_TYPE_WRCOMB, 1); +#endif + + /* Clear framebuffer, it's all white in memory after boot */ + memset_io(info->screen_base, 0, info->fix.smem_len); + + /* Allocate Cursor drawing pad. + info->fix.smem_len -= PAGE_SIZE; + addr = info->fix.smem_start + info->fix.smem_len; + write_le32(NEOREG_CURSMEMPOS, ((0x000f & (addr >> 10)) << 8) | + ((0x0ff0 & (addr >> 10)) >> 4), par); + addr = (unsigned long) info->screen_base + info->fix.smem_len; + info->sprite.addr = (u8 *) addr; */ + return 0; +} + +static void neo_unmap_video(struct fb_info *info) +{ + DBG("neo_unmap_video"); + +#ifdef CONFIG_MTRR + { + struct neofb_par *par = info->par; + + mtrr_del(par->mtrr, info->fix.smem_start, + info->fix.smem_len); + } +#endif + iounmap(info->screen_base); + info->screen_base = NULL; + + release_mem_region(info->fix.smem_start, + info->fix.smem_len); +} + +static int neo_scan_monitor(struct fb_info *info) +{ + struct neofb_par *par = info->par; + unsigned char type, display; + int w; + + // Eventually we will have i2c support. + info->monspecs.modedb = kmalloc(sizeof(struct fb_videomode), GFP_KERNEL); + if (!info->monspecs.modedb) + return -ENOMEM; + info->monspecs.modedb_len = 1; + + /* Determine the panel type */ + vga_wgfx(NULL, 0x09, 0x26); + type = vga_rgfx(NULL, 0x21); + display = vga_rgfx(NULL, 0x20); + if (!par->internal_display && !par->external_display) { + par->internal_display = display & 2 || !(display & 3) ? 1 : 0; + par->external_display = display & 1; + printk (KERN_INFO "Autodetected %s display\n", + par->internal_display && par->external_display ? "simultaneous" : + par->internal_display ? "internal" : "external"); + } + + /* Determine panel width -- used in NeoValidMode. */ + w = vga_rgfx(NULL, 0x20); + vga_wgfx(NULL, 0x09, 0x00); + switch ((w & 0x18) >> 3) { + case 0x00: + // 640x480@60 + par->NeoPanelWidth = 640; + par->NeoPanelHeight = 480; + memcpy(info->monspecs.modedb, &vesa_modes[3], sizeof(struct fb_videomode)); + break; + case 0x01: + par->NeoPanelWidth = 800; + if (par->libretto) { + par->NeoPanelHeight = 480; + memcpy(info->monspecs.modedb, &mode800x480, sizeof(struct fb_videomode)); + } else { + // 800x600@60 + par->NeoPanelHeight = 600; + memcpy(info->monspecs.modedb, &vesa_modes[8], sizeof(struct fb_videomode)); + } + break; + case 0x02: + // 1024x768@60 + par->NeoPanelWidth = 1024; + par->NeoPanelHeight = 768; + memcpy(info->monspecs.modedb, &vesa_modes[13], sizeof(struct fb_videomode)); + break; + case 0x03: + /* 1280x1024@60 panel support needs to be added */ +#ifdef NOT_DONE + par->NeoPanelWidth = 1280; + par->NeoPanelHeight = 1024; + memcpy(info->monspecs.modedb, &vesa_modes[20], sizeof(struct fb_videomode)); + break; +#else + printk(KERN_ERR + "neofb: Only 640x480, 800x600/480 and 1024x768 panels are currently supported\n"); + return -1; +#endif + default: + // 640x480@60 + par->NeoPanelWidth = 640; + par->NeoPanelHeight = 480; + memcpy(info->monspecs.modedb, &vesa_modes[3], sizeof(struct fb_videomode)); + break; + } + + printk(KERN_INFO "Panel is a %dx%d %s %s display\n", + par->NeoPanelWidth, + par->NeoPanelHeight, + (type & 0x02) ? "color" : "monochrome", + (type & 0x10) ? "TFT" : "dual scan"); + return 0; +} + +static int neo_init_hw(struct fb_info *info) +{ + struct neofb_par *par = info->par; + int videoRam = 896; + int maxClock = 65000; + int CursorMem = 1024; + int CursorOff = 0x100; + + DBG("neo_init_hw"); + + neoUnlock(); + +#if 0 + printk(KERN_DEBUG "--- Neo extended register dump ---\n"); + for (int w = 0; w < 0x85; w++) + printk(KERN_DEBUG "CR %p: %p\n", (void *) w, + (void *) vga_rcrt(NULL, w)); + for (int w = 0; w < 0xC7; w++) + printk(KERN_DEBUG "GR %p: %p\n", (void *) w, + (void *) vga_rgfx(NULL, w)); +#endif + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2070: + videoRam = 896; + maxClock = 65000; + break; + case FB_ACCEL_NEOMAGIC_NM2090: + case FB_ACCEL_NEOMAGIC_NM2093: + case FB_ACCEL_NEOMAGIC_NM2097: + videoRam = 1152; + maxClock = 80000; + break; + case FB_ACCEL_NEOMAGIC_NM2160: + videoRam = 2048; + maxClock = 90000; + break; + case FB_ACCEL_NEOMAGIC_NM2200: + videoRam = 2560; + maxClock = 110000; + break; + case FB_ACCEL_NEOMAGIC_NM2230: + videoRam = 3008; + maxClock = 110000; + break; + case FB_ACCEL_NEOMAGIC_NM2360: + videoRam = 4096; + maxClock = 110000; + break; + case FB_ACCEL_NEOMAGIC_NM2380: + videoRam = 6144; + maxClock = 110000; + break; + } + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2070: + case FB_ACCEL_NEOMAGIC_NM2090: + case FB_ACCEL_NEOMAGIC_NM2093: + CursorMem = 2048; + CursorOff = 0x100; + break; + case FB_ACCEL_NEOMAGIC_NM2097: + case FB_ACCEL_NEOMAGIC_NM2160: + CursorMem = 1024; + CursorOff = 0x100; + break; + case FB_ACCEL_NEOMAGIC_NM2200: + case FB_ACCEL_NEOMAGIC_NM2230: + case FB_ACCEL_NEOMAGIC_NM2360: + case FB_ACCEL_NEOMAGIC_NM2380: + CursorMem = 1024; + CursorOff = 0x1000; + + par->neo2200 = (Neo2200 __iomem *) par->mmio_vbase; + break; + } +/* + info->sprite.size = CursorMem; + info->sprite.scan_align = 1; + info->sprite.buf_align = 1; + info->sprite.flags = FB_PIXMAP_IO; + info->sprite.outbuf = neofb_draw_cursor; +*/ + par->maxClock = maxClock; + par->cursorOff = CursorOff; + return videoRam * 1024; +} + + +static struct fb_info *neo_alloc_fb_info(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct fb_info *info; + struct neofb_par *par; + + info = framebuffer_alloc(sizeof(struct neofb_par), &dev->dev); + + if (!info) + return NULL; + + par = info->par; + + info->fix.accel = id->driver_data; + + par->pci_burst = !nopciburst; + par->lcd_stretch = !nostretch; + par->libretto = libretto; + + par->internal_display = internal; + par->external_display = external; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + switch (info->fix.accel) { + case FB_ACCEL_NEOMAGIC_NM2070: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 128"); + break; + case FB_ACCEL_NEOMAGIC_NM2090: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 128V"); + break; + case FB_ACCEL_NEOMAGIC_NM2093: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 128ZV"); + break; + case FB_ACCEL_NEOMAGIC_NM2097: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 128ZV+"); + break; + case FB_ACCEL_NEOMAGIC_NM2160: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 128XD"); + break; + case FB_ACCEL_NEOMAGIC_NM2200: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 256AV"); + info->flags |= FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT; + break; + case FB_ACCEL_NEOMAGIC_NM2230: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 256AV+"); + info->flags |= FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT; + break; + case FB_ACCEL_NEOMAGIC_NM2360: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 256ZX"); + info->flags |= FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT; + break; + case FB_ACCEL_NEOMAGIC_NM2380: + snprintf(info->fix.id, sizeof(info->fix.id), + "MagicGraph 256XL+"); + info->flags |= FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT; + break; + } + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = 4; + info->fix.ywrapstep = 0; + info->fix.accel = id->driver_data; + + info->fbops = &neofb_ops; + info->pseudo_palette = par->palette; + return info; +} + +static void neo_free_fb_info(struct fb_info *info) +{ + if (info) { + /* + * Free the colourmap + */ + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } +} + +/* --------------------------------------------------------------------- */ + +static int neofb_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct fb_info *info; + u_int h_sync, v_sync; + int video_len, err; + + DBG("neofb_probe"); + + err = pci_enable_device(dev); + if (err) + return err; + + err = -ENOMEM; + info = neo_alloc_fb_info(dev, id); + if (!info) + return err; + + err = neo_map_mmio(info, dev); + if (err) + goto err_map_mmio; + + err = neo_scan_monitor(info); + if (err) + goto err_scan_monitor; + + video_len = neo_init_hw(info); + if (video_len < 0) { + err = video_len; + goto err_init_hw; + } + + err = neo_map_video(info, dev, video_len); + if (err) + goto err_init_hw; + + if (!fb_find_mode(&info->var, info, mode_option, NULL, 0, + info->monspecs.modedb, 16)) { + printk(KERN_ERR "neofb: Unable to find usable video mode.\n"); + err = -EINVAL; + goto err_map_video; + } + + /* + * Calculate the hsync and vsync frequencies. Note that + * we split the 1e12 constant up so that we can preserve + * the precision and fit the results into 32-bit registers. + * (1953125000 * 512 = 1e12) + */ + h_sync = 1953125000 / info->var.pixclock; + h_sync = + h_sync * 512 / (info->var.xres + info->var.left_margin + + info->var.right_margin + info->var.hsync_len); + v_sync = + h_sync / (info->var.yres + info->var.upper_margin + + info->var.lower_margin + info->var.vsync_len); + + printk(KERN_INFO "neofb v" NEOFB_VERSION + ": %dkB VRAM, using %dx%d, %d.%03dkHz, %dHz\n", + info->fix.smem_len >> 10, info->var.xres, + info->var.yres, h_sync / 1000, h_sync % 1000, v_sync); + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err < 0) + goto err_map_video; + + err = register_framebuffer(info); + if (err < 0) + goto err_reg_fb; + + fb_info(info, "%s frame buffer device\n", info->fix.id); + + /* + * Our driver data + */ + pci_set_drvdata(dev, info); + return 0; + +err_reg_fb: + fb_dealloc_cmap(&info->cmap); +err_map_video: + neo_unmap_video(info); +err_init_hw: + fb_destroy_modedb(info->monspecs.modedb); +err_scan_monitor: + neo_unmap_mmio(info); +err_map_mmio: + neo_free_fb_info(info); + return err; +} + +static void neofb_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + + DBG("neofb_remove"); + + if (info) { + /* + * If unregister_framebuffer fails, then + * we will be leaving hooks that could cause + * oopsen laying around. + */ + if (unregister_framebuffer(info)) + printk(KERN_WARNING + "neofb: danger danger! Oopsen imminent!\n"); + + neo_unmap_video(info); + fb_destroy_modedb(info->monspecs.modedb); + neo_unmap_mmio(info); + neo_free_fb_info(info); + } +} + +static struct pci_device_id neofb_devices[] = { + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2070, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2070}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2090, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2090}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2093, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2093}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2097, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2097}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2160, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2160}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2200, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2200}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2230, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2230}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2360, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2360}, + + {PCI_VENDOR_ID_NEOMAGIC, PCI_CHIP_NM2380, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_NEOMAGIC_NM2380}, + + {0, 0, 0, 0, 0, 0, 0} +}; + +MODULE_DEVICE_TABLE(pci, neofb_devices); + +static struct pci_driver neofb_driver = { + .name = "neofb", + .id_table = neofb_devices, + .probe = neofb_probe, + .remove = neofb_remove, +}; + +/* ************************* init in-kernel code ************************** */ + +#ifndef MODULE +static int __init neofb_setup(char *options) +{ + char *this_opt; + + DBG("neofb_setup"); + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + + if (!strncmp(this_opt, "internal", 8)) + internal = 1; + else if (!strncmp(this_opt, "external", 8)) + external = 1; + else if (!strncmp(this_opt, "nostretch", 9)) + nostretch = 1; + else if (!strncmp(this_opt, "nopciburst", 10)) + nopciburst = 1; + else if (!strncmp(this_opt, "libretto", 8)) + libretto = 1; + else + mode_option = this_opt; + } + return 0; +} +#endif /* MODULE */ + +static int __init neofb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("neofb", &option)) + return -ENODEV; + neofb_setup(option); +#endif + return pci_register_driver(&neofb_driver); +} + +module_init(neofb_init); + +#ifdef MODULE +static void __exit neofb_exit(void) +{ + pci_unregister_driver(&neofb_driver); +} + +module_exit(neofb_exit); +#endif /* MODULE */ diff --git a/drivers/video/fbdev/nuc900fb.c b/drivers/video/fbdev/nuc900fb.c new file mode 100644 index 000000000000..478f9808dee4 --- /dev/null +++ b/drivers/video/fbdev/nuc900fb.c @@ -0,0 +1,765 @@ +/* + * + * Copyright (c) 2009 Nuvoton technology corporation + * All rights reserved. + * + * 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. + * + * Description: + * Nuvoton LCD Controller Driver + * Author: + * Wang Qiang (rurality.linux@gmail.com) 2009/12/11 + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/io.h> +#include <linux/pm.h> +#include <linux/device.h> + +#include <mach/map.h> +#include <mach/regs-clock.h> +#include <mach/regs-ldm.h> +#include <linux/platform_data/video-nuc900fb.h> + +#include "nuc900fb.h" + + +/* + * Initialize the nuc900 video (dual) buffer address + */ +static void nuc900fb_set_lcdaddr(struct fb_info *info) +{ + struct nuc900fb_info *fbi = info->par; + void __iomem *regs = fbi->io; + unsigned long vbaddr1, vbaddr2; + + vbaddr1 = info->fix.smem_start; + vbaddr2 = info->fix.smem_start; + vbaddr2 += info->fix.line_length * info->var.yres; + + /* set frambuffer start phy addr*/ + writel(vbaddr1, regs + REG_LCM_VA_BADDR0); + writel(vbaddr2, regs + REG_LCM_VA_BADDR1); + + writel(fbi->regs.lcd_va_fbctrl, regs + REG_LCM_VA_FBCTRL); + writel(fbi->regs.lcd_va_scale, regs + REG_LCM_VA_SCALE); +} + +/* + * calculate divider for lcd div + */ +static unsigned int nuc900fb_calc_pixclk(struct nuc900fb_info *fbi, + unsigned long pixclk) +{ + unsigned long clk = fbi->clk_rate; + unsigned long long div; + + /* pixclk is in picseconds. our clock is in Hz*/ + /* div = (clk * pixclk)/10^12 */ + div = (unsigned long long)clk * pixclk; + div >>= 12; + do_div(div, 625 * 625UL * 625); + + dev_dbg(fbi->dev, "pixclk %ld, divisor is %lld\n", pixclk, div); + + return div; +} + +/* + * Check the video params of 'var'. + */ +static int nuc900fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct nuc900fb_info *fbi = info->par; + struct nuc900fb_mach_info *mach_info = dev_get_platdata(fbi->dev); + struct nuc900fb_display *display = NULL; + struct nuc900fb_display *default_display = mach_info->displays + + mach_info->default_display; + int i; + + dev_dbg(fbi->dev, "check_var(var=%p, info=%p)\n", var, info); + + /* validate x/y resolution */ + /* choose default mode if possible */ + if (var->xres == default_display->xres && + var->yres == default_display->yres && + var->bits_per_pixel == default_display->bpp) + display = default_display; + else + for (i = 0; i < mach_info->num_displays; i++) + if (var->xres == mach_info->displays[i].xres && + var->yres == mach_info->displays[i].yres && + var->bits_per_pixel == mach_info->displays[i].bpp) { + display = mach_info->displays + i; + break; + } + + if (display == NULL) { + printk(KERN_ERR "wrong resolution or depth %dx%d at %d bit per pixel\n", + var->xres, var->yres, var->bits_per_pixel); + return -EINVAL; + } + + /* it should be the same size as the display */ + var->xres_virtual = display->xres; + var->yres_virtual = display->yres; + var->height = display->height; + var->width = display->width; + + /* copy lcd settings */ + var->pixclock = display->pixclock; + var->left_margin = display->left_margin; + var->right_margin = display->right_margin; + var->upper_margin = display->upper_margin; + var->lower_margin = display->lower_margin; + var->vsync_len = display->vsync_len; + var->hsync_len = display->hsync_len; + + var->transp.offset = 0; + var->transp.length = 0; + + fbi->regs.lcd_dccs = display->dccs; + fbi->regs.lcd_device_ctrl = display->devctl; + fbi->regs.lcd_va_fbctrl = display->fbctrl; + fbi->regs.lcd_va_scale = display->scale; + + /* set R/G/B possions */ + switch (var->bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + default: + var->red.offset = 0; + var->red.length = var->bits_per_pixel; + var->green = var->red; + var->blue = var->red; + break; + case 12: + var->red.length = 4; + var->green.length = 4; + var->blue.length = 4; + var->red.offset = 8; + var->green.offset = 4; + var->blue.offset = 0; + break; + case 16: + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + break; + case 18: + var->red.length = 6; + var->green.length = 6; + var->blue.length = 6; + var->red.offset = 12; + var->green.offset = 6; + var->blue.offset = 0; + break; + case 32: + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + break; + } + + return 0; +} + +/* + * Calculate lcd register values from var setting & save into hw + */ +static void nuc900fb_calculate_lcd_regs(const struct fb_info *info, + struct nuc900fb_hw *regs) +{ + const struct fb_var_screeninfo *var = &info->var; + int vtt = var->height + var->upper_margin + var->lower_margin; + int htt = var->width + var->left_margin + var->right_margin; + int hsync = var->width + var->right_margin; + int vsync = var->height + var->lower_margin; + + regs->lcd_crtc_size = LCM_CRTC_SIZE_VTTVAL(vtt) | + LCM_CRTC_SIZE_HTTVAL(htt); + regs->lcd_crtc_dend = LCM_CRTC_DEND_VDENDVAL(var->height) | + LCM_CRTC_DEND_HDENDVAL(var->width); + regs->lcd_crtc_hr = LCM_CRTC_HR_EVAL(var->width + 5) | + LCM_CRTC_HR_SVAL(var->width + 1); + regs->lcd_crtc_hsync = LCM_CRTC_HSYNC_EVAL(hsync + var->hsync_len) | + LCM_CRTC_HSYNC_SVAL(hsync); + regs->lcd_crtc_vr = LCM_CRTC_VR_EVAL(vsync + var->vsync_len) | + LCM_CRTC_VR_SVAL(vsync); + +} + +/* + * Activate (set) the controller from the given framebuffer + * information + */ +static void nuc900fb_activate_var(struct fb_info *info) +{ + struct nuc900fb_info *fbi = info->par; + void __iomem *regs = fbi->io; + struct fb_var_screeninfo *var = &info->var; + int clkdiv; + + clkdiv = nuc900fb_calc_pixclk(fbi, var->pixclock) - 1; + if (clkdiv < 0) + clkdiv = 0; + + nuc900fb_calculate_lcd_regs(info, &fbi->regs); + + /* set the new lcd registers*/ + + dev_dbg(fbi->dev, "new lcd register set:\n"); + dev_dbg(fbi->dev, "dccs = 0x%08x\n", fbi->regs.lcd_dccs); + dev_dbg(fbi->dev, "dev_ctl = 0x%08x\n", fbi->regs.lcd_device_ctrl); + dev_dbg(fbi->dev, "crtc_size = 0x%08x\n", fbi->regs.lcd_crtc_size); + dev_dbg(fbi->dev, "crtc_dend = 0x%08x\n", fbi->regs.lcd_crtc_dend); + dev_dbg(fbi->dev, "crtc_hr = 0x%08x\n", fbi->regs.lcd_crtc_hr); + dev_dbg(fbi->dev, "crtc_hsync = 0x%08x\n", fbi->regs.lcd_crtc_hsync); + dev_dbg(fbi->dev, "crtc_vr = 0x%08x\n", fbi->regs.lcd_crtc_vr); + + writel(fbi->regs.lcd_device_ctrl, regs + REG_LCM_DEV_CTRL); + writel(fbi->regs.lcd_crtc_size, regs + REG_LCM_CRTC_SIZE); + writel(fbi->regs.lcd_crtc_dend, regs + REG_LCM_CRTC_DEND); + writel(fbi->regs.lcd_crtc_hr, regs + REG_LCM_CRTC_HR); + writel(fbi->regs.lcd_crtc_hsync, regs + REG_LCM_CRTC_HSYNC); + writel(fbi->regs.lcd_crtc_vr, regs + REG_LCM_CRTC_VR); + + /* set lcd address pointers */ + nuc900fb_set_lcdaddr(info); + + writel(fbi->regs.lcd_dccs, regs + REG_LCM_DCCS); +} + +/* + * Alters the hardware state. + * + */ +static int nuc900fb_set_par(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + switch (var->bits_per_pixel) { + case 32: + case 24: + case 18: + case 16: + case 12: + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + case 1: + info->fix.visual = FB_VISUAL_MONO01; + break; + default: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + + info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; + + /* activate this new configuration */ + nuc900fb_activate_var(info); + return 0; +} + +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 nuc900fb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + unsigned int val; + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* true-colour, use pseuo-palette */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + pal[regno] = val; + } + break; + + default: + return 1; /* unknown type */ + } + return 0; +} + +/** + * nuc900fb_blank + * + */ +static int nuc900fb_blank(int blank_mode, struct fb_info *info) +{ + + return 0; +} + +static struct fb_ops nuc900fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = nuc900fb_check_var, + .fb_set_par = nuc900fb_set_par, + .fb_blank = nuc900fb_blank, + .fb_setcolreg = nuc900fb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + + +static inline void modify_gpio(void __iomem *reg, + unsigned long set, unsigned long mask) +{ + unsigned long tmp; + tmp = readl(reg) & ~mask; + writel(tmp | set, reg); +} + +/* + * Initialise LCD-related registers + */ +static int nuc900fb_init_registers(struct fb_info *info) +{ + struct nuc900fb_info *fbi = info->par; + struct nuc900fb_mach_info *mach_info = dev_get_platdata(fbi->dev); + void __iomem *regs = fbi->io; + + /*reset the display engine*/ + writel(0, regs + REG_LCM_DCCS); + writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_ENG_RST, + regs + REG_LCM_DCCS); + ndelay(100); + writel(readl(regs + REG_LCM_DCCS) & (~LCM_DCCS_ENG_RST), + regs + REG_LCM_DCCS); + ndelay(100); + + writel(0, regs + REG_LCM_DEV_CTRL); + + /* config gpio output */ + modify_gpio(W90X900_VA_GPIO + 0x54, mach_info->gpio_dir, + mach_info->gpio_dir_mask); + modify_gpio(W90X900_VA_GPIO + 0x58, mach_info->gpio_data, + mach_info->gpio_data_mask); + + return 0; +} + + +/* + * Alloc the SDRAM region of NUC900 for the frame buffer. + * The buffer should be a non-cached, non-buffered, memory region + * to allow palette and pixel writes without flushing the cache. + */ +static int nuc900fb_map_video_memory(struct fb_info *info) +{ + struct nuc900fb_info *fbi = info->par; + dma_addr_t map_dma; + unsigned long map_size = PAGE_ALIGN(info->fix.smem_len); + + dev_dbg(fbi->dev, "nuc900fb_map_video_memory(fbi=%p) map_size %lu\n", + fbi, map_size); + + info->screen_base = dma_alloc_writecombine(fbi->dev, map_size, + &map_dma, GFP_KERNEL); + + if (!info->screen_base) + return -ENOMEM; + + memset(info->screen_base, 0x00, map_size); + info->fix.smem_start = map_dma; + + return 0; +} + +static inline void nuc900fb_unmap_video_memory(struct fb_info *info) +{ + struct nuc900fb_info *fbi = info->par; + dma_free_writecombine(fbi->dev, PAGE_ALIGN(info->fix.smem_len), + info->screen_base, info->fix.smem_start); +} + +static irqreturn_t nuc900fb_irqhandler(int irq, void *dev_id) +{ + struct nuc900fb_info *fbi = dev_id; + void __iomem *regs = fbi->io; + void __iomem *irq_base = fbi->irq_base; + unsigned long lcdirq = readl(regs + REG_LCM_INT_CS); + + if (lcdirq & LCM_INT_CS_DISP_F_STATUS) { + writel(readl(irq_base) | 1<<30, irq_base); + + /* wait VA_EN low */ + if ((readl(regs + REG_LCM_DCCS) & + LCM_DCCS_SINGLE) == LCM_DCCS_SINGLE) + while ((readl(regs + REG_LCM_DCCS) & + LCM_DCCS_VA_EN) == LCM_DCCS_VA_EN) + ; + /* display_out-enable */ + writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_DISP_OUT_EN, + regs + REG_LCM_DCCS); + /* va-enable*/ + writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_VA_EN, + regs + REG_LCM_DCCS); + } else if (lcdirq & LCM_INT_CS_UNDERRUN_INT) { + writel(readl(irq_base) | LCM_INT_CS_UNDERRUN_INT, irq_base); + } else if (lcdirq & LCM_INT_CS_BUS_ERROR_INT) { + writel(readl(irq_base) | LCM_INT_CS_BUS_ERROR_INT, irq_base); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_CPU_FREQ + +static int nuc900fb_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct nuc900fb_info *info; + struct fb_info *fbinfo; + long delta_f; + info = container_of(nb, struct nuc900fb_info, freq_transition); + fbinfo = platform_get_drvdata(to_platform_device(info->dev)); + + delta_f = info->clk_rate - clk_get_rate(info->clk); + + if ((val == CPUFREQ_POSTCHANGE && delta_f > 0) || + (val == CPUFREQ_PRECHANGE && delta_f < 0)) { + info->clk_rate = clk_get_rate(info->clk); + nuc900fb_activate_var(fbinfo); + } + + return 0; +} + +static inline int nuc900fb_cpufreq_register(struct nuc900fb_info *fbi) +{ + fbi->freq_transition.notifier_call = nuc900fb_cpufreq_transition; + return cpufreq_register_notifier(&fbi->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +static inline void nuc900fb_cpufreq_deregister(struct nuc900fb_info *fbi) +{ + cpufreq_unregister_notifier(&fbi->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} +#else +static inline int nuc900fb_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + return 0; +} + +static inline int nuc900fb_cpufreq_register(struct nuc900fb_info *fbi) +{ + return 0; +} + +static inline void nuc900fb_cpufreq_deregister(struct nuc900fb_info *info) +{ +} +#endif + +static char driver_name[] = "nuc900fb"; + +static int nuc900fb_probe(struct platform_device *pdev) +{ + struct nuc900fb_info *fbi; + struct nuc900fb_display *display; + struct fb_info *fbinfo; + struct nuc900fb_mach_info *mach_info; + struct resource *res; + int ret; + int irq; + int i; + int size; + + dev_dbg(&pdev->dev, "devinit\n"); + mach_info = dev_get_platdata(&pdev->dev); + if (mach_info == NULL) { + dev_err(&pdev->dev, + "no platform data for lcd, cannot attach\n"); + return -EINVAL; + } + + if (mach_info->default_display > mach_info->num_displays) { + dev_err(&pdev->dev, + "default display No. is %d but only %d displays \n", + mach_info->default_display, mach_info->num_displays); + return -EINVAL; + } + + + display = mach_info->displays + mach_info->default_display; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for device\n"); + return -ENOENT; + } + + fbinfo = framebuffer_alloc(sizeof(struct nuc900fb_info), &pdev->dev); + if (!fbinfo) + return -ENOMEM; + + platform_set_drvdata(pdev, fbinfo); + + fbi = fbinfo->par; + fbi->dev = &pdev->dev; + +#ifdef CONFIG_CPU_NUC950 + fbi->drv_type = LCDDRV_NUC950; +#endif + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + size = resource_size(res); + fbi->mem = request_mem_region(res->start, size, pdev->name); + if (fbi->mem == NULL) { + dev_err(&pdev->dev, "failed to alloc memory region\n"); + ret = -ENOENT; + goto free_fb; + } + + fbi->io = ioremap(res->start, size); + if (fbi->io == NULL) { + dev_err(&pdev->dev, "ioremap() of lcd registers failed\n"); + ret = -ENXIO; + goto release_mem_region; + } + + fbi->irq_base = fbi->io + REG_LCM_INT_CS; + + + /* Stop the LCD */ + writel(0, fbi->io + REG_LCM_DCCS); + + /* fill the fbinfo*/ + strcpy(fbinfo->fix.id, driver_name); + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_NONE; + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + fbinfo->fbops = &nuc900fb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + fbinfo->pseudo_palette = &fbi->pseudo_pal; + + ret = request_irq(irq, nuc900fb_irqhandler, 0, pdev->name, fbi); + if (ret) { + dev_err(&pdev->dev, "cannot register irq handler %d -err %d\n", + irq, ret); + ret = -EBUSY; + goto release_regs; + } + + fbi->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(fbi->clk)) { + printk(KERN_ERR "nuc900-lcd:failed to get lcd clock source\n"); + ret = PTR_ERR(fbi->clk); + goto release_irq; + } + + clk_enable(fbi->clk); + dev_dbg(&pdev->dev, "got and enabled clock\n"); + + fbi->clk_rate = clk_get_rate(fbi->clk); + + /* calutate the video buffer size */ + for (i = 0; i < mach_info->num_displays; i++) { + unsigned long smem_len = mach_info->displays[i].xres; + smem_len *= mach_info->displays[i].yres; + smem_len *= mach_info->displays[i].bpp; + smem_len >>= 3; + if (fbinfo->fix.smem_len < smem_len) + fbinfo->fix.smem_len = smem_len; + } + + /* Initialize Video Memory */ + ret = nuc900fb_map_video_memory(fbinfo); + if (ret) { + printk(KERN_ERR "Failed to allocate video RAM: %x\n", ret); + goto release_clock; + } + + dev_dbg(&pdev->dev, "got video memory\n"); + + fbinfo->var.xres = display->xres; + fbinfo->var.yres = display->yres; + fbinfo->var.bits_per_pixel = display->bpp; + + nuc900fb_init_registers(fbinfo); + + nuc900fb_check_var(&fbinfo->var, fbinfo); + + ret = nuc900fb_cpufreq_register(fbi); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register cpufreq\n"); + goto free_video_memory; + } + + ret = register_framebuffer(fbinfo); + if (ret) { + printk(KERN_ERR "failed to register framebuffer device: %d\n", + ret); + goto free_cpufreq; + } + + fb_info(fbinfo, "%s frame buffer device\n", fbinfo->fix.id); + + return 0; + +free_cpufreq: + nuc900fb_cpufreq_deregister(fbi); +free_video_memory: + nuc900fb_unmap_video_memory(fbinfo); +release_clock: + clk_disable(fbi->clk); + clk_put(fbi->clk); +release_irq: + free_irq(irq, fbi); +release_regs: + iounmap(fbi->io); +release_mem_region: + release_mem_region(res->start, size); +free_fb: + framebuffer_release(fbinfo); + return ret; +} + +/* + * shutdown the lcd controller + */ +static void nuc900fb_stop_lcd(struct fb_info *info) +{ + struct nuc900fb_info *fbi = info->par; + void __iomem *regs = fbi->io; + + writel((~LCM_DCCS_DISP_INT_EN) | (~LCM_DCCS_VA_EN) | (~LCM_DCCS_OSD_EN), + regs + REG_LCM_DCCS); +} + +/* + * Cleanup + */ +static int nuc900fb_remove(struct platform_device *pdev) +{ + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct nuc900fb_info *fbi = fbinfo->par; + int irq; + + nuc900fb_stop_lcd(fbinfo); + msleep(1); + + unregister_framebuffer(fbinfo); + nuc900fb_cpufreq_deregister(fbi); + nuc900fb_unmap_video_memory(fbinfo); + + iounmap(fbi->io); + + irq = platform_get_irq(pdev, 0); + free_irq(irq, fbi); + + release_resource(fbi->mem); + kfree(fbi->mem); + + framebuffer_release(fbinfo); + + return 0; +} + +#ifdef CONFIG_PM + +/* + * suspend and resume support for the lcd controller + */ + +static int nuc900fb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct fb_info *fbinfo = platform_get_drvdata(dev); + struct nuc900fb_info *info = fbinfo->par; + + nuc900fb_stop_lcd(fbinfo); + msleep(1); + clk_disable(info->clk); + return 0; +} + +static int nuc900fb_resume(struct platform_device *dev) +{ + struct fb_info *fbinfo = platform_get_drvdata(dev); + struct nuc900fb_info *fbi = fbinfo->par; + + printk(KERN_INFO "nuc900fb resume\n"); + + clk_enable(fbi->clk); + msleep(1); + + nuc900fb_init_registers(fbinfo); + nuc900fb_activate_var(fbinfo); + + return 0; +} + +#else +#define nuc900fb_suspend NULL +#define nuc900fb_resume NULL +#endif + +static struct platform_driver nuc900fb_driver = { + .probe = nuc900fb_probe, + .remove = nuc900fb_remove, + .suspend = nuc900fb_suspend, + .resume = nuc900fb_resume, + .driver = { + .name = "nuc900-lcd", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(nuc900fb_driver); + +MODULE_DESCRIPTION("Framebuffer driver for the NUC900"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/nuc900fb.h b/drivers/video/fbdev/nuc900fb.h new file mode 100644 index 000000000000..9a1ca6dbb6b2 --- /dev/null +++ b/drivers/video/fbdev/nuc900fb.h @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2009 Nuvoton technology corporation + * All rights reserved. + * + * 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. + * + * Author: + * Wang Qiang(rurality.linux@gmail.com) 2009/12/16 + */ + +#ifndef __NUC900FB_H +#define __NUC900FB_H + +#include <mach/map.h> +#include <linux/platform_data/video-nuc900fb.h> + +enum nuc900_lcddrv_type { + LCDDRV_NUC910, + LCDDRV_NUC930, + LCDDRV_NUC932, + LCDDRV_NUC950, + LCDDRV_NUC960, +}; + + +#define PALETTE_BUFFER_SIZE 256 +#define PALETTE_BUFF_CLEAR (0x80000000) /* entry is clear/invalid */ + +struct nuc900fb_info { + struct device *dev; + struct clk *clk; + + struct resource *mem; + void __iomem *io; + void __iomem *irq_base; + int drv_type; + struct nuc900fb_hw regs; + unsigned long clk_rate; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; +#endif + + /* keep these registers in case we need to re-write palette */ + u32 palette_buffer[PALETTE_BUFFER_SIZE]; + u32 pseudo_pal[16]; +}; + +int nuc900fb_init(void); + +#endif /* __NUC900FB_H */ diff --git a/drivers/video/fbdev/nvidia/Makefile b/drivers/video/fbdev/nvidia/Makefile new file mode 100644 index 000000000000..ca47432113e0 --- /dev/null +++ b/drivers/video/fbdev/nvidia/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for the nVidia framebuffer driver +# + +obj-$(CONFIG_FB_NVIDIA) += nvidiafb.o + +nvidiafb-y := nvidia.o nv_hw.o nv_setup.o \ + nv_accel.o +nvidiafb-$(CONFIG_FB_NVIDIA_I2C) += nv_i2c.o +nvidiafb-$(CONFIG_FB_NVIDIA_BACKLIGHT) += nv_backlight.o +nvidiafb-$(CONFIG_PPC_OF) += nv_of.o + +nvidiafb-objs := $(nvidiafb-y) diff --git a/drivers/video/fbdev/nvidia/nv_accel.c b/drivers/video/fbdev/nvidia/nv_accel.c new file mode 100644 index 000000000000..ad6472a894ea --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_accel.c @@ -0,0 +1,416 @@ + /***************************************************************************\ +|* *| +|* Copyright 1993-2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 1993-2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| + \***************************************************************************/ + +/* + * GPL Licensing Note - According to Mark Vojkovich, author of the Xorg/ + * XFree86 'nv' driver, this source code is provided under MIT-style licensing + * where the source code is provided "as is" without warranty of any kind. + * The only usage restriction is for the copyright notices to be retained + * whenever code is used. + * + * Antonino Daplas <adaplas@pol.net> 2005-03-11 + */ + +#include <linux/fb.h> +#include "nv_type.h" +#include "nv_proto.h" +#include "nv_dma.h" +#include "nv_local.h" + +/* There is a HW race condition with videoram command buffers. + You can't jump to the location of your put offset. We write put + at the jump offset + SKIPS dwords with noop padding in between + to solve this problem */ +#define SKIPS 8 + +static const int NVCopyROP[16] = { + 0xCC, /* copy */ + 0x55 /* invert */ +}; + +static const int NVCopyROP_PM[16] = { + 0xCA, /* copy */ + 0x5A, /* invert */ +}; + +static inline void nvidiafb_safe_mode(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + + touch_softlockup_watchdog(); + info->pixmap.scan_align = 1; + par->lockup = 1; +} + +static inline void NVFlush(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + int count = 1000000000; + + while (--count && READ_GET(par) != par->dmaPut) ; + + if (!count) { + printk("nvidiafb: DMA Flush lockup\n"); + nvidiafb_safe_mode(info); + } +} + +static inline void NVSync(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + int count = 1000000000; + + while (--count && NV_RD32(par->PGRAPH, 0x0700)) ; + + if (!count) { + printk("nvidiafb: DMA Sync lockup\n"); + nvidiafb_safe_mode(info); + } +} + +static void NVDmaKickoff(struct nvidia_par *par) +{ + if (par->dmaCurrent != par->dmaPut) { + par->dmaPut = par->dmaCurrent; + WRITE_PUT(par, par->dmaPut); + } +} + +static void NVDmaWait(struct fb_info *info, int size) +{ + struct nvidia_par *par = info->par; + int dmaGet; + int count = 1000000000, cnt; + size++; + + while (par->dmaFree < size && --count && !par->lockup) { + dmaGet = READ_GET(par); + + if (par->dmaPut >= dmaGet) { + par->dmaFree = par->dmaMax - par->dmaCurrent; + if (par->dmaFree < size) { + NVDmaNext(par, 0x20000000); + if (dmaGet <= SKIPS) { + if (par->dmaPut <= SKIPS) + WRITE_PUT(par, SKIPS + 1); + cnt = 1000000000; + do { + dmaGet = READ_GET(par); + } while (--cnt && dmaGet <= SKIPS); + if (!cnt) { + printk("DMA Get lockup\n"); + par->lockup = 1; + } + } + WRITE_PUT(par, SKIPS); + par->dmaCurrent = par->dmaPut = SKIPS; + par->dmaFree = dmaGet - (SKIPS + 1); + } + } else + par->dmaFree = dmaGet - par->dmaCurrent - 1; + } + + if (!count) { + printk("nvidiafb: DMA Wait Lockup\n"); + nvidiafb_safe_mode(info); + } +} + +static void NVSetPattern(struct fb_info *info, u32 clr0, u32 clr1, + u32 pat0, u32 pat1) +{ + struct nvidia_par *par = info->par; + + NVDmaStart(info, par, PATTERN_COLOR_0, 4); + NVDmaNext(par, clr0); + NVDmaNext(par, clr1); + NVDmaNext(par, pat0); + NVDmaNext(par, pat1); +} + +static void NVSetRopSolid(struct fb_info *info, u32 rop, u32 planemask) +{ + struct nvidia_par *par = info->par; + + if (planemask != ~0) { + NVSetPattern(info, 0, planemask, ~0, ~0); + if (par->currentRop != (rop + 32)) { + NVDmaStart(info, par, ROP_SET, 1); + NVDmaNext(par, NVCopyROP_PM[rop]); + par->currentRop = rop + 32; + } + } else if (par->currentRop != rop) { + if (par->currentRop >= 16) + NVSetPattern(info, ~0, ~0, ~0, ~0); + NVDmaStart(info, par, ROP_SET, 1); + NVDmaNext(par, NVCopyROP[rop]); + par->currentRop = rop; + } +} + +static void NVSetClippingRectangle(struct fb_info *info, int x1, int y1, + int x2, int y2) +{ + struct nvidia_par *par = info->par; + int h = y2 - y1 + 1; + int w = x2 - x1 + 1; + + NVDmaStart(info, par, CLIP_POINT, 2); + NVDmaNext(par, (y1 << 16) | x1); + NVDmaNext(par, (h << 16) | w); +} + +void NVResetGraphics(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + u32 surfaceFormat, patternFormat, rectFormat, lineFormat; + int pitch, i; + + pitch = info->fix.line_length; + + par->dmaBase = (u32 __iomem *) (&par->FbStart[par->FbUsableSize]); + + for (i = 0; i < SKIPS; i++) + NV_WR32(&par->dmaBase[i], 0, 0x00000000); + + NV_WR32(&par->dmaBase[0x0 + SKIPS], 0, 0x00040000); + NV_WR32(&par->dmaBase[0x1 + SKIPS], 0, 0x80000010); + NV_WR32(&par->dmaBase[0x2 + SKIPS], 0, 0x00042000); + NV_WR32(&par->dmaBase[0x3 + SKIPS], 0, 0x80000011); + NV_WR32(&par->dmaBase[0x4 + SKIPS], 0, 0x00044000); + NV_WR32(&par->dmaBase[0x5 + SKIPS], 0, 0x80000012); + NV_WR32(&par->dmaBase[0x6 + SKIPS], 0, 0x00046000); + NV_WR32(&par->dmaBase[0x7 + SKIPS], 0, 0x80000013); + NV_WR32(&par->dmaBase[0x8 + SKIPS], 0, 0x00048000); + NV_WR32(&par->dmaBase[0x9 + SKIPS], 0, 0x80000014); + NV_WR32(&par->dmaBase[0xA + SKIPS], 0, 0x0004A000); + NV_WR32(&par->dmaBase[0xB + SKIPS], 0, 0x80000015); + NV_WR32(&par->dmaBase[0xC + SKIPS], 0, 0x0004C000); + NV_WR32(&par->dmaBase[0xD + SKIPS], 0, 0x80000016); + NV_WR32(&par->dmaBase[0xE + SKIPS], 0, 0x0004E000); + NV_WR32(&par->dmaBase[0xF + SKIPS], 0, 0x80000017); + + par->dmaPut = 0; + par->dmaCurrent = 16 + SKIPS; + par->dmaMax = 8191; + par->dmaFree = par->dmaMax - par->dmaCurrent; + + switch (info->var.bits_per_pixel) { + case 32: + case 24: + surfaceFormat = SURFACE_FORMAT_DEPTH24; + patternFormat = PATTERN_FORMAT_DEPTH24; + rectFormat = RECT_FORMAT_DEPTH24; + lineFormat = LINE_FORMAT_DEPTH24; + break; + case 16: + surfaceFormat = SURFACE_FORMAT_DEPTH16; + patternFormat = PATTERN_FORMAT_DEPTH16; + rectFormat = RECT_FORMAT_DEPTH16; + lineFormat = LINE_FORMAT_DEPTH16; + break; + default: + surfaceFormat = SURFACE_FORMAT_DEPTH8; + patternFormat = PATTERN_FORMAT_DEPTH8; + rectFormat = RECT_FORMAT_DEPTH8; + lineFormat = LINE_FORMAT_DEPTH8; + break; + } + + NVDmaStart(info, par, SURFACE_FORMAT, 4); + NVDmaNext(par, surfaceFormat); + NVDmaNext(par, pitch | (pitch << 16)); + NVDmaNext(par, 0); + NVDmaNext(par, 0); + + NVDmaStart(info, par, PATTERN_FORMAT, 1); + NVDmaNext(par, patternFormat); + + NVDmaStart(info, par, RECT_FORMAT, 1); + NVDmaNext(par, rectFormat); + + NVDmaStart(info, par, LINE_FORMAT, 1); + NVDmaNext(par, lineFormat); + + par->currentRop = ~0; /* set to something invalid */ + NVSetRopSolid(info, ROP_COPY, ~0); + + NVSetClippingRectangle(info, 0, 0, info->var.xres_virtual, + info->var.yres_virtual); + + NVDmaKickoff(par); +} + +int nvidiafb_sync(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + + if (info->state != FBINFO_STATE_RUNNING) + return 0; + + if (!par->lockup) + NVFlush(info); + + if (!par->lockup) + NVSync(info); + + return 0; +} + +void nvidiafb_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct nvidia_par *par = info->par; + + if (info->state != FBINFO_STATE_RUNNING) + return; + + if (par->lockup) { + cfb_copyarea(info, region); + return; + } + + NVDmaStart(info, par, BLIT_POINT_SRC, 3); + NVDmaNext(par, (region->sy << 16) | region->sx); + NVDmaNext(par, (region->dy << 16) | region->dx); + NVDmaNext(par, (region->height << 16) | region->width); + + NVDmaKickoff(par); +} + +void nvidiafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct nvidia_par *par = info->par; + u32 color; + + if (info->state != FBINFO_STATE_RUNNING) + return; + + if (par->lockup) { + cfb_fillrect(info, rect); + return; + } + + if (info->var.bits_per_pixel == 8) + color = rect->color; + else + color = ((u32 *) info->pseudo_palette)[rect->color]; + + if (rect->rop != ROP_COPY) + NVSetRopSolid(info, rect->rop, ~0); + + NVDmaStart(info, par, RECT_SOLID_COLOR, 1); + NVDmaNext(par, color); + + NVDmaStart(info, par, RECT_SOLID_RECTS(0), 2); + NVDmaNext(par, (rect->dx << 16) | rect->dy); + NVDmaNext(par, (rect->width << 16) | rect->height); + + NVDmaKickoff(par); + + if (rect->rop != ROP_COPY) + NVSetRopSolid(info, ROP_COPY, ~0); +} + +static void nvidiafb_mono_color_expand(struct fb_info *info, + const struct fb_image *image) +{ + struct nvidia_par *par = info->par; + u32 fg, bg, mask = ~(~0 >> (32 - info->var.bits_per_pixel)); + u32 dsize, width, *data = (u32 *) image->data, tmp; + int j, k = 0; + + width = (image->width + 31) & ~31; + dsize = (width * image->height) >> 5; + + if (info->var.bits_per_pixel == 8) { + fg = image->fg_color | mask; + bg = image->bg_color | mask; + } else { + fg = ((u32 *) info->pseudo_palette)[image->fg_color] | mask; + bg = ((u32 *) info->pseudo_palette)[image->bg_color] | mask; + } + + NVDmaStart(info, par, RECT_EXPAND_TWO_COLOR_CLIP, 7); + NVDmaNext(par, (image->dy << 16) | (image->dx & 0xffff)); + NVDmaNext(par, ((image->dy + image->height) << 16) | + ((image->dx + image->width) & 0xffff)); + NVDmaNext(par, bg); + NVDmaNext(par, fg); + NVDmaNext(par, (image->height << 16) | width); + NVDmaNext(par, (image->height << 16) | width); + NVDmaNext(par, (image->dy << 16) | (image->dx & 0xffff)); + + while (dsize >= RECT_EXPAND_TWO_COLOR_DATA_MAX_DWORDS) { + NVDmaStart(info, par, RECT_EXPAND_TWO_COLOR_DATA(0), + RECT_EXPAND_TWO_COLOR_DATA_MAX_DWORDS); + + for (j = RECT_EXPAND_TWO_COLOR_DATA_MAX_DWORDS; j--;) { + tmp = data[k++]; + reverse_order(&tmp); + NVDmaNext(par, tmp); + } + + dsize -= RECT_EXPAND_TWO_COLOR_DATA_MAX_DWORDS; + } + + if (dsize) { + NVDmaStart(info, par, RECT_EXPAND_TWO_COLOR_DATA(0), dsize); + + for (j = dsize; j--;) { + tmp = data[k++]; + reverse_order(&tmp); + NVDmaNext(par, tmp); + } + } + + NVDmaKickoff(par); +} + +void nvidiafb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct nvidia_par *par = info->par; + + if (info->state != FBINFO_STATE_RUNNING) + return; + + if (image->depth == 1 && !par->lockup) + nvidiafb_mono_color_expand(info, image); + else + cfb_imageblit(info, image); +} diff --git a/drivers/video/fbdev/nvidia/nv_backlight.c b/drivers/video/fbdev/nvidia/nv_backlight.c new file mode 100644 index 000000000000..8471008aa6ff --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_backlight.c @@ -0,0 +1,148 @@ +/* + * Backlight code for nVidia based graphic cards + * + * Copyright 2004 Antonino Daplas <adaplas@pol.net> + * Copyright (c) 2006 Michael Hanselmann <linux-kernel@hansmi.ch> + * + * 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. + */ + +#include <linux/backlight.h> +#include <linux/fb.h> +#include <linux/pci.h> + +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif + +#include "nv_local.h" +#include "nv_type.h" +#include "nv_proto.h" + +/* We do not have any information about which values are allowed, thus + * we used safe values. + */ +#define MIN_LEVEL 0x158 +#define MAX_LEVEL 0x534 +#define LEVEL_STEP ((MAX_LEVEL - MIN_LEVEL) / FB_BACKLIGHT_MAX) + +static int nvidia_bl_get_level_brightness(struct nvidia_par *par, + int level) +{ + struct fb_info *info = pci_get_drvdata(par->pci_dev); + int nlevel; + + /* Get and convert the value */ + /* No locking of bl_curve since we read a single value */ + nlevel = MIN_LEVEL + info->bl_curve[level] * LEVEL_STEP; + + if (nlevel < 0) + nlevel = 0; + else if (nlevel < MIN_LEVEL) + nlevel = MIN_LEVEL; + else if (nlevel > MAX_LEVEL) + nlevel = MAX_LEVEL; + + return nlevel; +} + +static int nvidia_bl_update_status(struct backlight_device *bd) +{ + struct nvidia_par *par = bl_get_data(bd); + u32 tmp_pcrt, tmp_pmc, fpcontrol; + int level; + + if (!par->FlatPanel) + return 0; + + if (bd->props.power != FB_BLANK_UNBLANK || + bd->props.fb_blank != FB_BLANK_UNBLANK) + level = 0; + else + level = bd->props.brightness; + + tmp_pmc = NV_RD32(par->PMC, 0x10F0) & 0x0000FFFF; + tmp_pcrt = NV_RD32(par->PCRTC0, 0x081C) & 0xFFFFFFFC; + fpcontrol = NV_RD32(par->PRAMDAC, 0x0848) & 0xCFFFFFCC; + + if (level > 0) { + tmp_pcrt |= 0x1; + tmp_pmc |= (1 << 31); /* backlight bit */ + tmp_pmc |= nvidia_bl_get_level_brightness(par, level) << 16; + fpcontrol |= par->fpSyncs; + } else + fpcontrol |= 0x20000022; + + NV_WR32(par->PCRTC0, 0x081C, tmp_pcrt); + NV_WR32(par->PMC, 0x10F0, tmp_pmc); + NV_WR32(par->PRAMDAC, 0x848, fpcontrol); + + return 0; +} + +static int nvidia_bl_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static const struct backlight_ops nvidia_bl_ops = { + .get_brightness = nvidia_bl_get_brightness, + .update_status = nvidia_bl_update_status, +}; + +void nvidia_bl_init(struct nvidia_par *par) +{ + struct backlight_properties props; + struct fb_info *info = pci_get_drvdata(par->pci_dev); + struct backlight_device *bd; + char name[12]; + + if (!par->FlatPanel) + return; + +#ifdef CONFIG_PMAC_BACKLIGHT + if (!machine_is(powermac) || + !pmac_has_backlight_type("mnca")) + return; +#endif + + snprintf(name, sizeof(name), "nvidiabl%d", info->node); + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = FB_BACKLIGHT_LEVELS - 1; + bd = backlight_device_register(name, info->dev, par, &nvidia_bl_ops, + &props); + if (IS_ERR(bd)) { + info->bl_dev = NULL; + printk(KERN_WARNING "nvidia: Backlight registration failed\n"); + goto error; + } + + info->bl_dev = bd; + fb_bl_default_curve(info, 0, + 0x158 * FB_BACKLIGHT_MAX / MAX_LEVEL, + 0x534 * FB_BACKLIGHT_MAX / MAX_LEVEL); + + bd->props.brightness = bd->props.max_brightness; + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("nvidia: Backlight initialized (%s)\n", name); + + return; + +error: + return; +} + +void nvidia_bl_exit(struct nvidia_par *par) +{ + struct fb_info *info = pci_get_drvdata(par->pci_dev); + struct backlight_device *bd = info->bl_dev; + + backlight_device_unregister(bd); + printk("nvidia: Backlight unloaded\n"); +} diff --git a/drivers/video/fbdev/nvidia/nv_dma.h b/drivers/video/fbdev/nvidia/nv_dma.h new file mode 100644 index 000000000000..a7ed1c0acbbb --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_dma.h @@ -0,0 +1,188 @@ + + /***************************************************************************\ +|* *| +|* Copyright 2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| + \***************************************************************************/ + +/* + * GPL Licensing Note - According to Mark Vojkovich, author of the Xorg/ + * XFree86 'nv' driver, this source code is provided under MIT-style licensing + * where the source code is provided "as is" without warranty of any kind. + * The only usage restriction is for the copyright notices to be retained + * whenever code is used. + * + * Antonino Daplas <adaplas@pol.net> 2005-03-11 + */ + +#define SURFACE_FORMAT 0x00000300 +#define SURFACE_FORMAT_DEPTH8 0x00000001 +#define SURFACE_FORMAT_DEPTH15 0x00000002 +#define SURFACE_FORMAT_DEPTH16 0x00000004 +#define SURFACE_FORMAT_DEPTH24 0x00000006 +#define SURFACE_PITCH 0x00000304 +#define SURFACE_PITCH_SRC 15:0 +#define SURFACE_PITCH_DST 31:16 +#define SURFACE_OFFSET_SRC 0x00000308 +#define SURFACE_OFFSET_DST 0x0000030C + +#define ROP_SET 0x00002300 + +#define PATTERN_FORMAT 0x00004300 +#define PATTERN_FORMAT_DEPTH8 0x00000003 +#define PATTERN_FORMAT_DEPTH16 0x00000001 +#define PATTERN_FORMAT_DEPTH24 0x00000003 +#define PATTERN_COLOR_0 0x00004310 +#define PATTERN_COLOR_1 0x00004314 +#define PATTERN_PATTERN_0 0x00004318 +#define PATTERN_PATTERN_1 0x0000431C + +#define CLIP_POINT 0x00006300 +#define CLIP_POINT_X 15:0 +#define CLIP_POINT_Y 31:16 +#define CLIP_SIZE 0x00006304 +#define CLIP_SIZE_WIDTH 15:0 +#define CLIP_SIZE_HEIGHT 31:16 + +#define LINE_FORMAT 0x00008300 +#define LINE_FORMAT_DEPTH8 0x00000003 +#define LINE_FORMAT_DEPTH16 0x00000001 +#define LINE_FORMAT_DEPTH24 0x00000003 +#define LINE_COLOR 0x00008304 +#define LINE_MAX_LINES 16 +#define LINE_LINES(i) 0x00008400\ + +(i)*8 +#define LINE_LINES_POINT0_X 15:0 +#define LINE_LINES_POINT0_Y 31:16 +#define LINE_LINES_POINT1_X 47:32 +#define LINE_LINES_POINT1_Y 63:48 + +#define BLIT_POINT_SRC 0x0000A300 +#define BLIT_POINT_SRC_X 15:0 +#define BLIT_POINT_SRC_Y 31:16 +#define BLIT_POINT_DST 0x0000A304 +#define BLIT_POINT_DST_X 15:0 +#define BLIT_POINT_DST_Y 31:16 +#define BLIT_SIZE 0x0000A308 +#define BLIT_SIZE_WIDTH 15:0 +#define BLIT_SIZE_HEIGHT 31:16 + +#define RECT_FORMAT 0x0000C300 +#define RECT_FORMAT_DEPTH8 0x00000003 +#define RECT_FORMAT_DEPTH16 0x00000001 +#define RECT_FORMAT_DEPTH24 0x00000003 +#define RECT_SOLID_COLOR 0x0000C3FC +#define RECT_SOLID_RECTS_MAX_RECTS 32 +#define RECT_SOLID_RECTS(i) 0x0000C400\ + +(i)*8 +#define RECT_SOLID_RECTS_Y 15:0 +#define RECT_SOLID_RECTS_X 31:16 +#define RECT_SOLID_RECTS_HEIGHT 47:32 +#define RECT_SOLID_RECTS_WIDTH 63:48 + +#define RECT_EXPAND_ONE_COLOR_CLIP 0x0000C7EC +#define RECT_EXPAND_ONE_COLOR_CLIP_POINT0_X 15:0 +#define RECT_EXPAND_ONE_COLOR_CLIP_POINT0_Y 31:16 +#define RECT_EXPAND_ONE_COLOR_CLIP_POINT1_X 47:32 +#define RECT_EXPAND_ONE_COLOR_CLIP_POINT1_Y 63:48 +#define RECT_EXPAND_ONE_COLOR_COLOR 0x0000C7F4 +#define RECT_EXPAND_ONE_COLOR_SIZE 0x0000C7F8 +#define RECT_EXPAND_ONE_COLOR_SIZE_WIDTH 15:0 +#define RECT_EXPAND_ONE_COLOR_SIZE_HEIGHT 31:16 +#define RECT_EXPAND_ONE_COLOR_POINT 0x0000C7FC +#define RECT_EXPAND_ONE_COLOR_POINT_X 15:0 +#define RECT_EXPAND_ONE_COLOR_POINT_Y 31:16 +#define RECT_EXPAND_ONE_COLOR_DATA_MAX_DWORDS 128 +#define RECT_EXPAND_ONE_COLOR_DATA(i) 0x0000C800\ + +(i)*4 + +#define RECT_EXPAND_TWO_COLOR_CLIP 0x0000CBE4 +#define RECT_EXPAND_TWO_COLOR_CLIP_POINT0_X 15:0 +#define RECT_EXPAND_TWO_COLOR_CLIP_POINT0_Y 31:16 +#define RECT_EXPAND_TWO_COLOR_CLIP_POINT1_X 47:32 +#define RECT_EXPAND_TWO_COLOR_CLIP_POINT1_Y 63:48 +#define RECT_EXPAND_TWO_COLOR_COLOR_0 0x0000CBEC +#define RECT_EXPAND_TWO_COLOR_COLOR_1 0x0000CBF0 +#define RECT_EXPAND_TWO_COLOR_SIZE_IN 0x0000CBF4 +#define RECT_EXPAND_TWO_COLOR_SIZE_IN_WIDTH 15:0 +#define RECT_EXPAND_TWO_COLOR_SIZE_IN_HEIGHT 31:16 +#define RECT_EXPAND_TWO_COLOR_SIZE_OUT 0x0000CBF8 +#define RECT_EXPAND_TWO_COLOR_SIZE_OUT_WIDTH 15:0 +#define RECT_EXPAND_TWO_COLOR_SIZE_OUT_HEIGHT 31:16 +#define RECT_EXPAND_TWO_COLOR_POINT 0x0000CBFC +#define RECT_EXPAND_TWO_COLOR_POINT_X 15:0 +#define RECT_EXPAND_TWO_COLOR_POINT_Y 31:16 +#define RECT_EXPAND_TWO_COLOR_DATA_MAX_DWORDS 128 +#define RECT_EXPAND_TWO_COLOR_DATA(i) 0x0000CC00\ + +(i)*4 + +#define STRETCH_BLIT_FORMAT 0x0000E300 +#define STRETCH_BLIT_FORMAT_DEPTH8 0x00000004 +#define STRETCH_BLIT_FORMAT_DEPTH16 0x00000007 +#define STRETCH_BLIT_FORMAT_DEPTH24 0x00000004 +#define STRETCH_BLIT_FORMAT_X8R8G8B8 0x00000004 +#define STRETCH_BLIT_FORMAT_YUYV 0x00000005 +#define STRETCH_BLIT_FORMAT_UYVY 0x00000006 +#define STRETCH_BLIT_CLIP_POINT 0x0000E308 +#define STRETCH_BLIT_CLIP_POINT_X 15:0 +#define STRETCH_BLIT_CLIP_POINT_Y 31:16 +#define STRETCH_BLIT_CLIP_POINT 0x0000E308 +#define STRETCH_BLIT_CLIP_SIZE 0x0000E30C +#define STRETCH_BLIT_CLIP_SIZE_WIDTH 15:0 +#define STRETCH_BLIT_CLIP_SIZE_HEIGHT 31:16 +#define STRETCH_BLIT_DST_POINT 0x0000E310 +#define STRETCH_BLIT_DST_POINT_X 15:0 +#define STRETCH_BLIT_DST_POINT_Y 31:16 +#define STRETCH_BLIT_DST_SIZE 0x0000E314 +#define STRETCH_BLIT_DST_SIZE_WIDTH 15:0 +#define STRETCH_BLIT_DST_SIZE_HEIGHT 31:16 +#define STRETCH_BLIT_DU_DX 0x0000E318 +#define STRETCH_BLIT_DV_DY 0x0000E31C +#define STRETCH_BLIT_SRC_SIZE 0x0000E400 +#define STRETCH_BLIT_SRC_SIZE_WIDTH 15:0 +#define STRETCH_BLIT_SRC_SIZE_HEIGHT 31:16 +#define STRETCH_BLIT_SRC_FORMAT 0x0000E404 +#define STRETCH_BLIT_SRC_FORMAT_PITCH 15:0 +#define STRETCH_BLIT_SRC_FORMAT_ORIGIN 23:16 +#define STRETCH_BLIT_SRC_FORMAT_ORIGIN_CENTER 0x00000001 +#define STRETCH_BLIT_SRC_FORMAT_ORIGIN_CORNER 0x00000002 +#define STRETCH_BLIT_SRC_FORMAT_FILTER 31:24 +#define STRETCH_BLIT_SRC_FORMAT_FILTER_POINT_SAMPLE 0x00000000 +#define STRETCH_BLIT_SRC_FORMAT_FILTER_BILINEAR 0x00000001 +#define STRETCH_BLIT_SRC_OFFSET 0x0000E408 +#define STRETCH_BLIT_SRC_POINT 0x0000E40C +#define STRETCH_BLIT_SRC_POINT_U 15:0 +#define STRETCH_BLIT_SRC_POINT_V 31:16 diff --git a/drivers/video/fbdev/nvidia/nv_hw.c b/drivers/video/fbdev/nvidia/nv_hw.c new file mode 100644 index 000000000000..81c80ac3c76f --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_hw.c @@ -0,0 +1,1687 @@ + /***************************************************************************\ +|* *| +|* Copyright 1993-2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 1993-2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| + \***************************************************************************/ + +/* + * GPL Licensing Note - According to Mark Vojkovich, author of the Xorg/ + * XFree86 'nv' driver, this source code is provided under MIT-style licensing + * where the source code is provided "as is" without warranty of any kind. + * The only usage restriction is for the copyright notices to be retained + * whenever code is used. + * + * Antonino Daplas <adaplas@pol.net> 2005-03-11 + */ + +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/nv_hw.c,v 1.4 2003/11/03 05:11:25 tsi Exp $ */ + +#include <linux/pci.h> +#include "nv_type.h" +#include "nv_local.h" +#include "nv_proto.h" + +void NVLockUnlock(struct nvidia_par *par, int Lock) +{ + u8 cr11; + + VGA_WR08(par->PCIO, 0x3D4, 0x1F); + VGA_WR08(par->PCIO, 0x3D5, Lock ? 0x99 : 0x57); + + VGA_WR08(par->PCIO, 0x3D4, 0x11); + cr11 = VGA_RD08(par->PCIO, 0x3D5); + if (Lock) + cr11 |= 0x80; + else + cr11 &= ~0x80; + VGA_WR08(par->PCIO, 0x3D5, cr11); +} + +int NVShowHideCursor(struct nvidia_par *par, int ShowHide) +{ + int cur = par->CurrentState->cursor1; + + par->CurrentState->cursor1 = (par->CurrentState->cursor1 & 0xFE) | + (ShowHide & 0x01); + VGA_WR08(par->PCIO, 0x3D4, 0x31); + VGA_WR08(par->PCIO, 0x3D5, par->CurrentState->cursor1); + + if (par->Architecture == NV_ARCH_40) + NV_WR32(par->PRAMDAC, 0x0300, NV_RD32(par->PRAMDAC, 0x0300)); + + return (cur & 0x01); +} + +/****************************************************************************\ +* * +* The video arbitration routines calculate some "magic" numbers. Fixes * +* the snow seen when accessing the framebuffer without it. * +* It just works (I hope). * +* * +\****************************************************************************/ + +typedef struct { + int graphics_lwm; + int video_lwm; + int graphics_burst_size; + int video_burst_size; + int valid; +} nv4_fifo_info; + +typedef struct { + int pclk_khz; + int mclk_khz; + int nvclk_khz; + char mem_page_miss; + char mem_latency; + int memory_width; + char enable_video; + char gr_during_vid; + char pix_bpp; + char mem_aligned; + char enable_mp; +} nv4_sim_state; + +typedef struct { + int graphics_lwm; + int video_lwm; + int graphics_burst_size; + int video_burst_size; + int valid; +} nv10_fifo_info; + +typedef struct { + int pclk_khz; + int mclk_khz; + int nvclk_khz; + char mem_page_miss; + char mem_latency; + u32 memory_type; + int memory_width; + char enable_video; + char gr_during_vid; + char pix_bpp; + char mem_aligned; + char enable_mp; +} nv10_sim_state; + +static void nvGetClocks(struct nvidia_par *par, unsigned int *MClk, + unsigned int *NVClk) +{ + unsigned int pll, N, M, MB, NB, P; + + if (par->Architecture >= NV_ARCH_40) { + pll = NV_RD32(par->PMC, 0x4020); + P = (pll >> 16) & 0x07; + pll = NV_RD32(par->PMC, 0x4024); + M = pll & 0xFF; + N = (pll >> 8) & 0xFF; + if (((par->Chipset & 0xfff0) == 0x0290) || + ((par->Chipset & 0xfff0) == 0x0390)) { + MB = 1; + NB = 1; + } else { + MB = (pll >> 16) & 0xFF; + NB = (pll >> 24) & 0xFF; + } + *MClk = ((N * NB * par->CrystalFreqKHz) / (M * MB)) >> P; + + pll = NV_RD32(par->PMC, 0x4000); + P = (pll >> 16) & 0x07; + pll = NV_RD32(par->PMC, 0x4004); + M = pll & 0xFF; + N = (pll >> 8) & 0xFF; + MB = (pll >> 16) & 0xFF; + NB = (pll >> 24) & 0xFF; + + *NVClk = ((N * NB * par->CrystalFreqKHz) / (M * MB)) >> P; + } else if (par->twoStagePLL) { + pll = NV_RD32(par->PRAMDAC0, 0x0504); + M = pll & 0xFF; + N = (pll >> 8) & 0xFF; + P = (pll >> 16) & 0x0F; + pll = NV_RD32(par->PRAMDAC0, 0x0574); + if (pll & 0x80000000) { + MB = pll & 0xFF; + NB = (pll >> 8) & 0xFF; + } else { + MB = 1; + NB = 1; + } + *MClk = ((N * NB * par->CrystalFreqKHz) / (M * MB)) >> P; + + pll = NV_RD32(par->PRAMDAC0, 0x0500); + M = pll & 0xFF; + N = (pll >> 8) & 0xFF; + P = (pll >> 16) & 0x0F; + pll = NV_RD32(par->PRAMDAC0, 0x0570); + if (pll & 0x80000000) { + MB = pll & 0xFF; + NB = (pll >> 8) & 0xFF; + } else { + MB = 1; + NB = 1; + } + *NVClk = ((N * NB * par->CrystalFreqKHz) / (M * MB)) >> P; + } else + if (((par->Chipset & 0x0ff0) == 0x0300) || + ((par->Chipset & 0x0ff0) == 0x0330)) { + pll = NV_RD32(par->PRAMDAC0, 0x0504); + M = pll & 0x0F; + N = (pll >> 8) & 0xFF; + P = (pll >> 16) & 0x07; + if (pll & 0x00000080) { + MB = (pll >> 4) & 0x07; + NB = (pll >> 19) & 0x1f; + } else { + MB = 1; + NB = 1; + } + *MClk = ((N * NB * par->CrystalFreqKHz) / (M * MB)) >> P; + + pll = NV_RD32(par->PRAMDAC0, 0x0500); + M = pll & 0x0F; + N = (pll >> 8) & 0xFF; + P = (pll >> 16) & 0x07; + if (pll & 0x00000080) { + MB = (pll >> 4) & 0x07; + NB = (pll >> 19) & 0x1f; + } else { + MB = 1; + NB = 1; + } + *NVClk = ((N * NB * par->CrystalFreqKHz) / (M * MB)) >> P; + } else { + pll = NV_RD32(par->PRAMDAC0, 0x0504); + M = pll & 0xFF; + N = (pll >> 8) & 0xFF; + P = (pll >> 16) & 0x0F; + *MClk = (N * par->CrystalFreqKHz / M) >> P; + + pll = NV_RD32(par->PRAMDAC0, 0x0500); + M = pll & 0xFF; + N = (pll >> 8) & 0xFF; + P = (pll >> 16) & 0x0F; + *NVClk = (N * par->CrystalFreqKHz / M) >> P; + } +} + +static void nv4CalcArbitration(nv4_fifo_info * fifo, nv4_sim_state * arb) +{ + int data, pagemiss, cas, width, video_enable, bpp; + int nvclks, mclks, pclks, vpagemiss, crtpagemiss, vbs; + int found, mclk_extra, mclk_loop, cbs, m1, p1; + int mclk_freq, pclk_freq, nvclk_freq, mp_enable; + int us_m, us_n, us_p, video_drain_rate, crtc_drain_rate; + int vpm_us, us_video, vlwm, video_fill_us, cpm_us, us_crt, clwm; + + fifo->valid = 1; + pclk_freq = arb->pclk_khz; + mclk_freq = arb->mclk_khz; + nvclk_freq = arb->nvclk_khz; + pagemiss = arb->mem_page_miss; + cas = arb->mem_latency; + width = arb->memory_width >> 6; + video_enable = arb->enable_video; + bpp = arb->pix_bpp; + mp_enable = arb->enable_mp; + clwm = 0; + vlwm = 0; + cbs = 128; + pclks = 2; + nvclks = 2; + nvclks += 2; + nvclks += 1; + mclks = 5; + mclks += 3; + mclks += 1; + mclks += cas; + mclks += 1; + mclks += 1; + mclks += 1; + mclks += 1; + mclk_extra = 3; + nvclks += 2; + nvclks += 1; + nvclks += 1; + nvclks += 1; + if (mp_enable) + mclks += 4; + nvclks += 0; + pclks += 0; + found = 0; + vbs = 0; + while (found != 1) { + fifo->valid = 1; + found = 1; + mclk_loop = mclks + mclk_extra; + us_m = mclk_loop * 1000 * 1000 / mclk_freq; + us_n = nvclks * 1000 * 1000 / nvclk_freq; + us_p = nvclks * 1000 * 1000 / pclk_freq; + if (video_enable) { + video_drain_rate = pclk_freq * 2; + crtc_drain_rate = pclk_freq * bpp / 8; + vpagemiss = 2; + vpagemiss += 1; + crtpagemiss = 2; + vpm_us = + (vpagemiss * pagemiss) * 1000 * 1000 / mclk_freq; + if (nvclk_freq * 2 > mclk_freq * width) + video_fill_us = + cbs * 1000 * 1000 / 16 / nvclk_freq; + else + video_fill_us = + cbs * 1000 * 1000 / (8 * width) / + mclk_freq; + us_video = vpm_us + us_m + us_n + us_p + video_fill_us; + vlwm = us_video * video_drain_rate / (1000 * 1000); + vlwm++; + vbs = 128; + if (vlwm > 128) + vbs = 64; + if (vlwm > (256 - 64)) + vbs = 32; + if (nvclk_freq * 2 > mclk_freq * width) + video_fill_us = + vbs * 1000 * 1000 / 16 / nvclk_freq; + else + video_fill_us = + vbs * 1000 * 1000 / (8 * width) / + mclk_freq; + cpm_us = + crtpagemiss * pagemiss * 1000 * 1000 / mclk_freq; + us_crt = + us_video + video_fill_us + cpm_us + us_m + us_n + + us_p; + clwm = us_crt * crtc_drain_rate / (1000 * 1000); + clwm++; + } else { + crtc_drain_rate = pclk_freq * bpp / 8; + crtpagemiss = 2; + crtpagemiss += 1; + cpm_us = + crtpagemiss * pagemiss * 1000 * 1000 / mclk_freq; + us_crt = cpm_us + us_m + us_n + us_p; + clwm = us_crt * crtc_drain_rate / (1000 * 1000); + clwm++; + } + m1 = clwm + cbs - 512; + p1 = m1 * pclk_freq / mclk_freq; + p1 = p1 * bpp / 8; + if ((p1 < m1) && (m1 > 0)) { + fifo->valid = 0; + found = 0; + if (mclk_extra == 0) + found = 1; + mclk_extra--; + } else if (video_enable) { + if ((clwm > 511) || (vlwm > 255)) { + fifo->valid = 0; + found = 0; + if (mclk_extra == 0) + found = 1; + mclk_extra--; + } + } else { + if (clwm > 519) { + fifo->valid = 0; + found = 0; + if (mclk_extra == 0) + found = 1; + mclk_extra--; + } + } + if (clwm < 384) + clwm = 384; + if (vlwm < 128) + vlwm = 128; + data = (int)(clwm); + fifo->graphics_lwm = data; + fifo->graphics_burst_size = 128; + data = (int)((vlwm + 15)); + fifo->video_lwm = data; + fifo->video_burst_size = vbs; + } +} + +static void nv4UpdateArbitrationSettings(unsigned VClk, + unsigned pixelDepth, + unsigned *burst, + unsigned *lwm, struct nvidia_par *par) +{ + nv4_fifo_info fifo_data; + nv4_sim_state sim_data; + unsigned int MClk, NVClk, cfg1; + + nvGetClocks(par, &MClk, &NVClk); + + cfg1 = NV_RD32(par->PFB, 0x00000204); + sim_data.pix_bpp = (char)pixelDepth; + sim_data.enable_video = 0; + sim_data.enable_mp = 0; + sim_data.memory_width = (NV_RD32(par->PEXTDEV, 0x0000) & 0x10) ? + 128 : 64; + sim_data.mem_latency = (char)cfg1 & 0x0F; + sim_data.mem_aligned = 1; + sim_data.mem_page_miss = + (char)(((cfg1 >> 4) & 0x0F) + ((cfg1 >> 31) & 0x01)); + sim_data.gr_during_vid = 0; + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + sim_data.nvclk_khz = NVClk; + nv4CalcArbitration(&fifo_data, &sim_data); + if (fifo_data.valid) { + int b = fifo_data.graphics_burst_size >> 4; + *burst = 0; + while (b >>= 1) + (*burst)++; + *lwm = fifo_data.graphics_lwm >> 3; + } +} + +static void nv10CalcArbitration(nv10_fifo_info * fifo, nv10_sim_state * arb) +{ + int data, pagemiss, width, video_enable, bpp; + int nvclks, mclks, pclks, vpagemiss, crtpagemiss; + int nvclk_fill; + int found, mclk_extra, mclk_loop, cbs, m1; + int mclk_freq, pclk_freq, nvclk_freq, mp_enable; + int us_m, us_m_min, us_n, us_p, crtc_drain_rate; + int vus_m; + int vpm_us, us_video, cpm_us, us_crt, clwm; + int clwm_rnd_down; + int m2us, us_pipe_min, p1clk, p2; + int min_mclk_extra; + int us_min_mclk_extra; + + fifo->valid = 1; + pclk_freq = arb->pclk_khz; /* freq in KHz */ + mclk_freq = arb->mclk_khz; + nvclk_freq = arb->nvclk_khz; + pagemiss = arb->mem_page_miss; + width = arb->memory_width / 64; + video_enable = arb->enable_video; + bpp = arb->pix_bpp; + mp_enable = arb->enable_mp; + clwm = 0; + + cbs = 512; + + pclks = 4; /* lwm detect. */ + + nvclks = 3; /* lwm -> sync. */ + nvclks += 2; /* fbi bus cycles (1 req + 1 busy) */ + /* 2 edge sync. may be very close to edge so just put one. */ + mclks = 1; + mclks += 1; /* arb_hp_req */ + mclks += 5; /* ap_hp_req tiling pipeline */ + + mclks += 2; /* tc_req latency fifo */ + mclks += 2; /* fb_cas_n_ memory request to fbio block */ + mclks += 7; /* sm_d_rdv data returned from fbio block */ + + /* fb.rd.d.Put_gc need to accumulate 256 bits for read */ + if (arb->memory_type == 0) + if (arb->memory_width == 64) /* 64 bit bus */ + mclks += 4; + else + mclks += 2; + else if (arb->memory_width == 64) /* 64 bit bus */ + mclks += 2; + else + mclks += 1; + + if ((!video_enable) && (arb->memory_width == 128)) { + mclk_extra = (bpp == 32) ? 31 : 42; /* Margin of error */ + min_mclk_extra = 17; + } else { + mclk_extra = (bpp == 32) ? 8 : 4; /* Margin of error */ + /* mclk_extra = 4; *//* Margin of error */ + min_mclk_extra = 18; + } + + /* 2 edge sync. may be very close to edge so just put one. */ + nvclks += 1; + nvclks += 1; /* fbi_d_rdv_n */ + nvclks += 1; /* Fbi_d_rdata */ + nvclks += 1; /* crtfifo load */ + + if (mp_enable) + mclks += 4; /* Mp can get in with a burst of 8. */ + /* Extra clocks determined by heuristics */ + + nvclks += 0; + pclks += 0; + found = 0; + while (found != 1) { + fifo->valid = 1; + found = 1; + mclk_loop = mclks + mclk_extra; + /* Mclk latency in us */ + us_m = mclk_loop * 1000 * 1000 / mclk_freq; + /* Minimum Mclk latency in us */ + us_m_min = mclks * 1000 * 1000 / mclk_freq; + us_min_mclk_extra = min_mclk_extra * 1000 * 1000 / mclk_freq; + /* nvclk latency in us */ + us_n = nvclks * 1000 * 1000 / nvclk_freq; + /* nvclk latency in us */ + us_p = pclks * 1000 * 1000 / pclk_freq; + us_pipe_min = us_m_min + us_n + us_p; + + /* Mclk latency in us */ + vus_m = mclk_loop * 1000 * 1000 / mclk_freq; + + if (video_enable) { + crtc_drain_rate = pclk_freq * bpp / 8; /* MB/s */ + + vpagemiss = 1; /* self generating page miss */ + vpagemiss += 1; /* One higher priority before */ + + crtpagemiss = 2; /* self generating page miss */ + if (mp_enable) + crtpagemiss += 1; /* if MA0 conflict */ + + vpm_us = + (vpagemiss * pagemiss) * 1000 * 1000 / mclk_freq; + + /* Video has separate read return path */ + us_video = vpm_us + vus_m; + + cpm_us = + crtpagemiss * pagemiss * 1000 * 1000 / mclk_freq; + /* Wait for video */ + us_crt = us_video + + cpm_us /* CRT Page miss */ + + us_m + us_n + us_p /* other latency */ + ; + + clwm = us_crt * crtc_drain_rate / (1000 * 1000); + /* fixed point <= float_point - 1. Fixes that */ + clwm++; + } else { + /* bpp * pclk/8 */ + crtc_drain_rate = pclk_freq * bpp / 8; + + crtpagemiss = 1; /* self generating page miss */ + crtpagemiss += 1; /* MA0 page miss */ + if (mp_enable) + crtpagemiss += 1; /* if MA0 conflict */ + cpm_us = + crtpagemiss * pagemiss * 1000 * 1000 / mclk_freq; + us_crt = cpm_us + us_m + us_n + us_p; + clwm = us_crt * crtc_drain_rate / (1000 * 1000); + /* fixed point <= float_point - 1. Fixes that */ + clwm++; + + /* Finally, a heuristic check when width == 64 bits */ + if (width == 1) { + nvclk_fill = nvclk_freq * 8; + if (crtc_drain_rate * 100 >= nvclk_fill * 102) + /*Large number to fail */ + clwm = 0xfff; + + else if (crtc_drain_rate * 100 >= + nvclk_fill * 98) { + clwm = 1024; + cbs = 512; + } + } + } + + /* + Overfill check: + */ + + clwm_rnd_down = ((int)clwm / 8) * 8; + if (clwm_rnd_down < clwm) + clwm += 8; + + m1 = clwm + cbs - 1024; /* Amount of overfill */ + m2us = us_pipe_min + us_min_mclk_extra; + + /* pclk cycles to drain */ + p1clk = m2us * pclk_freq / (1000 * 1000); + p2 = p1clk * bpp / 8; /* bytes drained. */ + + if ((p2 < m1) && (m1 > 0)) { + fifo->valid = 0; + found = 0; + if (min_mclk_extra == 0) { + if (cbs <= 32) { + /* Can't adjust anymore! */ + found = 1; + } else { + /* reduce the burst size */ + cbs = cbs / 2; + } + } else { + min_mclk_extra--; + } + } else { + if (clwm > 1023) { /* Have some margin */ + fifo->valid = 0; + found = 0; + if (min_mclk_extra == 0) + /* Can't adjust anymore! */ + found = 1; + else + min_mclk_extra--; + } + } + + if (clwm < (1024 - cbs + 8)) + clwm = 1024 - cbs + 8; + data = (int)(clwm); + /* printf("CRT LWM: %f bytes, prog: 0x%x, bs: 256\n", + clwm, data ); */ + fifo->graphics_lwm = data; + fifo->graphics_burst_size = cbs; + + fifo->video_lwm = 1024; + fifo->video_burst_size = 512; + } +} + +static void nv10UpdateArbitrationSettings(unsigned VClk, + unsigned pixelDepth, + unsigned *burst, + unsigned *lwm, + struct nvidia_par *par) +{ + nv10_fifo_info fifo_data; + nv10_sim_state sim_data; + unsigned int MClk, NVClk, cfg1; + + nvGetClocks(par, &MClk, &NVClk); + + cfg1 = NV_RD32(par->PFB, 0x0204); + sim_data.pix_bpp = (char)pixelDepth; + sim_data.enable_video = 1; + sim_data.enable_mp = 0; + sim_data.memory_type = (NV_RD32(par->PFB, 0x0200) & 0x01) ? 1 : 0; + sim_data.memory_width = (NV_RD32(par->PEXTDEV, 0x0000) & 0x10) ? + 128 : 64; + sim_data.mem_latency = (char)cfg1 & 0x0F; + sim_data.mem_aligned = 1; + sim_data.mem_page_miss = + (char)(((cfg1 >> 4) & 0x0F) + ((cfg1 >> 31) & 0x01)); + sim_data.gr_during_vid = 0; + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + sim_data.nvclk_khz = NVClk; + nv10CalcArbitration(&fifo_data, &sim_data); + if (fifo_data.valid) { + int b = fifo_data.graphics_burst_size >> 4; + *burst = 0; + while (b >>= 1) + (*burst)++; + *lwm = fifo_data.graphics_lwm >> 3; + } +} + +static void nv30UpdateArbitrationSettings ( + struct nvidia_par *par, + unsigned int *burst, + unsigned int *lwm +) +{ + unsigned int MClk, NVClk; + unsigned int fifo_size, burst_size, graphics_lwm; + + fifo_size = 2048; + burst_size = 512; + graphics_lwm = fifo_size - burst_size; + + nvGetClocks(par, &MClk, &NVClk); + + *burst = 0; + burst_size >>= 5; + while(burst_size >>= 1) (*burst)++; + *lwm = graphics_lwm >> 3; +} + +static void nForceUpdateArbitrationSettings(unsigned VClk, + unsigned pixelDepth, + unsigned *burst, + unsigned *lwm, + struct nvidia_par *par) +{ + nv10_fifo_info fifo_data; + nv10_sim_state sim_data; + unsigned int M, N, P, pll, MClk, NVClk, memctrl; + struct pci_dev *dev; + + if ((par->Chipset & 0x0FF0) == 0x01A0) { + unsigned int uMClkPostDiv; + dev = pci_get_bus_and_slot(0, 3); + pci_read_config_dword(dev, 0x6C, &uMClkPostDiv); + uMClkPostDiv = (uMClkPostDiv >> 8) & 0xf; + + if (!uMClkPostDiv) + uMClkPostDiv = 4; + MClk = 400000 / uMClkPostDiv; + } else { + dev = pci_get_bus_and_slot(0, 5); + pci_read_config_dword(dev, 0x4c, &MClk); + MClk /= 1000; + } + pci_dev_put(dev); + pll = NV_RD32(par->PRAMDAC0, 0x0500); + M = (pll >> 0) & 0xFF; + N = (pll >> 8) & 0xFF; + P = (pll >> 16) & 0x0F; + NVClk = (N * par->CrystalFreqKHz / M) >> P; + sim_data.pix_bpp = (char)pixelDepth; + sim_data.enable_video = 0; + sim_data.enable_mp = 0; + dev = pci_get_bus_and_slot(0, 1); + pci_read_config_dword(dev, 0x7C, &sim_data.memory_type); + pci_dev_put(dev); + sim_data.memory_type = (sim_data.memory_type >> 12) & 1; + sim_data.memory_width = 64; + + dev = pci_get_bus_and_slot(0, 3); + pci_read_config_dword(dev, 0, &memctrl); + pci_dev_put(dev); + memctrl >>= 16; + + if ((memctrl == 0x1A9) || (memctrl == 0x1AB) || (memctrl == 0x1ED)) { + u32 dimm[3]; + + dev = pci_get_bus_and_slot(0, 2); + pci_read_config_dword(dev, 0x40, &dimm[0]); + dimm[0] = (dimm[0] >> 8) & 0x4f; + pci_read_config_dword(dev, 0x44, &dimm[1]); + dimm[1] = (dimm[1] >> 8) & 0x4f; + pci_read_config_dword(dev, 0x48, &dimm[2]); + dimm[2] = (dimm[2] >> 8) & 0x4f; + + if ((dimm[0] + dimm[1]) != dimm[2]) { + printk("nvidiafb: your nForce DIMMs are not arranged " + "in optimal banks!\n"); + } + pci_dev_put(dev); + } + + sim_data.mem_latency = 3; + sim_data.mem_aligned = 1; + sim_data.mem_page_miss = 10; + sim_data.gr_during_vid = 0; + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + sim_data.nvclk_khz = NVClk; + nv10CalcArbitration(&fifo_data, &sim_data); + if (fifo_data.valid) { + int b = fifo_data.graphics_burst_size >> 4; + *burst = 0; + while (b >>= 1) + (*burst)++; + *lwm = fifo_data.graphics_lwm >> 3; + } +} + +/****************************************************************************\ +* * +* RIVA Mode State Routines * +* * +\****************************************************************************/ + +/* + * Calculate the Video Clock parameters for the PLL. + */ +static void CalcVClock(int clockIn, + int *clockOut, u32 * pllOut, struct nvidia_par *par) +{ + unsigned lowM, highM; + unsigned DeltaNew, DeltaOld; + unsigned VClk, Freq; + unsigned M, N, P; + + DeltaOld = 0xFFFFFFFF; + + VClk = (unsigned)clockIn; + + if (par->CrystalFreqKHz == 13500) { + lowM = 7; + highM = 13; + } else { + lowM = 8; + highM = 14; + } + + for (P = 0; P <= 4; P++) { + Freq = VClk << P; + if ((Freq >= 128000) && (Freq <= 350000)) { + for (M = lowM; M <= highM; M++) { + N = ((VClk << P) * M) / par->CrystalFreqKHz; + if (N <= 255) { + Freq = + ((par->CrystalFreqKHz * N) / + M) >> P; + if (Freq > VClk) + DeltaNew = Freq - VClk; + else + DeltaNew = VClk - Freq; + if (DeltaNew < DeltaOld) { + *pllOut = + (P << 16) | (N << 8) | M; + *clockOut = Freq; + DeltaOld = DeltaNew; + } + } + } + } + } +} + +static void CalcVClock2Stage(int clockIn, + int *clockOut, + u32 * pllOut, + u32 * pllBOut, struct nvidia_par *par) +{ + unsigned DeltaNew, DeltaOld; + unsigned VClk, Freq; + unsigned M, N, P; + + DeltaOld = 0xFFFFFFFF; + + *pllBOut = 0x80000401; /* fixed at x4 for now */ + + VClk = (unsigned)clockIn; + + for (P = 0; P <= 6; P++) { + Freq = VClk << P; + if ((Freq >= 400000) && (Freq <= 1000000)) { + for (M = 1; M <= 13; M++) { + N = ((VClk << P) * M) / + (par->CrystalFreqKHz << 2); + if ((N >= 5) && (N <= 255)) { + Freq = + (((par->CrystalFreqKHz << 2) * N) / + M) >> P; + if (Freq > VClk) + DeltaNew = Freq - VClk; + else + DeltaNew = VClk - Freq; + if (DeltaNew < DeltaOld) { + *pllOut = + (P << 16) | (N << 8) | M; + *clockOut = Freq; + DeltaOld = DeltaNew; + } + } + } + } + } +} + +/* + * Calculate extended mode parameters (SVGA) and save in a + * mode state structure. + */ +void NVCalcStateExt(struct nvidia_par *par, + RIVA_HW_STATE * state, + int bpp, + int width, + int hDisplaySize, int height, int dotClock, int flags) +{ + int pixelDepth, VClk = 0; + /* + * Save mode parameters. + */ + state->bpp = bpp; /* this is not bitsPerPixel, it's 8,15,16,32 */ + state->width = width; + state->height = height; + /* + * Extended RIVA registers. + */ + pixelDepth = (bpp + 1) / 8; + if (par->twoStagePLL) + CalcVClock2Stage(dotClock, &VClk, &state->pll, &state->pllB, + par); + else + CalcVClock(dotClock, &VClk, &state->pll, par); + + switch (par->Architecture) { + case NV_ARCH_04: + nv4UpdateArbitrationSettings(VClk, + pixelDepth * 8, + &(state->arbitration0), + &(state->arbitration1), par); + state->cursor0 = 0x00; + state->cursor1 = 0xbC; + if (flags & FB_VMODE_DOUBLE) + state->cursor1 |= 2; + state->cursor2 = 0x00000000; + state->pllsel = 0x10000700; + state->config = 0x00001114; + state->general = bpp == 16 ? 0x00101100 : 0x00100100; + state->repaint1 = hDisplaySize < 1280 ? 0x04 : 0x00; + break; + case NV_ARCH_40: + if (!par->FlatPanel) + state->control = NV_RD32(par->PRAMDAC0, 0x0580) & + 0xeffffeff; + /* fallthrough */ + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + default: + if ((par->Chipset & 0xfff0) == 0x0240 || + (par->Chipset & 0xfff0) == 0x03d0) { + state->arbitration0 = 256; + state->arbitration1 = 0x0480; + } else if (((par->Chipset & 0xffff) == 0x01A0) || + ((par->Chipset & 0xffff) == 0x01f0)) { + nForceUpdateArbitrationSettings(VClk, + pixelDepth * 8, + &(state->arbitration0), + &(state->arbitration1), + par); + } else if (par->Architecture < NV_ARCH_30) { + nv10UpdateArbitrationSettings(VClk, + pixelDepth * 8, + &(state->arbitration0), + &(state->arbitration1), + par); + } else { + nv30UpdateArbitrationSettings(par, + &(state->arbitration0), + &(state->arbitration1)); + } + + state->cursor0 = 0x80 | (par->CursorStart >> 17); + state->cursor1 = (par->CursorStart >> 11) << 2; + state->cursor2 = par->CursorStart >> 24; + if (flags & FB_VMODE_DOUBLE) + state->cursor1 |= 2; + state->pllsel = 0x10000700; + state->config = NV_RD32(par->PFB, 0x00000200); + state->general = bpp == 16 ? 0x00101100 : 0x00100100; + state->repaint1 = hDisplaySize < 1280 ? 0x04 : 0x00; + break; + } + + if (bpp != 8) /* DirectColor */ + state->general |= 0x00000030; + + state->repaint0 = (((width / 8) * pixelDepth) & 0x700) >> 3; + state->pixel = (pixelDepth > 2) ? 3 : pixelDepth; +} + +void NVLoadStateExt(struct nvidia_par *par, RIVA_HW_STATE * state) +{ + int i, j; + + NV_WR32(par->PMC, 0x0140, 0x00000000); + NV_WR32(par->PMC, 0x0200, 0xFFFF00FF); + NV_WR32(par->PMC, 0x0200, 0xFFFFFFFF); + + NV_WR32(par->PTIMER, 0x0200 * 4, 0x00000008); + NV_WR32(par->PTIMER, 0x0210 * 4, 0x00000003); + NV_WR32(par->PTIMER, 0x0140 * 4, 0x00000000); + NV_WR32(par->PTIMER, 0x0100 * 4, 0xFFFFFFFF); + + if (par->Architecture == NV_ARCH_04) { + if (state) + NV_WR32(par->PFB, 0x0200, state->config); + } else if ((par->Architecture < NV_ARCH_40) || + (par->Chipset & 0xfff0) == 0x0040) { + for (i = 0; i < 8; i++) { + NV_WR32(par->PFB, 0x0240 + (i * 0x10), 0); + NV_WR32(par->PFB, 0x0244 + (i * 0x10), + par->FbMapSize - 1); + } + } else { + int regions = 12; + + if (((par->Chipset & 0xfff0) == 0x0090) || + ((par->Chipset & 0xfff0) == 0x01D0) || + ((par->Chipset & 0xfff0) == 0x0290) || + ((par->Chipset & 0xfff0) == 0x0390) || + ((par->Chipset & 0xfff0) == 0x03D0)) + regions = 15; + for(i = 0; i < regions; i++) { + NV_WR32(par->PFB, 0x0600 + (i * 0x10), 0); + NV_WR32(par->PFB, 0x0604 + (i * 0x10), + par->FbMapSize - 1); + } + } + + if (par->Architecture >= NV_ARCH_40) { + NV_WR32(par->PRAMIN, 0x0000 * 4, 0x80000010); + NV_WR32(par->PRAMIN, 0x0001 * 4, 0x00101202); + NV_WR32(par->PRAMIN, 0x0002 * 4, 0x80000011); + NV_WR32(par->PRAMIN, 0x0003 * 4, 0x00101204); + NV_WR32(par->PRAMIN, 0x0004 * 4, 0x80000012); + NV_WR32(par->PRAMIN, 0x0005 * 4, 0x00101206); + NV_WR32(par->PRAMIN, 0x0006 * 4, 0x80000013); + NV_WR32(par->PRAMIN, 0x0007 * 4, 0x00101208); + NV_WR32(par->PRAMIN, 0x0008 * 4, 0x80000014); + NV_WR32(par->PRAMIN, 0x0009 * 4, 0x0010120A); + NV_WR32(par->PRAMIN, 0x000A * 4, 0x80000015); + NV_WR32(par->PRAMIN, 0x000B * 4, 0x0010120C); + NV_WR32(par->PRAMIN, 0x000C * 4, 0x80000016); + NV_WR32(par->PRAMIN, 0x000D * 4, 0x0010120E); + NV_WR32(par->PRAMIN, 0x000E * 4, 0x80000017); + NV_WR32(par->PRAMIN, 0x000F * 4, 0x00101210); + NV_WR32(par->PRAMIN, 0x0800 * 4, 0x00003000); + NV_WR32(par->PRAMIN, 0x0801 * 4, par->FbMapSize - 1); + NV_WR32(par->PRAMIN, 0x0802 * 4, 0x00000002); + NV_WR32(par->PRAMIN, 0x0808 * 4, 0x02080062); + NV_WR32(par->PRAMIN, 0x0809 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x080A * 4, 0x00001200); + NV_WR32(par->PRAMIN, 0x080B * 4, 0x00001200); + NV_WR32(par->PRAMIN, 0x080C * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x080D * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0810 * 4, 0x02080043); + NV_WR32(par->PRAMIN, 0x0811 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0812 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0813 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0814 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0815 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0818 * 4, 0x02080044); + NV_WR32(par->PRAMIN, 0x0819 * 4, 0x02000000); + NV_WR32(par->PRAMIN, 0x081A * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x081B * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x081C * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x081D * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0820 * 4, 0x02080019); + NV_WR32(par->PRAMIN, 0x0821 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0822 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0823 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0824 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0825 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0828 * 4, 0x020A005C); + NV_WR32(par->PRAMIN, 0x0829 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x082A * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x082B * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x082C * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x082D * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0830 * 4, 0x0208009F); + NV_WR32(par->PRAMIN, 0x0831 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0832 * 4, 0x00001200); + NV_WR32(par->PRAMIN, 0x0833 * 4, 0x00001200); + NV_WR32(par->PRAMIN, 0x0834 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0835 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0838 * 4, 0x0208004A); + NV_WR32(par->PRAMIN, 0x0839 * 4, 0x02000000); + NV_WR32(par->PRAMIN, 0x083A * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x083B * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x083C * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x083D * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0840 * 4, 0x02080077); + NV_WR32(par->PRAMIN, 0x0841 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0842 * 4, 0x00001200); + NV_WR32(par->PRAMIN, 0x0843 * 4, 0x00001200); + NV_WR32(par->PRAMIN, 0x0844 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0845 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x084C * 4, 0x00003002); + NV_WR32(par->PRAMIN, 0x084D * 4, 0x00007FFF); + NV_WR32(par->PRAMIN, 0x084E * 4, + par->FbUsableSize | 0x00000002); + +#ifdef __BIG_ENDIAN + NV_WR32(par->PRAMIN, 0x080A * 4, + NV_RD32(par->PRAMIN, 0x080A * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x0812 * 4, + NV_RD32(par->PRAMIN, 0x0812 * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x081A * 4, + NV_RD32(par->PRAMIN, 0x081A * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x0822 * 4, + NV_RD32(par->PRAMIN, 0x0822 * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x082A * 4, + NV_RD32(par->PRAMIN, 0x082A * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x0832 * 4, + NV_RD32(par->PRAMIN, 0x0832 * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x083A * 4, + NV_RD32(par->PRAMIN, 0x083A * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x0842 * 4, + NV_RD32(par->PRAMIN, 0x0842 * 4) | 0x01000000); + NV_WR32(par->PRAMIN, 0x0819 * 4, 0x01000000); + NV_WR32(par->PRAMIN, 0x0839 * 4, 0x01000000); +#endif + } else { + NV_WR32(par->PRAMIN, 0x0000 * 4, 0x80000010); + NV_WR32(par->PRAMIN, 0x0001 * 4, 0x80011201); + NV_WR32(par->PRAMIN, 0x0002 * 4, 0x80000011); + NV_WR32(par->PRAMIN, 0x0003 * 4, 0x80011202); + NV_WR32(par->PRAMIN, 0x0004 * 4, 0x80000012); + NV_WR32(par->PRAMIN, 0x0005 * 4, 0x80011203); + NV_WR32(par->PRAMIN, 0x0006 * 4, 0x80000013); + NV_WR32(par->PRAMIN, 0x0007 * 4, 0x80011204); + NV_WR32(par->PRAMIN, 0x0008 * 4, 0x80000014); + NV_WR32(par->PRAMIN, 0x0009 * 4, 0x80011205); + NV_WR32(par->PRAMIN, 0x000A * 4, 0x80000015); + NV_WR32(par->PRAMIN, 0x000B * 4, 0x80011206); + NV_WR32(par->PRAMIN, 0x000C * 4, 0x80000016); + NV_WR32(par->PRAMIN, 0x000D * 4, 0x80011207); + NV_WR32(par->PRAMIN, 0x000E * 4, 0x80000017); + NV_WR32(par->PRAMIN, 0x000F * 4, 0x80011208); + NV_WR32(par->PRAMIN, 0x0800 * 4, 0x00003000); + NV_WR32(par->PRAMIN, 0x0801 * 4, par->FbMapSize - 1); + NV_WR32(par->PRAMIN, 0x0802 * 4, 0x00000002); + NV_WR32(par->PRAMIN, 0x0803 * 4, 0x00000002); + if (par->Architecture >= NV_ARCH_10) + NV_WR32(par->PRAMIN, 0x0804 * 4, 0x01008062); + else + NV_WR32(par->PRAMIN, 0x0804 * 4, 0x01008042); + NV_WR32(par->PRAMIN, 0x0805 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0806 * 4, 0x12001200); + NV_WR32(par->PRAMIN, 0x0807 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0808 * 4, 0x01008043); + NV_WR32(par->PRAMIN, 0x0809 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x080A * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x080B * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x080C * 4, 0x01008044); + NV_WR32(par->PRAMIN, 0x080D * 4, 0x00000002); + NV_WR32(par->PRAMIN, 0x080E * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x080F * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0810 * 4, 0x01008019); + NV_WR32(par->PRAMIN, 0x0811 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0812 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0813 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0814 * 4, 0x0100A05C); + NV_WR32(par->PRAMIN, 0x0815 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0816 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0817 * 4, 0x00000000); + if (par->WaitVSyncPossible) + NV_WR32(par->PRAMIN, 0x0818 * 4, 0x0100809F); + else + NV_WR32(par->PRAMIN, 0x0818 * 4, 0x0100805F); + NV_WR32(par->PRAMIN, 0x0819 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x081A * 4, 0x12001200); + NV_WR32(par->PRAMIN, 0x081B * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x081C * 4, 0x0100804A); + NV_WR32(par->PRAMIN, 0x081D * 4, 0x00000002); + NV_WR32(par->PRAMIN, 0x081E * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x081F * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0820 * 4, 0x01018077); + NV_WR32(par->PRAMIN, 0x0821 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0822 * 4, 0x12001200); + NV_WR32(par->PRAMIN, 0x0823 * 4, 0x00000000); + NV_WR32(par->PRAMIN, 0x0824 * 4, 0x00003002); + NV_WR32(par->PRAMIN, 0x0825 * 4, 0x00007FFF); + NV_WR32(par->PRAMIN, 0x0826 * 4, + par->FbUsableSize | 0x00000002); + NV_WR32(par->PRAMIN, 0x0827 * 4, 0x00000002); +#ifdef __BIG_ENDIAN + NV_WR32(par->PRAMIN, 0x0804 * 4, + NV_RD32(par->PRAMIN, 0x0804 * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x0808 * 4, + NV_RD32(par->PRAMIN, 0x0808 * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x080C * 4, + NV_RD32(par->PRAMIN, 0x080C * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x0810 * 4, + NV_RD32(par->PRAMIN, 0x0810 * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x0814 * 4, + NV_RD32(par->PRAMIN, 0x0814 * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x0818 * 4, + NV_RD32(par->PRAMIN, 0x0818 * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x081C * 4, + NV_RD32(par->PRAMIN, 0x081C * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x0820 * 4, + NV_RD32(par->PRAMIN, 0x0820 * 4) | 0x00080000); + NV_WR32(par->PRAMIN, 0x080D * 4, 0x00000001); + NV_WR32(par->PRAMIN, 0x081D * 4, 0x00000001); +#endif + } + if (par->Architecture < NV_ARCH_10) { + if ((par->Chipset & 0x0fff) == 0x0020) { + NV_WR32(par->PRAMIN, 0x0824 * 4, + NV_RD32(par->PRAMIN, 0x0824 * 4) | 0x00020000); + NV_WR32(par->PRAMIN, 0x0826 * 4, + NV_RD32(par->PRAMIN, + 0x0826 * 4) + par->FbAddress); + } + NV_WR32(par->PGRAPH, 0x0080, 0x000001FF); + NV_WR32(par->PGRAPH, 0x0080, 0x1230C000); + NV_WR32(par->PGRAPH, 0x0084, 0x72111101); + NV_WR32(par->PGRAPH, 0x0088, 0x11D5F071); + NV_WR32(par->PGRAPH, 0x008C, 0x0004FF31); + NV_WR32(par->PGRAPH, 0x008C, 0x4004FF31); + NV_WR32(par->PGRAPH, 0x0140, 0x00000000); + NV_WR32(par->PGRAPH, 0x0100, 0xFFFFFFFF); + NV_WR32(par->PGRAPH, 0x0170, 0x10010100); + NV_WR32(par->PGRAPH, 0x0710, 0xFFFFFFFF); + NV_WR32(par->PGRAPH, 0x0720, 0x00000001); + NV_WR32(par->PGRAPH, 0x0810, 0x00000000); + NV_WR32(par->PGRAPH, 0x0608, 0xFFFFFFFF); + } else { + NV_WR32(par->PGRAPH, 0x0080, 0xFFFFFFFF); + NV_WR32(par->PGRAPH, 0x0080, 0x00000000); + + NV_WR32(par->PGRAPH, 0x0140, 0x00000000); + NV_WR32(par->PGRAPH, 0x0100, 0xFFFFFFFF); + NV_WR32(par->PGRAPH, 0x0144, 0x10010100); + NV_WR32(par->PGRAPH, 0x0714, 0xFFFFFFFF); + NV_WR32(par->PGRAPH, 0x0720, 0x00000001); + NV_WR32(par->PGRAPH, 0x0710, + NV_RD32(par->PGRAPH, 0x0710) & 0x0007ff00); + NV_WR32(par->PGRAPH, 0x0710, + NV_RD32(par->PGRAPH, 0x0710) | 0x00020100); + + if (par->Architecture == NV_ARCH_10) { + NV_WR32(par->PGRAPH, 0x0084, 0x00118700); + NV_WR32(par->PGRAPH, 0x0088, 0x24E00810); + NV_WR32(par->PGRAPH, 0x008C, 0x55DE0030); + + for (i = 0; i < 32; i++) + NV_WR32(&par->PGRAPH[(0x0B00 / 4) + i], 0, + NV_RD32(&par->PFB[(0x0240 / 4) + i], + 0)); + + NV_WR32(par->PGRAPH, 0x640, 0); + NV_WR32(par->PGRAPH, 0x644, 0); + NV_WR32(par->PGRAPH, 0x684, par->FbMapSize - 1); + NV_WR32(par->PGRAPH, 0x688, par->FbMapSize - 1); + + NV_WR32(par->PGRAPH, 0x0810, 0x00000000); + NV_WR32(par->PGRAPH, 0x0608, 0xFFFFFFFF); + } else { + if (par->Architecture >= NV_ARCH_40) { + NV_WR32(par->PGRAPH, 0x0084, 0x401287c0); + NV_WR32(par->PGRAPH, 0x008C, 0x60de8051); + NV_WR32(par->PGRAPH, 0x0090, 0x00008000); + NV_WR32(par->PGRAPH, 0x0610, 0x00be3c5f); + NV_WR32(par->PGRAPH, 0x0bc4, + NV_RD32(par->PGRAPH, 0x0bc4) | + 0x00008000); + + j = NV_RD32(par->REGS, 0x1540) & 0xff; + + if (j) { + for (i = 0; !(j & 1); j >>= 1, i++); + NV_WR32(par->PGRAPH, 0x5000, i); + } + + if ((par->Chipset & 0xfff0) == 0x0040) { + NV_WR32(par->PGRAPH, 0x09b0, + 0x83280fff); + NV_WR32(par->PGRAPH, 0x09b4, + 0x000000a0); + } else { + NV_WR32(par->PGRAPH, 0x0820, + 0x83280eff); + NV_WR32(par->PGRAPH, 0x0824, + 0x000000a0); + } + + switch (par->Chipset & 0xfff0) { + case 0x0040: + case 0x0210: + NV_WR32(par->PGRAPH, 0x09b8, + 0x0078e366); + NV_WR32(par->PGRAPH, 0x09bc, + 0x0000014c); + NV_WR32(par->PFB, 0x033C, + NV_RD32(par->PFB, 0x33C) & + 0xffff7fff); + break; + case 0x00C0: + case 0x0120: + NV_WR32(par->PGRAPH, 0x0828, + 0x007596ff); + NV_WR32(par->PGRAPH, 0x082C, + 0x00000108); + break; + case 0x0160: + case 0x01D0: + case 0x0240: + case 0x03D0: + NV_WR32(par->PMC, 0x1700, + NV_RD32(par->PFB, 0x020C)); + NV_WR32(par->PMC, 0x1704, 0); + NV_WR32(par->PMC, 0x1708, 0); + NV_WR32(par->PMC, 0x170C, + NV_RD32(par->PFB, 0x020C)); + NV_WR32(par->PGRAPH, 0x0860, 0); + NV_WR32(par->PGRAPH, 0x0864, 0); + NV_WR32(par->PRAMDAC, 0x0608, + NV_RD32(par->PRAMDAC, + 0x0608) | 0x00100000); + break; + case 0x0140: + NV_WR32(par->PGRAPH, 0x0828, + 0x0072cb77); + NV_WR32(par->PGRAPH, 0x082C, + 0x00000108); + break; + case 0x0220: + NV_WR32(par->PGRAPH, 0x0860, 0); + NV_WR32(par->PGRAPH, 0x0864, 0); + NV_WR32(par->PRAMDAC, 0x0608, + NV_RD32(par->PRAMDAC, 0x0608) | + 0x00100000); + break; + case 0x0090: + case 0x0290: + case 0x0390: + NV_WR32(par->PRAMDAC, 0x0608, + NV_RD32(par->PRAMDAC, 0x0608) | + 0x00100000); + NV_WR32(par->PGRAPH, 0x0828, + 0x07830610); + NV_WR32(par->PGRAPH, 0x082C, + 0x0000016A); + break; + default: + break; + } + + NV_WR32(par->PGRAPH, 0x0b38, 0x2ffff800); + NV_WR32(par->PGRAPH, 0x0b3c, 0x00006000); + NV_WR32(par->PGRAPH, 0x032C, 0x01000000); + NV_WR32(par->PGRAPH, 0x0220, 0x00001200); + } else if (par->Architecture == NV_ARCH_30) { + NV_WR32(par->PGRAPH, 0x0084, 0x40108700); + NV_WR32(par->PGRAPH, 0x0890, 0x00140000); + NV_WR32(par->PGRAPH, 0x008C, 0xf00e0431); + NV_WR32(par->PGRAPH, 0x0090, 0x00008000); + NV_WR32(par->PGRAPH, 0x0610, 0xf04b1f36); + NV_WR32(par->PGRAPH, 0x0B80, 0x1002d888); + NV_WR32(par->PGRAPH, 0x0B88, 0x62ff007f); + } else { + NV_WR32(par->PGRAPH, 0x0084, 0x00118700); + NV_WR32(par->PGRAPH, 0x008C, 0xF20E0431); + NV_WR32(par->PGRAPH, 0x0090, 0x00000000); + NV_WR32(par->PGRAPH, 0x009C, 0x00000040); + + if ((par->Chipset & 0x0ff0) >= 0x0250) { + NV_WR32(par->PGRAPH, 0x0890, + 0x00080000); + NV_WR32(par->PGRAPH, 0x0610, + 0x304B1FB6); + NV_WR32(par->PGRAPH, 0x0B80, + 0x18B82880); + NV_WR32(par->PGRAPH, 0x0B84, + 0x44000000); + NV_WR32(par->PGRAPH, 0x0098, + 0x40000080); + NV_WR32(par->PGRAPH, 0x0B88, + 0x000000ff); + } else { + NV_WR32(par->PGRAPH, 0x0880, + 0x00080000); + NV_WR32(par->PGRAPH, 0x0094, + 0x00000005); + NV_WR32(par->PGRAPH, 0x0B80, + 0x45CAA208); + NV_WR32(par->PGRAPH, 0x0B84, + 0x24000000); + NV_WR32(par->PGRAPH, 0x0098, + 0x00000040); + NV_WR32(par->PGRAPH, 0x0750, + 0x00E00038); + NV_WR32(par->PGRAPH, 0x0754, + 0x00000030); + NV_WR32(par->PGRAPH, 0x0750, + 0x00E10038); + NV_WR32(par->PGRAPH, 0x0754, + 0x00000030); + } + } + + if ((par->Architecture < NV_ARCH_40) || + ((par->Chipset & 0xfff0) == 0x0040)) { + for (i = 0; i < 32; i++) { + NV_WR32(par->PGRAPH, 0x0900 + i*4, + NV_RD32(par->PFB, 0x0240 +i*4)); + NV_WR32(par->PGRAPH, 0x6900 + i*4, + NV_RD32(par->PFB, 0x0240 +i*4)); + } + } else { + if (((par->Chipset & 0xfff0) == 0x0090) || + ((par->Chipset & 0xfff0) == 0x01D0) || + ((par->Chipset & 0xfff0) == 0x0290) || + ((par->Chipset & 0xfff0) == 0x0390) || + ((par->Chipset & 0xfff0) == 0x03D0)) { + for (i = 0; i < 60; i++) { + NV_WR32(par->PGRAPH, + 0x0D00 + i*4, + NV_RD32(par->PFB, + 0x0600 + i*4)); + NV_WR32(par->PGRAPH, + 0x6900 + i*4, + NV_RD32(par->PFB, + 0x0600 + i*4)); + } + } else { + for (i = 0; i < 48; i++) { + NV_WR32(par->PGRAPH, + 0x0900 + i*4, + NV_RD32(par->PFB, + 0x0600 + i*4)); + if(((par->Chipset & 0xfff0) + != 0x0160) && + ((par->Chipset & 0xfff0) + != 0x0220) && + ((par->Chipset & 0xfff0) + != 0x240)) + NV_WR32(par->PGRAPH, + 0x6900 + i*4, + NV_RD32(par->PFB, + 0x0600 + i*4)); + } + } + } + + if (par->Architecture >= NV_ARCH_40) { + if ((par->Chipset & 0xfff0) == 0x0040) { + NV_WR32(par->PGRAPH, 0x09A4, + NV_RD32(par->PFB, 0x0200)); + NV_WR32(par->PGRAPH, 0x09A8, + NV_RD32(par->PFB, 0x0204)); + NV_WR32(par->PGRAPH, 0x69A4, + NV_RD32(par->PFB, 0x0200)); + NV_WR32(par->PGRAPH, 0x69A8, + NV_RD32(par->PFB, 0x0204)); + + NV_WR32(par->PGRAPH, 0x0820, 0); + NV_WR32(par->PGRAPH, 0x0824, 0); + NV_WR32(par->PGRAPH, 0x0864, + par->FbMapSize - 1); + NV_WR32(par->PGRAPH, 0x0868, + par->FbMapSize - 1); + } else { + if ((par->Chipset & 0xfff0) == 0x0090 || + (par->Chipset & 0xfff0) == 0x01D0 || + (par->Chipset & 0xfff0) == 0x0290 || + (par->Chipset & 0xfff0) == 0x0390) { + NV_WR32(par->PGRAPH, 0x0DF0, + NV_RD32(par->PFB, 0x0200)); + NV_WR32(par->PGRAPH, 0x0DF4, + NV_RD32(par->PFB, 0x0204)); + } else { + NV_WR32(par->PGRAPH, 0x09F0, + NV_RD32(par->PFB, 0x0200)); + NV_WR32(par->PGRAPH, 0x09F4, + NV_RD32(par->PFB, 0x0204)); + } + NV_WR32(par->PGRAPH, 0x69F0, + NV_RD32(par->PFB, 0x0200)); + NV_WR32(par->PGRAPH, 0x69F4, + NV_RD32(par->PFB, 0x0204)); + + NV_WR32(par->PGRAPH, 0x0840, 0); + NV_WR32(par->PGRAPH, 0x0844, 0); + NV_WR32(par->PGRAPH, 0x08a0, + par->FbMapSize - 1); + NV_WR32(par->PGRAPH, 0x08a4, + par->FbMapSize - 1); + } + } else { + NV_WR32(par->PGRAPH, 0x09A4, + NV_RD32(par->PFB, 0x0200)); + NV_WR32(par->PGRAPH, 0x09A8, + NV_RD32(par->PFB, 0x0204)); + NV_WR32(par->PGRAPH, 0x0750, 0x00EA0000); + NV_WR32(par->PGRAPH, 0x0754, + NV_RD32(par->PFB, 0x0200)); + NV_WR32(par->PGRAPH, 0x0750, 0x00EA0004); + NV_WR32(par->PGRAPH, 0x0754, + NV_RD32(par->PFB, 0x0204)); + + NV_WR32(par->PGRAPH, 0x0820, 0); + NV_WR32(par->PGRAPH, 0x0824, 0); + NV_WR32(par->PGRAPH, 0x0864, + par->FbMapSize - 1); + NV_WR32(par->PGRAPH, 0x0868, + par->FbMapSize - 1); + } + NV_WR32(par->PGRAPH, 0x0B20, 0x00000000); + NV_WR32(par->PGRAPH, 0x0B04, 0xFFFFFFFF); + } + } + NV_WR32(par->PGRAPH, 0x053C, 0); + NV_WR32(par->PGRAPH, 0x0540, 0); + NV_WR32(par->PGRAPH, 0x0544, 0x00007FFF); + NV_WR32(par->PGRAPH, 0x0548, 0x00007FFF); + + NV_WR32(par->PFIFO, 0x0140 * 4, 0x00000000); + NV_WR32(par->PFIFO, 0x0141 * 4, 0x00000001); + NV_WR32(par->PFIFO, 0x0480 * 4, 0x00000000); + NV_WR32(par->PFIFO, 0x0494 * 4, 0x00000000); + if (par->Architecture >= NV_ARCH_40) + NV_WR32(par->PFIFO, 0x0481 * 4, 0x00010000); + else + NV_WR32(par->PFIFO, 0x0481 * 4, 0x00000100); + NV_WR32(par->PFIFO, 0x0490 * 4, 0x00000000); + NV_WR32(par->PFIFO, 0x0491 * 4, 0x00000000); + if (par->Architecture >= NV_ARCH_40) + NV_WR32(par->PFIFO, 0x048B * 4, 0x00001213); + else + NV_WR32(par->PFIFO, 0x048B * 4, 0x00001209); + NV_WR32(par->PFIFO, 0x0400 * 4, 0x00000000); + NV_WR32(par->PFIFO, 0x0414 * 4, 0x00000000); + NV_WR32(par->PFIFO, 0x0084 * 4, 0x03000100); + NV_WR32(par->PFIFO, 0x0085 * 4, 0x00000110); + NV_WR32(par->PFIFO, 0x0086 * 4, 0x00000112); + NV_WR32(par->PFIFO, 0x0143 * 4, 0x0000FFFF); + NV_WR32(par->PFIFO, 0x0496 * 4, 0x0000FFFF); + NV_WR32(par->PFIFO, 0x0050 * 4, 0x00000000); + NV_WR32(par->PFIFO, 0x0040 * 4, 0xFFFFFFFF); + NV_WR32(par->PFIFO, 0x0415 * 4, 0x00000001); + NV_WR32(par->PFIFO, 0x048C * 4, 0x00000000); + NV_WR32(par->PFIFO, 0x04A0 * 4, 0x00000000); +#ifdef __BIG_ENDIAN + NV_WR32(par->PFIFO, 0x0489 * 4, 0x800F0078); +#else + NV_WR32(par->PFIFO, 0x0489 * 4, 0x000F0078); +#endif + NV_WR32(par->PFIFO, 0x0488 * 4, 0x00000001); + NV_WR32(par->PFIFO, 0x0480 * 4, 0x00000001); + NV_WR32(par->PFIFO, 0x0494 * 4, 0x00000001); + NV_WR32(par->PFIFO, 0x0495 * 4, 0x00000001); + NV_WR32(par->PFIFO, 0x0140 * 4, 0x00000001); + + if (!state) { + par->CurrentState = NULL; + return; + } + + if (par->Architecture >= NV_ARCH_10) { + if (par->twoHeads) { + NV_WR32(par->PCRTC0, 0x0860, state->head); + NV_WR32(par->PCRTC0, 0x2860, state->head2); + } + NV_WR32(par->PRAMDAC, 0x0404, NV_RD32(par->PRAMDAC, 0x0404) | + (1 << 25)); + + NV_WR32(par->PMC, 0x8704, 1); + NV_WR32(par->PMC, 0x8140, 0); + NV_WR32(par->PMC, 0x8920, 0); + NV_WR32(par->PMC, 0x8924, 0); + NV_WR32(par->PMC, 0x8908, par->FbMapSize - 1); + NV_WR32(par->PMC, 0x890C, par->FbMapSize - 1); + NV_WR32(par->PMC, 0x1588, 0); + + NV_WR32(par->PCRTC, 0x0810, state->cursorConfig); + NV_WR32(par->PCRTC, 0x0830, state->displayV - 3); + NV_WR32(par->PCRTC, 0x0834, state->displayV - 1); + + if (par->FlatPanel) { + if ((par->Chipset & 0x0ff0) == 0x0110) { + NV_WR32(par->PRAMDAC, 0x0528, state->dither); + } else if (par->twoHeads) { + NV_WR32(par->PRAMDAC, 0x083C, state->dither); + } + + VGA_WR08(par->PCIO, 0x03D4, 0x53); + VGA_WR08(par->PCIO, 0x03D5, state->timingH); + VGA_WR08(par->PCIO, 0x03D4, 0x54); + VGA_WR08(par->PCIO, 0x03D5, state->timingV); + VGA_WR08(par->PCIO, 0x03D4, 0x21); + VGA_WR08(par->PCIO, 0x03D5, 0xfa); + } + + VGA_WR08(par->PCIO, 0x03D4, 0x41); + VGA_WR08(par->PCIO, 0x03D5, state->extra); + } + + VGA_WR08(par->PCIO, 0x03D4, 0x19); + VGA_WR08(par->PCIO, 0x03D5, state->repaint0); + VGA_WR08(par->PCIO, 0x03D4, 0x1A); + VGA_WR08(par->PCIO, 0x03D5, state->repaint1); + VGA_WR08(par->PCIO, 0x03D4, 0x25); + VGA_WR08(par->PCIO, 0x03D5, state->screen); + VGA_WR08(par->PCIO, 0x03D4, 0x28); + VGA_WR08(par->PCIO, 0x03D5, state->pixel); + VGA_WR08(par->PCIO, 0x03D4, 0x2D); + VGA_WR08(par->PCIO, 0x03D5, state->horiz); + VGA_WR08(par->PCIO, 0x03D4, 0x1C); + VGA_WR08(par->PCIO, 0x03D5, state->fifo); + VGA_WR08(par->PCIO, 0x03D4, 0x1B); + VGA_WR08(par->PCIO, 0x03D5, state->arbitration0); + VGA_WR08(par->PCIO, 0x03D4, 0x20); + VGA_WR08(par->PCIO, 0x03D5, state->arbitration1); + + if(par->Architecture >= NV_ARCH_30) { + VGA_WR08(par->PCIO, 0x03D4, 0x47); + VGA_WR08(par->PCIO, 0x03D5, state->arbitration1 >> 8); + } + + VGA_WR08(par->PCIO, 0x03D4, 0x30); + VGA_WR08(par->PCIO, 0x03D5, state->cursor0); + VGA_WR08(par->PCIO, 0x03D4, 0x31); + VGA_WR08(par->PCIO, 0x03D5, state->cursor1); + VGA_WR08(par->PCIO, 0x03D4, 0x2F); + VGA_WR08(par->PCIO, 0x03D5, state->cursor2); + VGA_WR08(par->PCIO, 0x03D4, 0x39); + VGA_WR08(par->PCIO, 0x03D5, state->interlace); + + if (!par->FlatPanel) { + if (par->Architecture >= NV_ARCH_40) + NV_WR32(par->PRAMDAC0, 0x0580, state->control); + + NV_WR32(par->PRAMDAC0, 0x050C, state->pllsel); + NV_WR32(par->PRAMDAC0, 0x0508, state->vpll); + if (par->twoHeads) + NV_WR32(par->PRAMDAC0, 0x0520, state->vpll2); + if (par->twoStagePLL) { + NV_WR32(par->PRAMDAC0, 0x0578, state->vpllB); + NV_WR32(par->PRAMDAC0, 0x057C, state->vpll2B); + } + } else { + NV_WR32(par->PRAMDAC, 0x0848, state->scale); + NV_WR32(par->PRAMDAC, 0x0828, state->crtcSync + + par->PanelTweak); + } + + NV_WR32(par->PRAMDAC, 0x0600, state->general); + + NV_WR32(par->PCRTC, 0x0140, 0); + NV_WR32(par->PCRTC, 0x0100, 1); + + par->CurrentState = state; +} + +void NVUnloadStateExt(struct nvidia_par *par, RIVA_HW_STATE * state) { + VGA_WR08(par->PCIO, 0x03D4, 0x19); + state->repaint0 = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x1A); + state->repaint1 = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x25); + state->screen = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x28); + state->pixel = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x2D); + state->horiz = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x1C); + state->fifo = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x1B); + state->arbitration0 = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x20); + state->arbitration1 = VGA_RD08(par->PCIO, 0x03D5); + + if(par->Architecture >= NV_ARCH_30) { + VGA_WR08(par->PCIO, 0x03D4, 0x47); + state->arbitration1 |= (VGA_RD08(par->PCIO, 0x03D5) & 1) << 8; + } + + VGA_WR08(par->PCIO, 0x03D4, 0x30); + state->cursor0 = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x31); + state->cursor1 = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x2F); + state->cursor2 = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x39); + state->interlace = VGA_RD08(par->PCIO, 0x03D5); + state->vpll = NV_RD32(par->PRAMDAC0, 0x0508); + if (par->twoHeads) + state->vpll2 = NV_RD32(par->PRAMDAC0, 0x0520); + if (par->twoStagePLL) { + state->vpllB = NV_RD32(par->PRAMDAC0, 0x0578); + state->vpll2B = NV_RD32(par->PRAMDAC0, 0x057C); + } + state->pllsel = NV_RD32(par->PRAMDAC0, 0x050C); + state->general = NV_RD32(par->PRAMDAC, 0x0600); + state->scale = NV_RD32(par->PRAMDAC, 0x0848); + state->config = NV_RD32(par->PFB, 0x0200); + + if (par->Architecture >= NV_ARCH_40 && !par->FlatPanel) + state->control = NV_RD32(par->PRAMDAC0, 0x0580); + + if (par->Architecture >= NV_ARCH_10) { + if (par->twoHeads) { + state->head = NV_RD32(par->PCRTC0, 0x0860); + state->head2 = NV_RD32(par->PCRTC0, 0x2860); + VGA_WR08(par->PCIO, 0x03D4, 0x44); + state->crtcOwner = VGA_RD08(par->PCIO, 0x03D5); + } + VGA_WR08(par->PCIO, 0x03D4, 0x41); + state->extra = VGA_RD08(par->PCIO, 0x03D5); + state->cursorConfig = NV_RD32(par->PCRTC, 0x0810); + + if ((par->Chipset & 0x0ff0) == 0x0110) { + state->dither = NV_RD32(par->PRAMDAC, 0x0528); + } else if (par->twoHeads) { + state->dither = NV_RD32(par->PRAMDAC, 0x083C); + } + + if (par->FlatPanel) { + VGA_WR08(par->PCIO, 0x03D4, 0x53); + state->timingH = VGA_RD08(par->PCIO, 0x03D5); + VGA_WR08(par->PCIO, 0x03D4, 0x54); + state->timingV = VGA_RD08(par->PCIO, 0x03D5); + } + } +} + +void NVSetStartAddress(struct nvidia_par *par, u32 start) +{ + NV_WR32(par->PCRTC, 0x800, start); +} diff --git a/drivers/video/fbdev/nvidia/nv_i2c.c b/drivers/video/fbdev/nvidia/nv_i2c.c new file mode 100644 index 000000000000..d7994a173245 --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_i2c.c @@ -0,0 +1,171 @@ +/* + * linux/drivers/video/nvidia/nvidia-i2c.c - nVidia i2c + * + * Copyright 2004 Antonino A. Daplas <adaplas @pol.net> + * + * Based on rivafb-i2c.c + * + * 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/delay.h> +#include <linux/gfp.h> +#include <linux/pci.h> +#include <linux/fb.h> + +#include <asm/io.h> + +#include "nv_type.h" +#include "nv_local.h" +#include "nv_proto.h" + +#include "../edid.h" + +static void nvidia_gpio_setscl(void *data, int state) +{ + struct nvidia_i2c_chan *chan = data; + struct nvidia_par *par = chan->par; + u32 val; + + val = NVReadCrtc(par, chan->ddc_base + 1) & 0xf0; + + if (state) + val |= 0x20; + else + val &= ~0x20; + + NVWriteCrtc(par, chan->ddc_base + 1, val | 0x01); +} + +static void nvidia_gpio_setsda(void *data, int state) +{ + struct nvidia_i2c_chan *chan = data; + struct nvidia_par *par = chan->par; + u32 val; + + val = NVReadCrtc(par, chan->ddc_base + 1) & 0xf0; + + if (state) + val |= 0x10; + else + val &= ~0x10; + + NVWriteCrtc(par, chan->ddc_base + 1, val | 0x01); +} + +static int nvidia_gpio_getscl(void *data) +{ + struct nvidia_i2c_chan *chan = data; + struct nvidia_par *par = chan->par; + u32 val = 0; + + if (NVReadCrtc(par, chan->ddc_base) & 0x04) + val = 1; + + return val; +} + +static int nvidia_gpio_getsda(void *data) +{ + struct nvidia_i2c_chan *chan = data; + struct nvidia_par *par = chan->par; + u32 val = 0; + + if (NVReadCrtc(par, chan->ddc_base) & 0x08) + val = 1; + + return val; +} + +static int nvidia_setup_i2c_bus(struct nvidia_i2c_chan *chan, const char *name, + unsigned int i2c_class) +{ + int rc; + + strcpy(chan->adapter.name, name); + chan->adapter.owner = THIS_MODULE; + chan->adapter.class = i2c_class; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = &chan->par->pci_dev->dev; + chan->algo.setsda = nvidia_gpio_setsda; + chan->algo.setscl = nvidia_gpio_setscl; + chan->algo.getsda = nvidia_gpio_getsda; + chan->algo.getscl = nvidia_gpio_getscl; + chan->algo.udelay = 40; + chan->algo.timeout = msecs_to_jiffies(2); + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + /* Raise SCL and SDA */ + nvidia_gpio_setsda(chan, 1); + nvidia_gpio_setscl(chan, 1); + udelay(20); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + dev_dbg(&chan->par->pci_dev->dev, + "I2C bus %s registered.\n", name); + else { + dev_warn(&chan->par->pci_dev->dev, + "Failed to register I2C bus %s.\n", name); + chan->par = NULL; + } + + return rc; +} + +void nvidia_create_i2c_busses(struct nvidia_par *par) +{ + par->chan[0].par = par; + par->chan[1].par = par; + par->chan[2].par = par; + + par->chan[0].ddc_base = (par->reverse_i2c) ? 0x36 : 0x3e; + nvidia_setup_i2c_bus(&par->chan[0], "nvidia #0", + (par->reverse_i2c) ? I2C_CLASS_HWMON : 0); + + par->chan[1].ddc_base = (par->reverse_i2c) ? 0x3e : 0x36; + nvidia_setup_i2c_bus(&par->chan[1], "nvidia #1", + (par->reverse_i2c) ? 0 : I2C_CLASS_HWMON); + + par->chan[2].ddc_base = 0x50; + nvidia_setup_i2c_bus(&par->chan[2], "nvidia #2", 0); +} + +void nvidia_delete_i2c_busses(struct nvidia_par *par) +{ + int i; + + for (i = 0; i < 3; i++) { + if (!par->chan[i].par) + continue; + i2c_del_adapter(&par->chan[i].adapter); + par->chan[i].par = NULL; + } +} + +int nvidia_probe_i2c_connector(struct fb_info *info, int conn, u8 **out_edid) +{ + struct nvidia_par *par = info->par; + u8 *edid = NULL; + + if (par->chan[conn - 1].par) + edid = fb_ddc_read(&par->chan[conn - 1].adapter); + + if (!edid && conn == 1) { + /* try to get from firmware */ + const u8 *e = fb_firmware_edid(info->device); + + if (e != NULL) + edid = kmemdup(e, EDID_LENGTH, GFP_KERNEL); + } + + *out_edid = edid; + + return (edid) ? 0 : 1; +} diff --git a/drivers/video/fbdev/nvidia/nv_local.h b/drivers/video/fbdev/nvidia/nv_local.h new file mode 100644 index 000000000000..68e508daa417 --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_local.h @@ -0,0 +1,114 @@ +/***************************************************************************\ +|* *| +|* Copyright 1993-2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 1993-1999 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| + \***************************************************************************/ + +/* + * GPL Licensing Note - According to Mark Vojkovich, author of the Xorg/ + * XFree86 'nv' driver, this source code is provided under MIT-style licensing + * where the source code is provided "as is" without warranty of any kind. + * The only usage restriction is for the copyright notices to be retained + * whenever code is used. + * + * Antonino Daplas <adaplas@pol.net> 2005-03-11 + */ + +#ifndef __NV_LOCAL_H__ +#define __NV_LOCAL_H__ + +/* + * This file includes any environment or machine specific values to access the + * HW. Put all affected includes, typdefs, etc. here so the riva_hw.* files + * can stay generic in nature. + */ + +/* + * HW access macros. These assume memory-mapped I/O, and not normal I/O space. + */ +#define NV_WR08(p,i,d) (__raw_writeb((d), (void __iomem *)(p) + (i))) +#define NV_RD08(p,i) (__raw_readb((void __iomem *)(p) + (i))) +#define NV_WR16(p,i,d) (__raw_writew((d), (void __iomem *)(p) + (i))) +#define NV_RD16(p,i) (__raw_readw((void __iomem *)(p) + (i))) +#define NV_WR32(p,i,d) (__raw_writel((d), (void __iomem *)(p) + (i))) +#define NV_RD32(p,i) (__raw_readl((void __iomem *)(p) + (i))) + +/* VGA I/O is now always done through MMIO */ +#define VGA_WR08(p,i,d) (writeb((d), (void __iomem *)(p) + (i))) +#define VGA_RD08(p,i) (readb((void __iomem *)(p) + (i))) + +#define NVDmaNext(par, data) \ + NV_WR32(&(par)->dmaBase[(par)->dmaCurrent++], 0, (data)) + +#define NVDmaStart(info, par, tag, size) { \ + if((par)->dmaFree <= (size)) \ + NVDmaWait(info, size); \ + NVDmaNext(par, ((size) << 18) | (tag)); \ + (par)->dmaFree -= ((size) + 1); \ +} + +#if defined(__i386__) +#define _NV_FENCE() outb(0, 0x3D0); +#else +#define _NV_FENCE() mb(); +#endif + +#define WRITE_PUT(par, data) { \ + _NV_FENCE() \ + NV_RD08((par)->FbStart, 0); \ + NV_WR32(&(par)->FIFO[0x0010], 0, (data) << 2); \ + mb(); \ +} + +#define READ_GET(par) (NV_RD32(&(par)->FIFO[0x0011], 0) >> 2) + +#ifdef __LITTLE_ENDIAN + +#include <linux/bitrev.h> + +#define reverse_order(l) \ +do { \ + u8 *a = (u8 *)(l); \ + a[0] = bitrev8(a[0]); \ + a[1] = bitrev8(a[1]); \ + a[2] = bitrev8(a[2]); \ + a[3] = bitrev8(a[3]); \ +} while(0) +#else +#define reverse_order(l) do { } while(0) +#endif /* __LITTLE_ENDIAN */ + +#endif /* __NV_LOCAL_H__ */ diff --git a/drivers/video/fbdev/nvidia/nv_of.c b/drivers/video/fbdev/nvidia/nv_of.c new file mode 100644 index 000000000000..3bc13df4b120 --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_of.c @@ -0,0 +1,82 @@ +/* + * linux/drivers/video/nvidia/nv_of.c + * + * Copyright 2004 Antonino A. Daplas <adaplas @pol.net> + * + * Based on rivafb-i2c.c + * + * 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/delay.h> +#include <linux/gfp.h> +#include <linux/pci.h> +#include <linux/fb.h> + +#include <asm/io.h> + +#include <asm/prom.h> +#include <asm/pci-bridge.h> + +#include "nv_type.h" +#include "nv_local.h" +#include "nv_proto.h" + +#include "../edid.h" + +int nvidia_probe_of_connector(struct fb_info *info, int conn, u8 **out_edid) +{ + struct nvidia_par *par = info->par; + struct device_node *parent, *dp; + const unsigned char *pedid = NULL; + static char *propnames[] = { + "DFP,EDID", "LCD,EDID", "EDID", "EDID1", + "EDID,B", "EDID,A", NULL }; + int i; + + parent = pci_device_to_OF_node(par->pci_dev); + if (parent == NULL) + return -1; + if (par->twoHeads) { + const char *pname; + int len; + + for (dp = NULL; + (dp = of_get_next_child(parent, dp)) != NULL;) { + pname = of_get_property(dp, "name", NULL); + if (!pname) + continue; + len = strlen(pname); + if ((pname[len-1] == 'A' && conn == 1) || + (pname[len-1] == 'B' && conn == 2)) { + for (i = 0; propnames[i] != NULL; ++i) { + pedid = of_get_property(dp, + propnames[i], NULL); + if (pedid != NULL) + break; + } + of_node_put(dp); + break; + } + } + } + if (pedid == NULL) { + for (i = 0; propnames[i] != NULL; ++i) { + pedid = of_get_property(parent, propnames[i], NULL); + if (pedid != NULL) + break; + } + } + if (pedid) { + *out_edid = kmemdup(pedid, EDID_LENGTH, GFP_KERNEL); + if (*out_edid == NULL) + return -1; + printk(KERN_DEBUG "nvidiafb: Found OF EDID for head %d\n", conn); + return 0; + } + return -1; +} diff --git a/drivers/video/fbdev/nvidia/nv_proto.h b/drivers/video/fbdev/nvidia/nv_proto.h new file mode 100644 index 000000000000..ff5c410355ea --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_proto.h @@ -0,0 +1,75 @@ +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/nv_proto.h,v 1.10 2003/07/31 20:24:29 mvojkovi Exp $ */ + +#ifndef __NV_PROTO_H__ +#define __NV_PROTO_H__ + +/* in nv_setup.c */ +int NVCommonSetup(struct fb_info *info); +void NVWriteCrtc(struct nvidia_par *par, u8 index, u8 value); +u8 NVReadCrtc(struct nvidia_par *par, u8 index); +void NVWriteGr(struct nvidia_par *par, u8 index, u8 value); +u8 NVReadGr(struct nvidia_par *par, u8 index); +void NVWriteSeq(struct nvidia_par *par, u8 index, u8 value); +u8 NVReadSeq(struct nvidia_par *par, u8 index); +void NVWriteAttr(struct nvidia_par *par, u8 index, u8 value); +u8 NVReadAttr(struct nvidia_par *par, u8 index); +void NVWriteMiscOut(struct nvidia_par *par, u8 value); +u8 NVReadMiscOut(struct nvidia_par *par); +void NVWriteDacMask(struct nvidia_par *par, u8 value); +void NVWriteDacReadAddr(struct nvidia_par *par, u8 value); +void NVWriteDacWriteAddr(struct nvidia_par *par, u8 value); +void NVWriteDacData(struct nvidia_par *par, u8 value); +u8 NVReadDacData(struct nvidia_par *par); + +/* in nv_hw.c */ +void NVCalcStateExt(struct nvidia_par *par, struct _riva_hw_state *, + int, int, int, int, int, int); +void NVLoadStateExt(struct nvidia_par *par, struct _riva_hw_state *); +void NVUnloadStateExt(struct nvidia_par *par, struct _riva_hw_state *); +void NVSetStartAddress(struct nvidia_par *par, u32); +int NVShowHideCursor(struct nvidia_par *par, int); +void NVLockUnlock(struct nvidia_par *par, int); + +/* in nvidia-i2c.c */ +#ifdef CONFIG_FB_NVIDIA_I2C +void nvidia_create_i2c_busses(struct nvidia_par *par); +void nvidia_delete_i2c_busses(struct nvidia_par *par); +int nvidia_probe_i2c_connector(struct fb_info *info, int conn, + u8 ** out_edid); +#else +#define nvidia_create_i2c_busses(...) +#define nvidia_delete_i2c_busses(...) +#define nvidia_probe_i2c_connector(p, c, edid) (-1) +#endif + +#ifdef CONFIG_PPC_OF +int nvidia_probe_of_connector(struct fb_info *info, int conn, + u8 ** out_edid); +#else +static inline int nvidia_probe_of_connector(struct fb_info *info, int conn, + u8 ** out_edid) +{ + return -1; +} +#endif + +/* in nv_accel.c */ +extern void NVResetGraphics(struct fb_info *info); +extern void nvidiafb_copyarea(struct fb_info *info, + const struct fb_copyarea *region); +extern void nvidiafb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect); +extern void nvidiafb_imageblit(struct fb_info *info, + const struct fb_image *image); +extern int nvidiafb_sync(struct fb_info *info); + +/* in nv_backlight.h */ +#ifdef CONFIG_FB_NVIDIA_BACKLIGHT +extern void nvidia_bl_init(struct nvidia_par *par); +extern void nvidia_bl_exit(struct nvidia_par *par); +#else +static inline void nvidia_bl_init(struct nvidia_par *par) {} +static inline void nvidia_bl_exit(struct nvidia_par *par) {} +#endif + +#endif /* __NV_PROTO_H__ */ diff --git a/drivers/video/fbdev/nvidia/nv_setup.c b/drivers/video/fbdev/nvidia/nv_setup.c new file mode 100644 index 000000000000..2f2e162134fa --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_setup.c @@ -0,0 +1,675 @@ + /***************************************************************************\ +|* *| +|* Copyright 2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 2003 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| + \***************************************************************************/ + +/* + * GPL Licensing Note - According to Mark Vojkovich, author of the Xorg/ + * XFree86 'nv' driver, this source code is provided under MIT-style licensing + * where the source code is provided "as is" without warranty of any kind. + * The only usage restriction is for the copyright notices to be retained + * whenever code is used. + * + * Antonino Daplas <adaplas@pol.net> 2005-03-11 + */ + +#include <video/vga.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include "nv_type.h" +#include "nv_local.h" +#include "nv_proto.h" +/* + * Override VGA I/O routines. + */ +void NVWriteCrtc(struct nvidia_par *par, u8 index, u8 value) +{ + VGA_WR08(par->PCIO, par->IOBase + 0x04, index); + VGA_WR08(par->PCIO, par->IOBase + 0x05, value); +} +u8 NVReadCrtc(struct nvidia_par *par, u8 index) +{ + VGA_WR08(par->PCIO, par->IOBase + 0x04, index); + return (VGA_RD08(par->PCIO, par->IOBase + 0x05)); +} +void NVWriteGr(struct nvidia_par *par, u8 index, u8 value) +{ + VGA_WR08(par->PVIO, VGA_GFX_I, index); + VGA_WR08(par->PVIO, VGA_GFX_D, value); +} +u8 NVReadGr(struct nvidia_par *par, u8 index) +{ + VGA_WR08(par->PVIO, VGA_GFX_I, index); + return (VGA_RD08(par->PVIO, VGA_GFX_D)); +} +void NVWriteSeq(struct nvidia_par *par, u8 index, u8 value) +{ + VGA_WR08(par->PVIO, VGA_SEQ_I, index); + VGA_WR08(par->PVIO, VGA_SEQ_D, value); +} +u8 NVReadSeq(struct nvidia_par *par, u8 index) +{ + VGA_WR08(par->PVIO, VGA_SEQ_I, index); + return (VGA_RD08(par->PVIO, VGA_SEQ_D)); +} +void NVWriteAttr(struct nvidia_par *par, u8 index, u8 value) +{ + volatile u8 tmp; + + tmp = VGA_RD08(par->PCIO, par->IOBase + 0x0a); + if (par->paletteEnabled) + index &= ~0x20; + else + index |= 0x20; + VGA_WR08(par->PCIO, VGA_ATT_IW, index); + VGA_WR08(par->PCIO, VGA_ATT_W, value); +} +u8 NVReadAttr(struct nvidia_par *par, u8 index) +{ + volatile u8 tmp; + + tmp = VGA_RD08(par->PCIO, par->IOBase + 0x0a); + if (par->paletteEnabled) + index &= ~0x20; + else + index |= 0x20; + VGA_WR08(par->PCIO, VGA_ATT_IW, index); + return (VGA_RD08(par->PCIO, VGA_ATT_R)); +} +void NVWriteMiscOut(struct nvidia_par *par, u8 value) +{ + VGA_WR08(par->PVIO, VGA_MIS_W, value); +} +u8 NVReadMiscOut(struct nvidia_par *par) +{ + return (VGA_RD08(par->PVIO, VGA_MIS_R)); +} +#if 0 +void NVEnablePalette(struct nvidia_par *par) +{ + volatile u8 tmp; + + tmp = VGA_RD08(par->PCIO, par->IOBase + 0x0a); + VGA_WR08(par->PCIO, VGA_ATT_IW, 0x00); + par->paletteEnabled = 1; +} +void NVDisablePalette(struct nvidia_par *par) +{ + volatile u8 tmp; + + tmp = VGA_RD08(par->PCIO, par->IOBase + 0x0a); + VGA_WR08(par->PCIO, VGA_ATT_IW, 0x20); + par->paletteEnabled = 0; +} +#endif /* 0 */ +void NVWriteDacMask(struct nvidia_par *par, u8 value) +{ + VGA_WR08(par->PDIO, VGA_PEL_MSK, value); +} +#if 0 +u8 NVReadDacMask(struct nvidia_par *par) +{ + return (VGA_RD08(par->PDIO, VGA_PEL_MSK)); +} +#endif /* 0 */ +void NVWriteDacReadAddr(struct nvidia_par *par, u8 value) +{ + VGA_WR08(par->PDIO, VGA_PEL_IR, value); +} +void NVWriteDacWriteAddr(struct nvidia_par *par, u8 value) +{ + VGA_WR08(par->PDIO, VGA_PEL_IW, value); +} +void NVWriteDacData(struct nvidia_par *par, u8 value) +{ + VGA_WR08(par->PDIO, VGA_PEL_D, value); +} +u8 NVReadDacData(struct nvidia_par *par) +{ + return (VGA_RD08(par->PDIO, VGA_PEL_D)); +} + +static int NVIsConnected(struct nvidia_par *par, int output) +{ + volatile u32 __iomem *PRAMDAC = par->PRAMDAC0; + u32 reg52C, reg608, dac0_reg608 = 0; + int present; + + if (output) { + dac0_reg608 = NV_RD32(PRAMDAC, 0x0608); + PRAMDAC += 0x800; + } + + reg52C = NV_RD32(PRAMDAC, 0x052C); + reg608 = NV_RD32(PRAMDAC, 0x0608); + + NV_WR32(PRAMDAC, 0x0608, reg608 & ~0x00010000); + + NV_WR32(PRAMDAC, 0x052C, reg52C & 0x0000FEEE); + msleep(1); + NV_WR32(PRAMDAC, 0x052C, NV_RD32(PRAMDAC, 0x052C) | 1); + + NV_WR32(par->PRAMDAC0, 0x0610, 0x94050140); + NV_WR32(par->PRAMDAC0, 0x0608, NV_RD32(par->PRAMDAC0, 0x0608) | + 0x00001000); + + msleep(1); + + present = (NV_RD32(PRAMDAC, 0x0608) & (1 << 28)) ? 1 : 0; + + if (present) + printk("nvidiafb: CRTC%i analog found\n", output); + else + printk("nvidiafb: CRTC%i analog not found\n", output); + + if (output) + NV_WR32(par->PRAMDAC0, 0x0608, dac0_reg608); + + NV_WR32(PRAMDAC, 0x052C, reg52C); + NV_WR32(PRAMDAC, 0x0608, reg608); + + return present; +} + +static void NVSelectHeadRegisters(struct nvidia_par *par, int head) +{ + if (head) { + par->PCIO = par->PCIO0 + 0x2000; + par->PCRTC = par->PCRTC0 + 0x800; + par->PRAMDAC = par->PRAMDAC0 + 0x800; + par->PDIO = par->PDIO0 + 0x2000; + } else { + par->PCIO = par->PCIO0; + par->PCRTC = par->PCRTC0; + par->PRAMDAC = par->PRAMDAC0; + par->PDIO = par->PDIO0; + } +} + +static void nv4GetConfig(struct nvidia_par *par) +{ + if (NV_RD32(par->PFB, 0x0000) & 0x00000100) { + par->RamAmountKBytes = + ((NV_RD32(par->PFB, 0x0000) >> 12) & 0x0F) * 1024 * 2 + + 1024 * 2; + } else { + switch (NV_RD32(par->PFB, 0x0000) & 0x00000003) { + case 0: + par->RamAmountKBytes = 1024 * 32; + break; + case 1: + par->RamAmountKBytes = 1024 * 4; + break; + case 2: + par->RamAmountKBytes = 1024 * 8; + break; + case 3: + default: + par->RamAmountKBytes = 1024 * 16; + break; + } + } + par->CrystalFreqKHz = (NV_RD32(par->PEXTDEV, 0x0000) & 0x00000040) ? + 14318 : 13500; + par->CURSOR = &par->PRAMIN[0x1E00]; + par->MinVClockFreqKHz = 12000; + par->MaxVClockFreqKHz = 350000; +} + +static void nv10GetConfig(struct nvidia_par *par) +{ + struct pci_dev *dev; + u32 implementation = par->Chipset & 0x0ff0; + +#ifdef __BIG_ENDIAN + /* turn on big endian register access */ + if (!(NV_RD32(par->PMC, 0x0004) & 0x01000001)) { + NV_WR32(par->PMC, 0x0004, 0x01000001); + mb(); + } +#endif + + dev = pci_get_bus_and_slot(0, 1); + if ((par->Chipset & 0xffff) == 0x01a0) { + u32 amt; + + pci_read_config_dword(dev, 0x7c, &amt); + par->RamAmountKBytes = (((amt >> 6) & 31) + 1) * 1024; + } else if ((par->Chipset & 0xffff) == 0x01f0) { + u32 amt; + + pci_read_config_dword(dev, 0x84, &amt); + par->RamAmountKBytes = (((amt >> 4) & 127) + 1) * 1024; + } else { + par->RamAmountKBytes = + (NV_RD32(par->PFB, 0x020C) & 0xFFF00000) >> 10; + } + pci_dev_put(dev); + + par->CrystalFreqKHz = (NV_RD32(par->PEXTDEV, 0x0000) & (1 << 6)) ? + 14318 : 13500; + + if (par->twoHeads && (implementation != 0x0110)) { + if (NV_RD32(par->PEXTDEV, 0x0000) & (1 << 22)) + par->CrystalFreqKHz = 27000; + } + + par->CURSOR = NULL; /* can't set this here */ + par->MinVClockFreqKHz = 12000; + par->MaxVClockFreqKHz = par->twoStagePLL ? 400000 : 350000; +} + +int NVCommonSetup(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + struct fb_var_screeninfo *var; + u16 implementation = par->Chipset & 0x0ff0; + u8 *edidA = NULL, *edidB = NULL; + struct fb_monspecs *monitorA, *monitorB; + struct fb_monspecs *monA = NULL, *monB = NULL; + int mobile = 0; + int tvA = 0; + int tvB = 0; + int FlatPanel = -1; /* really means the CRTC is slaved */ + int Television = 0; + int err = 0; + + var = kzalloc(sizeof(struct fb_var_screeninfo), GFP_KERNEL); + monitorA = kzalloc(sizeof(struct fb_monspecs), GFP_KERNEL); + monitorB = kzalloc(sizeof(struct fb_monspecs), GFP_KERNEL); + + if (!var || !monitorA || !monitorB) { + err = -ENOMEM; + goto done; + } + + par->PRAMIN = par->REGS + (0x00710000 / 4); + par->PCRTC0 = par->REGS + (0x00600000 / 4); + par->PRAMDAC0 = par->REGS + (0x00680000 / 4); + par->PFB = par->REGS + (0x00100000 / 4); + par->PFIFO = par->REGS + (0x00002000 / 4); + par->PGRAPH = par->REGS + (0x00400000 / 4); + par->PEXTDEV = par->REGS + (0x00101000 / 4); + par->PTIMER = par->REGS + (0x00009000 / 4); + par->PMC = par->REGS + (0x00000000 / 4); + par->FIFO = par->REGS + (0x00800000 / 4); + + /* 8 bit registers */ + par->PCIO0 = (u8 __iomem *) par->REGS + 0x00601000; + par->PDIO0 = (u8 __iomem *) par->REGS + 0x00681000; + par->PVIO = (u8 __iomem *) par->REGS + 0x000C0000; + + par->twoHeads = (par->Architecture >= NV_ARCH_10) && + (implementation != 0x0100) && + (implementation != 0x0150) && + (implementation != 0x01A0) && (implementation != 0x0200); + + par->fpScaler = (par->FpScale && par->twoHeads && + (implementation != 0x0110)); + + par->twoStagePLL = (implementation == 0x0310) || + (implementation == 0x0340) || (par->Architecture >= NV_ARCH_40); + + par->WaitVSyncPossible = (par->Architecture >= NV_ARCH_10) && + (implementation != 0x0100); + + par->BlendingPossible = ((par->Chipset & 0xffff) != 0x0020); + + /* look for known laptop chips */ + switch (par->Chipset & 0xffff) { + case 0x0112: + case 0x0174: + case 0x0175: + case 0x0176: + case 0x0177: + case 0x0179: + case 0x017C: + case 0x017D: + case 0x0186: + case 0x0187: + case 0x018D: + case 0x01D7: + case 0x0228: + case 0x0286: + case 0x028C: + case 0x0316: + case 0x0317: + case 0x031A: + case 0x031B: + case 0x031C: + case 0x031D: + case 0x031E: + case 0x031F: + case 0x0324: + case 0x0325: + case 0x0328: + case 0x0329: + case 0x032C: + case 0x032D: + case 0x0347: + case 0x0348: + case 0x0349: + case 0x034B: + case 0x034C: + case 0x0160: + case 0x0166: + case 0x0169: + case 0x016B: + case 0x016C: + case 0x016D: + case 0x00C8: + case 0x00CC: + case 0x0144: + case 0x0146: + case 0x0147: + case 0x0148: + case 0x0098: + case 0x0099: + mobile = 1; + break; + default: + break; + } + + if (par->Architecture == NV_ARCH_04) + nv4GetConfig(par); + else + nv10GetConfig(par); + + NVSelectHeadRegisters(par, 0); + + NVLockUnlock(par, 0); + + par->IOBase = (NVReadMiscOut(par) & 0x01) ? 0x3d0 : 0x3b0; + + par->Television = 0; + + nvidia_create_i2c_busses(par); + if (!par->twoHeads) { + par->CRTCnumber = 0; + if (nvidia_probe_i2c_connector(info, 1, &edidA)) + nvidia_probe_of_connector(info, 1, &edidA); + if (edidA && !fb_parse_edid(edidA, var)) { + printk("nvidiafb: EDID found from BUS1\n"); + monA = monitorA; + fb_edid_to_monspecs(edidA, monA); + FlatPanel = (monA->input & FB_DISP_DDI) ? 1 : 0; + + /* NV4 doesn't support FlatPanels */ + if ((par->Chipset & 0x0fff) <= 0x0020) + FlatPanel = 0; + } else { + VGA_WR08(par->PCIO, 0x03D4, 0x28); + if (VGA_RD08(par->PCIO, 0x03D5) & 0x80) { + VGA_WR08(par->PCIO, 0x03D4, 0x33); + if (!(VGA_RD08(par->PCIO, 0x03D5) & 0x01)) + Television = 1; + FlatPanel = 1; + } else { + FlatPanel = 0; + } + printk("nvidiafb: HW is currently programmed for %s\n", + FlatPanel ? (Television ? "TV" : "DFP") : + "CRT"); + } + + if (par->FlatPanel == -1) { + par->FlatPanel = FlatPanel; + par->Television = Television; + } else { + printk("nvidiafb: Forcing display type to %s as " + "specified\n", par->FlatPanel ? "DFP" : "CRT"); + } + } else { + u8 outputAfromCRTC, outputBfromCRTC; + int CRTCnumber = -1; + u8 slaved_on_A, slaved_on_B; + int analog_on_A, analog_on_B; + u32 oldhead; + u8 cr44; + + if (implementation != 0x0110) { + if (NV_RD32(par->PRAMDAC0, 0x0000052C) & 0x100) + outputAfromCRTC = 1; + else + outputAfromCRTC = 0; + if (NV_RD32(par->PRAMDAC0, 0x0000252C) & 0x100) + outputBfromCRTC = 1; + else + outputBfromCRTC = 0; + analog_on_A = NVIsConnected(par, 0); + analog_on_B = NVIsConnected(par, 1); + } else { + outputAfromCRTC = 0; + outputBfromCRTC = 1; + analog_on_A = 0; + analog_on_B = 0; + } + + VGA_WR08(par->PCIO, 0x03D4, 0x44); + cr44 = VGA_RD08(par->PCIO, 0x03D5); + + VGA_WR08(par->PCIO, 0x03D5, 3); + NVSelectHeadRegisters(par, 1); + NVLockUnlock(par, 0); + + VGA_WR08(par->PCIO, 0x03D4, 0x28); + slaved_on_B = VGA_RD08(par->PCIO, 0x03D5) & 0x80; + if (slaved_on_B) { + VGA_WR08(par->PCIO, 0x03D4, 0x33); + tvB = !(VGA_RD08(par->PCIO, 0x03D5) & 0x01); + } + + VGA_WR08(par->PCIO, 0x03D4, 0x44); + VGA_WR08(par->PCIO, 0x03D5, 0); + NVSelectHeadRegisters(par, 0); + NVLockUnlock(par, 0); + + VGA_WR08(par->PCIO, 0x03D4, 0x28); + slaved_on_A = VGA_RD08(par->PCIO, 0x03D5) & 0x80; + if (slaved_on_A) { + VGA_WR08(par->PCIO, 0x03D4, 0x33); + tvA = !(VGA_RD08(par->PCIO, 0x03D5) & 0x01); + } + + oldhead = NV_RD32(par->PCRTC0, 0x00000860); + NV_WR32(par->PCRTC0, 0x00000860, oldhead | 0x00000010); + + if (nvidia_probe_i2c_connector(info, 1, &edidA)) + nvidia_probe_of_connector(info, 1, &edidA); + if (edidA && !fb_parse_edid(edidA, var)) { + printk("nvidiafb: EDID found from BUS1\n"); + monA = monitorA; + fb_edid_to_monspecs(edidA, monA); + } + + if (nvidia_probe_i2c_connector(info, 2, &edidB)) + nvidia_probe_of_connector(info, 2, &edidB); + if (edidB && !fb_parse_edid(edidB, var)) { + printk("nvidiafb: EDID found from BUS2\n"); + monB = monitorB; + fb_edid_to_monspecs(edidB, monB); + } + + if (slaved_on_A && !tvA) { + CRTCnumber = 0; + FlatPanel = 1; + printk("nvidiafb: CRTC 0 is currently programmed for " + "DFP\n"); + } else if (slaved_on_B && !tvB) { + CRTCnumber = 1; + FlatPanel = 1; + printk("nvidiafb: CRTC 1 is currently programmed " + "for DFP\n"); + } else if (analog_on_A) { + CRTCnumber = outputAfromCRTC; + FlatPanel = 0; + printk("nvidiafb: CRTC %i appears to have a " + "CRT attached\n", CRTCnumber); + } else if (analog_on_B) { + CRTCnumber = outputBfromCRTC; + FlatPanel = 0; + printk("nvidiafb: CRTC %i appears to have a " + "CRT attached\n", CRTCnumber); + } else if (slaved_on_A) { + CRTCnumber = 0; + FlatPanel = 1; + Television = 1; + printk("nvidiafb: CRTC 0 is currently programmed " + "for TV\n"); + } else if (slaved_on_B) { + CRTCnumber = 1; + FlatPanel = 1; + Television = 1; + printk("nvidiafb: CRTC 1 is currently programmed for " + "TV\n"); + } else if (monA) { + FlatPanel = (monA->input & FB_DISP_DDI) ? 1 : 0; + } else if (monB) { + FlatPanel = (monB->input & FB_DISP_DDI) ? 1 : 0; + } + + if (par->FlatPanel == -1) { + if (FlatPanel != -1) { + par->FlatPanel = FlatPanel; + par->Television = Television; + } else { + printk("nvidiafb: Unable to detect display " + "type...\n"); + if (mobile) { + printk("...On a laptop, assuming " + "DFP\n"); + par->FlatPanel = 1; + } else { + printk("...Using default of CRT\n"); + par->FlatPanel = 0; + } + } + } else { + printk("nvidiafb: Forcing display type to %s as " + "specified\n", par->FlatPanel ? "DFP" : "CRT"); + } + + if (par->CRTCnumber == -1) { + if (CRTCnumber != -1) + par->CRTCnumber = CRTCnumber; + else { + printk("nvidiafb: Unable to detect which " + "CRTCNumber...\n"); + if (par->FlatPanel) + par->CRTCnumber = 1; + else + par->CRTCnumber = 0; + printk("...Defaulting to CRTCNumber %i\n", + par->CRTCnumber); + } + } else { + printk("nvidiafb: Forcing CRTCNumber %i as " + "specified\n", par->CRTCnumber); + } + + if (monA) { + if (((monA->input & FB_DISP_DDI) && + par->FlatPanel) || + ((!(monA->input & FB_DISP_DDI)) && + !par->FlatPanel)) { + if (monB) { + fb_destroy_modedb(monB->modedb); + monB = NULL; + } + } else { + fb_destroy_modedb(monA->modedb); + monA = NULL; + } + } + + if (monB) { + if (((monB->input & FB_DISP_DDI) && + !par->FlatPanel) || + ((!(monB->input & FB_DISP_DDI)) && + par->FlatPanel)) { + fb_destroy_modedb(monB->modedb); + monB = NULL; + } else + monA = monB; + } + + if (implementation == 0x0110) + cr44 = par->CRTCnumber * 0x3; + + NV_WR32(par->PCRTC0, 0x00000860, oldhead); + + VGA_WR08(par->PCIO, 0x03D4, 0x44); + VGA_WR08(par->PCIO, 0x03D5, cr44); + NVSelectHeadRegisters(par, par->CRTCnumber); + } + + printk("nvidiafb: Using %s on CRTC %i\n", + par->FlatPanel ? (par->Television ? "TV" : "DFP") : "CRT", + par->CRTCnumber); + + if (par->FlatPanel && !par->Television) { + par->fpWidth = NV_RD32(par->PRAMDAC, 0x0820) + 1; + par->fpHeight = NV_RD32(par->PRAMDAC, 0x0800) + 1; + par->fpSyncs = NV_RD32(par->PRAMDAC, 0x0848) & 0x30000033; + + printk("nvidiafb: Panel size is %i x %i\n", par->fpWidth, par->fpHeight); + } + + if (monA) + info->monspecs = *monA; + + if (!par->FlatPanel || !par->twoHeads) + par->FPDither = 0; + + par->LVDS = 0; + if (par->FlatPanel && par->twoHeads) { + NV_WR32(par->PRAMDAC0, 0x08B0, 0x00010004); + if (NV_RD32(par->PRAMDAC0, 0x08b4) & 1) + par->LVDS = 1; + printk("nvidiafb: Panel is %s\n", par->LVDS ? "LVDS" : "TMDS"); + } + + kfree(edidA); + kfree(edidB); +done: + kfree(var); + kfree(monitorA); + kfree(monitorB); + return err; +} diff --git a/drivers/video/fbdev/nvidia/nv_type.h b/drivers/video/fbdev/nvidia/nv_type.h new file mode 100644 index 000000000000..c03f7f55c76d --- /dev/null +++ b/drivers/video/fbdev/nvidia/nv_type.h @@ -0,0 +1,180 @@ +#ifndef __NV_TYPE_H__ +#define __NV_TYPE_H__ + +#include <linux/fb.h> +#include <linux/types.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <video/vga.h> + +#define NV_ARCH_04 0x04 +#define NV_ARCH_10 0x10 +#define NV_ARCH_20 0x20 +#define NV_ARCH_30 0x30 +#define NV_ARCH_40 0x40 + +#define BITMASK(t,b) (((unsigned)(1U << (((t)-(b)+1)))-1) << (b)) +#define MASKEXPAND(mask) BITMASK(1?mask,0?mask) +#define SetBF(mask,value) ((value) << (0?mask)) +#define GetBF(var,mask) (((unsigned)((var) & MASKEXPAND(mask))) >> (0?mask) ) +#define SetBitField(value,from,to) SetBF(to, GetBF(value,from)) +#define SetBit(n) (1<<(n)) +#define Set8Bits(value) ((value)&0xff) + +#define V_DBLSCAN 1 + +typedef struct { + int bitsPerPixel; + int depth; + int displayWidth; + int weight; +} NVFBLayout; + +#define NUM_SEQ_REGS 0x05 +#define NUM_CRT_REGS 0x41 +#define NUM_GRC_REGS 0x09 +#define NUM_ATC_REGS 0x15 + +struct nvidia_par; + +struct nvidia_i2c_chan { + struct nvidia_par *par; + unsigned long ddc_base; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo; +}; + +typedef struct _riva_hw_state { + u8 attr[NUM_ATC_REGS]; + u8 crtc[NUM_CRT_REGS]; + u8 gra[NUM_GRC_REGS]; + u8 seq[NUM_SEQ_REGS]; + u8 misc_output; + u32 bpp; + u32 width; + u32 height; + u32 interlace; + u32 repaint0; + u32 repaint1; + u32 screen; + u32 scale; + u32 dither; + u32 extra; + u32 fifo; + u32 pixel; + u32 horiz; + u32 arbitration0; + u32 arbitration1; + u32 pll; + u32 pllB; + u32 vpll; + u32 vpll2; + u32 vpllB; + u32 vpll2B; + u32 pllsel; + u32 general; + u32 crtcOwner; + u32 head; + u32 head2; + u32 config; + u32 cursorConfig; + u32 cursor0; + u32 cursor1; + u32 cursor2; + u32 timingH; + u32 timingV; + u32 displayV; + u32 crtcSync; + u32 control; +} RIVA_HW_STATE; + +struct riva_regs { + RIVA_HW_STATE ext; +}; + +struct nvidia_par { + RIVA_HW_STATE SavedReg; + RIVA_HW_STATE ModeReg; + RIVA_HW_STATE initial_state; + RIVA_HW_STATE *CurrentState; + struct vgastate vgastate; + u32 pseudo_palette[16]; + struct pci_dev *pci_dev; + u32 Architecture; + u32 CursorStart; + int Chipset; + unsigned long FbAddress; + u8 __iomem *FbStart; + u32 FbMapSize; + u32 FbUsableSize; + u32 ScratchBufferSize; + u32 ScratchBufferStart; + int FpScale; + u32 MinVClockFreqKHz; + u32 MaxVClockFreqKHz; + u32 CrystalFreqKHz; + u32 RamAmountKBytes; + u32 IOBase; + NVFBLayout CurrentLayout; + int cursor_reset; + int lockup; + int videoKey; + int FlatPanel; + int FPDither; + int Television; + int CRTCnumber; + int alphaCursor; + int twoHeads; + int twoStagePLL; + int fpScaler; + int fpWidth; + int fpHeight; + int PanelTweak; + int paneltweak; + int LVDS; + int pm_state; + int reverse_i2c; + u32 crtcSync_read; + u32 fpSyncs; + u32 dmaPut; + u32 dmaCurrent; + u32 dmaFree; + u32 dmaMax; + u32 __iomem *dmaBase; + u32 currentRop; + int WaitVSyncPossible; + int BlendingPossible; + u32 paletteEnabled; + u32 forceCRTC; + u32 open_count; + u8 DDCBase; +#ifdef CONFIG_MTRR + struct { + int vram; + int vram_valid; + } mtrr; +#endif + struct nvidia_i2c_chan chan[3]; + + volatile u32 __iomem *REGS; + volatile u32 __iomem *PCRTC0; + volatile u32 __iomem *PCRTC; + volatile u32 __iomem *PRAMDAC0; + volatile u32 __iomem *PFB; + volatile u32 __iomem *PFIFO; + volatile u32 __iomem *PGRAPH; + volatile u32 __iomem *PEXTDEV; + volatile u32 __iomem *PTIMER; + volatile u32 __iomem *PMC; + volatile u32 __iomem *PRAMIN; + volatile u32 __iomem *FIFO; + volatile u32 __iomem *CURSOR; + volatile u8 __iomem *PCIO0; + volatile u8 __iomem *PCIO; + volatile u8 __iomem *PVIO; + volatile u8 __iomem *PDIO0; + volatile u8 __iomem *PDIO; + volatile u32 __iomem *PRAMDAC; +}; + +#endif /* __NV_TYPE_H__ */ diff --git a/drivers/video/fbdev/nvidia/nvidia.c b/drivers/video/fbdev/nvidia/nvidia.c new file mode 100644 index 000000000000..def041204676 --- /dev/null +++ b/drivers/video/fbdev/nvidia/nvidia.c @@ -0,0 +1,1607 @@ +/* + * linux/drivers/video/nvidia/nvidia.c - nVidia fb driver + * + * Copyright 2004 Antonino Daplas <adaplas@pol.net> + * + * 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> +#include <linux/console.h> +#include <linux/backlight.h> +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif +#ifdef CONFIG_PPC_OF +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#endif +#ifdef CONFIG_BOOTX_TEXT +#include <asm/btext.h> +#endif + +#include "nv_local.h" +#include "nv_type.h" +#include "nv_proto.h" +#include "nv_dma.h" + +#ifdef CONFIG_FB_NVIDIA_DEBUG +#define NVTRACE printk +#else +#define NVTRACE if (0) printk +#endif + +#define NVTRACE_ENTER(...) NVTRACE("%s START\n", __func__) +#define NVTRACE_LEAVE(...) NVTRACE("%s END\n", __func__) + +#ifdef CONFIG_FB_NVIDIA_DEBUG +#define assert(expr) \ + if (!(expr)) { \ + printk( "Assertion failed! %s,%s,%s,line=%d\n",\ + #expr,__FILE__,__func__,__LINE__); \ + BUG(); \ + } +#else +#define assert(expr) +#endif + +#define PFX "nvidiafb: " + +/* HW cursor parameters */ +#define MAX_CURS 32 + +static struct pci_device_id nvidiafb_pci_tbl[] = { + {PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, + PCI_BASE_CLASS_DISPLAY << 16, 0xff0000, 0}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, nvidiafb_pci_tbl); + +/* command line data, set in nvidiafb_setup() */ +static int flatpanel = -1; /* Autodetect later */ +static int fpdither = -1; +static int forceCRTC = -1; +static int hwcur = 0; +static int noaccel = 0; +static int noscale = 0; +static int paneltweak = 0; +static int vram = 0; +static int bpp = 8; +static int reverse_i2c; +#ifdef CONFIG_MTRR +static bool nomtrr = false; +#endif +#ifdef CONFIG_PMAC_BACKLIGHT +static int backlight = 1; +#else +static int backlight = 0; +#endif + +static char *mode_option = NULL; + +static struct fb_fix_screeninfo nvidiafb_fix = { + .type = FB_TYPE_PACKED_PIXELS, + .xpanstep = 8, + .ypanstep = 1, +}; + +static struct fb_var_screeninfo nvidiafb_default_var = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 8, + .red = {0, 8, 0}, + .green = {0, 8, 0}, + .blue = {0, 8, 0}, + .transp = {0, 0, 0}, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .pixclock = 39721, + .left_margin = 40, + .right_margin = 24, + .upper_margin = 32, + .lower_margin = 11, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED +}; + +static void nvidiafb_load_cursor_image(struct nvidia_par *par, u8 * data8, + u16 bg, u16 fg, u32 w, u32 h) +{ + u32 *data = (u32 *) data8; + int i, j, k = 0; + u32 b, tmp; + + w = (w + 1) & ~1; + + for (i = 0; i < h; i++) { + b = *data++; + reverse_order(&b); + + for (j = 0; j < w / 2; j++) { + tmp = 0; +#if defined (__BIG_ENDIAN) + tmp = (b & (1 << 31)) ? fg << 16 : bg << 16; + b <<= 1; + tmp |= (b & (1 << 31)) ? fg : bg; + b <<= 1; +#else + tmp = (b & 1) ? fg : bg; + b >>= 1; + tmp |= (b & 1) ? fg << 16 : bg << 16; + b >>= 1; +#endif + NV_WR32(&par->CURSOR[k++], 0, tmp); + } + k += (MAX_CURS - w) / 2; + } +} + +static void nvidia_write_clut(struct nvidia_par *par, + u8 regnum, u8 red, u8 green, u8 blue) +{ + NVWriteDacMask(par, 0xff); + NVWriteDacWriteAddr(par, regnum); + NVWriteDacData(par, red); + NVWriteDacData(par, green); + NVWriteDacData(par, blue); +} + +static void nvidia_read_clut(struct nvidia_par *par, + u8 regnum, u8 * red, u8 * green, u8 * blue) +{ + NVWriteDacMask(par, 0xff); + NVWriteDacReadAddr(par, regnum); + *red = NVReadDacData(par); + *green = NVReadDacData(par); + *blue = NVReadDacData(par); +} + +static int nvidia_panel_tweak(struct nvidia_par *par, + struct _riva_hw_state *state) +{ + int tweak = 0; + + if (par->paneltweak) { + tweak = par->paneltweak; + } else { + /* begin flat panel hacks */ + /* This is unfortunate, but some chips need this register + tweaked or else you get artifacts where adjacent pixels are + swapped. There are no hard rules for what to set here so all + we can do is experiment and apply hacks. */ + + if(((par->Chipset & 0xffff) == 0x0328) && (state->bpp == 32)) { + /* At least one NV34 laptop needs this workaround. */ + tweak = -1; + } + + if((par->Chipset & 0xfff0) == 0x0310) { + tweak = 1; + } + /* end flat panel hacks */ + } + + return tweak; +} + +static void nvidia_screen_off(struct nvidia_par *par, int on) +{ + unsigned char tmp; + + if (on) { + /* + * Turn off screen and disable sequencer. + */ + tmp = NVReadSeq(par, 0x01); + + NVWriteSeq(par, 0x00, 0x01); /* Synchronous Reset */ + NVWriteSeq(par, 0x01, tmp | 0x20); /* disable the display */ + } else { + /* + * Reenable sequencer, then turn on screen. + */ + + tmp = NVReadSeq(par, 0x01); + + NVWriteSeq(par, 0x01, tmp & ~0x20); /* reenable display */ + NVWriteSeq(par, 0x00, 0x03); /* End Reset */ + } +} + +static void nvidia_save_vga(struct nvidia_par *par, + struct _riva_hw_state *state) +{ + int i; + + NVTRACE_ENTER(); + NVLockUnlock(par, 0); + + NVUnloadStateExt(par, state); + + state->misc_output = NVReadMiscOut(par); + + for (i = 0; i < NUM_CRT_REGS; i++) + state->crtc[i] = NVReadCrtc(par, i); + + for (i = 0; i < NUM_ATC_REGS; i++) + state->attr[i] = NVReadAttr(par, i); + + for (i = 0; i < NUM_GRC_REGS; i++) + state->gra[i] = NVReadGr(par, i); + + for (i = 0; i < NUM_SEQ_REGS; i++) + state->seq[i] = NVReadSeq(par, i); + NVTRACE_LEAVE(); +} + +#undef DUMP_REG + +static void nvidia_write_regs(struct nvidia_par *par, + struct _riva_hw_state *state) +{ + int i; + + NVTRACE_ENTER(); + + NVLoadStateExt(par, state); + + NVWriteMiscOut(par, state->misc_output); + + for (i = 1; i < NUM_SEQ_REGS; i++) { +#ifdef DUMP_REG + printk(" SEQ[%02x] = %08x\n", i, state->seq[i]); +#endif + NVWriteSeq(par, i, state->seq[i]); + } + + /* Ensure CRTC registers 0-7 are unlocked by clearing bit 7 of CRTC[17] */ + NVWriteCrtc(par, 0x11, state->crtc[0x11] & ~0x80); + + for (i = 0; i < NUM_CRT_REGS; i++) { + switch (i) { + case 0x19: + case 0x20 ... 0x40: + break; + default: +#ifdef DUMP_REG + printk("CRTC[%02x] = %08x\n", i, state->crtc[i]); +#endif + NVWriteCrtc(par, i, state->crtc[i]); + } + } + + for (i = 0; i < NUM_GRC_REGS; i++) { +#ifdef DUMP_REG + printk(" GRA[%02x] = %08x\n", i, state->gra[i]); +#endif + NVWriteGr(par, i, state->gra[i]); + } + + for (i = 0; i < NUM_ATC_REGS; i++) { +#ifdef DUMP_REG + printk("ATTR[%02x] = %08x\n", i, state->attr[i]); +#endif + NVWriteAttr(par, i, state->attr[i]); + } + + NVTRACE_LEAVE(); +} + +static int nvidia_calc_regs(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + struct _riva_hw_state *state = &par->ModeReg; + int i, depth = fb_get_color_depth(&info->var, &info->fix); + int h_display = info->var.xres / 8 - 1; + int h_start = (info->var.xres + info->var.right_margin) / 8 - 1; + int h_end = (info->var.xres + info->var.right_margin + + info->var.hsync_len) / 8 - 1; + int h_total = (info->var.xres + info->var.right_margin + + info->var.hsync_len + info->var.left_margin) / 8 - 5; + int h_blank_s = h_display; + int h_blank_e = h_total + 4; + int v_display = info->var.yres - 1; + int v_start = info->var.yres + info->var.lower_margin - 1; + int v_end = (info->var.yres + info->var.lower_margin + + info->var.vsync_len) - 1; + int v_total = (info->var.yres + info->var.lower_margin + + info->var.vsync_len + info->var.upper_margin) - 2; + int v_blank_s = v_display; + int v_blank_e = v_total + 1; + + /* + * Set all CRTC values. + */ + + if (info->var.vmode & FB_VMODE_INTERLACED) + v_total |= 1; + + if (par->FlatPanel == 1) { + v_start = v_total - 3; + v_end = v_total - 2; + v_blank_s = v_start; + h_start = h_total - 5; + h_end = h_total - 2; + h_blank_e = h_total + 4; + } + + state->crtc[0x0] = Set8Bits(h_total); + state->crtc[0x1] = Set8Bits(h_display); + state->crtc[0x2] = Set8Bits(h_blank_s); + state->crtc[0x3] = SetBitField(h_blank_e, 4: 0, 4:0) + | SetBit(7); + state->crtc[0x4] = Set8Bits(h_start); + state->crtc[0x5] = SetBitField(h_blank_e, 5: 5, 7:7) + | SetBitField(h_end, 4: 0, 4:0); + state->crtc[0x6] = SetBitField(v_total, 7: 0, 7:0); + state->crtc[0x7] = SetBitField(v_total, 8: 8, 0:0) + | SetBitField(v_display, 8: 8, 1:1) + | SetBitField(v_start, 8: 8, 2:2) + | SetBitField(v_blank_s, 8: 8, 3:3) + | SetBit(4) + | SetBitField(v_total, 9: 9, 5:5) + | SetBitField(v_display, 9: 9, 6:6) + | SetBitField(v_start, 9: 9, 7:7); + state->crtc[0x9] = SetBitField(v_blank_s, 9: 9, 5:5) + | SetBit(6) + | ((info->var.vmode & FB_VMODE_DOUBLE) ? 0x80 : 0x00); + state->crtc[0x10] = Set8Bits(v_start); + state->crtc[0x11] = SetBitField(v_end, 3: 0, 3:0) | SetBit(5); + state->crtc[0x12] = Set8Bits(v_display); + state->crtc[0x13] = ((info->var.xres_virtual / 8) * + (info->var.bits_per_pixel / 8)); + state->crtc[0x15] = Set8Bits(v_blank_s); + state->crtc[0x16] = Set8Bits(v_blank_e); + + state->attr[0x10] = 0x01; + + if (par->Television) + state->attr[0x11] = 0x00; + + state->screen = SetBitField(h_blank_e, 6: 6, 4:4) + | SetBitField(v_blank_s, 10: 10, 3:3) + | SetBitField(v_start, 10: 10, 2:2) + | SetBitField(v_display, 10: 10, 1:1) + | SetBitField(v_total, 10: 10, 0:0); + + state->horiz = SetBitField(h_total, 8: 8, 0:0) + | SetBitField(h_display, 8: 8, 1:1) + | SetBitField(h_blank_s, 8: 8, 2:2) + | SetBitField(h_start, 8: 8, 3:3); + + state->extra = SetBitField(v_total, 11: 11, 0:0) + | SetBitField(v_display, 11: 11, 2:2) + | SetBitField(v_start, 11: 11, 4:4) + | SetBitField(v_blank_s, 11: 11, 6:6); + + if (info->var.vmode & FB_VMODE_INTERLACED) { + h_total = (h_total >> 1) & ~1; + state->interlace = Set8Bits(h_total); + state->horiz |= SetBitField(h_total, 8: 8, 4:4); + } else { + state->interlace = 0xff; /* interlace off */ + } + + /* + * Calculate the extended registers. + */ + + if (depth < 24) + i = depth; + else + i = 32; + + if (par->Architecture >= NV_ARCH_10) + par->CURSOR = (volatile u32 __iomem *)(info->screen_base + + par->CursorStart); + + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) + state->misc_output &= ~0x40; + else + state->misc_output |= 0x40; + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) + state->misc_output &= ~0x80; + else + state->misc_output |= 0x80; + + NVCalcStateExt(par, state, i, info->var.xres_virtual, + info->var.xres, info->var.yres_virtual, + 1000000000 / info->var.pixclock, info->var.vmode); + + state->scale = NV_RD32(par->PRAMDAC, 0x00000848) & 0xfff000ff; + if (par->FlatPanel == 1) { + state->pixel |= (1 << 7); + + if (!par->fpScaler || (par->fpWidth <= info->var.xres) + || (par->fpHeight <= info->var.yres)) { + state->scale |= (1 << 8); + } + + if (!par->crtcSync_read) { + state->crtcSync = NV_RD32(par->PRAMDAC, 0x0828); + par->crtcSync_read = 1; + } + + par->PanelTweak = nvidia_panel_tweak(par, state); + } + + state->vpll = state->pll; + state->vpll2 = state->pll; + state->vpllB = state->pllB; + state->vpll2B = state->pllB; + + VGA_WR08(par->PCIO, 0x03D4, 0x1C); + state->fifo = VGA_RD08(par->PCIO, 0x03D5) & ~(1<<5); + + if (par->CRTCnumber) { + state->head = NV_RD32(par->PCRTC0, 0x00000860) & ~0x00001000; + state->head2 = NV_RD32(par->PCRTC0, 0x00002860) | 0x00001000; + state->crtcOwner = 3; + state->pllsel |= 0x20000800; + state->vpll = NV_RD32(par->PRAMDAC0, 0x00000508); + if (par->twoStagePLL) + state->vpllB = NV_RD32(par->PRAMDAC0, 0x00000578); + } else if (par->twoHeads) { + state->head = NV_RD32(par->PCRTC0, 0x00000860) | 0x00001000; + state->head2 = NV_RD32(par->PCRTC0, 0x00002860) & ~0x00001000; + state->crtcOwner = 0; + state->vpll2 = NV_RD32(par->PRAMDAC0, 0x0520); + if (par->twoStagePLL) + state->vpll2B = NV_RD32(par->PRAMDAC0, 0x057C); + } + + state->cursorConfig = 0x00000100; + + if (info->var.vmode & FB_VMODE_DOUBLE) + state->cursorConfig |= (1 << 4); + + if (par->alphaCursor) { + if ((par->Chipset & 0x0ff0) != 0x0110) + state->cursorConfig |= 0x04011000; + else + state->cursorConfig |= 0x14011000; + state->general |= (1 << 29); + } else + state->cursorConfig |= 0x02000000; + + if (par->twoHeads) { + if ((par->Chipset & 0x0ff0) == 0x0110) { + state->dither = NV_RD32(par->PRAMDAC, 0x0528) & + ~0x00010000; + if (par->FPDither) + state->dither |= 0x00010000; + } else { + state->dither = NV_RD32(par->PRAMDAC, 0x083C) & ~1; + if (par->FPDither) + state->dither |= 1; + } + } + + state->timingH = 0; + state->timingV = 0; + state->displayV = info->var.xres; + + return 0; +} + +static void nvidia_init_vga(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + struct _riva_hw_state *state = &par->ModeReg; + int i; + + for (i = 0; i < 0x10; i++) + state->attr[i] = i; + state->attr[0x10] = 0x41; + state->attr[0x11] = 0xff; + state->attr[0x12] = 0x0f; + state->attr[0x13] = 0x00; + state->attr[0x14] = 0x00; + + memset(state->crtc, 0x00, NUM_CRT_REGS); + state->crtc[0x0a] = 0x20; + state->crtc[0x17] = 0xe3; + state->crtc[0x18] = 0xff; + state->crtc[0x28] = 0x40; + + memset(state->gra, 0x00, NUM_GRC_REGS); + state->gra[0x05] = 0x40; + state->gra[0x06] = 0x05; + state->gra[0x07] = 0x0f; + state->gra[0x08] = 0xff; + + state->seq[0x00] = 0x03; + state->seq[0x01] = 0x01; + state->seq[0x02] = 0x0f; + state->seq[0x03] = 0x00; + state->seq[0x04] = 0x0e; + + state->misc_output = 0xeb; +} + +static int nvidiafb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct nvidia_par *par = info->par; + u8 data[MAX_CURS * MAX_CURS / 8]; + int i, set = cursor->set; + u16 fg, bg; + + if (cursor->image.width > MAX_CURS || cursor->image.height > MAX_CURS) + return -ENXIO; + + NVShowHideCursor(par, 0); + + if (par->cursor_reset) { + set = FB_CUR_SETALL; + par->cursor_reset = 0; + } + + if (set & FB_CUR_SETSIZE) + memset_io(par->CURSOR, 0, MAX_CURS * MAX_CURS * 2); + + if (set & FB_CUR_SETPOS) { + u32 xx, yy, temp; + + yy = cursor->image.dy - info->var.yoffset; + xx = cursor->image.dx - info->var.xoffset; + temp = xx & 0xFFFF; + temp |= yy << 16; + + NV_WR32(par->PRAMDAC, 0x0000300, temp); + } + + if (set & (FB_CUR_SETSHAPE | FB_CUR_SETCMAP | FB_CUR_SETIMAGE)) { + u32 bg_idx = cursor->image.bg_color; + u32 fg_idx = cursor->image.fg_color; + u32 s_pitch = (cursor->image.width + 7) >> 3; + u32 d_pitch = MAX_CURS / 8; + u8 *dat = (u8 *) cursor->image.data; + u8 *msk = (u8 *) cursor->mask; + u8 *src; + + src = kmalloc(s_pitch * cursor->image.height, GFP_ATOMIC); + + if (src) { + switch (cursor->rop) { + case ROP_XOR: + for (i = 0; i < s_pitch * cursor->image.height; i++) + src[i] = dat[i] ^ msk[i]; + break; + case ROP_COPY: + default: + for (i = 0; i < s_pitch * cursor->image.height; i++) + src[i] = dat[i] & msk[i]; + break; + } + + fb_pad_aligned_buffer(data, d_pitch, src, s_pitch, + cursor->image.height); + + bg = ((info->cmap.red[bg_idx] & 0xf8) << 7) | + ((info->cmap.green[bg_idx] & 0xf8) << 2) | + ((info->cmap.blue[bg_idx] & 0xf8) >> 3) | 1 << 15; + + fg = ((info->cmap.red[fg_idx] & 0xf8) << 7) | + ((info->cmap.green[fg_idx] & 0xf8) << 2) | + ((info->cmap.blue[fg_idx] & 0xf8) >> 3) | 1 << 15; + + NVLockUnlock(par, 0); + + nvidiafb_load_cursor_image(par, data, bg, fg, + cursor->image.width, + cursor->image.height); + kfree(src); + } + } + + if (cursor->enable) + NVShowHideCursor(par, 1); + + return 0; +} + +static int nvidiafb_set_par(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + + NVTRACE_ENTER(); + + NVLockUnlock(par, 1); + if (!par->FlatPanel || !par->twoHeads) + par->FPDither = 0; + + if (par->FPDither < 0) { + if ((par->Chipset & 0x0ff0) == 0x0110) + par->FPDither = !!(NV_RD32(par->PRAMDAC, 0x0528) + & 0x00010000); + else + par->FPDither = !!(NV_RD32(par->PRAMDAC, 0x083C) & 1); + printk(KERN_INFO PFX "Flat panel dithering %s\n", + par->FPDither ? "enabled" : "disabled"); + } + + info->fix.visual = (info->var.bits_per_pixel == 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + + nvidia_init_vga(info); + nvidia_calc_regs(info); + + NVLockUnlock(par, 0); + if (par->twoHeads) { + VGA_WR08(par->PCIO, 0x03D4, 0x44); + VGA_WR08(par->PCIO, 0x03D5, par->ModeReg.crtcOwner); + NVLockUnlock(par, 0); + } + + nvidia_screen_off(par, 1); + + nvidia_write_regs(par, &par->ModeReg); + NVSetStartAddress(par, 0); + +#if defined (__BIG_ENDIAN) + /* turn on LFB swapping */ + { + unsigned char tmp; + + VGA_WR08(par->PCIO, 0x3d4, 0x46); + tmp = VGA_RD08(par->PCIO, 0x3d5); + tmp |= (1 << 7); + VGA_WR08(par->PCIO, 0x3d5, tmp); + } +#endif + + info->fix.line_length = (info->var.xres_virtual * + info->var.bits_per_pixel) >> 3; + if (info->var.accel_flags) { + info->fbops->fb_imageblit = nvidiafb_imageblit; + info->fbops->fb_fillrect = nvidiafb_fillrect; + info->fbops->fb_copyarea = nvidiafb_copyarea; + info->fbops->fb_sync = nvidiafb_sync; + info->pixmap.scan_align = 4; + info->flags &= ~FBINFO_HWACCEL_DISABLED; + info->flags |= FBINFO_READS_FAST; + NVResetGraphics(info); + } else { + info->fbops->fb_imageblit = cfb_imageblit; + info->fbops->fb_fillrect = cfb_fillrect; + info->fbops->fb_copyarea = cfb_copyarea; + info->fbops->fb_sync = NULL; + info->pixmap.scan_align = 1; + info->flags |= FBINFO_HWACCEL_DISABLED; + info->flags &= ~FBINFO_READS_FAST; + } + + par->cursor_reset = 1; + + nvidia_screen_off(par, 0); + +#ifdef CONFIG_BOOTX_TEXT + /* Update debug text engine */ + btext_update_display(info->fix.smem_start, + info->var.xres, info->var.yres, + info->var.bits_per_pixel, info->fix.line_length); +#endif + + NVLockUnlock(par, 0); + NVTRACE_LEAVE(); + return 0; +} + +static int nvidiafb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct nvidia_par *par = info->par; + int i; + + NVTRACE_ENTER(); + if (regno >= (1 << info->var.green.length)) + return -EINVAL; + + if (info->var.grayscale) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + if (regno < 16 && info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + ((u32 *) info->pseudo_palette)[regno] = + (regno << info->var.red.offset) | + (regno << info->var.green.offset) | + (regno << info->var.blue.offset); + } + + switch (info->var.bits_per_pixel) { + case 8: + /* "transparent" stuff is completely ignored. */ + nvidia_write_clut(par, regno, red >> 8, green >> 8, blue >> 8); + break; + case 16: + if (info->var.green.length == 5) { + for (i = 0; i < 8; i++) { + nvidia_write_clut(par, regno * 8 + i, red >> 8, + green >> 8, blue >> 8); + } + } else { + u8 r, g, b; + + if (regno < 32) { + for (i = 0; i < 8; i++) { + nvidia_write_clut(par, regno * 8 + i, + red >> 8, green >> 8, + blue >> 8); + } + } + + nvidia_read_clut(par, regno * 4, &r, &g, &b); + + for (i = 0; i < 4; i++) + nvidia_write_clut(par, regno * 4 + i, r, + green >> 8, b); + } + break; + case 32: + nvidia_write_clut(par, regno, red >> 8, green >> 8, blue >> 8); + break; + default: + /* do nothing */ + break; + } + + NVTRACE_LEAVE(); + return 0; +} + +static int nvidiafb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct nvidia_par *par = info->par; + int memlen, vramlen, mode_valid = 0; + int pitch, err = 0; + + NVTRACE_ENTER(); + + var->transp.offset = 0; + var->transp.length = 0; + + var->xres &= ~7; + + if (var->bits_per_pixel <= 8) + var->bits_per_pixel = 8; + else if (var->bits_per_pixel <= 16) + var->bits_per_pixel = 16; + else + var->bits_per_pixel = 32; + + switch (var->bits_per_pixel) { + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: + var->green.length = (var->green.length < 6) ? 5 : 6; + var->red.length = 5; + var->blue.length = 5; + var->transp.length = 6 - var->green.length; + var->blue.offset = 0; + var->green.offset = 5; + var->red.offset = 5 + var->green.length; + var->transp.offset = (5 + var->red.offset) & 15; + break; + case 32: /* RGBA 8888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.length = 8; + var->transp.offset = 24; + break; + } + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + if (!info->monspecs.hfmax || !info->monspecs.vfmax || + !info->monspecs.dclkmax || !fb_validate_mode(var, info)) + mode_valid = 1; + + /* calculate modeline if supported by monitor */ + if (!mode_valid && info->monspecs.gtf) { + if (!fb_get_mode(FB_MAXTIMINGS, 0, var, info)) + mode_valid = 1; + } + + if (!mode_valid) { + const struct fb_videomode *mode; + + mode = fb_find_best_mode(var, &info->modelist); + if (mode) { + fb_videomode_to_var(var, mode); + mode_valid = 1; + } + } + + if (!mode_valid && info->monspecs.modedb_len) + return -EINVAL; + + /* + * If we're on a flat panel, check if the mode is outside of the + * panel dimensions. If so, cap it and try for the next best mode + * before bailing out. + */ + if (par->fpWidth && par->fpHeight && (par->fpWidth < var->xres || + par->fpHeight < var->yres)) { + const struct fb_videomode *mode; + + var->xres = par->fpWidth; + var->yres = par->fpHeight; + + mode = fb_find_best_mode(var, &info->modelist); + if (!mode) { + printk(KERN_ERR PFX "mode out of range of flat " + "panel dimensions\n"); + return -EINVAL; + } + + fb_videomode_to_var(var, mode); + } + + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + var->xres_virtual = (var->xres_virtual + 63) & ~63; + + vramlen = info->screen_size; + pitch = ((var->xres_virtual * var->bits_per_pixel) + 7) / 8; + memlen = pitch * var->yres_virtual; + + if (memlen > vramlen) { + var->yres_virtual = vramlen / pitch; + + if (var->yres_virtual < var->yres) { + var->yres_virtual = var->yres; + var->xres_virtual = vramlen / var->yres_virtual; + var->xres_virtual /= var->bits_per_pixel / 8; + var->xres_virtual &= ~63; + pitch = (var->xres_virtual * + var->bits_per_pixel + 7) / 8; + memlen = pitch * var->yres; + + if (var->xres_virtual < var->xres) { + printk("nvidiafb: required video memory, " + "%d bytes, for %dx%d-%d (virtual) " + "is out of range\n", + memlen, var->xres_virtual, + var->yres_virtual, var->bits_per_pixel); + err = -ENOMEM; + } + } + } + + if (var->accel_flags) { + if (var->yres_virtual > 0x7fff) + var->yres_virtual = 0x7fff; + if (var->xres_virtual > 0x7fff) + var->xres_virtual = 0x7fff; + } + + var->xres_virtual &= ~63; + + NVTRACE_LEAVE(); + + return err; +} + +static int nvidiafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct nvidia_par *par = info->par; + u32 total; + + total = var->yoffset * info->fix.line_length + var->xoffset; + + NVSetStartAddress(par, total); + + return 0; +} + +static int nvidiafb_blank(int blank, struct fb_info *info) +{ + struct nvidia_par *par = info->par; + unsigned char tmp, vesa; + + tmp = NVReadSeq(par, 0x01) & ~0x20; /* screen on/off */ + vesa = NVReadCrtc(par, 0x1a) & ~0xc0; /* sync on/off */ + + NVTRACE_ENTER(); + + if (blank) + tmp |= 0x20; + + switch (blank) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + break; + case FB_BLANK_VSYNC_SUSPEND: + vesa |= 0x80; + break; + case FB_BLANK_HSYNC_SUSPEND: + vesa |= 0x40; + break; + case FB_BLANK_POWERDOWN: + vesa |= 0xc0; + break; + } + + NVWriteSeq(par, 0x01, tmp); + NVWriteCrtc(par, 0x1a, vesa); + + NVTRACE_LEAVE(); + + return 0; +} + +/* + * Because the VGA registers are not mapped linearly in its MMIO space, + * restrict VGA register saving and restore to x86 only, where legacy VGA IO + * access is legal. Consequently, we must also check if the device is the + * primary display. + */ +#ifdef CONFIG_X86 +static void save_vga_x86(struct nvidia_par *par) +{ + struct resource *res= &par->pci_dev->resource[PCI_ROM_RESOURCE]; + + if (res && res->flags & IORESOURCE_ROM_SHADOW) { + memset(&par->vgastate, 0, sizeof(par->vgastate)); + par->vgastate.flags = VGA_SAVE_MODE | VGA_SAVE_FONTS | + VGA_SAVE_CMAP; + save_vga(&par->vgastate); + } +} + +static void restore_vga_x86(struct nvidia_par *par) +{ + struct resource *res= &par->pci_dev->resource[PCI_ROM_RESOURCE]; + + if (res && res->flags & IORESOURCE_ROM_SHADOW) + restore_vga(&par->vgastate); +} +#else +#define save_vga_x86(x) do {} while (0) +#define restore_vga_x86(x) do {} while (0) +#endif /* X86 */ + +static int nvidiafb_open(struct fb_info *info, int user) +{ + struct nvidia_par *par = info->par; + + if (!par->open_count) { + save_vga_x86(par); + nvidia_save_vga(par, &par->initial_state); + } + + par->open_count++; + return 0; +} + +static int nvidiafb_release(struct fb_info *info, int user) +{ + struct nvidia_par *par = info->par; + int err = 0; + + if (!par->open_count) { + err = -EINVAL; + goto done; + } + + if (par->open_count == 1) { + nvidia_write_regs(par, &par->initial_state); + restore_vga_x86(par); + } + + par->open_count--; +done: + return err; +} + +static struct fb_ops nvidia_fb_ops = { + .owner = THIS_MODULE, + .fb_open = nvidiafb_open, + .fb_release = nvidiafb_release, + .fb_check_var = nvidiafb_check_var, + .fb_set_par = nvidiafb_set_par, + .fb_setcolreg = nvidiafb_setcolreg, + .fb_pan_display = nvidiafb_pan_display, + .fb_blank = nvidiafb_blank, + .fb_fillrect = nvidiafb_fillrect, + .fb_copyarea = nvidiafb_copyarea, + .fb_imageblit = nvidiafb_imageblit, + .fb_cursor = nvidiafb_cursor, + .fb_sync = nvidiafb_sync, +}; + +#ifdef CONFIG_PM +static int nvidiafb_suspend(struct pci_dev *dev, pm_message_t mesg) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct nvidia_par *par = info->par; + + if (mesg.event == PM_EVENT_PRETHAW) + mesg.event = PM_EVENT_FREEZE; + console_lock(); + par->pm_state = mesg.event; + + if (mesg.event & PM_EVENT_SLEEP) { + fb_set_suspend(info, 1); + nvidiafb_blank(FB_BLANK_POWERDOWN, info); + nvidia_write_regs(par, &par->SavedReg); + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, mesg)); + } + dev->dev.power.power_state = mesg; + + console_unlock(); + return 0; +} + +static int nvidiafb_resume(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct nvidia_par *par = info->par; + + console_lock(); + pci_set_power_state(dev, PCI_D0); + + if (par->pm_state != PM_EVENT_FREEZE) { + pci_restore_state(dev); + + if (pci_enable_device(dev)) + goto fail; + + pci_set_master(dev); + } + + par->pm_state = PM_EVENT_ON; + nvidiafb_set_par(info); + fb_set_suspend (info, 0); + nvidiafb_blank(FB_BLANK_UNBLANK, info); + +fail: + console_unlock(); + return 0; +} +#else +#define nvidiafb_suspend NULL +#define nvidiafb_resume NULL +#endif + +static int nvidia_set_fbinfo(struct fb_info *info) +{ + struct fb_monspecs *specs = &info->monspecs; + struct fb_videomode modedb; + struct nvidia_par *par = info->par; + int lpitch; + + NVTRACE_ENTER(); + info->flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_IMAGEBLIT + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_YPAN; + + fb_videomode_to_modelist(info->monspecs.modedb, + info->monspecs.modedb_len, &info->modelist); + fb_var_to_videomode(&modedb, &nvidiafb_default_var); + + switch (bpp) { + case 0 ... 8: + bpp = 8; + break; + case 9 ... 16: + bpp = 16; + break; + default: + bpp = 32; + break; + } + + if (specs->modedb != NULL) { + const struct fb_videomode *mode; + + mode = fb_find_best_display(specs, &info->modelist); + fb_videomode_to_var(&nvidiafb_default_var, mode); + nvidiafb_default_var.bits_per_pixel = bpp; + } else if (par->fpWidth && par->fpHeight) { + char buf[16]; + + memset(buf, 0, 16); + snprintf(buf, 15, "%dx%dMR", par->fpWidth, par->fpHeight); + fb_find_mode(&nvidiafb_default_var, info, buf, specs->modedb, + specs->modedb_len, &modedb, bpp); + } + + if (mode_option) + fb_find_mode(&nvidiafb_default_var, info, mode_option, + specs->modedb, specs->modedb_len, &modedb, bpp); + + info->var = nvidiafb_default_var; + info->fix.visual = (info->var.bits_per_pixel == 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + info->pseudo_palette = par->pseudo_palette; + fb_alloc_cmap(&info->cmap, 256, 0); + fb_destroy_modedb(info->monspecs.modedb); + info->monspecs.modedb = NULL; + + /* maximize virtual vertical length */ + lpitch = info->var.xres_virtual * + ((info->var.bits_per_pixel + 7) >> 3); + info->var.yres_virtual = info->screen_size / lpitch; + + info->pixmap.scan_align = 4; + info->pixmap.buf_align = 4; + info->pixmap.access_align = 32; + info->pixmap.size = 8 * 1024; + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + if (!hwcur) + info->fbops->fb_cursor = NULL; + + info->var.accel_flags = (!noaccel); + + switch (par->Architecture) { + case NV_ARCH_04: + info->fix.accel = FB_ACCEL_NV4; + break; + case NV_ARCH_10: + info->fix.accel = FB_ACCEL_NV_10; + break; + case NV_ARCH_20: + info->fix.accel = FB_ACCEL_NV_20; + break; + case NV_ARCH_30: + info->fix.accel = FB_ACCEL_NV_30; + break; + case NV_ARCH_40: + info->fix.accel = FB_ACCEL_NV_40; + break; + } + + NVTRACE_LEAVE(); + + return nvidiafb_check_var(&info->var, info); +} + +static u32 nvidia_get_chipset(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + u32 id = (par->pci_dev->vendor << 16) | par->pci_dev->device; + + printk(KERN_INFO PFX "Device ID: %x \n", id); + + if ((id & 0xfff0) == 0x00f0 || + (id & 0xfff0) == 0x02e0) { + /* pci-e */ + id = NV_RD32(par->REGS, 0x1800); + + if ((id & 0x0000ffff) == 0x000010DE) + id = 0x10DE0000 | (id >> 16); + else if ((id & 0xffff0000) == 0xDE100000) /* wrong endian */ + id = 0x10DE0000 | ((id << 8) & 0x0000ff00) | + ((id >> 8) & 0x000000ff); + printk(KERN_INFO PFX "Subsystem ID: %x \n", id); + } + + return id; +} + +static u32 nvidia_get_arch(struct fb_info *info) +{ + struct nvidia_par *par = info->par; + u32 arch = 0; + + switch (par->Chipset & 0x0ff0) { + case 0x0100: /* GeForce 256 */ + case 0x0110: /* GeForce2 MX */ + case 0x0150: /* GeForce2 */ + case 0x0170: /* GeForce4 MX */ + case 0x0180: /* GeForce4 MX (8x AGP) */ + case 0x01A0: /* nForce */ + case 0x01F0: /* nForce2 */ + arch = NV_ARCH_10; + break; + case 0x0200: /* GeForce3 */ + case 0x0250: /* GeForce4 Ti */ + case 0x0280: /* GeForce4 Ti (8x AGP) */ + arch = NV_ARCH_20; + break; + case 0x0300: /* GeForceFX 5800 */ + case 0x0310: /* GeForceFX 5600 */ + case 0x0320: /* GeForceFX 5200 */ + case 0x0330: /* GeForceFX 5900 */ + case 0x0340: /* GeForceFX 5700 */ + arch = NV_ARCH_30; + break; + case 0x0040: /* GeForce 6800 */ + case 0x00C0: /* GeForce 6800 */ + case 0x0120: /* GeForce 6800 */ + case 0x0140: /* GeForce 6600 */ + case 0x0160: /* GeForce 6200 */ + case 0x01D0: /* GeForce 7200, 7300, 7400 */ + case 0x0090: /* GeForce 7800 */ + case 0x0210: /* GeForce 6800 */ + case 0x0220: /* GeForce 6200 */ + case 0x0240: /* GeForce 6100 */ + case 0x0290: /* GeForce 7900 */ + case 0x0390: /* GeForce 7600 */ + case 0x03D0: + arch = NV_ARCH_40; + break; + case 0x0020: /* TNT, TNT2 */ + arch = NV_ARCH_04; + break; + default: /* unknown architecture */ + break; + } + + return arch; +} + +static int nvidiafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) +{ + struct nvidia_par *par; + struct fb_info *info; + unsigned short cmd; + + + NVTRACE_ENTER(); + assert(pd != NULL); + + info = framebuffer_alloc(sizeof(struct nvidia_par), &pd->dev); + + if (!info) + goto err_out; + + par = info->par; + par->pci_dev = pd; + info->pixmap.addr = kzalloc(8 * 1024, GFP_KERNEL); + + if (info->pixmap.addr == NULL) + goto err_out_kfree; + + if (pci_enable_device(pd)) { + printk(KERN_ERR PFX "cannot enable PCI device\n"); + goto err_out_enable; + } + + if (pci_request_regions(pd, "nvidiafb")) { + printk(KERN_ERR PFX "cannot request PCI regions\n"); + goto err_out_enable; + } + + par->FlatPanel = flatpanel; + if (flatpanel == 1) + printk(KERN_INFO PFX "flatpanel support enabled\n"); + par->FPDither = fpdither; + + par->CRTCnumber = forceCRTC; + par->FpScale = (!noscale); + par->paneltweak = paneltweak; + par->reverse_i2c = reverse_i2c; + + /* enable IO and mem if not already done */ + pci_read_config_word(pd, PCI_COMMAND, &cmd); + cmd |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY); + pci_write_config_word(pd, PCI_COMMAND, cmd); + + nvidiafb_fix.mmio_start = pci_resource_start(pd, 0); + nvidiafb_fix.smem_start = pci_resource_start(pd, 1); + nvidiafb_fix.mmio_len = pci_resource_len(pd, 0); + + par->REGS = ioremap(nvidiafb_fix.mmio_start, nvidiafb_fix.mmio_len); + + if (!par->REGS) { + printk(KERN_ERR PFX "cannot ioremap MMIO base\n"); + goto err_out_free_base0; + } + + par->Chipset = nvidia_get_chipset(info); + par->Architecture = nvidia_get_arch(info); + + if (par->Architecture == 0) { + printk(KERN_ERR PFX "unknown NV_ARCH\n"); + goto err_out_arch; + } + + sprintf(nvidiafb_fix.id, "NV%x", (pd->device & 0x0ff0) >> 4); + + if (NVCommonSetup(info)) + goto err_out_arch; + + par->FbAddress = nvidiafb_fix.smem_start; + par->FbMapSize = par->RamAmountKBytes * 1024; + if (vram && vram * 1024 * 1024 < par->FbMapSize) + par->FbMapSize = vram * 1024 * 1024; + + /* Limit amount of vram to 64 MB */ + if (par->FbMapSize > 64 * 1024 * 1024) + par->FbMapSize = 64 * 1024 * 1024; + + if(par->Architecture >= NV_ARCH_40) + par->FbUsableSize = par->FbMapSize - (560 * 1024); + else + par->FbUsableSize = par->FbMapSize - (128 * 1024); + par->ScratchBufferSize = (par->Architecture < NV_ARCH_10) ? 8 * 1024 : + 16 * 1024; + par->ScratchBufferStart = par->FbUsableSize - par->ScratchBufferSize; + par->CursorStart = par->FbUsableSize + (32 * 1024); + + info->screen_base = ioremap(nvidiafb_fix.smem_start, par->FbMapSize); + info->screen_size = par->FbUsableSize; + nvidiafb_fix.smem_len = par->RamAmountKBytes * 1024; + + if (!info->screen_base) { + printk(KERN_ERR PFX "cannot ioremap FB base\n"); + goto err_out_free_base1; + } + + par->FbStart = info->screen_base; + +#ifdef CONFIG_MTRR + if (!nomtrr) { + par->mtrr.vram = mtrr_add(nvidiafb_fix.smem_start, + par->RamAmountKBytes * 1024, + MTRR_TYPE_WRCOMB, 1); + if (par->mtrr.vram < 0) { + printk(KERN_ERR PFX "unable to setup MTRR\n"); + } else { + par->mtrr.vram_valid = 1; + /* let there be speed */ + printk(KERN_INFO PFX "MTRR set to ON\n"); + } + } +#endif /* CONFIG_MTRR */ + + info->fbops = &nvidia_fb_ops; + info->fix = nvidiafb_fix; + + if (nvidia_set_fbinfo(info) < 0) { + printk(KERN_ERR PFX "error setting initial video mode\n"); + goto err_out_iounmap_fb; + } + + nvidia_save_vga(par, &par->SavedReg); + + pci_set_drvdata(pd, info); + + if (backlight) + nvidia_bl_init(par); + + if (register_framebuffer(info) < 0) { + printk(KERN_ERR PFX "error registering nVidia framebuffer\n"); + goto err_out_iounmap_fb; + } + + + printk(KERN_INFO PFX + "PCI nVidia %s framebuffer (%dMB @ 0x%lX)\n", + info->fix.id, + par->FbMapSize / (1024 * 1024), info->fix.smem_start); + + NVTRACE_LEAVE(); + return 0; + +err_out_iounmap_fb: + iounmap(info->screen_base); +err_out_free_base1: + fb_destroy_modedb(info->monspecs.modedb); + nvidia_delete_i2c_busses(par); +err_out_arch: + iounmap(par->REGS); + err_out_free_base0: + pci_release_regions(pd); +err_out_enable: + kfree(info->pixmap.addr); +err_out_kfree: + framebuffer_release(info); +err_out: + return -ENODEV; +} + +static void nvidiafb_remove(struct pci_dev *pd) +{ + struct fb_info *info = pci_get_drvdata(pd); + struct nvidia_par *par = info->par; + + NVTRACE_ENTER(); + + unregister_framebuffer(info); + + nvidia_bl_exit(par); + +#ifdef CONFIG_MTRR + if (par->mtrr.vram_valid) + mtrr_del(par->mtrr.vram, info->fix.smem_start, + info->fix.smem_len); +#endif /* CONFIG_MTRR */ + + iounmap(info->screen_base); + fb_destroy_modedb(info->monspecs.modedb); + nvidia_delete_i2c_busses(par); + iounmap(par->REGS); + pci_release_regions(pd); + kfree(info->pixmap.addr); + framebuffer_release(info); + NVTRACE_LEAVE(); +} + +/* ------------------------------------------------------------------------- * + * + * initialization + * + * ------------------------------------------------------------------------- */ + +#ifndef MODULE +static int nvidiafb_setup(char *options) +{ + char *this_opt; + + NVTRACE_ENTER(); + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "forceCRTC", 9)) { + char *p; + + p = this_opt + 9; + if (!*p || !*(++p)) + continue; + forceCRTC = *p - '0'; + if (forceCRTC < 0 || forceCRTC > 1) + forceCRTC = -1; + } else if (!strncmp(this_opt, "flatpanel", 9)) { + flatpanel = 1; + } else if (!strncmp(this_opt, "hwcur", 5)) { + hwcur = 1; + } else if (!strncmp(this_opt, "noaccel", 6)) { + noaccel = 1; + } else if (!strncmp(this_opt, "noscale", 7)) { + noscale = 1; + } else if (!strncmp(this_opt, "reverse_i2c", 11)) { + reverse_i2c = 1; + } else if (!strncmp(this_opt, "paneltweak:", 11)) { + paneltweak = simple_strtoul(this_opt+11, NULL, 0); + } else if (!strncmp(this_opt, "vram:", 5)) { + vram = simple_strtoul(this_opt+5, NULL, 0); + } else if (!strncmp(this_opt, "backlight:", 10)) { + backlight = simple_strtoul(this_opt+10, NULL, 0); +#ifdef CONFIG_MTRR + } else if (!strncmp(this_opt, "nomtrr", 6)) { + nomtrr = true; +#endif + } else if (!strncmp(this_opt, "fpdither:", 9)) { + fpdither = simple_strtol(this_opt+9, NULL, 0); + } else if (!strncmp(this_opt, "bpp:", 4)) { + bpp = simple_strtoul(this_opt+4, NULL, 0); + } else + mode_option = this_opt; + } + NVTRACE_LEAVE(); + return 0; +} +#endif /* !MODULE */ + +static struct pci_driver nvidiafb_driver = { + .name = "nvidiafb", + .id_table = nvidiafb_pci_tbl, + .probe = nvidiafb_probe, + .suspend = nvidiafb_suspend, + .resume = nvidiafb_resume, + .remove = nvidiafb_remove, +}; + +/* ------------------------------------------------------------------------- * + * + * modularization + * + * ------------------------------------------------------------------------- */ + +static int nvidiafb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("nvidiafb", &option)) + return -ENODEV; + nvidiafb_setup(option); +#endif + return pci_register_driver(&nvidiafb_driver); +} + +module_init(nvidiafb_init); + +static void __exit nvidiafb_exit(void) +{ + pci_unregister_driver(&nvidiafb_driver); +} + +module_exit(nvidiafb_exit); + +module_param(flatpanel, int, 0); +MODULE_PARM_DESC(flatpanel, + "Enables experimental flat panel support for some chipsets. " + "(0=disabled, 1=enabled, -1=autodetect) (default=-1)"); +module_param(fpdither, int, 0); +MODULE_PARM_DESC(fpdither, + "Enables dithering of flat panel for 6 bits panels. " + "(0=disabled, 1=enabled, -1=autodetect) (default=-1)"); +module_param(hwcur, int, 0); +MODULE_PARM_DESC(hwcur, + "Enables hardware cursor implementation. (0 or 1=enabled) " + "(default=0)"); +module_param(noaccel, int, 0); +MODULE_PARM_DESC(noaccel, + "Disables hardware acceleration. (0 or 1=disable) " + "(default=0)"); +module_param(noscale, int, 0); +MODULE_PARM_DESC(noscale, + "Disables screen scaleing. (0 or 1=disable) " + "(default=0, do scaling)"); +module_param(paneltweak, int, 0); +MODULE_PARM_DESC(paneltweak, + "Tweak display settings for flatpanels. " + "(default=0, no tweaks)"); +module_param(forceCRTC, int, 0); +MODULE_PARM_DESC(forceCRTC, + "Forces usage of a particular CRTC in case autodetection " + "fails. (0 or 1) (default=autodetect)"); +module_param(vram, int, 0); +MODULE_PARM_DESC(vram, + "amount of framebuffer memory to remap in MiB" + "(default=0 - remap entire memory)"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Specify initial video mode"); +module_param(bpp, int, 0); +MODULE_PARM_DESC(bpp, "pixel width in bits" + "(default=8)"); +module_param(reverse_i2c, int, 0); +MODULE_PARM_DESC(reverse_i2c, "reverse port assignment of the i2c bus"); +#ifdef CONFIG_MTRR +module_param(nomtrr, bool, false); +MODULE_PARM_DESC(nomtrr, "Disables MTRR support (0 or 1=disabled) " + "(default=0)"); +#endif + +MODULE_AUTHOR("Antonino Daplas"); +MODULE_DESCRIPTION("Framebuffer driver for nVidia graphics chipset"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/ocfb.c b/drivers/video/fbdev/ocfb.c new file mode 100644 index 000000000000..7f9dc9bec309 --- /dev/null +++ b/drivers/video/fbdev/ocfb.c @@ -0,0 +1,440 @@ +/* + * OpenCores VGA/LCD 2.0 core frame buffer driver + * + * Copyright (C) 2013 Stefan Kristiansson, stefan.kristiansson@saunalahti.fi + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/slab.h> + +/* OCFB register defines */ +#define OCFB_CTRL 0x000 +#define OCFB_STAT 0x004 +#define OCFB_HTIM 0x008 +#define OCFB_VTIM 0x00c +#define OCFB_HVLEN 0x010 +#define OCFB_VBARA 0x014 +#define OCFB_PALETTE 0x800 + +#define OCFB_CTRL_VEN 0x00000001 /* Video Enable */ +#define OCFB_CTRL_HIE 0x00000002 /* HSync Interrupt Enable */ +#define OCFB_CTRL_PC 0x00000800 /* 8-bit Pseudo Color Enable*/ +#define OCFB_CTRL_CD8 0x00000000 /* Color Depth 8 */ +#define OCFB_CTRL_CD16 0x00000200 /* Color Depth 16 */ +#define OCFB_CTRL_CD24 0x00000400 /* Color Depth 24 */ +#define OCFB_CTRL_CD32 0x00000600 /* Color Depth 32 */ +#define OCFB_CTRL_VBL1 0x00000000 /* Burst Length 1 */ +#define OCFB_CTRL_VBL2 0x00000080 /* Burst Length 2 */ +#define OCFB_CTRL_VBL4 0x00000100 /* Burst Length 4 */ +#define OCFB_CTRL_VBL8 0x00000180 /* Burst Length 8 */ + +#define PALETTE_SIZE 256 + +#define OCFB_NAME "OC VGA/LCD" + +static char *mode_option; + +static const struct fb_videomode default_mode = { + /* 640x480 @ 60 Hz, 31.5 kHz hsync */ + NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2, + 0, FB_VMODE_NONINTERLACED +}; + +struct ocfb_dev { + struct fb_info info; + void __iomem *regs; + /* flag indicating whether the regs are little endian accessed */ + int little_endian; + /* Physical and virtual addresses of framebuffer */ + phys_addr_t fb_phys; + void __iomem *fb_virt; + u32 pseudo_palette[PALETTE_SIZE]; +}; + +#ifndef MODULE +static int __init ocfb_setup(char *options) +{ + char *curr_opt; + + if (!options || !*options) + return 0; + + while ((curr_opt = strsep(&options, ",")) != NULL) { + if (!*curr_opt) + continue; + mode_option = curr_opt; + } + + return 0; +} +#endif + +static inline u32 ocfb_readreg(struct ocfb_dev *fbdev, loff_t offset) +{ + if (fbdev->little_endian) + return ioread32(fbdev->regs + offset); + else + return ioread32be(fbdev->regs + offset); +} + +static void ocfb_writereg(struct ocfb_dev *fbdev, loff_t offset, u32 data) +{ + if (fbdev->little_endian) + iowrite32(data, fbdev->regs + offset); + else + iowrite32be(data, fbdev->regs + offset); +} + +static int ocfb_setupfb(struct ocfb_dev *fbdev) +{ + unsigned long bpp_config; + struct fb_var_screeninfo *var = &fbdev->info.var; + struct device *dev = fbdev->info.device; + u32 hlen; + u32 vlen; + + /* Disable display */ + ocfb_writereg(fbdev, OCFB_CTRL, 0); + + /* Register framebuffer address */ + fbdev->little_endian = 0; + ocfb_writereg(fbdev, OCFB_VBARA, fbdev->fb_phys); + + /* Detect endianess */ + if (ocfb_readreg(fbdev, OCFB_VBARA) != fbdev->fb_phys) { + fbdev->little_endian = 1; + ocfb_writereg(fbdev, OCFB_VBARA, fbdev->fb_phys); + } + + /* Horizontal timings */ + ocfb_writereg(fbdev, OCFB_HTIM, (var->hsync_len - 1) << 24 | + (var->right_margin - 1) << 16 | (var->xres - 1)); + + /* Vertical timings */ + ocfb_writereg(fbdev, OCFB_VTIM, (var->vsync_len - 1) << 24 | + (var->lower_margin - 1) << 16 | (var->yres - 1)); + + /* Total length of frame */ + hlen = var->left_margin + var->right_margin + var->hsync_len + + var->xres; + + vlen = var->upper_margin + var->lower_margin + var->vsync_len + + var->yres; + + ocfb_writereg(fbdev, OCFB_HVLEN, (hlen - 1) << 16 | (vlen - 1)); + + bpp_config = OCFB_CTRL_CD8; + switch (var->bits_per_pixel) { + case 8: + if (!var->grayscale) + bpp_config |= OCFB_CTRL_PC; /* enable palette */ + break; + + case 16: + bpp_config |= OCFB_CTRL_CD16; + break; + + case 24: + bpp_config |= OCFB_CTRL_CD24; + break; + + case 32: + bpp_config |= OCFB_CTRL_CD32; + break; + + default: + dev_err(dev, "no bpp specified\n"); + break; + } + + /* maximum (8) VBL (video memory burst length) */ + bpp_config |= OCFB_CTRL_VBL8; + + /* Enable output */ + ocfb_writereg(fbdev, OCFB_CTRL, (OCFB_CTRL_VEN | bpp_config)); + + return 0; +} + +static int ocfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct ocfb_dev *fbdev = (struct ocfb_dev *)info->par; + u32 color; + + if (regno >= info->cmap.len) { + dev_err(info->device, "regno >= cmap.len\n"); + return 1; + } + + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + transp >>= (16 - info->var.transp.length); + + if (info->var.bits_per_pixel == 8 && !info->var.grayscale) { + regno <<= 2; + color = (red << 16) | (green << 8) | blue; + ocfb_writereg(fbdev, OCFB_PALETTE + regno, color); + } else { + ((u32 *)(info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + } + + return 0; +} + +static int ocfb_init_fix(struct ocfb_dev *fbdev) +{ + struct fb_var_screeninfo *var = &fbdev->info.var; + struct fb_fix_screeninfo *fix = &fbdev->info.fix; + + strcpy(fix->id, OCFB_NAME); + + fix->line_length = var->xres * var->bits_per_pixel/8; + fix->smem_len = fix->line_length * var->yres; + fix->type = FB_TYPE_PACKED_PIXELS; + + if (var->bits_per_pixel == 8 && !var->grayscale) + fix->visual = FB_VISUAL_PSEUDOCOLOR; + else + fix->visual = FB_VISUAL_TRUECOLOR; + + return 0; +} + +static int ocfb_init_var(struct ocfb_dev *fbdev) +{ + struct fb_var_screeninfo *var = &fbdev->info.var; + + var->accel_flags = FB_ACCEL_NONE; + var->activate = FB_ACTIVATE_NOW; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + switch (var->bits_per_pixel) { + case 8: + var->transp.offset = 0; + var->transp.length = 0; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + case 16: + var->transp.offset = 0; + var->transp.length = 0; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + + case 24: + var->transp.offset = 0; + var->transp.length = 0; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + case 32: + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + } + + return 0; +} + +static struct fb_ops ocfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = ocfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int ocfb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ocfb_dev *fbdev; + struct resource *res; + int fbsize; + + fbdev = devm_kzalloc(&pdev->dev, sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) + return -ENOMEM; + + platform_set_drvdata(pdev, fbdev); + + fbdev->info.fbops = &ocfb_ops; + fbdev->info.device = &pdev->dev; + fbdev->info.par = fbdev; + + /* Video mode setup */ + if (!fb_find_mode(&fbdev->info.var, &fbdev->info, mode_option, + NULL, 0, &default_mode, 16)) { + dev_err(&pdev->dev, "No valid video modes found\n"); + return -EINVAL; + } + ocfb_init_var(fbdev); + ocfb_init_fix(fbdev); + + /* Request I/O resource */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "I/O resource request failed\n"); + return -ENXIO; + } + res->flags &= ~IORESOURCE_CACHEABLE; + fbdev->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fbdev->regs)) + return PTR_ERR(fbdev->regs); + + /* Allocate framebuffer memory */ + fbsize = fbdev->info.fix.smem_len; + fbdev->fb_virt = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbsize), + &fbdev->fb_phys, GFP_KERNEL); + if (!fbdev->fb_virt) { + dev_err(&pdev->dev, + "Frame buffer memory allocation failed\n"); + return -ENOMEM; + } + fbdev->info.fix.smem_start = fbdev->fb_phys; + fbdev->info.screen_base = fbdev->fb_virt; + fbdev->info.pseudo_palette = fbdev->pseudo_palette; + + /* Clear framebuffer */ + memset_io(fbdev->fb_virt, 0, fbsize); + + /* Setup and enable the framebuffer */ + ocfb_setupfb(fbdev); + + if (fbdev->little_endian) + fbdev->info.flags |= FBINFO_FOREIGN_ENDIAN; + + /* Allocate color map */ + ret = fb_alloc_cmap(&fbdev->info.cmap, PALETTE_SIZE, 0); + if (ret) { + dev_err(&pdev->dev, "Color map allocation failed\n"); + goto err_dma_free; + } + + /* Register framebuffer */ + ret = register_framebuffer(&fbdev->info); + if (ret) { + dev_err(&pdev->dev, "Framebuffer registration failed\n"); + goto err_dealloc_cmap; + } + + return 0; + +err_dealloc_cmap: + fb_dealloc_cmap(&fbdev->info.cmap); + +err_dma_free: + dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbsize), fbdev->fb_virt, + fbdev->fb_phys); + + return ret; +} + +static int ocfb_remove(struct platform_device *pdev) +{ + struct ocfb_dev *fbdev = platform_get_drvdata(pdev); + + unregister_framebuffer(&fbdev->info); + fb_dealloc_cmap(&fbdev->info.cmap); + dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbdev->info.fix.smem_len), + fbdev->fb_virt, fbdev->fb_phys); + + /* Disable display */ + ocfb_writereg(fbdev, OCFB_CTRL, 0); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct of_device_id ocfb_match[] = { + { .compatible = "opencores,ocfb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ocfb_match); + +static struct platform_driver ocfb_driver = { + .probe = ocfb_probe, + .remove = ocfb_remove, + .driver = { + .name = "ocfb_fb", + .of_match_table = ocfb_match, + } +}; + +/* + * Init and exit routines + */ +static int __init ocfb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("ocfb", &option)) + return -ENODEV; + ocfb_setup(option); +#endif + return platform_driver_register(&ocfb_driver); +} + +static void __exit ocfb_exit(void) +{ + platform_driver_unregister(&ocfb_driver); +} + +module_init(ocfb_init); +module_exit(ocfb_exit); + +MODULE_AUTHOR("Stefan Kristiansson <stefan.kristiansson@saunalahti.fi>"); +MODULE_DESCRIPTION("OpenCores VGA/LCD 2.0 frame buffer driver"); +MODULE_LICENSE("GPL v2"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Video mode ('<xres>x<yres>[-<bpp>][@refresh]')"); diff --git a/drivers/video/fbdev/offb.c b/drivers/video/fbdev/offb.c new file mode 100644 index 000000000000..7d44d669d5b6 --- /dev/null +++ b/drivers/video/fbdev/offb.c @@ -0,0 +1,687 @@ +/* + * linux/drivers/video/offb.c -- Open Firmware based frame buffer device + * + * Copyright (C) 1997 Geert Uytterhoeven + * + * This driver is partly based on the PowerMac console driver: + * + * Copyright (C) 1996 Paul Mackerras + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <asm/io.h> + +#ifdef CONFIG_PPC64 +#include <asm/pci-bridge.h> +#endif + +#ifdef CONFIG_PPC32 +#include <asm/bootx.h> +#endif + +#include "macmodes.h" + +/* Supported palette hacks */ +enum { + cmap_unknown, + cmap_simple, /* ATI Mach64 */ + cmap_r128, /* ATI Rage128 */ + cmap_M3A, /* ATI Rage Mobility M3 Head A */ + cmap_M3B, /* ATI Rage Mobility M3 Head B */ + cmap_radeon, /* ATI Radeon */ + cmap_gxt2000, /* IBM GXT2000 */ + cmap_avivo, /* ATI R5xx */ + cmap_qemu, /* qemu vga */ +}; + +struct offb_par { + volatile void __iomem *cmap_adr; + volatile void __iomem *cmap_data; + int cmap_type; + int blanked; +}; + +struct offb_par default_par; + +#ifdef CONFIG_PPC32 +extern boot_infos_t *boot_infos; +#endif + +/* Definitions used by the Avivo palette hack */ +#define AVIVO_DC_LUT_RW_SELECT 0x6480 +#define AVIVO_DC_LUT_RW_MODE 0x6484 +#define AVIVO_DC_LUT_RW_INDEX 0x6488 +#define AVIVO_DC_LUT_SEQ_COLOR 0x648c +#define AVIVO_DC_LUT_PWL_DATA 0x6490 +#define AVIVO_DC_LUT_30_COLOR 0x6494 +#define AVIVO_DC_LUT_READ_PIPE_SELECT 0x6498 +#define AVIVO_DC_LUT_WRITE_EN_MASK 0x649c +#define AVIVO_DC_LUT_AUTOFILL 0x64a0 + +#define AVIVO_DC_LUTA_CONTROL 0x64c0 +#define AVIVO_DC_LUTA_BLACK_OFFSET_BLUE 0x64c4 +#define AVIVO_DC_LUTA_BLACK_OFFSET_GREEN 0x64c8 +#define AVIVO_DC_LUTA_BLACK_OFFSET_RED 0x64cc +#define AVIVO_DC_LUTA_WHITE_OFFSET_BLUE 0x64d0 +#define AVIVO_DC_LUTA_WHITE_OFFSET_GREEN 0x64d4 +#define AVIVO_DC_LUTA_WHITE_OFFSET_RED 0x64d8 + +#define AVIVO_DC_LUTB_CONTROL 0x6cc0 +#define AVIVO_DC_LUTB_BLACK_OFFSET_BLUE 0x6cc4 +#define AVIVO_DC_LUTB_BLACK_OFFSET_GREEN 0x6cc8 +#define AVIVO_DC_LUTB_BLACK_OFFSET_RED 0x6ccc +#define AVIVO_DC_LUTB_WHITE_OFFSET_BLUE 0x6cd0 +#define AVIVO_DC_LUTB_WHITE_OFFSET_GREEN 0x6cd4 +#define AVIVO_DC_LUTB_WHITE_OFFSET_RED 0x6cd8 + +#define FB_RIGHT_POS(p, bpp) (fb_be_math(p) ? 0 : (32 - (bpp))) + +static inline u32 offb_cmap_byteswap(struct fb_info *info, u32 value) +{ + u32 bpp = info->var.bits_per_pixel; + + return cpu_to_be32(value) >> FB_RIGHT_POS(info, bpp); +} + + /* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ + +static int offb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct offb_par *par = (struct offb_par *) info->par; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 *pal = info->pseudo_palette; + u32 cr = red >> (16 - info->var.red.length); + u32 cg = green >> (16 - info->var.green.length); + u32 cb = blue >> (16 - info->var.blue.length); + u32 value; + + if (regno >= 16) + return -EINVAL; + + value = (cr << info->var.red.offset) | + (cg << info->var.green.offset) | + (cb << info->var.blue.offset); + if (info->var.transp.length > 0) { + u32 mask = (1 << info->var.transp.length) - 1; + mask <<= info->var.transp.offset; + value |= mask; + } + pal[regno] = offb_cmap_byteswap(info, value); + return 0; + } + + if (regno > 255) + return -EINVAL; + + red >>= 8; + green >>= 8; + blue >>= 8; + + if (!par->cmap_adr) + return 0; + + switch (par->cmap_type) { + case cmap_simple: + writeb(regno, par->cmap_adr); + writeb(red, par->cmap_data); + writeb(green, par->cmap_data); + writeb(blue, par->cmap_data); + break; + case cmap_M3A: + /* Clear PALETTE_ACCESS_CNTL in DAC_CNTL */ + out_le32(par->cmap_adr + 0x58, + in_le32(par->cmap_adr + 0x58) & ~0x20); + case cmap_r128: + /* Set palette index & data */ + out_8(par->cmap_adr + 0xb0, regno); + out_le32(par->cmap_adr + 0xb4, + (red << 16 | green << 8 | blue)); + break; + case cmap_M3B: + /* Set PALETTE_ACCESS_CNTL in DAC_CNTL */ + out_le32(par->cmap_adr + 0x58, + in_le32(par->cmap_adr + 0x58) | 0x20); + /* Set palette index & data */ + out_8(par->cmap_adr + 0xb0, regno); + out_le32(par->cmap_adr + 0xb4, (red << 16 | green << 8 | blue)); + break; + case cmap_radeon: + /* Set palette index & data (could be smarter) */ + out_8(par->cmap_adr + 0xb0, regno); + out_le32(par->cmap_adr + 0xb4, (red << 16 | green << 8 | blue)); + break; + case cmap_gxt2000: + out_le32(((unsigned __iomem *) par->cmap_adr) + regno, + (red << 16 | green << 8 | blue)); + break; + case cmap_avivo: + /* Write to both LUTs for now */ + writel(1, par->cmap_adr + AVIVO_DC_LUT_RW_SELECT); + writeb(regno, par->cmap_adr + AVIVO_DC_LUT_RW_INDEX); + writel(((red) << 22) | ((green) << 12) | ((blue) << 2), + par->cmap_adr + AVIVO_DC_LUT_30_COLOR); + writel(0, par->cmap_adr + AVIVO_DC_LUT_RW_SELECT); + writeb(regno, par->cmap_adr + AVIVO_DC_LUT_RW_INDEX); + writel(((red) << 22) | ((green) << 12) | ((blue) << 2), + par->cmap_adr + AVIVO_DC_LUT_30_COLOR); + break; + } + + return 0; +} + + /* + * Blank the display. + */ + +static int offb_blank(int blank, struct fb_info *info) +{ + struct offb_par *par = (struct offb_par *) info->par; + int i, j; + + if (!par->cmap_adr) + return 0; + + if (!par->blanked) + if (!blank) + return 0; + + par->blanked = blank; + + if (blank) + for (i = 0; i < 256; i++) { + switch (par->cmap_type) { + case cmap_simple: + writeb(i, par->cmap_adr); + for (j = 0; j < 3; j++) + writeb(0, par->cmap_data); + break; + case cmap_M3A: + /* Clear PALETTE_ACCESS_CNTL in DAC_CNTL */ + out_le32(par->cmap_adr + 0x58, + in_le32(par->cmap_adr + 0x58) & ~0x20); + case cmap_r128: + /* Set palette index & data */ + out_8(par->cmap_adr + 0xb0, i); + out_le32(par->cmap_adr + 0xb4, 0); + break; + case cmap_M3B: + /* Set PALETTE_ACCESS_CNTL in DAC_CNTL */ + out_le32(par->cmap_adr + 0x58, + in_le32(par->cmap_adr + 0x58) | 0x20); + /* Set palette index & data */ + out_8(par->cmap_adr + 0xb0, i); + out_le32(par->cmap_adr + 0xb4, 0); + break; + case cmap_radeon: + out_8(par->cmap_adr + 0xb0, i); + out_le32(par->cmap_adr + 0xb4, 0); + break; + case cmap_gxt2000: + out_le32(((unsigned __iomem *) par->cmap_adr) + i, + 0); + break; + case cmap_avivo: + writel(1, par->cmap_adr + AVIVO_DC_LUT_RW_SELECT); + writeb(i, par->cmap_adr + AVIVO_DC_LUT_RW_INDEX); + writel(0, par->cmap_adr + AVIVO_DC_LUT_30_COLOR); + writel(0, par->cmap_adr + AVIVO_DC_LUT_RW_SELECT); + writeb(i, par->cmap_adr + AVIVO_DC_LUT_RW_INDEX); + writel(0, par->cmap_adr + AVIVO_DC_LUT_30_COLOR); + break; + } + } else + fb_set_cmap(&info->cmap, info); + return 0; +} + +static int offb_set_par(struct fb_info *info) +{ + struct offb_par *par = (struct offb_par *) info->par; + + /* On avivo, initialize palette control */ + if (par->cmap_type == cmap_avivo) { + writel(0, par->cmap_adr + AVIVO_DC_LUTA_CONTROL); + writel(0, par->cmap_adr + AVIVO_DC_LUTA_BLACK_OFFSET_BLUE); + writel(0, par->cmap_adr + AVIVO_DC_LUTA_BLACK_OFFSET_GREEN); + writel(0, par->cmap_adr + AVIVO_DC_LUTA_BLACK_OFFSET_RED); + writel(0x0000ffff, par->cmap_adr + AVIVO_DC_LUTA_WHITE_OFFSET_BLUE); + writel(0x0000ffff, par->cmap_adr + AVIVO_DC_LUTA_WHITE_OFFSET_GREEN); + writel(0x0000ffff, par->cmap_adr + AVIVO_DC_LUTA_WHITE_OFFSET_RED); + writel(0, par->cmap_adr + AVIVO_DC_LUTB_CONTROL); + writel(0, par->cmap_adr + AVIVO_DC_LUTB_BLACK_OFFSET_BLUE); + writel(0, par->cmap_adr + AVIVO_DC_LUTB_BLACK_OFFSET_GREEN); + writel(0, par->cmap_adr + AVIVO_DC_LUTB_BLACK_OFFSET_RED); + writel(0x0000ffff, par->cmap_adr + AVIVO_DC_LUTB_WHITE_OFFSET_BLUE); + writel(0x0000ffff, par->cmap_adr + AVIVO_DC_LUTB_WHITE_OFFSET_GREEN); + writel(0x0000ffff, par->cmap_adr + AVIVO_DC_LUTB_WHITE_OFFSET_RED); + writel(1, par->cmap_adr + AVIVO_DC_LUT_RW_SELECT); + writel(0, par->cmap_adr + AVIVO_DC_LUT_RW_MODE); + writel(0x0000003f, par->cmap_adr + AVIVO_DC_LUT_WRITE_EN_MASK); + writel(0, par->cmap_adr + AVIVO_DC_LUT_RW_SELECT); + writel(0, par->cmap_adr + AVIVO_DC_LUT_RW_MODE); + writel(0x0000003f, par->cmap_adr + AVIVO_DC_LUT_WRITE_EN_MASK); + } + return 0; +} + +static void offb_destroy(struct fb_info *info) +{ + if (info->screen_base) + iounmap(info->screen_base); + release_mem_region(info->apertures->ranges[0].base, info->apertures->ranges[0].size); + framebuffer_release(info); +} + +static struct fb_ops offb_ops = { + .owner = THIS_MODULE, + .fb_destroy = offb_destroy, + .fb_setcolreg = offb_setcolreg, + .fb_set_par = offb_set_par, + .fb_blank = offb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static void __iomem *offb_map_reg(struct device_node *np, int index, + unsigned long offset, unsigned long size) +{ + const __be32 *addrp; + u64 asize, taddr; + unsigned int flags; + + addrp = of_get_pci_address(np, index, &asize, &flags); + if (addrp == NULL) + addrp = of_get_address(np, index, &asize, &flags); + if (addrp == NULL) + return NULL; + if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0) + return NULL; + if ((offset + size) > asize) + return NULL; + taddr = of_translate_address(np, addrp); + if (taddr == OF_BAD_ADDR) + return NULL; + return ioremap(taddr + offset, size); +} + +static void offb_init_palette_hacks(struct fb_info *info, struct device_node *dp, + const char *name, unsigned long address) +{ + struct offb_par *par = (struct offb_par *) info->par; + + if (dp && !strncmp(name, "ATY,Rage128", 11)) { + par->cmap_adr = offb_map_reg(dp, 2, 0, 0x1fff); + if (par->cmap_adr) + par->cmap_type = cmap_r128; + } else if (dp && (!strncmp(name, "ATY,RageM3pA", 12) + || !strncmp(name, "ATY,RageM3p12A", 14))) { + par->cmap_adr = offb_map_reg(dp, 2, 0, 0x1fff); + if (par->cmap_adr) + par->cmap_type = cmap_M3A; + } else if (dp && !strncmp(name, "ATY,RageM3pB", 12)) { + par->cmap_adr = offb_map_reg(dp, 2, 0, 0x1fff); + if (par->cmap_adr) + par->cmap_type = cmap_M3B; + } else if (dp && !strncmp(name, "ATY,Rage6", 9)) { + par->cmap_adr = offb_map_reg(dp, 1, 0, 0x1fff); + if (par->cmap_adr) + par->cmap_type = cmap_radeon; + } else if (!strncmp(name, "ATY,", 4)) { + unsigned long base = address & 0xff000000UL; + par->cmap_adr = + ioremap(base + 0x7ff000, 0x1000) + 0xcc0; + par->cmap_data = par->cmap_adr + 1; + par->cmap_type = cmap_simple; + } else if (dp && (of_device_is_compatible(dp, "pci1014,b7") || + of_device_is_compatible(dp, "pci1014,21c"))) { + par->cmap_adr = offb_map_reg(dp, 0, 0x6000, 0x1000); + if (par->cmap_adr) + par->cmap_type = cmap_gxt2000; + } else if (dp && !strncmp(name, "vga,Display-", 12)) { + /* Look for AVIVO initialized by SLOF */ + struct device_node *pciparent = of_get_parent(dp); + const u32 *vid, *did; + vid = of_get_property(pciparent, "vendor-id", NULL); + did = of_get_property(pciparent, "device-id", NULL); + /* This will match most R5xx */ + if (vid && did && *vid == 0x1002 && + ((*did >= 0x7100 && *did < 0x7800) || + (*did >= 0x9400))) { + par->cmap_adr = offb_map_reg(pciparent, 2, 0, 0x10000); + if (par->cmap_adr) + par->cmap_type = cmap_avivo; + } + of_node_put(pciparent); + } else if (dp && of_device_is_compatible(dp, "qemu,std-vga")) { +#ifdef __BIG_ENDIAN + const __be32 io_of_addr[3] = { 0x01000000, 0x0, 0x0 }; +#else + const __be32 io_of_addr[3] = { 0x00000001, 0x0, 0x0 }; +#endif + u64 io_addr = of_translate_address(dp, io_of_addr); + if (io_addr != OF_BAD_ADDR) { + par->cmap_adr = ioremap(io_addr + 0x3c8, 2); + if (par->cmap_adr) { + par->cmap_type = cmap_simple; + par->cmap_data = par->cmap_adr + 1; + } + } + } + info->fix.visual = (par->cmap_type != cmap_unknown) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_STATIC_PSEUDOCOLOR; +} + +static void __init offb_init_fb(const char *name, const char *full_name, + int width, int height, int depth, + int pitch, unsigned long address, + int foreign_endian, struct device_node *dp) +{ + unsigned long res_size = pitch * height; + struct offb_par *par = &default_par; + unsigned long res_start = address; + struct fb_fix_screeninfo *fix; + struct fb_var_screeninfo *var; + struct fb_info *info; + + if (!request_mem_region(res_start, res_size, "offb")) + return; + + printk(KERN_INFO + "Using unsupported %dx%d %s at %lx, depth=%d, pitch=%d\n", + width, height, name, address, depth, pitch); + if (depth != 8 && depth != 15 && depth != 16 && depth != 32) { + printk(KERN_ERR "%s: can't use depth = %d\n", full_name, + depth); + release_mem_region(res_start, res_size); + return; + } + + info = framebuffer_alloc(sizeof(u32) * 16, NULL); + + if (info == 0) { + release_mem_region(res_start, res_size); + return; + } + + fix = &info->fix; + var = &info->var; + info->par = par; + + strcpy(fix->id, "OFfb "); + strncat(fix->id, name, sizeof(fix->id) - sizeof("OFfb ")); + fix->id[sizeof(fix->id) - 1] = '\0'; + + var->xres = var->xres_virtual = width; + var->yres = var->yres_virtual = height; + fix->line_length = pitch; + + fix->smem_start = address; + fix->smem_len = pitch * height; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + + par->cmap_type = cmap_unknown; + if (depth == 8) + offb_init_palette_hacks(info, dp, name, address); + else + fix->visual = FB_VISUAL_TRUECOLOR; + + var->xoffset = var->yoffset = 0; + switch (depth) { + case 8: + var->bits_per_pixel = 8; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 15: /* RGB 555 */ + var->bits_per_pixel = 16; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGB 565 */ + var->bits_per_pixel = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: /* RGB 888 */ + var->bits_per_pixel = 32; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } + var->red.msb_right = var->green.msb_right = var->blue.msb_right = + var->transp.msb_right = 0; + var->grayscale = 0; + var->nonstd = 0; + var->activate = 0; + var->height = var->width = -1; + var->pixclock = 10000; + var->left_margin = var->right_margin = 16; + var->upper_margin = var->lower_margin = 16; + var->hsync_len = var->vsync_len = 8; + var->sync = 0; + var->vmode = FB_VMODE_NONINTERLACED; + + /* set offb aperture size for generic probing */ + info->apertures = alloc_apertures(1); + if (!info->apertures) + goto out_aper; + info->apertures->ranges[0].base = address; + info->apertures->ranges[0].size = fix->smem_len; + + info->fbops = &offb_ops; + info->screen_base = ioremap(address, fix->smem_len); + info->pseudo_palette = (void *) (info + 1); + info->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE | foreign_endian; + + fb_alloc_cmap(&info->cmap, 256, 0); + + if (register_framebuffer(info) < 0) + goto out_err; + + fb_info(info, "Open Firmware frame buffer device on %s\n", full_name); + return; + +out_err: + iounmap(info->screen_base); +out_aper: + iounmap(par->cmap_adr); + par->cmap_adr = NULL; + framebuffer_release(info); + release_mem_region(res_start, res_size); +} + + +static void __init offb_init_nodriver(struct device_node *dp, int no_real_node) +{ + unsigned int len; + int i, width = 640, height = 480, depth = 8, pitch = 640; + unsigned int flags, rsize, addr_prop = 0; + unsigned long max_size = 0; + u64 rstart, address = OF_BAD_ADDR; + const __be32 *pp, *addrp, *up; + u64 asize; + int foreign_endian = 0; + +#ifdef __BIG_ENDIAN + if (of_get_property(dp, "little-endian", NULL)) + foreign_endian = FBINFO_FOREIGN_ENDIAN; +#else + if (of_get_property(dp, "big-endian", NULL)) + foreign_endian = FBINFO_FOREIGN_ENDIAN; +#endif + + pp = of_get_property(dp, "linux,bootx-depth", &len); + if (pp == NULL) + pp = of_get_property(dp, "depth", &len); + if (pp && len == sizeof(u32)) + depth = be32_to_cpup(pp); + + pp = of_get_property(dp, "linux,bootx-width", &len); + if (pp == NULL) + pp = of_get_property(dp, "width", &len); + if (pp && len == sizeof(u32)) + width = be32_to_cpup(pp); + + pp = of_get_property(dp, "linux,bootx-height", &len); + if (pp == NULL) + pp = of_get_property(dp, "height", &len); + if (pp && len == sizeof(u32)) + height = be32_to_cpup(pp); + + pp = of_get_property(dp, "linux,bootx-linebytes", &len); + if (pp == NULL) + pp = of_get_property(dp, "linebytes", &len); + if (pp && len == sizeof(u32) && (*pp != 0xffffffffu)) + pitch = be32_to_cpup(pp); + else + pitch = width * ((depth + 7) / 8); + + rsize = (unsigned long)pitch * (unsigned long)height; + + /* Ok, now we try to figure out the address of the framebuffer. + * + * Unfortunately, Open Firmware doesn't provide a standard way to do + * so. All we can do is a dodgy heuristic that happens to work in + * practice. On most machines, the "address" property contains what + * we need, though not on Matrox cards found in IBM machines. What I've + * found that appears to give good results is to go through the PCI + * ranges and pick one that is both big enough and if possible encloses + * the "address" property. If none match, we pick the biggest + */ + up = of_get_property(dp, "linux,bootx-addr", &len); + if (up == NULL) + up = of_get_property(dp, "address", &len); + if (up && len == sizeof(u32)) + addr_prop = *up; + + /* Hack for when BootX is passing us */ + if (no_real_node) + goto skip_addr; + + for (i = 0; (addrp = of_get_address(dp, i, &asize, &flags)) + != NULL; i++) { + int match_addrp = 0; + + if (!(flags & IORESOURCE_MEM)) + continue; + if (asize < rsize) + continue; + rstart = of_translate_address(dp, addrp); + if (rstart == OF_BAD_ADDR) + continue; + if (addr_prop && (rstart <= addr_prop) && + ((rstart + asize) >= (addr_prop + rsize))) + match_addrp = 1; + if (match_addrp) { + address = addr_prop; + break; + } + if (rsize > max_size) { + max_size = rsize; + address = OF_BAD_ADDR; + } + + if (address == OF_BAD_ADDR) + address = rstart; + } + skip_addr: + if (address == OF_BAD_ADDR && addr_prop) + address = (u64)addr_prop; + if (address != OF_BAD_ADDR) { + /* kludge for valkyrie */ + if (strcmp(dp->name, "valkyrie") == 0) + address += 0x1000; + offb_init_fb(no_real_node ? "bootx" : dp->name, + no_real_node ? "display" : dp->full_name, + width, height, depth, pitch, address, + foreign_endian, no_real_node ? NULL : dp); + } +} + +static int __init offb_init(void) +{ + struct device_node *dp = NULL, *boot_disp = NULL; + + if (fb_get_options("offb", NULL)) + return -ENODEV; + + /* Check if we have a MacOS display without a node spec */ + if (of_get_property(of_chosen, "linux,bootx-noscreen", NULL) != NULL) { + /* The old code tried to work out which node was the MacOS + * display based on the address. I'm dropping that since the + * lack of a node spec only happens with old BootX versions + * (users can update) and with this code, they'll still get + * a display (just not the palette hacks). + */ + offb_init_nodriver(of_chosen, 1); + } + + for (dp = NULL; (dp = of_find_node_by_type(dp, "display"));) { + if (of_get_property(dp, "linux,opened", NULL) && + of_get_property(dp, "linux,boot-display", NULL)) { + boot_disp = dp; + offb_init_nodriver(dp, 0); + } + } + for (dp = NULL; (dp = of_find_node_by_type(dp, "display"));) { + if (of_get_property(dp, "linux,opened", NULL) && + dp != boot_disp) + offb_init_nodriver(dp, 0); + } + + return 0; +} + + +module_init(offb_init); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap/Kconfig b/drivers/video/fbdev/omap/Kconfig new file mode 100644 index 000000000000..0bc3a936ce2b --- /dev/null +++ b/drivers/video/fbdev/omap/Kconfig @@ -0,0 +1,52 @@ +config FB_OMAP + tristate "OMAP frame buffer support" + depends on FB + depends on ARCH_OMAP1 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Frame buffer driver for OMAP based boards. + +config FB_OMAP_LCDC_EXTERNAL + bool "External LCD controller support" + depends on FB_OMAP + help + Say Y here, if you want to have support for boards with an + external LCD controller connected to the SoSSI/RFBI interface. + +config FB_OMAP_LCDC_HWA742 + bool "Epson HWA742 LCD controller support" + depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL + help + Say Y here if you want to have support for the external + Epson HWA742 LCD controller. + +config FB_OMAP_MANUAL_UPDATE + bool "Default to manual update mode" + depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL + help + Say Y here, if your user-space applications are capable of + notifying the frame buffer driver when a change has occurred in + the frame buffer content and thus a reload of the image data to + the external frame buffer is required. If unsure, say N. + +config FB_OMAP_LCD_MIPID + bool "MIPI DBI-C/DCS compatible LCD support" + depends on FB_OMAP && SPI_MASTER + help + Say Y here if you want to have support for LCDs compatible with + the Mobile Industry Processor Interface DBI-C/DCS + specification. (Supported LCDs: Philips LPH8923, Sharp LS041Y3) + +config FB_OMAP_DMA_TUNE + bool "Set DMA SDRAM access priority high" + depends on FB_OMAP + help + On systems in which video memory is in system memory + (SDRAM) this will speed up graphics DMA operations. + If you have such a system and want to use rotation + answer yes. Answer no if you have a dedicated video + memory, or don't use any of the accelerated features. + + diff --git a/drivers/video/fbdev/omap/Makefile b/drivers/video/fbdev/omap/Makefile new file mode 100644 index 000000000000..1927faffb5bc --- /dev/null +++ b/drivers/video/fbdev/omap/Makefile @@ -0,0 +1,26 @@ +# +# Makefile for the OMAP1 framebuffer device driver +# + +obj-$(CONFIG_FB_OMAP) += omapfb.o + +objs-yy := omapfb_main.o lcdc.o + +objs-y$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += sossi.o + +objs-y$(CONFIG_FB_OMAP_LCDC_HWA742) += hwa742.o + +objs-y$(CONFIG_MACH_AMS_DELTA) += lcd_ams_delta.o +objs-y$(CONFIG_MACH_OMAP_H3) += lcd_h3.o +objs-y$(CONFIG_MACH_OMAP_PALMTE) += lcd_palmte.o +objs-y$(CONFIG_MACH_OMAP_PALMTT) += lcd_palmtt.o +objs-y$(CONFIG_MACH_OMAP_PALMZ71) += lcd_palmz71.o +objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1610.o +objs-$(CONFIG_ARCH_OMAP15XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1510.o +objs-y$(CONFIG_MACH_OMAP_OSK) += lcd_osk.o + +objs-y$(CONFIG_FB_OMAP_LCD_MIPID) += lcd_mipid.o +objs-y$(CONFIG_MACH_HERALD) += lcd_htcherald.o + +omapfb-objs := $(objs-yy) + diff --git a/drivers/video/fbdev/omap/hwa742.c b/drivers/video/fbdev/omap/hwa742.c new file mode 100644 index 000000000000..a4ee65b8f918 --- /dev/null +++ b/drivers/video/fbdev/omap/hwa742.c @@ -0,0 +1,1059 @@ +/* + * Epson HWA742 LCD controller driver + * + * Copyright (C) 2004-2005 Nokia Corporation + * Authors: Juha Yrjölä <juha.yrjola@nokia.com> + * Imre Deak <imre.deak@nokia.com> + * YUV support: Jussi Laako <jussi.laako@nokia.com> + * + * 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/mm.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/interrupt.h> + +#include "omapfb.h" + +#define HWA742_REV_CODE_REG 0x0 +#define HWA742_CONFIG_REG 0x2 +#define HWA742_PLL_DIV_REG 0x4 +#define HWA742_PLL_0_REG 0x6 +#define HWA742_PLL_1_REG 0x8 +#define HWA742_PLL_2_REG 0xa +#define HWA742_PLL_3_REG 0xc +#define HWA742_PLL_4_REG 0xe +#define HWA742_CLK_SRC_REG 0x12 +#define HWA742_PANEL_TYPE_REG 0x14 +#define HWA742_H_DISP_REG 0x16 +#define HWA742_H_NDP_REG 0x18 +#define HWA742_V_DISP_1_REG 0x1a +#define HWA742_V_DISP_2_REG 0x1c +#define HWA742_V_NDP_REG 0x1e +#define HWA742_HS_W_REG 0x20 +#define HWA742_HP_S_REG 0x22 +#define HWA742_VS_W_REG 0x24 +#define HWA742_VP_S_REG 0x26 +#define HWA742_PCLK_POL_REG 0x28 +#define HWA742_INPUT_MODE_REG 0x2a +#define HWA742_TRANSL_MODE_REG1 0x2e +#define HWA742_DISP_MODE_REG 0x34 +#define HWA742_WINDOW_TYPE 0x36 +#define HWA742_WINDOW_X_START_0 0x38 +#define HWA742_WINDOW_X_START_1 0x3a +#define HWA742_WINDOW_Y_START_0 0x3c +#define HWA742_WINDOW_Y_START_1 0x3e +#define HWA742_WINDOW_X_END_0 0x40 +#define HWA742_WINDOW_X_END_1 0x42 +#define HWA742_WINDOW_Y_END_0 0x44 +#define HWA742_WINDOW_Y_END_1 0x46 +#define HWA742_MEMORY_WRITE_LSB 0x48 +#define HWA742_MEMORY_WRITE_MSB 0x49 +#define HWA742_MEMORY_READ_0 0x4a +#define HWA742_MEMORY_READ_1 0x4c +#define HWA742_MEMORY_READ_2 0x4e +#define HWA742_POWER_SAVE 0x56 +#define HWA742_NDP_CTRL 0x58 + +#define HWA742_AUTO_UPDATE_TIME (HZ / 20) + +/* Reserve 4 request slots for requests in irq context */ +#define REQ_POOL_SIZE 24 +#define IRQ_REQ_POOL_SIZE 4 + +#define REQ_FROM_IRQ_POOL 0x01 + +#define REQ_COMPLETE 0 +#define REQ_PENDING 1 + +struct update_param { + int x, y, width, height; + int color_mode; + int flags; +}; + +struct hwa742_request { + struct list_head entry; + unsigned int flags; + + int (*handler)(struct hwa742_request *req); + void (*complete)(void *data); + void *complete_data; + + union { + struct update_param update; + struct completion *sync; + } par; +}; + +struct { + enum omapfb_update_mode update_mode; + enum omapfb_update_mode update_mode_before_suspend; + + struct timer_list auto_update_timer; + int stop_auto_update; + struct omapfb_update_window auto_update_window; + unsigned te_connected:1; + unsigned vsync_only:1; + + struct hwa742_request req_pool[REQ_POOL_SIZE]; + struct list_head pending_req_list; + struct list_head free_req_list; + struct semaphore req_sema; + spinlock_t req_lock; + + struct extif_timings reg_timings, lut_timings; + + int prev_color_mode; + int prev_flags; + int window_type; + + u32 max_transmit_size; + u32 extif_clk_period; + unsigned long pix_tx_time; + unsigned long line_upd_time; + + + struct omapfb_device *fbdev; + struct lcd_ctrl_extif *extif; + const struct lcd_ctrl *int_ctrl; + + struct clk *sys_ck; +} hwa742; + +struct lcd_ctrl hwa742_ctrl; + +static u8 hwa742_read_reg(u8 reg) +{ + u8 data; + + hwa742.extif->set_bits_per_cycle(8); + hwa742.extif->write_command(®, 1); + hwa742.extif->read_data(&data, 1); + + return data; +} + +static void hwa742_write_reg(u8 reg, u8 data) +{ + hwa742.extif->set_bits_per_cycle(8); + hwa742.extif->write_command(®, 1); + hwa742.extif->write_data(&data, 1); +} + +static void set_window_regs(int x_start, int y_start, int x_end, int y_end) +{ + u8 tmp[8]; + u8 cmd; + + x_end--; + y_end--; + tmp[0] = x_start; + tmp[1] = x_start >> 8; + tmp[2] = y_start; + tmp[3] = y_start >> 8; + tmp[4] = x_end; + tmp[5] = x_end >> 8; + tmp[6] = y_end; + tmp[7] = y_end >> 8; + + hwa742.extif->set_bits_per_cycle(8); + cmd = HWA742_WINDOW_X_START_0; + + hwa742.extif->write_command(&cmd, 1); + + hwa742.extif->write_data(tmp, 8); +} + +static void set_format_regs(int conv, int transl, int flags) +{ + if (flags & OMAPFB_FORMAT_FLAG_DOUBLE) { + hwa742.window_type = ((hwa742.window_type & 0xfc) | 0x01); +#ifdef VERBOSE + dev_dbg(hwa742.fbdev->dev, "hwa742: enabled pixel doubling\n"); +#endif + } else { + hwa742.window_type = (hwa742.window_type & 0xfc); +#ifdef VERBOSE + dev_dbg(hwa742.fbdev->dev, "hwa742: disabled pixel doubling\n"); +#endif + } + + hwa742_write_reg(HWA742_INPUT_MODE_REG, conv); + hwa742_write_reg(HWA742_TRANSL_MODE_REG1, transl); + hwa742_write_reg(HWA742_WINDOW_TYPE, hwa742.window_type); +} + +static void enable_tearsync(int y, int width, int height, int screen_height, + int force_vsync) +{ + u8 b; + + b = hwa742_read_reg(HWA742_NDP_CTRL); + b |= 1 << 2; + hwa742_write_reg(HWA742_NDP_CTRL, b); + + if (likely(hwa742.vsync_only || force_vsync)) { + hwa742.extif->enable_tearsync(1, 0); + return; + } + + if (width * hwa742.pix_tx_time < hwa742.line_upd_time) { + hwa742.extif->enable_tearsync(1, 0); + return; + } + + if ((width * hwa742.pix_tx_time / 1000) * height < + (y + height) * (hwa742.line_upd_time / 1000)) { + hwa742.extif->enable_tearsync(1, 0); + return; + } + + hwa742.extif->enable_tearsync(1, y + 1); +} + +static void disable_tearsync(void) +{ + u8 b; + + hwa742.extif->enable_tearsync(0, 0); + + b = hwa742_read_reg(HWA742_NDP_CTRL); + b &= ~(1 << 2); + hwa742_write_reg(HWA742_NDP_CTRL, b); +} + +static inline struct hwa742_request *alloc_req(void) +{ + unsigned long flags; + struct hwa742_request *req; + int req_flags = 0; + + if (!in_interrupt()) + down(&hwa742.req_sema); + else + req_flags = REQ_FROM_IRQ_POOL; + + spin_lock_irqsave(&hwa742.req_lock, flags); + BUG_ON(list_empty(&hwa742.free_req_list)); + req = list_entry(hwa742.free_req_list.next, + struct hwa742_request, entry); + list_del(&req->entry); + spin_unlock_irqrestore(&hwa742.req_lock, flags); + + INIT_LIST_HEAD(&req->entry); + req->flags = req_flags; + + return req; +} + +static inline void free_req(struct hwa742_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&hwa742.req_lock, flags); + + list_move(&req->entry, &hwa742.free_req_list); + if (!(req->flags & REQ_FROM_IRQ_POOL)) + up(&hwa742.req_sema); + + spin_unlock_irqrestore(&hwa742.req_lock, flags); +} + +static void process_pending_requests(void) +{ + unsigned long flags; + + spin_lock_irqsave(&hwa742.req_lock, flags); + + while (!list_empty(&hwa742.pending_req_list)) { + struct hwa742_request *req; + void (*complete)(void *); + void *complete_data; + + req = list_entry(hwa742.pending_req_list.next, + struct hwa742_request, entry); + spin_unlock_irqrestore(&hwa742.req_lock, flags); + + if (req->handler(req) == REQ_PENDING) + return; + + complete = req->complete; + complete_data = req->complete_data; + free_req(req); + + if (complete) + complete(complete_data); + + spin_lock_irqsave(&hwa742.req_lock, flags); + } + + spin_unlock_irqrestore(&hwa742.req_lock, flags); +} + +static void submit_req_list(struct list_head *head) +{ + unsigned long flags; + int process = 1; + + spin_lock_irqsave(&hwa742.req_lock, flags); + if (likely(!list_empty(&hwa742.pending_req_list))) + process = 0; + list_splice_init(head, hwa742.pending_req_list.prev); + spin_unlock_irqrestore(&hwa742.req_lock, flags); + + if (process) + process_pending_requests(); +} + +static void request_complete(void *data) +{ + struct hwa742_request *req = (struct hwa742_request *)data; + void (*complete)(void *); + void *complete_data; + + complete = req->complete; + complete_data = req->complete_data; + + free_req(req); + + if (complete) + complete(complete_data); + + process_pending_requests(); +} + +static int send_frame_handler(struct hwa742_request *req) +{ + struct update_param *par = &req->par.update; + int x = par->x; + int y = par->y; + int w = par->width; + int h = par->height; + int bpp; + int conv, transl; + unsigned long offset; + int color_mode = par->color_mode; + int flags = par->flags; + int scr_width = hwa742.fbdev->panel->x_res; + int scr_height = hwa742.fbdev->panel->y_res; + +#ifdef VERBOSE + dev_dbg(hwa742.fbdev->dev, "x %d y %d w %d h %d scr_width %d " + "color_mode %d flags %d\n", + x, y, w, h, scr_width, color_mode, flags); +#endif + + switch (color_mode) { + case OMAPFB_COLOR_YUV422: + bpp = 16; + conv = 0x08; + transl = 0x25; + break; + case OMAPFB_COLOR_YUV420: + bpp = 12; + conv = 0x09; + transl = 0x25; + break; + case OMAPFB_COLOR_RGB565: + bpp = 16; + conv = 0x01; + transl = 0x05; + break; + default: + return -EINVAL; + } + + if (hwa742.prev_flags != flags || + hwa742.prev_color_mode != color_mode) { + set_format_regs(conv, transl, flags); + hwa742.prev_color_mode = color_mode; + hwa742.prev_flags = flags; + } + flags = req->par.update.flags; + if (flags & OMAPFB_FORMAT_FLAG_TEARSYNC) + enable_tearsync(y, scr_width, h, scr_height, + flags & OMAPFB_FORMAT_FLAG_FORCE_VSYNC); + else + disable_tearsync(); + + set_window_regs(x, y, x + w, y + h); + + offset = (scr_width * y + x) * bpp / 8; + + hwa742.int_ctrl->setup_plane(OMAPFB_PLANE_GFX, + OMAPFB_CHANNEL_OUT_LCD, offset, scr_width, 0, 0, w, h, + color_mode); + + hwa742.extif->set_bits_per_cycle(16); + + hwa742.int_ctrl->enable_plane(OMAPFB_PLANE_GFX, 1); + hwa742.extif->transfer_area(w, h, request_complete, req); + + return REQ_PENDING; +} + +static void send_frame_complete(void *data) +{ + hwa742.int_ctrl->enable_plane(OMAPFB_PLANE_GFX, 0); +} + +#define ADD_PREQ(_x, _y, _w, _h) do { \ + req = alloc_req(); \ + req->handler = send_frame_handler; \ + req->complete = send_frame_complete; \ + req->par.update.x = _x; \ + req->par.update.y = _y; \ + req->par.update.width = _w; \ + req->par.update.height = _h; \ + req->par.update.color_mode = color_mode;\ + req->par.update.flags = flags; \ + list_add_tail(&req->entry, req_head); \ +} while(0) + +static void create_req_list(struct omapfb_update_window *win, + struct list_head *req_head) +{ + struct hwa742_request *req; + int x = win->x; + int y = win->y; + int width = win->width; + int height = win->height; + int color_mode; + int flags; + + flags = win->format & ~OMAPFB_FORMAT_MASK; + color_mode = win->format & OMAPFB_FORMAT_MASK; + + if (x & 1) { + ADD_PREQ(x, y, 1, height); + width--; + x++; + flags &= ~OMAPFB_FORMAT_FLAG_TEARSYNC; + } + if (width & ~1) { + unsigned int xspan = width & ~1; + unsigned int ystart = y; + unsigned int yspan = height; + + if (xspan * height * 2 > hwa742.max_transmit_size) { + yspan = hwa742.max_transmit_size / (xspan * 2); + ADD_PREQ(x, ystart, xspan, yspan); + ystart += yspan; + yspan = height - yspan; + flags &= ~OMAPFB_FORMAT_FLAG_TEARSYNC; + } + + ADD_PREQ(x, ystart, xspan, yspan); + x += xspan; + width -= xspan; + flags &= ~OMAPFB_FORMAT_FLAG_TEARSYNC; + } + if (width) + ADD_PREQ(x, y, 1, height); +} + +static void auto_update_complete(void *data) +{ + if (!hwa742.stop_auto_update) + mod_timer(&hwa742.auto_update_timer, + jiffies + HWA742_AUTO_UPDATE_TIME); +} + +static void hwa742_update_window_auto(unsigned long arg) +{ + LIST_HEAD(req_list); + struct hwa742_request *last; + + create_req_list(&hwa742.auto_update_window, &req_list); + last = list_entry(req_list.prev, struct hwa742_request, entry); + + last->complete = auto_update_complete; + last->complete_data = NULL; + + submit_req_list(&req_list); +} + +int hwa742_update_window_async(struct fb_info *fbi, + struct omapfb_update_window *win, + void (*complete_callback)(void *arg), + void *complete_callback_data) +{ + LIST_HEAD(req_list); + struct hwa742_request *last; + int r = 0; + + if (hwa742.update_mode != OMAPFB_MANUAL_UPDATE) { + dev_dbg(hwa742.fbdev->dev, "invalid update mode\n"); + r = -EINVAL; + goto out; + } + if (unlikely(win->format & + ~(0x03 | OMAPFB_FORMAT_FLAG_DOUBLE | + OMAPFB_FORMAT_FLAG_TEARSYNC | OMAPFB_FORMAT_FLAG_FORCE_VSYNC))) { + dev_dbg(hwa742.fbdev->dev, "invalid window flag\n"); + r = -EINVAL; + goto out; + } + + create_req_list(win, &req_list); + last = list_entry(req_list.prev, struct hwa742_request, entry); + + last->complete = complete_callback; + last->complete_data = (void *)complete_callback_data; + + submit_req_list(&req_list); + +out: + return r; +} +EXPORT_SYMBOL(hwa742_update_window_async); + +static int hwa742_setup_plane(int plane, int channel_out, + unsigned long offset, int screen_width, + int pos_x, int pos_y, int width, int height, + int color_mode) +{ + if (plane != OMAPFB_PLANE_GFX || + channel_out != OMAPFB_CHANNEL_OUT_LCD) + return -EINVAL; + + return 0; +} + +static int hwa742_enable_plane(int plane, int enable) +{ + if (plane != 0) + return -EINVAL; + + hwa742.int_ctrl->enable_plane(plane, enable); + + return 0; +} + +static int sync_handler(struct hwa742_request *req) +{ + complete(req->par.sync); + return REQ_COMPLETE; +} + +static void hwa742_sync(void) +{ + LIST_HEAD(req_list); + struct hwa742_request *req; + struct completion comp; + + req = alloc_req(); + + req->handler = sync_handler; + req->complete = NULL; + init_completion(&comp); + req->par.sync = ∁ + + list_add(&req->entry, &req_list); + submit_req_list(&req_list); + + wait_for_completion(&comp); +} + +static void hwa742_bind_client(struct omapfb_notifier_block *nb) +{ + dev_dbg(hwa742.fbdev->dev, "update_mode %d\n", hwa742.update_mode); + if (hwa742.update_mode == OMAPFB_MANUAL_UPDATE) { + omapfb_notify_clients(hwa742.fbdev, OMAPFB_EVENT_READY); + } +} + +static int hwa742_set_update_mode(enum omapfb_update_mode mode) +{ + if (mode != OMAPFB_MANUAL_UPDATE && mode != OMAPFB_AUTO_UPDATE && + mode != OMAPFB_UPDATE_DISABLED) + return -EINVAL; + + if (mode == hwa742.update_mode) + return 0; + + dev_info(hwa742.fbdev->dev, "HWA742: setting update mode to %s\n", + mode == OMAPFB_UPDATE_DISABLED ? "disabled" : + (mode == OMAPFB_AUTO_UPDATE ? "auto" : "manual")); + + switch (hwa742.update_mode) { + case OMAPFB_MANUAL_UPDATE: + omapfb_notify_clients(hwa742.fbdev, OMAPFB_EVENT_DISABLED); + break; + case OMAPFB_AUTO_UPDATE: + hwa742.stop_auto_update = 1; + del_timer_sync(&hwa742.auto_update_timer); + break; + case OMAPFB_UPDATE_DISABLED: + break; + } + + hwa742.update_mode = mode; + hwa742_sync(); + hwa742.stop_auto_update = 0; + + switch (mode) { + case OMAPFB_MANUAL_UPDATE: + omapfb_notify_clients(hwa742.fbdev, OMAPFB_EVENT_READY); + break; + case OMAPFB_AUTO_UPDATE: + hwa742_update_window_auto(0); + break; + case OMAPFB_UPDATE_DISABLED: + break; + } + + return 0; +} + +static enum omapfb_update_mode hwa742_get_update_mode(void) +{ + return hwa742.update_mode; +} + +static unsigned long round_to_extif_ticks(unsigned long ps, int div) +{ + int bus_tick = hwa742.extif_clk_period * div; + return (ps + bus_tick - 1) / bus_tick * bus_tick; +} + +static int calc_reg_timing(unsigned long sysclk, int div) +{ + struct extif_timings *t; + unsigned long systim; + + /* CSOnTime 0, WEOnTime 2 ns, REOnTime 2 ns, + * AccessTime 2 ns + 12.2 ns (regs), + * WEOffTime = WEOnTime + 1 ns, + * REOffTime = REOnTime + 16 ns (regs), + * CSOffTime = REOffTime + 1 ns + * ReadCycle = 2ns + 2*SYSCLK (regs), + * WriteCycle = 2*SYSCLK + 2 ns, + * CSPulseWidth = 10 ns */ + systim = 1000000000 / (sysclk / 1000); + dev_dbg(hwa742.fbdev->dev, "HWA742 systim %lu ps extif_clk_period %u ps" + "extif_clk_div %d\n", systim, hwa742.extif_clk_period, div); + + t = &hwa742.reg_timings; + memset(t, 0, sizeof(*t)); + t->clk_div = div; + t->cs_on_time = 0; + t->we_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div); + t->re_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div); + t->access_time = round_to_extif_ticks(t->re_on_time + 12200, div); + t->we_off_time = round_to_extif_ticks(t->we_on_time + 1000, div); + t->re_off_time = round_to_extif_ticks(t->re_on_time + 16000, div); + t->cs_off_time = round_to_extif_ticks(t->re_off_time + 1000, div); + t->we_cycle_time = round_to_extif_ticks(2 * systim + 2000, div); + if (t->we_cycle_time < t->we_off_time) + t->we_cycle_time = t->we_off_time; + t->re_cycle_time = round_to_extif_ticks(2 * systim + 2000, div); + if (t->re_cycle_time < t->re_off_time) + t->re_cycle_time = t->re_off_time; + t->cs_pulse_width = 0; + + dev_dbg(hwa742.fbdev->dev, "[reg]cson %d csoff %d reon %d reoff %d\n", + t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time); + dev_dbg(hwa742.fbdev->dev, "[reg]weon %d weoff %d recyc %d wecyc %d\n", + t->we_on_time, t->we_off_time, t->re_cycle_time, + t->we_cycle_time); + dev_dbg(hwa742.fbdev->dev, "[reg]rdaccess %d cspulse %d\n", + t->access_time, t->cs_pulse_width); + + return hwa742.extif->convert_timings(t); +} + +static int calc_lut_timing(unsigned long sysclk, int div) +{ + struct extif_timings *t; + unsigned long systim; + + /* CSOnTime 0, WEOnTime 2 ns, REOnTime 2 ns, + * AccessTime 2 ns + 4 * SYSCLK + 26 (lut), + * WEOffTime = WEOnTime + 1 ns, + * REOffTime = REOnTime + 4*SYSCLK + 26 ns (lut), + * CSOffTime = REOffTime + 1 ns + * ReadCycle = 2ns + 4*SYSCLK + 26 ns (lut), + * WriteCycle = 2*SYSCLK + 2 ns, + * CSPulseWidth = 10 ns + */ + systim = 1000000000 / (sysclk / 1000); + dev_dbg(hwa742.fbdev->dev, "HWA742 systim %lu ps extif_clk_period %u ps" + "extif_clk_div %d\n", systim, hwa742.extif_clk_period, div); + + t = &hwa742.lut_timings; + memset(t, 0, sizeof(*t)); + + t->clk_div = div; + + t->cs_on_time = 0; + t->we_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div); + t->re_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div); + t->access_time = round_to_extif_ticks(t->re_on_time + 4 * systim + + 26000, div); + t->we_off_time = round_to_extif_ticks(t->we_on_time + 1000, div); + t->re_off_time = round_to_extif_ticks(t->re_on_time + 4 * systim + + 26000, div); + t->cs_off_time = round_to_extif_ticks(t->re_off_time + 1000, div); + t->we_cycle_time = round_to_extif_ticks(2 * systim + 2000, div); + if (t->we_cycle_time < t->we_off_time) + t->we_cycle_time = t->we_off_time; + t->re_cycle_time = round_to_extif_ticks(2000 + 4 * systim + 26000, div); + if (t->re_cycle_time < t->re_off_time) + t->re_cycle_time = t->re_off_time; + t->cs_pulse_width = 0; + + dev_dbg(hwa742.fbdev->dev, "[lut]cson %d csoff %d reon %d reoff %d\n", + t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time); + dev_dbg(hwa742.fbdev->dev, "[lut]weon %d weoff %d recyc %d wecyc %d\n", + t->we_on_time, t->we_off_time, t->re_cycle_time, + t->we_cycle_time); + dev_dbg(hwa742.fbdev->dev, "[lut]rdaccess %d cspulse %d\n", + t->access_time, t->cs_pulse_width); + + return hwa742.extif->convert_timings(t); +} + +static int calc_extif_timings(unsigned long sysclk, int *extif_mem_div) +{ + int max_clk_div; + int div; + + hwa742.extif->get_clk_info(&hwa742.extif_clk_period, &max_clk_div); + for (div = 1; div < max_clk_div; div++) { + if (calc_reg_timing(sysclk, div) == 0) + break; + } + if (div >= max_clk_div) + goto err; + + *extif_mem_div = div; + + for (div = 1; div < max_clk_div; div++) { + if (calc_lut_timing(sysclk, div) == 0) + break; + } + + if (div >= max_clk_div) + goto err; + + return 0; + +err: + dev_err(hwa742.fbdev->dev, "can't setup timings\n"); + return -1; +} + +static void calc_hwa742_clk_rates(unsigned long ext_clk, + unsigned long *sys_clk, unsigned long *pix_clk) +{ + int pix_clk_src; + int sys_div = 0, sys_mul = 0; + int pix_div; + + pix_clk_src = hwa742_read_reg(HWA742_CLK_SRC_REG); + pix_div = ((pix_clk_src >> 3) & 0x1f) + 1; + if ((pix_clk_src & (0x3 << 1)) == 0) { + /* Source is the PLL */ + sys_div = (hwa742_read_reg(HWA742_PLL_DIV_REG) & 0x3f) + 1; + sys_mul = (hwa742_read_reg(HWA742_PLL_4_REG) & 0x7f) + 1; + *sys_clk = ext_clk * sys_mul / sys_div; + } else /* else source is ext clk, or oscillator */ + *sys_clk = ext_clk; + + *pix_clk = *sys_clk / pix_div; /* HZ */ + dev_dbg(hwa742.fbdev->dev, + "ext_clk %ld pix_src %d pix_div %d sys_div %d sys_mul %d\n", + ext_clk, pix_clk_src & (0x3 << 1), pix_div, sys_div, sys_mul); + dev_dbg(hwa742.fbdev->dev, "sys_clk %ld pix_clk %ld\n", + *sys_clk, *pix_clk); +} + + +static int setup_tearsync(unsigned long pix_clk, int extif_div) +{ + int hdisp, vdisp; + int hndp, vndp; + int hsw, vsw; + int hs, vs; + int hs_pol_inv, vs_pol_inv; + int use_hsvs, use_ndp; + u8 b; + + hsw = hwa742_read_reg(HWA742_HS_W_REG); + vsw = hwa742_read_reg(HWA742_VS_W_REG); + hs_pol_inv = !(hsw & 0x80); + vs_pol_inv = !(vsw & 0x80); + hsw = hsw & 0x7f; + vsw = vsw & 0x3f; + + hdisp = (hwa742_read_reg(HWA742_H_DISP_REG) & 0x7f) * 8; + vdisp = hwa742_read_reg(HWA742_V_DISP_1_REG) + + ((hwa742_read_reg(HWA742_V_DISP_2_REG) & 0x3) << 8); + + hndp = hwa742_read_reg(HWA742_H_NDP_REG) & 0x7f; + vndp = hwa742_read_reg(HWA742_V_NDP_REG); + + /* time to transfer one pixel (16bpp) in ps */ + hwa742.pix_tx_time = hwa742.reg_timings.we_cycle_time; + if (hwa742.extif->get_max_tx_rate != NULL) { + /* + * The external interface might have a rate limitation, + * if so, we have to maximize our transfer rate. + */ + unsigned long min_tx_time; + unsigned long max_tx_rate = hwa742.extif->get_max_tx_rate(); + + dev_dbg(hwa742.fbdev->dev, "max_tx_rate %ld HZ\n", + max_tx_rate); + min_tx_time = 1000000000 / (max_tx_rate / 1000); /* ps */ + if (hwa742.pix_tx_time < min_tx_time) + hwa742.pix_tx_time = min_tx_time; + } + + /* time to update one line in ps */ + hwa742.line_upd_time = (hdisp + hndp) * 1000000 / (pix_clk / 1000); + hwa742.line_upd_time *= 1000; + if (hdisp * hwa742.pix_tx_time > hwa742.line_upd_time) + /* + * transfer speed too low, we might have to use both + * HS and VS + */ + use_hsvs = 1; + else + /* decent transfer speed, we'll always use only VS */ + use_hsvs = 0; + + if (use_hsvs && (hs_pol_inv || vs_pol_inv)) { + /* + * HS or'ed with VS doesn't work, use the active high + * TE signal based on HNDP / VNDP + */ + use_ndp = 1; + hs_pol_inv = 0; + vs_pol_inv = 0; + hs = hndp; + vs = vndp; + } else { + /* + * Use HS or'ed with VS as a TE signal if both are needed + * or VNDP if only vsync is needed. + */ + use_ndp = 0; + hs = hsw; + vs = vsw; + if (!use_hsvs) { + hs_pol_inv = 0; + vs_pol_inv = 0; + } + } + + hs = hs * 1000000 / (pix_clk / 1000); /* ps */ + hs *= 1000; + + vs = vs * (hdisp + hndp) * 1000000 / (pix_clk / 1000); /* ps */ + vs *= 1000; + + if (vs <= hs) + return -EDOM; + /* set VS to 120% of HS to minimize VS detection time */ + vs = hs * 12 / 10; + /* minimize HS too */ + hs = 10000; + + b = hwa742_read_reg(HWA742_NDP_CTRL); + b &= ~0x3; + b |= use_hsvs ? 1 : 0; + b |= (use_ndp && use_hsvs) ? 0 : 2; + hwa742_write_reg(HWA742_NDP_CTRL, b); + + hwa742.vsync_only = !use_hsvs; + + dev_dbg(hwa742.fbdev->dev, + "pix_clk %ld HZ pix_tx_time %ld ps line_upd_time %ld ps\n", + pix_clk, hwa742.pix_tx_time, hwa742.line_upd_time); + dev_dbg(hwa742.fbdev->dev, + "hs %d ps vs %d ps mode %d vsync_only %d\n", + hs, vs, (b & 0x3), !use_hsvs); + + return hwa742.extif->setup_tearsync(1, hs, vs, + hs_pol_inv, vs_pol_inv, extif_div); +} + +static void hwa742_get_caps(int plane, struct omapfb_caps *caps) +{ + hwa742.int_ctrl->get_caps(plane, caps); + caps->ctrl |= OMAPFB_CAPS_MANUAL_UPDATE | + OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE; + if (hwa742.te_connected) + caps->ctrl |= OMAPFB_CAPS_TEARSYNC; + caps->wnd_color |= (1 << OMAPFB_COLOR_RGB565) | + (1 << OMAPFB_COLOR_YUV420); +} + +static void hwa742_suspend(void) +{ + hwa742.update_mode_before_suspend = hwa742.update_mode; + hwa742_set_update_mode(OMAPFB_UPDATE_DISABLED); + /* Enable sleep mode */ + hwa742_write_reg(HWA742_POWER_SAVE, 1 << 1); + clk_disable(hwa742.sys_ck); +} + +static void hwa742_resume(void) +{ + clk_enable(hwa742.sys_ck); + + /* Disable sleep mode */ + hwa742_write_reg(HWA742_POWER_SAVE, 0); + while (1) { + /* Loop until PLL output is stabilized */ + if (hwa742_read_reg(HWA742_PLL_DIV_REG) & (1 << 7)) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(5)); + } + hwa742_set_update_mode(hwa742.update_mode_before_suspend); +} + +static int hwa742_init(struct omapfb_device *fbdev, int ext_mode, + struct omapfb_mem_desc *req_vram) +{ + int r = 0, i; + u8 rev, conf; + unsigned long ext_clk; + unsigned long sys_clk, pix_clk; + int extif_mem_div; + struct omapfb_platform_data *omapfb_conf; + + BUG_ON(!fbdev->ext_if || !fbdev->int_ctrl); + + hwa742.fbdev = fbdev; + hwa742.extif = fbdev->ext_if; + hwa742.int_ctrl = fbdev->int_ctrl; + + omapfb_conf = dev_get_platdata(fbdev->dev); + + hwa742.sys_ck = clk_get(NULL, "hwa_sys_ck"); + + spin_lock_init(&hwa742.req_lock); + + if ((r = hwa742.int_ctrl->init(fbdev, 1, req_vram)) < 0) + goto err1; + + if ((r = hwa742.extif->init(fbdev)) < 0) + goto err2; + + ext_clk = clk_get_rate(hwa742.sys_ck); + if ((r = calc_extif_timings(ext_clk, &extif_mem_div)) < 0) + goto err3; + hwa742.extif->set_timings(&hwa742.reg_timings); + clk_enable(hwa742.sys_ck); + + calc_hwa742_clk_rates(ext_clk, &sys_clk, &pix_clk); + if ((r = calc_extif_timings(sys_clk, &extif_mem_div)) < 0) + goto err4; + hwa742.extif->set_timings(&hwa742.reg_timings); + + rev = hwa742_read_reg(HWA742_REV_CODE_REG); + if ((rev & 0xfc) != 0x80) { + dev_err(fbdev->dev, "HWA742: invalid revision %02x\n", rev); + r = -ENODEV; + goto err4; + } + + + if (!(hwa742_read_reg(HWA742_PLL_DIV_REG) & 0x80)) { + dev_err(fbdev->dev, + "HWA742: controller not initialized by the bootloader\n"); + r = -ENODEV; + goto err4; + } + + if ((r = setup_tearsync(pix_clk, extif_mem_div)) < 0) { + dev_err(hwa742.fbdev->dev, + "HWA742: can't setup tearing synchronization\n"); + goto err4; + } + hwa742.te_connected = 1; + + hwa742.max_transmit_size = hwa742.extif->max_transmit_size; + + hwa742.update_mode = OMAPFB_UPDATE_DISABLED; + + hwa742.auto_update_window.x = 0; + hwa742.auto_update_window.y = 0; + hwa742.auto_update_window.width = fbdev->panel->x_res; + hwa742.auto_update_window.height = fbdev->panel->y_res; + hwa742.auto_update_window.format = 0; + + init_timer(&hwa742.auto_update_timer); + hwa742.auto_update_timer.function = hwa742_update_window_auto; + hwa742.auto_update_timer.data = 0; + + hwa742.prev_color_mode = -1; + hwa742.prev_flags = 0; + + hwa742.fbdev = fbdev; + + INIT_LIST_HEAD(&hwa742.free_req_list); + INIT_LIST_HEAD(&hwa742.pending_req_list); + for (i = 0; i < ARRAY_SIZE(hwa742.req_pool); i++) + list_add(&hwa742.req_pool[i].entry, &hwa742.free_req_list); + BUG_ON(i <= IRQ_REQ_POOL_SIZE); + sema_init(&hwa742.req_sema, i - IRQ_REQ_POOL_SIZE); + + conf = hwa742_read_reg(HWA742_CONFIG_REG); + dev_info(fbdev->dev, ": Epson HWA742 LCD controller rev %d " + "initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07); + + return 0; +err4: + clk_disable(hwa742.sys_ck); +err3: + hwa742.extif->cleanup(); +err2: + hwa742.int_ctrl->cleanup(); +err1: + return r; +} + +static void hwa742_cleanup(void) +{ + hwa742_set_update_mode(OMAPFB_UPDATE_DISABLED); + hwa742.extif->cleanup(); + hwa742.int_ctrl->cleanup(); + clk_disable(hwa742.sys_ck); +} + +struct lcd_ctrl hwa742_ctrl = { + .name = "hwa742", + .init = hwa742_init, + .cleanup = hwa742_cleanup, + .bind_client = hwa742_bind_client, + .get_caps = hwa742_get_caps, + .set_update_mode = hwa742_set_update_mode, + .get_update_mode = hwa742_get_update_mode, + .setup_plane = hwa742_setup_plane, + .enable_plane = hwa742_enable_plane, + .update_window = hwa742_update_window_async, + .sync = hwa742_sync, + .suspend = hwa742_suspend, + .resume = hwa742_resume, +}; + diff --git a/drivers/video/fbdev/omap/lcd_ams_delta.c b/drivers/video/fbdev/omap/lcd_ams_delta.c new file mode 100644 index 000000000000..4a5f2cd3d3bf --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_ams_delta.c @@ -0,0 +1,225 @@ +/* + * Based on drivers/video/omap/lcd_inn1510.c + * + * LCD panel support for the Amstrad E3 (Delta) videophone. + * + * Copyright (C) 2006 Jonathan McDowell <noodles@earth.li> + * + * 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/platform_device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/lcd.h> +#include <linux/gpio.h> + +#include <mach/hardware.h> +#include <mach/board-ams-delta.h> + +#include "omapfb.h" + +#define AMS_DELTA_DEFAULT_CONTRAST 112 + +#define AMS_DELTA_MAX_CONTRAST 0x00FF +#define AMS_DELTA_LCD_POWER 0x0100 + + +/* LCD class device section */ + +static int ams_delta_lcd; + +static int ams_delta_lcd_set_power(struct lcd_device *dev, int power) +{ + if (power == FB_BLANK_UNBLANK) { + if (!(ams_delta_lcd & AMS_DELTA_LCD_POWER)) { + omap_writeb(ams_delta_lcd & AMS_DELTA_MAX_CONTRAST, + OMAP_PWL_ENABLE); + omap_writeb(1, OMAP_PWL_CLK_ENABLE); + ams_delta_lcd |= AMS_DELTA_LCD_POWER; + } + } else { + if (ams_delta_lcd & AMS_DELTA_LCD_POWER) { + omap_writeb(0, OMAP_PWL_ENABLE); + omap_writeb(0, OMAP_PWL_CLK_ENABLE); + ams_delta_lcd &= ~AMS_DELTA_LCD_POWER; + } + } + return 0; +} + +static int ams_delta_lcd_set_contrast(struct lcd_device *dev, int value) +{ + if ((value >= 0) && (value <= AMS_DELTA_MAX_CONTRAST)) { + omap_writeb(value, OMAP_PWL_ENABLE); + ams_delta_lcd &= ~AMS_DELTA_MAX_CONTRAST; + ams_delta_lcd |= value; + } + return 0; +} + +#ifdef CONFIG_LCD_CLASS_DEVICE +static int ams_delta_lcd_get_power(struct lcd_device *dev) +{ + if (ams_delta_lcd & AMS_DELTA_LCD_POWER) + return FB_BLANK_UNBLANK; + else + return FB_BLANK_POWERDOWN; +} + +static int ams_delta_lcd_get_contrast(struct lcd_device *dev) +{ + if (!(ams_delta_lcd & AMS_DELTA_LCD_POWER)) + return 0; + + return ams_delta_lcd & AMS_DELTA_MAX_CONTRAST; +} + +static struct lcd_ops ams_delta_lcd_ops = { + .get_power = ams_delta_lcd_get_power, + .set_power = ams_delta_lcd_set_power, + .get_contrast = ams_delta_lcd_get_contrast, + .set_contrast = ams_delta_lcd_set_contrast, +}; +#endif + + +/* omapfb panel section */ + +static const struct gpio _gpios[] = { + { + .gpio = AMS_DELTA_GPIO_PIN_LCD_VBLEN, + .flags = GPIOF_OUT_INIT_LOW, + .label = "lcd_vblen", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_LCD_NDISP, + .flags = GPIOF_OUT_INIT_LOW, + .label = "lcd_ndisp", + }, +}; + +static int ams_delta_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return gpio_request_array(_gpios, ARRAY_SIZE(_gpios)); +} + +static void ams_delta_panel_cleanup(struct lcd_panel *panel) +{ + gpio_free_array(_gpios, ARRAY_SIZE(_gpios)); +} + +static int ams_delta_panel_enable(struct lcd_panel *panel) +{ + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_NDISP, 1); + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_VBLEN, 1); + return 0; +} + +static void ams_delta_panel_disable(struct lcd_panel *panel) +{ + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_VBLEN, 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_NDISP, 0); +} + +static unsigned long ams_delta_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcd_panel ams_delta_panel = { + .name = "ams-delta", + .config = 0, + + .bpp = 12, + .data_lines = 16, + .x_res = 480, + .y_res = 320, + .pixel_clock = 4687, + .hsw = 3, + .hfp = 1, + .hbp = 1, + .vsw = 1, + .vfp = 0, + .vbp = 0, + .pcd = 0, + .acb = 37, + + .init = ams_delta_panel_init, + .cleanup = ams_delta_panel_cleanup, + .enable = ams_delta_panel_enable, + .disable = ams_delta_panel_disable, + .get_caps = ams_delta_panel_get_caps, +}; + + +/* platform driver section */ + +static int ams_delta_panel_probe(struct platform_device *pdev) +{ + struct lcd_device *lcd_device = NULL; +#ifdef CONFIG_LCD_CLASS_DEVICE + int ret; + + lcd_device = lcd_device_register("omapfb", &pdev->dev, NULL, + &ams_delta_lcd_ops); + + if (IS_ERR(lcd_device)) { + ret = PTR_ERR(lcd_device); + dev_err(&pdev->dev, "failed to register device\n"); + return ret; + } + + platform_set_drvdata(pdev, lcd_device); + lcd_device->props.max_contrast = AMS_DELTA_MAX_CONTRAST; +#endif + + ams_delta_lcd_set_contrast(lcd_device, AMS_DELTA_DEFAULT_CONTRAST); + ams_delta_lcd_set_power(lcd_device, FB_BLANK_UNBLANK); + + omapfb_register_panel(&ams_delta_panel); + return 0; +} + +static int ams_delta_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int ams_delta_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int ams_delta_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver ams_delta_panel_driver = { + .probe = ams_delta_panel_probe, + .remove = ams_delta_panel_remove, + .suspend = ams_delta_panel_suspend, + .resume = ams_delta_panel_resume, + .driver = { + .name = "lcd_ams_delta", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(ams_delta_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_h3.c b/drivers/video/fbdev/omap/lcd_h3.c new file mode 100644 index 000000000000..49bdeca81e50 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_h3.c @@ -0,0 +1,127 @@ +/* + * LCD panel support for the TI OMAP H3 board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * 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/platform_device.h> +#include <linux/i2c/tps65010.h> + +#include <asm/gpio.h> +#include "omapfb.h" + +#define MODULE_NAME "omapfb-lcd_h3" + +static int h3_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev) +{ + return 0; +} + +static void h3_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int h3_panel_enable(struct lcd_panel *panel) +{ + int r = 0; + + /* GPIO1 and GPIO2 of TPS65010 send LCD_ENBKL and LCD_ENVDD signals */ + r = tps65010_set_gpio_out_value(GPIO1, HIGH); + if (!r) + r = tps65010_set_gpio_out_value(GPIO2, HIGH); + if (r) + pr_err(MODULE_NAME ": Unable to turn on LCD panel\n"); + + return r; +} + +static void h3_panel_disable(struct lcd_panel *panel) +{ + int r = 0; + + /* GPIO1 and GPIO2 of TPS65010 send LCD_ENBKL and LCD_ENVDD signals */ + r = tps65010_set_gpio_out_value(GPIO1, LOW); + if (!r) + tps65010_set_gpio_out_value(GPIO2, LOW); + if (r) + pr_err(MODULE_NAME ": Unable to turn off LCD panel\n"); +} + +static unsigned long h3_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel h3_panel = { + .name = "h3", + .config = OMAP_LCDC_PANEL_TFT, + + .data_lines = 16, + .bpp = 16, + .x_res = 240, + .y_res = 320, + .pixel_clock = 12000, + .hsw = 12, + .hfp = 14, + .hbp = 72 - 12, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 0, + + .init = h3_panel_init, + .cleanup = h3_panel_cleanup, + .enable = h3_panel_enable, + .disable = h3_panel_disable, + .get_caps = h3_panel_get_caps, +}; + +static int h3_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&h3_panel); + return 0; +} + +static int h3_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int h3_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int h3_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver h3_panel_driver = { + .probe = h3_panel_probe, + .remove = h3_panel_remove, + .suspend = h3_panel_suspend, + .resume = h3_panel_resume, + .driver = { + .name = "lcd_h3", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(h3_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_htcherald.c b/drivers/video/fbdev/omap/lcd_htcherald.c new file mode 100644 index 000000000000..20f477851d54 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_htcherald.c @@ -0,0 +1,118 @@ +/* + * File: drivers/video/omap/lcd-htcherald.c + * + * LCD panel support for the HTC Herald + * + * Copyright (C) 2009 Cory Maccarrone <darkstar6262@gmail.com> + * Copyright (C) 2009 Wing Linux + * + * Based on the lcd_htcwizard.c file from the linwizard project: + * Copyright (C) linwizard.sourceforge.net + * Author: Angelo Arrifano <miknix@gmail.com> + * Based on lcd_h4 by Imre Deak <imre.deak@nokia.com> + * + * 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/platform_device.h> + +#include "omapfb.h" + +static int htcherald_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return 0; +} + +static void htcherald_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int htcherald_panel_enable(struct lcd_panel *panel) +{ + return 0; +} + +static void htcherald_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long htcherald_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +/* Found on WIZ200 (miknix) and some HERA110 models (darkstar62) */ +struct lcd_panel htcherald_panel_1 = { + .name = "lcd_herald", + .config = OMAP_LCDC_PANEL_TFT | + OMAP_LCDC_INV_HSYNC | + OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_PIX_CLOCK, + .bpp = 16, + .data_lines = 16, + .x_res = 240, + .y_res = 320, + .pixel_clock = 6093, + .pcd = 0, /* 15 */ + .hsw = 10, + .hfp = 10, + .hbp = 20, + .vsw = 3, + .vfp = 2, + .vbp = 2, + + .init = htcherald_panel_init, + .cleanup = htcherald_panel_cleanup, + .enable = htcherald_panel_enable, + .disable = htcherald_panel_disable, + .get_caps = htcherald_panel_get_caps, +}; + +static int htcherald_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&htcherald_panel_1); + return 0; +} + +static int htcherald_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int htcherald_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int htcherald_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver htcherald_panel_driver = { + .probe = htcherald_panel_probe, + .remove = htcherald_panel_remove, + .suspend = htcherald_panel_suspend, + .resume = htcherald_panel_resume, + .driver = { + .name = "lcd_htcherald", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(htcherald_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_inn1510.c b/drivers/video/fbdev/omap/lcd_inn1510.c new file mode 100644 index 000000000000..2ee423279e35 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_inn1510.c @@ -0,0 +1,113 @@ +/* + * LCD panel support for the TI OMAP1510 Innovator board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * 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/platform_device.h> +#include <linux/io.h> + +#include <mach/hardware.h> + +#include "omapfb.h" + +static int innovator1510_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return 0; +} + +static void innovator1510_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int innovator1510_panel_enable(struct lcd_panel *panel) +{ + __raw_writeb(0x7, OMAP1510_FPGA_LCD_PANEL_CONTROL); + return 0; +} + +static void innovator1510_panel_disable(struct lcd_panel *panel) +{ + __raw_writeb(0x0, OMAP1510_FPGA_LCD_PANEL_CONTROL); +} + +static unsigned long innovator1510_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel innovator1510_panel = { + .name = "inn1510", + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .data_lines = 16, + .x_res = 240, + .y_res = 320, + .pixel_clock = 12500, + .hsw = 40, + .hfp = 40, + .hbp = 72, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 12, + + .init = innovator1510_panel_init, + .cleanup = innovator1510_panel_cleanup, + .enable = innovator1510_panel_enable, + .disable = innovator1510_panel_disable, + .get_caps = innovator1510_panel_get_caps, +}; + +static int innovator1510_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&innovator1510_panel); + return 0; +} + +static int innovator1510_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int innovator1510_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int innovator1510_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver innovator1510_panel_driver = { + .probe = innovator1510_panel_probe, + .remove = innovator1510_panel_remove, + .suspend = innovator1510_panel_suspend, + .resume = innovator1510_panel_resume, + .driver = { + .name = "lcd_inn1510", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(innovator1510_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_inn1610.c b/drivers/video/fbdev/omap/lcd_inn1610.c new file mode 100644 index 000000000000..e3d3d135aa48 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_inn1610.c @@ -0,0 +1,134 @@ +/* + * LCD panel support for the TI OMAP1610 Innovator board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * 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/platform_device.h> + +#include <linux/gpio.h> +#include "omapfb.h" + +#define MODULE_NAME "omapfb-lcd_h3" + +static int innovator1610_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + int r = 0; + + /* configure GPIO(14, 15) as outputs */ + if (gpio_request_one(14, GPIOF_OUT_INIT_LOW, "lcd_en0")) { + pr_err(MODULE_NAME ": can't request GPIO 14\n"); + r = -1; + goto exit; + } + if (gpio_request_one(15, GPIOF_OUT_INIT_LOW, "lcd_en1")) { + pr_err(MODULE_NAME ": can't request GPIO 15\n"); + gpio_free(14); + r = -1; + goto exit; + } +exit: + return r; +} + +static void innovator1610_panel_cleanup(struct lcd_panel *panel) +{ + gpio_free(15); + gpio_free(14); +} + +static int innovator1610_panel_enable(struct lcd_panel *panel) +{ + /* set GPIO14 and GPIO15 high */ + gpio_set_value(14, 1); + gpio_set_value(15, 1); + return 0; +} + +static void innovator1610_panel_disable(struct lcd_panel *panel) +{ + /* set GPIO13, GPIO14 and GPIO15 low */ + gpio_set_value(14, 0); + gpio_set_value(15, 0); +} + +static unsigned long innovator1610_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel innovator1610_panel = { + .name = "inn1610", + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .data_lines = 16, + .x_res = 320, + .y_res = 240, + .pixel_clock = 12500, + .hsw = 40, + .hfp = 40, + .hbp = 72, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 12, + + .init = innovator1610_panel_init, + .cleanup = innovator1610_panel_cleanup, + .enable = innovator1610_panel_enable, + .disable = innovator1610_panel_disable, + .get_caps = innovator1610_panel_get_caps, +}; + +static int innovator1610_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&innovator1610_panel); + return 0; +} + +static int innovator1610_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int innovator1610_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int innovator1610_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver innovator1610_panel_driver = { + .probe = innovator1610_panel_probe, + .remove = innovator1610_panel_remove, + .suspend = innovator1610_panel_suspend, + .resume = innovator1610_panel_resume, + .driver = { + .name = "lcd_inn1610", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(innovator1610_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_mipid.c b/drivers/video/fbdev/omap/lcd_mipid.c new file mode 100644 index 000000000000..803fee618d57 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_mipid.c @@ -0,0 +1,615 @@ +/* + * LCD driver for MIPI DBI-C / DCS compatible LCDs + * + * Copyright (C) 2006 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * 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/device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/spi/spi.h> +#include <linux/module.h> + +#include <linux/platform_data/lcd-mipid.h> + +#include "omapfb.h" + +#define MIPID_MODULE_NAME "lcd_mipid" + +#define MIPID_CMD_READ_DISP_ID 0x04 +#define MIPID_CMD_READ_RED 0x06 +#define MIPID_CMD_READ_GREEN 0x07 +#define MIPID_CMD_READ_BLUE 0x08 +#define MIPID_CMD_READ_DISP_STATUS 0x09 +#define MIPID_CMD_RDDSDR 0x0F +#define MIPID_CMD_SLEEP_IN 0x10 +#define MIPID_CMD_SLEEP_OUT 0x11 +#define MIPID_CMD_DISP_OFF 0x28 +#define MIPID_CMD_DISP_ON 0x29 + +#define MIPID_ESD_CHECK_PERIOD msecs_to_jiffies(5000) + +#define to_mipid_device(p) container_of(p, struct mipid_device, \ + panel) +struct mipid_device { + int enabled; + int revision; + unsigned int saved_bklight_level; + unsigned long hw_guard_end; /* next value of jiffies + when we can issue the + next sleep in/out command */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + struct omapfb_device *fbdev; + struct spi_device *spi; + struct mutex mutex; + struct lcd_panel panel; + + struct workqueue_struct *esd_wq; + struct delayed_work esd_work; + void (*esd_check)(struct mipid_device *m); +}; + +static void mipid_transfer(struct mipid_device *md, int cmd, const u8 *wbuf, + int wlen, u8 *rbuf, int rlen) +{ + struct spi_message m; + struct spi_transfer *x, xfer[4]; + u16 w; + int r; + + BUG_ON(md->spi == NULL); + + spi_message_init(&m); + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + cmd &= 0xff; + x->tx_buf = &cmd; + x->bits_per_word = 9; + x->len = 2; + spi_message_add_tail(x, &m); + + if (wlen) { + x++; + x->tx_buf = wbuf; + x->len = wlen; + x->bits_per_word = 9; + spi_message_add_tail(x, &m); + } + + if (rlen) { + x++; + x->rx_buf = &w; + x->len = 1; + spi_message_add_tail(x, &m); + + if (rlen > 1) { + /* Arrange for the extra clock before the first + * data bit. + */ + x->bits_per_word = 9; + x->len = 2; + + x++; + x->rx_buf = &rbuf[1]; + x->len = rlen - 1; + spi_message_add_tail(x, &m); + } + } + + r = spi_sync(md->spi, &m); + if (r < 0) + dev_dbg(&md->spi->dev, "spi_sync %d\n", r); + + if (rlen) + rbuf[0] = w & 0xff; +} + +static inline void mipid_cmd(struct mipid_device *md, int cmd) +{ + mipid_transfer(md, cmd, NULL, 0, NULL, 0); +} + +static inline void mipid_write(struct mipid_device *md, + int reg, const u8 *buf, int len) +{ + mipid_transfer(md, reg, buf, len, NULL, 0); +} + +static inline void mipid_read(struct mipid_device *md, + int reg, u8 *buf, int len) +{ + mipid_transfer(md, reg, NULL, 0, buf, len); +} + +static void set_data_lines(struct mipid_device *md, int data_lines) +{ + u16 par; + + switch (data_lines) { + case 16: + par = 0x150; + break; + case 18: + par = 0x160; + break; + case 24: + par = 0x170; + break; + } + mipid_write(md, 0x3a, (u8 *)&par, 2); +} + +static void send_init_string(struct mipid_device *md) +{ + u16 initpar[] = { 0x0102, 0x0100, 0x0100 }; + + mipid_write(md, 0xc2, (u8 *)initpar, sizeof(initpar)); + set_data_lines(md, md->panel.data_lines); +} + +static void hw_guard_start(struct mipid_device *md, int guard_msec) +{ + md->hw_guard_wait = msecs_to_jiffies(guard_msec); + md->hw_guard_end = jiffies + md->hw_guard_wait; +} + +static void hw_guard_wait(struct mipid_device *md) +{ + unsigned long wait = md->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= md->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static void set_sleep_mode(struct mipid_device *md, int on) +{ + int cmd, sleep_time = 50; + + if (on) + cmd = MIPID_CMD_SLEEP_IN; + else + cmd = MIPID_CMD_SLEEP_OUT; + hw_guard_wait(md); + mipid_cmd(md, cmd); + hw_guard_start(md, 120); + /* + * When we enable the panel, it seems we _have_ to sleep + * 120 ms before sending the init string. When disabling the + * panel we'll sleep for the duration of 2 frames, so that the + * controller can still provide the PCLK,HS,VS signals. + */ + if (!on) + sleep_time = 120; + msleep(sleep_time); +} + +static void set_display_state(struct mipid_device *md, int enabled) +{ + int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF; + + mipid_cmd(md, cmd); +} + +static int mipid_set_bklight_level(struct lcd_panel *panel, unsigned int level) +{ + struct mipid_device *md = to_mipid_device(panel); + struct mipid_platform_data *pd = md->spi->dev.platform_data; + + if (pd->get_bklight_max == NULL || pd->set_bklight_level == NULL) + return -ENODEV; + if (level > pd->get_bklight_max(pd)) + return -EINVAL; + if (!md->enabled) { + md->saved_bklight_level = level; + return 0; + } + pd->set_bklight_level(pd, level); + + return 0; +} + +static unsigned int mipid_get_bklight_level(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + struct mipid_platform_data *pd = md->spi->dev.platform_data; + + if (pd->get_bklight_level == NULL) + return -ENODEV; + return pd->get_bklight_level(pd); +} + +static unsigned int mipid_get_bklight_max(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + struct mipid_platform_data *pd = md->spi->dev.platform_data; + + if (pd->get_bklight_max == NULL) + return -ENODEV; + + return pd->get_bklight_max(pd); +} + +static unsigned long mipid_get_caps(struct lcd_panel *panel) +{ + return OMAPFB_CAPS_SET_BACKLIGHT; +} + +static u16 read_first_pixel(struct mipid_device *md) +{ + u16 pixel; + u8 red, green, blue; + + mutex_lock(&md->mutex); + mipid_read(md, MIPID_CMD_READ_RED, &red, 1); + mipid_read(md, MIPID_CMD_READ_GREEN, &green, 1); + mipid_read(md, MIPID_CMD_READ_BLUE, &blue, 1); + mutex_unlock(&md->mutex); + + switch (md->panel.data_lines) { + case 16: + pixel = ((red >> 1) << 11) | (green << 5) | (blue >> 1); + break; + case 24: + /* 24 bit -> 16 bit */ + pixel = ((red >> 3) << 11) | ((green >> 2) << 5) | + (blue >> 3); + break; + default: + pixel = 0; + BUG(); + } + + return pixel; +} + +static int mipid_run_test(struct lcd_panel *panel, int test_num) +{ + struct mipid_device *md = to_mipid_device(panel); + static const u16 test_values[4] = { + 0x0000, 0xffff, 0xaaaa, 0x5555, + }; + int i; + + if (test_num != MIPID_TEST_RGB_LINES) + return MIPID_TEST_INVALID; + + for (i = 0; i < ARRAY_SIZE(test_values); i++) { + int delay; + unsigned long tmo; + + omapfb_write_first_pixel(md->fbdev, test_values[i]); + tmo = jiffies + msecs_to_jiffies(100); + delay = 25; + while (1) { + u16 pixel; + + msleep(delay); + pixel = read_first_pixel(md); + if (pixel == test_values[i]) + break; + if (time_after(jiffies, tmo)) { + dev_err(&md->spi->dev, + "MIPI LCD RGB I/F test failed: " + "expecting %04x, got %04x\n", + test_values[i], pixel); + return MIPID_TEST_FAILED; + } + delay = 10; + } + } + + return 0; +} + +static void ls041y3_esd_recover(struct mipid_device *md) +{ + dev_err(&md->spi->dev, "performing LCD ESD recovery\n"); + set_sleep_mode(md, 1); + set_sleep_mode(md, 0); +} + +static void ls041y3_esd_check_mode1(struct mipid_device *md) +{ + u8 state1, state2; + + mipid_read(md, MIPID_CMD_RDDSDR, &state1, 1); + set_sleep_mode(md, 0); + mipid_read(md, MIPID_CMD_RDDSDR, &state2, 1); + dev_dbg(&md->spi->dev, "ESD mode 1 state1 %02x state2 %02x\n", + state1, state2); + /* Each sleep out command will trigger a self diagnostic and flip + * Bit6 if the test passes. + */ + if (!((state1 ^ state2) & (1 << 6))) + ls041y3_esd_recover(md); +} + +static void ls041y3_esd_check_mode2(struct mipid_device *md) +{ + int i; + u8 rbuf[2]; + static const struct { + int cmd; + int wlen; + u16 wbuf[3]; + } *rd, rd_ctrl[7] = { + { 0xb0, 4, { 0x0101, 0x01fe, } }, + { 0xb1, 4, { 0x01de, 0x0121, } }, + { 0xc2, 4, { 0x0100, 0x0100, } }, + { 0xbd, 2, { 0x0100, } }, + { 0xc2, 4, { 0x01fc, 0x0103, } }, + { 0xb4, 0, }, + { 0x00, 0, }, + }; + + rd = rd_ctrl; + for (i = 0; i < 3; i++, rd++) + mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen); + + udelay(10); + mipid_read(md, rd->cmd, rbuf, 2); + rd++; + + for (i = 0; i < 3; i++, rd++) { + udelay(10); + mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen); + } + + dev_dbg(&md->spi->dev, "ESD mode 2 state %02x\n", rbuf[1]); + if (rbuf[1] == 0x00) + ls041y3_esd_recover(md); +} + +static void ls041y3_esd_check(struct mipid_device *md) +{ + ls041y3_esd_check_mode1(md); + if (md->revision >= 0x88) + ls041y3_esd_check_mode2(md); +} + +static void mipid_esd_start_check(struct mipid_device *md) +{ + if (md->esd_check != NULL) + queue_delayed_work(md->esd_wq, &md->esd_work, + MIPID_ESD_CHECK_PERIOD); +} + +static void mipid_esd_stop_check(struct mipid_device *md) +{ + if (md->esd_check != NULL) + cancel_delayed_work_sync(&md->esd_work); +} + +static void mipid_esd_work(struct work_struct *work) +{ + struct mipid_device *md = container_of(work, struct mipid_device, + esd_work.work); + + mutex_lock(&md->mutex); + md->esd_check(md); + mutex_unlock(&md->mutex); + mipid_esd_start_check(md); +} + +static int mipid_enable(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + mutex_lock(&md->mutex); + + if (md->enabled) { + mutex_unlock(&md->mutex); + return 0; + } + set_sleep_mode(md, 0); + md->enabled = 1; + send_init_string(md); + set_display_state(md, 1); + mipid_set_bklight_level(panel, md->saved_bklight_level); + mipid_esd_start_check(md); + + mutex_unlock(&md->mutex); + return 0; +} + +static void mipid_disable(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + /* + * A final ESD work might be called before returning, + * so do this without holding the lock. + */ + mipid_esd_stop_check(md); + mutex_lock(&md->mutex); + + if (!md->enabled) { + mutex_unlock(&md->mutex); + return; + } + md->saved_bklight_level = mipid_get_bklight_level(panel); + mipid_set_bklight_level(panel, 0); + set_display_state(md, 0); + set_sleep_mode(md, 1); + md->enabled = 0; + + mutex_unlock(&md->mutex); +} + +static int panel_enabled(struct mipid_device *md) +{ + u32 disp_status; + int enabled; + + mipid_read(md, MIPID_CMD_READ_DISP_STATUS, (u8 *)&disp_status, 4); + disp_status = __be32_to_cpu(disp_status); + enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10)); + dev_dbg(&md->spi->dev, + "LCD panel %senabled by bootloader (status 0x%04x)\n", + enabled ? "" : "not ", disp_status); + return enabled; +} + +static int mipid_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + struct mipid_device *md = to_mipid_device(panel); + + md->fbdev = fbdev; + md->esd_wq = create_singlethread_workqueue("mipid_esd"); + if (md->esd_wq == NULL) { + dev_err(&md->spi->dev, "can't create ESD workqueue\n"); + return -ENOMEM; + } + INIT_DELAYED_WORK(&md->esd_work, mipid_esd_work); + mutex_init(&md->mutex); + + md->enabled = panel_enabled(md); + + if (md->enabled) + mipid_esd_start_check(md); + else + md->saved_bklight_level = mipid_get_bklight_level(panel); + + return 0; +} + +static void mipid_cleanup(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + if (md->enabled) + mipid_esd_stop_check(md); + destroy_workqueue(md->esd_wq); +} + +static struct lcd_panel mipid_panel = { + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .x_res = 800, + .y_res = 480, + .pixel_clock = 21940, + .hsw = 50, + .hfp = 20, + .hbp = 15, + .vsw = 2, + .vfp = 1, + .vbp = 3, + + .init = mipid_init, + .cleanup = mipid_cleanup, + .enable = mipid_enable, + .disable = mipid_disable, + .get_caps = mipid_get_caps, + .set_bklight_level = mipid_set_bklight_level, + .get_bklight_level = mipid_get_bklight_level, + .get_bklight_max = mipid_get_bklight_max, + .run_test = mipid_run_test, +}; + +static int mipid_detect(struct mipid_device *md) +{ + struct mipid_platform_data *pdata; + u8 display_id[3]; + + pdata = md->spi->dev.platform_data; + if (pdata == NULL) { + dev_err(&md->spi->dev, "missing platform data\n"); + return -ENOENT; + } + + mipid_read(md, MIPID_CMD_READ_DISP_ID, display_id, 3); + dev_dbg(&md->spi->dev, "MIPI display ID: %02x%02x%02x\n", + display_id[0], display_id[1], display_id[2]); + + switch (display_id[0]) { + case 0x45: + md->panel.name = "lph8923"; + break; + case 0x83: + md->panel.name = "ls041y3"; + md->esd_check = ls041y3_esd_check; + break; + default: + md->panel.name = "unknown"; + dev_err(&md->spi->dev, "invalid display ID\n"); + return -ENODEV; + } + + md->revision = display_id[1]; + md->panel.data_lines = pdata->data_lines; + pr_info("omapfb: %s rev %02x LCD detected, %d data lines\n", + md->panel.name, md->revision, md->panel.data_lines); + + return 0; +} + +static int mipid_spi_probe(struct spi_device *spi) +{ + struct mipid_device *md; + int r; + + md = kzalloc(sizeof(*md), GFP_KERNEL); + if (md == NULL) { + dev_err(&spi->dev, "out of memory\n"); + return -ENOMEM; + } + + spi->mode = SPI_MODE_0; + md->spi = spi; + dev_set_drvdata(&spi->dev, md); + md->panel = mipid_panel; + + r = mipid_detect(md); + if (r < 0) + return r; + + omapfb_register_panel(&md->panel); + + return 0; +} + +static int mipid_spi_remove(struct spi_device *spi) +{ + struct mipid_device *md = dev_get_drvdata(&spi->dev); + + mipid_disable(&md->panel); + kfree(md); + + return 0; +} + +static struct spi_driver mipid_spi_driver = { + .driver = { + .name = MIPID_MODULE_NAME, + .owner = THIS_MODULE, + }, + .probe = mipid_spi_probe, + .remove = mipid_spi_remove, +}; + +module_spi_driver(mipid_spi_driver); + +MODULE_DESCRIPTION("MIPI display driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap/lcd_osk.c b/drivers/video/fbdev/omap/lcd_osk.c new file mode 100644 index 000000000000..7fbe04bce0ed --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_osk.c @@ -0,0 +1,133 @@ +/* + * LCD panel support for the TI OMAP OSK board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * Adapted for OSK by <dirk.behme@de.bosch.com> + * + * 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/platform_device.h> + +#include <asm/gpio.h> + +#include <mach/hardware.h> +#include <mach/mux.h> + +#include "omapfb.h" + +static int osk_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev) +{ + /* gpio2 was allocated in board init */ + return 0; +} + +static void osk_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int osk_panel_enable(struct lcd_panel *panel) +{ + /* configure PWL pin */ + omap_cfg_reg(PWL); + + /* Enable PWL unit */ + omap_writeb(0x01, OMAP_PWL_CLK_ENABLE); + + /* Set PWL level */ + omap_writeb(0xFF, OMAP_PWL_ENABLE); + + /* set GPIO2 high (lcd power enabled) */ + gpio_set_value(2, 1); + + return 0; +} + +static void osk_panel_disable(struct lcd_panel *panel) +{ + /* Set PWL level to zero */ + omap_writeb(0x00, OMAP_PWL_ENABLE); + + /* Disable PWL unit */ + omap_writeb(0x00, OMAP_PWL_CLK_ENABLE); + + /* set GPIO2 low */ + gpio_set_value(2, 0); +} + +static unsigned long osk_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel osk_panel = { + .name = "osk", + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .data_lines = 16, + .x_res = 240, + .y_res = 320, + .pixel_clock = 12500, + .hsw = 40, + .hfp = 40, + .hbp = 72, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 12, + + .init = osk_panel_init, + .cleanup = osk_panel_cleanup, + .enable = osk_panel_enable, + .disable = osk_panel_disable, + .get_caps = osk_panel_get_caps, +}; + +static int osk_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&osk_panel); + return 0; +} + +static int osk_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int osk_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int osk_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver osk_panel_driver = { + .probe = osk_panel_probe, + .remove = osk_panel_remove, + .suspend = osk_panel_suspend, + .resume = osk_panel_resume, + .driver = { + .name = "lcd_osk", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(osk_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_palmte.c b/drivers/video/fbdev/omap/lcd_palmte.c new file mode 100644 index 000000000000..ff4fb624b904 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_palmte.c @@ -0,0 +1,110 @@ +/* + * LCD panel support for the Palm Tungsten E + * + * Original version : Romain Goyet <r.goyet@gmail.com> + * Current version : Laurent Gonzalez <palmte.linux@free.fr> + * + * 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/platform_device.h> +#include <linux/io.h> + +#include "omapfb.h" + +static int palmte_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return 0; +} + +static void palmte_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int palmte_panel_enable(struct lcd_panel *panel) +{ + return 0; +} + +static void palmte_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long palmte_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel palmte_panel = { + .name = "palmte", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC | OMAP_LCDC_HSVS_RISING_EDGE | + OMAP_LCDC_HSVS_OPPOSITE, + + .data_lines = 16, + .bpp = 8, + .pixel_clock = 12000, + .x_res = 320, + .y_res = 320, + .hsw = 4, + .hfp = 8, + .hbp = 28, + .vsw = 1, + .vfp = 8, + .vbp = 7, + .pcd = 0, + + .init = palmte_panel_init, + .cleanup = palmte_panel_cleanup, + .enable = palmte_panel_enable, + .disable = palmte_panel_disable, + .get_caps = palmte_panel_get_caps, +}; + +static int palmte_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&palmte_panel); + return 0; +} + +static int palmte_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int palmte_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int palmte_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver palmte_panel_driver = { + .probe = palmte_panel_probe, + .remove = palmte_panel_remove, + .suspend = palmte_panel_suspend, + .resume = palmte_panel_resume, + .driver = { + .name = "lcd_palmte", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(palmte_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_palmtt.c b/drivers/video/fbdev/omap/lcd_palmtt.c new file mode 100644 index 000000000000..aaf3c8ba1243 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_palmtt.c @@ -0,0 +1,116 @@ +/* + * LCD panel support for Palm Tungsten|T + * Current version : Marek Vasut <marek.vasut@gmail.com> + * + * Modified from lcd_inn1510.c + * + * 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. + */ + +/* +GPIO11 - backlight +GPIO12 - screen blanking +GPIO13 - screen blanking +*/ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/io.h> + +#include <asm/gpio.h> +#include "omapfb.h" + +static int palmtt_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return 0; +} + +static void palmtt_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int palmtt_panel_enable(struct lcd_panel *panel) +{ + return 0; +} + +static void palmtt_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long palmtt_panel_get_caps(struct lcd_panel *panel) +{ + return OMAPFB_CAPS_SET_BACKLIGHT; +} + +struct lcd_panel palmtt_panel = { + .name = "palmtt", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC | OMAP_LCDC_HSVS_RISING_EDGE | + OMAP_LCDC_HSVS_OPPOSITE, + .bpp = 16, + .data_lines = 16, + .x_res = 320, + .y_res = 320, + .pixel_clock = 10000, + .hsw = 4, + .hfp = 8, + .hbp = 28, + .vsw = 1, + .vfp = 8, + .vbp = 7, + .pcd = 0, + + .init = palmtt_panel_init, + .cleanup = palmtt_panel_cleanup, + .enable = palmtt_panel_enable, + .disable = palmtt_panel_disable, + .get_caps = palmtt_panel_get_caps, +}; + +static int palmtt_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&palmtt_panel); + return 0; +} + +static int palmtt_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int palmtt_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int palmtt_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver palmtt_panel_driver = { + .probe = palmtt_panel_probe, + .remove = palmtt_panel_remove, + .suspend = palmtt_panel_suspend, + .resume = palmtt_panel_resume, + .driver = { + .name = "lcd_palmtt", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(palmtt_panel_driver); diff --git a/drivers/video/fbdev/omap/lcd_palmz71.c b/drivers/video/fbdev/omap/lcd_palmz71.c new file mode 100644 index 000000000000..3b7d8aa1cf34 --- /dev/null +++ b/drivers/video/fbdev/omap/lcd_palmz71.c @@ -0,0 +1,112 @@ +/* + * LCD panel support for the Palm Zire71 + * + * Original version : Romain Goyet + * Current version : Laurent Gonzalez + * Modified for zire71 : Marek Vasut + * + * 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/platform_device.h> +#include <linux/io.h> + +#include "omapfb.h" + +static int palmz71_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return 0; +} + +static void palmz71_panel_cleanup(struct lcd_panel *panel) +{ + +} + +static int palmz71_panel_enable(struct lcd_panel *panel) +{ + return 0; +} + +static void palmz71_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long palmz71_panel_get_caps(struct lcd_panel *panel) +{ + return OMAPFB_CAPS_SET_BACKLIGHT; +} + +struct lcd_panel palmz71_panel = { + .name = "palmz71", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC | OMAP_LCDC_HSVS_RISING_EDGE | + OMAP_LCDC_HSVS_OPPOSITE, + .data_lines = 16, + .bpp = 16, + .pixel_clock = 24000, + .x_res = 320, + .y_res = 320, + .hsw = 4, + .hfp = 8, + .hbp = 28, + .vsw = 1, + .vfp = 8, + .vbp = 7, + .pcd = 0, + + .init = palmz71_panel_init, + .cleanup = palmz71_panel_cleanup, + .enable = palmz71_panel_enable, + .disable = palmz71_panel_disable, + .get_caps = palmz71_panel_get_caps, +}; + +static int palmz71_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&palmz71_panel); + return 0; +} + +static int palmz71_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int palmz71_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int palmz71_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver palmz71_panel_driver = { + .probe = palmz71_panel_probe, + .remove = palmz71_panel_remove, + .suspend = palmz71_panel_suspend, + .resume = palmz71_panel_resume, + .driver = { + .name = "lcd_palmz71", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(palmz71_panel_driver); diff --git a/drivers/video/fbdev/omap/lcdc.c b/drivers/video/fbdev/omap/lcdc.c new file mode 100644 index 000000000000..b52f62595f65 --- /dev/null +++ b/drivers/video/fbdev/omap/lcdc.c @@ -0,0 +1,856 @@ +/* + * OMAP1 internal LCD controller + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * 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/device.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/clk.h> +#include <linux/gfp.h> + +#include <mach/lcdc.h> +#include <linux/omap-dma.h> + +#include <asm/mach-types.h> + +#include "omapfb.h" + +#include "lcdc.h" + +#define MODULE_NAME "lcdc" + +#define MAX_PALETTE_SIZE PAGE_SIZE + +enum lcdc_load_mode { + OMAP_LCDC_LOAD_PALETTE, + OMAP_LCDC_LOAD_FRAME, + OMAP_LCDC_LOAD_PALETTE_AND_FRAME +}; + +static struct omap_lcd_controller { + enum omapfb_update_mode update_mode; + int ext_mode; + + unsigned long frame_offset; + int screen_width; + int xres; + int yres; + + enum omapfb_color_format color_mode; + int bpp; + void *palette_virt; + dma_addr_t palette_phys; + int palette_code; + int palette_size; + + unsigned int irq_mask; + struct completion last_frame_complete; + struct completion palette_load_complete; + struct clk *lcd_ck; + struct omapfb_device *fbdev; + + void (*dma_callback)(void *data); + void *dma_callback_data; + + int fbmem_allocated; + dma_addr_t vram_phys; + void *vram_virt; + unsigned long vram_size; +} lcdc; + +static void inline enable_irqs(int mask) +{ + lcdc.irq_mask |= mask; +} + +static void inline disable_irqs(int mask) +{ + lcdc.irq_mask &= ~mask; +} + +static void set_load_mode(enum lcdc_load_mode mode) +{ + u32 l; + + l = omap_readl(OMAP_LCDC_CONTROL); + l &= ~(3 << 20); + switch (mode) { + case OMAP_LCDC_LOAD_PALETTE: + l |= 1 << 20; + break; + case OMAP_LCDC_LOAD_FRAME: + l |= 2 << 20; + break; + case OMAP_LCDC_LOAD_PALETTE_AND_FRAME: + break; + default: + BUG(); + } + omap_writel(l, OMAP_LCDC_CONTROL); +} + +static void enable_controller(void) +{ + u32 l; + + l = omap_readl(OMAP_LCDC_CONTROL); + l |= OMAP_LCDC_CTRL_LCD_EN; + l &= ~OMAP_LCDC_IRQ_MASK; + l |= lcdc.irq_mask | OMAP_LCDC_IRQ_DONE; /* enabled IRQs */ + omap_writel(l, OMAP_LCDC_CONTROL); +} + +static void disable_controller_async(void) +{ + u32 l; + u32 mask; + + l = omap_readl(OMAP_LCDC_CONTROL); + mask = OMAP_LCDC_CTRL_LCD_EN | OMAP_LCDC_IRQ_MASK; + /* + * Preserve the DONE mask, since we still want to get the + * final DONE irq. It will be disabled in the IRQ handler. + */ + mask &= ~OMAP_LCDC_IRQ_DONE; + l &= ~mask; + omap_writel(l, OMAP_LCDC_CONTROL); +} + +static void disable_controller(void) +{ + init_completion(&lcdc.last_frame_complete); + disable_controller_async(); + if (!wait_for_completion_timeout(&lcdc.last_frame_complete, + msecs_to_jiffies(500))) + dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n"); +} + +static void reset_controller(u32 status) +{ + static unsigned long reset_count; + static unsigned long last_jiffies; + + disable_controller_async(); + reset_count++; + if (reset_count == 1 || time_after(jiffies, last_jiffies + HZ)) { + dev_err(lcdc.fbdev->dev, + "resetting (status %#010x,reset count %lu)\n", + status, reset_count); + last_jiffies = jiffies; + } + if (reset_count < 100) { + enable_controller(); + } else { + reset_count = 0; + dev_err(lcdc.fbdev->dev, + "too many reset attempts, giving up.\n"); + } +} + +/* + * Configure the LCD DMA according to the current mode specified by parameters + * in lcdc.fbdev and fbdev->var. + */ +static void setup_lcd_dma(void) +{ + static const int dma_elem_type[] = { + 0, + OMAP_DMA_DATA_TYPE_S8, + OMAP_DMA_DATA_TYPE_S16, + 0, + OMAP_DMA_DATA_TYPE_S32, + }; + struct omapfb_plane_struct *plane = lcdc.fbdev->fb_info[0]->par; + struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var; + unsigned long src; + int esize, xelem, yelem; + + src = lcdc.vram_phys + lcdc.frame_offset; + + switch (var->rotate) { + case 0: + if (plane->info.mirror || (src & 3) || + lcdc.color_mode == OMAPFB_COLOR_YUV420 || + (lcdc.xres & 1)) + esize = 2; + else + esize = 4; + xelem = lcdc.xres * lcdc.bpp / 8 / esize; + yelem = lcdc.yres; + break; + case 90: + case 180: + case 270: + if (cpu_is_omap15xx()) { + BUG(); + } + esize = 2; + xelem = lcdc.yres * lcdc.bpp / 16; + yelem = lcdc.xres; + break; + default: + BUG(); + return; + } +#ifdef VERBOSE + dev_dbg(lcdc.fbdev->dev, + "setup_dma: src %#010lx esize %d xelem %d yelem %d\n", + src, esize, xelem, yelem); +#endif + omap_set_lcd_dma_b1(src, xelem, yelem, dma_elem_type[esize]); + if (!cpu_is_omap15xx()) { + int bpp = lcdc.bpp; + + /* + * YUV support is only for external mode when we have the + * YUV window embedded in a 16bpp frame buffer. + */ + if (lcdc.color_mode == OMAPFB_COLOR_YUV420) + bpp = 16; + /* Set virtual xres elem size */ + omap_set_lcd_dma_b1_vxres( + lcdc.screen_width * bpp / 8 / esize); + /* Setup transformations */ + omap_set_lcd_dma_b1_rotation(var->rotate); + omap_set_lcd_dma_b1_mirror(plane->info.mirror); + } + omap_setup_lcd_dma(); +} + +static irqreturn_t lcdc_irq_handler(int irq, void *dev_id) +{ + u32 status; + + status = omap_readl(OMAP_LCDC_STATUS); + + if (status & (OMAP_LCDC_STAT_FUF | OMAP_LCDC_STAT_SYNC_LOST)) + reset_controller(status); + else { + if (status & OMAP_LCDC_STAT_DONE) { + u32 l; + + /* + * Disable IRQ_DONE. The status bit will be cleared + * only when the controller is reenabled and we don't + * want to get more interrupts. + */ + l = omap_readl(OMAP_LCDC_CONTROL); + l &= ~OMAP_LCDC_IRQ_DONE; + omap_writel(l, OMAP_LCDC_CONTROL); + complete(&lcdc.last_frame_complete); + } + if (status & OMAP_LCDC_STAT_LOADED_PALETTE) { + disable_controller_async(); + complete(&lcdc.palette_load_complete); + } + } + + /* + * Clear these interrupt status bits. + * Sync_lost, FUF bits were cleared by disabling the LCD controller + * LOADED_PALETTE can be cleared this way only in palette only + * load mode. In other load modes it's cleared by disabling the + * controller. + */ + status &= ~(OMAP_LCDC_STAT_VSYNC | + OMAP_LCDC_STAT_LOADED_PALETTE | + OMAP_LCDC_STAT_ABC | + OMAP_LCDC_STAT_LINE_INT); + omap_writel(status, OMAP_LCDC_STATUS); + return IRQ_HANDLED; +} + +/* + * Change to a new video mode. We defer this to a later time to avoid any + * flicker and not to mess up the current LCD DMA context. For this we disable + * the LCD controller, which will generate a DONE irq after the last frame has + * been transferred. Then it'll be safe to reconfigure both the LCD controller + * as well as the LCD DMA. + */ +static int omap_lcdc_setup_plane(int plane, int channel_out, + unsigned long offset, int screen_width, + int pos_x, int pos_y, int width, int height, + int color_mode) +{ + struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var; + struct lcd_panel *panel = lcdc.fbdev->panel; + int rot_x, rot_y; + + if (var->rotate == 0) { + rot_x = panel->x_res; + rot_y = panel->y_res; + } else { + rot_x = panel->y_res; + rot_y = panel->x_res; + } + if (plane != 0 || channel_out != 0 || pos_x != 0 || pos_y != 0 || + width > rot_x || height > rot_y) { +#ifdef VERBOSE + dev_dbg(lcdc.fbdev->dev, + "invalid plane params plane %d pos_x %d pos_y %d " + "w %d h %d\n", plane, pos_x, pos_y, width, height); +#endif + return -EINVAL; + } + + lcdc.frame_offset = offset; + lcdc.xres = width; + lcdc.yres = height; + lcdc.screen_width = screen_width; + lcdc.color_mode = color_mode; + + switch (color_mode) { + case OMAPFB_COLOR_CLUT_8BPP: + lcdc.bpp = 8; + lcdc.palette_code = 0x3000; + lcdc.palette_size = 512; + break; + case OMAPFB_COLOR_RGB565: + lcdc.bpp = 16; + lcdc.palette_code = 0x4000; + lcdc.palette_size = 32; + break; + case OMAPFB_COLOR_RGB444: + lcdc.bpp = 16; + lcdc.palette_code = 0x4000; + lcdc.palette_size = 32; + break; + case OMAPFB_COLOR_YUV420: + if (lcdc.ext_mode) { + lcdc.bpp = 12; + break; + } + /* fallthrough */ + case OMAPFB_COLOR_YUV422: + if (lcdc.ext_mode) { + lcdc.bpp = 16; + break; + } + /* fallthrough */ + default: + /* FIXME: other BPPs. + * bpp1: code 0, size 256 + * bpp2: code 0x1000 size 256 + * bpp4: code 0x2000 size 256 + * bpp12: code 0x4000 size 32 + */ + dev_dbg(lcdc.fbdev->dev, "invalid color mode %d\n", color_mode); + BUG(); + return -1; + } + + if (lcdc.ext_mode) { + setup_lcd_dma(); + return 0; + } + + if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) { + disable_controller(); + omap_stop_lcd_dma(); + setup_lcd_dma(); + enable_controller(); + } + + return 0; +} + +static int omap_lcdc_enable_plane(int plane, int enable) +{ + dev_dbg(lcdc.fbdev->dev, + "plane %d enable %d update_mode %d ext_mode %d\n", + plane, enable, lcdc.update_mode, lcdc.ext_mode); + if (plane != OMAPFB_PLANE_GFX) + return -EINVAL; + + return 0; +} + +/* + * Configure the LCD DMA for a palette load operation and do the palette + * downloading synchronously. We don't use the frame+palette load mode of + * the controller, since the palette can always be downloaded separately. + */ +static void load_palette(void) +{ + u16 *palette; + + palette = (u16 *)lcdc.palette_virt; + + *(u16 *)palette &= 0x0fff; + *(u16 *)palette |= lcdc.palette_code; + + omap_set_lcd_dma_b1(lcdc.palette_phys, + lcdc.palette_size / 4 + 1, 1, OMAP_DMA_DATA_TYPE_S32); + + omap_set_lcd_dma_single_transfer(1); + omap_setup_lcd_dma(); + + init_completion(&lcdc.palette_load_complete); + enable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); + set_load_mode(OMAP_LCDC_LOAD_PALETTE); + enable_controller(); + if (!wait_for_completion_timeout(&lcdc.palette_load_complete, + msecs_to_jiffies(500))) + dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n"); + /* The controller gets disabled in the irq handler */ + disable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); + omap_stop_lcd_dma(); + + omap_set_lcd_dma_single_transfer(lcdc.ext_mode); +} + +/* Used only in internal controller mode */ +static int omap_lcdc_setcolreg(u_int regno, u16 red, u16 green, u16 blue, + u16 transp, int update_hw_pal) +{ + u16 *palette; + + if (lcdc.color_mode != OMAPFB_COLOR_CLUT_8BPP || regno > 255) + return -EINVAL; + + palette = (u16 *)lcdc.palette_virt; + + palette[regno] &= ~0x0fff; + palette[regno] |= ((red >> 12) << 8) | ((green >> 12) << 4 ) | + (blue >> 12); + + if (update_hw_pal) { + disable_controller(); + omap_stop_lcd_dma(); + load_palette(); + setup_lcd_dma(); + set_load_mode(OMAP_LCDC_LOAD_FRAME); + enable_controller(); + } + + return 0; +} + +static void calc_ck_div(int is_tft, int pck, int *pck_div) +{ + unsigned long lck; + + pck = max(1, pck); + lck = clk_get_rate(lcdc.lcd_ck); + *pck_div = (lck + pck - 1) / pck; + if (is_tft) + *pck_div = max(2, *pck_div); + else + *pck_div = max(3, *pck_div); + if (*pck_div > 255) { + /* FIXME: try to adjust logic clock divider as well */ + *pck_div = 255; + dev_warn(lcdc.fbdev->dev, "pixclock %d kHz too low.\n", + pck / 1000); + } +} + +static void inline setup_regs(void) +{ + u32 l; + struct lcd_panel *panel = lcdc.fbdev->panel; + int is_tft = panel->config & OMAP_LCDC_PANEL_TFT; + unsigned long lck; + int pcd; + + l = omap_readl(OMAP_LCDC_CONTROL); + l &= ~OMAP_LCDC_CTRL_LCD_TFT; + l |= is_tft ? OMAP_LCDC_CTRL_LCD_TFT : 0; +#ifdef CONFIG_MACH_OMAP_PALMTE +/* FIXME:if (machine_is_omap_palmte()) { */ + /* PalmTE uses alternate TFT setting in 8BPP mode */ + l |= (is_tft && panel->bpp == 8) ? 0x810000 : 0; +/* } */ +#endif + omap_writel(l, OMAP_LCDC_CONTROL); + + l = omap_readl(OMAP_LCDC_TIMING2); + l &= ~(((1 << 6) - 1) << 20); + l |= (panel->config & OMAP_LCDC_SIGNAL_MASK) << 20; + omap_writel(l, OMAP_LCDC_TIMING2); + + l = panel->x_res - 1; + l |= (panel->hsw - 1) << 10; + l |= (panel->hfp - 1) << 16; + l |= (panel->hbp - 1) << 24; + omap_writel(l, OMAP_LCDC_TIMING0); + + l = panel->y_res - 1; + l |= (panel->vsw - 1) << 10; + l |= panel->vfp << 16; + l |= panel->vbp << 24; + omap_writel(l, OMAP_LCDC_TIMING1); + + l = omap_readl(OMAP_LCDC_TIMING2); + l &= ~0xff; + + lck = clk_get_rate(lcdc.lcd_ck); + + if (!panel->pcd) + calc_ck_div(is_tft, panel->pixel_clock * 1000, &pcd); + else { + dev_warn(lcdc.fbdev->dev, + "Pixel clock divider value is obsolete.\n" + "Try to set pixel_clock to %lu and pcd to 0 " + "in drivers/video/omap/lcd_%s.c and submit a patch.\n", + lck / panel->pcd / 1000, panel->name); + + pcd = panel->pcd; + } + l |= pcd & 0xff; + l |= panel->acb << 8; + omap_writel(l, OMAP_LCDC_TIMING2); + + /* update panel info with the exact clock */ + panel->pixel_clock = lck / pcd / 1000; +} + +/* + * Configure the LCD controller, download the color palette and start a looped + * DMA transfer of the frame image data. Called only in internal + * controller mode. + */ +static int omap_lcdc_set_update_mode(enum omapfb_update_mode mode) +{ + int r = 0; + + if (mode != lcdc.update_mode) { + switch (mode) { + case OMAPFB_AUTO_UPDATE: + setup_regs(); + load_palette(); + + /* Setup and start LCD DMA */ + setup_lcd_dma(); + + set_load_mode(OMAP_LCDC_LOAD_FRAME); + enable_irqs(OMAP_LCDC_IRQ_DONE); + /* This will start the actual DMA transfer */ + enable_controller(); + lcdc.update_mode = mode; + break; + case OMAPFB_UPDATE_DISABLED: + disable_controller(); + omap_stop_lcd_dma(); + lcdc.update_mode = mode; + break; + default: + r = -EINVAL; + } + } + + return r; +} + +static enum omapfb_update_mode omap_lcdc_get_update_mode(void) +{ + return lcdc.update_mode; +} + +/* PM code called only in internal controller mode */ +static void omap_lcdc_suspend(void) +{ + omap_lcdc_set_update_mode(OMAPFB_UPDATE_DISABLED); +} + +static void omap_lcdc_resume(void) +{ + omap_lcdc_set_update_mode(OMAPFB_AUTO_UPDATE); +} + +static void omap_lcdc_get_caps(int plane, struct omapfb_caps *caps) +{ + return; +} + +int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data) +{ + BUG_ON(callback == NULL); + + if (lcdc.dma_callback) + return -EBUSY; + else { + lcdc.dma_callback = callback; + lcdc.dma_callback_data = data; + } + return 0; +} +EXPORT_SYMBOL(omap_lcdc_set_dma_callback); + +void omap_lcdc_free_dma_callback(void) +{ + lcdc.dma_callback = NULL; +} +EXPORT_SYMBOL(omap_lcdc_free_dma_callback); + +static void lcdc_dma_handler(u16 status, void *data) +{ + if (lcdc.dma_callback) + lcdc.dma_callback(lcdc.dma_callback_data); +} + +static int mmap_kern(void) +{ + struct vm_struct *kvma; + struct vm_area_struct vma; + pgprot_t pgprot; + unsigned long vaddr; + + kvma = get_vm_area(lcdc.vram_size, VM_IOREMAP); + if (kvma == NULL) { + dev_err(lcdc.fbdev->dev, "can't get kernel vm area\n"); + return -ENOMEM; + } + vma.vm_mm = &init_mm; + + vaddr = (unsigned long)kvma->addr; + vma.vm_start = vaddr; + vma.vm_end = vaddr + lcdc.vram_size; + + pgprot = pgprot_writecombine(pgprot_kernel); + if (io_remap_pfn_range(&vma, vaddr, + lcdc.vram_phys >> PAGE_SHIFT, + lcdc.vram_size, pgprot) < 0) { + dev_err(lcdc.fbdev->dev, "kernel mmap for FB memory failed\n"); + return -EAGAIN; + } + + lcdc.vram_virt = (void *)vaddr; + + return 0; +} + +static void unmap_kern(void) +{ + vunmap(lcdc.vram_virt); +} + +static int alloc_palette_ram(void) +{ + lcdc.palette_virt = dma_alloc_writecombine(lcdc.fbdev->dev, + MAX_PALETTE_SIZE, &lcdc.palette_phys, GFP_KERNEL); + if (lcdc.palette_virt == NULL) { + dev_err(lcdc.fbdev->dev, "failed to alloc palette memory\n"); + return -ENOMEM; + } + memset(lcdc.palette_virt, 0, MAX_PALETTE_SIZE); + + return 0; +} + +static void free_palette_ram(void) +{ + dma_free_writecombine(lcdc.fbdev->dev, MAX_PALETTE_SIZE, + lcdc.palette_virt, lcdc.palette_phys); +} + +static int alloc_fbmem(struct omapfb_mem_region *region) +{ + int bpp; + int frame_size; + struct lcd_panel *panel = lcdc.fbdev->panel; + + bpp = panel->bpp; + if (bpp == 12) + bpp = 16; + frame_size = PAGE_ALIGN(panel->x_res * bpp / 8 * panel->y_res); + if (region->size > frame_size) + frame_size = region->size; + lcdc.vram_size = frame_size; + lcdc.vram_virt = dma_alloc_writecombine(lcdc.fbdev->dev, + lcdc.vram_size, &lcdc.vram_phys, GFP_KERNEL); + if (lcdc.vram_virt == NULL) { + dev_err(lcdc.fbdev->dev, "unable to allocate FB DMA memory\n"); + return -ENOMEM; + } + region->size = frame_size; + region->paddr = lcdc.vram_phys; + region->vaddr = lcdc.vram_virt; + region->alloc = 1; + + memset(lcdc.vram_virt, 0, lcdc.vram_size); + + return 0; +} + +static void free_fbmem(void) +{ + dma_free_writecombine(lcdc.fbdev->dev, lcdc.vram_size, + lcdc.vram_virt, lcdc.vram_phys); +} + +static int setup_fbmem(struct omapfb_mem_desc *req_md) +{ + int r; + + if (!req_md->region_cnt) { + dev_err(lcdc.fbdev->dev, "no memory regions defined\n"); + return -EINVAL; + } + + if (req_md->region_cnt > 1) { + dev_err(lcdc.fbdev->dev, "only one plane is supported\n"); + req_md->region_cnt = 1; + } + + if (req_md->region[0].paddr == 0) { + lcdc.fbmem_allocated = 1; + if ((r = alloc_fbmem(&req_md->region[0])) < 0) + return r; + return 0; + } + + lcdc.vram_phys = req_md->region[0].paddr; + lcdc.vram_size = req_md->region[0].size; + + if ((r = mmap_kern()) < 0) + return r; + + dev_dbg(lcdc.fbdev->dev, "vram at %08x size %08lx mapped to 0x%p\n", + lcdc.vram_phys, lcdc.vram_size, lcdc.vram_virt); + + return 0; +} + +static void cleanup_fbmem(void) +{ + if (lcdc.fbmem_allocated) + free_fbmem(); + else + unmap_kern(); +} + +static int omap_lcdc_init(struct omapfb_device *fbdev, int ext_mode, + struct omapfb_mem_desc *req_vram) +{ + int r; + u32 l; + int rate; + struct clk *tc_ck; + + lcdc.irq_mask = 0; + + lcdc.fbdev = fbdev; + lcdc.ext_mode = ext_mode; + + l = 0; + omap_writel(l, OMAP_LCDC_CONTROL); + + /* FIXME: + * According to errata some platforms have a clock rate limitiation + */ + lcdc.lcd_ck = clk_get(fbdev->dev, "lcd_ck"); + if (IS_ERR(lcdc.lcd_ck)) { + dev_err(fbdev->dev, "unable to access LCD clock\n"); + r = PTR_ERR(lcdc.lcd_ck); + goto fail0; + } + + tc_ck = clk_get(fbdev->dev, "tc_ck"); + if (IS_ERR(tc_ck)) { + dev_err(fbdev->dev, "unable to access TC clock\n"); + r = PTR_ERR(tc_ck); + goto fail1; + } + + rate = clk_get_rate(tc_ck); + clk_put(tc_ck); + + if (machine_is_ams_delta()) + rate /= 4; + if (machine_is_omap_h3()) + rate /= 3; + r = clk_set_rate(lcdc.lcd_ck, rate); + if (r) { + dev_err(fbdev->dev, "failed to adjust LCD rate\n"); + goto fail1; + } + clk_enable(lcdc.lcd_ck); + + r = request_irq(OMAP_LCDC_IRQ, lcdc_irq_handler, 0, MODULE_NAME, fbdev); + if (r) { + dev_err(fbdev->dev, "unable to get IRQ\n"); + goto fail2; + } + + r = omap_request_lcd_dma(lcdc_dma_handler, NULL); + if (r) { + dev_err(fbdev->dev, "unable to get LCD DMA\n"); + goto fail3; + } + + omap_set_lcd_dma_single_transfer(ext_mode); + omap_set_lcd_dma_ext_controller(ext_mode); + + if (!ext_mode) + if ((r = alloc_palette_ram()) < 0) + goto fail4; + + if ((r = setup_fbmem(req_vram)) < 0) + goto fail5; + + pr_info("omapfb: LCDC initialized\n"); + + return 0; +fail5: + if (!ext_mode) + free_palette_ram(); +fail4: + omap_free_lcd_dma(); +fail3: + free_irq(OMAP_LCDC_IRQ, lcdc.fbdev); +fail2: + clk_disable(lcdc.lcd_ck); +fail1: + clk_put(lcdc.lcd_ck); +fail0: + return r; +} + +static void omap_lcdc_cleanup(void) +{ + if (!lcdc.ext_mode) + free_palette_ram(); + cleanup_fbmem(); + omap_free_lcd_dma(); + free_irq(OMAP_LCDC_IRQ, lcdc.fbdev); + clk_disable(lcdc.lcd_ck); + clk_put(lcdc.lcd_ck); +} + +const struct lcd_ctrl omap1_int_ctrl = { + .name = "internal", + .init = omap_lcdc_init, + .cleanup = omap_lcdc_cleanup, + .get_caps = omap_lcdc_get_caps, + .set_update_mode = omap_lcdc_set_update_mode, + .get_update_mode = omap_lcdc_get_update_mode, + .update_window = NULL, + .suspend = omap_lcdc_suspend, + .resume = omap_lcdc_resume, + .setup_plane = omap_lcdc_setup_plane, + .enable_plane = omap_lcdc_enable_plane, + .setcolreg = omap_lcdc_setcolreg, +}; diff --git a/drivers/video/fbdev/omap/lcdc.h b/drivers/video/fbdev/omap/lcdc.h new file mode 100644 index 000000000000..845222270db3 --- /dev/null +++ b/drivers/video/fbdev/omap/lcdc.h @@ -0,0 +1,9 @@ +#ifndef LCDC_H +#define LCDC_H + +int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data); +void omap_lcdc_free_dma_callback(void); + +extern const struct lcd_ctrl omap1_int_ctrl; + +#endif diff --git a/drivers/video/fbdev/omap/omapfb.h b/drivers/video/fbdev/omap/omapfb.h new file mode 100644 index 000000000000..2921d20e4fba --- /dev/null +++ b/drivers/video/fbdev/omap/omapfb.h @@ -0,0 +1,246 @@ +/* + * File: drivers/video/omap/omapfb.h + * + * Framebuffer driver for TI OMAP boards + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * 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. + */ + +#ifndef __OMAPFB_H +#define __OMAPFB_H + +#include <linux/fb.h> +#include <linux/mutex.h> +#include <linux/omapfb.h> + +#define OMAPFB_EVENT_READY 1 +#define OMAPFB_EVENT_DISABLED 2 + +#define OMAP_LCDC_INV_VSYNC 0x0001 +#define OMAP_LCDC_INV_HSYNC 0x0002 +#define OMAP_LCDC_INV_PIX_CLOCK 0x0004 +#define OMAP_LCDC_INV_OUTPUT_EN 0x0008 +#define OMAP_LCDC_HSVS_RISING_EDGE 0x0010 +#define OMAP_LCDC_HSVS_OPPOSITE 0x0020 + +#define OMAP_LCDC_SIGNAL_MASK 0x003f + +#define OMAP_LCDC_PANEL_TFT 0x0100 + +#define OMAPFB_PLANE_XRES_MIN 8 +#define OMAPFB_PLANE_YRES_MIN 8 + +struct omapfb_device; + +#define OMAPFB_PLANE_NUM 1 + +struct omapfb_mem_region { + u32 paddr; + void __iomem *vaddr; + unsigned long size; + u8 type; /* OMAPFB_PLANE_MEM_* */ + enum omapfb_color_format format;/* OMAPFB_COLOR_* */ + unsigned format_used:1; /* Must be set when format is set. + * Needed b/c of the badly chosen 0 + * base for OMAPFB_COLOR_* values + */ + unsigned alloc:1; /* allocated by the driver */ + unsigned map:1; /* kernel mapped by the driver */ +}; + +struct omapfb_mem_desc { + int region_cnt; + struct omapfb_mem_region region[OMAPFB_PLANE_NUM]; +}; + +struct lcd_panel { + const char *name; + int config; /* TFT/STN, signal inversion */ + int bpp; /* Pixel format in fb mem */ + int data_lines; /* Lines on LCD HW interface */ + + int x_res, y_res; + int pixel_clock; /* In kHz */ + int hsw; /* Horizontal synchronization + pulse width */ + int hfp; /* Horizontal front porch */ + int hbp; /* Horizontal back porch */ + int vsw; /* Vertical synchronization + pulse width */ + int vfp; /* Vertical front porch */ + int vbp; /* Vertical back porch */ + int acb; /* ac-bias pin frequency */ + int pcd; /* pixel clock divider. + Obsolete use pixel_clock instead */ + + int (*init) (struct lcd_panel *panel, + struct omapfb_device *fbdev); + void (*cleanup) (struct lcd_panel *panel); + int (*enable) (struct lcd_panel *panel); + void (*disable) (struct lcd_panel *panel); + unsigned long (*get_caps) (struct lcd_panel *panel); + int (*set_bklight_level)(struct lcd_panel *panel, + unsigned int level); + unsigned int (*get_bklight_level)(struct lcd_panel *panel); + unsigned int (*get_bklight_max) (struct lcd_panel *panel); + int (*run_test) (struct lcd_panel *panel, int test_num); +}; + +struct extif_timings { + int cs_on_time; + int cs_off_time; + int we_on_time; + int we_off_time; + int re_on_time; + int re_off_time; + int we_cycle_time; + int re_cycle_time; + int cs_pulse_width; + int access_time; + + int clk_div; + + u32 tim[5]; /* set by extif->convert_timings */ + + int converted; +}; + +struct lcd_ctrl_extif { + int (*init) (struct omapfb_device *fbdev); + void (*cleanup) (void); + void (*get_clk_info) (u32 *clk_period, u32 *max_clk_div); + unsigned long (*get_max_tx_rate)(void); + int (*convert_timings) (struct extif_timings *timings); + void (*set_timings) (const struct extif_timings *timings); + void (*set_bits_per_cycle)(int bpc); + void (*write_command) (const void *buf, unsigned int len); + void (*read_data) (void *buf, unsigned int len); + void (*write_data) (const void *buf, unsigned int len); + void (*transfer_area) (int width, int height, + void (callback)(void *data), void *data); + int (*setup_tearsync) (unsigned pin_cnt, + unsigned hs_pulse_time, unsigned vs_pulse_time, + int hs_pol_inv, int vs_pol_inv, int div); + int (*enable_tearsync) (int enable, unsigned line); + + unsigned long max_transmit_size; +}; + +struct omapfb_notifier_block { + struct notifier_block nb; + void *data; + int plane_idx; +}; + +typedef int (*omapfb_notifier_callback_t)(struct notifier_block *, + unsigned long event, + void *fbi); + +struct lcd_ctrl { + const char *name; + void *data; + + int (*init) (struct omapfb_device *fbdev, + int ext_mode, + struct omapfb_mem_desc *req_md); + void (*cleanup) (void); + void (*bind_client) (struct omapfb_notifier_block *nb); + void (*get_caps) (int plane, struct omapfb_caps *caps); + int (*set_update_mode)(enum omapfb_update_mode mode); + enum omapfb_update_mode (*get_update_mode)(void); + int (*setup_plane) (int plane, int channel_out, + unsigned long offset, + int screen_width, + int pos_x, int pos_y, int width, + int height, int color_mode); + int (*set_rotate) (int angle); + int (*setup_mem) (int plane, size_t size, + int mem_type, unsigned long *paddr); + int (*mmap) (struct fb_info *info, + struct vm_area_struct *vma); + int (*set_scale) (int plane, + int orig_width, int orig_height, + int out_width, int out_height); + int (*enable_plane) (int plane, int enable); + int (*update_window) (struct fb_info *fbi, + struct omapfb_update_window *win, + void (*callback)(void *), + void *callback_data); + void (*sync) (void); + void (*suspend) (void); + void (*resume) (void); + int (*run_test) (int test_num); + int (*setcolreg) (u_int regno, u16 red, u16 green, + u16 blue, u16 transp, + int update_hw_mem); + int (*set_color_key) (struct omapfb_color_key *ck); + int (*get_color_key) (struct omapfb_color_key *ck); +}; + +enum omapfb_state { + OMAPFB_DISABLED = 0, + OMAPFB_SUSPENDED = 99, + OMAPFB_ACTIVE = 100 +}; + +struct omapfb_plane_struct { + int idx; + struct omapfb_plane_info info; + enum omapfb_color_format color_mode; + struct omapfb_device *fbdev; +}; + +struct omapfb_device { + int state; + int ext_lcdc; /* Using external + LCD controller */ + struct mutex rqueue_mutex; + + int palette_size; + u32 pseudo_palette[17]; + + struct lcd_panel *panel; /* LCD panel */ + const struct lcd_ctrl *ctrl; /* LCD controller */ + const struct lcd_ctrl *int_ctrl; /* internal LCD ctrl */ + struct lcd_ctrl_extif *ext_if; /* LCD ctrl external + interface */ + struct device *dev; + struct fb_var_screeninfo new_var; /* for mode changes */ + + struct omapfb_mem_desc mem_desc; + struct fb_info *fb_info[OMAPFB_PLANE_NUM]; + + struct platform_device *dssdev; /* dummy dev for clocks */ +}; + +extern struct lcd_ctrl omap1_lcd_ctrl; + +extern void omapfb_register_panel(struct lcd_panel *panel); +extern void omapfb_write_first_pixel(struct omapfb_device *fbdev, u16 pixval); +extern void omapfb_notify_clients(struct omapfb_device *fbdev, + unsigned long event); +extern int omapfb_register_client(struct omapfb_notifier_block *nb, + omapfb_notifier_callback_t callback, + void *callback_data); +extern int omapfb_unregister_client(struct omapfb_notifier_block *nb); +extern int omapfb_update_window_async(struct fb_info *fbi, + struct omapfb_update_window *win, + void (*callback)(void *), + void *callback_data); + +#endif /* __OMAPFB_H */ diff --git a/drivers/video/fbdev/omap/omapfb_main.c b/drivers/video/fbdev/omap/omapfb_main.c new file mode 100644 index 000000000000..e4fc6d9b5371 --- /dev/null +++ b/drivers/video/fbdev/omap/omapfb_main.c @@ -0,0 +1,1971 @@ +/* + * Framebuffer driver for TI OMAP boards + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + * + * Acknowledgements: + * Alex McMains <aam@ridgerun.com> - Original driver + * Juha Yrjola <juha.yrjola@nokia.com> - Original driver and improvements + * Dirk Behme <dirk.behme@de.bosch.com> - changes for 2.6 kernel API + * Texas Instruments - H3 support + * + * 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/platform_device.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/module.h> + +#include <linux/omap-dma.h> + +#include <mach/hardware.h> + +#include "omapfb.h" +#include "lcdc.h" + +#define MODULE_NAME "omapfb" + +static unsigned int def_accel; +static unsigned long def_vram[OMAPFB_PLANE_NUM]; +static unsigned int def_vram_cnt; +static unsigned long def_vxres; +static unsigned long def_vyres; +static unsigned int def_rotate; +static unsigned int def_mirror; + +#ifdef CONFIG_FB_OMAP_MANUAL_UPDATE +static bool manual_update = 1; +#else +static bool manual_update; +#endif + +static struct platform_device *fbdev_pdev; +static struct lcd_panel *fbdev_panel; +static struct omapfb_device *omapfb_dev; + +struct caps_table_struct { + unsigned long flag; + const char *name; +}; + +static struct caps_table_struct ctrl_caps[] = { + { OMAPFB_CAPS_MANUAL_UPDATE, "manual update" }, + { OMAPFB_CAPS_TEARSYNC, "tearing synchronization" }, + { OMAPFB_CAPS_PLANE_RELOCATE_MEM, "relocate plane memory" }, + { OMAPFB_CAPS_PLANE_SCALE, "scale plane" }, + { OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE, "pixel double window" }, + { OMAPFB_CAPS_WINDOW_SCALE, "scale window" }, + { OMAPFB_CAPS_WINDOW_OVERLAY, "overlay window" }, + { OMAPFB_CAPS_WINDOW_ROTATE, "rotate window" }, + { OMAPFB_CAPS_SET_BACKLIGHT, "backlight setting" }, +}; + +static struct caps_table_struct color_caps[] = { + { 1 << OMAPFB_COLOR_RGB565, "RGB565", }, + { 1 << OMAPFB_COLOR_YUV422, "YUV422", }, + { 1 << OMAPFB_COLOR_YUV420, "YUV420", }, + { 1 << OMAPFB_COLOR_CLUT_8BPP, "CLUT8", }, + { 1 << OMAPFB_COLOR_CLUT_4BPP, "CLUT4", }, + { 1 << OMAPFB_COLOR_CLUT_2BPP, "CLUT2", }, + { 1 << OMAPFB_COLOR_CLUT_1BPP, "CLUT1", }, + { 1 << OMAPFB_COLOR_RGB444, "RGB444", }, + { 1 << OMAPFB_COLOR_YUY422, "YUY422", }, +}; + +static void omapdss_release(struct device *dev) +{ +} + +/* dummy device for clocks */ +static struct platform_device omapdss_device = { + .name = "omapdss_dss", + .id = -1, + .dev = { + .release = omapdss_release, + }, +}; + +/* + * --------------------------------------------------------------------------- + * LCD panel + * --------------------------------------------------------------------------- + */ +extern struct lcd_ctrl hwa742_ctrl; + +static const struct lcd_ctrl *ctrls[] = { + &omap1_int_ctrl, + +#ifdef CONFIG_FB_OMAP_LCDC_HWA742 + &hwa742_ctrl, +#endif +}; + +#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL +extern struct lcd_ctrl_extif omap1_ext_if; +#endif + +static void omapfb_rqueue_lock(struct omapfb_device *fbdev) +{ + mutex_lock(&fbdev->rqueue_mutex); +} + +static void omapfb_rqueue_unlock(struct omapfb_device *fbdev) +{ + mutex_unlock(&fbdev->rqueue_mutex); +} + +/* + * --------------------------------------------------------------------------- + * LCD controller and LCD DMA + * --------------------------------------------------------------------------- + */ +/* + * Allocate resources needed for LCD controller and LCD DMA operations. Video + * memory is allocated from system memory according to the virtual display + * size, except if a bigger memory size is specified explicitly as a kernel + * parameter. + */ +static int ctrl_init(struct omapfb_device *fbdev) +{ + int r; + int i; + + /* kernel/module vram parameters override boot tags/board config */ + if (def_vram_cnt) { + for (i = 0; i < def_vram_cnt; i++) + fbdev->mem_desc.region[i].size = + PAGE_ALIGN(def_vram[i]); + fbdev->mem_desc.region_cnt = i; + } + + if (!fbdev->mem_desc.region_cnt) { + struct lcd_panel *panel = fbdev->panel; + int def_size; + int bpp = panel->bpp; + + /* 12 bpp is packed in 16 bits */ + if (bpp == 12) + bpp = 16; + def_size = def_vxres * def_vyres * bpp / 8; + fbdev->mem_desc.region_cnt = 1; + fbdev->mem_desc.region[0].size = PAGE_ALIGN(def_size); + } + r = fbdev->ctrl->init(fbdev, 0, &fbdev->mem_desc); + if (r < 0) { + dev_err(fbdev->dev, "controller initialization failed (%d)\n", + r); + return r; + } + +#ifdef DEBUG + for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { + dev_dbg(fbdev->dev, "region%d phys %08x virt %p size=%lu\n", + i, + fbdev->mem_desc.region[i].paddr, + fbdev->mem_desc.region[i].vaddr, + fbdev->mem_desc.region[i].size); + } +#endif + return 0; +} + +static void ctrl_cleanup(struct omapfb_device *fbdev) +{ + fbdev->ctrl->cleanup(); +} + +/* Must be called with fbdev->rqueue_mutex held. */ +static int ctrl_change_mode(struct fb_info *fbi) +{ + int r; + unsigned long offset; + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct fb_var_screeninfo *var = &fbi->var; + + offset = var->yoffset * fbi->fix.line_length + + var->xoffset * var->bits_per_pixel / 8; + + if (fbdev->ctrl->sync) + fbdev->ctrl->sync(); + r = fbdev->ctrl->setup_plane(plane->idx, plane->info.channel_out, + offset, var->xres_virtual, + plane->info.pos_x, plane->info.pos_y, + var->xres, var->yres, plane->color_mode); + if (r < 0) + return r; + + if (fbdev->ctrl->set_rotate != NULL) { + r = fbdev->ctrl->set_rotate(var->rotate); + if (r < 0) + return r; + } + + if (fbdev->ctrl->set_scale != NULL) + r = fbdev->ctrl->set_scale(plane->idx, + var->xres, var->yres, + plane->info.out_width, + plane->info.out_height); + + return r; +} + +/* + * --------------------------------------------------------------------------- + * fbdev framework callbacks and the ioctl interface + * --------------------------------------------------------------------------- + */ +/* Called each time the omapfb device is opened */ +static int omapfb_open(struct fb_info *info, int user) +{ + return 0; +} + +static void omapfb_sync(struct fb_info *info); + +/* Called when the omapfb device is closed. We make sure that any pending + * gfx DMA operations are ended, before we return. */ +static int omapfb_release(struct fb_info *info, int user) +{ + omapfb_sync(info); + return 0; +} + +/* Store a single color palette entry into a pseudo palette or the hardware + * palette if one is available. For now we support only 16bpp and thus store + * the entry only to the pseudo palette. + */ +static int _setcolreg(struct fb_info *info, u_int regno, u_int red, u_int green, + u_int blue, u_int transp, int update_hw_pal) +{ + struct omapfb_plane_struct *plane = info->par; + struct omapfb_device *fbdev = plane->fbdev; + struct fb_var_screeninfo *var = &info->var; + int r = 0; + + switch (plane->color_mode) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUV420: + case OMAPFB_COLOR_YUY422: + r = -EINVAL; + break; + case OMAPFB_COLOR_CLUT_8BPP: + case OMAPFB_COLOR_CLUT_4BPP: + case OMAPFB_COLOR_CLUT_2BPP: + case OMAPFB_COLOR_CLUT_1BPP: + if (fbdev->ctrl->setcolreg) + r = fbdev->ctrl->setcolreg(regno, red, green, blue, + transp, update_hw_pal); + /* Fallthrough */ + case OMAPFB_COLOR_RGB565: + case OMAPFB_COLOR_RGB444: + if (r != 0) + break; + + if (regno < 0) { + r = -EINVAL; + break; + } + + if (regno < 16) { + u16 pal; + pal = ((red >> (16 - var->red.length)) << + var->red.offset) | + ((green >> (16 - var->green.length)) << + var->green.offset) | + (blue >> (16 - var->blue.length)); + ((u32 *)(info->pseudo_palette))[regno] = pal; + } + break; + default: + BUG(); + } + return r; +} + +static int omapfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + return _setcolreg(info, regno, red, green, blue, transp, 1); +} + +static int omapfb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int count, index, r; + u16 *red, *green, *blue, *transp; + u16 trans = 0xffff; + + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + index = cmap->start; + + for (count = 0; count < cmap->len; count++) { + if (transp) + trans = *transp++; + r = _setcolreg(info, index++, *red++, *green++, *blue++, trans, + count == cmap->len - 1); + if (r != 0) + return r; + } + + return 0; +} + +static int omapfb_update_full_screen(struct fb_info *fbi); + +static int omapfb_blank(int blank, struct fb_info *fbi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + int do_update = 0; + int r = 0; + + omapfb_rqueue_lock(fbdev); + switch (blank) { + case FB_BLANK_UNBLANK: + if (fbdev->state == OMAPFB_SUSPENDED) { + if (fbdev->ctrl->resume) + fbdev->ctrl->resume(); + fbdev->panel->enable(fbdev->panel); + fbdev->state = OMAPFB_ACTIVE; + if (fbdev->ctrl->get_update_mode() == + OMAPFB_MANUAL_UPDATE) + do_update = 1; + } + break; + case FB_BLANK_POWERDOWN: + if (fbdev->state == OMAPFB_ACTIVE) { + fbdev->panel->disable(fbdev->panel); + if (fbdev->ctrl->suspend) + fbdev->ctrl->suspend(); + fbdev->state = OMAPFB_SUSPENDED; + } + break; + default: + r = -EINVAL; + } + omapfb_rqueue_unlock(fbdev); + + if (r == 0 && do_update) + r = omapfb_update_full_screen(fbi); + + return r; +} + +static void omapfb_sync(struct fb_info *fbi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + + omapfb_rqueue_lock(fbdev); + if (fbdev->ctrl->sync) + fbdev->ctrl->sync(); + omapfb_rqueue_unlock(fbdev); +} + +/* + * Set fb_info.fix fields and also updates fbdev. + * When calling this fb_info.var must be set up already. + */ +static void set_fb_fix(struct fb_info *fbi, int from_init) +{ + struct fb_fix_screeninfo *fix = &fbi->fix; + struct fb_var_screeninfo *var = &fbi->var; + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_mem_region *rg; + int bpp; + + rg = &plane->fbdev->mem_desc.region[plane->idx]; + fbi->screen_base = rg->vaddr; + + if (!from_init) { + mutex_lock(&fbi->mm_lock); + fix->smem_start = rg->paddr; + fix->smem_len = rg->size; + mutex_unlock(&fbi->mm_lock); + } else { + fix->smem_start = rg->paddr; + fix->smem_len = rg->size; + } + + fix->type = FB_TYPE_PACKED_PIXELS; + bpp = var->bits_per_pixel; + if (var->nonstd) + fix->visual = FB_VISUAL_PSEUDOCOLOR; + else switch (var->bits_per_pixel) { + case 16: + case 12: + fix->visual = FB_VISUAL_TRUECOLOR; + /* 12bpp is stored in 16 bits */ + bpp = 16; + break; + case 1: + case 2: + case 4: + case 8: + fix->visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + fix->accel = FB_ACCEL_OMAP1610; + fix->line_length = var->xres_virtual * bpp / 8; +} + +static int set_color_mode(struct omapfb_plane_struct *plane, + struct fb_var_screeninfo *var) +{ + switch (var->nonstd) { + case 0: + break; + case OMAPFB_COLOR_YUV422: + var->bits_per_pixel = 16; + plane->color_mode = var->nonstd; + return 0; + case OMAPFB_COLOR_YUV420: + var->bits_per_pixel = 12; + plane->color_mode = var->nonstd; + return 0; + case OMAPFB_COLOR_YUY422: + var->bits_per_pixel = 16; + plane->color_mode = var->nonstd; + return 0; + default: + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 1: + plane->color_mode = OMAPFB_COLOR_CLUT_1BPP; + return 0; + case 2: + plane->color_mode = OMAPFB_COLOR_CLUT_2BPP; + return 0; + case 4: + plane->color_mode = OMAPFB_COLOR_CLUT_4BPP; + return 0; + case 8: + plane->color_mode = OMAPFB_COLOR_CLUT_8BPP; + return 0; + case 12: + var->bits_per_pixel = 16; + case 16: + if (plane->fbdev->panel->bpp == 12) + plane->color_mode = OMAPFB_COLOR_RGB444; + else + plane->color_mode = OMAPFB_COLOR_RGB565; + return 0; + default: + return -EINVAL; + } +} + +/* + * Check the values in var against our capabilities and in case of out of + * bound values try to adjust them. + */ +static int set_fb_var(struct fb_info *fbi, + struct fb_var_screeninfo *var) +{ + int bpp; + unsigned long max_frame_size; + unsigned long line_size; + int xres_min, xres_max; + int yres_min, yres_max; + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct lcd_panel *panel = fbdev->panel; + + if (set_color_mode(plane, var) < 0) + return -EINVAL; + + bpp = var->bits_per_pixel; + if (plane->color_mode == OMAPFB_COLOR_RGB444) + bpp = 16; + + switch (var->rotate) { + case 0: + case 180: + xres_min = OMAPFB_PLANE_XRES_MIN; + xres_max = panel->x_res; + yres_min = OMAPFB_PLANE_YRES_MIN; + yres_max = panel->y_res; + if (cpu_is_omap15xx()) { + var->xres = panel->x_res; + var->yres = panel->y_res; + } + break; + case 90: + case 270: + xres_min = OMAPFB_PLANE_YRES_MIN; + xres_max = panel->y_res; + yres_min = OMAPFB_PLANE_XRES_MIN; + yres_max = panel->x_res; + if (cpu_is_omap15xx()) { + var->xres = panel->y_res; + var->yres = panel->x_res; + } + break; + default: + return -EINVAL; + } + + if (var->xres < xres_min) + var->xres = xres_min; + if (var->yres < yres_min) + var->yres = yres_min; + if (var->xres > xres_max) + var->xres = xres_max; + if (var->yres > yres_max) + var->yres = yres_max; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + max_frame_size = fbdev->mem_desc.region[plane->idx].size; + line_size = var->xres_virtual * bpp / 8; + if (line_size * var->yres_virtual > max_frame_size) { + /* Try to keep yres_virtual first */ + line_size = max_frame_size / var->yres_virtual; + var->xres_virtual = line_size * 8 / bpp; + if (var->xres_virtual < var->xres) { + /* Still doesn't fit. Shrink yres_virtual too */ + var->xres_virtual = var->xres; + line_size = var->xres * bpp / 8; + var->yres_virtual = max_frame_size / line_size; + } + /* Recheck this, as the virtual size changed. */ + if (var->xres_virtual < var->xres) + var->xres = var->xres_virtual; + if (var->yres_virtual < var->yres) + var->yres = var->yres_virtual; + if (var->xres < xres_min || var->yres < yres_min) + return -EINVAL; + } + if (var->xres + var->xoffset > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yres + var->yoffset > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + if (plane->color_mode == OMAPFB_COLOR_RGB444) { + var->red.offset = 8; var->red.length = 4; + var->red.msb_right = 0; + var->green.offset = 4; var->green.length = 4; + var->green.msb_right = 0; + var->blue.offset = 0; var->blue.length = 4; + var->blue.msb_right = 0; + } else { + var->red.offset = 11; var->red.length = 5; + var->red.msb_right = 0; + var->green.offset = 5; var->green.length = 6; + var->green.msb_right = 0; + var->blue.offset = 0; var->blue.length = 5; + var->blue.msb_right = 0; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* pixclock in ps, the rest in pixclock */ + var->pixclock = 10000000 / (panel->pixel_clock / 100); + var->left_margin = panel->hfp; + var->right_margin = panel->hbp; + var->upper_margin = panel->vfp; + var->lower_margin = panel->vbp; + var->hsync_len = panel->hsw; + var->vsync_len = panel->vsw; + + /* TODO: get these from panel->config */ + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + + +/* Set rotation (0, 90, 180, 270 degree), and switch to the new mode. */ +static void omapfb_rotate(struct fb_info *fbi, int rotate) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + + omapfb_rqueue_lock(fbdev); + if (rotate != fbi->var.rotate) { + struct fb_var_screeninfo *new_var = &fbdev->new_var; + + memcpy(new_var, &fbi->var, sizeof(*new_var)); + new_var->rotate = rotate; + if (set_fb_var(fbi, new_var) == 0 && + memcmp(new_var, &fbi->var, sizeof(*new_var))) { + memcpy(&fbi->var, new_var, sizeof(*new_var)); + ctrl_change_mode(fbi); + } + } + omapfb_rqueue_unlock(fbdev); +} + +/* + * Set new x,y offsets in the virtual display for the visible area and switch + * to the new mode. + */ +static int omapfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + int r = 0; + + omapfb_rqueue_lock(fbdev); + if (var->xoffset != fbi->var.xoffset || + var->yoffset != fbi->var.yoffset) { + struct fb_var_screeninfo *new_var = &fbdev->new_var; + + memcpy(new_var, &fbi->var, sizeof(*new_var)); + new_var->xoffset = var->xoffset; + new_var->yoffset = var->yoffset; + if (set_fb_var(fbi, new_var)) + r = -EINVAL; + else { + memcpy(&fbi->var, new_var, sizeof(*new_var)); + ctrl_change_mode(fbi); + } + } + omapfb_rqueue_unlock(fbdev); + + return r; +} + +/* Set mirror to vertical axis and switch to the new mode. */ +static int omapfb_mirror(struct fb_info *fbi, int mirror) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + int r = 0; + + omapfb_rqueue_lock(fbdev); + mirror = mirror ? 1 : 0; + if (cpu_is_omap15xx()) + r = -EINVAL; + else if (mirror != plane->info.mirror) { + plane->info.mirror = mirror; + r = ctrl_change_mode(fbi); + } + omapfb_rqueue_unlock(fbdev); + + return r; +} + +/* + * Check values in var, try to adjust them in case of out of bound values if + * possible, or return error. + */ +static int omapfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + int r; + + omapfb_rqueue_lock(fbdev); + if (fbdev->ctrl->sync != NULL) + fbdev->ctrl->sync(); + r = set_fb_var(fbi, var); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +/* + * Switch to a new mode. The parameters for it has been check already by + * omapfb_check_var. + */ +static int omapfb_set_par(struct fb_info *fbi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + int r = 0; + + omapfb_rqueue_lock(fbdev); + set_fb_fix(fbi, 0); + r = ctrl_change_mode(fbi); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +int omapfb_update_window_async(struct fb_info *fbi, + struct omapfb_update_window *win, + void (*callback)(void *), + void *callback_data) +{ + int xres, yres; + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct fb_var_screeninfo *var = &fbi->var; + + switch (var->rotate) { + case 0: + case 180: + xres = fbdev->panel->x_res; + yres = fbdev->panel->y_res; + break; + case 90: + case 270: + xres = fbdev->panel->y_res; + yres = fbdev->panel->x_res; + break; + default: + return -EINVAL; + } + + if (win->x >= xres || win->y >= yres || + win->out_x > xres || win->out_y > yres) + return -EINVAL; + + if (!fbdev->ctrl->update_window || + fbdev->ctrl->get_update_mode() != OMAPFB_MANUAL_UPDATE) + return -ENODEV; + + if (win->x + win->width > xres) + win->width = xres - win->x; + if (win->y + win->height > yres) + win->height = yres - win->y; + if (win->out_x + win->out_width > xres) + win->out_width = xres - win->out_x; + if (win->out_y + win->out_height > yres) + win->out_height = yres - win->out_y; + if (!win->width || !win->height || !win->out_width || !win->out_height) + return 0; + + return fbdev->ctrl->update_window(fbi, win, callback, callback_data); +} +EXPORT_SYMBOL(omapfb_update_window_async); + +static int omapfb_update_win(struct fb_info *fbi, + struct omapfb_update_window *win) +{ + struct omapfb_plane_struct *plane = fbi->par; + int ret; + + omapfb_rqueue_lock(plane->fbdev); + ret = omapfb_update_window_async(fbi, win, NULL, NULL); + omapfb_rqueue_unlock(plane->fbdev); + + return ret; +} + +static int omapfb_update_full_screen(struct fb_info *fbi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct omapfb_update_window win; + int r; + + if (!fbdev->ctrl->update_window || + fbdev->ctrl->get_update_mode() != OMAPFB_MANUAL_UPDATE) + return -ENODEV; + + win.x = 0; + win.y = 0; + win.width = fbi->var.xres; + win.height = fbi->var.yres; + win.out_x = 0; + win.out_y = 0; + win.out_width = fbi->var.xres; + win.out_height = fbi->var.yres; + win.format = 0; + + omapfb_rqueue_lock(fbdev); + r = fbdev->ctrl->update_window(fbi, &win, NULL, NULL); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +static int omapfb_setup_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct lcd_panel *panel = fbdev->panel; + struct omapfb_plane_info old_info; + int r = 0; + + if (pi->pos_x + pi->out_width > panel->x_res || + pi->pos_y + pi->out_height > panel->y_res) + return -EINVAL; + + omapfb_rqueue_lock(fbdev); + if (pi->enabled && !fbdev->mem_desc.region[plane->idx].size) { + /* + * This plane's memory was freed, can't enable it + * until it's reallocated. + */ + r = -EINVAL; + goto out; + } + old_info = plane->info; + plane->info = *pi; + if (pi->enabled) { + r = ctrl_change_mode(fbi); + if (r < 0) { + plane->info = old_info; + goto out; + } + } + r = fbdev->ctrl->enable_plane(plane->idx, pi->enabled); + if (r < 0) { + plane->info = old_info; + goto out; + } +out: + omapfb_rqueue_unlock(fbdev); + return r; +} + +static int omapfb_query_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) +{ + struct omapfb_plane_struct *plane = fbi->par; + + *pi = plane->info; + return 0; +} + +static int omapfb_setup_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct omapfb_mem_region *rg = &fbdev->mem_desc.region[plane->idx]; + size_t size; + int r = 0; + + if (fbdev->ctrl->setup_mem == NULL) + return -ENODEV; + if (mi->type != OMAPFB_MEMTYPE_SDRAM) + return -EINVAL; + + size = PAGE_ALIGN(mi->size); + omapfb_rqueue_lock(fbdev); + if (plane->info.enabled) { + r = -EBUSY; + goto out; + } + if (rg->size != size || rg->type != mi->type) { + struct fb_var_screeninfo *new_var = &fbdev->new_var; + unsigned long old_size = rg->size; + u8 old_type = rg->type; + unsigned long paddr; + + rg->size = size; + rg->type = mi->type; + /* + * size == 0 is a special case, for which we + * don't check / adjust the screen parameters. + * This isn't a problem since the plane can't + * be reenabled unless its size is > 0. + */ + if (old_size != size && size) { + if (size) { + memcpy(new_var, &fbi->var, sizeof(*new_var)); + r = set_fb_var(fbi, new_var); + if (r < 0) + goto out; + } + } + + if (fbdev->ctrl->sync) + fbdev->ctrl->sync(); + r = fbdev->ctrl->setup_mem(plane->idx, size, mi->type, &paddr); + if (r < 0) { + /* Revert changes. */ + rg->size = old_size; + rg->type = old_type; + goto out; + } + rg->paddr = paddr; + + if (old_size != size) { + if (size) { + memcpy(&fbi->var, new_var, sizeof(fbi->var)); + set_fb_fix(fbi, 0); + } else { + /* + * Set these explicitly to indicate that the + * plane memory is dealloce'd, the other + * screen parameters in var / fix are invalid. + */ + mutex_lock(&fbi->mm_lock); + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + mutex_unlock(&fbi->mm_lock); + } + } + } +out: + omapfb_rqueue_unlock(fbdev); + + return r; +} + +static int omapfb_query_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct omapfb_mem_region *rg; + + rg = &fbdev->mem_desc.region[plane->idx]; + memset(mi, 0, sizeof(*mi)); + mi->size = rg->size; + mi->type = rg->type; + + return 0; +} + +static int omapfb_set_color_key(struct omapfb_device *fbdev, + struct omapfb_color_key *ck) +{ + int r; + + if (!fbdev->ctrl->set_color_key) + return -ENODEV; + + omapfb_rqueue_lock(fbdev); + r = fbdev->ctrl->set_color_key(ck); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +static int omapfb_get_color_key(struct omapfb_device *fbdev, + struct omapfb_color_key *ck) +{ + int r; + + if (!fbdev->ctrl->get_color_key) + return -ENODEV; + + omapfb_rqueue_lock(fbdev); + r = fbdev->ctrl->get_color_key(ck); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +static struct blocking_notifier_head omapfb_client_list[OMAPFB_PLANE_NUM]; +static int notifier_inited; + +static void omapfb_init_notifier(void) +{ + int i; + + for (i = 0; i < OMAPFB_PLANE_NUM; i++) + BLOCKING_INIT_NOTIFIER_HEAD(&omapfb_client_list[i]); +} + +int omapfb_register_client(struct omapfb_notifier_block *omapfb_nb, + omapfb_notifier_callback_t callback, + void *callback_data) +{ + int r; + + if ((unsigned)omapfb_nb->plane_idx > OMAPFB_PLANE_NUM) + return -EINVAL; + + if (!notifier_inited) { + omapfb_init_notifier(); + notifier_inited = 1; + } + + omapfb_nb->nb.notifier_call = (int (*)(struct notifier_block *, + unsigned long, void *))callback; + omapfb_nb->data = callback_data; + r = blocking_notifier_chain_register( + &omapfb_client_list[omapfb_nb->plane_idx], + &omapfb_nb->nb); + if (r) + return r; + if (omapfb_dev != NULL && + omapfb_dev->ctrl && omapfb_dev->ctrl->bind_client) { + omapfb_dev->ctrl->bind_client(omapfb_nb); + } + + return 0; +} +EXPORT_SYMBOL(omapfb_register_client); + +int omapfb_unregister_client(struct omapfb_notifier_block *omapfb_nb) +{ + return blocking_notifier_chain_unregister( + &omapfb_client_list[omapfb_nb->plane_idx], &omapfb_nb->nb); +} +EXPORT_SYMBOL(omapfb_unregister_client); + +void omapfb_notify_clients(struct omapfb_device *fbdev, unsigned long event) +{ + int i; + + if (!notifier_inited) + /* no client registered yet */ + return; + + for (i = 0; i < OMAPFB_PLANE_NUM; i++) + blocking_notifier_call_chain(&omapfb_client_list[i], event, + fbdev->fb_info[i]); +} +EXPORT_SYMBOL(omapfb_notify_clients); + +static int omapfb_set_update_mode(struct omapfb_device *fbdev, + enum omapfb_update_mode mode) +{ + int r; + + omapfb_rqueue_lock(fbdev); + r = fbdev->ctrl->set_update_mode(mode); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +static enum omapfb_update_mode omapfb_get_update_mode(struct omapfb_device *fbdev) +{ + int r; + + omapfb_rqueue_lock(fbdev); + r = fbdev->ctrl->get_update_mode(); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +static void omapfb_get_caps(struct omapfb_device *fbdev, int plane, + struct omapfb_caps *caps) +{ + memset(caps, 0, sizeof(*caps)); + fbdev->ctrl->get_caps(plane, caps); + caps->ctrl |= fbdev->panel->get_caps(fbdev->panel); +} + +/* For lcd testing */ +void omapfb_write_first_pixel(struct omapfb_device *fbdev, u16 pixval) +{ + omapfb_rqueue_lock(fbdev); + *(u16 *)fbdev->mem_desc.region[0].vaddr = pixval; + if (fbdev->ctrl->get_update_mode() == OMAPFB_MANUAL_UPDATE) { + struct omapfb_update_window win; + + memset(&win, 0, sizeof(win)); + win.width = 2; + win.height = 2; + win.out_width = 2; + win.out_height = 2; + fbdev->ctrl->update_window(fbdev->fb_info[0], &win, NULL, NULL); + } + omapfb_rqueue_unlock(fbdev); +} +EXPORT_SYMBOL(omapfb_write_first_pixel); + +/* + * Ioctl interface. Part of the kernel mode frame buffer API is duplicated + * here to be accessible by user mode code. + */ +static int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg) +{ + struct omapfb_plane_struct *plane = fbi->par; + struct omapfb_device *fbdev = plane->fbdev; + struct fb_ops *ops = fbi->fbops; + union { + struct omapfb_update_window update_window; + struct omapfb_plane_info plane_info; + struct omapfb_mem_info mem_info; + struct omapfb_color_key color_key; + enum omapfb_update_mode update_mode; + struct omapfb_caps caps; + unsigned int mirror; + int plane_out; + int enable_plane; + } p; + int r = 0; + + BUG_ON(!ops); + switch (cmd) { + case OMAPFB_MIRROR: + if (get_user(p.mirror, (int __user *)arg)) + r = -EFAULT; + else + omapfb_mirror(fbi, p.mirror); + break; + case OMAPFB_SYNC_GFX: + omapfb_sync(fbi); + break; + case OMAPFB_VSYNC: + break; + case OMAPFB_SET_UPDATE_MODE: + if (get_user(p.update_mode, (int __user *)arg)) + r = -EFAULT; + else + r = omapfb_set_update_mode(fbdev, p.update_mode); + break; + case OMAPFB_GET_UPDATE_MODE: + p.update_mode = omapfb_get_update_mode(fbdev); + if (put_user(p.update_mode, + (enum omapfb_update_mode __user *)arg)) + r = -EFAULT; + break; + case OMAPFB_UPDATE_WINDOW_OLD: + if (copy_from_user(&p.update_window, (void __user *)arg, + sizeof(struct omapfb_update_window_old))) + r = -EFAULT; + else { + struct omapfb_update_window *u = &p.update_window; + u->out_x = u->x; + u->out_y = u->y; + u->out_width = u->width; + u->out_height = u->height; + memset(u->reserved, 0, sizeof(u->reserved)); + r = omapfb_update_win(fbi, u); + } + break; + case OMAPFB_UPDATE_WINDOW: + if (copy_from_user(&p.update_window, (void __user *)arg, + sizeof(p.update_window))) + r = -EFAULT; + else + r = omapfb_update_win(fbi, &p.update_window); + break; + case OMAPFB_SETUP_PLANE: + if (copy_from_user(&p.plane_info, (void __user *)arg, + sizeof(p.plane_info))) + r = -EFAULT; + else + r = omapfb_setup_plane(fbi, &p.plane_info); + break; + case OMAPFB_QUERY_PLANE: + if ((r = omapfb_query_plane(fbi, &p.plane_info)) < 0) + break; + if (copy_to_user((void __user *)arg, &p.plane_info, + sizeof(p.plane_info))) + r = -EFAULT; + break; + case OMAPFB_SETUP_MEM: + if (copy_from_user(&p.mem_info, (void __user *)arg, + sizeof(p.mem_info))) + r = -EFAULT; + else + r = omapfb_setup_mem(fbi, &p.mem_info); + break; + case OMAPFB_QUERY_MEM: + if ((r = omapfb_query_mem(fbi, &p.mem_info)) < 0) + break; + if (copy_to_user((void __user *)arg, &p.mem_info, + sizeof(p.mem_info))) + r = -EFAULT; + break; + case OMAPFB_SET_COLOR_KEY: + if (copy_from_user(&p.color_key, (void __user *)arg, + sizeof(p.color_key))) + r = -EFAULT; + else + r = omapfb_set_color_key(fbdev, &p.color_key); + break; + case OMAPFB_GET_COLOR_KEY: + if ((r = omapfb_get_color_key(fbdev, &p.color_key)) < 0) + break; + if (copy_to_user((void __user *)arg, &p.color_key, + sizeof(p.color_key))) + r = -EFAULT; + break; + case OMAPFB_GET_CAPS: + omapfb_get_caps(fbdev, plane->idx, &p.caps); + if (copy_to_user((void __user *)arg, &p.caps, sizeof(p.caps))) + r = -EFAULT; + break; + case OMAPFB_LCD_TEST: + { + int test_num; + + if (get_user(test_num, (int __user *)arg)) { + r = -EFAULT; + break; + } + if (!fbdev->panel->run_test) { + r = -EINVAL; + break; + } + r = fbdev->panel->run_test(fbdev->panel, test_num); + break; + } + case OMAPFB_CTRL_TEST: + { + int test_num; + + if (get_user(test_num, (int __user *)arg)) { + r = -EFAULT; + break; + } + if (!fbdev->ctrl->run_test) { + r = -EINVAL; + break; + } + r = fbdev->ctrl->run_test(test_num); + break; + } + default: + r = -EINVAL; + } + + return r; +} + +static int omapfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct omapfb_plane_struct *plane = info->par; + struct omapfb_device *fbdev = plane->fbdev; + int r; + + omapfb_rqueue_lock(fbdev); + r = fbdev->ctrl->mmap(info, vma); + omapfb_rqueue_unlock(fbdev); + + return r; +} + +/* + * Callback table for the frame buffer framework. Some of these pointers + * will be changed according to the current setting of fb_info->accel_flags. + */ +static struct fb_ops omapfb_ops = { + .owner = THIS_MODULE, + .fb_open = omapfb_open, + .fb_release = omapfb_release, + .fb_setcolreg = omapfb_setcolreg, + .fb_setcmap = omapfb_setcmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = omapfb_blank, + .fb_ioctl = omapfb_ioctl, + .fb_check_var = omapfb_check_var, + .fb_set_par = omapfb_set_par, + .fb_rotate = omapfb_rotate, + .fb_pan_display = omapfb_pan_display, +}; + +/* + * --------------------------------------------------------------------------- + * Sysfs interface + * --------------------------------------------------------------------------- + */ +/* omapfbX sysfs entries */ +static ssize_t omapfb_show_caps_num(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + int plane; + size_t size; + struct omapfb_caps caps; + + plane = 0; + size = 0; + while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) { + omapfb_get_caps(fbdev, plane, &caps); + size += snprintf(&buf[size], PAGE_SIZE - size, + "plane#%d %#010x %#010x %#010x\n", + plane, caps.ctrl, caps.plane_color, caps.wnd_color); + plane++; + } + return size; +} + +static ssize_t omapfb_show_caps_text(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + int i; + struct omapfb_caps caps; + int plane; + size_t size; + + plane = 0; + size = 0; + while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) { + omapfb_get_caps(fbdev, plane, &caps); + size += snprintf(&buf[size], PAGE_SIZE - size, + "plane#%d:\n", plane); + for (i = 0; i < ARRAY_SIZE(ctrl_caps) && + size < PAGE_SIZE; i++) { + if (ctrl_caps[i].flag & caps.ctrl) + size += snprintf(&buf[size], PAGE_SIZE - size, + " %s\n", ctrl_caps[i].name); + } + size += snprintf(&buf[size], PAGE_SIZE - size, + " plane colors:\n"); + for (i = 0; i < ARRAY_SIZE(color_caps) && + size < PAGE_SIZE; i++) { + if (color_caps[i].flag & caps.plane_color) + size += snprintf(&buf[size], PAGE_SIZE - size, + " %s\n", color_caps[i].name); + } + size += snprintf(&buf[size], PAGE_SIZE - size, + " window colors:\n"); + for (i = 0; i < ARRAY_SIZE(color_caps) && + size < PAGE_SIZE; i++) { + if (color_caps[i].flag & caps.wnd_color) + size += snprintf(&buf[size], PAGE_SIZE - size, + " %s\n", color_caps[i].name); + } + + plane++; + } + return size; +} + +static DEVICE_ATTR(caps_num, 0444, omapfb_show_caps_num, NULL); +static DEVICE_ATTR(caps_text, 0444, omapfb_show_caps_text, NULL); + +/* panel sysfs entries */ +static ssize_t omapfb_show_panel_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", fbdev->panel->name); +} + +static ssize_t omapfb_show_bklight_level(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + int r; + + if (fbdev->panel->get_bklight_level) { + r = snprintf(buf, PAGE_SIZE, "%d\n", + fbdev->panel->get_bklight_level(fbdev->panel)); + } else + r = -ENODEV; + return r; +} + +static ssize_t omapfb_store_bklight_level(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + int r; + + if (fbdev->panel->set_bklight_level) { + unsigned int level; + + if (sscanf(buf, "%10d", &level) == 1) { + r = fbdev->panel->set_bklight_level(fbdev->panel, + level); + } else + r = -EINVAL; + } else + r = -ENODEV; + return r ? r : size; +} + +static ssize_t omapfb_show_bklight_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + int r; + + if (fbdev->panel->get_bklight_level) { + r = snprintf(buf, PAGE_SIZE, "%d\n", + fbdev->panel->get_bklight_max(fbdev->panel)); + } else + r = -ENODEV; + return r; +} + +static struct device_attribute dev_attr_panel_name = + __ATTR(name, 0444, omapfb_show_panel_name, NULL); +static DEVICE_ATTR(backlight_level, 0664, + omapfb_show_bklight_level, omapfb_store_bklight_level); +static DEVICE_ATTR(backlight_max, 0444, omapfb_show_bklight_max, NULL); + +static struct attribute *panel_attrs[] = { + &dev_attr_panel_name.attr, + &dev_attr_backlight_level.attr, + &dev_attr_backlight_max.attr, + NULL, +}; + +static struct attribute_group panel_attr_grp = { + .name = "panel", + .attrs = panel_attrs, +}; + +/* ctrl sysfs entries */ +static ssize_t omapfb_show_ctrl_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", fbdev->ctrl->name); +} + +static struct device_attribute dev_attr_ctrl_name = + __ATTR(name, 0444, omapfb_show_ctrl_name, NULL); + +static struct attribute *ctrl_attrs[] = { + &dev_attr_ctrl_name.attr, + NULL, +}; + +static struct attribute_group ctrl_attr_grp = { + .name = "ctrl", + .attrs = ctrl_attrs, +}; + +static int omapfb_register_sysfs(struct omapfb_device *fbdev) +{ + int r; + + if ((r = device_create_file(fbdev->dev, &dev_attr_caps_num))) + goto fail0; + + if ((r = device_create_file(fbdev->dev, &dev_attr_caps_text))) + goto fail1; + + if ((r = sysfs_create_group(&fbdev->dev->kobj, &panel_attr_grp))) + goto fail2; + + if ((r = sysfs_create_group(&fbdev->dev->kobj, &ctrl_attr_grp))) + goto fail3; + + return 0; +fail3: + sysfs_remove_group(&fbdev->dev->kobj, &panel_attr_grp); +fail2: + device_remove_file(fbdev->dev, &dev_attr_caps_text); +fail1: + device_remove_file(fbdev->dev, &dev_attr_caps_num); +fail0: + dev_err(fbdev->dev, "unable to register sysfs interface\n"); + return r; +} + +static void omapfb_unregister_sysfs(struct omapfb_device *fbdev) +{ + sysfs_remove_group(&fbdev->dev->kobj, &ctrl_attr_grp); + sysfs_remove_group(&fbdev->dev->kobj, &panel_attr_grp); + device_remove_file(fbdev->dev, &dev_attr_caps_num); + device_remove_file(fbdev->dev, &dev_attr_caps_text); +} + +/* + * --------------------------------------------------------------------------- + * LDM callbacks + * --------------------------------------------------------------------------- + */ +/* Initialize system fb_info object and set the default video mode. + * The frame buffer memory already allocated by lcddma_init + */ +static int fbinfo_init(struct omapfb_device *fbdev, struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct fb_fix_screeninfo *fix = &info->fix; + int r = 0; + + info->fbops = &omapfb_ops; + info->flags = FBINFO_FLAG_DEFAULT; + + strncpy(fix->id, MODULE_NAME, sizeof(fix->id)); + + info->pseudo_palette = fbdev->pseudo_palette; + + var->accel_flags = def_accel ? FB_ACCELF_TEXT : 0; + var->xres = def_vxres; + var->yres = def_vyres; + var->xres_virtual = def_vxres; + var->yres_virtual = def_vyres; + var->rotate = def_rotate; + var->bits_per_pixel = fbdev->panel->bpp; + + set_fb_var(info, var); + set_fb_fix(info, 1); + + r = fb_alloc_cmap(&info->cmap, 16, 0); + if (r != 0) + dev_err(fbdev->dev, "unable to allocate color map memory\n"); + + return r; +} + +/* Release the fb_info object */ +static void fbinfo_cleanup(struct omapfb_device *fbdev, struct fb_info *fbi) +{ + fb_dealloc_cmap(&fbi->cmap); +} + +static void planes_cleanup(struct omapfb_device *fbdev) +{ + int i; + + for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { + if (fbdev->fb_info[i] == NULL) + break; + fbinfo_cleanup(fbdev, fbdev->fb_info[i]); + framebuffer_release(fbdev->fb_info[i]); + } +} + +static int planes_init(struct omapfb_device *fbdev) +{ + struct fb_info *fbi; + int i; + int r; + + for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { + struct omapfb_plane_struct *plane; + fbi = framebuffer_alloc(sizeof(struct omapfb_plane_struct), + fbdev->dev); + if (fbi == NULL) { + dev_err(fbdev->dev, + "unable to allocate memory for plane info\n"); + planes_cleanup(fbdev); + return -ENOMEM; + } + plane = fbi->par; + plane->idx = i; + plane->fbdev = fbdev; + plane->info.mirror = def_mirror; + fbdev->fb_info[i] = fbi; + + if ((r = fbinfo_init(fbdev, fbi)) < 0) { + framebuffer_release(fbi); + planes_cleanup(fbdev); + return r; + } + plane->info.out_width = fbi->var.xres; + plane->info.out_height = fbi->var.yres; + } + return 0; +} + +/* + * Free driver resources. Can be called to rollback an aborted initialization + * sequence. + */ +static void omapfb_free_resources(struct omapfb_device *fbdev, int state) +{ + int i; + + switch (state) { + case OMAPFB_ACTIVE: + for (i = 0; i < fbdev->mem_desc.region_cnt; i++) + unregister_framebuffer(fbdev->fb_info[i]); + case 7: + omapfb_unregister_sysfs(fbdev); + case 6: + fbdev->panel->disable(fbdev->panel); + case 5: + omapfb_set_update_mode(fbdev, OMAPFB_UPDATE_DISABLED); + case 4: + planes_cleanup(fbdev); + case 3: + ctrl_cleanup(fbdev); + case 2: + fbdev->panel->cleanup(fbdev->panel); + case 1: + dev_set_drvdata(fbdev->dev, NULL); + kfree(fbdev); + case 0: + /* nothing to free */ + break; + default: + BUG(); + } +} + +static int omapfb_find_ctrl(struct omapfb_device *fbdev) +{ + struct omapfb_platform_data *conf; + char name[17]; + int i; + + conf = dev_get_platdata(fbdev->dev); + + fbdev->ctrl = NULL; + + strncpy(name, conf->lcd.ctrl_name, sizeof(name) - 1); + name[sizeof(name) - 1] = '\0'; + + if (strcmp(name, "internal") == 0) { + fbdev->ctrl = fbdev->int_ctrl; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(ctrls); i++) { + dev_dbg(fbdev->dev, "ctrl %s\n", ctrls[i]->name); + if (strcmp(ctrls[i]->name, name) == 0) { + fbdev->ctrl = ctrls[i]; + break; + } + } + + if (fbdev->ctrl == NULL) { + dev_dbg(fbdev->dev, "ctrl %s not supported\n", name); + return -1; + } + + return 0; +} + +static void check_required_callbacks(struct omapfb_device *fbdev) +{ +#define _C(x) (fbdev->ctrl->x != NULL) +#define _P(x) (fbdev->panel->x != NULL) + BUG_ON(fbdev->ctrl == NULL || fbdev->panel == NULL); + BUG_ON(!(_C(init) && _C(cleanup) && _C(get_caps) && + _C(set_update_mode) && _C(setup_plane) && _C(enable_plane) && + _P(init) && _P(cleanup) && _P(enable) && _P(disable) && + _P(get_caps))); +#undef _P +#undef _C +} + +/* + * Called by LDM binding to probe and attach a new device. + * Initialization sequence: + * 1. allocate system omapfb_device structure + * 2. select controller type according to platform configuration + * init LCD panel + * 3. init LCD controller and LCD DMA + * 4. init system fb_info structure for all planes + * 5. setup video mode for first plane and enable it + * 6. enable LCD panel + * 7. register sysfs attributes + * OMAPFB_ACTIVE: register system fb_info structure for all planes + */ +static int omapfb_do_probe(struct platform_device *pdev, + struct lcd_panel *panel) +{ + struct omapfb_device *fbdev = NULL; + int init_state; + unsigned long phz, hhz, vhz; + unsigned long vram; + int i; + int r = 0; + + init_state = 0; + + if (pdev->num_resources != 0) { + dev_err(&pdev->dev, "probed for an unknown device\n"); + r = -ENODEV; + goto cleanup; + } + + if (dev_get_platdata(&pdev->dev) == NULL) { + dev_err(&pdev->dev, "missing platform data\n"); + r = -ENOENT; + goto cleanup; + } + + fbdev = kzalloc(sizeof(struct omapfb_device), GFP_KERNEL); + if (fbdev == NULL) { + dev_err(&pdev->dev, + "unable to allocate memory for device info\n"); + r = -ENOMEM; + goto cleanup; + } + init_state++; + + fbdev->dev = &pdev->dev; + fbdev->panel = panel; + fbdev->dssdev = &omapdss_device; + platform_set_drvdata(pdev, fbdev); + + mutex_init(&fbdev->rqueue_mutex); + + fbdev->int_ctrl = &omap1_int_ctrl; +#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL + fbdev->ext_if = &omap1_ext_if; +#endif + if (omapfb_find_ctrl(fbdev) < 0) { + dev_err(fbdev->dev, + "LCD controller not found, board not supported\n"); + r = -ENODEV; + goto cleanup; + } + + r = fbdev->panel->init(fbdev->panel, fbdev); + if (r) + goto cleanup; + + pr_info("omapfb: configured for panel %s\n", fbdev->panel->name); + + def_vxres = def_vxres ? def_vxres : fbdev->panel->x_res; + def_vyres = def_vyres ? def_vyres : fbdev->panel->y_res; + + init_state++; + + r = ctrl_init(fbdev); + if (r) + goto cleanup; + if (fbdev->ctrl->mmap != NULL) + omapfb_ops.fb_mmap = omapfb_mmap; + init_state++; + + check_required_callbacks(fbdev); + + r = planes_init(fbdev); + if (r) + goto cleanup; + init_state++; + +#ifdef CONFIG_FB_OMAP_DMA_TUNE + /* Set DMA priority for EMIFF access to highest */ + omap_set_dma_priority(0, OMAP_DMA_PORT_EMIFF, 15); +#endif + + r = ctrl_change_mode(fbdev->fb_info[0]); + if (r) { + dev_err(fbdev->dev, "mode setting failed\n"); + goto cleanup; + } + + /* GFX plane is enabled by default */ + r = fbdev->ctrl->enable_plane(OMAPFB_PLANE_GFX, 1); + if (r) + goto cleanup; + + omapfb_set_update_mode(fbdev, manual_update ? + OMAPFB_MANUAL_UPDATE : OMAPFB_AUTO_UPDATE); + init_state++; + + r = fbdev->panel->enable(fbdev->panel); + if (r) + goto cleanup; + init_state++; + + r = omapfb_register_sysfs(fbdev); + if (r) + goto cleanup; + init_state++; + + vram = 0; + for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { + r = register_framebuffer(fbdev->fb_info[i]); + if (r != 0) { + dev_err(fbdev->dev, + "registering framebuffer %d failed\n", i); + goto cleanup; + } + vram += fbdev->mem_desc.region[i].size; + } + + fbdev->state = OMAPFB_ACTIVE; + + panel = fbdev->panel; + phz = panel->pixel_clock * 1000; + hhz = phz * 10 / (panel->hfp + panel->x_res + panel->hbp + panel->hsw); + vhz = hhz / (panel->vfp + panel->y_res + panel->vbp + panel->vsw); + + omapfb_dev = fbdev; + + pr_info("omapfb: Framebuffer initialized. Total vram %lu planes %d\n", + vram, fbdev->mem_desc.region_cnt); + pr_info("omapfb: Pixclock %lu kHz hfreq %lu.%lu kHz " + "vfreq %lu.%lu Hz\n", + phz / 1000, hhz / 10000, hhz % 10, vhz / 10, vhz % 10); + + return 0; + +cleanup: + omapfb_free_resources(fbdev, init_state); + + return r; +} + +static int omapfb_probe(struct platform_device *pdev) +{ + int r; + + BUG_ON(fbdev_pdev != NULL); + + r = platform_device_register(&omapdss_device); + if (r) { + dev_err(&pdev->dev, "can't register omapdss device\n"); + return r; + } + + /* Delay actual initialization until the LCD is registered */ + fbdev_pdev = pdev; + if (fbdev_panel != NULL) + omapfb_do_probe(fbdev_pdev, fbdev_panel); + return 0; +} + +void omapfb_register_panel(struct lcd_panel *panel) +{ + BUG_ON(fbdev_panel != NULL); + + fbdev_panel = panel; + if (fbdev_pdev != NULL) + omapfb_do_probe(fbdev_pdev, fbdev_panel); +} + +/* Called when the device is being detached from the driver */ +static int omapfb_remove(struct platform_device *pdev) +{ + struct omapfb_device *fbdev = platform_get_drvdata(pdev); + enum omapfb_state saved_state = fbdev->state; + + /* FIXME: wait till completion of pending events */ + + fbdev->state = OMAPFB_DISABLED; + omapfb_free_resources(fbdev, saved_state); + + platform_device_unregister(&omapdss_device); + fbdev->dssdev = NULL; + + return 0; +} + +/* PM suspend */ +static int omapfb_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct omapfb_device *fbdev = platform_get_drvdata(pdev); + + if (fbdev != NULL) + omapfb_blank(FB_BLANK_POWERDOWN, fbdev->fb_info[0]); + return 0; +} + +/* PM resume */ +static int omapfb_resume(struct platform_device *pdev) +{ + struct omapfb_device *fbdev = platform_get_drvdata(pdev); + + if (fbdev != NULL) + omapfb_blank(FB_BLANK_UNBLANK, fbdev->fb_info[0]); + return 0; +} + +static struct platform_driver omapfb_driver = { + .probe = omapfb_probe, + .remove = omapfb_remove, + .suspend = omapfb_suspend, + .resume = omapfb_resume, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + }, +}; + +#ifndef MODULE + +/* Process kernel command line parameters */ +static int __init omapfb_setup(char *options) +{ + char *this_opt = NULL; + int r = 0; + + pr_debug("omapfb: options %s\n", options); + + if (!options || !*options) + return 0; + + while (!r && (this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "accel", 5)) + def_accel = 1; + else if (!strncmp(this_opt, "vram:", 5)) { + char *suffix; + unsigned long vram; + vram = (simple_strtoul(this_opt + 5, &suffix, 0)); + switch (suffix[0]) { + case '\0': + break; + case 'm': + case 'M': + vram *= 1024; + /* Fall through */ + case 'k': + case 'K': + vram *= 1024; + break; + default: + pr_debug("omapfb: invalid vram suffix %c\n", + suffix[0]); + r = -1; + } + def_vram[def_vram_cnt++] = vram; + } + else if (!strncmp(this_opt, "vxres:", 6)) + def_vxres = simple_strtoul(this_opt + 6, NULL, 0); + else if (!strncmp(this_opt, "vyres:", 6)) + def_vyres = simple_strtoul(this_opt + 6, NULL, 0); + else if (!strncmp(this_opt, "rotate:", 7)) + def_rotate = (simple_strtoul(this_opt + 7, NULL, 0)); + else if (!strncmp(this_opt, "mirror:", 7)) + def_mirror = (simple_strtoul(this_opt + 7, NULL, 0)); + else if (!strncmp(this_opt, "manual_update", 13)) + manual_update = 1; + else { + pr_debug("omapfb: invalid option\n"); + r = -1; + } + } + + return r; +} + +#endif + +/* Register both the driver and the device */ +static int __init omapfb_init(void) +{ +#ifndef MODULE + char *option; + + if (fb_get_options("omapfb", &option)) + return -ENODEV; + omapfb_setup(option); +#endif + /* Register the driver with LDM */ + if (platform_driver_register(&omapfb_driver)) { + pr_debug("failed to register omapfb driver\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit omapfb_cleanup(void) +{ + platform_driver_unregister(&omapfb_driver); +} + +module_param_named(accel, def_accel, uint, 0664); +module_param_array_named(vram, def_vram, ulong, &def_vram_cnt, 0664); +module_param_named(vxres, def_vxres, long, 0664); +module_param_named(vyres, def_vyres, long, 0664); +module_param_named(rotate, def_rotate, uint, 0664); +module_param_named(mirror, def_mirror, uint, 0664); +module_param_named(manual_update, manual_update, bool, 0664); + +module_init(omapfb_init); +module_exit(omapfb_cleanup); + +MODULE_DESCRIPTION("TI OMAP framebuffer driver"); +MODULE_AUTHOR("Imre Deak <imre.deak@nokia.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap/sossi.c b/drivers/video/fbdev/omap/sossi.c new file mode 100644 index 000000000000..d4e7684e7045 --- /dev/null +++ b/drivers/video/fbdev/omap/sossi.c @@ -0,0 +1,693 @@ +/* + * OMAP1 Special OptimiSed Screen Interface support + * + * Copyright (C) 2004-2005 Nokia Corporation + * Author: Juha Yrjölä <juha.yrjola@nokia.com> + * + * 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/mm.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/interrupt.h> + +#include <linux/omap-dma.h> + +#include "omapfb.h" +#include "lcdc.h" + +#define MODULE_NAME "omapfb-sossi" + +#define OMAP_SOSSI_BASE 0xfffbac00 +#define SOSSI_ID_REG 0x00 +#define SOSSI_INIT1_REG 0x04 +#define SOSSI_INIT2_REG 0x08 +#define SOSSI_INIT3_REG 0x0c +#define SOSSI_FIFO_REG 0x10 +#define SOSSI_REOTABLE_REG 0x14 +#define SOSSI_TEARING_REG 0x18 +#define SOSSI_INIT1B_REG 0x1c +#define SOSSI_FIFOB_REG 0x20 + +#define DMA_GSCR 0xfffedc04 +#define DMA_LCD_CCR 0xfffee3c2 +#define DMA_LCD_CTRL 0xfffee3c4 +#define DMA_LCD_LCH_CTRL 0xfffee3ea + +#define CONF_SOSSI_RESET_R (1 << 23) + +#define RD_ACCESS 0 +#define WR_ACCESS 1 + +#define SOSSI_MAX_XMIT_BYTES (512 * 1024) + +static struct { + void __iomem *base; + struct clk *fck; + unsigned long fck_hz; + spinlock_t lock; + int bus_pick_count; + int bus_pick_width; + int tearsync_mode; + int tearsync_line; + void (*lcdc_callback)(void *data); + void *lcdc_callback_data; + int vsync_dma_pending; + /* timing for read and write access */ + int clk_div; + u8 clk_tw0[2]; + u8 clk_tw1[2]; + /* + * if last_access is the same as current we don't have to change + * the timings + */ + int last_access; + + struct omapfb_device *fbdev; +} sossi; + +static inline u32 sossi_read_reg(int reg) +{ + return readl(sossi.base + reg); +} + +static inline u16 sossi_read_reg16(int reg) +{ + return readw(sossi.base + reg); +} + +static inline u8 sossi_read_reg8(int reg) +{ + return readb(sossi.base + reg); +} + +static inline void sossi_write_reg(int reg, u32 value) +{ + writel(value, sossi.base + reg); +} + +static inline void sossi_write_reg16(int reg, u16 value) +{ + writew(value, sossi.base + reg); +} + +static inline void sossi_write_reg8(int reg, u8 value) +{ + writeb(value, sossi.base + reg); +} + +static void sossi_set_bits(int reg, u32 bits) +{ + sossi_write_reg(reg, sossi_read_reg(reg) | bits); +} + +static void sossi_clear_bits(int reg, u32 bits) +{ + sossi_write_reg(reg, sossi_read_reg(reg) & ~bits); +} + +#define HZ_TO_PS(x) (1000000000 / (x / 1000)) + +static u32 ps_to_sossi_ticks(u32 ps, int div) +{ + u32 clk_period = HZ_TO_PS(sossi.fck_hz) * div; + return (clk_period + ps - 1) / clk_period; +} + +static int calc_rd_timings(struct extif_timings *t) +{ + u32 tw0, tw1; + int reon, reoff, recyc, actim; + int div = t->clk_div; + + /* + * Make sure that after conversion it still holds that: + * reoff > reon, recyc >= reoff, actim > reon + */ + reon = ps_to_sossi_ticks(t->re_on_time, div); + /* reon will be exactly one sossi tick */ + if (reon > 1) + return -1; + + reoff = ps_to_sossi_ticks(t->re_off_time, div); + + if (reoff <= reon) + reoff = reon + 1; + + tw0 = reoff - reon; + if (tw0 > 0x10) + return -1; + + recyc = ps_to_sossi_ticks(t->re_cycle_time, div); + if (recyc <= reoff) + recyc = reoff + 1; + + tw1 = recyc - tw0; + /* values less then 3 result in the SOSSI block resetting itself */ + if (tw1 < 3) + tw1 = 3; + if (tw1 > 0x40) + return -1; + + actim = ps_to_sossi_ticks(t->access_time, div); + if (actim < reoff) + actim++; + /* + * access time (data hold time) will be exactly one sossi + * tick + */ + if (actim - reoff > 1) + return -1; + + t->tim[0] = tw0 - 1; + t->tim[1] = tw1 - 1; + + return 0; +} + +static int calc_wr_timings(struct extif_timings *t) +{ + u32 tw0, tw1; + int weon, weoff, wecyc; + int div = t->clk_div; + + /* + * Make sure that after conversion it still holds that: + * weoff > weon, wecyc >= weoff + */ + weon = ps_to_sossi_ticks(t->we_on_time, div); + /* weon will be exactly one sossi tick */ + if (weon > 1) + return -1; + + weoff = ps_to_sossi_ticks(t->we_off_time, div); + if (weoff <= weon) + weoff = weon + 1; + tw0 = weoff - weon; + if (tw0 > 0x10) + return -1; + + wecyc = ps_to_sossi_ticks(t->we_cycle_time, div); + if (wecyc <= weoff) + wecyc = weoff + 1; + + tw1 = wecyc - tw0; + /* values less then 3 result in the SOSSI block resetting itself */ + if (tw1 < 3) + tw1 = 3; + if (tw1 > 0x40) + return -1; + + t->tim[2] = tw0 - 1; + t->tim[3] = tw1 - 1; + + return 0; +} + +static void _set_timing(int div, int tw0, int tw1) +{ + u32 l; + +#ifdef VERBOSE + dev_dbg(sossi.fbdev->dev, "Using TW0 = %d, TW1 = %d, div = %d\n", + tw0 + 1, tw1 + 1, div); +#endif + + clk_set_rate(sossi.fck, sossi.fck_hz / div); + clk_enable(sossi.fck); + l = sossi_read_reg(SOSSI_INIT1_REG); + l &= ~((0x0f << 20) | (0x3f << 24)); + l |= (tw0 << 20) | (tw1 << 24); + sossi_write_reg(SOSSI_INIT1_REG, l); + clk_disable(sossi.fck); +} + +static void _set_bits_per_cycle(int bus_pick_count, int bus_pick_width) +{ + u32 l; + + l = sossi_read_reg(SOSSI_INIT3_REG); + l &= ~0x3ff; + l |= ((bus_pick_count - 1) << 5) | ((bus_pick_width - 1) & 0x1f); + sossi_write_reg(SOSSI_INIT3_REG, l); +} + +static void _set_tearsync_mode(int mode, unsigned line) +{ + u32 l; + + l = sossi_read_reg(SOSSI_TEARING_REG); + l &= ~(((1 << 11) - 1) << 15); + l |= line << 15; + l &= ~(0x3 << 26); + l |= mode << 26; + sossi_write_reg(SOSSI_TEARING_REG, l); + if (mode) + sossi_set_bits(SOSSI_INIT2_REG, 1 << 6); /* TE logic */ + else + sossi_clear_bits(SOSSI_INIT2_REG, 1 << 6); +} + +static inline void set_timing(int access) +{ + if (access != sossi.last_access) { + sossi.last_access = access; + _set_timing(sossi.clk_div, + sossi.clk_tw0[access], sossi.clk_tw1[access]); + } +} + +static void sossi_start_transfer(void) +{ + /* WE */ + sossi_clear_bits(SOSSI_INIT2_REG, 1 << 4); + /* CS active low */ + sossi_clear_bits(SOSSI_INIT1_REG, 1 << 30); +} + +static void sossi_stop_transfer(void) +{ + /* WE */ + sossi_set_bits(SOSSI_INIT2_REG, 1 << 4); + /* CS active low */ + sossi_set_bits(SOSSI_INIT1_REG, 1 << 30); +} + +static void wait_end_of_write(void) +{ + /* Before reading we must check if some writings are going on */ + while (!(sossi_read_reg(SOSSI_INIT2_REG) & (1 << 3))); +} + +static void send_data(const void *data, unsigned int len) +{ + while (len >= 4) { + sossi_write_reg(SOSSI_FIFO_REG, *(const u32 *) data); + len -= 4; + data += 4; + } + while (len >= 2) { + sossi_write_reg16(SOSSI_FIFO_REG, *(const u16 *) data); + len -= 2; + data += 2; + } + while (len) { + sossi_write_reg8(SOSSI_FIFO_REG, *(const u8 *) data); + len--; + data++; + } +} + +static void set_cycles(unsigned int len) +{ + unsigned long nr_cycles = len / (sossi.bus_pick_width / 8); + + BUG_ON((nr_cycles - 1) & ~0x3ffff); + + sossi_clear_bits(SOSSI_INIT1_REG, 0x3ffff); + sossi_set_bits(SOSSI_INIT1_REG, (nr_cycles - 1) & 0x3ffff); +} + +static int sossi_convert_timings(struct extif_timings *t) +{ + int r = 0; + int div = t->clk_div; + + t->converted = 0; + + if (div <= 0 || div > 8) + return -1; + + /* no CS on SOSSI, so ignore cson, csoff, cs_pulsewidth */ + if ((r = calc_rd_timings(t)) < 0) + return r; + + if ((r = calc_wr_timings(t)) < 0) + return r; + + t->tim[4] = div; + + t->converted = 1; + + return 0; +} + +static void sossi_set_timings(const struct extif_timings *t) +{ + BUG_ON(!t->converted); + + sossi.clk_tw0[RD_ACCESS] = t->tim[0]; + sossi.clk_tw1[RD_ACCESS] = t->tim[1]; + + sossi.clk_tw0[WR_ACCESS] = t->tim[2]; + sossi.clk_tw1[WR_ACCESS] = t->tim[3]; + + sossi.clk_div = t->tim[4]; +} + +static void sossi_get_clk_info(u32 *clk_period, u32 *max_clk_div) +{ + *clk_period = HZ_TO_PS(sossi.fck_hz); + *max_clk_div = 8; +} + +static void sossi_set_bits_per_cycle(int bpc) +{ + int bus_pick_count, bus_pick_width; + + /* + * We set explicitly the the bus_pick_count as well, although + * with remapping/reordering disabled it will be calculated by HW + * as (32 / bus_pick_width). + */ + switch (bpc) { + case 8: + bus_pick_count = 4; + bus_pick_width = 8; + break; + case 16: + bus_pick_count = 2; + bus_pick_width = 16; + break; + default: + BUG(); + return; + } + sossi.bus_pick_width = bus_pick_width; + sossi.bus_pick_count = bus_pick_count; +} + +static int sossi_setup_tearsync(unsigned pin_cnt, + unsigned hs_pulse_time, unsigned vs_pulse_time, + int hs_pol_inv, int vs_pol_inv, int div) +{ + int hs, vs; + u32 l; + + if (pin_cnt != 1 || div < 1 || div > 8) + return -EINVAL; + + hs = ps_to_sossi_ticks(hs_pulse_time, div); + vs = ps_to_sossi_ticks(vs_pulse_time, div); + if (vs < 8 || vs <= hs || vs >= (1 << 12)) + return -EDOM; + vs /= 8; + vs--; + if (hs > 8) + hs = 8; + if (hs) + hs--; + + dev_dbg(sossi.fbdev->dev, + "setup_tearsync: hs %d vs %d hs_inv %d vs_inv %d\n", + hs, vs, hs_pol_inv, vs_pol_inv); + + clk_enable(sossi.fck); + l = sossi_read_reg(SOSSI_TEARING_REG); + l &= ~((1 << 15) - 1); + l |= vs << 3; + l |= hs; + if (hs_pol_inv) + l |= 1 << 29; + else + l &= ~(1 << 29); + if (vs_pol_inv) + l |= 1 << 28; + else + l &= ~(1 << 28); + sossi_write_reg(SOSSI_TEARING_REG, l); + clk_disable(sossi.fck); + + return 0; +} + +static int sossi_enable_tearsync(int enable, unsigned line) +{ + int mode; + + dev_dbg(sossi.fbdev->dev, "tearsync %d line %d\n", enable, line); + if (line >= 1 << 11) + return -EINVAL; + if (enable) { + if (line) + mode = 2; /* HS or VS */ + else + mode = 3; /* VS only */ + } else + mode = 0; + sossi.tearsync_line = line; + sossi.tearsync_mode = mode; + + return 0; +} + +static void sossi_write_command(const void *data, unsigned int len) +{ + clk_enable(sossi.fck); + set_timing(WR_ACCESS); + _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width); + /* CMD#/DATA */ + sossi_clear_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(len); + sossi_start_transfer(); + send_data(data, len); + sossi_stop_transfer(); + wait_end_of_write(); + clk_disable(sossi.fck); +} + +static void sossi_write_data(const void *data, unsigned int len) +{ + clk_enable(sossi.fck); + set_timing(WR_ACCESS); + _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width); + /* CMD#/DATA */ + sossi_set_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(len); + sossi_start_transfer(); + send_data(data, len); + sossi_stop_transfer(); + wait_end_of_write(); + clk_disable(sossi.fck); +} + +static void sossi_transfer_area(int width, int height, + void (callback)(void *data), void *data) +{ + BUG_ON(callback == NULL); + + sossi.lcdc_callback = callback; + sossi.lcdc_callback_data = data; + + clk_enable(sossi.fck); + set_timing(WR_ACCESS); + _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width); + _set_tearsync_mode(sossi.tearsync_mode, sossi.tearsync_line); + /* CMD#/DATA */ + sossi_set_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(width * height * sossi.bus_pick_width / 8); + + sossi_start_transfer(); + if (sossi.tearsync_mode) { + /* + * Wait for the sync signal and start the transfer only + * then. We can't seem to be able to use HW sync DMA for + * this since LCD DMA shows huge latencies, as if it + * would ignore some of the DMA requests from SoSSI. + */ + unsigned long flags; + + spin_lock_irqsave(&sossi.lock, flags); + sossi.vsync_dma_pending++; + spin_unlock_irqrestore(&sossi.lock, flags); + } else + /* Just start the transfer right away. */ + omap_enable_lcd_dma(); +} + +static void sossi_dma_callback(void *data) +{ + omap_stop_lcd_dma(); + sossi_stop_transfer(); + clk_disable(sossi.fck); + sossi.lcdc_callback(sossi.lcdc_callback_data); +} + +static void sossi_read_data(void *data, unsigned int len) +{ + clk_enable(sossi.fck); + set_timing(RD_ACCESS); + _set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width); + /* CMD#/DATA */ + sossi_set_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(len); + sossi_start_transfer(); + while (len >= 4) { + *(u32 *) data = sossi_read_reg(SOSSI_FIFO_REG); + len -= 4; + data += 4; + } + while (len >= 2) { + *(u16 *) data = sossi_read_reg16(SOSSI_FIFO_REG); + len -= 2; + data += 2; + } + while (len) { + *(u8 *) data = sossi_read_reg8(SOSSI_FIFO_REG); + len--; + data++; + } + sossi_stop_transfer(); + clk_disable(sossi.fck); +} + +static irqreturn_t sossi_match_irq(int irq, void *data) +{ + unsigned long flags; + + spin_lock_irqsave(&sossi.lock, flags); + if (sossi.vsync_dma_pending) { + sossi.vsync_dma_pending--; + omap_enable_lcd_dma(); + } + spin_unlock_irqrestore(&sossi.lock, flags); + return IRQ_HANDLED; +} + +static int sossi_init(struct omapfb_device *fbdev) +{ + u32 l, k; + struct clk *fck; + struct clk *dpll1out_ck; + int r; + + sossi.base = ioremap(OMAP_SOSSI_BASE, SZ_1K); + if (!sossi.base) { + dev_err(fbdev->dev, "can't ioremap SoSSI\n"); + return -ENOMEM; + } + + sossi.fbdev = fbdev; + spin_lock_init(&sossi.lock); + + dpll1out_ck = clk_get(fbdev->dev, "ck_dpll1out"); + if (IS_ERR(dpll1out_ck)) { + dev_err(fbdev->dev, "can't get DPLL1OUT clock\n"); + return PTR_ERR(dpll1out_ck); + } + /* + * We need the parent clock rate, which we might divide further + * depending on the timing requirements of the controller. See + * _set_timings. + */ + sossi.fck_hz = clk_get_rate(dpll1out_ck); + clk_put(dpll1out_ck); + + fck = clk_get(fbdev->dev, "ck_sossi"); + if (IS_ERR(fck)) { + dev_err(fbdev->dev, "can't get SoSSI functional clock\n"); + return PTR_ERR(fck); + } + sossi.fck = fck; + + /* Reset and enable the SoSSI module */ + l = omap_readl(MOD_CONF_CTRL_1); + l |= CONF_SOSSI_RESET_R; + omap_writel(l, MOD_CONF_CTRL_1); + l &= ~CONF_SOSSI_RESET_R; + omap_writel(l, MOD_CONF_CTRL_1); + + clk_enable(sossi.fck); + l = omap_readl(ARM_IDLECT2); + l &= ~(1 << 8); /* DMACK_REQ */ + omap_writel(l, ARM_IDLECT2); + + l = sossi_read_reg(SOSSI_INIT2_REG); + /* Enable and reset the SoSSI block */ + l |= (1 << 0) | (1 << 1); + sossi_write_reg(SOSSI_INIT2_REG, l); + /* Take SoSSI out of reset */ + l &= ~(1 << 1); + sossi_write_reg(SOSSI_INIT2_REG, l); + + sossi_write_reg(SOSSI_ID_REG, 0); + l = sossi_read_reg(SOSSI_ID_REG); + k = sossi_read_reg(SOSSI_ID_REG); + + if (l != 0x55555555 || k != 0xaaaaaaaa) { + dev_err(fbdev->dev, + "invalid SoSSI sync pattern: %08x, %08x\n", l, k); + r = -ENODEV; + goto err; + } + + if ((r = omap_lcdc_set_dma_callback(sossi_dma_callback, NULL)) < 0) { + dev_err(fbdev->dev, "can't get LCDC IRQ\n"); + r = -ENODEV; + goto err; + } + + l = sossi_read_reg(SOSSI_ID_REG); /* Component code */ + l = sossi_read_reg(SOSSI_ID_REG); + dev_info(fbdev->dev, "SoSSI version %d.%d initialized\n", + l >> 16, l & 0xffff); + + l = sossi_read_reg(SOSSI_INIT1_REG); + l |= (1 << 19); /* DMA_MODE */ + l &= ~(1 << 31); /* REORDERING */ + sossi_write_reg(SOSSI_INIT1_REG, l); + + if ((r = request_irq(INT_1610_SoSSI_MATCH, sossi_match_irq, + IRQ_TYPE_EDGE_FALLING, + "sossi_match", sossi.fbdev->dev)) < 0) { + dev_err(sossi.fbdev->dev, "can't get SoSSI match IRQ\n"); + goto err; + } + + clk_disable(sossi.fck); + return 0; + +err: + clk_disable(sossi.fck); + clk_put(sossi.fck); + return r; +} + +static void sossi_cleanup(void) +{ + omap_lcdc_free_dma_callback(); + clk_put(sossi.fck); + iounmap(sossi.base); +} + +struct lcd_ctrl_extif omap1_ext_if = { + .init = sossi_init, + .cleanup = sossi_cleanup, + .get_clk_info = sossi_get_clk_info, + .convert_timings = sossi_convert_timings, + .set_timings = sossi_set_timings, + .set_bits_per_cycle = sossi_set_bits_per_cycle, + .setup_tearsync = sossi_setup_tearsync, + .enable_tearsync = sossi_enable_tearsync, + .write_command = sossi_write_command, + .read_data = sossi_read_data, + .write_data = sossi_write_data, + .transfer_area = sossi_transfer_area, + + .max_transmit_size = SOSSI_MAX_XMIT_BYTES, +}; + diff --git a/drivers/video/fbdev/omap2/Kconfig b/drivers/video/fbdev/omap2/Kconfig new file mode 100644 index 000000000000..c22955d2de9a --- /dev/null +++ b/drivers/video/fbdev/omap2/Kconfig @@ -0,0 +1,10 @@ +config OMAP2_VRFB + bool + +if ARCH_OMAP2PLUS + +source "drivers/video/fbdev/omap2/dss/Kconfig" +source "drivers/video/fbdev/omap2/omapfb/Kconfig" +source "drivers/video/fbdev/omap2/displays-new/Kconfig" + +endif diff --git a/drivers/video/fbdev/omap2/Makefile b/drivers/video/fbdev/omap2/Makefile new file mode 100644 index 000000000000..bf8127df8c71 --- /dev/null +++ b/drivers/video/fbdev/omap2/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_OMAP2_VRFB) += vrfb.o + +obj-$(CONFIG_OMAP2_DSS) += dss/ +obj-y += displays-new/ +obj-$(CONFIG_FB_OMAP2) += omapfb/ diff --git a/drivers/video/fbdev/omap2/displays-new/Kconfig b/drivers/video/fbdev/omap2/displays-new/Kconfig new file mode 100644 index 000000000000..e6cfc38160d3 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/Kconfig @@ -0,0 +1,80 @@ +menu "OMAP Display Device Drivers (new device model)" + depends on OMAP2_DSS + +config DISPLAY_ENCODER_TFP410 + tristate "TFP410 DPI to DVI Encoder" + help + Driver for TFP410 DPI to DVI encoder. + +config DISPLAY_ENCODER_TPD12S015 + tristate "TPD12S015 HDMI ESD protection and level shifter" + help + Driver for TPD12S015, which offers HDMI ESD protection and level + shifting. + +config DISPLAY_CONNECTOR_DVI + tristate "DVI Connector" + depends on I2C + help + Driver for a generic DVI connector. + +config DISPLAY_CONNECTOR_HDMI + tristate "HDMI Connector" + help + Driver for a generic HDMI connector. + +config DISPLAY_CONNECTOR_ANALOG_TV + tristate "Analog TV Connector" + help + Driver for a generic analog TV connector. + +config DISPLAY_PANEL_DPI + tristate "Generic DPI panel" + help + Driver for generic DPI panels. + +config DISPLAY_PANEL_DSI_CM + tristate "Generic DSI Command Mode Panel" + depends on BACKLIGHT_CLASS_DEVICE + help + Driver for generic DSI command mode panels. + +config DISPLAY_PANEL_SONY_ACX565AKM + tristate "ACX565AKM Panel" + depends on SPI && BACKLIGHT_CLASS_DEVICE + help + This is the LCD panel used on Nokia N900 + +config DISPLAY_PANEL_LGPHILIPS_LB035Q02 + tristate "LG.Philips LB035Q02 LCD Panel" + depends on SPI + help + LCD Panel used on the Gumstix Overo Palo35 + +config DISPLAY_PANEL_SHARP_LS037V7DW01 + tristate "Sharp LS037V7DW01 LCD Panel" + depends on BACKLIGHT_CLASS_DEVICE + help + LCD Panel used in TI's SDP3430 and EVM boards + +config DISPLAY_PANEL_TPO_TD028TTEC1 + tristate "TPO TD028TTEC1 LCD Panel" + depends on SPI + help + LCD panel used in Openmoko. + +config DISPLAY_PANEL_TPO_TD043MTEA1 + tristate "TPO TD043MTEA1 LCD Panel" + depends on SPI + help + LCD Panel used in OMAP3 Pandora + +config DISPLAY_PANEL_NEC_NL8048HL11 + tristate "NEC NL8048HL11 Panel" + depends on SPI + depends on BACKLIGHT_CLASS_DEVICE + help + This NEC NL8048HL11 panel is TFT LCD used in the + Zoom2/3/3630 sdp boards. + +endmenu diff --git a/drivers/video/fbdev/omap2/displays-new/Makefile b/drivers/video/fbdev/omap2/displays-new/Makefile new file mode 100644 index 000000000000..0323a8a1c682 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/Makefile @@ -0,0 +1,13 @@ +obj-$(CONFIG_DISPLAY_ENCODER_TFP410) += encoder-tfp410.o +obj-$(CONFIG_DISPLAY_ENCODER_TPD12S015) += encoder-tpd12s015.o +obj-$(CONFIG_DISPLAY_CONNECTOR_DVI) += connector-dvi.o +obj-$(CONFIG_DISPLAY_CONNECTOR_HDMI) += connector-hdmi.o +obj-$(CONFIG_DISPLAY_CONNECTOR_ANALOG_TV) += connector-analog-tv.o +obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o +obj-$(CONFIG_DISPLAY_PANEL_DSI_CM) += panel-dsi-cm.o +obj-$(CONFIG_DISPLAY_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o +obj-$(CONFIG_DISPLAY_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o +obj-$(CONFIG_DISPLAY_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o +obj-$(CONFIG_DISPLAY_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o +obj-$(CONFIG_DISPLAY_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o +obj-$(CONFIG_DISPLAY_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o diff --git a/drivers/video/fbdev/omap2/displays-new/connector-analog-tv.c b/drivers/video/fbdev/omap2/displays-new/connector-analog-tv.c new file mode 100644 index 000000000000..5ee3b5505f7f --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/connector-analog-tv.c @@ -0,0 +1,318 @@ +/* + * Analog TV Connector driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + struct device *dev; + + struct omap_video_timings timings; + + enum omap_dss_venc_type connector_type; + bool invert_polarity; +}; + +static const struct omap_video_timings tvc_pal_timings = { + .x_res = 720, + .y_res = 574, + .pixelclock = 13500000, + .hsw = 64, + .hfp = 12, + .hbp = 68, + .vsw = 5, + .vfp = 5, + .vbp = 41, + + .interlace = true, +}; + +static const struct of_device_id tvc_of_match[]; + +struct tvc_of_data { + enum omap_dss_venc_type connector_type; +}; + +#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) + +static int tvc_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(ddata->dev, "connect\n"); + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.atv->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void tvc_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + dev_dbg(ddata->dev, "disconnect\n"); + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.atv->disconnect(in, dssdev); +} + +static int tvc_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(ddata->dev, "enable\n"); + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.atv->set_timings(in, &ddata->timings); + + if (!ddata->dev->of_node) { + in->ops.atv->set_type(in, ddata->connector_type); + + in->ops.atv->invert_vid_out_polarity(in, + ddata->invert_polarity); + } + + r = in->ops.atv->enable(in); + if (r) + return r; + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return r; +} + +static void tvc_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + dev_dbg(ddata->dev, "disable\n"); + + if (!omapdss_device_is_enabled(dssdev)) + return; + + in->ops.atv->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void tvc_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->timings = *timings; + dssdev->panel.timings = *timings; + + in->ops.atv->set_timings(in, timings); +} + +static void tvc_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->timings; +} + +static int tvc_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.atv->check_timings(in, timings); +} + +static u32 tvc_get_wss(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.atv->get_wss(in); +} + +static int tvc_set_wss(struct omap_dss_device *dssdev, u32 wss) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.atv->set_wss(in, wss); +} + +static struct omap_dss_driver tvc_driver = { + .connect = tvc_connect, + .disconnect = tvc_disconnect, + + .enable = tvc_enable, + .disable = tvc_disable, + + .set_timings = tvc_set_timings, + .get_timings = tvc_get_timings, + .check_timings = tvc_check_timings, + + .get_resolution = omapdss_default_get_resolution, + + .get_wss = tvc_get_wss, + .set_wss = tvc_set_wss, +}; + +static int tvc_probe_pdata(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct connector_atv_platform_data *pdata; + struct omap_dss_device *in, *dssdev; + + pdata = dev_get_platdata(&pdev->dev); + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&pdev->dev, "Failed to find video source\n"); + return -EPROBE_DEFER; + } + + ddata->in = in; + + ddata->connector_type = pdata->connector_type; + ddata->invert_polarity = ddata->invert_polarity; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int tvc_probe_of(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + struct omap_dss_device *in; + + in = omapdss_of_find_source_for_first_ep(node); + if (IS_ERR(in)) { + dev_err(&pdev->dev, "failed to find video source\n"); + return PTR_ERR(in); + } + + ddata->in = in; + + return 0; +} + +static int tvc_probe(struct platform_device *pdev) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + ddata->dev = &pdev->dev; + + if (dev_get_platdata(&pdev->dev)) { + r = tvc_probe_pdata(pdev); + if (r) + return r; + } else if (pdev->dev.of_node) { + r = tvc_probe_of(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + ddata->timings = tvc_pal_timings; + + dssdev = &ddata->dssdev; + dssdev->driver = &tvc_driver; + dssdev->dev = &pdev->dev; + dssdev->type = OMAP_DISPLAY_TYPE_VENC; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = tvc_pal_timings; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&pdev->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; +err_reg: + omap_dss_put_device(ddata->in); + return r; +} + +static int __exit tvc_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_display(&ddata->dssdev); + + tvc_disable(dssdev); + tvc_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +static const struct of_device_id tvc_of_match[] = { + { .compatible = "omapdss,svideo-connector", }, + { .compatible = "omapdss,composite-video-connector", }, + {}, +}; + +static struct platform_driver tvc_connector_driver = { + .probe = tvc_probe, + .remove = __exit_p(tvc_remove), + .driver = { + .name = "connector-analog-tv", + .owner = THIS_MODULE, + .of_match_table = tvc_of_match, + }, +}; + +module_platform_driver(tvc_connector_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Analog TV Connector driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/connector-dvi.c b/drivers/video/fbdev/omap2/displays-new/connector-dvi.c new file mode 100644 index 000000000000..74de2bc50c4f --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/connector-dvi.c @@ -0,0 +1,401 @@ +/* + * Generic DVI Connector driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <drm/drm_edid.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +static const struct omap_video_timings dvic_default_timings = { + .x_res = 640, + .y_res = 480, + + .pixelclock = 23500000, + + .hfp = 48, + .hsw = 32, + .hbp = 80, + + .vfp = 3, + .vsw = 4, + .vbp = 7, + + .vsync_level = OMAPDSS_SIG_ACTIVE_HIGH, + .hsync_level = OMAPDSS_SIG_ACTIVE_HIGH, + .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, + .de_level = OMAPDSS_SIG_ACTIVE_HIGH, + .sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, +}; + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + struct omap_video_timings timings; + + struct i2c_adapter *i2c_adapter; +}; + +#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) + +static int dvic_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dvi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void dvic_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dvi->disconnect(in, dssdev); +} + +static int dvic_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dvi->set_timings(in, &ddata->timings); + + r = in->ops.dvi->enable(in); + if (r) + return r; + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void dvic_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + in->ops.dvi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void dvic_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->timings = *timings; + dssdev->panel.timings = *timings; + + in->ops.dvi->set_timings(in, timings); +} + +static void dvic_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->timings; +} + +static int dvic_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dvi->check_timings(in, timings); +} + +static int dvic_ddc_read(struct i2c_adapter *adapter, + unsigned char *buf, u16 count, u8 offset) +{ + int r, retries; + + for (retries = 3; retries > 0; retries--) { + struct i2c_msg msgs[] = { + { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &offset, + }, { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = count, + .buf = buf, + } + }; + + r = i2c_transfer(adapter, msgs, 2); + if (r == 2) + return 0; + + if (r != -EAGAIN) + break; + } + + return r < 0 ? r : -EIO; +} + +static int dvic_read_edid(struct omap_dss_device *dssdev, + u8 *edid, int len) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + int r, l, bytes_read; + + if (!ddata->i2c_adapter) + return -ENODEV; + + l = min(EDID_LENGTH, len); + r = dvic_ddc_read(ddata->i2c_adapter, edid, l, 0); + if (r) + return r; + + bytes_read = l; + + /* if there are extensions, read second block */ + if (len > EDID_LENGTH && edid[0x7e] > 0) { + l = min(EDID_LENGTH, len - EDID_LENGTH); + + r = dvic_ddc_read(ddata->i2c_adapter, edid + EDID_LENGTH, + l, EDID_LENGTH); + if (r) + return r; + + bytes_read += l; + } + + return bytes_read; +} + +static bool dvic_detect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + unsigned char out; + int r; + + if (!ddata->i2c_adapter) + return true; + + r = dvic_ddc_read(ddata->i2c_adapter, &out, 1, 0); + + return r == 0; +} + +static struct omap_dss_driver dvic_driver = { + .connect = dvic_connect, + .disconnect = dvic_disconnect, + + .enable = dvic_enable, + .disable = dvic_disable, + + .set_timings = dvic_set_timings, + .get_timings = dvic_get_timings, + .check_timings = dvic_check_timings, + + .get_resolution = omapdss_default_get_resolution, + + .read_edid = dvic_read_edid, + .detect = dvic_detect, +}; + +static int dvic_probe_pdata(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct connector_dvi_platform_data *pdata; + struct omap_dss_device *in, *dssdev; + int i2c_bus_num; + + pdata = dev_get_platdata(&pdev->dev); + i2c_bus_num = pdata->i2c_bus_num; + + if (i2c_bus_num != -1) { + struct i2c_adapter *adapter; + + adapter = i2c_get_adapter(i2c_bus_num); + if (!adapter) { + dev_err(&pdev->dev, + "Failed to get I2C adapter, bus %d\n", + i2c_bus_num); + return -EPROBE_DEFER; + } + + ddata->i2c_adapter = adapter; + } + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + if (ddata->i2c_adapter) + i2c_put_adapter(ddata->i2c_adapter); + + dev_err(&pdev->dev, "Failed to find video source\n"); + return -EPROBE_DEFER; + } + + ddata->in = in; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int dvic_probe_of(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + struct omap_dss_device *in; + struct device_node *adapter_node; + struct i2c_adapter *adapter; + + in = omapdss_of_find_source_for_first_ep(node); + if (IS_ERR(in)) { + dev_err(&pdev->dev, "failed to find video source\n"); + return PTR_ERR(in); + } + + ddata->in = in; + + adapter_node = of_parse_phandle(node, "ddc-i2c-bus", 0); + if (adapter_node) { + adapter = of_find_i2c_adapter_by_node(adapter_node); + if (adapter == NULL) { + dev_err(&pdev->dev, "failed to parse ddc-i2c-bus\n"); + omap_dss_put_device(ddata->in); + return -EPROBE_DEFER; + } + + ddata->i2c_adapter = adapter; + } + + return 0; +} + +static int dvic_probe(struct platform_device *pdev) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + + if (dev_get_platdata(&pdev->dev)) { + r = dvic_probe_pdata(pdev); + if (r) + return r; + } else if (pdev->dev.of_node) { + r = dvic_probe_of(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + ddata->timings = dvic_default_timings; + + dssdev = &ddata->dssdev; + dssdev->driver = &dvic_driver; + dssdev->dev = &pdev->dev; + dssdev->type = OMAP_DISPLAY_TYPE_DVI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = dvic_default_timings; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&pdev->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: + omap_dss_put_device(ddata->in); + + if (ddata->i2c_adapter) + i2c_put_adapter(ddata->i2c_adapter); + + return r; +} + +static int __exit dvic_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_display(&ddata->dssdev); + + dvic_disable(dssdev); + dvic_disconnect(dssdev); + + omap_dss_put_device(in); + + if (ddata->i2c_adapter) + i2c_put_adapter(ddata->i2c_adapter); + + return 0; +} + +static const struct of_device_id dvic_of_match[] = { + { .compatible = "omapdss,dvi-connector", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dvic_of_match); + +static struct platform_driver dvi_connector_driver = { + .probe = dvic_probe, + .remove = __exit_p(dvic_remove), + .driver = { + .name = "connector-dvi", + .owner = THIS_MODULE, + .of_match_table = dvic_of_match, + }, +}; + +module_platform_driver(dvi_connector_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Generic DVI Connector driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/connector-hdmi.c b/drivers/video/fbdev/omap2/displays-new/connector-hdmi.c new file mode 100644 index 000000000000..29ed21b9dce5 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/connector-hdmi.c @@ -0,0 +1,405 @@ +/* + * HDMI Connector driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <drm/drm_edid.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +static const struct omap_video_timings hdmic_default_timings = { + .x_res = 640, + .y_res = 480, + .pixelclock = 25175000, + .hsw = 96, + .hfp = 16, + .hbp = 48, + .vsw = 2, + .vfp = 11, + .vbp = 31, + + .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, + + .interlace = false, +}; + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + struct device *dev; + + struct omap_video_timings timings; +}; + +#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) + +static int hdmic_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(ddata->dev, "connect\n"); + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.hdmi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void hdmic_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + dev_dbg(ddata->dev, "disconnect\n"); + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.hdmi->disconnect(in, dssdev); +} + +static int hdmic_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(ddata->dev, "enable\n"); + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.hdmi->set_timings(in, &ddata->timings); + + r = in->ops.hdmi->enable(in); + if (r) + return r; + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return r; +} + +static void hdmic_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + dev_dbg(ddata->dev, "disable\n"); + + if (!omapdss_device_is_enabled(dssdev)) + return; + + in->ops.hdmi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void hdmic_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->timings = *timings; + dssdev->panel.timings = *timings; + + in->ops.hdmi->set_timings(in, timings); +} + +static void hdmic_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->timings; +} + +static int hdmic_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.hdmi->check_timings(in, timings); +} + +static int hdmic_read_edid(struct omap_dss_device *dssdev, + u8 *edid, int len) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.hdmi->read_edid(in, edid, len); +} + +static bool hdmic_detect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.hdmi->detect(in); +} + +static int hdmic_audio_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + /* enable audio only if the display is active */ + if (!omapdss_device_is_enabled(dssdev)) + return -EPERM; + + r = in->ops.hdmi->audio_enable(in); + if (r) + return r; + + dssdev->audio_state = OMAP_DSS_AUDIO_ENABLED; + + return 0; +} + +static void hdmic_audio_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + in->ops.hdmi->audio_disable(in); + + dssdev->audio_state = OMAP_DSS_AUDIO_DISABLED; +} + +static int hdmic_audio_start(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + /* + * No need to check the panel state. It was checked when trasitioning + * to AUDIO_ENABLED. + */ + if (dssdev->audio_state != OMAP_DSS_AUDIO_ENABLED) + return -EPERM; + + r = in->ops.hdmi->audio_start(in); + if (r) + return r; + + dssdev->audio_state = OMAP_DSS_AUDIO_PLAYING; + + return 0; +} + +static void hdmic_audio_stop(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + in->ops.hdmi->audio_stop(in); + + dssdev->audio_state = OMAP_DSS_AUDIO_ENABLED; +} + +static bool hdmic_audio_supported(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return false; + + return in->ops.hdmi->audio_supported(in); +} + +static int hdmic_audio_config(struct omap_dss_device *dssdev, + struct omap_dss_audio *audio) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + /* config audio only if the display is active */ + if (!omapdss_device_is_enabled(dssdev)) + return -EPERM; + + r = in->ops.hdmi->audio_config(in, audio); + if (r) + return r; + + dssdev->audio_state = OMAP_DSS_AUDIO_CONFIGURED; + + return 0; +} + +static struct omap_dss_driver hdmic_driver = { + .connect = hdmic_connect, + .disconnect = hdmic_disconnect, + + .enable = hdmic_enable, + .disable = hdmic_disable, + + .set_timings = hdmic_set_timings, + .get_timings = hdmic_get_timings, + .check_timings = hdmic_check_timings, + + .get_resolution = omapdss_default_get_resolution, + + .read_edid = hdmic_read_edid, + .detect = hdmic_detect, + + .audio_enable = hdmic_audio_enable, + .audio_disable = hdmic_audio_disable, + .audio_start = hdmic_audio_start, + .audio_stop = hdmic_audio_stop, + .audio_supported = hdmic_audio_supported, + .audio_config = hdmic_audio_config, +}; + +static int hdmic_probe_pdata(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct connector_hdmi_platform_data *pdata; + struct omap_dss_device *in, *dssdev; + + pdata = dev_get_platdata(&pdev->dev); + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&pdev->dev, "Failed to find video source\n"); + return -EPROBE_DEFER; + } + + ddata->in = in; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int hdmic_probe_of(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + struct omap_dss_device *in; + + in = omapdss_of_find_source_for_first_ep(node); + if (IS_ERR(in)) { + dev_err(&pdev->dev, "failed to find video source\n"); + return PTR_ERR(in); + } + + ddata->in = in; + + return 0; +} + +static int hdmic_probe(struct platform_device *pdev) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + ddata->dev = &pdev->dev; + + if (dev_get_platdata(&pdev->dev)) { + r = hdmic_probe_pdata(pdev); + if (r) + return r; + } else if (pdev->dev.of_node) { + r = hdmic_probe_of(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + ddata->timings = hdmic_default_timings; + + dssdev = &ddata->dssdev; + dssdev->driver = &hdmic_driver; + dssdev->dev = &pdev->dev; + dssdev->type = OMAP_DISPLAY_TYPE_HDMI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = hdmic_default_timings; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&pdev->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; +err_reg: + omap_dss_put_device(ddata->in); + return r; +} + +static int __exit hdmic_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_display(&ddata->dssdev); + + hdmic_disable(dssdev); + hdmic_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +static const struct of_device_id hdmic_of_match[] = { + { .compatible = "omapdss,hdmi-connector", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, hdmic_of_match); + +static struct platform_driver hdmi_connector_driver = { + .probe = hdmic_probe, + .remove = __exit_p(hdmic_remove), + .driver = { + .name = "connector-hdmi", + .owner = THIS_MODULE, + .of_match_table = hdmic_of_match, + }, +}; + +module_platform_driver(hdmi_connector_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("HDMI Connector driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/encoder-tfp410.c b/drivers/video/fbdev/omap2/displays-new/encoder-tfp410.c new file mode 100644 index 000000000000..b4e9a42a79e6 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/encoder-tfp410.c @@ -0,0 +1,308 @@ +/* + * TFP410 DPI-to-DVI encoder driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of_gpio.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + int pd_gpio; + int data_lines; + + struct omap_video_timings timings; +}; + +#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) + +static int tfp410_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return -EBUSY; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + dst->src = dssdev; + dssdev->dst = dst; + + return 0; +} + +static void tfp410_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + WARN_ON(!omapdss_device_is_connected(dssdev)); + if (!omapdss_device_is_connected(dssdev)) + return; + + WARN_ON(dst != dssdev->dst); + if (dst != dssdev->dst) + return; + + dst->src = NULL; + dssdev->dst = NULL; + + in->ops.dpi->disconnect(in, &ddata->dssdev); +} + +static int tfp410_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_timings(in, &ddata->timings); + if (ddata->data_lines) + in->ops.dpi->set_data_lines(in, ddata->data_lines); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + if (gpio_is_valid(ddata->pd_gpio)) + gpio_set_value_cansleep(ddata->pd_gpio, 1); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void tfp410_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + if (gpio_is_valid(ddata->pd_gpio)) + gpio_set_value_cansleep(ddata->pd_gpio, 0); + + in->ops.dpi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void tfp410_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->timings = *timings; + dssdev->panel.timings = *timings; + + in->ops.dpi->set_timings(in, timings); +} + +static void tfp410_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->timings; +} + +static int tfp410_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dpi->check_timings(in, timings); +} + +static const struct omapdss_dvi_ops tfp410_dvi_ops = { + .connect = tfp410_connect, + .disconnect = tfp410_disconnect, + + .enable = tfp410_enable, + .disable = tfp410_disable, + + .check_timings = tfp410_check_timings, + .set_timings = tfp410_set_timings, + .get_timings = tfp410_get_timings, +}; + +static int tfp410_probe_pdata(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct encoder_tfp410_platform_data *pdata; + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&pdev->dev); + + ddata->pd_gpio = pdata->power_down_gpio; + + ddata->data_lines = pdata->data_lines; + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&pdev->dev, "Failed to find video source\n"); + return -ENODEV; + } + + ddata->in = in; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int tfp410_probe_of(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + struct omap_dss_device *in; + int gpio; + + gpio = of_get_named_gpio(node, "powerdown-gpios", 0); + + if (gpio_is_valid(gpio) || gpio == -ENOENT) { + ddata->pd_gpio = gpio; + } else { + dev_err(&pdev->dev, "failed to parse PD gpio\n"); + return gpio; + } + + in = omapdss_of_find_source_for_first_ep(node); + if (IS_ERR(in)) { + dev_err(&pdev->dev, "failed to find video source\n"); + return PTR_ERR(in); + } + + ddata->in = in; + + return 0; +} + +static int tfp410_probe(struct platform_device *pdev) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + + if (dev_get_platdata(&pdev->dev)) { + r = tfp410_probe_pdata(pdev); + if (r) + return r; + } else if (pdev->dev.of_node) { + r = tfp410_probe_of(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + if (gpio_is_valid(ddata->pd_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->pd_gpio, + GPIOF_OUT_INIT_LOW, "tfp410 PD"); + if (r) { + dev_err(&pdev->dev, "Failed to request PD GPIO %d\n", + ddata->pd_gpio); + goto err_gpio; + } + } + + dssdev = &ddata->dssdev; + dssdev->ops.dvi = &tfp410_dvi_ops; + dssdev->dev = &pdev->dev; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->output_type = OMAP_DISPLAY_TYPE_DVI; + dssdev->owner = THIS_MODULE; + dssdev->phy.dpi.data_lines = ddata->data_lines; + + r = omapdss_register_output(dssdev); + if (r) { + dev_err(&pdev->dev, "Failed to register output\n"); + goto err_reg; + } + + return 0; +err_reg: +err_gpio: + omap_dss_put_device(ddata->in); + return r; +} + +static int __exit tfp410_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_output(&ddata->dssdev); + + WARN_ON(omapdss_device_is_enabled(dssdev)); + if (omapdss_device_is_enabled(dssdev)) + tfp410_disable(dssdev); + + WARN_ON(omapdss_device_is_connected(dssdev)); + if (omapdss_device_is_connected(dssdev)) + tfp410_disconnect(dssdev, dssdev->dst); + + omap_dss_put_device(in); + + return 0; +} + +static const struct of_device_id tfp410_of_match[] = { + { .compatible = "omapdss,ti,tfp410", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, tfp410_of_match); + +static struct platform_driver tfp410_driver = { + .probe = tfp410_probe, + .remove = __exit_p(tfp410_remove), + .driver = { + .name = "tfp410", + .owner = THIS_MODULE, + .of_match_table = tfp410_of_match, + }, +}; + +module_platform_driver(tfp410_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("TFP410 DPI to DVI encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/encoder-tpd12s015.c b/drivers/video/fbdev/omap2/displays-new/encoder-tpd12s015.c new file mode 100644 index 000000000000..7e33686171e3 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/encoder-tpd12s015.c @@ -0,0 +1,451 @@ +/* + * TPD12S015 HDMI ESD protection & level shifter chip driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/of_gpio.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + int ct_cp_hpd_gpio; + int ls_oe_gpio; + int hpd_gpio; + + struct omap_video_timings timings; + + struct completion hpd_completion; +}; + +#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) + +static irqreturn_t tpd_hpd_irq_handler(int irq, void *data) +{ + struct panel_drv_data *ddata = data; + bool hpd; + + hpd = gpio_get_value_cansleep(ddata->hpd_gpio); + + dev_dbg(ddata->dssdev.dev, "hpd %d\n", hpd); + + if (gpio_is_valid(ddata->ls_oe_gpio)) { + if (hpd) + gpio_set_value_cansleep(ddata->ls_oe_gpio, 1); + else + gpio_set_value_cansleep(ddata->ls_oe_gpio, 0); + } + + complete_all(&ddata->hpd_completion); + + return IRQ_HANDLED; +} + +static int tpd_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + r = in->ops.hdmi->connect(in, dssdev); + if (r) + return r; + + dst->src = dssdev; + dssdev->dst = dst; + + reinit_completion(&ddata->hpd_completion); + + gpio_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1); + /* DC-DC converter needs at max 300us to get to 90% of 5V */ + udelay(300); + + /* + * If there's a cable connected, wait for the hpd irq to trigger, + * which turns on the level shifters. + */ + if (gpio_get_value_cansleep(ddata->hpd_gpio)) { + unsigned long to; + to = wait_for_completion_timeout(&ddata->hpd_completion, + msecs_to_jiffies(250)); + WARN_ON_ONCE(to == 0); + } + + return 0; +} + +static void tpd_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + WARN_ON(dst != dssdev->dst); + + if (dst != dssdev->dst) + return; + + gpio_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0); + + dst->src = NULL; + dssdev->dst = NULL; + + in->ops.hdmi->disconnect(in, &ddata->dssdev); +} + +static int tpd_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + return 0; + + in->ops.hdmi->set_timings(in, &ddata->timings); + + r = in->ops.hdmi->enable(in); + if (r) + return r; + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return r; +} + +static void tpd_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) + return; + + in->ops.hdmi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void tpd_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->timings = *timings; + dssdev->panel.timings = *timings; + + in->ops.hdmi->set_timings(in, timings); +} + +static void tpd_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->timings; +} + +static int tpd_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + r = in->ops.hdmi->check_timings(in, timings); + + return r; +} + +static int tpd_read_edid(struct omap_dss_device *dssdev, + u8 *edid, int len) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!gpio_get_value_cansleep(ddata->hpd_gpio)) + return -ENODEV; + + return in->ops.hdmi->read_edid(in, edid, len); +} + +static bool tpd_detect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + return gpio_get_value_cansleep(ddata->hpd_gpio); +} + +static int tpd_audio_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.hdmi->audio_enable(in); +} + +static void tpd_audio_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + in->ops.hdmi->audio_disable(in); +} + +static int tpd_audio_start(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.hdmi->audio_start(in); +} + +static void tpd_audio_stop(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + in->ops.hdmi->audio_stop(in); +} + +static bool tpd_audio_supported(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.hdmi->audio_supported(in); +} + +static int tpd_audio_config(struct omap_dss_device *dssdev, + struct omap_dss_audio *audio) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.hdmi->audio_config(in, audio); +} + +static const struct omapdss_hdmi_ops tpd_hdmi_ops = { + .connect = tpd_connect, + .disconnect = tpd_disconnect, + + .enable = tpd_enable, + .disable = tpd_disable, + + .check_timings = tpd_check_timings, + .set_timings = tpd_set_timings, + .get_timings = tpd_get_timings, + + .read_edid = tpd_read_edid, + .detect = tpd_detect, + + .audio_enable = tpd_audio_enable, + .audio_disable = tpd_audio_disable, + .audio_start = tpd_audio_start, + .audio_stop = tpd_audio_stop, + .audio_supported = tpd_audio_supported, + .audio_config = tpd_audio_config, +}; + +static int tpd_probe_pdata(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct encoder_tpd12s015_platform_data *pdata; + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&pdev->dev); + + ddata->ct_cp_hpd_gpio = pdata->ct_cp_hpd_gpio; + ddata->ls_oe_gpio = pdata->ls_oe_gpio; + ddata->hpd_gpio = pdata->hpd_gpio; + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&pdev->dev, "Failed to find video source\n"); + return -ENODEV; + } + + ddata->in = in; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int tpd_probe_of(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + struct omap_dss_device *in; + int gpio; + + /* CT CP HPD GPIO */ + gpio = of_get_gpio(node, 0); + if (!gpio_is_valid(gpio)) { + dev_err(&pdev->dev, "failed to parse CT CP HPD gpio\n"); + return gpio; + } + ddata->ct_cp_hpd_gpio = gpio; + + /* LS OE GPIO */ + gpio = of_get_gpio(node, 1); + if (gpio_is_valid(gpio) || gpio == -ENOENT) { + ddata->ls_oe_gpio = gpio; + } else { + dev_err(&pdev->dev, "failed to parse LS OE gpio\n"); + return gpio; + } + + /* HPD GPIO */ + gpio = of_get_gpio(node, 2); + if (!gpio_is_valid(gpio)) { + dev_err(&pdev->dev, "failed to parse HPD gpio\n"); + return gpio; + } + ddata->hpd_gpio = gpio; + + in = omapdss_of_find_source_for_first_ep(node); + if (IS_ERR(in)) { + dev_err(&pdev->dev, "failed to find video source\n"); + return PTR_ERR(in); + } + + ddata->in = in; + + return 0; +} + +static int tpd_probe(struct platform_device *pdev) +{ + struct omap_dss_device *in, *dssdev; + struct panel_drv_data *ddata; + int r; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + + init_completion(&ddata->hpd_completion); + + if (dev_get_platdata(&pdev->dev)) { + r = tpd_probe_pdata(pdev); + if (r) + return r; + } else if (pdev->dev.of_node) { + r = tpd_probe_of(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + r = devm_gpio_request_one(&pdev->dev, ddata->ct_cp_hpd_gpio, + GPIOF_OUT_INIT_LOW, "hdmi_ct_cp_hpd"); + if (r) + goto err_gpio; + + if (gpio_is_valid(ddata->ls_oe_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->ls_oe_gpio, + GPIOF_OUT_INIT_LOW, "hdmi_ls_oe"); + if (r) + goto err_gpio; + } + + r = devm_gpio_request_one(&pdev->dev, ddata->hpd_gpio, + GPIOF_DIR_IN, "hdmi_hpd"); + if (r) + goto err_gpio; + + r = devm_request_threaded_irq(&pdev->dev, gpio_to_irq(ddata->hpd_gpio), + NULL, tpd_hpd_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, "hpd", ddata); + if (r) + goto err_irq; + + dssdev = &ddata->dssdev; + dssdev->ops.hdmi = &tpd_hdmi_ops; + dssdev->dev = &pdev->dev; + dssdev->type = OMAP_DISPLAY_TYPE_HDMI; + dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI; + dssdev->owner = THIS_MODULE; + + in = ddata->in; + + r = omapdss_register_output(dssdev); + if (r) { + dev_err(&pdev->dev, "Failed to register output\n"); + goto err_reg; + } + + return 0; +err_reg: +err_irq: +err_gpio: + omap_dss_put_device(ddata->in); + return r; +} + +static int __exit tpd_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_output(&ddata->dssdev); + + WARN_ON(omapdss_device_is_enabled(dssdev)); + if (omapdss_device_is_enabled(dssdev)) + tpd_disable(dssdev); + + WARN_ON(omapdss_device_is_connected(dssdev)); + if (omapdss_device_is_connected(dssdev)) + tpd_disconnect(dssdev, dssdev->dst); + + omap_dss_put_device(in); + + return 0; +} + +static const struct of_device_id tpd_of_match[] = { + { .compatible = "omapdss,ti,tpd12s015", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, tpd_of_match); + +static struct platform_driver tpd_driver = { + .probe = tpd_probe, + .remove = __exit_p(tpd_remove), + .driver = { + .name = "tpd12s015", + .owner = THIS_MODULE, + .of_match_table = tpd_of_match, + }, +}; + +module_platform_driver(tpd_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("TPD12S015 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-dpi.c b/drivers/video/fbdev/omap2/displays-new/panel-dpi.c new file mode 100644 index 000000000000..5f8f7e7c81ef --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-dpi.c @@ -0,0 +1,270 @@ +/* + * Generic MIPI DPI Panel Driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + int data_lines; + + struct omap_video_timings videomode; + + int backlight_gpio; + int enable_gpio; +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static int panel_dpi_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void panel_dpi_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dpi->disconnect(in, dssdev); +} + +static int panel_dpi_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_data_lines(in, ddata->data_lines); + in->ops.dpi->set_timings(in, &ddata->videomode); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + if (gpio_is_valid(ddata->enable_gpio)) + gpio_set_value_cansleep(ddata->enable_gpio, 1); + + if (gpio_is_valid(ddata->backlight_gpio)) + gpio_set_value_cansleep(ddata->backlight_gpio, 1); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void panel_dpi_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + if (gpio_is_valid(ddata->enable_gpio)) + gpio_set_value_cansleep(ddata->enable_gpio, 0); + + if (gpio_is_valid(ddata->backlight_gpio)) + gpio_set_value_cansleep(ddata->backlight_gpio, 0); + + in->ops.dpi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void panel_dpi_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->videomode = *timings; + dssdev->panel.timings = *timings; + + in->ops.dpi->set_timings(in, timings); +} + +static void panel_dpi_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->videomode; +} + +static int panel_dpi_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dpi->check_timings(in, timings); +} + +static struct omap_dss_driver panel_dpi_ops = { + .connect = panel_dpi_connect, + .disconnect = panel_dpi_disconnect, + + .enable = panel_dpi_enable, + .disable = panel_dpi_disable, + + .set_timings = panel_dpi_set_timings, + .get_timings = panel_dpi_get_timings, + .check_timings = panel_dpi_check_timings, + + .get_resolution = omapdss_default_get_resolution, +}; + +static int panel_dpi_probe_pdata(struct platform_device *pdev) +{ + const struct panel_dpi_platform_data *pdata; + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev, *in; + struct videomode vm; + + pdata = dev_get_platdata(&pdev->dev); + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&pdev->dev, "failed to find video source '%s'\n", + pdata->source); + return -EPROBE_DEFER; + } + + ddata->in = in; + + ddata->data_lines = pdata->data_lines; + + videomode_from_timing(pdata->display_timing, &vm); + videomode_to_omap_video_timings(&vm, &ddata->videomode); + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + ddata->enable_gpio = pdata->enable_gpio; + ddata->backlight_gpio = pdata->backlight_gpio; + + return 0; +} + +static int panel_dpi_probe(struct platform_device *pdev) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + + if (dev_get_platdata(&pdev->dev)) { + r = panel_dpi_probe_pdata(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + if (gpio_is_valid(ddata->enable_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->enable_gpio, + GPIOF_OUT_INIT_LOW, "panel enable"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->backlight_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->backlight_gpio, + GPIOF_OUT_INIT_LOW, "panel backlight"); + if (r) + goto err_gpio; + } + + dssdev = &ddata->dssdev; + dssdev->dev = &pdev->dev; + dssdev->driver = &panel_dpi_ops; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = ddata->videomode; + dssdev->phy.dpi.data_lines = ddata->data_lines; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&pdev->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: +err_gpio: + omap_dss_put_device(ddata->in); + return r; +} + +static int __exit panel_dpi_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_display(dssdev); + + panel_dpi_disable(dssdev); + panel_dpi_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +static struct platform_driver panel_dpi_driver = { + .probe = panel_dpi_probe, + .remove = __exit_p(panel_dpi_remove), + .driver = { + .name = "panel-dpi", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(panel_dpi_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Generic MIPI DPI Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-dsi-cm.c b/drivers/video/fbdev/omap2/displays-new/panel-dsi-cm.c new file mode 100644 index 000000000000..d6f14e8717e8 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-dsi-cm.c @@ -0,0 +1,1388 @@ +/* + * Generic DSI Command Mode panel driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +/* #define DEBUG */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> +#include <video/mipi_display.h> + +/* DSI Virtual channel. Hardcoded for now. */ +#define TCH 0 + +#define DCS_READ_NUM_ERRORS 0x05 +#define DCS_BRIGHTNESS 0x51 +#define DCS_CTRL_DISPLAY 0x53 +#define DCS_GET_ID1 0xda +#define DCS_GET_ID2 0xdb +#define DCS_GET_ID3 0xdc + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + struct omap_video_timings timings; + + struct platform_device *pdev; + + struct mutex lock; + + struct backlight_device *bldev; + + unsigned long hw_guard_end; /* next value of jiffies when we can + * issue the next sleep in/out command + */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + /* panel HW configuration from DT or platform data */ + int reset_gpio; + int ext_te_gpio; + + bool use_dsi_backlight; + + struct omap_dsi_pin_config pin_config; + + /* runtime variables */ + bool enabled; + + bool te_enabled; + + atomic_t do_update; + int channel; + + struct delayed_work te_timeout_work; + + bool intro_printed; + + struct workqueue_struct *workqueue; + + bool ulps_enabled; + unsigned ulps_timeout; + struct delayed_work ulps_work; +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static irqreturn_t dsicm_te_isr(int irq, void *data); +static void dsicm_te_timeout_work_callback(struct work_struct *work); +static int _dsicm_enable_te(struct panel_drv_data *ddata, bool enable); + +static int dsicm_panel_reset(struct panel_drv_data *ddata); + +static void dsicm_ulps_work(struct work_struct *work); + +static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec) +{ + ddata->hw_guard_wait = msecs_to_jiffies(guard_msec); + ddata->hw_guard_end = jiffies + ddata->hw_guard_wait; +} + +static void hw_guard_wait(struct panel_drv_data *ddata) +{ + unsigned long wait = ddata->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= ddata->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static int dsicm_dcs_read_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 *data) +{ + struct omap_dss_device *in = ddata->in; + int r; + u8 buf[1]; + + r = in->ops.dsi->dcs_read(in, ddata->channel, dcs_cmd, buf, 1); + + if (r < 0) + return r; + + *data = buf[0]; + + return 0; +} + +static int dsicm_dcs_write_0(struct panel_drv_data *ddata, u8 dcs_cmd) +{ + struct omap_dss_device *in = ddata->in; + return in->ops.dsi->dcs_write(in, ddata->channel, &dcs_cmd, 1); +} + +static int dsicm_dcs_write_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 param) +{ + struct omap_dss_device *in = ddata->in; + u8 buf[2] = { dcs_cmd, param }; + + return in->ops.dsi->dcs_write(in, ddata->channel, buf, 2); +} + +static int dsicm_sleep_in(struct panel_drv_data *ddata) + +{ + struct omap_dss_device *in = ddata->in; + u8 cmd; + int r; + + hw_guard_wait(ddata); + + cmd = MIPI_DCS_ENTER_SLEEP_MODE; + r = in->ops.dsi->dcs_write_nosync(in, ddata->channel, &cmd, 1); + if (r) + return r; + + hw_guard_start(ddata, 120); + + usleep_range(5000, 10000); + + return 0; +} + +static int dsicm_sleep_out(struct panel_drv_data *ddata) +{ + int r; + + hw_guard_wait(ddata); + + r = dsicm_dcs_write_0(ddata, MIPI_DCS_EXIT_SLEEP_MODE); + if (r) + return r; + + hw_guard_start(ddata, 120); + + usleep_range(5000, 10000); + + return 0; +} + +static int dsicm_get_id(struct panel_drv_data *ddata, u8 *id1, u8 *id2, u8 *id3) +{ + int r; + + r = dsicm_dcs_read_1(ddata, DCS_GET_ID1, id1); + if (r) + return r; + r = dsicm_dcs_read_1(ddata, DCS_GET_ID2, id2); + if (r) + return r; + r = dsicm_dcs_read_1(ddata, DCS_GET_ID3, id3); + if (r) + return r; + + return 0; +} + +static int dsicm_set_update_window(struct panel_drv_data *ddata, + u16 x, u16 y, u16 w, u16 h) +{ + struct omap_dss_device *in = ddata->in; + int r; + u16 x1 = x; + u16 x2 = x + w - 1; + u16 y1 = y; + u16 y2 = y + h - 1; + + u8 buf[5]; + buf[0] = MIPI_DCS_SET_COLUMN_ADDRESS; + buf[1] = (x1 >> 8) & 0xff; + buf[2] = (x1 >> 0) & 0xff; + buf[3] = (x2 >> 8) & 0xff; + buf[4] = (x2 >> 0) & 0xff; + + r = in->ops.dsi->dcs_write_nosync(in, ddata->channel, buf, sizeof(buf)); + if (r) + return r; + + buf[0] = MIPI_DCS_SET_PAGE_ADDRESS; + buf[1] = (y1 >> 8) & 0xff; + buf[2] = (y1 >> 0) & 0xff; + buf[3] = (y2 >> 8) & 0xff; + buf[4] = (y2 >> 0) & 0xff; + + r = in->ops.dsi->dcs_write_nosync(in, ddata->channel, buf, sizeof(buf)); + if (r) + return r; + + in->ops.dsi->bta_sync(in, ddata->channel); + + return r; +} + +static void dsicm_queue_ulps_work(struct panel_drv_data *ddata) +{ + if (ddata->ulps_timeout > 0) + queue_delayed_work(ddata->workqueue, &ddata->ulps_work, + msecs_to_jiffies(ddata->ulps_timeout)); +} + +static void dsicm_cancel_ulps_work(struct panel_drv_data *ddata) +{ + cancel_delayed_work(&ddata->ulps_work); +} + +static int dsicm_enter_ulps(struct panel_drv_data *ddata) +{ + struct omap_dss_device *in = ddata->in; + int r; + + if (ddata->ulps_enabled) + return 0; + + dsicm_cancel_ulps_work(ddata); + + r = _dsicm_enable_te(ddata, false); + if (r) + goto err; + + if (gpio_is_valid(ddata->ext_te_gpio)) + disable_irq(gpio_to_irq(ddata->ext_te_gpio)); + + in->ops.dsi->disable(in, false, true); + + ddata->ulps_enabled = true; + + return 0; + +err: + dev_err(&ddata->pdev->dev, "enter ULPS failed"); + dsicm_panel_reset(ddata); + + ddata->ulps_enabled = false; + + dsicm_queue_ulps_work(ddata); + + return r; +} + +static int dsicm_exit_ulps(struct panel_drv_data *ddata) +{ + struct omap_dss_device *in = ddata->in; + int r; + + if (!ddata->ulps_enabled) + return 0; + + r = in->ops.dsi->enable(in); + if (r) { + dev_err(&ddata->pdev->dev, "failed to enable DSI\n"); + goto err1; + } + + in->ops.dsi->enable_hs(in, ddata->channel, true); + + r = _dsicm_enable_te(ddata, true); + if (r) { + dev_err(&ddata->pdev->dev, "failed to re-enable TE"); + goto err2; + } + + if (gpio_is_valid(ddata->ext_te_gpio)) + enable_irq(gpio_to_irq(ddata->ext_te_gpio)); + + dsicm_queue_ulps_work(ddata); + + ddata->ulps_enabled = false; + + return 0; + +err2: + dev_err(&ddata->pdev->dev, "failed to exit ULPS"); + + r = dsicm_panel_reset(ddata); + if (!r) { + if (gpio_is_valid(ddata->ext_te_gpio)) + enable_irq(gpio_to_irq(ddata->ext_te_gpio)); + ddata->ulps_enabled = false; + } +err1: + dsicm_queue_ulps_work(ddata); + + return r; +} + +static int dsicm_wake_up(struct panel_drv_data *ddata) +{ + if (ddata->ulps_enabled) + return dsicm_exit_ulps(ddata); + + dsicm_cancel_ulps_work(ddata); + dsicm_queue_ulps_work(ddata); + return 0; +} + +static int dsicm_bl_update_status(struct backlight_device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); + struct omap_dss_device *in = ddata->in; + int r; + int level; + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + level = dev->props.brightness; + else + level = 0; + + dev_dbg(&ddata->pdev->dev, "update brightness to %d\n", level); + + mutex_lock(&ddata->lock); + + if (ddata->enabled) { + in->ops.dsi->bus_lock(in); + + r = dsicm_wake_up(ddata); + if (!r) + r = dsicm_dcs_write_1(ddata, DCS_BRIGHTNESS, level); + + in->ops.dsi->bus_unlock(in); + } else { + r = 0; + } + + mutex_unlock(&ddata->lock); + + return r; +} + +static int dsicm_bl_get_intensity(struct backlight_device *dev) +{ + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + return dev->props.brightness; + + return 0; +} + +static const struct backlight_ops dsicm_bl_ops = { + .get_brightness = dsicm_bl_get_intensity, + .update_status = dsicm_bl_update_status, +}; + +static void dsicm_get_resolution(struct omap_dss_device *dssdev, + u16 *xres, u16 *yres) +{ + *xres = dssdev->panel.timings.x_res; + *yres = dssdev->panel.timings.y_res; +} + +static ssize_t dsicm_num_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *in = ddata->in; + u8 errors = 0; + int r; + + mutex_lock(&ddata->lock); + + if (ddata->enabled) { + in->ops.dsi->bus_lock(in); + + r = dsicm_wake_up(ddata); + if (!r) + r = dsicm_dcs_read_1(ddata, DCS_READ_NUM_ERRORS, + &errors); + + in->ops.dsi->bus_unlock(in); + } else { + r = -ENODEV; + } + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return snprintf(buf, PAGE_SIZE, "%d\n", errors); +} + +static ssize_t dsicm_hw_revision_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *in = ddata->in; + u8 id1, id2, id3; + int r; + + mutex_lock(&ddata->lock); + + if (ddata->enabled) { + in->ops.dsi->bus_lock(in); + + r = dsicm_wake_up(ddata); + if (!r) + r = dsicm_get_id(ddata, &id1, &id2, &id3); + + in->ops.dsi->bus_unlock(in); + } else { + r = -ENODEV; + } + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3); +} + +static ssize_t dsicm_store_ulps(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *in = ddata->in; + unsigned long t; + int r; + + r = kstrtoul(buf, 0, &t); + if (r) + return r; + + mutex_lock(&ddata->lock); + + if (ddata->enabled) { + in->ops.dsi->bus_lock(in); + + if (t) + r = dsicm_enter_ulps(ddata); + else + r = dsicm_wake_up(ddata); + + in->ops.dsi->bus_unlock(in); + } + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return count; +} + +static ssize_t dsicm_show_ulps(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + unsigned t; + + mutex_lock(&ddata->lock); + t = ddata->ulps_enabled; + mutex_unlock(&ddata->lock); + + return snprintf(buf, PAGE_SIZE, "%u\n", t); +} + +static ssize_t dsicm_store_ulps_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *in = ddata->in; + unsigned long t; + int r; + + r = kstrtoul(buf, 0, &t); + if (r) + return r; + + mutex_lock(&ddata->lock); + ddata->ulps_timeout = t; + + if (ddata->enabled) { + /* dsicm_wake_up will restart the timer */ + in->ops.dsi->bus_lock(in); + r = dsicm_wake_up(ddata); + in->ops.dsi->bus_unlock(in); + } + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return count; +} + +static ssize_t dsicm_show_ulps_timeout(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + unsigned t; + + mutex_lock(&ddata->lock); + t = ddata->ulps_timeout; + mutex_unlock(&ddata->lock); + + return snprintf(buf, PAGE_SIZE, "%u\n", t); +} + +static DEVICE_ATTR(num_dsi_errors, S_IRUGO, dsicm_num_errors_show, NULL); +static DEVICE_ATTR(hw_revision, S_IRUGO, dsicm_hw_revision_show, NULL); +static DEVICE_ATTR(ulps, S_IRUGO | S_IWUSR, + dsicm_show_ulps, dsicm_store_ulps); +static DEVICE_ATTR(ulps_timeout, S_IRUGO | S_IWUSR, + dsicm_show_ulps_timeout, dsicm_store_ulps_timeout); + +static struct attribute *dsicm_attrs[] = { + &dev_attr_num_dsi_errors.attr, + &dev_attr_hw_revision.attr, + &dev_attr_ulps.attr, + &dev_attr_ulps_timeout.attr, + NULL, +}; + +static struct attribute_group dsicm_attr_group = { + .attrs = dsicm_attrs, +}; + +static void dsicm_hw_reset(struct panel_drv_data *ddata) +{ + if (!gpio_is_valid(ddata->reset_gpio)) + return; + + gpio_set_value(ddata->reset_gpio, 1); + udelay(10); + /* reset the panel */ + gpio_set_value(ddata->reset_gpio, 0); + /* assert reset */ + udelay(10); + gpio_set_value(ddata->reset_gpio, 1); + /* wait after releasing reset */ + usleep_range(5000, 10000); +} + +static int dsicm_power_on(struct panel_drv_data *ddata) +{ + struct omap_dss_device *in = ddata->in; + u8 id1, id2, id3; + int r; + struct omap_dss_dsi_config dsi_config = { + .mode = OMAP_DSS_DSI_CMD_MODE, + .pixel_format = OMAP_DSS_DSI_FMT_RGB888, + .timings = &ddata->timings, + .hs_clk_min = 150000000, + .hs_clk_max = 300000000, + .lp_clk_min = 7000000, + .lp_clk_max = 10000000, + }; + + if (ddata->pin_config.num_pins > 0) { + r = in->ops.dsi->configure_pins(in, &ddata->pin_config); + if (r) { + dev_err(&ddata->pdev->dev, + "failed to configure DSI pins\n"); + goto err0; + } + } + + r = in->ops.dsi->set_config(in, &dsi_config); + if (r) { + dev_err(&ddata->pdev->dev, "failed to configure DSI\n"); + goto err0; + } + + r = in->ops.dsi->enable(in); + if (r) { + dev_err(&ddata->pdev->dev, "failed to enable DSI\n"); + goto err0; + } + + dsicm_hw_reset(ddata); + + in->ops.dsi->enable_hs(in, ddata->channel, false); + + r = dsicm_sleep_out(ddata); + if (r) + goto err; + + r = dsicm_get_id(ddata, &id1, &id2, &id3); + if (r) + goto err; + + r = dsicm_dcs_write_1(ddata, DCS_BRIGHTNESS, 0xff); + if (r) + goto err; + + r = dsicm_dcs_write_1(ddata, DCS_CTRL_DISPLAY, + (1<<2) | (1<<5)); /* BL | BCTRL */ + if (r) + goto err; + + r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_PIXEL_FORMAT, + MIPI_DCS_PIXEL_FMT_24BIT); + if (r) + goto err; + + r = dsicm_dcs_write_0(ddata, MIPI_DCS_SET_DISPLAY_ON); + if (r) + goto err; + + r = _dsicm_enable_te(ddata, ddata->te_enabled); + if (r) + goto err; + + r = in->ops.dsi->enable_video_output(in, ddata->channel); + if (r) + goto err; + + ddata->enabled = 1; + + if (!ddata->intro_printed) { + dev_info(&ddata->pdev->dev, "panel revision %02x.%02x.%02x\n", + id1, id2, id3); + ddata->intro_printed = true; + } + + in->ops.dsi->enable_hs(in, ddata->channel, true); + + return 0; +err: + dev_err(&ddata->pdev->dev, "error while enabling panel, issuing HW reset\n"); + + dsicm_hw_reset(ddata); + + in->ops.dsi->disable(in, true, false); +err0: + return r; +} + +static void dsicm_power_off(struct panel_drv_data *ddata) +{ + struct omap_dss_device *in = ddata->in; + int r; + + in->ops.dsi->disable_video_output(in, ddata->channel); + + r = dsicm_dcs_write_0(ddata, MIPI_DCS_SET_DISPLAY_OFF); + if (!r) + r = dsicm_sleep_in(ddata); + + if (r) { + dev_err(&ddata->pdev->dev, + "error disabling panel, issuing HW reset\n"); + dsicm_hw_reset(ddata); + } + + in->ops.dsi->disable(in, true, false); + + ddata->enabled = 0; +} + +static int dsicm_panel_reset(struct panel_drv_data *ddata) +{ + dev_err(&ddata->pdev->dev, "performing LCD reset\n"); + + dsicm_power_off(ddata); + dsicm_hw_reset(ddata); + return dsicm_power_on(ddata); +} + +static int dsicm_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + struct device *dev = &ddata->pdev->dev; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dsi->connect(in, dssdev); + if (r) { + dev_err(dev, "Failed to connect to video source\n"); + return r; + } + + r = in->ops.dsi->request_vc(ddata->in, &ddata->channel); + if (r) { + dev_err(dev, "failed to get virtual channel\n"); + goto err_req_vc; + } + + r = in->ops.dsi->set_vc_id(ddata->in, ddata->channel, TCH); + if (r) { + dev_err(dev, "failed to set VC_ID\n"); + goto err_vc_id; + } + + return 0; + +err_vc_id: + in->ops.dsi->release_vc(ddata->in, ddata->channel); +err_req_vc: + in->ops.dsi->disconnect(in, dssdev); + return r; +} + +static void dsicm_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dsi->release_vc(in, ddata->channel); + in->ops.dsi->disconnect(in, dssdev); +} + +static int dsicm_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(&ddata->pdev->dev, "enable\n"); + + mutex_lock(&ddata->lock); + + if (!omapdss_device_is_connected(dssdev)) { + r = -ENODEV; + goto err; + } + + if (omapdss_device_is_enabled(dssdev)) { + r = 0; + goto err; + } + + in->ops.dsi->bus_lock(in); + + r = dsicm_power_on(ddata); + + in->ops.dsi->bus_unlock(in); + + if (r) + goto err; + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + mutex_unlock(&ddata->lock); + + return 0; +err: + dev_dbg(&ddata->pdev->dev, "enable failed\n"); + mutex_unlock(&ddata->lock); + return r; +} + +static void dsicm_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(&ddata->pdev->dev, "disable\n"); + + mutex_lock(&ddata->lock); + + dsicm_cancel_ulps_work(ddata); + + in->ops.dsi->bus_lock(in); + + if (omapdss_device_is_enabled(dssdev)) { + r = dsicm_wake_up(ddata); + if (!r) + dsicm_power_off(ddata); + } + + in->ops.dsi->bus_unlock(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; + + mutex_unlock(&ddata->lock); +} + +static void dsicm_framedone_cb(int err, void *data) +{ + struct panel_drv_data *ddata = data; + struct omap_dss_device *in = ddata->in; + + dev_dbg(&ddata->pdev->dev, "framedone, err %d\n", err); + in->ops.dsi->bus_unlock(ddata->in); +} + +static irqreturn_t dsicm_te_isr(int irq, void *data) +{ + struct panel_drv_data *ddata = data; + struct omap_dss_device *in = ddata->in; + int old; + int r; + + old = atomic_cmpxchg(&ddata->do_update, 1, 0); + + if (old) { + cancel_delayed_work(&ddata->te_timeout_work); + + r = in->ops.dsi->update(in, ddata->channel, dsicm_framedone_cb, + ddata); + if (r) + goto err; + } + + return IRQ_HANDLED; +err: + dev_err(&ddata->pdev->dev, "start update failed\n"); + in->ops.dsi->bus_unlock(in); + return IRQ_HANDLED; +} + +static void dsicm_te_timeout_work_callback(struct work_struct *work) +{ + struct panel_drv_data *ddata = container_of(work, struct panel_drv_data, + te_timeout_work.work); + struct omap_dss_device *in = ddata->in; + + dev_err(&ddata->pdev->dev, "TE not received for 250ms!\n"); + + atomic_set(&ddata->do_update, 0); + in->ops.dsi->bus_unlock(in); +} + +static int dsicm_update(struct omap_dss_device *dssdev, + u16 x, u16 y, u16 w, u16 h) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(&ddata->pdev->dev, "update %d, %d, %d x %d\n", x, y, w, h); + + mutex_lock(&ddata->lock); + in->ops.dsi->bus_lock(in); + + r = dsicm_wake_up(ddata); + if (r) + goto err; + + if (!ddata->enabled) { + r = 0; + goto err; + } + + /* XXX no need to send this every frame, but dsi break if not done */ + r = dsicm_set_update_window(ddata, 0, 0, + dssdev->panel.timings.x_res, + dssdev->panel.timings.y_res); + if (r) + goto err; + + if (ddata->te_enabled && gpio_is_valid(ddata->ext_te_gpio)) { + schedule_delayed_work(&ddata->te_timeout_work, + msecs_to_jiffies(250)); + atomic_set(&ddata->do_update, 1); + } else { + r = in->ops.dsi->update(in, ddata->channel, dsicm_framedone_cb, + ddata); + if (r) + goto err; + } + + /* note: no bus_unlock here. unlock is in framedone_cb */ + mutex_unlock(&ddata->lock); + return 0; +err: + in->ops.dsi->bus_unlock(in); + mutex_unlock(&ddata->lock); + return r; +} + +static int dsicm_sync(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + dev_dbg(&ddata->pdev->dev, "sync\n"); + + mutex_lock(&ddata->lock); + in->ops.dsi->bus_lock(in); + in->ops.dsi->bus_unlock(in); + mutex_unlock(&ddata->lock); + + dev_dbg(&ddata->pdev->dev, "sync done\n"); + + return 0; +} + +static int _dsicm_enable_te(struct panel_drv_data *ddata, bool enable) +{ + struct omap_dss_device *in = ddata->in; + int r; + + if (enable) + r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_TEAR_ON, 0); + else + r = dsicm_dcs_write_0(ddata, MIPI_DCS_SET_TEAR_OFF); + + if (!gpio_is_valid(ddata->ext_te_gpio)) + in->ops.dsi->enable_te(in, enable); + + /* possible panel bug */ + msleep(100); + + return r; +} + +static int dsicm_enable_te(struct omap_dss_device *dssdev, bool enable) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + mutex_lock(&ddata->lock); + + if (ddata->te_enabled == enable) + goto end; + + in->ops.dsi->bus_lock(in); + + if (ddata->enabled) { + r = dsicm_wake_up(ddata); + if (r) + goto err; + + r = _dsicm_enable_te(ddata, enable); + if (r) + goto err; + } + + ddata->te_enabled = enable; + + in->ops.dsi->bus_unlock(in); +end: + mutex_unlock(&ddata->lock); + + return 0; +err: + in->ops.dsi->bus_unlock(in); + mutex_unlock(&ddata->lock); + + return r; +} + +static int dsicm_get_te(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + int r; + + mutex_lock(&ddata->lock); + r = ddata->te_enabled; + mutex_unlock(&ddata->lock); + + return r; +} + +static int dsicm_memory_read(struct omap_dss_device *dssdev, + void *buf, size_t size, + u16 x, u16 y, u16 w, u16 h) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + int first = 1; + int plen; + unsigned buf_used = 0; + + if (size < w * h * 3) + return -ENOMEM; + + mutex_lock(&ddata->lock); + + if (!ddata->enabled) { + r = -ENODEV; + goto err1; + } + + size = min(w * h * 3, + dssdev->panel.timings.x_res * + dssdev->panel.timings.y_res * 3); + + in->ops.dsi->bus_lock(in); + + r = dsicm_wake_up(ddata); + if (r) + goto err2; + + /* plen 1 or 2 goes into short packet. until checksum error is fixed, + * use short packets. plen 32 works, but bigger packets seem to cause + * an error. */ + if (size % 2) + plen = 1; + else + plen = 2; + + dsicm_set_update_window(ddata, x, y, w, h); + + r = in->ops.dsi->set_max_rx_packet_size(in, ddata->channel, plen); + if (r) + goto err2; + + while (buf_used < size) { + u8 dcs_cmd = first ? 0x2e : 0x3e; + first = 0; + + r = in->ops.dsi->dcs_read(in, ddata->channel, dcs_cmd, + buf + buf_used, size - buf_used); + + if (r < 0) { + dev_err(dssdev->dev, "read error\n"); + goto err3; + } + + buf_used += r; + + if (r < plen) { + dev_err(&ddata->pdev->dev, "short read\n"); + break; + } + + if (signal_pending(current)) { + dev_err(&ddata->pdev->dev, "signal pending, " + "aborting memory read\n"); + r = -ERESTARTSYS; + goto err3; + } + } + + r = buf_used; + +err3: + in->ops.dsi->set_max_rx_packet_size(in, ddata->channel, 1); +err2: + in->ops.dsi->bus_unlock(in); +err1: + mutex_unlock(&ddata->lock); + return r; +} + +static void dsicm_ulps_work(struct work_struct *work) +{ + struct panel_drv_data *ddata = container_of(work, struct panel_drv_data, + ulps_work.work); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + mutex_lock(&ddata->lock); + + if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || !ddata->enabled) { + mutex_unlock(&ddata->lock); + return; + } + + in->ops.dsi->bus_lock(in); + + dsicm_enter_ulps(ddata); + + in->ops.dsi->bus_unlock(in); + mutex_unlock(&ddata->lock); +} + +static struct omap_dss_driver dsicm_ops = { + .connect = dsicm_connect, + .disconnect = dsicm_disconnect, + + .enable = dsicm_enable, + .disable = dsicm_disable, + + .update = dsicm_update, + .sync = dsicm_sync, + + .get_resolution = dsicm_get_resolution, + .get_recommended_bpp = omapdss_default_get_recommended_bpp, + + .enable_te = dsicm_enable_te, + .get_te = dsicm_get_te, + + .memory_read = dsicm_memory_read, +}; + +static int dsicm_probe_pdata(struct platform_device *pdev) +{ + const struct panel_dsicm_platform_data *pdata; + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&pdev->dev); + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&pdev->dev, "failed to find video source\n"); + return -EPROBE_DEFER; + } + ddata->in = in; + + ddata->reset_gpio = pdata->reset_gpio; + + if (pdata->use_ext_te) + ddata->ext_te_gpio = pdata->ext_te_gpio; + else + ddata->ext_te_gpio = -1; + + ddata->ulps_timeout = pdata->ulps_timeout; + + ddata->use_dsi_backlight = pdata->use_dsi_backlight; + + ddata->pin_config = pdata->pin_config; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int dsicm_probe_of(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *in; + int gpio; + + gpio = of_get_named_gpio(node, "reset-gpios", 0); + if (!gpio_is_valid(gpio)) { + dev_err(&pdev->dev, "failed to parse reset gpio\n"); + return gpio; + } + ddata->reset_gpio = gpio; + + gpio = of_get_named_gpio(node, "te-gpios", 0); + if (gpio_is_valid(gpio) || gpio == -ENOENT) { + ddata->ext_te_gpio = gpio; + } else { + dev_err(&pdev->dev, "failed to parse TE gpio\n"); + return gpio; + } + + in = omapdss_of_find_source_for_first_ep(node); + if (IS_ERR(in)) { + dev_err(&pdev->dev, "failed to find video source\n"); + return PTR_ERR(in); + } + + ddata->in = in; + + /* TODO: ulps, backlight */ + + return 0; +} + +static int dsicm_probe(struct platform_device *pdev) +{ + struct backlight_properties props; + struct panel_drv_data *ddata; + struct backlight_device *bldev = NULL; + struct device *dev = &pdev->dev; + struct omap_dss_device *dssdev; + int r; + + dev_dbg(dev, "probe\n"); + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + ddata->pdev = pdev; + + if (dev_get_platdata(dev)) { + r = dsicm_probe_pdata(pdev); + if (r) + return r; + } else if (pdev->dev.of_node) { + r = dsicm_probe_of(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + ddata->timings.x_res = 864; + ddata->timings.y_res = 480; + ddata->timings.pixelclock = 864 * 480 * 60; + + dssdev = &ddata->dssdev; + dssdev->dev = dev; + dssdev->driver = &dsicm_ops; + dssdev->panel.timings = ddata->timings; + dssdev->type = OMAP_DISPLAY_TYPE_DSI; + dssdev->owner = THIS_MODULE; + + dssdev->panel.dsi_pix_fmt = OMAP_DSS_DSI_FMT_RGB888; + dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE | + OMAP_DSS_DISPLAY_CAP_TEAR_ELIM; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(dev, "Failed to register panel\n"); + goto err_reg; + } + + mutex_init(&ddata->lock); + + atomic_set(&ddata->do_update, 0); + + if (gpio_is_valid(ddata->reset_gpio)) { + r = devm_gpio_request_one(dev, ddata->reset_gpio, + GPIOF_OUT_INIT_LOW, "taal rst"); + if (r) { + dev_err(dev, "failed to request reset gpio\n"); + return r; + } + } + + if (gpio_is_valid(ddata->ext_te_gpio)) { + r = devm_gpio_request_one(dev, ddata->ext_te_gpio, + GPIOF_IN, "taal irq"); + if (r) { + dev_err(dev, "GPIO request failed\n"); + return r; + } + + r = devm_request_irq(dev, gpio_to_irq(ddata->ext_te_gpio), + dsicm_te_isr, + IRQF_TRIGGER_RISING, + "taal vsync", ddata); + + if (r) { + dev_err(dev, "IRQ request failed\n"); + return r; + } + + INIT_DEFERRABLE_WORK(&ddata->te_timeout_work, + dsicm_te_timeout_work_callback); + + dev_dbg(dev, "Using GPIO TE\n"); + } + + ddata->workqueue = create_singlethread_workqueue("dsicm_wq"); + if (ddata->workqueue == NULL) { + dev_err(dev, "can't create workqueue\n"); + return -ENOMEM; + } + INIT_DELAYED_WORK(&ddata->ulps_work, dsicm_ulps_work); + + dsicm_hw_reset(ddata); + + if (ddata->use_dsi_backlight) { + memset(&props, 0, sizeof(struct backlight_properties)); + props.max_brightness = 255; + + props.type = BACKLIGHT_RAW; + bldev = backlight_device_register(dev_name(dev), + dev, ddata, &dsicm_bl_ops, &props); + if (IS_ERR(bldev)) { + r = PTR_ERR(bldev); + goto err_bl; + } + + ddata->bldev = bldev; + + bldev->props.fb_blank = FB_BLANK_UNBLANK; + bldev->props.power = FB_BLANK_UNBLANK; + bldev->props.brightness = 255; + + dsicm_bl_update_status(bldev); + } + + r = sysfs_create_group(&dev->kobj, &dsicm_attr_group); + if (r) { + dev_err(dev, "failed to create sysfs files\n"); + goto err_sysfs_create; + } + + return 0; + +err_sysfs_create: + if (bldev != NULL) + backlight_device_unregister(bldev); +err_bl: + destroy_workqueue(ddata->workqueue); +err_reg: + return r; +} + +static int __exit dsicm_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct backlight_device *bldev; + + dev_dbg(&pdev->dev, "remove\n"); + + omapdss_unregister_display(dssdev); + + dsicm_disable(dssdev); + dsicm_disconnect(dssdev); + + sysfs_remove_group(&pdev->dev.kobj, &dsicm_attr_group); + + bldev = ddata->bldev; + if (bldev != NULL) { + bldev->props.power = FB_BLANK_POWERDOWN; + dsicm_bl_update_status(bldev); + backlight_device_unregister(bldev); + } + + omap_dss_put_device(ddata->in); + + dsicm_cancel_ulps_work(ddata); + destroy_workqueue(ddata->workqueue); + + /* reset, to be sure that the panel is in a valid state */ + dsicm_hw_reset(ddata); + + return 0; +} + +static const struct of_device_id dsicm_of_match[] = { + { .compatible = "omapdss,panel-dsi-cm", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dsicm_of_match); + +static struct platform_driver dsicm_driver = { + .probe = dsicm_probe, + .remove = __exit_p(dsicm_remove), + .driver = { + .name = "panel-dsi-cm", + .owner = THIS_MODULE, + .of_match_table = dsicm_of_match, + }, +}; + +module_platform_driver(dsicm_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Generic DSI Command Mode Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-lgphilips-lb035q02.c b/drivers/video/fbdev/omap2/displays-new/panel-lgphilips-lb035q02.c new file mode 100644 index 000000000000..2e6b513222d9 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-lgphilips-lb035q02.c @@ -0,0 +1,358 @@ +/* + * LG.Philips LB035Q02 LCD Panel driver + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * Based on a driver by: Steve Sakoman <steve@sakoman.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/mutex.h> +#include <linux/gpio.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +static struct omap_video_timings lb035q02_timings = { + .x_res = 320, + .y_res = 240, + + .pixelclock = 6500000, + + .hsw = 2, + .hfp = 20, + .hbp = 68, + + .vsw = 2, + .vfp = 4, + .vbp = 18, + + .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, + .de_level = OMAPDSS_SIG_ACTIVE_HIGH, + .sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, +}; + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + struct spi_device *spi; + + int data_lines; + + struct omap_video_timings videomode; + + int reset_gpio; + int backlight_gpio; + int enable_gpio; +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static int lb035q02_write_reg(struct spi_device *spi, u8 reg, u16 val) +{ + struct spi_message msg; + struct spi_transfer index_xfer = { + .len = 3, + .cs_change = 1, + }; + struct spi_transfer value_xfer = { + .len = 3, + }; + u8 buffer[16]; + + spi_message_init(&msg); + + /* register index */ + buffer[0] = 0x70; + buffer[1] = 0x00; + buffer[2] = reg & 0x7f; + index_xfer.tx_buf = buffer; + spi_message_add_tail(&index_xfer, &msg); + + /* register value */ + buffer[4] = 0x72; + buffer[5] = val >> 8; + buffer[6] = val; + value_xfer.tx_buf = buffer + 4; + spi_message_add_tail(&value_xfer, &msg); + + return spi_sync(spi, &msg); +} + +static void init_lb035q02_panel(struct spi_device *spi) +{ + /* Init sequence from page 28 of the lb035q02 spec */ + lb035q02_write_reg(spi, 0x01, 0x6300); + lb035q02_write_reg(spi, 0x02, 0x0200); + lb035q02_write_reg(spi, 0x03, 0x0177); + lb035q02_write_reg(spi, 0x04, 0x04c7); + lb035q02_write_reg(spi, 0x05, 0xffc0); + lb035q02_write_reg(spi, 0x06, 0xe806); + lb035q02_write_reg(spi, 0x0a, 0x4008); + lb035q02_write_reg(spi, 0x0b, 0x0000); + lb035q02_write_reg(spi, 0x0d, 0x0030); + lb035q02_write_reg(spi, 0x0e, 0x2800); + lb035q02_write_reg(spi, 0x0f, 0x0000); + lb035q02_write_reg(spi, 0x16, 0x9f80); + lb035q02_write_reg(spi, 0x17, 0x0a0f); + lb035q02_write_reg(spi, 0x1e, 0x00c1); + lb035q02_write_reg(spi, 0x30, 0x0300); + lb035q02_write_reg(spi, 0x31, 0x0007); + lb035q02_write_reg(spi, 0x32, 0x0000); + lb035q02_write_reg(spi, 0x33, 0x0000); + lb035q02_write_reg(spi, 0x34, 0x0707); + lb035q02_write_reg(spi, 0x35, 0x0004); + lb035q02_write_reg(spi, 0x36, 0x0302); + lb035q02_write_reg(spi, 0x37, 0x0202); + lb035q02_write_reg(spi, 0x3a, 0x0a0d); + lb035q02_write_reg(spi, 0x3b, 0x0806); +} + +static int lb035q02_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + init_lb035q02_panel(ddata->spi); + + return 0; +} + +static void lb035q02_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dpi->disconnect(in, dssdev); +} + +static int lb035q02_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_data_lines(in, ddata->data_lines); + in->ops.dpi->set_timings(in, &ddata->videomode); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + if (gpio_is_valid(ddata->enable_gpio)) + gpio_set_value_cansleep(ddata->enable_gpio, 1); + + if (gpio_is_valid(ddata->backlight_gpio)) + gpio_set_value_cansleep(ddata->backlight_gpio, 1); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void lb035q02_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + if (gpio_is_valid(ddata->enable_gpio)) + gpio_set_value_cansleep(ddata->enable_gpio, 0); + + if (gpio_is_valid(ddata->backlight_gpio)) + gpio_set_value_cansleep(ddata->backlight_gpio, 0); + + in->ops.dpi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void lb035q02_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->videomode = *timings; + dssdev->panel.timings = *timings; + + in->ops.dpi->set_timings(in, timings); +} + +static void lb035q02_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->videomode; +} + +static int lb035q02_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dpi->check_timings(in, timings); +} + +static struct omap_dss_driver lb035q02_ops = { + .connect = lb035q02_connect, + .disconnect = lb035q02_disconnect, + + .enable = lb035q02_enable, + .disable = lb035q02_disable, + + .set_timings = lb035q02_set_timings, + .get_timings = lb035q02_get_timings, + .check_timings = lb035q02_check_timings, + + .get_resolution = omapdss_default_get_resolution, +}; + +static int lb035q02_probe_pdata(struct spi_device *spi) +{ + const struct panel_lb035q02_platform_data *pdata; + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&spi->dev); + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&spi->dev, "failed to find video source '%s'\n", + pdata->source); + return -EPROBE_DEFER; + } + + ddata->in = in; + + ddata->data_lines = pdata->data_lines; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + ddata->enable_gpio = pdata->enable_gpio; + ddata->backlight_gpio = pdata->backlight_gpio; + + return 0; +} + +static int lb035q02_panel_spi_probe(struct spi_device *spi) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, ddata); + + ddata->spi = spi; + + if (dev_get_platdata(&spi->dev)) { + r = lb035q02_probe_pdata(spi); + if (r) + return r; + } else { + return -ENODEV; + } + + if (gpio_is_valid(ddata->enable_gpio)) { + r = devm_gpio_request_one(&spi->dev, ddata->enable_gpio, + GPIOF_OUT_INIT_LOW, "panel enable"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->backlight_gpio)) { + r = devm_gpio_request_one(&spi->dev, ddata->backlight_gpio, + GPIOF_OUT_INIT_LOW, "panel backlight"); + if (r) + goto err_gpio; + } + + ddata->videomode = lb035q02_timings; + + dssdev = &ddata->dssdev; + dssdev->dev = &spi->dev; + dssdev->driver = &lb035q02_ops; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = ddata->videomode; + dssdev->phy.dpi.data_lines = ddata->data_lines; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&spi->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: +err_gpio: + omap_dss_put_device(ddata->in); + return r; +} + +static int lb035q02_panel_spi_remove(struct spi_device *spi) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_display(dssdev); + + lb035q02_disable(dssdev); + lb035q02_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +static struct spi_driver lb035q02_spi_driver = { + .probe = lb035q02_panel_spi_probe, + .remove = lb035q02_panel_spi_remove, + .driver = { + .name = "panel_lgphilips_lb035q02", + .owner = THIS_MODULE, + }, +}; + +module_spi_driver(lb035q02_spi_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-nec-nl8048hl11.c b/drivers/video/fbdev/omap2/displays-new/panel-nec-nl8048hl11.c new file mode 100644 index 000000000000..996fa004b48c --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-nec-nl8048hl11.c @@ -0,0 +1,394 @@ +/* + * NEC NL8048HL11 Panel driver + * + * Copyright (C) 2010 Texas Instruments Inc. + * Author: Erik Gilling <konkers@android.com> + * Converted to new DSS device model: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/fb.h> +#include <linux/gpio.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + struct omap_video_timings videomode; + + int data_lines; + + int res_gpio; + int qvga_gpio; + + struct spi_device *spi; +}; + +#define LCD_XRES 800 +#define LCD_YRES 480 +/* + * NEC PIX Clock Ratings + * MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz + */ +#define LCD_PIXEL_CLOCK 23800000 + +static const struct { + unsigned char addr; + unsigned char dat; +} nec_8048_init_seq[] = { + { 3, 0x01 }, { 0, 0x00 }, { 1, 0x01 }, { 4, 0x00 }, { 5, 0x14 }, + { 6, 0x24 }, { 16, 0xD7 }, { 17, 0x00 }, { 18, 0x00 }, { 19, 0x55 }, + { 20, 0x01 }, { 21, 0x70 }, { 22, 0x1E }, { 23, 0x25 }, { 24, 0x25 }, + { 25, 0x02 }, { 26, 0x02 }, { 27, 0xA0 }, { 32, 0x2F }, { 33, 0x0F }, + { 34, 0x0F }, { 35, 0x0F }, { 36, 0x0F }, { 37, 0x0F }, { 38, 0x0F }, + { 39, 0x00 }, { 40, 0x02 }, { 41, 0x02 }, { 42, 0x02 }, { 43, 0x0F }, + { 44, 0x0F }, { 45, 0x0F }, { 46, 0x0F }, { 47, 0x0F }, { 48, 0x0F }, + { 49, 0x0F }, { 50, 0x00 }, { 51, 0x02 }, { 52, 0x02 }, { 53, 0x02 }, + { 80, 0x0C }, { 83, 0x42 }, { 84, 0x42 }, { 85, 0x41 }, { 86, 0x14 }, + { 89, 0x88 }, { 90, 0x01 }, { 91, 0x00 }, { 92, 0x02 }, { 93, 0x0C }, + { 94, 0x1C }, { 95, 0x27 }, { 98, 0x49 }, { 99, 0x27 }, { 102, 0x76 }, + { 103, 0x27 }, { 112, 0x01 }, { 113, 0x0E }, { 114, 0x02 }, + { 115, 0x0C }, { 118, 0x0C }, { 121, 0x30 }, { 130, 0x00 }, + { 131, 0x00 }, { 132, 0xFC }, { 134, 0x00 }, { 136, 0x00 }, + { 138, 0x00 }, { 139, 0x00 }, { 140, 0x00 }, { 141, 0xFC }, + { 143, 0x00 }, { 145, 0x00 }, { 147, 0x00 }, { 148, 0x00 }, + { 149, 0x00 }, { 150, 0xFC }, { 152, 0x00 }, { 154, 0x00 }, + { 156, 0x00 }, { 157, 0x00 }, { 2, 0x00 }, +}; + +static const struct omap_video_timings nec_8048_panel_timings = { + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .pixelclock = LCD_PIXEL_CLOCK, + .hfp = 6, + .hsw = 1, + .hbp = 4, + .vfp = 3, + .vsw = 1, + .vbp = 4, + + .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, + .de_level = OMAPDSS_SIG_ACTIVE_HIGH, + .sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static int nec_8048_spi_send(struct spi_device *spi, unsigned char reg_addr, + unsigned char reg_data) +{ + int ret = 0; + unsigned int cmd = 0, data = 0; + + cmd = 0x0000 | reg_addr; /* register address write */ + data = 0x0100 | reg_data; /* register data write */ + data = (cmd << 16) | data; + + ret = spi_write(spi, (unsigned char *)&data, 4); + if (ret) + pr_err("error in spi_write %x\n", data); + + return ret; +} + +static int init_nec_8048_wvga_lcd(struct spi_device *spi) +{ + unsigned int i; + /* Initialization Sequence */ + /* nec_8048_spi_send(spi, REG, VAL) */ + for (i = 0; i < (ARRAY_SIZE(nec_8048_init_seq) - 1); i++) + nec_8048_spi_send(spi, nec_8048_init_seq[i].addr, + nec_8048_init_seq[i].dat); + udelay(20); + nec_8048_spi_send(spi, nec_8048_init_seq[i].addr, + nec_8048_init_seq[i].dat); + return 0; +} + +static int nec_8048_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void nec_8048_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dpi->disconnect(in, dssdev); +} + +static int nec_8048_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_data_lines(in, ddata->data_lines); + in->ops.dpi->set_timings(in, &ddata->videomode); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + if (gpio_is_valid(ddata->res_gpio)) + gpio_set_value_cansleep(ddata->res_gpio, 1); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void nec_8048_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + if (gpio_is_valid(ddata->res_gpio)) + gpio_set_value_cansleep(ddata->res_gpio, 0); + + in->ops.dpi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void nec_8048_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->videomode = *timings; + dssdev->panel.timings = *timings; + + in->ops.dpi->set_timings(in, timings); +} + +static void nec_8048_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->videomode; +} + +static int nec_8048_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dpi->check_timings(in, timings); +} + +static struct omap_dss_driver nec_8048_ops = { + .connect = nec_8048_connect, + .disconnect = nec_8048_disconnect, + + .enable = nec_8048_enable, + .disable = nec_8048_disable, + + .set_timings = nec_8048_set_timings, + .get_timings = nec_8048_get_timings, + .check_timings = nec_8048_check_timings, + + .get_resolution = omapdss_default_get_resolution, +}; + + +static int nec_8048_probe_pdata(struct spi_device *spi) +{ + const struct panel_nec_nl8048hl11_platform_data *pdata; + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&spi->dev); + + ddata->qvga_gpio = pdata->qvga_gpio; + ddata->res_gpio = pdata->res_gpio; + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&spi->dev, "failed to find video source '%s'\n", + pdata->source); + return -EPROBE_DEFER; + } + ddata->in = in; + + ddata->data_lines = pdata->data_lines; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int nec_8048_probe(struct spi_device *spi) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + dev_dbg(&spi->dev, "%s\n", __func__); + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 32; + + r = spi_setup(spi); + if (r < 0) { + dev_err(&spi->dev, "spi_setup failed: %d\n", r); + return r; + } + + init_nec_8048_wvga_lcd(spi); + + ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, ddata); + + ddata->spi = spi; + + if (dev_get_platdata(&spi->dev)) { + r = nec_8048_probe_pdata(spi); + if (r) + return r; + } else { + return -ENODEV; + } + + if (gpio_is_valid(ddata->qvga_gpio)) { + r = devm_gpio_request_one(&spi->dev, ddata->qvga_gpio, + GPIOF_OUT_INIT_HIGH, "lcd QVGA"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->res_gpio)) { + r = devm_gpio_request_one(&spi->dev, ddata->res_gpio, + GPIOF_OUT_INIT_LOW, "lcd RES"); + if (r) + goto err_gpio; + } + + ddata->videomode = nec_8048_panel_timings; + + dssdev = &ddata->dssdev; + dssdev->dev = &spi->dev; + dssdev->driver = &nec_8048_ops; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = ddata->videomode; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&spi->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: +err_gpio: + omap_dss_put_device(ddata->in); + return r; +} + +static int nec_8048_remove(struct spi_device *spi) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + dev_dbg(&ddata->spi->dev, "%s\n", __func__); + + omapdss_unregister_display(dssdev); + + nec_8048_disable(dssdev); + nec_8048_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int nec_8048_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + nec_8048_spi_send(spi, 2, 0x01); + mdelay(40); + + return 0; +} + +static int nec_8048_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + /* reinitialize the panel */ + spi_setup(spi); + nec_8048_spi_send(spi, 2, 0x00); + init_nec_8048_wvga_lcd(spi); + + return 0; +} +static SIMPLE_DEV_PM_OPS(nec_8048_pm_ops, nec_8048_suspend, + nec_8048_resume); +#define NEC_8048_PM_OPS (&nec_8048_pm_ops) +#else +#define NEC_8048_PM_OPS NULL +#endif + +static struct spi_driver nec_8048_driver = { + .driver = { + .name = "panel-nec-nl8048hl11", + .owner = THIS_MODULE, + .pm = NEC_8048_PM_OPS, + }, + .probe = nec_8048_probe, + .remove = nec_8048_remove, +}; + +module_spi_driver(nec_8048_driver); + +MODULE_AUTHOR("Erik Gilling <konkers@android.com>"); +MODULE_DESCRIPTION("NEC-NL8048HL11 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-sharp-ls037v7dw01.c b/drivers/video/fbdev/omap2/displays-new/panel-sharp-ls037v7dw01.c new file mode 100644 index 000000000000..b2f710be565d --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-sharp-ls037v7dw01.c @@ -0,0 +1,324 @@ +/* + * LCD panel driver for Sharp LS037V7DW01 + * + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + int data_lines; + + struct omap_video_timings videomode; + + int resb_gpio; + int ini_gpio; + int mo_gpio; + int lr_gpio; + int ud_gpio; +}; + +static const struct omap_video_timings sharp_ls_timings = { + .x_res = 480, + .y_res = 640, + + .pixelclock = 19200000, + + .hsw = 2, + .hfp = 1, + .hbp = 28, + + .vsw = 1, + .vfp = 1, + .vbp = 1, + + .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, + .de_level = OMAPDSS_SIG_ACTIVE_HIGH, + .sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static int sharp_ls_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void sharp_ls_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dpi->disconnect(in, dssdev); +} + +static int sharp_ls_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_data_lines(in, ddata->data_lines); + in->ops.dpi->set_timings(in, &ddata->videomode); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + /* wait couple of vsyncs until enabling the LCD */ + msleep(50); + + if (gpio_is_valid(ddata->resb_gpio)) + gpio_set_value_cansleep(ddata->resb_gpio, 1); + + if (gpio_is_valid(ddata->ini_gpio)) + gpio_set_value_cansleep(ddata->ini_gpio, 1); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void sharp_ls_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + if (gpio_is_valid(ddata->ini_gpio)) + gpio_set_value_cansleep(ddata->ini_gpio, 0); + + if (gpio_is_valid(ddata->resb_gpio)) + gpio_set_value_cansleep(ddata->resb_gpio, 0); + + /* wait at least 5 vsyncs after disabling the LCD */ + + msleep(100); + + in->ops.dpi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void sharp_ls_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->videomode = *timings; + dssdev->panel.timings = *timings; + + in->ops.dpi->set_timings(in, timings); +} + +static void sharp_ls_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->videomode; +} + +static int sharp_ls_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dpi->check_timings(in, timings); +} + +static struct omap_dss_driver sharp_ls_ops = { + .connect = sharp_ls_connect, + .disconnect = sharp_ls_disconnect, + + .enable = sharp_ls_enable, + .disable = sharp_ls_disable, + + .set_timings = sharp_ls_set_timings, + .get_timings = sharp_ls_get_timings, + .check_timings = sharp_ls_check_timings, + + .get_resolution = omapdss_default_get_resolution, +}; + +static int sharp_ls_probe_pdata(struct platform_device *pdev) +{ + const struct panel_sharp_ls037v7dw01_platform_data *pdata; + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&pdev->dev); + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&pdev->dev, "failed to find video source '%s'\n", + pdata->source); + return -EPROBE_DEFER; + } + + ddata->in = in; + + ddata->data_lines = pdata->data_lines; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + ddata->resb_gpio = pdata->resb_gpio; + ddata->ini_gpio = pdata->ini_gpio; + ddata->mo_gpio = pdata->mo_gpio; + ddata->lr_gpio = pdata->lr_gpio; + ddata->ud_gpio = pdata->ud_gpio; + + return 0; +} + +static int sharp_ls_probe(struct platform_device *pdev) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + + if (dev_get_platdata(&pdev->dev)) { + r = sharp_ls_probe_pdata(pdev); + if (r) + return r; + } else { + return -ENODEV; + } + + if (gpio_is_valid(ddata->mo_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->mo_gpio, + GPIOF_OUT_INIT_LOW, "lcd MO"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->lr_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->lr_gpio, + GPIOF_OUT_INIT_HIGH, "lcd LR"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->ud_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->ud_gpio, + GPIOF_OUT_INIT_HIGH, "lcd UD"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->resb_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->resb_gpio, + GPIOF_OUT_INIT_LOW, "lcd RESB"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->ini_gpio)) { + r = devm_gpio_request_one(&pdev->dev, ddata->ini_gpio, + GPIOF_OUT_INIT_LOW, "lcd INI"); + if (r) + goto err_gpio; + } + + ddata->videomode = sharp_ls_timings; + + dssdev = &ddata->dssdev; + dssdev->dev = &pdev->dev; + dssdev->driver = &sharp_ls_ops; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = ddata->videomode; + dssdev->phy.dpi.data_lines = ddata->data_lines; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&pdev->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: +err_gpio: + omap_dss_put_device(ddata->in); + return r; +} + +static int __exit sharp_ls_remove(struct platform_device *pdev) +{ + struct panel_drv_data *ddata = platform_get_drvdata(pdev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + omapdss_unregister_display(dssdev); + + sharp_ls_disable(dssdev); + sharp_ls_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +static struct platform_driver sharp_ls_driver = { + .probe = sharp_ls_probe, + .remove = __exit_p(sharp_ls_remove), + .driver = { + .name = "panel-sharp-ls037v7dw01", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(sharp_ls_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-sony-acx565akm.c b/drivers/video/fbdev/omap2/displays-new/panel-sony-acx565akm.c new file mode 100644 index 000000000000..c7ba4d8b928a --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-sony-acx565akm.c @@ -0,0 +1,911 @@ +/* + * Sony ACX565AKM LCD Panel driver + * + * Copyright (C) 2010 Nokia Corporation + * + * Original Driver Author: Imre Deak <imre.deak@nokia.com> + * Based on panel-generic.c by Tomi Valkeinen <tomi.valkeinen@nokia.com> + * Adapted to new DSS2 framework: Roger Quadros <roger.quadros@nokia.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/backlight.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_gpio.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +#define MIPID_CMD_READ_DISP_ID 0x04 +#define MIPID_CMD_READ_RED 0x06 +#define MIPID_CMD_READ_GREEN 0x07 +#define MIPID_CMD_READ_BLUE 0x08 +#define MIPID_CMD_READ_DISP_STATUS 0x09 +#define MIPID_CMD_RDDSDR 0x0F +#define MIPID_CMD_SLEEP_IN 0x10 +#define MIPID_CMD_SLEEP_OUT 0x11 +#define MIPID_CMD_DISP_OFF 0x28 +#define MIPID_CMD_DISP_ON 0x29 +#define MIPID_CMD_WRITE_DISP_BRIGHTNESS 0x51 +#define MIPID_CMD_READ_DISP_BRIGHTNESS 0x52 +#define MIPID_CMD_WRITE_CTRL_DISP 0x53 + +#define CTRL_DISP_BRIGHTNESS_CTRL_ON (1 << 5) +#define CTRL_DISP_AMBIENT_LIGHT_CTRL_ON (1 << 4) +#define CTRL_DISP_BACKLIGHT_ON (1 << 2) +#define CTRL_DISP_AUTO_BRIGHTNESS_ON (1 << 1) + +#define MIPID_CMD_READ_CTRL_DISP 0x54 +#define MIPID_CMD_WRITE_CABC 0x55 +#define MIPID_CMD_READ_CABC 0x56 + +#define MIPID_VER_LPH8923 3 +#define MIPID_VER_LS041Y3 4 +#define MIPID_VER_L4F00311 8 +#define MIPID_VER_ACX565AKM 9 + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + int reset_gpio; + int datapairs; + + struct omap_video_timings videomode; + + char *name; + int enabled; + int model; + int revision; + u8 display_id[3]; + unsigned has_bc:1; + unsigned has_cabc:1; + unsigned cabc_mode; + unsigned long hw_guard_end; /* next value of jiffies + when we can issue the + next sleep in/out command */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + struct spi_device *spi; + struct mutex mutex; + + struct backlight_device *bl_dev; +}; + +static const struct omap_video_timings acx565akm_panel_timings = { + .x_res = 800, + .y_res = 480, + .pixelclock = 24000000, + .hfp = 28, + .hsw = 4, + .hbp = 24, + .vfp = 3, + .vsw = 3, + .vbp = 4, + + .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, + + .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, + .de_level = OMAPDSS_SIG_ACTIVE_HIGH, + .sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static void acx565akm_transfer(struct panel_drv_data *ddata, int cmd, + const u8 *wbuf, int wlen, u8 *rbuf, int rlen) +{ + struct spi_message m; + struct spi_transfer *x, xfer[5]; + int r; + + BUG_ON(ddata->spi == NULL); + + spi_message_init(&m); + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + cmd &= 0xff; + x->tx_buf = &cmd; + x->bits_per_word = 9; + x->len = 2; + + if (rlen > 1 && wlen == 0) { + /* + * Between the command and the response data there is a + * dummy clock cycle. Add an extra bit after the command + * word to account for this. + */ + x->bits_per_word = 10; + cmd <<= 1; + } + spi_message_add_tail(x, &m); + + if (wlen) { + x++; + x->tx_buf = wbuf; + x->len = wlen; + x->bits_per_word = 9; + spi_message_add_tail(x, &m); + } + + if (rlen) { + x++; + x->rx_buf = rbuf; + x->len = rlen; + spi_message_add_tail(x, &m); + } + + r = spi_sync(ddata->spi, &m); + if (r < 0) + dev_dbg(&ddata->spi->dev, "spi_sync %d\n", r); +} + +static inline void acx565akm_cmd(struct panel_drv_data *ddata, int cmd) +{ + acx565akm_transfer(ddata, cmd, NULL, 0, NULL, 0); +} + +static inline void acx565akm_write(struct panel_drv_data *ddata, + int reg, const u8 *buf, int len) +{ + acx565akm_transfer(ddata, reg, buf, len, NULL, 0); +} + +static inline void acx565akm_read(struct panel_drv_data *ddata, + int reg, u8 *buf, int len) +{ + acx565akm_transfer(ddata, reg, NULL, 0, buf, len); +} + +static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec) +{ + ddata->hw_guard_wait = msecs_to_jiffies(guard_msec); + ddata->hw_guard_end = jiffies + ddata->hw_guard_wait; +} + +static void hw_guard_wait(struct panel_drv_data *ddata) +{ + unsigned long wait = ddata->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= ddata->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static void set_sleep_mode(struct panel_drv_data *ddata, int on) +{ + int cmd; + + if (on) + cmd = MIPID_CMD_SLEEP_IN; + else + cmd = MIPID_CMD_SLEEP_OUT; + /* + * We have to keep 120msec between sleep in/out commands. + * (8.2.15, 8.2.16). + */ + hw_guard_wait(ddata); + acx565akm_cmd(ddata, cmd); + hw_guard_start(ddata, 120); +} + +static void set_display_state(struct panel_drv_data *ddata, int enabled) +{ + int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF; + + acx565akm_cmd(ddata, cmd); +} + +static int panel_enabled(struct panel_drv_data *ddata) +{ + u32 disp_status; + int enabled; + + acx565akm_read(ddata, MIPID_CMD_READ_DISP_STATUS, + (u8 *)&disp_status, 4); + disp_status = __be32_to_cpu(disp_status); + enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10)); + dev_dbg(&ddata->spi->dev, + "LCD panel %senabled by bootloader (status 0x%04x)\n", + enabled ? "" : "not ", disp_status); + return enabled; +} + +static int panel_detect(struct panel_drv_data *ddata) +{ + acx565akm_read(ddata, MIPID_CMD_READ_DISP_ID, ddata->display_id, 3); + dev_dbg(&ddata->spi->dev, "MIPI display ID: %02x%02x%02x\n", + ddata->display_id[0], + ddata->display_id[1], + ddata->display_id[2]); + + switch (ddata->display_id[0]) { + case 0x10: + ddata->model = MIPID_VER_ACX565AKM; + ddata->name = "acx565akm"; + ddata->has_bc = 1; + ddata->has_cabc = 1; + break; + case 0x29: + ddata->model = MIPID_VER_L4F00311; + ddata->name = "l4f00311"; + break; + case 0x45: + ddata->model = MIPID_VER_LPH8923; + ddata->name = "lph8923"; + break; + case 0x83: + ddata->model = MIPID_VER_LS041Y3; + ddata->name = "ls041y3"; + break; + default: + ddata->name = "unknown"; + dev_err(&ddata->spi->dev, "invalid display ID\n"); + return -ENODEV; + } + + ddata->revision = ddata->display_id[1]; + + dev_info(&ddata->spi->dev, "omapfb: %s rev %02x LCD detected\n", + ddata->name, ddata->revision); + + return 0; +} + +/*----------------------Backlight Control-------------------------*/ + +static void enable_backlight_ctrl(struct panel_drv_data *ddata, int enable) +{ + u16 ctrl; + + acx565akm_read(ddata, MIPID_CMD_READ_CTRL_DISP, (u8 *)&ctrl, 1); + if (enable) { + ctrl |= CTRL_DISP_BRIGHTNESS_CTRL_ON | + CTRL_DISP_BACKLIGHT_ON; + } else { + ctrl &= ~(CTRL_DISP_BRIGHTNESS_CTRL_ON | + CTRL_DISP_BACKLIGHT_ON); + } + + ctrl |= 1 << 8; + acx565akm_write(ddata, MIPID_CMD_WRITE_CTRL_DISP, (u8 *)&ctrl, 2); +} + +static void set_cabc_mode(struct panel_drv_data *ddata, unsigned mode) +{ + u16 cabc_ctrl; + + ddata->cabc_mode = mode; + if (!ddata->enabled) + return; + cabc_ctrl = 0; + acx565akm_read(ddata, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1); + cabc_ctrl &= ~3; + cabc_ctrl |= (1 << 8) | (mode & 3); + acx565akm_write(ddata, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2); +} + +static unsigned get_cabc_mode(struct panel_drv_data *ddata) +{ + return ddata->cabc_mode; +} + +static unsigned get_hw_cabc_mode(struct panel_drv_data *ddata) +{ + u8 cabc_ctrl; + + acx565akm_read(ddata, MIPID_CMD_READ_CABC, &cabc_ctrl, 1); + return cabc_ctrl & 3; +} + +static void acx565akm_set_brightness(struct panel_drv_data *ddata, int level) +{ + int bv; + + bv = level | (1 << 8); + acx565akm_write(ddata, MIPID_CMD_WRITE_DISP_BRIGHTNESS, (u8 *)&bv, 2); + + if (level) + enable_backlight_ctrl(ddata, 1); + else + enable_backlight_ctrl(ddata, 0); +} + +static int acx565akm_get_actual_brightness(struct panel_drv_data *ddata) +{ + u8 bv; + + acx565akm_read(ddata, MIPID_CMD_READ_DISP_BRIGHTNESS, &bv, 1); + + return bv; +} + + +static int acx565akm_bl_update_status(struct backlight_device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); + int level; + + dev_dbg(&ddata->spi->dev, "%s\n", __func__); + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + level = dev->props.brightness; + else + level = 0; + + if (ddata->has_bc) + acx565akm_set_brightness(ddata, level); + else + return -ENODEV; + + return 0; +} + +static int acx565akm_bl_get_intensity(struct backlight_device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); + + dev_dbg(&dev->dev, "%s\n", __func__); + + if (!ddata->has_bc) + return -ENODEV; + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) { + if (ddata->has_bc) + return acx565akm_get_actual_brightness(ddata); + else + return dev->props.brightness; + } + + return 0; +} + +static int acx565akm_bl_update_status_locked(struct backlight_device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); + int r; + + mutex_lock(&ddata->mutex); + r = acx565akm_bl_update_status(dev); + mutex_unlock(&ddata->mutex); + + return r; +} + +static int acx565akm_bl_get_intensity_locked(struct backlight_device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); + int r; + + mutex_lock(&ddata->mutex); + r = acx565akm_bl_get_intensity(dev); + mutex_unlock(&ddata->mutex); + + return r; +} + +static const struct backlight_ops acx565akm_bl_ops = { + .get_brightness = acx565akm_bl_get_intensity_locked, + .update_status = acx565akm_bl_update_status_locked, +}; + +/*--------------------Auto Brightness control via Sysfs---------------------*/ + +static const char * const cabc_modes[] = { + "off", /* always used when CABC is not supported */ + "ui", + "still-image", + "moving-image", +}; + +static ssize_t show_cabc_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + const char *mode_str; + int mode; + int len; + + if (!ddata->has_cabc) + mode = 0; + else + mode = get_cabc_mode(ddata); + mode_str = "unknown"; + if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes)) + mode_str = cabc_modes[mode]; + len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str); + + return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1; +} + +static ssize_t store_cabc_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + int i; + + for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) { + const char *mode_str = cabc_modes[i]; + int cmp_len = strlen(mode_str); + + if (count > 0 && buf[count - 1] == '\n') + count--; + if (count != cmp_len) + continue; + + if (strncmp(buf, mode_str, cmp_len) == 0) + break; + } + + if (i == ARRAY_SIZE(cabc_modes)) + return -EINVAL; + + if (!ddata->has_cabc && i != 0) + return -EINVAL; + + mutex_lock(&ddata->mutex); + set_cabc_mode(ddata, i); + mutex_unlock(&ddata->mutex); + + return count; +} + +static ssize_t show_cabc_available_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + int len; + int i; + + if (!ddata->has_cabc) + return snprintf(buf, PAGE_SIZE, "%s\n", cabc_modes[0]); + + for (i = 0, len = 0; + len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++) + len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s", + i ? " " : "", cabc_modes[i], + i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : ""); + + return len < PAGE_SIZE ? len : PAGE_SIZE - 1; +} + +static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR, + show_cabc_mode, store_cabc_mode); +static DEVICE_ATTR(cabc_available_modes, S_IRUGO, + show_cabc_available_modes, NULL); + +static struct attribute *bldev_attrs[] = { + &dev_attr_cabc_mode.attr, + &dev_attr_cabc_available_modes.attr, + NULL, +}; + +static struct attribute_group bldev_attr_group = { + .attrs = bldev_attrs, +}; + +static int acx565akm_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.sdi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void acx565akm_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.sdi->disconnect(in, dssdev); +} + +static int acx565akm_panel_power_on(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + dev_dbg(&ddata->spi->dev, "%s\n", __func__); + + in->ops.sdi->set_timings(in, &ddata->videomode); + + if (ddata->datapairs > 0) + in->ops.sdi->set_datapairs(in, ddata->datapairs); + + r = in->ops.sdi->enable(in); + if (r) { + pr_err("%s sdi enable failed\n", __func__); + return r; + } + + /*FIXME tweak me */ + msleep(50); + + if (gpio_is_valid(ddata->reset_gpio)) + gpio_set_value(ddata->reset_gpio, 1); + + if (ddata->enabled) { + dev_dbg(&ddata->spi->dev, "panel already enabled\n"); + return 0; + } + + /* + * We have to meet all the following delay requirements: + * 1. tRW: reset pulse width 10usec (7.12.1) + * 2. tRT: reset cancel time 5msec (7.12.1) + * 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst + * case (7.6.2) + * 4. 120msec before the sleep out command (7.12.1) + */ + msleep(120); + + set_sleep_mode(ddata, 0); + ddata->enabled = 1; + + /* 5msec between sleep out and the next command. (8.2.16) */ + usleep_range(5000, 10000); + set_display_state(ddata, 1); + set_cabc_mode(ddata, ddata->cabc_mode); + + return acx565akm_bl_update_status(ddata->bl_dev); +} + +static void acx565akm_panel_power_off(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + dev_dbg(dssdev->dev, "%s\n", __func__); + + if (!ddata->enabled) + return; + + set_display_state(ddata, 0); + set_sleep_mode(ddata, 1); + ddata->enabled = 0; + /* + * We have to provide PCLK,HS,VS signals for 2 frames (worst case + * ~50msec) after sending the sleep in command and asserting the + * reset signal. We probably could assert the reset w/o the delay + * but we still delay to avoid possible artifacts. (7.6.1) + */ + msleep(50); + + if (gpio_is_valid(ddata->reset_gpio)) + gpio_set_value(ddata->reset_gpio, 0); + + /* FIXME need to tweak this delay */ + msleep(100); + + in->ops.sdi->disable(in); +} + +static int acx565akm_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + int r; + + dev_dbg(dssdev->dev, "%s\n", __func__); + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + mutex_lock(&ddata->mutex); + r = acx565akm_panel_power_on(dssdev); + mutex_unlock(&ddata->mutex); + if (r) + return r; + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void acx565akm_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + dev_dbg(dssdev->dev, "%s\n", __func__); + + if (!omapdss_device_is_enabled(dssdev)) + return; + + mutex_lock(&ddata->mutex); + acx565akm_panel_power_off(dssdev); + mutex_unlock(&ddata->mutex); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void acx565akm_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->videomode = *timings; + dssdev->panel.timings = *timings; + + in->ops.sdi->set_timings(in, timings); +} + +static void acx565akm_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->videomode; +} + +static int acx565akm_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.sdi->check_timings(in, timings); +} + +static struct omap_dss_driver acx565akm_ops = { + .connect = acx565akm_connect, + .disconnect = acx565akm_disconnect, + + .enable = acx565akm_enable, + .disable = acx565akm_disable, + + .set_timings = acx565akm_set_timings, + .get_timings = acx565akm_get_timings, + .check_timings = acx565akm_check_timings, + + .get_resolution = omapdss_default_get_resolution, +}; + +static int acx565akm_probe_pdata(struct spi_device *spi) +{ + const struct panel_acx565akm_platform_data *pdata; + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&spi->dev); + + ddata->reset_gpio = pdata->reset_gpio; + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&spi->dev, "failed to find video source '%s'\n", + pdata->source); + return -EPROBE_DEFER; + } + ddata->in = in; + + ddata->datapairs = pdata->datapairs; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int acx565akm_probe_of(struct spi_device *spi) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct device_node *np = spi->dev.of_node; + + ddata->reset_gpio = of_get_named_gpio(np, "reset-gpios", 0); + + ddata->in = omapdss_of_find_source_for_first_ep(np); + if (IS_ERR(ddata->in)) { + dev_err(&spi->dev, "failed to find video source\n"); + return PTR_ERR(ddata->in); + } + + return 0; +} + +static int acx565akm_probe(struct spi_device *spi) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + struct backlight_device *bldev; + int max_brightness, brightness; + struct backlight_properties props; + int r; + + dev_dbg(&spi->dev, "%s\n", __func__); + + spi->mode = SPI_MODE_3; + + ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, ddata); + + ddata->spi = spi; + + mutex_init(&ddata->mutex); + + if (dev_get_platdata(&spi->dev)) { + r = acx565akm_probe_pdata(spi); + if (r) + return r; + } else if (spi->dev.of_node) { + r = acx565akm_probe_of(spi); + if (r) + return r; + } else { + dev_err(&spi->dev, "platform data missing!\n"); + return -ENODEV; + } + + if (gpio_is_valid(ddata->reset_gpio)) { + r = devm_gpio_request_one(&spi->dev, ddata->reset_gpio, + GPIOF_OUT_INIT_LOW, "lcd reset"); + if (r) + goto err_gpio; + } + + if (gpio_is_valid(ddata->reset_gpio)) + gpio_set_value(ddata->reset_gpio, 1); + + /* + * After reset we have to wait 5 msec before the first + * command can be sent. + */ + usleep_range(5000, 10000); + + ddata->enabled = panel_enabled(ddata); + + r = panel_detect(ddata); + + if (!ddata->enabled && gpio_is_valid(ddata->reset_gpio)) + gpio_set_value(ddata->reset_gpio, 0); + + if (r) { + dev_err(&spi->dev, "%s panel detect error\n", __func__); + goto err_detect; + } + + memset(&props, 0, sizeof(props)); + props.fb_blank = FB_BLANK_UNBLANK; + props.power = FB_BLANK_UNBLANK; + props.type = BACKLIGHT_RAW; + + bldev = backlight_device_register("acx565akm", &ddata->spi->dev, + ddata, &acx565akm_bl_ops, &props); + ddata->bl_dev = bldev; + if (ddata->has_cabc) { + r = sysfs_create_group(&bldev->dev.kobj, &bldev_attr_group); + if (r) { + dev_err(&bldev->dev, + "%s failed to create sysfs files\n", __func__); + goto err_sysfs; + } + ddata->cabc_mode = get_hw_cabc_mode(ddata); + } + + max_brightness = 255; + + if (ddata->has_bc) + brightness = acx565akm_get_actual_brightness(ddata); + else + brightness = 0; + + bldev->props.max_brightness = max_brightness; + bldev->props.brightness = brightness; + + acx565akm_bl_update_status(bldev); + + + ddata->videomode = acx565akm_panel_timings; + + dssdev = &ddata->dssdev; + dssdev->dev = &spi->dev; + dssdev->driver = &acx565akm_ops; + dssdev->type = OMAP_DISPLAY_TYPE_SDI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = ddata->videomode; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&spi->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: + sysfs_remove_group(&bldev->dev.kobj, &bldev_attr_group); +err_sysfs: + backlight_device_unregister(bldev); +err_detect: +err_gpio: + omap_dss_put_device(ddata->in); + return r; +} + +static int acx565akm_remove(struct spi_device *spi) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + dev_dbg(&ddata->spi->dev, "%s\n", __func__); + + sysfs_remove_group(&ddata->bl_dev->dev.kobj, &bldev_attr_group); + backlight_device_unregister(ddata->bl_dev); + + omapdss_unregister_display(dssdev); + + acx565akm_disable(dssdev); + acx565akm_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +static const struct of_device_id acx565akm_of_match[] = { + { .compatible = "omapdss,sony,acx565akm", }, + {}, +}; + +static struct spi_driver acx565akm_driver = { + .driver = { + .name = "acx565akm", + .owner = THIS_MODULE, + .of_match_table = acx565akm_of_match, + }, + .probe = acx565akm_probe, + .remove = acx565akm_remove, +}; + +module_spi_driver(acx565akm_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("acx565akm LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-tpo-td028ttec1.c b/drivers/video/fbdev/omap2/displays-new/panel-tpo-td028ttec1.c new file mode 100644 index 000000000000..fae6adc005a7 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-tpo-td028ttec1.c @@ -0,0 +1,480 @@ +/* + * Toppoly TD028TTEC1 panel support + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Neo 1973 code (jbt6k74.c): + * Copyright (C) 2006-2007 by OpenMoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * + * Ported and adapted from Neo 1973 U-Boot by: + * H. Nikolaus Schaller <hns@goldelico.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/gpio.h> +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + int data_lines; + + struct omap_video_timings videomode; + + struct spi_device *spi_dev; +}; + +static struct omap_video_timings td028ttec1_panel_timings = { + .x_res = 480, + .y_res = 640, + .pixelclock = 22153000, + .hfp = 24, + .hsw = 8, + .hbp = 8, + .vfp = 4, + .vsw = 2, + .vbp = 2, + + .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, + + .data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE, + .de_level = OMAPDSS_SIG_ACTIVE_HIGH, + .sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, +}; + +#define JBT_COMMAND 0x000 +#define JBT_DATA 0x100 + +static int jbt_ret_write_0(struct panel_drv_data *ddata, u8 reg) +{ + int rc; + u16 tx_buf = JBT_COMMAND | reg; + + rc = spi_write(ddata->spi_dev, (u8 *)&tx_buf, + 1*sizeof(u16)); + if (rc != 0) + dev_err(&ddata->spi_dev->dev, + "jbt_ret_write_0 spi_write ret %d\n", rc); + + return rc; +} + +static int jbt_reg_write_1(struct panel_drv_data *ddata, u8 reg, u8 data) +{ + int rc; + u16 tx_buf[2]; + + tx_buf[0] = JBT_COMMAND | reg; + tx_buf[1] = JBT_DATA | data; + rc = spi_write(ddata->spi_dev, (u8 *)tx_buf, + 2*sizeof(u16)); + if (rc != 0) + dev_err(&ddata->spi_dev->dev, + "jbt_reg_write_1 spi_write ret %d\n", rc); + + return rc; +} + +static int jbt_reg_write_2(struct panel_drv_data *ddata, u8 reg, u16 data) +{ + int rc; + u16 tx_buf[3]; + + tx_buf[0] = JBT_COMMAND | reg; + tx_buf[1] = JBT_DATA | (data >> 8); + tx_buf[2] = JBT_DATA | (data & 0xff); + + rc = spi_write(ddata->spi_dev, (u8 *)tx_buf, + 3*sizeof(u16)); + + if (rc != 0) + dev_err(&ddata->spi_dev->dev, + "jbt_reg_write_2 spi_write ret %d\n", rc); + + return rc; +} + +enum jbt_register { + JBT_REG_SLEEP_IN = 0x10, + JBT_REG_SLEEP_OUT = 0x11, + + JBT_REG_DISPLAY_OFF = 0x28, + JBT_REG_DISPLAY_ON = 0x29, + + JBT_REG_RGB_FORMAT = 0x3a, + JBT_REG_QUAD_RATE = 0x3b, + + JBT_REG_POWER_ON_OFF = 0xb0, + JBT_REG_BOOSTER_OP = 0xb1, + JBT_REG_BOOSTER_MODE = 0xb2, + JBT_REG_BOOSTER_FREQ = 0xb3, + JBT_REG_OPAMP_SYSCLK = 0xb4, + JBT_REG_VSC_VOLTAGE = 0xb5, + JBT_REG_VCOM_VOLTAGE = 0xb6, + JBT_REG_EXT_DISPL = 0xb7, + JBT_REG_OUTPUT_CONTROL = 0xb8, + JBT_REG_DCCLK_DCEV = 0xb9, + JBT_REG_DISPLAY_MODE1 = 0xba, + JBT_REG_DISPLAY_MODE2 = 0xbb, + JBT_REG_DISPLAY_MODE = 0xbc, + JBT_REG_ASW_SLEW = 0xbd, + JBT_REG_DUMMY_DISPLAY = 0xbe, + JBT_REG_DRIVE_SYSTEM = 0xbf, + + JBT_REG_SLEEP_OUT_FR_A = 0xc0, + JBT_REG_SLEEP_OUT_FR_B = 0xc1, + JBT_REG_SLEEP_OUT_FR_C = 0xc2, + JBT_REG_SLEEP_IN_LCCNT_D = 0xc3, + JBT_REG_SLEEP_IN_LCCNT_E = 0xc4, + JBT_REG_SLEEP_IN_LCCNT_F = 0xc5, + JBT_REG_SLEEP_IN_LCCNT_G = 0xc6, + + JBT_REG_GAMMA1_FINE_1 = 0xc7, + JBT_REG_GAMMA1_FINE_2 = 0xc8, + JBT_REG_GAMMA1_INCLINATION = 0xc9, + JBT_REG_GAMMA1_BLUE_OFFSET = 0xca, + + JBT_REG_BLANK_CONTROL = 0xcf, + JBT_REG_BLANK_TH_TV = 0xd0, + JBT_REG_CKV_ON_OFF = 0xd1, + JBT_REG_CKV_1_2 = 0xd2, + JBT_REG_OEV_TIMING = 0xd3, + JBT_REG_ASW_TIMING_1 = 0xd4, + JBT_REG_ASW_TIMING_2 = 0xd5, + + JBT_REG_HCLOCK_VGA = 0xec, + JBT_REG_HCLOCK_QVGA = 0xed, +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static int td028ttec1_panel_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void td028ttec1_panel_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dpi->disconnect(in, dssdev); +} + +static int td028ttec1_panel_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_data_lines(in, ddata->data_lines); + in->ops.dpi->set_timings(in, &ddata->videomode); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + dev_dbg(dssdev->dev, "td028ttec1_panel_enable() - state %d\n", + dssdev->state); + + /* three times command zero */ + r |= jbt_ret_write_0(ddata, 0x00); + usleep_range(1000, 2000); + r |= jbt_ret_write_0(ddata, 0x00); + usleep_range(1000, 2000); + r |= jbt_ret_write_0(ddata, 0x00); + usleep_range(1000, 2000); + + if (r) { + dev_warn(dssdev->dev, "transfer error\n"); + goto transfer_err; + } + + /* deep standby out */ + r |= jbt_reg_write_1(ddata, JBT_REG_POWER_ON_OFF, 0x17); + + /* RGB I/F on, RAM write off, QVGA through, SIGCON enable */ + r |= jbt_reg_write_1(ddata, JBT_REG_DISPLAY_MODE, 0x80); + + /* Quad mode off */ + r |= jbt_reg_write_1(ddata, JBT_REG_QUAD_RATE, 0x00); + + /* AVDD on, XVDD on */ + r |= jbt_reg_write_1(ddata, JBT_REG_POWER_ON_OFF, 0x16); + + /* Output control */ + r |= jbt_reg_write_2(ddata, JBT_REG_OUTPUT_CONTROL, 0xfff9); + + /* Sleep mode off */ + r |= jbt_ret_write_0(ddata, JBT_REG_SLEEP_OUT); + + /* at this point we have like 50% grey */ + + /* initialize register set */ + r |= jbt_reg_write_1(ddata, JBT_REG_DISPLAY_MODE1, 0x01); + r |= jbt_reg_write_1(ddata, JBT_REG_DISPLAY_MODE2, 0x00); + r |= jbt_reg_write_1(ddata, JBT_REG_RGB_FORMAT, 0x60); + r |= jbt_reg_write_1(ddata, JBT_REG_DRIVE_SYSTEM, 0x10); + r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_OP, 0x56); + r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_MODE, 0x33); + r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_FREQ, 0x11); + r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_FREQ, 0x11); + r |= jbt_reg_write_1(ddata, JBT_REG_OPAMP_SYSCLK, 0x02); + r |= jbt_reg_write_1(ddata, JBT_REG_VSC_VOLTAGE, 0x2b); + r |= jbt_reg_write_1(ddata, JBT_REG_VCOM_VOLTAGE, 0x40); + r |= jbt_reg_write_1(ddata, JBT_REG_EXT_DISPL, 0x03); + r |= jbt_reg_write_1(ddata, JBT_REG_DCCLK_DCEV, 0x04); + /* + * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement + * to avoid red / blue flicker + */ + r |= jbt_reg_write_1(ddata, JBT_REG_ASW_SLEW, 0x04); + r |= jbt_reg_write_1(ddata, JBT_REG_DUMMY_DISPLAY, 0x00); + + r |= jbt_reg_write_1(ddata, JBT_REG_SLEEP_OUT_FR_A, 0x11); + r |= jbt_reg_write_1(ddata, JBT_REG_SLEEP_OUT_FR_B, 0x11); + r |= jbt_reg_write_1(ddata, JBT_REG_SLEEP_OUT_FR_C, 0x11); + r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040); + r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0); + r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020); + r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0); + + r |= jbt_reg_write_2(ddata, JBT_REG_GAMMA1_FINE_1, 0x5533); + r |= jbt_reg_write_1(ddata, JBT_REG_GAMMA1_FINE_2, 0x00); + r |= jbt_reg_write_1(ddata, JBT_REG_GAMMA1_INCLINATION, 0x00); + r |= jbt_reg_write_1(ddata, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00); + + r |= jbt_reg_write_2(ddata, JBT_REG_HCLOCK_VGA, 0x1f0); + r |= jbt_reg_write_1(ddata, JBT_REG_BLANK_CONTROL, 0x02); + r |= jbt_reg_write_2(ddata, JBT_REG_BLANK_TH_TV, 0x0804); + + r |= jbt_reg_write_1(ddata, JBT_REG_CKV_ON_OFF, 0x01); + r |= jbt_reg_write_2(ddata, JBT_REG_CKV_1_2, 0x0000); + + r |= jbt_reg_write_2(ddata, JBT_REG_OEV_TIMING, 0x0d0e); + r |= jbt_reg_write_2(ddata, JBT_REG_ASW_TIMING_1, 0x11a4); + r |= jbt_reg_write_1(ddata, JBT_REG_ASW_TIMING_2, 0x0e); + + r |= jbt_ret_write_0(ddata, JBT_REG_DISPLAY_ON); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + +transfer_err: + + return r ? -EIO : 0; +} + +static void td028ttec1_panel_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + dev_dbg(dssdev->dev, "td028ttec1_panel_disable()\n"); + + jbt_ret_write_0(ddata, JBT_REG_DISPLAY_OFF); + jbt_reg_write_2(ddata, JBT_REG_OUTPUT_CONTROL, 0x8002); + jbt_ret_write_0(ddata, JBT_REG_SLEEP_IN); + jbt_reg_write_1(ddata, JBT_REG_POWER_ON_OFF, 0x00); + + in->ops.dpi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void td028ttec1_panel_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->videomode = *timings; + dssdev->panel.timings = *timings; + + in->ops.dpi->set_timings(in, timings); +} + +static void td028ttec1_panel_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->videomode; +} + +static int td028ttec1_panel_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dpi->check_timings(in, timings); +} + +static struct omap_dss_driver td028ttec1_ops = { + .connect = td028ttec1_panel_connect, + .disconnect = td028ttec1_panel_disconnect, + + .enable = td028ttec1_panel_enable, + .disable = td028ttec1_panel_disable, + + .set_timings = td028ttec1_panel_set_timings, + .get_timings = td028ttec1_panel_get_timings, + .check_timings = td028ttec1_panel_check_timings, +}; + +static int td028ttec1_panel_probe_pdata(struct spi_device *spi) +{ + const struct panel_tpo_td028ttec1_platform_data *pdata; + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&spi->dev); + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&spi->dev, "failed to find video source '%s'\n", + pdata->source); + return -EPROBE_DEFER; + } + + ddata->in = in; + + ddata->data_lines = pdata->data_lines; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int td028ttec1_panel_probe(struct spi_device *spi) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + dev_dbg(&spi->dev, "%s\n", __func__); + + spi->bits_per_word = 9; + spi->mode = SPI_MODE_3; + + r = spi_setup(spi); + if (r < 0) { + dev_err(&spi->dev, "spi_setup failed: %d\n", r); + return r; + } + + ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, ddata); + + ddata->spi_dev = spi; + + if (dev_get_platdata(&spi->dev)) { + r = td028ttec1_panel_probe_pdata(spi); + if (r) + return r; + } else { + return -ENODEV; + } + + ddata->videomode = td028ttec1_panel_timings; + + dssdev = &ddata->dssdev; + dssdev->dev = &spi->dev; + dssdev->driver = &td028ttec1_ops; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = ddata->videomode; + dssdev->phy.dpi.data_lines = ddata->data_lines; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&spi->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: + omap_dss_put_device(ddata->in); + return r; +} + +static int td028ttec1_panel_remove(struct spi_device *spi) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + dev_dbg(&ddata->spi_dev->dev, "%s\n", __func__); + + omapdss_unregister_display(dssdev); + + td028ttec1_panel_disable(dssdev); + td028ttec1_panel_disconnect(dssdev); + + omap_dss_put_device(in); + + return 0; +} + +static struct spi_driver td028ttec1_spi_driver = { + .probe = td028ttec1_panel_probe, + .remove = td028ttec1_panel_remove, + + .driver = { + .name = "panel-tpo-td028ttec1", + .owner = THIS_MODULE, + }, +}; + +module_spi_driver(td028ttec1_spi_driver); + +MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>"); +MODULE_DESCRIPTION("Toppoly TD028TTEC1 panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/panel-tpo-td043mtea1.c b/drivers/video/fbdev/omap2/displays-new/panel-tpo-td043mtea1.c new file mode 100644 index 000000000000..875b40263b33 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/panel-tpo-td043mtea1.c @@ -0,0 +1,646 @@ +/* + * TPO TD043MTEA1 Panel driver + * + * Author: GraĹľvydas Ignotas <notasas@gmail.com> + * Converted to new DSS device model: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +#define TPO_R02_MODE(x) ((x) & 7) +#define TPO_R02_MODE_800x480 7 +#define TPO_R02_NCLK_RISING BIT(3) +#define TPO_R02_HSYNC_HIGH BIT(4) +#define TPO_R02_VSYNC_HIGH BIT(5) + +#define TPO_R03_NSTANDBY BIT(0) +#define TPO_R03_EN_CP_CLK BIT(1) +#define TPO_R03_EN_VGL_PUMP BIT(2) +#define TPO_R03_EN_PWM BIT(3) +#define TPO_R03_DRIVING_CAP_100 BIT(4) +#define TPO_R03_EN_PRE_CHARGE BIT(6) +#define TPO_R03_SOFTWARE_CTL BIT(7) + +#define TPO_R04_NFLIP_H BIT(0) +#define TPO_R04_NFLIP_V BIT(1) +#define TPO_R04_CP_CLK_FREQ_1H BIT(2) +#define TPO_R04_VGL_FREQ_1H BIT(4) + +#define TPO_R03_VAL_NORMAL (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | \ + TPO_R03_EN_VGL_PUMP | TPO_R03_EN_PWM | \ + TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ + TPO_R03_SOFTWARE_CTL) + +#define TPO_R03_VAL_STANDBY (TPO_R03_DRIVING_CAP_100 | \ + TPO_R03_EN_PRE_CHARGE | TPO_R03_SOFTWARE_CTL) + +static const u16 tpo_td043_def_gamma[12] = { + 105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023 +}; + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + + struct omap_video_timings videomode; + + int data_lines; + + struct spi_device *spi; + struct regulator *vcc_reg; + int nreset_gpio; + u16 gamma[12]; + u32 mode; + u32 hmirror:1; + u32 vmirror:1; + u32 powered_on:1; + u32 spi_suspended:1; + u32 power_on_resume:1; +}; + +static const struct omap_video_timings tpo_td043_timings = { + .x_res = 800, + .y_res = 480, + + .pixelclock = 36000000, + + .hsw = 1, + .hfp = 68, + .hbp = 214, + + .vsw = 1, + .vfp = 39, + .vbp = 34, + + .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, + .data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE, + .de_level = OMAPDSS_SIG_ACTIVE_HIGH, + .sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, +}; + +#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) + +static int tpo_td043_write(struct spi_device *spi, u8 addr, u8 data) +{ + struct spi_message m; + struct spi_transfer xfer; + u16 w; + int r; + + spi_message_init(&m); + + memset(&xfer, 0, sizeof(xfer)); + + w = ((u16)addr << 10) | (1 << 8) | data; + xfer.tx_buf = &w; + xfer.bits_per_word = 16; + xfer.len = 2; + spi_message_add_tail(&xfer, &m); + + r = spi_sync(spi, &m); + if (r < 0) + dev_warn(&spi->dev, "failed to write to LCD reg (%d)\n", r); + return r; +} + +static void tpo_td043_write_gamma(struct spi_device *spi, u16 gamma[12]) +{ + u8 i, val; + + /* gamma bits [9:8] */ + for (val = i = 0; i < 4; i++) + val |= (gamma[i] & 0x300) >> ((i + 1) * 2); + tpo_td043_write(spi, 0x11, val); + + for (val = i = 0; i < 4; i++) + val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2); + tpo_td043_write(spi, 0x12, val); + + for (val = i = 0; i < 4; i++) + val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2); + tpo_td043_write(spi, 0x13, val); + + /* gamma bits [7:0] */ + for (val = i = 0; i < 12; i++) + tpo_td043_write(spi, 0x14 + i, gamma[i] & 0xff); +} + +static int tpo_td043_write_mirror(struct spi_device *spi, bool h, bool v) +{ + u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V | + TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H; + if (h) + reg4 &= ~TPO_R04_NFLIP_H; + if (v) + reg4 &= ~TPO_R04_NFLIP_V; + + return tpo_td043_write(spi, 4, reg4); +} + +static int tpo_td043_set_hmirror(struct omap_dss_device *dssdev, bool enable) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev); + + ddata->hmirror = enable; + return tpo_td043_write_mirror(ddata->spi, ddata->hmirror, + ddata->vmirror); +} + +static bool tpo_td043_get_hmirror(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev); + + return ddata->hmirror; +} + +static ssize_t tpo_td043_vmirror_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", ddata->vmirror); +} + +static ssize_t tpo_td043_vmirror_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + int val; + int ret; + + ret = kstrtoint(buf, 0, &val); + if (ret < 0) + return ret; + + val = !!val; + + ret = tpo_td043_write_mirror(ddata->spi, ddata->hmirror, val); + if (ret < 0) + return ret; + + ddata->vmirror = val; + + return count; +} + +static ssize_t tpo_td043_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", ddata->mode); +} + +static ssize_t tpo_td043_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + long val; + int ret; + + ret = kstrtol(buf, 0, &val); + if (ret != 0 || val & ~7) + return -EINVAL; + + ddata->mode = val; + + val |= TPO_R02_NCLK_RISING; + tpo_td043_write(ddata->spi, 2, val); + + return count; +} + +static ssize_t tpo_td043_gamma_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + ssize_t len = 0; + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(ddata->gamma); i++) { + ret = snprintf(buf + len, PAGE_SIZE - len, "%u ", + ddata->gamma[i]); + if (ret < 0) + return ret; + len += ret; + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t tpo_td043_gamma_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + unsigned int g[12]; + int ret; + int i; + + ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u", + &g[0], &g[1], &g[2], &g[3], &g[4], &g[5], + &g[6], &g[7], &g[8], &g[9], &g[10], &g[11]); + + if (ret != 12) + return -EINVAL; + + for (i = 0; i < 12; i++) + ddata->gamma[i] = g[i]; + + tpo_td043_write_gamma(ddata->spi, ddata->gamma); + + return count; +} + +static DEVICE_ATTR(vmirror, S_IRUGO | S_IWUSR, + tpo_td043_vmirror_show, tpo_td043_vmirror_store); +static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + tpo_td043_mode_show, tpo_td043_mode_store); +static DEVICE_ATTR(gamma, S_IRUGO | S_IWUSR, + tpo_td043_gamma_show, tpo_td043_gamma_store); + +static struct attribute *tpo_td043_attrs[] = { + &dev_attr_vmirror.attr, + &dev_attr_mode.attr, + &dev_attr_gamma.attr, + NULL, +}; + +static struct attribute_group tpo_td043_attr_group = { + .attrs = tpo_td043_attrs, +}; + +static int tpo_td043_power_on(struct panel_drv_data *ddata) +{ + int r; + + if (ddata->powered_on) + return 0; + + r = regulator_enable(ddata->vcc_reg); + if (r != 0) + return r; + + /* wait for panel to stabilize */ + msleep(160); + + if (gpio_is_valid(ddata->nreset_gpio)) + gpio_set_value(ddata->nreset_gpio, 1); + + tpo_td043_write(ddata->spi, 2, + TPO_R02_MODE(ddata->mode) | TPO_R02_NCLK_RISING); + tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_NORMAL); + tpo_td043_write(ddata->spi, 0x20, 0xf0); + tpo_td043_write(ddata->spi, 0x21, 0xf0); + tpo_td043_write_mirror(ddata->spi, ddata->hmirror, + ddata->vmirror); + tpo_td043_write_gamma(ddata->spi, ddata->gamma); + + ddata->powered_on = 1; + return 0; +} + +static void tpo_td043_power_off(struct panel_drv_data *ddata) +{ + if (!ddata->powered_on) + return; + + tpo_td043_write(ddata->spi, 3, + TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM); + + if (gpio_is_valid(ddata->nreset_gpio)) + gpio_set_value(ddata->nreset_gpio, 0); + + /* wait for at least 2 vsyncs before cutting off power */ + msleep(50); + + tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_STANDBY); + + regulator_disable(ddata->vcc_reg); + + ddata->powered_on = 0; +} + +static int tpo_td043_connect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return 0; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + return 0; +} + +static void tpo_td043_disconnect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_connected(dssdev)) + return; + + in->ops.dpi->disconnect(in, dssdev); +} + +static int tpo_td043_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_data_lines(in, ddata->data_lines); + in->ops.dpi->set_timings(in, &ddata->videomode); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + /* + * If we are resuming from system suspend, SPI clocks might not be + * enabled yet, so we'll program the LCD from SPI PM resume callback. + */ + if (!ddata->spi_suspended) { + r = tpo_td043_power_on(ddata); + if (r) { + in->ops.dpi->disable(in); + return r; + } + } + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; +} + +static void tpo_td043_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + in->ops.dpi->disable(in); + + if (!ddata->spi_suspended) + tpo_td043_power_off(ddata); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void tpo_td043_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + ddata->videomode = *timings; + dssdev->panel.timings = *timings; + + in->ops.dpi->set_timings(in, timings); +} + +static void tpo_td043_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *timings = ddata->videomode; +} + +static int tpo_td043_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + return in->ops.dpi->check_timings(in, timings); +} + +static struct omap_dss_driver tpo_td043_ops = { + .connect = tpo_td043_connect, + .disconnect = tpo_td043_disconnect, + + .enable = tpo_td043_enable, + .disable = tpo_td043_disable, + + .set_timings = tpo_td043_set_timings, + .get_timings = tpo_td043_get_timings, + .check_timings = tpo_td043_check_timings, + + .set_mirror = tpo_td043_set_hmirror, + .get_mirror = tpo_td043_get_hmirror, + + .get_resolution = omapdss_default_get_resolution, +}; + + +static int tpo_td043_probe_pdata(struct spi_device *spi) +{ + const struct panel_tpo_td043mtea1_platform_data *pdata; + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev, *in; + + pdata = dev_get_platdata(&spi->dev); + + ddata->nreset_gpio = pdata->nreset_gpio; + + in = omap_dss_find_output(pdata->source); + if (in == NULL) { + dev_err(&spi->dev, "failed to find video source '%s'\n", + pdata->source); + return -EPROBE_DEFER; + } + ddata->in = in; + + ddata->data_lines = pdata->data_lines; + + dssdev = &ddata->dssdev; + dssdev->name = pdata->name; + + return 0; +} + +static int tpo_td043_probe(struct spi_device *spi) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + int r; + + dev_dbg(&spi->dev, "%s\n", __func__); + + spi->bits_per_word = 16; + spi->mode = SPI_MODE_0; + + r = spi_setup(spi); + if (r < 0) { + dev_err(&spi->dev, "spi_setup failed: %d\n", r); + return r; + } + + ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, ddata); + + ddata->spi = spi; + + if (dev_get_platdata(&spi->dev)) { + r = tpo_td043_probe_pdata(spi); + if (r) + return r; + } else { + return -ENODEV; + } + + ddata->mode = TPO_R02_MODE_800x480; + memcpy(ddata->gamma, tpo_td043_def_gamma, sizeof(ddata->gamma)); + + ddata->vcc_reg = devm_regulator_get(&spi->dev, "vcc"); + if (IS_ERR(ddata->vcc_reg)) { + dev_err(&spi->dev, "failed to get LCD VCC regulator\n"); + r = PTR_ERR(ddata->vcc_reg); + goto err_regulator; + } + + if (gpio_is_valid(ddata->nreset_gpio)) { + r = devm_gpio_request_one(&spi->dev, + ddata->nreset_gpio, GPIOF_OUT_INIT_LOW, + "lcd reset"); + if (r < 0) { + dev_err(&spi->dev, "couldn't request reset GPIO\n"); + goto err_gpio_req; + } + } + + r = sysfs_create_group(&spi->dev.kobj, &tpo_td043_attr_group); + if (r) { + dev_err(&spi->dev, "failed to create sysfs files\n"); + goto err_sysfs; + } + + ddata->videomode = tpo_td043_timings; + + dssdev = &ddata->dssdev; + dssdev->dev = &spi->dev; + dssdev->driver = &tpo_td043_ops; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->owner = THIS_MODULE; + dssdev->panel.timings = ddata->videomode; + + r = omapdss_register_display(dssdev); + if (r) { + dev_err(&spi->dev, "Failed to register panel\n"); + goto err_reg; + } + + return 0; + +err_reg: + sysfs_remove_group(&spi->dev.kobj, &tpo_td043_attr_group); +err_sysfs: +err_gpio_req: +err_regulator: + omap_dss_put_device(ddata->in); + return r; +} + +static int tpo_td043_remove(struct spi_device *spi) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct omap_dss_device *dssdev = &ddata->dssdev; + struct omap_dss_device *in = ddata->in; + + dev_dbg(&ddata->spi->dev, "%s\n", __func__); + + omapdss_unregister_display(dssdev); + + tpo_td043_disable(dssdev); + tpo_td043_disconnect(dssdev); + + omap_dss_put_device(in); + + sysfs_remove_group(&spi->dev.kobj, &tpo_td043_attr_group); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tpo_td043_spi_suspend(struct device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + + dev_dbg(dev, "tpo_td043_spi_suspend, tpo %p\n", ddata); + + ddata->power_on_resume = ddata->powered_on; + tpo_td043_power_off(ddata); + ddata->spi_suspended = 1; + + return 0; +} + +static int tpo_td043_spi_resume(struct device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "tpo_td043_spi_resume\n"); + + if (ddata->power_on_resume) { + ret = tpo_td043_power_on(ddata); + if (ret) + return ret; + } + ddata->spi_suspended = 0; + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(tpo_td043_spi_pm, + tpo_td043_spi_suspend, tpo_td043_spi_resume); + +static struct spi_driver tpo_td043_spi_driver = { + .driver = { + .name = "panel-tpo-td043mtea1", + .owner = THIS_MODULE, + .pm = &tpo_td043_spi_pm, + }, + .probe = tpo_td043_probe, + .remove = tpo_td043_remove, +}; + +module_spi_driver(tpo_td043_spi_driver); + +MODULE_AUTHOR("GraĹľvydas Ignotas <notasas@gmail.com>"); +MODULE_DESCRIPTION("TPO TD043MTEA1 LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/dss/Kconfig b/drivers/video/fbdev/omap2/dss/Kconfig new file mode 100644 index 000000000000..dde4281663b1 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/Kconfig @@ -0,0 +1,121 @@ +menuconfig OMAP2_DSS + tristate "OMAP2+ Display Subsystem support" + select VIDEOMODE_HELPERS + help + OMAP2+ Display Subsystem support. + +if OMAP2_DSS + +config OMAP2_DSS_DEBUG + bool "Debug support" + default n + help + This enables printing of debug messages. Alternatively, debug messages + can also be enabled by setting CONFIG_DYNAMIC_DEBUG and then setting + appropriate flags in <debugfs>/dynamic_debug/control. + +config OMAP2_DSS_DEBUGFS + bool "Debugfs filesystem support" + depends on DEBUG_FS + default n + help + This enables debugfs for OMAPDSS at <debugfs>/omapdss. This enables + querying about clock configuration and register configuration of dss, + dispc, dsi, hdmi and rfbi. + +config OMAP2_DSS_COLLECT_IRQ_STATS + bool "Collect DSS IRQ statistics" + depends on OMAP2_DSS_DEBUGFS + default n + help + Collect DSS IRQ statistics, printable via debugfs. + + The statistics can be found from + <debugfs>/omapdss/dispc_irq for DISPC interrupts, and + <debugfs>/omapdss/dsi_irq for DSI interrupts. + +config OMAP2_DSS_DPI + bool "DPI support" + default y + help + DPI Interface. This is the Parallel Display Interface. + +config OMAP2_DSS_RFBI + bool "RFBI support" + depends on BROKEN + default n + help + MIPI DBI support (RFBI, Remote Framebuffer Interface, in Texas + Instrument's terminology). + + DBI is a bus between the host processor and a peripheral, + such as a display or a framebuffer chip. + + See http://www.mipi.org/ for DBI specifications. + +config OMAP2_DSS_VENC + bool "VENC support" + default y + help + OMAP Video Encoder support for S-Video and composite TV-out. + +config OMAP4_DSS_HDMI + bool "HDMI support" + default y + help + HDMI Interface. This adds the High Definition Multimedia Interface. + See http://www.hdmi.org/ for HDMI specification. + +config OMAP4_DSS_HDMI_AUDIO + bool + +config OMAP2_DSS_SDI + bool "SDI support" + default n + help + SDI (Serial Display Interface) support. + + SDI is a high speed one-way display serial bus between the host + processor and a display. + +config OMAP2_DSS_DSI + bool "DSI support" + default n + help + MIPI DSI (Display Serial Interface) support. + + DSI is a high speed half-duplex serial interface between the host + processor and a peripheral, such as a display or a framebuffer chip. + + See http://www.mipi.org/ for DSI specifications. + +config OMAP2_DSS_MIN_FCK_PER_PCK + int "Minimum FCK/PCK ratio (for scaling)" + range 0 32 + default 0 + help + This can be used to adjust the minimum FCK/PCK ratio. + + With this you can make sure that DISPC FCK is at least + n x PCK. Video plane scaling requires higher FCK than + normally. + + If this is set to 0, there's no extra constraint on the + DISPC FCK. However, the FCK will at minimum be + 2xPCK (if active matrix) or 3xPCK (if passive matrix). + + Max FCK is 173MHz, so this doesn't work if your PCK + is very high. + +config OMAP2_DSS_SLEEP_AFTER_VENC_RESET + bool "Sleep 20ms after VENC reset" + default y + help + There is a 20ms sleep after VENC reset which seemed to fix the + reset. The reason for the bug is unclear, and it's also unclear + on what platforms this happens. + + This option enables the sleep, and is enabled by default. You can + disable the sleep if it doesn't cause problems on your platform. + +endif diff --git a/drivers/video/fbdev/omap2/dss/Makefile b/drivers/video/fbdev/omap2/dss/Makefile new file mode 100644 index 000000000000..8aec8bda27cc --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_OMAP2_DSS) += omapdss.o +# Core DSS files +omapdss-y := core.o dss.o dss_features.o dispc.o dispc_coefs.o display.o \ + output.o dss-of.o +# DSS compat layer files +omapdss-y += manager.o manager-sysfs.o overlay.o overlay-sysfs.o apply.o \ + dispc-compat.o display-sysfs.o +omapdss-$(CONFIG_OMAP2_DSS_DPI) += dpi.o +omapdss-$(CONFIG_OMAP2_DSS_RFBI) += rfbi.o +omapdss-$(CONFIG_OMAP2_DSS_VENC) += venc.o +omapdss-$(CONFIG_OMAP2_DSS_SDI) += sdi.o +omapdss-$(CONFIG_OMAP2_DSS_DSI) += dsi.o +omapdss-$(CONFIG_OMAP4_DSS_HDMI) += hdmi4.o hdmi_common.o hdmi_wp.o hdmi_pll.o \ + hdmi_phy.o hdmi4_core.o +ccflags-$(CONFIG_OMAP2_DSS_DEBUG) += -DDEBUG diff --git a/drivers/video/fbdev/omap2/dss/apply.c b/drivers/video/fbdev/omap2/dss/apply.c new file mode 100644 index 000000000000..0a0b084ce65d --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/apply.c @@ -0,0 +1,1700 @@ +/* + * Copyright (C) 2011 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "APPLY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/jiffies.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" +#include "dispc-compat.h" + +/* + * We have 4 levels of cache for the dispc settings. First two are in SW and + * the latter two in HW. + * + * set_info() + * v + * +--------------------+ + * | user_info | + * +--------------------+ + * v + * apply() + * v + * +--------------------+ + * | info | + * +--------------------+ + * v + * write_regs() + * v + * +--------------------+ + * | shadow registers | + * +--------------------+ + * v + * VFP or lcd/digit_enable + * v + * +--------------------+ + * | registers | + * +--------------------+ + */ + +struct ovl_priv_data { + + bool user_info_dirty; + struct omap_overlay_info user_info; + + bool info_dirty; + struct omap_overlay_info info; + + bool shadow_info_dirty; + + bool extra_info_dirty; + bool shadow_extra_info_dirty; + + bool enabled; + u32 fifo_low, fifo_high; + + /* + * True if overlay is to be enabled. Used to check and calculate configs + * for the overlay before it is enabled in the HW. + */ + bool enabling; +}; + +struct mgr_priv_data { + + bool user_info_dirty; + struct omap_overlay_manager_info user_info; + + bool info_dirty; + struct omap_overlay_manager_info info; + + bool shadow_info_dirty; + + /* If true, GO bit is up and shadow registers cannot be written. + * Never true for manual update displays */ + bool busy; + + /* If true, dispc output is enabled */ + bool updating; + + /* If true, a display is enabled using this manager */ + bool enabled; + + bool extra_info_dirty; + bool shadow_extra_info_dirty; + + struct omap_video_timings timings; + struct dss_lcd_mgr_config lcd_config; + + void (*framedone_handler)(void *); + void *framedone_handler_data; +}; + +static struct { + struct ovl_priv_data ovl_priv_data_array[MAX_DSS_OVERLAYS]; + struct mgr_priv_data mgr_priv_data_array[MAX_DSS_MANAGERS]; + + bool irq_enabled; +} dss_data; + +/* protects dss_data */ +static spinlock_t data_lock; +/* lock for blocking functions */ +static DEFINE_MUTEX(apply_lock); +static DECLARE_COMPLETION(extra_updated_completion); + +static void dss_register_vsync_isr(void); + +static struct ovl_priv_data *get_ovl_priv(struct omap_overlay *ovl) +{ + return &dss_data.ovl_priv_data_array[ovl->id]; +} + +static struct mgr_priv_data *get_mgr_priv(struct omap_overlay_manager *mgr) +{ + return &dss_data.mgr_priv_data_array[mgr->id]; +} + +static void apply_init_priv(void) +{ + const int num_ovls = dss_feat_get_num_ovls(); + struct mgr_priv_data *mp; + int i; + + spin_lock_init(&data_lock); + + for (i = 0; i < num_ovls; ++i) { + struct ovl_priv_data *op; + + op = &dss_data.ovl_priv_data_array[i]; + + op->info.color_mode = OMAP_DSS_COLOR_RGB16; + op->info.rotation_type = OMAP_DSS_ROT_DMA; + + op->info.global_alpha = 255; + + switch (i) { + case 0: + op->info.zorder = 0; + break; + case 1: + op->info.zorder = + dss_has_feature(FEAT_ALPHA_FREE_ZORDER) ? 3 : 0; + break; + case 2: + op->info.zorder = + dss_has_feature(FEAT_ALPHA_FREE_ZORDER) ? 2 : 0; + break; + case 3: + op->info.zorder = + dss_has_feature(FEAT_ALPHA_FREE_ZORDER) ? 1 : 0; + break; + } + + op->user_info = op->info; + } + + /* + * Initialize some of the lcd_config fields for TV manager, this lets + * us prevent checking if the manager is LCD or TV at some places + */ + mp = &dss_data.mgr_priv_data_array[OMAP_DSS_CHANNEL_DIGIT]; + + mp->lcd_config.video_port_width = 24; + mp->lcd_config.clock_info.lck_div = 1; + mp->lcd_config.clock_info.pck_div = 1; +} + +/* + * A LCD manager's stallmode decides whether it is in manual or auto update. TV + * manager is always auto update, stallmode field for TV manager is false by + * default + */ +static bool ovl_manual_update(struct omap_overlay *ovl) +{ + struct mgr_priv_data *mp = get_mgr_priv(ovl->manager); + + return mp->lcd_config.stallmode; +} + +static bool mgr_manual_update(struct omap_overlay_manager *mgr) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + return mp->lcd_config.stallmode; +} + +static int dss_check_settings_low(struct omap_overlay_manager *mgr, + bool applying) +{ + struct omap_overlay_info *oi; + struct omap_overlay_manager_info *mi; + struct omap_overlay *ovl; + struct omap_overlay_info *ois[MAX_DSS_OVERLAYS]; + struct ovl_priv_data *op; + struct mgr_priv_data *mp; + + mp = get_mgr_priv(mgr); + + if (!mp->enabled) + return 0; + + if (applying && mp->user_info_dirty) + mi = &mp->user_info; + else + mi = &mp->info; + + /* collect the infos to be tested into the array */ + list_for_each_entry(ovl, &mgr->overlays, list) { + op = get_ovl_priv(ovl); + + if (!op->enabled && !op->enabling) + oi = NULL; + else if (applying && op->user_info_dirty) + oi = &op->user_info; + else + oi = &op->info; + + ois[ovl->id] = oi; + } + + return dss_mgr_check(mgr, mi, &mp->timings, &mp->lcd_config, ois); +} + +/* + * check manager and overlay settings using overlay_info from data->info + */ +static int dss_check_settings(struct omap_overlay_manager *mgr) +{ + return dss_check_settings_low(mgr, false); +} + +/* + * check manager and overlay settings using overlay_info from ovl->info if + * dirty and from data->info otherwise + */ +static int dss_check_settings_apply(struct omap_overlay_manager *mgr) +{ + return dss_check_settings_low(mgr, true); +} + +static bool need_isr(void) +{ + const int num_mgrs = dss_feat_get_num_mgrs(); + int i; + + for (i = 0; i < num_mgrs; ++i) { + struct omap_overlay_manager *mgr; + struct mgr_priv_data *mp; + struct omap_overlay *ovl; + + mgr = omap_dss_get_overlay_manager(i); + mp = get_mgr_priv(mgr); + + if (!mp->enabled) + continue; + + if (mgr_manual_update(mgr)) { + /* to catch FRAMEDONE */ + if (mp->updating) + return true; + } else { + /* to catch GO bit going down */ + if (mp->busy) + return true; + + /* to write new values to registers */ + if (mp->info_dirty) + return true; + + /* to set GO bit */ + if (mp->shadow_info_dirty) + return true; + + /* + * NOTE: we don't check extra_info flags for disabled + * managers, once the manager is enabled, the extra_info + * related manager changes will be taken in by HW. + */ + + /* to write new values to registers */ + if (mp->extra_info_dirty) + return true; + + /* to set GO bit */ + if (mp->shadow_extra_info_dirty) + return true; + + list_for_each_entry(ovl, &mgr->overlays, list) { + struct ovl_priv_data *op; + + op = get_ovl_priv(ovl); + + /* + * NOTE: we check extra_info flags even for + * disabled overlays, as extra_infos need to be + * always written. + */ + + /* to write new values to registers */ + if (op->extra_info_dirty) + return true; + + /* to set GO bit */ + if (op->shadow_extra_info_dirty) + return true; + + if (!op->enabled) + continue; + + /* to write new values to registers */ + if (op->info_dirty) + return true; + + /* to set GO bit */ + if (op->shadow_info_dirty) + return true; + } + } + } + + return false; +} + +static bool need_go(struct omap_overlay_manager *mgr) +{ + struct omap_overlay *ovl; + struct mgr_priv_data *mp; + struct ovl_priv_data *op; + + mp = get_mgr_priv(mgr); + + if (mp->shadow_info_dirty || mp->shadow_extra_info_dirty) + return true; + + list_for_each_entry(ovl, &mgr->overlays, list) { + op = get_ovl_priv(ovl); + if (op->shadow_info_dirty || op->shadow_extra_info_dirty) + return true; + } + + return false; +} + +/* returns true if an extra_info field is currently being updated */ +static bool extra_info_update_ongoing(void) +{ + const int num_mgrs = dss_feat_get_num_mgrs(); + int i; + + for (i = 0; i < num_mgrs; ++i) { + struct omap_overlay_manager *mgr; + struct omap_overlay *ovl; + struct mgr_priv_data *mp; + + mgr = omap_dss_get_overlay_manager(i); + mp = get_mgr_priv(mgr); + + if (!mp->enabled) + continue; + + if (!mp->updating) + continue; + + if (mp->extra_info_dirty || mp->shadow_extra_info_dirty) + return true; + + list_for_each_entry(ovl, &mgr->overlays, list) { + struct ovl_priv_data *op = get_ovl_priv(ovl); + + if (op->extra_info_dirty || op->shadow_extra_info_dirty) + return true; + } + } + + return false; +} + +/* wait until no extra_info updates are pending */ +static void wait_pending_extra_info_updates(void) +{ + bool updating; + unsigned long flags; + unsigned long t; + int r; + + spin_lock_irqsave(&data_lock, flags); + + updating = extra_info_update_ongoing(); + + if (!updating) { + spin_unlock_irqrestore(&data_lock, flags); + return; + } + + init_completion(&extra_updated_completion); + + spin_unlock_irqrestore(&data_lock, flags); + + t = msecs_to_jiffies(500); + r = wait_for_completion_timeout(&extra_updated_completion, t); + if (r == 0) + DSSWARN("timeout in wait_pending_extra_info_updates\n"); +} + +static struct omap_dss_device *dss_mgr_get_device(struct omap_overlay_manager *mgr) +{ + struct omap_dss_device *dssdev; + + dssdev = mgr->output; + if (dssdev == NULL) + return NULL; + + while (dssdev->dst) + dssdev = dssdev->dst; + + if (dssdev->driver) + return dssdev; + else + return NULL; +} + +static struct omap_dss_device *dss_ovl_get_device(struct omap_overlay *ovl) +{ + return ovl->manager ? dss_mgr_get_device(ovl->manager) : NULL; +} + +static int dss_mgr_wait_for_vsync(struct omap_overlay_manager *mgr) +{ + unsigned long timeout = msecs_to_jiffies(500); + u32 irq; + int r; + + if (mgr->output == NULL) + return -ENODEV; + + r = dispc_runtime_get(); + if (r) + return r; + + switch (mgr->output->id) { + case OMAP_DSS_OUTPUT_VENC: + irq = DISPC_IRQ_EVSYNC_ODD; + break; + case OMAP_DSS_OUTPUT_HDMI: + irq = DISPC_IRQ_EVSYNC_EVEN; + break; + default: + irq = dispc_mgr_get_vsync_irq(mgr->id); + break; + } + + r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); + + dispc_runtime_put(); + + return r; +} + +static int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr) +{ + unsigned long timeout = msecs_to_jiffies(500); + struct mgr_priv_data *mp = get_mgr_priv(mgr); + u32 irq; + unsigned long flags; + int r; + int i; + + spin_lock_irqsave(&data_lock, flags); + + if (mgr_manual_update(mgr)) { + spin_unlock_irqrestore(&data_lock, flags); + return 0; + } + + if (!mp->enabled) { + spin_unlock_irqrestore(&data_lock, flags); + return 0; + } + + spin_unlock_irqrestore(&data_lock, flags); + + r = dispc_runtime_get(); + if (r) + return r; + + irq = dispc_mgr_get_vsync_irq(mgr->id); + + i = 0; + while (1) { + bool shadow_dirty, dirty; + + spin_lock_irqsave(&data_lock, flags); + dirty = mp->info_dirty; + shadow_dirty = mp->shadow_info_dirty; + spin_unlock_irqrestore(&data_lock, flags); + + if (!dirty && !shadow_dirty) { + r = 0; + break; + } + + /* 4 iterations is the worst case: + * 1 - initial iteration, dirty = true (between VFP and VSYNC) + * 2 - first VSYNC, dirty = true + * 3 - dirty = false, shadow_dirty = true + * 4 - shadow_dirty = false */ + if (i++ == 3) { + DSSERR("mgr(%d)->wait_for_go() not finishing\n", + mgr->id); + r = 0; + break; + } + + r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); + if (r == -ERESTARTSYS) + break; + + if (r) { + DSSERR("mgr(%d)->wait_for_go() timeout\n", mgr->id); + break; + } + } + + dispc_runtime_put(); + + return r; +} + +static int dss_mgr_wait_for_go_ovl(struct omap_overlay *ovl) +{ + unsigned long timeout = msecs_to_jiffies(500); + struct ovl_priv_data *op; + struct mgr_priv_data *mp; + u32 irq; + unsigned long flags; + int r; + int i; + + if (!ovl->manager) + return 0; + + mp = get_mgr_priv(ovl->manager); + + spin_lock_irqsave(&data_lock, flags); + + if (ovl_manual_update(ovl)) { + spin_unlock_irqrestore(&data_lock, flags); + return 0; + } + + if (!mp->enabled) { + spin_unlock_irqrestore(&data_lock, flags); + return 0; + } + + spin_unlock_irqrestore(&data_lock, flags); + + r = dispc_runtime_get(); + if (r) + return r; + + irq = dispc_mgr_get_vsync_irq(ovl->manager->id); + + op = get_ovl_priv(ovl); + i = 0; + while (1) { + bool shadow_dirty, dirty; + + spin_lock_irqsave(&data_lock, flags); + dirty = op->info_dirty; + shadow_dirty = op->shadow_info_dirty; + spin_unlock_irqrestore(&data_lock, flags); + + if (!dirty && !shadow_dirty) { + r = 0; + break; + } + + /* 4 iterations is the worst case: + * 1 - initial iteration, dirty = true (between VFP and VSYNC) + * 2 - first VSYNC, dirty = true + * 3 - dirty = false, shadow_dirty = true + * 4 - shadow_dirty = false */ + if (i++ == 3) { + DSSERR("ovl(%d)->wait_for_go() not finishing\n", + ovl->id); + r = 0; + break; + } + + r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); + if (r == -ERESTARTSYS) + break; + + if (r) { + DSSERR("ovl(%d)->wait_for_go() timeout\n", ovl->id); + break; + } + } + + dispc_runtime_put(); + + return r; +} + +static void dss_ovl_write_regs(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + struct omap_overlay_info *oi; + bool replication; + struct mgr_priv_data *mp; + int r; + + DSSDBG("writing ovl %d regs\n", ovl->id); + + if (!op->enabled || !op->info_dirty) + return; + + oi = &op->info; + + mp = get_mgr_priv(ovl->manager); + + replication = dss_ovl_use_replication(mp->lcd_config, oi->color_mode); + + r = dispc_ovl_setup(ovl->id, oi, replication, &mp->timings, false); + if (r) { + /* + * We can't do much here, as this function can be called from + * vsync interrupt. + */ + DSSERR("dispc_ovl_setup failed for ovl %d\n", ovl->id); + + /* This will leave fifo configurations in a nonoptimal state */ + op->enabled = false; + dispc_ovl_enable(ovl->id, false); + return; + } + + op->info_dirty = false; + if (mp->updating) + op->shadow_info_dirty = true; +} + +static void dss_ovl_write_regs_extra(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + struct mgr_priv_data *mp; + + DSSDBG("writing ovl %d regs extra\n", ovl->id); + + if (!op->extra_info_dirty) + return; + + /* note: write also when op->enabled == false, so that the ovl gets + * disabled */ + + dispc_ovl_enable(ovl->id, op->enabled); + dispc_ovl_set_fifo_threshold(ovl->id, op->fifo_low, op->fifo_high); + + mp = get_mgr_priv(ovl->manager); + + op->extra_info_dirty = false; + if (mp->updating) + op->shadow_extra_info_dirty = true; +} + +static void dss_mgr_write_regs(struct omap_overlay_manager *mgr) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + struct omap_overlay *ovl; + + DSSDBG("writing mgr %d regs\n", mgr->id); + + if (!mp->enabled) + return; + + WARN_ON(mp->busy); + + /* Commit overlay settings */ + list_for_each_entry(ovl, &mgr->overlays, list) { + dss_ovl_write_regs(ovl); + dss_ovl_write_regs_extra(ovl); + } + + if (mp->info_dirty) { + dispc_mgr_setup(mgr->id, &mp->info); + + mp->info_dirty = false; + if (mp->updating) + mp->shadow_info_dirty = true; + } +} + +static void dss_mgr_write_regs_extra(struct omap_overlay_manager *mgr) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + DSSDBG("writing mgr %d regs extra\n", mgr->id); + + if (!mp->extra_info_dirty) + return; + + dispc_mgr_set_timings(mgr->id, &mp->timings); + + /* lcd_config parameters */ + if (dss_mgr_is_lcd(mgr->id)) + dispc_mgr_set_lcd_config(mgr->id, &mp->lcd_config); + + mp->extra_info_dirty = false; + if (mp->updating) + mp->shadow_extra_info_dirty = true; +} + +static void dss_write_regs(void) +{ + const int num_mgrs = omap_dss_get_num_overlay_managers(); + int i; + + for (i = 0; i < num_mgrs; ++i) { + struct omap_overlay_manager *mgr; + struct mgr_priv_data *mp; + int r; + + mgr = omap_dss_get_overlay_manager(i); + mp = get_mgr_priv(mgr); + + if (!mp->enabled || mgr_manual_update(mgr) || mp->busy) + continue; + + r = dss_check_settings(mgr); + if (r) { + DSSERR("cannot write registers for manager %s: " + "illegal configuration\n", mgr->name); + continue; + } + + dss_mgr_write_regs(mgr); + dss_mgr_write_regs_extra(mgr); + } +} + +static void dss_set_go_bits(void) +{ + const int num_mgrs = omap_dss_get_num_overlay_managers(); + int i; + + for (i = 0; i < num_mgrs; ++i) { + struct omap_overlay_manager *mgr; + struct mgr_priv_data *mp; + + mgr = omap_dss_get_overlay_manager(i); + mp = get_mgr_priv(mgr); + + if (!mp->enabled || mgr_manual_update(mgr) || mp->busy) + continue; + + if (!need_go(mgr)) + continue; + + mp->busy = true; + + if (!dss_data.irq_enabled && need_isr()) + dss_register_vsync_isr(); + + dispc_mgr_go(mgr->id); + } + +} + +static void mgr_clear_shadow_dirty(struct omap_overlay_manager *mgr) +{ + struct omap_overlay *ovl; + struct mgr_priv_data *mp; + struct ovl_priv_data *op; + + mp = get_mgr_priv(mgr); + mp->shadow_info_dirty = false; + mp->shadow_extra_info_dirty = false; + + list_for_each_entry(ovl, &mgr->overlays, list) { + op = get_ovl_priv(ovl); + op->shadow_info_dirty = false; + op->shadow_extra_info_dirty = false; + } +} + +static int dss_mgr_connect_compat(struct omap_overlay_manager *mgr, + struct omap_dss_device *dst) +{ + return mgr->set_output(mgr, dst); +} + +static void dss_mgr_disconnect_compat(struct omap_overlay_manager *mgr, + struct omap_dss_device *dst) +{ + mgr->unset_output(mgr); +} + +static void dss_mgr_start_update_compat(struct omap_overlay_manager *mgr) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + unsigned long flags; + int r; + + spin_lock_irqsave(&data_lock, flags); + + WARN_ON(mp->updating); + + r = dss_check_settings(mgr); + if (r) { + DSSERR("cannot start manual update: illegal configuration\n"); + spin_unlock_irqrestore(&data_lock, flags); + return; + } + + dss_mgr_write_regs(mgr); + dss_mgr_write_regs_extra(mgr); + + mp->updating = true; + + if (!dss_data.irq_enabled && need_isr()) + dss_register_vsync_isr(); + + dispc_mgr_enable_sync(mgr->id); + + spin_unlock_irqrestore(&data_lock, flags); +} + +static void dss_apply_irq_handler(void *data, u32 mask); + +static void dss_register_vsync_isr(void) +{ + const int num_mgrs = dss_feat_get_num_mgrs(); + u32 mask; + int r, i; + + mask = 0; + for (i = 0; i < num_mgrs; ++i) + mask |= dispc_mgr_get_vsync_irq(i); + + for (i = 0; i < num_mgrs; ++i) + mask |= dispc_mgr_get_framedone_irq(i); + + r = omap_dispc_register_isr(dss_apply_irq_handler, NULL, mask); + WARN_ON(r); + + dss_data.irq_enabled = true; +} + +static void dss_unregister_vsync_isr(void) +{ + const int num_mgrs = dss_feat_get_num_mgrs(); + u32 mask; + int r, i; + + mask = 0; + for (i = 0; i < num_mgrs; ++i) + mask |= dispc_mgr_get_vsync_irq(i); + + for (i = 0; i < num_mgrs; ++i) + mask |= dispc_mgr_get_framedone_irq(i); + + r = omap_dispc_unregister_isr(dss_apply_irq_handler, NULL, mask); + WARN_ON(r); + + dss_data.irq_enabled = false; +} + +static void dss_apply_irq_handler(void *data, u32 mask) +{ + const int num_mgrs = dss_feat_get_num_mgrs(); + int i; + bool extra_updating; + + spin_lock(&data_lock); + + /* clear busy, updating flags, shadow_dirty flags */ + for (i = 0; i < num_mgrs; i++) { + struct omap_overlay_manager *mgr; + struct mgr_priv_data *mp; + + mgr = omap_dss_get_overlay_manager(i); + mp = get_mgr_priv(mgr); + + if (!mp->enabled) + continue; + + mp->updating = dispc_mgr_is_enabled(i); + + if (!mgr_manual_update(mgr)) { + bool was_busy = mp->busy; + mp->busy = dispc_mgr_go_busy(i); + + if (was_busy && !mp->busy) + mgr_clear_shadow_dirty(mgr); + } + } + + dss_write_regs(); + dss_set_go_bits(); + + extra_updating = extra_info_update_ongoing(); + if (!extra_updating) + complete_all(&extra_updated_completion); + + /* call framedone handlers for manual update displays */ + for (i = 0; i < num_mgrs; i++) { + struct omap_overlay_manager *mgr; + struct mgr_priv_data *mp; + + mgr = omap_dss_get_overlay_manager(i); + mp = get_mgr_priv(mgr); + + if (!mgr_manual_update(mgr) || !mp->framedone_handler) + continue; + + if (mask & dispc_mgr_get_framedone_irq(i)) + mp->framedone_handler(mp->framedone_handler_data); + } + + if (!need_isr()) + dss_unregister_vsync_isr(); + + spin_unlock(&data_lock); +} + +static void omap_dss_mgr_apply_ovl(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op; + + op = get_ovl_priv(ovl); + + if (!op->user_info_dirty) + return; + + op->user_info_dirty = false; + op->info_dirty = true; + op->info = op->user_info; +} + +static void omap_dss_mgr_apply_mgr(struct omap_overlay_manager *mgr) +{ + struct mgr_priv_data *mp; + + mp = get_mgr_priv(mgr); + + if (!mp->user_info_dirty) + return; + + mp->user_info_dirty = false; + mp->info_dirty = true; + mp->info = mp->user_info; +} + +static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) +{ + unsigned long flags; + struct omap_overlay *ovl; + int r; + + DSSDBG("omap_dss_mgr_apply(%s)\n", mgr->name); + + spin_lock_irqsave(&data_lock, flags); + + r = dss_check_settings_apply(mgr); + if (r) { + spin_unlock_irqrestore(&data_lock, flags); + DSSERR("failed to apply settings: illegal configuration.\n"); + return r; + } + + /* Configure overlays */ + list_for_each_entry(ovl, &mgr->overlays, list) + omap_dss_mgr_apply_ovl(ovl); + + /* Configure manager */ + omap_dss_mgr_apply_mgr(mgr); + + dss_write_regs(); + dss_set_go_bits(); + + spin_unlock_irqrestore(&data_lock, flags); + + return 0; +} + +static void dss_apply_ovl_enable(struct omap_overlay *ovl, bool enable) +{ + struct ovl_priv_data *op; + + op = get_ovl_priv(ovl); + + if (op->enabled == enable) + return; + + op->enabled = enable; + op->extra_info_dirty = true; +} + +static void dss_apply_ovl_fifo_thresholds(struct omap_overlay *ovl, + u32 fifo_low, u32 fifo_high) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + + if (op->fifo_low == fifo_low && op->fifo_high == fifo_high) + return; + + op->fifo_low = fifo_low; + op->fifo_high = fifo_high; + op->extra_info_dirty = true; +} + +static void dss_ovl_setup_fifo(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + u32 fifo_low, fifo_high; + bool use_fifo_merge = false; + + if (!op->enabled && !op->enabling) + return; + + dispc_ovl_compute_fifo_thresholds(ovl->id, &fifo_low, &fifo_high, + use_fifo_merge, ovl_manual_update(ovl)); + + dss_apply_ovl_fifo_thresholds(ovl, fifo_low, fifo_high); +} + +static void dss_mgr_setup_fifos(struct omap_overlay_manager *mgr) +{ + struct omap_overlay *ovl; + struct mgr_priv_data *mp; + + mp = get_mgr_priv(mgr); + + if (!mp->enabled) + return; + + list_for_each_entry(ovl, &mgr->overlays, list) + dss_ovl_setup_fifo(ovl); +} + +static void dss_setup_fifos(void) +{ + const int num_mgrs = omap_dss_get_num_overlay_managers(); + struct omap_overlay_manager *mgr; + int i; + + for (i = 0; i < num_mgrs; ++i) { + mgr = omap_dss_get_overlay_manager(i); + dss_mgr_setup_fifos(mgr); + } +} + +static int dss_mgr_enable_compat(struct omap_overlay_manager *mgr) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + unsigned long flags; + int r; + + mutex_lock(&apply_lock); + + if (mp->enabled) + goto out; + + spin_lock_irqsave(&data_lock, flags); + + mp->enabled = true; + + r = dss_check_settings(mgr); + if (r) { + DSSERR("failed to enable manager %d: check_settings failed\n", + mgr->id); + goto err; + } + + dss_setup_fifos(); + + dss_write_regs(); + dss_set_go_bits(); + + if (!mgr_manual_update(mgr)) + mp->updating = true; + + if (!dss_data.irq_enabled && need_isr()) + dss_register_vsync_isr(); + + spin_unlock_irqrestore(&data_lock, flags); + + if (!mgr_manual_update(mgr)) + dispc_mgr_enable_sync(mgr->id); + +out: + mutex_unlock(&apply_lock); + + return 0; + +err: + mp->enabled = false; + spin_unlock_irqrestore(&data_lock, flags); + mutex_unlock(&apply_lock); + return r; +} + +static void dss_mgr_disable_compat(struct omap_overlay_manager *mgr) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + unsigned long flags; + + mutex_lock(&apply_lock); + + if (!mp->enabled) + goto out; + + if (!mgr_manual_update(mgr)) + dispc_mgr_disable_sync(mgr->id); + + spin_lock_irqsave(&data_lock, flags); + + mp->updating = false; + mp->enabled = false; + + spin_unlock_irqrestore(&data_lock, flags); + +out: + mutex_unlock(&apply_lock); +} + +static int dss_mgr_set_info(struct omap_overlay_manager *mgr, + struct omap_overlay_manager_info *info) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + unsigned long flags; + int r; + + r = dss_mgr_simple_check(mgr, info); + if (r) + return r; + + spin_lock_irqsave(&data_lock, flags); + + mp->user_info = *info; + mp->user_info_dirty = true; + + spin_unlock_irqrestore(&data_lock, flags); + + return 0; +} + +static void dss_mgr_get_info(struct omap_overlay_manager *mgr, + struct omap_overlay_manager_info *info) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + unsigned long flags; + + spin_lock_irqsave(&data_lock, flags); + + *info = mp->user_info; + + spin_unlock_irqrestore(&data_lock, flags); +} + +static int dss_mgr_set_output(struct omap_overlay_manager *mgr, + struct omap_dss_device *output) +{ + int r; + + mutex_lock(&apply_lock); + + if (mgr->output) { + DSSERR("manager %s is already connected to an output\n", + mgr->name); + r = -EINVAL; + goto err; + } + + if ((mgr->supported_outputs & output->id) == 0) { + DSSERR("output does not support manager %s\n", + mgr->name); + r = -EINVAL; + goto err; + } + + output->manager = mgr; + mgr->output = output; + + mutex_unlock(&apply_lock); + + return 0; +err: + mutex_unlock(&apply_lock); + return r; +} + +static int dss_mgr_unset_output(struct omap_overlay_manager *mgr) +{ + int r; + struct mgr_priv_data *mp = get_mgr_priv(mgr); + unsigned long flags; + + mutex_lock(&apply_lock); + + if (!mgr->output) { + DSSERR("failed to unset output, output not set\n"); + r = -EINVAL; + goto err; + } + + spin_lock_irqsave(&data_lock, flags); + + if (mp->enabled) { + DSSERR("output can't be unset when manager is enabled\n"); + r = -EINVAL; + goto err1; + } + + spin_unlock_irqrestore(&data_lock, flags); + + mgr->output->manager = NULL; + mgr->output = NULL; + + mutex_unlock(&apply_lock); + + return 0; +err1: + spin_unlock_irqrestore(&data_lock, flags); +err: + mutex_unlock(&apply_lock); + + return r; +} + +static void dss_apply_mgr_timings(struct omap_overlay_manager *mgr, + const struct omap_video_timings *timings) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + mp->timings = *timings; + mp->extra_info_dirty = true; +} + +static void dss_mgr_set_timings_compat(struct omap_overlay_manager *mgr, + const struct omap_video_timings *timings) +{ + unsigned long flags; + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + spin_lock_irqsave(&data_lock, flags); + + if (mp->updating) { + DSSERR("cannot set timings for %s: manager needs to be disabled\n", + mgr->name); + goto out; + } + + dss_apply_mgr_timings(mgr, timings); +out: + spin_unlock_irqrestore(&data_lock, flags); +} + +static void dss_apply_mgr_lcd_config(struct omap_overlay_manager *mgr, + const struct dss_lcd_mgr_config *config) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + mp->lcd_config = *config; + mp->extra_info_dirty = true; +} + +static void dss_mgr_set_lcd_config_compat(struct omap_overlay_manager *mgr, + const struct dss_lcd_mgr_config *config) +{ + unsigned long flags; + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + spin_lock_irqsave(&data_lock, flags); + + if (mp->enabled) { + DSSERR("cannot apply lcd config for %s: manager needs to be disabled\n", + mgr->name); + goto out; + } + + dss_apply_mgr_lcd_config(mgr, config); +out: + spin_unlock_irqrestore(&data_lock, flags); +} + +static int dss_ovl_set_info(struct omap_overlay *ovl, + struct omap_overlay_info *info) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + unsigned long flags; + int r; + + r = dss_ovl_simple_check(ovl, info); + if (r) + return r; + + spin_lock_irqsave(&data_lock, flags); + + op->user_info = *info; + op->user_info_dirty = true; + + spin_unlock_irqrestore(&data_lock, flags); + + return 0; +} + +static void dss_ovl_get_info(struct omap_overlay *ovl, + struct omap_overlay_info *info) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + unsigned long flags; + + spin_lock_irqsave(&data_lock, flags); + + *info = op->user_info; + + spin_unlock_irqrestore(&data_lock, flags); +} + +static int dss_ovl_set_manager(struct omap_overlay *ovl, + struct omap_overlay_manager *mgr) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + unsigned long flags; + int r; + + if (!mgr) + return -EINVAL; + + mutex_lock(&apply_lock); + + if (ovl->manager) { + DSSERR("overlay '%s' already has a manager '%s'\n", + ovl->name, ovl->manager->name); + r = -EINVAL; + goto err; + } + + r = dispc_runtime_get(); + if (r) + goto err; + + spin_lock_irqsave(&data_lock, flags); + + if (op->enabled) { + spin_unlock_irqrestore(&data_lock, flags); + DSSERR("overlay has to be disabled to change the manager\n"); + r = -EINVAL; + goto err1; + } + + dispc_ovl_set_channel_out(ovl->id, mgr->id); + + ovl->manager = mgr; + list_add_tail(&ovl->list, &mgr->overlays); + + spin_unlock_irqrestore(&data_lock, flags); + + dispc_runtime_put(); + + mutex_unlock(&apply_lock); + + return 0; + +err1: + dispc_runtime_put(); +err: + mutex_unlock(&apply_lock); + return r; +} + +static int dss_ovl_unset_manager(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + unsigned long flags; + int r; + + mutex_lock(&apply_lock); + + if (!ovl->manager) { + DSSERR("failed to detach overlay: manager not set\n"); + r = -EINVAL; + goto err; + } + + spin_lock_irqsave(&data_lock, flags); + + if (op->enabled) { + spin_unlock_irqrestore(&data_lock, flags); + DSSERR("overlay has to be disabled to unset the manager\n"); + r = -EINVAL; + goto err; + } + + spin_unlock_irqrestore(&data_lock, flags); + + /* wait for pending extra_info updates to ensure the ovl is disabled */ + wait_pending_extra_info_updates(); + + /* + * For a manual update display, there is no guarantee that the overlay + * is really disabled in HW, we may need an extra update from this + * manager before the configurations can go in. Return an error if the + * overlay needed an update from the manager. + * + * TODO: Instead of returning an error, try to do a dummy manager update + * here to disable the overlay in hardware. Use the *GATED fields in + * the DISPC_CONFIG registers to do a dummy update. + */ + spin_lock_irqsave(&data_lock, flags); + + if (ovl_manual_update(ovl) && op->extra_info_dirty) { + spin_unlock_irqrestore(&data_lock, flags); + DSSERR("need an update to change the manager\n"); + r = -EINVAL; + goto err; + } + + ovl->manager = NULL; + list_del(&ovl->list); + + spin_unlock_irqrestore(&data_lock, flags); + + mutex_unlock(&apply_lock); + + return 0; +err: + mutex_unlock(&apply_lock); + return r; +} + +static bool dss_ovl_is_enabled(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + unsigned long flags; + bool e; + + spin_lock_irqsave(&data_lock, flags); + + e = op->enabled; + + spin_unlock_irqrestore(&data_lock, flags); + + return e; +} + +static int dss_ovl_enable(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + unsigned long flags; + int r; + + mutex_lock(&apply_lock); + + if (op->enabled) { + r = 0; + goto err1; + } + + if (ovl->manager == NULL || ovl->manager->output == NULL) { + r = -EINVAL; + goto err1; + } + + spin_lock_irqsave(&data_lock, flags); + + op->enabling = true; + + r = dss_check_settings(ovl->manager); + if (r) { + DSSERR("failed to enable overlay %d: check_settings failed\n", + ovl->id); + goto err2; + } + + dss_setup_fifos(); + + op->enabling = false; + dss_apply_ovl_enable(ovl, true); + + dss_write_regs(); + dss_set_go_bits(); + + spin_unlock_irqrestore(&data_lock, flags); + + mutex_unlock(&apply_lock); + + return 0; +err2: + op->enabling = false; + spin_unlock_irqrestore(&data_lock, flags); +err1: + mutex_unlock(&apply_lock); + return r; +} + +static int dss_ovl_disable(struct omap_overlay *ovl) +{ + struct ovl_priv_data *op = get_ovl_priv(ovl); + unsigned long flags; + int r; + + mutex_lock(&apply_lock); + + if (!op->enabled) { + r = 0; + goto err; + } + + if (ovl->manager == NULL || ovl->manager->output == NULL) { + r = -EINVAL; + goto err; + } + + spin_lock_irqsave(&data_lock, flags); + + dss_apply_ovl_enable(ovl, false); + dss_write_regs(); + dss_set_go_bits(); + + spin_unlock_irqrestore(&data_lock, flags); + + mutex_unlock(&apply_lock); + + return 0; + +err: + mutex_unlock(&apply_lock); + return r; +} + +static int dss_mgr_register_framedone_handler_compat(struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + if (mp->framedone_handler) + return -EBUSY; + + mp->framedone_handler = handler; + mp->framedone_handler_data = data; + + return 0; +} + +static void dss_mgr_unregister_framedone_handler_compat(struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ + struct mgr_priv_data *mp = get_mgr_priv(mgr); + + WARN_ON(mp->framedone_handler != handler || + mp->framedone_handler_data != data); + + mp->framedone_handler = NULL; + mp->framedone_handler_data = NULL; +} + +static const struct dss_mgr_ops apply_mgr_ops = { + .connect = dss_mgr_connect_compat, + .disconnect = dss_mgr_disconnect_compat, + .start_update = dss_mgr_start_update_compat, + .enable = dss_mgr_enable_compat, + .disable = dss_mgr_disable_compat, + .set_timings = dss_mgr_set_timings_compat, + .set_lcd_config = dss_mgr_set_lcd_config_compat, + .register_framedone_handler = dss_mgr_register_framedone_handler_compat, + .unregister_framedone_handler = dss_mgr_unregister_framedone_handler_compat, +}; + +static int compat_refcnt; +static DEFINE_MUTEX(compat_init_lock); + +int omapdss_compat_init(void) +{ + struct platform_device *pdev = dss_get_core_pdev(); + int i, r; + + mutex_lock(&compat_init_lock); + + if (compat_refcnt++ > 0) + goto out; + + apply_init_priv(); + + dss_init_overlay_managers_sysfs(pdev); + dss_init_overlays(pdev); + + for (i = 0; i < omap_dss_get_num_overlay_managers(); i++) { + struct omap_overlay_manager *mgr; + + mgr = omap_dss_get_overlay_manager(i); + + mgr->set_output = &dss_mgr_set_output; + mgr->unset_output = &dss_mgr_unset_output; + mgr->apply = &omap_dss_mgr_apply; + mgr->set_manager_info = &dss_mgr_set_info; + mgr->get_manager_info = &dss_mgr_get_info; + mgr->wait_for_go = &dss_mgr_wait_for_go; + mgr->wait_for_vsync = &dss_mgr_wait_for_vsync; + mgr->get_device = &dss_mgr_get_device; + } + + for (i = 0; i < omap_dss_get_num_overlays(); i++) { + struct omap_overlay *ovl = omap_dss_get_overlay(i); + + ovl->is_enabled = &dss_ovl_is_enabled; + ovl->enable = &dss_ovl_enable; + ovl->disable = &dss_ovl_disable; + ovl->set_manager = &dss_ovl_set_manager; + ovl->unset_manager = &dss_ovl_unset_manager; + ovl->set_overlay_info = &dss_ovl_set_info; + ovl->get_overlay_info = &dss_ovl_get_info; + ovl->wait_for_go = &dss_mgr_wait_for_go_ovl; + ovl->get_device = &dss_ovl_get_device; + } + + r = dss_install_mgr_ops(&apply_mgr_ops); + if (r) + goto err_mgr_ops; + + r = display_init_sysfs(pdev); + if (r) + goto err_disp_sysfs; + + dispc_runtime_get(); + + r = dss_dispc_initialize_irq(); + if (r) + goto err_init_irq; + + dispc_runtime_put(); + +out: + mutex_unlock(&compat_init_lock); + + return 0; + +err_init_irq: + dispc_runtime_put(); + display_uninit_sysfs(pdev); + +err_disp_sysfs: + dss_uninstall_mgr_ops(); + +err_mgr_ops: + dss_uninit_overlay_managers_sysfs(pdev); + dss_uninit_overlays(pdev); + + compat_refcnt--; + + mutex_unlock(&compat_init_lock); + + return r; +} +EXPORT_SYMBOL(omapdss_compat_init); + +void omapdss_compat_uninit(void) +{ + struct platform_device *pdev = dss_get_core_pdev(); + + mutex_lock(&compat_init_lock); + + if (--compat_refcnt > 0) + goto out; + + dss_dispc_uninitialize_irq(); + + display_uninit_sysfs(pdev); + + dss_uninstall_mgr_ops(); + + dss_uninit_overlay_managers_sysfs(pdev); + dss_uninit_overlays(pdev); +out: + mutex_unlock(&compat_init_lock); +} +EXPORT_SYMBOL(omapdss_compat_uninit); diff --git a/drivers/video/fbdev/omap2/dss/core.c b/drivers/video/fbdev/omap2/dss/core.c new file mode 100644 index 000000000000..ffa45c894cd4 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/core.c @@ -0,0 +1,360 @@ +/* + * linux/drivers/video/omap2/dss/core.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "CORE" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/regulator/consumer.h> +#include <linux/suspend.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static struct { + struct platform_device *pdev; + + const char *default_display_name; +} core; + +static char *def_disp_name; +module_param_named(def_disp, def_disp_name, charp, 0); +MODULE_PARM_DESC(def_disp, "default display name"); + +static bool dss_initialized; + +const char *omapdss_get_default_display_name(void) +{ + return core.default_display_name; +} +EXPORT_SYMBOL(omapdss_get_default_display_name); + +enum omapdss_version omapdss_get_version(void) +{ + struct omap_dss_board_info *pdata = core.pdev->dev.platform_data; + return pdata->version; +} +EXPORT_SYMBOL(omapdss_get_version); + +bool omapdss_is_initialized(void) +{ + return dss_initialized; +} +EXPORT_SYMBOL(omapdss_is_initialized); + +struct platform_device *dss_get_core_pdev(void) +{ + return core.pdev; +} + +int dss_dsi_enable_pads(int dsi_id, unsigned lane_mask) +{ + struct omap_dss_board_info *board_data = core.pdev->dev.platform_data; + + if (!board_data->dsi_enable_pads) + return -ENOENT; + + return board_data->dsi_enable_pads(dsi_id, lane_mask); +} + +void dss_dsi_disable_pads(int dsi_id, unsigned lane_mask) +{ + struct omap_dss_board_info *board_data = core.pdev->dev.platform_data; + + if (!board_data->dsi_disable_pads) + return; + + return board_data->dsi_disable_pads(dsi_id, lane_mask); +} + +int dss_set_min_bus_tput(struct device *dev, unsigned long tput) +{ + struct omap_dss_board_info *pdata = core.pdev->dev.platform_data; + + if (pdata->set_min_bus_tput) + return pdata->set_min_bus_tput(dev, tput); + else + return 0; +} + +#if defined(CONFIG_OMAP2_DSS_DEBUGFS) +static int dss_debug_show(struct seq_file *s, void *unused) +{ + void (*func)(struct seq_file *) = s->private; + func(s); + return 0; +} + +static int dss_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, dss_debug_show, inode->i_private); +} + +static const struct file_operations dss_debug_fops = { + .open = dss_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *dss_debugfs_dir; + +static int dss_initialize_debugfs(void) +{ + dss_debugfs_dir = debugfs_create_dir("omapdss", NULL); + if (IS_ERR(dss_debugfs_dir)) { + int err = PTR_ERR(dss_debugfs_dir); + dss_debugfs_dir = NULL; + return err; + } + + debugfs_create_file("clk", S_IRUGO, dss_debugfs_dir, + &dss_debug_dump_clocks, &dss_debug_fops); + + return 0; +} + +static void dss_uninitialize_debugfs(void) +{ + if (dss_debugfs_dir) + debugfs_remove_recursive(dss_debugfs_dir); +} + +int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *)) +{ + struct dentry *d; + + d = debugfs_create_file(name, S_IRUGO, dss_debugfs_dir, + write, &dss_debug_fops); + + return PTR_ERR_OR_ZERO(d); +} +#else /* CONFIG_OMAP2_DSS_DEBUGFS */ +static inline int dss_initialize_debugfs(void) +{ + return 0; +} +static inline void dss_uninitialize_debugfs(void) +{ +} +int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *)) +{ + return 0; +} +#endif /* CONFIG_OMAP2_DSS_DEBUGFS */ + +/* PLATFORM DEVICE */ +static int omap_dss_pm_notif(struct notifier_block *b, unsigned long v, void *d) +{ + DSSDBG("pm notif %lu\n", v); + + switch (v) { + case PM_SUSPEND_PREPARE: + DSSDBG("suspending displays\n"); + return dss_suspend_all_devices(); + + case PM_POST_SUSPEND: + DSSDBG("resuming displays\n"); + return dss_resume_all_devices(); + + default: + return 0; + } +} + +static struct notifier_block omap_dss_pm_notif_block = { + .notifier_call = omap_dss_pm_notif, +}; + +static int __init omap_dss_probe(struct platform_device *pdev) +{ + struct omap_dss_board_info *pdata = pdev->dev.platform_data; + int r; + + core.pdev = pdev; + + dss_features_init(omapdss_get_version()); + + r = dss_initialize_debugfs(); + if (r) + goto err_debugfs; + + if (def_disp_name) + core.default_display_name = def_disp_name; + else if (pdata->default_display_name) + core.default_display_name = pdata->default_display_name; + else if (pdata->default_device) + core.default_display_name = pdata->default_device->name; + + register_pm_notifier(&omap_dss_pm_notif_block); + + return 0; + +err_debugfs: + + return r; +} + +static int omap_dss_remove(struct platform_device *pdev) +{ + unregister_pm_notifier(&omap_dss_pm_notif_block); + + dss_uninitialize_debugfs(); + + return 0; +} + +static void omap_dss_shutdown(struct platform_device *pdev) +{ + DSSDBG("shutdown\n"); + dss_disable_all_devices(); +} + +static struct platform_driver omap_dss_driver = { + .remove = omap_dss_remove, + .shutdown = omap_dss_shutdown, + .driver = { + .name = "omapdss", + .owner = THIS_MODULE, + }, +}; + +/* INIT */ +static int (*dss_output_drv_reg_funcs[])(void) __initdata = { +#ifdef CONFIG_OMAP2_DSS_DSI + dsi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_DPI + dpi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_SDI + sdi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_RFBI + rfbi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_VENC + venc_init_platform_driver, +#endif +#ifdef CONFIG_OMAP4_DSS_HDMI + hdmi4_init_platform_driver, +#endif +}; + +static void (*dss_output_drv_unreg_funcs[])(void) __exitdata = { +#ifdef CONFIG_OMAP2_DSS_DSI + dsi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_DPI + dpi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_SDI + sdi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_RFBI + rfbi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_VENC + venc_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP4_DSS_HDMI + hdmi4_uninit_platform_driver, +#endif +}; + +static bool dss_output_drv_loaded[ARRAY_SIZE(dss_output_drv_reg_funcs)]; + +static int __init omap_dss_init(void) +{ + int r; + int i; + + r = platform_driver_probe(&omap_dss_driver, omap_dss_probe); + if (r) + return r; + + r = dss_init_platform_driver(); + if (r) { + DSSERR("Failed to initialize DSS platform driver\n"); + goto err_dss; + } + + r = dispc_init_platform_driver(); + if (r) { + DSSERR("Failed to initialize dispc platform driver\n"); + goto err_dispc; + } + + /* + * It's ok if the output-driver register fails. It happens, for example, + * when there is no output-device (e.g. SDI for OMAP4). + */ + for (i = 0; i < ARRAY_SIZE(dss_output_drv_reg_funcs); ++i) { + r = dss_output_drv_reg_funcs[i](); + if (r == 0) + dss_output_drv_loaded[i] = true; + } + + dss_initialized = true; + + return 0; + +err_dispc: + dss_uninit_platform_driver(); +err_dss: + platform_driver_unregister(&omap_dss_driver); + + return r; +} + +static void __exit omap_dss_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dss_output_drv_unreg_funcs); ++i) { + if (dss_output_drv_loaded[i]) + dss_output_drv_unreg_funcs[i](); + } + + dispc_uninit_platform_driver(); + dss_uninit_platform_driver(); + + platform_driver_unregister(&omap_dss_driver); +} + +module_init(omap_dss_init); +module_exit(omap_dss_exit); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@nokia.com>"); +MODULE_DESCRIPTION("OMAP2/3 Display Subsystem"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/video/fbdev/omap2/dss/dispc-compat.c b/drivers/video/fbdev/omap2/dss/dispc-compat.c new file mode 100644 index 000000000000..83779c2b292a --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc-compat.c @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "APPLY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/seq_file.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" +#include "dispc-compat.h" + +#define DISPC_IRQ_MASK_ERROR (DISPC_IRQ_GFX_FIFO_UNDERFLOW | \ + DISPC_IRQ_OCP_ERR | \ + DISPC_IRQ_VID1_FIFO_UNDERFLOW | \ + DISPC_IRQ_VID2_FIFO_UNDERFLOW | \ + DISPC_IRQ_SYNC_LOST | \ + DISPC_IRQ_SYNC_LOST_DIGIT) + +#define DISPC_MAX_NR_ISRS 8 + +struct omap_dispc_isr_data { + omap_dispc_isr_t isr; + void *arg; + u32 mask; +}; + +struct dispc_irq_stats { + unsigned long last_reset; + unsigned irq_count; + unsigned irqs[32]; +}; + +static struct { + spinlock_t irq_lock; + u32 irq_error_mask; + struct omap_dispc_isr_data registered_isr[DISPC_MAX_NR_ISRS]; + u32 error_irqs; + struct work_struct error_work; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS + spinlock_t irq_stats_lock; + struct dispc_irq_stats irq_stats; +#endif +} dispc_compat; + + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static void dispc_dump_irqs(struct seq_file *s) +{ + unsigned long flags; + struct dispc_irq_stats stats; + + spin_lock_irqsave(&dispc_compat.irq_stats_lock, flags); + + stats = dispc_compat.irq_stats; + memset(&dispc_compat.irq_stats, 0, sizeof(dispc_compat.irq_stats)); + dispc_compat.irq_stats.last_reset = jiffies; + + spin_unlock_irqrestore(&dispc_compat.irq_stats_lock, flags); + + seq_printf(s, "period %u ms\n", + jiffies_to_msecs(jiffies - stats.last_reset)); + + seq_printf(s, "irqs %d\n", stats.irq_count); +#define PIS(x) \ + seq_printf(s, "%-20s %10d\n", #x, stats.irqs[ffs(DISPC_IRQ_##x)-1]); + + PIS(FRAMEDONE); + PIS(VSYNC); + PIS(EVSYNC_EVEN); + PIS(EVSYNC_ODD); + PIS(ACBIAS_COUNT_STAT); + PIS(PROG_LINE_NUM); + PIS(GFX_FIFO_UNDERFLOW); + PIS(GFX_END_WIN); + PIS(PAL_GAMMA_MASK); + PIS(OCP_ERR); + PIS(VID1_FIFO_UNDERFLOW); + PIS(VID1_END_WIN); + PIS(VID2_FIFO_UNDERFLOW); + PIS(VID2_END_WIN); + if (dss_feat_get_num_ovls() > 3) { + PIS(VID3_FIFO_UNDERFLOW); + PIS(VID3_END_WIN); + } + PIS(SYNC_LOST); + PIS(SYNC_LOST_DIGIT); + PIS(WAKEUP); + if (dss_has_feature(FEAT_MGR_LCD2)) { + PIS(FRAMEDONE2); + PIS(VSYNC2); + PIS(ACBIAS_COUNT_STAT2); + PIS(SYNC_LOST2); + } + if (dss_has_feature(FEAT_MGR_LCD3)) { + PIS(FRAMEDONE3); + PIS(VSYNC3); + PIS(ACBIAS_COUNT_STAT3); + PIS(SYNC_LOST3); + } +#undef PIS +} +#endif + +/* dispc.irq_lock has to be locked by the caller */ +static void _omap_dispc_set_irqs(void) +{ + u32 mask; + int i; + struct omap_dispc_isr_data *isr_data; + + mask = dispc_compat.irq_error_mask; + + for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { + isr_data = &dispc_compat.registered_isr[i]; + + if (isr_data->isr == NULL) + continue; + + mask |= isr_data->mask; + } + + dispc_write_irqenable(mask); +} + +int omap_dispc_register_isr(omap_dispc_isr_t isr, void *arg, u32 mask) +{ + int i; + int ret; + unsigned long flags; + struct omap_dispc_isr_data *isr_data; + + if (isr == NULL) + return -EINVAL; + + spin_lock_irqsave(&dispc_compat.irq_lock, flags); + + /* check for duplicate entry */ + for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { + isr_data = &dispc_compat.registered_isr[i]; + if (isr_data->isr == isr && isr_data->arg == arg && + isr_data->mask == mask) { + ret = -EINVAL; + goto err; + } + } + + isr_data = NULL; + ret = -EBUSY; + + for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { + isr_data = &dispc_compat.registered_isr[i]; + + if (isr_data->isr != NULL) + continue; + + isr_data->isr = isr; + isr_data->arg = arg; + isr_data->mask = mask; + ret = 0; + + break; + } + + if (ret) + goto err; + + _omap_dispc_set_irqs(); + + spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + + return 0; +err: + spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + + return ret; +} +EXPORT_SYMBOL(omap_dispc_register_isr); + +int omap_dispc_unregister_isr(omap_dispc_isr_t isr, void *arg, u32 mask) +{ + int i; + unsigned long flags; + int ret = -EINVAL; + struct omap_dispc_isr_data *isr_data; + + spin_lock_irqsave(&dispc_compat.irq_lock, flags); + + for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { + isr_data = &dispc_compat.registered_isr[i]; + if (isr_data->isr != isr || isr_data->arg != arg || + isr_data->mask != mask) + continue; + + /* found the correct isr */ + + isr_data->isr = NULL; + isr_data->arg = NULL; + isr_data->mask = 0; + + ret = 0; + break; + } + + if (ret == 0) + _omap_dispc_set_irqs(); + + spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + + return ret; +} +EXPORT_SYMBOL(omap_dispc_unregister_isr); + +static void print_irq_status(u32 status) +{ + if ((status & dispc_compat.irq_error_mask) == 0) + return; + +#define PIS(x) (status & DISPC_IRQ_##x) ? (#x " ") : "" + + pr_debug("DISPC IRQ: 0x%x: %s%s%s%s%s%s%s%s%s\n", + status, + PIS(OCP_ERR), + PIS(GFX_FIFO_UNDERFLOW), + PIS(VID1_FIFO_UNDERFLOW), + PIS(VID2_FIFO_UNDERFLOW), + dss_feat_get_num_ovls() > 3 ? PIS(VID3_FIFO_UNDERFLOW) : "", + PIS(SYNC_LOST), + PIS(SYNC_LOST_DIGIT), + dss_has_feature(FEAT_MGR_LCD2) ? PIS(SYNC_LOST2) : "", + dss_has_feature(FEAT_MGR_LCD3) ? PIS(SYNC_LOST3) : ""); +#undef PIS +} + +/* Called from dss.c. Note that we don't touch clocks here, + * but we presume they are on because we got an IRQ. However, + * an irq handler may turn the clocks off, so we may not have + * clock later in the function. */ +static irqreturn_t omap_dispc_irq_handler(int irq, void *arg) +{ + int i; + u32 irqstatus, irqenable; + u32 handledirqs = 0; + u32 unhandled_errors; + struct omap_dispc_isr_data *isr_data; + struct omap_dispc_isr_data registered_isr[DISPC_MAX_NR_ISRS]; + + spin_lock(&dispc_compat.irq_lock); + + irqstatus = dispc_read_irqstatus(); + irqenable = dispc_read_irqenable(); + + /* IRQ is not for us */ + if (!(irqstatus & irqenable)) { + spin_unlock(&dispc_compat.irq_lock); + return IRQ_NONE; + } + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS + spin_lock(&dispc_compat.irq_stats_lock); + dispc_compat.irq_stats.irq_count++; + dss_collect_irq_stats(irqstatus, dispc_compat.irq_stats.irqs); + spin_unlock(&dispc_compat.irq_stats_lock); +#endif + + print_irq_status(irqstatus); + + /* Ack the interrupt. Do it here before clocks are possibly turned + * off */ + dispc_clear_irqstatus(irqstatus); + /* flush posted write */ + dispc_read_irqstatus(); + + /* make a copy and unlock, so that isrs can unregister + * themselves */ + memcpy(registered_isr, dispc_compat.registered_isr, + sizeof(registered_isr)); + + spin_unlock(&dispc_compat.irq_lock); + + for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { + isr_data = ®istered_isr[i]; + + if (!isr_data->isr) + continue; + + if (isr_data->mask & irqstatus) { + isr_data->isr(isr_data->arg, irqstatus); + handledirqs |= isr_data->mask; + } + } + + spin_lock(&dispc_compat.irq_lock); + + unhandled_errors = irqstatus & ~handledirqs & dispc_compat.irq_error_mask; + + if (unhandled_errors) { + dispc_compat.error_irqs |= unhandled_errors; + + dispc_compat.irq_error_mask &= ~unhandled_errors; + _omap_dispc_set_irqs(); + + schedule_work(&dispc_compat.error_work); + } + + spin_unlock(&dispc_compat.irq_lock); + + return IRQ_HANDLED; +} + +static void dispc_error_worker(struct work_struct *work) +{ + int i; + u32 errors; + unsigned long flags; + static const unsigned fifo_underflow_bits[] = { + DISPC_IRQ_GFX_FIFO_UNDERFLOW, + DISPC_IRQ_VID1_FIFO_UNDERFLOW, + DISPC_IRQ_VID2_FIFO_UNDERFLOW, + DISPC_IRQ_VID3_FIFO_UNDERFLOW, + }; + + spin_lock_irqsave(&dispc_compat.irq_lock, flags); + errors = dispc_compat.error_irqs; + dispc_compat.error_irqs = 0; + spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + + dispc_runtime_get(); + + for (i = 0; i < omap_dss_get_num_overlays(); ++i) { + struct omap_overlay *ovl; + unsigned bit; + + ovl = omap_dss_get_overlay(i); + bit = fifo_underflow_bits[i]; + + if (bit & errors) { + DSSERR("FIFO UNDERFLOW on %s, disabling the overlay\n", + ovl->name); + ovl->disable(ovl); + msleep(50); + } + } + + for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { + struct omap_overlay_manager *mgr; + unsigned bit; + + mgr = omap_dss_get_overlay_manager(i); + bit = dispc_mgr_get_sync_lost_irq(i); + + if (bit & errors) { + int j; + + DSSERR("SYNC_LOST on channel %s, restarting the output " + "with video overlays disabled\n", + mgr->name); + + dss_mgr_disable(mgr); + + for (j = 0; j < omap_dss_get_num_overlays(); ++j) { + struct omap_overlay *ovl; + ovl = omap_dss_get_overlay(j); + + if (ovl->id != OMAP_DSS_GFX && + ovl->manager == mgr) + ovl->disable(ovl); + } + + dss_mgr_enable(mgr); + } + } + + if (errors & DISPC_IRQ_OCP_ERR) { + DSSERR("OCP_ERR\n"); + for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { + struct omap_overlay_manager *mgr; + + mgr = omap_dss_get_overlay_manager(i); + dss_mgr_disable(mgr); + } + } + + spin_lock_irqsave(&dispc_compat.irq_lock, flags); + dispc_compat.irq_error_mask |= errors; + _omap_dispc_set_irqs(); + spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + + dispc_runtime_put(); +} + +int dss_dispc_initialize_irq(void) +{ + int r; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS + spin_lock_init(&dispc_compat.irq_stats_lock); + dispc_compat.irq_stats.last_reset = jiffies; + dss_debugfs_create_file("dispc_irq", dispc_dump_irqs); +#endif + + spin_lock_init(&dispc_compat.irq_lock); + + memset(dispc_compat.registered_isr, 0, + sizeof(dispc_compat.registered_isr)); + + dispc_compat.irq_error_mask = DISPC_IRQ_MASK_ERROR; + if (dss_has_feature(FEAT_MGR_LCD2)) + dispc_compat.irq_error_mask |= DISPC_IRQ_SYNC_LOST2; + if (dss_has_feature(FEAT_MGR_LCD3)) + dispc_compat.irq_error_mask |= DISPC_IRQ_SYNC_LOST3; + if (dss_feat_get_num_ovls() > 3) + dispc_compat.irq_error_mask |= DISPC_IRQ_VID3_FIFO_UNDERFLOW; + + /* + * there's SYNC_LOST_DIGIT waiting after enabling the DSS, + * so clear it + */ + dispc_clear_irqstatus(dispc_read_irqstatus()); + + INIT_WORK(&dispc_compat.error_work, dispc_error_worker); + + _omap_dispc_set_irqs(); + + r = dispc_request_irq(omap_dispc_irq_handler, &dispc_compat); + if (r) { + DSSERR("dispc_request_irq failed\n"); + return r; + } + + return 0; +} + +void dss_dispc_uninitialize_irq(void) +{ + dispc_free_irq(&dispc_compat); +} + +static void dispc_mgr_disable_isr(void *data, u32 mask) +{ + struct completion *compl = data; + complete(compl); +} + +static void dispc_mgr_enable_lcd_out(enum omap_channel channel) +{ + dispc_mgr_enable(channel, true); +} + +static void dispc_mgr_disable_lcd_out(enum omap_channel channel) +{ + DECLARE_COMPLETION_ONSTACK(framedone_compl); + int r; + u32 irq; + + if (dispc_mgr_is_enabled(channel) == false) + return; + + /* + * When we disable LCD output, we need to wait for FRAMEDONE to know + * that DISPC has finished with the LCD output. + */ + + irq = dispc_mgr_get_framedone_irq(channel); + + r = omap_dispc_register_isr(dispc_mgr_disable_isr, &framedone_compl, + irq); + if (r) + DSSERR("failed to register FRAMEDONE isr\n"); + + dispc_mgr_enable(channel, false); + + /* if we couldn't register for framedone, just sleep and exit */ + if (r) { + msleep(100); + return; + } + + if (!wait_for_completion_timeout(&framedone_compl, + msecs_to_jiffies(100))) + DSSERR("timeout waiting for FRAME DONE\n"); + + r = omap_dispc_unregister_isr(dispc_mgr_disable_isr, &framedone_compl, + irq); + if (r) + DSSERR("failed to unregister FRAMEDONE isr\n"); +} + +static void dispc_digit_out_enable_isr(void *data, u32 mask) +{ + struct completion *compl = data; + + /* ignore any sync lost interrupts */ + if (mask & (DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD)) + complete(compl); +} + +static void dispc_mgr_enable_digit_out(void) +{ + DECLARE_COMPLETION_ONSTACK(vsync_compl); + int r; + u32 irq_mask; + + if (dispc_mgr_is_enabled(OMAP_DSS_CHANNEL_DIGIT) == true) + return; + + /* + * Digit output produces some sync lost interrupts during the first + * frame when enabling. Those need to be ignored, so we register for the + * sync lost irq to prevent the error handler from triggering. + */ + + irq_mask = dispc_mgr_get_vsync_irq(OMAP_DSS_CHANNEL_DIGIT) | + dispc_mgr_get_sync_lost_irq(OMAP_DSS_CHANNEL_DIGIT); + + r = omap_dispc_register_isr(dispc_digit_out_enable_isr, &vsync_compl, + irq_mask); + if (r) { + DSSERR("failed to register %x isr\n", irq_mask); + return; + } + + dispc_mgr_enable(OMAP_DSS_CHANNEL_DIGIT, true); + + /* wait for the first evsync */ + if (!wait_for_completion_timeout(&vsync_compl, msecs_to_jiffies(100))) + DSSERR("timeout waiting for digit out to start\n"); + + r = omap_dispc_unregister_isr(dispc_digit_out_enable_isr, &vsync_compl, + irq_mask); + if (r) + DSSERR("failed to unregister %x isr\n", irq_mask); +} + +static void dispc_mgr_disable_digit_out(void) +{ + DECLARE_COMPLETION_ONSTACK(framedone_compl); + int r, i; + u32 irq_mask; + int num_irqs; + + if (dispc_mgr_is_enabled(OMAP_DSS_CHANNEL_DIGIT) == false) + return; + + /* + * When we disable the digit output, we need to wait for FRAMEDONE to + * know that DISPC has finished with the output. + */ + + irq_mask = dispc_mgr_get_framedone_irq(OMAP_DSS_CHANNEL_DIGIT); + num_irqs = 1; + + if (!irq_mask) { + /* + * omap 2/3 don't have framedone irq for TV, so we need to use + * vsyncs for this. + */ + + irq_mask = dispc_mgr_get_vsync_irq(OMAP_DSS_CHANNEL_DIGIT); + /* + * We need to wait for both even and odd vsyncs. Note that this + * is not totally reliable, as we could get a vsync interrupt + * before we disable the output, which leads to timeout in the + * wait_for_completion. + */ + num_irqs = 2; + } + + r = omap_dispc_register_isr(dispc_mgr_disable_isr, &framedone_compl, + irq_mask); + if (r) + DSSERR("failed to register %x isr\n", irq_mask); + + dispc_mgr_enable(OMAP_DSS_CHANNEL_DIGIT, false); + + /* if we couldn't register the irq, just sleep and exit */ + if (r) { + msleep(100); + return; + } + + for (i = 0; i < num_irqs; ++i) { + if (!wait_for_completion_timeout(&framedone_compl, + msecs_to_jiffies(100))) + DSSERR("timeout waiting for digit out to stop\n"); + } + + r = omap_dispc_unregister_isr(dispc_mgr_disable_isr, &framedone_compl, + irq_mask); + if (r) + DSSERR("failed to unregister %x isr\n", irq_mask); +} + +void dispc_mgr_enable_sync(enum omap_channel channel) +{ + if (dss_mgr_is_lcd(channel)) + dispc_mgr_enable_lcd_out(channel); + else if (channel == OMAP_DSS_CHANNEL_DIGIT) + dispc_mgr_enable_digit_out(); + else + WARN_ON(1); +} + +void dispc_mgr_disable_sync(enum omap_channel channel) +{ + if (dss_mgr_is_lcd(channel)) + dispc_mgr_disable_lcd_out(channel); + else if (channel == OMAP_DSS_CHANNEL_DIGIT) + dispc_mgr_disable_digit_out(); + else + WARN_ON(1); +} + +int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask, + unsigned long timeout) +{ + void dispc_irq_wait_handler(void *data, u32 mask) + { + complete((struct completion *)data); + } + + int r; + DECLARE_COMPLETION_ONSTACK(completion); + + r = omap_dispc_register_isr(dispc_irq_wait_handler, &completion, + irqmask); + + if (r) + return r; + + timeout = wait_for_completion_interruptible_timeout(&completion, + timeout); + + omap_dispc_unregister_isr(dispc_irq_wait_handler, &completion, irqmask); + + if (timeout == 0) + return -ETIMEDOUT; + + if (timeout == -ERESTARTSYS) + return -ERESTARTSYS; + + return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/dispc-compat.h b/drivers/video/fbdev/omap2/dss/dispc-compat.h new file mode 100644 index 000000000000..14a69b3d4fb0 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc-compat.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP2_DSS_DISPC_COMPAT_H +#define __OMAP2_DSS_DISPC_COMPAT_H + +void dispc_mgr_enable_sync(enum omap_channel channel); +void dispc_mgr_disable_sync(enum omap_channel channel); + +int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask, + unsigned long timeout); + +int dss_dispc_initialize_irq(void); +void dss_dispc_uninitialize_irq(void); + +#endif diff --git a/drivers/video/fbdev/omap2/dss/dispc.c b/drivers/video/fbdev/omap2/dss/dispc.c new file mode 100644 index 000000000000..f18397c33e8f --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc.c @@ -0,0 +1,3853 @@ +/* + * linux/drivers/video/omap2/dss/dispc.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "DISPC" + +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/export.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/seq_file.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/hardirq.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/sizes.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" +#include "dispc.h" + +/* DISPC */ +#define DISPC_SZ_REGS SZ_4K + +enum omap_burst_size { + BURST_SIZE_X2 = 0, + BURST_SIZE_X4 = 1, + BURST_SIZE_X8 = 2, +}; + +#define REG_GET(idx, start, end) \ + FLD_GET(dispc_read_reg(idx), start, end) + +#define REG_FLD_MOD(idx, val, start, end) \ + dispc_write_reg(idx, FLD_MOD(dispc_read_reg(idx), val, start, end)) + +struct dispc_features { + u8 sw_start; + u8 fp_start; + u8 bp_start; + u16 sw_max; + u16 vp_max; + u16 hp_max; + u8 mgr_width_start; + u8 mgr_height_start; + u16 mgr_width_max; + u16 mgr_height_max; + unsigned long max_lcd_pclk; + unsigned long max_tv_pclk; + int (*calc_scaling) (unsigned long pclk, unsigned long lclk, + const struct omap_video_timings *mgr_timings, + u16 width, u16 height, u16 out_width, u16 out_height, + enum omap_color_mode color_mode, bool *five_taps, + int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, bool mem_to_mem); + unsigned long (*calc_core_clk) (unsigned long pclk, + u16 width, u16 height, u16 out_width, u16 out_height, + bool mem_to_mem); + u8 num_fifos; + + /* swap GFX & WB fifos */ + bool gfx_fifo_workaround:1; + + /* no DISPC_IRQ_FRAMEDONETV on this SoC */ + bool no_framedone_tv:1; + + /* revert to the OMAP4 mechanism of DISPC Smart Standby operation */ + bool mstandby_workaround:1; + + bool set_max_preload:1; +}; + +#define DISPC_MAX_NR_FIFOS 5 + +static struct { + struct platform_device *pdev; + void __iomem *base; + + int irq; + irq_handler_t user_handler; + void *user_data; + + unsigned long core_clk_rate; + unsigned long tv_pclk_rate; + + u32 fifo_size[DISPC_MAX_NR_FIFOS]; + /* maps which plane is using a fifo. fifo-id -> plane-id */ + int fifo_assignment[DISPC_MAX_NR_FIFOS]; + + bool ctx_valid; + u32 ctx[DISPC_SZ_REGS / sizeof(u32)]; + + const struct dispc_features *feat; + + bool is_enabled; +} dispc; + +enum omap_color_component { + /* used for all color formats for OMAP3 and earlier + * and for RGB and Y color component on OMAP4 + */ + DISPC_COLOR_COMPONENT_RGB_Y = 1 << 0, + /* used for UV component for + * OMAP_DSS_COLOR_YUV2, OMAP_DSS_COLOR_UYVY, OMAP_DSS_COLOR_NV12 + * color formats on OMAP4 + */ + DISPC_COLOR_COMPONENT_UV = 1 << 1, +}; + +enum mgr_reg_fields { + DISPC_MGR_FLD_ENABLE, + DISPC_MGR_FLD_STNTFT, + DISPC_MGR_FLD_GO, + DISPC_MGR_FLD_TFTDATALINES, + DISPC_MGR_FLD_STALLMODE, + DISPC_MGR_FLD_TCKENABLE, + DISPC_MGR_FLD_TCKSELECTION, + DISPC_MGR_FLD_CPR, + DISPC_MGR_FLD_FIFOHANDCHECK, + /* used to maintain a count of the above fields */ + DISPC_MGR_FLD_NUM, +}; + +struct dispc_reg_field { + u16 reg; + u8 high; + u8 low; +}; + +static const struct { + const char *name; + u32 vsync_irq; + u32 framedone_irq; + u32 sync_lost_irq; + struct dispc_reg_field reg_desc[DISPC_MGR_FLD_NUM]; +} mgr_desc[] = { + [OMAP_DSS_CHANNEL_LCD] = { + .name = "LCD", + .vsync_irq = DISPC_IRQ_VSYNC, + .framedone_irq = DISPC_IRQ_FRAMEDONE, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL, 0, 0 }, + [DISPC_MGR_FLD_STNTFT] = { DISPC_CONTROL, 3, 3 }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL, 5, 5 }, + [DISPC_MGR_FLD_TFTDATALINES] = { DISPC_CONTROL, 9, 8 }, + [DISPC_MGR_FLD_STALLMODE] = { DISPC_CONTROL, 11, 11 }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG, 10, 10 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG, 11, 11 }, + [DISPC_MGR_FLD_CPR] = { DISPC_CONFIG, 15, 15 }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG, 16, 16 }, + }, + }, + [OMAP_DSS_CHANNEL_DIGIT] = { + .name = "DIGIT", + .vsync_irq = DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN, + .framedone_irq = DISPC_IRQ_FRAMEDONETV, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST_DIGIT, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL, 1, 1 }, + [DISPC_MGR_FLD_STNTFT] = { }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL, 6, 6 }, + [DISPC_MGR_FLD_TFTDATALINES] = { }, + [DISPC_MGR_FLD_STALLMODE] = { }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG, 12, 12 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG, 13, 13 }, + [DISPC_MGR_FLD_CPR] = { }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG, 16, 16 }, + }, + }, + [OMAP_DSS_CHANNEL_LCD2] = { + .name = "LCD2", + .vsync_irq = DISPC_IRQ_VSYNC2, + .framedone_irq = DISPC_IRQ_FRAMEDONE2, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST2, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL2, 0, 0 }, + [DISPC_MGR_FLD_STNTFT] = { DISPC_CONTROL2, 3, 3 }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL2, 5, 5 }, + [DISPC_MGR_FLD_TFTDATALINES] = { DISPC_CONTROL2, 9, 8 }, + [DISPC_MGR_FLD_STALLMODE] = { DISPC_CONTROL2, 11, 11 }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG2, 10, 10 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG2, 11, 11 }, + [DISPC_MGR_FLD_CPR] = { DISPC_CONFIG2, 15, 15 }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG2, 16, 16 }, + }, + }, + [OMAP_DSS_CHANNEL_LCD3] = { + .name = "LCD3", + .vsync_irq = DISPC_IRQ_VSYNC3, + .framedone_irq = DISPC_IRQ_FRAMEDONE3, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST3, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL3, 0, 0 }, + [DISPC_MGR_FLD_STNTFT] = { DISPC_CONTROL3, 3, 3 }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL3, 5, 5 }, + [DISPC_MGR_FLD_TFTDATALINES] = { DISPC_CONTROL3, 9, 8 }, + [DISPC_MGR_FLD_STALLMODE] = { DISPC_CONTROL3, 11, 11 }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG3, 10, 10 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG3, 11, 11 }, + [DISPC_MGR_FLD_CPR] = { DISPC_CONFIG3, 15, 15 }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG3, 16, 16 }, + }, + }, +}; + +struct color_conv_coef { + int ry, rcr, rcb, gy, gcr, gcb, by, bcr, bcb; + int full_range; +}; + +static unsigned long dispc_plane_pclk_rate(enum omap_plane plane); +static unsigned long dispc_plane_lclk_rate(enum omap_plane plane); + +static inline void dispc_write_reg(const u16 idx, u32 val) +{ + __raw_writel(val, dispc.base + idx); +} + +static inline u32 dispc_read_reg(const u16 idx) +{ + return __raw_readl(dispc.base + idx); +} + +static u32 mgr_fld_read(enum omap_channel channel, enum mgr_reg_fields regfld) +{ + const struct dispc_reg_field rfld = mgr_desc[channel].reg_desc[regfld]; + return REG_GET(rfld.reg, rfld.high, rfld.low); +} + +static void mgr_fld_write(enum omap_channel channel, + enum mgr_reg_fields regfld, int val) { + const struct dispc_reg_field rfld = mgr_desc[channel].reg_desc[regfld]; + REG_FLD_MOD(rfld.reg, val, rfld.high, rfld.low); +} + +#define SR(reg) \ + dispc.ctx[DISPC_##reg / sizeof(u32)] = dispc_read_reg(DISPC_##reg) +#define RR(reg) \ + dispc_write_reg(DISPC_##reg, dispc.ctx[DISPC_##reg / sizeof(u32)]) + +static void dispc_save_context(void) +{ + int i, j; + + DSSDBG("dispc_save_context\n"); + + SR(IRQENABLE); + SR(CONTROL); + SR(CONFIG); + SR(LINE_NUMBER); + if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER) || + dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) + SR(GLOBAL_ALPHA); + if (dss_has_feature(FEAT_MGR_LCD2)) { + SR(CONTROL2); + SR(CONFIG2); + } + if (dss_has_feature(FEAT_MGR_LCD3)) { + SR(CONTROL3); + SR(CONFIG3); + } + + for (i = 0; i < dss_feat_get_num_mgrs(); i++) { + SR(DEFAULT_COLOR(i)); + SR(TRANS_COLOR(i)); + SR(SIZE_MGR(i)); + if (i == OMAP_DSS_CHANNEL_DIGIT) + continue; + SR(TIMING_H(i)); + SR(TIMING_V(i)); + SR(POL_FREQ(i)); + SR(DIVISORo(i)); + + SR(DATA_CYCLE1(i)); + SR(DATA_CYCLE2(i)); + SR(DATA_CYCLE3(i)); + + if (dss_has_feature(FEAT_CPR)) { + SR(CPR_COEF_R(i)); + SR(CPR_COEF_G(i)); + SR(CPR_COEF_B(i)); + } + } + + for (i = 0; i < dss_feat_get_num_ovls(); i++) { + SR(OVL_BA0(i)); + SR(OVL_BA1(i)); + SR(OVL_POSITION(i)); + SR(OVL_SIZE(i)); + SR(OVL_ATTRIBUTES(i)); + SR(OVL_FIFO_THRESHOLD(i)); + SR(OVL_ROW_INC(i)); + SR(OVL_PIXEL_INC(i)); + if (dss_has_feature(FEAT_PRELOAD)) + SR(OVL_PRELOAD(i)); + if (i == OMAP_DSS_GFX) { + SR(OVL_WINDOW_SKIP(i)); + SR(OVL_TABLE_BA(i)); + continue; + } + SR(OVL_FIR(i)); + SR(OVL_PICTURE_SIZE(i)); + SR(OVL_ACCU0(i)); + SR(OVL_ACCU1(i)); + + for (j = 0; j < 8; j++) + SR(OVL_FIR_COEF_H(i, j)); + + for (j = 0; j < 8; j++) + SR(OVL_FIR_COEF_HV(i, j)); + + for (j = 0; j < 5; j++) + SR(OVL_CONV_COEF(i, j)); + + if (dss_has_feature(FEAT_FIR_COEF_V)) { + for (j = 0; j < 8; j++) + SR(OVL_FIR_COEF_V(i, j)); + } + + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + SR(OVL_BA0_UV(i)); + SR(OVL_BA1_UV(i)); + SR(OVL_FIR2(i)); + SR(OVL_ACCU2_0(i)); + SR(OVL_ACCU2_1(i)); + + for (j = 0; j < 8; j++) + SR(OVL_FIR_COEF_H2(i, j)); + + for (j = 0; j < 8; j++) + SR(OVL_FIR_COEF_HV2(i, j)); + + for (j = 0; j < 8; j++) + SR(OVL_FIR_COEF_V2(i, j)); + } + if (dss_has_feature(FEAT_ATTR2)) + SR(OVL_ATTRIBUTES2(i)); + } + + if (dss_has_feature(FEAT_CORE_CLK_DIV)) + SR(DIVISOR); + + dispc.ctx_valid = true; + + DSSDBG("context saved\n"); +} + +static void dispc_restore_context(void) +{ + int i, j; + + DSSDBG("dispc_restore_context\n"); + + if (!dispc.ctx_valid) + return; + + /*RR(IRQENABLE);*/ + /*RR(CONTROL);*/ + RR(CONFIG); + RR(LINE_NUMBER); + if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER) || + dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) + RR(GLOBAL_ALPHA); + if (dss_has_feature(FEAT_MGR_LCD2)) + RR(CONFIG2); + if (dss_has_feature(FEAT_MGR_LCD3)) + RR(CONFIG3); + + for (i = 0; i < dss_feat_get_num_mgrs(); i++) { + RR(DEFAULT_COLOR(i)); + RR(TRANS_COLOR(i)); + RR(SIZE_MGR(i)); + if (i == OMAP_DSS_CHANNEL_DIGIT) + continue; + RR(TIMING_H(i)); + RR(TIMING_V(i)); + RR(POL_FREQ(i)); + RR(DIVISORo(i)); + + RR(DATA_CYCLE1(i)); + RR(DATA_CYCLE2(i)); + RR(DATA_CYCLE3(i)); + + if (dss_has_feature(FEAT_CPR)) { + RR(CPR_COEF_R(i)); + RR(CPR_COEF_G(i)); + RR(CPR_COEF_B(i)); + } + } + + for (i = 0; i < dss_feat_get_num_ovls(); i++) { + RR(OVL_BA0(i)); + RR(OVL_BA1(i)); + RR(OVL_POSITION(i)); + RR(OVL_SIZE(i)); + RR(OVL_ATTRIBUTES(i)); + RR(OVL_FIFO_THRESHOLD(i)); + RR(OVL_ROW_INC(i)); + RR(OVL_PIXEL_INC(i)); + if (dss_has_feature(FEAT_PRELOAD)) + RR(OVL_PRELOAD(i)); + if (i == OMAP_DSS_GFX) { + RR(OVL_WINDOW_SKIP(i)); + RR(OVL_TABLE_BA(i)); + continue; + } + RR(OVL_FIR(i)); + RR(OVL_PICTURE_SIZE(i)); + RR(OVL_ACCU0(i)); + RR(OVL_ACCU1(i)); + + for (j = 0; j < 8; j++) + RR(OVL_FIR_COEF_H(i, j)); + + for (j = 0; j < 8; j++) + RR(OVL_FIR_COEF_HV(i, j)); + + for (j = 0; j < 5; j++) + RR(OVL_CONV_COEF(i, j)); + + if (dss_has_feature(FEAT_FIR_COEF_V)) { + for (j = 0; j < 8; j++) + RR(OVL_FIR_COEF_V(i, j)); + } + + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + RR(OVL_BA0_UV(i)); + RR(OVL_BA1_UV(i)); + RR(OVL_FIR2(i)); + RR(OVL_ACCU2_0(i)); + RR(OVL_ACCU2_1(i)); + + for (j = 0; j < 8; j++) + RR(OVL_FIR_COEF_H2(i, j)); + + for (j = 0; j < 8; j++) + RR(OVL_FIR_COEF_HV2(i, j)); + + for (j = 0; j < 8; j++) + RR(OVL_FIR_COEF_V2(i, j)); + } + if (dss_has_feature(FEAT_ATTR2)) + RR(OVL_ATTRIBUTES2(i)); + } + + if (dss_has_feature(FEAT_CORE_CLK_DIV)) + RR(DIVISOR); + + /* enable last, because LCD & DIGIT enable are here */ + RR(CONTROL); + if (dss_has_feature(FEAT_MGR_LCD2)) + RR(CONTROL2); + if (dss_has_feature(FEAT_MGR_LCD3)) + RR(CONTROL3); + /* clear spurious SYNC_LOST_DIGIT interrupts */ + dispc_clear_irqstatus(DISPC_IRQ_SYNC_LOST_DIGIT); + + /* + * enable last so IRQs won't trigger before + * the context is fully restored + */ + RR(IRQENABLE); + + DSSDBG("context restored\n"); +} + +#undef SR +#undef RR + +int dispc_runtime_get(void) +{ + int r; + + DSSDBG("dispc_runtime_get\n"); + + r = pm_runtime_get_sync(&dispc.pdev->dev); + WARN_ON(r < 0); + return r < 0 ? r : 0; +} +EXPORT_SYMBOL(dispc_runtime_get); + +void dispc_runtime_put(void) +{ + int r; + + DSSDBG("dispc_runtime_put\n"); + + r = pm_runtime_put_sync(&dispc.pdev->dev); + WARN_ON(r < 0 && r != -ENOSYS); +} +EXPORT_SYMBOL(dispc_runtime_put); + +u32 dispc_mgr_get_vsync_irq(enum omap_channel channel) +{ + return mgr_desc[channel].vsync_irq; +} +EXPORT_SYMBOL(dispc_mgr_get_vsync_irq); + +u32 dispc_mgr_get_framedone_irq(enum omap_channel channel) +{ + if (channel == OMAP_DSS_CHANNEL_DIGIT && dispc.feat->no_framedone_tv) + return 0; + + return mgr_desc[channel].framedone_irq; +} +EXPORT_SYMBOL(dispc_mgr_get_framedone_irq); + +u32 dispc_mgr_get_sync_lost_irq(enum omap_channel channel) +{ + return mgr_desc[channel].sync_lost_irq; +} +EXPORT_SYMBOL(dispc_mgr_get_sync_lost_irq); + +u32 dispc_wb_get_framedone_irq(void) +{ + return DISPC_IRQ_FRAMEDONEWB; +} + +bool dispc_mgr_go_busy(enum omap_channel channel) +{ + return mgr_fld_read(channel, DISPC_MGR_FLD_GO) == 1; +} +EXPORT_SYMBOL(dispc_mgr_go_busy); + +void dispc_mgr_go(enum omap_channel channel) +{ + WARN_ON(dispc_mgr_is_enabled(channel) == false); + WARN_ON(dispc_mgr_go_busy(channel)); + + DSSDBG("GO %s\n", mgr_desc[channel].name); + + mgr_fld_write(channel, DISPC_MGR_FLD_GO, 1); +} +EXPORT_SYMBOL(dispc_mgr_go); + +bool dispc_wb_go_busy(void) +{ + return REG_GET(DISPC_CONTROL2, 6, 6) == 1; +} + +void dispc_wb_go(void) +{ + enum omap_plane plane = OMAP_DSS_WB; + bool enable, go; + + enable = REG_GET(DISPC_OVL_ATTRIBUTES(plane), 0, 0) == 1; + + if (!enable) + return; + + go = REG_GET(DISPC_CONTROL2, 6, 6) == 1; + if (go) { + DSSERR("GO bit not down for WB\n"); + return; + } + + REG_FLD_MOD(DISPC_CONTROL2, 1, 6, 6); +} + +static void dispc_ovl_write_firh_reg(enum omap_plane plane, int reg, u32 value) +{ + dispc_write_reg(DISPC_OVL_FIR_COEF_H(plane, reg), value); +} + +static void dispc_ovl_write_firhv_reg(enum omap_plane plane, int reg, u32 value) +{ + dispc_write_reg(DISPC_OVL_FIR_COEF_HV(plane, reg), value); +} + +static void dispc_ovl_write_firv_reg(enum omap_plane plane, int reg, u32 value) +{ + dispc_write_reg(DISPC_OVL_FIR_COEF_V(plane, reg), value); +} + +static void dispc_ovl_write_firh2_reg(enum omap_plane plane, int reg, u32 value) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_write_reg(DISPC_OVL_FIR_COEF_H2(plane, reg), value); +} + +static void dispc_ovl_write_firhv2_reg(enum omap_plane plane, int reg, + u32 value) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_write_reg(DISPC_OVL_FIR_COEF_HV2(plane, reg), value); +} + +static void dispc_ovl_write_firv2_reg(enum omap_plane plane, int reg, u32 value) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_write_reg(DISPC_OVL_FIR_COEF_V2(plane, reg), value); +} + +static void dispc_ovl_set_scale_coef(enum omap_plane plane, int fir_hinc, + int fir_vinc, int five_taps, + enum omap_color_component color_comp) +{ + const struct dispc_coef *h_coef, *v_coef; + int i; + + h_coef = dispc_ovl_get_scale_coef(fir_hinc, true); + v_coef = dispc_ovl_get_scale_coef(fir_vinc, five_taps); + + for (i = 0; i < 8; i++) { + u32 h, hv; + + h = FLD_VAL(h_coef[i].hc0_vc00, 7, 0) + | FLD_VAL(h_coef[i].hc1_vc0, 15, 8) + | FLD_VAL(h_coef[i].hc2_vc1, 23, 16) + | FLD_VAL(h_coef[i].hc3_vc2, 31, 24); + hv = FLD_VAL(h_coef[i].hc4_vc22, 7, 0) + | FLD_VAL(v_coef[i].hc1_vc0, 15, 8) + | FLD_VAL(v_coef[i].hc2_vc1, 23, 16) + | FLD_VAL(v_coef[i].hc3_vc2, 31, 24); + + if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) { + dispc_ovl_write_firh_reg(plane, i, h); + dispc_ovl_write_firhv_reg(plane, i, hv); + } else { + dispc_ovl_write_firh2_reg(plane, i, h); + dispc_ovl_write_firhv2_reg(plane, i, hv); + } + + } + + if (five_taps) { + for (i = 0; i < 8; i++) { + u32 v; + v = FLD_VAL(v_coef[i].hc0_vc00, 7, 0) + | FLD_VAL(v_coef[i].hc4_vc22, 15, 8); + if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) + dispc_ovl_write_firv_reg(plane, i, v); + else + dispc_ovl_write_firv2_reg(plane, i, v); + } + } +} + + +static void dispc_ovl_write_color_conv_coef(enum omap_plane plane, + const struct color_conv_coef *ct) +{ +#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0)) + + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 0), CVAL(ct->rcr, ct->ry)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 1), CVAL(ct->gy, ct->rcb)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 2), CVAL(ct->gcb, ct->gcr)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 3), CVAL(ct->bcr, ct->by)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 4), CVAL(0, ct->bcb)); + + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), ct->full_range, 11, 11); + +#undef CVAL +} + +static void dispc_setup_color_conv_coef(void) +{ + int i; + int num_ovl = dss_feat_get_num_ovls(); + int num_wb = dss_feat_get_num_wbs(); + const struct color_conv_coef ctbl_bt601_5_ovl = { + 298, 409, 0, 298, -208, -100, 298, 0, 517, 0, + }; + const struct color_conv_coef ctbl_bt601_5_wb = { + 66, 112, -38, 129, -94, -74, 25, -18, 112, 0, + }; + + for (i = 1; i < num_ovl; i++) + dispc_ovl_write_color_conv_coef(i, &ctbl_bt601_5_ovl); + + for (; i < num_wb; i++) + dispc_ovl_write_color_conv_coef(i, &ctbl_bt601_5_wb); +} + +static void dispc_ovl_set_ba0(enum omap_plane plane, u32 paddr) +{ + dispc_write_reg(DISPC_OVL_BA0(plane), paddr); +} + +static void dispc_ovl_set_ba1(enum omap_plane plane, u32 paddr) +{ + dispc_write_reg(DISPC_OVL_BA1(plane), paddr); +} + +static void dispc_ovl_set_ba0_uv(enum omap_plane plane, u32 paddr) +{ + dispc_write_reg(DISPC_OVL_BA0_UV(plane), paddr); +} + +static void dispc_ovl_set_ba1_uv(enum omap_plane plane, u32 paddr) +{ + dispc_write_reg(DISPC_OVL_BA1_UV(plane), paddr); +} + +static void dispc_ovl_set_pos(enum omap_plane plane, + enum omap_overlay_caps caps, int x, int y) +{ + u32 val; + + if ((caps & OMAP_DSS_OVL_CAP_POS) == 0) + return; + + val = FLD_VAL(y, 26, 16) | FLD_VAL(x, 10, 0); + + dispc_write_reg(DISPC_OVL_POSITION(plane), val); +} + +static void dispc_ovl_set_input_size(enum omap_plane plane, int width, + int height) +{ + u32 val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); + + if (plane == OMAP_DSS_GFX || plane == OMAP_DSS_WB) + dispc_write_reg(DISPC_OVL_SIZE(plane), val); + else + dispc_write_reg(DISPC_OVL_PICTURE_SIZE(plane), val); +} + +static void dispc_ovl_set_output_size(enum omap_plane plane, int width, + int height) +{ + u32 val; + + BUG_ON(plane == OMAP_DSS_GFX); + + val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); + + if (plane == OMAP_DSS_WB) + dispc_write_reg(DISPC_OVL_PICTURE_SIZE(plane), val); + else + dispc_write_reg(DISPC_OVL_SIZE(plane), val); +} + +static void dispc_ovl_set_zorder(enum omap_plane plane, + enum omap_overlay_caps caps, u8 zorder) +{ + if ((caps & OMAP_DSS_OVL_CAP_ZORDER) == 0) + return; + + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), zorder, 27, 26); +} + +static void dispc_ovl_enable_zorder_planes(void) +{ + int i; + + if (!dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) + return; + + for (i = 0; i < dss_feat_get_num_ovls(); i++) + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(i), 1, 25, 25); +} + +static void dispc_ovl_set_pre_mult_alpha(enum omap_plane plane, + enum omap_overlay_caps caps, bool enable) +{ + if ((caps & OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA) == 0) + return; + + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), enable ? 1 : 0, 28, 28); +} + +static void dispc_ovl_setup_global_alpha(enum omap_plane plane, + enum omap_overlay_caps caps, u8 global_alpha) +{ + static const unsigned shifts[] = { 0, 8, 16, 24, }; + int shift; + + if ((caps & OMAP_DSS_OVL_CAP_GLOBAL_ALPHA) == 0) + return; + + shift = shifts[plane]; + REG_FLD_MOD(DISPC_GLOBAL_ALPHA, global_alpha, shift + 7, shift); +} + +static void dispc_ovl_set_pix_inc(enum omap_plane plane, s32 inc) +{ + dispc_write_reg(DISPC_OVL_PIXEL_INC(plane), inc); +} + +static void dispc_ovl_set_row_inc(enum omap_plane plane, s32 inc) +{ + dispc_write_reg(DISPC_OVL_ROW_INC(plane), inc); +} + +static void dispc_ovl_set_color_mode(enum omap_plane plane, + enum omap_color_mode color_mode) +{ + u32 m = 0; + if (plane != OMAP_DSS_GFX) { + switch (color_mode) { + case OMAP_DSS_COLOR_NV12: + m = 0x0; break; + case OMAP_DSS_COLOR_RGBX16: + m = 0x1; break; + case OMAP_DSS_COLOR_RGBA16: + m = 0x2; break; + case OMAP_DSS_COLOR_RGB12U: + m = 0x4; break; + case OMAP_DSS_COLOR_ARGB16: + m = 0x5; break; + case OMAP_DSS_COLOR_RGB16: + m = 0x6; break; + case OMAP_DSS_COLOR_ARGB16_1555: + m = 0x7; break; + case OMAP_DSS_COLOR_RGB24U: + m = 0x8; break; + case OMAP_DSS_COLOR_RGB24P: + m = 0x9; break; + case OMAP_DSS_COLOR_YUV2: + m = 0xa; break; + case OMAP_DSS_COLOR_UYVY: + m = 0xb; break; + case OMAP_DSS_COLOR_ARGB32: + m = 0xc; break; + case OMAP_DSS_COLOR_RGBA32: + m = 0xd; break; + case OMAP_DSS_COLOR_RGBX32: + m = 0xe; break; + case OMAP_DSS_COLOR_XRGB16_1555: + m = 0xf; break; + default: + BUG(); return; + } + } else { + switch (color_mode) { + case OMAP_DSS_COLOR_CLUT1: + m = 0x0; break; + case OMAP_DSS_COLOR_CLUT2: + m = 0x1; break; + case OMAP_DSS_COLOR_CLUT4: + m = 0x2; break; + case OMAP_DSS_COLOR_CLUT8: + m = 0x3; break; + case OMAP_DSS_COLOR_RGB12U: + m = 0x4; break; + case OMAP_DSS_COLOR_ARGB16: + m = 0x5; break; + case OMAP_DSS_COLOR_RGB16: + m = 0x6; break; + case OMAP_DSS_COLOR_ARGB16_1555: + m = 0x7; break; + case OMAP_DSS_COLOR_RGB24U: + m = 0x8; break; + case OMAP_DSS_COLOR_RGB24P: + m = 0x9; break; + case OMAP_DSS_COLOR_RGBX16: + m = 0xa; break; + case OMAP_DSS_COLOR_RGBA16: + m = 0xb; break; + case OMAP_DSS_COLOR_ARGB32: + m = 0xc; break; + case OMAP_DSS_COLOR_RGBA32: + m = 0xd; break; + case OMAP_DSS_COLOR_RGBX32: + m = 0xe; break; + case OMAP_DSS_COLOR_XRGB16_1555: + m = 0xf; break; + default: + BUG(); return; + } + } + + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), m, 4, 1); +} + +static void dispc_ovl_configure_burst_type(enum omap_plane plane, + enum omap_dss_rotation_type rotation_type) +{ + if (dss_has_feature(FEAT_BURST_2D) == 0) + return; + + if (rotation_type == OMAP_DSS_ROT_TILER) + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), 1, 29, 29); + else + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), 0, 29, 29); +} + +void dispc_ovl_set_channel_out(enum omap_plane plane, enum omap_channel channel) +{ + int shift; + u32 val; + int chan = 0, chan2 = 0; + + switch (plane) { + case OMAP_DSS_GFX: + shift = 8; + break; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + shift = 16; + break; + default: + BUG(); + return; + } + + val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + if (dss_has_feature(FEAT_MGR_LCD2)) { + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + chan = 0; + chan2 = 0; + break; + case OMAP_DSS_CHANNEL_DIGIT: + chan = 1; + chan2 = 0; + break; + case OMAP_DSS_CHANNEL_LCD2: + chan = 0; + chan2 = 1; + break; + case OMAP_DSS_CHANNEL_LCD3: + if (dss_has_feature(FEAT_MGR_LCD3)) { + chan = 0; + chan2 = 2; + } else { + BUG(); + return; + } + break; + default: + BUG(); + return; + } + + val = FLD_MOD(val, chan, shift, shift); + val = FLD_MOD(val, chan2, 31, 30); + } else { + val = FLD_MOD(val, channel, shift, shift); + } + dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), val); +} +EXPORT_SYMBOL(dispc_ovl_set_channel_out); + +static enum omap_channel dispc_ovl_get_channel_out(enum omap_plane plane) +{ + int shift; + u32 val; + enum omap_channel channel; + + switch (plane) { + case OMAP_DSS_GFX: + shift = 8; + break; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + shift = 16; + break; + default: + BUG(); + return 0; + } + + val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + + if (dss_has_feature(FEAT_MGR_LCD3)) { + if (FLD_GET(val, 31, 30) == 0) + channel = FLD_GET(val, shift, shift); + else if (FLD_GET(val, 31, 30) == 1) + channel = OMAP_DSS_CHANNEL_LCD2; + else + channel = OMAP_DSS_CHANNEL_LCD3; + } else if (dss_has_feature(FEAT_MGR_LCD2)) { + if (FLD_GET(val, 31, 30) == 0) + channel = FLD_GET(val, shift, shift); + else + channel = OMAP_DSS_CHANNEL_LCD2; + } else { + channel = FLD_GET(val, shift, shift); + } + + return channel; +} + +void dispc_wb_set_channel_in(enum dss_writeback_channel channel) +{ + enum omap_plane plane = OMAP_DSS_WB; + + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), channel, 18, 16); +} + +static void dispc_ovl_set_burst_size(enum omap_plane plane, + enum omap_burst_size burst_size) +{ + static const unsigned shifts[] = { 6, 14, 14, 14, 14, }; + int shift; + + shift = shifts[plane]; + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), burst_size, shift + 1, shift); +} + +static void dispc_configure_burst_sizes(void) +{ + int i; + const int burst_size = BURST_SIZE_X8; + + /* Configure burst size always to maximum size */ + for (i = 0; i < dss_feat_get_num_ovls(); ++i) + dispc_ovl_set_burst_size(i, burst_size); +} + +static u32 dispc_ovl_get_burst_size(enum omap_plane plane) +{ + unsigned unit = dss_feat_get_burst_size_unit(); + /* burst multiplier is always x8 (see dispc_configure_burst_sizes()) */ + return unit * 8; +} + +void dispc_enable_gamma_table(bool enable) +{ + /* + * This is partially implemented to support only disabling of + * the gamma table. + */ + if (enable) { + DSSWARN("Gamma table enabling for TV not yet supported"); + return; + } + + REG_FLD_MOD(DISPC_CONFIG, enable, 9, 9); +} + +static void dispc_mgr_enable_cpr(enum omap_channel channel, bool enable) +{ + if (channel == OMAP_DSS_CHANNEL_DIGIT) + return; + + mgr_fld_write(channel, DISPC_MGR_FLD_CPR, enable); +} + +static void dispc_mgr_set_cpr_coef(enum omap_channel channel, + const struct omap_dss_cpr_coefs *coefs) +{ + u32 coef_r, coef_g, coef_b; + + if (!dss_mgr_is_lcd(channel)) + return; + + coef_r = FLD_VAL(coefs->rr, 31, 22) | FLD_VAL(coefs->rg, 20, 11) | + FLD_VAL(coefs->rb, 9, 0); + coef_g = FLD_VAL(coefs->gr, 31, 22) | FLD_VAL(coefs->gg, 20, 11) | + FLD_VAL(coefs->gb, 9, 0); + coef_b = FLD_VAL(coefs->br, 31, 22) | FLD_VAL(coefs->bg, 20, 11) | + FLD_VAL(coefs->bb, 9, 0); + + dispc_write_reg(DISPC_CPR_COEF_R(channel), coef_r); + dispc_write_reg(DISPC_CPR_COEF_G(channel), coef_g); + dispc_write_reg(DISPC_CPR_COEF_B(channel), coef_b); +} + +static void dispc_ovl_set_vid_color_conv(enum omap_plane plane, bool enable) +{ + u32 val; + + BUG_ON(plane == OMAP_DSS_GFX); + + val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + val = FLD_MOD(val, enable, 9, 9); + dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), val); +} + +static void dispc_ovl_enable_replication(enum omap_plane plane, + enum omap_overlay_caps caps, bool enable) +{ + static const unsigned shifts[] = { 5, 10, 10, 10 }; + int shift; + + if ((caps & OMAP_DSS_OVL_CAP_REPLICATION) == 0) + return; + + shift = shifts[plane]; + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), enable, shift, shift); +} + +static void dispc_mgr_set_size(enum omap_channel channel, u16 width, + u16 height) +{ + u32 val; + + val = FLD_VAL(height - 1, dispc.feat->mgr_height_start, 16) | + FLD_VAL(width - 1, dispc.feat->mgr_width_start, 0); + + dispc_write_reg(DISPC_SIZE_MGR(channel), val); +} + +static void dispc_init_fifos(void) +{ + u32 size; + int fifo; + u8 start, end; + u32 unit; + + unit = dss_feat_get_buffer_size_unit(); + + dss_feat_get_reg_field(FEAT_REG_FIFOSIZE, &start, &end); + + for (fifo = 0; fifo < dispc.feat->num_fifos; ++fifo) { + size = REG_GET(DISPC_OVL_FIFO_SIZE_STATUS(fifo), start, end); + size *= unit; + dispc.fifo_size[fifo] = size; + + /* + * By default fifos are mapped directly to overlays, fifo 0 to + * ovl 0, fifo 1 to ovl 1, etc. + */ + dispc.fifo_assignment[fifo] = fifo; + } + + /* + * The GFX fifo on OMAP4 is smaller than the other fifos. The small fifo + * causes problems with certain use cases, like using the tiler in 2D + * mode. The below hack swaps the fifos of GFX and WB planes, thus + * giving GFX plane a larger fifo. WB but should work fine with a + * smaller fifo. + */ + if (dispc.feat->gfx_fifo_workaround) { + u32 v; + + v = dispc_read_reg(DISPC_GLOBAL_BUFFER); + + v = FLD_MOD(v, 4, 2, 0); /* GFX BUF top to WB */ + v = FLD_MOD(v, 4, 5, 3); /* GFX BUF bottom to WB */ + v = FLD_MOD(v, 0, 26, 24); /* WB BUF top to GFX */ + v = FLD_MOD(v, 0, 29, 27); /* WB BUF bottom to GFX */ + + dispc_write_reg(DISPC_GLOBAL_BUFFER, v); + + dispc.fifo_assignment[OMAP_DSS_GFX] = OMAP_DSS_WB; + dispc.fifo_assignment[OMAP_DSS_WB] = OMAP_DSS_GFX; + } +} + +static u32 dispc_ovl_get_fifo_size(enum omap_plane plane) +{ + int fifo; + u32 size = 0; + + for (fifo = 0; fifo < dispc.feat->num_fifos; ++fifo) { + if (dispc.fifo_assignment[fifo] == plane) + size += dispc.fifo_size[fifo]; + } + + return size; +} + +void dispc_ovl_set_fifo_threshold(enum omap_plane plane, u32 low, u32 high) +{ + u8 hi_start, hi_end, lo_start, lo_end; + u32 unit; + + unit = dss_feat_get_buffer_size_unit(); + + WARN_ON(low % unit != 0); + WARN_ON(high % unit != 0); + + low /= unit; + high /= unit; + + dss_feat_get_reg_field(FEAT_REG_FIFOHIGHTHRESHOLD, &hi_start, &hi_end); + dss_feat_get_reg_field(FEAT_REG_FIFOLOWTHRESHOLD, &lo_start, &lo_end); + + DSSDBG("fifo(%d) threshold (bytes), old %u/%u, new %u/%u\n", + plane, + REG_GET(DISPC_OVL_FIFO_THRESHOLD(plane), + lo_start, lo_end) * unit, + REG_GET(DISPC_OVL_FIFO_THRESHOLD(plane), + hi_start, hi_end) * unit, + low * unit, high * unit); + + dispc_write_reg(DISPC_OVL_FIFO_THRESHOLD(plane), + FLD_VAL(high, hi_start, hi_end) | + FLD_VAL(low, lo_start, lo_end)); + + /* + * configure the preload to the pipeline's high threhold, if HT it's too + * large for the preload field, set the threshold to the maximum value + * that can be held by the preload register + */ + if (dss_has_feature(FEAT_PRELOAD) && dispc.feat->set_max_preload && + plane != OMAP_DSS_WB) + dispc_write_reg(DISPC_OVL_PRELOAD(plane), min(high, 0xfffu)); +} +EXPORT_SYMBOL(dispc_ovl_set_fifo_threshold); + +void dispc_enable_fifomerge(bool enable) +{ + if (!dss_has_feature(FEAT_FIFO_MERGE)) { + WARN_ON(enable); + return; + } + + DSSDBG("FIFO merge %s\n", enable ? "enabled" : "disabled"); + REG_FLD_MOD(DISPC_CONFIG, enable ? 1 : 0, 14, 14); +} + +void dispc_ovl_compute_fifo_thresholds(enum omap_plane plane, + u32 *fifo_low, u32 *fifo_high, bool use_fifomerge, + bool manual_update) +{ + /* + * All sizes are in bytes. Both the buffer and burst are made of + * buffer_units, and the fifo thresholds must be buffer_unit aligned. + */ + + unsigned buf_unit = dss_feat_get_buffer_size_unit(); + unsigned ovl_fifo_size, total_fifo_size, burst_size; + int i; + + burst_size = dispc_ovl_get_burst_size(plane); + ovl_fifo_size = dispc_ovl_get_fifo_size(plane); + + if (use_fifomerge) { + total_fifo_size = 0; + for (i = 0; i < dss_feat_get_num_ovls(); ++i) + total_fifo_size += dispc_ovl_get_fifo_size(i); + } else { + total_fifo_size = ovl_fifo_size; + } + + /* + * We use the same low threshold for both fifomerge and non-fifomerge + * cases, but for fifomerge we calculate the high threshold using the + * combined fifo size + */ + + if (manual_update && dss_has_feature(FEAT_OMAP3_DSI_FIFO_BUG)) { + *fifo_low = ovl_fifo_size - burst_size * 2; + *fifo_high = total_fifo_size - burst_size; + } else if (plane == OMAP_DSS_WB) { + /* + * Most optimal configuration for writeback is to push out data + * to the interconnect the moment writeback pushes enough pixels + * in the FIFO to form a burst + */ + *fifo_low = 0; + *fifo_high = burst_size; + } else { + *fifo_low = ovl_fifo_size - burst_size; + *fifo_high = total_fifo_size - buf_unit; + } +} +EXPORT_SYMBOL(dispc_ovl_compute_fifo_thresholds); + +static void dispc_ovl_set_fir(enum omap_plane plane, + int hinc, int vinc, + enum omap_color_component color_comp) +{ + u32 val; + + if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) { + u8 hinc_start, hinc_end, vinc_start, vinc_end; + + dss_feat_get_reg_field(FEAT_REG_FIRHINC, + &hinc_start, &hinc_end); + dss_feat_get_reg_field(FEAT_REG_FIRVINC, + &vinc_start, &vinc_end); + val = FLD_VAL(vinc, vinc_start, vinc_end) | + FLD_VAL(hinc, hinc_start, hinc_end); + + dispc_write_reg(DISPC_OVL_FIR(plane), val); + } else { + val = FLD_VAL(vinc, 28, 16) | FLD_VAL(hinc, 12, 0); + dispc_write_reg(DISPC_OVL_FIR2(plane), val); + } +} + +static void dispc_ovl_set_vid_accu0(enum omap_plane plane, int haccu, int vaccu) +{ + u32 val; + u8 hor_start, hor_end, vert_start, vert_end; + + dss_feat_get_reg_field(FEAT_REG_HORIZONTALACCU, &hor_start, &hor_end); + dss_feat_get_reg_field(FEAT_REG_VERTICALACCU, &vert_start, &vert_end); + + val = FLD_VAL(vaccu, vert_start, vert_end) | + FLD_VAL(haccu, hor_start, hor_end); + + dispc_write_reg(DISPC_OVL_ACCU0(plane), val); +} + +static void dispc_ovl_set_vid_accu1(enum omap_plane plane, int haccu, int vaccu) +{ + u32 val; + u8 hor_start, hor_end, vert_start, vert_end; + + dss_feat_get_reg_field(FEAT_REG_HORIZONTALACCU, &hor_start, &hor_end); + dss_feat_get_reg_field(FEAT_REG_VERTICALACCU, &vert_start, &vert_end); + + val = FLD_VAL(vaccu, vert_start, vert_end) | + FLD_VAL(haccu, hor_start, hor_end); + + dispc_write_reg(DISPC_OVL_ACCU1(plane), val); +} + +static void dispc_ovl_set_vid_accu2_0(enum omap_plane plane, int haccu, + int vaccu) +{ + u32 val; + + val = FLD_VAL(vaccu, 26, 16) | FLD_VAL(haccu, 10, 0); + dispc_write_reg(DISPC_OVL_ACCU2_0(plane), val); +} + +static void dispc_ovl_set_vid_accu2_1(enum omap_plane plane, int haccu, + int vaccu) +{ + u32 val; + + val = FLD_VAL(vaccu, 26, 16) | FLD_VAL(haccu, 10, 0); + dispc_write_reg(DISPC_OVL_ACCU2_1(plane), val); +} + +static void dispc_ovl_set_scale_param(enum omap_plane plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool five_taps, u8 rotation, + enum omap_color_component color_comp) +{ + int fir_hinc, fir_vinc; + + fir_hinc = 1024 * orig_width / out_width; + fir_vinc = 1024 * orig_height / out_height; + + dispc_ovl_set_scale_coef(plane, fir_hinc, fir_vinc, five_taps, + color_comp); + dispc_ovl_set_fir(plane, fir_hinc, fir_vinc, color_comp); +} + +static void dispc_ovl_set_accu_uv(enum omap_plane plane, + u16 orig_width, u16 orig_height, u16 out_width, u16 out_height, + bool ilace, enum omap_color_mode color_mode, u8 rotation) +{ + int h_accu2_0, h_accu2_1; + int v_accu2_0, v_accu2_1; + int chroma_hinc, chroma_vinc; + int idx; + + struct accu { + s8 h0_m, h0_n; + s8 h1_m, h1_n; + s8 v0_m, v0_n; + s8 v1_m, v1_n; + }; + + const struct accu *accu_table; + const struct accu *accu_val; + + static const struct accu accu_nv12[4] = { + { 0, 1, 0, 1 , -1, 2, 0, 1 }, + { 1, 2, -3, 4 , 0, 1, 0, 1 }, + { -1, 1, 0, 1 , -1, 2, 0, 1 }, + { -1, 2, -1, 2 , -1, 1, 0, 1 }, + }; + + static const struct accu accu_nv12_ilace[4] = { + { 0, 1, 0, 1 , -3, 4, -1, 4 }, + { -1, 4, -3, 4 , 0, 1, 0, 1 }, + { -1, 1, 0, 1 , -1, 4, -3, 4 }, + { -3, 4, -3, 4 , -1, 1, 0, 1 }, + }; + + static const struct accu accu_yuv[4] = { + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { -1, 1, 0, 1, 0, 1, 0, 1 }, + { 0, 1, 0, 1, -1, 1, 0, 1 }, + }; + + switch (rotation) { + case OMAP_DSS_ROT_0: + idx = 0; + break; + case OMAP_DSS_ROT_90: + idx = 1; + break; + case OMAP_DSS_ROT_180: + idx = 2; + break; + case OMAP_DSS_ROT_270: + idx = 3; + break; + default: + BUG(); + return; + } + + switch (color_mode) { + case OMAP_DSS_COLOR_NV12: + if (ilace) + accu_table = accu_nv12_ilace; + else + accu_table = accu_nv12; + break; + case OMAP_DSS_COLOR_YUV2: + case OMAP_DSS_COLOR_UYVY: + accu_table = accu_yuv; + break; + default: + BUG(); + return; + } + + accu_val = &accu_table[idx]; + + chroma_hinc = 1024 * orig_width / out_width; + chroma_vinc = 1024 * orig_height / out_height; + + h_accu2_0 = (accu_val->h0_m * chroma_hinc / accu_val->h0_n) % 1024; + h_accu2_1 = (accu_val->h1_m * chroma_hinc / accu_val->h1_n) % 1024; + v_accu2_0 = (accu_val->v0_m * chroma_vinc / accu_val->v0_n) % 1024; + v_accu2_1 = (accu_val->v1_m * chroma_vinc / accu_val->v1_n) % 1024; + + dispc_ovl_set_vid_accu2_0(plane, h_accu2_0, v_accu2_0); + dispc_ovl_set_vid_accu2_1(plane, h_accu2_1, v_accu2_1); +} + +static void dispc_ovl_set_scaling_common(enum omap_plane plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool ilace, bool five_taps, + bool fieldmode, enum omap_color_mode color_mode, + u8 rotation) +{ + int accu0 = 0; + int accu1 = 0; + u32 l; + + dispc_ovl_set_scale_param(plane, orig_width, orig_height, + out_width, out_height, five_taps, + rotation, DISPC_COLOR_COMPONENT_RGB_Y); + l = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + + /* RESIZEENABLE and VERTICALTAPS */ + l &= ~((0x3 << 5) | (0x1 << 21)); + l |= (orig_width != out_width) ? (1 << 5) : 0; + l |= (orig_height != out_height) ? (1 << 6) : 0; + l |= five_taps ? (1 << 21) : 0; + + /* VRESIZECONF and HRESIZECONF */ + if (dss_has_feature(FEAT_RESIZECONF)) { + l &= ~(0x3 << 7); + l |= (orig_width <= out_width) ? 0 : (1 << 7); + l |= (orig_height <= out_height) ? 0 : (1 << 8); + } + + /* LINEBUFFERSPLIT */ + if (dss_has_feature(FEAT_LINEBUFFERSPLIT)) { + l &= ~(0x1 << 22); + l |= five_taps ? (1 << 22) : 0; + } + + dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), l); + + /* + * field 0 = even field = bottom field + * field 1 = odd field = top field + */ + if (ilace && !fieldmode) { + accu1 = 0; + accu0 = ((1024 * orig_height / out_height) / 2) & 0x3ff; + if (accu0 >= 1024/2) { + accu1 = 1024/2; + accu0 -= accu1; + } + } + + dispc_ovl_set_vid_accu0(plane, 0, accu0); + dispc_ovl_set_vid_accu1(plane, 0, accu1); +} + +static void dispc_ovl_set_scaling_uv(enum omap_plane plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool ilace, bool five_taps, + bool fieldmode, enum omap_color_mode color_mode, + u8 rotation) +{ + int scale_x = out_width != orig_width; + int scale_y = out_height != orig_height; + bool chroma_upscale = plane != OMAP_DSS_WB ? true : false; + + if (!dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) + return; + if ((color_mode != OMAP_DSS_COLOR_YUV2 && + color_mode != OMAP_DSS_COLOR_UYVY && + color_mode != OMAP_DSS_COLOR_NV12)) { + /* reset chroma resampling for RGB formats */ + if (plane != OMAP_DSS_WB) + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES2(plane), 0, 8, 8); + return; + } + + dispc_ovl_set_accu_uv(plane, orig_width, orig_height, out_width, + out_height, ilace, color_mode, rotation); + + switch (color_mode) { + case OMAP_DSS_COLOR_NV12: + if (chroma_upscale) { + /* UV is subsampled by 2 horizontally and vertically */ + orig_height >>= 1; + orig_width >>= 1; + } else { + /* UV is downsampled by 2 horizontally and vertically */ + orig_height <<= 1; + orig_width <<= 1; + } + + break; + case OMAP_DSS_COLOR_YUV2: + case OMAP_DSS_COLOR_UYVY: + /* For YUV422 with 90/270 rotation, we don't upsample chroma */ + if (rotation == OMAP_DSS_ROT_0 || + rotation == OMAP_DSS_ROT_180) { + if (chroma_upscale) + /* UV is subsampled by 2 horizontally */ + orig_width >>= 1; + else + /* UV is downsampled by 2 horizontally */ + orig_width <<= 1; + } + + /* must use FIR for YUV422 if rotated */ + if (rotation != OMAP_DSS_ROT_0) + scale_x = scale_y = true; + + break; + default: + BUG(); + return; + } + + if (out_width != orig_width) + scale_x = true; + if (out_height != orig_height) + scale_y = true; + + dispc_ovl_set_scale_param(plane, orig_width, orig_height, + out_width, out_height, five_taps, + rotation, DISPC_COLOR_COMPONENT_UV); + + if (plane != OMAP_DSS_WB) + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES2(plane), + (scale_x || scale_y) ? 1 : 0, 8, 8); + + /* set H scaling */ + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), scale_x ? 1 : 0, 5, 5); + /* set V scaling */ + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), scale_y ? 1 : 0, 6, 6); +} + +static void dispc_ovl_set_scaling(enum omap_plane plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool ilace, bool five_taps, + bool fieldmode, enum omap_color_mode color_mode, + u8 rotation) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_ovl_set_scaling_common(plane, + orig_width, orig_height, + out_width, out_height, + ilace, five_taps, + fieldmode, color_mode, + rotation); + + dispc_ovl_set_scaling_uv(plane, + orig_width, orig_height, + out_width, out_height, + ilace, five_taps, + fieldmode, color_mode, + rotation); +} + +static void dispc_ovl_set_rotation_attrs(enum omap_plane plane, u8 rotation, + enum omap_dss_rotation_type rotation_type, + bool mirroring, enum omap_color_mode color_mode) +{ + bool row_repeat = false; + int vidrot = 0; + + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) { + + if (mirroring) { + switch (rotation) { + case OMAP_DSS_ROT_0: + vidrot = 2; + break; + case OMAP_DSS_ROT_90: + vidrot = 1; + break; + case OMAP_DSS_ROT_180: + vidrot = 0; + break; + case OMAP_DSS_ROT_270: + vidrot = 3; + break; + } + } else { + switch (rotation) { + case OMAP_DSS_ROT_0: + vidrot = 0; + break; + case OMAP_DSS_ROT_90: + vidrot = 1; + break; + case OMAP_DSS_ROT_180: + vidrot = 2; + break; + case OMAP_DSS_ROT_270: + vidrot = 3; + break; + } + } + + if (rotation == OMAP_DSS_ROT_90 || rotation == OMAP_DSS_ROT_270) + row_repeat = true; + else + row_repeat = false; + } + + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), vidrot, 13, 12); + if (dss_has_feature(FEAT_ROWREPEATENABLE)) + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), + row_repeat ? 1 : 0, 18, 18); + + if (color_mode == OMAP_DSS_COLOR_NV12) { + bool doublestride = (rotation_type == OMAP_DSS_ROT_TILER) && + (rotation == OMAP_DSS_ROT_0 || + rotation == OMAP_DSS_ROT_180); + /* DOUBLESTRIDE */ + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), doublestride, 22, 22); + } + +} + +static int color_mode_to_bpp(enum omap_color_mode color_mode) +{ + switch (color_mode) { + case OMAP_DSS_COLOR_CLUT1: + return 1; + case OMAP_DSS_COLOR_CLUT2: + return 2; + case OMAP_DSS_COLOR_CLUT4: + return 4; + case OMAP_DSS_COLOR_CLUT8: + case OMAP_DSS_COLOR_NV12: + return 8; + case OMAP_DSS_COLOR_RGB12U: + case OMAP_DSS_COLOR_RGB16: + case OMAP_DSS_COLOR_ARGB16: + case OMAP_DSS_COLOR_YUV2: + case OMAP_DSS_COLOR_UYVY: + case OMAP_DSS_COLOR_RGBA16: + case OMAP_DSS_COLOR_RGBX16: + case OMAP_DSS_COLOR_ARGB16_1555: + case OMAP_DSS_COLOR_XRGB16_1555: + return 16; + case OMAP_DSS_COLOR_RGB24P: + return 24; + case OMAP_DSS_COLOR_RGB24U: + case OMAP_DSS_COLOR_ARGB32: + case OMAP_DSS_COLOR_RGBA32: + case OMAP_DSS_COLOR_RGBX32: + return 32; + default: + BUG(); + return 0; + } +} + +static s32 pixinc(int pixels, u8 ps) +{ + if (pixels == 1) + return 1; + else if (pixels > 1) + return 1 + (pixels - 1) * ps; + else if (pixels < 0) + return 1 - (-pixels + 1) * ps; + else + BUG(); + return 0; +} + +static void calc_vrfb_rotation_offset(u8 rotation, bool mirror, + u16 screen_width, + u16 width, u16 height, + enum omap_color_mode color_mode, bool fieldmode, + unsigned int field_offset, + unsigned *offset0, unsigned *offset1, + s32 *row_inc, s32 *pix_inc, int x_predecim, int y_predecim) +{ + u8 ps; + + /* FIXME CLUT formats */ + switch (color_mode) { + case OMAP_DSS_COLOR_CLUT1: + case OMAP_DSS_COLOR_CLUT2: + case OMAP_DSS_COLOR_CLUT4: + case OMAP_DSS_COLOR_CLUT8: + BUG(); + return; + case OMAP_DSS_COLOR_YUV2: + case OMAP_DSS_COLOR_UYVY: + ps = 4; + break; + default: + ps = color_mode_to_bpp(color_mode) / 8; + break; + } + + DSSDBG("calc_rot(%d): scrw %d, %dx%d\n", rotation, screen_width, + width, height); + + /* + * field 0 = even field = bottom field + * field 1 = odd field = top field + */ + switch (rotation + mirror * 4) { + case OMAP_DSS_ROT_0: + case OMAP_DSS_ROT_180: + /* + * If the pixel format is YUV or UYVY divide the width + * of the image by 2 for 0 and 180 degree rotation. + */ + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + width = width >> 1; + case OMAP_DSS_ROT_90: + case OMAP_DSS_ROT_270: + *offset1 = 0; + if (field_offset) + *offset0 = field_offset * screen_width * ps; + else + *offset0 = 0; + + *row_inc = pixinc(1 + + (y_predecim * screen_width - x_predecim * width) + + (fieldmode ? screen_width : 0), ps); + *pix_inc = pixinc(x_predecim, ps); + break; + + case OMAP_DSS_ROT_0 + 4: + case OMAP_DSS_ROT_180 + 4: + /* If the pixel format is YUV or UYVY divide the width + * of the image by 2 for 0 degree and 180 degree + */ + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + width = width >> 1; + case OMAP_DSS_ROT_90 + 4: + case OMAP_DSS_ROT_270 + 4: + *offset1 = 0; + if (field_offset) + *offset0 = field_offset * screen_width * ps; + else + *offset0 = 0; + *row_inc = pixinc(1 - + (y_predecim * screen_width + x_predecim * width) - + (fieldmode ? screen_width : 0), ps); + *pix_inc = pixinc(x_predecim, ps); + break; + + default: + BUG(); + return; + } +} + +static void calc_dma_rotation_offset(u8 rotation, bool mirror, + u16 screen_width, + u16 width, u16 height, + enum omap_color_mode color_mode, bool fieldmode, + unsigned int field_offset, + unsigned *offset0, unsigned *offset1, + s32 *row_inc, s32 *pix_inc, int x_predecim, int y_predecim) +{ + u8 ps; + u16 fbw, fbh; + + /* FIXME CLUT formats */ + switch (color_mode) { + case OMAP_DSS_COLOR_CLUT1: + case OMAP_DSS_COLOR_CLUT2: + case OMAP_DSS_COLOR_CLUT4: + case OMAP_DSS_COLOR_CLUT8: + BUG(); + return; + default: + ps = color_mode_to_bpp(color_mode) / 8; + break; + } + + DSSDBG("calc_rot(%d): scrw %d, %dx%d\n", rotation, screen_width, + width, height); + + /* width & height are overlay sizes, convert to fb sizes */ + + if (rotation == OMAP_DSS_ROT_0 || rotation == OMAP_DSS_ROT_180) { + fbw = width; + fbh = height; + } else { + fbw = height; + fbh = width; + } + + /* + * field 0 = even field = bottom field + * field 1 = odd field = top field + */ + switch (rotation + mirror * 4) { + case OMAP_DSS_ROT_0: + *offset1 = 0; + if (field_offset) + *offset0 = *offset1 + field_offset * screen_width * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(1 + + (y_predecim * screen_width - fbw * x_predecim) + + (fieldmode ? screen_width : 0), ps); + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + *pix_inc = pixinc(x_predecim, 2 * ps); + else + *pix_inc = pixinc(x_predecim, ps); + break; + case OMAP_DSS_ROT_90: + *offset1 = screen_width * (fbh - 1) * ps; + if (field_offset) + *offset0 = *offset1 + field_offset * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(screen_width * (fbh * x_predecim - 1) + + y_predecim + (fieldmode ? 1 : 0), ps); + *pix_inc = pixinc(-x_predecim * screen_width, ps); + break; + case OMAP_DSS_ROT_180: + *offset1 = (screen_width * (fbh - 1) + fbw - 1) * ps; + if (field_offset) + *offset0 = *offset1 - field_offset * screen_width * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(-1 - + (y_predecim * screen_width - fbw * x_predecim) - + (fieldmode ? screen_width : 0), ps); + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + *pix_inc = pixinc(-x_predecim, 2 * ps); + else + *pix_inc = pixinc(-x_predecim, ps); + break; + case OMAP_DSS_ROT_270: + *offset1 = (fbw - 1) * ps; + if (field_offset) + *offset0 = *offset1 - field_offset * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(-screen_width * (fbh * x_predecim - 1) - + y_predecim - (fieldmode ? 1 : 0), ps); + *pix_inc = pixinc(x_predecim * screen_width, ps); + break; + + /* mirroring */ + case OMAP_DSS_ROT_0 + 4: + *offset1 = (fbw - 1) * ps; + if (field_offset) + *offset0 = *offset1 + field_offset * screen_width * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(y_predecim * screen_width * 2 - 1 + + (fieldmode ? screen_width : 0), + ps); + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + *pix_inc = pixinc(-x_predecim, 2 * ps); + else + *pix_inc = pixinc(-x_predecim, ps); + break; + + case OMAP_DSS_ROT_90 + 4: + *offset1 = 0; + if (field_offset) + *offset0 = *offset1 + field_offset * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(-screen_width * (fbh * x_predecim - 1) + + y_predecim + (fieldmode ? 1 : 0), + ps); + *pix_inc = pixinc(x_predecim * screen_width, ps); + break; + + case OMAP_DSS_ROT_180 + 4: + *offset1 = screen_width * (fbh - 1) * ps; + if (field_offset) + *offset0 = *offset1 - field_offset * screen_width * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(1 - y_predecim * screen_width * 2 - + (fieldmode ? screen_width : 0), + ps); + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + *pix_inc = pixinc(x_predecim, 2 * ps); + else + *pix_inc = pixinc(x_predecim, ps); + break; + + case OMAP_DSS_ROT_270 + 4: + *offset1 = (screen_width * (fbh - 1) + fbw - 1) * ps; + if (field_offset) + *offset0 = *offset1 - field_offset * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(screen_width * (fbh * x_predecim - 1) - + y_predecim - (fieldmode ? 1 : 0), + ps); + *pix_inc = pixinc(-x_predecim * screen_width, ps); + break; + + default: + BUG(); + return; + } +} + +static void calc_tiler_rotation_offset(u16 screen_width, u16 width, + enum omap_color_mode color_mode, bool fieldmode, + unsigned int field_offset, unsigned *offset0, unsigned *offset1, + s32 *row_inc, s32 *pix_inc, int x_predecim, int y_predecim) +{ + u8 ps; + + switch (color_mode) { + case OMAP_DSS_COLOR_CLUT1: + case OMAP_DSS_COLOR_CLUT2: + case OMAP_DSS_COLOR_CLUT4: + case OMAP_DSS_COLOR_CLUT8: + BUG(); + return; + default: + ps = color_mode_to_bpp(color_mode) / 8; + break; + } + + DSSDBG("scrw %d, width %d\n", screen_width, width); + + /* + * field 0 = even field = bottom field + * field 1 = odd field = top field + */ + *offset1 = 0; + if (field_offset) + *offset0 = *offset1 + field_offset * screen_width * ps; + else + *offset0 = *offset1; + *row_inc = pixinc(1 + (y_predecim * screen_width - width * x_predecim) + + (fieldmode ? screen_width : 0), ps); + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + *pix_inc = pixinc(x_predecim, 2 * ps); + else + *pix_inc = pixinc(x_predecim, ps); +} + +/* + * This function is used to avoid synclosts in OMAP3, because of some + * undocumented horizontal position and timing related limitations. + */ +static int check_horiz_timing_omap3(unsigned long pclk, unsigned long lclk, + const struct omap_video_timings *t, u16 pos_x, + u16 width, u16 height, u16 out_width, u16 out_height, + bool five_taps) +{ + const int ds = DIV_ROUND_UP(height, out_height); + unsigned long nonactive; + static const u8 limits[3] = { 8, 10, 20 }; + u64 val, blank; + int i; + + nonactive = t->x_res + t->hfp + t->hsw + t->hbp - out_width; + + i = 0; + if (out_height < height) + i++; + if (out_width < width) + i++; + blank = div_u64((u64)(t->hbp + t->hsw + t->hfp) * lclk, pclk); + DSSDBG("blanking period + ppl = %llu (limit = %u)\n", blank, limits[i]); + if (blank <= limits[i]) + return -EINVAL; + + /* FIXME add checks for 3-tap filter once the limitations are known */ + if (!five_taps) + return 0; + + /* + * Pixel data should be prepared before visible display point starts. + * So, atleast DS-2 lines must have already been fetched by DISPC + * during nonactive - pos_x period. + */ + val = div_u64((u64)(nonactive - pos_x) * lclk, pclk); + DSSDBG("(nonactive - pos_x) * pcd = %llu max(0, DS - 2) * width = %d\n", + val, max(0, ds - 2) * width); + if (val < max(0, ds - 2) * width) + return -EINVAL; + + /* + * All lines need to be refilled during the nonactive period of which + * only one line can be loaded during the active period. So, atleast + * DS - 1 lines should be loaded during nonactive period. + */ + val = div_u64((u64)nonactive * lclk, pclk); + DSSDBG("nonactive * pcd = %llu, max(0, DS - 1) * width = %d\n", + val, max(0, ds - 1) * width); + if (val < max(0, ds - 1) * width) + return -EINVAL; + + return 0; +} + +static unsigned long calc_core_clk_five_taps(unsigned long pclk, + const struct omap_video_timings *mgr_timings, u16 width, + u16 height, u16 out_width, u16 out_height, + enum omap_color_mode color_mode) +{ + u32 core_clk = 0; + u64 tmp; + + if (height <= out_height && width <= out_width) + return (unsigned long) pclk; + + if (height > out_height) { + unsigned int ppl = mgr_timings->x_res; + + tmp = pclk * height * out_width; + do_div(tmp, 2 * out_height * ppl); + core_clk = tmp; + + if (height > 2 * out_height) { + if (ppl == out_width) + return 0; + + tmp = pclk * (height - 2 * out_height) * out_width; + do_div(tmp, 2 * out_height * (ppl - out_width)); + core_clk = max_t(u32, core_clk, tmp); + } + } + + if (width > out_width) { + tmp = pclk * width; + do_div(tmp, out_width); + core_clk = max_t(u32, core_clk, tmp); + + if (color_mode == OMAP_DSS_COLOR_RGB24U) + core_clk <<= 1; + } + + return core_clk; +} + +static unsigned long calc_core_clk_24xx(unsigned long pclk, u16 width, + u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ + if (height > out_height && width > out_width) + return pclk * 4; + else + return pclk * 2; +} + +static unsigned long calc_core_clk_34xx(unsigned long pclk, u16 width, + u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ + unsigned int hf, vf; + + /* + * FIXME how to determine the 'A' factor + * for the no downscaling case ? + */ + + if (width > 3 * out_width) + hf = 4; + else if (width > 2 * out_width) + hf = 3; + else if (width > out_width) + hf = 2; + else + hf = 1; + if (height > out_height) + vf = 2; + else + vf = 1; + + return pclk * vf * hf; +} + +static unsigned long calc_core_clk_44xx(unsigned long pclk, u16 width, + u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ + /* + * If the overlay/writeback is in mem to mem mode, there are no + * downscaling limitations with respect to pixel clock, return 1 as + * required core clock to represent that we have sufficient enough + * core clock to do maximum downscaling + */ + if (mem_to_mem) + return 1; + + if (width > out_width) + return DIV_ROUND_UP(pclk, out_width) * width; + else + return pclk; +} + +static int dispc_ovl_calc_scaling_24xx(unsigned long pclk, unsigned long lclk, + const struct omap_video_timings *mgr_timings, + u16 width, u16 height, u16 out_width, u16 out_height, + enum omap_color_mode color_mode, bool *five_taps, + int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, bool mem_to_mem) +{ + int error; + u16 in_width, in_height; + int min_factor = min(*decim_x, *decim_y); + const int maxsinglelinewidth = + dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH); + + *five_taps = false; + + do { + in_height = height / *decim_y; + in_width = width / *decim_x; + *core_clk = dispc.feat->calc_core_clk(pclk, in_width, + in_height, out_width, out_height, mem_to_mem); + error = (in_width > maxsinglelinewidth || !*core_clk || + *core_clk > dispc_core_clk_rate()); + if (error) { + if (*decim_x == *decim_y) { + *decim_x = min_factor; + ++*decim_y; + } else { + swap(*decim_x, *decim_y); + if (*decim_x < *decim_y) + ++*decim_x; + } + } + } while (*decim_x <= *x_predecim && *decim_y <= *y_predecim && error); + + if (in_width > maxsinglelinewidth) { + DSSERR("Cannot scale max input width exceeded"); + return -EINVAL; + } + return 0; +} + +static int dispc_ovl_calc_scaling_34xx(unsigned long pclk, unsigned long lclk, + const struct omap_video_timings *mgr_timings, + u16 width, u16 height, u16 out_width, u16 out_height, + enum omap_color_mode color_mode, bool *five_taps, + int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, bool mem_to_mem) +{ + int error; + u16 in_width, in_height; + int min_factor = min(*decim_x, *decim_y); + const int maxsinglelinewidth = + dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH); + + do { + in_height = height / *decim_y; + in_width = width / *decim_x; + *five_taps = in_height > out_height; + + if (in_width > maxsinglelinewidth) + if (in_height > out_height && + in_height < out_height * 2) + *five_taps = false; +again: + if (*five_taps) + *core_clk = calc_core_clk_five_taps(pclk, mgr_timings, + in_width, in_height, out_width, + out_height, color_mode); + else + *core_clk = dispc.feat->calc_core_clk(pclk, in_width, + in_height, out_width, out_height, + mem_to_mem); + + error = check_horiz_timing_omap3(pclk, lclk, mgr_timings, + pos_x, in_width, in_height, out_width, + out_height, *five_taps); + if (error && *five_taps) { + *five_taps = false; + goto again; + } + + error = (error || in_width > maxsinglelinewidth * 2 || + (in_width > maxsinglelinewidth && *five_taps) || + !*core_clk || *core_clk > dispc_core_clk_rate()); + if (error) { + if (*decim_x == *decim_y) { + *decim_x = min_factor; + ++*decim_y; + } else { + swap(*decim_x, *decim_y); + if (*decim_x < *decim_y) + ++*decim_x; + } + } + } while (*decim_x <= *x_predecim && *decim_y <= *y_predecim && error); + + if (check_horiz_timing_omap3(pclk, lclk, mgr_timings, pos_x, width, + height, out_width, out_height, *five_taps)) { + DSSERR("horizontal timing too tight\n"); + return -EINVAL; + } + + if (in_width > (maxsinglelinewidth * 2)) { + DSSERR("Cannot setup scaling"); + DSSERR("width exceeds maximum width possible"); + return -EINVAL; + } + + if (in_width > maxsinglelinewidth && *five_taps) { + DSSERR("cannot setup scaling with five taps"); + return -EINVAL; + } + return 0; +} + +static int dispc_ovl_calc_scaling_44xx(unsigned long pclk, unsigned long lclk, + const struct omap_video_timings *mgr_timings, + u16 width, u16 height, u16 out_width, u16 out_height, + enum omap_color_mode color_mode, bool *five_taps, + int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, bool mem_to_mem) +{ + u16 in_width, in_width_max; + int decim_x_min = *decim_x; + u16 in_height = height / *decim_y; + const int maxsinglelinewidth = + dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH); + const int maxdownscale = dss_feat_get_param_max(FEAT_PARAM_DOWNSCALE); + + if (mem_to_mem) { + in_width_max = out_width * maxdownscale; + } else { + in_width_max = dispc_core_clk_rate() / + DIV_ROUND_UP(pclk, out_width); + } + + *decim_x = DIV_ROUND_UP(width, in_width_max); + + *decim_x = *decim_x > decim_x_min ? *decim_x : decim_x_min; + if (*decim_x > *x_predecim) + return -EINVAL; + + do { + in_width = width / *decim_x; + } while (*decim_x <= *x_predecim && + in_width > maxsinglelinewidth && ++*decim_x); + + if (in_width > maxsinglelinewidth) { + DSSERR("Cannot scale width exceeds max line width"); + return -EINVAL; + } + + *core_clk = dispc.feat->calc_core_clk(pclk, in_width, in_height, + out_width, out_height, mem_to_mem); + return 0; +} + +static int dispc_ovl_calc_scaling(unsigned long pclk, unsigned long lclk, + enum omap_overlay_caps caps, + const struct omap_video_timings *mgr_timings, + u16 width, u16 height, u16 out_width, u16 out_height, + enum omap_color_mode color_mode, bool *five_taps, + int *x_predecim, int *y_predecim, u16 pos_x, + enum omap_dss_rotation_type rotation_type, bool mem_to_mem) +{ + const int maxdownscale = dss_feat_get_param_max(FEAT_PARAM_DOWNSCALE); + const int max_decim_limit = 16; + unsigned long core_clk = 0; + int decim_x, decim_y, ret; + + if (width == out_width && height == out_height) + return 0; + + if ((caps & OMAP_DSS_OVL_CAP_SCALE) == 0) + return -EINVAL; + + if (mem_to_mem) { + *x_predecim = *y_predecim = 1; + } else { + *x_predecim = max_decim_limit; + *y_predecim = (rotation_type == OMAP_DSS_ROT_TILER && + dss_has_feature(FEAT_BURST_2D)) ? + 2 : max_decim_limit; + } + + if (color_mode == OMAP_DSS_COLOR_CLUT1 || + color_mode == OMAP_DSS_COLOR_CLUT2 || + color_mode == OMAP_DSS_COLOR_CLUT4 || + color_mode == OMAP_DSS_COLOR_CLUT8) { + *x_predecim = 1; + *y_predecim = 1; + *five_taps = false; + return 0; + } + + decim_x = DIV_ROUND_UP(DIV_ROUND_UP(width, out_width), maxdownscale); + decim_y = DIV_ROUND_UP(DIV_ROUND_UP(height, out_height), maxdownscale); + + if (decim_x > *x_predecim || out_width > width * 8) + return -EINVAL; + + if (decim_y > *y_predecim || out_height > height * 8) + return -EINVAL; + + ret = dispc.feat->calc_scaling(pclk, lclk, mgr_timings, width, height, + out_width, out_height, color_mode, five_taps, + x_predecim, y_predecim, &decim_x, &decim_y, pos_x, &core_clk, + mem_to_mem); + if (ret) + return ret; + + DSSDBG("required core clk rate = %lu Hz\n", core_clk); + DSSDBG("current core clk rate = %lu Hz\n", dispc_core_clk_rate()); + + if (!core_clk || core_clk > dispc_core_clk_rate()) { + DSSERR("failed to set up scaling, " + "required core clk rate = %lu Hz, " + "current core clk rate = %lu Hz\n", + core_clk, dispc_core_clk_rate()); + return -EINVAL; + } + + *x_predecim = decim_x; + *y_predecim = decim_y; + return 0; +} + +int dispc_ovl_check(enum omap_plane plane, enum omap_channel channel, + const struct omap_overlay_info *oi, + const struct omap_video_timings *timings, + int *x_predecim, int *y_predecim) +{ + enum omap_overlay_caps caps = dss_feat_get_overlay_caps(plane); + bool five_taps = true; + bool fieldmode = false; + u16 in_height = oi->height; + u16 in_width = oi->width; + bool ilace = timings->interlace; + u16 out_width, out_height; + int pos_x = oi->pos_x; + unsigned long pclk = dispc_mgr_pclk_rate(channel); + unsigned long lclk = dispc_mgr_lclk_rate(channel); + + out_width = oi->out_width == 0 ? oi->width : oi->out_width; + out_height = oi->out_height == 0 ? oi->height : oi->out_height; + + if (ilace && oi->height == out_height) + fieldmode = true; + + if (ilace) { + if (fieldmode) + in_height /= 2; + out_height /= 2; + + DSSDBG("adjusting for ilace: height %d, out_height %d\n", + in_height, out_height); + } + + if (!dss_feat_color_mode_supported(plane, oi->color_mode)) + return -EINVAL; + + return dispc_ovl_calc_scaling(pclk, lclk, caps, timings, in_width, + in_height, out_width, out_height, oi->color_mode, + &five_taps, x_predecim, y_predecim, pos_x, + oi->rotation_type, false); +} +EXPORT_SYMBOL(dispc_ovl_check); + +static int dispc_ovl_setup_common(enum omap_plane plane, + enum omap_overlay_caps caps, u32 paddr, u32 p_uv_addr, + u16 screen_width, int pos_x, int pos_y, u16 width, u16 height, + u16 out_width, u16 out_height, enum omap_color_mode color_mode, + u8 rotation, bool mirror, u8 zorder, u8 pre_mult_alpha, + u8 global_alpha, enum omap_dss_rotation_type rotation_type, + bool replication, const struct omap_video_timings *mgr_timings, + bool mem_to_mem) +{ + bool five_taps = true; + bool fieldmode = false; + int r, cconv = 0; + unsigned offset0, offset1; + s32 row_inc; + s32 pix_inc; + u16 frame_width, frame_height; + unsigned int field_offset = 0; + u16 in_height = height; + u16 in_width = width; + int x_predecim = 1, y_predecim = 1; + bool ilace = mgr_timings->interlace; + unsigned long pclk = dispc_plane_pclk_rate(plane); + unsigned long lclk = dispc_plane_lclk_rate(plane); + + if (paddr == 0) + return -EINVAL; + + out_width = out_width == 0 ? width : out_width; + out_height = out_height == 0 ? height : out_height; + + if (ilace && height == out_height) + fieldmode = true; + + if (ilace) { + if (fieldmode) + in_height /= 2; + pos_y /= 2; + out_height /= 2; + + DSSDBG("adjusting for ilace: height %d, pos_y %d, " + "out_height %d\n", in_height, pos_y, + out_height); + } + + if (!dss_feat_color_mode_supported(plane, color_mode)) + return -EINVAL; + + r = dispc_ovl_calc_scaling(pclk, lclk, caps, mgr_timings, in_width, + in_height, out_width, out_height, color_mode, + &five_taps, &x_predecim, &y_predecim, pos_x, + rotation_type, mem_to_mem); + if (r) + return r; + + in_width = in_width / x_predecim; + in_height = in_height / y_predecim; + + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY || + color_mode == OMAP_DSS_COLOR_NV12) + cconv = 1; + + if (ilace && !fieldmode) { + /* + * when downscaling the bottom field may have to start several + * source lines below the top field. Unfortunately ACCUI + * registers will only hold the fractional part of the offset + * so the integer part must be added to the base address of the + * bottom field. + */ + if (!in_height || in_height == out_height) + field_offset = 0; + else + field_offset = in_height / out_height / 2; + } + + /* Fields are independent but interleaved in memory. */ + if (fieldmode) + field_offset = 1; + + offset0 = 0; + offset1 = 0; + row_inc = 0; + pix_inc = 0; + + if (plane == OMAP_DSS_WB) { + frame_width = out_width; + frame_height = out_height; + } else { + frame_width = in_width; + frame_height = height; + } + + if (rotation_type == OMAP_DSS_ROT_TILER) + calc_tiler_rotation_offset(screen_width, frame_width, + color_mode, fieldmode, field_offset, + &offset0, &offset1, &row_inc, &pix_inc, + x_predecim, y_predecim); + else if (rotation_type == OMAP_DSS_ROT_DMA) + calc_dma_rotation_offset(rotation, mirror, screen_width, + frame_width, frame_height, + color_mode, fieldmode, field_offset, + &offset0, &offset1, &row_inc, &pix_inc, + x_predecim, y_predecim); + else + calc_vrfb_rotation_offset(rotation, mirror, + screen_width, frame_width, frame_height, + color_mode, fieldmode, field_offset, + &offset0, &offset1, &row_inc, &pix_inc, + x_predecim, y_predecim); + + DSSDBG("offset0 %u, offset1 %u, row_inc %d, pix_inc %d\n", + offset0, offset1, row_inc, pix_inc); + + dispc_ovl_set_color_mode(plane, color_mode); + + dispc_ovl_configure_burst_type(plane, rotation_type); + + dispc_ovl_set_ba0(plane, paddr + offset0); + dispc_ovl_set_ba1(plane, paddr + offset1); + + if (OMAP_DSS_COLOR_NV12 == color_mode) { + dispc_ovl_set_ba0_uv(plane, p_uv_addr + offset0); + dispc_ovl_set_ba1_uv(plane, p_uv_addr + offset1); + } + + dispc_ovl_set_row_inc(plane, row_inc); + dispc_ovl_set_pix_inc(plane, pix_inc); + + DSSDBG("%d,%d %dx%d -> %dx%d\n", pos_x, pos_y, in_width, + in_height, out_width, out_height); + + dispc_ovl_set_pos(plane, caps, pos_x, pos_y); + + dispc_ovl_set_input_size(plane, in_width, in_height); + + if (caps & OMAP_DSS_OVL_CAP_SCALE) { + dispc_ovl_set_scaling(plane, in_width, in_height, out_width, + out_height, ilace, five_taps, fieldmode, + color_mode, rotation); + dispc_ovl_set_output_size(plane, out_width, out_height); + dispc_ovl_set_vid_color_conv(plane, cconv); + } + + dispc_ovl_set_rotation_attrs(plane, rotation, rotation_type, mirror, + color_mode); + + dispc_ovl_set_zorder(plane, caps, zorder); + dispc_ovl_set_pre_mult_alpha(plane, caps, pre_mult_alpha); + dispc_ovl_setup_global_alpha(plane, caps, global_alpha); + + dispc_ovl_enable_replication(plane, caps, replication); + + return 0; +} + +int dispc_ovl_setup(enum omap_plane plane, const struct omap_overlay_info *oi, + bool replication, const struct omap_video_timings *mgr_timings, + bool mem_to_mem) +{ + int r; + enum omap_overlay_caps caps = dss_feat_get_overlay_caps(plane); + enum omap_channel channel; + + channel = dispc_ovl_get_channel_out(plane); + + DSSDBG("dispc_ovl_setup %d, pa %x, pa_uv %x, sw %d, %d,%d, %dx%d -> " + "%dx%d, cmode %x, rot %d, mir %d, chan %d repl %d\n", + plane, oi->paddr, oi->p_uv_addr, oi->screen_width, oi->pos_x, + oi->pos_y, oi->width, oi->height, oi->out_width, oi->out_height, + oi->color_mode, oi->rotation, oi->mirror, channel, replication); + + r = dispc_ovl_setup_common(plane, caps, oi->paddr, oi->p_uv_addr, + oi->screen_width, oi->pos_x, oi->pos_y, oi->width, oi->height, + oi->out_width, oi->out_height, oi->color_mode, oi->rotation, + oi->mirror, oi->zorder, oi->pre_mult_alpha, oi->global_alpha, + oi->rotation_type, replication, mgr_timings, mem_to_mem); + + return r; +} +EXPORT_SYMBOL(dispc_ovl_setup); + +int dispc_wb_setup(const struct omap_dss_writeback_info *wi, + bool mem_to_mem, const struct omap_video_timings *mgr_timings) +{ + int r; + u32 l; + enum omap_plane plane = OMAP_DSS_WB; + const int pos_x = 0, pos_y = 0; + const u8 zorder = 0, global_alpha = 0; + const bool replication = false; + bool truncation; + int in_width = mgr_timings->x_res; + int in_height = mgr_timings->y_res; + enum omap_overlay_caps caps = + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA; + + DSSDBG("dispc_wb_setup, pa %x, pa_uv %x, %d,%d -> %dx%d, cmode %x, " + "rot %d, mir %d\n", wi->paddr, wi->p_uv_addr, in_width, + in_height, wi->width, wi->height, wi->color_mode, wi->rotation, + wi->mirror); + + r = dispc_ovl_setup_common(plane, caps, wi->paddr, wi->p_uv_addr, + wi->buf_width, pos_x, pos_y, in_width, in_height, wi->width, + wi->height, wi->color_mode, wi->rotation, wi->mirror, zorder, + wi->pre_mult_alpha, global_alpha, wi->rotation_type, + replication, mgr_timings, mem_to_mem); + + switch (wi->color_mode) { + case OMAP_DSS_COLOR_RGB16: + case OMAP_DSS_COLOR_RGB24P: + case OMAP_DSS_COLOR_ARGB16: + case OMAP_DSS_COLOR_RGBA16: + case OMAP_DSS_COLOR_RGB12U: + case OMAP_DSS_COLOR_ARGB16_1555: + case OMAP_DSS_COLOR_XRGB16_1555: + case OMAP_DSS_COLOR_RGBX16: + truncation = true; + break; + default: + truncation = false; + break; + } + + /* setup extra DISPC_WB_ATTRIBUTES */ + l = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + l = FLD_MOD(l, truncation, 10, 10); /* TRUNCATIONENABLE */ + l = FLD_MOD(l, mem_to_mem, 19, 19); /* WRITEBACKMODE */ + dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), l); + + return r; +} + +int dispc_ovl_enable(enum omap_plane plane, bool enable) +{ + DSSDBG("dispc_enable_plane %d, %d\n", plane, enable); + + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), enable ? 1 : 0, 0, 0); + + return 0; +} +EXPORT_SYMBOL(dispc_ovl_enable); + +bool dispc_ovl_enabled(enum omap_plane plane) +{ + return REG_GET(DISPC_OVL_ATTRIBUTES(plane), 0, 0); +} +EXPORT_SYMBOL(dispc_ovl_enabled); + +void dispc_mgr_enable(enum omap_channel channel, bool enable) +{ + mgr_fld_write(channel, DISPC_MGR_FLD_ENABLE, enable); + /* flush posted write */ + mgr_fld_read(channel, DISPC_MGR_FLD_ENABLE); +} +EXPORT_SYMBOL(dispc_mgr_enable); + +bool dispc_mgr_is_enabled(enum omap_channel channel) +{ + return !!mgr_fld_read(channel, DISPC_MGR_FLD_ENABLE); +} +EXPORT_SYMBOL(dispc_mgr_is_enabled); + +void dispc_wb_enable(bool enable) +{ + dispc_ovl_enable(OMAP_DSS_WB, enable); +} + +bool dispc_wb_is_enabled(void) +{ + return dispc_ovl_enabled(OMAP_DSS_WB); +} + +static void dispc_lcd_enable_signal_polarity(bool act_high) +{ + if (!dss_has_feature(FEAT_LCDENABLEPOL)) + return; + + REG_FLD_MOD(DISPC_CONTROL, act_high ? 1 : 0, 29, 29); +} + +void dispc_lcd_enable_signal(bool enable) +{ + if (!dss_has_feature(FEAT_LCDENABLESIGNAL)) + return; + + REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 28, 28); +} + +void dispc_pck_free_enable(bool enable) +{ + if (!dss_has_feature(FEAT_PCKFREEENABLE)) + return; + + REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 27, 27); +} + +static void dispc_mgr_enable_fifohandcheck(enum omap_channel channel, bool enable) +{ + mgr_fld_write(channel, DISPC_MGR_FLD_FIFOHANDCHECK, enable); +} + + +static void dispc_mgr_set_lcd_type_tft(enum omap_channel channel) +{ + mgr_fld_write(channel, DISPC_MGR_FLD_STNTFT, 1); +} + +void dispc_set_loadmode(enum omap_dss_load_mode mode) +{ + REG_FLD_MOD(DISPC_CONFIG, mode, 2, 1); +} + + +static void dispc_mgr_set_default_color(enum omap_channel channel, u32 color) +{ + dispc_write_reg(DISPC_DEFAULT_COLOR(channel), color); +} + +static void dispc_mgr_set_trans_key(enum omap_channel ch, + enum omap_dss_trans_key_type type, + u32 trans_key) +{ + mgr_fld_write(ch, DISPC_MGR_FLD_TCKSELECTION, type); + + dispc_write_reg(DISPC_TRANS_COLOR(ch), trans_key); +} + +static void dispc_mgr_enable_trans_key(enum omap_channel ch, bool enable) +{ + mgr_fld_write(ch, DISPC_MGR_FLD_TCKENABLE, enable); +} + +static void dispc_mgr_enable_alpha_fixed_zorder(enum omap_channel ch, + bool enable) +{ + if (!dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) + return; + + if (ch == OMAP_DSS_CHANNEL_LCD) + REG_FLD_MOD(DISPC_CONFIG, enable, 18, 18); + else if (ch == OMAP_DSS_CHANNEL_DIGIT) + REG_FLD_MOD(DISPC_CONFIG, enable, 19, 19); +} + +void dispc_mgr_setup(enum omap_channel channel, + const struct omap_overlay_manager_info *info) +{ + dispc_mgr_set_default_color(channel, info->default_color); + dispc_mgr_set_trans_key(channel, info->trans_key_type, info->trans_key); + dispc_mgr_enable_trans_key(channel, info->trans_enabled); + dispc_mgr_enable_alpha_fixed_zorder(channel, + info->partial_alpha_enabled); + if (dss_has_feature(FEAT_CPR)) { + dispc_mgr_enable_cpr(channel, info->cpr_enable); + dispc_mgr_set_cpr_coef(channel, &info->cpr_coefs); + } +} +EXPORT_SYMBOL(dispc_mgr_setup); + +static void dispc_mgr_set_tft_data_lines(enum omap_channel channel, u8 data_lines) +{ + int code; + + switch (data_lines) { + case 12: + code = 0; + break; + case 16: + code = 1; + break; + case 18: + code = 2; + break; + case 24: + code = 3; + break; + default: + BUG(); + return; + } + + mgr_fld_write(channel, DISPC_MGR_FLD_TFTDATALINES, code); +} + +static void dispc_mgr_set_io_pad_mode(enum dss_io_pad_mode mode) +{ + u32 l; + int gpout0, gpout1; + + switch (mode) { + case DSS_IO_PAD_MODE_RESET: + gpout0 = 0; + gpout1 = 0; + break; + case DSS_IO_PAD_MODE_RFBI: + gpout0 = 1; + gpout1 = 0; + break; + case DSS_IO_PAD_MODE_BYPASS: + gpout0 = 1; + gpout1 = 1; + break; + default: + BUG(); + return; + } + + l = dispc_read_reg(DISPC_CONTROL); + l = FLD_MOD(l, gpout0, 15, 15); + l = FLD_MOD(l, gpout1, 16, 16); + dispc_write_reg(DISPC_CONTROL, l); +} + +static void dispc_mgr_enable_stallmode(enum omap_channel channel, bool enable) +{ + mgr_fld_write(channel, DISPC_MGR_FLD_STALLMODE, enable); +} + +void dispc_mgr_set_lcd_config(enum omap_channel channel, + const struct dss_lcd_mgr_config *config) +{ + dispc_mgr_set_io_pad_mode(config->io_pad_mode); + + dispc_mgr_enable_stallmode(channel, config->stallmode); + dispc_mgr_enable_fifohandcheck(channel, config->fifohandcheck); + + dispc_mgr_set_clock_div(channel, &config->clock_info); + + dispc_mgr_set_tft_data_lines(channel, config->video_port_width); + + dispc_lcd_enable_signal_polarity(config->lcden_sig_polarity); + + dispc_mgr_set_lcd_type_tft(channel); +} +EXPORT_SYMBOL(dispc_mgr_set_lcd_config); + +static bool _dispc_mgr_size_ok(u16 width, u16 height) +{ + return width <= dispc.feat->mgr_width_max && + height <= dispc.feat->mgr_height_max; +} + +static bool _dispc_lcd_timings_ok(int hsw, int hfp, int hbp, + int vsw, int vfp, int vbp) +{ + if (hsw < 1 || hsw > dispc.feat->sw_max || + hfp < 1 || hfp > dispc.feat->hp_max || + hbp < 1 || hbp > dispc.feat->hp_max || + vsw < 1 || vsw > dispc.feat->sw_max || + vfp < 0 || vfp > dispc.feat->vp_max || + vbp < 0 || vbp > dispc.feat->vp_max) + return false; + return true; +} + +static bool _dispc_mgr_pclk_ok(enum omap_channel channel, + unsigned long pclk) +{ + if (dss_mgr_is_lcd(channel)) + return pclk <= dispc.feat->max_lcd_pclk ? true : false; + else + return pclk <= dispc.feat->max_tv_pclk ? true : false; +} + +bool dispc_mgr_timings_ok(enum omap_channel channel, + const struct omap_video_timings *timings) +{ + bool timings_ok; + + timings_ok = _dispc_mgr_size_ok(timings->x_res, timings->y_res); + + timings_ok &= _dispc_mgr_pclk_ok(channel, timings->pixelclock); + + if (dss_mgr_is_lcd(channel)) { + timings_ok &= _dispc_lcd_timings_ok(timings->hsw, timings->hfp, + timings->hbp, timings->vsw, timings->vfp, + timings->vbp); + } + + return timings_ok; +} + +static void _dispc_mgr_set_lcd_timings(enum omap_channel channel, int hsw, + int hfp, int hbp, int vsw, int vfp, int vbp, + enum omap_dss_signal_level vsync_level, + enum omap_dss_signal_level hsync_level, + enum omap_dss_signal_edge data_pclk_edge, + enum omap_dss_signal_level de_level, + enum omap_dss_signal_edge sync_pclk_edge) + +{ + u32 timing_h, timing_v, l; + bool onoff, rf, ipc; + + timing_h = FLD_VAL(hsw-1, dispc.feat->sw_start, 0) | + FLD_VAL(hfp-1, dispc.feat->fp_start, 8) | + FLD_VAL(hbp-1, dispc.feat->bp_start, 20); + timing_v = FLD_VAL(vsw-1, dispc.feat->sw_start, 0) | + FLD_VAL(vfp, dispc.feat->fp_start, 8) | + FLD_VAL(vbp, dispc.feat->bp_start, 20); + + dispc_write_reg(DISPC_TIMING_H(channel), timing_h); + dispc_write_reg(DISPC_TIMING_V(channel), timing_v); + + switch (data_pclk_edge) { + case OMAPDSS_DRIVE_SIG_RISING_EDGE: + ipc = false; + break; + case OMAPDSS_DRIVE_SIG_FALLING_EDGE: + ipc = true; + break; + case OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES: + default: + BUG(); + } + + switch (sync_pclk_edge) { + case OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES: + onoff = false; + rf = false; + break; + case OMAPDSS_DRIVE_SIG_FALLING_EDGE: + onoff = true; + rf = false; + break; + case OMAPDSS_DRIVE_SIG_RISING_EDGE: + onoff = true; + rf = true; + break; + default: + BUG(); + } + + l = dispc_read_reg(DISPC_POL_FREQ(channel)); + l |= FLD_VAL(onoff, 17, 17); + l |= FLD_VAL(rf, 16, 16); + l |= FLD_VAL(de_level, 15, 15); + l |= FLD_VAL(ipc, 14, 14); + l |= FLD_VAL(hsync_level, 13, 13); + l |= FLD_VAL(vsync_level, 12, 12); + dispc_write_reg(DISPC_POL_FREQ(channel), l); +} + +/* change name to mode? */ +void dispc_mgr_set_timings(enum omap_channel channel, + const struct omap_video_timings *timings) +{ + unsigned xtot, ytot; + unsigned long ht, vt; + struct omap_video_timings t = *timings; + + DSSDBG("channel %d xres %u yres %u\n", channel, t.x_res, t.y_res); + + if (!dispc_mgr_timings_ok(channel, &t)) { + BUG(); + return; + } + + if (dss_mgr_is_lcd(channel)) { + _dispc_mgr_set_lcd_timings(channel, t.hsw, t.hfp, t.hbp, t.vsw, + t.vfp, t.vbp, t.vsync_level, t.hsync_level, + t.data_pclk_edge, t.de_level, t.sync_pclk_edge); + + xtot = t.x_res + t.hfp + t.hsw + t.hbp; + ytot = t.y_res + t.vfp + t.vsw + t.vbp; + + ht = timings->pixelclock / xtot; + vt = timings->pixelclock / xtot / ytot; + + DSSDBG("pck %u\n", timings->pixelclock); + DSSDBG("hsw %d hfp %d hbp %d vsw %d vfp %d vbp %d\n", + t.hsw, t.hfp, t.hbp, t.vsw, t.vfp, t.vbp); + DSSDBG("vsync_level %d hsync_level %d data_pclk_edge %d de_level %d sync_pclk_edge %d\n", + t.vsync_level, t.hsync_level, t.data_pclk_edge, + t.de_level, t.sync_pclk_edge); + + DSSDBG("hsync %luHz, vsync %luHz\n", ht, vt); + } else { + if (t.interlace == true) + t.y_res /= 2; + } + + dispc_mgr_set_size(channel, t.x_res, t.y_res); +} +EXPORT_SYMBOL(dispc_mgr_set_timings); + +static void dispc_mgr_set_lcd_divisor(enum omap_channel channel, u16 lck_div, + u16 pck_div) +{ + BUG_ON(lck_div < 1); + BUG_ON(pck_div < 1); + + dispc_write_reg(DISPC_DIVISORo(channel), + FLD_VAL(lck_div, 23, 16) | FLD_VAL(pck_div, 7, 0)); + + if (dss_has_feature(FEAT_CORE_CLK_DIV) == false && + channel == OMAP_DSS_CHANNEL_LCD) + dispc.core_clk_rate = dispc_fclk_rate() / lck_div; +} + +static void dispc_mgr_get_lcd_divisor(enum omap_channel channel, int *lck_div, + int *pck_div) +{ + u32 l; + l = dispc_read_reg(DISPC_DIVISORo(channel)); + *lck_div = FLD_GET(l, 23, 16); + *pck_div = FLD_GET(l, 7, 0); +} + +unsigned long dispc_fclk_rate(void) +{ + struct platform_device *dsidev; + unsigned long r = 0; + + switch (dss_get_dispc_clk_source()) { + case OMAP_DSS_CLK_SRC_FCK: + r = dss_get_dispc_clk_rate(); + break; + case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: + dsidev = dsi_get_dsidev_from_id(0); + r = dsi_get_pll_hsdiv_dispc_rate(dsidev); + break; + case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: + dsidev = dsi_get_dsidev_from_id(1); + r = dsi_get_pll_hsdiv_dispc_rate(dsidev); + break; + default: + BUG(); + return 0; + } + + return r; +} + +unsigned long dispc_mgr_lclk_rate(enum omap_channel channel) +{ + struct platform_device *dsidev; + int lcd; + unsigned long r; + u32 l; + + if (dss_mgr_is_lcd(channel)) { + l = dispc_read_reg(DISPC_DIVISORo(channel)); + + lcd = FLD_GET(l, 23, 16); + + switch (dss_get_lcd_clk_source(channel)) { + case OMAP_DSS_CLK_SRC_FCK: + r = dss_get_dispc_clk_rate(); + break; + case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: + dsidev = dsi_get_dsidev_from_id(0); + r = dsi_get_pll_hsdiv_dispc_rate(dsidev); + break; + case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: + dsidev = dsi_get_dsidev_from_id(1); + r = dsi_get_pll_hsdiv_dispc_rate(dsidev); + break; + default: + BUG(); + return 0; + } + + return r / lcd; + } else { + return dispc_fclk_rate(); + } +} + +unsigned long dispc_mgr_pclk_rate(enum omap_channel channel) +{ + unsigned long r; + + if (dss_mgr_is_lcd(channel)) { + int pcd; + u32 l; + + l = dispc_read_reg(DISPC_DIVISORo(channel)); + + pcd = FLD_GET(l, 7, 0); + + r = dispc_mgr_lclk_rate(channel); + + return r / pcd; + } else { + return dispc.tv_pclk_rate; + } +} + +void dispc_set_tv_pclk(unsigned long pclk) +{ + dispc.tv_pclk_rate = pclk; +} + +unsigned long dispc_core_clk_rate(void) +{ + return dispc.core_clk_rate; +} + +static unsigned long dispc_plane_pclk_rate(enum omap_plane plane) +{ + enum omap_channel channel; + + if (plane == OMAP_DSS_WB) + return 0; + + channel = dispc_ovl_get_channel_out(plane); + + return dispc_mgr_pclk_rate(channel); +} + +static unsigned long dispc_plane_lclk_rate(enum omap_plane plane) +{ + enum omap_channel channel; + + if (plane == OMAP_DSS_WB) + return 0; + + channel = dispc_ovl_get_channel_out(plane); + + return dispc_mgr_lclk_rate(channel); +} + +static void dispc_dump_clocks_channel(struct seq_file *s, enum omap_channel channel) +{ + int lcd, pcd; + enum omap_dss_clk_source lcd_clk_src; + + seq_printf(s, "- %s -\n", mgr_desc[channel].name); + + lcd_clk_src = dss_get_lcd_clk_source(channel); + + seq_printf(s, "%s clk source = %s (%s)\n", mgr_desc[channel].name, + dss_get_generic_clk_source_name(lcd_clk_src), + dss_feat_get_clk_source_name(lcd_clk_src)); + + dispc_mgr_get_lcd_divisor(channel, &lcd, &pcd); + + seq_printf(s, "lck\t\t%-16lulck div\t%u\n", + dispc_mgr_lclk_rate(channel), lcd); + seq_printf(s, "pck\t\t%-16lupck div\t%u\n", + dispc_mgr_pclk_rate(channel), pcd); +} + +void dispc_dump_clocks(struct seq_file *s) +{ + int lcd; + u32 l; + enum omap_dss_clk_source dispc_clk_src = dss_get_dispc_clk_source(); + + if (dispc_runtime_get()) + return; + + seq_printf(s, "- DISPC -\n"); + + seq_printf(s, "dispc fclk source = %s (%s)\n", + dss_get_generic_clk_source_name(dispc_clk_src), + dss_feat_get_clk_source_name(dispc_clk_src)); + + seq_printf(s, "fck\t\t%-16lu\n", dispc_fclk_rate()); + + if (dss_has_feature(FEAT_CORE_CLK_DIV)) { + seq_printf(s, "- DISPC-CORE-CLK -\n"); + l = dispc_read_reg(DISPC_DIVISOR); + lcd = FLD_GET(l, 23, 16); + + seq_printf(s, "lck\t\t%-16lulck div\t%u\n", + (dispc_fclk_rate()/lcd), lcd); + } + + dispc_dump_clocks_channel(s, OMAP_DSS_CHANNEL_LCD); + + if (dss_has_feature(FEAT_MGR_LCD2)) + dispc_dump_clocks_channel(s, OMAP_DSS_CHANNEL_LCD2); + if (dss_has_feature(FEAT_MGR_LCD3)) + dispc_dump_clocks_channel(s, OMAP_DSS_CHANNEL_LCD3); + + dispc_runtime_put(); +} + +static void dispc_dump_regs(struct seq_file *s) +{ + int i, j; + const char *mgr_names[] = { + [OMAP_DSS_CHANNEL_LCD] = "LCD", + [OMAP_DSS_CHANNEL_DIGIT] = "TV", + [OMAP_DSS_CHANNEL_LCD2] = "LCD2", + [OMAP_DSS_CHANNEL_LCD3] = "LCD3", + }; + const char *ovl_names[] = { + [OMAP_DSS_GFX] = "GFX", + [OMAP_DSS_VIDEO1] = "VID1", + [OMAP_DSS_VIDEO2] = "VID2", + [OMAP_DSS_VIDEO3] = "VID3", + }; + const char **p_names; + +#define DUMPREG(r) seq_printf(s, "%-50s %08x\n", #r, dispc_read_reg(r)) + + if (dispc_runtime_get()) + return; + + /* DISPC common registers */ + DUMPREG(DISPC_REVISION); + DUMPREG(DISPC_SYSCONFIG); + DUMPREG(DISPC_SYSSTATUS); + DUMPREG(DISPC_IRQSTATUS); + DUMPREG(DISPC_IRQENABLE); + DUMPREG(DISPC_CONTROL); + DUMPREG(DISPC_CONFIG); + DUMPREG(DISPC_CAPABLE); + DUMPREG(DISPC_LINE_STATUS); + DUMPREG(DISPC_LINE_NUMBER); + if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER) || + dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) + DUMPREG(DISPC_GLOBAL_ALPHA); + if (dss_has_feature(FEAT_MGR_LCD2)) { + DUMPREG(DISPC_CONTROL2); + DUMPREG(DISPC_CONFIG2); + } + if (dss_has_feature(FEAT_MGR_LCD3)) { + DUMPREG(DISPC_CONTROL3); + DUMPREG(DISPC_CONFIG3); + } + if (dss_has_feature(FEAT_MFLAG)) + DUMPREG(DISPC_GLOBAL_MFLAG_ATTRIBUTE); + +#undef DUMPREG + +#define DISPC_REG(i, name) name(i) +#define DUMPREG(i, r) seq_printf(s, "%s(%s)%*s %08x\n", #r, p_names[i], \ + (int)(48 - strlen(#r) - strlen(p_names[i])), " ", \ + dispc_read_reg(DISPC_REG(i, r))) + + p_names = mgr_names; + + /* DISPC channel specific registers */ + for (i = 0; i < dss_feat_get_num_mgrs(); i++) { + DUMPREG(i, DISPC_DEFAULT_COLOR); + DUMPREG(i, DISPC_TRANS_COLOR); + DUMPREG(i, DISPC_SIZE_MGR); + + if (i == OMAP_DSS_CHANNEL_DIGIT) + continue; + + DUMPREG(i, DISPC_DEFAULT_COLOR); + DUMPREG(i, DISPC_TRANS_COLOR); + DUMPREG(i, DISPC_TIMING_H); + DUMPREG(i, DISPC_TIMING_V); + DUMPREG(i, DISPC_POL_FREQ); + DUMPREG(i, DISPC_DIVISORo); + DUMPREG(i, DISPC_SIZE_MGR); + + DUMPREG(i, DISPC_DATA_CYCLE1); + DUMPREG(i, DISPC_DATA_CYCLE2); + DUMPREG(i, DISPC_DATA_CYCLE3); + + if (dss_has_feature(FEAT_CPR)) { + DUMPREG(i, DISPC_CPR_COEF_R); + DUMPREG(i, DISPC_CPR_COEF_G); + DUMPREG(i, DISPC_CPR_COEF_B); + } + } + + p_names = ovl_names; + + for (i = 0; i < dss_feat_get_num_ovls(); i++) { + DUMPREG(i, DISPC_OVL_BA0); + DUMPREG(i, DISPC_OVL_BA1); + DUMPREG(i, DISPC_OVL_POSITION); + DUMPREG(i, DISPC_OVL_SIZE); + DUMPREG(i, DISPC_OVL_ATTRIBUTES); + DUMPREG(i, DISPC_OVL_FIFO_THRESHOLD); + DUMPREG(i, DISPC_OVL_FIFO_SIZE_STATUS); + DUMPREG(i, DISPC_OVL_ROW_INC); + DUMPREG(i, DISPC_OVL_PIXEL_INC); + if (dss_has_feature(FEAT_PRELOAD)) + DUMPREG(i, DISPC_OVL_PRELOAD); + + if (i == OMAP_DSS_GFX) { + DUMPREG(i, DISPC_OVL_WINDOW_SKIP); + DUMPREG(i, DISPC_OVL_TABLE_BA); + continue; + } + + DUMPREG(i, DISPC_OVL_FIR); + DUMPREG(i, DISPC_OVL_PICTURE_SIZE); + DUMPREG(i, DISPC_OVL_ACCU0); + DUMPREG(i, DISPC_OVL_ACCU1); + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + DUMPREG(i, DISPC_OVL_BA0_UV); + DUMPREG(i, DISPC_OVL_BA1_UV); + DUMPREG(i, DISPC_OVL_FIR2); + DUMPREG(i, DISPC_OVL_ACCU2_0); + DUMPREG(i, DISPC_OVL_ACCU2_1); + } + if (dss_has_feature(FEAT_ATTR2)) + DUMPREG(i, DISPC_OVL_ATTRIBUTES2); + if (dss_has_feature(FEAT_PRELOAD)) + DUMPREG(i, DISPC_OVL_PRELOAD); + if (dss_has_feature(FEAT_MFLAG)) + DUMPREG(i, DISPC_OVL_MFLAG_THRESHOLD); + } + +#undef DISPC_REG +#undef DUMPREG + +#define DISPC_REG(plane, name, i) name(plane, i) +#define DUMPREG(plane, name, i) \ + seq_printf(s, "%s_%d(%s)%*s %08x\n", #name, i, p_names[plane], \ + (int)(46 - strlen(#name) - strlen(p_names[plane])), " ", \ + dispc_read_reg(DISPC_REG(plane, name, i))) + + /* Video pipeline coefficient registers */ + + /* start from OMAP_DSS_VIDEO1 */ + for (i = 1; i < dss_feat_get_num_ovls(); i++) { + for (j = 0; j < 8; j++) + DUMPREG(i, DISPC_OVL_FIR_COEF_H, j); + + for (j = 0; j < 8; j++) + DUMPREG(i, DISPC_OVL_FIR_COEF_HV, j); + + for (j = 0; j < 5; j++) + DUMPREG(i, DISPC_OVL_CONV_COEF, j); + + if (dss_has_feature(FEAT_FIR_COEF_V)) { + for (j = 0; j < 8; j++) + DUMPREG(i, DISPC_OVL_FIR_COEF_V, j); + } + + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + for (j = 0; j < 8; j++) + DUMPREG(i, DISPC_OVL_FIR_COEF_H2, j); + + for (j = 0; j < 8; j++) + DUMPREG(i, DISPC_OVL_FIR_COEF_HV2, j); + + for (j = 0; j < 8; j++) + DUMPREG(i, DISPC_OVL_FIR_COEF_V2, j); + } + } + + dispc_runtime_put(); + +#undef DISPC_REG +#undef DUMPREG +} + +/* calculate clock rates using dividers in cinfo */ +int dispc_calc_clock_rates(unsigned long dispc_fclk_rate, + struct dispc_clock_info *cinfo) +{ + if (cinfo->lck_div > 255 || cinfo->lck_div == 0) + return -EINVAL; + if (cinfo->pck_div < 1 || cinfo->pck_div > 255) + return -EINVAL; + + cinfo->lck = dispc_fclk_rate / cinfo->lck_div; + cinfo->pck = cinfo->lck / cinfo->pck_div; + + return 0; +} + +bool dispc_div_calc(unsigned long dispc, + unsigned long pck_min, unsigned long pck_max, + dispc_div_calc_func func, void *data) +{ + int lckd, lckd_start, lckd_stop; + int pckd, pckd_start, pckd_stop; + unsigned long pck, lck; + unsigned long lck_max; + unsigned long pckd_hw_min, pckd_hw_max; + unsigned min_fck_per_pck; + unsigned long fck; + +#ifdef CONFIG_OMAP2_DSS_MIN_FCK_PER_PCK + min_fck_per_pck = CONFIG_OMAP2_DSS_MIN_FCK_PER_PCK; +#else + min_fck_per_pck = 0; +#endif + + pckd_hw_min = dss_feat_get_param_min(FEAT_PARAM_DSS_PCD); + pckd_hw_max = dss_feat_get_param_max(FEAT_PARAM_DSS_PCD); + + lck_max = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + + pck_min = pck_min ? pck_min : 1; + pck_max = pck_max ? pck_max : ULONG_MAX; + + lckd_start = max(DIV_ROUND_UP(dispc, lck_max), 1ul); + lckd_stop = min(dispc / pck_min, 255ul); + + for (lckd = lckd_start; lckd <= lckd_stop; ++lckd) { + lck = dispc / lckd; + + pckd_start = max(DIV_ROUND_UP(lck, pck_max), pckd_hw_min); + pckd_stop = min(lck / pck_min, pckd_hw_max); + + for (pckd = pckd_start; pckd <= pckd_stop; ++pckd) { + pck = lck / pckd; + + /* + * For OMAP2/3 the DISPC fclk is the same as LCD's logic + * clock, which means we're configuring DISPC fclk here + * also. Thus we need to use the calculated lck. For + * OMAP4+ the DISPC fclk is a separate clock. + */ + if (dss_has_feature(FEAT_CORE_CLK_DIV)) + fck = dispc_core_clk_rate(); + else + fck = lck; + + if (fck < pck * min_fck_per_pck) + continue; + + if (func(lckd, pckd, lck, pck, data)) + return true; + } + } + + return false; +} + +void dispc_mgr_set_clock_div(enum omap_channel channel, + const struct dispc_clock_info *cinfo) +{ + DSSDBG("lck = %lu (%u)\n", cinfo->lck, cinfo->lck_div); + DSSDBG("pck = %lu (%u)\n", cinfo->pck, cinfo->pck_div); + + dispc_mgr_set_lcd_divisor(channel, cinfo->lck_div, cinfo->pck_div); +} + +int dispc_mgr_get_clock_div(enum omap_channel channel, + struct dispc_clock_info *cinfo) +{ + unsigned long fck; + + fck = dispc_fclk_rate(); + + cinfo->lck_div = REG_GET(DISPC_DIVISORo(channel), 23, 16); + cinfo->pck_div = REG_GET(DISPC_DIVISORo(channel), 7, 0); + + cinfo->lck = fck / cinfo->lck_div; + cinfo->pck = cinfo->lck / cinfo->pck_div; + + return 0; +} + +u32 dispc_read_irqstatus(void) +{ + return dispc_read_reg(DISPC_IRQSTATUS); +} +EXPORT_SYMBOL(dispc_read_irqstatus); + +void dispc_clear_irqstatus(u32 mask) +{ + dispc_write_reg(DISPC_IRQSTATUS, mask); +} +EXPORT_SYMBOL(dispc_clear_irqstatus); + +u32 dispc_read_irqenable(void) +{ + return dispc_read_reg(DISPC_IRQENABLE); +} +EXPORT_SYMBOL(dispc_read_irqenable); + +void dispc_write_irqenable(u32 mask) +{ + u32 old_mask = dispc_read_reg(DISPC_IRQENABLE); + + /* clear the irqstatus for newly enabled irqs */ + dispc_clear_irqstatus((mask ^ old_mask) & mask); + + dispc_write_reg(DISPC_IRQENABLE, mask); +} +EXPORT_SYMBOL(dispc_write_irqenable); + +void dispc_enable_sidle(void) +{ + REG_FLD_MOD(DISPC_SYSCONFIG, 2, 4, 3); /* SIDLEMODE: smart idle */ +} + +void dispc_disable_sidle(void) +{ + REG_FLD_MOD(DISPC_SYSCONFIG, 1, 4, 3); /* SIDLEMODE: no idle */ +} + +static void _omap_dispc_initial_config(void) +{ + u32 l; + + /* Exclusively enable DISPC_CORE_CLK and set divider to 1 */ + if (dss_has_feature(FEAT_CORE_CLK_DIV)) { + l = dispc_read_reg(DISPC_DIVISOR); + /* Use DISPC_DIVISOR.LCD, instead of DISPC_DIVISOR1.LCD */ + l = FLD_MOD(l, 1, 0, 0); + l = FLD_MOD(l, 1, 23, 16); + dispc_write_reg(DISPC_DIVISOR, l); + + dispc.core_clk_rate = dispc_fclk_rate(); + } + + /* FUNCGATED */ + if (dss_has_feature(FEAT_FUNCGATED)) + REG_FLD_MOD(DISPC_CONFIG, 1, 9, 9); + + dispc_setup_color_conv_coef(); + + dispc_set_loadmode(OMAP_DSS_LOAD_FRAME_ONLY); + + dispc_init_fifos(); + + dispc_configure_burst_sizes(); + + dispc_ovl_enable_zorder_planes(); + + if (dispc.feat->mstandby_workaround) + REG_FLD_MOD(DISPC_MSTANDBY_CTRL, 1, 0, 0); +} + +static const struct dispc_features omap24xx_dispc_feats __initconst = { + .sw_start = 5, + .fp_start = 15, + .bp_start = 27, + .sw_max = 64, + .vp_max = 255, + .hp_max = 256, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .max_lcd_pclk = 66500000, + .calc_scaling = dispc_ovl_calc_scaling_24xx, + .calc_core_clk = calc_core_clk_24xx, + .num_fifos = 3, + .no_framedone_tv = true, + .set_max_preload = false, +}; + +static const struct dispc_features omap34xx_rev1_0_dispc_feats __initconst = { + .sw_start = 5, + .fp_start = 15, + .bp_start = 27, + .sw_max = 64, + .vp_max = 255, + .hp_max = 256, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .max_lcd_pclk = 173000000, + .max_tv_pclk = 59000000, + .calc_scaling = dispc_ovl_calc_scaling_34xx, + .calc_core_clk = calc_core_clk_34xx, + .num_fifos = 3, + .no_framedone_tv = true, + .set_max_preload = false, +}; + +static const struct dispc_features omap34xx_rev3_0_dispc_feats __initconst = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .max_lcd_pclk = 173000000, + .max_tv_pclk = 59000000, + .calc_scaling = dispc_ovl_calc_scaling_34xx, + .calc_core_clk = calc_core_clk_34xx, + .num_fifos = 3, + .no_framedone_tv = true, + .set_max_preload = false, +}; + +static const struct dispc_features omap44xx_dispc_feats __initconst = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .max_lcd_pclk = 170000000, + .max_tv_pclk = 185625000, + .calc_scaling = dispc_ovl_calc_scaling_44xx, + .calc_core_clk = calc_core_clk_44xx, + .num_fifos = 5, + .gfx_fifo_workaround = true, + .set_max_preload = true, +}; + +static const struct dispc_features omap54xx_dispc_feats __initconst = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 11, + .mgr_height_start = 27, + .mgr_width_max = 4096, + .mgr_height_max = 4096, + .max_lcd_pclk = 170000000, + .max_tv_pclk = 186000000, + .calc_scaling = dispc_ovl_calc_scaling_44xx, + .calc_core_clk = calc_core_clk_44xx, + .num_fifos = 5, + .gfx_fifo_workaround = true, + .mstandby_workaround = true, + .set_max_preload = true, +}; + +static int __init dispc_init_features(struct platform_device *pdev) +{ + const struct dispc_features *src; + struct dispc_features *dst; + + dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL); + if (!dst) { + dev_err(&pdev->dev, "Failed to allocate DISPC Features\n"); + return -ENOMEM; + } + + switch (omapdss_get_version()) { + case OMAPDSS_VER_OMAP24xx: + src = &omap24xx_dispc_feats; + break; + + case OMAPDSS_VER_OMAP34xx_ES1: + src = &omap34xx_rev1_0_dispc_feats; + break; + + case OMAPDSS_VER_OMAP34xx_ES3: + case OMAPDSS_VER_OMAP3630: + case OMAPDSS_VER_AM35xx: + src = &omap34xx_rev3_0_dispc_feats; + break; + + case OMAPDSS_VER_OMAP4430_ES1: + case OMAPDSS_VER_OMAP4430_ES2: + case OMAPDSS_VER_OMAP4: + src = &omap44xx_dispc_feats; + break; + + case OMAPDSS_VER_OMAP5: + src = &omap54xx_dispc_feats; + break; + + default: + return -ENODEV; + } + + memcpy(dst, src, sizeof(*dst)); + dispc.feat = dst; + + return 0; +} + +static irqreturn_t dispc_irq_handler(int irq, void *arg) +{ + if (!dispc.is_enabled) + return IRQ_NONE; + + return dispc.user_handler(irq, dispc.user_data); +} + +int dispc_request_irq(irq_handler_t handler, void *dev_id) +{ + int r; + + if (dispc.user_handler != NULL) + return -EBUSY; + + dispc.user_handler = handler; + dispc.user_data = dev_id; + + /* ensure the dispc_irq_handler sees the values above */ + smp_wmb(); + + r = devm_request_irq(&dispc.pdev->dev, dispc.irq, dispc_irq_handler, + IRQF_SHARED, "OMAP DISPC", &dispc); + if (r) { + dispc.user_handler = NULL; + dispc.user_data = NULL; + } + + return r; +} +EXPORT_SYMBOL(dispc_request_irq); + +void dispc_free_irq(void *dev_id) +{ + devm_free_irq(&dispc.pdev->dev, dispc.irq, &dispc); + + dispc.user_handler = NULL; + dispc.user_data = NULL; +} +EXPORT_SYMBOL(dispc_free_irq); + +/* DISPC HW IP initialisation */ +static int __init omap_dispchw_probe(struct platform_device *pdev) +{ + u32 rev; + int r = 0; + struct resource *dispc_mem; + + dispc.pdev = pdev; + + r = dispc_init_features(dispc.pdev); + if (r) + return r; + + dispc_mem = platform_get_resource(dispc.pdev, IORESOURCE_MEM, 0); + if (!dispc_mem) { + DSSERR("can't get IORESOURCE_MEM DISPC\n"); + return -EINVAL; + } + + dispc.base = devm_ioremap(&pdev->dev, dispc_mem->start, + resource_size(dispc_mem)); + if (!dispc.base) { + DSSERR("can't ioremap DISPC\n"); + return -ENOMEM; + } + + dispc.irq = platform_get_irq(dispc.pdev, 0); + if (dispc.irq < 0) { + DSSERR("platform_get_irq failed\n"); + return -ENODEV; + } + + pm_runtime_enable(&pdev->dev); + + r = dispc_runtime_get(); + if (r) + goto err_runtime_get; + + _omap_dispc_initial_config(); + + rev = dispc_read_reg(DISPC_REVISION); + dev_dbg(&pdev->dev, "OMAP DISPC rev %d.%d\n", + FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + + dispc_runtime_put(); + + dss_init_overlay_managers(); + + dss_debugfs_create_file("dispc", dispc_dump_regs); + + return 0; + +err_runtime_get: + pm_runtime_disable(&pdev->dev); + return r; +} + +static int __exit omap_dispchw_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + dss_uninit_overlay_managers(); + + return 0; +} + +static int dispc_runtime_suspend(struct device *dev) +{ + dispc.is_enabled = false; + /* ensure the dispc_irq_handler sees the is_enabled value */ + smp_wmb(); + /* wait for current handler to finish before turning the DISPC off */ + synchronize_irq(dispc.irq); + + dispc_save_context(); + + return 0; +} + +static int dispc_runtime_resume(struct device *dev) +{ + /* + * The reset value for load mode is 0 (OMAP_DSS_LOAD_CLUT_AND_FRAME) + * but we always initialize it to 2 (OMAP_DSS_LOAD_FRAME_ONLY) in + * _omap_dispc_initial_config(). We can thus use it to detect if + * we have lost register context. + */ + if (REG_GET(DISPC_CONFIG, 2, 1) != OMAP_DSS_LOAD_FRAME_ONLY) { + _omap_dispc_initial_config(); + + dispc_restore_context(); + } + + dispc.is_enabled = true; + /* ensure the dispc_irq_handler sees the is_enabled value */ + smp_wmb(); + + return 0; +} + +static const struct dev_pm_ops dispc_pm_ops = { + .runtime_suspend = dispc_runtime_suspend, + .runtime_resume = dispc_runtime_resume, +}; + +static const struct of_device_id dispc_of_match[] = { + { .compatible = "ti,omap2-dispc", }, + { .compatible = "ti,omap3-dispc", }, + { .compatible = "ti,omap4-dispc", }, + {}, +}; + +static struct platform_driver omap_dispchw_driver = { + .remove = __exit_p(omap_dispchw_remove), + .driver = { + .name = "omapdss_dispc", + .owner = THIS_MODULE, + .pm = &dispc_pm_ops, + .of_match_table = dispc_of_match, + }, +}; + +int __init dispc_init_platform_driver(void) +{ + return platform_driver_probe(&omap_dispchw_driver, omap_dispchw_probe); +} + +void __exit dispc_uninit_platform_driver(void) +{ + platform_driver_unregister(&omap_dispchw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/dispc.h b/drivers/video/fbdev/omap2/dss/dispc.h new file mode 100644 index 000000000000..78edb449c763 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc.h @@ -0,0 +1,917 @@ +/* + * linux/drivers/video/omap2/dss/dispc.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Archit Taneja <archit@ti.com> + * + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP2_DISPC_REG_H +#define __OMAP2_DISPC_REG_H + +/* DISPC common registers */ +#define DISPC_REVISION 0x0000 +#define DISPC_SYSCONFIG 0x0010 +#define DISPC_SYSSTATUS 0x0014 +#define DISPC_IRQSTATUS 0x0018 +#define DISPC_IRQENABLE 0x001C +#define DISPC_CONTROL 0x0040 +#define DISPC_CONFIG 0x0044 +#define DISPC_CAPABLE 0x0048 +#define DISPC_LINE_STATUS 0x005C +#define DISPC_LINE_NUMBER 0x0060 +#define DISPC_GLOBAL_ALPHA 0x0074 +#define DISPC_CONTROL2 0x0238 +#define DISPC_CONFIG2 0x0620 +#define DISPC_DIVISOR 0x0804 +#define DISPC_GLOBAL_BUFFER 0x0800 +#define DISPC_CONTROL3 0x0848 +#define DISPC_CONFIG3 0x084C +#define DISPC_MSTANDBY_CTRL 0x0858 +#define DISPC_GLOBAL_MFLAG_ATTRIBUTE 0x085C + +/* DISPC overlay registers */ +#define DISPC_OVL_BA0(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA0_OFFSET(n)) +#define DISPC_OVL_BA1(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA1_OFFSET(n)) +#define DISPC_OVL_BA0_UV(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA0_UV_OFFSET(n)) +#define DISPC_OVL_BA1_UV(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA1_UV_OFFSET(n)) +#define DISPC_OVL_POSITION(n) (DISPC_OVL_BASE(n) + \ + DISPC_POS_OFFSET(n)) +#define DISPC_OVL_SIZE(n) (DISPC_OVL_BASE(n) + \ + DISPC_SIZE_OFFSET(n)) +#define DISPC_OVL_ATTRIBUTES(n) (DISPC_OVL_BASE(n) + \ + DISPC_ATTR_OFFSET(n)) +#define DISPC_OVL_ATTRIBUTES2(n) (DISPC_OVL_BASE(n) + \ + DISPC_ATTR2_OFFSET(n)) +#define DISPC_OVL_FIFO_THRESHOLD(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIFO_THRESH_OFFSET(n)) +#define DISPC_OVL_FIFO_SIZE_STATUS(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIFO_SIZE_STATUS_OFFSET(n)) +#define DISPC_OVL_ROW_INC(n) (DISPC_OVL_BASE(n) + \ + DISPC_ROW_INC_OFFSET(n)) +#define DISPC_OVL_PIXEL_INC(n) (DISPC_OVL_BASE(n) + \ + DISPC_PIX_INC_OFFSET(n)) +#define DISPC_OVL_WINDOW_SKIP(n) (DISPC_OVL_BASE(n) + \ + DISPC_WINDOW_SKIP_OFFSET(n)) +#define DISPC_OVL_TABLE_BA(n) (DISPC_OVL_BASE(n) + \ + DISPC_TABLE_BA_OFFSET(n)) +#define DISPC_OVL_FIR(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_OFFSET(n)) +#define DISPC_OVL_FIR2(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIR2_OFFSET(n)) +#define DISPC_OVL_PICTURE_SIZE(n) (DISPC_OVL_BASE(n) + \ + DISPC_PIC_SIZE_OFFSET(n)) +#define DISPC_OVL_ACCU0(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU0_OFFSET(n)) +#define DISPC_OVL_ACCU1(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU1_OFFSET(n)) +#define DISPC_OVL_ACCU2_0(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU2_0_OFFSET(n)) +#define DISPC_OVL_ACCU2_1(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU2_1_OFFSET(n)) +#define DISPC_OVL_FIR_COEF_H(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_H_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_HV(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_HV_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_H2(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_H2_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_HV2(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_HV2_OFFSET(n, i)) +#define DISPC_OVL_CONV_COEF(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_CONV_COEF_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_V(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_V_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_V2(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_V2_OFFSET(n, i)) +#define DISPC_OVL_PRELOAD(n) (DISPC_OVL_BASE(n) + \ + DISPC_PRELOAD_OFFSET(n)) +#define DISPC_OVL_MFLAG_THRESHOLD(n) (DISPC_OVL_BASE(n) + \ + DISPC_MFLAG_THRESHOLD_OFFSET(n)) + +/* DISPC up/downsampling FIR filter coefficient structure */ +struct dispc_coef { + s8 hc4_vc22; + s8 hc3_vc2; + u8 hc2_vc1; + s8 hc1_vc0; + s8 hc0_vc00; +}; + +const struct dispc_coef *dispc_ovl_get_scale_coef(int inc, int five_taps); + +/* DISPC manager/channel specific registers */ +static inline u16 DISPC_DEFAULT_COLOR(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x004C; + case OMAP_DSS_CHANNEL_DIGIT: + return 0x0050; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03AC; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0814; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TRANS_COLOR(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0054; + case OMAP_DSS_CHANNEL_DIGIT: + return 0x0058; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03B0; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0818; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TIMING_H(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0064; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x0400; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0840; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TIMING_V(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0068; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x0404; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0844; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_POL_FREQ(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x006C; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x0408; + case OMAP_DSS_CHANNEL_LCD3: + return 0x083C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DIVISORo(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0070; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x040C; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0838; + default: + BUG(); + return 0; + } +} + +/* Named as DISPC_SIZE_LCD, DISPC_SIZE_DIGIT and DISPC_SIZE_LCD2 in TRM */ +static inline u16 DISPC_SIZE_MGR(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x007C; + case OMAP_DSS_CHANNEL_DIGIT: + return 0x0078; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03CC; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0834; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DATA_CYCLE1(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x01D4; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03C0; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0828; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DATA_CYCLE2(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x01D8; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03C4; + case OMAP_DSS_CHANNEL_LCD3: + return 0x082C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DATA_CYCLE3(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x01DC; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03C8; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0830; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_CPR_COEF_R(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0220; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03BC; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0824; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_CPR_COEF_G(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0224; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03B8; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0820; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_CPR_COEF_B(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0228; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03B4; + case OMAP_DSS_CHANNEL_LCD3: + return 0x081C; + default: + BUG(); + return 0; + } +} + +/* DISPC overlay register base addresses */ +static inline u16 DISPC_OVL_BASE(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0080; + case OMAP_DSS_VIDEO1: + return 0x00BC; + case OMAP_DSS_VIDEO2: + return 0x014C; + case OMAP_DSS_VIDEO3: + return 0x0300; + case OMAP_DSS_WB: + return 0x0500; + default: + BUG(); + return 0; + } +} + +/* DISPC overlay register offsets */ +static inline u16 DISPC_BA0_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0000; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0008; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_BA1_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0004; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x000C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_BA0_UV_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0544; + case OMAP_DSS_VIDEO2: + return 0x04BC; + case OMAP_DSS_VIDEO3: + return 0x0310; + case OMAP_DSS_WB: + return 0x0118; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_BA1_UV_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0548; + case OMAP_DSS_VIDEO2: + return 0x04C0; + case OMAP_DSS_VIDEO3: + return 0x0314; + case OMAP_DSS_WB: + return 0x011C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_POS_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0008; + case OMAP_DSS_VIDEO3: + return 0x009C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_SIZE_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x000C; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x00A8; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ATTR_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0020; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0010; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0070; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ATTR2_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0568; + case OMAP_DSS_VIDEO2: + return 0x04DC; + case OMAP_DSS_VIDEO3: + return 0x032C; + case OMAP_DSS_WB: + return 0x0310; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIFO_THRESH_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0024; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0014; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x008C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIFO_SIZE_STATUS_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0028; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0018; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0088; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ROW_INC_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x002C; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x001C; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x00A4; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_PIX_INC_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0030; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0020; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0098; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_WINDOW_SKIP_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0034; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + BUG(); + return 0; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TABLE_BA_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0038; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + BUG(); + return 0; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIR_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0024; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0090; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIR2_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0580; + case OMAP_DSS_VIDEO2: + return 0x055C; + case OMAP_DSS_VIDEO3: + return 0x0424; + case OMAP_DSS_WB: + return 0x290; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_PIC_SIZE_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0028; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0094; + default: + BUG(); + return 0; + } +} + + +static inline u16 DISPC_ACCU0_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x002C; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0000; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ACCU2_0_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0584; + case OMAP_DSS_VIDEO2: + return 0x0560; + case OMAP_DSS_VIDEO3: + return 0x0428; + case OMAP_DSS_WB: + return 0x0294; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ACCU1_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0030; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0004; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ACCU2_1_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0588; + case OMAP_DSS_VIDEO2: + return 0x0564; + case OMAP_DSS_VIDEO3: + return 0x042C; + case OMAP_DSS_WB: + return 0x0298; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_H_OFFSET(enum omap_plane plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0034 + i * 0x8; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0010 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_H2_OFFSET(enum omap_plane plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x058C + i * 0x8; + case OMAP_DSS_VIDEO2: + return 0x0568 + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0430 + i * 0x8; + case OMAP_DSS_WB: + return 0x02A0 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_HV_OFFSET(enum omap_plane plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0038 + i * 0x8; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0014 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_HV2_OFFSET(enum omap_plane plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0590 + i * 8; + case OMAP_DSS_VIDEO2: + return 0x056C + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0434 + i * 0x8; + case OMAP_DSS_WB: + return 0x02A4 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4,} */ +static inline u16 DISPC_CONV_COEF_OFFSET(enum omap_plane plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0074 + i * 0x4; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_V_OFFSET(enum omap_plane plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0124 + i * 0x4; + case OMAP_DSS_VIDEO2: + return 0x00B4 + i * 0x4; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0050 + i * 0x4; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_V2_OFFSET(enum omap_plane plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x05CC + i * 0x4; + case OMAP_DSS_VIDEO2: + return 0x05A8 + i * 0x4; + case OMAP_DSS_VIDEO3: + return 0x0470 + i * 0x4; + case OMAP_DSS_WB: + return 0x02E0 + i * 0x4; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_PRELOAD_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x01AC; + case OMAP_DSS_VIDEO1: + return 0x0174; + case OMAP_DSS_VIDEO2: + return 0x00E8; + case OMAP_DSS_VIDEO3: + return 0x00A0; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_MFLAG_THRESHOLD_OFFSET(enum omap_plane plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0860; + case OMAP_DSS_VIDEO1: + return 0x0864; + case OMAP_DSS_VIDEO2: + return 0x0868; + case OMAP_DSS_VIDEO3: + return 0x086c; + default: + BUG(); + return 0; + } +} +#endif diff --git a/drivers/video/fbdev/omap2/dss/dispc_coefs.c b/drivers/video/fbdev/omap2/dss/dispc_coefs.c new file mode 100644 index 000000000000..038c15b04215 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc_coefs.c @@ -0,0 +1,325 @@ +/* + * linux/drivers/video/omap2/dss/dispc_coefs.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Chandrabhanu Mahapatra <cmahapatra@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <video/omapdss.h> + +#include "dispc.h" + +static const struct dispc_coef coef3_M8[8] = { + { 0, 0, 128, 0, 0 }, + { 0, -4, 123, 9, 0 }, + { 0, -4, 108, 24, 0 }, + { 0, -2, 87, 43, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 43, 87, -2, 0 }, + { 0, 24, 108, -4, 0 }, + { 0, 9, 123, -4, 0 }, +}; + +static const struct dispc_coef coef3_M9[8] = { + { 0, 6, 116, 6, 0 }, + { 0, 0, 112, 16, 0 }, + { 0, -2, 100, 30, 0 }, + { 0, -2, 83, 47, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 47, 83, -2, 0 }, + { 0, 30, 100, -2, 0 }, + { 0, 16, 112, 0, 0 }, +}; + +static const struct dispc_coef coef3_M10[8] = { + { 0, 10, 108, 10, 0 }, + { 0, 3, 104, 21, 0 }, + { 0, 0, 94, 34, 0 }, + { 0, -1, 80, 49, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 49, 80, -1, 0 }, + { 0, 34, 94, 0, 0 }, + { 0, 21, 104, 3, 0 }, +}; + +static const struct dispc_coef coef3_M11[8] = { + { 0, 14, 100, 14, 0 }, + { 0, 6, 98, 24, 0 }, + { 0, 2, 90, 36, 0 }, + { 0, 0, 78, 50, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 50, 78, 0, 0 }, + { 0, 36, 90, 2, 0 }, + { 0, 24, 98, 6, 0 }, +}; + +static const struct dispc_coef coef3_M12[8] = { + { 0, 16, 96, 16, 0 }, + { 0, 9, 93, 26, 0 }, + { 0, 4, 86, 38, 0 }, + { 0, 1, 76, 51, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 51, 76, 1, 0 }, + { 0, 38, 86, 4, 0 }, + { 0, 26, 93, 9, 0 }, +}; + +static const struct dispc_coef coef3_M13[8] = { + { 0, 18, 92, 18, 0 }, + { 0, 10, 90, 28, 0 }, + { 0, 5, 83, 40, 0 }, + { 0, 1, 75, 52, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 52, 75, 1, 0 }, + { 0, 40, 83, 5, 0 }, + { 0, 28, 90, 10, 0 }, +}; + +static const struct dispc_coef coef3_M14[8] = { + { 0, 20, 88, 20, 0 }, + { 0, 12, 86, 30, 0 }, + { 0, 6, 81, 41, 0 }, + { 0, 2, 74, 52, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 52, 74, 2, 0 }, + { 0, 41, 81, 6, 0 }, + { 0, 30, 86, 12, 0 }, +}; + +static const struct dispc_coef coef3_M16[8] = { + { 0, 22, 84, 22, 0 }, + { 0, 14, 82, 32, 0 }, + { 0, 8, 78, 42, 0 }, + { 0, 3, 72, 53, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 53, 72, 3, 0 }, + { 0, 42, 78, 8, 0 }, + { 0, 32, 82, 14, 0 }, +}; + +static const struct dispc_coef coef3_M19[8] = { + { 0, 24, 80, 24, 0 }, + { 0, 16, 79, 33, 0 }, + { 0, 9, 76, 43, 0 }, + { 0, 4, 70, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 70, 4, 0 }, + { 0, 43, 76, 9, 0 }, + { 0, 33, 79, 16, 0 }, +}; + +static const struct dispc_coef coef3_M22[8] = { + { 0, 25, 78, 25, 0 }, + { 0, 17, 77, 34, 0 }, + { 0, 10, 74, 44, 0 }, + { 0, 5, 69, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 69, 5, 0 }, + { 0, 44, 74, 10, 0 }, + { 0, 34, 77, 17, 0 }, +}; + +static const struct dispc_coef coef3_M26[8] = { + { 0, 26, 76, 26, 0 }, + { 0, 19, 74, 35, 0 }, + { 0, 11, 72, 45, 0 }, + { 0, 5, 69, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 69, 5, 0 }, + { 0, 45, 72, 11, 0 }, + { 0, 35, 74, 19, 0 }, +}; + +static const struct dispc_coef coef3_M32[8] = { + { 0, 27, 74, 27, 0 }, + { 0, 19, 73, 36, 0 }, + { 0, 12, 71, 45, 0 }, + { 0, 6, 68, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 68, 6, 0 }, + { 0, 45, 71, 12, 0 }, + { 0, 36, 73, 19, 0 }, +}; + +static const struct dispc_coef coef5_M8[8] = { + { 0, 0, 128, 0, 0 }, + { -2, 14, 125, -10, 1 }, + { -6, 33, 114, -15, 2 }, + { -10, 55, 98, -16, 1 }, + { 0, -14, 78, 78, -14 }, + { 1, -16, 98, 55, -10 }, + { 2, -15, 114, 33, -6 }, + { 1, -10, 125, 14, -2 }, +}; + +static const struct dispc_coef coef5_M9[8] = { + { -3, 10, 114, 10, -3 }, + { -6, 24, 111, 0, -1 }, + { -8, 40, 103, -7, 0 }, + { -11, 58, 91, -11, 1 }, + { 0, -12, 76, 76, -12 }, + { 1, -11, 91, 58, -11 }, + { 0, -7, 103, 40, -8 }, + { -1, 0, 111, 24, -6 }, +}; + +static const struct dispc_coef coef5_M10[8] = { + { -4, 18, 100, 18, -4 }, + { -6, 30, 99, 8, -3 }, + { -8, 44, 93, 0, -1 }, + { -9, 58, 84, -5, 0 }, + { 0, -8, 72, 72, -8 }, + { 0, -5, 84, 58, -9 }, + { -1, 0, 93, 44, -8 }, + { -3, 8, 99, 30, -6 }, +}; + +static const struct dispc_coef coef5_M11[8] = { + { -5, 23, 92, 23, -5 }, + { -6, 34, 90, 13, -3 }, + { -6, 45, 85, 6, -2 }, + { -6, 57, 78, 0, -1 }, + { 0, -4, 68, 68, -4 }, + { -1, 0, 78, 57, -6 }, + { -2, 6, 85, 45, -6 }, + { -3, 13, 90, 34, -6 }, +}; + +static const struct dispc_coef coef5_M12[8] = { + { -4, 26, 84, 26, -4 }, + { -5, 36, 82, 18, -3 }, + { -4, 46, 78, 10, -2 }, + { -3, 55, 72, 5, -1 }, + { 0, 0, 64, 64, 0 }, + { -1, 5, 72, 55, -3 }, + { -2, 10, 78, 46, -4 }, + { -3, 18, 82, 36, -5 }, +}; + +static const struct dispc_coef coef5_M13[8] = { + { -3, 28, 78, 28, -3 }, + { -3, 37, 76, 21, -3 }, + { -2, 45, 73, 14, -2 }, + { 0, 53, 68, 8, -1 }, + { 0, 3, 61, 61, 3 }, + { -1, 8, 68, 53, 0 }, + { -2, 14, 73, 45, -2 }, + { -3, 21, 76, 37, -3 }, +}; + +static const struct dispc_coef coef5_M14[8] = { + { -2, 30, 72, 30, -2 }, + { -1, 37, 71, 23, -2 }, + { 0, 45, 69, 16, -2 }, + { 3, 52, 64, 10, -1 }, + { 0, 6, 58, 58, 6 }, + { -1, 10, 64, 52, 3 }, + { -2, 16, 69, 45, 0 }, + { -2, 23, 71, 37, -1 }, +}; + +static const struct dispc_coef coef5_M16[8] = { + { 0, 31, 66, 31, 0 }, + { 1, 38, 65, 25, -1 }, + { 3, 44, 62, 20, -1 }, + { 6, 49, 59, 14, 0 }, + { 0, 10, 54, 54, 10 }, + { 0, 14, 59, 49, 6 }, + { -1, 20, 62, 44, 3 }, + { -1, 25, 65, 38, 1 }, +}; + +static const struct dispc_coef coef5_M19[8] = { + { 3, 32, 58, 32, 3 }, + { 4, 38, 58, 27, 1 }, + { 7, 42, 55, 23, 1 }, + { 10, 46, 54, 18, 0 }, + { 0, 14, 50, 50, 14 }, + { 0, 18, 54, 46, 10 }, + { 1, 23, 55, 42, 7 }, + { 1, 27, 58, 38, 4 }, +}; + +static const struct dispc_coef coef5_M22[8] = { + { 4, 33, 54, 33, 4 }, + { 6, 37, 54, 28, 3 }, + { 9, 41, 53, 24, 1 }, + { 12, 45, 51, 20, 0 }, + { 0, 16, 48, 48, 16 }, + { 0, 20, 51, 45, 12 }, + { 1, 24, 53, 41, 9 }, + { 3, 28, 54, 37, 6 }, +}; + +static const struct dispc_coef coef5_M26[8] = { + { 6, 33, 50, 33, 6 }, + { 8, 36, 51, 29, 4 }, + { 11, 40, 50, 25, 2 }, + { 14, 43, 48, 22, 1 }, + { 0, 18, 46, 46, 18 }, + { 1, 22, 48, 43, 14 }, + { 2, 25, 50, 40, 11 }, + { 4, 29, 51, 36, 8 }, +}; + +static const struct dispc_coef coef5_M32[8] = { + { 7, 33, 48, 33, 7 }, + { 10, 36, 48, 29, 5 }, + { 13, 39, 47, 26, 3 }, + { 16, 42, 46, 23, 1 }, + { 0, 19, 45, 45, 19 }, + { 1, 23, 46, 42, 16 }, + { 3, 26, 47, 39, 13 }, + { 5, 29, 48, 36, 10 }, +}; + +const struct dispc_coef *dispc_ovl_get_scale_coef(int inc, int five_taps) +{ + int i; + static const struct { + int Mmin; + int Mmax; + const struct dispc_coef *coef_3; + const struct dispc_coef *coef_5; + } coefs[] = { + { 27, 32, coef3_M32, coef5_M32 }, + { 23, 26, coef3_M26, coef5_M26 }, + { 20, 22, coef3_M22, coef5_M22 }, + { 17, 19, coef3_M19, coef5_M19 }, + { 15, 16, coef3_M16, coef5_M16 }, + { 14, 14, coef3_M14, coef5_M14 }, + { 13, 13, coef3_M13, coef5_M13 }, + { 12, 12, coef3_M12, coef5_M12 }, + { 11, 11, coef3_M11, coef5_M11 }, + { 10, 10, coef3_M10, coef5_M10 }, + { 9, 9, coef3_M9, coef5_M9 }, + { 4, 8, coef3_M8, coef5_M8 }, + /* + * When upscaling more than two times, blockiness and outlines + * around the image are observed when M8 tables are used. M11, + * M16 and M19 tables are used to prevent this. + */ + { 3, 3, coef3_M11, coef5_M11 }, + { 2, 2, coef3_M16, coef5_M16 }, + { 0, 1, coef3_M19, coef5_M19 }, + }; + + inc /= 128; + for (i = 0; i < ARRAY_SIZE(coefs); ++i) + if (inc >= coefs[i].Mmin && inc <= coefs[i].Mmax) + return five_taps ? coefs[i].coef_5 : coefs[i].coef_3; + return NULL; +} diff --git a/drivers/video/fbdev/omap2/dss/display-sysfs.c b/drivers/video/fbdev/omap2/dss/display-sysfs.c new file mode 100644 index 000000000000..5a2095a98ed8 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/display-sysfs.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "DISPLAY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> + +#include <video/omapdss.h> +#include "dss.h" + +static struct omap_dss_device *to_dss_device_sysfs(struct device *dev) +{ + struct omap_dss_device *dssdev = NULL; + + for_each_dss_dev(dssdev) { + if (dssdev->dev == dev) { + omap_dss_put_device(dssdev); + return dssdev; + } + } + + return NULL; +} + +static ssize_t display_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + dssdev->name ? + dssdev->name : ""); +} + +static ssize_t display_enabled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", + omapdss_device_is_enabled(dssdev)); +} + +static ssize_t display_enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + int r; + bool enable; + + r = strtobool(buf, &enable); + if (r) + return r; + + if (enable == omapdss_device_is_enabled(dssdev)) + return size; + + if (omapdss_device_is_connected(dssdev) == false) + return -ENODEV; + + if (enable) { + r = dssdev->driver->enable(dssdev); + if (r) + return r; + } else { + dssdev->driver->disable(dssdev); + } + + return size; +} + +static ssize_t display_tear_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + return snprintf(buf, PAGE_SIZE, "%d\n", + dssdev->driver->get_te ? + dssdev->driver->get_te(dssdev) : 0); +} + +static ssize_t display_tear_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + int r; + bool te; + + if (!dssdev->driver->enable_te || !dssdev->driver->get_te) + return -ENOENT; + + r = strtobool(buf, &te); + if (r) + return r; + + r = dssdev->driver->enable_te(dssdev, te); + if (r) + return r; + + return size; +} + +static ssize_t display_timings_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + struct omap_video_timings t; + + if (!dssdev->driver->get_timings) + return -ENOENT; + + dssdev->driver->get_timings(dssdev, &t); + + return snprintf(buf, PAGE_SIZE, "%u,%u/%u/%u/%u,%u/%u/%u/%u\n", + t.pixelclock, + t.x_res, t.hfp, t.hbp, t.hsw, + t.y_res, t.vfp, t.vbp, t.vsw); +} + +static ssize_t display_timings_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + struct omap_video_timings t = dssdev->panel.timings; + int r, found; + + if (!dssdev->driver->set_timings || !dssdev->driver->check_timings) + return -ENOENT; + + found = 0; +#ifdef CONFIG_OMAP2_DSS_VENC + if (strncmp("pal", buf, 3) == 0) { + t = omap_dss_pal_timings; + found = 1; + } else if (strncmp("ntsc", buf, 4) == 0) { + t = omap_dss_ntsc_timings; + found = 1; + } +#endif + if (!found && sscanf(buf, "%u,%hu/%hu/%hu/%hu,%hu/%hu/%hu/%hu", + &t.pixelclock, + &t.x_res, &t.hfp, &t.hbp, &t.hsw, + &t.y_res, &t.vfp, &t.vbp, &t.vsw) != 9) + return -EINVAL; + + r = dssdev->driver->check_timings(dssdev, &t); + if (r) + return r; + + dssdev->driver->disable(dssdev); + dssdev->driver->set_timings(dssdev, &t); + r = dssdev->driver->enable(dssdev); + if (r) + return r; + + return size; +} + +static ssize_t display_rotate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + int rotate; + if (!dssdev->driver->get_rotate) + return -ENOENT; + rotate = dssdev->driver->get_rotate(dssdev); + return snprintf(buf, PAGE_SIZE, "%u\n", rotate); +} + +static ssize_t display_rotate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + int rot, r; + + if (!dssdev->driver->set_rotate || !dssdev->driver->get_rotate) + return -ENOENT; + + r = kstrtoint(buf, 0, &rot); + if (r) + return r; + + r = dssdev->driver->set_rotate(dssdev, rot); + if (r) + return r; + + return size; +} + +static ssize_t display_mirror_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + int mirror; + if (!dssdev->driver->get_mirror) + return -ENOENT; + mirror = dssdev->driver->get_mirror(dssdev); + return snprintf(buf, PAGE_SIZE, "%u\n", mirror); +} + +static ssize_t display_mirror_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + int r; + bool mirror; + + if (!dssdev->driver->set_mirror || !dssdev->driver->get_mirror) + return -ENOENT; + + r = strtobool(buf, &mirror); + if (r) + return r; + + r = dssdev->driver->set_mirror(dssdev, mirror); + if (r) + return r; + + return size; +} + +static ssize_t display_wss_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + unsigned int wss; + + if (!dssdev->driver->get_wss) + return -ENOENT; + + wss = dssdev->driver->get_wss(dssdev); + + return snprintf(buf, PAGE_SIZE, "0x%05x\n", wss); +} + +static ssize_t display_wss_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + u32 wss; + int r; + + if (!dssdev->driver->get_wss || !dssdev->driver->set_wss) + return -ENOENT; + + r = kstrtou32(buf, 0, &wss); + if (r) + return r; + + if (wss > 0xfffff) + return -EINVAL; + + r = dssdev->driver->set_wss(dssdev, wss); + if (r) + return r; + + return size; +} + +static DEVICE_ATTR(display_name, S_IRUGO, display_name_show, NULL); +static DEVICE_ATTR(enabled, S_IRUGO|S_IWUSR, + display_enabled_show, display_enabled_store); +static DEVICE_ATTR(tear_elim, S_IRUGO|S_IWUSR, + display_tear_show, display_tear_store); +static DEVICE_ATTR(timings, S_IRUGO|S_IWUSR, + display_timings_show, display_timings_store); +static DEVICE_ATTR(rotate, S_IRUGO|S_IWUSR, + display_rotate_show, display_rotate_store); +static DEVICE_ATTR(mirror, S_IRUGO|S_IWUSR, + display_mirror_show, display_mirror_store); +static DEVICE_ATTR(wss, S_IRUGO|S_IWUSR, + display_wss_show, display_wss_store); + +static const struct attribute *display_sysfs_attrs[] = { + &dev_attr_display_name.attr, + &dev_attr_enabled.attr, + &dev_attr_tear_elim.attr, + &dev_attr_timings.attr, + &dev_attr_rotate.attr, + &dev_attr_mirror.attr, + &dev_attr_wss.attr, + NULL +}; + +int display_init_sysfs(struct platform_device *pdev) +{ + struct omap_dss_device *dssdev = NULL; + int r; + + for_each_dss_dev(dssdev) { + struct kobject *kobj = &dssdev->dev->kobj; + + r = sysfs_create_files(kobj, display_sysfs_attrs); + if (r) { + DSSERR("failed to create sysfs files\n"); + goto err; + } + + r = sysfs_create_link(&pdev->dev.kobj, kobj, dssdev->alias); + if (r) { + sysfs_remove_files(kobj, display_sysfs_attrs); + + DSSERR("failed to create sysfs display link\n"); + goto err; + } + } + + return 0; + +err: + display_uninit_sysfs(pdev); + + return r; +} + +void display_uninit_sysfs(struct platform_device *pdev) +{ + struct omap_dss_device *dssdev = NULL; + + for_each_dss_dev(dssdev) { + sysfs_remove_link(&pdev->dev.kobj, dssdev->alias); + sysfs_remove_files(&dssdev->dev->kobj, + display_sysfs_attrs); + } +} diff --git a/drivers/video/fbdev/omap2/dss/display.c b/drivers/video/fbdev/omap2/dss/display.c new file mode 100644 index 000000000000..2412a0dd0c13 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/display.c @@ -0,0 +1,338 @@ +/* + * linux/drivers/video/omap2/dss/display.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "DISPLAY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <video/omapdss.h> +#include "dss.h" +#include "dss_features.h" + +void omapdss_default_get_resolution(struct omap_dss_device *dssdev, + u16 *xres, u16 *yres) +{ + *xres = dssdev->panel.timings.x_res; + *yres = dssdev->panel.timings.y_res; +} +EXPORT_SYMBOL(omapdss_default_get_resolution); + +int omapdss_default_get_recommended_bpp(struct omap_dss_device *dssdev) +{ + switch (dssdev->type) { + case OMAP_DISPLAY_TYPE_DPI: + if (dssdev->phy.dpi.data_lines == 24) + return 24; + else + return 16; + + case OMAP_DISPLAY_TYPE_DBI: + if (dssdev->ctrl.pixel_size == 24) + return 24; + else + return 16; + case OMAP_DISPLAY_TYPE_DSI: + if (dsi_get_pixel_size(dssdev->panel.dsi_pix_fmt) > 16) + return 24; + else + return 16; + case OMAP_DISPLAY_TYPE_VENC: + case OMAP_DISPLAY_TYPE_SDI: + case OMAP_DISPLAY_TYPE_HDMI: + case OMAP_DISPLAY_TYPE_DVI: + return 24; + default: + BUG(); + return 0; + } +} +EXPORT_SYMBOL(omapdss_default_get_recommended_bpp); + +void omapdss_default_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + *timings = dssdev->panel.timings; +} +EXPORT_SYMBOL(omapdss_default_get_timings); + +int dss_suspend_all_devices(void) +{ + struct omap_dss_device *dssdev = NULL; + + for_each_dss_dev(dssdev) { + if (!dssdev->driver) + continue; + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + dssdev->driver->disable(dssdev); + dssdev->activate_after_resume = true; + } else { + dssdev->activate_after_resume = false; + } + } + + return 0; +} + +int dss_resume_all_devices(void) +{ + struct omap_dss_device *dssdev = NULL; + + for_each_dss_dev(dssdev) { + if (!dssdev->driver) + continue; + + if (dssdev->activate_after_resume) { + dssdev->driver->enable(dssdev); + dssdev->activate_after_resume = false; + } + } + + return 0; +} + +void dss_disable_all_devices(void) +{ + struct omap_dss_device *dssdev = NULL; + + for_each_dss_dev(dssdev) { + if (!dssdev->driver) + continue; + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + dssdev->driver->disable(dssdev); + } +} + +static LIST_HEAD(panel_list); +static DEFINE_MUTEX(panel_list_mutex); +static int disp_num_counter; + +int omapdss_register_display(struct omap_dss_device *dssdev) +{ + struct omap_dss_driver *drv = dssdev->driver; + int id; + + /* + * Note: this presumes all the displays are either using DT or non-DT, + * which normally should be the case. This also presumes that all + * displays either have an DT alias, or none has. + */ + + if (dssdev->dev->of_node) { + id = of_alias_get_id(dssdev->dev->of_node, "display"); + + if (id < 0) + id = disp_num_counter++; + } else { + id = disp_num_counter++; + } + + snprintf(dssdev->alias, sizeof(dssdev->alias), "display%d", id); + + /* Use 'label' property for name, if it exists */ + if (dssdev->dev->of_node) + of_property_read_string(dssdev->dev->of_node, "label", + &dssdev->name); + + if (dssdev->name == NULL) + dssdev->name = dssdev->alias; + + if (drv && drv->get_resolution == NULL) + drv->get_resolution = omapdss_default_get_resolution; + if (drv && drv->get_recommended_bpp == NULL) + drv->get_recommended_bpp = omapdss_default_get_recommended_bpp; + if (drv && drv->get_timings == NULL) + drv->get_timings = omapdss_default_get_timings; + + mutex_lock(&panel_list_mutex); + list_add_tail(&dssdev->panel_list, &panel_list); + mutex_unlock(&panel_list_mutex); + return 0; +} +EXPORT_SYMBOL(omapdss_register_display); + +void omapdss_unregister_display(struct omap_dss_device *dssdev) +{ + mutex_lock(&panel_list_mutex); + list_del(&dssdev->panel_list); + mutex_unlock(&panel_list_mutex); +} +EXPORT_SYMBOL(omapdss_unregister_display); + +struct omap_dss_device *omap_dss_get_device(struct omap_dss_device *dssdev) +{ + if (!try_module_get(dssdev->owner)) + return NULL; + + if (get_device(dssdev->dev) == NULL) { + module_put(dssdev->owner); + return NULL; + } + + return dssdev; +} +EXPORT_SYMBOL(omap_dss_get_device); + +void omap_dss_put_device(struct omap_dss_device *dssdev) +{ + put_device(dssdev->dev); + module_put(dssdev->owner); +} +EXPORT_SYMBOL(omap_dss_put_device); + +/* + * ref count of the found device is incremented. + * ref count of from-device is decremented. + */ +struct omap_dss_device *omap_dss_get_next_device(struct omap_dss_device *from) +{ + struct list_head *l; + struct omap_dss_device *dssdev; + + mutex_lock(&panel_list_mutex); + + if (list_empty(&panel_list)) { + dssdev = NULL; + goto out; + } + + if (from == NULL) { + dssdev = list_first_entry(&panel_list, struct omap_dss_device, + panel_list); + omap_dss_get_device(dssdev); + goto out; + } + + omap_dss_put_device(from); + + list_for_each(l, &panel_list) { + dssdev = list_entry(l, struct omap_dss_device, panel_list); + if (dssdev == from) { + if (list_is_last(l, &panel_list)) { + dssdev = NULL; + goto out; + } + + dssdev = list_entry(l->next, struct omap_dss_device, + panel_list); + omap_dss_get_device(dssdev); + goto out; + } + } + + WARN(1, "'from' dssdev not found\n"); + + dssdev = NULL; +out: + mutex_unlock(&panel_list_mutex); + return dssdev; +} +EXPORT_SYMBOL(omap_dss_get_next_device); + +struct omap_dss_device *omap_dss_find_device(void *data, + int (*match)(struct omap_dss_device *dssdev, void *data)) +{ + struct omap_dss_device *dssdev = NULL; + + while ((dssdev = omap_dss_get_next_device(dssdev)) != NULL) { + if (match(dssdev, data)) + return dssdev; + } + + return NULL; +} +EXPORT_SYMBOL(omap_dss_find_device); + +void videomode_to_omap_video_timings(const struct videomode *vm, + struct omap_video_timings *ovt) +{ + memset(ovt, 0, sizeof(*ovt)); + + ovt->pixelclock = vm->pixelclock; + ovt->x_res = vm->hactive; + ovt->hbp = vm->hback_porch; + ovt->hfp = vm->hfront_porch; + ovt->hsw = vm->hsync_len; + ovt->y_res = vm->vactive; + ovt->vbp = vm->vback_porch; + ovt->vfp = vm->vfront_porch; + ovt->vsw = vm->vsync_len; + + ovt->vsync_level = vm->flags & DISPLAY_FLAGS_VSYNC_HIGH ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + ovt->hsync_level = vm->flags & DISPLAY_FLAGS_HSYNC_HIGH ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + ovt->de_level = vm->flags & DISPLAY_FLAGS_DE_HIGH ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + ovt->data_pclk_edge = vm->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE ? + OMAPDSS_DRIVE_SIG_RISING_EDGE : + OMAPDSS_DRIVE_SIG_FALLING_EDGE; + + ovt->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; +} +EXPORT_SYMBOL(videomode_to_omap_video_timings); + +void omap_video_timings_to_videomode(const struct omap_video_timings *ovt, + struct videomode *vm) +{ + memset(vm, 0, sizeof(*vm)); + + vm->pixelclock = ovt->pixelclock; + + vm->hactive = ovt->x_res; + vm->hback_porch = ovt->hbp; + vm->hfront_porch = ovt->hfp; + vm->hsync_len = ovt->hsw; + vm->vactive = ovt->y_res; + vm->vback_porch = ovt->vbp; + vm->vfront_porch = ovt->vfp; + vm->vsync_len = ovt->vsw; + + if (ovt->hsync_level == OMAPDSS_SIG_ACTIVE_HIGH) + vm->flags |= DISPLAY_FLAGS_HSYNC_HIGH; + else + vm->flags |= DISPLAY_FLAGS_HSYNC_LOW; + + if (ovt->vsync_level == OMAPDSS_SIG_ACTIVE_HIGH) + vm->flags |= DISPLAY_FLAGS_VSYNC_HIGH; + else + vm->flags |= DISPLAY_FLAGS_VSYNC_LOW; + + if (ovt->de_level == OMAPDSS_SIG_ACTIVE_HIGH) + vm->flags |= DISPLAY_FLAGS_DE_HIGH; + else + vm->flags |= DISPLAY_FLAGS_DE_LOW; + + if (ovt->data_pclk_edge == OMAPDSS_DRIVE_SIG_RISING_EDGE) + vm->flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE; + else + vm->flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE; +} +EXPORT_SYMBOL(omap_video_timings_to_videomode); diff --git a/drivers/video/fbdev/omap2/dss/dpi.c b/drivers/video/fbdev/omap2/dss/dpi.c new file mode 100644 index 000000000000..157921db447a --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dpi.c @@ -0,0 +1,774 @@ +/* + * linux/drivers/video/omap2/dss/dpi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "DPI" + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/string.h> +#include <linux/of.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static struct { + struct platform_device *pdev; + + struct regulator *vdds_dsi_reg; + struct platform_device *dsidev; + + struct mutex lock; + + struct omap_video_timings timings; + struct dss_lcd_mgr_config mgr_config; + int data_lines; + + struct omap_dss_device output; + + bool port_initialized; +} dpi; + +static struct platform_device *dpi_get_dsidev(enum omap_channel channel) +{ + /* + * XXX we can't currently use DSI PLL for DPI with OMAP3, as the DSI PLL + * would also be used for DISPC fclk. Meaning, when the DPI output is + * disabled, DISPC clock will be disabled, and TV out will stop. + */ + switch (omapdss_get_version()) { + case OMAPDSS_VER_OMAP24xx: + case OMAPDSS_VER_OMAP34xx_ES1: + case OMAPDSS_VER_OMAP34xx_ES3: + case OMAPDSS_VER_OMAP3630: + case OMAPDSS_VER_AM35xx: + return NULL; + + case OMAPDSS_VER_OMAP4430_ES1: + case OMAPDSS_VER_OMAP4430_ES2: + case OMAPDSS_VER_OMAP4: + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return dsi_get_dsidev_from_id(0); + case OMAP_DSS_CHANNEL_LCD2: + return dsi_get_dsidev_from_id(1); + default: + return NULL; + } + + case OMAPDSS_VER_OMAP5: + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return dsi_get_dsidev_from_id(0); + case OMAP_DSS_CHANNEL_LCD3: + return dsi_get_dsidev_from_id(1); + default: + return NULL; + } + + default: + return NULL; + } +} + +static enum omap_dss_clk_source dpi_get_alt_clk_src(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC; + case OMAP_DSS_CHANNEL_LCD2: + return OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC; + default: + /* this shouldn't happen */ + WARN_ON(1); + return OMAP_DSS_CLK_SRC_FCK; + } +} + +struct dpi_clk_calc_ctx { + struct platform_device *dsidev; + + /* inputs */ + + unsigned long pck_min, pck_max; + + /* outputs */ + + struct dsi_clock_info dsi_cinfo; + unsigned long fck; + struct dispc_clock_info dispc_cinfo; +}; + +static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, + unsigned long pck, void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + /* + * Odd dividers give us uneven duty cycle, causing problem when level + * shifted. So skip all odd dividers when the pixel clock is on the + * higher side. + */ + if (ctx->pck_min >= 100000000) { + if (lckd > 1 && lckd % 2 != 0) + return false; + + if (pckd > 1 && pckd % 2 != 0) + return false; + } + + ctx->dispc_cinfo.lck_div = lckd; + ctx->dispc_cinfo.pck_div = pckd; + ctx->dispc_cinfo.lck = lck; + ctx->dispc_cinfo.pck = pck; + + return true; +} + + +static bool dpi_calc_hsdiv_cb(int regm_dispc, unsigned long dispc, + void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + /* + * Odd dividers give us uneven duty cycle, causing problem when level + * shifted. So skip all odd dividers when the pixel clock is on the + * higher side. + */ + if (regm_dispc > 1 && regm_dispc % 2 != 0 && ctx->pck_min >= 100000000) + return false; + + ctx->dsi_cinfo.regm_dispc = regm_dispc; + ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc; + + return dispc_div_calc(dispc, ctx->pck_min, ctx->pck_max, + dpi_calc_dispc_cb, ctx); +} + + +static bool dpi_calc_pll_cb(int regn, int regm, unsigned long fint, + unsigned long pll, + void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + ctx->dsi_cinfo.regn = regn; + ctx->dsi_cinfo.regm = regm; + ctx->dsi_cinfo.fint = fint; + ctx->dsi_cinfo.clkin4ddr = pll; + + return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->pck_min, + dpi_calc_hsdiv_cb, ctx); +} + +static bool dpi_calc_dss_cb(unsigned long fck, void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + ctx->fck = fck; + + return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max, + dpi_calc_dispc_cb, ctx); +} + +static bool dpi_dsi_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx) +{ + unsigned long clkin; + unsigned long pll_min, pll_max; + + clkin = dsi_get_pll_clkin(dpi.dsidev); + + memset(ctx, 0, sizeof(*ctx)); + ctx->dsidev = dpi.dsidev; + ctx->pck_min = pck - 1000; + ctx->pck_max = pck + 1000; + ctx->dsi_cinfo.clkin = clkin; + + pll_min = 0; + pll_max = 0; + + return dsi_pll_calc(dpi.dsidev, clkin, + pll_min, pll_max, + dpi_calc_pll_cb, ctx); +} + +static bool dpi_dss_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx) +{ + int i; + + /* + * DSS fck gives us very few possibilities, so finding a good pixel + * clock may not be possible. We try multiple times to find the clock, + * each time widening the pixel clock range we look for, up to + * +/- ~15MHz. + */ + + for (i = 0; i < 25; ++i) { + bool ok; + + memset(ctx, 0, sizeof(*ctx)); + if (pck > 1000 * i * i * i) + ctx->pck_min = max(pck - 1000 * i * i * i, 0lu); + else + ctx->pck_min = 0; + ctx->pck_max = pck + 1000 * i * i * i; + + ok = dss_div_calc(pck, ctx->pck_min, dpi_calc_dss_cb, ctx); + if (ok) + return ok; + } + + return false; +} + + + +static int dpi_set_dsi_clk(enum omap_channel channel, + unsigned long pck_req, unsigned long *fck, int *lck_div, + int *pck_div) +{ + struct dpi_clk_calc_ctx ctx; + int r; + bool ok; + + ok = dpi_dsi_clk_calc(pck_req, &ctx); + if (!ok) + return -EINVAL; + + r = dsi_pll_set_clock_div(dpi.dsidev, &ctx.dsi_cinfo); + if (r) + return r; + + dss_select_lcd_clk_source(channel, + dpi_get_alt_clk_src(channel)); + + dpi.mgr_config.clock_info = ctx.dispc_cinfo; + + *fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk; + *lck_div = ctx.dispc_cinfo.lck_div; + *pck_div = ctx.dispc_cinfo.pck_div; + + return 0; +} + +static int dpi_set_dispc_clk(unsigned long pck_req, unsigned long *fck, + int *lck_div, int *pck_div) +{ + struct dpi_clk_calc_ctx ctx; + int r; + bool ok; + + ok = dpi_dss_clk_calc(pck_req, &ctx); + if (!ok) + return -EINVAL; + + r = dss_set_fck_rate(ctx.fck); + if (r) + return r; + + dpi.mgr_config.clock_info = ctx.dispc_cinfo; + + *fck = ctx.fck; + *lck_div = ctx.dispc_cinfo.lck_div; + *pck_div = ctx.dispc_cinfo.pck_div; + + return 0; +} + +static int dpi_set_mode(struct omap_overlay_manager *mgr) +{ + struct omap_video_timings *t = &dpi.timings; + int lck_div = 0, pck_div = 0; + unsigned long fck = 0; + unsigned long pck; + int r = 0; + + if (dpi.dsidev) + r = dpi_set_dsi_clk(mgr->id, t->pixelclock, &fck, + &lck_div, &pck_div); + else + r = dpi_set_dispc_clk(t->pixelclock, &fck, + &lck_div, &pck_div); + if (r) + return r; + + pck = fck / lck_div / pck_div; + + if (pck != t->pixelclock) { + DSSWARN("Could not find exact pixel clock. Requested %d Hz, got %lu Hz\n", + t->pixelclock, pck); + + t->pixelclock = pck; + } + + dss_mgr_set_timings(mgr, t); + + return 0; +} + +static void dpi_config_lcd_manager(struct omap_overlay_manager *mgr) +{ + dpi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; + + dpi.mgr_config.stallmode = false; + dpi.mgr_config.fifohandcheck = false; + + dpi.mgr_config.video_port_width = dpi.data_lines; + + dpi.mgr_config.lcden_sig_polarity = 0; + + dss_mgr_set_lcd_config(mgr, &dpi.mgr_config); +} + +static int dpi_display_enable(struct omap_dss_device *dssdev) +{ + struct omap_dss_device *out = &dpi.output; + int r; + + mutex_lock(&dpi.lock); + + if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI) && !dpi.vdds_dsi_reg) { + DSSERR("no VDSS_DSI regulator\n"); + r = -ENODEV; + goto err_no_reg; + } + + if (out == NULL || out->manager == NULL) { + DSSERR("failed to enable display: no output/manager\n"); + r = -ENODEV; + goto err_no_out_mgr; + } + + if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) { + r = regulator_enable(dpi.vdds_dsi_reg); + if (r) + goto err_reg_enable; + } + + r = dispc_runtime_get(); + if (r) + goto err_get_dispc; + + r = dss_dpi_select_source(out->manager->id); + if (r) + goto err_src_sel; + + if (dpi.dsidev) { + r = dsi_runtime_get(dpi.dsidev); + if (r) + goto err_get_dsi; + + r = dsi_pll_init(dpi.dsidev, 0, 1); + if (r) + goto err_dsi_pll_init; + } + + r = dpi_set_mode(out->manager); + if (r) + goto err_set_mode; + + dpi_config_lcd_manager(out->manager); + + mdelay(2); + + r = dss_mgr_enable(out->manager); + if (r) + goto err_mgr_enable; + + mutex_unlock(&dpi.lock); + + return 0; + +err_mgr_enable: +err_set_mode: + if (dpi.dsidev) + dsi_pll_uninit(dpi.dsidev, true); +err_dsi_pll_init: + if (dpi.dsidev) + dsi_runtime_put(dpi.dsidev); +err_get_dsi: +err_src_sel: + dispc_runtime_put(); +err_get_dispc: + if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) + regulator_disable(dpi.vdds_dsi_reg); +err_reg_enable: +err_no_out_mgr: +err_no_reg: + mutex_unlock(&dpi.lock); + return r; +} + +static void dpi_display_disable(struct omap_dss_device *dssdev) +{ + struct omap_overlay_manager *mgr = dpi.output.manager; + + mutex_lock(&dpi.lock); + + dss_mgr_disable(mgr); + + if (dpi.dsidev) { + dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); + dsi_pll_uninit(dpi.dsidev, true); + dsi_runtime_put(dpi.dsidev); + } + + dispc_runtime_put(); + + if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) + regulator_disable(dpi.vdds_dsi_reg); + + mutex_unlock(&dpi.lock); +} + +static void dpi_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + DSSDBG("dpi_set_timings\n"); + + mutex_lock(&dpi.lock); + + dpi.timings = *timings; + + mutex_unlock(&dpi.lock); +} + +static void dpi_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + mutex_lock(&dpi.lock); + + *timings = dpi.timings; + + mutex_unlock(&dpi.lock); +} + +static int dpi_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct omap_overlay_manager *mgr = dpi.output.manager; + int lck_div, pck_div; + unsigned long fck; + unsigned long pck; + struct dpi_clk_calc_ctx ctx; + bool ok; + + if (mgr && !dispc_mgr_timings_ok(mgr->id, timings)) + return -EINVAL; + + if (timings->pixelclock == 0) + return -EINVAL; + + if (dpi.dsidev) { + ok = dpi_dsi_clk_calc(timings->pixelclock, &ctx); + if (!ok) + return -EINVAL; + + fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk; + } else { + ok = dpi_dss_clk_calc(timings->pixelclock, &ctx); + if (!ok) + return -EINVAL; + + fck = ctx.fck; + } + + lck_div = ctx.dispc_cinfo.lck_div; + pck_div = ctx.dispc_cinfo.pck_div; + + pck = fck / lck_div / pck_div; + + timings->pixelclock = pck; + + return 0; +} + +static void dpi_set_data_lines(struct omap_dss_device *dssdev, int data_lines) +{ + mutex_lock(&dpi.lock); + + dpi.data_lines = data_lines; + + mutex_unlock(&dpi.lock); +} + +static int dpi_verify_dsi_pll(struct platform_device *dsidev) +{ + int r; + + /* do initial setup with the PLL to see if it is operational */ + + r = dsi_runtime_get(dsidev); + if (r) + return r; + + r = dsi_pll_init(dsidev, 0, 1); + if (r) { + dsi_runtime_put(dsidev); + return r; + } + + dsi_pll_uninit(dsidev, true); + dsi_runtime_put(dsidev); + + return 0; +} + +static int dpi_init_regulator(void) +{ + struct regulator *vdds_dsi; + + if (!dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) + return 0; + + if (dpi.vdds_dsi_reg) + return 0; + + vdds_dsi = devm_regulator_get(&dpi.pdev->dev, "vdds_dsi"); + if (IS_ERR(vdds_dsi)) { + if (PTR_ERR(vdds_dsi) != -EPROBE_DEFER) + DSSERR("can't get VDDS_DSI regulator\n"); + return PTR_ERR(vdds_dsi); + } + + dpi.vdds_dsi_reg = vdds_dsi; + + return 0; +} + +static void dpi_init_pll(void) +{ + struct platform_device *dsidev; + + if (dpi.dsidev) + return; + + dsidev = dpi_get_dsidev(dpi.output.dispc_channel); + if (!dsidev) + return; + + if (dpi_verify_dsi_pll(dsidev)) { + DSSWARN("DSI PLL not operational\n"); + return; + } + + dpi.dsidev = dsidev; +} + +/* + * Return a hardcoded channel for the DPI output. This should work for + * current use cases, but this can be later expanded to either resolve + * the channel in some more dynamic manner, or get the channel as a user + * parameter. + */ +static enum omap_channel dpi_get_channel(void) +{ + switch (omapdss_get_version()) { + case OMAPDSS_VER_OMAP24xx: + case OMAPDSS_VER_OMAP34xx_ES1: + case OMAPDSS_VER_OMAP34xx_ES3: + case OMAPDSS_VER_OMAP3630: + case OMAPDSS_VER_AM35xx: + return OMAP_DSS_CHANNEL_LCD; + + case OMAPDSS_VER_OMAP4430_ES1: + case OMAPDSS_VER_OMAP4430_ES2: + case OMAPDSS_VER_OMAP4: + return OMAP_DSS_CHANNEL_LCD2; + + case OMAPDSS_VER_OMAP5: + return OMAP_DSS_CHANNEL_LCD3; + + default: + DSSWARN("unsupported DSS version\n"); + return OMAP_DSS_CHANNEL_LCD; + } +} + +static int dpi_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct omap_overlay_manager *mgr; + int r; + + r = dpi_init_regulator(); + if (r) + return r; + + dpi_init_pll(); + + mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); + if (!mgr) + return -ENODEV; + + r = dss_mgr_connect(mgr, dssdev); + if (r) + return r; + + r = omapdss_output_set_device(dssdev, dst); + if (r) { + DSSERR("failed to connect output to new device: %s\n", + dst->name); + dss_mgr_disconnect(mgr, dssdev); + return r; + } + + return 0; +} + +static void dpi_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + WARN_ON(dst != dssdev->dst); + + if (dst != dssdev->dst) + return; + + omapdss_output_unset_device(dssdev); + + if (dssdev->manager) + dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_dpi_ops dpi_ops = { + .connect = dpi_connect, + .disconnect = dpi_disconnect, + + .enable = dpi_display_enable, + .disable = dpi_display_disable, + + .check_timings = dpi_check_timings, + .set_timings = dpi_set_timings, + .get_timings = dpi_get_timings, + + .set_data_lines = dpi_set_data_lines, +}; + +static void dpi_init_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &dpi.output; + + out->dev = &pdev->dev; + out->id = OMAP_DSS_OUTPUT_DPI; + out->output_type = OMAP_DISPLAY_TYPE_DPI; + out->name = "dpi.0"; + out->dispc_channel = dpi_get_channel(); + out->ops.dpi = &dpi_ops; + out->owner = THIS_MODULE; + + omapdss_register_output(out); +} + +static void __exit dpi_uninit_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &dpi.output; + + omapdss_unregister_output(out); +} + +static int omap_dpi_probe(struct platform_device *pdev) +{ + dpi.pdev = pdev; + + mutex_init(&dpi.lock); + + dpi_init_output(pdev); + + return 0; +} + +static int __exit omap_dpi_remove(struct platform_device *pdev) +{ + dpi_uninit_output(pdev); + + return 0; +} + +static struct platform_driver omap_dpi_driver = { + .probe = omap_dpi_probe, + .remove = __exit_p(omap_dpi_remove), + .driver = { + .name = "omapdss_dpi", + .owner = THIS_MODULE, + }, +}; + +int __init dpi_init_platform_driver(void) +{ + return platform_driver_register(&omap_dpi_driver); +} + +void __exit dpi_uninit_platform_driver(void) +{ + platform_driver_unregister(&omap_dpi_driver); +} + +int __init dpi_init_port(struct platform_device *pdev, struct device_node *port) +{ + struct device_node *ep; + u32 datalines; + int r; + + ep = omapdss_of_get_next_endpoint(port, NULL); + if (!ep) + return 0; + + r = of_property_read_u32(ep, "data-lines", &datalines); + if (r) { + DSSERR("failed to parse datalines\n"); + goto err_datalines; + } + + dpi.data_lines = datalines; + + of_node_put(ep); + + dpi.pdev = pdev; + + mutex_init(&dpi.lock); + + dpi_init_output(pdev); + + dpi.port_initialized = true; + + return 0; + +err_datalines: + of_node_put(ep); + + return r; +} + +void __exit dpi_uninit_port(void) +{ + if (!dpi.port_initialized) + return; + + dpi_uninit_output(dpi.pdev); +} diff --git a/drivers/video/fbdev/omap2/dss/dsi.c b/drivers/video/fbdev/omap2/dss/dsi.c new file mode 100644 index 000000000000..8be9b04d8849 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dsi.c @@ -0,0 +1,5751 @@ +/* + * linux/drivers/video/omap2/dss/dsi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "DSI" + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/semaphore.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <video/omapdss.h> +#include <video/mipi_display.h> + +#include "dss.h" +#include "dss_features.h" + +#define DSI_CATCH_MISSING_TE + +struct dsi_reg { u16 module; u16 idx; }; + +#define DSI_REG(mod, idx) ((const struct dsi_reg) { mod, idx }) + +/* DSI Protocol Engine */ + +#define DSI_PROTO 0 +#define DSI_PROTO_SZ 0x200 + +#define DSI_REVISION DSI_REG(DSI_PROTO, 0x0000) +#define DSI_SYSCONFIG DSI_REG(DSI_PROTO, 0x0010) +#define DSI_SYSSTATUS DSI_REG(DSI_PROTO, 0x0014) +#define DSI_IRQSTATUS DSI_REG(DSI_PROTO, 0x0018) +#define DSI_IRQENABLE DSI_REG(DSI_PROTO, 0x001C) +#define DSI_CTRL DSI_REG(DSI_PROTO, 0x0040) +#define DSI_GNQ DSI_REG(DSI_PROTO, 0x0044) +#define DSI_COMPLEXIO_CFG1 DSI_REG(DSI_PROTO, 0x0048) +#define DSI_COMPLEXIO_IRQ_STATUS DSI_REG(DSI_PROTO, 0x004C) +#define DSI_COMPLEXIO_IRQ_ENABLE DSI_REG(DSI_PROTO, 0x0050) +#define DSI_CLK_CTRL DSI_REG(DSI_PROTO, 0x0054) +#define DSI_TIMING1 DSI_REG(DSI_PROTO, 0x0058) +#define DSI_TIMING2 DSI_REG(DSI_PROTO, 0x005C) +#define DSI_VM_TIMING1 DSI_REG(DSI_PROTO, 0x0060) +#define DSI_VM_TIMING2 DSI_REG(DSI_PROTO, 0x0064) +#define DSI_VM_TIMING3 DSI_REG(DSI_PROTO, 0x0068) +#define DSI_CLK_TIMING DSI_REG(DSI_PROTO, 0x006C) +#define DSI_TX_FIFO_VC_SIZE DSI_REG(DSI_PROTO, 0x0070) +#define DSI_RX_FIFO_VC_SIZE DSI_REG(DSI_PROTO, 0x0074) +#define DSI_COMPLEXIO_CFG2 DSI_REG(DSI_PROTO, 0x0078) +#define DSI_RX_FIFO_VC_FULLNESS DSI_REG(DSI_PROTO, 0x007C) +#define DSI_VM_TIMING4 DSI_REG(DSI_PROTO, 0x0080) +#define DSI_TX_FIFO_VC_EMPTINESS DSI_REG(DSI_PROTO, 0x0084) +#define DSI_VM_TIMING5 DSI_REG(DSI_PROTO, 0x0088) +#define DSI_VM_TIMING6 DSI_REG(DSI_PROTO, 0x008C) +#define DSI_VM_TIMING7 DSI_REG(DSI_PROTO, 0x0090) +#define DSI_STOPCLK_TIMING DSI_REG(DSI_PROTO, 0x0094) +#define DSI_VC_CTRL(n) DSI_REG(DSI_PROTO, 0x0100 + (n * 0x20)) +#define DSI_VC_TE(n) DSI_REG(DSI_PROTO, 0x0104 + (n * 0x20)) +#define DSI_VC_LONG_PACKET_HEADER(n) DSI_REG(DSI_PROTO, 0x0108 + (n * 0x20)) +#define DSI_VC_LONG_PACKET_PAYLOAD(n) DSI_REG(DSI_PROTO, 0x010C + (n * 0x20)) +#define DSI_VC_SHORT_PACKET_HEADER(n) DSI_REG(DSI_PROTO, 0x0110 + (n * 0x20)) +#define DSI_VC_IRQSTATUS(n) DSI_REG(DSI_PROTO, 0x0118 + (n * 0x20)) +#define DSI_VC_IRQENABLE(n) DSI_REG(DSI_PROTO, 0x011C + (n * 0x20)) + +/* DSIPHY_SCP */ + +#define DSI_PHY 1 +#define DSI_PHY_OFFSET 0x200 +#define DSI_PHY_SZ 0x40 + +#define DSI_DSIPHY_CFG0 DSI_REG(DSI_PHY, 0x0000) +#define DSI_DSIPHY_CFG1 DSI_REG(DSI_PHY, 0x0004) +#define DSI_DSIPHY_CFG2 DSI_REG(DSI_PHY, 0x0008) +#define DSI_DSIPHY_CFG5 DSI_REG(DSI_PHY, 0x0014) +#define DSI_DSIPHY_CFG10 DSI_REG(DSI_PHY, 0x0028) + +/* DSI_PLL_CTRL_SCP */ + +#define DSI_PLL 2 +#define DSI_PLL_OFFSET 0x300 +#define DSI_PLL_SZ 0x20 + +#define DSI_PLL_CONTROL DSI_REG(DSI_PLL, 0x0000) +#define DSI_PLL_STATUS DSI_REG(DSI_PLL, 0x0004) +#define DSI_PLL_GO DSI_REG(DSI_PLL, 0x0008) +#define DSI_PLL_CONFIGURATION1 DSI_REG(DSI_PLL, 0x000C) +#define DSI_PLL_CONFIGURATION2 DSI_REG(DSI_PLL, 0x0010) + +#define REG_GET(dsidev, idx, start, end) \ + FLD_GET(dsi_read_reg(dsidev, idx), start, end) + +#define REG_FLD_MOD(dsidev, idx, val, start, end) \ + dsi_write_reg(dsidev, idx, FLD_MOD(dsi_read_reg(dsidev, idx), val, start, end)) + +/* Global interrupts */ +#define DSI_IRQ_VC0 (1 << 0) +#define DSI_IRQ_VC1 (1 << 1) +#define DSI_IRQ_VC2 (1 << 2) +#define DSI_IRQ_VC3 (1 << 3) +#define DSI_IRQ_WAKEUP (1 << 4) +#define DSI_IRQ_RESYNC (1 << 5) +#define DSI_IRQ_PLL_LOCK (1 << 7) +#define DSI_IRQ_PLL_UNLOCK (1 << 8) +#define DSI_IRQ_PLL_RECALL (1 << 9) +#define DSI_IRQ_COMPLEXIO_ERR (1 << 10) +#define DSI_IRQ_HS_TX_TIMEOUT (1 << 14) +#define DSI_IRQ_LP_RX_TIMEOUT (1 << 15) +#define DSI_IRQ_TE_TRIGGER (1 << 16) +#define DSI_IRQ_ACK_TRIGGER (1 << 17) +#define DSI_IRQ_SYNC_LOST (1 << 18) +#define DSI_IRQ_LDO_POWER_GOOD (1 << 19) +#define DSI_IRQ_TA_TIMEOUT (1 << 20) +#define DSI_IRQ_ERROR_MASK \ + (DSI_IRQ_HS_TX_TIMEOUT | DSI_IRQ_LP_RX_TIMEOUT | DSI_IRQ_SYNC_LOST | \ + DSI_IRQ_TA_TIMEOUT | DSI_IRQ_SYNC_LOST) +#define DSI_IRQ_CHANNEL_MASK 0xf + +/* Virtual channel interrupts */ +#define DSI_VC_IRQ_CS (1 << 0) +#define DSI_VC_IRQ_ECC_CORR (1 << 1) +#define DSI_VC_IRQ_PACKET_SENT (1 << 2) +#define DSI_VC_IRQ_FIFO_TX_OVF (1 << 3) +#define DSI_VC_IRQ_FIFO_RX_OVF (1 << 4) +#define DSI_VC_IRQ_BTA (1 << 5) +#define DSI_VC_IRQ_ECC_NO_CORR (1 << 6) +#define DSI_VC_IRQ_FIFO_TX_UDF (1 << 7) +#define DSI_VC_IRQ_PP_BUSY_CHANGE (1 << 8) +#define DSI_VC_IRQ_ERROR_MASK \ + (DSI_VC_IRQ_CS | DSI_VC_IRQ_ECC_CORR | DSI_VC_IRQ_FIFO_TX_OVF | \ + DSI_VC_IRQ_FIFO_RX_OVF | DSI_VC_IRQ_ECC_NO_CORR | \ + DSI_VC_IRQ_FIFO_TX_UDF) + +/* ComplexIO interrupts */ +#define DSI_CIO_IRQ_ERRSYNCESC1 (1 << 0) +#define DSI_CIO_IRQ_ERRSYNCESC2 (1 << 1) +#define DSI_CIO_IRQ_ERRSYNCESC3 (1 << 2) +#define DSI_CIO_IRQ_ERRSYNCESC4 (1 << 3) +#define DSI_CIO_IRQ_ERRSYNCESC5 (1 << 4) +#define DSI_CIO_IRQ_ERRESC1 (1 << 5) +#define DSI_CIO_IRQ_ERRESC2 (1 << 6) +#define DSI_CIO_IRQ_ERRESC3 (1 << 7) +#define DSI_CIO_IRQ_ERRESC4 (1 << 8) +#define DSI_CIO_IRQ_ERRESC5 (1 << 9) +#define DSI_CIO_IRQ_ERRCONTROL1 (1 << 10) +#define DSI_CIO_IRQ_ERRCONTROL2 (1 << 11) +#define DSI_CIO_IRQ_ERRCONTROL3 (1 << 12) +#define DSI_CIO_IRQ_ERRCONTROL4 (1 << 13) +#define DSI_CIO_IRQ_ERRCONTROL5 (1 << 14) +#define DSI_CIO_IRQ_STATEULPS1 (1 << 15) +#define DSI_CIO_IRQ_STATEULPS2 (1 << 16) +#define DSI_CIO_IRQ_STATEULPS3 (1 << 17) +#define DSI_CIO_IRQ_STATEULPS4 (1 << 18) +#define DSI_CIO_IRQ_STATEULPS5 (1 << 19) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_1 (1 << 20) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_1 (1 << 21) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_2 (1 << 22) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_2 (1 << 23) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_3 (1 << 24) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_3 (1 << 25) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_4 (1 << 26) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_4 (1 << 27) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_5 (1 << 28) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_5 (1 << 29) +#define DSI_CIO_IRQ_ULPSACTIVENOT_ALL0 (1 << 30) +#define DSI_CIO_IRQ_ULPSACTIVENOT_ALL1 (1 << 31) +#define DSI_CIO_IRQ_ERROR_MASK \ + (DSI_CIO_IRQ_ERRSYNCESC1 | DSI_CIO_IRQ_ERRSYNCESC2 | \ + DSI_CIO_IRQ_ERRSYNCESC3 | DSI_CIO_IRQ_ERRSYNCESC4 | \ + DSI_CIO_IRQ_ERRSYNCESC5 | \ + DSI_CIO_IRQ_ERRESC1 | DSI_CIO_IRQ_ERRESC2 | \ + DSI_CIO_IRQ_ERRESC3 | DSI_CIO_IRQ_ERRESC4 | \ + DSI_CIO_IRQ_ERRESC5 | \ + DSI_CIO_IRQ_ERRCONTROL1 | DSI_CIO_IRQ_ERRCONTROL2 | \ + DSI_CIO_IRQ_ERRCONTROL3 | DSI_CIO_IRQ_ERRCONTROL4 | \ + DSI_CIO_IRQ_ERRCONTROL5 | \ + DSI_CIO_IRQ_ERRCONTENTIONLP0_1 | DSI_CIO_IRQ_ERRCONTENTIONLP1_1 | \ + DSI_CIO_IRQ_ERRCONTENTIONLP0_2 | DSI_CIO_IRQ_ERRCONTENTIONLP1_2 | \ + DSI_CIO_IRQ_ERRCONTENTIONLP0_3 | DSI_CIO_IRQ_ERRCONTENTIONLP1_3 | \ + DSI_CIO_IRQ_ERRCONTENTIONLP0_4 | DSI_CIO_IRQ_ERRCONTENTIONLP1_4 | \ + DSI_CIO_IRQ_ERRCONTENTIONLP0_5 | DSI_CIO_IRQ_ERRCONTENTIONLP1_5) + +typedef void (*omap_dsi_isr_t) (void *arg, u32 mask); + +static int dsi_display_init_dispc(struct platform_device *dsidev, + struct omap_overlay_manager *mgr); +static void dsi_display_uninit_dispc(struct platform_device *dsidev, + struct omap_overlay_manager *mgr); + +static int dsi_vc_send_null(struct omap_dss_device *dssdev, int channel); + +#define DSI_MAX_NR_ISRS 2 +#define DSI_MAX_NR_LANES 5 + +enum dsi_lane_function { + DSI_LANE_UNUSED = 0, + DSI_LANE_CLK, + DSI_LANE_DATA1, + DSI_LANE_DATA2, + DSI_LANE_DATA3, + DSI_LANE_DATA4, +}; + +struct dsi_lane_config { + enum dsi_lane_function function; + u8 polarity; +}; + +struct dsi_isr_data { + omap_dsi_isr_t isr; + void *arg; + u32 mask; +}; + +enum fifo_size { + DSI_FIFO_SIZE_0 = 0, + DSI_FIFO_SIZE_32 = 1, + DSI_FIFO_SIZE_64 = 2, + DSI_FIFO_SIZE_96 = 3, + DSI_FIFO_SIZE_128 = 4, +}; + +enum dsi_vc_source { + DSI_VC_SOURCE_L4 = 0, + DSI_VC_SOURCE_VP, +}; + +struct dsi_irq_stats { + unsigned long last_reset; + unsigned irq_count; + unsigned dsi_irqs[32]; + unsigned vc_irqs[4][32]; + unsigned cio_irqs[32]; +}; + +struct dsi_isr_tables { + struct dsi_isr_data isr_table[DSI_MAX_NR_ISRS]; + struct dsi_isr_data isr_table_vc[4][DSI_MAX_NR_ISRS]; + struct dsi_isr_data isr_table_cio[DSI_MAX_NR_ISRS]; +}; + +struct dsi_clk_calc_ctx { + struct platform_device *dsidev; + + /* inputs */ + + const struct omap_dss_dsi_config *config; + + unsigned long req_pck_min, req_pck_nom, req_pck_max; + + /* outputs */ + + struct dsi_clock_info dsi_cinfo; + struct dispc_clock_info dispc_cinfo; + + struct omap_video_timings dispc_vm; + struct omap_dss_dsi_videomode_timings dsi_vm; +}; + +struct dsi_data { + struct platform_device *pdev; + void __iomem *proto_base; + void __iomem *phy_base; + void __iomem *pll_base; + + int module_id; + + int irq; + + bool is_enabled; + + struct clk *dss_clk; + struct clk *sys_clk; + + struct dispc_clock_info user_dispc_cinfo; + struct dsi_clock_info user_dsi_cinfo; + + struct dsi_clock_info current_cinfo; + + bool vdds_dsi_enabled; + struct regulator *vdds_dsi_reg; + + struct { + enum dsi_vc_source source; + struct omap_dss_device *dssdev; + enum fifo_size tx_fifo_size; + enum fifo_size rx_fifo_size; + int vc_id; + } vc[4]; + + struct mutex lock; + struct semaphore bus_lock; + + unsigned pll_locked; + + spinlock_t irq_lock; + struct dsi_isr_tables isr_tables; + /* space for a copy used by the interrupt handler */ + struct dsi_isr_tables isr_tables_copy; + + int update_channel; +#ifdef DSI_PERF_MEASURE + unsigned update_bytes; +#endif + + bool te_enabled; + bool ulps_enabled; + + void (*framedone_callback)(int, void *); + void *framedone_data; + + struct delayed_work framedone_timeout_work; + +#ifdef DSI_CATCH_MISSING_TE + struct timer_list te_timer; +#endif + + unsigned long cache_req_pck; + unsigned long cache_clk_freq; + struct dsi_clock_info cache_cinfo; + + u32 errors; + spinlock_t errors_lock; +#ifdef DSI_PERF_MEASURE + ktime_t perf_setup_time; + ktime_t perf_start_time; +#endif + int debug_read; + int debug_write; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS + spinlock_t irq_stats_lock; + struct dsi_irq_stats irq_stats; +#endif + /* DSI PLL Parameter Ranges */ + unsigned long regm_max, regn_max; + unsigned long regm_dispc_max, regm_dsi_max; + unsigned long fint_min, fint_max; + unsigned long lpdiv_max; + + unsigned num_lanes_supported; + unsigned line_buffer_size; + + struct dsi_lane_config lanes[DSI_MAX_NR_LANES]; + unsigned num_lanes_used; + + unsigned scp_clk_refcount; + + struct dss_lcd_mgr_config mgr_config; + struct omap_video_timings timings; + enum omap_dss_dsi_pixel_format pix_fmt; + enum omap_dss_dsi_mode mode; + struct omap_dss_dsi_videomode_timings vm_timings; + + struct omap_dss_device output; +}; + +struct dsi_packet_sent_handler_data { + struct platform_device *dsidev; + struct completion *completion; +}; + +struct dsi_module_id_data { + u32 address; + int id; +}; + +static const struct of_device_id dsi_of_match[]; + +#ifdef DSI_PERF_MEASURE +static bool dsi_perf; +module_param(dsi_perf, bool, 0644); +#endif + +static inline struct dsi_data *dsi_get_dsidrv_data(struct platform_device *dsidev) +{ + return dev_get_drvdata(&dsidev->dev); +} + +static inline struct platform_device *dsi_get_dsidev_from_dssdev(struct omap_dss_device *dssdev) +{ + return to_platform_device(dssdev->dev); +} + +struct platform_device *dsi_get_dsidev_from_id(int module) +{ + struct omap_dss_device *out; + enum omap_dss_output_id id; + + switch (module) { + case 0: + id = OMAP_DSS_OUTPUT_DSI1; + break; + case 1: + id = OMAP_DSS_OUTPUT_DSI2; + break; + default: + return NULL; + } + + out = omap_dss_get_output(id); + + return out ? to_platform_device(out->dev) : NULL; +} + +static inline void dsi_write_reg(struct platform_device *dsidev, + const struct dsi_reg idx, u32 val) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + void __iomem *base; + + switch(idx.module) { + case DSI_PROTO: base = dsi->proto_base; break; + case DSI_PHY: base = dsi->phy_base; break; + case DSI_PLL: base = dsi->pll_base; break; + default: return; + } + + __raw_writel(val, base + idx.idx); +} + +static inline u32 dsi_read_reg(struct platform_device *dsidev, + const struct dsi_reg idx) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + void __iomem *base; + + switch(idx.module) { + case DSI_PROTO: base = dsi->proto_base; break; + case DSI_PHY: base = dsi->phy_base; break; + case DSI_PLL: base = dsi->pll_base; break; + default: return 0; + } + + return __raw_readl(base + idx.idx); +} + +static void dsi_bus_lock(struct omap_dss_device *dssdev) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + down(&dsi->bus_lock); +} + +static void dsi_bus_unlock(struct omap_dss_device *dssdev) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + up(&dsi->bus_lock); +} + +static bool dsi_bus_is_locked(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + return dsi->bus_lock.count == 0; +} + +static void dsi_completion_handler(void *data, u32 mask) +{ + complete((struct completion *)data); +} + +static inline int wait_for_bit_change(struct platform_device *dsidev, + const struct dsi_reg idx, int bitnum, int value) +{ + unsigned long timeout; + ktime_t wait; + int t; + + /* first busyloop to see if the bit changes right away */ + t = 100; + while (t-- > 0) { + if (REG_GET(dsidev, idx, bitnum, bitnum) == value) + return value; + } + + /* then loop for 500ms, sleeping for 1ms in between */ + timeout = jiffies + msecs_to_jiffies(500); + while (time_before(jiffies, timeout)) { + if (REG_GET(dsidev, idx, bitnum, bitnum) == value) + return value; + + wait = ns_to_ktime(1000 * 1000); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_hrtimeout(&wait, HRTIMER_MODE_REL); + } + + return !value; +} + +u8 dsi_get_pixel_size(enum omap_dss_dsi_pixel_format fmt) +{ + switch (fmt) { + case OMAP_DSS_DSI_FMT_RGB888: + case OMAP_DSS_DSI_FMT_RGB666: + return 24; + case OMAP_DSS_DSI_FMT_RGB666_PACKED: + return 18; + case OMAP_DSS_DSI_FMT_RGB565: + return 16; + default: + BUG(); + return 0; + } +} + +#ifdef DSI_PERF_MEASURE +static void dsi_perf_mark_setup(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + dsi->perf_setup_time = ktime_get(); +} + +static void dsi_perf_mark_start(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + dsi->perf_start_time = ktime_get(); +} + +static void dsi_perf_show(struct platform_device *dsidev, const char *name) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + ktime_t t, setup_time, trans_time; + u32 total_bytes; + u32 setup_us, trans_us, total_us; + + if (!dsi_perf) + return; + + t = ktime_get(); + + setup_time = ktime_sub(dsi->perf_start_time, dsi->perf_setup_time); + setup_us = (u32)ktime_to_us(setup_time); + if (setup_us == 0) + setup_us = 1; + + trans_time = ktime_sub(t, dsi->perf_start_time); + trans_us = (u32)ktime_to_us(trans_time); + if (trans_us == 0) + trans_us = 1; + + total_us = setup_us + trans_us; + + total_bytes = dsi->update_bytes; + + printk(KERN_INFO "DSI(%s): %u us + %u us = %u us (%uHz), " + "%u bytes, %u kbytes/sec\n", + name, + setup_us, + trans_us, + total_us, + 1000*1000 / total_us, + total_bytes, + total_bytes * 1000 / total_us); +} +#else +static inline void dsi_perf_mark_setup(struct platform_device *dsidev) +{ +} + +static inline void dsi_perf_mark_start(struct platform_device *dsidev) +{ +} + +static inline void dsi_perf_show(struct platform_device *dsidev, + const char *name) +{ +} +#endif + +static int verbose_irq; + +static void print_irq_status(u32 status) +{ + if (status == 0) + return; + + if (!verbose_irq && (status & ~DSI_IRQ_CHANNEL_MASK) == 0) + return; + +#define PIS(x) (status & DSI_IRQ_##x) ? (#x " ") : "" + + pr_debug("DSI IRQ: 0x%x: %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + status, + verbose_irq ? PIS(VC0) : "", + verbose_irq ? PIS(VC1) : "", + verbose_irq ? PIS(VC2) : "", + verbose_irq ? PIS(VC3) : "", + PIS(WAKEUP), + PIS(RESYNC), + PIS(PLL_LOCK), + PIS(PLL_UNLOCK), + PIS(PLL_RECALL), + PIS(COMPLEXIO_ERR), + PIS(HS_TX_TIMEOUT), + PIS(LP_RX_TIMEOUT), + PIS(TE_TRIGGER), + PIS(ACK_TRIGGER), + PIS(SYNC_LOST), + PIS(LDO_POWER_GOOD), + PIS(TA_TIMEOUT)); +#undef PIS +} + +static void print_irq_status_vc(int channel, u32 status) +{ + if (status == 0) + return; + + if (!verbose_irq && (status & ~DSI_VC_IRQ_PACKET_SENT) == 0) + return; + +#define PIS(x) (status & DSI_VC_IRQ_##x) ? (#x " ") : "" + + pr_debug("DSI VC(%d) IRQ 0x%x: %s%s%s%s%s%s%s%s%s\n", + channel, + status, + PIS(CS), + PIS(ECC_CORR), + PIS(ECC_NO_CORR), + verbose_irq ? PIS(PACKET_SENT) : "", + PIS(BTA), + PIS(FIFO_TX_OVF), + PIS(FIFO_RX_OVF), + PIS(FIFO_TX_UDF), + PIS(PP_BUSY_CHANGE)); +#undef PIS +} + +static void print_irq_status_cio(u32 status) +{ + if (status == 0) + return; + +#define PIS(x) (status & DSI_CIO_IRQ_##x) ? (#x " ") : "" + + pr_debug("DSI CIO IRQ 0x%x: %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + status, + PIS(ERRSYNCESC1), + PIS(ERRSYNCESC2), + PIS(ERRSYNCESC3), + PIS(ERRESC1), + PIS(ERRESC2), + PIS(ERRESC3), + PIS(ERRCONTROL1), + PIS(ERRCONTROL2), + PIS(ERRCONTROL3), + PIS(STATEULPS1), + PIS(STATEULPS2), + PIS(STATEULPS3), + PIS(ERRCONTENTIONLP0_1), + PIS(ERRCONTENTIONLP1_1), + PIS(ERRCONTENTIONLP0_2), + PIS(ERRCONTENTIONLP1_2), + PIS(ERRCONTENTIONLP0_3), + PIS(ERRCONTENTIONLP1_3), + PIS(ULPSACTIVENOT_ALL0), + PIS(ULPSACTIVENOT_ALL1)); +#undef PIS +} + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static void dsi_collect_irq_stats(struct platform_device *dsidev, u32 irqstatus, + u32 *vcstatus, u32 ciostatus) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int i; + + spin_lock(&dsi->irq_stats_lock); + + dsi->irq_stats.irq_count++; + dss_collect_irq_stats(irqstatus, dsi->irq_stats.dsi_irqs); + + for (i = 0; i < 4; ++i) + dss_collect_irq_stats(vcstatus[i], dsi->irq_stats.vc_irqs[i]); + + dss_collect_irq_stats(ciostatus, dsi->irq_stats.cio_irqs); + + spin_unlock(&dsi->irq_stats_lock); +} +#else +#define dsi_collect_irq_stats(dsidev, irqstatus, vcstatus, ciostatus) +#endif + +static int debug_irq; + +static void dsi_handle_irq_errors(struct platform_device *dsidev, u32 irqstatus, + u32 *vcstatus, u32 ciostatus) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int i; + + if (irqstatus & DSI_IRQ_ERROR_MASK) { + DSSERR("DSI error, irqstatus %x\n", irqstatus); + print_irq_status(irqstatus); + spin_lock(&dsi->errors_lock); + dsi->errors |= irqstatus & DSI_IRQ_ERROR_MASK; + spin_unlock(&dsi->errors_lock); + } else if (debug_irq) { + print_irq_status(irqstatus); + } + + for (i = 0; i < 4; ++i) { + if (vcstatus[i] & DSI_VC_IRQ_ERROR_MASK) { + DSSERR("DSI VC(%d) error, vc irqstatus %x\n", + i, vcstatus[i]); + print_irq_status_vc(i, vcstatus[i]); + } else if (debug_irq) { + print_irq_status_vc(i, vcstatus[i]); + } + } + + if (ciostatus & DSI_CIO_IRQ_ERROR_MASK) { + DSSERR("DSI CIO error, cio irqstatus %x\n", ciostatus); + print_irq_status_cio(ciostatus); + } else if (debug_irq) { + print_irq_status_cio(ciostatus); + } +} + +static void dsi_call_isrs(struct dsi_isr_data *isr_array, + unsigned isr_array_size, u32 irqstatus) +{ + struct dsi_isr_data *isr_data; + int i; + + for (i = 0; i < isr_array_size; i++) { + isr_data = &isr_array[i]; + if (isr_data->isr && isr_data->mask & irqstatus) + isr_data->isr(isr_data->arg, irqstatus); + } +} + +static void dsi_handle_isrs(struct dsi_isr_tables *isr_tables, + u32 irqstatus, u32 *vcstatus, u32 ciostatus) +{ + int i; + + dsi_call_isrs(isr_tables->isr_table, + ARRAY_SIZE(isr_tables->isr_table), + irqstatus); + + for (i = 0; i < 4; ++i) { + if (vcstatus[i] == 0) + continue; + dsi_call_isrs(isr_tables->isr_table_vc[i], + ARRAY_SIZE(isr_tables->isr_table_vc[i]), + vcstatus[i]); + } + + if (ciostatus != 0) + dsi_call_isrs(isr_tables->isr_table_cio, + ARRAY_SIZE(isr_tables->isr_table_cio), + ciostatus); +} + +static irqreturn_t omap_dsi_irq_handler(int irq, void *arg) +{ + struct platform_device *dsidev; + struct dsi_data *dsi; + u32 irqstatus, vcstatus[4], ciostatus; + int i; + + dsidev = (struct platform_device *) arg; + dsi = dsi_get_dsidrv_data(dsidev); + + if (!dsi->is_enabled) + return IRQ_NONE; + + spin_lock(&dsi->irq_lock); + + irqstatus = dsi_read_reg(dsidev, DSI_IRQSTATUS); + + /* IRQ is not for us */ + if (!irqstatus) { + spin_unlock(&dsi->irq_lock); + return IRQ_NONE; + } + + dsi_write_reg(dsidev, DSI_IRQSTATUS, irqstatus & ~DSI_IRQ_CHANNEL_MASK); + /* flush posted write */ + dsi_read_reg(dsidev, DSI_IRQSTATUS); + + for (i = 0; i < 4; ++i) { + if ((irqstatus & (1 << i)) == 0) { + vcstatus[i] = 0; + continue; + } + + vcstatus[i] = dsi_read_reg(dsidev, DSI_VC_IRQSTATUS(i)); + + dsi_write_reg(dsidev, DSI_VC_IRQSTATUS(i), vcstatus[i]); + /* flush posted write */ + dsi_read_reg(dsidev, DSI_VC_IRQSTATUS(i)); + } + + if (irqstatus & DSI_IRQ_COMPLEXIO_ERR) { + ciostatus = dsi_read_reg(dsidev, DSI_COMPLEXIO_IRQ_STATUS); + + dsi_write_reg(dsidev, DSI_COMPLEXIO_IRQ_STATUS, ciostatus); + /* flush posted write */ + dsi_read_reg(dsidev, DSI_COMPLEXIO_IRQ_STATUS); + } else { + ciostatus = 0; + } + +#ifdef DSI_CATCH_MISSING_TE + if (irqstatus & DSI_IRQ_TE_TRIGGER) + del_timer(&dsi->te_timer); +#endif + + /* make a copy and unlock, so that isrs can unregister + * themselves */ + memcpy(&dsi->isr_tables_copy, &dsi->isr_tables, + sizeof(dsi->isr_tables)); + + spin_unlock(&dsi->irq_lock); + + dsi_handle_isrs(&dsi->isr_tables_copy, irqstatus, vcstatus, ciostatus); + + dsi_handle_irq_errors(dsidev, irqstatus, vcstatus, ciostatus); + + dsi_collect_irq_stats(dsidev, irqstatus, vcstatus, ciostatus); + + return IRQ_HANDLED; +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_configure_irqs(struct platform_device *dsidev, + struct dsi_isr_data *isr_array, + unsigned isr_array_size, u32 default_mask, + const struct dsi_reg enable_reg, + const struct dsi_reg status_reg) +{ + struct dsi_isr_data *isr_data; + u32 mask; + u32 old_mask; + int i; + + mask = default_mask; + + for (i = 0; i < isr_array_size; i++) { + isr_data = &isr_array[i]; + + if (isr_data->isr == NULL) + continue; + + mask |= isr_data->mask; + } + + old_mask = dsi_read_reg(dsidev, enable_reg); + /* clear the irqstatus for newly enabled irqs */ + dsi_write_reg(dsidev, status_reg, (mask ^ old_mask) & mask); + dsi_write_reg(dsidev, enable_reg, mask); + + /* flush posted writes */ + dsi_read_reg(dsidev, enable_reg); + dsi_read_reg(dsidev, status_reg); +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_set_irqs(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 mask = DSI_IRQ_ERROR_MASK; +#ifdef DSI_CATCH_MISSING_TE + mask |= DSI_IRQ_TE_TRIGGER; +#endif + _omap_dsi_configure_irqs(dsidev, dsi->isr_tables.isr_table, + ARRAY_SIZE(dsi->isr_tables.isr_table), mask, + DSI_IRQENABLE, DSI_IRQSTATUS); +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_set_irqs_vc(struct platform_device *dsidev, int vc) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + _omap_dsi_configure_irqs(dsidev, dsi->isr_tables.isr_table_vc[vc], + ARRAY_SIZE(dsi->isr_tables.isr_table_vc[vc]), + DSI_VC_IRQ_ERROR_MASK, + DSI_VC_IRQENABLE(vc), DSI_VC_IRQSTATUS(vc)); +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_set_irqs_cio(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + _omap_dsi_configure_irqs(dsidev, dsi->isr_tables.isr_table_cio, + ARRAY_SIZE(dsi->isr_tables.isr_table_cio), + DSI_CIO_IRQ_ERROR_MASK, + DSI_COMPLEXIO_IRQ_ENABLE, DSI_COMPLEXIO_IRQ_STATUS); +} + +static void _dsi_initialize_irq(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + int vc; + + spin_lock_irqsave(&dsi->irq_lock, flags); + + memset(&dsi->isr_tables, 0, sizeof(dsi->isr_tables)); + + _omap_dsi_set_irqs(dsidev); + for (vc = 0; vc < 4; ++vc) + _omap_dsi_set_irqs_vc(dsidev, vc); + _omap_dsi_set_irqs_cio(dsidev); + + spin_unlock_irqrestore(&dsi->irq_lock, flags); +} + +static int _dsi_register_isr(omap_dsi_isr_t isr, void *arg, u32 mask, + struct dsi_isr_data *isr_array, unsigned isr_array_size) +{ + struct dsi_isr_data *isr_data; + int free_idx; + int i; + + BUG_ON(isr == NULL); + + /* check for duplicate entry and find a free slot */ + free_idx = -1; + for (i = 0; i < isr_array_size; i++) { + isr_data = &isr_array[i]; + + if (isr_data->isr == isr && isr_data->arg == arg && + isr_data->mask == mask) { + return -EINVAL; + } + + if (isr_data->isr == NULL && free_idx == -1) + free_idx = i; + } + + if (free_idx == -1) + return -EBUSY; + + isr_data = &isr_array[free_idx]; + isr_data->isr = isr; + isr_data->arg = arg; + isr_data->mask = mask; + + return 0; +} + +static int _dsi_unregister_isr(omap_dsi_isr_t isr, void *arg, u32 mask, + struct dsi_isr_data *isr_array, unsigned isr_array_size) +{ + struct dsi_isr_data *isr_data; + int i; + + for (i = 0; i < isr_array_size; i++) { + isr_data = &isr_array[i]; + if (isr_data->isr != isr || isr_data->arg != arg || + isr_data->mask != mask) + continue; + + isr_data->isr = NULL; + isr_data->arg = NULL; + isr_data->mask = 0; + + return 0; + } + + return -EINVAL; +} + +static int dsi_register_isr(struct platform_device *dsidev, omap_dsi_isr_t isr, + void *arg, u32 mask) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + int r; + + spin_lock_irqsave(&dsi->irq_lock, flags); + + r = _dsi_register_isr(isr, arg, mask, dsi->isr_tables.isr_table, + ARRAY_SIZE(dsi->isr_tables.isr_table)); + + if (r == 0) + _omap_dsi_set_irqs(dsidev); + + spin_unlock_irqrestore(&dsi->irq_lock, flags); + + return r; +} + +static int dsi_unregister_isr(struct platform_device *dsidev, + omap_dsi_isr_t isr, void *arg, u32 mask) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + int r; + + spin_lock_irqsave(&dsi->irq_lock, flags); + + r = _dsi_unregister_isr(isr, arg, mask, dsi->isr_tables.isr_table, + ARRAY_SIZE(dsi->isr_tables.isr_table)); + + if (r == 0) + _omap_dsi_set_irqs(dsidev); + + spin_unlock_irqrestore(&dsi->irq_lock, flags); + + return r; +} + +static int dsi_register_isr_vc(struct platform_device *dsidev, int channel, + omap_dsi_isr_t isr, void *arg, u32 mask) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + int r; + + spin_lock_irqsave(&dsi->irq_lock, flags); + + r = _dsi_register_isr(isr, arg, mask, + dsi->isr_tables.isr_table_vc[channel], + ARRAY_SIZE(dsi->isr_tables.isr_table_vc[channel])); + + if (r == 0) + _omap_dsi_set_irqs_vc(dsidev, channel); + + spin_unlock_irqrestore(&dsi->irq_lock, flags); + + return r; +} + +static int dsi_unregister_isr_vc(struct platform_device *dsidev, int channel, + omap_dsi_isr_t isr, void *arg, u32 mask) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + int r; + + spin_lock_irqsave(&dsi->irq_lock, flags); + + r = _dsi_unregister_isr(isr, arg, mask, + dsi->isr_tables.isr_table_vc[channel], + ARRAY_SIZE(dsi->isr_tables.isr_table_vc[channel])); + + if (r == 0) + _omap_dsi_set_irqs_vc(dsidev, channel); + + spin_unlock_irqrestore(&dsi->irq_lock, flags); + + return r; +} + +static int dsi_register_isr_cio(struct platform_device *dsidev, + omap_dsi_isr_t isr, void *arg, u32 mask) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + int r; + + spin_lock_irqsave(&dsi->irq_lock, flags); + + r = _dsi_register_isr(isr, arg, mask, dsi->isr_tables.isr_table_cio, + ARRAY_SIZE(dsi->isr_tables.isr_table_cio)); + + if (r == 0) + _omap_dsi_set_irqs_cio(dsidev); + + spin_unlock_irqrestore(&dsi->irq_lock, flags); + + return r; +} + +static int dsi_unregister_isr_cio(struct platform_device *dsidev, + omap_dsi_isr_t isr, void *arg, u32 mask) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + int r; + + spin_lock_irqsave(&dsi->irq_lock, flags); + + r = _dsi_unregister_isr(isr, arg, mask, dsi->isr_tables.isr_table_cio, + ARRAY_SIZE(dsi->isr_tables.isr_table_cio)); + + if (r == 0) + _omap_dsi_set_irqs_cio(dsidev); + + spin_unlock_irqrestore(&dsi->irq_lock, flags); + + return r; +} + +static u32 dsi_get_errors(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + u32 e; + spin_lock_irqsave(&dsi->errors_lock, flags); + e = dsi->errors; + dsi->errors = 0; + spin_unlock_irqrestore(&dsi->errors_lock, flags); + return e; +} + +int dsi_runtime_get(struct platform_device *dsidev) +{ + int r; + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + DSSDBG("dsi_runtime_get\n"); + + r = pm_runtime_get_sync(&dsi->pdev->dev); + WARN_ON(r < 0); + return r < 0 ? r : 0; +} + +void dsi_runtime_put(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r; + + DSSDBG("dsi_runtime_put\n"); + + r = pm_runtime_put_sync(&dsi->pdev->dev); + WARN_ON(r < 0 && r != -ENOSYS); +} + +static int dsi_regulator_init(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct regulator *vdds_dsi; + + if (dsi->vdds_dsi_reg != NULL) + return 0; + + vdds_dsi = devm_regulator_get(&dsi->pdev->dev, "vdd"); + + if (IS_ERR(vdds_dsi)) { + if (PTR_ERR(vdds_dsi) != -EPROBE_DEFER) + DSSERR("can't get DSI VDD regulator\n"); + return PTR_ERR(vdds_dsi); + } + + dsi->vdds_dsi_reg = vdds_dsi; + + return 0; +} + +/* source clock for DSI PLL. this could also be PCLKFREE */ +static inline void dsi_enable_pll_clock(struct platform_device *dsidev, + bool enable) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (enable) + clk_prepare_enable(dsi->sys_clk); + else + clk_disable_unprepare(dsi->sys_clk); + + if (enable && dsi->pll_locked) { + if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 1, 1) != 1) + DSSERR("cannot lock PLL when enabling clocks\n"); + } +} + +static void _dsi_print_reset_status(struct platform_device *dsidev) +{ + u32 l; + int b0, b1, b2; + + /* A dummy read using the SCP interface to any DSIPHY register is + * required after DSIPHY reset to complete the reset of the DSI complex + * I/O. */ + l = dsi_read_reg(dsidev, DSI_DSIPHY_CFG5); + + if (dss_has_feature(FEAT_DSI_REVERSE_TXCLKESC)) { + b0 = 28; + b1 = 27; + b2 = 26; + } else { + b0 = 24; + b1 = 25; + b2 = 26; + } + +#define DSI_FLD_GET(fld, start, end)\ + FLD_GET(dsi_read_reg(dsidev, DSI_##fld), start, end) + + pr_debug("DSI resets: PLL (%d) CIO (%d) PHY (%x%x%x, %d, %d, %d)\n", + DSI_FLD_GET(PLL_STATUS, 0, 0), + DSI_FLD_GET(COMPLEXIO_CFG1, 29, 29), + DSI_FLD_GET(DSIPHY_CFG5, b0, b0), + DSI_FLD_GET(DSIPHY_CFG5, b1, b1), + DSI_FLD_GET(DSIPHY_CFG5, b2, b2), + DSI_FLD_GET(DSIPHY_CFG5, 29, 29), + DSI_FLD_GET(DSIPHY_CFG5, 30, 30), + DSI_FLD_GET(DSIPHY_CFG5, 31, 31)); + +#undef DSI_FLD_GET +} + +static inline int dsi_if_enable(struct platform_device *dsidev, bool enable) +{ + DSSDBG("dsi_if_enable(%d)\n", enable); + + enable = enable ? 1 : 0; + REG_FLD_MOD(dsidev, DSI_CTRL, enable, 0, 0); /* IF_EN */ + + if (wait_for_bit_change(dsidev, DSI_CTRL, 0, enable) != enable) { + DSSERR("Failed to set dsi_if_enable to %d\n", enable); + return -EIO; + } + + return 0; +} + +unsigned long dsi_get_pll_hsdiv_dispc_rate(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + return dsi->current_cinfo.dsi_pll_hsdiv_dispc_clk; +} + +static unsigned long dsi_get_pll_hsdiv_dsi_rate(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + return dsi->current_cinfo.dsi_pll_hsdiv_dsi_clk; +} + +static unsigned long dsi_get_txbyteclkhs(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + return dsi->current_cinfo.clkin4ddr / 16; +} + +static unsigned long dsi_fclk_rate(struct platform_device *dsidev) +{ + unsigned long r; + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (dss_get_dsi_clk_source(dsi->module_id) == OMAP_DSS_CLK_SRC_FCK) { + /* DSI FCLK source is DSS_CLK_FCK */ + r = clk_get_rate(dsi->dss_clk); + } else { + /* DSI FCLK source is dsi_pll_hsdiv_dsi_clk */ + r = dsi_get_pll_hsdiv_dsi_rate(dsidev); + } + + return r; +} + +static int dsi_lp_clock_calc(struct dsi_clock_info *cinfo, + unsigned long lp_clk_min, unsigned long lp_clk_max) +{ + unsigned long dsi_fclk = cinfo->dsi_pll_hsdiv_dsi_clk; + unsigned lp_clk_div; + unsigned long lp_clk; + + lp_clk_div = DIV_ROUND_UP(dsi_fclk, lp_clk_max * 2); + lp_clk = dsi_fclk / 2 / lp_clk_div; + + if (lp_clk < lp_clk_min || lp_clk > lp_clk_max) + return -EINVAL; + + cinfo->lp_clk_div = lp_clk_div; + cinfo->lp_clk = lp_clk; + + return 0; +} + +static int dsi_set_lp_clk_divisor(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long dsi_fclk; + unsigned lp_clk_div; + unsigned long lp_clk; + + lp_clk_div = dsi->user_dsi_cinfo.lp_clk_div; + + if (lp_clk_div == 0 || lp_clk_div > dsi->lpdiv_max) + return -EINVAL; + + dsi_fclk = dsi_fclk_rate(dsidev); + + lp_clk = dsi_fclk / 2 / lp_clk_div; + + DSSDBG("LP_CLK_DIV %u, LP_CLK %lu\n", lp_clk_div, lp_clk); + dsi->current_cinfo.lp_clk = lp_clk; + dsi->current_cinfo.lp_clk_div = lp_clk_div; + + /* LP_CLK_DIVISOR */ + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, lp_clk_div, 12, 0); + + /* LP_RX_SYNCHRO_ENABLE */ + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, dsi_fclk > 30000000 ? 1 : 0, 21, 21); + + return 0; +} + +static void dsi_enable_scp_clk(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (dsi->scp_clk_refcount++ == 0) + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 1, 14, 14); /* CIO_CLK_ICG */ +} + +static void dsi_disable_scp_clk(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + WARN_ON(dsi->scp_clk_refcount == 0); + if (--dsi->scp_clk_refcount == 0) + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 14, 14); /* CIO_CLK_ICG */ +} + +enum dsi_pll_power_state { + DSI_PLL_POWER_OFF = 0x0, + DSI_PLL_POWER_ON_HSCLK = 0x1, + DSI_PLL_POWER_ON_ALL = 0x2, + DSI_PLL_POWER_ON_DIV = 0x3, +}; + +static int dsi_pll_power(struct platform_device *dsidev, + enum dsi_pll_power_state state) +{ + int t = 0; + + /* DSI-PLL power command 0x3 is not working */ + if (dss_has_feature(FEAT_DSI_PLL_PWR_BUG) && + state == DSI_PLL_POWER_ON_DIV) + state = DSI_PLL_POWER_ON_ALL; + + /* PLL_PWR_CMD */ + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, state, 31, 30); + + /* PLL_PWR_STATUS */ + while (FLD_GET(dsi_read_reg(dsidev, DSI_CLK_CTRL), 29, 28) != state) { + if (++t > 1000) { + DSSERR("Failed to set DSI PLL power mode to %d\n", + state); + return -ENODEV; + } + udelay(1); + } + + return 0; +} + +unsigned long dsi_get_pll_clkin(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + return clk_get_rate(dsi->sys_clk); +} + +bool dsi_hsdiv_calc(struct platform_device *dsidev, unsigned long pll, + unsigned long out_min, dsi_hsdiv_calc_func func, void *data) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int regm, regm_start, regm_stop; + unsigned long out_max; + unsigned long out; + + out_min = out_min ? out_min : 1; + out_max = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + + regm_start = max(DIV_ROUND_UP(pll, out_max), 1ul); + regm_stop = min(pll / out_min, dsi->regm_dispc_max); + + for (regm = regm_start; regm <= regm_stop; ++regm) { + out = pll / regm; + + if (func(regm, out, data)) + return true; + } + + return false; +} + +bool dsi_pll_calc(struct platform_device *dsidev, unsigned long clkin, + unsigned long pll_min, unsigned long pll_max, + dsi_pll_calc_func func, void *data) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int regn, regn_start, regn_stop; + int regm, regm_start, regm_stop; + unsigned long fint, pll; + const unsigned long pll_hw_max = 1800000000; + unsigned long fint_hw_min, fint_hw_max; + + fint_hw_min = dsi->fint_min; + fint_hw_max = dsi->fint_max; + + regn_start = max(DIV_ROUND_UP(clkin, fint_hw_max), 1ul); + regn_stop = min(clkin / fint_hw_min, dsi->regn_max); + + pll_max = pll_max ? pll_max : ULONG_MAX; + + for (regn = regn_start; regn <= regn_stop; ++regn) { + fint = clkin / regn; + + regm_start = max(DIV_ROUND_UP(DIV_ROUND_UP(pll_min, fint), 2), + 1ul); + regm_stop = min3(pll_max / fint / 2, + pll_hw_max / fint / 2, + dsi->regm_max); + + for (regm = regm_start; regm <= regm_stop; ++regm) { + pll = 2 * regm * fint; + + if (func(regn, regm, fint, pll, data)) + return true; + } + } + + return false; +} + +/* calculate clock rates using dividers in cinfo */ +static int dsi_calc_clock_rates(struct platform_device *dsidev, + struct dsi_clock_info *cinfo) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (cinfo->regn == 0 || cinfo->regn > dsi->regn_max) + return -EINVAL; + + if (cinfo->regm == 0 || cinfo->regm > dsi->regm_max) + return -EINVAL; + + if (cinfo->regm_dispc > dsi->regm_dispc_max) + return -EINVAL; + + if (cinfo->regm_dsi > dsi->regm_dsi_max) + return -EINVAL; + + cinfo->clkin = clk_get_rate(dsi->sys_clk); + cinfo->fint = cinfo->clkin / cinfo->regn; + + if (cinfo->fint > dsi->fint_max || cinfo->fint < dsi->fint_min) + return -EINVAL; + + cinfo->clkin4ddr = 2 * cinfo->regm * cinfo->fint; + + if (cinfo->clkin4ddr > 1800 * 1000 * 1000) + return -EINVAL; + + if (cinfo->regm_dispc > 0) + cinfo->dsi_pll_hsdiv_dispc_clk = + cinfo->clkin4ddr / cinfo->regm_dispc; + else + cinfo->dsi_pll_hsdiv_dispc_clk = 0; + + if (cinfo->regm_dsi > 0) + cinfo->dsi_pll_hsdiv_dsi_clk = + cinfo->clkin4ddr / cinfo->regm_dsi; + else + cinfo->dsi_pll_hsdiv_dsi_clk = 0; + + return 0; +} + +static void dsi_pll_calc_dsi_fck(struct dsi_clock_info *cinfo) +{ + unsigned long max_dsi_fck; + + max_dsi_fck = dss_feat_get_param_max(FEAT_PARAM_DSI_FCK); + + cinfo->regm_dsi = DIV_ROUND_UP(cinfo->clkin4ddr, max_dsi_fck); + cinfo->dsi_pll_hsdiv_dsi_clk = cinfo->clkin4ddr / cinfo->regm_dsi; +} + +int dsi_pll_set_clock_div(struct platform_device *dsidev, + struct dsi_clock_info *cinfo) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r = 0; + u32 l; + int f = 0; + u8 regn_start, regn_end, regm_start, regm_end; + u8 regm_dispc_start, regm_dispc_end, regm_dsi_start, regm_dsi_end; + + DSSDBG("DSI PLL clock config starts"); + + dsi->current_cinfo.clkin = cinfo->clkin; + dsi->current_cinfo.fint = cinfo->fint; + dsi->current_cinfo.clkin4ddr = cinfo->clkin4ddr; + dsi->current_cinfo.dsi_pll_hsdiv_dispc_clk = + cinfo->dsi_pll_hsdiv_dispc_clk; + dsi->current_cinfo.dsi_pll_hsdiv_dsi_clk = + cinfo->dsi_pll_hsdiv_dsi_clk; + + dsi->current_cinfo.regn = cinfo->regn; + dsi->current_cinfo.regm = cinfo->regm; + dsi->current_cinfo.regm_dispc = cinfo->regm_dispc; + dsi->current_cinfo.regm_dsi = cinfo->regm_dsi; + + DSSDBG("DSI Fint %ld\n", cinfo->fint); + + DSSDBG("clkin rate %ld\n", cinfo->clkin); + + /* DSIPHY == CLKIN4DDR */ + DSSDBG("CLKIN4DDR = 2 * %d / %d * %lu = %lu\n", + cinfo->regm, + cinfo->regn, + cinfo->clkin, + cinfo->clkin4ddr); + + DSSDBG("Data rate on 1 DSI lane %ld Mbps\n", + cinfo->clkin4ddr / 1000 / 1000 / 2); + + DSSDBG("Clock lane freq %ld Hz\n", cinfo->clkin4ddr / 4); + + DSSDBG("regm_dispc = %d, %s (%s) = %lu\n", cinfo->regm_dispc, + dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC), + dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC), + cinfo->dsi_pll_hsdiv_dispc_clk); + DSSDBG("regm_dsi = %d, %s (%s) = %lu\n", cinfo->regm_dsi, + dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI), + dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI), + cinfo->dsi_pll_hsdiv_dsi_clk); + + dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGN, ®n_start, ®n_end); + dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGM, ®m_start, ®m_end); + dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGM_DISPC, ®m_dispc_start, + ®m_dispc_end); + dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGM_DSI, ®m_dsi_start, + ®m_dsi_end); + + /* DSI_PLL_AUTOMODE = manual */ + REG_FLD_MOD(dsidev, DSI_PLL_CONTROL, 0, 0, 0); + + l = dsi_read_reg(dsidev, DSI_PLL_CONFIGURATION1); + l = FLD_MOD(l, 1, 0, 0); /* DSI_PLL_STOPMODE */ + /* DSI_PLL_REGN */ + l = FLD_MOD(l, cinfo->regn - 1, regn_start, regn_end); + /* DSI_PLL_REGM */ + l = FLD_MOD(l, cinfo->regm, regm_start, regm_end); + /* DSI_CLOCK_DIV */ + l = FLD_MOD(l, cinfo->regm_dispc > 0 ? cinfo->regm_dispc - 1 : 0, + regm_dispc_start, regm_dispc_end); + /* DSIPROTO_CLOCK_DIV */ + l = FLD_MOD(l, cinfo->regm_dsi > 0 ? cinfo->regm_dsi - 1 : 0, + regm_dsi_start, regm_dsi_end); + dsi_write_reg(dsidev, DSI_PLL_CONFIGURATION1, l); + + BUG_ON(cinfo->fint < dsi->fint_min || cinfo->fint > dsi->fint_max); + + l = dsi_read_reg(dsidev, DSI_PLL_CONFIGURATION2); + + if (dss_has_feature(FEAT_DSI_PLL_FREQSEL)) { + f = cinfo->fint < 1000000 ? 0x3 : + cinfo->fint < 1250000 ? 0x4 : + cinfo->fint < 1500000 ? 0x5 : + cinfo->fint < 1750000 ? 0x6 : + 0x7; + + l = FLD_MOD(l, f, 4, 1); /* DSI_PLL_FREQSEL */ + } else if (dss_has_feature(FEAT_DSI_PLL_SELFREQDCO)) { + f = cinfo->clkin4ddr < 1000000000 ? 0x2 : 0x4; + + l = FLD_MOD(l, f, 4, 1); /* PLL_SELFREQDCO */ + } + + l = FLD_MOD(l, 1, 13, 13); /* DSI_PLL_REFEN */ + l = FLD_MOD(l, 0, 14, 14); /* DSIPHY_CLKINEN */ + l = FLD_MOD(l, 1, 20, 20); /* DSI_HSDIVBYPASS */ + if (dss_has_feature(FEAT_DSI_PLL_REFSEL)) + l = FLD_MOD(l, 3, 22, 21); /* REF_SYSCLK = sysclk */ + dsi_write_reg(dsidev, DSI_PLL_CONFIGURATION2, l); + + REG_FLD_MOD(dsidev, DSI_PLL_GO, 1, 0, 0); /* DSI_PLL_GO */ + + if (wait_for_bit_change(dsidev, DSI_PLL_GO, 0, 0) != 0) { + DSSERR("dsi pll go bit not going down.\n"); + r = -EIO; + goto err; + } + + if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 1, 1) != 1) { + DSSERR("cannot lock PLL\n"); + r = -EIO; + goto err; + } + + dsi->pll_locked = 1; + + l = dsi_read_reg(dsidev, DSI_PLL_CONFIGURATION2); + l = FLD_MOD(l, 0, 0, 0); /* DSI_PLL_IDLE */ + l = FLD_MOD(l, 0, 5, 5); /* DSI_PLL_PLLLPMODE */ + l = FLD_MOD(l, 0, 6, 6); /* DSI_PLL_LOWCURRSTBY */ + l = FLD_MOD(l, 0, 7, 7); /* DSI_PLL_TIGHTPHASELOCK */ + l = FLD_MOD(l, 0, 8, 8); /* DSI_PLL_DRIFTGUARDEN */ + l = FLD_MOD(l, 0, 10, 9); /* DSI_PLL_LOCKSEL */ + l = FLD_MOD(l, 1, 13, 13); /* DSI_PLL_REFEN */ + l = FLD_MOD(l, 1, 14, 14); /* DSIPHY_CLKINEN */ + l = FLD_MOD(l, 0, 15, 15); /* DSI_BYPASSEN */ + l = FLD_MOD(l, 1, 16, 16); /* DSS_CLOCK_EN */ + l = FLD_MOD(l, 0, 17, 17); /* DSS_CLOCK_PWDN */ + l = FLD_MOD(l, 1, 18, 18); /* DSI_PROTO_CLOCK_EN */ + l = FLD_MOD(l, 0, 19, 19); /* DSI_PROTO_CLOCK_PWDN */ + l = FLD_MOD(l, 0, 20, 20); /* DSI_HSDIVBYPASS */ + dsi_write_reg(dsidev, DSI_PLL_CONFIGURATION2, l); + + DSSDBG("PLL config done\n"); +err: + return r; +} + +int dsi_pll_init(struct platform_device *dsidev, bool enable_hsclk, + bool enable_hsdiv) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r = 0; + enum dsi_pll_power_state pwstate; + + DSSDBG("PLL init\n"); + + /* + * It seems that on many OMAPs we need to enable both to have a + * functional HSDivider. + */ + enable_hsclk = enable_hsdiv = true; + + r = dsi_regulator_init(dsidev); + if (r) + return r; + + dsi_enable_pll_clock(dsidev, 1); + /* + * Note: SCP CLK is not required on OMAP3, but it is required on OMAP4. + */ + dsi_enable_scp_clk(dsidev); + + if (!dsi->vdds_dsi_enabled) { + r = regulator_enable(dsi->vdds_dsi_reg); + if (r) + goto err0; + dsi->vdds_dsi_enabled = true; + } + + /* XXX PLL does not come out of reset without this... */ + dispc_pck_free_enable(1); + + if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 0, 1) != 1) { + DSSERR("PLL not coming out of reset.\n"); + r = -ENODEV; + dispc_pck_free_enable(0); + goto err1; + } + + /* XXX ... but if left on, we get problems when planes do not + * fill the whole display. No idea about this */ + dispc_pck_free_enable(0); + + if (enable_hsclk && enable_hsdiv) + pwstate = DSI_PLL_POWER_ON_ALL; + else if (enable_hsclk) + pwstate = DSI_PLL_POWER_ON_HSCLK; + else if (enable_hsdiv) + pwstate = DSI_PLL_POWER_ON_DIV; + else + pwstate = DSI_PLL_POWER_OFF; + + r = dsi_pll_power(dsidev, pwstate); + + if (r) + goto err1; + + DSSDBG("PLL init done\n"); + + return 0; +err1: + if (dsi->vdds_dsi_enabled) { + regulator_disable(dsi->vdds_dsi_reg); + dsi->vdds_dsi_enabled = false; + } +err0: + dsi_disable_scp_clk(dsidev); + dsi_enable_pll_clock(dsidev, 0); + return r; +} + +void dsi_pll_uninit(struct platform_device *dsidev, bool disconnect_lanes) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + dsi->pll_locked = 0; + dsi_pll_power(dsidev, DSI_PLL_POWER_OFF); + if (disconnect_lanes) { + WARN_ON(!dsi->vdds_dsi_enabled); + regulator_disable(dsi->vdds_dsi_reg); + dsi->vdds_dsi_enabled = false; + } + + dsi_disable_scp_clk(dsidev); + dsi_enable_pll_clock(dsidev, 0); + + DSSDBG("PLL uninit done\n"); +} + +static void dsi_dump_dsidev_clocks(struct platform_device *dsidev, + struct seq_file *s) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct dsi_clock_info *cinfo = &dsi->current_cinfo; + enum omap_dss_clk_source dispc_clk_src, dsi_clk_src; + int dsi_module = dsi->module_id; + + dispc_clk_src = dss_get_dispc_clk_source(); + dsi_clk_src = dss_get_dsi_clk_source(dsi_module); + + if (dsi_runtime_get(dsidev)) + return; + + seq_printf(s, "- DSI%d PLL -\n", dsi_module + 1); + + seq_printf(s, "dsi pll clkin\t%lu\n", cinfo->clkin); + + seq_printf(s, "Fint\t\t%-16luregn %u\n", cinfo->fint, cinfo->regn); + + seq_printf(s, "CLKIN4DDR\t%-16luregm %u\n", + cinfo->clkin4ddr, cinfo->regm); + + seq_printf(s, "DSI_PLL_HSDIV_DISPC (%s)\t%-16luregm_dispc %u\t(%s)\n", + dss_feat_get_clk_source_name(dsi_module == 0 ? + OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC : + OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC), + cinfo->dsi_pll_hsdiv_dispc_clk, + cinfo->regm_dispc, + dispc_clk_src == OMAP_DSS_CLK_SRC_FCK ? + "off" : "on"); + + seq_printf(s, "DSI_PLL_HSDIV_DSI (%s)\t%-16luregm_dsi %u\t(%s)\n", + dss_feat_get_clk_source_name(dsi_module == 0 ? + OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI : + OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI), + cinfo->dsi_pll_hsdiv_dsi_clk, + cinfo->regm_dsi, + dsi_clk_src == OMAP_DSS_CLK_SRC_FCK ? + "off" : "on"); + + seq_printf(s, "- DSI%d -\n", dsi_module + 1); + + seq_printf(s, "dsi fclk source = %s (%s)\n", + dss_get_generic_clk_source_name(dsi_clk_src), + dss_feat_get_clk_source_name(dsi_clk_src)); + + seq_printf(s, "DSI_FCLK\t%lu\n", dsi_fclk_rate(dsidev)); + + seq_printf(s, "DDR_CLK\t\t%lu\n", + cinfo->clkin4ddr / 4); + + seq_printf(s, "TxByteClkHS\t%lu\n", dsi_get_txbyteclkhs(dsidev)); + + seq_printf(s, "LP_CLK\t\t%lu\n", cinfo->lp_clk); + + dsi_runtime_put(dsidev); +} + +void dsi_dump_clocks(struct seq_file *s) +{ + struct platform_device *dsidev; + int i; + + for (i = 0; i < MAX_NUM_DSI; i++) { + dsidev = dsi_get_dsidev_from_id(i); + if (dsidev) + dsi_dump_dsidev_clocks(dsidev, s); + } +} + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static void dsi_dump_dsidev_irqs(struct platform_device *dsidev, + struct seq_file *s) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned long flags; + struct dsi_irq_stats stats; + + spin_lock_irqsave(&dsi->irq_stats_lock, flags); + + stats = dsi->irq_stats; + memset(&dsi->irq_stats, 0, sizeof(dsi->irq_stats)); + dsi->irq_stats.last_reset = jiffies; + + spin_unlock_irqrestore(&dsi->irq_stats_lock, flags); + + seq_printf(s, "period %u ms\n", + jiffies_to_msecs(jiffies - stats.last_reset)); + + seq_printf(s, "irqs %d\n", stats.irq_count); +#define PIS(x) \ + seq_printf(s, "%-20s %10d\n", #x, stats.dsi_irqs[ffs(DSI_IRQ_##x)-1]); + + seq_printf(s, "-- DSI%d interrupts --\n", dsi->module_id + 1); + PIS(VC0); + PIS(VC1); + PIS(VC2); + PIS(VC3); + PIS(WAKEUP); + PIS(RESYNC); + PIS(PLL_LOCK); + PIS(PLL_UNLOCK); + PIS(PLL_RECALL); + PIS(COMPLEXIO_ERR); + PIS(HS_TX_TIMEOUT); + PIS(LP_RX_TIMEOUT); + PIS(TE_TRIGGER); + PIS(ACK_TRIGGER); + PIS(SYNC_LOST); + PIS(LDO_POWER_GOOD); + PIS(TA_TIMEOUT); +#undef PIS + +#define PIS(x) \ + seq_printf(s, "%-20s %10d %10d %10d %10d\n", #x, \ + stats.vc_irqs[0][ffs(DSI_VC_IRQ_##x)-1], \ + stats.vc_irqs[1][ffs(DSI_VC_IRQ_##x)-1], \ + stats.vc_irqs[2][ffs(DSI_VC_IRQ_##x)-1], \ + stats.vc_irqs[3][ffs(DSI_VC_IRQ_##x)-1]); + + seq_printf(s, "-- VC interrupts --\n"); + PIS(CS); + PIS(ECC_CORR); + PIS(PACKET_SENT); + PIS(FIFO_TX_OVF); + PIS(FIFO_RX_OVF); + PIS(BTA); + PIS(ECC_NO_CORR); + PIS(FIFO_TX_UDF); + PIS(PP_BUSY_CHANGE); +#undef PIS + +#define PIS(x) \ + seq_printf(s, "%-20s %10d\n", #x, \ + stats.cio_irqs[ffs(DSI_CIO_IRQ_##x)-1]); + + seq_printf(s, "-- CIO interrupts --\n"); + PIS(ERRSYNCESC1); + PIS(ERRSYNCESC2); + PIS(ERRSYNCESC3); + PIS(ERRESC1); + PIS(ERRESC2); + PIS(ERRESC3); + PIS(ERRCONTROL1); + PIS(ERRCONTROL2); + PIS(ERRCONTROL3); + PIS(STATEULPS1); + PIS(STATEULPS2); + PIS(STATEULPS3); + PIS(ERRCONTENTIONLP0_1); + PIS(ERRCONTENTIONLP1_1); + PIS(ERRCONTENTIONLP0_2); + PIS(ERRCONTENTIONLP1_2); + PIS(ERRCONTENTIONLP0_3); + PIS(ERRCONTENTIONLP1_3); + PIS(ULPSACTIVENOT_ALL0); + PIS(ULPSACTIVENOT_ALL1); +#undef PIS +} + +static void dsi1_dump_irqs(struct seq_file *s) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_id(0); + + dsi_dump_dsidev_irqs(dsidev, s); +} + +static void dsi2_dump_irqs(struct seq_file *s) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_id(1); + + dsi_dump_dsidev_irqs(dsidev, s); +} +#endif + +static void dsi_dump_dsidev_regs(struct platform_device *dsidev, + struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, dsi_read_reg(dsidev, r)) + + if (dsi_runtime_get(dsidev)) + return; + dsi_enable_scp_clk(dsidev); + + DUMPREG(DSI_REVISION); + DUMPREG(DSI_SYSCONFIG); + DUMPREG(DSI_SYSSTATUS); + DUMPREG(DSI_IRQSTATUS); + DUMPREG(DSI_IRQENABLE); + DUMPREG(DSI_CTRL); + DUMPREG(DSI_COMPLEXIO_CFG1); + DUMPREG(DSI_COMPLEXIO_IRQ_STATUS); + DUMPREG(DSI_COMPLEXIO_IRQ_ENABLE); + DUMPREG(DSI_CLK_CTRL); + DUMPREG(DSI_TIMING1); + DUMPREG(DSI_TIMING2); + DUMPREG(DSI_VM_TIMING1); + DUMPREG(DSI_VM_TIMING2); + DUMPREG(DSI_VM_TIMING3); + DUMPREG(DSI_CLK_TIMING); + DUMPREG(DSI_TX_FIFO_VC_SIZE); + DUMPREG(DSI_RX_FIFO_VC_SIZE); + DUMPREG(DSI_COMPLEXIO_CFG2); + DUMPREG(DSI_RX_FIFO_VC_FULLNESS); + DUMPREG(DSI_VM_TIMING4); + DUMPREG(DSI_TX_FIFO_VC_EMPTINESS); + DUMPREG(DSI_VM_TIMING5); + DUMPREG(DSI_VM_TIMING6); + DUMPREG(DSI_VM_TIMING7); + DUMPREG(DSI_STOPCLK_TIMING); + + DUMPREG(DSI_VC_CTRL(0)); + DUMPREG(DSI_VC_TE(0)); + DUMPREG(DSI_VC_LONG_PACKET_HEADER(0)); + DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(0)); + DUMPREG(DSI_VC_SHORT_PACKET_HEADER(0)); + DUMPREG(DSI_VC_IRQSTATUS(0)); + DUMPREG(DSI_VC_IRQENABLE(0)); + + DUMPREG(DSI_VC_CTRL(1)); + DUMPREG(DSI_VC_TE(1)); + DUMPREG(DSI_VC_LONG_PACKET_HEADER(1)); + DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(1)); + DUMPREG(DSI_VC_SHORT_PACKET_HEADER(1)); + DUMPREG(DSI_VC_IRQSTATUS(1)); + DUMPREG(DSI_VC_IRQENABLE(1)); + + DUMPREG(DSI_VC_CTRL(2)); + DUMPREG(DSI_VC_TE(2)); + DUMPREG(DSI_VC_LONG_PACKET_HEADER(2)); + DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(2)); + DUMPREG(DSI_VC_SHORT_PACKET_HEADER(2)); + DUMPREG(DSI_VC_IRQSTATUS(2)); + DUMPREG(DSI_VC_IRQENABLE(2)); + + DUMPREG(DSI_VC_CTRL(3)); + DUMPREG(DSI_VC_TE(3)); + DUMPREG(DSI_VC_LONG_PACKET_HEADER(3)); + DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(3)); + DUMPREG(DSI_VC_SHORT_PACKET_HEADER(3)); + DUMPREG(DSI_VC_IRQSTATUS(3)); + DUMPREG(DSI_VC_IRQENABLE(3)); + + DUMPREG(DSI_DSIPHY_CFG0); + DUMPREG(DSI_DSIPHY_CFG1); + DUMPREG(DSI_DSIPHY_CFG2); + DUMPREG(DSI_DSIPHY_CFG5); + + DUMPREG(DSI_PLL_CONTROL); + DUMPREG(DSI_PLL_STATUS); + DUMPREG(DSI_PLL_GO); + DUMPREG(DSI_PLL_CONFIGURATION1); + DUMPREG(DSI_PLL_CONFIGURATION2); + + dsi_disable_scp_clk(dsidev); + dsi_runtime_put(dsidev); +#undef DUMPREG +} + +static void dsi1_dump_regs(struct seq_file *s) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_id(0); + + dsi_dump_dsidev_regs(dsidev, s); +} + +static void dsi2_dump_regs(struct seq_file *s) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_id(1); + + dsi_dump_dsidev_regs(dsidev, s); +} + +enum dsi_cio_power_state { + DSI_COMPLEXIO_POWER_OFF = 0x0, + DSI_COMPLEXIO_POWER_ON = 0x1, + DSI_COMPLEXIO_POWER_ULPS = 0x2, +}; + +static int dsi_cio_power(struct platform_device *dsidev, + enum dsi_cio_power_state state) +{ + int t = 0; + + /* PWR_CMD */ + REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG1, state, 28, 27); + + /* PWR_STATUS */ + while (FLD_GET(dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG1), + 26, 25) != state) { + if (++t > 1000) { + DSSERR("failed to set complexio power state to " + "%d\n", state); + return -ENODEV; + } + udelay(1); + } + + return 0; +} + +static unsigned dsi_get_line_buf_size(struct platform_device *dsidev) +{ + int val; + + /* line buffer on OMAP3 is 1024 x 24bits */ + /* XXX: for some reason using full buffer size causes + * considerable TX slowdown with update sizes that fill the + * whole buffer */ + if (!dss_has_feature(FEAT_DSI_GNQ)) + return 1023 * 3; + + val = REG_GET(dsidev, DSI_GNQ, 14, 12); /* VP1_LINE_BUFFER_SIZE */ + + switch (val) { + case 1: + return 512 * 3; /* 512x24 bits */ + case 2: + return 682 * 3; /* 682x24 bits */ + case 3: + return 853 * 3; /* 853x24 bits */ + case 4: + return 1024 * 3; /* 1024x24 bits */ + case 5: + return 1194 * 3; /* 1194x24 bits */ + case 6: + return 1365 * 3; /* 1365x24 bits */ + case 7: + return 1920 * 3; /* 1920x24 bits */ + default: + BUG(); + return 0; + } +} + +static int dsi_set_lane_config(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + static const u8 offsets[] = { 0, 4, 8, 12, 16 }; + static const enum dsi_lane_function functions[] = { + DSI_LANE_CLK, + DSI_LANE_DATA1, + DSI_LANE_DATA2, + DSI_LANE_DATA3, + DSI_LANE_DATA4, + }; + u32 r; + int i; + + r = dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG1); + + for (i = 0; i < dsi->num_lanes_used; ++i) { + unsigned offset = offsets[i]; + unsigned polarity, lane_number; + unsigned t; + + for (t = 0; t < dsi->num_lanes_supported; ++t) + if (dsi->lanes[t].function == functions[i]) + break; + + if (t == dsi->num_lanes_supported) + return -EINVAL; + + lane_number = t; + polarity = dsi->lanes[t].polarity; + + r = FLD_MOD(r, lane_number + 1, offset + 2, offset); + r = FLD_MOD(r, polarity, offset + 3, offset + 3); + } + + /* clear the unused lanes */ + for (; i < dsi->num_lanes_supported; ++i) { + unsigned offset = offsets[i]; + + r = FLD_MOD(r, 0, offset + 2, offset); + r = FLD_MOD(r, 0, offset + 3, offset + 3); + } + + dsi_write_reg(dsidev, DSI_COMPLEXIO_CFG1, r); + + return 0; +} + +static inline unsigned ns2ddr(struct platform_device *dsidev, unsigned ns) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + /* convert time in ns to ddr ticks, rounding up */ + unsigned long ddr_clk = dsi->current_cinfo.clkin4ddr / 4; + return (ns * (ddr_clk / 1000 / 1000) + 999) / 1000; +} + +static inline unsigned ddr2ns(struct platform_device *dsidev, unsigned ddr) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + unsigned long ddr_clk = dsi->current_cinfo.clkin4ddr / 4; + return ddr * 1000 * 1000 / (ddr_clk / 1000); +} + +static void dsi_cio_timings(struct platform_device *dsidev) +{ + u32 r; + u32 ths_prepare, ths_prepare_ths_zero, ths_trail, ths_exit; + u32 tlpx_half, tclk_trail, tclk_zero; + u32 tclk_prepare; + + /* calculate timings */ + + /* 1 * DDR_CLK = 2 * UI */ + + /* min 40ns + 4*UI max 85ns + 6*UI */ + ths_prepare = ns2ddr(dsidev, 70) + 2; + + /* min 145ns + 10*UI */ + ths_prepare_ths_zero = ns2ddr(dsidev, 175) + 2; + + /* min max(8*UI, 60ns+4*UI) */ + ths_trail = ns2ddr(dsidev, 60) + 5; + + /* min 100ns */ + ths_exit = ns2ddr(dsidev, 145); + + /* tlpx min 50n */ + tlpx_half = ns2ddr(dsidev, 25); + + /* min 60ns */ + tclk_trail = ns2ddr(dsidev, 60) + 2; + + /* min 38ns, max 95ns */ + tclk_prepare = ns2ddr(dsidev, 65); + + /* min tclk-prepare + tclk-zero = 300ns */ + tclk_zero = ns2ddr(dsidev, 260); + + DSSDBG("ths_prepare %u (%uns), ths_prepare_ths_zero %u (%uns)\n", + ths_prepare, ddr2ns(dsidev, ths_prepare), + ths_prepare_ths_zero, ddr2ns(dsidev, ths_prepare_ths_zero)); + DSSDBG("ths_trail %u (%uns), ths_exit %u (%uns)\n", + ths_trail, ddr2ns(dsidev, ths_trail), + ths_exit, ddr2ns(dsidev, ths_exit)); + + DSSDBG("tlpx_half %u (%uns), tclk_trail %u (%uns), " + "tclk_zero %u (%uns)\n", + tlpx_half, ddr2ns(dsidev, tlpx_half), + tclk_trail, ddr2ns(dsidev, tclk_trail), + tclk_zero, ddr2ns(dsidev, tclk_zero)); + DSSDBG("tclk_prepare %u (%uns)\n", + tclk_prepare, ddr2ns(dsidev, tclk_prepare)); + + /* program timings */ + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG0); + r = FLD_MOD(r, ths_prepare, 31, 24); + r = FLD_MOD(r, ths_prepare_ths_zero, 23, 16); + r = FLD_MOD(r, ths_trail, 15, 8); + r = FLD_MOD(r, ths_exit, 7, 0); + dsi_write_reg(dsidev, DSI_DSIPHY_CFG0, r); + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG1); + r = FLD_MOD(r, tlpx_half, 20, 16); + r = FLD_MOD(r, tclk_trail, 15, 8); + r = FLD_MOD(r, tclk_zero, 7, 0); + + if (dss_has_feature(FEAT_DSI_PHY_DCC)) { + r = FLD_MOD(r, 0, 21, 21); /* DCCEN = disable */ + r = FLD_MOD(r, 1, 22, 22); /* CLKINP_DIVBY2EN = enable */ + r = FLD_MOD(r, 1, 23, 23); /* CLKINP_SEL = enable */ + } + + dsi_write_reg(dsidev, DSI_DSIPHY_CFG1, r); + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG2); + r = FLD_MOD(r, tclk_prepare, 7, 0); + dsi_write_reg(dsidev, DSI_DSIPHY_CFG2, r); +} + +/* lane masks have lane 0 at lsb. mask_p for positive lines, n for negative */ +static void dsi_cio_enable_lane_override(struct platform_device *dsidev, + unsigned mask_p, unsigned mask_n) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int i; + u32 l; + u8 lptxscp_start = dsi->num_lanes_supported == 3 ? 22 : 26; + + l = 0; + + for (i = 0; i < dsi->num_lanes_supported; ++i) { + unsigned p = dsi->lanes[i].polarity; + + if (mask_p & (1 << i)) + l |= 1 << (i * 2 + (p ? 0 : 1)); + + if (mask_n & (1 << i)) + l |= 1 << (i * 2 + (p ? 1 : 0)); + } + + /* + * Bits in REGLPTXSCPDAT4TO0DXDY: + * 17: DY0 18: DX0 + * 19: DY1 20: DX1 + * 21: DY2 22: DX2 + * 23: DY3 24: DX3 + * 25: DY4 26: DX4 + */ + + /* Set the lane override configuration */ + + /* REGLPTXSCPDAT4TO0DXDY */ + REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, l, lptxscp_start, 17); + + /* Enable lane override */ + + /* ENLPTXSCPDAT */ + REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, 1, 27, 27); +} + +static void dsi_cio_disable_lane_override(struct platform_device *dsidev) +{ + /* Disable lane override */ + REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, 0, 27, 27); /* ENLPTXSCPDAT */ + /* Reset the lane override configuration */ + /* REGLPTXSCPDAT4TO0DXDY */ + REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, 0, 22, 17); +} + +static int dsi_cio_wait_tx_clk_esc_reset(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int t, i; + bool in_use[DSI_MAX_NR_LANES]; + static const u8 offsets_old[] = { 28, 27, 26 }; + static const u8 offsets_new[] = { 24, 25, 26, 27, 28 }; + const u8 *offsets; + + if (dss_has_feature(FEAT_DSI_REVERSE_TXCLKESC)) + offsets = offsets_old; + else + offsets = offsets_new; + + for (i = 0; i < dsi->num_lanes_supported; ++i) + in_use[i] = dsi->lanes[i].function != DSI_LANE_UNUSED; + + t = 100000; + while (true) { + u32 l; + int ok; + + l = dsi_read_reg(dsidev, DSI_DSIPHY_CFG5); + + ok = 0; + for (i = 0; i < dsi->num_lanes_supported; ++i) { + if (!in_use[i] || (l & (1 << offsets[i]))) + ok++; + } + + if (ok == dsi->num_lanes_supported) + break; + + if (--t == 0) { + for (i = 0; i < dsi->num_lanes_supported; ++i) { + if (!in_use[i] || (l & (1 << offsets[i]))) + continue; + + DSSERR("CIO TXCLKESC%d domain not coming " \ + "out of reset\n", i); + } + return -EIO; + } + } + + return 0; +} + +/* return bitmask of enabled lanes, lane0 being the lsb */ +static unsigned dsi_get_lane_mask(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned mask = 0; + int i; + + for (i = 0; i < dsi->num_lanes_supported; ++i) { + if (dsi->lanes[i].function != DSI_LANE_UNUSED) + mask |= 1 << i; + } + + return mask; +} + +static int dsi_cio_init(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r; + u32 l; + + DSSDBG("DSI CIO init starts"); + + r = dss_dsi_enable_pads(dsi->module_id, dsi_get_lane_mask(dsidev)); + if (r) + return r; + + dsi_enable_scp_clk(dsidev); + + /* A dummy read using the SCP interface to any DSIPHY register is + * required after DSIPHY reset to complete the reset of the DSI complex + * I/O. */ + dsi_read_reg(dsidev, DSI_DSIPHY_CFG5); + + if (wait_for_bit_change(dsidev, DSI_DSIPHY_CFG5, 30, 1) != 1) { + DSSERR("CIO SCP Clock domain not coming out of reset.\n"); + r = -EIO; + goto err_scp_clk_dom; + } + + r = dsi_set_lane_config(dsidev); + if (r) + goto err_scp_clk_dom; + + /* set TX STOP MODE timer to maximum for this operation */ + l = dsi_read_reg(dsidev, DSI_TIMING1); + l = FLD_MOD(l, 1, 15, 15); /* FORCE_TX_STOP_MODE_IO */ + l = FLD_MOD(l, 1, 14, 14); /* STOP_STATE_X16_IO */ + l = FLD_MOD(l, 1, 13, 13); /* STOP_STATE_X4_IO */ + l = FLD_MOD(l, 0x1fff, 12, 0); /* STOP_STATE_COUNTER_IO */ + dsi_write_reg(dsidev, DSI_TIMING1, l); + + if (dsi->ulps_enabled) { + unsigned mask_p; + int i; + + DSSDBG("manual ulps exit\n"); + + /* ULPS is exited by Mark-1 state for 1ms, followed by + * stop state. DSS HW cannot do this via the normal + * ULPS exit sequence, as after reset the DSS HW thinks + * that we are not in ULPS mode, and refuses to send the + * sequence. So we need to send the ULPS exit sequence + * manually by setting positive lines high and negative lines + * low for 1ms. + */ + + mask_p = 0; + + for (i = 0; i < dsi->num_lanes_supported; ++i) { + if (dsi->lanes[i].function == DSI_LANE_UNUSED) + continue; + mask_p |= 1 << i; + } + + dsi_cio_enable_lane_override(dsidev, mask_p, 0); + } + + r = dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_ON); + if (r) + goto err_cio_pwr; + + if (wait_for_bit_change(dsidev, DSI_COMPLEXIO_CFG1, 29, 1) != 1) { + DSSERR("CIO PWR clock domain not coming out of reset.\n"); + r = -ENODEV; + goto err_cio_pwr_dom; + } + + dsi_if_enable(dsidev, true); + dsi_if_enable(dsidev, false); + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 1, 20, 20); /* LP_CLK_ENABLE */ + + r = dsi_cio_wait_tx_clk_esc_reset(dsidev); + if (r) + goto err_tx_clk_esc_rst; + + if (dsi->ulps_enabled) { + /* Keep Mark-1 state for 1ms (as per DSI spec) */ + ktime_t wait = ns_to_ktime(1000 * 1000); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_hrtimeout(&wait, HRTIMER_MODE_REL); + + /* Disable the override. The lanes should be set to Mark-11 + * state by the HW */ + dsi_cio_disable_lane_override(dsidev); + } + + /* FORCE_TX_STOP_MODE_IO */ + REG_FLD_MOD(dsidev, DSI_TIMING1, 0, 15, 15); + + dsi_cio_timings(dsidev); + + if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { + /* DDR_CLK_ALWAYS_ON */ + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, + dsi->vm_timings.ddr_clk_always_on, 13, 13); + } + + dsi->ulps_enabled = false; + + DSSDBG("CIO init done\n"); + + return 0; + +err_tx_clk_esc_rst: + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 20, 20); /* LP_CLK_ENABLE */ +err_cio_pwr_dom: + dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_OFF); +err_cio_pwr: + if (dsi->ulps_enabled) + dsi_cio_disable_lane_override(dsidev); +err_scp_clk_dom: + dsi_disable_scp_clk(dsidev); + dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(dsidev)); + return r; +} + +static void dsi_cio_uninit(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + /* DDR_CLK_ALWAYS_ON */ + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 13, 13); + + dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_OFF); + dsi_disable_scp_clk(dsidev); + dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(dsidev)); +} + +static void dsi_config_tx_fifo(struct platform_device *dsidev, + enum fifo_size size1, enum fifo_size size2, + enum fifo_size size3, enum fifo_size size4) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 r = 0; + int add = 0; + int i; + + dsi->vc[0].tx_fifo_size = size1; + dsi->vc[1].tx_fifo_size = size2; + dsi->vc[2].tx_fifo_size = size3; + dsi->vc[3].tx_fifo_size = size4; + + for (i = 0; i < 4; i++) { + u8 v; + int size = dsi->vc[i].tx_fifo_size; + + if (add + size > 4) { + DSSERR("Illegal FIFO configuration\n"); + BUG(); + return; + } + + v = FLD_VAL(add, 2, 0) | FLD_VAL(size, 7, 4); + r |= v << (8 * i); + /*DSSDBG("TX FIFO vc %d: size %d, add %d\n", i, size, add); */ + add += size; + } + + dsi_write_reg(dsidev, DSI_TX_FIFO_VC_SIZE, r); +} + +static void dsi_config_rx_fifo(struct platform_device *dsidev, + enum fifo_size size1, enum fifo_size size2, + enum fifo_size size3, enum fifo_size size4) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 r = 0; + int add = 0; + int i; + + dsi->vc[0].rx_fifo_size = size1; + dsi->vc[1].rx_fifo_size = size2; + dsi->vc[2].rx_fifo_size = size3; + dsi->vc[3].rx_fifo_size = size4; + + for (i = 0; i < 4; i++) { + u8 v; + int size = dsi->vc[i].rx_fifo_size; + + if (add + size > 4) { + DSSERR("Illegal FIFO configuration\n"); + BUG(); + return; + } + + v = FLD_VAL(add, 2, 0) | FLD_VAL(size, 7, 4); + r |= v << (8 * i); + /*DSSDBG("RX FIFO vc %d: size %d, add %d\n", i, size, add); */ + add += size; + } + + dsi_write_reg(dsidev, DSI_RX_FIFO_VC_SIZE, r); +} + +static int dsi_force_tx_stop_mode_io(struct platform_device *dsidev) +{ + u32 r; + + r = dsi_read_reg(dsidev, DSI_TIMING1); + r = FLD_MOD(r, 1, 15, 15); /* FORCE_TX_STOP_MODE_IO */ + dsi_write_reg(dsidev, DSI_TIMING1, r); + + if (wait_for_bit_change(dsidev, DSI_TIMING1, 15, 0) != 0) { + DSSERR("TX_STOP bit not going down\n"); + return -EIO; + } + + return 0; +} + +static bool dsi_vc_is_enabled(struct platform_device *dsidev, int channel) +{ + return REG_GET(dsidev, DSI_VC_CTRL(channel), 0, 0); +} + +static void dsi_packet_sent_handler_vp(void *data, u32 mask) +{ + struct dsi_packet_sent_handler_data *vp_data = + (struct dsi_packet_sent_handler_data *) data; + struct dsi_data *dsi = dsi_get_dsidrv_data(vp_data->dsidev); + const int channel = dsi->update_channel; + u8 bit = dsi->te_enabled ? 30 : 31; + + if (REG_GET(vp_data->dsidev, DSI_VC_TE(channel), bit, bit) == 0) + complete(vp_data->completion); +} + +static int dsi_sync_vc_vp(struct platform_device *dsidev, int channel) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + DECLARE_COMPLETION_ONSTACK(completion); + struct dsi_packet_sent_handler_data vp_data = { dsidev, &completion }; + int r = 0; + u8 bit; + + bit = dsi->te_enabled ? 30 : 31; + + r = dsi_register_isr_vc(dsidev, channel, dsi_packet_sent_handler_vp, + &vp_data, DSI_VC_IRQ_PACKET_SENT); + if (r) + goto err0; + + /* Wait for completion only if TE_EN/TE_START is still set */ + if (REG_GET(dsidev, DSI_VC_TE(channel), bit, bit)) { + if (wait_for_completion_timeout(&completion, + msecs_to_jiffies(10)) == 0) { + DSSERR("Failed to complete previous frame transfer\n"); + r = -EIO; + goto err1; + } + } + + dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_vp, + &vp_data, DSI_VC_IRQ_PACKET_SENT); + + return 0; +err1: + dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_vp, + &vp_data, DSI_VC_IRQ_PACKET_SENT); +err0: + return r; +} + +static void dsi_packet_sent_handler_l4(void *data, u32 mask) +{ + struct dsi_packet_sent_handler_data *l4_data = + (struct dsi_packet_sent_handler_data *) data; + struct dsi_data *dsi = dsi_get_dsidrv_data(l4_data->dsidev); + const int channel = dsi->update_channel; + + if (REG_GET(l4_data->dsidev, DSI_VC_CTRL(channel), 5, 5) == 0) + complete(l4_data->completion); +} + +static int dsi_sync_vc_l4(struct platform_device *dsidev, int channel) +{ + DECLARE_COMPLETION_ONSTACK(completion); + struct dsi_packet_sent_handler_data l4_data = { dsidev, &completion }; + int r = 0; + + r = dsi_register_isr_vc(dsidev, channel, dsi_packet_sent_handler_l4, + &l4_data, DSI_VC_IRQ_PACKET_SENT); + if (r) + goto err0; + + /* Wait for completion only if TX_FIFO_NOT_EMPTY is still set */ + if (REG_GET(dsidev, DSI_VC_CTRL(channel), 5, 5)) { + if (wait_for_completion_timeout(&completion, + msecs_to_jiffies(10)) == 0) { + DSSERR("Failed to complete previous l4 transfer\n"); + r = -EIO; + goto err1; + } + } + + dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_l4, + &l4_data, DSI_VC_IRQ_PACKET_SENT); + + return 0; +err1: + dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_l4, + &l4_data, DSI_VC_IRQ_PACKET_SENT); +err0: + return r; +} + +static int dsi_sync_vc(struct platform_device *dsidev, int channel) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + WARN_ON(in_interrupt()); + + if (!dsi_vc_is_enabled(dsidev, channel)) + return 0; + + switch (dsi->vc[channel].source) { + case DSI_VC_SOURCE_VP: + return dsi_sync_vc_vp(dsidev, channel); + case DSI_VC_SOURCE_L4: + return dsi_sync_vc_l4(dsidev, channel); + default: + BUG(); + return -EINVAL; + } +} + +static int dsi_vc_enable(struct platform_device *dsidev, int channel, + bool enable) +{ + DSSDBG("dsi_vc_enable channel %d, enable %d\n", + channel, enable); + + enable = enable ? 1 : 0; + + REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), enable, 0, 0); + + if (wait_for_bit_change(dsidev, DSI_VC_CTRL(channel), + 0, enable) != enable) { + DSSERR("Failed to set dsi_vc_enable to %d\n", enable); + return -EIO; + } + + return 0; +} + +static void dsi_vc_initial_config(struct platform_device *dsidev, int channel) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 r; + + DSSDBG("Initial config of virtual channel %d", channel); + + r = dsi_read_reg(dsidev, DSI_VC_CTRL(channel)); + + if (FLD_GET(r, 15, 15)) /* VC_BUSY */ + DSSERR("VC(%d) busy when trying to configure it!\n", + channel); + + r = FLD_MOD(r, 0, 1, 1); /* SOURCE, 0 = L4 */ + r = FLD_MOD(r, 0, 2, 2); /* BTA_SHORT_EN */ + r = FLD_MOD(r, 0, 3, 3); /* BTA_LONG_EN */ + r = FLD_MOD(r, 0, 4, 4); /* MODE, 0 = command */ + r = FLD_MOD(r, 1, 7, 7); /* CS_TX_EN */ + r = FLD_MOD(r, 1, 8, 8); /* ECC_TX_EN */ + r = FLD_MOD(r, 0, 9, 9); /* MODE_SPEED, high speed on/off */ + if (dss_has_feature(FEAT_DSI_VC_OCP_WIDTH)) + r = FLD_MOD(r, 3, 11, 10); /* OCP_WIDTH = 32 bit */ + + r = FLD_MOD(r, 4, 29, 27); /* DMA_RX_REQ_NB = no dma */ + r = FLD_MOD(r, 4, 23, 21); /* DMA_TX_REQ_NB = no dma */ + + dsi_write_reg(dsidev, DSI_VC_CTRL(channel), r); + + dsi->vc[channel].source = DSI_VC_SOURCE_L4; +} + +static int dsi_vc_config_source(struct platform_device *dsidev, int channel, + enum dsi_vc_source source) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (dsi->vc[channel].source == source) + return 0; + + DSSDBG("Source config of virtual channel %d", channel); + + dsi_sync_vc(dsidev, channel); + + dsi_vc_enable(dsidev, channel, 0); + + /* VC_BUSY */ + if (wait_for_bit_change(dsidev, DSI_VC_CTRL(channel), 15, 0) != 0) { + DSSERR("vc(%d) busy when trying to config for VP\n", channel); + return -EIO; + } + + /* SOURCE, 0 = L4, 1 = video port */ + REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), source, 1, 1); + + /* DCS_CMD_ENABLE */ + if (dss_has_feature(FEAT_DSI_DCS_CMD_CONFIG_VC)) { + bool enable = source == DSI_VC_SOURCE_VP; + REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), enable, 30, 30); + } + + dsi_vc_enable(dsidev, channel, 1); + + dsi->vc[channel].source = source; + + return 0; +} + +static void dsi_vc_enable_hs(struct omap_dss_device *dssdev, int channel, + bool enable) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + DSSDBG("dsi_vc_enable_hs(%d, %d)\n", channel, enable); + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + dsi_vc_enable(dsidev, channel, 0); + dsi_if_enable(dsidev, 0); + + REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), enable, 9, 9); + + dsi_vc_enable(dsidev, channel, 1); + dsi_if_enable(dsidev, 1); + + dsi_force_tx_stop_mode_io(dsidev); + + /* start the DDR clock by sending a NULL packet */ + if (dsi->vm_timings.ddr_clk_always_on && enable) + dsi_vc_send_null(dssdev, channel); +} + +static void dsi_vc_flush_long_data(struct platform_device *dsidev, int channel) +{ + while (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { + u32 val; + val = dsi_read_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel)); + DSSDBG("\t\tb1 %#02x b2 %#02x b3 %#02x b4 %#02x\n", + (val >> 0) & 0xff, + (val >> 8) & 0xff, + (val >> 16) & 0xff, + (val >> 24) & 0xff); + } +} + +static void dsi_show_rx_ack_with_err(u16 err) +{ + DSSERR("\tACK with ERROR (%#x):\n", err); + if (err & (1 << 0)) + DSSERR("\t\tSoT Error\n"); + if (err & (1 << 1)) + DSSERR("\t\tSoT Sync Error\n"); + if (err & (1 << 2)) + DSSERR("\t\tEoT Sync Error\n"); + if (err & (1 << 3)) + DSSERR("\t\tEscape Mode Entry Command Error\n"); + if (err & (1 << 4)) + DSSERR("\t\tLP Transmit Sync Error\n"); + if (err & (1 << 5)) + DSSERR("\t\tHS Receive Timeout Error\n"); + if (err & (1 << 6)) + DSSERR("\t\tFalse Control Error\n"); + if (err & (1 << 7)) + DSSERR("\t\t(reserved7)\n"); + if (err & (1 << 8)) + DSSERR("\t\tECC Error, single-bit (corrected)\n"); + if (err & (1 << 9)) + DSSERR("\t\tECC Error, multi-bit (not corrected)\n"); + if (err & (1 << 10)) + DSSERR("\t\tChecksum Error\n"); + if (err & (1 << 11)) + DSSERR("\t\tData type not recognized\n"); + if (err & (1 << 12)) + DSSERR("\t\tInvalid VC ID\n"); + if (err & (1 << 13)) + DSSERR("\t\tInvalid Transmission Length\n"); + if (err & (1 << 14)) + DSSERR("\t\t(reserved14)\n"); + if (err & (1 << 15)) + DSSERR("\t\tDSI Protocol Violation\n"); +} + +static u16 dsi_vc_flush_receive_data(struct platform_device *dsidev, + int channel) +{ + /* RX_FIFO_NOT_EMPTY */ + while (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { + u32 val; + u8 dt; + val = dsi_read_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel)); + DSSERR("\trawval %#08x\n", val); + dt = FLD_GET(val, 5, 0); + if (dt == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT) { + u16 err = FLD_GET(val, 23, 8); + dsi_show_rx_ack_with_err(err); + } else if (dt == MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE) { + DSSERR("\tDCS short response, 1 byte: %#x\n", + FLD_GET(val, 23, 8)); + } else if (dt == MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE) { + DSSERR("\tDCS short response, 2 byte: %#x\n", + FLD_GET(val, 23, 8)); + } else if (dt == MIPI_DSI_RX_DCS_LONG_READ_RESPONSE) { + DSSERR("\tDCS long response, len %d\n", + FLD_GET(val, 23, 8)); + dsi_vc_flush_long_data(dsidev, channel); + } else { + DSSERR("\tunknown datatype 0x%02x\n", dt); + } + } + return 0; +} + +static int dsi_vc_send_bta(struct platform_device *dsidev, int channel) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (dsi->debug_write || dsi->debug_read) + DSSDBG("dsi_vc_send_bta %d\n", channel); + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + /* RX_FIFO_NOT_EMPTY */ + if (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { + DSSERR("rx fifo not empty when sending BTA, dumping data:\n"); + dsi_vc_flush_receive_data(dsidev, channel); + } + + REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), 1, 6, 6); /* BTA_EN */ + + /* flush posted write */ + dsi_read_reg(dsidev, DSI_VC_CTRL(channel)); + + return 0; +} + +static int dsi_vc_send_bta_sync(struct omap_dss_device *dssdev, int channel) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + DECLARE_COMPLETION_ONSTACK(completion); + int r = 0; + u32 err; + + r = dsi_register_isr_vc(dsidev, channel, dsi_completion_handler, + &completion, DSI_VC_IRQ_BTA); + if (r) + goto err0; + + r = dsi_register_isr(dsidev, dsi_completion_handler, &completion, + DSI_IRQ_ERROR_MASK); + if (r) + goto err1; + + r = dsi_vc_send_bta(dsidev, channel); + if (r) + goto err2; + + if (wait_for_completion_timeout(&completion, + msecs_to_jiffies(500)) == 0) { + DSSERR("Failed to receive BTA\n"); + r = -EIO; + goto err2; + } + + err = dsi_get_errors(dsidev); + if (err) { + DSSERR("Error while sending BTA: %x\n", err); + r = -EIO; + goto err2; + } +err2: + dsi_unregister_isr(dsidev, dsi_completion_handler, &completion, + DSI_IRQ_ERROR_MASK); +err1: + dsi_unregister_isr_vc(dsidev, channel, dsi_completion_handler, + &completion, DSI_VC_IRQ_BTA); +err0: + return r; +} + +static inline void dsi_vc_write_long_header(struct platform_device *dsidev, + int channel, u8 data_type, u16 len, u8 ecc) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 val; + u8 data_id; + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + data_id = data_type | dsi->vc[channel].vc_id << 6; + + val = FLD_VAL(data_id, 7, 0) | FLD_VAL(len, 23, 8) | + FLD_VAL(ecc, 31, 24); + + dsi_write_reg(dsidev, DSI_VC_LONG_PACKET_HEADER(channel), val); +} + +static inline void dsi_vc_write_long_payload(struct platform_device *dsidev, + int channel, u8 b1, u8 b2, u8 b3, u8 b4) +{ + u32 val; + + val = b4 << 24 | b3 << 16 | b2 << 8 | b1 << 0; + +/* DSSDBG("\twriting %02x, %02x, %02x, %02x (%#010x)\n", + b1, b2, b3, b4, val); */ + + dsi_write_reg(dsidev, DSI_VC_LONG_PACKET_PAYLOAD(channel), val); +} + +static int dsi_vc_send_long(struct platform_device *dsidev, int channel, + u8 data_type, u8 *data, u16 len, u8 ecc) +{ + /*u32 val; */ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int i; + u8 *p; + int r = 0; + u8 b1, b2, b3, b4; + + if (dsi->debug_write) + DSSDBG("dsi_vc_send_long, %d bytes\n", len); + + /* len + header */ + if (dsi->vc[channel].tx_fifo_size * 32 * 4 < len + 4) { + DSSERR("unable to send long packet: packet too long.\n"); + return -EINVAL; + } + + dsi_vc_config_source(dsidev, channel, DSI_VC_SOURCE_L4); + + dsi_vc_write_long_header(dsidev, channel, data_type, len, ecc); + + p = data; + for (i = 0; i < len >> 2; i++) { + if (dsi->debug_write) + DSSDBG("\tsending full packet %d\n", i); + + b1 = *p++; + b2 = *p++; + b3 = *p++; + b4 = *p++; + + dsi_vc_write_long_payload(dsidev, channel, b1, b2, b3, b4); + } + + i = len % 4; + if (i) { + b1 = 0; b2 = 0; b3 = 0; + + if (dsi->debug_write) + DSSDBG("\tsending remainder bytes %d\n", i); + + switch (i) { + case 3: + b1 = *p++; + b2 = *p++; + b3 = *p++; + break; + case 2: + b1 = *p++; + b2 = *p++; + break; + case 1: + b1 = *p++; + break; + } + + dsi_vc_write_long_payload(dsidev, channel, b1, b2, b3, 0); + } + + return r; +} + +static int dsi_vc_send_short(struct platform_device *dsidev, int channel, + u8 data_type, u16 data, u8 ecc) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 r; + u8 data_id; + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + if (dsi->debug_write) + DSSDBG("dsi_vc_send_short(ch%d, dt %#x, b1 %#x, b2 %#x)\n", + channel, + data_type, data & 0xff, (data >> 8) & 0xff); + + dsi_vc_config_source(dsidev, channel, DSI_VC_SOURCE_L4); + + if (FLD_GET(dsi_read_reg(dsidev, DSI_VC_CTRL(channel)), 16, 16)) { + DSSERR("ERROR FIFO FULL, aborting transfer\n"); + return -EINVAL; + } + + data_id = data_type | dsi->vc[channel].vc_id << 6; + + r = (data_id << 0) | (data << 8) | (ecc << 24); + + dsi_write_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel), r); + + return 0; +} + +static int dsi_vc_send_null(struct omap_dss_device *dssdev, int channel) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + + return dsi_vc_send_long(dsidev, channel, MIPI_DSI_NULL_PACKET, NULL, + 0, 0); +} + +static int dsi_vc_write_nosync_common(struct platform_device *dsidev, + int channel, u8 *data, int len, enum dss_dsi_content_type type) +{ + int r; + + if (len == 0) { + BUG_ON(type == DSS_DSI_CONTENT_DCS); + r = dsi_vc_send_short(dsidev, channel, + MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM, 0, 0); + } else if (len == 1) { + r = dsi_vc_send_short(dsidev, channel, + type == DSS_DSI_CONTENT_GENERIC ? + MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM : + MIPI_DSI_DCS_SHORT_WRITE, data[0], 0); + } else if (len == 2) { + r = dsi_vc_send_short(dsidev, channel, + type == DSS_DSI_CONTENT_GENERIC ? + MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM : + MIPI_DSI_DCS_SHORT_WRITE_PARAM, + data[0] | (data[1] << 8), 0); + } else { + r = dsi_vc_send_long(dsidev, channel, + type == DSS_DSI_CONTENT_GENERIC ? + MIPI_DSI_GENERIC_LONG_WRITE : + MIPI_DSI_DCS_LONG_WRITE, data, len, 0); + } + + return r; +} + +static int dsi_vc_dcs_write_nosync(struct omap_dss_device *dssdev, int channel, + u8 *data, int len) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + + return dsi_vc_write_nosync_common(dsidev, channel, data, len, + DSS_DSI_CONTENT_DCS); +} + +static int dsi_vc_generic_write_nosync(struct omap_dss_device *dssdev, int channel, + u8 *data, int len) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + + return dsi_vc_write_nosync_common(dsidev, channel, data, len, + DSS_DSI_CONTENT_GENERIC); +} + +static int dsi_vc_write_common(struct omap_dss_device *dssdev, int channel, + u8 *data, int len, enum dss_dsi_content_type type) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + int r; + + r = dsi_vc_write_nosync_common(dsidev, channel, data, len, type); + if (r) + goto err; + + r = dsi_vc_send_bta_sync(dssdev, channel); + if (r) + goto err; + + /* RX_FIFO_NOT_EMPTY */ + if (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { + DSSERR("rx fifo not empty after write, dumping data:\n"); + dsi_vc_flush_receive_data(dsidev, channel); + r = -EIO; + goto err; + } + + return 0; +err: + DSSERR("dsi_vc_write_common(ch %d, cmd 0x%02x, len %d) failed\n", + channel, data[0], len); + return r; +} + +static int dsi_vc_dcs_write(struct omap_dss_device *dssdev, int channel, u8 *data, + int len) +{ + return dsi_vc_write_common(dssdev, channel, data, len, + DSS_DSI_CONTENT_DCS); +} + +static int dsi_vc_generic_write(struct omap_dss_device *dssdev, int channel, u8 *data, + int len) +{ + return dsi_vc_write_common(dssdev, channel, data, len, + DSS_DSI_CONTENT_GENERIC); +} + +static int dsi_vc_dcs_send_read_request(struct platform_device *dsidev, + int channel, u8 dcs_cmd) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r; + + if (dsi->debug_read) + DSSDBG("dsi_vc_dcs_send_read_request(ch%d, dcs_cmd %x)\n", + channel, dcs_cmd); + + r = dsi_vc_send_short(dsidev, channel, MIPI_DSI_DCS_READ, dcs_cmd, 0); + if (r) { + DSSERR("dsi_vc_dcs_send_read_request(ch %d, cmd 0x%02x)" + " failed\n", channel, dcs_cmd); + return r; + } + + return 0; +} + +static int dsi_vc_generic_send_read_request(struct platform_device *dsidev, + int channel, u8 *reqdata, int reqlen) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u16 data; + u8 data_type; + int r; + + if (dsi->debug_read) + DSSDBG("dsi_vc_generic_send_read_request(ch %d, reqlen %d)\n", + channel, reqlen); + + if (reqlen == 0) { + data_type = MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM; + data = 0; + } else if (reqlen == 1) { + data_type = MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM; + data = reqdata[0]; + } else if (reqlen == 2) { + data_type = MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM; + data = reqdata[0] | (reqdata[1] << 8); + } else { + BUG(); + return -EINVAL; + } + + r = dsi_vc_send_short(dsidev, channel, data_type, data, 0); + if (r) { + DSSERR("dsi_vc_generic_send_read_request(ch %d, reqlen %d)" + " failed\n", channel, reqlen); + return r; + } + + return 0; +} + +static int dsi_vc_read_rx_fifo(struct platform_device *dsidev, int channel, + u8 *buf, int buflen, enum dss_dsi_content_type type) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 val; + u8 dt; + int r; + + /* RX_FIFO_NOT_EMPTY */ + if (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20) == 0) { + DSSERR("RX fifo empty when trying to read.\n"); + r = -EIO; + goto err; + } + + val = dsi_read_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel)); + if (dsi->debug_read) + DSSDBG("\theader: %08x\n", val); + dt = FLD_GET(val, 5, 0); + if (dt == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT) { + u16 err = FLD_GET(val, 23, 8); + dsi_show_rx_ack_with_err(err); + r = -EIO; + goto err; + + } else if (dt == (type == DSS_DSI_CONTENT_GENERIC ? + MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE : + MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE)) { + u8 data = FLD_GET(val, 15, 8); + if (dsi->debug_read) + DSSDBG("\t%s short response, 1 byte: %02x\n", + type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : + "DCS", data); + + if (buflen < 1) { + r = -EIO; + goto err; + } + + buf[0] = data; + + return 1; + } else if (dt == (type == DSS_DSI_CONTENT_GENERIC ? + MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE : + MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE)) { + u16 data = FLD_GET(val, 23, 8); + if (dsi->debug_read) + DSSDBG("\t%s short response, 2 byte: %04x\n", + type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : + "DCS", data); + + if (buflen < 2) { + r = -EIO; + goto err; + } + + buf[0] = data & 0xff; + buf[1] = (data >> 8) & 0xff; + + return 2; + } else if (dt == (type == DSS_DSI_CONTENT_GENERIC ? + MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE : + MIPI_DSI_RX_DCS_LONG_READ_RESPONSE)) { + int w; + int len = FLD_GET(val, 23, 8); + if (dsi->debug_read) + DSSDBG("\t%s long response, len %d\n", + type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : + "DCS", len); + + if (len > buflen) { + r = -EIO; + goto err; + } + + /* two byte checksum ends the packet, not included in len */ + for (w = 0; w < len + 2;) { + int b; + val = dsi_read_reg(dsidev, + DSI_VC_SHORT_PACKET_HEADER(channel)); + if (dsi->debug_read) + DSSDBG("\t\t%02x %02x %02x %02x\n", + (val >> 0) & 0xff, + (val >> 8) & 0xff, + (val >> 16) & 0xff, + (val >> 24) & 0xff); + + for (b = 0; b < 4; ++b) { + if (w < len) + buf[w] = (val >> (b * 8)) & 0xff; + /* we discard the 2 byte checksum */ + ++w; + } + } + + return len; + } else { + DSSERR("\tunknown datatype 0x%02x\n", dt); + r = -EIO; + goto err; + } + +err: + DSSERR("dsi_vc_read_rx_fifo(ch %d type %s) failed\n", channel, + type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : "DCS"); + + return r; +} + +static int dsi_vc_dcs_read(struct omap_dss_device *dssdev, int channel, u8 dcs_cmd, + u8 *buf, int buflen) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + int r; + + r = dsi_vc_dcs_send_read_request(dsidev, channel, dcs_cmd); + if (r) + goto err; + + r = dsi_vc_send_bta_sync(dssdev, channel); + if (r) + goto err; + + r = dsi_vc_read_rx_fifo(dsidev, channel, buf, buflen, + DSS_DSI_CONTENT_DCS); + if (r < 0) + goto err; + + if (r != buflen) { + r = -EIO; + goto err; + } + + return 0; +err: + DSSERR("dsi_vc_dcs_read(ch %d, cmd 0x%02x) failed\n", channel, dcs_cmd); + return r; +} + +static int dsi_vc_generic_read(struct omap_dss_device *dssdev, int channel, + u8 *reqdata, int reqlen, u8 *buf, int buflen) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + int r; + + r = dsi_vc_generic_send_read_request(dsidev, channel, reqdata, reqlen); + if (r) + return r; + + r = dsi_vc_send_bta_sync(dssdev, channel); + if (r) + return r; + + r = dsi_vc_read_rx_fifo(dsidev, channel, buf, buflen, + DSS_DSI_CONTENT_GENERIC); + if (r < 0) + return r; + + if (r != buflen) { + r = -EIO; + return r; + } + + return 0; +} + +static int dsi_vc_set_max_rx_packet_size(struct omap_dss_device *dssdev, int channel, + u16 len) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + + return dsi_vc_send_short(dsidev, channel, + MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, len, 0); +} + +static int dsi_enter_ulps(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + DECLARE_COMPLETION_ONSTACK(completion); + int r, i; + unsigned mask; + + DSSDBG("Entering ULPS"); + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + WARN_ON(dsi->ulps_enabled); + + if (dsi->ulps_enabled) + return 0; + + /* DDR_CLK_ALWAYS_ON */ + if (REG_GET(dsidev, DSI_CLK_CTRL, 13, 13)) { + dsi_if_enable(dsidev, 0); + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 13, 13); + dsi_if_enable(dsidev, 1); + } + + dsi_sync_vc(dsidev, 0); + dsi_sync_vc(dsidev, 1); + dsi_sync_vc(dsidev, 2); + dsi_sync_vc(dsidev, 3); + + dsi_force_tx_stop_mode_io(dsidev); + + dsi_vc_enable(dsidev, 0, false); + dsi_vc_enable(dsidev, 1, false); + dsi_vc_enable(dsidev, 2, false); + dsi_vc_enable(dsidev, 3, false); + + if (REG_GET(dsidev, DSI_COMPLEXIO_CFG2, 16, 16)) { /* HS_BUSY */ + DSSERR("HS busy when enabling ULPS\n"); + return -EIO; + } + + if (REG_GET(dsidev, DSI_COMPLEXIO_CFG2, 17, 17)) { /* LP_BUSY */ + DSSERR("LP busy when enabling ULPS\n"); + return -EIO; + } + + r = dsi_register_isr_cio(dsidev, dsi_completion_handler, &completion, + DSI_CIO_IRQ_ULPSACTIVENOT_ALL0); + if (r) + return r; + + mask = 0; + + for (i = 0; i < dsi->num_lanes_supported; ++i) { + if (dsi->lanes[i].function == DSI_LANE_UNUSED) + continue; + mask |= 1 << i; + } + /* Assert TxRequestEsc for data lanes and TxUlpsClk for clk lane */ + /* LANEx_ULPS_SIG2 */ + REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG2, mask, 9, 5); + + /* flush posted write and wait for SCP interface to finish the write */ + dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG2); + + if (wait_for_completion_timeout(&completion, + msecs_to_jiffies(1000)) == 0) { + DSSERR("ULPS enable timeout\n"); + r = -EIO; + goto err; + } + + dsi_unregister_isr_cio(dsidev, dsi_completion_handler, &completion, + DSI_CIO_IRQ_ULPSACTIVENOT_ALL0); + + /* Reset LANEx_ULPS_SIG2 */ + REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG2, 0, 9, 5); + + /* flush posted write and wait for SCP interface to finish the write */ + dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG2); + + dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_ULPS); + + dsi_if_enable(dsidev, false); + + dsi->ulps_enabled = true; + + return 0; + +err: + dsi_unregister_isr_cio(dsidev, dsi_completion_handler, &completion, + DSI_CIO_IRQ_ULPSACTIVENOT_ALL0); + return r; +} + +static void dsi_set_lp_rx_timeout(struct platform_device *dsidev, + unsigned ticks, bool x4, bool x16) +{ + unsigned long fck; + unsigned long total_ticks; + u32 r; + + BUG_ON(ticks > 0x1fff); + + /* ticks in DSI_FCK */ + fck = dsi_fclk_rate(dsidev); + + r = dsi_read_reg(dsidev, DSI_TIMING2); + r = FLD_MOD(r, 1, 15, 15); /* LP_RX_TO */ + r = FLD_MOD(r, x16 ? 1 : 0, 14, 14); /* LP_RX_TO_X16 */ + r = FLD_MOD(r, x4 ? 1 : 0, 13, 13); /* LP_RX_TO_X4 */ + r = FLD_MOD(r, ticks, 12, 0); /* LP_RX_COUNTER */ + dsi_write_reg(dsidev, DSI_TIMING2, r); + + total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); + + DSSDBG("LP_RX_TO %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x4 ? " x4" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_set_ta_timeout(struct platform_device *dsidev, unsigned ticks, + bool x8, bool x16) +{ + unsigned long fck; + unsigned long total_ticks; + u32 r; + + BUG_ON(ticks > 0x1fff); + + /* ticks in DSI_FCK */ + fck = dsi_fclk_rate(dsidev); + + r = dsi_read_reg(dsidev, DSI_TIMING1); + r = FLD_MOD(r, 1, 31, 31); /* TA_TO */ + r = FLD_MOD(r, x16 ? 1 : 0, 30, 30); /* TA_TO_X16 */ + r = FLD_MOD(r, x8 ? 1 : 0, 29, 29); /* TA_TO_X8 */ + r = FLD_MOD(r, ticks, 28, 16); /* TA_TO_COUNTER */ + dsi_write_reg(dsidev, DSI_TIMING1, r); + + total_ticks = ticks * (x16 ? 16 : 1) * (x8 ? 8 : 1); + + DSSDBG("TA_TO %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x8 ? " x8" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_set_stop_state_counter(struct platform_device *dsidev, + unsigned ticks, bool x4, bool x16) +{ + unsigned long fck; + unsigned long total_ticks; + u32 r; + + BUG_ON(ticks > 0x1fff); + + /* ticks in DSI_FCK */ + fck = dsi_fclk_rate(dsidev); + + r = dsi_read_reg(dsidev, DSI_TIMING1); + r = FLD_MOD(r, 1, 15, 15); /* FORCE_TX_STOP_MODE_IO */ + r = FLD_MOD(r, x16 ? 1 : 0, 14, 14); /* STOP_STATE_X16_IO */ + r = FLD_MOD(r, x4 ? 1 : 0, 13, 13); /* STOP_STATE_X4_IO */ + r = FLD_MOD(r, ticks, 12, 0); /* STOP_STATE_COUNTER_IO */ + dsi_write_reg(dsidev, DSI_TIMING1, r); + + total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); + + DSSDBG("STOP_STATE_COUNTER %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x4 ? " x4" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_set_hs_tx_timeout(struct platform_device *dsidev, + unsigned ticks, bool x4, bool x16) +{ + unsigned long fck; + unsigned long total_ticks; + u32 r; + + BUG_ON(ticks > 0x1fff); + + /* ticks in TxByteClkHS */ + fck = dsi_get_txbyteclkhs(dsidev); + + r = dsi_read_reg(dsidev, DSI_TIMING2); + r = FLD_MOD(r, 1, 31, 31); /* HS_TX_TO */ + r = FLD_MOD(r, x16 ? 1 : 0, 30, 30); /* HS_TX_TO_X16 */ + r = FLD_MOD(r, x4 ? 1 : 0, 29, 29); /* HS_TX_TO_X8 (4 really) */ + r = FLD_MOD(r, ticks, 28, 16); /* HS_TX_TO_COUNTER */ + dsi_write_reg(dsidev, DSI_TIMING2, r); + + total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); + + DSSDBG("HS_TX_TO %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x4 ? " x4" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_config_vp_num_line_buffers(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int num_line_buffers; + + if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { + int bpp = dsi_get_pixel_size(dsi->pix_fmt); + struct omap_video_timings *timings = &dsi->timings; + /* + * Don't use line buffers if width is greater than the video + * port's line buffer size + */ + if (dsi->line_buffer_size <= timings->x_res * bpp / 8) + num_line_buffers = 0; + else + num_line_buffers = 2; + } else { + /* Use maximum number of line buffers in command mode */ + num_line_buffers = 2; + } + + /* LINE_BUFFER */ + REG_FLD_MOD(dsidev, DSI_CTRL, num_line_buffers, 13, 12); +} + +static void dsi_config_vp_sync_events(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + bool sync_end; + u32 r; + + if (dsi->vm_timings.trans_mode == OMAP_DSS_DSI_PULSE_MODE) + sync_end = true; + else + sync_end = false; + + r = dsi_read_reg(dsidev, DSI_CTRL); + r = FLD_MOD(r, 1, 9, 9); /* VP_DE_POL */ + r = FLD_MOD(r, 1, 10, 10); /* VP_HSYNC_POL */ + r = FLD_MOD(r, 1, 11, 11); /* VP_VSYNC_POL */ + r = FLD_MOD(r, 1, 15, 15); /* VP_VSYNC_START */ + r = FLD_MOD(r, sync_end, 16, 16); /* VP_VSYNC_END */ + r = FLD_MOD(r, 1, 17, 17); /* VP_HSYNC_START */ + r = FLD_MOD(r, sync_end, 18, 18); /* VP_HSYNC_END */ + dsi_write_reg(dsidev, DSI_CTRL, r); +} + +static void dsi_config_blanking_modes(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int blanking_mode = dsi->vm_timings.blanking_mode; + int hfp_blanking_mode = dsi->vm_timings.hfp_blanking_mode; + int hbp_blanking_mode = dsi->vm_timings.hbp_blanking_mode; + int hsa_blanking_mode = dsi->vm_timings.hsa_blanking_mode; + u32 r; + + /* + * 0 = TX FIFO packets sent or LPS in corresponding blanking periods + * 1 = Long blanking packets are sent in corresponding blanking periods + */ + r = dsi_read_reg(dsidev, DSI_CTRL); + r = FLD_MOD(r, blanking_mode, 20, 20); /* BLANKING_MODE */ + r = FLD_MOD(r, hfp_blanking_mode, 21, 21); /* HFP_BLANKING */ + r = FLD_MOD(r, hbp_blanking_mode, 22, 22); /* HBP_BLANKING */ + r = FLD_MOD(r, hsa_blanking_mode, 23, 23); /* HSA_BLANKING */ + dsi_write_reg(dsidev, DSI_CTRL, r); +} + +/* + * According to section 'HS Command Mode Interleaving' in OMAP TRM, Scenario 3 + * results in maximum transition time for data and clock lanes to enter and + * exit HS mode. Hence, this is the scenario where the least amount of command + * mode data can be interleaved. We program the minimum amount of TXBYTECLKHS + * clock cycles that can be used to interleave command mode data in HS so that + * all scenarios are satisfied. + */ +static int dsi_compute_interleave_hs(int blank, bool ddr_alwon, int enter_hs, + int exit_hs, int exiths_clk, int ddr_pre, int ddr_post) +{ + int transition; + + /* + * If DDR_CLK_ALWAYS_ON is set, we need to consider HS mode transition + * time of data lanes only, if it isn't set, we need to consider HS + * transition time of both data and clock lanes. HS transition time + * of Scenario 3 is considered. + */ + if (ddr_alwon) { + transition = enter_hs + exit_hs + max(enter_hs, 2) + 1; + } else { + int trans1, trans2; + trans1 = ddr_pre + enter_hs + exit_hs + max(enter_hs, 2) + 1; + trans2 = ddr_pre + enter_hs + exiths_clk + ddr_post + ddr_pre + + enter_hs + 1; + transition = max(trans1, trans2); + } + + return blank > transition ? blank - transition : 0; +} + +/* + * According to section 'LP Command Mode Interleaving' in OMAP TRM, Scenario 1 + * results in maximum transition time for data lanes to enter and exit LP mode. + * Hence, this is the scenario where the least amount of command mode data can + * be interleaved. We program the minimum amount of bytes that can be + * interleaved in LP so that all scenarios are satisfied. + */ +static int dsi_compute_interleave_lp(int blank, int enter_hs, int exit_hs, + int lp_clk_div, int tdsi_fclk) +{ + int trans_lp; /* time required for a LP transition, in TXBYTECLKHS */ + int tlp_avail; /* time left for interleaving commands, in CLKIN4DDR */ + int ttxclkesc; /* period of LP transmit escape clock, in CLKIN4DDR */ + int thsbyte_clk = 16; /* Period of TXBYTECLKHS clock, in CLKIN4DDR */ + int lp_inter; /* cmd mode data that can be interleaved, in bytes */ + + /* maximum LP transition time according to Scenario 1 */ + trans_lp = exit_hs + max(enter_hs, 2) + 1; + + /* CLKIN4DDR = 16 * TXBYTECLKHS */ + tlp_avail = thsbyte_clk * (blank - trans_lp); + + ttxclkesc = tdsi_fclk * lp_clk_div; + + lp_inter = ((tlp_avail - 8 * thsbyte_clk - 5 * tdsi_fclk) / ttxclkesc - + 26) / 16; + + return max(lp_inter, 0); +} + +static void dsi_config_cmd_mode_interleaving(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int blanking_mode; + int hfp_blanking_mode, hbp_blanking_mode, hsa_blanking_mode; + int hsa, hfp, hbp, width_bytes, bllp, lp_clk_div; + int ddr_clk_pre, ddr_clk_post, enter_hs_mode_lat, exit_hs_mode_lat; + int tclk_trail, ths_exit, exiths_clk; + bool ddr_alwon; + struct omap_video_timings *timings = &dsi->timings; + int bpp = dsi_get_pixel_size(dsi->pix_fmt); + int ndl = dsi->num_lanes_used - 1; + int dsi_fclk_hsdiv = dsi->user_dsi_cinfo.regm_dsi + 1; + int hsa_interleave_hs = 0, hsa_interleave_lp = 0; + int hfp_interleave_hs = 0, hfp_interleave_lp = 0; + int hbp_interleave_hs = 0, hbp_interleave_lp = 0; + int bl_interleave_hs = 0, bl_interleave_lp = 0; + u32 r; + + r = dsi_read_reg(dsidev, DSI_CTRL); + blanking_mode = FLD_GET(r, 20, 20); + hfp_blanking_mode = FLD_GET(r, 21, 21); + hbp_blanking_mode = FLD_GET(r, 22, 22); + hsa_blanking_mode = FLD_GET(r, 23, 23); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING1); + hbp = FLD_GET(r, 11, 0); + hfp = FLD_GET(r, 23, 12); + hsa = FLD_GET(r, 31, 24); + + r = dsi_read_reg(dsidev, DSI_CLK_TIMING); + ddr_clk_post = FLD_GET(r, 7, 0); + ddr_clk_pre = FLD_GET(r, 15, 8); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING7); + exit_hs_mode_lat = FLD_GET(r, 15, 0); + enter_hs_mode_lat = FLD_GET(r, 31, 16); + + r = dsi_read_reg(dsidev, DSI_CLK_CTRL); + lp_clk_div = FLD_GET(r, 12, 0); + ddr_alwon = FLD_GET(r, 13, 13); + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG0); + ths_exit = FLD_GET(r, 7, 0); + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG1); + tclk_trail = FLD_GET(r, 15, 8); + + exiths_clk = ths_exit + tclk_trail; + + width_bytes = DIV_ROUND_UP(timings->x_res * bpp, 8); + bllp = hbp + hfp + hsa + DIV_ROUND_UP(width_bytes + 6, ndl); + + if (!hsa_blanking_mode) { + hsa_interleave_hs = dsi_compute_interleave_hs(hsa, ddr_alwon, + enter_hs_mode_lat, exit_hs_mode_lat, + exiths_clk, ddr_clk_pre, ddr_clk_post); + hsa_interleave_lp = dsi_compute_interleave_lp(hsa, + enter_hs_mode_lat, exit_hs_mode_lat, + lp_clk_div, dsi_fclk_hsdiv); + } + + if (!hfp_blanking_mode) { + hfp_interleave_hs = dsi_compute_interleave_hs(hfp, ddr_alwon, + enter_hs_mode_lat, exit_hs_mode_lat, + exiths_clk, ddr_clk_pre, ddr_clk_post); + hfp_interleave_lp = dsi_compute_interleave_lp(hfp, + enter_hs_mode_lat, exit_hs_mode_lat, + lp_clk_div, dsi_fclk_hsdiv); + } + + if (!hbp_blanking_mode) { + hbp_interleave_hs = dsi_compute_interleave_hs(hbp, ddr_alwon, + enter_hs_mode_lat, exit_hs_mode_lat, + exiths_clk, ddr_clk_pre, ddr_clk_post); + + hbp_interleave_lp = dsi_compute_interleave_lp(hbp, + enter_hs_mode_lat, exit_hs_mode_lat, + lp_clk_div, dsi_fclk_hsdiv); + } + + if (!blanking_mode) { + bl_interleave_hs = dsi_compute_interleave_hs(bllp, ddr_alwon, + enter_hs_mode_lat, exit_hs_mode_lat, + exiths_clk, ddr_clk_pre, ddr_clk_post); + + bl_interleave_lp = dsi_compute_interleave_lp(bllp, + enter_hs_mode_lat, exit_hs_mode_lat, + lp_clk_div, dsi_fclk_hsdiv); + } + + DSSDBG("DSI HS interleaving(TXBYTECLKHS) HSA %d, HFP %d, HBP %d, BLLP %d\n", + hsa_interleave_hs, hfp_interleave_hs, hbp_interleave_hs, + bl_interleave_hs); + + DSSDBG("DSI LP interleaving(bytes) HSA %d, HFP %d, HBP %d, BLLP %d\n", + hsa_interleave_lp, hfp_interleave_lp, hbp_interleave_lp, + bl_interleave_lp); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING4); + r = FLD_MOD(r, hsa_interleave_hs, 23, 16); + r = FLD_MOD(r, hfp_interleave_hs, 15, 8); + r = FLD_MOD(r, hbp_interleave_hs, 7, 0); + dsi_write_reg(dsidev, DSI_VM_TIMING4, r); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING5); + r = FLD_MOD(r, hsa_interleave_lp, 23, 16); + r = FLD_MOD(r, hfp_interleave_lp, 15, 8); + r = FLD_MOD(r, hbp_interleave_lp, 7, 0); + dsi_write_reg(dsidev, DSI_VM_TIMING5, r); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING6); + r = FLD_MOD(r, bl_interleave_hs, 31, 15); + r = FLD_MOD(r, bl_interleave_lp, 16, 0); + dsi_write_reg(dsidev, DSI_VM_TIMING6, r); +} + +static int dsi_proto_config(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u32 r; + int buswidth = 0; + + dsi_config_tx_fifo(dsidev, DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32); + + dsi_config_rx_fifo(dsidev, DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32); + + /* XXX what values for the timeouts? */ + dsi_set_stop_state_counter(dsidev, 0x1000, false, false); + dsi_set_ta_timeout(dsidev, 0x1fff, true, true); + dsi_set_lp_rx_timeout(dsidev, 0x1fff, true, true); + dsi_set_hs_tx_timeout(dsidev, 0x1fff, true, true); + + switch (dsi_get_pixel_size(dsi->pix_fmt)) { + case 16: + buswidth = 0; + break; + case 18: + buswidth = 1; + break; + case 24: + buswidth = 2; + break; + default: + BUG(); + return -EINVAL; + } + + r = dsi_read_reg(dsidev, DSI_CTRL); + r = FLD_MOD(r, 1, 1, 1); /* CS_RX_EN */ + r = FLD_MOD(r, 1, 2, 2); /* ECC_RX_EN */ + r = FLD_MOD(r, 1, 3, 3); /* TX_FIFO_ARBITRATION */ + r = FLD_MOD(r, 1, 4, 4); /* VP_CLK_RATIO, always 1, see errata*/ + r = FLD_MOD(r, buswidth, 7, 6); /* VP_DATA_BUS_WIDTH */ + r = FLD_MOD(r, 0, 8, 8); /* VP_CLK_POL */ + r = FLD_MOD(r, 1, 14, 14); /* TRIGGER_RESET_MODE */ + r = FLD_MOD(r, 1, 19, 19); /* EOT_ENABLE */ + if (!dss_has_feature(FEAT_DSI_DCS_CMD_CONFIG_VC)) { + r = FLD_MOD(r, 1, 24, 24); /* DCS_CMD_ENABLE */ + /* DCS_CMD_CODE, 1=start, 0=continue */ + r = FLD_MOD(r, 0, 25, 25); + } + + dsi_write_reg(dsidev, DSI_CTRL, r); + + dsi_config_vp_num_line_buffers(dsidev); + + if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { + dsi_config_vp_sync_events(dsidev); + dsi_config_blanking_modes(dsidev); + dsi_config_cmd_mode_interleaving(dsidev); + } + + dsi_vc_initial_config(dsidev, 0); + dsi_vc_initial_config(dsidev, 1); + dsi_vc_initial_config(dsidev, 2); + dsi_vc_initial_config(dsidev, 3); + + return 0; +} + +static void dsi_proto_timings(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + unsigned tlpx, tclk_zero, tclk_prepare, tclk_trail; + unsigned tclk_pre, tclk_post; + unsigned ths_prepare, ths_prepare_ths_zero, ths_zero; + unsigned ths_trail, ths_exit; + unsigned ddr_clk_pre, ddr_clk_post; + unsigned enter_hs_mode_lat, exit_hs_mode_lat; + unsigned ths_eot; + int ndl = dsi->num_lanes_used - 1; + u32 r; + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG0); + ths_prepare = FLD_GET(r, 31, 24); + ths_prepare_ths_zero = FLD_GET(r, 23, 16); + ths_zero = ths_prepare_ths_zero - ths_prepare; + ths_trail = FLD_GET(r, 15, 8); + ths_exit = FLD_GET(r, 7, 0); + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG1); + tlpx = FLD_GET(r, 20, 16) * 2; + tclk_trail = FLD_GET(r, 15, 8); + tclk_zero = FLD_GET(r, 7, 0); + + r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG2); + tclk_prepare = FLD_GET(r, 7, 0); + + /* min 8*UI */ + tclk_pre = 20; + /* min 60ns + 52*UI */ + tclk_post = ns2ddr(dsidev, 60) + 26; + + ths_eot = DIV_ROUND_UP(4, ndl); + + ddr_clk_pre = DIV_ROUND_UP(tclk_pre + tlpx + tclk_zero + tclk_prepare, + 4); + ddr_clk_post = DIV_ROUND_UP(tclk_post + ths_trail, 4) + ths_eot; + + BUG_ON(ddr_clk_pre == 0 || ddr_clk_pre > 255); + BUG_ON(ddr_clk_post == 0 || ddr_clk_post > 255); + + r = dsi_read_reg(dsidev, DSI_CLK_TIMING); + r = FLD_MOD(r, ddr_clk_pre, 15, 8); + r = FLD_MOD(r, ddr_clk_post, 7, 0); + dsi_write_reg(dsidev, DSI_CLK_TIMING, r); + + DSSDBG("ddr_clk_pre %u, ddr_clk_post %u\n", + ddr_clk_pre, + ddr_clk_post); + + enter_hs_mode_lat = 1 + DIV_ROUND_UP(tlpx, 4) + + DIV_ROUND_UP(ths_prepare, 4) + + DIV_ROUND_UP(ths_zero + 3, 4); + + exit_hs_mode_lat = DIV_ROUND_UP(ths_trail + ths_exit, 4) + 1 + ths_eot; + + r = FLD_VAL(enter_hs_mode_lat, 31, 16) | + FLD_VAL(exit_hs_mode_lat, 15, 0); + dsi_write_reg(dsidev, DSI_VM_TIMING7, r); + + DSSDBG("enter_hs_mode_lat %u, exit_hs_mode_lat %u\n", + enter_hs_mode_lat, exit_hs_mode_lat); + + if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { + /* TODO: Implement a video mode check_timings function */ + int hsa = dsi->vm_timings.hsa; + int hfp = dsi->vm_timings.hfp; + int hbp = dsi->vm_timings.hbp; + int vsa = dsi->vm_timings.vsa; + int vfp = dsi->vm_timings.vfp; + int vbp = dsi->vm_timings.vbp; + int window_sync = dsi->vm_timings.window_sync; + bool hsync_end; + struct omap_video_timings *timings = &dsi->timings; + int bpp = dsi_get_pixel_size(dsi->pix_fmt); + int tl, t_he, width_bytes; + + hsync_end = dsi->vm_timings.trans_mode == OMAP_DSS_DSI_PULSE_MODE; + t_he = hsync_end ? + ((hsa == 0 && ndl == 3) ? 1 : DIV_ROUND_UP(4, ndl)) : 0; + + width_bytes = DIV_ROUND_UP(timings->x_res * bpp, 8); + + /* TL = t_HS + HSA + t_HE + HFP + ceil((WC + 6) / NDL) + HBP */ + tl = DIV_ROUND_UP(4, ndl) + (hsync_end ? hsa : 0) + t_he + hfp + + DIV_ROUND_UP(width_bytes + 6, ndl) + hbp; + + DSSDBG("HBP: %d, HFP: %d, HSA: %d, TL: %d TXBYTECLKHS\n", hbp, + hfp, hsync_end ? hsa : 0, tl); + DSSDBG("VBP: %d, VFP: %d, VSA: %d, VACT: %d lines\n", vbp, vfp, + vsa, timings->y_res); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING1); + r = FLD_MOD(r, hbp, 11, 0); /* HBP */ + r = FLD_MOD(r, hfp, 23, 12); /* HFP */ + r = FLD_MOD(r, hsync_end ? hsa : 0, 31, 24); /* HSA */ + dsi_write_reg(dsidev, DSI_VM_TIMING1, r); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING2); + r = FLD_MOD(r, vbp, 7, 0); /* VBP */ + r = FLD_MOD(r, vfp, 15, 8); /* VFP */ + r = FLD_MOD(r, vsa, 23, 16); /* VSA */ + r = FLD_MOD(r, window_sync, 27, 24); /* WINDOW_SYNC */ + dsi_write_reg(dsidev, DSI_VM_TIMING2, r); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING3); + r = FLD_MOD(r, timings->y_res, 14, 0); /* VACT */ + r = FLD_MOD(r, tl, 31, 16); /* TL */ + dsi_write_reg(dsidev, DSI_VM_TIMING3, r); + } +} + +static int dsi_configure_pins(struct omap_dss_device *dssdev, + const struct omap_dsi_pin_config *pin_cfg) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int num_pins; + const int *pins; + struct dsi_lane_config lanes[DSI_MAX_NR_LANES]; + int num_lanes; + int i; + + static const enum dsi_lane_function functions[] = { + DSI_LANE_CLK, + DSI_LANE_DATA1, + DSI_LANE_DATA2, + DSI_LANE_DATA3, + DSI_LANE_DATA4, + }; + + num_pins = pin_cfg->num_pins; + pins = pin_cfg->pins; + + if (num_pins < 4 || num_pins > dsi->num_lanes_supported * 2 + || num_pins % 2 != 0) + return -EINVAL; + + for (i = 0; i < DSI_MAX_NR_LANES; ++i) + lanes[i].function = DSI_LANE_UNUSED; + + num_lanes = 0; + + for (i = 0; i < num_pins; i += 2) { + u8 lane, pol; + int dx, dy; + + dx = pins[i]; + dy = pins[i + 1]; + + if (dx < 0 || dx >= dsi->num_lanes_supported * 2) + return -EINVAL; + + if (dy < 0 || dy >= dsi->num_lanes_supported * 2) + return -EINVAL; + + if (dx & 1) { + if (dy != dx - 1) + return -EINVAL; + pol = 1; + } else { + if (dy != dx + 1) + return -EINVAL; + pol = 0; + } + + lane = dx / 2; + + lanes[lane].function = functions[i / 2]; + lanes[lane].polarity = pol; + num_lanes++; + } + + memcpy(dsi->lanes, lanes, sizeof(dsi->lanes)); + dsi->num_lanes_used = num_lanes; + + return 0; +} + +static int dsi_enable_video_output(struct omap_dss_device *dssdev, int channel) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct omap_overlay_manager *mgr = dsi->output.manager; + int bpp = dsi_get_pixel_size(dsi->pix_fmt); + struct omap_dss_device *out = &dsi->output; + u8 data_type; + u16 word_count; + int r; + + if (out == NULL || out->manager == NULL) { + DSSERR("failed to enable display: no output/manager\n"); + return -ENODEV; + } + + r = dsi_display_init_dispc(dsidev, mgr); + if (r) + goto err_init_dispc; + + if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { + switch (dsi->pix_fmt) { + case OMAP_DSS_DSI_FMT_RGB888: + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24; + break; + case OMAP_DSS_DSI_FMT_RGB666: + data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + break; + case OMAP_DSS_DSI_FMT_RGB666_PACKED: + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18; + break; + case OMAP_DSS_DSI_FMT_RGB565: + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16; + break; + default: + r = -EINVAL; + goto err_pix_fmt; + } + + dsi_if_enable(dsidev, false); + dsi_vc_enable(dsidev, channel, false); + + /* MODE, 1 = video mode */ + REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), 1, 4, 4); + + word_count = DIV_ROUND_UP(dsi->timings.x_res * bpp, 8); + + dsi_vc_write_long_header(dsidev, channel, data_type, + word_count, 0); + + dsi_vc_enable(dsidev, channel, true); + dsi_if_enable(dsidev, true); + } + + r = dss_mgr_enable(mgr); + if (r) + goto err_mgr_enable; + + return 0; + +err_mgr_enable: + if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { + dsi_if_enable(dsidev, false); + dsi_vc_enable(dsidev, channel, false); + } +err_pix_fmt: + dsi_display_uninit_dispc(dsidev, mgr); +err_init_dispc: + return r; +} + +static void dsi_disable_video_output(struct omap_dss_device *dssdev, int channel) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct omap_overlay_manager *mgr = dsi->output.manager; + + if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { + dsi_if_enable(dsidev, false); + dsi_vc_enable(dsidev, channel, false); + + /* MODE, 0 = command mode */ + REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), 0, 4, 4); + + dsi_vc_enable(dsidev, channel, true); + dsi_if_enable(dsidev, true); + } + + dss_mgr_disable(mgr); + + dsi_display_uninit_dispc(dsidev, mgr); +} + +static void dsi_update_screen_dispc(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct omap_overlay_manager *mgr = dsi->output.manager; + unsigned bytespp; + unsigned bytespl; + unsigned bytespf; + unsigned total_len; + unsigned packet_payload; + unsigned packet_len; + u32 l; + int r; + const unsigned channel = dsi->update_channel; + const unsigned line_buf_size = dsi->line_buffer_size; + u16 w = dsi->timings.x_res; + u16 h = dsi->timings.y_res; + + DSSDBG("dsi_update_screen_dispc(%dx%d)\n", w, h); + + dsi_vc_config_source(dsidev, channel, DSI_VC_SOURCE_VP); + + bytespp = dsi_get_pixel_size(dsi->pix_fmt) / 8; + bytespl = w * bytespp; + bytespf = bytespl * h; + + /* NOTE: packet_payload has to be equal to N * bytespl, where N is + * number of lines in a packet. See errata about VP_CLK_RATIO */ + + if (bytespf < line_buf_size) + packet_payload = bytespf; + else + packet_payload = (line_buf_size) / bytespl * bytespl; + + packet_len = packet_payload + 1; /* 1 byte for DCS cmd */ + total_len = (bytespf / packet_payload) * packet_len; + + if (bytespf % packet_payload) + total_len += (bytespf % packet_payload) + 1; + + l = FLD_VAL(total_len, 23, 0); /* TE_SIZE */ + dsi_write_reg(dsidev, DSI_VC_TE(channel), l); + + dsi_vc_write_long_header(dsidev, channel, MIPI_DSI_DCS_LONG_WRITE, + packet_len, 0); + + if (dsi->te_enabled) + l = FLD_MOD(l, 1, 30, 30); /* TE_EN */ + else + l = FLD_MOD(l, 1, 31, 31); /* TE_START */ + dsi_write_reg(dsidev, DSI_VC_TE(channel), l); + + /* We put SIDLEMODE to no-idle for the duration of the transfer, + * because DSS interrupts are not capable of waking up the CPU and the + * framedone interrupt could be delayed for quite a long time. I think + * the same goes for any DSS interrupts, but for some reason I have not + * seen the problem anywhere else than here. + */ + dispc_disable_sidle(); + + dsi_perf_mark_start(dsidev); + + r = schedule_delayed_work(&dsi->framedone_timeout_work, + msecs_to_jiffies(250)); + BUG_ON(r == 0); + + dss_mgr_set_timings(mgr, &dsi->timings); + + dss_mgr_start_update(mgr); + + if (dsi->te_enabled) { + /* disable LP_RX_TO, so that we can receive TE. Time to wait + * for TE is longer than the timer allows */ + REG_FLD_MOD(dsidev, DSI_TIMING2, 0, 15, 15); /* LP_RX_TO */ + + dsi_vc_send_bta(dsidev, channel); + +#ifdef DSI_CATCH_MISSING_TE + mod_timer(&dsi->te_timer, jiffies + msecs_to_jiffies(250)); +#endif + } +} + +#ifdef DSI_CATCH_MISSING_TE +static void dsi_te_timeout(unsigned long arg) +{ + DSSERR("TE not received for 250ms!\n"); +} +#endif + +static void dsi_handle_framedone(struct platform_device *dsidev, int error) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + /* SIDLEMODE back to smart-idle */ + dispc_enable_sidle(); + + if (dsi->te_enabled) { + /* enable LP_RX_TO again after the TE */ + REG_FLD_MOD(dsidev, DSI_TIMING2, 1, 15, 15); /* LP_RX_TO */ + } + + dsi->framedone_callback(error, dsi->framedone_data); + + if (!error) + dsi_perf_show(dsidev, "DISPC"); +} + +static void dsi_framedone_timeout_work_callback(struct work_struct *work) +{ + struct dsi_data *dsi = container_of(work, struct dsi_data, + framedone_timeout_work.work); + /* XXX While extremely unlikely, we could get FRAMEDONE interrupt after + * 250ms which would conflict with this timeout work. What should be + * done is first cancel the transfer on the HW, and then cancel the + * possibly scheduled framedone work. However, cancelling the transfer + * on the HW is buggy, and would probably require resetting the whole + * DSI */ + + DSSERR("Framedone not received for 250ms!\n"); + + dsi_handle_framedone(dsi->pdev, -ETIMEDOUT); +} + +static void dsi_framedone_irq_callback(void *data) +{ + struct platform_device *dsidev = (struct platform_device *) data; + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + /* Note: We get FRAMEDONE when DISPC has finished sending pixels and + * turns itself off. However, DSI still has the pixels in its buffers, + * and is sending the data. + */ + + cancel_delayed_work(&dsi->framedone_timeout_work); + + dsi_handle_framedone(dsidev, 0); +} + +static int dsi_update(struct omap_dss_device *dssdev, int channel, + void (*callback)(int, void *), void *data) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + u16 dw, dh; + + dsi_perf_mark_setup(dsidev); + + dsi->update_channel = channel; + + dsi->framedone_callback = callback; + dsi->framedone_data = data; + + dw = dsi->timings.x_res; + dh = dsi->timings.y_res; + +#ifdef DSI_PERF_MEASURE + dsi->update_bytes = dw * dh * + dsi_get_pixel_size(dsi->pix_fmt) / 8; +#endif + dsi_update_screen_dispc(dsidev); + + return 0; +} + +/* Display funcs */ + +static int dsi_configure_dispc_clocks(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct dispc_clock_info dispc_cinfo; + int r; + unsigned long fck; + + fck = dsi_get_pll_hsdiv_dispc_rate(dsidev); + + dispc_cinfo.lck_div = dsi->user_dispc_cinfo.lck_div; + dispc_cinfo.pck_div = dsi->user_dispc_cinfo.pck_div; + + r = dispc_calc_clock_rates(fck, &dispc_cinfo); + if (r) { + DSSERR("Failed to calc dispc clocks\n"); + return r; + } + + dsi->mgr_config.clock_info = dispc_cinfo; + + return 0; +} + +static int dsi_display_init_dispc(struct platform_device *dsidev, + struct omap_overlay_manager *mgr) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r; + + dss_select_lcd_clk_source(mgr->id, dsi->module_id == 0 ? + OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC : + OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC); + + if (dsi->mode == OMAP_DSS_DSI_CMD_MODE) { + r = dss_mgr_register_framedone_handler(mgr, + dsi_framedone_irq_callback, dsidev); + if (r) { + DSSERR("can't register FRAMEDONE handler\n"); + goto err; + } + + dsi->mgr_config.stallmode = true; + dsi->mgr_config.fifohandcheck = true; + } else { + dsi->mgr_config.stallmode = false; + dsi->mgr_config.fifohandcheck = false; + } + + /* + * override interlace, logic level and edge related parameters in + * omap_video_timings with default values + */ + dsi->timings.interlace = false; + dsi->timings.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + dsi->timings.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + dsi->timings.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + dsi->timings.de_level = OMAPDSS_SIG_ACTIVE_HIGH; + dsi->timings.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + + dss_mgr_set_timings(mgr, &dsi->timings); + + r = dsi_configure_dispc_clocks(dsidev); + if (r) + goto err1; + + dsi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; + dsi->mgr_config.video_port_width = + dsi_get_pixel_size(dsi->pix_fmt); + dsi->mgr_config.lcden_sig_polarity = 0; + + dss_mgr_set_lcd_config(mgr, &dsi->mgr_config); + + return 0; +err1: + if (dsi->mode == OMAP_DSS_DSI_CMD_MODE) + dss_mgr_unregister_framedone_handler(mgr, + dsi_framedone_irq_callback, dsidev); +err: + dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); + return r; +} + +static void dsi_display_uninit_dispc(struct platform_device *dsidev, + struct omap_overlay_manager *mgr) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (dsi->mode == OMAP_DSS_DSI_CMD_MODE) + dss_mgr_unregister_framedone_handler(mgr, + dsi_framedone_irq_callback, dsidev); + + dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); +} + +static int dsi_configure_dsi_clocks(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct dsi_clock_info cinfo; + int r; + + cinfo = dsi->user_dsi_cinfo; + + r = dsi_calc_clock_rates(dsidev, &cinfo); + if (r) { + DSSERR("Failed to calc dsi clocks\n"); + return r; + } + + r = dsi_pll_set_clock_div(dsidev, &cinfo); + if (r) { + DSSERR("Failed to set dsi clocks\n"); + return r; + } + + return 0; +} + +static int dsi_display_init_dsi(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r; + + r = dsi_pll_init(dsidev, true, true); + if (r) + goto err0; + + r = dsi_configure_dsi_clocks(dsidev); + if (r) + goto err1; + + dss_select_dsi_clk_source(dsi->module_id, dsi->module_id == 0 ? + OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI : + OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI); + + DSSDBG("PLL OK\n"); + + r = dsi_cio_init(dsidev); + if (r) + goto err2; + + _dsi_print_reset_status(dsidev); + + dsi_proto_timings(dsidev); + dsi_set_lp_clk_divisor(dsidev); + + if (1) + _dsi_print_reset_status(dsidev); + + r = dsi_proto_config(dsidev); + if (r) + goto err3; + + /* enable interface */ + dsi_vc_enable(dsidev, 0, 1); + dsi_vc_enable(dsidev, 1, 1); + dsi_vc_enable(dsidev, 2, 1); + dsi_vc_enable(dsidev, 3, 1); + dsi_if_enable(dsidev, 1); + dsi_force_tx_stop_mode_io(dsidev); + + return 0; +err3: + dsi_cio_uninit(dsidev); +err2: + dss_select_dsi_clk_source(dsi->module_id, OMAP_DSS_CLK_SRC_FCK); +err1: + dsi_pll_uninit(dsidev, true); +err0: + return r; +} + +static void dsi_display_uninit_dsi(struct platform_device *dsidev, + bool disconnect_lanes, bool enter_ulps) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (enter_ulps && !dsi->ulps_enabled) + dsi_enter_ulps(dsidev); + + /* disable interface */ + dsi_if_enable(dsidev, 0); + dsi_vc_enable(dsidev, 0, 0); + dsi_vc_enable(dsidev, 1, 0); + dsi_vc_enable(dsidev, 2, 0); + dsi_vc_enable(dsidev, 3, 0); + + dss_select_dsi_clk_source(dsi->module_id, OMAP_DSS_CLK_SRC_FCK); + dsi_cio_uninit(dsidev); + dsi_pll_uninit(dsidev, disconnect_lanes); +} + +static int dsi_display_enable(struct omap_dss_device *dssdev) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int r = 0; + + DSSDBG("dsi_display_enable\n"); + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + mutex_lock(&dsi->lock); + + r = dsi_runtime_get(dsidev); + if (r) + goto err_get_dsi; + + dsi_enable_pll_clock(dsidev, 1); + + _dsi_initialize_irq(dsidev); + + r = dsi_display_init_dsi(dsidev); + if (r) + goto err_init_dsi; + + mutex_unlock(&dsi->lock); + + return 0; + +err_init_dsi: + dsi_enable_pll_clock(dsidev, 0); + dsi_runtime_put(dsidev); +err_get_dsi: + mutex_unlock(&dsi->lock); + DSSDBG("dsi_display_enable FAILED\n"); + return r; +} + +static void dsi_display_disable(struct omap_dss_device *dssdev, + bool disconnect_lanes, bool enter_ulps) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + DSSDBG("dsi_display_disable\n"); + + WARN_ON(!dsi_bus_is_locked(dsidev)); + + mutex_lock(&dsi->lock); + + dsi_sync_vc(dsidev, 0); + dsi_sync_vc(dsidev, 1); + dsi_sync_vc(dsidev, 2); + dsi_sync_vc(dsidev, 3); + + dsi_display_uninit_dsi(dsidev, disconnect_lanes, enter_ulps); + + dsi_runtime_put(dsidev); + dsi_enable_pll_clock(dsidev, 0); + + mutex_unlock(&dsi->lock); +} + +static int dsi_enable_te(struct omap_dss_device *dssdev, bool enable) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + dsi->te_enabled = enable; + return 0; +} + +#ifdef PRINT_VERBOSE_VM_TIMINGS +static void print_dsi_vm(const char *str, + const struct omap_dss_dsi_videomode_timings *t) +{ + unsigned long byteclk = t->hsclk / 4; + int bl, wc, pps, tot; + + wc = DIV_ROUND_UP(t->hact * t->bitspp, 8); + pps = DIV_ROUND_UP(wc + 6, t->ndl); /* pixel packet size */ + bl = t->hss + t->hsa + t->hse + t->hbp + t->hfp; + tot = bl + pps; + +#define TO_DSI_T(x) ((u32)div64_u64((u64)x * 1000000000llu, byteclk)) + + pr_debug("%s bck %lu, %u/%u/%u/%u/%u/%u = %u+%u = %u, " + "%u/%u/%u/%u/%u/%u = %u + %u = %u\n", + str, + byteclk, + t->hss, t->hsa, t->hse, t->hbp, pps, t->hfp, + bl, pps, tot, + TO_DSI_T(t->hss), + TO_DSI_T(t->hsa), + TO_DSI_T(t->hse), + TO_DSI_T(t->hbp), + TO_DSI_T(pps), + TO_DSI_T(t->hfp), + + TO_DSI_T(bl), + TO_DSI_T(pps), + + TO_DSI_T(tot)); +#undef TO_DSI_T +} + +static void print_dispc_vm(const char *str, const struct omap_video_timings *t) +{ + unsigned long pck = t->pixelclock; + int hact, bl, tot; + + hact = t->x_res; + bl = t->hsw + t->hbp + t->hfp; + tot = hact + bl; + +#define TO_DISPC_T(x) ((u32)div64_u64((u64)x * 1000000000llu, pck)) + + pr_debug("%s pck %lu, %u/%u/%u/%u = %u+%u = %u, " + "%u/%u/%u/%u = %u + %u = %u\n", + str, + pck, + t->hsw, t->hbp, hact, t->hfp, + bl, hact, tot, + TO_DISPC_T(t->hsw), + TO_DISPC_T(t->hbp), + TO_DISPC_T(hact), + TO_DISPC_T(t->hfp), + TO_DISPC_T(bl), + TO_DISPC_T(hact), + TO_DISPC_T(tot)); +#undef TO_DISPC_T +} + +/* note: this is not quite accurate */ +static void print_dsi_dispc_vm(const char *str, + const struct omap_dss_dsi_videomode_timings *t) +{ + struct omap_video_timings vm = { 0 }; + unsigned long byteclk = t->hsclk / 4; + unsigned long pck; + u64 dsi_tput; + int dsi_hact, dsi_htot; + + dsi_tput = (u64)byteclk * t->ndl * 8; + pck = (u32)div64_u64(dsi_tput, t->bitspp); + dsi_hact = DIV_ROUND_UP(DIV_ROUND_UP(t->hact * t->bitspp, 8) + 6, t->ndl); + dsi_htot = t->hss + t->hsa + t->hse + t->hbp + dsi_hact + t->hfp; + + vm.pixelclock = pck; + vm.hsw = div64_u64((u64)(t->hsa + t->hse) * pck, byteclk); + vm.hbp = div64_u64((u64)t->hbp * pck, byteclk); + vm.hfp = div64_u64((u64)t->hfp * pck, byteclk); + vm.x_res = t->hact; + + print_dispc_vm(str, &vm); +} +#endif /* PRINT_VERBOSE_VM_TIMINGS */ + +static bool dsi_cm_calc_dispc_cb(int lckd, int pckd, unsigned long lck, + unsigned long pck, void *data) +{ + struct dsi_clk_calc_ctx *ctx = data; + struct omap_video_timings *t = &ctx->dispc_vm; + + ctx->dispc_cinfo.lck_div = lckd; + ctx->dispc_cinfo.pck_div = pckd; + ctx->dispc_cinfo.lck = lck; + ctx->dispc_cinfo.pck = pck; + + *t = *ctx->config->timings; + t->pixelclock = pck; + t->x_res = ctx->config->timings->x_res; + t->y_res = ctx->config->timings->y_res; + t->hsw = t->hfp = t->hbp = t->vsw = 1; + t->vfp = t->vbp = 0; + + return true; +} + +static bool dsi_cm_calc_hsdiv_cb(int regm_dispc, unsigned long dispc, + void *data) +{ + struct dsi_clk_calc_ctx *ctx = data; + + ctx->dsi_cinfo.regm_dispc = regm_dispc; + ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc; + + return dispc_div_calc(dispc, ctx->req_pck_min, ctx->req_pck_max, + dsi_cm_calc_dispc_cb, ctx); +} + +static bool dsi_cm_calc_pll_cb(int regn, int regm, unsigned long fint, + unsigned long pll, void *data) +{ + struct dsi_clk_calc_ctx *ctx = data; + + ctx->dsi_cinfo.regn = regn; + ctx->dsi_cinfo.regm = regm; + ctx->dsi_cinfo.fint = fint; + ctx->dsi_cinfo.clkin4ddr = pll; + + return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->req_pck_min, + dsi_cm_calc_hsdiv_cb, ctx); +} + +static bool dsi_cm_calc(struct dsi_data *dsi, + const struct omap_dss_dsi_config *cfg, + struct dsi_clk_calc_ctx *ctx) +{ + unsigned long clkin; + int bitspp, ndl; + unsigned long pll_min, pll_max; + unsigned long pck, txbyteclk; + + clkin = clk_get_rate(dsi->sys_clk); + bitspp = dsi_get_pixel_size(cfg->pixel_format); + ndl = dsi->num_lanes_used - 1; + + /* + * Here we should calculate minimum txbyteclk to be able to send the + * frame in time, and also to handle TE. That's not very simple, though, + * especially as we go to LP between each pixel packet due to HW + * "feature". So let's just estimate very roughly and multiply by 1.5. + */ + pck = cfg->timings->pixelclock; + pck = pck * 3 / 2; + txbyteclk = pck * bitspp / 8 / ndl; + + memset(ctx, 0, sizeof(*ctx)); + ctx->dsidev = dsi->pdev; + ctx->config = cfg; + ctx->req_pck_min = pck; + ctx->req_pck_nom = pck; + ctx->req_pck_max = pck * 3 / 2; + ctx->dsi_cinfo.clkin = clkin; + + pll_min = max(cfg->hs_clk_min * 4, txbyteclk * 4 * 4); + pll_max = cfg->hs_clk_max * 4; + + return dsi_pll_calc(dsi->pdev, clkin, + pll_min, pll_max, + dsi_cm_calc_pll_cb, ctx); +} + +static bool dsi_vm_calc_blanking(struct dsi_clk_calc_ctx *ctx) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(ctx->dsidev); + const struct omap_dss_dsi_config *cfg = ctx->config; + int bitspp = dsi_get_pixel_size(cfg->pixel_format); + int ndl = dsi->num_lanes_used - 1; + unsigned long hsclk = ctx->dsi_cinfo.clkin4ddr / 4; + unsigned long byteclk = hsclk / 4; + + unsigned long dispc_pck, req_pck_min, req_pck_nom, req_pck_max; + int xres; + int panel_htot, panel_hbl; /* pixels */ + int dispc_htot, dispc_hbl; /* pixels */ + int dsi_htot, dsi_hact, dsi_hbl, hss, hse; /* byteclks */ + int hfp, hsa, hbp; + const struct omap_video_timings *req_vm; + struct omap_video_timings *dispc_vm; + struct omap_dss_dsi_videomode_timings *dsi_vm; + u64 dsi_tput, dispc_tput; + + dsi_tput = (u64)byteclk * ndl * 8; + + req_vm = cfg->timings; + req_pck_min = ctx->req_pck_min; + req_pck_max = ctx->req_pck_max; + req_pck_nom = ctx->req_pck_nom; + + dispc_pck = ctx->dispc_cinfo.pck; + dispc_tput = (u64)dispc_pck * bitspp; + + xres = req_vm->x_res; + + panel_hbl = req_vm->hfp + req_vm->hbp + req_vm->hsw; + panel_htot = xres + panel_hbl; + + dsi_hact = DIV_ROUND_UP(DIV_ROUND_UP(xres * bitspp, 8) + 6, ndl); + + /* + * When there are no line buffers, DISPC and DSI must have the + * same tput. Otherwise DISPC tput needs to be higher than DSI's. + */ + if (dsi->line_buffer_size < xres * bitspp / 8) { + if (dispc_tput != dsi_tput) + return false; + } else { + if (dispc_tput < dsi_tput) + return false; + } + + /* DSI tput must be over the min requirement */ + if (dsi_tput < (u64)bitspp * req_pck_min) + return false; + + /* When non-burst mode, DSI tput must be below max requirement. */ + if (cfg->trans_mode != OMAP_DSS_DSI_BURST_MODE) { + if (dsi_tput > (u64)bitspp * req_pck_max) + return false; + } + + hss = DIV_ROUND_UP(4, ndl); + + if (cfg->trans_mode == OMAP_DSS_DSI_PULSE_MODE) { + if (ndl == 3 && req_vm->hsw == 0) + hse = 1; + else + hse = DIV_ROUND_UP(4, ndl); + } else { + hse = 0; + } + + /* DSI htot to match the panel's nominal pck */ + dsi_htot = div64_u64((u64)panel_htot * byteclk, req_pck_nom); + + /* fail if there would be no time for blanking */ + if (dsi_htot < hss + hse + dsi_hact) + return false; + + /* total DSI blanking needed to achieve panel's TL */ + dsi_hbl = dsi_htot - dsi_hact; + + /* DISPC htot to match the DSI TL */ + dispc_htot = div64_u64((u64)dsi_htot * dispc_pck, byteclk); + + /* verify that the DSI and DISPC TLs are the same */ + if ((u64)dsi_htot * dispc_pck != (u64)dispc_htot * byteclk) + return false; + + dispc_hbl = dispc_htot - xres; + + /* setup DSI videomode */ + + dsi_vm = &ctx->dsi_vm; + memset(dsi_vm, 0, sizeof(*dsi_vm)); + + dsi_vm->hsclk = hsclk; + + dsi_vm->ndl = ndl; + dsi_vm->bitspp = bitspp; + + if (cfg->trans_mode != OMAP_DSS_DSI_PULSE_MODE) { + hsa = 0; + } else if (ndl == 3 && req_vm->hsw == 0) { + hsa = 0; + } else { + hsa = div64_u64((u64)req_vm->hsw * byteclk, req_pck_nom); + hsa = max(hsa - hse, 1); + } + + hbp = div64_u64((u64)req_vm->hbp * byteclk, req_pck_nom); + hbp = max(hbp, 1); + + hfp = dsi_hbl - (hss + hsa + hse + hbp); + if (hfp < 1) { + int t; + /* we need to take cycles from hbp */ + + t = 1 - hfp; + hbp = max(hbp - t, 1); + hfp = dsi_hbl - (hss + hsa + hse + hbp); + + if (hfp < 1 && hsa > 0) { + /* we need to take cycles from hsa */ + t = 1 - hfp; + hsa = max(hsa - t, 1); + hfp = dsi_hbl - (hss + hsa + hse + hbp); + } + } + + if (hfp < 1) + return false; + + dsi_vm->hss = hss; + dsi_vm->hsa = hsa; + dsi_vm->hse = hse; + dsi_vm->hbp = hbp; + dsi_vm->hact = xres; + dsi_vm->hfp = hfp; + + dsi_vm->vsa = req_vm->vsw; + dsi_vm->vbp = req_vm->vbp; + dsi_vm->vact = req_vm->y_res; + dsi_vm->vfp = req_vm->vfp; + + dsi_vm->trans_mode = cfg->trans_mode; + + dsi_vm->blanking_mode = 0; + dsi_vm->hsa_blanking_mode = 1; + dsi_vm->hfp_blanking_mode = 1; + dsi_vm->hbp_blanking_mode = 1; + + dsi_vm->ddr_clk_always_on = cfg->ddr_clk_always_on; + dsi_vm->window_sync = 4; + + /* setup DISPC videomode */ + + dispc_vm = &ctx->dispc_vm; + *dispc_vm = *req_vm; + dispc_vm->pixelclock = dispc_pck; + + if (cfg->trans_mode == OMAP_DSS_DSI_PULSE_MODE) { + hsa = div64_u64((u64)req_vm->hsw * dispc_pck, + req_pck_nom); + hsa = max(hsa, 1); + } else { + hsa = 1; + } + + hbp = div64_u64((u64)req_vm->hbp * dispc_pck, req_pck_nom); + hbp = max(hbp, 1); + + hfp = dispc_hbl - hsa - hbp; + if (hfp < 1) { + int t; + /* we need to take cycles from hbp */ + + t = 1 - hfp; + hbp = max(hbp - t, 1); + hfp = dispc_hbl - hsa - hbp; + + if (hfp < 1) { + /* we need to take cycles from hsa */ + t = 1 - hfp; + hsa = max(hsa - t, 1); + hfp = dispc_hbl - hsa - hbp; + } + } + + if (hfp < 1) + return false; + + dispc_vm->hfp = hfp; + dispc_vm->hsw = hsa; + dispc_vm->hbp = hbp; + + return true; +} + + +static bool dsi_vm_calc_dispc_cb(int lckd, int pckd, unsigned long lck, + unsigned long pck, void *data) +{ + struct dsi_clk_calc_ctx *ctx = data; + + ctx->dispc_cinfo.lck_div = lckd; + ctx->dispc_cinfo.pck_div = pckd; + ctx->dispc_cinfo.lck = lck; + ctx->dispc_cinfo.pck = pck; + + if (dsi_vm_calc_blanking(ctx) == false) + return false; + +#ifdef PRINT_VERBOSE_VM_TIMINGS + print_dispc_vm("dispc", &ctx->dispc_vm); + print_dsi_vm("dsi ", &ctx->dsi_vm); + print_dispc_vm("req ", ctx->config->timings); + print_dsi_dispc_vm("act ", &ctx->dsi_vm); +#endif + + return true; +} + +static bool dsi_vm_calc_hsdiv_cb(int regm_dispc, unsigned long dispc, + void *data) +{ + struct dsi_clk_calc_ctx *ctx = data; + unsigned long pck_max; + + ctx->dsi_cinfo.regm_dispc = regm_dispc; + ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc; + + /* + * In burst mode we can let the dispc pck be arbitrarily high, but it + * limits our scaling abilities. So for now, don't aim too high. + */ + + if (ctx->config->trans_mode == OMAP_DSS_DSI_BURST_MODE) + pck_max = ctx->req_pck_max + 10000000; + else + pck_max = ctx->req_pck_max; + + return dispc_div_calc(dispc, ctx->req_pck_min, pck_max, + dsi_vm_calc_dispc_cb, ctx); +} + +static bool dsi_vm_calc_pll_cb(int regn, int regm, unsigned long fint, + unsigned long pll, void *data) +{ + struct dsi_clk_calc_ctx *ctx = data; + + ctx->dsi_cinfo.regn = regn; + ctx->dsi_cinfo.regm = regm; + ctx->dsi_cinfo.fint = fint; + ctx->dsi_cinfo.clkin4ddr = pll; + + return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->req_pck_min, + dsi_vm_calc_hsdiv_cb, ctx); +} + +static bool dsi_vm_calc(struct dsi_data *dsi, + const struct omap_dss_dsi_config *cfg, + struct dsi_clk_calc_ctx *ctx) +{ + const struct omap_video_timings *t = cfg->timings; + unsigned long clkin; + unsigned long pll_min; + unsigned long pll_max; + int ndl = dsi->num_lanes_used - 1; + int bitspp = dsi_get_pixel_size(cfg->pixel_format); + unsigned long byteclk_min; + + clkin = clk_get_rate(dsi->sys_clk); + + memset(ctx, 0, sizeof(*ctx)); + ctx->dsidev = dsi->pdev; + ctx->config = cfg; + + ctx->dsi_cinfo.clkin = clkin; + + /* these limits should come from the panel driver */ + ctx->req_pck_min = t->pixelclock - 1000; + ctx->req_pck_nom = t->pixelclock; + ctx->req_pck_max = t->pixelclock + 1000; + + byteclk_min = div64_u64((u64)ctx->req_pck_min * bitspp, ndl * 8); + pll_min = max(cfg->hs_clk_min * 4, byteclk_min * 4 * 4); + + if (cfg->trans_mode == OMAP_DSS_DSI_BURST_MODE) { + pll_max = cfg->hs_clk_max * 4; + } else { + unsigned long byteclk_max; + byteclk_max = div64_u64((u64)ctx->req_pck_max * bitspp, + ndl * 8); + + pll_max = byteclk_max * 4 * 4; + } + + return dsi_pll_calc(dsi->pdev, clkin, + pll_min, pll_max, + dsi_vm_calc_pll_cb, ctx); +} + +static int dsi_set_config(struct omap_dss_device *dssdev, + const struct omap_dss_dsi_config *config) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct dsi_clk_calc_ctx ctx; + bool ok; + int r; + + mutex_lock(&dsi->lock); + + dsi->pix_fmt = config->pixel_format; + dsi->mode = config->mode; + + if (config->mode == OMAP_DSS_DSI_VIDEO_MODE) + ok = dsi_vm_calc(dsi, config, &ctx); + else + ok = dsi_cm_calc(dsi, config, &ctx); + + if (!ok) { + DSSERR("failed to find suitable DSI clock settings\n"); + r = -EINVAL; + goto err; + } + + dsi_pll_calc_dsi_fck(&ctx.dsi_cinfo); + + r = dsi_lp_clock_calc(&ctx.dsi_cinfo, config->lp_clk_min, + config->lp_clk_max); + if (r) { + DSSERR("failed to find suitable DSI LP clock settings\n"); + goto err; + } + + dsi->user_dsi_cinfo = ctx.dsi_cinfo; + dsi->user_dispc_cinfo = ctx.dispc_cinfo; + + dsi->timings = ctx.dispc_vm; + dsi->vm_timings = ctx.dsi_vm; + + mutex_unlock(&dsi->lock); + + return 0; +err: + mutex_unlock(&dsi->lock); + + return r; +} + +/* + * Return a hardcoded channel for the DSI output. This should work for + * current use cases, but this can be later expanded to either resolve + * the channel in some more dynamic manner, or get the channel as a user + * parameter. + */ +static enum omap_channel dsi_get_channel(int module_id) +{ + switch (omapdss_get_version()) { + case OMAPDSS_VER_OMAP24xx: + DSSWARN("DSI not supported\n"); + return OMAP_DSS_CHANNEL_LCD; + + case OMAPDSS_VER_OMAP34xx_ES1: + case OMAPDSS_VER_OMAP34xx_ES3: + case OMAPDSS_VER_OMAP3630: + case OMAPDSS_VER_AM35xx: + return OMAP_DSS_CHANNEL_LCD; + + case OMAPDSS_VER_OMAP4430_ES1: + case OMAPDSS_VER_OMAP4430_ES2: + case OMAPDSS_VER_OMAP4: + switch (module_id) { + case 0: + return OMAP_DSS_CHANNEL_LCD; + case 1: + return OMAP_DSS_CHANNEL_LCD2; + default: + DSSWARN("unsupported module id\n"); + return OMAP_DSS_CHANNEL_LCD; + } + + case OMAPDSS_VER_OMAP5: + switch (module_id) { + case 0: + return OMAP_DSS_CHANNEL_LCD; + case 1: + return OMAP_DSS_CHANNEL_LCD3; + default: + DSSWARN("unsupported module id\n"); + return OMAP_DSS_CHANNEL_LCD; + } + + default: + DSSWARN("unsupported DSS version\n"); + return OMAP_DSS_CHANNEL_LCD; + } +} + +static int dsi_request_vc(struct omap_dss_device *dssdev, int *channel) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + int i; + + for (i = 0; i < ARRAY_SIZE(dsi->vc); i++) { + if (!dsi->vc[i].dssdev) { + dsi->vc[i].dssdev = dssdev; + *channel = i; + return 0; + } + } + + DSSERR("cannot get VC for display %s", dssdev->name); + return -ENOSPC; +} + +static int dsi_set_vc_id(struct omap_dss_device *dssdev, int channel, int vc_id) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (vc_id < 0 || vc_id > 3) { + DSSERR("VC ID out of range\n"); + return -EINVAL; + } + + if (channel < 0 || channel > 3) { + DSSERR("Virtual Channel out of range\n"); + return -EINVAL; + } + + if (dsi->vc[channel].dssdev != dssdev) { + DSSERR("Virtual Channel not allocated to display %s\n", + dssdev->name); + return -EINVAL; + } + + dsi->vc[channel].vc_id = vc_id; + + return 0; +} + +static void dsi_release_vc(struct omap_dss_device *dssdev, int channel) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if ((channel >= 0 && channel <= 3) && + dsi->vc[channel].dssdev == dssdev) { + dsi->vc[channel].dssdev = NULL; + dsi->vc[channel].vc_id = 0; + } +} + +void dsi_wait_pll_hsdiv_dispc_active(struct platform_device *dsidev) +{ + if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 7, 1) != 1) + DSSERR("%s (%s) not active\n", + dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC), + dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC)); +} + +void dsi_wait_pll_hsdiv_dsi_active(struct platform_device *dsidev) +{ + if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 8, 1) != 1) + DSSERR("%s (%s) not active\n", + dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI), + dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI)); +} + +static void dsi_calc_clock_param_ranges(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + dsi->regn_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGN); + dsi->regm_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGM); + dsi->regm_dispc_max = + dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGM_DISPC); + dsi->regm_dsi_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGM_DSI); + dsi->fint_min = dss_feat_get_param_min(FEAT_PARAM_DSIPLL_FINT); + dsi->fint_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_FINT); + dsi->lpdiv_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_LPDIV); +} + +static int dsi_get_clocks(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct clk *clk; + + clk = devm_clk_get(&dsidev->dev, "fck"); + if (IS_ERR(clk)) { + DSSERR("can't get fck\n"); + return PTR_ERR(clk); + } + + dsi->dss_clk = clk; + + clk = devm_clk_get(&dsidev->dev, "sys_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get sys_clk\n"); + return PTR_ERR(clk); + } + + dsi->sys_clk = clk; + + return 0; +} + +static int dsi_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct omap_overlay_manager *mgr; + int r; + + r = dsi_regulator_init(dsidev); + if (r) + return r; + + mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); + if (!mgr) + return -ENODEV; + + r = dss_mgr_connect(mgr, dssdev); + if (r) + return r; + + r = omapdss_output_set_device(dssdev, dst); + if (r) { + DSSERR("failed to connect output to new device: %s\n", + dssdev->name); + dss_mgr_disconnect(mgr, dssdev); + return r; + } + + return 0; +} + +static void dsi_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + WARN_ON(dst != dssdev->dst); + + if (dst != dssdev->dst) + return; + + omapdss_output_unset_device(dssdev); + + if (dssdev->manager) + dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_dsi_ops dsi_ops = { + .connect = dsi_connect, + .disconnect = dsi_disconnect, + + .bus_lock = dsi_bus_lock, + .bus_unlock = dsi_bus_unlock, + + .enable = dsi_display_enable, + .disable = dsi_display_disable, + + .enable_hs = dsi_vc_enable_hs, + + .configure_pins = dsi_configure_pins, + .set_config = dsi_set_config, + + .enable_video_output = dsi_enable_video_output, + .disable_video_output = dsi_disable_video_output, + + .update = dsi_update, + + .enable_te = dsi_enable_te, + + .request_vc = dsi_request_vc, + .set_vc_id = dsi_set_vc_id, + .release_vc = dsi_release_vc, + + .dcs_write = dsi_vc_dcs_write, + .dcs_write_nosync = dsi_vc_dcs_write_nosync, + .dcs_read = dsi_vc_dcs_read, + + .gen_write = dsi_vc_generic_write, + .gen_write_nosync = dsi_vc_generic_write_nosync, + .gen_read = dsi_vc_generic_read, + + .bta_sync = dsi_vc_send_bta_sync, + + .set_max_rx_packet_size = dsi_vc_set_max_rx_packet_size, +}; + +static void dsi_init_output(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct omap_dss_device *out = &dsi->output; + + out->dev = &dsidev->dev; + out->id = dsi->module_id == 0 ? + OMAP_DSS_OUTPUT_DSI1 : OMAP_DSS_OUTPUT_DSI2; + + out->output_type = OMAP_DISPLAY_TYPE_DSI; + out->name = dsi->module_id == 0 ? "dsi.0" : "dsi.1"; + out->dispc_channel = dsi_get_channel(dsi->module_id); + out->ops.dsi = &dsi_ops; + out->owner = THIS_MODULE; + + omapdss_register_output(out); +} + +static void dsi_uninit_output(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct omap_dss_device *out = &dsi->output; + + omapdss_unregister_output(out); +} + +static int dsi_probe_of(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct dsi_data *dsi = dsi_get_dsidrv_data(pdev); + struct property *prop; + u32 lane_arr[10]; + int len, num_pins; + int r, i; + struct device_node *ep; + struct omap_dsi_pin_config pin_cfg; + + ep = omapdss_of_get_first_endpoint(node); + if (!ep) + return 0; + + prop = of_find_property(ep, "lanes", &len); + if (prop == NULL) { + dev_err(&pdev->dev, "failed to find lane data\n"); + r = -EINVAL; + goto err; + } + + num_pins = len / sizeof(u32); + + if (num_pins < 4 || num_pins % 2 != 0 || + num_pins > dsi->num_lanes_supported * 2) { + dev_err(&pdev->dev, "bad number of lanes\n"); + r = -EINVAL; + goto err; + } + + r = of_property_read_u32_array(ep, "lanes", lane_arr, num_pins); + if (r) { + dev_err(&pdev->dev, "failed to read lane data\n"); + goto err; + } + + pin_cfg.num_pins = num_pins; + for (i = 0; i < num_pins; ++i) + pin_cfg.pins[i] = (int)lane_arr[i]; + + r = dsi_configure_pins(&dsi->output, &pin_cfg); + if (r) { + dev_err(&pdev->dev, "failed to configure pins"); + goto err; + } + + of_node_put(ep); + + return 0; + +err: + of_node_put(ep); + return r; +} + +/* DSI1 HW IP initialisation */ +static int omap_dsihw_probe(struct platform_device *dsidev) +{ + u32 rev; + int r, i; + struct dsi_data *dsi; + struct resource *dsi_mem; + struct resource *res; + struct resource temp_res; + + dsi = devm_kzalloc(&dsidev->dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->pdev = dsidev; + dev_set_drvdata(&dsidev->dev, dsi); + + spin_lock_init(&dsi->irq_lock); + spin_lock_init(&dsi->errors_lock); + dsi->errors = 0; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS + spin_lock_init(&dsi->irq_stats_lock); + dsi->irq_stats.last_reset = jiffies; +#endif + + mutex_init(&dsi->lock); + sema_init(&dsi->bus_lock, 1); + + INIT_DEFERRABLE_WORK(&dsi->framedone_timeout_work, + dsi_framedone_timeout_work_callback); + +#ifdef DSI_CATCH_MISSING_TE + init_timer(&dsi->te_timer); + dsi->te_timer.function = dsi_te_timeout; + dsi->te_timer.data = 0; +#endif + + res = platform_get_resource_byname(dsidev, IORESOURCE_MEM, "proto"); + if (!res) { + res = platform_get_resource(dsidev, IORESOURCE_MEM, 0); + if (!res) { + DSSERR("can't get IORESOURCE_MEM DSI\n"); + return -EINVAL; + } + + temp_res.start = res->start; + temp_res.end = temp_res.start + DSI_PROTO_SZ - 1; + res = &temp_res; + } + + dsi_mem = res; + + dsi->proto_base = devm_ioremap(&dsidev->dev, res->start, + resource_size(res)); + if (!dsi->proto_base) { + DSSERR("can't ioremap DSI protocol engine\n"); + return -ENOMEM; + } + + res = platform_get_resource_byname(dsidev, IORESOURCE_MEM, "phy"); + if (!res) { + res = platform_get_resource(dsidev, IORESOURCE_MEM, 0); + if (!res) { + DSSERR("can't get IORESOURCE_MEM DSI\n"); + return -EINVAL; + } + + temp_res.start = res->start + DSI_PHY_OFFSET; + temp_res.end = temp_res.start + DSI_PHY_SZ - 1; + res = &temp_res; + } + + dsi->phy_base = devm_ioremap(&dsidev->dev, res->start, + resource_size(res)); + if (!dsi->proto_base) { + DSSERR("can't ioremap DSI PHY\n"); + return -ENOMEM; + } + + res = platform_get_resource_byname(dsidev, IORESOURCE_MEM, "pll"); + if (!res) { + res = platform_get_resource(dsidev, IORESOURCE_MEM, 0); + if (!res) { + DSSERR("can't get IORESOURCE_MEM DSI\n"); + return -EINVAL; + } + + temp_res.start = res->start + DSI_PLL_OFFSET; + temp_res.end = temp_res.start + DSI_PLL_SZ - 1; + res = &temp_res; + } + + dsi->pll_base = devm_ioremap(&dsidev->dev, res->start, + resource_size(res)); + if (!dsi->proto_base) { + DSSERR("can't ioremap DSI PLL\n"); + return -ENOMEM; + } + + dsi->irq = platform_get_irq(dsi->pdev, 0); + if (dsi->irq < 0) { + DSSERR("platform_get_irq failed\n"); + return -ENODEV; + } + + r = devm_request_irq(&dsidev->dev, dsi->irq, omap_dsi_irq_handler, + IRQF_SHARED, dev_name(&dsidev->dev), dsi->pdev); + if (r < 0) { + DSSERR("request_irq failed\n"); + return r; + } + + if (dsidev->dev.of_node) { + const struct of_device_id *match; + const struct dsi_module_id_data *d; + + match = of_match_node(dsi_of_match, dsidev->dev.of_node); + if (!match) { + DSSERR("unsupported DSI module\n"); + return -ENODEV; + } + + d = match->data; + + while (d->address != 0 && d->address != dsi_mem->start) + d++; + + if (d->address == 0) { + DSSERR("unsupported DSI module\n"); + return -ENODEV; + } + + dsi->module_id = d->id; + } else { + dsi->module_id = dsidev->id; + } + + /* DSI VCs initialization */ + for (i = 0; i < ARRAY_SIZE(dsi->vc); i++) { + dsi->vc[i].source = DSI_VC_SOURCE_L4; + dsi->vc[i].dssdev = NULL; + dsi->vc[i].vc_id = 0; + } + + dsi_calc_clock_param_ranges(dsidev); + + r = dsi_get_clocks(dsidev); + if (r) + return r; + + pm_runtime_enable(&dsidev->dev); + + r = dsi_runtime_get(dsidev); + if (r) + goto err_runtime_get; + + rev = dsi_read_reg(dsidev, DSI_REVISION); + dev_dbg(&dsidev->dev, "OMAP DSI rev %d.%d\n", + FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + + /* DSI on OMAP3 doesn't have register DSI_GNQ, set number + * of data to 3 by default */ + if (dss_has_feature(FEAT_DSI_GNQ)) + /* NB_DATA_LANES */ + dsi->num_lanes_supported = 1 + REG_GET(dsidev, DSI_GNQ, 11, 9); + else + dsi->num_lanes_supported = 3; + + dsi->line_buffer_size = dsi_get_line_buf_size(dsidev); + + dsi_init_output(dsidev); + + if (dsidev->dev.of_node) { + r = dsi_probe_of(dsidev); + if (r) { + DSSERR("Invalid DSI DT data\n"); + goto err_probe_of; + } + + r = of_platform_populate(dsidev->dev.of_node, NULL, NULL, + &dsidev->dev); + if (r) + DSSERR("Failed to populate DSI child devices: %d\n", r); + } + + dsi_runtime_put(dsidev); + + if (dsi->module_id == 0) + dss_debugfs_create_file("dsi1_regs", dsi1_dump_regs); + else if (dsi->module_id == 1) + dss_debugfs_create_file("dsi2_regs", dsi2_dump_regs); + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS + if (dsi->module_id == 0) + dss_debugfs_create_file("dsi1_irqs", dsi1_dump_irqs); + else if (dsi->module_id == 1) + dss_debugfs_create_file("dsi2_irqs", dsi2_dump_irqs); +#endif + + return 0; + +err_probe_of: + dsi_uninit_output(dsidev); + dsi_runtime_put(dsidev); + +err_runtime_get: + pm_runtime_disable(&dsidev->dev); + return r; +} + +static int dsi_unregister_child(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + platform_device_unregister(pdev); + return 0; +} + +static int __exit omap_dsihw_remove(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + device_for_each_child(&dsidev->dev, NULL, dsi_unregister_child); + + WARN_ON(dsi->scp_clk_refcount > 0); + + dsi_uninit_output(dsidev); + + pm_runtime_disable(&dsidev->dev); + + if (dsi->vdds_dsi_reg != NULL && dsi->vdds_dsi_enabled) { + regulator_disable(dsi->vdds_dsi_reg); + dsi->vdds_dsi_enabled = false; + } + + return 0; +} + +static int dsi_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dsi_data *dsi = dsi_get_dsidrv_data(pdev); + + dsi->is_enabled = false; + /* ensure the irq handler sees the is_enabled value */ + smp_wmb(); + /* wait for current handler to finish before turning the DSI off */ + synchronize_irq(dsi->irq); + + dispc_runtime_put(); + + return 0; +} + +static int dsi_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dsi_data *dsi = dsi_get_dsidrv_data(pdev); + int r; + + r = dispc_runtime_get(); + if (r) + return r; + + dsi->is_enabled = true; + /* ensure the irq handler sees the is_enabled value */ + smp_wmb(); + + return 0; +} + +static const struct dev_pm_ops dsi_pm_ops = { + .runtime_suspend = dsi_runtime_suspend, + .runtime_resume = dsi_runtime_resume, +}; + +static const struct dsi_module_id_data dsi_of_data_omap3[] = { + { .address = 0x4804fc00, .id = 0, }, + { }, +}; + +static const struct dsi_module_id_data dsi_of_data_omap4[] = { + { .address = 0x58004000, .id = 0, }, + { .address = 0x58005000, .id = 1, }, + { }, +}; + +static const struct of_device_id dsi_of_match[] = { + { .compatible = "ti,omap3-dsi", .data = dsi_of_data_omap3, }, + { .compatible = "ti,omap4-dsi", .data = dsi_of_data_omap4, }, + {}, +}; + +static struct platform_driver omap_dsihw_driver = { + .probe = omap_dsihw_probe, + .remove = __exit_p(omap_dsihw_remove), + .driver = { + .name = "omapdss_dsi", + .owner = THIS_MODULE, + .pm = &dsi_pm_ops, + .of_match_table = dsi_of_match, + }, +}; + +int __init dsi_init_platform_driver(void) +{ + return platform_driver_register(&omap_dsihw_driver); +} + +void __exit dsi_uninit_platform_driver(void) +{ + platform_driver_unregister(&omap_dsihw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/dss-of.c b/drivers/video/fbdev/omap2/dss/dss-of.c new file mode 100644 index 000000000000..a4b20aaf6142 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss-of.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * 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. + * + * 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. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/seq_file.h> + +#include <video/omapdss.h> + +struct device_node * +omapdss_of_get_next_port(const struct device_node *parent, + struct device_node *prev) +{ + struct device_node *port = NULL; + + if (!parent) + return NULL; + + if (!prev) { + struct device_node *ports; + /* + * It's the first call, we have to find a port subnode + * within this node or within an optional 'ports' node. + */ + ports = of_get_child_by_name(parent, "ports"); + if (ports) + parent = ports; + + port = of_get_child_by_name(parent, "port"); + + /* release the 'ports' node */ + of_node_put(ports); + } else { + struct device_node *ports; + + ports = of_get_parent(prev); + if (!ports) + return NULL; + + do { + port = of_get_next_child(ports, prev); + if (!port) { + of_node_put(ports); + return NULL; + } + prev = port; + } while (of_node_cmp(port->name, "port") != 0); + } + + return port; +} +EXPORT_SYMBOL_GPL(omapdss_of_get_next_port); + +struct device_node * +omapdss_of_get_next_endpoint(const struct device_node *parent, + struct device_node *prev) +{ + struct device_node *ep = NULL; + + if (!parent) + return NULL; + + do { + ep = of_get_next_child(parent, prev); + if (!ep) + return NULL; + prev = ep; + } while (of_node_cmp(ep->name, "endpoint") != 0); + + return ep; +} +EXPORT_SYMBOL_GPL(omapdss_of_get_next_endpoint); + +static struct device_node * +omapdss_of_get_remote_device_node(const struct device_node *node) +{ + struct device_node *np; + int i; + + np = of_parse_phandle(node, "remote-endpoint", 0); + + if (!np) + return NULL; + + np = of_get_next_parent(np); + + for (i = 0; i < 3 && np; ++i) { + struct property *prop; + + prop = of_find_property(np, "compatible", NULL); + + if (prop) + return np; + + np = of_get_next_parent(np); + } + + return NULL; +} + +struct device_node * +omapdss_of_get_first_endpoint(const struct device_node *parent) +{ + struct device_node *port, *ep; + + port = omapdss_of_get_next_port(parent, NULL); + + if (!port) + return NULL; + + ep = omapdss_of_get_next_endpoint(port, NULL); + + of_node_put(port); + + return ep; +} +EXPORT_SYMBOL_GPL(omapdss_of_get_first_endpoint); + +struct omap_dss_device * +omapdss_of_find_source_for_first_ep(struct device_node *node) +{ + struct device_node *ep; + struct device_node *src_node; + struct omap_dss_device *src; + + ep = omapdss_of_get_first_endpoint(node); + if (!ep) + return ERR_PTR(-EINVAL); + + src_node = omapdss_of_get_remote_device_node(ep); + + of_node_put(ep); + + if (!src_node) + return ERR_PTR(-EINVAL); + + src = omap_dss_find_output_by_node(src_node); + + of_node_put(src_node); + + if (!src) + return ERR_PTR(-EPROBE_DEFER); + + return src; +} +EXPORT_SYMBOL_GPL(omapdss_of_find_source_for_first_ep); diff --git a/drivers/video/fbdev/omap2/dss/dss.c b/drivers/video/fbdev/omap2/dss/dss.c new file mode 100644 index 000000000000..d55266c0e029 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss.c @@ -0,0 +1,972 @@ +/* + * linux/drivers/video/omap2/dss/dss.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "DSS" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/gfp.h> +#include <linux/sizes.h> +#include <linux/of.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +#define DSS_SZ_REGS SZ_512 + +struct dss_reg { + u16 idx; +}; + +#define DSS_REG(idx) ((const struct dss_reg) { idx }) + +#define DSS_REVISION DSS_REG(0x0000) +#define DSS_SYSCONFIG DSS_REG(0x0010) +#define DSS_SYSSTATUS DSS_REG(0x0014) +#define DSS_CONTROL DSS_REG(0x0040) +#define DSS_SDI_CONTROL DSS_REG(0x0044) +#define DSS_PLL_CONTROL DSS_REG(0x0048) +#define DSS_SDI_STATUS DSS_REG(0x005C) + +#define REG_GET(idx, start, end) \ + FLD_GET(dss_read_reg(idx), start, end) + +#define REG_FLD_MOD(idx, val, start, end) \ + dss_write_reg(idx, FLD_MOD(dss_read_reg(idx), val, start, end)) + +static int dss_runtime_get(void); +static void dss_runtime_put(void); + +struct dss_features { + u8 fck_div_max; + u8 dss_fck_multiplier; + const char *parent_clk_name; + int (*dpi_select_source)(enum omap_channel channel); +}; + +static struct { + struct platform_device *pdev; + void __iomem *base; + + struct clk *parent_clk; + struct clk *dss_clk; + unsigned long dss_clk_rate; + + unsigned long cache_req_pck; + unsigned long cache_prate; + struct dispc_clock_info cache_dispc_cinfo; + + enum omap_dss_clk_source dsi_clk_source[MAX_NUM_DSI]; + enum omap_dss_clk_source dispc_clk_source; + enum omap_dss_clk_source lcd_clk_source[MAX_DSS_LCD_MANAGERS]; + + bool ctx_valid; + u32 ctx[DSS_SZ_REGS / sizeof(u32)]; + + const struct dss_features *feat; +} dss; + +static const char * const dss_generic_clk_source_names[] = { + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC] = "DSI_PLL_HSDIV_DISPC", + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI] = "DSI_PLL_HSDIV_DSI", + [OMAP_DSS_CLK_SRC_FCK] = "DSS_FCK", + [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC] = "DSI_PLL2_HSDIV_DISPC", + [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI] = "DSI_PLL2_HSDIV_DSI", +}; + +static inline void dss_write_reg(const struct dss_reg idx, u32 val) +{ + __raw_writel(val, dss.base + idx.idx); +} + +static inline u32 dss_read_reg(const struct dss_reg idx) +{ + return __raw_readl(dss.base + idx.idx); +} + +#define SR(reg) \ + dss.ctx[(DSS_##reg).idx / sizeof(u32)] = dss_read_reg(DSS_##reg) +#define RR(reg) \ + dss_write_reg(DSS_##reg, dss.ctx[(DSS_##reg).idx / sizeof(u32)]) + +static void dss_save_context(void) +{ + DSSDBG("dss_save_context\n"); + + SR(CONTROL); + + if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & + OMAP_DISPLAY_TYPE_SDI) { + SR(SDI_CONTROL); + SR(PLL_CONTROL); + } + + dss.ctx_valid = true; + + DSSDBG("context saved\n"); +} + +static void dss_restore_context(void) +{ + DSSDBG("dss_restore_context\n"); + + if (!dss.ctx_valid) + return; + + RR(CONTROL); + + if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & + OMAP_DISPLAY_TYPE_SDI) { + RR(SDI_CONTROL); + RR(PLL_CONTROL); + } + + DSSDBG("context restored\n"); +} + +#undef SR +#undef RR + +void dss_sdi_init(int datapairs) +{ + u32 l; + + BUG_ON(datapairs > 3 || datapairs < 1); + + l = dss_read_reg(DSS_SDI_CONTROL); + l = FLD_MOD(l, 0xf, 19, 15); /* SDI_PDIV */ + l = FLD_MOD(l, datapairs-1, 3, 2); /* SDI_PRSEL */ + l = FLD_MOD(l, 2, 1, 0); /* SDI_BWSEL */ + dss_write_reg(DSS_SDI_CONTROL, l); + + l = dss_read_reg(DSS_PLL_CONTROL); + l = FLD_MOD(l, 0x7, 25, 22); /* SDI_PLL_FREQSEL */ + l = FLD_MOD(l, 0xb, 16, 11); /* SDI_PLL_REGN */ + l = FLD_MOD(l, 0xb4, 10, 1); /* SDI_PLL_REGM */ + dss_write_reg(DSS_PLL_CONTROL, l); +} + +int dss_sdi_enable(void) +{ + unsigned long timeout; + + dispc_pck_free_enable(1); + + /* Reset SDI PLL */ + REG_FLD_MOD(DSS_PLL_CONTROL, 1, 18, 18); /* SDI_PLL_SYSRESET */ + udelay(1); /* wait 2x PCLK */ + + /* Lock SDI PLL */ + REG_FLD_MOD(DSS_PLL_CONTROL, 1, 28, 28); /* SDI_PLL_GOBIT */ + + /* Waiting for PLL lock request to complete */ + timeout = jiffies + msecs_to_jiffies(500); + while (dss_read_reg(DSS_SDI_STATUS) & (1 << 6)) { + if (time_after_eq(jiffies, timeout)) { + DSSERR("PLL lock request timed out\n"); + goto err1; + } + } + + /* Clearing PLL_GO bit */ + REG_FLD_MOD(DSS_PLL_CONTROL, 0, 28, 28); + + /* Waiting for PLL to lock */ + timeout = jiffies + msecs_to_jiffies(500); + while (!(dss_read_reg(DSS_SDI_STATUS) & (1 << 5))) { + if (time_after_eq(jiffies, timeout)) { + DSSERR("PLL lock timed out\n"); + goto err1; + } + } + + dispc_lcd_enable_signal(1); + + /* Waiting for SDI reset to complete */ + timeout = jiffies + msecs_to_jiffies(500); + while (!(dss_read_reg(DSS_SDI_STATUS) & (1 << 2))) { + if (time_after_eq(jiffies, timeout)) { + DSSERR("SDI reset timed out\n"); + goto err2; + } + } + + return 0; + + err2: + dispc_lcd_enable_signal(0); + err1: + /* Reset SDI PLL */ + REG_FLD_MOD(DSS_PLL_CONTROL, 0, 18, 18); /* SDI_PLL_SYSRESET */ + + dispc_pck_free_enable(0); + + return -ETIMEDOUT; +} + +void dss_sdi_disable(void) +{ + dispc_lcd_enable_signal(0); + + dispc_pck_free_enable(0); + + /* Reset SDI PLL */ + REG_FLD_MOD(DSS_PLL_CONTROL, 0, 18, 18); /* SDI_PLL_SYSRESET */ +} + +const char *dss_get_generic_clk_source_name(enum omap_dss_clk_source clk_src) +{ + return dss_generic_clk_source_names[clk_src]; +} + +void dss_dump_clocks(struct seq_file *s) +{ + const char *fclk_name, *fclk_real_name; + unsigned long fclk_rate; + + if (dss_runtime_get()) + return; + + seq_printf(s, "- DSS -\n"); + + fclk_name = dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_FCK); + fclk_real_name = dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_FCK); + fclk_rate = clk_get_rate(dss.dss_clk); + + seq_printf(s, "%s (%s) = %lu\n", + fclk_name, fclk_real_name, + fclk_rate); + + dss_runtime_put(); +} + +static void dss_dump_regs(struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, dss_read_reg(r)) + + if (dss_runtime_get()) + return; + + DUMPREG(DSS_REVISION); + DUMPREG(DSS_SYSCONFIG); + DUMPREG(DSS_SYSSTATUS); + DUMPREG(DSS_CONTROL); + + if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & + OMAP_DISPLAY_TYPE_SDI) { + DUMPREG(DSS_SDI_CONTROL); + DUMPREG(DSS_PLL_CONTROL); + DUMPREG(DSS_SDI_STATUS); + } + + dss_runtime_put(); +#undef DUMPREG +} + +static void dss_select_dispc_clk_source(enum omap_dss_clk_source clk_src) +{ + struct platform_device *dsidev; + int b; + u8 start, end; + + switch (clk_src) { + case OMAP_DSS_CLK_SRC_FCK: + b = 0; + break; + case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: + b = 1; + dsidev = dsi_get_dsidev_from_id(0); + dsi_wait_pll_hsdiv_dispc_active(dsidev); + break; + case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: + b = 2; + dsidev = dsi_get_dsidev_from_id(1); + dsi_wait_pll_hsdiv_dispc_active(dsidev); + break; + default: + BUG(); + return; + } + + dss_feat_get_reg_field(FEAT_REG_DISPC_CLK_SWITCH, &start, &end); + + REG_FLD_MOD(DSS_CONTROL, b, start, end); /* DISPC_CLK_SWITCH */ + + dss.dispc_clk_source = clk_src; +} + +void dss_select_dsi_clk_source(int dsi_module, + enum omap_dss_clk_source clk_src) +{ + struct platform_device *dsidev; + int b, pos; + + switch (clk_src) { + case OMAP_DSS_CLK_SRC_FCK: + b = 0; + break; + case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI: + BUG_ON(dsi_module != 0); + b = 1; + dsidev = dsi_get_dsidev_from_id(0); + dsi_wait_pll_hsdiv_dsi_active(dsidev); + break; + case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI: + BUG_ON(dsi_module != 1); + b = 1; + dsidev = dsi_get_dsidev_from_id(1); + dsi_wait_pll_hsdiv_dsi_active(dsidev); + break; + default: + BUG(); + return; + } + + pos = dsi_module == 0 ? 1 : 10; + REG_FLD_MOD(DSS_CONTROL, b, pos, pos); /* DSIx_CLK_SWITCH */ + + dss.dsi_clk_source[dsi_module] = clk_src; +} + +void dss_select_lcd_clk_source(enum omap_channel channel, + enum omap_dss_clk_source clk_src) +{ + struct platform_device *dsidev; + int b, ix, pos; + + if (!dss_has_feature(FEAT_LCD_CLK_SRC)) { + dss_select_dispc_clk_source(clk_src); + return; + } + + switch (clk_src) { + case OMAP_DSS_CLK_SRC_FCK: + b = 0; + break; + case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: + BUG_ON(channel != OMAP_DSS_CHANNEL_LCD); + b = 1; + dsidev = dsi_get_dsidev_from_id(0); + dsi_wait_pll_hsdiv_dispc_active(dsidev); + break; + case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: + BUG_ON(channel != OMAP_DSS_CHANNEL_LCD2 && + channel != OMAP_DSS_CHANNEL_LCD3); + b = 1; + dsidev = dsi_get_dsidev_from_id(1); + dsi_wait_pll_hsdiv_dispc_active(dsidev); + break; + default: + BUG(); + return; + } + + pos = channel == OMAP_DSS_CHANNEL_LCD ? 0 : + (channel == OMAP_DSS_CHANNEL_LCD2 ? 12 : 19); + REG_FLD_MOD(DSS_CONTROL, b, pos, pos); /* LCDx_CLK_SWITCH */ + + ix = channel == OMAP_DSS_CHANNEL_LCD ? 0 : + (channel == OMAP_DSS_CHANNEL_LCD2 ? 1 : 2); + dss.lcd_clk_source[ix] = clk_src; +} + +enum omap_dss_clk_source dss_get_dispc_clk_source(void) +{ + return dss.dispc_clk_source; +} + +enum omap_dss_clk_source dss_get_dsi_clk_source(int dsi_module) +{ + return dss.dsi_clk_source[dsi_module]; +} + +enum omap_dss_clk_source dss_get_lcd_clk_source(enum omap_channel channel) +{ + if (dss_has_feature(FEAT_LCD_CLK_SRC)) { + int ix = channel == OMAP_DSS_CHANNEL_LCD ? 0 : + (channel == OMAP_DSS_CHANNEL_LCD2 ? 1 : 2); + return dss.lcd_clk_source[ix]; + } else { + /* LCD_CLK source is the same as DISPC_FCLK source for + * OMAP2 and OMAP3 */ + return dss.dispc_clk_source; + } +} + +bool dss_div_calc(unsigned long pck, unsigned long fck_min, + dss_div_calc_func func, void *data) +{ + int fckd, fckd_start, fckd_stop; + unsigned long fck; + unsigned long fck_hw_max; + unsigned long fckd_hw_max; + unsigned long prate; + unsigned m; + + fck_hw_max = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + + if (dss.parent_clk == NULL) { + unsigned pckd; + + pckd = fck_hw_max / pck; + + fck = pck * pckd; + + fck = clk_round_rate(dss.dss_clk, fck); + + return func(fck, data); + } + + fckd_hw_max = dss.feat->fck_div_max; + + m = dss.feat->dss_fck_multiplier; + prate = clk_get_rate(dss.parent_clk); + + fck_min = fck_min ? fck_min : 1; + + fckd_start = min(prate * m / fck_min, fckd_hw_max); + fckd_stop = max(DIV_ROUND_UP(prate * m, fck_hw_max), 1ul); + + for (fckd = fckd_start; fckd >= fckd_stop; --fckd) { + fck = DIV_ROUND_UP(prate, fckd) * m; + + if (func(fck, data)) + return true; + } + + return false; +} + +int dss_set_fck_rate(unsigned long rate) +{ + int r; + + DSSDBG("set fck to %lu\n", rate); + + r = clk_set_rate(dss.dss_clk, rate); + if (r) + return r; + + dss.dss_clk_rate = clk_get_rate(dss.dss_clk); + + WARN_ONCE(dss.dss_clk_rate != rate, + "clk rate mismatch: %lu != %lu", dss.dss_clk_rate, + rate); + + return 0; +} + +unsigned long dss_get_dispc_clk_rate(void) +{ + return dss.dss_clk_rate; +} + +static int dss_setup_default_clock(void) +{ + unsigned long max_dss_fck, prate; + unsigned long fck; + unsigned fck_div; + int r; + + max_dss_fck = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + + if (dss.parent_clk == NULL) { + fck = clk_round_rate(dss.dss_clk, max_dss_fck); + } else { + prate = clk_get_rate(dss.parent_clk); + + fck_div = DIV_ROUND_UP(prate * dss.feat->dss_fck_multiplier, + max_dss_fck); + fck = DIV_ROUND_UP(prate, fck_div) * dss.feat->dss_fck_multiplier; + } + + r = dss_set_fck_rate(fck); + if (r) + return r; + + return 0; +} + +void dss_set_venc_output(enum omap_dss_venc_type type) +{ + int l = 0; + + if (type == OMAP_DSS_VENC_TYPE_COMPOSITE) + l = 0; + else if (type == OMAP_DSS_VENC_TYPE_SVIDEO) + l = 1; + else + BUG(); + + /* venc out selection. 0 = comp, 1 = svideo */ + REG_FLD_MOD(DSS_CONTROL, l, 6, 6); +} + +void dss_set_dac_pwrdn_bgz(bool enable) +{ + REG_FLD_MOD(DSS_CONTROL, enable, 5, 5); /* DAC Power-Down Control */ +} + +void dss_select_hdmi_venc_clk_source(enum dss_hdmi_venc_clk_source_select src) +{ + enum omap_display_type dp; + dp = dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_DIGIT); + + /* Complain about invalid selections */ + WARN_ON((src == DSS_VENC_TV_CLK) && !(dp & OMAP_DISPLAY_TYPE_VENC)); + WARN_ON((src == DSS_HDMI_M_PCLK) && !(dp & OMAP_DISPLAY_TYPE_HDMI)); + + /* Select only if we have options */ + if ((dp & OMAP_DISPLAY_TYPE_VENC) && (dp & OMAP_DISPLAY_TYPE_HDMI)) + REG_FLD_MOD(DSS_CONTROL, src, 15, 15); /* VENC_HDMI_SWITCH */ +} + +enum dss_hdmi_venc_clk_source_select dss_get_hdmi_venc_clk_source(void) +{ + enum omap_display_type displays; + + displays = dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_DIGIT); + if ((displays & OMAP_DISPLAY_TYPE_HDMI) == 0) + return DSS_VENC_TV_CLK; + + if ((displays & OMAP_DISPLAY_TYPE_VENC) == 0) + return DSS_HDMI_M_PCLK; + + return REG_GET(DSS_CONTROL, 15, 15); +} + +static int dss_dpi_select_source_omap2_omap3(enum omap_channel channel) +{ + if (channel != OMAP_DSS_CHANNEL_LCD) + return -EINVAL; + + return 0; +} + +static int dss_dpi_select_source_omap4(enum omap_channel channel) +{ + int val; + + switch (channel) { + case OMAP_DSS_CHANNEL_LCD2: + val = 0; + break; + case OMAP_DSS_CHANNEL_DIGIT: + val = 1; + break; + default: + return -EINVAL; + } + + REG_FLD_MOD(DSS_CONTROL, val, 17, 17); + + return 0; +} + +static int dss_dpi_select_source_omap5(enum omap_channel channel) +{ + int val; + + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + val = 1; + break; + case OMAP_DSS_CHANNEL_LCD2: + val = 2; + break; + case OMAP_DSS_CHANNEL_LCD3: + val = 3; + break; + case OMAP_DSS_CHANNEL_DIGIT: + val = 0; + break; + default: + return -EINVAL; + } + + REG_FLD_MOD(DSS_CONTROL, val, 17, 16); + + return 0; +} + +int dss_dpi_select_source(enum omap_channel channel) +{ + return dss.feat->dpi_select_source(channel); +} + +static int dss_get_clocks(void) +{ + struct clk *clk; + + clk = devm_clk_get(&dss.pdev->dev, "fck"); + if (IS_ERR(clk)) { + DSSERR("can't get clock fck\n"); + return PTR_ERR(clk); + } + + dss.dss_clk = clk; + + if (dss.feat->parent_clk_name) { + clk = clk_get(NULL, dss.feat->parent_clk_name); + if (IS_ERR(clk)) { + DSSERR("Failed to get %s\n", dss.feat->parent_clk_name); + return PTR_ERR(clk); + } + } else { + clk = NULL; + } + + dss.parent_clk = clk; + + return 0; +} + +static void dss_put_clocks(void) +{ + if (dss.parent_clk) + clk_put(dss.parent_clk); +} + +static int dss_runtime_get(void) +{ + int r; + + DSSDBG("dss_runtime_get\n"); + + r = pm_runtime_get_sync(&dss.pdev->dev); + WARN_ON(r < 0); + return r < 0 ? r : 0; +} + +static void dss_runtime_put(void) +{ + int r; + + DSSDBG("dss_runtime_put\n"); + + r = pm_runtime_put_sync(&dss.pdev->dev); + WARN_ON(r < 0 && r != -ENOSYS && r != -EBUSY); +} + +/* DEBUGFS */ +#if defined(CONFIG_OMAP2_DSS_DEBUGFS) +void dss_debug_dump_clocks(struct seq_file *s) +{ + dss_dump_clocks(s); + dispc_dump_clocks(s); +#ifdef CONFIG_OMAP2_DSS_DSI + dsi_dump_clocks(s); +#endif +} +#endif + +static const struct dss_features omap24xx_dss_feats __initconst = { + /* + * fck div max is really 16, but the divider range has gaps. The range + * from 1 to 6 has no gaps, so let's use that as a max. + */ + .fck_div_max = 6, + .dss_fck_multiplier = 2, + .parent_clk_name = "core_ck", + .dpi_select_source = &dss_dpi_select_source_omap2_omap3, +}; + +static const struct dss_features omap34xx_dss_feats __initconst = { + .fck_div_max = 16, + .dss_fck_multiplier = 2, + .parent_clk_name = "dpll4_ck", + .dpi_select_source = &dss_dpi_select_source_omap2_omap3, +}; + +static const struct dss_features omap3630_dss_feats __initconst = { + .fck_div_max = 32, + .dss_fck_multiplier = 1, + .parent_clk_name = "dpll4_ck", + .dpi_select_source = &dss_dpi_select_source_omap2_omap3, +}; + +static const struct dss_features omap44xx_dss_feats __initconst = { + .fck_div_max = 32, + .dss_fck_multiplier = 1, + .parent_clk_name = "dpll_per_x2_ck", + .dpi_select_source = &dss_dpi_select_source_omap4, +}; + +static const struct dss_features omap54xx_dss_feats __initconst = { + .fck_div_max = 64, + .dss_fck_multiplier = 1, + .parent_clk_name = "dpll_per_x2_ck", + .dpi_select_source = &dss_dpi_select_source_omap5, +}; + +static int __init dss_init_features(struct platform_device *pdev) +{ + const struct dss_features *src; + struct dss_features *dst; + + dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL); + if (!dst) { + dev_err(&pdev->dev, "Failed to allocate local DSS Features\n"); + return -ENOMEM; + } + + switch (omapdss_get_version()) { + case OMAPDSS_VER_OMAP24xx: + src = &omap24xx_dss_feats; + break; + + case OMAPDSS_VER_OMAP34xx_ES1: + case OMAPDSS_VER_OMAP34xx_ES3: + case OMAPDSS_VER_AM35xx: + src = &omap34xx_dss_feats; + break; + + case OMAPDSS_VER_OMAP3630: + src = &omap3630_dss_feats; + break; + + case OMAPDSS_VER_OMAP4430_ES1: + case OMAPDSS_VER_OMAP4430_ES2: + case OMAPDSS_VER_OMAP4: + src = &omap44xx_dss_feats; + break; + + case OMAPDSS_VER_OMAP5: + src = &omap54xx_dss_feats; + break; + + default: + return -ENODEV; + } + + memcpy(dst, src, sizeof(*dst)); + dss.feat = dst; + + return 0; +} + +static int __init dss_init_ports(struct platform_device *pdev) +{ + struct device_node *parent = pdev->dev.of_node; + struct device_node *port; + int r; + + if (parent == NULL) + return 0; + + port = omapdss_of_get_next_port(parent, NULL); + if (!port) { +#ifdef CONFIG_OMAP2_DSS_DPI + dpi_init_port(pdev, parent); +#endif + return 0; + } + + do { + u32 reg; + + r = of_property_read_u32(port, "reg", ®); + if (r) + reg = 0; + +#ifdef CONFIG_OMAP2_DSS_DPI + if (reg == 0) + dpi_init_port(pdev, port); +#endif + +#ifdef CONFIG_OMAP2_DSS_SDI + if (reg == 1) + sdi_init_port(pdev, port); +#endif + + } while ((port = omapdss_of_get_next_port(parent, port)) != NULL); + + return 0; +} + +static void dss_uninit_ports(void) +{ +#ifdef CONFIG_OMAP2_DSS_DPI + dpi_uninit_port(); +#endif + +#ifdef CONFIG_OMAP2_DSS_SDI + sdi_uninit_port(); +#endif +} + +/* DSS HW IP initialisation */ +static int __init omap_dsshw_probe(struct platform_device *pdev) +{ + struct resource *dss_mem; + u32 rev; + int r; + + dss.pdev = pdev; + + r = dss_init_features(dss.pdev); + if (r) + return r; + + dss_mem = platform_get_resource(dss.pdev, IORESOURCE_MEM, 0); + if (!dss_mem) { + DSSERR("can't get IORESOURCE_MEM DSS\n"); + return -EINVAL; + } + + dss.base = devm_ioremap(&pdev->dev, dss_mem->start, + resource_size(dss_mem)); + if (!dss.base) { + DSSERR("can't ioremap DSS\n"); + return -ENOMEM; + } + + r = dss_get_clocks(); + if (r) + return r; + + r = dss_setup_default_clock(); + if (r) + goto err_setup_clocks; + + pm_runtime_enable(&pdev->dev); + + r = dss_runtime_get(); + if (r) + goto err_runtime_get; + + dss.dss_clk_rate = clk_get_rate(dss.dss_clk); + + /* Select DPLL */ + REG_FLD_MOD(DSS_CONTROL, 0, 0, 0); + + dss_select_dispc_clk_source(OMAP_DSS_CLK_SRC_FCK); + +#ifdef CONFIG_OMAP2_DSS_VENC + REG_FLD_MOD(DSS_CONTROL, 1, 4, 4); /* venc dac demen */ + REG_FLD_MOD(DSS_CONTROL, 1, 3, 3); /* venc clock 4x enable */ + REG_FLD_MOD(DSS_CONTROL, 0, 2, 2); /* venc clock mode = normal */ +#endif + dss.dsi_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; + dss.dsi_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; + dss.dispc_clk_source = OMAP_DSS_CLK_SRC_FCK; + dss.lcd_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; + dss.lcd_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; + + dss_init_ports(pdev); + + rev = dss_read_reg(DSS_REVISION); + printk(KERN_INFO "OMAP DSS rev %d.%d\n", + FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + + dss_runtime_put(); + + dss_debugfs_create_file("dss", dss_dump_regs); + + return 0; + +err_runtime_get: + pm_runtime_disable(&pdev->dev); +err_setup_clocks: + dss_put_clocks(); + return r; +} + +static int __exit omap_dsshw_remove(struct platform_device *pdev) +{ + dss_uninit_ports(); + + pm_runtime_disable(&pdev->dev); + + dss_put_clocks(); + + return 0; +} + +static int dss_runtime_suspend(struct device *dev) +{ + dss_save_context(); + dss_set_min_bus_tput(dev, 0); + return 0; +} + +static int dss_runtime_resume(struct device *dev) +{ + int r; + /* + * Set an arbitrarily high tput request to ensure OPP100. + * What we should really do is to make a request to stay in OPP100, + * without any tput requirements, but that is not currently possible + * via the PM layer. + */ + + r = dss_set_min_bus_tput(dev, 1000000000); + if (r) + return r; + + dss_restore_context(); + return 0; +} + +static const struct dev_pm_ops dss_pm_ops = { + .runtime_suspend = dss_runtime_suspend, + .runtime_resume = dss_runtime_resume, +}; + +static const struct of_device_id dss_of_match[] = { + { .compatible = "ti,omap2-dss", }, + { .compatible = "ti,omap3-dss", }, + { .compatible = "ti,omap4-dss", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dss_of_match); + +static struct platform_driver omap_dsshw_driver = { + .remove = __exit_p(omap_dsshw_remove), + .driver = { + .name = "omapdss_dss", + .owner = THIS_MODULE, + .pm = &dss_pm_ops, + .of_match_table = dss_of_match, + }, +}; + +int __init dss_init_platform_driver(void) +{ + return platform_driver_probe(&omap_dsshw_driver, omap_dsshw_probe); +} + +void dss_uninit_platform_driver(void) +{ + platform_driver_unregister(&omap_dsshw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/dss.h b/drivers/video/fbdev/omap2/dss/dss.h new file mode 100644 index 000000000000..560078fcb198 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss.h @@ -0,0 +1,438 @@ +/* + * linux/drivers/video/omap2/dss/dss.h + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP2_DSS_H +#define __OMAP2_DSS_H + +#include <linux/interrupt.h> + +#ifdef pr_fmt +#undef pr_fmt +#endif + +#ifdef DSS_SUBSYS_NAME +#define pr_fmt(fmt) DSS_SUBSYS_NAME ": " fmt +#else +#define pr_fmt(fmt) fmt +#endif + +#define DSSDBG(format, ...) \ + pr_debug(format, ## __VA_ARGS__) + +#ifdef DSS_SUBSYS_NAME +#define DSSERR(format, ...) \ + printk(KERN_ERR "omapdss " DSS_SUBSYS_NAME " error: " format, \ + ## __VA_ARGS__) +#else +#define DSSERR(format, ...) \ + printk(KERN_ERR "omapdss error: " format, ## __VA_ARGS__) +#endif + +#ifdef DSS_SUBSYS_NAME +#define DSSINFO(format, ...) \ + printk(KERN_INFO "omapdss " DSS_SUBSYS_NAME ": " format, \ + ## __VA_ARGS__) +#else +#define DSSINFO(format, ...) \ + printk(KERN_INFO "omapdss: " format, ## __VA_ARGS__) +#endif + +#ifdef DSS_SUBSYS_NAME +#define DSSWARN(format, ...) \ + printk(KERN_WARNING "omapdss " DSS_SUBSYS_NAME ": " format, \ + ## __VA_ARGS__) +#else +#define DSSWARN(format, ...) \ + printk(KERN_WARNING "omapdss: " format, ## __VA_ARGS__) +#endif + +/* OMAP TRM gives bitfields as start:end, where start is the higher bit + number. For example 7:0 */ +#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end)) +#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end)) +#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end)) +#define FLD_MOD(orig, val, start, end) \ + (((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end)) + +enum dss_io_pad_mode { + DSS_IO_PAD_MODE_RESET, + DSS_IO_PAD_MODE_RFBI, + DSS_IO_PAD_MODE_BYPASS, +}; + +enum dss_hdmi_venc_clk_source_select { + DSS_VENC_TV_CLK = 0, + DSS_HDMI_M_PCLK = 1, +}; + +enum dss_dsi_content_type { + DSS_DSI_CONTENT_DCS, + DSS_DSI_CONTENT_GENERIC, +}; + +enum dss_writeback_channel { + DSS_WB_LCD1_MGR = 0, + DSS_WB_LCD2_MGR = 1, + DSS_WB_TV_MGR = 2, + DSS_WB_OVL0 = 3, + DSS_WB_OVL1 = 4, + DSS_WB_OVL2 = 5, + DSS_WB_OVL3 = 6, + DSS_WB_LCD3_MGR = 7, +}; + +struct dispc_clock_info { + /* rates that we get with dividers below */ + unsigned long lck; + unsigned long pck; + + /* dividers */ + u16 lck_div; + u16 pck_div; +}; + +struct dsi_clock_info { + /* rates that we get with dividers below */ + unsigned long fint; + unsigned long clkin4ddr; + unsigned long clkin; + unsigned long dsi_pll_hsdiv_dispc_clk; /* OMAP3: DSI1_PLL_CLK + * OMAP4: PLLx_CLK1 */ + unsigned long dsi_pll_hsdiv_dsi_clk; /* OMAP3: DSI2_PLL_CLK + * OMAP4: PLLx_CLK2 */ + unsigned long lp_clk; + + /* dividers */ + u16 regn; + u16 regm; + u16 regm_dispc; /* OMAP3: REGM3 + * OMAP4: REGM4 */ + u16 regm_dsi; /* OMAP3: REGM4 + * OMAP4: REGM5 */ + u16 lp_clk_div; +}; + +struct dss_lcd_mgr_config { + enum dss_io_pad_mode io_pad_mode; + + bool stallmode; + bool fifohandcheck; + + struct dispc_clock_info clock_info; + + int video_port_width; + + int lcden_sig_polarity; +}; + +struct seq_file; +struct platform_device; + +/* core */ +struct platform_device *dss_get_core_pdev(void); +int dss_dsi_enable_pads(int dsi_id, unsigned lane_mask); +void dss_dsi_disable_pads(int dsi_id, unsigned lane_mask); +int dss_set_min_bus_tput(struct device *dev, unsigned long tput); +int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *)); + +/* display */ +int dss_suspend_all_devices(void); +int dss_resume_all_devices(void); +void dss_disable_all_devices(void); + +int display_init_sysfs(struct platform_device *pdev); +void display_uninit_sysfs(struct platform_device *pdev); + +/* manager */ +int dss_init_overlay_managers(void); +void dss_uninit_overlay_managers(void); +int dss_init_overlay_managers_sysfs(struct platform_device *pdev); +void dss_uninit_overlay_managers_sysfs(struct platform_device *pdev); +int dss_mgr_simple_check(struct omap_overlay_manager *mgr, + const struct omap_overlay_manager_info *info); +int dss_mgr_check_timings(struct omap_overlay_manager *mgr, + const struct omap_video_timings *timings); +int dss_mgr_check(struct omap_overlay_manager *mgr, + struct omap_overlay_manager_info *info, + const struct omap_video_timings *mgr_timings, + const struct dss_lcd_mgr_config *config, + struct omap_overlay_info **overlay_infos); + +static inline bool dss_mgr_is_lcd(enum omap_channel id) +{ + if (id == OMAP_DSS_CHANNEL_LCD || id == OMAP_DSS_CHANNEL_LCD2 || + id == OMAP_DSS_CHANNEL_LCD3) + return true; + else + return false; +} + +int dss_manager_kobj_init(struct omap_overlay_manager *mgr, + struct platform_device *pdev); +void dss_manager_kobj_uninit(struct omap_overlay_manager *mgr); + +/* overlay */ +void dss_init_overlays(struct platform_device *pdev); +void dss_uninit_overlays(struct platform_device *pdev); +void dss_overlay_setup_dispc_manager(struct omap_overlay_manager *mgr); +int dss_ovl_simple_check(struct omap_overlay *ovl, + const struct omap_overlay_info *info); +int dss_ovl_check(struct omap_overlay *ovl, struct omap_overlay_info *info, + const struct omap_video_timings *mgr_timings); +bool dss_ovl_use_replication(struct dss_lcd_mgr_config config, + enum omap_color_mode mode); +int dss_overlay_kobj_init(struct omap_overlay *ovl, + struct platform_device *pdev); +void dss_overlay_kobj_uninit(struct omap_overlay *ovl); + +/* DSS */ +int dss_init_platform_driver(void) __init; +void dss_uninit_platform_driver(void); + +unsigned long dss_get_dispc_clk_rate(void); +int dss_dpi_select_source(enum omap_channel channel); +void dss_select_hdmi_venc_clk_source(enum dss_hdmi_venc_clk_source_select); +enum dss_hdmi_venc_clk_source_select dss_get_hdmi_venc_clk_source(void); +const char *dss_get_generic_clk_source_name(enum omap_dss_clk_source clk_src); +void dss_dump_clocks(struct seq_file *s); + +#if defined(CONFIG_OMAP2_DSS_DEBUGFS) +void dss_debug_dump_clocks(struct seq_file *s); +#endif + +void dss_sdi_init(int datapairs); +int dss_sdi_enable(void); +void dss_sdi_disable(void); + +void dss_select_dsi_clk_source(int dsi_module, + enum omap_dss_clk_source clk_src); +void dss_select_lcd_clk_source(enum omap_channel channel, + enum omap_dss_clk_source clk_src); +enum omap_dss_clk_source dss_get_dispc_clk_source(void); +enum omap_dss_clk_source dss_get_dsi_clk_source(int dsi_module); +enum omap_dss_clk_source dss_get_lcd_clk_source(enum omap_channel channel); + +void dss_set_venc_output(enum omap_dss_venc_type type); +void dss_set_dac_pwrdn_bgz(bool enable); + +int dss_set_fck_rate(unsigned long rate); + +typedef bool (*dss_div_calc_func)(unsigned long fck, void *data); +bool dss_div_calc(unsigned long pck, unsigned long fck_min, + dss_div_calc_func func, void *data); + +/* SDI */ +int sdi_init_platform_driver(void) __init; +void sdi_uninit_platform_driver(void) __exit; + +int sdi_init_port(struct platform_device *pdev, struct device_node *port) __init; +void sdi_uninit_port(void) __exit; + +/* DSI */ + +typedef bool (*dsi_pll_calc_func)(int regn, int regm, unsigned long fint, + unsigned long pll, void *data); +typedef bool (*dsi_hsdiv_calc_func)(int regm_dispc, unsigned long dispc, + void *data); + +#ifdef CONFIG_OMAP2_DSS_DSI + +struct dentry; +struct file_operations; + +int dsi_init_platform_driver(void) __init; +void dsi_uninit_platform_driver(void) __exit; + +int dsi_runtime_get(struct platform_device *dsidev); +void dsi_runtime_put(struct platform_device *dsidev); + +void dsi_dump_clocks(struct seq_file *s); + +void dsi_irq_handler(void); +u8 dsi_get_pixel_size(enum omap_dss_dsi_pixel_format fmt); + +unsigned long dsi_get_pll_clkin(struct platform_device *dsidev); + +bool dsi_hsdiv_calc(struct platform_device *dsidev, unsigned long pll, + unsigned long out_min, dsi_hsdiv_calc_func func, void *data); +bool dsi_pll_calc(struct platform_device *dsidev, unsigned long clkin, + unsigned long pll_min, unsigned long pll_max, + dsi_pll_calc_func func, void *data); + +unsigned long dsi_get_pll_hsdiv_dispc_rate(struct platform_device *dsidev); +int dsi_pll_set_clock_div(struct platform_device *dsidev, + struct dsi_clock_info *cinfo); +int dsi_pll_init(struct platform_device *dsidev, bool enable_hsclk, + bool enable_hsdiv); +void dsi_pll_uninit(struct platform_device *dsidev, bool disconnect_lanes); +void dsi_wait_pll_hsdiv_dispc_active(struct platform_device *dsidev); +void dsi_wait_pll_hsdiv_dsi_active(struct platform_device *dsidev); +struct platform_device *dsi_get_dsidev_from_id(int module); +#else +static inline int dsi_runtime_get(struct platform_device *dsidev) +{ + return 0; +} +static inline void dsi_runtime_put(struct platform_device *dsidev) +{ +} +static inline u8 dsi_get_pixel_size(enum omap_dss_dsi_pixel_format fmt) +{ + WARN("%s: DSI not compiled in, returning pixel_size as 0\n", __func__); + return 0; +} +static inline unsigned long dsi_get_pll_hsdiv_dispc_rate(struct platform_device *dsidev) +{ + WARN("%s: DSI not compiled in, returning rate as 0\n", __func__); + return 0; +} +static inline int dsi_pll_set_clock_div(struct platform_device *dsidev, + struct dsi_clock_info *cinfo) +{ + WARN("%s: DSI not compiled in\n", __func__); + return -ENODEV; +} +static inline int dsi_pll_init(struct platform_device *dsidev, + bool enable_hsclk, bool enable_hsdiv) +{ + WARN("%s: DSI not compiled in\n", __func__); + return -ENODEV; +} +static inline void dsi_pll_uninit(struct platform_device *dsidev, + bool disconnect_lanes) +{ +} +static inline void dsi_wait_pll_hsdiv_dispc_active(struct platform_device *dsidev) +{ +} +static inline void dsi_wait_pll_hsdiv_dsi_active(struct platform_device *dsidev) +{ +} +static inline struct platform_device *dsi_get_dsidev_from_id(int module) +{ + return NULL; +} + +static inline unsigned long dsi_get_pll_clkin(struct platform_device *dsidev) +{ + return 0; +} + +static inline bool dsi_hsdiv_calc(struct platform_device *dsidev, + unsigned long pll, unsigned long out_min, + dsi_hsdiv_calc_func func, void *data) +{ + return false; +} + +static inline bool dsi_pll_calc(struct platform_device *dsidev, + unsigned long clkin, + unsigned long pll_min, unsigned long pll_max, + dsi_pll_calc_func func, void *data) +{ + return false; +} + +#endif + +/* DPI */ +int dpi_init_platform_driver(void) __init; +void dpi_uninit_platform_driver(void) __exit; + +int dpi_init_port(struct platform_device *pdev, struct device_node *port) __init; +void dpi_uninit_port(void) __exit; + +/* DISPC */ +int dispc_init_platform_driver(void) __init; +void dispc_uninit_platform_driver(void) __exit; +void dispc_dump_clocks(struct seq_file *s); + +void dispc_enable_sidle(void); +void dispc_disable_sidle(void); + +void dispc_lcd_enable_signal(bool enable); +void dispc_pck_free_enable(bool enable); +void dispc_enable_fifomerge(bool enable); +void dispc_enable_gamma_table(bool enable); +void dispc_set_loadmode(enum omap_dss_load_mode mode); + +typedef bool (*dispc_div_calc_func)(int lckd, int pckd, unsigned long lck, + unsigned long pck, void *data); +bool dispc_div_calc(unsigned long dispc, + unsigned long pck_min, unsigned long pck_max, + dispc_div_calc_func func, void *data); + +bool dispc_mgr_timings_ok(enum omap_channel channel, + const struct omap_video_timings *timings); +unsigned long dispc_fclk_rate(void); +int dispc_calc_clock_rates(unsigned long dispc_fclk_rate, + struct dispc_clock_info *cinfo); + + +void dispc_ovl_set_fifo_threshold(enum omap_plane plane, u32 low, u32 high); +void dispc_ovl_compute_fifo_thresholds(enum omap_plane plane, + u32 *fifo_low, u32 *fifo_high, bool use_fifomerge, + bool manual_update); + +unsigned long dispc_mgr_lclk_rate(enum omap_channel channel); +unsigned long dispc_mgr_pclk_rate(enum omap_channel channel); +unsigned long dispc_core_clk_rate(void); +void dispc_mgr_set_clock_div(enum omap_channel channel, + const struct dispc_clock_info *cinfo); +int dispc_mgr_get_clock_div(enum omap_channel channel, + struct dispc_clock_info *cinfo); +void dispc_set_tv_pclk(unsigned long pclk); + +u32 dispc_wb_get_framedone_irq(void); +bool dispc_wb_go_busy(void); +void dispc_wb_go(void); +void dispc_wb_enable(bool enable); +bool dispc_wb_is_enabled(void); +void dispc_wb_set_channel_in(enum dss_writeback_channel channel); +int dispc_wb_setup(const struct omap_dss_writeback_info *wi, + bool mem_to_mem, const struct omap_video_timings *timings); + +/* VENC */ +int venc_init_platform_driver(void) __init; +void venc_uninit_platform_driver(void) __exit; + +/* HDMI */ +int hdmi4_init_platform_driver(void) __init; +void hdmi4_uninit_platform_driver(void) __exit; + +/* RFBI */ +int rfbi_init_platform_driver(void) __init; +void rfbi_uninit_platform_driver(void) __exit; + + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static inline void dss_collect_irq_stats(u32 irqstatus, unsigned *irq_arr) +{ + int b; + for (b = 0; b < 32; ++b) { + if (irqstatus & (1 << b)) + irq_arr[b]++; + } +} +#endif + +#endif diff --git a/drivers/video/fbdev/omap2/dss/dss_features.c b/drivers/video/fbdev/omap2/dss/dss_features.c new file mode 100644 index 000000000000..7f8969191dc6 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss_features.c @@ -0,0 +1,935 @@ +/* + * linux/drivers/video/omap2/dss/dss_features.c + * + * Copyright (C) 2010 Texas Instruments + * Author: Archit Taneja <archit@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +/* Defines a generic omap register field */ +struct dss_reg_field { + u8 start, end; +}; + +struct dss_param_range { + int min, max; +}; + +struct omap_dss_features { + const struct dss_reg_field *reg_fields; + const int num_reg_fields; + + const enum dss_feat_id *features; + const int num_features; + + const int num_mgrs; + const int num_ovls; + const int num_wbs; + const enum omap_display_type *supported_displays; + const enum omap_dss_output_id *supported_outputs; + const enum omap_color_mode *supported_color_modes; + const enum omap_overlay_caps *overlay_caps; + const char * const *clksrc_names; + const struct dss_param_range *dss_params; + + const enum omap_dss_rotation_type supported_rotation_types; + + const u32 buffer_size_unit; + const u32 burst_size_unit; +}; + +/* This struct is assigned to one of the below during initialization */ +static const struct omap_dss_features *omap_current_dss_features; + +static const struct dss_reg_field omap2_dss_reg_fields[] = { + [FEAT_REG_FIRHINC] = { 11, 0 }, + [FEAT_REG_FIRVINC] = { 27, 16 }, + [FEAT_REG_FIFOLOWTHRESHOLD] = { 8, 0 }, + [FEAT_REG_FIFOHIGHTHRESHOLD] = { 24, 16 }, + [FEAT_REG_FIFOSIZE] = { 8, 0 }, + [FEAT_REG_HORIZONTALACCU] = { 9, 0 }, + [FEAT_REG_VERTICALACCU] = { 25, 16 }, + [FEAT_REG_DISPC_CLK_SWITCH] = { 0, 0 }, + [FEAT_REG_DSIPLL_REGN] = { 0, 0 }, + [FEAT_REG_DSIPLL_REGM] = { 0, 0 }, + [FEAT_REG_DSIPLL_REGM_DISPC] = { 0, 0 }, + [FEAT_REG_DSIPLL_REGM_DSI] = { 0, 0 }, +}; + +static const struct dss_reg_field omap3_dss_reg_fields[] = { + [FEAT_REG_FIRHINC] = { 12, 0 }, + [FEAT_REG_FIRVINC] = { 28, 16 }, + [FEAT_REG_FIFOLOWTHRESHOLD] = { 11, 0 }, + [FEAT_REG_FIFOHIGHTHRESHOLD] = { 27, 16 }, + [FEAT_REG_FIFOSIZE] = { 10, 0 }, + [FEAT_REG_HORIZONTALACCU] = { 9, 0 }, + [FEAT_REG_VERTICALACCU] = { 25, 16 }, + [FEAT_REG_DISPC_CLK_SWITCH] = { 0, 0 }, + [FEAT_REG_DSIPLL_REGN] = { 7, 1 }, + [FEAT_REG_DSIPLL_REGM] = { 18, 8 }, + [FEAT_REG_DSIPLL_REGM_DISPC] = { 22, 19 }, + [FEAT_REG_DSIPLL_REGM_DSI] = { 26, 23 }, +}; + +static const struct dss_reg_field omap4_dss_reg_fields[] = { + [FEAT_REG_FIRHINC] = { 12, 0 }, + [FEAT_REG_FIRVINC] = { 28, 16 }, + [FEAT_REG_FIFOLOWTHRESHOLD] = { 15, 0 }, + [FEAT_REG_FIFOHIGHTHRESHOLD] = { 31, 16 }, + [FEAT_REG_FIFOSIZE] = { 15, 0 }, + [FEAT_REG_HORIZONTALACCU] = { 10, 0 }, + [FEAT_REG_VERTICALACCU] = { 26, 16 }, + [FEAT_REG_DISPC_CLK_SWITCH] = { 9, 8 }, + [FEAT_REG_DSIPLL_REGN] = { 8, 1 }, + [FEAT_REG_DSIPLL_REGM] = { 20, 9 }, + [FEAT_REG_DSIPLL_REGM_DISPC] = { 25, 21 }, + [FEAT_REG_DSIPLL_REGM_DSI] = { 30, 26 }, +}; + +static const struct dss_reg_field omap5_dss_reg_fields[] = { + [FEAT_REG_FIRHINC] = { 12, 0 }, + [FEAT_REG_FIRVINC] = { 28, 16 }, + [FEAT_REG_FIFOLOWTHRESHOLD] = { 15, 0 }, + [FEAT_REG_FIFOHIGHTHRESHOLD] = { 31, 16 }, + [FEAT_REG_FIFOSIZE] = { 15, 0 }, + [FEAT_REG_HORIZONTALACCU] = { 10, 0 }, + [FEAT_REG_VERTICALACCU] = { 26, 16 }, + [FEAT_REG_DISPC_CLK_SWITCH] = { 9, 7 }, + [FEAT_REG_DSIPLL_REGN] = { 8, 1 }, + [FEAT_REG_DSIPLL_REGM] = { 20, 9 }, + [FEAT_REG_DSIPLL_REGM_DISPC] = { 25, 21 }, + [FEAT_REG_DSIPLL_REGM_DSI] = { 30, 26 }, +}; + +static const enum omap_display_type omap2_dss_supported_displays[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DISPLAY_TYPE_VENC, +}; + +static const enum omap_display_type omap3430_dss_supported_displays[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | + OMAP_DISPLAY_TYPE_SDI | OMAP_DISPLAY_TYPE_DSI, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DISPLAY_TYPE_VENC, +}; + +static const enum omap_display_type omap3630_dss_supported_displays[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | + OMAP_DISPLAY_TYPE_DSI, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DISPLAY_TYPE_VENC, +}; + +static const enum omap_display_type omap4_dss_supported_displays[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DISPLAY_TYPE_DBI | OMAP_DISPLAY_TYPE_DSI, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DISPLAY_TYPE_VENC | OMAP_DISPLAY_TYPE_HDMI, + + /* OMAP_DSS_CHANNEL_LCD2 */ + OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | + OMAP_DISPLAY_TYPE_DSI, +}; + +static const enum omap_display_type omap5_dss_supported_displays[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | + OMAP_DISPLAY_TYPE_DSI, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DISPLAY_TYPE_HDMI | OMAP_DISPLAY_TYPE_DPI, + + /* OMAP_DSS_CHANNEL_LCD2 */ + OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | + OMAP_DISPLAY_TYPE_DSI, +}; + +static const enum omap_dss_output_id omap2_dss_supported_outputs[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DSS_OUTPUT_VENC, +}; + +static const enum omap_dss_output_id omap3430_dss_supported_outputs[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | + OMAP_DSS_OUTPUT_SDI | OMAP_DSS_OUTPUT_DSI1, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DSS_OUTPUT_VENC, +}; + +static const enum omap_dss_output_id omap3630_dss_supported_outputs[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | + OMAP_DSS_OUTPUT_DSI1, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DSS_OUTPUT_VENC, +}; + +static const enum omap_dss_output_id omap4_dss_supported_outputs[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DSS_OUTPUT_DBI | OMAP_DSS_OUTPUT_DSI1, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DSS_OUTPUT_VENC | OMAP_DSS_OUTPUT_HDMI, + + /* OMAP_DSS_CHANNEL_LCD2 */ + OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | + OMAP_DSS_OUTPUT_DSI2, +}; + +static const enum omap_dss_output_id omap5_dss_supported_outputs[] = { + /* OMAP_DSS_CHANNEL_LCD */ + OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | + OMAP_DSS_OUTPUT_DSI1 | OMAP_DSS_OUTPUT_DSI2, + + /* OMAP_DSS_CHANNEL_DIGIT */ + OMAP_DSS_OUTPUT_HDMI | OMAP_DSS_OUTPUT_DPI, + + /* OMAP_DSS_CHANNEL_LCD2 */ + OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | + OMAP_DSS_OUTPUT_DSI1, + + /* OMAP_DSS_CHANNEL_LCD3 */ + OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | + OMAP_DSS_OUTPUT_DSI2, +}; + +static const enum omap_color_mode omap2_dss_supported_color_modes[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_COLOR_CLUT1 | OMAP_DSS_COLOR_CLUT2 | + OMAP_DSS_COLOR_CLUT4 | OMAP_DSS_COLOR_CLUT8 | + OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_RGB16 | + OMAP_DSS_COLOR_RGB24U | OMAP_DSS_COLOR_RGB24P, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_YUV2 | + OMAP_DSS_COLOR_UYVY, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_YUV2 | + OMAP_DSS_COLOR_UYVY, +}; + +static const enum omap_color_mode omap3_dss_supported_color_modes[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_COLOR_CLUT1 | OMAP_DSS_COLOR_CLUT2 | + OMAP_DSS_COLOR_CLUT4 | OMAP_DSS_COLOR_CLUT8 | + OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_ARGB16 | + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_ARGB32 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_RGBX32, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_COLOR_RGB24U | OMAP_DSS_COLOR_RGB24P | + OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_RGB16 | + OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_UYVY, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_ARGB16 | + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_YUV2 | + OMAP_DSS_COLOR_UYVY | OMAP_DSS_COLOR_ARGB32 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_RGBX32, +}; + +static const enum omap_color_mode omap4_dss_supported_color_modes[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_COLOR_CLUT1 | OMAP_DSS_COLOR_CLUT2 | + OMAP_DSS_COLOR_CLUT4 | OMAP_DSS_COLOR_CLUT8 | + OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_ARGB16 | + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_ARGB32 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_RGBX32 | + OMAP_DSS_COLOR_ARGB16_1555 | OMAP_DSS_COLOR_RGBX16 | + OMAP_DSS_COLOR_RGBA16 | OMAP_DSS_COLOR_XRGB16_1555, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB12U | + OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_ARGB16_1555 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_NV12 | + OMAP_DSS_COLOR_RGBA16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_UYVY | + OMAP_DSS_COLOR_ARGB16 | OMAP_DSS_COLOR_XRGB16_1555 | + OMAP_DSS_COLOR_ARGB32 | OMAP_DSS_COLOR_RGBX16 | + OMAP_DSS_COLOR_RGBX32, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB12U | + OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_ARGB16_1555 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_NV12 | + OMAP_DSS_COLOR_RGBA16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_UYVY | + OMAP_DSS_COLOR_ARGB16 | OMAP_DSS_COLOR_XRGB16_1555 | + OMAP_DSS_COLOR_ARGB32 | OMAP_DSS_COLOR_RGBX16 | + OMAP_DSS_COLOR_RGBX32, + + /* OMAP_DSS_VIDEO3 */ + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB12U | + OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_ARGB16_1555 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_NV12 | + OMAP_DSS_COLOR_RGBA16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_UYVY | + OMAP_DSS_COLOR_ARGB16 | OMAP_DSS_COLOR_XRGB16_1555 | + OMAP_DSS_COLOR_ARGB32 | OMAP_DSS_COLOR_RGBX16 | + OMAP_DSS_COLOR_RGBX32, + + /* OMAP_DSS_WB */ + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB12U | + OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_ARGB16_1555 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_NV12 | + OMAP_DSS_COLOR_RGBA16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_UYVY | + OMAP_DSS_COLOR_ARGB16 | OMAP_DSS_COLOR_XRGB16_1555 | + OMAP_DSS_COLOR_ARGB32 | OMAP_DSS_COLOR_RGBX16 | + OMAP_DSS_COLOR_RGBX32, +}; + +static const enum omap_overlay_caps omap2_dss_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap3430_dss_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap3630_dss_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap4_dss_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | + OMAP_DSS_OVL_CAP_ZORDER | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO3 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const char * const omap2_dss_clk_source_names[] = { + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC] = "N/A", + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI] = "N/A", + [OMAP_DSS_CLK_SRC_FCK] = "DSS_FCLK1", +}; + +static const char * const omap3_dss_clk_source_names[] = { + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC] = "DSI1_PLL_FCLK", + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI] = "DSI2_PLL_FCLK", + [OMAP_DSS_CLK_SRC_FCK] = "DSS1_ALWON_FCLK", +}; + +static const char * const omap4_dss_clk_source_names[] = { + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC] = "PLL1_CLK1", + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI] = "PLL1_CLK2", + [OMAP_DSS_CLK_SRC_FCK] = "DSS_FCLK", + [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC] = "PLL2_CLK1", + [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI] = "PLL2_CLK2", +}; + +static const char * const omap5_dss_clk_source_names[] = { + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC] = "DPLL_DSI1_A_CLK1", + [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI] = "DPLL_DSI1_A_CLK2", + [OMAP_DSS_CLK_SRC_FCK] = "DSS_CLK", + [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC] = "DPLL_DSI1_C_CLK1", + [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI] = "DPLL_DSI1_C_CLK2", +}; + +static const struct dss_param_range omap2_dss_param_range[] = { + [FEAT_PARAM_DSS_FCK] = { 0, 133000000 }, + [FEAT_PARAM_DSS_PCD] = { 2, 255 }, + [FEAT_PARAM_DSIPLL_REGN] = { 0, 0 }, + [FEAT_PARAM_DSIPLL_REGM] = { 0, 0 }, + [FEAT_PARAM_DSIPLL_REGM_DISPC] = { 0, 0 }, + [FEAT_PARAM_DSIPLL_REGM_DSI] = { 0, 0 }, + [FEAT_PARAM_DSIPLL_FINT] = { 0, 0 }, + [FEAT_PARAM_DSIPLL_LPDIV] = { 0, 0 }, + [FEAT_PARAM_DOWNSCALE] = { 1, 2 }, + /* + * Assuming the line width buffer to be 768 pixels as OMAP2 DISPC + * scaler cannot scale a image with width more than 768. + */ + [FEAT_PARAM_LINEWIDTH] = { 1, 768 }, +}; + +static const struct dss_param_range omap3_dss_param_range[] = { + [FEAT_PARAM_DSS_FCK] = { 0, 173000000 }, + [FEAT_PARAM_DSS_PCD] = { 1, 255 }, + [FEAT_PARAM_DSIPLL_REGN] = { 0, (1 << 7) - 1 }, + [FEAT_PARAM_DSIPLL_REGM] = { 0, (1 << 11) - 1 }, + [FEAT_PARAM_DSIPLL_REGM_DISPC] = { 0, (1 << 4) - 1 }, + [FEAT_PARAM_DSIPLL_REGM_DSI] = { 0, (1 << 4) - 1 }, + [FEAT_PARAM_DSIPLL_FINT] = { 750000, 2100000 }, + [FEAT_PARAM_DSIPLL_LPDIV] = { 1, (1 << 13) - 1}, + [FEAT_PARAM_DSI_FCK] = { 0, 173000000 }, + [FEAT_PARAM_DOWNSCALE] = { 1, 4 }, + [FEAT_PARAM_LINEWIDTH] = { 1, 1024 }, +}; + +static const struct dss_param_range omap4_dss_param_range[] = { + [FEAT_PARAM_DSS_FCK] = { 0, 186000000 }, + [FEAT_PARAM_DSS_PCD] = { 1, 255 }, + [FEAT_PARAM_DSIPLL_REGN] = { 0, (1 << 8) - 1 }, + [FEAT_PARAM_DSIPLL_REGM] = { 0, (1 << 12) - 1 }, + [FEAT_PARAM_DSIPLL_REGM_DISPC] = { 0, (1 << 5) - 1 }, + [FEAT_PARAM_DSIPLL_REGM_DSI] = { 0, (1 << 5) - 1 }, + [FEAT_PARAM_DSIPLL_FINT] = { 500000, 2500000 }, + [FEAT_PARAM_DSIPLL_LPDIV] = { 0, (1 << 13) - 1 }, + [FEAT_PARAM_DSI_FCK] = { 0, 170000000 }, + [FEAT_PARAM_DOWNSCALE] = { 1, 4 }, + [FEAT_PARAM_LINEWIDTH] = { 1, 2048 }, +}; + +static const struct dss_param_range omap5_dss_param_range[] = { + [FEAT_PARAM_DSS_FCK] = { 0, 209250000 }, + [FEAT_PARAM_DSS_PCD] = { 1, 255 }, + [FEAT_PARAM_DSIPLL_REGN] = { 0, (1 << 8) - 1 }, + [FEAT_PARAM_DSIPLL_REGM] = { 0, (1 << 12) - 1 }, + [FEAT_PARAM_DSIPLL_REGM_DISPC] = { 0, (1 << 5) - 1 }, + [FEAT_PARAM_DSIPLL_REGM_DSI] = { 0, (1 << 5) - 1 }, + [FEAT_PARAM_DSIPLL_FINT] = { 150000, 52000000 }, + [FEAT_PARAM_DSIPLL_LPDIV] = { 0, (1 << 13) - 1 }, + [FEAT_PARAM_DSI_FCK] = { 0, 209250000 }, + [FEAT_PARAM_DOWNSCALE] = { 1, 4 }, + [FEAT_PARAM_LINEWIDTH] = { 1, 2048 }, +}; + +static const enum dss_feat_id omap2_dss_feat_list[] = { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, +}; + +static const enum dss_feat_id omap3430_dss_feat_list[] = { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_LINEBUFFERSPLIT, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, + FEAT_DSI_PLL_FREQSEL, + FEAT_DSI_REVERSE_TXCLKESC, + FEAT_VENC_REQUIRES_TV_DAC_CLK, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FIXED_ZORDER, + FEAT_FIFO_MERGE, + FEAT_OMAP3_DSI_FIFO_BUG, + FEAT_DPI_USES_VDDS_DSI, +}; + +static const enum dss_feat_id am35xx_dss_feat_list[] = { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_LINEBUFFERSPLIT, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, + FEAT_DSI_PLL_FREQSEL, + FEAT_DSI_REVERSE_TXCLKESC, + FEAT_VENC_REQUIRES_TV_DAC_CLK, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FIXED_ZORDER, + FEAT_FIFO_MERGE, + FEAT_OMAP3_DSI_FIFO_BUG, +}; + +static const enum dss_feat_id omap3630_dss_feat_list[] = { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_LINEBUFFERSPLIT, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, + FEAT_DSI_PLL_PWR_BUG, + FEAT_DSI_PLL_FREQSEL, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FIXED_ZORDER, + FEAT_FIFO_MERGE, + FEAT_OMAP3_DSI_FIFO_BUG, + FEAT_DPI_USES_VDDS_DSI, +}; + +static const enum dss_feat_id omap4430_es1_0_dss_feat_list[] = { + FEAT_MGR_LCD2, + FEAT_CORE_CLK_DIV, + FEAT_LCD_CLK_SRC, + FEAT_DSI_DCS_CMD_CONFIG_VC, + FEAT_DSI_VC_OCP_WIDTH, + FEAT_DSI_GNQ, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + FEAT_BURST_2D, +}; + +static const enum dss_feat_id omap4430_es2_0_1_2_dss_feat_list[] = { + FEAT_MGR_LCD2, + FEAT_CORE_CLK_DIV, + FEAT_LCD_CLK_SRC, + FEAT_DSI_DCS_CMD_CONFIG_VC, + FEAT_DSI_VC_OCP_WIDTH, + FEAT_DSI_GNQ, + FEAT_HDMI_CTS_SWMODE, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + FEAT_BURST_2D, +}; + +static const enum dss_feat_id omap4_dss_feat_list[] = { + FEAT_MGR_LCD2, + FEAT_CORE_CLK_DIV, + FEAT_LCD_CLK_SRC, + FEAT_DSI_DCS_CMD_CONFIG_VC, + FEAT_DSI_VC_OCP_WIDTH, + FEAT_DSI_GNQ, + FEAT_HDMI_CTS_SWMODE, + FEAT_HDMI_AUDIO_USE_MCLK, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + FEAT_BURST_2D, +}; + +static const enum dss_feat_id omap5_dss_feat_list[] = { + FEAT_MGR_LCD2, + FEAT_CORE_CLK_DIV, + FEAT_LCD_CLK_SRC, + FEAT_DSI_DCS_CMD_CONFIG_VC, + FEAT_DSI_VC_OCP_WIDTH, + FEAT_DSI_GNQ, + FEAT_HDMI_CTS_SWMODE, + FEAT_HDMI_AUDIO_USE_MCLK, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + FEAT_BURST_2D, + FEAT_DSI_PLL_SELFREQDCO, + FEAT_DSI_PLL_REFSEL, + FEAT_DSI_PHY_DCC, + FEAT_MFLAG, +}; + +/* OMAP2 DSS Features */ +static const struct omap_dss_features omap2_dss_features = { + .reg_fields = omap2_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap2_dss_reg_fields), + + .features = omap2_dss_feat_list, + .num_features = ARRAY_SIZE(omap2_dss_feat_list), + + .num_mgrs = 2, + .num_ovls = 3, + .supported_displays = omap2_dss_supported_displays, + .supported_outputs = omap2_dss_supported_outputs, + .supported_color_modes = omap2_dss_supported_color_modes, + .overlay_caps = omap2_dss_overlay_caps, + .clksrc_names = omap2_dss_clk_source_names, + .dss_params = omap2_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, + .buffer_size_unit = 1, + .burst_size_unit = 8, +}; + +/* OMAP3 DSS Features */ +static const struct omap_dss_features omap3430_dss_features = { + .reg_fields = omap3_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap3_dss_reg_fields), + + .features = omap3430_dss_feat_list, + .num_features = ARRAY_SIZE(omap3430_dss_feat_list), + + .num_mgrs = 2, + .num_ovls = 3, + .supported_displays = omap3430_dss_supported_displays, + .supported_outputs = omap3430_dss_supported_outputs, + .supported_color_modes = omap3_dss_supported_color_modes, + .overlay_caps = omap3430_dss_overlay_caps, + .clksrc_names = omap3_dss_clk_source_names, + .dss_params = omap3_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, + .buffer_size_unit = 1, + .burst_size_unit = 8, +}; + +/* + * AM35xx DSS Features. This is basically OMAP3 DSS Features without the + * vdds_dsi regulator. + */ +static const struct omap_dss_features am35xx_dss_features = { + .reg_fields = omap3_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap3_dss_reg_fields), + + .features = am35xx_dss_feat_list, + .num_features = ARRAY_SIZE(am35xx_dss_feat_list), + + .num_mgrs = 2, + .num_ovls = 3, + .supported_displays = omap3430_dss_supported_displays, + .supported_outputs = omap3430_dss_supported_outputs, + .supported_color_modes = omap3_dss_supported_color_modes, + .overlay_caps = omap3430_dss_overlay_caps, + .clksrc_names = omap3_dss_clk_source_names, + .dss_params = omap3_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, + .buffer_size_unit = 1, + .burst_size_unit = 8, +}; + +static const struct omap_dss_features omap3630_dss_features = { + .reg_fields = omap3_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap3_dss_reg_fields), + + .features = omap3630_dss_feat_list, + .num_features = ARRAY_SIZE(omap3630_dss_feat_list), + + .num_mgrs = 2, + .num_ovls = 3, + .supported_displays = omap3630_dss_supported_displays, + .supported_outputs = omap3630_dss_supported_outputs, + .supported_color_modes = omap3_dss_supported_color_modes, + .overlay_caps = omap3630_dss_overlay_caps, + .clksrc_names = omap3_dss_clk_source_names, + .dss_params = omap3_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, + .buffer_size_unit = 1, + .burst_size_unit = 8, +}; + +/* OMAP4 DSS Features */ +/* For OMAP4430 ES 1.0 revision */ +static const struct omap_dss_features omap4430_es1_0_dss_features = { + .reg_fields = omap4_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap4_dss_reg_fields), + + .features = omap4430_es1_0_dss_feat_list, + .num_features = ARRAY_SIZE(omap4430_es1_0_dss_feat_list), + + .num_mgrs = 3, + .num_ovls = 4, + .num_wbs = 1, + .supported_displays = omap4_dss_supported_displays, + .supported_outputs = omap4_dss_supported_outputs, + .supported_color_modes = omap4_dss_supported_color_modes, + .overlay_caps = omap4_dss_overlay_caps, + .clksrc_names = omap4_dss_clk_source_names, + .dss_params = omap4_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, + .buffer_size_unit = 16, + .burst_size_unit = 16, +}; + +/* For OMAP4430 ES 2.0, 2.1 and 2.2 revisions */ +static const struct omap_dss_features omap4430_es2_0_1_2_dss_features = { + .reg_fields = omap4_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap4_dss_reg_fields), + + .features = omap4430_es2_0_1_2_dss_feat_list, + .num_features = ARRAY_SIZE(omap4430_es2_0_1_2_dss_feat_list), + + .num_mgrs = 3, + .num_ovls = 4, + .num_wbs = 1, + .supported_displays = omap4_dss_supported_displays, + .supported_outputs = omap4_dss_supported_outputs, + .supported_color_modes = omap4_dss_supported_color_modes, + .overlay_caps = omap4_dss_overlay_caps, + .clksrc_names = omap4_dss_clk_source_names, + .dss_params = omap4_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, + .buffer_size_unit = 16, + .burst_size_unit = 16, +}; + +/* For all the other OMAP4 versions */ +static const struct omap_dss_features omap4_dss_features = { + .reg_fields = omap4_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap4_dss_reg_fields), + + .features = omap4_dss_feat_list, + .num_features = ARRAY_SIZE(omap4_dss_feat_list), + + .num_mgrs = 3, + .num_ovls = 4, + .num_wbs = 1, + .supported_displays = omap4_dss_supported_displays, + .supported_outputs = omap4_dss_supported_outputs, + .supported_color_modes = omap4_dss_supported_color_modes, + .overlay_caps = omap4_dss_overlay_caps, + .clksrc_names = omap4_dss_clk_source_names, + .dss_params = omap4_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, + .buffer_size_unit = 16, + .burst_size_unit = 16, +}; + +/* OMAP5 DSS Features */ +static const struct omap_dss_features omap5_dss_features = { + .reg_fields = omap5_dss_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap5_dss_reg_fields), + + .features = omap5_dss_feat_list, + .num_features = ARRAY_SIZE(omap5_dss_feat_list), + + .num_mgrs = 3, + .num_ovls = 4, + .supported_displays = omap5_dss_supported_displays, + .supported_outputs = omap5_dss_supported_outputs, + .supported_color_modes = omap4_dss_supported_color_modes, + .overlay_caps = omap4_dss_overlay_caps, + .clksrc_names = omap5_dss_clk_source_names, + .dss_params = omap5_dss_param_range, + .supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, + .buffer_size_unit = 16, + .burst_size_unit = 16, +}; + +/* Functions returning values related to a DSS feature */ +int dss_feat_get_num_mgrs(void) +{ + return omap_current_dss_features->num_mgrs; +} +EXPORT_SYMBOL(dss_feat_get_num_mgrs); + +int dss_feat_get_num_ovls(void) +{ + return omap_current_dss_features->num_ovls; +} +EXPORT_SYMBOL(dss_feat_get_num_ovls); + +int dss_feat_get_num_wbs(void) +{ + return omap_current_dss_features->num_wbs; +} + +unsigned long dss_feat_get_param_min(enum dss_range_param param) +{ + return omap_current_dss_features->dss_params[param].min; +} + +unsigned long dss_feat_get_param_max(enum dss_range_param param) +{ + return omap_current_dss_features->dss_params[param].max; +} + +enum omap_display_type dss_feat_get_supported_displays(enum omap_channel channel) +{ + return omap_current_dss_features->supported_displays[channel]; +} +EXPORT_SYMBOL(dss_feat_get_supported_displays); + +enum omap_dss_output_id dss_feat_get_supported_outputs(enum omap_channel channel) +{ + return omap_current_dss_features->supported_outputs[channel]; +} +EXPORT_SYMBOL(dss_feat_get_supported_outputs); + +enum omap_color_mode dss_feat_get_supported_color_modes(enum omap_plane plane) +{ + return omap_current_dss_features->supported_color_modes[plane]; +} +EXPORT_SYMBOL(dss_feat_get_supported_color_modes); + +enum omap_overlay_caps dss_feat_get_overlay_caps(enum omap_plane plane) +{ + return omap_current_dss_features->overlay_caps[plane]; +} + +bool dss_feat_color_mode_supported(enum omap_plane plane, + enum omap_color_mode color_mode) +{ + return omap_current_dss_features->supported_color_modes[plane] & + color_mode; +} + +const char *dss_feat_get_clk_source_name(enum omap_dss_clk_source id) +{ + return omap_current_dss_features->clksrc_names[id]; +} + +u32 dss_feat_get_buffer_size_unit(void) +{ + return omap_current_dss_features->buffer_size_unit; +} + +u32 dss_feat_get_burst_size_unit(void) +{ + return omap_current_dss_features->burst_size_unit; +} + +/* DSS has_feature check */ +bool dss_has_feature(enum dss_feat_id id) +{ + int i; + const enum dss_feat_id *features = omap_current_dss_features->features; + const int num_features = omap_current_dss_features->num_features; + + for (i = 0; i < num_features; i++) { + if (features[i] == id) + return true; + } + + return false; +} + +void dss_feat_get_reg_field(enum dss_feat_reg_field id, u8 *start, u8 *end) +{ + if (id >= omap_current_dss_features->num_reg_fields) + BUG(); + + *start = omap_current_dss_features->reg_fields[id].start; + *end = omap_current_dss_features->reg_fields[id].end; +} + +bool dss_feat_rotation_type_supported(enum omap_dss_rotation_type rot_type) +{ + return omap_current_dss_features->supported_rotation_types & rot_type; +} + +void dss_features_init(enum omapdss_version version) +{ + switch (version) { + case OMAPDSS_VER_OMAP24xx: + omap_current_dss_features = &omap2_dss_features; + break; + + case OMAPDSS_VER_OMAP34xx_ES1: + case OMAPDSS_VER_OMAP34xx_ES3: + omap_current_dss_features = &omap3430_dss_features; + break; + + case OMAPDSS_VER_OMAP3630: + omap_current_dss_features = &omap3630_dss_features; + break; + + case OMAPDSS_VER_OMAP4430_ES1: + omap_current_dss_features = &omap4430_es1_0_dss_features; + break; + + case OMAPDSS_VER_OMAP4430_ES2: + omap_current_dss_features = &omap4430_es2_0_1_2_dss_features; + break; + + case OMAPDSS_VER_OMAP4: + omap_current_dss_features = &omap4_dss_features; + break; + + case OMAPDSS_VER_OMAP5: + omap_current_dss_features = &omap5_dss_features; + break; + + case OMAPDSS_VER_AM35xx: + omap_current_dss_features = &am35xx_dss_features; + break; + + default: + DSSWARN("Unsupported OMAP version"); + break; + } +} diff --git a/drivers/video/fbdev/omap2/dss/dss_features.h b/drivers/video/fbdev/omap2/dss/dss_features.h new file mode 100644 index 000000000000..e3ef3b714896 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss_features.h @@ -0,0 +1,117 @@ +/* + * linux/drivers/video/omap2/dss/dss_features.h + * + * Copyright (C) 2010 Texas Instruments + * Author: Archit Taneja <archit@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP2_DSS_FEATURES_H +#define __OMAP2_DSS_FEATURES_H + +#define MAX_DSS_MANAGERS 4 +#define MAX_DSS_OVERLAYS 4 +#define MAX_DSS_LCD_MANAGERS 3 +#define MAX_NUM_DSI 2 + +/* DSS has feature id */ +enum dss_feat_id { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_MGR_LCD2, + FEAT_MGR_LCD3, + FEAT_LINEBUFFERSPLIT, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, + /* Independent core clk divider */ + FEAT_CORE_CLK_DIV, + FEAT_LCD_CLK_SRC, + /* DSI-PLL power command 0x3 is not working */ + FEAT_DSI_PLL_PWR_BUG, + FEAT_DSI_PLL_FREQSEL, + FEAT_DSI_DCS_CMD_CONFIG_VC, + FEAT_DSI_VC_OCP_WIDTH, + FEAT_DSI_REVERSE_TXCLKESC, + FEAT_DSI_GNQ, + FEAT_DPI_USES_VDDS_DSI, + FEAT_HDMI_CTS_SWMODE, + FEAT_HDMI_AUDIO_USE_MCLK, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_VENC_REQUIRES_TV_DAC_CLK, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FIXED_ZORDER, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + /* An unknown HW bug causing the normal FIFO thresholds not to work */ + FEAT_OMAP3_DSI_FIFO_BUG, + FEAT_BURST_2D, + FEAT_DSI_PLL_SELFREQDCO, + FEAT_DSI_PLL_REFSEL, + FEAT_DSI_PHY_DCC, + FEAT_MFLAG, +}; + +/* DSS register field id */ +enum dss_feat_reg_field { + FEAT_REG_FIRHINC, + FEAT_REG_FIRVINC, + FEAT_REG_FIFOHIGHTHRESHOLD, + FEAT_REG_FIFOLOWTHRESHOLD, + FEAT_REG_FIFOSIZE, + FEAT_REG_HORIZONTALACCU, + FEAT_REG_VERTICALACCU, + FEAT_REG_DISPC_CLK_SWITCH, + FEAT_REG_DSIPLL_REGN, + FEAT_REG_DSIPLL_REGM, + FEAT_REG_DSIPLL_REGM_DISPC, + FEAT_REG_DSIPLL_REGM_DSI, +}; + +enum dss_range_param { + FEAT_PARAM_DSS_FCK, + FEAT_PARAM_DSS_PCD, + FEAT_PARAM_DSIPLL_REGN, + FEAT_PARAM_DSIPLL_REGM, + FEAT_PARAM_DSIPLL_REGM_DISPC, + FEAT_PARAM_DSIPLL_REGM_DSI, + FEAT_PARAM_DSIPLL_FINT, + FEAT_PARAM_DSIPLL_LPDIV, + FEAT_PARAM_DSI_FCK, + FEAT_PARAM_DOWNSCALE, + FEAT_PARAM_LINEWIDTH, +}; + +/* DSS Feature Functions */ +int dss_feat_get_num_wbs(void); +unsigned long dss_feat_get_param_min(enum dss_range_param param); +unsigned long dss_feat_get_param_max(enum dss_range_param param); +enum omap_overlay_caps dss_feat_get_overlay_caps(enum omap_plane plane); +bool dss_feat_color_mode_supported(enum omap_plane plane, + enum omap_color_mode color_mode); +const char *dss_feat_get_clk_source_name(enum omap_dss_clk_source id); + +u32 dss_feat_get_buffer_size_unit(void); /* in bytes */ +u32 dss_feat_get_burst_size_unit(void); /* in bytes */ + +bool dss_feat_rotation_type_supported(enum omap_dss_rotation_type rot_type); + +bool dss_has_feature(enum dss_feat_id id); +void dss_feat_get_reg_field(enum dss_feat_reg_field id, u8 *start, u8 *end); +void dss_features_init(enum omapdss_version version); +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi.h b/drivers/video/fbdev/omap2/dss/hdmi.h new file mode 100644 index 000000000000..e25681ff5a70 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi.h @@ -0,0 +1,444 @@ +/* + * HDMI driver definition for TI OMAP4 Processor. + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _HDMI_H +#define _HDMI_H + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <video/omapdss.h> + +#include "dss.h" + +/* HDMI Wrapper */ + +#define HDMI_WP_REVISION 0x0 +#define HDMI_WP_SYSCONFIG 0x10 +#define HDMI_WP_IRQSTATUS_RAW 0x24 +#define HDMI_WP_IRQSTATUS 0x28 +#define HDMI_WP_IRQENABLE_SET 0x2C +#define HDMI_WP_IRQENABLE_CLR 0x30 +#define HDMI_WP_IRQWAKEEN 0x34 +#define HDMI_WP_PWR_CTRL 0x40 +#define HDMI_WP_DEBOUNCE 0x44 +#define HDMI_WP_VIDEO_CFG 0x50 +#define HDMI_WP_VIDEO_SIZE 0x60 +#define HDMI_WP_VIDEO_TIMING_H 0x68 +#define HDMI_WP_VIDEO_TIMING_V 0x6C +#define HDMI_WP_CLK 0x70 +#define HDMI_WP_AUDIO_CFG 0x80 +#define HDMI_WP_AUDIO_CFG2 0x84 +#define HDMI_WP_AUDIO_CTRL 0x88 +#define HDMI_WP_AUDIO_DATA 0x8C + +/* HDMI WP IRQ flags */ +#define HDMI_IRQ_CORE (1 << 0) +#define HDMI_IRQ_OCP_TIMEOUT (1 << 4) +#define HDMI_IRQ_AUDIO_FIFO_UNDERFLOW (1 << 8) +#define HDMI_IRQ_AUDIO_FIFO_OVERFLOW (1 << 9) +#define HDMI_IRQ_AUDIO_FIFO_SAMPLE_REQ (1 << 10) +#define HDMI_IRQ_VIDEO_VSYNC (1 << 16) +#define HDMI_IRQ_VIDEO_FRAME_DONE (1 << 17) +#define HDMI_IRQ_PHY_LINE5V_ASSERT (1 << 24) +#define HDMI_IRQ_LINK_CONNECT (1 << 25) +#define HDMI_IRQ_LINK_DISCONNECT (1 << 26) +#define HDMI_IRQ_PLL_LOCK (1 << 29) +#define HDMI_IRQ_PLL_UNLOCK (1 << 30) +#define HDMI_IRQ_PLL_RECAL (1 << 31) + +/* HDMI PLL */ + +#define PLLCTRL_PLL_CONTROL 0x0 +#define PLLCTRL_PLL_STATUS 0x4 +#define PLLCTRL_PLL_GO 0x8 +#define PLLCTRL_CFG1 0xC +#define PLLCTRL_CFG2 0x10 +#define PLLCTRL_CFG3 0x14 +#define PLLCTRL_SSC_CFG1 0x18 +#define PLLCTRL_SSC_CFG2 0x1C +#define PLLCTRL_CFG4 0x20 + +/* HDMI PHY */ + +#define HDMI_TXPHY_TX_CTRL 0x0 +#define HDMI_TXPHY_DIGITAL_CTRL 0x4 +#define HDMI_TXPHY_POWER_CTRL 0x8 +#define HDMI_TXPHY_PAD_CFG_CTRL 0xC + +enum hdmi_pll_pwr { + HDMI_PLLPWRCMD_ALLOFF = 0, + HDMI_PLLPWRCMD_PLLONLY = 1, + HDMI_PLLPWRCMD_BOTHON_ALLCLKS = 2, + HDMI_PLLPWRCMD_BOTHON_NOPHYCLK = 3 +}; + +enum hdmi_phy_pwr { + HDMI_PHYPWRCMD_OFF = 0, + HDMI_PHYPWRCMD_LDOON = 1, + HDMI_PHYPWRCMD_TXON = 2 +}; + +enum hdmi_core_hdmi_dvi { + HDMI_DVI = 0, + HDMI_HDMI = 1 +}; + +enum hdmi_clk_refsel { + HDMI_REFSEL_PCLK = 0, + HDMI_REFSEL_REF1 = 1, + HDMI_REFSEL_REF2 = 2, + HDMI_REFSEL_SYSCLK = 3 +}; + +enum hdmi_packing_mode { + HDMI_PACK_10b_RGB_YUV444 = 0, + HDMI_PACK_24b_RGB_YUV444_YUV422 = 1, + HDMI_PACK_20b_YUV422 = 2, + HDMI_PACK_ALREADYPACKED = 7 +}; + +enum hdmi_stereo_channels { + HDMI_AUDIO_STEREO_NOCHANNELS = 0, + HDMI_AUDIO_STEREO_ONECHANNEL = 1, + HDMI_AUDIO_STEREO_TWOCHANNELS = 2, + HDMI_AUDIO_STEREO_THREECHANNELS = 3, + HDMI_AUDIO_STEREO_FOURCHANNELS = 4 +}; + +enum hdmi_audio_type { + HDMI_AUDIO_TYPE_LPCM = 0, + HDMI_AUDIO_TYPE_IEC = 1 +}; + +enum hdmi_audio_justify { + HDMI_AUDIO_JUSTIFY_LEFT = 0, + HDMI_AUDIO_JUSTIFY_RIGHT = 1 +}; + +enum hdmi_audio_sample_order { + HDMI_AUDIO_SAMPLE_RIGHT_FIRST = 0, + HDMI_AUDIO_SAMPLE_LEFT_FIRST = 1 +}; + +enum hdmi_audio_samples_perword { + HDMI_AUDIO_ONEWORD_ONESAMPLE = 0, + HDMI_AUDIO_ONEWORD_TWOSAMPLES = 1 +}; + +enum hdmi_audio_sample_size { + HDMI_AUDIO_SAMPLE_16BITS = 0, + HDMI_AUDIO_SAMPLE_24BITS = 1 +}; + +enum hdmi_audio_transf_mode { + HDMI_AUDIO_TRANSF_DMA = 0, + HDMI_AUDIO_TRANSF_IRQ = 1 +}; + +enum hdmi_audio_blk_strt_end_sig { + HDMI_AUDIO_BLOCK_SIG_STARTEND_ON = 0, + HDMI_AUDIO_BLOCK_SIG_STARTEND_OFF = 1 +}; + +enum hdmi_core_audio_layout { + HDMI_AUDIO_LAYOUT_2CH = 0, + HDMI_AUDIO_LAYOUT_8CH = 1 +}; + +enum hdmi_core_cts_mode { + HDMI_AUDIO_CTS_MODE_HW = 0, + HDMI_AUDIO_CTS_MODE_SW = 1 +}; + +enum hdmi_audio_mclk_mode { + HDMI_AUDIO_MCLK_128FS = 0, + HDMI_AUDIO_MCLK_256FS = 1, + HDMI_AUDIO_MCLK_384FS = 2, + HDMI_AUDIO_MCLK_512FS = 3, + HDMI_AUDIO_MCLK_768FS = 4, + HDMI_AUDIO_MCLK_1024FS = 5, + HDMI_AUDIO_MCLK_1152FS = 6, + HDMI_AUDIO_MCLK_192FS = 7 +}; + +/* INFOFRAME_AVI_ and INFOFRAME_AUDIO_ definitions */ +enum hdmi_core_infoframe { + HDMI_INFOFRAME_AVI_DB1Y_RGB = 0, + HDMI_INFOFRAME_AVI_DB1Y_YUV422 = 1, + HDMI_INFOFRAME_AVI_DB1Y_YUV444 = 2, + HDMI_INFOFRAME_AVI_DB1A_ACTIVE_FORMAT_OFF = 0, + HDMI_INFOFRAME_AVI_DB1A_ACTIVE_FORMAT_ON = 1, + HDMI_INFOFRAME_AVI_DB1B_NO = 0, + HDMI_INFOFRAME_AVI_DB1B_VERT = 1, + HDMI_INFOFRAME_AVI_DB1B_HORI = 2, + HDMI_INFOFRAME_AVI_DB1B_VERTHORI = 3, + HDMI_INFOFRAME_AVI_DB1S_0 = 0, + HDMI_INFOFRAME_AVI_DB1S_1 = 1, + HDMI_INFOFRAME_AVI_DB1S_2 = 2, + HDMI_INFOFRAME_AVI_DB2C_NO = 0, + HDMI_INFOFRAME_AVI_DB2C_ITU601 = 1, + HDMI_INFOFRAME_AVI_DB2C_ITU709 = 2, + HDMI_INFOFRAME_AVI_DB2C_EC_EXTENDED = 3, + HDMI_INFOFRAME_AVI_DB2M_NO = 0, + HDMI_INFOFRAME_AVI_DB2M_43 = 1, + HDMI_INFOFRAME_AVI_DB2M_169 = 2, + HDMI_INFOFRAME_AVI_DB2R_SAME = 8, + HDMI_INFOFRAME_AVI_DB2R_43 = 9, + HDMI_INFOFRAME_AVI_DB2R_169 = 10, + HDMI_INFOFRAME_AVI_DB2R_149 = 11, + HDMI_INFOFRAME_AVI_DB3ITC_NO = 0, + HDMI_INFOFRAME_AVI_DB3ITC_YES = 1, + HDMI_INFOFRAME_AVI_DB3EC_XVYUV601 = 0, + HDMI_INFOFRAME_AVI_DB3EC_XVYUV709 = 1, + HDMI_INFOFRAME_AVI_DB3Q_DEFAULT = 0, + HDMI_INFOFRAME_AVI_DB3Q_LR = 1, + HDMI_INFOFRAME_AVI_DB3Q_FR = 2, + HDMI_INFOFRAME_AVI_DB3SC_NO = 0, + HDMI_INFOFRAME_AVI_DB3SC_HORI = 1, + HDMI_INFOFRAME_AVI_DB3SC_VERT = 2, + HDMI_INFOFRAME_AVI_DB3SC_HORIVERT = 3, + HDMI_INFOFRAME_AVI_DB5PR_NO = 0, + HDMI_INFOFRAME_AVI_DB5PR_2 = 1, + HDMI_INFOFRAME_AVI_DB5PR_3 = 2, + HDMI_INFOFRAME_AVI_DB5PR_4 = 3, + HDMI_INFOFRAME_AVI_DB5PR_5 = 4, + HDMI_INFOFRAME_AVI_DB5PR_6 = 5, + HDMI_INFOFRAME_AVI_DB5PR_7 = 6, + HDMI_INFOFRAME_AVI_DB5PR_8 = 7, + HDMI_INFOFRAME_AVI_DB5PR_9 = 8, + HDMI_INFOFRAME_AVI_DB5PR_10 = 9, +}; + +struct hdmi_cm { + int code; + int mode; +}; + +struct hdmi_video_format { + enum hdmi_packing_mode packing_mode; + u32 y_res; /* Line per panel */ + u32 x_res; /* pixel per line */ +}; + +struct hdmi_config { + struct omap_video_timings timings; + struct hdmi_cm cm; +}; + +/* HDMI PLL structure */ +struct hdmi_pll_info { + u16 regn; + u16 regm; + u32 regmf; + u16 regm2; + u16 regsd; + u16 dcofreq; + enum hdmi_clk_refsel refsel; +}; + +struct hdmi_audio_format { + enum hdmi_stereo_channels stereo_channels; + u8 active_chnnls_msk; + enum hdmi_audio_type type; + enum hdmi_audio_justify justification; + enum hdmi_audio_sample_order sample_order; + enum hdmi_audio_samples_perword samples_per_word; + enum hdmi_audio_sample_size sample_size; + enum hdmi_audio_blk_strt_end_sig en_sig_blk_strt_end; +}; + +struct hdmi_audio_dma { + u8 transfer_size; + u8 block_size; + enum hdmi_audio_transf_mode mode; + u16 fifo_threshold; +}; + +struct hdmi_core_audio_i2s_config { + u8 in_length_bits; + u8 justification; + u8 sck_edge_mode; + u8 vbit; + u8 direction; + u8 shift; + u8 active_sds; +}; + +struct hdmi_core_audio_config { + struct hdmi_core_audio_i2s_config i2s_cfg; + struct snd_aes_iec958 *iec60958_cfg; + bool fs_override; + u32 n; + u32 cts; + u32 aud_par_busclk; + enum hdmi_core_audio_layout layout; + enum hdmi_core_cts_mode cts_mode; + bool use_mclk; + enum hdmi_audio_mclk_mode mclk_mode; + bool en_acr_pkt; + bool en_dsd_audio; + bool en_parallel_aud_input; + bool en_spdif; +}; + +/* + * Refer to section 8.2 in HDMI 1.3 specification for + * details about infoframe databytes + */ +struct hdmi_core_infoframe_avi { + /* Y0, Y1 rgb,yCbCr */ + u8 db1_format; + /* A0 Active information Present */ + u8 db1_active_info; + /* B0, B1 Bar info data valid */ + u8 db1_bar_info_dv; + /* S0, S1 scan information */ + u8 db1_scan_info; + /* C0, C1 colorimetry */ + u8 db2_colorimetry; + /* M0, M1 Aspect ratio (4:3, 16:9) */ + u8 db2_aspect_ratio; + /* R0...R3 Active format aspect ratio */ + u8 db2_active_fmt_ar; + /* ITC IT content. */ + u8 db3_itc; + /* EC0, EC1, EC2 Extended colorimetry */ + u8 db3_ec; + /* Q1, Q0 Quantization range */ + u8 db3_q_range; + /* SC1, SC0 Non-uniform picture scaling */ + u8 db3_nup_scaling; + /* VIC0..6 Video format identification */ + u8 db4_videocode; + /* PR0..PR3 Pixel repetition factor */ + u8 db5_pixel_repeat; + /* Line number end of top bar */ + u16 db6_7_line_eoftop; + /* Line number start of bottom bar */ + u16 db8_9_line_sofbottom; + /* Pixel number end of left bar */ + u16 db10_11_pixel_eofleft; + /* Pixel number start of right bar */ + u16 db12_13_pixel_sofright; +}; + +struct hdmi_wp_data { + void __iomem *base; +}; + +struct hdmi_pll_data { + void __iomem *base; + + struct hdmi_pll_info info; +}; + +struct hdmi_phy_data { + void __iomem *base; + + int irq; +}; + +struct hdmi_core_data { + void __iomem *base; + + struct hdmi_core_infoframe_avi avi_cfg; +}; + +static inline void hdmi_write_reg(void __iomem *base_addr, const u16 idx, + u32 val) +{ + __raw_writel(val, base_addr + idx); +} + +static inline u32 hdmi_read_reg(void __iomem *base_addr, const u16 idx) +{ + return __raw_readl(base_addr + idx); +} + +#define REG_FLD_MOD(base, idx, val, start, end) \ + hdmi_write_reg(base, idx, FLD_MOD(hdmi_read_reg(base, idx),\ + val, start, end)) +#define REG_GET(base, idx, start, end) \ + FLD_GET(hdmi_read_reg(base, idx), start, end) + +static inline int hdmi_wait_for_bit_change(void __iomem *base_addr, + const u32 idx, int b2, int b1, u32 val) +{ + u32 t = 0, v; + while (val != (v = REG_GET(base_addr, idx, b2, b1))) { + if (t++ > 10000) + return v; + udelay(1); + } + return v; +} + +/* HDMI wrapper funcs */ +int hdmi_wp_video_start(struct hdmi_wp_data *wp); +void hdmi_wp_video_stop(struct hdmi_wp_data *wp); +void hdmi_wp_dump(struct hdmi_wp_data *wp, struct seq_file *s); +u32 hdmi_wp_get_irqstatus(struct hdmi_wp_data *wp); +void hdmi_wp_set_irqstatus(struct hdmi_wp_data *wp, u32 irqstatus); +void hdmi_wp_set_irqenable(struct hdmi_wp_data *wp, u32 mask); +void hdmi_wp_clear_irqenable(struct hdmi_wp_data *wp, u32 mask); +int hdmi_wp_set_phy_pwr(struct hdmi_wp_data *wp, enum hdmi_phy_pwr val); +int hdmi_wp_set_pll_pwr(struct hdmi_wp_data *wp, enum hdmi_pll_pwr val); +void hdmi_wp_video_config_format(struct hdmi_wp_data *wp, + struct hdmi_video_format *video_fmt); +void hdmi_wp_video_config_interface(struct hdmi_wp_data *wp, + struct omap_video_timings *timings); +void hdmi_wp_video_config_timing(struct hdmi_wp_data *wp, + struct omap_video_timings *timings); +void hdmi_wp_init_vid_fmt_timings(struct hdmi_video_format *video_fmt, + struct omap_video_timings *timings, struct hdmi_config *param); +int hdmi_wp_init(struct platform_device *pdev, struct hdmi_wp_data *wp); + +/* HDMI PLL funcs */ +int hdmi_pll_enable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp); +void hdmi_pll_disable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp); +void hdmi_pll_dump(struct hdmi_pll_data *pll, struct seq_file *s); +void hdmi_pll_compute(struct hdmi_pll_data *pll, unsigned long clkin, int phy); +int hdmi_pll_init(struct platform_device *pdev, struct hdmi_pll_data *pll); + +/* HDMI PHY funcs */ +int hdmi_phy_enable(struct hdmi_phy_data *phy, struct hdmi_wp_data *wp, + struct hdmi_config *cfg); +void hdmi_phy_disable(struct hdmi_phy_data *phy, struct hdmi_wp_data *wp); +void hdmi_phy_dump(struct hdmi_phy_data *phy, struct seq_file *s); +int hdmi_phy_init(struct platform_device *pdev, struct hdmi_phy_data *phy); + +/* HDMI common funcs */ +const struct hdmi_config *hdmi_default_timing(void); +const struct hdmi_config *hdmi_get_timings(int mode, int code); +struct hdmi_cm hdmi_get_code(struct omap_video_timings *timing); + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +int hdmi_compute_acr(u32 pclk, u32 sample_freq, u32 *n, u32 *cts); +int hdmi_wp_audio_enable(struct hdmi_wp_data *wp, bool enable); +int hdmi_wp_audio_core_req_enable(struct hdmi_wp_data *wp, bool enable); +void hdmi_wp_audio_config_format(struct hdmi_wp_data *wp, + struct hdmi_audio_format *aud_fmt); +void hdmi_wp_audio_config_dma(struct hdmi_wp_data *wp, + struct hdmi_audio_dma *aud_dma); +static inline bool hdmi_mode_has_audio(int mode) +{ + return mode == HDMI_HDMI ? true : false; +} +#endif +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi4.c b/drivers/video/fbdev/omap2/dss/hdmi4.c new file mode 100644 index 000000000000..f5f7944a1fd1 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi4.c @@ -0,0 +1,703 @@ +/* + * HDMI interface DSS driver for TI's OMAP4 family of SoCs. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Yong Zhi + * Mythri pk <mythripk@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "HDMI" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <video/omapdss.h> + +#include "hdmi4_core.h" +#include "dss.h" +#include "dss_features.h" + +static struct { + struct mutex lock; + struct platform_device *pdev; + + struct hdmi_wp_data wp; + struct hdmi_pll_data pll; + struct hdmi_phy_data phy; + struct hdmi_core_data core; + + struct hdmi_config cfg; + + struct clk *sys_clk; + struct regulator *vdda_hdmi_dac_reg; + + bool core_enabled; + + struct omap_dss_device output; +} hdmi; + +static int hdmi_runtime_get(void) +{ + int r; + + DSSDBG("hdmi_runtime_get\n"); + + r = pm_runtime_get_sync(&hdmi.pdev->dev); + WARN_ON(r < 0); + if (r < 0) + return r; + + return 0; +} + +static void hdmi_runtime_put(void) +{ + int r; + + DSSDBG("hdmi_runtime_put\n"); + + r = pm_runtime_put_sync(&hdmi.pdev->dev); + WARN_ON(r < 0 && r != -ENOSYS); +} + +static int hdmi_init_regulator(void) +{ + struct regulator *reg; + + if (hdmi.vdda_hdmi_dac_reg != NULL) + return 0; + + reg = devm_regulator_get(&hdmi.pdev->dev, "vdda"); + + if (IS_ERR(reg)) { + if (PTR_ERR(reg) != -EPROBE_DEFER) + DSSERR("can't get VDDA regulator\n"); + return PTR_ERR(reg); + } + + hdmi.vdda_hdmi_dac_reg = reg; + + return 0; +} + +static int hdmi_power_on_core(struct omap_dss_device *dssdev) +{ + int r; + + r = regulator_enable(hdmi.vdda_hdmi_dac_reg); + if (r) + return r; + + r = hdmi_runtime_get(); + if (r) + goto err_runtime_get; + + /* Make selection of HDMI in DSS */ + dss_select_hdmi_venc_clk_source(DSS_HDMI_M_PCLK); + + hdmi.core_enabled = true; + + return 0; + +err_runtime_get: + regulator_disable(hdmi.vdda_hdmi_dac_reg); + + return r; +} + +static void hdmi_power_off_core(struct omap_dss_device *dssdev) +{ + hdmi.core_enabled = false; + + hdmi_runtime_put(); + regulator_disable(hdmi.vdda_hdmi_dac_reg); +} + +static int hdmi_power_on_full(struct omap_dss_device *dssdev) +{ + int r; + struct omap_video_timings *p; + struct omap_overlay_manager *mgr = hdmi.output.manager; + unsigned long phy; + + r = hdmi_power_on_core(dssdev); + if (r) + return r; + + p = &hdmi.cfg.timings; + + DSSDBG("hdmi_power_on x_res= %d y_res = %d\n", p->x_res, p->y_res); + + /* the functions below use kHz pixel clock. TODO: change to Hz */ + phy = p->pixelclock / 1000; + + hdmi_pll_compute(&hdmi.pll, clk_get_rate(hdmi.sys_clk), phy); + + /* config the PLL and PHY hdmi_set_pll_pwrfirst */ + r = hdmi_pll_enable(&hdmi.pll, &hdmi.wp); + if (r) { + DSSDBG("Failed to lock PLL\n"); + goto err_pll_enable; + } + + r = hdmi_phy_enable(&hdmi.phy, &hdmi.wp, &hdmi.cfg); + if (r) { + DSSDBG("Failed to start PHY\n"); + goto err_phy_enable; + } + + hdmi4_configure(&hdmi.core, &hdmi.wp, &hdmi.cfg); + + /* bypass TV gamma table */ + dispc_enable_gamma_table(0); + + /* tv size */ + dss_mgr_set_timings(mgr, p); + + r = hdmi_wp_video_start(&hdmi.wp); + if (r) + goto err_vid_enable; + + r = dss_mgr_enable(mgr); + if (r) + goto err_mgr_enable; + + return 0; + +err_mgr_enable: + hdmi_wp_video_stop(&hdmi.wp); +err_vid_enable: + hdmi_phy_disable(&hdmi.phy, &hdmi.wp); +err_phy_enable: + hdmi_pll_disable(&hdmi.pll, &hdmi.wp); +err_pll_enable: + hdmi_power_off_core(dssdev); + return -EIO; +} + +static void hdmi_power_off_full(struct omap_dss_device *dssdev) +{ + struct omap_overlay_manager *mgr = hdmi.output.manager; + + dss_mgr_disable(mgr); + + hdmi_wp_video_stop(&hdmi.wp); + hdmi_phy_disable(&hdmi.phy, &hdmi.wp); + hdmi_pll_disable(&hdmi.pll, &hdmi.wp); + + hdmi_power_off_core(dssdev); +} + +static int hdmi_display_check_timing(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct omap_dss_device *out = &hdmi.output; + + if (!dispc_mgr_timings_ok(out->dispc_channel, timings)) + return -EINVAL; + + return 0; +} + +static void hdmi_display_set_timing(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct hdmi_cm cm; + const struct hdmi_config *t; + + mutex_lock(&hdmi.lock); + + cm = hdmi_get_code(timings); + hdmi.cfg.cm = cm; + + t = hdmi_get_timings(cm.mode, cm.code); + if (t != NULL) { + hdmi.cfg = *t; + + dispc_set_tv_pclk(t->timings.pixelclock); + } else { + hdmi.cfg.timings = *timings; + hdmi.cfg.cm.code = 0; + hdmi.cfg.cm.mode = HDMI_DVI; + + dispc_set_tv_pclk(timings->pixelclock); + } + + DSSDBG("using mode: %s, code %d\n", hdmi.cfg.cm.mode == HDMI_DVI ? + "DVI" : "HDMI", hdmi.cfg.cm.code); + + mutex_unlock(&hdmi.lock); +} + +static void hdmi_display_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + const struct hdmi_config *cfg; + struct hdmi_cm cm = hdmi.cfg.cm; + + cfg = hdmi_get_timings(cm.mode, cm.code); + if (cfg == NULL) + cfg = hdmi_default_timing(); + + memcpy(timings, &cfg->timings, sizeof(cfg->timings)); +} + +static void hdmi_dump_regs(struct seq_file *s) +{ + mutex_lock(&hdmi.lock); + + if (hdmi_runtime_get()) { + mutex_unlock(&hdmi.lock); + return; + } + + hdmi_wp_dump(&hdmi.wp, s); + hdmi_pll_dump(&hdmi.pll, s); + hdmi_phy_dump(&hdmi.phy, s); + hdmi4_core_dump(&hdmi.core, s); + + hdmi_runtime_put(); + mutex_unlock(&hdmi.lock); +} + +static int read_edid(u8 *buf, int len) +{ + int r; + + mutex_lock(&hdmi.lock); + + r = hdmi_runtime_get(); + BUG_ON(r); + + r = hdmi4_read_edid(&hdmi.core, buf, len); + + hdmi_runtime_put(); + mutex_unlock(&hdmi.lock); + + return r; +} + +static int hdmi_display_enable(struct omap_dss_device *dssdev) +{ + struct omap_dss_device *out = &hdmi.output; + int r = 0; + + DSSDBG("ENTER hdmi_display_enable\n"); + + mutex_lock(&hdmi.lock); + + if (out == NULL || out->manager == NULL) { + DSSERR("failed to enable display: no output/manager\n"); + r = -ENODEV; + goto err0; + } + + r = hdmi_power_on_full(dssdev); + if (r) { + DSSERR("failed to power on device\n"); + goto err0; + } + + mutex_unlock(&hdmi.lock); + return 0; + +err0: + mutex_unlock(&hdmi.lock); + return r; +} + +static void hdmi_display_disable(struct omap_dss_device *dssdev) +{ + DSSDBG("Enter hdmi_display_disable\n"); + + mutex_lock(&hdmi.lock); + + hdmi_power_off_full(dssdev); + + mutex_unlock(&hdmi.lock); +} + +static int hdmi_core_enable(struct omap_dss_device *dssdev) +{ + int r = 0; + + DSSDBG("ENTER omapdss_hdmi_core_enable\n"); + + mutex_lock(&hdmi.lock); + + r = hdmi_power_on_core(dssdev); + if (r) { + DSSERR("failed to power on device\n"); + goto err0; + } + + mutex_unlock(&hdmi.lock); + return 0; + +err0: + mutex_unlock(&hdmi.lock); + return r; +} + +static void hdmi_core_disable(struct omap_dss_device *dssdev) +{ + DSSDBG("Enter omapdss_hdmi_core_disable\n"); + + mutex_lock(&hdmi.lock); + + hdmi_power_off_core(dssdev); + + mutex_unlock(&hdmi.lock); +} + +static int hdmi_get_clocks(struct platform_device *pdev) +{ + struct clk *clk; + + clk = devm_clk_get(&pdev->dev, "sys_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get sys_clk\n"); + return PTR_ERR(clk); + } + + hdmi.sys_clk = clk; + + return 0; +} + +static int hdmi_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct omap_overlay_manager *mgr; + int r; + + r = hdmi_init_regulator(); + if (r) + return r; + + mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); + if (!mgr) + return -ENODEV; + + r = dss_mgr_connect(mgr, dssdev); + if (r) + return r; + + r = omapdss_output_set_device(dssdev, dst); + if (r) { + DSSERR("failed to connect output to new device: %s\n", + dst->name); + dss_mgr_disconnect(mgr, dssdev); + return r; + } + + return 0; +} + +static void hdmi_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + WARN_ON(dst != dssdev->dst); + + if (dst != dssdev->dst) + return; + + omapdss_output_unset_device(dssdev); + + if (dssdev->manager) + dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static int hdmi_read_edid(struct omap_dss_device *dssdev, + u8 *edid, int len) +{ + bool need_enable; + int r; + + need_enable = hdmi.core_enabled == false; + + if (need_enable) { + r = hdmi_core_enable(dssdev); + if (r) + return r; + } + + r = read_edid(edid, len); + + if (need_enable) + hdmi_core_disable(dssdev); + + return r; +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +static int hdmi_audio_enable(struct omap_dss_device *dssdev) +{ + int r; + + mutex_lock(&hdmi.lock); + + if (!hdmi_mode_has_audio(hdmi.cfg.cm.mode)) { + r = -EPERM; + goto err; + } + + r = hdmi_wp_audio_enable(&hdmi.wp, true); + if (r) + goto err; + + mutex_unlock(&hdmi.lock); + return 0; + +err: + mutex_unlock(&hdmi.lock); + return r; +} + +static void hdmi_audio_disable(struct omap_dss_device *dssdev) +{ + hdmi_wp_audio_enable(&hdmi.wp, false); +} + +static int hdmi_audio_start(struct omap_dss_device *dssdev) +{ + return hdmi4_audio_start(&hdmi.core, &hdmi.wp); +} + +static void hdmi_audio_stop(struct omap_dss_device *dssdev) +{ + hdmi4_audio_stop(&hdmi.core, &hdmi.wp); +} + +static bool hdmi_audio_supported(struct omap_dss_device *dssdev) +{ + bool r; + + mutex_lock(&hdmi.lock); + + r = hdmi_mode_has_audio(hdmi.cfg.cm.mode); + + mutex_unlock(&hdmi.lock); + return r; +} + +static int hdmi_audio_config(struct omap_dss_device *dssdev, + struct omap_dss_audio *audio) +{ + int r; + u32 pclk = hdmi.cfg.timings.pixelclock; + + mutex_lock(&hdmi.lock); + + if (!hdmi_mode_has_audio(hdmi.cfg.cm.mode)) { + r = -EPERM; + goto err; + } + + r = hdmi4_audio_config(&hdmi.core, &hdmi.wp, audio, pclk); + if (r) + goto err; + + mutex_unlock(&hdmi.lock); + return 0; + +err: + mutex_unlock(&hdmi.lock); + return r; +} +#else +static int hdmi_audio_enable(struct omap_dss_device *dssdev) +{ + return -EPERM; +} + +static void hdmi_audio_disable(struct omap_dss_device *dssdev) +{ +} + +static int hdmi_audio_start(struct omap_dss_device *dssdev) +{ + return -EPERM; +} + +static void hdmi_audio_stop(struct omap_dss_device *dssdev) +{ +} + +static bool hdmi_audio_supported(struct omap_dss_device *dssdev) +{ + return false; +} + +static int hdmi_audio_config(struct omap_dss_device *dssdev, + struct omap_dss_audio *audio) +{ + return -EPERM; +} +#endif + +static const struct omapdss_hdmi_ops hdmi_ops = { + .connect = hdmi_connect, + .disconnect = hdmi_disconnect, + + .enable = hdmi_display_enable, + .disable = hdmi_display_disable, + + .check_timings = hdmi_display_check_timing, + .set_timings = hdmi_display_set_timing, + .get_timings = hdmi_display_get_timings, + + .read_edid = hdmi_read_edid, + + .audio_enable = hdmi_audio_enable, + .audio_disable = hdmi_audio_disable, + .audio_start = hdmi_audio_start, + .audio_stop = hdmi_audio_stop, + .audio_supported = hdmi_audio_supported, + .audio_config = hdmi_audio_config, +}; + +static void hdmi_init_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &hdmi.output; + + out->dev = &pdev->dev; + out->id = OMAP_DSS_OUTPUT_HDMI; + out->output_type = OMAP_DISPLAY_TYPE_HDMI; + out->name = "hdmi.0"; + out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; + out->ops.hdmi = &hdmi_ops; + out->owner = THIS_MODULE; + + omapdss_register_output(out); +} + +static void __exit hdmi_uninit_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &hdmi.output; + + omapdss_unregister_output(out); +} + +/* HDMI HW IP initialisation */ +static int omapdss_hdmihw_probe(struct platform_device *pdev) +{ + int r; + + hdmi.pdev = pdev; + + mutex_init(&hdmi.lock); + + r = hdmi_wp_init(pdev, &hdmi.wp); + if (r) + return r; + + r = hdmi_pll_init(pdev, &hdmi.pll); + if (r) + return r; + + r = hdmi_phy_init(pdev, &hdmi.phy); + if (r) + return r; + + r = hdmi4_core_init(pdev, &hdmi.core); + if (r) + return r; + + r = hdmi_get_clocks(pdev); + if (r) { + DSSERR("can't get clocks\n"); + return r; + } + + pm_runtime_enable(&pdev->dev); + + hdmi_init_output(pdev); + + dss_debugfs_create_file("hdmi", hdmi_dump_regs); + + return 0; +} + +static int __exit omapdss_hdmihw_remove(struct platform_device *pdev) +{ + hdmi_uninit_output(pdev); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int hdmi_runtime_suspend(struct device *dev) +{ + clk_disable_unprepare(hdmi.sys_clk); + + dispc_runtime_put(); + + return 0; +} + +static int hdmi_runtime_resume(struct device *dev) +{ + int r; + + r = dispc_runtime_get(); + if (r < 0) + return r; + + clk_prepare_enable(hdmi.sys_clk); + + return 0; +} + +static const struct dev_pm_ops hdmi_pm_ops = { + .runtime_suspend = hdmi_runtime_suspend, + .runtime_resume = hdmi_runtime_resume, +}; + +static const struct of_device_id hdmi_of_match[] = { + { .compatible = "ti,omap4-hdmi", }, + {}, +}; + +static struct platform_driver omapdss_hdmihw_driver = { + .probe = omapdss_hdmihw_probe, + .remove = __exit_p(omapdss_hdmihw_remove), + .driver = { + .name = "omapdss_hdmi", + .owner = THIS_MODULE, + .pm = &hdmi_pm_ops, + .of_match_table = hdmi_of_match, + }, +}; + +int __init hdmi4_init_platform_driver(void) +{ + return platform_driver_register(&omapdss_hdmihw_driver); +} + +void __exit hdmi4_uninit_platform_driver(void) +{ + platform_driver_unregister(&omapdss_hdmihw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi4_core.c b/drivers/video/fbdev/omap2/dss/hdmi4_core.c new file mode 100644 index 000000000000..2eb04dcf807c --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi4_core.c @@ -0,0 +1,1036 @@ +/* + * ti_hdmi_4xxx_ip.c + * + * HDMI TI81xx, TI38xx, TI OMAP4 etc IP driver Library + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Yong Zhi + * Mythri pk <mythripk@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "HDMICORE" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/seq_file.h> +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +#include <sound/asound.h> +#include <sound/asoundef.h> +#endif + +#include "hdmi4_core.h" +#include "dss_features.h" + +#define HDMI_CORE_AV 0x500 + +static inline void __iomem *hdmi_av_base(struct hdmi_core_data *core) +{ + return core->base + HDMI_CORE_AV; +} + +static int hdmi_core_ddc_init(struct hdmi_core_data *core) +{ + void __iomem *base = core->base; + + /* Turn on CLK for DDC */ + REG_FLD_MOD(base, HDMI_CORE_AV_DPD, 0x7, 2, 0); + + /* IN_PROG */ + if (REG_GET(base, HDMI_CORE_DDC_STATUS, 4, 4) == 1) { + /* Abort transaction */ + REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0xf, 3, 0); + /* IN_PROG */ + if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, + 4, 4, 0) != 0) { + DSSERR("Timeout aborting DDC transaction\n"); + return -ETIMEDOUT; + } + } + + /* Clk SCL Devices */ + REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0xA, 3, 0); + + /* HDMI_CORE_DDC_STATUS_IN_PROG */ + if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, + 4, 4, 0) != 0) { + DSSERR("Timeout starting SCL clock\n"); + return -ETIMEDOUT; + } + + /* Clear FIFO */ + REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x9, 3, 0); + + /* HDMI_CORE_DDC_STATUS_IN_PROG */ + if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, + 4, 4, 0) != 0) { + DSSERR("Timeout clearing DDC fifo\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int hdmi_core_ddc_edid(struct hdmi_core_data *core, + u8 *pedid, int ext) +{ + void __iomem *base = core->base; + u32 i; + char checksum; + u32 offset = 0; + + /* HDMI_CORE_DDC_STATUS_IN_PROG */ + if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, + 4, 4, 0) != 0) { + DSSERR("Timeout waiting DDC to be ready\n"); + return -ETIMEDOUT; + } + + if (ext % 2 != 0) + offset = 0x80; + + /* Load Segment Address Register */ + REG_FLD_MOD(base, HDMI_CORE_DDC_SEGM, ext / 2, 7, 0); + + /* Load Slave Address Register */ + REG_FLD_MOD(base, HDMI_CORE_DDC_ADDR, 0xA0 >> 1, 7, 1); + + /* Load Offset Address Register */ + REG_FLD_MOD(base, HDMI_CORE_DDC_OFFSET, offset, 7, 0); + + /* Load Byte Count */ + REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT1, 0x80, 7, 0); + REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT2, 0x0, 1, 0); + + /* Set DDC_CMD */ + if (ext) + REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x4, 3, 0); + else + REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x2, 3, 0); + + /* HDMI_CORE_DDC_STATUS_BUS_LOW */ + if (REG_GET(base, HDMI_CORE_DDC_STATUS, 6, 6) == 1) { + DSSERR("I2C Bus Low?\n"); + return -EIO; + } + /* HDMI_CORE_DDC_STATUS_NO_ACK */ + if (REG_GET(base, HDMI_CORE_DDC_STATUS, 5, 5) == 1) { + DSSERR("I2C No Ack\n"); + return -EIO; + } + + for (i = 0; i < 0x80; ++i) { + int t; + + /* IN_PROG */ + if (REG_GET(base, HDMI_CORE_DDC_STATUS, 4, 4) == 0) { + DSSERR("operation stopped when reading edid\n"); + return -EIO; + } + + t = 0; + /* FIFO_EMPTY */ + while (REG_GET(base, HDMI_CORE_DDC_STATUS, 2, 2) == 1) { + if (t++ > 10000) { + DSSERR("timeout reading edid\n"); + return -ETIMEDOUT; + } + udelay(1); + } + + pedid[i] = REG_GET(base, HDMI_CORE_DDC_DATA, 7, 0); + } + + checksum = 0; + for (i = 0; i < 0x80; ++i) + checksum += pedid[i]; + + if (checksum != 0) { + DSSERR("E-EDID checksum failed!!\n"); + return -EIO; + } + + return 0; +} + +int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len) +{ + int r, l; + + if (len < 128) + return -EINVAL; + + r = hdmi_core_ddc_init(core); + if (r) + return r; + + r = hdmi_core_ddc_edid(core, edid, 0); + if (r) + return r; + + l = 128; + + if (len >= 128 * 2 && edid[0x7e] > 0) { + r = hdmi_core_ddc_edid(core, edid + 0x80, 1); + if (r) + return r; + l += 128; + } + + return l; +} + +static void hdmi_core_init(struct hdmi_core_video_config *video_cfg, + struct hdmi_core_infoframe_avi *avi_cfg, + struct hdmi_core_packet_enable_repeat *repeat_cfg) +{ + DSSDBG("Enter hdmi_core_init\n"); + + /* video core */ + video_cfg->ip_bus_width = HDMI_INPUT_8BIT; + video_cfg->op_dither_truc = HDMI_OUTPUTTRUNCATION_8BIT; + video_cfg->deep_color_pkt = HDMI_DEEPCOLORPACKECTDISABLE; + video_cfg->pkt_mode = HDMI_PACKETMODERESERVEDVALUE; + video_cfg->hdmi_dvi = HDMI_DVI; + video_cfg->tclk_sel_clkmult = HDMI_FPLL10IDCK; + + /* info frame */ + avi_cfg->db1_format = 0; + avi_cfg->db1_active_info = 0; + avi_cfg->db1_bar_info_dv = 0; + avi_cfg->db1_scan_info = 0; + avi_cfg->db2_colorimetry = 0; + avi_cfg->db2_aspect_ratio = 0; + avi_cfg->db2_active_fmt_ar = 0; + avi_cfg->db3_itc = 0; + avi_cfg->db3_ec = 0; + avi_cfg->db3_q_range = 0; + avi_cfg->db3_nup_scaling = 0; + avi_cfg->db4_videocode = 0; + avi_cfg->db5_pixel_repeat = 0; + avi_cfg->db6_7_line_eoftop = 0; + avi_cfg->db8_9_line_sofbottom = 0; + avi_cfg->db10_11_pixel_eofleft = 0; + avi_cfg->db12_13_pixel_sofright = 0; + + /* packet enable and repeat */ + repeat_cfg->audio_pkt = 0; + repeat_cfg->audio_pkt_repeat = 0; + repeat_cfg->avi_infoframe = 0; + repeat_cfg->avi_infoframe_repeat = 0; + repeat_cfg->gen_cntrl_pkt = 0; + repeat_cfg->gen_cntrl_pkt_repeat = 0; + repeat_cfg->generic_pkt = 0; + repeat_cfg->generic_pkt_repeat = 0; +} + +static void hdmi_core_powerdown_disable(struct hdmi_core_data *core) +{ + DSSDBG("Enter hdmi_core_powerdown_disable\n"); + REG_FLD_MOD(core->base, HDMI_CORE_SYS_SYS_CTRL1, 0x0, 0, 0); +} + +static void hdmi_core_swreset_release(struct hdmi_core_data *core) +{ + DSSDBG("Enter hdmi_core_swreset_release\n"); + REG_FLD_MOD(core->base, HDMI_CORE_SYS_SRST, 0x0, 0, 0); +} + +static void hdmi_core_swreset_assert(struct hdmi_core_data *core) +{ + DSSDBG("Enter hdmi_core_swreset_assert\n"); + REG_FLD_MOD(core->base, HDMI_CORE_SYS_SRST, 0x1, 0, 0); +} + +/* HDMI_CORE_VIDEO_CONFIG */ +static void hdmi_core_video_config(struct hdmi_core_data *core, + struct hdmi_core_video_config *cfg) +{ + u32 r = 0; + void __iomem *core_sys_base = core->base; + void __iomem *core_av_base = hdmi_av_base(core); + + /* sys_ctrl1 default configuration not tunable */ + r = hdmi_read_reg(core_sys_base, HDMI_CORE_SYS_SYS_CTRL1); + r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_VEN_FOLLOWVSYNC, 5, 5); + r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_HEN_FOLLOWHSYNC, 4, 4); + r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_BSEL_24BITBUS, 2, 2); + r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_EDGE_RISINGEDGE, 1, 1); + hdmi_write_reg(core_sys_base, HDMI_CORE_SYS_SYS_CTRL1, r); + + REG_FLD_MOD(core_sys_base, + HDMI_CORE_SYS_VID_ACEN, cfg->ip_bus_width, 7, 6); + + /* Vid_Mode */ + r = hdmi_read_reg(core_sys_base, HDMI_CORE_SYS_VID_MODE); + + /* dither truncation configuration */ + if (cfg->op_dither_truc > HDMI_OUTPUTTRUNCATION_12BIT) { + r = FLD_MOD(r, cfg->op_dither_truc - 3, 7, 6); + r = FLD_MOD(r, 1, 5, 5); + } else { + r = FLD_MOD(r, cfg->op_dither_truc, 7, 6); + r = FLD_MOD(r, 0, 5, 5); + } + hdmi_write_reg(core_sys_base, HDMI_CORE_SYS_VID_MODE, r); + + /* HDMI_Ctrl */ + r = hdmi_read_reg(core_av_base, HDMI_CORE_AV_HDMI_CTRL); + r = FLD_MOD(r, cfg->deep_color_pkt, 6, 6); + r = FLD_MOD(r, cfg->pkt_mode, 5, 3); + r = FLD_MOD(r, cfg->hdmi_dvi, 0, 0); + hdmi_write_reg(core_av_base, HDMI_CORE_AV_HDMI_CTRL, r); + + /* TMDS_CTRL */ + REG_FLD_MOD(core_sys_base, + HDMI_CORE_SYS_TMDS_CTRL, cfg->tclk_sel_clkmult, 6, 5); +} + +static void hdmi_core_aux_infoframe_avi_config(struct hdmi_core_data *core) +{ + u32 val; + char sum = 0, checksum = 0; + void __iomem *av_base = hdmi_av_base(core); + struct hdmi_core_infoframe_avi info_avi = core->avi_cfg; + + sum += 0x82 + 0x002 + 0x00D; + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_TYPE, 0x082); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_VERS, 0x002); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_LEN, 0x00D); + + val = (info_avi.db1_format << 5) | + (info_avi.db1_active_info << 4) | + (info_avi.db1_bar_info_dv << 2) | + (info_avi.db1_scan_info); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(0), val); + sum += val; + + val = (info_avi.db2_colorimetry << 6) | + (info_avi.db2_aspect_ratio << 4) | + (info_avi.db2_active_fmt_ar); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(1), val); + sum += val; + + val = (info_avi.db3_itc << 7) | + (info_avi.db3_ec << 4) | + (info_avi.db3_q_range << 2) | + (info_avi.db3_nup_scaling); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(2), val); + sum += val; + + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(3), + info_avi.db4_videocode); + sum += info_avi.db4_videocode; + + val = info_avi.db5_pixel_repeat; + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(4), val); + sum += val; + + val = info_avi.db6_7_line_eoftop & 0x00FF; + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(5), val); + sum += val; + + val = ((info_avi.db6_7_line_eoftop >> 8) & 0x00FF); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(6), val); + sum += val; + + val = info_avi.db8_9_line_sofbottom & 0x00FF; + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(7), val); + sum += val; + + val = ((info_avi.db8_9_line_sofbottom >> 8) & 0x00FF); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(8), val); + sum += val; + + val = info_avi.db10_11_pixel_eofleft & 0x00FF; + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(9), val); + sum += val; + + val = ((info_avi.db10_11_pixel_eofleft >> 8) & 0x00FF); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(10), val); + sum += val; + + val = info_avi.db12_13_pixel_sofright & 0x00FF; + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(11), val); + sum += val; + + val = ((info_avi.db12_13_pixel_sofright >> 8) & 0x00FF); + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(12), val); + sum += val; + + checksum = 0x100 - sum; + hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_CHSUM, checksum); +} + +static void hdmi_core_av_packet_config(struct hdmi_core_data *core, + struct hdmi_core_packet_enable_repeat repeat_cfg) +{ + /* enable/repeat the infoframe */ + hdmi_write_reg(hdmi_av_base(core), HDMI_CORE_AV_PB_CTRL1, + (repeat_cfg.audio_pkt << 5) | + (repeat_cfg.audio_pkt_repeat << 4) | + (repeat_cfg.avi_infoframe << 1) | + (repeat_cfg.avi_infoframe_repeat)); + + /* enable/repeat the packet */ + hdmi_write_reg(hdmi_av_base(core), HDMI_CORE_AV_PB_CTRL2, + (repeat_cfg.gen_cntrl_pkt << 3) | + (repeat_cfg.gen_cntrl_pkt_repeat << 2) | + (repeat_cfg.generic_pkt << 1) | + (repeat_cfg.generic_pkt_repeat)); +} + +void hdmi4_configure(struct hdmi_core_data *core, + struct hdmi_wp_data *wp, struct hdmi_config *cfg) +{ + /* HDMI */ + struct omap_video_timings video_timing; + struct hdmi_video_format video_format; + /* HDMI core */ + struct hdmi_core_infoframe_avi *avi_cfg = &core->avi_cfg; + struct hdmi_core_video_config v_core_cfg; + struct hdmi_core_packet_enable_repeat repeat_cfg; + + hdmi_core_init(&v_core_cfg, avi_cfg, &repeat_cfg); + + hdmi_wp_init_vid_fmt_timings(&video_format, &video_timing, cfg); + + hdmi_wp_video_config_timing(wp, &video_timing); + + /* video config */ + video_format.packing_mode = HDMI_PACK_24b_RGB_YUV444_YUV422; + + hdmi_wp_video_config_format(wp, &video_format); + + hdmi_wp_video_config_interface(wp, &video_timing); + + /* + * configure core video part + * set software reset in the core + */ + hdmi_core_swreset_assert(core); + + /* power down off */ + hdmi_core_powerdown_disable(core); + + v_core_cfg.pkt_mode = HDMI_PACKETMODE24BITPERPIXEL; + v_core_cfg.hdmi_dvi = cfg->cm.mode; + + hdmi_core_video_config(core, &v_core_cfg); + + /* release software reset in the core */ + hdmi_core_swreset_release(core); + + /* + * configure packet + * info frame video see doc CEA861-D page 65 + */ + avi_cfg->db1_format = HDMI_INFOFRAME_AVI_DB1Y_RGB; + avi_cfg->db1_active_info = + HDMI_INFOFRAME_AVI_DB1A_ACTIVE_FORMAT_OFF; + avi_cfg->db1_bar_info_dv = HDMI_INFOFRAME_AVI_DB1B_NO; + avi_cfg->db1_scan_info = HDMI_INFOFRAME_AVI_DB1S_0; + avi_cfg->db2_colorimetry = HDMI_INFOFRAME_AVI_DB2C_NO; + avi_cfg->db2_aspect_ratio = HDMI_INFOFRAME_AVI_DB2M_NO; + avi_cfg->db2_active_fmt_ar = HDMI_INFOFRAME_AVI_DB2R_SAME; + avi_cfg->db3_itc = HDMI_INFOFRAME_AVI_DB3ITC_NO; + avi_cfg->db3_ec = HDMI_INFOFRAME_AVI_DB3EC_XVYUV601; + avi_cfg->db3_q_range = HDMI_INFOFRAME_AVI_DB3Q_DEFAULT; + avi_cfg->db3_nup_scaling = HDMI_INFOFRAME_AVI_DB3SC_NO; + avi_cfg->db4_videocode = cfg->cm.code; + avi_cfg->db5_pixel_repeat = HDMI_INFOFRAME_AVI_DB5PR_NO; + avi_cfg->db6_7_line_eoftop = 0; + avi_cfg->db8_9_line_sofbottom = 0; + avi_cfg->db10_11_pixel_eofleft = 0; + avi_cfg->db12_13_pixel_sofright = 0; + + hdmi_core_aux_infoframe_avi_config(core); + + /* enable/repeat the infoframe */ + repeat_cfg.avi_infoframe = HDMI_PACKETENABLE; + repeat_cfg.avi_infoframe_repeat = HDMI_PACKETREPEATON; + /* wakeup */ + repeat_cfg.audio_pkt = HDMI_PACKETENABLE; + repeat_cfg.audio_pkt_repeat = HDMI_PACKETREPEATON; + hdmi_core_av_packet_config(core, repeat_cfg); +} + +void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s) +{ + int i; + +#define CORE_REG(i, name) name(i) +#define DUMPCORE(r) seq_printf(s, "%-35s %08x\n", #r,\ + hdmi_read_reg(core->base, r)) +#define DUMPCOREAV(r) seq_printf(s, "%-35s %08x\n", #r,\ + hdmi_read_reg(hdmi_av_base(core), r)) +#define DUMPCOREAV2(i, r) seq_printf(s, "%s[%d]%*s %08x\n", #r, i, \ + (i < 10) ? 32 - (int)strlen(#r) : 31 - (int)strlen(#r), " ", \ + hdmi_read_reg(hdmi_av_base(core), CORE_REG(i, r))) + + DUMPCORE(HDMI_CORE_SYS_VND_IDL); + DUMPCORE(HDMI_CORE_SYS_DEV_IDL); + DUMPCORE(HDMI_CORE_SYS_DEV_IDH); + DUMPCORE(HDMI_CORE_SYS_DEV_REV); + DUMPCORE(HDMI_CORE_SYS_SRST); + DUMPCORE(HDMI_CORE_SYS_SYS_CTRL1); + DUMPCORE(HDMI_CORE_SYS_SYS_STAT); + DUMPCORE(HDMI_CORE_SYS_SYS_CTRL3); + DUMPCORE(HDMI_CORE_SYS_DE_DLY); + DUMPCORE(HDMI_CORE_SYS_DE_CTRL); + DUMPCORE(HDMI_CORE_SYS_DE_TOP); + DUMPCORE(HDMI_CORE_SYS_DE_CNTL); + DUMPCORE(HDMI_CORE_SYS_DE_CNTH); + DUMPCORE(HDMI_CORE_SYS_DE_LINL); + DUMPCORE(HDMI_CORE_SYS_DE_LINH_1); + DUMPCORE(HDMI_CORE_SYS_HRES_L); + DUMPCORE(HDMI_CORE_SYS_HRES_H); + DUMPCORE(HDMI_CORE_SYS_VRES_L); + DUMPCORE(HDMI_CORE_SYS_VRES_H); + DUMPCORE(HDMI_CORE_SYS_IADJUST); + DUMPCORE(HDMI_CORE_SYS_POLDETECT); + DUMPCORE(HDMI_CORE_SYS_HWIDTH1); + DUMPCORE(HDMI_CORE_SYS_HWIDTH2); + DUMPCORE(HDMI_CORE_SYS_VWIDTH); + DUMPCORE(HDMI_CORE_SYS_VID_CTRL); + DUMPCORE(HDMI_CORE_SYS_VID_ACEN); + DUMPCORE(HDMI_CORE_SYS_VID_MODE); + DUMPCORE(HDMI_CORE_SYS_VID_BLANK1); + DUMPCORE(HDMI_CORE_SYS_VID_BLANK3); + DUMPCORE(HDMI_CORE_SYS_VID_BLANK1); + DUMPCORE(HDMI_CORE_SYS_DC_HEADER); + DUMPCORE(HDMI_CORE_SYS_VID_DITHER); + DUMPCORE(HDMI_CORE_SYS_RGB2XVYCC_CT); + DUMPCORE(HDMI_CORE_SYS_R2Y_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_R2Y_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_G2Y_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_G2Y_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_B2Y_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_B2Y_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_R2CB_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_R2CB_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_G2CB_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_G2CB_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_B2CB_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_B2CB_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_R2CR_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_R2CR_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_G2CR_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_G2CR_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_B2CR_COEFF_LOW); + DUMPCORE(HDMI_CORE_SYS_B2CR_COEFF_UP); + DUMPCORE(HDMI_CORE_SYS_RGB_OFFSET_LOW); + DUMPCORE(HDMI_CORE_SYS_RGB_OFFSET_UP); + DUMPCORE(HDMI_CORE_SYS_Y_OFFSET_LOW); + DUMPCORE(HDMI_CORE_SYS_Y_OFFSET_UP); + DUMPCORE(HDMI_CORE_SYS_CBCR_OFFSET_LOW); + DUMPCORE(HDMI_CORE_SYS_CBCR_OFFSET_UP); + DUMPCORE(HDMI_CORE_SYS_INTR_STATE); + DUMPCORE(HDMI_CORE_SYS_INTR1); + DUMPCORE(HDMI_CORE_SYS_INTR2); + DUMPCORE(HDMI_CORE_SYS_INTR3); + DUMPCORE(HDMI_CORE_SYS_INTR4); + DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK1); + DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK2); + DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK3); + DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK4); + DUMPCORE(HDMI_CORE_SYS_INTR_CTRL); + DUMPCORE(HDMI_CORE_SYS_TMDS_CTRL); + + DUMPCORE(HDMI_CORE_DDC_ADDR); + DUMPCORE(HDMI_CORE_DDC_SEGM); + DUMPCORE(HDMI_CORE_DDC_OFFSET); + DUMPCORE(HDMI_CORE_DDC_COUNT1); + DUMPCORE(HDMI_CORE_DDC_COUNT2); + DUMPCORE(HDMI_CORE_DDC_STATUS); + DUMPCORE(HDMI_CORE_DDC_CMD); + DUMPCORE(HDMI_CORE_DDC_DATA); + + DUMPCOREAV(HDMI_CORE_AV_ACR_CTRL); + DUMPCOREAV(HDMI_CORE_AV_FREQ_SVAL); + DUMPCOREAV(HDMI_CORE_AV_N_SVAL1); + DUMPCOREAV(HDMI_CORE_AV_N_SVAL2); + DUMPCOREAV(HDMI_CORE_AV_N_SVAL3); + DUMPCOREAV(HDMI_CORE_AV_CTS_SVAL1); + DUMPCOREAV(HDMI_CORE_AV_CTS_SVAL2); + DUMPCOREAV(HDMI_CORE_AV_CTS_SVAL3); + DUMPCOREAV(HDMI_CORE_AV_CTS_HVAL1); + DUMPCOREAV(HDMI_CORE_AV_CTS_HVAL2); + DUMPCOREAV(HDMI_CORE_AV_CTS_HVAL3); + DUMPCOREAV(HDMI_CORE_AV_AUD_MODE); + DUMPCOREAV(HDMI_CORE_AV_SPDIF_CTRL); + DUMPCOREAV(HDMI_CORE_AV_HW_SPDIF_FS); + DUMPCOREAV(HDMI_CORE_AV_SWAP_I2S); + DUMPCOREAV(HDMI_CORE_AV_SPDIF_ERTH); + DUMPCOREAV(HDMI_CORE_AV_I2S_IN_MAP); + DUMPCOREAV(HDMI_CORE_AV_I2S_IN_CTRL); + DUMPCOREAV(HDMI_CORE_AV_I2S_CHST0); + DUMPCOREAV(HDMI_CORE_AV_I2S_CHST1); + DUMPCOREAV(HDMI_CORE_AV_I2S_CHST2); + DUMPCOREAV(HDMI_CORE_AV_I2S_CHST4); + DUMPCOREAV(HDMI_CORE_AV_I2S_CHST5); + DUMPCOREAV(HDMI_CORE_AV_ASRC); + DUMPCOREAV(HDMI_CORE_AV_I2S_IN_LEN); + DUMPCOREAV(HDMI_CORE_AV_HDMI_CTRL); + DUMPCOREAV(HDMI_CORE_AV_AUDO_TXSTAT); + DUMPCOREAV(HDMI_CORE_AV_AUD_PAR_BUSCLK_1); + DUMPCOREAV(HDMI_CORE_AV_AUD_PAR_BUSCLK_2); + DUMPCOREAV(HDMI_CORE_AV_AUD_PAR_BUSCLK_3); + DUMPCOREAV(HDMI_CORE_AV_TEST_TXCTRL); + DUMPCOREAV(HDMI_CORE_AV_DPD); + DUMPCOREAV(HDMI_CORE_AV_PB_CTRL1); + DUMPCOREAV(HDMI_CORE_AV_PB_CTRL2); + DUMPCOREAV(HDMI_CORE_AV_AVI_TYPE); + DUMPCOREAV(HDMI_CORE_AV_AVI_VERS); + DUMPCOREAV(HDMI_CORE_AV_AVI_LEN); + DUMPCOREAV(HDMI_CORE_AV_AVI_CHSUM); + + for (i = 0; i < HDMI_CORE_AV_AVI_DBYTE_NELEMS; i++) + DUMPCOREAV2(i, HDMI_CORE_AV_AVI_DBYTE); + + DUMPCOREAV(HDMI_CORE_AV_SPD_TYPE); + DUMPCOREAV(HDMI_CORE_AV_SPD_VERS); + DUMPCOREAV(HDMI_CORE_AV_SPD_LEN); + DUMPCOREAV(HDMI_CORE_AV_SPD_CHSUM); + + for (i = 0; i < HDMI_CORE_AV_SPD_DBYTE_NELEMS; i++) + DUMPCOREAV2(i, HDMI_CORE_AV_SPD_DBYTE); + + DUMPCOREAV(HDMI_CORE_AV_AUDIO_TYPE); + DUMPCOREAV(HDMI_CORE_AV_AUDIO_VERS); + DUMPCOREAV(HDMI_CORE_AV_AUDIO_LEN); + DUMPCOREAV(HDMI_CORE_AV_AUDIO_CHSUM); + + for (i = 0; i < HDMI_CORE_AV_AUD_DBYTE_NELEMS; i++) + DUMPCOREAV2(i, HDMI_CORE_AV_AUD_DBYTE); + + DUMPCOREAV(HDMI_CORE_AV_MPEG_TYPE); + DUMPCOREAV(HDMI_CORE_AV_MPEG_VERS); + DUMPCOREAV(HDMI_CORE_AV_MPEG_LEN); + DUMPCOREAV(HDMI_CORE_AV_MPEG_CHSUM); + + for (i = 0; i < HDMI_CORE_AV_MPEG_DBYTE_NELEMS; i++) + DUMPCOREAV2(i, HDMI_CORE_AV_MPEG_DBYTE); + + for (i = 0; i < HDMI_CORE_AV_GEN_DBYTE_NELEMS; i++) + DUMPCOREAV2(i, HDMI_CORE_AV_GEN_DBYTE); + + DUMPCOREAV(HDMI_CORE_AV_CP_BYTE1); + + for (i = 0; i < HDMI_CORE_AV_GEN2_DBYTE_NELEMS; i++) + DUMPCOREAV2(i, HDMI_CORE_AV_GEN2_DBYTE); + + DUMPCOREAV(HDMI_CORE_AV_CEC_ADDR_ID); +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +static void hdmi_core_audio_config(struct hdmi_core_data *core, + struct hdmi_core_audio_config *cfg) +{ + u32 r; + void __iomem *av_base = hdmi_av_base(core); + + /* + * Parameters for generation of Audio Clock Recovery packets + */ + REG_FLD_MOD(av_base, HDMI_CORE_AV_N_SVAL1, cfg->n, 7, 0); + REG_FLD_MOD(av_base, HDMI_CORE_AV_N_SVAL2, cfg->n >> 8, 7, 0); + REG_FLD_MOD(av_base, HDMI_CORE_AV_N_SVAL3, cfg->n >> 16, 7, 0); + + if (cfg->cts_mode == HDMI_AUDIO_CTS_MODE_SW) { + REG_FLD_MOD(av_base, HDMI_CORE_AV_CTS_SVAL1, cfg->cts, 7, 0); + REG_FLD_MOD(av_base, + HDMI_CORE_AV_CTS_SVAL2, cfg->cts >> 8, 7, 0); + REG_FLD_MOD(av_base, + HDMI_CORE_AV_CTS_SVAL3, cfg->cts >> 16, 7, 0); + } else { + REG_FLD_MOD(av_base, HDMI_CORE_AV_AUD_PAR_BUSCLK_1, + cfg->aud_par_busclk, 7, 0); + REG_FLD_MOD(av_base, HDMI_CORE_AV_AUD_PAR_BUSCLK_2, + (cfg->aud_par_busclk >> 8), 7, 0); + REG_FLD_MOD(av_base, HDMI_CORE_AV_AUD_PAR_BUSCLK_3, + (cfg->aud_par_busclk >> 16), 7, 0); + } + + /* Set ACR clock divisor */ + REG_FLD_MOD(av_base, + HDMI_CORE_AV_FREQ_SVAL, cfg->mclk_mode, 2, 0); + + r = hdmi_read_reg(av_base, HDMI_CORE_AV_ACR_CTRL); + /* + * Use TMDS clock for ACR packets. For devices that use + * the MCLK, this is the first part of the MCLK initialization. + */ + r = FLD_MOD(r, 0, 2, 2); + + r = FLD_MOD(r, cfg->en_acr_pkt, 1, 1); + r = FLD_MOD(r, cfg->cts_mode, 0, 0); + hdmi_write_reg(av_base, HDMI_CORE_AV_ACR_CTRL, r); + + /* For devices using MCLK, this completes its initialization. */ + if (cfg->use_mclk) + REG_FLD_MOD(av_base, HDMI_CORE_AV_ACR_CTRL, 1, 2, 2); + + /* Override of SPDIF sample frequency with value in I2S_CHST4 */ + REG_FLD_MOD(av_base, HDMI_CORE_AV_SPDIF_CTRL, + cfg->fs_override, 1, 1); + + /* + * Set IEC-60958-3 channel status word. It is passed to the IP + * just as it is received. The user of the driver is responsible + * for its contents. + */ + hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST0, + cfg->iec60958_cfg->status[0]); + hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST1, + cfg->iec60958_cfg->status[1]); + hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST2, + cfg->iec60958_cfg->status[2]); + /* yes, this is correct: status[3] goes to CHST4 register */ + hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST4, + cfg->iec60958_cfg->status[3]); + /* yes, this is correct: status[4] goes to CHST5 register */ + hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST5, + cfg->iec60958_cfg->status[4]); + + /* set I2S parameters */ + r = hdmi_read_reg(av_base, HDMI_CORE_AV_I2S_IN_CTRL); + r = FLD_MOD(r, cfg->i2s_cfg.sck_edge_mode, 6, 6); + r = FLD_MOD(r, cfg->i2s_cfg.vbit, 4, 4); + r = FLD_MOD(r, cfg->i2s_cfg.justification, 2, 2); + r = FLD_MOD(r, cfg->i2s_cfg.direction, 1, 1); + r = FLD_MOD(r, cfg->i2s_cfg.shift, 0, 0); + hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_IN_CTRL, r); + + REG_FLD_MOD(av_base, HDMI_CORE_AV_I2S_IN_LEN, + cfg->i2s_cfg.in_length_bits, 3, 0); + + /* Audio channels and mode parameters */ + REG_FLD_MOD(av_base, HDMI_CORE_AV_HDMI_CTRL, cfg->layout, 2, 1); + r = hdmi_read_reg(av_base, HDMI_CORE_AV_AUD_MODE); + r = FLD_MOD(r, cfg->i2s_cfg.active_sds, 7, 4); + r = FLD_MOD(r, cfg->en_dsd_audio, 3, 3); + r = FLD_MOD(r, cfg->en_parallel_aud_input, 2, 2); + r = FLD_MOD(r, cfg->en_spdif, 1, 1); + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_MODE, r); + + /* Audio channel mappings */ + /* TODO: Make channel mapping dynamic. For now, map channels + * in the ALSA order: FL/FR/RL/RR/C/LFE/SL/SR. Remapping is needed as + * HDMI speaker order is different. See CEA-861 Section 6.6.2. + */ + hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_IN_MAP, 0x78); + REG_FLD_MOD(av_base, HDMI_CORE_AV_SWAP_I2S, 1, 5, 5); +} + +static void hdmi_core_audio_infoframe_cfg(struct hdmi_core_data *core, + struct snd_cea_861_aud_if *info_aud) +{ + u8 sum = 0, checksum = 0; + void __iomem *av_base = hdmi_av_base(core); + + /* + * Set audio info frame type, version and length as + * described in HDMI 1.4a Section 8.2.2 specification. + * Checksum calculation is defined in Section 5.3.5. + */ + hdmi_write_reg(av_base, HDMI_CORE_AV_AUDIO_TYPE, 0x84); + hdmi_write_reg(av_base, HDMI_CORE_AV_AUDIO_VERS, 0x01); + hdmi_write_reg(av_base, HDMI_CORE_AV_AUDIO_LEN, 0x0a); + sum += 0x84 + 0x001 + 0x00a; + + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(0), + info_aud->db1_ct_cc); + sum += info_aud->db1_ct_cc; + + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(1), + info_aud->db2_sf_ss); + sum += info_aud->db2_sf_ss; + + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(2), info_aud->db3); + sum += info_aud->db3; + + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(3), info_aud->db4_ca); + sum += info_aud->db4_ca; + + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(4), + info_aud->db5_dminh_lsv); + sum += info_aud->db5_dminh_lsv; + + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(5), 0x00); + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(6), 0x00); + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(7), 0x00); + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(8), 0x00); + hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(9), 0x00); + + checksum = 0x100 - sum; + hdmi_write_reg(av_base, + HDMI_CORE_AV_AUDIO_CHSUM, checksum); + + /* + * TODO: Add MPEG and SPD enable and repeat cfg when EDID parsing + * is available. + */ +} + +int hdmi4_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp, + struct omap_dss_audio *audio, u32 pclk) +{ + struct hdmi_audio_format audio_format; + struct hdmi_audio_dma audio_dma; + struct hdmi_core_audio_config acore; + int err, n, cts, channel_count; + unsigned int fs_nr; + bool word_length_16b = false; + + if (!audio || !audio->iec || !audio->cea || !core) + return -EINVAL; + + acore.iec60958_cfg = audio->iec; + /* + * In the IEC-60958 status word, check if the audio sample word length + * is 16-bit as several optimizations can be performed in such case. + */ + if (!(audio->iec->status[4] & IEC958_AES4_CON_MAX_WORDLEN_24)) + if (audio->iec->status[4] & IEC958_AES4_CON_WORDLEN_20_16) + word_length_16b = true; + + /* I2S configuration. See Phillips' specification */ + if (word_length_16b) + acore.i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_LEFT; + else + acore.i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_RIGHT; + /* + * The I2S input word length is twice the lenght given in the IEC-60958 + * status word. If the word size is greater than + * 20 bits, increment by one. + */ + acore.i2s_cfg.in_length_bits = audio->iec->status[4] + & IEC958_AES4_CON_WORDLEN; + if (audio->iec->status[4] & IEC958_AES4_CON_MAX_WORDLEN_24) + acore.i2s_cfg.in_length_bits++; + acore.i2s_cfg.sck_edge_mode = HDMI_AUDIO_I2S_SCK_EDGE_RISING; + acore.i2s_cfg.vbit = HDMI_AUDIO_I2S_VBIT_FOR_PCM; + acore.i2s_cfg.direction = HDMI_AUDIO_I2S_MSB_SHIFTED_FIRST; + acore.i2s_cfg.shift = HDMI_AUDIO_I2S_FIRST_BIT_SHIFT; + + /* convert sample frequency to a number */ + switch (audio->iec->status[3] & IEC958_AES3_CON_FS) { + case IEC958_AES3_CON_FS_32000: + fs_nr = 32000; + break; + case IEC958_AES3_CON_FS_44100: + fs_nr = 44100; + break; + case IEC958_AES3_CON_FS_48000: + fs_nr = 48000; + break; + case IEC958_AES3_CON_FS_88200: + fs_nr = 88200; + break; + case IEC958_AES3_CON_FS_96000: + fs_nr = 96000; + break; + case IEC958_AES3_CON_FS_176400: + fs_nr = 176400; + break; + case IEC958_AES3_CON_FS_192000: + fs_nr = 192000; + break; + default: + return -EINVAL; + } + + err = hdmi_compute_acr(pclk, fs_nr, &n, &cts); + + /* Audio clock regeneration settings */ + acore.n = n; + acore.cts = cts; + if (dss_has_feature(FEAT_HDMI_CTS_SWMODE)) { + acore.aud_par_busclk = 0; + acore.cts_mode = HDMI_AUDIO_CTS_MODE_SW; + acore.use_mclk = dss_has_feature(FEAT_HDMI_AUDIO_USE_MCLK); + } else { + acore.aud_par_busclk = (((128 * 31) - 1) << 8); + acore.cts_mode = HDMI_AUDIO_CTS_MODE_HW; + acore.use_mclk = true; + } + + if (acore.use_mclk) + acore.mclk_mode = HDMI_AUDIO_MCLK_128FS; + + /* Audio channels settings */ + channel_count = (audio->cea->db1_ct_cc & + CEA861_AUDIO_INFOFRAME_DB1CC) + 1; + + switch (channel_count) { + case 2: + audio_format.active_chnnls_msk = 0x03; + break; + case 3: + audio_format.active_chnnls_msk = 0x07; + break; + case 4: + audio_format.active_chnnls_msk = 0x0f; + break; + case 5: + audio_format.active_chnnls_msk = 0x1f; + break; + case 6: + audio_format.active_chnnls_msk = 0x3f; + break; + case 7: + audio_format.active_chnnls_msk = 0x7f; + break; + case 8: + audio_format.active_chnnls_msk = 0xff; + break; + default: + return -EINVAL; + } + + /* + * the HDMI IP needs to enable four stereo channels when transmitting + * more than 2 audio channels + */ + if (channel_count == 2) { + audio_format.stereo_channels = HDMI_AUDIO_STEREO_ONECHANNEL; + acore.i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN; + acore.layout = HDMI_AUDIO_LAYOUT_2CH; + } else { + audio_format.stereo_channels = HDMI_AUDIO_STEREO_FOURCHANNELS; + acore.i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN | + HDMI_AUDIO_I2S_SD1_EN | HDMI_AUDIO_I2S_SD2_EN | + HDMI_AUDIO_I2S_SD3_EN; + acore.layout = HDMI_AUDIO_LAYOUT_8CH; + } + + acore.en_spdif = false; + /* use sample frequency from channel status word */ + acore.fs_override = true; + /* enable ACR packets */ + acore.en_acr_pkt = true; + /* disable direct streaming digital audio */ + acore.en_dsd_audio = false; + /* use parallel audio interface */ + acore.en_parallel_aud_input = true; + + /* DMA settings */ + if (word_length_16b) + audio_dma.transfer_size = 0x10; + else + audio_dma.transfer_size = 0x20; + audio_dma.block_size = 0xC0; + audio_dma.mode = HDMI_AUDIO_TRANSF_DMA; + audio_dma.fifo_threshold = 0x20; /* in number of samples */ + + /* audio FIFO format settings */ + if (word_length_16b) { + audio_format.samples_per_word = HDMI_AUDIO_ONEWORD_TWOSAMPLES; + audio_format.sample_size = HDMI_AUDIO_SAMPLE_16BITS; + audio_format.justification = HDMI_AUDIO_JUSTIFY_LEFT; + } else { + audio_format.samples_per_word = HDMI_AUDIO_ONEWORD_ONESAMPLE; + audio_format.sample_size = HDMI_AUDIO_SAMPLE_24BITS; + audio_format.justification = HDMI_AUDIO_JUSTIFY_RIGHT; + } + audio_format.type = HDMI_AUDIO_TYPE_LPCM; + audio_format.sample_order = HDMI_AUDIO_SAMPLE_LEFT_FIRST; + /* disable start/stop signals of IEC 60958 blocks */ + audio_format.en_sig_blk_strt_end = HDMI_AUDIO_BLOCK_SIG_STARTEND_ON; + + /* configure DMA and audio FIFO format*/ + hdmi_wp_audio_config_dma(wp, &audio_dma); + hdmi_wp_audio_config_format(wp, &audio_format); + + /* configure the core*/ + hdmi_core_audio_config(core, &acore); + + /* configure CEA 861 audio infoframe*/ + hdmi_core_audio_infoframe_cfg(core, audio->cea); + + return 0; +} + +int hdmi4_audio_start(struct hdmi_core_data *core, struct hdmi_wp_data *wp) +{ + REG_FLD_MOD(hdmi_av_base(core), + HDMI_CORE_AV_AUD_MODE, true, 0, 0); + + hdmi_wp_audio_core_req_enable(wp, true); + + return 0; +} + +void hdmi4_audio_stop(struct hdmi_core_data *core, struct hdmi_wp_data *wp) +{ + REG_FLD_MOD(hdmi_av_base(core), + HDMI_CORE_AV_AUD_MODE, false, 0, 0); + + hdmi_wp_audio_core_req_enable(wp, false); +} + +int hdmi4_audio_get_dma_port(u32 *offset, u32 *size) +{ + if (!offset || !size) + return -EINVAL; + *offset = HDMI_WP_AUDIO_DATA; + *size = 4; + return 0; +} + +#endif + +#define CORE_OFFSET 0x400 +#define CORE_SIZE 0xc00 + +int hdmi4_core_init(struct platform_device *pdev, struct hdmi_core_data *core) +{ + struct resource *res; + struct resource temp_res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!res) { + DSSDBG("can't get CORE mem resource by name\n"); + /* + * if hwmod/DT doesn't have the memory resource information + * split into HDMI sub blocks by name, we try again by getting + * the platform's first resource. this code will be removed when + * the driver can get the mem resources by name + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DSSERR("can't get CORE mem resource\n"); + return -EINVAL; + } + + temp_res.start = res->start + CORE_OFFSET; + temp_res.end = temp_res.start + CORE_SIZE - 1; + res = &temp_res; + } + + core->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!core->base) { + DSSERR("can't ioremap CORE\n"); + return -ENOMEM; + } + + return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi4_core.h b/drivers/video/fbdev/omap2/dss/hdmi4_core.h new file mode 100644 index 000000000000..bb646896fa82 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi4_core.h @@ -0,0 +1,276 @@ +/* + * HDMI header definition for OMAP4 HDMI core IP + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _HDMI4_CORE_H_ +#define _HDMI4_CORE_H_ + +#include "hdmi.h" + +/* OMAP4 HDMI IP Core System */ + +#define HDMI_CORE_SYS_VND_IDL 0x0 +#define HDMI_CORE_SYS_DEV_IDL 0x8 +#define HDMI_CORE_SYS_DEV_IDH 0xC +#define HDMI_CORE_SYS_DEV_REV 0x10 +#define HDMI_CORE_SYS_SRST 0x14 +#define HDMI_CORE_SYS_SYS_CTRL1 0x20 +#define HDMI_CORE_SYS_SYS_STAT 0x24 +#define HDMI_CORE_SYS_SYS_CTRL3 0x28 +#define HDMI_CORE_SYS_DCTL 0x34 +#define HDMI_CORE_SYS_DE_DLY 0xC8 +#define HDMI_CORE_SYS_DE_CTRL 0xCC +#define HDMI_CORE_SYS_DE_TOP 0xD0 +#define HDMI_CORE_SYS_DE_CNTL 0xD8 +#define HDMI_CORE_SYS_DE_CNTH 0xDC +#define HDMI_CORE_SYS_DE_LINL 0xE0 +#define HDMI_CORE_SYS_DE_LINH_1 0xE4 +#define HDMI_CORE_SYS_HRES_L 0xE8 +#define HDMI_CORE_SYS_HRES_H 0xEC +#define HDMI_CORE_SYS_VRES_L 0xF0 +#define HDMI_CORE_SYS_VRES_H 0xF4 +#define HDMI_CORE_SYS_IADJUST 0xF8 +#define HDMI_CORE_SYS_POLDETECT 0xFC +#define HDMI_CORE_SYS_HWIDTH1 0x110 +#define HDMI_CORE_SYS_HWIDTH2 0x114 +#define HDMI_CORE_SYS_VWIDTH 0x11C +#define HDMI_CORE_SYS_VID_CTRL 0x120 +#define HDMI_CORE_SYS_VID_ACEN 0x124 +#define HDMI_CORE_SYS_VID_MODE 0x128 +#define HDMI_CORE_SYS_VID_BLANK1 0x12C +#define HDMI_CORE_SYS_VID_BLANK2 0x130 +#define HDMI_CORE_SYS_VID_BLANK3 0x134 +#define HDMI_CORE_SYS_DC_HEADER 0x138 +#define HDMI_CORE_SYS_VID_DITHER 0x13C +#define HDMI_CORE_SYS_RGB2XVYCC_CT 0x140 +#define HDMI_CORE_SYS_R2Y_COEFF_LOW 0x144 +#define HDMI_CORE_SYS_R2Y_COEFF_UP 0x148 +#define HDMI_CORE_SYS_G2Y_COEFF_LOW 0x14C +#define HDMI_CORE_SYS_G2Y_COEFF_UP 0x150 +#define HDMI_CORE_SYS_B2Y_COEFF_LOW 0x154 +#define HDMI_CORE_SYS_B2Y_COEFF_UP 0x158 +#define HDMI_CORE_SYS_R2CB_COEFF_LOW 0x15C +#define HDMI_CORE_SYS_R2CB_COEFF_UP 0x160 +#define HDMI_CORE_SYS_G2CB_COEFF_LOW 0x164 +#define HDMI_CORE_SYS_G2CB_COEFF_UP 0x168 +#define HDMI_CORE_SYS_B2CB_COEFF_LOW 0x16C +#define HDMI_CORE_SYS_B2CB_COEFF_UP 0x170 +#define HDMI_CORE_SYS_R2CR_COEFF_LOW 0x174 +#define HDMI_CORE_SYS_R2CR_COEFF_UP 0x178 +#define HDMI_CORE_SYS_G2CR_COEFF_LOW 0x17C +#define HDMI_CORE_SYS_G2CR_COEFF_UP 0x180 +#define HDMI_CORE_SYS_B2CR_COEFF_LOW 0x184 +#define HDMI_CORE_SYS_B2CR_COEFF_UP 0x188 +#define HDMI_CORE_SYS_RGB_OFFSET_LOW 0x18C +#define HDMI_CORE_SYS_RGB_OFFSET_UP 0x190 +#define HDMI_CORE_SYS_Y_OFFSET_LOW 0x194 +#define HDMI_CORE_SYS_Y_OFFSET_UP 0x198 +#define HDMI_CORE_SYS_CBCR_OFFSET_LOW 0x19C +#define HDMI_CORE_SYS_CBCR_OFFSET_UP 0x1A0 +#define HDMI_CORE_SYS_INTR_STATE 0x1C0 +#define HDMI_CORE_SYS_INTR1 0x1C4 +#define HDMI_CORE_SYS_INTR2 0x1C8 +#define HDMI_CORE_SYS_INTR3 0x1CC +#define HDMI_CORE_SYS_INTR4 0x1D0 +#define HDMI_CORE_SYS_INTR_UNMASK1 0x1D4 +#define HDMI_CORE_SYS_INTR_UNMASK2 0x1D8 +#define HDMI_CORE_SYS_INTR_UNMASK3 0x1DC +#define HDMI_CORE_SYS_INTR_UNMASK4 0x1E0 +#define HDMI_CORE_SYS_INTR_CTRL 0x1E4 +#define HDMI_CORE_SYS_TMDS_CTRL 0x208 + +/* value definitions for HDMI_CORE_SYS_SYS_CTRL1 fields */ +#define HDMI_CORE_SYS_SYS_CTRL1_VEN_FOLLOWVSYNC 0x1 +#define HDMI_CORE_SYS_SYS_CTRL1_HEN_FOLLOWHSYNC 0x1 +#define HDMI_CORE_SYS_SYS_CTRL1_BSEL_24BITBUS 0x1 +#define HDMI_CORE_SYS_SYS_CTRL1_EDGE_RISINGEDGE 0x1 + +/* HDMI DDC E-DID */ +#define HDMI_CORE_DDC_ADDR 0x3B4 +#define HDMI_CORE_DDC_SEGM 0x3B8 +#define HDMI_CORE_DDC_OFFSET 0x3BC +#define HDMI_CORE_DDC_COUNT1 0x3C0 +#define HDMI_CORE_DDC_COUNT2 0x3C4 +#define HDMI_CORE_DDC_STATUS 0x3C8 +#define HDMI_CORE_DDC_CMD 0x3CC +#define HDMI_CORE_DDC_DATA 0x3D0 + +/* HDMI IP Core Audio Video */ + +#define HDMI_CORE_AV_ACR_CTRL 0x4 +#define HDMI_CORE_AV_FREQ_SVAL 0x8 +#define HDMI_CORE_AV_N_SVAL1 0xC +#define HDMI_CORE_AV_N_SVAL2 0x10 +#define HDMI_CORE_AV_N_SVAL3 0x14 +#define HDMI_CORE_AV_CTS_SVAL1 0x18 +#define HDMI_CORE_AV_CTS_SVAL2 0x1C +#define HDMI_CORE_AV_CTS_SVAL3 0x20 +#define HDMI_CORE_AV_CTS_HVAL1 0x24 +#define HDMI_CORE_AV_CTS_HVAL2 0x28 +#define HDMI_CORE_AV_CTS_HVAL3 0x2C +#define HDMI_CORE_AV_AUD_MODE 0x50 +#define HDMI_CORE_AV_SPDIF_CTRL 0x54 +#define HDMI_CORE_AV_HW_SPDIF_FS 0x60 +#define HDMI_CORE_AV_SWAP_I2S 0x64 +#define HDMI_CORE_AV_SPDIF_ERTH 0x6C +#define HDMI_CORE_AV_I2S_IN_MAP 0x70 +#define HDMI_CORE_AV_I2S_IN_CTRL 0x74 +#define HDMI_CORE_AV_I2S_CHST0 0x78 +#define HDMI_CORE_AV_I2S_CHST1 0x7C +#define HDMI_CORE_AV_I2S_CHST2 0x80 +#define HDMI_CORE_AV_I2S_CHST4 0x84 +#define HDMI_CORE_AV_I2S_CHST5 0x88 +#define HDMI_CORE_AV_ASRC 0x8C +#define HDMI_CORE_AV_I2S_IN_LEN 0x90 +#define HDMI_CORE_AV_HDMI_CTRL 0xBC +#define HDMI_CORE_AV_AUDO_TXSTAT 0xC0 +#define HDMI_CORE_AV_AUD_PAR_BUSCLK_1 0xCC +#define HDMI_CORE_AV_AUD_PAR_BUSCLK_2 0xD0 +#define HDMI_CORE_AV_AUD_PAR_BUSCLK_3 0xD4 +#define HDMI_CORE_AV_TEST_TXCTRL 0xF0 +#define HDMI_CORE_AV_DPD 0xF4 +#define HDMI_CORE_AV_PB_CTRL1 0xF8 +#define HDMI_CORE_AV_PB_CTRL2 0xFC +#define HDMI_CORE_AV_AVI_TYPE 0x100 +#define HDMI_CORE_AV_AVI_VERS 0x104 +#define HDMI_CORE_AV_AVI_LEN 0x108 +#define HDMI_CORE_AV_AVI_CHSUM 0x10C +#define HDMI_CORE_AV_AVI_DBYTE(n) (n * 4 + 0x110) +#define HDMI_CORE_AV_SPD_TYPE 0x180 +#define HDMI_CORE_AV_SPD_VERS 0x184 +#define HDMI_CORE_AV_SPD_LEN 0x188 +#define HDMI_CORE_AV_SPD_CHSUM 0x18C +#define HDMI_CORE_AV_SPD_DBYTE(n) (n * 4 + 0x190) +#define HDMI_CORE_AV_AUDIO_TYPE 0x200 +#define HDMI_CORE_AV_AUDIO_VERS 0x204 +#define HDMI_CORE_AV_AUDIO_LEN 0x208 +#define HDMI_CORE_AV_AUDIO_CHSUM 0x20C +#define HDMI_CORE_AV_AUD_DBYTE(n) (n * 4 + 0x210) +#define HDMI_CORE_AV_MPEG_TYPE 0x280 +#define HDMI_CORE_AV_MPEG_VERS 0x284 +#define HDMI_CORE_AV_MPEG_LEN 0x288 +#define HDMI_CORE_AV_MPEG_CHSUM 0x28C +#define HDMI_CORE_AV_MPEG_DBYTE(n) (n * 4 + 0x290) +#define HDMI_CORE_AV_GEN_DBYTE(n) (n * 4 + 0x300) +#define HDMI_CORE_AV_CP_BYTE1 0x37C +#define HDMI_CORE_AV_GEN2_DBYTE(n) (n * 4 + 0x380) +#define HDMI_CORE_AV_CEC_ADDR_ID 0x3FC + +#define HDMI_CORE_AV_SPD_DBYTE_ELSIZE 0x4 +#define HDMI_CORE_AV_GEN2_DBYTE_ELSIZE 0x4 +#define HDMI_CORE_AV_MPEG_DBYTE_ELSIZE 0x4 +#define HDMI_CORE_AV_GEN_DBYTE_ELSIZE 0x4 + +#define HDMI_CORE_AV_AVI_DBYTE_NELEMS 15 +#define HDMI_CORE_AV_SPD_DBYTE_NELEMS 27 +#define HDMI_CORE_AV_AUD_DBYTE_NELEMS 10 +#define HDMI_CORE_AV_MPEG_DBYTE_NELEMS 27 +#define HDMI_CORE_AV_GEN_DBYTE_NELEMS 31 +#define HDMI_CORE_AV_GEN2_DBYTE_NELEMS 31 + +enum hdmi_core_inputbus_width { + HDMI_INPUT_8BIT = 0, + HDMI_INPUT_10BIT = 1, + HDMI_INPUT_12BIT = 2 +}; + +enum hdmi_core_dither_trunc { + HDMI_OUTPUTTRUNCATION_8BIT = 0, + HDMI_OUTPUTTRUNCATION_10BIT = 1, + HDMI_OUTPUTTRUNCATION_12BIT = 2, + HDMI_OUTPUTDITHER_8BIT = 3, + HDMI_OUTPUTDITHER_10BIT = 4, + HDMI_OUTPUTDITHER_12BIT = 5 +}; + +enum hdmi_core_deepcolor_ed { + HDMI_DEEPCOLORPACKECTDISABLE = 0, + HDMI_DEEPCOLORPACKECTENABLE = 1 +}; + +enum hdmi_core_packet_mode { + HDMI_PACKETMODERESERVEDVALUE = 0, + HDMI_PACKETMODE24BITPERPIXEL = 4, + HDMI_PACKETMODE30BITPERPIXEL = 5, + HDMI_PACKETMODE36BITPERPIXEL = 6, + HDMI_PACKETMODE48BITPERPIXEL = 7 +}; + +enum hdmi_core_tclkselclkmult { + HDMI_FPLL05IDCK = 0, + HDMI_FPLL10IDCK = 1, + HDMI_FPLL20IDCK = 2, + HDMI_FPLL40IDCK = 3 +}; + +enum hdmi_core_packet_ctrl { + HDMI_PACKETENABLE = 1, + HDMI_PACKETDISABLE = 0, + HDMI_PACKETREPEATON = 1, + HDMI_PACKETREPEATOFF = 0 +}; + +enum hdmi_audio_i2s_config { + HDMI_AUDIO_I2S_MSB_SHIFTED_FIRST = 0, + HDMI_AUDIO_I2S_LSB_SHIFTED_FIRST = 1, + HDMI_AUDIO_I2S_SCK_EDGE_FALLING = 0, + HDMI_AUDIO_I2S_SCK_EDGE_RISING = 1, + HDMI_AUDIO_I2S_VBIT_FOR_PCM = 0, + HDMI_AUDIO_I2S_VBIT_FOR_COMPRESSED = 1, + HDMI_AUDIO_I2S_FIRST_BIT_SHIFT = 0, + HDMI_AUDIO_I2S_FIRST_BIT_NO_SHIFT = 1, + HDMI_AUDIO_I2S_SD0_EN = 1, + HDMI_AUDIO_I2S_SD1_EN = 1 << 1, + HDMI_AUDIO_I2S_SD2_EN = 1 << 2, + HDMI_AUDIO_I2S_SD3_EN = 1 << 3, +}; + +struct hdmi_core_video_config { + enum hdmi_core_inputbus_width ip_bus_width; + enum hdmi_core_dither_trunc op_dither_truc; + enum hdmi_core_deepcolor_ed deep_color_pkt; + enum hdmi_core_packet_mode pkt_mode; + enum hdmi_core_hdmi_dvi hdmi_dvi; + enum hdmi_core_tclkselclkmult tclk_sel_clkmult; +}; + +struct hdmi_core_packet_enable_repeat { + u32 audio_pkt; + u32 audio_pkt_repeat; + u32 avi_infoframe; + u32 avi_infoframe_repeat; + u32 gen_cntrl_pkt; + u32 gen_cntrl_pkt_repeat; + u32 generic_pkt; + u32 generic_pkt_repeat; +}; + +int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len); +void hdmi4_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp, + struct hdmi_config *cfg); +void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s); +int hdmi4_core_init(struct platform_device *pdev, struct hdmi_core_data *core); + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +int hdmi4_audio_start(struct hdmi_core_data *core, struct hdmi_wp_data *wp); +void hdmi4_audio_stop(struct hdmi_core_data *core, struct hdmi_wp_data *wp); +int hdmi4_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp, + struct omap_dss_audio *audio, u32 pclk); +int hdmi4_audio_get_dma_port(u32 *offset, u32 *size); +#endif + +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi_common.c b/drivers/video/fbdev/omap2/dss/hdmi_common.c new file mode 100644 index 000000000000..0b12a3f62fe1 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_common.c @@ -0,0 +1,425 @@ + +/* + * Logic for the below structure : + * user enters the CEA or VESA timings by specifying the HDMI/DVI code. + * There is a correspondence between CEA/VESA timing and code, please + * refer to section 6.3 in HDMI 1.3 specification for timing code. + * + * In the below structure, cea_vesa_timings corresponds to all OMAP4 + * supported CEA and VESA timing values.code_cea corresponds to the CEA + * code, It is used to get the timing from cea_vesa_timing array.Similarly + * with code_vesa. Code_index is used for back mapping, that is once EDID + * is read from the TV, EDID is parsed to find the timing values and then + * map it to corresponding CEA or VESA index. + */ + +#define DSS_SUBSYS_NAME "HDMI" + +#include <linux/kernel.h> +#include <linux/err.h> +#include <video/omapdss.h> + +#include "hdmi.h" + +static const struct hdmi_config cea_timings[] = { + { + { 640, 480, 25200000, 96, 16, 48, 2, 10, 33, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 1, HDMI_HDMI }, + }, + { + { 720, 480, 27027000, 62, 16, 60, 6, 9, 30, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 2, HDMI_HDMI }, + }, + { + { 1280, 720, 74250000, 40, 110, 220, 5, 5, 20, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 4, HDMI_HDMI }, + }, + { + { 1920, 540, 74250000, 44, 88, 148, 5, 2, 15, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + true, }, + { 5, HDMI_HDMI }, + }, + { + { 1440, 240, 27027000, 124, 38, 114, 3, 4, 15, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + true, }, + { 6, HDMI_HDMI }, + }, + { + { 1920, 1080, 148500000, 44, 88, 148, 5, 4, 36, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 16, HDMI_HDMI }, + }, + { + { 720, 576, 27000000, 64, 12, 68, 5, 5, 39, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 17, HDMI_HDMI }, + }, + { + { 1280, 720, 74250000, 40, 440, 220, 5, 5, 20, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 19, HDMI_HDMI }, + }, + { + { 1920, 540, 74250000, 44, 528, 148, 5, 2, 15, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + true, }, + { 20, HDMI_HDMI }, + }, + { + { 1440, 288, 27000000, 126, 24, 138, 3, 2, 19, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + true, }, + { 21, HDMI_HDMI }, + }, + { + { 1440, 576, 54000000, 128, 24, 136, 5, 5, 39, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 29, HDMI_HDMI }, + }, + { + { 1920, 1080, 148500000, 44, 528, 148, 5, 4, 36, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 31, HDMI_HDMI }, + }, + { + { 1920, 1080, 74250000, 44, 638, 148, 5, 4, 36, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 32, HDMI_HDMI }, + }, + { + { 2880, 480, 108108000, 248, 64, 240, 6, 9, 30, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 35, HDMI_HDMI }, + }, + { + { 2880, 576, 108000000, 256, 48, 272, 5, 5, 39, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 37, HDMI_HDMI }, + }, +}; + +static const struct hdmi_config vesa_timings[] = { +/* VESA From Here */ + { + { 640, 480, 25175000, 96, 16, 48, 2, 11, 31, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 4, HDMI_DVI }, + }, + { + { 800, 600, 40000000, 128, 40, 88, 4, 1, 23, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 9, HDMI_DVI }, + }, + { + { 848, 480, 33750000, 112, 16, 112, 8, 6, 23, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0xE, HDMI_DVI }, + }, + { + { 1280, 768, 79500000, 128, 64, 192, 7, 3, 20, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 0x17, HDMI_DVI }, + }, + { + { 1280, 800, 83500000, 128, 72, 200, 6, 3, 22, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 0x1C, HDMI_DVI }, + }, + { + { 1360, 768, 85500000, 112, 64, 256, 6, 3, 18, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x27, HDMI_DVI }, + }, + { + { 1280, 960, 108000000, 112, 96, 312, 3, 1, 36, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x20, HDMI_DVI }, + }, + { + { 1280, 1024, 108000000, 112, 48, 248, 3, 1, 38, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x23, HDMI_DVI }, + }, + { + { 1024, 768, 65000000, 136, 24, 160, 6, 3, 29, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 0x10, HDMI_DVI }, + }, + { + { 1400, 1050, 121750000, 144, 88, 232, 4, 3, 32, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 0x2A, HDMI_DVI }, + }, + { + { 1440, 900, 106500000, 152, 80, 232, 6, 3, 25, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 0x2F, HDMI_DVI }, + }, + { + { 1680, 1050, 146250000, 176 , 104, 280, 6, 3, 30, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, + false, }, + { 0x3A, HDMI_DVI }, + }, + { + { 1366, 768, 85500000, 143, 70, 213, 3, 3, 24, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x51, HDMI_DVI }, + }, + { + { 1920, 1080, 148500000, 44, 148, 80, 5, 4, 36, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x52, HDMI_DVI }, + }, + { + { 1280, 768, 68250000, 32, 48, 80, 7, 3, 12, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x16, HDMI_DVI }, + }, + { + { 1400, 1050, 101000000, 32, 48, 80, 4, 3, 23, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x29, HDMI_DVI }, + }, + { + { 1680, 1050, 119000000, 32, 48, 80, 6, 3, 21, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x39, HDMI_DVI }, + }, + { + { 1280, 800, 79500000, 32, 48, 80, 6, 3, 14, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x1B, HDMI_DVI }, + }, + { + { 1280, 720, 74250000, 40, 110, 220, 5, 5, 20, + OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x55, HDMI_DVI }, + }, + { + { 1920, 1200, 154000000, 32, 48, 80, 6, 3, 26, + OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, + false, }, + { 0x44, HDMI_DVI }, + }, +}; + +const struct hdmi_config *hdmi_default_timing(void) +{ + return &vesa_timings[0]; +} + +static const struct hdmi_config *hdmi_find_timing(int code, + const struct hdmi_config *timings_arr, int len) +{ + int i; + + for (i = 0; i < len; i++) { + if (timings_arr[i].cm.code == code) + return &timings_arr[i]; + } + + return NULL; +} + +const struct hdmi_config *hdmi_get_timings(int mode, int code) +{ + const struct hdmi_config *arr; + int len; + + if (mode == HDMI_DVI) { + arr = vesa_timings; + len = ARRAY_SIZE(vesa_timings); + } else { + arr = cea_timings; + len = ARRAY_SIZE(cea_timings); + } + + return hdmi_find_timing(code, arr, len); +} + +static bool hdmi_timings_compare(struct omap_video_timings *timing1, + const struct omap_video_timings *timing2) +{ + int timing1_vsync, timing1_hsync, timing2_vsync, timing2_hsync; + + if ((DIV_ROUND_CLOSEST(timing2->pixelclock, 1000000) == + DIV_ROUND_CLOSEST(timing1->pixelclock, 1000000)) && + (timing2->x_res == timing1->x_res) && + (timing2->y_res == timing1->y_res)) { + + timing2_hsync = timing2->hfp + timing2->hsw + timing2->hbp; + timing1_hsync = timing1->hfp + timing1->hsw + timing1->hbp; + timing2_vsync = timing2->vfp + timing2->vsw + timing2->vbp; + timing1_vsync = timing1->vfp + timing1->vsw + timing1->vbp; + + DSSDBG("timing1_hsync = %d timing1_vsync = %d"\ + "timing2_hsync = %d timing2_vsync = %d\n", + timing1_hsync, timing1_vsync, + timing2_hsync, timing2_vsync); + + if ((timing1_hsync == timing2_hsync) && + (timing1_vsync == timing2_vsync)) { + return true; + } + } + return false; +} + +struct hdmi_cm hdmi_get_code(struct omap_video_timings *timing) +{ + int i; + struct hdmi_cm cm = {-1}; + DSSDBG("hdmi_get_code\n"); + + for (i = 0; i < ARRAY_SIZE(cea_timings); i++) { + if (hdmi_timings_compare(timing, &cea_timings[i].timings)) { + cm = cea_timings[i].cm; + goto end; + } + } + for (i = 0; i < ARRAY_SIZE(vesa_timings); i++) { + if (hdmi_timings_compare(timing, &vesa_timings[i].timings)) { + cm = vesa_timings[i].cm; + goto end; + } + } + +end: + return cm; +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +int hdmi_compute_acr(u32 pclk, u32 sample_freq, u32 *n, u32 *cts) +{ + u32 deep_color; + bool deep_color_correct = false; + + if (n == NULL || cts == NULL) + return -EINVAL; + + /* TODO: When implemented, query deep color mode here. */ + deep_color = 100; + + /* + * When using deep color, the default N value (as in the HDMI + * specification) yields to an non-integer CTS. Hence, we + * modify it while keeping the restrictions described in + * section 7.2.1 of the HDMI 1.4a specification. + */ + switch (sample_freq) { + case 32000: + case 48000: + case 96000: + case 192000: + if (deep_color == 125) + if (pclk == 27027000 || pclk == 74250000) + deep_color_correct = true; + if (deep_color == 150) + if (pclk == 27027000) + deep_color_correct = true; + break; + case 44100: + case 88200: + case 176400: + if (deep_color == 125) + if (pclk == 27027000) + deep_color_correct = true; + break; + default: + return -EINVAL; + } + + if (deep_color_correct) { + switch (sample_freq) { + case 32000: + *n = 8192; + break; + case 44100: + *n = 12544; + break; + case 48000: + *n = 8192; + break; + case 88200: + *n = 25088; + break; + case 96000: + *n = 16384; + break; + case 176400: + *n = 50176; + break; + case 192000: + *n = 32768; + break; + default: + return -EINVAL; + } + } else { + switch (sample_freq) { + case 32000: + *n = 4096; + break; + case 44100: + *n = 6272; + break; + case 48000: + *n = 6144; + break; + case 88200: + *n = 12544; + break; + case 96000: + *n = 12288; + break; + case 176400: + *n = 25088; + break; + case 192000: + *n = 24576; + break; + default: + return -EINVAL; + } + } + /* Calculate CTS. See HDMI 1.3a or 1.4a specifications */ + *cts = (pclk/1000) * (*n / 128) * deep_color / (sample_freq / 10); + + return 0; +} +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi_phy.c b/drivers/video/fbdev/omap2/dss/hdmi_phy.c new file mode 100644 index 000000000000..dd376ce8da01 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_phy.c @@ -0,0 +1,160 @@ +/* + * HDMI PHY + * + * Copyright (C) 2013 Texas Instruments Incorporated + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <video/omapdss.h> + +#include "dss.h" +#include "hdmi.h" + +void hdmi_phy_dump(struct hdmi_phy_data *phy, struct seq_file *s) +{ +#define DUMPPHY(r) seq_printf(s, "%-35s %08x\n", #r,\ + hdmi_read_reg(phy->base, r)) + + DUMPPHY(HDMI_TXPHY_TX_CTRL); + DUMPPHY(HDMI_TXPHY_DIGITAL_CTRL); + DUMPPHY(HDMI_TXPHY_POWER_CTRL); + DUMPPHY(HDMI_TXPHY_PAD_CFG_CTRL); +} + +static irqreturn_t hdmi_irq_handler(int irq, void *data) +{ + struct hdmi_wp_data *wp = data; + u32 irqstatus; + + irqstatus = hdmi_wp_get_irqstatus(wp); + hdmi_wp_set_irqstatus(wp, irqstatus); + + if ((irqstatus & HDMI_IRQ_LINK_CONNECT) && + irqstatus & HDMI_IRQ_LINK_DISCONNECT) { + /* + * If we get both connect and disconnect interrupts at the same + * time, turn off the PHY, clear interrupts, and restart, which + * raises connect interrupt if a cable is connected, or nothing + * if cable is not connected. + */ + hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); + + hdmi_wp_set_irqstatus(wp, HDMI_IRQ_LINK_CONNECT | + HDMI_IRQ_LINK_DISCONNECT); + + hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); + } else if (irqstatus & HDMI_IRQ_LINK_CONNECT) { + hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_TXON); + } else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) { + hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); + } + + return IRQ_HANDLED; +} + +int hdmi_phy_enable(struct hdmi_phy_data *phy, struct hdmi_wp_data *wp, + struct hdmi_config *cfg) +{ + u16 r = 0; + u32 irqstatus; + + hdmi_wp_clear_irqenable(wp, 0xffffffff); + + irqstatus = hdmi_wp_get_irqstatus(wp); + hdmi_wp_set_irqstatus(wp, irqstatus); + + r = hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); + if (r) + return r; + + /* + * Read address 0 in order to get the SCP reset done completed + * Dummy access performed to make sure reset is done + */ + hdmi_read_reg(phy->base, HDMI_TXPHY_TX_CTRL); + + /* + * Write to phy address 0 to configure the clock + * use HFBITCLK write HDMI_TXPHY_TX_CONTROL_FREQOUT field + */ + REG_FLD_MOD(phy->base, HDMI_TXPHY_TX_CTRL, 0x1, 31, 30); + + /* Write to phy address 1 to start HDMI line (TXVALID and TMDSCLKEN) */ + hdmi_write_reg(phy->base, HDMI_TXPHY_DIGITAL_CTRL, 0xF0000000); + + /* Setup max LDO voltage */ + REG_FLD_MOD(phy->base, HDMI_TXPHY_POWER_CTRL, 0xB, 3, 0); + + /* Write to phy address 3 to change the polarity control */ + REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, 0x1, 27, 27); + + r = request_threaded_irq(phy->irq, NULL, hdmi_irq_handler, + IRQF_ONESHOT, "OMAP HDMI", wp); + if (r) { + DSSERR("HDMI IRQ request failed\n"); + hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); + return r; + } + + hdmi_wp_set_irqenable(wp, + HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT); + + return 0; +} + +void hdmi_phy_disable(struct hdmi_phy_data *phy, struct hdmi_wp_data *wp) +{ + free_irq(phy->irq, wp); + + hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); +} + +#define PHY_OFFSET 0x300 +#define PHY_SIZE 0x100 + +int hdmi_phy_init(struct platform_device *pdev, struct hdmi_phy_data *phy) +{ + struct resource *res; + struct resource temp_res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); + if (!res) { + DSSDBG("can't get PHY mem resource by name\n"); + /* + * if hwmod/DT doesn't have the memory resource information + * split into HDMI sub blocks by name, we try again by getting + * the platform's first resource. this code will be removed when + * the driver can get the mem resources by name + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DSSERR("can't get PHY mem resource\n"); + return -EINVAL; + } + + temp_res.start = res->start + PHY_OFFSET; + temp_res.end = temp_res.start + PHY_SIZE - 1; + res = &temp_res; + } + + phy->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!phy->base) { + DSSERR("can't ioremap TX PHY\n"); + return -ENOMEM; + } + + phy->irq = platform_get_irq(pdev, 0); + if (phy->irq < 0) { + DSSERR("platform_get_irq failed\n"); + return -ENODEV; + } + + return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi_pll.c b/drivers/video/fbdev/omap2/dss/hdmi_pll.c new file mode 100644 index 000000000000..5fc71215c303 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_pll.c @@ -0,0 +1,232 @@ +/* + * HDMI PLL + * + * Copyright (C) 2013 Texas Instruments Incorporated + * + * 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. + */ + +#define DSS_SUBSYS_NAME "HDMIPLL" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <video/omapdss.h> + +#include "dss.h" +#include "hdmi.h" + +#define HDMI_DEFAULT_REGN 16 +#define HDMI_DEFAULT_REGM2 1 + +void hdmi_pll_dump(struct hdmi_pll_data *pll, struct seq_file *s) +{ +#define DUMPPLL(r) seq_printf(s, "%-35s %08x\n", #r,\ + hdmi_read_reg(pll->base, r)) + + DUMPPLL(PLLCTRL_PLL_CONTROL); + DUMPPLL(PLLCTRL_PLL_STATUS); + DUMPPLL(PLLCTRL_PLL_GO); + DUMPPLL(PLLCTRL_CFG1); + DUMPPLL(PLLCTRL_CFG2); + DUMPPLL(PLLCTRL_CFG3); + DUMPPLL(PLLCTRL_SSC_CFG1); + DUMPPLL(PLLCTRL_SSC_CFG2); + DUMPPLL(PLLCTRL_CFG4); +} + +void hdmi_pll_compute(struct hdmi_pll_data *pll, unsigned long clkin, int phy) +{ + struct hdmi_pll_info *pi = &pll->info; + unsigned long refclk; + u32 mf; + + /* use our funky units */ + clkin /= 10000; + + /* + * Input clock is predivided by N + 1 + * out put of which is reference clk + */ + + pi->regn = HDMI_DEFAULT_REGN; + + refclk = clkin / pi->regn; + + pi->regm2 = HDMI_DEFAULT_REGM2; + + /* + * multiplier is pixel_clk/ref_clk + * Multiplying by 100 to avoid fractional part removal + */ + pi->regm = phy * pi->regm2 / refclk; + + /* + * fractional multiplier is remainder of the difference between + * multiplier and actual phy(required pixel clock thus should be + * multiplied by 2^18(262144) divided by the reference clock + */ + mf = (phy - pi->regm / pi->regm2 * refclk) * 262144; + pi->regmf = pi->regm2 * mf / refclk; + + /* + * Dcofreq should be set to 1 if required pixel clock + * is greater than 1000MHz + */ + pi->dcofreq = phy > 1000 * 100; + pi->regsd = ((pi->regm * clkin / 10) / (pi->regn * 250) + 5) / 10; + + /* Set the reference clock to sysclk reference */ + pi->refsel = HDMI_REFSEL_SYSCLK; + + DSSDBG("M = %d Mf = %d\n", pi->regm, pi->regmf); + DSSDBG("range = %d sd = %d\n", pi->dcofreq, pi->regsd); +} + + +static int hdmi_pll_config(struct hdmi_pll_data *pll) +{ + u32 r; + struct hdmi_pll_info *fmt = &pll->info; + + /* PLL start always use manual mode */ + REG_FLD_MOD(pll->base, PLLCTRL_PLL_CONTROL, 0x0, 0, 0); + + r = hdmi_read_reg(pll->base, PLLCTRL_CFG1); + r = FLD_MOD(r, fmt->regm, 20, 9); /* CFG1_PLL_REGM */ + r = FLD_MOD(r, fmt->regn - 1, 8, 1); /* CFG1_PLL_REGN */ + hdmi_write_reg(pll->base, PLLCTRL_CFG1, r); + + r = hdmi_read_reg(pll->base, PLLCTRL_CFG2); + + r = FLD_MOD(r, 0x0, 12, 12); /* PLL_HIGHFREQ divide by 2 */ + r = FLD_MOD(r, 0x1, 13, 13); /* PLL_REFEN */ + r = FLD_MOD(r, 0x0, 14, 14); /* PHY_CLKINEN de-assert during locking */ + r = FLD_MOD(r, fmt->refsel, 22, 21); /* REFSEL */ + + if (fmt->dcofreq) { + /* divider programming for frequency beyond 1000Mhz */ + REG_FLD_MOD(pll->base, PLLCTRL_CFG3, fmt->regsd, 17, 10); + r = FLD_MOD(r, 0x4, 3, 1); /* 1000MHz and 2000MHz */ + } else { + r = FLD_MOD(r, 0x2, 3, 1); /* 500MHz and 1000MHz */ + } + + hdmi_write_reg(pll->base, PLLCTRL_CFG2, r); + + r = hdmi_read_reg(pll->base, PLLCTRL_CFG4); + r = FLD_MOD(r, fmt->regm2, 24, 18); + r = FLD_MOD(r, fmt->regmf, 17, 0); + hdmi_write_reg(pll->base, PLLCTRL_CFG4, r); + + /* go now */ + REG_FLD_MOD(pll->base, PLLCTRL_PLL_GO, 0x1, 0, 0); + + /* wait for bit change */ + if (hdmi_wait_for_bit_change(pll->base, PLLCTRL_PLL_GO, + 0, 0, 1) != 1) { + DSSERR("PLL GO bit not set\n"); + return -ETIMEDOUT; + } + + /* Wait till the lock bit is set in PLL status */ + if (hdmi_wait_for_bit_change(pll->base, + PLLCTRL_PLL_STATUS, 1, 1, 1) != 1) { + DSSERR("cannot lock PLL\n"); + DSSERR("CFG1 0x%x\n", + hdmi_read_reg(pll->base, PLLCTRL_CFG1)); + DSSERR("CFG2 0x%x\n", + hdmi_read_reg(pll->base, PLLCTRL_CFG2)); + DSSERR("CFG4 0x%x\n", + hdmi_read_reg(pll->base, PLLCTRL_CFG4)); + return -ETIMEDOUT; + } + + DSSDBG("PLL locked!\n"); + + return 0; +} + +static int hdmi_pll_reset(struct hdmi_pll_data *pll) +{ + /* SYSRESET controlled by power FSM */ + REG_FLD_MOD(pll->base, PLLCTRL_PLL_CONTROL, 0x0, 3, 3); + + /* READ 0x0 reset is in progress */ + if (hdmi_wait_for_bit_change(pll->base, PLLCTRL_PLL_STATUS, 0, 0, 1) + != 1) { + DSSERR("Failed to sysreset PLL\n"); + return -ETIMEDOUT; + } + + return 0; +} + +int hdmi_pll_enable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp) +{ + u16 r = 0; + + r = hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_ALLOFF); + if (r) + return r; + + r = hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_BOTHON_ALLCLKS); + if (r) + return r; + + r = hdmi_pll_reset(pll); + if (r) + return r; + + r = hdmi_pll_config(pll); + if (r) + return r; + + return 0; +} + +void hdmi_pll_disable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp) +{ + hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_ALLOFF); +} + +#define PLL_OFFSET 0x200 +#define PLL_SIZE 0x100 + +int hdmi_pll_init(struct platform_device *pdev, struct hdmi_pll_data *pll) +{ + struct resource *res; + struct resource temp_res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll"); + if (!res) { + DSSDBG("can't get PLL mem resource by name\n"); + /* + * if hwmod/DT doesn't have the memory resource information + * split into HDMI sub blocks by name, we try again by getting + * the platform's first resource. this code will be removed when + * the driver can get the mem resources by name + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DSSERR("can't get PLL mem resource\n"); + return -EINVAL; + } + + temp_res.start = res->start + PLL_OFFSET; + temp_res.end = temp_res.start + PLL_SIZE - 1; + res = &temp_res; + } + + pll->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!pll->base) { + DSSERR("can't ioremap PLLCTRL\n"); + return -ENOMEM; + } + + return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi_wp.c b/drivers/video/fbdev/omap2/dss/hdmi_wp.c new file mode 100644 index 000000000000..f5f4ccf50d90 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_wp.c @@ -0,0 +1,275 @@ +/* + * HDMI wrapper + * + * Copyright (C) 2013 Texas Instruments Incorporated + * + * 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. + */ + +#define DSS_SUBSYS_NAME "HDMIWP" + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <video/omapdss.h> + +#include "dss.h" +#include "hdmi.h" + +void hdmi_wp_dump(struct hdmi_wp_data *wp, struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, hdmi_read_reg(wp->base, r)) + + DUMPREG(HDMI_WP_REVISION); + DUMPREG(HDMI_WP_SYSCONFIG); + DUMPREG(HDMI_WP_IRQSTATUS_RAW); + DUMPREG(HDMI_WP_IRQSTATUS); + DUMPREG(HDMI_WP_IRQENABLE_SET); + DUMPREG(HDMI_WP_IRQENABLE_CLR); + DUMPREG(HDMI_WP_IRQWAKEEN); + DUMPREG(HDMI_WP_PWR_CTRL); + DUMPREG(HDMI_WP_DEBOUNCE); + DUMPREG(HDMI_WP_VIDEO_CFG); + DUMPREG(HDMI_WP_VIDEO_SIZE); + DUMPREG(HDMI_WP_VIDEO_TIMING_H); + DUMPREG(HDMI_WP_VIDEO_TIMING_V); + DUMPREG(HDMI_WP_CLK); + DUMPREG(HDMI_WP_AUDIO_CFG); + DUMPREG(HDMI_WP_AUDIO_CFG2); + DUMPREG(HDMI_WP_AUDIO_CTRL); + DUMPREG(HDMI_WP_AUDIO_DATA); +} + +u32 hdmi_wp_get_irqstatus(struct hdmi_wp_data *wp) +{ + return hdmi_read_reg(wp->base, HDMI_WP_IRQSTATUS); +} + +void hdmi_wp_set_irqstatus(struct hdmi_wp_data *wp, u32 irqstatus) +{ + hdmi_write_reg(wp->base, HDMI_WP_IRQSTATUS, irqstatus); + /* flush posted write */ + hdmi_read_reg(wp->base, HDMI_WP_IRQSTATUS); +} + +void hdmi_wp_set_irqenable(struct hdmi_wp_data *wp, u32 mask) +{ + hdmi_write_reg(wp->base, HDMI_WP_IRQENABLE_SET, mask); +} + +void hdmi_wp_clear_irqenable(struct hdmi_wp_data *wp, u32 mask) +{ + hdmi_write_reg(wp->base, HDMI_WP_IRQENABLE_CLR, mask); +} + +/* PHY_PWR_CMD */ +int hdmi_wp_set_phy_pwr(struct hdmi_wp_data *wp, enum hdmi_phy_pwr val) +{ + /* Return if already the state */ + if (REG_GET(wp->base, HDMI_WP_PWR_CTRL, 5, 4) == val) + return 0; + + /* Command for power control of HDMI PHY */ + REG_FLD_MOD(wp->base, HDMI_WP_PWR_CTRL, val, 7, 6); + + /* Status of the power control of HDMI PHY */ + if (hdmi_wait_for_bit_change(wp->base, HDMI_WP_PWR_CTRL, 5, 4, val) + != val) { + DSSERR("Failed to set PHY power mode to %d\n", val); + return -ETIMEDOUT; + } + + return 0; +} + +/* PLL_PWR_CMD */ +int hdmi_wp_set_pll_pwr(struct hdmi_wp_data *wp, enum hdmi_pll_pwr val) +{ + /* Command for power control of HDMI PLL */ + REG_FLD_MOD(wp->base, HDMI_WP_PWR_CTRL, val, 3, 2); + + /* wait till PHY_PWR_STATUS is set */ + if (hdmi_wait_for_bit_change(wp->base, HDMI_WP_PWR_CTRL, 1, 0, val) + != val) { + DSSERR("Failed to set PLL_PWR_STATUS\n"); + return -ETIMEDOUT; + } + + return 0; +} + +int hdmi_wp_video_start(struct hdmi_wp_data *wp) +{ + REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, true, 31, 31); + + return 0; +} + +void hdmi_wp_video_stop(struct hdmi_wp_data *wp) +{ + REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, false, 31, 31); +} + +void hdmi_wp_video_config_format(struct hdmi_wp_data *wp, + struct hdmi_video_format *video_fmt) +{ + u32 l = 0; + + REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, video_fmt->packing_mode, + 10, 8); + + l |= FLD_VAL(video_fmt->y_res, 31, 16); + l |= FLD_VAL(video_fmt->x_res, 15, 0); + hdmi_write_reg(wp->base, HDMI_WP_VIDEO_SIZE, l); +} + +void hdmi_wp_video_config_interface(struct hdmi_wp_data *wp, + struct omap_video_timings *timings) +{ + u32 r; + bool vsync_pol, hsync_pol; + DSSDBG("Enter hdmi_wp_video_config_interface\n"); + + vsync_pol = timings->vsync_level == OMAPDSS_SIG_ACTIVE_HIGH; + hsync_pol = timings->hsync_level == OMAPDSS_SIG_ACTIVE_HIGH; + + r = hdmi_read_reg(wp->base, HDMI_WP_VIDEO_CFG); + r = FLD_MOD(r, vsync_pol, 7, 7); + r = FLD_MOD(r, hsync_pol, 6, 6); + r = FLD_MOD(r, timings->interlace, 3, 3); + r = FLD_MOD(r, 1, 1, 0); /* HDMI_TIMING_MASTER_24BIT */ + hdmi_write_reg(wp->base, HDMI_WP_VIDEO_CFG, r); +} + +void hdmi_wp_video_config_timing(struct hdmi_wp_data *wp, + struct omap_video_timings *timings) +{ + u32 timing_h = 0; + u32 timing_v = 0; + + DSSDBG("Enter hdmi_wp_video_config_timing\n"); + + timing_h |= FLD_VAL(timings->hbp, 31, 20); + timing_h |= FLD_VAL(timings->hfp, 19, 8); + timing_h |= FLD_VAL(timings->hsw, 7, 0); + hdmi_write_reg(wp->base, HDMI_WP_VIDEO_TIMING_H, timing_h); + + timing_v |= FLD_VAL(timings->vbp, 31, 20); + timing_v |= FLD_VAL(timings->vfp, 19, 8); + timing_v |= FLD_VAL(timings->vsw, 7, 0); + hdmi_write_reg(wp->base, HDMI_WP_VIDEO_TIMING_V, timing_v); +} + +void hdmi_wp_init_vid_fmt_timings(struct hdmi_video_format *video_fmt, + struct omap_video_timings *timings, struct hdmi_config *param) +{ + DSSDBG("Enter hdmi_wp_video_init_format\n"); + + video_fmt->packing_mode = HDMI_PACK_10b_RGB_YUV444; + video_fmt->y_res = param->timings.y_res; + video_fmt->x_res = param->timings.x_res; + if (param->timings.interlace) + video_fmt->y_res /= 2; + + timings->hbp = param->timings.hbp; + timings->hfp = param->timings.hfp; + timings->hsw = param->timings.hsw; + timings->vbp = param->timings.vbp; + timings->vfp = param->timings.vfp; + timings->vsw = param->timings.vsw; + timings->vsync_level = param->timings.vsync_level; + timings->hsync_level = param->timings.hsync_level; + timings->interlace = param->timings.interlace; +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +void hdmi_wp_audio_config_format(struct hdmi_wp_data *wp, + struct hdmi_audio_format *aud_fmt) +{ + u32 r; + + DSSDBG("Enter hdmi_wp_audio_config_format\n"); + + r = hdmi_read_reg(wp->base, HDMI_WP_AUDIO_CFG); + r = FLD_MOD(r, aud_fmt->stereo_channels, 26, 24); + r = FLD_MOD(r, aud_fmt->active_chnnls_msk, 23, 16); + r = FLD_MOD(r, aud_fmt->en_sig_blk_strt_end, 5, 5); + r = FLD_MOD(r, aud_fmt->type, 4, 4); + r = FLD_MOD(r, aud_fmt->justification, 3, 3); + r = FLD_MOD(r, aud_fmt->sample_order, 2, 2); + r = FLD_MOD(r, aud_fmt->samples_per_word, 1, 1); + r = FLD_MOD(r, aud_fmt->sample_size, 0, 0); + hdmi_write_reg(wp->base, HDMI_WP_AUDIO_CFG, r); +} + +void hdmi_wp_audio_config_dma(struct hdmi_wp_data *wp, + struct hdmi_audio_dma *aud_dma) +{ + u32 r; + + DSSDBG("Enter hdmi_wp_audio_config_dma\n"); + + r = hdmi_read_reg(wp->base, HDMI_WP_AUDIO_CFG2); + r = FLD_MOD(r, aud_dma->transfer_size, 15, 8); + r = FLD_MOD(r, aud_dma->block_size, 7, 0); + hdmi_write_reg(wp->base, HDMI_WP_AUDIO_CFG2, r); + + r = hdmi_read_reg(wp->base, HDMI_WP_AUDIO_CTRL); + r = FLD_MOD(r, aud_dma->mode, 9, 9); + r = FLD_MOD(r, aud_dma->fifo_threshold, 8, 0); + hdmi_write_reg(wp->base, HDMI_WP_AUDIO_CTRL, r); +} + +int hdmi_wp_audio_enable(struct hdmi_wp_data *wp, bool enable) +{ + REG_FLD_MOD(wp->base, HDMI_WP_AUDIO_CTRL, enable, 31, 31); + + return 0; +} + +int hdmi_wp_audio_core_req_enable(struct hdmi_wp_data *wp, bool enable) +{ + REG_FLD_MOD(wp->base, HDMI_WP_AUDIO_CTRL, enable, 30, 30); + + return 0; +} +#endif + +#define WP_SIZE 0x200 + +int hdmi_wp_init(struct platform_device *pdev, struct hdmi_wp_data *wp) +{ + struct resource *res; + struct resource temp_res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wp"); + if (!res) { + DSSDBG("can't get WP mem resource by name\n"); + /* + * if hwmod/DT doesn't have the memory resource information + * split into HDMI sub blocks by name, we try again by getting + * the platform's first resource. this code will be removed when + * the driver can get the mem resources by name + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DSSERR("can't get WP mem resource\n"); + return -EINVAL; + } + + temp_res.start = res->start; + temp_res.end = temp_res.start + WP_SIZE - 1; + res = &temp_res; + } + + wp->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!wp->base) { + DSSERR("can't ioremap HDMI WP\n"); + return -ENOMEM; + } + + return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/manager-sysfs.c b/drivers/video/fbdev/omap2/dss/manager-sysfs.c new file mode 100644 index 000000000000..37b59fe28dc8 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/manager-sysfs.c @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "MANAGER" + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/jiffies.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static ssize_t manager_name_show(struct omap_overlay_manager *mgr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", mgr->name); +} + +static ssize_t manager_display_show(struct omap_overlay_manager *mgr, char *buf) +{ + struct omap_dss_device *dssdev = mgr->get_device(mgr); + + return snprintf(buf, PAGE_SIZE, "%s\n", dssdev ? + dssdev->name : "<none>"); +} + +static ssize_t manager_display_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + int r = 0; + size_t len = size; + struct omap_dss_device *dssdev = NULL; + struct omap_dss_device *old_dssdev; + + int match(struct omap_dss_device *dssdev, void *data) + { + const char *str = data; + return sysfs_streq(dssdev->name, str); + } + + if (buf[size-1] == '\n') + --len; + + if (len > 0) + dssdev = omap_dss_find_device((void *)buf, match); + + if (len > 0 && dssdev == NULL) + return -EINVAL; + + if (dssdev) { + DSSDBG("display %s found\n", dssdev->name); + + if (omapdss_device_is_connected(dssdev)) { + DSSERR("new display is already connected\n"); + r = -EINVAL; + goto put_device; + } + + if (omapdss_device_is_enabled(dssdev)) { + DSSERR("new display is not disabled\n"); + r = -EINVAL; + goto put_device; + } + } + + old_dssdev = mgr->get_device(mgr); + if (old_dssdev) { + if (omapdss_device_is_enabled(old_dssdev)) { + DSSERR("old display is not disabled\n"); + r = -EINVAL; + goto put_device; + } + + old_dssdev->driver->disconnect(old_dssdev); + } + + if (dssdev) { + r = dssdev->driver->connect(dssdev); + if (r) { + DSSERR("failed to connect new device\n"); + goto put_device; + } + + old_dssdev = mgr->get_device(mgr); + if (old_dssdev != dssdev) { + DSSERR("failed to connect device to this manager\n"); + dssdev->driver->disconnect(dssdev); + goto put_device; + } + + r = mgr->apply(mgr); + if (r) { + DSSERR("failed to apply dispc config\n"); + goto put_device; + } + } + +put_device: + if (dssdev) + omap_dss_put_device(dssdev); + + return r ? r : size; +} + +static ssize_t manager_default_color_show(struct omap_overlay_manager *mgr, + char *buf) +{ + struct omap_overlay_manager_info info; + + mgr->get_manager_info(mgr, &info); + + return snprintf(buf, PAGE_SIZE, "%#x\n", info.default_color); +} + +static ssize_t manager_default_color_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + u32 color; + int r; + + r = kstrtouint(buf, 0, &color); + if (r) + return r; + + mgr->get_manager_info(mgr, &info); + + info.default_color = color; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +static const char *trans_key_type_str[] = { + "gfx-destination", + "video-source", +}; + +static ssize_t manager_trans_key_type_show(struct omap_overlay_manager *mgr, + char *buf) +{ + enum omap_dss_trans_key_type key_type; + struct omap_overlay_manager_info info; + + mgr->get_manager_info(mgr, &info); + + key_type = info.trans_key_type; + BUG_ON(key_type >= ARRAY_SIZE(trans_key_type_str)); + + return snprintf(buf, PAGE_SIZE, "%s\n", trans_key_type_str[key_type]); +} + +static ssize_t manager_trans_key_type_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + enum omap_dss_trans_key_type key_type; + struct omap_overlay_manager_info info; + int r; + + for (key_type = OMAP_DSS_COLOR_KEY_GFX_DST; + key_type < ARRAY_SIZE(trans_key_type_str); key_type++) { + if (sysfs_streq(buf, trans_key_type_str[key_type])) + break; + } + + if (key_type == ARRAY_SIZE(trans_key_type_str)) + return -EINVAL; + + mgr->get_manager_info(mgr, &info); + + info.trans_key_type = key_type; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +static ssize_t manager_trans_key_value_show(struct omap_overlay_manager *mgr, + char *buf) +{ + struct omap_overlay_manager_info info; + + mgr->get_manager_info(mgr, &info); + + return snprintf(buf, PAGE_SIZE, "%#x\n", info.trans_key); +} + +static ssize_t manager_trans_key_value_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + u32 key_value; + int r; + + r = kstrtouint(buf, 0, &key_value); + if (r) + return r; + + mgr->get_manager_info(mgr, &info); + + info.trans_key = key_value; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +static ssize_t manager_trans_key_enabled_show(struct omap_overlay_manager *mgr, + char *buf) +{ + struct omap_overlay_manager_info info; + + mgr->get_manager_info(mgr, &info); + + return snprintf(buf, PAGE_SIZE, "%d\n", info.trans_enabled); +} + +static ssize_t manager_trans_key_enabled_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + bool enable; + int r; + + r = strtobool(buf, &enable); + if (r) + return r; + + mgr->get_manager_info(mgr, &info); + + info.trans_enabled = enable; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +static ssize_t manager_alpha_blending_enabled_show( + struct omap_overlay_manager *mgr, char *buf) +{ + struct omap_overlay_manager_info info; + + if(!dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) + return -ENODEV; + + mgr->get_manager_info(mgr, &info); + + return snprintf(buf, PAGE_SIZE, "%d\n", + info.partial_alpha_enabled); +} + +static ssize_t manager_alpha_blending_enabled_store( + struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + bool enable; + int r; + + if(!dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) + return -ENODEV; + + r = strtobool(buf, &enable); + if (r) + return r; + + mgr->get_manager_info(mgr, &info); + + info.partial_alpha_enabled = enable; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +static ssize_t manager_cpr_enable_show(struct omap_overlay_manager *mgr, + char *buf) +{ + struct omap_overlay_manager_info info; + + mgr->get_manager_info(mgr, &info); + + return snprintf(buf, PAGE_SIZE, "%d\n", info.cpr_enable); +} + +static ssize_t manager_cpr_enable_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + int r; + bool enable; + + if (!dss_has_feature(FEAT_CPR)) + return -ENODEV; + + r = strtobool(buf, &enable); + if (r) + return r; + + mgr->get_manager_info(mgr, &info); + + if (info.cpr_enable == enable) + return size; + + info.cpr_enable = enable; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +static ssize_t manager_cpr_coef_show(struct omap_overlay_manager *mgr, + char *buf) +{ + struct omap_overlay_manager_info info; + + mgr->get_manager_info(mgr, &info); + + return snprintf(buf, PAGE_SIZE, + "%d %d %d %d %d %d %d %d %d\n", + info.cpr_coefs.rr, + info.cpr_coefs.rg, + info.cpr_coefs.rb, + info.cpr_coefs.gr, + info.cpr_coefs.gg, + info.cpr_coefs.gb, + info.cpr_coefs.br, + info.cpr_coefs.bg, + info.cpr_coefs.bb); +} + +static ssize_t manager_cpr_coef_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + struct omap_dss_cpr_coefs coefs; + int r, i; + s16 *arr; + + if (!dss_has_feature(FEAT_CPR)) + return -ENODEV; + + if (sscanf(buf, "%hd %hd %hd %hd %hd %hd %hd %hd %hd", + &coefs.rr, &coefs.rg, &coefs.rb, + &coefs.gr, &coefs.gg, &coefs.gb, + &coefs.br, &coefs.bg, &coefs.bb) != 9) + return -EINVAL; + + arr = (s16[]){ coefs.rr, coefs.rg, coefs.rb, + coefs.gr, coefs.gg, coefs.gb, + coefs.br, coefs.bg, coefs.bb }; + + for (i = 0; i < 9; ++i) { + if (arr[i] < -512 || arr[i] > 511) + return -EINVAL; + } + + mgr->get_manager_info(mgr, &info); + + info.cpr_coefs = coefs; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +struct manager_attribute { + struct attribute attr; + ssize_t (*show)(struct omap_overlay_manager *, char *); + ssize_t (*store)(struct omap_overlay_manager *, const char *, size_t); +}; + +#define MANAGER_ATTR(_name, _mode, _show, _store) \ + struct manager_attribute manager_attr_##_name = \ + __ATTR(_name, _mode, _show, _store) + +static MANAGER_ATTR(name, S_IRUGO, manager_name_show, NULL); +static MANAGER_ATTR(display, S_IRUGO|S_IWUSR, + manager_display_show, manager_display_store); +static MANAGER_ATTR(default_color, S_IRUGO|S_IWUSR, + manager_default_color_show, manager_default_color_store); +static MANAGER_ATTR(trans_key_type, S_IRUGO|S_IWUSR, + manager_trans_key_type_show, manager_trans_key_type_store); +static MANAGER_ATTR(trans_key_value, S_IRUGO|S_IWUSR, + manager_trans_key_value_show, manager_trans_key_value_store); +static MANAGER_ATTR(trans_key_enabled, S_IRUGO|S_IWUSR, + manager_trans_key_enabled_show, + manager_trans_key_enabled_store); +static MANAGER_ATTR(alpha_blending_enabled, S_IRUGO|S_IWUSR, + manager_alpha_blending_enabled_show, + manager_alpha_blending_enabled_store); +static MANAGER_ATTR(cpr_enable, S_IRUGO|S_IWUSR, + manager_cpr_enable_show, + manager_cpr_enable_store); +static MANAGER_ATTR(cpr_coef, S_IRUGO|S_IWUSR, + manager_cpr_coef_show, + manager_cpr_coef_store); + + +static struct attribute *manager_sysfs_attrs[] = { + &manager_attr_name.attr, + &manager_attr_display.attr, + &manager_attr_default_color.attr, + &manager_attr_trans_key_type.attr, + &manager_attr_trans_key_value.attr, + &manager_attr_trans_key_enabled.attr, + &manager_attr_alpha_blending_enabled.attr, + &manager_attr_cpr_enable.attr, + &manager_attr_cpr_coef.attr, + NULL +}; + +static ssize_t manager_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct omap_overlay_manager *manager; + struct manager_attribute *manager_attr; + + manager = container_of(kobj, struct omap_overlay_manager, kobj); + manager_attr = container_of(attr, struct manager_attribute, attr); + + if (!manager_attr->show) + return -ENOENT; + + return manager_attr->show(manager, buf); +} + +static ssize_t manager_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t size) +{ + struct omap_overlay_manager *manager; + struct manager_attribute *manager_attr; + + manager = container_of(kobj, struct omap_overlay_manager, kobj); + manager_attr = container_of(attr, struct manager_attribute, attr); + + if (!manager_attr->store) + return -ENOENT; + + return manager_attr->store(manager, buf, size); +} + +static const struct sysfs_ops manager_sysfs_ops = { + .show = manager_attr_show, + .store = manager_attr_store, +}; + +static struct kobj_type manager_ktype = { + .sysfs_ops = &manager_sysfs_ops, + .default_attrs = manager_sysfs_attrs, +}; + +int dss_manager_kobj_init(struct omap_overlay_manager *mgr, + struct platform_device *pdev) +{ + return kobject_init_and_add(&mgr->kobj, &manager_ktype, + &pdev->dev.kobj, "manager%d", mgr->id); +} + +void dss_manager_kobj_uninit(struct omap_overlay_manager *mgr) +{ + kobject_del(&mgr->kobj); + kobject_put(&mgr->kobj); + + memset(&mgr->kobj, 0, sizeof(mgr->kobj)); +} diff --git a/drivers/video/fbdev/omap2/dss/manager.c b/drivers/video/fbdev/omap2/dss/manager.c new file mode 100644 index 000000000000..1aac9b4191a9 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/manager.c @@ -0,0 +1,263 @@ +/* + * linux/drivers/video/omap2/dss/manager.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "MANAGER" + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/jiffies.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static int num_managers; +static struct omap_overlay_manager *managers; + +int dss_init_overlay_managers(void) +{ + int i; + + num_managers = dss_feat_get_num_mgrs(); + + managers = kzalloc(sizeof(struct omap_overlay_manager) * num_managers, + GFP_KERNEL); + + BUG_ON(managers == NULL); + + for (i = 0; i < num_managers; ++i) { + struct omap_overlay_manager *mgr = &managers[i]; + + switch (i) { + case 0: + mgr->name = "lcd"; + mgr->id = OMAP_DSS_CHANNEL_LCD; + break; + case 1: + mgr->name = "tv"; + mgr->id = OMAP_DSS_CHANNEL_DIGIT; + break; + case 2: + mgr->name = "lcd2"; + mgr->id = OMAP_DSS_CHANNEL_LCD2; + break; + case 3: + mgr->name = "lcd3"; + mgr->id = OMAP_DSS_CHANNEL_LCD3; + break; + } + + mgr->caps = 0; + mgr->supported_displays = + dss_feat_get_supported_displays(mgr->id); + mgr->supported_outputs = + dss_feat_get_supported_outputs(mgr->id); + + INIT_LIST_HEAD(&mgr->overlays); + } + + return 0; +} + +int dss_init_overlay_managers_sysfs(struct platform_device *pdev) +{ + int i, r; + + for (i = 0; i < num_managers; ++i) { + struct omap_overlay_manager *mgr = &managers[i]; + + r = dss_manager_kobj_init(mgr, pdev); + if (r) + DSSERR("failed to create sysfs file\n"); + } + + return 0; +} + +void dss_uninit_overlay_managers(void) +{ + kfree(managers); + managers = NULL; + num_managers = 0; +} + +void dss_uninit_overlay_managers_sysfs(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < num_managers; ++i) { + struct omap_overlay_manager *mgr = &managers[i]; + + dss_manager_kobj_uninit(mgr); + } +} + +int omap_dss_get_num_overlay_managers(void) +{ + return num_managers; +} +EXPORT_SYMBOL(omap_dss_get_num_overlay_managers); + +struct omap_overlay_manager *omap_dss_get_overlay_manager(int num) +{ + if (num >= num_managers) + return NULL; + + return &managers[num]; +} +EXPORT_SYMBOL(omap_dss_get_overlay_manager); + +int dss_mgr_simple_check(struct omap_overlay_manager *mgr, + const struct omap_overlay_manager_info *info) +{ + if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) { + /* + * OMAP3 supports only graphics source transparency color key + * and alpha blending simultaneously. See TRM 15.4.2.4.2.2 + * Alpha Mode. + */ + if (info->partial_alpha_enabled && info->trans_enabled + && info->trans_key_type != OMAP_DSS_COLOR_KEY_GFX_DST) { + DSSERR("check_manager: illegal transparency key\n"); + return -EINVAL; + } + } + + return 0; +} + +static int dss_mgr_check_zorder(struct omap_overlay_manager *mgr, + struct omap_overlay_info **overlay_infos) +{ + struct omap_overlay *ovl1, *ovl2; + struct omap_overlay_info *info1, *info2; + + list_for_each_entry(ovl1, &mgr->overlays, list) { + info1 = overlay_infos[ovl1->id]; + + if (info1 == NULL) + continue; + + list_for_each_entry(ovl2, &mgr->overlays, list) { + if (ovl1 == ovl2) + continue; + + info2 = overlay_infos[ovl2->id]; + + if (info2 == NULL) + continue; + + if (info1->zorder == info2->zorder) { + DSSERR("overlays %d and %d have the same " + "zorder %d\n", + ovl1->id, ovl2->id, info1->zorder); + return -EINVAL; + } + } + } + + return 0; +} + +int dss_mgr_check_timings(struct omap_overlay_manager *mgr, + const struct omap_video_timings *timings) +{ + if (!dispc_mgr_timings_ok(mgr->id, timings)) { + DSSERR("check_manager: invalid timings\n"); + return -EINVAL; + } + + return 0; +} + +static int dss_mgr_check_lcd_config(struct omap_overlay_manager *mgr, + const struct dss_lcd_mgr_config *config) +{ + struct dispc_clock_info cinfo = config->clock_info; + int dl = config->video_port_width; + bool stallmode = config->stallmode; + bool fifohandcheck = config->fifohandcheck; + + if (cinfo.lck_div < 1 || cinfo.lck_div > 255) + return -EINVAL; + + if (cinfo.pck_div < 1 || cinfo.pck_div > 255) + return -EINVAL; + + if (dl != 12 && dl != 16 && dl != 18 && dl != 24) + return -EINVAL; + + /* fifohandcheck should be used only with stallmode */ + if (stallmode == false && fifohandcheck == true) + return -EINVAL; + + /* + * io pad mode can be only checked by using dssdev connected to the + * manager. Ignore checking these for now, add checks when manager + * is capable of holding information related to the connected interface + */ + + return 0; +} + +int dss_mgr_check(struct omap_overlay_manager *mgr, + struct omap_overlay_manager_info *info, + const struct omap_video_timings *mgr_timings, + const struct dss_lcd_mgr_config *lcd_config, + struct omap_overlay_info **overlay_infos) +{ + struct omap_overlay *ovl; + int r; + + if (dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) { + r = dss_mgr_check_zorder(mgr, overlay_infos); + if (r) + return r; + } + + r = dss_mgr_check_timings(mgr, mgr_timings); + if (r) + return r; + + r = dss_mgr_check_lcd_config(mgr, lcd_config); + if (r) + return r; + + list_for_each_entry(ovl, &mgr->overlays, list) { + struct omap_overlay_info *oi; + int r; + + oi = overlay_infos[ovl->id]; + + if (oi == NULL) + continue; + + r = dss_ovl_check(ovl, oi, mgr_timings); + if (r) + return r; + } + + return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/output.c b/drivers/video/fbdev/omap2/dss/output.c new file mode 100644 index 000000000000..2ab3afa615e8 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/output.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2012 Texas Instruments Ltd + * Author: Archit Taneja <archit@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" + +static LIST_HEAD(output_list); +static DEFINE_MUTEX(output_lock); + +int omapdss_output_set_device(struct omap_dss_device *out, + struct omap_dss_device *dssdev) +{ + int r; + + mutex_lock(&output_lock); + + if (out->dst) { + DSSERR("output already has device %s connected to it\n", + out->dst->name); + r = -EINVAL; + goto err; + } + + if (out->output_type != dssdev->type) { + DSSERR("output type and display type don't match\n"); + r = -EINVAL; + goto err; + } + + out->dst = dssdev; + dssdev->src = out; + + mutex_unlock(&output_lock); + + return 0; +err: + mutex_unlock(&output_lock); + + return r; +} +EXPORT_SYMBOL(omapdss_output_set_device); + +int omapdss_output_unset_device(struct omap_dss_device *out) +{ + int r; + + mutex_lock(&output_lock); + + if (!out->dst) { + DSSERR("output doesn't have a device connected to it\n"); + r = -EINVAL; + goto err; + } + + if (out->dst->state != OMAP_DSS_DISPLAY_DISABLED) { + DSSERR("device %s is not disabled, cannot unset device\n", + out->dst->name); + r = -EINVAL; + goto err; + } + + out->dst->src = NULL; + out->dst = NULL; + + mutex_unlock(&output_lock); + + return 0; +err: + mutex_unlock(&output_lock); + + return r; +} +EXPORT_SYMBOL(omapdss_output_unset_device); + +int omapdss_register_output(struct omap_dss_device *out) +{ + list_add_tail(&out->list, &output_list); + return 0; +} +EXPORT_SYMBOL(omapdss_register_output); + +void omapdss_unregister_output(struct omap_dss_device *out) +{ + list_del(&out->list); +} +EXPORT_SYMBOL(omapdss_unregister_output); + +struct omap_dss_device *omap_dss_get_output(enum omap_dss_output_id id) +{ + struct omap_dss_device *out; + + list_for_each_entry(out, &output_list, list) { + if (out->id == id) + return out; + } + + return NULL; +} +EXPORT_SYMBOL(omap_dss_get_output); + +struct omap_dss_device *omap_dss_find_output(const char *name) +{ + struct omap_dss_device *out; + + list_for_each_entry(out, &output_list, list) { + if (strcmp(out->name, name) == 0) + return omap_dss_get_device(out); + } + + return NULL; +} +EXPORT_SYMBOL(omap_dss_find_output); + +struct omap_dss_device *omap_dss_find_output_by_node(struct device_node *node) +{ + struct omap_dss_device *out; + + list_for_each_entry(out, &output_list, list) { + if (out->dev->of_node == node) + return omap_dss_get_device(out); + } + + return NULL; +} +EXPORT_SYMBOL(omap_dss_find_output_by_node); + +struct omap_dss_device *omapdss_find_output_from_display(struct omap_dss_device *dssdev) +{ + while (dssdev->src) + dssdev = dssdev->src; + + if (dssdev->id != 0) + return omap_dss_get_device(dssdev); + + return NULL; +} +EXPORT_SYMBOL(omapdss_find_output_from_display); + +struct omap_overlay_manager *omapdss_find_mgr_from_display(struct omap_dss_device *dssdev) +{ + struct omap_dss_device *out; + struct omap_overlay_manager *mgr; + + out = omapdss_find_output_from_display(dssdev); + + if (out == NULL) + return NULL; + + mgr = out->manager; + + omap_dss_put_device(out); + + return mgr; +} +EXPORT_SYMBOL(omapdss_find_mgr_from_display); + +static const struct dss_mgr_ops *dss_mgr_ops; + +int dss_install_mgr_ops(const struct dss_mgr_ops *mgr_ops) +{ + if (dss_mgr_ops) + return -EBUSY; + + dss_mgr_ops = mgr_ops; + + return 0; +} +EXPORT_SYMBOL(dss_install_mgr_ops); + +void dss_uninstall_mgr_ops(void) +{ + dss_mgr_ops = NULL; +} +EXPORT_SYMBOL(dss_uninstall_mgr_ops); + +int dss_mgr_connect(struct omap_overlay_manager *mgr, + struct omap_dss_device *dst) +{ + return dss_mgr_ops->connect(mgr, dst); +} +EXPORT_SYMBOL(dss_mgr_connect); + +void dss_mgr_disconnect(struct omap_overlay_manager *mgr, + struct omap_dss_device *dst) +{ + dss_mgr_ops->disconnect(mgr, dst); +} +EXPORT_SYMBOL(dss_mgr_disconnect); + +void dss_mgr_set_timings(struct omap_overlay_manager *mgr, + const struct omap_video_timings *timings) +{ + dss_mgr_ops->set_timings(mgr, timings); +} +EXPORT_SYMBOL(dss_mgr_set_timings); + +void dss_mgr_set_lcd_config(struct omap_overlay_manager *mgr, + const struct dss_lcd_mgr_config *config) +{ + dss_mgr_ops->set_lcd_config(mgr, config); +} +EXPORT_SYMBOL(dss_mgr_set_lcd_config); + +int dss_mgr_enable(struct omap_overlay_manager *mgr) +{ + return dss_mgr_ops->enable(mgr); +} +EXPORT_SYMBOL(dss_mgr_enable); + +void dss_mgr_disable(struct omap_overlay_manager *mgr) +{ + dss_mgr_ops->disable(mgr); +} +EXPORT_SYMBOL(dss_mgr_disable); + +void dss_mgr_start_update(struct omap_overlay_manager *mgr) +{ + dss_mgr_ops->start_update(mgr); +} +EXPORT_SYMBOL(dss_mgr_start_update); + +int dss_mgr_register_framedone_handler(struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ + return dss_mgr_ops->register_framedone_handler(mgr, handler, data); +} +EXPORT_SYMBOL(dss_mgr_register_framedone_handler); + +void dss_mgr_unregister_framedone_handler(struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ + dss_mgr_ops->unregister_framedone_handler(mgr, handler, data); +} +EXPORT_SYMBOL(dss_mgr_unregister_framedone_handler); diff --git a/drivers/video/fbdev/omap2/dss/overlay-sysfs.c b/drivers/video/fbdev/omap2/dss/overlay-sysfs.c new file mode 100644 index 000000000000..4cc5ddebfb34 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/overlay-sysfs.c @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "OVERLAY" + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/kobject.h> +#include <linux/platform_device.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static ssize_t overlay_name_show(struct omap_overlay *ovl, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", ovl->name); +} + +static ssize_t overlay_manager_show(struct omap_overlay *ovl, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", + ovl->manager ? ovl->manager->name : "<none>"); +} + +static ssize_t overlay_manager_store(struct omap_overlay *ovl, const char *buf, + size_t size) +{ + int i, r; + struct omap_overlay_manager *mgr = NULL; + struct omap_overlay_manager *old_mgr; + int len = size; + + if (buf[size-1] == '\n') + --len; + + if (len > 0) { + for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { + mgr = omap_dss_get_overlay_manager(i); + + if (sysfs_streq(buf, mgr->name)) + break; + + mgr = NULL; + } + } + + if (len > 0 && mgr == NULL) + return -EINVAL; + + if (mgr) + DSSDBG("manager %s found\n", mgr->name); + + if (mgr == ovl->manager) + return size; + + old_mgr = ovl->manager; + + r = dispc_runtime_get(); + if (r) + return r; + + /* detach old manager */ + if (old_mgr) { + r = ovl->unset_manager(ovl); + if (r) { + DSSERR("detach failed\n"); + goto err; + } + + r = old_mgr->apply(old_mgr); + if (r) + goto err; + } + + if (mgr) { + r = ovl->set_manager(ovl, mgr); + if (r) { + DSSERR("Failed to attach overlay\n"); + goto err; + } + + r = mgr->apply(mgr); + if (r) + goto err; + } + + dispc_runtime_put(); + + return size; + +err: + dispc_runtime_put(); + return r; +} + +static ssize_t overlay_input_size_show(struct omap_overlay *ovl, char *buf) +{ + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", + info.width, info.height); +} + +static ssize_t overlay_screen_width_show(struct omap_overlay *ovl, char *buf) +{ + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + return snprintf(buf, PAGE_SIZE, "%d\n", info.screen_width); +} + +static ssize_t overlay_position_show(struct omap_overlay *ovl, char *buf) +{ + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", + info.pos_x, info.pos_y); +} + +static ssize_t overlay_position_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + char *last; + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + info.pos_x = simple_strtoul(buf, &last, 10); + ++last; + if (last - buf >= size) + return -EINVAL; + + info.pos_y = simple_strtoul(last, &last, 10); + + r = ovl->set_overlay_info(ovl, &info); + if (r) + return r; + + if (ovl->manager) { + r = ovl->manager->apply(ovl->manager); + if (r) + return r; + } + + return size; +} + +static ssize_t overlay_output_size_show(struct omap_overlay *ovl, char *buf) +{ + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", + info.out_width, info.out_height); +} + +static ssize_t overlay_output_size_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + char *last; + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + info.out_width = simple_strtoul(buf, &last, 10); + ++last; + if (last - buf >= size) + return -EINVAL; + + info.out_height = simple_strtoul(last, &last, 10); + + r = ovl->set_overlay_info(ovl, &info); + if (r) + return r; + + if (ovl->manager) { + r = ovl->manager->apply(ovl->manager); + if (r) + return r; + } + + return size; +} + +static ssize_t overlay_enabled_show(struct omap_overlay *ovl, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", ovl->is_enabled(ovl)); +} + +static ssize_t overlay_enabled_store(struct omap_overlay *ovl, const char *buf, + size_t size) +{ + int r; + bool enable; + + r = strtobool(buf, &enable); + if (r) + return r; + + if (enable) + r = ovl->enable(ovl); + else + r = ovl->disable(ovl); + + if (r) + return r; + + return size; +} + +static ssize_t overlay_global_alpha_show(struct omap_overlay *ovl, char *buf) +{ + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + return snprintf(buf, PAGE_SIZE, "%d\n", + info.global_alpha); +} + +static ssize_t overlay_global_alpha_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + u8 alpha; + struct omap_overlay_info info; + + if ((ovl->caps & OMAP_DSS_OVL_CAP_GLOBAL_ALPHA) == 0) + return -ENODEV; + + r = kstrtou8(buf, 0, &alpha); + if (r) + return r; + + ovl->get_overlay_info(ovl, &info); + + info.global_alpha = alpha; + + r = ovl->set_overlay_info(ovl, &info); + if (r) + return r; + + if (ovl->manager) { + r = ovl->manager->apply(ovl->manager); + if (r) + return r; + } + + return size; +} + +static ssize_t overlay_pre_mult_alpha_show(struct omap_overlay *ovl, + char *buf) +{ + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + return snprintf(buf, PAGE_SIZE, "%d\n", + info.pre_mult_alpha); +} + +static ssize_t overlay_pre_mult_alpha_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + u8 alpha; + struct omap_overlay_info info; + + if ((ovl->caps & OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA) == 0) + return -ENODEV; + + r = kstrtou8(buf, 0, &alpha); + if (r) + return r; + + ovl->get_overlay_info(ovl, &info); + + info.pre_mult_alpha = alpha; + + r = ovl->set_overlay_info(ovl, &info); + if (r) + return r; + + if (ovl->manager) { + r = ovl->manager->apply(ovl->manager); + if (r) + return r; + } + + return size; +} + +static ssize_t overlay_zorder_show(struct omap_overlay *ovl, char *buf) +{ + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + return snprintf(buf, PAGE_SIZE, "%d\n", info.zorder); +} + +static ssize_t overlay_zorder_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + u8 zorder; + struct omap_overlay_info info; + + if ((ovl->caps & OMAP_DSS_OVL_CAP_ZORDER) == 0) + return -ENODEV; + + r = kstrtou8(buf, 0, &zorder); + if (r) + return r; + + ovl->get_overlay_info(ovl, &info); + + info.zorder = zorder; + + r = ovl->set_overlay_info(ovl, &info); + if (r) + return r; + + if (ovl->manager) { + r = ovl->manager->apply(ovl->manager); + if (r) + return r; + } + + return size; +} + +struct overlay_attribute { + struct attribute attr; + ssize_t (*show)(struct omap_overlay *, char *); + ssize_t (*store)(struct omap_overlay *, const char *, size_t); +}; + +#define OVERLAY_ATTR(_name, _mode, _show, _store) \ + struct overlay_attribute overlay_attr_##_name = \ + __ATTR(_name, _mode, _show, _store) + +static OVERLAY_ATTR(name, S_IRUGO, overlay_name_show, NULL); +static OVERLAY_ATTR(manager, S_IRUGO|S_IWUSR, + overlay_manager_show, overlay_manager_store); +static OVERLAY_ATTR(input_size, S_IRUGO, overlay_input_size_show, NULL); +static OVERLAY_ATTR(screen_width, S_IRUGO, overlay_screen_width_show, NULL); +static OVERLAY_ATTR(position, S_IRUGO|S_IWUSR, + overlay_position_show, overlay_position_store); +static OVERLAY_ATTR(output_size, S_IRUGO|S_IWUSR, + overlay_output_size_show, overlay_output_size_store); +static OVERLAY_ATTR(enabled, S_IRUGO|S_IWUSR, + overlay_enabled_show, overlay_enabled_store); +static OVERLAY_ATTR(global_alpha, S_IRUGO|S_IWUSR, + overlay_global_alpha_show, overlay_global_alpha_store); +static OVERLAY_ATTR(pre_mult_alpha, S_IRUGO|S_IWUSR, + overlay_pre_mult_alpha_show, + overlay_pre_mult_alpha_store); +static OVERLAY_ATTR(zorder, S_IRUGO|S_IWUSR, + overlay_zorder_show, overlay_zorder_store); + +static struct attribute *overlay_sysfs_attrs[] = { + &overlay_attr_name.attr, + &overlay_attr_manager.attr, + &overlay_attr_input_size.attr, + &overlay_attr_screen_width.attr, + &overlay_attr_position.attr, + &overlay_attr_output_size.attr, + &overlay_attr_enabled.attr, + &overlay_attr_global_alpha.attr, + &overlay_attr_pre_mult_alpha.attr, + &overlay_attr_zorder.attr, + NULL +}; + +static ssize_t overlay_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct omap_overlay *overlay; + struct overlay_attribute *overlay_attr; + + overlay = container_of(kobj, struct omap_overlay, kobj); + overlay_attr = container_of(attr, struct overlay_attribute, attr); + + if (!overlay_attr->show) + return -ENOENT; + + return overlay_attr->show(overlay, buf); +} + +static ssize_t overlay_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t size) +{ + struct omap_overlay *overlay; + struct overlay_attribute *overlay_attr; + + overlay = container_of(kobj, struct omap_overlay, kobj); + overlay_attr = container_of(attr, struct overlay_attribute, attr); + + if (!overlay_attr->store) + return -ENOENT; + + return overlay_attr->store(overlay, buf, size); +} + +static const struct sysfs_ops overlay_sysfs_ops = { + .show = overlay_attr_show, + .store = overlay_attr_store, +}; + +static struct kobj_type overlay_ktype = { + .sysfs_ops = &overlay_sysfs_ops, + .default_attrs = overlay_sysfs_attrs, +}; + +int dss_overlay_kobj_init(struct omap_overlay *ovl, + struct platform_device *pdev) +{ + return kobject_init_and_add(&ovl->kobj, &overlay_ktype, + &pdev->dev.kobj, "overlay%d", ovl->id); +} + +void dss_overlay_kobj_uninit(struct omap_overlay *ovl) +{ + kobject_del(&ovl->kobj); + kobject_put(&ovl->kobj); +} diff --git a/drivers/video/fbdev/omap2/dss/overlay.c b/drivers/video/fbdev/omap2/dss/overlay.c new file mode 100644 index 000000000000..2f7cee985cdd --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/overlay.c @@ -0,0 +1,202 @@ +/* + * linux/drivers/video/omap2/dss/overlay.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "OVERLAY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static int num_overlays; +static struct omap_overlay *overlays; + +int omap_dss_get_num_overlays(void) +{ + return num_overlays; +} +EXPORT_SYMBOL(omap_dss_get_num_overlays); + +struct omap_overlay *omap_dss_get_overlay(int num) +{ + if (num >= num_overlays) + return NULL; + + return &overlays[num]; +} +EXPORT_SYMBOL(omap_dss_get_overlay); + +void dss_init_overlays(struct platform_device *pdev) +{ + int i, r; + + num_overlays = dss_feat_get_num_ovls(); + + overlays = kzalloc(sizeof(struct omap_overlay) * num_overlays, + GFP_KERNEL); + + BUG_ON(overlays == NULL); + + for (i = 0; i < num_overlays; ++i) { + struct omap_overlay *ovl = &overlays[i]; + + switch (i) { + case 0: + ovl->name = "gfx"; + ovl->id = OMAP_DSS_GFX; + break; + case 1: + ovl->name = "vid1"; + ovl->id = OMAP_DSS_VIDEO1; + break; + case 2: + ovl->name = "vid2"; + ovl->id = OMAP_DSS_VIDEO2; + break; + case 3: + ovl->name = "vid3"; + ovl->id = OMAP_DSS_VIDEO3; + break; + } + + ovl->caps = dss_feat_get_overlay_caps(ovl->id); + ovl->supported_modes = + dss_feat_get_supported_color_modes(ovl->id); + + r = dss_overlay_kobj_init(ovl, pdev); + if (r) + DSSERR("failed to create sysfs file\n"); + } +} + +void dss_uninit_overlays(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < num_overlays; ++i) { + struct omap_overlay *ovl = &overlays[i]; + dss_overlay_kobj_uninit(ovl); + } + + kfree(overlays); + overlays = NULL; + num_overlays = 0; +} + +int dss_ovl_simple_check(struct omap_overlay *ovl, + const struct omap_overlay_info *info) +{ + if ((ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0) { + if (info->out_width != 0 && info->width != info->out_width) { + DSSERR("check_overlay: overlay %d doesn't support " + "scaling\n", ovl->id); + return -EINVAL; + } + + if (info->out_height != 0 && info->height != info->out_height) { + DSSERR("check_overlay: overlay %d doesn't support " + "scaling\n", ovl->id); + return -EINVAL; + } + } + + if ((ovl->supported_modes & info->color_mode) == 0) { + DSSERR("check_overlay: overlay %d doesn't support mode %d\n", + ovl->id, info->color_mode); + return -EINVAL; + } + + if (info->zorder >= omap_dss_get_num_overlays()) { + DSSERR("check_overlay: zorder %d too high\n", info->zorder); + return -EINVAL; + } + + if (dss_feat_rotation_type_supported(info->rotation_type) == 0) { + DSSERR("check_overlay: rotation type %d not supported\n", + info->rotation_type); + return -EINVAL; + } + + return 0; +} + +int dss_ovl_check(struct omap_overlay *ovl, struct omap_overlay_info *info, + const struct omap_video_timings *mgr_timings) +{ + u16 outw, outh; + u16 dw, dh; + + dw = mgr_timings->x_res; + dh = mgr_timings->y_res; + + if ((ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0) { + outw = info->width; + outh = info->height; + } else { + if (info->out_width == 0) + outw = info->width; + else + outw = info->out_width; + + if (info->out_height == 0) + outh = info->height; + else + outh = info->out_height; + } + + if (dw < info->pos_x + outw) { + DSSERR("overlay %d horizontally not inside the display area " + "(%d + %d >= %d)\n", + ovl->id, info->pos_x, outw, dw); + return -EINVAL; + } + + if (dh < info->pos_y + outh) { + DSSERR("overlay %d vertically not inside the display area " + "(%d + %d >= %d)\n", + ovl->id, info->pos_y, outh, dh); + return -EINVAL; + } + + return 0; +} + +/* + * Checks if replication logic should be used. Only use when overlay is in + * RGB12U or RGB16 mode, and video port width interface is 18bpp or 24bpp + */ +bool dss_ovl_use_replication(struct dss_lcd_mgr_config config, + enum omap_color_mode mode) +{ + if (mode != OMAP_DSS_COLOR_RGB12U && mode != OMAP_DSS_COLOR_RGB16) + return false; + + return config.video_port_width > 16; +} diff --git a/drivers/video/fbdev/omap2/dss/rfbi.c b/drivers/video/fbdev/omap2/dss/rfbi.c new file mode 100644 index 000000000000..c8a81a2b879c --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/rfbi.c @@ -0,0 +1,1058 @@ +/* + * linux/drivers/video/omap2/dss/rfbi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "RFBI" + +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/export.h> +#include <linux/vmalloc.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/kfifo.h> +#include <linux/ktime.h> +#include <linux/hrtimer.h> +#include <linux/seq_file.h> +#include <linux/semaphore.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <video/omapdss.h> +#include "dss.h" + +struct rfbi_reg { u16 idx; }; + +#define RFBI_REG(idx) ((const struct rfbi_reg) { idx }) + +#define RFBI_REVISION RFBI_REG(0x0000) +#define RFBI_SYSCONFIG RFBI_REG(0x0010) +#define RFBI_SYSSTATUS RFBI_REG(0x0014) +#define RFBI_CONTROL RFBI_REG(0x0040) +#define RFBI_PIXEL_CNT RFBI_REG(0x0044) +#define RFBI_LINE_NUMBER RFBI_REG(0x0048) +#define RFBI_CMD RFBI_REG(0x004c) +#define RFBI_PARAM RFBI_REG(0x0050) +#define RFBI_DATA RFBI_REG(0x0054) +#define RFBI_READ RFBI_REG(0x0058) +#define RFBI_STATUS RFBI_REG(0x005c) + +#define RFBI_CONFIG(n) RFBI_REG(0x0060 + (n)*0x18) +#define RFBI_ONOFF_TIME(n) RFBI_REG(0x0064 + (n)*0x18) +#define RFBI_CYCLE_TIME(n) RFBI_REG(0x0068 + (n)*0x18) +#define RFBI_DATA_CYCLE1(n) RFBI_REG(0x006c + (n)*0x18) +#define RFBI_DATA_CYCLE2(n) RFBI_REG(0x0070 + (n)*0x18) +#define RFBI_DATA_CYCLE3(n) RFBI_REG(0x0074 + (n)*0x18) + +#define RFBI_VSYNC_WIDTH RFBI_REG(0x0090) +#define RFBI_HSYNC_WIDTH RFBI_REG(0x0094) + +#define REG_FLD_MOD(idx, val, start, end) \ + rfbi_write_reg(idx, FLD_MOD(rfbi_read_reg(idx), val, start, end)) + +enum omap_rfbi_cycleformat { + OMAP_DSS_RFBI_CYCLEFORMAT_1_1 = 0, + OMAP_DSS_RFBI_CYCLEFORMAT_2_1 = 1, + OMAP_DSS_RFBI_CYCLEFORMAT_3_1 = 2, + OMAP_DSS_RFBI_CYCLEFORMAT_3_2 = 3, +}; + +enum omap_rfbi_datatype { + OMAP_DSS_RFBI_DATATYPE_12 = 0, + OMAP_DSS_RFBI_DATATYPE_16 = 1, + OMAP_DSS_RFBI_DATATYPE_18 = 2, + OMAP_DSS_RFBI_DATATYPE_24 = 3, +}; + +enum omap_rfbi_parallelmode { + OMAP_DSS_RFBI_PARALLELMODE_8 = 0, + OMAP_DSS_RFBI_PARALLELMODE_9 = 1, + OMAP_DSS_RFBI_PARALLELMODE_12 = 2, + OMAP_DSS_RFBI_PARALLELMODE_16 = 3, +}; + +static int rfbi_convert_timings(struct rfbi_timings *t); +static void rfbi_get_clk_info(u32 *clk_period, u32 *max_clk_div); + +static struct { + struct platform_device *pdev; + void __iomem *base; + + unsigned long l4_khz; + + enum omap_rfbi_datatype datatype; + enum omap_rfbi_parallelmode parallelmode; + + enum omap_rfbi_te_mode te_mode; + int te_enabled; + + void (*framedone_callback)(void *data); + void *framedone_callback_data; + + struct omap_dss_device *dssdev[2]; + + struct semaphore bus_lock; + + struct omap_video_timings timings; + int pixel_size; + int data_lines; + struct rfbi_timings intf_timings; + + struct omap_dss_device output; +} rfbi; + +static inline void rfbi_write_reg(const struct rfbi_reg idx, u32 val) +{ + __raw_writel(val, rfbi.base + idx.idx); +} + +static inline u32 rfbi_read_reg(const struct rfbi_reg idx) +{ + return __raw_readl(rfbi.base + idx.idx); +} + +static int rfbi_runtime_get(void) +{ + int r; + + DSSDBG("rfbi_runtime_get\n"); + + r = pm_runtime_get_sync(&rfbi.pdev->dev); + WARN_ON(r < 0); + return r < 0 ? r : 0; +} + +static void rfbi_runtime_put(void) +{ + int r; + + DSSDBG("rfbi_runtime_put\n"); + + r = pm_runtime_put_sync(&rfbi.pdev->dev); + WARN_ON(r < 0 && r != -ENOSYS); +} + +static void rfbi_bus_lock(void) +{ + down(&rfbi.bus_lock); +} + +static void rfbi_bus_unlock(void) +{ + up(&rfbi.bus_lock); +} + +static void rfbi_write_command(const void *buf, u32 len) +{ + switch (rfbi.parallelmode) { + case OMAP_DSS_RFBI_PARALLELMODE_8: + { + const u8 *b = buf; + for (; len; len--) + rfbi_write_reg(RFBI_CMD, *b++); + break; + } + + case OMAP_DSS_RFBI_PARALLELMODE_16: + { + const u16 *w = buf; + BUG_ON(len & 1); + for (; len; len -= 2) + rfbi_write_reg(RFBI_CMD, *w++); + break; + } + + case OMAP_DSS_RFBI_PARALLELMODE_9: + case OMAP_DSS_RFBI_PARALLELMODE_12: + default: + BUG(); + } +} + +static void rfbi_read_data(void *buf, u32 len) +{ + switch (rfbi.parallelmode) { + case OMAP_DSS_RFBI_PARALLELMODE_8: + { + u8 *b = buf; + for (; len; len--) { + rfbi_write_reg(RFBI_READ, 0); + *b++ = rfbi_read_reg(RFBI_READ); + } + break; + } + + case OMAP_DSS_RFBI_PARALLELMODE_16: + { + u16 *w = buf; + BUG_ON(len & ~1); + for (; len; len -= 2) { + rfbi_write_reg(RFBI_READ, 0); + *w++ = rfbi_read_reg(RFBI_READ); + } + break; + } + + case OMAP_DSS_RFBI_PARALLELMODE_9: + case OMAP_DSS_RFBI_PARALLELMODE_12: + default: + BUG(); + } +} + +static void rfbi_write_data(const void *buf, u32 len) +{ + switch (rfbi.parallelmode) { + case OMAP_DSS_RFBI_PARALLELMODE_8: + { + const u8 *b = buf; + for (; len; len--) + rfbi_write_reg(RFBI_PARAM, *b++); + break; + } + + case OMAP_DSS_RFBI_PARALLELMODE_16: + { + const u16 *w = buf; + BUG_ON(len & 1); + for (; len; len -= 2) + rfbi_write_reg(RFBI_PARAM, *w++); + break; + } + + case OMAP_DSS_RFBI_PARALLELMODE_9: + case OMAP_DSS_RFBI_PARALLELMODE_12: + default: + BUG(); + + } +} + +static void rfbi_write_pixels(const void __iomem *buf, int scr_width, + u16 x, u16 y, + u16 w, u16 h) +{ + int start_offset = scr_width * y + x; + int horiz_offset = scr_width - w; + int i; + + if (rfbi.datatype == OMAP_DSS_RFBI_DATATYPE_16 && + rfbi.parallelmode == OMAP_DSS_RFBI_PARALLELMODE_8) { + const u16 __iomem *pd = buf; + pd += start_offset; + + for (; h; --h) { + for (i = 0; i < w; ++i) { + const u8 __iomem *b = (const u8 __iomem *)pd; + rfbi_write_reg(RFBI_PARAM, __raw_readb(b+1)); + rfbi_write_reg(RFBI_PARAM, __raw_readb(b+0)); + ++pd; + } + pd += horiz_offset; + } + } else if (rfbi.datatype == OMAP_DSS_RFBI_DATATYPE_24 && + rfbi.parallelmode == OMAP_DSS_RFBI_PARALLELMODE_8) { + const u32 __iomem *pd = buf; + pd += start_offset; + + for (; h; --h) { + for (i = 0; i < w; ++i) { + const u8 __iomem *b = (const u8 __iomem *)pd; + rfbi_write_reg(RFBI_PARAM, __raw_readb(b+2)); + rfbi_write_reg(RFBI_PARAM, __raw_readb(b+1)); + rfbi_write_reg(RFBI_PARAM, __raw_readb(b+0)); + ++pd; + } + pd += horiz_offset; + } + } else if (rfbi.datatype == OMAP_DSS_RFBI_DATATYPE_16 && + rfbi.parallelmode == OMAP_DSS_RFBI_PARALLELMODE_16) { + const u16 __iomem *pd = buf; + pd += start_offset; + + for (; h; --h) { + for (i = 0; i < w; ++i) { + rfbi_write_reg(RFBI_PARAM, __raw_readw(pd)); + ++pd; + } + pd += horiz_offset; + } + } else { + BUG(); + } +} + +static int rfbi_transfer_area(struct omap_dss_device *dssdev, + void (*callback)(void *data), void *data) +{ + u32 l; + int r; + struct omap_overlay_manager *mgr = rfbi.output.manager; + u16 width = rfbi.timings.x_res; + u16 height = rfbi.timings.y_res; + + /*BUG_ON(callback == 0);*/ + BUG_ON(rfbi.framedone_callback != NULL); + + DSSDBG("rfbi_transfer_area %dx%d\n", width, height); + + dss_mgr_set_timings(mgr, &rfbi.timings); + + r = dss_mgr_enable(mgr); + if (r) + return r; + + rfbi.framedone_callback = callback; + rfbi.framedone_callback_data = data; + + rfbi_write_reg(RFBI_PIXEL_CNT, width * height); + + l = rfbi_read_reg(RFBI_CONTROL); + l = FLD_MOD(l, 1, 0, 0); /* enable */ + if (!rfbi.te_enabled) + l = FLD_MOD(l, 1, 4, 4); /* ITE */ + + rfbi_write_reg(RFBI_CONTROL, l); + + return 0; +} + +static void framedone_callback(void *data) +{ + void (*callback)(void *data); + + DSSDBG("FRAMEDONE\n"); + + REG_FLD_MOD(RFBI_CONTROL, 0, 0, 0); + + callback = rfbi.framedone_callback; + rfbi.framedone_callback = NULL; + + if (callback != NULL) + callback(rfbi.framedone_callback_data); +} + +#if 1 /* VERBOSE */ +static void rfbi_print_timings(void) +{ + u32 l; + u32 time; + + l = rfbi_read_reg(RFBI_CONFIG(0)); + time = 1000000000 / rfbi.l4_khz; + if (l & (1 << 4)) + time *= 2; + + DSSDBG("Tick time %u ps\n", time); + l = rfbi_read_reg(RFBI_ONOFF_TIME(0)); + DSSDBG("CSONTIME %d, CSOFFTIME %d, WEONTIME %d, WEOFFTIME %d, " + "REONTIME %d, REOFFTIME %d\n", + l & 0x0f, (l >> 4) & 0x3f, (l >> 10) & 0x0f, (l >> 14) & 0x3f, + (l >> 20) & 0x0f, (l >> 24) & 0x3f); + + l = rfbi_read_reg(RFBI_CYCLE_TIME(0)); + DSSDBG("WECYCLETIME %d, RECYCLETIME %d, CSPULSEWIDTH %d, " + "ACCESSTIME %d\n", + (l & 0x3f), (l >> 6) & 0x3f, (l >> 12) & 0x3f, + (l >> 22) & 0x3f); +} +#else +static void rfbi_print_timings(void) {} +#endif + + + + +static u32 extif_clk_period; + +static inline unsigned long round_to_extif_ticks(unsigned long ps, int div) +{ + int bus_tick = extif_clk_period * div; + return (ps + bus_tick - 1) / bus_tick * bus_tick; +} + +static int calc_reg_timing(struct rfbi_timings *t, int div) +{ + t->clk_div = div; + + t->cs_on_time = round_to_extif_ticks(t->cs_on_time, div); + + t->we_on_time = round_to_extif_ticks(t->we_on_time, div); + t->we_off_time = round_to_extif_ticks(t->we_off_time, div); + t->we_cycle_time = round_to_extif_ticks(t->we_cycle_time, div); + + t->re_on_time = round_to_extif_ticks(t->re_on_time, div); + t->re_off_time = round_to_extif_ticks(t->re_off_time, div); + t->re_cycle_time = round_to_extif_ticks(t->re_cycle_time, div); + + t->access_time = round_to_extif_ticks(t->access_time, div); + t->cs_off_time = round_to_extif_ticks(t->cs_off_time, div); + t->cs_pulse_width = round_to_extif_ticks(t->cs_pulse_width, div); + + DSSDBG("[reg]cson %d csoff %d reon %d reoff %d\n", + t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time); + DSSDBG("[reg]weon %d weoff %d recyc %d wecyc %d\n", + t->we_on_time, t->we_off_time, t->re_cycle_time, + t->we_cycle_time); + DSSDBG("[reg]rdaccess %d cspulse %d\n", + t->access_time, t->cs_pulse_width); + + return rfbi_convert_timings(t); +} + +static int calc_extif_timings(struct rfbi_timings *t) +{ + u32 max_clk_div; + int div; + + rfbi_get_clk_info(&extif_clk_period, &max_clk_div); + for (div = 1; div <= max_clk_div; div++) { + if (calc_reg_timing(t, div) == 0) + break; + } + + if (div <= max_clk_div) + return 0; + + DSSERR("can't setup timings\n"); + return -1; +} + + +static void rfbi_set_timings(int rfbi_module, struct rfbi_timings *t) +{ + int r; + + if (!t->converted) { + r = calc_extif_timings(t); + if (r < 0) + DSSERR("Failed to calc timings\n"); + } + + BUG_ON(!t->converted); + + rfbi_write_reg(RFBI_ONOFF_TIME(rfbi_module), t->tim[0]); + rfbi_write_reg(RFBI_CYCLE_TIME(rfbi_module), t->tim[1]); + + /* TIMEGRANULARITY */ + REG_FLD_MOD(RFBI_CONFIG(rfbi_module), + (t->tim[2] ? 1 : 0), 4, 4); + + rfbi_print_timings(); +} + +static int ps_to_rfbi_ticks(int time, int div) +{ + unsigned long tick_ps; + int ret; + + /* Calculate in picosecs to yield more exact results */ + tick_ps = 1000000000 / (rfbi.l4_khz) * div; + + ret = (time + tick_ps - 1) / tick_ps; + + return ret; +} + +static void rfbi_get_clk_info(u32 *clk_period, u32 *max_clk_div) +{ + *clk_period = 1000000000 / rfbi.l4_khz; + *max_clk_div = 2; +} + +static int rfbi_convert_timings(struct rfbi_timings *t) +{ + u32 l; + int reon, reoff, weon, weoff, cson, csoff, cs_pulse; + int actim, recyc, wecyc; + int div = t->clk_div; + + if (div <= 0 || div > 2) + return -1; + + /* Make sure that after conversion it still holds that: + * weoff > weon, reoff > reon, recyc >= reoff, wecyc >= weoff, + * csoff > cson, csoff >= max(weoff, reoff), actim > reon + */ + weon = ps_to_rfbi_ticks(t->we_on_time, div); + weoff = ps_to_rfbi_ticks(t->we_off_time, div); + if (weoff <= weon) + weoff = weon + 1; + if (weon > 0x0f) + return -1; + if (weoff > 0x3f) + return -1; + + reon = ps_to_rfbi_ticks(t->re_on_time, div); + reoff = ps_to_rfbi_ticks(t->re_off_time, div); + if (reoff <= reon) + reoff = reon + 1; + if (reon > 0x0f) + return -1; + if (reoff > 0x3f) + return -1; + + cson = ps_to_rfbi_ticks(t->cs_on_time, div); + csoff = ps_to_rfbi_ticks(t->cs_off_time, div); + if (csoff <= cson) + csoff = cson + 1; + if (csoff < max(weoff, reoff)) + csoff = max(weoff, reoff); + if (cson > 0x0f) + return -1; + if (csoff > 0x3f) + return -1; + + l = cson; + l |= csoff << 4; + l |= weon << 10; + l |= weoff << 14; + l |= reon << 20; + l |= reoff << 24; + + t->tim[0] = l; + + actim = ps_to_rfbi_ticks(t->access_time, div); + if (actim <= reon) + actim = reon + 1; + if (actim > 0x3f) + return -1; + + wecyc = ps_to_rfbi_ticks(t->we_cycle_time, div); + if (wecyc < weoff) + wecyc = weoff; + if (wecyc > 0x3f) + return -1; + + recyc = ps_to_rfbi_ticks(t->re_cycle_time, div); + if (recyc < reoff) + recyc = reoff; + if (recyc > 0x3f) + return -1; + + cs_pulse = ps_to_rfbi_ticks(t->cs_pulse_width, div); + if (cs_pulse > 0x3f) + return -1; + + l = wecyc; + l |= recyc << 6; + l |= cs_pulse << 12; + l |= actim << 22; + + t->tim[1] = l; + + t->tim[2] = div - 1; + + t->converted = 1; + + return 0; +} + +/* xxx FIX module selection missing */ +static int rfbi_setup_te(enum omap_rfbi_te_mode mode, + unsigned hs_pulse_time, unsigned vs_pulse_time, + int hs_pol_inv, int vs_pol_inv, int extif_div) +{ + int hs, vs; + int min; + u32 l; + + hs = ps_to_rfbi_ticks(hs_pulse_time, 1); + vs = ps_to_rfbi_ticks(vs_pulse_time, 1); + if (hs < 2) + return -EDOM; + if (mode == OMAP_DSS_RFBI_TE_MODE_2) + min = 2; + else /* OMAP_DSS_RFBI_TE_MODE_1 */ + min = 4; + if (vs < min) + return -EDOM; + if (vs == hs) + return -EINVAL; + rfbi.te_mode = mode; + DSSDBG("setup_te: mode %d hs %d vs %d hs_inv %d vs_inv %d\n", + mode, hs, vs, hs_pol_inv, vs_pol_inv); + + rfbi_write_reg(RFBI_HSYNC_WIDTH, hs); + rfbi_write_reg(RFBI_VSYNC_WIDTH, vs); + + l = rfbi_read_reg(RFBI_CONFIG(0)); + if (hs_pol_inv) + l &= ~(1 << 21); + else + l |= 1 << 21; + if (vs_pol_inv) + l &= ~(1 << 20); + else + l |= 1 << 20; + + return 0; +} + +/* xxx FIX module selection missing */ +static int rfbi_enable_te(bool enable, unsigned line) +{ + u32 l; + + DSSDBG("te %d line %d mode %d\n", enable, line, rfbi.te_mode); + if (line > (1 << 11) - 1) + return -EINVAL; + + l = rfbi_read_reg(RFBI_CONFIG(0)); + l &= ~(0x3 << 2); + if (enable) { + rfbi.te_enabled = 1; + l |= rfbi.te_mode << 2; + } else + rfbi.te_enabled = 0; + rfbi_write_reg(RFBI_CONFIG(0), l); + rfbi_write_reg(RFBI_LINE_NUMBER, line); + + return 0; +} + +static int rfbi_configure_bus(int rfbi_module, int bpp, int lines) +{ + u32 l; + int cycle1 = 0, cycle2 = 0, cycle3 = 0; + enum omap_rfbi_cycleformat cycleformat; + enum omap_rfbi_datatype datatype; + enum omap_rfbi_parallelmode parallelmode; + + switch (bpp) { + case 12: + datatype = OMAP_DSS_RFBI_DATATYPE_12; + break; + case 16: + datatype = OMAP_DSS_RFBI_DATATYPE_16; + break; + case 18: + datatype = OMAP_DSS_RFBI_DATATYPE_18; + break; + case 24: + datatype = OMAP_DSS_RFBI_DATATYPE_24; + break; + default: + BUG(); + return 1; + } + rfbi.datatype = datatype; + + switch (lines) { + case 8: + parallelmode = OMAP_DSS_RFBI_PARALLELMODE_8; + break; + case 9: + parallelmode = OMAP_DSS_RFBI_PARALLELMODE_9; + break; + case 12: + parallelmode = OMAP_DSS_RFBI_PARALLELMODE_12; + break; + case 16: + parallelmode = OMAP_DSS_RFBI_PARALLELMODE_16; + break; + default: + BUG(); + return 1; + } + rfbi.parallelmode = parallelmode; + + if ((bpp % lines) == 0) { + switch (bpp / lines) { + case 1: + cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_1_1; + break; + case 2: + cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_2_1; + break; + case 3: + cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_3_1; + break; + default: + BUG(); + return 1; + } + } else if ((2 * bpp % lines) == 0) { + if ((2 * bpp / lines) == 3) + cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_3_2; + else { + BUG(); + return 1; + } + } else { + BUG(); + return 1; + } + + switch (cycleformat) { + case OMAP_DSS_RFBI_CYCLEFORMAT_1_1: + cycle1 = lines; + break; + + case OMAP_DSS_RFBI_CYCLEFORMAT_2_1: + cycle1 = lines; + cycle2 = lines; + break; + + case OMAP_DSS_RFBI_CYCLEFORMAT_3_1: + cycle1 = lines; + cycle2 = lines; + cycle3 = lines; + break; + + case OMAP_DSS_RFBI_CYCLEFORMAT_3_2: + cycle1 = lines; + cycle2 = (lines / 2) | ((lines / 2) << 16); + cycle3 = (lines << 16); + break; + } + + REG_FLD_MOD(RFBI_CONTROL, 0, 3, 2); /* clear CS */ + + l = 0; + l |= FLD_VAL(parallelmode, 1, 0); + l |= FLD_VAL(0, 3, 2); /* TRIGGERMODE: ITE */ + l |= FLD_VAL(0, 4, 4); /* TIMEGRANULARITY */ + l |= FLD_VAL(datatype, 6, 5); + /* l |= FLD_VAL(2, 8, 7); */ /* L4FORMAT, 2pix/L4 */ + l |= FLD_VAL(0, 8, 7); /* L4FORMAT, 1pix/L4 */ + l |= FLD_VAL(cycleformat, 10, 9); + l |= FLD_VAL(0, 12, 11); /* UNUSEDBITS */ + l |= FLD_VAL(0, 16, 16); /* A0POLARITY */ + l |= FLD_VAL(0, 17, 17); /* REPOLARITY */ + l |= FLD_VAL(0, 18, 18); /* WEPOLARITY */ + l |= FLD_VAL(0, 19, 19); /* CSPOLARITY */ + l |= FLD_VAL(1, 20, 20); /* TE_VSYNC_POLARITY */ + l |= FLD_VAL(1, 21, 21); /* HSYNCPOLARITY */ + rfbi_write_reg(RFBI_CONFIG(rfbi_module), l); + + rfbi_write_reg(RFBI_DATA_CYCLE1(rfbi_module), cycle1); + rfbi_write_reg(RFBI_DATA_CYCLE2(rfbi_module), cycle2); + rfbi_write_reg(RFBI_DATA_CYCLE3(rfbi_module), cycle3); + + + l = rfbi_read_reg(RFBI_CONTROL); + l = FLD_MOD(l, rfbi_module+1, 3, 2); /* Select CSx */ + l = FLD_MOD(l, 0, 1, 1); /* clear bypass */ + rfbi_write_reg(RFBI_CONTROL, l); + + + DSSDBG("RFBI config: bpp %d, lines %d, cycles: 0x%x 0x%x 0x%x\n", + bpp, lines, cycle1, cycle2, cycle3); + + return 0; +} + +static int rfbi_configure(struct omap_dss_device *dssdev) +{ + return rfbi_configure_bus(dssdev->phy.rfbi.channel, rfbi.pixel_size, + rfbi.data_lines); +} + +static int rfbi_update(struct omap_dss_device *dssdev, void (*callback)(void *), + void *data) +{ + return rfbi_transfer_area(dssdev, callback, data); +} + +static void rfbi_set_size(struct omap_dss_device *dssdev, u16 w, u16 h) +{ + rfbi.timings.x_res = w; + rfbi.timings.y_res = h; +} + +static void rfbi_set_pixel_size(struct omap_dss_device *dssdev, int pixel_size) +{ + rfbi.pixel_size = pixel_size; +} + +static void rfbi_set_data_lines(struct omap_dss_device *dssdev, int data_lines) +{ + rfbi.data_lines = data_lines; +} + +static void rfbi_set_interface_timings(struct omap_dss_device *dssdev, + struct rfbi_timings *timings) +{ + rfbi.intf_timings = *timings; +} + +static void rfbi_dump_regs(struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, rfbi_read_reg(r)) + + if (rfbi_runtime_get()) + return; + + DUMPREG(RFBI_REVISION); + DUMPREG(RFBI_SYSCONFIG); + DUMPREG(RFBI_SYSSTATUS); + DUMPREG(RFBI_CONTROL); + DUMPREG(RFBI_PIXEL_CNT); + DUMPREG(RFBI_LINE_NUMBER); + DUMPREG(RFBI_CMD); + DUMPREG(RFBI_PARAM); + DUMPREG(RFBI_DATA); + DUMPREG(RFBI_READ); + DUMPREG(RFBI_STATUS); + + DUMPREG(RFBI_CONFIG(0)); + DUMPREG(RFBI_ONOFF_TIME(0)); + DUMPREG(RFBI_CYCLE_TIME(0)); + DUMPREG(RFBI_DATA_CYCLE1(0)); + DUMPREG(RFBI_DATA_CYCLE2(0)); + DUMPREG(RFBI_DATA_CYCLE3(0)); + + DUMPREG(RFBI_CONFIG(1)); + DUMPREG(RFBI_ONOFF_TIME(1)); + DUMPREG(RFBI_CYCLE_TIME(1)); + DUMPREG(RFBI_DATA_CYCLE1(1)); + DUMPREG(RFBI_DATA_CYCLE2(1)); + DUMPREG(RFBI_DATA_CYCLE3(1)); + + DUMPREG(RFBI_VSYNC_WIDTH); + DUMPREG(RFBI_HSYNC_WIDTH); + + rfbi_runtime_put(); +#undef DUMPREG +} + +static void rfbi_config_lcd_manager(struct omap_dss_device *dssdev) +{ + struct omap_overlay_manager *mgr = rfbi.output.manager; + struct dss_lcd_mgr_config mgr_config; + + mgr_config.io_pad_mode = DSS_IO_PAD_MODE_RFBI; + + mgr_config.stallmode = true; + /* Do we need fifohandcheck for RFBI? */ + mgr_config.fifohandcheck = false; + + mgr_config.video_port_width = rfbi.pixel_size; + mgr_config.lcden_sig_polarity = 0; + + dss_mgr_set_lcd_config(mgr, &mgr_config); + + /* + * Set rfbi.timings with default values, the x_res and y_res fields + * are expected to be already configured by the panel driver via + * omapdss_rfbi_set_size() + */ + rfbi.timings.hsw = 1; + rfbi.timings.hfp = 1; + rfbi.timings.hbp = 1; + rfbi.timings.vsw = 1; + rfbi.timings.vfp = 0; + rfbi.timings.vbp = 0; + + rfbi.timings.interlace = false; + rfbi.timings.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + rfbi.timings.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + rfbi.timings.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + rfbi.timings.de_level = OMAPDSS_SIG_ACTIVE_HIGH; + rfbi.timings.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + + dss_mgr_set_timings(mgr, &rfbi.timings); +} + +static int rfbi_display_enable(struct omap_dss_device *dssdev) +{ + struct omap_dss_device *out = &rfbi.output; + int r; + + if (out == NULL || out->manager == NULL) { + DSSERR("failed to enable display: no output/manager\n"); + return -ENODEV; + } + + r = rfbi_runtime_get(); + if (r) + return r; + + r = dss_mgr_register_framedone_handler(out->manager, + framedone_callback, NULL); + if (r) { + DSSERR("can't get FRAMEDONE irq\n"); + goto err1; + } + + rfbi_config_lcd_manager(dssdev); + + rfbi_configure_bus(dssdev->phy.rfbi.channel, rfbi.pixel_size, + rfbi.data_lines); + + rfbi_set_timings(dssdev->phy.rfbi.channel, &rfbi.intf_timings); + + return 0; +err1: + rfbi_runtime_put(); + return r; +} + +static void rfbi_display_disable(struct omap_dss_device *dssdev) +{ + struct omap_dss_device *out = &rfbi.output; + + dss_mgr_unregister_framedone_handler(out->manager, + framedone_callback, NULL); + + rfbi_runtime_put(); +} + +static int rfbi_init_display(struct omap_dss_device *dssdev) +{ + rfbi.dssdev[dssdev->phy.rfbi.channel] = dssdev; + return 0; +} + +static void rfbi_init_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &rfbi.output; + + out->dev = &pdev->dev; + out->id = OMAP_DSS_OUTPUT_DBI; + out->output_type = OMAP_DISPLAY_TYPE_DBI; + out->name = "rfbi.0"; + out->dispc_channel = OMAP_DSS_CHANNEL_LCD; + out->owner = THIS_MODULE; + + omapdss_register_output(out); +} + +static void __exit rfbi_uninit_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &rfbi.output; + + omapdss_unregister_output(out); +} + +/* RFBI HW IP initialisation */ +static int omap_rfbihw_probe(struct platform_device *pdev) +{ + u32 rev; + struct resource *rfbi_mem; + struct clk *clk; + int r; + + rfbi.pdev = pdev; + + sema_init(&rfbi.bus_lock, 1); + + rfbi_mem = platform_get_resource(rfbi.pdev, IORESOURCE_MEM, 0); + if (!rfbi_mem) { + DSSERR("can't get IORESOURCE_MEM RFBI\n"); + return -EINVAL; + } + + rfbi.base = devm_ioremap(&pdev->dev, rfbi_mem->start, + resource_size(rfbi_mem)); + if (!rfbi.base) { + DSSERR("can't ioremap RFBI\n"); + return -ENOMEM; + } + + clk = clk_get(&pdev->dev, "ick"); + if (IS_ERR(clk)) { + DSSERR("can't get ick\n"); + return PTR_ERR(clk); + } + + rfbi.l4_khz = clk_get_rate(clk) / 1000; + + clk_put(clk); + + pm_runtime_enable(&pdev->dev); + + r = rfbi_runtime_get(); + if (r) + goto err_runtime_get; + + msleep(10); + + rev = rfbi_read_reg(RFBI_REVISION); + dev_dbg(&pdev->dev, "OMAP RFBI rev %d.%d\n", + FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + + rfbi_runtime_put(); + + dss_debugfs_create_file("rfbi", rfbi_dump_regs); + + rfbi_init_output(pdev); + + return 0; + +err_runtime_get: + pm_runtime_disable(&pdev->dev); + return r; +} + +static int __exit omap_rfbihw_remove(struct platform_device *pdev) +{ + rfbi_uninit_output(pdev); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int rfbi_runtime_suspend(struct device *dev) +{ + dispc_runtime_put(); + + return 0; +} + +static int rfbi_runtime_resume(struct device *dev) +{ + int r; + + r = dispc_runtime_get(); + if (r < 0) + return r; + + return 0; +} + +static const struct dev_pm_ops rfbi_pm_ops = { + .runtime_suspend = rfbi_runtime_suspend, + .runtime_resume = rfbi_runtime_resume, +}; + +static struct platform_driver omap_rfbihw_driver = { + .probe = omap_rfbihw_probe, + .remove = __exit_p(omap_rfbihw_remove), + .driver = { + .name = "omapdss_rfbi", + .owner = THIS_MODULE, + .pm = &rfbi_pm_ops, + }, +}; + +int __init rfbi_init_platform_driver(void) +{ + return platform_driver_register(&omap_rfbihw_driver); +} + +void __exit rfbi_uninit_platform_driver(void) +{ + platform_driver_unregister(&omap_rfbihw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/sdi.c b/drivers/video/fbdev/omap2/dss/sdi.c new file mode 100644 index 000000000000..911dcc9173a6 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/sdi.c @@ -0,0 +1,433 @@ +/* + * linux/drivers/video/omap2/dss/sdi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "SDI" + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/regulator/consumer.h> +#include <linux/export.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/of.h> + +#include <video/omapdss.h> +#include "dss.h" + +static struct { + struct platform_device *pdev; + + bool update_enabled; + struct regulator *vdds_sdi_reg; + + struct dss_lcd_mgr_config mgr_config; + struct omap_video_timings timings; + int datapairs; + + struct omap_dss_device output; + + bool port_initialized; +} sdi; + +struct sdi_clk_calc_ctx { + unsigned long pck_min, pck_max; + + unsigned long fck; + struct dispc_clock_info dispc_cinfo; +}; + +static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, + unsigned long pck, void *data) +{ + struct sdi_clk_calc_ctx *ctx = data; + + ctx->dispc_cinfo.lck_div = lckd; + ctx->dispc_cinfo.pck_div = pckd; + ctx->dispc_cinfo.lck = lck; + ctx->dispc_cinfo.pck = pck; + + return true; +} + +static bool dpi_calc_dss_cb(unsigned long fck, void *data) +{ + struct sdi_clk_calc_ctx *ctx = data; + + ctx->fck = fck; + + return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max, + dpi_calc_dispc_cb, ctx); +} + +static int sdi_calc_clock_div(unsigned long pclk, + unsigned long *fck, + struct dispc_clock_info *dispc_cinfo) +{ + int i; + struct sdi_clk_calc_ctx ctx; + + /* + * DSS fclk gives us very few possibilities, so finding a good pixel + * clock may not be possible. We try multiple times to find the clock, + * each time widening the pixel clock range we look for, up to + * +/- 1MHz. + */ + + for (i = 0; i < 10; ++i) { + bool ok; + + memset(&ctx, 0, sizeof(ctx)); + if (pclk > 1000 * i * i * i) + ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu); + else + ctx.pck_min = 0; + ctx.pck_max = pclk + 1000 * i * i * i; + + ok = dss_div_calc(pclk, ctx.pck_min, dpi_calc_dss_cb, &ctx); + if (ok) { + *fck = ctx.fck; + *dispc_cinfo = ctx.dispc_cinfo; + return 0; + } + } + + return -EINVAL; +} + +static void sdi_config_lcd_manager(struct omap_dss_device *dssdev) +{ + struct omap_overlay_manager *mgr = sdi.output.manager; + + sdi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; + + sdi.mgr_config.stallmode = false; + sdi.mgr_config.fifohandcheck = false; + + sdi.mgr_config.video_port_width = 24; + sdi.mgr_config.lcden_sig_polarity = 1; + + dss_mgr_set_lcd_config(mgr, &sdi.mgr_config); +} + +static int sdi_display_enable(struct omap_dss_device *dssdev) +{ + struct omap_dss_device *out = &sdi.output; + struct omap_video_timings *t = &sdi.timings; + unsigned long fck; + struct dispc_clock_info dispc_cinfo; + unsigned long pck; + int r; + + if (out == NULL || out->manager == NULL) { + DSSERR("failed to enable display: no output/manager\n"); + return -ENODEV; + } + + r = regulator_enable(sdi.vdds_sdi_reg); + if (r) + goto err_reg_enable; + + r = dispc_runtime_get(); + if (r) + goto err_get_dispc; + + /* 15.5.9.1.2 */ + t->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + t->sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + + r = sdi_calc_clock_div(t->pixelclock, &fck, &dispc_cinfo); + if (r) + goto err_calc_clock_div; + + sdi.mgr_config.clock_info = dispc_cinfo; + + pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div; + + if (pck != t->pixelclock) { + DSSWARN("Could not find exact pixel clock. Requested %d Hz, got %lu Hz\n", + t->pixelclock, pck); + + t->pixelclock = pck; + } + + + dss_mgr_set_timings(out->manager, t); + + r = dss_set_fck_rate(fck); + if (r) + goto err_set_dss_clock_div; + + sdi_config_lcd_manager(dssdev); + + /* + * LCLK and PCLK divisors are located in shadow registers, and we + * normally write them to DISPC registers when enabling the output. + * However, SDI uses pck-free as source clock for its PLL, and pck-free + * is affected by the divisors. And as we need the PLL before enabling + * the output, we need to write the divisors early. + * + * It seems just writing to the DISPC register is enough, and we don't + * need to care about the shadow register mechanism for pck-free. The + * exact reason for this is unknown. + */ + dispc_mgr_set_clock_div(out->manager->id, &sdi.mgr_config.clock_info); + + dss_sdi_init(sdi.datapairs); + r = dss_sdi_enable(); + if (r) + goto err_sdi_enable; + mdelay(2); + + r = dss_mgr_enable(out->manager); + if (r) + goto err_mgr_enable; + + return 0; + +err_mgr_enable: + dss_sdi_disable(); +err_sdi_enable: +err_set_dss_clock_div: +err_calc_clock_div: + dispc_runtime_put(); +err_get_dispc: + regulator_disable(sdi.vdds_sdi_reg); +err_reg_enable: + return r; +} + +static void sdi_display_disable(struct omap_dss_device *dssdev) +{ + struct omap_overlay_manager *mgr = sdi.output.manager; + + dss_mgr_disable(mgr); + + dss_sdi_disable(); + + dispc_runtime_put(); + + regulator_disable(sdi.vdds_sdi_reg); +} + +static void sdi_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + sdi.timings = *timings; +} + +static void sdi_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + *timings = sdi.timings; +} + +static int sdi_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct omap_overlay_manager *mgr = sdi.output.manager; + + if (mgr && !dispc_mgr_timings_ok(mgr->id, timings)) + return -EINVAL; + + if (timings->pixelclock == 0) + return -EINVAL; + + return 0; +} + +static void sdi_set_datapairs(struct omap_dss_device *dssdev, int datapairs) +{ + sdi.datapairs = datapairs; +} + +static int sdi_init_regulator(void) +{ + struct regulator *vdds_sdi; + + if (sdi.vdds_sdi_reg) + return 0; + + vdds_sdi = devm_regulator_get(&sdi.pdev->dev, "vdds_sdi"); + if (IS_ERR(vdds_sdi)) { + if (PTR_ERR(vdds_sdi) != -EPROBE_DEFER) + DSSERR("can't get VDDS_SDI regulator\n"); + return PTR_ERR(vdds_sdi); + } + + sdi.vdds_sdi_reg = vdds_sdi; + + return 0; +} + +static int sdi_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct omap_overlay_manager *mgr; + int r; + + r = sdi_init_regulator(); + if (r) + return r; + + mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); + if (!mgr) + return -ENODEV; + + r = dss_mgr_connect(mgr, dssdev); + if (r) + return r; + + r = omapdss_output_set_device(dssdev, dst); + if (r) { + DSSERR("failed to connect output to new device: %s\n", + dst->name); + dss_mgr_disconnect(mgr, dssdev); + return r; + } + + return 0; +} + +static void sdi_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + WARN_ON(dst != dssdev->dst); + + if (dst != dssdev->dst) + return; + + omapdss_output_unset_device(dssdev); + + if (dssdev->manager) + dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_sdi_ops sdi_ops = { + .connect = sdi_connect, + .disconnect = sdi_disconnect, + + .enable = sdi_display_enable, + .disable = sdi_display_disable, + + .check_timings = sdi_check_timings, + .set_timings = sdi_set_timings, + .get_timings = sdi_get_timings, + + .set_datapairs = sdi_set_datapairs, +}; + +static void sdi_init_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &sdi.output; + + out->dev = &pdev->dev; + out->id = OMAP_DSS_OUTPUT_SDI; + out->output_type = OMAP_DISPLAY_TYPE_SDI; + out->name = "sdi.0"; + out->dispc_channel = OMAP_DSS_CHANNEL_LCD; + out->ops.sdi = &sdi_ops; + out->owner = THIS_MODULE; + + omapdss_register_output(out); +} + +static void __exit sdi_uninit_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &sdi.output; + + omapdss_unregister_output(out); +} + +static int omap_sdi_probe(struct platform_device *pdev) +{ + sdi.pdev = pdev; + + sdi_init_output(pdev); + + return 0; +} + +static int __exit omap_sdi_remove(struct platform_device *pdev) +{ + sdi_uninit_output(pdev); + + return 0; +} + +static struct platform_driver omap_sdi_driver = { + .probe = omap_sdi_probe, + .remove = __exit_p(omap_sdi_remove), + .driver = { + .name = "omapdss_sdi", + .owner = THIS_MODULE, + }, +}; + +int __init sdi_init_platform_driver(void) +{ + return platform_driver_register(&omap_sdi_driver); +} + +void __exit sdi_uninit_platform_driver(void) +{ + platform_driver_unregister(&omap_sdi_driver); +} + +int __init sdi_init_port(struct platform_device *pdev, struct device_node *port) +{ + struct device_node *ep; + u32 datapairs; + int r; + + ep = omapdss_of_get_next_endpoint(port, NULL); + if (!ep) + return 0; + + r = of_property_read_u32(ep, "datapairs", &datapairs); + if (r) { + DSSERR("failed to parse datapairs\n"); + goto err_datapairs; + } + + sdi.datapairs = datapairs; + + of_node_put(ep); + + sdi.pdev = pdev; + + sdi_init_output(pdev); + + sdi.port_initialized = true; + + return 0; + +err_datapairs: + of_node_put(ep); + + return r; +} + +void __exit sdi_uninit_port(void) +{ + if (!sdi.port_initialized) + return; + + sdi_uninit_output(sdi.pdev); +} diff --git a/drivers/video/fbdev/omap2/dss/venc.c b/drivers/video/fbdev/omap2/dss/venc.c new file mode 100644 index 000000000000..21d81113962b --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/venc.c @@ -0,0 +1,980 @@ +/* + * linux/drivers/video/omap2/dss/venc.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * VENC settings from TI's DSS driver + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define DSS_SUBSYS_NAME "VENC" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +/* Venc registers */ +#define VENC_REV_ID 0x00 +#define VENC_STATUS 0x04 +#define VENC_F_CONTROL 0x08 +#define VENC_VIDOUT_CTRL 0x10 +#define VENC_SYNC_CTRL 0x14 +#define VENC_LLEN 0x1C +#define VENC_FLENS 0x20 +#define VENC_HFLTR_CTRL 0x24 +#define VENC_CC_CARR_WSS_CARR 0x28 +#define VENC_C_PHASE 0x2C +#define VENC_GAIN_U 0x30 +#define VENC_GAIN_V 0x34 +#define VENC_GAIN_Y 0x38 +#define VENC_BLACK_LEVEL 0x3C +#define VENC_BLANK_LEVEL 0x40 +#define VENC_X_COLOR 0x44 +#define VENC_M_CONTROL 0x48 +#define VENC_BSTAMP_WSS_DATA 0x4C +#define VENC_S_CARR 0x50 +#define VENC_LINE21 0x54 +#define VENC_LN_SEL 0x58 +#define VENC_L21__WC_CTL 0x5C +#define VENC_HTRIGGER_VTRIGGER 0x60 +#define VENC_SAVID__EAVID 0x64 +#define VENC_FLEN__FAL 0x68 +#define VENC_LAL__PHASE_RESET 0x6C +#define VENC_HS_INT_START_STOP_X 0x70 +#define VENC_HS_EXT_START_STOP_X 0x74 +#define VENC_VS_INT_START_X 0x78 +#define VENC_VS_INT_STOP_X__VS_INT_START_Y 0x7C +#define VENC_VS_INT_STOP_Y__VS_EXT_START_X 0x80 +#define VENC_VS_EXT_STOP_X__VS_EXT_START_Y 0x84 +#define VENC_VS_EXT_STOP_Y 0x88 +#define VENC_AVID_START_STOP_X 0x90 +#define VENC_AVID_START_STOP_Y 0x94 +#define VENC_FID_INT_START_X__FID_INT_START_Y 0xA0 +#define VENC_FID_INT_OFFSET_Y__FID_EXT_START_X 0xA4 +#define VENC_FID_EXT_START_Y__FID_EXT_OFFSET_Y 0xA8 +#define VENC_TVDETGP_INT_START_STOP_X 0xB0 +#define VENC_TVDETGP_INT_START_STOP_Y 0xB4 +#define VENC_GEN_CTRL 0xB8 +#define VENC_OUTPUT_CONTROL 0xC4 +#define VENC_OUTPUT_TEST 0xC8 +#define VENC_DAC_B__DAC_C 0xC8 + +struct venc_config { + u32 f_control; + u32 vidout_ctrl; + u32 sync_ctrl; + u32 llen; + u32 flens; + u32 hfltr_ctrl; + u32 cc_carr_wss_carr; + u32 c_phase; + u32 gain_u; + u32 gain_v; + u32 gain_y; + u32 black_level; + u32 blank_level; + u32 x_color; + u32 m_control; + u32 bstamp_wss_data; + u32 s_carr; + u32 line21; + u32 ln_sel; + u32 l21__wc_ctl; + u32 htrigger_vtrigger; + u32 savid__eavid; + u32 flen__fal; + u32 lal__phase_reset; + u32 hs_int_start_stop_x; + u32 hs_ext_start_stop_x; + u32 vs_int_start_x; + u32 vs_int_stop_x__vs_int_start_y; + u32 vs_int_stop_y__vs_ext_start_x; + u32 vs_ext_stop_x__vs_ext_start_y; + u32 vs_ext_stop_y; + u32 avid_start_stop_x; + u32 avid_start_stop_y; + u32 fid_int_start_x__fid_int_start_y; + u32 fid_int_offset_y__fid_ext_start_x; + u32 fid_ext_start_y__fid_ext_offset_y; + u32 tvdetgp_int_start_stop_x; + u32 tvdetgp_int_start_stop_y; + u32 gen_ctrl; +}; + +/* from TRM */ +static const struct venc_config venc_config_pal_trm = { + .f_control = 0, + .vidout_ctrl = 1, + .sync_ctrl = 0x40, + .llen = 0x35F, /* 863 */ + .flens = 0x270, /* 624 */ + .hfltr_ctrl = 0, + .cc_carr_wss_carr = 0x2F7225ED, + .c_phase = 0, + .gain_u = 0x111, + .gain_v = 0x181, + .gain_y = 0x140, + .black_level = 0x3B, + .blank_level = 0x3B, + .x_color = 0x7, + .m_control = 0x2, + .bstamp_wss_data = 0x3F, + .s_carr = 0x2A098ACB, + .line21 = 0, + .ln_sel = 0x01290015, + .l21__wc_ctl = 0x0000F603, + .htrigger_vtrigger = 0, + + .savid__eavid = 0x06A70108, + .flen__fal = 0x00180270, + .lal__phase_reset = 0x00040135, + .hs_int_start_stop_x = 0x00880358, + .hs_ext_start_stop_x = 0x000F035F, + .vs_int_start_x = 0x01A70000, + .vs_int_stop_x__vs_int_start_y = 0x000001A7, + .vs_int_stop_y__vs_ext_start_x = 0x01AF0000, + .vs_ext_stop_x__vs_ext_start_y = 0x000101AF, + .vs_ext_stop_y = 0x00000025, + .avid_start_stop_x = 0x03530083, + .avid_start_stop_y = 0x026C002E, + .fid_int_start_x__fid_int_start_y = 0x0001008A, + .fid_int_offset_y__fid_ext_start_x = 0x002E0138, + .fid_ext_start_y__fid_ext_offset_y = 0x01380001, + + .tvdetgp_int_start_stop_x = 0x00140001, + .tvdetgp_int_start_stop_y = 0x00010001, + .gen_ctrl = 0x00FF0000, +}; + +/* from TRM */ +static const struct venc_config venc_config_ntsc_trm = { + .f_control = 0, + .vidout_ctrl = 1, + .sync_ctrl = 0x8040, + .llen = 0x359, + .flens = 0x20C, + .hfltr_ctrl = 0, + .cc_carr_wss_carr = 0x043F2631, + .c_phase = 0, + .gain_u = 0x102, + .gain_v = 0x16C, + .gain_y = 0x12F, + .black_level = 0x43, + .blank_level = 0x38, + .x_color = 0x7, + .m_control = 0x1, + .bstamp_wss_data = 0x38, + .s_carr = 0x21F07C1F, + .line21 = 0, + .ln_sel = 0x01310011, + .l21__wc_ctl = 0x0000F003, + .htrigger_vtrigger = 0, + + .savid__eavid = 0x069300F4, + .flen__fal = 0x0016020C, + .lal__phase_reset = 0x00060107, + .hs_int_start_stop_x = 0x008E0350, + .hs_ext_start_stop_x = 0x000F0359, + .vs_int_start_x = 0x01A00000, + .vs_int_stop_x__vs_int_start_y = 0x020701A0, + .vs_int_stop_y__vs_ext_start_x = 0x01AC0024, + .vs_ext_stop_x__vs_ext_start_y = 0x020D01AC, + .vs_ext_stop_y = 0x00000006, + .avid_start_stop_x = 0x03480078, + .avid_start_stop_y = 0x02060024, + .fid_int_start_x__fid_int_start_y = 0x0001008A, + .fid_int_offset_y__fid_ext_start_x = 0x01AC0106, + .fid_ext_start_y__fid_ext_offset_y = 0x01060006, + + .tvdetgp_int_start_stop_x = 0x00140001, + .tvdetgp_int_start_stop_y = 0x00010001, + .gen_ctrl = 0x00F90000, +}; + +static const struct venc_config venc_config_pal_bdghi = { + .f_control = 0, + .vidout_ctrl = 0, + .sync_ctrl = 0, + .hfltr_ctrl = 0, + .x_color = 0, + .line21 = 0, + .ln_sel = 21, + .htrigger_vtrigger = 0, + .tvdetgp_int_start_stop_x = 0x00140001, + .tvdetgp_int_start_stop_y = 0x00010001, + .gen_ctrl = 0x00FB0000, + + .llen = 864-1, + .flens = 625-1, + .cc_carr_wss_carr = 0x2F7625ED, + .c_phase = 0xDF, + .gain_u = 0x111, + .gain_v = 0x181, + .gain_y = 0x140, + .black_level = 0x3e, + .blank_level = 0x3e, + .m_control = 0<<2 | 1<<1, + .bstamp_wss_data = 0x42, + .s_carr = 0x2a098acb, + .l21__wc_ctl = 0<<13 | 0x16<<8 | 0<<0, + .savid__eavid = 0x06A70108, + .flen__fal = 23<<16 | 624<<0, + .lal__phase_reset = 2<<17 | 310<<0, + .hs_int_start_stop_x = 0x00920358, + .hs_ext_start_stop_x = 0x000F035F, + .vs_int_start_x = 0x1a7<<16, + .vs_int_stop_x__vs_int_start_y = 0x000601A7, + .vs_int_stop_y__vs_ext_start_x = 0x01AF0036, + .vs_ext_stop_x__vs_ext_start_y = 0x27101af, + .vs_ext_stop_y = 0x05, + .avid_start_stop_x = 0x03530082, + .avid_start_stop_y = 0x0270002E, + .fid_int_start_x__fid_int_start_y = 0x0005008A, + .fid_int_offset_y__fid_ext_start_x = 0x002E0138, + .fid_ext_start_y__fid_ext_offset_y = 0x01380005, +}; + +const struct omap_video_timings omap_dss_pal_timings = { + .x_res = 720, + .y_res = 574, + .pixelclock = 13500000, + .hsw = 64, + .hfp = 12, + .hbp = 68, + .vsw = 5, + .vfp = 5, + .vbp = 41, + + .interlace = true, +}; +EXPORT_SYMBOL(omap_dss_pal_timings); + +const struct omap_video_timings omap_dss_ntsc_timings = { + .x_res = 720, + .y_res = 482, + .pixelclock = 13500000, + .hsw = 64, + .hfp = 16, + .hbp = 58, + .vsw = 6, + .vfp = 6, + .vbp = 31, + + .interlace = true, +}; +EXPORT_SYMBOL(omap_dss_ntsc_timings); + +static struct { + struct platform_device *pdev; + void __iomem *base; + struct mutex venc_lock; + u32 wss_data; + struct regulator *vdda_dac_reg; + + struct clk *tv_dac_clk; + + struct omap_video_timings timings; + enum omap_dss_venc_type type; + bool invert_polarity; + + struct omap_dss_device output; +} venc; + +static inline void venc_write_reg(int idx, u32 val) +{ + __raw_writel(val, venc.base + idx); +} + +static inline u32 venc_read_reg(int idx) +{ + u32 l = __raw_readl(venc.base + idx); + return l; +} + +static void venc_write_config(const struct venc_config *config) +{ + DSSDBG("write venc conf\n"); + + venc_write_reg(VENC_LLEN, config->llen); + venc_write_reg(VENC_FLENS, config->flens); + venc_write_reg(VENC_CC_CARR_WSS_CARR, config->cc_carr_wss_carr); + venc_write_reg(VENC_C_PHASE, config->c_phase); + venc_write_reg(VENC_GAIN_U, config->gain_u); + venc_write_reg(VENC_GAIN_V, config->gain_v); + venc_write_reg(VENC_GAIN_Y, config->gain_y); + venc_write_reg(VENC_BLACK_LEVEL, config->black_level); + venc_write_reg(VENC_BLANK_LEVEL, config->blank_level); + venc_write_reg(VENC_M_CONTROL, config->m_control); + venc_write_reg(VENC_BSTAMP_WSS_DATA, config->bstamp_wss_data | + venc.wss_data); + venc_write_reg(VENC_S_CARR, config->s_carr); + venc_write_reg(VENC_L21__WC_CTL, config->l21__wc_ctl); + venc_write_reg(VENC_SAVID__EAVID, config->savid__eavid); + venc_write_reg(VENC_FLEN__FAL, config->flen__fal); + venc_write_reg(VENC_LAL__PHASE_RESET, config->lal__phase_reset); + venc_write_reg(VENC_HS_INT_START_STOP_X, config->hs_int_start_stop_x); + venc_write_reg(VENC_HS_EXT_START_STOP_X, config->hs_ext_start_stop_x); + venc_write_reg(VENC_VS_INT_START_X, config->vs_int_start_x); + venc_write_reg(VENC_VS_INT_STOP_X__VS_INT_START_Y, + config->vs_int_stop_x__vs_int_start_y); + venc_write_reg(VENC_VS_INT_STOP_Y__VS_EXT_START_X, + config->vs_int_stop_y__vs_ext_start_x); + venc_write_reg(VENC_VS_EXT_STOP_X__VS_EXT_START_Y, + config->vs_ext_stop_x__vs_ext_start_y); + venc_write_reg(VENC_VS_EXT_STOP_Y, config->vs_ext_stop_y); + venc_write_reg(VENC_AVID_START_STOP_X, config->avid_start_stop_x); + venc_write_reg(VENC_AVID_START_STOP_Y, config->avid_start_stop_y); + venc_write_reg(VENC_FID_INT_START_X__FID_INT_START_Y, + config->fid_int_start_x__fid_int_start_y); + venc_write_reg(VENC_FID_INT_OFFSET_Y__FID_EXT_START_X, + config->fid_int_offset_y__fid_ext_start_x); + venc_write_reg(VENC_FID_EXT_START_Y__FID_EXT_OFFSET_Y, + config->fid_ext_start_y__fid_ext_offset_y); + + venc_write_reg(VENC_DAC_B__DAC_C, venc_read_reg(VENC_DAC_B__DAC_C)); + venc_write_reg(VENC_VIDOUT_CTRL, config->vidout_ctrl); + venc_write_reg(VENC_HFLTR_CTRL, config->hfltr_ctrl); + venc_write_reg(VENC_X_COLOR, config->x_color); + venc_write_reg(VENC_LINE21, config->line21); + venc_write_reg(VENC_LN_SEL, config->ln_sel); + venc_write_reg(VENC_HTRIGGER_VTRIGGER, config->htrigger_vtrigger); + venc_write_reg(VENC_TVDETGP_INT_START_STOP_X, + config->tvdetgp_int_start_stop_x); + venc_write_reg(VENC_TVDETGP_INT_START_STOP_Y, + config->tvdetgp_int_start_stop_y); + venc_write_reg(VENC_GEN_CTRL, config->gen_ctrl); + venc_write_reg(VENC_F_CONTROL, config->f_control); + venc_write_reg(VENC_SYNC_CTRL, config->sync_ctrl); +} + +static void venc_reset(void) +{ + int t = 1000; + + venc_write_reg(VENC_F_CONTROL, 1<<8); + while (venc_read_reg(VENC_F_CONTROL) & (1<<8)) { + if (--t == 0) { + DSSERR("Failed to reset venc\n"); + return; + } + } + +#ifdef CONFIG_OMAP2_DSS_SLEEP_AFTER_VENC_RESET + /* the magical sleep that makes things work */ + /* XXX more info? What bug this circumvents? */ + msleep(20); +#endif +} + +static int venc_runtime_get(void) +{ + int r; + + DSSDBG("venc_runtime_get\n"); + + r = pm_runtime_get_sync(&venc.pdev->dev); + WARN_ON(r < 0); + return r < 0 ? r : 0; +} + +static void venc_runtime_put(void) +{ + int r; + + DSSDBG("venc_runtime_put\n"); + + r = pm_runtime_put_sync(&venc.pdev->dev); + WARN_ON(r < 0 && r != -ENOSYS); +} + +static const struct venc_config *venc_timings_to_config( + struct omap_video_timings *timings) +{ + if (memcmp(&omap_dss_pal_timings, timings, sizeof(*timings)) == 0) + return &venc_config_pal_trm; + + if (memcmp(&omap_dss_ntsc_timings, timings, sizeof(*timings)) == 0) + return &venc_config_ntsc_trm; + + BUG(); + return NULL; +} + +static int venc_power_on(struct omap_dss_device *dssdev) +{ + struct omap_overlay_manager *mgr = venc.output.manager; + u32 l; + int r; + + r = venc_runtime_get(); + if (r) + goto err0; + + venc_reset(); + venc_write_config(venc_timings_to_config(&venc.timings)); + + dss_set_venc_output(venc.type); + dss_set_dac_pwrdn_bgz(1); + + l = 0; + + if (venc.type == OMAP_DSS_VENC_TYPE_COMPOSITE) + l |= 1 << 1; + else /* S-Video */ + l |= (1 << 0) | (1 << 2); + + if (venc.invert_polarity == false) + l |= 1 << 3; + + venc_write_reg(VENC_OUTPUT_CONTROL, l); + + dss_mgr_set_timings(mgr, &venc.timings); + + r = regulator_enable(venc.vdda_dac_reg); + if (r) + goto err1; + + r = dss_mgr_enable(mgr); + if (r) + goto err2; + + return 0; + +err2: + regulator_disable(venc.vdda_dac_reg); +err1: + venc_write_reg(VENC_OUTPUT_CONTROL, 0); + dss_set_dac_pwrdn_bgz(0); + + venc_runtime_put(); +err0: + return r; +} + +static void venc_power_off(struct omap_dss_device *dssdev) +{ + struct omap_overlay_manager *mgr = venc.output.manager; + + venc_write_reg(VENC_OUTPUT_CONTROL, 0); + dss_set_dac_pwrdn_bgz(0); + + dss_mgr_disable(mgr); + + regulator_disable(venc.vdda_dac_reg); + + venc_runtime_put(); +} + +static int venc_display_enable(struct omap_dss_device *dssdev) +{ + struct omap_dss_device *out = &venc.output; + int r; + + DSSDBG("venc_display_enable\n"); + + mutex_lock(&venc.venc_lock); + + if (out == NULL || out->manager == NULL) { + DSSERR("Failed to enable display: no output/manager\n"); + r = -ENODEV; + goto err0; + } + + r = venc_power_on(dssdev); + if (r) + goto err0; + + venc.wss_data = 0; + + mutex_unlock(&venc.venc_lock); + + return 0; +err0: + mutex_unlock(&venc.venc_lock); + return r; +} + +static void venc_display_disable(struct omap_dss_device *dssdev) +{ + DSSDBG("venc_display_disable\n"); + + mutex_lock(&venc.venc_lock); + + venc_power_off(dssdev); + + mutex_unlock(&venc.venc_lock); +} + +static void venc_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + DSSDBG("venc_set_timings\n"); + + mutex_lock(&venc.venc_lock); + + /* Reset WSS data when the TV standard changes. */ + if (memcmp(&venc.timings, timings, sizeof(*timings))) + venc.wss_data = 0; + + venc.timings = *timings; + + dispc_set_tv_pclk(13500000); + + mutex_unlock(&venc.venc_lock); +} + +static int venc_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + DSSDBG("venc_check_timings\n"); + + if (memcmp(&omap_dss_pal_timings, timings, sizeof(*timings)) == 0) + return 0; + + if (memcmp(&omap_dss_ntsc_timings, timings, sizeof(*timings)) == 0) + return 0; + + return -EINVAL; +} + +static void venc_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + mutex_lock(&venc.venc_lock); + + *timings = venc.timings; + + mutex_unlock(&venc.venc_lock); +} + +static u32 venc_get_wss(struct omap_dss_device *dssdev) +{ + /* Invert due to VENC_L21_WC_CTL:INV=1 */ + return (venc.wss_data >> 8) ^ 0xfffff; +} + +static int venc_set_wss(struct omap_dss_device *dssdev, u32 wss) +{ + const struct venc_config *config; + int r; + + DSSDBG("venc_set_wss\n"); + + mutex_lock(&venc.venc_lock); + + config = venc_timings_to_config(&venc.timings); + + /* Invert due to VENC_L21_WC_CTL:INV=1 */ + venc.wss_data = (wss ^ 0xfffff) << 8; + + r = venc_runtime_get(); + if (r) + goto err; + + venc_write_reg(VENC_BSTAMP_WSS_DATA, config->bstamp_wss_data | + venc.wss_data); + + venc_runtime_put(); + +err: + mutex_unlock(&venc.venc_lock); + + return r; +} + +static void venc_set_type(struct omap_dss_device *dssdev, + enum omap_dss_venc_type type) +{ + mutex_lock(&venc.venc_lock); + + venc.type = type; + + mutex_unlock(&venc.venc_lock); +} + +static void venc_invert_vid_out_polarity(struct omap_dss_device *dssdev, + bool invert_polarity) +{ + mutex_lock(&venc.venc_lock); + + venc.invert_polarity = invert_polarity; + + mutex_unlock(&venc.venc_lock); +} + +static int venc_init_regulator(void) +{ + struct regulator *vdda_dac; + + if (venc.vdda_dac_reg != NULL) + return 0; + + if (venc.pdev->dev.of_node) + vdda_dac = devm_regulator_get(&venc.pdev->dev, "vdda"); + else + vdda_dac = devm_regulator_get(&venc.pdev->dev, "vdda_dac"); + + if (IS_ERR(vdda_dac)) { + if (PTR_ERR(vdda_dac) != -EPROBE_DEFER) + DSSERR("can't get VDDA_DAC regulator\n"); + return PTR_ERR(vdda_dac); + } + + venc.vdda_dac_reg = vdda_dac; + + return 0; +} + +static void venc_dump_regs(struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, venc_read_reg(r)) + + if (venc_runtime_get()) + return; + + DUMPREG(VENC_F_CONTROL); + DUMPREG(VENC_VIDOUT_CTRL); + DUMPREG(VENC_SYNC_CTRL); + DUMPREG(VENC_LLEN); + DUMPREG(VENC_FLENS); + DUMPREG(VENC_HFLTR_CTRL); + DUMPREG(VENC_CC_CARR_WSS_CARR); + DUMPREG(VENC_C_PHASE); + DUMPREG(VENC_GAIN_U); + DUMPREG(VENC_GAIN_V); + DUMPREG(VENC_GAIN_Y); + DUMPREG(VENC_BLACK_LEVEL); + DUMPREG(VENC_BLANK_LEVEL); + DUMPREG(VENC_X_COLOR); + DUMPREG(VENC_M_CONTROL); + DUMPREG(VENC_BSTAMP_WSS_DATA); + DUMPREG(VENC_S_CARR); + DUMPREG(VENC_LINE21); + DUMPREG(VENC_LN_SEL); + DUMPREG(VENC_L21__WC_CTL); + DUMPREG(VENC_HTRIGGER_VTRIGGER); + DUMPREG(VENC_SAVID__EAVID); + DUMPREG(VENC_FLEN__FAL); + DUMPREG(VENC_LAL__PHASE_RESET); + DUMPREG(VENC_HS_INT_START_STOP_X); + DUMPREG(VENC_HS_EXT_START_STOP_X); + DUMPREG(VENC_VS_INT_START_X); + DUMPREG(VENC_VS_INT_STOP_X__VS_INT_START_Y); + DUMPREG(VENC_VS_INT_STOP_Y__VS_EXT_START_X); + DUMPREG(VENC_VS_EXT_STOP_X__VS_EXT_START_Y); + DUMPREG(VENC_VS_EXT_STOP_Y); + DUMPREG(VENC_AVID_START_STOP_X); + DUMPREG(VENC_AVID_START_STOP_Y); + DUMPREG(VENC_FID_INT_START_X__FID_INT_START_Y); + DUMPREG(VENC_FID_INT_OFFSET_Y__FID_EXT_START_X); + DUMPREG(VENC_FID_EXT_START_Y__FID_EXT_OFFSET_Y); + DUMPREG(VENC_TVDETGP_INT_START_STOP_X); + DUMPREG(VENC_TVDETGP_INT_START_STOP_Y); + DUMPREG(VENC_GEN_CTRL); + DUMPREG(VENC_OUTPUT_CONTROL); + DUMPREG(VENC_OUTPUT_TEST); + + venc_runtime_put(); + +#undef DUMPREG +} + +static int venc_get_clocks(struct platform_device *pdev) +{ + struct clk *clk; + + if (dss_has_feature(FEAT_VENC_REQUIRES_TV_DAC_CLK)) { + clk = devm_clk_get(&pdev->dev, "tv_dac_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get tv_dac_clk\n"); + return PTR_ERR(clk); + } + } else { + clk = NULL; + } + + venc.tv_dac_clk = clk; + + return 0; +} + +static int venc_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct omap_overlay_manager *mgr; + int r; + + r = venc_init_regulator(); + if (r) + return r; + + mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); + if (!mgr) + return -ENODEV; + + r = dss_mgr_connect(mgr, dssdev); + if (r) + return r; + + r = omapdss_output_set_device(dssdev, dst); + if (r) { + DSSERR("failed to connect output to new device: %s\n", + dst->name); + dss_mgr_disconnect(mgr, dssdev); + return r; + } + + return 0; +} + +static void venc_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + WARN_ON(dst != dssdev->dst); + + if (dst != dssdev->dst) + return; + + omapdss_output_unset_device(dssdev); + + if (dssdev->manager) + dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_atv_ops venc_ops = { + .connect = venc_connect, + .disconnect = venc_disconnect, + + .enable = venc_display_enable, + .disable = venc_display_disable, + + .check_timings = venc_check_timings, + .set_timings = venc_set_timings, + .get_timings = venc_get_timings, + + .set_type = venc_set_type, + .invert_vid_out_polarity = venc_invert_vid_out_polarity, + + .set_wss = venc_set_wss, + .get_wss = venc_get_wss, +}; + +static void venc_init_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &venc.output; + + out->dev = &pdev->dev; + out->id = OMAP_DSS_OUTPUT_VENC; + out->output_type = OMAP_DISPLAY_TYPE_VENC; + out->name = "venc.0"; + out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; + out->ops.atv = &venc_ops; + out->owner = THIS_MODULE; + + omapdss_register_output(out); +} + +static void __exit venc_uninit_output(struct platform_device *pdev) +{ + struct omap_dss_device *out = &venc.output; + + omapdss_unregister_output(out); +} + +static int venc_probe_of(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device_node *ep; + u32 channels; + int r; + + ep = omapdss_of_get_first_endpoint(node); + if (!ep) + return 0; + + venc.invert_polarity = of_property_read_bool(ep, "ti,invert-polarity"); + + r = of_property_read_u32(ep, "ti,channels", &channels); + if (r) { + dev_err(&pdev->dev, + "failed to read property 'ti,channels': %d\n", r); + goto err; + } + + switch (channels) { + case 1: + venc.type = OMAP_DSS_VENC_TYPE_COMPOSITE; + break; + case 2: + venc.type = OMAP_DSS_VENC_TYPE_SVIDEO; + break; + default: + dev_err(&pdev->dev, "bad channel propert '%d'\n", channels); + r = -EINVAL; + goto err; + } + + of_node_put(ep); + + return 0; +err: + of_node_put(ep); + + return 0; +} + +/* VENC HW IP initialisation */ +static int omap_venchw_probe(struct platform_device *pdev) +{ + u8 rev_id; + struct resource *venc_mem; + int r; + + venc.pdev = pdev; + + mutex_init(&venc.venc_lock); + + venc.wss_data = 0; + + venc_mem = platform_get_resource(venc.pdev, IORESOURCE_MEM, 0); + if (!venc_mem) { + DSSERR("can't get IORESOURCE_MEM VENC\n"); + return -EINVAL; + } + + venc.base = devm_ioremap(&pdev->dev, venc_mem->start, + resource_size(venc_mem)); + if (!venc.base) { + DSSERR("can't ioremap VENC\n"); + return -ENOMEM; + } + + r = venc_get_clocks(pdev); + if (r) + return r; + + pm_runtime_enable(&pdev->dev); + + r = venc_runtime_get(); + if (r) + goto err_runtime_get; + + rev_id = (u8)(venc_read_reg(VENC_REV_ID) & 0xff); + dev_dbg(&pdev->dev, "OMAP VENC rev %d\n", rev_id); + + venc_runtime_put(); + + if (pdev->dev.of_node) { + r = venc_probe_of(pdev); + if (r) { + DSSERR("Invalid DT data\n"); + goto err_probe_of; + } + } + + dss_debugfs_create_file("venc", venc_dump_regs); + + venc_init_output(pdev); + + return 0; + +err_probe_of: +err_runtime_get: + pm_runtime_disable(&pdev->dev); + return r; +} + +static int __exit omap_venchw_remove(struct platform_device *pdev) +{ + venc_uninit_output(pdev); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int venc_runtime_suspend(struct device *dev) +{ + if (venc.tv_dac_clk) + clk_disable_unprepare(venc.tv_dac_clk); + + dispc_runtime_put(); + + return 0; +} + +static int venc_runtime_resume(struct device *dev) +{ + int r; + + r = dispc_runtime_get(); + if (r < 0) + return r; + + if (venc.tv_dac_clk) + clk_prepare_enable(venc.tv_dac_clk); + + return 0; +} + +static const struct dev_pm_ops venc_pm_ops = { + .runtime_suspend = venc_runtime_suspend, + .runtime_resume = venc_runtime_resume, +}; + + +static const struct of_device_id venc_of_match[] = { + { .compatible = "ti,omap2-venc", }, + { .compatible = "ti,omap3-venc", }, + { .compatible = "ti,omap4-venc", }, + {}, +}; + +static struct platform_driver omap_venchw_driver = { + .probe = omap_venchw_probe, + .remove = __exit_p(omap_venchw_remove), + .driver = { + .name = "omapdss_venc", + .owner = THIS_MODULE, + .pm = &venc_pm_ops, + .of_match_table = venc_of_match, + }, +}; + +int __init venc_init_platform_driver(void) +{ + return platform_driver_register(&omap_venchw_driver); +} + +void __exit venc_uninit_platform_driver(void) +{ + platform_driver_unregister(&omap_venchw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/venc_panel.c b/drivers/video/fbdev/omap2/dss/venc_panel.c new file mode 100644 index 000000000000..af68cd444d7e --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/venc_panel.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * VENC panel driver + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/module.h> + +#include <video/omapdss.h> + +#include "dss.h" + +static struct { + struct mutex lock; +} venc_panel; + +static ssize_t display_output_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + const char *ret; + + switch (dssdev->phy.venc.type) { + case OMAP_DSS_VENC_TYPE_COMPOSITE: + ret = "composite"; + break; + case OMAP_DSS_VENC_TYPE_SVIDEO: + ret = "svideo"; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE, "%s\n", ret); +} + +static ssize_t display_output_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + enum omap_dss_venc_type new_type; + + if (sysfs_streq("composite", buf)) + new_type = OMAP_DSS_VENC_TYPE_COMPOSITE; + else if (sysfs_streq("svideo", buf)) + new_type = OMAP_DSS_VENC_TYPE_SVIDEO; + else + return -EINVAL; + + mutex_lock(&venc_panel.lock); + + if (dssdev->phy.venc.type != new_type) { + dssdev->phy.venc.type = new_type; + omapdss_venc_set_type(dssdev, new_type); + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + omapdss_venc_display_disable(dssdev); + omapdss_venc_display_enable(dssdev); + } + } + + mutex_unlock(&venc_panel.lock); + + return size; +} + +static DEVICE_ATTR(output_type, S_IRUGO | S_IWUSR, + display_output_type_show, display_output_type_store); + +static int venc_panel_probe(struct omap_dss_device *dssdev) +{ + /* set default timings to PAL */ + const struct omap_video_timings default_timings = { + .x_res = 720, + .y_res = 574, + .pixelclock = 13500000, + .hsw = 64, + .hfp = 12, + .hbp = 68, + .vsw = 5, + .vfp = 5, + .vbp = 41, + + .vsync_level = OMAPDSS_SIG_ACTIVE_HIGH, + .hsync_level = OMAPDSS_SIG_ACTIVE_HIGH, + + .interlace = true, + }; + + mutex_init(&venc_panel.lock); + + dssdev->panel.timings = default_timings; + + return device_create_file(dssdev->dev, &dev_attr_output_type); +} + +static void venc_panel_remove(struct omap_dss_device *dssdev) +{ + device_remove_file(dssdev->dev, &dev_attr_output_type); +} + +static int venc_panel_enable(struct omap_dss_device *dssdev) +{ + int r; + + dev_dbg(dssdev->dev, "venc_panel_enable\n"); + + mutex_lock(&venc_panel.lock); + + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) { + r = -EINVAL; + goto err; + } + + omapdss_venc_set_timings(dssdev, &dssdev->panel.timings); + omapdss_venc_set_type(dssdev, dssdev->phy.venc.type); + omapdss_venc_invert_vid_out_polarity(dssdev, + dssdev->phy.venc.invert_polarity); + + r = omapdss_venc_display_enable(dssdev); + if (r) + goto err; + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + mutex_unlock(&venc_panel.lock); + + return 0; +err: + mutex_unlock(&venc_panel.lock); + + return r; +} + +static void venc_panel_disable(struct omap_dss_device *dssdev) +{ + dev_dbg(dssdev->dev, "venc_panel_disable\n"); + + mutex_lock(&venc_panel.lock); + + if (dssdev->state == OMAP_DSS_DISPLAY_DISABLED) + goto end; + + omapdss_venc_display_disable(dssdev); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +end: + mutex_unlock(&venc_panel.lock); +} + +static void venc_panel_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + dev_dbg(dssdev->dev, "venc_panel_set_timings\n"); + + mutex_lock(&venc_panel.lock); + + omapdss_venc_set_timings(dssdev, timings); + dssdev->panel.timings = *timings; + + mutex_unlock(&venc_panel.lock); +} + +static int venc_panel_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + dev_dbg(dssdev->dev, "venc_panel_check_timings\n"); + + return omapdss_venc_check_timings(dssdev, timings); +} + +static u32 venc_panel_get_wss(struct omap_dss_device *dssdev) +{ + dev_dbg(dssdev->dev, "venc_panel_get_wss\n"); + + return omapdss_venc_get_wss(dssdev); +} + +static int venc_panel_set_wss(struct omap_dss_device *dssdev, u32 wss) +{ + dev_dbg(dssdev->dev, "venc_panel_set_wss\n"); + + return omapdss_venc_set_wss(dssdev, wss); +} + +static struct omap_dss_driver venc_driver = { + .probe = venc_panel_probe, + .remove = venc_panel_remove, + + .enable = venc_panel_enable, + .disable = venc_panel_disable, + + .get_resolution = omapdss_default_get_resolution, + .get_recommended_bpp = omapdss_default_get_recommended_bpp, + + .set_timings = venc_panel_set_timings, + .check_timings = venc_panel_check_timings, + + .get_wss = venc_panel_get_wss, + .set_wss = venc_panel_set_wss, + + .driver = { + .name = "venc", + .owner = THIS_MODULE, + }, +}; + +int venc_panel_init(void) +{ + return omap_dss_register_driver(&venc_driver); +} + +void venc_panel_exit(void) +{ + omap_dss_unregister_driver(&venc_driver); +} diff --git a/drivers/video/fbdev/omap2/omapfb/Kconfig b/drivers/video/fbdev/omap2/omapfb/Kconfig new file mode 100644 index 000000000000..4cb12ce68855 --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/Kconfig @@ -0,0 +1,27 @@ +menuconfig FB_OMAP2 + tristate "OMAP2+ frame buffer support" + depends on FB && OMAP2_DSS && !DRM_OMAP + + select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Frame buffer driver for OMAP2+ based boards. + +config FB_OMAP2_DEBUG_SUPPORT + bool "Debug support for OMAP2+ FB" + default y + depends on FB_OMAP2 + help + Support for debug output. You have to enable the actual printing + with 'debug' module parameter. + +config FB_OMAP2_NUM_FBS + int "Number of framebuffers" + range 1 10 + default 3 + depends on FB_OMAP2 + help + Select the number of framebuffers created. OMAP2/3 has 3 overlays + so normally this would be 3. diff --git a/drivers/video/fbdev/omap2/omapfb/Makefile b/drivers/video/fbdev/omap2/omapfb/Makefile new file mode 100644 index 000000000000..51c2e00d9bf8 --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_FB_OMAP2) += omapfb.o +omapfb-y := omapfb-main.o omapfb-sysfs.o omapfb-ioctl.o diff --git a/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c b/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c new file mode 100644 index 000000000000..146b6f5428db --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c @@ -0,0 +1,922 @@ +/* + * linux/drivers/video/omap2/omapfb-ioctl.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/fb.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/mm.h> +#include <linux/omapfb.h> +#include <linux/vmalloc.h> +#include <linux/export.h> +#include <linux/sizes.h> + +#include <video/omapdss.h> +#include <video/omapvrfb.h> + +#include "omapfb.h" + +static u8 get_mem_idx(struct omapfb_info *ofbi) +{ + if (ofbi->id == ofbi->region->id) + return 0; + + return OMAPFB_MEM_IDX_ENABLED | ofbi->region->id; +} + +static struct omapfb2_mem_region *get_mem_region(struct omapfb_info *ofbi, + u8 mem_idx) +{ + struct omapfb2_device *fbdev = ofbi->fbdev; + + if (mem_idx & OMAPFB_MEM_IDX_ENABLED) + mem_idx &= OMAPFB_MEM_IDX_MASK; + else + mem_idx = ofbi->id; + + if (mem_idx >= fbdev->num_fbs) + return NULL; + + return &fbdev->regions[mem_idx]; +} + +static int omapfb_setup_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_overlay *ovl; + struct omap_overlay_info old_info; + struct omapfb2_mem_region *old_rg, *new_rg; + int r = 0; + + DBG("omapfb_setup_plane\n"); + + if (ofbi->num_overlays == 0) { + r = -EINVAL; + goto out; + } + + /* XXX uses only the first overlay */ + ovl = ofbi->overlays[0]; + + old_rg = ofbi->region; + new_rg = get_mem_region(ofbi, pi->mem_idx); + if (!new_rg) { + r = -EINVAL; + goto out; + } + + /* Take the locks in a specific order to keep lockdep happy */ + if (old_rg->id < new_rg->id) { + omapfb_get_mem_region(old_rg); + omapfb_get_mem_region(new_rg); + } else if (new_rg->id < old_rg->id) { + omapfb_get_mem_region(new_rg); + omapfb_get_mem_region(old_rg); + } else + omapfb_get_mem_region(old_rg); + + if (pi->enabled && !new_rg->size) { + /* + * This plane's memory was freed, can't enable it + * until it's reallocated. + */ + r = -EINVAL; + goto put_mem; + } + + ovl->get_overlay_info(ovl, &old_info); + + if (old_rg != new_rg) { + ofbi->region = new_rg; + set_fb_fix(fbi); + } + + if (!pi->enabled) { + r = ovl->disable(ovl); + if (r) + goto undo; + } + + if (pi->enabled) { + r = omapfb_setup_overlay(fbi, ovl, pi->pos_x, pi->pos_y, + pi->out_width, pi->out_height); + if (r) + goto undo; + } else { + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + info.pos_x = pi->pos_x; + info.pos_y = pi->pos_y; + info.out_width = pi->out_width; + info.out_height = pi->out_height; + + r = ovl->set_overlay_info(ovl, &info); + if (r) + goto undo; + } + + if (ovl->manager) + ovl->manager->apply(ovl->manager); + + if (pi->enabled) { + r = ovl->enable(ovl); + if (r) + goto undo; + } + + /* Release the locks in a specific order to keep lockdep happy */ + if (old_rg->id > new_rg->id) { + omapfb_put_mem_region(old_rg); + omapfb_put_mem_region(new_rg); + } else if (new_rg->id > old_rg->id) { + omapfb_put_mem_region(new_rg); + omapfb_put_mem_region(old_rg); + } else + omapfb_put_mem_region(old_rg); + + return 0; + + undo: + if (old_rg != new_rg) { + ofbi->region = old_rg; + set_fb_fix(fbi); + } + + ovl->set_overlay_info(ovl, &old_info); + put_mem: + /* Release the locks in a specific order to keep lockdep happy */ + if (old_rg->id > new_rg->id) { + omapfb_put_mem_region(old_rg); + omapfb_put_mem_region(new_rg); + } else if (new_rg->id > old_rg->id) { + omapfb_put_mem_region(new_rg); + omapfb_put_mem_region(old_rg); + } else + omapfb_put_mem_region(old_rg); + out: + dev_err(fbdev->dev, "setup_plane failed\n"); + + return r; +} + +static int omapfb_query_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + + if (ofbi->num_overlays == 0) { + memset(pi, 0, sizeof(*pi)); + } else { + struct omap_overlay *ovl; + struct omap_overlay_info ovli; + + ovl = ofbi->overlays[0]; + ovl->get_overlay_info(ovl, &ovli); + + pi->pos_x = ovli.pos_x; + pi->pos_y = ovli.pos_y; + pi->enabled = ovl->is_enabled(ovl); + pi->channel_out = 0; /* xxx */ + pi->mirror = 0; + pi->mem_idx = get_mem_idx(ofbi); + pi->out_width = ovli.out_width; + pi->out_height = ovli.out_height; + } + + return 0; +} + +static int omapfb_setup_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_dss_device *display = fb2display(fbi); + struct omapfb2_mem_region *rg; + int r = 0, i; + size_t size; + + if (mi->type != OMAPFB_MEMTYPE_SDRAM) + return -EINVAL; + + size = PAGE_ALIGN(mi->size); + + if (display && display->driver->sync) + display->driver->sync(display); + + rg = ofbi->region; + + down_write_nested(&rg->lock, rg->id); + atomic_inc(&rg->lock_count); + + if (rg->size == size && rg->type == mi->type) + goto out; + + if (atomic_read(&rg->map_count)) { + r = -EBUSY; + goto out; + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi2 = FB2OFB(fbdev->fbs[i]); + int j; + + if (ofbi2->region != rg) + continue; + + for (j = 0; j < ofbi2->num_overlays; j++) { + struct omap_overlay *ovl; + ovl = ofbi2->overlays[j]; + if (ovl->is_enabled(ovl)) { + r = -EBUSY; + goto out; + } + } + } + + r = omapfb_realloc_fbmem(fbi, size, mi->type); + if (r) { + dev_err(fbdev->dev, "realloc fbmem failed\n"); + goto out; + } + + out: + atomic_dec(&rg->lock_count); + up_write(&rg->lock); + + return r; +} + +static int omapfb_query_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_mem_region *rg; + + rg = omapfb_get_mem_region(ofbi->region); + memset(mi, 0, sizeof(*mi)); + + mi->size = rg->size; + mi->type = rg->type; + + omapfb_put_mem_region(rg); + + return 0; +} + +static int omapfb_update_window(struct fb_info *fbi, + u32 x, u32 y, u32 w, u32 h) +{ + struct omap_dss_device *display = fb2display(fbi); + u16 dw, dh; + + if (!display) + return 0; + + if (w == 0 || h == 0) + return 0; + + display->driver->get_resolution(display, &dw, &dh); + + if (x + w > dw || y + h > dh) + return -EINVAL; + + return display->driver->update(display, x, y, w, h); +} + +int omapfb_set_update_mode(struct fb_info *fbi, + enum omapfb_update_mode mode) +{ + struct omap_dss_device *display = fb2display(fbi); + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb_display_data *d; + int r; + + if (!display) + return -EINVAL; + + if (mode != OMAPFB_AUTO_UPDATE && mode != OMAPFB_MANUAL_UPDATE) + return -EINVAL; + + omapfb_lock(fbdev); + + d = get_display_data(fbdev, display); + + if (d->update_mode == mode) { + omapfb_unlock(fbdev); + return 0; + } + + r = 0; + + if (display->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) { + if (mode == OMAPFB_AUTO_UPDATE) + omapfb_start_auto_update(fbdev, display); + else /* MANUAL_UPDATE */ + omapfb_stop_auto_update(fbdev, display); + + d->update_mode = mode; + } else { /* AUTO_UPDATE */ + if (mode == OMAPFB_MANUAL_UPDATE) + r = -EINVAL; + } + + omapfb_unlock(fbdev); + + return r; +} + +int omapfb_get_update_mode(struct fb_info *fbi, + enum omapfb_update_mode *mode) +{ + struct omap_dss_device *display = fb2display(fbi); + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb_display_data *d; + + if (!display) + return -EINVAL; + + omapfb_lock(fbdev); + + d = get_display_data(fbdev, display); + + *mode = d->update_mode; + + omapfb_unlock(fbdev); + + return 0; +} + +/* XXX this color key handling is a hack... */ +static struct omapfb_color_key omapfb_color_keys[2]; + +static int _omapfb_set_color_key(struct omap_overlay_manager *mgr, + struct omapfb_color_key *ck) +{ + struct omap_overlay_manager_info info; + enum omap_dss_trans_key_type kt; + int r; + + mgr->get_manager_info(mgr, &info); + + if (ck->key_type == OMAPFB_COLOR_KEY_DISABLED) { + info.trans_enabled = false; + omapfb_color_keys[mgr->id] = *ck; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + + return r; + } + + switch (ck->key_type) { + case OMAPFB_COLOR_KEY_GFX_DST: + kt = OMAP_DSS_COLOR_KEY_GFX_DST; + break; + case OMAPFB_COLOR_KEY_VID_SRC: + kt = OMAP_DSS_COLOR_KEY_VID_SRC; + break; + default: + return -EINVAL; + } + + info.default_color = ck->background; + info.trans_key = ck->trans_key; + info.trans_key_type = kt; + info.trans_enabled = true; + + omapfb_color_keys[mgr->id] = *ck; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + + return r; +} + +static int omapfb_set_color_key(struct fb_info *fbi, + struct omapfb_color_key *ck) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + int r; + int i; + struct omap_overlay_manager *mgr = NULL; + + omapfb_lock(fbdev); + + for (i = 0; i < ofbi->num_overlays; i++) { + if (ofbi->overlays[i]->manager) { + mgr = ofbi->overlays[i]->manager; + break; + } + } + + if (!mgr) { + r = -EINVAL; + goto err; + } + + r = _omapfb_set_color_key(mgr, ck); +err: + omapfb_unlock(fbdev); + + return r; +} + +static int omapfb_get_color_key(struct fb_info *fbi, + struct omapfb_color_key *ck) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_overlay_manager *mgr = NULL; + int r = 0; + int i; + + omapfb_lock(fbdev); + + for (i = 0; i < ofbi->num_overlays; i++) { + if (ofbi->overlays[i]->manager) { + mgr = ofbi->overlays[i]->manager; + break; + } + } + + if (!mgr) { + r = -EINVAL; + goto err; + } + + *ck = omapfb_color_keys[mgr->id]; +err: + omapfb_unlock(fbdev); + + return r; +} + +static int omapfb_memory_read(struct fb_info *fbi, + struct omapfb_memory_read *mr) +{ + struct omap_dss_device *display = fb2display(fbi); + void *buf; + int r; + + if (!display || !display->driver->memory_read) + return -ENOENT; + + if (!access_ok(VERIFY_WRITE, mr->buffer, mr->buffer_size)) + return -EFAULT; + + if (mr->w * mr->h * 3 > mr->buffer_size) + return -EINVAL; + + buf = vmalloc(mr->buffer_size); + if (!buf) { + DBG("vmalloc failed\n"); + return -ENOMEM; + } + + r = display->driver->memory_read(display, buf, mr->buffer_size, + mr->x, mr->y, mr->w, mr->h); + + if (r > 0) { + if (copy_to_user(mr->buffer, buf, mr->buffer_size)) + r = -EFAULT; + } + + vfree(buf); + + return r; +} + +static int omapfb_get_ovl_colormode(struct omapfb2_device *fbdev, + struct omapfb_ovl_colormode *mode) +{ + int ovl_idx = mode->overlay_idx; + int mode_idx = mode->mode_idx; + struct omap_overlay *ovl; + enum omap_color_mode supported_modes; + struct fb_var_screeninfo var; + int i; + + if (ovl_idx >= fbdev->num_overlays) + return -ENODEV; + ovl = fbdev->overlays[ovl_idx]; + supported_modes = ovl->supported_modes; + + mode_idx = mode->mode_idx; + + for (i = 0; i < sizeof(supported_modes) * 8; i++) { + if (!(supported_modes & (1 << i))) + continue; + /* + * It's possible that the FB doesn't support a mode + * that is supported by the overlay, so call the + * following here. + */ + if (dss_mode_to_fb_mode(1 << i, &var) < 0) + continue; + + mode_idx--; + if (mode_idx < 0) + break; + } + + if (i == sizeof(supported_modes) * 8) + return -ENOENT; + + mode->bits_per_pixel = var.bits_per_pixel; + mode->nonstd = var.nonstd; + mode->red = var.red; + mode->green = var.green; + mode->blue = var.blue; + mode->transp = var.transp; + + return 0; +} + +static int omapfb_wait_for_go(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + int r = 0; + int i; + + for (i = 0; i < ofbi->num_overlays; ++i) { + struct omap_overlay *ovl = ofbi->overlays[i]; + r = ovl->wait_for_go(ovl); + if (r) + break; + } + + return r; +} + +int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_dss_device *display = fb2display(fbi); + struct omap_overlay_manager *mgr; + + union { + struct omapfb_update_window_old uwnd_o; + struct omapfb_update_window uwnd; + struct omapfb_plane_info plane_info; + struct omapfb_caps caps; + struct omapfb_mem_info mem_info; + struct omapfb_color_key color_key; + struct omapfb_ovl_colormode ovl_colormode; + enum omapfb_update_mode update_mode; + int test_num; + struct omapfb_memory_read memory_read; + struct omapfb_vram_info vram_info; + struct omapfb_tearsync_info tearsync_info; + struct omapfb_display_info display_info; + u32 crt; + } p; + + int r = 0; + + switch (cmd) { + case OMAPFB_SYNC_GFX: + DBG("ioctl SYNC_GFX\n"); + if (!display || !display->driver->sync) { + /* DSS1 never returns an error here, so we neither */ + /*r = -EINVAL;*/ + break; + } + + r = display->driver->sync(display); + break; + + case OMAPFB_UPDATE_WINDOW_OLD: + DBG("ioctl UPDATE_WINDOW_OLD\n"); + if (!display || !display->driver->update) { + r = -EINVAL; + break; + } + + if (copy_from_user(&p.uwnd_o, + (void __user *)arg, + sizeof(p.uwnd_o))) { + r = -EFAULT; + break; + } + + r = omapfb_update_window(fbi, p.uwnd_o.x, p.uwnd_o.y, + p.uwnd_o.width, p.uwnd_o.height); + break; + + case OMAPFB_UPDATE_WINDOW: + DBG("ioctl UPDATE_WINDOW\n"); + if (!display || !display->driver->update) { + r = -EINVAL; + break; + } + + if (copy_from_user(&p.uwnd, (void __user *)arg, + sizeof(p.uwnd))) { + r = -EFAULT; + break; + } + + r = omapfb_update_window(fbi, p.uwnd.x, p.uwnd.y, + p.uwnd.width, p.uwnd.height); + break; + + case OMAPFB_SETUP_PLANE: + DBG("ioctl SETUP_PLANE\n"); + if (copy_from_user(&p.plane_info, (void __user *)arg, + sizeof(p.plane_info))) + r = -EFAULT; + else + r = omapfb_setup_plane(fbi, &p.plane_info); + break; + + case OMAPFB_QUERY_PLANE: + DBG("ioctl QUERY_PLANE\n"); + r = omapfb_query_plane(fbi, &p.plane_info); + if (r < 0) + break; + if (copy_to_user((void __user *)arg, &p.plane_info, + sizeof(p.plane_info))) + r = -EFAULT; + break; + + case OMAPFB_SETUP_MEM: + DBG("ioctl SETUP_MEM\n"); + if (copy_from_user(&p.mem_info, (void __user *)arg, + sizeof(p.mem_info))) + r = -EFAULT; + else + r = omapfb_setup_mem(fbi, &p.mem_info); + break; + + case OMAPFB_QUERY_MEM: + DBG("ioctl QUERY_MEM\n"); + r = omapfb_query_mem(fbi, &p.mem_info); + if (r < 0) + break; + if (copy_to_user((void __user *)arg, &p.mem_info, + sizeof(p.mem_info))) + r = -EFAULT; + break; + + case OMAPFB_GET_CAPS: + DBG("ioctl GET_CAPS\n"); + if (!display) { + r = -EINVAL; + break; + } + + memset(&p.caps, 0, sizeof(p.caps)); + if (display->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) + p.caps.ctrl |= OMAPFB_CAPS_MANUAL_UPDATE; + if (display->caps & OMAP_DSS_DISPLAY_CAP_TEAR_ELIM) + p.caps.ctrl |= OMAPFB_CAPS_TEARSYNC; + + if (copy_to_user((void __user *)arg, &p.caps, sizeof(p.caps))) + r = -EFAULT; + break; + + case OMAPFB_GET_OVERLAY_COLORMODE: + DBG("ioctl GET_OVERLAY_COLORMODE\n"); + if (copy_from_user(&p.ovl_colormode, (void __user *)arg, + sizeof(p.ovl_colormode))) { + r = -EFAULT; + break; + } + r = omapfb_get_ovl_colormode(fbdev, &p.ovl_colormode); + if (r < 0) + break; + if (copy_to_user((void __user *)arg, &p.ovl_colormode, + sizeof(p.ovl_colormode))) + r = -EFAULT; + break; + + case OMAPFB_SET_UPDATE_MODE: + DBG("ioctl SET_UPDATE_MODE\n"); + if (get_user(p.update_mode, (int __user *)arg)) + r = -EFAULT; + else + r = omapfb_set_update_mode(fbi, p.update_mode); + break; + + case OMAPFB_GET_UPDATE_MODE: + DBG("ioctl GET_UPDATE_MODE\n"); + r = omapfb_get_update_mode(fbi, &p.update_mode); + if (r) + break; + if (put_user(p.update_mode, + (enum omapfb_update_mode __user *)arg)) + r = -EFAULT; + break; + + case OMAPFB_SET_COLOR_KEY: + DBG("ioctl SET_COLOR_KEY\n"); + if (copy_from_user(&p.color_key, (void __user *)arg, + sizeof(p.color_key))) + r = -EFAULT; + else + r = omapfb_set_color_key(fbi, &p.color_key); + break; + + case OMAPFB_GET_COLOR_KEY: + DBG("ioctl GET_COLOR_KEY\n"); + r = omapfb_get_color_key(fbi, &p.color_key); + if (r) + break; + if (copy_to_user((void __user *)arg, &p.color_key, + sizeof(p.color_key))) + r = -EFAULT; + break; + + case FBIO_WAITFORVSYNC: + if (get_user(p.crt, (__u32 __user *)arg)) { + r = -EFAULT; + break; + } + if (p.crt != 0) { + r = -ENODEV; + break; + } + /* FALLTHROUGH */ + + case OMAPFB_WAITFORVSYNC: + DBG("ioctl WAITFORVSYNC\n"); + + if (!display) { + r = -EINVAL; + break; + } + + mgr = omapdss_find_mgr_from_display(display); + if (!mgr) { + r = -EINVAL; + break; + } + + r = mgr->wait_for_vsync(mgr); + break; + + case OMAPFB_WAITFORGO: + DBG("ioctl WAITFORGO\n"); + if (!display) { + r = -EINVAL; + break; + } + + r = omapfb_wait_for_go(fbi); + break; + + /* LCD and CTRL tests do the same thing for backward + * compatibility */ + case OMAPFB_LCD_TEST: + DBG("ioctl LCD_TEST\n"); + if (get_user(p.test_num, (int __user *)arg)) { + r = -EFAULT; + break; + } + if (!display || !display->driver->run_test) { + r = -EINVAL; + break; + } + + r = display->driver->run_test(display, p.test_num); + + break; + + case OMAPFB_CTRL_TEST: + DBG("ioctl CTRL_TEST\n"); + if (get_user(p.test_num, (int __user *)arg)) { + r = -EFAULT; + break; + } + if (!display || !display->driver->run_test) { + r = -EINVAL; + break; + } + + r = display->driver->run_test(display, p.test_num); + + break; + + case OMAPFB_MEMORY_READ: + DBG("ioctl MEMORY_READ\n"); + + if (copy_from_user(&p.memory_read, (void __user *)arg, + sizeof(p.memory_read))) { + r = -EFAULT; + break; + } + + r = omapfb_memory_read(fbi, &p.memory_read); + + break; + + case OMAPFB_GET_VRAM_INFO: { + DBG("ioctl GET_VRAM_INFO\n"); + + /* + * We don't have the ability to get this vram info anymore. + * Fill in something that should keep the applications working. + */ + p.vram_info.total = SZ_1M * 64; + p.vram_info.free = SZ_1M * 64; + p.vram_info.largest_free_block = SZ_1M * 64; + + if (copy_to_user((void __user *)arg, &p.vram_info, + sizeof(p.vram_info))) + r = -EFAULT; + break; + } + + case OMAPFB_SET_TEARSYNC: { + DBG("ioctl SET_TEARSYNC\n"); + + if (copy_from_user(&p.tearsync_info, (void __user *)arg, + sizeof(p.tearsync_info))) { + r = -EFAULT; + break; + } + + if (!display || !display->driver->enable_te) { + r = -ENODEV; + break; + } + + r = display->driver->enable_te(display, + !!p.tearsync_info.enabled); + + break; + } + + case OMAPFB_GET_DISPLAY_INFO: { + u16 xres, yres; + + DBG("ioctl GET_DISPLAY_INFO\n"); + + if (display == NULL) { + r = -ENODEV; + break; + } + + display->driver->get_resolution(display, &xres, &yres); + + p.display_info.xres = xres; + p.display_info.yres = yres; + + if (display->driver->get_dimensions) { + u32 w, h; + display->driver->get_dimensions(display, &w, &h); + p.display_info.width = w; + p.display_info.height = h; + } else { + p.display_info.width = 0; + p.display_info.height = 0; + } + + if (copy_to_user((void __user *)arg, &p.display_info, + sizeof(p.display_info))) + r = -EFAULT; + break; + } + + default: + dev_err(fbdev->dev, "Unknown ioctl 0x%x\n", cmd); + r = -EINVAL; + } + + if (r < 0) + DBG("ioctl failed: %d\n", r); + + return r; +} + + diff --git a/drivers/video/fbdev/omap2/omapfb/omapfb-main.c b/drivers/video/fbdev/omap2/omapfb/omapfb-main.c new file mode 100644 index 000000000000..ec2d132c782d --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/omapfb-main.c @@ -0,0 +1,2656 @@ +/* + * linux/drivers/video/omap2/omapfb-main.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/omapfb.h> + +#include <video/omapdss.h> +#include <video/omapvrfb.h> + +#include "omapfb.h" + +#define MODULE_NAME "omapfb" + +#define OMAPFB_PLANE_XRES_MIN 8 +#define OMAPFB_PLANE_YRES_MIN 8 + +static char *def_mode; +static char *def_vram; +static bool def_vrfb; +static int def_rotate; +static bool def_mirror; +static bool auto_update; +static unsigned int auto_update_freq; +module_param(auto_update, bool, 0); +module_param(auto_update_freq, uint, 0644); + +#ifdef DEBUG +bool omapfb_debug; +module_param_named(debug, omapfb_debug, bool, 0644); +static bool omapfb_test_pattern; +module_param_named(test, omapfb_test_pattern, bool, 0644); +#endif + +static int omapfb_fb_init(struct omapfb2_device *fbdev, struct fb_info *fbi); +static int omapfb_get_recommended_bpp(struct omapfb2_device *fbdev, + struct omap_dss_device *dssdev); + +#ifdef DEBUG +static void draw_pixel(struct fb_info *fbi, int x, int y, unsigned color) +{ + struct fb_var_screeninfo *var = &fbi->var; + struct fb_fix_screeninfo *fix = &fbi->fix; + void __iomem *addr = fbi->screen_base; + const unsigned bytespp = var->bits_per_pixel >> 3; + const unsigned line_len = fix->line_length / bytespp; + + int r = (color >> 16) & 0xff; + int g = (color >> 8) & 0xff; + int b = (color >> 0) & 0xff; + + if (var->bits_per_pixel == 16) { + u16 __iomem *p = (u16 __iomem *)addr; + p += y * line_len + x; + + r = r * 32 / 256; + g = g * 64 / 256; + b = b * 32 / 256; + + __raw_writew((r << 11) | (g << 5) | (b << 0), p); + } else if (var->bits_per_pixel == 24) { + u8 __iomem *p = (u8 __iomem *)addr; + p += (y * line_len + x) * 3; + + __raw_writeb(b, p + 0); + __raw_writeb(g, p + 1); + __raw_writeb(r, p + 2); + } else if (var->bits_per_pixel == 32) { + u32 __iomem *p = (u32 __iomem *)addr; + p += y * line_len + x; + __raw_writel(color, p); + } +} + +static void fill_fb(struct fb_info *fbi) +{ + struct fb_var_screeninfo *var = &fbi->var; + const short w = var->xres_virtual; + const short h = var->yres_virtual; + void __iomem *addr = fbi->screen_base; + int y, x; + + if (!addr) + return; + + DBG("fill_fb %dx%d, line_len %d bytes\n", w, h, fbi->fix.line_length); + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (x < 20 && y < 20) + draw_pixel(fbi, x, y, 0xffffff); + else if (x < 20 && (y > 20 && y < h - 20)) + draw_pixel(fbi, x, y, 0xff); + else if (y < 20 && (x > 20 && x < w - 20)) + draw_pixel(fbi, x, y, 0xff00); + else if (x > w - 20 && (y > 20 && y < h - 20)) + draw_pixel(fbi, x, y, 0xff0000); + else if (y > h - 20 && (x > 20 && x < w - 20)) + draw_pixel(fbi, x, y, 0xffff00); + else if (x == 20 || x == w - 20 || + y == 20 || y == h - 20) + draw_pixel(fbi, x, y, 0xffffff); + else if (x == y || w - x == h - y) + draw_pixel(fbi, x, y, 0xff00ff); + else if (w - x == y || x == h - y) + draw_pixel(fbi, x, y, 0x00ffff); + else if (x > 20 && y > 20 && x < w - 20 && y < h - 20) { + int t = x * 3 / w; + unsigned r = 0, g = 0, b = 0; + unsigned c; + if (var->bits_per_pixel == 16) { + if (t == 0) + b = (y % 32) * 256 / 32; + else if (t == 1) + g = (y % 64) * 256 / 64; + else if (t == 2) + r = (y % 32) * 256 / 32; + } else { + if (t == 0) + b = (y % 256); + else if (t == 1) + g = (y % 256); + else if (t == 2) + r = (y % 256); + } + c = (r << 16) | (g << 8) | (b << 0); + draw_pixel(fbi, x, y, c); + } else { + draw_pixel(fbi, x, y, 0); + } + } + } +} +#endif + +static unsigned omapfb_get_vrfb_offset(const struct omapfb_info *ofbi, int rot) +{ + const struct vrfb *vrfb = &ofbi->region->vrfb; + unsigned offset; + + switch (rot) { + case FB_ROTATE_UR: + offset = 0; + break; + case FB_ROTATE_CW: + offset = vrfb->yoffset; + break; + case FB_ROTATE_UD: + offset = vrfb->yoffset * OMAP_VRFB_LINE_LEN + vrfb->xoffset; + break; + case FB_ROTATE_CCW: + offset = vrfb->xoffset * OMAP_VRFB_LINE_LEN; + break; + default: + BUG(); + return 0; + } + + offset *= vrfb->bytespp; + + return offset; +} + +static u32 omapfb_get_region_rot_paddr(const struct omapfb_info *ofbi, int rot) +{ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + return ofbi->region->vrfb.paddr[rot] + + omapfb_get_vrfb_offset(ofbi, rot); + } else { + return ofbi->region->paddr; + } +} + +static u32 omapfb_get_region_paddr(const struct omapfb_info *ofbi) +{ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + return ofbi->region->vrfb.paddr[0]; + else + return ofbi->region->paddr; +} + +static void __iomem *omapfb_get_region_vaddr(const struct omapfb_info *ofbi) +{ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + return ofbi->region->vrfb.vaddr[0]; + else + return ofbi->region->vaddr; +} + +static struct omapfb_colormode omapfb_colormodes[] = { + { + .dssmode = OMAP_DSS_COLOR_UYVY, + .bits_per_pixel = 16, + .nonstd = OMAPFB_COLOR_YUV422, + }, { + .dssmode = OMAP_DSS_COLOR_YUV2, + .bits_per_pixel = 16, + .nonstd = OMAPFB_COLOR_YUY422, + }, { + .dssmode = OMAP_DSS_COLOR_ARGB16, + .bits_per_pixel = 16, + .red = { .length = 4, .offset = 8, .msb_right = 0 }, + .green = { .length = 4, .offset = 4, .msb_right = 0 }, + .blue = { .length = 4, .offset = 0, .msb_right = 0 }, + .transp = { .length = 4, .offset = 12, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGB16, + .bits_per_pixel = 16, + .red = { .length = 5, .offset = 11, .msb_right = 0 }, + .green = { .length = 6, .offset = 5, .msb_right = 0 }, + .blue = { .length = 5, .offset = 0, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGB24P, + .bits_per_pixel = 24, + .red = { .length = 8, .offset = 16, .msb_right = 0 }, + .green = { .length = 8, .offset = 8, .msb_right = 0 }, + .blue = { .length = 8, .offset = 0, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGB24U, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 16, .msb_right = 0 }, + .green = { .length = 8, .offset = 8, .msb_right = 0 }, + .blue = { .length = 8, .offset = 0, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_ARGB32, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 16, .msb_right = 0 }, + .green = { .length = 8, .offset = 8, .msb_right = 0 }, + .blue = { .length = 8, .offset = 0, .msb_right = 0 }, + .transp = { .length = 8, .offset = 24, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGBA32, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 24, .msb_right = 0 }, + .green = { .length = 8, .offset = 16, .msb_right = 0 }, + .blue = { .length = 8, .offset = 8, .msb_right = 0 }, + .transp = { .length = 8, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGBX32, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 24, .msb_right = 0 }, + .green = { .length = 8, .offset = 16, .msb_right = 0 }, + .blue = { .length = 8, .offset = 8, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, +}; + +static bool cmp_var_to_colormode(struct fb_var_screeninfo *var, + struct omapfb_colormode *color) +{ + bool cmp_component(struct fb_bitfield *f1, struct fb_bitfield *f2) + { + return f1->length == f2->length && + f1->offset == f2->offset && + f1->msb_right == f2->msb_right; + } + + if (var->bits_per_pixel == 0 || + var->red.length == 0 || + var->blue.length == 0 || + var->green.length == 0) + return 0; + + return var->bits_per_pixel == color->bits_per_pixel && + cmp_component(&var->red, &color->red) && + cmp_component(&var->green, &color->green) && + cmp_component(&var->blue, &color->blue) && + cmp_component(&var->transp, &color->transp); +} + +static void assign_colormode_to_var(struct fb_var_screeninfo *var, + struct omapfb_colormode *color) +{ + var->bits_per_pixel = color->bits_per_pixel; + var->nonstd = color->nonstd; + var->red = color->red; + var->green = color->green; + var->blue = color->blue; + var->transp = color->transp; +} + +static int fb_mode_to_dss_mode(struct fb_var_screeninfo *var, + enum omap_color_mode *mode) +{ + enum omap_color_mode dssmode; + int i; + + /* first match with nonstd field */ + if (var->nonstd) { + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *m = &omapfb_colormodes[i]; + if (var->nonstd == m->nonstd) { + assign_colormode_to_var(var, m); + *mode = m->dssmode; + return 0; + } + } + + return -EINVAL; + } + + /* then try exact match of bpp and colors */ + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *m = &omapfb_colormodes[i]; + if (cmp_var_to_colormode(var, m)) { + assign_colormode_to_var(var, m); + *mode = m->dssmode; + return 0; + } + } + + /* match with bpp if user has not filled color fields + * properly */ + switch (var->bits_per_pixel) { + case 1: + dssmode = OMAP_DSS_COLOR_CLUT1; + break; + case 2: + dssmode = OMAP_DSS_COLOR_CLUT2; + break; + case 4: + dssmode = OMAP_DSS_COLOR_CLUT4; + break; + case 8: + dssmode = OMAP_DSS_COLOR_CLUT8; + break; + case 12: + dssmode = OMAP_DSS_COLOR_RGB12U; + break; + case 16: + dssmode = OMAP_DSS_COLOR_RGB16; + break; + case 24: + dssmode = OMAP_DSS_COLOR_RGB24P; + break; + case 32: + dssmode = OMAP_DSS_COLOR_RGB24U; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *m = &omapfb_colormodes[i]; + if (dssmode == m->dssmode) { + assign_colormode_to_var(var, m); + *mode = m->dssmode; + return 0; + } + } + + return -EINVAL; +} + +static int check_fb_res_bounds(struct fb_var_screeninfo *var) +{ + int xres_min = OMAPFB_PLANE_XRES_MIN; + int xres_max = 2048; + int yres_min = OMAPFB_PLANE_YRES_MIN; + int yres_max = 2048; + + /* XXX: some applications seem to set virtual res to 0. */ + if (var->xres_virtual == 0) + var->xres_virtual = var->xres; + + if (var->yres_virtual == 0) + var->yres_virtual = var->yres; + + if (var->xres_virtual < xres_min || var->yres_virtual < yres_min) + return -EINVAL; + + if (var->xres < xres_min) + var->xres = xres_min; + if (var->yres < yres_min) + var->yres = yres_min; + if (var->xres > xres_max) + var->xres = xres_max; + if (var->yres > yres_max) + var->yres = yres_max; + + if (var->xres > var->xres_virtual) + var->xres = var->xres_virtual; + if (var->yres > var->yres_virtual) + var->yres = var->yres_virtual; + + return 0; +} + +static void shrink_height(unsigned long max_frame_size, + struct fb_var_screeninfo *var) +{ + DBG("can't fit FB into memory, reducing y\n"); + var->yres_virtual = max_frame_size / + (var->xres_virtual * var->bits_per_pixel >> 3); + + if (var->yres_virtual < OMAPFB_PLANE_YRES_MIN) + var->yres_virtual = OMAPFB_PLANE_YRES_MIN; + + if (var->yres > var->yres_virtual) + var->yres = var->yres_virtual; +} + +static void shrink_width(unsigned long max_frame_size, + struct fb_var_screeninfo *var) +{ + DBG("can't fit FB into memory, reducing x\n"); + var->xres_virtual = max_frame_size / var->yres_virtual / + (var->bits_per_pixel >> 3); + + if (var->xres_virtual < OMAPFB_PLANE_XRES_MIN) + var->xres_virtual = OMAPFB_PLANE_XRES_MIN; + + if (var->xres > var->xres_virtual) + var->xres = var->xres_virtual; +} + +static int check_vrfb_fb_size(unsigned long region_size, + const struct fb_var_screeninfo *var) +{ + unsigned long min_phys_size = omap_vrfb_min_phys_size(var->xres_virtual, + var->yres_virtual, var->bits_per_pixel >> 3); + + return min_phys_size > region_size ? -EINVAL : 0; +} + +static int check_fb_size(const struct omapfb_info *ofbi, + struct fb_var_screeninfo *var) +{ + unsigned long max_frame_size = ofbi->region->size; + int bytespp = var->bits_per_pixel >> 3; + unsigned long line_size = var->xres_virtual * bytespp; + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + /* One needs to check for both VRFB and OMAPFB limitations. */ + if (check_vrfb_fb_size(max_frame_size, var)) + shrink_height(omap_vrfb_max_height( + max_frame_size, var->xres_virtual, bytespp) * + line_size, var); + + if (check_vrfb_fb_size(max_frame_size, var)) { + DBG("cannot fit FB to memory\n"); + return -EINVAL; + } + + return 0; + } + + DBG("max frame size %lu, line size %lu\n", max_frame_size, line_size); + + if (line_size * var->yres_virtual > max_frame_size) + shrink_height(max_frame_size, var); + + if (line_size * var->yres_virtual > max_frame_size) { + shrink_width(max_frame_size, var); + line_size = var->xres_virtual * bytespp; + } + + if (line_size * var->yres_virtual > max_frame_size) { + DBG("cannot fit FB to memory\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Consider if VRFB assisted rotation is in use and if the virtual space for + * the zero degree view needs to be mapped. The need for mapping also acts as + * the trigger for setting up the hardware on the context in question. This + * ensures that one does not attempt to access the virtual view before the + * hardware is serving the address translations. + */ +static int setup_vrfb_rotation(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_mem_region *rg = ofbi->region; + struct vrfb *vrfb = &rg->vrfb; + struct fb_var_screeninfo *var = &fbi->var; + struct fb_fix_screeninfo *fix = &fbi->fix; + unsigned bytespp; + bool yuv_mode; + enum omap_color_mode mode; + int r; + bool reconf; + + if (!rg->size || ofbi->rotation_type != OMAP_DSS_ROT_VRFB) + return 0; + + DBG("setup_vrfb_rotation\n"); + + r = fb_mode_to_dss_mode(var, &mode); + if (r) + return r; + + bytespp = var->bits_per_pixel >> 3; + + yuv_mode = mode == OMAP_DSS_COLOR_YUV2 || mode == OMAP_DSS_COLOR_UYVY; + + /* We need to reconfigure VRFB if the resolution changes, if yuv mode + * is enabled/disabled, or if bytes per pixel changes */ + + /* XXX we shouldn't allow this when framebuffer is mmapped */ + + reconf = false; + + if (yuv_mode != vrfb->yuv_mode) + reconf = true; + else if (bytespp != vrfb->bytespp) + reconf = true; + else if (vrfb->xres != var->xres_virtual || + vrfb->yres != var->yres_virtual) + reconf = true; + + if (vrfb->vaddr[0] && reconf) { + fbi->screen_base = NULL; + fix->smem_start = 0; + fix->smem_len = 0; + iounmap(vrfb->vaddr[0]); + vrfb->vaddr[0] = NULL; + DBG("setup_vrfb_rotation: reset fb\n"); + } + + if (vrfb->vaddr[0]) + return 0; + + omap_vrfb_setup(&rg->vrfb, rg->paddr, + var->xres_virtual, + var->yres_virtual, + bytespp, yuv_mode); + + /* Now one can ioremap the 0 angle view */ + r = omap_vrfb_map_angle(vrfb, var->yres_virtual, 0); + if (r) + return r; + + /* used by open/write in fbmem.c */ + fbi->screen_base = ofbi->region->vrfb.vaddr[0]; + + fix->smem_start = ofbi->region->vrfb.paddr[0]; + + switch (var->nonstd) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUY422: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 2; + break; + default: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 3; + break; + } + + fix->smem_len = var->yres_virtual * fix->line_length; + + return 0; +} + +int dss_mode_to_fb_mode(enum omap_color_mode dssmode, + struct fb_var_screeninfo *var) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *mode = &omapfb_colormodes[i]; + if (dssmode == mode->dssmode) { + assign_colormode_to_var(var, mode); + return 0; + } + } + return -ENOENT; +} + +void set_fb_fix(struct fb_info *fbi) +{ + struct fb_fix_screeninfo *fix = &fbi->fix; + struct fb_var_screeninfo *var = &fbi->var; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_mem_region *rg = ofbi->region; + + DBG("set_fb_fix\n"); + + /* used by open/write in fbmem.c */ + fbi->screen_base = (char __iomem *)omapfb_get_region_vaddr(ofbi); + + /* used by mmap in fbmem.c */ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + switch (var->nonstd) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUY422: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 2; + break; + default: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 3; + break; + } + + fix->smem_len = var->yres_virtual * fix->line_length; + } else { + fix->line_length = + (var->xres_virtual * var->bits_per_pixel) >> 3; + fix->smem_len = rg->size; + } + + fix->smem_start = omapfb_get_region_paddr(ofbi); + + fix->type = FB_TYPE_PACKED_PIXELS; + + if (var->nonstd) + fix->visual = FB_VISUAL_PSEUDOCOLOR; + else { + switch (var->bits_per_pixel) { + case 32: + case 24: + case 16: + case 12: + fix->visual = FB_VISUAL_TRUECOLOR; + /* 12bpp is stored in 16 bits */ + break; + case 1: + case 2: + case 4: + case 8: + fix->visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + } + + fix->accel = FB_ACCEL_NONE; + + fix->xpanstep = 1; + fix->ypanstep = 1; +} + +/* check new var and possibly modify it to be ok */ +int check_fb_var(struct fb_info *fbi, struct fb_var_screeninfo *var) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omap_dss_device *display = fb2display(fbi); + enum omap_color_mode mode = 0; + int i; + int r; + + DBG("check_fb_var %d\n", ofbi->id); + + WARN_ON(!atomic_read(&ofbi->region->lock_count)); + + r = fb_mode_to_dss_mode(var, &mode); + if (r) { + DBG("cannot convert var to omap dss mode\n"); + return r; + } + + for (i = 0; i < ofbi->num_overlays; ++i) { + if ((ofbi->overlays[i]->supported_modes & mode) == 0) { + DBG("invalid mode\n"); + return -EINVAL; + } + } + + if (var->rotate > 3) + return -EINVAL; + + if (check_fb_res_bounds(var)) + return -EINVAL; + + /* When no memory is allocated ignore the size check */ + if (ofbi->region->size != 0 && check_fb_size(ofbi, var)) + return -EINVAL; + + if (var->xres + var->xoffset > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yres + var->yoffset > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + DBG("xres = %d, yres = %d, vxres = %d, vyres = %d\n", + var->xres, var->yres, + var->xres_virtual, var->yres_virtual); + + if (display && display->driver->get_dimensions) { + u32 w, h; + display->driver->get_dimensions(display, &w, &h); + var->width = DIV_ROUND_CLOSEST(w, 1000); + var->height = DIV_ROUND_CLOSEST(h, 1000); + } else { + var->height = -1; + var->width = -1; + } + + var->grayscale = 0; + + if (display && display->driver->get_timings) { + struct omap_video_timings timings; + display->driver->get_timings(display, &timings); + + /* pixclock in ps, the rest in pixclock */ + var->pixclock = timings.pixelclock != 0 ? + KHZ2PICOS(timings.pixelclock / 1000) : + 0; + var->left_margin = timings.hbp; + var->right_margin = timings.hfp; + var->upper_margin = timings.vbp; + var->lower_margin = timings.vfp; + var->hsync_len = timings.hsw; + var->vsync_len = timings.vsw; + var->sync |= timings.hsync_level == OMAPDSS_SIG_ACTIVE_HIGH ? + FB_SYNC_HOR_HIGH_ACT : 0; + var->sync |= timings.vsync_level == OMAPDSS_SIG_ACTIVE_HIGH ? + FB_SYNC_VERT_HIGH_ACT : 0; + var->vmode = timings.interlace ? + FB_VMODE_INTERLACED : FB_VMODE_NONINTERLACED; + } else { + var->pixclock = 0; + var->left_margin = 0; + var->right_margin = 0; + var->upper_margin = 0; + var->lower_margin = 0; + var->hsync_len = 0; + var->vsync_len = 0; + var->sync = 0; + var->vmode = FB_VMODE_NONINTERLACED; + } + + return 0; +} + +/* + * --------------------------------------------------------------------------- + * fbdev framework callbacks + * --------------------------------------------------------------------------- + */ +static int omapfb_open(struct fb_info *fbi, int user) +{ + return 0; +} + +static int omapfb_release(struct fb_info *fbi, int user) +{ + return 0; +} + +static unsigned calc_rotation_offset_dma(const struct fb_var_screeninfo *var, + const struct fb_fix_screeninfo *fix, int rotation) +{ + unsigned offset; + + offset = var->yoffset * fix->line_length + + var->xoffset * (var->bits_per_pixel >> 3); + + return offset; +} + +static unsigned calc_rotation_offset_vrfb(const struct fb_var_screeninfo *var, + const struct fb_fix_screeninfo *fix, int rotation) +{ + unsigned offset; + + if (rotation == FB_ROTATE_UD) + offset = (var->yres_virtual - var->yres) * + fix->line_length; + else if (rotation == FB_ROTATE_CW) + offset = (var->yres_virtual - var->yres) * + (var->bits_per_pixel >> 3); + else + offset = 0; + + if (rotation == FB_ROTATE_UR) + offset += var->yoffset * fix->line_length + + var->xoffset * (var->bits_per_pixel >> 3); + else if (rotation == FB_ROTATE_UD) + offset -= var->yoffset * fix->line_length + + var->xoffset * (var->bits_per_pixel >> 3); + else if (rotation == FB_ROTATE_CW) + offset -= var->xoffset * fix->line_length + + var->yoffset * (var->bits_per_pixel >> 3); + else if (rotation == FB_ROTATE_CCW) + offset += var->xoffset * fix->line_length + + var->yoffset * (var->bits_per_pixel >> 3); + + return offset; +} + +static void omapfb_calc_addr(const struct omapfb_info *ofbi, + const struct fb_var_screeninfo *var, + const struct fb_fix_screeninfo *fix, + int rotation, u32 *paddr) +{ + u32 data_start_p; + int offset; + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + data_start_p = omapfb_get_region_rot_paddr(ofbi, rotation); + else + data_start_p = omapfb_get_region_paddr(ofbi); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + offset = calc_rotation_offset_vrfb(var, fix, rotation); + else + offset = calc_rotation_offset_dma(var, fix, rotation); + + data_start_p += offset; + + if (offset) + DBG("offset %d, %d = %d\n", + var->xoffset, var->yoffset, offset); + + DBG("paddr %x\n", data_start_p); + + *paddr = data_start_p; +} + +/* setup overlay according to the fb */ +int omapfb_setup_overlay(struct fb_info *fbi, struct omap_overlay *ovl, + u16 posx, u16 posy, u16 outw, u16 outh) +{ + int r = 0; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_var_screeninfo *var = &fbi->var; + struct fb_fix_screeninfo *fix = &fbi->fix; + enum omap_color_mode mode = 0; + u32 data_start_p = 0; + struct omap_overlay_info info; + int xres, yres; + int screen_width; + int mirror; + int rotation = var->rotate; + int i; + + WARN_ON(!atomic_read(&ofbi->region->lock_count)); + + for (i = 0; i < ofbi->num_overlays; i++) { + if (ovl != ofbi->overlays[i]) + continue; + + rotation = (rotation + ofbi->rotation[i]) % 4; + break; + } + + DBG("setup_overlay %d, posx %d, posy %d, outw %d, outh %d\n", ofbi->id, + posx, posy, outw, outh); + + if (rotation == FB_ROTATE_CW || rotation == FB_ROTATE_CCW) { + xres = var->yres; + yres = var->xres; + } else { + xres = var->xres; + yres = var->yres; + } + + if (ofbi->region->size) + omapfb_calc_addr(ofbi, var, fix, rotation, &data_start_p); + + r = fb_mode_to_dss_mode(var, &mode); + if (r) { + DBG("fb_mode_to_dss_mode failed"); + goto err; + } + + switch (var->nonstd) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUY422: + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + screen_width = fix->line_length + / (var->bits_per_pixel >> 2); + break; + } + default: + screen_width = fix->line_length / (var->bits_per_pixel >> 3); + break; + } + + ovl->get_overlay_info(ovl, &info); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + mirror = 0; + else + mirror = ofbi->mirror; + + info.paddr = data_start_p; + info.screen_width = screen_width; + info.width = xres; + info.height = yres; + info.color_mode = mode; + info.rotation_type = ofbi->rotation_type; + info.rotation = rotation; + info.mirror = mirror; + + info.pos_x = posx; + info.pos_y = posy; + info.out_width = outw; + info.out_height = outh; + + r = ovl->set_overlay_info(ovl, &info); + if (r) { + DBG("ovl->setup_overlay_info failed\n"); + goto err; + } + + return 0; + +err: + DBG("setup_overlay failed\n"); + return r; +} + +/* apply var to the overlay */ +int omapfb_apply_changes(struct fb_info *fbi, int init) +{ + int r = 0; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_var_screeninfo *var = &fbi->var; + struct omap_overlay *ovl; + u16 posx, posy; + u16 outw, outh; + int i; + +#ifdef DEBUG + if (omapfb_test_pattern) + fill_fb(fbi); +#endif + + WARN_ON(!atomic_read(&ofbi->region->lock_count)); + + for (i = 0; i < ofbi->num_overlays; i++) { + ovl = ofbi->overlays[i]; + + DBG("apply_changes, fb %d, ovl %d\n", ofbi->id, ovl->id); + + if (ofbi->region->size == 0) { + /* the fb is not available. disable the overlay */ + omapfb_overlay_enable(ovl, 0); + if (!init && ovl->manager) + ovl->manager->apply(ovl->manager); + continue; + } + + if (init || (ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0) { + int rotation = (var->rotate + ofbi->rotation[i]) % 4; + if (rotation == FB_ROTATE_CW || + rotation == FB_ROTATE_CCW) { + outw = var->yres; + outh = var->xres; + } else { + outw = var->xres; + outh = var->yres; + } + } else { + struct omap_overlay_info info; + ovl->get_overlay_info(ovl, &info); + outw = info.out_width; + outh = info.out_height; + } + + if (init) { + posx = 0; + posy = 0; + } else { + struct omap_overlay_info info; + ovl->get_overlay_info(ovl, &info); + posx = info.pos_x; + posy = info.pos_y; + } + + r = omapfb_setup_overlay(fbi, ovl, posx, posy, outw, outh); + if (r) + goto err; + + if (!init && ovl->manager) + ovl->manager->apply(ovl->manager); + } + return 0; +err: + DBG("apply_changes failed\n"); + return r; +} + +/* checks var and eventually tweaks it to something supported, + * DO NOT MODIFY PAR */ +static int omapfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + int r; + + DBG("check_var(%d)\n", FB2OFB(fbi)->id); + + omapfb_get_mem_region(ofbi->region); + + r = check_fb_var(fbi, var); + + omapfb_put_mem_region(ofbi->region); + + return r; +} + +/* set the video mode according to info->var */ +static int omapfb_set_par(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + int r; + + DBG("set_par(%d)\n", FB2OFB(fbi)->id); + + omapfb_get_mem_region(ofbi->region); + + set_fb_fix(fbi); + + r = setup_vrfb_rotation(fbi); + if (r) + goto out; + + r = omapfb_apply_changes(fbi, 0); + + out: + omapfb_put_mem_region(ofbi->region); + + return r; +} + +static int omapfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_var_screeninfo new_var; + int r; + + DBG("pan_display(%d)\n", FB2OFB(fbi)->id); + + if (var->xoffset == fbi->var.xoffset && + var->yoffset == fbi->var.yoffset) + return 0; + + new_var = fbi->var; + new_var.xoffset = var->xoffset; + new_var.yoffset = var->yoffset; + + fbi->var = new_var; + + omapfb_get_mem_region(ofbi->region); + + r = omapfb_apply_changes(fbi, 0); + + omapfb_put_mem_region(ofbi->region); + + return r; +} + +static void mmap_user_open(struct vm_area_struct *vma) +{ + struct omapfb2_mem_region *rg = vma->vm_private_data; + + omapfb_get_mem_region(rg); + atomic_inc(&rg->map_count); + omapfb_put_mem_region(rg); +} + +static void mmap_user_close(struct vm_area_struct *vma) +{ + struct omapfb2_mem_region *rg = vma->vm_private_data; + + omapfb_get_mem_region(rg); + atomic_dec(&rg->map_count); + omapfb_put_mem_region(rg); +} + +static struct vm_operations_struct mmap_user_ops = { + .open = mmap_user_open, + .close = mmap_user_close, +}; + +static int omapfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_fix_screeninfo *fix = &fbi->fix; + struct omapfb2_mem_region *rg; + unsigned long start; + u32 len; + int r; + + rg = omapfb_get_mem_region(ofbi->region); + + start = omapfb_get_region_paddr(ofbi); + len = fix->smem_len; + + DBG("user mmap region start %lx, len %d, off %lx\n", start, len, + vma->vm_pgoff << PAGE_SHIFT); + + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &mmap_user_ops; + vma->vm_private_data = rg; + + r = vm_iomap_memory(vma, start, len); + if (r) + goto error; + + /* vm_ops.open won't be called for mmap itself. */ + atomic_inc(&rg->map_count); + + omapfb_put_mem_region(rg); + + return 0; + +error: + omapfb_put_mem_region(ofbi->region); + + return r; +} + +/* Store a single color palette entry into a pseudo palette or the hardware + * palette if one is available. For now we support only 16bpp and thus store + * the entry only to the pseudo palette. + */ +static int _setcolreg(struct fb_info *fbi, u_int regno, u_int red, u_int green, + u_int blue, u_int transp, int update_hw_pal) +{ + /*struct omapfb_info *ofbi = FB2OFB(fbi);*/ + /*struct omapfb2_device *fbdev = ofbi->fbdev;*/ + struct fb_var_screeninfo *var = &fbi->var; + int r = 0; + + enum omapfb_color_format mode = OMAPFB_COLOR_RGB24U; /* XXX */ + + /*switch (plane->color_mode) {*/ + switch (mode) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUV420: + case OMAPFB_COLOR_YUY422: + r = -EINVAL; + break; + case OMAPFB_COLOR_CLUT_8BPP: + case OMAPFB_COLOR_CLUT_4BPP: + case OMAPFB_COLOR_CLUT_2BPP: + case OMAPFB_COLOR_CLUT_1BPP: + /* + if (fbdev->ctrl->setcolreg) + r = fbdev->ctrl->setcolreg(regno, red, green, blue, + transp, update_hw_pal); + */ + /* Fallthrough */ + r = -EINVAL; + break; + case OMAPFB_COLOR_RGB565: + case OMAPFB_COLOR_RGB444: + case OMAPFB_COLOR_RGB24P: + case OMAPFB_COLOR_RGB24U: + if (r != 0) + break; + + if (regno < 16) { + u32 pal; + pal = ((red >> (16 - var->red.length)) << + var->red.offset) | + ((green >> (16 - var->green.length)) << + var->green.offset) | + (blue >> (16 - var->blue.length)); + ((u32 *)(fbi->pseudo_palette))[regno] = pal; + } + break; + default: + BUG(); + } + return r; +} + +static int omapfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + DBG("setcolreg\n"); + + return _setcolreg(info, regno, red, green, blue, transp, 1); +} + +static int omapfb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int count, index, r; + u16 *red, *green, *blue, *transp; + u16 trans = 0xffff; + + DBG("setcmap\n"); + + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + index = cmap->start; + + for (count = 0; count < cmap->len; count++) { + if (transp) + trans = *transp++; + r = _setcolreg(info, index++, *red++, *green++, *blue++, trans, + count == cmap->len - 1); + if (r != 0) + return r; + } + + return 0; +} + +static int omapfb_blank(int blank, struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_dss_device *display = fb2display(fbi); + struct omapfb_display_data *d; + int r = 0; + + if (!display) + return -EINVAL; + + omapfb_lock(fbdev); + + d = get_display_data(fbdev, display); + + switch (blank) { + case FB_BLANK_UNBLANK: + if (display->state == OMAP_DSS_DISPLAY_ACTIVE) + goto exit; + + r = display->driver->enable(display); + + if ((display->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) && + d->update_mode == OMAPFB_AUTO_UPDATE && + !d->auto_update_work_enabled) + omapfb_start_auto_update(fbdev, display); + + break; + + case FB_BLANK_NORMAL: + /* FB_BLANK_NORMAL could be implemented. + * Needs DSS additions. */ + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + if (display->state != OMAP_DSS_DISPLAY_ACTIVE) + goto exit; + + if (d->auto_update_work_enabled) + omapfb_stop_auto_update(fbdev, display); + + display->driver->disable(display); + + break; + + default: + r = -EINVAL; + } + +exit: + omapfb_unlock(fbdev); + + return r; +} + +#if 0 +/* XXX fb_read and fb_write are needed for VRFB */ +ssize_t omapfb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + DBG("omapfb_write %d, %lu\n", count, (unsigned long)*ppos); + /* XXX needed for VRFB */ + return count; +} +#endif + +static struct fb_ops omapfb_ops = { + .owner = THIS_MODULE, + .fb_open = omapfb_open, + .fb_release = omapfb_release, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = omapfb_blank, + .fb_ioctl = omapfb_ioctl, + .fb_check_var = omapfb_check_var, + .fb_set_par = omapfb_set_par, + .fb_pan_display = omapfb_pan_display, + .fb_mmap = omapfb_mmap, + .fb_setcolreg = omapfb_setcolreg, + .fb_setcmap = omapfb_setcmap, + /*.fb_write = omapfb_write,*/ +}; + +static void omapfb_free_fbmem(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb2_mem_region *rg; + + rg = ofbi->region; + + if (rg->token == NULL) + return; + + WARN_ON(atomic_read(&rg->map_count)); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + /* unmap the 0 angle rotation */ + if (rg->vrfb.vaddr[0]) { + iounmap(rg->vrfb.vaddr[0]); + rg->vrfb.vaddr[0] = NULL; + } + + omap_vrfb_release_ctx(&rg->vrfb); + } + + dma_free_attrs(fbdev->dev, rg->size, rg->token, rg->dma_handle, + &rg->attrs); + + rg->token = NULL; + rg->vaddr = NULL; + rg->paddr = 0; + rg->alloc = 0; + rg->size = 0; +} + +static void clear_fb_info(struct fb_info *fbi) +{ + memset(&fbi->var, 0, sizeof(fbi->var)); + memset(&fbi->fix, 0, sizeof(fbi->fix)); + strlcpy(fbi->fix.id, MODULE_NAME, sizeof(fbi->fix.id)); +} + +static int omapfb_free_all_fbmem(struct omapfb2_device *fbdev) +{ + int i; + + DBG("free all fbmem\n"); + + for (i = 0; i < fbdev->num_fbs; i++) { + struct fb_info *fbi = fbdev->fbs[i]; + omapfb_free_fbmem(fbi); + clear_fb_info(fbi); + } + + return 0; +} + +static int omapfb_alloc_fbmem(struct fb_info *fbi, unsigned long size, + unsigned long paddr) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb2_mem_region *rg; + void *token; + DEFINE_DMA_ATTRS(attrs); + dma_addr_t dma_handle; + int r; + + rg = ofbi->region; + + rg->paddr = 0; + rg->vaddr = NULL; + memset(&rg->vrfb, 0, sizeof rg->vrfb); + rg->size = 0; + rg->type = 0; + rg->alloc = false; + rg->map = false; + + size = PAGE_ALIGN(size); + + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &attrs); + + DBG("allocating %lu bytes for fb %d\n", size, ofbi->id); + + token = dma_alloc_attrs(fbdev->dev, size, &dma_handle, + GFP_KERNEL, &attrs); + + if (token == NULL) { + dev_err(fbdev->dev, "failed to allocate framebuffer\n"); + return -ENOMEM; + } + + DBG("allocated VRAM paddr %lx, vaddr %p\n", + (unsigned long)dma_handle, token); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + r = omap_vrfb_request_ctx(&rg->vrfb); + if (r) { + dma_free_attrs(fbdev->dev, size, token, dma_handle, + &attrs); + dev_err(fbdev->dev, "vrfb create ctx failed\n"); + return r; + } + } + + rg->attrs = attrs; + rg->token = token; + rg->dma_handle = dma_handle; + + rg->paddr = (unsigned long)dma_handle; + rg->vaddr = (void __iomem *)token; + rg->size = size; + rg->alloc = 1; + + return 0; +} + +/* allocate fbmem using display resolution as reference */ +static int omapfb_alloc_fbmem_display(struct fb_info *fbi, unsigned long size, + unsigned long paddr) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_dss_device *display; + int bytespp; + + display = fb2display(fbi); + + if (!display) + return 0; + + switch (omapfb_get_recommended_bpp(fbdev, display)) { + case 16: + bytespp = 2; + break; + case 24: + bytespp = 4; + break; + default: + bytespp = 4; + break; + } + + if (!size) { + u16 w, h; + + display->driver->get_resolution(display, &w, &h); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + size = max(omap_vrfb_min_phys_size(w, h, bytespp), + omap_vrfb_min_phys_size(h, w, bytespp)); + + DBG("adjusting fb mem size for VRFB, %u -> %lu\n", + w * h * bytespp, size); + } else { + size = w * h * bytespp; + } + } + + if (!size) + return 0; + + return omapfb_alloc_fbmem(fbi, size, paddr); +} + +static int omapfb_parse_vram_param(const char *param, int max_entries, + unsigned long *sizes, unsigned long *paddrs) +{ + int fbnum; + unsigned long size; + unsigned long paddr = 0; + char *p, *start; + + start = (char *)param; + + while (1) { + p = start; + + fbnum = simple_strtoul(p, &p, 10); + + if (p == start) + return -EINVAL; + + if (*p != ':') + return -EINVAL; + + if (fbnum >= max_entries) + return -EINVAL; + + size = memparse(p + 1, &p); + + if (!size) + return -EINVAL; + + paddr = 0; + + if (*p == '@') { + paddr = simple_strtoul(p + 1, &p, 16); + + if (!paddr) + return -EINVAL; + + } + + WARN_ONCE(paddr, + "reserving memory at predefined address not supported\n"); + + paddrs[fbnum] = paddr; + sizes[fbnum] = size; + + if (*p == 0) + break; + + if (*p != ',') + return -EINVAL; + + ++p; + + start = p; + } + + return 0; +} + +static int omapfb_allocate_all_fbs(struct omapfb2_device *fbdev) +{ + int i, r; + unsigned long vram_sizes[10]; + unsigned long vram_paddrs[10]; + + memset(&vram_sizes, 0, sizeof(vram_sizes)); + memset(&vram_paddrs, 0, sizeof(vram_paddrs)); + + if (def_vram && omapfb_parse_vram_param(def_vram, 10, + vram_sizes, vram_paddrs)) { + dev_err(fbdev->dev, "failed to parse vram parameter\n"); + + memset(&vram_sizes, 0, sizeof(vram_sizes)); + memset(&vram_paddrs, 0, sizeof(vram_paddrs)); + } + + for (i = 0; i < fbdev->num_fbs; i++) { + /* allocate memory automatically only for fb0, or if + * excplicitly defined with vram or plat data option */ + if (i == 0 || vram_sizes[i] != 0) { + r = omapfb_alloc_fbmem_display(fbdev->fbs[i], + vram_sizes[i], vram_paddrs[i]); + + if (r) + return r; + } + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + struct omapfb2_mem_region *rg; + rg = ofbi->region; + + DBG("region%d phys %08x virt %p size=%lu\n", + i, + rg->paddr, + rg->vaddr, + rg->size); + } + + return 0; +} + +static void omapfb_clear_fb(struct fb_info *fbi) +{ + const struct fb_fillrect rect = { + .dx = 0, + .dy = 0, + .width = fbi->var.xres_virtual, + .height = fbi->var.yres_virtual, + .color = 0, + .rop = ROP_COPY, + }; + + cfb_fillrect(fbi, &rect); +} + +int omapfb_realloc_fbmem(struct fb_info *fbi, unsigned long size, int type) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb2_mem_region *rg = ofbi->region; + unsigned long old_size = rg->size; + unsigned long old_paddr = rg->paddr; + int old_type = rg->type; + int r; + + if (type != OMAPFB_MEMTYPE_SDRAM) + return -EINVAL; + + size = PAGE_ALIGN(size); + + if (old_size == size && old_type == type) + return 0; + + omapfb_free_fbmem(fbi); + + if (size == 0) { + clear_fb_info(fbi); + return 0; + } + + r = omapfb_alloc_fbmem(fbi, size, 0); + + if (r) { + if (old_size) + omapfb_alloc_fbmem(fbi, old_size, old_paddr); + + if (rg->size == 0) + clear_fb_info(fbi); + + return r; + } + + if (old_size == size) + return 0; + + if (old_size == 0) { + DBG("initializing fb %d\n", ofbi->id); + r = omapfb_fb_init(fbdev, fbi); + if (r) { + DBG("omapfb_fb_init failed\n"); + goto err; + } + r = omapfb_apply_changes(fbi, 1); + if (r) { + DBG("omapfb_apply_changes failed\n"); + goto err; + } + } else { + struct fb_var_screeninfo new_var; + memcpy(&new_var, &fbi->var, sizeof(new_var)); + r = check_fb_var(fbi, &new_var); + if (r) + goto err; + memcpy(&fbi->var, &new_var, sizeof(fbi->var)); + set_fb_fix(fbi); + r = setup_vrfb_rotation(fbi); + if (r) + goto err; + } + + omapfb_clear_fb(fbi); + + return 0; +err: + omapfb_free_fbmem(fbi); + clear_fb_info(fbi); + return r; +} + +static void omapfb_auto_update_work(struct work_struct *work) +{ + struct omap_dss_device *dssdev; + struct omap_dss_driver *dssdrv; + struct omapfb_display_data *d; + u16 w, h; + unsigned int freq; + struct omapfb2_device *fbdev; + + d = container_of(work, struct omapfb_display_data, + auto_update_work.work); + + dssdev = d->dssdev; + dssdrv = dssdev->driver; + fbdev = d->fbdev; + + if (!dssdrv || !dssdrv->update) + return; + + if (dssdrv->sync) + dssdrv->sync(dssdev); + + dssdrv->get_resolution(dssdev, &w, &h); + dssdrv->update(dssdev, 0, 0, w, h); + + freq = auto_update_freq; + if (freq == 0) + freq = 20; + queue_delayed_work(fbdev->auto_update_wq, + &d->auto_update_work, HZ / freq); +} + +void omapfb_start_auto_update(struct omapfb2_device *fbdev, + struct omap_dss_device *display) +{ + struct omapfb_display_data *d; + + if (fbdev->auto_update_wq == NULL) { + struct workqueue_struct *wq; + + wq = create_singlethread_workqueue("omapfb_auto_update"); + + if (wq == NULL) { + dev_err(fbdev->dev, "Failed to create workqueue for " + "auto-update\n"); + return; + } + + fbdev->auto_update_wq = wq; + } + + d = get_display_data(fbdev, display); + + INIT_DELAYED_WORK(&d->auto_update_work, omapfb_auto_update_work); + + d->auto_update_work_enabled = true; + + omapfb_auto_update_work(&d->auto_update_work.work); +} + +void omapfb_stop_auto_update(struct omapfb2_device *fbdev, + struct omap_dss_device *display) +{ + struct omapfb_display_data *d; + + d = get_display_data(fbdev, display); + + cancel_delayed_work_sync(&d->auto_update_work); + + d->auto_update_work_enabled = false; +} + +/* initialize fb_info, var, fix to something sane based on the display */ +static int omapfb_fb_init(struct omapfb2_device *fbdev, struct fb_info *fbi) +{ + struct fb_var_screeninfo *var = &fbi->var; + struct omap_dss_device *display = fb2display(fbi); + struct omapfb_info *ofbi = FB2OFB(fbi); + int r = 0; + + fbi->fbops = &omapfb_ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = fbdev->pseudo_palette; + + if (ofbi->region->size == 0) { + clear_fb_info(fbi); + return 0; + } + + var->nonstd = 0; + var->bits_per_pixel = 0; + + var->rotate = def_rotate; + + if (display) { + u16 w, h; + int rotation = (var->rotate + ofbi->rotation[0]) % 4; + + display->driver->get_resolution(display, &w, &h); + + if (rotation == FB_ROTATE_CW || + rotation == FB_ROTATE_CCW) { + var->xres = h; + var->yres = w; + } else { + var->xres = w; + var->yres = h; + } + + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + if (!var->bits_per_pixel) { + switch (omapfb_get_recommended_bpp(fbdev, display)) { + case 16: + var->bits_per_pixel = 16; + break; + case 24: + var->bits_per_pixel = 32; + break; + default: + dev_err(fbdev->dev, "illegal display " + "bpp\n"); + return -EINVAL; + } + } + } else { + /* if there's no display, let's just guess some basic values */ + var->xres = 320; + var->yres = 240; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + if (!var->bits_per_pixel) + var->bits_per_pixel = 16; + } + + r = check_fb_var(fbi, var); + if (r) + goto err; + + set_fb_fix(fbi); + r = setup_vrfb_rotation(fbi); + if (r) + goto err; + + r = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (r) + dev_err(fbdev->dev, "unable to allocate color map memory\n"); + +err: + return r; +} + +static void fbinfo_cleanup(struct omapfb2_device *fbdev, struct fb_info *fbi) +{ + fb_dealloc_cmap(&fbi->cmap); +} + + +static void omapfb_free_resources(struct omapfb2_device *fbdev) +{ + int i; + + DBG("free_resources\n"); + + if (fbdev == NULL) + return; + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + int j; + + for (j = 0; j < ofbi->num_overlays; j++) { + struct omap_overlay *ovl = ofbi->overlays[j]; + ovl->disable(ovl); + } + } + + for (i = 0; i < fbdev->num_fbs; i++) + unregister_framebuffer(fbdev->fbs[i]); + + /* free the reserved fbmem */ + omapfb_free_all_fbmem(fbdev); + + for (i = 0; i < fbdev->num_fbs; i++) { + fbinfo_cleanup(fbdev, fbdev->fbs[i]); + framebuffer_release(fbdev->fbs[i]); + } + + for (i = 0; i < fbdev->num_displays; i++) { + struct omap_dss_device *dssdev = fbdev->displays[i].dssdev; + + if (fbdev->displays[i].auto_update_work_enabled) + omapfb_stop_auto_update(fbdev, dssdev); + + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) + dssdev->driver->disable(dssdev); + + dssdev->driver->disconnect(dssdev); + + omap_dss_put_device(dssdev); + } + + if (fbdev->auto_update_wq != NULL) { + flush_workqueue(fbdev->auto_update_wq); + destroy_workqueue(fbdev->auto_update_wq); + fbdev->auto_update_wq = NULL; + } + + dev_set_drvdata(fbdev->dev, NULL); +} + +static int omapfb_create_framebuffers(struct omapfb2_device *fbdev) +{ + int r, i; + + fbdev->num_fbs = 0; + + DBG("create %d framebuffers\n", CONFIG_FB_OMAP2_NUM_FBS); + + /* allocate fb_infos */ + for (i = 0; i < CONFIG_FB_OMAP2_NUM_FBS; i++) { + struct fb_info *fbi; + struct omapfb_info *ofbi; + + fbi = framebuffer_alloc(sizeof(struct omapfb_info), + fbdev->dev); + + if (fbi == NULL) { + dev_err(fbdev->dev, + "unable to allocate memory for plane info\n"); + return -ENOMEM; + } + + clear_fb_info(fbi); + + fbdev->fbs[i] = fbi; + + ofbi = FB2OFB(fbi); + ofbi->fbdev = fbdev; + ofbi->id = i; + + ofbi->region = &fbdev->regions[i]; + ofbi->region->id = i; + init_rwsem(&ofbi->region->lock); + + /* assign these early, so that fb alloc can use them */ + ofbi->rotation_type = def_vrfb ? OMAP_DSS_ROT_VRFB : + OMAP_DSS_ROT_DMA; + ofbi->mirror = def_mirror; + + fbdev->num_fbs++; + } + + DBG("fb_infos allocated\n"); + + /* assign overlays for the fbs */ + for (i = 0; i < min(fbdev->num_fbs, fbdev->num_overlays); i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + + ofbi->overlays[0] = fbdev->overlays[i]; + ofbi->num_overlays = 1; + } + + /* allocate fb memories */ + r = omapfb_allocate_all_fbs(fbdev); + if (r) { + dev_err(fbdev->dev, "failed to allocate fbmem\n"); + return r; + } + + DBG("fbmems allocated\n"); + + /* setup fb_infos */ + for (i = 0; i < fbdev->num_fbs; i++) { + struct fb_info *fbi = fbdev->fbs[i]; + struct omapfb_info *ofbi = FB2OFB(fbi); + + omapfb_get_mem_region(ofbi->region); + r = omapfb_fb_init(fbdev, fbi); + omapfb_put_mem_region(ofbi->region); + + if (r) { + dev_err(fbdev->dev, "failed to setup fb_info\n"); + return r; + } + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct fb_info *fbi = fbdev->fbs[i]; + struct omapfb_info *ofbi = FB2OFB(fbi); + + if (ofbi->region->size == 0) + continue; + + omapfb_clear_fb(fbi); + } + + DBG("fb_infos initialized\n"); + + for (i = 0; i < fbdev->num_fbs; i++) { + r = register_framebuffer(fbdev->fbs[i]); + if (r != 0) { + dev_err(fbdev->dev, + "registering framebuffer %d failed\n", i); + return r; + } + } + + DBG("framebuffers registered\n"); + + for (i = 0; i < fbdev->num_fbs; i++) { + struct fb_info *fbi = fbdev->fbs[i]; + struct omapfb_info *ofbi = FB2OFB(fbi); + + omapfb_get_mem_region(ofbi->region); + r = omapfb_apply_changes(fbi, 1); + omapfb_put_mem_region(ofbi->region); + + if (r) { + dev_err(fbdev->dev, "failed to change mode\n"); + return r; + } + } + + /* Enable fb0 */ + if (fbdev->num_fbs > 0) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[0]); + + if (ofbi->num_overlays > 0) { + struct omap_overlay *ovl = ofbi->overlays[0]; + + ovl->manager->apply(ovl->manager); + + r = omapfb_overlay_enable(ovl, 1); + + if (r) { + dev_err(fbdev->dev, + "failed to enable overlay\n"); + return r; + } + } + } + + DBG("create_framebuffers done\n"); + + return 0; +} + +static int omapfb_mode_to_timings(const char *mode_str, + struct omap_dss_device *display, + struct omap_video_timings *timings, u8 *bpp) +{ + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct fb_ops *fbops; + int r; + +#ifdef CONFIG_OMAP2_DSS_VENC + if (strcmp(mode_str, "pal") == 0) { + *timings = omap_dss_pal_timings; + *bpp = 24; + return 0; + } else if (strcmp(mode_str, "ntsc") == 0) { + *timings = omap_dss_ntsc_timings; + *bpp = 24; + return 0; + } +#endif + + /* this is quite a hack, but I wanted to use the modedb and for + * that we need fb_info and var, so we create dummy ones */ + + *bpp = 0; + fbi = NULL; + var = NULL; + fbops = NULL; + + fbi = kzalloc(sizeof(*fbi), GFP_KERNEL); + if (fbi == NULL) { + r = -ENOMEM; + goto err; + } + + var = kzalloc(sizeof(*var), GFP_KERNEL); + if (var == NULL) { + r = -ENOMEM; + goto err; + } + + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (fbops == NULL) { + r = -ENOMEM; + goto err; + } + + fbi->fbops = fbops; + + r = fb_find_mode(var, fbi, mode_str, NULL, 0, NULL, 24); + if (r == 0) { + r = -EINVAL; + goto err; + } + + if (display->driver->get_timings) { + display->driver->get_timings(display, timings); + } else { + timings->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + timings->de_level = OMAPDSS_SIG_ACTIVE_HIGH; + timings->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + } + + timings->pixelclock = PICOS2KHZ(var->pixclock) * 1000; + timings->hbp = var->left_margin; + timings->hfp = var->right_margin; + timings->vbp = var->upper_margin; + timings->vfp = var->lower_margin; + timings->hsw = var->hsync_len; + timings->vsw = var->vsync_len; + timings->x_res = var->xres; + timings->y_res = var->yres; + timings->hsync_level = var->sync & FB_SYNC_HOR_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + timings->vsync_level = var->sync & FB_SYNC_VERT_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + timings->interlace = var->vmode & FB_VMODE_INTERLACED; + + switch (var->bits_per_pixel) { + case 16: + *bpp = 16; + break; + case 24: + case 32: + default: + *bpp = 24; + break; + } + + r = 0; + +err: + kfree(fbi); + kfree(var); + kfree(fbops); + + return r; +} + +static int omapfb_set_def_mode(struct omapfb2_device *fbdev, + struct omap_dss_device *display, char *mode_str) +{ + int r; + u8 bpp; + struct omap_video_timings timings, temp_timings; + struct omapfb_display_data *d; + + r = omapfb_mode_to_timings(mode_str, display, &timings, &bpp); + if (r) + return r; + + d = get_display_data(fbdev, display); + d->bpp_override = bpp; + + if (display->driver->check_timings) { + r = display->driver->check_timings(display, &timings); + if (r) + return r; + } else { + /* If check_timings is not present compare xres and yres */ + if (display->driver->get_timings) { + display->driver->get_timings(display, &temp_timings); + + if (temp_timings.x_res != timings.x_res || + temp_timings.y_res != timings.y_res) + return -EINVAL; + } + } + + if (display->driver->set_timings) + display->driver->set_timings(display, &timings); + + return 0; +} + +static int omapfb_get_recommended_bpp(struct omapfb2_device *fbdev, + struct omap_dss_device *dssdev) +{ + struct omapfb_display_data *d; + + BUG_ON(dssdev->driver->get_recommended_bpp == NULL); + + d = get_display_data(fbdev, dssdev); + + if (d->bpp_override != 0) + return d->bpp_override; + + return dssdev->driver->get_recommended_bpp(dssdev); +} + +static int omapfb_parse_def_modes(struct omapfb2_device *fbdev) +{ + char *str, *options, *this_opt; + int r = 0; + + str = kstrdup(def_mode, GFP_KERNEL); + if (!str) + return -ENOMEM; + options = str; + + while (!r && (this_opt = strsep(&options, ",")) != NULL) { + char *p, *display_str, *mode_str; + struct omap_dss_device *display; + int i; + + p = strchr(this_opt, ':'); + if (!p) { + r = -EINVAL; + break; + } + + *p = 0; + display_str = this_opt; + mode_str = p + 1; + + display = NULL; + for (i = 0; i < fbdev->num_displays; ++i) { + if (strcmp(fbdev->displays[i].dssdev->name, + display_str) == 0) { + display = fbdev->displays[i].dssdev; + break; + } + } + + if (!display) { + r = -EINVAL; + break; + } + + r = omapfb_set_def_mode(fbdev, display, mode_str); + if (r) + break; + } + + kfree(str); + + return r; +} + +static void fb_videomode_to_omap_timings(struct fb_videomode *m, + struct omap_dss_device *display, + struct omap_video_timings *t) +{ + if (display->driver->get_timings) { + display->driver->get_timings(display, t); + } else { + t->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + t->de_level = OMAPDSS_SIG_ACTIVE_HIGH; + t->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + } + + t->x_res = m->xres; + t->y_res = m->yres; + t->pixelclock = PICOS2KHZ(m->pixclock) * 1000; + t->hsw = m->hsync_len; + t->hfp = m->right_margin; + t->hbp = m->left_margin; + t->vsw = m->vsync_len; + t->vfp = m->lower_margin; + t->vbp = m->upper_margin; + t->hsync_level = m->sync & FB_SYNC_HOR_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + t->vsync_level = m->sync & FB_SYNC_VERT_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + t->interlace = m->vmode & FB_VMODE_INTERLACED; +} + +static int omapfb_find_best_mode(struct omap_dss_device *display, + struct omap_video_timings *timings) +{ + struct fb_monspecs *specs; + u8 *edid; + int r, i, best_idx, len; + + if (!display->driver->read_edid) + return -ENODEV; + + len = 0x80 * 2; + edid = kmalloc(len, GFP_KERNEL); + if (edid == NULL) + return -ENOMEM; + + r = display->driver->read_edid(display, edid, len); + if (r < 0) + goto err1; + + specs = kzalloc(sizeof(*specs), GFP_KERNEL); + if (specs == NULL) { + r = -ENOMEM; + goto err1; + } + + fb_edid_to_monspecs(edid, specs); + + best_idx = -1; + + for (i = 0; i < specs->modedb_len; ++i) { + struct fb_videomode *m; + struct omap_video_timings t; + + m = &specs->modedb[i]; + + if (m->pixclock == 0) + continue; + + /* skip repeated pixel modes */ + if (m->xres == 2880 || m->xres == 1440) + continue; + + if (m->vmode & FB_VMODE_INTERLACED || + m->vmode & FB_VMODE_DOUBLE) + continue; + + fb_videomode_to_omap_timings(m, display, &t); + + r = display->driver->check_timings(display, &t); + if (r == 0) { + best_idx = i; + break; + } + } + + if (best_idx == -1) { + r = -ENOENT; + goto err2; + } + + fb_videomode_to_omap_timings(&specs->modedb[best_idx], display, + timings); + + r = 0; + +err2: + fb_destroy_modedb(specs->modedb); + kfree(specs); +err1: + kfree(edid); + + return r; +} + +static int omapfb_init_display(struct omapfb2_device *fbdev, + struct omap_dss_device *dssdev) +{ + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omapfb_display_data *d; + int r; + + r = dssdrv->enable(dssdev); + if (r) { + dev_warn(fbdev->dev, "Failed to enable display '%s'\n", + dssdev->name); + return r; + } + + d = get_display_data(fbdev, dssdev); + + d->fbdev = fbdev; + + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) { + u16 w, h; + + if (auto_update) { + omapfb_start_auto_update(fbdev, dssdev); + d->update_mode = OMAPFB_AUTO_UPDATE; + } else { + d->update_mode = OMAPFB_MANUAL_UPDATE; + } + + if (dssdrv->enable_te) { + r = dssdrv->enable_te(dssdev, 1); + if (r) { + dev_err(fbdev->dev, "Failed to set TE\n"); + return r; + } + } + + dssdrv->get_resolution(dssdev, &w, &h); + r = dssdrv->update(dssdev, 0, 0, w, h); + if (r) { + dev_err(fbdev->dev, + "Failed to update display\n"); + return r; + } + } else { + d->update_mode = OMAPFB_AUTO_UPDATE; + } + + return 0; +} + +static int omapfb_init_connections(struct omapfb2_device *fbdev, + struct omap_dss_device *def_dssdev) +{ + int i, r; + struct omap_overlay_manager *mgr; + + r = def_dssdev->driver->connect(def_dssdev); + if (r) { + dev_err(fbdev->dev, "failed to connect default display\n"); + return r; + } + + for (i = 0; i < fbdev->num_displays; ++i) { + struct omap_dss_device *dssdev = fbdev->displays[i].dssdev; + + if (dssdev == def_dssdev) + continue; + + /* + * We don't care if the connect succeeds or not. We just want to + * connect as many displays as possible. + */ + dssdev->driver->connect(dssdev); + } + + mgr = omapdss_find_mgr_from_display(def_dssdev); + + if (!mgr) { + dev_err(fbdev->dev, "no ovl manager for the default display\n"); + return -EINVAL; + } + + for (i = 0; i < fbdev->num_overlays; i++) { + struct omap_overlay *ovl = fbdev->overlays[i]; + + if (ovl->manager) + ovl->unset_manager(ovl); + + r = ovl->set_manager(ovl, mgr); + if (r) + dev_warn(fbdev->dev, + "failed to connect overlay %s to manager %s\n", + ovl->name, mgr->name); + } + + return 0; +} + +static struct omap_dss_device * +omapfb_find_default_display(struct omapfb2_device *fbdev) +{ + const char *def_name; + int i; + + /* + * Search with the display name from the user or the board file, + * comparing to display names and aliases + */ + + def_name = omapdss_get_default_display_name(); + + if (def_name) { + for (i = 0; i < fbdev->num_displays; ++i) { + struct omap_dss_device *dssdev; + + dssdev = fbdev->displays[i].dssdev; + + if (dssdev->name && strcmp(def_name, dssdev->name) == 0) + return dssdev; + + if (strcmp(def_name, dssdev->alias) == 0) + return dssdev; + } + + /* def_name given but not found */ + return NULL; + } + + /* then look for DT alias display0 */ + for (i = 0; i < fbdev->num_displays; ++i) { + struct omap_dss_device *dssdev; + int id; + + dssdev = fbdev->displays[i].dssdev; + + if (dssdev->dev->of_node == NULL) + continue; + + id = of_alias_get_id(dssdev->dev->of_node, "display"); + if (id == 0) + return dssdev; + } + + /* return the first display we have in the list */ + return fbdev->displays[0].dssdev; +} + +static int omapfb_probe(struct platform_device *pdev) +{ + struct omapfb2_device *fbdev = NULL; + int r = 0; + int i; + struct omap_dss_device *def_display; + struct omap_dss_device *dssdev; + + DBG("omapfb_probe\n"); + + if (omapdss_is_initialized() == false) + return -EPROBE_DEFER; + + if (pdev->num_resources != 0) { + dev_err(&pdev->dev, "probed for an unknown device\n"); + r = -ENODEV; + goto err0; + } + + fbdev = devm_kzalloc(&pdev->dev, sizeof(struct omapfb2_device), + GFP_KERNEL); + if (fbdev == NULL) { + r = -ENOMEM; + goto err0; + } + + if (def_vrfb && !omap_vrfb_supported()) { + def_vrfb = 0; + dev_warn(&pdev->dev, "VRFB is not supported on this hardware, " + "ignoring the module parameter vrfb=y\n"); + } + + r = omapdss_compat_init(); + if (r) + goto err0; + + mutex_init(&fbdev->mtx); + + fbdev->dev = &pdev->dev; + platform_set_drvdata(pdev, fbdev); + + fbdev->num_displays = 0; + dssdev = NULL; + for_each_dss_dev(dssdev) { + struct omapfb_display_data *d; + + omap_dss_get_device(dssdev); + + if (!dssdev->driver) { + dev_warn(&pdev->dev, "no driver for display: %s\n", + dssdev->name); + omap_dss_put_device(dssdev); + continue; + } + + d = &fbdev->displays[fbdev->num_displays++]; + d->dssdev = dssdev; + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) + d->update_mode = OMAPFB_MANUAL_UPDATE; + else + d->update_mode = OMAPFB_AUTO_UPDATE; + } + + if (fbdev->num_displays == 0) { + dev_err(&pdev->dev, "no displays\n"); + r = -EPROBE_DEFER; + goto cleanup; + } + + fbdev->num_overlays = omap_dss_get_num_overlays(); + for (i = 0; i < fbdev->num_overlays; i++) + fbdev->overlays[i] = omap_dss_get_overlay(i); + + fbdev->num_managers = omap_dss_get_num_overlay_managers(); + for (i = 0; i < fbdev->num_managers; i++) + fbdev->managers[i] = omap_dss_get_overlay_manager(i); + + def_display = omapfb_find_default_display(fbdev); + if (def_display == NULL) { + dev_err(fbdev->dev, "failed to find default display\n"); + r = -EPROBE_DEFER; + goto cleanup; + } + + r = omapfb_init_connections(fbdev, def_display); + if (r) { + dev_err(fbdev->dev, "failed to init overlay connections\n"); + goto cleanup; + } + + if (def_mode && strlen(def_mode) > 0) { + if (omapfb_parse_def_modes(fbdev)) + dev_warn(&pdev->dev, "cannot parse default modes\n"); + } else if (def_display && def_display->driver->set_timings && + def_display->driver->check_timings) { + struct omap_video_timings t; + + r = omapfb_find_best_mode(def_display, &t); + + if (r == 0) + def_display->driver->set_timings(def_display, &t); + } + + r = omapfb_create_framebuffers(fbdev); + if (r) + goto cleanup; + + for (i = 0; i < fbdev->num_managers; i++) { + struct omap_overlay_manager *mgr; + mgr = fbdev->managers[i]; + r = mgr->apply(mgr); + if (r) + dev_warn(fbdev->dev, "failed to apply dispc config\n"); + } + + DBG("mgr->apply'ed\n"); + + if (def_display) { + r = omapfb_init_display(fbdev, def_display); + if (r) { + dev_err(fbdev->dev, + "failed to initialize default " + "display\n"); + goto cleanup; + } + } + + DBG("create sysfs for fbs\n"); + r = omapfb_create_sysfs(fbdev); + if (r) { + dev_err(fbdev->dev, "failed to create sysfs entries\n"); + goto cleanup; + } + + if (def_display) { + u16 w, h; + + def_display->driver->get_resolution(def_display, &w, &h); + + dev_info(fbdev->dev, "using display '%s' mode %dx%d\n", + def_display->name, w, h); + } + + return 0; + +cleanup: + omapfb_free_resources(fbdev); + omapdss_compat_uninit(); +err0: + dev_err(&pdev->dev, "failed to setup omapfb\n"); + return r; +} + +static int __exit omapfb_remove(struct platform_device *pdev) +{ + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + + /* FIXME: wait till completion of pending events */ + + omapfb_remove_sysfs(fbdev); + + omapfb_free_resources(fbdev); + + omapdss_compat_uninit(); + + return 0; +} + +static struct platform_driver omapfb_driver = { + .probe = omapfb_probe, + .remove = __exit_p(omapfb_remove), + .driver = { + .name = "omapfb", + .owner = THIS_MODULE, + }, +}; + +module_param_named(mode, def_mode, charp, 0); +module_param_named(vram, def_vram, charp, 0); +module_param_named(rotate, def_rotate, int, 0); +module_param_named(vrfb, def_vrfb, bool, 0); +module_param_named(mirror, def_mirror, bool, 0); + +module_platform_driver(omapfb_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@nokia.com>"); +MODULE_DESCRIPTION("OMAP2/3 Framebuffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/omap2/omapfb/omapfb-sysfs.c b/drivers/video/fbdev/omap2/omapfb/omapfb-sysfs.c new file mode 100644 index 000000000000..18fa9e1d0033 --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/omapfb-sysfs.c @@ -0,0 +1,605 @@ +/* + * linux/drivers/video/omap2/omapfb-sysfs.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/fb.h> +#include <linux/sysfs.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/omapfb.h> + +#include <video/omapdss.h> +#include <video/omapvrfb.h> + +#include "omapfb.h" + +static ssize_t show_rotate_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + + return snprintf(buf, PAGE_SIZE, "%d\n", ofbi->rotation_type); +} + +static ssize_t store_rotate_type(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_mem_region *rg; + int rot_type; + int r; + + r = kstrtoint(buf, 0, &rot_type); + if (r) + return r; + + if (rot_type != OMAP_DSS_ROT_DMA && rot_type != OMAP_DSS_ROT_VRFB) + return -EINVAL; + + if (!lock_fb_info(fbi)) + return -ENODEV; + + r = 0; + if (rot_type == ofbi->rotation_type) + goto out; + + rg = omapfb_get_mem_region(ofbi->region); + + if (rg->size) { + r = -EBUSY; + goto put_region; + } + + ofbi->rotation_type = rot_type; + + /* + * Since the VRAM for this FB is not allocated at the moment we don't + * need to do any further parameter checking at this point. + */ +put_region: + omapfb_put_mem_region(rg); +out: + unlock_fb_info(fbi); + + return r ? r : count; +} + + +static ssize_t show_mirror(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + + return snprintf(buf, PAGE_SIZE, "%d\n", ofbi->mirror); +} + +static ssize_t store_mirror(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + bool mirror; + int r; + struct fb_var_screeninfo new_var; + + r = strtobool(buf, &mirror); + if (r) + return r; + + if (!lock_fb_info(fbi)) + return -ENODEV; + + ofbi->mirror = mirror; + + omapfb_get_mem_region(ofbi->region); + + memcpy(&new_var, &fbi->var, sizeof(new_var)); + r = check_fb_var(fbi, &new_var); + if (r) + goto out; + memcpy(&fbi->var, &new_var, sizeof(fbi->var)); + + set_fb_fix(fbi); + + r = omapfb_apply_changes(fbi, 0); + if (r) + goto out; + + r = count; +out: + omapfb_put_mem_region(ofbi->region); + + unlock_fb_info(fbi); + + return r; +} + +static ssize_t show_overlays(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + ssize_t l = 0; + int t; + + if (!lock_fb_info(fbi)) + return -ENODEV; + omapfb_lock(fbdev); + + for (t = 0; t < ofbi->num_overlays; t++) { + struct omap_overlay *ovl = ofbi->overlays[t]; + int ovlnum; + + for (ovlnum = 0; ovlnum < fbdev->num_overlays; ++ovlnum) + if (ovl == fbdev->overlays[ovlnum]) + break; + + l += snprintf(buf + l, PAGE_SIZE - l, "%s%d", + t == 0 ? "" : ",", ovlnum); + } + + l += snprintf(buf + l, PAGE_SIZE - l, "\n"); + + omapfb_unlock(fbdev); + unlock_fb_info(fbi); + + return l; +} + +static struct omapfb_info *get_overlay_fb(struct omapfb2_device *fbdev, + struct omap_overlay *ovl) +{ + int i, t; + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + + for (t = 0; t < ofbi->num_overlays; t++) { + if (ofbi->overlays[t] == ovl) + return ofbi; + } + } + + return NULL; +} + +static ssize_t store_overlays(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_overlay *ovls[OMAPFB_MAX_OVL_PER_FB]; + struct omap_overlay *ovl; + int num_ovls, r, i; + int len; + bool added = false; + + num_ovls = 0; + + len = strlen(buf); + if (buf[len - 1] == '\n') + len = len - 1; + + if (!lock_fb_info(fbi)) + return -ENODEV; + omapfb_lock(fbdev); + + if (len > 0) { + char *p = (char *)buf; + int ovlnum; + + while (p < buf + len) { + int found; + if (num_ovls == OMAPFB_MAX_OVL_PER_FB) { + r = -EINVAL; + goto out; + } + + ovlnum = simple_strtoul(p, &p, 0); + if (ovlnum > fbdev->num_overlays) { + r = -EINVAL; + goto out; + } + + found = 0; + for (i = 0; i < num_ovls; ++i) { + if (ovls[i] == fbdev->overlays[ovlnum]) { + found = 1; + break; + } + } + + if (!found) + ovls[num_ovls++] = fbdev->overlays[ovlnum]; + + p++; + } + } + + for (i = 0; i < num_ovls; ++i) { + struct omapfb_info *ofbi2 = get_overlay_fb(fbdev, ovls[i]); + if (ofbi2 && ofbi2 != ofbi) { + dev_err(fbdev->dev, "overlay already in use\n"); + r = -EINVAL; + goto out; + } + } + + /* detach unused overlays */ + for (i = 0; i < ofbi->num_overlays; ++i) { + int t, found; + + ovl = ofbi->overlays[i]; + + found = 0; + + for (t = 0; t < num_ovls; ++t) { + if (ovl == ovls[t]) { + found = 1; + break; + } + } + + if (found) + continue; + + DBG("detaching %d\n", ofbi->overlays[i]->id); + + omapfb_get_mem_region(ofbi->region); + + omapfb_overlay_enable(ovl, 0); + + if (ovl->manager) + ovl->manager->apply(ovl->manager); + + omapfb_put_mem_region(ofbi->region); + + for (t = i + 1; t < ofbi->num_overlays; t++) { + ofbi->rotation[t-1] = ofbi->rotation[t]; + ofbi->overlays[t-1] = ofbi->overlays[t]; + } + + ofbi->num_overlays--; + i--; + } + + for (i = 0; i < num_ovls; ++i) { + int t, found; + + ovl = ovls[i]; + + found = 0; + + for (t = 0; t < ofbi->num_overlays; ++t) { + if (ovl == ofbi->overlays[t]) { + found = 1; + break; + } + } + + if (found) + continue; + ofbi->rotation[ofbi->num_overlays] = 0; + ofbi->overlays[ofbi->num_overlays++] = ovl; + + added = true; + } + + if (added) { + omapfb_get_mem_region(ofbi->region); + + r = omapfb_apply_changes(fbi, 0); + + omapfb_put_mem_region(ofbi->region); + + if (r) + goto out; + } + + r = count; +out: + omapfb_unlock(fbdev); + unlock_fb_info(fbi); + + return r; +} + +static ssize_t show_overlays_rotate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + ssize_t l = 0; + int t; + + if (!lock_fb_info(fbi)) + return -ENODEV; + + for (t = 0; t < ofbi->num_overlays; t++) { + l += snprintf(buf + l, PAGE_SIZE - l, "%s%d", + t == 0 ? "" : ",", ofbi->rotation[t]); + } + + l += snprintf(buf + l, PAGE_SIZE - l, "\n"); + + unlock_fb_info(fbi); + + return l; +} + +static ssize_t store_overlays_rotate(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + int num_ovls = 0, r, i; + int len; + bool changed = false; + u8 rotation[OMAPFB_MAX_OVL_PER_FB]; + + len = strlen(buf); + if (buf[len - 1] == '\n') + len = len - 1; + + if (!lock_fb_info(fbi)) + return -ENODEV; + + if (len > 0) { + char *p = (char *)buf; + + while (p < buf + len) { + int rot; + + if (num_ovls == ofbi->num_overlays) { + r = -EINVAL; + goto out; + } + + rot = simple_strtoul(p, &p, 0); + if (rot < 0 || rot > 3) { + r = -EINVAL; + goto out; + } + + if (ofbi->rotation[num_ovls] != rot) + changed = true; + + rotation[num_ovls++] = rot; + + p++; + } + } + + if (num_ovls != ofbi->num_overlays) { + r = -EINVAL; + goto out; + } + + if (changed) { + for (i = 0; i < num_ovls; ++i) + ofbi->rotation[i] = rotation[i]; + + omapfb_get_mem_region(ofbi->region); + + r = omapfb_apply_changes(fbi, 0); + + omapfb_put_mem_region(ofbi->region); + + if (r) + goto out; + + /* FIXME error handling? */ + } + + r = count; +out: + unlock_fb_info(fbi); + + return r; +} + +static ssize_t show_size(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + + return snprintf(buf, PAGE_SIZE, "%lu\n", ofbi->region->size); +} + +static ssize_t store_size(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_dss_device *display = fb2display(fbi); + struct omapfb2_mem_region *rg; + unsigned long size; + int r; + int i; + + r = kstrtoul(buf, 0, &size); + if (r) + return r; + + size = PAGE_ALIGN(size); + + if (!lock_fb_info(fbi)) + return -ENODEV; + + if (display && display->driver->sync) + display->driver->sync(display); + + rg = ofbi->region; + + down_write_nested(&rg->lock, rg->id); + atomic_inc(&rg->lock_count); + + if (atomic_read(&rg->map_count)) { + r = -EBUSY; + goto out; + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi2 = FB2OFB(fbdev->fbs[i]); + int j; + + if (ofbi2->region != rg) + continue; + + for (j = 0; j < ofbi2->num_overlays; j++) { + struct omap_overlay *ovl; + ovl = ofbi2->overlays[j]; + if (ovl->is_enabled(ovl)) { + r = -EBUSY; + goto out; + } + } + } + + if (size != ofbi->region->size) { + r = omapfb_realloc_fbmem(fbi, size, ofbi->region->type); + if (r) { + dev_err(dev, "realloc fbmem failed\n"); + goto out; + } + } + + r = count; +out: + atomic_dec(&rg->lock_count); + up_write(&rg->lock); + + unlock_fb_info(fbi); + + return r; +} + +static ssize_t show_phys(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + + return snprintf(buf, PAGE_SIZE, "%0x\n", ofbi->region->paddr); +} + +static ssize_t show_virt(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct omapfb_info *ofbi = FB2OFB(fbi); + + return snprintf(buf, PAGE_SIZE, "%p\n", ofbi->region->vaddr); +} + +static ssize_t show_upd_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + enum omapfb_update_mode mode; + int r; + + r = omapfb_get_update_mode(fbi, &mode); + + if (r) + return r; + + return snprintf(buf, PAGE_SIZE, "%u\n", (unsigned)mode); +} + +static ssize_t store_upd_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + unsigned mode; + int r; + + r = kstrtouint(buf, 0, &mode); + if (r) + return r; + + r = omapfb_set_update_mode(fbi, mode); + if (r) + return r; + + return count; +} + +static struct device_attribute omapfb_attrs[] = { + __ATTR(rotate_type, S_IRUGO | S_IWUSR, show_rotate_type, + store_rotate_type), + __ATTR(mirror, S_IRUGO | S_IWUSR, show_mirror, store_mirror), + __ATTR(size, S_IRUGO | S_IWUSR, show_size, store_size), + __ATTR(overlays, S_IRUGO | S_IWUSR, show_overlays, store_overlays), + __ATTR(overlays_rotate, S_IRUGO | S_IWUSR, show_overlays_rotate, + store_overlays_rotate), + __ATTR(phys_addr, S_IRUGO, show_phys, NULL), + __ATTR(virt_addr, S_IRUGO, show_virt, NULL), + __ATTR(update_mode, S_IRUGO | S_IWUSR, show_upd_mode, store_upd_mode), +}; + +int omapfb_create_sysfs(struct omapfb2_device *fbdev) +{ + int i; + int r; + + DBG("create sysfs for fbs\n"); + for (i = 0; i < fbdev->num_fbs; i++) { + int t; + for (t = 0; t < ARRAY_SIZE(omapfb_attrs); t++) { + r = device_create_file(fbdev->fbs[i]->dev, + &omapfb_attrs[t]); + + if (r) { + dev_err(fbdev->dev, "failed to create sysfs " + "file\n"); + return r; + } + } + } + + return 0; +} + +void omapfb_remove_sysfs(struct omapfb2_device *fbdev) +{ + int i, t; + + DBG("remove sysfs for fbs\n"); + for (i = 0; i < fbdev->num_fbs; i++) { + for (t = 0; t < ARRAY_SIZE(omapfb_attrs); t++) + device_remove_file(fbdev->fbs[i]->dev, + &omapfb_attrs[t]); + } +} + diff --git a/drivers/video/fbdev/omap2/omapfb/omapfb.h b/drivers/video/fbdev/omap2/omapfb/omapfb.h new file mode 100644 index 000000000000..623cd872a367 --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/omapfb.h @@ -0,0 +1,208 @@ +/* + * linux/drivers/video/omap2/omapfb.h + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __DRIVERS_VIDEO_OMAP2_OMAPFB_H__ +#define __DRIVERS_VIDEO_OMAP2_OMAPFB_H__ + +#ifdef CONFIG_FB_OMAP2_DEBUG_SUPPORT +#define DEBUG +#endif + +#include <linux/rwsem.h> +#include <linux/dma-attrs.h> +#include <linux/dma-mapping.h> + +#include <video/omapdss.h> + +#ifdef DEBUG +extern bool omapfb_debug; +#define DBG(format, ...) \ + do { \ + if (omapfb_debug) \ + printk(KERN_DEBUG "OMAPFB: " format, ## __VA_ARGS__); \ + } while (0) +#else +#define DBG(format, ...) +#endif + +#define FB2OFB(fb_info) ((struct omapfb_info *)(fb_info->par)) + +/* max number of overlays to which a framebuffer data can be direct */ +#define OMAPFB_MAX_OVL_PER_FB 3 + +struct omapfb2_mem_region { + int id; + struct dma_attrs attrs; + void *token; + dma_addr_t dma_handle; + u32 paddr; + void __iomem *vaddr; + struct vrfb vrfb; + unsigned long size; + u8 type; /* OMAPFB_PLANE_MEM_* */ + bool alloc; /* allocated by the driver */ + bool map; /* kernel mapped by the driver */ + atomic_t map_count; + struct rw_semaphore lock; + atomic_t lock_count; +}; + +/* appended to fb_info */ +struct omapfb_info { + int id; + struct omapfb2_mem_region *region; + int num_overlays; + struct omap_overlay *overlays[OMAPFB_MAX_OVL_PER_FB]; + struct omapfb2_device *fbdev; + enum omap_dss_rotation_type rotation_type; + u8 rotation[OMAPFB_MAX_OVL_PER_FB]; + bool mirror; +}; + +struct omapfb_display_data { + struct omapfb2_device *fbdev; + struct omap_dss_device *dssdev; + u8 bpp_override; + enum omapfb_update_mode update_mode; + bool auto_update_work_enabled; + struct delayed_work auto_update_work; +}; + +struct omapfb2_device { + struct device *dev; + struct mutex mtx; + + u32 pseudo_palette[17]; + + int state; + + unsigned num_fbs; + struct fb_info *fbs[10]; + struct omapfb2_mem_region regions[10]; + + unsigned num_displays; + struct omapfb_display_data displays[10]; + unsigned num_overlays; + struct omap_overlay *overlays[10]; + unsigned num_managers; + struct omap_overlay_manager *managers[10]; + + struct workqueue_struct *auto_update_wq; +}; + +struct omapfb_colormode { + enum omap_color_mode dssmode; + u32 bits_per_pixel; + u32 nonstd; + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; +}; + +void set_fb_fix(struct fb_info *fbi); +int check_fb_var(struct fb_info *fbi, struct fb_var_screeninfo *var); +int omapfb_realloc_fbmem(struct fb_info *fbi, unsigned long size, int type); +int omapfb_apply_changes(struct fb_info *fbi, int init); + +int omapfb_create_sysfs(struct omapfb2_device *fbdev); +void omapfb_remove_sysfs(struct omapfb2_device *fbdev); + +int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg); + +int dss_mode_to_fb_mode(enum omap_color_mode dssmode, + struct fb_var_screeninfo *var); + +int omapfb_setup_overlay(struct fb_info *fbi, struct omap_overlay *ovl, + u16 posx, u16 posy, u16 outw, u16 outh); + +void omapfb_start_auto_update(struct omapfb2_device *fbdev, + struct omap_dss_device *display); +void omapfb_stop_auto_update(struct omapfb2_device *fbdev, + struct omap_dss_device *display); +int omapfb_get_update_mode(struct fb_info *fbi, enum omapfb_update_mode *mode); +int omapfb_set_update_mode(struct fb_info *fbi, enum omapfb_update_mode mode); + +/* find the display connected to this fb, if any */ +static inline struct omap_dss_device *fb2display(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omap_overlay *ovl; + + /* XXX: returns the display connected to first attached overlay */ + + if (ofbi->num_overlays == 0) + return NULL; + + ovl = ofbi->overlays[0]; + + return ovl->get_device(ovl); +} + +static inline struct omapfb_display_data *get_display_data( + struct omapfb2_device *fbdev, struct omap_dss_device *dssdev) +{ + int i; + + for (i = 0; i < fbdev->num_displays; ++i) + if (fbdev->displays[i].dssdev == dssdev) + return &fbdev->displays[i]; + + /* This should never happen */ + BUG(); + return NULL; +} + +static inline void omapfb_lock(struct omapfb2_device *fbdev) +{ + mutex_lock(&fbdev->mtx); +} + +static inline void omapfb_unlock(struct omapfb2_device *fbdev) +{ + mutex_unlock(&fbdev->mtx); +} + +static inline int omapfb_overlay_enable(struct omap_overlay *ovl, + int enable) +{ + if (enable) + return ovl->enable(ovl); + else + return ovl->disable(ovl); +} + +static inline struct omapfb2_mem_region * +omapfb_get_mem_region(struct omapfb2_mem_region *rg) +{ + down_read_nested(&rg->lock, rg->id); + atomic_inc(&rg->lock_count); + return rg; +} + +static inline void omapfb_put_mem_region(struct omapfb2_mem_region *rg) +{ + atomic_dec(&rg->lock_count); + up_read(&rg->lock); +} + +#endif diff --git a/drivers/video/fbdev/omap2/vrfb.c b/drivers/video/fbdev/omap2/vrfb.c new file mode 100644 index 000000000000..f346b02eee1d --- /dev/null +++ b/drivers/video/fbdev/omap2/vrfb.c @@ -0,0 +1,399 @@ +/* + * VRFB Rotation Engine + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * 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. + * + * 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. + */ + +/*#define DEBUG*/ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <video/omapvrfb.h> + +#ifdef DEBUG +#define DBG(format, ...) pr_debug("VRFB: " format, ## __VA_ARGS__) +#else +#define DBG(format, ...) +#endif + +#define SMS_ROT_CONTROL(context) (0x0 + 0x10 * context) +#define SMS_ROT_SIZE(context) (0x4 + 0x10 * context) +#define SMS_ROT_PHYSICAL_BA(context) (0x8 + 0x10 * context) +#define SMS_ROT_VIRT_BASE(rot) (0x1000000 * (rot)) + +#define OMAP_VRFB_SIZE (2048 * 2048 * 4) + +#define VRFB_PAGE_WIDTH_EXP 5 /* Assuming SDRAM pagesize= 1024 */ +#define VRFB_PAGE_HEIGHT_EXP 5 /* 1024 = 2^5 * 2^5 */ +#define VRFB_PAGE_WIDTH (1 << VRFB_PAGE_WIDTH_EXP) +#define VRFB_PAGE_HEIGHT (1 << VRFB_PAGE_HEIGHT_EXP) +#define SMS_IMAGEHEIGHT_OFFSET 16 +#define SMS_IMAGEWIDTH_OFFSET 0 +#define SMS_PH_OFFSET 8 +#define SMS_PW_OFFSET 4 +#define SMS_PS_OFFSET 0 + +/* bitmap of reserved contexts */ +static unsigned long ctx_map; + +struct vrfb_ctx { + u32 base; + u32 physical_ba; + u32 control; + u32 size; +}; + +static DEFINE_MUTEX(ctx_lock); + +/* + * Access to this happens from client drivers or the PM core after wake-up. + * For the first case we require locking at the driver level, for the second + * we don't need locking, since no drivers will run until after the wake-up + * has finished. + */ + +static void __iomem *vrfb_base; + +static int num_ctxs; +static struct vrfb_ctx *ctxs; + +static bool vrfb_loaded; + +static void omap2_sms_write_rot_control(u32 val, unsigned ctx) +{ + __raw_writel(val, vrfb_base + SMS_ROT_CONTROL(ctx)); +} + +static void omap2_sms_write_rot_size(u32 val, unsigned ctx) +{ + __raw_writel(val, vrfb_base + SMS_ROT_SIZE(ctx)); +} + +static void omap2_sms_write_rot_physical_ba(u32 val, unsigned ctx) +{ + __raw_writel(val, vrfb_base + SMS_ROT_PHYSICAL_BA(ctx)); +} + +static inline void restore_hw_context(int ctx) +{ + omap2_sms_write_rot_control(ctxs[ctx].control, ctx); + omap2_sms_write_rot_size(ctxs[ctx].size, ctx); + omap2_sms_write_rot_physical_ba(ctxs[ctx].physical_ba, ctx); +} + +static u32 get_image_width_roundup(u16 width, u8 bytespp) +{ + unsigned long stride = width * bytespp; + unsigned long ceil_pages_per_stride = (stride / VRFB_PAGE_WIDTH) + + (stride % VRFB_PAGE_WIDTH != 0); + + return ceil_pages_per_stride * VRFB_PAGE_WIDTH / bytespp; +} + +/* + * This the extra space needed in the VRFB physical area for VRFB to safely wrap + * any memory accesses to the invisible part of the virtual view to the physical + * area. + */ +static inline u32 get_extra_physical_size(u16 image_width_roundup, u8 bytespp) +{ + return (OMAP_VRFB_LINE_LEN - image_width_roundup) * VRFB_PAGE_HEIGHT * + bytespp; +} + +void omap_vrfb_restore_context(void) +{ + int i; + unsigned long map = ctx_map; + + for (i = ffs(map); i; i = ffs(map)) { + /* i=1..32 */ + i--; + map &= ~(1 << i); + restore_hw_context(i); + } +} + +void omap_vrfb_adjust_size(u16 *width, u16 *height, + u8 bytespp) +{ + *width = ALIGN(*width * bytespp, VRFB_PAGE_WIDTH) / bytespp; + *height = ALIGN(*height, VRFB_PAGE_HEIGHT); +} +EXPORT_SYMBOL(omap_vrfb_adjust_size); + +u32 omap_vrfb_min_phys_size(u16 width, u16 height, u8 bytespp) +{ + unsigned long image_width_roundup = get_image_width_roundup(width, + bytespp); + + if (image_width_roundup > OMAP_VRFB_LINE_LEN) + return 0; + + return (width * height * bytespp) + get_extra_physical_size( + image_width_roundup, bytespp); +} +EXPORT_SYMBOL(omap_vrfb_min_phys_size); + +u16 omap_vrfb_max_height(u32 phys_size, u16 width, u8 bytespp) +{ + unsigned long image_width_roundup = get_image_width_roundup(width, + bytespp); + unsigned long height; + unsigned long extra; + + if (image_width_roundup > OMAP_VRFB_LINE_LEN) + return 0; + + extra = get_extra_physical_size(image_width_roundup, bytespp); + + if (phys_size < extra) + return 0; + + height = (phys_size - extra) / (width * bytespp); + + /* Virtual views provided by VRFB are limited to 2048x2048. */ + return min_t(unsigned long, height, 2048); +} +EXPORT_SYMBOL(omap_vrfb_max_height); + +void omap_vrfb_setup(struct vrfb *vrfb, unsigned long paddr, + u16 width, u16 height, + unsigned bytespp, bool yuv_mode) +{ + unsigned pixel_size_exp; + u16 vrfb_width; + u16 vrfb_height; + u8 ctx = vrfb->context; + u32 size; + u32 control; + + DBG("omapfb_set_vrfb(%d, %lx, %dx%d, %d, %d)\n", ctx, paddr, + width, height, bytespp, yuv_mode); + + /* For YUV2 and UYVY modes VRFB needs to handle pixels a bit + * differently. See TRM. */ + if (yuv_mode) { + bytespp *= 2; + width /= 2; + } + + if (bytespp == 4) + pixel_size_exp = 2; + else if (bytespp == 2) + pixel_size_exp = 1; + else { + BUG(); + return; + } + + vrfb_width = ALIGN(width * bytespp, VRFB_PAGE_WIDTH) / bytespp; + vrfb_height = ALIGN(height, VRFB_PAGE_HEIGHT); + + DBG("vrfb w %u, h %u bytespp %d\n", vrfb_width, vrfb_height, bytespp); + + size = vrfb_width << SMS_IMAGEWIDTH_OFFSET; + size |= vrfb_height << SMS_IMAGEHEIGHT_OFFSET; + + control = pixel_size_exp << SMS_PS_OFFSET; + control |= VRFB_PAGE_WIDTH_EXP << SMS_PW_OFFSET; + control |= VRFB_PAGE_HEIGHT_EXP << SMS_PH_OFFSET; + + ctxs[ctx].physical_ba = paddr; + ctxs[ctx].size = size; + ctxs[ctx].control = control; + + omap2_sms_write_rot_physical_ba(paddr, ctx); + omap2_sms_write_rot_size(size, ctx); + omap2_sms_write_rot_control(control, ctx); + + DBG("vrfb offset pixels %d, %d\n", + vrfb_width - width, vrfb_height - height); + + vrfb->xres = width; + vrfb->yres = height; + vrfb->xoffset = vrfb_width - width; + vrfb->yoffset = vrfb_height - height; + vrfb->bytespp = bytespp; + vrfb->yuv_mode = yuv_mode; +} +EXPORT_SYMBOL(omap_vrfb_setup); + +int omap_vrfb_map_angle(struct vrfb *vrfb, u16 height, u8 rot) +{ + unsigned long size = height * OMAP_VRFB_LINE_LEN * vrfb->bytespp; + + vrfb->vaddr[rot] = ioremap_wc(vrfb->paddr[rot], size); + + if (!vrfb->vaddr[rot]) { + printk(KERN_ERR "vrfb: ioremap failed\n"); + return -ENOMEM; + } + + DBG("ioremapped vrfb area %d of size %lu into %p\n", rot, size, + vrfb->vaddr[rot]); + + return 0; +} +EXPORT_SYMBOL(omap_vrfb_map_angle); + +void omap_vrfb_release_ctx(struct vrfb *vrfb) +{ + int rot; + int ctx = vrfb->context; + + if (ctx == 0xff) + return; + + DBG("release ctx %d\n", ctx); + + mutex_lock(&ctx_lock); + + BUG_ON(!(ctx_map & (1 << ctx))); + + clear_bit(ctx, &ctx_map); + + for (rot = 0; rot < 4; ++rot) { + if (vrfb->paddr[rot]) { + release_mem_region(vrfb->paddr[rot], OMAP_VRFB_SIZE); + vrfb->paddr[rot] = 0; + } + } + + vrfb->context = 0xff; + + mutex_unlock(&ctx_lock); +} +EXPORT_SYMBOL(omap_vrfb_release_ctx); + +int omap_vrfb_request_ctx(struct vrfb *vrfb) +{ + int rot; + u32 paddr; + u8 ctx; + int r; + + DBG("request ctx\n"); + + mutex_lock(&ctx_lock); + + for (ctx = 0; ctx < num_ctxs; ++ctx) + if ((ctx_map & (1 << ctx)) == 0) + break; + + if (ctx == num_ctxs) { + pr_err("vrfb: no free contexts\n"); + r = -EBUSY; + goto out; + } + + DBG("found free ctx %d\n", ctx); + + set_bit(ctx, &ctx_map); + + memset(vrfb, 0, sizeof(*vrfb)); + + vrfb->context = ctx; + + for (rot = 0; rot < 4; ++rot) { + paddr = ctxs[ctx].base + SMS_ROT_VIRT_BASE(rot); + if (!request_mem_region(paddr, OMAP_VRFB_SIZE, "vrfb")) { + pr_err("vrfb: failed to reserve VRFB " + "area for ctx %d, rotation %d\n", + ctx, rot * 90); + omap_vrfb_release_ctx(vrfb); + r = -ENOMEM; + goto out; + } + + vrfb->paddr[rot] = paddr; + + DBG("VRFB %d/%d: %lx\n", ctx, rot*90, vrfb->paddr[rot]); + } + + r = 0; +out: + mutex_unlock(&ctx_lock); + return r; +} +EXPORT_SYMBOL(omap_vrfb_request_ctx); + +bool omap_vrfb_supported(void) +{ + return vrfb_loaded; +} +EXPORT_SYMBOL(omap_vrfb_supported); + +static int __init vrfb_probe(struct platform_device *pdev) +{ + struct resource *mem; + int i; + + /* first resource is the register res, the rest are vrfb contexts */ + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + vrfb_base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(vrfb_base)) + return PTR_ERR(vrfb_base); + + num_ctxs = pdev->num_resources - 1; + + ctxs = devm_kzalloc(&pdev->dev, + sizeof(struct vrfb_ctx) * num_ctxs, + GFP_KERNEL); + + if (!ctxs) + return -ENOMEM; + + for (i = 0; i < num_ctxs; ++i) { + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1 + i); + if (!mem) { + dev_err(&pdev->dev, "can't get vrfb ctx %d address\n", + i); + return -EINVAL; + } + + ctxs[i].base = mem->start; + } + + vrfb_loaded = true; + + return 0; +} + +static void __exit vrfb_remove(struct platform_device *pdev) +{ + vrfb_loaded = false; +} + +static struct platform_driver vrfb_driver = { + .driver.name = "omapvrfb", + .remove = __exit_p(vrfb_remove), +}; + +module_platform_driver_probe(vrfb_driver, vrfb_probe); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("OMAP VRFB"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/p9100.c b/drivers/video/fbdev/p9100.c new file mode 100644 index 000000000000..367cea8f43f3 --- /dev/null +++ b/drivers/video/fbdev/p9100.c @@ -0,0 +1,382 @@ +/* p9100.c: P9100 frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright 1999 Derrick J Brashear (shadow@dementia.org) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int p9100_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); +static int p9100_blank(int, struct fb_info *); + +static int p9100_mmap(struct fb_info *, struct vm_area_struct *); +static int p9100_ioctl(struct fb_info *, unsigned int, unsigned long); + +/* + * Frame buffer operations + */ + +static struct fb_ops p9100_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = p9100_setcolreg, + .fb_blank = p9100_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = p9100_mmap, + .fb_ioctl = p9100_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + +/* P9100 control registers */ +#define P9100_SYSCTL_OFF 0x0UL +#define P9100_VIDEOCTL_OFF 0x100UL +#define P9100_VRAMCTL_OFF 0x180UL +#define P9100_RAMDAC_OFF 0x200UL +#define P9100_VIDEOCOPROC_OFF 0x400UL + +/* P9100 command registers */ +#define P9100_CMD_OFF 0x0UL + +/* P9100 framebuffer memory */ +#define P9100_FB_OFF 0x0UL + +/* 3 bits: 2=8bpp 3=16bpp 5=32bpp 7=24bpp */ +#define SYS_CONFIG_PIXELSIZE_SHIFT 26 + +#define SCREENPAINT_TIMECTL1_ENABLE_VIDEO 0x20 /* 0 = off, 1 = on */ + +struct p9100_regs { + /* Registers for the system control */ + u32 sys_base; + u32 sys_config; + u32 sys_intr; + u32 sys_int_ena; + u32 sys_alt_rd; + u32 sys_alt_wr; + u32 sys_xxx[58]; + + /* Registers for the video control */ + u32 vid_base; + u32 vid_hcnt; + u32 vid_htotal; + u32 vid_hsync_rise; + u32 vid_hblank_rise; + u32 vid_hblank_fall; + u32 vid_hcnt_preload; + u32 vid_vcnt; + u32 vid_vlen; + u32 vid_vsync_rise; + u32 vid_vblank_rise; + u32 vid_vblank_fall; + u32 vid_vcnt_preload; + u32 vid_screenpaint_addr; + u32 vid_screenpaint_timectl1; + u32 vid_screenpaint_qsfcnt; + u32 vid_screenpaint_timectl2; + u32 vid_xxx[15]; + + /* Registers for the video control */ + u32 vram_base; + u32 vram_memcfg; + u32 vram_refresh_pd; + u32 vram_refresh_cnt; + u32 vram_raslo_max; + u32 vram_raslo_cur; + u32 pwrup_cfg; + u32 vram_xxx[25]; + + /* Registers for IBM RGB528 Palette */ + u32 ramdac_cmap_wridx; + u32 ramdac_palette_data; + u32 ramdac_pixel_mask; + u32 ramdac_palette_rdaddr; + u32 ramdac_idx_lo; + u32 ramdac_idx_hi; + u32 ramdac_idx_data; + u32 ramdac_idx_ctl; + u32 ramdac_xxx[1784]; +}; + +struct p9100_cmd_parameng { + u32 parameng_status; + u32 parameng_bltcmd; + u32 parameng_quadcmd; +}; + +struct p9100_par { + spinlock_t lock; + struct p9100_regs __iomem *regs; + + u32 flags; +#define P9100_FLAG_BLANKED 0x00000001 + + unsigned long which_io; +}; + +/** + * p9100_setcolreg - Optional function. Sets a color register. + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int p9100_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct p9100_par *par = (struct p9100_par *) info->par; + struct p9100_regs __iomem *regs = par->regs; + unsigned long flags; + + if (regno >= 256) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + spin_lock_irqsave(&par->lock, flags); + + sbus_writel((regno << 16), ®s->ramdac_cmap_wridx); + sbus_writel((red << 16), ®s->ramdac_palette_data); + sbus_writel((green << 16), ®s->ramdac_palette_data); + sbus_writel((blue << 16), ®s->ramdac_palette_data); + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +/** + * p9100_blank - Optional function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int +p9100_blank(int blank, struct fb_info *info) +{ + struct p9100_par *par = (struct p9100_par *) info->par; + struct p9100_regs __iomem *regs = par->regs; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&par->lock, flags); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + val = sbus_readl(®s->vid_screenpaint_timectl1); + val |= SCREENPAINT_TIMECTL1_ENABLE_VIDEO; + sbus_writel(val, ®s->vid_screenpaint_timectl1); + par->flags &= ~P9100_FLAG_BLANKED; + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + case FB_BLANK_POWERDOWN: /* Poweroff */ + val = sbus_readl(®s->vid_screenpaint_timectl1); + val &= ~SCREENPAINT_TIMECTL1_ENABLE_VIDEO; + sbus_writel(val, ®s->vid_screenpaint_timectl1); + par->flags |= P9100_FLAG_BLANKED; + break; + } + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static struct sbus_mmap_map p9100_mmap_map[] = { + { CG3_MMAP_OFFSET, 0, SBUS_MMAP_FBSIZE(1) }, + { 0, 0, 0 } +}; + +static int p9100_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct p9100_par *par = (struct p9100_par *)info->par; + + return sbusfb_mmap_helper(p9100_mmap_map, + info->fix.smem_start, info->fix.smem_len, + par->which_io, vma); +} + +static int p9100_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + /* Make it look like a cg3. */ + return sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_SUN3COLOR, 8, info->fix.smem_len); +} + +/* + * Initialisation + */ + +static void p9100_init_fix(struct fb_info *info, int linebytes, struct device_node *dp) +{ + strlcpy(info->fix.id, dp->name, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = linebytes; + + info->fix.accel = FB_ACCEL_SUN_CGTHREE; +} + +static int p9100_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct p9100_par *par; + int linebytes, err; + + info = framebuffer_alloc(sizeof(struct p9100_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + par = info->par; + + spin_lock_init(&par->lock); + + /* This is the framebuffer and the only resource apps can mmap. */ + info->fix.smem_start = op->resource[2].start; + par->which_io = op->resource[2].flags & IORESOURCE_BITS; + + sbusfb_fill_var(&info->var, dp, 8); + info->var.red.length = 8; + info->var.green.length = 8; + info->var.blue.length = 8; + + linebytes = of_getintprop_default(dp, "linebytes", info->var.xres); + info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); + + par->regs = of_ioremap(&op->resource[0], 0, + sizeof(struct p9100_regs), "p9100 regs"); + if (!par->regs) + goto out_release_fb; + + info->flags = FBINFO_DEFAULT; + info->fbops = &p9100_ops; + info->screen_base = of_ioremap(&op->resource[2], 0, + info->fix.smem_len, "p9100 ram"); + if (!info->screen_base) + goto out_unmap_regs; + + p9100_blank(FB_BLANK_UNBLANK, info); + + if (fb_alloc_cmap(&info->cmap, 256, 0)) + goto out_unmap_screen; + + p9100_init_fix(info, linebytes, dp); + + err = register_framebuffer(info); + if (err < 0) + goto out_dealloc_cmap; + + fb_set_cmap(&info->cmap, info); + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: p9100 at %lx:%lx\n", + dp->full_name, + par->which_io, info->fix.smem_start); + + return 0; + +out_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + +out_unmap_screen: + of_iounmap(&op->resource[2], info->screen_base, info->fix.smem_len); + +out_unmap_regs: + of_iounmap(&op->resource[0], par->regs, sizeof(struct p9100_regs)); + +out_release_fb: + framebuffer_release(info); + +out_err: + return err; +} + +static int p9100_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct p9100_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + of_iounmap(&op->resource[0], par->regs, sizeof(struct p9100_regs)); + of_iounmap(&op->resource[2], info->screen_base, info->fix.smem_len); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id p9100_match[] = { + { + .name = "p9100", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, p9100_match); + +static struct platform_driver p9100_driver = { + .driver = { + .name = "p9100", + .owner = THIS_MODULE, + .of_match_table = p9100_match, + }, + .probe = p9100_probe, + .remove = p9100_remove, +}; + +static int __init p9100_init(void) +{ + if (fb_get_options("p9100fb", NULL)) + return -ENODEV; + + return platform_driver_register(&p9100_driver); +} + +static void __exit p9100_exit(void) +{ + platform_driver_unregister(&p9100_driver); +} + +module_init(p9100_init); +module_exit(p9100_exit); + +MODULE_DESCRIPTION("framebuffer driver for P9100 chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/platinumfb.c b/drivers/video/fbdev/platinumfb.c new file mode 100644 index 000000000000..4c9299576827 --- /dev/null +++ b/drivers/video/fbdev/platinumfb.c @@ -0,0 +1,714 @@ +/* + * platinumfb.c -- frame buffer device for the PowerMac 'platinum' display + * + * Copyright (C) 1998 Franz Sirl + * + * Frame buffer structure from: + * drivers/video/controlfb.c -- frame buffer device for + * Apple 'control' display chip. + * Copyright (C) 1998 Dan Jacobowitz + * + * Hardware information from: + * platinum.c: Console support for PowerMac "platinum" display adaptor. + * Copyright (C) 1996 Paul Mackerras and Mark Abene + * + * 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. + */ + +#undef DEBUG + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/nvram.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <asm/io.h> +#include <asm/prom.h> +#include <asm/pgtable.h> + +#include "macmodes.h" +#include "platinumfb.h" + +static int default_vmode = VMODE_NVRAM; +static int default_cmode = CMODE_NVRAM; + +struct fb_info_platinum { + struct fb_info *info; + + int vmode, cmode; + int xres, yres; + int vxres, vyres; + int xoffset, yoffset; + + struct { + __u8 red, green, blue; + } palette[256]; + u32 pseudo_palette[16]; + + volatile struct cmap_regs __iomem *cmap_regs; + unsigned long cmap_regs_phys; + + volatile struct platinum_regs __iomem *platinum_regs; + unsigned long platinum_regs_phys; + + __u8 __iomem *frame_buffer; + volatile __u8 __iomem *base_frame_buffer; + unsigned long frame_buffer_phys; + + unsigned long total_vram; + int clktype; + int dactype; + + struct resource rsrc_fb, rsrc_reg; +}; + +/* + * Frame buffer device API + */ + +static int platinumfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int platinumfb_blank(int blank_mode, struct fb_info *info); +static int platinumfb_set_par (struct fb_info *info); +static int platinumfb_check_var (struct fb_var_screeninfo *var, struct fb_info *info); + +/* + * internal functions + */ + +static inline int platinum_vram_reqd(int video_mode, int color_mode); +static int read_platinum_sense(struct fb_info_platinum *pinfo); +static void set_platinum_clock(struct fb_info_platinum *pinfo); +static void platinum_set_hardware(struct fb_info_platinum *pinfo); +static int platinum_var_to_par(struct fb_var_screeninfo *var, + struct fb_info_platinum *pinfo, + int check_only); + +/* + * Interface used by the world + */ + +static struct fb_ops platinumfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = platinumfb_check_var, + .fb_set_par = platinumfb_set_par, + .fb_setcolreg = platinumfb_setcolreg, + .fb_blank = platinumfb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* + * Checks a var structure + */ +static int platinumfb_check_var (struct fb_var_screeninfo *var, struct fb_info *info) +{ + return platinum_var_to_par(var, info->par, 1); +} + +/* + * Applies current var to display + */ +static int platinumfb_set_par (struct fb_info *info) +{ + struct fb_info_platinum *pinfo = info->par; + struct platinum_regvals *init; + int err, offset = 0x20; + + if((err = platinum_var_to_par(&info->var, pinfo, 0))) { + printk (KERN_ERR "platinumfb_set_par: error calling" + " platinum_var_to_par: %d.\n", err); + return err; + } + + platinum_set_hardware(pinfo); + + init = platinum_reg_init[pinfo->vmode-1]; + + if ((pinfo->vmode == VMODE_832_624_75) && (pinfo->cmode > CMODE_8)) + offset = 0x10; + + info->screen_base = pinfo->frame_buffer + init->fb_offset + offset; + mutex_lock(&info->mm_lock); + info->fix.smem_start = (pinfo->frame_buffer_phys) + init->fb_offset + offset; + mutex_unlock(&info->mm_lock); + info->fix.visual = (pinfo->cmode == CMODE_8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + info->fix.line_length = vmode_attrs[pinfo->vmode-1].hres * (1<<pinfo->cmode) + + offset; + printk("line_length: %x\n", info->fix.line_length); + return 0; +} + +static int platinumfb_blank(int blank, struct fb_info *fb) +{ +/* + * Blank the screen if blank_mode != 0, else unblank. If blank == NULL + * then the caller blanks by setting the CLUT (Color Look Up Table) to all + * black. Return 0 if blanking succeeded, != 0 if un-/blanking failed due + * to e.g. a video mode which doesn't support it. Implements VESA suspend + * and powerdown modes on hardware that supports disabling hsync/vsync: + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ +/* [danj] I think there's something fishy about those constants... */ +/* + struct fb_info_platinum *info = (struct fb_info_platinum *) fb; + int ctrl; + + ctrl = ld_le32(&info->platinum_regs->ctrl.r) | 0x33; + if (blank) + --blank_mode; + if (blank & VESA_VSYNC_SUSPEND) + ctrl &= ~3; + if (blank & VESA_HSYNC_SUSPEND) + ctrl &= ~0x30; + out_le32(&info->platinum_regs->ctrl.r, ctrl); +*/ +/* TODO: Figure out how the heck to powerdown this thing! */ + return 0; +} + +static int platinumfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct fb_info_platinum *pinfo = info->par; + volatile struct cmap_regs __iomem *cmap_regs = pinfo->cmap_regs; + + if (regno > 255) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + pinfo->palette[regno].red = red; + pinfo->palette[regno].green = green; + pinfo->palette[regno].blue = blue; + + out_8(&cmap_regs->addr, regno); /* tell clut what addr to fill */ + out_8(&cmap_regs->lut, red); /* send one color channel at */ + out_8(&cmap_regs->lut, green); /* a time... */ + out_8(&cmap_regs->lut, blue); + + if (regno < 16) { + int i; + u32 *pal = info->pseudo_palette; + switch (pinfo->cmode) { + case CMODE_16: + pal[regno] = (regno << 10) | (regno << 5) | regno; + break; + case CMODE_32: + i = (regno << 8) | regno; + pal[regno] = (i << 16) | i; + break; + } + } + + return 0; +} + +static inline int platinum_vram_reqd(int video_mode, int color_mode) +{ + int baseval = vmode_attrs[video_mode-1].hres * (1<<color_mode); + + if ((video_mode == VMODE_832_624_75) && (color_mode > CMODE_8)) + baseval += 0x10; + else + baseval += 0x20; + + return vmode_attrs[video_mode-1].vres * baseval + 0x1000; +} + +#define STORE_D2(a, d) { \ + out_8(&cmap_regs->addr, (a+32)); \ + out_8(&cmap_regs->d2, (d)); \ +} + +static void set_platinum_clock(struct fb_info_platinum *pinfo) +{ + volatile struct cmap_regs __iomem *cmap_regs = pinfo->cmap_regs; + struct platinum_regvals *init; + + init = platinum_reg_init[pinfo->vmode-1]; + + STORE_D2(6, 0xc6); + out_8(&cmap_regs->addr,3+32); + + if (in_8(&cmap_regs->d2) == 2) { + STORE_D2(7, init->clock_params[pinfo->clktype][0]); + STORE_D2(8, init->clock_params[pinfo->clktype][1]); + STORE_D2(3, 3); + } else { + STORE_D2(4, init->clock_params[pinfo->clktype][0]); + STORE_D2(5, init->clock_params[pinfo->clktype][1]); + STORE_D2(3, 2); + } + + __delay(5000); + STORE_D2(9, 0xa6); +} + + +/* Now how about actually saying, Make it so! */ +/* Some things in here probably don't need to be done each time. */ +static void platinum_set_hardware(struct fb_info_platinum *pinfo) +{ + volatile struct platinum_regs __iomem *platinum_regs = pinfo->platinum_regs; + volatile struct cmap_regs __iomem *cmap_regs = pinfo->cmap_regs; + struct platinum_regvals *init; + int i; + int vmode, cmode; + + vmode = pinfo->vmode; + cmode = pinfo->cmode; + + init = platinum_reg_init[vmode - 1]; + + /* Initialize display timing registers */ + out_be32(&platinum_regs->reg[24].r, 7); /* turn display off */ + + for (i = 0; i < 26; ++i) + out_be32(&platinum_regs->reg[i+32].r, init->regs[i]); + + out_be32(&platinum_regs->reg[26+32].r, (pinfo->total_vram == 0x100000 ? + init->offset[cmode] + 4 - cmode : + init->offset[cmode])); + out_be32(&platinum_regs->reg[16].r, (unsigned) pinfo->frame_buffer_phys+init->fb_offset+0x10); + out_be32(&platinum_regs->reg[18].r, init->pitch[cmode]); + out_be32(&platinum_regs->reg[19].r, (pinfo->total_vram == 0x100000 ? + init->mode[cmode+1] : + init->mode[cmode])); + out_be32(&platinum_regs->reg[20].r, (pinfo->total_vram == 0x100000 ? 0x11 : 0x1011)); + out_be32(&platinum_regs->reg[21].r, 0x100); + out_be32(&platinum_regs->reg[22].r, 1); + out_be32(&platinum_regs->reg[23].r, 1); + out_be32(&platinum_regs->reg[26].r, 0xc00); + out_be32(&platinum_regs->reg[27].r, 0x235); + /* out_be32(&platinum_regs->reg[27].r, 0x2aa); */ + + STORE_D2(0, (pinfo->total_vram == 0x100000 ? + init->dacula_ctrl[cmode] & 0xf : + init->dacula_ctrl[cmode])); + STORE_D2(1, 4); + STORE_D2(2, 0); + + set_platinum_clock(pinfo); + + out_be32(&platinum_regs->reg[24].r, 0); /* turn display on */ +} + +/* + * Set misc info vars for this driver + */ +static void platinum_init_info(struct fb_info *info, + struct fb_info_platinum *pinfo) +{ + /* Fill fb_info */ + info->fbops = &platinumfb_ops; + info->pseudo_palette = pinfo->pseudo_palette; + info->flags = FBINFO_DEFAULT; + info->screen_base = pinfo->frame_buffer + 0x20; + + fb_alloc_cmap(&info->cmap, 256, 0); + + /* Fill fix common fields */ + strcpy(info->fix.id, "platinum"); + info->fix.mmio_start = pinfo->platinum_regs_phys; + info->fix.mmio_len = 0x1000; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.smem_start = pinfo->frame_buffer_phys + 0x20; /* will be updated later */ + info->fix.smem_len = pinfo->total_vram - 0x20; + info->fix.ywrapstep = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.type_aux = 0; + info->fix.accel = FB_ACCEL_NONE; +} + + +static int platinum_init_fb(struct fb_info *info) +{ + struct fb_info_platinum *pinfo = info->par; + struct fb_var_screeninfo var; + int sense, rc; + + sense = read_platinum_sense(pinfo); + printk(KERN_INFO "platinumfb: Monitor sense value = 0x%x, ", sense); + if (default_vmode == VMODE_NVRAM) { +#ifdef CONFIG_NVRAM + default_vmode = nvram_read_byte(NV_VMODE); + if (default_vmode <= 0 || default_vmode > VMODE_MAX || + !platinum_reg_init[default_vmode-1]) +#endif + default_vmode = VMODE_CHOOSE; + } + if (default_vmode == VMODE_CHOOSE) { + default_vmode = mac_map_monitor_sense(sense); + } + if (default_vmode <= 0 || default_vmode > VMODE_MAX) + default_vmode = VMODE_640_480_60; +#ifdef CONFIG_NVRAM + if (default_cmode == CMODE_NVRAM) + default_cmode = nvram_read_byte(NV_CMODE); +#endif + if (default_cmode < CMODE_8 || default_cmode > CMODE_32) + default_cmode = CMODE_8; + /* + * Reduce the pixel size if we don't have enough VRAM. + */ + while(default_cmode > CMODE_8 && + platinum_vram_reqd(default_vmode, default_cmode) > pinfo->total_vram) + default_cmode--; + + printk("platinumfb: Using video mode %d and color mode %d.\n", default_vmode, default_cmode); + + /* Setup default var */ + if (mac_vmode_to_var(default_vmode, default_cmode, &var) < 0) { + /* This shouldn't happen! */ + printk("mac_vmode_to_var(%d, %d,) failed\n", default_vmode, default_cmode); +try_again: + default_vmode = VMODE_640_480_60; + default_cmode = CMODE_8; + if (mac_vmode_to_var(default_vmode, default_cmode, &var) < 0) { + printk(KERN_ERR "platinumfb: mac_vmode_to_var() failed\n"); + return -ENXIO; + } + } + + /* Initialize info structure */ + platinum_init_info(info, pinfo); + + /* Apply default var */ + info->var = var; + var.activate = FB_ACTIVATE_NOW; + rc = fb_set_var(info, &var); + if (rc && (default_vmode != VMODE_640_480_60 || default_cmode != CMODE_8)) + goto try_again; + + /* Register with fbdev layer */ + rc = register_framebuffer(info); + if (rc < 0) + return rc; + + fb_info(info, "Apple Platinum frame buffer device\n"); + + return 0; +} + +/* + * Get the monitor sense value. + * Note that this can be called before calibrate_delay, + * so we can't use udelay. + */ +static int read_platinum_sense(struct fb_info_platinum *info) +{ + volatile struct platinum_regs __iomem *platinum_regs = info->platinum_regs; + int sense; + + out_be32(&platinum_regs->reg[23].r, 7); /* turn off drivers */ + __delay(2000); + sense = (~in_be32(&platinum_regs->reg[23].r) & 7) << 8; + + /* drive each sense line low in turn and collect the other 2 */ + out_be32(&platinum_regs->reg[23].r, 3); /* drive A low */ + __delay(2000); + sense |= (~in_be32(&platinum_regs->reg[23].r) & 3) << 4; + out_be32(&platinum_regs->reg[23].r, 5); /* drive B low */ + __delay(2000); + sense |= (~in_be32(&platinum_regs->reg[23].r) & 4) << 1; + sense |= (~in_be32(&platinum_regs->reg[23].r) & 1) << 2; + out_be32(&platinum_regs->reg[23].r, 6); /* drive C low */ + __delay(2000); + sense |= (~in_be32(&platinum_regs->reg[23].r) & 6) >> 1; + + out_be32(&platinum_regs->reg[23].r, 7); /* turn off drivers */ + + return sense; +} + +/* + * This routine takes a user-supplied var, and picks the best vmode/cmode from it. + * It also updates the var structure to the actual mode data obtained + */ +static int platinum_var_to_par(struct fb_var_screeninfo *var, + struct fb_info_platinum *pinfo, + int check_only) +{ + int vmode, cmode; + + if (mac_var_to_vmode(var, &vmode, &cmode) != 0) { + printk(KERN_ERR "platinum_var_to_par: mac_var_to_vmode unsuccessful.\n"); + printk(KERN_ERR "platinum_var_to_par: var->xres = %d\n", var->xres); + printk(KERN_ERR "platinum_var_to_par: var->yres = %d\n", var->yres); + printk(KERN_ERR "platinum_var_to_par: var->xres_virtual = %d\n", var->xres_virtual); + printk(KERN_ERR "platinum_var_to_par: var->yres_virtual = %d\n", var->yres_virtual); + printk(KERN_ERR "platinum_var_to_par: var->bits_per_pixel = %d\n", var->bits_per_pixel); + printk(KERN_ERR "platinum_var_to_par: var->pixclock = %d\n", var->pixclock); + printk(KERN_ERR "platinum_var_to_par: var->vmode = %d\n", var->vmode); + return -EINVAL; + } + + if (!platinum_reg_init[vmode-1]) { + printk(KERN_ERR "platinum_var_to_par, vmode %d not valid.\n", vmode); + return -EINVAL; + } + + if (platinum_vram_reqd(vmode, cmode) > pinfo->total_vram) { + printk(KERN_ERR "platinum_var_to_par, not enough ram for vmode %d, cmode %d.\n", vmode, cmode); + return -EINVAL; + } + + if (mac_vmode_to_var(vmode, cmode, var)) + return -EINVAL; + + if (check_only) + return 0; + + pinfo->vmode = vmode; + pinfo->cmode = cmode; + pinfo->xres = vmode_attrs[vmode-1].hres; + pinfo->yres = vmode_attrs[vmode-1].vres; + pinfo->xoffset = 0; + pinfo->yoffset = 0; + pinfo->vxres = pinfo->xres; + pinfo->vyres = pinfo->yres; + + return 0; +} + + +/* + * Parse user specified options (`video=platinumfb:') + */ +static int __init platinumfb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "vmode:", 6)) { + int vmode = simple_strtoul(this_opt+6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX) + default_vmode = vmode; + } else if (!strncmp(this_opt, "cmode:", 6)) { + int depth = simple_strtoul(this_opt+6, NULL, 0); + switch (depth) { + case 0: + case 8: + default_cmode = CMODE_8; + break; + case 15: + case 16: + default_cmode = CMODE_16; + break; + case 24: + case 32: + default_cmode = CMODE_32; + break; + } + } + } + return 0; +} + +#ifdef __powerpc__ +#define invalidate_cache(addr) \ + asm volatile("eieio; dcbf 0,%1" \ + : "=m" (*(addr)) : "r" (addr) : "memory"); +#else +#define invalidate_cache(addr) +#endif + +static int platinumfb_probe(struct platform_device* odev) +{ + struct device_node *dp = odev->dev.of_node; + struct fb_info *info; + struct fb_info_platinum *pinfo; + volatile __u8 *fbuffer; + int bank0, bank1, bank2, bank3, rc; + + dev_info(&odev->dev, "Found Apple Platinum video hardware\n"); + + info = framebuffer_alloc(sizeof(*pinfo), &odev->dev); + if (info == NULL) { + dev_err(&odev->dev, "Failed to allocate fbdev !\n"); + return -ENOMEM; + } + pinfo = info->par; + + if (of_address_to_resource(dp, 0, &pinfo->rsrc_reg) || + of_address_to_resource(dp, 1, &pinfo->rsrc_fb)) { + dev_err(&odev->dev, "Can't get resources\n"); + framebuffer_release(info); + return -ENXIO; + } + dev_dbg(&odev->dev, " registers : 0x%llx...0x%llx\n", + (unsigned long long)pinfo->rsrc_reg.start, + (unsigned long long)pinfo->rsrc_reg.end); + dev_dbg(&odev->dev, " framebuffer: 0x%llx...0x%llx\n", + (unsigned long long)pinfo->rsrc_fb.start, + (unsigned long long)pinfo->rsrc_fb.end); + + /* Do not try to request register space, they overlap with the + * northbridge and that can fail. Only request framebuffer + */ + if (!request_mem_region(pinfo->rsrc_fb.start, + resource_size(&pinfo->rsrc_fb), + "platinumfb framebuffer")) { + printk(KERN_ERR "platinumfb: Can't request framebuffer !\n"); + framebuffer_release(info); + return -ENXIO; + } + + /* frame buffer - map only 4MB */ + pinfo->frame_buffer_phys = pinfo->rsrc_fb.start; + pinfo->frame_buffer = __ioremap(pinfo->rsrc_fb.start, 0x400000, + _PAGE_WRITETHRU); + pinfo->base_frame_buffer = pinfo->frame_buffer; + + /* registers */ + pinfo->platinum_regs_phys = pinfo->rsrc_reg.start; + pinfo->platinum_regs = ioremap(pinfo->rsrc_reg.start, 0x1000); + + pinfo->cmap_regs_phys = 0xf301b000; /* XXX not in prom? */ + request_mem_region(pinfo->cmap_regs_phys, 0x1000, "platinumfb cmap"); + pinfo->cmap_regs = ioremap(pinfo->cmap_regs_phys, 0x1000); + + /* Grok total video ram */ + out_be32(&pinfo->platinum_regs->reg[16].r, (unsigned)pinfo->frame_buffer_phys); + out_be32(&pinfo->platinum_regs->reg[20].r, 0x1011); /* select max vram */ + out_be32(&pinfo->platinum_regs->reg[24].r, 0); /* switch in vram */ + + fbuffer = pinfo->base_frame_buffer; + fbuffer[0x100000] = 0x34; + fbuffer[0x100008] = 0x0; + invalidate_cache(&fbuffer[0x100000]); + fbuffer[0x200000] = 0x56; + fbuffer[0x200008] = 0x0; + invalidate_cache(&fbuffer[0x200000]); + fbuffer[0x300000] = 0x78; + fbuffer[0x300008] = 0x0; + invalidate_cache(&fbuffer[0x300000]); + bank0 = 1; /* builtin 1MB vram, always there */ + bank1 = fbuffer[0x100000] == 0x34; + bank2 = fbuffer[0x200000] == 0x56; + bank3 = fbuffer[0x300000] == 0x78; + pinfo->total_vram = (bank0 + bank1 + bank2 + bank3) * 0x100000; + printk(KERN_INFO "platinumfb: Total VRAM = %dMB (%d%d%d%d)\n", + (unsigned int) (pinfo->total_vram / 1024 / 1024), + bank3, bank2, bank1, bank0); + + /* + * Try to determine whether we have an old or a new DACula. + */ + out_8(&pinfo->cmap_regs->addr, 0x40); + pinfo->dactype = in_8(&pinfo->cmap_regs->d2); + switch (pinfo->dactype) { + case 0x3c: + pinfo->clktype = 1; + printk(KERN_INFO "platinumfb: DACula type 0x3c\n"); + break; + case 0x84: + pinfo->clktype = 0; + printk(KERN_INFO "platinumfb: DACula type 0x84\n"); + break; + default: + pinfo->clktype = 0; + printk(KERN_INFO "platinumfb: Unknown DACula type: %x\n", pinfo->dactype); + break; + } + dev_set_drvdata(&odev->dev, info); + + rc = platinum_init_fb(info); + if (rc != 0) { + iounmap(pinfo->frame_buffer); + iounmap(pinfo->platinum_regs); + iounmap(pinfo->cmap_regs); + framebuffer_release(info); + } + + return rc; +} + +static int platinumfb_remove(struct platform_device* odev) +{ + struct fb_info *info = dev_get_drvdata(&odev->dev); + struct fb_info_platinum *pinfo = info->par; + + unregister_framebuffer (info); + + /* Unmap frame buffer and registers */ + iounmap(pinfo->frame_buffer); + iounmap(pinfo->platinum_regs); + iounmap(pinfo->cmap_regs); + + release_mem_region(pinfo->rsrc_fb.start, + resource_size(&pinfo->rsrc_fb)); + + release_mem_region(pinfo->cmap_regs_phys, 0x1000); + + framebuffer_release(info); + + return 0; +} + +static struct of_device_id platinumfb_match[] = +{ + { + .name = "platinum", + }, + {}, +}; + +static struct platform_driver platinum_driver = +{ + .driver = { + .name = "platinumfb", + .owner = THIS_MODULE, + .of_match_table = platinumfb_match, + }, + .probe = platinumfb_probe, + .remove = platinumfb_remove, +}; + +static int __init platinumfb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("platinumfb", &option)) + return -ENODEV; + platinumfb_setup(option); +#endif + platform_driver_register(&platinum_driver); + + return 0; +} + +static void __exit platinumfb_exit(void) +{ + platform_driver_unregister(&platinum_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("framebuffer driver for Apple Platinum video"); +module_init(platinumfb_init); + +#ifdef MODULE +module_exit(platinumfb_exit); +#endif diff --git a/drivers/video/fbdev/platinumfb.h b/drivers/video/fbdev/platinumfb.h new file mode 100644 index 000000000000..f6bd77cafd17 --- /dev/null +++ b/drivers/video/fbdev/platinumfb.h @@ -0,0 +1,368 @@ +/* + * linux/drivers/video/platinumfb-hw.c -- Frame buffer device for the + * Platinum on-board video in PowerMac 7200s (and some clones based + * on the same motherboard.) + * + * Created 09 Feb 1998 by Jon Howell <jonh@cs.dartmouth.edu> + * + * Copyright (C) 1998 Jon Howell + * + * based on drivers/macintosh/platinum.c: Console support + * for PowerMac "platinum" display adaptor. + * Copyright (C) 1996 Paul Mackerras and Mark Abene. + * + * based on skeletonfb.c: + * Created 28 Dec 1997 by Geert Uytterhoeven + * + * 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. + */ + +/* + * Structure of the registers for the DACula colormap device. + */ +struct cmap_regs { + unsigned char addr; + char pad1[15]; + unsigned char d1; + char pad2[15]; + unsigned char d2; + char pad3[15]; + unsigned char lut; + char pad4[15]; +}; + +/* + * Structure of the registers for the "platinum" display adaptor". + */ +struct preg { /* padded register */ + unsigned r; /* notice this is 32 bits. */ + char pad[12]; +}; + +struct platinum_regs { + struct preg reg[128]; +}; + +/* + * Register initialization tables for the platinum display. + * + * It seems that there are two different types of platinum display + * out there. Older ones use the values in clocksel[1], for which + * the formula for the clock frequency seems to be + * F = 14.3MHz * c0 / (c1 & 0x1f) / (1 << (c1 >> 5)) + * Newer ones use the values in clocksel[0], for which the formula + * seems to be + * F = 15MHz * c0 / ((c1 & 0x1f) + 2) / (1 << (c1 >> 5)) + */ +struct platinum_regvals { + int fb_offset; + int pitch[3]; + unsigned regs[26]; + unsigned char offset[3]; + unsigned char mode[3]; + unsigned char dacula_ctrl[3]; + unsigned char clock_params[2][2]; +}; + +#define DIV2 0x20 +#define DIV4 0x40 +#define DIV8 0x60 +#define DIV16 0x80 + +/* 1280x1024, 75Hz (20) */ +static struct platinum_regvals platinum_reg_init_20 = { + 0x5c00, + { 1312, 2592, 2592 }, + { 0xffc, 4, 0, 0, 0, 0, 0x428, 0, + 0, 0xb3, 0xd3, 0x12, 0x1a5, 0x23, 0x28, 0x2d, + 0x5e, 0x19e, 0x1a4, 0x854, 0x852, 4, 9, 0x50, + 0x850, 0x851 }, { 0x58, 0x5d, 0x5d }, + { 0, 0xff, 0xff }, { 0x51, 0x55, 0x55 }, + {{ 45, 3 }, { 66, 7 }} +}; + +/* 1280x960, 75Hz (19) */ +static struct platinum_regvals platinum_reg_init_19 = { + 0x5c00, + { 1312, 2592, 2592 }, + { 0xffc, 4, 0, 0, 0, 0, 0x428, 0, + 0, 0xb2, 0xd2, 0x12, 0x1a3, 0x23, 0x28, 0x2d, + 0x5c, 0x19c, 0x1a2, 0x7d0, 0x7ce, 4, 9, 0x4c, + 0x7cc, 0x7cd }, { 0x56, 0x5b, 0x5b }, + { 0, 0xff, 0xff }, { 0x51, 0x55, 0x55 }, + {{ 42, 3 }, { 44, 5 }} +}; + +/* 1152x870, 75Hz (18) */ +static struct platinum_regvals platinum_reg_init_18 = { + 0x11b0, + { 1184, 2336, 4640 }, + { 0xff0, 4, 0, 0, 0, 0, 0x38f, 0, + 0, 0x294, 0x16c, 0x20, 0x2d7, 0x3f, 0x49, 0x53, + 0x82, 0x2c2, 0x2d6, 0x726, 0x724, 4, 9, 0x52, + 0x71e, 0x722 }, { 0x74, 0x7c, 0x81 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 26, 0 + DIV2 }, { 42, 6 }} +}; + +/* 1024x768, 75Hz (17) */ +static struct platinum_regvals platinum_reg_init_17 = { + 0x10b0, + { 1056, 2080, 4128 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x254, 0x14b, 0x18, 0x295, 0x2f, 0x32, 0x3b, + 0x80, 0x280, 0x296, 0x648, 0x646, 4, 9, 0x40, + 0x640, 0x644 }, { 0x72, 0x7a, 0x7f }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 54, 3 + DIV2 }, { 67, 12 }} +}; + +/* 1024x768, 75Hz (16) */ +static struct platinum_regvals platinum_reg_init_16 = { + 0x10b0, + { 1056, 2080, 4128 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x250, 0x147, 0x17, 0x28f, 0x2f, 0x35, 0x47, + 0x82, 0x282, 0x28e, 0x640, 0x63e, 4, 9, 0x3c, + 0x63c, 0x63d }, { 0x74, 0x7c, 0x81 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 20, 0 + DIV2 }, { 11, 2 }} +}; + +/* 1024x768, 70Hz (15) */ +static struct platinum_regvals platinum_reg_init_15 = { + 0x10b0, + { 1056, 2080, 4128 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x254, 0x14b, 0x22, 0x297, 0x43, 0x49, 0x5b, + 0x86, 0x286, 0x296, 0x64c, 0x64a, 0xa, 0xf, 0x44, + 0x644, 0x646 }, { 0x78, 0x80, 0x85 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 19, 0 + DIV2 }, { 110, 21 }} +}; + +/* 1024x768, 60Hz (14) */ +static struct platinum_regvals platinum_reg_init_14 = { + 0x10b0, + { 1056, 2080, 4128 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x25a, 0x14f, 0x22, 0x29f, 0x43, 0x49, 0x5b, + 0x8e, 0x28e, 0x29e, 0x64c, 0x64a, 0xa, 0xf, 0x44, + 0x644, 0x646 }, { 0x80, 0x88, 0x8d }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 71, 6 + DIV2 }, { 118, 13 + DIV2 }} +}; + +/* 832x624, 75Hz (13) */ +static struct platinum_regvals platinum_reg_init_13 = { + 0x70, + { 864, 1680, 3344 }, /* MacOS does 1680 instead of 1696 to fit 16bpp in 1MB, + * and we use 3344 instead of 3360 to fit in 2Mb + */ + { 0xff0, 4, 0, 0, 0, 0, 0x299, 0, + 0, 0x21e, 0x120, 0x10, 0x23f, 0x1f, 0x25, 0x37, + 0x8a, 0x22a, 0x23e, 0x536, 0x534, 4, 9, 0x52, + 0x532, 0x533 }, { 0x7c, 0x84, 0x89 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 30, 0 + DIV4 }, { 56, 7 + DIV2 }} +}; + +/* 800x600, 75Hz (12) */ +static struct platinum_regvals platinum_reg_init_12 = { + 0x1010, + { 832, 1632, 3232 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x1ce, 0x108, 0x14, 0x20f, 0x27, 0x30, 0x39, + 0x72, 0x202, 0x20e, 0x4e2, 0x4e0, 4, 9, 0x2e, + 0x4de, 0x4df }, { 0x64, 0x6c, 0x71 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 122, 7 + DIV4 }, { 62, 9 + DIV2 }} +}; + +/* 800x600, 72Hz (11) */ +static struct platinum_regvals platinum_reg_init_11 = { + 0x1010, + { 832, 1632, 3232 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x1ca, 0x104, 0x1e, 0x207, 0x3b, 0x44, 0x4d, + 0x56, 0x1e6, 0x206, 0x534, 0x532, 0xa, 0xe, 0x38, + 0x4e8, 0x4ec }, { 0x48, 0x50, 0x55 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 26, 0 + DIV4 }, { 42, 6 + DIV2 }} +}; + +/* 800x600, 60Hz (10) */ +static struct platinum_regvals platinum_reg_init_10 = { + 0x1010, + { 832, 1632, 3232 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x1ce, 0x108, 0x20, 0x20f, 0x3f, 0x45, 0x5d, + 0x66, 0x1f6, 0x20e, 0x4e8, 0x4e6, 6, 0xa, 0x34, + 0x4e4, 0x4e5 }, { 0x58, 0x60, 0x65 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 54, 3 + DIV4 }, { 95, 1 + DIV8 }} +}; + +/* 800x600, 56Hz (9) --unsupported? copy of mode 10 for now... */ +static struct platinum_regvals platinum_reg_init_9 = { + 0x1010, + { 832, 1632, 3232 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x1ce, 0x108, 0x20, 0x20f, 0x3f, 0x45, 0x5d, + 0x66, 0x1f6, 0x20e, 0x4e8, 0x4e6, 6, 0xa, 0x34, + 0x4e4, 0x4e5 }, { 0x58, 0x60, 0x65 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 54, 3 + DIV4 }, { 88, 1 + DIV8 }} +}; + +/* 768x576, 50Hz Interlaced-PAL (8) */ +static struct platinum_regvals platinum_reg_init_8 = { + 0x1010, + { 800, 1568, 3104 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0xc8, 0xec, 0x11, 0x1d7, 0x22, 0x25, 0x36, + 0x47, 0x1c7, 0x1d6, 0x271, 0x270, 4, 9, 0x27, + 0x267, 0x26b }, { 0x39, 0x41, 0x46 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 31, 0 + DIV16 }, { 74, 9 + DIV8 }} +}; + +/* 640x870, 75Hz Portrait (7) */ +static struct platinum_regvals platinum_reg_init_7 = { + 0xb10, + { 672, 1312, 2592 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x176, 0xd0, 0x14, 0x19f, 0x27, 0x2d, 0x3f, + 0x4a, 0x18a, 0x19e, 0x72c, 0x72a, 4, 9, 0x58, + 0x724, 0x72a }, { 0x3c, 0x44, 0x49 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 30, 0 + DIV4 }, { 56, 7 + DIV2 }} +}; + +/* 640x480, 67Hz (6) */ +static struct platinum_regvals platinum_reg_init_6 = { + 0x1010, + { 672, 1312, 2592 }, + { 0xff0, 4, 0, 0, 0, 0, 0x209, 0, + 0, 0x18e, 0xd8, 0x10, 0x1af, 0x1f, 0x25, 0x37, + 0x4a, 0x18a, 0x1ae, 0x41a, 0x418, 4, 9, 0x52, + 0x412, 0x416 }, { 0x3c, 0x44, 0x49 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 99, 4 + DIV8 }, { 42, 5 + DIV4 }} +}; + +/* 640x480, 60Hz (5) */ +static struct platinum_regvals platinum_reg_init_5 = { + 0x1010, + { 672, 1312, 2592 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x15e, 0xc8, 0x18, 0x18f, 0x2f, 0x35, 0x3e, + 0x42, 0x182, 0x18e, 0x41a, 0x418, 2, 7, 0x44, + 0x404, 0x408 }, { 0x34, 0x3c, 0x41 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 26, 0 + DIV8 }, { 14, 2 + DIV4 }} +}; + +/* 640x480, 60Hz Interlaced-NTSC (4) */ +static struct platinum_regvals platinum_reg_init_4 = { + 0x1010, + { 672, 1312, 2592 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0xa5, 0xc3, 0xe, 0x185, 0x1c, 0x1f, 0x30, + 0x37, 0x177, 0x184, 0x20d, 0x20c, 5, 0xb, 0x23, + 0x203, 0x206 }, { 0x29, 0x31, 0x36 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 94, 5 + DIV16 }, { 48, 7 + DIV8 }} +}; + +/* 640x480, 50Hz Interlaced-PAL (3) */ +static struct platinum_regvals platinum_reg_init_3 = { + 0x1010, + { 672, 1312, 2592 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0xc8, 0xec, 0x11, 0x1d7, 0x22, 0x25, 0x36, + 0x67, 0x1a7, 0x1d6, 0x271, 0x270, 4, 9, 0x57, + 0x237, 0x26b }, { 0x59, 0x61, 0x66 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 31, 0 + DIV16 }, { 74, 9 + DIV8 }} +}; + +/* 512x384, 60Hz (2) */ +static struct platinum_regvals platinum_reg_init_2 = { + 0x1010, + { 544, 1056, 2080 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0x25c, 0x140, 0x10, 0x27f, 0x1f, 0x2b, 0x4f, + 0x68, 0x268, 0x27e, 0x32e, 0x32c, 4, 9, 0x2a, + 0x32a, 0x32b }, { 0x5a, 0x62, 0x67 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 33, 2 + DIV8 }, { 79, 9 + DIV8 }} +}; + +/* 512x384, 60Hz Interlaced-NTSC (1) */ +static struct platinum_regvals platinum_reg_init_1 = { + 0x1010, + { 544, 1056, 2080 }, + { 0xff0, 4, 0, 0, 0, 0, 0x320, 0, + 0, 0xa5, 0xc3, 0xe, 0x185, 0x1c, 0x1f, 0x30, + 0x57, 0x157, 0x184, 0x20d, 0x20c, 5, 0xb, 0x53, + 0x1d3, 0x206 }, { 0x49, 0x51, 0x56 }, + { 2, 0, 0xff }, { 0x11, 0x15, 0x19 }, + {{ 94, 5 + DIV16 }, { 48, 7 + DIV8 }} +}; + +static struct platinum_regvals *platinum_reg_init[VMODE_MAX] = { + &platinum_reg_init_1, + &platinum_reg_init_2, + &platinum_reg_init_3, + &platinum_reg_init_4, + &platinum_reg_init_5, + &platinum_reg_init_6, + &platinum_reg_init_7, + &platinum_reg_init_8, + &platinum_reg_init_9, + &platinum_reg_init_10, + &platinum_reg_init_11, + &platinum_reg_init_12, + &platinum_reg_init_13, + &platinum_reg_init_14, + &platinum_reg_init_15, + &platinum_reg_init_16, + &platinum_reg_init_17, + &platinum_reg_init_18, + &platinum_reg_init_19, + &platinum_reg_init_20 +}; + +struct vmode_attr { + int hres; + int vres; + int vfreq; + int interlaced; +}; + +struct vmode_attr vmode_attrs[VMODE_MAX] = { + {512, 384, 60, 1}, + {512, 384, 60}, + {640, 480, 50, 1}, + {640, 480, 60, 1}, + {640, 480, 60}, + {640, 480, 67}, + {640, 870, 75}, + {768, 576, 50, 1}, + {800, 600, 56}, + {800, 600, 60}, + {800, 600, 72}, + {800, 600, 75}, + {832, 624, 75}, + {1024, 768, 60}, + {1024, 768, 72}, + {1024, 768, 75}, + {1024, 768, 75}, + {1152, 870, 75}, + {1280, 960, 75}, + {1280, 1024, 75} +}; + diff --git a/drivers/video/fbdev/pm2fb.c b/drivers/video/fbdev/pm2fb.c new file mode 100644 index 000000000000..3b85b647bc10 --- /dev/null +++ b/drivers/video/fbdev/pm2fb.c @@ -0,0 +1,1858 @@ +/* + * Permedia2 framebuffer driver. + * + * 2.5/2.6 driver: + * Copyright (c) 2003 Jim Hague (jim.hague@acm.org) + * + * based on 2.4 driver: + * Copyright (c) 1998-2000 Ilario Nardinocchi (nardinoc@CS.UniBO.IT) + * Copyright (c) 1999 Jakub Jelinek (jakub@redhat.com) + * + * and additional input from James Simmon's port of Hannu Mallat's tdfx + * driver. + * + * I have a Creative Graphics Blaster Exxtreme card - pm2fb on x86. I + * have no access to other pm2fb implementations. Sparc (and thus + * hopefully other big-endian) devices now work, thanks to a lot of + * testing work by Ron Murray. I have no access to CVision hardware, + * and therefore for now I am omitting the CVision code. + * + * Multiple boards support has been on the TODO list for ages. + * Don't expect this to change. + * + * 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/moduleparam.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/permedia2.h> +#include <video/cvisionppc.h> + +#if !defined(__LITTLE_ENDIAN) && !defined(__BIG_ENDIAN) +#error "The endianness of the target host has not been defined." +#endif + +#if !defined(CONFIG_PCI) +#error "Only generic PCI cards supported." +#endif + +#undef PM2FB_MASTER_DEBUG +#ifdef PM2FB_MASTER_DEBUG +#define DPRINTK(a, b...) \ + printk(KERN_DEBUG "pm2fb: %s: " a, __func__ , ## b) +#else +#define DPRINTK(a, b...) +#endif + +#define PM2_PIXMAP_SIZE (1600 * 4) + +/* + * Driver data + */ +static int hwcursor = 1; +static char *mode_option; + +/* + * The XFree GLINT driver will (I think to implement hardware cursor + * support on TVP4010 and similar where there is no RAMDAC - see + * comment in set_video) always request +ve sync regardless of what + * the mode requires. This screws me because I have a Sun + * fixed-frequency monitor which absolutely has to have -ve sync. So + * these flags allow the user to specify that requests for +ve sync + * should be silently turned in -ve sync. + */ +static bool lowhsync; +static bool lowvsync; +static bool noaccel; +/* mtrr option */ +#ifdef CONFIG_MTRR +static bool nomtrr; +#endif + +/* + * The hardware state of the graphics card that isn't part of the + * screeninfo. + */ +struct pm2fb_par +{ + pm2type_t type; /* Board type */ + unsigned char __iomem *v_regs;/* virtual address of p_regs */ + u32 memclock; /* memclock */ + u32 video; /* video flags before blanking */ + u32 mem_config; /* MemConfig reg at probe */ + u32 mem_control; /* MemControl reg at probe */ + u32 boot_address; /* BootAddress reg at probe */ + 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. + */ +static struct fb_fix_screeninfo pm2fb_fix = { + .id = "", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 0, + .accel = FB_ACCEL_3DLABS_PERMEDIA2, +}; + +/* + * Default video mode. In case the modedb doesn't work. + */ +static struct fb_var_screeninfo pm2fb_var = { + /* "640x480, 8 bpp @ 60 Hz */ + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 8, + .red = {0, 8, 0}, + .blue = {0, 8, 0}, + .green = {0, 8, 0}, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .accel_flags = 0, + .pixclock = 39721, + .left_margin = 40, + .right_margin = 24, + .upper_margin = 32, + .lower_margin = 11, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED +}; + +/* + * Utility functions + */ + +static inline u32 pm2_RD(struct pm2fb_par *p, s32 off) +{ + return fb_readl(p->v_regs + off); +} + +static inline void pm2_WR(struct pm2fb_par *p, s32 off, u32 v) +{ + fb_writel(v, p->v_regs + off); +} + +static inline u32 pm2_RDAC_RD(struct pm2fb_par *p, s32 idx) +{ + pm2_WR(p, PM2R_RD_PALETTE_WRITE_ADDRESS, idx); + mb(); + return pm2_RD(p, PM2R_RD_INDEXED_DATA); +} + +static inline u32 pm2v_RDAC_RD(struct pm2fb_par *p, s32 idx) +{ + pm2_WR(p, PM2VR_RD_INDEX_LOW, idx & 0xff); + mb(); + return pm2_RD(p, PM2VR_RD_INDEXED_DATA); +} + +static inline void pm2_RDAC_WR(struct pm2fb_par *p, s32 idx, u32 v) +{ + pm2_WR(p, PM2R_RD_PALETTE_WRITE_ADDRESS, idx); + wmb(); + pm2_WR(p, PM2R_RD_INDEXED_DATA, v); + wmb(); +} + +static inline void pm2v_RDAC_WR(struct pm2fb_par *p, s32 idx, u32 v) +{ + pm2_WR(p, PM2VR_RD_INDEX_LOW, idx & 0xff); + wmb(); + pm2_WR(p, PM2VR_RD_INDEXED_DATA, v); + wmb(); +} + +#ifdef CONFIG_FB_PM2_FIFO_DISCONNECT +#define WAIT_FIFO(p, a) +#else +static inline void WAIT_FIFO(struct pm2fb_par *p, u32 a) +{ + while (pm2_RD(p, PM2R_IN_FIFO_SPACE) < a) + cpu_relax(); +} +#endif + +/* + * partial products for the supported horizontal resolutions. + */ +#define PACKPP(p0, p1, p2) (((p2) << 6) | ((p1) << 3) | (p0)) +static const struct { + u16 width; + u16 pp; +} pp_table[] = { + { 32, PACKPP(1, 0, 0) }, { 64, PACKPP(1, 1, 0) }, + { 96, PACKPP(1, 1, 1) }, { 128, PACKPP(2, 1, 1) }, + { 160, PACKPP(2, 2, 1) }, { 192, PACKPP(2, 2, 2) }, + { 224, PACKPP(3, 2, 1) }, { 256, PACKPP(3, 2, 2) }, + { 288, PACKPP(3, 3, 1) }, { 320, PACKPP(3, 3, 2) }, + { 384, PACKPP(3, 3, 3) }, { 416, PACKPP(4, 3, 1) }, + { 448, PACKPP(4, 3, 2) }, { 512, PACKPP(4, 3, 3) }, + { 544, PACKPP(4, 4, 1) }, { 576, PACKPP(4, 4, 2) }, + { 640, PACKPP(4, 4, 3) }, { 768, PACKPP(4, 4, 4) }, + { 800, PACKPP(5, 4, 1) }, { 832, PACKPP(5, 4, 2) }, + { 896, PACKPP(5, 4, 3) }, { 1024, PACKPP(5, 4, 4) }, + { 1056, PACKPP(5, 5, 1) }, { 1088, PACKPP(5, 5, 2) }, + { 1152, PACKPP(5, 5, 3) }, { 1280, PACKPP(5, 5, 4) }, + { 1536, PACKPP(5, 5, 5) }, { 1568, PACKPP(6, 5, 1) }, + { 1600, PACKPP(6, 5, 2) }, { 1664, PACKPP(6, 5, 3) }, + { 1792, PACKPP(6, 5, 4) }, { 2048, PACKPP(6, 5, 5) }, + { 0, 0 } }; + +static u32 partprod(u32 xres) +{ + int i; + + for (i = 0; pp_table[i].width && pp_table[i].width != xres; i++) + ; + if (pp_table[i].width == 0) + DPRINTK("invalid width %u\n", xres); + return pp_table[i].pp; +} + +static u32 to3264(u32 timing, int bpp, int is64) +{ + switch (bpp) { + case 24: + timing *= 3; + case 8: + timing >>= 1; + case 16: + timing >>= 1; + case 32: + break; + } + if (is64) + timing >>= 1; + return timing; +} + +static void pm2_mnp(u32 clk, unsigned char *mm, unsigned char *nn, + unsigned char *pp) +{ + unsigned char m; + unsigned char n; + unsigned char p; + u32 f; + s32 curr; + s32 delta = 100000; + + *mm = *nn = *pp = 0; + for (n = 2; n < 15; n++) { + for (m = 2; m; m++) { + f = PM2_REFERENCE_CLOCK * m / n; + if (f >= 150000 && f <= 300000) { + for (p = 0; p < 5; p++, f >>= 1) { + curr = (clk > f) ? clk - f : f - clk; + if (curr < delta) { + delta = curr; + *mm = m; + *nn = n; + *pp = p; + } + } + } + } + } +} + +static void pm2v_mnp(u32 clk, unsigned char *mm, unsigned char *nn, + unsigned char *pp) +{ + unsigned char m; + unsigned char n; + unsigned char p; + u32 f; + s32 delta = 1000; + + *mm = *nn = *pp = 0; + for (m = 1; m < 128; m++) { + for (n = 2 * m + 1; n; n++) { + for (p = 0; p < 2; p++) { + f = (PM2_REFERENCE_CLOCK >> (p + 1)) * n / m; + if (clk > f - delta && clk < f + delta) { + delta = (clk > f) ? clk - f : f - clk; + *mm = m; + *nn = n; + *pp = p; + } + } + } + } +} + +static void clear_palette(struct pm2fb_par *p) +{ + int i = 256; + + WAIT_FIFO(p, 1); + pm2_WR(p, PM2R_RD_PALETTE_WRITE_ADDRESS, 0); + wmb(); + while (i--) { + WAIT_FIFO(p, 3); + pm2_WR(p, PM2R_RD_PALETTE_DATA, 0); + pm2_WR(p, PM2R_RD_PALETTE_DATA, 0); + pm2_WR(p, PM2R_RD_PALETTE_DATA, 0); + } +} + +static void reset_card(struct pm2fb_par *p) +{ + if (p->type == PM2_TYPE_PERMEDIA2V) + pm2_WR(p, PM2VR_RD_INDEX_HIGH, 0); + pm2_WR(p, PM2R_RESET_STATUS, 0); + mb(); + while (pm2_RD(p, PM2R_RESET_STATUS) & PM2F_BEING_RESET) + cpu_relax(); + mb(); +#ifdef CONFIG_FB_PM2_FIFO_DISCONNECT + DPRINTK("FIFO disconnect enabled\n"); + pm2_WR(p, PM2R_FIFO_DISCON, 1); + mb(); +#endif + + /* Restore stashed memory config information from probe */ + WAIT_FIFO(p, 3); + pm2_WR(p, PM2R_MEM_CONTROL, p->mem_control); + pm2_WR(p, PM2R_BOOT_ADDRESS, p->boot_address); + wmb(); + pm2_WR(p, PM2R_MEM_CONFIG, p->mem_config); +} + +static void reset_config(struct pm2fb_par *p) +{ + WAIT_FIFO(p, 53); + pm2_WR(p, PM2R_CHIP_CONFIG, pm2_RD(p, PM2R_CHIP_CONFIG) & + ~(PM2F_VGA_ENABLE | PM2F_VGA_FIXED)); + pm2_WR(p, PM2R_BYPASS_WRITE_MASK, ~(0L)); + pm2_WR(p, PM2R_FRAMEBUFFER_WRITE_MASK, ~(0L)); + pm2_WR(p, PM2R_FIFO_CONTROL, 0); + pm2_WR(p, PM2R_APERTURE_ONE, 0); + pm2_WR(p, PM2R_APERTURE_TWO, 0); + pm2_WR(p, PM2R_RASTERIZER_MODE, 0); + pm2_WR(p, PM2R_DELTA_MODE, PM2F_DELTA_ORDER_RGB); + pm2_WR(p, PM2R_LB_READ_FORMAT, 0); + pm2_WR(p, PM2R_LB_WRITE_FORMAT, 0); + pm2_WR(p, PM2R_LB_READ_MODE, 0); + pm2_WR(p, PM2R_LB_SOURCE_OFFSET, 0); + pm2_WR(p, PM2R_FB_SOURCE_OFFSET, 0); + pm2_WR(p, PM2R_FB_PIXEL_OFFSET, 0); + pm2_WR(p, PM2R_FB_WINDOW_BASE, 0); + pm2_WR(p, PM2R_LB_WINDOW_BASE, 0); + pm2_WR(p, PM2R_FB_SOFT_WRITE_MASK, ~(0L)); + pm2_WR(p, PM2R_FB_HARD_WRITE_MASK, ~(0L)); + pm2_WR(p, PM2R_FB_READ_PIXEL, 0); + pm2_WR(p, PM2R_DITHER_MODE, 0); + pm2_WR(p, PM2R_AREA_STIPPLE_MODE, 0); + pm2_WR(p, PM2R_DEPTH_MODE, 0); + pm2_WR(p, PM2R_STENCIL_MODE, 0); + pm2_WR(p, PM2R_TEXTURE_ADDRESS_MODE, 0); + pm2_WR(p, PM2R_TEXTURE_READ_MODE, 0); + pm2_WR(p, PM2R_TEXEL_LUT_MODE, 0); + pm2_WR(p, PM2R_YUV_MODE, 0); + pm2_WR(p, PM2R_COLOR_DDA_MODE, 0); + pm2_WR(p, PM2R_TEXTURE_COLOR_MODE, 0); + pm2_WR(p, PM2R_FOG_MODE, 0); + pm2_WR(p, PM2R_ALPHA_BLEND_MODE, 0); + pm2_WR(p, PM2R_LOGICAL_OP_MODE, 0); + pm2_WR(p, PM2R_STATISTICS_MODE, 0); + pm2_WR(p, PM2R_SCISSOR_MODE, 0); + pm2_WR(p, PM2R_FILTER_MODE, PM2F_SYNCHRONIZATION); + pm2_WR(p, PM2R_RD_PIXEL_MASK, 0xff); + switch (p->type) { + case PM2_TYPE_PERMEDIA2: + pm2_RDAC_WR(p, PM2I_RD_MODE_CONTROL, 0); /* no overlay */ + pm2_RDAC_WR(p, PM2I_RD_CURSOR_CONTROL, 0); + pm2_RDAC_WR(p, PM2I_RD_MISC_CONTROL, PM2F_RD_PALETTE_WIDTH_8); + pm2_RDAC_WR(p, PM2I_RD_COLOR_KEY_CONTROL, 0); + pm2_RDAC_WR(p, PM2I_RD_OVERLAY_KEY, 0); + pm2_RDAC_WR(p, PM2I_RD_RED_KEY, 0); + pm2_RDAC_WR(p, PM2I_RD_GREEN_KEY, 0); + pm2_RDAC_WR(p, PM2I_RD_BLUE_KEY, 0); + break; + case PM2_TYPE_PERMEDIA2V: + pm2v_RDAC_WR(p, PM2VI_RD_MISC_CONTROL, 1); /* 8bit */ + break; + } +} + +static void set_aperture(struct pm2fb_par *p, u32 depth) +{ + /* + * The hardware is little-endian. When used in big-endian + * hosts, the on-chip aperture settings are used where + * possible to translate from host to card byte order. + */ + WAIT_FIFO(p, 2); +#ifdef __LITTLE_ENDIAN + pm2_WR(p, PM2R_APERTURE_ONE, PM2F_APERTURE_STANDARD); +#else + switch (depth) { + case 24: /* RGB->BGR */ + /* + * We can't use the aperture to translate host to + * card byte order here, so we switch to BGR mode + * in pm2fb_set_par(). + */ + case 8: /* B->B */ + pm2_WR(p, PM2R_APERTURE_ONE, PM2F_APERTURE_STANDARD); + break; + case 16: /* HL->LH */ + pm2_WR(p, PM2R_APERTURE_ONE, PM2F_APERTURE_HALFWORDSWAP); + break; + case 32: /* RGBA->ABGR */ + pm2_WR(p, PM2R_APERTURE_ONE, PM2F_APERTURE_BYTESWAP); + break; + } +#endif + + /* We don't use aperture two, so this may be superflous */ + pm2_WR(p, PM2R_APERTURE_TWO, PM2F_APERTURE_STANDARD); +} + +static void set_color(struct pm2fb_par *p, unsigned char regno, + unsigned char r, unsigned char g, unsigned char b) +{ + WAIT_FIFO(p, 4); + pm2_WR(p, PM2R_RD_PALETTE_WRITE_ADDRESS, regno); + wmb(); + pm2_WR(p, PM2R_RD_PALETTE_DATA, r); + wmb(); + pm2_WR(p, PM2R_RD_PALETTE_DATA, g); + wmb(); + pm2_WR(p, PM2R_RD_PALETTE_DATA, b); +} + +static void set_memclock(struct pm2fb_par *par, u32 clk) +{ + int i; + unsigned char m, n, p; + + switch (par->type) { + case PM2_TYPE_PERMEDIA2V: + pm2v_mnp(clk/2, &m, &n, &p); + WAIT_FIFO(par, 12); + pm2_WR(par, PM2VR_RD_INDEX_HIGH, PM2VI_RD_MCLK_CONTROL >> 8); + pm2v_RDAC_WR(par, PM2VI_RD_MCLK_CONTROL, 0); + pm2v_RDAC_WR(par, PM2VI_RD_MCLK_PRESCALE, m); + pm2v_RDAC_WR(par, PM2VI_RD_MCLK_FEEDBACK, n); + pm2v_RDAC_WR(par, PM2VI_RD_MCLK_POSTSCALE, p); + pm2v_RDAC_WR(par, PM2VI_RD_MCLK_CONTROL, 1); + rmb(); + for (i = 256; i; i--) + if (pm2v_RDAC_RD(par, PM2VI_RD_MCLK_CONTROL) & 2) + break; + pm2_WR(par, PM2VR_RD_INDEX_HIGH, 0); + break; + case PM2_TYPE_PERMEDIA2: + pm2_mnp(clk, &m, &n, &p); + WAIT_FIFO(par, 10); + pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_3, 6); + pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_1, m); + pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_2, n); + pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_3, 8|p); + pm2_RDAC_RD(par, PM2I_RD_MEMORY_CLOCK_STATUS); + rmb(); + for (i = 256; i; i--) + if (pm2_RD(par, PM2R_RD_INDEXED_DATA) & PM2F_PLL_LOCKED) + break; + break; + } +} + +static void set_pixclock(struct pm2fb_par *par, u32 clk) +{ + int i; + unsigned char m, n, p; + + switch (par->type) { + case PM2_TYPE_PERMEDIA2: + pm2_mnp(clk, &m, &n, &p); + WAIT_FIFO(par, 10); + pm2_RDAC_WR(par, PM2I_RD_PIXEL_CLOCK_A3, 0); + pm2_RDAC_WR(par, PM2I_RD_PIXEL_CLOCK_A1, m); + pm2_RDAC_WR(par, PM2I_RD_PIXEL_CLOCK_A2, n); + pm2_RDAC_WR(par, PM2I_RD_PIXEL_CLOCK_A3, 8|p); + pm2_RDAC_RD(par, PM2I_RD_PIXEL_CLOCK_STATUS); + rmb(); + for (i = 256; i; i--) + if (pm2_RD(par, PM2R_RD_INDEXED_DATA) & PM2F_PLL_LOCKED) + break; + break; + case PM2_TYPE_PERMEDIA2V: + pm2v_mnp(clk/2, &m, &n, &p); + WAIT_FIFO(par, 8); + pm2_WR(par, PM2VR_RD_INDEX_HIGH, PM2VI_RD_CLK0_PRESCALE >> 8); + pm2v_RDAC_WR(par, PM2VI_RD_CLK0_PRESCALE, m); + pm2v_RDAC_WR(par, PM2VI_RD_CLK0_FEEDBACK, n); + pm2v_RDAC_WR(par, PM2VI_RD_CLK0_POSTSCALE, p); + pm2_WR(par, PM2VR_RD_INDEX_HIGH, 0); + break; + } +} + +static void set_video(struct pm2fb_par *p, u32 video) +{ + u32 tmp; + u32 vsync = video; + + DPRINTK("video = 0x%x\n", video); + + /* + * The hardware cursor needs +vsync to recognise vert retrace. + * We may not be using the hardware cursor, but the X Glint + * driver may well. So always set +hsync/+vsync and then set + * the RAMDAC to invert the sync if necessary. + */ + vsync &= ~(PM2F_HSYNC_MASK | PM2F_VSYNC_MASK); + vsync |= PM2F_HSYNC_ACT_HIGH | PM2F_VSYNC_ACT_HIGH; + + WAIT_FIFO(p, 3); + pm2_WR(p, PM2R_VIDEO_CONTROL, vsync); + + switch (p->type) { + case PM2_TYPE_PERMEDIA2: + tmp = PM2F_RD_PALETTE_WIDTH_8; + if ((video & PM2F_HSYNC_MASK) == PM2F_HSYNC_ACT_LOW) + tmp |= 4; /* invert hsync */ + if ((video & PM2F_VSYNC_MASK) == PM2F_VSYNC_ACT_LOW) + tmp |= 8; /* invert vsync */ + pm2_RDAC_WR(p, PM2I_RD_MISC_CONTROL, tmp); + break; + case PM2_TYPE_PERMEDIA2V: + tmp = 0; + if ((video & PM2F_HSYNC_MASK) == PM2F_HSYNC_ACT_LOW) + tmp |= 1; /* invert hsync */ + if ((video & PM2F_VSYNC_MASK) == PM2F_VSYNC_ACT_LOW) + tmp |= 4; /* invert vsync */ + pm2v_RDAC_WR(p, PM2VI_RD_SYNC_CONTROL, tmp); + break; + } +} + +/* + * pm2fb_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 + * + * Checks to see if the hardware supports the state requested by + * var passed in. + * + * Returns negative errno on error, or zero on success. + */ +static int pm2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 lpitch; + + if (var->bits_per_pixel != 8 && var->bits_per_pixel != 16 && + var->bits_per_pixel != 24 && var->bits_per_pixel != 32) { + DPRINTK("depth not supported: %u\n", var->bits_per_pixel); + return -EINVAL; + } + + if (var->xres != var->xres_virtual) { + DPRINTK("virtual x resolution != " + "physical x resolution not supported\n"); + return -EINVAL; + } + + if (var->yres > var->yres_virtual) { + DPRINTK("virtual y resolution < " + "physical y resolution not possible\n"); + return -EINVAL; + } + + /* permedia cannot blit over 2048 */ + if (var->yres_virtual > 2047) { + var->yres_virtual = 2047; + } + + if (var->xoffset) { + DPRINTK("xoffset not supported\n"); + return -EINVAL; + } + + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + DPRINTK("interlace not supported\n"); + return -EINVAL; + } + + var->xres = (var->xres + 15) & ~15; /* could sometimes be 8 */ + lpitch = var->xres * ((var->bits_per_pixel + 7) >> 3); + + if (var->xres < 320 || var->xres > 1600) { + DPRINTK("width not supported: %u\n", var->xres); + return -EINVAL; + } + + if (var->yres < 200 || var->yres > 1200) { + DPRINTK("height not supported: %u\n", var->yres); + return -EINVAL; + } + + if (lpitch * var->yres_virtual > info->fix.smem_len) { + DPRINTK("no memory for screen (%ux%ux%u)\n", + var->xres, var->yres_virtual, var->bits_per_pixel); + return -EINVAL; + } + + if (PICOS2KHZ(var->pixclock) > PM2_MAX_PIXCLOCK) { + DPRINTK("pixclock too high (%ldKHz)\n", + PICOS2KHZ(var->pixclock)); + return -EINVAL; + } + + var->transp.offset = 0; + var->transp.length = 0; + switch (var->bits_per_pixel) { + case 8: + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case 16: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + case 32: + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case 24: +#ifdef __BIG_ENDIAN + var->red.offset = 0; + var->blue.offset = 16; +#else + var->red.offset = 16; + var->blue.offset = 0; +#endif + var->green.offset = 8; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + } + var->height = -1; + var->width = -1; + + var->accel_flags = 0; /* Can't mmap if this is on */ + + DPRINTK("Checking graphics mode at %dx%d depth %d\n", + var->xres, var->yres, var->bits_per_pixel); + return 0; +} + +/** + * pm2fb_set_par - Alters the hardware state. + * @info: frame buffer structure that represents a single frame buffer + * + * Using the fb_var_screeninfo in fb_info we set the resolution of the + * this particular framebuffer. + */ +static int pm2fb_set_par(struct fb_info *info) +{ + struct pm2fb_par *par = info->par; + u32 pixclock; + u32 width = (info->var.xres_virtual + 7) & ~7; + u32 height = info->var.yres_virtual; + u32 depth = (info->var.bits_per_pixel + 7) & ~7; + u32 hsstart, hsend, hbend, htotal; + u32 vsstart, vsend, vbend, vtotal; + u32 stride; + u32 base; + u32 video = 0; + u32 clrmode = PM2F_RD_COLOR_MODE_RGB | PM2F_RD_GUI_ACTIVE; + u32 txtmap = 0; + u32 pixsize = 0; + u32 clrformat = 0; + u32 misc = 1; /* 8-bit DAC */ + u32 xres = (info->var.xres + 31) & ~31; + int data64; + + reset_card(par); + reset_config(par); + clear_palette(par); + if (par->memclock) + set_memclock(par, par->memclock); + + depth = (depth > 32) ? 32 : depth; + data64 = depth > 8 || par->type == PM2_TYPE_PERMEDIA2V; + + pixclock = PICOS2KHZ(info->var.pixclock); + if (pixclock > PM2_MAX_PIXCLOCK) { + DPRINTK("pixclock too high (%uKHz)\n", pixclock); + return -EINVAL; + } + + hsstart = to3264(info->var.right_margin, depth, data64); + hsend = hsstart + to3264(info->var.hsync_len, depth, data64); + hbend = hsend + to3264(info->var.left_margin, depth, data64); + htotal = to3264(xres, depth, data64) + hbend - 1; + vsstart = (info->var.lower_margin) + ? info->var.lower_margin - 1 + : 0; /* FIXME! */ + vsend = info->var.lower_margin + info->var.vsync_len - 1; + vbend = info->var.lower_margin + info->var.vsync_len + + info->var.upper_margin; + vtotal = info->var.yres + vbend - 1; + stride = to3264(width, depth, 1); + base = to3264(info->var.yoffset * xres + info->var.xoffset, depth, 1); + if (data64) + video |= PM2F_DATA_64_ENABLE; + + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) { + if (lowhsync) { + DPRINTK("ignoring +hsync, using -hsync.\n"); + video |= PM2F_HSYNC_ACT_LOW; + } else + video |= PM2F_HSYNC_ACT_HIGH; + } else + video |= PM2F_HSYNC_ACT_LOW; + + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) { + if (lowvsync) { + DPRINTK("ignoring +vsync, using -vsync.\n"); + video |= PM2F_VSYNC_ACT_LOW; + } else + video |= PM2F_VSYNC_ACT_HIGH; + } else + video |= PM2F_VSYNC_ACT_LOW; + + if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + DPRINTK("interlaced not supported\n"); + return -EINVAL; + } + if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) + video |= PM2F_LINE_DOUBLE; + if ((info->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) + video |= PM2F_VIDEO_ENABLE; + par->video = video; + + info->fix.visual = + (depth == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.xres * depth / 8; + info->cmap.len = 256; + + /* + * Settings calculated. Now write them out. + */ + if (par->type == PM2_TYPE_PERMEDIA2V) { + WAIT_FIFO(par, 1); + pm2_WR(par, PM2VR_RD_INDEX_HIGH, 0); + } + + set_aperture(par, depth); + + mb(); + WAIT_FIFO(par, 19); + switch (depth) { + case 8: + pm2_WR(par, PM2R_FB_READ_PIXEL, 0); + clrformat = 0x2e; + break; + case 16: + pm2_WR(par, PM2R_FB_READ_PIXEL, 1); + clrmode |= PM2F_RD_TRUECOLOR | PM2F_RD_PIXELFORMAT_RGB565; + txtmap = PM2F_TEXTEL_SIZE_16; + pixsize = 1; + clrformat = 0x70; + misc |= 8; + break; + case 32: + pm2_WR(par, PM2R_FB_READ_PIXEL, 2); + clrmode |= PM2F_RD_TRUECOLOR | PM2F_RD_PIXELFORMAT_RGBA8888; + txtmap = PM2F_TEXTEL_SIZE_32; + pixsize = 2; + clrformat = 0x20; + misc |= 8; + break; + case 24: + pm2_WR(par, PM2R_FB_READ_PIXEL, 4); + clrmode |= PM2F_RD_TRUECOLOR | PM2F_RD_PIXELFORMAT_RGB888; + txtmap = PM2F_TEXTEL_SIZE_24; + pixsize = 4; + clrformat = 0x20; + misc |= 8; + break; + } + pm2_WR(par, PM2R_FB_WRITE_MODE, PM2F_FB_WRITE_ENABLE); + pm2_WR(par, PM2R_FB_READ_MODE, partprod(xres)); + pm2_WR(par, PM2R_LB_READ_MODE, partprod(xres)); + pm2_WR(par, PM2R_TEXTURE_MAP_FORMAT, txtmap | partprod(xres)); + pm2_WR(par, PM2R_H_TOTAL, htotal); + pm2_WR(par, PM2R_HS_START, hsstart); + pm2_WR(par, PM2R_HS_END, hsend); + pm2_WR(par, PM2R_HG_END, hbend); + pm2_WR(par, PM2R_HB_END, hbend); + pm2_WR(par, PM2R_V_TOTAL, vtotal); + pm2_WR(par, PM2R_VS_START, vsstart); + pm2_WR(par, PM2R_VS_END, vsend); + pm2_WR(par, PM2R_VB_END, vbend); + pm2_WR(par, PM2R_SCREEN_STRIDE, stride); + wmb(); + pm2_WR(par, PM2R_WINDOW_ORIGIN, 0); + pm2_WR(par, PM2R_SCREEN_SIZE, (height << 16) | width); + pm2_WR(par, PM2R_SCISSOR_MODE, PM2F_SCREEN_SCISSOR_ENABLE); + wmb(); + pm2_WR(par, PM2R_SCREEN_BASE, base); + wmb(); + set_video(par, video); + WAIT_FIFO(par, 10); + switch (par->type) { + case PM2_TYPE_PERMEDIA2: + pm2_RDAC_WR(par, PM2I_RD_COLOR_MODE, clrmode); + pm2_RDAC_WR(par, PM2I_RD_COLOR_KEY_CONTROL, + (depth == 8) ? 0 : PM2F_COLOR_KEY_TEST_OFF); + break; + case PM2_TYPE_PERMEDIA2V: + pm2v_RDAC_WR(par, PM2VI_RD_DAC_CONTROL, 0); + pm2v_RDAC_WR(par, PM2VI_RD_PIXEL_SIZE, pixsize); + pm2v_RDAC_WR(par, PM2VI_RD_COLOR_FORMAT, clrformat); + pm2v_RDAC_WR(par, PM2VI_RD_MISC_CONTROL, misc); + pm2v_RDAC_WR(par, PM2VI_RD_OVERLAY_KEY, 0); + break; + } + set_pixclock(par, pixclock); + DPRINTK("Setting graphics mode at %dx%d depth %d\n", + info->var.xres, info->var.yres, info->var.bits_per_pixel); + return 0; +} + +/** + * pm2fb_setcolreg - Sets a color register. + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + * + * Set a single color register. The values supplied have a 16 bit + * magnitude which needs to be scaled in this function for the hardware. + * Pretty much a direct lift from tdfxfb.c. + * + * Returns negative errno on error, or zero on success. + */ +static int pm2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct pm2fb_par *par = info->par; + + if (regno >= info->cmap.len) /* no. of hw registers */ + return -EINVAL; + /* + * Program hardware... do anything you want with transp + */ + + /* grayscale works only partially under directcolor */ + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + if (info->var.grayscale) + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + + /* Directcolor: + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * {hardwarespecific} contains width of DAC + * cmap[X] is programmed to + * (X << red.offset) | (X << green.offset) | (X << blue.offset) + * RAMDAC[X] is programmed to (red, green, blue) + * + * Pseudocolor: + * uses offset = 0 && length = DAC register width. + * var->{color}.offset is 0 + * var->{color}.length contains width of DAC + * cmap is not used + * DAC[X] is programmed to (red, green, blue) + * Truecolor: + * does not use RAMDAC (usually has 3 of them). + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * cmap is programmed to + * (red << red.offset) | (green << green.offset) | + * (blue << blue.offset) | (transp << transp.offset) + * RAMDAC does not exist + */ +#define CNVT_TOHW(val, width) ((((val) << (width)) + 0x7FFF -(val)) >> 16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + /* example here assumes 8 bit DAC. Might be different + * for your hardware */ + red = CNVT_TOHW(red, 8); + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + + if (regno >= 16) + return -EINVAL; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + switch (info->var.bits_per_pixel) { + case 8: + break; + case 16: + case 24: + case 32: + par->palette[regno] = v; + break; + } + return 0; + } else if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) + set_color(par, regno, red, green, blue); + + return 0; +} + +/** + * pm2fb_pan_display - Pans the display. + * @var: frame buffer variable screen structure + * @info: frame buffer structure that represents a single frame buffer + * + * Pan (or wrap, depending on the `vmode' field) the display using the + * `xoffset' and `yoffset' fields of the `var' structure. + * If the values don't fit, return -EINVAL. + * + * Returns negative errno on error, or zero on success. + * + */ +static int pm2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct pm2fb_par *p = info->par; + u32 base; + u32 depth = (info->var.bits_per_pixel + 7) & ~7; + u32 xres = (info->var.xres + 31) & ~31; + + depth = (depth > 32) ? 32 : depth; + base = to3264(var->yoffset * xres + var->xoffset, depth, 1); + WAIT_FIFO(p, 1); + pm2_WR(p, PM2R_SCREEN_BASE, base); + return 0; +} + +/** + * pm2fb_blank - Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + * + * Blank the screen if blank_mode != 0, else unblank. Return 0 if + * blanking succeeded, != 0 if un-/blanking failed due to e.g. a + * video mode which doesn't support it. Implements VESA suspend + * and powerdown modes on hardware that supports disabling hsync/vsync: + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + * + * Returns negative errno on error, or zero on success. + * + */ +static int pm2fb_blank(int blank_mode, struct fb_info *info) +{ + struct pm2fb_par *par = info->par; + u32 video = par->video; + + DPRINTK("blank_mode %d\n", blank_mode); + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + /* Screen: On */ + video |= PM2F_VIDEO_ENABLE; + break; + case FB_BLANK_NORMAL: + /* Screen: Off */ + video &= ~PM2F_VIDEO_ENABLE; + break; + case FB_BLANK_VSYNC_SUSPEND: + /* VSync: Off */ + video &= ~(PM2F_VSYNC_MASK | PM2F_BLANK_LOW); + break; + case FB_BLANK_HSYNC_SUSPEND: + /* HSync: Off */ + video &= ~(PM2F_HSYNC_MASK | PM2F_BLANK_LOW); + break; + case FB_BLANK_POWERDOWN: + /* HSync: Off, VSync: Off */ + video &= ~(PM2F_VSYNC_MASK | PM2F_HSYNC_MASK | PM2F_BLANK_LOW); + break; + } + set_video(par, video); + return 0; +} + +static int pm2fb_sync(struct fb_info *info) +{ + struct pm2fb_par *par = info->par; + + WAIT_FIFO(par, 1); + pm2_WR(par, PM2R_SYNC, 0); + mb(); + do { + while (pm2_RD(par, PM2R_OUT_FIFO_WORDS) == 0) + cpu_relax(); + } while (pm2_RD(par, PM2R_OUT_FIFO) != PM2TAG(PM2R_SYNC)); + + return 0; +} + +static void pm2fb_fillrect(struct fb_info *info, + const struct fb_fillrect *region) +{ + struct pm2fb_par *par = info->par; + struct fb_fillrect modded; + int vxres, vyres; + u32 color = (info->fix.visual == FB_VISUAL_TRUECOLOR) ? + ((u32 *)info->pseudo_palette)[region->color] : region->color; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if ((info->flags & FBINFO_HWACCEL_DISABLED) || + region->rop != ROP_COPY ) { + cfb_fillrect(info, region); + return; + } + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + memcpy(&modded, region, sizeof(struct fb_fillrect)); + + if (!modded.width || !modded.height || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + if (info->var.bits_per_pixel == 8) + color |= color << 8; + if (info->var.bits_per_pixel <= 16) + color |= color << 16; + + WAIT_FIFO(par, 3); + pm2_WR(par, PM2R_CONFIG, PM2F_CONFIG_FB_WRITE_ENABLE); + pm2_WR(par, PM2R_RECTANGLE_ORIGIN, (modded.dy << 16) | modded.dx); + pm2_WR(par, PM2R_RECTANGLE_SIZE, (modded.height << 16) | modded.width); + if (info->var.bits_per_pixel != 24) { + WAIT_FIFO(par, 2); + pm2_WR(par, PM2R_FB_BLOCK_COLOR, color); + wmb(); + pm2_WR(par, PM2R_RENDER, + PM2F_RENDER_RECTANGLE | PM2F_RENDER_FASTFILL); + } else { + WAIT_FIFO(par, 4); + pm2_WR(par, PM2R_COLOR_DDA_MODE, 1); + pm2_WR(par, PM2R_CONSTANT_COLOR, color); + wmb(); + pm2_WR(par, PM2R_RENDER, + PM2F_RENDER_RECTANGLE | + PM2F_INCREASE_X | PM2F_INCREASE_Y ); + pm2_WR(par, PM2R_COLOR_DDA_MODE, 0); + } +} + +static void pm2fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct pm2fb_par *par = info->par; + struct fb_copyarea modded; + u32 vxres, vyres; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_copyarea(info, area); + return; + } + + memcpy(&modded, area, sizeof(struct fb_copyarea)); + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if (!modded.width || !modded.height || + modded.sx >= vxres || modded.sy >= vyres || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.sx + modded.width > vxres) + modded.width = vxres - modded.sx; + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.sy + modded.height > vyres) + modded.height = vyres - modded.sy; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + WAIT_FIFO(par, 5); + pm2_WR(par, PM2R_CONFIG, PM2F_CONFIG_FB_WRITE_ENABLE | + PM2F_CONFIG_FB_READ_SOURCE_ENABLE); + pm2_WR(par, PM2R_FB_SOURCE_DELTA, + ((modded.sy - modded.dy) & 0xfff) << 16 | + ((modded.sx - modded.dx) & 0xfff)); + pm2_WR(par, PM2R_RECTANGLE_ORIGIN, (modded.dy << 16) | modded.dx); + pm2_WR(par, PM2R_RECTANGLE_SIZE, (modded.height << 16) | modded.width); + wmb(); + pm2_WR(par, PM2R_RENDER, PM2F_RENDER_RECTANGLE | + (modded.dx < modded.sx ? PM2F_INCREASE_X : 0) | + (modded.dy < modded.sy ? PM2F_INCREASE_Y : 0)); +} + +static void pm2fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct pm2fb_par *par = info->par; + u32 height = image->height; + u32 fgx, bgx; + const u32 *src = (const u32 *)image->data; + u32 xres = (info->var.xres + 31) & ~31; + int raster_mode = 1; /* invert bits */ + +#ifdef __LITTLE_ENDIAN + raster_mode |= 3 << 7; /* reverse byte order */ +#endif + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED || image->depth != 1) { + cfb_imageblit(info, image); + return; + } + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + fgx = image->fg_color; + bgx = image->bg_color; + break; + case FB_VISUAL_TRUECOLOR: + default: + fgx = par->palette[image->fg_color]; + bgx = par->palette[image->bg_color]; + break; + } + if (info->var.bits_per_pixel == 8) { + fgx |= fgx << 8; + bgx |= bgx << 8; + } + if (info->var.bits_per_pixel <= 16) { + fgx |= fgx << 16; + bgx |= bgx << 16; + } + + WAIT_FIFO(par, 13); + pm2_WR(par, PM2R_FB_READ_MODE, partprod(xres)); + pm2_WR(par, PM2R_SCISSOR_MIN_XY, + ((image->dy & 0xfff) << 16) | (image->dx & 0x0fff)); + pm2_WR(par, PM2R_SCISSOR_MAX_XY, + (((image->dy + image->height) & 0x0fff) << 16) | + ((image->dx + image->width) & 0x0fff)); + pm2_WR(par, PM2R_SCISSOR_MODE, 1); + /* GXcopy & UNIT_ENABLE */ + pm2_WR(par, PM2R_LOGICAL_OP_MODE, (0x3 << 1) | 1); + pm2_WR(par, PM2R_RECTANGLE_ORIGIN, + ((image->dy & 0xfff) << 16) | (image->dx & 0x0fff)); + pm2_WR(par, PM2R_RECTANGLE_SIZE, + ((image->height & 0x0fff) << 16) | + ((image->width) & 0x0fff)); + if (info->var.bits_per_pixel == 24) { + pm2_WR(par, PM2R_COLOR_DDA_MODE, 1); + /* clear area */ + pm2_WR(par, PM2R_CONSTANT_COLOR, bgx); + pm2_WR(par, PM2R_RENDER, + PM2F_RENDER_RECTANGLE | + PM2F_INCREASE_X | PM2F_INCREASE_Y); + /* BitMapPackEachScanline */ + pm2_WR(par, PM2R_RASTERIZER_MODE, raster_mode | (1 << 9)); + pm2_WR(par, PM2R_CONSTANT_COLOR, fgx); + pm2_WR(par, PM2R_RENDER, + PM2F_RENDER_RECTANGLE | + PM2F_INCREASE_X | PM2F_INCREASE_Y | + PM2F_RENDER_SYNC_ON_BIT_MASK); + } else { + pm2_WR(par, PM2R_COLOR_DDA_MODE, 0); + /* clear area */ + pm2_WR(par, PM2R_FB_BLOCK_COLOR, bgx); + pm2_WR(par, PM2R_RENDER, + PM2F_RENDER_RECTANGLE | + PM2F_RENDER_FASTFILL | + PM2F_INCREASE_X | PM2F_INCREASE_Y); + pm2_WR(par, PM2R_RASTERIZER_MODE, raster_mode); + pm2_WR(par, PM2R_FB_BLOCK_COLOR, fgx); + pm2_WR(par, PM2R_RENDER, + PM2F_RENDER_RECTANGLE | + PM2F_INCREASE_X | PM2F_INCREASE_Y | + PM2F_RENDER_FASTFILL | + PM2F_RENDER_SYNC_ON_BIT_MASK); + } + + while (height--) { + int width = ((image->width + 7) >> 3) + + info->pixmap.scan_align - 1; + width >>= 2; + WAIT_FIFO(par, width); + while (width--) { + pm2_WR(par, PM2R_BIT_MASK_PATTERN, *src); + src++; + } + } + WAIT_FIFO(par, 3); + pm2_WR(par, PM2R_RASTERIZER_MODE, 0); + pm2_WR(par, PM2R_COLOR_DDA_MODE, 0); + pm2_WR(par, PM2R_SCISSOR_MODE, 0); +} + +/* + * Hardware cursor support. + */ +static const u8 cursor_bits_lookup[16] = { + 0x00, 0x40, 0x10, 0x50, 0x04, 0x44, 0x14, 0x54, + 0x01, 0x41, 0x11, 0x51, 0x05, 0x45, 0x15, 0x55 +}; + +static int pm2vfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct pm2fb_par *par = info->par; + u8 mode = PM2F_CURSORMODE_TYPE_X; + int x = cursor->image.dx - info->var.xoffset; + int y = cursor->image.dy - info->var.yoffset; + + if (cursor->enable) + mode |= PM2F_CURSORMODE_CURSOR_ENABLE; + + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_MODE, mode); + + if (!cursor->enable) + x = 2047; /* push it outside display */ + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_X_LOW, x & 0xff); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_X_HIGH, (x >> 8) & 0xf); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_Y_LOW, y & 0xff); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_Y_HIGH, (y >> 8) & 0xf); + + /* + * If the cursor is not be changed this means either we want the + * current cursor state (if enable is set) or we want to query what + * we can do with the cursor (if enable is not set) + */ + if (!cursor->set) + return 0; + + if (cursor->set & FB_CUR_SETHOT) { + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_X_HOT, + cursor->hot.x & 0x3f); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_Y_HOT, + cursor->hot.y & 0x3f); + } + + if (cursor->set & FB_CUR_SETCMAP) { + u32 fg_idx = cursor->image.fg_color; + u32 bg_idx = cursor->image.bg_color; + struct fb_cmap cmap = info->cmap; + + /* the X11 driver says one should use these color registers */ + pm2_WR(par, PM2VR_RD_INDEX_HIGH, PM2VI_RD_CURSOR_PALETTE >> 8); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_PALETTE + 0, + cmap.red[bg_idx] >> 8 ); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_PALETTE + 1, + cmap.green[bg_idx] >> 8 ); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_PALETTE + 2, + cmap.blue[bg_idx] >> 8 ); + + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_PALETTE + 3, + cmap.red[fg_idx] >> 8 ); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_PALETTE + 4, + cmap.green[fg_idx] >> 8 ); + pm2v_RDAC_WR(par, PM2VI_RD_CURSOR_PALETTE + 5, + cmap.blue[fg_idx] >> 8 ); + pm2_WR(par, PM2VR_RD_INDEX_HIGH, 0); + } + + if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETIMAGE)) { + u8 *bitmap = (u8 *)cursor->image.data; + u8 *mask = (u8 *)cursor->mask; + int i; + int pos = PM2VI_RD_CURSOR_PATTERN; + + for (i = 0; i < cursor->image.height; i++) { + int j = (cursor->image.width + 7) >> 3; + int k = 8 - j; + + pm2_WR(par, PM2VR_RD_INDEX_HIGH, pos >> 8); + + for (; j > 0; j--) { + u8 data = *bitmap ^ *mask; + + if (cursor->rop == ROP_COPY) + data = *mask & *bitmap; + /* Upper 4 bits of bitmap data */ + pm2v_RDAC_WR(par, pos++, + cursor_bits_lookup[data >> 4] | + (cursor_bits_lookup[*mask >> 4] << 1)); + /* Lower 4 bits of bitmap */ + pm2v_RDAC_WR(par, pos++, + cursor_bits_lookup[data & 0xf] | + (cursor_bits_lookup[*mask & 0xf] << 1)); + bitmap++; + mask++; + } + for (; k > 0; k--) { + pm2v_RDAC_WR(par, pos++, 0); + pm2v_RDAC_WR(par, pos++, 0); + } + } + + while (pos < (1024 + PM2VI_RD_CURSOR_PATTERN)) { + pm2_WR(par, PM2VR_RD_INDEX_HIGH, pos >> 8); + pm2v_RDAC_WR(par, pos++, 0); + } + + pm2_WR(par, PM2VR_RD_INDEX_HIGH, 0); + } + return 0; +} + +static int pm2fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct pm2fb_par *par = info->par; + u8 mode; + + if (!hwcursor) + return -EINVAL; /* just to force soft_cursor() call */ + + /* Too large of a cursor or wrong bpp :-( */ + if (cursor->image.width > 64 || + cursor->image.height > 64 || + cursor->image.depth > 1) + return -EINVAL; + + if (par->type == PM2_TYPE_PERMEDIA2V) + return pm2vfb_cursor(info, cursor); + + mode = 0x40; + if (cursor->enable) + mode = 0x43; + + pm2_RDAC_WR(par, PM2I_RD_CURSOR_CONTROL, mode); + + /* + * If the cursor is not be changed this means either we want the + * current cursor state (if enable is set) or we want to query what + * we can do with the cursor (if enable is not set) + */ + if (!cursor->set) + return 0; + + if (cursor->set & FB_CUR_SETPOS) { + int x = cursor->image.dx - info->var.xoffset + 63; + int y = cursor->image.dy - info->var.yoffset + 63; + + WAIT_FIFO(par, 4); + pm2_WR(par, PM2R_RD_CURSOR_X_LSB, x & 0xff); + pm2_WR(par, PM2R_RD_CURSOR_X_MSB, (x >> 8) & 0x7); + pm2_WR(par, PM2R_RD_CURSOR_Y_LSB, y & 0xff); + pm2_WR(par, PM2R_RD_CURSOR_Y_MSB, (y >> 8) & 0x7); + } + + if (cursor->set & FB_CUR_SETCMAP) { + u32 fg_idx = cursor->image.fg_color; + u32 bg_idx = cursor->image.bg_color; + + WAIT_FIFO(par, 7); + pm2_WR(par, PM2R_RD_CURSOR_COLOR_ADDRESS, 1); + pm2_WR(par, PM2R_RD_CURSOR_COLOR_DATA, + info->cmap.red[bg_idx] >> 8); + pm2_WR(par, PM2R_RD_CURSOR_COLOR_DATA, + info->cmap.green[bg_idx] >> 8); + pm2_WR(par, PM2R_RD_CURSOR_COLOR_DATA, + info->cmap.blue[bg_idx] >> 8); + + pm2_WR(par, PM2R_RD_CURSOR_COLOR_DATA, + info->cmap.red[fg_idx] >> 8); + pm2_WR(par, PM2R_RD_CURSOR_COLOR_DATA, + info->cmap.green[fg_idx] >> 8); + pm2_WR(par, PM2R_RD_CURSOR_COLOR_DATA, + info->cmap.blue[fg_idx] >> 8); + } + + if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETIMAGE)) { + u8 *bitmap = (u8 *)cursor->image.data; + u8 *mask = (u8 *)cursor->mask; + int i; + + WAIT_FIFO(par, 1); + pm2_WR(par, PM2R_RD_PALETTE_WRITE_ADDRESS, 0); + + for (i = 0; i < cursor->image.height; i++) { + int j = (cursor->image.width + 7) >> 3; + int k = 8 - j; + + WAIT_FIFO(par, 8); + for (; j > 0; j--) { + u8 data = *bitmap ^ *mask; + + if (cursor->rop == ROP_COPY) + data = *mask & *bitmap; + /* bitmap data */ + pm2_WR(par, PM2R_RD_CURSOR_DATA, data); + bitmap++; + mask++; + } + for (; k > 0; k--) + pm2_WR(par, PM2R_RD_CURSOR_DATA, 0); + } + for (; i < 64; i++) { + int j = 8; + WAIT_FIFO(par, 8); + while (j-- > 0) + pm2_WR(par, PM2R_RD_CURSOR_DATA, 0); + } + + mask = (u8 *)cursor->mask; + for (i = 0; i < cursor->image.height; i++) { + int j = (cursor->image.width + 7) >> 3; + int k = 8 - j; + + WAIT_FIFO(par, 8); + for (; j > 0; j--) { + /* mask */ + pm2_WR(par, PM2R_RD_CURSOR_DATA, *mask); + mask++; + } + for (; k > 0; k--) + pm2_WR(par, PM2R_RD_CURSOR_DATA, 0); + } + for (; i < 64; i++) { + int j = 8; + WAIT_FIFO(par, 8); + while (j-- > 0) + pm2_WR(par, PM2R_RD_CURSOR_DATA, 0); + } + } + return 0; +} + +/* ------------ Hardware Independent Functions ------------ */ + +/* + * Frame buffer operations + */ + +static struct fb_ops pm2fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = pm2fb_check_var, + .fb_set_par = pm2fb_set_par, + .fb_setcolreg = pm2fb_setcolreg, + .fb_blank = pm2fb_blank, + .fb_pan_display = pm2fb_pan_display, + .fb_fillrect = pm2fb_fillrect, + .fb_copyarea = pm2fb_copyarea, + .fb_imageblit = pm2fb_imageblit, + .fb_sync = pm2fb_sync, + .fb_cursor = pm2fb_cursor, +}; + +/* + * PCI stuff + */ + + +/** + * Device initialisation + * + * Initialise and allocate resource for PCI device. + * + * @param pdev PCI device. + * @param id PCI device ID. + */ +static int pm2fb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct pm2fb_par *default_par; + struct fb_info *info; + int err; + int retval = -ENXIO; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_WARNING "pm2fb: Can't enable pdev: %d\n", err); + return err; + } + + info = framebuffer_alloc(sizeof(struct pm2fb_par), &pdev->dev); + if (!info) + return -ENOMEM; + default_par = info->par; + + switch (pdev->device) { + case PCI_DEVICE_ID_TI_TVP4020: + strcpy(pm2fb_fix.id, "TVP4020"); + default_par->type = PM2_TYPE_PERMEDIA2; + break; + case PCI_DEVICE_ID_3DLABS_PERMEDIA2: + strcpy(pm2fb_fix.id, "Permedia2"); + default_par->type = PM2_TYPE_PERMEDIA2; + break; + case PCI_DEVICE_ID_3DLABS_PERMEDIA2V: + strcpy(pm2fb_fix.id, "Permedia2v"); + default_par->type = PM2_TYPE_PERMEDIA2V; + break; + } + + pm2fb_fix.mmio_start = pci_resource_start(pdev, 0); + pm2fb_fix.mmio_len = PM2_REGS_SIZE; + +#if defined(__BIG_ENDIAN) + /* + * PM2 has a 64k register file, mapped twice in 128k. Lower + * map is little-endian, upper map is big-endian. + */ + pm2fb_fix.mmio_start += PM2_REGS_SIZE; + DPRINTK("Adjusting register base for big-endian.\n"); +#endif + DPRINTK("Register base at 0x%lx\n", pm2fb_fix.mmio_start); + + /* Registers - request region and map it. */ + if (!request_mem_region(pm2fb_fix.mmio_start, pm2fb_fix.mmio_len, + "pm2fb regbase")) { + printk(KERN_WARNING "pm2fb: Can't reserve regbase.\n"); + goto err_exit_neither; + } + default_par->v_regs = + ioremap_nocache(pm2fb_fix.mmio_start, pm2fb_fix.mmio_len); + if (!default_par->v_regs) { + printk(KERN_WARNING "pm2fb: Can't remap %s register area.\n", + pm2fb_fix.id); + release_mem_region(pm2fb_fix.mmio_start, pm2fb_fix.mmio_len); + goto err_exit_neither; + } + + /* Stash away memory register info for use when we reset the board */ + default_par->mem_control = pm2_RD(default_par, PM2R_MEM_CONTROL); + default_par->boot_address = pm2_RD(default_par, PM2R_BOOT_ADDRESS); + default_par->mem_config = pm2_RD(default_par, PM2R_MEM_CONFIG); + DPRINTK("MemControl 0x%x BootAddress 0x%x MemConfig 0x%x\n", + default_par->mem_control, default_par->boot_address, + default_par->mem_config); + + if (default_par->mem_control == 0 && + default_par->boot_address == 0x31 && + default_par->mem_config == 0x259fffff) { + default_par->memclock = CVPPC_MEMCLOCK; + default_par->mem_control = 0; + default_par->boot_address = 0x20; + default_par->mem_config = 0xe6002021; + if (pdev->subsystem_vendor == 0x1048 && + pdev->subsystem_device == 0x0a31) { + DPRINTK("subsystem_vendor: %04x, " + "subsystem_device: %04x\n", + pdev->subsystem_vendor, pdev->subsystem_device); + DPRINTK("We have not been initialized by VGA BIOS and " + "are running on an Elsa Winner 2000 Office\n"); + DPRINTK("Initializing card timings manually...\n"); + default_par->memclock = 100000; + } + if (pdev->subsystem_vendor == 0x3d3d && + pdev->subsystem_device == 0x0100) { + DPRINTK("subsystem_vendor: %04x, " + "subsystem_device: %04x\n", + pdev->subsystem_vendor, pdev->subsystem_device); + DPRINTK("We have not been initialized by VGA BIOS and " + "are running on an 3dlabs reference board\n"); + DPRINTK("Initializing card timings manually...\n"); + default_par->memclock = 74894; + } + } + + /* Now work out how big lfb is going to be. */ + switch (default_par->mem_config & PM2F_MEM_CONFIG_RAM_MASK) { + case PM2F_MEM_BANKS_1: + pm2fb_fix.smem_len = 0x200000; + break; + case PM2F_MEM_BANKS_2: + pm2fb_fix.smem_len = 0x400000; + break; + case PM2F_MEM_BANKS_3: + pm2fb_fix.smem_len = 0x600000; + break; + case PM2F_MEM_BANKS_4: + pm2fb_fix.smem_len = 0x800000; + break; + } + pm2fb_fix.smem_start = pci_resource_start(pdev, 1); + + /* Linear frame buffer - request region and map it. */ + if (!request_mem_region(pm2fb_fix.smem_start, pm2fb_fix.smem_len, + "pm2fb smem")) { + printk(KERN_WARNING "pm2fb: Can't reserve smem.\n"); + goto err_exit_mmio; + } + info->screen_base = + ioremap_nocache(pm2fb_fix.smem_start, pm2fb_fix.smem_len); + if (!info->screen_base) { + printk(KERN_WARNING "pm2fb: Can't ioremap smem area.\n"); + release_mem_region(pm2fb_fix.smem_start, pm2fb_fix.smem_len); + goto err_exit_mmio; + } + +#ifdef CONFIG_MTRR + default_par->mtrr_handle = -1; + if (!nomtrr) + default_par->mtrr_handle = + mtrr_add(pm2fb_fix.smem_start, + pm2fb_fix.smem_len, + MTRR_TYPE_WRCOMB, 1); +#endif + + info->fbops = &pm2fb_ops; + info->fix = pm2fb_fix; + info->pseudo_palette = default_par->palette; + info->flags = FBINFO_DEFAULT | + FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_FILLRECT; + + info->pixmap.addr = kmalloc(PM2_PIXMAP_SIZE, GFP_KERNEL); + if (!info->pixmap.addr) { + retval = -ENOMEM; + goto err_exit_pixmap; + } + info->pixmap.size = PM2_PIXMAP_SIZE; + info->pixmap.buf_align = 4; + info->pixmap.scan_align = 4; + info->pixmap.access_align = 32; + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + if (noaccel) { + printk(KERN_DEBUG "disabling acceleration\n"); + info->flags |= FBINFO_HWACCEL_DISABLED; + info->pixmap.scan_align = 1; + } + + if (!mode_option) + mode_option = "640x480@60"; + + err = fb_find_mode(&info->var, info, mode_option, NULL, 0, NULL, 8); + if (!err || err == 4) + info->var = pm2fb_var; + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) + goto err_exit_both; + + retval = register_framebuffer(info); + if (retval < 0) + goto err_exit_all; + + fb_info(info, "%s frame buffer device, memory = %dK\n", + info->fix.id, pm2fb_fix.smem_len / 1024); + + /* + * Our driver data + */ + pci_set_drvdata(pdev, info); + + return 0; + + err_exit_all: + fb_dealloc_cmap(&info->cmap); + err_exit_both: + kfree(info->pixmap.addr); + err_exit_pixmap: + iounmap(info->screen_base); + release_mem_region(pm2fb_fix.smem_start, pm2fb_fix.smem_len); + err_exit_mmio: + iounmap(default_par->v_regs); + release_mem_region(pm2fb_fix.mmio_start, pm2fb_fix.mmio_len); + err_exit_neither: + framebuffer_release(info); + return retval; +} + +/** + * Device removal. + * + * Release all device resources. + * + * @param pdev PCI device to clean up. + */ +static void pm2fb_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct fb_fix_screeninfo *fix = &info->fix; + struct pm2fb_par *par = info->par; + + unregister_framebuffer(info); + +#ifdef CONFIG_MTRR + if (par->mtrr_handle >= 0) + mtrr_del(par->mtrr_handle, info->fix.smem_start, + info->fix.smem_len); +#endif /* CONFIG_MTRR */ + iounmap(info->screen_base); + release_mem_region(fix->smem_start, fix->smem_len); + iounmap(par->v_regs); + release_mem_region(fix->mmio_start, fix->mmio_len); + + fb_dealloc_cmap(&info->cmap); + kfree(info->pixmap.addr); + framebuffer_release(info); +} + +static struct pci_device_id pm2fb_id_table[] = { + { PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_TVP4020, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_3DLABS, PCI_DEVICE_ID_3DLABS_PERMEDIA2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_3DLABS, PCI_DEVICE_ID_3DLABS_PERMEDIA2V, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +static struct pci_driver pm2fb_driver = { + .name = "pm2fb", + .id_table = pm2fb_id_table, + .probe = pm2fb_probe, + .remove = pm2fb_remove, +}; + +MODULE_DEVICE_TABLE(pci, pm2fb_id_table); + + +#ifndef MODULE +/** + * Parse user specified options. + * + * This is, comma-separated options following `video=pm2fb:'. + */ +static int __init pm2fb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + if (!strcmp(this_opt, "lowhsync")) + lowhsync = 1; + else if (!strcmp(this_opt, "lowvsync")) + lowvsync = 1; + else if (!strncmp(this_opt, "hwcursor=", 9)) + hwcursor = simple_strtoul(this_opt + 9, NULL, 0); +#ifdef CONFIG_MTRR + else if (!strncmp(this_opt, "nomtrr", 6)) + nomtrr = 1; +#endif + else if (!strncmp(this_opt, "noaccel", 7)) + noaccel = 1; + else + mode_option = this_opt; + } + return 0; +} +#endif + + +static int __init pm2fb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("pm2fb", &option)) + return -ENODEV; + pm2fb_setup(option); +#endif + + return pci_register_driver(&pm2fb_driver); +} + +module_init(pm2fb_init); + +#ifdef MODULE +/* + * Cleanup + */ + +static void __exit pm2fb_exit(void) +{ + pci_unregister_driver(&pm2fb_driver); +} +#endif + +#ifdef MODULE +module_exit(pm2fb_exit); + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Initial video mode e.g. '648x480-8@60'"); +module_param_named(mode, mode_option, charp, 0); +MODULE_PARM_DESC(mode, "Initial video mode e.g. '648x480-8@60' (deprecated)"); +module_param(lowhsync, bool, 0); +MODULE_PARM_DESC(lowhsync, "Force horizontal sync low regardless of mode"); +module_param(lowvsync, bool, 0); +MODULE_PARM_DESC(lowvsync, "Force vertical sync low regardless of mode"); +module_param(noaccel, bool, 0); +MODULE_PARM_DESC(noaccel, "Disable acceleration"); +module_param(hwcursor, int, 0644); +MODULE_PARM_DESC(hwcursor, "Enable hardware cursor " + "(1=enable, 0=disable, default=1)"); +#ifdef CONFIG_MTRR +module_param(nomtrr, bool, 0); +MODULE_PARM_DESC(nomtrr, "Disable MTRR support (0 or 1=disabled) (default=0)"); +#endif + +MODULE_AUTHOR("Jim Hague <jim.hague@acm.org>"); +MODULE_DESCRIPTION("Permedia2 framebuffer device driver"); +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/video/fbdev/pm3fb.c b/drivers/video/fbdev/pm3fb.c new file mode 100644 index 000000000000..4bf3273d0433 --- /dev/null +++ b/drivers/video/fbdev/pm3fb.c @@ -0,0 +1,1586 @@ +/* + * 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, __func__ , ## b) +#else +#define DPRINTK(a, b...) +#endif + +#define PM3_PIXMAP_SIZE (2048 * 4) + +/* + * Driver data + */ +static int hwcursor = 1; +static char *mode_option; +static bool noaccel; + +/* mtrr option */ +#ifdef CONFIG_MTRR +static bool nomtrr; +#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 = { + .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; + default: + DPRINTK(1, "Unsupported depth %d\n", + info->current_par->depth); + break; + } + } + + PM3_WRITE_REG(par, PM3dXDom, 0x0); + PM3_WRITE_REG(par, PM3dXSub, 0x0); + PM3_WRITE_REG(par, PM3dY, 1 << 16); + PM3_WRITE_REG(par, PM3StartXDom, 0x0); + PM3_WRITE_REG(par, PM3StartXSub, 0x0); + PM3_WRITE_REG(par, PM3StartY, 0x0); + PM3_WRITE_REG(par, PM3Count, 0x0); + +/* Disable LocalBuffer. better safe than sorry */ + PM3_WRITE_REG(par, PM3LBDestReadMode, 0x0); + PM3_WRITE_REG(par, PM3LBDestReadEnables, 0x0); + PM3_WRITE_REG(par, PM3LBSourceReadMode, 0x0); + PM3_WRITE_REG(par, PM3LBWriteMode, 0x0); + + pm3fb_sync(info); +} + +static void pm3fb_fillrect(struct fb_info *info, + const struct fb_fillrect *region) +{ + struct pm3_par *par = info->par; + struct fb_fillrect modded; + int vxres, vyres; + int rop; + u32 color = (info->fix.visual == FB_VISUAL_TRUECOLOR) ? + ((u32 *)info->pseudo_palette)[region->color] : region->color; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_fillrect(info, region); + return; + } + if (region->rop == ROP_COPY ) + rop = PM3Config2D_ForegroundROP(0x3); /* GXcopy */ + else + rop = PM3Config2D_ForegroundROP(0x6) | /* GXxor */ + PM3Config2D_FBDestReadEnable; + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + memcpy(&modded, region, sizeof(struct fb_fillrect)); + + if (!modded.width || !modded.height || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + if (info->var.bits_per_pixel == 8) + color |= color << 8; + if (info->var.bits_per_pixel <= 16) + color |= color << 16; + + PM3_WAIT(par, 4); + /* ROP Ox3 is GXcopy */ + PM3_WRITE_REG(par, PM3Config2D, + PM3Config2D_UseConstantSource | + PM3Config2D_ForegroundROPEnable | + rop | + PM3Config2D_FBWriteEnable); + + PM3_WRITE_REG(par, PM3ForegroundColor, color); + + PM3_WRITE_REG(par, PM3RectanglePosition, + PM3RectanglePosition_XOffset(modded.dx) | + PM3RectanglePosition_YOffset(modded.dy)); + + PM3_WRITE_REG(par, PM3Render2D, + PM3Render2D_XPositive | + PM3Render2D_YPositive | + PM3Render2D_Operation_Normal | + PM3Render2D_SpanOperation | + PM3Render2D_Width(modded.width) | + PM3Render2D_Height(modded.height)); +} + +static void pm3fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct pm3_par *par = info->par; + struct fb_copyarea modded; + u32 vxres, vyres; + int x_align, o_x, o_y; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_copyarea(info, area); + return; + } + + memcpy(&modded, area, sizeof(struct fb_copyarea)); + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if (!modded.width || !modded.height || + modded.sx >= vxres || modded.sy >= vyres || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.sx + modded.width > vxres) + modded.width = vxres - modded.sx; + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.sy + modded.height > vyres) + modded.height = vyres - modded.sy; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + o_x = modded.sx - modded.dx; /*(sx > dx ) ? (sx - dx) : (dx - sx); */ + o_y = modded.sy - modded.dy; /*(sy > dy ) ? (sy - dy) : (dy - sy); */ + + x_align = (modded.sx & 0x1f); + + PM3_WAIT(par, 6); + + PM3_WRITE_REG(par, PM3Config2D, + PM3Config2D_UserScissorEnable | + PM3Config2D_ForegroundROPEnable | + PM3Config2D_Blocking | + PM3Config2D_ForegroundROP(0x3) | /* Ox3 is GXcopy */ + PM3Config2D_FBWriteEnable); + + PM3_WRITE_REG(par, PM3ScissorMinXY, + ((modded.dy & 0x0fff) << 16) | (modded.dx & 0x0fff)); + PM3_WRITE_REG(par, PM3ScissorMaxXY, + (((modded.dy + modded.height) & 0x0fff) << 16) | + ((modded.dx + modded.width) & 0x0fff)); + + PM3_WRITE_REG(par, PM3FBSourceReadBufferOffset, + PM3FBSourceReadBufferOffset_XOffset(o_x) | + PM3FBSourceReadBufferOffset_YOffset(o_y)); + + PM3_WRITE_REG(par, PM3RectanglePosition, + PM3RectanglePosition_XOffset(modded.dx - x_align) | + PM3RectanglePosition_YOffset(modded.dy)); + + PM3_WRITE_REG(par, PM3Render2D, + ((modded.sx > modded.dx) ? PM3Render2D_XPositive : 0) | + ((modded.sy > modded.dy) ? PM3Render2D_YPositive : 0) | + PM3Render2D_Operation_Normal | + PM3Render2D_SpanOperation | + PM3Render2D_FBSourceReadEnable | + PM3Render2D_Width(modded.width + x_align) | + PM3Render2D_Height(modded.height)); +} + +static void pm3fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct pm3_par *par = info->par; + u32 height = image->height; + u32 fgx, bgx; + const u32 *src = (const u32 *)image->data; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_imageblit(info, image); + return; + } + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + fgx = image->fg_color; + bgx = image->bg_color; + break; + case FB_VISUAL_TRUECOLOR: + default: + fgx = par->palette[image->fg_color]; + bgx = par->palette[image->bg_color]; + break; + } + if (image->depth != 1) { + cfb_imageblit(info, image); + return; + } + + if (info->var.bits_per_pixel == 8) { + fgx |= fgx << 8; + bgx |= bgx << 8; + } + if (info->var.bits_per_pixel <= 16) { + fgx |= fgx << 16; + bgx |= bgx << 16; + } + + PM3_WAIT(par, 7); + + PM3_WRITE_REG(par, PM3ForegroundColor, fgx); + PM3_WRITE_REG(par, PM3BackgroundColor, bgx); + + /* ROP Ox3 is GXcopy */ + PM3_WRITE_REG(par, PM3Config2D, + PM3Config2D_UserScissorEnable | + PM3Config2D_UseConstantSource | + PM3Config2D_ForegroundROPEnable | + PM3Config2D_ForegroundROP(0x3) | + PM3Config2D_OpaqueSpan | + PM3Config2D_FBWriteEnable); + PM3_WRITE_REG(par, PM3ScissorMinXY, + ((image->dy & 0x0fff) << 16) | (image->dx & 0x0fff)); + PM3_WRITE_REG(par, PM3ScissorMaxXY, + (((image->dy + image->height) & 0x0fff) << 16) | + ((image->dx + image->width) & 0x0fff)); + PM3_WRITE_REG(par, PM3RectanglePosition, + PM3RectanglePosition_XOffset(image->dx) | + PM3RectanglePosition_YOffset(image->dy)); + PM3_WRITE_REG(par, PM3Render2D, + PM3Render2D_XPositive | + PM3Render2D_YPositive | + PM3Render2D_Operation_SyncOnBitMask | + PM3Render2D_SpanOperation | + PM3Render2D_Width(image->width) | + PM3Render2D_Height(image->height)); + + + while (height--) { + int width = ((image->width + 7) >> 3) + + info->pixmap.scan_align - 1; + width >>= 2; + + while (width >= PM3_FIFO_SIZE) { + int i = PM3_FIFO_SIZE - 1; + + PM3_WAIT(par, PM3_FIFO_SIZE); + while (i--) { + PM3_WRITE_REG(par, PM3BitMaskPattern, *src); + src++; + } + width -= PM3_FIFO_SIZE - 1; + } + + PM3_WAIT(par, width + 1); + while (width--) { + PM3_WRITE_REG(par, PM3BitMaskPattern, *src); + src++; + } + } +} +/* end of acceleration functions */ + +/* + * Hardware Cursor support. + */ +static const u8 cursor_bits_lookup[16] = { + 0x00, 0x40, 0x10, 0x50, 0x04, 0x44, 0x14, 0x54, + 0x01, 0x41, 0x11, 0x51, 0x05, 0x45, 0x15, 0x55 +}; + +static int pm3fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct pm3_par *par = info->par; + u8 mode; + + if (!hwcursor) + return -EINVAL; /* just to force soft_cursor() call */ + + /* Too large of a cursor or wrong bpp :-( */ + if (cursor->image.width > 64 || + cursor->image.height > 64 || + cursor->image.depth > 1) + return -EINVAL; + + mode = PM3RD_CursorMode_TYPE_X; + if (cursor->enable) + mode |= PM3RD_CursorMode_CURSOR_ENABLE; + + PM3_WRITE_DAC_REG(par, PM3RD_CursorMode, mode); + + /* + * If the cursor is not be changed this means either we want the + * current cursor state (if enable is set) or we want to query what + * we can do with the cursor (if enable is not set) + */ + if (!cursor->set) + return 0; + + if (cursor->set & FB_CUR_SETPOS) { + int x = cursor->image.dx - info->var.xoffset; + int y = cursor->image.dy - info->var.yoffset; + + PM3_WRITE_DAC_REG(par, PM3RD_CursorXLow, x & 0xff); + PM3_WRITE_DAC_REG(par, PM3RD_CursorXHigh, (x >> 8) & 0xf); + PM3_WRITE_DAC_REG(par, PM3RD_CursorYLow, y & 0xff); + PM3_WRITE_DAC_REG(par, PM3RD_CursorYHigh, (y >> 8) & 0xf); + } + + if (cursor->set & FB_CUR_SETHOT) { + PM3_WRITE_DAC_REG(par, PM3RD_CursorHotSpotX, + cursor->hot.x & 0x3f); + PM3_WRITE_DAC_REG(par, PM3RD_CursorHotSpotY, + cursor->hot.y & 0x3f); + } + + if (cursor->set & FB_CUR_SETCMAP) { + u32 fg_idx = cursor->image.fg_color; + u32 bg_idx = cursor->image.bg_color; + struct fb_cmap cmap = info->cmap; + + /* the X11 driver says one should use these color registers */ + PM3_WRITE_DAC_REG(par, PM3RD_CursorPalette(39), + cmap.red[fg_idx] >> 8 ); + PM3_WRITE_DAC_REG(par, PM3RD_CursorPalette(40), + cmap.green[fg_idx] >> 8 ); + PM3_WRITE_DAC_REG(par, PM3RD_CursorPalette(41), + cmap.blue[fg_idx] >> 8 ); + + PM3_WRITE_DAC_REG(par, PM3RD_CursorPalette(42), + cmap.red[bg_idx] >> 8 ); + PM3_WRITE_DAC_REG(par, PM3RD_CursorPalette(43), + cmap.green[bg_idx] >> 8 ); + PM3_WRITE_DAC_REG(par, PM3RD_CursorPalette(44), + cmap.blue[bg_idx] >> 8 ); + } + + if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETIMAGE)) { + u8 *bitmap = (u8 *)cursor->image.data; + u8 *mask = (u8 *)cursor->mask; + int i; + int pos = PM3RD_CursorPattern(0); + + for (i = 0; i < cursor->image.height; i++) { + int j = (cursor->image.width + 7) >> 3; + int k = 8 - j; + + for (; j > 0; j--) { + u8 data = *bitmap ^ *mask; + + if (cursor->rop == ROP_COPY) + data = *mask & *bitmap; + /* Upper 4 bits of bitmap data */ + PM3_WRITE_DAC_REG(par, pos++, + cursor_bits_lookup[data >> 4] | + (cursor_bits_lookup[*mask >> 4] << 1)); + /* Lower 4 bits of bitmap */ + PM3_WRITE_DAC_REG(par, pos++, + cursor_bits_lookup[data & 0xf] | + (cursor_bits_lookup[*mask & 0xf] << 1)); + bitmap++; + mask++; + } + for (; k > 0; k--) { + PM3_WRITE_DAC_REG(par, pos++, 0); + PM3_WRITE_DAC_REG(par, pos++, 0); + } + } + while (pos < PM3RD_CursorPattern(1024)) + PM3_WRITE_DAC_REG(par, pos++, 0); + } + return 0; +} + +/* write the mode to registers */ +static void pm3fb_write_mode(struct fb_info *info) +{ + struct pm3_par *par = info->par; + char tempsync = 0x00; + char tempmisc = 0x00; + const u32 hsstart = info->var.right_margin; + const u32 hsend = hsstart + info->var.hsync_len; + const u32 hbend = hsend + info->var.left_margin; + const u32 xres = (info->var.xres + 31) & ~31; + const u32 htotal = xres + hbend; + const u32 vsstart = info->var.lower_margin; + const u32 vsend = vsstart + info->var.vsync_len; + const u32 vbend = vsend + info->var.upper_margin; + const u32 vtotal = info->var.yres + vbend; + const u32 width = (info->var.xres_virtual + 7) & ~7; + const unsigned bpp = info->var.bits_per_pixel; + + PM3_WAIT(par, 20); + PM3_WRITE_REG(par, PM3MemBypassWriteMask, 0xffffffff); + PM3_WRITE_REG(par, PM3Aperture0, 0x00000000); + PM3_WRITE_REG(par, PM3Aperture1, 0x00000000); + PM3_WRITE_REG(par, PM3FIFODis, 0x00000007); + + PM3_WRITE_REG(par, PM3HTotal, + pm3fb_shift_bpp(bpp, htotal - 1)); + PM3_WRITE_REG(par, PM3HsEnd, + pm3fb_shift_bpp(bpp, hsend)); + PM3_WRITE_REG(par, PM3HsStart, + pm3fb_shift_bpp(bpp, hsstart)); + PM3_WRITE_REG(par, PM3HbEnd, + pm3fb_shift_bpp(bpp, hbend)); + PM3_WRITE_REG(par, PM3HgEnd, + pm3fb_shift_bpp(bpp, hbend)); + PM3_WRITE_REG(par, PM3ScreenStride, + pm3fb_shift_bpp(bpp, width)); + PM3_WRITE_REG(par, PM3VTotal, vtotal - 1); + PM3_WRITE_REG(par, PM3VsEnd, vsend - 1); + PM3_WRITE_REG(par, PM3VsStart, vsstart - 1); + PM3_WRITE_REG(par, PM3VbEnd, vbend); + + switch (bpp) { + case 8: + PM3_WRITE_REG(par, PM3ByAperture1Mode, + PM3ByApertureMode_PIXELSIZE_8BIT); + PM3_WRITE_REG(par, PM3ByAperture2Mode, + PM3ByApertureMode_PIXELSIZE_8BIT); + break; + + case 16: +#ifndef __BIG_ENDIAN + PM3_WRITE_REG(par, PM3ByAperture1Mode, + PM3ByApertureMode_PIXELSIZE_16BIT); + PM3_WRITE_REG(par, PM3ByAperture2Mode, + PM3ByApertureMode_PIXELSIZE_16BIT); +#else + PM3_WRITE_REG(par, PM3ByAperture1Mode, + PM3ByApertureMode_PIXELSIZE_16BIT | + PM3ByApertureMode_BYTESWAP_BADC); + PM3_WRITE_REG(par, PM3ByAperture2Mode, + PM3ByApertureMode_PIXELSIZE_16BIT | + PM3ByApertureMode_BYTESWAP_BADC); +#endif /* ! __BIG_ENDIAN */ + break; + + case 32: +#ifndef __BIG_ENDIAN + PM3_WRITE_REG(par, PM3ByAperture1Mode, + PM3ByApertureMode_PIXELSIZE_32BIT); + PM3_WRITE_REG(par, PM3ByAperture2Mode, + PM3ByApertureMode_PIXELSIZE_32BIT); +#else + PM3_WRITE_REG(par, PM3ByAperture1Mode, + PM3ByApertureMode_PIXELSIZE_32BIT | + PM3ByApertureMode_BYTESWAP_DCBA); + PM3_WRITE_REG(par, PM3ByAperture2Mode, + PM3ByApertureMode_PIXELSIZE_32BIT | + PM3ByApertureMode_BYTESWAP_DCBA); +#endif /* ! __BIG_ENDIAN */ + break; + + default: + DPRINTK("Unsupported depth %d\n", bpp); + break; + } + + /* + * Oxygen VX1 - it appears that setting PM3VideoControl and + * then PM3RD_SyncControl to the same SYNC settings undoes + * any net change - they seem to xor together. Only set the + * sync options in PM3RD_SyncControl. --rmk + */ + { + unsigned int video = par->video; + + video &= ~(PM3VideoControl_HSYNC_MASK | + PM3VideoControl_VSYNC_MASK); + video |= PM3VideoControl_HSYNC_ACTIVE_HIGH | + PM3VideoControl_VSYNC_ACTIVE_HIGH; + PM3_WRITE_REG(par, PM3VideoControl, video); + } + PM3_WRITE_REG(par, PM3VClkCtl, + (PM3_READ_REG(par, PM3VClkCtl) & 0xFFFFFFFC)); + PM3_WRITE_REG(par, PM3ScreenBase, par->base); + PM3_WRITE_REG(par, PM3ChipConfig, + (PM3_READ_REG(par, PM3ChipConfig) & 0xFFFFFFFD)); + + wmb(); + { + unsigned char uninitialized_var(m); /* ClkPreScale */ + unsigned char uninitialized_var(n); /* ClkFeedBackScale */ + unsigned char uninitialized_var(p); /* ClkPostScale */ + unsigned long pixclock = PICOS2KHZ(info->var.pixclock); + + (void)pm3fb_calculate_clock(pixclock, &m, &n, &p); + + DPRINTK("Pixclock: %ld, Pre: %d, Feedback: %d, Post: %d\n", + pixclock, (int) m, (int) n, (int) p); + + PM3_WRITE_DAC_REG(par, PM3RD_DClk0PreScale, m); + PM3_WRITE_DAC_REG(par, PM3RD_DClk0FeedbackScale, n); + PM3_WRITE_DAC_REG(par, PM3RD_DClk0PostScale, p); + } + /* + PM3_WRITE_DAC_REG(par, PM3RD_IndexControl, 0x00); + */ + /* + PM3_SLOW_WRITE_REG(par, PM3RD_IndexControl, 0x00); + */ + if ((par->video & PM3VideoControl_HSYNC_MASK) == + PM3VideoControl_HSYNC_ACTIVE_HIGH) + tempsync |= PM3RD_SyncControl_HSYNC_ACTIVE_HIGH; + if ((par->video & PM3VideoControl_VSYNC_MASK) == + PM3VideoControl_VSYNC_ACTIVE_HIGH) + tempsync |= PM3RD_SyncControl_VSYNC_ACTIVE_HIGH; + + PM3_WRITE_DAC_REG(par, PM3RD_SyncControl, tempsync); + DPRINTK("PM3RD_SyncControl: %d\n", tempsync); + + PM3_WRITE_DAC_REG(par, PM3RD_DACControl, 0x00); + + switch (pm3fb_depth(&info->var)) { + case 8: + PM3_WRITE_DAC_REG(par, PM3RD_PixelSize, + PM3RD_PixelSize_8_BIT_PIXELS); + PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat, + PM3RD_ColorFormat_CI8_COLOR | + PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW); + tempmisc |= PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE; + break; + case 12: + PM3_WRITE_DAC_REG(par, PM3RD_PixelSize, + PM3RD_PixelSize_16_BIT_PIXELS); + PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat, + PM3RD_ColorFormat_4444_COLOR | + PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW | + PM3RD_ColorFormat_LINEAR_COLOR_EXT_ENABLE); + tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE | + PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE; + break; + case 15: + PM3_WRITE_DAC_REG(par, PM3RD_PixelSize, + PM3RD_PixelSize_16_BIT_PIXELS); + PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat, + PM3RD_ColorFormat_5551_FRONT_COLOR | + PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW | + PM3RD_ColorFormat_LINEAR_COLOR_EXT_ENABLE); + tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE | + PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE; + break; + case 16: + PM3_WRITE_DAC_REG(par, PM3RD_PixelSize, + PM3RD_PixelSize_16_BIT_PIXELS); + PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat, + PM3RD_ColorFormat_565_FRONT_COLOR | + PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW | + PM3RD_ColorFormat_LINEAR_COLOR_EXT_ENABLE); + tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE | + PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE; + break; + case 32: + PM3_WRITE_DAC_REG(par, PM3RD_PixelSize, + PM3RD_PixelSize_32_BIT_PIXELS); + PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat, + PM3RD_ColorFormat_8888_COLOR | + PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW); + tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE | + PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE; + break; + } + PM3_WRITE_DAC_REG(par, PM3RD_MiscControl, tempmisc); +} + +/* + * hardware independent functions + */ +static int pm3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 lpitch; + unsigned bpp = var->red.length + var->green.length + + var->blue.length + var->transp.length; + + if (bpp != var->bits_per_pixel) { + /* set predefined mode for bits_per_pixel settings */ + + switch (var->bits_per_pixel) { + case 8: + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: + var->red.length = 5; + var->blue.length = 5; + var->green.length = 6; + var->transp.length = 0; + break; + case 32: + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + break; + default: + DPRINTK("depth not supported: %u\n", + var->bits_per_pixel); + return -EINVAL; + } + } + /* it is assumed BGRA order */ + if (var->bits_per_pixel > 8 ) { + var->blue.offset = 0; + var->green.offset = var->blue.length; + var->red.offset = var->green.offset + var->green.length; + var->transp.offset = var->red.offset + var->red.length; + } + var->height = -1; + var->width = -1; + + if (var->xres != var->xres_virtual) { + DPRINTK("virtual x resolution != " + "physical x resolution not supported\n"); + return -EINVAL; + } + + if (var->yres > var->yres_virtual) { + DPRINTK("virtual y resolution < " + "physical y resolution not possible\n"); + return -EINVAL; + } + + if (var->xoffset) { + DPRINTK("xoffset not supported\n"); + return -EINVAL; + } + + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + DPRINTK("interlace not supported\n"); + return -EINVAL; + } + + var->xres = (var->xres + 31) & ~31; /* could sometimes be 8 */ + lpitch = var->xres * ((var->bits_per_pixel + 7) >> 3); + + if (var->xres < 200 || var->xres > 2048) { + DPRINTK("width not supported: %u\n", var->xres); + return -EINVAL; + } + + if (var->yres < 200 || var->yres > 4095) { + DPRINTK("height not supported: %u\n", var->yres); + return -EINVAL; + } + + if (lpitch * var->yres_virtual > info->fix.smem_len) { + DPRINTK("no memory for screen (%ux%ux%u)\n", + var->xres, var->yres_virtual, var->bits_per_pixel); + return -EINVAL; + } + + if (PICOS2KHZ(var->pixclock) > PM3_MAX_PIXCLOCK) { + DPRINTK("pixclock too high (%ldKHz)\n", + PICOS2KHZ(var->pixclock)); + return -EINVAL; + } + + var->accel_flags = 0; /* Can't mmap if this is on */ + + DPRINTK("Checking graphics mode at %dx%d depth %d\n", + var->xres, var->yres, var->bits_per_pixel); + return 0; +} + +static int pm3fb_set_par(struct fb_info *info) +{ + struct pm3_par *par = info->par; + const u32 xres = (info->var.xres + 31) & ~31; + const unsigned bpp = info->var.bits_per_pixel; + + par->base = pm3fb_shift_bpp(bpp, (info->var.yoffset * xres) + + info->var.xoffset); + par->video = 0; + + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) + par->video |= PM3VideoControl_HSYNC_ACTIVE_HIGH; + else + par->video |= PM3VideoControl_HSYNC_ACTIVE_LOW; + + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) + par->video |= PM3VideoControl_VSYNC_ACTIVE_HIGH; + else + par->video |= PM3VideoControl_VSYNC_ACTIVE_LOW; + + if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) + par->video |= PM3VideoControl_LINE_DOUBLE_ON; + + if ((info->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) + par->video |= PM3VideoControl_ENABLE; + else + DPRINTK("PM3Video disabled\n"); + + switch (bpp) { + case 8: + par->video |= PM3VideoControl_PIXELSIZE_8BIT; + break; + case 16: + par->video |= PM3VideoControl_PIXELSIZE_16BIT; + break; + case 32: + par->video |= PM3VideoControl_PIXELSIZE_32BIT; + break; + default: + DPRINTK("Unsupported depth\n"); + break; + } + + info->fix.visual = + (bpp == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->fix.line_length = ((info->var.xres_virtual + 7) >> 3) * bpp; + +/* pm3fb_clear_memory(info, 0);*/ + pm3fb_clear_colormap(par, 0, 0, 0); + PM3_WRITE_DAC_REG(par, PM3RD_CursorMode, 0); + pm3fb_init_engine(info); + pm3fb_write_mode(info); + return 0; +} + +static int pm3fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct pm3_par *par = info->par; + + if (regno >= 256) /* no. of hw registers */ + return -EINVAL; + + /* grayscale works only partially under directcolor */ + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + if (info->var.grayscale) + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + + /* Directcolor: + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * {hardwarespecific} contains width of DAC + * pseudo_palette[X] is programmed to (X << red.offset) | + * (X << green.offset) | + * (X << blue.offset) + * RAMDAC[X] is programmed to (red, green, blue) + * color depth = SUM(var->{color}.length) + * + * Pseudocolor: + * var->{color}.offset is 0 + * var->{color}.length contains width of DAC or the number + * of unique colors available (color depth) + * pseudo_palette is not used + * RAMDAC[X] is programmed to (red, green, blue) + * color depth = var->{color}.length + */ + + /* + * This is the point where the color is converted to something that + * is acceptable by the hardware. + */ +#define CNVT_TOHW(val, width) ((((val) << (width)) + 0x7FFF - (val)) >> 16) + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); +#undef CNVT_TOHW + + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + u32 v; + + if (regno >= 16) + return -EINVAL; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + switch (info->var.bits_per_pixel) { + case 8: + break; + case 16: + case 32: + ((u32 *)(info->pseudo_palette))[regno] = v; + break; + } + return 0; + } else if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) + pm3fb_set_color(par, regno, red, green, blue); + + return 0; +} + +static int pm3fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct pm3_par *par = info->par; + const u32 xres = (info->var.xres + 31) & ~31; + + par->base = pm3fb_shift_bpp(info->var.bits_per_pixel, + (var->yoffset * xres) + + var->xoffset); + PM3_WAIT(par, 1); + PM3_WRITE_REG(par, PM3ScreenBase, par->base); + return 0; +} + +static int pm3fb_blank(int blank_mode, struct fb_info *info) +{ + struct pm3_par *par = info->par; + u32 video = par->video; + + /* + * Oxygen VX1 - it appears that setting PM3VideoControl and + * then PM3RD_SyncControl to the same SYNC settings undoes + * any net change - they seem to xor together. Only set the + * sync options in PM3RD_SyncControl. --rmk + */ + video &= ~(PM3VideoControl_HSYNC_MASK | + PM3VideoControl_VSYNC_MASK); + video |= PM3VideoControl_HSYNC_ACTIVE_HIGH | + PM3VideoControl_VSYNC_ACTIVE_HIGH; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + video |= PM3VideoControl_ENABLE; + break; + case FB_BLANK_NORMAL: + video &= ~PM3VideoControl_ENABLE; + break; + case FB_BLANK_HSYNC_SUSPEND: + video &= ~(PM3VideoControl_HSYNC_MASK | + PM3VideoControl_BLANK_ACTIVE_LOW); + break; + case FB_BLANK_VSYNC_SUSPEND: + video &= ~(PM3VideoControl_VSYNC_MASK | + PM3VideoControl_BLANK_ACTIVE_LOW); + break; + case FB_BLANK_POWERDOWN: + video &= ~(PM3VideoControl_HSYNC_MASK | + PM3VideoControl_VSYNC_MASK | + PM3VideoControl_BLANK_ACTIVE_LOW); + break; + default: + DPRINTK("Unsupported blanking %d\n", blank_mode); + return 1; + } + + PM3_WAIT(par, 1); + PM3_WRITE_REG(par, PM3VideoControl, video); + return 0; +} + + /* + * Frame buffer operations + */ + +static struct fb_ops pm3fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = pm3fb_check_var, + .fb_set_par = pm3fb_set_par, + .fb_setcolreg = pm3fb_setcolreg, + .fb_pan_display = pm3fb_pan_display, + .fb_fillrect = pm3fb_fillrect, + .fb_copyarea = pm3fb_copyarea, + .fb_imageblit = pm3fb_imageblit, + .fb_blank = pm3fb_blank, + .fb_sync = pm3fb_sync, + .fb_cursor = pm3fb_cursor, +}; + +/* ------------------------------------------------------------------------- */ + + /* + * Initialization + */ + +/* mmio register are already mapped when this function is called */ +/* the pm3fb_fix.smem_start is also set */ +static unsigned long pm3fb_size_memory(struct pm3_par *par) +{ + unsigned long memsize = 0; + unsigned long tempBypass, i, temp1, temp2; + unsigned char __iomem *screen_mem; + + pm3fb_fix.smem_len = 64 * 1024l * 1024; /* request full aperture size */ + /* Linear frame buffer - request region and map it. */ + if (!request_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len, + "pm3fb smem")) { + printk(KERN_WARNING "pm3fb: Can't reserve smem.\n"); + return 0; + } + screen_mem = + ioremap_nocache(pm3fb_fix.smem_start, pm3fb_fix.smem_len); + if (!screen_mem) { + printk(KERN_WARNING "pm3fb: Can't ioremap smem area.\n"); + release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len); + return 0; + } + + /* TODO: card-specific stuff, *before* accessing *any* FB memory */ + /* For Appian Jeronimo 2000 board second head */ + + tempBypass = PM3_READ_REG(par, PM3MemBypassWriteMask); + + DPRINTK("PM3MemBypassWriteMask was: 0x%08lx\n", tempBypass); + + PM3_WAIT(par, 1); + PM3_WRITE_REG(par, PM3MemBypassWriteMask, 0xFFFFFFFF); + + /* pm3 split up memory, replicates, and do a lot of + * nasty stuff IMHO ;-) + */ + for (i = 0; i < 32; i++) { + fb_writel(i * 0x00345678, + (screen_mem + (i * 1048576))); + mb(); + temp1 = fb_readl((screen_mem + (i * 1048576))); + + /* Let's check for wrapover, write will fail at 16MB boundary */ + if (temp1 == (i * 0x00345678)) + memsize = i; + else + break; + } + + DPRINTK("First detect pass already got %ld MB\n", memsize + 1); + + if (memsize + 1 == i) { + for (i = 0; i < 32; i++) { + /* Clear first 32MB ; 0 is 0, no need to byteswap */ + writel(0x0000000, (screen_mem + (i * 1048576))); + } + wmb(); + + for (i = 32; i < 64; i++) { + fb_writel(i * 0x00345678, + (screen_mem + (i * 1048576))); + mb(); + temp1 = + fb_readl((screen_mem + (i * 1048576))); + temp2 = + fb_readl((screen_mem + ((i - 32) * 1048576))); + /* different value, different RAM... */ + if ((temp1 == (i * 0x00345678)) && (temp2 == 0)) + memsize = i; + else + break; + } + } + DPRINTK("Second detect pass got %ld MB\n", memsize + 1); + + PM3_WAIT(par, 1); + PM3_WRITE_REG(par, PM3MemBypassWriteMask, tempBypass); + + iounmap(screen_mem); + release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len); + memsize = 1048576 * (memsize + 1); + + DPRINTK("Returning 0x%08lx bytes\n", memsize); + + return memsize; +} + +static int pm3fb_probe(struct pci_dev *dev, const struct pci_device_id *ent) +{ + struct fb_info *info; + struct pm3_par *par; + struct device *device = &dev->dev; /* for pci drivers */ + int err; + int retval = -ENXIO; + + err = pci_enable_device(dev); + if (err) { + printk(KERN_WARNING "pm3fb: Can't enable PCI dev: %d\n", err); + return err; + } + /* + * Dynamically allocate info and par + */ + info = framebuffer_alloc(sizeof(struct pm3_par), device); + + if (!info) + return -ENOMEM; + par = info->par; + + /* + * Here we set the screen_base to the virtual memory address + * for the framebuffer. + */ + pm3fb_fix.mmio_start = pci_resource_start(dev, 0); + pm3fb_fix.mmio_len = PM3_REGS_SIZE; +#if defined(__BIG_ENDIAN) + pm3fb_fix.mmio_start += PM3_REGS_SIZE; + DPRINTK("Adjusting register base for big-endian.\n"); +#endif + + /* Registers - request region and map it. */ + if (!request_mem_region(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len, + "pm3fb regbase")) { + printk(KERN_WARNING "pm3fb: Can't reserve regbase.\n"); + goto err_exit_neither; + } + par->v_regs = + ioremap_nocache(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len); + if (!par->v_regs) { + printk(KERN_WARNING "pm3fb: Can't remap %s register area.\n", + pm3fb_fix.id); + release_mem_region(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len); + goto err_exit_neither; + } + + /* Linear frame buffer - request region and map it. */ + pm3fb_fix.smem_start = pci_resource_start(dev, 1); + pm3fb_fix.smem_len = pm3fb_size_memory(par); + if (!pm3fb_fix.smem_len) { + printk(KERN_WARNING "pm3fb: Can't find memory on board.\n"); + goto err_exit_mmio; + } + if (!request_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len, + "pm3fb smem")) { + printk(KERN_WARNING "pm3fb: Can't reserve smem.\n"); + goto err_exit_mmio; + } + info->screen_base = + ioremap_nocache(pm3fb_fix.smem_start, pm3fb_fix.smem_len); + if (!info->screen_base) { + printk(KERN_WARNING "pm3fb: Can't ioremap smem area.\n"); + release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len); + goto err_exit_mmio; + } + info->screen_size = pm3fb_fix.smem_len; + +#ifdef CONFIG_MTRR + if (!nomtrr) + par->mtrr_handle = mtrr_add(pm3fb_fix.smem_start, + pm3fb_fix.smem_len, + MTRR_TYPE_WRCOMB, 1); +#endif + info->fbops = &pm3fb_ops; + + par->video = PM3_READ_REG(par, PM3VideoControl); + + info->fix = pm3fb_fix; + info->pseudo_palette = par->palette; + info->flags = FBINFO_DEFAULT | + FBINFO_HWACCEL_XPAN | + FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_HWACCEL_FILLRECT; + + if (noaccel) { + printk(KERN_DEBUG "disabling acceleration\n"); + info->flags |= FBINFO_HWACCEL_DISABLED; + } + info->pixmap.addr = kmalloc(PM3_PIXMAP_SIZE, GFP_KERNEL); + if (!info->pixmap.addr) { + retval = -ENOMEM; + goto err_exit_pixmap; + } + info->pixmap.size = PM3_PIXMAP_SIZE; + info->pixmap.buf_align = 4; + info->pixmap.scan_align = 4; + info->pixmap.access_align = 32; + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + /* + * This should give a reasonable default video mode. The following is + * done when we can set a video mode. + */ + if (!mode_option) + mode_option = "640x480@60"; + + retval = fb_find_mode(&info->var, info, mode_option, NULL, 0, NULL, 8); + + if (!retval || retval == 4) { + retval = -EINVAL; + goto err_exit_both; + } + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + retval = -ENOMEM; + goto err_exit_both; + } + + /* + * For drivers that can... + */ + pm3fb_check_var(&info->var, info); + + if (register_framebuffer(info) < 0) { + retval = -EINVAL; + goto err_exit_all; + } + fb_info(info, "%s frame buffer device\n", info->fix.id); + pci_set_drvdata(dev, info); + return 0; + + err_exit_all: + fb_dealloc_cmap(&info->cmap); + err_exit_both: + kfree(info->pixmap.addr); + err_exit_pixmap: + iounmap(info->screen_base); + release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len); + err_exit_mmio: + iounmap(par->v_regs); + release_mem_region(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len); + err_exit_neither: + framebuffer_release(info); + return retval; +} + + /* + * Cleanup + */ +static void pm3fb_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + + if (info) { + struct fb_fix_screeninfo *fix = &info->fix; + struct pm3_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + +#ifdef CONFIG_MTRR + if (par->mtrr_handle >= 0) + mtrr_del(par->mtrr_handle, info->fix.smem_start, + info->fix.smem_len); +#endif /* CONFIG_MTRR */ + iounmap(info->screen_base); + release_mem_region(fix->smem_start, fix->smem_len); + iounmap(par->v_regs); + release_mem_region(fix->mmio_start, fix->mmio_len); + + kfree(info->pixmap.addr); + framebuffer_release(info); + } +} + +static struct pci_device_id pm3fb_id_table[] = { + { PCI_VENDOR_ID_3DLABS, 0x0a, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +/* For PCI drivers */ +static struct pci_driver pm3fb_driver = { + .name = "pm3fb", + .id_table = pm3fb_id_table, + .probe = pm3fb_probe, + .remove = pm3fb_remove, +}; + +MODULE_DEVICE_TABLE(pci, pm3fb_id_table); + +#ifndef MODULE + /* + * Setup + */ + +/* + * Only necessary if your driver takes special options, + * otherwise we fall back on the generic fb_setup(). + */ +static int __init pm3fb_setup(char *options) +{ + char *this_opt; + + /* Parse user specified options (`video=pm3fb:') */ + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + else if (!strncmp(this_opt, "noaccel", 7)) + noaccel = 1; + else if (!strncmp(this_opt, "hwcursor=", 9)) + hwcursor = simple_strtoul(this_opt + 9, NULL, 0); +#ifdef CONFIG_MTRR + else if (!strncmp(this_opt, "nomtrr", 6)) + nomtrr = 1; +#endif + else + mode_option = this_opt; + } + return 0; +} +#endif /* MODULE */ + +static int __init pm3fb_init(void) +{ + /* + * For kernel boot options (in 'video=pm3fb:<options>' format) + */ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("pm3fb", &option)) + return -ENODEV; + pm3fb_setup(option); +#endif + + return pci_register_driver(&pm3fb_driver); +} + +#ifdef MODULE +static void __exit pm3fb_exit(void) +{ + pci_unregister_driver(&pm3fb_driver); +} + +module_exit(pm3fb_exit); +#endif +module_init(pm3fb_init); + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Initial video mode e.g. '648x480-8@60'"); +module_param(noaccel, bool, 0); +MODULE_PARM_DESC(noaccel, "Disable acceleration"); +module_param(hwcursor, int, 0644); +MODULE_PARM_DESC(hwcursor, "Enable hardware cursor " + "(1=enable, 0=disable, default=1)"); +#ifdef CONFIG_MTRR +module_param(nomtrr, bool, 0); +MODULE_PARM_DESC(nomtrr, "Disable MTRR support (0 or 1=disabled) (default=0)"); +#endif + +MODULE_DESCRIPTION("Permedia3 framebuffer device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/pmag-aa-fb.c b/drivers/video/fbdev/pmag-aa-fb.c new file mode 100644 index 000000000000..838424817de2 --- /dev/null +++ b/drivers/video/fbdev/pmag-aa-fb.c @@ -0,0 +1,510 @@ +/* + * linux/drivers/video/pmag-aa-fb.c + * Copyright 2002 Karsten Merker <merker@debian.org> + * + * PMAG-AA TurboChannel framebuffer card support ... derived from + * pmag-ba-fb.c, which is Copyright (C) 1999, 2000, 2001 by + * Michael Engel <engel@unix-ag.org>, Karsten Merker <merker@debian.org> + * and Harald Koerfgen <hkoerfg@web.de>, which itself is derived from + * "HP300 Topcat framebuffer support (derived from macfb of all things) + * Phil Blundell <philb@gnu.org> 1998" + * + * 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. + * + * 2002-09-28 Karsten Merker <merker@linuxtag.org> + * Version 0.01: First try to get a PMAG-AA running. + * + * 2003-02-24 Thiemo Seufer <seufer@csv.ica.uni-stuttgart.de> + * Version 0.02: Major code cleanup. + * + * 2003-09-21 Thiemo Seufer <seufer@csv.ica.uni-stuttgart.de> + * Hardware cursor support. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/console.h> + +#include <asm/bootinfo.h> +#include <asm/dec/machtype.h> +#include <asm/dec/tc.h> + +#include <video/fbcon.h> +#include <video/fbcon-cfb8.h> + +#include "bt455.h" +#include "bt431.h" + +/* Version information */ +#define DRIVER_VERSION "0.02" +#define DRIVER_AUTHOR "Karsten Merker <merker@linuxtag.org>" +#define DRIVER_DESCRIPTION "PMAG-AA Framebuffer Driver" + +/* Prototypes */ +static int aafb_set_var(struct fb_var_screeninfo *var, int con, + struct fb_info *info); + +/* + * Bt455 RAM DAC register base offset (rel. to TC slot base address). + */ +#define PMAG_AA_BT455_OFFSET 0x100000 + +/* + * Bt431 cursor generator offset (rel. to TC slot base address). + */ +#define PMAG_AA_BT431_OFFSET 0x180000 + +/* + * Begin of PMAG-AA framebuffer memory relative to TC slot address, + * resolution is 1280x1024x1 (8 bits deep, but only LSB is used). + */ +#define PMAG_AA_ONBOARD_FBMEM_OFFSET 0x200000 + +struct aafb_cursor { + struct timer_list timer; + int enable; + int on; + int vbl_cnt; + int blink_rate; + u16 x, y, width, height; +}; + +#define CURSOR_TIMER_FREQ (HZ / 50) +#define CURSOR_BLINK_RATE (20) +#define CURSOR_DRAW_DELAY (2) + +struct aafb_info { + struct fb_info info; + struct display disp; + struct aafb_cursor cursor; + struct bt455_regs *bt455; + struct bt431_regs *bt431; + unsigned long fb_start; + unsigned long fb_size; + unsigned long fb_line_length; +}; + +/* + * Max 3 TURBOchannel slots -> max 3 PMAG-AA. + */ +static struct aafb_info my_fb_info[3]; + +static struct aafb_par { +} current_par; + +static int currcon = -1; + +static void aafb_set_cursor(struct aafb_info *info, int on) +{ + struct aafb_cursor *c = &info->cursor; + + if (on) { + bt431_position_cursor(info->bt431, c->x, c->y); + bt431_enable_cursor(info->bt431); + } else + bt431_erase_cursor(info->bt431); +} + +static void aafbcon_cursor(struct display *disp, int mode, int x, int y) +{ + struct aafb_info *info = (struct aafb_info *)disp->fb_info; + struct aafb_cursor *c = &info->cursor; + + x *= fontwidth(disp); + y *= fontheight(disp); + + if (c->x == x && c->y == y && (mode == CM_ERASE) == !c->enable) + return; + + c->enable = 0; + if (c->on) + aafb_set_cursor(info, 0); + c->x = x - disp->var.xoffset; + c->y = y - disp->var.yoffset; + + switch (mode) { + case CM_ERASE: + c->on = 0; + break; + case CM_DRAW: + case CM_MOVE: + if (c->on) + aafb_set_cursor(info, c->on); + else + c->vbl_cnt = CURSOR_DRAW_DELAY; + c->enable = 1; + break; + } +} + +static int aafbcon_set_font(struct display *disp, int width, int height) +{ + struct aafb_info *info = (struct aafb_info *)disp->fb_info; + struct aafb_cursor *c = &info->cursor; + u8 fgc = ~attr_bgcol_ec(disp, disp->conp, &info->info); + + if (width > 64 || height > 64 || width < 0 || height < 0) + return -EINVAL; + + c->height = height; + c->width = width; + + bt431_set_font(info->bt431, fgc, width, height); + + return 1; +} + +static void aafb_cursor_timer_handler(unsigned long data) +{ + struct aafb_info *info = (struct aafb_info *)data; + struct aafb_cursor *c = &info->cursor; + + if (!c->enable) + goto out; + + if (c->vbl_cnt && --c->vbl_cnt == 0) { + c->on ^= 1; + aafb_set_cursor(info, c->on); + c->vbl_cnt = c->blink_rate; + } + +out: + c->timer.expires = jiffies + CURSOR_TIMER_FREQ; + add_timer(&c->timer); +} + +static void __init aafb_cursor_init(struct aafb_info *info) +{ + struct aafb_cursor *c = &info->cursor; + + c->enable = 1; + c->on = 1; + c->x = c->y = 0; + c->width = c->height = 0; + c->vbl_cnt = CURSOR_DRAW_DELAY; + c->blink_rate = CURSOR_BLINK_RATE; + + init_timer(&c->timer); + c->timer.data = (unsigned long)info; + c->timer.function = aafb_cursor_timer_handler; + mod_timer(&c->timer, jiffies + CURSOR_TIMER_FREQ); +} + +static void __exit aafb_cursor_exit(struct aafb_info *info) +{ + struct aafb_cursor *c = &info->cursor; + + del_timer_sync(&c->timer); +} + +static struct display_switch aafb_switch8 = { + .setup = fbcon_cfb8_setup, + .bmove = fbcon_cfb8_bmove, + .clear = fbcon_cfb8_clear, + .putc = fbcon_cfb8_putc, + .putcs = fbcon_cfb8_putcs, + .revc = fbcon_cfb8_revc, + .cursor = aafbcon_cursor, + .set_font = aafbcon_set_font, + .clear_margins = fbcon_cfb8_clear_margins, + .fontwidthmask = FONTWIDTH(4)|FONTWIDTH(8)|FONTWIDTH(12)|FONTWIDTH(16) +}; + +static void aafb_get_par(struct aafb_par *par) +{ + *par = current_par; +} + +static int aafb_get_fix(struct fb_fix_screeninfo *fix, int con, + struct fb_info *info) +{ + struct aafb_info *ip = (struct aafb_info *)info; + + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + strcpy(fix->id, "PMAG-AA"); + fix->smem_start = ip->fb_start; + fix->smem_len = ip->fb_size; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->ypanstep = 1; + fix->ywrapstep = 1; + fix->visual = FB_VISUAL_MONO10; + fix->line_length = 1280; + fix->accel = FB_ACCEL_NONE; + + return 0; +} + +static void aafb_set_disp(struct display *disp, int con, + struct aafb_info *info) +{ + struct fb_fix_screeninfo fix; + + disp->fb_info = &info->info; + aafb_set_var(&disp->var, con, &info->info); + if (disp->conp && disp->conp->vc_sw && disp->conp->vc_sw->con_cursor) + disp->conp->vc_sw->con_cursor(disp->conp, CM_ERASE); + disp->dispsw = &aafb_switch8; + disp->dispsw_data = 0; + + aafb_get_fix(&fix, con, &info->info); + disp->screen_base = (u8 *) fix.smem_start; + disp->visual = fix.visual; + disp->type = fix.type; + disp->type_aux = fix.type_aux; + disp->ypanstep = fix.ypanstep; + disp->ywrapstep = fix.ywrapstep; + disp->line_length = fix.line_length; + disp->next_line = 2048; + disp->can_soft_blank = 1; + disp->inverse = 0; + disp->scrollmode = SCROLL_YREDRAW; + + aafbcon_set_font(disp, fontwidth(disp), fontheight(disp)); +} + +static int aafb_get_cmap(struct fb_cmap *cmap, int kspc, int con, + struct fb_info *info) +{ + static u16 color[2] = {0x0000, 0x000f}; + static struct fb_cmap aafb_cmap = {0, 2, color, color, color, NULL}; + + fb_copy_cmap(&aafb_cmap, cmap, kspc ? 0 : 2); + return 0; +} + +static int aafb_set_cmap(struct fb_cmap *cmap, int kspc, int con, + struct fb_info *info) +{ + u16 color[2] = {0x0000, 0x000f}; + + if (cmap->start == 0 + && cmap->len == 2 + && memcmp(cmap->red, color, sizeof(color)) == 0 + && memcmp(cmap->green, color, sizeof(color)) == 0 + && memcmp(cmap->blue, color, sizeof(color)) == 0 + && cmap->transp == NULL) + return 0; + else + return -EINVAL; +} + +static int aafb_ioctl(struct fb_info *info, u32 cmd, unsigned long arg) +{ + /* TODO: Not yet implemented */ + return -ENOIOCTLCMD; +} + +static int aafb_switch(int con, struct fb_info *info) +{ + struct aafb_info *ip = (struct aafb_info *)info; + struct display *old = (currcon < 0) ? &ip->disp : (fb_display + currcon); + struct display *new = (con < 0) ? &ip->disp : (fb_display + con); + + if (old->conp && old->conp->vc_sw && old->conp->vc_sw->con_cursor) + old->conp->vc_sw->con_cursor(old->conp, CM_ERASE); + + /* Set the current console. */ + currcon = con; + aafb_set_disp(new, con, ip); + + return 0; +} + +static void aafb_encode_var(struct fb_var_screeninfo *var, + struct aafb_par *par) +{ + var->xres = 1280; + var->yres = 1024; + var->xres_virtual = 2048; + var->yres_virtual = 1024; + var->xoffset = 0; + var->yoffset = 0; + var->bits_per_pixel = 8; + var->grayscale = 1; + var->red.offset = 0; + var->red.length = 0; + var->red.msb_right = 0; + var->green.offset = 0; + var->green.length = 1; + var->green.msb_right = 0; + var->blue.offset = 0; + var->blue.length = 0; + var->blue.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->nonstd = 0; + var->activate &= ~FB_ACTIVATE_MASK & FB_ACTIVATE_NOW; + var->accel_flags = 0; + var->sync = FB_SYNC_ON_GREEN; + var->vmode &= ~FB_VMODE_MASK & FB_VMODE_NONINTERLACED; +} + +static int aafb_get_var(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + if (con < 0) { + struct aafb_par par; + + memset(var, 0, sizeof(struct fb_var_screeninfo)); + aafb_get_par(&par); + aafb_encode_var(var, &par); + } else + *var = info->var; + + return 0; +} + +static int aafb_set_var(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + struct aafb_par par; + + aafb_get_par(&par); + aafb_encode_var(var, &par); + info->var = *var; + + return 0; +} + +static int aafb_update_var(int con, struct fb_info *info) +{ + struct aafb_info *ip = (struct aafb_info *)info; + struct display *disp = (con < 0) ? &ip->disp : (fb_display + con); + + if (con == currcon) + aafbcon_cursor(disp, CM_ERASE, ip->cursor.x, ip->cursor.y); + + return 0; +} + +/* 0 unblanks, any other blanks. */ + +static void aafb_blank(int blank, struct fb_info *info) +{ + struct aafb_info *ip = (struct aafb_info *)info; + u8 val = blank ? 0x00 : 0x0f; + + bt455_write_cmap_entry(ip->bt455, 1, val, val, val); + aafbcon_cursor(&ip->disp, CM_ERASE, ip->cursor.x, ip->cursor.y); +} + +static struct fb_ops aafb_ops = { + .owner = THIS_MODULE, + .fb_get_fix = aafb_get_fix, + .fb_get_var = aafb_get_var, + .fb_set_var = aafb_set_var, + .fb_get_cmap = aafb_get_cmap, + .fb_set_cmap = aafb_set_cmap, + .fb_ioctl = aafb_ioctl +}; + +static int __init init_one(int slot) +{ + unsigned long base_addr = CKSEG1ADDR(get_tc_base_addr(slot)); + struct aafb_info *ip = &my_fb_info[slot]; + + memset(ip, 0, sizeof(struct aafb_info)); + + /* + * Framebuffer display memory base address and friends. + */ + ip->bt455 = (struct bt455_regs *) (base_addr + PMAG_AA_BT455_OFFSET); + ip->bt431 = (struct bt431_regs *) (base_addr + PMAG_AA_BT431_OFFSET); + ip->fb_start = base_addr + PMAG_AA_ONBOARD_FBMEM_OFFSET; + ip->fb_size = 2048 * 1024; /* fb_fix_screeninfo.smem_length + seems to be physical */ + ip->fb_line_length = 2048; + + /* + * Let there be consoles.. + */ + strcpy(ip->info.modename, "PMAG-AA"); + ip->info.node = -1; + ip->info.flags = FBINFO_FLAG_DEFAULT; + ip->info.fbops = &aafb_ops; + ip->info.disp = &ip->disp; + ip->info.changevar = NULL; + ip->info.switch_con = &aafb_switch; + ip->info.updatevar = &aafb_update_var; + ip->info.blank = &aafb_blank; + + aafb_set_disp(&ip->disp, currcon, ip); + + /* + * Configure the RAM DACs. + */ + bt455_erase_cursor(ip->bt455); + + /* Init colormap. */ + bt455_write_cmap_entry(ip->bt455, 0, 0x00, 0x00, 0x00); + bt455_write_cmap_entry(ip->bt455, 1, 0x0f, 0x0f, 0x0f); + + /* Init hardware cursor. */ + bt431_init_cursor(ip->bt431); + aafb_cursor_init(ip); + + /* Clear the screen. */ + memset ((void *)ip->fb_start, 0, ip->fb_size); + + if (register_framebuffer(&ip->info) < 0) + return -EINVAL; + + printk(KERN_INFO "fb%d: %s frame buffer in TC slot %d\n", + GET_FB_IDX(ip->info.node), ip->info.modename, slot); + + return 0; +} + +static int __exit exit_one(int slot) +{ + struct aafb_info *ip = &my_fb_info[slot]; + + if (unregister_framebuffer(&ip->info) < 0) + return -EINVAL; + + return 0; +} + +/* + * Initialise the framebuffer. + */ +int __init pmagaafb_init(void) +{ + int sid; + int found = 0; + + while ((sid = search_tc_card("PMAG-AA")) >= 0) { + found = 1; + claim_tc_card(sid); + init_one(sid); + } + + return found ? 0 : -ENXIO; +} + +static void __exit pmagaafb_exit(void) +{ + int sid; + + while ((sid = search_tc_card("PMAG-AA")) >= 0) { + exit_one(sid); + release_tc_card(sid); + } +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESCRIPTION); +MODULE_LICENSE("GPL"); +#ifdef MODULE +module_init(pmagaafb_init); +module_exit(pmagaafb_exit); +#endif diff --git a/drivers/video/fbdev/pmag-ba-fb.c b/drivers/video/fbdev/pmag-ba-fb.c new file mode 100644 index 000000000000..914a52ba8477 --- /dev/null +++ b/drivers/video/fbdev/pmag-ba-fb.c @@ -0,0 +1,295 @@ +/* + * linux/drivers/video/pmag-ba-fb.c + * + * PMAG-BA TURBOchannel Color Frame Buffer (CFB) card support, + * derived from: + * "HP300 Topcat framebuffer support (derived from macfb of all things) + * Phil Blundell <philb@gnu.org> 1998", the original code can be + * found in the file hpfb.c in the same directory. + * + * Based on digital document: + * "PMAG-BA TURBOchannel Color Frame Buffer + * Functional Specification", Revision 1.2, August 27, 1990 + * + * DECstation related code Copyright (C) 1999, 2000, 2001 by + * Michael Engel <engel@unix-ag.org>, + * Karsten Merker <merker@linuxtag.org> and + * Harald Koerfgen. + * Copyright (c) 2005, 2006 Maciej W. Rozycki + * Copyright (c) 2005 James Simmons + * + * 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/compiler.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/tc.h> +#include <linux/types.h> + +#include <asm/io.h> + +#include <video/pmag-ba-fb.h> + + +struct pmagbafb_par { + volatile void __iomem *mmio; + volatile u32 __iomem *dac; +}; + + +static struct fb_var_screeninfo pmagbafb_defined = { + .xres = 1024, + .yres = 864, + .xres_virtual = 1024, + .yres_virtual = 864, + .bits_per_pixel = 8, + .red.length = 8, + .green.length = 8, + .blue.length = 8, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .accel_flags = FB_ACCEL_NONE, + .pixclock = 14452, + .left_margin = 116, + .right_margin = 12, + .upper_margin = 34, + .lower_margin = 12, + .hsync_len = 128, + .vsync_len = 3, + .sync = FB_SYNC_ON_GREEN, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo pmagbafb_fix = { + .id = "PMAG-BA", + .smem_len = (1024 * 1024), + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .line_length = 1024, + .mmio_len = PMAG_BA_SIZE - PMAG_BA_BT459, +}; + + +static inline void dac_write(struct pmagbafb_par *par, unsigned int reg, u8 v) +{ + writeb(v, par->dac + reg / 4); +} + +static inline u8 dac_read(struct pmagbafb_par *par, unsigned int reg) +{ + return readb(par->dac + reg / 4); +} + + +/* + * Set the palette. + */ +static int pmagbafb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct pmagbafb_par *par = info->par; + + if (regno >= info->cmap.len) + return 1; + + red >>= 8; /* The cmap fields are 16 bits */ + green >>= 8; /* wide, but the hardware colormap */ + blue >>= 8; /* registers are only 8 bits wide */ + + mb(); + dac_write(par, BT459_ADDR_LO, regno); + dac_write(par, BT459_ADDR_HI, 0x00); + wmb(); + dac_write(par, BT459_CMAP, red); + wmb(); + dac_write(par, BT459_CMAP, green); + wmb(); + dac_write(par, BT459_CMAP, blue); + + return 0; +} + +static struct fb_ops pmagbafb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = pmagbafb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + + +/* + * Turn the hardware cursor off. + */ +static void __init pmagbafb_erase_cursor(struct fb_info *info) +{ + struct pmagbafb_par *par = info->par; + + mb(); + dac_write(par, BT459_ADDR_LO, 0x00); + dac_write(par, BT459_ADDR_HI, 0x03); + wmb(); + dac_write(par, BT459_DATA, 0x00); +} + + +static int pmagbafb_probe(struct device *dev) +{ + struct tc_dev *tdev = to_tc_dev(dev); + resource_size_t start, len; + struct fb_info *info; + struct pmagbafb_par *par; + int err; + + info = framebuffer_alloc(sizeof(struct pmagbafb_par), dev); + if (!info) { + printk(KERN_ERR "%s: Cannot allocate memory\n", dev_name(dev)); + return -ENOMEM; + } + + par = info->par; + dev_set_drvdata(dev, info); + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + printk(KERN_ERR "%s: Cannot allocate color map\n", + dev_name(dev)); + err = -ENOMEM; + goto err_alloc; + } + + info->fbops = &pmagbafb_ops; + info->fix = pmagbafb_fix; + info->var = pmagbafb_defined; + info->flags = FBINFO_DEFAULT; + + /* Request the I/O MEM resource. */ + start = tdev->resource.start; + len = tdev->resource.end - start + 1; + if (!request_mem_region(start, len, dev_name(dev))) { + printk(KERN_ERR "%s: Cannot reserve FB region\n", + dev_name(dev)); + err = -EBUSY; + goto err_cmap; + } + + /* MMIO mapping setup. */ + info->fix.mmio_start = start; + par->mmio = ioremap_nocache(info->fix.mmio_start, info->fix.mmio_len); + if (!par->mmio) { + printk(KERN_ERR "%s: Cannot map MMIO\n", dev_name(dev)); + err = -ENOMEM; + goto err_resource; + } + par->dac = par->mmio + PMAG_BA_BT459; + + /* Frame buffer mapping setup. */ + info->fix.smem_start = start + PMAG_BA_FBMEM; + info->screen_base = ioremap_nocache(info->fix.smem_start, + info->fix.smem_len); + if (!info->screen_base) { + printk(KERN_ERR "%s: Cannot map FB\n", dev_name(dev)); + err = -ENOMEM; + goto err_mmio_map; + } + info->screen_size = info->fix.smem_len; + + pmagbafb_erase_cursor(info); + + err = register_framebuffer(info); + if (err < 0) { + printk(KERN_ERR "%s: Cannot register framebuffer\n", + dev_name(dev)); + goto err_smem_map; + } + + get_device(dev); + + fb_info(info, "%s frame buffer device at %s\n", + info->fix.id, dev_name(dev)); + + return 0; + + +err_smem_map: + iounmap(info->screen_base); + +err_mmio_map: + iounmap(par->mmio); + +err_resource: + release_mem_region(start, len); + +err_cmap: + fb_dealloc_cmap(&info->cmap); + +err_alloc: + framebuffer_release(info); + return err; +} + +static int __exit pmagbafb_remove(struct device *dev) +{ + struct tc_dev *tdev = to_tc_dev(dev); + struct fb_info *info = dev_get_drvdata(dev); + struct pmagbafb_par *par = info->par; + resource_size_t start, len; + + put_device(dev); + unregister_framebuffer(info); + iounmap(info->screen_base); + iounmap(par->mmio); + start = tdev->resource.start; + len = tdev->resource.end - start + 1; + release_mem_region(start, len); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + return 0; +} + + +/* + * Initialize the framebuffer. + */ +static const struct tc_device_id pmagbafb_tc_table[] = { + { "DEC ", "PMAG-BA " }, + { } +}; +MODULE_DEVICE_TABLE(tc, pmagbafb_tc_table); + +static struct tc_driver pmagbafb_driver = { + .id_table = pmagbafb_tc_table, + .driver = { + .name = "pmagbafb", + .bus = &tc_bus_type, + .probe = pmagbafb_probe, + .remove = __exit_p(pmagbafb_remove), + }, +}; + +static int __init pmagbafb_init(void) +{ +#ifndef MODULE + if (fb_get_options("pmagbafb", NULL)) + return -ENXIO; +#endif + return tc_register_driver(&pmagbafb_driver); +} + +static void __exit pmagbafb_exit(void) +{ + tc_unregister_driver(&pmagbafb_driver); +} + + +module_init(pmagbafb_init); +module_exit(pmagbafb_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/pmagb-b-fb.c b/drivers/video/fbdev/pmagb-b-fb.c new file mode 100644 index 000000000000..0822b6f8dddc --- /dev/null +++ b/drivers/video/fbdev/pmagb-b-fb.c @@ -0,0 +1,413 @@ +/* + * linux/drivers/video/pmagb-b-fb.c + * + * PMAGB-B TURBOchannel Smart Frame Buffer (SFB) card support, + * derived from: + * "HP300 Topcat framebuffer support (derived from macfb of all things) + * Phil Blundell <philb@gnu.org> 1998", the original code can be + * found in the file hpfb.c in the same directory. + * + * DECstation related code Copyright (C) 1999, 2000, 2001 by + * Michael Engel <engel@unix-ag.org>, + * Karsten Merker <merker@linuxtag.org> and + * Harald Koerfgen. + * Copyright (c) 2005, 2006 Maciej W. Rozycki + * + * 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/compiler.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/tc.h> +#include <linux/types.h> + +#include <asm/io.h> + +#include <video/pmagb-b-fb.h> + + +struct pmagbbfb_par { + volatile void __iomem *mmio; + volatile void __iomem *smem; + volatile u32 __iomem *sfb; + volatile u32 __iomem *dac; + unsigned int osc0; + unsigned int osc1; + int slot; +}; + + +static struct fb_var_screeninfo pmagbbfb_defined = { + .bits_per_pixel = 8, + .red.length = 8, + .green.length = 8, + .blue.length = 8, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .accel_flags = FB_ACCEL_NONE, + .sync = FB_SYNC_ON_GREEN, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo pmagbbfb_fix = { + .id = "PMAGB-BA", + .smem_len = (2048 * 1024), + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .mmio_len = PMAGB_B_FBMEM, +}; + + +static inline void sfb_write(struct pmagbbfb_par *par, unsigned int reg, u32 v) +{ + writel(v, par->sfb + reg / 4); +} + +static inline u32 sfb_read(struct pmagbbfb_par *par, unsigned int reg) +{ + return readl(par->sfb + reg / 4); +} + +static inline void dac_write(struct pmagbbfb_par *par, unsigned int reg, u8 v) +{ + writeb(v, par->dac + reg / 4); +} + +static inline u8 dac_read(struct pmagbbfb_par *par, unsigned int reg) +{ + return readb(par->dac + reg / 4); +} + +static inline void gp0_write(struct pmagbbfb_par *par, u32 v) +{ + writel(v, par->mmio + PMAGB_B_GP0); +} + + +/* + * Set the palette. + */ +static int pmagbbfb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct pmagbbfb_par *par = info->par; + + if (regno >= info->cmap.len) + return 1; + + red >>= 8; /* The cmap fields are 16 bits */ + green >>= 8; /* wide, but the hardware colormap */ + blue >>= 8; /* registers are only 8 bits wide */ + + mb(); + dac_write(par, BT459_ADDR_LO, regno); + dac_write(par, BT459_ADDR_HI, 0x00); + wmb(); + dac_write(par, BT459_CMAP, red); + wmb(); + dac_write(par, BT459_CMAP, green); + wmb(); + dac_write(par, BT459_CMAP, blue); + + return 0; +} + +static struct fb_ops pmagbbfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = pmagbbfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + + +/* + * Turn the hardware cursor off. + */ +static void __init pmagbbfb_erase_cursor(struct fb_info *info) +{ + struct pmagbbfb_par *par = info->par; + + mb(); + dac_write(par, BT459_ADDR_LO, 0x00); + dac_write(par, BT459_ADDR_HI, 0x03); + wmb(); + dac_write(par, BT459_DATA, 0x00); +} + +/* + * Set up screen parameters. + */ +static void pmagbbfb_screen_setup(struct fb_info *info) +{ + struct pmagbbfb_par *par = info->par; + + info->var.xres = ((sfb_read(par, SFB_REG_VID_HOR) >> + SFB_VID_HOR_PIX_SHIFT) & SFB_VID_HOR_PIX_MASK) * 4; + info->var.xres_virtual = info->var.xres; + info->var.yres = (sfb_read(par, SFB_REG_VID_VER) >> + SFB_VID_VER_SL_SHIFT) & SFB_VID_VER_SL_MASK; + info->var.yres_virtual = info->var.yres; + info->var.left_margin = ((sfb_read(par, SFB_REG_VID_HOR) >> + SFB_VID_HOR_BP_SHIFT) & + SFB_VID_HOR_BP_MASK) * 4; + info->var.right_margin = ((sfb_read(par, SFB_REG_VID_HOR) >> + SFB_VID_HOR_FP_SHIFT) & + SFB_VID_HOR_FP_MASK) * 4; + info->var.upper_margin = (sfb_read(par, SFB_REG_VID_VER) >> + SFB_VID_VER_BP_SHIFT) & SFB_VID_VER_BP_MASK; + info->var.lower_margin = (sfb_read(par, SFB_REG_VID_VER) >> + SFB_VID_VER_FP_SHIFT) & SFB_VID_VER_FP_MASK; + info->var.hsync_len = ((sfb_read(par, SFB_REG_VID_HOR) >> + SFB_VID_HOR_SYN_SHIFT) & + SFB_VID_HOR_SYN_MASK) * 4; + info->var.vsync_len = (sfb_read(par, SFB_REG_VID_VER) >> + SFB_VID_VER_SYN_SHIFT) & SFB_VID_VER_SYN_MASK; + + info->fix.line_length = info->var.xres; +}; + +/* + * Determine oscillator configuration. + */ +static void pmagbbfb_osc_setup(struct fb_info *info) +{ + static unsigned int pmagbbfb_freqs[] = { + 130808, 119843, 104000, 92980, 74370, 72800, + 69197, 66000, 65000, 50350, 36000, 32000, 25175 + }; + struct pmagbbfb_par *par = info->par; + struct tc_bus *tbus = to_tc_dev(info->device)->bus; + u32 count0 = 8, count1 = 8, counttc = 16 * 256 + 8; + u32 freq0, freq1, freqtc = tc_get_speed(tbus) / 250; + int i, j; + + gp0_write(par, 0); /* select Osc0 */ + for (j = 0; j < 16; j++) { + mb(); + sfb_write(par, SFB_REG_TCCLK_COUNT, 0); + mb(); + for (i = 0; i < 100; i++) { /* nominally max. 20.5us */ + if (sfb_read(par, SFB_REG_TCCLK_COUNT) == 0) + break; + udelay(1); + } + count0 += sfb_read(par, SFB_REG_VIDCLK_COUNT); + } + + gp0_write(par, 1); /* select Osc1 */ + for (j = 0; j < 16; j++) { + mb(); + sfb_write(par, SFB_REG_TCCLK_COUNT, 0); + + for (i = 0; i < 100; i++) { /* nominally max. 20.5us */ + if (sfb_read(par, SFB_REG_TCCLK_COUNT) == 0) + break; + udelay(1); + } + count1 += sfb_read(par, SFB_REG_VIDCLK_COUNT); + } + + freq0 = (freqtc * count0 + counttc / 2) / counttc; + par->osc0 = freq0; + if (freq0 >= pmagbbfb_freqs[0] - (pmagbbfb_freqs[0] + 32) / 64 && + freq0 <= pmagbbfb_freqs[0] + (pmagbbfb_freqs[0] + 32) / 64) + par->osc0 = pmagbbfb_freqs[0]; + + freq1 = (par->osc0 * count1 + count0 / 2) / count0; + par->osc1 = freq1; + for (i = 0; i < ARRAY_SIZE(pmagbbfb_freqs); i++) + if (freq1 >= pmagbbfb_freqs[i] - + (pmagbbfb_freqs[i] + 128) / 256 && + freq1 <= pmagbbfb_freqs[i] + + (pmagbbfb_freqs[i] + 128) / 256) { + par->osc1 = pmagbbfb_freqs[i]; + break; + } + + if (par->osc0 - par->osc1 <= (par->osc0 + par->osc1 + 256) / 512 || + par->osc1 - par->osc0 <= (par->osc0 + par->osc1 + 256) / 512) + par->osc1 = 0; + + gp0_write(par, par->osc1 != 0); /* reselect OscX */ + + info->var.pixclock = par->osc1 ? + (1000000000 + par->osc1 / 2) / par->osc1 : + (1000000000 + par->osc0 / 2) / par->osc0; +}; + + +static int pmagbbfb_probe(struct device *dev) +{ + struct tc_dev *tdev = to_tc_dev(dev); + resource_size_t start, len; + struct fb_info *info; + struct pmagbbfb_par *par; + char freq0[12], freq1[12]; + u32 vid_base; + int err; + + info = framebuffer_alloc(sizeof(struct pmagbbfb_par), dev); + if (!info) { + printk(KERN_ERR "%s: Cannot allocate memory\n", dev_name(dev)); + return -ENOMEM; + } + + par = info->par; + dev_set_drvdata(dev, info); + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + printk(KERN_ERR "%s: Cannot allocate color map\n", + dev_name(dev)); + err = -ENOMEM; + goto err_alloc; + } + + info->fbops = &pmagbbfb_ops; + info->fix = pmagbbfb_fix; + info->var = pmagbbfb_defined; + info->flags = FBINFO_DEFAULT; + + /* Request the I/O MEM resource. */ + start = tdev->resource.start; + len = tdev->resource.end - start + 1; + if (!request_mem_region(start, len, dev_name(dev))) { + printk(KERN_ERR "%s: Cannot reserve FB region\n", + dev_name(dev)); + err = -EBUSY; + goto err_cmap; + } + + /* MMIO mapping setup. */ + info->fix.mmio_start = start; + par->mmio = ioremap_nocache(info->fix.mmio_start, info->fix.mmio_len); + if (!par->mmio) { + printk(KERN_ERR "%s: Cannot map MMIO\n", dev_name(dev)); + err = -ENOMEM; + goto err_resource; + } + par->sfb = par->mmio + PMAGB_B_SFB; + par->dac = par->mmio + PMAGB_B_BT459; + + /* Frame buffer mapping setup. */ + info->fix.smem_start = start + PMAGB_B_FBMEM; + par->smem = ioremap_nocache(info->fix.smem_start, info->fix.smem_len); + if (!par->smem) { + printk(KERN_ERR "%s: Cannot map FB\n", dev_name(dev)); + err = -ENOMEM; + goto err_mmio_map; + } + vid_base = sfb_read(par, SFB_REG_VID_BASE); + info->screen_base = (void __iomem *)par->smem + vid_base * 0x1000; + info->screen_size = info->fix.smem_len - 2 * vid_base * 0x1000; + + pmagbbfb_erase_cursor(info); + pmagbbfb_screen_setup(info); + pmagbbfb_osc_setup(info); + + err = register_framebuffer(info); + if (err < 0) { + printk(KERN_ERR "%s: Cannot register framebuffer\n", + dev_name(dev)); + goto err_smem_map; + } + + get_device(dev); + + snprintf(freq0, sizeof(freq0), "%u.%03uMHz", + par->osc0 / 1000, par->osc0 % 1000); + snprintf(freq1, sizeof(freq1), "%u.%03uMHz", + par->osc1 / 1000, par->osc1 % 1000); + + fb_info(info, "%s frame buffer device at %s\n", + info->fix.id, dev_name(dev)); + fb_info(info, "Osc0: %s, Osc1: %s, Osc%u selected\n", + freq0, par->osc1 ? freq1 : "disabled", par->osc1 != 0); + + return 0; + + +err_smem_map: + iounmap(par->smem); + +err_mmio_map: + iounmap(par->mmio); + +err_resource: + release_mem_region(start, len); + +err_cmap: + fb_dealloc_cmap(&info->cmap); + +err_alloc: + framebuffer_release(info); + return err; +} + +static int __exit pmagbbfb_remove(struct device *dev) +{ + struct tc_dev *tdev = to_tc_dev(dev); + struct fb_info *info = dev_get_drvdata(dev); + struct pmagbbfb_par *par = info->par; + resource_size_t start, len; + + put_device(dev); + unregister_framebuffer(info); + iounmap(par->smem); + iounmap(par->mmio); + start = tdev->resource.start; + len = tdev->resource.end - start + 1; + release_mem_region(start, len); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + return 0; +} + + +/* + * Initialize the framebuffer. + */ +static const struct tc_device_id pmagbbfb_tc_table[] = { + { "DEC ", "PMAGB-BA" }, + { } +}; +MODULE_DEVICE_TABLE(tc, pmagbbfb_tc_table); + +static struct tc_driver pmagbbfb_driver = { + .id_table = pmagbbfb_tc_table, + .driver = { + .name = "pmagbbfb", + .bus = &tc_bus_type, + .probe = pmagbbfb_probe, + .remove = __exit_p(pmagbbfb_remove), + }, +}; + +static int __init pmagbbfb_init(void) +{ +#ifndef MODULE + if (fb_get_options("pmagbbfb", NULL)) + return -ENXIO; +#endif + return tc_register_driver(&pmagbbfb_driver); +} + +static void __exit pmagbbfb_exit(void) +{ + tc_unregister_driver(&pmagbbfb_driver); +} + + +module_init(pmagbbfb_init); +module_exit(pmagbbfb_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/ps3fb.c b/drivers/video/fbdev/ps3fb.c new file mode 100644 index 000000000000..b269abd932aa --- /dev/null +++ b/drivers/video/fbdev/ps3fb.c @@ -0,0 +1,1307 @@ +/* + * linux/drivers/video/ps3fb.c -- PS3 GPU frame buffer device + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006, 2007 Sony Corporation + * + * This file is based on : + * + * linux/drivers/video/vfb.c -- Virtual frame buffer device + * + * Copyright (C) 2002 James Simmons + * + * Copyright (C) 1997 Geert Uytterhoeven + * + * 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/interrupt.h> +#include <linux/console.h> +#include <linux/ioctl.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/uaccess.h> +#include <linux/fb.h> +#include <linux/init.h> + +#include <asm/cell-regs.h> +#include <asm/lv1call.h> +#include <asm/ps3av.h> +#include <asm/ps3fb.h> +#include <asm/ps3.h> +#include <asm/ps3gpu.h> + + +#define DEVICE_NAME "ps3fb" + +#define GPU_CMD_BUF_SIZE (2 * 1024 * 1024) +#define GPU_FB_START (64 * 1024) +#define GPU_IOIF (0x0d000000UL) +#define GPU_ALIGN_UP(x) _ALIGN_UP((x), 64) +#define GPU_MAX_LINE_LENGTH (65536 - 64) + +#define GPU_INTR_STATUS_VSYNC_0 0 /* vsync on head A */ +#define GPU_INTR_STATUS_VSYNC_1 1 /* vsync on head B */ +#define GPU_INTR_STATUS_FLIP_0 3 /* flip head A */ +#define GPU_INTR_STATUS_FLIP_1 4 /* flip head B */ +#define GPU_INTR_STATUS_QUEUE_0 5 /* queue head A */ +#define GPU_INTR_STATUS_QUEUE_1 6 /* queue head B */ + +#define GPU_DRIVER_INFO_VERSION 0x211 + +/* gpu internals */ +struct display_head { + u64 be_time_stamp; + u32 status; + u32 offset; + u32 res1; + u32 res2; + u32 field; + u32 reserved1; + + u64 res3; + u32 raster; + + u64 vblank_count; + u32 field_vsync; + u32 reserved2; +}; + +struct gpu_irq { + u32 irq_outlet; + u32 status; + u32 mask; + u32 video_cause; + u32 graph_cause; + u32 user_cause; + + u32 res1; + u64 res2; + + u32 reserved[4]; +}; + +struct gpu_driver_info { + u32 version_driver; + u32 version_gpu; + u32 memory_size; + u32 hardware_channel; + + u32 nvcore_frequency; + u32 memory_frequency; + + u32 reserved[1063]; + struct display_head display_head[8]; + struct gpu_irq irq; +}; + +struct ps3fb_priv { + unsigned int irq_no; + + u64 context_handle, memory_handle; + struct gpu_driver_info *dinfo; + + u64 vblank_count; /* frame count */ + wait_queue_head_t wait_vsync; + + atomic_t ext_flip; /* on/off flip with vsync */ + atomic_t f_count; /* fb_open count */ + int is_blanked; + int is_kicked; + struct task_struct *task; +}; +static struct ps3fb_priv ps3fb; + +struct ps3fb_par { + u32 pseudo_palette[16]; + int mode_id, new_mode_id; + unsigned int num_frames; /* num of frame buffers */ + unsigned int width; + unsigned int height; + unsigned int ddr_line_length; + unsigned int ddr_frame_size; + unsigned int xdr_frame_size; + unsigned int full_offset; /* start of fullscreen DDR fb */ + unsigned int fb_offset; /* start of actual DDR fb */ + unsigned int pan_offset; +}; + + +#define FIRST_NATIVE_MODE_INDEX 10 + +static const struct fb_videomode ps3fb_modedb[] = { + /* 60 Hz broadcast modes (modes "1" to "5") */ + { + /* 480i */ + "480i", 60, 576, 384, 74074, 130, 89, 78, 57, 63, 6, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 480p */ + "480p", 60, 576, 384, 37037, 130, 89, 78, 57, 63, 6, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 720p */ + "720p", 60, 1124, 644, 13481, 298, 148, 57, 44, 80, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 1080i */ + "1080i", 60, 1688, 964, 13481, 264, 160, 94, 62, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 1080p */ + "1080p", 60, 1688, 964, 6741, 264, 160, 94, 62, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, + + /* 50 Hz broadcast modes (modes "6" to "10") */ + { + /* 576i */ + "576i", 50, 576, 460, 74074, 142, 83, 97, 63, 63, 5, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 576p */ + "576p", 50, 576, 460, 37037, 142, 83, 97, 63, 63, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 720p */ + "720p", 50, 1124, 644, 13468, 298, 478, 57, 44, 80, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 1080i */ + "1080i", 50, 1688, 964, 13468, 264, 600, 94, 62, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 1080p */ + "1080p", 50, 1688, 964, 6734, 264, 600, 94, 62, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, + + [FIRST_NATIVE_MODE_INDEX] = + /* 60 Hz broadcast modes (full resolution versions of modes "1" to "5") */ + { + /* 480if */ + "480if", 60, 720, 480, 74074, 58, 17, 30, 9, 63, 6, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 480pf */ + "480pf", 60, 720, 480, 37037, 58, 17, 30, 9, 63, 6, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 720pf */ + "720pf", 60, 1280, 720, 13481, 220, 70, 19, 6, 80, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 1080if */ + "1080if", 60, 1920, 1080, 13481, 148, 44, 36, 4, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 1080pf */ + "1080pf", 60, 1920, 1080, 6741, 148, 44, 36, 4, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, + + /* 50 Hz broadcast modes (full resolution versions of modes "6" to "10") */ + { + /* 576if */ + "576if", 50, 720, 576, 74074, 70, 11, 39, 5, 63, 5, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 576pf */ + "576pf", 50, 720, 576, 37037, 70, 11, 39, 5, 63, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 720pf */ + "720pf", 50, 1280, 720, 13468, 220, 400, 19, 6, 80, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, { + /* 1080if */ + "1080if", 50, 1920, 1080, 13468, 148, 484, 36, 4, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED + }, { + /* 1080pf */ + "1080pf", 50, 1920, 1080, 6734, 148, 484, 36, 4, 88, 5, + FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED + }, + + /* VESA modes (modes "11" to "13") */ + { + /* WXGA */ + "wxga", 60, 1280, 768, 12924, 160, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED, + FB_MODE_IS_VESA + }, { + /* SXGA */ + "sxga", 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, + FB_MODE_IS_VESA + }, { + /* WUXGA */ + "wuxga", 60, 1920, 1200, 6494, 80, 48, 26, 3, 32, 6, + FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, + FB_MODE_IS_VESA + } +}; + + +#define HEAD_A +#define HEAD_B + +#define BPP 4 /* number of bytes per pixel */ + + +static int ps3fb_mode; +module_param(ps3fb_mode, int, 0); + +static char *mode_option; + +static int ps3fb_cmp_mode(const struct fb_videomode *vmode, + const struct fb_var_screeninfo *var) +{ + long xres, yres, left_margin, right_margin, upper_margin, lower_margin; + long dx, dy; + + /* maximum values */ + if (var->xres > vmode->xres || var->yres > vmode->yres || + var->pixclock > vmode->pixclock || + var->hsync_len > vmode->hsync_len || + var->vsync_len > vmode->vsync_len) + return -1; + + /* progressive/interlaced must match */ + if ((var->vmode & FB_VMODE_MASK) != vmode->vmode) + return -1; + + /* minimum resolution */ + xres = max(var->xres, 1U); + yres = max(var->yres, 1U); + + /* minimum margins */ + left_margin = max(var->left_margin, vmode->left_margin); + right_margin = max(var->right_margin, vmode->right_margin); + upper_margin = max(var->upper_margin, vmode->upper_margin); + lower_margin = max(var->lower_margin, vmode->lower_margin); + + /* resolution + margins may not exceed native parameters */ + dx = ((long)vmode->left_margin + (long)vmode->xres + + (long)vmode->right_margin) - + (left_margin + xres + right_margin); + if (dx < 0) + return -1; + + dy = ((long)vmode->upper_margin + (long)vmode->yres + + (long)vmode->lower_margin) - + (upper_margin + yres + lower_margin); + if (dy < 0) + return -1; + + /* exact match */ + if (!dx && !dy) + return 0; + + /* resolution difference */ + return (vmode->xres - xres) * (vmode->yres - yres); +} + +static const struct fb_videomode *ps3fb_native_vmode(enum ps3av_mode_num id) +{ + return &ps3fb_modedb[FIRST_NATIVE_MODE_INDEX + id - 1]; +} + +static const struct fb_videomode *ps3fb_vmode(int id) +{ + u32 mode = id & PS3AV_MODE_MASK; + + if (mode < PS3AV_MODE_480I || mode > PS3AV_MODE_WUXGA) + return NULL; + + if (mode <= PS3AV_MODE_1080P50 && !(id & PS3AV_MODE_FULL)) { + /* Non-fullscreen broadcast mode */ + return &ps3fb_modedb[mode - 1]; + } + + return ps3fb_native_vmode(mode); +} + +static unsigned int ps3fb_find_mode(struct fb_var_screeninfo *var, + u32 *ddr_line_length, u32 *xdr_line_length) +{ + unsigned int id, best_id; + int diff, best_diff; + const struct fb_videomode *vmode; + long gap; + + best_id = 0; + best_diff = INT_MAX; + pr_debug("%s: wanted %u [%u] %u x %u [%u] %u\n", __func__, + var->left_margin, var->xres, var->right_margin, + var->upper_margin, var->yres, var->lower_margin); + for (id = PS3AV_MODE_480I; id <= PS3AV_MODE_WUXGA; id++) { + vmode = ps3fb_native_vmode(id); + diff = ps3fb_cmp_mode(vmode, var); + pr_debug("%s: mode %u: %u [%u] %u x %u [%u] %u: diff = %d\n", + __func__, id, vmode->left_margin, vmode->xres, + vmode->right_margin, vmode->upper_margin, + vmode->yres, vmode->lower_margin, diff); + if (diff < 0) + continue; + if (diff < best_diff) { + best_id = id; + if (!diff) + break; + best_diff = diff; + } + } + + if (!best_id) { + pr_debug("%s: no suitable mode found\n", __func__); + return 0; + } + + id = best_id; + vmode = ps3fb_native_vmode(id); + + *ddr_line_length = vmode->xres * BPP; + + /* minimum resolution */ + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + + /* minimum virtual resolution */ + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + /* minimum margins */ + if (var->left_margin < vmode->left_margin) + var->left_margin = vmode->left_margin; + if (var->right_margin < vmode->right_margin) + var->right_margin = vmode->right_margin; + if (var->upper_margin < vmode->upper_margin) + var->upper_margin = vmode->upper_margin; + if (var->lower_margin < vmode->lower_margin) + var->lower_margin = vmode->lower_margin; + + /* extra margins */ + gap = ((long)vmode->left_margin + (long)vmode->xres + + (long)vmode->right_margin) - + ((long)var->left_margin + (long)var->xres + + (long)var->right_margin); + if (gap > 0) { + var->left_margin += gap/2; + var->right_margin += (gap+1)/2; + pr_debug("%s: rounded up H to %u [%u] %u\n", __func__, + var->left_margin, var->xres, var->right_margin); + } + + gap = ((long)vmode->upper_margin + (long)vmode->yres + + (long)vmode->lower_margin) - + ((long)var->upper_margin + (long)var->yres + + (long)var->lower_margin); + if (gap > 0) { + var->upper_margin += gap/2; + var->lower_margin += (gap+1)/2; + pr_debug("%s: rounded up V to %u [%u] %u\n", __func__, + var->upper_margin, var->yres, var->lower_margin); + } + + /* fixed fields */ + var->pixclock = vmode->pixclock; + var->hsync_len = vmode->hsync_len; + var->vsync_len = vmode->vsync_len; + var->sync = vmode->sync; + + if (ps3_compare_firmware_version(1, 9, 0) >= 0) { + *xdr_line_length = GPU_ALIGN_UP(var->xres_virtual * BPP); + if (*xdr_line_length > GPU_MAX_LINE_LENGTH) + *xdr_line_length = GPU_MAX_LINE_LENGTH; + } else + *xdr_line_length = *ddr_line_length; + + if (vmode->sync & FB_SYNC_BROADCAST) { + /* Full broadcast modes have the full mode bit set */ + if (vmode->xres == var->xres && vmode->yres == var->yres) + id |= PS3AV_MODE_FULL; + } + + pr_debug("%s: mode %u\n", __func__, id); + return id; +} + +static void ps3fb_sync_image(struct device *dev, u64 frame_offset, + u64 dst_offset, u64 src_offset, u32 width, + u32 height, u32 dst_line_length, + u32 src_line_length) +{ + int status; + u64 line_length; + + line_length = dst_line_length; + if (src_line_length != dst_line_length) + line_length |= (u64)src_line_length << 32; + + src_offset += GPU_FB_START; + + mutex_lock(&ps3_gpu_mutex); + status = lv1_gpu_fb_blit(ps3fb.context_handle, dst_offset, + GPU_IOIF + src_offset, + L1GPU_FB_BLIT_WAIT_FOR_COMPLETION | + (width << 16) | height, + line_length); + mutex_unlock(&ps3_gpu_mutex); + + if (status) + dev_err(dev, "%s: lv1_gpu_fb_blit failed: %d\n", __func__, + status); +#ifdef HEAD_A + status = lv1_gpu_display_flip(ps3fb.context_handle, 0, frame_offset); + if (status) + dev_err(dev, "%s: lv1_gpu_display_flip failed: %d\n", __func__, + status); +#endif +#ifdef HEAD_B + status = lv1_gpu_display_flip(ps3fb.context_handle, 1, frame_offset); + if (status) + dev_err(dev, "%s: lv1_gpu_display_flip failed: %d\n", __func__, + status); +#endif +} + +static int ps3fb_sync(struct fb_info *info, u32 frame) +{ + struct ps3fb_par *par = info->par; + int error = 0; + u64 ddr_base, xdr_base; + + if (frame > par->num_frames - 1) { + dev_dbg(info->device, "%s: invalid frame number (%u)\n", + __func__, frame); + error = -EINVAL; + goto out; + } + + xdr_base = frame * par->xdr_frame_size; + ddr_base = frame * par->ddr_frame_size; + + ps3fb_sync_image(info->device, ddr_base + par->full_offset, + ddr_base + par->fb_offset, xdr_base + par->pan_offset, + par->width, par->height, par->ddr_line_length, + info->fix.line_length); + +out: + return error; +} + +static int ps3fb_open(struct fb_info *info, int user) +{ + atomic_inc(&ps3fb.f_count); + return 0; +} + +static int ps3fb_release(struct fb_info *info, int user) +{ + if (atomic_dec_and_test(&ps3fb.f_count)) { + if (atomic_read(&ps3fb.ext_flip)) { + atomic_set(&ps3fb.ext_flip, 0); + if (console_trylock()) { + ps3fb_sync(info, 0); /* single buffer */ + console_unlock(); + } + } + } + return 0; +} + + /* + * Setting the video mode has been split into two parts. + * First part, xxxfb_check_var, must not write anything + * to hardware, it should only verify and adjust var. + * This means it doesn't alter par but it does use hardware + * data from it to check this var. + */ + +static int ps3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 xdr_line_length, ddr_line_length; + int mode; + + mode = ps3fb_find_mode(var, &ddr_line_length, &xdr_line_length); + if (!mode) + return -EINVAL; + + /* Virtual screen */ + if (var->xres_virtual > xdr_line_length / BPP) { + dev_dbg(info->device, + "Horizontal virtual screen size too large\n"); + return -EINVAL; + } + + if (var->xoffset + var->xres > var->xres_virtual || + var->yoffset + var->yres > var->yres_virtual) { + dev_dbg(info->device, "panning out-of-range\n"); + return -EINVAL; + } + + /* We support ARGB8888 only */ + if (var->bits_per_pixel > 32 || var->grayscale || + var->red.offset > 16 || var->green.offset > 8 || + var->blue.offset > 0 || var->transp.offset > 24 || + var->red.length > 8 || var->green.length > 8 || + var->blue.length > 8 || var->transp.length > 8 || + var->red.msb_right || var->green.msb_right || + var->blue.msb_right || var->transp.msb_right || var->nonstd) { + dev_dbg(info->device, "We support ARGB8888 only\n"); + return -EINVAL; + } + + var->bits_per_pixel = 32; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->transp.offset = 24; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + /* Rotation is not supported */ + if (var->rotate) { + dev_dbg(info->device, "Rotation is not supported\n"); + return -EINVAL; + } + + /* Memory limit */ + if (var->yres_virtual * xdr_line_length > info->fix.smem_len) { + dev_dbg(info->device, "Not enough memory\n"); + return -ENOMEM; + } + + var->height = -1; + var->width = -1; + + return 0; +} + + /* + * This routine actually sets the video mode. + */ + +static int ps3fb_set_par(struct fb_info *info) +{ + struct ps3fb_par *par = info->par; + unsigned int mode, ddr_line_length, xdr_line_length, lines, maxlines; + unsigned int ddr_xoff, ddr_yoff, offset; + const struct fb_videomode *vmode; + u64 dst; + + mode = ps3fb_find_mode(&info->var, &ddr_line_length, &xdr_line_length); + if (!mode) + return -EINVAL; + + vmode = ps3fb_native_vmode(mode & PS3AV_MODE_MASK); + + info->fix.xpanstep = info->var.xres_virtual > info->var.xres ? 1 : 0; + info->fix.ypanstep = info->var.yres_virtual > info->var.yres ? 1 : 0; + info->fix.line_length = xdr_line_length; + + par->ddr_line_length = ddr_line_length; + par->ddr_frame_size = vmode->yres * ddr_line_length; + par->xdr_frame_size = info->var.yres_virtual * xdr_line_length; + + par->num_frames = info->fix.smem_len / + max(par->ddr_frame_size, par->xdr_frame_size); + + /* Keep the special bits we cannot set using fb_var_screeninfo */ + par->new_mode_id = (par->new_mode_id & ~PS3AV_MODE_MASK) | mode; + + par->width = info->var.xres; + par->height = info->var.yres; + + /* Start of the virtual frame buffer (relative to fullscreen) */ + ddr_xoff = info->var.left_margin - vmode->left_margin; + ddr_yoff = info->var.upper_margin - vmode->upper_margin; + offset = ddr_yoff * ddr_line_length + ddr_xoff * BPP; + + par->fb_offset = GPU_ALIGN_UP(offset); + par->full_offset = par->fb_offset - offset; + par->pan_offset = info->var.yoffset * xdr_line_length + + info->var.xoffset * BPP; + + if (par->new_mode_id != par->mode_id) { + if (ps3av_set_video_mode(par->new_mode_id)) { + par->new_mode_id = par->mode_id; + return -EINVAL; + } + par->mode_id = par->new_mode_id; + } + + /* Clear XDR frame buffer memory */ + memset((void __force *)info->screen_base, 0, info->fix.smem_len); + + /* Clear DDR frame buffer memory */ + lines = vmode->yres * par->num_frames; + if (par->full_offset) + lines++; + maxlines = info->fix.smem_len / ddr_line_length; + for (dst = 0; lines; dst += maxlines * ddr_line_length) { + unsigned int l = min(lines, maxlines); + ps3fb_sync_image(info->device, 0, dst, 0, vmode->xres, l, + ddr_line_length, ddr_line_length); + lines -= l; + } + + return 0; +} + + /* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ + +static int ps3fb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + if (regno >= 16) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + transp >>= 8; + + ((u32 *)info->pseudo_palette)[regno] = transp << 24 | red << 16 | + green << 8 | blue; + return 0; +} + +static int ps3fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct ps3fb_par *par = info->par; + + par->pan_offset = var->yoffset * info->fix.line_length + + var->xoffset * BPP; + return 0; +} + + /* + * As we have a virtual frame buffer, we need our own mmap function + */ + +static int ps3fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + int r; + + r = vm_iomap_memory(vma, info->fix.smem_start, info->fix.smem_len); + + dev_dbg(info->device, "ps3fb: mmap framebuffer P(%lx)->V(%lx)\n", + info->fix.smem_start + (vma->vm_pgoff << PAGE_SHIFT), + vma->vm_start); + + return r; +} + + /* + * Blank the display + */ + +static int ps3fb_blank(int blank, struct fb_info *info) +{ + int retval; + + dev_dbg(info->device, "%s: blank:%d\n", __func__, blank); + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_NORMAL: + retval = ps3av_video_mute(1); /* mute on */ + if (!retval) + ps3fb.is_blanked = 1; + break; + + default: /* unblank */ + retval = ps3av_video_mute(0); /* mute off */ + if (!retval) + ps3fb.is_blanked = 0; + break; + } + return retval; +} + +static int ps3fb_get_vblank(struct fb_vblank *vblank) +{ + memset(vblank, 0, sizeof(*vblank)); + vblank->flags = FB_VBLANK_HAVE_VSYNC; + return 0; +} + +static int ps3fb_wait_for_vsync(u32 crtc) +{ + int ret; + u64 count; + + count = ps3fb.vblank_count; + ret = wait_event_interruptible_timeout(ps3fb.wait_vsync, + count != ps3fb.vblank_count, + HZ / 10); + if (!ret) + return -ETIMEDOUT; + + return 0; +} + + + /* + * ioctl + */ + +static int ps3fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + u32 val; + int retval = -EFAULT; + + switch (cmd) { + case FBIOGET_VBLANK: + { + struct fb_vblank vblank; + dev_dbg(info->device, "FBIOGET_VBLANK:\n"); + retval = ps3fb_get_vblank(&vblank); + if (retval) + break; + + if (copy_to_user(argp, &vblank, sizeof(vblank))) + retval = -EFAULT; + break; + } + + case FBIO_WAITFORVSYNC: + { + u32 crt; + dev_dbg(info->device, "FBIO_WAITFORVSYNC:\n"); + if (get_user(crt, (u32 __user *) arg)) + break; + + retval = ps3fb_wait_for_vsync(crt); + break; + } + + case PS3FB_IOCTL_SETMODE: + { + struct ps3fb_par *par = info->par; + const struct fb_videomode *vmode; + struct fb_var_screeninfo var; + + if (copy_from_user(&val, argp, sizeof(val))) + break; + + if (!(val & PS3AV_MODE_MASK)) { + u32 id = ps3av_get_auto_mode(); + if (id > 0) + val = (val & ~PS3AV_MODE_MASK) | id; + } + dev_dbg(info->device, "PS3FB_IOCTL_SETMODE:%x\n", val); + retval = -EINVAL; + vmode = ps3fb_vmode(val); + if (vmode) { + var = info->var; + fb_videomode_to_var(&var, vmode); + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + /* Force, in case only special bits changed */ + var.activate |= FB_ACTIVATE_FORCE; + par->new_mode_id = val; + retval = fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + } + break; + } + + case PS3FB_IOCTL_GETMODE: + val = ps3av_get_mode(); + dev_dbg(info->device, "PS3FB_IOCTL_GETMODE:%x\n", val); + if (!copy_to_user(argp, &val, sizeof(val))) + retval = 0; + break; + + case PS3FB_IOCTL_SCREENINFO: + { + struct ps3fb_par *par = info->par; + struct ps3fb_ioctl_res res; + dev_dbg(info->device, "PS3FB_IOCTL_SCREENINFO:\n"); + res.xres = info->fix.line_length / BPP; + res.yres = info->var.yres_virtual; + res.xoff = (res.xres - info->var.xres) / 2; + res.yoff = (res.yres - info->var.yres) / 2; + res.num_frames = par->num_frames; + if (!copy_to_user(argp, &res, sizeof(res))) + retval = 0; + break; + } + + case PS3FB_IOCTL_ON: + dev_dbg(info->device, "PS3FB_IOCTL_ON:\n"); + atomic_inc(&ps3fb.ext_flip); + retval = 0; + break; + + case PS3FB_IOCTL_OFF: + dev_dbg(info->device, "PS3FB_IOCTL_OFF:\n"); + atomic_dec_if_positive(&ps3fb.ext_flip); + retval = 0; + break; + + case PS3FB_IOCTL_FSEL: + if (copy_from_user(&val, argp, sizeof(val))) + break; + + dev_dbg(info->device, "PS3FB_IOCTL_FSEL:%d\n", val); + console_lock(); + retval = ps3fb_sync(info, val); + console_unlock(); + break; + + default: + retval = -ENOIOCTLCMD; + break; + } + return retval; +} + +static int ps3fbd(void *arg) +{ + struct fb_info *info = arg; + + set_freezable(); + while (!kthread_should_stop()) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (ps3fb.is_kicked) { + ps3fb.is_kicked = 0; + console_lock(); + ps3fb_sync(info, 0); /* single buffer */ + console_unlock(); + } + schedule(); + } + return 0; +} + +static irqreturn_t ps3fb_vsync_interrupt(int irq, void *ptr) +{ + struct device *dev = ptr; + u64 v1; + int status; + struct display_head *head = &ps3fb.dinfo->display_head[1]; + + status = lv1_gpu_context_intr(ps3fb.context_handle, &v1); + if (status) { + dev_err(dev, "%s: lv1_gpu_context_intr failed: %d\n", __func__, + status); + return IRQ_NONE; + } + + if (v1 & (1 << GPU_INTR_STATUS_VSYNC_1)) { + /* VSYNC */ + ps3fb.vblank_count = head->vblank_count; + if (ps3fb.task && !ps3fb.is_blanked && + !atomic_read(&ps3fb.ext_flip)) { + ps3fb.is_kicked = 1; + wake_up_process(ps3fb.task); + } + wake_up_interruptible(&ps3fb.wait_vsync); + } + + return IRQ_HANDLED; +} + + +static struct fb_ops ps3fb_ops = { + .fb_open = ps3fb_open, + .fb_release = ps3fb_release, + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_check_var = ps3fb_check_var, + .fb_set_par = ps3fb_set_par, + .fb_setcolreg = ps3fb_setcolreg, + .fb_pan_display = ps3fb_pan_display, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_mmap = ps3fb_mmap, + .fb_blank = ps3fb_blank, + .fb_ioctl = ps3fb_ioctl, + .fb_compat_ioctl = ps3fb_ioctl +}; + +static struct fb_fix_screeninfo ps3fb_fix = { + .id = DEVICE_NAME, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .accel = FB_ACCEL_NONE, +}; + +static int ps3fb_probe(struct ps3_system_bus_device *dev) +{ + struct fb_info *info; + struct ps3fb_par *par; + int retval; + u64 ddr_lpar = 0; + u64 lpar_dma_control = 0; + u64 lpar_driver_info = 0; + u64 lpar_reports = 0; + u64 lpar_reports_size = 0; + u64 xdr_lpar; + struct gpu_driver_info *dinfo; + void *fb_start; + int status; + struct task_struct *task; + unsigned long max_ps3fb_size; + + if (ps3fb_videomemory.size < GPU_CMD_BUF_SIZE) { + dev_err(&dev->core, "%s: Not enough video memory\n", __func__); + return -ENOMEM; + } + + retval = ps3_open_hv_device(dev); + if (retval) { + dev_err(&dev->core, "%s: ps3_open_hv_device failed\n", + __func__); + goto err; + } + + if (!ps3fb_mode) + ps3fb_mode = ps3av_get_mode(); + dev_dbg(&dev->core, "ps3fb_mode: %d\n", ps3fb_mode); + + atomic_set(&ps3fb.f_count, -1); /* fbcon opens ps3fb */ + atomic_set(&ps3fb.ext_flip, 0); /* for flip with vsync */ + init_waitqueue_head(&ps3fb.wait_vsync); + +#ifdef HEAD_A + status = lv1_gpu_display_sync(0x0, 0, L1GPU_DISPLAY_SYNC_VSYNC); + if (status) { + dev_err(&dev->core, "%s: lv1_gpu_display_sync failed: %d\n", + __func__, status); + retval = -ENODEV; + goto err_close_device; + } +#endif +#ifdef HEAD_B + status = lv1_gpu_display_sync(0x0, 1, L1GPU_DISPLAY_SYNC_VSYNC); + if (status) { + dev_err(&dev->core, "%s: lv1_gpu_display_sync failed: %d\n", + __func__, status); + retval = -ENODEV; + goto err_close_device; + } +#endif + + max_ps3fb_size = _ALIGN_UP(GPU_IOIF, 256*1024*1024) - GPU_IOIF; + if (ps3fb_videomemory.size > max_ps3fb_size) { + dev_info(&dev->core, "Limiting ps3fb mem size to %lu bytes\n", + max_ps3fb_size); + ps3fb_videomemory.size = max_ps3fb_size; + } + + /* get gpu context handle */ + status = lv1_gpu_memory_allocate(ps3fb_videomemory.size, 0, 0, 0, 0, + &ps3fb.memory_handle, &ddr_lpar); + if (status) { + dev_err(&dev->core, "%s: lv1_gpu_memory_allocate failed: %d\n", + __func__, status); + retval = -ENOMEM; + goto err_close_device; + } + dev_dbg(&dev->core, "ddr:lpar:0x%llx\n", ddr_lpar); + + status = lv1_gpu_context_allocate(ps3fb.memory_handle, 0, + &ps3fb.context_handle, + &lpar_dma_control, &lpar_driver_info, + &lpar_reports, &lpar_reports_size); + if (status) { + dev_err(&dev->core, + "%s: lv1_gpu_context_allocate failed: %d\n", __func__, + status); + retval = -ENOMEM; + goto err_gpu_memory_free; + } + + /* vsync interrupt */ + dinfo = (void __force *)ioremap(lpar_driver_info, 128 * 1024); + if (!dinfo) { + dev_err(&dev->core, "%s: ioremap failed\n", __func__); + retval = -ENOMEM; + goto err_gpu_context_free; + } + + ps3fb.dinfo = dinfo; + dev_dbg(&dev->core, "version_driver:%x\n", dinfo->version_driver); + dev_dbg(&dev->core, "irq outlet:%x\n", dinfo->irq.irq_outlet); + dev_dbg(&dev->core, "version_gpu: %x memory_size: %x ch: %x " + "core_freq: %d mem_freq:%d\n", dinfo->version_gpu, + dinfo->memory_size, dinfo->hardware_channel, + dinfo->nvcore_frequency/1000000, + dinfo->memory_frequency/1000000); + + if (dinfo->version_driver != GPU_DRIVER_INFO_VERSION) { + dev_err(&dev->core, "%s: version_driver err:%x\n", __func__, + dinfo->version_driver); + retval = -EINVAL; + goto err_iounmap_dinfo; + } + + retval = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY, dinfo->irq.irq_outlet, + &ps3fb.irq_no); + if (retval) { + dev_err(&dev->core, "%s: ps3_alloc_irq failed %d\n", __func__, + retval); + goto err_iounmap_dinfo; + } + + retval = request_irq(ps3fb.irq_no, ps3fb_vsync_interrupt, + 0, DEVICE_NAME, &dev->core); + if (retval) { + dev_err(&dev->core, "%s: request_irq failed %d\n", __func__, + retval); + goto err_destroy_plug; + } + + dinfo->irq.mask = (1 << GPU_INTR_STATUS_VSYNC_1) | + (1 << GPU_INTR_STATUS_FLIP_1); + + /* Clear memory to prevent kernel info leakage into userspace */ + memset(ps3fb_videomemory.address, 0, ps3fb_videomemory.size); + + xdr_lpar = ps3_mm_phys_to_lpar(__pa(ps3fb_videomemory.address)); + + status = lv1_gpu_context_iomap(ps3fb.context_handle, GPU_IOIF, + xdr_lpar, ps3fb_videomemory.size, + CBE_IOPTE_PP_W | CBE_IOPTE_PP_R | + CBE_IOPTE_M); + if (status) { + dev_err(&dev->core, "%s: lv1_gpu_context_iomap failed: %d\n", + __func__, status); + retval = -ENXIO; + goto err_free_irq; + } + + dev_dbg(&dev->core, "video:%p ioif:%lx lpar:%llx size:%lx\n", + ps3fb_videomemory.address, GPU_IOIF, xdr_lpar, + ps3fb_videomemory.size); + + status = lv1_gpu_fb_setup(ps3fb.context_handle, xdr_lpar, + GPU_CMD_BUF_SIZE, GPU_IOIF); + if (status) { + dev_err(&dev->core, "%s: lv1_gpu_fb_setup failed: %d\n", + __func__, status); + retval = -ENXIO; + goto err_context_unmap; + } + + info = framebuffer_alloc(sizeof(struct ps3fb_par), &dev->core); + if (!info) { + retval = -ENOMEM; + goto err_context_fb_close; + } + + par = info->par; + par->mode_id = ~ps3fb_mode; /* != ps3fb_mode, to trigger change */ + par->new_mode_id = ps3fb_mode; + par->num_frames = 1; + + info->fbops = &ps3fb_ops; + info->fix = ps3fb_fix; + + /* + * The GPU command buffer is at the start of video memory + * As we don't use the full command buffer, we can put the actual + * frame buffer at offset GPU_FB_START and save some precious XDR + * memory + */ + fb_start = ps3fb_videomemory.address + GPU_FB_START; + info->screen_base = (char __force __iomem *)fb_start; + info->fix.smem_start = __pa(fb_start); + info->fix.smem_len = ps3fb_videomemory.size - GPU_FB_START; + + info->pseudo_palette = par->pseudo_palette; + info->flags = FBINFO_DEFAULT | FBINFO_READS_FAST | + FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) + goto err_framebuffer_release; + + if (!fb_find_mode(&info->var, info, mode_option, ps3fb_modedb, + ARRAY_SIZE(ps3fb_modedb), + ps3fb_vmode(par->new_mode_id), 32)) { + retval = -EINVAL; + goto err_fb_dealloc; + } + + fb_videomode_to_modelist(ps3fb_modedb, ARRAY_SIZE(ps3fb_modedb), + &info->modelist); + + retval = register_framebuffer(info); + if (retval < 0) + goto err_fb_dealloc; + + ps3_system_bus_set_drvdata(dev, info); + + dev_info(info->device, "%s %s, using %u KiB of video memory\n", + dev_driver_string(info->dev), dev_name(info->dev), + info->fix.smem_len >> 10); + + task = kthread_run(ps3fbd, info, DEVICE_NAME); + if (IS_ERR(task)) { + retval = PTR_ERR(task); + goto err_unregister_framebuffer; + } + + ps3fb.task = task; + + return 0; + +err_unregister_framebuffer: + unregister_framebuffer(info); +err_fb_dealloc: + fb_dealloc_cmap(&info->cmap); +err_framebuffer_release: + framebuffer_release(info); +err_context_fb_close: + lv1_gpu_fb_close(ps3fb.context_handle); +err_context_unmap: + lv1_gpu_context_iomap(ps3fb.context_handle, GPU_IOIF, xdr_lpar, + ps3fb_videomemory.size, CBE_IOPTE_M); +err_free_irq: + free_irq(ps3fb.irq_no, &dev->core); +err_destroy_plug: + ps3_irq_plug_destroy(ps3fb.irq_no); +err_iounmap_dinfo: + iounmap((u8 __force __iomem *)ps3fb.dinfo); +err_gpu_context_free: + lv1_gpu_context_free(ps3fb.context_handle); +err_gpu_memory_free: + lv1_gpu_memory_free(ps3fb.memory_handle); +err_close_device: + ps3_close_hv_device(dev); +err: + return retval; +} + +static int ps3fb_shutdown(struct ps3_system_bus_device *dev) +{ + struct fb_info *info = ps3_system_bus_get_drvdata(dev); + u64 xdr_lpar = ps3_mm_phys_to_lpar(__pa(ps3fb_videomemory.address)); + + dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__); + + atomic_inc(&ps3fb.ext_flip); /* flip off */ + ps3fb.dinfo->irq.mask = 0; + + if (ps3fb.task) { + struct task_struct *task = ps3fb.task; + ps3fb.task = NULL; + kthread_stop(task); + } + if (ps3fb.irq_no) { + free_irq(ps3fb.irq_no, &dev->core); + ps3_irq_plug_destroy(ps3fb.irq_no); + } + if (info) { + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + ps3_system_bus_set_drvdata(dev, NULL); + } + iounmap((u8 __force __iomem *)ps3fb.dinfo); + lv1_gpu_fb_close(ps3fb.context_handle); + lv1_gpu_context_iomap(ps3fb.context_handle, GPU_IOIF, xdr_lpar, + ps3fb_videomemory.size, CBE_IOPTE_M); + lv1_gpu_context_free(ps3fb.context_handle); + lv1_gpu_memory_free(ps3fb.memory_handle); + ps3_close_hv_device(dev); + dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__); + + return 0; +} + +static struct ps3_system_bus_driver ps3fb_driver = { + .match_id = PS3_MATCH_ID_GPU, + .match_sub_id = PS3_MATCH_SUB_ID_GPU_FB, + .core.name = DEVICE_NAME, + .core.owner = THIS_MODULE, + .probe = ps3fb_probe, + .remove = ps3fb_shutdown, + .shutdown = ps3fb_shutdown, +}; + +static int __init ps3fb_setup(void) +{ + char *options; + +#ifdef MODULE + return 0; +#endif + + if (fb_get_options(DEVICE_NAME, &options)) + return -ENXIO; + + if (!options || !*options) + return 0; + + while (1) { + char *this_opt = strsep(&options, ","); + + if (!this_opt) + break; + if (!*this_opt) + continue; + if (!strncmp(this_opt, "mode:", 5)) + ps3fb_mode = simple_strtoul(this_opt + 5, NULL, 0); + else + mode_option = this_opt; + } + return 0; +} + +static int __init ps3fb_init(void) +{ + if (!ps3fb_videomemory.address || ps3fb_setup()) + return -ENXIO; + + return ps3_system_bus_driver_register(&ps3fb_driver); +} + +static void __exit ps3fb_exit(void) +{ + pr_debug(" -> %s:%d\n", __func__, __LINE__); + ps3_system_bus_driver_unregister(&ps3fb_driver); + pr_debug(" <- %s:%d\n", __func__, __LINE__); +} + +module_init(ps3fb_init); +module_exit(ps3fb_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PS3 GPU Frame Buffer Driver"); +MODULE_AUTHOR("Sony Computer Entertainment Inc."); +MODULE_ALIAS(PS3_MODULE_ALIAS_GPU_FB); diff --git a/drivers/video/fbdev/pvr2fb.c b/drivers/video/fbdev/pvr2fb.c new file mode 100644 index 000000000000..167cffff3d4e --- /dev/null +++ b/drivers/video/fbdev/pvr2fb.c @@ -0,0 +1,1142 @@ +/* + * drivers/video/pvr2fb.c + * + * Frame buffer and fbcon support for the NEC PowerVR2 found within the Sega + * Dreamcast. + * + * Copyright (c) 2001 M. R. Brown <mrbrown@0xd6.org> + * Copyright (c) 2001 - 2008 Paul Mundt <lethal@linux-sh.org> + * + * This driver is mostly based on the excellent amifb and vfb sources. It uses + * an odd scheme for converting hardware values to/from framebuffer values, + * here are some hacked-up formulas: + * + * The Dreamcast has screen offsets from each side of its four borders and + * the start offsets of the display window. I used these values to calculate + * 'pseudo' values (think of them as placeholders) for the fb video mode, so + * that when it came time to convert these values back into their hardware + * values, I could just add mode- specific offsets to get the correct mode + * settings: + * + * left_margin = diwstart_h - borderstart_h; + * right_margin = borderstop_h - (diwstart_h + xres); + * upper_margin = diwstart_v - borderstart_v; + * lower_margin = borderstop_v - (diwstart_h + yres); + * + * hsync_len = borderstart_h + (hsync_total - borderstop_h); + * vsync_len = borderstart_v + (vsync_total - borderstop_v); + * + * Then, when it's time to convert back to hardware settings, the only + * constants are the borderstart_* offsets, all other values are derived from + * the fb video mode: + * + * // PAL + * borderstart_h = 116; + * borderstart_v = 44; + * ... + * borderstop_h = borderstart_h + hsync_total - hsync_len; + * ... + * diwstart_v = borderstart_v - upper_margin; + * + * However, in the current implementation, the borderstart values haven't had + * the benefit of being fully researched, so some modes may be broken. + */ + +#undef DEBUG + +#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/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> + +#ifdef CONFIG_SH_DREAMCAST +#include <asm/machvec.h> +#include <mach-dreamcast/mach/sysasic.h> +#endif + +#ifdef CONFIG_PVR2_DMA +#include <linux/pagemap.h> +#include <mach/dma.h> +#include <asm/dma.h> +#endif + +#ifdef CONFIG_SH_STORE_QUEUES +#include <linux/uaccess.h> +#include <cpu/sq.h> +#endif + +#ifndef PCI_DEVICE_ID_NEC_NEON250 +# define PCI_DEVICE_ID_NEC_NEON250 0x0067 +#endif + +/* 2D video registers */ +#define DISP_BASE par->mmio_base +#define DISP_BRDRCOLR (DISP_BASE + 0x40) +#define DISP_DIWMODE (DISP_BASE + 0x44) +#define DISP_DIWADDRL (DISP_BASE + 0x50) +#define DISP_DIWADDRS (DISP_BASE + 0x54) +#define DISP_DIWSIZE (DISP_BASE + 0x5c) +#define DISP_SYNCCONF (DISP_BASE + 0xd0) +#define DISP_BRDRHORZ (DISP_BASE + 0xd4) +#define DISP_SYNCSIZE (DISP_BASE + 0xd8) +#define DISP_BRDRVERT (DISP_BASE + 0xdc) +#define DISP_DIWCONF (DISP_BASE + 0xe8) +#define DISP_DIWHSTRT (DISP_BASE + 0xec) +#define DISP_DIWVSTRT (DISP_BASE + 0xf0) +#define DISP_PIXDEPTH (DISP_BASE + 0x108) + +/* Pixel clocks, one for TV output, doubled for VGA output */ +#define TV_CLK 74239 +#define VGA_CLK 37119 + +/* This is for 60Hz - the VTOTAL is doubled for interlaced modes */ +#define PAL_HTOTAL 863 +#define PAL_VTOTAL 312 +#define NTSC_HTOTAL 857 +#define NTSC_VTOTAL 262 + +/* Supported cable types */ +enum { CT_VGA, CT_NONE, CT_RGB, CT_COMPOSITE }; + +/* Supported video output types */ +enum { VO_PAL, VO_NTSC, VO_VGA }; + +/* Supported palette types */ +enum { PAL_ARGB1555, PAL_RGB565, PAL_ARGB4444, PAL_ARGB8888 }; + +struct pvr2_params { unsigned int val; char *name; }; +static struct pvr2_params cables[] = { + { CT_VGA, "VGA" }, { CT_RGB, "RGB" }, { CT_COMPOSITE, "COMPOSITE" }, +}; + +static struct pvr2_params outputs[] = { + { VO_PAL, "PAL" }, { VO_NTSC, "NTSC" }, { VO_VGA, "VGA" }, +}; + +/* + * This describes the current video mode + */ + +static struct pvr2fb_par { + unsigned int hsync_total; /* Clocks/line */ + unsigned int vsync_total; /* Lines/field */ + unsigned int borderstart_h; + unsigned int borderstop_h; + unsigned int borderstart_v; + unsigned int borderstop_v; + unsigned int diwstart_h; /* Horizontal offset of the display field */ + unsigned int diwstart_v; /* Vertical offset of the display field, for + interlaced modes, this is the long field */ + unsigned long disp_start; /* Address of image within VRAM */ + unsigned char is_interlaced; /* Is the display interlaced? */ + unsigned char is_doublescan; /* Are scanlines output twice? (doublescan) */ + unsigned char is_lowres; /* Is horizontal pixel-doubling enabled? */ + + unsigned long mmio_base; /* MMIO base */ + u32 palette[16]; +} *currentpar; + +static struct fb_info *fb_info; + +static struct fb_fix_screeninfo pvr2_fix = { + .id = "NEC PowerVR2", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .ypanstep = 1, + .ywrapstep = 1, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo pvr2_var = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel =16, + .red = { 11, 5, 0 }, + .green = { 5, 6, 0 }, + .blue = { 0, 5, 0 }, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static int cable_type = CT_VGA; +static int video_output = VO_VGA; + +static int nopan = 0; +static int nowrap = 1; + +/* + * We do all updating, blanking, etc. during the vertical retrace period + */ +static unsigned int do_vmode_full = 0; /* Change the video mode */ +static unsigned int do_vmode_pan = 0; /* Update the video mode */ +static short do_blank = 0; /* (Un)Blank the screen */ + +static unsigned int is_blanked = 0; /* Is the screen blanked? */ + +#ifdef CONFIG_SH_STORE_QUEUES +static unsigned long pvr2fb_map; +#endif + +#ifdef CONFIG_PVR2_DMA +static unsigned int shdma = PVR2_CASCADE_CHAN; +static unsigned int pvr2dma = ONCHIP_NR_DMA_CHANNELS; +#endif + +static int pvr2fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info); +static int pvr2fb_blank(int blank, struct fb_info *info); +static unsigned long get_line_length(int xres_virtual, int bpp); +static void set_color_bitfields(struct fb_var_screeninfo *var); +static int pvr2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +static int pvr2fb_set_par(struct fb_info *info); +static void pvr2_update_display(struct fb_info *info); +static void pvr2_init_display(struct fb_info *info); +static void pvr2_do_blank(void); +static irqreturn_t pvr2fb_interrupt(int irq, void *dev_id); +static int pvr2_init_cable(void); +static int pvr2_get_param(const struct pvr2_params *p, const char *s, + int val, int size); +#ifdef CONFIG_PVR2_DMA +static ssize_t pvr2fb_write(struct fb_info *info, const char *buf, + size_t count, loff_t *ppos); +#endif + +static struct fb_ops pvr2fb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = pvr2fb_setcolreg, + .fb_blank = pvr2fb_blank, + .fb_check_var = pvr2fb_check_var, + .fb_set_par = pvr2fb_set_par, +#ifdef CONFIG_PVR2_DMA + .fb_write = pvr2fb_write, +#endif + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_videomode pvr2_modedb[] = { + /* + * Broadcast video modes (PAL and NTSC). I'm unfamiliar with + * PAL-M and PAL-N, but from what I've read both modes parallel PAL and + * NTSC, so it shouldn't be a problem (I hope). + */ + + { + /* 640x480 @ 60Hz interlaced (NTSC) */ + "ntsc_640x480i", 60, 640, 480, TV_CLK, 38, 33, 0, 18, 146, 26, + FB_SYNC_BROADCAST, FB_VMODE_INTERLACED | FB_VMODE_YWRAP + }, { + /* 640x240 @ 60Hz (NTSC) */ + /* XXX: Broken! Don't use... */ + "ntsc_640x240", 60, 640, 240, TV_CLK, 38, 33, 0, 0, 146, 22, + FB_SYNC_BROADCAST, FB_VMODE_YWRAP + }, { + /* 640x480 @ 60hz (VGA) */ + "vga_640x480", 60, 640, 480, VGA_CLK, 38, 33, 0, 18, 146, 26, + 0, FB_VMODE_YWRAP + }, +}; + +#define NUM_TOTAL_MODES ARRAY_SIZE(pvr2_modedb) + +#define DEFMODE_NTSC 0 +#define DEFMODE_PAL 0 +#define DEFMODE_VGA 2 + +static int defmode = DEFMODE_NTSC; +static char *mode_option = NULL; + +static inline void pvr2fb_set_pal_type(unsigned int type) +{ + struct pvr2fb_par *par = (struct pvr2fb_par *)fb_info->par; + + fb_writel(type, par->mmio_base + 0x108); +} + +static inline void pvr2fb_set_pal_entry(struct pvr2fb_par *par, + unsigned int regno, + unsigned int val) +{ + fb_writel(val, par->mmio_base + 0x1000 + (4 * regno)); +} + +static int pvr2fb_blank(int blank, struct fb_info *info) +{ + do_blank = blank ? blank : -1; + return 0; +} + +static inline unsigned long get_line_length(int xres_virtual, int bpp) +{ + return (unsigned long)((((xres_virtual*bpp)+31)&~31) >> 3); +} + +static void set_color_bitfields(struct fb_var_screeninfo *var) +{ + switch (var->bits_per_pixel) { + case 16: /* RGB 565 */ + pvr2fb_set_pal_type(PAL_RGB565); + var->red.offset = 11; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 0; var->blue.length = 5; + var->transp.offset = 0; var->transp.length = 0; + break; + case 24: /* RGB 888 */ + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + case 32: /* ARGB 8888 */ + pvr2fb_set_pal_type(PAL_ARGB8888); + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 24; var->transp.length = 8; + break; + } +} + +static int pvr2fb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct pvr2fb_par *par = (struct pvr2fb_par *)info->par; + unsigned int tmp; + + if (regno > info->cmap.len) + return 1; + + /* + * We only support the hardware palette for 16 and 32bpp. It's also + * expected that the palette format has been set by the time we get + * here, so we don't waste time setting it again. + */ + switch (info->var.bits_per_pixel) { + case 16: /* RGB 565 */ + tmp = (red & 0xf800) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + + pvr2fb_set_pal_entry(par, regno, tmp); + break; + case 24: /* RGB 888 */ + red >>= 8; green >>= 8; blue >>= 8; + tmp = (red << 16) | (green << 8) | blue; + break; + case 32: /* ARGB 8888 */ + red >>= 8; green >>= 8; blue >>= 8; + tmp = (transp << 24) | (red << 16) | (green << 8) | blue; + + pvr2fb_set_pal_entry(par, regno, tmp); + break; + default: + pr_debug("Invalid bit depth %d?!?\n", info->var.bits_per_pixel); + return 1; + } + + if (regno < 16) + ((u32*)(info->pseudo_palette))[regno] = tmp; + + return 0; +} + +static int pvr2fb_set_par(struct fb_info *info) +{ + struct pvr2fb_par *par = (struct pvr2fb_par *)info->par; + struct fb_var_screeninfo *var = &info->var; + unsigned long line_length; + unsigned int vtotal; + + /* + * XXX: It's possible that a user could use a VGA box, change the cable + * type in hardware (i.e. switch from VGA<->composite), then change + * modes (i.e. switching to another VT). If that happens we should + * automagically change the output format to cope, but currently I + * don't have a VGA box to make sure this works properly. + */ + cable_type = pvr2_init_cable(); + if (cable_type == CT_VGA && video_output != VO_VGA) + video_output = VO_VGA; + + var->vmode &= FB_VMODE_MASK; + if (var->vmode & FB_VMODE_INTERLACED && video_output != VO_VGA) + par->is_interlaced = 1; + /* + * XXX: Need to be more creative with this (i.e. allow doublecan for + * PAL/NTSC output). + */ + if (var->vmode & FB_VMODE_DOUBLE && video_output == VO_VGA) + par->is_doublescan = 1; + + par->hsync_total = var->left_margin + var->xres + var->right_margin + + var->hsync_len; + par->vsync_total = var->upper_margin + var->yres + var->lower_margin + + var->vsync_len; + + if (var->sync & FB_SYNC_BROADCAST) { + vtotal = par->vsync_total; + if (par->is_interlaced) + vtotal /= 2; + if (vtotal > (PAL_VTOTAL + NTSC_VTOTAL)/2) { + /* XXX: Check for start values here... */ + /* XXX: Check hardware for PAL-compatibility */ + par->borderstart_h = 116; + par->borderstart_v = 44; + } else { + /* NTSC video output */ + par->borderstart_h = 126; + par->borderstart_v = 18; + } + } else { + /* VGA mode */ + /* XXX: What else needs to be checked? */ + /* + * XXX: We have a little freedom in VGA modes, what ranges + * should be here (i.e. hsync/vsync totals, etc.)? + */ + par->borderstart_h = 126; + par->borderstart_v = 40; + } + + /* Calculate the remainding offsets */ + par->diwstart_h = par->borderstart_h + var->left_margin; + par->diwstart_v = par->borderstart_v + var->upper_margin; + par->borderstop_h = par->diwstart_h + var->xres + + var->right_margin; + par->borderstop_v = par->diwstart_v + var->yres + + var->lower_margin; + + if (!par->is_interlaced) + par->borderstop_v /= 2; + if (info->var.xres < 640) + par->is_lowres = 1; + + line_length = get_line_length(var->xres_virtual, var->bits_per_pixel); + par->disp_start = info->fix.smem_start + (line_length * var->yoffset) * line_length; + info->fix.line_length = line_length; + return 0; +} + +static int pvr2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct pvr2fb_par *par = (struct pvr2fb_par *)info->par; + unsigned int vtotal, hsync_total; + unsigned long line_length; + + if (var->pixclock != TV_CLK && var->pixclock != VGA_CLK) { + pr_debug("Invalid pixclock value %d\n", var->pixclock); + return -EINVAL; + } + + if (var->xres < 320) + var->xres = 320; + if (var->yres < 240) + var->yres = 240; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->bits_per_pixel <= 16) + var->bits_per_pixel = 16; + else if (var->bits_per_pixel <= 24) + var->bits_per_pixel = 24; + else if (var->bits_per_pixel <= 32) + var->bits_per_pixel = 32; + + set_color_bitfields(var); + + if (var->vmode & FB_VMODE_YWRAP) { + if (var->xoffset || var->yoffset < 0 || + var->yoffset >= var->yres_virtual) { + var->xoffset = var->yoffset = 0; + } else { + if (var->xoffset > var->xres_virtual - var->xres || + var->yoffset > var->yres_virtual - var->yres || + var->xoffset < 0 || var->yoffset < 0) + var->xoffset = var->yoffset = 0; + } + } else { + var->xoffset = var->yoffset = 0; + } + + /* + * XXX: Need to be more creative with this (i.e. allow doublecan for + * PAL/NTSC output). + */ + if (var->yres < 480 && video_output == VO_VGA) + var->vmode |= FB_VMODE_DOUBLE; + + if (video_output != VO_VGA) { + var->sync |= FB_SYNC_BROADCAST; + var->vmode |= FB_VMODE_INTERLACED; + } else { + var->sync &= ~FB_SYNC_BROADCAST; + var->vmode &= ~FB_VMODE_INTERLACED; + var->vmode |= FB_VMODE_NONINTERLACED; + } + + if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_TEST) { + var->right_margin = par->borderstop_h - + (par->diwstart_h + var->xres); + var->left_margin = par->diwstart_h - par->borderstart_h; + var->hsync_len = par->borderstart_h + + (par->hsync_total - par->borderstop_h); + + var->upper_margin = par->diwstart_v - par->borderstart_v; + var->lower_margin = par->borderstop_v - + (par->diwstart_v + var->yres); + var->vsync_len = par->borderstop_v + + (par->vsync_total - par->borderstop_v); + } + + hsync_total = var->left_margin + var->xres + var->right_margin + + var->hsync_len; + vtotal = var->upper_margin + var->yres + var->lower_margin + + var->vsync_len; + + if (var->sync & FB_SYNC_BROADCAST) { + if (var->vmode & FB_VMODE_INTERLACED) + vtotal /= 2; + if (vtotal > (PAL_VTOTAL + NTSC_VTOTAL)/2) { + /* PAL video output */ + /* XXX: Should be using a range here ... ? */ + if (hsync_total != PAL_HTOTAL) { + pr_debug("invalid hsync total for PAL\n"); + return -EINVAL; + } + } else { + /* NTSC video output */ + if (hsync_total != NTSC_HTOTAL) { + pr_debug("invalid hsync total for NTSC\n"); + return -EINVAL; + } + } + } + + /* Check memory sizes */ + line_length = get_line_length(var->xres_virtual, var->bits_per_pixel); + if (line_length * var->yres_virtual > info->fix.smem_len) + return -ENOMEM; + + return 0; +} + +static void pvr2_update_display(struct fb_info *info) +{ + struct pvr2fb_par *par = (struct pvr2fb_par *) info->par; + struct fb_var_screeninfo *var = &info->var; + + /* Update the start address of the display image */ + fb_writel(par->disp_start, DISP_DIWADDRL); + fb_writel(par->disp_start + + get_line_length(var->xoffset+var->xres, var->bits_per_pixel), + DISP_DIWADDRS); +} + +/* + * Initialize the video mode. Currently, the 16bpp and 24bpp modes aren't + * very stable. It's probably due to the fact that a lot of the 2D video + * registers are still undocumented. + */ + +static void pvr2_init_display(struct fb_info *info) +{ + struct pvr2fb_par *par = (struct pvr2fb_par *) info->par; + struct fb_var_screeninfo *var = &info->var; + unsigned int diw_height, diw_width, diw_modulo = 1; + unsigned int bytesperpixel = var->bits_per_pixel >> 3; + + /* hsync and vsync totals */ + fb_writel((par->vsync_total << 16) | par->hsync_total, DISP_SYNCSIZE); + + /* column height, modulo, row width */ + /* since we're "panning" within vram, we need to offset things based + * on the offset from the virtual x start to our real gfx. */ + if (video_output != VO_VGA && par->is_interlaced) + diw_modulo += info->fix.line_length / 4; + diw_height = (par->is_interlaced ? var->yres / 2 : var->yres); + diw_width = get_line_length(var->xres, var->bits_per_pixel) / 4; + fb_writel((diw_modulo << 20) | (--diw_height << 10) | --diw_width, + DISP_DIWSIZE); + + /* display address, long and short fields */ + fb_writel(par->disp_start, DISP_DIWADDRL); + fb_writel(par->disp_start + + get_line_length(var->xoffset+var->xres, var->bits_per_pixel), + DISP_DIWADDRS); + + /* border horizontal, border vertical, border color */ + fb_writel((par->borderstart_h << 16) | par->borderstop_h, DISP_BRDRHORZ); + fb_writel((par->borderstart_v << 16) | par->borderstop_v, DISP_BRDRVERT); + fb_writel(0, DISP_BRDRCOLR); + + /* display window start position */ + fb_writel(par->diwstart_h, DISP_DIWHSTRT); + fb_writel((par->diwstart_v << 16) | par->diwstart_v, DISP_DIWVSTRT); + + /* misc. settings */ + fb_writel((0x16 << 16) | par->is_lowres, DISP_DIWCONF); + + /* clock doubler (for VGA), scan doubler, display enable */ + fb_writel(((video_output == VO_VGA) << 23) | + (par->is_doublescan << 1) | 1, DISP_DIWMODE); + + /* bits per pixel */ + fb_writel(fb_readl(DISP_DIWMODE) | (--bytesperpixel << 2), DISP_DIWMODE); + fb_writel(bytesperpixel << 2, DISP_PIXDEPTH); + + /* video enable, color sync, interlace, + * hsync and vsync polarity (currently unused) */ + fb_writel(0x100 | ((par->is_interlaced /*|4*/) << 4), DISP_SYNCCONF); +} + +/* Simulate blanking by making the border cover the entire screen */ + +#define BLANK_BIT (1<<3) + +static void pvr2_do_blank(void) +{ + struct pvr2fb_par *par = currentpar; + unsigned long diwconf; + + diwconf = fb_readl(DISP_DIWCONF); + if (do_blank > 0) + fb_writel(diwconf | BLANK_BIT, DISP_DIWCONF); + else + fb_writel(diwconf & ~BLANK_BIT, DISP_DIWCONF); + + is_blanked = do_blank > 0 ? do_blank : 0; +} + +static irqreturn_t pvr2fb_interrupt(int irq, void *dev_id) +{ + struct fb_info *info = dev_id; + + if (do_vmode_pan || do_vmode_full) + pvr2_update_display(info); + if (do_vmode_full) + pvr2_init_display(info); + if (do_vmode_pan) + do_vmode_pan = 0; + if (do_vmode_full) + do_vmode_full = 0; + if (do_blank) { + pvr2_do_blank(); + do_blank = 0; + } + return IRQ_HANDLED; +} + +/* + * Determine the cable type and initialize the cable output format. Don't do + * anything if the cable type has been overidden (via "cable:XX"). + */ + +#define PCTRA 0xff80002c +#define PDTRA 0xff800030 +#define VOUTC 0xa0702c00 + +static int pvr2_init_cable(void) +{ + if (cable_type < 0) { + fb_writel((fb_readl(PCTRA) & 0xfff0ffff) | 0x000a0000, + PCTRA); + cable_type = (fb_readw(PDTRA) >> 8) & 3; + } + + /* Now select the output format (either composite or other) */ + /* XXX: Save the previous val first, as this reg is also AICA + related */ + if (cable_type == CT_COMPOSITE) + fb_writel(3 << 8, VOUTC); + else if (cable_type == CT_RGB) + fb_writel(1 << 9, VOUTC); + else + fb_writel(0, VOUTC); + + return cable_type; +} + +#ifdef CONFIG_PVR2_DMA +static ssize_t pvr2fb_write(struct fb_info *info, const char *buf, + size_t count, loff_t *ppos) +{ + unsigned long dst, start, end, len; + unsigned int nr_pages; + struct page **pages; + int ret, i; + + nr_pages = (count + PAGE_SIZE - 1) >> PAGE_SHIFT; + + pages = kmalloc(nr_pages * sizeof(struct page *), GFP_KERNEL); + if (!pages) + return -ENOMEM; + + down_read(¤t->mm->mmap_sem); + ret = get_user_pages(current, current->mm, (unsigned long)buf, + nr_pages, WRITE, 0, pages, NULL); + up_read(¤t->mm->mmap_sem); + + if (ret < nr_pages) { + nr_pages = ret; + ret = -EINVAL; + goto out_unmap; + } + + dma_configure_channel(shdma, 0x12c1); + + dst = (unsigned long)fb_info->screen_base + *ppos; + start = (unsigned long)page_address(pages[0]); + end = (unsigned long)page_address(pages[nr_pages]); + len = nr_pages << PAGE_SHIFT; + + /* Half-assed contig check */ + if (start + len == end) { + /* As we do this in one shot, it's either all or nothing.. */ + if ((*ppos + len) > fb_info->fix.smem_len) { + ret = -ENOSPC; + goto out_unmap; + } + + dma_write(shdma, start, 0, len); + dma_write(pvr2dma, 0, dst, len); + dma_wait_for_completion(pvr2dma); + + goto out; + } + + /* Not contiguous, writeout per-page instead.. */ + for (i = 0; i < nr_pages; i++, dst += PAGE_SIZE) { + if ((*ppos + (i << PAGE_SHIFT)) > fb_info->fix.smem_len) { + ret = -ENOSPC; + goto out_unmap; + } + + dma_write_page(shdma, (unsigned long)page_address(pages[i]), 0); + dma_write_page(pvr2dma, 0, dst); + dma_wait_for_completion(pvr2dma); + } + +out: + *ppos += count; + ret = count; + +out_unmap: + for (i = 0; i < nr_pages; i++) + page_cache_release(pages[i]); + + kfree(pages); + + return ret; +} +#endif /* CONFIG_PVR2_DMA */ + +/** + * pvr2fb_common_init + * + * Common init code for the PVR2 chips. + * + * This mostly takes care of the common aspects of the fb setup and + * registration. It's expected that the board-specific init code has + * already setup pvr2_fix with something meaningful at this point. + * + * Device info reporting is also done here, as well as picking a sane + * default from the modedb. For board-specific modelines, simply define + * a per-board modedb. + * + * Also worth noting is that the cable and video output types are likely + * always going to be VGA for the PCI-based PVR2 boards, but we leave this + * in for flexibility anyways. Who knows, maybe someone has tv-out on a + * PCI-based version of these things ;-) + */ +static int pvr2fb_common_init(void) +{ + struct pvr2fb_par *par = currentpar; + unsigned long modememused, rev; + + fb_info->screen_base = ioremap_nocache(pvr2_fix.smem_start, + pvr2_fix.smem_len); + + if (!fb_info->screen_base) { + printk(KERN_ERR "pvr2fb: Failed to remap smem space\n"); + goto out_err; + } + + par->mmio_base = (unsigned long)ioremap_nocache(pvr2_fix.mmio_start, + pvr2_fix.mmio_len); + if (!par->mmio_base) { + printk(KERN_ERR "pvr2fb: Failed to remap mmio space\n"); + goto out_err; + } + + fb_memset(fb_info->screen_base, 0, pvr2_fix.smem_len); + + pvr2_fix.ypanstep = nopan ? 0 : 1; + pvr2_fix.ywrapstep = nowrap ? 0 : 1; + + fb_info->fbops = &pvr2fb_ops; + fb_info->fix = pvr2_fix; + fb_info->par = currentpar; + fb_info->pseudo_palette = currentpar->palette; + fb_info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + if (video_output == VO_VGA) + defmode = DEFMODE_VGA; + + if (!mode_option) + mode_option = "640x480@60"; + + if (!fb_find_mode(&fb_info->var, fb_info, mode_option, pvr2_modedb, + NUM_TOTAL_MODES, &pvr2_modedb[defmode], 16)) + fb_info->var = pvr2_var; + + fb_alloc_cmap(&fb_info->cmap, 256, 0); + + if (register_framebuffer(fb_info) < 0) + goto out_err; + /*Must write PIXDEPTH to register before anything is displayed - so force init */ + pvr2_init_display(fb_info); + + modememused = get_line_length(fb_info->var.xres_virtual, + fb_info->var.bits_per_pixel); + modememused *= fb_info->var.yres_virtual; + + rev = fb_readl(par->mmio_base + 0x04); + + fb_info(fb_info, "%s (rev %ld.%ld) frame buffer device, using %ldk/%ldk of video memory\n", + fb_info->fix.id, (rev >> 4) & 0x0f, rev & 0x0f, + modememused >> 10, + (unsigned long)(fb_info->fix.smem_len >> 10)); + fb_info(fb_info, "Mode %dx%d-%d pitch = %ld cable: %s video output: %s\n", + fb_info->var.xres, fb_info->var.yres, + fb_info->var.bits_per_pixel, + get_line_length(fb_info->var.xres, fb_info->var.bits_per_pixel), + (char *)pvr2_get_param(cables, NULL, cable_type, 3), + (char *)pvr2_get_param(outputs, NULL, video_output, 3)); + +#ifdef CONFIG_SH_STORE_QUEUES + fb_notice(fb_info, "registering with SQ API\n"); + + pvr2fb_map = sq_remap(fb_info->fix.smem_start, fb_info->fix.smem_len, + fb_info->fix.id, PAGE_SHARED); + + fb_notice(fb_info, "Mapped video memory to SQ addr 0x%lx\n", + pvr2fb_map); +#endif + + return 0; + +out_err: + if (fb_info->screen_base) + iounmap(fb_info->screen_base); + if (par->mmio_base) + iounmap((void *)par->mmio_base); + + return -ENXIO; +} + +#ifdef CONFIG_SH_DREAMCAST +static int __init pvr2fb_dc_init(void) +{ + if (!mach_is_dreamcast()) + return -ENXIO; + + /* Make a guess at the monitor based on the attached cable */ + if (pvr2_init_cable() == CT_VGA) { + fb_info->monspecs.hfmin = 30000; + fb_info->monspecs.hfmax = 70000; + fb_info->monspecs.vfmin = 60; + fb_info->monspecs.vfmax = 60; + } else { + /* Not VGA, using a TV (taken from acornfb) */ + fb_info->monspecs.hfmin = 15469; + fb_info->monspecs.hfmax = 15781; + fb_info->monspecs.vfmin = 49; + fb_info->monspecs.vfmax = 51; + } + + /* + * XXX: This needs to pull default video output via BIOS or other means + */ + if (video_output < 0) { + if (cable_type == CT_VGA) { + video_output = VO_VGA; + } else { + video_output = VO_NTSC; + } + } + + /* + * Nothing exciting about the DC PVR2 .. only a measly 8MiB. + */ + pvr2_fix.smem_start = 0xa5000000; /* RAM starts here */ + pvr2_fix.smem_len = 8 << 20; + + pvr2_fix.mmio_start = 0xa05f8000; /* registers start here */ + pvr2_fix.mmio_len = 0x2000; + + if (request_irq(HW_EVENT_VSYNC, pvr2fb_interrupt, IRQF_SHARED, + "pvr2 VBL handler", fb_info)) { + return -EBUSY; + } + +#ifdef CONFIG_PVR2_DMA + if (request_dma(pvr2dma, "pvr2") != 0) { + free_irq(HW_EVENT_VSYNC, fb_info); + return -EBUSY; + } +#endif + + return pvr2fb_common_init(); +} + +static void __exit pvr2fb_dc_exit(void) +{ + if (fb_info->screen_base) { + iounmap(fb_info->screen_base); + fb_info->screen_base = NULL; + } + if (currentpar->mmio_base) { + iounmap((void *)currentpar->mmio_base); + currentpar->mmio_base = 0; + } + + free_irq(HW_EVENT_VSYNC, fb_info); +#ifdef CONFIG_PVR2_DMA + free_dma(pvr2dma); +#endif +} +#endif /* CONFIG_SH_DREAMCAST */ + +#ifdef CONFIG_PCI +static int pvr2fb_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int ret; + + ret = pci_enable_device(pdev); + if (ret) { + printk(KERN_ERR "pvr2fb: PCI enable failed\n"); + return ret; + } + + ret = pci_request_regions(pdev, "pvr2fb"); + if (ret) { + printk(KERN_ERR "pvr2fb: PCI request regions failed\n"); + return ret; + } + + /* + * Slightly more exciting than the DC PVR2 .. 16MiB! + */ + pvr2_fix.smem_start = pci_resource_start(pdev, 0); + pvr2_fix.smem_len = pci_resource_len(pdev, 0); + + pvr2_fix.mmio_start = pci_resource_start(pdev, 1); + pvr2_fix.mmio_len = pci_resource_len(pdev, 1); + + fb_info->device = &pdev->dev; + + return pvr2fb_common_init(); +} + +static void pvr2fb_pci_remove(struct pci_dev *pdev) +{ + if (fb_info->screen_base) { + iounmap(fb_info->screen_base); + fb_info->screen_base = NULL; + } + if (currentpar->mmio_base) { + iounmap((void *)currentpar->mmio_base); + currentpar->mmio_base = 0; + } + + pci_release_regions(pdev); +} + +static struct pci_device_id pvr2fb_pci_tbl[] = { + { PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_NEON250, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, pvr2fb_pci_tbl); + +static struct pci_driver pvr2fb_pci_driver = { + .name = "pvr2fb", + .id_table = pvr2fb_pci_tbl, + .probe = pvr2fb_pci_probe, + .remove = pvr2fb_pci_remove, +}; + +static int __init pvr2fb_pci_init(void) +{ + return pci_register_driver(&pvr2fb_pci_driver); +} + +static void __exit pvr2fb_pci_exit(void) +{ + pci_unregister_driver(&pvr2fb_pci_driver); +} +#endif /* CONFIG_PCI */ + +static int pvr2_get_param(const struct pvr2_params *p, const char *s, int val, + int size) +{ + int i; + + for (i = 0 ; i < size ; i++ ) { + if (s != NULL) { + if (!strnicmp(p[i].name, s, strlen(s))) + return p[i].val; + } else { + if (p[i].val == val) + return (int)p[i].name; + } + } + return -1; +} + +/* + * Parse command arguments. Supported arguments are: + * inverse Use inverse color maps + * cable:composite|rgb|vga Override the video cable type + * output:NTSC|PAL|VGA Override the video output format + * + * <xres>x<yres>[-<bpp>][@<refresh>] or, + * <name>[-<bpp>][@<refresh>] Startup using this video mode + */ + +#ifndef MODULE +static int __init pvr2fb_setup(char *options) +{ + char *this_opt; + char cable_arg[80]; + char output_arg[80]; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ","))) { + if (!*this_opt) + continue; + if (!strcmp(this_opt, "inverse")) { + fb_invert_cmaps(); + } else if (!strncmp(this_opt, "cable:", 6)) { + strcpy(cable_arg, this_opt + 6); + } else if (!strncmp(this_opt, "output:", 7)) { + strcpy(output_arg, this_opt + 7); + } else if (!strncmp(this_opt, "nopan", 5)) { + nopan = 1; + } else if (!strncmp(this_opt, "nowrap", 6)) { + nowrap = 1; + } else { + mode_option = this_opt; + } + } + + if (*cable_arg) + cable_type = pvr2_get_param(cables, cable_arg, 0, 3); + if (*output_arg) + video_output = pvr2_get_param(outputs, output_arg, 0, 3); + + return 0; +} +#endif + +static struct pvr2_board { + int (*init)(void); + void (*exit)(void); + char name[16]; +} board_driver[] __refdata = { +#ifdef CONFIG_SH_DREAMCAST + { pvr2fb_dc_init, pvr2fb_dc_exit, "Sega DC PVR2" }, +#endif +#ifdef CONFIG_PCI + { pvr2fb_pci_init, pvr2fb_pci_exit, "PCI PVR2" }, +#endif + { 0, }, +}; + +static int __init pvr2fb_init(void) +{ + int i, ret = -ENODEV; + int size; + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("pvr2fb", &option)) + return -ENODEV; + pvr2fb_setup(option); +#endif + size = sizeof(struct fb_info) + sizeof(struct pvr2fb_par) + 16 * sizeof(u32); + + fb_info = framebuffer_alloc(sizeof(struct pvr2fb_par), NULL); + + if (!fb_info) { + printk(KERN_ERR "Failed to allocate memory for fb_info\n"); + return -ENOMEM; + } + + + currentpar = fb_info->par; + + for (i = 0; i < ARRAY_SIZE(board_driver); i++) { + struct pvr2_board *pvr_board = board_driver + i; + + if (!pvr_board->init) + continue; + + ret = pvr_board->init(); + + if (ret != 0) { + printk(KERN_ERR "pvr2fb: Failed init of %s device\n", + pvr_board->name); + framebuffer_release(fb_info); + break; + } + } + + return ret; +} + +static void __exit pvr2fb_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(board_driver); i++) { + struct pvr2_board *pvr_board = board_driver + i; + + if (pvr_board->exit) + pvr_board->exit(); + } + +#ifdef CONFIG_SH_STORE_QUEUES + sq_unmap(pvr2fb_map); +#endif + + unregister_framebuffer(fb_info); + framebuffer_release(fb_info); +} + +module_init(pvr2fb_init); +module_exit(pvr2fb_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>, M. R. Brown <mrbrown@0xd6.org>"); +MODULE_DESCRIPTION("Framebuffer driver for NEC PowerVR 2 based graphics boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/pxa168fb.c b/drivers/video/fbdev/pxa168fb.c new file mode 100644 index 000000000000..c95b9e46d48f --- /dev/null +++ b/drivers/video/fbdev/pxa168fb.c @@ -0,0 +1,837 @@ +/* + * linux/drivers/video/pxa168fb.c -- Marvell PXA168 LCD Controller + * + * Copyright (C) 2008 Marvell International Ltd. + * All rights reserved. + * + * 2009-02-16 adapted from original version for PXA168/910 + * Jun Nie <njun@marvell.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/sched.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/uaccess.h> +#include <video/pxa168fb.h> + +#include "pxa168fb.h" + +#define DEFAULT_REFRESH 60 /* Hz */ + +static int determine_best_pix_fmt(struct fb_var_screeninfo *var) +{ + /* + * Pseudocolor mode? + */ + if (var->bits_per_pixel == 8) + return PIX_FMT_PSEUDOCOLOR; + + /* + * Check for 565/1555. + */ + if (var->bits_per_pixel == 16 && var->red.length <= 5 && + var->green.length <= 6 && var->blue.length <= 5) { + if (var->transp.length == 0) { + if (var->red.offset >= var->blue.offset) + return PIX_FMT_RGB565; + else + return PIX_FMT_BGR565; + } + + if (var->transp.length == 1 && var->green.length <= 5) { + if (var->red.offset >= var->blue.offset) + return PIX_FMT_RGB1555; + else + return PIX_FMT_BGR1555; + } + + /* fall through */ + } + + /* + * Check for 888/A888. + */ + if (var->bits_per_pixel <= 32 && var->red.length <= 8 && + var->green.length <= 8 && var->blue.length <= 8) { + if (var->bits_per_pixel == 24 && var->transp.length == 0) { + if (var->red.offset >= var->blue.offset) + return PIX_FMT_RGB888PACK; + else + return PIX_FMT_BGR888PACK; + } + + if (var->bits_per_pixel == 32 && var->transp.length == 8) { + if (var->red.offset >= var->blue.offset) + return PIX_FMT_RGBA888; + else + return PIX_FMT_BGRA888; + } else { + if (var->red.offset >= var->blue.offset) + return PIX_FMT_RGB888UNPACK; + else + return PIX_FMT_BGR888UNPACK; + } + + /* fall through */ + } + + return -EINVAL; +} + +static void set_pix_fmt(struct fb_var_screeninfo *var, int pix_fmt) +{ + switch (pix_fmt) { + case PIX_FMT_RGB565: + var->bits_per_pixel = 16; + var->red.offset = 11; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 0; var->blue.length = 5; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIX_FMT_BGR565: + var->bits_per_pixel = 16; + var->red.offset = 0; var->red.length = 5; + var->green.offset = 5; var->green.length = 6; + var->blue.offset = 11; var->blue.length = 5; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIX_FMT_RGB1555: + var->bits_per_pixel = 16; + var->red.offset = 10; var->red.length = 5; + var->green.offset = 5; var->green.length = 5; + var->blue.offset = 0; var->blue.length = 5; + var->transp.offset = 15; var->transp.length = 1; + break; + case PIX_FMT_BGR1555: + var->bits_per_pixel = 16; + var->red.offset = 0; var->red.length = 5; + var->green.offset = 5; var->green.length = 5; + var->blue.offset = 10; var->blue.length = 5; + var->transp.offset = 15; var->transp.length = 1; + break; + case PIX_FMT_RGB888PACK: + var->bits_per_pixel = 24; + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIX_FMT_BGR888PACK: + var->bits_per_pixel = 24; + var->red.offset = 0; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 16; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + case PIX_FMT_RGBA888: + var->bits_per_pixel = 32; + var->red.offset = 16; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 24; var->transp.length = 8; + break; + case PIX_FMT_BGRA888: + var->bits_per_pixel = 32; + var->red.offset = 0; var->red.length = 8; + var->green.offset = 8; var->green.length = 8; + var->blue.offset = 16; var->blue.length = 8; + var->transp.offset = 24; var->transp.length = 8; + break; + case PIX_FMT_PSEUDOCOLOR: + var->bits_per_pixel = 8; + var->red.offset = 0; var->red.length = 8; + var->green.offset = 0; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 0; + break; + } +} + +static void set_mode(struct pxa168fb_info *fbi, struct fb_var_screeninfo *var, + struct fb_videomode *mode, int pix_fmt, int ystretch) +{ + struct fb_info *info = fbi->info; + + set_pix_fmt(var, pix_fmt); + + var->xres = mode->xres; + var->yres = mode->yres; + var->xres_virtual = max(var->xres, var->xres_virtual); + if (ystretch) + var->yres_virtual = info->fix.smem_len / + (var->xres_virtual * (var->bits_per_pixel >> 3)); + else + var->yres_virtual = max(var->yres, var->yres_virtual); + var->grayscale = 0; + var->accel_flags = FB_ACCEL_NONE; + var->pixclock = mode->pixclock; + var->left_margin = mode->left_margin; + var->right_margin = mode->right_margin; + var->upper_margin = mode->upper_margin; + var->lower_margin = mode->lower_margin; + var->hsync_len = mode->hsync_len; + var->vsync_len = mode->vsync_len; + var->sync = mode->sync; + var->vmode = FB_VMODE_NONINTERLACED; + var->rotate = FB_ROTATE_UR; +} + +static int pxa168fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct pxa168fb_info *fbi = info->par; + int pix_fmt; + + /* + * Determine which pixel format we're going to use. + */ + pix_fmt = determine_best_pix_fmt(var); + if (pix_fmt < 0) + return pix_fmt; + set_pix_fmt(var, pix_fmt); + fbi->pix_fmt = pix_fmt; + + /* + * Basic geometry sanity checks. + */ + if (var->xoffset + var->xres > var->xres_virtual) + return -EINVAL; + if (var->yoffset + var->yres > var->yres_virtual) + return -EINVAL; + if (var->xres + var->right_margin + + var->hsync_len + var->left_margin > 2048) + return -EINVAL; + if (var->yres + var->lower_margin + + var->vsync_len + var->upper_margin > 2048) + return -EINVAL; + + /* + * Check size of framebuffer. + */ + if (var->xres_virtual * var->yres_virtual * + (var->bits_per_pixel >> 3) > info->fix.smem_len) + return -EINVAL; + + return 0; +} + +/* + * The hardware clock divider has an integer and a fractional + * stage: + * + * clk2 = clk_in / integer_divider + * clk_out = clk2 * (1 - (fractional_divider >> 12)) + * + * Calculate integer and fractional divider for given clk_in + * and clk_out. + */ +static void set_clock_divider(struct pxa168fb_info *fbi, + const struct fb_videomode *m) +{ + int divider_int; + int needed_pixclk; + u64 div_result; + u32 x = 0; + + /* + * Notice: The field pixclock is used by linux fb + * is in pixel second. E.g. struct fb_videomode & + * struct fb_var_screeninfo + */ + + /* + * Check input values. + */ + if (!m || !m->pixclock || !m->refresh) { + dev_err(fbi->dev, "Input refresh or pixclock is wrong.\n"); + return; + } + + /* + * Using PLL/AXI clock. + */ + x = 0x80000000; + + /* + * Calc divider according to refresh rate. + */ + div_result = 1000000000000ll; + do_div(div_result, m->pixclock); + needed_pixclk = (u32)div_result; + + divider_int = clk_get_rate(fbi->clk) / needed_pixclk; + + /* check whether divisor is too small. */ + if (divider_int < 2) { + dev_warn(fbi->dev, "Warning: clock source is too slow." + "Try smaller resolution\n"); + divider_int = 2; + } + + /* + * Set setting to reg. + */ + x |= divider_int; + writel(x, fbi->reg_base + LCD_CFG_SCLK_DIV); +} + +static void set_dma_control0(struct pxa168fb_info *fbi) +{ + u32 x; + + /* + * Set bit to enable graphics DMA. + */ + x = readl(fbi->reg_base + LCD_SPU_DMA_CTRL0); + x &= ~CFG_GRA_ENA_MASK; + x |= fbi->active ? CFG_GRA_ENA(1) : CFG_GRA_ENA(0); + + /* + * If we are in a pseudo-color mode, we need to enable + * palette lookup. + */ + if (fbi->pix_fmt == PIX_FMT_PSEUDOCOLOR) + x |= 0x10000000; + + /* + * Configure hardware pixel format. + */ + x &= ~(0xF << 16); + x |= (fbi->pix_fmt >> 1) << 16; + + /* + * Check red and blue pixel swap. + * 1. source data swap + * 2. panel output data swap + */ + x &= ~(1 << 12); + x |= ((fbi->pix_fmt & 1) ^ (fbi->panel_rbswap)) << 12; + + writel(x, fbi->reg_base + LCD_SPU_DMA_CTRL0); +} + +static void set_dma_control1(struct pxa168fb_info *fbi, int sync) +{ + u32 x; + + /* + * Configure default bits: vsync triggers DMA, gated clock + * enable, power save enable, configure alpha registers to + * display 100% graphics, and set pixel command. + */ + x = readl(fbi->reg_base + LCD_SPU_DMA_CTRL1); + x |= 0x2032ff81; + + /* + * We trigger DMA on the falling edge of vsync if vsync is + * active low, or on the rising edge if vsync is active high. + */ + if (!(sync & FB_SYNC_VERT_HIGH_ACT)) + x |= 0x08000000; + + writel(x, fbi->reg_base + LCD_SPU_DMA_CTRL1); +} + +static void set_graphics_start(struct fb_info *info, int xoffset, int yoffset) +{ + struct pxa168fb_info *fbi = info->par; + struct fb_var_screeninfo *var = &info->var; + int pixel_offset; + unsigned long addr; + + pixel_offset = (yoffset * var->xres_virtual) + xoffset; + + addr = fbi->fb_start_dma + (pixel_offset * (var->bits_per_pixel >> 3)); + writel(addr, fbi->reg_base + LCD_CFG_GRA_START_ADDR0); +} + +static void set_dumb_panel_control(struct fb_info *info) +{ + struct pxa168fb_info *fbi = info->par; + struct pxa168fb_mach_info *mi = dev_get_platdata(fbi->dev); + u32 x; + + /* + * Preserve enable flag. + */ + x = readl(fbi->reg_base + LCD_SPU_DUMB_CTRL) & 0x00000001; + + x |= (fbi->is_blanked ? 0x7 : mi->dumb_mode) << 28; + x |= mi->gpio_output_data << 20; + x |= mi->gpio_output_mask << 12; + x |= mi->panel_rgb_reverse_lanes ? 0x00000080 : 0; + x |= mi->invert_composite_blank ? 0x00000040 : 0; + x |= (info->var.sync & FB_SYNC_COMP_HIGH_ACT) ? 0x00000020 : 0; + x |= mi->invert_pix_val_ena ? 0x00000010 : 0; + x |= (info->var.sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : 0x00000008; + x |= (info->var.sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : 0x00000004; + x |= mi->invert_pixclock ? 0x00000002 : 0; + + writel(x, fbi->reg_base + LCD_SPU_DUMB_CTRL); +} + +static void set_dumb_screen_dimensions(struct fb_info *info) +{ + struct pxa168fb_info *fbi = info->par; + struct fb_var_screeninfo *v = &info->var; + int x; + int y; + + x = v->xres + v->right_margin + v->hsync_len + v->left_margin; + y = v->yres + v->lower_margin + v->vsync_len + v->upper_margin; + + writel((y << 16) | x, fbi->reg_base + LCD_SPUT_V_H_TOTAL); +} + +static int pxa168fb_set_par(struct fb_info *info) +{ + struct pxa168fb_info *fbi = info->par; + struct fb_var_screeninfo *var = &info->var; + struct fb_videomode mode; + u32 x; + struct pxa168fb_mach_info *mi; + + mi = dev_get_platdata(fbi->dev); + + /* + * Set additional mode info. + */ + if (fbi->pix_fmt == PIX_FMT_PSEUDOCOLOR) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; + info->fix.ypanstep = var->yres; + + /* + * Disable panel output while we setup the display. + */ + x = readl(fbi->reg_base + LCD_SPU_DUMB_CTRL); + writel(x & ~1, fbi->reg_base + LCD_SPU_DUMB_CTRL); + + /* + * Configure global panel parameters. + */ + writel((var->yres << 16) | var->xres, + fbi->reg_base + LCD_SPU_V_H_ACTIVE); + + /* + * convet var to video mode + */ + fb_var_to_videomode(&mode, &info->var); + + /* Calculate clock divisor. */ + set_clock_divider(fbi, &mode); + + /* Configure dma ctrl regs. */ + set_dma_control0(fbi); + set_dma_control1(fbi, info->var.sync); + + /* + * Configure graphics DMA parameters. + */ + x = readl(fbi->reg_base + LCD_CFG_GRA_PITCH); + x = (x & ~0xFFFF) | ((var->xres_virtual * var->bits_per_pixel) >> 3); + writel(x, fbi->reg_base + LCD_CFG_GRA_PITCH); + writel((var->yres << 16) | var->xres, + fbi->reg_base + LCD_SPU_GRA_HPXL_VLN); + writel((var->yres << 16) | var->xres, + fbi->reg_base + LCD_SPU_GZM_HPXL_VLN); + + /* + * Configure dumb panel ctrl regs & timings. + */ + set_dumb_panel_control(info); + set_dumb_screen_dimensions(info); + + writel((var->left_margin << 16) | var->right_margin, + fbi->reg_base + LCD_SPU_H_PORCH); + writel((var->upper_margin << 16) | var->lower_margin, + fbi->reg_base + LCD_SPU_V_PORCH); + + /* + * Re-enable panel output. + */ + x = readl(fbi->reg_base + LCD_SPU_DUMB_CTRL); + writel(x | 1, fbi->reg_base + LCD_SPU_DUMB_CTRL); + + return 0; +} + +static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) +{ + return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset; +} + +static u32 to_rgb(u16 red, u16 green, u16 blue) +{ + red >>= 8; + green >>= 8; + blue >>= 8; + + return (red << 16) | (green << 8) | blue; +} + +static int +pxa168fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, + unsigned int blue, unsigned int trans, struct fb_info *info) +{ + struct pxa168fb_info *fbi = info->par; + u32 val; + + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR && 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); + fbi->pseudo_palette[regno] = val; + } + + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR && regno < 256) { + val = to_rgb(red, green, blue); + writel(val, fbi->reg_base + LCD_SPU_SRAM_WRDAT); + writel(0x8300 | regno, fbi->reg_base + LCD_SPU_SRAM_CTRL); + } + + return 0; +} + +static int pxa168fb_blank(int blank, struct fb_info *info) +{ + struct pxa168fb_info *fbi = info->par; + + fbi->is_blanked = (blank == FB_BLANK_UNBLANK) ? 0 : 1; + set_dumb_panel_control(info); + + return 0; +} + +static int pxa168fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + set_graphics_start(info, var->xoffset, var->yoffset); + + return 0; +} + +static irqreturn_t pxa168fb_handle_irq(int irq, void *dev_id) +{ + struct pxa168fb_info *fbi = dev_id; + u32 isr = readl(fbi->reg_base + SPU_IRQ_ISR); + + if ((isr & GRA_FRAME_IRQ0_ENA_MASK)) { + + writel(isr & (~GRA_FRAME_IRQ0_ENA_MASK), + fbi->reg_base + SPU_IRQ_ISR); + + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static struct fb_ops pxa168fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = pxa168fb_check_var, + .fb_set_par = pxa168fb_set_par, + .fb_setcolreg = pxa168fb_setcolreg, + .fb_blank = pxa168fb_blank, + .fb_pan_display = pxa168fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int pxa168fb_init_mode(struct fb_info *info, + struct pxa168fb_mach_info *mi) +{ + struct pxa168fb_info *fbi = info->par; + struct fb_var_screeninfo *var = &info->var; + int ret = 0; + u32 total_w, total_h, refresh; + u64 div_result; + const struct fb_videomode *m; + + /* + * Set default value + */ + refresh = DEFAULT_REFRESH; + + /* try to find best video mode. */ + m = fb_find_best_mode(&info->var, &info->modelist); + if (m) + fb_videomode_to_var(&info->var, m); + + /* Init settings. */ + var->xres_virtual = var->xres; + var->yres_virtual = info->fix.smem_len / + (var->xres_virtual * (var->bits_per_pixel >> 3)); + dev_dbg(fbi->dev, "pxa168fb: find best mode: res = %dx%d\n", + var->xres, var->yres); + + /* correct pixclock. */ + total_w = var->xres + var->left_margin + var->right_margin + + var->hsync_len; + total_h = var->yres + var->upper_margin + var->lower_margin + + var->vsync_len; + + div_result = 1000000000000ll; + do_div(div_result, total_w * total_h * refresh); + var->pixclock = (u32)div_result; + + return ret; +} + +static int pxa168fb_probe(struct platform_device *pdev) +{ + struct pxa168fb_mach_info *mi; + struct fb_info *info = 0; + struct pxa168fb_info *fbi = 0; + struct resource *res; + struct clk *clk; + int irq, ret; + + mi = dev_get_platdata(&pdev->dev); + if (mi == NULL) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -EINVAL; + } + + clk = clk_get(&pdev->dev, "LCDCLK"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "unable to get LCDCLK"); + return PTR_ERR(clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no IO memory defined\n"); + ret = -ENOENT; + goto failed_put_clk; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no IRQ defined\n"); + ret = -ENOENT; + goto failed_put_clk; + } + + info = framebuffer_alloc(sizeof(struct pxa168fb_info), &pdev->dev); + if (info == NULL) { + ret = -ENOMEM; + goto failed_put_clk; + } + + /* Initialize private data */ + fbi = info->par; + fbi->info = info; + fbi->clk = clk; + fbi->dev = info->dev = &pdev->dev; + fbi->panel_rbswap = mi->panel_rbswap; + fbi->is_blanked = 0; + fbi->active = mi->active; + + /* + * Initialise static fb parameters. + */ + info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | + FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; + info->node = -1; + strlcpy(info->fix.id, mi->id, 16); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.mmio_start = res->start; + info->fix.mmio_len = resource_size(res); + info->fix.accel = FB_ACCEL_NONE; + info->fbops = &pxa168fb_ops; + info->pseudo_palette = fbi->pseudo_palette; + + /* + * Map LCD controller registers. + */ + fbi->reg_base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (fbi->reg_base == NULL) { + ret = -ENOMEM; + goto failed_free_info; + } + + /* + * Allocate framebuffer memory. + */ + info->fix.smem_len = PAGE_ALIGN(DEFAULT_FB_SIZE); + + info->screen_base = dma_alloc_writecombine(fbi->dev, info->fix.smem_len, + &fbi->fb_start_dma, GFP_KERNEL); + if (info->screen_base == NULL) { + ret = -ENOMEM; + goto failed_free_info; + } + + info->fix.smem_start = (unsigned long)fbi->fb_start_dma; + set_graphics_start(info, 0, 0); + + /* + * Set video mode according to platform data. + */ + set_mode(fbi, &info->var, mi->modes, mi->pix_fmt, 1); + + fb_videomode_to_modelist(mi->modes, mi->num_modes, &info->modelist); + + /* + * init video mode data. + */ + pxa168fb_init_mode(info, mi); + + /* + * Fill in sane defaults. + */ + ret = pxa168fb_check_var(&info->var, info); + if (ret) + goto failed_free_fbmem; + + /* + * enable controller clock + */ + clk_enable(fbi->clk); + + pxa168fb_set_par(info); + + /* + * Configure default register values. + */ + writel(0, fbi->reg_base + LCD_SPU_BLANKCOLOR); + writel(mi->io_pin_allocation_mode, fbi->reg_base + SPU_IOPAD_CONTROL); + writel(0, fbi->reg_base + LCD_CFG_GRA_START_ADDR1); + writel(0, fbi->reg_base + LCD_SPU_GRA_OVSA_HPXL_VLN); + writel(0, fbi->reg_base + LCD_SPU_SRAM_PARA0); + writel(CFG_CSB_256x32(0x1)|CFG_CSB_256x24(0x1)|CFG_CSB_256x8(0x1), + fbi->reg_base + LCD_SPU_SRAM_PARA1); + + /* + * Allocate color map. + */ + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + ret = -ENOMEM; + goto failed_free_clk; + } + + /* + * Register irq handler. + */ + ret = devm_request_irq(&pdev->dev, irq, pxa168fb_handle_irq, + IRQF_SHARED, info->fix.id, fbi); + if (ret < 0) { + dev_err(&pdev->dev, "unable to request IRQ\n"); + ret = -ENXIO; + goto failed_free_cmap; + } + + /* + * Enable GFX interrupt + */ + writel(GRA_FRAME_IRQ0_ENA(0x1), fbi->reg_base + SPU_IRQ_ENA); + + /* + * Register framebuffer. + */ + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register pxa168-fb: %d\n", ret); + ret = -ENXIO; + goto failed_free_cmap; + } + + platform_set_drvdata(pdev, fbi); + return 0; + +failed_free_cmap: + fb_dealloc_cmap(&info->cmap); +failed_free_clk: + clk_disable(fbi->clk); +failed_free_fbmem: + dma_free_coherent(fbi->dev, info->fix.smem_len, + info->screen_base, fbi->fb_start_dma); +failed_free_info: + kfree(info); +failed_put_clk: + clk_put(clk); + + dev_err(&pdev->dev, "frame buffer device init failed with %d\n", ret); + return ret; +} + +static int pxa168fb_remove(struct platform_device *pdev) +{ + struct pxa168fb_info *fbi = platform_get_drvdata(pdev); + struct fb_info *info; + int irq; + unsigned int data; + + if (!fbi) + return 0; + + /* disable DMA transfer */ + data = readl(fbi->reg_base + LCD_SPU_DMA_CTRL0); + data &= ~CFG_GRA_ENA_MASK; + writel(data, fbi->reg_base + LCD_SPU_DMA_CTRL0); + + info = fbi->info; + + unregister_framebuffer(info); + + writel(GRA_FRAME_IRQ0_ENA(0x0), fbi->reg_base + SPU_IRQ_ENA); + + if (info->cmap.len) + fb_dealloc_cmap(&info->cmap); + + irq = platform_get_irq(pdev, 0); + + dma_free_writecombine(fbi->dev, PAGE_ALIGN(info->fix.smem_len), + info->screen_base, info->fix.smem_start); + + clk_disable(fbi->clk); + clk_put(fbi->clk); + + framebuffer_release(info); + + return 0; +} + +static struct platform_driver pxa168fb_driver = { + .driver = { + .name = "pxa168-fb", + .owner = THIS_MODULE, + }, + .probe = pxa168fb_probe, + .remove = pxa168fb_remove, +}; + +module_platform_driver(pxa168fb_driver); + +MODULE_AUTHOR("Lennert Buytenhek <buytenh@marvell.com> " + "Green Wan <gwan@marvell.com>"); +MODULE_DESCRIPTION("Framebuffer driver for PXA168/910"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/pxa168fb.h b/drivers/video/fbdev/pxa168fb.h new file mode 100644 index 000000000000..eee09279c524 --- /dev/null +++ b/drivers/video/fbdev/pxa168fb.h @@ -0,0 +1,558 @@ +#ifndef __PXA168FB_H__ +#define __PXA168FB_H__ + +/* ------------< LCD register >------------ */ +/* Video Frame 0&1 start address registers */ +#define LCD_SPU_DMA_START_ADDR_Y0 0x00C0 +#define LCD_SPU_DMA_START_ADDR_U0 0x00C4 +#define LCD_SPU_DMA_START_ADDR_V0 0x00C8 +#define LCD_CFG_DMA_START_ADDR_0 0x00CC /* Cmd address */ +#define LCD_SPU_DMA_START_ADDR_Y1 0x00D0 +#define LCD_SPU_DMA_START_ADDR_U1 0x00D4 +#define LCD_SPU_DMA_START_ADDR_V1 0x00D8 +#define LCD_CFG_DMA_START_ADDR_1 0x00DC /* Cmd address */ + +/* YC & UV Pitch */ +#define LCD_SPU_DMA_PITCH_YC 0x00E0 +#define SPU_DMA_PITCH_C(c) ((c) << 16) +#define SPU_DMA_PITCH_Y(y) (y) +#define LCD_SPU_DMA_PITCH_UV 0x00E4 +#define SPU_DMA_PITCH_V(v) ((v) << 16) +#define SPU_DMA_PITCH_U(u) (u) + +/* Video Starting Point on Screen Register */ +#define LCD_SPUT_DMA_OVSA_HPXL_VLN 0x00E8 +#define CFG_DMA_OVSA_VLN(y) ((y) << 16) /* 0~0xfff */ +#define CFG_DMA_OVSA_HPXL(x) (x) /* 0~0xfff */ + +/* Video Size Register */ +#define LCD_SPU_DMA_HPXL_VLN 0x00EC +#define CFG_DMA_VLN(y) ((y) << 16) +#define CFG_DMA_HPXL(x) (x) + +/* Video Size After zooming Register */ +#define LCD_SPU_DZM_HPXL_VLN 0x00F0 +#define CFG_DZM_VLN(y) ((y) << 16) +#define CFG_DZM_HPXL(x) (x) + +/* Graphic Frame 0&1 Starting Address Register */ +#define LCD_CFG_GRA_START_ADDR0 0x00F4 +#define LCD_CFG_GRA_START_ADDR1 0x00F8 + +/* Graphic Frame Pitch */ +#define LCD_CFG_GRA_PITCH 0x00FC + +/* Graphic Starting Point on Screen Register */ +#define LCD_SPU_GRA_OVSA_HPXL_VLN 0x0100 +#define CFG_GRA_OVSA_VLN(y) ((y) << 16) +#define CFG_GRA_OVSA_HPXL(x) (x) + +/* Graphic Size Register */ +#define LCD_SPU_GRA_HPXL_VLN 0x0104 +#define CFG_GRA_VLN(y) ((y) << 16) +#define CFG_GRA_HPXL(x) (x) + +/* Graphic Size after Zooming Register */ +#define LCD_SPU_GZM_HPXL_VLN 0x0108 +#define CFG_GZM_VLN(y) ((y) << 16) +#define CFG_GZM_HPXL(x) (x) + +/* HW Cursor Starting Point on Screen Register */ +#define LCD_SPU_HWC_OVSA_HPXL_VLN 0x010C +#define CFG_HWC_OVSA_VLN(y) ((y) << 16) +#define CFG_HWC_OVSA_HPXL(x) (x) + +/* HW Cursor Size */ +#define LCD_SPU_HWC_HPXL_VLN 0x0110 +#define CFG_HWC_VLN(y) ((y) << 16) +#define CFG_HWC_HPXL(x) (x) + +/* Total Screen Size Register */ +#define LCD_SPUT_V_H_TOTAL 0x0114 +#define CFG_V_TOTAL(y) ((y) << 16) +#define CFG_H_TOTAL(x) (x) + +/* Total Screen Active Size Register */ +#define LCD_SPU_V_H_ACTIVE 0x0118 +#define CFG_V_ACTIVE(y) ((y) << 16) +#define CFG_H_ACTIVE(x) (x) + +/* Screen H&V Porch Register */ +#define LCD_SPU_H_PORCH 0x011C +#define CFG_H_BACK_PORCH(b) ((b) << 16) +#define CFG_H_FRONT_PORCH(f) (f) +#define LCD_SPU_V_PORCH 0x0120 +#define CFG_V_BACK_PORCH(b) ((b) << 16) +#define CFG_V_FRONT_PORCH(f) (f) + +/* Screen Blank Color Register */ +#define LCD_SPU_BLANKCOLOR 0x0124 +#define CFG_BLANKCOLOR_MASK 0x00FFFFFF +#define CFG_BLANKCOLOR_R_MASK 0x000000FF +#define CFG_BLANKCOLOR_G_MASK 0x0000FF00 +#define CFG_BLANKCOLOR_B_MASK 0x00FF0000 + +/* HW Cursor Color 1&2 Register */ +#define LCD_SPU_ALPHA_COLOR1 0x0128 +#define CFG_HWC_COLOR1 0x00FFFFFF +#define CFG_HWC_COLOR1_R(red) ((red) << 16) +#define CFG_HWC_COLOR1_G(green) ((green) << 8) +#define CFG_HWC_COLOR1_B(blue) (blue) +#define CFG_HWC_COLOR1_R_MASK 0x000000FF +#define CFG_HWC_COLOR1_G_MASK 0x0000FF00 +#define CFG_HWC_COLOR1_B_MASK 0x00FF0000 +#define LCD_SPU_ALPHA_COLOR2 0x012C +#define CFG_HWC_COLOR2 0x00FFFFFF +#define CFG_HWC_COLOR2_R_MASK 0x000000FF +#define CFG_HWC_COLOR2_G_MASK 0x0000FF00 +#define CFG_HWC_COLOR2_B_MASK 0x00FF0000 + +/* Video YUV Color Key Control */ +#define LCD_SPU_COLORKEY_Y 0x0130 +#define CFG_CKEY_Y2(y2) ((y2) << 24) +#define CFG_CKEY_Y2_MASK 0xFF000000 +#define CFG_CKEY_Y1(y1) ((y1) << 16) +#define CFG_CKEY_Y1_MASK 0x00FF0000 +#define CFG_CKEY_Y(y) ((y) << 8) +#define CFG_CKEY_Y_MASK 0x0000FF00 +#define CFG_ALPHA_Y(y) (y) +#define CFG_ALPHA_Y_MASK 0x000000FF +#define LCD_SPU_COLORKEY_U 0x0134 +#define CFG_CKEY_U2(u2) ((u2) << 24) +#define CFG_CKEY_U2_MASK 0xFF000000 +#define CFG_CKEY_U1(u1) ((u1) << 16) +#define CFG_CKEY_U1_MASK 0x00FF0000 +#define CFG_CKEY_U(u) ((u) << 8) +#define CFG_CKEY_U_MASK 0x0000FF00 +#define CFG_ALPHA_U(u) (u) +#define CFG_ALPHA_U_MASK 0x000000FF +#define LCD_SPU_COLORKEY_V 0x0138 +#define CFG_CKEY_V2(v2) ((v2) << 24) +#define CFG_CKEY_V2_MASK 0xFF000000 +#define CFG_CKEY_V1(v1) ((v1) << 16) +#define CFG_CKEY_V1_MASK 0x00FF0000 +#define CFG_CKEY_V(v) ((v) << 8) +#define CFG_CKEY_V_MASK 0x0000FF00 +#define CFG_ALPHA_V(v) (v) +#define CFG_ALPHA_V_MASK 0x000000FF + +/* SPI Read Data Register */ +#define LCD_SPU_SPI_RXDATA 0x0140 + +/* Smart Panel Read Data Register */ +#define LCD_SPU_ISA_RSDATA 0x0144 +#define ISA_RXDATA_16BIT_1_DATA_MASK 0x000000FF +#define ISA_RXDATA_16BIT_2_DATA_MASK 0x0000FF00 +#define ISA_RXDATA_16BIT_3_DATA_MASK 0x00FF0000 +#define ISA_RXDATA_16BIT_4_DATA_MASK 0xFF000000 +#define ISA_RXDATA_32BIT_1_DATA_MASK 0x00FFFFFF + +/* HWC SRAM Read Data Register */ +#define LCD_SPU_HWC_RDDAT 0x0158 + +/* Gamma Table SRAM Read Data Register */ +#define LCD_SPU_GAMMA_RDDAT 0x015c +#define CFG_GAMMA_RDDAT_MASK 0x000000FF + +/* Palette Table SRAM Read Data Register */ +#define LCD_SPU_PALETTE_RDDAT 0x0160 +#define CFG_PALETTE_RDDAT_MASK 0x00FFFFFF + +/* I/O Pads Input Read Only Register */ +#define LCD_SPU_IOPAD_IN 0x0178 +#define CFG_IOPAD_IN_MASK 0x0FFFFFFF + +/* Reserved Read Only Registers */ +#define LCD_CFG_RDREG5F 0x017C +#define IRE_FRAME_CNT_MASK 0x000000C0 +#define IPE_FRAME_CNT_MASK 0x00000030 +#define GRA_FRAME_CNT_MASK 0x0000000C /* Graphic */ +#define DMA_FRAME_CNT_MASK 0x00000003 /* Video */ + +/* SPI Control Register. */ +#define LCD_SPU_SPI_CTRL 0x0180 +#define CFG_SCLKCNT(div) ((div) << 24) /* 0xFF~0x2 */ +#define CFG_SCLKCNT_MASK 0xFF000000 +#define CFG_RXBITS(rx) ((rx) << 16) /* 0x1F~0x1 */ +#define CFG_RXBITS_MASK 0x00FF0000 +#define CFG_TXBITS(tx) ((tx) << 8) /* 0x1F~0x1 */ +#define CFG_TXBITS_MASK 0x0000FF00 +#define CFG_CLKINV(clk) ((clk) << 7) +#define CFG_CLKINV_MASK 0x00000080 +#define CFG_KEEPXFER(transfer) ((transfer) << 6) +#define CFG_KEEPXFER_MASK 0x00000040 +#define CFG_RXBITSTO0(rx) ((rx) << 5) +#define CFG_RXBITSTO0_MASK 0x00000020 +#define CFG_TXBITSTO0(tx) ((tx) << 4) +#define CFG_TXBITSTO0_MASK 0x00000010 +#define CFG_SPI_ENA(spi) ((spi) << 3) +#define CFG_SPI_ENA_MASK 0x00000008 +#define CFG_SPI_SEL(spi) ((spi) << 2) +#define CFG_SPI_SEL_MASK 0x00000004 +#define CFG_SPI_3W4WB(wire) ((wire) << 1) +#define CFG_SPI_3W4WB_MASK 0x00000002 +#define CFG_SPI_START(start) (start) +#define CFG_SPI_START_MASK 0x00000001 + +/* SPI Tx Data Register */ +#define LCD_SPU_SPI_TXDATA 0x0184 + +/* + 1. Smart Pannel 8-bit Bus Control Register. + 2. AHB Slave Path Data Port Register +*/ +#define LCD_SPU_SMPN_CTRL 0x0188 + +/* DMA Control 0 Register */ +#define LCD_SPU_DMA_CTRL0 0x0190 +#define CFG_NOBLENDING(nb) ((nb) << 31) +#define CFG_NOBLENDING_MASK 0x80000000 +#define CFG_GAMMA_ENA(gn) ((gn) << 30) +#define CFG_GAMMA_ENA_MASK 0x40000000 +#define CFG_CBSH_ENA(cn) ((cn) << 29) +#define CFG_CBSH_ENA_MASK 0x20000000 +#define CFG_PALETTE_ENA(pn) ((pn) << 28) +#define CFG_PALETTE_ENA_MASK 0x10000000 +#define CFG_ARBFAST_ENA(an) ((an) << 27) +#define CFG_ARBFAST_ENA_MASK 0x08000000 +#define CFG_HWC_1BITMOD(mode) ((mode) << 26) +#define CFG_HWC_1BITMOD_MASK 0x04000000 +#define CFG_HWC_1BITENA(mn) ((mn) << 25) +#define CFG_HWC_1BITENA_MASK 0x02000000 +#define CFG_HWC_ENA(cn) ((cn) << 24) +#define CFG_HWC_ENA_MASK 0x01000000 +#define CFG_DMAFORMAT(dmaformat) ((dmaformat) << 20) +#define CFG_DMAFORMAT_MASK 0x00F00000 +#define CFG_GRAFORMAT(graformat) ((graformat) << 16) +#define CFG_GRAFORMAT_MASK 0x000F0000 +/* for graphic part */ +#define CFG_GRA_FTOGGLE(toggle) ((toggle) << 15) +#define CFG_GRA_FTOGGLE_MASK 0x00008000 +#define CFG_GRA_HSMOOTH(smooth) ((smooth) << 14) +#define CFG_GRA_HSMOOTH_MASK 0x00004000 +#define CFG_GRA_TSTMODE(test) ((test) << 13) +#define CFG_GRA_TSTMODE_MASK 0x00002000 +#define CFG_GRA_SWAPRB(swap) ((swap) << 12) +#define CFG_GRA_SWAPRB_MASK 0x00001000 +#define CFG_GRA_SWAPUV(swap) ((swap) << 11) +#define CFG_GRA_SWAPUV_MASK 0x00000800 +#define CFG_GRA_SWAPYU(swap) ((swap) << 10) +#define CFG_GRA_SWAPYU_MASK 0x00000400 +#define CFG_YUV2RGB_GRA(cvrt) ((cvrt) << 9) +#define CFG_YUV2RGB_GRA_MASK 0x00000200 +#define CFG_GRA_ENA(gra) ((gra) << 8) +#define CFG_GRA_ENA_MASK 0x00000100 +/* for video part */ +#define CFG_DMA_FTOGGLE(toggle) ((toggle) << 7) +#define CFG_DMA_FTOGGLE_MASK 0x00000080 +#define CFG_DMA_HSMOOTH(smooth) ((smooth) << 6) +#define CFG_DMA_HSMOOTH_MASK 0x00000040 +#define CFG_DMA_TSTMODE(test) ((test) << 5) +#define CFG_DMA_TSTMODE_MASK 0x00000020 +#define CFG_DMA_SWAPRB(swap) ((swap) << 4) +#define CFG_DMA_SWAPRB_MASK 0x00000010 +#define CFG_DMA_SWAPUV(swap) ((swap) << 3) +#define CFG_DMA_SWAPUV_MASK 0x00000008 +#define CFG_DMA_SWAPYU(swap) ((swap) << 2) +#define CFG_DMA_SWAPYU_MASK 0x00000004 +#define CFG_DMA_SWAP_MASK 0x0000001C +#define CFG_YUV2RGB_DMA(cvrt) ((cvrt) << 1) +#define CFG_YUV2RGB_DMA_MASK 0x00000002 +#define CFG_DMA_ENA(video) (video) +#define CFG_DMA_ENA_MASK 0x00000001 + +/* DMA Control 1 Register */ +#define LCD_SPU_DMA_CTRL1 0x0194 +#define CFG_FRAME_TRIG(trig) ((trig) << 31) +#define CFG_FRAME_TRIG_MASK 0x80000000 +#define CFG_VSYNC_TRIG(trig) ((trig) << 28) +#define CFG_VSYNC_TRIG_MASK 0x70000000 +#define CFG_VSYNC_INV(inv) ((inv) << 27) +#define CFG_VSYNC_INV_MASK 0x08000000 +#define CFG_COLOR_KEY_MODE(cmode) ((cmode) << 24) +#define CFG_COLOR_KEY_MASK 0x07000000 +#define CFG_CARRY(carry) ((carry) << 23) +#define CFG_CARRY_MASK 0x00800000 +#define CFG_LNBUF_ENA(lnbuf) ((lnbuf) << 22) +#define CFG_LNBUF_ENA_MASK 0x00400000 +#define CFG_GATED_ENA(gated) ((gated) << 21) +#define CFG_GATED_ENA_MASK 0x00200000 +#define CFG_PWRDN_ENA(power) ((power) << 20) +#define CFG_PWRDN_ENA_MASK 0x00100000 +#define CFG_DSCALE(dscale) ((dscale) << 18) +#define CFG_DSCALE_MASK 0x000C0000 +#define CFG_ALPHA_MODE(amode) ((amode) << 16) +#define CFG_ALPHA_MODE_MASK 0x00030000 +#define CFG_ALPHA(alpha) ((alpha) << 8) +#define CFG_ALPHA_MASK 0x0000FF00 +#define CFG_PXLCMD(pxlcmd) (pxlcmd) +#define CFG_PXLCMD_MASK 0x000000FF + +/* SRAM Control Register */ +#define LCD_SPU_SRAM_CTRL 0x0198 +#define CFG_SRAM_INIT_WR_RD(mode) ((mode) << 14) +#define CFG_SRAM_INIT_WR_RD_MASK 0x0000C000 +#define CFG_SRAM_ADDR_LCDID(id) ((id) << 8) +#define CFG_SRAM_ADDR_LCDID_MASK 0x00000F00 +#define CFG_SRAM_ADDR(addr) (addr) +#define CFG_SRAM_ADDR_MASK 0x000000FF + +/* SRAM Write Data Register */ +#define LCD_SPU_SRAM_WRDAT 0x019C + +/* SRAM RTC/WTC Control Register */ +#define LCD_SPU_SRAM_PARA0 0x01A0 + +/* SRAM Power Down Control Register */ +#define LCD_SPU_SRAM_PARA1 0x01A4 +#define CFG_CSB_256x32(hwc) ((hwc) << 15) /* HWC */ +#define CFG_CSB_256x32_MASK 0x00008000 +#define CFG_CSB_256x24(palette) ((palette) << 14) /* Palette */ +#define CFG_CSB_256x24_MASK 0x00004000 +#define CFG_CSB_256x8(gamma) ((gamma) << 13) /* Gamma */ +#define CFG_CSB_256x8_MASK 0x00002000 +#define CFG_PDWN256x32(pdwn) ((pdwn) << 7) /* HWC */ +#define CFG_PDWN256x32_MASK 0x00000080 +#define CFG_PDWN256x24(pdwn) ((pdwn) << 6) /* Palette */ +#define CFG_PDWN256x24_MASK 0x00000040 +#define CFG_PDWN256x8(pdwn) ((pdwn) << 5) /* Gamma */ +#define CFG_PDWN256x8_MASK 0x00000020 +#define CFG_PDWN32x32(pdwn) ((pdwn) << 3) +#define CFG_PDWN32x32_MASK 0x00000008 +#define CFG_PDWN16x66(pdwn) ((pdwn) << 2) +#define CFG_PDWN16x66_MASK 0x00000004 +#define CFG_PDWN32x66(pdwn) ((pdwn) << 1) +#define CFG_PDWN32x66_MASK 0x00000002 +#define CFG_PDWN64x66(pdwn) (pdwn) +#define CFG_PDWN64x66_MASK 0x00000001 + +/* Smart or Dumb Panel Clock Divider */ +#define LCD_CFG_SCLK_DIV 0x01A8 +#define SCLK_SOURCE_SELECT(src) ((src) << 31) +#define SCLK_SOURCE_SELECT_MASK 0x80000000 +#define CLK_FRACDIV(frac) ((frac) << 16) +#define CLK_FRACDIV_MASK 0x0FFF0000 +#define CLK_INT_DIV(div) (div) +#define CLK_INT_DIV_MASK 0x0000FFFF + +/* Video Contrast Register */ +#define LCD_SPU_CONTRAST 0x01AC +#define CFG_BRIGHTNESS(bright) ((bright) << 16) +#define CFG_BRIGHTNESS_MASK 0xFFFF0000 +#define CFG_CONTRAST(contrast) (contrast) +#define CFG_CONTRAST_MASK 0x0000FFFF + +/* Video Saturation Register */ +#define LCD_SPU_SATURATION 0x01B0 +#define CFG_C_MULTS(mult) ((mult) << 16) +#define CFG_C_MULTS_MASK 0xFFFF0000 +#define CFG_SATURATION(sat) (sat) +#define CFG_SATURATION_MASK 0x0000FFFF + +/* Video Hue Adjust Register */ +#define LCD_SPU_CBSH_HUE 0x01B4 +#define CFG_SIN0(sin0) ((sin0) << 16) +#define CFG_SIN0_MASK 0xFFFF0000 +#define CFG_COS0(con0) (con0) +#define CFG_COS0_MASK 0x0000FFFF + +/* Dump LCD Panel Control Register */ +#define LCD_SPU_DUMB_CTRL 0x01B8 +#define CFG_DUMBMODE(mode) ((mode) << 28) +#define CFG_DUMBMODE_MASK 0xF0000000 +#define CFG_LCDGPIO_O(data) ((data) << 20) +#define CFG_LCDGPIO_O_MASK 0x0FF00000 +#define CFG_LCDGPIO_ENA(gpio) ((gpio) << 12) +#define CFG_LCDGPIO_ENA_MASK 0x000FF000 +#define CFG_BIAS_OUT(bias) ((bias) << 8) +#define CFG_BIAS_OUT_MASK 0x00000100 +#define CFG_REVERSE_RGB(rRGB) ((rRGB) << 7) +#define CFG_REVERSE_RGB_MASK 0x00000080 +#define CFG_INV_COMPBLANK(blank) ((blank) << 6) +#define CFG_INV_COMPBLANK_MASK 0x00000040 +#define CFG_INV_COMPSYNC(sync) ((sync) << 5) +#define CFG_INV_COMPSYNC_MASK 0x00000020 +#define CFG_INV_HENA(hena) ((hena) << 4) +#define CFG_INV_HENA_MASK 0x00000010 +#define CFG_INV_VSYNC(vsync) ((vsync) << 3) +#define CFG_INV_VSYNC_MASK 0x00000008 +#define CFG_INV_HSYNC(hsync) ((hsync) << 2) +#define CFG_INV_HSYNC_MASK 0x00000004 +#define CFG_INV_PCLK(pclk) ((pclk) << 1) +#define CFG_INV_PCLK_MASK 0x00000002 +#define CFG_DUMB_ENA(dumb) (dumb) +#define CFG_DUMB_ENA_MASK 0x00000001 + +/* LCD I/O Pads Control Register */ +#define SPU_IOPAD_CONTROL 0x01BC +#define CFG_GRA_VM_ENA(vm) ((vm) << 15) /* gfx */ +#define CFG_GRA_VM_ENA_MASK 0x00008000 +#define CFG_DMA_VM_ENA(vm) ((vm) << 13) /* video */ +#define CFG_DMA_VM_ENA_MASK 0x00002000 +#define CFG_CMD_VM_ENA(vm) ((vm) << 13) +#define CFG_CMD_VM_ENA_MASK 0x00000800 +#define CFG_CSC(csc) ((csc) << 8) /* csc */ +#define CFG_CSC_MASK 0x00000300 +#define CFG_AXICTRL(axi) ((axi) << 4) +#define CFG_AXICTRL_MASK 0x000000F0 +#define CFG_IOPADMODE(iopad) (iopad) +#define CFG_IOPADMODE_MASK 0x0000000F + +/* LCD Interrupt Control Register */ +#define SPU_IRQ_ENA 0x01C0 +#define DMA_FRAME_IRQ0_ENA(irq) ((irq) << 31) +#define DMA_FRAME_IRQ0_ENA_MASK 0x80000000 +#define DMA_FRAME_IRQ1_ENA(irq) ((irq) << 30) +#define DMA_FRAME_IRQ1_ENA_MASK 0x40000000 +#define DMA_FF_UNDERFLOW_ENA(ff) ((ff) << 29) +#define DMA_FF_UNDERFLOW_ENA_MASK 0x20000000 +#define GRA_FRAME_IRQ0_ENA(irq) ((irq) << 27) +#define GRA_FRAME_IRQ0_ENA_MASK 0x08000000 +#define GRA_FRAME_IRQ1_ENA(irq) ((irq) << 26) +#define GRA_FRAME_IRQ1_ENA_MASK 0x04000000 +#define GRA_FF_UNDERFLOW_ENA(ff) ((ff) << 25) +#define GRA_FF_UNDERFLOW_ENA_MASK 0x02000000 +#define VSYNC_IRQ_ENA(vsync_irq) ((vsync_irq) << 23) +#define VSYNC_IRQ_ENA_MASK 0x00800000 +#define DUMB_FRAMEDONE_ENA(fdone) ((fdone) << 22) +#define DUMB_FRAMEDONE_ENA_MASK 0x00400000 +#define TWC_FRAMEDONE_ENA(fdone) ((fdone) << 21) +#define TWC_FRAMEDONE_ENA_MASK 0x00200000 +#define HWC_FRAMEDONE_ENA(fdone) ((fdone) << 20) +#define HWC_FRAMEDONE_ENA_MASK 0x00100000 +#define SLV_IRQ_ENA(irq) ((irq) << 19) +#define SLV_IRQ_ENA_MASK 0x00080000 +#define SPI_IRQ_ENA(irq) ((irq) << 18) +#define SPI_IRQ_ENA_MASK 0x00040000 +#define PWRDN_IRQ_ENA(irq) ((irq) << 17) +#define PWRDN_IRQ_ENA_MASK 0x00020000 +#define ERR_IRQ_ENA(irq) ((irq) << 16) +#define ERR_IRQ_ENA_MASK 0x00010000 +#define CLEAN_SPU_IRQ_ISR(irq) (irq) +#define CLEAN_SPU_IRQ_ISR_MASK 0x0000FFFF + +/* LCD Interrupt Status Register */ +#define SPU_IRQ_ISR 0x01C4 +#define DMA_FRAME_IRQ0(irq) ((irq) << 31) +#define DMA_FRAME_IRQ0_MASK 0x80000000 +#define DMA_FRAME_IRQ1(irq) ((irq) << 30) +#define DMA_FRAME_IRQ1_MASK 0x40000000 +#define DMA_FF_UNDERFLOW(ff) ((ff) << 29) +#define DMA_FF_UNDERFLOW_MASK 0x20000000 +#define GRA_FRAME_IRQ0(irq) ((irq) << 27) +#define GRA_FRAME_IRQ0_MASK 0x08000000 +#define GRA_FRAME_IRQ1(irq) ((irq) << 26) +#define GRA_FRAME_IRQ1_MASK 0x04000000 +#define GRA_FF_UNDERFLOW(ff) ((ff) << 25) +#define GRA_FF_UNDERFLOW_MASK 0x02000000 +#define VSYNC_IRQ(vsync_irq) ((vsync_irq) << 23) +#define VSYNC_IRQ_MASK 0x00800000 +#define DUMB_FRAMEDONE(fdone) ((fdone) << 22) +#define DUMB_FRAMEDONE_MASK 0x00400000 +#define TWC_FRAMEDONE(fdone) ((fdone) << 21) +#define TWC_FRAMEDONE_MASK 0x00200000 +#define HWC_FRAMEDONE(fdone) ((fdone) << 20) +#define HWC_FRAMEDONE_MASK 0x00100000 +#define SLV_IRQ(irq) ((irq) << 19) +#define SLV_IRQ_MASK 0x00080000 +#define SPI_IRQ(irq) ((irq) << 18) +#define SPI_IRQ_MASK 0x00040000 +#define PWRDN_IRQ(irq) ((irq) << 17) +#define PWRDN_IRQ_MASK 0x00020000 +#define ERR_IRQ(irq) ((irq) << 16) +#define ERR_IRQ_MASK 0x00010000 +/* read-only */ +#define DMA_FRAME_IRQ0_LEVEL_MASK 0x00008000 +#define DMA_FRAME_IRQ1_LEVEL_MASK 0x00004000 +#define DMA_FRAME_CNT_ISR_MASK 0x00003000 +#define GRA_FRAME_IRQ0_LEVEL_MASK 0x00000800 +#define GRA_FRAME_IRQ1_LEVEL_MASK 0x00000400 +#define GRA_FRAME_CNT_ISR_MASK 0x00000300 +#define VSYNC_IRQ_LEVEL_MASK 0x00000080 +#define DUMB_FRAMEDONE_LEVEL_MASK 0x00000040 +#define TWC_FRAMEDONE_LEVEL_MASK 0x00000020 +#define HWC_FRAMEDONE_LEVEL_MASK 0x00000010 +#define SLV_FF_EMPTY_MASK 0x00000008 +#define DMA_FF_ALLEMPTY_MASK 0x00000004 +#define GRA_FF_ALLEMPTY_MASK 0x00000002 +#define PWRDN_IRQ_LEVEL_MASK 0x00000001 + + +/* + * defined Video Memory Color format for DMA control 0 register + * DMA0 bit[23:20] + */ +#define VMODE_RGB565 0x0 +#define VMODE_RGB1555 0x1 +#define VMODE_RGB888PACKED 0x2 +#define VMODE_RGB888UNPACKED 0x3 +#define VMODE_RGBA888 0x4 +#define VMODE_YUV422PACKED 0x5 +#define VMODE_YUV422PLANAR 0x6 +#define VMODE_YUV420PLANAR 0x7 +#define VMODE_SMPNCMD 0x8 +#define VMODE_PALETTE4BIT 0x9 +#define VMODE_PALETTE8BIT 0xa +#define VMODE_RESERVED 0xb + +/* + * defined Graphic Memory Color format for DMA control 0 register + * DMA0 bit[19:16] + */ +#define GMODE_RGB565 0x0 +#define GMODE_RGB1555 0x1 +#define GMODE_RGB888PACKED 0x2 +#define GMODE_RGB888UNPACKED 0x3 +#define GMODE_RGBA888 0x4 +#define GMODE_YUV422PACKED 0x5 +#define GMODE_YUV422PLANAR 0x6 +#define GMODE_YUV420PLANAR 0x7 +#define GMODE_SMPNCMD 0x8 +#define GMODE_PALETTE4BIT 0x9 +#define GMODE_PALETTE8BIT 0xa +#define GMODE_RESERVED 0xb + +/* + * define for DMA control 1 register + */ +#define DMA1_FRAME_TRIG 31 /* bit location */ +#define DMA1_VSYNC_MODE 28 +#define DMA1_VSYNC_INV 27 +#define DMA1_CKEY 24 +#define DMA1_CARRY 23 +#define DMA1_LNBUF_ENA 22 +#define DMA1_GATED_ENA 21 +#define DMA1_PWRDN_ENA 20 +#define DMA1_DSCALE 18 +#define DMA1_ALPHA_MODE 16 +#define DMA1_ALPHA 08 +#define DMA1_PXLCMD 00 + +/* + * defined for Configure Dumb Mode + * DUMB LCD Panel bit[31:28] + */ +#define DUMB16_RGB565_0 0x0 +#define DUMB16_RGB565_1 0x1 +#define DUMB18_RGB666_0 0x2 +#define DUMB18_RGB666_1 0x3 +#define DUMB12_RGB444_0 0x4 +#define DUMB12_RGB444_1 0x5 +#define DUMB24_RGB888_0 0x6 +#define DUMB_BLANK 0x7 + +/* + * defined for Configure I/O Pin Allocation Mode + * LCD LCD I/O Pads control register bit[3:0] + */ +#define IOPAD_DUMB24 0x0 +#define IOPAD_DUMB18SPI 0x1 +#define IOPAD_DUMB18GPIO 0x2 +#define IOPAD_DUMB16SPI 0x3 +#define IOPAD_DUMB16GPIO 0x4 +#define IOPAD_DUMB12 0x5 +#define IOPAD_SMART18SPI 0x6 +#define IOPAD_SMART16SPI 0x7 +#define IOPAD_SMART8BOTH 0x8 + +#endif /* __PXA168FB_H__ */ diff --git a/drivers/video/fbdev/pxa3xx-gcu.c b/drivers/video/fbdev/pxa3xx-gcu.c new file mode 100644 index 000000000000..417f9a27eb7d --- /dev/null +++ b/drivers/video/fbdev/pxa3xx-gcu.c @@ -0,0 +1,724 @@ +/* + * pxa3xx-gcu.c - Linux kernel module for PXA3xx graphics controllers + * + * This driver needs a DirectFB counterpart in user space, communication + * is handled via mmap()ed memory areas and an ioctl. + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + * Copyright (c) 2009 Janine Kropp <nin@directfb.org> + * Copyright (c) 2009 Denis Oliver Kropp <dok@directfb.org> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * WARNING: This controller is attached to System Bus 2 of the PXA which + * needs its arbiter to be enabled explicitly (CKENB & 1<<9). + * There is currently no way to do this from Linux, so you need to teach + * your bootloader for now. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/miscdevice.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/fs.h> +#include <linux/io.h> + +#include "pxa3xx-gcu.h" + +#define DRV_NAME "pxa3xx-gcu" +#define MISCDEV_MINOR 197 + +#define REG_GCCR 0x00 +#define GCCR_SYNC_CLR (1 << 9) +#define GCCR_BP_RST (1 << 8) +#define GCCR_ABORT (1 << 6) +#define GCCR_STOP (1 << 4) + +#define REG_GCISCR 0x04 +#define REG_GCIECR 0x08 +#define REG_GCRBBR 0x20 +#define REG_GCRBLR 0x24 +#define REG_GCRBHR 0x28 +#define REG_GCRBTR 0x2C +#define REG_GCRBEXHR 0x30 + +#define IE_EOB (1 << 0) +#define IE_EEOB (1 << 5) +#define IE_ALL 0xff + +#define SHARED_SIZE PAGE_ALIGN(sizeof(struct pxa3xx_gcu_shared)) + +/* #define PXA3XX_GCU_DEBUG */ +/* #define PXA3XX_GCU_DEBUG_TIMER */ + +#ifdef PXA3XX_GCU_DEBUG +#define QDUMP(msg) \ + do { \ + QPRINT(priv, KERN_DEBUG, msg); \ + } while (0) +#else +#define QDUMP(msg) do {} while (0) +#endif + +#define QERROR(msg) \ + do { \ + QPRINT(priv, KERN_ERR, msg); \ + } while (0) + +struct pxa3xx_gcu_batch { + struct pxa3xx_gcu_batch *next; + u32 *ptr; + dma_addr_t phys; + unsigned long length; +}; + +struct pxa3xx_gcu_priv { + void __iomem *mmio_base; + struct clk *clk; + struct pxa3xx_gcu_shared *shared; + dma_addr_t shared_phys; + struct resource *resource_mem; + struct miscdevice misc_dev; + wait_queue_head_t wait_idle; + wait_queue_head_t wait_free; + spinlock_t spinlock; + struct timeval base_time; + + struct pxa3xx_gcu_batch *free; + struct pxa3xx_gcu_batch *ready; + struct pxa3xx_gcu_batch *ready_last; + struct pxa3xx_gcu_batch *running; +}; + +static inline unsigned long +gc_readl(struct pxa3xx_gcu_priv *priv, unsigned int off) +{ + return __raw_readl(priv->mmio_base + off); +} + +static inline void +gc_writel(struct pxa3xx_gcu_priv *priv, unsigned int off, unsigned long val) +{ + __raw_writel(val, priv->mmio_base + off); +} + +#define QPRINT(priv, level, msg) \ + do { \ + struct timeval tv; \ + struct pxa3xx_gcu_shared *shared = priv->shared; \ + u32 base = gc_readl(priv, REG_GCRBBR); \ + \ + do_gettimeofday(&tv); \ + \ + printk(level "%ld.%03ld.%03ld - %-17s: %-21s (%s, " \ + "STATUS " \ + "0x%02lx, B 0x%08lx [%ld], E %5ld, H %5ld, " \ + "T %5ld)\n", \ + tv.tv_sec - priv->base_time.tv_sec, \ + tv.tv_usec / 1000, tv.tv_usec % 1000, \ + __func__, msg, \ + shared->hw_running ? "running" : " idle", \ + gc_readl(priv, REG_GCISCR), \ + gc_readl(priv, REG_GCRBBR), \ + gc_readl(priv, REG_GCRBLR), \ + (gc_readl(priv, REG_GCRBEXHR) - base) / 4, \ + (gc_readl(priv, REG_GCRBHR) - base) / 4, \ + (gc_readl(priv, REG_GCRBTR) - base) / 4); \ + } while (0) + +static void +pxa3xx_gcu_reset(struct pxa3xx_gcu_priv *priv) +{ + QDUMP("RESET"); + + /* disable interrupts */ + gc_writel(priv, REG_GCIECR, 0); + + /* reset hardware */ + gc_writel(priv, REG_GCCR, GCCR_ABORT); + gc_writel(priv, REG_GCCR, 0); + + memset(priv->shared, 0, SHARED_SIZE); + priv->shared->buffer_phys = priv->shared_phys; + priv->shared->magic = PXA3XX_GCU_SHARED_MAGIC; + + do_gettimeofday(&priv->base_time); + + /* set up the ring buffer pointers */ + gc_writel(priv, REG_GCRBLR, 0); + gc_writel(priv, REG_GCRBBR, priv->shared_phys); + gc_writel(priv, REG_GCRBTR, priv->shared_phys); + + /* enable all IRQs except EOB */ + gc_writel(priv, REG_GCIECR, IE_ALL & ~IE_EOB); +} + +static void +dump_whole_state(struct pxa3xx_gcu_priv *priv) +{ + struct pxa3xx_gcu_shared *sh = priv->shared; + u32 base = gc_readl(priv, REG_GCRBBR); + + QDUMP("DUMP"); + + printk(KERN_DEBUG "== PXA3XX-GCU DUMP ==\n" + "%s, STATUS 0x%02lx, B 0x%08lx [%ld], E %5ld, H %5ld, T %5ld\n", + sh->hw_running ? "running" : "idle ", + gc_readl(priv, REG_GCISCR), + gc_readl(priv, REG_GCRBBR), + gc_readl(priv, REG_GCRBLR), + (gc_readl(priv, REG_GCRBEXHR) - base) / 4, + (gc_readl(priv, REG_GCRBHR) - base) / 4, + (gc_readl(priv, REG_GCRBTR) - base) / 4); +} + +static void +flush_running(struct pxa3xx_gcu_priv *priv) +{ + struct pxa3xx_gcu_batch *running = priv->running; + struct pxa3xx_gcu_batch *next; + + while (running) { + next = running->next; + running->next = priv->free; + priv->free = running; + running = next; + } + + priv->running = NULL; +} + +static void +run_ready(struct pxa3xx_gcu_priv *priv) +{ + unsigned int num = 0; + struct pxa3xx_gcu_shared *shared = priv->shared; + struct pxa3xx_gcu_batch *ready = priv->ready; + + QDUMP("Start"); + + BUG_ON(!ready); + + shared->buffer[num++] = 0x05000000; + + while (ready) { + shared->buffer[num++] = 0x00000001; + shared->buffer[num++] = ready->phys; + ready = ready->next; + } + + shared->buffer[num++] = 0x05000000; + priv->running = priv->ready; + priv->ready = priv->ready_last = NULL; + gc_writel(priv, REG_GCRBLR, 0); + shared->hw_running = 1; + + /* ring base address */ + gc_writel(priv, REG_GCRBBR, shared->buffer_phys); + + /* ring tail address */ + gc_writel(priv, REG_GCRBTR, shared->buffer_phys + num * 4); + + /* ring length */ + gc_writel(priv, REG_GCRBLR, ((num + 63) & ~63) * 4); +} + +static irqreturn_t +pxa3xx_gcu_handle_irq(int irq, void *ctx) +{ + struct pxa3xx_gcu_priv *priv = ctx; + struct pxa3xx_gcu_shared *shared = priv->shared; + u32 status = gc_readl(priv, REG_GCISCR) & IE_ALL; + + QDUMP("-Interrupt"); + + if (!status) + return IRQ_NONE; + + spin_lock(&priv->spinlock); + shared->num_interrupts++; + + if (status & IE_EEOB) { + QDUMP(" [EEOB]"); + + flush_running(priv); + wake_up_all(&priv->wait_free); + + if (priv->ready) { + run_ready(priv); + } else { + /* There is no more data prepared by the userspace. + * Set hw_running = 0 and wait for the next userspace + * kick-off */ + shared->num_idle++; + shared->hw_running = 0; + + QDUMP(" '-> Idle."); + + /* set ring buffer length to zero */ + gc_writel(priv, REG_GCRBLR, 0); + + wake_up_all(&priv->wait_idle); + } + + shared->num_done++; + } else { + QERROR(" [???]"); + dump_whole_state(priv); + } + + /* Clear the interrupt */ + gc_writel(priv, REG_GCISCR, status); + spin_unlock(&priv->spinlock); + + return IRQ_HANDLED; +} + +static int +pxa3xx_gcu_wait_idle(struct pxa3xx_gcu_priv *priv) +{ + int ret = 0; + + QDUMP("Waiting for idle..."); + + /* Does not need to be atomic. There's a lock in user space, + * but anyhow, this is just for statistics. */ + priv->shared->num_wait_idle++; + + while (priv->shared->hw_running) { + int num = priv->shared->num_interrupts; + u32 rbexhr = gc_readl(priv, REG_GCRBEXHR); + + ret = wait_event_interruptible_timeout(priv->wait_idle, + !priv->shared->hw_running, HZ*4); + + if (ret != 0) + break; + + if (gc_readl(priv, REG_GCRBEXHR) == rbexhr && + priv->shared->num_interrupts == num) { + QERROR("TIMEOUT"); + ret = -ETIMEDOUT; + break; + } + } + + QDUMP("done"); + + return ret; +} + +static int +pxa3xx_gcu_wait_free(struct pxa3xx_gcu_priv *priv) +{ + int ret = 0; + + QDUMP("Waiting for free..."); + + /* Does not need to be atomic. There's a lock in user space, + * but anyhow, this is just for statistics. */ + priv->shared->num_wait_free++; + + while (!priv->free) { + u32 rbexhr = gc_readl(priv, REG_GCRBEXHR); + + ret = wait_event_interruptible_timeout(priv->wait_free, + priv->free, HZ*4); + + if (ret < 0) + break; + + if (ret > 0) + continue; + + if (gc_readl(priv, REG_GCRBEXHR) == rbexhr) { + QERROR("TIMEOUT"); + ret = -ETIMEDOUT; + break; + } + } + + QDUMP("done"); + + return ret; +} + +/* Misc device layer */ + +static inline struct pxa3xx_gcu_priv *to_pxa3xx_gcu_priv(struct file *file) +{ + struct miscdevice *dev = file->private_data; + return container_of(dev, struct pxa3xx_gcu_priv, misc_dev); +} + +/* + * provide an empty .open callback, so the core sets file->private_data + * for us. + */ +static int pxa3xx_gcu_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t +pxa3xx_gcu_write(struct file *file, const char *buff, + size_t count, loff_t *offp) +{ + int ret; + unsigned long flags; + struct pxa3xx_gcu_batch *buffer; + struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file); + + int words = count / 4; + + /* Does not need to be atomic. There's a lock in user space, + * but anyhow, this is just for statistics. */ + priv->shared->num_writes++; + priv->shared->num_words += words; + + /* Last word reserved for batch buffer end command */ + if (words >= PXA3XX_GCU_BATCH_WORDS) + return -E2BIG; + + /* Wait for a free buffer */ + if (!priv->free) { + ret = pxa3xx_gcu_wait_free(priv); + if (ret < 0) + return ret; + } + + /* + * Get buffer from free list + */ + spin_lock_irqsave(&priv->spinlock, flags); + buffer = priv->free; + priv->free = buffer->next; + spin_unlock_irqrestore(&priv->spinlock, flags); + + + /* Copy data from user into buffer */ + ret = copy_from_user(buffer->ptr, buff, words * 4); + if (ret) { + spin_lock_irqsave(&priv->spinlock, flags); + buffer->next = priv->free; + priv->free = buffer; + spin_unlock_irqrestore(&priv->spinlock, flags); + return -EFAULT; + } + + buffer->length = words; + + /* Append batch buffer end command */ + buffer->ptr[words] = 0x01000000; + + /* + * Add buffer to ready list + */ + spin_lock_irqsave(&priv->spinlock, flags); + + buffer->next = NULL; + + if (priv->ready) { + BUG_ON(priv->ready_last == NULL); + + priv->ready_last->next = buffer; + } else + priv->ready = buffer; + + priv->ready_last = buffer; + + if (!priv->shared->hw_running) + run_ready(priv); + + spin_unlock_irqrestore(&priv->spinlock, flags); + + return words * 4; +} + + +static long +pxa3xx_gcu_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned long flags; + struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file); + + switch (cmd) { + case PXA3XX_GCU_IOCTL_RESET: + spin_lock_irqsave(&priv->spinlock, flags); + pxa3xx_gcu_reset(priv); + spin_unlock_irqrestore(&priv->spinlock, flags); + return 0; + + case PXA3XX_GCU_IOCTL_WAIT_IDLE: + return pxa3xx_gcu_wait_idle(priv); + } + + return -ENOSYS; +} + +static int +pxa3xx_gcu_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned int size = vma->vm_end - vma->vm_start; + struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file); + + switch (vma->vm_pgoff) { + case 0: + /* hand out the shared data area */ + if (size != SHARED_SIZE) + return -EINVAL; + + return dma_mmap_coherent(NULL, vma, + priv->shared, priv->shared_phys, size); + + case SHARED_SIZE >> PAGE_SHIFT: + /* hand out the MMIO base for direct register access + * from userspace */ + if (size != resource_size(priv->resource_mem)) + return -EINVAL; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return io_remap_pfn_range(vma, vma->vm_start, + priv->resource_mem->start >> PAGE_SHIFT, + size, vma->vm_page_prot); + } + + return -EINVAL; +} + + +#ifdef PXA3XX_GCU_DEBUG_TIMER +static struct timer_list pxa3xx_gcu_debug_timer; + +static void pxa3xx_gcu_debug_timedout(unsigned long ptr) +{ + struct pxa3xx_gcu_priv *priv = (struct pxa3xx_gcu_priv *) ptr; + + QERROR("Timer DUMP"); + + /* init the timer structure */ + init_timer(&pxa3xx_gcu_debug_timer); + pxa3xx_gcu_debug_timer.function = pxa3xx_gcu_debug_timedout; + pxa3xx_gcu_debug_timer.data = ptr; + pxa3xx_gcu_debug_timer.expires = jiffies + 5*HZ; /* one second */ + + add_timer(&pxa3xx_gcu_debug_timer); +} + +static void pxa3xx_gcu_init_debug_timer(void) +{ + pxa3xx_gcu_debug_timedout((unsigned long) &pxa3xx_gcu_debug_timer); +} +#else +static inline void pxa3xx_gcu_init_debug_timer(void) {} +#endif + +static int +pxa3xx_gcu_add_buffer(struct device *dev, + struct pxa3xx_gcu_priv *priv) +{ + struct pxa3xx_gcu_batch *buffer; + + buffer = kzalloc(sizeof(struct pxa3xx_gcu_batch), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + buffer->ptr = dma_alloc_coherent(dev, PXA3XX_GCU_BATCH_WORDS * 4, + &buffer->phys, GFP_KERNEL); + if (!buffer->ptr) { + kfree(buffer); + return -ENOMEM; + } + + buffer->next = priv->free; + priv->free = buffer; + + return 0; +} + +static void +pxa3xx_gcu_free_buffers(struct device *dev, + struct pxa3xx_gcu_priv *priv) +{ + struct pxa3xx_gcu_batch *next, *buffer = priv->free; + + while (buffer) { + next = buffer->next; + + dma_free_coherent(dev, PXA3XX_GCU_BATCH_WORDS * 4, + buffer->ptr, buffer->phys); + + kfree(buffer); + buffer = next; + } + + priv->free = NULL; +} + +static const struct file_operations pxa3xx_gcu_miscdev_fops = { + .owner = THIS_MODULE, + .open = pxa3xx_gcu_open, + .write = pxa3xx_gcu_write, + .unlocked_ioctl = pxa3xx_gcu_ioctl, + .mmap = pxa3xx_gcu_mmap, +}; + +static int pxa3xx_gcu_probe(struct platform_device *pdev) +{ + int i, ret, irq; + struct resource *r; + struct pxa3xx_gcu_priv *priv; + struct device *dev = &pdev->dev; + + priv = devm_kzalloc(dev, sizeof(struct pxa3xx_gcu_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + init_waitqueue_head(&priv->wait_idle); + init_waitqueue_head(&priv->wait_free); + spin_lock_init(&priv->spinlock); + + /* we allocate the misc device structure as part of our own allocation, + * so we can get a pointer to our priv structure later on with + * container_of(). This isn't really necessary as we have a fixed minor + * number anyway, but this is to avoid statics. */ + + priv->misc_dev.minor = MISCDEV_MINOR, + priv->misc_dev.name = DRV_NAME, + priv->misc_dev.fops = &pxa3xx_gcu_miscdev_fops; + + /* handle IO resources */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->mmio_base = devm_request_and_ioremap(dev, r); + if (IS_ERR(priv->mmio_base)) { + dev_err(dev, "failed to map I/O memory\n"); + return PTR_ERR(priv->mmio_base); + } + + /* enable the clock */ + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get clock\n"); + return PTR_ERR(priv->clk); + } + + /* request the IRQ */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "no IRQ defined\n"); + return -ENODEV; + } + + ret = devm_request_irq(dev, irq, pxa3xx_gcu_handle_irq, + 0, DRV_NAME, priv); + if (ret < 0) { + dev_err(dev, "request_irq failed\n"); + return ret; + } + + /* allocate dma memory */ + priv->shared = dma_alloc_coherent(dev, SHARED_SIZE, + &priv->shared_phys, GFP_KERNEL); + if (!priv->shared) { + dev_err(dev, "failed to allocate DMA memory\n"); + return -ENOMEM; + } + + /* register misc device */ + ret = misc_register(&priv->misc_dev); + if (ret < 0) { + dev_err(dev, "misc_register() for minor %d failed\n", + MISCDEV_MINOR); + goto err_free_dma; + } + + ret = clk_enable(priv->clk); + if (ret < 0) { + dev_err(dev, "failed to enable clock\n"); + goto err_misc_deregister; + } + + for (i = 0; i < 8; i++) { + ret = pxa3xx_gcu_add_buffer(dev, priv); + if (ret) { + dev_err(dev, "failed to allocate DMA memory\n"); + goto err_disable_clk; + } + } + + platform_set_drvdata(pdev, priv); + priv->resource_mem = r; + pxa3xx_gcu_reset(priv); + pxa3xx_gcu_init_debug_timer(); + + dev_info(dev, "registered @0x%p, DMA 0x%p (%d bytes), IRQ %d\n", + (void *) r->start, (void *) priv->shared_phys, + SHARED_SIZE, irq); + return 0; + +err_free_dma: + dma_free_coherent(dev, SHARED_SIZE, + priv->shared, priv->shared_phys); + +err_misc_deregister: + misc_deregister(&priv->misc_dev); + +err_disable_clk: + clk_disable(priv->clk); + + return ret; +} + +static int pxa3xx_gcu_remove(struct platform_device *pdev) +{ + struct pxa3xx_gcu_priv *priv = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + pxa3xx_gcu_wait_idle(priv); + misc_deregister(&priv->misc_dev); + dma_free_coherent(dev, SHARED_SIZE, priv->shared, priv->shared_phys); + pxa3xx_gcu_free_buffers(dev, priv); + + return 0; +} + +static struct platform_driver pxa3xx_gcu_driver = { + .probe = pxa3xx_gcu_probe, + .remove = pxa3xx_gcu_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +module_platform_driver(pxa3xx_gcu_driver); + +MODULE_DESCRIPTION("PXA3xx graphics controller unit driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(MISCDEV_MINOR); +MODULE_AUTHOR("Janine Kropp <nin@directfb.org>, " + "Denis Oliver Kropp <dok@directfb.org>, " + "Daniel Mack <daniel@caiaq.de>"); diff --git a/drivers/video/fbdev/pxa3xx-gcu.h b/drivers/video/fbdev/pxa3xx-gcu.h new file mode 100644 index 000000000000..0428ed03dc49 --- /dev/null +++ b/drivers/video/fbdev/pxa3xx-gcu.h @@ -0,0 +1,38 @@ +#ifndef __PXA3XX_GCU_H__ +#define __PXA3XX_GCU_H__ + +#include <linux/types.h> + +/* Number of 32bit words in display list (ring buffer). */ +#define PXA3XX_GCU_BUFFER_WORDS ((256 * 1024 - 256) / 4) + +/* To be increased when breaking the ABI */ +#define PXA3XX_GCU_SHARED_MAGIC 0x30000001 + +#define PXA3XX_GCU_BATCH_WORDS 8192 + +struct pxa3xx_gcu_shared { + u32 buffer[PXA3XX_GCU_BUFFER_WORDS]; + + bool hw_running; + + unsigned long buffer_phys; + + unsigned int num_words; + unsigned int num_writes; + unsigned int num_done; + unsigned int num_interrupts; + unsigned int num_wait_idle; + unsigned int num_wait_free; + unsigned int num_idle; + + u32 magic; +}; + +/* Initialization and synchronization. + * Hardware is started upon write(). */ +#define PXA3XX_GCU_IOCTL_RESET _IO('G', 0) +#define PXA3XX_GCU_IOCTL_WAIT_IDLE _IO('G', 2) + +#endif /* __PXA3XX_GCU_H__ */ + diff --git a/drivers/video/fbdev/pxafb.c b/drivers/video/fbdev/pxafb.c new file mode 100644 index 000000000000..1ecd9cec2921 --- /dev/null +++ b/drivers/video/fbdev/pxafb.c @@ -0,0 +1,2332 @@ +/* + * linux/drivers/video/pxafb.c + * + * Copyright (C) 1999 Eric A. Thomas. + * Copyright (C) 2004 Jean-Frederic Clere. + * Copyright (C) 2004 Ian Campbell. + * Copyright (C) 2004 Jeff Lackey. + * Based on sa1100fb.c Copyright (C) 1999 Eric A. Thomas + * which in turn is + * Based on acornfb.c Copyright (C) Russell King. + * + * 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. + * + * Intel PXA250/210 LCD Controller Frame Buffer Driver + * + * Please direct your questions and comments on this driver to the following + * email address: + * + * linux-arm-kernel@lists.arm.linux.org.uk + * + * Add support for overlay1 and overlay2 based on pxafb_overlay.c: + * + * Copyright (C) 2004, Intel Corporation + * + * 2003/08/27: <yu.tang@intel.com> + * 2004/03/10: <stanley.cai@intel.com> + * 2004/10/28: <yan.yin@intel.com> + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * All Rights Reserved + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/cpufreq.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/console.h> + +#include <mach/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/div64.h> +#include <mach/bitfield.h> +#include <linux/platform_data/video-pxafb.h> + +/* + * Complain if VAR is out of range. + */ +#define DEBUG_VAR 1 + +#include "pxafb.h" + +/* Bits which should not be set in machine configuration structures */ +#define LCCR0_INVALID_CONFIG_MASK (LCCR0_OUM | LCCR0_BM | LCCR0_QDM |\ + LCCR0_DIS | LCCR0_EFM | LCCR0_IUM |\ + LCCR0_SFM | LCCR0_LDM | LCCR0_ENB) + +#define LCCR3_INVALID_CONFIG_MASK (LCCR3_HSP | LCCR3_VSP |\ + LCCR3_PCD | LCCR3_BPP(0xf)) + +static int pxafb_activate_var(struct fb_var_screeninfo *var, + struct pxafb_info *); +static void set_ctrlr_state(struct pxafb_info *fbi, u_int state); +static void setup_base_frame(struct pxafb_info *fbi, + struct fb_var_screeninfo *var, int branch); +static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, + unsigned long offset, size_t size); + +static unsigned long video_mem_size = 0; + +static inline unsigned long +lcd_readl(struct pxafb_info *fbi, unsigned int off) +{ + return __raw_readl(fbi->mmio_base + off); +} + +static inline void +lcd_writel(struct pxafb_info *fbi, unsigned int off, unsigned long val) +{ + __raw_writel(val, fbi->mmio_base + off); +} + +static inline void pxafb_schedule_work(struct pxafb_info *fbi, u_int state) +{ + unsigned long flags; + + local_irq_save(flags); + /* + * We need to handle two requests being made at the same time. + * There are two important cases: + * 1. When we are changing VT (C_REENABLE) while unblanking + * (C_ENABLE) We must perform the unblanking, which will + * do our REENABLE for us. + * 2. When we are blanking, but immediately unblank before + * we have blanked. We do the "REENABLE" thing here as + * well, just to be sure. + */ + if (fbi->task_state == C_ENABLE && state == C_REENABLE) + state = (u_int) -1; + if (fbi->task_state == C_DISABLE && state == C_ENABLE) + state = C_REENABLE; + + if (state != (u_int)-1) { + fbi->task_state = state; + schedule_work(&fbi->task); + } + local_irq_restore(flags); +} + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +pxafb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + struct pxafb_info *fbi = (struct pxafb_info *)info; + u_int val; + + if (regno >= fbi->palette_size) + return 1; + + if (fbi->fb.var.grayscale) { + fbi->palette_cpu[regno] = ((blue >> 8) & 0x00ff); + return 0; + } + + switch (fbi->lccr4 & LCCR4_PAL_FOR_MASK) { + case LCCR4_PAL_FOR_0: + val = ((red >> 0) & 0xf800); + val |= ((green >> 5) & 0x07e0); + val |= ((blue >> 11) & 0x001f); + fbi->palette_cpu[regno] = val; + break; + case LCCR4_PAL_FOR_1: + val = ((red << 8) & 0x00f80000); + val |= ((green >> 0) & 0x0000fc00); + val |= ((blue >> 8) & 0x000000f8); + ((u32 *)(fbi->palette_cpu))[regno] = val; + break; + case LCCR4_PAL_FOR_2: + val = ((red << 8) & 0x00fc0000); + val |= ((green >> 0) & 0x0000fc00); + val |= ((blue >> 8) & 0x000000fc); + ((u32 *)(fbi->palette_cpu))[regno] = val; + break; + case LCCR4_PAL_FOR_3: + val = ((red << 8) & 0x00ff0000); + val |= ((green >> 0) & 0x0000ff00); + val |= ((blue >> 8) & 0x000000ff); + ((u32 *)(fbi->palette_cpu))[regno] = val; + break; + } + + return 0; +} + +static int +pxafb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + struct pxafb_info *fbi = (struct pxafb_info *)info; + unsigned int val; + int ret = 1; + + /* + * If inverse mode was selected, invert all the colours + * rather than the register number. The register number + * is what you poke into the framebuffer to produce the + * colour you requested. + */ + if (fbi->cmap_inverse) { + red = 0xffff - red; + green = 0xffff - green; + blue = 0xffff - blue; + } + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->fb.var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + switch (fbi->fb.fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->fb.pseudo_palette; + + val = chan_to_field(red, &fbi->fb.var.red); + val |= chan_to_field(green, &fbi->fb.var.green); + val |= chan_to_field(blue, &fbi->fb.var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + ret = pxafb_setpalettereg(regno, red, green, blue, trans, info); + break; + } + + return ret; +} + +/* calculate pixel depth, transparency bit included, >=16bpp formats _only_ */ +static inline int var_to_depth(struct fb_var_screeninfo *var) +{ + return var->red.length + var->green.length + + var->blue.length + var->transp.length; +} + +/* calculate 4-bit BPP value for LCCR3 and OVLxC1 */ +static int pxafb_var_to_bpp(struct fb_var_screeninfo *var) +{ + int bpp = -EINVAL; + + switch (var->bits_per_pixel) { + case 1: bpp = 0; break; + case 2: bpp = 1; break; + case 4: bpp = 2; break; + case 8: bpp = 3; break; + case 16: bpp = 4; break; + case 24: + switch (var_to_depth(var)) { + case 18: bpp = 6; break; /* 18-bits/pixel packed */ + case 19: bpp = 8; break; /* 19-bits/pixel packed */ + case 24: bpp = 9; break; + } + break; + case 32: + switch (var_to_depth(var)) { + case 18: bpp = 5; break; /* 18-bits/pixel unpacked */ + case 19: bpp = 7; break; /* 19-bits/pixel unpacked */ + case 25: bpp = 10; break; + } + break; + } + return bpp; +} + +/* + * pxafb_var_to_lccr3(): + * Convert a bits per pixel value to the correct bit pattern for LCCR3 + * + * NOTE: for PXA27x with overlays support, the LCCR3_PDFOR_x bits have an + * implication of the acutal use of transparency bit, which we handle it + * here separatedly. See PXA27x Developer's Manual, Section <<7.4.6 Pixel + * Formats>> for the valid combination of PDFOR, PAL_FOR for various BPP. + * + * Transparency for palette pixel formats is not supported at the moment. + */ +static uint32_t pxafb_var_to_lccr3(struct fb_var_screeninfo *var) +{ + int bpp = pxafb_var_to_bpp(var); + uint32_t lccr3; + + if (bpp < 0) + return 0; + + lccr3 = LCCR3_BPP(bpp); + + switch (var_to_depth(var)) { + case 16: lccr3 |= var->transp.length ? LCCR3_PDFOR_3 : 0; break; + case 18: lccr3 |= LCCR3_PDFOR_3; break; + case 24: lccr3 |= var->transp.length ? LCCR3_PDFOR_2 : LCCR3_PDFOR_3; + break; + case 19: + case 25: lccr3 |= LCCR3_PDFOR_0; break; + } + return lccr3; +} + +#define SET_PIXFMT(v, r, g, b, t) \ +({ \ + (v)->transp.offset = (t) ? (r) + (g) + (b) : 0; \ + (v)->transp.length = (t) ? (t) : 0; \ + (v)->blue.length = (b); (v)->blue.offset = 0; \ + (v)->green.length = (g); (v)->green.offset = (b); \ + (v)->red.length = (r); (v)->red.offset = (b) + (g); \ +}) + +/* set the RGBT bitfields of fb_var_screeninf according to + * var->bits_per_pixel and given depth + */ +static void pxafb_set_pixfmt(struct fb_var_screeninfo *var, int depth) +{ + if (depth == 0) + depth = var->bits_per_pixel; + + if (var->bits_per_pixel < 16) { + /* indexed pixel formats */ + var->red.offset = 0; var->red.length = 8; + var->green.offset = 0; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 8; + } + + switch (depth) { + case 16: var->transp.length ? + SET_PIXFMT(var, 5, 5, 5, 1) : /* RGBT555 */ + SET_PIXFMT(var, 5, 6, 5, 0); break; /* RGB565 */ + case 18: SET_PIXFMT(var, 6, 6, 6, 0); break; /* RGB666 */ + case 19: SET_PIXFMT(var, 6, 6, 6, 1); break; /* RGBT666 */ + case 24: var->transp.length ? + SET_PIXFMT(var, 8, 8, 7, 1) : /* RGBT887 */ + SET_PIXFMT(var, 8, 8, 8, 0); break; /* RGB888 */ + case 25: SET_PIXFMT(var, 8, 8, 8, 1); break; /* RGBT888 */ + } +} + +#ifdef CONFIG_CPU_FREQ +/* + * pxafb_display_dma_period() + * Calculate the minimum period (in picoseconds) between two DMA + * requests for the LCD controller. If we hit this, it means we're + * doing nothing but LCD DMA. + */ +static unsigned int pxafb_display_dma_period(struct fb_var_screeninfo *var) +{ + /* + * Period = pixclock * bits_per_byte * bytes_per_transfer + * / memory_bits_per_pixel; + */ + return var->pixclock * 8 * 16 / var->bits_per_pixel; +} +#endif + +/* + * Select the smallest mode that allows the desired resolution to be + * displayed. If desired parameters can be rounded up. + */ +static struct pxafb_mode_info *pxafb_getmode(struct pxafb_mach_info *mach, + struct fb_var_screeninfo *var) +{ + struct pxafb_mode_info *mode = NULL; + struct pxafb_mode_info *modelist = mach->modes; + unsigned int best_x = 0xffffffff, best_y = 0xffffffff; + unsigned int i; + + for (i = 0; i < mach->num_modes; i++) { + if (modelist[i].xres >= var->xres && + modelist[i].yres >= var->yres && + modelist[i].xres < best_x && + modelist[i].yres < best_y && + modelist[i].bpp >= var->bits_per_pixel) { + best_x = modelist[i].xres; + best_y = modelist[i].yres; + mode = &modelist[i]; + } + } + + return mode; +} + +static void pxafb_setmode(struct fb_var_screeninfo *var, + struct pxafb_mode_info *mode) +{ + var->xres = mode->xres; + var->yres = mode->yres; + var->bits_per_pixel = mode->bpp; + var->pixclock = mode->pixclock; + var->hsync_len = mode->hsync_len; + var->left_margin = mode->left_margin; + var->right_margin = mode->right_margin; + var->vsync_len = mode->vsync_len; + var->upper_margin = mode->upper_margin; + var->lower_margin = mode->lower_margin; + var->sync = mode->sync; + var->grayscale = mode->cmap_greyscale; + var->transp.length = mode->transparency; + + /* set the initial RGBA bitfields */ + pxafb_set_pixfmt(var, mode->depth); +} + +static int pxafb_adjust_timing(struct pxafb_info *fbi, + struct fb_var_screeninfo *var) +{ + int line_length; + + var->xres = max_t(int, var->xres, MIN_XRES); + var->yres = max_t(int, var->yres, MIN_YRES); + + if (!(fbi->lccr0 & LCCR0_LCDT)) { + clamp_val(var->hsync_len, 1, 64); + clamp_val(var->vsync_len, 1, 64); + clamp_val(var->left_margin, 1, 255); + clamp_val(var->right_margin, 1, 255); + clamp_val(var->upper_margin, 1, 255); + clamp_val(var->lower_margin, 1, 255); + } + + /* make sure each line is aligned on word boundary */ + line_length = var->xres * var->bits_per_pixel / 8; + line_length = ALIGN(line_length, 4); + var->xres = line_length * 8 / var->bits_per_pixel; + + /* we don't support xpan, force xres_virtual to be equal to xres */ + var->xres_virtual = var->xres; + + if (var->accel_flags & FB_ACCELF_TEXT) + var->yres_virtual = fbi->fb.fix.smem_len / line_length; + else + var->yres_virtual = max(var->yres_virtual, var->yres); + + /* check for limits */ + if (var->xres > MAX_XRES || var->yres > MAX_YRES) + return -EINVAL; + + if (var->yres > var->yres_virtual) + return -EINVAL; + + return 0; +} + +/* + * pxafb_check_var(): + * Get the video params out of 'var'. If a value doesn't fit, round it up, + * if it's too big, return -EINVAL. + * + * Round up in the following order: bits_per_pixel, xres, + * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale, + * bitfields, horizontal timing, vertical timing. + */ +static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct pxafb_info *fbi = (struct pxafb_info *)info; + struct pxafb_mach_info *inf = dev_get_platdata(fbi->dev); + int err; + + if (inf->fixed_modes) { + struct pxafb_mode_info *mode; + + mode = pxafb_getmode(inf, var); + if (!mode) + return -EINVAL; + pxafb_setmode(var, mode); + } + + /* do a test conversion to BPP fields to check the color formats */ + err = pxafb_var_to_bpp(var); + if (err < 0) + return err; + + pxafb_set_pixfmt(var, var_to_depth(var)); + + err = pxafb_adjust_timing(fbi, var); + if (err) + return err; + +#ifdef CONFIG_CPU_FREQ + pr_debug("pxafb: dma period = %d ps\n", + pxafb_display_dma_period(var)); +#endif + + return 0; +} + +/* + * pxafb_set_par(): + * Set the user defined part of the display for the specified console + */ +static int pxafb_set_par(struct fb_info *info) +{ + struct pxafb_info *fbi = (struct pxafb_info *)info; + struct fb_var_screeninfo *var = &info->var; + + if (var->bits_per_pixel >= 16) + fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR; + else if (!fbi->cmap_static) + fbi->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + else { + /* + * Some people have weird ideas about wanting static + * pseudocolor maps. I suspect their user space + * applications are broken. + */ + fbi->fb.fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + } + + fbi->fb.fix.line_length = var->xres_virtual * + var->bits_per_pixel / 8; + if (var->bits_per_pixel >= 16) + fbi->palette_size = 0; + else + fbi->palette_size = var->bits_per_pixel == 1 ? + 4 : 1 << var->bits_per_pixel; + + fbi->palette_cpu = (u16 *)&fbi->dma_buff->palette[0]; + + if (fbi->fb.var.bits_per_pixel >= 16) + fb_dealloc_cmap(&fbi->fb.cmap); + else + fb_alloc_cmap(&fbi->fb.cmap, 1<<fbi->fb.var.bits_per_pixel, 0); + + pxafb_activate_var(var, fbi); + + return 0; +} + +static int pxafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct pxafb_info *fbi = (struct pxafb_info *)info; + struct fb_var_screeninfo newvar; + int dma = DMA_MAX + DMA_BASE; + + if (fbi->state != C_ENABLE) + return 0; + + /* Only take .xoffset, .yoffset and .vmode & FB_VMODE_YWRAP from what + * was passed in and copy the rest from the old screeninfo. + */ + memcpy(&newvar, &fbi->fb.var, sizeof(newvar)); + newvar.xoffset = var->xoffset; + newvar.yoffset = var->yoffset; + newvar.vmode &= ~FB_VMODE_YWRAP; + newvar.vmode |= var->vmode & FB_VMODE_YWRAP; + + setup_base_frame(fbi, &newvar, 1); + + if (fbi->lccr0 & LCCR0_SDS) + lcd_writel(fbi, FBR1, fbi->fdadr[dma + 1] | 0x1); + + lcd_writel(fbi, FBR0, fbi->fdadr[dma] | 0x1); + return 0; +} + +/* + * pxafb_blank(): + * Blank the display by setting all palette values to zero. Note, the + * 16 bpp mode does not really use the palette, so this will not + * blank the display in all modes. + */ +static int pxafb_blank(int blank, struct fb_info *info) +{ + struct pxafb_info *fbi = (struct pxafb_info *)info; + int i; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR || + fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) + for (i = 0; i < fbi->palette_size; i++) + pxafb_setpalettereg(i, 0, 0, 0, 0, info); + + pxafb_schedule_work(fbi, C_DISABLE); + /* TODO if (pxafb_blank_helper) pxafb_blank_helper(blank); */ + break; + + case FB_BLANK_UNBLANK: + /* TODO if (pxafb_blank_helper) pxafb_blank_helper(blank); */ + if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR || + fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) + fb_set_cmap(&fbi->fb.cmap, info); + pxafb_schedule_work(fbi, C_ENABLE); + } + return 0; +} + +static struct fb_ops pxafb_ops = { + .owner = THIS_MODULE, + .fb_check_var = pxafb_check_var, + .fb_set_par = pxafb_set_par, + .fb_pan_display = pxafb_pan_display, + .fb_setcolreg = pxafb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = pxafb_blank, +}; + +#ifdef CONFIG_FB_PXA_OVERLAY +static void overlay1fb_setup(struct pxafb_layer *ofb) +{ + int size = ofb->fb.fix.line_length * ofb->fb.var.yres_virtual; + unsigned long start = ofb->video_mem_phys; + setup_frame_dma(ofb->fbi, DMA_OV1, PAL_NONE, start, size); +} + +/* Depending on the enable status of overlay1/2, the DMA should be + * updated from FDADRx (when disabled) or FBRx (when enabled). + */ +static void overlay1fb_enable(struct pxafb_layer *ofb) +{ + int enabled = lcd_readl(ofb->fbi, OVL1C1) & OVLxC1_OEN; + uint32_t fdadr1 = ofb->fbi->fdadr[DMA_OV1] | (enabled ? 0x1 : 0); + + lcd_writel(ofb->fbi, enabled ? FBR1 : FDADR1, fdadr1); + lcd_writel(ofb->fbi, OVL1C2, ofb->control[1]); + lcd_writel(ofb->fbi, OVL1C1, ofb->control[0] | OVLxC1_OEN); +} + +static void overlay1fb_disable(struct pxafb_layer *ofb) +{ + uint32_t lccr5; + + if (!(lcd_readl(ofb->fbi, OVL1C1) & OVLxC1_OEN)) + return; + + lccr5 = lcd_readl(ofb->fbi, LCCR5); + + lcd_writel(ofb->fbi, OVL1C1, ofb->control[0] & ~OVLxC1_OEN); + + lcd_writel(ofb->fbi, LCSR1, LCSR1_BS(1)); + lcd_writel(ofb->fbi, LCCR5, lccr5 & ~LCSR1_BS(1)); + lcd_writel(ofb->fbi, FBR1, ofb->fbi->fdadr[DMA_OV1] | 0x3); + + if (wait_for_completion_timeout(&ofb->branch_done, 1 * HZ) == 0) + pr_warning("%s: timeout disabling overlay1\n", __func__); + + lcd_writel(ofb->fbi, LCCR5, lccr5); +} + +static void overlay2fb_setup(struct pxafb_layer *ofb) +{ + int size, div = 1, pfor = NONSTD_TO_PFOR(ofb->fb.var.nonstd); + unsigned long start[3] = { ofb->video_mem_phys, 0, 0 }; + + if (pfor == OVERLAY_FORMAT_RGB || pfor == OVERLAY_FORMAT_YUV444_PACKED) { + size = ofb->fb.fix.line_length * ofb->fb.var.yres_virtual; + setup_frame_dma(ofb->fbi, DMA_OV2_Y, -1, start[0], size); + } else { + size = ofb->fb.var.xres_virtual * ofb->fb.var.yres_virtual; + switch (pfor) { + case OVERLAY_FORMAT_YUV444_PLANAR: div = 1; break; + case OVERLAY_FORMAT_YUV422_PLANAR: div = 2; break; + case OVERLAY_FORMAT_YUV420_PLANAR: div = 4; break; + } + start[1] = start[0] + size; + start[2] = start[1] + size / div; + setup_frame_dma(ofb->fbi, DMA_OV2_Y, -1, start[0], size); + setup_frame_dma(ofb->fbi, DMA_OV2_Cb, -1, start[1], size / div); + setup_frame_dma(ofb->fbi, DMA_OV2_Cr, -1, start[2], size / div); + } +} + +static void overlay2fb_enable(struct pxafb_layer *ofb) +{ + int pfor = NONSTD_TO_PFOR(ofb->fb.var.nonstd); + int enabled = lcd_readl(ofb->fbi, OVL2C1) & OVLxC1_OEN; + uint32_t fdadr2 = ofb->fbi->fdadr[DMA_OV2_Y] | (enabled ? 0x1 : 0); + uint32_t fdadr3 = ofb->fbi->fdadr[DMA_OV2_Cb] | (enabled ? 0x1 : 0); + uint32_t fdadr4 = ofb->fbi->fdadr[DMA_OV2_Cr] | (enabled ? 0x1 : 0); + + if (pfor == OVERLAY_FORMAT_RGB || pfor == OVERLAY_FORMAT_YUV444_PACKED) + lcd_writel(ofb->fbi, enabled ? FBR2 : FDADR2, fdadr2); + else { + lcd_writel(ofb->fbi, enabled ? FBR2 : FDADR2, fdadr2); + lcd_writel(ofb->fbi, enabled ? FBR3 : FDADR3, fdadr3); + lcd_writel(ofb->fbi, enabled ? FBR4 : FDADR4, fdadr4); + } + lcd_writel(ofb->fbi, OVL2C2, ofb->control[1]); + lcd_writel(ofb->fbi, OVL2C1, ofb->control[0] | OVLxC1_OEN); +} + +static void overlay2fb_disable(struct pxafb_layer *ofb) +{ + uint32_t lccr5; + + if (!(lcd_readl(ofb->fbi, OVL2C1) & OVLxC1_OEN)) + return; + + lccr5 = lcd_readl(ofb->fbi, LCCR5); + + lcd_writel(ofb->fbi, OVL2C1, ofb->control[0] & ~OVLxC1_OEN); + + lcd_writel(ofb->fbi, LCSR1, LCSR1_BS(2)); + lcd_writel(ofb->fbi, LCCR5, lccr5 & ~LCSR1_BS(2)); + lcd_writel(ofb->fbi, FBR2, ofb->fbi->fdadr[DMA_OV2_Y] | 0x3); + lcd_writel(ofb->fbi, FBR3, ofb->fbi->fdadr[DMA_OV2_Cb] | 0x3); + lcd_writel(ofb->fbi, FBR4, ofb->fbi->fdadr[DMA_OV2_Cr] | 0x3); + + if (wait_for_completion_timeout(&ofb->branch_done, 1 * HZ) == 0) + pr_warning("%s: timeout disabling overlay2\n", __func__); +} + +static struct pxafb_layer_ops ofb_ops[] = { + [0] = { + .enable = overlay1fb_enable, + .disable = overlay1fb_disable, + .setup = overlay1fb_setup, + }, + [1] = { + .enable = overlay2fb_enable, + .disable = overlay2fb_disable, + .setup = overlay2fb_setup, + }, +}; + +static int overlayfb_open(struct fb_info *info, int user) +{ + struct pxafb_layer *ofb = (struct pxafb_layer *)info; + + /* no support for framebuffer console on overlay */ + if (user == 0) + return -ENODEV; + + if (ofb->usage++ == 0) { + /* unblank the base framebuffer */ + console_lock(); + fb_blank(&ofb->fbi->fb, FB_BLANK_UNBLANK); + console_unlock(); + } + + return 0; +} + +static int overlayfb_release(struct fb_info *info, int user) +{ + struct pxafb_layer *ofb = (struct pxafb_layer*) info; + + if (ofb->usage == 1) { + ofb->ops->disable(ofb); + ofb->fb.var.height = -1; + ofb->fb.var.width = -1; + ofb->fb.var.xres = ofb->fb.var.xres_virtual = 0; + ofb->fb.var.yres = ofb->fb.var.yres_virtual = 0; + + ofb->usage--; + } + return 0; +} + +static int overlayfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct pxafb_layer *ofb = (struct pxafb_layer *)info; + struct fb_var_screeninfo *base_var = &ofb->fbi->fb.var; + int xpos, ypos, pfor, bpp; + + xpos = NONSTD_TO_XPOS(var->nonstd); + ypos = NONSTD_TO_YPOS(var->nonstd); + pfor = NONSTD_TO_PFOR(var->nonstd); + + bpp = pxafb_var_to_bpp(var); + if (bpp < 0) + return -EINVAL; + + /* no support for YUV format on overlay1 */ + if (ofb->id == OVERLAY1 && pfor != 0) + return -EINVAL; + + /* for YUV packed formats, bpp = 'minimum bpp of YUV components' */ + switch (pfor) { + case OVERLAY_FORMAT_RGB: + bpp = pxafb_var_to_bpp(var); + if (bpp < 0) + return -EINVAL; + + pxafb_set_pixfmt(var, var_to_depth(var)); + break; + case OVERLAY_FORMAT_YUV444_PACKED: bpp = 24; break; + case OVERLAY_FORMAT_YUV444_PLANAR: bpp = 8; break; + case OVERLAY_FORMAT_YUV422_PLANAR: bpp = 4; break; + case OVERLAY_FORMAT_YUV420_PLANAR: bpp = 2; break; + default: + return -EINVAL; + } + + /* each line must start at a 32-bit word boundary */ + if ((xpos * bpp) % 32) + return -EINVAL; + + /* xres must align on 32-bit word boundary */ + var->xres = roundup(var->xres * bpp, 32) / bpp; + + if ((xpos + var->xres > base_var->xres) || + (ypos + var->yres > base_var->yres)) + return -EINVAL; + + var->xres_virtual = var->xres; + var->yres_virtual = max(var->yres, var->yres_virtual); + return 0; +} + +static int overlayfb_check_video_memory(struct pxafb_layer *ofb) +{ + struct fb_var_screeninfo *var = &ofb->fb.var; + int pfor = NONSTD_TO_PFOR(var->nonstd); + int size, bpp = 0; + + switch (pfor) { + case OVERLAY_FORMAT_RGB: bpp = var->bits_per_pixel; break; + case OVERLAY_FORMAT_YUV444_PACKED: bpp = 24; break; + case OVERLAY_FORMAT_YUV444_PLANAR: bpp = 24; break; + case OVERLAY_FORMAT_YUV422_PLANAR: bpp = 16; break; + case OVERLAY_FORMAT_YUV420_PLANAR: bpp = 12; break; + } + + ofb->fb.fix.line_length = var->xres_virtual * bpp / 8; + + size = PAGE_ALIGN(ofb->fb.fix.line_length * var->yres_virtual); + + if (ofb->video_mem) { + if (ofb->video_mem_size >= size) + return 0; + } + return -EINVAL; +} + +static int overlayfb_set_par(struct fb_info *info) +{ + struct pxafb_layer *ofb = (struct pxafb_layer *)info; + struct fb_var_screeninfo *var = &info->var; + int xpos, ypos, pfor, bpp, ret; + + ret = overlayfb_check_video_memory(ofb); + if (ret) + return ret; + + bpp = pxafb_var_to_bpp(var); + xpos = NONSTD_TO_XPOS(var->nonstd); + ypos = NONSTD_TO_YPOS(var->nonstd); + pfor = NONSTD_TO_PFOR(var->nonstd); + + ofb->control[0] = OVLxC1_PPL(var->xres) | OVLxC1_LPO(var->yres) | + OVLxC1_BPP(bpp); + ofb->control[1] = OVLxC2_XPOS(xpos) | OVLxC2_YPOS(ypos); + + if (ofb->id == OVERLAY2) + ofb->control[1] |= OVL2C2_PFOR(pfor); + + ofb->ops->setup(ofb); + ofb->ops->enable(ofb); + return 0; +} + +static struct fb_ops overlay_fb_ops = { + .owner = THIS_MODULE, + .fb_open = overlayfb_open, + .fb_release = overlayfb_release, + .fb_check_var = overlayfb_check_var, + .fb_set_par = overlayfb_set_par, +}; + +static void init_pxafb_overlay(struct pxafb_info *fbi, struct pxafb_layer *ofb, + int id) +{ + sprintf(ofb->fb.fix.id, "overlay%d", id + 1); + + ofb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + ofb->fb.fix.xpanstep = 0; + ofb->fb.fix.ypanstep = 1; + + ofb->fb.var.activate = FB_ACTIVATE_NOW; + ofb->fb.var.height = -1; + ofb->fb.var.width = -1; + ofb->fb.var.vmode = FB_VMODE_NONINTERLACED; + + ofb->fb.fbops = &overlay_fb_ops; + ofb->fb.flags = FBINFO_FLAG_DEFAULT; + ofb->fb.node = -1; + ofb->fb.pseudo_palette = NULL; + + ofb->id = id; + ofb->ops = &ofb_ops[id]; + ofb->usage = 0; + ofb->fbi = fbi; + init_completion(&ofb->branch_done); +} + +static inline int pxafb_overlay_supported(void) +{ + if (cpu_is_pxa27x() || cpu_is_pxa3xx()) + return 1; + + return 0; +} + +static int pxafb_overlay_map_video_memory(struct pxafb_info *pxafb, + struct pxafb_layer *ofb) +{ + /* We assume that user will use at most video_mem_size for overlay fb, + * anyway, it's useless to use 16bpp main plane and 24bpp overlay + */ + ofb->video_mem = alloc_pages_exact(PAGE_ALIGN(pxafb->video_mem_size), + GFP_KERNEL | __GFP_ZERO); + if (ofb->video_mem == NULL) + return -ENOMEM; + + ofb->video_mem_phys = virt_to_phys(ofb->video_mem); + ofb->video_mem_size = PAGE_ALIGN(pxafb->video_mem_size); + + mutex_lock(&ofb->fb.mm_lock); + ofb->fb.fix.smem_start = ofb->video_mem_phys; + ofb->fb.fix.smem_len = pxafb->video_mem_size; + mutex_unlock(&ofb->fb.mm_lock); + + ofb->fb.screen_base = ofb->video_mem; + + return 0; +} + +static void pxafb_overlay_init(struct pxafb_info *fbi) +{ + int i, ret; + + if (!pxafb_overlay_supported()) + return; + + for (i = 0; i < 2; i++) { + struct pxafb_layer *ofb = &fbi->overlay[i]; + init_pxafb_overlay(fbi, ofb, i); + ret = register_framebuffer(&ofb->fb); + if (ret) { + dev_err(fbi->dev, "failed to register overlay %d\n", i); + continue; + } + ret = pxafb_overlay_map_video_memory(fbi, ofb); + if (ret) { + dev_err(fbi->dev, + "failed to map video memory for overlay %d\n", + i); + unregister_framebuffer(&ofb->fb); + continue; + } + ofb->registered = 1; + } + + /* mask all IU/BS/EOF/SOF interrupts */ + lcd_writel(fbi, LCCR5, ~0); + + pr_info("PXA Overlay driver loaded successfully!\n"); +} + +static void pxafb_overlay_exit(struct pxafb_info *fbi) +{ + int i; + + if (!pxafb_overlay_supported()) + return; + + for (i = 0; i < 2; i++) { + struct pxafb_layer *ofb = &fbi->overlay[i]; + if (ofb->registered) { + if (ofb->video_mem) + free_pages_exact(ofb->video_mem, + ofb->video_mem_size); + unregister_framebuffer(&ofb->fb); + } + } +} +#else +static inline void pxafb_overlay_init(struct pxafb_info *fbi) {} +static inline void pxafb_overlay_exit(struct pxafb_info *fbi) {} +#endif /* CONFIG_FB_PXA_OVERLAY */ + +/* + * Calculate the PCD value from the clock rate (in picoseconds). + * We take account of the PPCR clock setting. + * From PXA Developer's Manual: + * + * PixelClock = LCLK + * ------------- + * 2 ( PCD + 1 ) + * + * PCD = LCLK + * ------------- - 1 + * 2(PixelClock) + * + * Where: + * LCLK = LCD/Memory Clock + * PCD = LCCR3[7:0] + * + * PixelClock here is in Hz while the pixclock argument given is the + * period in picoseconds. Hence PixelClock = 1 / ( pixclock * 10^-12 ) + * + * The function get_lclk_frequency_10khz returns LCLK in units of + * 10khz. Calling the result of this function lclk gives us the + * following + * + * PCD = (lclk * 10^4 ) * ( pixclock * 10^-12 ) + * -------------------------------------- - 1 + * 2 + * + * Factoring the 10^4 and 10^-12 out gives 10^-8 == 1 / 100000000 as used below. + */ +static inline unsigned int get_pcd(struct pxafb_info *fbi, + unsigned int pixclock) +{ + unsigned long long pcd; + + /* FIXME: Need to take into account Double Pixel Clock mode + * (DPC) bit? or perhaps set it based on the various clock + * speeds */ + pcd = (unsigned long long)(clk_get_rate(fbi->clk) / 10000); + pcd *= pixclock; + do_div(pcd, 100000000 * 2); + /* no need for this, since we should subtract 1 anyway. they cancel */ + /* pcd += 1; */ /* make up for integer math truncations */ + return (unsigned int)pcd; +} + +/* + * Some touchscreens need hsync information from the video driver to + * function correctly. We export it here. Note that 'hsync_time' and + * the value returned from pxafb_get_hsync_time() is the *reciprocal* + * of the hsync period in seconds. + */ +static inline void set_hsync_time(struct pxafb_info *fbi, unsigned int pcd) +{ + unsigned long htime; + + if ((pcd == 0) || (fbi->fb.var.hsync_len == 0)) { + fbi->hsync_time = 0; + return; + } + + htime = clk_get_rate(fbi->clk) / (pcd * fbi->fb.var.hsync_len); + + fbi->hsync_time = htime; +} + +unsigned long pxafb_get_hsync_time(struct device *dev) +{ + struct pxafb_info *fbi = dev_get_drvdata(dev); + + /* If display is blanked/suspended, hsync isn't active */ + if (!fbi || (fbi->state != C_ENABLE)) + return 0; + + return fbi->hsync_time; +} +EXPORT_SYMBOL(pxafb_get_hsync_time); + +static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, + unsigned long start, size_t size) +{ + struct pxafb_dma_descriptor *dma_desc, *pal_desc; + unsigned int dma_desc_off, pal_desc_off; + + if (dma < 0 || dma >= DMA_MAX * 2) + return -EINVAL; + + dma_desc = &fbi->dma_buff->dma_desc[dma]; + dma_desc_off = offsetof(struct pxafb_dma_buff, dma_desc[dma]); + + dma_desc->fsadr = start; + dma_desc->fidr = 0; + dma_desc->ldcmd = size; + + if (pal < 0 || pal >= PAL_MAX * 2) { + dma_desc->fdadr = fbi->dma_buff_phys + dma_desc_off; + fbi->fdadr[dma] = fbi->dma_buff_phys + dma_desc_off; + } else { + pal_desc = &fbi->dma_buff->pal_desc[pal]; + pal_desc_off = offsetof(struct pxafb_dma_buff, pal_desc[pal]); + + pal_desc->fsadr = fbi->dma_buff_phys + pal * PALETTE_SIZE; + pal_desc->fidr = 0; + + if ((fbi->lccr4 & LCCR4_PAL_FOR_MASK) == LCCR4_PAL_FOR_0) + pal_desc->ldcmd = fbi->palette_size * sizeof(u16); + else + pal_desc->ldcmd = fbi->palette_size * sizeof(u32); + + pal_desc->ldcmd |= LDCMD_PAL; + + /* flip back and forth between palette and frame buffer */ + pal_desc->fdadr = fbi->dma_buff_phys + dma_desc_off; + dma_desc->fdadr = fbi->dma_buff_phys + pal_desc_off; + fbi->fdadr[dma] = fbi->dma_buff_phys + dma_desc_off; + } + + return 0; +} + +static void setup_base_frame(struct pxafb_info *fbi, + struct fb_var_screeninfo *var, + int branch) +{ + struct fb_fix_screeninfo *fix = &fbi->fb.fix; + int nbytes, dma, pal, bpp = var->bits_per_pixel; + unsigned long offset; + + dma = DMA_BASE + (branch ? DMA_MAX : 0); + pal = (bpp >= 16) ? PAL_NONE : PAL_BASE + (branch ? PAL_MAX : 0); + + nbytes = fix->line_length * var->yres; + offset = fix->line_length * var->yoffset + fbi->video_mem_phys; + + if (fbi->lccr0 & LCCR0_SDS) { + nbytes = nbytes / 2; + setup_frame_dma(fbi, dma + 1, PAL_NONE, offset + nbytes, nbytes); + } + + setup_frame_dma(fbi, dma, pal, offset, nbytes); +} + +#ifdef CONFIG_FB_PXA_SMARTPANEL +static int setup_smart_dma(struct pxafb_info *fbi) +{ + struct pxafb_dma_descriptor *dma_desc; + unsigned long dma_desc_off, cmd_buff_off; + + dma_desc = &fbi->dma_buff->dma_desc[DMA_CMD]; + dma_desc_off = offsetof(struct pxafb_dma_buff, dma_desc[DMA_CMD]); + cmd_buff_off = offsetof(struct pxafb_dma_buff, cmd_buff); + + dma_desc->fdadr = fbi->dma_buff_phys + dma_desc_off; + dma_desc->fsadr = fbi->dma_buff_phys + cmd_buff_off; + dma_desc->fidr = 0; + dma_desc->ldcmd = fbi->n_smart_cmds * sizeof(uint16_t); + + fbi->fdadr[DMA_CMD] = dma_desc->fdadr; + return 0; +} + +int pxafb_smart_flush(struct fb_info *info) +{ + struct pxafb_info *fbi = container_of(info, struct pxafb_info, fb); + uint32_t prsr; + int ret = 0; + + /* disable controller until all registers are set up */ + lcd_writel(fbi, LCCR0, fbi->reg_lccr0 & ~LCCR0_ENB); + + /* 1. make it an even number of commands to align on 32-bit boundary + * 2. add the interrupt command to the end of the chain so we can + * keep track of the end of the transfer + */ + + while (fbi->n_smart_cmds & 1) + fbi->smart_cmds[fbi->n_smart_cmds++] = SMART_CMD_NOOP; + + fbi->smart_cmds[fbi->n_smart_cmds++] = SMART_CMD_INTERRUPT; + fbi->smart_cmds[fbi->n_smart_cmds++] = SMART_CMD_WAIT_FOR_VSYNC; + setup_smart_dma(fbi); + + /* continue to execute next command */ + prsr = lcd_readl(fbi, PRSR) | PRSR_ST_OK | PRSR_CON_NT; + lcd_writel(fbi, PRSR, prsr); + + /* stop the processor in case it executed "wait for sync" cmd */ + lcd_writel(fbi, CMDCR, 0x0001); + + /* don't send interrupts for fifo underruns on channel 6 */ + lcd_writel(fbi, LCCR5, LCCR5_IUM(6)); + + lcd_writel(fbi, LCCR1, fbi->reg_lccr1); + lcd_writel(fbi, LCCR2, fbi->reg_lccr2); + lcd_writel(fbi, LCCR3, fbi->reg_lccr3); + lcd_writel(fbi, LCCR4, fbi->reg_lccr4); + lcd_writel(fbi, FDADR0, fbi->fdadr[0]); + lcd_writel(fbi, FDADR6, fbi->fdadr[6]); + + /* begin sending */ + lcd_writel(fbi, LCCR0, fbi->reg_lccr0 | LCCR0_ENB); + + if (wait_for_completion_timeout(&fbi->command_done, HZ/2) == 0) { + pr_warning("%s: timeout waiting for command done\n", + __func__); + ret = -ETIMEDOUT; + } + + /* quick disable */ + prsr = lcd_readl(fbi, PRSR) & ~(PRSR_ST_OK | PRSR_CON_NT); + lcd_writel(fbi, PRSR, prsr); + lcd_writel(fbi, LCCR0, fbi->reg_lccr0 & ~LCCR0_ENB); + lcd_writel(fbi, FDADR6, 0); + fbi->n_smart_cmds = 0; + return ret; +} + +int pxafb_smart_queue(struct fb_info *info, uint16_t *cmds, int n_cmds) +{ + int i; + struct pxafb_info *fbi = container_of(info, struct pxafb_info, fb); + + for (i = 0; i < n_cmds; i++, cmds++) { + /* if it is a software delay, flush and delay */ + if ((*cmds & 0xff00) == SMART_CMD_DELAY) { + pxafb_smart_flush(info); + mdelay(*cmds & 0xff); + continue; + } + + /* leave 2 commands for INTERRUPT and WAIT_FOR_SYNC */ + if (fbi->n_smart_cmds == CMD_BUFF_SIZE - 8) + pxafb_smart_flush(info); + + fbi->smart_cmds[fbi->n_smart_cmds++] = *cmds; + } + + return 0; +} + +static unsigned int __smart_timing(unsigned time_ns, unsigned long lcd_clk) +{ + unsigned int t = (time_ns * (lcd_clk / 1000000) / 1000); + return (t == 0) ? 1 : t; +} + +static void setup_smart_timing(struct pxafb_info *fbi, + struct fb_var_screeninfo *var) +{ + struct pxafb_mach_info *inf = dev_get_platdata(fbi->dev); + struct pxafb_mode_info *mode = &inf->modes[0]; + unsigned long lclk = clk_get_rate(fbi->clk); + unsigned t1, t2, t3, t4; + + t1 = max(mode->a0csrd_set_hld, mode->a0cswr_set_hld); + t2 = max(mode->rd_pulse_width, mode->wr_pulse_width); + t3 = mode->op_hold_time; + t4 = mode->cmd_inh_time; + + fbi->reg_lccr1 = + LCCR1_DisWdth(var->xres) | + LCCR1_BegLnDel(__smart_timing(t1, lclk)) | + LCCR1_EndLnDel(__smart_timing(t2, lclk)) | + LCCR1_HorSnchWdth(__smart_timing(t3, lclk)); + + fbi->reg_lccr2 = LCCR2_DisHght(var->yres); + fbi->reg_lccr3 = fbi->lccr3 | LCCR3_PixClkDiv(__smart_timing(t4, lclk)); + fbi->reg_lccr3 |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? LCCR3_HSP : 0; + fbi->reg_lccr3 |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? LCCR3_VSP : 0; + + /* FIXME: make this configurable */ + fbi->reg_cmdcr = 1; +} + +static int pxafb_smart_thread(void *arg) +{ + struct pxafb_info *fbi = arg; + struct pxafb_mach_info *inf = dev_get_platdata(fbi->dev); + + if (!inf->smart_update) { + pr_err("%s: not properly initialized, thread terminated\n", + __func__); + return -EINVAL; + } + inf = dev_get_platdata(fbi->dev); + + pr_debug("%s(): task starting\n", __func__); + + set_freezable(); + while (!kthread_should_stop()) { + + if (try_to_freeze()) + continue; + + mutex_lock(&fbi->ctrlr_lock); + + if (fbi->state == C_ENABLE) { + inf->smart_update(&fbi->fb); + complete(&fbi->refresh_done); + } + + mutex_unlock(&fbi->ctrlr_lock); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(30 * HZ / 1000); + } + + pr_debug("%s(): task ending\n", __func__); + return 0; +} + +static int pxafb_smart_init(struct pxafb_info *fbi) +{ + if (!(fbi->lccr0 & LCCR0_LCDT)) + return 0; + + fbi->smart_cmds = (uint16_t *) fbi->dma_buff->cmd_buff; + fbi->n_smart_cmds = 0; + + init_completion(&fbi->command_done); + init_completion(&fbi->refresh_done); + + fbi->smart_thread = kthread_run(pxafb_smart_thread, fbi, + "lcd_refresh"); + if (IS_ERR(fbi->smart_thread)) { + pr_err("%s: unable to create kernel thread\n", __func__); + return PTR_ERR(fbi->smart_thread); + } + + return 0; +} +#else +static inline int pxafb_smart_init(struct pxafb_info *fbi) { return 0; } +#endif /* CONFIG_FB_PXA_SMARTPANEL */ + +static void setup_parallel_timing(struct pxafb_info *fbi, + struct fb_var_screeninfo *var) +{ + unsigned int lines_per_panel, pcd = get_pcd(fbi, var->pixclock); + + fbi->reg_lccr1 = + LCCR1_DisWdth(var->xres) + + LCCR1_HorSnchWdth(var->hsync_len) + + LCCR1_BegLnDel(var->left_margin) + + LCCR1_EndLnDel(var->right_margin); + + /* + * If we have a dual scan LCD, we need to halve + * the YRES parameter. + */ + lines_per_panel = var->yres; + if ((fbi->lccr0 & LCCR0_SDS) == LCCR0_Dual) + lines_per_panel /= 2; + + fbi->reg_lccr2 = + LCCR2_DisHght(lines_per_panel) + + LCCR2_VrtSnchWdth(var->vsync_len) + + LCCR2_BegFrmDel(var->upper_margin) + + LCCR2_EndFrmDel(var->lower_margin); + + fbi->reg_lccr3 = fbi->lccr3 | + (var->sync & FB_SYNC_HOR_HIGH_ACT ? + LCCR3_HorSnchH : LCCR3_HorSnchL) | + (var->sync & FB_SYNC_VERT_HIGH_ACT ? + LCCR3_VrtSnchH : LCCR3_VrtSnchL); + + if (pcd) { + fbi->reg_lccr3 |= LCCR3_PixClkDiv(pcd); + set_hsync_time(fbi, pcd); + } +} + +/* + * pxafb_activate_var(): + * Configures LCD Controller based on entries in var parameter. + * Settings are only written to the controller if changes were made. + */ +static int pxafb_activate_var(struct fb_var_screeninfo *var, + struct pxafb_info *fbi) +{ + u_long flags; + + /* Update shadow copy atomically */ + local_irq_save(flags); + +#ifdef CONFIG_FB_PXA_SMARTPANEL + if (fbi->lccr0 & LCCR0_LCDT) + setup_smart_timing(fbi, var); + else +#endif + setup_parallel_timing(fbi, var); + + setup_base_frame(fbi, var, 0); + + fbi->reg_lccr0 = fbi->lccr0 | + (LCCR0_LDM | LCCR0_SFM | LCCR0_IUM | LCCR0_EFM | + LCCR0_QDM | LCCR0_BM | LCCR0_OUM); + + fbi->reg_lccr3 |= pxafb_var_to_lccr3(var); + + fbi->reg_lccr4 = lcd_readl(fbi, LCCR4) & ~LCCR4_PAL_FOR_MASK; + fbi->reg_lccr4 |= (fbi->lccr4 & LCCR4_PAL_FOR_MASK); + local_irq_restore(flags); + + /* + * Only update the registers if the controller is enabled + * and something has changed. + */ + if ((lcd_readl(fbi, LCCR0) != fbi->reg_lccr0) || + (lcd_readl(fbi, LCCR1) != fbi->reg_lccr1) || + (lcd_readl(fbi, LCCR2) != fbi->reg_lccr2) || + (lcd_readl(fbi, LCCR3) != fbi->reg_lccr3) || + (lcd_readl(fbi, LCCR4) != fbi->reg_lccr4) || + (lcd_readl(fbi, FDADR0) != fbi->fdadr[0]) || + ((fbi->lccr0 & LCCR0_SDS) && + (lcd_readl(fbi, FDADR1) != fbi->fdadr[1]))) + pxafb_schedule_work(fbi, C_REENABLE); + + return 0; +} + +/* + * NOTE! The following functions are purely helpers for set_ctrlr_state. + * Do not call them directly; set_ctrlr_state does the correct serialisation + * to ensure that things happen in the right way 100% of time time. + * -- rmk + */ +static inline void __pxafb_backlight_power(struct pxafb_info *fbi, int on) +{ + pr_debug("pxafb: backlight o%s\n", on ? "n" : "ff"); + + if (fbi->backlight_power) + fbi->backlight_power(on); +} + +static inline void __pxafb_lcd_power(struct pxafb_info *fbi, int on) +{ + pr_debug("pxafb: LCD power o%s\n", on ? "n" : "ff"); + + if (fbi->lcd_power) + fbi->lcd_power(on, &fbi->fb.var); +} + +static void pxafb_enable_controller(struct pxafb_info *fbi) +{ + pr_debug("pxafb: Enabling LCD controller\n"); + pr_debug("fdadr0 0x%08x\n", (unsigned int) fbi->fdadr[0]); + pr_debug("fdadr1 0x%08x\n", (unsigned int) fbi->fdadr[1]); + pr_debug("reg_lccr0 0x%08x\n", (unsigned int) fbi->reg_lccr0); + pr_debug("reg_lccr1 0x%08x\n", (unsigned int) fbi->reg_lccr1); + pr_debug("reg_lccr2 0x%08x\n", (unsigned int) fbi->reg_lccr2); + pr_debug("reg_lccr3 0x%08x\n", (unsigned int) fbi->reg_lccr3); + + /* enable LCD controller clock */ + clk_prepare_enable(fbi->clk); + + if (fbi->lccr0 & LCCR0_LCDT) + return; + + /* Sequence from 11.7.10 */ + lcd_writel(fbi, LCCR4, fbi->reg_lccr4); + lcd_writel(fbi, LCCR3, fbi->reg_lccr3); + lcd_writel(fbi, LCCR2, fbi->reg_lccr2); + lcd_writel(fbi, LCCR1, fbi->reg_lccr1); + lcd_writel(fbi, LCCR0, fbi->reg_lccr0 & ~LCCR0_ENB); + + lcd_writel(fbi, FDADR0, fbi->fdadr[0]); + if (fbi->lccr0 & LCCR0_SDS) + lcd_writel(fbi, FDADR1, fbi->fdadr[1]); + lcd_writel(fbi, LCCR0, fbi->reg_lccr0 | LCCR0_ENB); +} + +static void pxafb_disable_controller(struct pxafb_info *fbi) +{ + uint32_t lccr0; + +#ifdef CONFIG_FB_PXA_SMARTPANEL + if (fbi->lccr0 & LCCR0_LCDT) { + wait_for_completion_timeout(&fbi->refresh_done, + 200 * HZ / 1000); + return; + } +#endif + + /* Clear LCD Status Register */ + lcd_writel(fbi, LCSR, 0xffffffff); + + lccr0 = lcd_readl(fbi, LCCR0) & ~LCCR0_LDM; + lcd_writel(fbi, LCCR0, lccr0); + lcd_writel(fbi, LCCR0, lccr0 | LCCR0_DIS); + + wait_for_completion_timeout(&fbi->disable_done, 200 * HZ / 1000); + + /* disable LCD controller clock */ + clk_disable_unprepare(fbi->clk); +} + +/* + * pxafb_handle_irq: Handle 'LCD DONE' interrupts. + */ +static irqreturn_t pxafb_handle_irq(int irq, void *dev_id) +{ + struct pxafb_info *fbi = dev_id; + unsigned int lccr0, lcsr; + + lcsr = lcd_readl(fbi, LCSR); + if (lcsr & LCSR_LDD) { + lccr0 = lcd_readl(fbi, LCCR0); + lcd_writel(fbi, LCCR0, lccr0 | LCCR0_LDM); + complete(&fbi->disable_done); + } + +#ifdef CONFIG_FB_PXA_SMARTPANEL + if (lcsr & LCSR_CMD_INT) + complete(&fbi->command_done); +#endif + lcd_writel(fbi, LCSR, lcsr); + +#ifdef CONFIG_FB_PXA_OVERLAY + { + unsigned int lcsr1 = lcd_readl(fbi, LCSR1); + if (lcsr1 & LCSR1_BS(1)) + complete(&fbi->overlay[0].branch_done); + + if (lcsr1 & LCSR1_BS(2)) + complete(&fbi->overlay[1].branch_done); + + lcd_writel(fbi, LCSR1, lcsr1); + } +#endif + return IRQ_HANDLED; +} + +/* + * This function must be called from task context only, since it will + * sleep when disabling the LCD controller, or if we get two contending + * processes trying to alter state. + */ +static void set_ctrlr_state(struct pxafb_info *fbi, u_int state) +{ + u_int old_state; + + mutex_lock(&fbi->ctrlr_lock); + + old_state = fbi->state; + + /* + * Hack around fbcon initialisation. + */ + if (old_state == C_STARTUP && state == C_REENABLE) + state = C_ENABLE; + + switch (state) { + case C_DISABLE_CLKCHANGE: + /* + * Disable controller for clock change. If the + * controller is already disabled, then do nothing. + */ + if (old_state != C_DISABLE && old_state != C_DISABLE_PM) { + fbi->state = state; + /* TODO __pxafb_lcd_power(fbi, 0); */ + pxafb_disable_controller(fbi); + } + break; + + case C_DISABLE_PM: + case C_DISABLE: + /* + * Disable controller + */ + if (old_state != C_DISABLE) { + fbi->state = state; + __pxafb_backlight_power(fbi, 0); + __pxafb_lcd_power(fbi, 0); + if (old_state != C_DISABLE_CLKCHANGE) + pxafb_disable_controller(fbi); + } + break; + + case C_ENABLE_CLKCHANGE: + /* + * Enable the controller after clock change. Only + * do this if we were disabled for the clock change. + */ + if (old_state == C_DISABLE_CLKCHANGE) { + fbi->state = C_ENABLE; + pxafb_enable_controller(fbi); + /* TODO __pxafb_lcd_power(fbi, 1); */ + } + break; + + case C_REENABLE: + /* + * Re-enable the controller only if it was already + * enabled. This is so we reprogram the control + * registers. + */ + if (old_state == C_ENABLE) { + __pxafb_lcd_power(fbi, 0); + pxafb_disable_controller(fbi); + pxafb_enable_controller(fbi); + __pxafb_lcd_power(fbi, 1); + } + break; + + case C_ENABLE_PM: + /* + * Re-enable the controller after PM. This is not + * perfect - think about the case where we were doing + * a clock change, and we suspended half-way through. + */ + if (old_state != C_DISABLE_PM) + break; + /* fall through */ + + case C_ENABLE: + /* + * Power up the LCD screen, enable controller, and + * turn on the backlight. + */ + if (old_state != C_ENABLE) { + fbi->state = C_ENABLE; + pxafb_enable_controller(fbi); + __pxafb_lcd_power(fbi, 1); + __pxafb_backlight_power(fbi, 1); + } + break; + } + mutex_unlock(&fbi->ctrlr_lock); +} + +/* + * Our LCD controller task (which is called when we blank or unblank) + * via keventd. + */ +static void pxafb_task(struct work_struct *work) +{ + struct pxafb_info *fbi = + container_of(work, struct pxafb_info, task); + u_int state = xchg(&fbi->task_state, -1); + + set_ctrlr_state(fbi, state); +} + +#ifdef CONFIG_CPU_FREQ +/* + * CPU clock speed change handler. We need to adjust the LCD timing + * parameters when the CPU clock is adjusted by the power management + * subsystem. + * + * TODO: Determine why f->new != 10*get_lclk_frequency_10khz() + */ +static int +pxafb_freq_transition(struct notifier_block *nb, unsigned long val, void *data) +{ + struct pxafb_info *fbi = TO_INF(nb, freq_transition); + /* TODO struct cpufreq_freqs *f = data; */ + u_int pcd; + + switch (val) { + case CPUFREQ_PRECHANGE: +#ifdef CONFIG_FB_PXA_OVERLAY + if (!(fbi->overlay[0].usage || fbi->overlay[1].usage)) +#endif + set_ctrlr_state(fbi, C_DISABLE_CLKCHANGE); + break; + + case CPUFREQ_POSTCHANGE: + pcd = get_pcd(fbi, fbi->fb.var.pixclock); + set_hsync_time(fbi, pcd); + fbi->reg_lccr3 = (fbi->reg_lccr3 & ~0xff) | + LCCR3_PixClkDiv(pcd); + set_ctrlr_state(fbi, C_ENABLE_CLKCHANGE); + break; + } + return 0; +} + +static int +pxafb_freq_policy(struct notifier_block *nb, unsigned long val, void *data) +{ + struct pxafb_info *fbi = TO_INF(nb, freq_policy); + struct fb_var_screeninfo *var = &fbi->fb.var; + struct cpufreq_policy *policy = data; + + switch (val) { + case CPUFREQ_ADJUST: + case CPUFREQ_INCOMPATIBLE: + pr_debug("min dma period: %d ps, " + "new clock %d kHz\n", pxafb_display_dma_period(var), + policy->max); + /* TODO: fill in min/max values */ + break; + } + return 0; +} +#endif + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ +static int pxafb_suspend(struct device *dev) +{ + struct pxafb_info *fbi = dev_get_drvdata(dev); + + set_ctrlr_state(fbi, C_DISABLE_PM); + return 0; +} + +static int pxafb_resume(struct device *dev) +{ + struct pxafb_info *fbi = dev_get_drvdata(dev); + + set_ctrlr_state(fbi, C_ENABLE_PM); + return 0; +} + +static const struct dev_pm_ops pxafb_pm_ops = { + .suspend = pxafb_suspend, + .resume = pxafb_resume, +}; +#endif + +static int pxafb_init_video_memory(struct pxafb_info *fbi) +{ + int size = PAGE_ALIGN(fbi->video_mem_size); + + fbi->video_mem = alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO); + if (fbi->video_mem == NULL) + return -ENOMEM; + + fbi->video_mem_phys = virt_to_phys(fbi->video_mem); + fbi->video_mem_size = size; + + fbi->fb.fix.smem_start = fbi->video_mem_phys; + fbi->fb.fix.smem_len = fbi->video_mem_size; + fbi->fb.screen_base = fbi->video_mem; + + return fbi->video_mem ? 0 : -ENOMEM; +} + +static void pxafb_decode_mach_info(struct pxafb_info *fbi, + struct pxafb_mach_info *inf) +{ + unsigned int lcd_conn = inf->lcd_conn; + struct pxafb_mode_info *m; + int i; + + fbi->cmap_inverse = inf->cmap_inverse; + fbi->cmap_static = inf->cmap_static; + fbi->lccr4 = inf->lccr4; + + switch (lcd_conn & LCD_TYPE_MASK) { + case LCD_TYPE_MONO_STN: + fbi->lccr0 = LCCR0_CMS; + break; + case LCD_TYPE_MONO_DSTN: + fbi->lccr0 = LCCR0_CMS | LCCR0_SDS; + break; + case LCD_TYPE_COLOR_STN: + fbi->lccr0 = 0; + break; + case LCD_TYPE_COLOR_DSTN: + fbi->lccr0 = LCCR0_SDS; + break; + case LCD_TYPE_COLOR_TFT: + fbi->lccr0 = LCCR0_PAS; + break; + case LCD_TYPE_SMART_PANEL: + fbi->lccr0 = LCCR0_LCDT | LCCR0_PAS; + break; + default: + /* fall back to backward compatibility way */ + fbi->lccr0 = inf->lccr0; + fbi->lccr3 = inf->lccr3; + goto decode_mode; + } + + if (lcd_conn == LCD_MONO_STN_8BPP) + fbi->lccr0 |= LCCR0_DPD; + + fbi->lccr0 |= (lcd_conn & LCD_ALTERNATE_MAPPING) ? LCCR0_LDDALT : 0; + + fbi->lccr3 = LCCR3_Acb((inf->lcd_conn >> 10) & 0xff); + fbi->lccr3 |= (lcd_conn & LCD_BIAS_ACTIVE_LOW) ? LCCR3_OEP : 0; + fbi->lccr3 |= (lcd_conn & LCD_PCLK_EDGE_FALL) ? LCCR3_PCP : 0; + +decode_mode: + pxafb_setmode(&fbi->fb.var, &inf->modes[0]); + + /* decide video memory size as follows: + * 1. default to mode of maximum resolution + * 2. allow platform to override + * 3. allow module parameter to override + */ + for (i = 0, m = &inf->modes[0]; i < inf->num_modes; i++, m++) + fbi->video_mem_size = max_t(size_t, fbi->video_mem_size, + m->xres * m->yres * m->bpp / 8); + + if (inf->video_mem_size > fbi->video_mem_size) + fbi->video_mem_size = inf->video_mem_size; + + if (video_mem_size > fbi->video_mem_size) + fbi->video_mem_size = video_mem_size; +} + +static struct pxafb_info *pxafb_init_fbinfo(struct device *dev) +{ + struct pxafb_info *fbi; + void *addr; + struct pxafb_mach_info *inf = dev_get_platdata(dev); + + /* Alloc the pxafb_info and pseudo_palette in one step */ + fbi = kmalloc(sizeof(struct pxafb_info) + sizeof(u32) * 16, GFP_KERNEL); + if (!fbi) + return NULL; + + memset(fbi, 0, sizeof(struct pxafb_info)); + fbi->dev = dev; + + fbi->clk = clk_get(dev, NULL); + if (IS_ERR(fbi->clk)) { + kfree(fbi); + return NULL; + } + + strcpy(fbi->fb.fix.id, PXA_NAME); + + fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fbi->fb.fix.type_aux = 0; + fbi->fb.fix.xpanstep = 0; + fbi->fb.fix.ypanstep = 1; + fbi->fb.fix.ywrapstep = 0; + fbi->fb.fix.accel = FB_ACCEL_NONE; + + fbi->fb.var.nonstd = 0; + fbi->fb.var.activate = FB_ACTIVATE_NOW; + fbi->fb.var.height = -1; + fbi->fb.var.width = -1; + fbi->fb.var.accel_flags = FB_ACCELF_TEXT; + fbi->fb.var.vmode = FB_VMODE_NONINTERLACED; + + fbi->fb.fbops = &pxafb_ops; + fbi->fb.flags = FBINFO_DEFAULT; + fbi->fb.node = -1; + + addr = fbi; + addr = addr + sizeof(struct pxafb_info); + fbi->fb.pseudo_palette = addr; + + fbi->state = C_STARTUP; + fbi->task_state = (u_char)-1; + + pxafb_decode_mach_info(fbi, inf); + +#ifdef CONFIG_FB_PXA_OVERLAY + /* place overlay(s) on top of base */ + if (pxafb_overlay_supported()) + fbi->lccr0 |= LCCR0_OUC; +#endif + + init_waitqueue_head(&fbi->ctrlr_wait); + INIT_WORK(&fbi->task, pxafb_task); + mutex_init(&fbi->ctrlr_lock); + init_completion(&fbi->disable_done); + + return fbi; +} + +#ifdef CONFIG_FB_PXA_PARAMETERS +static int parse_opt_mode(struct device *dev, const char *this_opt) +{ + struct pxafb_mach_info *inf = dev_get_platdata(dev); + + const char *name = this_opt+5; + unsigned int namelen = strlen(name); + int res_specified = 0, bpp_specified = 0; + unsigned int xres = 0, yres = 0, bpp = 0; + int yres_specified = 0; + int i; + for (i = namelen-1; i >= 0; i--) { + switch (name[i]) { + case '-': + namelen = i; + if (!bpp_specified && !yres_specified) { + bpp = simple_strtoul(&name[i+1], NULL, 0); + bpp_specified = 1; + } else + goto done; + break; + case 'x': + if (!yres_specified) { + yres = simple_strtoul(&name[i+1], NULL, 0); + yres_specified = 1; + } else + goto done; + break; + case '0' ... '9': + break; + default: + goto done; + } + } + if (i < 0 && yres_specified) { + xres = simple_strtoul(name, NULL, 0); + res_specified = 1; + } +done: + if (res_specified) { + dev_info(dev, "overriding resolution: %dx%d\n", xres, yres); + inf->modes[0].xres = xres; inf->modes[0].yres = yres; + } + if (bpp_specified) + switch (bpp) { + case 1: + case 2: + case 4: + case 8: + case 16: + inf->modes[0].bpp = bpp; + dev_info(dev, "overriding bit depth: %d\n", bpp); + break; + default: + dev_err(dev, "Depth %d is not valid\n", bpp); + return -EINVAL; + } + return 0; +} + +static int parse_opt(struct device *dev, char *this_opt) +{ + struct pxafb_mach_info *inf = dev_get_platdata(dev); + struct pxafb_mode_info *mode = &inf->modes[0]; + char s[64]; + + s[0] = '\0'; + + if (!strncmp(this_opt, "vmem:", 5)) { + video_mem_size = memparse(this_opt + 5, NULL); + } else if (!strncmp(this_opt, "mode:", 5)) { + return parse_opt_mode(dev, this_opt); + } else if (!strncmp(this_opt, "pixclock:", 9)) { + mode->pixclock = simple_strtoul(this_opt+9, NULL, 0); + sprintf(s, "pixclock: %ld\n", mode->pixclock); + } else if (!strncmp(this_opt, "left:", 5)) { + mode->left_margin = simple_strtoul(this_opt+5, NULL, 0); + sprintf(s, "left: %u\n", mode->left_margin); + } else if (!strncmp(this_opt, "right:", 6)) { + mode->right_margin = simple_strtoul(this_opt+6, NULL, 0); + sprintf(s, "right: %u\n", mode->right_margin); + } else if (!strncmp(this_opt, "upper:", 6)) { + mode->upper_margin = simple_strtoul(this_opt+6, NULL, 0); + sprintf(s, "upper: %u\n", mode->upper_margin); + } else if (!strncmp(this_opt, "lower:", 6)) { + mode->lower_margin = simple_strtoul(this_opt+6, NULL, 0); + sprintf(s, "lower: %u\n", mode->lower_margin); + } else if (!strncmp(this_opt, "hsynclen:", 9)) { + mode->hsync_len = simple_strtoul(this_opt+9, NULL, 0); + sprintf(s, "hsynclen: %u\n", mode->hsync_len); + } else if (!strncmp(this_opt, "vsynclen:", 9)) { + mode->vsync_len = simple_strtoul(this_opt+9, NULL, 0); + sprintf(s, "vsynclen: %u\n", mode->vsync_len); + } else if (!strncmp(this_opt, "hsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0) == 0) { + sprintf(s, "hsync: Active Low\n"); + mode->sync &= ~FB_SYNC_HOR_HIGH_ACT; + } else { + sprintf(s, "hsync: Active High\n"); + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + } + } else if (!strncmp(this_opt, "vsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0) == 0) { + sprintf(s, "vsync: Active Low\n"); + mode->sync &= ~FB_SYNC_VERT_HIGH_ACT; + } else { + sprintf(s, "vsync: Active High\n"); + mode->sync |= FB_SYNC_VERT_HIGH_ACT; + } + } else if (!strncmp(this_opt, "dpc:", 4)) { + if (simple_strtoul(this_opt+4, NULL, 0) == 0) { + sprintf(s, "double pixel clock: false\n"); + inf->lccr3 &= ~LCCR3_DPC; + } else { + sprintf(s, "double pixel clock: true\n"); + inf->lccr3 |= LCCR3_DPC; + } + } else if (!strncmp(this_opt, "outputen:", 9)) { + if (simple_strtoul(this_opt+9, NULL, 0) == 0) { + sprintf(s, "output enable: active low\n"); + inf->lccr3 = (inf->lccr3 & ~LCCR3_OEP) | LCCR3_OutEnL; + } else { + sprintf(s, "output enable: active high\n"); + inf->lccr3 = (inf->lccr3 & ~LCCR3_OEP) | LCCR3_OutEnH; + } + } else if (!strncmp(this_opt, "pixclockpol:", 12)) { + if (simple_strtoul(this_opt+12, NULL, 0) == 0) { + sprintf(s, "pixel clock polarity: falling edge\n"); + inf->lccr3 = (inf->lccr3 & ~LCCR3_PCP) | LCCR3_PixFlEdg; + } else { + sprintf(s, "pixel clock polarity: rising edge\n"); + inf->lccr3 = (inf->lccr3 & ~LCCR3_PCP) | LCCR3_PixRsEdg; + } + } else if (!strncmp(this_opt, "color", 5)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_CMS) | LCCR0_Color; + } else if (!strncmp(this_opt, "mono", 4)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_CMS) | LCCR0_Mono; + } else if (!strncmp(this_opt, "active", 6)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_PAS) | LCCR0_Act; + } else if (!strncmp(this_opt, "passive", 7)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_PAS) | LCCR0_Pas; + } else if (!strncmp(this_opt, "single", 6)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_SDS) | LCCR0_Sngl; + } else if (!strncmp(this_opt, "dual", 4)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_SDS) | LCCR0_Dual; + } else if (!strncmp(this_opt, "4pix", 4)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_DPD) | LCCR0_4PixMono; + } else if (!strncmp(this_opt, "8pix", 4)) { + inf->lccr0 = (inf->lccr0 & ~LCCR0_DPD) | LCCR0_8PixMono; + } else { + dev_err(dev, "unknown option: %s\n", this_opt); + return -EINVAL; + } + + if (s[0] != '\0') + dev_info(dev, "override %s", s); + + return 0; +} + +static int pxafb_parse_options(struct device *dev, char *options) +{ + char *this_opt; + int ret; + + if (!options || !*options) + return 0; + + dev_dbg(dev, "options are \"%s\"\n", options ? options : "null"); + + /* could be made table driven or similar?... */ + while ((this_opt = strsep(&options, ",")) != NULL) { + ret = parse_opt(dev, this_opt); + if (ret) + return ret; + } + return 0; +} + +static char g_options[256] = ""; + +#ifndef MODULE +static int __init pxafb_setup_options(void) +{ + char *options = NULL; + + if (fb_get_options("pxafb", &options)) + return -ENODEV; + + if (options) + strlcpy(g_options, options, sizeof(g_options)); + + return 0; +} +#else +#define pxafb_setup_options() (0) + +module_param_string(options, g_options, sizeof(g_options), 0); +MODULE_PARM_DESC(options, "LCD parameters (see Documentation/fb/pxafb.txt)"); +#endif + +#else +#define pxafb_parse_options(...) (0) +#define pxafb_setup_options() (0) +#endif + +#ifdef DEBUG_VAR +/* Check for various illegal bit-combinations. Currently only + * a warning is given. */ +static void pxafb_check_options(struct device *dev, struct pxafb_mach_info *inf) +{ + if (inf->lcd_conn) + return; + + if (inf->lccr0 & LCCR0_INVALID_CONFIG_MASK) + dev_warn(dev, "machine LCCR0 setting contains " + "illegal bits: %08x\n", + inf->lccr0 & LCCR0_INVALID_CONFIG_MASK); + if (inf->lccr3 & LCCR3_INVALID_CONFIG_MASK) + dev_warn(dev, "machine LCCR3 setting contains " + "illegal bits: %08x\n", + inf->lccr3 & LCCR3_INVALID_CONFIG_MASK); + if (inf->lccr0 & LCCR0_DPD && + ((inf->lccr0 & LCCR0_PAS) != LCCR0_Pas || + (inf->lccr0 & LCCR0_SDS) != LCCR0_Sngl || + (inf->lccr0 & LCCR0_CMS) != LCCR0_Mono)) + dev_warn(dev, "Double Pixel Data (DPD) mode is " + "only valid in passive mono" + " single panel mode\n"); + if ((inf->lccr0 & LCCR0_PAS) == LCCR0_Act && + (inf->lccr0 & LCCR0_SDS) == LCCR0_Dual) + dev_warn(dev, "Dual panel only valid in passive mode\n"); + if ((inf->lccr0 & LCCR0_PAS) == LCCR0_Pas && + (inf->modes->upper_margin || inf->modes->lower_margin)) + dev_warn(dev, "Upper and lower margins must be 0 in " + "passive mode\n"); +} +#else +#define pxafb_check_options(...) do {} while (0) +#endif + +static int pxafb_probe(struct platform_device *dev) +{ + struct pxafb_info *fbi; + struct pxafb_mach_info *inf; + struct resource *r; + int irq, ret; + + dev_dbg(&dev->dev, "pxafb_probe\n"); + + inf = dev_get_platdata(&dev->dev); + ret = -ENOMEM; + fbi = NULL; + if (!inf) + goto failed; + + ret = pxafb_parse_options(&dev->dev, g_options); + if (ret < 0) + goto failed; + + pxafb_check_options(&dev->dev, inf); + + dev_dbg(&dev->dev, "got a %dx%dx%d LCD\n", + inf->modes->xres, + inf->modes->yres, + inf->modes->bpp); + if (inf->modes->xres == 0 || + inf->modes->yres == 0 || + inf->modes->bpp == 0) { + dev_err(&dev->dev, "Invalid resolution or bit depth\n"); + ret = -EINVAL; + goto failed; + } + + fbi = pxafb_init_fbinfo(&dev->dev); + if (!fbi) { + /* only reason for pxafb_init_fbinfo to fail is kmalloc */ + dev_err(&dev->dev, "Failed to initialize framebuffer device\n"); + ret = -ENOMEM; + goto failed; + } + + if (cpu_is_pxa3xx() && inf->acceleration_enabled) + fbi->fb.fix.accel = FB_ACCEL_PXA3XX; + + fbi->backlight_power = inf->pxafb_backlight_power; + fbi->lcd_power = inf->pxafb_lcd_power; + + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&dev->dev, "no I/O memory resource defined\n"); + ret = -ENODEV; + goto failed_fbi; + } + + r = request_mem_region(r->start, resource_size(r), dev->name); + if (r == NULL) { + dev_err(&dev->dev, "failed to request I/O memory\n"); + ret = -EBUSY; + goto failed_fbi; + } + + fbi->mmio_base = ioremap(r->start, resource_size(r)); + if (fbi->mmio_base == NULL) { + dev_err(&dev->dev, "failed to map I/O memory\n"); + ret = -EBUSY; + goto failed_free_res; + } + + fbi->dma_buff_size = PAGE_ALIGN(sizeof(struct pxafb_dma_buff)); + fbi->dma_buff = dma_alloc_coherent(fbi->dev, fbi->dma_buff_size, + &fbi->dma_buff_phys, GFP_KERNEL); + if (fbi->dma_buff == NULL) { + dev_err(&dev->dev, "failed to allocate memory for DMA\n"); + ret = -ENOMEM; + goto failed_free_io; + } + + ret = pxafb_init_video_memory(fbi); + if (ret) { + dev_err(&dev->dev, "Failed to allocate video RAM: %d\n", ret); + ret = -ENOMEM; + goto failed_free_dma; + } + + irq = platform_get_irq(dev, 0); + if (irq < 0) { + dev_err(&dev->dev, "no IRQ defined\n"); + ret = -ENODEV; + goto failed_free_mem; + } + + ret = request_irq(irq, pxafb_handle_irq, 0, "LCD", fbi); + if (ret) { + dev_err(&dev->dev, "request_irq failed: %d\n", ret); + ret = -EBUSY; + goto failed_free_mem; + } + + ret = pxafb_smart_init(fbi); + if (ret) { + dev_err(&dev->dev, "failed to initialize smartpanel\n"); + goto failed_free_irq; + } + + /* + * This makes sure that our colour bitfield + * descriptors are correctly initialised. + */ + ret = pxafb_check_var(&fbi->fb.var, &fbi->fb); + if (ret) { + dev_err(&dev->dev, "failed to get suitable mode\n"); + goto failed_free_irq; + } + + ret = pxafb_set_par(&fbi->fb); + if (ret) { + dev_err(&dev->dev, "Failed to set parameters\n"); + goto failed_free_irq; + } + + platform_set_drvdata(dev, fbi); + + ret = register_framebuffer(&fbi->fb); + if (ret < 0) { + dev_err(&dev->dev, + "Failed to register framebuffer device: %d\n", ret); + goto failed_free_cmap; + } + + pxafb_overlay_init(fbi); + +#ifdef CONFIG_CPU_FREQ + fbi->freq_transition.notifier_call = pxafb_freq_transition; + fbi->freq_policy.notifier_call = pxafb_freq_policy; + cpufreq_register_notifier(&fbi->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); + cpufreq_register_notifier(&fbi->freq_policy, + CPUFREQ_POLICY_NOTIFIER); +#endif + + /* + * Ok, now enable the LCD controller + */ + set_ctrlr_state(fbi, C_ENABLE); + + return 0; + +failed_free_cmap: + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); +failed_free_irq: + free_irq(irq, fbi); +failed_free_mem: + free_pages_exact(fbi->video_mem, fbi->video_mem_size); +failed_free_dma: + dma_free_coherent(&dev->dev, fbi->dma_buff_size, + fbi->dma_buff, fbi->dma_buff_phys); +failed_free_io: + iounmap(fbi->mmio_base); +failed_free_res: + release_mem_region(r->start, resource_size(r)); +failed_fbi: + clk_put(fbi->clk); + kfree(fbi); +failed: + return ret; +} + +static int pxafb_remove(struct platform_device *dev) +{ + struct pxafb_info *fbi = platform_get_drvdata(dev); + struct resource *r; + int irq; + struct fb_info *info; + + if (!fbi) + return 0; + + info = &fbi->fb; + + pxafb_overlay_exit(fbi); + unregister_framebuffer(info); + + pxafb_disable_controller(fbi); + + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); + + irq = platform_get_irq(dev, 0); + free_irq(irq, fbi); + + free_pages_exact(fbi->video_mem, fbi->video_mem_size); + + dma_free_writecombine(&dev->dev, fbi->dma_buff_size, + fbi->dma_buff, fbi->dma_buff_phys); + + iounmap(fbi->mmio_base); + + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + clk_put(fbi->clk); + kfree(fbi); + + return 0; +} + +static struct platform_driver pxafb_driver = { + .probe = pxafb_probe, + .remove = pxafb_remove, + .driver = { + .owner = THIS_MODULE, + .name = "pxa2xx-fb", +#ifdef CONFIG_PM + .pm = &pxafb_pm_ops, +#endif + }, +}; + +static int __init pxafb_init(void) +{ + if (pxafb_setup_options()) + return -EINVAL; + + return platform_driver_register(&pxafb_driver); +} + +static void __exit pxafb_exit(void) +{ + platform_driver_unregister(&pxafb_driver); +} + +module_init(pxafb_init); +module_exit(pxafb_exit); + +MODULE_DESCRIPTION("loadable framebuffer driver for PXA"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/pxafb.h b/drivers/video/fbdev/pxafb.h new file mode 100644 index 000000000000..26ba9fa3f737 --- /dev/null +++ b/drivers/video/fbdev/pxafb.h @@ -0,0 +1,200 @@ +#ifndef __PXAFB_H__ +#define __PXAFB_H__ + +/* + * linux/drivers/video/pxafb.h + * -- Intel PXA250/210 LCD Controller Frame Buffer Device + * + * Copyright (C) 1999 Eric A. Thomas. + * Copyright (C) 2004 Jean-Frederic Clere. + * Copyright (C) 2004 Ian Campbell. + * Copyright (C) 2004 Jeff Lackey. + * Based on sa1100fb.c Copyright (C) 1999 Eric A. Thomas + * which in turn is + * Based on acornfb.c Copyright (C) Russell King. + * + * 2001-08-03: Cliff Brake <cbrake@acclent.com> + * - ported SA1100 code to PXA + * + * 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. + */ + +/* PXA LCD DMA descriptor */ +struct pxafb_dma_descriptor { + unsigned int fdadr; + unsigned int fsadr; + unsigned int fidr; + unsigned int ldcmd; +}; + +enum { + PAL_NONE = -1, + PAL_BASE = 0, + PAL_OV1 = 1, + PAL_OV2 = 2, + PAL_MAX, +}; + +enum { + DMA_BASE = 0, + DMA_UPPER = 0, + DMA_LOWER = 1, + DMA_OV1 = 1, + DMA_OV2_Y = 2, + DMA_OV2_Cb = 3, + DMA_OV2_Cr = 4, + DMA_CURSOR = 5, + DMA_CMD = 6, + DMA_MAX, +}; + +/* maximum palette size - 256 entries, each 4 bytes long */ +#define PALETTE_SIZE (256 * 4) +#define CMD_BUFF_SIZE (1024 * 50) + +/* NOTE: the palette and frame dma descriptors are doubled to allow + * the 2nd set for branch settings (FBRx) + */ +struct pxafb_dma_buff { + unsigned char palette[PAL_MAX * PALETTE_SIZE]; + uint16_t cmd_buff[CMD_BUFF_SIZE]; + struct pxafb_dma_descriptor pal_desc[PAL_MAX * 2]; + struct pxafb_dma_descriptor dma_desc[DMA_MAX * 2]; +}; + +enum { + OVERLAY1, + OVERLAY2, +}; + +enum { + OVERLAY_FORMAT_RGB = 0, + OVERLAY_FORMAT_YUV444_PACKED, + OVERLAY_FORMAT_YUV444_PLANAR, + OVERLAY_FORMAT_YUV422_PLANAR, + OVERLAY_FORMAT_YUV420_PLANAR, +}; + +#define NONSTD_TO_XPOS(x) (((x) >> 0) & 0x3ff) +#define NONSTD_TO_YPOS(x) (((x) >> 10) & 0x3ff) +#define NONSTD_TO_PFOR(x) (((x) >> 20) & 0x7) + +struct pxafb_layer; + +struct pxafb_layer_ops { + void (*enable)(struct pxafb_layer *); + void (*disable)(struct pxafb_layer *); + void (*setup)(struct pxafb_layer *); +}; + +struct pxafb_layer { + struct fb_info fb; + int id; + int registered; + uint32_t usage; + uint32_t control[2]; + + struct pxafb_layer_ops *ops; + + void __iomem *video_mem; + unsigned long video_mem_phys; + size_t video_mem_size; + struct completion branch_done; + + struct pxafb_info *fbi; +}; + +struct pxafb_info { + struct fb_info fb; + struct device *dev; + struct clk *clk; + + void __iomem *mmio_base; + + struct pxafb_dma_buff *dma_buff; + size_t dma_buff_size; + dma_addr_t dma_buff_phys; + dma_addr_t fdadr[DMA_MAX * 2]; + + void __iomem *video_mem; /* virtual address of frame buffer */ + unsigned long video_mem_phys; /* physical address of frame buffer */ + size_t video_mem_size; /* size of the frame buffer */ + u16 * palette_cpu; /* virtual address of palette memory */ + u_int palette_size; + + u_int lccr0; + u_int lccr3; + u_int lccr4; + u_int cmap_inverse:1, + cmap_static:1, + unused:30; + + u_int reg_lccr0; + u_int reg_lccr1; + u_int reg_lccr2; + u_int reg_lccr3; + u_int reg_lccr4; + u_int reg_cmdcr; + + unsigned long hsync_time; + + volatile u_char state; + volatile u_char task_state; + struct mutex ctrlr_lock; + wait_queue_head_t ctrlr_wait; + struct work_struct task; + + struct completion disable_done; + +#ifdef CONFIG_FB_PXA_SMARTPANEL + uint16_t *smart_cmds; + size_t n_smart_cmds; + struct completion command_done; + struct completion refresh_done; + struct task_struct *smart_thread; +#endif + +#ifdef CONFIG_FB_PXA_OVERLAY + struct pxafb_layer overlay[2]; +#endif + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; + struct notifier_block freq_policy; +#endif + + void (*lcd_power)(int, struct fb_var_screeninfo *); + void (*backlight_power)(int); +}; + +#define TO_INF(ptr,member) container_of(ptr,struct pxafb_info,member) + +/* + * These are the actions for set_ctrlr_state + */ +#define C_DISABLE (0) +#define C_ENABLE (1) +#define C_DISABLE_CLKCHANGE (2) +#define C_ENABLE_CLKCHANGE (3) +#define C_REENABLE (4) +#define C_DISABLE_PM (5) +#define C_ENABLE_PM (6) +#define C_STARTUP (7) + +#define PXA_NAME "PXA" + +/* + * Minimum X and Y resolutions + */ +#define MIN_XRES 64 +#define MIN_YRES 64 + +/* maximum X and Y resolutions - note these are limits from the register + * bits length instead of the real ones + */ +#define MAX_XRES 1024 +#define MAX_YRES 1024 + +#endif /* __PXAFB_H__ */ diff --git a/drivers/video/fbdev/q40fb.c b/drivers/video/fbdev/q40fb.c new file mode 100644 index 000000000000..7487f76f6275 --- /dev/null +++ b/drivers/video/fbdev/q40fb.c @@ -0,0 +1,155 @@ +/* + * linux/drivers/video/q40fb.c -- Q40 frame buffer device + * + * Copyright (C) 2001 + * + * Richard Zidlicky <rz@linux-m68k.org> + * + * 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/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <asm/uaccess.h> +#include <asm/setup.h> +#include <asm/q40_master.h> +#include <linux/fb.h> +#include <linux/module.h> +#include <asm/pgtable.h> + +#define Q40_PHYS_SCREEN_ADDR 0xFE800000 + +static struct fb_fix_screeninfo q40fb_fix = { + .id = "Q40", + .smem_len = 1024*1024, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .line_length = 1024*2, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo q40fb_var = { + .xres = 1024, + .yres = 512, + .xres_virtual = 1024, + .yres_virtual = 512, + .bits_per_pixel = 16, + .red = {6, 5, 0}, + .green = {11, 5, 0}, + .blue = {0, 6, 0}, + .activate = FB_ACTIVATE_NOW, + .height = 230, + .width = 300, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static int q40fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + /* + * Set a single color register. The values supplied have a 16 bit + * magnitude. + * Return != 0 for invalid regno. + */ + + if (regno > 255) + return 1; + red>>=11; + green>>=11; + blue>>=10; + + if (regno < 16) { + ((u32 *)info->pseudo_palette)[regno] = ((red & 31) <<6) | + ((green & 31) << 11) | + (blue & 63); + } + return 0; +} + +static struct fb_ops q40fb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = q40fb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int q40fb_probe(struct platform_device *dev) +{ + struct fb_info *info; + + if (!MACH_IS_Q40) + return -ENXIO; + + /* mapped in q40/config.c */ + q40fb_fix.smem_start = Q40_PHYS_SCREEN_ADDR; + + info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev); + if (!info) + return -ENOMEM; + + info->var = q40fb_var; + info->fix = q40fb_fix; + info->fbops = &q40fb_ops; + info->flags = FBINFO_DEFAULT; /* not as module for now */ + info->pseudo_palette = info->par; + info->par = NULL; + info->screen_base = (char *) q40fb_fix.smem_start; + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + framebuffer_release(info); + return -ENOMEM; + } + + master_outb(3, DISPLAY_CONTROL_REG); + + if (register_framebuffer(info) < 0) { + printk(KERN_ERR "Unable to register Q40 frame buffer\n"); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + return -EINVAL; + } + + fb_info(info, "Q40 frame buffer alive and kicking !\n"); + return 0; +} + +static struct platform_driver q40fb_driver = { + .probe = q40fb_probe, + .driver = { + .name = "q40fb", + }, +}; + +static struct platform_device q40fb_device = { + .name = "q40fb", +}; + +int __init q40fb_init(void) +{ + int ret = 0; + + if (fb_get_options("q40fb", NULL)) + return -ENODEV; + + ret = platform_driver_register(&q40fb_driver); + + if (!ret) { + ret = platform_device_register(&q40fb_device); + if (ret) + platform_driver_unregister(&q40fb_driver); + } + return ret; +} + +module_init(q40fb_init); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/riva/Makefile b/drivers/video/fbdev/riva/Makefile new file mode 100644 index 000000000000..8898c9915b02 --- /dev/null +++ b/drivers/video/fbdev/riva/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the Riva framebuffer driver +# + +obj-$(CONFIG_FB_RIVA) += rivafb.o + +rivafb-objs := fbdev.o riva_hw.o nv_driver.o + +ifdef CONFIG_FB_RIVA_I2C + rivafb-objs += rivafb-i2c.o +endif diff --git a/drivers/video/fbdev/riva/fbdev.c b/drivers/video/fbdev/riva/fbdev.c new file mode 100644 index 000000000000..8a8d7f060784 --- /dev/null +++ b/drivers/video/fbdev/riva/fbdev.c @@ -0,0 +1,2230 @@ +/* + * linux/drivers/video/riva/fbdev.c - nVidia RIVA 128/TNT/TNT2 fb driver + * + * Maintained by Ani Joshi <ajoshi@shell.unixbox.com> + * + * Copyright 1999-2000 Jeff Garzik + * + * Contributors: + * + * Ani Joshi: Lots of debugging and cleanup work, really helped + * get the driver going + * + * Ferenc Bakonyi: Bug fixes, cleanup, modularization + * + * Jindrich Makovicka: Accel code help, hw cursor, mtrr + * + * Paul Richards: Bug fixes, updates + * + * Initial template from skeletonfb.c, created 28 Dec 1997 by Geert Uytterhoeven + * Includes riva_hw.c from nVidia, see copyright below. + * KGI code provided the basis for state storage, init, and mode switching. + * + * 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. + * + * Known bugs and issues: + * restoring text mode fails + * doublescan modes are broken + */ + +#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> +#include <linux/backlight.h> +#include <linux/bitrev.h> +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif +#ifdef CONFIG_PPC_OF +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#endif +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/machdep.h> +#include <asm/backlight.h> +#endif + +#include "rivafb.h" +#include "nvreg.h" + +/* version number of this driver */ +#define RIVAFB_VERSION "0.9.5b" + +/* ------------------------------------------------------------------------- * + * + * various helpful macros and constants + * + * ------------------------------------------------------------------------- */ +#ifdef CONFIG_FB_RIVA_DEBUG +#define NVTRACE printk +#else +#define NVTRACE if(0) printk +#endif + +#define NVTRACE_ENTER(...) NVTRACE("%s START\n", __func__) +#define NVTRACE_LEAVE(...) NVTRACE("%s END\n", __func__) + +#ifdef CONFIG_FB_RIVA_DEBUG +#define assert(expr) \ + if(!(expr)) { \ + printk( "Assertion failed! %s,%s,%s,line=%d\n",\ + #expr,__FILE__,__func__,__LINE__); \ + BUG(); \ + } +#else +#define assert(expr) +#endif + +#define PFX "rivafb: " + +/* macro that allows you to set overflow bits */ +#define SetBitField(value,from,to) SetBF(to,GetBF(value,from)) +#define SetBit(n) (1<<(n)) +#define Set8Bits(value) ((value)&0xff) + +/* HW cursor parameters */ +#define MAX_CURS 32 + +/* ------------------------------------------------------------------------- * + * + * prototypes + * + * ------------------------------------------------------------------------- */ + +static int rivafb_blank(int blank, struct fb_info *info); + +/* ------------------------------------------------------------------------- * + * + * card identification + * + * ------------------------------------------------------------------------- */ + +static struct pci_device_id rivafb_pci_tbl[] = { + { PCI_VENDOR_ID_NVIDIA_SGS, PCI_DEVICE_ID_NVIDIA_SGS_RIVA128, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_TNT, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_TNT2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_UTNT2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_VTNT2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_UVTNT2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_ITNT2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE_SDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE_DDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE2_MX, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE2_MX2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE2_GO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO2_MXR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE2_GTS, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE2_GTS2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE2_ULTRA, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO2_PRO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_MX_460, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_MX_440, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + // NF2/IGP version, GeForce 4 MX, NV18 + { PCI_VENDOR_ID_NVIDIA, 0x01f0, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_MX_420, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_440_GO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_420_GO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_420_GO_M32, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO4_500XGL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_440_GO_M64, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO4_200, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO4_550XGL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO4_500_GOGL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_IGEFORCE2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE3, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE3_1, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE3_2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO_DDC, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_TI_4600, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_TI_4400, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE4_TI_4200, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO4_900XGL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO4_750XGL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_QUADRO4_700XGL, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_GEFORCE_FX_GO_5200, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, rivafb_pci_tbl); + +/* ------------------------------------------------------------------------- * + * + * global variables + * + * ------------------------------------------------------------------------- */ + +/* command line data, set in rivafb_setup() */ +static int flatpanel = -1; /* Autodetect later */ +static int forceCRTC = -1; +static bool noaccel = 0; +#ifdef CONFIG_MTRR +static bool nomtrr = 0; +#endif +#ifdef CONFIG_PMAC_BACKLIGHT +static int backlight = 1; +#else +static int backlight = 0; +#endif + +static char *mode_option = NULL; +static bool strictmode = 0; + +static struct fb_fix_screeninfo rivafb_fix = { + .type = FB_TYPE_PACKED_PIXELS, + .xpanstep = 1, + .ypanstep = 1, +}; + +static struct fb_var_screeninfo rivafb_default_var = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 8, + .red = {0, 8, 0}, + .green = {0, 8, 0}, + .blue = {0, 8, 0}, + .transp = {0, 0, 0}, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .pixclock = 39721, + .left_margin = 40, + .right_margin = 24, + .upper_margin = 32, + .lower_margin = 11, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED +}; + +/* from GGI */ +static const struct riva_regs reg_template = { + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* ATTR */ + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x41, 0x01, 0x0F, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* CRT */ + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, /* 0x10 */ + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x20 */ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x30 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, /* 0x40 */ + }, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, /* GRA */ + 0xFF}, + {0x03, 0x01, 0x0F, 0x00, 0x0E}, /* SEQ */ + 0xEB /* MISC */ +}; + +/* + * Backlight control + */ +#ifdef CONFIG_FB_RIVA_BACKLIGHT +/* We do not have any information about which values are allowed, thus + * we used safe values. + */ +#define MIN_LEVEL 0x158 +#define MAX_LEVEL 0x534 +#define LEVEL_STEP ((MAX_LEVEL - MIN_LEVEL) / FB_BACKLIGHT_MAX) + +static int riva_bl_get_level_brightness(struct riva_par *par, + int level) +{ + struct fb_info *info = pci_get_drvdata(par->pdev); + int nlevel; + + /* Get and convert the value */ + /* No locking on bl_curve since accessing a single value */ + nlevel = MIN_LEVEL + info->bl_curve[level] * LEVEL_STEP; + + if (nlevel < 0) + nlevel = 0; + else if (nlevel < MIN_LEVEL) + nlevel = MIN_LEVEL; + else if (nlevel > MAX_LEVEL) + nlevel = MAX_LEVEL; + + return nlevel; +} + +static int riva_bl_update_status(struct backlight_device *bd) +{ + struct riva_par *par = bl_get_data(bd); + U032 tmp_pcrt, tmp_pmc; + int level; + + if (bd->props.power != FB_BLANK_UNBLANK || + bd->props.fb_blank != FB_BLANK_UNBLANK) + level = 0; + else + level = bd->props.brightness; + + tmp_pmc = NV_RD32(par->riva.PMC, 0x10F0) & 0x0000FFFF; + tmp_pcrt = NV_RD32(par->riva.PCRTC0, 0x081C) & 0xFFFFFFFC; + if(level > 0) { + tmp_pcrt |= 0x1; + tmp_pmc |= (1 << 31); /* backlight bit */ + tmp_pmc |= riva_bl_get_level_brightness(par, level) << 16; /* level */ + } + NV_WR32(par->riva.PCRTC0, 0x081C, tmp_pcrt); + NV_WR32(par->riva.PMC, 0x10F0, tmp_pmc); + + return 0; +} + +static int riva_bl_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static const struct backlight_ops riva_bl_ops = { + .get_brightness = riva_bl_get_brightness, + .update_status = riva_bl_update_status, +}; + +static void riva_bl_init(struct riva_par *par) +{ + struct backlight_properties props; + struct fb_info *info = pci_get_drvdata(par->pdev); + struct backlight_device *bd; + char name[12]; + + if (!par->FlatPanel) + return; + +#ifdef CONFIG_PMAC_BACKLIGHT + if (!machine_is(powermac) || + !pmac_has_backlight_type("mnca")) + return; +#endif + + snprintf(name, sizeof(name), "rivabl%d", info->node); + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = FB_BACKLIGHT_LEVELS - 1; + bd = backlight_device_register(name, info->dev, par, &riva_bl_ops, + &props); + if (IS_ERR(bd)) { + info->bl_dev = NULL; + printk(KERN_WARNING "riva: Backlight registration failed\n"); + goto error; + } + + info->bl_dev = bd; + fb_bl_default_curve(info, 0, + MIN_LEVEL * FB_BACKLIGHT_MAX / MAX_LEVEL, + FB_BACKLIGHT_MAX); + + bd->props.brightness = bd->props.max_brightness; + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("riva: Backlight initialized (%s)\n", name); + + return; + +error: + return; +} + +static void riva_bl_exit(struct fb_info *info) +{ + struct backlight_device *bd = info->bl_dev; + + backlight_device_unregister(bd); + printk("riva: Backlight unloaded\n"); +} +#else +static inline void riva_bl_init(struct riva_par *par) {} +static inline void riva_bl_exit(struct fb_info *info) {} +#endif /* CONFIG_FB_RIVA_BACKLIGHT */ + +/* ------------------------------------------------------------------------- * + * + * MMIO access macros + * + * ------------------------------------------------------------------------- */ + +static inline void CRTCout(struct riva_par *par, unsigned char index, + unsigned char val) +{ + VGA_WR08(par->riva.PCIO, 0x3d4, index); + VGA_WR08(par->riva.PCIO, 0x3d5, val); +} + +static inline unsigned char CRTCin(struct riva_par *par, + unsigned char index) +{ + VGA_WR08(par->riva.PCIO, 0x3d4, index); + return (VGA_RD08(par->riva.PCIO, 0x3d5)); +} + +static inline void GRAout(struct riva_par *par, unsigned char index, + unsigned char val) +{ + VGA_WR08(par->riva.PVIO, 0x3ce, index); + VGA_WR08(par->riva.PVIO, 0x3cf, val); +} + +static inline unsigned char GRAin(struct riva_par *par, + unsigned char index) +{ + VGA_WR08(par->riva.PVIO, 0x3ce, index); + return (VGA_RD08(par->riva.PVIO, 0x3cf)); +} + +static inline void SEQout(struct riva_par *par, unsigned char index, + unsigned char val) +{ + VGA_WR08(par->riva.PVIO, 0x3c4, index); + VGA_WR08(par->riva.PVIO, 0x3c5, val); +} + +static inline unsigned char SEQin(struct riva_par *par, + unsigned char index) +{ + VGA_WR08(par->riva.PVIO, 0x3c4, index); + return (VGA_RD08(par->riva.PVIO, 0x3c5)); +} + +static inline void ATTRout(struct riva_par *par, unsigned char index, + unsigned char val) +{ + VGA_WR08(par->riva.PCIO, 0x3c0, index); + VGA_WR08(par->riva.PCIO, 0x3c0, val); +} + +static inline unsigned char ATTRin(struct riva_par *par, + unsigned char index) +{ + VGA_WR08(par->riva.PCIO, 0x3c0, index); + return (VGA_RD08(par->riva.PCIO, 0x3c1)); +} + +static inline void MISCout(struct riva_par *par, unsigned char val) +{ + VGA_WR08(par->riva.PVIO, 0x3c2, val); +} + +static inline unsigned char MISCin(struct riva_par *par) +{ + return (VGA_RD08(par->riva.PVIO, 0x3cc)); +} + +static inline void reverse_order(u32 *l) +{ + u8 *a = (u8 *)l; + a[0] = bitrev8(a[0]); + a[1] = bitrev8(a[1]); + a[2] = bitrev8(a[2]); + a[3] = bitrev8(a[3]); +} + +/* ------------------------------------------------------------------------- * + * + * cursor stuff + * + * ------------------------------------------------------------------------- */ + +/** + * rivafb_load_cursor_image - load cursor image to hardware + * @data: address to monochrome bitmap (1 = foreground color, 0 = background) + * @par: pointer to private data + * @w: width of cursor image in pixels + * @h: height of cursor image in scanlines + * @bg: background color (ARGB1555) - alpha bit determines opacity + * @fg: foreground color (ARGB1555) + * + * DESCRIPTiON: + * Loads cursor image based on a monochrome source and mask bitmap. The + * image bits determines the color of the pixel, 0 for background, 1 for + * foreground. Only the affected region (as determined by @w and @h + * parameters) will be updated. + * + * CALLED FROM: + * rivafb_cursor() + */ +static void rivafb_load_cursor_image(struct riva_par *par, u8 *data8, + u16 bg, u16 fg, u32 w, u32 h) +{ + int i, j, k = 0; + u32 b, tmp; + u32 *data = (u32 *)data8; + bg = le16_to_cpu(bg); + fg = le16_to_cpu(fg); + + w = (w + 1) & ~1; + + for (i = 0; i < h; i++) { + b = *data++; + reverse_order(&b); + + for (j = 0; j < w/2; j++) { + tmp = 0; +#if defined (__BIG_ENDIAN) + tmp = (b & (1 << 31)) ? fg << 16 : bg << 16; + b <<= 1; + tmp |= (b & (1 << 31)) ? fg : bg; + b <<= 1; +#else + tmp = (b & 1) ? fg : bg; + b >>= 1; + tmp |= (b & 1) ? fg << 16 : bg << 16; + b >>= 1; +#endif + writel(tmp, &par->riva.CURSOR[k++]); + } + k += (MAX_CURS - w)/2; + } +} + +/* ------------------------------------------------------------------------- * + * + * general utility functions + * + * ------------------------------------------------------------------------- */ + +/** + * riva_wclut - set CLUT entry + * @chip: pointer to RIVA_HW_INST object + * @regnum: register number + * @red: red component + * @green: green component + * @blue: blue component + * + * DESCRIPTION: + * Sets color register @regnum. + * + * CALLED FROM: + * rivafb_setcolreg() + */ +static void riva_wclut(RIVA_HW_INST *chip, + unsigned char regnum, unsigned char red, + unsigned char green, unsigned char blue) +{ + VGA_WR08(chip->PDIO, 0x3c8, regnum); + VGA_WR08(chip->PDIO, 0x3c9, red); + VGA_WR08(chip->PDIO, 0x3c9, green); + VGA_WR08(chip->PDIO, 0x3c9, blue); +} + +/** + * riva_rclut - read fromCLUT register + * @chip: pointer to RIVA_HW_INST object + * @regnum: register number + * @red: red component + * @green: green component + * @blue: blue component + * + * DESCRIPTION: + * Reads red, green, and blue from color register @regnum. + * + * CALLED FROM: + * rivafb_setcolreg() + */ +static void riva_rclut(RIVA_HW_INST *chip, + unsigned char regnum, unsigned char *red, + unsigned char *green, unsigned char *blue) +{ + + VGA_WR08(chip->PDIO, 0x3c7, regnum); + *red = VGA_RD08(chip->PDIO, 0x3c9); + *green = VGA_RD08(chip->PDIO, 0x3c9); + *blue = VGA_RD08(chip->PDIO, 0x3c9); +} + +/** + * riva_save_state - saves current chip state + * @par: pointer to riva_par object containing info for current riva board + * @regs: pointer to riva_regs object + * + * DESCRIPTION: + * Saves current chip state to @regs. + * + * CALLED FROM: + * rivafb_probe() + */ +/* from GGI */ +static void riva_save_state(struct riva_par *par, struct riva_regs *regs) +{ + int i; + + NVTRACE_ENTER(); + par->riva.LockUnlock(&par->riva, 0); + + par->riva.UnloadStateExt(&par->riva, ®s->ext); + + regs->misc_output = MISCin(par); + + for (i = 0; i < NUM_CRT_REGS; i++) + regs->crtc[i] = CRTCin(par, i); + + for (i = 0; i < NUM_ATC_REGS; i++) + regs->attr[i] = ATTRin(par, i); + + for (i = 0; i < NUM_GRC_REGS; i++) + regs->gra[i] = GRAin(par, i); + + for (i = 0; i < NUM_SEQ_REGS; i++) + regs->seq[i] = SEQin(par, i); + NVTRACE_LEAVE(); +} + +/** + * riva_load_state - loads current chip state + * @par: pointer to riva_par object containing info for current riva board + * @regs: pointer to riva_regs object + * + * DESCRIPTION: + * Loads chip state from @regs. + * + * CALLED FROM: + * riva_load_video_mode() + * rivafb_probe() + * rivafb_remove() + */ +/* from GGI */ +static void riva_load_state(struct riva_par *par, struct riva_regs *regs) +{ + RIVA_HW_STATE *state = ®s->ext; + int i; + + NVTRACE_ENTER(); + CRTCout(par, 0x11, 0x00); + + par->riva.LockUnlock(&par->riva, 0); + + par->riva.LoadStateExt(&par->riva, state); + + MISCout(par, regs->misc_output); + + for (i = 0; i < NUM_CRT_REGS; i++) { + switch (i) { + case 0x19: + case 0x20 ... 0x40: + break; + default: + CRTCout(par, i, regs->crtc[i]); + } + } + + for (i = 0; i < NUM_ATC_REGS; i++) + ATTRout(par, i, regs->attr[i]); + + for (i = 0; i < NUM_GRC_REGS; i++) + GRAout(par, i, regs->gra[i]); + + for (i = 0; i < NUM_SEQ_REGS; i++) + SEQout(par, i, regs->seq[i]); + NVTRACE_LEAVE(); +} + +/** + * riva_load_video_mode - calculate timings + * @info: pointer to fb_info object containing info for current riva board + * + * DESCRIPTION: + * Calculate some timings and then send em off to riva_load_state(). + * + * CALLED FROM: + * rivafb_set_par() + */ +static int riva_load_video_mode(struct fb_info *info) +{ + int bpp, width, hDisplaySize, hDisplay, hStart, + hEnd, hTotal, height, vDisplay, vStart, vEnd, vTotal, dotClock; + int hBlankStart, hBlankEnd, vBlankStart, vBlankEnd; + int rc; + struct riva_par *par = info->par; + struct riva_regs newmode; + + NVTRACE_ENTER(); + /* time to calculate */ + rivafb_blank(FB_BLANK_NORMAL, info); + + bpp = info->var.bits_per_pixel; + if (bpp == 16 && info->var.green.length == 5) + bpp = 15; + width = info->var.xres_virtual; + hDisplaySize = info->var.xres; + hDisplay = (hDisplaySize / 8) - 1; + hStart = (hDisplaySize + info->var.right_margin) / 8 - 1; + hEnd = (hDisplaySize + info->var.right_margin + + info->var.hsync_len) / 8 - 1; + hTotal = (hDisplaySize + info->var.right_margin + + info->var.hsync_len + info->var.left_margin) / 8 - 5; + hBlankStart = hDisplay; + hBlankEnd = hTotal + 4; + + height = info->var.yres_virtual; + vDisplay = info->var.yres - 1; + vStart = info->var.yres + info->var.lower_margin - 1; + vEnd = info->var.yres + info->var.lower_margin + + info->var.vsync_len - 1; + vTotal = info->var.yres + info->var.lower_margin + + info->var.vsync_len + info->var.upper_margin + 2; + vBlankStart = vDisplay; + vBlankEnd = vTotal + 1; + dotClock = 1000000000 / info->var.pixclock; + + memcpy(&newmode, ®_template, sizeof(struct riva_regs)); + + if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) + vTotal |= 1; + + if (par->FlatPanel) { + vStart = vTotal - 3; + vEnd = vTotal - 2; + vBlankStart = vStart; + hStart = hTotal - 3; + hEnd = hTotal - 2; + hBlankEnd = hTotal + 4; + } + + newmode.crtc[0x0] = Set8Bits (hTotal); + newmode.crtc[0x1] = Set8Bits (hDisplay); + newmode.crtc[0x2] = Set8Bits (hBlankStart); + newmode.crtc[0x3] = SetBitField (hBlankEnd, 4: 0, 4:0) | SetBit (7); + newmode.crtc[0x4] = Set8Bits (hStart); + newmode.crtc[0x5] = SetBitField (hBlankEnd, 5: 5, 7:7) + | SetBitField (hEnd, 4: 0, 4:0); + newmode.crtc[0x6] = SetBitField (vTotal, 7: 0, 7:0); + newmode.crtc[0x7] = SetBitField (vTotal, 8: 8, 0:0) + | SetBitField (vDisplay, 8: 8, 1:1) + | SetBitField (vStart, 8: 8, 2:2) + | SetBitField (vBlankStart, 8: 8, 3:3) + | SetBit (4) + | SetBitField (vTotal, 9: 9, 5:5) + | SetBitField (vDisplay, 9: 9, 6:6) + | SetBitField (vStart, 9: 9, 7:7); + newmode.crtc[0x9] = SetBitField (vBlankStart, 9: 9, 5:5) + | SetBit (6); + newmode.crtc[0x10] = Set8Bits (vStart); + newmode.crtc[0x11] = SetBitField (vEnd, 3: 0, 3:0) + | SetBit (5); + newmode.crtc[0x12] = Set8Bits (vDisplay); + newmode.crtc[0x13] = (width / 8) * ((bpp + 1) / 8); + newmode.crtc[0x15] = Set8Bits (vBlankStart); + newmode.crtc[0x16] = Set8Bits (vBlankEnd); + + newmode.ext.screen = SetBitField(hBlankEnd,6:6,4:4) + | SetBitField(vBlankStart,10:10,3:3) + | SetBitField(vStart,10:10,2:2) + | SetBitField(vDisplay,10:10,1:1) + | SetBitField(vTotal,10:10,0:0); + newmode.ext.horiz = SetBitField(hTotal,8:8,0:0) + | SetBitField(hDisplay,8:8,1:1) + | SetBitField(hBlankStart,8:8,2:2) + | SetBitField(hStart,8:8,3:3); + newmode.ext.extra = SetBitField(vTotal,11:11,0:0) + | SetBitField(vDisplay,11:11,2:2) + | SetBitField(vStart,11:11,4:4) + | SetBitField(vBlankStart,11:11,6:6); + + if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + int tmp = (hTotal >> 1) & ~1; + newmode.ext.interlace = Set8Bits(tmp); + newmode.ext.horiz |= SetBitField(tmp, 8:8,4:4); + } else + newmode.ext.interlace = 0xff; /* interlace off */ + + if (par->riva.Architecture >= NV_ARCH_10) + par->riva.CURSOR = (U032 __iomem *)(info->screen_base + par->riva.CursorStart); + + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) + newmode.misc_output &= ~0x40; + else + newmode.misc_output |= 0x40; + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) + newmode.misc_output &= ~0x80; + else + newmode.misc_output |= 0x80; + + rc = CalcStateExt(&par->riva, &newmode.ext, bpp, width, + hDisplaySize, height, dotClock); + if (rc) + goto out; + + newmode.ext.scale = NV_RD32(par->riva.PRAMDAC, 0x00000848) & + 0xfff000ff; + if (par->FlatPanel == 1) { + newmode.ext.pixel |= (1 << 7); + newmode.ext.scale |= (1 << 8); + } + if (par->SecondCRTC) { + newmode.ext.head = NV_RD32(par->riva.PCRTC0, 0x00000860) & + ~0x00001000; + newmode.ext.head2 = NV_RD32(par->riva.PCRTC0, 0x00002860) | + 0x00001000; + newmode.ext.crtcOwner = 3; + newmode.ext.pllsel |= 0x20000800; + newmode.ext.vpll2 = newmode.ext.vpll; + } else if (par->riva.twoHeads) { + newmode.ext.head = NV_RD32(par->riva.PCRTC0, 0x00000860) | + 0x00001000; + newmode.ext.head2 = NV_RD32(par->riva.PCRTC0, 0x00002860) & + ~0x00001000; + newmode.ext.crtcOwner = 0; + newmode.ext.vpll2 = NV_RD32(par->riva.PRAMDAC0, 0x00000520); + } + if (par->FlatPanel == 1) { + newmode.ext.pixel |= (1 << 7); + newmode.ext.scale |= (1 << 8); + } + newmode.ext.cursorConfig = 0x02000100; + par->current_state = newmode; + riva_load_state(par, &par->current_state); + par->riva.LockUnlock(&par->riva, 0); /* important for HW cursor */ + +out: + rivafb_blank(FB_BLANK_UNBLANK, info); + NVTRACE_LEAVE(); + + return rc; +} + +static void riva_update_var(struct fb_var_screeninfo *var, + const struct fb_videomode *modedb) +{ + NVTRACE_ENTER(); + var->xres = var->xres_virtual = modedb->xres; + var->yres = modedb->yres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + var->xoffset = var->yoffset = 0; + var->pixclock = modedb->pixclock; + var->left_margin = modedb->left_margin; + var->right_margin = modedb->right_margin; + var->upper_margin = modedb->upper_margin; + var->lower_margin = modedb->lower_margin; + var->hsync_len = modedb->hsync_len; + var->vsync_len = modedb->vsync_len; + var->sync = modedb->sync; + var->vmode = modedb->vmode; + NVTRACE_LEAVE(); +} + +/** + * rivafb_do_maximize - + * @info: pointer to fb_info object containing info for current riva board + * @var: + * @nom: + * @den: + * + * DESCRIPTION: + * . + * + * RETURNS: + * -EINVAL on failure, 0 on success + * + * + * CALLED FROM: + * rivafb_check_var() + */ +static int rivafb_do_maximize(struct fb_info *info, + struct fb_var_screeninfo *var, + int nom, int den) +{ + static struct { + int xres, yres; + } modes[] = { + {1600, 1280}, + {1280, 1024}, + {1024, 768}, + {800, 600}, + {640, 480}, + {-1, -1} + }; + int i; + + NVTRACE_ENTER(); + /* use highest possible virtual resolution */ + if (var->xres_virtual == -1 && var->yres_virtual == -1) { + printk(KERN_WARNING PFX + "using maximum available virtual resolution\n"); + for (i = 0; modes[i].xres != -1; i++) { + if (modes[i].xres * nom / den * modes[i].yres < + info->fix.smem_len) + break; + } + if (modes[i].xres == -1) { + printk(KERN_ERR PFX + "could not find a virtual resolution that fits into video memory!!\n"); + NVTRACE("EXIT - EINVAL error\n"); + return -EINVAL; + } + var->xres_virtual = modes[i].xres; + var->yres_virtual = modes[i].yres; + + printk(KERN_INFO PFX + "virtual resolution set to maximum of %dx%d\n", + var->xres_virtual, var->yres_virtual); + } else if (var->xres_virtual == -1) { + var->xres_virtual = (info->fix.smem_len * den / + (nom * var->yres_virtual)) & ~15; + printk(KERN_WARNING PFX + "setting virtual X resolution to %d\n", var->xres_virtual); + } else if (var->yres_virtual == -1) { + var->xres_virtual = (var->xres_virtual + 15) & ~15; + var->yres_virtual = info->fix.smem_len * den / + (nom * var->xres_virtual); + printk(KERN_WARNING PFX + "setting virtual Y resolution to %d\n", var->yres_virtual); + } else { + var->xres_virtual = (var->xres_virtual + 15) & ~15; + if (var->xres_virtual * nom / den * var->yres_virtual > info->fix.smem_len) { + printk(KERN_ERR PFX + "mode %dx%dx%d rejected...resolution too high to fit into video memory!\n", + var->xres, var->yres, var->bits_per_pixel); + NVTRACE("EXIT - EINVAL error\n"); + return -EINVAL; + } + } + + if (var->xres_virtual * nom / den >= 8192) { + printk(KERN_WARNING PFX + "virtual X resolution (%d) is too high, lowering to %d\n", + var->xres_virtual, 8192 * den / nom - 16); + var->xres_virtual = 8192 * den / nom - 16; + } + + if (var->xres_virtual < var->xres) { + printk(KERN_ERR PFX + "virtual X resolution (%d) is smaller than real\n", var->xres_virtual); + return -EINVAL; + } + + if (var->yres_virtual < var->yres) { + printk(KERN_ERR PFX + "virtual Y resolution (%d) is smaller than real\n", var->yres_virtual); + return -EINVAL; + } + if (var->yres_virtual > 0x7fff/nom) + var->yres_virtual = 0x7fff/nom; + if (var->xres_virtual > 0x7fff/nom) + var->xres_virtual = 0x7fff/nom; + NVTRACE_LEAVE(); + return 0; +} + +static void +riva_set_pattern(struct riva_par *par, int clr0, int clr1, int pat0, int pat1) +{ + RIVA_FIFO_FREE(par->riva, Patt, 4); + NV_WR32(&par->riva.Patt->Color0, 0, clr0); + NV_WR32(&par->riva.Patt->Color1, 0, clr1); + NV_WR32(par->riva.Patt->Monochrome, 0, pat0); + NV_WR32(par->riva.Patt->Monochrome, 4, pat1); +} + +/* acceleration routines */ +static inline void wait_for_idle(struct riva_par *par) +{ + while (par->riva.Busy(&par->riva)); +} + +/* + * Set ROP. Translate X rop into ROP3. Internal routine. + */ +static void +riva_set_rop_solid(struct riva_par *par, int rop) +{ + riva_set_pattern(par, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + RIVA_FIFO_FREE(par->riva, Rop, 1); + NV_WR32(&par->riva.Rop->Rop3, 0, rop); + +} + +static void riva_setup_accel(struct fb_info *info) +{ + struct riva_par *par = info->par; + + RIVA_FIFO_FREE(par->riva, Clip, 2); + NV_WR32(&par->riva.Clip->TopLeft, 0, 0x0); + NV_WR32(&par->riva.Clip->WidthHeight, 0, + (info->var.xres_virtual & 0xffff) | + (info->var.yres_virtual << 16)); + riva_set_rop_solid(par, 0xcc); + wait_for_idle(par); +} + +/** + * riva_get_cmap_len - query current color map length + * @var: standard kernel fb changeable data + * + * DESCRIPTION: + * Get current color map length. + * + * RETURNS: + * Length of color map + * + * CALLED FROM: + * rivafb_setcolreg() + */ +static int riva_get_cmap_len(const struct fb_var_screeninfo *var) +{ + int rc = 256; /* reasonable default */ + + switch (var->green.length) { + case 8: + rc = 256; /* 256 entries (2^8), 8 bpp and RGB8888 */ + break; + case 5: + rc = 32; /* 32 entries (2^5), 16 bpp, RGB555 */ + break; + case 6: + rc = 64; /* 64 entries (2^6), 16 bpp, RGB565 */ + break; + default: + /* should not occur */ + break; + } + return rc; +} + +/* ------------------------------------------------------------------------- * + * + * framebuffer operations + * + * ------------------------------------------------------------------------- */ + +static int rivafb_open(struct fb_info *info, int user) +{ + struct riva_par *par = info->par; + + NVTRACE_ENTER(); + mutex_lock(&par->open_lock); + if (!par->ref_count) { +#ifdef CONFIG_X86 + memset(&par->state, 0, sizeof(struct vgastate)); + par->state.flags = VGA_SAVE_MODE | VGA_SAVE_FONTS; + /* save the DAC for Riva128 */ + if (par->riva.Architecture == NV_ARCH_03) + par->state.flags |= VGA_SAVE_CMAP; + save_vga(&par->state); +#endif + /* vgaHWunlock() + riva unlock (0x7F) */ + CRTCout(par, 0x11, 0xFF); + par->riva.LockUnlock(&par->riva, 0); + + riva_save_state(par, &par->initial_state); + } + par->ref_count++; + mutex_unlock(&par->open_lock); + NVTRACE_LEAVE(); + return 0; +} + +static int rivafb_release(struct fb_info *info, int user) +{ + struct riva_par *par = info->par; + + NVTRACE_ENTER(); + mutex_lock(&par->open_lock); + if (!par->ref_count) { + mutex_unlock(&par->open_lock); + return -EINVAL; + } + if (par->ref_count == 1) { + par->riva.LockUnlock(&par->riva, 0); + par->riva.LoadStateExt(&par->riva, &par->initial_state.ext); + riva_load_state(par, &par->initial_state); +#ifdef CONFIG_X86 + restore_vga(&par->state); +#endif + par->riva.LockUnlock(&par->riva, 1); + } + par->ref_count--; + mutex_unlock(&par->open_lock); + NVTRACE_LEAVE(); + return 0; +} + +static int rivafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + const struct fb_videomode *mode; + struct riva_par *par = info->par; + int nom, den; /* translating from pixels->bytes */ + int mode_valid = 0; + + NVTRACE_ENTER(); + switch (var->bits_per_pixel) { + case 1 ... 8: + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + var->bits_per_pixel = 8; + nom = den = 1; + break; + case 9 ... 15: + var->green.length = 5; + /* fall through */ + case 16: + var->bits_per_pixel = 16; + /* The Riva128 supports RGB555 only */ + if (par->riva.Architecture == NV_ARCH_03) + var->green.length = 5; + if (var->green.length == 5) { + /* 0rrrrrgg gggbbbbb */ + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + } else { + /* rrrrrggg gggbbbbb */ + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + } + nom = 2; + den = 1; + break; + case 17 ... 32: + var->red.length = var->green.length = var->blue.length = 8; + var->bits_per_pixel = 32; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + nom = 4; + den = 1; + break; + default: + printk(KERN_ERR PFX + "mode %dx%dx%d rejected...color depth not supported.\n", + var->xres, var->yres, var->bits_per_pixel); + NVTRACE("EXIT, returning -EINVAL\n"); + return -EINVAL; + } + + if (!strictmode) { + if (!info->monspecs.vfmax || !info->monspecs.hfmax || + !info->monspecs.dclkmax || !fb_validate_mode(var, info)) + mode_valid = 1; + } + + /* calculate modeline if supported by monitor */ + if (!mode_valid && info->monspecs.gtf) { + if (!fb_get_mode(FB_MAXTIMINGS, 0, var, info)) + mode_valid = 1; + } + + if (!mode_valid) { + mode = fb_find_best_mode(var, &info->modelist); + if (mode) { + riva_update_var(var, mode); + mode_valid = 1; + } + } + + if (!mode_valid && info->monspecs.modedb_len) + return -EINVAL; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual <= var->yres) + var->yres_virtual = -1; + if (rivafb_do_maximize(info, var, nom, den) < 0) + return -EINVAL; + + /* truncate xoffset and yoffset to maximum if too high */ + if (var->xoffset > var->xres_virtual - var->xres) + var->xoffset = var->xres_virtual - var->xres - 1; + + if (var->yoffset > var->yres_virtual - var->yres) + var->yoffset = var->yres_virtual - var->yres - 1; + + var->red.msb_right = + var->green.msb_right = + var->blue.msb_right = + var->transp.offset = var->transp.length = var->transp.msb_right = 0; + NVTRACE_LEAVE(); + return 0; +} + +static int rivafb_set_par(struct fb_info *info) +{ + struct riva_par *par = info->par; + int rc = 0; + + NVTRACE_ENTER(); + /* vgaHWunlock() + riva unlock (0x7F) */ + CRTCout(par, 0x11, 0xFF); + par->riva.LockUnlock(&par->riva, 0); + rc = riva_load_video_mode(info); + if (rc) + goto out; + if(!(info->flags & FBINFO_HWACCEL_DISABLED)) + riva_setup_accel(info); + + par->cursor_reset = 1; + info->fix.line_length = (info->var.xres_virtual * (info->var.bits_per_pixel >> 3)); + info->fix.visual = (info->var.bits_per_pixel == 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + + if (info->flags & FBINFO_HWACCEL_DISABLED) + info->pixmap.scan_align = 1; + else + info->pixmap.scan_align = 4; + +out: + NVTRACE_LEAVE(); + return rc; +} + +/** + * rivafb_pan_display + * @var: standard kernel fb changeable data + * @con: TODO + * @info: pointer to fb_info object containing info for current riva board + * + * DESCRIPTION: + * Pan (or wrap, depending on the `vmode' field) the display using the + * `xoffset' and `yoffset' fields of the `var' structure. + * If the values don't fit, return -EINVAL. + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ +static int rivafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct riva_par *par = info->par; + unsigned int base; + + NVTRACE_ENTER(); + base = var->yoffset * info->fix.line_length + var->xoffset; + par->riva.SetStartAddress(&par->riva, base); + NVTRACE_LEAVE(); + return 0; +} + +static int rivafb_blank(int blank, struct fb_info *info) +{ + struct riva_par *par= info->par; + unsigned char tmp, vesa; + + tmp = SEQin(par, 0x01) & ~0x20; /* screen on/off */ + vesa = CRTCin(par, 0x1a) & ~0xc0; /* sync on/off */ + + NVTRACE_ENTER(); + + if (blank) + tmp |= 0x20; + + switch (blank) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + break; + case FB_BLANK_VSYNC_SUSPEND: + vesa |= 0x80; + break; + case FB_BLANK_HSYNC_SUSPEND: + vesa |= 0x40; + break; + case FB_BLANK_POWERDOWN: + vesa |= 0xc0; + break; + } + + SEQout(par, 0x01, tmp); + CRTCout(par, 0x1a, vesa); + + NVTRACE_LEAVE(); + + return 0; +} + +/** + * rivafb_setcolreg + * @regno: register index + * @red: red component + * @green: green component + * @blue: blue component + * @transp: transparency + * @info: pointer to fb_info object containing info for current riva board + * + * DESCRIPTION: + * Set a single color register. The values supplied have a 16 bit + * magnitude. + * + * RETURNS: + * Return != 0 for invalid regno. + * + * CALLED FROM: + * fbcmap.c:fb_set_cmap() + */ +static int rivafb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct riva_par *par = info->par; + RIVA_HW_INST *chip = &par->riva; + int i; + + if (regno >= riva_get_cmap_len(&info->var)) + return -EINVAL; + + if (info->var.grayscale) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = + (red * 77 + green * 151 + blue * 28) >> 8; + } + + if (regno < 16 && info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + ((u32 *) info->pseudo_palette)[regno] = + (regno << info->var.red.offset) | + (regno << info->var.green.offset) | + (regno << info->var.blue.offset); + /* + * The Riva128 2D engine requires color information in + * TrueColor format even if framebuffer is in DirectColor + */ + if (par->riva.Architecture == NV_ARCH_03) { + switch (info->var.bits_per_pixel) { + case 16: + par->palette[regno] = ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + break; + case 32: + par->palette[regno] = ((red & 0xff00) << 8) | + ((green & 0xff00)) | + ((blue & 0xff00) >> 8); + break; + } + } + } + + switch (info->var.bits_per_pixel) { + case 8: + /* "transparent" stuff is completely ignored. */ + riva_wclut(chip, regno, red >> 8, green >> 8, blue >> 8); + break; + case 16: + if (info->var.green.length == 5) { + for (i = 0; i < 8; i++) { + riva_wclut(chip, regno*8+i, red >> 8, + green >> 8, blue >> 8); + } + } else { + u8 r, g, b; + + if (regno < 32) { + for (i = 0; i < 8; i++) { + riva_wclut(chip, regno*8+i, + red >> 8, green >> 8, + blue >> 8); + } + } + riva_rclut(chip, regno*4, &r, &g, &b); + for (i = 0; i < 4; i++) + riva_wclut(chip, regno*4+i, r, + green >> 8, b); + } + break; + case 32: + riva_wclut(chip, regno, red >> 8, green >> 8, blue >> 8); + break; + default: + /* do nothing */ + break; + } + return 0; +} + +/** + * rivafb_fillrect - hardware accelerated color fill function + * @info: pointer to fb_info structure + * @rect: pointer to fb_fillrect structure + * + * DESCRIPTION: + * This function fills up a region of framebuffer memory with a solid + * color with a choice of two different ROP's, copy or invert. + * + * CALLED FROM: + * framebuffer hook + */ +static void rivafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct riva_par *par = info->par; + u_int color, rop = 0; + + if ((info->flags & FBINFO_HWACCEL_DISABLED)) { + cfb_fillrect(info, rect); + return; + } + + if (info->var.bits_per_pixel == 8) + color = rect->color; + else { + if (par->riva.Architecture != NV_ARCH_03) + color = ((u32 *)info->pseudo_palette)[rect->color]; + else + color = par->palette[rect->color]; + } + + switch (rect->rop) { + case ROP_XOR: + rop = 0x66; + break; + case ROP_COPY: + default: + rop = 0xCC; + break; + } + + riva_set_rop_solid(par, rop); + + RIVA_FIFO_FREE(par->riva, Bitmap, 1); + NV_WR32(&par->riva.Bitmap->Color1A, 0, color); + + RIVA_FIFO_FREE(par->riva, Bitmap, 2); + NV_WR32(&par->riva.Bitmap->UnclippedRectangle[0].TopLeft, 0, + (rect->dx << 16) | rect->dy); + mb(); + NV_WR32(&par->riva.Bitmap->UnclippedRectangle[0].WidthHeight, 0, + (rect->width << 16) | rect->height); + mb(); + riva_set_rop_solid(par, 0xcc); + +} + +/** + * rivafb_copyarea - hardware accelerated blit function + * @info: pointer to fb_info structure + * @region: pointer to fb_copyarea structure + * + * DESCRIPTION: + * This copies an area of pixels from one location to another + * + * CALLED FROM: + * framebuffer hook + */ +static void rivafb_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct riva_par *par = info->par; + + if ((info->flags & FBINFO_HWACCEL_DISABLED)) { + cfb_copyarea(info, region); + return; + } + + RIVA_FIFO_FREE(par->riva, Blt, 3); + NV_WR32(&par->riva.Blt->TopLeftSrc, 0, + (region->sy << 16) | region->sx); + NV_WR32(&par->riva.Blt->TopLeftDst, 0, + (region->dy << 16) | region->dx); + mb(); + NV_WR32(&par->riva.Blt->WidthHeight, 0, + (region->height << 16) | region->width); + mb(); +} + +static inline void convert_bgcolor_16(u32 *col) +{ + *col = ((*col & 0x0000F800) << 8) + | ((*col & 0x00007E0) << 5) + | ((*col & 0x0000001F) << 3) + | 0xFF000000; + mb(); +} + +/** + * rivafb_imageblit: hardware accelerated color expand function + * @info: pointer to fb_info structure + * @image: pointer to fb_image structure + * + * DESCRIPTION: + * If the source is a monochrome bitmap, the function fills up a a region + * of framebuffer memory with pixels whose color is determined by the bit + * setting of the bitmap, 1 - foreground, 0 - background. + * + * If the source is not a monochrome bitmap, color expansion is not done. + * In this case, it is channeled to a software function. + * + * CALLED FROM: + * framebuffer hook + */ +static void rivafb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct riva_par *par = info->par; + u32 fgx = 0, bgx = 0, width, tmp; + u8 *cdat = (u8 *) image->data; + volatile u32 __iomem *d; + int i, size; + + if ((info->flags & FBINFO_HWACCEL_DISABLED) || image->depth != 1) { + cfb_imageblit(info, image); + return; + } + + switch (info->var.bits_per_pixel) { + case 8: + fgx = image->fg_color; + bgx = image->bg_color; + break; + case 16: + case 32: + if (par->riva.Architecture != NV_ARCH_03) { + fgx = ((u32 *)info->pseudo_palette)[image->fg_color]; + bgx = ((u32 *)info->pseudo_palette)[image->bg_color]; + } else { + fgx = par->palette[image->fg_color]; + bgx = par->palette[image->bg_color]; + } + if (info->var.green.length == 6) + convert_bgcolor_16(&bgx); + break; + } + + RIVA_FIFO_FREE(par->riva, Bitmap, 7); + NV_WR32(&par->riva.Bitmap->ClipE.TopLeft, 0, + (image->dy << 16) | (image->dx & 0xFFFF)); + NV_WR32(&par->riva.Bitmap->ClipE.BottomRight, 0, + (((image->dy + image->height) << 16) | + ((image->dx + image->width) & 0xffff))); + NV_WR32(&par->riva.Bitmap->Color0E, 0, bgx); + NV_WR32(&par->riva.Bitmap->Color1E, 0, fgx); + NV_WR32(&par->riva.Bitmap->WidthHeightInE, 0, + (image->height << 16) | ((image->width + 31) & ~31)); + NV_WR32(&par->riva.Bitmap->WidthHeightOutE, 0, + (image->height << 16) | ((image->width + 31) & ~31)); + NV_WR32(&par->riva.Bitmap->PointE, 0, + (image->dy << 16) | (image->dx & 0xFFFF)); + + d = &par->riva.Bitmap->MonochromeData01E; + + width = (image->width + 31)/32; + size = width * image->height; + while (size >= 16) { + RIVA_FIFO_FREE(par->riva, Bitmap, 16); + for (i = 0; i < 16; i++) { + tmp = *((u32 *)cdat); + cdat = (u8 *)((u32 *)cdat + 1); + reverse_order(&tmp); + NV_WR32(d, i*4, tmp); + } + size -= 16; + } + if (size) { + RIVA_FIFO_FREE(par->riva, Bitmap, size); + for (i = 0; i < size; i++) { + tmp = *((u32 *) cdat); + cdat = (u8 *)((u32 *)cdat + 1); + reverse_order(&tmp); + NV_WR32(d, i*4, tmp); + } + } +} + +/** + * rivafb_cursor - hardware cursor function + * @info: pointer to info structure + * @cursor: pointer to fbcursor structure + * + * DESCRIPTION: + * A cursor function that supports displaying a cursor image via hardware. + * Within the kernel, copy and invert rops are supported. If exported + * to user space, only the copy rop will be supported. + * + * CALLED FROM + * framebuffer hook + */ +static int rivafb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct riva_par *par = info->par; + u8 data[MAX_CURS * MAX_CURS/8]; + int i, set = cursor->set; + u16 fg, bg; + + if (cursor->image.width > MAX_CURS || cursor->image.height > MAX_CURS) + return -ENXIO; + + par->riva.ShowHideCursor(&par->riva, 0); + + if (par->cursor_reset) { + set = FB_CUR_SETALL; + par->cursor_reset = 0; + } + + if (set & FB_CUR_SETSIZE) + memset_io(par->riva.CURSOR, 0, MAX_CURS * MAX_CURS * 2); + + if (set & FB_CUR_SETPOS) { + u32 xx, yy, temp; + + yy = cursor->image.dy - info->var.yoffset; + xx = cursor->image.dx - info->var.xoffset; + temp = xx & 0xFFFF; + temp |= yy << 16; + + NV_WR32(par->riva.PRAMDAC, 0x0000300, temp); + } + + + if (set & (FB_CUR_SETSHAPE | FB_CUR_SETCMAP | FB_CUR_SETIMAGE)) { + u32 bg_idx = cursor->image.bg_color; + u32 fg_idx = cursor->image.fg_color; + u32 s_pitch = (cursor->image.width+7) >> 3; + u32 d_pitch = MAX_CURS/8; + u8 *dat = (u8 *) cursor->image.data; + u8 *msk = (u8 *) cursor->mask; + u8 *src; + + src = kmalloc(s_pitch * cursor->image.height, GFP_ATOMIC); + + if (src) { + switch (cursor->rop) { + case ROP_XOR: + for (i = 0; i < s_pitch * cursor->image.height; i++) + src[i] = dat[i] ^ msk[i]; + break; + case ROP_COPY: + default: + for (i = 0; i < s_pitch * cursor->image.height; i++) + src[i] = dat[i] & msk[i]; + break; + } + + fb_pad_aligned_buffer(data, d_pitch, src, s_pitch, + cursor->image.height); + + bg = ((info->cmap.red[bg_idx] & 0xf8) << 7) | + ((info->cmap.green[bg_idx] & 0xf8) << 2) | + ((info->cmap.blue[bg_idx] & 0xf8) >> 3) | + 1 << 15; + + fg = ((info->cmap.red[fg_idx] & 0xf8) << 7) | + ((info->cmap.green[fg_idx] & 0xf8) << 2) | + ((info->cmap.blue[fg_idx] & 0xf8) >> 3) | + 1 << 15; + + par->riva.LockUnlock(&par->riva, 0); + + rivafb_load_cursor_image(par, data, bg, fg, + cursor->image.width, + cursor->image.height); + kfree(src); + } + } + + if (cursor->enable) + par->riva.ShowHideCursor(&par->riva, 1); + + return 0; +} + +static int rivafb_sync(struct fb_info *info) +{ + struct riva_par *par = info->par; + + wait_for_idle(par); + return 0; +} + +/* ------------------------------------------------------------------------- * + * + * initialization helper functions + * + * ------------------------------------------------------------------------- */ + +/* kernel interface */ +static struct fb_ops riva_fb_ops = { + .owner = THIS_MODULE, + .fb_open = rivafb_open, + .fb_release = rivafb_release, + .fb_check_var = rivafb_check_var, + .fb_set_par = rivafb_set_par, + .fb_setcolreg = rivafb_setcolreg, + .fb_pan_display = rivafb_pan_display, + .fb_blank = rivafb_blank, + .fb_fillrect = rivafb_fillrect, + .fb_copyarea = rivafb_copyarea, + .fb_imageblit = rivafb_imageblit, + .fb_cursor = rivafb_cursor, + .fb_sync = rivafb_sync, +}; + +static int riva_set_fbinfo(struct fb_info *info) +{ + unsigned int cmap_len; + struct riva_par *par = info->par; + + NVTRACE_ENTER(); + info->flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_XPAN + | FBINFO_HWACCEL_YPAN + | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_IMAGEBLIT; + + /* Accel seems to not work properly on NV30 yet...*/ + if ((par->riva.Architecture == NV_ARCH_30) || noaccel) { + printk(KERN_DEBUG PFX "disabling acceleration\n"); + info->flags |= FBINFO_HWACCEL_DISABLED; + } + + info->var = rivafb_default_var; + info->fix.visual = (info->var.bits_per_pixel == 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + + info->pseudo_palette = par->pseudo_palette; + + cmap_len = riva_get_cmap_len(&info->var); + fb_alloc_cmap(&info->cmap, cmap_len, 0); + + info->pixmap.size = 8 * 1024; + info->pixmap.buf_align = 4; + info->pixmap.access_align = 32; + info->pixmap.flags = FB_PIXMAP_SYSTEM; + info->var.yres_virtual = -1; + NVTRACE_LEAVE(); + return (rivafb_check_var(&info->var, info)); +} + +#ifdef CONFIG_PPC_OF +static int riva_get_EDID_OF(struct fb_info *info, struct pci_dev *pd) +{ + struct riva_par *par = info->par; + struct device_node *dp; + const unsigned char *pedid = NULL; + const unsigned char *disptype = NULL; + static char *propnames[] = { + "DFP,EDID", "LCD,EDID", "EDID", "EDID1", "EDID,B", "EDID,A", NULL }; + int i; + + NVTRACE_ENTER(); + dp = pci_device_to_OF_node(pd); + for (; dp != NULL; dp = dp->child) { + disptype = of_get_property(dp, "display-type", NULL); + if (disptype == NULL) + continue; + if (strncmp(disptype, "LCD", 3) != 0) + continue; + for (i = 0; propnames[i] != NULL; ++i) { + pedid = of_get_property(dp, propnames[i], NULL); + if (pedid != NULL) { + par->EDID = (unsigned char *)pedid; + NVTRACE("LCD found.\n"); + return 1; + } + } + } + NVTRACE_LEAVE(); + return 0; +} +#endif /* CONFIG_PPC_OF */ + +#if defined(CONFIG_FB_RIVA_I2C) && !defined(CONFIG_PPC_OF) +static int riva_get_EDID_i2c(struct fb_info *info) +{ + struct riva_par *par = info->par; + struct fb_var_screeninfo var; + int i; + + NVTRACE_ENTER(); + riva_create_i2c_busses(par); + for (i = 0; i < 3; i++) { + if (!par->chan[i].par) + continue; + riva_probe_i2c_connector(par, i, &par->EDID); + if (par->EDID && !fb_parse_edid(par->EDID, &var)) { + printk(PFX "Found EDID Block from BUS %i\n", i); + break; + } + } + + NVTRACE_LEAVE(); + return (par->EDID) ? 1 : 0; +} +#endif /* CONFIG_FB_RIVA_I2C */ + +static void riva_update_default_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct fb_monspecs *specs = &info->monspecs; + struct fb_videomode modedb; + + NVTRACE_ENTER(); + /* respect mode options */ + if (mode_option) { + fb_find_mode(var, info, mode_option, + specs->modedb, specs->modedb_len, + NULL, 8); + } else if (specs->modedb != NULL) { + /* get first mode in database as fallback */ + modedb = specs->modedb[0]; + /* get preferred timing */ + if (info->monspecs.misc & FB_MISC_1ST_DETAIL) { + int i; + + for (i = 0; i < specs->modedb_len; i++) { + if (specs->modedb[i].flag & FB_MODE_IS_FIRST) { + modedb = specs->modedb[i]; + break; + } + } + } + var->bits_per_pixel = 8; + riva_update_var(var, &modedb); + } + NVTRACE_LEAVE(); +} + + +static void riva_get_EDID(struct fb_info *info, struct pci_dev *pdev) +{ + NVTRACE_ENTER(); +#ifdef CONFIG_PPC_OF + if (!riva_get_EDID_OF(info, pdev)) + printk(PFX "could not retrieve EDID from OF\n"); +#elif defined(CONFIG_FB_RIVA_I2C) + if (!riva_get_EDID_i2c(info)) + printk(PFX "could not retrieve EDID from DDC/I2C\n"); +#endif + NVTRACE_LEAVE(); +} + + +static void riva_get_edidinfo(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &rivafb_default_var; + struct riva_par *par = info->par; + + fb_edid_to_monspecs(par->EDID, &info->monspecs); + fb_videomode_to_modelist(info->monspecs.modedb, info->monspecs.modedb_len, + &info->modelist); + riva_update_default_var(var, info); + + /* if user specified flatpanel, we respect that */ + if (info->monspecs.input & FB_DISP_DDI) + par->FlatPanel = 1; +} + +/* ------------------------------------------------------------------------- * + * + * PCI bus + * + * ------------------------------------------------------------------------- */ + +static u32 riva_get_arch(struct pci_dev *pd) +{ + u32 arch = 0; + + switch (pd->device & 0x0ff0) { + case 0x0100: /* GeForce 256 */ + case 0x0110: /* GeForce2 MX */ + case 0x0150: /* GeForce2 */ + case 0x0170: /* GeForce4 MX */ + case 0x0180: /* GeForce4 MX (8x AGP) */ + case 0x01A0: /* nForce */ + case 0x01F0: /* nForce2 */ + arch = NV_ARCH_10; + break; + case 0x0200: /* GeForce3 */ + case 0x0250: /* GeForce4 Ti */ + case 0x0280: /* GeForce4 Ti (8x AGP) */ + arch = NV_ARCH_20; + break; + case 0x0300: /* GeForceFX 5800 */ + case 0x0310: /* GeForceFX 5600 */ + case 0x0320: /* GeForceFX 5200 */ + case 0x0330: /* GeForceFX 5900 */ + case 0x0340: /* GeForceFX 5700 */ + arch = NV_ARCH_30; + break; + case 0x0020: /* TNT, TNT2 */ + arch = NV_ARCH_04; + break; + case 0x0010: /* Riva128 */ + arch = NV_ARCH_03; + break; + default: /* unknown architecture */ + break; + } + return arch; +} + +static int rivafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) +{ + struct riva_par *default_par; + struct fb_info *info; + int ret; + + NVTRACE_ENTER(); + assert(pd != NULL); + + info = framebuffer_alloc(sizeof(struct riva_par), &pd->dev); + if (!info) { + printk (KERN_ERR PFX "could not allocate memory\n"); + ret = -ENOMEM; + goto err_ret; + } + default_par = info->par; + default_par->pdev = pd; + + info->pixmap.addr = kzalloc(8 * 1024, GFP_KERNEL); + if (info->pixmap.addr == NULL) { + ret = -ENOMEM; + goto err_framebuffer_release; + } + + ret = pci_enable_device(pd); + if (ret < 0) { + printk(KERN_ERR PFX "cannot enable PCI device\n"); + goto err_free_pixmap; + } + + ret = pci_request_regions(pd, "rivafb"); + if (ret < 0) { + printk(KERN_ERR PFX "cannot request PCI regions\n"); + goto err_disable_device; + } + + mutex_init(&default_par->open_lock); + default_par->riva.Architecture = riva_get_arch(pd); + + default_par->Chipset = (pd->vendor << 16) | pd->device; + printk(KERN_INFO PFX "nVidia device/chipset %X\n",default_par->Chipset); + + if(default_par->riva.Architecture == 0) { + printk(KERN_ERR PFX "unknown NV_ARCH\n"); + ret=-ENODEV; + goto err_release_region; + } + if(default_par->riva.Architecture == NV_ARCH_10 || + default_par->riva.Architecture == NV_ARCH_20 || + default_par->riva.Architecture == NV_ARCH_30) { + sprintf(rivafb_fix.id, "NV%x", (pd->device & 0x0ff0) >> 4); + } else { + sprintf(rivafb_fix.id, "NV%x", default_par->riva.Architecture); + } + + default_par->FlatPanel = flatpanel; + if (flatpanel == 1) + printk(KERN_INFO PFX "flatpanel support enabled\n"); + default_par->forceCRTC = forceCRTC; + + rivafb_fix.mmio_len = pci_resource_len(pd, 0); + rivafb_fix.smem_len = pci_resource_len(pd, 1); + + { + /* enable IO and mem if not already done */ + unsigned short cmd; + + pci_read_config_word(pd, PCI_COMMAND, &cmd); + cmd |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY); + pci_write_config_word(pd, PCI_COMMAND, cmd); + } + + rivafb_fix.mmio_start = pci_resource_start(pd, 0); + rivafb_fix.smem_start = pci_resource_start(pd, 1); + + default_par->ctrl_base = ioremap(rivafb_fix.mmio_start, + rivafb_fix.mmio_len); + if (!default_par->ctrl_base) { + printk(KERN_ERR PFX "cannot ioremap MMIO base\n"); + ret = -EIO; + goto err_release_region; + } + + switch (default_par->riva.Architecture) { + case NV_ARCH_03: + /* Riva128's PRAMIN is in the "framebuffer" space + * Since these cards were never made with more than 8 megabytes + * we can safely allocate this separately. + */ + default_par->riva.PRAMIN = ioremap(rivafb_fix.smem_start + 0x00C00000, 0x00008000); + if (!default_par->riva.PRAMIN) { + printk(KERN_ERR PFX "cannot ioremap PRAMIN region\n"); + ret = -EIO; + goto err_iounmap_ctrl_base; + } + break; + case NV_ARCH_04: + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + default_par->riva.PCRTC0 = + (u32 __iomem *)(default_par->ctrl_base + 0x00600000); + default_par->riva.PRAMIN = + (u32 __iomem *)(default_par->ctrl_base + 0x00710000); + break; + } + riva_common_setup(default_par); + + if (default_par->riva.Architecture == NV_ARCH_03) { + default_par->riva.PCRTC = default_par->riva.PCRTC0 + = default_par->riva.PGRAPH; + } + + rivafb_fix.smem_len = riva_get_memlen(default_par) * 1024; + default_par->dclk_max = riva_get_maxdclk(default_par) * 1000; + info->screen_base = ioremap(rivafb_fix.smem_start, + rivafb_fix.smem_len); + if (!info->screen_base) { + printk(KERN_ERR PFX "cannot ioremap FB base\n"); + ret = -EIO; + goto err_iounmap_pramin; + } + +#ifdef CONFIG_MTRR + if (!nomtrr) { + default_par->mtrr.vram = mtrr_add(rivafb_fix.smem_start, + rivafb_fix.smem_len, + MTRR_TYPE_WRCOMB, 1); + if (default_par->mtrr.vram < 0) { + printk(KERN_ERR PFX "unable to setup MTRR\n"); + } else { + default_par->mtrr.vram_valid = 1; + /* let there be speed */ + printk(KERN_INFO PFX "RIVA MTRR set to ON\n"); + } + } +#endif /* CONFIG_MTRR */ + + info->fbops = &riva_fb_ops; + info->fix = rivafb_fix; + riva_get_EDID(info, pd); + riva_get_edidinfo(info); + + ret=riva_set_fbinfo(info); + if (ret < 0) { + printk(KERN_ERR PFX "error setting initial video mode\n"); + goto err_iounmap_screen_base; + } + + fb_destroy_modedb(info->monspecs.modedb); + info->monspecs.modedb = NULL; + + pci_set_drvdata(pd, info); + + if (backlight) + riva_bl_init(info->par); + + ret = register_framebuffer(info); + if (ret < 0) { + printk(KERN_ERR PFX + "error registering riva framebuffer\n"); + goto err_iounmap_screen_base; + } + + printk(KERN_INFO PFX + "PCI nVidia %s framebuffer ver %s (%dMB @ 0x%lX)\n", + info->fix.id, + RIVAFB_VERSION, + info->fix.smem_len / (1024 * 1024), + info->fix.smem_start); + + NVTRACE_LEAVE(); + return 0; + +err_iounmap_screen_base: +#ifdef CONFIG_FB_RIVA_I2C + riva_delete_i2c_busses(info->par); +#endif + iounmap(info->screen_base); +err_iounmap_pramin: + if (default_par->riva.Architecture == NV_ARCH_03) + iounmap(default_par->riva.PRAMIN); +err_iounmap_ctrl_base: + iounmap(default_par->ctrl_base); +err_release_region: + pci_release_regions(pd); +err_disable_device: +err_free_pixmap: + kfree(info->pixmap.addr); +err_framebuffer_release: + framebuffer_release(info); +err_ret: + return ret; +} + +static void rivafb_remove(struct pci_dev *pd) +{ + struct fb_info *info = pci_get_drvdata(pd); + struct riva_par *par = info->par; + + NVTRACE_ENTER(); + +#ifdef CONFIG_FB_RIVA_I2C + riva_delete_i2c_busses(par); + kfree(par->EDID); +#endif + + unregister_framebuffer(info); + + riva_bl_exit(info); + +#ifdef CONFIG_MTRR + if (par->mtrr.vram_valid) + mtrr_del(par->mtrr.vram, info->fix.smem_start, + info->fix.smem_len); +#endif /* CONFIG_MTRR */ + + iounmap(par->ctrl_base); + iounmap(info->screen_base); + if (par->riva.Architecture == NV_ARCH_03) + iounmap(par->riva.PRAMIN); + pci_release_regions(pd); + kfree(info->pixmap.addr); + framebuffer_release(info); + NVTRACE_LEAVE(); +} + +/* ------------------------------------------------------------------------- * + * + * initialization + * + * ------------------------------------------------------------------------- */ + +#ifndef MODULE +static int rivafb_setup(char *options) +{ + char *this_opt; + + NVTRACE_ENTER(); + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "forceCRTC", 9)) { + char *p; + + p = this_opt + 9; + if (!*p || !*(++p)) continue; + forceCRTC = *p - '0'; + if (forceCRTC < 0 || forceCRTC > 1) + forceCRTC = -1; + } else if (!strncmp(this_opt, "flatpanel", 9)) { + flatpanel = 1; + } else if (!strncmp(this_opt, "backlight:", 10)) { + backlight = simple_strtoul(this_opt+10, NULL, 0); +#ifdef CONFIG_MTRR + } else if (!strncmp(this_opt, "nomtrr", 6)) { + nomtrr = 1; +#endif + } else if (!strncmp(this_opt, "strictmode", 10)) { + strictmode = 1; + } else if (!strncmp(this_opt, "noaccel", 7)) { + noaccel = 1; + } else + mode_option = this_opt; + } + NVTRACE_LEAVE(); + return 0; +} +#endif /* !MODULE */ + +static struct pci_driver rivafb_driver = { + .name = "rivafb", + .id_table = rivafb_pci_tbl, + .probe = rivafb_probe, + .remove = rivafb_remove, +}; + + + +/* ------------------------------------------------------------------------- * + * + * modularization + * + * ------------------------------------------------------------------------- */ + +static int rivafb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("rivafb", &option)) + return -ENODEV; + rivafb_setup(option); +#endif + return pci_register_driver(&rivafb_driver); +} + + +module_init(rivafb_init); + +static void __exit rivafb_exit(void) +{ + pci_unregister_driver(&rivafb_driver); +} + +module_exit(rivafb_exit); + +module_param(noaccel, bool, 0); +MODULE_PARM_DESC(noaccel, "bool: disable acceleration"); +module_param(flatpanel, int, 0); +MODULE_PARM_DESC(flatpanel, "Enables experimental flat panel support for some chipsets. (0 or 1=enabled) (default=0)"); +module_param(forceCRTC, int, 0); +MODULE_PARM_DESC(forceCRTC, "Forces usage of a particular CRTC in case autodetection fails. (0 or 1) (default=autodetect)"); +#ifdef CONFIG_MTRR +module_param(nomtrr, bool, 0); +MODULE_PARM_DESC(nomtrr, "Disables MTRR support (0 or 1=disabled) (default=0)"); +#endif +module_param(strictmode, bool, 0); +MODULE_PARM_DESC(strictmode, "Only use video modes from EDID"); + +MODULE_AUTHOR("Ani Joshi, maintainer"); +MODULE_DESCRIPTION("Framebuffer driver for nVidia Riva 128, TNT, TNT2, and the GeForce series"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/riva/nv_driver.c b/drivers/video/fbdev/riva/nv_driver.c new file mode 100644 index 000000000000..f3694cf17e58 --- /dev/null +++ b/drivers/video/fbdev/riva/nv_driver.c @@ -0,0 +1,422 @@ +/* $XConsortium: nv_driver.c /main/3 1996/10/28 05:13:37 kaleb $ */ +/* + * Copyright 1996-1997 David J. McKay + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * DAVID J. MCKAY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * GPL licensing note -- nVidia is allowing a liberal interpretation of + * the documentation restriction above, to merely say that this nVidia's + * copyright and disclaimer should be included with all code derived + * from this source. -- Jeff Garzik <jgarzik@pobox.com>, 01/Nov/99 + */ + +/* Hacked together from mga driver and 3.3.4 NVIDIA driver by Jarno Paananen + <jpaana@s2.org> */ + +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/nv_setup.c,v 1.18 2002/08/0 +5 20:47:06 mvojkovi Exp $ */ + +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include "nv_type.h" +#include "rivafb.h" +#include "nvreg.h" + +#define PFX "rivafb: " + +static inline unsigned char MISCin(struct riva_par *par) +{ + return (VGA_RD08(par->riva.PVIO, 0x3cc)); +} + +static Bool +riva_is_connected(struct riva_par *par, Bool second) +{ + volatile U032 __iomem *PRAMDAC = par->riva.PRAMDAC0; + U032 reg52C, reg608; + Bool present; + + if(second) PRAMDAC += 0x800; + + reg52C = NV_RD32(PRAMDAC, 0x052C); + reg608 = NV_RD32(PRAMDAC, 0x0608); + + NV_WR32(PRAMDAC, 0x0608, reg608 & ~0x00010000); + + NV_WR32(PRAMDAC, 0x052C, reg52C & 0x0000FEEE); + mdelay(1); + NV_WR32(PRAMDAC, 0x052C, NV_RD32(PRAMDAC, 0x052C) | 1); + + NV_WR32(par->riva.PRAMDAC0, 0x0610, 0x94050140); + NV_WR32(par->riva.PRAMDAC0, 0x0608, 0x00001000); + + mdelay(1); + + present = (NV_RD32(PRAMDAC, 0x0608) & (1 << 28)) ? TRUE : FALSE; + + NV_WR32(par->riva.PRAMDAC0, 0x0608, + NV_RD32(par->riva.PRAMDAC0, 0x0608) & 0x0000EFFF); + + NV_WR32(PRAMDAC, 0x052C, reg52C); + NV_WR32(PRAMDAC, 0x0608, reg608); + + return present; +} + +static void +riva_override_CRTC(struct riva_par *par) +{ + printk(KERN_INFO PFX + "Detected CRTC controller %i being used\n", + par->SecondCRTC ? 1 : 0); + + if(par->forceCRTC != -1) { + printk(KERN_INFO PFX + "Forcing usage of CRTC %i\n", par->forceCRTC); + par->SecondCRTC = par->forceCRTC; + } +} + +static void +riva_is_second(struct riva_par *par) +{ + if (par->FlatPanel == 1) { + switch(par->Chipset & 0xffff) { + case 0x0174: + case 0x0175: + case 0x0176: + case 0x0177: + case 0x0179: + case 0x017C: + case 0x017D: + case 0x0186: + case 0x0187: + /* this might not be a good default for the chips below */ + case 0x0286: + case 0x028C: + case 0x0316: + case 0x0317: + case 0x031A: + case 0x031B: + case 0x031C: + case 0x031D: + case 0x031E: + case 0x031F: + case 0x0324: + case 0x0325: + case 0x0328: + case 0x0329: + case 0x032C: + case 0x032D: + par->SecondCRTC = TRUE; + break; + default: + par->SecondCRTC = FALSE; + break; + } + } else { + if(riva_is_connected(par, 0)) { + + if (NV_RD32(par->riva.PRAMDAC0, 0x0000052C) & 0x100) + par->SecondCRTC = TRUE; + else + par->SecondCRTC = FALSE; + } else + if (riva_is_connected(par, 1)) { + if(NV_RD32(par->riva.PRAMDAC0, 0x0000252C) & 0x100) + par->SecondCRTC = TRUE; + else + par->SecondCRTC = FALSE; + } else /* default */ + par->SecondCRTC = FALSE; + } + riva_override_CRTC(par); +} + +unsigned long riva_get_memlen(struct riva_par *par) +{ + RIVA_HW_INST *chip = &par->riva; + unsigned long memlen = 0; + unsigned int chipset = par->Chipset; + struct pci_dev* dev; + u32 amt; + + switch (chip->Architecture) { + case NV_ARCH_03: + if (NV_RD32(chip->PFB, 0x00000000) & 0x00000020) { + if (((NV_RD32(chip->PMC, 0x00000000) & 0xF0) == 0x20) + && ((NV_RD32(chip->PMC, 0x00000000)&0x0F)>=0x02)) { + /* + * SDRAM 128 ZX. + */ + switch (NV_RD32(chip->PFB,0x00000000) & 0x03) { + case 2: + memlen = 1024 * 4; + break; + case 1: + memlen = 1024 * 2; + break; + default: + memlen = 1024 * 8; + break; + } + } else { + memlen = 1024 * 8; + } + } else { + /* + * SGRAM 128. + */ + switch (NV_RD32(chip->PFB, 0x00000000) & 0x00000003) { + case 0: + memlen = 1024 * 8; + break; + case 2: + memlen = 1024 * 4; + break; + default: + memlen = 1024 * 2; + break; + } + } + break; + case NV_ARCH_04: + if (NV_RD32(chip->PFB, 0x00000000) & 0x00000100) { + memlen = ((NV_RD32(chip->PFB, 0x00000000)>>12)&0x0F) * + 1024 * 2 + 1024 * 2; + } else { + switch (NV_RD32(chip->PFB, 0x00000000) & 0x00000003) { + case 0: + memlen = 1024 * 32; + break; + case 1: + memlen = 1024 * 4; + break; + case 2: + memlen = 1024 * 8; + break; + case 3: + default: + memlen = 1024 * 16; + break; + } + } + break; + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + if(chipset == NV_CHIP_IGEFORCE2) { + + dev = pci_get_bus_and_slot(0, 1); + pci_read_config_dword(dev, 0x7C, &amt); + pci_dev_put(dev); + memlen = (((amt >> 6) & 31) + 1) * 1024; + } else if (chipset == NV_CHIP_0x01F0) { + dev = pci_get_bus_and_slot(0, 1); + pci_read_config_dword(dev, 0x84, &amt); + pci_dev_put(dev); + memlen = (((amt >> 4) & 127) + 1) * 1024; + } else { + switch ((NV_RD32(chip->PFB, 0x0000020C) >> 20) & + 0x000000FF){ + case 0x02: + memlen = 1024 * 2; + break; + case 0x04: + memlen = 1024 * 4; + break; + case 0x08: + memlen = 1024 * 8; + break; + case 0x10: + memlen = 1024 * 16; + break; + case 0x20: + memlen = 1024 * 32; + break; + case 0x40: + memlen = 1024 * 64; + break; + case 0x80: + memlen = 1024 * 128; + break; + default: + memlen = 1024 * 16; + break; + } + } + break; + } + return memlen; +} + +unsigned long riva_get_maxdclk(struct riva_par *par) +{ + RIVA_HW_INST *chip = &par->riva; + unsigned long dclk = 0; + + switch (chip->Architecture) { + case NV_ARCH_03: + if (NV_RD32(chip->PFB, 0x00000000) & 0x00000020) { + if (((NV_RD32(chip->PMC, 0x00000000) & 0xF0) == 0x20) + && ((NV_RD32(chip->PMC,0x00000000)&0x0F) >= 0x02)) { + /* + * SDRAM 128 ZX. + */ + dclk = 800000; + } else { + dclk = 1000000; + } + } else { + /* + * SGRAM 128. + */ + dclk = 1000000; + } + break; + case NV_ARCH_04: + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + switch ((NV_RD32(chip->PFB, 0x00000000) >> 3) & 0x00000003) { + case 3: + dclk = 800000; + break; + default: + dclk = 1000000; + break; + } + break; + } + return dclk; +} + +void +riva_common_setup(struct riva_par *par) +{ + par->riva.EnableIRQ = 0; + par->riva.PRAMDAC0 = + (volatile U032 __iomem *)(par->ctrl_base + 0x00680000); + par->riva.PFB = + (volatile U032 __iomem *)(par->ctrl_base + 0x00100000); + par->riva.PFIFO = + (volatile U032 __iomem *)(par->ctrl_base + 0x00002000); + par->riva.PGRAPH = + (volatile U032 __iomem *)(par->ctrl_base + 0x00400000); + par->riva.PEXTDEV = + (volatile U032 __iomem *)(par->ctrl_base + 0x00101000); + par->riva.PTIMER = + (volatile U032 __iomem *)(par->ctrl_base + 0x00009000); + par->riva.PMC = + (volatile U032 __iomem *)(par->ctrl_base + 0x00000000); + par->riva.FIFO = + (volatile U032 __iomem *)(par->ctrl_base + 0x00800000); + par->riva.PCIO0 = par->ctrl_base + 0x00601000; + par->riva.PDIO0 = par->ctrl_base + 0x00681000; + par->riva.PVIO = par->ctrl_base + 0x000C0000; + + par->riva.IO = (MISCin(par) & 0x01) ? 0x3D0 : 0x3B0; + + if (par->FlatPanel == -1) { + switch (par->Chipset & 0xffff) { + case 0x0112: /* known laptop chips */ + case 0x0174: + case 0x0175: + case 0x0176: + case 0x0177: + case 0x0179: + case 0x017C: + case 0x017D: + case 0x0186: + case 0x0187: + case 0x0286: + case 0x028C: + case 0x0316: + case 0x0317: + case 0x031A: + case 0x031B: + case 0x031C: + case 0x031D: + case 0x031E: + case 0x031F: + case 0x0324: + case 0x0325: + case 0x0328: + case 0x0329: + case 0x032C: + case 0x032D: + printk(KERN_INFO PFX + "On a laptop. Assuming Digital Flat Panel\n"); + par->FlatPanel = 1; + break; + default: + break; + } + } + + switch (par->Chipset & 0x0ff0) { + case 0x0110: + if (par->Chipset == NV_CHIP_GEFORCE2_GO) + par->SecondCRTC = TRUE; +#if defined(__powerpc__) + if (par->FlatPanel == 1) + par->SecondCRTC = TRUE; +#endif + riva_override_CRTC(par); + break; + case 0x0170: + case 0x0180: + case 0x01F0: + case 0x0250: + case 0x0280: + case 0x0300: + case 0x0310: + case 0x0320: + case 0x0330: + case 0x0340: + riva_is_second(par); + break; + default: + break; + } + + if (par->SecondCRTC) { + par->riva.PCIO = par->riva.PCIO0 + 0x2000; + par->riva.PCRTC = par->riva.PCRTC0 + 0x800; + par->riva.PRAMDAC = par->riva.PRAMDAC0 + 0x800; + par->riva.PDIO = par->riva.PDIO0 + 0x2000; + } else { + par->riva.PCIO = par->riva.PCIO0; + par->riva.PCRTC = par->riva.PCRTC0; + par->riva.PRAMDAC = par->riva.PRAMDAC0; + par->riva.PDIO = par->riva.PDIO0; + } + + if (par->FlatPanel == -1) { + /* Fix me, need x86 DDC code */ + par->FlatPanel = 0; + } + par->riva.flatPanel = (par->FlatPanel > 0) ? TRUE : FALSE; + + RivaGetConfig(&par->riva, par->Chipset); +} + diff --git a/drivers/video/fbdev/riva/nv_type.h b/drivers/video/fbdev/riva/nv_type.h new file mode 100644 index 000000000000..a69480c9a67c --- /dev/null +++ b/drivers/video/fbdev/riva/nv_type.h @@ -0,0 +1,58 @@ +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/nv_type.h,v 1.35 2002/08/05 20:47:06 mvojkovi Exp $ */ + +#ifndef __NV_STRUCT_H__ +#define __NV_STRUCT_H__ + +#define NV_CHIP_RIVA_128 ((PCI_VENDOR_ID_NVIDIA_SGS << 16)| PCI_DEVICE_ID_NVIDIA_RIVA128) +#define NV_CHIP_TNT ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_TNT) +#define NV_CHIP_TNT2 ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_TNT2) +#define NV_CHIP_UTNT2 ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_UTNT2) +#define NV_CHIP_VTNT2 ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_VTNT2) +#define NV_CHIP_UVTNT2 ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_UVTNT2) +#define NV_CHIP_ITNT2 ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_ITNT2) +#define NV_CHIP_GEFORCE_256 ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_GEFORCE_256) +#define NV_CHIP_GEFORCE_DDR ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_GEFORCE_DDR) +#define NV_CHIP_QUADRO ((PCI_VENDOR_ID_NVIDIA << 16)| PCI_DEVICE_ID_NVIDIA_QUADRO) +#define NV_CHIP_GEFORCE2_MX ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE2_MX) +#define NV_CHIP_GEFORCE2_MX_100 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE2_MX_100) +#define NV_CHIP_QUADRO2_MXR ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO2_MXR) +#define NV_CHIP_GEFORCE2_GO ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE2_GO) +#define NV_CHIP_GEFORCE2_GTS ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE2_GTS) +#define NV_CHIP_GEFORCE2_TI ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE2_TI) +#define NV_CHIP_GEFORCE2_ULTRA ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE2_ULTRA) +#define NV_CHIP_QUADRO2_PRO ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO2_PRO) +#define NV_CHIP_GEFORCE4_MX_460 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_MX_460) +#define NV_CHIP_GEFORCE4_MX_440 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_MX_440) +#define NV_CHIP_GEFORCE4_MX_420 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_MX_420) +#define NV_CHIP_GEFORCE4_440_GO ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_440_GO) +#define NV_CHIP_GEFORCE4_420_GO ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_420_GO) +#define NV_CHIP_GEFORCE4_420_GO_M32 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_420_GO_M32) +#define NV_CHIP_QUADRO4_500XGL ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO4_500XGL) +#define NV_CHIP_GEFORCE4_440_GO_M64 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_440_GO_M64) +#define NV_CHIP_QUADRO4_200 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO4_200) +#define NV_CHIP_QUADRO4_550XGL ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO4_550XGL) +#define NV_CHIP_QUADRO4_500_GOGL ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO4_500_GOGL) +#define NV_CHIP_0x0180 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0180) +#define NV_CHIP_0x0181 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0181) +#define NV_CHIP_0x0182 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0182) +#define NV_CHIP_0x0188 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0188) +#define NV_CHIP_0x018A ((PCI_VENDOR_ID_NVIDIA << 16) | 0x018A) +#define NV_CHIP_0x018B ((PCI_VENDOR_ID_NVIDIA << 16) | 0x018B) +#define NV_CHIP_IGEFORCE2 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_IGEFORCE2) +#define NV_CHIP_0x01F0 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x01F0) +#define NV_CHIP_GEFORCE3 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE3) +#define NV_CHIP_GEFORCE3_TI_200 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE3_TI_200) +#define NV_CHIP_GEFORCE3_TI_500 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE3_TI_500) +#define NV_CHIP_QUADRO_DCC ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO_DCC) +#define NV_CHIP_GEFORCE4_TI_4600 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_TI_4600) +#define NV_CHIP_GEFORCE4_TI_4400 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_TI_4400) +#define NV_CHIP_GEFORCE4_TI_4200 ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_GEFORCE4_TI_4200) +#define NV_CHIP_QUADRO4_900XGL ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO4_900XGL) +#define NV_CHIP_QUADRO4_750XGL ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO4_750XGL) +#define NV_CHIP_QUADRO4_700XGL ((PCI_VENDOR_ID_NVIDIA << 16) | PCI_DEVICE_ID_NVIDIA_QUADRO4_700XGL) +#define NV_CHIP_0x0280 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0280) +#define NV_CHIP_0x0281 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0281) +#define NV_CHIP_0x0288 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0288) +#define NV_CHIP_0x0289 ((PCI_VENDOR_ID_NVIDIA << 16) | 0x0289) + +#endif /* __NV_STRUCT_H__ */ diff --git a/drivers/video/fbdev/riva/nvreg.h b/drivers/video/fbdev/riva/nvreg.h new file mode 100644 index 000000000000..abfc167ae8d8 --- /dev/null +++ b/drivers/video/fbdev/riva/nvreg.h @@ -0,0 +1,188 @@ +/* $XConsortium: nvreg.h /main/2 1996/10/28 05:13:41 kaleb $ */ +/* + * Copyright 1996-1997 David J. McKay + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * DAVID J. MCKAY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* $XFree86: xc/programs/Xserver/hw/xfree86/vga256/drivers/nv/nvreg.h,v 3.2.2.1 1998/01/18 10:35:36 hohndel Exp $ */ + +#ifndef __NVREG_H_ +#define __NVREG_H_ + +/* Little macro to construct bitmask for contiguous ranges of bits */ +#define BITMASK(t,b) (((unsigned)(1U << (((t)-(b)+1)))-1) << (b)) +#define MASKEXPAND(mask) BITMASK(1?mask,0?mask) + +/* Macro to set specific bitfields (mask has to be a macro x:y) ! */ +#define SetBF(mask,value) ((value) << (0?mask)) +#define GetBF(var,mask) (((unsigned)((var) & MASKEXPAND(mask))) >> (0?mask) ) + +#define MaskAndSetBF(var,mask,value) (var)=(((var)&(~MASKEXPAND(mask)) \ + | SetBF(mask,value))) + +#define DEVICE_BASE(device) (0?NV##_##device) +#define DEVICE_SIZE(device) ((1?NV##_##device) - DEVICE_BASE(device)+1) + +/* This is where we will have to have conditional compilation */ +#define DEVICE_ACCESS(device,reg) \ + nvCONTROL[(NV_##device##_##reg)/4] + +#define DEVICE_WRITE(device,reg,value) DEVICE_ACCESS(device,reg)=(value) +#define DEVICE_READ(device,reg) DEVICE_ACCESS(device,reg) +#define DEVICE_PRINT(device,reg) \ + ErrorF("NV_"#device"_"#reg"=#%08lx\n",DEVICE_ACCESS(device,reg)) +#define DEVICE_DEF(device,mask,value) \ + SetBF(NV_##device##_##mask,NV_##device##_##mask##_##value) +#define DEVICE_VALUE(device,mask,value) SetBF(NV_##device##_##mask,value) +#define DEVICE_MASK(device,mask) MASKEXPAND(NV_##device##_##mask) + +#define PDAC_Write(reg,value) DEVICE_WRITE(PDAC,reg,value) +#define PDAC_Read(reg) DEVICE_READ(PDAC,reg) +#define PDAC_Print(reg) DEVICE_PRINT(PDAC,reg) +#define PDAC_Def(mask,value) DEVICE_DEF(PDAC,mask,value) +#define PDAC_Val(mask,value) DEVICE_VALUE(PDAC,mask,value) +#define PDAC_Mask(mask) DEVICE_MASK(PDAC,mask) + +#define PFB_Write(reg,value) DEVICE_WRITE(PFB,reg,value) +#define PFB_Read(reg) DEVICE_READ(PFB,reg) +#define PFB_Print(reg) DEVICE_PRINT(PFB,reg) +#define PFB_Def(mask,value) DEVICE_DEF(PFB,mask,value) +#define PFB_Val(mask,value) DEVICE_VALUE(PFB,mask,value) +#define PFB_Mask(mask) DEVICE_MASK(PFB,mask) + +#define PRM_Write(reg,value) DEVICE_WRITE(PRM,reg,value) +#define PRM_Read(reg) DEVICE_READ(PRM,reg) +#define PRM_Print(reg) DEVICE_PRINT(PRM,reg) +#define PRM_Def(mask,value) DEVICE_DEF(PRM,mask,value) +#define PRM_Val(mask,value) DEVICE_VALUE(PRM,mask,value) +#define PRM_Mask(mask) DEVICE_MASK(PRM,mask) + +#define PGRAPH_Write(reg,value) DEVICE_WRITE(PGRAPH,reg,value) +#define PGRAPH_Read(reg) DEVICE_READ(PGRAPH,reg) +#define PGRAPH_Print(reg) DEVICE_PRINT(PGRAPH,reg) +#define PGRAPH_Def(mask,value) DEVICE_DEF(PGRAPH,mask,value) +#define PGRAPH_Val(mask,value) DEVICE_VALUE(PGRAPH,mask,value) +#define PGRAPH_Mask(mask) DEVICE_MASK(PGRAPH,mask) + +#define PDMA_Write(reg,value) DEVICE_WRITE(PDMA,reg,value) +#define PDMA_Read(reg) DEVICE_READ(PDMA,reg) +#define PDMA_Print(reg) DEVICE_PRINT(PDMA,reg) +#define PDMA_Def(mask,value) DEVICE_DEF(PDMA,mask,value) +#define PDMA_Val(mask,value) DEVICE_VALUE(PDMA,mask,value) +#define PDMA_Mask(mask) DEVICE_MASK(PDMA,mask) + +#define PTIMER_Write(reg,value) DEVICE_WRITE(PTIMER,reg,value) +#define PTIMER_Read(reg) DEVICE_READ(PTIMER,reg) +#define PTIMER_Print(reg) DEVICE_PRINT(PTIMER,reg) +#define PTIMER_Def(mask,value) DEVICE_DEF(PTIMER,mask,value) +#define PTIMER_Val(mask,value) DEVICE_VALUE(PTIEMR,mask,value) +#define PTIMER_Mask(mask) DEVICE_MASK(PTIMER,mask) + +#define PEXTDEV_Write(reg,value) DEVICE_WRITE(PEXTDEV,reg,value) +#define PEXTDEV_Read(reg) DEVICE_READ(PEXTDEV,reg) +#define PEXTDEV_Print(reg) DEVICE_PRINT(PEXTDEV,reg) +#define PEXTDEV_Def(mask,value) DEVICE_DEF(PEXTDEV,mask,value) +#define PEXTDEV_Val(mask,value) DEVICE_VALUE(PEXTDEV,mask,value) +#define PEXTDEV_Mask(mask) DEVICE_MASK(PEXTDEV,mask) + +#define PFIFO_Write(reg,value) DEVICE_WRITE(PFIFO,reg,value) +#define PFIFO_Read(reg) DEVICE_READ(PFIFO,reg) +#define PFIFO_Print(reg) DEVICE_PRINT(PFIFO,reg) +#define PFIFO_Def(mask,value) DEVICE_DEF(PFIFO,mask,value) +#define PFIFO_Val(mask,value) DEVICE_VALUE(PFIFO,mask,value) +#define PFIFO_Mask(mask) DEVICE_MASK(PFIFO,mask) + +#define PRAM_Write(reg,value) DEVICE_WRITE(PRAM,reg,value) +#define PRAM_Read(reg) DEVICE_READ(PRAM,reg) +#define PRAM_Print(reg) DEVICE_PRINT(PRAM,reg) +#define PRAM_Def(mask,value) DEVICE_DEF(PRAM,mask,value) +#define PRAM_Val(mask,value) DEVICE_VALUE(PRAM,mask,value) +#define PRAM_Mask(mask) DEVICE_MASK(PRAM,mask) + +#define PRAMFC_Write(reg,value) DEVICE_WRITE(PRAMFC,reg,value) +#define PRAMFC_Read(reg) DEVICE_READ(PRAMFC,reg) +#define PRAMFC_Print(reg) DEVICE_PRINT(PRAMFC,reg) +#define PRAMFC_Def(mask,value) DEVICE_DEF(PRAMFC,mask,value) +#define PRAMFC_Val(mask,value) DEVICE_VALUE(PRAMFC,mask,value) +#define PRAMFC_Mask(mask) DEVICE_MASK(PRAMFC,mask) + +#define PMC_Write(reg,value) DEVICE_WRITE(PMC,reg,value) +#define PMC_Read(reg) DEVICE_READ(PMC,reg) +#define PMC_Print(reg) DEVICE_PRINT(PMC,reg) +#define PMC_Def(mask,value) DEVICE_DEF(PMC,mask,value) +#define PMC_Val(mask,value) DEVICE_VALUE(PMC,mask,value) +#define PMC_Mask(mask) DEVICE_MASK(PMC,mask) + +#define PMC_Write(reg,value) DEVICE_WRITE(PMC,reg,value) +#define PMC_Read(reg) DEVICE_READ(PMC,reg) +#define PMC_Print(reg) DEVICE_PRINT(PMC,reg) +#define PMC_Def(mask,value) DEVICE_DEF(PMC,mask,value) +#define PMC_Val(mask,value) DEVICE_VALUE(PMC,mask,value) +#define PMC_Mask(mask) DEVICE_MASK(PMC,mask) + + +#define PBUS_Write(reg,value) DEVICE_WRITE(PBUS,reg,value) +#define PBUS_Read(reg) DEVICE_READ(PBUS,reg) +#define PBUS_Print(reg) DEVICE_PRINT(PBUS,reg) +#define PBUS_Def(mask,value) DEVICE_DEF(PBUS,mask,value) +#define PBUS_Val(mask,value) DEVICE_VALUE(PBUS,mask,value) +#define PBUS_Mask(mask) DEVICE_MASK(PBUS,mask) + + +#define PRAMDAC_Write(reg,value) DEVICE_WRITE(PRAMDAC,reg,value) +#define PRAMDAC_Read(reg) DEVICE_READ(PRAMDAC,reg) +#define PRAMDAC_Print(reg) DEVICE_PRINT(PRAMDAC,reg) +#define PRAMDAC_Def(mask,value) DEVICE_DEF(PRAMDAC,mask,value) +#define PRAMDAC_Val(mask,value) DEVICE_VALUE(PRAMDAC,mask,value) +#define PRAMDAC_Mask(mask) DEVICE_MASK(PRAMDAC,mask) + + +#define PDAC_ReadExt(reg) \ + ((PDAC_Write(INDEX_LO,(NV_PDAC_EXT_##reg) & 0xff)),\ + (PDAC_Write(INDEX_HI,((NV_PDAC_EXT_##reg) >> 8) & 0xff)),\ + (PDAC_Read(INDEX_DATA))) + +#define PDAC_WriteExt(reg,value)\ + ((PDAC_Write(INDEX_LO,(NV_PDAC_EXT_##reg) & 0xff)),\ + (PDAC_Write(INDEX_HI,((NV_PDAC_EXT_##reg) >> 8) & 0xff)),\ + (PDAC_Write(INDEX_DATA,(value)))) + +#define CRTC_Write(index,value) outb((index), 0x3d4); outb(value, 0x3d5) +#define CRTC_Read(index) (outb(index, 0x3d4),inb(0x3d5)) + +#define PCRTC_Write(index,value) CRTC_Write(NV_PCRTC_##index,value) +#define PCRTC_Read(index) CRTC_Read(NV_PCRTC_##index) + +#define PCRTC_Def(mask,value) DEVICE_DEF(PCRTC,mask,value) +#define PCRTC_Val(mask,value) DEVICE_VALUE(PCRTC,mask,value) +#define PCRTC_Mask(mask) DEVICE_MASK(PCRTC,mask) + +#define SR_Write(index,value) outb(0x3c4,(index));outb(0x3c5,value) +#define SR_Read(index) (outb(0x3c4,index),inb(0x3c5)) + +extern volatile unsigned *nvCONTROL; + +typedef enum {NV1,NV3,NV4,NumNVChips} NVChipType; + +NVChipType GetChipType(void); + +#endif + + diff --git a/drivers/video/fbdev/riva/riva_hw.c b/drivers/video/fbdev/riva/riva_hw.c new file mode 100644 index 000000000000..78fdbf5178d7 --- /dev/null +++ b/drivers/video/fbdev/riva/riva_hw.c @@ -0,0 +1,2268 @@ + /***************************************************************************\ +|* *| +|* Copyright 1993-1999 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 1993-1999 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| + \***************************************************************************/ + +/* + * GPL licensing note -- nVidia is allowing a liberal interpretation of + * the documentation restriction above, to merely say that this nVidia's + * copyright and disclaimer should be included with all code derived + * from this source. -- Jeff Garzik <jgarzik@pobox.com>, 01/Nov/99 + */ + +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/riva_hw.c,v 1.33 2002/08/05 20:47:06 mvojkovi Exp $ */ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include "riva_hw.h" +#include "riva_tbl.h" +#include "nv_type.h" + +/* + * This file is an OS-agnostic file used to make RIVA 128 and RIVA TNT + * operate identically (except TNT has more memory and better 3D quality. + */ +static int nv3Busy +( + RIVA_HW_INST *chip +) +{ + return ((NV_RD32(&chip->Rop->FifoFree, 0) < chip->FifoEmptyCount) || + NV_RD32(&chip->PGRAPH[0x000006B0/4], 0) & 0x01); +} +static int nv4Busy +( + RIVA_HW_INST *chip +) +{ + return ((NV_RD32(&chip->Rop->FifoFree, 0) < chip->FifoEmptyCount) || + NV_RD32(&chip->PGRAPH[0x00000700/4], 0) & 0x01); +} +static int nv10Busy +( + RIVA_HW_INST *chip +) +{ + return ((NV_RD32(&chip->Rop->FifoFree, 0) < chip->FifoEmptyCount) || + NV_RD32(&chip->PGRAPH[0x00000700/4], 0) & 0x01); +} + +static void vgaLockUnlock +( + RIVA_HW_INST *chip, + int Lock +) +{ + U008 cr11; + VGA_WR08(chip->PCIO, 0x3D4, 0x11); + cr11 = VGA_RD08(chip->PCIO, 0x3D5); + if(Lock) cr11 |= 0x80; + else cr11 &= ~0x80; + VGA_WR08(chip->PCIO, 0x3D5, cr11); +} +static void nv3LockUnlock +( + RIVA_HW_INST *chip, + int Lock +) +{ + VGA_WR08(chip->PVIO, 0x3C4, 0x06); + VGA_WR08(chip->PVIO, 0x3C5, Lock ? 0x99 : 0x57); + vgaLockUnlock(chip, Lock); +} +static void nv4LockUnlock +( + RIVA_HW_INST *chip, + int Lock +) +{ + VGA_WR08(chip->PCIO, 0x3D4, 0x1F); + VGA_WR08(chip->PCIO, 0x3D5, Lock ? 0x99 : 0x57); + vgaLockUnlock(chip, Lock); +} + +static int ShowHideCursor +( + RIVA_HW_INST *chip, + int ShowHide +) +{ + int cursor; + cursor = chip->CurrentState->cursor1; + chip->CurrentState->cursor1 = (chip->CurrentState->cursor1 & 0xFE) | + (ShowHide & 0x01); + VGA_WR08(chip->PCIO, 0x3D4, 0x31); + VGA_WR08(chip->PCIO, 0x3D5, chip->CurrentState->cursor1); + return (cursor & 0x01); +} + +/****************************************************************************\ +* * +* The video arbitration routines calculate some "magic" numbers. Fixes * +* the snow seen when accessing the framebuffer without it. * +* It just works (I hope). * +* * +\****************************************************************************/ + +#define DEFAULT_GR_LWM 100 +#define DEFAULT_VID_LWM 100 +#define DEFAULT_GR_BURST_SIZE 256 +#define DEFAULT_VID_BURST_SIZE 128 +#define VIDEO 0 +#define GRAPHICS 1 +#define MPORT 2 +#define ENGINE 3 +#define GFIFO_SIZE 320 +#define GFIFO_SIZE_128 256 +#define MFIFO_SIZE 120 +#define VFIFO_SIZE 256 + +typedef struct { + int gdrain_rate; + int vdrain_rate; + int mdrain_rate; + int gburst_size; + int vburst_size; + char vid_en; + char gr_en; + int wcmocc, wcgocc, wcvocc, wcvlwm, wcglwm; + int by_gfacc; + char vid_only_once; + char gr_only_once; + char first_vacc; + char first_gacc; + char first_macc; + int vocc; + int gocc; + int mocc; + char cur; + char engine_en; + char converged; + int priority; +} nv3_arb_info; +typedef struct { + int graphics_lwm; + int video_lwm; + int graphics_burst_size; + int video_burst_size; + int graphics_hi_priority; + int media_hi_priority; + int rtl_values; + int valid; +} nv3_fifo_info; +typedef struct { + char pix_bpp; + char enable_video; + char gr_during_vid; + char enable_mp; + int memory_width; + int video_scale; + int pclk_khz; + int mclk_khz; + int mem_page_miss; + int mem_latency; + char mem_aligned; +} nv3_sim_state; +typedef struct { + int graphics_lwm; + int video_lwm; + int graphics_burst_size; + int video_burst_size; + int valid; +} nv4_fifo_info; +typedef struct { + int pclk_khz; + int mclk_khz; + int nvclk_khz; + char mem_page_miss; + char mem_latency; + int memory_width; + char enable_video; + char gr_during_vid; + char pix_bpp; + char mem_aligned; + char enable_mp; +} nv4_sim_state; +typedef struct { + int graphics_lwm; + int video_lwm; + int graphics_burst_size; + int video_burst_size; + int valid; +} nv10_fifo_info; +typedef struct { + int pclk_khz; + int mclk_khz; + int nvclk_khz; + char mem_page_miss; + char mem_latency; + u32 memory_type; + int memory_width; + char enable_video; + char gr_during_vid; + char pix_bpp; + char mem_aligned; + char enable_mp; +} nv10_sim_state; +static int nv3_iterate(nv3_fifo_info *res_info, nv3_sim_state * state, nv3_arb_info *ainfo) +{ + int iter = 0; + int tmp; + int vfsize, mfsize, gfsize; + int mburst_size = 32; + int mmisses, gmisses, vmisses; + int misses; + int vlwm, glwm, mlwm; + int last, next, cur; + int max_gfsize ; + long ns; + + vlwm = 0; + glwm = 0; + mlwm = 0; + vfsize = 0; + gfsize = 0; + cur = ainfo->cur; + mmisses = 2; + gmisses = 2; + vmisses = 2; + if (ainfo->gburst_size == 128) max_gfsize = GFIFO_SIZE_128; + else max_gfsize = GFIFO_SIZE; + max_gfsize = GFIFO_SIZE; + while (1) + { + if (ainfo->vid_en) + { + if (ainfo->wcvocc > ainfo->vocc) ainfo->wcvocc = ainfo->vocc; + if (ainfo->wcvlwm > vlwm) ainfo->wcvlwm = vlwm ; + ns = 1000000 * ainfo->vburst_size/(state->memory_width/8)/state->mclk_khz; + vfsize = ns * ainfo->vdrain_rate / 1000000; + vfsize = ainfo->wcvlwm - ainfo->vburst_size + vfsize; + } + if (state->enable_mp) + { + if (ainfo->wcmocc > ainfo->mocc) ainfo->wcmocc = ainfo->mocc; + } + if (ainfo->gr_en) + { + if (ainfo->wcglwm > glwm) ainfo->wcglwm = glwm ; + if (ainfo->wcgocc > ainfo->gocc) ainfo->wcgocc = ainfo->gocc; + ns = 1000000 * (ainfo->gburst_size/(state->memory_width/8))/state->mclk_khz; + gfsize = (ns * (long) ainfo->gdrain_rate)/1000000; + gfsize = ainfo->wcglwm - ainfo->gburst_size + gfsize; + } + mfsize = 0; + if (!state->gr_during_vid && ainfo->vid_en) + if (ainfo->vid_en && (ainfo->vocc < 0) && !ainfo->vid_only_once) + next = VIDEO; + else if (ainfo->mocc < 0) + next = MPORT; + else if (ainfo->gocc< ainfo->by_gfacc) + next = GRAPHICS; + else return (0); + else switch (ainfo->priority) + { + case VIDEO: + if (ainfo->vid_en && ainfo->vocc<0 && !ainfo->vid_only_once) + next = VIDEO; + else if (ainfo->gr_en && ainfo->gocc<0 && !ainfo->gr_only_once) + next = GRAPHICS; + else if (ainfo->mocc<0) + next = MPORT; + else return (0); + break; + case GRAPHICS: + if (ainfo->gr_en && ainfo->gocc<0 && !ainfo->gr_only_once) + next = GRAPHICS; + else if (ainfo->vid_en && ainfo->vocc<0 && !ainfo->vid_only_once) + next = VIDEO; + else if (ainfo->mocc<0) + next = MPORT; + else return (0); + break; + default: + if (ainfo->mocc<0) + next = MPORT; + else if (ainfo->gr_en && ainfo->gocc<0 && !ainfo->gr_only_once) + next = GRAPHICS; + else if (ainfo->vid_en && ainfo->vocc<0 && !ainfo->vid_only_once) + next = VIDEO; + else return (0); + break; + } + last = cur; + cur = next; + iter++; + switch (cur) + { + case VIDEO: + if (last==cur) misses = 0; + else if (ainfo->first_vacc) misses = vmisses; + else misses = 1; + ainfo->first_vacc = 0; + if (last!=cur) + { + ns = 1000000 * (vmisses*state->mem_page_miss + state->mem_latency)/state->mclk_khz; + vlwm = ns * ainfo->vdrain_rate/ 1000000; + vlwm = ainfo->vocc - vlwm; + } + ns = 1000000*(misses*state->mem_page_miss + ainfo->vburst_size)/(state->memory_width/8)/state->mclk_khz; + ainfo->vocc = ainfo->vocc + ainfo->vburst_size - ns*ainfo->vdrain_rate/1000000; + ainfo->gocc = ainfo->gocc - ns*ainfo->gdrain_rate/1000000; + ainfo->mocc = ainfo->mocc - ns*ainfo->mdrain_rate/1000000; + break; + case GRAPHICS: + if (last==cur) misses = 0; + else if (ainfo->first_gacc) misses = gmisses; + else misses = 1; + ainfo->first_gacc = 0; + if (last!=cur) + { + ns = 1000000*(gmisses*state->mem_page_miss + state->mem_latency)/state->mclk_khz ; + glwm = ns * ainfo->gdrain_rate/1000000; + glwm = ainfo->gocc - glwm; + } + ns = 1000000*(misses*state->mem_page_miss + ainfo->gburst_size/(state->memory_width/8))/state->mclk_khz; + ainfo->vocc = ainfo->vocc + 0 - ns*ainfo->vdrain_rate/1000000; + ainfo->gocc = ainfo->gocc + ainfo->gburst_size - ns*ainfo->gdrain_rate/1000000; + ainfo->mocc = ainfo->mocc + 0 - ns*ainfo->mdrain_rate/1000000; + break; + default: + if (last==cur) misses = 0; + else if (ainfo->first_macc) misses = mmisses; + else misses = 1; + ainfo->first_macc = 0; + ns = 1000000*(misses*state->mem_page_miss + mburst_size/(state->memory_width/8))/state->mclk_khz; + ainfo->vocc = ainfo->vocc + 0 - ns*ainfo->vdrain_rate/1000000; + ainfo->gocc = ainfo->gocc + 0 - ns*ainfo->gdrain_rate/1000000; + ainfo->mocc = ainfo->mocc + mburst_size - ns*ainfo->mdrain_rate/1000000; + break; + } + if (iter>100) + { + ainfo->converged = 0; + return (1); + } + ns = 1000000*ainfo->gburst_size/(state->memory_width/8)/state->mclk_khz; + tmp = ns * ainfo->gdrain_rate/1000000; + if (abs(ainfo->gburst_size) + ((abs(ainfo->wcglwm) + 16 ) & ~0x7) - tmp > max_gfsize) + { + ainfo->converged = 0; + return (1); + } + ns = 1000000*ainfo->vburst_size/(state->memory_width/8)/state->mclk_khz; + tmp = ns * ainfo->vdrain_rate/1000000; + if (abs(ainfo->vburst_size) + (abs(ainfo->wcvlwm + 32) & ~0xf) - tmp> VFIFO_SIZE) + { + ainfo->converged = 0; + return (1); + } + if (abs(ainfo->gocc) > max_gfsize) + { + ainfo->converged = 0; + return (1); + } + if (abs(ainfo->vocc) > VFIFO_SIZE) + { + ainfo->converged = 0; + return (1); + } + if (abs(ainfo->mocc) > MFIFO_SIZE) + { + ainfo->converged = 0; + return (1); + } + if (abs(vfsize) > VFIFO_SIZE) + { + ainfo->converged = 0; + return (1); + } + if (abs(gfsize) > max_gfsize) + { + ainfo->converged = 0; + return (1); + } + if (abs(mfsize) > MFIFO_SIZE) + { + ainfo->converged = 0; + return (1); + } + } +} +static char nv3_arb(nv3_fifo_info * res_info, nv3_sim_state * state, nv3_arb_info *ainfo) +{ + long ens, vns, mns, gns; + int mmisses, gmisses, vmisses, eburst_size, mburst_size; + int refresh_cycle; + + refresh_cycle = 0; + refresh_cycle = 2*(state->mclk_khz/state->pclk_khz) + 5; + mmisses = 2; + if (state->mem_aligned) gmisses = 2; + else gmisses = 3; + vmisses = 2; + eburst_size = state->memory_width * 1; + mburst_size = 32; + gns = 1000000 * (gmisses*state->mem_page_miss + state->mem_latency)/state->mclk_khz; + ainfo->by_gfacc = gns*ainfo->gdrain_rate/1000000; + ainfo->wcmocc = 0; + ainfo->wcgocc = 0; + ainfo->wcvocc = 0; + ainfo->wcvlwm = 0; + ainfo->wcglwm = 0; + ainfo->engine_en = 1; + ainfo->converged = 1; + if (ainfo->engine_en) + { + ens = 1000000*(state->mem_page_miss + eburst_size/(state->memory_width/8) +refresh_cycle)/state->mclk_khz; + ainfo->mocc = state->enable_mp ? 0-ens*ainfo->mdrain_rate/1000000 : 0; + ainfo->vocc = ainfo->vid_en ? 0-ens*ainfo->vdrain_rate/1000000 : 0; + ainfo->gocc = ainfo->gr_en ? 0-ens*ainfo->gdrain_rate/1000000 : 0; + ainfo->cur = ENGINE; + ainfo->first_vacc = 1; + ainfo->first_gacc = 1; + ainfo->first_macc = 1; + nv3_iterate(res_info, state,ainfo); + } + if (state->enable_mp) + { + mns = 1000000 * (mmisses*state->mem_page_miss + mburst_size/(state->memory_width/8) + refresh_cycle)/state->mclk_khz; + ainfo->mocc = state->enable_mp ? 0 : mburst_size - mns*ainfo->mdrain_rate/1000000; + ainfo->vocc = ainfo->vid_en ? 0 : 0- mns*ainfo->vdrain_rate/1000000; + ainfo->gocc = ainfo->gr_en ? 0: 0- mns*ainfo->gdrain_rate/1000000; + ainfo->cur = MPORT; + ainfo->first_vacc = 1; + ainfo->first_gacc = 1; + ainfo->first_macc = 0; + nv3_iterate(res_info, state,ainfo); + } + if (ainfo->gr_en) + { + ainfo->first_vacc = 1; + ainfo->first_gacc = 0; + ainfo->first_macc = 1; + gns = 1000000*(gmisses*state->mem_page_miss + ainfo->gburst_size/(state->memory_width/8) + refresh_cycle)/state->mclk_khz; + ainfo->gocc = ainfo->gburst_size - gns*ainfo->gdrain_rate/1000000; + ainfo->vocc = ainfo->vid_en? 0-gns*ainfo->vdrain_rate/1000000 : 0; + ainfo->mocc = state->enable_mp ? 0-gns*ainfo->mdrain_rate/1000000: 0; + ainfo->cur = GRAPHICS; + nv3_iterate(res_info, state,ainfo); + } + if (ainfo->vid_en) + { + ainfo->first_vacc = 0; + ainfo->first_gacc = 1; + ainfo->first_macc = 1; + vns = 1000000*(vmisses*state->mem_page_miss + ainfo->vburst_size/(state->memory_width/8) + refresh_cycle)/state->mclk_khz; + ainfo->vocc = ainfo->vburst_size - vns*ainfo->vdrain_rate/1000000; + ainfo->gocc = ainfo->gr_en? (0-vns*ainfo->gdrain_rate/1000000) : 0; + ainfo->mocc = state->enable_mp? 0-vns*ainfo->mdrain_rate/1000000 :0 ; + ainfo->cur = VIDEO; + nv3_iterate(res_info, state, ainfo); + } + if (ainfo->converged) + { + res_info->graphics_lwm = (int)abs(ainfo->wcglwm) + 16; + res_info->video_lwm = (int)abs(ainfo->wcvlwm) + 32; + res_info->graphics_burst_size = ainfo->gburst_size; + res_info->video_burst_size = ainfo->vburst_size; + res_info->graphics_hi_priority = (ainfo->priority == GRAPHICS); + res_info->media_hi_priority = (ainfo->priority == MPORT); + if (res_info->video_lwm > 160) + { + res_info->graphics_lwm = 256; + res_info->video_lwm = 128; + res_info->graphics_burst_size = 64; + res_info->video_burst_size = 64; + res_info->graphics_hi_priority = 0; + res_info->media_hi_priority = 0; + ainfo->converged = 0; + return (0); + } + if (res_info->video_lwm > 128) + { + res_info->video_lwm = 128; + } + return (1); + } + else + { + res_info->graphics_lwm = 256; + res_info->video_lwm = 128; + res_info->graphics_burst_size = 64; + res_info->video_burst_size = 64; + res_info->graphics_hi_priority = 0; + res_info->media_hi_priority = 0; + return (0); + } +} +static char nv3_get_param(nv3_fifo_info *res_info, nv3_sim_state * state, nv3_arb_info *ainfo) +{ + int done, g,v, p; + + done = 0; + for (p=0; p < 2; p++) + { + for (g=128 ; g > 32; g= g>> 1) + { + for (v=128; v >=32; v = v>> 1) + { + ainfo->priority = p; + ainfo->gburst_size = g; + ainfo->vburst_size = v; + done = nv3_arb(res_info, state,ainfo); + if (done && (g==128)) + if ((res_info->graphics_lwm + g) > 256) + done = 0; + if (done) + goto Done; + } + } + } + + Done: + return done; +} +static void nv3CalcArbitration +( + nv3_fifo_info * res_info, + nv3_sim_state * state +) +{ + nv3_fifo_info save_info; + nv3_arb_info ainfo; + char res_gr, res_vid; + + ainfo.gr_en = 1; + ainfo.vid_en = state->enable_video; + ainfo.vid_only_once = 0; + ainfo.gr_only_once = 0; + ainfo.gdrain_rate = (int) state->pclk_khz * (state->pix_bpp/8); + ainfo.vdrain_rate = (int) state->pclk_khz * 2; + if (state->video_scale != 0) + ainfo.vdrain_rate = ainfo.vdrain_rate/state->video_scale; + ainfo.mdrain_rate = 33000; + res_info->rtl_values = 0; + if (!state->gr_during_vid && state->enable_video) + { + ainfo.gr_only_once = 1; + ainfo.gr_en = 1; + ainfo.gdrain_rate = 0; + res_vid = nv3_get_param(res_info, state, &ainfo); + res_vid = ainfo.converged; + save_info.video_lwm = res_info->video_lwm; + save_info.video_burst_size = res_info->video_burst_size; + ainfo.vid_en = 1; + ainfo.vid_only_once = 1; + ainfo.gr_en = 1; + ainfo.gdrain_rate = (int) state->pclk_khz * (state->pix_bpp/8); + ainfo.vdrain_rate = 0; + res_gr = nv3_get_param(res_info, state, &ainfo); + res_gr = ainfo.converged; + res_info->video_lwm = save_info.video_lwm; + res_info->video_burst_size = save_info.video_burst_size; + res_info->valid = res_gr & res_vid; + } + else + { + if (!ainfo.gr_en) ainfo.gdrain_rate = 0; + if (!ainfo.vid_en) ainfo.vdrain_rate = 0; + res_gr = nv3_get_param(res_info, state, &ainfo); + res_info->valid = ainfo.converged; + } +} +static void nv3UpdateArbitrationSettings +( + unsigned VClk, + unsigned pixelDepth, + unsigned *burst, + unsigned *lwm, + RIVA_HW_INST *chip +) +{ + nv3_fifo_info fifo_data; + nv3_sim_state sim_data; + unsigned int M, N, P, pll, MClk; + + pll = NV_RD32(&chip->PRAMDAC0[0x00000504/4], 0); + M = (pll >> 0) & 0xFF; N = (pll >> 8) & 0xFF; P = (pll >> 16) & 0x0F; + MClk = (N * chip->CrystalFreqKHz / M) >> P; + sim_data.pix_bpp = (char)pixelDepth; + sim_data.enable_video = 0; + sim_data.enable_mp = 0; + sim_data.video_scale = 1; + sim_data.memory_width = (NV_RD32(&chip->PEXTDEV[0x00000000/4], 0) & 0x10) ? + 128 : 64; + sim_data.memory_width = 128; + + sim_data.mem_latency = 9; + sim_data.mem_aligned = 1; + sim_data.mem_page_miss = 11; + sim_data.gr_during_vid = 0; + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + nv3CalcArbitration(&fifo_data, &sim_data); + if (fifo_data.valid) + { + int b = fifo_data.graphics_burst_size >> 4; + *burst = 0; + while (b >>= 1) + (*burst)++; + *lwm = fifo_data.graphics_lwm >> 3; + } + else + { + *lwm = 0x24; + *burst = 0x2; + } +} +static void nv4CalcArbitration +( + nv4_fifo_info *fifo, + nv4_sim_state *arb +) +{ + int data, pagemiss, cas,width, video_enable, color_key_enable, bpp, align; + int nvclks, mclks, pclks, vpagemiss, crtpagemiss, vbs; + int found, mclk_extra, mclk_loop, cbs, m1, p1; + int mclk_freq, pclk_freq, nvclk_freq, mp_enable; + int us_m, us_n, us_p, video_drain_rate, crtc_drain_rate; + int vpm_us, us_video, vlwm, video_fill_us, cpm_us, us_crt,clwm; + int craw, vraw; + + fifo->valid = 1; + pclk_freq = arb->pclk_khz; + mclk_freq = arb->mclk_khz; + nvclk_freq = arb->nvclk_khz; + pagemiss = arb->mem_page_miss; + cas = arb->mem_latency; + width = arb->memory_width >> 6; + video_enable = arb->enable_video; + color_key_enable = arb->gr_during_vid; + bpp = arb->pix_bpp; + align = arb->mem_aligned; + mp_enable = arb->enable_mp; + clwm = 0; + vlwm = 0; + cbs = 128; + pclks = 2; + nvclks = 2; + nvclks += 2; + nvclks += 1; + mclks = 5; + mclks += 3; + mclks += 1; + mclks += cas; + mclks += 1; + mclks += 1; + mclks += 1; + mclks += 1; + mclk_extra = 3; + nvclks += 2; + nvclks += 1; + nvclks += 1; + nvclks += 1; + if (mp_enable) + mclks+=4; + nvclks += 0; + pclks += 0; + found = 0; + vbs = 0; + while (found != 1) + { + fifo->valid = 1; + found = 1; + mclk_loop = mclks+mclk_extra; + us_m = mclk_loop *1000*1000 / mclk_freq; + us_n = nvclks*1000*1000 / nvclk_freq; + us_p = nvclks*1000*1000 / pclk_freq; + if (video_enable) + { + video_drain_rate = pclk_freq * 2; + crtc_drain_rate = pclk_freq * bpp/8; + vpagemiss = 2; + vpagemiss += 1; + crtpagemiss = 2; + vpm_us = (vpagemiss * pagemiss)*1000*1000/mclk_freq; + if (nvclk_freq * 2 > mclk_freq * width) + video_fill_us = cbs*1000*1000 / 16 / nvclk_freq ; + else + video_fill_us = cbs*1000*1000 / (8 * width) / mclk_freq; + us_video = vpm_us + us_m + us_n + us_p + video_fill_us; + vlwm = us_video * video_drain_rate/(1000*1000); + vlwm++; + vbs = 128; + if (vlwm > 128) vbs = 64; + if (vlwm > (256-64)) vbs = 32; + if (nvclk_freq * 2 > mclk_freq * width) + video_fill_us = vbs *1000*1000/ 16 / nvclk_freq ; + else + video_fill_us = vbs*1000*1000 / (8 * width) / mclk_freq; + cpm_us = crtpagemiss * pagemiss *1000*1000/ mclk_freq; + us_crt = + us_video + +video_fill_us + +cpm_us + +us_m + us_n +us_p + ; + clwm = us_crt * crtc_drain_rate/(1000*1000); + clwm++; + } + else + { + crtc_drain_rate = pclk_freq * bpp/8; + crtpagemiss = 2; + crtpagemiss += 1; + cpm_us = crtpagemiss * pagemiss *1000*1000/ mclk_freq; + us_crt = cpm_us + us_m + us_n + us_p ; + clwm = us_crt * crtc_drain_rate/(1000*1000); + clwm++; + } + m1 = clwm + cbs - 512; + p1 = m1 * pclk_freq / mclk_freq; + p1 = p1 * bpp / 8; + if ((p1 < m1) && (m1 > 0)) + { + fifo->valid = 0; + found = 0; + if (mclk_extra ==0) found = 1; + mclk_extra--; + } + else if (video_enable) + { + if ((clwm > 511) || (vlwm > 255)) + { + fifo->valid = 0; + found = 0; + if (mclk_extra ==0) found = 1; + mclk_extra--; + } + } + else + { + if (clwm > 519) + { + fifo->valid = 0; + found = 0; + if (mclk_extra ==0) found = 1; + mclk_extra--; + } + } + craw = clwm; + vraw = vlwm; + if (clwm < 384) clwm = 384; + if (vlwm < 128) vlwm = 128; + data = (int)(clwm); + fifo->graphics_lwm = data; + fifo->graphics_burst_size = 128; + data = (int)((vlwm+15)); + fifo->video_lwm = data; + fifo->video_burst_size = vbs; + } +} +static void nv4UpdateArbitrationSettings +( + unsigned VClk, + unsigned pixelDepth, + unsigned *burst, + unsigned *lwm, + RIVA_HW_INST *chip +) +{ + nv4_fifo_info fifo_data; + nv4_sim_state sim_data; + unsigned int M, N, P, pll, MClk, NVClk, cfg1; + + pll = NV_RD32(&chip->PRAMDAC0[0x00000504/4], 0); + M = (pll >> 0) & 0xFF; N = (pll >> 8) & 0xFF; P = (pll >> 16) & 0x0F; + MClk = (N * chip->CrystalFreqKHz / M) >> P; + pll = NV_RD32(&chip->PRAMDAC0[0x00000500/4], 0); + M = (pll >> 0) & 0xFF; N = (pll >> 8) & 0xFF; P = (pll >> 16) & 0x0F; + NVClk = (N * chip->CrystalFreqKHz / M) >> P; + cfg1 = NV_RD32(&chip->PFB[0x00000204/4], 0); + sim_data.pix_bpp = (char)pixelDepth; + sim_data.enable_video = 0; + sim_data.enable_mp = 0; + sim_data.memory_width = (NV_RD32(&chip->PEXTDEV[0x00000000/4], 0) & 0x10) ? + 128 : 64; + sim_data.mem_latency = (char)cfg1 & 0x0F; + sim_data.mem_aligned = 1; + sim_data.mem_page_miss = (char)(((cfg1 >> 4) &0x0F) + ((cfg1 >> 31) & 0x01)); + sim_data.gr_during_vid = 0; + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + sim_data.nvclk_khz = NVClk; + nv4CalcArbitration(&fifo_data, &sim_data); + if (fifo_data.valid) + { + int b = fifo_data.graphics_burst_size >> 4; + *burst = 0; + while (b >>= 1) + (*burst)++; + *lwm = fifo_data.graphics_lwm >> 3; + } +} +static void nv10CalcArbitration +( + nv10_fifo_info *fifo, + nv10_sim_state *arb +) +{ + int data, pagemiss, cas,width, video_enable, color_key_enable, bpp, align; + int nvclks, mclks, pclks, vpagemiss, crtpagemiss, vbs; + int nvclk_fill, us_extra; + int found, mclk_extra, mclk_loop, cbs, m1; + int mclk_freq, pclk_freq, nvclk_freq, mp_enable; + int us_m, us_m_min, us_n, us_p, video_drain_rate, crtc_drain_rate; + int vus_m, vus_n, vus_p; + int vpm_us, us_video, vlwm, cpm_us, us_crt,clwm; + int clwm_rnd_down; + int craw, m2us, us_pipe, us_pipe_min, vus_pipe, p1clk, p2; + int pclks_2_top_fifo, min_mclk_extra; + int us_min_mclk_extra; + + fifo->valid = 1; + pclk_freq = arb->pclk_khz; /* freq in KHz */ + mclk_freq = arb->mclk_khz; + nvclk_freq = arb->nvclk_khz; + pagemiss = arb->mem_page_miss; + cas = arb->mem_latency; + width = arb->memory_width/64; + video_enable = arb->enable_video; + color_key_enable = arb->gr_during_vid; + bpp = arb->pix_bpp; + align = arb->mem_aligned; + mp_enable = arb->enable_mp; + clwm = 0; + vlwm = 1024; + + cbs = 512; + vbs = 512; + + pclks = 4; /* lwm detect. */ + + nvclks = 3; /* lwm -> sync. */ + nvclks += 2; /* fbi bus cycles (1 req + 1 busy) */ + + mclks = 1; /* 2 edge sync. may be very close to edge so just put one. */ + + mclks += 1; /* arb_hp_req */ + mclks += 5; /* ap_hp_req tiling pipeline */ + + mclks += 2; /* tc_req latency fifo */ + mclks += 2; /* fb_cas_n_ memory request to fbio block */ + mclks += 7; /* sm_d_rdv data returned from fbio block */ + + /* fb.rd.d.Put_gc need to accumulate 256 bits for read */ + if (arb->memory_type == 0) + if (arb->memory_width == 64) /* 64 bit bus */ + mclks += 4; + else + mclks += 2; + else + if (arb->memory_width == 64) /* 64 bit bus */ + mclks += 2; + else + mclks += 1; + + if ((!video_enable) && (arb->memory_width == 128)) + { + mclk_extra = (bpp == 32) ? 31 : 42; /* Margin of error */ + min_mclk_extra = 17; + } + else + { + mclk_extra = (bpp == 32) ? 8 : 4; /* Margin of error */ + /* mclk_extra = 4; */ /* Margin of error */ + min_mclk_extra = 18; + } + + nvclks += 1; /* 2 edge sync. may be very close to edge so just put one. */ + nvclks += 1; /* fbi_d_rdv_n */ + nvclks += 1; /* Fbi_d_rdata */ + nvclks += 1; /* crtfifo load */ + + if(mp_enable) + mclks+=4; /* Mp can get in with a burst of 8. */ + /* Extra clocks determined by heuristics */ + + nvclks += 0; + pclks += 0; + found = 0; + while(found != 1) { + fifo->valid = 1; + found = 1; + mclk_loop = mclks+mclk_extra; + us_m = mclk_loop *1000*1000 / mclk_freq; /* Mclk latency in us */ + us_m_min = mclks * 1000*1000 / mclk_freq; /* Minimum Mclk latency in us */ + us_min_mclk_extra = min_mclk_extra *1000*1000 / mclk_freq; + us_n = nvclks*1000*1000 / nvclk_freq;/* nvclk latency in us */ + us_p = pclks*1000*1000 / pclk_freq;/* nvclk latency in us */ + us_pipe = us_m + us_n + us_p; + us_pipe_min = us_m_min + us_n + us_p; + us_extra = 0; + + vus_m = mclk_loop *1000*1000 / mclk_freq; /* Mclk latency in us */ + vus_n = (4)*1000*1000 / nvclk_freq;/* nvclk latency in us */ + vus_p = 0*1000*1000 / pclk_freq;/* pclk latency in us */ + vus_pipe = vus_m + vus_n + vus_p; + + if(video_enable) { + video_drain_rate = pclk_freq * 4; /* MB/s */ + crtc_drain_rate = pclk_freq * bpp/8; /* MB/s */ + + vpagemiss = 1; /* self generating page miss */ + vpagemiss += 1; /* One higher priority before */ + + crtpagemiss = 2; /* self generating page miss */ + if(mp_enable) + crtpagemiss += 1; /* if MA0 conflict */ + + vpm_us = (vpagemiss * pagemiss)*1000*1000/mclk_freq; + + us_video = vpm_us + vus_m; /* Video has separate read return path */ + + cpm_us = crtpagemiss * pagemiss *1000*1000/ mclk_freq; + us_crt = + us_video /* Wait for video */ + +cpm_us /* CRT Page miss */ + +us_m + us_n +us_p /* other latency */ + ; + + clwm = us_crt * crtc_drain_rate/(1000*1000); + clwm++; /* fixed point <= float_point - 1. Fixes that */ + } else { + crtc_drain_rate = pclk_freq * bpp/8; /* bpp * pclk/8 */ + + crtpagemiss = 1; /* self generating page miss */ + crtpagemiss += 1; /* MA0 page miss */ + if(mp_enable) + crtpagemiss += 1; /* if MA0 conflict */ + cpm_us = crtpagemiss * pagemiss *1000*1000/ mclk_freq; + us_crt = cpm_us + us_m + us_n + us_p ; + clwm = us_crt * crtc_drain_rate/(1000*1000); + clwm++; /* fixed point <= float_point - 1. Fixes that */ + + /* + // + // Another concern, only for high pclks so don't do this + // with video: + // What happens if the latency to fetch the cbs is so large that + // fifo empties. In that case we need to have an alternate clwm value + // based off the total burst fetch + // + us_crt = (cbs * 1000 * 1000)/ (8*width)/mclk_freq ; + us_crt = us_crt + us_m + us_n + us_p + (4 * 1000 * 1000)/mclk_freq; + clwm_mt = us_crt * crtc_drain_rate/(1000*1000); + clwm_mt ++; + if(clwm_mt > clwm) + clwm = clwm_mt; + */ + /* Finally, a heuristic check when width == 64 bits */ + if(width == 1){ + nvclk_fill = nvclk_freq * 8; + if(crtc_drain_rate * 100 >= nvclk_fill * 102) + clwm = 0xfff; /*Large number to fail */ + + else if(crtc_drain_rate * 100 >= nvclk_fill * 98) { + clwm = 1024; + cbs = 512; + us_extra = (cbs * 1000 * 1000)/ (8*width)/mclk_freq ; + } + } + } + + + /* + Overfill check: + + */ + + clwm_rnd_down = ((int)clwm/8)*8; + if (clwm_rnd_down < clwm) + clwm += 8; + + m1 = clwm + cbs - 1024; /* Amount of overfill */ + m2us = us_pipe_min + us_min_mclk_extra; + pclks_2_top_fifo = (1024-clwm)/(8*width); + + /* pclk cycles to drain */ + p1clk = m2us * pclk_freq/(1000*1000); + p2 = p1clk * bpp / 8; /* bytes drained. */ + + if((p2 < m1) && (m1 > 0)) { + fifo->valid = 0; + found = 0; + if(min_mclk_extra == 0) { + if(cbs <= 32) { + found = 1; /* Can't adjust anymore! */ + } else { + cbs = cbs/2; /* reduce the burst size */ + } + } else { + min_mclk_extra--; + } + } else { + if (clwm > 1023){ /* Have some margin */ + fifo->valid = 0; + found = 0; + if(min_mclk_extra == 0) + found = 1; /* Can't adjust anymore! */ + else + min_mclk_extra--; + } + } + craw = clwm; + + if(clwm < (1024-cbs+8)) clwm = 1024-cbs+8; + data = (int)(clwm); + /* printf("CRT LWM: %f bytes, prog: 0x%x, bs: 256\n", clwm, data ); */ + fifo->graphics_lwm = data; fifo->graphics_burst_size = cbs; + + /* printf("VID LWM: %f bytes, prog: 0x%x, bs: %d\n, ", vlwm, data, vbs ); */ + fifo->video_lwm = 1024; fifo->video_burst_size = 512; + } +} +static void nv10UpdateArbitrationSettings +( + unsigned VClk, + unsigned pixelDepth, + unsigned *burst, + unsigned *lwm, + RIVA_HW_INST *chip +) +{ + nv10_fifo_info fifo_data; + nv10_sim_state sim_data; + unsigned int M, N, P, pll, MClk, NVClk, cfg1; + + pll = NV_RD32(&chip->PRAMDAC0[0x00000504/4], 0); + M = (pll >> 0) & 0xFF; N = (pll >> 8) & 0xFF; P = (pll >> 16) & 0x0F; + MClk = (N * chip->CrystalFreqKHz / M) >> P; + pll = NV_RD32(&chip->PRAMDAC0[0x00000500/4], 0); + M = (pll >> 0) & 0xFF; N = (pll >> 8) & 0xFF; P = (pll >> 16) & 0x0F; + NVClk = (N * chip->CrystalFreqKHz / M) >> P; + cfg1 = NV_RD32(&chip->PFB[0x00000204/4], 0); + sim_data.pix_bpp = (char)pixelDepth; + sim_data.enable_video = 0; + sim_data.enable_mp = 0; + sim_data.memory_type = (NV_RD32(&chip->PFB[0x00000200/4], 0) & 0x01) ? + 1 : 0; + sim_data.memory_width = (NV_RD32(&chip->PEXTDEV[0x00000000/4], 0) & 0x10) ? + 128 : 64; + sim_data.mem_latency = (char)cfg1 & 0x0F; + sim_data.mem_aligned = 1; + sim_data.mem_page_miss = (char)(((cfg1 >> 4) &0x0F) + ((cfg1 >> 31) & 0x01)); + sim_data.gr_during_vid = 0; + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + sim_data.nvclk_khz = NVClk; + nv10CalcArbitration(&fifo_data, &sim_data); + if (fifo_data.valid) + { + int b = fifo_data.graphics_burst_size >> 4; + *burst = 0; + while (b >>= 1) + (*burst)++; + *lwm = fifo_data.graphics_lwm >> 3; + } +} + +static void nForceUpdateArbitrationSettings +( + unsigned VClk, + unsigned pixelDepth, + unsigned *burst, + unsigned *lwm, + RIVA_HW_INST *chip +) +{ + nv10_fifo_info fifo_data; + nv10_sim_state sim_data; + unsigned int M, N, P, pll, MClk, NVClk; + unsigned int uMClkPostDiv; + struct pci_dev *dev; + + dev = pci_get_bus_and_slot(0, 3); + pci_read_config_dword(dev, 0x6C, &uMClkPostDiv); + pci_dev_put(dev); + uMClkPostDiv = (uMClkPostDiv >> 8) & 0xf; + + if(!uMClkPostDiv) uMClkPostDiv = 4; + MClk = 400000 / uMClkPostDiv; + + pll = NV_RD32(&chip->PRAMDAC0[0x00000500/4], 0); + M = (pll >> 0) & 0xFF; N = (pll >> 8) & 0xFF; P = (pll >> 16) & 0x0F; + NVClk = (N * chip->CrystalFreqKHz / M) >> P; + sim_data.pix_bpp = (char)pixelDepth; + sim_data.enable_video = 0; + sim_data.enable_mp = 0; + + dev = pci_get_bus_and_slot(0, 1); + pci_read_config_dword(dev, 0x7C, &sim_data.memory_type); + pci_dev_put(dev); + sim_data.memory_type = (sim_data.memory_type >> 12) & 1; + + sim_data.memory_width = 64; + sim_data.mem_latency = 3; + sim_data.mem_aligned = 1; + sim_data.mem_page_miss = 10; + sim_data.gr_during_vid = 0; + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + sim_data.nvclk_khz = NVClk; + nv10CalcArbitration(&fifo_data, &sim_data); + if (fifo_data.valid) + { + int b = fifo_data.graphics_burst_size >> 4; + *burst = 0; + while (b >>= 1) + (*burst)++; + *lwm = fifo_data.graphics_lwm >> 3; + } +} + +/****************************************************************************\ +* * +* RIVA Mode State Routines * +* * +\****************************************************************************/ + +/* + * Calculate the Video Clock parameters for the PLL. + */ +static int CalcVClock +( + int clockIn, + int *clockOut, + int *mOut, + int *nOut, + int *pOut, + RIVA_HW_INST *chip +) +{ + unsigned lowM, highM, highP; + unsigned DeltaNew, DeltaOld; + unsigned VClk, Freq; + unsigned M, N, P; + + DeltaOld = 0xFFFFFFFF; + + VClk = (unsigned)clockIn; + + if (chip->CrystalFreqKHz == 13500) + { + lowM = 7; + highM = 13 - (chip->Architecture == NV_ARCH_03); + } + else + { + lowM = 8; + highM = 14 - (chip->Architecture == NV_ARCH_03); + } + + highP = 4 - (chip->Architecture == NV_ARCH_03); + for (P = 0; P <= highP; P ++) + { + Freq = VClk << P; + if ((Freq >= 128000) && (Freq <= chip->MaxVClockFreqKHz)) + { + for (M = lowM; M <= highM; M++) + { + N = (VClk << P) * M / chip->CrystalFreqKHz; + if(N <= 255) { + Freq = (chip->CrystalFreqKHz * N / M) >> P; + if (Freq > VClk) + DeltaNew = Freq - VClk; + else + DeltaNew = VClk - Freq; + if (DeltaNew < DeltaOld) + { + *mOut = M; + *nOut = N; + *pOut = P; + *clockOut = Freq; + DeltaOld = DeltaNew; + } + } + } + } + } + + /* non-zero: M/N/P/clock values assigned. zero: error (not set) */ + return (DeltaOld != 0xFFFFFFFF); +} +/* + * Calculate extended mode parameters (SVGA) and save in a + * mode state structure. + */ +int CalcStateExt +( + RIVA_HW_INST *chip, + RIVA_HW_STATE *state, + int bpp, + int width, + int hDisplaySize, + int height, + int dotClock +) +{ + int pixelDepth; + int uninitialized_var(VClk),uninitialized_var(m), + uninitialized_var(n), uninitialized_var(p); + + /* + * Save mode parameters. + */ + state->bpp = bpp; /* this is not bitsPerPixel, it's 8,15,16,32 */ + state->width = width; + state->height = height; + /* + * Extended RIVA registers. + */ + pixelDepth = (bpp + 1)/8; + if (!CalcVClock(dotClock, &VClk, &m, &n, &p, chip)) + return -EINVAL; + + switch (chip->Architecture) + { + case NV_ARCH_03: + nv3UpdateArbitrationSettings(VClk, + pixelDepth * 8, + &(state->arbitration0), + &(state->arbitration1), + chip); + state->cursor0 = 0x00; + state->cursor1 = 0x78; + state->cursor2 = 0x00000000; + state->pllsel = 0x10010100; + state->config = ((width + 31)/32) + | (((pixelDepth > 2) ? 3 : pixelDepth) << 8) + | 0x1000; + state->general = 0x00100100; + state->repaint1 = hDisplaySize < 1280 ? 0x06 : 0x02; + break; + case NV_ARCH_04: + nv4UpdateArbitrationSettings(VClk, + pixelDepth * 8, + &(state->arbitration0), + &(state->arbitration1), + chip); + state->cursor0 = 0x00; + state->cursor1 = 0xFC; + state->cursor2 = 0x00000000; + state->pllsel = 0x10000700; + state->config = 0x00001114; + state->general = bpp == 16 ? 0x00101100 : 0x00100100; + state->repaint1 = hDisplaySize < 1280 ? 0x04 : 0x00; + break; + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + if((chip->Chipset == NV_CHIP_IGEFORCE2) || + (chip->Chipset == NV_CHIP_0x01F0)) + { + nForceUpdateArbitrationSettings(VClk, + pixelDepth * 8, + &(state->arbitration0), + &(state->arbitration1), + chip); + } else { + nv10UpdateArbitrationSettings(VClk, + pixelDepth * 8, + &(state->arbitration0), + &(state->arbitration1), + chip); + } + state->cursor0 = 0x80 | (chip->CursorStart >> 17); + state->cursor1 = (chip->CursorStart >> 11) << 2; + state->cursor2 = chip->CursorStart >> 24; + state->pllsel = 0x10000700; + state->config = NV_RD32(&chip->PFB[0x00000200/4], 0); + state->general = bpp == 16 ? 0x00101100 : 0x00100100; + state->repaint1 = hDisplaySize < 1280 ? 0x04 : 0x00; + break; + } + + /* Paul Richards: below if block borks things in kernel for some reason */ + /* Tony: Below is needed to set hardware in DirectColor */ + if((bpp != 8) && (chip->Architecture != NV_ARCH_03)) + state->general |= 0x00000030; + + state->vpll = (p << 16) | (n << 8) | m; + state->repaint0 = (((width/8)*pixelDepth) & 0x700) >> 3; + state->pixel = pixelDepth > 2 ? 3 : pixelDepth; + state->offset0 = + state->offset1 = + state->offset2 = + state->offset3 = 0; + state->pitch0 = + state->pitch1 = + state->pitch2 = + state->pitch3 = pixelDepth * width; + + return 0; +} +/* + * Load fixed function state and pre-calculated/stored state. + */ +#if 0 +#define LOAD_FIXED_STATE(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev)/8; i++) \ + chip->dev[tbl##Table##dev[i][0]] = tbl##Table##dev[i][1] +#define LOAD_FIXED_STATE_8BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_8BPP)/8; i++) \ + chip->dev[tbl##Table##dev##_8BPP[i][0]] = tbl##Table##dev##_8BPP[i][1] +#define LOAD_FIXED_STATE_15BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_15BPP)/8; i++) \ + chip->dev[tbl##Table##dev##_15BPP[i][0]] = tbl##Table##dev##_15BPP[i][1] +#define LOAD_FIXED_STATE_16BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_16BPP)/8; i++) \ + chip->dev[tbl##Table##dev##_16BPP[i][0]] = tbl##Table##dev##_16BPP[i][1] +#define LOAD_FIXED_STATE_32BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_32BPP)/8; i++) \ + chip->dev[tbl##Table##dev##_32BPP[i][0]] = tbl##Table##dev##_32BPP[i][1] +#endif + +#define LOAD_FIXED_STATE(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev)/8; i++) \ + NV_WR32(&chip->dev[tbl##Table##dev[i][0]], 0, tbl##Table##dev[i][1]) +#define LOAD_FIXED_STATE_8BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_8BPP)/8; i++) \ + NV_WR32(&chip->dev[tbl##Table##dev##_8BPP[i][0]], 0, tbl##Table##dev##_8BPP[i][1]) +#define LOAD_FIXED_STATE_15BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_15BPP)/8; i++) \ + NV_WR32(&chip->dev[tbl##Table##dev##_15BPP[i][0]], 0, tbl##Table##dev##_15BPP[i][1]) +#define LOAD_FIXED_STATE_16BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_16BPP)/8; i++) \ + NV_WR32(&chip->dev[tbl##Table##dev##_16BPP[i][0]], 0, tbl##Table##dev##_16BPP[i][1]) +#define LOAD_FIXED_STATE_32BPP(tbl,dev) \ + for (i = 0; i < sizeof(tbl##Table##dev##_32BPP)/8; i++) \ + NV_WR32(&chip->dev[tbl##Table##dev##_32BPP[i][0]], 0, tbl##Table##dev##_32BPP[i][1]) + +static void UpdateFifoState +( + RIVA_HW_INST *chip +) +{ + int i; + + switch (chip->Architecture) + { + case NV_ARCH_04: + LOAD_FIXED_STATE(nv4,FIFO); + chip->Tri03 = NULL; + chip->Tri05 = (RivaTexturedTriangle05 __iomem *)&(chip->FIFO[0x0000E000/4]); + break; + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + /* + * Initialize state for the RivaTriangle3D05 routines. + */ + LOAD_FIXED_STATE(nv10tri05,PGRAPH); + LOAD_FIXED_STATE(nv10,FIFO); + chip->Tri03 = NULL; + chip->Tri05 = (RivaTexturedTriangle05 __iomem *)&(chip->FIFO[0x0000E000/4]); + break; + } +} +static void LoadStateExt +( + RIVA_HW_INST *chip, + RIVA_HW_STATE *state +) +{ + int i; + + /* + * Load HW fixed function state. + */ + LOAD_FIXED_STATE(Riva,PMC); + LOAD_FIXED_STATE(Riva,PTIMER); + switch (chip->Architecture) + { + case NV_ARCH_03: + /* + * Make sure frame buffer config gets set before loading PRAMIN. + */ + NV_WR32(chip->PFB, 0x00000200, state->config); + LOAD_FIXED_STATE(nv3,PFIFO); + LOAD_FIXED_STATE(nv3,PRAMIN); + LOAD_FIXED_STATE(nv3,PGRAPH); + switch (state->bpp) + { + case 15: + case 16: + LOAD_FIXED_STATE_15BPP(nv3,PRAMIN); + LOAD_FIXED_STATE_15BPP(nv3,PGRAPH); + chip->Tri03 = (RivaTexturedTriangle03 __iomem *)&(chip->FIFO[0x0000E000/4]); + break; + case 24: + case 32: + LOAD_FIXED_STATE_32BPP(nv3,PRAMIN); + LOAD_FIXED_STATE_32BPP(nv3,PGRAPH); + chip->Tri03 = NULL; + break; + case 8: + default: + LOAD_FIXED_STATE_8BPP(nv3,PRAMIN); + LOAD_FIXED_STATE_8BPP(nv3,PGRAPH); + chip->Tri03 = NULL; + break; + } + for (i = 0x00000; i < 0x00800; i++) + NV_WR32(&chip->PRAMIN[0x00000502 + i], 0, (i << 12) | 0x03); + NV_WR32(chip->PGRAPH, 0x00000630, state->offset0); + NV_WR32(chip->PGRAPH, 0x00000634, state->offset1); + NV_WR32(chip->PGRAPH, 0x00000638, state->offset2); + NV_WR32(chip->PGRAPH, 0x0000063C, state->offset3); + NV_WR32(chip->PGRAPH, 0x00000650, state->pitch0); + NV_WR32(chip->PGRAPH, 0x00000654, state->pitch1); + NV_WR32(chip->PGRAPH, 0x00000658, state->pitch2); + NV_WR32(chip->PGRAPH, 0x0000065C, state->pitch3); + break; + case NV_ARCH_04: + /* + * Make sure frame buffer config gets set before loading PRAMIN. + */ + NV_WR32(chip->PFB, 0x00000200, state->config); + LOAD_FIXED_STATE(nv4,PFIFO); + LOAD_FIXED_STATE(nv4,PRAMIN); + LOAD_FIXED_STATE(nv4,PGRAPH); + switch (state->bpp) + { + case 15: + LOAD_FIXED_STATE_15BPP(nv4,PRAMIN); + LOAD_FIXED_STATE_15BPP(nv4,PGRAPH); + chip->Tri03 = (RivaTexturedTriangle03 __iomem *)&(chip->FIFO[0x0000E000/4]); + break; + case 16: + LOAD_FIXED_STATE_16BPP(nv4,PRAMIN); + LOAD_FIXED_STATE_16BPP(nv4,PGRAPH); + chip->Tri03 = (RivaTexturedTriangle03 __iomem *)&(chip->FIFO[0x0000E000/4]); + break; + case 24: + case 32: + LOAD_FIXED_STATE_32BPP(nv4,PRAMIN); + LOAD_FIXED_STATE_32BPP(nv4,PGRAPH); + chip->Tri03 = NULL; + break; + case 8: + default: + LOAD_FIXED_STATE_8BPP(nv4,PRAMIN); + LOAD_FIXED_STATE_8BPP(nv4,PGRAPH); + chip->Tri03 = NULL; + break; + } + NV_WR32(chip->PGRAPH, 0x00000640, state->offset0); + NV_WR32(chip->PGRAPH, 0x00000644, state->offset1); + NV_WR32(chip->PGRAPH, 0x00000648, state->offset2); + NV_WR32(chip->PGRAPH, 0x0000064C, state->offset3); + NV_WR32(chip->PGRAPH, 0x00000670, state->pitch0); + NV_WR32(chip->PGRAPH, 0x00000674, state->pitch1); + NV_WR32(chip->PGRAPH, 0x00000678, state->pitch2); + NV_WR32(chip->PGRAPH, 0x0000067C, state->pitch3); + break; + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + if(chip->twoHeads) { + VGA_WR08(chip->PCIO, 0x03D4, 0x44); + VGA_WR08(chip->PCIO, 0x03D5, state->crtcOwner); + chip->LockUnlock(chip, 0); + } + + LOAD_FIXED_STATE(nv10,PFIFO); + LOAD_FIXED_STATE(nv10,PRAMIN); + LOAD_FIXED_STATE(nv10,PGRAPH); + switch (state->bpp) + { + case 15: + LOAD_FIXED_STATE_15BPP(nv10,PRAMIN); + LOAD_FIXED_STATE_15BPP(nv10,PGRAPH); + chip->Tri03 = (RivaTexturedTriangle03 __iomem *)&(chip->FIFO[0x0000E000/4]); + break; + case 16: + LOAD_FIXED_STATE_16BPP(nv10,PRAMIN); + LOAD_FIXED_STATE_16BPP(nv10,PGRAPH); + chip->Tri03 = (RivaTexturedTriangle03 __iomem *)&(chip->FIFO[0x0000E000/4]); + break; + case 24: + case 32: + LOAD_FIXED_STATE_32BPP(nv10,PRAMIN); + LOAD_FIXED_STATE_32BPP(nv10,PGRAPH); + chip->Tri03 = NULL; + break; + case 8: + default: + LOAD_FIXED_STATE_8BPP(nv10,PRAMIN); + LOAD_FIXED_STATE_8BPP(nv10,PGRAPH); + chip->Tri03 = NULL; + break; + } + + if(chip->Architecture == NV_ARCH_10) { + NV_WR32(chip->PGRAPH, 0x00000640, state->offset0); + NV_WR32(chip->PGRAPH, 0x00000644, state->offset1); + NV_WR32(chip->PGRAPH, 0x00000648, state->offset2); + NV_WR32(chip->PGRAPH, 0x0000064C, state->offset3); + NV_WR32(chip->PGRAPH, 0x00000670, state->pitch0); + NV_WR32(chip->PGRAPH, 0x00000674, state->pitch1); + NV_WR32(chip->PGRAPH, 0x00000678, state->pitch2); + NV_WR32(chip->PGRAPH, 0x0000067C, state->pitch3); + NV_WR32(chip->PGRAPH, 0x00000680, state->pitch3); + } else { + NV_WR32(chip->PGRAPH, 0x00000820, state->offset0); + NV_WR32(chip->PGRAPH, 0x00000824, state->offset1); + NV_WR32(chip->PGRAPH, 0x00000828, state->offset2); + NV_WR32(chip->PGRAPH, 0x0000082C, state->offset3); + NV_WR32(chip->PGRAPH, 0x00000850, state->pitch0); + NV_WR32(chip->PGRAPH, 0x00000854, state->pitch1); + NV_WR32(chip->PGRAPH, 0x00000858, state->pitch2); + NV_WR32(chip->PGRAPH, 0x0000085C, state->pitch3); + NV_WR32(chip->PGRAPH, 0x00000860, state->pitch3); + NV_WR32(chip->PGRAPH, 0x00000864, state->pitch3); + NV_WR32(chip->PGRAPH, 0x000009A4, NV_RD32(chip->PFB, 0x00000200)); + NV_WR32(chip->PGRAPH, 0x000009A8, NV_RD32(chip->PFB, 0x00000204)); + } + if(chip->twoHeads) { + NV_WR32(chip->PCRTC0, 0x00000860, state->head); + NV_WR32(chip->PCRTC0, 0x00002860, state->head2); + } + NV_WR32(chip->PRAMDAC, 0x00000404, NV_RD32(chip->PRAMDAC, 0x00000404) | (1 << 25)); + + NV_WR32(chip->PMC, 0x00008704, 1); + NV_WR32(chip->PMC, 0x00008140, 0); + NV_WR32(chip->PMC, 0x00008920, 0); + NV_WR32(chip->PMC, 0x00008924, 0); + NV_WR32(chip->PMC, 0x00008908, 0x01ffffff); + NV_WR32(chip->PMC, 0x0000890C, 0x01ffffff); + NV_WR32(chip->PMC, 0x00001588, 0); + + NV_WR32(chip->PFB, 0x00000240, 0); + NV_WR32(chip->PFB, 0x00000250, 0); + NV_WR32(chip->PFB, 0x00000260, 0); + NV_WR32(chip->PFB, 0x00000270, 0); + NV_WR32(chip->PFB, 0x00000280, 0); + NV_WR32(chip->PFB, 0x00000290, 0); + NV_WR32(chip->PFB, 0x000002A0, 0); + NV_WR32(chip->PFB, 0x000002B0, 0); + + NV_WR32(chip->PGRAPH, 0x00000B00, NV_RD32(chip->PFB, 0x00000240)); + NV_WR32(chip->PGRAPH, 0x00000B04, NV_RD32(chip->PFB, 0x00000244)); + NV_WR32(chip->PGRAPH, 0x00000B08, NV_RD32(chip->PFB, 0x00000248)); + NV_WR32(chip->PGRAPH, 0x00000B0C, NV_RD32(chip->PFB, 0x0000024C)); + NV_WR32(chip->PGRAPH, 0x00000B10, NV_RD32(chip->PFB, 0x00000250)); + NV_WR32(chip->PGRAPH, 0x00000B14, NV_RD32(chip->PFB, 0x00000254)); + NV_WR32(chip->PGRAPH, 0x00000B18, NV_RD32(chip->PFB, 0x00000258)); + NV_WR32(chip->PGRAPH, 0x00000B1C, NV_RD32(chip->PFB, 0x0000025C)); + NV_WR32(chip->PGRAPH, 0x00000B20, NV_RD32(chip->PFB, 0x00000260)); + NV_WR32(chip->PGRAPH, 0x00000B24, NV_RD32(chip->PFB, 0x00000264)); + NV_WR32(chip->PGRAPH, 0x00000B28, NV_RD32(chip->PFB, 0x00000268)); + NV_WR32(chip->PGRAPH, 0x00000B2C, NV_RD32(chip->PFB, 0x0000026C)); + NV_WR32(chip->PGRAPH, 0x00000B30, NV_RD32(chip->PFB, 0x00000270)); + NV_WR32(chip->PGRAPH, 0x00000B34, NV_RD32(chip->PFB, 0x00000274)); + NV_WR32(chip->PGRAPH, 0x00000B38, NV_RD32(chip->PFB, 0x00000278)); + NV_WR32(chip->PGRAPH, 0x00000B3C, NV_RD32(chip->PFB, 0x0000027C)); + NV_WR32(chip->PGRAPH, 0x00000B40, NV_RD32(chip->PFB, 0x00000280)); + NV_WR32(chip->PGRAPH, 0x00000B44, NV_RD32(chip->PFB, 0x00000284)); + NV_WR32(chip->PGRAPH, 0x00000B48, NV_RD32(chip->PFB, 0x00000288)); + NV_WR32(chip->PGRAPH, 0x00000B4C, NV_RD32(chip->PFB, 0x0000028C)); + NV_WR32(chip->PGRAPH, 0x00000B50, NV_RD32(chip->PFB, 0x00000290)); + NV_WR32(chip->PGRAPH, 0x00000B54, NV_RD32(chip->PFB, 0x00000294)); + NV_WR32(chip->PGRAPH, 0x00000B58, NV_RD32(chip->PFB, 0x00000298)); + NV_WR32(chip->PGRAPH, 0x00000B5C, NV_RD32(chip->PFB, 0x0000029C)); + NV_WR32(chip->PGRAPH, 0x00000B60, NV_RD32(chip->PFB, 0x000002A0)); + NV_WR32(chip->PGRAPH, 0x00000B64, NV_RD32(chip->PFB, 0x000002A4)); + NV_WR32(chip->PGRAPH, 0x00000B68, NV_RD32(chip->PFB, 0x000002A8)); + NV_WR32(chip->PGRAPH, 0x00000B6C, NV_RD32(chip->PFB, 0x000002AC)); + NV_WR32(chip->PGRAPH, 0x00000B70, NV_RD32(chip->PFB, 0x000002B0)); + NV_WR32(chip->PGRAPH, 0x00000B74, NV_RD32(chip->PFB, 0x000002B4)); + NV_WR32(chip->PGRAPH, 0x00000B78, NV_RD32(chip->PFB, 0x000002B8)); + NV_WR32(chip->PGRAPH, 0x00000B7C, NV_RD32(chip->PFB, 0x000002BC)); + NV_WR32(chip->PGRAPH, 0x00000F40, 0x10000000); + NV_WR32(chip->PGRAPH, 0x00000F44, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00000040); + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000008); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00000200); + for (i = 0; i < (3*16); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00000040); + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00000800); + for (i = 0; i < (16*16); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F40, 0x30000000); + NV_WR32(chip->PGRAPH, 0x00000F44, 0x00000004); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00006400); + for (i = 0; i < (59*4); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00006800); + for (i = 0; i < (47*4); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00006C00); + for (i = 0; i < (3*4); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00007000); + for (i = 0; i < (19*4); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00007400); + for (i = 0; i < (12*4); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00007800); + for (i = 0; i < (12*4); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00004400); + for (i = 0; i < (8*4); i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00000000); + for (i = 0; i < 16; i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + NV_WR32(chip->PGRAPH, 0x00000F50, 0x00000040); + for (i = 0; i < 4; i++) + NV_WR32(chip->PGRAPH, 0x00000F54, 0x00000000); + + NV_WR32(chip->PCRTC, 0x00000810, state->cursorConfig); + + if(chip->flatPanel) { + if((chip->Chipset & 0x0ff0) == 0x0110) { + NV_WR32(chip->PRAMDAC, 0x0528, state->dither); + } else + if((chip->Chipset & 0x0ff0) >= 0x0170) { + NV_WR32(chip->PRAMDAC, 0x083C, state->dither); + } + + VGA_WR08(chip->PCIO, 0x03D4, 0x53); + VGA_WR08(chip->PCIO, 0x03D5, 0); + VGA_WR08(chip->PCIO, 0x03D4, 0x54); + VGA_WR08(chip->PCIO, 0x03D5, 0); + VGA_WR08(chip->PCIO, 0x03D4, 0x21); + VGA_WR08(chip->PCIO, 0x03D5, 0xfa); + } + + VGA_WR08(chip->PCIO, 0x03D4, 0x41); + VGA_WR08(chip->PCIO, 0x03D5, state->extra); + } + LOAD_FIXED_STATE(Riva,FIFO); + UpdateFifoState(chip); + /* + * Load HW mode state. + */ + VGA_WR08(chip->PCIO, 0x03D4, 0x19); + VGA_WR08(chip->PCIO, 0x03D5, state->repaint0); + VGA_WR08(chip->PCIO, 0x03D4, 0x1A); + VGA_WR08(chip->PCIO, 0x03D5, state->repaint1); + VGA_WR08(chip->PCIO, 0x03D4, 0x25); + VGA_WR08(chip->PCIO, 0x03D5, state->screen); + VGA_WR08(chip->PCIO, 0x03D4, 0x28); + VGA_WR08(chip->PCIO, 0x03D5, state->pixel); + VGA_WR08(chip->PCIO, 0x03D4, 0x2D); + VGA_WR08(chip->PCIO, 0x03D5, state->horiz); + VGA_WR08(chip->PCIO, 0x03D4, 0x1B); + VGA_WR08(chip->PCIO, 0x03D5, state->arbitration0); + VGA_WR08(chip->PCIO, 0x03D4, 0x20); + VGA_WR08(chip->PCIO, 0x03D5, state->arbitration1); + VGA_WR08(chip->PCIO, 0x03D4, 0x30); + VGA_WR08(chip->PCIO, 0x03D5, state->cursor0); + VGA_WR08(chip->PCIO, 0x03D4, 0x31); + VGA_WR08(chip->PCIO, 0x03D5, state->cursor1); + VGA_WR08(chip->PCIO, 0x03D4, 0x2F); + VGA_WR08(chip->PCIO, 0x03D5, state->cursor2); + VGA_WR08(chip->PCIO, 0x03D4, 0x39); + VGA_WR08(chip->PCIO, 0x03D5, state->interlace); + + if(!chip->flatPanel) { + NV_WR32(chip->PRAMDAC0, 0x00000508, state->vpll); + NV_WR32(chip->PRAMDAC0, 0x0000050C, state->pllsel); + if(chip->twoHeads) + NV_WR32(chip->PRAMDAC0, 0x00000520, state->vpll2); + } else { + NV_WR32(chip->PRAMDAC, 0x00000848 , state->scale); + } + NV_WR32(chip->PRAMDAC, 0x00000600 , state->general); + + /* + * Turn off VBlank enable and reset. + */ + NV_WR32(chip->PCRTC, 0x00000140, 0); + NV_WR32(chip->PCRTC, 0x00000100, chip->VBlankBit); + /* + * Set interrupt enable. + */ + NV_WR32(chip->PMC, 0x00000140, chip->EnableIRQ & 0x01); + /* + * Set current state pointer. + */ + chip->CurrentState = state; + /* + * Reset FIFO free and empty counts. + */ + chip->FifoFreeCount = 0; + /* Free count from first subchannel */ + chip->FifoEmptyCount = NV_RD32(&chip->Rop->FifoFree, 0); +} +static void UnloadStateExt +( + RIVA_HW_INST *chip, + RIVA_HW_STATE *state +) +{ + /* + * Save current HW state. + */ + VGA_WR08(chip->PCIO, 0x03D4, 0x19); + state->repaint0 = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x1A); + state->repaint1 = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x25); + state->screen = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x28); + state->pixel = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x2D); + state->horiz = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x1B); + state->arbitration0 = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x20); + state->arbitration1 = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x30); + state->cursor0 = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x31); + state->cursor1 = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x2F); + state->cursor2 = VGA_RD08(chip->PCIO, 0x03D5); + VGA_WR08(chip->PCIO, 0x03D4, 0x39); + state->interlace = VGA_RD08(chip->PCIO, 0x03D5); + state->vpll = NV_RD32(chip->PRAMDAC0, 0x00000508); + state->vpll2 = NV_RD32(chip->PRAMDAC0, 0x00000520); + state->pllsel = NV_RD32(chip->PRAMDAC0, 0x0000050C); + state->general = NV_RD32(chip->PRAMDAC, 0x00000600); + state->scale = NV_RD32(chip->PRAMDAC, 0x00000848); + state->config = NV_RD32(chip->PFB, 0x00000200); + switch (chip->Architecture) + { + case NV_ARCH_03: + state->offset0 = NV_RD32(chip->PGRAPH, 0x00000630); + state->offset1 = NV_RD32(chip->PGRAPH, 0x00000634); + state->offset2 = NV_RD32(chip->PGRAPH, 0x00000638); + state->offset3 = NV_RD32(chip->PGRAPH, 0x0000063C); + state->pitch0 = NV_RD32(chip->PGRAPH, 0x00000650); + state->pitch1 = NV_RD32(chip->PGRAPH, 0x00000654); + state->pitch2 = NV_RD32(chip->PGRAPH, 0x00000658); + state->pitch3 = NV_RD32(chip->PGRAPH, 0x0000065C); + break; + case NV_ARCH_04: + state->offset0 = NV_RD32(chip->PGRAPH, 0x00000640); + state->offset1 = NV_RD32(chip->PGRAPH, 0x00000644); + state->offset2 = NV_RD32(chip->PGRAPH, 0x00000648); + state->offset3 = NV_RD32(chip->PGRAPH, 0x0000064C); + state->pitch0 = NV_RD32(chip->PGRAPH, 0x00000670); + state->pitch1 = NV_RD32(chip->PGRAPH, 0x00000674); + state->pitch2 = NV_RD32(chip->PGRAPH, 0x00000678); + state->pitch3 = NV_RD32(chip->PGRAPH, 0x0000067C); + break; + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + state->offset0 = NV_RD32(chip->PGRAPH, 0x00000640); + state->offset1 = NV_RD32(chip->PGRAPH, 0x00000644); + state->offset2 = NV_RD32(chip->PGRAPH, 0x00000648); + state->offset3 = NV_RD32(chip->PGRAPH, 0x0000064C); + state->pitch0 = NV_RD32(chip->PGRAPH, 0x00000670); + state->pitch1 = NV_RD32(chip->PGRAPH, 0x00000674); + state->pitch2 = NV_RD32(chip->PGRAPH, 0x00000678); + state->pitch3 = NV_RD32(chip->PGRAPH, 0x0000067C); + if(chip->twoHeads) { + state->head = NV_RD32(chip->PCRTC0, 0x00000860); + state->head2 = NV_RD32(chip->PCRTC0, 0x00002860); + VGA_WR08(chip->PCIO, 0x03D4, 0x44); + state->crtcOwner = VGA_RD08(chip->PCIO, 0x03D5); + } + VGA_WR08(chip->PCIO, 0x03D4, 0x41); + state->extra = VGA_RD08(chip->PCIO, 0x03D5); + state->cursorConfig = NV_RD32(chip->PCRTC, 0x00000810); + + if((chip->Chipset & 0x0ff0) == 0x0110) { + state->dither = NV_RD32(chip->PRAMDAC, 0x0528); + } else + if((chip->Chipset & 0x0ff0) >= 0x0170) { + state->dither = NV_RD32(chip->PRAMDAC, 0x083C); + } + break; + } +} +static void SetStartAddress +( + RIVA_HW_INST *chip, + unsigned start +) +{ + NV_WR32(chip->PCRTC, 0x800, start); +} + +static void SetStartAddress3 +( + RIVA_HW_INST *chip, + unsigned start +) +{ + int offset = start >> 2; + int pan = (start & 3) << 1; + unsigned char tmp; + + /* + * Unlock extended registers. + */ + chip->LockUnlock(chip, 0); + /* + * Set start address. + */ + VGA_WR08(chip->PCIO, 0x3D4, 0x0D); VGA_WR08(chip->PCIO, 0x3D5, offset); + offset >>= 8; + VGA_WR08(chip->PCIO, 0x3D4, 0x0C); VGA_WR08(chip->PCIO, 0x3D5, offset); + offset >>= 8; + VGA_WR08(chip->PCIO, 0x3D4, 0x19); tmp = VGA_RD08(chip->PCIO, 0x3D5); + VGA_WR08(chip->PCIO, 0x3D5, (offset & 0x01F) | (tmp & ~0x1F)); + VGA_WR08(chip->PCIO, 0x3D4, 0x2D); tmp = VGA_RD08(chip->PCIO, 0x3D5); + VGA_WR08(chip->PCIO, 0x3D5, (offset & 0x60) | (tmp & ~0x60)); + /* + * 4 pixel pan register. + */ + offset = VGA_RD08(chip->PCIO, chip->IO + 0x0A); + VGA_WR08(chip->PCIO, 0x3C0, 0x13); + VGA_WR08(chip->PCIO, 0x3C0, pan); +} +static void nv3SetSurfaces2D +( + RIVA_HW_INST *chip, + unsigned surf0, + unsigned surf1 +) +{ + RivaSurface __iomem *Surface = + (RivaSurface __iomem *)&(chip->FIFO[0x0000E000/4]); + + RIVA_FIFO_FREE(*chip,Tri03,5); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000003); + NV_WR32(&Surface->Offset, 0, surf0); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000004); + NV_WR32(&Surface->Offset, 0, surf1); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000013); +} +static void nv4SetSurfaces2D +( + RIVA_HW_INST *chip, + unsigned surf0, + unsigned surf1 +) +{ + RivaSurface __iomem *Surface = + (RivaSurface __iomem *)&(chip->FIFO[0x0000E000/4]); + + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000003); + NV_WR32(&Surface->Offset, 0, surf0); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000004); + NV_WR32(&Surface->Offset, 0, surf1); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000014); +} +static void nv10SetSurfaces2D +( + RIVA_HW_INST *chip, + unsigned surf0, + unsigned surf1 +) +{ + RivaSurface __iomem *Surface = + (RivaSurface __iomem *)&(chip->FIFO[0x0000E000/4]); + + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000003); + NV_WR32(&Surface->Offset, 0, surf0); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000004); + NV_WR32(&Surface->Offset, 0, surf1); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000014); +} +static void nv3SetSurfaces3D +( + RIVA_HW_INST *chip, + unsigned surf0, + unsigned surf1 +) +{ + RivaSurface __iomem *Surface = + (RivaSurface __iomem *)&(chip->FIFO[0x0000E000/4]); + + RIVA_FIFO_FREE(*chip,Tri03,5); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000005); + NV_WR32(&Surface->Offset, 0, surf0); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000006); + NV_WR32(&Surface->Offset, 0, surf1); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000013); +} +static void nv4SetSurfaces3D +( + RIVA_HW_INST *chip, + unsigned surf0, + unsigned surf1 +) +{ + RivaSurface __iomem *Surface = + (RivaSurface __iomem *)&(chip->FIFO[0x0000E000/4]); + + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000005); + NV_WR32(&Surface->Offset, 0, surf0); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000006); + NV_WR32(&Surface->Offset, 0, surf1); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000014); +} +static void nv10SetSurfaces3D +( + RIVA_HW_INST *chip, + unsigned surf0, + unsigned surf1 +) +{ + RivaSurface3D __iomem *Surfaces3D = + (RivaSurface3D __iomem *)&(chip->FIFO[0x0000E000/4]); + + RIVA_FIFO_FREE(*chip,Tri03,4); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000007); + NV_WR32(&Surfaces3D->RenderBufferOffset, 0, surf0); + NV_WR32(&Surfaces3D->ZBufferOffset, 0, surf1); + NV_WR32(&chip->FIFO[0x00003800], 0, 0x80000014); +} + +/****************************************************************************\ +* * +* Probe RIVA Chip Configuration * +* * +\****************************************************************************/ + +static void nv3GetConfig +( + RIVA_HW_INST *chip +) +{ + /* + * Fill in chip configuration. + */ + if (NV_RD32(&chip->PFB[0x00000000/4], 0) & 0x00000020) + { + if (((NV_RD32(chip->PMC, 0x00000000) & 0xF0) == 0x20) + && ((NV_RD32(chip->PMC, 0x00000000) & 0x0F) >= 0x02)) + { + /* + * SDRAM 128 ZX. + */ + chip->RamBandwidthKBytesPerSec = 800000; + switch (NV_RD32(chip->PFB, 0x00000000) & 0x03) + { + case 2: + chip->RamAmountKBytes = 1024 * 4; + break; + case 1: + chip->RamAmountKBytes = 1024 * 2; + break; + default: + chip->RamAmountKBytes = 1024 * 8; + break; + } + } + else + { + chip->RamBandwidthKBytesPerSec = 1000000; + chip->RamAmountKBytes = 1024 * 8; + } + } + else + { + /* + * SGRAM 128. + */ + chip->RamBandwidthKBytesPerSec = 1000000; + switch (NV_RD32(chip->PFB, 0x00000000) & 0x00000003) + { + case 0: + chip->RamAmountKBytes = 1024 * 8; + break; + case 2: + chip->RamAmountKBytes = 1024 * 4; + break; + default: + chip->RamAmountKBytes = 1024 * 2; + break; + } + } + chip->CrystalFreqKHz = (NV_RD32(chip->PEXTDEV, 0x00000000) & 0x00000040) ? 14318 : 13500; + chip->CURSOR = &(chip->PRAMIN[0x00008000/4 - 0x0800/4]); + chip->VBlankBit = 0x00000100; + chip->MaxVClockFreqKHz = 256000; + /* + * Set chip functions. + */ + chip->Busy = nv3Busy; + chip->ShowHideCursor = ShowHideCursor; + chip->LoadStateExt = LoadStateExt; + chip->UnloadStateExt = UnloadStateExt; + chip->SetStartAddress = SetStartAddress3; + chip->SetSurfaces2D = nv3SetSurfaces2D; + chip->SetSurfaces3D = nv3SetSurfaces3D; + chip->LockUnlock = nv3LockUnlock; +} +static void nv4GetConfig +( + RIVA_HW_INST *chip +) +{ + /* + * Fill in chip configuration. + */ + if (NV_RD32(chip->PFB, 0x00000000) & 0x00000100) + { + chip->RamAmountKBytes = ((NV_RD32(chip->PFB, 0x00000000) >> 12) & 0x0F) * 1024 * 2 + + 1024 * 2; + } + else + { + switch (NV_RD32(chip->PFB, 0x00000000) & 0x00000003) + { + case 0: + chip->RamAmountKBytes = 1024 * 32; + break; + case 1: + chip->RamAmountKBytes = 1024 * 4; + break; + case 2: + chip->RamAmountKBytes = 1024 * 8; + break; + case 3: + default: + chip->RamAmountKBytes = 1024 * 16; + break; + } + } + switch ((NV_RD32(chip->PFB, 0x00000000) >> 3) & 0x00000003) + { + case 3: + chip->RamBandwidthKBytesPerSec = 800000; + break; + default: + chip->RamBandwidthKBytesPerSec = 1000000; + break; + } + chip->CrystalFreqKHz = (NV_RD32(chip->PEXTDEV, 0x00000000) & 0x00000040) ? 14318 : 13500; + chip->CURSOR = &(chip->PRAMIN[0x00010000/4 - 0x0800/4]); + chip->VBlankBit = 0x00000001; + chip->MaxVClockFreqKHz = 350000; + /* + * Set chip functions. + */ + chip->Busy = nv4Busy; + chip->ShowHideCursor = ShowHideCursor; + chip->LoadStateExt = LoadStateExt; + chip->UnloadStateExt = UnloadStateExt; + chip->SetStartAddress = SetStartAddress; + chip->SetSurfaces2D = nv4SetSurfaces2D; + chip->SetSurfaces3D = nv4SetSurfaces3D; + chip->LockUnlock = nv4LockUnlock; +} +static void nv10GetConfig +( + RIVA_HW_INST *chip, + unsigned int chipset +) +{ + struct pci_dev* dev; + u32 amt; + +#ifdef __BIG_ENDIAN + /* turn on big endian register access */ + if(!(NV_RD32(chip->PMC, 0x00000004) & 0x01000001)) + NV_WR32(chip->PMC, 0x00000004, 0x01000001); +#endif + + /* + * Fill in chip configuration. + */ + if(chipset == NV_CHIP_IGEFORCE2) { + dev = pci_get_bus_and_slot(0, 1); + pci_read_config_dword(dev, 0x7C, &amt); + pci_dev_put(dev); + chip->RamAmountKBytes = (((amt >> 6) & 31) + 1) * 1024; + } else if(chipset == NV_CHIP_0x01F0) { + dev = pci_get_bus_and_slot(0, 1); + pci_read_config_dword(dev, 0x84, &amt); + pci_dev_put(dev); + chip->RamAmountKBytes = (((amt >> 4) & 127) + 1) * 1024; + } else { + switch ((NV_RD32(chip->PFB, 0x0000020C) >> 20) & 0x000000FF) + { + case 0x02: + chip->RamAmountKBytes = 1024 * 2; + break; + case 0x04: + chip->RamAmountKBytes = 1024 * 4; + break; + case 0x08: + chip->RamAmountKBytes = 1024 * 8; + break; + case 0x10: + chip->RamAmountKBytes = 1024 * 16; + break; + case 0x20: + chip->RamAmountKBytes = 1024 * 32; + break; + case 0x40: + chip->RamAmountKBytes = 1024 * 64; + break; + case 0x80: + chip->RamAmountKBytes = 1024 * 128; + break; + default: + chip->RamAmountKBytes = 1024 * 16; + break; + } + } + switch ((NV_RD32(chip->PFB, 0x00000000) >> 3) & 0x00000003) + { + case 3: + chip->RamBandwidthKBytesPerSec = 800000; + break; + default: + chip->RamBandwidthKBytesPerSec = 1000000; + break; + } + chip->CrystalFreqKHz = (NV_RD32(chip->PEXTDEV, 0x0000) & (1 << 6)) ? + 14318 : 13500; + + switch (chipset & 0x0ff0) { + case 0x0170: + case 0x0180: + case 0x01F0: + case 0x0250: + case 0x0280: + case 0x0300: + case 0x0310: + case 0x0320: + case 0x0330: + case 0x0340: + if(NV_RD32(chip->PEXTDEV, 0x0000) & (1 << 22)) + chip->CrystalFreqKHz = 27000; + break; + default: + break; + } + + chip->CursorStart = (chip->RamAmountKBytes - 128) * 1024; + chip->CURSOR = NULL; /* can't set this here */ + chip->VBlankBit = 0x00000001; + chip->MaxVClockFreqKHz = 350000; + /* + * Set chip functions. + */ + chip->Busy = nv10Busy; + chip->ShowHideCursor = ShowHideCursor; + chip->LoadStateExt = LoadStateExt; + chip->UnloadStateExt = UnloadStateExt; + chip->SetStartAddress = SetStartAddress; + chip->SetSurfaces2D = nv10SetSurfaces2D; + chip->SetSurfaces3D = nv10SetSurfaces3D; + chip->LockUnlock = nv4LockUnlock; + + switch(chipset & 0x0ff0) { + case 0x0110: + case 0x0170: + case 0x0180: + case 0x01F0: + case 0x0250: + case 0x0280: + case 0x0300: + case 0x0310: + case 0x0320: + case 0x0330: + case 0x0340: + chip->twoHeads = TRUE; + break; + default: + chip->twoHeads = FALSE; + break; + } +} +int RivaGetConfig +( + RIVA_HW_INST *chip, + unsigned int chipset +) +{ + /* + * Save this so future SW know whats it's dealing with. + */ + chip->Version = RIVA_SW_VERSION; + /* + * Chip specific configuration. + */ + switch (chip->Architecture) + { + case NV_ARCH_03: + nv3GetConfig(chip); + break; + case NV_ARCH_04: + nv4GetConfig(chip); + break; + case NV_ARCH_10: + case NV_ARCH_20: + case NV_ARCH_30: + nv10GetConfig(chip, chipset); + break; + default: + return (-1); + } + chip->Chipset = chipset; + /* + * Fill in FIFO pointers. + */ + chip->Rop = (RivaRop __iomem *)&(chip->FIFO[0x00000000/4]); + chip->Clip = (RivaClip __iomem *)&(chip->FIFO[0x00002000/4]); + chip->Patt = (RivaPattern __iomem *)&(chip->FIFO[0x00004000/4]); + chip->Pixmap = (RivaPixmap __iomem *)&(chip->FIFO[0x00006000/4]); + chip->Blt = (RivaScreenBlt __iomem *)&(chip->FIFO[0x00008000/4]); + chip->Bitmap = (RivaBitmap __iomem *)&(chip->FIFO[0x0000A000/4]); + chip->Line = (RivaLine __iomem *)&(chip->FIFO[0x0000C000/4]); + chip->Tri03 = (RivaTexturedTriangle03 __iomem *)&(chip->FIFO[0x0000E000/4]); + return (0); +} + diff --git a/drivers/video/fbdev/riva/riva_hw.h b/drivers/video/fbdev/riva/riva_hw.h new file mode 100644 index 000000000000..c2769f73e0b2 --- /dev/null +++ b/drivers/video/fbdev/riva/riva_hw.h @@ -0,0 +1,563 @@ +/***************************************************************************\ +|* *| +|* Copyright 1993-1999 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 1993-1999 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| +\***************************************************************************/ + +/* + * GPL licensing note -- nVidia is allowing a liberal interpretation of + * the documentation restriction above, to merely say that this nVidia's + * copyright and disclaimer should be included with all code derived + * from this source. -- Jeff Garzik <jgarzik@pobox.com>, 01/Nov/99 + */ + +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/riva_hw.h,v 1.21 2002/10/14 18:22:46 mvojkovi Exp $ */ +#ifndef __RIVA_HW_H__ +#define __RIVA_HW_H__ +#define RIVA_SW_VERSION 0x00010003 + +#ifndef Bool +typedef int Bool; +#endif + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef NULL +#define NULL 0 +#endif + +/* + * Typedefs to force certain sized values. + */ +typedef unsigned char U008; +typedef unsigned short U016; +typedef unsigned int U032; + +/* + * HW access macros. + */ +#include <asm/io.h> + +#define NV_WR08(p,i,d) (__raw_writeb((d), (void __iomem *)(p) + (i))) +#define NV_RD08(p,i) (__raw_readb((void __iomem *)(p) + (i))) +#define NV_WR16(p,i,d) (__raw_writew((d), (void __iomem *)(p) + (i))) +#define NV_RD16(p,i) (__raw_readw((void __iomem *)(p) + (i))) +#define NV_WR32(p,i,d) (__raw_writel((d), (void __iomem *)(p) + (i))) +#define NV_RD32(p,i) (__raw_readl((void __iomem *)(p) + (i))) + +#define VGA_WR08(p,i,d) (writeb((d), (void __iomem *)(p) + (i))) +#define VGA_RD08(p,i) (readb((void __iomem *)(p) + (i))) + +/* + * Define different architectures. + */ +#define NV_ARCH_03 0x03 +#define NV_ARCH_04 0x04 +#define NV_ARCH_10 0x10 +#define NV_ARCH_20 0x20 +#define NV_ARCH_30 0x30 +#define NV_ARCH_40 0x40 + +/***************************************************************************\ +* * +* FIFO registers. * +* * +\***************************************************************************/ + +/* + * Raster OPeration. Windows style ROP3. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BB]; + U032 Rop3; +} RivaRop; +/* + * 8X8 Monochrome pattern. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BD]; + U032 Shape; + U032 reserved03[0x001]; + U032 Color0; + U032 Color1; + U032 Monochrome[2]; +} RivaPattern; +/* + * Scissor clip rectangle. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BB]; + U032 TopLeft; + U032 WidthHeight; +} RivaClip; +/* + * 2D filled rectangle. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop[1]; +#endif + U032 reserved01[0x0BC]; + U032 Color; + U032 reserved03[0x03E]; + U032 TopLeft; + U032 WidthHeight; +} RivaRectangle; +/* + * 2D screen-screen BLT. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BB]; + U032 TopLeftSrc; + U032 TopLeftDst; + U032 WidthHeight; +} RivaScreenBlt; +/* + * 2D pixel BLT. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop[1]; +#endif + U032 reserved01[0x0BC]; + U032 TopLeft; + U032 WidthHeight; + U032 WidthHeightIn; + U032 reserved02[0x03C]; + U032 Pixels; +} RivaPixmap; +/* + * Filled rectangle combined with monochrome expand. Useful for glyphs. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BB]; + U032 reserved03[(0x040)-1]; + U032 Color1A; + struct + { + U032 TopLeft; + U032 WidthHeight; + } UnclippedRectangle[64]; + U032 reserved04[(0x080)-3]; + struct + { + U032 TopLeft; + U032 BottomRight; + } ClipB; + U032 Color1B; + struct + { + U032 TopLeft; + U032 BottomRight; + } ClippedRectangle[64]; + U032 reserved05[(0x080)-5]; + struct + { + U032 TopLeft; + U032 BottomRight; + } ClipC; + U032 Color1C; + U032 WidthHeightC; + U032 PointC; + U032 MonochromeData1C; + U032 reserved06[(0x080)+121]; + struct + { + U032 TopLeft; + U032 BottomRight; + } ClipD; + U032 Color1D; + U032 WidthHeightInD; + U032 WidthHeightOutD; + U032 PointD; + U032 MonochromeData1D; + U032 reserved07[(0x080)+120]; + struct + { + U032 TopLeft; + U032 BottomRight; + } ClipE; + U032 Color0E; + U032 Color1E; + U032 WidthHeightInE; + U032 WidthHeightOutE; + U032 PointE; + U032 MonochromeData01E; +} RivaBitmap; +/* + * 3D textured, Z buffered triangle. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BC]; + U032 TextureOffset; + U032 TextureFormat; + U032 TextureFilter; + U032 FogColor; +/* This is a problem on LynxOS */ +#ifdef Control +#undef Control +#endif + U032 Control; + U032 AlphaTest; + U032 reserved02[0x339]; + U032 FogAndIndex; + U032 Color; + float ScreenX; + float ScreenY; + float ScreenZ; + float EyeM; + float TextureS; + float TextureT; +} RivaTexturedTriangle03; +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BB]; + U032 ColorKey; + U032 TextureOffset; + U032 TextureFormat; + U032 TextureFilter; + U032 Blend; +/* This is a problem on LynxOS */ +#ifdef Control +#undef Control +#endif + U032 Control; + U032 FogColor; + U032 reserved02[0x39]; + struct + { + float ScreenX; + float ScreenY; + float ScreenZ; + float EyeM; + U032 Color; + U032 Specular; + float TextureS; + float TextureT; + } Vertex[16]; + U032 DrawTriangle3D; +} RivaTexturedTriangle05; +/* + * 2D line. + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop[1]; +#endif + U032 reserved01[0x0BC]; + U032 Color; /* source color 0304-0307*/ + U032 Reserved02[0x03e]; + struct { /* start aliased methods in array 0400- */ + U032 point0; /* y_x S16_S16 in pixels 0- 3*/ + U032 point1; /* y_x S16_S16 in pixels 4- 7*/ + } Lin[16]; /* end of aliased methods in array -047f*/ + struct { /* start aliased methods in array 0480- */ + U032 point0X; /* in pixels, 0 at left 0- 3*/ + U032 point0Y; /* in pixels, 0 at top 4- 7*/ + U032 point1X; /* in pixels, 0 at left 8- b*/ + U032 point1Y; /* in pixels, 0 at top c- f*/ + } Lin32[8]; /* end of aliased methods in array -04ff*/ + U032 PolyLin[32]; /* y_x S16_S16 in pixels 0500-057f*/ + struct { /* start aliased methods in array 0580- */ + U032 x; /* in pixels, 0 at left 0- 3*/ + U032 y; /* in pixels, 0 at top 4- 7*/ + } PolyLin32[16]; /* end of aliased methods in array -05ff*/ + struct { /* start aliased methods in array 0600- */ + U032 color; /* source color 0- 3*/ + U032 point; /* y_x S16_S16 in pixels 4- 7*/ + } ColorPolyLin[16]; /* end of aliased methods in array -067f*/ +} RivaLine; +/* + * 2D/3D surfaces + */ +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BE]; + U032 Offset; +} RivaSurface; +typedef volatile struct +{ + U032 reserved00[4]; +#ifdef __BIG_ENDIAN + U032 FifoFree; +#else + U016 FifoFree; + U016 Nop; +#endif + U032 reserved01[0x0BD]; + U032 Pitch; + U032 RenderBufferOffset; + U032 ZBufferOffset; +} RivaSurface3D; + +/***************************************************************************\ +* * +* Virtualized RIVA H/W interface. * +* * +\***************************************************************************/ + +#define FP_ENABLE 1 +#define FP_DITHER 2 + +struct _riva_hw_inst; +struct _riva_hw_state; +/* + * Virtialized chip interface. Makes RIVA 128 and TNT look alike. + */ +typedef struct _riva_hw_inst +{ + /* + * Chip specific settings. + */ + U032 Architecture; + U032 Version; + U032 Chipset; + U032 CrystalFreqKHz; + U032 RamAmountKBytes; + U032 MaxVClockFreqKHz; + U032 RamBandwidthKBytesPerSec; + U032 EnableIRQ; + U032 IO; + U032 VBlankBit; + U032 FifoFreeCount; + U032 FifoEmptyCount; + U032 CursorStart; + U032 flatPanel; + Bool twoHeads; + /* + * Non-FIFO registers. + */ + volatile U032 __iomem *PCRTC0; + volatile U032 __iomem *PCRTC; + volatile U032 __iomem *PRAMDAC0; + volatile U032 __iomem *PFB; + volatile U032 __iomem *PFIFO; + volatile U032 __iomem *PGRAPH; + volatile U032 __iomem *PEXTDEV; + volatile U032 __iomem *PTIMER; + volatile U032 __iomem *PMC; + volatile U032 __iomem *PRAMIN; + volatile U032 __iomem *FIFO; + volatile U032 __iomem *CURSOR; + volatile U008 __iomem *PCIO0; + volatile U008 __iomem *PCIO; + volatile U008 __iomem *PVIO; + volatile U008 __iomem *PDIO0; + volatile U008 __iomem *PDIO; + volatile U032 __iomem *PRAMDAC; + /* + * Common chip functions. + */ + int (*Busy)(struct _riva_hw_inst *); + void (*LoadStateExt)(struct _riva_hw_inst *,struct _riva_hw_state *); + void (*UnloadStateExt)(struct _riva_hw_inst *,struct _riva_hw_state *); + void (*SetStartAddress)(struct _riva_hw_inst *,U032); + void (*SetSurfaces2D)(struct _riva_hw_inst *,U032,U032); + void (*SetSurfaces3D)(struct _riva_hw_inst *,U032,U032); + int (*ShowHideCursor)(struct _riva_hw_inst *,int); + void (*LockUnlock)(struct _riva_hw_inst *, int); + /* + * Current extended mode settings. + */ + struct _riva_hw_state *CurrentState; + /* + * FIFO registers. + */ + RivaRop __iomem *Rop; + RivaPattern __iomem *Patt; + RivaClip __iomem *Clip; + RivaPixmap __iomem *Pixmap; + RivaScreenBlt __iomem *Blt; + RivaBitmap __iomem *Bitmap; + RivaLine __iomem *Line; + RivaTexturedTriangle03 __iomem *Tri03; + RivaTexturedTriangle05 __iomem *Tri05; +} RIVA_HW_INST; +/* + * Extended mode state information. + */ +typedef struct _riva_hw_state +{ + U032 bpp; + U032 width; + U032 height; + U032 interlace; + U032 repaint0; + U032 repaint1; + U032 screen; + U032 scale; + U032 dither; + U032 extra; + U032 pixel; + U032 horiz; + U032 arbitration0; + U032 arbitration1; + U032 vpll; + U032 vpll2; + U032 pllsel; + U032 general; + U032 crtcOwner; + U032 head; + U032 head2; + U032 config; + U032 cursorConfig; + U032 cursor0; + U032 cursor1; + U032 cursor2; + U032 offset0; + U032 offset1; + U032 offset2; + U032 offset3; + U032 pitch0; + U032 pitch1; + U032 pitch2; + U032 pitch3; +} RIVA_HW_STATE; + +/* + * function prototypes + */ + +extern int CalcStateExt +( + RIVA_HW_INST *chip, + RIVA_HW_STATE *state, + int bpp, + int width, + int hDisplaySize, + int height, + int dotClock +); + +/* + * External routines. + */ +int RivaGetConfig(RIVA_HW_INST *, unsigned int); +/* + * FIFO Free Count. Should attempt to yield processor if RIVA is busy. + */ + +#define RIVA_FIFO_FREE(hwinst,hwptr,cnt) \ +{ \ + while ((hwinst).FifoFreeCount < (cnt)) { \ + mb();mb(); \ + (hwinst).FifoFreeCount = NV_RD32(&(hwinst).hwptr->FifoFree, 0) >> 2; \ + } \ + (hwinst).FifoFreeCount -= (cnt); \ +} +#endif /* __RIVA_HW_H__ */ + diff --git a/drivers/video/fbdev/riva/riva_tbl.h b/drivers/video/fbdev/riva/riva_tbl.h new file mode 100644 index 000000000000..7ee7d72932d4 --- /dev/null +++ b/drivers/video/fbdev/riva/riva_tbl.h @@ -0,0 +1,1008 @@ + /***************************************************************************\ +|* *| +|* Copyright 1993-1999 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: The source code is copyrighted under U.S. and *| +|* international laws. Users and possessors of this source code are *| +|* hereby granted a nonexclusive, royalty-free copyright license to *| +|* use this code in individual and commercial software. *| +|* *| +|* Any use of this source code must include, in the user documenta- *| +|* tion and internal comments to the code, notices to the end user *| +|* as follows: *| +|* *| +|* Copyright 1993-1999 NVIDIA, Corporation. All rights reserved. *| +|* *| +|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *| +|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *| +|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *| +|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *| +|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *| +|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *| +|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *| +|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *| +|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *| +|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *| +|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial *| +|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *| +|* consisting of "commercial computer software" and "commercial *| +|* computer software documentation," as such terms are used in *| +|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *| +|* ment only as a commercial end item. Consistent with 48 C.F.R. *| +|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *| +|* all U.S. Government End Users acquire the source code with only *| +|* those rights set forth herein. *| +|* *| + \***************************************************************************/ + +/* + * GPL licensing note -- nVidia is allowing a liberal interpretation of + * the documentation restriction above, to merely say that this nVidia's + * copyright and disclaimer should be included with all code derived + * from this source. -- Jeff Garzik <jgarzik@pobox.com>, 01/Nov/99 + */ + +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/riva_tbl.h,v 1.9 2002/01/30 01:35:03 mvojkovi Exp $ */ + + +/* + * RIVA Fixed Functionality Init Tables. + */ +static unsigned RivaTablePMC[][2] = +{ + {0x00000050, 0x00000000}, + {0x00000080, 0xFFFF00FF}, + {0x00000080, 0xFFFFFFFF} +}; +static unsigned RivaTablePTIMER[][2] = +{ + {0x00000080, 0x00000008}, + {0x00000084, 0x00000003}, + {0x00000050, 0x00000000}, + {0x00000040, 0xFFFFFFFF} +}; +static unsigned RivaTableFIFO[][2] = +{ + {0x00000000, 0x80000000}, + {0x00000800, 0x80000001}, + {0x00001000, 0x80000002}, + {0x00001800, 0x80000010}, + {0x00002000, 0x80000011}, + {0x00002800, 0x80000012}, + {0x00003000, 0x80000016}, + {0x00003800, 0x80000013} +}; +static unsigned nv3TablePFIFO[][2] = +{ + {0x00000140, 0x00000000}, + {0x00000480, 0x00000000}, + {0x00000490, 0x00000000}, + {0x00000494, 0x00000000}, + {0x00000481, 0x00000000}, + {0x00000084, 0x00000000}, + {0x00000086, 0x00002000}, + {0x00000085, 0x00002200}, + {0x00000484, 0x00000000}, + {0x0000049C, 0x00000000}, + {0x00000104, 0x00000000}, + {0x00000108, 0x00000000}, + {0x00000100, 0x00000000}, + {0x000004A0, 0x00000000}, + {0x000004A4, 0x00000000}, + {0x000004A8, 0x00000000}, + {0x000004AC, 0x00000000}, + {0x000004B0, 0x00000000}, + {0x000004B4, 0x00000000}, + {0x000004B8, 0x00000000}, + {0x000004BC, 0x00000000}, + {0x00000050, 0x00000000}, + {0x00000040, 0xFFFFFFFF}, + {0x00000480, 0x00000001}, + {0x00000490, 0x00000001}, + {0x00000140, 0x00000001} +}; +static unsigned nv3TablePGRAPH[][2] = +{ + {0x00000020, 0x1230001F}, + {0x00000021, 0x10113000}, + {0x00000022, 0x1131F101}, + {0x00000023, 0x0100F531}, + {0x00000060, 0x00000000}, + {0x00000065, 0x00000000}, + {0x00000068, 0x00000000}, + {0x00000069, 0x00000000}, + {0x0000006A, 0x00000000}, + {0x0000006B, 0x00000000}, + {0x0000006C, 0x00000000}, + {0x0000006D, 0x00000000}, + {0x0000006E, 0x00000000}, + {0x0000006F, 0x00000000}, + {0x000001A8, 0x00000000}, + {0x00000440, 0xFFFFFFFF}, + {0x00000480, 0x00000001}, + {0x000001A0, 0x00000000}, + {0x000001A2, 0x00000000}, + {0x0000018A, 0xFFFFFFFF}, + {0x00000190, 0x00000000}, + {0x00000142, 0x00000000}, + {0x00000154, 0x00000000}, + {0x00000155, 0xFFFFFFFF}, + {0x00000156, 0x00000000}, + {0x00000157, 0xFFFFFFFF}, + {0x00000064, 0x10010002}, + {0x00000050, 0x00000000}, + {0x00000051, 0x00000000}, + {0x00000040, 0xFFFFFFFF}, + {0x00000041, 0xFFFFFFFF}, + {0x00000440, 0xFFFFFFFF}, + {0x000001A9, 0x00000001} +}; +static unsigned nv3TablePGRAPH_8BPP[][2] = +{ + {0x000001AA, 0x00001111} +}; +static unsigned nv3TablePGRAPH_15BPP[][2] = +{ + {0x000001AA, 0x00002222} +}; +static unsigned nv3TablePGRAPH_32BPP[][2] = +{ + {0x000001AA, 0x00003333} +}; +static unsigned nv3TablePRAMIN[][2] = +{ + {0x00000500, 0x00010000}, + {0x00000501, 0x007FFFFF}, + {0x00000200, 0x80000000}, + {0x00000201, 0x00C20341}, + {0x00000204, 0x80000001}, + {0x00000205, 0x00C50342}, + {0x00000208, 0x80000002}, + {0x00000209, 0x00C60343}, + {0x0000020C, 0x80000003}, + {0x0000020D, 0x00DC0348}, + {0x00000210, 0x80000004}, + {0x00000211, 0x00DC0349}, + {0x00000214, 0x80000005}, + {0x00000215, 0x00DC034A}, + {0x00000218, 0x80000006}, + {0x00000219, 0x00DC034B}, + {0x00000240, 0x80000010}, + {0x00000241, 0x00D10344}, + {0x00000244, 0x80000011}, + {0x00000245, 0x00D00345}, + {0x00000248, 0x80000012}, + {0x00000249, 0x00CC0346}, + {0x0000024C, 0x80000013}, + {0x0000024D, 0x00D70347}, + {0x00000258, 0x80000016}, + {0x00000259, 0x00CA034C}, + {0x00000D05, 0x00000000}, + {0x00000D06, 0x00000000}, + {0x00000D07, 0x00000000}, + {0x00000D09, 0x00000000}, + {0x00000D0A, 0x00000000}, + {0x00000D0B, 0x00000000}, + {0x00000D0D, 0x00000000}, + {0x00000D0E, 0x00000000}, + {0x00000D0F, 0x00000000}, + {0x00000D11, 0x00000000}, + {0x00000D12, 0x00000000}, + {0x00000D13, 0x00000000}, + {0x00000D15, 0x00000000}, + {0x00000D16, 0x00000000}, + {0x00000D17, 0x00000000}, + {0x00000D19, 0x00000000}, + {0x00000D1A, 0x00000000}, + {0x00000D1B, 0x00000000}, + {0x00000D1D, 0x00000140}, + {0x00000D1E, 0x00000000}, + {0x00000D1F, 0x00000000}, + {0x00000D20, 0x10100200}, + {0x00000D21, 0x00000000}, + {0x00000D22, 0x00000000}, + {0x00000D23, 0x00000000}, + {0x00000D24, 0x10210200}, + {0x00000D25, 0x00000000}, + {0x00000D26, 0x00000000}, + {0x00000D27, 0x00000000}, + {0x00000D28, 0x10420200}, + {0x00000D29, 0x00000000}, + {0x00000D2A, 0x00000000}, + {0x00000D2B, 0x00000000}, + {0x00000D2C, 0x10830200}, + {0x00000D2D, 0x00000000}, + {0x00000D2E, 0x00000000}, + {0x00000D2F, 0x00000000}, + {0x00000D31, 0x00000000}, + {0x00000D32, 0x00000000}, + {0x00000D33, 0x00000000} +}; +static unsigned nv3TablePRAMIN_8BPP[][2] = +{ + /* 0xXXXXX3XX For MSB mono format */ + /* 0xXXXXX2XX For LSB mono format */ + {0x00000D04, 0x10110203}, + {0x00000D08, 0x10110203}, + {0x00000D0C, 0x1011020B}, + {0x00000D10, 0x10118203}, + {0x00000D14, 0x10110203}, + {0x00000D18, 0x10110203}, + {0x00000D1C, 0x10419208}, + {0x00000D30, 0x10118203} +}; +static unsigned nv3TablePRAMIN_15BPP[][2] = +{ + /* 0xXXXXX2XX For MSB mono format */ + /* 0xXXXXX3XX For LSB mono format */ + {0x00000D04, 0x10110200}, + {0x00000D08, 0x10110200}, + {0x00000D0C, 0x10110208}, + {0x00000D10, 0x10118200}, + {0x00000D14, 0x10110200}, + {0x00000D18, 0x10110200}, + {0x00000D1C, 0x10419208}, + {0x00000D30, 0x10118200} +}; +static unsigned nv3TablePRAMIN_32BPP[][2] = +{ + /* 0xXXXXX3XX For MSB mono format */ + /* 0xXXXXX2XX For LSB mono format */ + {0x00000D04, 0x10110201}, + {0x00000D08, 0x10110201}, + {0x00000D0C, 0x10110209}, + {0x00000D10, 0x10118201}, + {0x00000D14, 0x10110201}, + {0x00000D18, 0x10110201}, + {0x00000D1C, 0x10419208}, + {0x00000D30, 0x10118201} +}; +static unsigned nv4TableFIFO[][2] = +{ + {0x00003800, 0x80000014} +}; +static unsigned nv4TablePFIFO[][2] = +{ + {0x00000140, 0x00000000}, + {0x00000480, 0x00000000}, + {0x00000494, 0x00000000}, + {0x00000481, 0x00000000}, + {0x0000048B, 0x00000000}, + {0x00000400, 0x00000000}, + {0x00000414, 0x00000000}, + {0x00000084, 0x03000100}, + {0x00000085, 0x00000110}, + {0x00000086, 0x00000112}, + {0x00000143, 0x0000FFFF}, + {0x00000496, 0x0000FFFF}, + {0x00000050, 0x00000000}, + {0x00000040, 0xFFFFFFFF}, + {0x00000415, 0x00000001}, + {0x00000480, 0x00000001}, + {0x00000494, 0x00000001}, + {0x00000495, 0x00000001}, + {0x00000140, 0x00000001} +}; +static unsigned nv4TablePGRAPH[][2] = +{ + {0x00000020, 0x1231C001}, + {0x00000021, 0x72111101}, + {0x00000022, 0x11D5F071}, + {0x00000023, 0x10D4FF31}, + {0x00000060, 0x00000000}, + {0x00000068, 0x00000000}, + {0x00000070, 0x00000000}, + {0x00000078, 0x00000000}, + {0x00000061, 0x00000000}, + {0x00000069, 0x00000000}, + {0x00000071, 0x00000000}, + {0x00000079, 0x00000000}, + {0x00000062, 0x00000000}, + {0x0000006A, 0x00000000}, + {0x00000072, 0x00000000}, + {0x0000007A, 0x00000000}, + {0x00000063, 0x00000000}, + {0x0000006B, 0x00000000}, + {0x00000073, 0x00000000}, + {0x0000007B, 0x00000000}, + {0x00000064, 0x00000000}, + {0x0000006C, 0x00000000}, + {0x00000074, 0x00000000}, + {0x0000007C, 0x00000000}, + {0x00000065, 0x00000000}, + {0x0000006D, 0x00000000}, + {0x00000075, 0x00000000}, + {0x0000007D, 0x00000000}, + {0x00000066, 0x00000000}, + {0x0000006E, 0x00000000}, + {0x00000076, 0x00000000}, + {0x0000007E, 0x00000000}, + {0x00000067, 0x00000000}, + {0x0000006F, 0x00000000}, + {0x00000077, 0x00000000}, + {0x0000007F, 0x00000000}, + {0x00000058, 0x00000000}, + {0x00000059, 0x00000000}, + {0x0000005A, 0x00000000}, + {0x0000005B, 0x00000000}, + {0x00000196, 0x00000000}, + {0x000001A1, 0x01FFFFFF}, + {0x00000197, 0x00000000}, + {0x000001A2, 0x01FFFFFF}, + {0x00000198, 0x00000000}, + {0x000001A3, 0x01FFFFFF}, + {0x00000199, 0x00000000}, + {0x000001A4, 0x01FFFFFF}, + {0x00000050, 0x00000000}, + {0x00000040, 0xFFFFFFFF}, + {0x0000005C, 0x10010100}, + {0x000001C4, 0xFFFFFFFF}, + {0x000001C8, 0x00000001}, + {0x00000204, 0x00000000}, + {0x000001C3, 0x00000001} +}; +static unsigned nv4TablePGRAPH_8BPP[][2] = +{ + {0x000001C9, 0x00111111}, + {0x00000186, 0x00001010}, + {0x0000020C, 0x03020202} +}; +static unsigned nv4TablePGRAPH_15BPP[][2] = +{ + {0x000001C9, 0x00226222}, + {0x00000186, 0x00002071}, + {0x0000020C, 0x09080808} +}; +static unsigned nv4TablePGRAPH_16BPP[][2] = +{ + {0x000001C9, 0x00556555}, + {0x00000186, 0x000050C2}, + {0x0000020C, 0x0C0B0B0B} +}; +static unsigned nv4TablePGRAPH_32BPP[][2] = +{ + {0x000001C9, 0x0077D777}, + {0x00000186, 0x000070E5}, + {0x0000020C, 0x0E0D0D0D} +}; +static unsigned nv4TablePRAMIN[][2] = +{ + {0x00000000, 0x80000010}, + {0x00000001, 0x80011145}, + {0x00000002, 0x80000011}, + {0x00000003, 0x80011146}, + {0x00000004, 0x80000012}, + {0x00000005, 0x80011147}, + {0x00000006, 0x80000013}, + {0x00000007, 0x80011148}, + {0x00000008, 0x80000014}, + {0x00000009, 0x80011149}, + {0x0000000A, 0x80000015}, + {0x0000000B, 0x8001114A}, + {0x0000000C, 0x80000016}, + {0x0000000D, 0x8001114F}, + {0x00000020, 0x80000000}, + {0x00000021, 0x80011142}, + {0x00000022, 0x80000001}, + {0x00000023, 0x80011143}, + {0x00000024, 0x80000002}, + {0x00000025, 0x80011144}, + {0x00000026, 0x80000003}, + {0x00000027, 0x8001114B}, + {0x00000028, 0x80000004}, + {0x00000029, 0x8001114C}, + {0x0000002A, 0x80000005}, + {0x0000002B, 0x8001114D}, + {0x0000002C, 0x80000006}, + {0x0000002D, 0x8001114E}, + {0x00000500, 0x00003000}, + {0x00000501, 0x01FFFFFF}, + {0x00000502, 0x00000002}, + {0x00000503, 0x00000002}, + {0x00000508, 0x01008043}, + {0x0000050A, 0x00000000}, + {0x0000050B, 0x00000000}, + {0x0000050C, 0x01008019}, + {0x0000050E, 0x00000000}, + {0x0000050F, 0x00000000}, +#if 1 + {0x00000510, 0x01008018}, +#else + {0x00000510, 0x01008044}, +#endif + {0x00000512, 0x00000000}, + {0x00000513, 0x00000000}, + {0x00000514, 0x01008021}, + {0x00000516, 0x00000000}, + {0x00000517, 0x00000000}, + {0x00000518, 0x0100805F}, + {0x0000051A, 0x00000000}, + {0x0000051B, 0x00000000}, +#if 1 + {0x0000051C, 0x0100804B}, +#else + {0x0000051C, 0x0100804A}, +#endif + {0x0000051E, 0x00000000}, + {0x0000051F, 0x00000000}, + {0x00000520, 0x0100A048}, + {0x00000521, 0x00000D01}, + {0x00000522, 0x11401140}, + {0x00000523, 0x00000000}, + {0x00000524, 0x0300A054}, + {0x00000525, 0x00000D01}, + {0x00000526, 0x11401140}, + {0x00000527, 0x00000000}, + {0x00000528, 0x0300A055}, + {0x00000529, 0x00000D01}, + {0x0000052A, 0x11401140}, + {0x0000052B, 0x00000000}, + {0x0000052C, 0x00000058}, + {0x0000052E, 0x11401140}, + {0x0000052F, 0x00000000}, + {0x00000530, 0x00000059}, + {0x00000532, 0x11401140}, + {0x00000533, 0x00000000}, + {0x00000534, 0x0000005A}, + {0x00000536, 0x11401140}, + {0x00000537, 0x00000000}, + {0x00000538, 0x0000005B}, + {0x0000053A, 0x11401140}, + {0x0000053B, 0x00000000}, + {0x0000053C, 0x0300A01C}, + {0x0000053E, 0x11401140}, + {0x0000053F, 0x00000000} +}; +static unsigned nv4TablePRAMIN_8BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000302}, + {0x0000050D, 0x00000302}, + {0x00000511, 0x00000202}, + {0x00000515, 0x00000302}, + {0x00000519, 0x00000302}, + {0x0000051D, 0x00000302}, + {0x0000052D, 0x00000302}, + {0x0000052E, 0x00000302}, + {0x00000535, 0x00000000}, + {0x00000539, 0x00000000}, + {0x0000053D, 0x00000302} +}; +static unsigned nv4TablePRAMIN_15BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000902}, + {0x0000050D, 0x00000902}, + {0x00000511, 0x00000802}, + {0x00000515, 0x00000902}, + {0x00000519, 0x00000902}, + {0x0000051D, 0x00000902}, + {0x0000052D, 0x00000902}, + {0x0000052E, 0x00000902}, + {0x00000535, 0x00000702}, + {0x00000539, 0x00000702}, + {0x0000053D, 0x00000902} +}; +static unsigned nv4TablePRAMIN_16BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000C02}, + {0x0000050D, 0x00000C02}, + {0x00000511, 0x00000B02}, + {0x00000515, 0x00000C02}, + {0x00000519, 0x00000C02}, + {0x0000051D, 0x00000C02}, + {0x0000052D, 0x00000C02}, + {0x0000052E, 0x00000C02}, + {0x00000535, 0x00000702}, + {0x00000539, 0x00000702}, + {0x0000053D, 0x00000C02} +}; +static unsigned nv4TablePRAMIN_32BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000E02}, + {0x0000050D, 0x00000E02}, + {0x00000511, 0x00000D02}, + {0x00000515, 0x00000E02}, + {0x00000519, 0x00000E02}, + {0x0000051D, 0x00000E02}, + {0x0000052D, 0x00000E02}, + {0x0000052E, 0x00000E02}, + {0x00000535, 0x00000E02}, + {0x00000539, 0x00000E02}, + {0x0000053D, 0x00000E02} +}; +static unsigned nv10TableFIFO[][2] = +{ + {0x00003800, 0x80000014} +}; +static unsigned nv10TablePFIFO[][2] = +{ + {0x00000140, 0x00000000}, + {0x00000480, 0x00000000}, + {0x00000494, 0x00000000}, + {0x00000481, 0x00000000}, + {0x0000048B, 0x00000000}, + {0x00000400, 0x00000000}, + {0x00000414, 0x00000000}, + {0x00000084, 0x03000100}, + {0x00000085, 0x00000110}, + {0x00000086, 0x00000112}, + {0x00000143, 0x0000FFFF}, + {0x00000496, 0x0000FFFF}, + {0x00000050, 0x00000000}, + {0x00000040, 0xFFFFFFFF}, + {0x00000415, 0x00000001}, + {0x00000480, 0x00000001}, + {0x00000494, 0x00000001}, + {0x00000495, 0x00000001}, + {0x00000140, 0x00000001} +}; +static unsigned nv10TablePGRAPH[][2] = +{ + {0x00000020, 0x0003FFFF}, + {0x00000021, 0x00118701}, + {0x00000022, 0x24F82AD9}, + {0x00000023, 0x55DE0030}, + {0x00000020, 0x00000000}, + {0x00000024, 0x00000000}, + {0x00000058, 0x00000000}, + {0x00000060, 0x00000000}, + {0x00000068, 0x00000000}, + {0x00000070, 0x00000000}, + {0x00000078, 0x00000000}, + {0x00000059, 0x00000000}, + {0x00000061, 0x00000000}, + {0x00000069, 0x00000000}, + {0x00000071, 0x00000000}, + {0x00000079, 0x00000000}, + {0x0000005A, 0x00000000}, + {0x00000062, 0x00000000}, + {0x0000006A, 0x00000000}, + {0x00000072, 0x00000000}, + {0x0000007A, 0x00000000}, + {0x0000005B, 0x00000000}, + {0x00000063, 0x00000000}, + {0x0000006B, 0x00000000}, + {0x00000073, 0x00000000}, + {0x0000007B, 0x00000000}, + {0x0000005C, 0x00000000}, + {0x00000064, 0x00000000}, + {0x0000006C, 0x00000000}, + {0x00000074, 0x00000000}, + {0x0000007C, 0x00000000}, + {0x0000005D, 0x00000000}, + {0x00000065, 0x00000000}, + {0x0000006D, 0x00000000}, + {0x00000075, 0x00000000}, + {0x0000007D, 0x00000000}, + {0x0000005E, 0x00000000}, + {0x00000066, 0x00000000}, + {0x0000006E, 0x00000000}, + {0x00000076, 0x00000000}, + {0x0000007E, 0x00000000}, + {0x0000005F, 0x00000000}, + {0x00000067, 0x00000000}, + {0x0000006F, 0x00000000}, + {0x00000077, 0x00000000}, + {0x0000007F, 0x00000000}, + {0x00000053, 0x00000000}, + {0x00000054, 0x00000000}, + {0x00000055, 0x00000000}, + {0x00000056, 0x00000000}, + {0x00000057, 0x00000000}, + {0x00000196, 0x00000000}, + {0x000001A1, 0x01FFFFFF}, + {0x00000197, 0x00000000}, + {0x000001A2, 0x01FFFFFF}, + {0x00000198, 0x00000000}, + {0x000001A3, 0x01FFFFFF}, + {0x00000199, 0x00000000}, + {0x000001A4, 0x01FFFFFF}, + {0x0000019A, 0x00000000}, + {0x000001A5, 0x01FFFFFF}, + {0x0000019B, 0x00000000}, + {0x000001A6, 0x01FFFFFF}, + {0x00000050, 0x01111111}, + {0x00000040, 0xFFFFFFFF}, + {0x00000051, 0x10010100}, + {0x000001C5, 0xFFFFFFFF}, + {0x000001C8, 0x00000001}, + {0x00000204, 0x00000000}, + {0x000001C4, 0x00000001} +}; +static unsigned nv10TablePGRAPH_8BPP[][2] = +{ + {0x000001C9, 0x00111111}, + {0x00000186, 0x00001010}, + {0x0000020C, 0x03020202} +}; +static unsigned nv10TablePGRAPH_15BPP[][2] = +{ + {0x000001C9, 0x00226222}, + {0x00000186, 0x00002071}, + {0x0000020C, 0x09080808} +}; +static unsigned nv10TablePGRAPH_16BPP[][2] = +{ + {0x000001C9, 0x00556555}, + {0x00000186, 0x000050C2}, + {0x0000020C, 0x000B0B0C} +}; +static unsigned nv10TablePGRAPH_32BPP[][2] = +{ + {0x000001C9, 0x0077D777}, + {0x00000186, 0x000070E5}, + {0x0000020C, 0x0E0D0D0D} +}; +static unsigned nv10tri05TablePGRAPH[][2] = +{ + {(0x00000E00/4), 0x00000000}, + {(0x00000E04/4), 0x00000000}, + {(0x00000E08/4), 0x00000000}, + {(0x00000E0C/4), 0x00000000}, + {(0x00000E10/4), 0x00001000}, + {(0x00000E14/4), 0x00001000}, + {(0x00000E18/4), 0x4003ff80}, + {(0x00000E1C/4), 0x00000000}, + {(0x00000E20/4), 0x00000000}, + {(0x00000E24/4), 0x00000000}, + {(0x00000E28/4), 0x00000000}, + {(0x00000E2C/4), 0x00000000}, + {(0x00000E30/4), 0x00080008}, + {(0x00000E34/4), 0x00080008}, + {(0x00000E38/4), 0x00000000}, + {(0x00000E3C/4), 0x00000000}, + {(0x00000E40/4), 0x00000000}, + {(0x00000E44/4), 0x00000000}, + {(0x00000E48/4), 0x00000000}, + {(0x00000E4C/4), 0x00000000}, + {(0x00000E50/4), 0x00000000}, + {(0x00000E54/4), 0x00000000}, + {(0x00000E58/4), 0x00000000}, + {(0x00000E5C/4), 0x00000000}, + {(0x00000E60/4), 0x00000000}, + {(0x00000E64/4), 0x10000000}, + {(0x00000E68/4), 0x00000000}, + {(0x00000E6C/4), 0x00000000}, + {(0x00000E70/4), 0x00000000}, + {(0x00000E74/4), 0x00000000}, + {(0x00000E78/4), 0x00000000}, + {(0x00000E7C/4), 0x00000000}, + {(0x00000E80/4), 0x00000000}, + {(0x00000E84/4), 0x00000000}, + {(0x00000E88/4), 0x08000000}, + {(0x00000E8C/4), 0x00000000}, + {(0x00000E90/4), 0x00000000}, + {(0x00000E94/4), 0x00000000}, + {(0x00000E98/4), 0x00000000}, + {(0x00000E9C/4), 0x4B7FFFFF}, + {(0x00000EA0/4), 0x00000000}, + {(0x00000EA4/4), 0x00000000}, + {(0x00000EA8/4), 0x00000000}, + {(0x00000F00/4), 0x07FF0800}, + {(0x00000F04/4), 0x07FF0800}, + {(0x00000F08/4), 0x07FF0800}, + {(0x00000F0C/4), 0x07FF0800}, + {(0x00000F10/4), 0x07FF0800}, + {(0x00000F14/4), 0x07FF0800}, + {(0x00000F18/4), 0x07FF0800}, + {(0x00000F1C/4), 0x07FF0800}, + {(0x00000F20/4), 0x07FF0800}, + {(0x00000F24/4), 0x07FF0800}, + {(0x00000F28/4), 0x07FF0800}, + {(0x00000F2C/4), 0x07FF0800}, + {(0x00000F30/4), 0x07FF0800}, + {(0x00000F34/4), 0x07FF0800}, + {(0x00000F38/4), 0x07FF0800}, + {(0x00000F3C/4), 0x07FF0800}, + {(0x00000F40/4), 0x10000000}, + {(0x00000F44/4), 0x00000000}, + {(0x00000F50/4), 0x00006740}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F50/4), 0x00006750}, + {(0x00000F54/4), 0x40000000}, + {(0x00000F54/4), 0x40000000}, + {(0x00000F54/4), 0x40000000}, + {(0x00000F54/4), 0x40000000}, + {(0x00000F50/4), 0x00006760}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x00006770}, + {(0x00000F54/4), 0xC5000000}, + {(0x00000F54/4), 0xC5000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x00006780}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x000067A0}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F50/4), 0x00006AB0}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F50/4), 0x00006AC0}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x00006C10}, + {(0x00000F54/4), 0xBF800000}, + {(0x00000F50/4), 0x00007030}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x00007040}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x00007050}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x00007060}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x00007070}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x00007080}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x00007090}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x000070A0}, + {(0x00000F54/4), 0x7149F2CA}, + {(0x00000F50/4), 0x00006A80}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F50/4), 0x00006AA0}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x00000040}, + {(0x00000F54/4), 0x00000005}, + {(0x00000F50/4), 0x00006400}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x4B7FFFFF}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x00006410}, + {(0x00000F54/4), 0xC5000000}, + {(0x00000F54/4), 0xC5000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x00006420}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x00006430}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x000064C0}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F54/4), 0x477FFFFF}, + {(0x00000F54/4), 0x3F800000}, + {(0x00000F50/4), 0x000064D0}, + {(0x00000F54/4), 0xC5000000}, + {(0x00000F54/4), 0xC5000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x000064E0}, + {(0x00000F54/4), 0xC4FFF000}, + {(0x00000F54/4), 0xC4FFF000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F50/4), 0x000064F0}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F54/4), 0x00000000}, + {(0x00000F40/4), 0x30000000}, + {(0x00000F44/4), 0x00000004}, + {(0x00000F48/4), 0x10000000}, + {(0x00000F4C/4), 0x00000000} +}; +static unsigned nv10TablePRAMIN[][2] = +{ + {0x00000000, 0x80000010}, + {0x00000001, 0x80011145}, + {0x00000002, 0x80000011}, + {0x00000003, 0x80011146}, + {0x00000004, 0x80000012}, + {0x00000005, 0x80011147}, + {0x00000006, 0x80000013}, + {0x00000007, 0x80011148}, + {0x00000008, 0x80000014}, + {0x00000009, 0x80011149}, + {0x0000000A, 0x80000015}, + {0x0000000B, 0x8001114A}, + {0x0000000C, 0x80000016}, + {0x0000000D, 0x80011150}, + {0x00000020, 0x80000000}, + {0x00000021, 0x80011142}, + {0x00000022, 0x80000001}, + {0x00000023, 0x80011143}, + {0x00000024, 0x80000002}, + {0x00000025, 0x80011144}, + {0x00000026, 0x80000003}, + {0x00000027, 0x8001114B}, + {0x00000028, 0x80000004}, + {0x00000029, 0x8001114C}, + {0x0000002A, 0x80000005}, + {0x0000002B, 0x8001114D}, + {0x0000002C, 0x80000006}, + {0x0000002D, 0x8001114E}, + {0x0000002E, 0x80000007}, + {0x0000002F, 0x8001114F}, + {0x00000500, 0x00003000}, + {0x00000501, 0x01FFFFFF}, + {0x00000502, 0x00000002}, + {0x00000503, 0x00000002}, +#ifdef __BIG_ENDIAN + {0x00000508, 0x01088043}, +#else + {0x00000508, 0x01008043}, +#endif + {0x0000050A, 0x00000000}, + {0x0000050B, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x0000050C, 0x01088019}, +#else + {0x0000050C, 0x01008019}, +#endif + {0x0000050E, 0x00000000}, + {0x0000050F, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x00000510, 0x01088018}, +#else + {0x00000510, 0x01008018}, +#endif + {0x00000512, 0x00000000}, + {0x00000513, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x00000514, 0x01088021}, +#else + {0x00000514, 0x01008021}, +#endif + {0x00000516, 0x00000000}, + {0x00000517, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x00000518, 0x0108805F}, +#else + {0x00000518, 0x0100805F}, +#endif + {0x0000051A, 0x00000000}, + {0x0000051B, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x0000051C, 0x0108804B}, +#else + {0x0000051C, 0x0100804B}, +#endif + {0x0000051E, 0x00000000}, + {0x0000051F, 0x00000000}, + {0x00000520, 0x0100A048}, + {0x00000521, 0x00000D01}, + {0x00000522, 0x11401140}, + {0x00000523, 0x00000000}, + {0x00000524, 0x0300A094}, + {0x00000525, 0x00000D01}, + {0x00000526, 0x11401140}, + {0x00000527, 0x00000000}, + {0x00000528, 0x0300A095}, + {0x00000529, 0x00000D01}, + {0x0000052A, 0x11401140}, + {0x0000052B, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x0000052C, 0x00080058}, +#else + {0x0000052C, 0x00000058}, +#endif + {0x0000052E, 0x11401140}, + {0x0000052F, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x00000530, 0x00080059}, +#else + {0x00000530, 0x00000059}, +#endif + {0x00000532, 0x11401140}, + {0x00000533, 0x00000000}, + {0x00000534, 0x0000005A}, + {0x00000536, 0x11401140}, + {0x00000537, 0x00000000}, + {0x00000538, 0x0000005B}, + {0x0000053A, 0x11401140}, + {0x0000053B, 0x00000000}, + {0x0000053C, 0x00000093}, + {0x0000053E, 0x11401140}, + {0x0000053F, 0x00000000}, +#ifdef __BIG_ENDIAN + {0x00000540, 0x0308A01C}, +#else + {0x00000540, 0x0300A01C}, +#endif + {0x00000542, 0x11401140}, + {0x00000543, 0x00000000} +}; +static unsigned nv10TablePRAMIN_8BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000302}, + {0x0000050D, 0x00000302}, + {0x00000511, 0x00000202}, + {0x00000515, 0x00000302}, + {0x00000519, 0x00000302}, + {0x0000051D, 0x00000302}, + {0x0000052D, 0x00000302}, + {0x0000052E, 0x00000302}, + {0x00000535, 0x00000000}, + {0x00000539, 0x00000000}, + {0x0000053D, 0x00000000}, + {0x00000541, 0x00000302} +}; +static unsigned nv10TablePRAMIN_15BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000902}, + {0x0000050D, 0x00000902}, + {0x00000511, 0x00000802}, + {0x00000515, 0x00000902}, + {0x00000519, 0x00000902}, + {0x0000051D, 0x00000902}, + {0x0000052D, 0x00000902}, + {0x0000052E, 0x00000902}, + {0x00000535, 0x00000902}, + {0x00000539, 0x00000902}, + {0x0000053D, 0x00000902}, + {0x00000541, 0x00000902} +}; +static unsigned nv10TablePRAMIN_16BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000C02}, + {0x0000050D, 0x00000C02}, + {0x00000511, 0x00000B02}, + {0x00000515, 0x00000C02}, + {0x00000519, 0x00000C02}, + {0x0000051D, 0x00000C02}, + {0x0000052D, 0x00000C02}, + {0x0000052E, 0x00000C02}, + {0x00000535, 0x00000C02}, + {0x00000539, 0x00000C02}, + {0x0000053D, 0x00000C02}, + {0x00000541, 0x00000C02} +}; +static unsigned nv10TablePRAMIN_32BPP[][2] = +{ + /* 0xXXXXXX01 For MSB mono format */ + /* 0xXXXXXX02 For LSB mono format */ + {0x00000509, 0x00000E02}, + {0x0000050D, 0x00000E02}, + {0x00000511, 0x00000D02}, + {0x00000515, 0x00000E02}, + {0x00000519, 0x00000E02}, + {0x0000051D, 0x00000E02}, + {0x0000052D, 0x00000E02}, + {0x0000052E, 0x00000E02}, + {0x00000535, 0x00000E02}, + {0x00000539, 0x00000E02}, + {0x0000053D, 0x00000E02}, + {0x00000541, 0x00000E02} +}; + diff --git a/drivers/video/fbdev/riva/rivafb-i2c.c b/drivers/video/fbdev/riva/rivafb-i2c.c new file mode 100644 index 000000000000..6a183375ced1 --- /dev/null +++ b/drivers/video/fbdev/riva/rivafb-i2c.c @@ -0,0 +1,166 @@ +/* + * linux/drivers/video/riva/fbdev-i2c.c - nVidia i2c + * + * Maintained by Ani Joshi <ajoshi@shell.unixbox.com> + * + * Copyright 2004 Antonino A. Daplas <adaplas @pol.net> + * + * Based on radeonfb-i2c.c + * + * 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/delay.h> +#include <linux/pci.h> +#include <linux/fb.h> +#include <linux/jiffies.h> + +#include <asm/io.h> + +#include "rivafb.h" +#include "../edid.h" + +static void riva_gpio_setscl(void* data, int state) +{ + struct riva_i2c_chan *chan = data; + struct riva_par *par = chan->par; + u32 val; + + VGA_WR08(par->riva.PCIO, 0x3d4, chan->ddc_base + 1); + val = VGA_RD08(par->riva.PCIO, 0x3d5) & 0xf0; + + if (state) + val |= 0x20; + else + val &= ~0x20; + + VGA_WR08(par->riva.PCIO, 0x3d4, chan->ddc_base + 1); + VGA_WR08(par->riva.PCIO, 0x3d5, val | 0x1); +} + +static void riva_gpio_setsda(void* data, int state) +{ + struct riva_i2c_chan *chan = data; + struct riva_par *par = chan->par; + u32 val; + + VGA_WR08(par->riva.PCIO, 0x3d4, chan->ddc_base + 1); + val = VGA_RD08(par->riva.PCIO, 0x3d5) & 0xf0; + + if (state) + val |= 0x10; + else + val &= ~0x10; + + VGA_WR08(par->riva.PCIO, 0x3d4, chan->ddc_base + 1); + VGA_WR08(par->riva.PCIO, 0x3d5, val | 0x1); +} + +static int riva_gpio_getscl(void* data) +{ + struct riva_i2c_chan *chan = data; + struct riva_par *par = chan->par; + u32 val = 0; + + VGA_WR08(par->riva.PCIO, 0x3d4, chan->ddc_base); + if (VGA_RD08(par->riva.PCIO, 0x3d5) & 0x04) + val = 1; + + return val; +} + +static int riva_gpio_getsda(void* data) +{ + struct riva_i2c_chan *chan = data; + struct riva_par *par = chan->par; + u32 val = 0; + + VGA_WR08(par->riva.PCIO, 0x3d4, chan->ddc_base); + if (VGA_RD08(par->riva.PCIO, 0x3d5) & 0x08) + val = 1; + + return val; +} + +static int riva_setup_i2c_bus(struct riva_i2c_chan *chan, const char *name, + unsigned int i2c_class) +{ + int rc; + + strcpy(chan->adapter.name, name); + chan->adapter.owner = THIS_MODULE; + chan->adapter.class = i2c_class; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = &chan->par->pdev->dev; + chan->algo.setsda = riva_gpio_setsda; + chan->algo.setscl = riva_gpio_setscl; + chan->algo.getsda = riva_gpio_getsda; + chan->algo.getscl = riva_gpio_getscl; + chan->algo.udelay = 40; + chan->algo.timeout = msecs_to_jiffies(2); + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + /* Raise SCL and SDA */ + riva_gpio_setsda(chan, 1); + riva_gpio_setscl(chan, 1); + udelay(20); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + dev_dbg(&chan->par->pdev->dev, "I2C bus %s registered.\n", name); + else { + dev_warn(&chan->par->pdev->dev, + "Failed to register I2C bus %s.\n", name); + chan->par = NULL; + } + + return rc; +} + +void riva_create_i2c_busses(struct riva_par *par) +{ + par->chan[0].par = par; + par->chan[1].par = par; + par->chan[2].par = par; + + par->chan[0].ddc_base = 0x36; + par->chan[1].ddc_base = 0x3e; + par->chan[2].ddc_base = 0x50; + riva_setup_i2c_bus(&par->chan[0], "BUS1", I2C_CLASS_HWMON); + riva_setup_i2c_bus(&par->chan[1], "BUS2", 0); + riva_setup_i2c_bus(&par->chan[2], "BUS3", 0); +} + +void riva_delete_i2c_busses(struct riva_par *par) +{ + int i; + + for (i = 0; i < 3; i++) { + if (!par->chan[i].par) + continue; + i2c_del_adapter(&par->chan[i].adapter); + par->chan[i].par = NULL; + } +} + +int riva_probe_i2c_connector(struct riva_par *par, int conn, u8 **out_edid) +{ + u8 *edid = NULL; + + if (par->chan[conn].par) + edid = fb_ddc_read(&par->chan[conn].adapter); + + if (out_edid) + *out_edid = edid; + if (!edid) + return 1; + + return 0; +} + diff --git a/drivers/video/fbdev/riva/rivafb.h b/drivers/video/fbdev/riva/rivafb.h new file mode 100644 index 000000000000..d9f107b704c6 --- /dev/null +++ b/drivers/video/fbdev/riva/rivafb.h @@ -0,0 +1,77 @@ +#ifndef __RIVAFB_H +#define __RIVAFB_H + +#include <linux/fb.h> +#include <video/vga.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "riva_hw.h" + +/* GGI compatibility macros */ +#define NUM_SEQ_REGS 0x05 +#define NUM_CRT_REGS 0x41 +#define NUM_GRC_REGS 0x09 +#define NUM_ATC_REGS 0x15 + +/* I2C */ +#define DDC_SCL_READ_MASK (1 << 2) +#define DDC_SCL_WRITE_MASK (1 << 5) +#define DDC_SDA_READ_MASK (1 << 3) +#define DDC_SDA_WRITE_MASK (1 << 4) + +/* holds the state of the VGA core and extended Riva hw state from riva_hw.c. + * From KGI originally. */ +struct riva_regs { + u8 attr[NUM_ATC_REGS]; + u8 crtc[NUM_CRT_REGS]; + u8 gra[NUM_GRC_REGS]; + u8 seq[NUM_SEQ_REGS]; + u8 misc_output; + RIVA_HW_STATE ext; +}; + +struct riva_par; + +struct riva_i2c_chan { + struct riva_par *par; + unsigned long ddc_base; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo; +}; + +struct riva_par { + RIVA_HW_INST riva; /* interface to riva_hw.c */ + u32 pseudo_palette[16]; /* default palette */ + u32 palette[16]; /* for Riva128 */ + u8 __iomem *ctrl_base; /* virtual control register base addr */ + unsigned dclk_max; /* max DCLK */ + + struct riva_regs initial_state; /* initial startup video mode */ + struct riva_regs current_state; +#ifdef CONFIG_X86 + struct vgastate state; +#endif + struct mutex open_lock; + unsigned int ref_count; + unsigned char *EDID; + unsigned int Chipset; + int forceCRTC; + Bool SecondCRTC; + int FlatPanel; + struct pci_dev *pdev; + int cursor_reset; +#ifdef CONFIG_MTRR + struct { int vram; int vram_valid; } mtrr; +#endif + struct riva_i2c_chan chan[3]; +}; + +void riva_common_setup(struct riva_par *); +unsigned long riva_get_memlen(struct riva_par *); +unsigned long riva_get_maxdclk(struct riva_par *); +void riva_delete_i2c_busses(struct riva_par *par); +void riva_create_i2c_busses(struct riva_par *par); +int riva_probe_i2c_connector(struct riva_par *par, int conn, u8 **out_edid); + +#endif /* __RIVAFB_H */ diff --git a/drivers/video/fbdev/s1d13xxxfb.c b/drivers/video/fbdev/s1d13xxxfb.c new file mode 100644 index 000000000000..83433cb0dfba --- /dev/null +++ b/drivers/video/fbdev/s1d13xxxfb.c @@ -0,0 +1,1040 @@ +/* drivers/video/s1d13xxxfb.c + * + * (c) 2004 Simtec Electronics + * (c) 2005 Thibaut VARENE <varenet@parisc-linux.org> + * (c) 2009 Kristoffer Ericson <kristoffer.ericson@gmail.com> + * + * Driver for Epson S1D13xxx series framebuffer chips + * + * Adapted from + * linux/drivers/video/skeletonfb.c + * linux/drivers/video/epson1355fb.c + * linux/drivers/video/epson/s1d13xxxfb.c (2.4 driver by Epson) + * + * TODO: - handle dual screen display (CRT and LCD at the same time). + * - check_var(), mode change, etc. + * - probably not SMP safe :) + * - support all bitblt operations on all cards + * + * 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/platform_device.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/fb.h> +#include <linux/spinlock_types.h> +#include <linux/spinlock.h> +#include <linux/slab.h> + +#include <asm/io.h> + +#include <video/s1d13xxxfb.h> + +#define PFX "s1d13xxxfb: " +#define BLIT "s1d13xxxfb_bitblt: " + +/* + * set this to enable debugging on general functions + */ +#if 0 +#define dbg(fmt, args...) do { printk(KERN_INFO fmt, ## args); } while(0) +#else +#define dbg(fmt, args...) do { } while (0) +#endif + +/* + * set this to enable debugging on 2D acceleration + */ +#if 0 +#define dbg_blit(fmt, args...) do { printk(KERN_INFO BLIT fmt, ## args); } while (0) +#else +#define dbg_blit(fmt, args...) do { } while (0) +#endif + +/* + * we make sure only one bitblt operation is running + */ +static DEFINE_SPINLOCK(s1d13xxxfb_bitblt_lock); + +/* + * list of card production ids + */ +static const int s1d13xxxfb_prod_ids[] = { + S1D13505_PROD_ID, + S1D13506_PROD_ID, + S1D13806_PROD_ID, +}; + +/* + * List of card strings + */ +static const char *s1d13xxxfb_prod_names[] = { + "S1D13505", + "S1D13506", + "S1D13806", +}; + +/* + * here we define the default struct fb_fix_screeninfo + */ +static struct fb_fix_screeninfo s1d13xxxfb_fix = { + .id = S1D_FBID, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 0, + .ypanstep = 1, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static inline u8 +s1d13xxxfb_readreg(struct s1d13xxxfb_par *par, u16 regno) +{ +#if defined(CONFIG_PLAT_M32700UT) || defined(CONFIG_PLAT_OPSPUT) || defined(CONFIG_PLAT_MAPPI3) + regno=((regno & 1) ? (regno & ~1L) : (regno + 1)); +#endif + return readb(par->regs + regno); +} + +static inline void +s1d13xxxfb_writereg(struct s1d13xxxfb_par *par, u16 regno, u8 value) +{ +#if defined(CONFIG_PLAT_M32700UT) || defined(CONFIG_PLAT_OPSPUT) || defined(CONFIG_PLAT_MAPPI3) + regno=((regno & 1) ? (regno & ~1L) : (regno + 1)); +#endif + writeb(value, par->regs + regno); +} + +static inline void +s1d13xxxfb_runinit(struct s1d13xxxfb_par *par, + const struct s1d13xxxfb_regval *initregs, + const unsigned int size) +{ + int i; + + for (i = 0; i < size; i++) { + if ((initregs[i].addr == S1DREG_DELAYOFF) || + (initregs[i].addr == S1DREG_DELAYON)) + mdelay((int)initregs[i].value); + else { + s1d13xxxfb_writereg(par, initregs[i].addr, initregs[i].value); + } + } + + /* make sure the hardware can cope with us */ + mdelay(1); +} + +static inline void +lcd_enable(struct s1d13xxxfb_par *par, int enable) +{ + u8 mode = s1d13xxxfb_readreg(par, S1DREG_COM_DISP_MODE); + + if (enable) + mode |= 0x01; + else + mode &= ~0x01; + + s1d13xxxfb_writereg(par, S1DREG_COM_DISP_MODE, mode); +} + +static inline void +crt_enable(struct s1d13xxxfb_par *par, int enable) +{ + u8 mode = s1d13xxxfb_readreg(par, S1DREG_COM_DISP_MODE); + + if (enable) + mode |= 0x02; + else + mode &= ~0x02; + + s1d13xxxfb_writereg(par, S1DREG_COM_DISP_MODE, mode); +} + + +/************************************************************* + framebuffer control functions + *************************************************************/ +static inline void +s1d13xxxfb_setup_pseudocolour(struct fb_info *info) +{ + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->var.red.length = 4; + info->var.green.length = 4; + info->var.blue.length = 4; +} + +static inline void +s1d13xxxfb_setup_truecolour(struct fb_info *info) +{ + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->var.bits_per_pixel = 16; + + info->var.red.length = 5; + info->var.red.offset = 11; + + info->var.green.length = 6; + info->var.green.offset = 5; + + info->var.blue.length = 5; + info->var.blue.offset = 0; +} + +/** + * s1d13xxxfb_set_par - Alters the hardware state. + * @info: frame buffer structure + * + * Using the fb_var_screeninfo in fb_info we set the depth of the + * framebuffer. This function alters the par AND the + * fb_fix_screeninfo stored in fb_info. It doesn't not alter var in + * fb_info since we are using that data. This means we depend on the + * data in var inside fb_info to be supported by the hardware. + * xxxfb_check_var is always called before xxxfb_set_par to ensure this. + * + * XXX TODO: write proper s1d13xxxfb_check_var(), without which that + * function is quite useless. + */ +static int +s1d13xxxfb_set_par(struct fb_info *info) +{ + struct s1d13xxxfb_par *s1dfb = info->par; + unsigned int val; + + dbg("s1d13xxxfb_set_par: bpp=%d\n", info->var.bits_per_pixel); + + if ((s1dfb->display & 0x01)) /* LCD */ + val = s1d13xxxfb_readreg(s1dfb, S1DREG_LCD_DISP_MODE); /* read colour control */ + else /* CRT */ + val = s1d13xxxfb_readreg(s1dfb, S1DREG_CRT_DISP_MODE); /* read colour control */ + + val &= ~0x07; + + switch (info->var.bits_per_pixel) { + case 4: + dbg("pseudo colour 4\n"); + s1d13xxxfb_setup_pseudocolour(info); + val |= 2; + break; + case 8: + dbg("pseudo colour 8\n"); + s1d13xxxfb_setup_pseudocolour(info); + val |= 3; + break; + case 16: + dbg("true colour\n"); + s1d13xxxfb_setup_truecolour(info); + val |= 5; + break; + + default: + dbg("bpp not supported!\n"); + return -EINVAL; + } + + dbg("writing %02x to display mode register\n", val); + + if ((s1dfb->display & 0x01)) /* LCD */ + s1d13xxxfb_writereg(s1dfb, S1DREG_LCD_DISP_MODE, val); + else /* CRT */ + s1d13xxxfb_writereg(s1dfb, S1DREG_CRT_DISP_MODE, val); + + info->fix.line_length = info->var.xres * info->var.bits_per_pixel; + info->fix.line_length /= 8; + + dbg("setting line_length to %d\n", info->fix.line_length); + + dbg("done setup\n"); + + return 0; +} + +/** + * s1d13xxxfb_setcolreg - sets a color register. + * @regno: Which register in the CLUT we are programming + * @red: The red value which can be up to 16 bits wide + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + * + * Returns negative errno on error, or zero on success. + */ +static int +s1d13xxxfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct s1d13xxxfb_par *s1dfb = info->par; + unsigned int pseudo_val; + + if (regno >= S1D_PALETTE_SIZE) + return -EINVAL; + + dbg("s1d13xxxfb_setcolreg: %d: rgb=%d,%d,%d, tr=%d\n", + regno, red, green, blue, transp); + + if (info->var.grayscale) + red = green = blue = (19595*red + 38470*green + 7471*blue) >> 16; + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno >= 16) + return -EINVAL; + + /* deal with creating pseudo-palette entries */ + + pseudo_val = (red >> 11) << info->var.red.offset; + pseudo_val |= (green >> 10) << info->var.green.offset; + pseudo_val |= (blue >> 11) << info->var.blue.offset; + + dbg("s1d13xxxfb_setcolreg: pseudo %d, val %08x\n", + regno, pseudo_val); + +#if defined(CONFIG_PLAT_MAPPI) + ((u32 *)info->pseudo_palette)[regno] = cpu_to_le16(pseudo_val); +#else + ((u32 *)info->pseudo_palette)[regno] = pseudo_val; +#endif + + break; + case FB_VISUAL_PSEUDOCOLOR: + s1d13xxxfb_writereg(s1dfb, S1DREG_LKUP_ADDR, regno); + s1d13xxxfb_writereg(s1dfb, S1DREG_LKUP_DATA, red); + s1d13xxxfb_writereg(s1dfb, S1DREG_LKUP_DATA, green); + s1d13xxxfb_writereg(s1dfb, S1DREG_LKUP_DATA, blue); + + break; + default: + return -ENOSYS; + } + + dbg("s1d13xxxfb_setcolreg: done\n"); + + return 0; +} + +/** + * s1d13xxxfb_blank - blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + * + * Blank the screen if blank_mode != 0, else unblank. Return 0 if + * blanking succeeded, != 0 if un-/blanking failed due to e.g. a + * video mode which doesn't support it. Implements VESA suspend + * and powerdown modes on hardware that supports disabling hsync/vsync: + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + * + * Returns negative errno on error, or zero on success. + */ +static int +s1d13xxxfb_blank(int blank_mode, struct fb_info *info) +{ + struct s1d13xxxfb_par *par = info->par; + + dbg("s1d13xxxfb_blank: blank=%d, info=%p\n", blank_mode, info); + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + if ((par->display & 0x01) != 0) + lcd_enable(par, 1); + if ((par->display & 0x02) != 0) + crt_enable(par, 1); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + break; + case FB_BLANK_POWERDOWN: + lcd_enable(par, 0); + crt_enable(par, 0); + break; + default: + return -EINVAL; + } + + /* let fbcon do a soft blank for us */ + return ((blank_mode == FB_BLANK_NORMAL) ? 1 : 0); +} + +/** + * s1d13xxxfb_pan_display - Pans the display. + * @var: frame buffer variable screen structure + * @info: frame buffer structure that represents a single frame buffer + * + * Pan (or wrap, depending on the `vmode' field) the display using the + * `yoffset' field of the `var' structure (`xoffset' not yet supported). + * If the values don't fit, return -EINVAL. + * + * Returns negative errno on error, or zero on success. + */ +static int +s1d13xxxfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct s1d13xxxfb_par *par = info->par; + u32 start; + + if (var->xoffset != 0) /* not yet ... */ + return -EINVAL; + + if (var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + start = (info->fix.line_length >> 1) * var->yoffset; + + if ((par->display & 0x01)) { + /* LCD */ + s1d13xxxfb_writereg(par, S1DREG_LCD_DISP_START0, (start & 0xff)); + s1d13xxxfb_writereg(par, S1DREG_LCD_DISP_START1, ((start >> 8) & 0xff)); + s1d13xxxfb_writereg(par, S1DREG_LCD_DISP_START2, ((start >> 16) & 0x0f)); + } else { + /* CRT */ + s1d13xxxfb_writereg(par, S1DREG_CRT_DISP_START0, (start & 0xff)); + s1d13xxxfb_writereg(par, S1DREG_CRT_DISP_START1, ((start >> 8) & 0xff)); + s1d13xxxfb_writereg(par, S1DREG_CRT_DISP_START2, ((start >> 16) & 0x0f)); + } + + return 0; +} + +/************************************************************ + functions to handle bitblt acceleration + ************************************************************/ + +/** + * bltbit_wait_bitclear - waits for change in register value + * @info : frambuffer structure + * @bit : value currently in register + * @timeout : ... + * + * waits until value changes FROM bit + * + */ +static u8 +bltbit_wait_bitclear(struct fb_info *info, u8 bit, int timeout) +{ + while (s1d13xxxfb_readreg(info->par, S1DREG_BBLT_CTL0) & bit) { + udelay(10); + if (!--timeout) { + dbg_blit("wait_bitclear timeout\n"); + break; + } + } + + return timeout; +} + +/* + * s1d13xxxfb_bitblt_copyarea - accelerated copyarea function + * @info : framebuffer structure + * @area : fb_copyarea structure + * + * supports (atleast) S1D13506 + * + */ +static void +s1d13xxxfb_bitblt_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + u32 dst, src; + u32 stride; + u16 reverse = 0; + u16 sx = area->sx, sy = area->sy; + u16 dx = area->dx, dy = area->dy; + u16 width = area->width, height = area->height; + u16 bpp; + + spin_lock(&s1d13xxxfb_bitblt_lock); + + /* bytes per xres line */ + bpp = (info->var.bits_per_pixel >> 3); + stride = bpp * info->var.xres; + + /* reverse, calculate the last pixel in rectangle */ + if ((dy > sy) || ((dy == sy) && (dx >= sx))) { + dst = (((dy + height - 1) * stride) + (bpp * (dx + width - 1))); + src = (((sy + height - 1) * stride) + (bpp * (sx + width - 1))); + reverse = 1; + /* not reverse, calculate the first pixel in rectangle */ + } else { /* (y * xres) + (bpp * x) */ + dst = (dy * stride) + (bpp * dx); + src = (sy * stride) + (bpp * sx); + } + + /* set source address */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_SRC_START0, (src & 0xff)); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_SRC_START1, (src >> 8) & 0x00ff); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_SRC_START2, (src >> 16) & 0x00ff); + + /* set destination address */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_DST_START0, (dst & 0xff)); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_DST_START1, (dst >> 8) & 0x00ff); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_DST_START2, (dst >> 16) & 0x00ff); + + /* program height and width */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_WIDTH0, (width & 0xff) - 1); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_WIDTH1, (width >> 8)); + + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_HEIGHT0, (height & 0xff) - 1); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_HEIGHT1, (height >> 8)); + + /* negative direction ROP */ + if (reverse == 1) { + dbg_blit("(copyarea) negative rop\n"); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_OP, 0x03); + } else /* positive direction ROP */ { + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_OP, 0x02); + dbg_blit("(copyarea) positive rop\n"); + } + + /* set for rectangel mode and not linear */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_CTL0, 0x0); + + /* setup the bpp 1 = 16bpp, 0 = 8bpp*/ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_CTL1, (bpp >> 1)); + + /* set words per xres */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_MEM_OFF0, (stride >> 1) & 0xff); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_MEM_OFF1, (stride >> 9)); + + dbg_blit("(copyarea) dx=%d, dy=%d\n", dx, dy); + dbg_blit("(copyarea) sx=%d, sy=%d\n", sx, sy); + dbg_blit("(copyarea) width=%d, height=%d\n", width - 1, height - 1); + dbg_blit("(copyarea) stride=%d\n", stride); + dbg_blit("(copyarea) bpp=%d=0x0%d, mem_offset1=%d, mem_offset2=%d\n", bpp, (bpp >> 1), + (stride >> 1) & 0xff, stride >> 9); + + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_CC_EXP, 0x0c); + + /* initialize the engine */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_CTL0, 0x80); + + /* wait to complete */ + bltbit_wait_bitclear(info, 0x80, 8000); + + spin_unlock(&s1d13xxxfb_bitblt_lock); +} + +/** + * + * s1d13xxxfb_bitblt_solidfill - accelerated solidfill function + * @info : framebuffer structure + * @rect : fb_fillrect structure + * + * supports (atleast 13506) + * + **/ +static void +s1d13xxxfb_bitblt_solidfill(struct fb_info *info, const struct fb_fillrect *rect) +{ + u32 screen_stride, dest; + u32 fg; + u16 bpp = (info->var.bits_per_pixel >> 3); + + /* grab spinlock */ + spin_lock(&s1d13xxxfb_bitblt_lock); + + /* bytes per x width */ + screen_stride = (bpp * info->var.xres); + + /* bytes to starting point */ + dest = ((rect->dy * screen_stride) + (bpp * rect->dx)); + + dbg_blit("(solidfill) dx=%d, dy=%d, stride=%d, dest=%d\n" + "(solidfill) : rect_width=%d, rect_height=%d\n", + rect->dx, rect->dy, screen_stride, dest, + rect->width - 1, rect->height - 1); + + dbg_blit("(solidfill) : xres=%d, yres=%d, bpp=%d\n", + info->var.xres, info->var.yres, + info->var.bits_per_pixel); + dbg_blit("(solidfill) : rop=%d\n", rect->rop); + + /* We split the destination into the three registers */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_DST_START0, (dest & 0x00ff)); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_DST_START1, ((dest >> 8) & 0x00ff)); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_DST_START2, ((dest >> 16) & 0x00ff)); + + /* give information regarding rectangel width */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_WIDTH0, ((rect->width) & 0x00ff) - 1); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_WIDTH1, (rect->width >> 8)); + + /* give information regarding rectangel height */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_HEIGHT0, ((rect->height) & 0x00ff) - 1); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_HEIGHT1, (rect->height >> 8)); + + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + fg = ((u32 *)info->pseudo_palette)[rect->color]; + dbg_blit("(solidfill) truecolor/directcolor\n"); + dbg_blit("(solidfill) pseudo_palette[%d] = %d\n", rect->color, fg); + } else { + fg = rect->color; + dbg_blit("(solidfill) color = %d\n", rect->color); + } + + /* set foreground color */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_FGC0, (fg & 0xff)); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_FGC1, (fg >> 8) & 0xff); + + /* set rectangual region of memory (rectangle and not linear) */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_CTL0, 0x0); + + /* set operation mode SOLID_FILL */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_OP, BBLT_SOLID_FILL); + + /* set bits per pixel (1 = 16bpp, 0 = 8bpp) */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_CTL1, (info->var.bits_per_pixel >> 4)); + + /* set the memory offset for the bblt in word sizes */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_MEM_OFF0, (screen_stride >> 1) & 0x00ff); + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_MEM_OFF1, (screen_stride >> 9)); + + /* and away we go.... */ + s1d13xxxfb_writereg(info->par, S1DREG_BBLT_CTL0, 0x80); + + /* wait until its done */ + bltbit_wait_bitclear(info, 0x80, 8000); + + /* let others play */ + spin_unlock(&s1d13xxxfb_bitblt_lock); +} + +/* framebuffer information structures */ +static struct fb_ops s1d13xxxfb_fbops = { + .owner = THIS_MODULE, + .fb_set_par = s1d13xxxfb_set_par, + .fb_setcolreg = s1d13xxxfb_setcolreg, + .fb_blank = s1d13xxxfb_blank, + + .fb_pan_display = s1d13xxxfb_pan_display, + + /* gets replaced at chip detection time */ + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int s1d13xxxfb_width_tab[2][4] = { + {4, 8, 16, -1}, + {9, 12, 18, -1}, +}; + +/** + * s1d13xxxfb_fetch_hw_state - Configure the framebuffer according to + * hardware setup. + * @info: frame buffer structure + * + * We setup the framebuffer structures according to the current + * hardware setup. On some machines, the BIOS will have filled + * the chip registers with such info, on others, these values will + * have been written in some init procedure. In any case, the + * software values needs to match the hardware ones. This is what + * this function ensures. + * + * Note: some of the hardcoded values here might need some love to + * work on various chips, and might need to no longer be hardcoded. + */ +static void s1d13xxxfb_fetch_hw_state(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct fb_fix_screeninfo *fix = &info->fix; + struct s1d13xxxfb_par *par = info->par; + u8 panel, display; + u16 offset; + u32 xres, yres; + u32 xres_virtual, yres_virtual; + int bpp, lcd_bpp; + int is_color, is_dual, is_tft; + int lcd_enabled, crt_enabled; + + fix->type = FB_TYPE_PACKED_PIXELS; + + /* general info */ + par->display = s1d13xxxfb_readreg(par, S1DREG_COM_DISP_MODE); + crt_enabled = (par->display & 0x02) != 0; + lcd_enabled = (par->display & 0x01) != 0; + + if (lcd_enabled && crt_enabled) + printk(KERN_WARNING PFX "Warning: LCD and CRT detected, using LCD\n"); + + if (lcd_enabled) + display = s1d13xxxfb_readreg(par, S1DREG_LCD_DISP_MODE); + else /* CRT */ + display = s1d13xxxfb_readreg(par, S1DREG_CRT_DISP_MODE); + + bpp = display & 0x07; + + switch (bpp) { + case 2: /* 4 bpp */ + case 3: /* 8 bpp */ + var->bits_per_pixel = 8; + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + break; + case 5: /* 16 bpp */ + s1d13xxxfb_setup_truecolour(info); + break; + default: + dbg("bpp: %i\n", bpp); + } + fb_alloc_cmap(&info->cmap, 256, 0); + + /* LCD info */ + panel = s1d13xxxfb_readreg(par, S1DREG_PANEL_TYPE); + is_color = (panel & 0x04) != 0; + is_dual = (panel & 0x02) != 0; + is_tft = (panel & 0x01) != 0; + lcd_bpp = s1d13xxxfb_width_tab[is_tft][(panel >> 4) & 3]; + + if (lcd_enabled) { + xres = (s1d13xxxfb_readreg(par, S1DREG_LCD_DISP_HWIDTH) + 1) * 8; + yres = (s1d13xxxfb_readreg(par, S1DREG_LCD_DISP_VHEIGHT0) + + ((s1d13xxxfb_readreg(par, S1DREG_LCD_DISP_VHEIGHT1) & 0x03) << 8) + 1); + + offset = (s1d13xxxfb_readreg(par, S1DREG_LCD_MEM_OFF0) + + ((s1d13xxxfb_readreg(par, S1DREG_LCD_MEM_OFF1) & 0x7) << 8)); + } else { /* crt */ + xres = (s1d13xxxfb_readreg(par, S1DREG_CRT_DISP_HWIDTH) + 1) * 8; + yres = (s1d13xxxfb_readreg(par, S1DREG_CRT_DISP_VHEIGHT0) + + ((s1d13xxxfb_readreg(par, S1DREG_CRT_DISP_VHEIGHT1) & 0x03) << 8) + 1); + + offset = (s1d13xxxfb_readreg(par, S1DREG_CRT_MEM_OFF0) + + ((s1d13xxxfb_readreg(par, S1DREG_CRT_MEM_OFF1) & 0x7) << 8)); + } + xres_virtual = offset * 16 / var->bits_per_pixel; + yres_virtual = fix->smem_len / (offset * 2); + + var->xres = xres; + var->yres = yres; + var->xres_virtual = xres_virtual; + var->yres_virtual = yres_virtual; + var->xoffset = var->yoffset = 0; + + fix->line_length = offset * 2; + + var->grayscale = !is_color; + + var->activate = FB_ACTIVATE_NOW; + + dbg(PFX "bpp=%d, lcd_bpp=%d, " + "crt_enabled=%d, lcd_enabled=%d\n", + var->bits_per_pixel, lcd_bpp, crt_enabled, lcd_enabled); + dbg(PFX "xres=%d, yres=%d, vxres=%d, vyres=%d " + "is_color=%d, is_dual=%d, is_tft=%d\n", + xres, yres, xres_virtual, yres_virtual, is_color, is_dual, is_tft); +} + + +static int +s1d13xxxfb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct s1d13xxxfb_par *par = NULL; + + if (info) { + par = info->par; + if (par && par->regs) { + /* disable output & enable powersave */ + s1d13xxxfb_writereg(par, S1DREG_COM_DISP_MODE, 0x00); + s1d13xxxfb_writereg(par, S1DREG_PS_CNF, 0x11); + iounmap(par->regs); + } + + fb_dealloc_cmap(&info->cmap); + + if (info->screen_base) + iounmap(info->screen_base); + + framebuffer_release(info); + } + + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start +1); + release_mem_region(pdev->resource[1].start, + pdev->resource[1].end - pdev->resource[1].start +1); + return 0; +} + +static int s1d13xxxfb_probe(struct platform_device *pdev) +{ + struct s1d13xxxfb_par *default_par; + struct fb_info *info; + struct s1d13xxxfb_pdata *pdata = NULL; + int ret = 0; + int i; + u8 revision, prod_id; + + dbg("probe called: device is %p\n", pdev); + + printk(KERN_INFO "Epson S1D13XXX FB Driver\n"); + + /* enable platform-dependent hardware glue, if any */ + if (dev_get_platdata(&pdev->dev)) + pdata = dev_get_platdata(&pdev->dev); + + if (pdata && pdata->platform_init_video) + pdata->platform_init_video(); + + if (pdev->num_resources != 2) { + dev_err(&pdev->dev, "invalid num_resources: %i\n", + pdev->num_resources); + ret = -ENODEV; + goto bail; + } + + /* resource[0] is VRAM, resource[1] is registers */ + if (pdev->resource[0].flags != IORESOURCE_MEM + || pdev->resource[1].flags != IORESOURCE_MEM) { + dev_err(&pdev->dev, "invalid resource type\n"); + ret = -ENODEV; + goto bail; + } + + if (!request_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start +1, "s1d13xxxfb mem")) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto bail; + } + + if (!request_mem_region(pdev->resource[1].start, + pdev->resource[1].end - pdev->resource[1].start +1, "s1d13xxxfb regs")) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto bail; + } + + info = framebuffer_alloc(sizeof(struct s1d13xxxfb_par) + sizeof(u32) * 256, &pdev->dev); + if (!info) { + ret = -ENOMEM; + goto bail; + } + + platform_set_drvdata(pdev, info); + default_par = info->par; + default_par->regs = ioremap_nocache(pdev->resource[1].start, + pdev->resource[1].end - pdev->resource[1].start +1); + if (!default_par->regs) { + printk(KERN_ERR PFX "unable to map registers\n"); + ret = -ENOMEM; + goto bail; + } + info->pseudo_palette = default_par->pseudo_palette; + + info->screen_base = ioremap_nocache(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start +1); + + if (!info->screen_base) { + printk(KERN_ERR PFX "unable to map framebuffer\n"); + ret = -ENOMEM; + goto bail; + } + + /* production id is top 6 bits */ + prod_id = s1d13xxxfb_readreg(default_par, S1DREG_REV_CODE) >> 2; + /* revision id is lower 2 bits */ + revision = s1d13xxxfb_readreg(default_par, S1DREG_REV_CODE) & 0x3; + ret = -ENODEV; + + for (i = 0; i < ARRAY_SIZE(s1d13xxxfb_prod_ids); i++) { + if (prod_id == s1d13xxxfb_prod_ids[i]) { + /* looks like we got it in our list */ + default_par->prod_id = prod_id; + default_par->revision = revision; + ret = 0; + break; + } + } + + if (!ret) { + printk(KERN_INFO PFX "chip production id %i = %s\n", + prod_id, s1d13xxxfb_prod_names[i]); + printk(KERN_INFO PFX "chip revision %i\n", revision); + } else { + printk(KERN_INFO PFX + "unknown chip production id %i, revision %i\n", + prod_id, revision); + printk(KERN_INFO PFX "please contact maintainer\n"); + goto bail; + } + + info->fix = s1d13xxxfb_fix; + info->fix.mmio_start = pdev->resource[1].start; + info->fix.mmio_len = pdev->resource[1].end - pdev->resource[1].start + 1; + info->fix.smem_start = pdev->resource[0].start; + info->fix.smem_len = pdev->resource[0].end - pdev->resource[0].start + 1; + + printk(KERN_INFO PFX "regs mapped at 0x%p, fb %d KiB mapped at 0x%p\n", + default_par->regs, info->fix.smem_len / 1024, info->screen_base); + + info->par = default_par; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + info->fbops = &s1d13xxxfb_fbops; + + switch(prod_id) { + case S1D13506_PROD_ID: /* activate acceleration */ + s1d13xxxfb_fbops.fb_fillrect = s1d13xxxfb_bitblt_solidfill; + s1d13xxxfb_fbops.fb_copyarea = s1d13xxxfb_bitblt_copyarea; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_COPYAREA; + break; + default: + break; + } + + /* perform "manual" chip initialization, if needed */ + if (pdata && pdata->initregs) + s1d13xxxfb_runinit(info->par, pdata->initregs, pdata->initregssize); + + s1d13xxxfb_fetch_hw_state(info); + + if (register_framebuffer(info) < 0) { + ret = -EINVAL; + goto bail; + } + + fb_info(info, "%s frame buffer device\n", info->fix.id); + + return 0; + +bail: + s1d13xxxfb_remove(pdev); + return ret; + +} + +#ifdef CONFIG_PM +static int s1d13xxxfb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct s1d13xxxfb_par *s1dfb = info->par; + struct s1d13xxxfb_pdata *pdata = NULL; + + /* disable display */ + lcd_enable(s1dfb, 0); + crt_enable(s1dfb, 0); + + if (dev_get_platdata(&dev->dev)) + pdata = dev_get_platdata(&dev->dev); + +#if 0 + if (!s1dfb->disp_save) + s1dfb->disp_save = kmalloc(info->fix.smem_len, GFP_KERNEL); + + if (!s1dfb->disp_save) { + printk(KERN_ERR PFX "no memory to save screen"); + return -ENOMEM; + } + + memcpy_fromio(s1dfb->disp_save, info->screen_base, info->fix.smem_len); +#else + s1dfb->disp_save = NULL; +#endif + + if (!s1dfb->regs_save) + s1dfb->regs_save = kmalloc(info->fix.mmio_len, GFP_KERNEL); + + if (!s1dfb->regs_save) { + printk(KERN_ERR PFX "no memory to save registers"); + return -ENOMEM; + } + + /* backup all registers */ + memcpy_fromio(s1dfb->regs_save, s1dfb->regs, info->fix.mmio_len); + + /* now activate power save mode */ + s1d13xxxfb_writereg(s1dfb, S1DREG_PS_CNF, 0x11); + + if (pdata && pdata->platform_suspend_video) + return pdata->platform_suspend_video(); + else + return 0; +} + +static int s1d13xxxfb_resume(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct s1d13xxxfb_par *s1dfb = info->par; + struct s1d13xxxfb_pdata *pdata = NULL; + + /* awaken the chip */ + s1d13xxxfb_writereg(s1dfb, S1DREG_PS_CNF, 0x10); + + /* do not let go until SDRAM "wakes up" */ + while ((s1d13xxxfb_readreg(s1dfb, S1DREG_PS_STATUS) & 0x01)) + udelay(10); + + if (dev_get_platdata(&dev->dev)) + pdata = dev_get_platdata(&dev->dev); + + if (s1dfb->regs_save) { + /* will write RO regs, *should* get away with it :) */ + memcpy_toio(s1dfb->regs, s1dfb->regs_save, info->fix.mmio_len); + kfree(s1dfb->regs_save); + } + + if (s1dfb->disp_save) { + memcpy_toio(info->screen_base, s1dfb->disp_save, + info->fix.smem_len); + kfree(s1dfb->disp_save); /* XXX kmalloc()'d when? */ + } + + if ((s1dfb->display & 0x01) != 0) + lcd_enable(s1dfb, 1); + if ((s1dfb->display & 0x02) != 0) + crt_enable(s1dfb, 1); + + if (pdata && pdata->platform_resume_video) + return pdata->platform_resume_video(); + else + return 0; +} +#endif /* CONFIG_PM */ + +static struct platform_driver s1d13xxxfb_driver = { + .probe = s1d13xxxfb_probe, + .remove = s1d13xxxfb_remove, +#ifdef CONFIG_PM + .suspend = s1d13xxxfb_suspend, + .resume = s1d13xxxfb_resume, +#endif + .driver = { + .name = S1D_DEVICENAME, + }, +}; + + +static int __init +s1d13xxxfb_init(void) +{ + +#ifndef MODULE + if (fb_get_options("s1d13xxxfb", NULL)) + return -ENODEV; +#endif + + return platform_driver_register(&s1d13xxxfb_driver); +} + + +static void __exit +s1d13xxxfb_exit(void) +{ + platform_driver_unregister(&s1d13xxxfb_driver); +} + +module_init(s1d13xxxfb_init); +module_exit(s1d13xxxfb_exit); + + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Framebuffer driver for S1D13xxx devices"); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Thibaut VARENE <varenet@parisc-linux.org>"); diff --git a/drivers/video/fbdev/s3c-fb.c b/drivers/video/fbdev/s3c-fb.c new file mode 100644 index 000000000000..62acae2694a9 --- /dev/null +++ b/drivers/video/fbdev/s3c-fb.c @@ -0,0 +1,2049 @@ +/* linux/drivers/video/s3c-fb.c + * + * Copyright 2008 Openmoko Inc. + * Copyright 2008-2010 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * Samsung SoC Framebuffer driver + * + * 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. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/fb.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/platform_data/video_s3c.h> + +#include <video/samsung_fimd.h> + +/* This driver will export a number of framebuffer interfaces depending + * on the configuration passed in via the platform data. Each fb instance + * maps to a hardware window. Currently there is no support for runtime + * setting of the alpha-blending functions that each window has, so only + * window 0 is actually useful. + * + * Window 0 is treated specially, it is used for the basis of the LCD + * output timings and as the control for the output power-down state. +*/ + +/* note, the previous use of <mach/regs-fb.h> to get platform specific data + * has been replaced by using the platform device name to pick the correct + * configuration data for the system. +*/ + +#ifdef CONFIG_FB_S3C_DEBUG_REGWRITE +#undef writel +#define writel(v, r) do { \ + pr_debug("%s: %08x => %p\n", __func__, (unsigned int)v, r); \ + __raw_writel(v, r); \ +} while (0) +#endif /* FB_S3C_DEBUG_REGWRITE */ + +/* irq_flags bits */ +#define S3C_FB_VSYNC_IRQ_EN 0 + +#define VSYNC_TIMEOUT_MSEC 50 + +struct s3c_fb; + +#define VALID_BPP(x) (1 << ((x) - 1)) + +#define OSD_BASE(win, variant) ((variant).osd + ((win) * (variant).osd_stride)) +#define VIDOSD_A(win, variant) (OSD_BASE(win, variant) + 0x00) +#define VIDOSD_B(win, variant) (OSD_BASE(win, variant) + 0x04) +#define VIDOSD_C(win, variant) (OSD_BASE(win, variant) + 0x08) +#define VIDOSD_D(win, variant) (OSD_BASE(win, variant) + 0x0C) + +/** + * struct s3c_fb_variant - fb variant information + * @is_2443: Set if S3C2443/S3C2416 style hardware. + * @nr_windows: The number of windows. + * @vidtcon: The base for the VIDTCONx registers + * @wincon: The base for the WINxCON registers. + * @winmap: The base for the WINxMAP registers. + * @keycon: The abse for the WxKEYCON registers. + * @buf_start: Offset of buffer start registers. + * @buf_size: Offset of buffer size registers. + * @buf_end: Offset of buffer end registers. + * @osd: The base for the OSD registers. + * @palette: Address of palette memory, or 0 if none. + * @has_prtcon: Set if has PRTCON register. + * @has_shadowcon: Set if has SHADOWCON register. + * @has_blendcon: Set if has BLENDCON register. + * @has_clksel: Set if VIDCON0 register has CLKSEL bit. + * @has_fixvclk: Set if VIDCON1 register has FIXVCLK bits. + */ +struct s3c_fb_variant { + unsigned int is_2443:1; + unsigned short nr_windows; + unsigned int vidtcon; + unsigned short wincon; + unsigned short winmap; + unsigned short keycon; + unsigned short buf_start; + unsigned short buf_end; + unsigned short buf_size; + unsigned short osd; + unsigned short osd_stride; + unsigned short palette[S3C_FB_MAX_WIN]; + + unsigned int has_prtcon:1; + unsigned int has_shadowcon:1; + unsigned int has_blendcon:1; + unsigned int has_clksel:1; + unsigned int has_fixvclk:1; +}; + +/** + * struct s3c_fb_win_variant + * @has_osd_c: Set if has OSD C register. + * @has_osd_d: Set if has OSD D register. + * @has_osd_alpha: Set if can change alpha transparency for a window. + * @palette_sz: Size of palette in entries. + * @palette_16bpp: Set if palette is 16bits wide. + * @osd_size_off: If != 0, supports setting up OSD for a window; the appropriate + * register is located at the given offset from OSD_BASE. + * @valid_bpp: 1 bit per BPP setting to show valid bits-per-pixel. + * + * valid_bpp bit x is set if (x+1)BPP is supported. + */ +struct s3c_fb_win_variant { + unsigned int has_osd_c:1; + unsigned int has_osd_d:1; + unsigned int has_osd_alpha:1; + unsigned int palette_16bpp:1; + unsigned short osd_size_off; + unsigned short palette_sz; + u32 valid_bpp; +}; + +/** + * struct s3c_fb_driverdata - per-device type driver data for init time. + * @variant: The variant information for this driver. + * @win: The window information for each window. + */ +struct s3c_fb_driverdata { + struct s3c_fb_variant variant; + struct s3c_fb_win_variant *win[S3C_FB_MAX_WIN]; +}; + +/** + * struct s3c_fb_palette - palette information + * @r: Red bitfield. + * @g: Green bitfield. + * @b: Blue bitfield. + * @a: Alpha bitfield. + */ +struct s3c_fb_palette { + struct fb_bitfield r; + struct fb_bitfield g; + struct fb_bitfield b; + struct fb_bitfield a; +}; + +/** + * struct s3c_fb_win - per window private data for each framebuffer. + * @windata: The platform data supplied for the window configuration. + * @parent: The hardware that this window is part of. + * @fbinfo: Pointer pack to the framebuffer info for this window. + * @varint: The variant information for this window. + * @palette_buffer: Buffer/cache to hold palette entries. + * @pseudo_palette: For use in TRUECOLOUR modes for entries 0..15/ + * @index: The window number of this window. + * @palette: The bitfields for changing r/g/b into a hardware palette entry. + */ +struct s3c_fb_win { + struct s3c_fb_pd_win *windata; + struct s3c_fb *parent; + struct fb_info *fbinfo; + struct s3c_fb_palette palette; + struct s3c_fb_win_variant variant; + + u32 *palette_buffer; + u32 pseudo_palette[16]; + unsigned int index; +}; + +/** + * struct s3c_fb_vsync - vsync information + * @wait: a queue for processes waiting for vsync + * @count: vsync interrupt count + */ +struct s3c_fb_vsync { + wait_queue_head_t wait; + unsigned int count; +}; + +/** + * struct s3c_fb - overall hardware state of the hardware + * @slock: The spinlock protection for this data structure. + * @dev: The device that we bound to, for printing, etc. + * @bus_clk: The clk (hclk) feeding our interface and possibly pixclk. + * @lcd_clk: The clk (sclk) feeding pixclk. + * @regs: The mapped hardware registers. + * @variant: Variant information for this hardware. + * @enabled: A bitmask of enabled hardware windows. + * @output_on: Flag if the physical output is enabled. + * @pdata: The platform configuration data passed with the device. + * @windows: The hardware windows that have been claimed. + * @irq_no: IRQ line number + * @irq_flags: irq flags + * @vsync_info: VSYNC-related information (count, queues...) + */ +struct s3c_fb { + spinlock_t slock; + struct device *dev; + struct clk *bus_clk; + struct clk *lcd_clk; + void __iomem *regs; + struct s3c_fb_variant variant; + + unsigned char enabled; + bool output_on; + + struct s3c_fb_platdata *pdata; + struct s3c_fb_win *windows[S3C_FB_MAX_WIN]; + + int irq_no; + unsigned long irq_flags; + struct s3c_fb_vsync vsync_info; +}; + +/** + * s3c_fb_validate_win_bpp - validate the bits-per-pixel for this mode. + * @win: The device window. + * @bpp: The bit depth. + */ +static bool s3c_fb_validate_win_bpp(struct s3c_fb_win *win, unsigned int bpp) +{ + return win->variant.valid_bpp & VALID_BPP(bpp); +} + +/** + * s3c_fb_check_var() - framebuffer layer request to verify a given mode. + * @var: The screen information to verify. + * @info: The framebuffer device. + * + * Framebuffer layer call to verify the given information and allow us to + * update various information depending on the hardware capabilities. + */ +static int s3c_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct s3c_fb_win *win = info->par; + struct s3c_fb *sfb = win->parent; + + dev_dbg(sfb->dev, "checking parameters\n"); + + var->xres_virtual = max(var->xres_virtual, var->xres); + var->yres_virtual = max(var->yres_virtual, var->yres); + + if (!s3c_fb_validate_win_bpp(win, var->bits_per_pixel)) { + dev_dbg(sfb->dev, "win %d: unsupported bpp %d\n", + win->index, var->bits_per_pixel); + return -EINVAL; + } + + /* always ensure these are zero, for drop through cases below */ + var->transp.offset = 0; + var->transp.length = 0; + + switch (var->bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + if (sfb->variant.palette[win->index] != 0) { + /* non palletised, A:1,R:2,G:3,B:2 mode */ + var->red.offset = 5; + var->green.offset = 2; + var->blue.offset = 0; + var->red.length = 2; + var->green.length = 3; + var->blue.length = 2; + var->transp.offset = 7; + var->transp.length = 1; + } else { + var->red.offset = 0; + var->red.length = var->bits_per_pixel; + var->green = var->red; + var->blue = var->red; + } + break; + + case 19: + /* 666 with one bit alpha/transparency */ + var->transp.offset = 18; + var->transp.length = 1; + /* drop through */ + case 18: + var->bits_per_pixel = 32; + + /* 666 format */ + var->red.offset = 12; + var->green.offset = 6; + var->blue.offset = 0; + var->red.length = 6; + var->green.length = 6; + var->blue.length = 6; + break; + + case 16: + /* 16 bpp, 565 format */ + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + break; + + case 32: + case 28: + case 25: + var->transp.length = var->bits_per_pixel - 24; + var->transp.offset = 24; + /* drop through */ + case 24: + /* our 24bpp is unpacked, so 32bpp */ + var->bits_per_pixel = 32; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + default: + dev_err(sfb->dev, "invalid bpp\n"); + return -EINVAL; + } + + dev_dbg(sfb->dev, "%s: verified parameters\n", __func__); + return 0; +} + +/** + * s3c_fb_calc_pixclk() - calculate the divider to create the pixel clock. + * @sfb: The hardware state. + * @pixclock: The pixel clock wanted, in picoseconds. + * + * Given the specified pixel clock, work out the necessary divider to get + * close to the output frequency. + */ +static int s3c_fb_calc_pixclk(struct s3c_fb *sfb, unsigned int pixclk) +{ + unsigned long clk; + unsigned long long tmp; + unsigned int result; + + if (sfb->variant.has_clksel) + clk = clk_get_rate(sfb->bus_clk); + else + clk = clk_get_rate(sfb->lcd_clk); + + tmp = (unsigned long long)clk; + tmp *= pixclk; + + do_div(tmp, 1000000000UL); + result = (unsigned int)tmp / 1000; + + dev_dbg(sfb->dev, "pixclk=%u, clk=%lu, div=%d (%lu)\n", + pixclk, clk, result, result ? clk / result : clk); + + return result; +} + +/** + * s3c_fb_align_word() - align pixel count to word boundary + * @bpp: The number of bits per pixel + * @pix: The value to be aligned. + * + * Align the given pixel count so that it will start on an 32bit word + * boundary. + */ +static int s3c_fb_align_word(unsigned int bpp, unsigned int pix) +{ + int pix_per_word; + + if (bpp > 16) + return pix; + + pix_per_word = (8 * 32) / bpp; + return ALIGN(pix, pix_per_word); +} + +/** + * vidosd_set_size() - set OSD size for a window + * + * @win: the window to set OSD size for + * @size: OSD size register value + */ +static void vidosd_set_size(struct s3c_fb_win *win, u32 size) +{ + struct s3c_fb *sfb = win->parent; + + /* OSD can be set up if osd_size_off != 0 for this window */ + if (win->variant.osd_size_off) + writel(size, sfb->regs + OSD_BASE(win->index, sfb->variant) + + win->variant.osd_size_off); +} + +/** + * vidosd_set_alpha() - set alpha transparency for a window + * + * @win: the window to set OSD size for + * @alpha: alpha register value + */ +static void vidosd_set_alpha(struct s3c_fb_win *win, u32 alpha) +{ + struct s3c_fb *sfb = win->parent; + + if (win->variant.has_osd_alpha) + writel(alpha, sfb->regs + VIDOSD_C(win->index, sfb->variant)); +} + +/** + * shadow_protect_win() - disable updating values from shadow registers at vsync + * + * @win: window to protect registers for + * @protect: 1 to protect (disable updates) + */ +static void shadow_protect_win(struct s3c_fb_win *win, bool protect) +{ + struct s3c_fb *sfb = win->parent; + u32 reg; + + if (protect) { + if (sfb->variant.has_prtcon) { + writel(PRTCON_PROTECT, sfb->regs + PRTCON); + } else if (sfb->variant.has_shadowcon) { + reg = readl(sfb->regs + SHADOWCON); + writel(reg | SHADOWCON_WINx_PROTECT(win->index), + sfb->regs + SHADOWCON); + } + } else { + if (sfb->variant.has_prtcon) { + writel(0, sfb->regs + PRTCON); + } else if (sfb->variant.has_shadowcon) { + reg = readl(sfb->regs + SHADOWCON); + writel(reg & ~SHADOWCON_WINx_PROTECT(win->index), + sfb->regs + SHADOWCON); + } + } +} + +/** + * s3c_fb_enable() - Set the state of the main LCD output + * @sfb: The main framebuffer state. + * @enable: The state to set. + */ +static void s3c_fb_enable(struct s3c_fb *sfb, int enable) +{ + u32 vidcon0 = readl(sfb->regs + VIDCON0); + + if (enable && !sfb->output_on) + pm_runtime_get_sync(sfb->dev); + + if (enable) { + vidcon0 |= VIDCON0_ENVID | VIDCON0_ENVID_F; + } else { + /* see the note in the framebuffer datasheet about + * why you cannot take both of these bits down at the + * same time. */ + + if (vidcon0 & VIDCON0_ENVID) { + vidcon0 |= VIDCON0_ENVID; + vidcon0 &= ~VIDCON0_ENVID_F; + } + } + + writel(vidcon0, sfb->regs + VIDCON0); + + if (!enable && sfb->output_on) + pm_runtime_put_sync(sfb->dev); + + sfb->output_on = enable; +} + +/** + * s3c_fb_set_par() - framebuffer request to set new framebuffer state. + * @info: The framebuffer to change. + * + * Framebuffer layer request to set a new mode for the specified framebuffer + */ +static int s3c_fb_set_par(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct s3c_fb_win *win = info->par; + struct s3c_fb *sfb = win->parent; + void __iomem *regs = sfb->regs; + void __iomem *buf = regs; + int win_no = win->index; + u32 alpha = 0; + u32 data; + u32 pagewidth; + + dev_dbg(sfb->dev, "setting framebuffer parameters\n"); + + pm_runtime_get_sync(sfb->dev); + + shadow_protect_win(win, 1); + + switch (var->bits_per_pixel) { + case 32: + case 24: + case 16: + case 12: + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + case 8: + if (win->variant.palette_sz >= 256) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + case 1: + info->fix.visual = FB_VISUAL_MONO01; + break; + default: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + + info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; + + info->fix.xpanstep = info->var.xres_virtual > info->var.xres ? 1 : 0; + info->fix.ypanstep = info->var.yres_virtual > info->var.yres ? 1 : 0; + + /* disable the window whilst we update it */ + writel(0, regs + WINCON(win_no)); + + if (!sfb->output_on) + s3c_fb_enable(sfb, 1); + + /* write the buffer address */ + + /* start and end registers stride is 8 */ + buf = regs + win_no * 8; + + writel(info->fix.smem_start, buf + sfb->variant.buf_start); + + data = info->fix.smem_start + info->fix.line_length * var->yres; + writel(data, buf + sfb->variant.buf_end); + + pagewidth = (var->xres * var->bits_per_pixel) >> 3; + data = VIDW_BUF_SIZE_OFFSET(info->fix.line_length - pagewidth) | + VIDW_BUF_SIZE_PAGEWIDTH(pagewidth) | + VIDW_BUF_SIZE_OFFSET_E(info->fix.line_length - pagewidth) | + VIDW_BUF_SIZE_PAGEWIDTH_E(pagewidth); + writel(data, regs + sfb->variant.buf_size + (win_no * 4)); + + /* write 'OSD' registers to control position of framebuffer */ + + data = VIDOSDxA_TOPLEFT_X(0) | VIDOSDxA_TOPLEFT_Y(0) | + VIDOSDxA_TOPLEFT_X_E(0) | VIDOSDxA_TOPLEFT_Y_E(0); + writel(data, regs + VIDOSD_A(win_no, sfb->variant)); + + data = VIDOSDxB_BOTRIGHT_X(s3c_fb_align_word(var->bits_per_pixel, + var->xres - 1)) | + VIDOSDxB_BOTRIGHT_Y(var->yres - 1) | + VIDOSDxB_BOTRIGHT_X_E(s3c_fb_align_word(var->bits_per_pixel, + var->xres - 1)) | + VIDOSDxB_BOTRIGHT_Y_E(var->yres - 1); + + writel(data, regs + VIDOSD_B(win_no, sfb->variant)); + + data = var->xres * var->yres; + + alpha = VIDISD14C_ALPHA1_R(0xf) | + VIDISD14C_ALPHA1_G(0xf) | + VIDISD14C_ALPHA1_B(0xf); + + vidosd_set_alpha(win, alpha); + vidosd_set_size(win, data); + + /* Enable DMA channel for this window */ + if (sfb->variant.has_shadowcon) { + data = readl(sfb->regs + SHADOWCON); + data |= SHADOWCON_CHx_ENABLE(win_no); + writel(data, sfb->regs + SHADOWCON); + } + + data = WINCONx_ENWIN; + sfb->enabled |= (1 << win->index); + + /* note, since we have to round up the bits-per-pixel, we end up + * relying on the bitfield information for r/g/b/a to work out + * exactly which mode of operation is intended. */ + + switch (var->bits_per_pixel) { + case 1: + data |= WINCON0_BPPMODE_1BPP; + data |= WINCONx_BITSWP; + data |= WINCONx_BURSTLEN_4WORD; + break; + case 2: + data |= WINCON0_BPPMODE_2BPP; + data |= WINCONx_BITSWP; + data |= WINCONx_BURSTLEN_8WORD; + break; + case 4: + data |= WINCON0_BPPMODE_4BPP; + data |= WINCONx_BITSWP; + data |= WINCONx_BURSTLEN_8WORD; + break; + case 8: + if (var->transp.length != 0) + data |= WINCON1_BPPMODE_8BPP_1232; + else + data |= WINCON0_BPPMODE_8BPP_PALETTE; + data |= WINCONx_BURSTLEN_8WORD; + data |= WINCONx_BYTSWP; + break; + case 16: + if (var->transp.length != 0) + data |= WINCON1_BPPMODE_16BPP_A1555; + else + data |= WINCON0_BPPMODE_16BPP_565; + data |= WINCONx_HAWSWP; + data |= WINCONx_BURSTLEN_16WORD; + break; + case 24: + case 32: + if (var->red.length == 6) { + if (var->transp.length != 0) + data |= WINCON1_BPPMODE_19BPP_A1666; + else + data |= WINCON1_BPPMODE_18BPP_666; + } else if (var->transp.length == 1) + data |= WINCON1_BPPMODE_25BPP_A1888 + | WINCON1_BLD_PIX; + else if ((var->transp.length == 4) || + (var->transp.length == 8)) + data |= WINCON1_BPPMODE_28BPP_A4888 + | WINCON1_BLD_PIX | WINCON1_ALPHA_SEL; + else + data |= WINCON0_BPPMODE_24BPP_888; + + data |= WINCONx_WSWP; + data |= WINCONx_BURSTLEN_16WORD; + break; + } + + /* Enable the colour keying for the window below this one */ + if (win_no > 0) { + u32 keycon0_data = 0, keycon1_data = 0; + void __iomem *keycon = regs + sfb->variant.keycon; + + keycon0_data = ~(WxKEYCON0_KEYBL_EN | + WxKEYCON0_KEYEN_F | + WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0); + + keycon1_data = WxKEYCON1_COLVAL(0xffffff); + + keycon += (win_no - 1) * 8; + + writel(keycon0_data, keycon + WKEYCON0); + writel(keycon1_data, keycon + WKEYCON1); + } + + writel(data, regs + sfb->variant.wincon + (win_no * 4)); + writel(0x0, regs + sfb->variant.winmap + (win_no * 4)); + + /* Set alpha value width */ + if (sfb->variant.has_blendcon) { + data = readl(sfb->regs + BLENDCON); + data &= ~BLENDCON_NEW_MASK; + if (var->transp.length > 4) + data |= BLENDCON_NEW_8BIT_ALPHA_VALUE; + else + data |= BLENDCON_NEW_4BIT_ALPHA_VALUE; + writel(data, sfb->regs + BLENDCON); + } + + shadow_protect_win(win, 0); + + pm_runtime_put_sync(sfb->dev); + + return 0; +} + +/** + * s3c_fb_update_palette() - set or schedule a palette update. + * @sfb: The hardware information. + * @win: The window being updated. + * @reg: The palette index being changed. + * @value: The computed palette value. + * + * Change the value of a palette register, either by directly writing to + * the palette (this requires the palette RAM to be disconnected from the + * hardware whilst this is in progress) or schedule the update for later. + * + * At the moment, since we have no VSYNC interrupt support, we simply set + * the palette entry directly. + */ +static void s3c_fb_update_palette(struct s3c_fb *sfb, + struct s3c_fb_win *win, + unsigned int reg, + u32 value) +{ + void __iomem *palreg; + u32 palcon; + + palreg = sfb->regs + sfb->variant.palette[win->index]; + + dev_dbg(sfb->dev, "%s: win %d, reg %d (%p): %08x\n", + __func__, win->index, reg, palreg, value); + + win->palette_buffer[reg] = value; + + palcon = readl(sfb->regs + WPALCON); + writel(palcon | WPALCON_PAL_UPDATE, sfb->regs + WPALCON); + + if (win->variant.palette_16bpp) + writew(value, palreg + (reg * 2)); + else + writel(value, palreg + (reg * 4)); + + writel(palcon, sfb->regs + WPALCON); +} + +static inline unsigned int chan_to_field(unsigned int chan, + struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +/** + * s3c_fb_setcolreg() - framebuffer layer request to change palette. + * @regno: The palette index to change. + * @red: The red field for the palette data. + * @green: The green field for the palette data. + * @blue: The blue field for the palette data. + * @trans: The transparency (alpha) field for the palette data. + * @info: The framebuffer being changed. + */ +static int s3c_fb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct s3c_fb_win *win = info->par; + struct s3c_fb *sfb = win->parent; + unsigned int val; + + dev_dbg(sfb->dev, "%s: win %d: %d => rgb=%d/%d/%d\n", + __func__, win->index, regno, red, green, blue); + + pm_runtime_get_sync(sfb->dev); + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* true-colour, use pseudo-palette */ + + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + } + break; + + case FB_VISUAL_PSEUDOCOLOR: + if (regno < win->variant.palette_sz) { + val = chan_to_field(red, &win->palette.r); + val |= chan_to_field(green, &win->palette.g); + val |= chan_to_field(blue, &win->palette.b); + + s3c_fb_update_palette(sfb, win, regno, val); + } + + break; + + default: + pm_runtime_put_sync(sfb->dev); + return 1; /* unknown type */ + } + + pm_runtime_put_sync(sfb->dev); + return 0; +} + +/** + * s3c_fb_blank() - blank or unblank the given window + * @blank_mode: The blank state from FB_BLANK_* + * @info: The framebuffer to blank. + * + * Framebuffer layer request to change the power state. + */ +static int s3c_fb_blank(int blank_mode, struct fb_info *info) +{ + struct s3c_fb_win *win = info->par; + struct s3c_fb *sfb = win->parent; + unsigned int index = win->index; + u32 wincon; + u32 output_on = sfb->output_on; + + dev_dbg(sfb->dev, "blank mode %d\n", blank_mode); + + pm_runtime_get_sync(sfb->dev); + + wincon = readl(sfb->regs + sfb->variant.wincon + (index * 4)); + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + wincon &= ~WINCONx_ENWIN; + sfb->enabled &= ~(1 << index); + /* fall through to FB_BLANK_NORMAL */ + + case FB_BLANK_NORMAL: + /* disable the DMA and display 0x0 (black) */ + shadow_protect_win(win, 1); + writel(WINxMAP_MAP | WINxMAP_MAP_COLOUR(0x0), + sfb->regs + sfb->variant.winmap + (index * 4)); + shadow_protect_win(win, 0); + break; + + case FB_BLANK_UNBLANK: + shadow_protect_win(win, 1); + writel(0x0, sfb->regs + sfb->variant.winmap + (index * 4)); + shadow_protect_win(win, 0); + wincon |= WINCONx_ENWIN; + sfb->enabled |= (1 << index); + break; + + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + default: + pm_runtime_put_sync(sfb->dev); + return 1; + } + + shadow_protect_win(win, 1); + writel(wincon, sfb->regs + sfb->variant.wincon + (index * 4)); + + /* Check the enabled state to see if we need to be running the + * main LCD interface, as if there are no active windows then + * it is highly likely that we also do not need to output + * anything. + */ + s3c_fb_enable(sfb, sfb->enabled ? 1 : 0); + shadow_protect_win(win, 0); + + pm_runtime_put_sync(sfb->dev); + + return output_on == sfb->output_on; +} + +/** + * s3c_fb_pan_display() - Pan the display. + * + * Note that the offsets can be written to the device at any time, as their + * values are latched at each vsync automatically. This also means that only + * the last call to this function will have any effect on next vsync, but + * there is no need to sleep waiting for it to prevent tearing. + * + * @var: The screen information to verify. + * @info: The framebuffer device. + */ +static int s3c_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct s3c_fb_win *win = info->par; + struct s3c_fb *sfb = win->parent; + void __iomem *buf = sfb->regs + win->index * 8; + unsigned int start_boff, end_boff; + + pm_runtime_get_sync(sfb->dev); + + /* Offset in bytes to the start of the displayed area */ + start_boff = var->yoffset * info->fix.line_length; + /* X offset depends on the current bpp */ + if (info->var.bits_per_pixel >= 8) { + start_boff += var->xoffset * (info->var.bits_per_pixel >> 3); + } else { + switch (info->var.bits_per_pixel) { + case 4: + start_boff += var->xoffset >> 1; + break; + case 2: + start_boff += var->xoffset >> 2; + break; + case 1: + start_boff += var->xoffset >> 3; + break; + default: + dev_err(sfb->dev, "invalid bpp\n"); + pm_runtime_put_sync(sfb->dev); + return -EINVAL; + } + } + /* Offset in bytes to the end of the displayed area */ + end_boff = start_boff + info->var.yres * info->fix.line_length; + + /* Temporarily turn off per-vsync update from shadow registers until + * both start and end addresses are updated to prevent corruption */ + shadow_protect_win(win, 1); + + writel(info->fix.smem_start + start_boff, buf + sfb->variant.buf_start); + writel(info->fix.smem_start + end_boff, buf + sfb->variant.buf_end); + + shadow_protect_win(win, 0); + + pm_runtime_put_sync(sfb->dev); + return 0; +} + +/** + * s3c_fb_enable_irq() - enable framebuffer interrupts + * @sfb: main hardware state + */ +static void s3c_fb_enable_irq(struct s3c_fb *sfb) +{ + void __iomem *regs = sfb->regs; + u32 irq_ctrl_reg; + + if (!test_and_set_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) { + /* IRQ disabled, enable it */ + irq_ctrl_reg = readl(regs + VIDINTCON0); + + irq_ctrl_reg |= VIDINTCON0_INT_ENABLE; + irq_ctrl_reg |= VIDINTCON0_INT_FRAME; + + irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL0_MASK; + irq_ctrl_reg |= VIDINTCON0_FRAMESEL0_VSYNC; + irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL1_MASK; + irq_ctrl_reg |= VIDINTCON0_FRAMESEL1_NONE; + + writel(irq_ctrl_reg, regs + VIDINTCON0); + } +} + +/** + * s3c_fb_disable_irq() - disable framebuffer interrupts + * @sfb: main hardware state + */ +static void s3c_fb_disable_irq(struct s3c_fb *sfb) +{ + void __iomem *regs = sfb->regs; + u32 irq_ctrl_reg; + + if (test_and_clear_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) { + /* IRQ enabled, disable it */ + irq_ctrl_reg = readl(regs + VIDINTCON0); + + irq_ctrl_reg &= ~VIDINTCON0_INT_FRAME; + irq_ctrl_reg &= ~VIDINTCON0_INT_ENABLE; + + writel(irq_ctrl_reg, regs + VIDINTCON0); + } +} + +static irqreturn_t s3c_fb_irq(int irq, void *dev_id) +{ + struct s3c_fb *sfb = dev_id; + void __iomem *regs = sfb->regs; + u32 irq_sts_reg; + + spin_lock(&sfb->slock); + + irq_sts_reg = readl(regs + VIDINTCON1); + + if (irq_sts_reg & VIDINTCON1_INT_FRAME) { + + /* VSYNC interrupt, accept it */ + writel(VIDINTCON1_INT_FRAME, regs + VIDINTCON1); + + sfb->vsync_info.count++; + wake_up_interruptible(&sfb->vsync_info.wait); + } + + /* We only support waiting for VSYNC for now, so it's safe + * to always disable irqs here. + */ + s3c_fb_disable_irq(sfb); + + spin_unlock(&sfb->slock); + return IRQ_HANDLED; +} + +/** + * s3c_fb_wait_for_vsync() - sleep until next VSYNC interrupt or timeout + * @sfb: main hardware state + * @crtc: head index. + */ +static int s3c_fb_wait_for_vsync(struct s3c_fb *sfb, u32 crtc) +{ + unsigned long count; + int ret; + + if (crtc != 0) + return -ENODEV; + + pm_runtime_get_sync(sfb->dev); + + count = sfb->vsync_info.count; + s3c_fb_enable_irq(sfb); + ret = wait_event_interruptible_timeout(sfb->vsync_info.wait, + count != sfb->vsync_info.count, + msecs_to_jiffies(VSYNC_TIMEOUT_MSEC)); + + pm_runtime_put_sync(sfb->dev); + + if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + +static int s3c_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct s3c_fb_win *win = info->par; + struct s3c_fb *sfb = win->parent; + int ret; + u32 crtc; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + if (get_user(crtc, (u32 __user *)arg)) { + ret = -EFAULT; + break; + } + + ret = s3c_fb_wait_for_vsync(sfb, crtc); + break; + default: + ret = -ENOTTY; + } + + return ret; +} + +static struct fb_ops s3c_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = s3c_fb_check_var, + .fb_set_par = s3c_fb_set_par, + .fb_blank = s3c_fb_blank, + .fb_setcolreg = s3c_fb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_pan_display = s3c_fb_pan_display, + .fb_ioctl = s3c_fb_ioctl, +}; + +/** + * s3c_fb_missing_pixclock() - calculates pixel clock + * @mode: The video mode to change. + * + * Calculate the pixel clock when none has been given through platform data. + */ +static void s3c_fb_missing_pixclock(struct fb_videomode *mode) +{ + u64 pixclk = 1000000000000ULL; + u32 div; + + div = mode->left_margin + mode->hsync_len + mode->right_margin + + mode->xres; + div *= mode->upper_margin + mode->vsync_len + mode->lower_margin + + mode->yres; + div *= mode->refresh ? : 60; + + do_div(pixclk, div); + + mode->pixclock = pixclk; +} + +/** + * s3c_fb_alloc_memory() - allocate display memory for framebuffer window + * @sfb: The base resources for the hardware. + * @win: The window to initialise memory for. + * + * Allocate memory for the given framebuffer. + */ +static int s3c_fb_alloc_memory(struct s3c_fb *sfb, struct s3c_fb_win *win) +{ + struct s3c_fb_pd_win *windata = win->windata; + unsigned int real_size, virt_size, size; + struct fb_info *fbi = win->fbinfo; + dma_addr_t map_dma; + + dev_dbg(sfb->dev, "allocating memory for display\n"); + + real_size = windata->xres * windata->yres; + virt_size = windata->virtual_x * windata->virtual_y; + + dev_dbg(sfb->dev, "real_size=%u (%u.%u), virt_size=%u (%u.%u)\n", + real_size, windata->xres, windata->yres, + virt_size, windata->virtual_x, windata->virtual_y); + + size = (real_size > virt_size) ? real_size : virt_size; + size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp; + size /= 8; + + fbi->fix.smem_len = size; + size = PAGE_ALIGN(size); + + dev_dbg(sfb->dev, "want %u bytes for window\n", size); + + fbi->screen_base = dma_alloc_writecombine(sfb->dev, size, + &map_dma, GFP_KERNEL); + if (!fbi->screen_base) + return -ENOMEM; + + dev_dbg(sfb->dev, "mapped %x to %p\n", + (unsigned int)map_dma, fbi->screen_base); + + memset(fbi->screen_base, 0x0, size); + fbi->fix.smem_start = map_dma; + + return 0; +} + +/** + * s3c_fb_free_memory() - free the display memory for the given window + * @sfb: The base resources for the hardware. + * @win: The window to free the display memory for. + * + * Free the display memory allocated by s3c_fb_alloc_memory(). + */ +static void s3c_fb_free_memory(struct s3c_fb *sfb, struct s3c_fb_win *win) +{ + struct fb_info *fbi = win->fbinfo; + + if (fbi->screen_base) + dma_free_writecombine(sfb->dev, PAGE_ALIGN(fbi->fix.smem_len), + fbi->screen_base, fbi->fix.smem_start); +} + +/** + * s3c_fb_release_win() - release resources for a framebuffer window. + * @win: The window to cleanup the resources for. + * + * Release the resources that where claimed for the hardware window, + * such as the framebuffer instance and any memory claimed for it. + */ +static void s3c_fb_release_win(struct s3c_fb *sfb, struct s3c_fb_win *win) +{ + u32 data; + + if (win->fbinfo) { + if (sfb->variant.has_shadowcon) { + data = readl(sfb->regs + SHADOWCON); + data &= ~SHADOWCON_CHx_ENABLE(win->index); + data &= ~SHADOWCON_CHx_LOCAL_ENABLE(win->index); + writel(data, sfb->regs + SHADOWCON); + } + unregister_framebuffer(win->fbinfo); + if (win->fbinfo->cmap.len) + fb_dealloc_cmap(&win->fbinfo->cmap); + s3c_fb_free_memory(sfb, win); + framebuffer_release(win->fbinfo); + } +} + +/** + * s3c_fb_probe_win() - register an hardware window + * @sfb: The base resources for the hardware + * @variant: The variant information for this window. + * @res: Pointer to where to place the resultant window. + * + * Allocate and do the basic initialisation for one of the hardware's graphics + * windows. + */ +static int s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no, + struct s3c_fb_win_variant *variant, + struct s3c_fb_win **res) +{ + struct fb_var_screeninfo *var; + struct fb_videomode initmode; + struct s3c_fb_pd_win *windata; + struct s3c_fb_win *win; + struct fb_info *fbinfo; + int palette_size; + int ret; + + dev_dbg(sfb->dev, "probing window %d, variant %p\n", win_no, variant); + + init_waitqueue_head(&sfb->vsync_info.wait); + + palette_size = variant->palette_sz * 4; + + fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) + + palette_size * sizeof(u32), sfb->dev); + if (!fbinfo) { + dev_err(sfb->dev, "failed to allocate framebuffer\n"); + return -ENOENT; + } + + windata = sfb->pdata->win[win_no]; + initmode = *sfb->pdata->vtiming; + + WARN_ON(windata->max_bpp == 0); + WARN_ON(windata->xres == 0); + WARN_ON(windata->yres == 0); + + win = fbinfo->par; + *res = win; + var = &fbinfo->var; + win->variant = *variant; + win->fbinfo = fbinfo; + win->parent = sfb; + win->windata = windata; + win->index = win_no; + win->palette_buffer = (u32 *)(win + 1); + + ret = s3c_fb_alloc_memory(sfb, win); + if (ret) { + dev_err(sfb->dev, "failed to allocate display memory\n"); + return ret; + } + + /* setup the r/b/g positions for the window's palette */ + if (win->variant.palette_16bpp) { + /* Set RGB 5:6:5 as default */ + win->palette.r.offset = 11; + win->palette.r.length = 5; + win->palette.g.offset = 5; + win->palette.g.length = 6; + win->palette.b.offset = 0; + win->palette.b.length = 5; + + } else { + /* Set 8bpp or 8bpp and 1bit alpha */ + win->palette.r.offset = 16; + win->palette.r.length = 8; + win->palette.g.offset = 8; + win->palette.g.length = 8; + win->palette.b.offset = 0; + win->palette.b.length = 8; + } + + /* setup the initial video mode from the window */ + initmode.xres = windata->xres; + initmode.yres = windata->yres; + fb_videomode_to_var(&fbinfo->var, &initmode); + + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.accel = FB_ACCEL_NONE; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + fbinfo->var.bits_per_pixel = windata->default_bpp; + fbinfo->fbops = &s3c_fb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + fbinfo->pseudo_palette = &win->pseudo_palette; + + /* prepare to actually start the framebuffer */ + + ret = s3c_fb_check_var(&fbinfo->var, fbinfo); + if (ret < 0) { + dev_err(sfb->dev, "check_var failed on initial video params\n"); + return ret; + } + + /* create initial colour map */ + + ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1); + if (ret == 0) + fb_set_cmap(&fbinfo->cmap, fbinfo); + else + dev_err(sfb->dev, "failed to allocate fb cmap\n"); + + s3c_fb_set_par(fbinfo); + + dev_dbg(sfb->dev, "about to register framebuffer\n"); + + /* run the check_var and set_par on our configuration. */ + + ret = register_framebuffer(fbinfo); + if (ret < 0) { + dev_err(sfb->dev, "failed to register framebuffer\n"); + return ret; + } + + dev_info(sfb->dev, "window %d: fb %s\n", win_no, fbinfo->fix.id); + + return 0; +} + +/** + * s3c_fb_set_rgb_timing() - set video timing for rgb interface. + * @sfb: The base resources for the hardware. + * + * Set horizontal and vertical lcd rgb interface timing. + */ +static void s3c_fb_set_rgb_timing(struct s3c_fb *sfb) +{ + struct fb_videomode *vmode = sfb->pdata->vtiming; + void __iomem *regs = sfb->regs; + int clkdiv; + u32 data; + + if (!vmode->pixclock) + s3c_fb_missing_pixclock(vmode); + + clkdiv = s3c_fb_calc_pixclk(sfb, vmode->pixclock); + + data = sfb->pdata->vidcon0; + data &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR); + + if (clkdiv > 1) + data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR; + else + data &= ~VIDCON0_CLKDIR; /* 1:1 clock */ + + if (sfb->variant.is_2443) + data |= (1 << 5); + writel(data, regs + VIDCON0); + + data = VIDTCON0_VBPD(vmode->upper_margin - 1) | + VIDTCON0_VFPD(vmode->lower_margin - 1) | + VIDTCON0_VSPW(vmode->vsync_len - 1); + writel(data, regs + sfb->variant.vidtcon); + + data = VIDTCON1_HBPD(vmode->left_margin - 1) | + VIDTCON1_HFPD(vmode->right_margin - 1) | + VIDTCON1_HSPW(vmode->hsync_len - 1); + writel(data, regs + sfb->variant.vidtcon + 4); + + data = VIDTCON2_LINEVAL(vmode->yres - 1) | + VIDTCON2_HOZVAL(vmode->xres - 1) | + VIDTCON2_LINEVAL_E(vmode->yres - 1) | + VIDTCON2_HOZVAL_E(vmode->xres - 1); + writel(data, regs + sfb->variant.vidtcon + 8); +} + +/** + * s3c_fb_clear_win() - clear hardware window registers. + * @sfb: The base resources for the hardware. + * @win: The window to process. + * + * Reset the specific window registers to a known state. + */ +static void s3c_fb_clear_win(struct s3c_fb *sfb, int win) +{ + void __iomem *regs = sfb->regs; + u32 reg; + + writel(0, regs + sfb->variant.wincon + (win * 4)); + writel(0, regs + VIDOSD_A(win, sfb->variant)); + writel(0, regs + VIDOSD_B(win, sfb->variant)); + writel(0, regs + VIDOSD_C(win, sfb->variant)); + + if (sfb->variant.has_shadowcon) { + reg = readl(sfb->regs + SHADOWCON); + reg &= ~(SHADOWCON_WINx_PROTECT(win) | + SHADOWCON_CHx_ENABLE(win) | + SHADOWCON_CHx_LOCAL_ENABLE(win)); + writel(reg, sfb->regs + SHADOWCON); + } +} + +static int s3c_fb_probe(struct platform_device *pdev) +{ + const struct platform_device_id *platid; + struct s3c_fb_driverdata *fbdrv; + struct device *dev = &pdev->dev; + struct s3c_fb_platdata *pd; + struct s3c_fb *sfb; + struct resource *res; + int win; + int ret = 0; + u32 reg; + + platid = platform_get_device_id(pdev); + fbdrv = (struct s3c_fb_driverdata *)platid->driver_data; + + if (fbdrv->variant.nr_windows > S3C_FB_MAX_WIN) { + dev_err(dev, "too many windows, cannot attach\n"); + return -EINVAL; + } + + pd = dev_get_platdata(&pdev->dev); + if (!pd) { + dev_err(dev, "no platform data specified\n"); + return -EINVAL; + } + + sfb = devm_kzalloc(dev, sizeof(struct s3c_fb), GFP_KERNEL); + if (!sfb) { + dev_err(dev, "no memory for framebuffers\n"); + return -ENOMEM; + } + + dev_dbg(dev, "allocate new framebuffer %p\n", sfb); + + sfb->dev = dev; + sfb->pdata = pd; + sfb->variant = fbdrv->variant; + + spin_lock_init(&sfb->slock); + + sfb->bus_clk = devm_clk_get(dev, "lcd"); + if (IS_ERR(sfb->bus_clk)) { + dev_err(dev, "failed to get bus clock\n"); + return PTR_ERR(sfb->bus_clk); + } + + clk_prepare_enable(sfb->bus_clk); + + if (!sfb->variant.has_clksel) { + sfb->lcd_clk = devm_clk_get(dev, "sclk_fimd"); + if (IS_ERR(sfb->lcd_clk)) { + dev_err(dev, "failed to get lcd clock\n"); + ret = PTR_ERR(sfb->lcd_clk); + goto err_bus_clk; + } + + clk_prepare_enable(sfb->lcd_clk); + } + + pm_runtime_enable(sfb->dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sfb->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(sfb->regs)) { + ret = PTR_ERR(sfb->regs); + goto err_lcd_clk; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "failed to acquire irq resource\n"); + ret = -ENOENT; + goto err_lcd_clk; + } + sfb->irq_no = res->start; + ret = devm_request_irq(dev, sfb->irq_no, s3c_fb_irq, + 0, "s3c_fb", sfb); + if (ret) { + dev_err(dev, "irq request failed\n"); + goto err_lcd_clk; + } + + dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs); + + platform_set_drvdata(pdev, sfb); + pm_runtime_get_sync(sfb->dev); + + /* setup gpio and output polarity controls */ + + pd->setup_gpio(); + + writel(pd->vidcon1, sfb->regs + VIDCON1); + + /* set video clock running at under-run */ + if (sfb->variant.has_fixvclk) { + reg = readl(sfb->regs + VIDCON1); + reg &= ~VIDCON1_VCLK_MASK; + reg |= VIDCON1_VCLK_RUN; + writel(reg, sfb->regs + VIDCON1); + } + + /* zero all windows before we do anything */ + + for (win = 0; win < fbdrv->variant.nr_windows; win++) + s3c_fb_clear_win(sfb, win); + + /* initialise colour key controls */ + for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) { + void __iomem *regs = sfb->regs + sfb->variant.keycon; + + regs += (win * 8); + writel(0xffffff, regs + WKEYCON0); + writel(0xffffff, regs + WKEYCON1); + } + + s3c_fb_set_rgb_timing(sfb); + + /* we have the register setup, start allocating framebuffers */ + + for (win = 0; win < fbdrv->variant.nr_windows; win++) { + if (!pd->win[win]) + continue; + + ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win], + &sfb->windows[win]); + if (ret < 0) { + dev_err(dev, "failed to create window %d\n", win); + for (; win >= 0; win--) + s3c_fb_release_win(sfb, sfb->windows[win]); + goto err_pm_runtime; + } + } + + platform_set_drvdata(pdev, sfb); + pm_runtime_put_sync(sfb->dev); + + return 0; + +err_pm_runtime: + pm_runtime_put_sync(sfb->dev); + +err_lcd_clk: + pm_runtime_disable(sfb->dev); + + if (!sfb->variant.has_clksel) + clk_disable_unprepare(sfb->lcd_clk); + +err_bus_clk: + clk_disable_unprepare(sfb->bus_clk); + + return ret; +} + +/** + * s3c_fb_remove() - Cleanup on module finalisation + * @pdev: The platform device we are bound to. + * + * Shutdown and then release all the resources that the driver allocated + * on initialisation. + */ +static int s3c_fb_remove(struct platform_device *pdev) +{ + struct s3c_fb *sfb = platform_get_drvdata(pdev); + int win; + + pm_runtime_get_sync(sfb->dev); + + for (win = 0; win < S3C_FB_MAX_WIN; win++) + if (sfb->windows[win]) + s3c_fb_release_win(sfb, sfb->windows[win]); + + if (!sfb->variant.has_clksel) + clk_disable_unprepare(sfb->lcd_clk); + + clk_disable_unprepare(sfb->bus_clk); + + pm_runtime_put_sync(sfb->dev); + pm_runtime_disable(sfb->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int s3c_fb_suspend(struct device *dev) +{ + struct s3c_fb *sfb = dev_get_drvdata(dev); + struct s3c_fb_win *win; + int win_no; + + pm_runtime_get_sync(sfb->dev); + + for (win_no = S3C_FB_MAX_WIN - 1; win_no >= 0; win_no--) { + win = sfb->windows[win_no]; + if (!win) + continue; + + /* use the blank function to push into power-down */ + s3c_fb_blank(FB_BLANK_POWERDOWN, win->fbinfo); + } + + if (!sfb->variant.has_clksel) + clk_disable_unprepare(sfb->lcd_clk); + + clk_disable_unprepare(sfb->bus_clk); + + pm_runtime_put_sync(sfb->dev); + + return 0; +} + +static int s3c_fb_resume(struct device *dev) +{ + struct s3c_fb *sfb = dev_get_drvdata(dev); + struct s3c_fb_platdata *pd = sfb->pdata; + struct s3c_fb_win *win; + int win_no; + u32 reg; + + pm_runtime_get_sync(sfb->dev); + + clk_prepare_enable(sfb->bus_clk); + + if (!sfb->variant.has_clksel) + clk_prepare_enable(sfb->lcd_clk); + + /* setup gpio and output polarity controls */ + pd->setup_gpio(); + writel(pd->vidcon1, sfb->regs + VIDCON1); + + /* set video clock running at under-run */ + if (sfb->variant.has_fixvclk) { + reg = readl(sfb->regs + VIDCON1); + reg &= ~VIDCON1_VCLK_MASK; + reg |= VIDCON1_VCLK_RUN; + writel(reg, sfb->regs + VIDCON1); + } + + /* zero all windows before we do anything */ + for (win_no = 0; win_no < sfb->variant.nr_windows; win_no++) + s3c_fb_clear_win(sfb, win_no); + + for (win_no = 0; win_no < sfb->variant.nr_windows - 1; win_no++) { + void __iomem *regs = sfb->regs + sfb->variant.keycon; + win = sfb->windows[win_no]; + if (!win) + continue; + + shadow_protect_win(win, 1); + regs += (win_no * 8); + writel(0xffffff, regs + WKEYCON0); + writel(0xffffff, regs + WKEYCON1); + shadow_protect_win(win, 0); + } + + s3c_fb_set_rgb_timing(sfb); + + /* restore framebuffers */ + for (win_no = 0; win_no < S3C_FB_MAX_WIN; win_no++) { + win = sfb->windows[win_no]; + if (!win) + continue; + + dev_dbg(dev, "resuming window %d\n", win_no); + s3c_fb_set_par(win->fbinfo); + } + + pm_runtime_put_sync(sfb->dev); + + return 0; +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int s3c_fb_runtime_suspend(struct device *dev) +{ + struct s3c_fb *sfb = dev_get_drvdata(dev); + + if (!sfb->variant.has_clksel) + clk_disable_unprepare(sfb->lcd_clk); + + clk_disable_unprepare(sfb->bus_clk); + + return 0; +} + +static int s3c_fb_runtime_resume(struct device *dev) +{ + struct s3c_fb *sfb = dev_get_drvdata(dev); + struct s3c_fb_platdata *pd = sfb->pdata; + + clk_prepare_enable(sfb->bus_clk); + + if (!sfb->variant.has_clksel) + clk_prepare_enable(sfb->lcd_clk); + + /* setup gpio and output polarity controls */ + pd->setup_gpio(); + writel(pd->vidcon1, sfb->regs + VIDCON1); + + return 0; +} +#endif + +#define VALID_BPP124 (VALID_BPP(1) | VALID_BPP(2) | VALID_BPP(4)) +#define VALID_BPP1248 (VALID_BPP124 | VALID_BPP(8)) + +static struct s3c_fb_win_variant s3c_fb_data_64xx_wins[] = { + [0] = { + .has_osd_c = 1, + .osd_size_off = 0x8, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(24)), + }, + [1] = { + .has_osd_c = 1, + .has_osd_d = 1, + .osd_size_off = 0xc, + .has_osd_alpha = 1, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(28)), + }, + [2] = { + .has_osd_c = 1, + .has_osd_d = 1, + .osd_size_off = 0xc, + .has_osd_alpha = 1, + .palette_sz = 16, + .palette_16bpp = 1, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(28)), + }, + [3] = { + .has_osd_c = 1, + .has_osd_alpha = 1, + .palette_sz = 16, + .palette_16bpp = 1, + .valid_bpp = (VALID_BPP124 | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(28)), + }, + [4] = { + .has_osd_c = 1, + .has_osd_alpha = 1, + .palette_sz = 4, + .palette_16bpp = 1, + .valid_bpp = (VALID_BPP(1) | VALID_BPP(2) | + VALID_BPP(16) | VALID_BPP(18) | + VALID_BPP(19) | VALID_BPP(24) | + VALID_BPP(25) | VALID_BPP(28)), + }, +}; + +static struct s3c_fb_win_variant s3c_fb_data_s5p_wins[] = { + [0] = { + .has_osd_c = 1, + .osd_size_off = 0x8, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(13) | + VALID_BPP(15) | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(32)), + }, + [1] = { + .has_osd_c = 1, + .has_osd_d = 1, + .osd_size_off = 0xc, + .has_osd_alpha = 1, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(13) | + VALID_BPP(15) | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(32)), + }, + [2] = { + .has_osd_c = 1, + .has_osd_d = 1, + .osd_size_off = 0xc, + .has_osd_alpha = 1, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(13) | + VALID_BPP(15) | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(32)), + }, + [3] = { + .has_osd_c = 1, + .has_osd_alpha = 1, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(13) | + VALID_BPP(15) | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(32)), + }, + [4] = { + .has_osd_c = 1, + .has_osd_alpha = 1, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(13) | + VALID_BPP(15) | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(32)), + }, +}; + +static struct s3c_fb_driverdata s3c_fb_data_64xx = { + .variant = { + .nr_windows = 5, + .vidtcon = VIDTCON0, + .wincon = WINCON(0), + .winmap = WINxMAP(0), + .keycon = WKEYCON, + .osd = VIDOSD_BASE, + .osd_stride = 16, + .buf_start = VIDW_BUF_START(0), + .buf_size = VIDW_BUF_SIZE(0), + .buf_end = VIDW_BUF_END(0), + + .palette = { + [0] = 0x400, + [1] = 0x800, + [2] = 0x300, + [3] = 0x320, + [4] = 0x340, + }, + + .has_prtcon = 1, + .has_clksel = 1, + }, + .win[0] = &s3c_fb_data_64xx_wins[0], + .win[1] = &s3c_fb_data_64xx_wins[1], + .win[2] = &s3c_fb_data_64xx_wins[2], + .win[3] = &s3c_fb_data_64xx_wins[3], + .win[4] = &s3c_fb_data_64xx_wins[4], +}; + +static struct s3c_fb_driverdata s3c_fb_data_s5pc100 = { + .variant = { + .nr_windows = 5, + .vidtcon = VIDTCON0, + .wincon = WINCON(0), + .winmap = WINxMAP(0), + .keycon = WKEYCON, + .osd = VIDOSD_BASE, + .osd_stride = 16, + .buf_start = VIDW_BUF_START(0), + .buf_size = VIDW_BUF_SIZE(0), + .buf_end = VIDW_BUF_END(0), + + .palette = { + [0] = 0x2400, + [1] = 0x2800, + [2] = 0x2c00, + [3] = 0x3000, + [4] = 0x3400, + }, + + .has_prtcon = 1, + .has_blendcon = 1, + .has_clksel = 1, + }, + .win[0] = &s3c_fb_data_s5p_wins[0], + .win[1] = &s3c_fb_data_s5p_wins[1], + .win[2] = &s3c_fb_data_s5p_wins[2], + .win[3] = &s3c_fb_data_s5p_wins[3], + .win[4] = &s3c_fb_data_s5p_wins[4], +}; + +static struct s3c_fb_driverdata s3c_fb_data_s5pv210 = { + .variant = { + .nr_windows = 5, + .vidtcon = VIDTCON0, + .wincon = WINCON(0), + .winmap = WINxMAP(0), + .keycon = WKEYCON, + .osd = VIDOSD_BASE, + .osd_stride = 16, + .buf_start = VIDW_BUF_START(0), + .buf_size = VIDW_BUF_SIZE(0), + .buf_end = VIDW_BUF_END(0), + + .palette = { + [0] = 0x2400, + [1] = 0x2800, + [2] = 0x2c00, + [3] = 0x3000, + [4] = 0x3400, + }, + + .has_shadowcon = 1, + .has_blendcon = 1, + .has_clksel = 1, + .has_fixvclk = 1, + }, + .win[0] = &s3c_fb_data_s5p_wins[0], + .win[1] = &s3c_fb_data_s5p_wins[1], + .win[2] = &s3c_fb_data_s5p_wins[2], + .win[3] = &s3c_fb_data_s5p_wins[3], + .win[4] = &s3c_fb_data_s5p_wins[4], +}; + +static struct s3c_fb_driverdata s3c_fb_data_exynos4 = { + .variant = { + .nr_windows = 5, + .vidtcon = VIDTCON0, + .wincon = WINCON(0), + .winmap = WINxMAP(0), + .keycon = WKEYCON, + .osd = VIDOSD_BASE, + .osd_stride = 16, + .buf_start = VIDW_BUF_START(0), + .buf_size = VIDW_BUF_SIZE(0), + .buf_end = VIDW_BUF_END(0), + + .palette = { + [0] = 0x2400, + [1] = 0x2800, + [2] = 0x2c00, + [3] = 0x3000, + [4] = 0x3400, + }, + + .has_shadowcon = 1, + .has_blendcon = 1, + .has_fixvclk = 1, + }, + .win[0] = &s3c_fb_data_s5p_wins[0], + .win[1] = &s3c_fb_data_s5p_wins[1], + .win[2] = &s3c_fb_data_s5p_wins[2], + .win[3] = &s3c_fb_data_s5p_wins[3], + .win[4] = &s3c_fb_data_s5p_wins[4], +}; + +static struct s3c_fb_driverdata s3c_fb_data_exynos5 = { + .variant = { + .nr_windows = 5, + .vidtcon = FIMD_V8_VIDTCON0, + .wincon = WINCON(0), + .winmap = WINxMAP(0), + .keycon = WKEYCON, + .osd = VIDOSD_BASE, + .osd_stride = 16, + .buf_start = VIDW_BUF_START(0), + .buf_size = VIDW_BUF_SIZE(0), + .buf_end = VIDW_BUF_END(0), + + .palette = { + [0] = 0x2400, + [1] = 0x2800, + [2] = 0x2c00, + [3] = 0x3000, + [4] = 0x3400, + }, + .has_shadowcon = 1, + .has_blendcon = 1, + .has_fixvclk = 1, + }, + .win[0] = &s3c_fb_data_s5p_wins[0], + .win[1] = &s3c_fb_data_s5p_wins[1], + .win[2] = &s3c_fb_data_s5p_wins[2], + .win[3] = &s3c_fb_data_s5p_wins[3], + .win[4] = &s3c_fb_data_s5p_wins[4], +}; + +/* S3C2443/S3C2416 style hardware */ +static struct s3c_fb_driverdata s3c_fb_data_s3c2443 = { + .variant = { + .nr_windows = 2, + .is_2443 = 1, + + .vidtcon = 0x08, + .wincon = 0x14, + .winmap = 0xd0, + .keycon = 0xb0, + .osd = 0x28, + .osd_stride = 12, + .buf_start = 0x64, + .buf_size = 0x94, + .buf_end = 0x7c, + + .palette = { + [0] = 0x400, + [1] = 0x800, + }, + .has_clksel = 1, + }, + .win[0] = &(struct s3c_fb_win_variant) { + .palette_sz = 256, + .valid_bpp = VALID_BPP1248 | VALID_BPP(16) | VALID_BPP(24), + }, + .win[1] = &(struct s3c_fb_win_variant) { + .has_osd_c = 1, + .has_osd_alpha = 1, + .palette_sz = 256, + .valid_bpp = (VALID_BPP1248 | VALID_BPP(16) | + VALID_BPP(18) | VALID_BPP(19) | + VALID_BPP(24) | VALID_BPP(25) | + VALID_BPP(28)), + }, +}; + +static struct s3c_fb_driverdata s3c_fb_data_s5p64x0 = { + .variant = { + .nr_windows = 3, + .vidtcon = VIDTCON0, + .wincon = WINCON(0), + .winmap = WINxMAP(0), + .keycon = WKEYCON, + .osd = VIDOSD_BASE, + .osd_stride = 16, + .buf_start = VIDW_BUF_START(0), + .buf_size = VIDW_BUF_SIZE(0), + .buf_end = VIDW_BUF_END(0), + + .palette = { + [0] = 0x2400, + [1] = 0x2800, + [2] = 0x2c00, + }, + + .has_blendcon = 1, + .has_fixvclk = 1, + }, + .win[0] = &s3c_fb_data_s5p_wins[0], + .win[1] = &s3c_fb_data_s5p_wins[1], + .win[2] = &s3c_fb_data_s5p_wins[2], +}; + +static struct platform_device_id s3c_fb_driver_ids[] = { + { + .name = "s3c-fb", + .driver_data = (unsigned long)&s3c_fb_data_64xx, + }, { + .name = "s5pc100-fb", + .driver_data = (unsigned long)&s3c_fb_data_s5pc100, + }, { + .name = "s5pv210-fb", + .driver_data = (unsigned long)&s3c_fb_data_s5pv210, + }, { + .name = "exynos4-fb", + .driver_data = (unsigned long)&s3c_fb_data_exynos4, + }, { + .name = "exynos5-fb", + .driver_data = (unsigned long)&s3c_fb_data_exynos5, + }, { + .name = "s3c2443-fb", + .driver_data = (unsigned long)&s3c_fb_data_s3c2443, + }, { + .name = "s5p64x0-fb", + .driver_data = (unsigned long)&s3c_fb_data_s5p64x0, + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, s3c_fb_driver_ids); + +static const struct dev_pm_ops s3cfb_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(s3c_fb_suspend, s3c_fb_resume) + SET_RUNTIME_PM_OPS(s3c_fb_runtime_suspend, s3c_fb_runtime_resume, + NULL) +}; + +static struct platform_driver s3c_fb_driver = { + .probe = s3c_fb_probe, + .remove = s3c_fb_remove, + .id_table = s3c_fb_driver_ids, + .driver = { + .name = "s3c-fb", + .owner = THIS_MODULE, + .pm = &s3cfb_pm_ops, + }, +}; + +module_platform_driver(s3c_fb_driver); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("Samsung S3C SoC Framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c-fb"); diff --git a/drivers/video/fbdev/s3c2410fb.c b/drivers/video/fbdev/s3c2410fb.c new file mode 100644 index 000000000000..81af5a63e9e1 --- /dev/null +++ b/drivers/video/fbdev/s3c2410fb.c @@ -0,0 +1,1146 @@ +/* linux/drivers/video/s3c2410fb.c + * Copyright (c) 2004,2005 Arnaud Patard + * Copyright (c) 2004-2008 Ben Dooks + * + * S3C2410 LCD Framebuffer Driver + * + * 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. + * + * Driver based on skeletonfb.c, sa1100fb.c and others. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.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/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/io.h> + +#include <asm/div64.h> + +#include <asm/mach/map.h> +#include <mach/regs-lcd.h> +#include <mach/regs-gpio.h> +#include <mach/fb.h> + +#ifdef CONFIG_PM +#include <linux/pm.h> +#endif + +#include "s3c2410fb.h" + +/* Debugging stuff */ +#ifdef CONFIG_FB_S3C2410_DEBUG +static int debug = 1; +#else +static int debug; +#endif + +#define dprintk(msg...) \ +do { \ + if (debug) \ + pr_debug(msg); \ +} while (0) + +/* useful functions */ + +static int is_s3c2412(struct s3c2410fb_info *fbi) +{ + return (fbi->drv_type == DRV_S3C2412); +} + +/* s3c2410fb_set_lcdaddr + * + * initialise lcd controller address pointers + */ +static void s3c2410fb_set_lcdaddr(struct fb_info *info) +{ + unsigned long saddr1, saddr2, saddr3; + struct s3c2410fb_info *fbi = info->par; + void __iomem *regs = fbi->io; + + saddr1 = info->fix.smem_start >> 1; + saddr2 = info->fix.smem_start; + saddr2 += info->fix.line_length * info->var.yres; + saddr2 >>= 1; + + saddr3 = S3C2410_OFFSIZE(0) | + S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff); + + dprintk("LCDSADDR1 = 0x%08lx\n", saddr1); + dprintk("LCDSADDR2 = 0x%08lx\n", saddr2); + dprintk("LCDSADDR3 = 0x%08lx\n", saddr3); + + writel(saddr1, regs + S3C2410_LCDSADDR1); + writel(saddr2, regs + S3C2410_LCDSADDR2); + writel(saddr3, regs + S3C2410_LCDSADDR3); +} + +/* s3c2410fb_calc_pixclk() + * + * calculate divisor for clk->pixclk + */ +static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi, + unsigned long pixclk) +{ + unsigned long clk = fbi->clk_rate; + unsigned long long div; + + /* pixclk is in picoseconds, our clock is in Hz + * + * Hz -> picoseconds is / 10^-12 + */ + + div = (unsigned long long)clk * pixclk; + div >>= 12; /* div / 2^12 */ + do_div(div, 625 * 625UL * 625); /* div / 5^12 */ + + dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div); + return div; +} + +/* + * s3c2410fb_check_var(): + * Get the video params out of 'var'. If a value doesn't fit, round it up, + * if it's too big, return -EINVAL. + * + */ +static int s3c2410fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + struct s3c2410fb_mach_info *mach_info = dev_get_platdata(fbi->dev); + struct s3c2410fb_display *display = NULL; + struct s3c2410fb_display *default_display = mach_info->displays + + mach_info->default_display; + int type = default_display->type; + unsigned i; + + dprintk("check_var(var=%p, info=%p)\n", var, info); + + /* validate x/y resolution */ + /* choose default mode if possible */ + if (var->yres == default_display->yres && + var->xres == default_display->xres && + var->bits_per_pixel == default_display->bpp) + display = default_display; + else + for (i = 0; i < mach_info->num_displays; i++) + if (type == mach_info->displays[i].type && + var->yres == mach_info->displays[i].yres && + var->xres == mach_info->displays[i].xres && + var->bits_per_pixel == mach_info->displays[i].bpp) { + display = mach_info->displays + i; + break; + } + + if (!display) { + dprintk("wrong resolution or depth %dx%d at %d bpp\n", + var->xres, var->yres, var->bits_per_pixel); + return -EINVAL; + } + + /* it is always the size as the display */ + var->xres_virtual = display->xres; + var->yres_virtual = display->yres; + var->height = display->height; + var->width = display->width; + + /* copy lcd settings */ + var->pixclock = display->pixclock; + var->left_margin = display->left_margin; + var->right_margin = display->right_margin; + var->upper_margin = display->upper_margin; + var->lower_margin = display->lower_margin; + var->vsync_len = display->vsync_len; + var->hsync_len = display->hsync_len; + + fbi->regs.lcdcon5 = display->lcdcon5; + /* set display type */ + fbi->regs.lcdcon1 = display->type; + + var->transp.offset = 0; + var->transp.length = 0; + /* set r/g/b positions */ + switch (var->bits_per_pixel) { + case 1: + case 2: + case 4: + var->red.offset = 0; + var->red.length = var->bits_per_pixel; + var->green = var->red; + var->blue = var->red; + break; + case 8: + if (display->type != S3C2410_LCDCON1_TFT) { + /* 8 bpp 332 */ + var->red.length = 3; + var->red.offset = 5; + var->green.length = 3; + var->green.offset = 2; + var->blue.length = 2; + var->blue.offset = 0; + } else { + var->red.offset = 0; + var->red.length = 8; + var->green = var->red; + var->blue = var->red; + } + break; + case 12: + /* 12 bpp 444 */ + var->red.length = 4; + var->red.offset = 8; + var->green.length = 4; + var->green.offset = 4; + var->blue.length = 4; + var->blue.offset = 0; + break; + + default: + case 16: + if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) { + /* 16 bpp, 565 format */ + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + } else { + /* 16 bpp, 5551 format */ + var->red.offset = 11; + var->green.offset = 6; + var->blue.offset = 1; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + } + break; + case 32: + /* 24 bpp 888 and 8 dummy */ + var->red.length = 8; + var->red.offset = 16; + var->green.length = 8; + var->green.offset = 8; + var->blue.length = 8; + var->blue.offset = 0; + break; + } + return 0; +} + +/* s3c2410fb_calculate_stn_lcd_regs + * + * calculate register values from var settings + */ +static void s3c2410fb_calculate_stn_lcd_regs(const struct fb_info *info, + struct s3c2410fb_hw *regs) +{ + const struct s3c2410fb_info *fbi = info->par; + const struct fb_var_screeninfo *var = &info->var; + int type = regs->lcdcon1 & ~S3C2410_LCDCON1_TFT; + int hs = var->xres >> 2; + unsigned wdly = (var->left_margin >> 4) - 1; + unsigned wlh = (var->hsync_len >> 4) - 1; + + if (type != S3C2410_LCDCON1_STN4) + hs >>= 1; + + switch (var->bits_per_pixel) { + case 1: + regs->lcdcon1 |= S3C2410_LCDCON1_STN1BPP; + break; + case 2: + regs->lcdcon1 |= S3C2410_LCDCON1_STN2GREY; + break; + case 4: + regs->lcdcon1 |= S3C2410_LCDCON1_STN4GREY; + break; + case 8: + regs->lcdcon1 |= S3C2410_LCDCON1_STN8BPP; + hs *= 3; + break; + case 12: + regs->lcdcon1 |= S3C2410_LCDCON1_STN12BPP; + hs *= 3; + break; + + default: + /* invalid pixel depth */ + dev_err(fbi->dev, "invalid bpp %d\n", + var->bits_per_pixel); + } + /* update X/Y info */ + dprintk("setting horz: lft=%d, rt=%d, sync=%d\n", + var->left_margin, var->right_margin, var->hsync_len); + + regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1); + + if (wdly > 3) + wdly = 3; + + if (wlh > 3) + wlh = 3; + + regs->lcdcon3 = S3C2410_LCDCON3_WDLY(wdly) | + S3C2410_LCDCON3_LINEBLANK(var->right_margin / 8) | + S3C2410_LCDCON3_HOZVAL(hs - 1); + + regs->lcdcon4 = S3C2410_LCDCON4_WLH(wlh); +} + +/* s3c2410fb_calculate_tft_lcd_regs + * + * calculate register values from var settings + */ +static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info, + struct s3c2410fb_hw *regs) +{ + const struct s3c2410fb_info *fbi = info->par; + const struct fb_var_screeninfo *var = &info->var; + + switch (var->bits_per_pixel) { + case 1: + regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP; + break; + case 2: + regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP; + break; + case 4: + regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP; + break; + case 8: + regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP; + regs->lcdcon5 |= S3C2410_LCDCON5_BSWP | + S3C2410_LCDCON5_FRM565; + regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP; + break; + case 16: + regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP; + regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP; + regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP; + break; + case 32: + regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP; + regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP | + S3C2410_LCDCON5_HWSWP | + S3C2410_LCDCON5_BPP24BL); + break; + default: + /* invalid pixel depth */ + dev_err(fbi->dev, "invalid bpp %d\n", + var->bits_per_pixel); + } + /* update X/Y info */ + dprintk("setting vert: up=%d, low=%d, sync=%d\n", + var->upper_margin, var->lower_margin, var->vsync_len); + + dprintk("setting horz: lft=%d, rt=%d, sync=%d\n", + var->left_margin, var->right_margin, var->hsync_len); + + regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) | + S3C2410_LCDCON2_VBPD(var->upper_margin - 1) | + S3C2410_LCDCON2_VFPD(var->lower_margin - 1) | + S3C2410_LCDCON2_VSPW(var->vsync_len - 1); + + regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) | + S3C2410_LCDCON3_HFPD(var->left_margin - 1) | + S3C2410_LCDCON3_HOZVAL(var->xres - 1); + + regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1); +} + +/* s3c2410fb_activate_var + * + * activate (set) the controller from the given framebuffer + * information + */ +static void s3c2410fb_activate_var(struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + void __iomem *regs = fbi->io; + int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT; + struct fb_var_screeninfo *var = &info->var; + int clkdiv; + + clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2); + + dprintk("%s: var->xres = %d\n", __func__, var->xres); + dprintk("%s: var->yres = %d\n", __func__, var->yres); + dprintk("%s: var->bpp = %d\n", __func__, var->bits_per_pixel); + + if (type == S3C2410_LCDCON1_TFT) { + s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs); + --clkdiv; + if (clkdiv < 0) + clkdiv = 0; + } else { + s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs); + if (clkdiv < 2) + clkdiv = 2; + } + + fbi->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv); + + /* write new registers */ + + dprintk("new register set:\n"); + dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1); + dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2); + dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3); + dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4); + dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5); + + writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, + regs + S3C2410_LCDCON1); + writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2); + writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3); + writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4); + writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5); + + /* set lcd address pointers */ + s3c2410fb_set_lcdaddr(info); + + fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID, + writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1); +} + +/* + * s3c2410fb_set_par - Alters the hardware state. + * @info: frame buffer structure that represents a single frame buffer + * + */ +static int s3c2410fb_set_par(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + switch (var->bits_per_pixel) { + case 32: + case 16: + case 12: + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + case 1: + info->fix.visual = FB_VISUAL_MONO01; + break; + default: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + + info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; + + /* activate this new configuration */ + + s3c2410fb_activate_var(info); + return 0; +} + +static void schedule_palette_update(struct s3c2410fb_info *fbi, + unsigned int regno, unsigned int val) +{ + unsigned long flags; + unsigned long irqen; + void __iomem *irq_base = fbi->irq_base; + + local_irq_save(flags); + + fbi->palette_buffer[regno] = val; + + if (!fbi->palette_ready) { + fbi->palette_ready = 1; + + /* enable IRQ */ + irqen = readl(irq_base + S3C24XX_LCDINTMSK); + irqen &= ~S3C2410_LCDINT_FRSYNC; + writel(irqen, irq_base + S3C24XX_LCDINTMSK); + } + + local_irq_restore(flags); +} + +/* from pxafb.c */ +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 s3c2410fb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + void __iomem *regs = fbi->io; + unsigned int val; + + /* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n", + regno, red, green, blue); */ + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* true-colour, use pseudo-palette */ + + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + } + break; + + case FB_VISUAL_PSEUDOCOLOR: + if (regno < 256) { + /* currently assume RGB 5-6-5 mode */ + + val = (red >> 0) & 0xf800; + val |= (green >> 5) & 0x07e0; + val |= (blue >> 11) & 0x001f; + + writel(val, regs + S3C2410_TFTPAL(regno)); + schedule_palette_update(fbi, regno, val); + } + + break; + + default: + return 1; /* unknown type */ + } + + return 0; +} + +/* s3c2410fb_lcd_enable + * + * shutdown the lcd controller + */ +static void s3c2410fb_lcd_enable(struct s3c2410fb_info *fbi, int enable) +{ + unsigned long flags; + + local_irq_save(flags); + + if (enable) + fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID; + else + fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID; + + writel(fbi->regs.lcdcon1, fbi->io + S3C2410_LCDCON1); + + local_irq_restore(flags); +} + + +/* + * s3c2410fb_blank + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + * + * Blank the screen if blank_mode != 0, else unblank. Return 0 if + * blanking succeeded, != 0 if un-/blanking failed due to e.g. a + * video mode which doesn't support it. Implements VESA suspend + * and powerdown modes on hardware that supports disabling hsync/vsync: + * + * Returns negative errno on error, or zero on success. + * + */ +static int s3c2410fb_blank(int blank_mode, struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + void __iomem *tpal_reg = fbi->io; + + dprintk("blank(mode=%d, info=%p)\n", blank_mode, info); + + tpal_reg += is_s3c2412(fbi) ? S3C2412_TPAL : S3C2410_TPAL; + + if (blank_mode == FB_BLANK_POWERDOWN) + s3c2410fb_lcd_enable(fbi, 0); + else + s3c2410fb_lcd_enable(fbi, 1); + + if (blank_mode == FB_BLANK_UNBLANK) + writel(0x0, tpal_reg); + else { + dprintk("setting TPAL to output 0x000000\n"); + writel(S3C2410_TPAL_EN, tpal_reg); + } + + return 0; +} + +static int s3c2410fb_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", debug ? "on" : "off"); +} + +static int s3c2410fb_debug_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || + strnicmp(buf, "1", 1) == 0) { + debug = 1; + dev_dbg(dev, "s3c2410fb: Debug On"); + } else if (strnicmp(buf, "off", 3) == 0 || + strnicmp(buf, "0", 1) == 0) { + debug = 0; + dev_dbg(dev, "s3c2410fb: Debug Off"); + } else { + return -EINVAL; + } + + return len; +} + +static DEVICE_ATTR(debug, 0666, s3c2410fb_debug_show, s3c2410fb_debug_store); + +static struct fb_ops s3c2410fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = s3c2410fb_check_var, + .fb_set_par = s3c2410fb_set_par, + .fb_blank = s3c2410fb_blank, + .fb_setcolreg = s3c2410fb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* + * s3c2410fb_map_video_memory(): + * Allocates the DRAM memory for the frame buffer. This buffer is + * remapped into a non-cached, non-buffered, memory region to + * allow palette and pixel writes to occur without flushing the + * cache. Once this area is remapped, all virtual memory + * access to the video memory should occur at the new region. + */ +static int s3c2410fb_map_video_memory(struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + dma_addr_t map_dma; + unsigned map_size = PAGE_ALIGN(info->fix.smem_len); + + dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size); + + info->screen_base = dma_alloc_writecombine(fbi->dev, map_size, + &map_dma, GFP_KERNEL); + + if (info->screen_base) { + /* prevent initial garbage on screen */ + dprintk("map_video_memory: clear %p:%08x\n", + info->screen_base, map_size); + memset(info->screen_base, 0x00, map_size); + + info->fix.smem_start = map_dma; + + dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n", + info->fix.smem_start, info->screen_base, map_size); + } + + return info->screen_base ? 0 : -ENOMEM; +} + +static inline void s3c2410fb_unmap_video_memory(struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + + dma_free_writecombine(fbi->dev, PAGE_ALIGN(info->fix.smem_len), + info->screen_base, info->fix.smem_start); +} + +static inline void modify_gpio(void __iomem *reg, + unsigned long set, unsigned long mask) +{ + unsigned long tmp; + + tmp = readl(reg) & ~mask; + writel(tmp | set, reg); +} + +/* + * s3c2410fb_init_registers - Initialise all LCD-related registers + */ +static int s3c2410fb_init_registers(struct fb_info *info) +{ + struct s3c2410fb_info *fbi = info->par; + struct s3c2410fb_mach_info *mach_info = dev_get_platdata(fbi->dev); + unsigned long flags; + void __iomem *regs = fbi->io; + void __iomem *tpal; + void __iomem *lpcsel; + + if (is_s3c2412(fbi)) { + tpal = regs + S3C2412_TPAL; + lpcsel = regs + S3C2412_TCONSEL; + } else { + tpal = regs + S3C2410_TPAL; + lpcsel = regs + S3C2410_LPCSEL; + } + + /* Initialise LCD with values from haret */ + + local_irq_save(flags); + + /* modify the gpio(s) with interrupts set (bjd) */ + + modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask); + modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask); + modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask); + modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask); + + local_irq_restore(flags); + + dprintk("LPCSEL = 0x%08lx\n", mach_info->lpcsel); + writel(mach_info->lpcsel, lpcsel); + + dprintk("replacing TPAL %08x\n", readl(tpal)); + + /* ensure temporary palette disabled */ + writel(0x00, tpal); + + return 0; +} + +static void s3c2410fb_write_palette(struct s3c2410fb_info *fbi) +{ + unsigned int i; + void __iomem *regs = fbi->io; + + fbi->palette_ready = 0; + + for (i = 0; i < 256; i++) { + unsigned long ent = fbi->palette_buffer[i]; + if (ent == PALETTE_BUFF_CLEAR) + continue; + + writel(ent, regs + S3C2410_TFTPAL(i)); + + /* it seems the only way to know exactly + * if the palette wrote ok, is to check + * to see if the value verifies ok + */ + + if (readw(regs + S3C2410_TFTPAL(i)) == ent) + fbi->palette_buffer[i] = PALETTE_BUFF_CLEAR; + else + fbi->palette_ready = 1; /* retry */ + } +} + +static irqreturn_t s3c2410fb_irq(int irq, void *dev_id) +{ + struct s3c2410fb_info *fbi = dev_id; + void __iomem *irq_base = fbi->irq_base; + unsigned long lcdirq = readl(irq_base + S3C24XX_LCDINTPND); + + if (lcdirq & S3C2410_LCDINT_FRSYNC) { + if (fbi->palette_ready) + s3c2410fb_write_palette(fbi); + + writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDINTPND); + writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDSRCPND); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_CPU_FREQ + +static int s3c2410fb_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct s3c2410fb_info *info; + struct fb_info *fbinfo; + long delta_f; + + info = container_of(nb, struct s3c2410fb_info, freq_transition); + fbinfo = platform_get_drvdata(to_platform_device(info->dev)); + + /* work out change, <0 for speed-up */ + delta_f = info->clk_rate - clk_get_rate(info->clk); + + if ((val == CPUFREQ_POSTCHANGE && delta_f > 0) || + (val == CPUFREQ_PRECHANGE && delta_f < 0)) { + info->clk_rate = clk_get_rate(info->clk); + s3c2410fb_activate_var(fbinfo); + } + + return 0; +} + +static inline int s3c2410fb_cpufreq_register(struct s3c2410fb_info *info) +{ + info->freq_transition.notifier_call = s3c2410fb_cpufreq_transition; + + return cpufreq_register_notifier(&info->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +static inline void s3c2410fb_cpufreq_deregister(struct s3c2410fb_info *info) +{ + cpufreq_unregister_notifier(&info->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +#else +static inline int s3c2410fb_cpufreq_register(struct s3c2410fb_info *info) +{ + return 0; +} + +static inline void s3c2410fb_cpufreq_deregister(struct s3c2410fb_info *info) +{ +} +#endif + + +static const char driver_name[] = "s3c2410fb"; + +static int s3c24xxfb_probe(struct platform_device *pdev, + enum s3c_drv_type drv_type) +{ + struct s3c2410fb_info *info; + struct s3c2410fb_display *display; + struct fb_info *fbinfo; + struct s3c2410fb_mach_info *mach_info; + struct resource *res; + int ret; + int irq; + int i; + int size; + u32 lcdcon1; + + mach_info = dev_get_platdata(&pdev->dev); + if (mach_info == NULL) { + dev_err(&pdev->dev, + "no platform data for lcd, cannot attach\n"); + return -EINVAL; + } + + if (mach_info->default_display >= mach_info->num_displays) { + dev_err(&pdev->dev, "default is %d but only %d displays\n", + mach_info->default_display, mach_info->num_displays); + return -EINVAL; + } + + display = mach_info->displays + mach_info->default_display; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for device\n"); + return -ENOENT; + } + + fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); + if (!fbinfo) + return -ENOMEM; + + platform_set_drvdata(pdev, fbinfo); + + info = fbinfo->par; + info->dev = &pdev->dev; + info->drv_type = drv_type; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory registers\n"); + ret = -ENXIO; + goto dealloc_fb; + } + + size = resource_size(res); + info->mem = request_mem_region(res->start, size, pdev->name); + if (info->mem == NULL) { + dev_err(&pdev->dev, "failed to get memory region\n"); + ret = -ENOENT; + goto dealloc_fb; + } + + info->io = ioremap(res->start, size); + if (info->io == NULL) { + dev_err(&pdev->dev, "ioremap() of registers failed\n"); + ret = -ENXIO; + goto release_mem; + } + + if (drv_type == DRV_S3C2412) + info->irq_base = info->io + S3C2412_LCDINTBASE; + else + info->irq_base = info->io + S3C2410_LCDINTBASE; + + dprintk("devinit\n"); + + strcpy(fbinfo->fix.id, driver_name); + + /* Stop the video */ + lcdcon1 = readl(info->io + S3C2410_LCDCON1); + writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1); + + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_NONE; + + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + fbinfo->fbops = &s3c2410fb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + fbinfo->pseudo_palette = &info->pseudo_pal; + + for (i = 0; i < 256; i++) + info->palette_buffer[i] = PALETTE_BUFF_CLEAR; + + ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info); + if (ret) { + dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret); + ret = -EBUSY; + goto release_regs; + } + + info->clk = clk_get(NULL, "lcd"); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed to get lcd clock source\n"); + ret = PTR_ERR(info->clk); + goto release_irq; + } + + clk_enable(info->clk); + dprintk("got and enabled clock\n"); + + usleep_range(1000, 1100); + + info->clk_rate = clk_get_rate(info->clk); + + /* find maximum required memory size for display */ + for (i = 0; i < mach_info->num_displays; i++) { + unsigned long smem_len = mach_info->displays[i].xres; + + smem_len *= mach_info->displays[i].yres; + smem_len *= mach_info->displays[i].bpp; + smem_len >>= 3; + if (fbinfo->fix.smem_len < smem_len) + fbinfo->fix.smem_len = smem_len; + } + + /* Initialize video memory */ + ret = s3c2410fb_map_video_memory(fbinfo); + if (ret) { + dev_err(&pdev->dev, "Failed to allocate video RAM: %d\n", ret); + ret = -ENOMEM; + goto release_clock; + } + + dprintk("got video memory\n"); + + fbinfo->var.xres = display->xres; + fbinfo->var.yres = display->yres; + fbinfo->var.bits_per_pixel = display->bpp; + + s3c2410fb_init_registers(fbinfo); + + s3c2410fb_check_var(&fbinfo->var, fbinfo); + + ret = s3c2410fb_cpufreq_register(info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register cpufreq\n"); + goto free_video_memory; + } + + ret = register_framebuffer(fbinfo); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register framebuffer device: %d\n", + ret); + goto free_cpufreq; + } + + /* create device files */ + ret = device_create_file(&pdev->dev, &dev_attr_debug); + if (ret) + dev_err(&pdev->dev, "failed to add debug attribute\n"); + + dev_info(&pdev->dev, "fb%d: %s frame buffer device\n", + fbinfo->node, fbinfo->fix.id); + + return 0; + + free_cpufreq: + s3c2410fb_cpufreq_deregister(info); +free_video_memory: + s3c2410fb_unmap_video_memory(fbinfo); +release_clock: + clk_disable(info->clk); + clk_put(info->clk); +release_irq: + free_irq(irq, info); +release_regs: + iounmap(info->io); +release_mem: + release_mem_region(res->start, size); +dealloc_fb: + framebuffer_release(fbinfo); + return ret; +} + +static int s3c2410fb_probe(struct platform_device *pdev) +{ + return s3c24xxfb_probe(pdev, DRV_S3C2410); +} + +static int s3c2412fb_probe(struct platform_device *pdev) +{ + return s3c24xxfb_probe(pdev, DRV_S3C2412); +} + + +/* + * Cleanup + */ +static int s3c2410fb_remove(struct platform_device *pdev) +{ + struct fb_info *fbinfo = platform_get_drvdata(pdev); + struct s3c2410fb_info *info = fbinfo->par; + int irq; + + unregister_framebuffer(fbinfo); + s3c2410fb_cpufreq_deregister(info); + + s3c2410fb_lcd_enable(info, 0); + usleep_range(1000, 1100); + + s3c2410fb_unmap_video_memory(fbinfo); + + if (info->clk) { + clk_disable(info->clk); + clk_put(info->clk); + info->clk = NULL; + } + + irq = platform_get_irq(pdev, 0); + free_irq(irq, info); + + iounmap(info->io); + + release_mem_region(info->mem->start, resource_size(info->mem)); + + framebuffer_release(fbinfo); + + return 0; +} + +#ifdef CONFIG_PM + +/* suspend and resume support for the lcd controller */ +static int s3c2410fb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct fb_info *fbinfo = platform_get_drvdata(dev); + struct s3c2410fb_info *info = fbinfo->par; + + s3c2410fb_lcd_enable(info, 0); + + /* sleep before disabling the clock, we need to ensure + * the LCD DMA engine is not going to get back on the bus + * before the clock goes off again (bjd) */ + + usleep_range(1000, 1100); + clk_disable(info->clk); + + return 0; +} + +static int s3c2410fb_resume(struct platform_device *dev) +{ + struct fb_info *fbinfo = platform_get_drvdata(dev); + struct s3c2410fb_info *info = fbinfo->par; + + clk_enable(info->clk); + usleep_range(1000, 1100); + + s3c2410fb_init_registers(fbinfo); + + /* re-activate our display after resume */ + s3c2410fb_activate_var(fbinfo); + s3c2410fb_blank(FB_BLANK_UNBLANK, fbinfo); + + return 0; +} + +#else +#define s3c2410fb_suspend NULL +#define s3c2410fb_resume NULL +#endif + +static struct platform_driver s3c2410fb_driver = { + .probe = s3c2410fb_probe, + .remove = s3c2410fb_remove, + .suspend = s3c2410fb_suspend, + .resume = s3c2410fb_resume, + .driver = { + .name = "s3c2410-lcd", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver s3c2412fb_driver = { + .probe = s3c2412fb_probe, + .remove = s3c2410fb_remove, + .suspend = s3c2410fb_suspend, + .resume = s3c2410fb_resume, + .driver = { + .name = "s3c2412-lcd", + .owner = THIS_MODULE, + }, +}; + +int __init s3c2410fb_init(void) +{ + int ret = platform_driver_register(&s3c2410fb_driver); + + if (ret == 0) + ret = platform_driver_register(&s3c2412fb_driver); + + return ret; +} + +static void __exit s3c2410fb_cleanup(void) +{ + platform_driver_unregister(&s3c2410fb_driver); + platform_driver_unregister(&s3c2412fb_driver); +} + +module_init(s3c2410fb_init); +module_exit(s3c2410fb_cleanup); + +MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); +MODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>"); +MODULE_DESCRIPTION("Framebuffer driver for the s3c2410"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c2410-lcd"); +MODULE_ALIAS("platform:s3c2412-lcd"); diff --git a/drivers/video/fbdev/s3c2410fb.h b/drivers/video/fbdev/s3c2410fb.h new file mode 100644 index 000000000000..47a17bd23011 --- /dev/null +++ b/drivers/video/fbdev/s3c2410fb.h @@ -0,0 +1,48 @@ +/* + * linux/drivers/video/s3c2410fb.h + * Copyright (c) 2004 Arnaud Patard + * + * S3C2410 LCD Framebuffer Driver + * + * 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. + * +*/ + +#ifndef __S3C2410FB_H +#define __S3C2410FB_H + +enum s3c_drv_type { + DRV_S3C2410, + DRV_S3C2412, +}; + +struct s3c2410fb_info { + struct device *dev; + struct clk *clk; + + struct resource *mem; + void __iomem *io; + void __iomem *irq_base; + + enum s3c_drv_type drv_type; + struct s3c2410fb_hw regs; + + unsigned long clk_rate; + unsigned int palette_ready; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; +#endif + + /* keep these registers in case we need to re-write palette */ + u32 palette_buffer[256]; + u32 pseudo_pal[16]; +}; + +#define PALETTE_BUFF_CLEAR (0x80000000) /* entry is clear/invalid */ + +int s3c2410fb_init(void); + +#endif diff --git a/drivers/video/fbdev/s3fb.c b/drivers/video/fbdev/s3fb.c new file mode 100644 index 000000000000..9a3f8f1c6aab --- /dev/null +++ b/drivers/video/fbdev/s3fb.c @@ -0,0 +1,1597 @@ +/* + * linux/drivers/video/s3fb.c -- Frame buffer device driver for S3 Trio/Virge + * + * Copyright (c) 2006-2007 Ondrej Zajicek <santiago@crfreenet.org> + * + * 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. + * + * Code is based on David Boucher's viafb (http://davesdomain.org.uk/viafb/) + * which is based on the code of neofb. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/svga.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/console.h> /* Why should fb driver call console functions? because console_lock() */ +#include <video/vga.h> + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +struct s3fb_info { + int chip, rev, mclk_freq; + int mtrr_reg; + struct vgastate state; + struct mutex open_lock; + unsigned int ref_count; + u32 pseudo_palette[16]; +#ifdef CONFIG_FB_S3_DDC + u8 __iomem *mmio; + bool ddc_registered; + struct i2c_adapter ddc_adapter; + struct i2c_algo_bit_data ddc_algo; +#endif +}; + + +/* ------------------------------------------------------------------------- */ + +static const struct svga_fb_format s3fb_formats[] = { + { 0, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 0, + FB_TYPE_TEXT, FB_AUX_TEXT_SVGA_STEP4, FB_VISUAL_PSEUDOCOLOR, 8, 16}, + { 4, {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_PSEUDOCOLOR, 8, 16}, + { 4, {0, 4, 0}, {0, 4, 0}, {0, 4, 0}, {0, 0, 0}, 1, + FB_TYPE_INTERLEAVED_PLANES, 1, FB_VISUAL_PSEUDOCOLOR, 8, 16}, + { 8, {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_PSEUDOCOLOR, 4, 8}, + {16, {10, 5, 0}, {5, 5, 0}, {0, 5, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 2, 4}, + {16, {11, 5, 0}, {5, 6, 0}, {0, 5, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 2, 4}, + {24, {16, 8, 0}, {8, 8, 0}, {0, 8, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 1, 2}, + {32, {16, 8, 0}, {8, 8, 0}, {0, 8, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 1, 2}, + SVGA_FORMAT_END +}; + + +static const struct svga_pll s3_pll = {3, 129, 3, 33, 0, 3, + 35000, 240000, 14318}; +static const struct svga_pll s3_trio3d_pll = {3, 129, 3, 31, 0, 4, + 230000, 460000, 14318}; + +static const int s3_memsizes[] = {4096, 0, 3072, 8192, 2048, 6144, 1024, 512}; + +static const char * const s3_names[] = {"S3 Unknown", "S3 Trio32", "S3 Trio64", "S3 Trio64V+", + "S3 Trio64UV+", "S3 Trio64V2/DX", "S3 Trio64V2/GX", + "S3 Plato/PX", "S3 Aurora64V+", "S3 Virge", + "S3 Virge/VX", "S3 Virge/DX", "S3 Virge/GX", + "S3 Virge/GX2", "S3 Virge/GX2+", "", + "S3 Trio3D/1X", "S3 Trio3D/2X", "S3 Trio3D/2X", + "S3 Trio3D", "S3 Virge/MX"}; + +#define CHIP_UNKNOWN 0x00 +#define CHIP_732_TRIO32 0x01 +#define CHIP_764_TRIO64 0x02 +#define CHIP_765_TRIO64VP 0x03 +#define CHIP_767_TRIO64UVP 0x04 +#define CHIP_775_TRIO64V2_DX 0x05 +#define CHIP_785_TRIO64V2_GX 0x06 +#define CHIP_551_PLATO_PX 0x07 +#define CHIP_M65_AURORA64VP 0x08 +#define CHIP_325_VIRGE 0x09 +#define CHIP_988_VIRGE_VX 0x0A +#define CHIP_375_VIRGE_DX 0x0B +#define CHIP_385_VIRGE_GX 0x0C +#define CHIP_357_VIRGE_GX2 0x0D +#define CHIP_359_VIRGE_GX2P 0x0E +#define CHIP_360_TRIO3D_1X 0x10 +#define CHIP_362_TRIO3D_2X 0x11 +#define CHIP_368_TRIO3D_2X 0x12 +#define CHIP_365_TRIO3D 0x13 +#define CHIP_260_VIRGE_MX 0x14 + +#define CHIP_XXX_TRIO 0x80 +#define CHIP_XXX_TRIO64V2_DXGX 0x81 +#define CHIP_XXX_VIRGE_DXGX 0x82 +#define CHIP_36X_TRIO3D_1X_2X 0x83 + +#define CHIP_UNDECIDED_FLAG 0x80 +#define CHIP_MASK 0xFF + +#define MMIO_OFFSET 0x1000000 +#define MMIO_SIZE 0x10000 + +/* CRT timing register sets */ + +static const struct vga_regset s3_h_total_regs[] = {{0x00, 0, 7}, {0x5D, 0, 0}, VGA_REGSET_END}; +static const struct vga_regset s3_h_display_regs[] = {{0x01, 0, 7}, {0x5D, 1, 1}, VGA_REGSET_END}; +static const struct vga_regset s3_h_blank_start_regs[] = {{0x02, 0, 7}, {0x5D, 2, 2}, VGA_REGSET_END}; +static const struct vga_regset s3_h_blank_end_regs[] = {{0x03, 0, 4}, {0x05, 7, 7}, VGA_REGSET_END}; +static const struct vga_regset s3_h_sync_start_regs[] = {{0x04, 0, 7}, {0x5D, 4, 4}, VGA_REGSET_END}; +static const struct vga_regset s3_h_sync_end_regs[] = {{0x05, 0, 4}, VGA_REGSET_END}; + +static const struct vga_regset s3_v_total_regs[] = {{0x06, 0, 7}, {0x07, 0, 0}, {0x07, 5, 5}, {0x5E, 0, 0}, VGA_REGSET_END}; +static const struct vga_regset s3_v_display_regs[] = {{0x12, 0, 7}, {0x07, 1, 1}, {0x07, 6, 6}, {0x5E, 1, 1}, VGA_REGSET_END}; +static const struct vga_regset s3_v_blank_start_regs[] = {{0x15, 0, 7}, {0x07, 3, 3}, {0x09, 5, 5}, {0x5E, 2, 2}, VGA_REGSET_END}; +static const struct vga_regset s3_v_blank_end_regs[] = {{0x16, 0, 7}, VGA_REGSET_END}; +static const struct vga_regset s3_v_sync_start_regs[] = {{0x10, 0, 7}, {0x07, 2, 2}, {0x07, 7, 7}, {0x5E, 4, 4}, VGA_REGSET_END}; +static const struct vga_regset s3_v_sync_end_regs[] = {{0x11, 0, 3}, VGA_REGSET_END}; + +static const struct vga_regset s3_line_compare_regs[] = {{0x18, 0, 7}, {0x07, 4, 4}, {0x09, 6, 6}, {0x5E, 6, 6}, VGA_REGSET_END}; +static const struct vga_regset s3_start_address_regs[] = {{0x0d, 0, 7}, {0x0c, 0, 7}, {0x69, 0, 4}, VGA_REGSET_END}; +static const struct vga_regset s3_offset_regs[] = {{0x13, 0, 7}, {0x51, 4, 5}, VGA_REGSET_END}; /* set 0x43 bit 2 to 0 */ + +static const struct vga_regset s3_dtpc_regs[] = {{0x3B, 0, 7}, {0x5D, 6, 6}, VGA_REGSET_END}; + +static const struct svga_timing_regs s3_timing_regs = { + s3_h_total_regs, s3_h_display_regs, s3_h_blank_start_regs, + s3_h_blank_end_regs, s3_h_sync_start_regs, s3_h_sync_end_regs, + s3_v_total_regs, s3_v_display_regs, s3_v_blank_start_regs, + s3_v_blank_end_regs, s3_v_sync_start_regs, s3_v_sync_end_regs, +}; + + +/* ------------------------------------------------------------------------- */ + +/* Module parameters */ + + +static char *mode_option; + +#ifdef CONFIG_MTRR +static int mtrr = 1; +#endif + +static int fasttext = 1; + + +MODULE_AUTHOR("(c) 2006-2007 Ondrej Zajicek <santiago@crfreenet.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("fbdev driver for S3 Trio/Virge"); + +module_param(mode_option, charp, 0444); +MODULE_PARM_DESC(mode_option, "Default video mode ('640x480-8@60', etc)"); +module_param_named(mode, mode_option, charp, 0444); +MODULE_PARM_DESC(mode, "Default video mode ('640x480-8@60', etc) (deprecated)"); + +#ifdef CONFIG_MTRR +module_param(mtrr, int, 0444); +MODULE_PARM_DESC(mtrr, "Enable write-combining with MTRR (1=enable, 0=disable, default=1)"); +#endif + +module_param(fasttext, int, 0644); +MODULE_PARM_DESC(fasttext, "Enable S3 fast text mode (1=enable, 0=disable, default=1)"); + + +/* ------------------------------------------------------------------------- */ + +#ifdef CONFIG_FB_S3_DDC + +#define DDC_REG 0xaa /* Trio 3D/1X/2X */ +#define DDC_MMIO_REG 0xff20 /* all other chips */ +#define DDC_SCL_OUT (1 << 0) +#define DDC_SDA_OUT (1 << 1) +#define DDC_SCL_IN (1 << 2) +#define DDC_SDA_IN (1 << 3) +#define DDC_DRIVE_EN (1 << 4) + +static bool s3fb_ddc_needs_mmio(int chip) +{ + return !(chip == CHIP_360_TRIO3D_1X || + chip == CHIP_362_TRIO3D_2X || + chip == CHIP_368_TRIO3D_2X); +} + +static u8 s3fb_ddc_read(struct s3fb_info *par) +{ + if (s3fb_ddc_needs_mmio(par->chip)) + return readb(par->mmio + DDC_MMIO_REG); + else + return vga_rcrt(par->state.vgabase, DDC_REG); +} + +static void s3fb_ddc_write(struct s3fb_info *par, u8 val) +{ + if (s3fb_ddc_needs_mmio(par->chip)) + writeb(val, par->mmio + DDC_MMIO_REG); + else + vga_wcrt(par->state.vgabase, DDC_REG, val); +} + +static void s3fb_ddc_setscl(void *data, int val) +{ + struct s3fb_info *par = data; + unsigned char reg; + + reg = s3fb_ddc_read(par) | DDC_DRIVE_EN; + if (val) + reg |= DDC_SCL_OUT; + else + reg &= ~DDC_SCL_OUT; + s3fb_ddc_write(par, reg); +} + +static void s3fb_ddc_setsda(void *data, int val) +{ + struct s3fb_info *par = data; + unsigned char reg; + + reg = s3fb_ddc_read(par) | DDC_DRIVE_EN; + if (val) + reg |= DDC_SDA_OUT; + else + reg &= ~DDC_SDA_OUT; + s3fb_ddc_write(par, reg); +} + +static int s3fb_ddc_getscl(void *data) +{ + struct s3fb_info *par = data; + + return !!(s3fb_ddc_read(par) & DDC_SCL_IN); +} + +static int s3fb_ddc_getsda(void *data) +{ + struct s3fb_info *par = data; + + return !!(s3fb_ddc_read(par) & DDC_SDA_IN); +} + +static int s3fb_setup_ddc_bus(struct fb_info *info) +{ + struct s3fb_info *par = info->par; + + strlcpy(par->ddc_adapter.name, info->fix.id, + sizeof(par->ddc_adapter.name)); + par->ddc_adapter.owner = THIS_MODULE; + par->ddc_adapter.class = I2C_CLASS_DDC; + par->ddc_adapter.algo_data = &par->ddc_algo; + par->ddc_adapter.dev.parent = info->device; + par->ddc_algo.setsda = s3fb_ddc_setsda; + par->ddc_algo.setscl = s3fb_ddc_setscl; + par->ddc_algo.getsda = s3fb_ddc_getsda; + par->ddc_algo.getscl = s3fb_ddc_getscl; + par->ddc_algo.udelay = 10; + par->ddc_algo.timeout = 20; + par->ddc_algo.data = par; + + i2c_set_adapdata(&par->ddc_adapter, par); + + /* + * some Virge cards have external MUX to switch chip I2C bus between + * DDC and extension pins - switch it do DDC + */ +/* vga_wseq(par->state.vgabase, 0x08, 0x06); - not needed, already unlocked */ + if (par->chip == CHIP_357_VIRGE_GX2 || + par->chip == CHIP_359_VIRGE_GX2P || + par->chip == CHIP_260_VIRGE_MX) + svga_wseq_mask(par->state.vgabase, 0x0d, 0x01, 0x03); + else + svga_wseq_mask(par->state.vgabase, 0x0d, 0x00, 0x03); + /* some Virge need this or the DDC is ignored */ + svga_wcrt_mask(par->state.vgabase, 0x5c, 0x03, 0x03); + + return i2c_bit_add_bus(&par->ddc_adapter); +} +#endif /* CONFIG_FB_S3_DDC */ + + +/* ------------------------------------------------------------------------- */ + +/* Set font in S3 fast text mode */ + +static void s3fb_settile_fast(struct fb_info *info, struct fb_tilemap *map) +{ + const u8 *font = map->data; + u8 __iomem *fb = (u8 __iomem *) info->screen_base; + int i, c; + + if ((map->width != 8) || (map->height != 16) || + (map->depth != 1) || (map->length != 256)) { + fb_err(info, "unsupported font parameters: width %d, height %d, depth %d, length %d\n", + map->width, map->height, map->depth, map->length); + return; + } + + fb += 2; + for (i = 0; i < map->height; i++) { + for (c = 0; c < map->length; c++) { + fb_writeb(font[c * map->height + i], fb + c * 4); + } + fb += 1024; + } +} + +static void s3fb_tilecursor(struct fb_info *info, struct fb_tilecursor *cursor) +{ + struct s3fb_info *par = info->par; + + svga_tilecursor(par->state.vgabase, info, cursor); +} + +static struct fb_tile_ops s3fb_tile_ops = { + .fb_settile = svga_settile, + .fb_tilecopy = svga_tilecopy, + .fb_tilefill = svga_tilefill, + .fb_tileblit = svga_tileblit, + .fb_tilecursor = s3fb_tilecursor, + .fb_get_tilemax = svga_get_tilemax, +}; + +static struct fb_tile_ops s3fb_fast_tile_ops = { + .fb_settile = s3fb_settile_fast, + .fb_tilecopy = svga_tilecopy, + .fb_tilefill = svga_tilefill, + .fb_tileblit = svga_tileblit, + .fb_tilecursor = s3fb_tilecursor, + .fb_get_tilemax = svga_get_tilemax, +}; + + +/* ------------------------------------------------------------------------- */ + +/* image data is MSB-first, fb structure is MSB-first too */ +static inline u32 expand_color(u32 c) +{ + return ((c & 1) | ((c & 2) << 7) | ((c & 4) << 14) | ((c & 8) << 21)) * 0xFF; +} + +/* s3fb_iplan_imageblit silently assumes that almost everything is 8-pixel aligned */ +static void s3fb_iplan_imageblit(struct fb_info *info, const struct fb_image *image) +{ + u32 fg = expand_color(image->fg_color); + u32 bg = expand_color(image->bg_color); + const u8 *src1, *src; + u8 __iomem *dst1; + u32 __iomem *dst; + u32 val; + int x, y; + + src1 = image->data; + dst1 = info->screen_base + (image->dy * info->fix.line_length) + + ((image->dx / 8) * 4); + + for (y = 0; y < image->height; y++) { + src = src1; + dst = (u32 __iomem *) dst1; + for (x = 0; x < image->width; x += 8) { + val = *(src++) * 0x01010101; + val = (val & fg) | (~val & bg); + fb_writel(val, dst++); + } + src1 += image->width / 8; + dst1 += info->fix.line_length; + } + +} + +/* s3fb_iplan_fillrect silently assumes that almost everything is 8-pixel aligned */ +static void s3fb_iplan_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + u32 fg = expand_color(rect->color); + u8 __iomem *dst1; + u32 __iomem *dst; + int x, y; + + dst1 = info->screen_base + (rect->dy * info->fix.line_length) + + ((rect->dx / 8) * 4); + + for (y = 0; y < rect->height; y++) { + dst = (u32 __iomem *) dst1; + for (x = 0; x < rect->width; x += 8) { + fb_writel(fg, dst++); + } + dst1 += info->fix.line_length; + } +} + + +/* image data is MSB-first, fb structure is high-nibble-in-low-byte-first */ +static inline u32 expand_pixel(u32 c) +{ + return (((c & 1) << 24) | ((c & 2) << 27) | ((c & 4) << 14) | ((c & 8) << 17) | + ((c & 16) << 4) | ((c & 32) << 7) | ((c & 64) >> 6) | ((c & 128) >> 3)) * 0xF; +} + +/* s3fb_cfb4_imageblit silently assumes that almost everything is 8-pixel aligned */ +static void s3fb_cfb4_imageblit(struct fb_info *info, const struct fb_image *image) +{ + u32 fg = image->fg_color * 0x11111111; + u32 bg = image->bg_color * 0x11111111; + const u8 *src1, *src; + u8 __iomem *dst1; + u32 __iomem *dst; + u32 val; + int x, y; + + src1 = image->data; + dst1 = info->screen_base + (image->dy * info->fix.line_length) + + ((image->dx / 8) * 4); + + for (y = 0; y < image->height; y++) { + src = src1; + dst = (u32 __iomem *) dst1; + for (x = 0; x < image->width; x += 8) { + val = expand_pixel(*(src++)); + val = (val & fg) | (~val & bg); + fb_writel(val, dst++); + } + src1 += image->width / 8; + dst1 += info->fix.line_length; + } +} + +static void s3fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + if ((info->var.bits_per_pixel == 4) && (image->depth == 1) + && ((image->width % 8) == 0) && ((image->dx % 8) == 0)) { + if (info->fix.type == FB_TYPE_INTERLEAVED_PLANES) + s3fb_iplan_imageblit(info, image); + else + s3fb_cfb4_imageblit(info, image); + } else + cfb_imageblit(info, image); +} + +static void s3fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + if ((info->var.bits_per_pixel == 4) + && ((rect->width % 8) == 0) && ((rect->dx % 8) == 0) + && (info->fix.type == FB_TYPE_INTERLEAVED_PLANES)) + s3fb_iplan_fillrect(info, rect); + else + cfb_fillrect(info, rect); +} + + + +/* ------------------------------------------------------------------------- */ + + +static void s3_set_pixclock(struct fb_info *info, u32 pixclock) +{ + struct s3fb_info *par = info->par; + u16 m, n, r; + u8 regval; + int rv; + + rv = svga_compute_pll((par->chip == CHIP_365_TRIO3D) ? &s3_trio3d_pll : &s3_pll, + 1000000000 / pixclock, &m, &n, &r, info->node); + if (rv < 0) { + fb_err(info, "cannot set requested pixclock, keeping old value\n"); + return; + } + + /* Set VGA misc register */ + regval = vga_r(par->state.vgabase, VGA_MIS_R); + vga_w(par->state.vgabase, VGA_MIS_W, regval | VGA_MIS_ENB_PLL_LOAD); + + /* Set S3 clock registers */ + if (par->chip == CHIP_357_VIRGE_GX2 || + par->chip == CHIP_359_VIRGE_GX2P || + par->chip == CHIP_360_TRIO3D_1X || + par->chip == CHIP_362_TRIO3D_2X || + par->chip == CHIP_368_TRIO3D_2X || + par->chip == CHIP_260_VIRGE_MX) { + vga_wseq(par->state.vgabase, 0x12, (n - 2) | ((r & 3) << 6)); /* n and two bits of r */ + vga_wseq(par->state.vgabase, 0x29, r >> 2); /* remaining highest bit of r */ + } else + vga_wseq(par->state.vgabase, 0x12, (n - 2) | (r << 5)); + vga_wseq(par->state.vgabase, 0x13, m - 2); + + udelay(1000); + + /* Activate clock - write 0, 1, 0 to seq/15 bit 5 */ + regval = vga_rseq (par->state.vgabase, 0x15); /* | 0x80; */ + vga_wseq(par->state.vgabase, 0x15, regval & ~(1<<5)); + vga_wseq(par->state.vgabase, 0x15, regval | (1<<5)); + vga_wseq(par->state.vgabase, 0x15, regval & ~(1<<5)); +} + + +/* Open framebuffer */ + +static int s3fb_open(struct fb_info *info, int user) +{ + struct s3fb_info *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + void __iomem *vgabase = par->state.vgabase; + + memset(&(par->state), 0, sizeof(struct vgastate)); + par->state.vgabase = vgabase; + par->state.flags = VGA_SAVE_MODE | VGA_SAVE_FONTS | VGA_SAVE_CMAP; + par->state.num_crtc = 0x70; + par->state.num_seq = 0x20; + save_vga(&(par->state)); + } + + par->ref_count++; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +/* Close framebuffer */ + +static int s3fb_release(struct fb_info *info, int user) +{ + struct s3fb_info *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + mutex_unlock(&(par->open_lock)); + return -EINVAL; + } + + if (par->ref_count == 1) + restore_vga(&(par->state)); + + par->ref_count--; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +/* Validate passed in var */ + +static int s3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct s3fb_info *par = info->par; + int rv, mem, step; + u16 m, n, r; + + /* Find appropriate format */ + rv = svga_match_format (s3fb_formats, var, NULL); + + /* 32bpp mode is not supported on VIRGE VX, + 24bpp is not supported on others */ + if ((par->chip == CHIP_988_VIRGE_VX) ? (rv == 7) : (rv == 6)) + rv = -EINVAL; + + if (rv < 0) { + fb_err(info, "unsupported mode requested\n"); + return rv; + } + + /* Do not allow to have real resoulution larger than virtual */ + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + /* Round up xres_virtual to have proper alignment of lines */ + step = s3fb_formats[rv].xresstep - 1; + var->xres_virtual = (var->xres_virtual+step) & ~step; + + /* Check whether have enough memory */ + mem = ((var->bits_per_pixel * var->xres_virtual) >> 3) * var->yres_virtual; + if (mem > info->screen_size) { + fb_err(info, "not enough framebuffer memory (%d kB requested , %u kB available)\n", + mem >> 10, (unsigned int) (info->screen_size >> 10)); + return -EINVAL; + } + + rv = svga_check_timings (&s3_timing_regs, var, info->node); + if (rv < 0) { + fb_err(info, "invalid timings requested\n"); + return rv; + } + + rv = svga_compute_pll(&s3_pll, PICOS2KHZ(var->pixclock), &m, &n, &r, + info->node); + if (rv < 0) { + fb_err(info, "invalid pixclock value requested\n"); + return rv; + } + + return 0; +} + +/* Set video mode from par */ + +static int s3fb_set_par(struct fb_info *info) +{ + struct s3fb_info *par = info->par; + u32 value, mode, hmul, offset_value, screen_size, multiplex, dbytes; + u32 bpp = info->var.bits_per_pixel; + u32 htotal, hsstart; + + if (bpp != 0) { + info->fix.ypanstep = 1; + info->fix.line_length = (info->var.xres_virtual * bpp) / 8; + + info->flags &= ~FBINFO_MISC_TILEBLITTING; + info->tileops = NULL; + + /* in 4bpp supports 8p wide tiles only, any tiles otherwise */ + info->pixmap.blit_x = (bpp == 4) ? (1 << (8 - 1)) : (~(u32)0); + info->pixmap.blit_y = ~(u32)0; + + offset_value = (info->var.xres_virtual * bpp) / 64; + screen_size = info->var.yres_virtual * info->fix.line_length; + } else { + info->fix.ypanstep = 16; + info->fix.line_length = 0; + + info->flags |= FBINFO_MISC_TILEBLITTING; + info->tileops = fasttext ? &s3fb_fast_tile_ops : &s3fb_tile_ops; + + /* supports 8x16 tiles only */ + info->pixmap.blit_x = 1 << (8 - 1); + info->pixmap.blit_y = 1 << (16 - 1); + + offset_value = info->var.xres_virtual / 16; + screen_size = (info->var.xres_virtual * info->var.yres_virtual) / 64; + } + + info->var.xoffset = 0; + info->var.yoffset = 0; + info->var.activate = FB_ACTIVATE_NOW; + + /* Unlock registers */ + vga_wcrt(par->state.vgabase, 0x38, 0x48); + vga_wcrt(par->state.vgabase, 0x39, 0xA5); + vga_wseq(par->state.vgabase, 0x08, 0x06); + svga_wcrt_mask(par->state.vgabase, 0x11, 0x00, 0x80); + + /* Blank screen and turn off sync */ + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + svga_wcrt_mask(par->state.vgabase, 0x17, 0x00, 0x80); + + /* Set default values */ + svga_set_default_gfx_regs(par->state.vgabase); + svga_set_default_atc_regs(par->state.vgabase); + svga_set_default_seq_regs(par->state.vgabase); + svga_set_default_crt_regs(par->state.vgabase); + svga_wcrt_multi(par->state.vgabase, s3_line_compare_regs, 0xFFFFFFFF); + svga_wcrt_multi(par->state.vgabase, s3_start_address_regs, 0); + + /* S3 specific initialization */ + svga_wcrt_mask(par->state.vgabase, 0x58, 0x10, 0x10); /* enable linear framebuffer */ + svga_wcrt_mask(par->state.vgabase, 0x31, 0x08, 0x08); /* enable sequencer access to framebuffer above 256 kB */ + +/* svga_wcrt_mask(par->state.vgabase, 0x33, 0x08, 0x08); */ /* DDR ? */ +/* svga_wcrt_mask(par->state.vgabase, 0x43, 0x01, 0x01); */ /* DDR ? */ + svga_wcrt_mask(par->state.vgabase, 0x33, 0x00, 0x08); /* no DDR ? */ + svga_wcrt_mask(par->state.vgabase, 0x43, 0x00, 0x01); /* no DDR ? */ + + svga_wcrt_mask(par->state.vgabase, 0x5D, 0x00, 0x28); /* Clear strange HSlen bits */ + +/* svga_wcrt_mask(par->state.vgabase, 0x58, 0x03, 0x03); */ + +/* svga_wcrt_mask(par->state.vgabase, 0x53, 0x12, 0x13); */ /* enable MMIO */ +/* svga_wcrt_mask(par->state.vgabase, 0x40, 0x08, 0x08); */ /* enable write buffer */ + + + /* Set the offset register */ + fb_dbg(info, "offset register : %d\n", offset_value); + svga_wcrt_multi(par->state.vgabase, s3_offset_regs, offset_value); + + if (par->chip != CHIP_357_VIRGE_GX2 && + par->chip != CHIP_359_VIRGE_GX2P && + par->chip != CHIP_360_TRIO3D_1X && + par->chip != CHIP_362_TRIO3D_2X && + par->chip != CHIP_368_TRIO3D_2X && + par->chip != CHIP_260_VIRGE_MX) { + vga_wcrt(par->state.vgabase, 0x54, 0x18); /* M parameter */ + vga_wcrt(par->state.vgabase, 0x60, 0xff); /* N parameter */ + vga_wcrt(par->state.vgabase, 0x61, 0xff); /* L parameter */ + vga_wcrt(par->state.vgabase, 0x62, 0xff); /* L parameter */ + } + + vga_wcrt(par->state.vgabase, 0x3A, 0x35); + svga_wattr(par->state.vgabase, 0x33, 0x00); + + if (info->var.vmode & FB_VMODE_DOUBLE) + svga_wcrt_mask(par->state.vgabase, 0x09, 0x80, 0x80); + else + svga_wcrt_mask(par->state.vgabase, 0x09, 0x00, 0x80); + + if (info->var.vmode & FB_VMODE_INTERLACED) + svga_wcrt_mask(par->state.vgabase, 0x42, 0x20, 0x20); + else + svga_wcrt_mask(par->state.vgabase, 0x42, 0x00, 0x20); + + /* Disable hardware graphics cursor */ + svga_wcrt_mask(par->state.vgabase, 0x45, 0x00, 0x01); + /* Disable Streams engine */ + svga_wcrt_mask(par->state.vgabase, 0x67, 0x00, 0x0C); + + mode = svga_match_format(s3fb_formats, &(info->var), &(info->fix)); + + /* S3 virge DX hack */ + if (par->chip == CHIP_375_VIRGE_DX) { + vga_wcrt(par->state.vgabase, 0x86, 0x80); + vga_wcrt(par->state.vgabase, 0x90, 0x00); + } + + /* S3 virge VX hack */ + if (par->chip == CHIP_988_VIRGE_VX) { + vga_wcrt(par->state.vgabase, 0x50, 0x00); + vga_wcrt(par->state.vgabase, 0x67, 0x50); + msleep(10); /* screen remains blank sometimes without this */ + vga_wcrt(par->state.vgabase, 0x63, (mode <= 2) ? 0x90 : 0x09); + vga_wcrt(par->state.vgabase, 0x66, 0x90); + } + + if (par->chip == CHIP_357_VIRGE_GX2 || + par->chip == CHIP_359_VIRGE_GX2P || + par->chip == CHIP_360_TRIO3D_1X || + par->chip == CHIP_362_TRIO3D_2X || + par->chip == CHIP_368_TRIO3D_2X || + par->chip == CHIP_365_TRIO3D || + par->chip == CHIP_375_VIRGE_DX || + par->chip == CHIP_385_VIRGE_GX || + par->chip == CHIP_260_VIRGE_MX) { + dbytes = info->var.xres * ((bpp+7)/8); + vga_wcrt(par->state.vgabase, 0x91, (dbytes + 7) / 8); + vga_wcrt(par->state.vgabase, 0x90, (((dbytes + 7) / 8) >> 8) | 0x80); + + vga_wcrt(par->state.vgabase, 0x66, 0x81); + } + + if (par->chip == CHIP_357_VIRGE_GX2 || + par->chip == CHIP_359_VIRGE_GX2P || + par->chip == CHIP_360_TRIO3D_1X || + par->chip == CHIP_362_TRIO3D_2X || + par->chip == CHIP_368_TRIO3D_2X || + par->chip == CHIP_260_VIRGE_MX) + vga_wcrt(par->state.vgabase, 0x34, 0x00); + else /* enable Data Transfer Position Control (DTPC) */ + vga_wcrt(par->state.vgabase, 0x34, 0x10); + + svga_wcrt_mask(par->state.vgabase, 0x31, 0x00, 0x40); + multiplex = 0; + hmul = 1; + + /* Set mode-specific register values */ + switch (mode) { + case 0: + fb_dbg(info, "text mode\n"); + svga_set_textmode_vga_regs(par->state.vgabase); + + /* Set additional registers like in 8-bit mode */ + svga_wcrt_mask(par->state.vgabase, 0x50, 0x00, 0x30); + svga_wcrt_mask(par->state.vgabase, 0x67, 0x00, 0xF0); + + /* Disable enhanced mode */ + svga_wcrt_mask(par->state.vgabase, 0x3A, 0x00, 0x30); + + if (fasttext) { + fb_dbg(info, "high speed text mode set\n"); + svga_wcrt_mask(par->state.vgabase, 0x31, 0x40, 0x40); + } + break; + case 1: + fb_dbg(info, "4 bit pseudocolor\n"); + vga_wgfx(par->state.vgabase, VGA_GFX_MODE, 0x40); + + /* Set additional registers like in 8-bit mode */ + svga_wcrt_mask(par->state.vgabase, 0x50, 0x00, 0x30); + svga_wcrt_mask(par->state.vgabase, 0x67, 0x00, 0xF0); + + /* disable enhanced mode */ + svga_wcrt_mask(par->state.vgabase, 0x3A, 0x00, 0x30); + break; + case 2: + fb_dbg(info, "4 bit pseudocolor, planar\n"); + + /* Set additional registers like in 8-bit mode */ + svga_wcrt_mask(par->state.vgabase, 0x50, 0x00, 0x30); + svga_wcrt_mask(par->state.vgabase, 0x67, 0x00, 0xF0); + + /* disable enhanced mode */ + svga_wcrt_mask(par->state.vgabase, 0x3A, 0x00, 0x30); + break; + case 3: + fb_dbg(info, "8 bit pseudocolor\n"); + svga_wcrt_mask(par->state.vgabase, 0x50, 0x00, 0x30); + if (info->var.pixclock > 20000 || + par->chip == CHIP_357_VIRGE_GX2 || + par->chip == CHIP_359_VIRGE_GX2P || + par->chip == CHIP_360_TRIO3D_1X || + par->chip == CHIP_362_TRIO3D_2X || + par->chip == CHIP_368_TRIO3D_2X || + par->chip == CHIP_260_VIRGE_MX) + svga_wcrt_mask(par->state.vgabase, 0x67, 0x00, 0xF0); + else { + svga_wcrt_mask(par->state.vgabase, 0x67, 0x10, 0xF0); + multiplex = 1; + } + break; + case 4: + fb_dbg(info, "5/5/5 truecolor\n"); + if (par->chip == CHIP_988_VIRGE_VX) { + if (info->var.pixclock > 20000) + svga_wcrt_mask(par->state.vgabase, 0x67, 0x20, 0xF0); + else + svga_wcrt_mask(par->state.vgabase, 0x67, 0x30, 0xF0); + } else if (par->chip == CHIP_365_TRIO3D) { + svga_wcrt_mask(par->state.vgabase, 0x50, 0x10, 0x30); + if (info->var.pixclock > 8695) { + svga_wcrt_mask(par->state.vgabase, 0x67, 0x30, 0xF0); + hmul = 2; + } else { + svga_wcrt_mask(par->state.vgabase, 0x67, 0x20, 0xF0); + multiplex = 1; + } + } else { + svga_wcrt_mask(par->state.vgabase, 0x50, 0x10, 0x30); + svga_wcrt_mask(par->state.vgabase, 0x67, 0x30, 0xF0); + if (par->chip != CHIP_357_VIRGE_GX2 && + par->chip != CHIP_359_VIRGE_GX2P && + par->chip != CHIP_360_TRIO3D_1X && + par->chip != CHIP_362_TRIO3D_2X && + par->chip != CHIP_368_TRIO3D_2X && + par->chip != CHIP_260_VIRGE_MX) + hmul = 2; + } + break; + case 5: + fb_dbg(info, "5/6/5 truecolor\n"); + if (par->chip == CHIP_988_VIRGE_VX) { + if (info->var.pixclock > 20000) + svga_wcrt_mask(par->state.vgabase, 0x67, 0x40, 0xF0); + else + svga_wcrt_mask(par->state.vgabase, 0x67, 0x50, 0xF0); + } else if (par->chip == CHIP_365_TRIO3D) { + svga_wcrt_mask(par->state.vgabase, 0x50, 0x10, 0x30); + if (info->var.pixclock > 8695) { + svga_wcrt_mask(par->state.vgabase, 0x67, 0x50, 0xF0); + hmul = 2; + } else { + svga_wcrt_mask(par->state.vgabase, 0x67, 0x40, 0xF0); + multiplex = 1; + } + } else { + svga_wcrt_mask(par->state.vgabase, 0x50, 0x10, 0x30); + svga_wcrt_mask(par->state.vgabase, 0x67, 0x50, 0xF0); + if (par->chip != CHIP_357_VIRGE_GX2 && + par->chip != CHIP_359_VIRGE_GX2P && + par->chip != CHIP_360_TRIO3D_1X && + par->chip != CHIP_362_TRIO3D_2X && + par->chip != CHIP_368_TRIO3D_2X && + par->chip != CHIP_260_VIRGE_MX) + hmul = 2; + } + break; + case 6: + /* VIRGE VX case */ + fb_dbg(info, "8/8/8 truecolor\n"); + svga_wcrt_mask(par->state.vgabase, 0x67, 0xD0, 0xF0); + break; + case 7: + fb_dbg(info, "8/8/8/8 truecolor\n"); + svga_wcrt_mask(par->state.vgabase, 0x50, 0x30, 0x30); + svga_wcrt_mask(par->state.vgabase, 0x67, 0xD0, 0xF0); + break; + default: + fb_err(info, "unsupported mode - bug\n"); + return -EINVAL; + } + + if (par->chip != CHIP_988_VIRGE_VX) { + svga_wseq_mask(par->state.vgabase, 0x15, multiplex ? 0x10 : 0x00, 0x10); + svga_wseq_mask(par->state.vgabase, 0x18, multiplex ? 0x80 : 0x00, 0x80); + } + + s3_set_pixclock(info, info->var.pixclock); + svga_set_timings(par->state.vgabase, &s3_timing_regs, &(info->var), hmul, 1, + (info->var.vmode & FB_VMODE_DOUBLE) ? 2 : 1, + (info->var.vmode & FB_VMODE_INTERLACED) ? 2 : 1, + hmul, info->node); + + /* Set interlaced mode start/end register */ + htotal = info->var.xres + info->var.left_margin + info->var.right_margin + info->var.hsync_len; + htotal = ((htotal * hmul) / 8) - 5; + vga_wcrt(par->state.vgabase, 0x3C, (htotal + 1) / 2); + + /* Set Data Transfer Position */ + hsstart = ((info->var.xres + info->var.right_margin) * hmul) / 8; + /* + 2 is needed for Virge/VX, does no harm on other cards */ + value = clamp((htotal + hsstart + 1) / 2 + 2, hsstart + 4, htotal + 1); + svga_wcrt_multi(par->state.vgabase, s3_dtpc_regs, value); + + memset_io(info->screen_base, 0x00, screen_size); + /* Device and screen back on */ + svga_wcrt_mask(par->state.vgabase, 0x17, 0x80, 0x80); + svga_wseq_mask(par->state.vgabase, 0x01, 0x00, 0x20); + + return 0; +} + +/* Set a colour register */ + +static int s3fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *fb) +{ + switch (fb->var.bits_per_pixel) { + case 0: + case 4: + if (regno >= 16) + return -EINVAL; + + if ((fb->var.bits_per_pixel == 4) && + (fb->var.nonstd == 0)) { + outb(0xF0, VGA_PEL_MSK); + outb(regno*16, VGA_PEL_IW); + } else { + outb(0x0F, VGA_PEL_MSK); + outb(regno, VGA_PEL_IW); + } + outb(red >> 10, VGA_PEL_D); + outb(green >> 10, VGA_PEL_D); + outb(blue >> 10, VGA_PEL_D); + break; + case 8: + if (regno >= 256) + return -EINVAL; + + outb(0xFF, VGA_PEL_MSK); + outb(regno, VGA_PEL_IW); + outb(red >> 10, VGA_PEL_D); + outb(green >> 10, VGA_PEL_D); + outb(blue >> 10, VGA_PEL_D); + break; + case 16: + if (regno >= 16) + return 0; + + if (fb->var.green.length == 5) + ((u32*)fb->pseudo_palette)[regno] = ((red & 0xF800) >> 1) | + ((green & 0xF800) >> 6) | ((blue & 0xF800) >> 11); + else if (fb->var.green.length == 6) + ((u32*)fb->pseudo_palette)[regno] = (red & 0xF800) | + ((green & 0xFC00) >> 5) | ((blue & 0xF800) >> 11); + else return -EINVAL; + break; + case 24: + case 32: + if (regno >= 16) + return 0; + + ((u32*)fb->pseudo_palette)[regno] = ((red & 0xFF00) << 8) | + (green & 0xFF00) | ((blue & 0xFF00) >> 8); + break; + default: + return -EINVAL; + } + + return 0; +} + + +/* Set the display blanking state */ + +static int s3fb_blank(int blank_mode, struct fb_info *info) +{ + struct s3fb_info *par = info->par; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + fb_dbg(info, "unblank\n"); + svga_wcrt_mask(par->state.vgabase, 0x56, 0x00, 0x06); + svga_wseq_mask(par->state.vgabase, 0x01, 0x00, 0x20); + break; + case FB_BLANK_NORMAL: + fb_dbg(info, "blank\n"); + svga_wcrt_mask(par->state.vgabase, 0x56, 0x00, 0x06); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + case FB_BLANK_HSYNC_SUSPEND: + fb_dbg(info, "hsync\n"); + svga_wcrt_mask(par->state.vgabase, 0x56, 0x02, 0x06); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + case FB_BLANK_VSYNC_SUSPEND: + fb_dbg(info, "vsync\n"); + svga_wcrt_mask(par->state.vgabase, 0x56, 0x04, 0x06); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + case FB_BLANK_POWERDOWN: + fb_dbg(info, "sync down\n"); + svga_wcrt_mask(par->state.vgabase, 0x56, 0x06, 0x06); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + } + + return 0; +} + + +/* Pan the display */ + +static int s3fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct s3fb_info *par = info->par; + unsigned int offset; + + /* Calculate the offset */ + if (info->var.bits_per_pixel == 0) { + offset = (var->yoffset / 16) * (info->var.xres_virtual / 2) + + (var->xoffset / 2); + offset = offset >> 2; + } else { + offset = (var->yoffset * info->fix.line_length) + + (var->xoffset * info->var.bits_per_pixel / 8); + offset = offset >> 2; + } + + /* Set the offset */ + svga_wcrt_multi(par->state.vgabase, s3_start_address_regs, offset); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +/* Frame buffer operations */ + +static struct fb_ops s3fb_ops = { + .owner = THIS_MODULE, + .fb_open = s3fb_open, + .fb_release = s3fb_release, + .fb_check_var = s3fb_check_var, + .fb_set_par = s3fb_set_par, + .fb_setcolreg = s3fb_setcolreg, + .fb_blank = s3fb_blank, + .fb_pan_display = s3fb_pan_display, + .fb_fillrect = s3fb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = s3fb_imageblit, + .fb_get_caps = svga_get_caps, +}; + +/* ------------------------------------------------------------------------- */ + +static int s3_identification(struct s3fb_info *par) +{ + int chip = par->chip; + + if (chip == CHIP_XXX_TRIO) { + u8 cr30 = vga_rcrt(par->state.vgabase, 0x30); + u8 cr2e = vga_rcrt(par->state.vgabase, 0x2e); + u8 cr2f = vga_rcrt(par->state.vgabase, 0x2f); + + if ((cr30 == 0xE0) || (cr30 == 0xE1)) { + if (cr2e == 0x10) + return CHIP_732_TRIO32; + if (cr2e == 0x11) { + if (! (cr2f & 0x40)) + return CHIP_764_TRIO64; + else + return CHIP_765_TRIO64VP; + } + } + } + + if (chip == CHIP_XXX_TRIO64V2_DXGX) { + u8 cr6f = vga_rcrt(par->state.vgabase, 0x6f); + + if (! (cr6f & 0x01)) + return CHIP_775_TRIO64V2_DX; + else + return CHIP_785_TRIO64V2_GX; + } + + if (chip == CHIP_XXX_VIRGE_DXGX) { + u8 cr6f = vga_rcrt(par->state.vgabase, 0x6f); + + if (! (cr6f & 0x01)) + return CHIP_375_VIRGE_DX; + else + return CHIP_385_VIRGE_GX; + } + + if (chip == CHIP_36X_TRIO3D_1X_2X) { + switch (vga_rcrt(par->state.vgabase, 0x2f)) { + case 0x00: + return CHIP_360_TRIO3D_1X; + case 0x01: + return CHIP_362_TRIO3D_2X; + case 0x02: + return CHIP_368_TRIO3D_2X; + } + } + + return CHIP_UNKNOWN; +} + + +/* PCI probe */ + +static int s3_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct pci_bus_region bus_reg; + struct resource vga_res; + struct fb_info *info; + struct s3fb_info *par; + int rc; + u8 regval, cr38, cr39; + bool found = false; + + /* Ignore secondary VGA device because there is no VGA arbitration */ + if (! svga_primary_device(dev)) { + dev_info(&(dev->dev), "ignoring secondary device\n"); + return -ENODEV; + } + + /* Allocate and fill driver data structure */ + info = framebuffer_alloc(sizeof(struct s3fb_info), &(dev->dev)); + if (!info) { + dev_err(&(dev->dev), "cannot allocate memory\n"); + return -ENOMEM; + } + + par = info->par; + mutex_init(&par->open_lock); + + info->flags = FBINFO_PARTIAL_PAN_OK | FBINFO_HWACCEL_YPAN; + info->fbops = &s3fb_ops; + + /* Prepare PCI device */ + rc = pci_enable_device(dev); + if (rc < 0) { + dev_err(info->device, "cannot enable PCI device\n"); + goto err_enable_device; + } + + rc = pci_request_regions(dev, "s3fb"); + if (rc < 0) { + dev_err(info->device, "cannot reserve framebuffer region\n"); + goto err_request_regions; + } + + + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = pci_resource_len(dev, 0); + + /* Map physical IO memory address into kernel space */ + info->screen_base = pci_iomap(dev, 0, 0); + if (! info->screen_base) { + rc = -ENOMEM; + dev_err(info->device, "iomap for framebuffer failed\n"); + goto err_iomap; + } + + bus_reg.start = 0; + bus_reg.end = 64 * 1024; + + vga_res.flags = IORESOURCE_IO; + + pcibios_bus_to_resource(dev->bus, &vga_res, &bus_reg); + + par->state.vgabase = (void __iomem *) vga_res.start; + + /* Unlock regs */ + cr38 = vga_rcrt(par->state.vgabase, 0x38); + cr39 = vga_rcrt(par->state.vgabase, 0x39); + vga_wseq(par->state.vgabase, 0x08, 0x06); + vga_wcrt(par->state.vgabase, 0x38, 0x48); + vga_wcrt(par->state.vgabase, 0x39, 0xA5); + + /* Identify chip type */ + par->chip = id->driver_data & CHIP_MASK; + par->rev = vga_rcrt(par->state.vgabase, 0x2f); + if (par->chip & CHIP_UNDECIDED_FLAG) + par->chip = s3_identification(par); + + /* Find how many physical memory there is on card */ + /* 0x36 register is accessible even if other registers are locked */ + regval = vga_rcrt(par->state.vgabase, 0x36); + if (par->chip == CHIP_360_TRIO3D_1X || + par->chip == CHIP_362_TRIO3D_2X || + par->chip == CHIP_368_TRIO3D_2X || + par->chip == CHIP_365_TRIO3D) { + switch ((regval & 0xE0) >> 5) { + case 0: /* 8MB -- only 4MB usable for display */ + case 1: /* 4MB with 32-bit bus */ + case 2: /* 4MB */ + info->screen_size = 4 << 20; + break; + case 4: /* 2MB on 365 Trio3D */ + case 6: /* 2MB */ + info->screen_size = 2 << 20; + break; + } + } else if (par->chip == CHIP_357_VIRGE_GX2 || + par->chip == CHIP_359_VIRGE_GX2P || + par->chip == CHIP_260_VIRGE_MX) { + switch ((regval & 0xC0) >> 6) { + case 1: /* 4MB */ + info->screen_size = 4 << 20; + break; + case 3: /* 2MB */ + info->screen_size = 2 << 20; + break; + } + } else if (par->chip == CHIP_988_VIRGE_VX) { + switch ((regval & 0x60) >> 5) { + case 0: /* 2MB */ + info->screen_size = 2 << 20; + break; + case 1: /* 4MB */ + info->screen_size = 4 << 20; + break; + case 2: /* 6MB */ + info->screen_size = 6 << 20; + break; + case 3: /* 8MB */ + info->screen_size = 8 << 20; + break; + } + /* off-screen memory */ + regval = vga_rcrt(par->state.vgabase, 0x37); + switch ((regval & 0x60) >> 5) { + case 1: /* 4MB */ + info->screen_size -= 4 << 20; + break; + case 2: /* 2MB */ + info->screen_size -= 2 << 20; + break; + } + } else + info->screen_size = s3_memsizes[regval >> 5] << 10; + info->fix.smem_len = info->screen_size; + + /* Find MCLK frequency */ + regval = vga_rseq(par->state.vgabase, 0x10); + par->mclk_freq = ((vga_rseq(par->state.vgabase, 0x11) + 2) * 14318) / ((regval & 0x1F) + 2); + par->mclk_freq = par->mclk_freq >> (regval >> 5); + + /* Restore locks */ + vga_wcrt(par->state.vgabase, 0x38, cr38); + vga_wcrt(par->state.vgabase, 0x39, cr39); + + strcpy(info->fix.id, s3_names [par->chip]); + info->fix.mmio_start = 0; + info->fix.mmio_len = 0; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.ypanstep = 0; + info->fix.accel = FB_ACCEL_NONE; + info->pseudo_palette = (void*) (par->pseudo_palette); + info->var.bits_per_pixel = 8; + +#ifdef CONFIG_FB_S3_DDC + /* Enable MMIO if needed */ + if (s3fb_ddc_needs_mmio(par->chip)) { + par->mmio = ioremap(info->fix.smem_start + MMIO_OFFSET, MMIO_SIZE); + if (par->mmio) + svga_wcrt_mask(par->state.vgabase, 0x53, 0x08, 0x08); /* enable MMIO */ + else + dev_err(info->device, "unable to map MMIO at 0x%lx, disabling DDC", + info->fix.smem_start + MMIO_OFFSET); + } + if (!s3fb_ddc_needs_mmio(par->chip) || par->mmio) + if (s3fb_setup_ddc_bus(info) == 0) { + u8 *edid = fb_ddc_read(&par->ddc_adapter); + par->ddc_registered = true; + if (edid) { + fb_edid_to_monspecs(edid, &info->monspecs); + kfree(edid); + if (!info->monspecs.modedb) + dev_err(info->device, "error getting mode database\n"); + else { + const struct fb_videomode *m; + + fb_videomode_to_modelist(info->monspecs.modedb, + info->monspecs.modedb_len, + &info->modelist); + m = fb_find_best_display(&info->monspecs, &info->modelist); + if (m) { + fb_videomode_to_var(&info->var, m); + /* fill all other info->var's fields */ + if (s3fb_check_var(&info->var, info) == 0) + found = true; + } + } + } + } +#endif + if (!mode_option && !found) + mode_option = "640x480-8@60"; + + /* Prepare startup mode */ + if (mode_option) { + rc = fb_find_mode(&info->var, info, mode_option, + info->monspecs.modedb, info->monspecs.modedb_len, + NULL, info->var.bits_per_pixel); + if (!rc || rc == 4) { + rc = -EINVAL; + dev_err(info->device, "mode %s not found\n", mode_option); + fb_destroy_modedb(info->monspecs.modedb); + info->monspecs.modedb = NULL; + goto err_find_mode; + } + } + + fb_destroy_modedb(info->monspecs.modedb); + info->monspecs.modedb = NULL; + + /* maximize virtual vertical size for fast scrolling */ + info->var.yres_virtual = info->fix.smem_len * 8 / + (info->var.bits_per_pixel * info->var.xres_virtual); + if (info->var.yres_virtual < info->var.yres) { + dev_err(info->device, "virtual vertical size smaller than real\n"); + rc = -EINVAL; + goto err_find_mode; + } + + rc = fb_alloc_cmap(&info->cmap, 256, 0); + if (rc < 0) { + dev_err(info->device, "cannot allocate colormap\n"); + goto err_alloc_cmap; + } + + rc = register_framebuffer(info); + if (rc < 0) { + dev_err(info->device, "cannot register framebuffer\n"); + goto err_reg_fb; + } + + fb_info(info, "%s on %s, %d MB RAM, %d MHz MCLK\n", + info->fix.id, pci_name(dev), + info->fix.smem_len >> 20, (par->mclk_freq + 500) / 1000); + + if (par->chip == CHIP_UNKNOWN) + fb_info(info, "unknown chip, CR2D=%x, CR2E=%x, CRT2F=%x, CRT30=%x\n", + vga_rcrt(par->state.vgabase, 0x2d), + vga_rcrt(par->state.vgabase, 0x2e), + vga_rcrt(par->state.vgabase, 0x2f), + vga_rcrt(par->state.vgabase, 0x30)); + + /* Record a reference to the driver data */ + pci_set_drvdata(dev, info); + +#ifdef CONFIG_MTRR + if (mtrr) { + par->mtrr_reg = -1; + par->mtrr_reg = mtrr_add(info->fix.smem_start, info->fix.smem_len, MTRR_TYPE_WRCOMB, 1); + } +#endif + + return 0; + + /* Error handling */ +err_reg_fb: + fb_dealloc_cmap(&info->cmap); +err_alloc_cmap: +err_find_mode: +#ifdef CONFIG_FB_S3_DDC + if (par->ddc_registered) + i2c_del_adapter(&par->ddc_adapter); + if (par->mmio) + iounmap(par->mmio); +#endif + pci_iounmap(dev, info->screen_base); +err_iomap: + pci_release_regions(dev); +err_request_regions: +/* pci_disable_device(dev); */ +err_enable_device: + framebuffer_release(info); + return rc; +} + + +/* PCI remove */ + +static void s3_pci_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct s3fb_info __maybe_unused *par = info->par; + + if (info) { + +#ifdef CONFIG_MTRR + if (par->mtrr_reg >= 0) { + mtrr_del(par->mtrr_reg, 0, 0); + par->mtrr_reg = -1; + } +#endif + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + +#ifdef CONFIG_FB_S3_DDC + if (par->ddc_registered) + i2c_del_adapter(&par->ddc_adapter); + if (par->mmio) + iounmap(par->mmio); +#endif + + pci_iounmap(dev, info->screen_base); + pci_release_regions(dev); +/* pci_disable_device(dev); */ + + framebuffer_release(info); + } +} + +/* PCI suspend */ + +static int s3_pci_suspend(struct pci_dev* dev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct s3fb_info *par = info->par; + + dev_info(info->device, "suspend\n"); + + console_lock(); + mutex_lock(&(par->open_lock)); + + if ((state.event == PM_EVENT_FREEZE) || (par->ref_count == 0)) { + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; + } + + fb_set_suspend(info, 1); + + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, state)); + + mutex_unlock(&(par->open_lock)); + console_unlock(); + + return 0; +} + + +/* PCI resume */ + +static int s3_pci_resume(struct pci_dev* dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct s3fb_info *par = info->par; + int err; + + dev_info(info->device, "resume\n"); + + console_lock(); + mutex_lock(&(par->open_lock)); + + if (par->ref_count == 0) { + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; + } + + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + err = pci_enable_device(dev); + if (err) { + mutex_unlock(&(par->open_lock)); + console_unlock(); + dev_err(info->device, "error %d enabling device for resume\n", err); + return err; + } + pci_set_master(dev); + + s3fb_set_par(info); + fb_set_suspend(info, 0); + + mutex_unlock(&(par->open_lock)); + console_unlock(); + + return 0; +} + + +/* List of boards that we are trying to support */ + +static struct pci_device_id s3_devices[] = { + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8810), .driver_data = CHIP_XXX_TRIO}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8811), .driver_data = CHIP_XXX_TRIO}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8812), .driver_data = CHIP_M65_AURORA64VP}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8814), .driver_data = CHIP_767_TRIO64UVP}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8901), .driver_data = CHIP_XXX_TRIO64V2_DXGX}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8902), .driver_data = CHIP_551_PLATO_PX}, + + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x5631), .driver_data = CHIP_325_VIRGE}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x883D), .driver_data = CHIP_988_VIRGE_VX}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8A01), .driver_data = CHIP_XXX_VIRGE_DXGX}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8A10), .driver_data = CHIP_357_VIRGE_GX2}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8A11), .driver_data = CHIP_359_VIRGE_GX2P}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8A12), .driver_data = CHIP_359_VIRGE_GX2P}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8A13), .driver_data = CHIP_36X_TRIO3D_1X_2X}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8904), .driver_data = CHIP_365_TRIO3D}, + {PCI_DEVICE(PCI_VENDOR_ID_S3, 0x8C01), .driver_data = CHIP_260_VIRGE_MX}, + + {0, 0, 0, 0, 0, 0, 0} +}; + + +MODULE_DEVICE_TABLE(pci, s3_devices); + +static struct pci_driver s3fb_pci_driver = { + .name = "s3fb", + .id_table = s3_devices, + .probe = s3_pci_probe, + .remove = s3_pci_remove, + .suspend = s3_pci_suspend, + .resume = s3_pci_resume, +}; + +/* Parse user specified options */ + +#ifndef MODULE +static int __init s3fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + + if (!*opt) + continue; +#ifdef CONFIG_MTRR + else if (!strncmp(opt, "mtrr:", 5)) + mtrr = simple_strtoul(opt + 5, NULL, 0); +#endif + else if (!strncmp(opt, "fasttext:", 9)) + fasttext = simple_strtoul(opt + 9, NULL, 0); + else + mode_option = opt; + } + + return 0; +} +#endif + +/* Cleanup */ + +static void __exit s3fb_cleanup(void) +{ + pr_debug("s3fb: cleaning up\n"); + pci_unregister_driver(&s3fb_pci_driver); +} + +/* Driver Initialisation */ + +static int __init s3fb_init(void) +{ + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("s3fb", &option)) + return -ENODEV; + s3fb_setup(option); +#endif + + pr_debug("s3fb: initializing\n"); + return pci_register_driver(&s3fb_pci_driver); +} + +/* ------------------------------------------------------------------------- */ + +/* Modularization */ + +module_init(s3fb_init); +module_exit(s3fb_cleanup); diff --git a/drivers/video/fbdev/sa1100fb.c b/drivers/video/fbdev/sa1100fb.c new file mode 100644 index 000000000000..580c444ec301 --- /dev/null +++ b/drivers/video/fbdev/sa1100fb.c @@ -0,0 +1,1340 @@ +/* + * linux/drivers/video/sa1100fb.c + * + * Copyright (C) 1999 Eric A. Thomas + * Based on acornfb.c Copyright (C) Russell King. + * + * 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. + * + * StrongARM 1100 LCD Controller Frame Buffer Driver + * + * Please direct your questions and comments on this driver to the following + * email address: + * + * linux-arm-kernel@lists.arm.linux.org.uk + * + * Clean patches should be sent to the ARM Linux Patch System. Please see the + * following web page for more information: + * + * http://www.arm.linux.org.uk/developer/patches/info.shtml + * + * Thank you. + * + * Known problems: + * - With the Neponset plugged into an Assabet, LCD powerdown + * doesn't work (LCD stays powered up). Therefore we shouldn't + * blank the screen. + * - We don't limit the CPU clock rate nor the mode selection + * according to the available SDRAM bandwidth. + * + * Other notes: + * - Linear grayscale palettes and the kernel. + * Such code does not belong in the kernel. The kernel frame buffer + * drivers do not expect a linear colourmap, but a colourmap based on + * the VT100 standard mapping. + * + * If your _userspace_ requires a linear colourmap, then the setup of + * such a colourmap belongs _in userspace_, not in the kernel. Code + * to set the colourmap correctly from user space has been sent to + * David Neuer. It's around 8 lines of C code, plus another 4 to + * detect if we are using grayscale. + * + * - The following must never be specified in a panel definition: + * LCCR0_LtlEnd, LCCR3_PixClkDiv, LCCR3_VrtSnchL, LCCR3_HorSnchL + * + * - The following should be specified: + * either LCCR0_Color or LCCR0_Mono + * either LCCR0_Sngl or LCCR0_Dual + * either LCCR0_Act or LCCR0_Pas + * either LCCR3_OutEnH or LCCD3_OutEnL + * either LCCR3_PixRsEdg or LCCR3_PixFlEdg + * either LCCR3_ACBsDiv or LCCR3_ACBsCntOff + * + * Code Status: + * 1999/04/01: + * - Driver appears to be working for Brutus 320x200x8bpp mode. Other + * resolutions are working, but only the 8bpp mode is supported. + * Changes need to be made to the palette encode and decode routines + * to support 4 and 16 bpp modes. + * Driver is not designed to be a module. The FrameBuffer is statically + * allocated since dynamic allocation of a 300k buffer cannot be + * guaranteed. + * + * 1999/06/17: + * - FrameBuffer memory is now allocated at run-time when the + * driver is initialized. + * + * 2000/04/10: Nicolas Pitre <nico@fluxnic.net> + * - Big cleanup for dynamic selection of machine type at run time. + * + * 2000/07/19: Jamey Hicks <jamey@crl.dec.com> + * - Support for Bitsy aka Compaq iPAQ H3600 added. + * + * 2000/08/07: Tak-Shing Chan <tchan.rd@idthk.com> + * Jeff Sutherland <jsutherland@accelent.com> + * - Resolved an issue caused by a change made to the Assabet's PLD + * earlier this year which broke the framebuffer driver for newer + * Phase 4 Assabets. Some other parameters were changed to optimize + * for the Sharp display. + * + * 2000/08/09: Kunihiko IMAI <imai@vasara.co.jp> + * - XP860 support added + * + * 2000/08/19: Mark Huang <mhuang@livetoy.com> + * - Allows standard options to be passed on the kernel command line + * for most common passive displays. + * + * 2000/08/29: + * - s/save_flags_cli/local_irq_save/ + * - remove unneeded extra save_flags_cli in sa1100fb_enable_lcd_controller + * + * 2000/10/10: Erik Mouw <J.A.K.Mouw@its.tudelft.nl> + * - Updated LART stuff. Fixed some minor bugs. + * + * 2000/10/30: Murphy Chen <murphy@mail.dialogue.com.tw> + * - Pangolin support added + * + * 2000/10/31: Roman Jordan <jor@hoeft-wessel.de> + * - Huw Webpanel support added + * + * 2000/11/23: Eric Peng <ericpeng@coventive.com> + * - Freebird add + * + * 2001/02/07: Jamey Hicks <jamey.hicks@compaq.com> + * Cliff Brake <cbrake@accelent.com> + * - Added PM callback + * + * 2001/05/26: <rmk@arm.linux.org.uk> + * - Fix 16bpp so that (a) we use the right colours rather than some + * totally random colour depending on what was in page 0, and (b) + * we don't de-reference a NULL pointer. + * - remove duplicated implementation of consistent_alloc() + * - convert dma address types to dma_addr_t + * - remove unused 'montype' stuff + * - remove redundant zero inits of init_var after the initial + * memset. + * - remove allow_modeset (acornfb idea does not belong here) + * + * 2001/05/28: <rmk@arm.linux.org.uk> + * - massive cleanup - move machine dependent data into structures + * - I've left various #warnings in - if you see one, and know + * the hardware concerned, please get in contact with me. + * + * 2001/05/31: <rmk@arm.linux.org.uk> + * - Fix LCCR1 HSW value, fix all machine type specifications to + * keep values in line. (Please check your machine type specs) + * + * 2001/06/10: <rmk@arm.linux.org.uk> + * - Fiddle with the LCD controller from task context only; mainly + * so that we can run with interrupts on, and sleep. + * - Convert #warnings into #errors. No pain, no gain. ;) + * + * 2001/06/14: <rmk@arm.linux.org.uk> + * - Make the palette BPS value for 12bpp come out correctly. + * - Take notice of "greyscale" on any colour depth. + * - Make truecolor visuals use the RGB channel encoding information. + * + * 2001/07/02: <rmk@arm.linux.org.uk> + * - Fix colourmap problems. + * + * 2001/07/13: <abraham@2d3d.co.za> + * - Added support for the ICP LCD-Kit01 on LART. This LCD is + * manufactured by Prime View, model no V16C6448AB + * + * 2001/07/23: <rmk@arm.linux.org.uk> + * - Hand merge version from handhelds.org CVS tree. See patch + * notes for 595/1 for more information. + * - Drop 12bpp (it's 16bpp with different colour register mappings). + * - This hardware can not do direct colour. Therefore we don't + * support it. + * + * 2001/07/27: <rmk@arm.linux.org.uk> + * - Halve YRES on dual scan LCDs. + * + * 2001/08/22: <rmk@arm.linux.org.uk> + * - Add b/w iPAQ pixclock value. + * + * 2001/10/12: <rmk@arm.linux.org.uk> + * - Add patch 681/1 and clean up stork definitions. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/cpufreq.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/mutex.h> +#include <linux/io.h> + +#include <video/sa1100fb.h> + +#include <mach/hardware.h> +#include <asm/mach-types.h> +#include <mach/shannon.h> + +/* + * Complain if VAR is out of range. + */ +#define DEBUG_VAR 1 + +#include "sa1100fb.h" + +static const struct sa1100fb_rgb rgb_4 = { + .red = { .offset = 0, .length = 4, }, + .green = { .offset = 0, .length = 4, }, + .blue = { .offset = 0, .length = 4, }, + .transp = { .offset = 0, .length = 0, }, +}; + +static const struct sa1100fb_rgb rgb_8 = { + .red = { .offset = 0, .length = 8, }, + .green = { .offset = 0, .length = 8, }, + .blue = { .offset = 0, .length = 8, }, + .transp = { .offset = 0, .length = 0, }, +}; + +static const struct sa1100fb_rgb def_rgb_16 = { + .red = { .offset = 11, .length = 5, }, + .green = { .offset = 5, .length = 6, }, + .blue = { .offset = 0, .length = 5, }, + .transp = { .offset = 0, .length = 0, }, +}; + + + +static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_info *); +static void set_ctrlr_state(struct sa1100fb_info *fbi, u_int state); + +static inline void sa1100fb_schedule_work(struct sa1100fb_info *fbi, u_int state) +{ + unsigned long flags; + + local_irq_save(flags); + /* + * We need to handle two requests being made at the same time. + * There are two important cases: + * 1. When we are changing VT (C_REENABLE) while unblanking (C_ENABLE) + * We must perform the unblanking, which will do our REENABLE for us. + * 2. When we are blanking, but immediately unblank before we have + * blanked. We do the "REENABLE" thing here as well, just to be sure. + */ + if (fbi->task_state == C_ENABLE && state == C_REENABLE) + state = (u_int) -1; + if (fbi->task_state == C_DISABLE && state == C_ENABLE) + state = C_REENABLE; + + if (state != (u_int)-1) { + fbi->task_state = state; + schedule_work(&fbi->task); + } + local_irq_restore(flags); +} + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +/* + * Convert bits-per-pixel to a hardware palette PBS value. + */ +static inline u_int palette_pbs(struct fb_var_screeninfo *var) +{ + int ret = 0; + switch (var->bits_per_pixel) { + case 4: ret = 0 << 12; break; + case 8: ret = 1 << 12; break; + case 16: ret = 2 << 12; break; + } + return ret; +} + +static int +sa1100fb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; + u_int val, ret = 1; + + if (regno < fbi->palette_size) { + val = ((red >> 4) & 0xf00); + val |= ((green >> 8) & 0x0f0); + val |= ((blue >> 12) & 0x00f); + + if (regno == 0) + val |= palette_pbs(&fbi->fb.var); + + fbi->palette_cpu[regno] = val; + ret = 0; + } + return ret; +} + +static int +sa1100fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; + unsigned int val; + int ret = 1; + + /* + * If inverse mode was selected, invert all the colours + * rather than the register number. The register number + * is what you poke into the framebuffer to produce the + * colour you requested. + */ + if (fbi->inf->cmap_inverse) { + red = 0xffff - red; + green = 0xffff - green; + blue = 0xffff - blue; + } + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no mater what visual we are using. + */ + if (fbi->fb.var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + switch (fbi->fb.fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 12 or 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->fb.pseudo_palette; + + val = chan_to_field(red, &fbi->fb.var.red); + val |= chan_to_field(green, &fbi->fb.var.green); + val |= chan_to_field(blue, &fbi->fb.var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + ret = sa1100fb_setpalettereg(regno, red, green, blue, trans, info); + break; + } + + return ret; +} + +#ifdef CONFIG_CPU_FREQ +/* + * sa1100fb_display_dma_period() + * Calculate the minimum period (in picoseconds) between two DMA + * requests for the LCD controller. If we hit this, it means we're + * doing nothing but LCD DMA. + */ +static inline unsigned int sa1100fb_display_dma_period(struct fb_var_screeninfo *var) +{ + /* + * Period = pixclock * bits_per_byte * bytes_per_transfer + * / memory_bits_per_pixel; + */ + return var->pixclock * 8 * 16 / var->bits_per_pixel; +} +#endif + +/* + * sa1100fb_check_var(): + * Round up in the following order: bits_per_pixel, xres, + * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale, + * bitfields, horizontal timing, vertical timing. + */ +static int +sa1100fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; + int rgbidx; + + if (var->xres < MIN_XRES) + var->xres = MIN_XRES; + if (var->yres < MIN_YRES) + var->yres = MIN_YRES; + if (var->xres > fbi->inf->xres) + var->xres = fbi->inf->xres; + if (var->yres > fbi->inf->yres) + var->yres = fbi->inf->yres; + var->xres_virtual = max(var->xres_virtual, var->xres); + var->yres_virtual = max(var->yres_virtual, var->yres); + + dev_dbg(fbi->dev, "var->bits_per_pixel=%d\n", var->bits_per_pixel); + switch (var->bits_per_pixel) { + case 4: + rgbidx = RGB_4; + break; + case 8: + rgbidx = RGB_8; + break; + case 16: + rgbidx = RGB_16; + break; + default: + return -EINVAL; + } + + /* + * Copy the RGB parameters for this display + * from the machine specific parameters. + */ + var->red = fbi->rgb[rgbidx]->red; + var->green = fbi->rgb[rgbidx]->green; + var->blue = fbi->rgb[rgbidx]->blue; + var->transp = fbi->rgb[rgbidx]->transp; + + dev_dbg(fbi->dev, "RGBT length = %d:%d:%d:%d\n", + var->red.length, var->green.length, var->blue.length, + var->transp.length); + + dev_dbg(fbi->dev, "RGBT offset = %d:%d:%d:%d\n", + var->red.offset, var->green.offset, var->blue.offset, + var->transp.offset); + +#ifdef CONFIG_CPU_FREQ + dev_dbg(fbi->dev, "dma period = %d ps, clock = %d kHz\n", + sa1100fb_display_dma_period(var), + cpufreq_get(smp_processor_id())); +#endif + + return 0; +} + +static void sa1100fb_set_visual(struct sa1100fb_info *fbi, u32 visual) +{ + if (fbi->inf->set_visual) + fbi->inf->set_visual(visual); +} + +/* + * sa1100fb_set_par(): + * Set the user defined part of the display for the specified console + */ +static int sa1100fb_set_par(struct fb_info *info) +{ + struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; + struct fb_var_screeninfo *var = &info->var; + unsigned long palette_mem_size; + + dev_dbg(fbi->dev, "set_par\n"); + + if (var->bits_per_pixel == 16) + fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR; + else if (!fbi->inf->cmap_static) + fbi->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + else { + /* + * Some people have weird ideas about wanting static + * pseudocolor maps. I suspect their user space + * applications are broken. + */ + fbi->fb.fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + } + + fbi->fb.fix.line_length = var->xres_virtual * + var->bits_per_pixel / 8; + fbi->palette_size = var->bits_per_pixel == 8 ? 256 : 16; + + palette_mem_size = fbi->palette_size * sizeof(u16); + + dev_dbg(fbi->dev, "palette_mem_size = 0x%08lx\n", palette_mem_size); + + fbi->palette_cpu = (u16 *)(fbi->map_cpu + PAGE_SIZE - palette_mem_size); + fbi->palette_dma = fbi->map_dma + PAGE_SIZE - palette_mem_size; + + /* + * Set (any) board control register to handle new color depth + */ + sa1100fb_set_visual(fbi, fbi->fb.fix.visual); + sa1100fb_activate_var(var, fbi); + + return 0; +} + +#if 0 +static int +sa1100fb_set_cmap(struct fb_cmap *cmap, int kspc, int con, + struct fb_info *info) +{ + struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; + + /* + * Make sure the user isn't doing something stupid. + */ + if (!kspc && (fbi->fb.var.bits_per_pixel == 16 || fbi->inf->cmap_static)) + return -EINVAL; + + return gen_set_cmap(cmap, kspc, con, info); +} +#endif + +/* + * Formal definition of the VESA spec: + * On + * This refers to the state of the display when it is in full operation + * Stand-By + * This defines an optional operating state of minimal power reduction with + * the shortest recovery time + * Suspend + * This refers to a level of power management in which substantial power + * reduction is achieved by the display. The display can have a longer + * recovery time from this state than from the Stand-by state + * Off + * This indicates that the display is consuming the lowest level of power + * and is non-operational. Recovery from this state may optionally require + * the user to manually power on the monitor + * + * Now, the fbdev driver adds an additional state, (blank), where they + * turn off the video (maybe by colormap tricks), but don't mess with the + * video itself: think of it semantically between on and Stand-By. + * + * So here's what we should do in our fbdev blank routine: + * + * VESA_NO_BLANKING (mode 0) Video on, front/back light on + * VESA_VSYNC_SUSPEND (mode 1) Video on, front/back light off + * VESA_HSYNC_SUSPEND (mode 2) Video on, front/back light off + * VESA_POWERDOWN (mode 3) Video off, front/back light off + * + * This will match the matrox implementation. + */ +/* + * sa1100fb_blank(): + * Blank the display by setting all palette values to zero. Note, the + * 12 and 16 bpp modes don't really use the palette, so this will not + * blank the display in all modes. + */ +static int sa1100fb_blank(int blank, struct fb_info *info) +{ + struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; + int i; + + dev_dbg(fbi->dev, "sa1100fb_blank: blank=%d\n", blank); + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR || + fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) + for (i = 0; i < fbi->palette_size; i++) + sa1100fb_setpalettereg(i, 0, 0, 0, 0, info); + sa1100fb_schedule_work(fbi, C_DISABLE); + break; + + case FB_BLANK_UNBLANK: + if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR || + fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) + fb_set_cmap(&fbi->fb.cmap, info); + sa1100fb_schedule_work(fbi, C_ENABLE); + } + return 0; +} + +static int sa1100fb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + + if (off < info->fix.smem_len) { + vma->vm_pgoff += 1; /* skip over the palette */ + return dma_mmap_writecombine(fbi->dev, vma, fbi->map_cpu, + fbi->map_dma, fbi->map_size); + } + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return vm_iomap_memory(vma, info->fix.mmio_start, info->fix.mmio_len); +} + +static struct fb_ops sa1100fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = sa1100fb_check_var, + .fb_set_par = sa1100fb_set_par, +// .fb_set_cmap = sa1100fb_set_cmap, + .fb_setcolreg = sa1100fb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = sa1100fb_blank, + .fb_mmap = sa1100fb_mmap, +}; + +/* + * Calculate the PCD value from the clock rate (in picoseconds). + * We take account of the PPCR clock setting. + */ +static inline unsigned int get_pcd(unsigned int pixclock, unsigned int cpuclock) +{ + unsigned int pcd = cpuclock / 100; + + pcd *= pixclock; + pcd /= 10000000; + + return pcd + 1; /* make up for integer math truncations */ +} + +/* + * sa1100fb_activate_var(): + * Configures LCD Controller based on entries in var parameter. Settings are + * only written to the controller if changes were made. + */ +static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_info *fbi) +{ + struct sa1100fb_lcd_reg new_regs; + u_int half_screen_size, yres, pcd; + u_long flags; + + dev_dbg(fbi->dev, "Configuring SA1100 LCD\n"); + + dev_dbg(fbi->dev, "var: xres=%d hslen=%d lm=%d rm=%d\n", + var->xres, var->hsync_len, + var->left_margin, var->right_margin); + dev_dbg(fbi->dev, "var: yres=%d vslen=%d um=%d bm=%d\n", + var->yres, var->vsync_len, + var->upper_margin, var->lower_margin); + +#if DEBUG_VAR + if (var->xres < 16 || var->xres > 1024) + dev_err(fbi->dev, "%s: invalid xres %d\n", + fbi->fb.fix.id, var->xres); + if (var->hsync_len < 1 || var->hsync_len > 64) + dev_err(fbi->dev, "%s: invalid hsync_len %d\n", + fbi->fb.fix.id, var->hsync_len); + if (var->left_margin < 1 || var->left_margin > 255) + dev_err(fbi->dev, "%s: invalid left_margin %d\n", + fbi->fb.fix.id, var->left_margin); + if (var->right_margin < 1 || var->right_margin > 255) + dev_err(fbi->dev, "%s: invalid right_margin %d\n", + fbi->fb.fix.id, var->right_margin); + if (var->yres < 1 || var->yres > 1024) + dev_err(fbi->dev, "%s: invalid yres %d\n", + fbi->fb.fix.id, var->yres); + if (var->vsync_len < 1 || var->vsync_len > 64) + dev_err(fbi->dev, "%s: invalid vsync_len %d\n", + fbi->fb.fix.id, var->vsync_len); + if (var->upper_margin < 0 || var->upper_margin > 255) + dev_err(fbi->dev, "%s: invalid upper_margin %d\n", + fbi->fb.fix.id, var->upper_margin); + if (var->lower_margin < 0 || var->lower_margin > 255) + dev_err(fbi->dev, "%s: invalid lower_margin %d\n", + fbi->fb.fix.id, var->lower_margin); +#endif + + new_regs.lccr0 = fbi->inf->lccr0 | + LCCR0_LEN | LCCR0_LDM | LCCR0_BAM | + LCCR0_ERM | LCCR0_LtlEnd | LCCR0_DMADel(0); + + new_regs.lccr1 = + LCCR1_DisWdth(var->xres) + + LCCR1_HorSnchWdth(var->hsync_len) + + LCCR1_BegLnDel(var->left_margin) + + LCCR1_EndLnDel(var->right_margin); + + /* + * If we have a dual scan LCD, then we need to halve + * the YRES parameter. + */ + yres = var->yres; + if (fbi->inf->lccr0 & LCCR0_Dual) + yres /= 2; + + new_regs.lccr2 = + LCCR2_DisHght(yres) + + LCCR2_VrtSnchWdth(var->vsync_len) + + LCCR2_BegFrmDel(var->upper_margin) + + LCCR2_EndFrmDel(var->lower_margin); + + pcd = get_pcd(var->pixclock, cpufreq_get(0)); + new_regs.lccr3 = LCCR3_PixClkDiv(pcd) | fbi->inf->lccr3 | + (var->sync & FB_SYNC_HOR_HIGH_ACT ? LCCR3_HorSnchH : LCCR3_HorSnchL) | + (var->sync & FB_SYNC_VERT_HIGH_ACT ? LCCR3_VrtSnchH : LCCR3_VrtSnchL); + + dev_dbg(fbi->dev, "nlccr0 = 0x%08lx\n", new_regs.lccr0); + dev_dbg(fbi->dev, "nlccr1 = 0x%08lx\n", new_regs.lccr1); + dev_dbg(fbi->dev, "nlccr2 = 0x%08lx\n", new_regs.lccr2); + dev_dbg(fbi->dev, "nlccr3 = 0x%08lx\n", new_regs.lccr3); + + half_screen_size = var->bits_per_pixel; + half_screen_size = half_screen_size * var->xres * var->yres / 16; + + /* Update shadow copy atomically */ + local_irq_save(flags); + fbi->dbar1 = fbi->palette_dma; + fbi->dbar2 = fbi->screen_dma + half_screen_size; + + fbi->reg_lccr0 = new_regs.lccr0; + fbi->reg_lccr1 = new_regs.lccr1; + fbi->reg_lccr2 = new_regs.lccr2; + fbi->reg_lccr3 = new_regs.lccr3; + local_irq_restore(flags); + + /* + * Only update the registers if the controller is enabled + * and something has changed. + */ + if (readl_relaxed(fbi->base + LCCR0) != fbi->reg_lccr0 || + readl_relaxed(fbi->base + LCCR1) != fbi->reg_lccr1 || + readl_relaxed(fbi->base + LCCR2) != fbi->reg_lccr2 || + readl_relaxed(fbi->base + LCCR3) != fbi->reg_lccr3 || + readl_relaxed(fbi->base + DBAR1) != fbi->dbar1 || + readl_relaxed(fbi->base + DBAR2) != fbi->dbar2) + sa1100fb_schedule_work(fbi, C_REENABLE); + + return 0; +} + +/* + * NOTE! The following functions are purely helpers for set_ctrlr_state. + * Do not call them directly; set_ctrlr_state does the correct serialisation + * to ensure that things happen in the right way 100% of time time. + * -- rmk + */ +static inline void __sa1100fb_backlight_power(struct sa1100fb_info *fbi, int on) +{ + dev_dbg(fbi->dev, "backlight o%s\n", on ? "n" : "ff"); + + if (fbi->inf->backlight_power) + fbi->inf->backlight_power(on); +} + +static inline void __sa1100fb_lcd_power(struct sa1100fb_info *fbi, int on) +{ + dev_dbg(fbi->dev, "LCD power o%s\n", on ? "n" : "ff"); + + if (fbi->inf->lcd_power) + fbi->inf->lcd_power(on); +} + +static void sa1100fb_setup_gpio(struct sa1100fb_info *fbi) +{ + u_int mask = 0; + + /* + * Enable GPIO<9:2> for LCD use if: + * 1. Active display, or + * 2. Color Dual Passive display + * + * see table 11.8 on page 11-27 in the SA1100 manual + * -- Erik. + * + * SA1110 spec update nr. 25 says we can and should + * clear LDD15 to 12 for 4 or 8bpp modes with active + * panels. + */ + if ((fbi->reg_lccr0 & LCCR0_CMS) == LCCR0_Color && + (fbi->reg_lccr0 & (LCCR0_Dual|LCCR0_Act)) != 0) { + mask = GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9 | GPIO_LDD8; + + if (fbi->fb.var.bits_per_pixel > 8 || + (fbi->reg_lccr0 & (LCCR0_Dual|LCCR0_Act)) == LCCR0_Dual) + mask |= GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12; + + } + + if (mask) { + unsigned long flags; + + /* + * SA-1100 requires the GPIO direction register set + * appropriately for the alternate function. Hence + * we set it here via bitmask rather than excessive + * fiddling via the GPIO subsystem - and even then + * we'll still have to deal with GAFR. + */ + local_irq_save(flags); + GPDR |= mask; + GAFR |= mask; + local_irq_restore(flags); + } +} + +static void sa1100fb_enable_controller(struct sa1100fb_info *fbi) +{ + dev_dbg(fbi->dev, "Enabling LCD controller\n"); + + /* + * Make sure the mode bits are present in the first palette entry + */ + fbi->palette_cpu[0] &= 0xcfff; + fbi->palette_cpu[0] |= palette_pbs(&fbi->fb.var); + + /* Sequence from 11.7.10 */ + writel_relaxed(fbi->reg_lccr3, fbi->base + LCCR3); + writel_relaxed(fbi->reg_lccr2, fbi->base + LCCR2); + writel_relaxed(fbi->reg_lccr1, fbi->base + LCCR1); + writel_relaxed(fbi->reg_lccr0 & ~LCCR0_LEN, fbi->base + LCCR0); + writel_relaxed(fbi->dbar1, fbi->base + DBAR1); + writel_relaxed(fbi->dbar2, fbi->base + DBAR2); + writel_relaxed(fbi->reg_lccr0 | LCCR0_LEN, fbi->base + LCCR0); + + if (machine_is_shannon()) + gpio_set_value(SHANNON_GPIO_DISP_EN, 1); + + dev_dbg(fbi->dev, "DBAR1: 0x%08x\n", readl_relaxed(fbi->base + DBAR1)); + dev_dbg(fbi->dev, "DBAR2: 0x%08x\n", readl_relaxed(fbi->base + DBAR2)); + dev_dbg(fbi->dev, "LCCR0: 0x%08x\n", readl_relaxed(fbi->base + LCCR0)); + dev_dbg(fbi->dev, "LCCR1: 0x%08x\n", readl_relaxed(fbi->base + LCCR1)); + dev_dbg(fbi->dev, "LCCR2: 0x%08x\n", readl_relaxed(fbi->base + LCCR2)); + dev_dbg(fbi->dev, "LCCR3: 0x%08x\n", readl_relaxed(fbi->base + LCCR3)); +} + +static void sa1100fb_disable_controller(struct sa1100fb_info *fbi) +{ + DECLARE_WAITQUEUE(wait, current); + u32 lccr0; + + dev_dbg(fbi->dev, "Disabling LCD controller\n"); + + if (machine_is_shannon()) + gpio_set_value(SHANNON_GPIO_DISP_EN, 0); + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&fbi->ctrlr_wait, &wait); + + /* Clear LCD Status Register */ + writel_relaxed(~0, fbi->base + LCSR); + + lccr0 = readl_relaxed(fbi->base + LCCR0); + lccr0 &= ~LCCR0_LDM; /* Enable LCD Disable Done Interrupt */ + writel_relaxed(lccr0, fbi->base + LCCR0); + lccr0 &= ~LCCR0_LEN; /* Disable LCD Controller */ + writel_relaxed(lccr0, fbi->base + LCCR0); + + schedule_timeout(20 * HZ / 1000); + remove_wait_queue(&fbi->ctrlr_wait, &wait); +} + +/* + * sa1100fb_handle_irq: Handle 'LCD DONE' interrupts. + */ +static irqreturn_t sa1100fb_handle_irq(int irq, void *dev_id) +{ + struct sa1100fb_info *fbi = dev_id; + unsigned int lcsr = readl_relaxed(fbi->base + LCSR); + + if (lcsr & LCSR_LDD) { + u32 lccr0 = readl_relaxed(fbi->base + LCCR0) | LCCR0_LDM; + writel_relaxed(lccr0, fbi->base + LCCR0); + wake_up(&fbi->ctrlr_wait); + } + + writel_relaxed(lcsr, fbi->base + LCSR); + return IRQ_HANDLED; +} + +/* + * This function must be called from task context only, since it will + * sleep when disabling the LCD controller, or if we get two contending + * processes trying to alter state. + */ +static void set_ctrlr_state(struct sa1100fb_info *fbi, u_int state) +{ + u_int old_state; + + mutex_lock(&fbi->ctrlr_lock); + + old_state = fbi->state; + + /* + * Hack around fbcon initialisation. + */ + if (old_state == C_STARTUP && state == C_REENABLE) + state = C_ENABLE; + + switch (state) { + case C_DISABLE_CLKCHANGE: + /* + * Disable controller for clock change. If the + * controller is already disabled, then do nothing. + */ + if (old_state != C_DISABLE && old_state != C_DISABLE_PM) { + fbi->state = state; + sa1100fb_disable_controller(fbi); + } + break; + + case C_DISABLE_PM: + case C_DISABLE: + /* + * Disable controller + */ + if (old_state != C_DISABLE) { + fbi->state = state; + + __sa1100fb_backlight_power(fbi, 0); + if (old_state != C_DISABLE_CLKCHANGE) + sa1100fb_disable_controller(fbi); + __sa1100fb_lcd_power(fbi, 0); + } + break; + + case C_ENABLE_CLKCHANGE: + /* + * Enable the controller after clock change. Only + * do this if we were disabled for the clock change. + */ + if (old_state == C_DISABLE_CLKCHANGE) { + fbi->state = C_ENABLE; + sa1100fb_enable_controller(fbi); + } + break; + + case C_REENABLE: + /* + * Re-enable the controller only if it was already + * enabled. This is so we reprogram the control + * registers. + */ + if (old_state == C_ENABLE) { + sa1100fb_disable_controller(fbi); + sa1100fb_setup_gpio(fbi); + sa1100fb_enable_controller(fbi); + } + break; + + case C_ENABLE_PM: + /* + * Re-enable the controller after PM. This is not + * perfect - think about the case where we were doing + * a clock change, and we suspended half-way through. + */ + if (old_state != C_DISABLE_PM) + break; + /* fall through */ + + case C_ENABLE: + /* + * Power up the LCD screen, enable controller, and + * turn on the backlight. + */ + if (old_state != C_ENABLE) { + fbi->state = C_ENABLE; + sa1100fb_setup_gpio(fbi); + __sa1100fb_lcd_power(fbi, 1); + sa1100fb_enable_controller(fbi); + __sa1100fb_backlight_power(fbi, 1); + } + break; + } + mutex_unlock(&fbi->ctrlr_lock); +} + +/* + * Our LCD controller task (which is called when we blank or unblank) + * via keventd. + */ +static void sa1100fb_task(struct work_struct *w) +{ + struct sa1100fb_info *fbi = container_of(w, struct sa1100fb_info, task); + u_int state = xchg(&fbi->task_state, -1); + + set_ctrlr_state(fbi, state); +} + +#ifdef CONFIG_CPU_FREQ +/* + * Calculate the minimum DMA period over all displays that we own. + * This, together with the SDRAM bandwidth defines the slowest CPU + * frequency that can be selected. + */ +static unsigned int sa1100fb_min_dma_period(struct sa1100fb_info *fbi) +{ +#if 0 + unsigned int min_period = (unsigned int)-1; + int i; + + for (i = 0; i < MAX_NR_CONSOLES; i++) { + struct display *disp = &fb_display[i]; + unsigned int period; + + /* + * Do we own this display? + */ + if (disp->fb_info != &fbi->fb) + continue; + + /* + * Ok, calculate its DMA period + */ + period = sa1100fb_display_dma_period(&disp->var); + if (period < min_period) + min_period = period; + } + + return min_period; +#else + /* + * FIXME: we need to verify _all_ consoles. + */ + return sa1100fb_display_dma_period(&fbi->fb.var); +#endif +} + +/* + * CPU clock speed change handler. We need to adjust the LCD timing + * parameters when the CPU clock is adjusted by the power management + * subsystem. + */ +static int +sa1100fb_freq_transition(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct sa1100fb_info *fbi = TO_INF(nb, freq_transition); + struct cpufreq_freqs *f = data; + u_int pcd; + + switch (val) { + case CPUFREQ_PRECHANGE: + set_ctrlr_state(fbi, C_DISABLE_CLKCHANGE); + break; + + case CPUFREQ_POSTCHANGE: + pcd = get_pcd(fbi->fb.var.pixclock, f->new); + fbi->reg_lccr3 = (fbi->reg_lccr3 & ~0xff) | LCCR3_PixClkDiv(pcd); + set_ctrlr_state(fbi, C_ENABLE_CLKCHANGE); + break; + } + return 0; +} + +static int +sa1100fb_freq_policy(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct sa1100fb_info *fbi = TO_INF(nb, freq_policy); + struct cpufreq_policy *policy = data; + + switch (val) { + case CPUFREQ_ADJUST: + case CPUFREQ_INCOMPATIBLE: + dev_dbg(fbi->dev, "min dma period: %d ps, " + "new clock %d kHz\n", sa1100fb_min_dma_period(fbi), + policy->max); + /* todo: fill in min/max values */ + break; + case CPUFREQ_NOTIFY: + do {} while(0); + /* todo: panic if min/max values aren't fulfilled + * [can't really happen unless there's a bug in the + * CPU policy verififcation process * + */ + break; + } + return 0; +} +#endif + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ +static int sa1100fb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct sa1100fb_info *fbi = platform_get_drvdata(dev); + + set_ctrlr_state(fbi, C_DISABLE_PM); + return 0; +} + +static int sa1100fb_resume(struct platform_device *dev) +{ + struct sa1100fb_info *fbi = platform_get_drvdata(dev); + + set_ctrlr_state(fbi, C_ENABLE_PM); + return 0; +} +#else +#define sa1100fb_suspend NULL +#define sa1100fb_resume NULL +#endif + +/* + * sa1100fb_map_video_memory(): + * Allocates the DRAM memory for the frame buffer. This buffer is + * remapped into a non-cached, non-buffered, memory region to + * allow palette and pixel writes to occur without flushing the + * cache. Once this area is remapped, all virtual memory + * access to the video memory should occur at the new region. + */ +static int sa1100fb_map_video_memory(struct sa1100fb_info *fbi) +{ + /* + * We reserve one page for the palette, plus the size + * of the framebuffer. + */ + fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE); + fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size, + &fbi->map_dma, GFP_KERNEL); + + if (fbi->map_cpu) { + fbi->fb.screen_base = fbi->map_cpu + PAGE_SIZE; + fbi->screen_dma = fbi->map_dma + PAGE_SIZE; + /* + * FIXME: this is actually the wrong thing to place in + * smem_start. But fbdev suffers from the problem that + * it needs an API which doesn't exist (in this case, + * dma_writecombine_mmap) + */ + fbi->fb.fix.smem_start = fbi->screen_dma; + } + + return fbi->map_cpu ? 0 : -ENOMEM; +} + +/* Fake monspecs to fill in fbinfo structure */ +static struct fb_monspecs monspecs = { + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 50, + .vfmax = 65, +}; + + +static struct sa1100fb_info *sa1100fb_init_fbinfo(struct device *dev) +{ + struct sa1100fb_mach_info *inf = dev_get_platdata(dev); + struct sa1100fb_info *fbi; + unsigned i; + + fbi = kmalloc(sizeof(struct sa1100fb_info) + sizeof(u32) * 16, + GFP_KERNEL); + if (!fbi) + return NULL; + + memset(fbi, 0, sizeof(struct sa1100fb_info)); + fbi->dev = dev; + + strcpy(fbi->fb.fix.id, SA1100_NAME); + + fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fbi->fb.fix.type_aux = 0; + fbi->fb.fix.xpanstep = 0; + fbi->fb.fix.ypanstep = 0; + fbi->fb.fix.ywrapstep = 0; + fbi->fb.fix.accel = FB_ACCEL_NONE; + + fbi->fb.var.nonstd = 0; + fbi->fb.var.activate = FB_ACTIVATE_NOW; + fbi->fb.var.height = -1; + fbi->fb.var.width = -1; + fbi->fb.var.accel_flags = 0; + fbi->fb.var.vmode = FB_VMODE_NONINTERLACED; + + fbi->fb.fbops = &sa1100fb_ops; + fbi->fb.flags = FBINFO_DEFAULT; + fbi->fb.monspecs = monspecs; + fbi->fb.pseudo_palette = (fbi + 1); + + fbi->rgb[RGB_4] = &rgb_4; + fbi->rgb[RGB_8] = &rgb_8; + fbi->rgb[RGB_16] = &def_rgb_16; + + /* + * People just don't seem to get this. We don't support + * anything but correct entries now, so panic if someone + * does something stupid. + */ + if (inf->lccr3 & (LCCR3_VrtSnchL|LCCR3_HorSnchL|0xff) || + inf->pixclock == 0) + panic("sa1100fb error: invalid LCCR3 fields set or zero " + "pixclock."); + + fbi->fb.var.xres = inf->xres; + fbi->fb.var.xres_virtual = inf->xres; + fbi->fb.var.yres = inf->yres; + fbi->fb.var.yres_virtual = inf->yres; + fbi->fb.var.bits_per_pixel = inf->bpp; + fbi->fb.var.pixclock = inf->pixclock; + fbi->fb.var.hsync_len = inf->hsync_len; + fbi->fb.var.left_margin = inf->left_margin; + fbi->fb.var.right_margin = inf->right_margin; + fbi->fb.var.vsync_len = inf->vsync_len; + fbi->fb.var.upper_margin = inf->upper_margin; + fbi->fb.var.lower_margin = inf->lower_margin; + fbi->fb.var.sync = inf->sync; + fbi->fb.var.grayscale = inf->cmap_greyscale; + fbi->state = C_STARTUP; + fbi->task_state = (u_char)-1; + fbi->fb.fix.smem_len = inf->xres * inf->yres * + inf->bpp / 8; + fbi->inf = inf; + + /* Copy the RGB bitfield overrides */ + for (i = 0; i < NR_RGB; i++) + if (inf->rgb[i]) + fbi->rgb[i] = inf->rgb[i]; + + init_waitqueue_head(&fbi->ctrlr_wait); + INIT_WORK(&fbi->task, sa1100fb_task); + mutex_init(&fbi->ctrlr_lock); + + return fbi; +} + +static int sa1100fb_probe(struct platform_device *pdev) +{ + struct sa1100fb_info *fbi; + struct resource *res; + int ret, irq; + + if (!dev_get_platdata(&pdev->dev)) { + dev_err(&pdev->dev, "no platform LCD data\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (irq < 0 || !res) + return -EINVAL; + + if (!request_mem_region(res->start, resource_size(res), "LCD")) + return -EBUSY; + + fbi = sa1100fb_init_fbinfo(&pdev->dev); + ret = -ENOMEM; + if (!fbi) + goto failed; + + fbi->base = ioremap(res->start, resource_size(res)); + if (!fbi->base) + goto failed; + + /* Initialize video memory */ + ret = sa1100fb_map_video_memory(fbi); + if (ret) + goto failed; + + ret = request_irq(irq, sa1100fb_handle_irq, 0, "LCD", fbi); + if (ret) { + dev_err(&pdev->dev, "request_irq failed: %d\n", ret); + goto failed; + } + + if (machine_is_shannon()) { + ret = gpio_request_one(SHANNON_GPIO_DISP_EN, + GPIOF_OUT_INIT_LOW, "display enable"); + if (ret) + goto err_free_irq; + } + + /* + * This makes sure that our colour bitfield + * descriptors are correctly initialised. + */ + sa1100fb_check_var(&fbi->fb.var, &fbi->fb); + + platform_set_drvdata(pdev, fbi); + + ret = register_framebuffer(&fbi->fb); + if (ret < 0) + goto err_reg_fb; + +#ifdef CONFIG_CPU_FREQ + fbi->freq_transition.notifier_call = sa1100fb_freq_transition; + fbi->freq_policy.notifier_call = sa1100fb_freq_policy; + cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); + cpufreq_register_notifier(&fbi->freq_policy, CPUFREQ_POLICY_NOTIFIER); +#endif + + /* This driver cannot be unloaded at the moment */ + return 0; + + err_reg_fb: + if (machine_is_shannon()) + gpio_free(SHANNON_GPIO_DISP_EN); + err_free_irq: + free_irq(irq, fbi); + failed: + if (fbi) + iounmap(fbi->base); + kfree(fbi); + release_mem_region(res->start, resource_size(res)); + return ret; +} + +static struct platform_driver sa1100fb_driver = { + .probe = sa1100fb_probe, + .suspend = sa1100fb_suspend, + .resume = sa1100fb_resume, + .driver = { + .name = "sa11x0-fb", + .owner = THIS_MODULE, + }, +}; + +int __init sa1100fb_init(void) +{ + if (fb_get_options("sa1100fb", NULL)) + return -ENODEV; + + return platform_driver_register(&sa1100fb_driver); +} + +int __init sa1100fb_setup(char *options) +{ +#if 0 + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + + if (!strncmp(this_opt, "bpp:", 4)) + current_par.max_bpp = + simple_strtoul(this_opt + 4, NULL, 0); + + if (!strncmp(this_opt, "lccr0:", 6)) + lcd_shadow.lccr0 = + simple_strtoul(this_opt + 6, NULL, 0); + if (!strncmp(this_opt, "lccr1:", 6)) { + lcd_shadow.lccr1 = + simple_strtoul(this_opt + 6, NULL, 0); + current_par.max_xres = + (lcd_shadow.lccr1 & 0x3ff) + 16; + } + if (!strncmp(this_opt, "lccr2:", 6)) { + lcd_shadow.lccr2 = + simple_strtoul(this_opt + 6, NULL, 0); + current_par.max_yres = + (lcd_shadow. + lccr0 & LCCR0_SDS) ? ((lcd_shadow. + lccr2 & 0x3ff) + + 1) * + 2 : ((lcd_shadow.lccr2 & 0x3ff) + 1); + } + if (!strncmp(this_opt, "lccr3:", 6)) + lcd_shadow.lccr3 = + simple_strtoul(this_opt + 6, NULL, 0); + } +#endif + return 0; +} + +module_init(sa1100fb_init); +MODULE_DESCRIPTION("StrongARM-1100/1110 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/sa1100fb.h b/drivers/video/fbdev/sa1100fb.h new file mode 100644 index 000000000000..fc5d4292fad6 --- /dev/null +++ b/drivers/video/fbdev/sa1100fb.h @@ -0,0 +1,96 @@ +/* + * linux/drivers/video/sa1100fb.h + * -- StrongARM 1100 LCD Controller Frame Buffer Device + * + * Copyright (C) 1999 Eric A. Thomas + * Based on acornfb.c Copyright (C) Russell King. + * + * 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. + */ + +#define LCCR0 0x0000 /* LCD Control Reg. 0 */ +#define LCSR 0x0004 /* LCD Status Reg. */ +#define DBAR1 0x0010 /* LCD DMA Base Address Reg. channel 1 */ +#define DCAR1 0x0014 /* LCD DMA Current Address Reg. channel 1 */ +#define DBAR2 0x0018 /* LCD DMA Base Address Reg. channel 2 */ +#define DCAR2 0x001C /* LCD DMA Current Address Reg. channel 2 */ +#define LCCR1 0x0020 /* LCD Control Reg. 1 */ +#define LCCR2 0x0024 /* LCD Control Reg. 2 */ +#define LCCR3 0x0028 /* LCD Control Reg. 3 */ + +/* Shadows for LCD controller registers */ +struct sa1100fb_lcd_reg { + unsigned long lccr0; + unsigned long lccr1; + unsigned long lccr2; + unsigned long lccr3; +}; + +struct sa1100fb_info { + struct fb_info fb; + struct device *dev; + const struct sa1100fb_rgb *rgb[NR_RGB]; + void __iomem *base; + + /* + * These are the addresses we mapped + * the framebuffer memory region to. + */ + dma_addr_t map_dma; + u_char * map_cpu; + u_int map_size; + + u_char * screen_cpu; + dma_addr_t screen_dma; + u16 * palette_cpu; + dma_addr_t palette_dma; + u_int palette_size; + + dma_addr_t dbar1; + dma_addr_t dbar2; + + u_int reg_lccr0; + u_int reg_lccr1; + u_int reg_lccr2; + u_int reg_lccr3; + + volatile u_char state; + volatile u_char task_state; + struct mutex ctrlr_lock; + wait_queue_head_t ctrlr_wait; + struct work_struct task; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; + struct notifier_block freq_policy; +#endif + + const struct sa1100fb_mach_info *inf; +}; + +#define TO_INF(ptr,member) container_of(ptr,struct sa1100fb_info,member) + +#define SA1100_PALETTE_MODE_VAL(bpp) (((bpp) & 0x018) << 9) + +/* + * These are the actions for set_ctrlr_state + */ +#define C_DISABLE (0) +#define C_ENABLE (1) +#define C_DISABLE_CLKCHANGE (2) +#define C_ENABLE_CLKCHANGE (3) +#define C_REENABLE (4) +#define C_DISABLE_PM (5) +#define C_ENABLE_PM (6) +#define C_STARTUP (7) + +#define SA1100_NAME "SA1100" + +/* + * Minimum X and Y resolutions + */ +#define MIN_XRES 64 +#define MIN_YRES 64 + diff --git a/drivers/video/fbdev/savage/Makefile b/drivers/video/fbdev/savage/Makefile new file mode 100644 index 000000000000..e09770fff8ea --- /dev/null +++ b/drivers/video/fbdev/savage/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the S3 Savage framebuffer driver +# + +obj-$(CONFIG_FB_SAVAGE) += savagefb.o + +savagefb-y += savagefb_driver.o +savagefb-$(CONFIG_FB_SAVAGE_I2C) += savagefb-i2c.o +savagefb-$(CONFIG_FB_SAVAGE_ACCEL) += savagefb_accel.o diff --git a/drivers/video/fbdev/savage/savagefb-i2c.c b/drivers/video/fbdev/savage/savagefb-i2c.c new file mode 100644 index 000000000000..80fa87e2ae2f --- /dev/null +++ b/drivers/video/fbdev/savage/savagefb-i2c.c @@ -0,0 +1,241 @@ +/* + * linux/drivers/video/savage/savagefb-i2c.c - S3 Savage DDC2 + * + * Copyright 2004 Antonino A. Daplas <adaplas @pol.net> + * + * Based partly on rivafb-i2c.c + * + * 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/delay.h> +#include <linux/gfp.h> +#include <linux/pci.h> +#include <linux/fb.h> + +#include <asm/io.h> +#include "savagefb.h" + +#define SAVAGE_DDC 0x50 + +#define VGA_CR_IX 0x3d4 +#define VGA_CR_DATA 0x3d5 + +#define CR_SERIAL1 0xa0 /* I2C serial communications interface */ +#define MM_SERIAL1 0xff20 +#define CR_SERIAL2 0xb1 /* DDC2 monitor communications interface */ + +/* based on vt8365 documentation */ +#define PROSAVAGE_I2C_ENAB 0x10 +#define PROSAVAGE_I2C_SCL_OUT 0x01 +#define PROSAVAGE_I2C_SDA_OUT 0x02 +#define PROSAVAGE_I2C_SCL_IN 0x04 +#define PROSAVAGE_I2C_SDA_IN 0x08 + +#define SAVAGE4_I2C_ENAB 0x00000020 +#define SAVAGE4_I2C_SCL_OUT 0x00000001 +#define SAVAGE4_I2C_SDA_OUT 0x00000002 +#define SAVAGE4_I2C_SCL_IN 0x00000008 +#define SAVAGE4_I2C_SDA_IN 0x00000010 + +static void savage4_gpio_setscl(void *data, int val) +{ + struct savagefb_i2c_chan *chan = data; + unsigned int r; + + r = readl(chan->ioaddr + chan->reg); + if(val) + r |= SAVAGE4_I2C_SCL_OUT; + else + r &= ~SAVAGE4_I2C_SCL_OUT; + writel(r, chan->ioaddr + chan->reg); + readl(chan->ioaddr + chan->reg); /* flush posted write */ +} + +static void savage4_gpio_setsda(void *data, int val) +{ + struct savagefb_i2c_chan *chan = data; + + unsigned int r; + r = readl(chan->ioaddr + chan->reg); + if(val) + r |= SAVAGE4_I2C_SDA_OUT; + else + r &= ~SAVAGE4_I2C_SDA_OUT; + writel(r, chan->ioaddr + chan->reg); + readl(chan->ioaddr + chan->reg); /* flush posted write */ +} + +static int savage4_gpio_getscl(void *data) +{ + struct savagefb_i2c_chan *chan = data; + + return (0 != (readl(chan->ioaddr + chan->reg) & SAVAGE4_I2C_SCL_IN)); +} + +static int savage4_gpio_getsda(void *data) +{ + struct savagefb_i2c_chan *chan = data; + + return (0 != (readl(chan->ioaddr + chan->reg) & SAVAGE4_I2C_SDA_IN)); +} + +static void prosavage_gpio_setscl(void* data, int val) +{ + struct savagefb_i2c_chan *chan = data; + u32 r; + + r = VGArCR(chan->reg, chan->par); + r |= PROSAVAGE_I2C_ENAB; + if (val) { + r |= PROSAVAGE_I2C_SCL_OUT; + } else { + r &= ~PROSAVAGE_I2C_SCL_OUT; + } + + VGAwCR(chan->reg, r, chan->par); +} + +static void prosavage_gpio_setsda(void* data, int val) +{ + struct savagefb_i2c_chan *chan = data; + unsigned int r; + + r = VGArCR(chan->reg, chan->par); + r |= PROSAVAGE_I2C_ENAB; + if (val) { + r |= PROSAVAGE_I2C_SDA_OUT; + } else { + r &= ~PROSAVAGE_I2C_SDA_OUT; + } + + VGAwCR(chan->reg, r, chan->par); +} + +static int prosavage_gpio_getscl(void* data) +{ + struct savagefb_i2c_chan *chan = data; + + return (VGArCR(chan->reg, chan->par) & PROSAVAGE_I2C_SCL_IN) ? 1 : 0; +} + +static int prosavage_gpio_getsda(void* data) +{ + struct savagefb_i2c_chan *chan = data; + + return (VGArCR(chan->reg, chan->par) & PROSAVAGE_I2C_SDA_IN) ? 1 : 0; +} + +static int savage_setup_i2c_bus(struct savagefb_i2c_chan *chan, + const char *name) +{ + int rc = 0; + + if (chan->par) { + strcpy(chan->adapter.name, name); + chan->adapter.owner = THIS_MODULE; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = &chan->par->pcidev->dev; + chan->algo.udelay = 10; + chan->algo.timeout = 20; + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + /* Raise SCL and SDA */ + chan->algo.setsda(chan, 1); + chan->algo.setscl(chan, 1); + udelay(20); + + rc = i2c_bit_add_bus(&chan->adapter); + + if (rc == 0) + dev_dbg(&chan->par->pcidev->dev, + "I2C bus %s registered.\n", name); + else + dev_warn(&chan->par->pcidev->dev, + "Failed to register I2C bus %s.\n", name); + } + + return rc; +} + +void savagefb_create_i2c_busses(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + par->chan.par = par; + + switch (par->chip) { + case S3_PROSAVAGE: + case S3_PROSAVAGEDDR: + case S3_TWISTER: + par->chan.reg = CR_SERIAL2; + par->chan.ioaddr = par->mmio.vbase; + par->chan.algo.setsda = prosavage_gpio_setsda; + par->chan.algo.setscl = prosavage_gpio_setscl; + par->chan.algo.getsda = prosavage_gpio_getsda; + par->chan.algo.getscl = prosavage_gpio_getscl; + break; + case S3_SAVAGE4: + par->chan.reg = CR_SERIAL1; + if (par->pcidev->revision > 1 && !(VGArCR(0xa6, par) & 0x40)) + par->chan.reg = CR_SERIAL2; + par->chan.ioaddr = par->mmio.vbase; + par->chan.algo.setsda = prosavage_gpio_setsda; + par->chan.algo.setscl = prosavage_gpio_setscl; + par->chan.algo.getsda = prosavage_gpio_getsda; + par->chan.algo.getscl = prosavage_gpio_getscl; + break; + case S3_SAVAGE2000: + par->chan.reg = MM_SERIAL1; + par->chan.ioaddr = par->mmio.vbase; + par->chan.algo.setsda = savage4_gpio_setsda; + par->chan.algo.setscl = savage4_gpio_setscl; + par->chan.algo.getsda = savage4_gpio_getsda; + par->chan.algo.getscl = savage4_gpio_getscl; + break; + default: + par->chan.par = NULL; + } + + savage_setup_i2c_bus(&par->chan, "SAVAGE DDC2"); +} + +void savagefb_delete_i2c_busses(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + + if (par->chan.par) + i2c_del_adapter(&par->chan.adapter); + + par->chan.par = NULL; +} + +int savagefb_probe_i2c_connector(struct fb_info *info, u8 **out_edid) +{ + struct savagefb_par *par = info->par; + u8 *edid; + + if (par->chan.par) + edid = fb_ddc_read(&par->chan.adapter); + else + edid = NULL; + + if (!edid) { + /* try to get from firmware */ + const u8 *e = fb_firmware_edid(info->device); + + if (e) + edid = kmemdup(e, EDID_LENGTH, GFP_KERNEL); + } + + *out_edid = edid; + + return (edid) ? 0 : 1; +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/savage/savagefb.h b/drivers/video/fbdev/savage/savagefb.h new file mode 100644 index 000000000000..dcaab9012ca2 --- /dev/null +++ b/drivers/video/fbdev/savage/savagefb.h @@ -0,0 +1,415 @@ +/* + * linux/drivers/video/savagefb.h -- S3 Savage Framebuffer Driver + * + * Copyright (c) 2001 Denis Oliver Kropp <dok@convergence.de> + * + * 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. + */ + + +#ifndef __SAVAGEFB_H__ +#define __SAVAGEFB_H__ + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/mutex.h> +#include <video/vga.h> +#include "../edid.h" + +#ifdef SAVAGEFB_DEBUG +# define DBG(x) printk (KERN_DEBUG "savagefb: %s\n", (x)); +#else +# define DBG(x) +# define SavagePrintRegs(...) +#endif + + +#define PCI_CHIP_SAVAGE4 0x8a22 +#define PCI_CHIP_SAVAGE3D 0x8a20 +#define PCI_CHIP_SAVAGE3D_MV 0x8a21 +#define PCI_CHIP_SAVAGE2000 0x9102 +#define PCI_CHIP_SAVAGE_MX_MV 0x8c10 +#define PCI_CHIP_SAVAGE_MX 0x8c11 +#define PCI_CHIP_SAVAGE_IX_MV 0x8c12 +#define PCI_CHIP_SAVAGE_IX 0x8c13 +#define PCI_CHIP_PROSAVAGE_PM 0x8a25 +#define PCI_CHIP_PROSAVAGE_KM 0x8a26 +#define PCI_CHIP_S3TWISTER_P 0x8d01 +#define PCI_CHIP_S3TWISTER_K 0x8d02 +#define PCI_CHIP_PROSAVAGE_DDR 0x8d03 +#define PCI_CHIP_PROSAVAGE_DDRK 0x8d04 +#define PCI_CHIP_SUPSAV_MX128 0x8c22 +#define PCI_CHIP_SUPSAV_MX64 0x8c24 +#define PCI_CHIP_SUPSAV_MX64C 0x8c26 +#define PCI_CHIP_SUPSAV_IX128SDR 0x8c2a +#define PCI_CHIP_SUPSAV_IX128DDR 0x8c2b +#define PCI_CHIP_SUPSAV_IX64SDR 0x8c2c +#define PCI_CHIP_SUPSAV_IX64DDR 0x8c2d +#define PCI_CHIP_SUPSAV_IXCSDR 0x8c2e +#define PCI_CHIP_SUPSAV_IXCDDR 0x8c2f + + +#define S3_SAVAGE_SERIES(chip) ((chip>=S3_SAVAGE3D) && (chip<=S3_SAVAGE2000)) + +#define S3_SAVAGE3D_SERIES(chip) ((chip>=S3_SAVAGE3D) && (chip<=S3_SAVAGE_MX)) + +#define S3_SAVAGE4_SERIES(chip) ((chip>=S3_SAVAGE4) && (chip<=S3_PROSAVAGEDDR)) + +#define S3_SAVAGE_MOBILE_SERIES(chip) ((chip==S3_SAVAGE_MX) || (chip==S3_SUPERSAVAGE)) + +#define S3_MOBILE_TWISTER_SERIES(chip) ((chip==S3_TWISTER) || (chip==S3_PROSAVAGEDDR)) + +/* Chip tags. These are used to group the adapters into + * related families. + */ + +typedef enum { + S3_UNKNOWN = 0, + S3_SAVAGE3D, + S3_SAVAGE_MX, + S3_SAVAGE4, + S3_PROSAVAGE, + S3_TWISTER, + S3_PROSAVAGEDDR, + S3_SUPERSAVAGE, + S3_SAVAGE2000, + S3_LAST +} savage_chipset; + +#define BIOS_BSIZE 1024 +#define BIOS_BASE 0xc0000 + +#define SAVAGE_NEWMMIO_REGBASE_S3 0x1000000 /* 16MB */ +#define SAVAGE_NEWMMIO_REGBASE_S4 0x0000000 +#define SAVAGE_NEWMMIO_REGSIZE 0x0080000 /* 512kb */ +#define SAVAGE_NEWMMIO_VGABASE 0x8000 + +#define BASE_FREQ 14318 +#define HALF_BASE_FREQ 7159 + +#define FIFO_CONTROL_REG 0x8200 +#define MIU_CONTROL_REG 0x8204 +#define STREAMS_TIMEOUT_REG 0x8208 +#define MISC_TIMEOUT_REG 0x820c + +#define MONO_PAT_0 0xa4e8 +#define MONO_PAT_1 0xa4ec + +#define MAXFIFO 0x7f00 + +#define BCI_CMD_NOP 0x40000000 +#define BCI_CMD_SETREG 0x96000000 +#define BCI_CMD_RECT 0x48000000 +#define BCI_CMD_RECT_XP 0x01000000 +#define BCI_CMD_RECT_YP 0x02000000 +#define BCI_CMD_SEND_COLOR 0x00008000 +#define BCI_CMD_DEST_GBD 0x00000000 +#define BCI_CMD_SRC_GBD 0x00000020 +#define BCI_CMD_SRC_SOLID 0x00000000 +#define BCI_CMD_SRC_MONO 0x00000060 +#define BCI_CMD_CLIP_NEW 0x00006000 +#define BCI_CMD_CLIP_LR 0x00004000 + +#define BCI_CLIP_LR(l, r) ((((r) << 16) | (l)) & 0x0FFF0FFF) +#define BCI_CLIP_TL(t, l) ((((t) << 16) | (l)) & 0x0FFF0FFF) +#define BCI_CLIP_BR(b, r) ((((b) << 16) | (r)) & 0x0FFF0FFF) +#define BCI_W_H(w, h) (((h) << 16) | ((w) & 0xFFF)) +#define BCI_X_Y(x, y) (((y) << 16) | ((x) & 0xFFF)) + +#define BCI_GBD1 0xE0 +#define BCI_GBD2 0xE1 + +#define BCI_BUFFER_OFFSET 0x10000 +#define BCI_SIZE 0x4000 + +#define BCI_SEND(dw) writel(dw, par->bci_base + par->bci_ptr++) + +#define BCI_CMD_GET_ROP(cmd) (((cmd) >> 16) & 0xFF) +#define BCI_CMD_SET_ROP(cmd, rop) ((cmd) |= ((rop & 0xFF) << 16)) +#define BCI_CMD_SEND_COLOR 0x00008000 + +#define DISP_CRT 1 +#define DISP_LCD 2 +#define DISP_DFP 3 + +struct xtimings { + unsigned int Clock; + unsigned int HDisplay; + unsigned int HSyncStart; + unsigned int HSyncEnd; + unsigned int HTotal; + unsigned int HAdjusted; + unsigned int VDisplay; + unsigned int VSyncStart; + unsigned int VSyncEnd; + unsigned int VTotal; + unsigned int sync; + int dblscan; + int interlaced; +}; + +struct savage_reg { + unsigned char MiscOutReg; /* Misc */ + unsigned char CRTC[25]; /* Crtc Controller */ + unsigned char Sequencer[5]; /* Video Sequencer */ + unsigned char Graphics[9]; /* Video Graphics */ + unsigned char Attribute[21]; /* Video Attribute */ + + unsigned int mode, refresh; + unsigned char SR08, SR0E, SR0F; + unsigned char SR10, SR11, SR12, SR13, SR15, SR18, SR29, SR30; + unsigned char SR54[8]; + unsigned char Clock; + unsigned char CR31, CR32, CR33, CR34, CR36, CR3A, CR3B, CR3C; + unsigned char CR40, CR41, CR42, CR43, CR45; + unsigned char CR50, CR51, CR53, CR55, CR58, CR5B, CR5D, CR5E; + unsigned char CR60, CR63, CR65, CR66, CR67, CR68, CR69, CR6D, CR6F; + unsigned char CR86, CR88; + unsigned char CR90, CR91, CRB0; + unsigned int STREAMS[22]; /* yuck, streams regs */ + unsigned int MMPR0, MMPR1, MMPR2, MMPR3; +}; +/* --------------------------------------------------------------------- */ + +#define NR_PALETTE 256 + + +struct savagefb_par; + +struct savagefb_i2c_chan { + struct savagefb_par *par; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo; + volatile u8 __iomem *ioaddr; + u32 reg; +}; + +struct savagefb_par { + struct pci_dev *pcidev; + savage_chipset chip; + struct savagefb_i2c_chan chan; + struct savage_reg state; + struct savage_reg save; + struct savage_reg initial; + struct vgastate vgastate; + struct mutex open_lock; + unsigned char *edid; + u32 pseudo_palette[16]; + u32 open_count; + int paletteEnabled; + int pm_state; + int display_type; + int dvi; + int crtonly; + int dacSpeedBpp; + int maxClock; + int minClock; + int numClocks; + int clock[4]; + int MCLK, REFCLK, LCDclk; + struct { + void __iomem *vbase; + u32 pbase; + u32 len; +#ifdef CONFIG_MTRR + int mtrr; +#endif + } video; + + struct { + void __iomem *vbase; + u32 pbase; + u32 len; + } mmio; + + volatile u32 __iomem *bci_base; + unsigned int bci_ptr; + u32 cob_offset; + u32 cob_size; + int cob_index; + + void (*SavageWaitIdle) (struct savagefb_par *par); + void (*SavageWaitFifo) (struct savagefb_par *par, int space); + + int HorizScaleFactor; + + /* Panels size */ + int SavagePanelWidth; + int SavagePanelHeight; + + struct { + u16 red, green, blue, transp; + } palette[NR_PALETTE]; + + int depth; + int vwidth; +}; + +#define BCI_BD_BW_DISABLE 0x10000000 +#define BCI_BD_SET_BPP(bd, bpp) ((bd) |= (((bpp) & 0xFF) << 16)) +#define BCI_BD_SET_STRIDE(bd, st) ((bd) |= ((st) & 0xFFFF)) + + +/* IO functions */ +static inline u8 savage_in8(u32 addr, struct savagefb_par *par) +{ + return readb(par->mmio.vbase + addr); +} + +static inline u16 savage_in16(u32 addr, struct savagefb_par *par) +{ + return readw(par->mmio.vbase + addr); +} + +static inline u32 savage_in32(u32 addr, struct savagefb_par *par) +{ + return readl(par->mmio.vbase + addr); +} + +static inline void savage_out8(u32 addr, u8 val, struct savagefb_par *par) +{ + writeb(val, par->mmio.vbase + addr); +} + +static inline void savage_out16(u32 addr, u16 val, struct savagefb_par *par) +{ + writew(val, par->mmio.vbase + addr); +} + +static inline void savage_out32(u32 addr, u32 val, struct savagefb_par *par) +{ + writel(val, par->mmio.vbase + addr); +} + +static inline u8 vga_in8(int addr, struct savagefb_par *par) +{ + return savage_in8(0x8000 + addr, par); +} + +static inline u16 vga_in16(int addr, struct savagefb_par *par) +{ + return savage_in16(0x8000 + addr, par); +} + +static inline u8 vga_in32(int addr, struct savagefb_par *par) +{ + return savage_in32(0x8000 + addr, par); +} + +static inline void vga_out8(int addr, u8 val, struct savagefb_par *par) +{ + savage_out8(0x8000 + addr, val, par); +} + +static inline void vga_out16(int addr, u16 val, struct savagefb_par *par) +{ + savage_out16(0x8000 + addr, val, par); +} + +static inline void vga_out32(int addr, u32 val, struct savagefb_par *par) +{ + savage_out32(0x8000 + addr, val, par); +} + +static inline u8 VGArCR (u8 index, struct savagefb_par *par) +{ + vga_out8(0x3d4, index, par); + return vga_in8(0x3d5, par); +} + +static inline u8 VGArGR (u8 index, struct savagefb_par *par) +{ + vga_out8(0x3ce, index, par); + return vga_in8(0x3cf, par); +} + +static inline u8 VGArSEQ (u8 index, struct savagefb_par *par) +{ + vga_out8(0x3c4, index, par); + return vga_in8(0x3c5, par); +} + +static inline void VGAwCR(u8 index, u8 val, struct savagefb_par *par) +{ + vga_out8(0x3d4, index, par); + vga_out8(0x3d5, val, par); +} + +static inline void VGAwGR(u8 index, u8 val, struct savagefb_par *par) +{ + vga_out8(0x3ce, index, par); + vga_out8(0x3cf, val, par); +} + +static inline void VGAwSEQ(u8 index, u8 val, struct savagefb_par *par) +{ + vga_out8(0x3c4, index, par); + vga_out8 (0x3c5, val, par); +} + +static inline void VGAenablePalette(struct savagefb_par *par) +{ + u8 tmp; + + tmp = vga_in8(0x3da, par); + vga_out8(0x3c0, 0x00, par); + par->paletteEnabled = 1; +} + +static inline void VGAdisablePalette(struct savagefb_par *par) +{ + u8 tmp; + + tmp = vga_in8(0x3da, par); + vga_out8(0x3c0, 0x20, par); + par->paletteEnabled = 0; +} + +static inline void VGAwATTR(u8 index, u8 value, struct savagefb_par *par) +{ + u8 tmp; + + if (par->paletteEnabled) + index &= ~0x20; + else + index |= 0x20; + + tmp = vga_in8(0x3da, par); + vga_out8(0x3c0, index, par); + vga_out8 (0x3c0, value, par); +} + +static inline void VGAwMISC(u8 value, struct savagefb_par *par) +{ + vga_out8(0x3c2, value, par); +} + +#ifndef CONFIG_FB_SAVAGE_ACCEL +#define savagefb_set_clip(x) +#endif + +static inline void VerticalRetraceWait(struct savagefb_par *par) +{ + vga_out8(0x3d4, 0x17, par); + if (vga_in8(0x3d5, par) & 0x80) { + while ((vga_in8(0x3da, par) & 0x08) == 0x08); + while ((vga_in8(0x3da, par) & 0x08) == 0x00); + } +} + +extern int savagefb_probe_i2c_connector(struct fb_info *info, + u8 **out_edid); +extern void savagefb_create_i2c_busses(struct fb_info *info); +extern void savagefb_delete_i2c_busses(struct fb_info *info); +extern int savagefb_sync(struct fb_info *info); +extern void savagefb_copyarea(struct fb_info *info, + const struct fb_copyarea *region); +extern void savagefb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect); +extern void savagefb_imageblit(struct fb_info *info, + const struct fb_image *image); + + +#endif /* __SAVAGEFB_H__ */ diff --git a/drivers/video/fbdev/savage/savagefb_accel.c b/drivers/video/fbdev/savage/savagefb_accel.c new file mode 100644 index 000000000000..bfefa6234cf0 --- /dev/null +++ b/drivers/video/fbdev/savage/savagefb_accel.c @@ -0,0 +1,137 @@ +/*-*- linux-c -*- + * linux/drivers/video/savage/savage_accel.c -- Hardware Acceleration + * + * Copyright (C) 2004 Antonino Daplas<adaplas@pol.net> + * All Rights Reserved + * + * 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/kernel.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/module.h> + +#include "savagefb.h" + +static u32 savagefb_rop[] = { + 0xCC, /* ROP_COPY */ + 0x5A /* ROP_XOR */ +}; + +int savagefb_sync(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + + par->SavageWaitIdle(par); + return 0; +} + +void savagefb_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct savagefb_par *par = info->par; + int sx = region->sx, dx = region->dx; + int sy = region->sy, dy = region->dy; + int cmd; + + if (!region->width || !region->height) + return; + par->bci_ptr = 0; + cmd = BCI_CMD_RECT | BCI_CMD_DEST_GBD | BCI_CMD_SRC_GBD; + BCI_CMD_SET_ROP(cmd, savagefb_rop[0]); + + if (dx <= sx) { + cmd |= BCI_CMD_RECT_XP; + } else { + sx += region->width - 1; + dx += region->width - 1; + } + + if (dy <= sy) { + cmd |= BCI_CMD_RECT_YP; + } else { + sy += region->height - 1; + dy += region->height - 1; + } + + par->SavageWaitFifo(par,4); + BCI_SEND(cmd); + BCI_SEND(BCI_X_Y(sx, sy)); + BCI_SEND(BCI_X_Y(dx, dy)); + BCI_SEND(BCI_W_H(region->width, region->height)); +} + +void savagefb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct savagefb_par *par = info->par; + int cmd, color; + + if (!rect->width || !rect->height) + return; + + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) + color = rect->color; + else + color = ((u32 *)info->pseudo_palette)[rect->color]; + + cmd = BCI_CMD_RECT | BCI_CMD_RECT_XP | BCI_CMD_RECT_YP | + BCI_CMD_DEST_GBD | BCI_CMD_SRC_SOLID | + BCI_CMD_SEND_COLOR; + + par->bci_ptr = 0; + BCI_CMD_SET_ROP(cmd, savagefb_rop[rect->rop]); + + par->SavageWaitFifo(par,4); + BCI_SEND(cmd); + BCI_SEND(color); + BCI_SEND( BCI_X_Y(rect->dx, rect->dy) ); + BCI_SEND( BCI_W_H(rect->width, rect->height) ); +} + +void savagefb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct savagefb_par *par = info->par; + int fg, bg, size, i, width; + int cmd; + u32 *src = (u32 *) image->data; + + if (!image->width || !image->height) + return; + + if (image->depth != 1) { + cfb_imageblit(info, image); + return; + } + + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) { + fg = image->fg_color; + bg = image->bg_color; + } else { + fg = ((u32 *)info->pseudo_palette)[image->fg_color]; + bg = ((u32 *)info->pseudo_palette)[image->bg_color]; + } + + cmd = BCI_CMD_RECT | BCI_CMD_RECT_XP | BCI_CMD_RECT_YP | + BCI_CMD_CLIP_LR | BCI_CMD_DEST_GBD | BCI_CMD_SRC_MONO | + BCI_CMD_SEND_COLOR; + + par->bci_ptr = 0; + BCI_CMD_SET_ROP(cmd, savagefb_rop[0]); + + width = (image->width + 31) & ~31; + size = (width * image->height)/8; + size >>= 2; + + par->SavageWaitFifo(par, size + 5); + BCI_SEND(cmd); + BCI_SEND(BCI_CLIP_LR(image->dx, image->dx + image->width - 1)); + BCI_SEND(fg); + BCI_SEND(bg); + BCI_SEND(BCI_X_Y(image->dx, image->dy)); + BCI_SEND(BCI_W_H(width, image->height)); + for (i = 0; i < size; i++) + BCI_SEND(src[i]); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/savage/savagefb_driver.c b/drivers/video/fbdev/savage/savagefb_driver.c new file mode 100644 index 000000000000..4dbf45f3b21a --- /dev/null +++ b/drivers/video/fbdev/savage/savagefb_driver.c @@ -0,0 +1,2571 @@ +/* + * linux/drivers/video/savagefb.c -- S3 Savage Framebuffer Driver + * + * Copyright (c) 2001-2002 Denis Oliver Kropp <dok@directfb.org> + * Sven Neumann <neo@directfb.org> + * + * + * Card specific code is based on XFree86's savage driver. + * Framebuffer framework code is based on code of cyber2000fb and tdfxfb. + * + * 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. + * + * 0.4.0 (neo) + * - hardware accelerated clear and move + * + * 0.3.2 (dok) + * - wait for vertical retrace before writing to cr67 + * at the beginning of savagefb_set_par + * - use synchronization registers cr23 and cr26 + * + * 0.3.1 (dok) + * - reset 3D engine + * - don't return alpha bits for 32bit format + * + * 0.3.0 (dok) + * - added WaitIdle functions for all Savage types + * - do WaitIdle before mode switching + * - code cleanup + * + * 0.2.0 (dok) + * - first working version + * + * + * TODO + * - clock validations in decode_var + * + * BUGS + * - white margin on bootup + * + */ + +#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/pci.h> +#include <linux/init.h> +#include <linux/console.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/pgtable.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include "savagefb.h" + + +#define SAVAGEFB_VERSION "0.4.0_2.6" + +/* --------------------------------------------------------------------- */ + + +static char *mode_option = NULL; + +#ifdef MODULE + +MODULE_AUTHOR("(c) 2001-2002 Denis Oliver Kropp <dok@directfb.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("FBDev driver for S3 Savage PCI/AGP Chips"); + +#endif + + +/* --------------------------------------------------------------------- */ + +static void vgaHWSeqReset(struct savagefb_par *par, int start) +{ + if (start) + VGAwSEQ(0x00, 0x01, par); /* Synchronous Reset */ + else + VGAwSEQ(0x00, 0x03, par); /* End Reset */ +} + +static void vgaHWProtect(struct savagefb_par *par, int on) +{ + unsigned char tmp; + + if (on) { + /* + * Turn off screen and disable sequencer. + */ + tmp = VGArSEQ(0x01, par); + + vgaHWSeqReset(par, 1); /* start synchronous reset */ + VGAwSEQ(0x01, tmp | 0x20, par);/* disable the display */ + + VGAenablePalette(par); + } else { + /* + * Reenable sequencer, then turn on screen. + */ + + tmp = VGArSEQ(0x01, par); + + VGAwSEQ(0x01, tmp & ~0x20, par);/* reenable display */ + vgaHWSeqReset(par, 0); /* clear synchronous reset */ + + VGAdisablePalette(par); + } +} + +static void vgaHWRestore(struct savagefb_par *par, struct savage_reg *reg) +{ + int i; + + VGAwMISC(reg->MiscOutReg, par); + + for (i = 1; i < 5; i++) + VGAwSEQ(i, reg->Sequencer[i], par); + + /* Ensure CRTC registers 0-7 are unlocked by clearing bit 7 or + CRTC[17] */ + VGAwCR(17, reg->CRTC[17] & ~0x80, par); + + for (i = 0; i < 25; i++) + VGAwCR(i, reg->CRTC[i], par); + + for (i = 0; i < 9; i++) + VGAwGR(i, reg->Graphics[i], par); + + VGAenablePalette(par); + + for (i = 0; i < 21; i++) + VGAwATTR(i, reg->Attribute[i], par); + + VGAdisablePalette(par); +} + +static void vgaHWInit(struct fb_var_screeninfo *var, + struct savagefb_par *par, + struct xtimings *timings, + struct savage_reg *reg) +{ + reg->MiscOutReg = 0x23; + + if (!(timings->sync & FB_SYNC_HOR_HIGH_ACT)) + reg->MiscOutReg |= 0x40; + + if (!(timings->sync & FB_SYNC_VERT_HIGH_ACT)) + reg->MiscOutReg |= 0x80; + + /* + * Time Sequencer + */ + reg->Sequencer[0x00] = 0x00; + reg->Sequencer[0x01] = 0x01; + reg->Sequencer[0x02] = 0x0F; + reg->Sequencer[0x03] = 0x00; /* Font select */ + reg->Sequencer[0x04] = 0x0E; /* Misc */ + + /* + * CRTC Controller + */ + reg->CRTC[0x00] = (timings->HTotal >> 3) - 5; + reg->CRTC[0x01] = (timings->HDisplay >> 3) - 1; + reg->CRTC[0x02] = (timings->HSyncStart >> 3) - 1; + reg->CRTC[0x03] = (((timings->HSyncEnd >> 3) - 1) & 0x1f) | 0x80; + reg->CRTC[0x04] = (timings->HSyncStart >> 3); + reg->CRTC[0x05] = ((((timings->HSyncEnd >> 3) - 1) & 0x20) << 2) | + (((timings->HSyncEnd >> 3)) & 0x1f); + reg->CRTC[0x06] = (timings->VTotal - 2) & 0xFF; + reg->CRTC[0x07] = (((timings->VTotal - 2) & 0x100) >> 8) | + (((timings->VDisplay - 1) & 0x100) >> 7) | + ((timings->VSyncStart & 0x100) >> 6) | + (((timings->VSyncStart - 1) & 0x100) >> 5) | + 0x10 | + (((timings->VTotal - 2) & 0x200) >> 4) | + (((timings->VDisplay - 1) & 0x200) >> 3) | + ((timings->VSyncStart & 0x200) >> 2); + reg->CRTC[0x08] = 0x00; + reg->CRTC[0x09] = (((timings->VSyncStart - 1) & 0x200) >> 4) | 0x40; + + if (timings->dblscan) + reg->CRTC[0x09] |= 0x80; + + reg->CRTC[0x0a] = 0x00; + reg->CRTC[0x0b] = 0x00; + reg->CRTC[0x0c] = 0x00; + reg->CRTC[0x0d] = 0x00; + reg->CRTC[0x0e] = 0x00; + reg->CRTC[0x0f] = 0x00; + reg->CRTC[0x10] = timings->VSyncStart & 0xff; + reg->CRTC[0x11] = (timings->VSyncEnd & 0x0f) | 0x20; + reg->CRTC[0x12] = (timings->VDisplay - 1) & 0xff; + reg->CRTC[0x13] = var->xres_virtual >> 4; + reg->CRTC[0x14] = 0x00; + reg->CRTC[0x15] = (timings->VSyncStart - 1) & 0xff; + reg->CRTC[0x16] = (timings->VSyncEnd - 1) & 0xff; + reg->CRTC[0x17] = 0xc3; + reg->CRTC[0x18] = 0xff; + + /* + * are these unnecessary? + * vgaHWHBlankKGA(mode, regp, 0, KGA_FIX_OVERSCAN|KGA_ENABLE_ON_ZERO); + * vgaHWVBlankKGA(mode, regp, 0, KGA_FIX_OVERSCAN|KGA_ENABLE_ON_ZERO); + */ + + /* + * Graphics Display Controller + */ + reg->Graphics[0x00] = 0x00; + reg->Graphics[0x01] = 0x00; + reg->Graphics[0x02] = 0x00; + reg->Graphics[0x03] = 0x00; + reg->Graphics[0x04] = 0x00; + reg->Graphics[0x05] = 0x40; + reg->Graphics[0x06] = 0x05; /* only map 64k VGA memory !!!! */ + reg->Graphics[0x07] = 0x0F; + reg->Graphics[0x08] = 0xFF; + + + reg->Attribute[0x00] = 0x00; /* standard colormap translation */ + reg->Attribute[0x01] = 0x01; + reg->Attribute[0x02] = 0x02; + reg->Attribute[0x03] = 0x03; + reg->Attribute[0x04] = 0x04; + reg->Attribute[0x05] = 0x05; + reg->Attribute[0x06] = 0x06; + reg->Attribute[0x07] = 0x07; + reg->Attribute[0x08] = 0x08; + reg->Attribute[0x09] = 0x09; + reg->Attribute[0x0a] = 0x0A; + reg->Attribute[0x0b] = 0x0B; + reg->Attribute[0x0c] = 0x0C; + reg->Attribute[0x0d] = 0x0D; + reg->Attribute[0x0e] = 0x0E; + reg->Attribute[0x0f] = 0x0F; + reg->Attribute[0x10] = 0x41; + reg->Attribute[0x11] = 0xFF; + reg->Attribute[0x12] = 0x0F; + reg->Attribute[0x13] = 0x00; + reg->Attribute[0x14] = 0x00; +} + +/* -------------------- Hardware specific routines ------------------------- */ + +/* + * Hardware Acceleration for SavageFB + */ + +/* Wait for fifo space */ +static void +savage3D_waitfifo(struct savagefb_par *par, int space) +{ + int slots = MAXFIFO - space; + + while ((savage_in32(0x48C00, par) & 0x0000ffff) > slots); +} + +static void +savage4_waitfifo(struct savagefb_par *par, int space) +{ + int slots = MAXFIFO - space; + + while ((savage_in32(0x48C60, par) & 0x001fffff) > slots); +} + +static void +savage2000_waitfifo(struct savagefb_par *par, int space) +{ + int slots = MAXFIFO - space; + + while ((savage_in32(0x48C60, par) & 0x0000ffff) > slots); +} + +/* Wait for idle accelerator */ +static void +savage3D_waitidle(struct savagefb_par *par) +{ + while ((savage_in32(0x48C00, par) & 0x0008ffff) != 0x80000); +} + +static void +savage4_waitidle(struct savagefb_par *par) +{ + while ((savage_in32(0x48C60, par) & 0x00a00000) != 0x00a00000); +} + +static void +savage2000_waitidle(struct savagefb_par *par) +{ + while ((savage_in32(0x48C60, par) & 0x009fffff)); +} + +#ifdef CONFIG_FB_SAVAGE_ACCEL +static void +SavageSetup2DEngine(struct savagefb_par *par) +{ + unsigned long GlobalBitmapDescriptor; + + GlobalBitmapDescriptor = 1 | 8 | BCI_BD_BW_DISABLE; + BCI_BD_SET_BPP(GlobalBitmapDescriptor, par->depth); + BCI_BD_SET_STRIDE(GlobalBitmapDescriptor, par->vwidth); + + switch(par->chip) { + case S3_SAVAGE3D: + case S3_SAVAGE_MX: + /* Disable BCI */ + savage_out32(0x48C18, savage_in32(0x48C18, par) & 0x3FF0, par); + /* Setup BCI command overflow buffer */ + savage_out32(0x48C14, + (par->cob_offset >> 11) | (par->cob_index << 29), + par); + /* Program shadow status update. */ + savage_out32(0x48C10, 0x78207220, par); + savage_out32(0x48C0C, 0, par); + /* Enable BCI and command overflow buffer */ + savage_out32(0x48C18, savage_in32(0x48C18, par) | 0x0C, par); + break; + case S3_SAVAGE4: + case S3_TWISTER: + case S3_PROSAVAGE: + case S3_PROSAVAGEDDR: + case S3_SUPERSAVAGE: + /* Disable BCI */ + savage_out32(0x48C18, savage_in32(0x48C18, par) & 0x3FF0, par); + /* Program shadow status update */ + savage_out32(0x48C10, 0x00700040, par); + savage_out32(0x48C0C, 0, par); + /* Enable BCI without the COB */ + savage_out32(0x48C18, savage_in32(0x48C18, par) | 0x08, par); + break; + case S3_SAVAGE2000: + /* Disable BCI */ + savage_out32(0x48C18, 0, par); + /* Setup BCI command overflow buffer */ + savage_out32(0x48C18, + (par->cob_offset >> 7) | (par->cob_index), + par); + /* Disable shadow status update */ + savage_out32(0x48A30, 0, par); + /* Enable BCI and command overflow buffer */ + savage_out32(0x48C18, savage_in32(0x48C18, par) | 0x00280000, + par); + break; + default: + break; + } + /* Turn on 16-bit register access. */ + vga_out8(0x3d4, 0x31, par); + vga_out8(0x3d5, 0x0c, par); + + /* Set stride to use GBD. */ + vga_out8(0x3d4, 0x50, par); + vga_out8(0x3d5, vga_in8(0x3d5, par) | 0xC1, par); + + /* Enable 2D engine. */ + vga_out8(0x3d4, 0x40, par); + vga_out8(0x3d5, 0x01, par); + + savage_out32(MONO_PAT_0, ~0, par); + savage_out32(MONO_PAT_1, ~0, par); + + /* Setup plane masks */ + savage_out32(0x8128, ~0, par); /* enable all write planes */ + savage_out32(0x812C, ~0, par); /* enable all read planes */ + savage_out16(0x8134, 0x27, par); + savage_out16(0x8136, 0x07, par); + + /* Now set the GBD */ + par->bci_ptr = 0; + par->SavageWaitFifo(par, 4); + + BCI_SEND(BCI_CMD_SETREG | (1 << 16) | BCI_GBD1); + BCI_SEND(0); + BCI_SEND(BCI_CMD_SETREG | (1 << 16) | BCI_GBD2); + BCI_SEND(GlobalBitmapDescriptor); + + /* + * I don't know why, sending this twice fixes the initial black screen, + * prevents X from crashing at least in Toshiba laptops with SavageIX. + * --Tony + */ + par->bci_ptr = 0; + par->SavageWaitFifo(par, 4); + + BCI_SEND(BCI_CMD_SETREG | (1 << 16) | BCI_GBD1); + BCI_SEND(0); + BCI_SEND(BCI_CMD_SETREG | (1 << 16) | BCI_GBD2); + BCI_SEND(GlobalBitmapDescriptor); +} + +static void savagefb_set_clip(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + int cmd; + + cmd = BCI_CMD_NOP | BCI_CMD_CLIP_NEW; + par->bci_ptr = 0; + par->SavageWaitFifo(par,3); + BCI_SEND(cmd); + BCI_SEND(BCI_CLIP_TL(0, 0)); + BCI_SEND(BCI_CLIP_BR(0xfff, 0xfff)); +} +#else +static void SavageSetup2DEngine(struct savagefb_par *par) {} + +#endif + +static void SavageCalcClock(long freq, int min_m, int min_n1, int max_n1, + int min_n2, int max_n2, long freq_min, + long freq_max, unsigned int *mdiv, + unsigned int *ndiv, unsigned int *r) +{ + long diff, best_diff; + unsigned int m; + unsigned char n1, n2, best_n1=16+2, best_n2=2, best_m=125+2; + + if (freq < freq_min / (1 << max_n2)) { + printk(KERN_ERR "invalid frequency %ld Khz\n", freq); + freq = freq_min / (1 << max_n2); + } + if (freq > freq_max / (1 << min_n2)) { + printk(KERN_ERR "invalid frequency %ld Khz\n", freq); + freq = freq_max / (1 << min_n2); + } + + /* work out suitable timings */ + best_diff = freq; + + for (n2=min_n2; n2<=max_n2; n2++) { + for (n1=min_n1+2; n1<=max_n1+2; n1++) { + m = (freq * n1 * (1 << n2) + HALF_BASE_FREQ) / + BASE_FREQ; + if (m < min_m+2 || m > 127+2) + continue; + if ((m * BASE_FREQ >= freq_min * n1) && + (m * BASE_FREQ <= freq_max * n1)) { + diff = freq * (1 << n2) * n1 - BASE_FREQ * m; + if (diff < 0) + diff = -diff; + if (diff < best_diff) { + best_diff = diff; + best_m = m; + best_n1 = n1; + best_n2 = n2; + } + } + } + } + + *ndiv = best_n1 - 2; + *r = best_n2; + *mdiv = best_m - 2; +} + +static int common_calc_clock(long freq, int min_m, int min_n1, int max_n1, + int min_n2, int max_n2, long freq_min, + long freq_max, unsigned char *mdiv, + unsigned char *ndiv) +{ + long diff, best_diff; + unsigned int m; + unsigned char n1, n2; + unsigned char best_n1 = 16+2, best_n2 = 2, best_m = 125+2; + + best_diff = freq; + + for (n2 = min_n2; n2 <= max_n2; n2++) { + for (n1 = min_n1+2; n1 <= max_n1+2; n1++) { + m = (freq * n1 * (1 << n2) + HALF_BASE_FREQ) / + BASE_FREQ; + if (m < min_m + 2 || m > 127+2) + continue; + if ((m * BASE_FREQ >= freq_min * n1) && + (m * BASE_FREQ <= freq_max * n1)) { + diff = freq * (1 << n2) * n1 - BASE_FREQ * m; + if (diff < 0) + diff = -diff; + if (diff < best_diff) { + best_diff = diff; + best_m = m; + best_n1 = n1; + best_n2 = n2; + } + } + } + } + + if (max_n1 == 63) + *ndiv = (best_n1 - 2) | (best_n2 << 6); + else + *ndiv = (best_n1 - 2) | (best_n2 << 5); + + *mdiv = best_m - 2; + + return 0; +} + +#ifdef SAVAGEFB_DEBUG +/* This function is used to debug, it prints out the contents of s3 regs */ + +static void SavagePrintRegs(struct savagefb_par *par) +{ + unsigned char i; + int vgaCRIndex = 0x3d4; + int vgaCRReg = 0x3d5; + + printk(KERN_DEBUG "SR x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE " + "xF"); + + for (i = 0; i < 0x70; i++) { + if (!(i % 16)) + printk(KERN_DEBUG "\nSR%xx ", i >> 4); + vga_out8(0x3c4, i, par); + printk(KERN_DEBUG " %02x", vga_in8(0x3c5, par)); + } + + printk(KERN_DEBUG "\n\nCR x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC " + "xD xE xF"); + + for (i = 0; i < 0xB7; i++) { + if (!(i % 16)) + printk(KERN_DEBUG "\nCR%xx ", i >> 4); + vga_out8(vgaCRIndex, i, par); + printk(KERN_DEBUG " %02x", vga_in8(vgaCRReg, par)); + } + + printk(KERN_DEBUG "\n\n"); +} +#endif + +/* --------------------------------------------------------------------- */ + +static void savage_get_default_par(struct savagefb_par *par, struct savage_reg *reg) +{ + unsigned char cr3a, cr53, cr66; + + vga_out16(0x3d4, 0x4838, par); + vga_out16(0x3d4, 0xa039, par); + vga_out16(0x3c4, 0x0608, par); + + vga_out8(0x3d4, 0x66, par); + cr66 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr66 | 0x80, par); + vga_out8(0x3d4, 0x3a, par); + cr3a = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr3a | 0x80, par); + vga_out8(0x3d4, 0x53, par); + cr53 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr53 & 0x7f, par); + + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66, par); + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, cr3a, par); + + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66, par); + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, cr3a, par); + + /* unlock extended seq regs */ + vga_out8(0x3c4, 0x08, par); + reg->SR08 = vga_in8(0x3c5, par); + vga_out8(0x3c5, 0x06, par); + + /* now save all the extended regs we need */ + vga_out8(0x3d4, 0x31, par); + reg->CR31 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x32, par); + reg->CR32 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x34, par); + reg->CR34 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x36, par); + reg->CR36 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x3a, par); + reg->CR3A = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x40, par); + reg->CR40 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x42, par); + reg->CR42 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x45, par); + reg->CR45 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x50, par); + reg->CR50 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x51, par); + reg->CR51 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x53, par); + reg->CR53 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x58, par); + reg->CR58 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x60, par); + reg->CR60 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x66, par); + reg->CR66 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x67, par); + reg->CR67 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x68, par); + reg->CR68 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x69, par); + reg->CR69 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x6f, par); + reg->CR6F = vga_in8(0x3d5, par); + + vga_out8(0x3d4, 0x33, par); + reg->CR33 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x86, par); + reg->CR86 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x88, par); + reg->CR88 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x90, par); + reg->CR90 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x91, par); + reg->CR91 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0xb0, par); + reg->CRB0 = vga_in8(0x3d5, par) | 0x80; + + /* extended mode timing regs */ + vga_out8(0x3d4, 0x3b, par); + reg->CR3B = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x3c, par); + reg->CR3C = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x43, par); + reg->CR43 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x5d, par); + reg->CR5D = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x5e, par); + reg->CR5E = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x65, par); + reg->CR65 = vga_in8(0x3d5, par); + + /* save seq extended regs for DCLK PLL programming */ + vga_out8(0x3c4, 0x0e, par); + reg->SR0E = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x0f, par); + reg->SR0F = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x10, par); + reg->SR10 = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x11, par); + reg->SR11 = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x12, par); + reg->SR12 = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x13, par); + reg->SR13 = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x29, par); + reg->SR29 = vga_in8(0x3c5, par); + + vga_out8(0x3c4, 0x15, par); + reg->SR15 = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x30, par); + reg->SR30 = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x18, par); + reg->SR18 = vga_in8(0x3c5, par); + + /* Save flat panel expansion registers. */ + if (par->chip == S3_SAVAGE_MX) { + int i; + + for (i = 0; i < 8; i++) { + vga_out8(0x3c4, 0x54+i, par); + reg->SR54[i] = vga_in8(0x3c5, par); + } + } + + vga_out8(0x3d4, 0x66, par); + cr66 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr66 | 0x80, par); + vga_out8(0x3d4, 0x3a, par); + cr3a = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr3a | 0x80, par); + + /* now save MIU regs */ + if (par->chip != S3_SAVAGE_MX) { + reg->MMPR0 = savage_in32(FIFO_CONTROL_REG, par); + reg->MMPR1 = savage_in32(MIU_CONTROL_REG, par); + reg->MMPR2 = savage_in32(STREAMS_TIMEOUT_REG, par); + reg->MMPR3 = savage_in32(MISC_TIMEOUT_REG, par); + } + + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, cr3a, par); + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66, par); +} + +static void savage_set_default_par(struct savagefb_par *par, + struct savage_reg *reg) +{ + unsigned char cr3a, cr53, cr66; + + vga_out16(0x3d4, 0x4838, par); + vga_out16(0x3d4, 0xa039, par); + vga_out16(0x3c4, 0x0608, par); + + vga_out8(0x3d4, 0x66, par); + cr66 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr66 | 0x80, par); + vga_out8(0x3d4, 0x3a, par); + cr3a = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr3a | 0x80, par); + vga_out8(0x3d4, 0x53, par); + cr53 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr53 & 0x7f, par); + + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66, par); + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, cr3a, par); + + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66, par); + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, cr3a, par); + + /* unlock extended seq regs */ + vga_out8(0x3c4, 0x08, par); + vga_out8(0x3c5, reg->SR08, par); + vga_out8(0x3c5, 0x06, par); + + /* now restore all the extended regs we need */ + vga_out8(0x3d4, 0x31, par); + vga_out8(0x3d5, reg->CR31, par); + vga_out8(0x3d4, 0x32, par); + vga_out8(0x3d5, reg->CR32, par); + vga_out8(0x3d4, 0x34, par); + vga_out8(0x3d5, reg->CR34, par); + vga_out8(0x3d4, 0x36, par); + vga_out8(0x3d5,reg->CR36, par); + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, reg->CR3A, par); + vga_out8(0x3d4, 0x40, par); + vga_out8(0x3d5, reg->CR40, par); + vga_out8(0x3d4, 0x42, par); + vga_out8(0x3d5, reg->CR42, par); + vga_out8(0x3d4, 0x45, par); + vga_out8(0x3d5, reg->CR45, par); + vga_out8(0x3d4, 0x50, par); + vga_out8(0x3d5, reg->CR50, par); + vga_out8(0x3d4, 0x51, par); + vga_out8(0x3d5, reg->CR51, par); + vga_out8(0x3d4, 0x53, par); + vga_out8(0x3d5, reg->CR53, par); + vga_out8(0x3d4, 0x58, par); + vga_out8(0x3d5, reg->CR58, par); + vga_out8(0x3d4, 0x60, par); + vga_out8(0x3d5, reg->CR60, par); + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, reg->CR66, par); + vga_out8(0x3d4, 0x67, par); + vga_out8(0x3d5, reg->CR67, par); + vga_out8(0x3d4, 0x68, par); + vga_out8(0x3d5, reg->CR68, par); + vga_out8(0x3d4, 0x69, par); + vga_out8(0x3d5, reg->CR69, par); + vga_out8(0x3d4, 0x6f, par); + vga_out8(0x3d5, reg->CR6F, par); + + vga_out8(0x3d4, 0x33, par); + vga_out8(0x3d5, reg->CR33, par); + vga_out8(0x3d4, 0x86, par); + vga_out8(0x3d5, reg->CR86, par); + vga_out8(0x3d4, 0x88, par); + vga_out8(0x3d5, reg->CR88, par); + vga_out8(0x3d4, 0x90, par); + vga_out8(0x3d5, reg->CR90, par); + vga_out8(0x3d4, 0x91, par); + vga_out8(0x3d5, reg->CR91, par); + vga_out8(0x3d4, 0xb0, par); + vga_out8(0x3d5, reg->CRB0, par); + + /* extended mode timing regs */ + vga_out8(0x3d4, 0x3b, par); + vga_out8(0x3d5, reg->CR3B, par); + vga_out8(0x3d4, 0x3c, par); + vga_out8(0x3d5, reg->CR3C, par); + vga_out8(0x3d4, 0x43, par); + vga_out8(0x3d5, reg->CR43, par); + vga_out8(0x3d4, 0x5d, par); + vga_out8(0x3d5, reg->CR5D, par); + vga_out8(0x3d4, 0x5e, par); + vga_out8(0x3d5, reg->CR5E, par); + vga_out8(0x3d4, 0x65, par); + vga_out8(0x3d5, reg->CR65, par); + + /* save seq extended regs for DCLK PLL programming */ + vga_out8(0x3c4, 0x0e, par); + vga_out8(0x3c5, reg->SR0E, par); + vga_out8(0x3c4, 0x0f, par); + vga_out8(0x3c5, reg->SR0F, par); + vga_out8(0x3c4, 0x10, par); + vga_out8(0x3c5, reg->SR10, par); + vga_out8(0x3c4, 0x11, par); + vga_out8(0x3c5, reg->SR11, par); + vga_out8(0x3c4, 0x12, par); + vga_out8(0x3c5, reg->SR12, par); + vga_out8(0x3c4, 0x13, par); + vga_out8(0x3c5, reg->SR13, par); + vga_out8(0x3c4, 0x29, par); + vga_out8(0x3c5, reg->SR29, par); + + vga_out8(0x3c4, 0x15, par); + vga_out8(0x3c5, reg->SR15, par); + vga_out8(0x3c4, 0x30, par); + vga_out8(0x3c5, reg->SR30, par); + vga_out8(0x3c4, 0x18, par); + vga_out8(0x3c5, reg->SR18, par); + + /* Save flat panel expansion registers. */ + if (par->chip == S3_SAVAGE_MX) { + int i; + + for (i = 0; i < 8; i++) { + vga_out8(0x3c4, 0x54+i, par); + vga_out8(0x3c5, reg->SR54[i], par); + } + } + + vga_out8(0x3d4, 0x66, par); + cr66 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr66 | 0x80, par); + vga_out8(0x3d4, 0x3a, par); + cr3a = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr3a | 0x80, par); + + /* now save MIU regs */ + if (par->chip != S3_SAVAGE_MX) { + savage_out32(FIFO_CONTROL_REG, reg->MMPR0, par); + savage_out32(MIU_CONTROL_REG, reg->MMPR1, par); + savage_out32(STREAMS_TIMEOUT_REG, reg->MMPR2, par); + savage_out32(MISC_TIMEOUT_REG, reg->MMPR3, par); + } + + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, cr3a, par); + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66, par); +} + +static void savage_update_var(struct fb_var_screeninfo *var, + const struct fb_videomode *modedb) +{ + var->xres = var->xres_virtual = modedb->xres; + var->yres = modedb->yres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + var->xoffset = var->yoffset = 0; + var->pixclock = modedb->pixclock; + var->left_margin = modedb->left_margin; + var->right_margin = modedb->right_margin; + var->upper_margin = modedb->upper_margin; + var->lower_margin = modedb->lower_margin; + var->hsync_len = modedb->hsync_len; + var->vsync_len = modedb->vsync_len; + var->sync = modedb->sync; + var->vmode = modedb->vmode; +} + +static int savagefb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct savagefb_par *par = info->par; + int memlen, vramlen, mode_valid = 0; + + DBG("savagefb_check_var"); + + var->transp.offset = 0; + var->transp.length = 0; + switch (var->bits_per_pixel) { + case 8: + var->red.offset = var->green.offset = + var->blue.offset = 0; + var->red.length = var->green.length = + var->blue.length = var->bits_per_pixel; + break; + case 16: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + case 32: + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + break; + + default: + return -EINVAL; + } + + if (!info->monspecs.hfmax || !info->monspecs.vfmax || + !info->monspecs.dclkmax || !fb_validate_mode(var, info)) + mode_valid = 1; + + /* calculate modeline if supported by monitor */ + if (!mode_valid && info->monspecs.gtf) { + if (!fb_get_mode(FB_MAXTIMINGS, 0, var, info)) + mode_valid = 1; + } + + if (!mode_valid) { + const struct fb_videomode *mode; + + mode = fb_find_best_mode(var, &info->modelist); + if (mode) { + savage_update_var(var, mode); + mode_valid = 1; + } + } + + if (!mode_valid && info->monspecs.modedb_len) + return -EINVAL; + + /* Is the mode larger than the LCD panel? */ + if (par->SavagePanelWidth && + (var->xres > par->SavagePanelWidth || + var->yres > par->SavagePanelHeight)) { + printk(KERN_INFO "Mode (%dx%d) larger than the LCD panel " + "(%dx%d)\n", var->xres, var->yres, + par->SavagePanelWidth, + par->SavagePanelHeight); + return -1; + } + + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + vramlen = info->fix.smem_len; + + memlen = var->xres_virtual * var->bits_per_pixel * + var->yres_virtual / 8; + if (memlen > vramlen) { + var->yres_virtual = vramlen * 8 / + (var->xres_virtual * var->bits_per_pixel); + memlen = var->xres_virtual * var->bits_per_pixel * + var->yres_virtual / 8; + } + + /* we must round yres/xres down, we already rounded y/xres_virtual up + if it was possible. We should return -EINVAL, but I disagree */ + if (var->yres_virtual < var->yres) + var->yres = var->yres_virtual; + if (var->xres_virtual < var->xres) + var->xres = var->xres_virtual; + if (var->xoffset + var->xres > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yoffset + var->yres > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + return 0; +} + + +static int savagefb_decode_var(struct fb_var_screeninfo *var, + struct savagefb_par *par, + struct savage_reg *reg) +{ + struct xtimings timings; + int width, dclk, i, j; /*, refresh; */ + unsigned int m, n, r; + unsigned char tmp = 0; + unsigned int pixclock = var->pixclock; + + DBG("savagefb_decode_var"); + + memset(&timings, 0, sizeof(timings)); + + if (!pixclock) pixclock = 10000; /* 10ns = 100MHz */ + timings.Clock = 1000000000 / pixclock; + if (timings.Clock < 1) timings.Clock = 1; + timings.dblscan = var->vmode & FB_VMODE_DOUBLE; + timings.interlaced = var->vmode & FB_VMODE_INTERLACED; + timings.HDisplay = var->xres; + timings.HSyncStart = timings.HDisplay + var->right_margin; + timings.HSyncEnd = timings.HSyncStart + var->hsync_len; + timings.HTotal = timings.HSyncEnd + var->left_margin; + timings.VDisplay = var->yres; + timings.VSyncStart = timings.VDisplay + var->lower_margin; + timings.VSyncEnd = timings.VSyncStart + var->vsync_len; + timings.VTotal = timings.VSyncEnd + var->upper_margin; + timings.sync = var->sync; + + + par->depth = var->bits_per_pixel; + par->vwidth = var->xres_virtual; + + if (var->bits_per_pixel == 16 && par->chip == S3_SAVAGE3D) { + timings.HDisplay *= 2; + timings.HSyncStart *= 2; + timings.HSyncEnd *= 2; + timings.HTotal *= 2; + } + + /* + * This will allocate the datastructure and initialize all of the + * generic VGA registers. + */ + vgaHWInit(var, par, &timings, reg); + + /* We need to set CR67 whether or not we use the BIOS. */ + + dclk = timings.Clock; + reg->CR67 = 0x00; + + switch(var->bits_per_pixel) { + case 8: + if ((par->chip == S3_SAVAGE2000) && (dclk >= 230000)) + reg->CR67 = 0x10; /* 8bpp, 2 pixels/clock */ + else + reg->CR67 = 0x00; /* 8bpp, 1 pixel/clock */ + break; + case 15: + if (S3_SAVAGE_MOBILE_SERIES(par->chip) || + ((par->chip == S3_SAVAGE2000) && (dclk >= 230000))) + reg->CR67 = 0x30; /* 15bpp, 2 pixel/clock */ + else + reg->CR67 = 0x20; /* 15bpp, 1 pixels/clock */ + break; + case 16: + if (S3_SAVAGE_MOBILE_SERIES(par->chip) || + ((par->chip == S3_SAVAGE2000) && (dclk >= 230000))) + reg->CR67 = 0x50; /* 16bpp, 2 pixel/clock */ + else + reg->CR67 = 0x40; /* 16bpp, 1 pixels/clock */ + break; + case 24: + reg->CR67 = 0x70; + break; + case 32: + reg->CR67 = 0xd0; + break; + } + + /* + * Either BIOS use is disabled, or we failed to find a suitable + * match. Fall back to traditional register-crunching. + */ + + vga_out8(0x3d4, 0x3a, par); + tmp = vga_in8(0x3d5, par); + if (1 /*FIXME:psav->pci_burst*/) + reg->CR3A = (tmp & 0x7f) | 0x15; + else + reg->CR3A = tmp | 0x95; + + reg->CR53 = 0x00; + reg->CR31 = 0x8c; + reg->CR66 = 0x89; + + vga_out8(0x3d4, 0x58, par); + reg->CR58 = vga_in8(0x3d5, par) & 0x80; + reg->CR58 |= 0x13; + + reg->SR15 = 0x03 | 0x80; + reg->SR18 = 0x00; + reg->CR43 = reg->CR45 = reg->CR65 = 0x00; + + vga_out8(0x3d4, 0x40, par); + reg->CR40 = vga_in8(0x3d5, par) & ~0x01; + + reg->MMPR0 = 0x010400; + reg->MMPR1 = 0x00; + reg->MMPR2 = 0x0808; + reg->MMPR3 = 0x08080810; + + SavageCalcClock(dclk, 1, 1, 127, 0, 4, 180000, 360000, &m, &n, &r); + /* m = 107; n = 4; r = 2; */ + + if (par->MCLK <= 0) { + reg->SR10 = 255; + reg->SR11 = 255; + } else { + common_calc_clock(par->MCLK, 1, 1, 31, 0, 3, 135000, 270000, + ®->SR11, ®->SR10); + /* reg->SR10 = 80; // MCLK == 286000 */ + /* reg->SR11 = 125; */ + } + + reg->SR12 = (r << 6) | (n & 0x3f); + reg->SR13 = m & 0xff; + reg->SR29 = (r & 4) | (m & 0x100) >> 5 | (n & 0x40) >> 2; + + if (var->bits_per_pixel < 24) + reg->MMPR0 -= 0x8000; + else + reg->MMPR0 -= 0x4000; + + if (timings.interlaced) + reg->CR42 = 0x20; + else + reg->CR42 = 0x00; + + reg->CR34 = 0x10; /* display fifo */ + + i = ((((timings.HTotal >> 3) - 5) & 0x100) >> 8) | + ((((timings.HDisplay >> 3) - 1) & 0x100) >> 7) | + ((((timings.HSyncStart >> 3) - 1) & 0x100) >> 6) | + ((timings.HSyncStart & 0x800) >> 7); + + if ((timings.HSyncEnd >> 3) - (timings.HSyncStart >> 3) > 64) + i |= 0x08; + if ((timings.HSyncEnd >> 3) - (timings.HSyncStart >> 3) > 32) + i |= 0x20; + + j = (reg->CRTC[0] + ((i & 0x01) << 8) + + reg->CRTC[4] + ((i & 0x10) << 4) + 1) / 2; + + if (j - (reg->CRTC[4] + ((i & 0x10) << 4)) < 4) { + if (reg->CRTC[4] + ((i & 0x10) << 4) + 4 <= + reg->CRTC[0] + ((i & 0x01) << 8)) + j = reg->CRTC[4] + ((i & 0x10) << 4) + 4; + else + j = reg->CRTC[0] + ((i & 0x01) << 8) + 1; + } + + reg->CR3B = j & 0xff; + i |= (j & 0x100) >> 2; + reg->CR3C = (reg->CRTC[0] + ((i & 0x01) << 8)) / 2; + reg->CR5D = i; + reg->CR5E = (((timings.VTotal - 2) & 0x400) >> 10) | + (((timings.VDisplay - 1) & 0x400) >> 9) | + (((timings.VSyncStart) & 0x400) >> 8) | + (((timings.VSyncStart) & 0x400) >> 6) | 0x40; + width = (var->xres_virtual * ((var->bits_per_pixel+7) / 8)) >> 3; + reg->CR91 = reg->CRTC[19] = 0xff & width; + reg->CR51 = (0x300 & width) >> 4; + reg->CR90 = 0x80 | (width >> 8); + reg->MiscOutReg |= 0x0c; + + /* Set frame buffer description. */ + + if (var->bits_per_pixel <= 8) + reg->CR50 = 0; + else if (var->bits_per_pixel <= 16) + reg->CR50 = 0x10; + else + reg->CR50 = 0x30; + + if (var->xres_virtual <= 640) + reg->CR50 |= 0x40; + else if (var->xres_virtual == 800) + reg->CR50 |= 0x80; + else if (var->xres_virtual == 1024) + reg->CR50 |= 0x00; + else if (var->xres_virtual == 1152) + reg->CR50 |= 0x01; + else if (var->xres_virtual == 1280) + reg->CR50 |= 0xc0; + else if (var->xres_virtual == 1600) + reg->CR50 |= 0x81; + else + reg->CR50 |= 0xc1; /* Use GBD */ + + if (par->chip == S3_SAVAGE2000) + reg->CR33 = 0x08; + else + reg->CR33 = 0x20; + + reg->CRTC[0x17] = 0xeb; + + reg->CR67 |= 1; + + vga_out8(0x3d4, 0x36, par); + reg->CR36 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x68, par); + reg->CR68 = vga_in8(0x3d5, par); + reg->CR69 = 0; + vga_out8(0x3d4, 0x6f, par); + reg->CR6F = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x86, par); + reg->CR86 = vga_in8(0x3d5, par); + vga_out8(0x3d4, 0x88, par); + reg->CR88 = vga_in8(0x3d5, par) | 0x08; + vga_out8(0x3d4, 0xb0, par); + reg->CRB0 = vga_in8(0x3d5, par) | 0x80; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Set a single color register. Return != 0 for invalid regno. + */ +static int savagefb_setcolreg(unsigned regno, + unsigned red, + unsigned green, + unsigned blue, + unsigned transp, + struct fb_info *info) +{ + struct savagefb_par *par = info->par; + + if (regno >= NR_PALETTE) + return -EINVAL; + + par->palette[regno].red = red; + par->palette[regno].green = green; + par->palette[regno].blue = blue; + par->palette[regno].transp = transp; + + switch (info->var.bits_per_pixel) { + case 8: + vga_out8(0x3c8, regno, par); + + vga_out8(0x3c9, red >> 10, par); + vga_out8(0x3c9, green >> 10, par); + vga_out8(0x3c9, blue >> 10, par); + break; + + case 16: + if (regno < 16) + ((u32 *)info->pseudo_palette)[regno] = + ((red & 0xf800) ) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + + case 24: + if (regno < 16) + ((u32 *)info->pseudo_palette)[regno] = + ((red & 0xff00) << 8) | + ((green & 0xff00) ) | + ((blue & 0xff00) >> 8); + break; + case 32: + if (regno < 16) + ((u32 *)info->pseudo_palette)[regno] = + ((transp & 0xff00) << 16) | + ((red & 0xff00) << 8) | + ((green & 0xff00) ) | + ((blue & 0xff00) >> 8); + break; + + default: + return 1; + } + + return 0; +} + +static void savagefb_set_par_int(struct savagefb_par *par, struct savage_reg *reg) +{ + unsigned char tmp, cr3a, cr66, cr67; + + DBG("savagefb_set_par_int"); + + par->SavageWaitIdle(par); + + vga_out8(0x3c2, 0x23, par); + + vga_out16(0x3d4, 0x4838, par); + vga_out16(0x3d4, 0xa539, par); + vga_out16(0x3c4, 0x0608, par); + + vgaHWProtect(par, 1); + + /* + * Some Savage/MX and /IX systems go nuts when trying to exit the + * server after WindowMaker has displayed a gradient background. I + * haven't been able to find what causes it, but a non-destructive + * switch to mode 3 here seems to eliminate the issue. + */ + + VerticalRetraceWait(par); + vga_out8(0x3d4, 0x67, par); + cr67 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr67/*par->CR67*/ & ~0x0c, par); /* no STREAMS yet */ + + vga_out8(0x3d4, 0x23, par); + vga_out8(0x3d5, 0x00, par); + vga_out8(0x3d4, 0x26, par); + vga_out8(0x3d5, 0x00, par); + + /* restore extended regs */ + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, reg->CR66, par); + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, reg->CR3A, par); + vga_out8(0x3d4, 0x31, par); + vga_out8(0x3d5, reg->CR31, par); + vga_out8(0x3d4, 0x32, par); + vga_out8(0x3d5, reg->CR32, par); + vga_out8(0x3d4, 0x58, par); + vga_out8(0x3d5, reg->CR58, par); + vga_out8(0x3d4, 0x53, par); + vga_out8(0x3d5, reg->CR53 & 0x7f, par); + + vga_out16(0x3c4, 0x0608, par); + + /* Restore DCLK registers. */ + + vga_out8(0x3c4, 0x0e, par); + vga_out8(0x3c5, reg->SR0E, par); + vga_out8(0x3c4, 0x0f, par); + vga_out8(0x3c5, reg->SR0F, par); + vga_out8(0x3c4, 0x29, par); + vga_out8(0x3c5, reg->SR29, par); + vga_out8(0x3c4, 0x15, par); + vga_out8(0x3c5, reg->SR15, par); + + /* Restore flat panel expansion registers. */ + if (par->chip == S3_SAVAGE_MX) { + int i; + + for (i = 0; i < 8; i++) { + vga_out8(0x3c4, 0x54+i, par); + vga_out8(0x3c5, reg->SR54[i], par); + } + } + + vgaHWRestore (par, reg); + + /* extended mode timing registers */ + vga_out8(0x3d4, 0x53, par); + vga_out8(0x3d5, reg->CR53, par); + vga_out8(0x3d4, 0x5d, par); + vga_out8(0x3d5, reg->CR5D, par); + vga_out8(0x3d4, 0x5e, par); + vga_out8(0x3d5, reg->CR5E, par); + vga_out8(0x3d4, 0x3b, par); + vga_out8(0x3d5, reg->CR3B, par); + vga_out8(0x3d4, 0x3c, par); + vga_out8(0x3d5, reg->CR3C, par); + vga_out8(0x3d4, 0x43, par); + vga_out8(0x3d5, reg->CR43, par); + vga_out8(0x3d4, 0x65, par); + vga_out8(0x3d5, reg->CR65, par); + + /* restore the desired video mode with cr67 */ + vga_out8(0x3d4, 0x67, par); + /* following part not present in X11 driver */ + cr67 = vga_in8(0x3d5, par) & 0xf; + vga_out8(0x3d5, 0x50 | cr67, par); + mdelay(10); + vga_out8(0x3d4, 0x67, par); + /* end of part */ + vga_out8(0x3d5, reg->CR67 & ~0x0c, par); + + /* other mode timing and extended regs */ + vga_out8(0x3d4, 0x34, par); + vga_out8(0x3d5, reg->CR34, par); + vga_out8(0x3d4, 0x40, par); + vga_out8(0x3d5, reg->CR40, par); + vga_out8(0x3d4, 0x42, par); + vga_out8(0x3d5, reg->CR42, par); + vga_out8(0x3d4, 0x45, par); + vga_out8(0x3d5, reg->CR45, par); + vga_out8(0x3d4, 0x50, par); + vga_out8(0x3d5, reg->CR50, par); + vga_out8(0x3d4, 0x51, par); + vga_out8(0x3d5, reg->CR51, par); + + /* memory timings */ + vga_out8(0x3d4, 0x36, par); + vga_out8(0x3d5, reg->CR36, par); + vga_out8(0x3d4, 0x60, par); + vga_out8(0x3d5, reg->CR60, par); + vga_out8(0x3d4, 0x68, par); + vga_out8(0x3d5, reg->CR68, par); + vga_out8(0x3d4, 0x69, par); + vga_out8(0x3d5, reg->CR69, par); + vga_out8(0x3d4, 0x6f, par); + vga_out8(0x3d5, reg->CR6F, par); + + vga_out8(0x3d4, 0x33, par); + vga_out8(0x3d5, reg->CR33, par); + vga_out8(0x3d4, 0x86, par); + vga_out8(0x3d5, reg->CR86, par); + vga_out8(0x3d4, 0x88, par); + vga_out8(0x3d5, reg->CR88, par); + vga_out8(0x3d4, 0x90, par); + vga_out8(0x3d5, reg->CR90, par); + vga_out8(0x3d4, 0x91, par); + vga_out8(0x3d5, reg->CR91, par); + + if (par->chip == S3_SAVAGE4) { + vga_out8(0x3d4, 0xb0, par); + vga_out8(0x3d5, reg->CRB0, par); + } + + vga_out8(0x3d4, 0x32, par); + vga_out8(0x3d5, reg->CR32, par); + + /* unlock extended seq regs */ + vga_out8(0x3c4, 0x08, par); + vga_out8(0x3c5, 0x06, par); + + /* Restore extended sequencer regs for MCLK. SR10 == 255 indicates + * that we should leave the default SR10 and SR11 values there. + */ + if (reg->SR10 != 255) { + vga_out8(0x3c4, 0x10, par); + vga_out8(0x3c5, reg->SR10, par); + vga_out8(0x3c4, 0x11, par); + vga_out8(0x3c5, reg->SR11, par); + } + + /* restore extended seq regs for dclk */ + vga_out8(0x3c4, 0x0e, par); + vga_out8(0x3c5, reg->SR0E, par); + vga_out8(0x3c4, 0x0f, par); + vga_out8(0x3c5, reg->SR0F, par); + vga_out8(0x3c4, 0x12, par); + vga_out8(0x3c5, reg->SR12, par); + vga_out8(0x3c4, 0x13, par); + vga_out8(0x3c5, reg->SR13, par); + vga_out8(0x3c4, 0x29, par); + vga_out8(0x3c5, reg->SR29, par); + vga_out8(0x3c4, 0x18, par); + vga_out8(0x3c5, reg->SR18, par); + + /* load new m, n pll values for dclk & mclk */ + vga_out8(0x3c4, 0x15, par); + tmp = vga_in8(0x3c5, par) & ~0x21; + + vga_out8(0x3c5, tmp | 0x03, par); + vga_out8(0x3c5, tmp | 0x23, par); + vga_out8(0x3c5, tmp | 0x03, par); + vga_out8(0x3c5, reg->SR15, par); + udelay(100); + + vga_out8(0x3c4, 0x30, par); + vga_out8(0x3c5, reg->SR30, par); + vga_out8(0x3c4, 0x08, par); + vga_out8(0x3c5, reg->SR08, par); + + /* now write out cr67 in full, possibly starting STREAMS */ + VerticalRetraceWait(par); + vga_out8(0x3d4, 0x67, par); + vga_out8(0x3d5, reg->CR67, par); + + vga_out8(0x3d4, 0x66, par); + cr66 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr66 | 0x80, par); + vga_out8(0x3d4, 0x3a, par); + cr3a = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr3a | 0x80, par); + + if (par->chip != S3_SAVAGE_MX) { + VerticalRetraceWait(par); + savage_out32(FIFO_CONTROL_REG, reg->MMPR0, par); + par->SavageWaitIdle(par); + savage_out32(MIU_CONTROL_REG, reg->MMPR1, par); + par->SavageWaitIdle(par); + savage_out32(STREAMS_TIMEOUT_REG, reg->MMPR2, par); + par->SavageWaitIdle(par); + savage_out32(MISC_TIMEOUT_REG, reg->MMPR3, par); + } + + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66, par); + vga_out8(0x3d4, 0x3a, par); + vga_out8(0x3d5, cr3a, par); + + SavageSetup2DEngine(par); + vgaHWProtect(par, 0); +} + +static void savagefb_update_start(struct savagefb_par *par, int base) +{ + /* program the start address registers */ + vga_out16(0x3d4, (base & 0x00ff00) | 0x0c, par); + vga_out16(0x3d4, ((base & 0x00ff) << 8) | 0x0d, par); + vga_out8(0x3d4, 0x69, par); + vga_out8(0x3d5, (base & 0x7f0000) >> 16, par); +} + + +static void savagefb_set_fix(struct fb_info *info) +{ + info->fix.line_length = info->var.xres_virtual * + info->var.bits_per_pixel / 8; + + if (info->var.bits_per_pixel == 8) { + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.xpanstep = 4; + } else { + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.xpanstep = 2; + } + +} + +static int savagefb_set_par(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + struct fb_var_screeninfo *var = &info->var; + int err; + + DBG("savagefb_set_par"); + err = savagefb_decode_var(var, par, &par->state); + if (err) + return err; + + if (par->dacSpeedBpp <= 0) { + if (var->bits_per_pixel > 24) + par->dacSpeedBpp = par->clock[3]; + else if (var->bits_per_pixel >= 24) + par->dacSpeedBpp = par->clock[2]; + else if ((var->bits_per_pixel > 8) && (var->bits_per_pixel < 24)) + par->dacSpeedBpp = par->clock[1]; + else if (var->bits_per_pixel <= 8) + par->dacSpeedBpp = par->clock[0]; + } + + /* Set ramdac limits */ + par->maxClock = par->dacSpeedBpp; + par->minClock = 10000; + + savagefb_set_par_int(par, &par->state); + fb_set_cmap(&info->cmap, info); + savagefb_set_fix(info); + savagefb_set_clip(info); + + SavagePrintRegs(par); + return 0; +} + +/* + * Pan or Wrap the Display + */ +static int savagefb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct savagefb_par *par = info->par; + int base; + + base = (var->yoffset * info->fix.line_length + + (var->xoffset & ~1) * ((info->var.bits_per_pixel+7) / 8)) >> 2; + + savagefb_update_start(par, base); + return 0; +} + +static int savagefb_blank(int blank, struct fb_info *info) +{ + struct savagefb_par *par = info->par; + u8 sr8 = 0, srd = 0; + + if (par->display_type == DISP_CRT) { + vga_out8(0x3c4, 0x08, par); + sr8 = vga_in8(0x3c5, par); + sr8 |= 0x06; + vga_out8(0x3c5, sr8, par); + vga_out8(0x3c4, 0x0d, par); + srd = vga_in8(0x3c5, par); + srd &= 0x50; + + switch (blank) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + break; + case FB_BLANK_VSYNC_SUSPEND: + srd |= 0x10; + break; + case FB_BLANK_HSYNC_SUSPEND: + srd |= 0x40; + break; + case FB_BLANK_POWERDOWN: + srd |= 0x50; + break; + } + + vga_out8(0x3c4, 0x0d, par); + vga_out8(0x3c5, srd, par); + } + + if (par->display_type == DISP_LCD || + par->display_type == DISP_DFP) { + switch(blank) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + vga_out8(0x3c4, 0x31, par); /* SR31 bit 4 - FP enable */ + vga_out8(0x3c5, vga_in8(0x3c5, par) | 0x10, par); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + vga_out8(0x3c4, 0x31, par); /* SR31 bit 4 - FP enable */ + vga_out8(0x3c5, vga_in8(0x3c5, par) & ~0x10, par); + break; + } + } + + return (blank == FB_BLANK_NORMAL) ? 1 : 0; +} + +static int savagefb_open(struct fb_info *info, int user) +{ + struct savagefb_par *par = info->par; + + mutex_lock(&par->open_lock); + + if (!par->open_count) { + memset(&par->vgastate, 0, sizeof(par->vgastate)); + par->vgastate.flags = VGA_SAVE_CMAP | VGA_SAVE_FONTS | + VGA_SAVE_MODE; + par->vgastate.vgabase = par->mmio.vbase + 0x8000; + save_vga(&par->vgastate); + savage_get_default_par(par, &par->initial); + } + + par->open_count++; + mutex_unlock(&par->open_lock); + return 0; +} + +static int savagefb_release(struct fb_info *info, int user) +{ + struct savagefb_par *par = info->par; + + mutex_lock(&par->open_lock); + + if (par->open_count == 1) { + savage_set_default_par(par, &par->initial); + restore_vga(&par->vgastate); + } + + par->open_count--; + mutex_unlock(&par->open_lock); + return 0; +} + +static struct fb_ops savagefb_ops = { + .owner = THIS_MODULE, + .fb_open = savagefb_open, + .fb_release = savagefb_release, + .fb_check_var = savagefb_check_var, + .fb_set_par = savagefb_set_par, + .fb_setcolreg = savagefb_setcolreg, + .fb_pan_display = savagefb_pan_display, + .fb_blank = savagefb_blank, +#if defined(CONFIG_FB_SAVAGE_ACCEL) + .fb_fillrect = savagefb_fillrect, + .fb_copyarea = savagefb_copyarea, + .fb_imageblit = savagefb_imageblit, + .fb_sync = savagefb_sync, +#else + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +#endif +}; + +/* --------------------------------------------------------------------- */ + +static struct fb_var_screeninfo savagefb_var800x600x8 = { + .accel_flags = FB_ACCELF_TEXT, + .xres = 800, + .yres = 600, + .xres_virtual = 800, + .yres_virtual = 600, + .bits_per_pixel = 8, + .pixclock = 25000, + .left_margin = 88, + .right_margin = 40, + .upper_margin = 23, + .lower_margin = 1, + .hsync_len = 128, + .vsync_len = 4, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED +}; + +static void savage_enable_mmio(struct savagefb_par *par) +{ + unsigned char val; + + DBG("savage_enable_mmio\n"); + + val = vga_in8(0x3c3, par); + vga_out8(0x3c3, val | 0x01, par); + val = vga_in8(0x3cc, par); + vga_out8(0x3c2, val | 0x01, par); + + if (par->chip >= S3_SAVAGE4) { + vga_out8(0x3d4, 0x40, par); + val = vga_in8(0x3d5, par); + vga_out8(0x3d5, val | 1, par); + } +} + + +static void savage_disable_mmio(struct savagefb_par *par) +{ + unsigned char val; + + DBG("savage_disable_mmio\n"); + + if (par->chip >= S3_SAVAGE4) { + vga_out8(0x3d4, 0x40, par); + val = vga_in8(0x3d5, par); + vga_out8(0x3d5, val | 1, par); + } +} + + +static int savage_map_mmio(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + DBG("savage_map_mmio"); + + if (S3_SAVAGE3D_SERIES(par->chip)) + par->mmio.pbase = pci_resource_start(par->pcidev, 0) + + SAVAGE_NEWMMIO_REGBASE_S3; + else + par->mmio.pbase = pci_resource_start(par->pcidev, 0) + + SAVAGE_NEWMMIO_REGBASE_S4; + + par->mmio.len = SAVAGE_NEWMMIO_REGSIZE; + + par->mmio.vbase = ioremap(par->mmio.pbase, par->mmio.len); + if (!par->mmio.vbase) { + printk("savagefb: unable to map memory mapped IO\n"); + return -ENOMEM; + } else + printk(KERN_INFO "savagefb: mapped io at %p\n", + par->mmio.vbase); + + info->fix.mmio_start = par->mmio.pbase; + info->fix.mmio_len = par->mmio.len; + + par->bci_base = (u32 __iomem *)(par->mmio.vbase + BCI_BUFFER_OFFSET); + par->bci_ptr = 0; + + savage_enable_mmio(par); + + return 0; +} + +static void savage_unmap_mmio(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + DBG("savage_unmap_mmio"); + + savage_disable_mmio(par); + + if (par->mmio.vbase) { + iounmap(par->mmio.vbase); + par->mmio.vbase = NULL; + } +} + +static int savage_map_video(struct fb_info *info, int video_len) +{ + struct savagefb_par *par = info->par; + int resource; + + DBG("savage_map_video"); + + if (S3_SAVAGE3D_SERIES(par->chip)) + resource = 0; + else + resource = 1; + + par->video.pbase = pci_resource_start(par->pcidev, resource); + par->video.len = video_len; + par->video.vbase = ioremap(par->video.pbase, par->video.len); + + if (!par->video.vbase) { + printk("savagefb: unable to map screen memory\n"); + return -ENOMEM; + } else + printk(KERN_INFO "savagefb: mapped framebuffer at %p, " + "pbase == %x\n", par->video.vbase, par->video.pbase); + + info->fix.smem_start = par->video.pbase; + info->fix.smem_len = par->video.len - par->cob_size; + info->screen_base = par->video.vbase; + +#ifdef CONFIG_MTRR + par->video.mtrr = mtrr_add(par->video.pbase, video_len, + MTRR_TYPE_WRCOMB, 1); +#endif + + /* Clear framebuffer, it's all white in memory after boot */ + memset_io(par->video.vbase, 0, par->video.len); + + return 0; +} + +static void savage_unmap_video(struct fb_info *info) +{ + struct savagefb_par *par = info->par; + + DBG("savage_unmap_video"); + + if (par->video.vbase) { +#ifdef CONFIG_MTRR + mtrr_del(par->video.mtrr, par->video.pbase, par->video.len); +#endif + + iounmap(par->video.vbase); + par->video.vbase = NULL; + info->screen_base = NULL; + } +} + +static int savage_init_hw(struct savagefb_par *par) +{ + unsigned char config1, m, n, n1, n2, sr8, cr3f, cr66 = 0, tmp; + + static unsigned char RamSavage3D[] = { 8, 4, 4, 2 }; + static unsigned char RamSavage4[] = { 2, 4, 8, 12, 16, 32, 64, 32 }; + static unsigned char RamSavageMX[] = { 2, 8, 4, 16, 8, 16, 4, 16 }; + static unsigned char RamSavageNB[] = { 0, 2, 4, 8, 16, 32, 2, 2 }; + int videoRam, videoRambytes, dvi; + + DBG("savage_init_hw"); + + /* unprotect CRTC[0-7] */ + vga_out8(0x3d4, 0x11, par); + tmp = vga_in8(0x3d5, par); + vga_out8(0x3d5, tmp & 0x7f, par); + + /* unlock extended regs */ + vga_out16(0x3d4, 0x4838, par); + vga_out16(0x3d4, 0xa039, par); + vga_out16(0x3c4, 0x0608, par); + + vga_out8(0x3d4, 0x40, par); + tmp = vga_in8(0x3d5, par); + vga_out8(0x3d5, tmp & ~0x01, par); + + /* unlock sys regs */ + vga_out8(0x3d4, 0x38, par); + vga_out8(0x3d5, 0x48, par); + + /* Unlock system registers. */ + vga_out16(0x3d4, 0x4838, par); + + /* Next go on to detect amount of installed ram */ + + vga_out8(0x3d4, 0x36, par); /* for register CR36 (CONFG_REG1), */ + config1 = vga_in8(0x3d5, par); /* get amount of vram installed */ + + /* Compute the amount of video memory and offscreen memory. */ + + switch (par->chip) { + case S3_SAVAGE3D: + videoRam = RamSavage3D[(config1 & 0xC0) >> 6 ] * 1024; + break; + + case S3_SAVAGE4: + /* + * The Savage4 has one ugly special case to consider. On + * systems with 4 banks of 2Mx32 SDRAM, the BIOS says 4MB + * when it really means 8MB. Why do it the same when you + * can do it different... + */ + vga_out8(0x3d4, 0x68, par); /* memory control 1 */ + if ((vga_in8(0x3d5, par) & 0xC0) == (0x01 << 6)) + RamSavage4[1] = 8; + + /*FALLTHROUGH*/ + + case S3_SAVAGE2000: + videoRam = RamSavage4[(config1 & 0xE0) >> 5] * 1024; + break; + + case S3_SAVAGE_MX: + case S3_SUPERSAVAGE: + videoRam = RamSavageMX[(config1 & 0x0E) >> 1] * 1024; + break; + + case S3_PROSAVAGE: + case S3_PROSAVAGEDDR: + case S3_TWISTER: + videoRam = RamSavageNB[(config1 & 0xE0) >> 5] * 1024; + break; + + default: + /* How did we get here? */ + videoRam = 0; + break; + } + + videoRambytes = videoRam * 1024; + + printk(KERN_INFO "savagefb: probed videoram: %dk\n", videoRam); + + /* reset graphics engine to avoid memory corruption */ + vga_out8(0x3d4, 0x66, par); + cr66 = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr66 | 0x02, par); + mdelay(10); + + vga_out8(0x3d4, 0x66, par); + vga_out8(0x3d5, cr66 & ~0x02, par); /* clear reset flag */ + mdelay(10); + + + /* + * reset memory interface, 3D engine, AGP master, PCI master, + * master engine unit, motion compensation/LPB + */ + vga_out8(0x3d4, 0x3f, par); + cr3f = vga_in8(0x3d5, par); + vga_out8(0x3d5, cr3f | 0x08, par); + mdelay(10); + + vga_out8(0x3d4, 0x3f, par); + vga_out8(0x3d5, cr3f & ~0x08, par); /* clear reset flags */ + mdelay(10); + + /* Savage ramdac speeds */ + par->numClocks = 4; + par->clock[0] = 250000; + par->clock[1] = 250000; + par->clock[2] = 220000; + par->clock[3] = 220000; + + /* detect current mclk */ + vga_out8(0x3c4, 0x08, par); + sr8 = vga_in8(0x3c5, par); + vga_out8(0x3c5, 0x06, par); + vga_out8(0x3c4, 0x10, par); + n = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x11, par); + m = vga_in8(0x3c5, par); + vga_out8(0x3c4, 0x08, par); + vga_out8(0x3c5, sr8, par); + m &= 0x7f; + n1 = n & 0x1f; + n2 = (n >> 5) & 0x03; + par->MCLK = ((1431818 * (m+2)) / (n1+2) / (1 << n2) + 50) / 100; + printk(KERN_INFO "savagefb: Detected current MCLK value of %d kHz\n", + par->MCLK); + + /* check for DVI/flat panel */ + dvi = 0; + + if (par->chip == S3_SAVAGE4) { + unsigned char sr30 = 0x00; + + vga_out8(0x3c4, 0x30, par); + /* clear bit 1 */ + vga_out8(0x3c5, vga_in8(0x3c5, par) & ~0x02, par); + sr30 = vga_in8(0x3c5, par); + if (sr30 & 0x02 /*0x04 */) { + dvi = 1; + printk("savagefb: Digital Flat Panel Detected\n"); + } + } + + if ((S3_SAVAGE_MOBILE_SERIES(par->chip) || + S3_MOBILE_TWISTER_SERIES(par->chip)) && !par->crtonly) + par->display_type = DISP_LCD; + else if (dvi || (par->chip == S3_SAVAGE4 && par->dvi)) + par->display_type = DISP_DFP; + else + par->display_type = DISP_CRT; + + /* Check LCD panel parrmation */ + + if (par->display_type == DISP_LCD) { + unsigned char cr6b = VGArCR(0x6b, par); + + int panelX = (VGArSEQ(0x61, par) + + ((VGArSEQ(0x66, par) & 0x02) << 7) + 1) * 8; + int panelY = (VGArSEQ(0x69, par) + + ((VGArSEQ(0x6e, par) & 0x70) << 4) + 1); + + char * sTechnology = "Unknown"; + + /* OK, I admit it. I don't know how to limit the max dot clock + * for LCD panels of various sizes. I thought I copied the + * formula from the BIOS, but many users have parrmed me of + * my folly. + * + * Instead, I'll abandon any attempt to automatically limit the + * clock, and add an LCDClock option to XF86Config. Some day, + * I should come back to this. + */ + + enum ACTIVE_DISPLAYS { /* These are the bits in CR6B */ + ActiveCRT = 0x01, + ActiveLCD = 0x02, + ActiveTV = 0x04, + ActiveCRT2 = 0x20, + ActiveDUO = 0x80 + }; + + if ((VGArSEQ(0x39, par) & 0x03) == 0) { + sTechnology = "TFT"; + } else if ((VGArSEQ(0x30, par) & 0x01) == 0) { + sTechnology = "DSTN"; + } else { + sTechnology = "STN"; + } + + printk(KERN_INFO "savagefb: %dx%d %s LCD panel detected %s\n", + panelX, panelY, sTechnology, + cr6b & ActiveLCD ? "and active" : "but not active"); + + if (cr6b & ActiveLCD) { + /* + * If the LCD is active and panel expansion is enabled, + * we probably want to kill the HW cursor. + */ + + printk(KERN_INFO "savagefb: Limiting video mode to " + "%dx%d\n", panelX, panelY); + + par->SavagePanelWidth = panelX; + par->SavagePanelHeight = panelY; + + } else + par->display_type = DISP_CRT; + } + + savage_get_default_par(par, &par->state); + par->save = par->state; + + if (S3_SAVAGE4_SERIES(par->chip)) { + /* + * The Savage4 and ProSavage have COB coherency bugs which + * render the buffer useless. We disable it. + */ + par->cob_index = 2; + par->cob_size = 0x8000 << par->cob_index; + par->cob_offset = videoRambytes; + } else { + /* We use 128kB for the COB on all chips. */ + + par->cob_index = 7; + par->cob_size = 0x400 << par->cob_index; + par->cob_offset = videoRambytes - par->cob_size; + } + + return videoRambytes; +} + +static int savage_init_fb_info(struct fb_info *info, struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct savagefb_par *par = info->par; + int err = 0; + + par->pcidev = dev; + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.ypanstep = 1; + info->fix.ywrapstep = 0; + info->fix.accel = id->driver_data; + + switch (info->fix.accel) { + case FB_ACCEL_SUPERSAVAGE: + par->chip = S3_SUPERSAVAGE; + snprintf(info->fix.id, 16, "SuperSavage"); + break; + case FB_ACCEL_SAVAGE4: + par->chip = S3_SAVAGE4; + snprintf(info->fix.id, 16, "Savage4"); + break; + case FB_ACCEL_SAVAGE3D: + par->chip = S3_SAVAGE3D; + snprintf(info->fix.id, 16, "Savage3D"); + break; + case FB_ACCEL_SAVAGE3D_MV: + par->chip = S3_SAVAGE3D; + snprintf(info->fix.id, 16, "Savage3D-MV"); + break; + case FB_ACCEL_SAVAGE2000: + par->chip = S3_SAVAGE2000; + snprintf(info->fix.id, 16, "Savage2000"); + break; + case FB_ACCEL_SAVAGE_MX_MV: + par->chip = S3_SAVAGE_MX; + snprintf(info->fix.id, 16, "Savage/MX-MV"); + break; + case FB_ACCEL_SAVAGE_MX: + par->chip = S3_SAVAGE_MX; + snprintf(info->fix.id, 16, "Savage/MX"); + break; + case FB_ACCEL_SAVAGE_IX_MV: + par->chip = S3_SAVAGE_MX; + snprintf(info->fix.id, 16, "Savage/IX-MV"); + break; + case FB_ACCEL_SAVAGE_IX: + par->chip = S3_SAVAGE_MX; + snprintf(info->fix.id, 16, "Savage/IX"); + break; + case FB_ACCEL_PROSAVAGE_PM: + par->chip = S3_PROSAVAGE; + snprintf(info->fix.id, 16, "ProSavagePM"); + break; + case FB_ACCEL_PROSAVAGE_KM: + par->chip = S3_PROSAVAGE; + snprintf(info->fix.id, 16, "ProSavageKM"); + break; + case FB_ACCEL_S3TWISTER_P: + par->chip = S3_TWISTER; + snprintf(info->fix.id, 16, "TwisterP"); + break; + case FB_ACCEL_S3TWISTER_K: + par->chip = S3_TWISTER; + snprintf(info->fix.id, 16, "TwisterK"); + break; + case FB_ACCEL_PROSAVAGE_DDR: + par->chip = S3_PROSAVAGEDDR; + snprintf(info->fix.id, 16, "ProSavageDDR"); + break; + case FB_ACCEL_PROSAVAGE_DDRK: + par->chip = S3_PROSAVAGEDDR; + snprintf(info->fix.id, 16, "ProSavage8"); + break; + } + + if (S3_SAVAGE3D_SERIES(par->chip)) { + par->SavageWaitIdle = savage3D_waitidle; + par->SavageWaitFifo = savage3D_waitfifo; + } else if (S3_SAVAGE4_SERIES(par->chip) || + S3_SUPERSAVAGE == par->chip) { + par->SavageWaitIdle = savage4_waitidle; + par->SavageWaitFifo = savage4_waitfifo; + } else { + par->SavageWaitIdle = savage2000_waitidle; + par->SavageWaitFifo = savage2000_waitfifo; + } + + info->var.nonstd = 0; + info->var.activate = FB_ACTIVATE_NOW; + info->var.width = -1; + info->var.height = -1; + info->var.accel_flags = 0; + + info->fbops = &savagefb_ops; + info->flags = FBINFO_DEFAULT | + FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_XPAN; + + info->pseudo_palette = par->pseudo_palette; + +#if defined(CONFIG_FB_SAVAGE_ACCEL) + /* FIFO size + padding for commands */ + info->pixmap.addr = kcalloc(8, 1024, GFP_KERNEL); + + err = -ENOMEM; + if (info->pixmap.addr) { + info->pixmap.size = 8*1024; + info->pixmap.scan_align = 4; + info->pixmap.buf_align = 4; + info->pixmap.access_align = 32; + + err = fb_alloc_cmap(&info->cmap, NR_PALETTE, 0); + if (!err) + info->flags |= FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_IMAGEBLIT; + } +#endif + return err; +} + +/* --------------------------------------------------------------------- */ + +static int savagefb_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct fb_info *info; + struct savagefb_par *par; + u_int h_sync, v_sync; + int err, lpitch; + int video_len; + + DBG("savagefb_probe"); + + info = framebuffer_alloc(sizeof(struct savagefb_par), &dev->dev); + if (!info) + return -ENOMEM; + par = info->par; + mutex_init(&par->open_lock); + err = pci_enable_device(dev); + if (err) + goto failed_enable; + + if ((err = pci_request_regions(dev, "savagefb"))) { + printk(KERN_ERR "cannot request PCI regions\n"); + goto failed_enable; + } + + err = -ENOMEM; + + if ((err = savage_init_fb_info(info, dev, id))) + goto failed_init; + + err = savage_map_mmio(info); + if (err) + goto failed_mmio; + + video_len = savage_init_hw(par); + /* FIXME: can't be negative */ + if (video_len < 0) { + err = video_len; + goto failed_mmio; + } + + err = savage_map_video(info, video_len); + if (err) + goto failed_video; + + INIT_LIST_HEAD(&info->modelist); +#if defined(CONFIG_FB_SAVAGE_I2C) + savagefb_create_i2c_busses(info); + savagefb_probe_i2c_connector(info, &par->edid); + fb_edid_to_monspecs(par->edid, &info->monspecs); + kfree(par->edid); + fb_videomode_to_modelist(info->monspecs.modedb, + info->monspecs.modedb_len, + &info->modelist); +#endif + info->var = savagefb_var800x600x8; + /* if a panel was detected, default to a CVT mode instead */ + if (par->SavagePanelWidth) { + struct fb_videomode cvt_mode; + + memset(&cvt_mode, 0, sizeof(cvt_mode)); + cvt_mode.xres = par->SavagePanelWidth; + cvt_mode.yres = par->SavagePanelHeight; + cvt_mode.refresh = 60; + /* FIXME: if we know there is only the panel + * we can enable reduced blanking as well */ + if (fb_find_mode_cvt(&cvt_mode, 0, 0)) + printk(KERN_WARNING "No CVT mode found for panel\n"); + else if (fb_find_mode(&info->var, info, NULL, NULL, 0, + &cvt_mode, 0) != 3) + info->var = savagefb_var800x600x8; + } + + if (mode_option) { + fb_find_mode(&info->var, info, mode_option, + info->monspecs.modedb, info->monspecs.modedb_len, + NULL, 8); + } else if (info->monspecs.modedb != NULL) { + const struct fb_videomode *mode; + + mode = fb_find_best_display(&info->monspecs, &info->modelist); + savage_update_var(&info->var, mode); + } + + /* maximize virtual vertical length */ + lpitch = info->var.xres_virtual*((info->var.bits_per_pixel + 7) >> 3); + info->var.yres_virtual = info->fix.smem_len/lpitch; + + if (info->var.yres_virtual < info->var.yres) { + err = -ENOMEM; + goto failed; + } + +#if defined(CONFIG_FB_SAVAGE_ACCEL) + /* + * The clipping coordinates are masked with 0xFFF, so limit our + * virtual resolutions to these sizes. + */ + if (info->var.yres_virtual > 0x1000) + info->var.yres_virtual = 0x1000; + + if (info->var.xres_virtual > 0x1000) + info->var.xres_virtual = 0x1000; +#endif + savagefb_check_var(&info->var, info); + savagefb_set_fix(info); + + /* + * Calculate the hsync and vsync frequencies. Note that + * we split the 1e12 constant up so that we can preserve + * the precision and fit the results into 32-bit registers. + * (1953125000 * 512 = 1e12) + */ + h_sync = 1953125000 / info->var.pixclock; + h_sync = h_sync * 512 / (info->var.xres + info->var.left_margin + + info->var.right_margin + + info->var.hsync_len); + v_sync = h_sync / (info->var.yres + info->var.upper_margin + + info->var.lower_margin + info->var.vsync_len); + + printk(KERN_INFO "savagefb v" SAVAGEFB_VERSION ": " + "%dkB VRAM, using %dx%d, %d.%03dkHz, %dHz\n", + info->fix.smem_len >> 10, + info->var.xres, info->var.yres, + h_sync / 1000, h_sync % 1000, v_sync); + + + fb_destroy_modedb(info->monspecs.modedb); + info->monspecs.modedb = NULL; + + err = register_framebuffer(info); + if (err < 0) + goto failed; + + printk(KERN_INFO "fb: S3 %s frame buffer device\n", + info->fix.id); + + /* + * Our driver data + */ + pci_set_drvdata(dev, info); + + return 0; + + failed: +#ifdef CONFIG_FB_SAVAGE_I2C + savagefb_delete_i2c_busses(info); +#endif + fb_alloc_cmap(&info->cmap, 0, 0); + savage_unmap_video(info); + failed_video: + savage_unmap_mmio(info); + failed_mmio: + kfree(info->pixmap.addr); + failed_init: + pci_release_regions(dev); + failed_enable: + framebuffer_release(info); + + return err; +} + +static void savagefb_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + + DBG("savagefb_remove"); + + if (info) { + /* + * If unregister_framebuffer fails, then + * we will be leaving hooks that could cause + * oopsen laying around. + */ + if (unregister_framebuffer(info)) + printk(KERN_WARNING "savagefb: danger danger! " + "Oopsen imminent!\n"); + +#ifdef CONFIG_FB_SAVAGE_I2C + savagefb_delete_i2c_busses(info); +#endif + fb_alloc_cmap(&info->cmap, 0, 0); + savage_unmap_video(info); + savage_unmap_mmio(info); + kfree(info->pixmap.addr); + pci_release_regions(dev); + framebuffer_release(info); + } +} + +static int savagefb_suspend(struct pci_dev *dev, pm_message_t mesg) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct savagefb_par *par = info->par; + + DBG("savagefb_suspend"); + + if (mesg.event == PM_EVENT_PRETHAW) + mesg.event = PM_EVENT_FREEZE; + par->pm_state = mesg.event; + dev->dev.power.power_state = mesg; + + /* + * For PM_EVENT_FREEZE, do not power down so the console + * can remain active. + */ + if (mesg.event == PM_EVENT_FREEZE) + return 0; + + console_lock(); + fb_set_suspend(info, 1); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + savagefb_blank(FB_BLANK_POWERDOWN, info); + savage_set_default_par(par, &par->save); + savage_disable_mmio(par); + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, mesg)); + console_unlock(); + + return 0; +} + +static int savagefb_resume(struct pci_dev* dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct savagefb_par *par = info->par; + int cur_state = par->pm_state; + + DBG("savage_resume"); + + par->pm_state = PM_EVENT_ON; + + /* + * The adapter was not powered down coming back from a + * PM_EVENT_FREEZE. + */ + if (cur_state == PM_EVENT_FREEZE) { + pci_set_power_state(dev, PCI_D0); + return 0; + } + + console_lock(); + + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + + if (pci_enable_device(dev)) + DBG("err"); + + pci_set_master(dev); + savage_enable_mmio(par); + savage_init_hw(par); + savagefb_set_par(info); + fb_set_suspend(info, 0); + savagefb_blank(FB_BLANK_UNBLANK, info); + console_unlock(); + + return 0; +} + + +static struct pci_device_id savagefb_devices[] = { + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_MX128, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_MX64, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_MX64C, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_IX128SDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_IX128DDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_IX64SDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_IX64DDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_IXCSDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SUPSAV_IXCDDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SUPERSAVAGE}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE4, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE4}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE3D, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE3D}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE3D_MV, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE3D_MV}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE2000, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE2000}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE_MX_MV, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE_MX_MV}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE_MX, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE_MX}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE_IX_MV, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE_IX_MV}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_SAVAGE_IX, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_SAVAGE_IX}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_PROSAVAGE_PM, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_PROSAVAGE_PM}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_PROSAVAGE_KM, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_PROSAVAGE_KM}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_S3TWISTER_P, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_S3TWISTER_P}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_S3TWISTER_K, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_S3TWISTER_K}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_PROSAVAGE_DDR, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_PROSAVAGE_DDR}, + + {PCI_VENDOR_ID_S3, PCI_CHIP_PROSAVAGE_DDRK, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_PROSAVAGE_DDRK}, + + {0, 0, 0, 0, 0, 0, 0} +}; + +MODULE_DEVICE_TABLE(pci, savagefb_devices); + +static struct pci_driver savagefb_driver = { + .name = "savagefb", + .id_table = savagefb_devices, + .probe = savagefb_probe, + .suspend = savagefb_suspend, + .resume = savagefb_resume, + .remove = savagefb_remove, +}; + +/* **************************** exit-time only **************************** */ + +static void __exit savage_done(void) +{ + DBG("savage_done"); + pci_unregister_driver(&savagefb_driver); +} + + +/* ************************* init in-kernel code ************************** */ + +static int __init savagefb_setup(char *options) +{ +#ifndef MODULE + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + mode_option = this_opt; + } +#endif /* !MODULE */ + return 0; +} + +static int __init savagefb_init(void) +{ + char *option; + + DBG("savagefb_init"); + + if (fb_get_options("savagefb", &option)) + return -ENODEV; + + savagefb_setup(option); + return pci_register_driver(&savagefb_driver); + +} + +module_init(savagefb_init); +module_exit(savage_done); + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Specify initial video mode"); diff --git a/drivers/video/fbdev/sbuslib.c b/drivers/video/fbdev/sbuslib.c new file mode 100644 index 000000000000..a350209ffbd3 --- /dev/null +++ b/drivers/video/fbdev/sbuslib.c @@ -0,0 +1,267 @@ +/* sbuslib.c: Helper library for SBUS framebuffer drivers. + * + * Copyright (C) 2003 David S. Miller (davem@redhat.com) + */ + +#include <linux/compat.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/uaccess.h> +#include <linux/of_device.h> + +#include <asm/fbio.h> + +#include "sbuslib.h" + +void sbusfb_fill_var(struct fb_var_screeninfo *var, struct device_node *dp, + int bpp) +{ + memset(var, 0, sizeof(*var)); + + var->xres = of_getintprop_default(dp, "width", 1152); + var->yres = of_getintprop_default(dp, "height", 900); + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + var->bits_per_pixel = bpp; +} + +EXPORT_SYMBOL(sbusfb_fill_var); + +static unsigned long sbusfb_mmapsize(long size, unsigned long fbsize) +{ + if (size == SBUS_MMAP_EMPTY) return 0; + if (size >= 0) return size; + return fbsize * (-size); +} + +int sbusfb_mmap_helper(struct sbus_mmap_map *map, + unsigned long physbase, + unsigned long fbsize, + unsigned long iospace, + struct vm_area_struct *vma) +{ + unsigned int size, page, r, map_size; + unsigned long map_offset = 0; + unsigned long off; + int i; + + if (!(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) + return -EINVAL; + + size = vma->vm_end - vma->vm_start; + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + + off = vma->vm_pgoff << PAGE_SHIFT; + + /* VM_IO | VM_DONTEXPAND | VM_DONTDUMP are set by remap_pfn_range() */ + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Each page, see which map applies */ + for (page = 0; page < size; ){ + map_size = 0; + for (i = 0; map[i].size; i++) + if (map[i].voff == off+page) { + map_size = sbusfb_mmapsize(map[i].size, fbsize); +#ifdef __sparc_v9__ +#define POFF_MASK (PAGE_MASK|0x1UL) +#else +#define POFF_MASK (PAGE_MASK) +#endif + map_offset = (physbase + map[i].poff) & POFF_MASK; + break; + } + if (!map_size) { + page += PAGE_SIZE; + continue; + } + if (page + map_size > size) + map_size = size - page; + r = io_remap_pfn_range(vma, + vma->vm_start + page, + MK_IOSPACE_PFN(iospace, + map_offset >> PAGE_SHIFT), + map_size, + vma->vm_page_prot); + if (r) + return -EAGAIN; + page += map_size; + } + + return 0; +} +EXPORT_SYMBOL(sbusfb_mmap_helper); + +int sbusfb_ioctl_helper(unsigned long cmd, unsigned long arg, + struct fb_info *info, + int type, int fb_depth, unsigned long fb_size) +{ + switch(cmd) { + case FBIOGTYPE: { + struct fbtype __user *f = (struct fbtype __user *) arg; + + if (put_user(type, &f->fb_type) || + __put_user(info->var.yres, &f->fb_height) || + __put_user(info->var.xres, &f->fb_width) || + __put_user(fb_depth, &f->fb_depth) || + __put_user(0, &f->fb_cmsize) || + __put_user(fb_size, &f->fb_cmsize)) + return -EFAULT; + return 0; + } + case FBIOPUTCMAP_SPARC: { + struct fbcmap __user *c = (struct fbcmap __user *) arg; + struct fb_cmap cmap; + u16 red, green, blue; + u8 red8, green8, blue8; + unsigned char __user *ured; + unsigned char __user *ugreen; + unsigned char __user *ublue; + int index, count, i; + + if (get_user(index, &c->index) || + __get_user(count, &c->count) || + __get_user(ured, &c->red) || + __get_user(ugreen, &c->green) || + __get_user(ublue, &c->blue)) + return -EFAULT; + + cmap.len = 1; + cmap.red = &red; + cmap.green = &green; + cmap.blue = &blue; + cmap.transp = NULL; + for (i = 0; i < count; i++) { + int err; + + if (get_user(red8, &ured[i]) || + get_user(green8, &ugreen[i]) || + get_user(blue8, &ublue[i])) + return -EFAULT; + + red = red8 << 8; + green = green8 << 8; + blue = blue8 << 8; + + cmap.start = index + i; + err = fb_set_cmap(&cmap, info); + if (err) + return err; + } + return 0; + } + case FBIOGETCMAP_SPARC: { + struct fbcmap __user *c = (struct fbcmap __user *) arg; + unsigned char __user *ured; + unsigned char __user *ugreen; + unsigned char __user *ublue; + struct fb_cmap *cmap = &info->cmap; + int index, count, i; + u8 red, green, blue; + + if (get_user(index, &c->index) || + __get_user(count, &c->count) || + __get_user(ured, &c->red) || + __get_user(ugreen, &c->green) || + __get_user(ublue, &c->blue)) + return -EFAULT; + + if (index + count > cmap->len) + return -EINVAL; + + for (i = 0; i < count; i++) { + red = cmap->red[index + i] >> 8; + green = cmap->green[index + i] >> 8; + blue = cmap->blue[index + i] >> 8; + if (put_user(red, &ured[i]) || + put_user(green, &ugreen[i]) || + put_user(blue, &ublue[i])) + return -EFAULT; + } + return 0; + } + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(sbusfb_ioctl_helper); + +#ifdef CONFIG_COMPAT +static int fbiogetputcmap(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + struct fbcmap32 __user *argp = (void __user *)arg; + struct fbcmap __user *p = compat_alloc_user_space(sizeof(*p)); + u32 addr; + int ret; + + ret = copy_in_user(p, argp, 2 * sizeof(int)); + ret |= get_user(addr, &argp->red); + ret |= put_user(compat_ptr(addr), &p->red); + ret |= get_user(addr, &argp->green); + ret |= put_user(compat_ptr(addr), &p->green); + ret |= get_user(addr, &argp->blue); + ret |= put_user(compat_ptr(addr), &p->blue); + if (ret) + return -EFAULT; + return info->fbops->fb_ioctl(info, + (cmd == FBIOPUTCMAP32) ? + FBIOPUTCMAP_SPARC : FBIOGETCMAP_SPARC, + (unsigned long)p); +} + +static int fbiogscursor(struct fb_info *info, unsigned long arg) +{ + struct fbcursor __user *p = compat_alloc_user_space(sizeof(*p)); + struct fbcursor32 __user *argp = (void __user *)arg; + compat_uptr_t addr; + int ret; + + ret = copy_in_user(p, argp, + 2 * sizeof (short) + 2 * sizeof(struct fbcurpos)); + ret |= copy_in_user(&p->size, &argp->size, sizeof(struct fbcurpos)); + ret |= copy_in_user(&p->cmap, &argp->cmap, 2 * sizeof(int)); + ret |= get_user(addr, &argp->cmap.red); + ret |= put_user(compat_ptr(addr), &p->cmap.red); + ret |= get_user(addr, &argp->cmap.green); + ret |= put_user(compat_ptr(addr), &p->cmap.green); + ret |= get_user(addr, &argp->cmap.blue); + ret |= put_user(compat_ptr(addr), &p->cmap.blue); + ret |= get_user(addr, &argp->mask); + ret |= put_user(compat_ptr(addr), &p->mask); + ret |= get_user(addr, &argp->image); + ret |= put_user(compat_ptr(addr), &p->image); + if (ret) + return -EFAULT; + return info->fbops->fb_ioctl(info, FBIOSCURSOR, (unsigned long)p); +} + +int sbusfb_compat_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FBIOGTYPE: + case FBIOSATTR: + case FBIOGATTR: + case FBIOSVIDEO: + case FBIOGVIDEO: + case FBIOGCURSOR32: /* This is not implemented yet. + Later it should be converted... */ + case FBIOSCURPOS: + case FBIOGCURPOS: + case FBIOGCURMAX: + return info->fbops->fb_ioctl(info, cmd, arg); + case FBIOPUTCMAP32: + return fbiogetputcmap(info, cmd, arg); + case FBIOGETCMAP32: + return fbiogetputcmap(info, cmd, arg); + case FBIOSCURSOR32: + return fbiogscursor(info, arg); + default: + return -ENOIOCTLCMD; + } +} +EXPORT_SYMBOL(sbusfb_compat_ioctl); +#endif diff --git a/drivers/video/fbdev/sbuslib.h b/drivers/video/fbdev/sbuslib.h new file mode 100644 index 000000000000..7ba3250236bd --- /dev/null +++ b/drivers/video/fbdev/sbuslib.h @@ -0,0 +1,27 @@ +/* sbuslib.h: SBUS fb helper library interfaces */ +#ifndef _SBUSLIB_H +#define _SBUSLIB_H + +struct sbus_mmap_map { + unsigned long voff; + unsigned long poff; + unsigned long size; +}; + +#define SBUS_MMAP_FBSIZE(n) (-n) +#define SBUS_MMAP_EMPTY 0x80000000 + +extern void sbusfb_fill_var(struct fb_var_screeninfo *var, + struct device_node *dp, int bpp); +struct vm_area_struct; +extern int sbusfb_mmap_helper(struct sbus_mmap_map *map, + unsigned long physbase, unsigned long fbsize, + unsigned long iospace, + struct vm_area_struct *vma); +int sbusfb_ioctl_helper(unsigned long cmd, unsigned long arg, + struct fb_info *info, + int type, int fb_depth, unsigned long fb_size); +int sbusfb_compat_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); + +#endif /* _SBUSLIB_H */ diff --git a/drivers/video/fbdev/sh7760fb.c b/drivers/video/fbdev/sh7760fb.c new file mode 100644 index 000000000000..1265b25f9f99 --- /dev/null +++ b/drivers/video/fbdev/sh7760fb.c @@ -0,0 +1,591 @@ +/* + * SH7760/SH7763 LCDC Framebuffer driver. + * + * (c) 2006-2008 MSC Vertriebsges.m.b.H., + * Manuel Lauss <mano@roarinelk.homelinux.net> + * (c) 2008 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.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. + * + * PLEASE HAVE A LOOK AT Documentation/fb/sh7760fb.txt! + * + * Thanks to Siegfried Schaefer <s.schaefer at schaefer-edv.de> + * for his original source and testing! + * + * sh7760_setcolreg get from drivers/video/sh_mobile_lcdcfb.c + */ + +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <asm/sh7760fb.h> + +struct sh7760fb_par { + void __iomem *base; + int irq; + + struct sh7760fb_platdata *pd; /* display information */ + + dma_addr_t fbdma; /* physical address */ + + int rot; /* rotation enabled? */ + + u32 pseudo_palette[16]; + + struct platform_device *dev; + struct resource *ioarea; + struct completion vsync; /* vsync irq event */ +}; + +static irqreturn_t sh7760fb_irq(int irq, void *data) +{ + struct completion *c = data; + + complete(c); + + return IRQ_HANDLED; +} + +/* wait_for_lps - wait until power supply has reached a certain state. */ +static int wait_for_lps(struct sh7760fb_par *par, int val) +{ + int i = 100; + while (--i && ((ioread16(par->base + LDPMMR) & 3) != val)) + msleep(1); + + if (i <= 0) + return -ETIMEDOUT; + + return 0; +} + +/* en/disable the LCDC */ +static int sh7760fb_blank(int blank, struct fb_info *info) +{ + struct sh7760fb_par *par = info->par; + struct sh7760fb_platdata *pd = par->pd; + unsigned short cntr = ioread16(par->base + LDCNTR); + unsigned short intr = ioread16(par->base + LDINTR); + int lps; + + if (blank == FB_BLANK_UNBLANK) { + intr |= VINT_START; + cntr = LDCNTR_DON2 | LDCNTR_DON; + lps = 3; + } else { + intr &= ~VINT_START; + cntr = LDCNTR_DON2; + lps = 0; + } + + if (pd->blank) + pd->blank(blank); + + iowrite16(intr, par->base + LDINTR); + iowrite16(cntr, par->base + LDCNTR); + + return wait_for_lps(par, lps); +} + +static int sh7760_setcolreg (u_int regno, + u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + u32 *palette = info->pseudo_palette; + + if (regno >= 16) + return -EINVAL; + + /* only FB_VISUAL_TRUECOLOR supported */ + + red >>= 16 - info->var.red.length; + green >>= 16 - info->var.green.length; + blue >>= 16 - info->var.blue.length; + transp >>= 16 - info->var.transp.length; + + palette[regno] = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + return 0; +} + +static int sh7760fb_get_color_info(struct device *dev, + u16 lddfr, int *bpp, int *gray) +{ + int lbpp, lgray; + + lgray = lbpp = 0; + + switch (lddfr & LDDFR_COLOR_MASK) { + case LDDFR_1BPP_MONO: + lgray = 1; + lbpp = 1; + break; + case LDDFR_2BPP_MONO: + lgray = 1; + lbpp = 2; + break; + case LDDFR_4BPP_MONO: + lgray = 1; + case LDDFR_4BPP: + lbpp = 4; + break; + case LDDFR_6BPP_MONO: + lgray = 1; + case LDDFR_8BPP: + lbpp = 8; + break; + case LDDFR_16BPP_RGB555: + case LDDFR_16BPP_RGB565: + lbpp = 16; + lgray = 0; + break; + default: + dev_dbg(dev, "unsupported LDDFR bit depth.\n"); + return -EINVAL; + } + + if (bpp) + *bpp = lbpp; + if (gray) + *gray = lgray; + + return 0; +} + +static int sh7760fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct sh7760fb_par *par = info->par; + int ret, bpp; + + /* get color info from register value */ + ret = sh7760fb_get_color_info(info->dev, par->pd->lddfr, &bpp, NULL); + if (ret) + return ret; + + var->bits_per_pixel = bpp; + + if ((var->grayscale) && (var->bits_per_pixel == 1)) + fix->visual = FB_VISUAL_MONO10; + else if (var->bits_per_pixel >= 15) + fix->visual = FB_VISUAL_TRUECOLOR; + else + fix->visual = FB_VISUAL_PSEUDOCOLOR; + + /* TODO: add some more validation here */ + return 0; +} + +/* + * sh7760fb_set_par - set videomode. + * + * NOTE: The rotation, grayscale and DSTN codepaths are + * totally untested! + */ +static int sh7760fb_set_par(struct fb_info *info) +{ + struct sh7760fb_par *par = info->par; + struct fb_videomode *vm = par->pd->def_mode; + unsigned long sbase, dstn_off, ldsarl, stride; + unsigned short hsynp, hsynw, htcn, hdcn; + unsigned short vsynp, vsynw, vtln, vdln; + unsigned short lddfr, ldmtr; + int ret, bpp, gray; + + par->rot = par->pd->rotate; + + /* rotate only works with xres <= 320 */ + if (par->rot && (vm->xres > 320)) { + dev_dbg(info->dev, "rotation disabled due to display size\n"); + par->rot = 0; + } + + /* calculate LCDC reg vals from display parameters */ + hsynp = vm->right_margin + vm->xres; + hsynw = vm->hsync_len; + htcn = vm->left_margin + hsynp + hsynw; + hdcn = vm->xres; + vsynp = vm->lower_margin + vm->yres; + vsynw = vm->vsync_len; + vtln = vm->upper_margin + vsynp + vsynw; + vdln = vm->yres; + + /* get color info from register value */ + ret = sh7760fb_get_color_info(info->dev, par->pd->lddfr, &bpp, &gray); + if (ret) + return ret; + + dev_dbg(info->dev, "%dx%d %dbpp %s (orientation %s)\n", hdcn, + vdln, bpp, gray ? "grayscale" : "color", + par->rot ? "rotated" : "normal"); + +#ifdef CONFIG_CPU_LITTLE_ENDIAN + lddfr = par->pd->lddfr | (1 << 8); +#else + lddfr = par->pd->lddfr & ~(1 << 8); +#endif + + ldmtr = par->pd->ldmtr; + + if (!(vm->sync & FB_SYNC_HOR_HIGH_ACT)) + ldmtr |= LDMTR_CL1POL; + if (!(vm->sync & FB_SYNC_VERT_HIGH_ACT)) + ldmtr |= LDMTR_FLMPOL; + + /* shut down LCDC before changing display parameters */ + sh7760fb_blank(FB_BLANK_POWERDOWN, info); + + iowrite16(par->pd->ldickr, par->base + LDICKR); /* pixclock */ + iowrite16(ldmtr, par->base + LDMTR); /* polarities */ + iowrite16(lddfr, par->base + LDDFR); /* color/depth */ + iowrite16((par->rot ? 1 << 13 : 0), par->base + LDSMR); /* rotate */ + iowrite16(par->pd->ldpmmr, par->base + LDPMMR); /* Power Management */ + iowrite16(par->pd->ldpspr, par->base + LDPSPR); /* Power Supply Ctrl */ + + /* display resolution */ + iowrite16(((htcn >> 3) - 1) | (((hdcn >> 3) - 1) << 8), + par->base + LDHCNR); + iowrite16(vdln - 1, par->base + LDVDLNR); + iowrite16(vtln - 1, par->base + LDVTLNR); + /* h/v sync signals */ + iowrite16((vsynp - 1) | ((vsynw - 1) << 12), par->base + LDVSYNR); + iowrite16(((hsynp >> 3) - 1) | (((hsynw >> 3) - 1) << 12), + par->base + LDHSYNR); + /* AC modulation sig */ + iowrite16(par->pd->ldaclnr, par->base + LDACLNR); + + stride = (par->rot) ? vtln : hdcn; + if (!gray) + stride *= (bpp + 7) >> 3; + else { + if (bpp == 1) + stride >>= 3; + else if (bpp == 2) + stride >>= 2; + else if (bpp == 4) + stride >>= 1; + /* 6 bpp == 8 bpp */ + } + + /* if rotated, stride must be power of 2 */ + if (par->rot) { + unsigned long bit = 1 << 31; + while (bit) { + if (stride & bit) + break; + bit >>= 1; + } + if (stride & ~bit) + stride = bit << 1; /* not P-o-2, round up */ + } + iowrite16(stride, par->base + LDLAOR); + + /* set display mem start address */ + sbase = (unsigned long)par->fbdma; + if (par->rot) + sbase += (hdcn - 1) * stride; + + iowrite32(sbase, par->base + LDSARU); + + /* + * for DSTN need to set address for lower half. + * I (mlau) don't know which address to set it to, + * so I guessed at (stride * yres/2). + */ + if (((ldmtr & 0x003f) >= LDMTR_DSTN_MONO_8) && + ((ldmtr & 0x003f) <= LDMTR_DSTN_COLOR_16)) { + + dev_dbg(info->dev, " ***** DSTN untested! *****\n"); + + dstn_off = stride; + if (par->rot) + dstn_off *= hdcn >> 1; + else + dstn_off *= vdln >> 1; + + ldsarl = sbase + dstn_off; + } else + ldsarl = 0; + + iowrite32(ldsarl, par->base + LDSARL); /* mem for lower half of DSTN */ + + info->fix.line_length = stride; + + sh7760fb_check_var(&info->var, info); + + sh7760fb_blank(FB_BLANK_UNBLANK, info); /* panel on! */ + + dev_dbg(info->dev, "hdcn : %6d htcn : %6d\n", hdcn, htcn); + dev_dbg(info->dev, "hsynw : %6d hsynp : %6d\n", hsynw, hsynp); + dev_dbg(info->dev, "vdln : %6d vtln : %6d\n", vdln, vtln); + dev_dbg(info->dev, "vsynw : %6d vsynp : %6d\n", vsynw, vsynp); + dev_dbg(info->dev, "clksrc: %6d clkdiv: %6d\n", + (par->pd->ldickr >> 12) & 3, par->pd->ldickr & 0x1f); + dev_dbg(info->dev, "ldpmmr: 0x%04x ldpspr: 0x%04x\n", par->pd->ldpmmr, + par->pd->ldpspr); + dev_dbg(info->dev, "ldmtr : 0x%04x lddfr : 0x%04x\n", ldmtr, lddfr); + dev_dbg(info->dev, "ldlaor: %ld\n", stride); + dev_dbg(info->dev, "ldsaru: 0x%08lx ldsarl: 0x%08lx\n", sbase, ldsarl); + + return 0; +} + +static struct fb_ops sh7760fb_ops = { + .owner = THIS_MODULE, + .fb_blank = sh7760fb_blank, + .fb_check_var = sh7760fb_check_var, + .fb_setcolreg = sh7760_setcolreg, + .fb_set_par = sh7760fb_set_par, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static void sh7760fb_free_mem(struct fb_info *info) +{ + struct sh7760fb_par *par = info->par; + + if (!info->screen_base) + return; + + dma_free_coherent(info->dev, info->screen_size, + info->screen_base, par->fbdma); + + par->fbdma = 0; + info->screen_base = NULL; + info->screen_size = 0; +} + +/* allocate the framebuffer memory. This memory must be in Area3, + * (dictated by the DMA engine) and contiguous, at a 512 byte boundary. + */ +static int sh7760fb_alloc_mem(struct fb_info *info) +{ + struct sh7760fb_par *par = info->par; + void *fbmem; + unsigned long vram; + int ret, bpp; + + if (info->screen_base) + return 0; + + /* get color info from register value */ + ret = sh7760fb_get_color_info(info->dev, par->pd->lddfr, &bpp, NULL); + if (ret) { + printk(KERN_ERR "colinfo\n"); + return ret; + } + + /* min VRAM: xres_min = 16, yres_min = 1, bpp = 1: 2byte -> 1 page + max VRAM: xres_max = 1024, yres_max = 1024, bpp = 16: 2MB */ + + vram = info->var.xres * info->var.yres; + if (info->var.grayscale) { + if (bpp == 1) + vram >>= 3; + else if (bpp == 2) + vram >>= 2; + else if (bpp == 4) + vram >>= 1; + } else if (bpp > 8) + vram *= 2; + if ((vram < 1) || (vram > 1024 * 2048)) { + dev_dbg(info->dev, "too much VRAM required. Check settings\n"); + return -ENODEV; + } + + if (vram < PAGE_SIZE) + vram = PAGE_SIZE; + + fbmem = dma_alloc_coherent(info->dev, vram, &par->fbdma, GFP_KERNEL); + + if (!fbmem) + return -ENOMEM; + + if ((par->fbdma & SH7760FB_DMA_MASK) != SH7760FB_DMA_MASK) { + sh7760fb_free_mem(info); + dev_err(info->dev, "kernel gave me memory at 0x%08lx, which is" + "unusable for the LCDC\n", (unsigned long)par->fbdma); + return -ENOMEM; + } + + info->screen_base = fbmem; + info->screen_size = vram; + info->fix.smem_start = (unsigned long)info->screen_base; + info->fix.smem_len = info->screen_size; + + return 0; +} + +static int sh7760fb_probe(struct platform_device *pdev) +{ + struct fb_info *info; + struct resource *res; + struct sh7760fb_par *par; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(res == NULL)) { + dev_err(&pdev->dev, "invalid resource\n"); + return -EINVAL; + } + + info = framebuffer_alloc(sizeof(struct sh7760fb_par), &pdev->dev); + if (!info) + return -ENOMEM; + + par = info->par; + par->dev = pdev; + + par->pd = pdev->dev.platform_data; + if (!par->pd) { + dev_dbg(info->dev, "no display setup data!\n"); + ret = -ENODEV; + goto out_fb; + } + + par->ioarea = request_mem_region(res->start, + resource_size(res), pdev->name); + if (!par->ioarea) { + dev_err(&pdev->dev, "mmio area busy\n"); + ret = -EBUSY; + goto out_fb; + } + + par->base = ioremap_nocache(res->start, resource_size(res)); + if (!par->base) { + dev_err(&pdev->dev, "cannot remap\n"); + ret = -ENODEV; + goto out_res; + } + + iowrite16(0, par->base + LDINTR); /* disable vsync irq */ + par->irq = platform_get_irq(pdev, 0); + if (par->irq >= 0) { + ret = request_irq(par->irq, sh7760fb_irq, 0, + "sh7760-lcdc", &par->vsync); + if (ret) { + dev_err(&pdev->dev, "cannot grab IRQ\n"); + par->irq = -ENXIO; + } else + disable_irq_nosync(par->irq); + } + + fb_videomode_to_var(&info->var, par->pd->def_mode); + + ret = sh7760fb_alloc_mem(info); + if (ret) { + dev_dbg(info->dev, "framebuffer memory allocation failed!\n"); + goto out_unmap; + } + + info->pseudo_palette = par->pseudo_palette; + + /* fixup color register bitpositions. These are fixed by hardware */ + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.red.msb_right = 0; + + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.green.msb_right = 0; + + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->var.blue.msb_right = 0; + + info->var.transp.offset = 0; + info->var.transp.length = 0; + info->var.transp.msb_right = 0; + + strcpy(info->fix.id, "sh7760-lcdc"); + + /* set the DON2 bit now, before cmap allocation, as it will randomize + * palette memory. + */ + iowrite16(LDCNTR_DON2, par->base + LDCNTR); + info->fbops = &sh7760fb_ops; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) { + dev_dbg(info->dev, "Unable to allocate cmap memory\n"); + goto out_mem; + } + + ret = register_framebuffer(info); + if (ret < 0) { + dev_dbg(info->dev, "cannot register fb!\n"); + goto out_cmap; + } + platform_set_drvdata(pdev, info); + + printk(KERN_INFO "%s: memory at phys 0x%08lx-0x%08lx, size %ld KiB\n", + pdev->name, + (unsigned long)par->fbdma, + (unsigned long)(par->fbdma + info->screen_size - 1), + info->screen_size >> 10); + + return 0; + +out_cmap: + sh7760fb_blank(FB_BLANK_POWERDOWN, info); + fb_dealloc_cmap(&info->cmap); +out_mem: + sh7760fb_free_mem(info); +out_unmap: + if (par->irq >= 0) + free_irq(par->irq, &par->vsync); + iounmap(par->base); +out_res: + release_mem_region(res->start, resource_size(res)); +out_fb: + framebuffer_release(info); + return ret; +} + +static int sh7760fb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct sh7760fb_par *par = info->par; + + sh7760fb_blank(FB_BLANK_POWERDOWN, info); + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + sh7760fb_free_mem(info); + if (par->irq >= 0) + free_irq(par->irq, &par->vsync); + iounmap(par->base); + release_mem_region(par->ioarea->start, resource_size(par->ioarea)); + framebuffer_release(info); + + return 0; +} + +static struct platform_driver sh7760_lcdc_driver = { + .driver = { + .name = "sh7760-lcdc", + .owner = THIS_MODULE, + }, + .probe = sh7760fb_probe, + .remove = sh7760fb_remove, +}; + +module_platform_driver(sh7760_lcdc_driver); + +MODULE_AUTHOR("Nobuhiro Iwamatsu, Manuel Lauss"); +MODULE_DESCRIPTION("FBdev for SH7760/63 integrated LCD Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/sh_mipi_dsi.c b/drivers/video/fbdev/sh_mipi_dsi.c new file mode 100644 index 000000000000..8f6e8ff620d4 --- /dev/null +++ b/drivers/video/fbdev/sh_mipi_dsi.c @@ -0,0 +1,587 @@ +/* + * Renesas SH-mobile MIPI DSI support + * + * Copyright (C) 2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * This is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + */ + +#include <linux/bitmap.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/module.h> + +#include <video/mipi_display.h> +#include <video/sh_mipi_dsi.h> +#include <video/sh_mobile_lcdc.h> + +#include "sh_mobile_lcdcfb.h" + +#define SYSCTRL 0x0000 +#define SYSCONF 0x0004 +#define TIMSET 0x0008 +#define RESREQSET0 0x0018 +#define RESREQSET1 0x001c +#define HSTTOVSET 0x0020 +#define LPRTOVSET 0x0024 +#define TATOVSET 0x0028 +#define PRTOVSET 0x002c +#define DSICTRL 0x0030 +#define DSIINTE 0x0060 +#define PHYCTRL 0x0070 + +/* relative to linkbase */ +#define DTCTR 0x0000 +#define VMCTR1 0x0020 +#define VMCTR2 0x0024 +#define VMLEN1 0x0028 +#define VMLEN2 0x002c +#define CMTSRTREQ 0x0070 +#define CMTSRTCTR 0x00d0 + +/* E.g., sh7372 has 2 MIPI-DSIs - one for each LCDC */ +#define MAX_SH_MIPI_DSI 2 + +struct sh_mipi { + struct sh_mobile_lcdc_entity entity; + + void __iomem *base; + void __iomem *linkbase; + struct clk *dsit_clk; + struct platform_device *pdev; +}; + +#define to_sh_mipi(e) container_of(e, struct sh_mipi, entity) + +static struct sh_mipi *mipi_dsi[MAX_SH_MIPI_DSI]; + +/* Protect the above array */ +static DEFINE_MUTEX(array_lock); + +static struct sh_mipi *sh_mipi_by_handle(int handle) +{ + if (handle >= ARRAY_SIZE(mipi_dsi) || handle < 0) + return NULL; + + return mipi_dsi[handle]; +} + +static int sh_mipi_send_short(struct sh_mipi *mipi, u8 dsi_cmd, + u8 cmd, u8 param) +{ + u32 data = (dsi_cmd << 24) | (cmd << 16) | (param << 8); + int cnt = 100; + + /* transmit a short packet to LCD panel */ + iowrite32(1 | data, mipi->linkbase + CMTSRTCTR); + iowrite32(1, mipi->linkbase + CMTSRTREQ); + + while ((ioread32(mipi->linkbase + CMTSRTREQ) & 1) && --cnt) + udelay(1); + + return cnt ? 0 : -ETIMEDOUT; +} + +#define LCD_CHAN2MIPI(c) ((c) < LCDC_CHAN_MAINLCD || (c) > LCDC_CHAN_SUBLCD ? \ + -EINVAL : (c) - 1) + +static int sh_mipi_dcs(int handle, u8 cmd) +{ + struct sh_mipi *mipi = sh_mipi_by_handle(LCD_CHAN2MIPI(handle)); + if (!mipi) + return -ENODEV; + return sh_mipi_send_short(mipi, MIPI_DSI_DCS_SHORT_WRITE, cmd, 0); +} + +static int sh_mipi_dcs_param(int handle, u8 cmd, u8 param) +{ + struct sh_mipi *mipi = sh_mipi_by_handle(LCD_CHAN2MIPI(handle)); + if (!mipi) + return -ENODEV; + return sh_mipi_send_short(mipi, MIPI_DSI_DCS_SHORT_WRITE_PARAM, cmd, + param); +} + +static void sh_mipi_dsi_enable(struct sh_mipi *mipi, bool enable) +{ + /* + * enable LCDC data tx, transition to LPS after completion of each HS + * packet + */ + iowrite32(0x00000002 | enable, mipi->linkbase + DTCTR); +} + +static void sh_mipi_shutdown(struct platform_device *pdev) +{ + struct sh_mipi *mipi = to_sh_mipi(platform_get_drvdata(pdev)); + + sh_mipi_dsi_enable(mipi, false); +} + +static int sh_mipi_setup(struct sh_mipi *mipi, const struct fb_videomode *mode) +{ + void __iomem *base = mipi->base; + struct sh_mipi_dsi_info *pdata = mipi->pdev->dev.platform_data; + u32 pctype, datatype, pixfmt, linelength, vmctr2; + u32 tmp, top, bottom, delay, div; + int bpp; + + /* + * Select data format. MIPI DSI is not hot-pluggable, so, we just use + * the default videomode. If this ever becomes a problem, We'll have to + * move this to mipi_display_on() above and use info->var.xres + */ + switch (pdata->data_format) { + case MIPI_RGB888: + pctype = 0; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_RGB565: + pctype = 1; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_RGB666_LP: + pctype = 2; + datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_RGB666: + pctype = 3; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; + pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; + linelength = (mode->xres * 18 + 7) / 8; + break; + case MIPI_BGR888: + pctype = 8; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_BGR565: + pctype = 9; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_BGR666_LP: + pctype = 0xa; + datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_BGR666: + pctype = 0xb; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; + pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; + linelength = (mode->xres * 18 + 7) / 8; + break; + case MIPI_YUYV: + pctype = 4; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_UYVY: + pctype = 5; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_YUV420_L: + pctype = 6; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12; + pixfmt = MIPI_DCS_PIXEL_FMT_12BIT; + linelength = (mode->xres * 12 + 7) / 8; + break; + case MIPI_YUV420: + pctype = 7; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12; + pixfmt = MIPI_DCS_PIXEL_FMT_12BIT; + /* Length of U/V line */ + linelength = (mode->xres + 1) / 2; + break; + default: + return -EINVAL; + } + + if (!pdata->lane) + return -EINVAL; + + /* reset DSI link */ + iowrite32(0x00000001, base + SYSCTRL); + /* Hold reset for 100 cycles of the slowest of bus, HS byte and LP clock */ + udelay(50); + iowrite32(0x00000000, base + SYSCTRL); + + /* setup DSI link */ + + /* + * T_wakeup = 0x7000 + * T_hs-trail = 3 + * T_hs-prepare = 3 + * T_clk-trail = 3 + * T_clk-prepare = 2 + */ + iowrite32(0x70003332, base + TIMSET); + /* no responses requested */ + iowrite32(0x00000000, base + RESREQSET0); + /* request response to packets of type 0x28 */ + iowrite32(0x00000100, base + RESREQSET1); + /* High-speed transmission timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + HSTTOVSET); + /* LP reception timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + LPRTOVSET); + /* Turn-around timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + TATOVSET); + /* Peripheral reset timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + PRTOVSET); + /* Interrupts not used, disable all */ + iowrite32(0, base + DSIINTE); + /* DSI-Tx bias on */ + iowrite32(0x00000001, base + PHYCTRL); + udelay(200); + /* Deassert resets, power on */ + iowrite32(0x03070001 | pdata->phyctrl, base + PHYCTRL); + + /* + * Default = ULPS enable | + * Contention detection enabled | + * EoT packet transmission enable | + * CRC check enable | + * ECC check enable + */ + bitmap_fill((unsigned long *)&tmp, pdata->lane); + tmp |= 0x00003700; + iowrite32(tmp, base + SYSCONF); + + /* setup l-bridge */ + + /* + * Enable transmission of all packets, + * transmit LPS after each HS packet completion + */ + iowrite32(0x00000006, mipi->linkbase + DTCTR); + /* VSYNC width = 2 (<< 17) */ + iowrite32((mode->vsync_len << pdata->vsynw_offset) | + (pdata->clksrc << 16) | (pctype << 12) | datatype, + mipi->linkbase + VMCTR1); + + /* + * Non-burst mode with sync pulses: VSE and HSE are output, + * HSA period allowed, no commands in LP + */ + vmctr2 = 0; + if (pdata->flags & SH_MIPI_DSI_VSEE) + vmctr2 |= 1 << 23; + if (pdata->flags & SH_MIPI_DSI_HSEE) + vmctr2 |= 1 << 22; + if (pdata->flags & SH_MIPI_DSI_HSAE) + vmctr2 |= 1 << 21; + if (pdata->flags & SH_MIPI_DSI_BL2E) + vmctr2 |= 1 << 17; + if (pdata->flags & SH_MIPI_DSI_HSABM) + vmctr2 |= 1 << 5; + if (pdata->flags & SH_MIPI_DSI_HBPBM) + vmctr2 |= 1 << 4; + if (pdata->flags & SH_MIPI_DSI_HFPBM) + vmctr2 |= 1 << 3; + iowrite32(vmctr2, mipi->linkbase + VMCTR2); + + /* + * VMLEN1 = RGBLEN | HSALEN + * + * see + * Video mode - Blanking Packet setting + */ + top = linelength << 16; /* RGBLEN */ + bottom = 0x00000001; + if (pdata->flags & SH_MIPI_DSI_HSABM) /* HSALEN */ + bottom = (pdata->lane * mode->hsync_len) - 10; + iowrite32(top | bottom , mipi->linkbase + VMLEN1); + + /* + * VMLEN2 = HBPLEN | HFPLEN + * + * see + * Video mode - Blanking Packet setting + */ + top = 0x00010000; + bottom = 0x00000001; + delay = 0; + + div = 1; /* HSbyteCLK is calculation base + * HS4divCLK = HSbyteCLK/2 + * HS6divCLK is not supported for now */ + if (pdata->flags & SH_MIPI_DSI_HS4divCLK) + div = 2; + + if (pdata->flags & SH_MIPI_DSI_HFPBM) { /* HBPLEN */ + top = mode->hsync_len + mode->left_margin; + top = ((pdata->lane * top / div) - 10) << 16; + } + if (pdata->flags & SH_MIPI_DSI_HBPBM) { /* HFPLEN */ + bottom = mode->right_margin; + bottom = (pdata->lane * bottom / div) - 12; + } + + bpp = linelength / mode->xres; /* byte / pixel */ + if ((pdata->lane / div) > bpp) { + tmp = mode->xres / bpp; /* output cycle */ + tmp = mode->xres - tmp; /* (input - output) cycle */ + delay = (pdata->lane * tmp); + } + + iowrite32(top | (bottom + delay) , mipi->linkbase + VMLEN2); + + msleep(5); + + /* setup LCD panel */ + + /* cf. drivers/video/omap/lcd_mipid.c */ + sh_mipi_dcs(pdata->channel, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(120); + /* + * [7] - Page Address Mode + * [6] - Column Address Mode + * [5] - Page / Column Address Mode + * [4] - Display Device Line Refresh Order + * [3] - RGB/BGR Order + * [2] - Display Data Latch Data Order + * [1] - Flip Horizontal + * [0] - Flip Vertical + */ + sh_mipi_dcs_param(pdata->channel, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + /* cf. set_data_lines() */ + sh_mipi_dcs_param(pdata->channel, MIPI_DCS_SET_PIXEL_FORMAT, + pixfmt << 4); + sh_mipi_dcs(pdata->channel, MIPI_DCS_SET_DISPLAY_ON); + + /* Enable timeout counters */ + iowrite32(0x00000f00, base + DSICTRL); + + return 0; +} + +static int mipi_display_on(struct sh_mobile_lcdc_entity *entity) +{ + struct sh_mipi *mipi = to_sh_mipi(entity); + struct sh_mipi_dsi_info *pdata = mipi->pdev->dev.platform_data; + int ret; + + pm_runtime_get_sync(&mipi->pdev->dev); + + ret = pdata->set_dot_clock(mipi->pdev, mipi->base, 1); + if (ret < 0) + goto mipi_display_on_fail1; + + ret = sh_mipi_setup(mipi, &entity->def_mode); + if (ret < 0) + goto mipi_display_on_fail2; + + sh_mipi_dsi_enable(mipi, true); + + return SH_MOBILE_LCDC_DISPLAY_CONNECTED; + +mipi_display_on_fail1: + pm_runtime_put_sync(&mipi->pdev->dev); +mipi_display_on_fail2: + pdata->set_dot_clock(mipi->pdev, mipi->base, 0); + + return ret; +} + +static void mipi_display_off(struct sh_mobile_lcdc_entity *entity) +{ + struct sh_mipi *mipi = to_sh_mipi(entity); + struct sh_mipi_dsi_info *pdata = mipi->pdev->dev.platform_data; + + sh_mipi_dsi_enable(mipi, false); + + pdata->set_dot_clock(mipi->pdev, mipi->base, 0); + + pm_runtime_put_sync(&mipi->pdev->dev); +} + +static const struct sh_mobile_lcdc_entity_ops mipi_ops = { + .display_on = mipi_display_on, + .display_off = mipi_display_off, +}; + +static int __init sh_mipi_probe(struct platform_device *pdev) +{ + struct sh_mipi *mipi; + struct sh_mipi_dsi_info *pdata = pdev->dev.platform_data; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct resource *res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + unsigned long rate, f_current; + int idx = pdev->id, ret; + + if (!res || !res2 || idx >= ARRAY_SIZE(mipi_dsi) || !pdata) + return -ENODEV; + + if (!pdata->set_dot_clock) + return -EINVAL; + + mutex_lock(&array_lock); + if (idx < 0) + for (idx = 0; idx < ARRAY_SIZE(mipi_dsi) && mipi_dsi[idx]; idx++) + ; + + if (idx == ARRAY_SIZE(mipi_dsi)) { + ret = -EBUSY; + goto efindslot; + } + + mipi = kzalloc(sizeof(*mipi), GFP_KERNEL); + if (!mipi) { + ret = -ENOMEM; + goto ealloc; + } + + mipi->entity.owner = THIS_MODULE; + mipi->entity.ops = &mipi_ops; + + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + dev_err(&pdev->dev, "MIPI register region already claimed\n"); + ret = -EBUSY; + goto ereqreg; + } + + mipi->base = ioremap(res->start, resource_size(res)); + if (!mipi->base) { + ret = -ENOMEM; + goto emap; + } + + if (!request_mem_region(res2->start, resource_size(res2), pdev->name)) { + dev_err(&pdev->dev, "MIPI register region 2 already claimed\n"); + ret = -EBUSY; + goto ereqreg2; + } + + mipi->linkbase = ioremap(res2->start, resource_size(res2)); + if (!mipi->linkbase) { + ret = -ENOMEM; + goto emap2; + } + + mipi->pdev = pdev; + + mipi->dsit_clk = clk_get(&pdev->dev, "dsit_clk"); + if (IS_ERR(mipi->dsit_clk)) { + ret = PTR_ERR(mipi->dsit_clk); + goto eclktget; + } + + f_current = clk_get_rate(mipi->dsit_clk); + /* 80MHz required by the datasheet */ + rate = clk_round_rate(mipi->dsit_clk, 80000000); + if (rate > 0 && rate != f_current) + ret = clk_set_rate(mipi->dsit_clk, rate); + else + ret = rate; + if (ret < 0) + goto esettrate; + + dev_dbg(&pdev->dev, "DSI-T clk %lu -> %lu\n", f_current, rate); + + ret = clk_enable(mipi->dsit_clk); + if (ret < 0) + goto eclkton; + + mipi_dsi[idx] = mipi; + + pm_runtime_enable(&pdev->dev); + pm_runtime_resume(&pdev->dev); + + mutex_unlock(&array_lock); + platform_set_drvdata(pdev, &mipi->entity); + + return 0; + +eclkton: +esettrate: + clk_put(mipi->dsit_clk); +eclktget: + iounmap(mipi->linkbase); +emap2: + release_mem_region(res2->start, resource_size(res2)); +ereqreg2: + iounmap(mipi->base); +emap: + release_mem_region(res->start, resource_size(res)); +ereqreg: + kfree(mipi); +ealloc: +efindslot: + mutex_unlock(&array_lock); + + return ret; +} + +static int sh_mipi_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct resource *res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + struct sh_mipi *mipi = to_sh_mipi(platform_get_drvdata(pdev)); + int i, ret; + + mutex_lock(&array_lock); + + for (i = 0; i < ARRAY_SIZE(mipi_dsi) && mipi_dsi[i] != mipi; i++) + ; + + if (i == ARRAY_SIZE(mipi_dsi)) { + ret = -EINVAL; + } else { + ret = 0; + mipi_dsi[i] = NULL; + } + + mutex_unlock(&array_lock); + + if (ret < 0) + return ret; + + pm_runtime_disable(&pdev->dev); + clk_disable(mipi->dsit_clk); + clk_put(mipi->dsit_clk); + + iounmap(mipi->linkbase); + if (res2) + release_mem_region(res2->start, resource_size(res2)); + iounmap(mipi->base); + if (res) + release_mem_region(res->start, resource_size(res)); + kfree(mipi); + + return 0; +} + +static struct platform_driver sh_mipi_driver = { + .remove = sh_mipi_remove, + .shutdown = sh_mipi_shutdown, + .driver = { + .name = "sh-mipi-dsi", + }, +}; + +module_platform_driver_probe(sh_mipi_driver, sh_mipi_probe); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("SuperH / ARM-shmobile MIPI DSI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/sh_mobile_hdmi.c b/drivers/video/fbdev/sh_mobile_hdmi.c new file mode 100644 index 000000000000..9a33ee0413fb --- /dev/null +++ b/drivers/video/fbdev/sh_mobile_hdmi.c @@ -0,0 +1,1449 @@ +/* + * SH-Mobile High-Definition Multimedia Interface (HDMI) driver + * for SLISHDMI13T and SLIPHDMIT IP cores + * + * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <video/sh_mobile_hdmi.h> +#include <video/sh_mobile_lcdc.h> + +#include "sh_mobile_lcdcfb.h" + +/* HDMI Core Control Register (HTOP0) */ +#define HDMI_SYSTEM_CTRL 0x00 /* System control */ +#define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control, + bits 19..16 of 20-bit N for Audio Clock Regeneration packet */ +#define HDMI_20_BIT_N_FOR_AUDIO_RPKT_15_8 0x02 /* bits 15..8 of 20-bit N for Audio Clock Regeneration packet */ +#define HDMI_20_BIT_N_FOR_AUDIO_RPKT_7_0 0x03 /* bits 7..0 of 20-bit N for Audio Clock Regeneration packet */ +#define HDMI_SPDIF_AUDIO_SAMP_FREQ_CTS 0x04 /* SPDIF audio sampling frequency, + bits 19..16 of Internal CTS */ +#define HDMI_INTERNAL_CTS_15_8 0x05 /* bits 15..8 of Internal CTS */ +#define HDMI_INTERNAL_CTS_7_0 0x06 /* bits 7..0 of Internal CTS */ +#define HDMI_EXTERNAL_CTS_19_16 0x07 /* External CTS */ +#define HDMI_EXTERNAL_CTS_15_8 0x08 /* External CTS */ +#define HDMI_EXTERNAL_CTS_7_0 0x09 /* External CTS */ +#define HDMI_AUDIO_SETTING_1 0x0A /* Audio setting.1 */ +#define HDMI_AUDIO_SETTING_2 0x0B /* Audio setting.2 */ +#define HDMI_I2S_AUDIO_SET 0x0C /* I2S audio setting */ +#define HDMI_DSD_AUDIO_SET 0x0D /* DSD audio setting */ +#define HDMI_DEBUG_MONITOR_1 0x0E /* Debug monitor.1 */ +#define HDMI_DEBUG_MONITOR_2 0x0F /* Debug monitor.2 */ +#define HDMI_I2S_INPUT_PIN_SWAP 0x10 /* I2S input pin swap */ +#define HDMI_AUDIO_STATUS_BITS_SETTING_1 0x11 /* Audio status bits setting.1 */ +#define HDMI_AUDIO_STATUS_BITS_SETTING_2 0x12 /* Audio status bits setting.2 */ +#define HDMI_CATEGORY_CODE 0x13 /* Category code */ +#define HDMI_SOURCE_NUM_AUDIO_WORD_LEN 0x14 /* Source number/Audio word length */ +#define HDMI_AUDIO_VIDEO_SETTING_1 0x15 /* Audio/Video setting.1 */ +#define HDMI_VIDEO_SETTING_1 0x16 /* Video setting.1 */ +#define HDMI_DEEP_COLOR_MODES 0x17 /* Deep Color Modes */ + +/* 12 16- and 10-bit Color space conversion parameters: 0x18..0x2f */ +#define HDMI_COLOR_SPACE_CONVERSION_PARAMETERS 0x18 + +#define HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS 0x30 /* External video parameter settings */ +#define HDMI_EXTERNAL_H_TOTAL_7_0 0x31 /* External horizontal total (LSB) */ +#define HDMI_EXTERNAL_H_TOTAL_11_8 0x32 /* External horizontal total (MSB) */ +#define HDMI_EXTERNAL_H_BLANK_7_0 0x33 /* External horizontal blank (LSB) */ +#define HDMI_EXTERNAL_H_BLANK_9_8 0x34 /* External horizontal blank (MSB) */ +#define HDMI_EXTERNAL_H_DELAY_7_0 0x35 /* External horizontal delay (LSB) */ +#define HDMI_EXTERNAL_H_DELAY_9_8 0x36 /* External horizontal delay (MSB) */ +#define HDMI_EXTERNAL_H_DURATION_7_0 0x37 /* External horizontal duration (LSB) */ +#define HDMI_EXTERNAL_H_DURATION_9_8 0x38 /* External horizontal duration (MSB) */ +#define HDMI_EXTERNAL_V_TOTAL_7_0 0x39 /* External vertical total (LSB) */ +#define HDMI_EXTERNAL_V_TOTAL_9_8 0x3A /* External vertical total (MSB) */ +#define HDMI_AUDIO_VIDEO_SETTING_2 0x3B /* Audio/Video setting.2 */ +#define HDMI_EXTERNAL_V_BLANK 0x3D /* External vertical blank */ +#define HDMI_EXTERNAL_V_DELAY 0x3E /* External vertical delay */ +#define HDMI_EXTERNAL_V_DURATION 0x3F /* External vertical duration */ +#define HDMI_CTRL_PKT_MANUAL_SEND_CONTROL 0x40 /* Control packet manual send control */ +#define HDMI_CTRL_PKT_AUTO_SEND 0x41 /* Control packet auto send with VSYNC control */ +#define HDMI_AUTO_CHECKSUM_OPTION 0x42 /* Auto checksum option */ +#define HDMI_VIDEO_SETTING_2 0x45 /* Video setting.2 */ +#define HDMI_OUTPUT_OPTION 0x46 /* Output option */ +#define HDMI_SLIPHDMIT_PARAM_OPTION 0x51 /* SLIPHDMIT parameter option */ +#define HDMI_HSYNC_PMENT_AT_EMB_7_0 0x52 /* HSYNC placement at embedded sync (LSB) */ +#define HDMI_HSYNC_PMENT_AT_EMB_15_8 0x53 /* HSYNC placement at embedded sync (MSB) */ +#define HDMI_VSYNC_PMENT_AT_EMB_7_0 0x54 /* VSYNC placement at embedded sync (LSB) */ +#define HDMI_VSYNC_PMENT_AT_EMB_14_8 0x55 /* VSYNC placement at embedded sync (MSB) */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_1 0x56 /* SLIPHDMIT parameter settings.1 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_2 0x57 /* SLIPHDMIT parameter settings.2 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_3 0x58 /* SLIPHDMIT parameter settings.3 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_5 0x59 /* SLIPHDMIT parameter settings.5 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_6 0x5A /* SLIPHDMIT parameter settings.6 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_7 0x5B /* SLIPHDMIT parameter settings.7 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_8 0x5C /* SLIPHDMIT parameter settings.8 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_9 0x5D /* SLIPHDMIT parameter settings.9 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_10 0x5E /* SLIPHDMIT parameter settings.10 */ +#define HDMI_CTRL_PKT_BUF_INDEX 0x5F /* Control packet buffer index */ +#define HDMI_CTRL_PKT_BUF_ACCESS_HB0 0x60 /* Control packet data buffer access window - HB0 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_HB1 0x61 /* Control packet data buffer access window - HB1 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_HB2 0x62 /* Control packet data buffer access window - HB2 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB0 0x63 /* Control packet data buffer access window - PB0 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB1 0x64 /* Control packet data buffer access window - PB1 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB2 0x65 /* Control packet data buffer access window - PB2 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB3 0x66 /* Control packet data buffer access window - PB3 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB4 0x67 /* Control packet data buffer access window - PB4 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB5 0x68 /* Control packet data buffer access window - PB5 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB6 0x69 /* Control packet data buffer access window - PB6 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB7 0x6A /* Control packet data buffer access window - PB7 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB8 0x6B /* Control packet data buffer access window - PB8 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB9 0x6C /* Control packet data buffer access window - PB9 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB10 0x6D /* Control packet data buffer access window - PB10 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB11 0x6E /* Control packet data buffer access window - PB11 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB12 0x6F /* Control packet data buffer access window - PB12 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB13 0x70 /* Control packet data buffer access window - PB13 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB14 0x71 /* Control packet data buffer access window - PB14 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB15 0x72 /* Control packet data buffer access window - PB15 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB16 0x73 /* Control packet data buffer access window - PB16 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB17 0x74 /* Control packet data buffer access window - PB17 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB18 0x75 /* Control packet data buffer access window - PB18 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB19 0x76 /* Control packet data buffer access window - PB19 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB20 0x77 /* Control packet data buffer access window - PB20 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB21 0x78 /* Control packet data buffer access window - PB21 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB22 0x79 /* Control packet data buffer access window - PB22 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB23 0x7A /* Control packet data buffer access window - PB23 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB24 0x7B /* Control packet data buffer access window - PB24 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB25 0x7C /* Control packet data buffer access window - PB25 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB26 0x7D /* Control packet data buffer access window - PB26 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB27 0x7E /* Control packet data buffer access window - PB27 */ +#define HDMI_EDID_KSV_FIFO_ACCESS_WINDOW 0x80 /* EDID/KSV FIFO access window */ +#define HDMI_DDC_BUS_ACCESS_FREQ_CTRL_7_0 0x81 /* DDC bus access frequency control (LSB) */ +#define HDMI_DDC_BUS_ACCESS_FREQ_CTRL_15_8 0x82 /* DDC bus access frequency control (MSB) */ +#define HDMI_INTERRUPT_MASK_1 0x92 /* Interrupt mask.1 */ +#define HDMI_INTERRUPT_MASK_2 0x93 /* Interrupt mask.2 */ +#define HDMI_INTERRUPT_STATUS_1 0x94 /* Interrupt status.1 */ +#define HDMI_INTERRUPT_STATUS_2 0x95 /* Interrupt status.2 */ +#define HDMI_INTERRUPT_MASK_3 0x96 /* Interrupt mask.3 */ +#define HDMI_INTERRUPT_MASK_4 0x97 /* Interrupt mask.4 */ +#define HDMI_INTERRUPT_STATUS_3 0x98 /* Interrupt status.3 */ +#define HDMI_INTERRUPT_STATUS_4 0x99 /* Interrupt status.4 */ +#define HDMI_SOFTWARE_HDCP_CONTROL_1 0x9A /* Software HDCP control.1 */ +#define HDMI_FRAME_COUNTER 0x9C /* Frame counter */ +#define HDMI_FRAME_COUNTER_FOR_RI_CHECK 0x9D /* Frame counter for Ri check */ +#define HDMI_HDCP_CONTROL 0xAF /* HDCP control */ +#define HDMI_RI_FRAME_COUNT_REGISTER 0xB2 /* Ri frame count register */ +#define HDMI_DDC_BUS_CONTROL 0xB7 /* DDC bus control */ +#define HDMI_HDCP_STATUS 0xB8 /* HDCP status */ +#define HDMI_SHA0 0xB9 /* sha0 */ +#define HDMI_SHA1 0xBA /* sha1 */ +#define HDMI_SHA2 0xBB /* sha2 */ +#define HDMI_SHA3 0xBC /* sha3 */ +#define HDMI_SHA4 0xBD /* sha4 */ +#define HDMI_BCAPS_READ 0xBE /* BCAPS read / debug */ +#define HDMI_AKSV_BKSV_7_0_MONITOR 0xBF /* AKSV/BKSV[7:0] monitor */ +#define HDMI_AKSV_BKSV_15_8_MONITOR 0xC0 /* AKSV/BKSV[15:8] monitor */ +#define HDMI_AKSV_BKSV_23_16_MONITOR 0xC1 /* AKSV/BKSV[23:16] monitor */ +#define HDMI_AKSV_BKSV_31_24_MONITOR 0xC2 /* AKSV/BKSV[31:24] monitor */ +#define HDMI_AKSV_BKSV_39_32_MONITOR 0xC3 /* AKSV/BKSV[39:32] monitor */ +#define HDMI_EDID_SEGMENT_POINTER 0xC4 /* EDID segment pointer */ +#define HDMI_EDID_WORD_ADDRESS 0xC5 /* EDID word address */ +#define HDMI_EDID_DATA_FIFO_ADDRESS 0xC6 /* EDID data FIFO address */ +#define HDMI_NUM_OF_HDMI_DEVICES 0xC7 /* Number of HDMI devices */ +#define HDMI_HDCP_ERROR_CODE 0xC8 /* HDCP error code */ +#define HDMI_100MS_TIMER_SET 0xC9 /* 100ms timer setting */ +#define HDMI_5SEC_TIMER_SET 0xCA /* 5sec timer setting */ +#define HDMI_RI_READ_COUNT 0xCB /* Ri read count */ +#define HDMI_AN_SEED 0xCC /* An seed */ +#define HDMI_MAX_NUM_OF_RCIVRS_ALLOWED 0xCD /* Maximum number of receivers allowed */ +#define HDMI_HDCP_MEMORY_ACCESS_CONTROL_1 0xCE /* HDCP memory access control.1 */ +#define HDMI_HDCP_MEMORY_ACCESS_CONTROL_2 0xCF /* HDCP memory access control.2 */ +#define HDMI_HDCP_CONTROL_2 0xD0 /* HDCP Control 2 */ +#define HDMI_HDCP_KEY_MEMORY_CONTROL 0xD2 /* HDCP Key Memory Control */ +#define HDMI_COLOR_SPACE_CONV_CONFIG_1 0xD3 /* Color space conversion configuration.1 */ +#define HDMI_VIDEO_SETTING_3 0xD4 /* Video setting.3 */ +#define HDMI_RI_7_0 0xD5 /* Ri[7:0] */ +#define HDMI_RI_15_8 0xD6 /* Ri[15:8] */ +#define HDMI_PJ 0xD7 /* Pj */ +#define HDMI_SHA_RD 0xD8 /* sha_rd */ +#define HDMI_RI_7_0_SAVED 0xD9 /* Ri[7:0] saved */ +#define HDMI_RI_15_8_SAVED 0xDA /* Ri[15:8] saved */ +#define HDMI_PJ_SAVED 0xDB /* Pj saved */ +#define HDMI_NUM_OF_DEVICES 0xDC /* Number of devices */ +#define HDMI_HOT_PLUG_MSENS_STATUS 0xDF /* Hot plug/MSENS status */ +#define HDMI_BCAPS_WRITE 0xE0 /* bcaps */ +#define HDMI_BSTAT_7_0 0xE1 /* bstat[7:0] */ +#define HDMI_BSTAT_15_8 0xE2 /* bstat[15:8] */ +#define HDMI_BKSV_7_0 0xE3 /* bksv[7:0] */ +#define HDMI_BKSV_15_8 0xE4 /* bksv[15:8] */ +#define HDMI_BKSV_23_16 0xE5 /* bksv[23:16] */ +#define HDMI_BKSV_31_24 0xE6 /* bksv[31:24] */ +#define HDMI_BKSV_39_32 0xE7 /* bksv[39:32] */ +#define HDMI_AN_7_0 0xE8 /* An[7:0] */ +#define HDMI_AN_15_8 0xE9 /* An [15:8] */ +#define HDMI_AN_23_16 0xEA /* An [23:16] */ +#define HDMI_AN_31_24 0xEB /* An [31:24] */ +#define HDMI_AN_39_32 0xEC /* An [39:32] */ +#define HDMI_AN_47_40 0xED /* An [47:40] */ +#define HDMI_AN_55_48 0xEE /* An [55:48] */ +#define HDMI_AN_63_56 0xEF /* An [63:56] */ +#define HDMI_PRODUCT_ID 0xF0 /* Product ID */ +#define HDMI_REVISION_ID 0xF1 /* Revision ID */ +#define HDMI_TEST_MODE 0xFE /* Test mode */ + +/* HDMI Control Register (HTOP1) */ +#define HDMI_HTOP1_TEST_MODE 0x0000 /* Test mode */ +#define HDMI_HTOP1_VIDEO_INPUT 0x0008 /* VideoInput */ +#define HDMI_HTOP1_CORE_RSTN 0x000C /* CoreResetn */ +#define HDMI_HTOP1_PLLBW 0x0018 /* PLLBW */ +#define HDMI_HTOP1_CLK_TO_PHY 0x001C /* Clk to Phy */ +#define HDMI_HTOP1_VIDEO_INPUT2 0x0020 /* VideoInput2 */ +#define HDMI_HTOP1_TISEMP0_1 0x0024 /* tisemp0-1 */ +#define HDMI_HTOP1_TISEMP2_C 0x0028 /* tisemp2-c */ +#define HDMI_HTOP1_TISIDRV 0x002C /* tisidrv */ +#define HDMI_HTOP1_TISEN 0x0034 /* tisen */ +#define HDMI_HTOP1_TISDREN 0x0038 /* tisdren */ +#define HDMI_HTOP1_CISRANGE 0x003C /* cisrange */ +#define HDMI_HTOP1_ENABLE_SELECTOR 0x0040 /* Enable Selector */ +#define HDMI_HTOP1_MACRO_RESET 0x0044 /* Macro reset */ +#define HDMI_HTOP1_PLL_CALIBRATION 0x0048 /* PLL calibration */ +#define HDMI_HTOP1_RE_CALIBRATION 0x004C /* Re-calibration */ +#define HDMI_HTOP1_CURRENT 0x0050 /* Current */ +#define HDMI_HTOP1_PLL_LOCK_DETECT 0x0054 /* PLL lock detect */ +#define HDMI_HTOP1_PHY_TEST_MODE 0x0058 /* PHY Test Mode */ +#define HDMI_HTOP1_CLK_SET 0x0080 /* Clock Set */ +#define HDMI_HTOP1_DDC_FAIL_SAFE 0x0084 /* DDC fail safe */ +#define HDMI_HTOP1_PRBS 0x0088 /* PRBS */ +#define HDMI_HTOP1_EDID_AINC_CONTROL 0x008C /* EDID ainc Control */ +#define HDMI_HTOP1_HTOP_DCL_MODE 0x00FC /* Deep Coloer Mode */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF0 0x0100 /* Deep Color:FRC COEF0 */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF1 0x0104 /* Deep Color:FRC COEF1 */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF2 0x0108 /* Deep Color:FRC COEF2 */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF3 0x010C /* Deep Color:FRC COEF3 */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF0_C 0x0110 /* Deep Color:FRC COEF0C */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF1_C 0x0114 /* Deep Color:FRC COEF1C */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF2_C 0x0118 /* Deep Color:FRC COEF2C */ +#define HDMI_HTOP1_HTOP_DCL_FRC_COEF3_C 0x011C /* Deep Color:FRC COEF3C */ +#define HDMI_HTOP1_HTOP_DCL_FRC_MODE 0x0120 /* Deep Color:FRC Mode */ +#define HDMI_HTOP1_HTOP_DCL_RECT_START1 0x0124 /* Deep Color:Rect Start1 */ +#define HDMI_HTOP1_HTOP_DCL_RECT_SIZE1 0x0128 /* Deep Color:Rect Size1 */ +#define HDMI_HTOP1_HTOP_DCL_RECT_START2 0x012C /* Deep Color:Rect Start2 */ +#define HDMI_HTOP1_HTOP_DCL_RECT_SIZE2 0x0130 /* Deep Color:Rect Size2 */ +#define HDMI_HTOP1_HTOP_DCL_RECT_START3 0x0134 /* Deep Color:Rect Start3 */ +#define HDMI_HTOP1_HTOP_DCL_RECT_SIZE3 0x0138 /* Deep Color:Rect Size3 */ +#define HDMI_HTOP1_HTOP_DCL_RECT_START4 0x013C /* Deep Color:Rect Start4 */ +#define HDMI_HTOP1_HTOP_DCL_RECT_SIZE4 0x0140 /* Deep Color:Rect Size4 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y1_1 0x0144 /* Deep Color:Fil Para Y1_1 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y1_2 0x0148 /* Deep Color:Fil Para Y1_2 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB1_1 0x014C /* Deep Color:Fil Para CB1_1 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB1_2 0x0150 /* Deep Color:Fil Para CB1_2 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR1_1 0x0154 /* Deep Color:Fil Para CR1_1 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR1_2 0x0158 /* Deep Color:Fil Para CR1_2 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y2_1 0x015C /* Deep Color:Fil Para Y2_1 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y2_2 0x0160 /* Deep Color:Fil Para Y2_2 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB2_1 0x0164 /* Deep Color:Fil Para CB2_1 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB2_2 0x0168 /* Deep Color:Fil Para CB2_2 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR2_1 0x016C /* Deep Color:Fil Para CR2_1 */ +#define HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR2_2 0x0170 /* Deep Color:Fil Para CR2_2 */ +#define HDMI_HTOP1_HTOP_DCL_COR_PARA_Y1 0x0174 /* Deep Color:Cor Para Y1 */ +#define HDMI_HTOP1_HTOP_DCL_COR_PARA_CB1 0x0178 /* Deep Color:Cor Para CB1 */ +#define HDMI_HTOP1_HTOP_DCL_COR_PARA_CR1 0x017C /* Deep Color:Cor Para CR1 */ +#define HDMI_HTOP1_HTOP_DCL_COR_PARA_Y2 0x0180 /* Deep Color:Cor Para Y2 */ +#define HDMI_HTOP1_HTOP_DCL_COR_PARA_CB2 0x0184 /* Deep Color:Cor Para CB2 */ +#define HDMI_HTOP1_HTOP_DCL_COR_PARA_CR2 0x0188 /* Deep Color:Cor Para CR2 */ +#define HDMI_HTOP1_EDID_DATA_READ 0x0200 /* EDID Data Read 128Byte:0x03FC */ + +enum hotplug_state { + HDMI_HOTPLUG_DISCONNECTED, + HDMI_HOTPLUG_CONNECTED, + HDMI_HOTPLUG_EDID_DONE, +}; + +struct sh_hdmi { + struct sh_mobile_lcdc_entity entity; + + void __iomem *base; + void __iomem *htop1; + enum hotplug_state hp_state; /* hot-plug status */ + u8 preprogrammed_vic; /* use a pre-programmed VIC or + the external mode */ + u8 edid_block_addr; + u8 edid_segment_nr; + u8 edid_blocks; + struct clk *hdmi_clk; + struct device *dev; + struct delayed_work edid_work; + struct fb_videomode mode; + struct fb_monspecs monspec; + + /* register access functions */ + void (*write)(struct sh_hdmi *hdmi, u8 data, u8 reg); + u8 (*read)(struct sh_hdmi *hdmi, u8 reg); +}; + +#define entity_to_sh_hdmi(e) container_of(e, struct sh_hdmi, entity) + +static void __hdmi_write8(struct sh_hdmi *hdmi, u8 data, u8 reg) +{ + iowrite8(data, hdmi->base + reg); +} + +static u8 __hdmi_read8(struct sh_hdmi *hdmi, u8 reg) +{ + return ioread8(hdmi->base + reg); +} + +static void __hdmi_write32(struct sh_hdmi *hdmi, u8 data, u8 reg) +{ + iowrite32((u32)data, hdmi->base + (reg * 4)); + udelay(100); +} + +static u8 __hdmi_read32(struct sh_hdmi *hdmi, u8 reg) +{ + return (u8)ioread32(hdmi->base + (reg * 4)); +} + +static void hdmi_write(struct sh_hdmi *hdmi, u8 data, u8 reg) +{ + hdmi->write(hdmi, data, reg); +} + +static u8 hdmi_read(struct sh_hdmi *hdmi, u8 reg) +{ + return hdmi->read(hdmi, reg); +} + +static void hdmi_bit_set(struct sh_hdmi *hdmi, u8 mask, u8 data, u8 reg) +{ + u8 val = hdmi_read(hdmi, reg); + + val &= ~mask; + val |= (data & mask); + + hdmi_write(hdmi, val, reg); +} + +static void hdmi_htop1_write(struct sh_hdmi *hdmi, u32 data, u32 reg) +{ + iowrite32(data, hdmi->htop1 + reg); + udelay(100); +} + +static u32 hdmi_htop1_read(struct sh_hdmi *hdmi, u32 reg) +{ + return ioread32(hdmi->htop1 + reg); +} + +/* + * HDMI sound + */ +static unsigned int sh_hdmi_snd_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct sh_hdmi *hdmi = snd_soc_codec_get_drvdata(codec); + + return hdmi_read(hdmi, reg); +} + +static int sh_hdmi_snd_write(struct snd_soc_codec *codec, + unsigned int reg, + unsigned int value) +{ + struct sh_hdmi *hdmi = snd_soc_codec_get_drvdata(codec); + + hdmi_write(hdmi, value, reg); + return 0; +} + +static struct snd_soc_dai_driver sh_hdmi_dai = { + .name = "sh_mobile_hdmi-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}; + +static int sh_hdmi_snd_probe(struct snd_soc_codec *codec) +{ + dev_info(codec->dev, "SH Mobile HDMI Audio Codec"); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_sh_hdmi = { + .probe = sh_hdmi_snd_probe, + .read = sh_hdmi_snd_read, + .write = sh_hdmi_snd_write, +}; + +/* + * HDMI video + */ + +/* External video parameter settings */ +static void sh_hdmi_external_video_param(struct sh_hdmi *hdmi) +{ + struct fb_videomode *mode = &hdmi->mode; + u16 htotal, hblank, hdelay, vtotal, vblank, vdelay, voffset; + u8 sync = 0; + + htotal = mode->xres + mode->right_margin + mode->left_margin + + mode->hsync_len; + hdelay = mode->hsync_len + mode->left_margin; + hblank = mode->right_margin + hdelay; + + /* + * Vertical timing looks a bit different in Figure 18, + * but let's try the same first by setting offset = 0 + */ + vtotal = mode->yres + mode->upper_margin + mode->lower_margin + + mode->vsync_len; + vdelay = mode->vsync_len + mode->upper_margin; + vblank = mode->lower_margin + vdelay; + voffset = min(mode->upper_margin / 2, 6U); + + /* + * [3]: VSYNC polarity: Positive + * [2]: HSYNC polarity: Positive + * [1]: Interlace/Progressive: Progressive + * [0]: External video settings enable: used. + */ + if (mode->sync & FB_SYNC_HOR_HIGH_ACT) + sync |= 4; + if (mode->sync & FB_SYNC_VERT_HIGH_ACT) + sync |= 8; + + dev_dbg(hdmi->dev, "H: %u, %u, %u, %u; V: %u, %u, %u, %u; sync 0x%x\n", + htotal, hblank, hdelay, mode->hsync_len, + vtotal, vblank, vdelay, mode->vsync_len, sync); + + hdmi_write(hdmi, sync | (voffset << 4), HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS); + + hdmi_write(hdmi, htotal, HDMI_EXTERNAL_H_TOTAL_7_0); + hdmi_write(hdmi, htotal >> 8, HDMI_EXTERNAL_H_TOTAL_11_8); + + hdmi_write(hdmi, hblank, HDMI_EXTERNAL_H_BLANK_7_0); + hdmi_write(hdmi, hblank >> 8, HDMI_EXTERNAL_H_BLANK_9_8); + + hdmi_write(hdmi, hdelay, HDMI_EXTERNAL_H_DELAY_7_0); + hdmi_write(hdmi, hdelay >> 8, HDMI_EXTERNAL_H_DELAY_9_8); + + hdmi_write(hdmi, mode->hsync_len, HDMI_EXTERNAL_H_DURATION_7_0); + hdmi_write(hdmi, mode->hsync_len >> 8, HDMI_EXTERNAL_H_DURATION_9_8); + + hdmi_write(hdmi, vtotal, HDMI_EXTERNAL_V_TOTAL_7_0); + hdmi_write(hdmi, vtotal >> 8, HDMI_EXTERNAL_V_TOTAL_9_8); + + hdmi_write(hdmi, vblank, HDMI_EXTERNAL_V_BLANK); + + hdmi_write(hdmi, vdelay, HDMI_EXTERNAL_V_DELAY); + + hdmi_write(hdmi, mode->vsync_len, HDMI_EXTERNAL_V_DURATION); + + /* Set bit 0 of HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS here for external mode */ + if (!hdmi->preprogrammed_vic) + hdmi_write(hdmi, sync | 1 | (voffset << 4), + HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS); +} + +/** + * sh_hdmi_video_config() + */ +static void sh_hdmi_video_config(struct sh_hdmi *hdmi) +{ + /* + * [7:4]: Audio sampling frequency: 48kHz + * [3:1]: Input video format: RGB and YCbCr 4:4:4 (Y on Green) + * [0]: Internal/External DE select: internal + */ + hdmi_write(hdmi, 0x20, HDMI_AUDIO_VIDEO_SETTING_1); + + /* + * [7:6]: Video output format: RGB 4:4:4 + * [5:4]: Input video data width: 8 bit + * [3:1]: EAV/SAV location: channel 1 + * [0]: Video input color space: RGB + */ + hdmi_write(hdmi, 0x34, HDMI_VIDEO_SETTING_1); + + /* + * [7:6]: Together with bit [6] of HDMI_AUDIO_VIDEO_SETTING_2, which is + * left at 0 by default, this configures 24bpp and sets the Color Depth + * (CD) field in the General Control Packet + */ + hdmi_write(hdmi, 0x20, HDMI_DEEP_COLOR_MODES); +} + +/** + * sh_hdmi_audio_config() + */ +static void sh_hdmi_audio_config(struct sh_hdmi *hdmi) +{ + u8 data; + struct sh_mobile_hdmi_info *pdata = dev_get_platdata(hdmi->dev); + + /* + * [7:4] L/R data swap control + * [3:0] appropriate N[19:16] + */ + hdmi_write(hdmi, 0x00, HDMI_L_R_DATA_SWAP_CTRL_RPKT); + /* appropriate N[15:8] */ + hdmi_write(hdmi, 0x18, HDMI_20_BIT_N_FOR_AUDIO_RPKT_15_8); + /* appropriate N[7:0] */ + hdmi_write(hdmi, 0x00, HDMI_20_BIT_N_FOR_AUDIO_RPKT_7_0); + + /* [7:4] 48 kHz SPDIF not used */ + hdmi_write(hdmi, 0x20, HDMI_SPDIF_AUDIO_SAMP_FREQ_CTS); + + /* + * [6:5] set required down sampling rate if required + * [4:3] set required audio source + */ + switch (pdata->flags & HDMI_SND_SRC_MASK) { + default: + /* fall through */ + case HDMI_SND_SRC_I2S: + data = 0x0 << 3; + break; + case HDMI_SND_SRC_SPDIF: + data = 0x1 << 3; + break; + case HDMI_SND_SRC_DSD: + data = 0x2 << 3; + break; + case HDMI_SND_SRC_HBR: + data = 0x3 << 3; + break; + } + hdmi_write(hdmi, data, HDMI_AUDIO_SETTING_1); + + /* [3:0] set sending channel number for channel status */ + hdmi_write(hdmi, 0x40, HDMI_AUDIO_SETTING_2); + + /* + * [5:2] set valid I2S source input pin + * [1:0] set input I2S source mode + */ + hdmi_write(hdmi, 0x04, HDMI_I2S_AUDIO_SET); + + /* [7:4] set valid DSD source input pin */ + hdmi_write(hdmi, 0x00, HDMI_DSD_AUDIO_SET); + + /* [7:0] set appropriate I2S input pin swap settings if required */ + hdmi_write(hdmi, 0x00, HDMI_I2S_INPUT_PIN_SWAP); + + /* + * [7] set validity bit for channel status + * [3:0] set original sample frequency for channel status + */ + hdmi_write(hdmi, 0x00, HDMI_AUDIO_STATUS_BITS_SETTING_1); + + /* + * [7] set value for channel status + * [6] set value for channel status + * [5] set copyright bit for channel status + * [4:2] set additional information for channel status + * [1:0] set clock accuracy for channel status + */ + hdmi_write(hdmi, 0x00, HDMI_AUDIO_STATUS_BITS_SETTING_2); + + /* [7:0] set category code for channel status */ + hdmi_write(hdmi, 0x00, HDMI_CATEGORY_CODE); + + /* + * [7:4] set source number for channel status + * [3:0] set word length for channel status + */ + hdmi_write(hdmi, 0x00, HDMI_SOURCE_NUM_AUDIO_WORD_LEN); + + /* [7:4] set sample frequency for channel status */ + hdmi_write(hdmi, 0x20, HDMI_AUDIO_VIDEO_SETTING_1); +} + +/** + * sh_hdmi_phy_config() - configure the HDMI PHY for the used video mode + */ +static void sh_hdmi_phy_config(struct sh_hdmi *hdmi) +{ + if (hdmi->mode.pixclock < 10000) { + /* for 1080p8bit 148MHz */ + hdmi_write(hdmi, 0x1d, HDMI_SLIPHDMIT_PARAM_SETTINGS_1); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3); + hdmi_write(hdmi, 0x4c, HDMI_SLIPHDMIT_PARAM_SETTINGS_5); + hdmi_write(hdmi, 0x1e, HDMI_SLIPHDMIT_PARAM_SETTINGS_6); + hdmi_write(hdmi, 0x48, HDMI_SLIPHDMIT_PARAM_SETTINGS_7); + hdmi_write(hdmi, 0x0e, HDMI_SLIPHDMIT_PARAM_SETTINGS_8); + hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9); + hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10); + } else if (hdmi->mode.pixclock < 30000) { + /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */ + /* + * [1:0] Speed_A + * [3:2] Speed_B + * [4] PLLA_Bypass + * [6] DRV_TEST_EN + * [7] DRV_TEST_IN + */ + hdmi_write(hdmi, 0x0f, HDMI_SLIPHDMIT_PARAM_SETTINGS_1); + /* PLLB_CONFIG[17], PLLA_CONFIG[17] - not in PHY datasheet */ + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2); + /* + * [2:0] BGR_I_OFFSET + * [6:4] BGR_V_OFFSET + */ + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3); + /* PLLA_CONFIG[7:0]: VCO gain, VCO offset, LPF resistance[0] */ + hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5); + /* + * PLLA_CONFIG[15:8]: regulator voltage[0], CP current, + * LPF capacitance, LPF resistance[1] + */ + hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6); + /* PLLB_CONFIG[7:0]: LPF resistance[0], VCO offset, VCO gain */ + hdmi_write(hdmi, 0x4A, HDMI_SLIPHDMIT_PARAM_SETTINGS_7); + /* + * PLLB_CONFIG[15:8]: regulator voltage[0], CP current, + * LPF capacitance, LPF resistance[1] + */ + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_8); + /* DRV_CONFIG, PE_CONFIG */ + hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9); + /* + * [2:0] AMON_SEL (4 == LPF voltage) + * [4] PLLA_CONFIG[16] + * [5] PLLB_CONFIG[16] + */ + hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10); + } else { + /* for 480p8bit 27MHz */ + hdmi_write(hdmi, 0x19, HDMI_SLIPHDMIT_PARAM_SETTINGS_1); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3); + hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5); + hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6); + hdmi_write(hdmi, 0x48, HDMI_SLIPHDMIT_PARAM_SETTINGS_7); + hdmi_write(hdmi, 0x0F, HDMI_SLIPHDMIT_PARAM_SETTINGS_8); + hdmi_write(hdmi, 0x20, HDMI_SLIPHDMIT_PARAM_SETTINGS_9); + hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10); + } +} + +/** + * sh_hdmi_avi_infoframe_setup() - Auxiliary Video Information InfoFrame CONTROL PACKET + */ +static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) +{ + u8 vic; + + /* AVI InfoFrame */ + hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_INDEX); + + /* Packet Type = 0x82 */ + hdmi_write(hdmi, 0x82, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + + /* Version = 0x02 */ + hdmi_write(hdmi, 0x02, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + + /* Length = 13 (0x0D) */ + hdmi_write(hdmi, 0x0D, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* N. A. Checksum */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0); + + /* + * Y = RGB + * A0 = No Data + * B = Bar Data not valid + * S = No Data + */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB1); + + /* + * [7:6] C = Colorimetry: no data + * [5:4] M = 2: 16:9, 1: 4:3 Picture Aspect Ratio + * [3:0] R = 8: Active Frame Aspect Ratio: same as picture aspect ratio + */ + hdmi_write(hdmi, 0x28, HDMI_CTRL_PKT_BUF_ACCESS_PB2); + + /* + * ITC = No Data + * EC = xvYCC601 + * Q = Default (depends on video format) + * SC = No Known non_uniform Scaling + */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB3); + + /* + * VIC should be ignored if external config is used, so, we could just use 0, + * but play safe and use a valid value in any case just in case + */ + if (hdmi->preprogrammed_vic) + vic = hdmi->preprogrammed_vic; + else + vic = 4; + hdmi_write(hdmi, vic, HDMI_CTRL_PKT_BUF_ACCESS_PB4); + + /* PR = No Repetition */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB5); + + /* Line Number of End of Top Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB6); + + /* Line Number of End of Top Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB7); + + /* Line Number of Start of Bottom Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB8); + + /* Line Number of Start of Bottom Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB9); + + /* Pixel Number of End of Left Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB10); + + /* Pixel Number of End of Left Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB11); + + /* Pixel Number of Start of Right Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB12); + + /* Pixel Number of Start of Right Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB13); +} + +/** + * sh_hdmi_audio_infoframe_setup() - Audio InfoFrame of CONTROL PACKET + */ +static void sh_hdmi_audio_infoframe_setup(struct sh_hdmi *hdmi) +{ + /* Audio InfoFrame */ + hdmi_write(hdmi, 0x08, HDMI_CTRL_PKT_BUF_INDEX); + + /* Packet Type = 0x84 */ + hdmi_write(hdmi, 0x84, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + + /* Version Number = 0x01 */ + hdmi_write(hdmi, 0x01, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + + /* 0 Length = 10 (0x0A) */ + hdmi_write(hdmi, 0x0A, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* n. a. Checksum */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0); + + /* Audio Channel Count = Refer to Stream Header */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB1); + + /* Refer to Stream Header */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB2); + + /* Format depends on coding type (i.e. CT0...CT3) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB3); + + /* Speaker Channel Allocation = Front Right + Front Left */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB4); + + /* Level Shift Value = 0 dB, Down - mix is permitted or no information */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB5); + + /* Reserved (0) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB6); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB7); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB8); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB9); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB10); +} + +/** + * sh_hdmi_configure() - Initialise HDMI for output + */ +static void sh_hdmi_configure(struct sh_hdmi *hdmi) +{ + /* Configure video format */ + sh_hdmi_video_config(hdmi); + + /* Configure audio format */ + sh_hdmi_audio_config(hdmi); + + /* Configure PHY */ + sh_hdmi_phy_config(hdmi); + + /* Auxiliary Video Information (AVI) InfoFrame */ + sh_hdmi_avi_infoframe_setup(hdmi); + + /* Audio InfoFrame */ + sh_hdmi_audio_infoframe_setup(hdmi); + + /* + * Control packet auto send with VSYNC control: auto send + * General control, Gamut metadata, ISRC, and ACP packets + */ + hdmi_write(hdmi, 0x8E, HDMI_CTRL_PKT_AUTO_SEND); + + /* FIXME */ + msleep(10); + + /* PS mode b->d, reset PLLA and PLLB */ + hdmi_bit_set(hdmi, 0xFC, 0x4C, HDMI_SYSTEM_CTRL); + + udelay(10); + + hdmi_bit_set(hdmi, 0xFC, 0x40, HDMI_SYSTEM_CTRL); +} + +static unsigned long sh_hdmi_rate_error(struct sh_hdmi *hdmi, + const struct fb_videomode *mode, + unsigned long *hdmi_rate, unsigned long *parent_rate) +{ + unsigned long target = PICOS2KHZ(mode->pixclock) * 1000, rate_error; + struct sh_mobile_hdmi_info *pdata = dev_get_platdata(hdmi->dev); + + *hdmi_rate = clk_round_rate(hdmi->hdmi_clk, target); + if ((long)*hdmi_rate < 0) + *hdmi_rate = clk_get_rate(hdmi->hdmi_clk); + + rate_error = (long)*hdmi_rate > 0 ? abs(*hdmi_rate - target) : ULONG_MAX; + if (rate_error && pdata->clk_optimize_parent) + rate_error = pdata->clk_optimize_parent(target, hdmi_rate, parent_rate); + else if (clk_get_parent(hdmi->hdmi_clk)) + *parent_rate = clk_get_rate(clk_get_parent(hdmi->hdmi_clk)); + + dev_dbg(hdmi->dev, "%u-%u-%u-%u x %u-%u-%u-%u\n", + mode->left_margin, mode->xres, + mode->right_margin, mode->hsync_len, + mode->upper_margin, mode->yres, + mode->lower_margin, mode->vsync_len); + + dev_dbg(hdmi->dev, "\t@%lu(+/-%lu)Hz, e=%lu / 1000, r=%uHz, p=%luHz\n", target, + rate_error, rate_error ? 10000 / (10 * target / rate_error) : 0, + mode->refresh, *parent_rate); + + return rate_error; +} + +static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate, + unsigned long *parent_rate) +{ + struct sh_mobile_lcdc_chan *ch = hdmi->entity.lcdc; + const struct fb_videomode *mode, *found = NULL; + unsigned int f_width = 0, f_height = 0, f_refresh = 0; + unsigned long found_rate_error = ULONG_MAX; /* silly compiler... */ + bool scanning = false, preferred_bad = false; + bool use_edid_mode = false; + u8 edid[128]; + char *forced; + int i; + + /* Read EDID */ + dev_dbg(hdmi->dev, "Read back EDID code:"); + for (i = 0; i < 128; i++) { + edid[i] = (hdmi->htop1) ? + (u8)hdmi_htop1_read(hdmi, HDMI_HTOP1_EDID_DATA_READ + (i * 4)) : + hdmi_read(hdmi, HDMI_EDID_KSV_FIFO_ACCESS_WINDOW); +#ifdef DEBUG + if ((i % 16) == 0) { + printk(KERN_CONT "\n"); + printk(KERN_DEBUG "%02X | %02X", i, edid[i]); + } else { + printk(KERN_CONT " %02X", edid[i]); + } +#endif + } +#ifdef DEBUG + printk(KERN_CONT "\n"); +#endif + + if (!hdmi->edid_blocks) { + fb_edid_to_monspecs(edid, &hdmi->monspec); + hdmi->edid_blocks = edid[126] + 1; + + dev_dbg(hdmi->dev, "%d main modes, %d extension blocks\n", + hdmi->monspec.modedb_len, hdmi->edid_blocks - 1); + } else { + dev_dbg(hdmi->dev, "Extension %u detected, DTD start %u\n", + edid[0], edid[2]); + fb_edid_add_monspecs(edid, &hdmi->monspec); + } + + if (hdmi->edid_blocks > hdmi->edid_segment_nr * 2 + + (hdmi->edid_block_addr >> 7) + 1) { + /* More blocks to read */ + if (hdmi->edid_block_addr) { + hdmi->edid_block_addr = 0; + hdmi->edid_segment_nr++; + } else { + hdmi->edid_block_addr = 0x80; + } + /* Set EDID word address */ + hdmi_write(hdmi, hdmi->edid_block_addr, HDMI_EDID_WORD_ADDRESS); + /* Enable EDID interrupt */ + hdmi_write(hdmi, 0xC6, HDMI_INTERRUPT_MASK_1); + /* Set EDID segment pointer - starts reading EDID */ + hdmi_write(hdmi, hdmi->edid_segment_nr, HDMI_EDID_SEGMENT_POINTER); + return -EAGAIN; + } + + /* All E-EDID blocks ready */ + dev_dbg(hdmi->dev, "%d main and extended modes\n", hdmi->monspec.modedb_len); + + fb_get_options("sh_mobile_lcdc", &forced); + if (forced && *forced) { + /* Only primitive parsing so far */ + i = sscanf(forced, "%ux%u@%u", + &f_width, &f_height, &f_refresh); + if (i < 2) { + f_width = 0; + f_height = 0; + } else { + /* The user wants us to use the EDID data */ + scanning = true; + } + dev_dbg(hdmi->dev, "Forced mode %ux%u@%uHz\n", + f_width, f_height, f_refresh); + } + + /* Walk monitor modes to find the best or the exact match */ + for (i = 0, mode = hdmi->monspec.modedb; + i < hdmi->monspec.modedb_len && scanning; + i++, mode++) { + unsigned long rate_error; + + if (!f_width && !f_height) { + /* + * A parameter string "video=sh_mobile_lcdc:0x0" means + * use the preferred EDID mode. If it is rejected by + * .fb_check_var(), keep looking, until an acceptable + * one is found. + */ + if ((mode->flag & FB_MODE_IS_FIRST) || preferred_bad) + scanning = false; + else + continue; + } else if (f_width != mode->xres || f_height != mode->yres) { + /* No interest in unmatching modes */ + continue; + } + + rate_error = sh_hdmi_rate_error(hdmi, mode, hdmi_rate, parent_rate); + + if (scanning) { + if (f_refresh == mode->refresh || (!f_refresh && !rate_error)) + /* + * Exact match if either the refresh rate + * matches or it hasn't been specified and we've + * found a mode, for which we can configure the + * clock precisely + */ + scanning = false; + else if (found && found_rate_error <= rate_error) + /* + * We otherwise search for the closest matching + * clock rate - either if no refresh rate has + * been specified or we cannot find an exactly + * matching one + */ + continue; + } + + /* Check if supported: sufficient fb memory, supported clock-rate */ + if (ch && ch->notify && + ch->notify(ch, SH_MOBILE_LCDC_EVENT_DISPLAY_MODE, mode, + NULL)) { + scanning = true; + preferred_bad = true; + continue; + } + + found = mode; + found_rate_error = rate_error; + use_edid_mode = true; + } + + /* + * TODO 1: if no default mode is present, postpone running the config + * until after the LCDC channel is initialized. + * TODO 2: consider registering the HDMI platform device from the LCDC + * driver. + */ + if (!found && hdmi->entity.def_mode.xres != 0) { + found = &hdmi->entity.def_mode; + found_rate_error = sh_hdmi_rate_error(hdmi, found, hdmi_rate, + parent_rate); + } + + /* No cookie today */ + if (!found) + return -ENXIO; + + if (found->xres == 640 && found->yres == 480 && found->refresh == 60) + hdmi->preprogrammed_vic = 1; + else if (found->xres == 720 && found->yres == 480 && found->refresh == 60) + hdmi->preprogrammed_vic = 2; + else if (found->xres == 720 && found->yres == 576 && found->refresh == 50) + hdmi->preprogrammed_vic = 17; + else if (found->xres == 1280 && found->yres == 720 && found->refresh == 60) + hdmi->preprogrammed_vic = 4; + else if (found->xres == 1920 && found->yres == 1080 && found->refresh == 24) + hdmi->preprogrammed_vic = 32; + else if (found->xres == 1920 && found->yres == 1080 && found->refresh == 50) + hdmi->preprogrammed_vic = 31; + else if (found->xres == 1920 && found->yres == 1080 && found->refresh == 60) + hdmi->preprogrammed_vic = 16; + else + hdmi->preprogrammed_vic = 0; + + dev_dbg(hdmi->dev, "Using %s %s mode %ux%u@%uHz (%luHz), " + "clock error %luHz\n", use_edid_mode ? "EDID" : "default", + hdmi->preprogrammed_vic ? "VIC" : "external", found->xres, + found->yres, found->refresh, PICOS2KHZ(found->pixclock) * 1000, + found_rate_error); + + hdmi->mode = *found; + sh_hdmi_external_video_param(hdmi); + + return 0; +} + +static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id) +{ + struct sh_hdmi *hdmi = dev_id; + u8 status1, status2, mask1, mask2; + + /* mode_b and PLLA and PLLB reset */ + hdmi_bit_set(hdmi, 0xFC, 0x2C, HDMI_SYSTEM_CTRL); + + /* How long shall reset be held? */ + udelay(10); + + /* mode_b and PLLA and PLLB reset release */ + hdmi_bit_set(hdmi, 0xFC, 0x20, HDMI_SYSTEM_CTRL); + + status1 = hdmi_read(hdmi, HDMI_INTERRUPT_STATUS_1); + status2 = hdmi_read(hdmi, HDMI_INTERRUPT_STATUS_2); + + mask1 = hdmi_read(hdmi, HDMI_INTERRUPT_MASK_1); + mask2 = hdmi_read(hdmi, HDMI_INTERRUPT_MASK_2); + + /* Correct would be to ack only set bits, but the datasheet requires 0xff */ + hdmi_write(hdmi, 0xFF, HDMI_INTERRUPT_STATUS_1); + hdmi_write(hdmi, 0xFF, HDMI_INTERRUPT_STATUS_2); + + if (printk_ratelimit()) + dev_dbg(hdmi->dev, "IRQ #%d: Status #1: 0x%x & 0x%x, #2: 0x%x & 0x%x\n", + irq, status1, mask1, status2, mask2); + + if (!((status1 & mask1) | (status2 & mask2))) { + return IRQ_NONE; + } else if (status1 & 0xc0) { + u8 msens; + + /* Datasheet specifies 10ms... */ + udelay(500); + + msens = hdmi_read(hdmi, HDMI_HOT_PLUG_MSENS_STATUS); + dev_dbg(hdmi->dev, "MSENS 0x%x\n", msens); + /* Check, if hot plug & MSENS pin status are both high */ + if ((msens & 0xC0) == 0xC0) { + /* Display plug in */ + hdmi->edid_segment_nr = 0; + hdmi->edid_block_addr = 0; + hdmi->edid_blocks = 0; + hdmi->hp_state = HDMI_HOTPLUG_CONNECTED; + + /* Set EDID word address */ + hdmi_write(hdmi, 0x00, HDMI_EDID_WORD_ADDRESS); + /* Enable EDID interrupt */ + hdmi_write(hdmi, 0xC6, HDMI_INTERRUPT_MASK_1); + /* Set EDID segment pointer - starts reading EDID */ + hdmi_write(hdmi, 0x00, HDMI_EDID_SEGMENT_POINTER); + } else if (!(status1 & 0x80)) { + /* Display unplug, beware multiple interrupts */ + if (hdmi->hp_state != HDMI_HOTPLUG_DISCONNECTED) { + hdmi->hp_state = HDMI_HOTPLUG_DISCONNECTED; + schedule_delayed_work(&hdmi->edid_work, 0); + } + /* display_off will switch back to mode_a */ + } + } else if (status1 & 2) { + /* EDID error interrupt: retry */ + /* Set EDID word address */ + hdmi_write(hdmi, hdmi->edid_block_addr, HDMI_EDID_WORD_ADDRESS); + /* Set EDID segment pointer */ + hdmi_write(hdmi, hdmi->edid_segment_nr, HDMI_EDID_SEGMENT_POINTER); + } else if (status1 & 4) { + /* Disable EDID interrupt */ + hdmi_write(hdmi, 0xC0, HDMI_INTERRUPT_MASK_1); + schedule_delayed_work(&hdmi->edid_work, msecs_to_jiffies(10)); + } + + return IRQ_HANDLED; +} + +static int sh_hdmi_display_on(struct sh_mobile_lcdc_entity *entity) +{ + struct sh_hdmi *hdmi = entity_to_sh_hdmi(entity); + + dev_dbg(hdmi->dev, "%s(%p): state %x\n", __func__, hdmi, + hdmi->hp_state); + + /* + * hp_state can be set to + * HDMI_HOTPLUG_DISCONNECTED: on monitor unplug + * HDMI_HOTPLUG_CONNECTED: on monitor plug-in + * HDMI_HOTPLUG_EDID_DONE: on EDID read completion + */ + if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) { + /* PS mode d->e. All functions are active */ + hdmi_bit_set(hdmi, 0xFC, 0x80, HDMI_SYSTEM_CTRL); + dev_dbg(hdmi->dev, "HDMI running\n"); + } + + return hdmi->hp_state == HDMI_HOTPLUG_DISCONNECTED + ? SH_MOBILE_LCDC_DISPLAY_DISCONNECTED + : SH_MOBILE_LCDC_DISPLAY_CONNECTED; +} + +static void sh_hdmi_display_off(struct sh_mobile_lcdc_entity *entity) +{ + struct sh_hdmi *hdmi = entity_to_sh_hdmi(entity); + + dev_dbg(hdmi->dev, "%s(%p)\n", __func__, hdmi); + /* PS mode e->a */ + hdmi_bit_set(hdmi, 0xFC, 0x10, HDMI_SYSTEM_CTRL); +} + +static const struct sh_mobile_lcdc_entity_ops sh_hdmi_ops = { + .display_on = sh_hdmi_display_on, + .display_off = sh_hdmi_display_off, +}; + +/** + * sh_hdmi_clk_configure() - set HDMI clock frequency and enable the clock + * @hdmi: driver context + * @hdmi_rate: HDMI clock frequency in Hz + * @parent_rate: if != 0 - set parent clock rate for optimal precision + * return: configured positive rate if successful + * 0 if couldn't set the rate, but managed to enable the + * clock, negative error, if couldn't enable the clock + */ +static long sh_hdmi_clk_configure(struct sh_hdmi *hdmi, unsigned long hdmi_rate, + unsigned long parent_rate) +{ + int ret; + + if (parent_rate && clk_get_parent(hdmi->hdmi_clk)) { + ret = clk_set_rate(clk_get_parent(hdmi->hdmi_clk), parent_rate); + if (ret < 0) { + dev_warn(hdmi->dev, "Cannot set parent rate %ld: %d\n", parent_rate, ret); + hdmi_rate = clk_round_rate(hdmi->hdmi_clk, hdmi_rate); + } else { + dev_dbg(hdmi->dev, "HDMI set parent frequency %lu\n", parent_rate); + } + } + + ret = clk_set_rate(hdmi->hdmi_clk, hdmi_rate); + if (ret < 0) { + dev_warn(hdmi->dev, "Cannot set rate %ld: %d\n", hdmi_rate, ret); + hdmi_rate = 0; + } else { + dev_dbg(hdmi->dev, "HDMI set frequency %lu\n", hdmi_rate); + } + + return hdmi_rate; +} + +/* Hotplug interrupt occurred, read EDID */ +static void sh_hdmi_edid_work_fn(struct work_struct *work) +{ + struct sh_hdmi *hdmi = container_of(work, struct sh_hdmi, edid_work.work); + struct sh_mobile_lcdc_chan *ch = hdmi->entity.lcdc; + int ret; + + dev_dbg(hdmi->dev, "%s(%p): begin, hotplug status %d\n", __func__, hdmi, + hdmi->hp_state); + + if (hdmi->hp_state == HDMI_HOTPLUG_CONNECTED) { + unsigned long parent_rate = 0, hdmi_rate; + + ret = sh_hdmi_read_edid(hdmi, &hdmi_rate, &parent_rate); + if (ret < 0) + goto out; + + hdmi->hp_state = HDMI_HOTPLUG_EDID_DONE; + + /* Reconfigure the clock */ + ret = sh_hdmi_clk_configure(hdmi, hdmi_rate, parent_rate); + if (ret < 0) + goto out; + + msleep(10); + sh_hdmi_configure(hdmi); + /* Switched to another (d) power-save mode */ + msleep(10); + + if (ch && ch->notify) + ch->notify(ch, SH_MOBILE_LCDC_EVENT_DISPLAY_CONNECT, + &hdmi->mode, &hdmi->monspec); + } else { + hdmi->monspec.modedb_len = 0; + fb_destroy_modedb(hdmi->monspec.modedb); + hdmi->monspec.modedb = NULL; + + if (ch && ch->notify) + ch->notify(ch, SH_MOBILE_LCDC_EVENT_DISPLAY_DISCONNECT, + NULL, NULL); + + ret = 0; + } + +out: + if (ret < 0 && ret != -EAGAIN) + hdmi->hp_state = HDMI_HOTPLUG_DISCONNECTED; + + dev_dbg(hdmi->dev, "%s(%p): end\n", __func__, hdmi); +} + +static void sh_hdmi_htop1_init(struct sh_hdmi *hdmi) +{ + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_HTOP_DCL_MODE); + hdmi_htop1_write(hdmi, 0x0000000b, 0x0010); + hdmi_htop1_write(hdmi, 0x00006710, HDMI_HTOP1_HTOP_DCL_FRC_MODE); + hdmi_htop1_write(hdmi, 0x01020406, HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y1_1); + hdmi_htop1_write(hdmi, 0x07080806, HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y1_2); + hdmi_htop1_write(hdmi, 0x01020406, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB1_1); + hdmi_htop1_write(hdmi, 0x07080806, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB1_2); + hdmi_htop1_write(hdmi, 0x01020406, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR1_1); + hdmi_htop1_write(hdmi, 0x07080806, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR1_2); + hdmi_htop1_write(hdmi, 0x01020406, HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y2_1); + hdmi_htop1_write(hdmi, 0x07080806, HDMI_HTOP1_HTOP_DCL_FIL_PARA_Y2_2); + hdmi_htop1_write(hdmi, 0x01020406, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB2_1); + hdmi_htop1_write(hdmi, 0x07080806, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CB2_2); + hdmi_htop1_write(hdmi, 0x01020406, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR2_1); + hdmi_htop1_write(hdmi, 0x07080806, HDMI_HTOP1_HTOP_DCL_FIL_PARA_CR2_2); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_HTOP_DCL_COR_PARA_Y1); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_HTOP_DCL_COR_PARA_CB1); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_HTOP_DCL_COR_PARA_CR1); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_HTOP_DCL_COR_PARA_Y2); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_HTOP_DCL_COR_PARA_CB2); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_HTOP_DCL_COR_PARA_CR2); + hdmi_htop1_write(hdmi, 0x00000008, HDMI_HTOP1_CURRENT); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_TISEMP0_1); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_TISEMP2_C); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_PHY_TEST_MODE); + hdmi_htop1_write(hdmi, 0x00000081, HDMI_HTOP1_TISIDRV); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_PLLBW); + hdmi_htop1_write(hdmi, 0x0000000f, HDMI_HTOP1_TISEN); + hdmi_htop1_write(hdmi, 0x0000000f, HDMI_HTOP1_TISDREN); + hdmi_htop1_write(hdmi, 0x00000003, HDMI_HTOP1_ENABLE_SELECTOR); + hdmi_htop1_write(hdmi, 0x00000001, HDMI_HTOP1_MACRO_RESET); + hdmi_htop1_write(hdmi, 0x00000016, HDMI_HTOP1_CISRANGE); + msleep(100); + hdmi_htop1_write(hdmi, 0x00000001, HDMI_HTOP1_ENABLE_SELECTOR); + msleep(100); + hdmi_htop1_write(hdmi, 0x00000003, HDMI_HTOP1_ENABLE_SELECTOR); + hdmi_htop1_write(hdmi, 0x00000001, HDMI_HTOP1_MACRO_RESET); + hdmi_htop1_write(hdmi, 0x0000000f, HDMI_HTOP1_TISEN); + hdmi_htop1_write(hdmi, 0x0000000f, HDMI_HTOP1_TISDREN); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_VIDEO_INPUT); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_CLK_TO_PHY); + hdmi_htop1_write(hdmi, 0x00000000, HDMI_HTOP1_VIDEO_INPUT2); + hdmi_htop1_write(hdmi, 0x0000000a, HDMI_HTOP1_CLK_SET); +} + +static int __init sh_hdmi_probe(struct platform_device *pdev) +{ + struct sh_mobile_hdmi_info *pdata = dev_get_platdata(&pdev->dev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct resource *htop1_res; + int irq = platform_get_irq(pdev, 0), ret; + struct sh_hdmi *hdmi; + long rate; + + if (!res || !pdata || irq < 0) + return -ENODEV; + + htop1_res = NULL; + if (pdata->flags & HDMI_HAS_HTOP1) { + htop1_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!htop1_res) { + dev_err(&pdev->dev, "htop1 needs register base\n"); + return -EINVAL; + } + } + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) { + dev_err(&pdev->dev, "Cannot allocate device data\n"); + return -ENOMEM; + } + + hdmi->dev = &pdev->dev; + hdmi->entity.owner = THIS_MODULE; + hdmi->entity.ops = &sh_hdmi_ops; + + hdmi->hdmi_clk = clk_get(&pdev->dev, "ick"); + if (IS_ERR(hdmi->hdmi_clk)) { + ret = PTR_ERR(hdmi->hdmi_clk); + dev_err(&pdev->dev, "Unable to get clock: %d\n", ret); + return ret; + } + + /* select register access functions */ + if (pdata->flags & HDMI_32BIT_REG) { + hdmi->write = __hdmi_write32; + hdmi->read = __hdmi_read32; + } else { + hdmi->write = __hdmi_write8; + hdmi->read = __hdmi_read8; + } + + /* An arbitrary relaxed pixclock just to get things started: from standard 480p */ + rate = clk_round_rate(hdmi->hdmi_clk, PICOS2KHZ(37037)); + if (rate > 0) + rate = sh_hdmi_clk_configure(hdmi, rate, 0); + + if (rate < 0) { + ret = rate; + goto erate; + } + + ret = clk_prepare_enable(hdmi->hdmi_clk); + if (ret < 0) { + dev_err(hdmi->dev, "Cannot enable clock: %d\n", ret); + goto erate; + } + + dev_dbg(&pdev->dev, "Enabled HDMI clock at %luHz\n", rate); + + if (!request_mem_region(res->start, resource_size(res), dev_name(&pdev->dev))) { + dev_err(&pdev->dev, "HDMI register region already claimed\n"); + ret = -EBUSY; + goto ereqreg; + } + + hdmi->base = ioremap(res->start, resource_size(res)); + if (!hdmi->base) { + dev_err(&pdev->dev, "HDMI register region already claimed\n"); + ret = -ENOMEM; + goto emap; + } + + platform_set_drvdata(pdev, &hdmi->entity); + + INIT_DELAYED_WORK(&hdmi->edid_work, sh_hdmi_edid_work_fn); + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + /* init interrupt polarity */ + if (pdata->flags & HDMI_OUTPUT_PUSH_PULL) + hdmi_bit_set(hdmi, 0x02, 0x02, HDMI_SYSTEM_CTRL); + + if (pdata->flags & HDMI_OUTPUT_POLARITY_HI) + hdmi_bit_set(hdmi, 0x01, 0x01, HDMI_SYSTEM_CTRL); + + /* enable htop1 register if needed */ + if (htop1_res) { + hdmi->htop1 = ioremap(htop1_res->start, resource_size(htop1_res)); + if (!hdmi->htop1) { + dev_err(&pdev->dev, "control register region already claimed\n"); + ret = -ENOMEM; + goto emap_htop1; + } + sh_hdmi_htop1_init(hdmi); + } + + /* Product and revision IDs are 0 in sh-mobile version */ + dev_info(&pdev->dev, "Detected HDMI controller 0x%x:0x%x\n", + hdmi_read(hdmi, HDMI_PRODUCT_ID), hdmi_read(hdmi, HDMI_REVISION_ID)); + + ret = request_irq(irq, sh_hdmi_hotplug, 0, + dev_name(&pdev->dev), hdmi); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to request irq: %d\n", ret); + goto ereqirq; + } + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_sh_hdmi, &sh_hdmi_dai, 1); + if (ret < 0) { + dev_err(&pdev->dev, "codec registration failed\n"); + goto ecodec; + } + + return 0; + +ecodec: + free_irq(irq, hdmi); +ereqirq: + if (hdmi->htop1) + iounmap(hdmi->htop1); +emap_htop1: + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + iounmap(hdmi->base); +emap: + release_mem_region(res->start, resource_size(res)); +ereqreg: + clk_disable_unprepare(hdmi->hdmi_clk); +erate: + clk_put(hdmi->hdmi_clk); + + return ret; +} + +static int __exit sh_hdmi_remove(struct platform_device *pdev) +{ + struct sh_hdmi *hdmi = entity_to_sh_hdmi(platform_get_drvdata(pdev)); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int irq = platform_get_irq(pdev, 0); + + snd_soc_unregister_codec(&pdev->dev); + + /* No new work will be scheduled, wait for running ISR */ + free_irq(irq, hdmi); + /* Wait for already scheduled work */ + cancel_delayed_work_sync(&hdmi->edid_work); + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(hdmi->hdmi_clk); + clk_put(hdmi->hdmi_clk); + if (hdmi->htop1) + iounmap(hdmi->htop1); + iounmap(hdmi->base); + release_mem_region(res->start, resource_size(res)); + + return 0; +} + +static struct platform_driver sh_hdmi_driver = { + .remove = __exit_p(sh_hdmi_remove), + .driver = { + .name = "sh-mobile-hdmi", + }, +}; + +module_platform_driver_probe(sh_hdmi_driver, sh_hdmi_probe); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("SuperH / ARM-shmobile HDMI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/sh_mobile_lcdcfb.c b/drivers/video/fbdev/sh_mobile_lcdcfb.c new file mode 100644 index 000000000000..2bcc84ac18c7 --- /dev/null +++ b/drivers/video/fbdev/sh_mobile_lcdcfb.c @@ -0,0 +1,2863 @@ +/* + * SuperH Mobile LCDC Framebuffer + * + * Copyright (c) 2008 Magnus Damm + * + * 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/atomic.h> +#include <linux/backlight.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/ctype.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioctl.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/vmalloc.h> + +#include <video/sh_mobile_lcdc.h> +#include <video/sh_mobile_meram.h> + +#include "sh_mobile_lcdcfb.h" + +/* ---------------------------------------------------------------------------- + * Overlay register definitions + */ + +#define LDBCR 0xb00 +#define LDBCR_UPC(n) (1 << ((n) + 16)) +#define LDBCR_UPF(n) (1 << ((n) + 8)) +#define LDBCR_UPD(n) (1 << ((n) + 0)) +#define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00) +#define LDBBSIFR_EN (1 << 31) +#define LDBBSIFR_VS (1 << 29) +#define LDBBSIFR_BRSEL (1 << 28) +#define LDBBSIFR_MX (1 << 27) +#define LDBBSIFR_MY (1 << 26) +#define LDBBSIFR_CV3 (3 << 24) +#define LDBBSIFR_CV2 (2 << 24) +#define LDBBSIFR_CV1 (1 << 24) +#define LDBBSIFR_CV0 (0 << 24) +#define LDBBSIFR_CV_MASK (3 << 24) +#define LDBBSIFR_LAY_MASK (0xff << 16) +#define LDBBSIFR_LAY_SHIFT 16 +#define LDBBSIFR_ROP3_MASK (0xff << 16) +#define LDBBSIFR_ROP3_SHIFT 16 +#define LDBBSIFR_AL_PL8 (3 << 14) +#define LDBBSIFR_AL_PL1 (2 << 14) +#define LDBBSIFR_AL_PK (1 << 14) +#define LDBBSIFR_AL_1 (0 << 14) +#define LDBBSIFR_AL_MASK (3 << 14) +#define LDBBSIFR_SWPL (1 << 10) +#define LDBBSIFR_SWPW (1 << 9) +#define LDBBSIFR_SWPB (1 << 8) +#define LDBBSIFR_RY (1 << 7) +#define LDBBSIFR_CHRR_420 (2 << 0) +#define LDBBSIFR_CHRR_422 (1 << 0) +#define LDBBSIFR_CHRR_444 (0 << 0) +#define LDBBSIFR_RPKF_ARGB32 (0x00 << 0) +#define LDBBSIFR_RPKF_RGB16 (0x03 << 0) +#define LDBBSIFR_RPKF_RGB24 (0x0b << 0) +#define LDBBSIFR_RPKF_MASK (0x1f << 0) +#define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04) +#define LDBBSSZR_BVSS_MASK (0xfff << 16) +#define LDBBSSZR_BVSS_SHIFT 16 +#define LDBBSSZR_BHSS_MASK (0xfff << 0) +#define LDBBSSZR_BHSS_SHIFT 0 +#define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08) +#define LDBBLOCR_CVLC_MASK (0xfff << 16) +#define LDBBLOCR_CVLC_SHIFT 16 +#define LDBBLOCR_CHLC_MASK (0xfff << 0) +#define LDBBLOCR_CHLC_SHIFT 0 +#define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c) +#define LDBBSMWR_BSMWA_MASK (0xffff << 16) +#define LDBBSMWR_BSMWA_SHIFT 16 +#define LDBBSMWR_BSMW_MASK (0xffff << 0) +#define LDBBSMWR_BSMW_SHIFT 0 +#define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10) +#define LDBBSAYR_FG1A_MASK (0xff << 24) +#define LDBBSAYR_FG1A_SHIFT 24 +#define LDBBSAYR_FG1R_MASK (0xff << 16) +#define LDBBSAYR_FG1R_SHIFT 16 +#define LDBBSAYR_FG1G_MASK (0xff << 8) +#define LDBBSAYR_FG1G_SHIFT 8 +#define LDBBSAYR_FG1B_MASK (0xff << 0) +#define LDBBSAYR_FG1B_SHIFT 0 +#define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14) +#define LDBBSACR_FG2A_MASK (0xff << 24) +#define LDBBSACR_FG2A_SHIFT 24 +#define LDBBSACR_FG2R_MASK (0xff << 16) +#define LDBBSACR_FG2R_SHIFT 16 +#define LDBBSACR_FG2G_MASK (0xff << 8) +#define LDBBSACR_FG2G_SHIFT 8 +#define LDBBSACR_FG2B_MASK (0xff << 0) +#define LDBBSACR_FG2B_SHIFT 0 +#define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18) +#define LDBBSAAR_AP_MASK (0xff << 24) +#define LDBBSAAR_AP_SHIFT 24 +#define LDBBSAAR_R_MASK (0xff << 16) +#define LDBBSAAR_R_SHIFT 16 +#define LDBBSAAR_GY_MASK (0xff << 8) +#define LDBBSAAR_GY_SHIFT 8 +#define LDBBSAAR_B_MASK (0xff << 0) +#define LDBBSAAR_B_SHIFT 0 +#define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c) +#define LDBBPPCR_AP_MASK (0xff << 24) +#define LDBBPPCR_AP_SHIFT 24 +#define LDBBPPCR_R_MASK (0xff << 16) +#define LDBBPPCR_R_SHIFT 16 +#define LDBBPPCR_GY_MASK (0xff << 8) +#define LDBBPPCR_GY_SHIFT 8 +#define LDBBPPCR_B_MASK (0xff << 0) +#define LDBBPPCR_B_SHIFT 0 +#define LDBnBBGCL(n) (0xb10 + (n) * 0x04) +#define LDBBBGCL_BGA_MASK (0xff << 24) +#define LDBBBGCL_BGA_SHIFT 24 +#define LDBBBGCL_BGR_MASK (0xff << 16) +#define LDBBBGCL_BGR_SHIFT 16 +#define LDBBBGCL_BGG_MASK (0xff << 8) +#define LDBBBGCL_BGG_SHIFT 8 +#define LDBBBGCL_BGB_MASK (0xff << 0) +#define LDBBBGCL_BGB_SHIFT 0 + +#define SIDE_B_OFFSET 0x1000 +#define MIRROR_OFFSET 0x2000 + +#define MAX_XRES 1920 +#define MAX_YRES 1080 + +enum sh_mobile_lcdc_overlay_mode { + LCDC_OVERLAY_BLEND, + LCDC_OVERLAY_ROP3, +}; + +/* + * struct sh_mobile_lcdc_overlay - LCDC display overlay + * + * @channel: LCDC channel this overlay belongs to + * @cfg: Overlay configuration + * @info: Frame buffer device + * @index: Overlay index (0-3) + * @base: Overlay registers base address + * @enabled: True if the overlay is enabled + * @mode: Overlay blending mode (alpha blend or ROP3) + * @alpha: Global alpha blending value (0-255, for alpha blending mode) + * @rop3: Raster operation (for ROP3 mode) + * @fb_mem: Frame buffer virtual memory address + * @fb_size: Frame buffer size in bytes + * @dma_handle: Frame buffer DMA address + * @base_addr_y: Overlay base address (RGB or luma component) + * @base_addr_c: Overlay base address (chroma component) + * @pan_y_offset: Panning linear offset in bytes (luma component) + * @format: Current pixelf format + * @xres: Horizontal visible resolution + * @xres_virtual: Horizontal total resolution + * @yres: Vertical visible resolution + * @yres_virtual: Vertical total resolution + * @pitch: Overlay line pitch + * @pos_x: Horizontal overlay position + * @pos_y: Vertical overlay position + */ +struct sh_mobile_lcdc_overlay { + struct sh_mobile_lcdc_chan *channel; + + const struct sh_mobile_lcdc_overlay_cfg *cfg; + struct fb_info *info; + + unsigned int index; + unsigned long base; + + bool enabled; + enum sh_mobile_lcdc_overlay_mode mode; + unsigned int alpha; + unsigned int rop3; + + void *fb_mem; + unsigned long fb_size; + + dma_addr_t dma_handle; + unsigned long base_addr_y; + unsigned long base_addr_c; + unsigned long pan_y_offset; + + const struct sh_mobile_lcdc_format_info *format; + unsigned int xres; + unsigned int xres_virtual; + unsigned int yres; + unsigned int yres_virtual; + unsigned int pitch; + int pos_x; + int pos_y; +}; + +struct sh_mobile_lcdc_priv { + void __iomem *base; + int irq; + atomic_t hw_usecnt; + struct device *dev; + struct clk *dot_clk; + unsigned long lddckr; + + struct sh_mobile_lcdc_chan ch[2]; + struct sh_mobile_lcdc_overlay overlays[4]; + + struct notifier_block notifier; + int started; + int forced_fourcc; /* 2 channel LCDC must share fourcc setting */ + struct sh_mobile_meram_info *meram_dev; +}; + +/* ----------------------------------------------------------------------------- + * Registers access + */ + +static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { + [LDDCKPAT1R] = 0x400, + [LDDCKPAT2R] = 0x404, + [LDMT1R] = 0x418, + [LDMT2R] = 0x41c, + [LDMT3R] = 0x420, + [LDDFR] = 0x424, + [LDSM1R] = 0x428, + [LDSM2R] = 0x42c, + [LDSA1R] = 0x430, + [LDSA2R] = 0x434, + [LDMLSR] = 0x438, + [LDHCNR] = 0x448, + [LDHSYNR] = 0x44c, + [LDVLNR] = 0x450, + [LDVSYNR] = 0x454, + [LDPMR] = 0x460, + [LDHAJR] = 0x4a0, +}; + +static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { + [LDDCKPAT1R] = 0x408, + [LDDCKPAT2R] = 0x40c, + [LDMT1R] = 0x600, + [LDMT2R] = 0x604, + [LDMT3R] = 0x608, + [LDDFR] = 0x60c, + [LDSM1R] = 0x610, + [LDSM2R] = 0x614, + [LDSA1R] = 0x618, + [LDMLSR] = 0x620, + [LDHCNR] = 0x624, + [LDHSYNR] = 0x628, + [LDVLNR] = 0x62c, + [LDVSYNR] = 0x630, + [LDPMR] = 0x63c, +}; + +static bool banked(int reg_nr) +{ + switch (reg_nr) { + case LDMT1R: + case LDMT2R: + case LDMT3R: + case LDDFR: + case LDSM1R: + case LDSA1R: + case LDSA2R: + case LDMLSR: + case LDHCNR: + case LDHSYNR: + case LDVLNR: + case LDVSYNR: + return true; + } + return false; +} + +static int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan) +{ + return chan->cfg->chan == LCDC_CHAN_SUBLCD; +} + +static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, + int reg_nr, unsigned long data) +{ + iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]); + if (banked(reg_nr)) + iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] + + SIDE_B_OFFSET); +} + +static void lcdc_write_chan_mirror(struct sh_mobile_lcdc_chan *chan, + int reg_nr, unsigned long data) +{ + iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] + + MIRROR_OFFSET); +} + +static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan, + int reg_nr) +{ + return ioread32(chan->lcdc->base + chan->reg_offs[reg_nr]); +} + +static void lcdc_write_overlay(struct sh_mobile_lcdc_overlay *ovl, + int reg, unsigned long data) +{ + iowrite32(data, ovl->channel->lcdc->base + reg); + iowrite32(data, ovl->channel->lcdc->base + reg + SIDE_B_OFFSET); +} + +static void lcdc_write(struct sh_mobile_lcdc_priv *priv, + unsigned long reg_offs, unsigned long data) +{ + iowrite32(data, priv->base + reg_offs); +} + +static unsigned long lcdc_read(struct sh_mobile_lcdc_priv *priv, + unsigned long reg_offs) +{ + return ioread32(priv->base + reg_offs); +} + +static void lcdc_wait_bit(struct sh_mobile_lcdc_priv *priv, + unsigned long reg_offs, + unsigned long mask, unsigned long until) +{ + while ((lcdc_read(priv, reg_offs) & mask) != until) + cpu_relax(); +} + +/* ----------------------------------------------------------------------------- + * Clock management + */ + +static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) +{ + if (atomic_inc_and_test(&priv->hw_usecnt)) { + if (priv->dot_clk) + clk_prepare_enable(priv->dot_clk); + pm_runtime_get_sync(priv->dev); + if (priv->meram_dev && priv->meram_dev->pdev) + pm_runtime_get_sync(&priv->meram_dev->pdev->dev); + } +} + +static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) +{ + if (atomic_sub_return(1, &priv->hw_usecnt) == -1) { + if (priv->meram_dev && priv->meram_dev->pdev) + pm_runtime_put_sync(&priv->meram_dev->pdev->dev); + pm_runtime_put(priv->dev); + if (priv->dot_clk) + clk_disable_unprepare(priv->dot_clk); + } +} + +static int sh_mobile_lcdc_setup_clocks(struct sh_mobile_lcdc_priv *priv, + int clock_source) +{ + struct clk *clk; + char *str; + + switch (clock_source) { + case LCDC_CLK_BUS: + str = "bus_clk"; + priv->lddckr = LDDCKR_ICKSEL_BUS; + break; + case LCDC_CLK_PERIPHERAL: + str = "peripheral_clk"; + priv->lddckr = LDDCKR_ICKSEL_MIPI; + break; + case LCDC_CLK_EXTERNAL: + str = NULL; + priv->lddckr = LDDCKR_ICKSEL_HDMI; + break; + default: + return -EINVAL; + } + + if (str == NULL) + return 0; + + clk = clk_get(priv->dev, str); + if (IS_ERR(clk)) { + dev_err(priv->dev, "cannot get dot clock %s\n", str); + return PTR_ERR(clk); + } + + priv->dot_clk = clk; + return 0; +} + +/* ----------------------------------------------------------------------------- + * Display, panel and deferred I/O + */ + +static void lcdc_sys_write_index(void *handle, unsigned long data) +{ + struct sh_mobile_lcdc_chan *ch = handle; + + lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT); + lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); + lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA | + (lcdc_chan_is_sublcd(ch) ? 2 : 0)); + lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); +} + +static void lcdc_sys_write_data(void *handle, unsigned long data) +{ + struct sh_mobile_lcdc_chan *ch = handle; + + lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT | LDDWDxR_RSW); + lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); + lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA | + (lcdc_chan_is_sublcd(ch) ? 2 : 0)); + lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); +} + +static unsigned long lcdc_sys_read_data(void *handle) +{ + struct sh_mobile_lcdc_chan *ch = handle; + + lcdc_write(ch->lcdc, _LDDRDR, LDDRDR_RSR); + lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); + lcdc_write(ch->lcdc, _LDDRAR, LDDRAR_RA | + (lcdc_chan_is_sublcd(ch) ? 2 : 0)); + udelay(1); + lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); + + return lcdc_read(ch->lcdc, _LDDRDR) & LDDRDR_DRD_MASK; +} + +static struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { + lcdc_sys_write_index, + lcdc_sys_write_data, + lcdc_sys_read_data, +}; + +static int sh_mobile_lcdc_sginit(struct fb_info *info, + struct list_head *pagelist) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT; + struct page *page; + int nr_pages = 0; + + sg_init_table(ch->sglist, nr_pages_max); + + list_for_each_entry(page, pagelist, lru) + sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0); + + return nr_pages; +} + +static void sh_mobile_lcdc_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; + + /* enable clocks before accessing hardware */ + sh_mobile_lcdc_clk_on(ch->lcdc); + + /* + * It's possible to get here without anything on the pagelist via + * sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync() + * invocation. In the former case, the acceleration routines are + * stepped in to when using the framebuffer console causing the + * workqueue to be scheduled without any dirty pages on the list. + * + * Despite this, a panel update is still needed given that the + * acceleration routines have their own methods for writing in + * that still need to be updated. + * + * The fsync() and empty pagelist case could be optimized for, + * but we don't bother, as any application exhibiting such + * behaviour is fundamentally broken anyways. + */ + if (!list_empty(pagelist)) { + unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist); + + /* trigger panel update */ + dma_map_sg(ch->lcdc->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); + if (panel->start_transfer) + panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops); + lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG); + dma_unmap_sg(ch->lcdc->dev, ch->sglist, nr_pages, + DMA_TO_DEVICE); + } else { + if (panel->start_transfer) + panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops); + lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG); + } +} + +static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + + if (fbdefio) + schedule_delayed_work(&info->deferred_work, fbdefio->delay); +} + +static void sh_mobile_lcdc_display_on(struct sh_mobile_lcdc_chan *ch) +{ + const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; + + if (ch->tx_dev) { + int ret; + + ret = ch->tx_dev->ops->display_on(ch->tx_dev); + if (ret < 0) + return; + + if (ret == SH_MOBILE_LCDC_DISPLAY_DISCONNECTED) + ch->info->state = FBINFO_STATE_SUSPENDED; + } + + /* HDMI must be enabled before LCDC configuration */ + if (panel->display_on) + panel->display_on(); +} + +static void sh_mobile_lcdc_display_off(struct sh_mobile_lcdc_chan *ch) +{ + const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; + + if (panel->display_off) + panel->display_off(); + + if (ch->tx_dev) + ch->tx_dev->ops->display_off(ch->tx_dev); +} + +static bool +sh_mobile_lcdc_must_reconfigure(struct sh_mobile_lcdc_chan *ch, + const struct fb_videomode *new_mode) +{ + dev_dbg(ch->info->dev, "Old %ux%u, new %ux%u\n", + ch->display.mode.xres, ch->display.mode.yres, + new_mode->xres, new_mode->yres); + + /* It can be a different monitor with an equal video-mode */ + if (fb_mode_is_equal(&ch->display.mode, new_mode)) + return false; + + dev_dbg(ch->info->dev, "Switching %u -> %u lines\n", + ch->display.mode.yres, new_mode->yres); + ch->display.mode = *new_mode; + + return true; +} + +static int sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); + +static int sh_mobile_lcdc_display_notify(struct sh_mobile_lcdc_chan *ch, + enum sh_mobile_lcdc_entity_event event, + const struct fb_videomode *mode, + const struct fb_monspecs *monspec) +{ + struct fb_info *info = ch->info; + struct fb_var_screeninfo var; + int ret = 0; + + switch (event) { + case SH_MOBILE_LCDC_EVENT_DISPLAY_CONNECT: + /* HDMI plug in */ + console_lock(); + if (lock_fb_info(info)) { + + + ch->display.width = monspec->max_x * 10; + ch->display.height = monspec->max_y * 10; + + if (!sh_mobile_lcdc_must_reconfigure(ch, mode) && + info->state == FBINFO_STATE_RUNNING) { + /* First activation with the default monitor. + * Just turn on, if we run a resume here, the + * logo disappears. + */ + info->var.width = ch->display.width; + info->var.height = ch->display.height; + sh_mobile_lcdc_display_on(ch); + } else { + /* New monitor or have to wake up */ + fb_set_suspend(info, 0); + } + + + unlock_fb_info(info); + } + console_unlock(); + break; + + case SH_MOBILE_LCDC_EVENT_DISPLAY_DISCONNECT: + /* HDMI disconnect */ + console_lock(); + if (lock_fb_info(info)) { + fb_set_suspend(info, 1); + unlock_fb_info(info); + } + console_unlock(); + break; + + case SH_MOBILE_LCDC_EVENT_DISPLAY_MODE: + /* Validate a proposed new mode */ + fb_videomode_to_var(&var, mode); + var.bits_per_pixel = info->var.bits_per_pixel; + var.grayscale = info->var.grayscale; + ret = sh_mobile_lcdc_check_var(&var, info); + break; + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +struct sh_mobile_lcdc_format_info { + u32 fourcc; + unsigned int bpp; + bool yuv; + u32 lddfr; +}; + +static const struct sh_mobile_lcdc_format_info sh_mobile_format_infos[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .bpp = 16, + .yuv = false, + .lddfr = LDDFR_PKF_RGB16, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .bpp = 24, + .yuv = false, + .lddfr = LDDFR_PKF_RGB24, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .bpp = 32, + .yuv = false, + .lddfr = LDDFR_PKF_ARGB32, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .bpp = 12, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_420, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .bpp = 12, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_420, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .bpp = 16, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_422, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .bpp = 16, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_422, + }, { + .fourcc = V4L2_PIX_FMT_NV24, + .bpp = 24, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_444, + }, { + .fourcc = V4L2_PIX_FMT_NV42, + .bpp = 24, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_444, + }, +}; + +static const struct sh_mobile_lcdc_format_info * +sh_mobile_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sh_mobile_format_infos); ++i) { + if (sh_mobile_format_infos[i].fourcc == fourcc) + return &sh_mobile_format_infos[i]; + } + + return NULL; +} + +static int sh_mobile_format_fourcc(const struct fb_var_screeninfo *var) +{ + if (var->grayscale > 1) + return var->grayscale; + + switch (var->bits_per_pixel) { + case 16: + return V4L2_PIX_FMT_RGB565; + case 24: + return V4L2_PIX_FMT_BGR24; + case 32: + return V4L2_PIX_FMT_BGR32; + default: + return 0; + } +} + +static int sh_mobile_format_is_fourcc(const struct fb_var_screeninfo *var) +{ + return var->grayscale > 1; +} + +/* ----------------------------------------------------------------------------- + * Start, stop and IRQ + */ + +static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) +{ + struct sh_mobile_lcdc_priv *priv = data; + struct sh_mobile_lcdc_chan *ch; + unsigned long ldintr; + int is_sub; + int k; + + /* Acknowledge interrupts and disable further VSYNC End IRQs. */ + ldintr = lcdc_read(priv, _LDINTR); + lcdc_write(priv, _LDINTR, (ldintr ^ LDINTR_STATUS_MASK) & ~LDINTR_VEE); + + /* figure out if this interrupt is for main or sub lcd */ + is_sub = (lcdc_read(priv, _LDSR) & LDSR_MSS) ? 1 : 0; + + /* wake up channel and disable clocks */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + ch = &priv->ch[k]; + + if (!ch->enabled) + continue; + + /* Frame End */ + if (ldintr & LDINTR_FS) { + if (is_sub == lcdc_chan_is_sublcd(ch)) { + ch->frame_end = 1; + wake_up(&ch->frame_end_wait); + + sh_mobile_lcdc_clk_off(priv); + } + } + + /* VSYNC End */ + if (ldintr & LDINTR_VES) + complete(&ch->vsync_completion); + } + + return IRQ_HANDLED; +} + +static int sh_mobile_lcdc_wait_for_vsync(struct sh_mobile_lcdc_chan *ch) +{ + unsigned long ldintr; + int ret; + + /* Enable VSync End interrupt and be careful not to acknowledge any + * pending interrupt. + */ + ldintr = lcdc_read(ch->lcdc, _LDINTR); + ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK; + lcdc_write(ch->lcdc, _LDINTR, ldintr); + + ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion, + msecs_to_jiffies(100)); + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, + int start) +{ + unsigned long tmp = lcdc_read(priv, _LDCNT2R); + int k; + + /* start or stop the lcdc */ + if (start) + lcdc_write(priv, _LDCNT2R, tmp | LDCNT2R_DO); + else + lcdc_write(priv, _LDCNT2R, tmp & ~LDCNT2R_DO); + + /* wait until power is applied/stopped on all channels */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) + if (lcdc_read(priv, _LDCNT2R) & priv->ch[k].enabled) + while (1) { + tmp = lcdc_read_chan(&priv->ch[k], LDPMR) + & LDPMR_LPS; + if (start && tmp == LDPMR_LPS) + break; + if (!start && tmp == 0) + break; + cpu_relax(); + } + + if (!start) + lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */ +} + +static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) +{ + const struct fb_var_screeninfo *var = &ch->info->var; + const struct fb_videomode *mode = &ch->display.mode; + unsigned long h_total, hsync_pos, display_h_total; + u32 tmp; + + tmp = ch->ldmt1r_value; + tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : LDMT1R_VPOL; + tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : LDMT1R_HPOL; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DWPOL) ? LDMT1R_DWPOL : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DIPOL) ? LDMT1R_DIPOL : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DAPOL) ? LDMT1R_DAPOL : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_HSCNT) ? LDMT1R_HSCNT : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DWCNT) ? LDMT1R_DWCNT : 0; + lcdc_write_chan(ch, LDMT1R, tmp); + + /* setup SYS bus */ + lcdc_write_chan(ch, LDMT2R, ch->cfg->sys_bus_cfg.ldmt2r); + lcdc_write_chan(ch, LDMT3R, ch->cfg->sys_bus_cfg.ldmt3r); + + /* horizontal configuration */ + h_total = mode->xres + mode->hsync_len + mode->left_margin + + mode->right_margin; + tmp = h_total / 8; /* HTCN */ + tmp |= (min(mode->xres, ch->xres) / 8) << 16; /* HDCN */ + lcdc_write_chan(ch, LDHCNR, tmp); + + hsync_pos = mode->xres + mode->right_margin; + tmp = hsync_pos / 8; /* HSYNP */ + tmp |= (mode->hsync_len / 8) << 16; /* HSYNW */ + lcdc_write_chan(ch, LDHSYNR, tmp); + + /* vertical configuration */ + tmp = mode->yres + mode->vsync_len + mode->upper_margin + + mode->lower_margin; /* VTLN */ + tmp |= min(mode->yres, ch->yres) << 16; /* VDLN */ + lcdc_write_chan(ch, LDVLNR, tmp); + + tmp = mode->yres + mode->lower_margin; /* VSYNP */ + tmp |= mode->vsync_len << 16; /* VSYNW */ + lcdc_write_chan(ch, LDVSYNR, tmp); + + /* Adjust horizontal synchronisation for HDMI */ + display_h_total = mode->xres + mode->hsync_len + mode->left_margin + + mode->right_margin; + tmp = ((mode->xres & 7) << 24) | ((display_h_total & 7) << 16) + | ((mode->hsync_len & 7) << 8) | (hsync_pos & 7); + lcdc_write_chan(ch, LDHAJR, tmp); + lcdc_write_chan_mirror(ch, LDHAJR, tmp); +} + +static void sh_mobile_lcdc_overlay_setup(struct sh_mobile_lcdc_overlay *ovl) +{ + u32 format = 0; + + if (!ovl->enabled) { + lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index)); + lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), 0); + lcdc_write(ovl->channel->lcdc, LDBCR, + LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index)); + return; + } + + ovl->base_addr_y = ovl->dma_handle; + ovl->base_addr_c = ovl->dma_handle + + ovl->xres_virtual * ovl->yres_virtual; + + switch (ovl->mode) { + case LCDC_OVERLAY_BLEND: + format = LDBBSIFR_EN | (ovl->alpha << LDBBSIFR_LAY_SHIFT); + break; + + case LCDC_OVERLAY_ROP3: + format = LDBBSIFR_EN | LDBBSIFR_BRSEL + | (ovl->rop3 << LDBBSIFR_ROP3_SHIFT); + break; + } + + switch (ovl->format->fourcc) { + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV42: + format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV24: + format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW | LDBBSIFR_SWPB; + break; + case V4L2_PIX_FMT_BGR32: + default: + format |= LDBBSIFR_SWPL; + break; + } + + switch (ovl->format->fourcc) { + case V4L2_PIX_FMT_RGB565: + format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16; + break; + case V4L2_PIX_FMT_BGR24: + format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24; + break; + case V4L2_PIX_FMT_BGR32: + format |= LDBBSIFR_AL_PK | LDBBSIFR_RY | LDDFR_PKF_ARGB32; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_420; + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_422; + break; + case V4L2_PIX_FMT_NV24: + case V4L2_PIX_FMT_NV42: + format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_444; + break; + } + + lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index)); + + lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), format); + + lcdc_write_overlay(ovl, LDBnBSSZR(ovl->index), + (ovl->yres << LDBBSSZR_BVSS_SHIFT) | + (ovl->xres << LDBBSSZR_BHSS_SHIFT)); + lcdc_write_overlay(ovl, LDBnBLOCR(ovl->index), + (ovl->pos_y << LDBBLOCR_CVLC_SHIFT) | + (ovl->pos_x << LDBBLOCR_CHLC_SHIFT)); + lcdc_write_overlay(ovl, LDBnBSMWR(ovl->index), + ovl->pitch << LDBBSMWR_BSMW_SHIFT); + + lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y); + lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c); + + lcdc_write(ovl->channel->lcdc, LDBCR, + LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index)); +} + +/* + * __sh_mobile_lcdc_start - Configure and start the LCDC + * @priv: LCDC device + * + * Configure all enabled channels and start the LCDC device. All external + * devices (clocks, MERAM, panels, ...) are not touched by this function. + */ +static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) +{ + struct sh_mobile_lcdc_chan *ch; + unsigned long tmp; + int k, m; + + /* Enable LCDC channels. Read data from external memory, avoid using the + * BEU for now. + */ + lcdc_write(priv, _LDCNT2R, priv->ch[0].enabled | priv->ch[1].enabled); + + /* Stop the LCDC first and disable all interrupts. */ + sh_mobile_lcdc_start_stop(priv, 0); + lcdc_write(priv, _LDINTR, 0); + + /* Configure power supply, dot clocks and start them. */ + tmp = priv->lddckr; + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + ch = &priv->ch[k]; + if (!ch->enabled) + continue; + + /* Power supply */ + lcdc_write_chan(ch, LDPMR, 0); + + m = ch->cfg->clock_divider; + if (!m) + continue; + + /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider + * denominator. + */ + lcdc_write_chan(ch, LDDCKPAT1R, 0); + lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); + + if (m == 1) + m = LDDCKR_MOSEL; + tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); + } + + lcdc_write(priv, _LDDCKR, tmp); + lcdc_write(priv, _LDDCKSTPR, 0); + lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0); + + /* Setup geometry, format, frame buffer memory and operation mode. */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + ch = &priv->ch[k]; + if (!ch->enabled) + continue; + + sh_mobile_lcdc_geometry(ch); + + tmp = ch->format->lddfr; + + if (ch->format->yuv) { + switch (ch->colorspace) { + case V4L2_COLORSPACE_REC709: + tmp |= LDDFR_CF1; + break; + case V4L2_COLORSPACE_JPEG: + tmp |= LDDFR_CF0; + break; + } + } + + lcdc_write_chan(ch, LDDFR, tmp); + lcdc_write_chan(ch, LDMLSR, ch->line_size); + lcdc_write_chan(ch, LDSA1R, ch->base_addr_y); + if (ch->format->yuv) + lcdc_write_chan(ch, LDSA2R, ch->base_addr_c); + + /* When using deferred I/O mode, configure the LCDC for one-shot + * operation and enable the frame end interrupt. Otherwise use + * continuous read mode. + */ + if (ch->ldmt1r_value & LDMT1R_IFM && + ch->cfg->sys_bus_cfg.deferred_io_msec) { + lcdc_write_chan(ch, LDSM1R, LDSM1R_OS); + lcdc_write(priv, _LDINTR, LDINTR_FE); + } else { + lcdc_write_chan(ch, LDSM1R, 0); + } + } + + /* Word and long word swap. */ + switch (priv->ch[0].format->fourcc) { + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV42: + tmp = LDDDSR_LS | LDDDSR_WS; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV24: + tmp = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS; + break; + case V4L2_PIX_FMT_BGR32: + default: + tmp = LDDDSR_LS; + break; + } + lcdc_write(priv, _LDDDSR, tmp); + + /* Enable the display output. */ + lcdc_write(priv, _LDCNT1R, LDCNT1R_DE); + sh_mobile_lcdc_start_stop(priv, 1); + priv->started = 1; +} + +static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) +{ + struct sh_mobile_meram_info *mdev = priv->meram_dev; + struct sh_mobile_lcdc_chan *ch; + unsigned long tmp; + int ret; + int k; + + /* enable clocks before accessing the hardware */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + if (priv->ch[k].enabled) + sh_mobile_lcdc_clk_on(priv); + } + + /* reset */ + lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LDCNT2R_BR); + lcdc_wait_bit(priv, _LDCNT2R, LDCNT2R_BR, 0); + + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + const struct sh_mobile_lcdc_panel_cfg *panel; + + ch = &priv->ch[k]; + if (!ch->enabled) + continue; + + panel = &ch->cfg->panel_cfg; + if (panel->setup_sys) { + ret = panel->setup_sys(ch, &sh_mobile_lcdc_sys_bus_ops); + if (ret) + return ret; + } + } + + /* Compute frame buffer base address and pitch for each channel. */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + int pixelformat; + void *cache; + + ch = &priv->ch[k]; + if (!ch->enabled) + continue; + + ch->base_addr_y = ch->dma_handle; + ch->base_addr_c = ch->dma_handle + + ch->xres_virtual * ch->yres_virtual; + ch->line_size = ch->pitch; + + /* Enable MERAM if possible. */ + if (mdev == NULL || ch->cfg->meram_cfg == NULL) + continue; + + /* Free the allocated MERAM cache. */ + if (ch->cache) { + sh_mobile_meram_cache_free(mdev, ch->cache); + ch->cache = NULL; + } + + switch (ch->format->fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + pixelformat = SH_MOBILE_MERAM_PF_NV; + break; + case V4L2_PIX_FMT_NV24: + case V4L2_PIX_FMT_NV42: + pixelformat = SH_MOBILE_MERAM_PF_NV24; + break; + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_BGR32: + default: + pixelformat = SH_MOBILE_MERAM_PF_RGB; + break; + } + + cache = sh_mobile_meram_cache_alloc(mdev, ch->cfg->meram_cfg, + ch->pitch, ch->yres, pixelformat, + &ch->line_size); + if (!IS_ERR(cache)) { + sh_mobile_meram_cache_update(mdev, cache, + ch->base_addr_y, ch->base_addr_c, + &ch->base_addr_y, &ch->base_addr_c); + ch->cache = cache; + } + } + + for (k = 0; k < ARRAY_SIZE(priv->overlays); ++k) { + struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[k]; + sh_mobile_lcdc_overlay_setup(ovl); + } + + /* Start the LCDC. */ + __sh_mobile_lcdc_start(priv); + + /* Setup deferred I/O, tell the board code to enable the panels, and + * turn backlight on. + */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + ch = &priv->ch[k]; + if (!ch->enabled) + continue; + + tmp = ch->cfg->sys_bus_cfg.deferred_io_msec; + if (ch->ldmt1r_value & LDMT1R_IFM && tmp) { + ch->defio.deferred_io = sh_mobile_lcdc_deferred_io; + ch->defio.delay = msecs_to_jiffies(tmp); + ch->info->fbdefio = &ch->defio; + fb_deferred_io_init(ch->info); + } + + sh_mobile_lcdc_display_on(ch); + + if (ch->bl) { + ch->bl->props.power = FB_BLANK_UNBLANK; + backlight_update_status(ch->bl); + } + } + + return 0; +} + +static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) +{ + struct sh_mobile_lcdc_chan *ch; + int k; + + /* clean up deferred io and ask board code to disable panel */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { + ch = &priv->ch[k]; + if (!ch->enabled) + continue; + + /* deferred io mode: + * flush frame, and wait for frame end interrupt + * clean up deferred io and enable clock + */ + if (ch->info && ch->info->fbdefio) { + ch->frame_end = 0; + schedule_delayed_work(&ch->info->deferred_work, 0); + wait_event(ch->frame_end_wait, ch->frame_end); + fb_deferred_io_cleanup(ch->info); + ch->info->fbdefio = NULL; + sh_mobile_lcdc_clk_on(priv); + } + + if (ch->bl) { + ch->bl->props.power = FB_BLANK_POWERDOWN; + backlight_update_status(ch->bl); + } + + sh_mobile_lcdc_display_off(ch); + + /* Free the MERAM cache. */ + if (ch->cache) { + sh_mobile_meram_cache_free(priv->meram_dev, ch->cache); + ch->cache = NULL; + } + + } + + /* stop the lcdc */ + if (priv->started) { + sh_mobile_lcdc_start_stop(priv, 0); + priv->started = 0; + } + + /* stop clocks */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) + if (priv->ch[k].enabled) + sh_mobile_lcdc_clk_off(priv); +} + +static int __sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->xres > MAX_XRES || var->yres > MAX_YRES) + return -EINVAL; + + /* Make sure the virtual resolution is at least as big as the visible + * resolution. + */ + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (sh_mobile_format_is_fourcc(var)) { + const struct sh_mobile_lcdc_format_info *format; + + format = sh_mobile_format_info(var->grayscale); + if (format == NULL) + return -EINVAL; + var->bits_per_pixel = format->bpp; + + /* Default to RGB and JPEG color-spaces for RGB and YUV formats + * respectively. + */ + if (!format->yuv) + var->colorspace = V4L2_COLORSPACE_SRGB; + else if (var->colorspace != V4L2_COLORSPACE_REC709) + var->colorspace = V4L2_COLORSPACE_JPEG; + } else { + if (var->bits_per_pixel <= 16) { /* RGB 565 */ + var->bits_per_pixel = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } else if (var->bits_per_pixel <= 24) { /* RGB 888 */ + var->bits_per_pixel = 24; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + } else if (var->bits_per_pixel <= 32) { /* RGBA 888 */ + var->bits_per_pixel = 32; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + } else + return -EINVAL; + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + } + + /* Make sure we don't exceed our allocated memory. */ + if (var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8 > + info->fix.smem_len) + return -EINVAL; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Frame buffer operations - Overlays + */ + +static ssize_t +overlay_alpha_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + + return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->alpha); +} + +static ssize_t +overlay_alpha_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + unsigned int alpha; + char *endp; + + alpha = simple_strtoul(buf, &endp, 10); + if (isspace(*endp)) + endp++; + + if (endp - buf != count) + return -EINVAL; + + if (alpha > 255) + return -EINVAL; + + if (ovl->alpha != alpha) { + ovl->alpha = alpha; + + if (ovl->mode == LCDC_OVERLAY_BLEND && ovl->enabled) + sh_mobile_lcdc_overlay_setup(ovl); + } + + return count; +} + +static ssize_t +overlay_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + + return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->mode); +} + +static ssize_t +overlay_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + unsigned int mode; + char *endp; + + mode = simple_strtoul(buf, &endp, 10); + if (isspace(*endp)) + endp++; + + if (endp - buf != count) + return -EINVAL; + + if (mode != LCDC_OVERLAY_BLEND && mode != LCDC_OVERLAY_ROP3) + return -EINVAL; + + if (ovl->mode != mode) { + ovl->mode = mode; + + if (ovl->enabled) + sh_mobile_lcdc_overlay_setup(ovl); + } + + return count; +} + +static ssize_t +overlay_position_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + + return scnprintf(buf, PAGE_SIZE, "%d,%d\n", ovl->pos_x, ovl->pos_y); +} + +static ssize_t +overlay_position_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + char *endp; + int pos_x; + int pos_y; + + pos_x = simple_strtol(buf, &endp, 10); + if (*endp != ',') + return -EINVAL; + + pos_y = simple_strtol(endp + 1, &endp, 10); + if (isspace(*endp)) + endp++; + + if (endp - buf != count) + return -EINVAL; + + if (ovl->pos_x != pos_x || ovl->pos_y != pos_y) { + ovl->pos_x = pos_x; + ovl->pos_y = pos_y; + + if (ovl->enabled) + sh_mobile_lcdc_overlay_setup(ovl); + } + + return count; +} + +static ssize_t +overlay_rop3_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + + return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->rop3); +} + +static ssize_t +overlay_rop3_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct sh_mobile_lcdc_overlay *ovl = info->par; + unsigned int rop3; + char *endp; + + rop3 = !!simple_strtoul(buf, &endp, 10); + if (isspace(*endp)) + endp++; + + if (endp - buf != count) + return -EINVAL; + + if (rop3 > 255) + return -EINVAL; + + if (ovl->rop3 != rop3) { + ovl->rop3 = rop3; + + if (ovl->mode == LCDC_OVERLAY_ROP3 && ovl->enabled) + sh_mobile_lcdc_overlay_setup(ovl); + } + + return count; +} + +static const struct device_attribute overlay_sysfs_attrs[] = { + __ATTR(ovl_alpha, S_IRUGO|S_IWUSR, + overlay_alpha_show, overlay_alpha_store), + __ATTR(ovl_mode, S_IRUGO|S_IWUSR, + overlay_mode_show, overlay_mode_store), + __ATTR(ovl_position, S_IRUGO|S_IWUSR, + overlay_position_show, overlay_position_store), + __ATTR(ovl_rop3, S_IRUGO|S_IWUSR, + overlay_rop3_show, overlay_rop3_store), +}; + +static const struct fb_fix_screeninfo sh_mobile_lcdc_overlay_fix = { + .id = "SH Mobile LCDC", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .accel = FB_ACCEL_NONE, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 0, + .capabilities = FB_CAP_FOURCC, +}; + +static int sh_mobile_lcdc_overlay_pan(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct sh_mobile_lcdc_overlay *ovl = info->par; + unsigned long base_addr_y; + unsigned long base_addr_c; + unsigned long y_offset; + unsigned long c_offset; + + if (!ovl->format->yuv) { + y_offset = (var->yoffset * ovl->xres_virtual + var->xoffset) + * ovl->format->bpp / 8; + c_offset = 0; + } else { + unsigned int xsub = ovl->format->bpp < 24 ? 2 : 1; + unsigned int ysub = ovl->format->bpp < 16 ? 2 : 1; + + y_offset = var->yoffset * ovl->xres_virtual + var->xoffset; + c_offset = var->yoffset / ysub * ovl->xres_virtual * 2 / xsub + + var->xoffset * 2 / xsub; + } + + /* If the Y offset hasn't changed, the C offset hasn't either. There's + * nothing to do in that case. + */ + if (y_offset == ovl->pan_y_offset) + return 0; + + /* Set the source address for the next refresh */ + base_addr_y = ovl->dma_handle + y_offset; + base_addr_c = ovl->dma_handle + ovl->xres_virtual * ovl->yres_virtual + + c_offset; + + ovl->base_addr_y = base_addr_y; + ovl->base_addr_c = base_addr_c; + ovl->pan_y_offset = y_offset; + + lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index)); + + lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y); + lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c); + + lcdc_write(ovl->channel->lcdc, LDBCR, + LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index)); + + return 0; +} + +static int sh_mobile_lcdc_overlay_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct sh_mobile_lcdc_overlay *ovl = info->par; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + return sh_mobile_lcdc_wait_for_vsync(ovl->channel); + + default: + return -ENOIOCTLCMD; + } +} + +static int sh_mobile_lcdc_overlay_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + return __sh_mobile_lcdc_check_var(var, info); +} + +static int sh_mobile_lcdc_overlay_set_par(struct fb_info *info) +{ + struct sh_mobile_lcdc_overlay *ovl = info->par; + + ovl->format = + sh_mobile_format_info(sh_mobile_format_fourcc(&info->var)); + + ovl->xres = info->var.xres; + ovl->xres_virtual = info->var.xres_virtual; + ovl->yres = info->var.yres; + ovl->yres_virtual = info->var.yres_virtual; + + if (ovl->format->yuv) + ovl->pitch = info->var.xres_virtual; + else + ovl->pitch = info->var.xres_virtual * ovl->format->bpp / 8; + + sh_mobile_lcdc_overlay_setup(ovl); + + info->fix.line_length = ovl->pitch; + + if (sh_mobile_format_is_fourcc(&info->var)) { + info->fix.type = FB_TYPE_FOURCC; + info->fix.visual = FB_VISUAL_FOURCC; + } else { + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + } + + return 0; +} + +/* Overlay blanking. Disable the overlay when blanked. */ +static int sh_mobile_lcdc_overlay_blank(int blank, struct fb_info *info) +{ + struct sh_mobile_lcdc_overlay *ovl = info->par; + + ovl->enabled = !blank; + sh_mobile_lcdc_overlay_setup(ovl); + + /* Prevent the backlight from receiving a blanking event by returning + * a non-zero value. + */ + return 1; +} + +static int +sh_mobile_lcdc_overlay_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct sh_mobile_lcdc_overlay *ovl = info->par; + + return dma_mmap_coherent(ovl->channel->lcdc->dev, vma, ovl->fb_mem, + ovl->dma_handle, ovl->fb_size); +} + +static struct fb_ops sh_mobile_lcdc_overlay_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_blank = sh_mobile_lcdc_overlay_blank, + .fb_pan_display = sh_mobile_lcdc_overlay_pan, + .fb_ioctl = sh_mobile_lcdc_overlay_ioctl, + .fb_check_var = sh_mobile_lcdc_overlay_check_var, + .fb_set_par = sh_mobile_lcdc_overlay_set_par, + .fb_mmap = sh_mobile_lcdc_overlay_mmap, +}; + +static void +sh_mobile_lcdc_overlay_fb_unregister(struct sh_mobile_lcdc_overlay *ovl) +{ + struct fb_info *info = ovl->info; + + if (info == NULL || info->dev == NULL) + return; + + unregister_framebuffer(ovl->info); +} + +static int +sh_mobile_lcdc_overlay_fb_register(struct sh_mobile_lcdc_overlay *ovl) +{ + struct sh_mobile_lcdc_priv *lcdc = ovl->channel->lcdc; + struct fb_info *info = ovl->info; + unsigned int i; + int ret; + + if (info == NULL) + return 0; + + ret = register_framebuffer(info); + if (ret < 0) + return ret; + + dev_info(lcdc->dev, "registered %s/overlay %u as %dx%d %dbpp.\n", + dev_name(lcdc->dev), ovl->index, info->var.xres, + info->var.yres, info->var.bits_per_pixel); + + for (i = 0; i < ARRAY_SIZE(overlay_sysfs_attrs); ++i) { + ret = device_create_file(info->dev, &overlay_sysfs_attrs[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +static void +sh_mobile_lcdc_overlay_fb_cleanup(struct sh_mobile_lcdc_overlay *ovl) +{ + struct fb_info *info = ovl->info; + + if (info == NULL || info->device == NULL) + return; + + framebuffer_release(info); +} + +static int +sh_mobile_lcdc_overlay_fb_init(struct sh_mobile_lcdc_overlay *ovl) +{ + struct sh_mobile_lcdc_priv *priv = ovl->channel->lcdc; + struct fb_var_screeninfo *var; + struct fb_info *info; + + /* Allocate and initialize the frame buffer device. */ + info = framebuffer_alloc(0, priv->dev); + if (info == NULL) { + dev_err(priv->dev, "unable to allocate fb_info\n"); + return -ENOMEM; + } + + ovl->info = info; + + info->flags = FBINFO_FLAG_DEFAULT; + info->fbops = &sh_mobile_lcdc_overlay_ops; + info->device = priv->dev; + info->screen_base = ovl->fb_mem; + info->par = ovl; + + /* Initialize fixed screen information. Restrict pan to 2 lines steps + * for NV12 and NV21. + */ + info->fix = sh_mobile_lcdc_overlay_fix; + snprintf(info->fix.id, sizeof(info->fix.id), + "SH Mobile LCDC Overlay %u", ovl->index); + info->fix.smem_start = ovl->dma_handle; + info->fix.smem_len = ovl->fb_size; + info->fix.line_length = ovl->pitch; + + if (ovl->format->yuv) + info->fix.visual = FB_VISUAL_FOURCC; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + switch (ovl->format->fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + info->fix.ypanstep = 2; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + info->fix.xpanstep = 2; + } + + /* Initialize variable screen information. */ + var = &info->var; + memset(var, 0, sizeof(*var)); + var->xres = ovl->xres; + var->yres = ovl->yres; + var->xres_virtual = ovl->xres_virtual; + var->yres_virtual = ovl->yres_virtual; + var->activate = FB_ACTIVATE_NOW; + + /* Use the legacy API by default for RGB formats, and the FOURCC API + * for YUV formats. + */ + if (!ovl->format->yuv) + var->bits_per_pixel = ovl->format->bpp; + else + var->grayscale = ovl->format->fourcc; + + return sh_mobile_lcdc_overlay_check_var(var, info); +} + +/* ----------------------------------------------------------------------------- + * Frame buffer operations - main frame buffer + */ + +static int sh_mobile_lcdc_setcolreg(u_int regno, + u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + u32 *palette = info->pseudo_palette; + + if (regno >= PALETTE_NR) + return -EINVAL; + + /* only FB_VISUAL_TRUECOLOR supported */ + + red >>= 16 - info->var.red.length; + green >>= 16 - info->var.green.length; + blue >>= 16 - info->var.blue.length; + transp >>= 16 - info->var.transp.length; + + palette[regno] = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + return 0; +} + +static const struct fb_fix_screeninfo sh_mobile_lcdc_fix = { + .id = "SH Mobile LCDC", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .accel = FB_ACCEL_NONE, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 0, + .capabilities = FB_CAP_FOURCC, +}; + +static void sh_mobile_lcdc_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + sys_fillrect(info, rect); + sh_mobile_lcdc_deferred_io_touch(info); +} + +static void sh_mobile_lcdc_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + sys_copyarea(info, area); + sh_mobile_lcdc_deferred_io_touch(info); +} + +static void sh_mobile_lcdc_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + sys_imageblit(info, image); + sh_mobile_lcdc_deferred_io_touch(info); +} + +static int sh_mobile_lcdc_pan(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + struct sh_mobile_lcdc_priv *priv = ch->lcdc; + unsigned long ldrcntr; + unsigned long base_addr_y, base_addr_c; + unsigned long y_offset; + unsigned long c_offset; + + if (!ch->format->yuv) { + y_offset = (var->yoffset * ch->xres_virtual + var->xoffset) + * ch->format->bpp / 8; + c_offset = 0; + } else { + unsigned int xsub = ch->format->bpp < 24 ? 2 : 1; + unsigned int ysub = ch->format->bpp < 16 ? 2 : 1; + + y_offset = var->yoffset * ch->xres_virtual + var->xoffset; + c_offset = var->yoffset / ysub * ch->xres_virtual * 2 / xsub + + var->xoffset * 2 / xsub; + } + + /* If the Y offset hasn't changed, the C offset hasn't either. There's + * nothing to do in that case. + */ + if (y_offset == ch->pan_y_offset) + return 0; + + /* Set the source address for the next refresh */ + base_addr_y = ch->dma_handle + y_offset; + base_addr_c = ch->dma_handle + ch->xres_virtual * ch->yres_virtual + + c_offset; + + if (ch->cache) + sh_mobile_meram_cache_update(priv->meram_dev, ch->cache, + base_addr_y, base_addr_c, + &base_addr_y, &base_addr_c); + + ch->base_addr_y = base_addr_y; + ch->base_addr_c = base_addr_c; + ch->pan_y_offset = y_offset; + + lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y); + if (ch->format->yuv) + lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c); + + ldrcntr = lcdc_read(priv, _LDRCNTR); + if (lcdc_chan_is_sublcd(ch)) + lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); + else + lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_MRS); + + + sh_mobile_lcdc_deferred_io_touch(info); + + return 0; +} + +static int sh_mobile_lcdc_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + int retval; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + retval = sh_mobile_lcdc_wait_for_vsync(ch); + break; + + default: + retval = -ENOIOCTLCMD; + break; + } + return retval; +} + +static void sh_mobile_fb_reconfig(struct fb_info *info) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + struct fb_var_screeninfo var; + struct fb_videomode mode; + struct fb_event event; + int evnt = FB_EVENT_MODE_CHANGE_ALL; + + if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par)) + /* More framebuffer users are active */ + return; + + fb_var_to_videomode(&mode, &info->var); + + if (fb_mode_is_equal(&ch->display.mode, &mode)) + return; + + /* Display has been re-plugged, framebuffer is free now, reconfigure */ + var = info->var; + fb_videomode_to_var(&var, &ch->display.mode); + var.width = ch->display.width; + var.height = ch->display.height; + var.activate = FB_ACTIVATE_NOW; + + if (fb_set_var(info, &var) < 0) + /* Couldn't reconfigure, hopefully, can continue as before */ + return; + + /* + * fb_set_var() calls the notifier change internally, only if + * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a + * user event, we have to call the chain ourselves. + */ + event.info = info; + event.data = &ch->display.mode; + fb_notifier_call_chain(evnt, &event); +} + +/* + * Locking: both .fb_release() and .fb_open() are called with info->lock held if + * user == 1, or with console sem held, if user == 0. + */ +static int sh_mobile_lcdc_release(struct fb_info *info, int user) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + + mutex_lock(&ch->open_lock); + dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); + + ch->use_count--; + + /* Nothing to reconfigure, when called from fbcon */ + if (user) { + console_lock(); + sh_mobile_fb_reconfig(info); + console_unlock(); + } + + mutex_unlock(&ch->open_lock); + + return 0; +} + +static int sh_mobile_lcdc_open(struct fb_info *info, int user) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + + mutex_lock(&ch->open_lock); + ch->use_count++; + + dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); + mutex_unlock(&ch->open_lock); + + return 0; +} + +static int sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + struct sh_mobile_lcdc_priv *p = ch->lcdc; + unsigned int best_dist = (unsigned int)-1; + unsigned int best_xres = 0; + unsigned int best_yres = 0; + unsigned int i; + int ret; + + /* If board code provides us with a list of available modes, make sure + * we use one of them. Find the mode closest to the requested one. The + * distance between two modes is defined as the size of the + * non-overlapping parts of the two rectangles. + */ + for (i = 0; i < ch->cfg->num_modes; ++i) { + const struct fb_videomode *mode = &ch->cfg->lcd_modes[i]; + unsigned int dist; + + /* We can only round up. */ + if (var->xres > mode->xres || var->yres > mode->yres) + continue; + + dist = var->xres * var->yres + mode->xres * mode->yres + - 2 * min(var->xres, mode->xres) + * min(var->yres, mode->yres); + + if (dist < best_dist) { + best_xres = mode->xres; + best_yres = mode->yres; + best_dist = dist; + } + } + + /* If no available mode can be used, return an error. */ + if (ch->cfg->num_modes != 0) { + if (best_dist == (unsigned int)-1) + return -EINVAL; + + var->xres = best_xres; + var->yres = best_yres; + } + + ret = __sh_mobile_lcdc_check_var(var, info); + if (ret < 0) + return ret; + + /* only accept the forced_fourcc for dual channel configurations */ + if (p->forced_fourcc && + p->forced_fourcc != sh_mobile_format_fourcc(var)) + return -EINVAL; + + return 0; +} + +static int sh_mobile_lcdc_set_par(struct fb_info *info) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + int ret; + + sh_mobile_lcdc_stop(ch->lcdc); + + ch->format = sh_mobile_format_info(sh_mobile_format_fourcc(&info->var)); + ch->colorspace = info->var.colorspace; + + ch->xres = info->var.xres; + ch->xres_virtual = info->var.xres_virtual; + ch->yres = info->var.yres; + ch->yres_virtual = info->var.yres_virtual; + + if (ch->format->yuv) + ch->pitch = info->var.xres_virtual; + else + ch->pitch = info->var.xres_virtual * ch->format->bpp / 8; + + ret = sh_mobile_lcdc_start(ch->lcdc); + if (ret < 0) + dev_err(info->dev, "%s: unable to restart LCDC\n", __func__); + + info->fix.line_length = ch->pitch; + + if (sh_mobile_format_is_fourcc(&info->var)) { + info->fix.type = FB_TYPE_FOURCC; + info->fix.visual = FB_VISUAL_FOURCC; + } else { + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + } + + return ret; +} + +/* + * Screen blanking. Behavior is as follows: + * FB_BLANK_UNBLANK: screen unblanked, clocks enabled + * FB_BLANK_NORMAL: screen blanked, clocks enabled + * FB_BLANK_VSYNC, + * FB_BLANK_HSYNC, + * FB_BLANK_POWEROFF: screen blanked, clocks disabled + */ +static int sh_mobile_lcdc_blank(int blank, struct fb_info *info) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + struct sh_mobile_lcdc_priv *p = ch->lcdc; + + /* blank the screen? */ + if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) { + struct fb_fillrect rect = { + .width = ch->xres, + .height = ch->yres, + }; + sh_mobile_lcdc_fillrect(info, &rect); + } + /* turn clocks on? */ + if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) { + sh_mobile_lcdc_clk_on(p); + } + /* turn clocks off? */ + if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) { + /* make sure the screen is updated with the black fill before + * switching the clocks off. one vsync is not enough since + * blanking may occur in the middle of a refresh. deferred io + * mode will reenable the clocks and update the screen in time, + * so it does not need this. */ + if (!info->fbdefio) { + sh_mobile_lcdc_wait_for_vsync(ch); + sh_mobile_lcdc_wait_for_vsync(ch); + } + sh_mobile_lcdc_clk_off(p); + } + + ch->blank_status = blank; + return 0; +} + +static int +sh_mobile_lcdc_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + + return dma_mmap_coherent(ch->lcdc->dev, vma, ch->fb_mem, + ch->dma_handle, ch->fb_size); +} + +static struct fb_ops sh_mobile_lcdc_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = sh_mobile_lcdc_setcolreg, + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_fillrect = sh_mobile_lcdc_fillrect, + .fb_copyarea = sh_mobile_lcdc_copyarea, + .fb_imageblit = sh_mobile_lcdc_imageblit, + .fb_blank = sh_mobile_lcdc_blank, + .fb_pan_display = sh_mobile_lcdc_pan, + .fb_ioctl = sh_mobile_lcdc_ioctl, + .fb_open = sh_mobile_lcdc_open, + .fb_release = sh_mobile_lcdc_release, + .fb_check_var = sh_mobile_lcdc_check_var, + .fb_set_par = sh_mobile_lcdc_set_par, + .fb_mmap = sh_mobile_lcdc_mmap, +}; + +static void +sh_mobile_lcdc_channel_fb_unregister(struct sh_mobile_lcdc_chan *ch) +{ + if (ch->info && ch->info->dev) + unregister_framebuffer(ch->info); +} + +static int +sh_mobile_lcdc_channel_fb_register(struct sh_mobile_lcdc_chan *ch) +{ + struct fb_info *info = ch->info; + int ret; + + if (info->fbdefio) { + ch->sglist = vmalloc(sizeof(struct scatterlist) * + ch->fb_size >> PAGE_SHIFT); + if (!ch->sglist) { + dev_err(ch->lcdc->dev, "cannot allocate sglist\n"); + return -ENOMEM; + } + } + + info->bl_dev = ch->bl; + + ret = register_framebuffer(info); + if (ret < 0) + return ret; + + dev_info(ch->lcdc->dev, "registered %s/%s as %dx%d %dbpp.\n", + dev_name(ch->lcdc->dev), (ch->cfg->chan == LCDC_CHAN_MAINLCD) ? + "mainlcd" : "sublcd", info->var.xres, info->var.yres, + info->var.bits_per_pixel); + + /* deferred io mode: disable clock to save power */ + if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED) + sh_mobile_lcdc_clk_off(ch->lcdc); + + return ret; +} + +static void +sh_mobile_lcdc_channel_fb_cleanup(struct sh_mobile_lcdc_chan *ch) +{ + struct fb_info *info = ch->info; + + if (!info || !info->device) + return; + + if (ch->sglist) + vfree(ch->sglist); + + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + +static int +sh_mobile_lcdc_channel_fb_init(struct sh_mobile_lcdc_chan *ch, + const struct fb_videomode *modes, + unsigned int num_modes) +{ + struct sh_mobile_lcdc_priv *priv = ch->lcdc; + struct fb_var_screeninfo *var; + struct fb_info *info; + int ret; + + /* Allocate and initialize the frame buffer device. Create the modes + * list and allocate the color map. + */ + info = framebuffer_alloc(0, priv->dev); + if (info == NULL) { + dev_err(priv->dev, "unable to allocate fb_info\n"); + return -ENOMEM; + } + + ch->info = info; + + info->flags = FBINFO_FLAG_DEFAULT; + info->fbops = &sh_mobile_lcdc_ops; + info->device = priv->dev; + info->screen_base = ch->fb_mem; + info->pseudo_palette = &ch->pseudo_palette; + info->par = ch; + + fb_videomode_to_modelist(modes, num_modes, &info->modelist); + + ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); + if (ret < 0) { + dev_err(priv->dev, "unable to allocate cmap\n"); + return ret; + } + + /* Initialize fixed screen information. Restrict pan to 2 lines steps + * for NV12 and NV21. + */ + info->fix = sh_mobile_lcdc_fix; + info->fix.smem_start = ch->dma_handle; + info->fix.smem_len = ch->fb_size; + info->fix.line_length = ch->pitch; + + if (ch->format->yuv) + info->fix.visual = FB_VISUAL_FOURCC; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + switch (ch->format->fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + info->fix.ypanstep = 2; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + info->fix.xpanstep = 2; + } + + /* Initialize variable screen information using the first mode as + * default. + */ + var = &info->var; + fb_videomode_to_var(var, modes); + var->width = ch->display.width; + var->height = ch->display.height; + var->xres_virtual = ch->xres_virtual; + var->yres_virtual = ch->yres_virtual; + var->activate = FB_ACTIVATE_NOW; + + /* Use the legacy API by default for RGB formats, and the FOURCC API + * for YUV formats. + */ + if (!ch->format->yuv) + var->bits_per_pixel = ch->format->bpp; + else + var->grayscale = ch->format->fourcc; + + ret = sh_mobile_lcdc_check_var(var, info); + if (ret) + return ret; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Backlight + */ + +static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev) +{ + struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); + int brightness = bdev->props.brightness; + + if (bdev->props.power != FB_BLANK_UNBLANK || + bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + ch->bl_brightness = brightness; + return ch->cfg->bl_info.set_brightness(brightness); +} + +static int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev) +{ + struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); + + return ch->bl_brightness; +} + +static int sh_mobile_lcdc_check_fb(struct backlight_device *bdev, + struct fb_info *info) +{ + return (info->bl_dev == bdev); +} + +static struct backlight_ops sh_mobile_lcdc_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = sh_mobile_lcdc_update_bl, + .get_brightness = sh_mobile_lcdc_get_brightness, + .check_fb = sh_mobile_lcdc_check_fb, +}; + +static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent, + struct sh_mobile_lcdc_chan *ch) +{ + struct backlight_device *bl; + + bl = backlight_device_register(ch->cfg->bl_info.name, parent, ch, + &sh_mobile_lcdc_bl_ops, NULL); + if (IS_ERR(bl)) { + dev_err(parent, "unable to register backlight device: %ld\n", + PTR_ERR(bl)); + return NULL; + } + + bl->props.max_brightness = ch->cfg->bl_info.max_brightness; + bl->props.brightness = bl->props.max_brightness; + backlight_update_status(bl); + + return bl; +} + +static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev) +{ + backlight_device_unregister(bdev); +} + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int sh_mobile_lcdc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + sh_mobile_lcdc_stop(platform_get_drvdata(pdev)); + return 0; +} + +static int sh_mobile_lcdc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); +} + +static int sh_mobile_lcdc_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev); + + /* turn off LCDC hardware */ + lcdc_write(priv, _LDCNT1R, 0); + + return 0; +} + +static int sh_mobile_lcdc_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev); + + __sh_mobile_lcdc_start(priv); + + return 0; +} + +static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { + .suspend = sh_mobile_lcdc_suspend, + .resume = sh_mobile_lcdc_resume, + .runtime_suspend = sh_mobile_lcdc_runtime_suspend, + .runtime_resume = sh_mobile_lcdc_runtime_resume, +}; + +/* ----------------------------------------------------------------------------- + * Framebuffer notifier + */ + +/* locking: called with info->lock held */ +static int sh_mobile_lcdc_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct fb_event *event = data; + struct fb_info *info = event->info; + struct sh_mobile_lcdc_chan *ch = info->par; + + if (&ch->lcdc->notifier != nb) + return NOTIFY_DONE; + + dev_dbg(info->dev, "%s(): action = %lu, data = %p\n", + __func__, action, event->data); + + switch(action) { + case FB_EVENT_SUSPEND: + sh_mobile_lcdc_display_off(ch); + sh_mobile_lcdc_stop(ch->lcdc); + break; + case FB_EVENT_RESUME: + mutex_lock(&ch->open_lock); + sh_mobile_fb_reconfig(info); + mutex_unlock(&ch->open_lock); + + sh_mobile_lcdc_display_on(ch); + sh_mobile_lcdc_start(ch->lcdc); + } + + return NOTIFY_OK; +} + +/* ----------------------------------------------------------------------------- + * Probe/remove and driver init/exit + */ + +static const struct fb_videomode default_720p = { + .name = "HDMI 720p", + .xres = 1280, + .yres = 720, + + .left_margin = 220, + .right_margin = 110, + .hsync_len = 40, + + .upper_margin = 20, + .lower_margin = 5, + .vsync_len = 5, + + .pixclock = 13468, + .refresh = 60, + .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT, +}; + +static int sh_mobile_lcdc_remove(struct platform_device *pdev) +{ + struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev); + unsigned int i; + + fb_unregister_client(&priv->notifier); + + for (i = 0; i < ARRAY_SIZE(priv->overlays); i++) + sh_mobile_lcdc_overlay_fb_unregister(&priv->overlays[i]); + for (i = 0; i < ARRAY_SIZE(priv->ch); i++) + sh_mobile_lcdc_channel_fb_unregister(&priv->ch[i]); + + sh_mobile_lcdc_stop(priv); + + for (i = 0; i < ARRAY_SIZE(priv->overlays); i++) { + struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i]; + + sh_mobile_lcdc_overlay_fb_cleanup(ovl); + + if (ovl->fb_mem) + dma_free_coherent(&pdev->dev, ovl->fb_size, + ovl->fb_mem, ovl->dma_handle); + } + + for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { + struct sh_mobile_lcdc_chan *ch = &priv->ch[i]; + + if (ch->tx_dev) { + ch->tx_dev->lcdc = NULL; + module_put(ch->cfg->tx_dev->dev.driver->owner); + } + + sh_mobile_lcdc_channel_fb_cleanup(ch); + + if (ch->fb_mem) + dma_free_coherent(&pdev->dev, ch->fb_size, + ch->fb_mem, ch->dma_handle); + } + + for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { + struct sh_mobile_lcdc_chan *ch = &priv->ch[i]; + + if (ch->bl) + sh_mobile_lcdc_bl_remove(ch->bl); + mutex_destroy(&ch->open_lock); + } + + if (priv->dot_clk) { + pm_runtime_disable(&pdev->dev); + clk_put(priv->dot_clk); + } + + if (priv->base) + iounmap(priv->base); + + if (priv->irq) + free_irq(priv->irq, priv); + kfree(priv); + return 0; +} + +static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch) +{ + int interface_type = ch->cfg->interface_type; + + switch (interface_type) { + case RGB8: + case RGB9: + case RGB12A: + case RGB12B: + case RGB16: + case RGB18: + case RGB24: + case SYS8A: + case SYS8B: + case SYS8C: + case SYS8D: + case SYS9: + case SYS12: + case SYS16A: + case SYS16B: + case SYS16C: + case SYS18: + case SYS24: + break; + default: + return -EINVAL; + } + + /* SUBLCD only supports SYS interface */ + if (lcdc_chan_is_sublcd(ch)) { + if (!(interface_type & LDMT1R_IFM)) + return -EINVAL; + + interface_type &= ~LDMT1R_IFM; + } + + ch->ldmt1r_value = interface_type; + return 0; +} + +static int +sh_mobile_lcdc_overlay_init(struct sh_mobile_lcdc_overlay *ovl) +{ + const struct sh_mobile_lcdc_format_info *format; + struct device *dev = ovl->channel->lcdc->dev; + int ret; + + if (ovl->cfg->fourcc == 0) + return 0; + + /* Validate the format. */ + format = sh_mobile_format_info(ovl->cfg->fourcc); + if (format == NULL) { + dev_err(dev, "Invalid FOURCC %08x\n", ovl->cfg->fourcc); + return -EINVAL; + } + + ovl->enabled = false; + ovl->mode = LCDC_OVERLAY_BLEND; + ovl->alpha = 255; + ovl->rop3 = 0; + ovl->pos_x = 0; + ovl->pos_y = 0; + + /* The default Y virtual resolution is twice the panel size to allow for + * double-buffering. + */ + ovl->format = format; + ovl->xres = ovl->cfg->max_xres; + ovl->xres_virtual = ovl->xres; + ovl->yres = ovl->cfg->max_yres; + ovl->yres_virtual = ovl->yres * 2; + + if (!format->yuv) + ovl->pitch = ovl->xres_virtual * format->bpp / 8; + else + ovl->pitch = ovl->xres_virtual; + + /* Allocate frame buffer memory. */ + ovl->fb_size = ovl->cfg->max_xres * ovl->cfg->max_yres + * format->bpp / 8 * 2; + ovl->fb_mem = dma_alloc_coherent(dev, ovl->fb_size, &ovl->dma_handle, + GFP_KERNEL); + if (!ovl->fb_mem) { + dev_err(dev, "unable to allocate buffer\n"); + return -ENOMEM; + } + + ret = sh_mobile_lcdc_overlay_fb_init(ovl); + if (ret < 0) + return ret; + + return 0; +} + +static int +sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch) +{ + const struct sh_mobile_lcdc_format_info *format; + const struct sh_mobile_lcdc_chan_cfg *cfg = ch->cfg; + struct device *dev = ch->lcdc->dev; + const struct fb_videomode *max_mode; + const struct fb_videomode *mode; + unsigned int num_modes; + unsigned int max_size; + unsigned int i; + + mutex_init(&ch->open_lock); + ch->notify = sh_mobile_lcdc_display_notify; + + /* Validate the format. */ + format = sh_mobile_format_info(cfg->fourcc); + if (format == NULL) { + dev_err(dev, "Invalid FOURCC %08x.\n", cfg->fourcc); + return -EINVAL; + } + + /* Iterate through the modes to validate them and find the highest + * resolution. + */ + max_mode = NULL; + max_size = 0; + + for (i = 0, mode = cfg->lcd_modes; i < cfg->num_modes; i++, mode++) { + unsigned int size = mode->yres * mode->xres; + + /* NV12/NV21 buffers must have even number of lines */ + if ((cfg->fourcc == V4L2_PIX_FMT_NV12 || + cfg->fourcc == V4L2_PIX_FMT_NV21) && (mode->yres & 0x1)) { + dev_err(dev, "yres must be multiple of 2 for " + "YCbCr420 mode.\n"); + return -EINVAL; + } + + if (size > max_size) { + max_mode = mode; + max_size = size; + } + } + + if (!max_size) + max_size = MAX_XRES * MAX_YRES; + else + dev_dbg(dev, "Found largest videomode %ux%u\n", + max_mode->xres, max_mode->yres); + + if (cfg->lcd_modes == NULL) { + mode = &default_720p; + num_modes = 1; + } else { + mode = cfg->lcd_modes; + num_modes = cfg->num_modes; + } + + /* Use the first mode as default. The default Y virtual resolution is + * twice the panel size to allow for double-buffering. + */ + ch->format = format; + ch->xres = mode->xres; + ch->xres_virtual = mode->xres; + ch->yres = mode->yres; + ch->yres_virtual = mode->yres * 2; + + if (!format->yuv) { + ch->colorspace = V4L2_COLORSPACE_SRGB; + ch->pitch = ch->xres_virtual * format->bpp / 8; + } else { + ch->colorspace = V4L2_COLORSPACE_REC709; + ch->pitch = ch->xres_virtual; + } + + ch->display.width = cfg->panel_cfg.width; + ch->display.height = cfg->panel_cfg.height; + ch->display.mode = *mode; + + /* Allocate frame buffer memory. */ + ch->fb_size = max_size * format->bpp / 8 * 2; + ch->fb_mem = dma_alloc_coherent(dev, ch->fb_size, &ch->dma_handle, + GFP_KERNEL); + if (ch->fb_mem == NULL) { + dev_err(dev, "unable to allocate buffer\n"); + return -ENOMEM; + } + + /* Initialize the transmitter device if present. */ + if (cfg->tx_dev) { + if (!cfg->tx_dev->dev.driver || + !try_module_get(cfg->tx_dev->dev.driver->owner)) { + dev_warn(dev, "unable to get transmitter device\n"); + return -EINVAL; + } + ch->tx_dev = platform_get_drvdata(cfg->tx_dev); + ch->tx_dev->lcdc = ch; + ch->tx_dev->def_mode = *mode; + } + + return sh_mobile_lcdc_channel_fb_init(ch, mode, num_modes); +} + +static int sh_mobile_lcdc_probe(struct platform_device *pdev) +{ + struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data; + struct sh_mobile_lcdc_priv *priv; + struct resource *res; + int num_channels; + int error; + int i; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i = platform_get_irq(pdev, 0); + if (!res || i < 0) { + dev_err(&pdev->dev, "cannot get platform resources\n"); + return -ENOENT; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "cannot allocate device data\n"); + return -ENOMEM; + } + + priv->dev = &pdev->dev; + priv->meram_dev = pdata->meram_dev; + platform_set_drvdata(pdev, priv); + + error = request_irq(i, sh_mobile_lcdc_irq, 0, + dev_name(&pdev->dev), priv); + if (error) { + dev_err(&pdev->dev, "unable to request irq\n"); + goto err1; + } + + priv->irq = i; + atomic_set(&priv->hw_usecnt, -1); + + for (i = 0, num_channels = 0; i < ARRAY_SIZE(pdata->ch); i++) { + struct sh_mobile_lcdc_chan *ch = priv->ch + num_channels; + + ch->lcdc = priv; + ch->cfg = &pdata->ch[i]; + + error = sh_mobile_lcdc_check_interface(ch); + if (error) { + dev_err(&pdev->dev, "unsupported interface type\n"); + goto err1; + } + init_waitqueue_head(&ch->frame_end_wait); + init_completion(&ch->vsync_completion); + + /* probe the backlight is there is one defined */ + if (ch->cfg->bl_info.max_brightness) + ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch); + + switch (pdata->ch[i].chan) { + case LCDC_CHAN_MAINLCD: + ch->enabled = LDCNT2R_ME; + ch->reg_offs = lcdc_offs_mainlcd; + num_channels++; + break; + case LCDC_CHAN_SUBLCD: + ch->enabled = LDCNT2R_SE; + ch->reg_offs = lcdc_offs_sublcd; + num_channels++; + break; + } + } + + if (!num_channels) { + dev_err(&pdev->dev, "no channels defined\n"); + error = -EINVAL; + goto err1; + } + + /* for dual channel LCDC (MAIN + SUB) force shared format setting */ + if (num_channels == 2) + priv->forced_fourcc = pdata->ch[0].fourcc; + + priv->base = ioremap_nocache(res->start, resource_size(res)); + if (!priv->base) + goto err1; + + error = sh_mobile_lcdc_setup_clocks(priv, pdata->clock_source); + if (error) { + dev_err(&pdev->dev, "unable to setup clocks\n"); + goto err1; + } + + /* Enable runtime PM. */ + pm_runtime_enable(&pdev->dev); + + for (i = 0; i < num_channels; i++) { + struct sh_mobile_lcdc_chan *ch = &priv->ch[i]; + + error = sh_mobile_lcdc_channel_init(ch); + if (error) + goto err1; + } + + for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) { + struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i]; + + ovl->cfg = &pdata->overlays[i]; + ovl->channel = &priv->ch[0]; + + error = sh_mobile_lcdc_overlay_init(ovl); + if (error) + goto err1; + } + + error = sh_mobile_lcdc_start(priv); + if (error) { + dev_err(&pdev->dev, "unable to start hardware\n"); + goto err1; + } + + for (i = 0; i < num_channels; i++) { + struct sh_mobile_lcdc_chan *ch = priv->ch + i; + + error = sh_mobile_lcdc_channel_fb_register(ch); + if (error) + goto err1; + } + + for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) { + struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i]; + + error = sh_mobile_lcdc_overlay_fb_register(ovl); + if (error) + goto err1; + } + + /* Failure ignored */ + priv->notifier.notifier_call = sh_mobile_lcdc_notify; + fb_register_client(&priv->notifier); + + return 0; +err1: + sh_mobile_lcdc_remove(pdev); + + return error; +} + +static struct platform_driver sh_mobile_lcdc_driver = { + .driver = { + .name = "sh_mobile_lcdc_fb", + .owner = THIS_MODULE, + .pm = &sh_mobile_lcdc_dev_pm_ops, + }, + .probe = sh_mobile_lcdc_probe, + .remove = sh_mobile_lcdc_remove, +}; + +module_platform_driver(sh_mobile_lcdc_driver); + +MODULE_DESCRIPTION("SuperH Mobile LCDC Framebuffer driver"); +MODULE_AUTHOR("Magnus Damm <damm@opensource.se>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/sh_mobile_lcdcfb.h b/drivers/video/fbdev/sh_mobile_lcdcfb.h new file mode 100644 index 000000000000..f839adef1d90 --- /dev/null +++ b/drivers/video/fbdev/sh_mobile_lcdcfb.h @@ -0,0 +1,112 @@ +#ifndef SH_MOBILE_LCDCFB_H +#define SH_MOBILE_LCDCFB_H + +#include <linux/completion.h> +#include <linux/fb.h> +#include <linux/mutex.h> +#include <linux/wait.h> + +/* per-channel registers */ +enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, + LDSM2R, LDSA1R, LDSA2R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, + LDHAJR, + NR_CH_REGS }; + +#define PALETTE_NR 16 + +struct backlight_device; +struct fb_info; +struct module; +struct sh_mobile_lcdc_chan; +struct sh_mobile_lcdc_entity; +struct sh_mobile_lcdc_format_info; +struct sh_mobile_lcdc_priv; + +#define SH_MOBILE_LCDC_DISPLAY_DISCONNECTED 0 +#define SH_MOBILE_LCDC_DISPLAY_CONNECTED 1 + +struct sh_mobile_lcdc_entity_ops { + /* Display */ + int (*display_on)(struct sh_mobile_lcdc_entity *entity); + void (*display_off)(struct sh_mobile_lcdc_entity *entity); +}; + +enum sh_mobile_lcdc_entity_event { + SH_MOBILE_LCDC_EVENT_DISPLAY_CONNECT, + SH_MOBILE_LCDC_EVENT_DISPLAY_DISCONNECT, + SH_MOBILE_LCDC_EVENT_DISPLAY_MODE, +}; + +struct sh_mobile_lcdc_entity { + struct module *owner; + const struct sh_mobile_lcdc_entity_ops *ops; + struct sh_mobile_lcdc_chan *lcdc; + struct fb_videomode def_mode; +}; + +/* + * struct sh_mobile_lcdc_chan - LCDC display channel + * + * @pan_y_offset: Panning linear offset in bytes (luma component) + * @base_addr_y: Frame buffer viewport base address (luma component) + * @base_addr_c: Frame buffer viewport base address (chroma component) + * @pitch: Frame buffer line pitch + */ +struct sh_mobile_lcdc_chan { + struct sh_mobile_lcdc_priv *lcdc; + struct sh_mobile_lcdc_entity *tx_dev; + const struct sh_mobile_lcdc_chan_cfg *cfg; + + unsigned long *reg_offs; + unsigned long ldmt1r_value; + unsigned long enabled; /* ME and SE in LDCNT2R */ + void *cache; + + struct mutex open_lock; /* protects the use counter */ + int use_count; + + void *fb_mem; + unsigned long fb_size; + + dma_addr_t dma_handle; + unsigned long pan_y_offset; + + unsigned long frame_end; + wait_queue_head_t frame_end_wait; + struct completion vsync_completion; + + const struct sh_mobile_lcdc_format_info *format; + u32 colorspace; + unsigned int xres; + unsigned int xres_virtual; + unsigned int yres; + unsigned int yres_virtual; + unsigned int pitch; + + unsigned long base_addr_y; + unsigned long base_addr_c; + unsigned int line_size; + + int (*notify)(struct sh_mobile_lcdc_chan *ch, + enum sh_mobile_lcdc_entity_event event, + const struct fb_videomode *mode, + const struct fb_monspecs *monspec); + + /* Backlight */ + struct backlight_device *bl; + unsigned int bl_brightness; + + /* FB */ + struct fb_info *info; + u32 pseudo_palette[PALETTE_NR]; + struct { + unsigned int width; + unsigned int height; + struct fb_videomode mode; + } display; + struct fb_deferred_io defio; + struct scatterlist *sglist; + int blank_status; +}; + +#endif diff --git a/drivers/video/fbdev/sh_mobile_meram.c b/drivers/video/fbdev/sh_mobile_meram.c new file mode 100644 index 000000000000..a297de5cc859 --- /dev/null +++ b/drivers/video/fbdev/sh_mobile_meram.c @@ -0,0 +1,759 @@ +/* + * SuperH Mobile MERAM Driver for SuperH Mobile LCDC Driver + * + * Copyright (c) 2011 Damian Hobson-Garcia <dhobsong@igel.co.jp> + * Takanari Hayama <taki@igel.co.jp> + * + * 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/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/genalloc.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> + +#include <video/sh_mobile_meram.h> + +/* ----------------------------------------------------------------------------- + * MERAM registers + */ + +#define MEVCR1 0x4 +#define MEVCR1_RST (1 << 31) +#define MEVCR1_WD (1 << 30) +#define MEVCR1_AMD1 (1 << 29) +#define MEVCR1_AMD0 (1 << 28) +#define MEQSEL1 0x40 +#define MEQSEL2 0x44 + +#define MExxCTL 0x400 +#define MExxCTL_BV (1 << 31) +#define MExxCTL_BSZ_SHIFT 28 +#define MExxCTL_MSAR_MASK (0x7ff << MExxCTL_MSAR_SHIFT) +#define MExxCTL_MSAR_SHIFT 16 +#define MExxCTL_NXT_MASK (0x1f << MExxCTL_NXT_SHIFT) +#define MExxCTL_NXT_SHIFT 11 +#define MExxCTL_WD1 (1 << 10) +#define MExxCTL_WD0 (1 << 9) +#define MExxCTL_WS (1 << 8) +#define MExxCTL_CB (1 << 7) +#define MExxCTL_WBF (1 << 6) +#define MExxCTL_WF (1 << 5) +#define MExxCTL_RF (1 << 4) +#define MExxCTL_CM (1 << 3) +#define MExxCTL_MD_READ (1 << 0) +#define MExxCTL_MD_WRITE (2 << 0) +#define MExxCTL_MD_ICB_WB (3 << 0) +#define MExxCTL_MD_ICB (4 << 0) +#define MExxCTL_MD_FB (7 << 0) +#define MExxCTL_MD_MASK (7 << 0) +#define MExxBSIZE 0x404 +#define MExxBSIZE_RCNT_SHIFT 28 +#define MExxBSIZE_YSZM1_SHIFT 16 +#define MExxBSIZE_XSZM1_SHIFT 0 +#define MExxMNCF 0x408 +#define MExxMNCF_KWBNM_SHIFT 28 +#define MExxMNCF_KRBNM_SHIFT 24 +#define MExxMNCF_BNM_SHIFT 16 +#define MExxMNCF_XBV (1 << 15) +#define MExxMNCF_CPL_YCBCR444 (1 << 12) +#define MExxMNCF_CPL_YCBCR420 (2 << 12) +#define MExxMNCF_CPL_YCBCR422 (3 << 12) +#define MExxMNCF_CPL_MSK (3 << 12) +#define MExxMNCF_BL (1 << 2) +#define MExxMNCF_LNM_SHIFT 0 +#define MExxSARA 0x410 +#define MExxSARB 0x414 +#define MExxSBSIZE 0x418 +#define MExxSBSIZE_HDV (1 << 31) +#define MExxSBSIZE_HSZ16 (0 << 28) +#define MExxSBSIZE_HSZ32 (1 << 28) +#define MExxSBSIZE_HSZ64 (2 << 28) +#define MExxSBSIZE_HSZ128 (3 << 28) +#define MExxSBSIZE_SBSIZZ_SHIFT 0 + +#define MERAM_MExxCTL_VAL(next, addr) \ + ((((next) << MExxCTL_NXT_SHIFT) & MExxCTL_NXT_MASK) | \ + (((addr) << MExxCTL_MSAR_SHIFT) & MExxCTL_MSAR_MASK)) +#define MERAM_MExxBSIZE_VAL(rcnt, yszm1, xszm1) \ + (((rcnt) << MExxBSIZE_RCNT_SHIFT) | \ + ((yszm1) << MExxBSIZE_YSZM1_SHIFT) | \ + ((xszm1) << MExxBSIZE_XSZM1_SHIFT)) + +static const unsigned long common_regs[] = { + MEVCR1, + MEQSEL1, + MEQSEL2, +}; +#define MERAM_REGS_SIZE ARRAY_SIZE(common_regs) + +static const unsigned long icb_regs[] = { + MExxCTL, + MExxBSIZE, + MExxMNCF, + MExxSARA, + MExxSARB, + MExxSBSIZE, +}; +#define ICB_REGS_SIZE ARRAY_SIZE(icb_regs) + +/* + * sh_mobile_meram_icb - MERAM ICB information + * @regs: Registers cache + * @index: ICB index + * @offset: MERAM block offset + * @size: MERAM block size in KiB + * @cache_unit: Bytes to cache per ICB + * @pixelformat: Video pixel format of the data stored in the ICB + * @current_reg: Which of Start Address Register A (0) or B (1) is in use + */ +struct sh_mobile_meram_icb { + unsigned long regs[ICB_REGS_SIZE]; + unsigned int index; + unsigned long offset; + unsigned int size; + + unsigned int cache_unit; + unsigned int pixelformat; + unsigned int current_reg; +}; + +#define MERAM_ICB_NUM 32 + +struct sh_mobile_meram_fb_plane { + struct sh_mobile_meram_icb *marker; + struct sh_mobile_meram_icb *cache; +}; + +struct sh_mobile_meram_fb_cache { + unsigned int nplanes; + struct sh_mobile_meram_fb_plane planes[2]; +}; + +/* + * sh_mobile_meram_priv - MERAM device + * @base: Registers base address + * @meram: MERAM physical address + * @regs: Registers cache + * @lock: Protects used_icb and icbs + * @used_icb: Bitmask of used ICBs + * @icbs: ICBs + * @pool: Allocation pool to manage the MERAM + */ +struct sh_mobile_meram_priv { + void __iomem *base; + unsigned long meram; + unsigned long regs[MERAM_REGS_SIZE]; + + struct mutex lock; + unsigned long used_icb; + struct sh_mobile_meram_icb icbs[MERAM_ICB_NUM]; + + struct gen_pool *pool; +}; + +/* settings */ +#define MERAM_GRANULARITY 1024 +#define MERAM_SEC_LINE 15 +#define MERAM_LINE_WIDTH 2048 + +/* ----------------------------------------------------------------------------- + * Registers access + */ + +#define MERAM_ICB_OFFSET(base, idx, off) ((base) + (off) + (idx) * 0x20) + +static inline void meram_write_icb(void __iomem *base, unsigned int idx, + unsigned int off, unsigned long val) +{ + iowrite32(val, MERAM_ICB_OFFSET(base, idx, off)); +} + +static inline unsigned long meram_read_icb(void __iomem *base, unsigned int idx, + unsigned int off) +{ + return ioread32(MERAM_ICB_OFFSET(base, idx, off)); +} + +static inline void meram_write_reg(void __iomem *base, unsigned int off, + unsigned long val) +{ + iowrite32(val, base + off); +} + +static inline unsigned long meram_read_reg(void __iomem *base, unsigned int off) +{ + return ioread32(base + off); +} + +/* ----------------------------------------------------------------------------- + * MERAM allocation and free + */ + +static unsigned long meram_alloc(struct sh_mobile_meram_priv *priv, size_t size) +{ + return gen_pool_alloc(priv->pool, size); +} + +static void meram_free(struct sh_mobile_meram_priv *priv, unsigned long mem, + size_t size) +{ + gen_pool_free(priv->pool, mem, size); +} + +/* ----------------------------------------------------------------------------- + * LCDC cache planes allocation, init, cleanup and free + */ + +/* Allocate ICBs and MERAM for a plane. */ +static int meram_plane_alloc(struct sh_mobile_meram_priv *priv, + struct sh_mobile_meram_fb_plane *plane, + size_t size) +{ + unsigned long mem; + unsigned long idx; + + idx = find_first_zero_bit(&priv->used_icb, 28); + if (idx == 28) + return -ENOMEM; + plane->cache = &priv->icbs[idx]; + + idx = find_next_zero_bit(&priv->used_icb, 32, 28); + if (idx == 32) + return -ENOMEM; + plane->marker = &priv->icbs[idx]; + + mem = meram_alloc(priv, size * 1024); + if (mem == 0) + return -ENOMEM; + + __set_bit(plane->marker->index, &priv->used_icb); + __set_bit(plane->cache->index, &priv->used_icb); + + plane->marker->offset = mem - priv->meram; + plane->marker->size = size; + + return 0; +} + +/* Free ICBs and MERAM for a plane. */ +static void meram_plane_free(struct sh_mobile_meram_priv *priv, + struct sh_mobile_meram_fb_plane *plane) +{ + meram_free(priv, priv->meram + plane->marker->offset, + plane->marker->size * 1024); + + __clear_bit(plane->marker->index, &priv->used_icb); + __clear_bit(plane->cache->index, &priv->used_icb); +} + +/* Is this a YCbCr(NV12, NV16 or NV24) colorspace? */ +static int is_nvcolor(int cspace) +{ + if (cspace == SH_MOBILE_MERAM_PF_NV || + cspace == SH_MOBILE_MERAM_PF_NV24) + return 1; + return 0; +} + +/* Set the next address to fetch. */ +static void meram_set_next_addr(struct sh_mobile_meram_priv *priv, + struct sh_mobile_meram_fb_cache *cache, + unsigned long base_addr_y, + unsigned long base_addr_c) +{ + struct sh_mobile_meram_icb *icb = cache->planes[0].marker; + unsigned long target; + + icb->current_reg ^= 1; + target = icb->current_reg ? MExxSARB : MExxSARA; + + /* set the next address to fetch */ + meram_write_icb(priv->base, cache->planes[0].cache->index, target, + base_addr_y); + meram_write_icb(priv->base, cache->planes[0].marker->index, target, + base_addr_y + cache->planes[0].marker->cache_unit); + + if (cache->nplanes == 2) { + meram_write_icb(priv->base, cache->planes[1].cache->index, + target, base_addr_c); + meram_write_icb(priv->base, cache->planes[1].marker->index, + target, base_addr_c + + cache->planes[1].marker->cache_unit); + } +} + +/* Get the next ICB address. */ +static void +meram_get_next_icb_addr(struct sh_mobile_meram_info *pdata, + struct sh_mobile_meram_fb_cache *cache, + unsigned long *icb_addr_y, unsigned long *icb_addr_c) +{ + struct sh_mobile_meram_icb *icb = cache->planes[0].marker; + unsigned long icb_offset; + + if (pdata->addr_mode == SH_MOBILE_MERAM_MODE0) + icb_offset = 0x80000000 | (icb->current_reg << 29); + else + icb_offset = 0xc0000000 | (icb->current_reg << 23); + + *icb_addr_y = icb_offset | (cache->planes[0].marker->index << 24); + if (cache->nplanes == 2) + *icb_addr_c = icb_offset + | (cache->planes[1].marker->index << 24); +} + +#define MERAM_CALC_BYTECOUNT(x, y) \ + (((x) * (y) + (MERAM_LINE_WIDTH - 1)) & ~(MERAM_LINE_WIDTH - 1)) + +/* Initialize MERAM. */ +static int meram_plane_init(struct sh_mobile_meram_priv *priv, + struct sh_mobile_meram_fb_plane *plane, + unsigned int xres, unsigned int yres, + unsigned int *out_pitch) +{ + struct sh_mobile_meram_icb *marker = plane->marker; + unsigned long total_byte_count = MERAM_CALC_BYTECOUNT(xres, yres); + unsigned long bnm; + unsigned int lcdc_pitch; + unsigned int xpitch; + unsigned int line_cnt; + unsigned int save_lines; + + /* adjust pitch to 1024, 2048, 4096 or 8192 */ + lcdc_pitch = (xres - 1) | 1023; + lcdc_pitch = lcdc_pitch | (lcdc_pitch >> 1); + lcdc_pitch = lcdc_pitch | (lcdc_pitch >> 2); + lcdc_pitch += 1; + + /* derive settings */ + if (lcdc_pitch == 8192 && yres >= 1024) { + lcdc_pitch = xpitch = MERAM_LINE_WIDTH; + line_cnt = total_byte_count >> 11; + *out_pitch = xres; + save_lines = plane->marker->size / 16 / MERAM_SEC_LINE; + save_lines *= MERAM_SEC_LINE; + } else { + xpitch = xres; + line_cnt = yres; + *out_pitch = lcdc_pitch; + save_lines = plane->marker->size / (lcdc_pitch >> 10) / 2; + save_lines &= 0xff; + } + bnm = (save_lines - 1) << 16; + + /* TODO: we better to check if we have enough MERAM buffer size */ + + /* set up ICB */ + meram_write_icb(priv->base, plane->cache->index, MExxBSIZE, + MERAM_MExxBSIZE_VAL(0x0, line_cnt - 1, xpitch - 1)); + meram_write_icb(priv->base, plane->marker->index, MExxBSIZE, + MERAM_MExxBSIZE_VAL(0xf, line_cnt - 1, xpitch - 1)); + + meram_write_icb(priv->base, plane->cache->index, MExxMNCF, bnm); + meram_write_icb(priv->base, plane->marker->index, MExxMNCF, bnm); + + meram_write_icb(priv->base, plane->cache->index, MExxSBSIZE, xpitch); + meram_write_icb(priv->base, plane->marker->index, MExxSBSIZE, xpitch); + + /* save a cache unit size */ + plane->cache->cache_unit = xres * save_lines; + plane->marker->cache_unit = xres * save_lines; + + /* + * Set MERAM for framebuffer + * + * we also chain the cache_icb and the marker_icb. + * we also split the allocated MERAM buffer between two ICBs. + */ + meram_write_icb(priv->base, plane->cache->index, MExxCTL, + MERAM_MExxCTL_VAL(plane->marker->index, marker->offset) + | MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM | + MExxCTL_MD_FB); + meram_write_icb(priv->base, plane->marker->index, MExxCTL, + MERAM_MExxCTL_VAL(plane->cache->index, marker->offset + + plane->marker->size / 2) | + MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM | + MExxCTL_MD_FB); + + return 0; +} + +static void meram_plane_cleanup(struct sh_mobile_meram_priv *priv, + struct sh_mobile_meram_fb_plane *plane) +{ + /* disable ICB */ + meram_write_icb(priv->base, plane->cache->index, MExxCTL, + MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF); + meram_write_icb(priv->base, plane->marker->index, MExxCTL, + MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF); + + plane->cache->cache_unit = 0; + plane->marker->cache_unit = 0; +} + +/* ----------------------------------------------------------------------------- + * MERAM operations + */ + +unsigned long sh_mobile_meram_alloc(struct sh_mobile_meram_info *pdata, + size_t size) +{ + struct sh_mobile_meram_priv *priv = pdata->priv; + + return meram_alloc(priv, size); +} +EXPORT_SYMBOL_GPL(sh_mobile_meram_alloc); + +void sh_mobile_meram_free(struct sh_mobile_meram_info *pdata, unsigned long mem, + size_t size) +{ + struct sh_mobile_meram_priv *priv = pdata->priv; + + meram_free(priv, mem, size); +} +EXPORT_SYMBOL_GPL(sh_mobile_meram_free); + +/* Allocate memory for the ICBs and mark them as used. */ +static struct sh_mobile_meram_fb_cache * +meram_cache_alloc(struct sh_mobile_meram_priv *priv, + const struct sh_mobile_meram_cfg *cfg, + int pixelformat) +{ + unsigned int nplanes = is_nvcolor(pixelformat) ? 2 : 1; + struct sh_mobile_meram_fb_cache *cache; + int ret; + + cache = kzalloc(sizeof(*cache), GFP_KERNEL); + if (cache == NULL) + return ERR_PTR(-ENOMEM); + + cache->nplanes = nplanes; + + ret = meram_plane_alloc(priv, &cache->planes[0], + cfg->icb[0].meram_size); + if (ret < 0) + goto error; + + cache->planes[0].marker->current_reg = 1; + cache->planes[0].marker->pixelformat = pixelformat; + + if (cache->nplanes == 1) + return cache; + + ret = meram_plane_alloc(priv, &cache->planes[1], + cfg->icb[1].meram_size); + if (ret < 0) { + meram_plane_free(priv, &cache->planes[0]); + goto error; + } + + return cache; + +error: + kfree(cache); + return ERR_PTR(-ENOMEM); +} + +void *sh_mobile_meram_cache_alloc(struct sh_mobile_meram_info *pdata, + const struct sh_mobile_meram_cfg *cfg, + unsigned int xres, unsigned int yres, + unsigned int pixelformat, unsigned int *pitch) +{ + struct sh_mobile_meram_fb_cache *cache; + struct sh_mobile_meram_priv *priv = pdata->priv; + struct platform_device *pdev = pdata->pdev; + unsigned int nplanes = is_nvcolor(pixelformat) ? 2 : 1; + unsigned int out_pitch; + + if (priv == NULL) + return ERR_PTR(-ENODEV); + + if (pixelformat != SH_MOBILE_MERAM_PF_NV && + pixelformat != SH_MOBILE_MERAM_PF_NV24 && + pixelformat != SH_MOBILE_MERAM_PF_RGB) + return ERR_PTR(-EINVAL); + + dev_dbg(&pdev->dev, "registering %dx%d (%s)", xres, yres, + !pixelformat ? "yuv" : "rgb"); + + /* we can't handle wider than 8192px */ + if (xres > 8192) { + dev_err(&pdev->dev, "width exceeding the limit (> 8192)."); + return ERR_PTR(-EINVAL); + } + + if (cfg->icb[0].meram_size == 0) + return ERR_PTR(-EINVAL); + + if (nplanes == 2 && cfg->icb[1].meram_size == 0) + return ERR_PTR(-EINVAL); + + mutex_lock(&priv->lock); + + /* We now register the ICBs and allocate the MERAM regions. */ + cache = meram_cache_alloc(priv, cfg, pixelformat); + if (IS_ERR(cache)) { + dev_err(&pdev->dev, "MERAM allocation failed (%ld).", + PTR_ERR(cache)); + goto err; + } + + /* initialize MERAM */ + meram_plane_init(priv, &cache->planes[0], xres, yres, &out_pitch); + *pitch = out_pitch; + if (pixelformat == SH_MOBILE_MERAM_PF_NV) + meram_plane_init(priv, &cache->planes[1], + xres, (yres + 1) / 2, &out_pitch); + else if (pixelformat == SH_MOBILE_MERAM_PF_NV24) + meram_plane_init(priv, &cache->planes[1], + 2 * xres, (yres + 1) / 2, &out_pitch); + +err: + mutex_unlock(&priv->lock); + return cache; +} +EXPORT_SYMBOL_GPL(sh_mobile_meram_cache_alloc); + +void +sh_mobile_meram_cache_free(struct sh_mobile_meram_info *pdata, void *data) +{ + struct sh_mobile_meram_fb_cache *cache = data; + struct sh_mobile_meram_priv *priv = pdata->priv; + + mutex_lock(&priv->lock); + + /* Cleanup and free. */ + meram_plane_cleanup(priv, &cache->planes[0]); + meram_plane_free(priv, &cache->planes[0]); + + if (cache->nplanes == 2) { + meram_plane_cleanup(priv, &cache->planes[1]); + meram_plane_free(priv, &cache->planes[1]); + } + + kfree(cache); + + mutex_unlock(&priv->lock); +} +EXPORT_SYMBOL_GPL(sh_mobile_meram_cache_free); + +void +sh_mobile_meram_cache_update(struct sh_mobile_meram_info *pdata, void *data, + unsigned long base_addr_y, + unsigned long base_addr_c, + unsigned long *icb_addr_y, + unsigned long *icb_addr_c) +{ + struct sh_mobile_meram_fb_cache *cache = data; + struct sh_mobile_meram_priv *priv = pdata->priv; + + mutex_lock(&priv->lock); + + meram_set_next_addr(priv, cache, base_addr_y, base_addr_c); + meram_get_next_icb_addr(pdata, cache, icb_addr_y, icb_addr_c); + + mutex_unlock(&priv->lock); +} +EXPORT_SYMBOL_GPL(sh_mobile_meram_cache_update); + +/* ----------------------------------------------------------------------------- + * Power management + */ + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME) +static int sh_mobile_meram_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev); + unsigned int i, j; + + for (i = 0; i < MERAM_REGS_SIZE; i++) + priv->regs[i] = meram_read_reg(priv->base, common_regs[i]); + + for (i = 0; i < 32; i++) { + if (!test_bit(i, &priv->used_icb)) + continue; + for (j = 0; j < ICB_REGS_SIZE; j++) { + priv->icbs[i].regs[j] = + meram_read_icb(priv->base, i, icb_regs[j]); + /* Reset ICB on resume */ + if (icb_regs[j] == MExxCTL) + priv->icbs[i].regs[j] |= + MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF; + } + } + return 0; +} + +static int sh_mobile_meram_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev); + unsigned int i, j; + + for (i = 0; i < 32; i++) { + if (!test_bit(i, &priv->used_icb)) + continue; + for (j = 0; j < ICB_REGS_SIZE; j++) + meram_write_icb(priv->base, i, icb_regs[j], + priv->icbs[i].regs[j]); + } + + for (i = 0; i < MERAM_REGS_SIZE; i++) + meram_write_reg(priv->base, common_regs[i], priv->regs[i]); + return 0; +} +#endif /* CONFIG_PM_SLEEP || CONFIG_PM_RUNTIME */ + +static UNIVERSAL_DEV_PM_OPS(sh_mobile_meram_dev_pm_ops, + sh_mobile_meram_suspend, + sh_mobile_meram_resume, NULL); + +/* ----------------------------------------------------------------------------- + * Probe/remove and driver init/exit + */ + +static int sh_mobile_meram_probe(struct platform_device *pdev) +{ + struct sh_mobile_meram_priv *priv; + struct sh_mobile_meram_info *pdata = pdev->dev.platform_data; + struct resource *regs; + struct resource *meram; + unsigned int i; + int error; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -EINVAL; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + meram = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (regs == NULL || meram == NULL) { + dev_err(&pdev->dev, "cannot get platform resources\n"); + return -ENOENT; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "cannot allocate device data\n"); + return -ENOMEM; + } + + /* Initialize private data. */ + mutex_init(&priv->lock); + priv->used_icb = pdata->reserved_icbs; + + for (i = 0; i < MERAM_ICB_NUM; ++i) + priv->icbs[i].index = i; + + pdata->priv = priv; + pdata->pdev = pdev; + + /* Request memory regions and remap the registers. */ + if (!request_mem_region(regs->start, resource_size(regs), pdev->name)) { + dev_err(&pdev->dev, "MERAM registers region already claimed\n"); + error = -EBUSY; + goto err_req_regs; + } + + if (!request_mem_region(meram->start, resource_size(meram), + pdev->name)) { + dev_err(&pdev->dev, "MERAM memory region already claimed\n"); + error = -EBUSY; + goto err_req_meram; + } + + priv->base = ioremap_nocache(regs->start, resource_size(regs)); + if (!priv->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + error = -EFAULT; + goto err_ioremap; + } + + priv->meram = meram->start; + + /* Create and initialize the MERAM memory pool. */ + priv->pool = gen_pool_create(ilog2(MERAM_GRANULARITY), -1); + if (priv->pool == NULL) { + error = -ENOMEM; + goto err_genpool; + } + + error = gen_pool_add(priv->pool, meram->start, resource_size(meram), + -1); + if (error < 0) + goto err_genpool; + + /* initialize ICB addressing mode */ + if (pdata->addr_mode == SH_MOBILE_MERAM_MODE1) + meram_write_reg(priv->base, MEVCR1, MEVCR1_AMD1); + + platform_set_drvdata(pdev, priv); + pm_runtime_enable(&pdev->dev); + + dev_info(&pdev->dev, "sh_mobile_meram initialized."); + + return 0; + +err_genpool: + if (priv->pool) + gen_pool_destroy(priv->pool); + iounmap(priv->base); +err_ioremap: + release_mem_region(meram->start, resource_size(meram)); +err_req_meram: + release_mem_region(regs->start, resource_size(regs)); +err_req_regs: + mutex_destroy(&priv->lock); + kfree(priv); + + return error; +} + + +static int sh_mobile_meram_remove(struct platform_device *pdev) +{ + struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev); + struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct resource *meram = platform_get_resource(pdev, IORESOURCE_MEM, 1); + + pm_runtime_disable(&pdev->dev); + + gen_pool_destroy(priv->pool); + + iounmap(priv->base); + release_mem_region(meram->start, resource_size(meram)); + release_mem_region(regs->start, resource_size(regs)); + + mutex_destroy(&priv->lock); + + kfree(priv); + + return 0; +} + +static struct platform_driver sh_mobile_meram_driver = { + .driver = { + .name = "sh_mobile_meram", + .owner = THIS_MODULE, + .pm = &sh_mobile_meram_dev_pm_ops, + }, + .probe = sh_mobile_meram_probe, + .remove = sh_mobile_meram_remove, +}; + +module_platform_driver(sh_mobile_meram_driver); + +MODULE_DESCRIPTION("SuperH Mobile MERAM driver"); +MODULE_AUTHOR("Damian Hobson-Garcia / Takanari Hayama"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/simplefb.c b/drivers/video/fbdev/simplefb.c new file mode 100644 index 000000000000..210f3a02121a --- /dev/null +++ b/drivers/video/fbdev/simplefb.c @@ -0,0 +1,280 @@ +/* + * Simplest possible simple frame-buffer driver, as a platform device + * + * Copyright (c) 2013, Stephen Warren + * + * Based on q40fb.c, which was: + * Copyright (C) 2001 Richard Zidlicky <rz@linux-m68k.org> + * + * Also based on offb.c, which was: + * Copyright (C) 1997 Geert Uytterhoeven + * Copyright (C) 1996 Paul Mackerras + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_data/simplefb.h> +#include <linux/platform_device.h> + +static struct fb_fix_screeninfo simplefb_fix = { + .id = "simple", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo simplefb_var = { + .height = -1, + .width = -1, + .activate = FB_ACTIVATE_NOW, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static int simplefb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + u32 *pal = info->pseudo_palette; + u32 cr = red >> (16 - info->var.red.length); + u32 cg = green >> (16 - info->var.green.length); + u32 cb = blue >> (16 - info->var.blue.length); + u32 value; + + if (regno >= 16) + return -EINVAL; + + value = (cr << info->var.red.offset) | + (cg << info->var.green.offset) | + (cb << info->var.blue.offset); + if (info->var.transp.length > 0) { + u32 mask = (1 << info->var.transp.length) - 1; + mask <<= info->var.transp.offset; + value |= mask; + } + pal[regno] = value; + + return 0; +} + +static void simplefb_destroy(struct fb_info *info) +{ + if (info->screen_base) + iounmap(info->screen_base); +} + +static struct fb_ops simplefb_ops = { + .owner = THIS_MODULE, + .fb_destroy = simplefb_destroy, + .fb_setcolreg = simplefb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS; + +struct simplefb_params { + u32 width; + u32 height; + u32 stride; + struct simplefb_format *format; +}; + +static int simplefb_parse_dt(struct platform_device *pdev, + struct simplefb_params *params) +{ + struct device_node *np = pdev->dev.of_node; + int ret; + const char *format; + int i; + + ret = of_property_read_u32(np, "width", ¶ms->width); + if (ret) { + dev_err(&pdev->dev, "Can't parse width property\n"); + return ret; + } + + ret = of_property_read_u32(np, "height", ¶ms->height); + if (ret) { + dev_err(&pdev->dev, "Can't parse height property\n"); + return ret; + } + + ret = of_property_read_u32(np, "stride", ¶ms->stride); + if (ret) { + dev_err(&pdev->dev, "Can't parse stride property\n"); + return ret; + } + + ret = of_property_read_string(np, "format", &format); + if (ret) { + dev_err(&pdev->dev, "Can't parse format property\n"); + return ret; + } + params->format = NULL; + for (i = 0; i < ARRAY_SIZE(simplefb_formats); i++) { + if (strcmp(format, simplefb_formats[i].name)) + continue; + params->format = &simplefb_formats[i]; + break; + } + if (!params->format) { + dev_err(&pdev->dev, "Invalid format value\n"); + return -EINVAL; + } + + return 0; +} + +static int simplefb_parse_pd(struct platform_device *pdev, + struct simplefb_params *params) +{ + struct simplefb_platform_data *pd = dev_get_platdata(&pdev->dev); + int i; + + params->width = pd->width; + params->height = pd->height; + params->stride = pd->stride; + + params->format = NULL; + for (i = 0; i < ARRAY_SIZE(simplefb_formats); i++) { + if (strcmp(pd->format, simplefb_formats[i].name)) + continue; + + params->format = &simplefb_formats[i]; + break; + } + + if (!params->format) { + dev_err(&pdev->dev, "Invalid format value\n"); + return -EINVAL; + } + + return 0; +} + +static int simplefb_probe(struct platform_device *pdev) +{ + int ret; + struct simplefb_params params; + struct fb_info *info; + struct resource *mem; + + if (fb_get_options("simplefb", NULL)) + return -ENODEV; + + ret = -ENODEV; + if (dev_get_platdata(&pdev->dev)) + ret = simplefb_parse_pd(pdev, ¶ms); + else if (pdev->dev.of_node) + ret = simplefb_parse_dt(pdev, ¶ms); + + if (ret) + return ret; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + return -EINVAL; + } + + info = framebuffer_alloc(sizeof(u32) * 16, &pdev->dev); + if (!info) + return -ENOMEM; + platform_set_drvdata(pdev, info); + + info->fix = simplefb_fix; + info->fix.smem_start = mem->start; + info->fix.smem_len = resource_size(mem); + info->fix.line_length = params.stride; + + info->var = simplefb_var; + info->var.xres = params.width; + info->var.yres = params.height; + info->var.xres_virtual = params.width; + info->var.yres_virtual = params.height; + info->var.bits_per_pixel = params.format->bits_per_pixel; + info->var.red = params.format->red; + info->var.green = params.format->green; + info->var.blue = params.format->blue; + info->var.transp = params.format->transp; + + info->apertures = alloc_apertures(1); + if (!info->apertures) { + framebuffer_release(info); + return -ENOMEM; + } + info->apertures->ranges[0].base = info->fix.smem_start; + info->apertures->ranges[0].size = info->fix.smem_len; + + info->fbops = &simplefb_ops; + info->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE; + info->screen_base = ioremap_wc(info->fix.smem_start, + info->fix.smem_len); + if (!info->screen_base) { + framebuffer_release(info); + return -ENODEV; + } + info->pseudo_palette = (void *)(info + 1); + + dev_info(&pdev->dev, "framebuffer at 0x%lx, 0x%x bytes, mapped to 0x%p\n", + info->fix.smem_start, info->fix.smem_len, + info->screen_base); + dev_info(&pdev->dev, "format=%s, mode=%dx%dx%d, linelength=%d\n", + params.format->name, + info->var.xres, info->var.yres, + info->var.bits_per_pixel, info->fix.line_length); + + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to register simplefb: %d\n", ret); + iounmap(info->screen_base); + framebuffer_release(info); + return ret; + } + + dev_info(&pdev->dev, "fb%d: simplefb registered!\n", info->node); + + return 0; +} + +static int simplefb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + + unregister_framebuffer(info); + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id simplefb_of_match[] = { + { .compatible = "simple-framebuffer", }, + { }, +}; +MODULE_DEVICE_TABLE(of, simplefb_of_match); + +static struct platform_driver simplefb_driver = { + .driver = { + .name = "simple-framebuffer", + .owner = THIS_MODULE, + .of_match_table = simplefb_of_match, + }, + .probe = simplefb_probe, + .remove = simplefb_remove, +}; +module_platform_driver(simplefb_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@wwwdotorg.org>"); +MODULE_DESCRIPTION("Simple framebuffer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/sis/300vtbl.h b/drivers/video/fbdev/sis/300vtbl.h new file mode 100644 index 000000000000..e4b4a2626da4 --- /dev/null +++ b/drivers/video/fbdev/sis/300vtbl.h @@ -0,0 +1,1072 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Register settings for SiS 300 series + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +static const struct SiS_Ext SiS300_EModeIDTable[] = +{ + {0x6a,0x2212,0x0102,SIS_RI_800x600, 0x00,0x00,0x00,0x00,0x00,-1}, /* 800x600x? */ + {0x2e,0x0a1b,0x0101,SIS_RI_640x480, 0x00,0x00,0x00,0x00,0x08,-1}, + {0x2f,0x021b,0x0100,SIS_RI_640x400, 0x00,0x00,0x00,0x00,0x10,-1}, /* 640x400x8 */ + {0x30,0x2a1b,0x0103,SIS_RI_800x600, 0x00,0x00,0x00,0x00,0x00,-1}, + {0x31,0x4a1b,0x0000,SIS_RI_720x480, 0x00,0x00,0x00,0x00,0x11,-1}, /* 720x480x8 */ + {0x32,0x6a1b,0x0000,SIS_RI_720x576, 0x00,0x00,0x00,0x00,0x12,-1}, /* 720x576x8 */ + {0x33,0x4a1d,0x0000,SIS_RI_720x480, 0x00,0x00,0x00,0x00,0x11,-1}, /* 720x480x16 */ + {0x34,0x6a1d,0x0000,SIS_RI_720x576, 0x00,0x00,0x00,0x00,0x12,-1}, /* 720x576x16 */ + {0x35,0x4a1f,0x0000,SIS_RI_720x480, 0x00,0x00,0x00,0x00,0x11,-1}, /* 720x480x32 */ + {0x36,0x6a1f,0x0000,SIS_RI_720x576, 0x00,0x00,0x00,0x00,0x12,-1}, /* 720x576x32 */ + {0x37,0x0212,0x0104,SIS_RI_1024x768, 0x00,0x00,0x00,0x00,0x13,-1}, /* 1024x768x? */ + {0x38,0x0a1b,0x0105,SIS_RI_1024x768, 0x00,0x00,0x00,0x00,0x13,-1}, /* 1024x768x8 */ + {0x3a,0x0e3b,0x0107,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a,-1}, /* 1280x1024x8 */ + {0x3c,0x063b,0x0130,SIS_RI_1600x1200,0x00,0x00,0x00,0x00,0x1e,-1}, + {0x3d,0x067d,0x0131,SIS_RI_1600x1200,0x00,0x00,0x00,0x00,0x1e,-1}, + {0x40,0x921c,0x010d,SIS_RI_320x200, 0x00,0x00,0x00,0x00,0x23,-1}, /* 320x200x15 */ + {0x41,0x921d,0x010e,SIS_RI_320x200, 0x00,0x00,0x00,0x00,0x23,-1}, /* 320x200x16 */ + {0x43,0x0a1c,0x0110,SIS_RI_640x480, 0x00,0x00,0x00,0x00,0x08,-1}, + {0x44,0x0a1d,0x0111,SIS_RI_640x480, 0x00,0x00,0x00,0x00,0x08,-1}, + {0x46,0x2a1c,0x0113,SIS_RI_800x600, 0x00,0x00,0x00,0x00,0x00,-1}, /* 800x600x15 */ + {0x47,0x2a1d,0x0114,SIS_RI_800x600, 0x00,0x00,0x00,0x00,0x00,-1}, /* 800x600x16 */ + {0x49,0x0a3c,0x0116,SIS_RI_1024x768, 0x00,0x00,0x00,0x00,0x13,-1}, + {0x4a,0x0a3d,0x0117,SIS_RI_1024x768, 0x00,0x00,0x00,0x00,0x13,-1}, + {0x4c,0x0e7c,0x0119,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a,-1}, + {0x4d,0x0e7d,0x011a,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a,-1}, + {0x50,0x921b,0x0132,SIS_RI_320x240, 0x00,0x00,0x00,0x00,0x24,-1}, /* 320x240x8 */ + {0x51,0xb21b,0x0133,SIS_RI_400x300, 0x00,0x00,0x00,0x00,0x25,-1}, /* 400x300x8 */ + {0x52,0x921b,0x0134,SIS_RI_512x384, 0x00,0x00,0x00,0x00,0x26,-1}, /* 512x384x8 */ + {0x56,0x921d,0x0135,SIS_RI_320x240, 0x00,0x00,0x00,0x00,0x24,-1}, /* 320x240x16 */ + {0x57,0xb21d,0x0136,SIS_RI_400x300, 0x00,0x00,0x00,0x00,0x25,-1}, /* 400x300x16 */ + {0x58,0x921d,0x0137,SIS_RI_512x384, 0x00,0x00,0x00,0x00,0x26,-1}, /* 512x384x16 */ + {0x59,0x921b,0x0138,SIS_RI_320x200, 0x00,0x00,0x00,0x00,0x23,-1}, /* 320x200x8 */ + {0x5c,0x921f,0x0000,SIS_RI_512x384, 0x00,0x00,0x00,0x00,0x26,-1}, /* 512x384x32 */ + {0x5d,0x021d,0x0139,SIS_RI_640x400, 0x00,0x00,0x00,0x00,0x10,-1}, /* 640x400x16 */ + {0x5e,0x021f,0x0000,SIS_RI_640x400, 0x00,0x00,0x00,0x00,0x10,-1}, /* 640x400x32 */ + {0x62,0x0a3f,0x013a,SIS_RI_640x480, 0x00,0x00,0x00,0x00,0x08,-1}, + {0x63,0x2a3f,0x013b,SIS_RI_800x600, 0x00,0x00,0x00,0x00,0x00,-1}, /* 800x600x32 */ + {0x64,0x0a7f,0x013c,SIS_RI_1024x768, 0x00,0x00,0x00,0x00,0x13,-1}, + {0x65,0x0eff,0x013d,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a,-1}, + {0x66,0x06ff,0x013e,SIS_RI_1600x1200,0x00,0x00,0x00,0x00,0x1e,-1}, + {0x68,0x067b,0x013f,SIS_RI_1920x1440,0x00,0x00,0x00,0x00,0x27,-1}, + {0x69,0x06fd,0x0140,SIS_RI_1920x1440,0x00,0x00,0x00,0x00,0x27,-1}, + {0x6b,0x07ff,0x0000,SIS_RI_1920x1440,0x00,0x00,0x00,0x00,0x27,-1}, + {0x6c,0x067b,0x0000,SIS_RI_2048x1536,0x00,0x00,0x00,0x00,0x28,-1}, /* 2048x1536x8 */ + {0x6d,0x06fd,0x0000,SIS_RI_2048x1536,0x00,0x00,0x00,0x00,0x28,-1}, /* 2048x1536x16 */ + {0x70,0x6a1b,0x0000,SIS_RI_800x480, 0x00,0x00,0x07,0x00,0x2d,-1}, /* 800x480x8 */ + {0x71,0x4a1b,0x0000,SIS_RI_1024x576, 0x00,0x00,0x00,0x00,0x30,-1}, /* 1024x576x8 */ + {0x74,0x4a1d,0x0000,SIS_RI_1024x576, 0x00,0x00,0x00,0x00,0x30,-1}, /* 1024x576x16 */ + {0x75,0x0e3d,0x0000,SIS_RI_1280x720, 0x00,0x00,0x00,0x00,0x33,-1}, /* 1280x720x16 */ + {0x76,0x6a1f,0x0000,SIS_RI_800x480, 0x00,0x00,0x07,0x00,0x2d,-1}, /* 800x480x32 */ + {0x77,0x4a3f,0x0000,SIS_RI_1024x576, 0x00,0x00,0x00,0x00,0x30,-1}, /* 1024x576x32 */ + {0x78,0x0eff,0x0000,SIS_RI_1280x720, 0x00,0x00,0x00,0x00,0x33,-1}, /* 1280x720x32 */ + {0x79,0x0e3b,0x0000,SIS_RI_1280x720, 0x00,0x00,0x00,0x00,0x33,-1}, /* 1280x720x8 */ + {0x7a,0x6a1d,0x0000,SIS_RI_800x480, 0x00,0x00,0x07,0x00,0x2d,-1}, /* 800x480x16 */ + {0x7c,0x0a3b,0x0000,SIS_RI_1280x960, 0x00,0x00,0x00,0x00,0x29,-1}, /* 1280x960x8 */ + {0x7d,0x0a7d,0x0000,SIS_RI_1280x960, 0x00,0x00,0x00,0x00,0x29,-1}, /* 1280x960x16 */ + {0x7e,0x0aff,0x0000,SIS_RI_1280x960, 0x00,0x00,0x00,0x00,0x29,-1}, /* 1280x960x32 */ + {0x20,0x4a1b,0x0000,SIS_RI_1024x600, 0x00,0x00,0x00,0x00,0x2b,-1}, /* 1024x600 */ + {0x21,0x4a3d,0x0000,SIS_RI_1024x600, 0x00,0x00,0x00,0x00,0x2b,-1}, + {0x22,0x4a7f,0x0000,SIS_RI_1024x600, 0x00,0x00,0x00,0x00,0x2b,-1}, + {0x23,0x4a1b,0x0000,SIS_RI_1152x768, 0x00,0x00,0x00,0x00,0x2c,-1}, /* 1152x768 */ + {0x24,0x4a3d,0x0000,SIS_RI_1152x768, 0x00,0x00,0x00,0x00,0x2c,-1}, + {0x25,0x4a7f,0x0000,SIS_RI_1152x768, 0x00,0x00,0x00,0x00,0x2c,-1}, + {0x29,0x4e1b,0x0000,SIS_RI_1152x864, 0x00,0x00,0x00,0x00,0x36,-1}, /* 1152x864 */ + {0x2a,0x4e3d,0x0000,SIS_RI_1152x864, 0x00,0x00,0x00,0x00,0x36,-1}, + {0x2b,0x4e7f,0x0000,SIS_RI_1152x864, 0x00,0x00,0x00,0x00,0x36,-1}, + {0x39,0x6a1b,0x0000,SIS_RI_848x480, 0x00,0x00,0x00,0x00,0x39,-1}, /* 848x480 */ + {0x3b,0x6a3d,0x0000,SIS_RI_848x480, 0x00,0x00,0x00,0x00,0x39,-1}, + {0x3e,0x6a7f,0x0000,SIS_RI_848x480, 0x00,0x00,0x00,0x00,0x39,-1}, + {0x3f,0x6a1b,0x0000,SIS_RI_856x480, 0x00,0x00,0x00,0x00,0x3b,-1}, /* 856x480 */ + {0x42,0x6a3d,0x0000,SIS_RI_856x480, 0x00,0x00,0x00,0x00,0x3b,-1}, + {0x45,0x6a7f,0x0000,SIS_RI_856x480, 0x00,0x00,0x00,0x00,0x3b,-1}, + {0x48,0x6a3b,0x0000,SIS_RI_1360x768, 0x00,0x00,0x00,0x00,0x3d,-1}, /* 1360x768 */ + {0x4b,0x6a7d,0x0000,SIS_RI_1360x768, 0x00,0x00,0x00,0x00,0x3d,-1}, + {0x4e,0x6aff,0x0000,SIS_RI_1360x768, 0x00,0x00,0x00,0x00,0x3d,-1}, + {0x4f,0x921f,0x0000,SIS_RI_320x200, 0x00,0x00,0x00,0x00,0x23,-1}, /* 320x200x32 */ + {0x53,0x921f,0x0000,SIS_RI_320x240, 0x00,0x00,0x00,0x00,0x24,-1}, /* 320x240x32 */ + {0x54,0xb21f,0x0000,SIS_RI_400x300, 0x00,0x00,0x00,0x00,0x25,-1}, /* 400x300x32 */ + {0x55,0x2e3b,0x0000,SIS_RI_1280x768, 0x00,0x00,0x00,0x00,0x3e,-1}, /* 1280x768 */ + {0x5a,0x2e7d,0x0000,SIS_RI_1280x768, 0x00,0x00,0x00,0x00,0x3e,-1}, + {0x5b,0x2eff,0x0000,SIS_RI_1280x768, 0x00,0x00,0x00,0x00,0x3e,-1}, + {0x5f,0x6a1b,0x0000,SIS_RI_768x576, 0x00,0x00,0x00,0x00,0x3f,-1}, /* 768x576x8 */ + {0x60,0x6a1d,0x0000,SIS_RI_768x576, 0x00,0x00,0x00,0x00,0x3f,-1}, /* 768x576x16 */ + {0x61,0x6a1f,0x0000,SIS_RI_768x576, 0x00,0x00,0x00,0x00,0x3f,-1}, /* 768x576x32 */ + {0x67,0x6e3b,0x0000,SIS_RI_1360x1024,0x00,0x00,0x00,0x00,0x40,-1}, /* 1360x1024x8 (BARCO) */ + {0x6f,0x6e7d,0x0000,SIS_RI_1360x1024,0x00,0x00,0x00,0x00,0x40,-1}, /* 1360x1024x16 (BARCO) */ + {0x72,0x6eff,0x0000,SIS_RI_1360x1024,0x00,0x00,0x00,0x00,0x40,-1}, /* 1360x1024x32 (BARCO) */ + {0xff,0x0000,0xffff,0, 0x00,0x00,0x00,0x00,0x00} +}; + +static const struct SiS_Ext2 SiS300_RefIndex[] = +{ + {0x085f,0x0d,0x03,0x05,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 00 */ + {0x0467,0x0e,0x04,0x05,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 01 */ + {0x0067,0x0f,0x07,0x48,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 02 - CRT1CRTC was 0x4f */ + {0x0067,0x10,0x06,0x8b,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 03 */ + {0x0147,0x11,0x08,0x00,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 04 */ + {0x0147,0x12,0x0c,0x00,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 05 */ + {0x0047,0x11,0x0e,0x00,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 06 - CRT1CRTC was 0x51 */ + {0x0047,0x11,0x13,0x00,0x05,0x6a, 800, 600, 0, 0x00, 0x00}, /* 07 */ + {0xc85f,0x05,0x00,0x04,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 08 */ + {0xc067,0x06,0x02,0x04,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 09 */ + {0xc067,0x07,0x02,0x47,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 0a */ + {0xc067,0x08,0x03,0x8a,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 0b */ + {0xc047,0x09,0x05,0x00,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 0c */ + {0xc047,0x0a,0x08,0x00,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 0d */ + {0xc047,0x0b,0x0a,0x00,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 0e */ + {0xc047,0x0c,0x10,0x00,0x04,0x2e, 640, 480, 0, 0x00, 0x00}, /* 0f */ + {0x487f,0x04,0x00,0x00,0x00,0x2f, 640, 400, 0, 0x4a, 0x49}, /* 10 */ + {0xc06f,0x31,0x01,0x06,0x13,0x31, 720, 480, 0, 0x00, 0x00}, /* 11 */ + {0x006f,0x32,0x4a,0x06,0x14,0x32, 720, 576, 0, 0x00, 0x00}, /* 12 */ /* 4a was 03 */ + {0x0187,0x15,0x05,0x00,0x06,0x37,1024, 768, 0, 0x00, 0x00}, /* 13 */ + {0xc877,0x16,0x09,0x06,0x06,0x37,1024, 768, 0, 0x00, 0x00}, /* 14 */ + {0xc067,0x17,0x0b,0x49,0x06,0x37,1024, 768, 0, 0x00, 0x00}, /* 15 - CRT1CRTC was 0x97 */ + {0x0267,0x18,0x0d,0x00,0x06,0x37,1024, 768, 0, 0x00, 0x00}, /* 16 */ + {0x0047,0x19,0x11,0x8c,0x06,0x37,1024, 768, 0, 0x00, 0x00}, /* 17 - CRT1CRTC was 0x59 */ + {0x0047,0x1a,0x12,0x00,0x06,0x37,1024, 768, 0, 0x00, 0x00}, /* 18 */ + {0x0007,0x1b,0x16,0x00,0x06,0x37,1024, 768, 0, 0x00, 0x00}, /* 19 - CRT1CRTC was 0x5b */ + {0x0387,0x1c,0x0d,0x00,0x07,0x3a,1280,1024, 0, 0x00, 0x00}, /* 1a - CRT1CRTC was 0x5c */ + {0x0077,0x1d,0x14,0x07,0x07,0x3a,1280,1024, 0, 0x00, 0x00}, /* 1b */ + {0x0047,0x1e,0x17,0x00,0x07,0x3a,1280,1024, 0, 0x00, 0x00}, /* 1c */ + {0x0007,0x1f,0x18,0x00,0x07,0x3a,1280,1024, 0, 0x00, 0x00}, /* 1d */ + {0x0007,0x20,0x19,0x00,0x00,0x3c,1600,1200, 0, 0x00, 0x00}, /* 1e - CRT1CRTC was 0x60 */ + {0x0007,0x21,0x1a,0x00,0x00,0x3c,1600,1200, 0, 0x00, 0x00}, /* 1f */ + {0x0007,0x22,0x1b,0x00,0x00,0x3c,1600,1200, 0, 0x00, 0x00}, /* 20 */ + {0x0007,0x23,0x1d,0x00,0x00,0x3c,1600,1200, 0, 0x00, 0x00}, /* 21 - CRT1CRTC was 0x63 */ + {0x0007,0x24,0x1e,0x00,0x00,0x3c,1600,1200, 0, 0x00, 0x00}, /* 22 */ + {0x407f,0x00,0x00,0x00,0x00,0x40, 320, 200, 0, 0x4b, 0x4b}, /* 23 */ + {0xc07f,0x01,0x00,0x04,0x04,0x50, 320, 240, 0, 0x00, 0x00}, /* 24 */ + {0x0077,0x02,0x04,0x05,0x05,0x51, 400, 300, 0, 0x00, 0x00}, /* 25 */ + {0xc877,0x03,0x09,0x06,0x06,0x52, 512, 384, 0, 0x00, 0x00}, /* 26 */ /* was c077 */ + {0x8207,0x25,0x1f,0x00,0x00,0x68,1920,1440, 0, 0x00, 0x00}, /* 27 */ + {0x0007,0x26,0x20,0x00,0x00,0x6c,2048,1536, 0, 0x00, 0x00}, /* 28 */ + {0x0067,0x27,0x14,0x08,0x0a,0x6e,1280, 960, 0, 0x00, 0x00}, /* 29 - 1280x960-60 */ + {0x0027,0x45,0x3c,0x08,0x0a,0x6e,1280, 960, 0, 0x00, 0x00}, /* 2a - 1280x960-85 */ + {0xc077,0x33,0x09,0x06,0x00,0x20,1024, 600, 0, 0x00, 0x00}, /* 2b */ + {0xc077,0x34,0x0b,0x06,0x00,0x23,1152, 768, 0, 0x00, 0x00}, /* 2c */ /* VCLK 0x09 */ + {0x0077,0x35,0x27,0x08,0x18,0x70, 800, 480, 0, 0x00, 0x00}, /* 2d */ + {0x0047,0x36,0x37,0x08,0x18,0x70, 800, 480, 0, 0x00, 0x00}, /* 2e */ + {0x0047,0x37,0x08,0x08,0x18,0x70, 800, 480, 0, 0x00, 0x00}, /* 2f */ + {0x0077,0x38,0x09,0x09,0x19,0x71,1024, 576, 0, 0x00, 0x00}, /* 30 */ + {0x0047,0x39,0x38,0x09,0x19,0x71,1024, 576, 0, 0x00, 0x00}, /* 31 */ + {0x0047,0x3a,0x11,0x09,0x19,0x71,1024, 576, 0, 0x00, 0x00}, /* 32 */ + {0x0077,0x3b,0x39,0x0a,0x0c,0x75,1280, 720, 0, 0x00, 0x00}, /* 33 */ + {0x0047,0x3c,0x3a,0x0a,0x0c,0x75,1280, 720, 0, 0x00, 0x00}, /* 34 */ + {0x0007,0x3d,0x3b,0x0a,0x0c,0x75,1280, 720, 0, 0x00, 0x00}, /* 35 */ + {0x0067,0x49,0x35,0x06,0x1a,0x29,1152, 864, 0, 0x00, 0x00}, /* 36 1152x864-60Hz */ + {0x0067,0x3e,0x34,0x06,0x1a,0x29,1152, 864, 0, 0x00, 0x00}, /* 37 1152x864-75Hz */ + {0x0047,0x44,0x3a,0x06,0x1a,0x29,1152, 864, 0, 0x00, 0x00}, /* 38 1152x864-85Hz */ + {0x00c7,0x3f,0x28,0x00,0x16,0x39, 848, 480, 0, 0x00, 0x00}, /* 39 848x480-38Hzi */ + {0xc067,0x40,0x3d,0x0b,0x16,0x39, 848, 480, 0, 0x00, 0x00}, /* 3a 848x480-60Hz */ + {0x00c7,0x41,0x28,0x00,0x17,0x3f, 856, 480, 0, 0x00, 0x00}, /* 3b 856x480-38Hzi */ + {0xc067,0x42,0x28,0x0c,0x17,0x3f, 856, 480, 0, 0x00, 0x00}, /* 3c 856x480-60Hz */ + {0x0067,0x43,0x3e,0x0d,0x1b,0x48,1360, 768, 0, 0x00, 0x00}, /* 3d 1360x768-60Hz */ + {0x0077,0x46,0x3f,0x08,0x08,0x55,1280, 768, 0, 0x00, 0x00}, /* 3e 1280x768-60Hz */ + {0x006f,0x47,0x4c,0x06,0x15,0x5f, 768, 576, 0, 0x00, 0x00}, /* 3f 768x576 */ + {0x0027,0x48,0x13,0x08,0x00,0x67,1360,1024, 0, 0x00, 0x00}, /* 40 1360x1024-59Hz (BARCO1366 only) */ + {0xffff, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00} +}; + +static const struct SiS_VBMode SiS300_VBModeIDTable[] = +{ + {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01}, + {0x01,0x00,0x00,0x00,0x01,0x00,0x01,0x02}, + {0x03,0x00,0x00,0x00,0x02,0x00,0x02,0x00}, + {0x03,0x00,0x00,0x00,0x02,0x00,0x02,0x01}, + {0x03,0x00,0x00,0x00,0x03,0x00,0x03,0x02}, + {0x05,0x00,0x00,0x01,0x04,0x00,0x00,0x00}, + {0x06,0x00,0x00,0x01,0x05,0x00,0x02,0x00}, + {0x07,0x00,0x00,0x00,0x03,0x00,0x03,0x01}, + {0x07,0x00,0x00,0x00,0x03,0x00,0x03,0x02}, + {0x0d,0x00,0x00,0x01,0x04,0x00,0x00,0x00}, + {0x0e,0x00,0x00,0x01,0x05,0x00,0x02,0x00}, + {0x0f,0x00,0x00,0x01,0x05,0x00,0x02,0x01}, + {0x10,0x00,0x00,0x01,0x05,0x00,0x02,0x01}, + {0x11,0x00,0x00,0x01,0x05,0x00,0x02,0x03}, + {0x12,0x00,0x00,0x01,0x05,0x00,0x02,0x03}, + {0x13,0x00,0x00,0x01,0x04,0x00,0x04,0x00}, + {0x6a,0x00,0x00,0x01,0x07,0x00,0x08,0x0a}, + {0x2e,0x00,0x00,0x01,0x05,0x00,0x06,0x08}, + {0x2f,0x00,0x00,0x01,0x05,0x00,0x06,0x06}, + {0x30,0x00,0x00,0x01,0x07,0x00,0x08,0x0a}, + {0x31,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x32,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x33,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x34,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x35,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x36,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x37,0x00,0x00,0x01,0x00,0x00,0x0a,0x0c}, + {0x38,0x00,0x00,0x01,0x00,0x00,0x0a,0x0c}, + {0x3a,0x00,0x00,0x01,0x00,0x00,0x0b,0x0d}, + {0x40,0x00,0x00,0x01,0x04,0x00,0x05,0x05}, + {0x41,0x00,0x00,0x01,0x04,0x00,0x05,0x05}, + {0x43,0x00,0x00,0x01,0x05,0x00,0x06,0x08}, + {0x44,0x00,0x00,0x01,0x05,0x00,0x06,0x08}, + {0x46,0x00,0x00,0x01,0x07,0x00,0x08,0x0a}, + {0x47,0x00,0x00,0x01,0x07,0x00,0x08,0x0a}, + {0x49,0x00,0x00,0x01,0x00,0x00,0x0a,0x0c}, + {0x4a,0x00,0x00,0x01,0x00,0x00,0x0a,0x0c}, + {0x4c,0x00,0x00,0x01,0x00,0x00,0x0b,0x0d}, + {0x4d,0x00,0x00,0x01,0x00,0x00,0x0b,0x0d}, + {0x4f,0x00,0x00,0x01,0x04,0x00,0x05,0x05}, + {0x50,0x00,0x00,0x01,0x04,0x00,0x05,0x07}, + {0x51,0x00,0x00,0x01,0x07,0x00,0x07,0x09}, + {0x52,0x00,0x00,0x01,0x00,0x00,0x09,0x0b}, + {0x53,0x00,0x00,0x01,0x04,0x00,0x05,0x07}, + {0x54,0x00,0x00,0x01,0x07,0x00,0x07,0x09}, + {0x56,0x00,0x00,0x01,0x04,0x00,0x05,0x07}, + {0x57,0x00,0x00,0x01,0x07,0x00,0x07,0x09}, + {0x58,0x00,0x00,0x01,0x00,0x00,0x09,0x0b}, + {0x59,0x00,0x00,0x01,0x04,0x00,0x05,0x05}, + {0x5c,0x00,0x00,0x01,0x00,0x00,0x09,0x0b}, + {0x5d,0x00,0x00,0x01,0x05,0x00,0x06,0x06}, + {0x5e,0x00,0x00,0x01,0x05,0x00,0x06,0x06}, + {0x5f,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x60,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x61,0x00,0x00,0x01,0x06,0x00,0x00,0x00}, + {0x62,0x00,0x00,0x01,0x05,0x00,0x06,0x08}, + {0x63,0x00,0x00,0x01,0x07,0x00,0x08,0x0a}, + {0x64,0x00,0x00,0x01,0x00,0x00,0x0a,0x0c}, + {0x65,0x00,0x00,0x01,0x00,0x00,0x0b,0x0d}, + {0x6c,0x00,0x00,0x01,0x00,0x00,0x0b,0x0d}, + {0x6d,0x00,0x00,0x01,0x00,0x00,0x0b,0x0d}, + {0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00} +}; + +static const struct SiS_CRT1Table SiS300_CRT1Table[] = +{ + {{0x2d,0x27,0x28,0x90,0x2c,0x80,0xbf,0x1f, /* 0x00 - 320x200 */ + 0x9c,0x8e,0x8f,0x96,0xb9,0x30,0x00,0x00, /* HRE [4],[15] is invalid - but correcting it does not work */ + 0x00}}, + {{0x2d,0x27,0x28,0x90,0x2c,0x80,0x0b,0x3e, /* 0x01 */ + 0xe9,0x8b,0xdf,0xe7,0x04,0x00,0x00,0x00, /* HRE [4],[15] is invalid - but correcting it does not work */ + 0x00}}, + {{0x3d,0x31,0x31,0x81,0x37,0x1f,0x72,0xf0, /* 0x02 */ + 0x58,0x8c,0x57,0x57,0x73,0x20,0x00,0x05, + 0x01}}, + {{0x4f,0x3f,0x3f,0x93,0x45,0x0d,0x24,0xf5, + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x01, + 0x01}}, + {{0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x9c,0x8e,0x8f,0x96,0xb9,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x4f,0x83,0x55,0x81,0x0b,0x3e, /* 0x05 - corrected 640x480-60 */ + 0xe9,0x8b,0xdf,0xe8,0x0c,0x00,0x00,0x05, + 0x00}}, + {{0x63,0x4f,0x4f,0x87,0x56,0x9b,0x06,0x3e, /* 0x06 - corrected 640x480-72 */ + 0xe8,0x8a,0xdf,0xe7,0x07,0x00,0x00,0x01, + 0x00}}, + {{0x64,0x4f,0x4f,0x88,0x55,0x9d,0xf2,0x1f, + 0xe0,0x83,0xdf,0xdf,0xf3,0x10,0x00,0x01, + 0x00}}, + {{0x63,0x4f,0x4f,0x87,0x5a,0x81,0xfb,0x1f, + 0xe0,0x83,0xdf,0xdf,0xfc,0x10,0x00,0x05, + 0x00}}, + {{0x67,0x4f,0x4f,0x8b,0x57,0x83,0x10,0x3e, /* 0x09 - corrected 640x480-100 */ + 0xe7,0x8d,0xdf,0xe6,0x11,0x00,0x00,0x05, + 0x00}}, + {{0x67,0x4f,0x4f,0x8b,0x57,0x83,0x10,0x3e, /* 0x0a - corrected 640x480-120 */ + 0xe7,0x8d,0xdf,0xe6,0x11,0x00,0x00,0x05, + 0x00}}, + {{0x63,0x4f,0x4f,0x87,0x56,0x9d,0xfb,0x1f, + 0xe0,0x83,0xdf,0xdf,0xfc,0x10,0x00,0x01, + 0x00}}, + {{0x65,0x4f,0x4f,0x89,0x57,0x9f,0xfb,0x1f, + 0xe6,0x8a,0xdf,0xdf,0xfc,0x10,0x00,0x01, /* Corrected VDE, VBE */ + 0x00}}, + {{0x7b,0x63,0x63,0x9f,0x6a,0x93,0x6f,0xf0, + 0x58,0x8a,0x57,0x57,0x70,0x20,0x00,0x05, + 0x01}}, + {{0x7f,0x63,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x57,0x73,0x20,0x00,0x06, + 0x01}}, + {{0x7d,0x63,0x63,0x81,0x6e,0x1d,0x98,0xf0, + 0x7c,0x82,0x57,0x57,0x99,0x00,0x00,0x06, + 0x01}}, + {{0x7f,0x63,0x63,0x83,0x69,0x13,0x6f,0xf0, + 0x58,0x8b,0x57,0x57,0x70,0x20,0x00,0x06, + 0x01}}, + {{0x7e,0x63,0x63,0x82,0x6b,0x13,0x75,0xf0, + 0x58,0x8b,0x57,0x57,0x76,0x20,0x00,0x06, + 0x01}}, + {{0x8c,0x63,0x63,0x87,0x72,0x16,0x7e,0xf0, + 0x59,0x8d,0x57,0x57,0x7f,0x00,0x00,0x06, + 0x01}}, + {{0x7e,0x63,0x63,0x82,0x6c,0x14,0x75,0xe0, + 0x58,0x0b,0x57,0x57,0x76,0x20,0x00,0x06, + 0x01}}, + {{0x7e,0x63,0x63,0x82,0x6c,0x14,0x75,0xe0, /* 0x14 */ + 0x58,0x0b,0x57,0x57,0x76,0x20,0x00,0x06, + 0x01}}, + {{0x99,0x7f,0x7f,0x9d,0x84,0x1a,0x96,0x1f, + 0x7f,0x83,0x7f,0x7f,0x97,0x10,0x00,0x02, + 0x00}}, + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x24,0xf5, + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x02, + 0x01}}, + {{0xa1,0x7f,0x7f,0x85,0x86,0x97,0x24,0xf5, + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x02, + 0x01}}, + {{0x9f,0x7f,0x7f,0x83,0x85,0x91,0x1e,0xf5, + 0x00,0x83,0xff,0xff,0x1f,0x10,0x00,0x02, + 0x01}}, + {{0xa7,0x7f,0x7f,0x8b,0x89,0x95,0x26,0xf5, + 0x00,0x83,0xff,0xff,0x27,0x10,0x00,0x02, + 0x01}}, + {{0x9f,0x7f,0x7f,0x83,0x83,0x93,0x1e,0xf5, /* 0x1a */ + 0x00,0x84,0xff,0xff,0x1f,0x10,0x00,0x02, + 0x01}}, + {{0xa2,0x7f,0x7f,0x86,0x84,0x94,0x37,0xf5, + 0x0b,0x82,0xff,0xff,0x38,0x10,0x00,0x02, + 0x01}}, + {{0xcf,0x9f,0x9f,0x93,0xb2,0x01,0x14,0xba, + 0x00,0x83,0xff,0xff,0x15,0x00,0x00,0x03, + 0x00}}, + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0x5a, + 0x00,0x83,0xff,0xff,0x29,0x09,0x00,0x07, + 0x01}}, + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0x5a, /* 0x1e */ + 0x00,0x83,0xff,0xff,0x29,0x09,0x00,0x07, + 0x01}}, + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0x5a, + 0x00,0x83,0xff,0xff,0x2f,0x09,0x00,0x07, + 0x01}}, + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, /* 36: 1600x1200x85Hz */ + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, + {{0x3f,0xef,0xef,0x83,0xfd,0x1a,0xda,0x1f, /* 37: 1920x1440x60Hz */ + 0xa0,0x84,0x9f,0x9f,0xdb,0x1f,0x01,0x01, + 0x00}}, + {{0x55,0xff,0xff,0x99,0x0d,0x0c,0x3e,0xba, + 0x00,0x84,0xff,0xff,0x3f,0x0f,0x41,0x05, + 0x00}}, + {{0xdc,0x9f,0x9f,0x80,0xaf,0x9d,0xe6,0xff, /* 0x27: 1280x960-60 - correct */ + 0xc0,0x83,0xbf,0xbf,0xe7,0x10,0x00,0x07, + 0x01}}, + {{0x7f,0x63,0x63,0x83,0x6c,0x1c,0x72,0xba, /* 0x28 */ + 0x27,0x8b,0xdf,0xdf,0x73,0x00,0x00,0x06, + 0x01}}, + {{0x7f,0x63,0x63,0x83,0x69,0x13,0x6f,0xba, + 0x26,0x89,0xdf,0xdf,0x6f,0x00,0x00,0x06, + 0x01}}, + {{0x7f,0x63,0x63,0x82,0x6b,0x13,0x75,0xba, + 0x29,0x8c,0xdf,0xdf,0x75,0x00,0x00,0x06, + 0x01}}, + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x24,0xf1, + 0xaf,0x85,0x3f,0x3f,0x25,0x30,0x00,0x02, + 0x01}}, + {{0x9f,0x7f,0x7f,0x83,0x85,0x91,0x1e,0xf1, + 0xad,0x81,0x3f,0x3f,0x1f,0x30,0x00,0x02, + 0x01}}, + {{0xa7,0x7f,0x7f,0x88,0x89,0x15,0x26,0xf1, + 0xb1,0x85,0x3f,0x3f,0x27,0x30,0x00,0x02, + 0x01}}, + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0xc4, + 0x7a,0x8e,0xcf,0xcf,0x29,0x21,0x00,0x07, + 0x01}}, + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0xd4, + 0x7a,0x8e,0xcf,0xcf,0x29,0x21,0x00,0x07, + 0x01}}, + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0xd4, + 0x7d,0x81,0xcf,0xcf,0x2f,0x21,0x00,0x07, + 0x01}}, + {{0x6b,0x59,0x59,0x8f,0x5e,0x8c,0x0b,0x3e, + 0xe9,0x8b,0xdf,0xe7,0x04,0x00,0x00,0x05, + 0x00}}, + {{0x6d,0x59,0x59,0x91,0x60,0x89,0x53,0xf0, /* 0x32: 720x576, corrected to 60Hz */ + 0x41,0x84,0x3f,0x3f,0x54,0x00,0x00,0x05, + 0x41}}, + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x1e,0xf1, /* 0x33 - 1024x600 */ + 0xae,0x85,0x57,0x57,0x1f,0x30,0x00,0x02, + 0x01}}, + {{0xa3,0x8f,0x8f,0x97,0x96,0x97,0x24,0xf5, /* 0x34 - 1152x768 - corrected */ + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x02, + 0x01}}, + {{0x7f,0x63,0x63,0x83,0x6c,0x1c,0x72,0xba, /* 0x35 */ + 0x27,0x8b,0xdf,0xdf,0x73,0x00,0x00,0x06, + 0x01}}, /* 0x35 */ + {{0x7f,0x63,0x63,0x83,0x69,0x13,0x6f,0xba, + 0x26,0x89,0xdf,0xdf,0x6f,0x00,0x00,0x06, + 0x01}}, /* 0x36 */ + {{0x7f,0x63,0x63,0x82,0x6b,0x13,0x75,0xba, + 0x29,0x8c,0xdf,0xdf,0x75,0x00,0x00,0x06, + 0x01}}, /* 0x37 */ + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x24,0xf1, + 0xaf,0x85,0x3f,0x3f,0x25,0x30,0x00,0x02, + 0x01}}, /* 0x38 */ + {{0x9f,0x7f,0x7f,0x83,0x85,0x91,0x1e,0xf1, + 0xad,0x81,0x3f,0x3f,0x1f,0x30,0x00,0x02, + 0x01}}, /* 0x39 */ + {{0xa7,0x7f,0x7f,0x88,0x89,0x95,0x26,0xf1, /* 95 was 15 - illegal HBE! */ + 0xb1,0x85,0x3f,0x3f,0x27,0x30,0x00,0x02, + 0x01}}, /* 0x3a */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0xc4, + 0x7a,0x8e,0xcf,0xcf,0x29,0x21,0x00,0x07, + 0x01}}, /* 0x3b */ + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0xd4, + 0x7a,0x8e,0xcf,0xcf,0x29,0x21,0x00,0x07, + 0x01}}, /* 0x3c */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0xd4, + 0x7d,0x81,0xcf,0xcf,0x2f,0x21,0x00,0x07, + 0x01}}, /* 0x3d */ + {{0xc3,0x8f,0x8f,0x87,0x9b,0x0b,0x82,0xef, /* 1152x864-75 */ + 0x60,0x83,0x5f,0x5f,0x83,0x10,0x00,0x07, + 0x01}}, /* 0x3e */ + {{0x86,0x69,0x69,0x8A,0x74,0x06,0x8C,0x15, /* 848x480-38i */ + 0x4F,0x83,0xEF,0xEF,0x8D,0x30,0x00,0x02, + 0x00}}, /* 0x3f */ + {{0x83,0x69,0x69,0x87,0x6f,0x1d,0x03,0x3E, /* 848x480-60 */ + 0xE5,0x8d,0xDF,0xe4,0x04,0x00,0x00,0x06, + 0x00}}, /* 0x40 */ + {{0x86,0x6A,0x6A,0x8A,0x74,0x06,0x8C,0x15, /* 856x480-38i */ + 0x4F,0x83,0xEF,0xEF,0x8D,0x30,0x00,0x02, + 0x00}}, /* 0x41 */ + {{0x81,0x6A,0x6A,0x85,0x70,0x00,0x0F,0x3E, /* 856x480-60 */ + 0xEB,0x8E,0xDF,0xDF,0x10,0x00,0x00,0x02, + 0x00}}, /* 0x42 */ + {{0xdd,0xa9,0xa9,0x81,0xb4,0x97,0x26,0xfd, /* 1360x768-60 */ + 0x01,0x8d,0xff,0x00,0x27,0x10,0x00,0x03, + 0x01}}, /* 0x43 */ + {{0xd9,0x8f,0x8f,0x9d,0xba,0x0a,0x8a,0xff, /* 1152x864-84 */ + 0x60,0x8b,0x5f,0x5f,0x8b,0x10,0x00,0x03, + 0x01}}, /* 0x44 */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0xf1,0xff, /* 1280x960-85 */ + 0xc0,0x83,0xbf,0xbf,0xf2,0x10,0x00,0x07, + 0x01}}, /* 0x45 */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x20,0xf5, /* 1280x768-60 */ + 0x03,0x88,0xff,0xff,0x21,0x10,0x00,0x07, + 0x01}}, /* 0x46 */ + {{0x75,0x5f,0x5f,0x99,0x66,0x90,0x53,0xf0, /* 768x576, corrected to 60Hz */ + 0x41,0x84,0x3f,0x3f,0x54,0x00,0x00,0x05, + 0x41}}, /* 0x47 */ + {{0xce,0xa9,0xa9,0x92,0xb1,0x07,0x28,0x52, /* 1360x1024 (Barco iQ Pro R300) */ + 0x02,0x8e,0xff,0x00,0x29,0x0d,0x00,0x03, + 0x00}}, /* 0x48 */ + {{0xcd,0x8f,0x8f,0x91,0x9b,0x1b,0x7a,0xff, /* 1152x864-60 */ + 0x64,0x8c,0x5f,0x62,0x7b,0x10,0x00,0x07, + 0x41}}, /* 0x49 */ + {{0x5c,0x4f,0x4f,0x80,0x57,0x80,0xa3,0x1f, /* fake 640x400@60Hz (for LCD and TV, not actually used) */ + 0x98,0x8c,0x8f,0x96,0xa4,0x30,0x00,0x05, + 0x40}}, /* 0x4a */ + {{0x2c,0x27,0x27,0x90,0x2d,0x92,0xa4,0x1f, /* fake 320x200@60Hz (for LCD and TV, not actually used) */ + 0x98,0x8c,0x8f,0x96,0xa5,0x30,0x00,0x04, + 0x00}} /* 0x4b */ +}; + +static const struct SiS_MCLKData SiS300_MCLKData_630[] = +{ + { 0x5a,0x64,0x80, 66}, + { 0xb3,0x45,0x80, 83}, + { 0x37,0x61,0x80,100}, + { 0x37,0x22,0x80,133}, + { 0x37,0x61,0x80,100}, + { 0x37,0x61,0x80,100}, + { 0x37,0x61,0x80,100}, + { 0x37,0x61,0x80,100} +}; + +static const struct SiS_MCLKData SiS300_MCLKData_300[] = +{ + { 0x68,0x43,0x80,125}, + { 0x68,0x43,0x80,125}, + { 0x68,0x43,0x80,125}, + { 0x37,0x61,0x80,100}, + { 0x37,0x61,0x80,100}, + { 0x37,0x61,0x80,100}, + { 0x37,0x61,0x80,100}, + { 0x37,0x61,0x80,100} +}; + +static struct SiS_VCLKData SiS300_VCLKData[] = +{ + { 0x1b,0xe1, 25}, /* 0x00 */ + { 0x4e,0xe4, 28}, /* 0x01 */ + { 0x57,0xe4, 32}, /* 0x02 */ + { 0xc3,0xc8, 36}, /* 0x03 */ + { 0x42,0xc3, 40}, /* 0x04 */ + { 0x5d,0xc4, 45}, /* 0x05 */ + { 0x52,0x65, 50}, /* 0x06 */ + { 0x53,0x65, 50}, /* 0x07 */ + { 0x6d,0x66, 56}, /* 0x08 */ + { 0x5a,0x64, 65}, /* 0x09 */ + { 0x46,0x44, 68}, /* 0x0a */ + { 0x3e,0x43, 75}, /* 0x0b */ + { 0x6d,0x46, 76}, /* 0x0c */ /* 800x600 | LVDS_2(CH), MITAC(CH); - 730, A901(301B): 0xb1,0x46, 76 */ + { 0x41,0x43, 79}, /* 0x0d */ + { 0x31,0x42, 79}, /* 0x0e */ + { 0x46,0x25, 85}, /* 0x0f */ + { 0x78,0x29, 87}, /* 0x10 */ + { 0x62,0x44, 95}, /* 0x11 */ + { 0x2b,0x22,105}, /* 0x12 */ + { 0x49,0x24,106}, /* 0x13 */ + { 0xc3,0x28,108}, /* 0x14 */ + { 0x3c,0x23,109}, /* 0x15 */ + { 0xf7,0x2c,132}, /* 0x16 */ + { 0xd4,0x28,136}, /* 0x17 */ + { 0x41,0x05,158}, /* 0x18 */ + { 0x43,0x05,162}, /* 0x19 */ + { 0xe1,0x0f,175}, /* 0x1a */ + { 0xfc,0x12,189}, /* 0x1b */ + { 0xde,0x26,194}, /* 0x1c */ + { 0x54,0x05,203}, /* 0x1d */ + { 0x3f,0x03,230}, /* 0x1e */ + { 0x30,0x02,234}, /* 0x1f */ + { 0x24,0x01,266}, /* 0x20 */ + { 0x52,0x2a, 54}, /* 0x21 */ /* 301 TV */ + { 0x52,0x6a, 27}, /* 0x22 */ /* 301 TV */ + { 0x62,0x24, 70}, /* 0x23 */ /* 301 TV */ + { 0x62,0x64, 70}, /* 0x24 */ /* 301 TV */ + { 0xa8,0x4c, 30}, /* 0x25 */ /* 301 TV */ + { 0x20,0x26, 33}, /* 0x26 */ /* 301 TV */ + { 0x31,0xc2, 39}, /* 0x27 */ + { 0xbf,0xc8, 35}, /* 0x28 */ /* 856x480 */ + { 0x60,0x36, 30}, /* 0x29 */ /* CH/UNTSC TEXT | LVDS_2(CH) - 730, A901(301B), Mitac(CH): 0xe0, 0xb6, 30 */ + { 0x40,0x4a, 28}, /* 0x2a */ /* CH-TV */ + { 0x9f,0x46, 44}, /* 0x2b */ /* CH-TV */ + { 0x97,0x2c, 26}, /* 0x2c */ /* CH-TV */ + { 0x44,0xe4, 25}, /* 0x2d */ /* CH-TV */ + { 0x7e,0x32, 47}, /* 0x2e */ /* CH-TV */ + { 0x8a,0x24, 31}, /* 0x2f */ /* CH/PAL TEXT | LVDS_2(CH), Mitac(CH) - 730, A901(301B): 0x57, 0xe4, 31 */ + { 0x97,0x2c, 26}, /* 0x30 */ /* CH-TV */ + { 0xce,0x3c, 39}, /* 0x31 */ /* CH-TV */ + { 0x52,0x4a, 36}, /* 0x32 */ /* CH/PAL 800x600 5/6 */ + { 0x34,0x61, 95}, /* 0x33 */ + { 0x78,0x27,108}, /* 0x34 */ /* Replacement for index 0x14 for 630 (?) */ + { 0x70,0x28, 90}, /* 0x35 */ /* 1152x864@60 */ + { 0x45,0x6b, 21}, /* 0x36 */ /* Chrontel SuperOverscan */ + { 0x52,0xe2, 49}, /* 0x37 */ /* 16:9 modes */ + { 0x2b,0x61, 78}, /* 0x38 */ /* 16:9 modes */ + { 0x70,0x44,108}, /* 0x39 */ /* 16:9 modes */ + { 0x54,0x42,135}, /* 0x3a */ /* 16:9 modes */ + { 0x41,0x22,157}, /* 0x3b */ /* 16:9 modes */ + { 0x52,0x07,149}, /* 0x3c */ /* 1280x960-85 */ + { 0x62,0xc6, 34}, /* 0x3d */ /* 848x480-60 */ + { 0x30,0x23, 88}, /* 0x3e */ /* 1360x768-60 */ + { 0x70,0x29, 81}, /* 0x3f */ /* 1280x768-60 */ + { 0x72,0x2a, 76}, /* 0x40 */ /* test for SiS730 --- LIMIT for table (&0x3f) */ + { 0x15,0x21, 79}, /* 0x41 */ /* test for SiS730 */ + { 0xa1,0x42,108}, /* 0x42 */ /* 1280x960 LCD */ + { 0x37,0x61,100}, /* 0x43 */ /* 1280x960 LCD */ + { 0xe3,0x9a,106}, /* 0x44 */ /* 1360x1024 - special for Barco iQ R300 */ + { 0xe2,0x46,135}, /* 0x45 */ /* 1280x1024-75, better clock for VGA2 */ + { 0x70,0x29, 81}, /* 0x46 */ /* unused */ + { 0, 0, 0}, /* 0x47 custom (will be filled out) */ + { 0xce,0x25,189}, /* 0x48 */ /* Replacement for index 0x1b for 730 (and 540?) */ + { 0x15,0xe1, 20}, /* 0x49 */ /* 640x400@60 (fake, not actually used) */ + { 0x5f,0xc6, 33}, /* 0x4a */ /* 720x576@60 */ + { 0x37,0x5a, 10}, /* 0x4b */ /* 320x200@60 (fake, not actually used) */ + { 0x2b,0xc2, 35} /* 0x4c */ /* 768@576@60 */ +}; + +static const unsigned char SiS300_SR15[4 * 8] = +{ + 0x01,0x09,0xa3,0x00, + 0x43,0x43,0x43,0x00, + 0x1e,0x1e,0x1e,0x00, + 0x2a,0x2a,0x2a,0x00, + 0x06,0x06,0x06,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 +}; + +static const struct SiS_PanelDelayTbl SiS300_PanelDelayTbl[] = +{ + {{0x05,0xaa}}, + {{0x05,0x14}}, + {{0x05,0x36}}, + {{0x05,0x14}}, + {{0x05,0x14}}, + {{0x05,0x14}}, + {{0x05,0x90}}, + {{0x05,0x90}}, + {{0x05,0x14}}, + {{0x05,0x14}}, + {{0x05,0x14}}, + {{0x05,0x14}}, + {{0x20,0x80}}, + {{0x05,0x14}}, + {{0x05,0x40}}, + {{0x05,0x60}} +}; + +/**************************************************************/ +/* SIS VIDEO BRIDGE ----------------------------------------- */ +/**************************************************************/ + +static const struct SiS_LCDData SiS300_St2LCD1024x768Data[] = +{ + { 62, 25, 800, 546,1344, 806}, + { 32, 15, 930, 546,1344, 806}, + { 32, 15, 930, 546,1344, 806}, + { 104, 45, 945, 496,1344, 806}, + { 62, 25, 800, 546,1344, 806}, + { 31, 18,1008, 624,1344, 806}, + { 1, 1,1344, 806,1344, 806} +}; + +static const struct SiS_LCDData SiS300_ExtLCD1024x768Data[] = +{ + { 12, 5, 896, 512,1344, 806}, + { 12, 5, 896, 510,1344, 806}, + { 32, 15,1008, 505,1344, 806}, + { 32, 15,1008, 514,1344, 806}, + { 12, 5, 896, 500,1344, 806}, + { 42, 25,1024, 625,1344, 806}, + { 1, 1,1344, 806,1344, 806}, + { 12, 5, 896, 500,1344, 806}, + { 42, 25,1024, 625,1344, 806}, + { 1, 1,1344, 806,1344, 806}, + { 12, 5, 896, 500,1344, 806}, + { 42, 25,1024, 625,1344, 806}, + { 1, 1,1344, 806,1344, 806} +}; + +static const struct SiS_LCDData SiS300_St2LCD1280x1024Data[] = +{ + { 22, 5, 800, 510,1650,1088}, + { 22, 5, 800, 510,1650,1088}, + { 176, 45, 900, 510,1650,1088}, + { 176, 45, 900, 510,1650,1088}, + { 22, 5, 800, 510,1650,1088}, + { 13, 5,1024, 675,1560,1152}, + { 16, 9,1266, 804,1688,1072}, + { 1, 1,1688,1066,1688,1066} +}; + +static const struct SiS_LCDData SiS300_ExtLCD1280x1024Data[] = +{ + { 211, 60,1024, 501,1688,1066}, + { 211, 60,1024, 508,1688,1066}, + { 211, 60,1024, 501,1688,1066}, + { 211, 60,1024, 508,1688,1066}, + { 211, 60,1024, 500,1688,1066}, + { 211, 75,1024, 625,1688,1066}, + { 211, 120,1280, 798,1688,1066}, + { 1, 1,1688,1066,1688,1066} +}; + +static const struct SiS_Part2PortTbl SiS300_CRT2Part2_1024x768_1[] = +{ /* VESA Timing */ + {{0x21,0x12,0xbf,0xe4,0xc0,0x21,0x45,0x09,0x00,0xa9,0x09,0x04}}, + {{0x2c,0x12,0x9a,0xae,0x88,0x21,0x45,0x09,0x00,0xa9,0x09,0x04}}, + {{0x21,0x12,0xbf,0xe4,0xc0,0x21,0x45,0x09,0x00,0xa9,0x09,0x04}}, + {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + {{0x22,0x13,0xfe,0x25,0xff,0x21,0x45,0x0a,0x00,0xa9,0x0d,0x04}}, + {{0x22,0x13,0xfe,0x25,0xff,0x21,0x45,0x0a,0x00,0xa9,0x0d,0x04}}, + {{0x22,0x13,0xfe,0x25,0xff,0x21,0x45,0x0a,0x00,0xa9,0x0d,0x04}} +}; + +static const struct SiS_Part2PortTbl SiS300_CRT2Part2_1024x768_2[] = +{ /* Non-VESA */ + {{0x28,0x12,0xa3,0xd0,0xaa,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x2c,0x12,0x9a,0xae,0x88,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x28,0x12,0xa3,0xd0,0xaa,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x2c,0x12,0x9a,0xae,0x88,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x28,0x13,0xe7,0x0b,0xe8,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x38,0x18,0x16,0x00,0x00,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x36,0x13,0x13,0x25,0xff,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}} +}; + +static const struct SiS_Part2PortTbl SiS300_CRT2Part2_1024x768_3[] = +{ + {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}} +}; + +/**************************************************************/ +/* LVDS/Chrontel -------------------------------------------- */ +/**************************************************************/ + +/* Custom data for Barco iQ R series */ +static const struct SiS_LVDSData SiS300_LVDSBARCO1366Data_1[]= +{ + { 832, 438,1331, 806}, + { 832, 388,1331, 806}, + { 832, 438,1331, 806}, + { 832, 388,1331, 806}, + { 832, 518,1331, 806}, + {1050, 638,1344, 806}, + {1344, 806,1344, 806}, + {1688,1066,1688,1066}, + {1688,1066,1688,1066} /* 1360x1024 */ +}; + +/* Custom data for Barco iQ R series */ +static const struct SiS_LVDSData SiS300_LVDSBARCO1366Data_2[]= +{ + {1344, 806,1344, 806}, + {1344, 806,1344, 806}, + {1344, 806,1344, 806}, + {1344, 806,1344, 806}, + {1344, 806,1344, 806}, + {1344, 806,1344, 806}, + {1344, 806,1344, 806}, + {1688,1066,1688,1066}, + {1688,1066,1688,1066} /* 1360x1024 */ +}; + +/* Custom data for Barco iQ G series */ +static const struct SiS_LVDSData SiS300_LVDSBARCO1024Data_1[]= +{ + { 832, 438,1331, 806}, + { 832, 409,1331, 806}, + { 832, 438,1331, 806}, + { 832, 409,1331, 806}, + { 832, 518,1331, 806}, /* 640x480 */ + {1050, 638,1344, 806}, /* 800x600 */ + {1344, 806,1344, 806}, /* 1024x768 */ +}; + +/* Custom data for 848x480 and 856x480 parallel LVDS panels */ +static const struct SiS_LVDSData SiS300_LVDS848x480Data_1[]= +{ + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + {1088, 525,1088, 525}, /* 640x480 TODO */ + {1088, 525,1088, 525}, /* 800x600 TODO */ + {1088, 525,1088, 525}, /* 1024x768 TODO */ + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + {1088, 525,1088, 525}, /* 848x480 */ + {1088, 525,1088, 525}, /* 856x480 */ + {1088, 525,1088, 525} /* 1360x768 TODO */ +}; + +/* Custom data for 848x480 parallel panel */ +static const struct SiS_LVDSData SiS300_LVDS848x480Data_2[]= +{ + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + {1088, 525,1088, 525}, /* 640x480 */ + {1088, 525,1088, 525}, /* 800x600 */ + {1088, 525,1088, 525}, /* 1024x768 */ + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + { 0, 0, 0, 0}, + {1088, 525,1088, 525}, /* 848x480 */ + {1088, 525,1088, 525}, /* 856x480 */ + {1088, 525,1088, 525} /* 1360x768 TODO */ +}; + +static const struct SiS_LVDSData SiS300_CHTVUPALData[] = +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 750, 840, 750}, + { 936, 836, 936, 836} +}; + +static const struct SiS_LVDSData SiS300_CHTVOPALData[] = +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 625, 840, 625}, + { 960, 750, 960, 750} +}; + +static const struct SiS_LVDSData SiS300_CHTVSOPALData[] = +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 500, 840, 500}, + { 944, 625, 944, 625} +}; + +/* Custom des data for Barco iQ R200/300/400 (BIOS 2.00.07) */ +static const struct SiS_LVDSDes SiS300_PanelType04_1a[] = /* 1280x1024 (1366x1024) */ +{ + {1330, 798}, /* 320x200 */ + {1330, 794}, + {1330, 798}, + {1330, 794}, + {1330, 0}, /* 640x480 / 320x240 */ + {1343, 0}, /* 800x600 / 400x300 */ + { 0, 805}, /* 1024x768 / 512x384 */ + {1688,1066}, /* 1280x1024 */ + { 0, 0} /* 1360x1024 */ +}; + +static const struct SiS_LVDSDes SiS300_PanelType04_2a[] = +{ + {1152, 622}, + {1152, 597}, + {1152, 622}, + {1152, 597}, + {1152, 662}, + {1232, 722}, + { 0, 805}, + {1688,1066}, + { 0, 0} +}; + +/* Custom des data for Barco iQ G200/300/400 (BIOS 2.00.07) */ +static const struct SiS_LVDSDes SiS300_PanelType04_1b[] = /* 1024x768 */ +{ + {1330, 798}, /* 320x200 */ + {1330, 794}, + {1330, 798}, + {1330, 794}, + {1330, 0}, /* 640x480 / 320x240 */ + {1343, 0}, /* 800x600 / 400x300 */ + { 0, 805} /* 1024x768 / 512x384 */ +}; + +static const struct SiS_LVDSDes SiS300_PanelType04_2b[] = +{ + {1152, 622}, + {1152, 597}, + {1152, 622}, + {1152, 597}, + {1152, 662}, + {1232, 722}, + { 0, 805} +}; + +/* CRT1 CRTC for slave modes */ + +static const struct SiS_LVDSCRT1Data SiS300_CHTVCRT1UNTSC[] = +{ + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xe8,0x84,0x8f,0x57,0x20,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xd0,0x82,0x5d,0x57,0x00,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xe8,0x84,0x8f,0x57,0x20,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xd0,0x82,0x5d,0x57,0x00,0x00,0x01, + 0x00 }}, + {{0x5d,0x4f,0x81,0x53,0x9c,0x56,0xba, + 0x18,0x84,0xdf,0x57,0x00,0x00,0x01, + 0x00 }}, + {{0x80,0x63,0x84,0x6c,0x17,0xec,0xf0, + 0x90,0x8c,0x57,0xed,0x20,0x00,0x06, + 0x01 }} +}; + +static const struct SiS_LVDSCRT1Data SiS300_CHTVCRT1ONTSC[] = +{ + {{0x64,0x4f,0x88,0x5a,0x9f,0x0b,0x3e, + 0xc0,0x84,0x8f,0x0c,0x20,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x5a,0x9f,0x0b,0x3e, + 0xb0,0x8d,0x5d,0x0c,0x00,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x5a,0x9f,0x0b,0x3e, + 0xc0,0x84,0x8f,0x0c,0x20,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x5a,0x9f,0x0b,0x3e, + 0xb0,0x8d,0x5d,0x0c,0x00,0x00,0x01, + 0x00 }}, + {{0x5d,0x4f,0x81,0x56,0x9c,0x0b,0x3e, + 0xe8,0x84,0xdf,0x0c,0x00,0x00,0x01, + 0x00 }}, + {{0x7d,0x63,0x81,0x6a,0x16,0xba,0xf0, + 0x7f,0x86,0x57,0xbb,0x00,0x00,0x06, + 0x01 }} +}; + +static const struct SiS_LVDSCRT1Data SiS300_CHTVCRT1UPAL[] = +{ + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf8,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf8,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x64,0x4f,0x88,0x55,0x80,0xec,0xba, + 0x50,0x84,0xdf,0xed,0x00,0x00,0x05, + 0x00 }}, + {{0x70,0x63,0x94,0x68,0x8d,0x42,0xf1, + 0xc8,0x8c,0x57,0xe9,0x20,0x00,0x05, + 0x01 }} +}; + +static const struct SiS_LVDSCRT1Data SiS300_CHTVCRT1OPAL[] = +{ + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf0,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf0,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x64,0x4f,0x88,0x55,0x80,0x6f,0xba, + 0x20,0x83,0xdf,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x73,0x63,0x97,0x69,0x8e,0xec,0xf0, + 0x90,0x8c,0x57,0xed,0x20,0x00,0x05, + 0x01 }} +}; + +static const struct SiS_LVDSCRT1Data SiS300_CHTVCRT1SOPAL[] = +{ + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf0,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf0,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x64,0x4f,0x88,0x55,0x80,0x6f,0xba, /* TODO */ + 0x20,0x83,0xdf,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x73,0x63,0x97,0x69,0x8e,0xec,0xf0, /* TODO */ + 0x90,0x8c,0x57,0xed,0x20,0x00,0x05, + 0x01 }} +}; + +static const struct SiS_CHTVRegData SiS300_CHTVReg_UNTSC[] = +{ + {{0x4a,0x94,0x00,0x48,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x4a,0x94,0x00,0x48,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x4a,0x94,0x00,0x48,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x4a,0x94,0x00,0x48,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x6a,0x6a,0x00,0x2d,0xfa,0,0,0,0,0,0,0,0,0,0,0}}, /* Mode 17: 640x480 NTSC 7/8 */ + {{0x8d,0xc4,0x00,0x3b,0xfb,0,0,0,0,0,0,0,0,0,0,0}} /* Mode 24: 800x600 NTSC 7/10 */ +}; + +static const struct SiS_CHTVRegData SiS300_CHTVReg_ONTSC[] = +{ + {{0x49,0x94,0x00,0x34,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x49,0x94,0x00,0x34,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x49,0x94,0x00,0x34,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x49,0x94,0x00,0x34,0xfe,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x69,0x6a,0x00,0x1e,0xfd,0,0,0,0,0,0,0,0,0,0,0}}, /* Mode 16: 640x480 NTSC 1/1 */ + {{0x8c,0xb4,0x00,0x32,0xf9,0,0,0,0,0,0,0,0,0,0,0}} /* Mode 23: 800x600 NTSC 3/4 */ +}; + +static const struct SiS_CHTVRegData SiS300_CHTVReg_UPAL[] = +{ + {{0x41,0x12,0x01,0x50,0x34,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x41,0x12,0x00,0x50,0x00,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x41,0x12,0x01,0x50,0x34,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x41,0x12,0x00,0x50,0x00,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x63,0x94,0x01,0x50,0x30,0,0,0,0,0,0,0,0,0,0,0}}, /* Mode 15: 640x480 PAL 5/6 */ + {{0x84,0x64,0x01,0x4e,0x2f,0,0,0,0,0,0,0,0,0,0,0}} /* Mode 21: 800x600 PAL 3/4 */ + +}; + +static const struct SiS_CHTVRegData SiS300_CHTVReg_OPAL[] = +{ + {{0x41,0x12,0x01,0x50,0x34,0,0,0,0,0,0,0,0,0,0,0}}, /* Mode 9: 640x400 PAL 1/1 */ + {{0x41,0x12,0x00,0x50,0x00,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x41,0x12,0x01,0x50,0x34,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x41,0x12,0x00,0x50,0x00,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x61,0x94,0x01,0x36,0x30,0,0,0,0,0,0,0,0,0,0,0}}, /* Mode 14: 640x480 PAL 1/1 */ + {{0x83,0x76,0x01,0x40,0x31,0,0,0,0,0,0,0,0,0,0,0}} /* Mode 20: 800x600 PAL 5/6 */ + +}; + +static const struct SiS_CHTVRegData SiS300_CHTVReg_SOPAL[] = +{ + {{0x41,0x12,0x01,0x50,0x34,0,0,0,0,0,0,0,0,0,0,0}}, /* Mode 9: 640x400 PAL 1/1 */ + {{0x41,0x12,0x00,0x50,0x00,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x41,0x12,0x01,0x50,0x34,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x41,0x12,0x00,0x50,0x00,0,0,0,0,0,0,0,0,0,0,0}}, + {{0x60,0x30,0x00,0x10,0x00,0,0,0,0,0,0,0,0,0,0,0}}, /* Mode 13: 640x480 PAL 5/4 */ + {{0x81,0x50,0x00,0x1b,0x00,0,0,0,0,0,0,0,0,0,0,0}} /* Mode 19: 800x600 PAL 1/1 */ +}; + +static const unsigned char SiS300_CHTVVCLKUNTSC[] = { 0x29,0x29,0x29,0x29,0x2a,0x2e }; + +static const unsigned char SiS300_CHTVVCLKONTSC[] = { 0x2c,0x2c,0x2c,0x2c,0x2d,0x2b }; + +static const unsigned char SiS300_CHTVVCLKSONTSC[] = { 0x2c,0x2c,0x2c,0x2c,0x2d,0x2b }; + +static const unsigned char SiS300_CHTVVCLKUPAL[] = { 0x2f,0x2f,0x2f,0x2f,0x2f,0x31 }; + +static const unsigned char SiS300_CHTVVCLKOPAL[] = { 0x2f,0x2f,0x2f,0x2f,0x30,0x32 }; + +static const unsigned char SiS300_CHTVVCLKSOPAL[] = { 0x2f,0x2f,0x2f,0x2f,0x36,0x29 }; + + diff --git a/drivers/video/fbdev/sis/310vtbl.h b/drivers/video/fbdev/sis/310vtbl.h new file mode 100644 index 000000000000..54fcbbf4ef63 --- /dev/null +++ b/drivers/video/fbdev/sis/310vtbl.h @@ -0,0 +1,1339 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Register settings for SiS 315/330/340 series + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +static const struct SiS_Ext SiS310_EModeIDTable[] = +{ + {0x6a,0x2212,0x0102,SIS_RI_800x600, 0x00,0x00,0x07,0x06,0x00, 3}, /* 800x600x? */ + {0x2e,0x0a1b,0x0101,SIS_RI_640x480, 0x00,0x00,0x05,0x05,0x08, 2}, /* 640x480x8 */ + {0x2f,0x0a1b,0x0100,SIS_RI_640x400, 0x00,0x00,0x05,0x05,0x10, 0}, /* 640x400x8 */ + {0x30,0x2a1b,0x0103,SIS_RI_800x600, 0x00,0x00,0x07,0x06,0x00, 3}, /* 800x600x8 */ + {0x31,0x4a1b,0x0000,SIS_RI_720x480, 0x00,0x00,0x06,0x06,0x11,-1}, /* 720x480x8 */ + {0x32,0x4a1b,0x0000,SIS_RI_720x576, 0x00,0x00,0x06,0x06,0x12,-1}, /* 720x576x8 */ + {0x33,0x4a1d,0x0000,SIS_RI_720x480, 0x00,0x00,0x06,0x06,0x11,-1}, /* 720x480x16 */ + {0x34,0x6a1d,0x0000,SIS_RI_720x576, 0x00,0x00,0x06,0x06,0x12,-1}, /* 720x576x16 */ + {0x35,0x4a1f,0x0000,SIS_RI_720x480, 0x00,0x00,0x06,0x06,0x11,-1}, /* 720x480x32 */ + {0x36,0x6a1f,0x0000,SIS_RI_720x576, 0x00,0x00,0x06,0x06,0x12,-1}, /* 720x576x32 */ + {0x37,0x0212,0x0104,SIS_RI_1024x768, 0x00,0x00,0x08,0x07,0x13, 4}, /* 1024x768x? */ + {0x38,0x0a1b,0x0105,SIS_RI_1024x768, 0x00,0x00,0x08,0x07,0x13, 4}, /* 1024x768x8 */ + {0x3a,0x0e3b,0x0107,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a, 8}, /* 1280x1024x8 */ + {0x3c,0x0e3b,0x0130,SIS_RI_1600x1200,0x00,0x00,0x00,0x00,0x1e,10}, /* 1600x1200x8 */ + {0x3d,0x0e7d,0x0131,SIS_RI_1600x1200,0x00,0x00,0x00,0x00,0x1e,10}, /* 1600x1200x16 */ + {0x40,0x9a1c,0x010d,SIS_RI_320x200, 0x00,0x00,0x04,0x04,0x25, 0}, /* 320x200x15 */ + {0x41,0x9a1d,0x010e,SIS_RI_320x200, 0x00,0x00,0x04,0x04,0x25, 0}, /* 320x200x16 */ + {0x43,0x0a1c,0x0110,SIS_RI_640x480, 0x00,0x00,0x05,0x05,0x08, 2}, + {0x44,0x0a1d,0x0111,SIS_RI_640x480, 0x00,0x00,0x05,0x05,0x08, 2}, /* 640x480x16 */ + {0x46,0x2a1c,0x0113,SIS_RI_800x600, 0x00,0x00,0x07,0x06,0x00, 3}, + {0x47,0x2a1d,0x0114,SIS_RI_800x600, 0x00,0x00,0x07,0x06,0x00, 3}, /* 800x600x16 */ + {0x49,0x0a3c,0x0116,SIS_RI_1024x768, 0x00,0x00,0x00,0x07,0x13, 4}, + {0x4a,0x0a3d,0x0117,SIS_RI_1024x768, 0x00,0x00,0x08,0x07,0x13, 4}, /* 1024x768x16 */ + {0x4c,0x0e7c,0x0119,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a, 8}, + {0x4d,0x0e7d,0x011a,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a, 8}, /* 1280x1024x16 */ + {0x50,0x9a1b,0x0132,SIS_RI_320x240, 0x00,0x00,0x04,0x04,0x26, 2}, /* 320x240x8 */ + {0x51,0xba1b,0x0133,SIS_RI_400x300, 0x00,0x00,0x07,0x07,0x27, 3}, /* 400x300x8 */ + {0x52,0xba1b,0x0134,SIS_RI_512x384, 0x00,0x00,0x00,0x00,0x28, 4}, /* 512x384x8 */ + {0x56,0x9a1d,0x0135,SIS_RI_320x240, 0x00,0x00,0x04,0x04,0x26, 2}, /* 320x240x16 */ + {0x57,0xba1d,0x0136,SIS_RI_400x300, 0x00,0x00,0x07,0x07,0x27, 3}, /* 400x300x16 */ + {0x58,0xba1d,0x0137,SIS_RI_512x384, 0x00,0x00,0x00,0x00,0x28, 4}, /* 512x384x16 */ + {0x59,0x9a1b,0x0138,SIS_RI_320x200, 0x00,0x00,0x04,0x04,0x25, 0}, /* 320x200x8 */ + {0x5a,0x021b,0x0138,SIS_RI_320x240, 0x00,0x00,0x00,0x00,0x3f, 2}, /* 320x240x8 fstn */ + {0x5b,0x0a1d,0x0135,SIS_RI_320x240, 0x00,0x00,0x00,0x00,0x3f, 2}, /* 320x240x16 fstn */ + {0x5c,0xba1f,0x0000,SIS_RI_512x384, 0x00,0x00,0x00,0x00,0x28, 4}, /* 512x384x32 */ + {0x5d,0x0a1d,0x0139,SIS_RI_640x400, 0x00,0x00,0x05,0x07,0x10, 0}, + {0x5e,0x0a1f,0x0000,SIS_RI_640x400, 0x00,0x00,0x05,0x07,0x10, 0}, /* 640x400x32 */ + {0x62,0x0a3f,0x013a,SIS_RI_640x480, 0x00,0x00,0x05,0x05,0x08, 2}, /* 640x480x32 */ + {0x63,0x2a3f,0x013b,SIS_RI_800x600, 0x00,0x00,0x07,0x06,0x00, 3}, /* 800x600x32 */ + {0x64,0x0a7f,0x013c,SIS_RI_1024x768, 0x00,0x00,0x08,0x07,0x13, 4}, /* 1024x768x32 */ + {0x65,0x0eff,0x013d,SIS_RI_1280x1024,0x00,0x00,0x00,0x00,0x1a, 8}, /* 1280x1024x32 */ + {0x66,0x0eff,0x013e,SIS_RI_1600x1200,0x00,0x00,0x00,0x00,0x1e,10}, /* 1600x1200x32 */ + {0x68,0x067b,0x013f,SIS_RI_1920x1440,0x00,0x00,0x00,0x00,0x29,-1}, /* 1920x1440x8 */ + {0x69,0x06fd,0x0140,SIS_RI_1920x1440,0x00,0x00,0x00,0x00,0x29,-1}, /* 1920x1440x16 */ + {0x6b,0x07ff,0x0141,SIS_RI_1920x1440,0x00,0x00,0x00,0x00,0x29,-1}, /* 1920x1440x32 */ + {0x6c,0x067b,0x0000,SIS_RI_2048x1536,0x00,0x00,0x00,0x00,0x2f,-1}, /* 2048x1536x8 */ + {0x6d,0x06fd,0x0000,SIS_RI_2048x1536,0x00,0x00,0x00,0x00,0x2f,-1}, /* 2048x1536x16 */ + {0x6e,0x07ff,0x0000,SIS_RI_2048x1536,0x00,0x00,0x00,0x00,0x2f,-1}, /* 2048x1536x32 */ + {0x70,0x6a1b,0x0000,SIS_RI_800x480, 0x00,0x00,0x07,0x07,0x34,-1}, /* 800x480x8 */ + {0x71,0x4a1b,0x0000,SIS_RI_1024x576, 0x00,0x00,0x00,0x00,0x37,-1}, /* 1024x576x8 */ + {0x74,0x4a1d,0x0000,SIS_RI_1024x576, 0x00,0x00,0x00,0x00,0x37,-1}, /* 1024x576x16 */ + {0x75,0x0a3d,0x0000,SIS_RI_1280x720, 0x00,0x00,0x00,0x00,0x3a, 5}, /* 1280x720x16 */ + {0x76,0x6a1f,0x0000,SIS_RI_800x480, 0x00,0x00,0x07,0x07,0x34,-1}, /* 800x480x32 */ + {0x77,0x4a1f,0x0000,SIS_RI_1024x576, 0x00,0x00,0x00,0x00,0x37,-1}, /* 1024x576x32 */ + {0x78,0x0a3f,0x0000,SIS_RI_1280x720, 0x00,0x00,0x00,0x00,0x3a, 5}, /* 1280x720x32 */ + {0x79,0x0a3b,0x0000,SIS_RI_1280x720, 0x00,0x00,0x00,0x00,0x3a, 5}, /* 1280x720x8 */ + {0x7a,0x6a1d,0x0000,SIS_RI_800x480, 0x00,0x00,0x07,0x07,0x34,-1}, /* 800x480x16 */ + {0x7c,0x0e3b,0x0000,SIS_RI_1280x960, 0x00,0x00,0x00,0x00,0x3d,-1}, /* 1280x960x8 */ + {0x7d,0x0e7d,0x0000,SIS_RI_1280x960, 0x00,0x00,0x00,0x00,0x3d,-1}, /* 1280x960x16 */ + {0x7e,0x0eff,0x0000,SIS_RI_1280x960, 0x00,0x00,0x00,0x00,0x3d,-1}, /* 1280x960x32 */ + {0x23,0x0e3b,0x0000,SIS_RI_1280x768, 0x00,0x00,0x00,0x00,0x40, 6}, /* 1280x768x8 */ + {0x24,0x0e7d,0x0000,SIS_RI_1280x768, 0x00,0x00,0x00,0x00,0x40, 6}, /* 1280x768x16 */ + {0x25,0x0eff,0x0000,SIS_RI_1280x768, 0x00,0x00,0x00,0x00,0x40, 6}, /* 1280x768x32 */ + {0x26,0x0e3b,0x0000,SIS_RI_1400x1050,0x00,0x00,0x00,0x00,0x43, 9}, /* 1400x1050x8 */ + {0x27,0x0e7d,0x0000,SIS_RI_1400x1050,0x00,0x00,0x00,0x00,0x43, 9}, /* 1400x1050x16 */ + {0x28,0x0eff,0x0000,SIS_RI_1400x1050,0x00,0x00,0x00,0x00,0x43, 9}, /* 1400x1050x32*/ + {0x29,0x4e1b,0x0000,SIS_RI_1152x864, 0x00,0x00,0x00,0x00,0x45,-1}, /* 1152x864 */ + {0x2a,0x4e3d,0x0000,SIS_RI_1152x864, 0x00,0x00,0x00,0x00,0x45,-1}, + {0x2b,0x4e7f,0x0000,SIS_RI_1152x864, 0x00,0x00,0x00,0x00,0x45,-1}, + {0x39,0x6a1b,0x0000,SIS_RI_848x480, 0x00,0x00,0x00,0x00,0x48,-1}, /* 848x480 */ + {0x3b,0x6a3d,0x0000,SIS_RI_848x480, 0x00,0x00,0x00,0x00,0x48,-1}, + {0x3e,0x6a7f,0x0000,SIS_RI_848x480, 0x00,0x00,0x00,0x00,0x48,-1}, + {0x3f,0x6a1b,0x0000,SIS_RI_856x480, 0x00,0x00,0x00,0x00,0x4a,-1}, /* 856x480 */ + {0x42,0x6a3d,0x0000,SIS_RI_856x480, 0x00,0x00,0x00,0x00,0x4a,-1}, + {0x45,0x6a7f,0x0000,SIS_RI_856x480, 0x00,0x00,0x00,0x00,0x4a,-1}, + {0x48,0x6a3b,0x0000,SIS_RI_1360x768, 0x00,0x00,0x00,0x00,0x4c,-1}, /* 1360x768 */ + {0x4b,0x6a7d,0x0000,SIS_RI_1360x768, 0x00,0x00,0x00,0x00,0x4c,-1}, + {0x4e,0x6aff,0x0000,SIS_RI_1360x768, 0x00,0x00,0x00,0x00,0x4c,-1}, + {0x4f,0x9a1f,0x0000,SIS_RI_320x200, 0x00,0x00,0x04,0x04,0x25, 0}, /* 320x200x32 */ + {0x53,0x9a1f,0x0000,SIS_RI_320x240, 0x00,0x00,0x04,0x04,0x26, 2}, /* 320x240x32 */ + {0x54,0xba1f,0x0000,SIS_RI_400x300, 0x00,0x00,0x07,0x07,0x27, 3}, /* 400x300x32 */ + {0x5f,0x6a1b,0x0000,SIS_RI_768x576, 0x00,0x00,0x06,0x06,0x4d,-1}, /* 768x576 */ + {0x60,0x6a1d,0x0000,SIS_RI_768x576, 0x00,0x00,0x06,0x06,0x4d,-1}, + {0x61,0x6a3f,0x0000,SIS_RI_768x576, 0x00,0x00,0x06,0x06,0x4d,-1}, + {0x14,0x0e3b,0x0000,SIS_RI_1280x800, 0x00,0x00,0x00,0x00,0x4e, 7}, /* 1280x800 */ + {0x15,0x0e7d,0x0000,SIS_RI_1280x800, 0x00,0x00,0x00,0x00,0x4e, 7}, + {0x16,0x0eff,0x0000,SIS_RI_1280x800, 0x00,0x00,0x00,0x00,0x4e, 7}, + {0x17,0x0e3b,0x0000,SIS_RI_1680x1050,0x00,0x00,0x00,0x00,0x51, 9}, /* 1680x1050 */ + {0x18,0x0e7d,0x0000,SIS_RI_1680x1050,0x00,0x00,0x00,0x00,0x51, 9}, + {0x19,0x0eff,0x0000,SIS_RI_1680x1050,0x00,0x00,0x00,0x00,0x51, 9}, + {0x2c,0x267b,0x0000,SIS_RI_1920x1080,0x00,0x00,0x00,0x00,0x52,-1}, /* 1920x1080(i) */ + {0x2d,0x26fd,0x0000,SIS_RI_1920x1080,0x00,0x00,0x00,0x00,0x52,-1}, + {0x73,0x27ff,0x0000,SIS_RI_1920x1080,0x00,0x00,0x00,0x00,0x52,-1}, + {0x1d,0x6a1b,0x0000,SIS_RI_960x540, 0x00,0x00,0x00,0x00,0x53,-1}, /* 960x540 */ + {0x1e,0x6a3d,0x0000,SIS_RI_960x540, 0x00,0x00,0x00,0x00,0x53,-1}, + {0x1f,0x6a7f,0x0000,SIS_RI_960x540, 0x00,0x00,0x00,0x00,0x53,-1}, + {0x20,0x6a1b,0x0000,SIS_RI_960x600, 0x00,0x00,0x00,0x00,0x54,-1}, /* 960x600 */ + {0x21,0x6a3d,0x0000,SIS_RI_960x600, 0x00,0x00,0x00,0x00,0x54,-1}, + {0x22,0x6a7f,0x0000,SIS_RI_960x600, 0x00,0x00,0x00,0x00,0x54,-1}, + {0x1a,0x0e3b,0x0000,SIS_RI_1280x854, 0x00,0x00,0x00,0x00,0x55, 8}, /* 1280x854 */ + {0x1b,0x0e7d,0x0000,SIS_RI_1280x854, 0x00,0x00,0x00,0x00,0x55, 8}, + {0x1c,0x0eff,0x0000,SIS_RI_1280x854, 0x00,0x00,0x00,0x00,0x55, 8}, + {0xff,0x0000,0x0000,0, 0x00,0x00,0x00,0x00,0x00,-1} +}; + +static const struct SiS_Ext2 SiS310_RefIndex[] = +{ + {0x085f,0x0d,0x03,0x05,0x05,0x6a, 800, 600, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x0 */ + {0x0067,0x0e,0x04,0x05,0x05,0x6a, 800, 600, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x1 */ + {0x0067,0x0f,0x08,0x48,0x05,0x6a, 800, 600, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x2 */ + {0x0067,0x10,0x07,0x8b,0x05,0x6a, 800, 600, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x3 */ + {0x0047,0x11,0x0a,0x00,0x05,0x6a, 800, 600, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x4 */ + {0x0047,0x12,0x0d,0x00,0x05,0x6a, 800, 600, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x5 */ + {0x0047,0x13,0x13,0x00,0x05,0x6a, 800, 600, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x6 */ + {0x0107,0x14,0x1c,0x00,0x05,0x6a, 800, 600, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x7 */ + {0xc85f,0x05,0x00,0x04,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x8 */ + {0xc067,0x06,0x02,0x04,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x9 */ + {0xc067,0x07,0x02,0x47,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0xa */ + {0xc067,0x08,0x03,0x8a,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0xb */ + {0xc047,0x09,0x05,0x00,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0xc */ + {0xc047,0x0a,0x09,0x00,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0xd */ + {0xc047,0x0b,0x0e,0x00,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0xe */ + {0xc047,0x0c,0x15,0x00,0x04,0x2e, 640, 480, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0xf */ + {0x487f,0x04,0x00,0x00,0x00,0x2f, 640, 400, 0x30, 0x55, 0x6e, 0x00, 0x00, 0x00, 0x00}, /* 0x10 */ + {0xc06f,0x3c,0x01,0x06,0x13,0x31, 720, 480, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x11 */ + {0x006f,0x3d,0x6f,0x06,0x14,0x32, 720, 576, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x12 (6f was 03) */ + {0x0087,0x15,0x06,0x00,0x06,0x37,1024, 768, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x13 */ + {0xc877,0x16,0x0b,0x06,0x06,0x37,1024, 768, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x14 */ + {0xc067,0x17,0x0f,0x49,0x06,0x37,1024, 768, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x15 */ + {0x0067,0x18,0x11,0x00,0x06,0x37,1024, 768, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x16 */ + {0x0047,0x19,0x16,0x8c,0x06,0x37,1024, 768, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x17 */ + {0x0107,0x1a,0x1b,0x00,0x06,0x37,1024, 768, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x18 */ + {0x0107,0x1b,0x1f,0x00,0x06,0x37,1024, 768, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x19 */ + {0x0087,0x1c,0x11,0x00,0x07,0x3a,1280,1024, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x1a */ + {0x0137,0x1d,0x19,0x07,0x07,0x3a,1280,1024, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x1b */ + {0x0107,0x1e,0x1e,0x00,0x07,0x3a,1280,1024, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x1c */ + {0x0207,0x1f,0x20,0x00,0x07,0x3a,1280,1024, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x1d */ + {0x0227,0x20,0x21,0x09,0x09,0x3c,1600,1200, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x1e */ + {0x0407,0x21,0x22,0x00,0x09,0x3c,1600,1200, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x1f */ + {0x0407,0x22,0x23,0x00,0x09,0x3c,1600,1200, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x20 */ + {0x0407,0x23,0x25,0x00,0x09,0x3c,1600,1200, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x21 */ + {0x0007,0x24,0x26,0x00,0x09,0x3c,1600,1200, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x22 */ + {0x0007,0x25,0x2c,0x00,0x09,0x3c,1600,1200, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x23 */ + {0x0007,0x26,0x34,0x00,0x09,0x3c,1600,1200, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x24 */ + {0x407f,0x00,0x00,0x00,0x00,0x40, 320, 200, 0x30, 0x56, 0x4e, 0x00, 0x00, 0x00, 0x00}, /* 0x25 */ + {0xc07f,0x01,0x00,0x04,0x04,0x50, 320, 240, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x26 */ + {0x007f,0x02,0x04,0x05,0x05,0x51, 400, 300, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x27 */ + {0xc077,0x03,0x0b,0x06,0x06,0x52, 512, 384, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x28 */ + {0x8007,0x27,0x27,0x00,0x00,0x68,1920,1440, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x29 */ + {0x4007,0x28,0x29,0x00,0x00,0x68,1920,1440, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x2a */ + {0x4007,0x29,0x2e,0x00,0x00,0x68,1920,1440, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x2b */ + {0x4007,0x2a,0x30,0x00,0x00,0x68,1920,1440, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x2c */ + {0x4007,0x2b,0x35,0x00,0x00,0x68,1920,1440, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x2d */ + {0x4005,0x2c,0x39,0x00,0x00,0x68,1920,1440, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x2e */ + {0x4007,0x2d,0x2b,0x00,0x00,0x6c,2048,1536, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x2f */ + {0x4007,0x2e,0x31,0x00,0x00,0x6c,2048,1536, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x30 */ + {0x4007,0x2f,0x33,0x00,0x00,0x6c,2048,1536, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x31 */ + {0x4007,0x30,0x37,0x00,0x00,0x6c,2048,1536, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x32 */ + {0x4005,0x31,0x38,0x00,0x00,0x6c,2048,1536, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x33 */ + {0x2077,0x32,0x40,0x08,0x18,0x70, 800, 480, 0x30, 0x00, 0x00, 0x32, 0x40, 0x5e, 0x73}, /* 0x34 */ + {0x2047,0x33,0x07,0x08,0x18,0x70, 800, 480, 0x30, 0x00, 0x00, 0x33, 0x07, 0xff, 0xff}, /* 0x35 */ + {0x2047,0x34,0x0a,0x08,0x18,0x70, 800, 480, 0x30, 0x00, 0x00, 0x34, 0x0a, 0xff, 0xff}, /* 0x36 */ + {0x2077,0x35,0x0b,0x09,0x19,0x71,1024, 576, 0x30, 0x00, 0x00, 0x35, 0x0b, 0x5f, 0x74}, /* 0x37 */ + {0x2047,0x36,0x11,0x09,0x19,0x71,1024, 576, 0x30, 0x00, 0x00, 0x36, 0x11, 0xff, 0xff}, /* 0x38 */ + {0x2047,0x37,0x16,0x09,0x19,0x71,1024, 576, 0x30, 0x00, 0x00, 0x37, 0x16, 0xff, 0xff}, /* 0x39 */ + {0x3137,0x38,0x19,0x0a,0x0c,0x75,1280, 720, 0x30, 0x00, 0x00, 0x38, 0x19, 0x60, 0x75}, /* 0x3a */ + {0x3107,0x39,0x1e,0x0a,0x0c,0x75,1280, 720, 0x30, 0x00, 0x00, 0x39, 0x1e, 0xff, 0xff}, /* 0x3b */ + {0x3307,0x3a,0x20,0x0a,0x0c,0x75,1280, 720, 0x30, 0x00, 0x00, 0x3a, 0x20, 0xff, 0xff}, /* 0x3c */ + {0x0127,0x3b,0x19,0x08,0x0a,0x7c,1280, 960, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x3d */ + {0x0227,0x4c,0x59,0x08,0x0a,0x7c,1280, 960, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x3e */ + {0xc07f,0x4e,0x00,0x06,0x04,0x5a, 320, 240, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x3f */ /* FSTN 320x240 */ + {0x2077,0x42,0x5b,0x08,0x11,0x23,1280, 768, 0x30, 0x00, 0x00, 0x58, 0x19, 0x42, 0x5b}, /* 0x40 */ /* 0x5b was 0x12 */ + {0x2077,0x42,0x5b,0x08,0x11,0x23,1280, 768, 0x30, 0x00, 0x00, 0x59, 0x1e, 0xff, 0xff}, /* 0x41 */ + {0x2077,0x42,0x5b,0x08,0x11,0x23,1280, 768, 0x30, 0x00, 0x00, 0x5a, 0x20, 0xff, 0xff}, /* 0x42 */ + {0x0127,0x43,0x4d,0x08,0x0b,0x26,1400,1050, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x43 */ + {0x0207,0x4b,0x5a,0x08,0x0b,0x26,1400,1050, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x44 1400x1050-75Hz */ + {0x0127,0x54,0x6d,0x00,0x1a,0x29,1152, 864, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x45 1152x864-60Hz */ + {0x0127,0x44,0x19,0x00,0x1a,0x29,1152, 864, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x46 1152x864-75Hz */ + {0x0127,0x4a,0x1e,0x00,0x1a,0x29,1152, 864, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x47 1152x864-85Hz */ + {0x0087,0x45,0x57,0x00,0x16,0x39, 848, 480, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x48 848x480-38Hzi */ + {0xc067,0x46,0x55,0x0b,0x16,0x39, 848, 480, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x49 848x480-60Hz */ + {0x0087,0x47,0x57,0x00,0x17,0x3f, 856, 480, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x4a 856x480-38Hzi */ + {0xc067,0x48,0x57,0x00,0x17,0x3f, 856, 480, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x4b 856x480-60Hz */ + {0x0067,0x49,0x58,0x0c,0x1b,0x48,1360, 768, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x4c 1360x768-60Hz */ + {0x006f,0x4d,0x71,0x06,0x15,0x5f, 768, 576, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x4d 768x576-56Hz */ + {0x2067,0x4f,0x5c,0x08,0x0d,0x14,1280, 800, 0x30, 0x00, 0x00, 0x5b, 0x19, 0x4f, 0x5c}, /* 0x4e 1280x800-60Hz */ + {0x2067,0x4f,0x5c,0x08,0x0d,0x14,1280, 800, 0x30, 0x00, 0x00, 0x5c, 0x1e, 0xff, 0xff}, /* 0x4f 1280x800-75Hz */ + {0x2067,0x4f,0x5c,0x08,0x0d,0x14,1280, 800, 0x30, 0x00, 0x00, 0x5d, 0x20, 0xff, 0xff}, /* 0x50 1280x800-85Hz */ + {0x0067,0x50,0x5d,0x0c,0x0e,0x17,1680,1050, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x51 1680x1050-60Hz */ + {0x0087,0x51,0x69,0x00,0x00,0x2c,1920,1080, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x52 1920x1080 60Hzi */ + {0x0067,0x52,0x6a,0x00,0x1c,0x1d, 960, 540, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x53 960x540 60Hz */ + {0x0077,0x53,0x6b,0x0b,0x1d,0x20, 960, 600, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x54 960x600 60Hz */ + {0x2067,0x61,0x76,0x0d,0x22,0x1a,1280, 854, 0x30, 0x00, 0x00, 0x62, 0x19, 0x61, 0x76}, /* 0x55 1280x854-60Hz */ + {0x2067,0x61,0x76,0x0d,0x22,0x1a,1280, 854, 0x30, 0x00, 0x00, 0x63, 0x1e, 0xff, 0xff}, /* 0x56 1280x854-75Hz */ + {0x2067,0x61,0x76,0x0d,0x22,0x1a,1280, 854, 0x30, 0x00, 0x00, 0x64, 0x20, 0xff, 0xff}, /* 0x57 1280x854-85Hz */ + {0xffff,0x00,0x00,0x00,0x00,0x00, 0, 0, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +static const struct SiS_CRT1Table SiS310_CRT1Table[] = +{ + {{0x2d,0x27,0x28,0x90,0x2c,0x80,0xbf,0x1f, + 0x9c,0x8e,0x8f,0x96,0xb9,0x30,0x00,0x00, + 0x00}}, /* 0x0 */ + {{0x2d,0x27,0x28,0x90,0x2c,0x80,0x0b,0x3e, + 0xe9,0x8b,0xdf,0xe7,0x04,0x00,0x00,0x00, + 0x00}}, /* 0x1 */ + {{0x3d,0x31,0x31,0x81,0x37,0x1f,0x72,0xf0, + 0x58,0x8c,0x57,0x57,0x73,0x20,0x00,0x05, + 0x01}}, /* 0x2 */ + {{0x4f,0x3f,0x3f,0x93,0x45,0x0d,0x24,0xf5, + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x01, + 0x01}}, /* 0x3 */ + {{0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x9c,0x8e,0x8f,0x96,0xb9,0x30,0x00,0x05, + 0x00}}, /* 0x4 */ + {{0x5f,0x4f,0x4f,0x83,0x55,0x81,0x0b,0x3e, /* corrected 640x480-60 */ + 0xe9,0x8b,0xdf,0xe8,0x0c,0x00,0x00,0x05, + 0x00}}, /* 0x5 */ + {{0x63,0x4f,0x4f,0x87,0x56,0x9b,0x06,0x3e, /* corrected 640x480-72 */ + 0xe8,0x8a,0xdf,0xe7,0x07,0x00,0x00,0x01, + 0x00}}, /* 0x6 */ + {{0x64,0x4f,0x4f,0x88,0x55,0x9d,0xf2,0x1f, + 0xe0,0x83,0xdf,0xdf,0xf3,0x10,0x00,0x01, + 0x00}}, /* 0x7 */ + {{0x63,0x4f,0x4f,0x87,0x5a,0x81,0xfb,0x1f, + 0xe0,0x83,0xdf,0xdf,0xfc,0x10,0x00,0x05, + 0x00}}, /* 0x8 */ + {{0x65,0x4f,0x4f,0x89,0x58,0x80,0xfb,0x1f, + 0xe0,0x83,0xdf,0xdf,0xfc,0x10,0x00,0x05, /* Corrected VBE */ + 0x61}}, /* 0x9 */ + {{0x65,0x4f,0x4f,0x89,0x58,0x80,0x01,0x3e, + 0xe0,0x83,0xdf,0xdf,0x02,0x00,0x00,0x05, + 0x61}}, /* 0xa */ + {{0x67,0x4f,0x4f,0x8b,0x58,0x81,0x0d,0x3e, + 0xe0,0x83,0xdf,0xdf,0x0e,0x00,0x00,0x05, /* Corrected VBE */ + 0x61}}, /* 0xb */ + {{0x65,0x4f,0x4f,0x89,0x57,0x9f,0xfb,0x1f, + 0xe6,0x8a,0xdf,0xdf,0xfc,0x10,0x00,0x01, /* Corrected VDE, VBE */ + 0x00}}, /* 0xc */ + {{0x7b,0x63,0x63,0x9f,0x6a,0x93,0x6f,0xf0, + 0x58,0x8a,0x57,0x57,0x70,0x20,0x00,0x05, + 0x01}}, /* 0xd */ + {{0x7f,0x63,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x57,0x73,0x20,0x00,0x06, + 0x01}}, /* 0xe */ + {{0x7d,0x63,0x63,0x81,0x6e,0x1d,0x98,0xf0, + 0x7c,0x82,0x57,0x57,0x99,0x00,0x00,0x06, + 0x01}}, /* 0xf */ + {{0x7f,0x63,0x63,0x83,0x69,0x13,0x6f,0xf0, + 0x58,0x8b,0x57,0x57,0x70,0x20,0x00,0x06, + 0x01}}, /* 0x10 */ + {{0x7e,0x63,0x63,0x82,0x6b,0x13,0x75,0xf0, + 0x58,0x8b,0x57,0x57,0x76,0x20,0x00,0x06, + 0x01}}, /* 0x11 */ + {{0x81,0x63,0x63,0x85,0x6d,0x18,0x7a,0xf0, + 0x58,0x8b,0x57,0x57,0x7b,0x20,0x00,0x06, + 0x61}}, /* 0x12 */ + {{0x83,0x63,0x63,0x87,0x6e,0x19,0x81,0xf0, + 0x58,0x8b,0x57,0x57,0x82,0x20,0x00,0x06, + 0x61}}, /* 0x13 */ + {{0x85,0x63,0x63,0x89,0x6f,0x1a,0x91,0xf0, + 0x58,0x8b,0x57,0x57,0x92,0x20,0x00,0x06, + 0x61}}, /* 0x14 */ + {{0x99,0x7f,0x7f,0x9d,0x84,0x1a,0x96,0x1f, + 0x7f,0x83,0x7f,0x7f,0x97,0x10,0x00,0x02, + 0x00}}, /* 0x15 */ + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x24,0xf5, + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x02, + 0x01}}, /* 0x16 */ + {{0xa1,0x7f,0x7f,0x85,0x86,0x97,0x24,0xf5, + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x02, + 0x01}}, /* 0x17 */ + {{0x9f,0x7f,0x7f,0x83,0x85,0x91,0x1e,0xf5, + 0x00,0x83,0xff,0xff,0x1f,0x10,0x00,0x02, + 0x01}}, /* 0x18 */ + {{0xa7,0x7f,0x7f,0x8b,0x89,0x95,0x26,0xf5, + 0x00,0x83,0xff,0xff,0x27,0x10,0x00,0x02, + 0x01}}, /* 0x19 */ + {{0xa9,0x7f,0x7f,0x8d,0x8c,0x9a,0x2c,0xf5, + 0x00,0x83,0xff,0xff,0x2d,0x14,0x00,0x02, + 0x62}}, /* 0x1a */ + {{0xab,0x7f,0x7f,0x8f,0x8d,0x9b,0x35,0xf5, + 0x00,0x83,0xff,0xff,0x36,0x14,0x00,0x02, + 0x62}}, /* 0x1b */ + {{0xcf,0x9f,0x9f,0x93,0xb2,0x01,0x14,0xba, + 0x00,0x83,0xff,0xff,0x15,0x00,0x00,0x03, + 0x00}}, /* 0x1c */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0x5a, + 0x00,0x83,0xff,0xff,0x29,0x09,0x00,0x07, + 0x01}}, /* 0x1d */ + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0x5a, + 0x00,0x83,0xff,0xff,0x29,0x09,0x00,0x07, + 0x01}}, /* 0x1e */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0x5a, + 0x00,0x83,0xff,0xff,0x2f,0x09,0x00,0x07, + 0x01}}, /* 0x1f */ + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, /* 0x20 */ + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, /* 0x21 */ + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, /* 0x22 */ + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, /* 0x23 */ + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, /* 0x24 */ + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, /* 0x25 */ + {{0x09,0xc7,0xc7,0x8d,0xd3,0x0b,0xe0,0x10, + 0xb0,0x83,0xaf,0xaf,0xe1,0x2f,0x01,0x04, + 0x00}}, /* 0x26 */ + {{0x40,0xef,0xef,0x84,0x03,0x1d,0xda,0x1f, + 0xa0,0x83,0x9f,0x9f,0xdb,0x1f,0x41,0x01, + 0x00}}, /* 0x27 */ + {{0x43,0xef,0xef,0x87,0x06,0x00,0xd4,0x1f, + 0xa0,0x83,0x9f,0x9f,0xd5,0x1f,0x41,0x05, + 0x63}}, /* 0x28 */ + {{0x45,0xef,0xef,0x89,0x07,0x01,0xd9,0x1f, + 0xa0,0x83,0x9f,0x9f,0xda,0x1f,0x41,0x05, + 0x63}}, /* 0x29 */ + {{0x40,0xef,0xef,0x84,0x03,0x1d,0xda,0x1f, + 0xa0,0x83,0x9f,0x9f,0xdb,0x1f,0x41,0x01, + 0x00}}, /* 0x2a */ + {{0x40,0xef,0xef,0x84,0x03,0x1d,0xda,0x1f, + 0xa0,0x83,0x9f,0x9f,0xdb,0x1f,0x41,0x01, + 0x00}}, /* 0x2b */ + {{0x40,0xef,0xef,0x84,0x03,0x1d,0xda,0x1f, + 0xa0,0x83,0x9f,0x9f,0xdb,0x1f,0x41,0x01, + 0x00}}, /* 0x2c */ + {{0x59,0xff,0xff,0x9d,0x17,0x13,0x33,0xba, + 0x00,0x83,0xff,0xff,0x34,0x0f,0x41,0x05, + 0x44}}, /* 0x2d */ + {{0x5b,0xff,0xff,0x9f,0x18,0x14,0x38,0xba, + 0x00,0x83,0xff,0xff,0x39,0x0f,0x41,0x05, + 0x44}}, /* 0x2e */ + {{0x5b,0xff,0xff,0x9f,0x18,0x14,0x3d,0xba, + 0x00,0x83,0xff,0xff,0x3e,0x0f,0x41,0x05, + 0x44}}, /* 0x2f */ + {{0x5d,0xff,0xff,0x81,0x19,0x95,0x41,0xba, + 0x00,0x84,0xff,0xff,0x42,0x0f,0x41,0x05, + 0x44}}, /* 0x30 */ + {{0x55,0xff,0xff,0x99,0x0d,0x0c,0x3e,0xba, + 0x00,0x84,0xff,0xff,0x3f,0x0f,0x41,0x05, + 0x00}}, /* 0x31 */ + {{0x7f,0x63,0x63,0x83,0x6c,0x1c,0x72,0xba, + 0x27,0x8b,0xdf,0xdf,0x73,0x00,0x00,0x06, + 0x01}}, /* 0x32 */ + {{0x7f,0x63,0x63,0x83,0x69,0x13,0x6f,0xba, + 0x26,0x89,0xdf,0xdf,0x6f,0x00,0x00,0x06, + 0x01}}, /* 0x33 */ + {{0x7f,0x63,0x63,0x82,0x6b,0x13,0x75,0xba, + 0x29,0x8c,0xdf,0xdf,0x75,0x00,0x00,0x06, + 0x01}}, /* 0x34 */ + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x24,0xf1, + 0xaf,0x85,0x3f,0x3f,0x25,0x30,0x00,0x02, + 0x01}}, /* 0x35 */ + {{0x9f,0x7f,0x7f,0x83,0x85,0x91,0x1e,0xf1, + 0xad,0x81,0x3f,0x3f,0x1f,0x30,0x00,0x02, + 0x01}}, /* 0x36 */ + {{0xa7,0x7f,0x7f,0x88,0x89,0x95,0x26,0xf1, /* 95 was 15 - illegal HBE! */ + 0xb1,0x85,0x3f,0x3f,0x27,0x30,0x00,0x02, + 0x01}}, /* 0x37 */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0xc4, + 0x7a,0x8e,0xcf,0xcf,0x29,0x21,0x00,0x07, + 0x01}}, /* 0x38 */ + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0xd4, + 0x7a,0x8e,0xcf,0xcf,0x29,0x21,0x00,0x07, + 0x01}}, /* 0x39 */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0xd4, + 0x7d,0x81,0xcf,0xcf,0x2f,0x21,0x00,0x07, + 0x01}}, /* 0x3a */ + {{0xdc,0x9f,0x9f,0x80,0xaf,0x9d,0xe6,0xff, /* 1280x960-60 - corrected */ + 0xc0,0x83,0xbf,0xbf,0xe7,0x10,0x00,0x07, + 0x01}}, /* 0x3b */ + {{0x6b,0x59,0x59,0x8f,0x5e,0x8c,0x0b,0x3e, + 0xe9,0x8b,0xdf,0xe7,0x04,0x00,0x00,0x05, + 0x00}}, /* 0x3c */ + {{0x6d,0x59,0x59,0x91,0x60,0x89,0x53,0xf0, /* 720x576, corrected to 60Hz */ + 0x41,0x84,0x3f,0x3f,0x54,0x00,0x00,0x05, + 0x41}}, /* 0x3d */ + {{0x86,0x6a,0x6a,0x8a,0x74,0x06,0x8c,0x15, + 0x4f,0x83,0xef,0xef,0x8d,0x30,0x00,0x02, + 0x00}}, /* 0x3e */ + {{0x81,0x6a,0x6a,0x85,0x70,0x00,0x0f,0x3e, + 0xeb,0x8e,0xdf,0xdf,0x10,0x00,0x00,0x02, + 0x00}}, /* 0x3f */ + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x1e,0xf1, + 0xae,0x85,0x57,0x57,0x1f,0x30,0x00,0x02, + 0x01}}, /* 0x40 */ + {{0xa3,0x7f,0x7f,0x87,0x86,0x97,0x24,0xf5, + 0x02,0x88,0xff,0xff,0x25,0x10,0x00,0x02, + 0x01}}, /* 0x41 */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x20,0xf5, + 0x03,0x88,0xff,0xff,0x21,0x10,0x00,0x07, + 0x01}}, /* 0x42 */ + {{0xe6,0xae,0xae,0x8a,0xbd,0x90,0x3d,0x10, + 0x1a,0x8d,0x19,0x19,0x3e,0x2f,0x00,0x03, + 0x00}}, /* 0x43 */ + {{0xc3,0x8f,0x8f,0x87,0x9b,0x0b,0x82,0xef, /* 1152x864-75 */ + 0x60,0x83,0x5f,0x5f,0x83,0x10,0x00,0x07, + 0x01}}, /* 0x44 */ + {{0x86,0x69,0x69,0x8A,0x74,0x06,0x8C,0x15, /* 848x480-38i */ + 0x4F,0x83,0xEF,0xEF,0x8D,0x30,0x00,0x02, + 0x00}}, /* 0x45 */ + {{0x83,0x69,0x69,0x87,0x6f,0x1d,0x03,0x3E, /* 848x480-60 */ + 0xE5,0x8d,0xDF,0xe4,0x04,0x00,0x00,0x06, + 0x00}}, /* 0x46 */ + {{0x86,0x6A,0x6A,0x8A,0x74,0x06,0x8C,0x15, /* 856x480-38i */ + 0x4F,0x83,0xEF,0xEF,0x8D,0x30,0x00,0x02, + 0x00}}, /* 0x47 */ + {{0x81,0x6A,0x6A,0x85,0x70,0x00,0x0F,0x3E, /* 856x480-60 */ + 0xEB,0x8E,0xDF,0xDF,0x10,0x00,0x00,0x02, + 0x00}}, /* 0x48 */ + {{0xdd,0xa9,0xa9,0x81,0xb4,0x97,0x26,0xfd, /* 1360x768-60 */ + 0x01,0x8d,0xff,0x00,0x27,0x10,0x00,0x03, + 0x01}}, /* 0x49 */ + {{0xd9,0x8f,0x8f,0x9d,0xba,0x0a,0x8a,0xff, /* 1152x864-84 */ + 0x60,0x8b,0x5f,0x5f,0x8b,0x10,0x00,0x03, + 0x01}}, /* 0x4a */ + {{0xea,0xae,0xae,0x8e,0xba,0x82,0x40,0x10, /* 1400x1050-75 */ + 0x1b,0x87,0x19,0x1a,0x41,0x0f,0x00,0x03, + 0x00}}, /* 0x4b */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0xf1,0xff, /* 1280x960-85 */ + 0xc0,0x83,0xbf,0xbf,0xf2,0x10,0x00,0x07, + 0x01}}, /* 0x4c */ + {{0x75,0x5f,0x5f,0x99,0x66,0x90,0x53,0xf0, /* 768x576, corrected to 60Hz */ + 0x41,0x84,0x3f,0x3f,0x54,0x00,0x00,0x05, + 0x41}}, /* 0x4d */ + {{0x5f,0x27,0x4f,0x83,0x55,0x81,0x0b,0x3e, /* FSTN 320x240 (working) */ + 0xe9,0x8b,0xdf,0xe8,0x0c,0x00,0x00,0x05, + 0x00}}, /* 0x4e */ + {{0xcd,0x9f,0x9f,0x91,0xab,0x1c,0x3a,0xff, /* 1280x800-60 */ + 0x20,0x83,0x1f,0x1f,0x3b,0x10,0x00,0x07, + 0x21}}, /* 0x4f */ + {{0x15,0xd1,0xd1,0x99,0xe2,0x19,0x3d,0x10, /* 1680x1050-60 */ + 0x1a,0x8d,0x19,0x19,0x3e,0x2f,0x01,0x0c, + 0x20}}, /* 0x50 */ + {{0x0e,0xef,0xef,0x92,0xfe,0x03,0x30,0xf0, /* 1920x1080-60i */ + 0x1e,0x83,0x1b,0x1c,0x31,0x00,0x01,0x00, + 0x61}}, /* 0x51 */ + {{0x85,0x77,0x77,0x89,0x7d,0x01,0x31,0xf0, /* 960x540-60 */ + 0x1e,0x84,0x1b,0x1c,0x32,0x00,0x00,0x02, + 0x41}}, /* 0x52 */ + {{0x87,0x77,0x77,0x8b,0x81,0x0b,0x68,0xf0, /* 960x600-60 */ + 0x5a,0x80,0x57,0x57,0x69,0x00,0x00,0x02, + 0x01}}, /* 0x53 */ + {{0xcd,0x8f,0x8f,0x91,0x9b,0x1b,0x7a,0xff, /* 1152x864-60 */ + 0x64,0x8c,0x5f,0x62,0x7b,0x10,0x00,0x07, + 0x41}}, /* 0x54 */ + {{0x5c,0x4f,0x4f,0x80,0x57,0x80,0xa3,0x1f, /* fake 640x400@60Hz (for LCD and TV, not actually used) */ + 0x98,0x8c,0x8f,0x96,0xa4,0x30,0x00,0x05, + 0x40}}, /* 0x55 */ + {{0x2c,0x27,0x27,0x90,0x2d,0x92,0xa4,0x1f, /* fake 320x200@60Hz (for LCD and TV, not actually used) */ + 0x98,0x8c,0x8f,0x96,0xa5,0x30,0x00,0x04, + 0x00}}, /* 0x56 */ + {{0xd7,0xc7,0xc7,0x9b,0xd1,0x15,0xd1,0x10, /* 1600x1200 for LCDA */ + 0xb2,0x86,0xaf,0xb0,0xd2,0x2f,0x00,0x03, + 0x00}}, /* 0x57 */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0xdc, /* 1280x768 (1280x1024) 60 Hz */ + 0x92,0x86,0xff,0x91,0x29,0x21,0x00,0x07, + 0x01}}, /* 0x58 */ + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0xdc, /* 1280x768 (1280x1024) 75 Hz */ + 0x92,0x86,0xff,0x91,0x29,0x21,0x00,0x07, + 0x01}}, /* 0x59 */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0xdc, /* 1280x768 (1280x1024) 85 Hz */ + 0x95,0x89,0xff,0x94,0x2f,0x21,0x00,0x07, + 0x01}}, /* 0x5a */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0xde, /* 1280x800 (1280x1024) 60 Hz */ + 0xa2,0x86,0x1f,0xa1,0x29,0x01,0x00,0x07, + 0x01}}, /* 0x5b */ + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0xde, /* 1280x800 (1280x1024) 75 Hz */ + 0xa2,0x86,0x1f,0xa1,0x29,0x01,0x00,0x07, + 0x01}}, /* 0x5c */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0xde, /* 1280x800 (1280x1024) 85 Hz */ + 0xa5,0x89,0x1f,0xa4,0x2f,0x01,0x00,0x07, + 0x01}}, /* 0x5d */ + {{0x7f,0x63,0x63,0x83,0x6d,0x1d,0x0b,0x3e, /* 800x480 (wide) 60 Hz */ + 0xe9,0x8b,0xdf,0xe8,0x0c,0x00,0x00,0x06, + 0x00}}, /* 0x5e */ + {{0xa0,0x7f,0x7f,0x84,0x85,0x97,0x52,0xf0, /* 1024x576 (wide) 60 Hz */ + 0x41,0x85,0x3f,0x40,0x53,0x00,0x00,0x02, + 0x01}}, /* 0x5f */ + {{0xc9,0x9f,0x9f,0x8d,0xb0,0x15,0xec,0xf0, /* 1280x720 (wide) 60 Hz */ + 0xd4,0x89,0xcf,0xd3,0xed,0x20,0x00,0x07, + 0x01}}, /* 0x60 */ + {{0xcb,0x9f,0x9f,0x8f,0xa5,0x13,0x5b,0xff, /* 1280x854-60 wide */ + 0x56,0x89,0x55,0x55,0x5c,0x30,0x00,0x07, + 0x01}}, /* 0x61 */ + {{0xce,0x9f,0x9f,0x92,0xa9,0x17,0x28,0xde, /* 1280x854 (1280x1024) 60 Hz */ + 0xbd,0x81,0x55,0xbc,0x29,0x01,0x00,0x07, + 0x41}}, /* 0x62 */ + {{0xce,0x9f,0x9f,0x92,0xa5,0x17,0x28,0xde, /* 1280x854 (1280x1024) 75 Hz */ + 0xbd,0x81,0x55,0xbc,0x29,0x01,0x00,0x07, + 0x41}}, /* 0x63 */ + {{0xd3,0x9f,0x9f,0x97,0xab,0x1f,0x2e,0xde, /* 1280x854 (1280x1024) 85 Hz */ + 0xc0,0x84,0x55,0xbf,0x2f,0x01,0x00,0x07, + 0x41}} /* 0x64 */ +}; + +static const struct SiS_MCLKData SiS310_MCLKData_0_315[] = +{ + { 0x3b,0x22,0x01,143}, + { 0x5c,0x23,0x01,166}, + { 0x5c,0x23,0x01,166}, + { 0x5c,0x23,0x01,166}, + { 0x5c,0x23,0x01,166}, + { 0x5c,0x23,0x01,166}, + { 0x5c,0x23,0x01,166}, + { 0x5c,0x23,0x01,166} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_0_650[] = +{ + { 0x5a,0x64,0x82, 66}, + { 0xb3,0x45,0x82, 83}, + { 0x37,0x61,0x82,100}, + { 0x37,0x22,0x82,133}, + { 0x37,0x61,0x82,100}, + { 0x37,0x22,0x82,133}, + { 0x37,0x22,0x82,133}, + { 0x37,0x22,0x82,133} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_0_330[] = +{ + { 0x5c,0x23,0x01,166}, + { 0x5c,0x23,0x01,166}, + { 0x7c,0x08,0x01,200}, + { 0x79,0x06,0x01,250}, + { 0x7c,0x08,0x01,200}, + { 0x7c,0x08,0x01,200}, + { 0x7c,0x08,0x01,200}, + { 0x79,0x06,0x01,250} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_0_660[] = +{ + { 0x5c,0x23,0x82,166}, + { 0x5c,0x23,0x82,166}, + { 0x37,0x21,0x82,200}, + { 0x37,0x22,0x82,133}, + { 0x29,0x21,0x82,150}, + { 0x5c,0x23,0x82,166}, + { 0x65,0x23,0x82,183}, + { 0x37,0x21,0x82,200} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_0_760[] = +{ + { 0x37,0x22,0x82,133}, + { 0x5c,0x23,0x82,166}, + { 0x65,0x23,0x82,183}, + { 0x7c,0x08,0x82,200}, + { 0x29,0x21,0x82,150}, + { 0x5c,0x23,0x82,166}, + { 0x65,0x23,0x82,183}, + { 0x37,0x21,0x82,200} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_0_761[] = +{ + { 0x37,0x22,0x82,133}, /* Preliminary */ + { 0x5c,0x23,0x82,166}, + { 0x65,0x23,0x82,183}, + { 0x7c,0x08,0x82,200}, + { 0x29,0x21,0x82,150}, + { 0x5c,0x23,0x82,166}, + { 0x65,0x23,0x82,183}, + { 0x37,0x21,0x82,200} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_0_340[] = +{ + { 0x79,0x06,0x01,250}, + { 0x7c,0x08,0x01,200}, + { 0x7c,0x08,0x80,200}, + { 0x79,0x06,0x80,250}, + { 0x29,0x01,0x81,300}, + { 0x29,0x01,0x81,300}, + { 0x29,0x01,0x81,300}, + { 0x29,0x01,0x81,300} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_1[] = /* ECLK */ +{ + { 0x29,0x21,0x82,150}, + { 0x5c,0x23,0x82,166}, + { 0x65,0x23,0x82,183}, + { 0x37,0x21,0x82,200}, + { 0x37,0x22,0x82,133}, + { 0x37,0x22,0x82,133}, + { 0x37,0x22,0x82,133}, + { 0x37,0x22,0x82,133} +}; + +static const struct SiS_MCLKData SiS310_MCLKData_1_340[] = +{ + { 0x7c,0x08,0x01,200}, + { 0x7c,0x08,0x01,200}, + { 0x7c,0x08,0x80,200}, + { 0x79,0x06,0x80,250}, + { 0x29,0x01,0x81,300}, + { 0x29,0x01,0x81,300}, + { 0x29,0x01,0x81,300}, + { 0x29,0x01,0x81,300} +}; + +static struct SiS_VCLKData SiS310_VCLKData[] = +{ + { 0x1b,0xe1, 25}, /* 0x00 */ + { 0x4e,0xe4, 28}, /* 0x01 */ + { 0x57,0xe4, 31}, /* 0x02 */ + { 0xc3,0xc8, 36}, /* 0x03 */ + { 0x42,0xe2, 40}, /* 0x04 */ + { 0xfe,0xcd, 43}, /* 0x05 */ + { 0x5d,0xc4, 44}, /* 0x06 */ + { 0x52,0xe2, 49}, /* 0x07 */ + { 0x53,0xe2, 50}, /* 0x08 */ + { 0x74,0x67, 52}, /* 0x09 */ + { 0x6d,0x66, 56}, /* 0x0a */ + { 0x5a,0x64, 65}, /* 0x0b */ /* was 6c c3 - WRONG */ + { 0x46,0x44, 67}, /* 0x0c */ + { 0xb1,0x46, 68}, /* 0x0d */ + { 0xd3,0x4a, 72}, /* 0x0e */ + { 0x29,0x61, 75}, /* 0x0f */ + { 0x6e,0x46, 76}, /* 0x10 */ + { 0x2b,0x61, 78}, /* 0x11 */ + { 0x31,0x42, 79}, /* 0x12 */ + { 0xab,0x44, 83}, /* 0x13 */ + { 0x46,0x25, 84}, /* 0x14 */ + { 0x78,0x29, 86}, /* 0x15 */ + { 0x62,0x44, 94}, /* 0x16 */ + { 0x2b,0x41,104}, /* 0x17 */ + { 0x3a,0x23,105}, /* 0x18 */ + { 0x70,0x44,108}, /* 0x19 */ + { 0x3c,0x23,109}, /* 0x1a */ + { 0x5e,0x43,113}, /* 0x1b */ + { 0xbc,0x44,116}, /* 0x1c */ + { 0xe0,0x46,132}, /* 0x1d */ + { 0x54,0x42,135}, /* 0x1e */ + { 0xea,0x2a,139}, /* 0x1f */ + { 0x41,0x22,157}, /* 0x20 */ + { 0x70,0x24,162}, /* 0x21 */ + { 0x30,0x21,175}, /* 0x22 */ + { 0x4e,0x22,189}, /* 0x23 */ + { 0xde,0x26,194}, /* 0x24 */ + { 0x62,0x06,202}, /* 0x25 */ + { 0x3f,0x03,229}, /* 0x26 */ + { 0xb8,0x06,234}, /* 0x27 */ + { 0x34,0x02,253}, /* 0x28 */ + { 0x58,0x04,255}, /* 0x29 */ + { 0x24,0x01,265}, /* 0x2a */ + { 0x9b,0x02,267}, /* 0x2b */ + { 0x70,0x05,270}, /* 0x2c */ + { 0x25,0x01,272}, /* 0x2d */ + { 0x9c,0x02,277}, /* 0x2e */ + { 0x27,0x01,286}, /* 0x2f */ + { 0x3c,0x02,291}, /* 0x30 */ + { 0xef,0x0a,292}, /* 0x31 */ + { 0xf6,0x0a,310}, /* 0x32 */ + { 0x95,0x01,315}, /* 0x33 */ + { 0xf0,0x09,324}, /* 0x34 */ + { 0xfe,0x0a,331}, /* 0x35 */ + { 0xf3,0x09,332}, /* 0x36 */ + { 0xea,0x08,340}, /* 0x37 */ + { 0xe8,0x07,376}, /* 0x38 */ + { 0xde,0x06,389}, /* 0x39 */ + { 0x52,0x2a, 54}, /* 0x3a 301 TV */ + { 0x52,0x6a, 27}, /* 0x3b 301 TV */ + { 0x62,0x24, 70}, /* 0x3c 301 TV */ + { 0x62,0x64, 70}, /* 0x3d 301 TV */ + { 0xa8,0x4c, 30}, /* 0x3e 301 TV */ + { 0x20,0x26, 33}, /* 0x3f 301 TV */ + { 0x31,0xc2, 39}, /* 0x40 */ + { 0x60,0x36, 30}, /* 0x41 Chrontel */ + { 0x40,0x4a, 28}, /* 0x42 Chrontel */ + { 0x9f,0x46, 44}, /* 0x43 Chrontel */ + { 0x97,0x2c, 26}, /* 0x44 */ + { 0x44,0xe4, 25}, /* 0x45 Chrontel */ + { 0x7e,0x32, 47}, /* 0x46 Chrontel */ + { 0x8a,0x24, 31}, /* 0x47 Chrontel */ + { 0x97,0x2c, 26}, /* 0x48 Chrontel */ + { 0xce,0x3c, 39}, /* 0x49 */ + { 0x52,0x4a, 36}, /* 0x4a Chrontel */ + { 0x34,0x61, 95}, /* 0x4b */ + { 0x78,0x27,108}, /* 0x4c - was 102 */ + { 0x66,0x43,123}, /* 0x4d Modes 0x26-0x28 (1400x1050) */ + { 0x41,0x4e, 21}, /* 0x4e */ + { 0xa1,0x4a, 29}, /* 0x4f Chrontel */ + { 0x19,0x42, 42}, /* 0x50 */ + { 0x54,0x46, 58}, /* 0x51 Chrontel */ + { 0x25,0x42, 61}, /* 0x52 */ + { 0x44,0x44, 66}, /* 0x53 Chrontel */ + { 0x3a,0x62, 70}, /* 0x54 Chrontel */ + { 0x62,0xc6, 34}, /* 0x55 848x480-60 */ + { 0x6a,0xc6, 37}, /* 0x56 848x480-75 - TEMP */ + { 0xbf,0xc8, 35}, /* 0x57 856x480-38i,60 */ + { 0x30,0x23, 88}, /* 0x58 1360x768-62 (is 60Hz!) */ + { 0x52,0x07,149}, /* 0x59 1280x960-85 */ + { 0x56,0x07,156}, /* 0x5a 1400x1050-75 */ + { 0x70,0x29, 81}, /* 0x5b 1280x768 LCD */ + { 0x45,0x25, 83}, /* 0x5c 1280x800 */ + { 0x70,0x0a,147}, /* 0x5d 1680x1050 */ + { 0x70,0x24,162}, /* 0x5e 1600x1200 */ + { 0x5a,0x64, 65}, /* 0x5f 1280x720 - temp */ + { 0x63,0x46, 68}, /* 0x60 1280x768_2 */ + { 0x31,0x42, 79}, /* 0x61 1280x768_3 - temp */ + { 0, 0, 0}, /* 0x62 - custom (will be filled out at run-time) */ + { 0x5a,0x64, 65}, /* 0x63 1280x720 (LCD LVDS) */ + { 0x70,0x28, 90}, /* 0x64 1152x864@60 */ + { 0x41,0xc4, 32}, /* 0x65 848x480@60 */ + { 0x5c,0xc6, 32}, /* 0x66 856x480@60 */ + { 0x76,0xe7, 27}, /* 0x67 720x480@60 */ + { 0x5f,0xc6, 33}, /* 0x68 720/768x576@60 */ + { 0x52,0x27, 75}, /* 0x69 1920x1080i 60Hz interlaced */ + { 0x7c,0x6b, 38}, /* 0x6a 960x540@60 */ + { 0xe3,0x56, 41}, /* 0x6b 960x600@60 */ + { 0x45,0x25, 83}, /* 0x6c 1280x800 */ + { 0x70,0x28, 90}, /* 0x6d 1152x864@60 */ + { 0x15,0xe1, 20}, /* 0x6e 640x400@60 (fake, not actually used) */ + { 0x5f,0xc6, 33}, /* 0x6f 720x576@60 */ + { 0x37,0x5a, 10}, /* 0x70 320x200@60 (fake, not actually used) */ + { 0x2b,0xc2, 35}, /* 0x71 768x576@60 */ + { 0xa8,0x42,131}, /* 0x72 1600x1200@60 for LCDA */ + { 0x1b,0xc1, 34}, /* 0x73 800x480 60Hz (wide) */ + { 0x41,0x64, 48}, /* 0x74 1024x576 60Hz (wide) */ + { 0x52,0x27, 75}, /* 0x75 1280x720 60Hz (wide) */ + { 0x75,0x13, 84} /* 0x76 1280x854 60Hz (wide) */ +}; + +static struct SiS_VBVCLKData SiS310_VBVCLKData[] = +{ + { 0x1b,0xe1, 25}, /* 0x00 */ + { 0x4e,0xe4, 28}, /* 0x01 */ + { 0x57,0xe4, 31}, /* 0x02 */ + { 0xc3,0xc8, 36}, /* 0x03 */ + { 0x42,0x47, 40}, /* 0x04 */ + { 0xfe,0xcd, 43}, /* 0x05 */ + { 0x5d,0xc4, 44}, /* 0x06 */ + { 0x52,0x47, 49}, /* 0x07 */ + { 0x53,0x47, 50}, /* 0x08 */ + { 0x74,0x67, 52}, /* 0x09 */ + { 0x6d,0x66, 56}, /* 0x0a */ + { 0x35,0x62, 65}, /* 0x0b */ /* Was 0x5a,0x64 - 650/LVDS+301: 35,62 */ + { 0x46,0x44, 67}, /* 0x0c */ + { 0xb1,0x46, 68}, /* 0x0d */ + { 0xd3,0x4a, 72}, /* 0x0e */ + { 0x29,0x61, 75}, /* 0x0f */ + { 0x6d,0x46, 75}, /* 0x10 */ + { 0x41,0x43, 78}, /* 0x11 */ + { 0x31,0x42, 79}, /* 0x12 */ + { 0xab,0x44, 83}, /* 0x13 */ + { 0x46,0x25, 84}, /* 0x14 */ + { 0x78,0x29, 86}, /* 0x15 */ + { 0x62,0x44, 94}, /* 0x16 */ + { 0x2b,0x22,104}, /* 0x17 */ + { 0x49,0x24,105}, /* 0x18 */ + { 0xf8,0x2f,108}, /* 0x19 */ /* 1400x1050 LCD */ + { 0x3c,0x23,109}, /* 0x1a */ + { 0x5e,0x43,113}, /* 0x1b */ + { 0xbc,0x44,116}, /* 0x1c */ + { 0xe0,0x46,132}, /* 0x1d */ + { 0xe2,0x46,135}, /* 0x1e */ /* 1280x1024-75, better clock for VGA2 */ + { 0xe5,0x46,139}, /* 0x1f */ /* 1024x768-120, better clock for VGA2 */ + { 0x15,0x01,157}, /* 0x20 */ /* 1280x1024-85, better clock for VGA2 */ + { 0x70,0x09,162}, /* 0x21 */ /* 1600x1200-60, better clock for VGA2 */ + { 0x30,0x21,175}, /* 0x22 */ + { 0x4e,0x22,189}, /* 0x23 */ + { 0xde,0x26,194}, /* 0x24 */ + { 0x70,0x07,202}, /* 0x25 */ + { 0x3f,0x03,229}, /* 0x26 */ + { 0xb8,0x06,234}, /* 0x27 */ + { 0x34,0x02,253}, /* 0x28 */ + { 0x58,0x04,255}, /* 0x29 */ + { 0x24,0x01,265}, /* 0x2a */ + { 0x9b,0x02,267}, /* 0x2b */ + { 0x70,0x05,270}, /* 0x2c */ + { 0x25,0x01,272}, /* 0x2d */ + { 0x9c,0x02,277}, /* 0x2e */ + { 0x27,0x01,286}, /* 0x2f */ + { 0x3c,0x02,291}, /* 0x30 */ + { 0xef,0x0a,292}, /* 0x31 */ + { 0xf6,0x0a,310}, /* 0x32 */ + { 0x95,0x01,315}, /* 0x33 */ + { 0xf0,0x09,324}, /* 0x34 */ + { 0xfe,0x0a,331}, /* 0x35 */ + { 0xf3,0x09,332}, /* 0x36 */ + { 0xea,0x08,340}, /* 0x37 */ + { 0xe8,0x07,376}, /* 0x38 */ + { 0xde,0x06,389}, /* 0x39 */ + { 0x52,0x2a, 54}, /* 0x3a 301 TV - start */ + { 0x52,0x6a, 27}, /* 0x3b 301 TV */ + { 0x62,0x24, 70}, /* 0x3c 301 TV */ + { 0x62,0x64, 70}, /* 0x3d 301 TV */ + { 0xa8,0x4c, 30}, /* 0x3e 301 TV */ + { 0x20,0x26, 33}, /* 0x3f 301 TV */ + { 0x31,0xc2, 39}, /* 0x40 */ + { 0x2e,0x48, 25}, /* 0x41 Replacement for LCD on 315 for index 0 */ + { 0x24,0x46, 25}, /* 0x42 Replacement for LCD on 315 for modes 0x01, 0x03, 0x0f, 0x10, 0x12 */ + { 0x26,0x64, 28}, /* 0x43 Replacement for LCD on 315 for index 1 */ + { 0x37,0x64, 40}, /* 0x44 Replacement for LCD on 315 for index 4 */ + { 0xa1,0x42,108}, /* 0x45 1280x960 LCD */ + { 0x37,0x61,100}, /* 0x46 1280x960 LCD */ + { 0x78,0x27,108}, /* 0x47 */ + { 0x97,0x2c, 26}, /* 0x48 UNUSED */ + { 0xce,0x3c, 39}, /* 0x49 UNUSED */ + { 0x52,0x4a, 36}, /* 0x4a UNUSED */ + { 0x34,0x61, 95}, /* 0x4b UNUSED */ + { 0x78,0x27,108}, /* 0x4c UNUSED */ + { 0x66,0x43,123}, /* 0x4d 1400x1050-60 */ + { 0x41,0x4e, 21}, /* 0x4e */ + { 0xa1,0x4a, 29}, /* 0x4f UNUSED */ + { 0x19,0x42, 42}, /* 0x50 UNUSED */ + { 0x54,0x46, 58}, /* 0x51 UNUSED */ + { 0x25,0x42, 61}, /* 0x52 UNUSED */ + { 0x44,0x44, 66}, /* 0x53 UNUSED */ + { 0x3a,0x62, 70}, /* 0x54 UNUSED */ + { 0x62,0xc6, 34}, /* 0x55 848x480-60 */ + { 0x6a,0xc6, 37}, /* 0x56 848x480-75 - TEMP, UNUSED */ + { 0xbf,0xc8, 35}, /* 0x57 856x480-38i,60 */ + { 0x30,0x23, 88}, /* 0x58 1360x768-62 (is 60Hz!) TEMP, UNUSED */ + { 0x52,0x07,149}, /* 0x59 1280x960-85 */ + { 0x56,0x07,156}, /* 0x5a 1400x1050-75 */ + { 0x70,0x29, 81}, /* 0x5b 1280x768 LCD (TMDS) */ + { 0xce,0x1e, 73}, /* 0x5c 1280x800_2 LCD (SiS LVDS) - (CRT1: 45 25 83) */ + { 0xbe,0x44,121}, /* 0x5d 1680x1050 LCD */ + { 0x70,0x24,162}, /* 0x5e 1600x1200 LCD */ + { 0x52,0x27, 75}, /* 0x5f 1280x720 (TMDS + HDTV) (correct) */ + { 0xc8,0x48, 77}, /* 0x60 1280x768_2 (SiS LVDS) */ + { 0x31,0x42, 79}, /* 0x61 1280x768_3 (SiS LVDS) - temp */ + { 0, 0, 0}, /* 0x62 - custom (will be filled out at run-time) */ + { 0x9c,0x62, 69}, /* 0x63 1280x720 (SiS LVDS) */ + { 0x70,0x28, 90}, /* 0x64 1152x864@60 */ + { 0x41,0xc4, 32}, /* 0x65 848x480@60 */ + { 0x5c,0xc6, 32}, /* 0x66 856x480@60 */ + { 0x76,0xe7, 27}, /* 0x67 720x480@60 */ + { 0x5f,0xc6, 33}, /* 0x68 720/768x576@60 */ + { 0x52,0x27, 75}, /* 0x69 1920x1080i 60Hz interlaced (UNUSED) */ + { 0x7c,0x6b, 38}, /* 0x6a 960x540@60 */ + { 0xe3,0x56, 41}, /* 0x6b 960x600@60 */ + { 0x9c,0x62, 69}, /* 0x6c 1280x800 (SiS TMDS) (special) */ + { 0x70,0x28, 90}, /* 0x6d 1152x864@60 */ + { 0x15,0xe1, 20}, /* 0x6e 640x400@60 (fake, not actually used) */ + { 0x5f,0xc6, 33}, /* 0x6f 720x576@60 */ + { 0x37,0x5a, 10}, /* 0x70 320x200@60 (fake, not actually used) */ + { 0x2b,0xc2, 35}, /* 0x71 768@576@60 */ + { 0xa8,0x42,131}, /* 0x72 1600x1200@60 for LCDA */ + { 0x1b,0xc1, 34}, /* 0x73 800x480 60Hz (wide) */ + { 0x41,0x64, 48}, /* 0x74 1024x576 60Hz (wide) */ + { 0x52,0x27, 75}, /* 0x75 1280x720 60Hz (wide) */ + { 0x75,0x13, 84} /* 0x76 1280x854 60Hz (SiS LVDS) LCD */ +}; + +static const unsigned char SiS310_SR15[4 * 8] = +{ + 0x00,0x04,0x60,0x60, + 0x0f,0x0f,0x0f,0x0f, + 0xba,0xba,0xba,0xba, + 0xa9,0xa9,0xac,0xac, + 0xa0,0xa0,0xa0,0xa8, + 0x00,0x00,0x02,0x02, + 0x30,0x30,0x40,0x40, + 0x00,0xa5,0xfb,0xf6 +}; + +static const struct SiS_PanelDelayTbl SiS310_PanelDelayTbl[] = +{ + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}}, + {{0x10,0x40}} +}; + +static const struct SiS_PanelDelayTbl SiS310_PanelDelayTblLVDS[] = +{ + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}}, + {{0x28,0xc8}} +}; + +/**************************************************************/ +/* SIS VIDEO BRIDGE ----------------------------------------- */ +/**************************************************************/ + +static const struct SiS_LCDData SiS310_St2LCD1024x768Data[] = +{ + { 62, 25, 800, 546,1344, 806}, + { 32, 15, 930, 546,1344, 806}, + { 62, 25, 800, 546,1344, 806}, + { 104, 45, 945, 496,1344, 806}, + { 62, 25, 800, 546,1344, 806}, + { 31, 18,1008, 624,1344, 806}, + { 1, 1,1344, 806,1344, 806} +}; + +static const struct SiS_LCDData SiS310_ExtLCD1024x768Data[] = +{ + { 42, 25,1536, 419,1344, 806}, + { 48, 25,1536, 369,1344, 806}, + { 42, 25,1536, 419,1344, 806}, + { 48, 25,1536, 369,1344, 806}, + { 12, 5, 896, 500,1344, 806}, + { 42, 25,1024, 625,1344, 806}, + { 1, 1,1344, 806,1344, 806} +}; + +static const struct SiS_LCDData SiS310_St2LCD1280x1024Data[] = +{ + { 22, 5, 800, 510,1650,1088}, + { 22, 5, 800, 510,1650,1088}, + { 176, 45, 900, 510,1650,1088}, + { 176, 45, 900, 510,1650,1088}, + { 22, 5, 800, 510,1650,1088}, + { 13, 5,1024, 675,1560,1152}, + { 16, 9,1266, 804,1688,1072}, + { 1, 1,1688,1066,1688,1066} +}; + +static const struct SiS_LCDData SiS310_ExtLCD1280x1024Data[] = +{ + { 211, 60,1024, 501,1688,1066}, + { 211, 60,1024, 508,1688,1066}, + { 211, 60,1024, 501,1688,1066}, + { 211, 60,1024, 508,1688,1066}, + { 211, 60,1024, 500,1688,1066}, + { 211, 75,1024, 625,1688,1066}, + { 211, 120,1280, 798,1688,1066}, + { 1, 1,1688,1066,1688,1066} +}; + +static const struct SiS_Part2PortTbl SiS310_CRT2Part2_1024x768_1[] = +{ + {{0x25,0x12,0xc9,0xdc,0xb6,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x2c,0x12,0x9a,0xae,0x88,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x25,0x12,0xc9,0xdc,0xb6,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + {{0x38,0x13,0x16,0x0c,0xe6,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x38,0x18,0x16,0x00,0x00,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x36,0x13,0x13,0x25,0xff,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}} +}; + +/**************************************************************/ +/* LVDS, CHRONTEL ------------------------------------------- */ +/**************************************************************/ + +static const struct SiS_LVDSData SiS310_CHTVUPALData[] = +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 625, 840, 625}, + { 960, 750, 960, 750}, + {1400,1000,1400,1000} +}; + +static const struct SiS_LVDSData SiS310_CHTVOPALData[] = +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 625, 840, 625}, + { 944, 625, 944, 625}, + {1400, 875,1400, 875} +}; + +static const struct SiS_LVDSData SiS310_CHTVUPALMData[] = +{ + { 840, 600, 840, 600}, + { 840, 600, 840, 600}, + { 840, 600, 840, 600}, + { 840, 600, 840, 600}, + { 784, 600, 784, 600}, + {1064, 750,1064, 750}, + {1160, 945,1160, 945} +}; + +static const struct SiS_LVDSData SiS310_CHTVOPALMData[] = +{ + { 840, 525, 840, 525}, + { 840, 525, 840, 525}, + { 840, 525, 840, 525}, + { 840, 525, 840, 525}, + { 784, 525, 784, 525}, + {1040, 700,1040, 700}, + {1160, 840,1160, 840} +}; + +static const struct SiS_LVDSData SiS310_CHTVUPALNData[] = +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 625, 840, 625}, + { 960, 750, 960, 750}, + {1400,1000,1400,1000} +}; + +static const struct SiS_LVDSData SiS310_CHTVOPALNData[] = +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 625, 840, 625}, + { 944, 625, 944, 625}, + {1400, 875,1400, 875} +}; + +static const struct SiS_LVDSData SiS310_CHTVSOPALData[] = /* (super overscan - no effect on 7019) */ +{ + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + {1008, 625,1008, 625}, + { 840, 625, 840, 625}, + { 944, 625, 944, 625}, + {1400, 875,1400, 875} +}; + +/* CRT1 CRTC for Chrontel TV slave modes */ + +static const struct SiS_LVDSCRT1Data SiS310_CHTVCRT1UNTSC[] = +{ + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xe8,0x84,0x8f,0x57,0x20,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xd0,0x82,0x5d,0x57,0x00,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xe8,0x84,0x8f,0x57,0x20,0x00,0x01, + 0x00 }}, + {{0x64,0x4f,0x88,0x56,0x9f,0x56,0x3e, + 0xd0,0x82,0x5d,0x57,0x00,0x00,0x01, + 0x00 }}, + {{0x5d,0x4f,0x81,0x56,0x99,0x56,0xba, + 0x0a,0x84,0xdf,0x57,0x00,0x00,0x01, + 0x00 }}, + {{0x80,0x63,0x84,0x6d,0x0f,0xec,0xf0, + 0x7a,0x8f,0x57,0xed,0x20,0x00,0x06, + 0x01 }}, + {{0x8c,0x7f,0x90,0x86,0x09,0xaf,0xf5, + 0x36,0x88,0xff,0xb0,0x10,0x00,0x02, + 0x01}} +}; + +static const struct SiS_LVDSCRT1Data SiS310_CHTVCRT1ONTSC[] = +{ + {{0x63,0x4f,0x87,0x5a,0x9f,0x0b,0x3e, + 0xc0,0x84,0x8f,0x0c,0x20,0x00,0x01, + 0x00 }}, + {{0x63,0x4f,0x87,0x5a,0x9f,0x0b,0x3e, + 0xb0,0x8d,0x5d,0x0c,0x00,0x00,0x01, + 0x00 }}, + {{0x63,0x4f,0x87,0x5a,0x9f,0x0b,0x3e, + 0xc0,0x84,0x8f,0x0c,0x20,0x00,0x01, + 0x00 }}, + {{0x63,0x4f,0x87,0x5a,0x9f,0x0b,0x3e, + 0xb0,0x8d,0x5d,0x0c,0x00,0x00,0x01, + 0x00 }}, + {{0x5d,0x4f,0x81,0x58,0x9d,0x0b,0x3e, + 0xe8,0x84,0xdf,0x0c,0x00,0x00,0x01, + 0x00 }}, + {{0x7d,0x63,0x81,0x68,0x0e,0xba,0xf0, + 0x78,0x8a,0x57,0xbb,0x20,0x00,0x06, + 0x01 }}, + {{0x8c,0x7f,0x90,0x82,0x06,0x46,0xf5, + 0x15,0x88,0xff,0x47,0x70,0x00,0x02, + 0x01 }} +}; + +static const struct SiS_LVDSCRT1Data SiS310_CHTVCRT1UPAL[] = +{ + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf8,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf8,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x64,0x4f,0x88,0x5a,0x9f,0x6f,0xba, + 0x15,0x83,0xdf,0x70,0x00,0x00,0x01, + 0x00 }}, + {{0x73,0x63,0x97,0x69,0x8b,0xec,0xf0, + 0x90,0x8c,0x57,0xed,0x20,0x00,0x05, + 0x01 }}, + {{0xaa,0x7f,0x8e,0x8e,0x96,0xe6,0xf5, + 0x50,0x88,0xff,0xe7,0x10,0x00,0x02, + 0x01}} +}; + +static const struct SiS_LVDSCRT1Data SiS310_CHTVCRT1OPAL[] = +{ + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf0,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xf0,0x83,0x8f,0x70,0x20,0x00,0x05, + 0x00 }}, + {{0x79,0x4f,0x9d,0x5a,0x90,0x6f,0x3e, + 0xde,0x81,0x5d,0x70,0x00,0x00,0x05, + 0x00 }}, + {{0x64,0x4f,0x88,0x58,0x9d,0x6f,0xba, + 0x15,0x83,0xdf,0x70,0x00,0x00,0x01, + 0x00 }}, + {{0x71,0x63,0x95,0x69,0x8c,0x6f,0xf0, + 0x5a,0x8b,0x57,0x70,0x20,0x00,0x05, + 0x01 }}, + {{0xaa,0x7f,0x8e,0x8f,0x96,0x69,0xf5, + 0x28,0x88,0xff,0x6a,0x10,0x00,0x02, + 0x01 }} +}; + +static const struct SiS_CHTVRegData SiS310_CHTVReg_UNTSC[] = +{ + {{0x4a,0x77,0xbb,0x94,0x84,0x48,0xfe,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x4a,0x77,0xbb,0x94,0x84,0x48,0xfe,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x4a,0x77,0xbb,0x94,0x84,0x48,0xfe,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x4a,0x77,0xbb,0x94,0x84,0x48,0xfe,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x6a,0x77,0xbb,0x6e,0x84,0x2e,0x02,0x5a,0x04,0x00,0x80,0x20,0x7e,0x80,0x98,0x00}}, + {{0xcf,0x77,0xb7,0xc8,0x84,0x3b,0x02,0x5a,0x04,0x00,0x80,0x19,0x88,0x30,0x7f,0x00}}, + {{0xee,0x77,0xbb,0x66,0x87,0x32,0x01,0x5a,0x04,0x00,0x80,0x1b,0xd3,0xf2,0x36,0x00}} +}; /* WRONG: 0x02: should be 0xfx, because if CIVEnable is clear, this should be set; + 0x07: Blacklevel: NTSC/PAL-M: Should be 131 (0x83), and not 0x50/0x5a + PAL/PAL-N: 110 (0x6e) + NTSC-J: 102 (0x66) + 0x0c-0x0f: CIV is not default as in datasheet + MISSING: 0x21: Should set D1 to ZERO (for NTSC, PAL-M) or ONE (PAL, NTSC-J) + Most of this is wrong in all NTSC and PAL register arrays. But I won't correct + it as long as it works. For NTSC-J, the blacklevel is corrected in init301.c; + for PAL-M and PAL-N all above is corrected. + */ + +static const struct SiS_CHTVRegData SiS310_CHTVReg_ONTSC[] = +{ + {{0x49,0x77,0xbb,0x7b,0x84,0x34,0x00,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x49,0x77,0xbb,0x7b,0x84,0x34,0x00,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x49,0x77,0xbb,0x7b,0x84,0x34,0x00,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x49,0x77,0xbb,0x7b,0x84,0x34,0x00,0x50,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x69,0x77,0xbb,0x6e,0x84,0x1e,0x00,0x5a,0x04,0x00,0x80,0x25,0x1a,0x43,0x04,0x00}}, + {{0xce,0x77,0xb7,0xb6,0x83,0x2c,0x02,0x5a,0x04,0x00,0x80,0x1c,0x00,0x82,0x97,0x00}}, + {{0xed,0x77,0xbb,0x66,0x8c,0x21,0x02,0x5a,0x04,0x00,0x80,0x1f,0x9f,0xc1,0x0c,0x00}} +}; + +static const struct SiS_CHTVRegData SiS310_CHTVReg_UPAL[] = +{ + {{0x41,0x7f,0xb7,0x34,0xad,0x50,0x34,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x41,0x7f,0xb7,0x80,0x85,0x50,0x00,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x41,0x7f,0xb7,0x34,0xad,0x50,0x34,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x41,0x7f,0xb7,0x12,0x85,0x50,0x00,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x61,0x7f,0xb7,0x99,0x84,0x35,0x04,0x5a,0x05,0x00,0x80,0x26,0x2a,0x55,0x5d,0x00}}, + {{0xc3,0x7f,0xb7,0x7a,0x84,0x40,0x02,0x5a,0x05,0x00,0x80,0x1f,0x84,0x3d,0x28,0x00}}, + {{0xe5,0x7f,0xb7,0x1d,0xa7,0x3e,0x04,0x5a,0x05,0x00,0x80,0x20,0x3e,0xe4,0x22,0x00}} +}; + +static const struct SiS_CHTVRegData SiS310_CHTVReg_OPAL[] = +{ + {{0x41,0x7f,0xb7,0x36,0xad,0x50,0x34,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x41,0x7f,0xb7,0x86,0x85,0x50,0x00,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x41,0x7f,0xb7,0x36,0xad,0x50,0x34,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x41,0x7f,0xb7,0x86,0x85,0x50,0x00,0x83,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x61,0x7f,0xb7,0x99,0x84,0x35,0x04,0x5a,0x05,0x00,0x80,0x26,0x2a,0x55,0x5d,0x00}}, + {{0xc1,0x7f,0xb7,0x4d,0x8c,0x1e,0x31,0x5a,0x05,0x00,0x80,0x26,0x78,0x19,0x34,0x00}}, + {{0xe4,0x7f,0xb7,0x1e,0xaf,0x29,0x37,0x5a,0x05,0x00,0x80,0x25,0x8c,0xb2,0x2a,0x00}} +}; + +static const struct SiS_CHTVRegData SiS310_CHTVReg_UPALM[] = +{ + {{0x52,0x77,0xbb,0x94,0x84,0x48,0xfe,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x52,0x77,0xbb,0x94,0x84,0x48,0xfe,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x52,0x77,0xbb,0x94,0x84,0x48,0xfe,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x52,0x77,0xbb,0x94,0x84,0x48,0xfe,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x72,0x77,0xfb,0x6e,0x84,0x2e,0x02,0x83,0x04,0x00,0x80,0x20,0x76,0xdb,0x6e,0x00}}, + {{0xd7,0x77,0xf7,0xc8,0x84,0x3b,0x02,0x83,0x04,0x00,0x80,0x19,0x84,0x0a,0xc7,0x00}}, + {{0xf6,0x77,0xfb,0x66,0x87,0x32,0x01,0x83,0x04,0x00,0x80,0x1b,0xdc,0xb0,0x8d,0x00}} +#if 0 /* Correct blacklevel and CFRB */ + {{0x72,0x77,0xbb,0x6e,0x84,0x2e,0x02,0x5a,0x04,0x00,0x80,0x20,0x76,0xdb,0x6e,0x00}}, + {{0xd7,0x77,0xb7,0xc8,0x84,0x3b,0x02,0x5a,0x04,0x00,0x80,0x19,0x84,0x0a,0xc7,0x00}}, + {{0xf6,0x77,0xbb,0x66,0x87,0x32,0x01,0x5a,0x04,0x00,0x80,0x1b,0xdc,0xb0,0x8d,0x00}} +#endif +}; + +static const struct SiS_CHTVRegData SiS310_CHTVReg_OPALM[] = +{ + {{0x51,0x77,0xbb,0x7b,0x84,0x34,0x00,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x51,0x77,0xbb,0x7b,0x84,0x34,0x00,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x51,0x77,0xbb,0x7b,0x84,0x34,0x00,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x51,0x77,0xbb,0x7b,0x84,0x34,0x00,0x83,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x01}}, + {{0x71,0x77,0xfb,0x6e,0x84,0x1e,0x00,0x83,0x04,0x00,0x80,0x25,0x1a,0x1f,0x59,0x00}}, + {{0xd6,0x77,0xf7,0xb6,0x83,0x2c,0x02,0x83,0x04,0x00,0x80,0x1b,0xf8,0x1f,0x82,0x00}}, + {{0xf5,0x77,0xfb,0x66,0x8c,0x21,0x02,0x83,0x04,0x00,0x80,0x1f,0x58,0x46,0x9f,0x00}} +#if 0 /* Correct blacklevel and CFRB */ + {{0x71,0x77,0xbb,0x6e,0x84,0x1e,0x00,0x5a,0x04,0x00,0x80,0x25,0x1a,0x1f,0x59,0x00}}, + {{0xd6,0x77,0xb7,0xb6,0x83,0x2c,0x02,0x5a,0x04,0x00,0x80,0x1b,0xf8,0x1f,0x82,0x00}}, + {{0xf5,0x77,0xbb,0x66,0x8c,0x21,0x02,0x5a,0x04,0x00,0x80,0x1f,0x58,0x46,0x9f,0x00}} +#endif +}; + +static const struct SiS_CHTVRegData SiS310_CHTVReg_UPALN[] = +{ + {{0x41,0x7f,0xb7,0x34,0xad,0x50,0x34,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x41,0x7f,0xb7,0x80,0x85,0x50,0x00,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x41,0x7f,0xb7,0x34,0xad,0x50,0x34,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x41,0x7f,0xb7,0x12,0x85,0x50,0x00,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x61,0x7f,0xb7,0x99,0x84,0x35,0x04,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0xc3,0x7f,0xb7,0x7a,0x84,0x40,0x02,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0xe5,0x7f,0xb7,0x1d,0xa7,0x3e,0x04,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}} +#if 0 /* Correct blacklevel, CIV and CFRB */ + {{0x61,0x7f,0xb7,0x99,0x84,0x35,0x04,0x5a,0x05,0x00,0x80,0x1f,0x0d,0x54,0x5e,0x00}}, + {{0xc3,0x7f,0xb7,0x7a,0x84,0x40,0x02,0x5a,0x05,0x00,0x80,0x19,0x78,0xef,0x35,0x00}}, + {{0xe5,0x7f,0xb7,0x1d,0xa7,0x3e,0x04,0x5a,0x05,0x00,0x80,0x1a,0x33,0x3f,0x2f,0x00}} +#endif +}; + +static const struct SiS_CHTVRegData SiS310_CHTVReg_OPALN[] = +{ + {{0x41,0x7f,0xb7,0x36,0xad,0x50,0x34,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x41,0x7f,0xb7,0x86,0x85,0x50,0x00,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x41,0x7f,0xb7,0x36,0xad,0x50,0x34,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x41,0x7f,0xb7,0x86,0x85,0x50,0x00,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0x61,0x7f,0xb7,0x99,0x84,0x35,0x04,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0xc1,0x7f,0xb7,0x4d,0x8c,0x1e,0x31,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}}, + {{0xe4,0x7f,0xb7,0x1e,0xaf,0x29,0x37,0x6e,0x05,0x00,0x80,0x00,0x00,0x00,0x00,0x03}} +#if 0 /* Correct blacklevel, CIV and CFRB */ + {{0x61,0x7f,0xb7,0x99,0x84,0x35,0x04,0x5a,0x05,0x00,0x80,0x1f,0x0d,0x54,0x5e,0x00}}, + {{0xc1,0x7f,0xb7,0x4d,0x8c,0x1e,0x31,0x5a,0x05,0x00,0x80,0x1f,0x15,0xc0,0x1e,0x00}}, + {{0xe4,0x7f,0xb7,0x1e,0xaf,0x29,0x37,0x5a,0x05,0x00,0x80,0x1d,0xf1,0x6c,0xcb,0x00}} +#endif +}; + +static const unsigned char SiS310_CHTVVCLKUNTSC[] = { 0x41,0x41,0x41,0x41,0x42,0x46,0x53 }; +static const unsigned char SiS310_CHTVVCLKONTSC[] = { 0x48,0x48,0x48,0x48,0x45,0x43,0x51 }; + +static const unsigned char SiS310_CHTVVCLKUPAL[] = { 0x47,0x47,0x47,0x47,0x48,0x4a,0x54 }; +static const unsigned char SiS310_CHTVVCLKOPAL[] = { 0x47,0x47,0x47,0x47,0x48,0x4f,0x52 }; + +static const unsigned char SiS310_CHTVVCLKUPALM[] = { 0x41,0x41,0x41,0x41,0x42,0x46,0x53 }; +static const unsigned char SiS310_CHTVVCLKOPALM[] = { 0x48,0x48,0x48,0x48,0x45,0x43,0x51 }; + +static const unsigned char SiS310_CHTVVCLKUPALN[] = { 0x47,0x47,0x47,0x47,0x48,0x4a,0x54 }; +static const unsigned char SiS310_CHTVVCLKOPALN[] = { 0x47,0x47,0x47,0x47,0x48,0x4f,0x52 }; + + diff --git a/drivers/video/fbdev/sis/Makefile b/drivers/video/fbdev/sis/Makefile new file mode 100644 index 000000000000..f7c0046e5b1d --- /dev/null +++ b/drivers/video/fbdev/sis/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the SiS framebuffer device driver +# + +obj-$(CONFIG_FB_SIS) += sisfb.o + +sisfb-objs := sis_main.o sis_accel.o init.o init301.o initextlfb.o diff --git a/drivers/video/fbdev/sis/init.c b/drivers/video/fbdev/sis/init.c new file mode 100644 index 000000000000..bd40f5ecd901 --- /dev/null +++ b/drivers/video/fbdev/sis/init.c @@ -0,0 +1,3655 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Mode initializing code (CRT1 section) for + * for SiS 300/305/540/630/730, + * SiS 315/550/[M]650/651/[M]661[FGM]X/[M]74x[GX]/330/[M]76x[GX], + * XGI Volari V3XT/V5/V8, Z7 + * (Universal module for Linux kernel framebuffer and X.org/XFree86 4.x) + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + * Formerly based on non-functional code-fragements for 300 series by SiS, Inc. + * Used by permission. + */ + +#include "init.h" + +#ifdef CONFIG_FB_SIS_300 +#include "300vtbl.h" +#endif + +#ifdef CONFIG_FB_SIS_315 +#include "310vtbl.h" +#endif + +#if defined(ALLOC_PRAGMA) +#pragma alloc_text(PAGE,SiSSetMode) +#endif + +/*********************************************/ +/* POINTER INITIALIZATION */ +/*********************************************/ + +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) +static void +InitCommonPointer(struct SiS_Private *SiS_Pr) +{ + SiS_Pr->SiS_SModeIDTable = SiS_SModeIDTable; + SiS_Pr->SiS_StResInfo = SiS_StResInfo; + SiS_Pr->SiS_ModeResInfo = SiS_ModeResInfo; + SiS_Pr->SiS_StandTable = SiS_StandTable; + + SiS_Pr->SiS_NTSCTiming = SiS_NTSCTiming; + SiS_Pr->SiS_PALTiming = SiS_PALTiming; + SiS_Pr->SiS_HiTVSt1Timing = SiS_HiTVSt1Timing; + SiS_Pr->SiS_HiTVSt2Timing = SiS_HiTVSt2Timing; + + SiS_Pr->SiS_HiTVExtTiming = SiS_HiTVExtTiming; + SiS_Pr->SiS_HiTVGroup3Data = SiS_HiTVGroup3Data; + SiS_Pr->SiS_HiTVGroup3Simu = SiS_HiTVGroup3Simu; +#if 0 + SiS_Pr->SiS_HiTVTextTiming = SiS_HiTVTextTiming; + SiS_Pr->SiS_HiTVGroup3Text = SiS_HiTVGroup3Text; +#endif + + SiS_Pr->SiS_StPALData = SiS_StPALData; + SiS_Pr->SiS_ExtPALData = SiS_ExtPALData; + SiS_Pr->SiS_StNTSCData = SiS_StNTSCData; + SiS_Pr->SiS_ExtNTSCData = SiS_ExtNTSCData; + SiS_Pr->SiS_St1HiTVData = SiS_StHiTVData; + SiS_Pr->SiS_St2HiTVData = SiS_St2HiTVData; + SiS_Pr->SiS_ExtHiTVData = SiS_ExtHiTVData; + SiS_Pr->SiS_St525iData = SiS_StNTSCData; + SiS_Pr->SiS_St525pData = SiS_St525pData; + SiS_Pr->SiS_St750pData = SiS_St750pData; + SiS_Pr->SiS_Ext525iData = SiS_ExtNTSCData; + SiS_Pr->SiS_Ext525pData = SiS_ExtNTSCData; + SiS_Pr->SiS_Ext750pData = SiS_Ext750pData; + + SiS_Pr->pSiS_OutputSelect = &SiS_OutputSelect; + SiS_Pr->pSiS_SoftSetting = &SiS_SoftSetting; + + SiS_Pr->SiS_LCD1280x720Data = SiS_LCD1280x720Data; + SiS_Pr->SiS_StLCD1280x768_2Data = SiS_StLCD1280x768_2Data; + SiS_Pr->SiS_ExtLCD1280x768_2Data = SiS_ExtLCD1280x768_2Data; + SiS_Pr->SiS_LCD1280x800Data = SiS_LCD1280x800Data; + SiS_Pr->SiS_LCD1280x800_2Data = SiS_LCD1280x800_2Data; + SiS_Pr->SiS_LCD1280x854Data = SiS_LCD1280x854Data; + SiS_Pr->SiS_LCD1280x960Data = SiS_LCD1280x960Data; + SiS_Pr->SiS_StLCD1400x1050Data = SiS_StLCD1400x1050Data; + SiS_Pr->SiS_ExtLCD1400x1050Data = SiS_ExtLCD1400x1050Data; + SiS_Pr->SiS_LCD1680x1050Data = SiS_LCD1680x1050Data; + SiS_Pr->SiS_StLCD1600x1200Data = SiS_StLCD1600x1200Data; + SiS_Pr->SiS_ExtLCD1600x1200Data = SiS_ExtLCD1600x1200Data; + SiS_Pr->SiS_NoScaleData = SiS_NoScaleData; + + SiS_Pr->SiS_LVDS320x240Data_1 = SiS_LVDS320x240Data_1; + SiS_Pr->SiS_LVDS320x240Data_2 = SiS_LVDS320x240Data_2; + SiS_Pr->SiS_LVDS640x480Data_1 = SiS_LVDS640x480Data_1; + SiS_Pr->SiS_LVDS800x600Data_1 = SiS_LVDS800x600Data_1; + SiS_Pr->SiS_LVDS1024x600Data_1 = SiS_LVDS1024x600Data_1; + SiS_Pr->SiS_LVDS1024x768Data_1 = SiS_LVDS1024x768Data_1; + + SiS_Pr->SiS_LVDSCRT1320x240_1 = SiS_LVDSCRT1320x240_1; + SiS_Pr->SiS_LVDSCRT1320x240_2 = SiS_LVDSCRT1320x240_2; + SiS_Pr->SiS_LVDSCRT1320x240_2_H = SiS_LVDSCRT1320x240_2_H; + SiS_Pr->SiS_LVDSCRT1320x240_3 = SiS_LVDSCRT1320x240_3; + SiS_Pr->SiS_LVDSCRT1320x240_3_H = SiS_LVDSCRT1320x240_3_H; + SiS_Pr->SiS_LVDSCRT1640x480_1 = SiS_LVDSCRT1640x480_1; + SiS_Pr->SiS_LVDSCRT1640x480_1_H = SiS_LVDSCRT1640x480_1_H; +#if 0 + SiS_Pr->SiS_LVDSCRT11024x600_1 = SiS_LVDSCRT11024x600_1; + SiS_Pr->SiS_LVDSCRT11024x600_1_H = SiS_LVDSCRT11024x600_1_H; + SiS_Pr->SiS_LVDSCRT11024x600_2 = SiS_LVDSCRT11024x600_2; + SiS_Pr->SiS_LVDSCRT11024x600_2_H = SiS_LVDSCRT11024x600_2_H; +#endif + + SiS_Pr->SiS_CHTVUNTSCData = SiS_CHTVUNTSCData; + SiS_Pr->SiS_CHTVONTSCData = SiS_CHTVONTSCData; + + SiS_Pr->SiS_PanelMinLVDS = Panel_800x600; /* lowest value LVDS/LCDA */ + SiS_Pr->SiS_PanelMin301 = Panel_1024x768; /* lowest value 301 */ +} +#endif + +#ifdef CONFIG_FB_SIS_300 +static void +InitTo300Pointer(struct SiS_Private *SiS_Pr) +{ + InitCommonPointer(SiS_Pr); + + SiS_Pr->SiS_VBModeIDTable = SiS300_VBModeIDTable; + SiS_Pr->SiS_EModeIDTable = SiS300_EModeIDTable; + SiS_Pr->SiS_RefIndex = SiS300_RefIndex; + SiS_Pr->SiS_CRT1Table = SiS300_CRT1Table; + if(SiS_Pr->ChipType == SIS_300) { + SiS_Pr->SiS_MCLKData_0 = SiS300_MCLKData_300; /* 300 */ + } else { + SiS_Pr->SiS_MCLKData_0 = SiS300_MCLKData_630; /* 630, 730 */ + } + SiS_Pr->SiS_VCLKData = SiS300_VCLKData; + SiS_Pr->SiS_VBVCLKData = (struct SiS_VBVCLKData *)SiS300_VCLKData; + + SiS_Pr->SiS_SR15 = SiS300_SR15; + + SiS_Pr->SiS_PanelDelayTbl = SiS300_PanelDelayTbl; + SiS_Pr->SiS_PanelDelayTblLVDS = SiS300_PanelDelayTbl; + + SiS_Pr->SiS_ExtLCD1024x768Data = SiS300_ExtLCD1024x768Data; + SiS_Pr->SiS_St2LCD1024x768Data = SiS300_St2LCD1024x768Data; + SiS_Pr->SiS_ExtLCD1280x1024Data = SiS300_ExtLCD1280x1024Data; + SiS_Pr->SiS_St2LCD1280x1024Data = SiS300_St2LCD1280x1024Data; + + SiS_Pr->SiS_CRT2Part2_1024x768_1 = SiS300_CRT2Part2_1024x768_1; + SiS_Pr->SiS_CRT2Part2_1024x768_2 = SiS300_CRT2Part2_1024x768_2; + SiS_Pr->SiS_CRT2Part2_1024x768_3 = SiS300_CRT2Part2_1024x768_3; + + SiS_Pr->SiS_CHTVUPALData = SiS300_CHTVUPALData; + SiS_Pr->SiS_CHTVOPALData = SiS300_CHTVOPALData; + SiS_Pr->SiS_CHTVUPALMData = SiS_CHTVUNTSCData; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVOPALMData = SiS_CHTVONTSCData; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVUPALNData = SiS300_CHTVUPALData; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVOPALNData = SiS300_CHTVOPALData; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVSOPALData = SiS300_CHTVSOPALData; + + SiS_Pr->SiS_LVDS848x480Data_1 = SiS300_LVDS848x480Data_1; + SiS_Pr->SiS_LVDS848x480Data_2 = SiS300_LVDS848x480Data_2; + SiS_Pr->SiS_LVDSBARCO1024Data_1 = SiS300_LVDSBARCO1024Data_1; + SiS_Pr->SiS_LVDSBARCO1366Data_1 = SiS300_LVDSBARCO1366Data_1; + SiS_Pr->SiS_LVDSBARCO1366Data_2 = SiS300_LVDSBARCO1366Data_2; + + SiS_Pr->SiS_PanelType04_1a = SiS300_PanelType04_1a; + SiS_Pr->SiS_PanelType04_2a = SiS300_PanelType04_2a; + SiS_Pr->SiS_PanelType04_1b = SiS300_PanelType04_1b; + SiS_Pr->SiS_PanelType04_2b = SiS300_PanelType04_2b; + + SiS_Pr->SiS_CHTVCRT1UNTSC = SiS300_CHTVCRT1UNTSC; + SiS_Pr->SiS_CHTVCRT1ONTSC = SiS300_CHTVCRT1ONTSC; + SiS_Pr->SiS_CHTVCRT1UPAL = SiS300_CHTVCRT1UPAL; + SiS_Pr->SiS_CHTVCRT1OPAL = SiS300_CHTVCRT1OPAL; + SiS_Pr->SiS_CHTVCRT1SOPAL = SiS300_CHTVCRT1SOPAL; + SiS_Pr->SiS_CHTVReg_UNTSC = SiS300_CHTVReg_UNTSC; + SiS_Pr->SiS_CHTVReg_ONTSC = SiS300_CHTVReg_ONTSC; + SiS_Pr->SiS_CHTVReg_UPAL = SiS300_CHTVReg_UPAL; + SiS_Pr->SiS_CHTVReg_OPAL = SiS300_CHTVReg_OPAL; + SiS_Pr->SiS_CHTVReg_UPALM = SiS300_CHTVReg_UNTSC; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVReg_OPALM = SiS300_CHTVReg_ONTSC; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVReg_UPALN = SiS300_CHTVReg_UPAL; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVReg_OPALN = SiS300_CHTVReg_OPAL; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVReg_SOPAL = SiS300_CHTVReg_SOPAL; + SiS_Pr->SiS_CHTVVCLKUNTSC = SiS300_CHTVVCLKUNTSC; + SiS_Pr->SiS_CHTVVCLKONTSC = SiS300_CHTVVCLKONTSC; + SiS_Pr->SiS_CHTVVCLKUPAL = SiS300_CHTVVCLKUPAL; + SiS_Pr->SiS_CHTVVCLKOPAL = SiS300_CHTVVCLKOPAL; + SiS_Pr->SiS_CHTVVCLKUPALM = SiS300_CHTVVCLKUNTSC; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVVCLKOPALM = SiS300_CHTVVCLKONTSC; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVVCLKUPALN = SiS300_CHTVVCLKUPAL; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVVCLKOPALN = SiS300_CHTVVCLKOPAL; /* not supported on 300 series */ + SiS_Pr->SiS_CHTVVCLKSOPAL = SiS300_CHTVVCLKSOPAL; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static void +InitTo310Pointer(struct SiS_Private *SiS_Pr) +{ + InitCommonPointer(SiS_Pr); + + SiS_Pr->SiS_EModeIDTable = SiS310_EModeIDTable; + SiS_Pr->SiS_RefIndex = SiS310_RefIndex; + SiS_Pr->SiS_CRT1Table = SiS310_CRT1Table; + if(SiS_Pr->ChipType >= SIS_340) { + SiS_Pr->SiS_MCLKData_0 = SiS310_MCLKData_0_340; /* 340 + XGI */ + } else if(SiS_Pr->ChipType >= SIS_761) { + SiS_Pr->SiS_MCLKData_0 = SiS310_MCLKData_0_761; /* 761 - preliminary */ + } else if(SiS_Pr->ChipType >= SIS_760) { + SiS_Pr->SiS_MCLKData_0 = SiS310_MCLKData_0_760; /* 760 */ + } else if(SiS_Pr->ChipType >= SIS_661) { + SiS_Pr->SiS_MCLKData_0 = SiS310_MCLKData_0_660; /* 661/741 */ + } else if(SiS_Pr->ChipType == SIS_330) { + SiS_Pr->SiS_MCLKData_0 = SiS310_MCLKData_0_330; /* 330 */ + } else if(SiS_Pr->ChipType > SIS_315PRO) { + SiS_Pr->SiS_MCLKData_0 = SiS310_MCLKData_0_650; /* 550, 650, 740 */ + } else { + SiS_Pr->SiS_MCLKData_0 = SiS310_MCLKData_0_315; /* 315 */ + } + if(SiS_Pr->ChipType >= SIS_340) { + SiS_Pr->SiS_MCLKData_1 = SiS310_MCLKData_1_340; + } else { + SiS_Pr->SiS_MCLKData_1 = SiS310_MCLKData_1; + } + SiS_Pr->SiS_VCLKData = SiS310_VCLKData; + SiS_Pr->SiS_VBVCLKData = SiS310_VBVCLKData; + + SiS_Pr->SiS_SR15 = SiS310_SR15; + + SiS_Pr->SiS_PanelDelayTbl = SiS310_PanelDelayTbl; + SiS_Pr->SiS_PanelDelayTblLVDS = SiS310_PanelDelayTblLVDS; + + SiS_Pr->SiS_St2LCD1024x768Data = SiS310_St2LCD1024x768Data; + SiS_Pr->SiS_ExtLCD1024x768Data = SiS310_ExtLCD1024x768Data; + SiS_Pr->SiS_St2LCD1280x1024Data = SiS310_St2LCD1280x1024Data; + SiS_Pr->SiS_ExtLCD1280x1024Data = SiS310_ExtLCD1280x1024Data; + + SiS_Pr->SiS_CRT2Part2_1024x768_1 = SiS310_CRT2Part2_1024x768_1; + + SiS_Pr->SiS_CHTVUPALData = SiS310_CHTVUPALData; + SiS_Pr->SiS_CHTVOPALData = SiS310_CHTVOPALData; + SiS_Pr->SiS_CHTVUPALMData = SiS310_CHTVUPALMData; + SiS_Pr->SiS_CHTVOPALMData = SiS310_CHTVOPALMData; + SiS_Pr->SiS_CHTVUPALNData = SiS310_CHTVUPALNData; + SiS_Pr->SiS_CHTVOPALNData = SiS310_CHTVOPALNData; + SiS_Pr->SiS_CHTVSOPALData = SiS310_CHTVSOPALData; + + SiS_Pr->SiS_CHTVCRT1UNTSC = SiS310_CHTVCRT1UNTSC; + SiS_Pr->SiS_CHTVCRT1ONTSC = SiS310_CHTVCRT1ONTSC; + SiS_Pr->SiS_CHTVCRT1UPAL = SiS310_CHTVCRT1UPAL; + SiS_Pr->SiS_CHTVCRT1OPAL = SiS310_CHTVCRT1OPAL; + SiS_Pr->SiS_CHTVCRT1SOPAL = SiS310_CHTVCRT1OPAL; + + SiS_Pr->SiS_CHTVReg_UNTSC = SiS310_CHTVReg_UNTSC; + SiS_Pr->SiS_CHTVReg_ONTSC = SiS310_CHTVReg_ONTSC; + SiS_Pr->SiS_CHTVReg_UPAL = SiS310_CHTVReg_UPAL; + SiS_Pr->SiS_CHTVReg_OPAL = SiS310_CHTVReg_OPAL; + SiS_Pr->SiS_CHTVReg_UPALM = SiS310_CHTVReg_UPALM; + SiS_Pr->SiS_CHTVReg_OPALM = SiS310_CHTVReg_OPALM; + SiS_Pr->SiS_CHTVReg_UPALN = SiS310_CHTVReg_UPALN; + SiS_Pr->SiS_CHTVReg_OPALN = SiS310_CHTVReg_OPALN; + SiS_Pr->SiS_CHTVReg_SOPAL = SiS310_CHTVReg_OPAL; + + SiS_Pr->SiS_CHTVVCLKUNTSC = SiS310_CHTVVCLKUNTSC; + SiS_Pr->SiS_CHTVVCLKONTSC = SiS310_CHTVVCLKONTSC; + SiS_Pr->SiS_CHTVVCLKUPAL = SiS310_CHTVVCLKUPAL; + SiS_Pr->SiS_CHTVVCLKOPAL = SiS310_CHTVVCLKOPAL; + SiS_Pr->SiS_CHTVVCLKUPALM = SiS310_CHTVVCLKUPALM; + SiS_Pr->SiS_CHTVVCLKOPALM = SiS310_CHTVVCLKOPALM; + SiS_Pr->SiS_CHTVVCLKUPALN = SiS310_CHTVVCLKUPALN; + SiS_Pr->SiS_CHTVVCLKOPALN = SiS310_CHTVVCLKOPALN; + SiS_Pr->SiS_CHTVVCLKSOPAL = SiS310_CHTVVCLKOPAL; +} +#endif + +bool +SiSInitPtr(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + InitTo300Pointer(SiS_Pr); +#else + return false; +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + InitTo310Pointer(SiS_Pr); +#else + return false; +#endif + } + return true; +} + +/*********************************************/ +/* HELPER: Get ModeID */ +/*********************************************/ + +static +unsigned short +SiS_GetModeID(int VGAEngine, unsigned int VBFlags, int HDisplay, int VDisplay, + int Depth, bool FSTN, int LCDwidth, int LCDheight) +{ + unsigned short ModeIndex = 0; + + switch(HDisplay) + { + case 320: + if(VDisplay == 200) ModeIndex = ModeIndex_320x200[Depth]; + else if(VDisplay == 240) { + if((VBFlags & CRT2_LCD) && (FSTN)) + ModeIndex = ModeIndex_320x240_FSTN[Depth]; + else + ModeIndex = ModeIndex_320x240[Depth]; + } + break; + case 400: + if((!(VBFlags & CRT1_LCDA)) || ((LCDwidth >= 800) && (LCDwidth >= 600))) { + if(VDisplay == 300) ModeIndex = ModeIndex_400x300[Depth]; + } + break; + case 512: + if((!(VBFlags & CRT1_LCDA)) || ((LCDwidth >= 1024) && (LCDwidth >= 768))) { + if(VDisplay == 384) ModeIndex = ModeIndex_512x384[Depth]; + } + break; + case 640: + if(VDisplay == 480) ModeIndex = ModeIndex_640x480[Depth]; + else if(VDisplay == 400) ModeIndex = ModeIndex_640x400[Depth]; + break; + case 720: + if(VDisplay == 480) ModeIndex = ModeIndex_720x480[Depth]; + else if(VDisplay == 576) ModeIndex = ModeIndex_720x576[Depth]; + break; + case 768: + if(VDisplay == 576) ModeIndex = ModeIndex_768x576[Depth]; + break; + case 800: + if(VDisplay == 600) ModeIndex = ModeIndex_800x600[Depth]; + else if(VDisplay == 480) ModeIndex = ModeIndex_800x480[Depth]; + break; + case 848: + if(VDisplay == 480) ModeIndex = ModeIndex_848x480[Depth]; + break; + case 856: + if(VDisplay == 480) ModeIndex = ModeIndex_856x480[Depth]; + break; + case 960: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 540) ModeIndex = ModeIndex_960x540[Depth]; + else if(VDisplay == 600) ModeIndex = ModeIndex_960x600[Depth]; + } + break; + case 1024: + if(VDisplay == 576) ModeIndex = ModeIndex_1024x576[Depth]; + else if(VDisplay == 768) ModeIndex = ModeIndex_1024x768[Depth]; + else if(VGAEngine == SIS_300_VGA) { + if(VDisplay == 600) ModeIndex = ModeIndex_1024x600[Depth]; + } + break; + case 1152: + if(VDisplay == 864) ModeIndex = ModeIndex_1152x864[Depth]; + if(VGAEngine == SIS_300_VGA) { + if(VDisplay == 768) ModeIndex = ModeIndex_1152x768[Depth]; + } + break; + case 1280: + switch(VDisplay) { + case 720: + ModeIndex = ModeIndex_1280x720[Depth]; + break; + case 768: + if(VGAEngine == SIS_300_VGA) { + ModeIndex = ModeIndex_300_1280x768[Depth]; + } else { + ModeIndex = ModeIndex_310_1280x768[Depth]; + } + break; + case 800: + if(VGAEngine == SIS_315_VGA) { + ModeIndex = ModeIndex_1280x800[Depth]; + } + break; + case 854: + if(VGAEngine == SIS_315_VGA) { + ModeIndex = ModeIndex_1280x854[Depth]; + } + break; + case 960: + ModeIndex = ModeIndex_1280x960[Depth]; + break; + case 1024: + ModeIndex = ModeIndex_1280x1024[Depth]; + break; + } + break; + case 1360: + if(VDisplay == 768) ModeIndex = ModeIndex_1360x768[Depth]; + if(VGAEngine == SIS_300_VGA) { + if(VDisplay == 1024) ModeIndex = ModeIndex_300_1360x1024[Depth]; + } + break; + case 1400: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 1050) { + ModeIndex = ModeIndex_1400x1050[Depth]; + } + } + break; + case 1600: + if(VDisplay == 1200) ModeIndex = ModeIndex_1600x1200[Depth]; + break; + case 1680: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 1050) ModeIndex = ModeIndex_1680x1050[Depth]; + } + break; + case 1920: + if(VDisplay == 1440) ModeIndex = ModeIndex_1920x1440[Depth]; + else if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 1080) ModeIndex = ModeIndex_1920x1080[Depth]; + } + break; + case 2048: + if(VDisplay == 1536) { + if(VGAEngine == SIS_300_VGA) { + ModeIndex = ModeIndex_300_2048x1536[Depth]; + } else { + ModeIndex = ModeIndex_310_2048x1536[Depth]; + } + } + break; + } + + return ModeIndex; +} + +unsigned short +SiS_GetModeID_LCD(int VGAEngine, unsigned int VBFlags, int HDisplay, int VDisplay, + int Depth, bool FSTN, unsigned short CustomT, int LCDwidth, int LCDheight, + unsigned int VBFlags2) +{ + unsigned short ModeIndex = 0; + + if(VBFlags2 & (VB2_LVDS | VB2_30xBDH)) { + + switch(HDisplay) + { + case 320: + if((CustomT != CUT_PANEL848) && (CustomT != CUT_PANEL856)) { + if(VDisplay == 200) { + if(!FSTN) ModeIndex = ModeIndex_320x200[Depth]; + } else if(VDisplay == 240) { + if(!FSTN) ModeIndex = ModeIndex_320x240[Depth]; + else if(VGAEngine == SIS_315_VGA) { + ModeIndex = ModeIndex_320x240_FSTN[Depth]; + } + } + } + break; + case 400: + if((CustomT != CUT_PANEL848) && (CustomT != CUT_PANEL856)) { + if(!((VGAEngine == SIS_300_VGA) && (VBFlags2 & VB2_TRUMPION))) { + if(VDisplay == 300) ModeIndex = ModeIndex_400x300[Depth]; + } + } + break; + case 512: + if((CustomT != CUT_PANEL848) && (CustomT != CUT_PANEL856)) { + if(!((VGAEngine == SIS_300_VGA) && (VBFlags2 & VB2_TRUMPION))) { + if(LCDwidth >= 1024 && LCDwidth != 1152 && LCDheight >= 768) { + if(VDisplay == 384) { + ModeIndex = ModeIndex_512x384[Depth]; + } + } + } + } + break; + case 640: + if(VDisplay == 480) ModeIndex = ModeIndex_640x480[Depth]; + else if(VDisplay == 400) { + if((CustomT != CUT_PANEL848) && (CustomT != CUT_PANEL856)) + ModeIndex = ModeIndex_640x400[Depth]; + } + break; + case 800: + if(VDisplay == 600) ModeIndex = ModeIndex_800x600[Depth]; + break; + case 848: + if(CustomT == CUT_PANEL848) { + if(VDisplay == 480) ModeIndex = ModeIndex_848x480[Depth]; + } + break; + case 856: + if(CustomT == CUT_PANEL856) { + if(VDisplay == 480) ModeIndex = ModeIndex_856x480[Depth]; + } + break; + case 1024: + if(VDisplay == 768) ModeIndex = ModeIndex_1024x768[Depth]; + else if(VGAEngine == SIS_300_VGA) { + if((VDisplay == 600) && (LCDheight == 600)) { + ModeIndex = ModeIndex_1024x600[Depth]; + } + } + break; + case 1152: + if(VGAEngine == SIS_300_VGA) { + if((VDisplay == 768) && (LCDheight == 768)) { + ModeIndex = ModeIndex_1152x768[Depth]; + } + } + break; + case 1280: + if(VDisplay == 1024) ModeIndex = ModeIndex_1280x1024[Depth]; + else if(VGAEngine == SIS_315_VGA) { + if((VDisplay == 768) && (LCDheight == 768)) { + ModeIndex = ModeIndex_310_1280x768[Depth]; + } + } + break; + case 1360: + if(VGAEngine == SIS_300_VGA) { + if(CustomT == CUT_BARCO1366) { + if(VDisplay == 1024) ModeIndex = ModeIndex_300_1360x1024[Depth]; + } + } + if(CustomT == CUT_PANEL848) { + if(VDisplay == 768) ModeIndex = ModeIndex_1360x768[Depth]; + } + break; + case 1400: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 1050) ModeIndex = ModeIndex_1400x1050[Depth]; + } + break; + case 1600: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 1200) ModeIndex = ModeIndex_1600x1200[Depth]; + } + break; + } + + } else if(VBFlags2 & VB2_SISBRIDGE) { + + switch(HDisplay) + { + case 320: + if(VDisplay == 200) ModeIndex = ModeIndex_320x200[Depth]; + else if(VDisplay == 240) ModeIndex = ModeIndex_320x240[Depth]; + break; + case 400: + if(LCDwidth >= 800 && LCDheight >= 600) { + if(VDisplay == 300) ModeIndex = ModeIndex_400x300[Depth]; + } + break; + case 512: + if(LCDwidth >= 1024 && LCDheight >= 768 && LCDwidth != 1152) { + if(VDisplay == 384) ModeIndex = ModeIndex_512x384[Depth]; + } + break; + case 640: + if(VDisplay == 480) ModeIndex = ModeIndex_640x480[Depth]; + else if(VDisplay == 400) ModeIndex = ModeIndex_640x400[Depth]; + break; + case 720: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 480) ModeIndex = ModeIndex_720x480[Depth]; + else if(VDisplay == 576) ModeIndex = ModeIndex_720x576[Depth]; + } + break; + case 768: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 576) ModeIndex = ModeIndex_768x576[Depth]; + } + break; + case 800: + if(VDisplay == 600) ModeIndex = ModeIndex_800x600[Depth]; + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 480) ModeIndex = ModeIndex_800x480[Depth]; + } + break; + case 848: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 480) ModeIndex = ModeIndex_848x480[Depth]; + } + break; + case 856: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 480) ModeIndex = ModeIndex_856x480[Depth]; + } + break; + case 960: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 540) ModeIndex = ModeIndex_960x540[Depth]; + else if(VDisplay == 600) ModeIndex = ModeIndex_960x600[Depth]; + } + break; + case 1024: + if(VDisplay == 768) ModeIndex = ModeIndex_1024x768[Depth]; + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 576) ModeIndex = ModeIndex_1024x576[Depth]; + } + break; + case 1152: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 864) ModeIndex = ModeIndex_1152x864[Depth]; + } + break; + case 1280: + switch(VDisplay) { + case 720: + ModeIndex = ModeIndex_1280x720[Depth]; + break; + case 768: + if(VGAEngine == SIS_300_VGA) { + ModeIndex = ModeIndex_300_1280x768[Depth]; + } else { + ModeIndex = ModeIndex_310_1280x768[Depth]; + } + break; + case 800: + if(VGAEngine == SIS_315_VGA) { + ModeIndex = ModeIndex_1280x800[Depth]; + } + break; + case 854: + if(VGAEngine == SIS_315_VGA) { + ModeIndex = ModeIndex_1280x854[Depth]; + } + break; + case 960: + ModeIndex = ModeIndex_1280x960[Depth]; + break; + case 1024: + ModeIndex = ModeIndex_1280x1024[Depth]; + break; + } + break; + case 1360: + if(VGAEngine == SIS_315_VGA) { /* OVER1280 only? */ + if(VDisplay == 768) ModeIndex = ModeIndex_1360x768[Depth]; + } + break; + case 1400: + if(VGAEngine == SIS_315_VGA) { + if(VBFlags2 & VB2_LCDOVER1280BRIDGE) { + if(VDisplay == 1050) ModeIndex = ModeIndex_1400x1050[Depth]; + } + } + break; + case 1600: + if(VGAEngine == SIS_315_VGA) { + if(VBFlags2 & VB2_LCDOVER1280BRIDGE) { + if(VDisplay == 1200) ModeIndex = ModeIndex_1600x1200[Depth]; + } + } + break; +#ifndef VB_FORBID_CRT2LCD_OVER_1600 + case 1680: + if(VGAEngine == SIS_315_VGA) { + if(VBFlags2 & VB2_LCDOVER1280BRIDGE) { + if(VDisplay == 1050) ModeIndex = ModeIndex_1680x1050[Depth]; + } + } + break; + case 1920: + if(VGAEngine == SIS_315_VGA) { + if(VBFlags2 & VB2_LCDOVER1600BRIDGE) { + if(VDisplay == 1440) ModeIndex = ModeIndex_1920x1440[Depth]; + } + } + break; + case 2048: + if(VGAEngine == SIS_315_VGA) { + if(VBFlags2 & VB2_LCDOVER1600BRIDGE) { + if(VDisplay == 1536) ModeIndex = ModeIndex_310_2048x1536[Depth]; + } + } + break; +#endif + } + } + + return ModeIndex; +} + +unsigned short +SiS_GetModeID_TV(int VGAEngine, unsigned int VBFlags, int HDisplay, int VDisplay, int Depth, + unsigned int VBFlags2) +{ + unsigned short ModeIndex = 0; + + if(VBFlags2 & VB2_CHRONTEL) { + + switch(HDisplay) + { + case 512: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 384) ModeIndex = ModeIndex_512x384[Depth]; + } + break; + case 640: + if(VDisplay == 480) ModeIndex = ModeIndex_640x480[Depth]; + else if(VDisplay == 400) ModeIndex = ModeIndex_640x400[Depth]; + break; + case 800: + if(VDisplay == 600) ModeIndex = ModeIndex_800x600[Depth]; + break; + case 1024: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 768) ModeIndex = ModeIndex_1024x768[Depth]; + } + break; + } + + } else if(VBFlags2 & VB2_SISTVBRIDGE) { + + switch(HDisplay) + { + case 320: + if(VDisplay == 200) ModeIndex = ModeIndex_320x200[Depth]; + else if(VDisplay == 240) ModeIndex = ModeIndex_320x240[Depth]; + break; + case 400: + if(VDisplay == 300) ModeIndex = ModeIndex_400x300[Depth]; + break; + case 512: + if( ((VBFlags & TV_YPBPR) && (VBFlags & (TV_YPBPR750P | TV_YPBPR1080I))) || + (VBFlags & TV_HIVISION) || + ((!(VBFlags & (TV_YPBPR | TV_PALM))) && (VBFlags & TV_PAL)) ) { + if(VDisplay == 384) ModeIndex = ModeIndex_512x384[Depth]; + } + break; + case 640: + if(VDisplay == 480) ModeIndex = ModeIndex_640x480[Depth]; + else if(VDisplay == 400) ModeIndex = ModeIndex_640x400[Depth]; + break; + case 720: + if((!(VBFlags & TV_HIVISION)) && (!((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR1080I)))) { + if(VDisplay == 480) { + ModeIndex = ModeIndex_720x480[Depth]; + } else if(VDisplay == 576) { + if( ((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR750P)) || + ((!(VBFlags & (TV_YPBPR | TV_PALM))) && (VBFlags & TV_PAL)) ) + ModeIndex = ModeIndex_720x576[Depth]; + } + } + break; + case 768: + if((!(VBFlags & TV_HIVISION)) && (!((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR1080I)))) { + if( ((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR750P)) || + ((!(VBFlags & (TV_YPBPR | TV_PALM))) && (VBFlags & TV_PAL)) ) { + if(VDisplay == 576) ModeIndex = ModeIndex_768x576[Depth]; + } + } + break; + case 800: + if(VDisplay == 600) ModeIndex = ModeIndex_800x600[Depth]; + else if(VDisplay == 480) { + if(!((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR750P))) { + ModeIndex = ModeIndex_800x480[Depth]; + } + } + break; + case 960: + if(VGAEngine == SIS_315_VGA) { + if(VDisplay == 600) { + if((VBFlags & TV_HIVISION) || ((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR1080I))) { + ModeIndex = ModeIndex_960x600[Depth]; + } + } + } + break; + case 1024: + if(VDisplay == 768) { + if(VBFlags2 & VB2_30xBLV) { + ModeIndex = ModeIndex_1024x768[Depth]; + } + } else if(VDisplay == 576) { + if( (VBFlags & TV_HIVISION) || + ((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR1080I)) || + ((VBFlags2 & VB2_30xBLV) && + ((!(VBFlags & (TV_YPBPR | TV_PALM))) && (VBFlags & TV_PAL))) ) { + ModeIndex = ModeIndex_1024x576[Depth]; + } + } + break; + case 1280: + if(VDisplay == 720) { + if((VBFlags & TV_HIVISION) || + ((VBFlags & TV_YPBPR) && (VBFlags & (TV_YPBPR1080I | TV_YPBPR750P)))) { + ModeIndex = ModeIndex_1280x720[Depth]; + } + } else if(VDisplay == 1024) { + if((VBFlags & TV_HIVISION) || + ((VBFlags & TV_YPBPR) && (VBFlags & TV_YPBPR1080I))) { + ModeIndex = ModeIndex_1280x1024[Depth]; + } + } + break; + } + } + return ModeIndex; +} + +unsigned short +SiS_GetModeID_VGA2(int VGAEngine, unsigned int VBFlags, int HDisplay, int VDisplay, int Depth, + unsigned int VBFlags2) +{ + if(!(VBFlags2 & VB2_SISVGA2BRIDGE)) return 0; + + if(HDisplay >= 1920) return 0; + + switch(HDisplay) + { + case 1600: + if(VDisplay == 1200) { + if(VGAEngine != SIS_315_VGA) return 0; + if(!(VBFlags2 & VB2_30xB)) return 0; + } + break; + case 1680: + if(VDisplay == 1050) { + if(VGAEngine != SIS_315_VGA) return 0; + if(!(VBFlags2 & VB2_30xB)) return 0; + } + break; + } + + return SiS_GetModeID(VGAEngine, 0, HDisplay, VDisplay, Depth, false, 0, 0); +} + + +/*********************************************/ +/* HELPER: SetReg, GetReg */ +/*********************************************/ + +void +SiS_SetReg(SISIOADDRESS port, u8 index, u8 data) +{ + outb(index, port); + outb(data, port + 1); +} + +void +SiS_SetRegByte(SISIOADDRESS port, u8 data) +{ + outb(data, port); +} + +void +SiS_SetRegShort(SISIOADDRESS port, u16 data) +{ + outw(data, port); +} + +void +SiS_SetRegLong(SISIOADDRESS port, u32 data) +{ + outl(data, port); +} + +u8 +SiS_GetReg(SISIOADDRESS port, u8 index) +{ + outb(index, port); + return inb(port + 1); +} + +u8 +SiS_GetRegByte(SISIOADDRESS port) +{ + return inb(port); +} + +u16 +SiS_GetRegShort(SISIOADDRESS port) +{ + return inw(port); +} + +u32 +SiS_GetRegLong(SISIOADDRESS port) +{ + return inl(port); +} + +void +SiS_SetRegANDOR(SISIOADDRESS Port, u8 Index, u8 DataAND, u8 DataOR) +{ + u8 temp; + + temp = SiS_GetReg(Port, Index); + temp = (temp & (DataAND)) | DataOR; + SiS_SetReg(Port, Index, temp); +} + +void +SiS_SetRegAND(SISIOADDRESS Port, u8 Index, u8 DataAND) +{ + u8 temp; + + temp = SiS_GetReg(Port, Index); + temp &= DataAND; + SiS_SetReg(Port, Index, temp); +} + +void +SiS_SetRegOR(SISIOADDRESS Port, u8 Index, u8 DataOR) +{ + u8 temp; + + temp = SiS_GetReg(Port, Index); + temp |= DataOR; + SiS_SetReg(Port, Index, temp); +} + +/*********************************************/ +/* HELPER: DisplayOn, DisplayOff */ +/*********************************************/ + +void +SiS_DisplayOn(struct SiS_Private *SiS_Pr) +{ + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x01,0xDF); +} + +void +SiS_DisplayOff(struct SiS_Private *SiS_Pr) +{ + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x01,0x20); +} + + +/*********************************************/ +/* HELPER: Init Port Addresses */ +/*********************************************/ + +void +SiSRegInit(struct SiS_Private *SiS_Pr, SISIOADDRESS BaseAddr) +{ + SiS_Pr->SiS_P3c4 = BaseAddr + 0x14; + SiS_Pr->SiS_P3d4 = BaseAddr + 0x24; + SiS_Pr->SiS_P3c0 = BaseAddr + 0x10; + SiS_Pr->SiS_P3ce = BaseAddr + 0x1e; + SiS_Pr->SiS_P3c2 = BaseAddr + 0x12; + SiS_Pr->SiS_P3ca = BaseAddr + 0x1a; + SiS_Pr->SiS_P3c6 = BaseAddr + 0x16; + SiS_Pr->SiS_P3c7 = BaseAddr + 0x17; + SiS_Pr->SiS_P3c8 = BaseAddr + 0x18; + SiS_Pr->SiS_P3c9 = BaseAddr + 0x19; + SiS_Pr->SiS_P3cb = BaseAddr + 0x1b; + SiS_Pr->SiS_P3cc = BaseAddr + 0x1c; + SiS_Pr->SiS_P3cd = BaseAddr + 0x1d; + SiS_Pr->SiS_P3da = BaseAddr + 0x2a; + SiS_Pr->SiS_Part1Port = BaseAddr + SIS_CRT2_PORT_04; + SiS_Pr->SiS_Part2Port = BaseAddr + SIS_CRT2_PORT_10; + SiS_Pr->SiS_Part3Port = BaseAddr + SIS_CRT2_PORT_12; + SiS_Pr->SiS_Part4Port = BaseAddr + SIS_CRT2_PORT_14; + SiS_Pr->SiS_Part5Port = BaseAddr + SIS_CRT2_PORT_14 + 2; + SiS_Pr->SiS_DDC_Port = BaseAddr + 0x14; + SiS_Pr->SiS_VidCapt = BaseAddr + SIS_VIDEO_CAPTURE; + SiS_Pr->SiS_VidPlay = BaseAddr + SIS_VIDEO_PLAYBACK; +} + +/*********************************************/ +/* HELPER: GetSysFlags */ +/*********************************************/ + +static void +SiS_GetSysFlags(struct SiS_Private *SiS_Pr) +{ + unsigned char cr5f, temp1, temp2; + + /* 661 and newer: NEVER write non-zero to SR11[7:4] */ + /* (SR11 is used for DDC and in enable/disablebridge) */ + SiS_Pr->SiS_SensibleSR11 = false; + SiS_Pr->SiS_MyCR63 = 0x63; + if(SiS_Pr->ChipType >= SIS_330) { + SiS_Pr->SiS_MyCR63 = 0x53; + if(SiS_Pr->ChipType >= SIS_661) { + SiS_Pr->SiS_SensibleSR11 = true; + } + } + + /* You should use the macros, not these flags directly */ + + SiS_Pr->SiS_SysFlags = 0; + if(SiS_Pr->ChipType == SIS_650) { + cr5f = SiS_GetReg(SiS_Pr->SiS_P3d4,0x5f) & 0xf0; + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x5c,0x07); + temp1 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x5c) & 0xf8; + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x5c,0xf8); + temp2 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x5c) & 0xf8; + if((!temp1) || (temp2)) { + switch(cr5f) { + case 0x80: + case 0x90: + case 0xc0: + SiS_Pr->SiS_SysFlags |= SF_IsM650; + break; + case 0xa0: + case 0xb0: + case 0xe0: + SiS_Pr->SiS_SysFlags |= SF_Is651; + break; + } + } else { + switch(cr5f) { + case 0x90: + temp1 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x5c) & 0xf8; + switch(temp1) { + case 0x00: SiS_Pr->SiS_SysFlags |= SF_IsM652; break; + case 0x40: SiS_Pr->SiS_SysFlags |= SF_IsM653; break; + default: SiS_Pr->SiS_SysFlags |= SF_IsM650; break; + } + break; + case 0xb0: + SiS_Pr->SiS_SysFlags |= SF_Is652; + break; + default: + SiS_Pr->SiS_SysFlags |= SF_IsM650; + break; + } + } + } + + if(SiS_Pr->ChipType >= SIS_760 && SiS_Pr->ChipType <= SIS_761) { + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x78) & 0x30) { + SiS_Pr->SiS_SysFlags |= SF_760LFB; + } + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x79) & 0xf0) { + SiS_Pr->SiS_SysFlags |= SF_760UMA; + } + } +} + +/*********************************************/ +/* HELPER: Init PCI & Engines */ +/*********************************************/ + +static void +SiSInitPCIetc(struct SiS_Private *SiS_Pr) +{ + switch(SiS_Pr->ChipType) { +#ifdef CONFIG_FB_SIS_300 + case SIS_300: + case SIS_540: + case SIS_630: + case SIS_730: + /* Set - PCI LINEAR ADDRESSING ENABLE (0x80) + * - RELOCATED VGA IO ENABLED (0x20) + * - MMIO ENABLED (0x01) + * Leave other bits untouched. + */ + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x20,0xa1); + /* - Enable 2D (0x40) + * - Enable 3D (0x02) + * - Enable 3D Vertex command fetch (0x10) ? + * - Enable 3D command parser (0x08) ? + */ + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x5A); + break; +#endif +#ifdef CONFIG_FB_SIS_315 + case SIS_315H: + case SIS_315: + case SIS_315PRO: + case SIS_650: + case SIS_740: + case SIS_330: + case SIS_661: + case SIS_741: + case SIS_660: + case SIS_760: + case SIS_761: + case SIS_340: + case XGI_40: + /* See above */ + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x20,0xa1); + /* - Enable 3D G/L transformation engine (0x80) + * - Enable 2D (0x40) + * - Enable 3D vertex command fetch (0x10) + * - Enable 3D command parser (0x08) + * - Enable 3D (0x02) + */ + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0xDA); + break; + case XGI_20: + case SIS_550: + /* See above */ + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x20,0xa1); + /* No 3D engine ! */ + /* - Enable 2D (0x40) + * - disable 3D + */ + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x1E,0x60,0x40); + break; +#endif + default: + break; + } +} + +/*********************************************/ +/* HELPER: SetLVDSetc */ +/*********************************************/ + +static +void +SiSSetLVDSetc(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + SiS_Pr->SiS_IF_DEF_LVDS = 0; + SiS_Pr->SiS_IF_DEF_TRUMPION = 0; + SiS_Pr->SiS_IF_DEF_CH70xx = 0; + SiS_Pr->SiS_IF_DEF_CONEX = 0; + + SiS_Pr->SiS_ChrontelInit = 0; + + if(SiS_Pr->ChipType == XGI_20) return; + + /* Check for SiS30x first */ + temp = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x00); + if((temp == 1) || (temp == 2)) return; + + switch(SiS_Pr->ChipType) { +#ifdef CONFIG_FB_SIS_300 + case SIS_540: + case SIS_630: + case SIS_730: + temp = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x37) & 0x0e) >> 1; + if((temp >= 2) && (temp <= 5)) SiS_Pr->SiS_IF_DEF_LVDS = 1; + if(temp == 3) SiS_Pr->SiS_IF_DEF_TRUMPION = 1; + if((temp == 4) || (temp == 5)) { + /* Save power status (and error check) - UNUSED */ + SiS_Pr->SiS_Backup70xx = SiS_GetCH700x(SiS_Pr, 0x0e); + SiS_Pr->SiS_IF_DEF_CH70xx = 1; + } + break; +#endif +#ifdef CONFIG_FB_SIS_315 + case SIS_550: + case SIS_650: + case SIS_740: + case SIS_330: + temp = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x37) & 0x0e) >> 1; + if((temp >= 2) && (temp <= 3)) SiS_Pr->SiS_IF_DEF_LVDS = 1; + if(temp == 3) SiS_Pr->SiS_IF_DEF_CH70xx = 2; + break; + case SIS_661: + case SIS_741: + case SIS_660: + case SIS_760: + case SIS_761: + case SIS_340: + case XGI_20: + case XGI_40: + temp = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x38) & 0xe0) >> 5; + if((temp >= 2) && (temp <= 3)) SiS_Pr->SiS_IF_DEF_LVDS = 1; + if(temp == 3) SiS_Pr->SiS_IF_DEF_CH70xx = 2; + if(temp == 4) SiS_Pr->SiS_IF_DEF_CONEX = 1; /* Not yet supported */ + break; +#endif + default: + break; + } +} + +/*********************************************/ +/* HELPER: Enable DSTN/FSTN */ +/*********************************************/ + +void +SiS_SetEnableDstn(struct SiS_Private *SiS_Pr, int enable) +{ + SiS_Pr->SiS_IF_DEF_DSTN = enable ? 1 : 0; +} + +void +SiS_SetEnableFstn(struct SiS_Private *SiS_Pr, int enable) +{ + SiS_Pr->SiS_IF_DEF_FSTN = enable ? 1 : 0; +} + +/*********************************************/ +/* HELPER: Get modeflag */ +/*********************************************/ + +unsigned short +SiS_GetModeFlag(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex) +{ + if(SiS_Pr->UseCustomMode) { + return SiS_Pr->CModeFlag; + } else if(ModeNo <= 0x13) { + return SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + } else { + return SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + } +} + +/*********************************************/ +/* HELPER: Determine ROM usage */ +/*********************************************/ + +bool +SiSDetermineROMLayout661(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romversoffs, romvmaj = 1, romvmin = 0; + + if(SiS_Pr->ChipType >= XGI_20) { + /* XGI ROMs don't qualify */ + return false; + } else if(SiS_Pr->ChipType >= SIS_761) { + /* I very much assume 761, 340 and newer will use new layout */ + return true; + } else if(SiS_Pr->ChipType >= SIS_661) { + if((ROMAddr[0x1a] == 'N') && + (ROMAddr[0x1b] == 'e') && + (ROMAddr[0x1c] == 'w') && + (ROMAddr[0x1d] == 'V')) { + return true; + } + romversoffs = ROMAddr[0x16] | (ROMAddr[0x17] << 8); + if(romversoffs) { + if((ROMAddr[romversoffs+1] == '.') || (ROMAddr[romversoffs+4] == '.')) { + romvmaj = ROMAddr[romversoffs] - '0'; + romvmin = ((ROMAddr[romversoffs+2] -'0') * 10) + (ROMAddr[romversoffs+3] - '0'); + } + } + if((romvmaj != 0) || (romvmin >= 92)) { + return true; + } + } else if(IS_SIS650740) { + if((ROMAddr[0x1a] == 'N') && + (ROMAddr[0x1b] == 'e') && + (ROMAddr[0x1c] == 'w') && + (ROMAddr[0x1d] == 'V')) { + return true; + } + } + return false; +} + +static void +SiSDetermineROMUsage(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr = 0; + + SiS_Pr->SiS_UseROM = false; + SiS_Pr->SiS_ROMNew = false; + SiS_Pr->SiS_PWDOffset = 0; + + if(SiS_Pr->ChipType >= XGI_20) return; + + if((ROMAddr) && (SiS_Pr->UseROM)) { + if(SiS_Pr->ChipType == SIS_300) { + /* 300: We check if the code starts below 0x220 by + * checking the jmp instruction at the beginning + * of the BIOS image. + */ + if((ROMAddr[3] == 0xe9) && ((ROMAddr[5] << 8) | ROMAddr[4]) > 0x21a) + SiS_Pr->SiS_UseROM = true; + } else if(SiS_Pr->ChipType < SIS_315H) { + /* Sony's VAIO BIOS 1.09 follows the standard, so perhaps + * the others do as well + */ + SiS_Pr->SiS_UseROM = true; + } else { + /* 315/330 series stick to the standard(s) */ + SiS_Pr->SiS_UseROM = true; + if((SiS_Pr->SiS_ROMNew = SiSDetermineROMLayout661(SiS_Pr))) { + SiS_Pr->SiS_EMIOffset = 14; + SiS_Pr->SiS_PWDOffset = 17; + SiS_Pr->SiS661LCD2TableSize = 36; + /* Find out about LCD data table entry size */ + if((romptr = SISGETROMW(0x0102))) { + if(ROMAddr[romptr + (32 * 16)] == 0xff) + SiS_Pr->SiS661LCD2TableSize = 32; + else if(ROMAddr[romptr + (34 * 16)] == 0xff) + SiS_Pr->SiS661LCD2TableSize = 34; + else if(ROMAddr[romptr + (36 * 16)] == 0xff) /* 0.94, 2.05.00+ */ + SiS_Pr->SiS661LCD2TableSize = 36; + else if( (ROMAddr[romptr + (38 * 16)] == 0xff) || /* 2.00.00 - 2.02.00 */ + (ROMAddr[0x6F] & 0x01) ) { /* 2.03.00 - <2.05.00 */ + SiS_Pr->SiS661LCD2TableSize = 38; /* UMC data layout abandoned at 2.05.00 */ + SiS_Pr->SiS_EMIOffset = 16; + SiS_Pr->SiS_PWDOffset = 19; + } + } + } + } + } +} + +/*********************************************/ +/* HELPER: SET SEGMENT REGISTERS */ +/*********************************************/ + +static void +SiS_SetSegRegLower(struct SiS_Private *SiS_Pr, unsigned short value) +{ + unsigned short temp; + + value &= 0x00ff; + temp = SiS_GetRegByte(SiS_Pr->SiS_P3cb) & 0xf0; + temp |= (value >> 4); + SiS_SetRegByte(SiS_Pr->SiS_P3cb, temp); + temp = SiS_GetRegByte(SiS_Pr->SiS_P3cd) & 0xf0; + temp |= (value & 0x0f); + SiS_SetRegByte(SiS_Pr->SiS_P3cd, temp); +} + +static void +SiS_SetSegRegUpper(struct SiS_Private *SiS_Pr, unsigned short value) +{ + unsigned short temp; + + value &= 0x00ff; + temp = SiS_GetRegByte(SiS_Pr->SiS_P3cb) & 0x0f; + temp |= (value & 0xf0); + SiS_SetRegByte(SiS_Pr->SiS_P3cb, temp); + temp = SiS_GetRegByte(SiS_Pr->SiS_P3cd) & 0x0f; + temp |= (value << 4); + SiS_SetRegByte(SiS_Pr->SiS_P3cd, temp); +} + +static void +SiS_SetSegmentReg(struct SiS_Private *SiS_Pr, unsigned short value) +{ + SiS_SetSegRegLower(SiS_Pr, value); + SiS_SetSegRegUpper(SiS_Pr, value); +} + +static void +SiS_ResetSegmentReg(struct SiS_Private *SiS_Pr) +{ + SiS_SetSegmentReg(SiS_Pr, 0); +} + +static void +SiS_SetSegmentRegOver(struct SiS_Private *SiS_Pr, unsigned short value) +{ + unsigned short temp = value >> 8; + + temp &= 0x07; + temp |= (temp << 4); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x1d,temp); + SiS_SetSegmentReg(SiS_Pr, value); +} + +static void +SiS_ResetSegmentRegOver(struct SiS_Private *SiS_Pr) +{ + SiS_SetSegmentRegOver(SiS_Pr, 0); +} + +static void +SiS_ResetSegmentRegisters(struct SiS_Private *SiS_Pr) +{ + if((IS_SIS65x) || (SiS_Pr->ChipType >= SIS_661)) { + SiS_ResetSegmentReg(SiS_Pr); + SiS_ResetSegmentRegOver(SiS_Pr); + } +} + +/*********************************************/ +/* HELPER: GetVBType */ +/*********************************************/ + +static +void +SiS_GetVBType(struct SiS_Private *SiS_Pr) +{ + unsigned short flag = 0, rev = 0, nolcd = 0; + unsigned short p4_0f, p4_25, p4_27; + + SiS_Pr->SiS_VBType = 0; + + if((SiS_Pr->SiS_IF_DEF_LVDS) || (SiS_Pr->SiS_IF_DEF_CONEX)) + return; + + if(SiS_Pr->ChipType == XGI_20) + return; + + flag = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x00); + + if(flag > 3) + return; + + rev = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x01); + + if(flag >= 2) { + SiS_Pr->SiS_VBType = VB_SIS302B; + } else if(flag == 1) { + if(rev >= 0xC0) { + SiS_Pr->SiS_VBType = VB_SIS301C; + } else if(rev >= 0xB0) { + SiS_Pr->SiS_VBType = VB_SIS301B; + /* Check if 30xB DH version (no LCD support, use Panel Link instead) */ + nolcd = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x23); + if(!(nolcd & 0x02)) SiS_Pr->SiS_VBType |= VB_NoLCD; + } else { + SiS_Pr->SiS_VBType = VB_SIS301; + } + } + if(SiS_Pr->SiS_VBType & (VB_SIS301B | VB_SIS301C | VB_SIS302B)) { + if(rev >= 0xE0) { + flag = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x39); + if(flag == 0xff) SiS_Pr->SiS_VBType = VB_SIS302LV; + else SiS_Pr->SiS_VBType = VB_SIS301C; /* VB_SIS302ELV; */ + } else if(rev >= 0xD0) { + SiS_Pr->SiS_VBType = VB_SIS301LV; + } + } + if(SiS_Pr->SiS_VBType & (VB_SIS301C | VB_SIS301LV | VB_SIS302LV | VB_SIS302ELV)) { + p4_0f = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x0f); + p4_25 = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x25); + p4_27 = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x27); + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x0f,0x7f); + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x25,0x08); + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x27,0xfd); + if(SiS_GetReg(SiS_Pr->SiS_Part4Port,0x26) & 0x08) { + SiS_Pr->SiS_VBType |= VB_UMC; + } + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x27,p4_27); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x25,p4_25); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0f,p4_0f); + } +} + +/*********************************************/ +/* HELPER: Check RAM size */ +/*********************************************/ + +static bool +SiS_CheckMemorySize(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex) +{ + unsigned short AdapterMemSize = SiS_Pr->VideoMemorySize / (1024*1024); + unsigned short modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + unsigned short memorysize = ((modeflag & MemoryInfoFlag) >> MemorySizeShift) + 1; + + if(!AdapterMemSize) return true; + + if(AdapterMemSize < memorysize) return false; + return true; +} + +/*********************************************/ +/* HELPER: Get DRAM type */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_315 +static unsigned char +SiS_Get310DRAMType(struct SiS_Private *SiS_Pr) +{ + unsigned char data; + + if((*SiS_Pr->pSiS_SoftSetting) & SoftDRAMType) { + data = (*SiS_Pr->pSiS_SoftSetting) & 0x03; + } else { + if(SiS_Pr->ChipType >= XGI_20) { + /* Do I need this? SR17 seems to be zero anyway... */ + data = 0; + } else if(SiS_Pr->ChipType >= SIS_340) { + /* TODO */ + data = 0; + } if(SiS_Pr->ChipType >= SIS_661) { + if(SiS_Pr->SiS_ROMNew) { + data = ((SiS_GetReg(SiS_Pr->SiS_P3d4,0x78) & 0xc0) >> 6); + } else { + data = SiS_GetReg(SiS_Pr->SiS_P3d4,0x78) & 0x07; + } + } else if(IS_SIS550650740) { + data = SiS_GetReg(SiS_Pr->SiS_P3c4,0x13) & 0x07; + } else { /* 315, 330 */ + data = SiS_GetReg(SiS_Pr->SiS_P3c4,0x3a) & 0x03; + if(SiS_Pr->ChipType == SIS_330) { + if(data > 1) { + switch(SiS_GetReg(SiS_Pr->SiS_P3d4,0x5f) & 0x30) { + case 0x00: data = 1; break; + case 0x10: data = 3; break; + case 0x20: data = 3; break; + case 0x30: data = 2; break; + } + } else { + data = 0; + } + } + } + } + + return data; +} + +static unsigned short +SiS_GetMCLK(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index; + + index = SiS_Get310DRAMType(SiS_Pr); + if(SiS_Pr->ChipType >= SIS_661) { + if(SiS_Pr->SiS_ROMNew) { + return((unsigned short)(SISGETROMW((0x90 + (index * 5) + 3)))); + } + return(SiS_Pr->SiS_MCLKData_0[index].CLOCK); + } else if(index >= 4) { + return(SiS_Pr->SiS_MCLKData_1[index - 4].CLOCK); + } else { + return(SiS_Pr->SiS_MCLKData_0[index].CLOCK); + } +} +#endif + +/*********************************************/ +/* HELPER: ClearBuffer */ +/*********************************************/ + +static void +SiS_ClearBuffer(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned char SISIOMEMTYPE *memaddr = SiS_Pr->VideoMemoryAddress; + unsigned int memsize = SiS_Pr->VideoMemorySize; + unsigned short SISIOMEMTYPE *pBuffer; + int i; + + if(!memaddr || !memsize) return; + + if(SiS_Pr->SiS_ModeType >= ModeEGA) { + if(ModeNo > 0x13) { + memset_io(memaddr, 0, memsize); + } else { + pBuffer = (unsigned short SISIOMEMTYPE *)memaddr; + for(i = 0; i < 0x4000; i++) writew(0x0000, &pBuffer[i]); + } + } else if(SiS_Pr->SiS_ModeType < ModeCGA) { + pBuffer = (unsigned short SISIOMEMTYPE *)memaddr; + for(i = 0; i < 0x4000; i++) writew(0x0720, &pBuffer[i]); + } else { + memset_io(memaddr, 0, 0x8000); + } +} + +/*********************************************/ +/* HELPER: SearchModeID */ +/*********************************************/ + +bool +SiS_SearchModeID(struct SiS_Private *SiS_Pr, unsigned short *ModeNo, + unsigned short *ModeIdIndex) +{ + unsigned char VGAINFO = SiS_Pr->SiS_VGAINFO; + + if((*ModeNo) <= 0x13) { + + if((*ModeNo) <= 0x05) (*ModeNo) |= 0x01; + + for((*ModeIdIndex) = 0; ;(*ModeIdIndex)++) { + if(SiS_Pr->SiS_SModeIDTable[(*ModeIdIndex)].St_ModeID == (*ModeNo)) break; + if(SiS_Pr->SiS_SModeIDTable[(*ModeIdIndex)].St_ModeID == 0xFF) return false; + } + + if((*ModeNo) == 0x07) { + if(VGAINFO & 0x10) (*ModeIdIndex)++; /* 400 lines */ + /* else 350 lines */ + } + if((*ModeNo) <= 0x03) { + if(!(VGAINFO & 0x80)) (*ModeIdIndex)++; + if(VGAINFO & 0x10) (*ModeIdIndex)++; /* 400 lines */ + /* else 350 lines */ + } + /* else 200 lines */ + + } else { + + for((*ModeIdIndex) = 0; ;(*ModeIdIndex)++) { + if(SiS_Pr->SiS_EModeIDTable[(*ModeIdIndex)].Ext_ModeID == (*ModeNo)) break; + if(SiS_Pr->SiS_EModeIDTable[(*ModeIdIndex)].Ext_ModeID == 0xFF) return false; + } + + } + return true; +} + +/*********************************************/ +/* HELPER: GetModePtr */ +/*********************************************/ + +unsigned short +SiS_GetModePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short index; + + if(ModeNo <= 0x13) { + index = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_StTableIndex; + } else { + if(SiS_Pr->SiS_ModeType <= ModeEGA) index = 0x1B; + else index = 0x0F; + } + return index; +} + +/*********************************************/ +/* HELPERS: Get some indices */ +/*********************************************/ + +unsigned short +SiS_GetRefCRTVCLK(struct SiS_Private *SiS_Pr, unsigned short Index, int UseWide) +{ + if(SiS_Pr->SiS_RefIndex[Index].Ext_InfoFlag & HaveWideTiming) { + if(UseWide == 1) { + return SiS_Pr->SiS_RefIndex[Index].Ext_CRTVCLK_WIDE; + } else { + return SiS_Pr->SiS_RefIndex[Index].Ext_CRTVCLK_NORM; + } + } else { + return SiS_Pr->SiS_RefIndex[Index].Ext_CRTVCLK; + } +} + +unsigned short +SiS_GetRefCRT1CRTC(struct SiS_Private *SiS_Pr, unsigned short Index, int UseWide) +{ + if(SiS_Pr->SiS_RefIndex[Index].Ext_InfoFlag & HaveWideTiming) { + if(UseWide == 1) { + return SiS_Pr->SiS_RefIndex[Index].Ext_CRT1CRTC_WIDE; + } else { + return SiS_Pr->SiS_RefIndex[Index].Ext_CRT1CRTC_NORM; + } + } else { + return SiS_Pr->SiS_RefIndex[Index].Ext_CRT1CRTC; + } +} + +/*********************************************/ +/* HELPER: LowModeTests */ +/*********************************************/ + +static bool +SiS_DoLowModeTest(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned short temp, temp1, temp2; + + if((ModeNo != 0x03) && (ModeNo != 0x10) && (ModeNo != 0x12)) + return true; + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x11); + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x11,0x80); + temp1 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x00); + SiS_SetReg(SiS_Pr->SiS_P3d4,0x00,0x55); + temp2 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x00); + SiS_SetReg(SiS_Pr->SiS_P3d4,0x00,temp1); + SiS_SetReg(SiS_Pr->SiS_P3d4,0x11,temp); + if((SiS_Pr->ChipType >= SIS_315H) || + (SiS_Pr->ChipType == SIS_300)) { + if(temp2 == 0x55) return false; + else return true; + } else { + if(temp2 != 0x55) return true; + else { + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x35,0x01); + return false; + } + } +} + +static void +SiS_SetLowModeTest(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + if(SiS_DoLowModeTest(SiS_Pr, ModeNo)) { + SiS_Pr->SiS_SetFlag |= LowModeTests; + } +} + +/*********************************************/ +/* HELPER: OPEN/CLOSE CRT1 CRTC */ +/*********************************************/ + +static void +SiS_OpenCRTC(struct SiS_Private *SiS_Pr) +{ + if(IS_SIS650) { + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x51,0x1f); + if(IS_SIS651) SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x51,0x20); + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x56,0xe7); + } else if(IS_SIS661741660760) { + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x61,0xf7); + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x51,0x1f); + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x56,0xe7); + if(!SiS_Pr->SiS_ROMNew) { + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x3a,0xef); + } + } +} + +static void +SiS_CloseCRTC(struct SiS_Private *SiS_Pr) +{ +#if 0 /* This locks some CRTC registers. We don't want that. */ + unsigned short temp1 = 0, temp2 = 0; + + if(IS_SIS661741660760) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + temp1 = 0xa0; temp2 = 0x08; + } + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x51,0x1f,temp1); + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x56,0xe7,temp2); + } +#endif +} + +static void +SiS_HandleCRT1(struct SiS_Private *SiS_Pr) +{ + /* Enable CRT1 gating */ + SiS_SetRegAND(SiS_Pr->SiS_P3d4,SiS_Pr->SiS_MyCR63,0xbf); +#if 0 + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x15) & 0x01)) { + if((SiS_GetReg(SiS_Pr->SiS_P3c4,0x15) & 0x0a) || + (SiS_GetReg(SiS_Pr->SiS_P3c4,0x16) & 0x01)) { + SiS_SetRegOR(SiS_Pr->SiS_P3d4,SiS_Pr->SiS_MyCR63,0x40); + } + } +#endif +} + +/*********************************************/ +/* HELPER: GetColorDepth */ +/*********************************************/ + +unsigned short +SiS_GetColorDepth(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex) +{ + static const unsigned short ColorDepth[6] = { 1, 2, 4, 4, 6, 8 }; + unsigned short modeflag; + short index; + + /* Do NOT check UseCustomMode, will skrew up FIFO */ + if(ModeNo == 0xfe) { + modeflag = SiS_Pr->CModeFlag; + } else if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + } + + index = (modeflag & ModeTypeMask) - ModeEGA; + if(index < 0) index = 0; + return ColorDepth[index]; +} + +/*********************************************/ +/* HELPER: GetOffset */ +/*********************************************/ + +unsigned short +SiS_GetOffset(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI) +{ + unsigned short xres, temp, colordepth, infoflag; + + if(SiS_Pr->UseCustomMode) { + infoflag = SiS_Pr->CInfoFlag; + xres = SiS_Pr->CHDisplay; + } else { + infoflag = SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag; + xres = SiS_Pr->SiS_RefIndex[RRTI].XRes; + } + + colordepth = SiS_GetColorDepth(SiS_Pr, ModeNo, ModeIdIndex); + + temp = xres / 16; + if(infoflag & InterlaceMode) temp <<= 1; + temp *= colordepth; + if(xres % 16) temp += (colordepth >> 1); + + return temp; +} + +/*********************************************/ +/* SEQ */ +/*********************************************/ + +static void +SiS_SetSeqRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char SRdata; + int i; + + SiS_SetReg(SiS_Pr->SiS_P3c4,0x00,0x03); + + /* or "display off" */ + SRdata = SiS_Pr->SiS_StandTable[StandTableIndex].SR[0] | 0x20; + + /* determine whether to force x8 dotclock */ + if((SiS_Pr->SiS_VBType & VB_SISVB) || (SiS_Pr->SiS_IF_DEF_LVDS)) { + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToTV)) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) SRdata |= 0x01; + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) SRdata |= 0x01; + + } + + SiS_SetReg(SiS_Pr->SiS_P3c4,0x01,SRdata); + + for(i = 2; i <= 4; i++) { + SRdata = SiS_Pr->SiS_StandTable[StandTableIndex].SR[i - 1]; + SiS_SetReg(SiS_Pr->SiS_P3c4,i,SRdata); + } +} + +/*********************************************/ +/* MISC */ +/*********************************************/ + +static void +SiS_SetMiscRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char Miscdata; + + Miscdata = SiS_Pr->SiS_StandTable[StandTableIndex].MISC; + + if(SiS_Pr->ChipType < SIS_661) { + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + Miscdata |= 0x0C; + } + } + } + + SiS_SetRegByte(SiS_Pr->SiS_P3c2,Miscdata); +} + +/*********************************************/ +/* CRTC */ +/*********************************************/ + +static void +SiS_SetCRTCRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char CRTCdata; + unsigned short i; + + /* Unlock CRTC */ + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x11,0x7f); + + for(i = 0; i <= 0x18; i++) { + CRTCdata = SiS_Pr->SiS_StandTable[StandTableIndex].CRTC[i]; + SiS_SetReg(SiS_Pr->SiS_P3d4,i,CRTCdata); + } + + if(SiS_Pr->ChipType >= SIS_661) { + SiS_OpenCRTC(SiS_Pr); + for(i = 0x13; i <= 0x14; i++) { + CRTCdata = SiS_Pr->SiS_StandTable[StandTableIndex].CRTC[i]; + SiS_SetReg(SiS_Pr->SiS_P3d4,i,CRTCdata); + } + } else if( ( (SiS_Pr->ChipType == SIS_630) || + (SiS_Pr->ChipType == SIS_730) ) && + (SiS_Pr->ChipRevision >= 0x30) ) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToTV)) { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x18,0xFE); + } + } + } +} + +/*********************************************/ +/* ATT */ +/*********************************************/ + +static void +SiS_SetATTRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char ARdata; + unsigned short i; + + for(i = 0; i <= 0x13; i++) { + ARdata = SiS_Pr->SiS_StandTable[StandTableIndex].ATTR[i]; + + if(i == 0x13) { + /* Pixel shift. If screen on LCD or TV is shifted left or right, + * this might be the cause. + */ + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) ARdata = 0; + } + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) ARdata = 0; + } + } + } + if(SiS_Pr->ChipType >= SIS_661) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToTV | SetCRT2ToLCD)) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) ARdata = 0; + } + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->ChipType >= SIS_315H) { + if(IS_SIS550650740660) { + /* 315, 330 don't do this */ + if(SiS_Pr->SiS_VBType & VB_SIS30xB) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) ARdata = 0; + } else { + ARdata = 0; + } + } + } else { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) ARdata = 0; + } + } + } + SiS_GetRegByte(SiS_Pr->SiS_P3da); /* reset 3da */ + SiS_SetRegByte(SiS_Pr->SiS_P3c0,i); /* set index */ + SiS_SetRegByte(SiS_Pr->SiS_P3c0,ARdata); /* set data */ + } + + SiS_GetRegByte(SiS_Pr->SiS_P3da); /* reset 3da */ + SiS_SetRegByte(SiS_Pr->SiS_P3c0,0x14); /* set index */ + SiS_SetRegByte(SiS_Pr->SiS_P3c0,0x00); /* set data */ + + SiS_GetRegByte(SiS_Pr->SiS_P3da); + SiS_SetRegByte(SiS_Pr->SiS_P3c0,0x20); /* Enable Attribute */ + SiS_GetRegByte(SiS_Pr->SiS_P3da); +} + +/*********************************************/ +/* GRC */ +/*********************************************/ + +static void +SiS_SetGRCRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char GRdata; + unsigned short i; + + for(i = 0; i <= 0x08; i++) { + GRdata = SiS_Pr->SiS_StandTable[StandTableIndex].GRC[i]; + SiS_SetReg(SiS_Pr->SiS_P3ce,i,GRdata); + } + + if(SiS_Pr->SiS_ModeType > ModeVGA) { + /* 256 color disable */ + SiS_SetRegAND(SiS_Pr->SiS_P3ce,0x05,0xBF); + } +} + +/*********************************************/ +/* CLEAR EXTENDED REGISTERS */ +/*********************************************/ + +static void +SiS_ClearExt1Regs(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned short i; + + for(i = 0x0A; i <= 0x0E; i++) { + SiS_SetReg(SiS_Pr->SiS_P3c4,i,0x00); + } + + if(SiS_Pr->ChipType >= SIS_315H) { + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x37,0xFE); + if(ModeNo <= 0x13) { + if(ModeNo == 0x06 || ModeNo >= 0x0e) { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x0e,0x20); + } + } + } +} + +/*********************************************/ +/* RESET VCLK */ +/*********************************************/ + +static void +SiS_ResetCRT1VCLK(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->ChipType < SIS_661) { + if(SiS_Pr->SiS_IF_DEF_LVDS == 0) return; + } + } else { + if((SiS_Pr->SiS_IF_DEF_LVDS == 0) && + (!(SiS_Pr->SiS_VBType & VB_SIS30xBLV)) ) { + return; + } + } + + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x31,0xcf,0x20); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2B,SiS_Pr->SiS_VCLKData[1].SR2B); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2C,SiS_Pr->SiS_VCLKData[1].SR2C); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2D,0x80); + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x31,0xcf,0x10); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2B,SiS_Pr->SiS_VCLKData[0].SR2B); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2C,SiS_Pr->SiS_VCLKData[0].SR2C); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2D,0x80); +} + +/*********************************************/ +/* SYNC */ +/*********************************************/ + +static void +SiS_SetCRT1Sync(struct SiS_Private *SiS_Pr, unsigned short RRTI) +{ + unsigned short sync; + + if(SiS_Pr->UseCustomMode) { + sync = SiS_Pr->CInfoFlag >> 8; + } else { + sync = SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag >> 8; + } + + sync &= 0xC0; + sync |= 0x2f; + SiS_SetRegByte(SiS_Pr->SiS_P3c2,sync); +} + +/*********************************************/ +/* CRTC/2 */ +/*********************************************/ + +static void +SiS_SetCRT1CRTC(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI) +{ + unsigned short temp, i, j, modeflag; + unsigned char *crt1data = NULL; + + modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + + if(SiS_Pr->UseCustomMode) { + + crt1data = &SiS_Pr->CCRT1CRTC[0]; + + } else { + + temp = SiS_GetRefCRT1CRTC(SiS_Pr, RRTI, SiS_Pr->SiS_UseWide); + + /* Alternate for 1600x1200 LCDA */ + if((temp == 0x20) && (SiS_Pr->Alternate1600x1200)) temp = 0x57; + + crt1data = (unsigned char *)&SiS_Pr->SiS_CRT1Table[temp].CR[0]; + + } + + /* unlock cr0-7 */ + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x11,0x7f); + + for(i = 0, j = 0; i <= 7; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3d4,j,crt1data[i]); + } + for(j = 0x10; i <= 10; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3d4,j,crt1data[i]); + } + for(j = 0x15; i <= 12; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3d4,j,crt1data[i]); + } + for(j = 0x0A; i <= 15; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3c4,j,crt1data[i]); + } + + SiS_SetReg(SiS_Pr->SiS_P3c4,0x0E,crt1data[16] & 0xE0); + + temp = (crt1data[16] & 0x01) << 5; + if(modeflag & DoubleScanMode) temp |= 0x80; + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x09,0x5F,temp); + + if(SiS_Pr->SiS_ModeType > ModeVGA) { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x14,0x4F); + } + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType == XGI_20) { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x04,crt1data[4] - 1); + if(!(temp = crt1data[5] & 0x1f)) { + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x0c,0xfb); + } + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x05,0xe0,((temp - 1) & 0x1f)); + temp = (crt1data[16] >> 5) + 3; + if(temp > 7) temp -= 7; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0e,0x1f,(temp << 5)); + } +#endif +} + +/*********************************************/ +/* OFFSET & PITCH */ +/*********************************************/ +/* (partly overruled by SetPitch() in XF86) */ +/*********************************************/ + +static void +SiS_SetCRT1Offset(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI) +{ + unsigned short temp, DisplayUnit, infoflag; + + if(SiS_Pr->UseCustomMode) { + infoflag = SiS_Pr->CInfoFlag; + } else { + infoflag = SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag; + } + + DisplayUnit = SiS_GetOffset(SiS_Pr, ModeNo, ModeIdIndex, RRTI); + + temp = (DisplayUnit >> 8) & 0x0f; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0E,0xF0,temp); + + SiS_SetReg(SiS_Pr->SiS_P3d4,0x13,DisplayUnit & 0xFF); + + if(infoflag & InterlaceMode) DisplayUnit >>= 1; + + DisplayUnit <<= 5; + temp = (DisplayUnit >> 8) + 1; + if(DisplayUnit & 0xff) temp++; + if(SiS_Pr->ChipType == XGI_20) { + if(ModeNo == 0x4a || ModeNo == 0x49) temp--; + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x10,temp); +} + +/*********************************************/ +/* VCLK */ +/*********************************************/ + +static void +SiS_SetCRT1VCLK(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI) +{ + unsigned short index = 0, clka, clkb; + + if(SiS_Pr->UseCustomMode) { + clka = SiS_Pr->CSR2B; + clkb = SiS_Pr->CSR2C; + } else { + index = SiS_GetVCLK2Ptr(SiS_Pr, ModeNo, ModeIdIndex, RRTI); + if((SiS_Pr->SiS_VBType & VB_SIS30xBLV) && + (SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + /* Alternate for 1600x1200 LCDA */ + if((index == 0x21) && (SiS_Pr->Alternate1600x1200)) index = 0x72; + clka = SiS_Pr->SiS_VBVCLKData[index].Part4_A; + clkb = SiS_Pr->SiS_VBVCLKData[index].Part4_B; + } else { + clka = SiS_Pr->SiS_VCLKData[index].SR2B; + clkb = SiS_Pr->SiS_VCLKData[index].SR2C; + } + } + + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x31,0xCF); + + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2b,clka); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2c,clkb); + + if(SiS_Pr->ChipType >= SIS_315H) { +#ifdef CONFIG_FB_SIS_315 + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2D,0x01); + if(SiS_Pr->ChipType == XGI_20) { + unsigned short mf = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + if(mf & HalfDCLK) { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2b,SiS_GetReg(SiS_Pr->SiS_P3c4,0x2b)); + clkb = SiS_GetReg(SiS_Pr->SiS_P3c4,0x2c); + clkb = (((clkb & 0x1f) << 1) + 1) | (clkb & 0xe0); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2c,clkb); + } + } +#endif + } else { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2D,0x80); + } +} + +/*********************************************/ +/* FIFO */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_300 +void +SiS_GetFIFOThresholdIndex300(struct SiS_Private *SiS_Pr, unsigned short *idx1, + unsigned short *idx2) +{ + unsigned short temp1, temp2; + static const unsigned char ThTiming[8] = { + 1, 2, 2, 3, 0, 1, 1, 2 + }; + + temp1 = temp2 = (SiS_GetReg(SiS_Pr->SiS_P3c4,0x18) & 0x62) >> 1; + (*idx2) = (unsigned short)(ThTiming[((temp2 >> 3) | temp1) & 0x07]); + (*idx1) = (unsigned short)(SiS_GetReg(SiS_Pr->SiS_P3c4,0x16) >> 6) & 0x03; + (*idx1) |= (unsigned short)(((SiS_GetReg(SiS_Pr->SiS_P3c4,0x14) >> 4) & 0x0c)); + (*idx1) <<= 1; +} + +static unsigned short +SiS_GetFIFOThresholdA300(unsigned short idx1, unsigned short idx2) +{ + static const unsigned char ThLowA[8 * 3] = { + 61, 3,52, 5,68, 7,100,11, + 43, 3,42, 5,54, 7, 78,11, + 34, 3,37, 5,47, 7, 67,11 + }; + + return (unsigned short)((ThLowA[idx1 + 1] * idx2) + ThLowA[idx1]); +} + +unsigned short +SiS_GetFIFOThresholdB300(unsigned short idx1, unsigned short idx2) +{ + static const unsigned char ThLowB[8 * 3] = { + 81, 4,72, 6,88, 8,120,12, + 55, 4,54, 6,66, 8, 90,12, + 42, 4,45, 6,55, 8, 75,12 + }; + + return (unsigned short)((ThLowB[idx1 + 1] * idx2) + ThLowB[idx1]); +} + +static unsigned short +SiS_DoCalcDelay(struct SiS_Private *SiS_Pr, unsigned short MCLK, unsigned short VCLK, + unsigned short colordepth, unsigned short key) +{ + unsigned short idx1, idx2; + unsigned int longtemp = VCLK * colordepth; + + SiS_GetFIFOThresholdIndex300(SiS_Pr, &idx1, &idx2); + + if(key == 0) { + longtemp *= SiS_GetFIFOThresholdA300(idx1, idx2); + } else { + longtemp *= SiS_GetFIFOThresholdB300(idx1, idx2); + } + idx1 = longtemp % (MCLK * 16); + longtemp /= (MCLK * 16); + if(idx1) longtemp++; + return (unsigned short)longtemp; +} + +static unsigned short +SiS_CalcDelay(struct SiS_Private *SiS_Pr, unsigned short VCLK, + unsigned short colordepth, unsigned short MCLK) +{ + unsigned short temp1, temp2; + + temp2 = SiS_DoCalcDelay(SiS_Pr, MCLK, VCLK, colordepth, 0); + temp1 = SiS_DoCalcDelay(SiS_Pr, MCLK, VCLK, colordepth, 1); + if(temp1 < 4) temp1 = 4; + temp1 -= 4; + if(temp2 < temp1) temp2 = temp1; + return temp2; +} + +static void +SiS_SetCRT1FIFO_300(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short RefreshRateTableIndex) +{ + unsigned short ThresholdLow = 0; + unsigned short temp, index, VCLK, MCLK, colorth; + static const unsigned short colortharray[6] = { 1, 1, 2, 2, 3, 4 }; + + if(ModeNo > 0x13) { + + /* Get VCLK */ + if(SiS_Pr->UseCustomMode) { + VCLK = SiS_Pr->CSRClock; + } else { + index = SiS_GetRefCRTVCLK(SiS_Pr, RefreshRateTableIndex, SiS_Pr->SiS_UseWide); + VCLK = SiS_Pr->SiS_VCLKData[index].CLOCK; + } + + /* Get half colordepth */ + colorth = colortharray[(SiS_Pr->SiS_ModeType - ModeEGA)]; + + /* Get MCLK */ + index = SiS_GetReg(SiS_Pr->SiS_P3c4,0x3A) & 0x07; + MCLK = SiS_Pr->SiS_MCLKData_0[index].CLOCK; + + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x35) & 0xc3; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x16,0x3c,temp); + + do { + ThresholdLow = SiS_CalcDelay(SiS_Pr, VCLK, colorth, MCLK) + 1; + if(ThresholdLow < 0x13) break; + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x16,0xfc); + ThresholdLow = 0x13; + temp = SiS_GetReg(SiS_Pr->SiS_P3c4,0x16) >> 6; + if(!temp) break; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x16,0x3f,((temp - 1) << 6)); + } while(0); + + } else ThresholdLow = 2; + + /* Write CRT/CPU threshold low, CRT/Engine threshold high */ + temp = (ThresholdLow << 4) | 0x0f; + SiS_SetReg(SiS_Pr->SiS_P3c4,0x08,temp); + + temp = (ThresholdLow & 0x10) << 1; + if(ModeNo > 0x13) temp |= 0x40; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0f,0x9f,temp); + + /* What is this? */ + SiS_SetReg(SiS_Pr->SiS_P3c4,0x3B,0x09); + + /* Write CRT/CPU threshold high */ + temp = ThresholdLow + 3; + if(temp > 0x0f) temp = 0x0f; + SiS_SetReg(SiS_Pr->SiS_P3c4,0x09,temp); +} + +unsigned short +SiS_GetLatencyFactor630(struct SiS_Private *SiS_Pr, unsigned short index) +{ + static const unsigned char LatencyFactor[] = { + 97, 88, 86, 79, 77, 0, /* 64 bit BQ=2 */ + 0, 87, 85, 78, 76, 54, /* 64 bit BQ=1 */ + 97, 88, 86, 79, 77, 0, /* 128 bit BQ=2 */ + 0, 79, 77, 70, 68, 48, /* 128 bit BQ=1 */ + 80, 72, 69, 63, 61, 0, /* 64 bit BQ=2 */ + 0, 70, 68, 61, 59, 37, /* 64 bit BQ=1 */ + 86, 77, 75, 68, 66, 0, /* 128 bit BQ=2 */ + 0, 68, 66, 59, 57, 37 /* 128 bit BQ=1 */ + }; + static const unsigned char LatencyFactor730[] = { + 69, 63, 61, + 86, 79, 77, + 103, 96, 94, + 120,113,111, + 137,130,128 + }; + + if(SiS_Pr->ChipType == SIS_730) { + return (unsigned short)LatencyFactor730[index]; + } else { + return (unsigned short)LatencyFactor[index]; + } +} + +static unsigned short +SiS_CalcDelay2(struct SiS_Private *SiS_Pr, unsigned char key) +{ + unsigned short index; + + if(SiS_Pr->ChipType == SIS_730) { + index = ((key & 0x0f) * 3) + ((key & 0xc0) >> 6); + } else { + index = (key & 0xe0) >> 5; + if(key & 0x10) index += 6; + if(!(key & 0x01)) index += 24; + if(SiS_GetReg(SiS_Pr->SiS_P3c4,0x14) & 0x80) index += 12; + } + return SiS_GetLatencyFactor630(SiS_Pr, index); +} + +static void +SiS_SetCRT1FIFO_630(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short RefreshRateTableIndex) +{ + unsigned short ThresholdLow = 0; + unsigned short i, data, VCLK, MCLK16, colorth = 0; + unsigned int templ, datal; + const unsigned char *queuedata = NULL; + static const unsigned char FQBQData[21] = { + 0x01,0x21,0x41,0x61,0x81, + 0x31,0x51,0x71,0x91,0xb1, + 0x00,0x20,0x40,0x60,0x80, + 0x30,0x50,0x70,0x90,0xb0, + 0xff + }; + static const unsigned char FQBQData730[16] = { + 0x34,0x74,0xb4, + 0x23,0x63,0xa3, + 0x12,0x52,0x92, + 0x01,0x41,0x81, + 0x00,0x40,0x80, + 0xff + }; + static const unsigned short colortharray[6] = { + 1, 1, 2, 2, 3, 4 + }; + + i = 0; + + if(ModeNo > 0x13) { + + /* Get VCLK */ + if(SiS_Pr->UseCustomMode) { + VCLK = SiS_Pr->CSRClock; + } else { + data = SiS_GetRefCRTVCLK(SiS_Pr, RefreshRateTableIndex, SiS_Pr->SiS_UseWide); + VCLK = SiS_Pr->SiS_VCLKData[data].CLOCK; + } + + /* Get MCLK * 16 */ + data = SiS_GetReg(SiS_Pr->SiS_P3c4,0x1A) & 0x07; + MCLK16 = SiS_Pr->SiS_MCLKData_0[data].CLOCK * 16; + + /* Get half colordepth */ + colorth = colortharray[(SiS_Pr->SiS_ModeType - ModeEGA)]; + + if(SiS_Pr->ChipType == SIS_730) { + queuedata = &FQBQData730[0]; + } else { + queuedata = &FQBQData[0]; + } + + do { + templ = SiS_CalcDelay2(SiS_Pr, queuedata[i]) * VCLK * colorth; + + datal = templ % MCLK16; + templ = (templ / MCLK16) + 1; + if(datal) templ++; + + if(templ > 0x13) { + if(queuedata[i + 1] == 0xFF) { + ThresholdLow = 0x13; + break; + } + i++; + } else { + ThresholdLow = templ; + break; + } + } while(queuedata[i] != 0xFF); + + } else { + + if(SiS_Pr->ChipType != SIS_730) i = 9; + ThresholdLow = 0x02; + + } + + /* Write CRT/CPU threshold low, CRT/Engine threshold high */ + data = ((ThresholdLow & 0x0f) << 4) | 0x0f; + SiS_SetReg(SiS_Pr->SiS_P3c4,0x08,data); + + data = (ThresholdLow & 0x10) << 1; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0F,0xDF,data); + + /* What is this? */ + SiS_SetReg(SiS_Pr->SiS_P3c4,0x3B,0x09); + + /* Write CRT/CPU threshold high (gap = 3) */ + data = ThresholdLow + 3; + if(data > 0x0f) data = 0x0f; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x09,0x80,data); + + /* Write foreground and background queue */ + templ = sisfb_read_nbridge_pci_dword(SiS_Pr, 0x50); + + if(SiS_Pr->ChipType == SIS_730) { + + templ &= 0xfffff9ff; + templ |= ((queuedata[i] & 0xc0) << 3); + + } else { + + templ &= 0xf0ffffff; + if( (ModeNo <= 0x13) && + (SiS_Pr->ChipType == SIS_630) && + (SiS_Pr->ChipRevision >= 0x30) ) { + templ |= 0x0b000000; + } else { + templ |= ((queuedata[i] & 0xf0) << 20); + } + + } + + sisfb_write_nbridge_pci_dword(SiS_Pr, 0x50, templ); + templ = sisfb_read_nbridge_pci_dword(SiS_Pr, 0xA0); + + /* GUI grant timer (PCI config 0xA3) */ + if(SiS_Pr->ChipType == SIS_730) { + + templ &= 0x00ffffff; + datal = queuedata[i] << 8; + templ |= (((datal & 0x0f00) | ((datal & 0x3000) >> 8)) << 20); + + } else { + + templ &= 0xf0ffffff; + templ |= ((queuedata[i] & 0x0f) << 24); + + } + + sisfb_write_nbridge_pci_dword(SiS_Pr, 0xA0, templ); +} +#endif /* CONFIG_FB_SIS_300 */ + +#ifdef CONFIG_FB_SIS_315 +static void +SiS_SetCRT1FIFO_310(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short modeflag; + + /* disable auto-threshold */ + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x3D,0xFE); + + modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + + SiS_SetReg(SiS_Pr->SiS_P3c4,0x08,0xAE); + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x09,0xF0); + if(ModeNo > 0x13) { + if(SiS_Pr->ChipType >= XGI_20) { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x08,0x34); + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x3D,0x01); + } else if(SiS_Pr->ChipType >= SIS_661) { + if(!(modeflag & HalfDCLK)) { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x08,0x34); + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x3D,0x01); + } + } else { + if((!(modeflag & DoubleScanMode)) || (!(modeflag & HalfDCLK))) { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x08,0x34); + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x3D,0x01); + } + } + } +} +#endif + +/*********************************************/ +/* MODE REGISTERS */ +/*********************************************/ + +static void +SiS_SetVCLKState(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short RefreshRateTableIndex, unsigned short ModeIdIndex) +{ + unsigned short data = 0, VCLK = 0, index = 0; + + if(ModeNo > 0x13) { + if(SiS_Pr->UseCustomMode) { + VCLK = SiS_Pr->CSRClock; + } else { + index = SiS_GetVCLK2Ptr(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + VCLK = SiS_Pr->SiS_VCLKData[index].CLOCK; + } + } + + if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + if(VCLK > 150) data |= 0x80; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x07,0x7B,data); + + data = 0x00; + if(VCLK >= 150) data |= 0x08; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x32,0xF7,data); +#endif + } else if(SiS_Pr->ChipType < XGI_20) { +#ifdef CONFIG_FB_SIS_315 + if(VCLK >= 166) data |= 0x0c; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x32,0xf3,data); + + if(VCLK >= 166) { + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x1f,0xe7); + } +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + if(VCLK >= 200) data |= 0x0c; + if(SiS_Pr->ChipType == XGI_20) data &= ~0x04; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x32,0xf3,data); + if(SiS_Pr->ChipType != XGI_20) { + data = SiS_GetReg(SiS_Pr->SiS_P3c4,0x1f) & 0xe7; + if(VCLK < 200) data |= 0x10; + SiS_SetReg(SiS_Pr->SiS_P3c4,0x1f,data); + } +#endif + } + + /* DAC speed */ + if(SiS_Pr->ChipType >= SIS_661) { + + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x07,0xE8,0x10); + + } else { + + data = 0x03; + if(VCLK >= 260) data = 0x00; + else if(VCLK >= 160) data = 0x01; + else if(VCLK >= 135) data = 0x02; + + if(SiS_Pr->ChipType == SIS_540) { + /* Was == 203 or < 234 which made no sense */ + if (VCLK < 234) data = 0x02; + } + + if(SiS_Pr->ChipType < SIS_315H) { + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x07,0xFC,data); + } else { + if(SiS_Pr->ChipType > SIS_315PRO) { + if(ModeNo > 0x13) data &= 0xfc; + } + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x07,0xF8,data); + } + + } +} + +static void +SiS_SetCRT1ModeRegs(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI) +{ + unsigned short data, infoflag = 0, modeflag, resindex; +#ifdef CONFIG_FB_SIS_315 + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short data2, data3; +#endif + + modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + + if(SiS_Pr->UseCustomMode) { + infoflag = SiS_Pr->CInfoFlag; + } else { + resindex = SiS_GetResInfo(SiS_Pr, ModeNo, ModeIdIndex); + if(ModeNo > 0x13) { + infoflag = SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag; + } + } + + /* Disable DPMS */ + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x1F,0x3F); + + data = 0; + if(ModeNo > 0x13) { + if(SiS_Pr->SiS_ModeType > ModeEGA) { + data |= 0x02; + data |= ((SiS_Pr->SiS_ModeType - ModeVGA) << 2); + } + if(infoflag & InterlaceMode) data |= 0x20; + } + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x06,0xC0,data); + + if(SiS_Pr->ChipType != SIS_300) { + data = 0; + if(infoflag & InterlaceMode) { + /* data = (Hsync / 8) - ((Htotal / 8) / 2) + 3 */ + int hrs = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x04) | + ((SiS_GetReg(SiS_Pr->SiS_P3c4,0x0b) & 0xc0) << 2)) - 3; + int hto = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x00) | + ((SiS_GetReg(SiS_Pr->SiS_P3c4,0x0b) & 0x03) << 8)) + 5; + data = hrs - (hto >> 1) + 3; + } + SiS_SetReg(SiS_Pr->SiS_P3d4,0x19,data); + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x1a,0xFC,((data >> 8) & 0x03)); + } + + if(modeflag & HalfDCLK) { + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x01,0x08); + } + + data = 0; + if(modeflag & LineCompareOff) data = 0x08; + if(SiS_Pr->ChipType == SIS_300) { + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0F,0xF7,data); + } else { + if(SiS_Pr->ChipType >= XGI_20) data |= 0x20; + if(SiS_Pr->SiS_ModeType == ModeEGA) { + if(ModeNo > 0x13) { + data |= 0x40; + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0F,0xB7,data); + } + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x31,0xfb); + } + + if(SiS_Pr->ChipType == SIS_315PRO) { + + data = SiS_Pr->SiS_SR15[(2 * 4) + SiS_Get310DRAMType(SiS_Pr)]; + if(SiS_Pr->SiS_ModeType == ModeText) { + data &= 0xc7; + } else { + data2 = SiS_GetOffset(SiS_Pr, ModeNo, ModeIdIndex, RRTI) >> 1; + if(infoflag & InterlaceMode) data2 >>= 1; + data3 = SiS_GetColorDepth(SiS_Pr, ModeNo, ModeIdIndex) >> 1; + if(data3) data2 /= data3; + if(data2 >= 0x50) { + data &= 0x0f; + data |= 0x50; + } + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x17,data); + + } else if((SiS_Pr->ChipType == SIS_330) || (SiS_Pr->SiS_SysFlags & SF_760LFB)) { + + data = SiS_Get310DRAMType(SiS_Pr); + if(SiS_Pr->ChipType == SIS_330) { + data = SiS_Pr->SiS_SR15[(2 * 4) + data]; + } else { + if(SiS_Pr->SiS_ROMNew) data = ROMAddr[0xf6]; + else if(SiS_Pr->SiS_UseROM) data = ROMAddr[0x100 + data]; + else data = 0xba; + } + if(SiS_Pr->SiS_ModeType <= ModeEGA) { + data &= 0xc7; + } else { + if(SiS_Pr->UseCustomMode) { + data2 = SiS_Pr->CSRClock; + } else { + data2 = SiS_GetVCLK2Ptr(SiS_Pr, ModeNo, ModeIdIndex, RRTI); + data2 = SiS_Pr->SiS_VCLKData[data2].CLOCK; + } + + data3 = SiS_GetColorDepth(SiS_Pr, ModeNo, ModeIdIndex) >> 1; + if(data3) data2 *= data3; + + data2 = ((unsigned int)(SiS_GetMCLK(SiS_Pr) * 1024)) / data2; + + if(SiS_Pr->ChipType == SIS_330) { + if(SiS_Pr->SiS_ModeType != Mode16Bpp) { + if (data2 >= 0x19c) data = 0xba; + else if(data2 >= 0x140) data = 0x7a; + else if(data2 >= 0x101) data = 0x3a; + else if(data2 >= 0xf5) data = 0x32; + else if(data2 >= 0xe2) data = 0x2a; + else if(data2 >= 0xc4) data = 0x22; + else if(data2 >= 0xac) data = 0x1a; + else if(data2 >= 0x9e) data = 0x12; + else if(data2 >= 0x8e) data = 0x0a; + else data = 0x02; + } else { + if(data2 >= 0x127) data = 0xba; + else data = 0x7a; + } + } else { /* 76x+LFB */ + if (data2 >= 0x190) data = 0xba; + else if(data2 >= 0xff) data = 0x7a; + else if(data2 >= 0xd3) data = 0x3a; + else if(data2 >= 0xa9) data = 0x1a; + else if(data2 >= 0x93) data = 0x0a; + else data = 0x02; + } + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x17,data); + + } + /* XGI: Nothing. */ + /* TODO: Check SiS340 */ +#endif + + data = 0x60; + if(SiS_Pr->SiS_ModeType != ModeText) { + data ^= 0x60; + if(SiS_Pr->SiS_ModeType != ModeEGA) { + data ^= 0xA0; + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x21,0x1F,data); + + SiS_SetVCLKState(SiS_Pr, ModeNo, RRTI, ModeIdIndex); + +#ifdef CONFIG_FB_SIS_315 + if(((SiS_Pr->ChipType >= SIS_315H) && (SiS_Pr->ChipType < SIS_661)) || + (SiS_Pr->ChipType == XGI_40)) { + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) & 0x40) { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x52,0x2c); + } else { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x52,0x6c); + } + } else if(SiS_Pr->ChipType == XGI_20) { + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) & 0x40) { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x52,0x33); + } else { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x52,0x73); + } + SiS_SetReg(SiS_Pr->SiS_P3d4,0x51,0x02); + } +#endif +} + +#ifdef CONFIG_FB_SIS_315 +static void +SiS_SetupDualChip(struct SiS_Private *SiS_Pr) +{ +#if 0 + /* TODO: Find out about IOAddress2 */ + SISIOADDRESS P2_3c2 = SiS_Pr->IOAddress2 + 0x12; + SISIOADDRESS P2_3c4 = SiS_Pr->IOAddress2 + 0x14; + SISIOADDRESS P2_3ce = SiS_Pr->IOAddress2 + 0x1e; + int i; + + if((SiS_Pr->ChipRevision != 0) || + (!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x3a) & 0x04))) + return; + + for(i = 0; i <= 4; i++) { /* SR00 - SR04 */ + SiS_SetReg(P2_3c4,i,SiS_GetReg(SiS_Pr->SiS_P3c4,i)); + } + for(i = 0; i <= 8; i++) { /* GR00 - GR08 */ + SiS_SetReg(P2_3ce,i,SiS_GetReg(SiS_Pr->SiS_P3ce,i)); + } + SiS_SetReg(P2_3c4,0x05,0x86); + SiS_SetReg(P2_3c4,0x06,SiS_GetReg(SiS_Pr->SiS_P3c4,0x06)); /* SR06 */ + SiS_SetReg(P2_3c4,0x21,SiS_GetReg(SiS_Pr->SiS_P3c4,0x21)); /* SR21 */ + SiS_SetRegByte(P2_3c2,SiS_GetRegByte(SiS_Pr->SiS_P3cc)); /* MISC */ + SiS_SetReg(P2_3c4,0x05,0x00); +#endif +} +#endif + +/*********************************************/ +/* LOAD DAC */ +/*********************************************/ + +static void +SiS_WriteDAC(struct SiS_Private *SiS_Pr, SISIOADDRESS DACData, unsigned short shiftflag, + unsigned short dl, unsigned short ah, unsigned short al, unsigned short dh) +{ + unsigned short d1, d2, d3; + + switch(dl) { + case 0: d1 = dh; d2 = ah; d3 = al; break; + case 1: d1 = ah; d2 = al; d3 = dh; break; + default: d1 = al; d2 = dh; d3 = ah; + } + SiS_SetRegByte(DACData, (d1 << shiftflag)); + SiS_SetRegByte(DACData, (d2 << shiftflag)); + SiS_SetRegByte(DACData, (d3 << shiftflag)); +} + +void +SiS_LoadDAC(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short data, data2, time, i, j, k, m, n, o; + unsigned short si, di, bx, sf; + SISIOADDRESS DACAddr, DACData; + const unsigned char *table = NULL; + + data = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex) & DACInfoFlag; + + j = time = 64; + if(data == 0x00) table = SiS_MDA_DAC; + else if(data == 0x08) table = SiS_CGA_DAC; + else if(data == 0x10) table = SiS_EGA_DAC; + else if(data == 0x18) { + j = 16; + time = 256; + table = SiS_VGA_DAC; + } + + if( ( (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) && /* 301B-DH LCD */ + (SiS_Pr->SiS_VBType & VB_NoLCD) ) || + (SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) || /* LCDA */ + (!(SiS_Pr->SiS_SetFlag & ProgrammingCRT2)) ) { /* Programming CRT1 */ + SiS_SetRegByte(SiS_Pr->SiS_P3c6,0xFF); + DACAddr = SiS_Pr->SiS_P3c8; + DACData = SiS_Pr->SiS_P3c9; + sf = 0; + } else { + DACAddr = SiS_Pr->SiS_Part5Port; + DACData = SiS_Pr->SiS_Part5Port + 1; + sf = 2; + } + + SiS_SetRegByte(DACAddr,0x00); + + for(i = 0; i < j; i++) { + data = table[i]; + for(k = 0; k < 3; k++) { + data2 = 0; + if(data & 0x01) data2 += 0x2A; + if(data & 0x02) data2 += 0x15; + SiS_SetRegByte(DACData, (data2 << sf)); + data >>= 2; + } + } + + if(time == 256) { + for(i = 16; i < 32; i++) { + data = table[i] << sf; + for(k = 0; k < 3; k++) SiS_SetRegByte(DACData, data); + } + si = 32; + for(m = 0; m < 9; m++) { + di = si; + bx = si + 4; + for(n = 0; n < 3; n++) { + for(o = 0; o < 5; o++) { + SiS_WriteDAC(SiS_Pr, DACData, sf, n, table[di], table[bx], table[si]); + si++; + } + si -= 2; + for(o = 0; o < 3; o++) { + SiS_WriteDAC(SiS_Pr, DACData, sf, n, table[di], table[si], table[bx]); + si--; + } + } /* for n < 3 */ + si += 5; + } /* for m < 9 */ + } +} + +/*********************************************/ +/* SET CRT1 REGISTER GROUP */ +/*********************************************/ + +static void +SiS_SetCRT1Group(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short StandTableIndex, RefreshRateTableIndex; + + SiS_Pr->SiS_CRT1Mode = ModeNo; + + StandTableIndex = SiS_GetModePtr(SiS_Pr, ModeNo, ModeIdIndex); + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + if(SiS_Pr->SiS_VBInfo & (SetSimuScanMode | SwitchCRT2)) { + SiS_DisableBridge(SiS_Pr); + } + } + + SiS_ResetSegmentRegisters(SiS_Pr); + + SiS_SetSeqRegs(SiS_Pr, StandTableIndex); + SiS_SetMiscRegs(SiS_Pr, StandTableIndex); + SiS_SetCRTCRegs(SiS_Pr, StandTableIndex); + SiS_SetATTRegs(SiS_Pr, StandTableIndex); + SiS_SetGRCRegs(SiS_Pr, StandTableIndex); + SiS_ClearExt1Regs(SiS_Pr, ModeNo); + SiS_ResetCRT1VCLK(SiS_Pr); + + SiS_Pr->SiS_SelectCRT2Rate = 0; + SiS_Pr->SiS_SetFlag &= (~ProgrammingCRT2); + + if(SiS_Pr->SiS_VBInfo & SetSimuScanMode) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + SiS_Pr->SiS_SetFlag |= ProgrammingCRT2; + } + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_Pr->SiS_SetFlag |= ProgrammingCRT2; + } + + RefreshRateTableIndex = SiS_GetRatePtr(SiS_Pr, ModeNo, ModeIdIndex); + + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + SiS_Pr->SiS_SetFlag &= ~ProgrammingCRT2; + } + + if(RefreshRateTableIndex != 0xFFFF) { + SiS_SetCRT1Sync(SiS_Pr, RefreshRateTableIndex); + SiS_SetCRT1CRTC(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + SiS_SetCRT1Offset(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + SiS_SetCRT1VCLK(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + + switch(SiS_Pr->ChipType) { +#ifdef CONFIG_FB_SIS_300 + case SIS_300: + SiS_SetCRT1FIFO_300(SiS_Pr, ModeNo, RefreshRateTableIndex); + break; + case SIS_540: + case SIS_630: + case SIS_730: + SiS_SetCRT1FIFO_630(SiS_Pr, ModeNo, RefreshRateTableIndex); + break; +#endif + default: +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType == XGI_20) { + unsigned char sr2b = 0, sr2c = 0; + switch(ModeNo) { + case 0x00: + case 0x01: sr2b = 0x4e; sr2c = 0xe9; break; + case 0x04: + case 0x05: + case 0x0d: sr2b = 0x1b; sr2c = 0xe3; break; + } + if(sr2b) { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2b,sr2b); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x2c,sr2c); + SiS_SetRegByte(SiS_Pr->SiS_P3c2,(SiS_GetRegByte(SiS_Pr->SiS_P3cc) | 0x0c)); + } + } + SiS_SetCRT1FIFO_310(SiS_Pr, ModeNo, ModeIdIndex); +#endif + break; + } + + SiS_SetCRT1ModeRegs(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType == XGI_40) { + SiS_SetupDualChip(SiS_Pr); + } +#endif + + SiS_LoadDAC(SiS_Pr, ModeNo, ModeIdIndex); + + if(SiS_Pr->SiS_flag_clearbuffer) { + SiS_ClearBuffer(SiS_Pr, ModeNo); + } + + if(!(SiS_Pr->SiS_VBInfo & (SetSimuScanMode | SwitchCRT2 | SetCRT2ToLCDA))) { + SiS_WaitRetrace1(SiS_Pr); + SiS_DisplayOn(SiS_Pr); + } +} + +/*********************************************/ +/* HELPER: VIDEO BRIDGE PROG CLK */ +/*********************************************/ + +static void +SiS_InitVB(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + + SiS_Pr->Init_P4_0E = 0; + if(SiS_Pr->SiS_ROMNew) { + SiS_Pr->Init_P4_0E = ROMAddr[0x82]; + } else if(SiS_Pr->ChipType >= XGI_40) { + if(SiS_Pr->SiS_XGIROM) { + SiS_Pr->Init_P4_0E = ROMAddr[0x80]; + } + } +} + +static void +SiS_ResetVB(struct SiS_Private *SiS_Pr) +{ +#ifdef CONFIG_FB_SIS_315 + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short temp; + + /* VB programming clock */ + if(SiS_Pr->SiS_UseROM) { + if(SiS_Pr->ChipType < SIS_330) { + temp = ROMAddr[VB310Data_1_2_Offset] | 0x40; + if(SiS_Pr->SiS_ROMNew) temp = ROMAddr[0x80] | 0x40; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x02,temp); + } else if(SiS_Pr->ChipType >= SIS_661 && SiS_Pr->ChipType < XGI_20) { + temp = ROMAddr[0x7e] | 0x40; + if(SiS_Pr->SiS_ROMNew) temp = ROMAddr[0x80] | 0x40; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x02,temp); + } + } else if(SiS_Pr->ChipType >= XGI_40) { + temp = 0x40; + if(SiS_Pr->SiS_XGIROM) temp |= ROMAddr[0x7e]; + /* Can we do this on any chipset? */ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x02,temp); + } +#endif +} + +/*********************************************/ +/* HELPER: SET VIDEO/CAPTURE REGISTERS */ +/*********************************************/ + +static void +SiS_StrangeStuff(struct SiS_Private *SiS_Pr) +{ + /* SiS65x and XGI set up some sort of "lock mode" for text + * which locks CRT2 in some way to CRT1 timing. Disable + * this here. + */ +#ifdef CONFIG_FB_SIS_315 + if((IS_SIS651) || (IS_SISM650) || + SiS_Pr->ChipType == SIS_340 || + SiS_Pr->ChipType == XGI_40) { + SiS_SetReg(SiS_Pr->SiS_VidCapt, 0x3f, 0x00); /* Fiddle with capture regs */ + SiS_SetReg(SiS_Pr->SiS_VidCapt, 0x00, 0x00); + SiS_SetReg(SiS_Pr->SiS_VidPlay, 0x00, 0x86); /* (BIOS does NOT unlock) */ + SiS_SetRegAND(SiS_Pr->SiS_VidPlay, 0x30, 0xfe); /* Fiddle with video regs */ + SiS_SetRegAND(SiS_Pr->SiS_VidPlay, 0x3f, 0xef); + } + /* !!! This does not support modes < 0x13 !!! */ +#endif +} + +/*********************************************/ +/* HELPER: SET AGP TIMING FOR SiS760 */ +/*********************************************/ + +static void +SiS_Handle760(struct SiS_Private *SiS_Pr) +{ +#ifdef CONFIG_FB_SIS_315 + unsigned int somebase; + unsigned char temp1, temp2, temp3; + + if( (SiS_Pr->ChipType != SIS_760) || + ((SiS_GetReg(SiS_Pr->SiS_P3d4, 0x5c) & 0xf8) != 0x80) || + (!(SiS_Pr->SiS_SysFlags & SF_760LFB)) || + (!(SiS_Pr->SiS_SysFlags & SF_760UMA)) ) + return; + + somebase = sisfb_read_mio_pci_word(SiS_Pr, 0x74); + somebase &= 0xffff; + + if(somebase == 0) return; + + temp3 = SiS_GetRegByte((somebase + 0x85)) & 0xb7; + + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) & 0x40) { + temp1 = 0x21; + temp2 = 0x03; + temp3 |= 0x08; + } else { + temp1 = 0x25; + temp2 = 0x0b; + } + + sisfb_write_nbridge_pci_byte(SiS_Pr, 0x7e, temp1); + sisfb_write_nbridge_pci_byte(SiS_Pr, 0x8d, temp2); + + SiS_SetRegByte((somebase + 0x85), temp3); +#endif +} + +/*********************************************/ +/* SiSSetMode() */ +/*********************************************/ + +bool +SiSSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + SISIOADDRESS BaseAddr = SiS_Pr->IOAddress; + unsigned short RealModeNo, ModeIdIndex; + unsigned char backupreg = 0; + unsigned short KeepLockReg; + + SiS_Pr->UseCustomMode = false; + SiS_Pr->CRT1UsesCustomMode = false; + + SiS_Pr->SiS_flag_clearbuffer = 0; + + if(SiS_Pr->UseCustomMode) { + ModeNo = 0xfe; + } else { + if(!(ModeNo & 0x80)) SiS_Pr->SiS_flag_clearbuffer = 1; + ModeNo &= 0x7f; + } + + /* Don't use FSTN mode for CRT1 */ + RealModeNo = ModeNo; + if(ModeNo == 0x5b) ModeNo = 0x56; + + SiSInitPtr(SiS_Pr); + SiSRegInit(SiS_Pr, BaseAddr); + SiS_GetSysFlags(SiS_Pr); + + SiS_Pr->SiS_VGAINFO = 0x11; + + KeepLockReg = SiS_GetReg(SiS_Pr->SiS_P3c4,0x05); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x05,0x86); + + SiSInitPCIetc(SiS_Pr); + SiSSetLVDSetc(SiS_Pr); + SiSDetermineROMUsage(SiS_Pr); + + SiS_UnLockCRT2(SiS_Pr); + + if(!SiS_Pr->UseCustomMode) { + if(!(SiS_SearchModeID(SiS_Pr, &ModeNo, &ModeIdIndex))) return false; + } else { + ModeIdIndex = 0; + } + + SiS_GetVBType(SiS_Pr); + + /* Init/restore some VB registers */ + SiS_InitVB(SiS_Pr); + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(SiS_Pr->ChipType >= SIS_315H) { + SiS_ResetVB(SiS_Pr); + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x32,0x10); + SiS_SetRegOR(SiS_Pr->SiS_Part2Port,0x00,0x0c); + backupreg = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + } else { + backupreg = SiS_GetReg(SiS_Pr->SiS_P3d4,0x35); + } + } + + /* Get VB information (connectors, connected devices) */ + SiS_GetVBInfo(SiS_Pr, ModeNo, ModeIdIndex, (SiS_Pr->UseCustomMode) ? 0 : 1); + SiS_SetYPbPr(SiS_Pr); + SiS_SetTVMode(SiS_Pr, ModeNo, ModeIdIndex); + SiS_GetLCDResInfo(SiS_Pr, ModeNo, ModeIdIndex); + SiS_SetLowModeTest(SiS_Pr, ModeNo); + + /* Check memory size (kernel framebuffer driver only) */ + if(!SiS_CheckMemorySize(SiS_Pr, ModeNo, ModeIdIndex)) { + return false; + } + + SiS_OpenCRTC(SiS_Pr); + + if(SiS_Pr->UseCustomMode) { + SiS_Pr->CRT1UsesCustomMode = true; + SiS_Pr->CSRClock_CRT1 = SiS_Pr->CSRClock; + SiS_Pr->CModeFlag_CRT1 = SiS_Pr->CModeFlag; + } else { + SiS_Pr->CRT1UsesCustomMode = false; + } + + /* Set mode on CRT1 */ + if( (SiS_Pr->SiS_VBInfo & (SetSimuScanMode | SetCRT2ToLCDA)) || + (!(SiS_Pr->SiS_VBInfo & SwitchCRT2)) ) { + SiS_SetCRT1Group(SiS_Pr, ModeNo, ModeIdIndex); + } + + /* Set mode on CRT2 */ + if(SiS_Pr->SiS_VBInfo & (SetSimuScanMode | SwitchCRT2 | SetCRT2ToLCDA)) { + if( (SiS_Pr->SiS_VBType & VB_SISVB) || + (SiS_Pr->SiS_IF_DEF_LVDS == 1) || + (SiS_Pr->SiS_IF_DEF_CH70xx != 0) || + (SiS_Pr->SiS_IF_DEF_TRUMPION != 0) ) { + SiS_SetCRT2Group(SiS_Pr, RealModeNo); + } + } + + SiS_HandleCRT1(SiS_Pr); + + SiS_StrangeStuff(SiS_Pr); + + SiS_DisplayOn(SiS_Pr); + SiS_SetRegByte(SiS_Pr->SiS_P3c6,0xFF); + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + if(!(SiS_IsDualEdge(SiS_Pr))) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x13,0xfb); + } + } + } +#endif + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(SiS_Pr->ChipType >= SIS_315H) { +#ifdef CONFIG_FB_SIS_315 + if(!SiS_Pr->SiS_ROMNew) { + if(SiS_IsVAMode(SiS_Pr)) { + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x35,0x01); + } else { + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x35,0xFE); + } + } + + SiS_SetReg(SiS_Pr->SiS_P3d4,0x38,backupreg); + + if((IS_SIS650) && (SiS_GetReg(SiS_Pr->SiS_P3d4,0x30) & 0xfc)) { + if((ModeNo == 0x03) || (ModeNo == 0x10)) { + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x51,0x80); + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x56,0x08); + } + } + + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x30) & SetCRT2ToLCD) { + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x38,0xfc); + } +#endif + } else if((SiS_Pr->ChipType == SIS_630) || + (SiS_Pr->ChipType == SIS_730)) { + SiS_SetReg(SiS_Pr->SiS_P3d4,0x35,backupreg); + } + } + + SiS_CloseCRTC(SiS_Pr); + + SiS_Handle760(SiS_Pr); + + /* We never lock registers in XF86 */ + if(KeepLockReg != 0xA1) SiS_SetReg(SiS_Pr->SiS_P3c4,0x05,0x00); + + return true; +} + +#ifndef GETBITSTR +#define GENBITSMASK(mask) GENMASK(1?mask,0?mask) +#define GETBITS(var,mask) (((var) & GENBITSMASK(mask)) >> (0?mask)) +#define GETBITSTR(val,from,to) ((GETBITS(val,from)) << (0?to)) +#endif + +void +SiS_CalcCRRegisters(struct SiS_Private *SiS_Pr, int depth) +{ + int x = 1; /* Fix sync */ + + SiS_Pr->CCRT1CRTC[0] = ((SiS_Pr->CHTotal >> 3) - 5) & 0xff; /* CR0 */ + SiS_Pr->CCRT1CRTC[1] = (SiS_Pr->CHDisplay >> 3) - 1; /* CR1 */ + SiS_Pr->CCRT1CRTC[2] = (SiS_Pr->CHBlankStart >> 3) - 1; /* CR2 */ + SiS_Pr->CCRT1CRTC[3] = (((SiS_Pr->CHBlankEnd >> 3) - 1) & 0x1F) | 0x80; /* CR3 */ + SiS_Pr->CCRT1CRTC[4] = (SiS_Pr->CHSyncStart >> 3) + 3; /* CR4 */ + SiS_Pr->CCRT1CRTC[5] = ((((SiS_Pr->CHBlankEnd >> 3) - 1) & 0x20) << 2) | /* CR5 */ + (((SiS_Pr->CHSyncEnd >> 3) + 3) & 0x1F); + + SiS_Pr->CCRT1CRTC[6] = (SiS_Pr->CVTotal - 2) & 0xFF; /* CR6 */ + SiS_Pr->CCRT1CRTC[7] = (((SiS_Pr->CVTotal - 2) & 0x100) >> 8) /* CR7 */ + | (((SiS_Pr->CVDisplay - 1) & 0x100) >> 7) + | (((SiS_Pr->CVSyncStart - x) & 0x100) >> 6) + | (((SiS_Pr->CVBlankStart- 1) & 0x100) >> 5) + | 0x10 + | (((SiS_Pr->CVTotal - 2) & 0x200) >> 4) + | (((SiS_Pr->CVDisplay - 1) & 0x200) >> 3) + | (((SiS_Pr->CVSyncStart - x) & 0x200) >> 2); + + SiS_Pr->CCRT1CRTC[16] = ((((SiS_Pr->CVBlankStart - 1) & 0x200) >> 4) >> 5); /* CR9 */ + + if(depth != 8) { + if(SiS_Pr->CHDisplay >= 1600) SiS_Pr->CCRT1CRTC[16] |= 0x60; /* SRE */ + else if(SiS_Pr->CHDisplay >= 640) SiS_Pr->CCRT1CRTC[16] |= 0x40; + } + + SiS_Pr->CCRT1CRTC[8] = (SiS_Pr->CVSyncStart - x) & 0xFF; /* CR10 */ + SiS_Pr->CCRT1CRTC[9] = ((SiS_Pr->CVSyncEnd - x) & 0x0F) | 0x80; /* CR11 */ + SiS_Pr->CCRT1CRTC[10] = (SiS_Pr->CVDisplay - 1) & 0xFF; /* CR12 */ + SiS_Pr->CCRT1CRTC[11] = (SiS_Pr->CVBlankStart - 1) & 0xFF; /* CR15 */ + SiS_Pr->CCRT1CRTC[12] = (SiS_Pr->CVBlankEnd - 1) & 0xFF; /* CR16 */ + + SiS_Pr->CCRT1CRTC[13] = /* SRA */ + GETBITSTR((SiS_Pr->CVTotal -2), 10:10, 0:0) | + GETBITSTR((SiS_Pr->CVDisplay -1), 10:10, 1:1) | + GETBITSTR((SiS_Pr->CVBlankStart-1), 10:10, 2:2) | + GETBITSTR((SiS_Pr->CVSyncStart -x), 10:10, 3:3) | + GETBITSTR((SiS_Pr->CVBlankEnd -1), 8:8, 4:4) | + GETBITSTR((SiS_Pr->CVSyncEnd ), 4:4, 5:5) ; + + SiS_Pr->CCRT1CRTC[14] = /* SRB */ + GETBITSTR((SiS_Pr->CHTotal >> 3) - 5, 9:8, 1:0) | + GETBITSTR((SiS_Pr->CHDisplay >> 3) - 1, 9:8, 3:2) | + GETBITSTR((SiS_Pr->CHBlankStart >> 3) - 1, 9:8, 5:4) | + GETBITSTR((SiS_Pr->CHSyncStart >> 3) + 3, 9:8, 7:6) ; + + + SiS_Pr->CCRT1CRTC[15] = /* SRC */ + GETBITSTR((SiS_Pr->CHBlankEnd >> 3) - 1, 7:6, 1:0) | + GETBITSTR((SiS_Pr->CHSyncEnd >> 3) + 3, 5:5, 2:2) ; +} + +void +SiS_CalcLCDACRT1Timing(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex) +{ + unsigned short modeflag, tempax, tempbx = 0, remaining = 0; + unsigned short VGAHDE = SiS_Pr->SiS_VGAHDE; + int i, j; + + /* 1:1 data: use data set by setcrt1crtc() */ + if(SiS_Pr->SiS_LCDInfo & LCDPass11) return; + + modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + + if(modeflag & HalfDCLK) VGAHDE >>= 1; + + SiS_Pr->CHDisplay = VGAHDE; + SiS_Pr->CHBlankStart = VGAHDE; + + SiS_Pr->CVDisplay = SiS_Pr->SiS_VGAVDE; + SiS_Pr->CVBlankStart = SiS_Pr->SiS_VGAVDE; + + if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + tempbx = SiS_Pr->SiS_VGAHT; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempbx = SiS_Pr->PanelHT; + } + if(modeflag & HalfDCLK) tempbx >>= 1; + remaining = tempbx % 8; +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + /* OK for LCDA, LVDS */ + tempbx = SiS_Pr->PanelHT - SiS_Pr->PanelXRes; + tempax = SiS_Pr->SiS_VGAHDE; /* not /2 ! */ + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempax = SiS_Pr->PanelXRes; + } + tempbx += tempax; + if(modeflag & HalfDCLK) tempbx -= VGAHDE; +#endif + } + SiS_Pr->CHTotal = SiS_Pr->CHBlankEnd = tempbx; + + if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_VGAHDE == SiS_Pr->PanelXRes) { + SiS_Pr->CHSyncStart = SiS_Pr->SiS_VGAHDE + ((SiS_Pr->PanelHRS + 1) & ~1); + SiS_Pr->CHSyncEnd = SiS_Pr->CHSyncStart + SiS_Pr->PanelHRE; + if(modeflag & HalfDCLK) { + SiS_Pr->CHSyncStart >>= 1; + SiS_Pr->CHSyncEnd >>= 1; + } + } else if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempax = (SiS_Pr->PanelXRes - SiS_Pr->SiS_VGAHDE) >> 1; + tempbx = (SiS_Pr->PanelHRS + 1) & ~1; + if(modeflag & HalfDCLK) { + tempax >>= 1; + tempbx >>= 1; + } + SiS_Pr->CHSyncStart = (VGAHDE + tempax + tempbx + 7) & ~7; + tempax = SiS_Pr->PanelHRE + 7; + if(modeflag & HalfDCLK) tempax >>= 1; + SiS_Pr->CHSyncEnd = (SiS_Pr->CHSyncStart + tempax) & ~7; + } else { + SiS_Pr->CHSyncStart = SiS_Pr->SiS_VGAHDE; + if(modeflag & HalfDCLK) { + SiS_Pr->CHSyncStart >>= 1; + tempax = ((SiS_Pr->CHTotal - SiS_Pr->CHSyncStart) / 3) << 1; + SiS_Pr->CHSyncEnd = SiS_Pr->CHSyncStart + tempax; + } else { + SiS_Pr->CHSyncEnd = (SiS_Pr->CHSyncStart + (SiS_Pr->CHTotal / 10) + 7) & ~7; + SiS_Pr->CHSyncStart += 8; + } + } +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + tempax = VGAHDE; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempbx = SiS_Pr->PanelXRes; + if(modeflag & HalfDCLK) tempbx >>= 1; + tempax += ((tempbx - tempax) >> 1); + } + tempax += SiS_Pr->PanelHRS; + SiS_Pr->CHSyncStart = tempax; + tempax += SiS_Pr->PanelHRE; + SiS_Pr->CHSyncEnd = tempax; +#endif + } + + tempbx = SiS_Pr->PanelVT - SiS_Pr->PanelYRes; + tempax = SiS_Pr->SiS_VGAVDE; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempax = SiS_Pr->PanelYRes; + } else if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + /* Stupid hack for 640x400/320x200 */ + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if((tempax + tempbx) == 438) tempbx += 16; + } else if((SiS_Pr->SiS_LCDResInfo == Panel_800x600) || + (SiS_Pr->SiS_LCDResInfo == Panel_1024x600)) { + tempax = 0; + tempbx = SiS_Pr->SiS_VGAVT; + } +#endif + } + SiS_Pr->CVTotal = SiS_Pr->CVBlankEnd = tempbx + tempax; + + tempax = SiS_Pr->SiS_VGAVDE; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempax += (SiS_Pr->PanelYRes - tempax) >> 1; + } + tempax += SiS_Pr->PanelVRS; + SiS_Pr->CVSyncStart = tempax; + tempax += SiS_Pr->PanelVRE; + SiS_Pr->CVSyncEnd = tempax; + if(SiS_Pr->ChipType < SIS_315H) { + SiS_Pr->CVSyncStart--; + SiS_Pr->CVSyncEnd--; + } + + SiS_CalcCRRegisters(SiS_Pr, 8); + SiS_Pr->CCRT1CRTC[15] &= ~0xF8; + SiS_Pr->CCRT1CRTC[15] |= (remaining << 4); + SiS_Pr->CCRT1CRTC[16] &= ~0xE0; + + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x11,0x7f); + + for(i = 0, j = 0; i <= 7; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3d4,j,SiS_Pr->CCRT1CRTC[i]); + } + for(j = 0x10; i <= 10; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3d4,j,SiS_Pr->CCRT1CRTC[i]); + } + for(j = 0x15; i <= 12; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3d4,j,SiS_Pr->CCRT1CRTC[i]); + } + for(j = 0x0A; i <= 15; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_P3c4,j,SiS_Pr->CCRT1CRTC[i]); + } + + tempax = SiS_Pr->CCRT1CRTC[16] & 0xE0; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0E,0x1F,tempax); + + tempax = (SiS_Pr->CCRT1CRTC[16] & 0x01) << 5; + if(modeflag & DoubleScanMode) tempax |= 0x80; + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x09,0x5F,tempax); + +} + +void +SiS_Generic_ConvertCRData(struct SiS_Private *SiS_Pr, unsigned char *crdata, + int xres, int yres, + struct fb_var_screeninfo *var, bool writeres +) +{ + unsigned short HRE, HBE, HRS, HBS, HDE, HT; + unsigned short VRE, VBE, VRS, VBS, VDE, VT; + unsigned char sr_data, cr_data, cr_data2; + int A, B, C, D, E, F, temp; + + sr_data = crdata[14]; + + /* Horizontal total */ + HT = crdata[0] | ((unsigned short)(sr_data & 0x03) << 8); + A = HT + 5; + + /* Horizontal display enable end */ + HDE = crdata[1] | ((unsigned short)(sr_data & 0x0C) << 6); + E = HDE + 1; + + /* Horizontal retrace (=sync) start */ + HRS = crdata[4] | ((unsigned short)(sr_data & 0xC0) << 2); + F = HRS - E - 3; + + /* Horizontal blank start */ + HBS = crdata[2] | ((unsigned short)(sr_data & 0x30) << 4); + + sr_data = crdata[15]; + cr_data = crdata[5]; + + /* Horizontal blank end */ + HBE = (crdata[3] & 0x1f) | + ((unsigned short)(cr_data & 0x80) >> 2) | + ((unsigned short)(sr_data & 0x03) << 6); + + /* Horizontal retrace (=sync) end */ + HRE = (cr_data & 0x1f) | ((sr_data & 0x04) << 3); + + temp = HBE - ((E - 1) & 255); + B = (temp > 0) ? temp : (temp + 256); + + temp = HRE - ((E + F + 3) & 63); + C = (temp > 0) ? temp : (temp + 64); + + D = B - F - C; + + if(writeres) var->xres = xres = E * 8; + var->left_margin = D * 8; + var->right_margin = F * 8; + var->hsync_len = C * 8; + + /* Vertical */ + sr_data = crdata[13]; + cr_data = crdata[7]; + + /* Vertical total */ + VT = crdata[6] | + ((unsigned short)(cr_data & 0x01) << 8) | + ((unsigned short)(cr_data & 0x20) << 4) | + ((unsigned short)(sr_data & 0x01) << 10); + A = VT + 2; + + /* Vertical display enable end */ + VDE = crdata[10] | + ((unsigned short)(cr_data & 0x02) << 7) | + ((unsigned short)(cr_data & 0x40) << 3) | + ((unsigned short)(sr_data & 0x02) << 9); + E = VDE + 1; + + /* Vertical retrace (=sync) start */ + VRS = crdata[8] | + ((unsigned short)(cr_data & 0x04) << 6) | + ((unsigned short)(cr_data & 0x80) << 2) | + ((unsigned short)(sr_data & 0x08) << 7); + F = VRS + 1 - E; + + cr_data2 = (crdata[16] & 0x01) << 5; + + /* Vertical blank start */ + VBS = crdata[11] | + ((unsigned short)(cr_data & 0x08) << 5) | + ((unsigned short)(cr_data2 & 0x20) << 4) | + ((unsigned short)(sr_data & 0x04) << 8); + + /* Vertical blank end */ + VBE = crdata[12] | ((unsigned short)(sr_data & 0x10) << 4); + temp = VBE - ((E - 1) & 511); + B = (temp > 0) ? temp : (temp + 512); + + /* Vertical retrace (=sync) end */ + VRE = (crdata[9] & 0x0f) | ((sr_data & 0x20) >> 1); + temp = VRE - ((E + F - 1) & 31); + C = (temp > 0) ? temp : (temp + 32); + + D = B - F - C; + + if(writeres) var->yres = yres = E; + var->upper_margin = D; + var->lower_margin = F; + var->vsync_len = C; + + if((xres == 320) && ((yres == 200) || (yres == 240))) { + /* Terrible hack, but correct CRTC data for + * these modes only produces a black screen... + * (HRE is 0, leading into a too large C and + * a negative D. The CRT controller does not + * seem to like correcting HRE to 50) + */ + var->left_margin = (400 - 376); + var->right_margin = (328 - 320); + var->hsync_len = (376 - 328); + + } + +} + + + + diff --git a/drivers/video/fbdev/sis/init.h b/drivers/video/fbdev/sis/init.h new file mode 100644 index 000000000000..85d6738b6c64 --- /dev/null +++ b/drivers/video/fbdev/sis/init.h @@ -0,0 +1,1541 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Data and prototypes for init.c + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +#ifndef _INIT_H_ +#define _INIT_H_ + +#include "initdef.h" + +#include "vgatypes.h" +#include "vstruct.h" +#ifdef SIS_CP +#undef SIS_CP +#endif +#include <linux/types.h> +#include <asm/io.h> +#include <linux/fb.h> +#include "sis.h" +#include <video/sisfb.h> + +/* Mode numbers */ +static const unsigned short ModeIndex_320x200[] = {0x59, 0x41, 0x00, 0x4f}; +static const unsigned short ModeIndex_320x240[] = {0x50, 0x56, 0x00, 0x53}; +static const unsigned short ModeIndex_320x240_FSTN[] = {0x5a, 0x5b, 0x00, 0x00}; /* FSTN */ +static const unsigned short ModeIndex_400x300[] = {0x51, 0x57, 0x00, 0x54}; +static const unsigned short ModeIndex_512x384[] = {0x52, 0x58, 0x00, 0x5c}; +static const unsigned short ModeIndex_640x400[] = {0x2f, 0x5d, 0x00, 0x5e}; +static const unsigned short ModeIndex_640x480[] = {0x2e, 0x44, 0x00, 0x62}; +static const unsigned short ModeIndex_720x480[] = {0x31, 0x33, 0x00, 0x35}; +static const unsigned short ModeIndex_720x576[] = {0x32, 0x34, 0x00, 0x36}; +static const unsigned short ModeIndex_768x576[] = {0x5f, 0x60, 0x00, 0x61}; +static const unsigned short ModeIndex_800x480[] = {0x70, 0x7a, 0x00, 0x76}; +static const unsigned short ModeIndex_800x600[] = {0x30, 0x47, 0x00, 0x63}; +static const unsigned short ModeIndex_848x480[] = {0x39, 0x3b, 0x00, 0x3e}; +static const unsigned short ModeIndex_856x480[] = {0x3f, 0x42, 0x00, 0x45}; +static const unsigned short ModeIndex_960x540[] = {0x1d, 0x1e, 0x00, 0x1f}; /* 315 series only */ +static const unsigned short ModeIndex_960x600[] = {0x20, 0x21, 0x00, 0x22}; /* 315 series only */ +static const unsigned short ModeIndex_1024x768[] = {0x38, 0x4a, 0x00, 0x64}; +static const unsigned short ModeIndex_1024x576[] = {0x71, 0x74, 0x00, 0x77}; +static const unsigned short ModeIndex_1024x600[] = {0x20, 0x21, 0x00, 0x22}; /* 300 series only */ +static const unsigned short ModeIndex_1280x1024[] = {0x3a, 0x4d, 0x00, 0x65}; +static const unsigned short ModeIndex_1280x960[] = {0x7c, 0x7d, 0x00, 0x7e}; +static const unsigned short ModeIndex_1152x768[] = {0x23, 0x24, 0x00, 0x25}; /* 300 series only */ +static const unsigned short ModeIndex_1152x864[] = {0x29, 0x2a, 0x00, 0x2b}; +static const unsigned short ModeIndex_300_1280x768[] = {0x55, 0x5a, 0x00, 0x5b}; +static const unsigned short ModeIndex_310_1280x768[] = {0x23, 0x24, 0x00, 0x25}; +static const unsigned short ModeIndex_1280x720[] = {0x79, 0x75, 0x00, 0x78}; +static const unsigned short ModeIndex_1280x800[] = {0x14, 0x15, 0x00, 0x16}; +static const unsigned short ModeIndex_1280x854[] = {0x1a, 0x1b, 0x00, 0x1c}; +static const unsigned short ModeIndex_1360x768[] = {0x48, 0x4b, 0x00, 0x4e}; +static const unsigned short ModeIndex_300_1360x1024[]= {0x67, 0x6f, 0x00, 0x72}; /* 300 series, BARCO only */ +static const unsigned short ModeIndex_1400x1050[] = {0x26, 0x27, 0x00, 0x28}; /* 315 series only */ +static const unsigned short ModeIndex_1680x1050[] = {0x17, 0x18, 0x00, 0x19}; /* 315 series only */ +static const unsigned short ModeIndex_1600x1200[] = {0x3c, 0x3d, 0x00, 0x66}; +static const unsigned short ModeIndex_1920x1080[] = {0x2c, 0x2d, 0x00, 0x73}; /* 315 series only */ +static const unsigned short ModeIndex_1920x1440[] = {0x68, 0x69, 0x00, 0x6b}; +static const unsigned short ModeIndex_300_2048x1536[]= {0x6c, 0x6d, 0x00, 0x00}; +static const unsigned short ModeIndex_310_2048x1536[]= {0x6c, 0x6d, 0x00, 0x6e}; + +static const unsigned char SiS_MDA_DAC[] = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15, + 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15, + 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15, + 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15, + 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F +}; + +static const unsigned char SiS_CGA_DAC[] = +{ + 0x00,0x10,0x04,0x14,0x01,0x11,0x09,0x15, + 0x00,0x10,0x04,0x14,0x01,0x11,0x09,0x15, + 0x2A,0x3A,0x2E,0x3E,0x2B,0x3B,0x2F,0x3F, + 0x2A,0x3A,0x2E,0x3E,0x2B,0x3B,0x2F,0x3F, + 0x00,0x10,0x04,0x14,0x01,0x11,0x09,0x15, + 0x00,0x10,0x04,0x14,0x01,0x11,0x09,0x15, + 0x2A,0x3A,0x2E,0x3E,0x2B,0x3B,0x2F,0x3F, + 0x2A,0x3A,0x2E,0x3E,0x2B,0x3B,0x2F,0x3F +}; + +static const unsigned char SiS_EGA_DAC[] = +{ + 0x00,0x10,0x04,0x14,0x01,0x11,0x05,0x15, + 0x20,0x30,0x24,0x34,0x21,0x31,0x25,0x35, + 0x08,0x18,0x0C,0x1C,0x09,0x19,0x0D,0x1D, + 0x28,0x38,0x2C,0x3C,0x29,0x39,0x2D,0x3D, + 0x02,0x12,0x06,0x16,0x03,0x13,0x07,0x17, + 0x22,0x32,0x26,0x36,0x23,0x33,0x27,0x37, + 0x0A,0x1A,0x0E,0x1E,0x0B,0x1B,0x0F,0x1F, + 0x2A,0x3A,0x2E,0x3E,0x2B,0x3B,0x2F,0x3F +}; + +static const unsigned char SiS_VGA_DAC[] = +{ + 0x00,0x10,0x04,0x14,0x01,0x11,0x09,0x15, + 0x2A,0x3A,0x2E,0x3E,0x2B,0x3B,0x2F,0x3F, + 0x00,0x05,0x08,0x0B,0x0E,0x11,0x14,0x18, + 0x1C,0x20,0x24,0x28,0x2D,0x32,0x38,0x3F, + 0x00,0x10,0x1F,0x2F,0x3F,0x1F,0x27,0x2F, + 0x37,0x3F,0x2D,0x31,0x36,0x3A,0x3F,0x00, + 0x07,0x0E,0x15,0x1C,0x0E,0x11,0x15,0x18, + 0x1C,0x14,0x16,0x18,0x1A,0x1C,0x00,0x04, + 0x08,0x0C,0x10,0x08,0x0A,0x0C,0x0E,0x10, + 0x0B,0x0C,0x0D,0x0F,0x10 +}; + +static const struct SiS_St SiS_SModeIDTable[] = +{ + {0x01,0x9208,0x01,0x00,0x00,0x00,0x01,0x00,0x40}, + {0x01,0x1210,0x14,0x01,0x01,0x00,0x01,0x00,0x40}, + {0x01,0x1010,0x17,0x02,0x02,0x00,0x01,0x01,0x40}, + {0x03,0x8208,0x03,0x00,0x00,0x00,0x01,0x02,0x40}, + {0x03,0x0210,0x16,0x01,0x01,0x00,0x01,0x02,0x40}, + {0x03,0x0010,0x18,0x02,0x02,0x00,0x01,0x03,0x40}, + {0x05,0x9209,0x05,0x00,0x00,0x00,0x00,0x04,0x40}, + {0x06,0x8209,0x06,0x00,0x00,0x00,0x00,0x05,0x40}, + {0x07,0x0000,0x07,0x03,0x03,0x00,0x01,0x03,0x40}, + {0x07,0x0000,0x19,0x02,0x02,0x00,0x01,0x03,0x40}, + {0x0d,0x920a,0x0d,0x00,0x00,0x00,0x00,0x04,0x40}, + {0x0e,0x820a,0x0e,0x00,0x00,0x00,0x00,0x05,0x40}, + {0x0f,0x0202,0x11,0x01,0x01,0x00,0x00,0x05,0x40}, + {0x10,0x0212,0x12,0x01,0x01,0x00,0x00,0x05,0x40}, + {0x11,0x0212,0x1a,0x04,0x04,0x00,0x00,0x05,0x40}, + {0x12,0x0212,0x1b,0x04,0x04,0x00,0x00,0x05,0x40}, + {0x13,0x021b,0x1c,0x00,0x00,0x00,0x00,0x04,0x40}, + {0x12,0x0010,0x18,0x02,0x02,0x00,0x00,0x05,0x40}, + {0x12,0x0210,0x18,0x01,0x01,0x00,0x00,0x05,0x40}, + {0xff,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00} +}; + +static const struct SiS_StResInfo_S SiS_StResInfo[]= +{ + { 640,400}, + { 640,350}, + { 720,400}, + { 720,350}, + { 640,480} +}; + +static const struct SiS_ModeResInfo_S SiS_ModeResInfo[] = +{ + { 320, 200, 8, 8}, /* 0x00 */ + { 320, 240, 8, 8}, /* 0x01 */ + { 320, 400, 8, 8}, /* 0x02 */ + { 400, 300, 8, 8}, /* 0x03 */ + { 512, 384, 8, 8}, /* 0x04 */ + { 640, 400, 8,16}, /* 0x05 */ + { 640, 480, 8,16}, /* 0x06 */ + { 800, 600, 8,16}, /* 0x07 */ + { 1024, 768, 8,16}, /* 0x08 */ + { 1280,1024, 8,16}, /* 0x09 */ + { 1600,1200, 8,16}, /* 0x0a */ + { 1920,1440, 8,16}, /* 0x0b */ + { 2048,1536, 8,16}, /* 0x0c */ + { 720, 480, 8,16}, /* 0x0d */ + { 720, 576, 8,16}, /* 0x0e */ + { 1280, 960, 8,16}, /* 0x0f */ + { 800, 480, 8,16}, /* 0x10 */ + { 1024, 576, 8,16}, /* 0x11 */ + { 1280, 720, 8,16}, /* 0x12 */ + { 856, 480, 8,16}, /* 0x13 */ + { 1280, 768, 8,16}, /* 0x14 */ + { 1400,1050, 8,16}, /* 0x15 */ + { 1152, 864, 8,16}, /* 0x16 */ + { 848, 480, 8,16}, /* 0x17 */ + { 1360, 768, 8,16}, /* 0x18 */ + { 1024, 600, 8,16}, /* 0x19 */ + { 1152, 768, 8,16}, /* 0x1a */ + { 768, 576, 8,16}, /* 0x1b */ + { 1360,1024, 8,16}, /* 0x1c */ + { 1680,1050, 8,16}, /* 0x1d */ + { 1280, 800, 8,16}, /* 0x1e */ + { 1920,1080, 8,16}, /* 0x1f */ + { 960, 540, 8,16}, /* 0x20 */ + { 960, 600, 8,16}, /* 0x21 */ + { 1280, 854, 8,16} /* 0x22 */ +}; + +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) +static const struct SiS_StandTable_S SiS_StandTable[]= +{ +/* 0x00: MD_0_200 */ + { + 0x28,0x18,0x08,0x0800, + {0x09,0x03,0x00,0x02}, + 0x63, + {0x2d,0x27,0x28,0x90,0x2b,0xa0,0xbf,0x1f, + 0x00,0xc7,0x06,0x07,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x14,0x1f,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x01: MD_1_200 */ + { + 0x28,0x18,0x08,0x0800, + {0x09,0x03,0x00,0x02}, + 0x63, + {0x2d,0x27,0x28,0x90,0x2b,0xa0,0xbf,0x1f, + 0x00,0xc7,0x06,0x07,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x14,0x1f,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x02: MD_2_200 */ + { + 0x50,0x18,0x08,0x1000, + {0x01,0x03,0x00,0x02}, + 0x63, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x00,0xc7,0x06,0x07,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x28,0x1f,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x03: MD_3_200 - mode 0x03 - 0 */ + { + 0x50,0x18,0x08,0x1000, + {0x01,0x03,0x00,0x02}, + 0x63, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x00,0xc7,0x06,0x07,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x28,0x1f,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x04: MD_4 */ + { + 0x28,0x18,0x08,0x4000, + {0x09,0x03,0x00,0x02}, + 0x63, + {0x2d,0x27,0x28,0x90,0x2c,0x80,0xbf,0x1f, /* 0x2c is 2b for 300 */ + 0x00,0xc1,0x00,0x00,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x14,0x00,0x96,0xb9,0xa2, + 0xff}, + {0x00,0x13,0x15,0x17,0x02,0x04,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x01,0x00,0x03,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x30,0x0f,0x00, + 0xff} + }, +/* 0x05: MD_5 */ + { + 0x28,0x18,0x08,0x4000, + {0x09,0x03,0x00,0x02}, + 0x63, + {0x2d,0x27,0x28,0x90,0x2c,0x80,0xbf,0x1f, /* 0x2c is 2b for 300 */ + 0x00,0xc1,0x00,0x00,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x14,0x00,0x96,0xb9,0xa2, + 0xff}, + {0x00,0x13,0x15,0x17,0x02,0x04,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x01,0x00,0x03,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x30,0x0f,0x00, + 0xff} + }, +/* 0x06: MD_6 */ + { + 0x50,0x18,0x08,0x4000, + {0x01,0x01,0x00,0x06}, + 0x63, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, /* 55,81 is 54,80 for 300 */ + 0x00,0xc1,0x00,0x00,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x28,0x00,0x96,0xb9,0xc2, + 0xff}, + {0x00,0x17,0x17,0x17,0x17,0x17,0x17,0x17, + 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17, + 0x01,0x00,0x01,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x00, + 0xff} + }, +/* 0x07: MD_7 */ + { + 0x50,0x18,0x0e,0x1000, + {0x00,0x03,0x00,0x03}, + 0xa6, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x00,0x4d,0x0b,0x0c,0x00,0x00,0x00,0x00, + 0x83,0x85,0x5d,0x28,0x0d,0x63,0xba,0xa3, + 0xff}, + {0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x08, + 0x10,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x0e,0x00,0x0f,0x08}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0a,0x00, + 0xff} + }, +/* 0x08: MDA_DAC */ + { + 0x00,0x00,0x00,0x0000, + {0x00,0x00,0x00,0x15}, + 0x15, + {0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15, + 0x15,0x15,0x15,0x15,0x15,0x15,0x3f,0x3f, + 0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x00,0x00, + 0x00}, + {0x00,0x00,0x00,0x00,0x00,0x15,0x15,0x15, + 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15, + 0x15,0x15,0x15,0x15}, + {0x15,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f, + 0x3f} + }, +/* 0x09: CGA_DAC */ + { + 0x00,0x10,0x04,0x0114, + {0x11,0x09,0x15,0x00}, + 0x10, + {0x04,0x14,0x01,0x11,0x09,0x15,0x2a,0x3a, + 0x2e,0x3e,0x2b,0x3b,0x2f,0x3f,0x2a,0x3a, + 0x2e,0x3e,0x2b,0x3b,0x2f,0x3f,0x00,0x10, + 0x04}, + {0x14,0x01,0x11,0x09,0x15,0x00,0x10,0x04, + 0x14,0x01,0x11,0x09,0x15,0x2a,0x3a,0x2e, + 0x3e,0x2b,0x3b,0x2f}, + {0x3f,0x2a,0x3a,0x2e,0x3e,0x2b,0x3b,0x2f, + 0x3f} + }, +/* 0x0a: EGA_DAC */ + { + 0x00,0x10,0x04,0x0114, + {0x11,0x05,0x15,0x20}, + 0x30, + {0x24,0x34,0x21,0x31,0x25,0x35,0x08,0x18, + 0x0c,0x1c,0x09,0x19,0x0d,0x1d,0x28,0x38, + 0x2c,0x3c,0x29,0x39,0x2d,0x3d,0x02,0x12, + 0x06}, + {0x16,0x03,0x13,0x07,0x17,0x22,0x32,0x26, + 0x36,0x23,0x33,0x27,0x37,0x0a,0x1a,0x0e, + 0x1e,0x0b,0x1b,0x0f}, + {0x1f,0x2a,0x3a,0x2e,0x3e,0x2b,0x3b,0x2f, + 0x3f} + }, +/* 0x0b: VGA_DAC */ + { + 0x00,0x10,0x04,0x0114, + {0x11,0x09,0x15,0x2a}, + 0x3a, + {0x2e,0x3e,0x2b,0x3b,0x2f,0x3f,0x00,0x05, + 0x08,0x0b,0x0e,0x11,0x14,0x18,0x1c,0x20, + 0x24,0x28,0x2d,0x32,0x38,0x3f,0x00,0x10, + 0x1f}, + {0x2f,0x3f,0x1f,0x27,0x2f,0x37,0x3f,0x2d, + 0x31,0x36,0x3a,0x3f,0x00,0x07,0x0e,0x15, + 0x1c,0x0e,0x11,0x15}, + {0x18,0x1c,0x14,0x16,0x18,0x1a,0x1c,0x00, + 0x04} + }, +/* 0x0c */ + { + 0x08,0x0c,0x10,0x0a08, + {0x0c,0x0e,0x10,0x0b}, + 0x0c, + {0x0d,0x0f,0x10,0x10,0x01,0x08,0x00,0x00, + 0x00,0x00,0x01,0x00,0x02,0x02,0x01,0x00, + 0x04,0x04,0x01,0x00,0x05,0x02,0x05,0x00, + 0x06}, + {0x01,0x06,0x05,0x06,0x00,0x08,0x01,0x08, + 0x00,0x07,0x02,0x07,0x06,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00} + }, +/* 0x0d: MD_D */ + { + 0x28,0x18,0x08,0x2000, + {0x09,0x0f,0x00,0x06}, + 0x63, + {0x2d,0x27,0x28,0x90,0x2c,0x80,0xbf,0x1f, /* 2c is 2b for 300 */ + 0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x14,0x00,0x96,0xb9,0xe3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x01,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x0f, + 0xff} + }, +/* 0x0e: MD_E */ + { + 0x50,0x18,0x08,0x4000, + {0x01,0x0f,0x00,0x06}, + 0x63, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, /* 55,81 is 54,80 for 300 */ + 0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x28,0x00,0x96,0xb9,0xe3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x01,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x0f, + 0xff} + }, +/* 0x0f: ExtVGATable - modes > 0x13 */ + { + 0x00,0x00,0x00,0x0000, + {0x01,0x0f,0x00,0x0e}, + 0x23, + {0x5f,0x4f,0x50,0x82,0x54,0x80,0x0b,0x3e, + 0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00, + 0xea,0x8c,0xdf,0x28,0x40,0xe7,0x04,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x01,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x40,0x05,0x0f, + 0xff} + }, +/* 0x10: ROM_SAVEPTR - totally different for 300 */ + { + 0x9f,0x3b,0x00,0x00c0, + {0x00,0x00,0x00,0x00}, + 0x00, + {0x00,0x00,0x00,0x00,0x00,0x00,0xbb,0x3f, + 0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x1a,0x00,0xac,0x3e,0x00,0xc0, + 0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00} + }, +/* 0x11: MD_F */ + { + 0x50,0x18,0x0e,0x8000, + {0x01,0x0f,0x00,0x06}, + 0xa2, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, /* 55,81 is 54,80 on 300 */ + 0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00, + 0x82,0x84,0x5d,0x28,0x0f,0x63,0xba,0xe3, /* 82,84 is 83,85 on 300 */ + 0xff}, + {0x00,0x08,0x00,0x00,0x18,0x18,0x00,0x00, + 0x00,0x08,0x00,0x00,0x00,0x18,0x00,0x00, + 0x0b,0x00,0x05,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x05, + 0xff} + }, +/* 0x12: MD_10 */ + { + 0x50,0x18,0x0e,0x8000, + {0x01,0x0f,0x00,0x06}, + 0xa3, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, /* 55,81 is 54,80 on 300 */ + 0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00, + 0x82,0x84,0x5d,0x28,0x0f,0x63,0xba,0xe3, /* 82,84 is 83,85 on 300 */ + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x01,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x0f, + 0xff} + }, +/* 0x13: MD_0_350 */ + { + 0x28,0x18,0x0e,0x0800, + {0x09,0x03,0x00,0x02}, + 0xa3, + {0x2d,0x27,0x28,0x90,0x2b,0xb1,0xbf,0x1f, /* b1 is a0 on 300 */ + 0x00,0x4d,0x0b,0x0c,0x00,0x00,0x00,0x00, + 0x83,0x85,0x5d,0x14,0x1f,0x63,0xba,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x14: MD_1_350 */ + { + 0x28,0x18,0x0e,0x0800, + {0x09,0x03,0x00,0x02}, + 0xa3, + {0x2d,0x27,0x28,0x90,0x2b,0xa0,0xbf,0x1f, + 0x00,0x4d,0x0b,0x0c,0x00,0x00,0x00,0x00, + 0x83,0x85,0x5d,0x14,0x1f,0x63,0xba,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x15: MD_2_350 */ + { + 0x50,0x18,0x0e,0x1000, + {0x01,0x03,0x00,0x02}, + 0xa3, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x00,0x4d,0x0b,0x0c,0x00,0x00,0x00,0x00, + 0x83,0x85,0x5d,0x28,0x1f,0x63,0xba,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x16: MD_3_350 - mode 0x03 - 1 */ + { + 0x50,0x18,0x0e,0x1000, + {0x01,0x03,0x00,0x02}, + 0xa3, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x00,0x4d,0x0b,0x0c,0x00,0x00,0x00,0x00, + 0x83,0x85,0x5d,0x28,0x1f,0x63,0xba,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x08,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x17: MD_0_1_400 */ + { + 0x28,0x18,0x10,0x0800, + {0x08,0x03,0x00,0x02}, + 0x67, + {0x2d,0x27,0x28,0x90,0x2b,0xb1,0xbf,0x1f, /* b1 is a0 on 300 */ + 0x00,0x4f,0x0d,0x0e,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x14,0x1f,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x0c,0x00,0x0f,0x08}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x18: MD_2_3_400 - mode 0x03 - 2 */ + { + 0x50,0x18,0x10,0x1000, + {0x00,0x03,0x00,0x02}, + 0x67, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x00,0x4f,0x0d,0x0e,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x28,0x1f,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x0c,0x00,0x0f,0x08}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0e,0x00, + 0xff} + }, +/* 0x19: MD_7_400 */ + { + 0x50,0x18,0x10,0x1000, + {0x00,0x03,0x00,0x02}, + 0x66, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, + 0x00,0x4f,0x0d,0x0e,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x28,0x0f,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x08, + 0x10,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x0e,0x00,0x0f,0x08}, + {0x00,0x00,0x00,0x00,0x00,0x10,0x0a,0x00, + 0xff} + }, +/* 0x1a: MD_11 */ + { + 0x50,0x1d,0x10,0xa000, + {0x01,0x0f,0x00,0x06}, + 0xe3, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0x0b,0x3e, /* 55,81 is 54,80 on 300 */ + 0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe9,0x8b,0xdf,0x28,0x00,0xe7,0x04,0xc3, /* e9,8b is ea,8c on 300 */ + 0xff}, + {0x00,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f, + 0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f, + 0x01,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x01, + 0xff} + }, +/* 0x1b: ExtEGATable - Modes <= 0x02 */ + { + 0x50,0x1d,0x10,0xa000, + {0x01,0x0f,0x00,0x06}, + 0xe3, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0x0b,0x3e, /* 55,81 is 54,80 on 300 */ + 0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe9,0x8b,0xdf,0x28,0x00,0xe7,0x04,0xe3, /* e9,8b is ea,8c on 300 */ + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x14,0x07, + 0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x01,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x0f, + 0xff} + }, +/* 0x1c: MD_13 */ + { + 0x28,0x18,0x08,0x2000, + {0x01,0x0f,0x00,0x0e}, + 0x63, + {0x5f,0x4f,0x50,0x82,0x55,0x81,0xbf,0x1f, /* 55,81 is 54,80 on 300 */ + 0x00,0x41,0x00,0x00,0x00,0x00,0x00,0x00, + 0x9c,0x8e,0x8f,0x28,0x40,0x96,0xb9,0xa3, + 0xff}, + {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x41,0x00,0x0f,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x40,0x05,0x0f, + 0xff} + } +}; +#endif + +/**************************************************************/ +/* SIS VIDEO BRIDGE ----------------------------------------- */ +/**************************************************************/ + +static const unsigned char SiS_SoftSetting = 0x30; /* RAM setting */ + +static const unsigned char SiS_OutputSelect = 0x40; + +static const unsigned char SiS_NTSCTiming[] = { + 0x17,0x1d,0x03,0x09,0x05,0x06,0x0c,0x0c, + 0x94,0x49,0x01,0x0a,0x06,0x0d,0x04,0x0a, + 0x06,0x14,0x0d,0x04,0x0a,0x00,0x85,0x1b, + 0x0c,0x50,0x00,0x97,0x00,0xda,0x4a,0x17, + 0x7d,0x05,0x4b,0x00,0x00,0xe2,0x00,0x02, + 0x03,0x0a,0x65,0x9d,0x08,0x92,0x8f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x50, + 0x00,0x40,0x44,0x00,0xdb,0x02,0x3b,0x00 +}; + +static const unsigned char SiS_PALTiming[] = { + 0x19,0x52,0x35,0x6e,0x04,0x38,0x3d,0x70, + 0x94,0x49,0x01,0x12,0x06,0x3e,0x35,0x6d, + 0x06,0x14,0x3e,0x35,0x6d,0x00,0x45,0x2b, + 0x70,0x50,0x00,0x9b,0x00,0xd9,0x5d,0x17, + 0x7d,0x05,0x45,0x00,0x00,0xe8,0x00,0x02, + 0x0d,0x00,0x68,0xb0,0x0b,0x92,0x8f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x63, + 0x00,0x40,0x3e,0x00,0xe1,0x02,0x28,0x00 +}; + +static const unsigned char SiS_HiTVExtTiming[] = { + 0x32,0x65,0x2c,0x5f,0x08,0x31,0x3a,0x64, + 0x28,0x02,0x01,0x3d,0x06,0x3e,0x35,0x6d, + 0x06,0x14,0x3e,0x35,0x6d,0x00,0xc5,0x3f, + 0x64,0x90,0x33,0x8c,0x18,0x36,0x3e,0x13, + 0x2a,0xde,0x2a,0x44,0x40,0x2a,0x44,0x40, + 0x8e,0x8e,0x82,0x07,0x0b,0x92,0x0f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x3d, + 0x63,0x4f,0x27,0x00,0xfc,0xff,0x6a,0x00 +}; + +static const unsigned char SiS_HiTVSt1Timing[] = { + 0x32,0x65,0x2c,0x5f,0x08,0x31,0x3a,0x65, + 0x28,0x02,0x01,0x3d,0x06,0x3e,0x35,0x6d, + 0x06,0x14,0x3e,0x35,0x6d,0x00,0xc5,0x3f, + 0x65,0x90,0x7b,0xa8,0x03,0xf0,0x87,0x03, + 0x11,0x15,0x11,0xcf,0x10,0x11,0xcf,0x10, + 0x35,0x35,0x3b,0x69,0x1d,0x92,0x0f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x04,0x86, + 0xaf,0x5d,0x0e,0x00,0xfc,0xff,0x2d,0x00 +}; + +static const unsigned char SiS_HiTVSt2Timing[] = { + 0x32,0x65,0x2c,0x5f,0x08,0x31,0x3a,0x64, + 0x28,0x02,0x01,0x3d,0x06,0x3e,0x35,0x6d, + 0x06,0x14,0x3e,0x35,0x6d,0x00,0xc5,0x3f, + 0x64,0x90,0x33,0x8c,0x18,0x36,0x3e,0x13, + 0x2a,0xde,0x2a,0x44,0x40,0x2a,0x44,0x40, + 0x8e,0x8e,0x82,0x07,0x0b,0x92,0x0f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x3d, + 0x63,0x4f,0x27,0x00,0xfc,0xff,0x6a,0x00 +}; + +#if 0 +static const unsigned char SiS_HiTVTextTiming[] = { + 0x32,0x65,0x2c,0x5f,0x08,0x31,0x3a,0x65, + 0x28,0x02,0x01,0x3d,0x06,0x3e,0x35,0x6d, + 0x06,0x14,0x3e,0x35,0x6d,0x00,0xc5,0x3f, + 0x65,0x90,0xe7,0xbc,0x03,0x0c,0x97,0x03, + 0x14,0x78,0x14,0x08,0x20,0x14,0x08,0x20, + 0xc8,0xc8,0x3b,0xd2,0x26,0x92,0x0f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x04,0x96, + 0x72,0x5c,0x11,0x00,0xfc,0xff,0x32,0x00 +}; +#endif + +static const unsigned char SiS_HiTVGroup3Data[] = { + 0x00,0x1a,0x22,0x63,0x62,0x22,0x08,0x5f, + 0x05,0x21,0xb2,0xb2,0x55,0x77,0x2a,0xa6, + 0x25,0x2f,0x47,0xfa,0xc8,0xff,0x8e,0x20, + 0x8c,0x6e,0x60,0x2e,0x58,0x48,0x72,0x44, + 0x56,0x36,0x4f,0x6e,0x3f,0x80,0x00,0x80, + 0x4f,0x7f,0x03,0xa8,0x7d,0x20,0x1a,0xa9, + 0x14,0x05,0x03,0x7e,0x64,0x31,0x14,0x75, + 0x18,0x05,0x18,0x05,0x4c,0xa8,0x01 +}; + +static const unsigned char SiS_HiTVGroup3Simu[] = { + 0x00,0x1a,0x22,0x63,0x62,0x22,0x08,0x95, + 0xdb,0x20,0xb8,0xb8,0x55,0x47,0x2a,0xa6, + 0x25,0x2f,0x47,0xfa,0xc8,0xff,0x8e,0x20, + 0x8c,0x6e,0x60,0x15,0x26,0xd3,0xe4,0x11, + 0x56,0x36,0x4f,0x6e,0x3f,0x80,0x00,0x80, + 0x67,0x36,0x01,0x47,0x0e,0x10,0xbe,0xb4, + 0x01,0x05,0x03,0x7e,0x65,0x31,0x14,0x75, + 0x18,0x05,0x18,0x05,0x4c,0xa8,0x01 +}; + +#if 0 +static const unsigned char SiS_HiTVGroup3Text[] = { + 0x00,0x1a,0x22,0x63,0x62,0x22,0x08,0xa7, + 0xf5,0x20,0xce,0xce,0x55,0x47,0x2a,0xa6, + 0x25,0x2f,0x47,0xfa,0xc8,0xff,0x8e,0x20, + 0x8c,0x6e,0x60,0x18,0x2c,0x0c,0x20,0x22, + 0x56,0x36,0x4f,0x6e,0x3f,0x80,0x00,0x80, + 0x93,0x3c,0x01,0x50,0x2f,0x10,0xf4,0xca, + 0x01,0x05,0x03,0x7e,0x65,0x31,0x14,0x75, + 0x18,0x05,0x18,0x05,0x4c,0xa8,0x01 +}; +#endif + +static const struct SiS_TVData SiS_StPALData[] = +{ + { 1, 1, 864, 525,1270, 400, 100, 0, 760, 0,0xf4,0xff,0x1c,0x22}, + { 1, 1, 864, 525,1270, 350, 100, 0, 760, 0,0xf4,0xff,0x1c,0x22}, + { 1, 1, 864, 525,1270, 400, 0, 0, 720, 0,0xf1,0x04,0x1f,0x18}, + { 1, 1, 864, 525,1270, 350, 0, 0, 720, 0,0xf4,0x0b,0x1c,0x0a}, + { 1, 1, 864, 525,1270, 480, 50, 0, 760, 0,0xf4,0xff,0x1c,0x22}, + { 1, 1, 864, 525,1270, 600, 50, 0, 0,0x300,0xf4,0xff,0x1c,0x22} +}; + +static const struct SiS_TVData SiS_ExtPALData[] = +{ + { 27, 10, 848, 448,1270, 530, 50, 0, 50, 0,0xf4,0xff,0x1c,0x22}, /* 640x400, 320x200 */ + { 108, 35, 848, 398,1270, 530, 50, 0, 50, 0,0xf4,0xff,0x1c,0x22}, + { 12, 5, 954, 448,1270, 530, 50, 0, 50, 0,0xf1,0x04,0x1f,0x18}, + { 9, 4, 960, 463,1644, 438, 50, 0, 50, 0,0xf4,0x0b,0x1c,0x0a}, + { 9, 4, 848, 528,1270, 530, 0, 0, 50, 0,0xf5,0xfb,0x1b,0x2a}, /* 640x480, 320x240 */ + { 36, 25,1060, 648,1270, 530, 438, 0, 438, 0,0xeb,0x05,0x25,0x16}, /* 800x600, 400x300 */ + { 3, 2,1080, 619,1270, 540, 438, 0, 438, 0,0xf3,0x00,0x1d,0x20}, /* 720x576 */ + { 1, 1,1170, 821,1270, 520, 686, 0, 686, 0,0xF3,0x00,0x1D,0x20}, /* 1024x768 */ + { 1, 1,1170, 821,1270, 520, 686, 0, 686, 0,0xF3,0x00,0x1D,0x20}, /* 1024x768 (for NTSC equ) */ + { 9, 4, 848, 528,1270, 530, 0, 0, 50, 0,0xf5,0xfb,0x1b,0x2a} /* 720x480 */ +}; + +static const struct SiS_TVData SiS_StNTSCData[] = +{ + { 1, 1, 858, 525,1270, 400, 50, 0, 760, 0,0xf1,0x04,0x1f,0x18}, + { 1, 1, 858, 525,1270, 350, 50, 0, 640, 0,0xf1,0x04,0x1f,0x18}, + { 1, 1, 858, 525,1270, 400, 0, 0, 720, 0,0xf1,0x04,0x1f,0x18}, + { 1, 1, 858, 525,1270, 350, 0, 0, 720, 0,0xf4,0x0b,0x1c,0x0a}, + { 1, 1, 858, 525,1270, 480, 0, 0, 760, 0,0xf1,0x04,0x1f,0x18} +}; + +static const struct SiS_TVData SiS_ExtNTSCData[] = +{ + { 143, 65, 858, 443,1270, 440, 171, 0, 171, 0,0xf1,0x04,0x1f,0x18}, /* 640x400, 320x200 */ + { 88, 35, 858, 393,1270, 440, 171, 0, 171, 0,0xf1,0x04,0x1f,0x18}, + { 143, 70, 924, 443,1270, 440, 92, 0, 92, 0,0xf1,0x04,0x1f,0x18}, + { 143, 70, 924, 393,1270, 440, 92, 0, 92, 0,0xf4,0x0b,0x1c,0x0a}, + { 143, 76, 836, 523,1270, 440, 224, 0, 0, 0,0xf1,0x05,0x1f,0x16}, /* 640x480, 320x240 */ + { 143, 120,1056, 643,1270, 440, 0, 1, 0, 0,0xf4,0x10,0x1c,0x00}, /* 800x600, 400x300 */ + { 143, 76, 836, 523,1270, 440, 0, 1, 0, 0,0xee,0x0c,0x22,0x08}, /* 720x480 - BETTER (from 300 series) */ + { 1, 1,1100, 811,1412, 440, 0, 1, 0, 0,0xee,0x0c,0x22,0x08}, /* 1024x768 (525i) CORRECTED */ +#if 0 /* flimmert und ist unten abgeschnitten (NTSCHT, NTSC clock) */ + { 65, 64,1056, 791,1270, 480, 455, 0, 0, 0,0x00,0x00,0x00,0x00} /* 1024x768 (525p) */ +#endif +#if 0 + { 1, 1,1100, 811,1412, 440, 0, 1, 0, 0,0x00,0x00,0x00,0x00} /* 1024x768 (525p) */ +#endif +#if 0 + { 1, 1,1120, 821,1516, 420, 0, 1, 0, 0,0x00,0x00,0x00,0x00} /* 1024x768 (525p) */ +#endif +#if 0 + { 1, 1, 938, 821,1516, 420, 0, 1, 0, 0,0x00,0x00,0x00,0x00} /* 1024x768 (525p) */ +#endif +#if 0 /* zoom hin, unten abgeschnitten (NTSC2HT, NTSC1024 clock) */ + { 1, 1,1072, 791,1270, 480, 455, 0, 0, 0,0x00,0x00,0x00,0x00} /* 1024x768 (525p) */ +#endif +#if 1 /* zu weit links (squeezed) (NTSC2HT, NTSC1024 clock) */ + { 1, 1,1100, 846,1270, 440, 455, 0, 0, 0,0x00,0x00,0x00,0x00} /* 1024x768 (525p) */ +#endif +#if 0 /* zu weit links, rechts abgeschnitten (NTSC2HT, NTSC1024 clock) */ + { 1, 1,1100, 846,1412, 440, 455, 0, 0, 0,0x00,0x00,0x00,0x00} /* 1024x768 (525p) */ +#endif +}; + +static const struct SiS_TVData SiS_StHiTVData[] = /* Slave + TVSimu */ +{ + { 1, 1, 0x37c,0x233,0x2b2,0x320, 0, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 0x37c,0x233,0x2b2,0x2bc, 0, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 0x37c,0x233,0x2b2,0x320, 0, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 0x37c,0x233,0x2b2,0x2bc, 0, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 0x37c,0x233,0x2b2,0x3c0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 8, 5, 0x41a,0x2ab,0x670,0x3c0,0x150, 1, 0, 0, 0, 0, 0, 0} +}; + +static const struct SiS_TVData SiS_St2HiTVData[] = /* Slave */ +{ + { 3, 1, 0x348,0x1e3,0x670,0x3c0,0x032, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 0x37c,0x233,0x2b2,0x2bc, 0, 0, 0, 0, 0, 0, 0, 0}, + { 3, 1, 0x348,0x1e3,0x670,0x3c0,0x032, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 0x37c,0x233,0x2b2,0x2bc, 0, 0, 0, 0, 0, 0, 0, 0}, + { 5, 2, 0x348,0x233,0x670,0x3c0,0x08d, 1, 0, 0, 0, 0, 0, 0}, + { 8, 5, 0x41a,0x2ab,0x670,0x3c0,0x17c, 1, 0, 0, 0, 0, 0, 0} +}; + +static const struct SiS_TVData SiS_ExtHiTVData[] = +{ /* all ok */ + { 6, 1, 0x348,0x233,0x660,0x3c0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 3, 1, 0x3c0,0x233,0x660,0x3c0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 6, 1, 0x348,0x233,0x660,0x3c0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 3, 1, 0x3c0,0x233,0x660,0x3c0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 5, 1, 0x348,0x233,0x670,0x3c0,0x166, 1, 0, 0, 0, 0, 0, 0}, /* 640x480 */ + { 16, 5, 0x41a,0x2ab,0x670,0x3c0,0x143, 1, 0, 0, 0, 0, 0, 0}, /* 800x600 */ + { 25, 12, 0x4ec,0x353,0x670,0x3c0,0x032, 0, 0, 0, 0, 0, 0, 0}, /* 1024x768 */ + { 5, 4, 0x627,0x464,0x670,0x3c0,0x128, 0, 0, 0, 0, 0, 0, 0}, /* 1280x1024 */ + { 4, 1, 0x41a,0x233,0x60c,0x3c0,0x143, 1, 0, 0, 0, 0, 0, 0}, /* 800x480 */ + { 5, 2, 0x578,0x293,0x670,0x3c0,0x032, 0, 0, 0, 0, 0, 0, 0}, /* 1024x576 */ + { 8, 5, 0x6d6,0x323,0x670,0x3c0,0x128, 0, 0, 0, 0, 0, 0, 0}, /* 1280x720 */ + { 8, 3, 0x4ec,0x353,0x670,0x3c0,0x032, 0, 0, 0, 0, 0, 0, 0}, /* 960x600 */ +}; + +static const struct SiS_TVData SiS_St525pData[] = +{ + { 1, 1, 0x6b4,0x20d,0x4f6,0x190, 50, 0, 0x2f8, 0, 0, 0, 0, 0}, + { 1, 1, 0x6b4,0x20d,0x4f6,0x15e, 50, 0, 0x280, 0, 0, 0, 0, 0}, + { 1, 1, 0x6b4,0x20d,0x4f6,0x190, 50, 0, 0x2f8, 0, 0, 0, 0, 0}, + { 1, 1, 0x6b4,0x20d,0x4f6,0x15e, 50, 0, 0x280, 0, 0, 0, 0, 0}, + { 1, 1, 0x6b4,0x20d,0x4f6,0x1e0, 0, 0, 0x2f8, 0, 0, 0, 0, 0} +}; + +static const struct SiS_TVData SiS_St750pData[] = +{ + { 1, 1, 0x672,0x2ee,0x500,0x190, 50, 0, 0x2f8, 0, 0, 0, 0, 0}, + { 1, 1, 0x672,0x2ee,0x500,0x15e, 50, 0, 0x280, 0, 0, 0, 0, 0}, + { 1, 1, 0x672,0x2ee,0x500,0x190, 0, 0, 0x2d0, 0, 0, 0, 0, 0}, + { 1, 1, 0x672,0x2ee,0x500,0x15e, 0, 0, 0x2d0, 0, 0, 0, 0, 0}, + { 1, 1, 0x672,0x2ee,0x500,0x1e0, 0, 0, 0x2f8, 0, 0, 0, 0, 0} +}; + +static const struct SiS_TVData SiS_Ext750pData[] = +{ /* all ok */ + { 3, 1, 935, 470, 1130, 680, 50, 0, 0, 0, 0, 0, 0, 0}, /* 320x200/640x400 */ + { 24, 7, 935, 420, 1130, 680, 50, 0, 0, 0, 0, 0, 0, 0}, + { 3, 1, 935, 470, 1130, 680, 50, 0, 0, 0, 0, 0, 0, 0}, + { 24, 7, 935, 420, 1130, 680, 50, 0, 0, 0, 0, 0, 0, 0}, + { 2, 1, 1100, 590, 1130, 640, 50, 0, 0, 0, 0, 0, 0, 0}, /* 640x480 */ + { 3, 2, 1210, 690, 1130, 660, 50, 0, 0, 0, 0, 0, 0, 0}, /* 800x600 OK */ + { 2, 1, 1100, 562, 1130, 640, 0, 1, 0, 0, 0, 0, 0, 0}, /* 720x480 OK */ + { 1, 1, 1375, 878, 1130, 640, 638, 0, 0, 0, 0, 0, 0, 0}, /* 1024x768 OK */ + { 5, 3, 1100, 675, 1130, 640, 0, 1, 0, 0, 0, 0, 0, 0}, /* 720/768x576 OK */ + { 25, 24, 1496, 755, 1120, 680, 50, 0, 0, 0, 0, 0, 0, 0} /* 1280x720 OK */ +}; + +static const struct SiS_LCDData SiS_LCD1280x720Data[] = /* 2.03.00 */ +{ + { 44, 15, 864, 430, 1408, 806 }, /* 640x400 */ + { 128, 35, 792, 385, 1408, 806 }, + { 44, 15, 864, 430, 1408, 806 }, + { 128, 35, 792, 385, 1408, 806 }, + { 22, 9, 864, 516, 1408, 806 }, /* 640x480 */ + { 8, 5, 1056, 655, 1408, 806 }, /* 800x600 */ + { 0, 0, 0, 0, 0, 0 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, /* 1280x1024 */ + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1408, 806, 1408, 806 } /* 1280x720 */ +}; + +/* About 1280x768: For TMDS, Panel_1280x768 will only be set if + * the panel is a Fujitsu 7911 (VL-17WDX8) (with clock 81, 1688x802) + * Other TMDS panels of this resolution will be treated as custom. + * For LVDS, we know another type (_2). + * (Note: 1280x768_3 is now special for SiS301/NetVista + */ + +static const struct SiS_LCDData SiS_StLCD1280x768_2Data[] = /* 2.03.00 */ +{ + { 64, 21, 858, 434, 1408, 806 }, /* 640x400 */ + { 32, 9, 858, 372, 1408, 806 }, + { 64, 21, 858, 434, 1408, 806 }, + { 32, 9, 858, 372, 1408, 806 }, + { 143, 68, 1024, 527, 1408, 806 }, /* 640x480 */ + { 64, 51, 1364, 663, 1408, 806 }, /* 800x600 */ + { 88, 81, 1296, 806, 1408, 806 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1408, 806, 1408, 806 }, /* 1280x768 */ + { 0, 0, 0, 0, 0, 0 }, + { 16, 15, 1600, 750, 1600, 806 } /* 1280x720 - from Ext */ +}; + +static const struct SiS_LCDData SiS_ExtLCD1280x768_2Data[] = /* 2.03.00 */ +{ + { 16, 5, 960, 410, 1600, 806 }, /* 640x400 */ + { 64, 21, 1152, 364, 1600, 806 }, + { 16, 5, 960, 410, 1600, 806 }, + { 64, 21, 1152, 364, 1600, 806 }, + { 32, 13, 1040, 493, 1600, 806 }, /* 640x480 */ + { 16, 9, 1152, 618, 1600, 806 }, /* 800x600 */ + { 25, 21, 1344, 796, 1600, 806 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1600, 806, 1600, 806 }, /* 1280x768 */ + { 0, 0, 0, 0, 0, 0 }, + { 16, 15, 1600, 750, 1600, 806 } /* 1280x720 */ +}; + +#if 0 /* Not used; _3 now reserved for NetVista (SiS301) */ +static const struct SiS_LCDData SiS_LCD1280x768_3Data[] = +{ + { 64, 25, 1056, 422, 1664, 798 }, /* 640x400 */ + { 128, 39, 884, 396, 1408, 806 }, /* ,640 */ + { 64, 25, 1056, 422, 1664, 798 }, /* 640x400 */ + { 128, 39, 884, 396, 1408, 806 }, /* ,640 */ + { 32, 15, 1056, 513, 1408, 806 }, /* ,664 */ /* 640x480 */ + { 176, 125, 1280, 640, 1408, 806 }, /* ,768 */ /* 800x600 */ + { 64, 61, 1342, 806, 1408, 806 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1408, 806, 1408, 806 }, /* 1280x768 */ + { 0, 0, 0, 0, 0, 0 }, + { 16, 15, 1600, 750, 1600, 806 } /* 1280x720 from above */ +}; +#endif + +static const struct SiS_LCDData SiS_LCD1280x800Data[] = /* 0.93.12a (TMDS) */ +{ + { 128, 51, 1122, 412, 1408, 816 }, /* 640x400 */ + { 128, 49, 1232, 361, 1408, 816 }, + { 128, 51, 1122, 412, 1408, 816 }, + { 128, 49, 1232, 361, 1408, 816 }, + { 8, 3, 880, 491, 1408, 816 }, /* 640x480 */ + { 11, 6, 1024, 612, 1408, 816 }, /* 800x600 */ + { 22, 21, 1400, 784, 1408, 816 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, /* 1280x1024 */ + { 1, 1, 1408, 816, 1408, 816 }, /* 1280x800 */ + { 0, 0, 0, 0, 0, 0 }, /* 1280x768 (patch index) */ + { 0, 0, 0, 0, 0, 0 } /* 1280x720 */ +}; + +static const struct SiS_LCDData SiS_LCD1280x800_2Data[] = /* 2.03.00 (LVDS) */ +{ + { 97, 42, 1344, 409, 1552, 812 }, /* 640x400 */ + { 97, 35, 1280, 358, 1552, 812 }, + { 97, 42, 1344, 409, 1552, 812 }, + { 97, 35, 1280, 358, 1552, 812 }, + { 97, 39, 1040, 488, 1552, 812 }, /* 640x480 */ + { 194, 105, 1120, 608, 1552, 812 }, /* 800x600 */ + { 97, 84, 1400, 780, 1552, 812 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, /* 1280x1024 */ + { 1, 1, 1552, 812, 1552, 812 }, /* 1280x800 */ + { 97, 96, 1600, 780, 1552, 812 }, /* 1280x768 - patch index */ + { 97, 90, 1600, 730, 1552, 812 } /* 1280x720 */ +}; + +#if 0 +static const struct SiS_LCDData SiS_LCD1280x800_3Data[] = /* 2.02.05a (LVDS); m250 */ +{ + { 128, 51, 1122, 412, 1408, 816 }, /* 640x400 */ + { 128, 49, 1232, 361, 1408, 816 }, + { 128, 51, 1122, 412, 1408, 816 }, + { 128, 49, 1232, 361, 1408, 816 }, + { 8, 3, 880, 491, 1408, 816 }, /* 640x480 */ + { 11, 6, 1024, 612, 1408, 816 }, /* 800x600 */ + { 22, 21, 1400, 784, 1408, 816 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, /* 1280x1024 */ + { 1, 1, 1408, 816, 1408, 816 }, /* 1280x800 */ + { 0, 0, 0, 0, 0, 0 }, /* 1280x768 - patch index */ + { 0, 0, 0, 0, 0, 0 } /* 1280x720 */ +}; +#endif + +static const struct SiS_LCDData SiS_LCD1280x854Data[] = /* 2.21.00CS (LVDS) */ +{ + { 56, 15, 936, 410, 1664, 861 }, /* 640x400 */ + { 64, 25, 1586, 355, 1664, 861 }, + { 56, 15, 936, 410, 1664, 861 }, + { 64, 25, 1586, 355, 1664, 861 }, + { 91, 45, 1464, 485, 1664, 861 }, /* 640x480 */ + { 182, 75, 976, 605, 1664, 861 }, /* 800x600 */ + { 91, 66, 1342, 774, 1664, 861 }, /* 1024x768 */ + { 0, 0, 0, 0, 0, 0 }, /* 1280x1024 */ + { 26, 25, 1708, 807, 1664, 861 }, /* 1280x800 */ + { 13, 12, 1708, 774, 1664, 861 }, /* 1280x768 - patch index */ + { 52, 45, 1708, 725, 1664, 861 }, /* 1280x720 */ + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1664, 861, 1664, 861 } /* 1280x854 */ +}; + +static const struct SiS_LCDData SiS_LCD1280x960Data[] = +{ + { 9, 2, 800, 500, 1800, 1000 }, + { 9, 2, 800, 500, 1800, 1000 }, + { 4, 1, 900, 500, 1800, 1000 }, + { 4, 1, 900, 500, 1800, 1000 }, + { 9, 2, 800, 500, 1800, 1000 }, + { 30, 11, 1056, 625, 1800, 1000 }, + { 5, 3, 1350, 800, 1800, 1000 }, + { 1, 1, 1576, 1050, 1576, 1050 }, + { 1, 1, 1800, 1000, 1800, 1000 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 } +}; + +static const struct SiS_LCDData SiS_StLCD1400x1050Data[] = +{ + { 211, 100, 2100, 408, 1688, 1066 }, + { 211, 64, 1536, 358, 1688, 1066 }, + { 211, 100, 2100, 408, 1688, 1066 }, + { 211, 64, 1536, 358, 1688, 1066 }, + { 211, 48, 840, 488, 1688, 1066 }, + { 211, 72, 1008, 609, 1688, 1066 }, + { 211, 128, 1400, 776, 1688, 1066 }, + { 211, 205, 1680, 1041, 1688, 1066 }, + { 1, 1, 1688, 1066, 1688, 1066 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 } +}; + +static const struct SiS_LCDData SiS_ExtLCD1400x1050Data[] = +{ +/* { 211, 60, 1260, 410, 1688, 1066 }, 640x400 (6330) */ + { 211, 100, 2100, 408, 1688, 1066 }, /* 640x400 (6325) WORKS */ + { 211, 64, 1536, 358, 1688, 1066 }, + { 211, 100, 2100, 408, 1688, 1066 }, + { 211, 64, 1536, 358, 1688, 1066 }, +/* { 211, 80, 1400, 490, 1688, 1066 }, 640x480 (6330) */ + { 211, 48, 840, 488, 1688, 1066 }, /* 640x480 (6325) WORKS */ +/* { 211, 117, 1638, 613, 1688, 1066 }, 800x600 (6330) */ + { 211, 72, 1008, 609, 1688, 1066 }, /* 800x600 (6325) WORKS */ + { 211, 128, 1400, 776, 1688, 1066 }, /* 1024x768 */ + { 211, 205, 1680, 1041, 1688, 1066 }, /* 1280x1024 - not used (always unscaled) */ + { 1, 1, 1688, 1066, 1688, 1066 }, /* 1400x1050 */ + { 0, 0, 0, 0, 0, 0 }, /* kludge */ + { 211, 120, 1400, 730, 1688, 1066 }, /* 1280x720 */ + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 } +}; + +static const struct SiS_LCDData SiS_LCD1680x1050Data[] = +{ + { 95, 24, 1260, 410, 1900, 1066 }, /* 0 640x400 */ + { 10, 3, 1710, 362, 1900, 1066 }, + { 95, 24, 1260, 410, 1900, 1066 }, + { 10, 3, 1710, 362, 1900, 1066 }, + { 95, 32, 1400, 490, 1900, 1066 }, /* 4 640x480 */ + { 95, 42, 1470, 610, 1900, 1066 }, /* 5 800x600 */ + { 95, 64, 1750, 784, 1900, 1066 }, /* 6 1024x768 */ + { 95, 94, 1900, 1055, 1900, 1066 }, /* 7 1280x1024 */ + { 41, 31, 1900, 806, 1900, 1066 }, /* 8 1280x768 */ + { 95, 69, 1800, 817, 1900, 1066 }, /* 9 1280x800 patch index */ + { 13, 9, 1900, 739, 1900, 1066 }, /* 10 1280x720 */ + { 95, 94, 1880, 1066, 1900, 1066 }, /* 11 1400x1050 patch index */ + { 1, 1, 1900, 1066, 1900, 1066 }, /* 12 1680x1050 */ + { 0, 0, 0, 0, 0, 0 } +}; + +static const struct SiS_LCDData SiS_StLCD1600x1200Data[] = +{ + {27, 4, 800, 500, 2160, 1250 }, + {27, 4, 800, 500, 2160, 1250 }, + { 6, 1, 900, 500, 2160, 1250 }, + { 6, 1, 900, 500, 2160, 1250 }, + {27, 1, 800, 500, 2160, 1250 }, + { 4, 1,1080, 625, 2160, 1250 }, + { 5, 2,1350, 800, 2160, 1250 }, + {135,88,1600,1100, 2160, 1250 }, + {72, 49,1680,1092, 2160, 1250 }, + { 1, 1,2160,1250, 2160, 1250 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 } +}; + +static const struct SiS_LCDData SiS_ExtLCD1600x1200Data[] = +{ + {72,11, 990, 422, 2160, 1250 }, /* 640x400 (6330) WORKS */ +/* {27, 4, 800, 500, 2160, 1250 }, 640x400 (6235) */ + {27, 4, 800, 500, 2160, 1250 }, + { 6, 1, 900, 500, 2160, 1250 }, + { 6, 1, 900, 500, 2160, 1250 }, + {45, 8, 960, 505, 2160, 1250 }, /* 640x480 (6330) WORKS */ +/* {27, 1, 800, 500, 2160, 1250 }, 640x480 (6325) */ + { 4, 1,1080, 625, 2160, 1250 }, + { 5, 2,1350, 800, 2160, 1250 }, + {27,16,1500,1064, 2160, 1250 }, /* 1280x1024 */ + {72,49,1680,1092, 2160, 1250 }, /* 1400x1050 (6330, was not supported on 6325) */ + { 1, 1,2160,1250, 2160, 1250 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 } +}; + +static const struct SiS_LCDData SiS_NoScaleData[] = +{ + { 1, 1, 800, 449, 800, 449 }, /* 0x00: 320x200, 640x400 */ + { 1, 1, 800, 449, 800, 449 }, + { 1, 1, 900, 449, 900, 449 }, + { 1, 1, 900, 449, 900, 449 }, + { 1, 1, 800, 525, 800, 525 }, /* 0x04: 320x240, 640x480 */ + { 1, 1,1056, 628,1056, 628 }, /* 0x05: 400x300, 800x600 */ + { 1, 1,1344, 806,1344, 806 }, /* 0x06: 512x384, 1024x768 */ + { 1, 1,1688,1066,1688,1066 }, /* 0x07: 1280x1024 */ + { 1, 1,1688, 802,1688, 802 }, /* 0x08: 1280x768: Fujitsu, TMDS only */ + { 1, 1,2160,1250,2160,1250 }, /* 0x09: 1600x1200 */ + { 1, 1,1800,1000,1800,1000 }, /* 0x0a: 1280x960 */ + { 1, 1,1688,1066,1688,1066 }, /* 0x0b: 1400x1050 */ + { 1, 1,1650, 750,1650, 750 }, /* 0x0c: 1280x720 (TMDS, projector) */ + { 1, 1,1552, 812,1552, 812 }, /* 0x0d: 1280x800_2 (LVDS) (was: 1408,816/ 1656,841) */ + { 1, 1,1900,1066,1900,1066 }, /* 0x0e: 1680x1050 (LVDS) */ + { 1, 1,1660, 806,1660, 806 }, /* 0x0f: 1280x768_2 (LVDS) */ + { 1, 1,1664, 798,1664, 798 }, /* 0x10: 1280x768_3 (NetVista SiS 301) - TODO */ + { 1, 1,1688, 802,1688, 802 }, /* 0x11: 1280x768 (TMDS Fujitsu) */ + { 1, 1,1408, 806,1408, 806 }, /* 0x12: 1280x720 (LVDS) */ + { 1, 1, 896, 497, 896, 497 }, /* 0x13: 720x480 */ + { 1, 1, 912, 597, 912, 597 }, /* 0x14: 720x576 */ + { 1, 1, 912, 597, 912, 597 }, /* 0x15: 768x576 */ + { 1, 1,1056, 497,1056, 497 }, /* 0x16: 848x480 */ + { 1, 1,1064, 497,1064, 497 }, /* 0x17: 856x480 */ + { 1, 1,1056, 497,1056, 497 }, /* 0x18: 800x480 */ + { 1, 1,1328, 739,1328, 739 }, /* 0x19: 1024x576 */ + { 1, 1,1680, 892,1680, 892 }, /* 0x1a: 1152x864 */ + { 1, 1,1808, 808,1808, 808 }, /* 0x1b: 1360x768 */ + { 1, 1,1104, 563,1104, 563 }, /* 0x1c: 960x540 */ + { 1, 1,1120, 618,1120, 618 }, /* 0x1d: 960x600 */ + { 1, 1,1408, 816,1408, 816 }, /* 0x1f: 1280x800 (TMDS special) */ + { 1, 1,1760,1235,1760,1235 }, /* 0x20: 1600x1200 for LCDA */ + { 1, 1,2048,1320,2048,1320 }, /* 0x21: 1600x1200 for non-SiS LVDS */ + { 1, 1,1664, 861,1664, 861 } /* 0x22: 1280x854 */ +}; + +/**************************************************************/ +/* LVDS ----------------------------------------------------- */ +/**************************************************************/ + +/* FSTN/DSTN 320x240, 2 variants */ +static const struct SiS_LVDSData SiS_LVDS320x240Data_1[]= +{ + { 848, 433, 400, 525}, + { 848, 389, 400, 525}, + { 848, 433, 400, 525}, + { 848, 389, 400, 525}, + { 848, 518, 400, 525}, + {1056, 628, 400, 525}, + { 400, 525, 400, 525} /* xSTN */ +}; + +static const struct SiS_LVDSData SiS_LVDS320x240Data_2[]= +{ + { 800, 445, 800, 525}, + { 800, 395, 800, 525}, + { 800, 445, 800, 525}, + { 800, 395, 800, 525}, + { 800, 525, 800, 525}, + {1056, 628,1056, 628}, + { 480, 525, 480, 525} /* xSTN */ +}; + +static const struct SiS_LVDSData SiS_LVDS640x480Data_1[]= +{ + { 800, 445, 800, 525}, /* 800, 449, 800, 449 */ + { 800, 395, 800, 525}, + { 800, 445, 800, 525}, + { 800, 395, 800, 525}, + { 800, 525, 800, 525} +}; + +static const struct SiS_LVDSData SiS_LVDS800x600Data_1[]= +{ + { 848, 433,1060, 629}, + { 848, 389,1060, 629}, + { 848, 433,1060, 629}, + { 848, 389,1060, 629}, + { 848, 518,1060, 629}, + {1056, 628,1056, 628} +}; + +static const struct SiS_LVDSData SiS_LVDS1024x600Data_1[] = +{ + { 840, 604,1344, 800}, + { 840, 560,1344, 800}, + { 840, 604,1344, 800}, + { 840, 560,1344, 800}, + { 840, 689,1344, 800}, + {1050, 800,1344, 800}, + {1344, 800,1344, 800} +}; + +static const struct SiS_LVDSData SiS_LVDS1024x768Data_1[]= +{ + { 840, 438,1344, 806}, + { 840, 409,1344, 806}, + { 840, 438,1344, 806}, + { 840, 409,1344, 806}, + { 840, 518,1344, 806}, /* 640x480 */ + {1050, 638,1344, 806}, /* 800x600 */ + {1344, 806,1344, 806}, /* 1024x768 */ +}; + +static const struct SiS_LVDSData SiS_CHTVUNTSCData[]= +{ + { 840, 600, 840, 600}, + { 840, 600, 840, 600}, + { 840, 600, 840, 600}, + { 840, 600, 840, 600}, + { 784, 600, 784, 600}, + {1064, 750,1064, 750}, + {1160, 945,1160, 945} +}; + +static const struct SiS_LVDSData SiS_CHTVONTSCData[]= +{ + { 840, 525, 840, 525}, + { 840, 525, 840, 525}, + { 840, 525, 840, 525}, + { 840, 525, 840, 525}, + { 784, 525, 784, 525}, + {1040, 700,1040, 700}, + {1160, 840,1160, 840} +}; + +/* CRT1 CRTC data for slave modes */ + +static const struct SiS_LVDSCRT1Data SiS_LVDSCRT1320x240_1[] = +{ + {{0x65,0x4f,0x89,0x56,0x83,0xaa,0x1f, + 0x90,0x85,0x8f,0xab,0x30,0x00,0x05, + 0x00 }}, + {{0x65,0x4f,0x89,0x56,0x83,0x83,0x1f, + 0x5e,0x83,0x5d,0x79,0x10,0x00,0x05, + 0x00 }}, + {{0x65,0x4f,0x89,0x54,0x9f,0xc4,0x1f, + 0x92,0x89,0x8f,0xb5,0x30,0x00,0x01, + 0x00 }}, + {{0x65,0x4f,0x89,0x56,0x83,0x83,0x1f, + 0x5e,0x83,0x5d,0x79,0x10,0x00,0x05, + 0x00 }}, + {{0x65,0x4f,0x89,0x56,0x83,0x04,0x3e, + 0xe0,0x85,0xdf,0xfb,0x10,0x00,0x05, + 0x00 }}, + {{0x7f,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x73,0x20,0x00,0x06, + 0x01 }}, + {{0x2d,0x27,0x90,0x2c,0x80,0x0b,0x3e, + 0xe9,0x8b,0xe7,0x04,0x00,0x00,0x00, + 0x00 }} +}; + +static const struct SiS_LVDSCRT1Data SiS_LVDSCRT1320x240_2[] = +{ + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x7f,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x73,0x20,0x00,0x06, + 0x01}}, +#if 0 + {{0x2d,0x27,0x90,0x2c,0x80,0x0b,0x3e, + 0xe9,0x8b,0xe7,0x04,0x00,0x00,0x00, + 0x00}} +#endif + {{0x5f,0x4f,0x83,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xe8,0x0c,0x00,0x00,0x05, + 0x00}}, +}; + +static const struct SiS_LVDSCRT1Data SiS_LVDSCRT1320x240_2_H[] = +{ + {{0x65,0x4f,0x89,0x56,0x83,0xaa,0x1f, + 0x90,0x85,0x8f,0xab,0x30,0x00,0x05, + 0x00}}, + {{0x65,0x4f,0x89,0x56,0x83,0x83,0x1f, + 0x5e,0x83,0x5d,0x79,0x10,0x00,0x05, + 0x00}}, + {{0x65,0x4f,0x89,0x54,0x9f,0xc4,0x1f, + 0x92,0x89,0x8f,0xb5,0x30,0x00,0x01, + 0x00}}, + {{0x65,0x4f,0x89,0x56,0x83,0x83,0x1f, + 0x5e,0x83,0x5d,0x79,0x10,0x00,0x05, + 0x00}}, + {{0x65,0x4f,0x89,0x56,0x83,0x04,0x3e, + 0xe0,0x85,0xdf,0xfb,0x10,0x00,0x05, + 0x00}}, + {{0x7f,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x73,0x20,0x00,0x06, + 0x01}}, + {{0x2d,0x27,0x90,0x2c,0x80,0x0b,0x3e, + 0xe9,0x8b,0xe7,0x04,0x00,0x00,0x00, + 0x00}} +}; + +static const struct SiS_LVDSCRT1Data SiS_LVDSCRT1320x240_3[] = +{ + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x00,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x00,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x00,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x00,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x00,0x00,0x05, + 0x00}}, + {{0x7f,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x73,0x20,0x00,0x06, + 0x01}}, + {{0x2d,0x27,0x90,0x2c,0x80,0x0b,0x3e, + 0xe9,0x8b,0xe7,0x04,0x00,0x00,0x00, + 0x00}} +}; + +static const struct SiS_LVDSCRT1Data SiS_LVDSCRT1320x240_3_H[] = +{ + {{0x65,0x4f,0x89,0x56,0x83,0xaa,0x1f, + 0x90,0x85,0x8f,0xab,0x30,0x00,0x05, + 0x00}}, + {{0x65,0x4f,0x89,0x56,0x83,0x83,0x1f, + 0x5e,0x83,0x5d,0x79,0x10,0x00,0x05, + 0x00}}, + {{0x65,0x4f,0x89,0x54,0x9f,0xc4,0x1f, + 0x92,0x89,0x8f,0xb5,0x30,0x00,0x01, + 0x00}}, + {{0x65,0x4f,0x89,0x56,0x83,0x83,0x1f, + 0x5e,0x83,0x5d,0x79,0x10,0x00,0x05, + 0x00}}, + {{0x65,0x4f,0x89,0x56,0x83,0x04,0x3e, + 0xe0,0x85,0xdf,0xfb,0x10,0x00,0x05, + 0x00}}, + {{0x7f,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x73,0x20,0x00,0x06, + 0x01}}, + {{0x2d,0x27,0x90,0x2c,0x80,0x0b,0x3e, + 0xe9,0x8b,0xe7,0x04,0x00,0x00,0x00, + 0x00}} +}; + +static const struct SiS_LVDSCRT1Data SiS_LVDSCRT1640x480_1[] = +{ + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x5f,0x4f,0x82,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0x04,0x30,0x00,0x05, + 0x00}}, + {{0x7f,0x63,0x83,0x6c,0x1c,0x72,0xf0, + 0x58,0x8c,0x57,0x73,0x20,0x00,0x06, + 0x01}} +}; + +static const struct SiS_LVDSCRT1Data SiS_LVDSCRT1640x480_1_H[] = +{ + {{0x2d,0x28,0x90,0x2b,0xa0,0xbf,0x1f, + 0x9c,0x8e,0x96,0xb9,0x00,0x00,0x00, + 0x00}}, + {{0x2d,0x28,0x90,0x2b,0xa0,0xbf,0x1f, + 0x83,0x85,0x63,0xba,0x00,0x00,0x00, + 0x00}}, + {{0x2d,0x28,0x90,0x2b,0xa0,0xbf,0x1f, + 0x9c,0x8e,0x96,0xb9,0x00,0x00,0x00, + 0x00}}, + {{0x2d,0x28,0x90,0x2b,0xa0,0xbf,0x1f, + 0x83,0x85,0x63,0xba,0x00,0x00,0x00, + 0x00}}, + {{0x2d,0x28,0x90,0x2c,0x80,0x0b,0x3e, + 0xe9,0x8b,0xe7,0x04,0x00,0x00,0x00, + 0x00}} +}; + +bool SiSInitPtr(struct SiS_Private *SiS_Pr); +unsigned short SiS_GetModeID_LCD(int VGAEngine, unsigned int VBFlags, int HDisplay, + int VDisplay, int Depth, bool FSTN, + unsigned short CustomT, int LCDwith, int LCDheight, + unsigned int VBFlags2); +unsigned short SiS_GetModeID_TV(int VGAEngine, unsigned int VBFlags, int HDisplay, + int VDisplay, int Depth, unsigned int VBFlags2); +unsigned short SiS_GetModeID_VGA2(int VGAEngine, unsigned int VBFlags, int HDisplay, + int VDisplay, int Depth, unsigned int VBFlags2); + +void SiS_DisplayOn(struct SiS_Private *SiS_Pr); +void SiS_DisplayOff(struct SiS_Private *SiS_Pr); +void SiSRegInit(struct SiS_Private *SiS_Pr, SISIOADDRESS BaseAddr); +void SiS_SetEnableDstn(struct SiS_Private *SiS_Pr, int enable); +void SiS_SetEnableFstn(struct SiS_Private *SiS_Pr, int enable); +unsigned short SiS_GetModeFlag(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +bool SiSDetermineROMLayout661(struct SiS_Private *SiS_Pr); + +bool SiS_SearchModeID(struct SiS_Private *SiS_Pr, unsigned short *ModeNo, + unsigned short *ModeIdIndex); +unsigned short SiS_GetModePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +unsigned short SiS_GetRefCRTVCLK(struct SiS_Private *SiS_Pr, unsigned short Index, int UseWide); +unsigned short SiS_GetRefCRT1CRTC(struct SiS_Private *SiS_Pr, unsigned short Index, int UseWide); +unsigned short SiS_GetColorDepth(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +unsigned short SiS_GetOffset(struct SiS_Private *SiS_Pr,unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI); +#ifdef CONFIG_FB_SIS_300 +void SiS_GetFIFOThresholdIndex300(struct SiS_Private *SiS_Pr, unsigned short *idx1, + unsigned short *idx2); +unsigned short SiS_GetFIFOThresholdB300(unsigned short idx1, unsigned short idx2); +unsigned short SiS_GetLatencyFactor630(struct SiS_Private *SiS_Pr, unsigned short index); +#endif +void SiS_LoadDAC(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex); +bool SiSSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo); +void SiS_CalcCRRegisters(struct SiS_Private *SiS_Pr, int depth); +void SiS_CalcLCDACRT1Timing(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +void SiS_Generic_ConvertCRData(struct SiS_Private *SiS_Pr, unsigned char *crdata, int xres, + int yres, struct fb_var_screeninfo *var, bool writeres); + +/* From init301.c: */ +extern void SiS_GetVBInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, int chkcrt2mode); +extern void SiS_GetLCDResInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +extern void SiS_SetYPbPr(struct SiS_Private *SiS_Pr); +extern void SiS_SetTVMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +extern void SiS_UnLockCRT2(struct SiS_Private *SiS_Pr); +extern void SiS_DisableBridge(struct SiS_Private *); +extern bool SiS_SetCRT2Group(struct SiS_Private *, unsigned short); +extern unsigned short SiS_GetRatePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +extern void SiS_WaitRetrace1(struct SiS_Private *SiS_Pr); +extern unsigned short SiS_GetResInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +extern unsigned short SiS_GetCH700x(struct SiS_Private *SiS_Pr, unsigned short tempax); +extern unsigned short SiS_GetVCLK2Ptr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI); +extern bool SiS_IsVAMode(struct SiS_Private *); +extern bool SiS_IsDualEdge(struct SiS_Private *); + +#ifdef CONFIG_FB_SIS_300 +extern unsigned int sisfb_read_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg); +extern void sisfb_write_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg, + unsigned int val); +#endif +#ifdef CONFIG_FB_SIS_315 +extern void sisfb_write_nbridge_pci_byte(struct SiS_Private *SiS_Pr, int reg, + unsigned char val); +extern unsigned int sisfb_read_mio_pci_word(struct SiS_Private *SiS_Pr, int reg); +#endif + +#endif + diff --git a/drivers/video/fbdev/sis/init301.c b/drivers/video/fbdev/sis/init301.c new file mode 100644 index 000000000000..a89e3cafd5ad --- /dev/null +++ b/drivers/video/fbdev/sis/init301.c @@ -0,0 +1,11071 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Mode initializing code (CRT2 section) + * for SiS 300/305/540/630/730, + * SiS 315/550/[M]650/651/[M]661[FGM]X/[M]74x[GX]/330/[M]76x[GX], + * XGI V3XT/V5/V8, Z7 + * (Universal module for Linux kernel framebuffer and X.org/XFree86 4.x) + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + * Formerly based on non-functional code-fragements for 300 series by SiS, Inc. + * Used by permission. + * + */ + +#if 1 +#define SET_EMI /* 302LV/ELV: Set EMI values */ +#endif + +#if 1 +#define SET_PWD /* 301/302LV: Set PWD */ +#endif + +#define COMPAL_HACK /* Needed for Compal 1400x1050 (EMI) */ +#define COMPAQ_HACK /* Needed for Inventec/Compaq 1280x1024 (EMI) */ +#define ASUS_HACK /* Needed for Asus A2H 1024x768 (EMI) */ + +#include "init301.h" + +#ifdef CONFIG_FB_SIS_300 +#include "oem300.h" +#endif + +#ifdef CONFIG_FB_SIS_315 +#include "oem310.h" +#endif + +#define SiS_I2CDELAY 1000 +#define SiS_I2CDELAYSHORT 150 + +static unsigned short SiS_GetBIOSLCDResInfo(struct SiS_Private *SiS_Pr); +static void SiS_SetCH70xx(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val); + +/*********************************************/ +/* HELPER: Lock/Unlock CRT2 */ +/*********************************************/ + +void +SiS_UnLockCRT2(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType == XGI_20) + return; + else if(SiS_Pr->ChipType >= SIS_315H) + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2f,0x01); + else + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x24,0x01); +} + +static +void +SiS_LockCRT2(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType == XGI_20) + return; + else if(SiS_Pr->ChipType >= SIS_315H) + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2F,0xFE); + else + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x24,0xFE); +} + +/*********************************************/ +/* HELPER: Write SR11 */ +/*********************************************/ + +static void +SiS_SetRegSR11ANDOR(struct SiS_Private *SiS_Pr, unsigned short DataAND, unsigned short DataOR) +{ + if(SiS_Pr->ChipType >= SIS_661) { + DataAND &= 0x0f; + DataOR &= 0x0f; + } + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x11,DataAND,DataOR); +} + +/*********************************************/ +/* HELPER: Get Pointer to LCD structure */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_315 +static unsigned char * +GetLCDStructPtr661(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned char *myptr = NULL; + unsigned short romindex = 0, reg = 0, idx = 0; + + /* Use the BIOS tables only for LVDS panels; TMDS is unreliable + * due to the variaty of panels the BIOS doesn't know about. + * Exception: If the BIOS has better knowledge (such as in case + * of machines with a 301C and a panel that does not support DDC) + * use the BIOS data as well. + */ + + if((SiS_Pr->SiS_ROMNew) && + ((SiS_Pr->SiS_VBType & VB_SISLVDS) || (!SiS_Pr->PanelSelfDetected))) { + + if(SiS_Pr->ChipType < SIS_661) reg = 0x3c; + else reg = 0x7d; + + idx = (SiS_GetReg(SiS_Pr->SiS_P3d4,reg) & 0x1f) * 26; + + if(idx < (8*26)) { + myptr = (unsigned char *)&SiS_LCDStruct661[idx]; + } + romindex = SISGETROMW(0x100); + if(romindex) { + romindex += idx; + myptr = &ROMAddr[romindex]; + } + } + return myptr; +} + +static unsigned short +GetLCDStructPtr661_2(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr = 0; + + /* Use the BIOS tables only for LVDS panels; TMDS is unreliable + * due to the variaty of panels the BIOS doesn't know about. + * Exception: If the BIOS has better knowledge (such as in case + * of machines with a 301C and a panel that does not support DDC) + * use the BIOS data as well. + */ + + if((SiS_Pr->SiS_ROMNew) && + ((SiS_Pr->SiS_VBType & VB_SISLVDS) || (!SiS_Pr->PanelSelfDetected))) { + romptr = SISGETROMW(0x102); + romptr += ((SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) >> 4) * SiS_Pr->SiS661LCD2TableSize); + } + + return romptr; +} +#endif + +/*********************************************/ +/* Adjust Rate for CRT2 */ +/*********************************************/ + +static bool +SiS_AdjustCRT2Rate(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RRTI, unsigned short *i) +{ + unsigned short checkmask=0, modeid, infoflag; + + modeid = SiS_Pr->SiS_RefIndex[RRTI + (*i)].ModeID; + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC) { + + checkmask |= SupportRAMDAC2; + if(SiS_Pr->ChipType >= SIS_315H) { + checkmask |= SupportRAMDAC2_135; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + checkmask |= SupportRAMDAC2_162; + if(SiS_Pr->SiS_VBType & VB_SISRAMDAC202) { + checkmask |= SupportRAMDAC2_202; + } + } + } + + } else if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + + checkmask |= SupportLCD; + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (SiS_Pr->SiS_LCDInfo & LCDPass11)) { + if(modeid == 0x2e) checkmask |= Support64048060Hz; + } + } + } + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + + checkmask |= SupportHiVision; + + } else if(SiS_Pr->SiS_VBInfo & (SetCRT2ToYPbPr525750|SetCRT2ToAVIDEO|SetCRT2ToSVIDEO|SetCRT2ToSCART)) { + + checkmask |= SupportTV; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + checkmask |= SupportTV1024; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) { + checkmask |= SupportYPbPr750p; + } + } + } + + } + + } else { /* LVDS */ + + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + checkmask |= SupportCHTV; + } + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + checkmask |= SupportLCD; + } + + } + + /* Look backwards in table for matching CRT2 mode */ + for(; SiS_Pr->SiS_RefIndex[RRTI + (*i)].ModeID == modeid; (*i)--) { + infoflag = SiS_Pr->SiS_RefIndex[RRTI + (*i)].Ext_InfoFlag; + if(infoflag & checkmask) return true; + if((*i) == 0) break; + } + + /* Look through the whole mode-section of the table from the beginning + * for a matching CRT2 mode if no mode was found yet. + */ + for((*i) = 0; ; (*i)++) { + if(SiS_Pr->SiS_RefIndex[RRTI + (*i)].ModeID != modeid) break; + infoflag = SiS_Pr->SiS_RefIndex[RRTI + (*i)].Ext_InfoFlag; + if(infoflag & checkmask) return true; + } + return false; +} + +/*********************************************/ +/* Get rate index */ +/*********************************************/ + +unsigned short +SiS_GetRatePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short RRTI,i,backup_i; + unsigned short modeflag,index,temp,backupindex; + static const unsigned short LCDRefreshIndex[] = { + 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00 + }; + + /* Do NOT check for UseCustomMode here, will skrew up FIFO */ + if(ModeNo == 0xfe) return 0; + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(modeflag & HalfDCLK) return 0; + } + } + + if(ModeNo < 0x14) return 0xFFFF; + + index = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x33) >> SiS_Pr->SiS_SelectCRT2Rate) & 0x0F; + backupindex = index; + + if(index > 0) index--; + + if(SiS_Pr->SiS_SetFlag & ProgrammingCRT2) { + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->SiS_VBType & VB_NoLCD) index = 0; + else if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) index = backupindex = 0; + } + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_VBType & VB_NoLCD)) { + temp = LCDRefreshIndex[SiS_GetBIOSLCDResInfo(SiS_Pr)]; + if(index > temp) index = temp; + } + } + } else { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) index = 0; + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) index = 0; + } + } + } + + RRTI = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].REFindex; + ModeNo = SiS_Pr->SiS_RefIndex[RRTI].ModeID; + + if(SiS_Pr->ChipType >= SIS_315H) { + if(!(SiS_Pr->SiS_VBInfo & DriverMode)) { + if( (SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_VESAID == 0x105) || + (SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_VESAID == 0x107) ) { + if(backupindex <= 1) RRTI++; + } + } + } + + i = 0; + do { + if(SiS_Pr->SiS_RefIndex[RRTI + i].ModeID != ModeNo) break; + temp = SiS_Pr->SiS_RefIndex[RRTI + i].Ext_InfoFlag; + temp &= ModeTypeMask; + if(temp < SiS_Pr->SiS_ModeType) break; + i++; + index--; + } while(index != 0xFFFF); + + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC)) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + temp = SiS_Pr->SiS_RefIndex[RRTI + i - 1].Ext_InfoFlag; + if(temp & InterlaceMode) i++; + } + } + + i--; + + if((SiS_Pr->SiS_SetFlag & ProgrammingCRT2) && (!(SiS_Pr->SiS_VBInfo & DisableCRT2Display))) { + backup_i = i; + if(!(SiS_AdjustCRT2Rate(SiS_Pr, ModeNo, ModeIdIndex, RRTI, &i))) { + i = backup_i; + } + } + + return (RRTI + i); +} + +/*********************************************/ +/* STORE CRT2 INFO in CR34 */ +/*********************************************/ + +static void +SiS_SaveCRT2Info(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned short temp1, temp2; + + /* Store CRT1 ModeNo in CR34 */ + SiS_SetReg(SiS_Pr->SiS_P3d4,0x34,ModeNo); + temp1 = (SiS_Pr->SiS_VBInfo & SetInSlaveMode) >> 8; + temp2 = ~(SetInSlaveMode >> 8); + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x31,temp2,temp1); +} + +/*********************************************/ +/* HELPER: GET SOME DATA FROM BIOS ROM */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_300 +static bool +SiS_CR36BIOSWord23b(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short temp,temp1; + + if(SiS_Pr->SiS_UseROM) { + if((ROMAddr[0x233] == 0x12) && (ROMAddr[0x234] == 0x34)) { + temp = 1 << ((SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) >> 4) & 0x0f); + temp1 = SISGETROMW(0x23b); + if(temp1 & temp) return true; + } + } + return false; +} + +static bool +SiS_CR36BIOSWord23d(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short temp,temp1; + + if(SiS_Pr->SiS_UseROM) { + if((ROMAddr[0x233] == 0x12) && (ROMAddr[0x234] == 0x34)) { + temp = 1 << ((SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) >> 4) & 0x0f); + temp1 = SISGETROMW(0x23d); + if(temp1 & temp) return true; + } + } + return false; +} +#endif + +/*********************************************/ +/* HELPER: DELAY FUNCTIONS */ +/*********************************************/ + +void +SiS_DDC2Delay(struct SiS_Private *SiS_Pr, unsigned int delaytime) +{ + while (delaytime-- > 0) + SiS_GetReg(SiS_Pr->SiS_P3c4, 0x05); +} + +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) +static void +SiS_GenericDelay(struct SiS_Private *SiS_Pr, unsigned short delay) +{ + SiS_DDC2Delay(SiS_Pr, delay * 36); +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static void +SiS_LongDelay(struct SiS_Private *SiS_Pr, unsigned short delay) +{ + while(delay--) { + SiS_GenericDelay(SiS_Pr, 6623); + } +} +#endif + +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) +static void +SiS_ShortDelay(struct SiS_Private *SiS_Pr, unsigned short delay) +{ + while(delay--) { + SiS_GenericDelay(SiS_Pr, 66); + } +} +#endif + +static void +SiS_PanelDelay(struct SiS_Private *SiS_Pr, unsigned short DelayTime) +{ +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short PanelID, DelayIndex, Delay=0; +#endif + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 + + PanelID = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36); + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->SiS_VBType & VB_SIS301) PanelID &= 0xf7; + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x18) & 0x10)) PanelID = 0x12; + } + DelayIndex = PanelID >> 4; + if((DelayTime >= 2) && ((PanelID & 0x0f) == 1)) { + Delay = 3; + } else { + if(DelayTime >= 2) DelayTime -= 2; + if(!(DelayTime & 0x01)) { + Delay = SiS_Pr->SiS_PanelDelayTbl[DelayIndex].timer[0]; + } else { + Delay = SiS_Pr->SiS_PanelDelayTbl[DelayIndex].timer[1]; + } + if(SiS_Pr->SiS_UseROM) { + if(ROMAddr[0x220] & 0x40) { + if(!(DelayTime & 0x01)) Delay = (unsigned short)ROMAddr[0x225]; + else Delay = (unsigned short)ROMAddr[0x226]; + } + } + } + SiS_ShortDelay(SiS_Pr, Delay); + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 + + if((SiS_Pr->ChipType >= SIS_661) || + (SiS_Pr->ChipType <= SIS_315PRO) || + (SiS_Pr->ChipType == SIS_330) || + (SiS_Pr->SiS_ROMNew)) { + + if(!(DelayTime & 0x01)) { + SiS_DDC2Delay(SiS_Pr, 0x1000); + } else { + SiS_DDC2Delay(SiS_Pr, 0x4000); + } + + } else if((SiS_Pr->SiS_IF_DEF_LVDS == 1) /* || + (SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) || + (SiS_Pr->SiS_CustomT == CUT_CLEVO1400) */ ) { /* 315 series, LVDS; Special */ + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 0) { + PanelID = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36); + if(SiS_Pr->SiS_CustomT == CUT_CLEVO1400) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x1b) & 0x10)) PanelID = 0x12; + } + if(SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) { + DelayIndex = PanelID & 0x0f; + } else { + DelayIndex = PanelID >> 4; + } + if((DelayTime >= 2) && ((PanelID & 0x0f) == 1)) { + Delay = 3; + } else { + if(DelayTime >= 2) DelayTime -= 2; + if(!(DelayTime & 0x01)) { + Delay = SiS_Pr->SiS_PanelDelayTblLVDS[DelayIndex].timer[0]; + } else { + Delay = SiS_Pr->SiS_PanelDelayTblLVDS[DelayIndex].timer[1]; + } + if((SiS_Pr->SiS_UseROM) && (!(SiS_Pr->SiS_ROMNew))) { + if(ROMAddr[0x13c] & 0x40) { + if(!(DelayTime & 0x01)) { + Delay = (unsigned short)ROMAddr[0x17e]; + } else { + Delay = (unsigned short)ROMAddr[0x17f]; + } + } + } + } + SiS_ShortDelay(SiS_Pr, Delay); + } + + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { /* 315 series, all bridges */ + + DelayIndex = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) >> 4; + if(!(DelayTime & 0x01)) { + Delay = SiS_Pr->SiS_PanelDelayTbl[DelayIndex].timer[0]; + } else { + Delay = SiS_Pr->SiS_PanelDelayTbl[DelayIndex].timer[1]; + } + Delay <<= 8; + SiS_DDC2Delay(SiS_Pr, Delay); + + } + +#endif /* CONFIG_FB_SIS_315 */ + + } +} + +#ifdef CONFIG_FB_SIS_315 +static void +SiS_PanelDelayLoop(struct SiS_Private *SiS_Pr, unsigned short DelayTime, unsigned short DelayLoop) +{ + int i; + for(i = 0; i < DelayLoop; i++) { + SiS_PanelDelay(SiS_Pr, DelayTime); + } +} +#endif + +/*********************************************/ +/* HELPER: WAIT-FOR-RETRACE FUNCTIONS */ +/*********************************************/ + +void +SiS_WaitRetrace1(struct SiS_Private *SiS_Pr) +{ + unsigned short watchdog; + + if(SiS_GetReg(SiS_Pr->SiS_P3c4,0x1f) & 0xc0) return; + if(!(SiS_GetReg(SiS_Pr->SiS_P3d4,0x17) & 0x80)) return; + + watchdog = 65535; + while((SiS_GetRegByte(SiS_Pr->SiS_P3da) & 0x08) && --watchdog); + watchdog = 65535; + while((!(SiS_GetRegByte(SiS_Pr->SiS_P3da) & 0x08)) && --watchdog); +} + +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) +static void +SiS_WaitRetrace2(struct SiS_Private *SiS_Pr, unsigned short reg) +{ + unsigned short watchdog; + + watchdog = 65535; + while((SiS_GetReg(SiS_Pr->SiS_Part1Port,reg) & 0x02) && --watchdog); + watchdog = 65535; + while((!(SiS_GetReg(SiS_Pr->SiS_Part1Port,reg) & 0x02)) && --watchdog); +} +#endif + +static void +SiS_WaitVBRetrace(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(!(SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00) & 0x20)) return; + } + if(!(SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00) & 0x80)) { + SiS_WaitRetrace1(SiS_Pr); + } else { + SiS_WaitRetrace2(SiS_Pr, 0x25); + } +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + if(!(SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00) & 0x40)) { + SiS_WaitRetrace1(SiS_Pr); + } else { + SiS_WaitRetrace2(SiS_Pr, 0x30); + } +#endif + } +} + +static void +SiS_VBWait(struct SiS_Private *SiS_Pr) +{ + unsigned short tempal,temp,i,j; + + temp = 0; + for(i = 0; i < 3; i++) { + for(j = 0; j < 100; j++) { + tempal = SiS_GetRegByte(SiS_Pr->SiS_P3da); + if(temp & 0x01) { + if((tempal & 0x08)) continue; + else break; + } else { + if(!(tempal & 0x08)) continue; + else break; + } + } + temp ^= 0x01; + } +} + +static void +SiS_VBLongWait(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + SiS_VBWait(SiS_Pr); + } else { + SiS_WaitRetrace1(SiS_Pr); + } +} + +/*********************************************/ +/* HELPER: MISC */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_300 +static bool +SiS_Is301B(struct SiS_Private *SiS_Pr) +{ + if(SiS_GetReg(SiS_Pr->SiS_Part4Port,0x01) >= 0xb0) return true; + return false; +} +#endif + +static bool +SiS_CRT2IsLCD(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType == SIS_730) { + if(SiS_GetReg(SiS_Pr->SiS_P3c4,0x13) & 0x20) return true; + } + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x30) & 0x20) return true; + return false; +} + +bool +SiS_IsDualEdge(struct SiS_Private *SiS_Pr) +{ +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + if((SiS_Pr->ChipType != SIS_650) || (SiS_GetReg(SiS_Pr->SiS_P3d4,0x5f) & 0xf0)) { + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x38) & EnableDualEdge) return true; + } + } +#endif + return false; +} + +bool +SiS_IsVAMode(struct SiS_Private *SiS_Pr) +{ +#ifdef CONFIG_FB_SIS_315 + unsigned short flag; + + if(SiS_Pr->ChipType >= SIS_315H) { + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + if((flag & EnableDualEdge) && (flag & SetToLCDA)) return true; + } +#endif + return false; +} + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_IsVAorLCD(struct SiS_Private *SiS_Pr) +{ + if(SiS_IsVAMode(SiS_Pr)) return true; + if(SiS_CRT2IsLCD(SiS_Pr)) return true; + return false; +} +#endif + +static bool +SiS_IsDualLink(struct SiS_Private *SiS_Pr) +{ +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + if((SiS_CRT2IsLCD(SiS_Pr)) || + (SiS_IsVAMode(SiS_Pr))) { + if(SiS_Pr->SiS_LCDInfo & LCDDualLink) return true; + } + } +#endif + return false; +} + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_TVEnabled(struct SiS_Private *SiS_Pr) +{ + if((SiS_GetReg(SiS_Pr->SiS_Part2Port,0x00) & 0x0f) != 0x0c) return true; + if(SiS_Pr->SiS_VBType & VB_SISYPBPR) { + if(SiS_GetReg(SiS_Pr->SiS_Part2Port,0x4d) & 0x10) return true; + } + return false; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_LCDAEnabled(struct SiS_Private *SiS_Pr) +{ + if(SiS_GetReg(SiS_Pr->SiS_Part1Port,0x13) & 0x04) return true; + return false; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_WeHaveBacklightCtrl(struct SiS_Private *SiS_Pr) +{ + if((SiS_Pr->ChipType >= SIS_315H) && (SiS_Pr->ChipType < SIS_661)) { + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x79) & 0x10) return true; + } + return false; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_IsNotM650orLater(struct SiS_Private *SiS_Pr) +{ + unsigned short flag; + + if(SiS_Pr->ChipType == SIS_650) { + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x5f) & 0xf0; + /* Check for revision != A0 only */ + if((flag == 0xe0) || (flag == 0xc0) || + (flag == 0xb0) || (flag == 0x90)) return false; + } else if(SiS_Pr->ChipType >= SIS_661) return false; + return true; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_IsYPbPr(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType >= SIS_315H) { + /* YPrPb = 0x08 */ + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x38) & EnableCHYPbPr) return true; + } + return false; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_IsChScart(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType >= SIS_315H) { + /* Scart = 0x04 */ + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x38) & EnableCHScart) return true; + } + return false; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_IsTVOrYPbPrOrScart(struct SiS_Private *SiS_Pr) +{ + unsigned short flag; + + if(SiS_Pr->ChipType >= SIS_315H) { + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + if(flag & SetCRT2ToTV) return true; + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + if(flag & EnableCHYPbPr) return true; /* = YPrPb = 0x08 */ + if(flag & EnableCHScart) return true; /* = Scart = 0x04 - TW */ + } else { + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + if(flag & SetCRT2ToTV) return true; + } + return false; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +static bool +SiS_IsLCDOrLCDA(struct SiS_Private *SiS_Pr) +{ + unsigned short flag; + + if(SiS_Pr->ChipType >= SIS_315H) { + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + if(flag & SetCRT2ToLCD) return true; + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + if(flag & SetToLCDA) return true; + } else { + flag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + if(flag & SetCRT2ToLCD) return true; + } + return false; +} +#endif + +static bool +SiS_HaveBridge(struct SiS_Private *SiS_Pr) +{ + unsigned short flag; + + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + return true; + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { + flag = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x00); + if((flag == 1) || (flag == 2)) return true; + } + return false; +} + +static bool +SiS_BridgeIsEnabled(struct SiS_Private *SiS_Pr) +{ + unsigned short flag; + + if(SiS_HaveBridge(SiS_Pr)) { + flag = SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00); + if(SiS_Pr->ChipType < SIS_315H) { + flag &= 0xa0; + if((flag == 0x80) || (flag == 0x20)) return true; + } else { + flag &= 0x50; + if((flag == 0x40) || (flag == 0x10)) return true; + } + } + return false; +} + +static bool +SiS_BridgeInSlavemode(struct SiS_Private *SiS_Pr) +{ + unsigned short flag1; + + flag1 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x31); + if(flag1 & (SetInSlaveMode >> 8)) return true; + return false; +} + +/*********************************************/ +/* GET VIDEO BRIDGE CONFIG INFO */ +/*********************************************/ + +/* Setup general purpose IO for Chrontel communication */ +#ifdef CONFIG_FB_SIS_300 +void +SiS_SetChrontelGPIO(struct SiS_Private *SiS_Pr, unsigned short myvbinfo) +{ + unsigned int acpibase; + unsigned short temp; + + if(!(SiS_Pr->SiS_ChSW)) return; + + acpibase = sisfb_read_lpc_pci_dword(SiS_Pr, 0x74); + acpibase &= 0xFFFF; + if(!acpibase) return; + temp = SiS_GetRegShort((acpibase + 0x3c)); /* ACPI register 0x3c: GP Event 1 I/O mode select */ + temp &= 0xFEFF; + SiS_SetRegShort((acpibase + 0x3c), temp); + temp = SiS_GetRegShort((acpibase + 0x3c)); + temp = SiS_GetRegShort((acpibase + 0x3a)); /* ACPI register 0x3a: GP Pin Level (low/high) */ + temp &= 0xFEFF; + if(!(myvbinfo & SetCRT2ToTV)) temp |= 0x0100; + SiS_SetRegShort((acpibase + 0x3a), temp); + temp = SiS_GetRegShort((acpibase + 0x3a)); +} +#endif + +void +SiS_GetVBInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, int checkcrt2mode) +{ + unsigned short tempax, tempbx, temp; + unsigned short modeflag, resinfo = 0; + + SiS_Pr->SiS_SetFlag = 0; + + modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + + SiS_Pr->SiS_ModeType = modeflag & ModeTypeMask; + + if((ModeNo > 0x13) && (!SiS_Pr->UseCustomMode)) { + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + } + + tempbx = 0; + + if(SiS_HaveBridge(SiS_Pr)) { + + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + tempbx |= temp; + tempax = SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) << 8; + tempax &= (DriverMode | LoadDACFlag | SetNotSimuMode | SetPALTV); + tempbx |= tempax; + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_VBType & VB_SISLCDA) { + if(ModeNo == 0x03) { + /* Mode 0x03 is never in driver mode */ + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x31,0xbf); + } + if(!(SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) & (DriverMode >> 8))) { + /* Reset LCDA setting if not driver mode */ + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x38,0xfc); + } + if(IS_SIS650) { + if(SiS_Pr->SiS_UseLCDA) { + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x5f) & 0xF0) { + if((ModeNo <= 0x13) || (!(SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) & (DriverMode >> 8)))) { + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x38,(EnableDualEdge | SetToLCDA)); + } + } + } + } + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + if((temp & (EnableDualEdge | SetToLCDA)) == (EnableDualEdge | SetToLCDA)) { + tempbx |= SetCRT2ToLCDA; + } + } + + if(SiS_Pr->ChipType >= SIS_661) { /* New CR layout */ + tempbx &= ~(SetCRT2ToYPbPr525750 | SetCRT2ToHiVision); + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x38) & 0x04) { + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x35) & 0xe0; + if(temp == 0x60) tempbx |= SetCRT2ToHiVision; + else if(SiS_Pr->SiS_VBType & VB_SISYPBPR) { + tempbx |= SetCRT2ToYPbPr525750; + } + } + } + + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + if(temp & SetToLCDA) { + tempbx |= SetCRT2ToLCDA; + } + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(temp & EnableCHYPbPr) { + tempbx |= SetCRT2ToCHYPbPr; + } + } + } + } + +#endif /* CONFIG_FB_SIS_315 */ + + if(!(SiS_Pr->SiS_VBType & VB_SISVGA2)) { + tempbx &= ~(SetCRT2ToRAMDAC); + } + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + temp = SetCRT2ToSVIDEO | + SetCRT2ToAVIDEO | + SetCRT2ToSCART | + SetCRT2ToLCDA | + SetCRT2ToLCD | + SetCRT2ToRAMDAC | + SetCRT2ToHiVision | + SetCRT2ToYPbPr525750; + } else { + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + temp = SetCRT2ToAVIDEO | + SetCRT2ToSVIDEO | + SetCRT2ToSCART | + SetCRT2ToLCDA | + SetCRT2ToLCD | + SetCRT2ToCHYPbPr; + } else { + temp = SetCRT2ToLCDA | + SetCRT2ToLCD; + } + } else { + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + temp = SetCRT2ToTV | SetCRT2ToLCD; + } else { + temp = SetCRT2ToLCD; + } + } + } + + if(!(tempbx & temp)) { + tempax = DisableCRT2Display; + tempbx = 0; + } + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + unsigned short clearmask = ( DriverMode | + DisableCRT2Display | + LoadDACFlag | + SetNotSimuMode | + SetInSlaveMode | + SetPALTV | + SwitchCRT2 | + SetSimuScanMode ); + + if(tempbx & SetCRT2ToLCDA) tempbx &= (clearmask | SetCRT2ToLCDA); + if(tempbx & SetCRT2ToRAMDAC) tempbx &= (clearmask | SetCRT2ToRAMDAC); + if(tempbx & SetCRT2ToLCD) tempbx &= (clearmask | SetCRT2ToLCD); + if(tempbx & SetCRT2ToSCART) tempbx &= (clearmask | SetCRT2ToSCART); + if(tempbx & SetCRT2ToHiVision) tempbx &= (clearmask | SetCRT2ToHiVision); + if(tempbx & SetCRT2ToYPbPr525750) tempbx &= (clearmask | SetCRT2ToYPbPr525750); + + } else { + + if(SiS_Pr->ChipType >= SIS_315H) { + if(tempbx & SetCRT2ToLCDA) { + tempbx &= (0xFF00|SwitchCRT2|SetSimuScanMode); + } + } + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(tempbx & SetCRT2ToTV) { + tempbx &= (0xFF00|SetCRT2ToTV|SwitchCRT2|SetSimuScanMode); + } + } + if(tempbx & SetCRT2ToLCD) { + tempbx &= (0xFF00|SetCRT2ToLCD|SwitchCRT2|SetSimuScanMode); + } + if(SiS_Pr->ChipType >= SIS_315H) { + if(tempbx & SetCRT2ToLCDA) { + tempbx |= SetCRT2ToLCD; + } + } + + } + + if(tempax & DisableCRT2Display) { + if(!(tempbx & (SwitchCRT2 | SetSimuScanMode))) { + tempbx = SetSimuScanMode | DisableCRT2Display; + } + } + + if(!(tempbx & DriverMode)) tempbx |= SetSimuScanMode; + + /* LVDS/CHRONTEL (LCD/TV) and 301BDH (LCD) can only be slave in 8bpp modes */ + if(SiS_Pr->SiS_ModeType <= ModeVGA) { + if( (SiS_Pr->SiS_IF_DEF_LVDS == 1) || + ((SiS_Pr->SiS_VBType & VB_NoLCD) && (tempbx & SetCRT2ToLCD)) ) { + modeflag &= (~CRT2Mode); + } + } + + if(!(tempbx & SetSimuScanMode)) { + if(tempbx & SwitchCRT2) { + if((!(modeflag & CRT2Mode)) && (checkcrt2mode)) { + if(resinfo != SIS_RI_1600x1200) { + tempbx |= SetSimuScanMode; + } + } + } else { + if(SiS_BridgeIsEnabled(SiS_Pr)) { + if(!(tempbx & DriverMode)) { + if(SiS_BridgeInSlavemode(SiS_Pr)) { + tempbx |= SetSimuScanMode; + } + } + } + } + } + + if(!(tempbx & DisableCRT2Display)) { + if(tempbx & DriverMode) { + if(tempbx & SetSimuScanMode) { + if((!(modeflag & CRT2Mode)) && (checkcrt2mode)) { + if(resinfo != SIS_RI_1600x1200) { + tempbx |= SetInSlaveMode; + } + } + } + } else { + tempbx |= SetInSlaveMode; + } + } + + } + + SiS_Pr->SiS_VBInfo = tempbx; + +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->ChipType == SIS_630) { + SiS_SetChrontelGPIO(SiS_Pr, SiS_Pr->SiS_VBInfo); + } +#endif + +#if 0 + printk(KERN_DEBUG "sisfb: (init301: VBInfo= 0x%04x, SetFlag=0x%04x)\n", + SiS_Pr->SiS_VBInfo, SiS_Pr->SiS_SetFlag); +#endif +} + +/*********************************************/ +/* DETERMINE YPbPr MODE */ +/*********************************************/ + +void +SiS_SetYPbPr(struct SiS_Private *SiS_Pr) +{ + + unsigned char temp; + + /* Note: This variable is only used on 30xLV systems. + * CR38 has a different meaning on LVDS/CH7019 systems. + * On 661 and later, these bits moved to CR35. + * + * On 301, 301B, only HiVision 1080i is supported. + * On 30xLV, 301C, only YPbPr 1080i is supported. + */ + + SiS_Pr->SiS_YPbPr = 0; + if(SiS_Pr->ChipType >= SIS_661) return; + + if(SiS_Pr->SiS_VBType) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + SiS_Pr->SiS_YPbPr = YPbPrHiVision; + } + } + + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_VBType & VB_SISYPBPR) { + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + if(temp & 0x08) { + switch((temp >> 4)) { + case 0x00: SiS_Pr->SiS_YPbPr = YPbPr525i; break; + case 0x01: SiS_Pr->SiS_YPbPr = YPbPr525p; break; + case 0x02: SiS_Pr->SiS_YPbPr = YPbPr750p; break; + case 0x03: SiS_Pr->SiS_YPbPr = YPbPrHiVision; break; + } + } + } + } + +} + +/*********************************************/ +/* DETERMINE TVMode flag */ +/*********************************************/ + +void +SiS_SetTVMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short temp, temp1, resinfo = 0, romindex = 0; + unsigned char OutputSelect = *SiS_Pr->pSiS_OutputSelect; + + SiS_Pr->SiS_TVMode = 0; + + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) return; + if(SiS_Pr->UseCustomMode) return; + + if(ModeNo > 0x13) { + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + } + + if(SiS_Pr->ChipType < SIS_661) { + + if(SiS_Pr->SiS_VBInfo & SetPALTV) SiS_Pr->SiS_TVMode |= TVSetPAL; + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + temp = 0; + if((SiS_Pr->ChipType == SIS_630) || + (SiS_Pr->ChipType == SIS_730)) { + temp = 0x35; + romindex = 0xfe; + } else if(SiS_Pr->ChipType >= SIS_315H) { + temp = 0x38; + if(SiS_Pr->ChipType < XGI_20) { + romindex = 0xf3; + if(SiS_Pr->ChipType >= SIS_330) romindex = 0x11b; + } + } + if(temp) { + if(romindex && SiS_Pr->SiS_UseROM && (!(SiS_Pr->SiS_ROMNew))) { + OutputSelect = ROMAddr[romindex]; + if(!(OutputSelect & EnablePALMN)) { + SiS_SetRegAND(SiS_Pr->SiS_P3d4,temp,0x3F); + } + } + temp1 = SiS_GetReg(SiS_Pr->SiS_P3d4,temp); + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + if(temp1 & EnablePALM) { /* 0x40 */ + SiS_Pr->SiS_TVMode |= TVSetPALM; + SiS_Pr->SiS_TVMode &= ~TVSetPAL; + } else if(temp1 & EnablePALN) { /* 0x80 */ + SiS_Pr->SiS_TVMode |= TVSetPALN; + } + } else { + if(temp1 & EnableNTSCJ) { /* 0x40 */ + SiS_Pr->SiS_TVMode |= TVSetNTSCJ; + } + } + } + /* Translate HiVision/YPbPr to our new flags */ + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + if(SiS_Pr->SiS_YPbPr == YPbPr750p) SiS_Pr->SiS_TVMode |= TVSetYPbPr750p; + else if(SiS_Pr->SiS_YPbPr == YPbPr525p) SiS_Pr->SiS_TVMode |= TVSetYPbPr525p; + else if(SiS_Pr->SiS_YPbPr == YPbPrHiVision) SiS_Pr->SiS_TVMode |= TVSetHiVision; + else SiS_Pr->SiS_TVMode |= TVSetYPbPr525i; + if(SiS_Pr->SiS_TVMode & (TVSetYPbPr750p | TVSetYPbPr525p | TVSetYPbPr525i)) { + SiS_Pr->SiS_VBInfo &= ~SetCRT2ToHiVision; + SiS_Pr->SiS_VBInfo |= SetCRT2ToYPbPr525750; + } else if(SiS_Pr->SiS_TVMode & TVSetHiVision) { + SiS_Pr->SiS_TVMode |= TVSetPAL; + } + } + } else if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_Pr->SiS_CHOverScan) { + if(SiS_Pr->SiS_IF_DEF_CH70xx == 1) { + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x35); + if((temp & TVOverScan) || (SiS_Pr->SiS_CHOverScan == 1)) { + SiS_Pr->SiS_TVMode |= TVSetCHOverScan; + } + } else if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x79); + if((temp & 0x80) || (SiS_Pr->SiS_CHOverScan == 1)) { + SiS_Pr->SiS_TVMode |= TVSetCHOverScan; + } + } + if(SiS_Pr->SiS_CHSOverScan) { + SiS_Pr->SiS_TVMode |= TVSetCHOverScan; + } + } + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x38); + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + if(temp & EnablePALM) SiS_Pr->SiS_TVMode |= TVSetPALM; + else if(temp & EnablePALN) SiS_Pr->SiS_TVMode |= TVSetPALN; + } else { + if(temp & EnableNTSCJ) { + SiS_Pr->SiS_TVMode |= TVSetNTSCJ; + } + } + } + } + + } else { /* 661 and later */ + + temp1 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x35); + if(temp1 & 0x01) { + SiS_Pr->SiS_TVMode |= TVSetPAL; + if(temp1 & 0x08) { + SiS_Pr->SiS_TVMode |= TVSetPALN; + } else if(temp1 & 0x04) { + if(SiS_Pr->SiS_VBType & VB_SISVB) { + SiS_Pr->SiS_TVMode &= ~TVSetPAL; + } + SiS_Pr->SiS_TVMode |= TVSetPALM; + } + } else { + if(temp1 & 0x02) { + SiS_Pr->SiS_TVMode |= TVSetNTSCJ; + } + } + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(SiS_Pr->SiS_CHOverScan) { + if((temp1 & 0x10) || (SiS_Pr->SiS_CHOverScan == 1)) { + SiS_Pr->SiS_TVMode |= TVSetCHOverScan; + } + } + } + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + temp1 &= 0xe0; + if(temp1 == 0x00) SiS_Pr->SiS_TVMode |= TVSetYPbPr525i; + else if(temp1 == 0x20) SiS_Pr->SiS_TVMode |= TVSetYPbPr525p; + else if(temp1 == 0x40) SiS_Pr->SiS_TVMode |= TVSetYPbPr750p; + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + SiS_Pr->SiS_TVMode |= (TVSetHiVision | TVSetPAL); + } + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToYPbPr525750 | SetCRT2ToHiVision)) { + if(resinfo == SIS_RI_800x480 || resinfo == SIS_RI_1024x576 || resinfo == SIS_RI_1280x720) { + SiS_Pr->SiS_TVMode |= TVAspect169; + } else { + temp1 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x39); + if(temp1 & 0x02) { + if(SiS_Pr->SiS_TVMode & (TVSetYPbPr750p | TVSetHiVision)) { + SiS_Pr->SiS_TVMode |= TVAspect169; + } else { + SiS_Pr->SiS_TVMode |= TVAspect43LB; + } + } else { + SiS_Pr->SiS_TVMode |= TVAspect43; + } + } + } + } + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToSCART) SiS_Pr->SiS_TVMode |= TVSetPAL; + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + SiS_Pr->SiS_TVMode |= TVSetPAL; + SiS_Pr->SiS_TVMode &= ~(TVSetPALM | TVSetPALN | TVSetNTSCJ); + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(SiS_Pr->SiS_TVMode & (TVSetYPbPr525i | TVSetYPbPr525p | TVSetYPbPr750p)) { + SiS_Pr->SiS_TVMode &= ~(TVSetPAL | TVSetNTSCJ | TVSetPALM | TVSetPALN); + } + } + + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(!(SiS_Pr->SiS_VBInfo & SetNotSimuMode)) { + SiS_Pr->SiS_TVMode |= TVSetTVSimuMode; + } + } + + if(!(SiS_Pr->SiS_TVMode & TVSetPAL)) { + if(resinfo == SIS_RI_1024x768) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) { + SiS_Pr->SiS_TVMode |= TVSet525p1024; + } else if(!(SiS_Pr->SiS_TVMode & (TVSetHiVision | TVSetYPbPr750p))) { + SiS_Pr->SiS_TVMode |= TVSetNTSC1024; + } + } + } + + SiS_Pr->SiS_TVMode |= TVRPLLDIV2XO; + if((SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) && + (SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + SiS_Pr->SiS_TVMode &= ~TVRPLLDIV2XO; + } else if(SiS_Pr->SiS_TVMode & (TVSetYPbPr525p | TVSetYPbPr750p)) { + SiS_Pr->SiS_TVMode &= ~TVRPLLDIV2XO; + } else if(!(SiS_Pr->SiS_VBType & VB_SIS30xBLV)) { + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) { + SiS_Pr->SiS_TVMode &= ~TVRPLLDIV2XO; + } + } + + } + + SiS_Pr->SiS_VBInfo &= ~SetPALTV; +} + +/*********************************************/ +/* GET LCD INFO */ +/*********************************************/ + +static unsigned short +SiS_GetBIOSLCDResInfo(struct SiS_Private *SiS_Pr) +{ + unsigned short temp = SiS_Pr->SiS_LCDResInfo; + /* Translate my LCDResInfo to BIOS value */ + switch(temp) { + case Panel_1280x768_2: temp = Panel_1280x768; break; + case Panel_1280x800_2: temp = Panel_1280x800; break; + case Panel_1280x854: temp = Panel661_1280x854; break; + } + return temp; +} + +static void +SiS_GetLCDInfoBIOS(struct SiS_Private *SiS_Pr) +{ +#ifdef CONFIG_FB_SIS_315 + unsigned char *ROMAddr; + unsigned short temp; + + if((ROMAddr = GetLCDStructPtr661(SiS_Pr))) { + if((temp = SISGETROMW(6)) != SiS_Pr->PanelHT) { + SiS_Pr->SiS_NeedRomModeData = true; + SiS_Pr->PanelHT = temp; + } + if((temp = SISGETROMW(8)) != SiS_Pr->PanelVT) { + SiS_Pr->SiS_NeedRomModeData = true; + SiS_Pr->PanelVT = temp; + } + SiS_Pr->PanelHRS = SISGETROMW(10); + SiS_Pr->PanelHRE = SISGETROMW(12); + SiS_Pr->PanelVRS = SISGETROMW(14); + SiS_Pr->PanelVRE = SISGETROMW(16); + SiS_Pr->PanelVCLKIdx315 = VCLK_CUSTOM_315; + SiS_Pr->SiS_VCLKData[VCLK_CUSTOM_315].CLOCK = + SiS_Pr->SiS_VBVCLKData[VCLK_CUSTOM_315].CLOCK = (unsigned short)((unsigned char)ROMAddr[18]); + SiS_Pr->SiS_VCLKData[VCLK_CUSTOM_315].SR2B = + SiS_Pr->SiS_VBVCLKData[VCLK_CUSTOM_315].Part4_A = ROMAddr[19]; + SiS_Pr->SiS_VCLKData[VCLK_CUSTOM_315].SR2C = + SiS_Pr->SiS_VBVCLKData[VCLK_CUSTOM_315].Part4_B = ROMAddr[20]; + + } +#endif +} + +static void +SiS_CheckScaling(struct SiS_Private *SiS_Pr, unsigned short resinfo, + const unsigned char *nonscalingmodes) +{ + int i = 0; + while(nonscalingmodes[i] != 0xff) { + if(nonscalingmodes[i++] == resinfo) { + if((SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) || + (SiS_Pr->UsePanelScaler == -1)) { + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + break; + } + } +} + +void +SiS_GetLCDResInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short temp,modeflag,resinfo=0,modexres=0,modeyres=0; + bool panelcanscale = false; +#ifdef CONFIG_FB_SIS_300 + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + static const unsigned char SiS300SeriesLCDRes[] = + { 0, 1, 2, 3, 7, 4, 5, 8, + 0, 0, 10, 0, 0, 0, 0, 15 }; +#endif +#ifdef CONFIG_FB_SIS_315 + unsigned char *myptr = NULL; +#endif + + SiS_Pr->SiS_LCDResInfo = 0; + SiS_Pr->SiS_LCDTypeInfo = 0; + SiS_Pr->SiS_LCDInfo = 0; + SiS_Pr->PanelHRS = 999; /* HSync start */ + SiS_Pr->PanelHRE = 999; /* HSync end */ + SiS_Pr->PanelVRS = 999; /* VSync start */ + SiS_Pr->PanelVRE = 999; /* VSync end */ + SiS_Pr->SiS_NeedRomModeData = false; + + /* Alternative 1600x1200@60 timing for 1600x1200 LCDA */ + SiS_Pr->Alternate1600x1200 = false; + + if(!(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA))) return; + + modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + + if((ModeNo > 0x13) && (!SiS_Pr->UseCustomMode)) { + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + modexres = SiS_Pr->SiS_ModeResInfo[resinfo].HTotal; + modeyres = SiS_Pr->SiS_ModeResInfo[resinfo].VTotal; + } + + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36); + + /* For broken BIOSes: Assume 1024x768 */ + if(temp == 0) temp = 0x02; + + if((SiS_Pr->ChipType >= SIS_661) || (SiS_Pr->SiS_ROMNew)) { + SiS_Pr->SiS_LCDTypeInfo = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x39) & 0x7c) >> 2; + } else if((SiS_Pr->ChipType < SIS_315H) || (SiS_Pr->ChipType >= SIS_661)) { + SiS_Pr->SiS_LCDTypeInfo = temp >> 4; + } else { + SiS_Pr->SiS_LCDTypeInfo = (temp & 0x0F) - 1; + } + temp &= 0x0f; +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->ChipType < SIS_315H) { + /* Very old BIOSes only know 7 sizes (NetVista 2179, 1.01g) */ + if(SiS_Pr->SiS_VBType & VB_SIS301) { + if(temp < 0x0f) temp &= 0x07; + } + /* Translate 300 series LCDRes to 315 series for unified usage */ + temp = SiS300SeriesLCDRes[temp]; + } +#endif + + /* Translate to our internal types */ +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType == SIS_550) { + if (temp == Panel310_1152x768) temp = Panel_320x240_2; /* Verified working */ + else if(temp == Panel310_320x240_2) temp = Panel_320x240_2; + else if(temp == Panel310_320x240_3) temp = Panel_320x240_3; + } else if(SiS_Pr->ChipType >= SIS_661) { + if(temp == Panel661_1280x854) temp = Panel_1280x854; + } +#endif + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { /* SiS LVDS */ + if(temp == Panel310_1280x768) { + temp = Panel_1280x768_2; + } + if(SiS_Pr->SiS_ROMNew) { + if(temp == Panel661_1280x800) { + temp = Panel_1280x800_2; + } + } + } + + SiS_Pr->SiS_LCDResInfo = temp; + +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + if(SiS_Pr->SiS_CustomT == CUT_BARCO1366) { + SiS_Pr->SiS_LCDResInfo = Panel_Barco1366; + } else if(SiS_Pr->SiS_CustomT == CUT_PANEL848) { + SiS_Pr->SiS_LCDResInfo = Panel_848x480; + } else if(SiS_Pr->SiS_CustomT == CUT_PANEL856) { + SiS_Pr->SiS_LCDResInfo = Panel_856x480; + } + } +#endif + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->SiS_LCDResInfo < SiS_Pr->SiS_PanelMin301) + SiS_Pr->SiS_LCDResInfo = SiS_Pr->SiS_PanelMin301; + } else { + if(SiS_Pr->SiS_LCDResInfo < SiS_Pr->SiS_PanelMinLVDS) + SiS_Pr->SiS_LCDResInfo = SiS_Pr->SiS_PanelMinLVDS; + } + + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x37); + SiS_Pr->SiS_LCDInfo = temp & ~0x000e; + /* Need temp below! */ + + /* These must/can't scale no matter what */ + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_320x240_1: + case Panel_320x240_2: + case Panel_320x240_3: + case Panel_1280x960: + SiS_Pr->SiS_LCDInfo &= ~DontExpandLCD; + break; + case Panel_640x480: + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + + panelcanscale = (bool)(SiS_Pr->SiS_LCDInfo & DontExpandLCD); + + if(!SiS_Pr->UsePanelScaler) SiS_Pr->SiS_LCDInfo &= ~DontExpandLCD; + else if(SiS_Pr->UsePanelScaler == 1) SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + + /* Dual link, Pass 1:1 BIOS default, etc. */ +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_661) { + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + if(temp & 0x08) SiS_Pr->SiS_LCDInfo |= LCDPass11; + } + if(SiS_Pr->SiS_VBType & VB_SISDUALLINK) { + if(SiS_Pr->SiS_ROMNew) { + if(temp & 0x02) SiS_Pr->SiS_LCDInfo |= LCDDualLink; + } else if((myptr = GetLCDStructPtr661(SiS_Pr))) { + if(myptr[2] & 0x01) SiS_Pr->SiS_LCDInfo |= LCDDualLink; + } + } + } else if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x39) & 0x01) SiS_Pr->SiS_LCDInfo |= LCDPass11; + } + if((SiS_Pr->SiS_ROMNew) && (!(SiS_Pr->PanelSelfDetected))) { + SiS_Pr->SiS_LCDInfo &= ~(LCDRGB18Bit); + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x35); + if(temp & 0x01) SiS_Pr->SiS_LCDInfo |= LCDRGB18Bit; + if(SiS_Pr->SiS_VBType & VB_SISDUALLINK) { + if(temp & 0x02) SiS_Pr->SiS_LCDInfo |= LCDDualLink; + } + } else if(!(SiS_Pr->SiS_ROMNew)) { + if(SiS_Pr->SiS_VBType & VB_SISDUALLINK) { + if((SiS_Pr->SiS_CustomT == CUT_CLEVO1024) && + (SiS_Pr->SiS_LCDResInfo == Panel_1024x768)) { + SiS_Pr->SiS_LCDInfo |= LCDDualLink; + } + if((SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) || + (SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) || + (SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) || + (SiS_Pr->SiS_LCDResInfo == Panel_1680x1050)) { + SiS_Pr->SiS_LCDInfo |= LCDDualLink; + } + } + } + } +#endif + + /* Pass 1:1 */ + if((SiS_Pr->SiS_IF_DEF_LVDS == 1) || (SiS_Pr->SiS_VBType & VB_NoLCD)) { + /* Always center screen on LVDS (if scaling is disabled) */ + SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + /* Always center screen on SiS LVDS (if scaling is disabled) */ + SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + } else { + /* By default, pass 1:1 on SiS TMDS (if scaling is supported) */ + if(panelcanscale) SiS_Pr->SiS_LCDInfo |= LCDPass11; + if(SiS_Pr->CenterScreen == 1) SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + } + } + + SiS_Pr->PanelVCLKIdx300 = VCLK65_300; + SiS_Pr->PanelVCLKIdx315 = VCLK108_2_315; + + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_320x240_1: + case Panel_320x240_2: + case Panel_320x240_3: SiS_Pr->PanelXRes = 640; SiS_Pr->PanelYRes = 480; + SiS_Pr->PanelVRS = 24; SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx300 = VCLK28; + SiS_Pr->PanelVCLKIdx315 = VCLK28; + break; + case Panel_640x480: SiS_Pr->PanelXRes = 640; SiS_Pr->PanelYRes = 480; + SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx300 = VCLK28; + SiS_Pr->PanelVCLKIdx315 = VCLK28; + break; + case Panel_800x600: SiS_Pr->PanelXRes = 800; SiS_Pr->PanelYRes = 600; + SiS_Pr->PanelHT = 1056; SiS_Pr->PanelVT = 628; + SiS_Pr->PanelHRS = 40; SiS_Pr->PanelHRE = 128; + SiS_Pr->PanelVRS = 1; SiS_Pr->PanelVRE = 4; + SiS_Pr->PanelVCLKIdx300 = VCLK40; + SiS_Pr->PanelVCLKIdx315 = VCLK40; + break; + case Panel_1024x600: SiS_Pr->PanelXRes = 1024; SiS_Pr->PanelYRes = 600; + SiS_Pr->PanelHT = 1344; SiS_Pr->PanelVT = 800; + SiS_Pr->PanelHRS = 24; SiS_Pr->PanelHRE = 136; + SiS_Pr->PanelVRS = 2 /* 88 */ ; SiS_Pr->PanelVRE = 6; + SiS_Pr->PanelVCLKIdx300 = VCLK65_300; + SiS_Pr->PanelVCLKIdx315 = VCLK65_315; + break; + case Panel_1024x768: SiS_Pr->PanelXRes = 1024; SiS_Pr->PanelYRes = 768; + SiS_Pr->PanelHT = 1344; SiS_Pr->PanelVT = 806; + SiS_Pr->PanelHRS = 24; SiS_Pr->PanelHRE = 136; + SiS_Pr->PanelVRS = 3; SiS_Pr->PanelVRE = 6; + if(SiS_Pr->ChipType < SIS_315H) { + SiS_Pr->PanelHRS = 23; + SiS_Pr->PanelVRE = 5; + } + SiS_Pr->PanelVCLKIdx300 = VCLK65_300; + SiS_Pr->PanelVCLKIdx315 = VCLK65_315; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1152x768: SiS_Pr->PanelXRes = 1152; SiS_Pr->PanelYRes = 768; + SiS_Pr->PanelHT = 1344; SiS_Pr->PanelVT = 806; + SiS_Pr->PanelHRS = 24; SiS_Pr->PanelHRE = 136; + SiS_Pr->PanelVRS = 3; SiS_Pr->PanelVRE = 6; + if(SiS_Pr->ChipType < SIS_315H) { + SiS_Pr->PanelHRS = 23; + SiS_Pr->PanelVRE = 5; + } + SiS_Pr->PanelVCLKIdx300 = VCLK65_300; + SiS_Pr->PanelVCLKIdx315 = VCLK65_315; + break; + case Panel_1152x864: SiS_Pr->PanelXRes = 1152; SiS_Pr->PanelYRes = 864; + break; + case Panel_1280x720: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 720; + SiS_Pr->PanelHT = 1650; SiS_Pr->PanelVT = 750; + SiS_Pr->PanelHRS = 110; SiS_Pr->PanelHRE = 40; + SiS_Pr->PanelVRS = 5; SiS_Pr->PanelVRE = 5; + SiS_Pr->PanelVCLKIdx315 = VCLK_1280x720; + /* Data above for TMDS (projector); get from BIOS for LVDS */ + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1280x768: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 768; + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + SiS_Pr->PanelHT = 1408; SiS_Pr->PanelVT = 806; + SiS_Pr->PanelVCLKIdx300 = VCLK81_300; /* ? */ + SiS_Pr->PanelVCLKIdx315 = VCLK81_315; /* ? */ + } else { + SiS_Pr->PanelHT = 1688; SiS_Pr->PanelVT = 802; + SiS_Pr->PanelHRS = 48; SiS_Pr->PanelHRS = 112; + SiS_Pr->PanelVRS = 3; SiS_Pr->PanelVRE = 6; + SiS_Pr->PanelVCLKIdx300 = VCLK81_300; + SiS_Pr->PanelVCLKIdx315 = VCLK81_315; + } + break; + case Panel_1280x768_2: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 768; + SiS_Pr->PanelHT = 1660; SiS_Pr->PanelVT = 806; + SiS_Pr->PanelHRS = 48; SiS_Pr->PanelHRE = 112; + SiS_Pr->PanelVRS = 3; SiS_Pr->PanelVRE = 6; + SiS_Pr->PanelVCLKIdx315 = VCLK_1280x768_2; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1280x800: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 800; + SiS_Pr->PanelHT = 1408; SiS_Pr->PanelVT = 816; + SiS_Pr->PanelHRS = 21; SiS_Pr->PanelHRE = 24; + SiS_Pr->PanelVRS = 4; SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx315 = VCLK_1280x800_315; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1280x800_2: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 800; + SiS_Pr->PanelHT = 1552; SiS_Pr->PanelVT = 812; + SiS_Pr->PanelHRS = 48; SiS_Pr->PanelHRE = 112; + SiS_Pr->PanelVRS = 4; SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx315 = VCLK_1280x800_315_2; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1280x854: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 854; + SiS_Pr->PanelHT = 1664; SiS_Pr->PanelVT = 861; + SiS_Pr->PanelHRS = 16; SiS_Pr->PanelHRE = 112; + SiS_Pr->PanelVRS = 1; SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx315 = VCLK_1280x854; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1280x960: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 960; + SiS_Pr->PanelHT = 1800; SiS_Pr->PanelVT = 1000; + SiS_Pr->PanelVCLKIdx300 = VCLK108_3_300; + SiS_Pr->PanelVCLKIdx315 = VCLK108_3_315; + if(resinfo == SIS_RI_1280x1024) { + SiS_Pr->PanelVCLKIdx300 = VCLK100_300; + SiS_Pr->PanelVCLKIdx315 = VCLK100_315; + } + break; + case Panel_1280x1024: SiS_Pr->PanelXRes = 1280; SiS_Pr->PanelYRes = 1024; + SiS_Pr->PanelHT = 1688; SiS_Pr->PanelVT = 1066; + SiS_Pr->PanelHRS = 48; SiS_Pr->PanelHRE = 112; + SiS_Pr->PanelVRS = 1; SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx300 = VCLK108_3_300; + SiS_Pr->PanelVCLKIdx315 = VCLK108_2_315; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1400x1050: SiS_Pr->PanelXRes = 1400; SiS_Pr->PanelYRes = 1050; + SiS_Pr->PanelHT = 1688; SiS_Pr->PanelVT = 1066; + SiS_Pr->PanelHRS = 48; SiS_Pr->PanelHRE = 112; + SiS_Pr->PanelVRS = 1; SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx315 = VCLK108_2_315; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1600x1200: SiS_Pr->PanelXRes = 1600; SiS_Pr->PanelYRes = 1200; + SiS_Pr->PanelHT = 2160; SiS_Pr->PanelVT = 1250; + SiS_Pr->PanelHRS = 64; SiS_Pr->PanelHRE = 192; + SiS_Pr->PanelVRS = 1; SiS_Pr->PanelVRE = 3; + SiS_Pr->PanelVCLKIdx315 = VCLK162_315; + if(SiS_Pr->SiS_VBType & VB_SISTMDSLCDA) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_Pr->PanelHT = 1760; SiS_Pr->PanelVT = 1235; + SiS_Pr->PanelHRS = 48; SiS_Pr->PanelHRE = 32; + SiS_Pr->PanelVRS = 2; SiS_Pr->PanelVRE = 4; + SiS_Pr->PanelVCLKIdx315 = VCLK130_315; + SiS_Pr->Alternate1600x1200 = true; + } + } else if(SiS_Pr->SiS_IF_DEF_LVDS) { + SiS_Pr->PanelHT = 2048; SiS_Pr->PanelVT = 1320; + SiS_Pr->PanelHRS = SiS_Pr->PanelHRE = 999; + SiS_Pr->PanelVRS = SiS_Pr->PanelVRE = 999; + } + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_1680x1050: SiS_Pr->PanelXRes = 1680; SiS_Pr->PanelYRes = 1050; + SiS_Pr->PanelHT = 1900; SiS_Pr->PanelVT = 1066; + SiS_Pr->PanelHRS = 26; SiS_Pr->PanelHRE = 76; + SiS_Pr->PanelVRS = 3; SiS_Pr->PanelVRE = 6; + SiS_Pr->PanelVCLKIdx315 = VCLK121_315; + SiS_GetLCDInfoBIOS(SiS_Pr); + break; + case Panel_Barco1366: SiS_Pr->PanelXRes = 1360; SiS_Pr->PanelYRes = 1024; + SiS_Pr->PanelHT = 1688; SiS_Pr->PanelVT = 1066; + break; + case Panel_848x480: SiS_Pr->PanelXRes = 848; SiS_Pr->PanelYRes = 480; + SiS_Pr->PanelHT = 1088; SiS_Pr->PanelVT = 525; + break; + case Panel_856x480: SiS_Pr->PanelXRes = 856; SiS_Pr->PanelYRes = 480; + SiS_Pr->PanelHT = 1088; SiS_Pr->PanelVT = 525; + break; + case Panel_Custom: SiS_Pr->PanelXRes = SiS_Pr->CP_MaxX; + SiS_Pr->PanelYRes = SiS_Pr->CP_MaxY; + SiS_Pr->PanelHT = SiS_Pr->CHTotal; + SiS_Pr->PanelVT = SiS_Pr->CVTotal; + if(SiS_Pr->CP_PreferredIndex != -1) { + SiS_Pr->PanelXRes = SiS_Pr->CP_HDisplay[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelYRes = SiS_Pr->CP_VDisplay[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelHT = SiS_Pr->CP_HTotal[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelVT = SiS_Pr->CP_VTotal[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelHRS = SiS_Pr->CP_HSyncStart[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelHRE = SiS_Pr->CP_HSyncEnd[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelVRS = SiS_Pr->CP_VSyncStart[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelVRE = SiS_Pr->CP_VSyncEnd[SiS_Pr->CP_PreferredIndex]; + SiS_Pr->PanelHRS -= SiS_Pr->PanelXRes; + SiS_Pr->PanelHRE -= SiS_Pr->PanelHRS; + SiS_Pr->PanelVRS -= SiS_Pr->PanelYRes; + SiS_Pr->PanelVRE -= SiS_Pr->PanelVRS; + if(SiS_Pr->CP_PrefClock) { + int idx; + SiS_Pr->PanelVCLKIdx315 = VCLK_CUSTOM_315; + SiS_Pr->PanelVCLKIdx300 = VCLK_CUSTOM_300; + if(SiS_Pr->ChipType < SIS_315H) idx = VCLK_CUSTOM_300; + else idx = VCLK_CUSTOM_315; + SiS_Pr->SiS_VCLKData[idx].CLOCK = + SiS_Pr->SiS_VBVCLKData[idx].CLOCK = SiS_Pr->CP_PrefClock; + SiS_Pr->SiS_VCLKData[idx].SR2B = + SiS_Pr->SiS_VBVCLKData[idx].Part4_A = SiS_Pr->CP_PrefSR2B; + SiS_Pr->SiS_VCLKData[idx].SR2C = + SiS_Pr->SiS_VBVCLKData[idx].Part4_B = SiS_Pr->CP_PrefSR2C; + } + } + break; + default: SiS_Pr->PanelXRes = 1024; SiS_Pr->PanelYRes = 768; + SiS_Pr->PanelHT = 1344; SiS_Pr->PanelVT = 806; + break; + } + + /* Special cases */ + if( (SiS_Pr->SiS_IF_DEF_FSTN) || + (SiS_Pr->SiS_IF_DEF_DSTN) || + (SiS_Pr->SiS_CustomT == CUT_BARCO1366) || + (SiS_Pr->SiS_CustomT == CUT_BARCO1024) || + (SiS_Pr->SiS_CustomT == CUT_PANEL848) || + (SiS_Pr->SiS_CustomT == CUT_PANEL856) ) { + SiS_Pr->PanelHRS = 999; + SiS_Pr->PanelHRE = 999; + } + + if( (SiS_Pr->SiS_CustomT == CUT_BARCO1366) || + (SiS_Pr->SiS_CustomT == CUT_BARCO1024) || + (SiS_Pr->SiS_CustomT == CUT_PANEL848) || + (SiS_Pr->SiS_CustomT == CUT_PANEL856) ) { + SiS_Pr->PanelVRS = 999; + SiS_Pr->PanelVRE = 999; + } + + /* DontExpand overrule */ + if((SiS_Pr->SiS_VBType & VB_SISVB) && (!(SiS_Pr->SiS_VBType & VB_NoLCD))) { + + if((SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) && (modeflag & NoSupportLCDScale)) { + /* No scaling for this mode on any panel (LCD=CRT2)*/ + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + + switch(SiS_Pr->SiS_LCDResInfo) { + + case Panel_Custom: + case Panel_1152x864: + case Panel_1280x768: /* TMDS only */ + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + break; + + case Panel_800x600: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, 0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + break; + } + case Panel_1024x768: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + 0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + break; + } + case Panel_1280x720: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + 0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + if(SiS_Pr->PanelHT == 1650) { + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + break; + } + case Panel_1280x768_2: { /* LVDS only */ + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + switch(resinfo) { + case SIS_RI_1280x720: if(SiS_Pr->UsePanelScaler == -1) { + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + break; + } + break; + } + case Panel_1280x800: { /* SiS TMDS special (Averatec 6200 series) */ + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,SIS_RI_1280x720,SIS_RI_1280x768,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + break; + } + case Panel_1280x800_2: { /* SiS LVDS */ + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + switch(resinfo) { + case SIS_RI_1280x720: + case SIS_RI_1280x768: if(SiS_Pr->UsePanelScaler == -1) { + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + break; + } + break; + } + case Panel_1280x854: { /* SiS LVDS */ + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + switch(resinfo) { + case SIS_RI_1280x720: + case SIS_RI_1280x768: + case SIS_RI_1280x800: if(SiS_Pr->UsePanelScaler == -1) { + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + break; + } + break; + } + case Panel_1280x960: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,SIS_RI_1152x864,SIS_RI_1280x720,SIS_RI_1280x768,SIS_RI_1280x800, + SIS_RI_1280x854,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + break; + } + case Panel_1280x1024: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,SIS_RI_1152x864,SIS_RI_1280x720,SIS_RI_1280x768,SIS_RI_1280x800, + SIS_RI_1280x854,SIS_RI_1280x960,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + break; + } + case Panel_1400x1050: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,SIS_RI_1152x864,SIS_RI_1280x768,SIS_RI_1280x800,SIS_RI_1280x854, + SIS_RI_1280x960,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + switch(resinfo) { + case SIS_RI_1280x720: if(SiS_Pr->UsePanelScaler == -1) { + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + } + break; + case SIS_RI_1280x1024: SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + break; + } + break; + } + case Panel_1600x1200: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,SIS_RI_1152x864,SIS_RI_1280x720,SIS_RI_1280x768,SIS_RI_1280x800, + SIS_RI_1280x854,SIS_RI_1280x960,SIS_RI_1360x768,SIS_RI_1360x1024,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + break; + } + case Panel_1680x1050: { + static const unsigned char nonscalingmodes[] = { + SIS_RI_720x480, SIS_RI_720x576, SIS_RI_768x576, SIS_RI_800x480, SIS_RI_848x480, + SIS_RI_856x480, SIS_RI_960x540, SIS_RI_960x600, SIS_RI_1024x576,SIS_RI_1024x600, + SIS_RI_1152x768,SIS_RI_1152x864,SIS_RI_1280x854,SIS_RI_1280x960,SIS_RI_1360x768, + SIS_RI_1360x1024,0xff + }; + SiS_CheckScaling(SiS_Pr, resinfo, nonscalingmodes); + break; + } + } + } + +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + if(SiS_Pr->SiS_CustomT == CUT_PANEL848 || SiS_Pr->SiS_CustomT == CUT_PANEL856) { + SiS_Pr->SiS_LCDInfo = 0x80 | 0x40 | 0x20; /* neg h/v sync, RGB24(D0 = 0) */ + } + } + + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + if(SiS_Pr->SiS_UseROM) { + if((ROMAddr[0x233] == 0x12) && (ROMAddr[0x234] == 0x34)) { + if(!(ROMAddr[0x235] & 0x02)) { + SiS_Pr->SiS_LCDInfo &= (~DontExpandLCD); + } + } + } + } else if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if((SiS_Pr->SiS_SetFlag & SetDOSMode) && ((ModeNo == 0x03) || (ModeNo == 0x10))) { + SiS_Pr->SiS_LCDInfo &= (~DontExpandLCD); + } + } + } +#endif + + /* Special cases */ + + if(modexres == SiS_Pr->PanelXRes && modeyres == SiS_Pr->PanelYRes) { + SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + } + + if(SiS_Pr->SiS_IF_DEF_TRUMPION) { + SiS_Pr->SiS_LCDInfo |= (DontExpandLCD | LCDPass11); + } + + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_640x480: + SiS_Pr->SiS_LCDInfo |= (DontExpandLCD | LCDPass11); + break; + case Panel_1280x800: + /* Don't pass 1:1 by default (TMDS special) */ + if(SiS_Pr->CenterScreen == -1) SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + break; + case Panel_1280x960: + SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + break; + case Panel_Custom: + if((!SiS_Pr->CP_PrefClock) || + (modexres > SiS_Pr->PanelXRes) || (modeyres > SiS_Pr->PanelYRes)) { + SiS_Pr->SiS_LCDInfo |= LCDPass11; + } + break; + } + + if((SiS_Pr->UseCustomMode) || (SiS_Pr->SiS_CustomT == CUT_UNKNOWNLCD)) { + SiS_Pr->SiS_LCDInfo |= (DontExpandLCD | LCDPass11); + } + + /* (In)validate LCDPass11 flag */ + if(!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) { + SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + } + + /* LVDS DDA */ + if(!((SiS_Pr->ChipType < SIS_315H) && (SiS_Pr->SiS_SetFlag & SetDOSMode))) { + + if((SiS_Pr->SiS_IF_DEF_LVDS == 1) || (SiS_Pr->SiS_VBType & VB_NoLCD)) { + if(SiS_Pr->SiS_IF_DEF_TRUMPION == 0) { + if(ModeNo == 0x12) { + if(SiS_Pr->SiS_LCDInfo & LCDPass11) { + SiS_Pr->SiS_SetFlag |= EnableLVDSDDA; + } + } else if(ModeNo > 0x13) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x600) { + if(!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) { + if((resinfo == SIS_RI_800x600) || (resinfo == SIS_RI_400x300)) { + SiS_Pr->SiS_SetFlag |= EnableLVDSDDA; + } + } + } + } + } + } + + if(modeflag & HalfDCLK) { + if(SiS_Pr->SiS_IF_DEF_TRUMPION == 1) { + SiS_Pr->SiS_SetFlag |= EnableLVDSDDA; + } else if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + SiS_Pr->SiS_SetFlag |= EnableLVDSDDA; + } else if(SiS_Pr->SiS_LCDResInfo == Panel_640x480) { + SiS_Pr->SiS_SetFlag |= EnableLVDSDDA; + } else if(ModeNo > 0x13) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(resinfo == SIS_RI_512x384) SiS_Pr->SiS_SetFlag |= EnableLVDSDDA; + } else if(SiS_Pr->SiS_LCDResInfo == Panel_800x600) { + if(resinfo == SIS_RI_400x300) SiS_Pr->SiS_SetFlag |= EnableLVDSDDA; + } + } + } + + } + + /* VESA timing */ + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(SiS_Pr->SiS_VBInfo & SetNotSimuMode) { + SiS_Pr->SiS_SetFlag |= LCDVESATiming; + } + } else { + SiS_Pr->SiS_SetFlag |= LCDVESATiming; + } + +#if 0 + printk(KERN_DEBUG "sisfb: (LCDInfo=0x%04x LCDResInfo=0x%02x LCDTypeInfo=0x%02x)\n", + SiS_Pr->SiS_LCDInfo, SiS_Pr->SiS_LCDResInfo, SiS_Pr->SiS_LCDTypeInfo); +#endif +} + +/*********************************************/ +/* GET VCLK */ +/*********************************************/ + +unsigned short +SiS_GetVCLK2Ptr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short CRT2Index, VCLKIndex = 0, VCLKIndexGEN = 0, VCLKIndexGENCRT = 0; + unsigned short modeflag, resinfo, tempbx; + const unsigned char *CHTVVCLKPtr = NULL; + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + resinfo = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ResInfo; + CRT2Index = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + VCLKIndexGEN = (SiS_GetRegByte((SiS_Pr->SiS_P3ca+0x02)) >> 2) & 0x03; + VCLKIndexGENCRT = VCLKIndexGEN; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + CRT2Index = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + VCLKIndexGEN = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRTVCLK; + VCLKIndexGENCRT = SiS_GetRefCRTVCLK(SiS_Pr, RefreshRateTableIndex, + (SiS_Pr->SiS_SetFlag & ProgrammingCRT2) ? SiS_Pr->SiS_UseWideCRT2 : SiS_Pr->SiS_UseWide); + } + + if(SiS_Pr->SiS_VBType & VB_SISVB) { /* 30x/B/LV */ + + if(SiS_Pr->SiS_SetFlag & ProgrammingCRT2) { + + CRT2Index >>= 6; + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { /* LCD */ + + if(SiS_Pr->ChipType < SIS_315H) { + VCLKIndex = SiS_Pr->PanelVCLKIdx300; + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (SiS_Pr->SiS_LCDInfo & LCDPass11)) { + VCLKIndex = VCLKIndexGEN; + } + } else { + VCLKIndex = SiS_Pr->PanelVCLKIdx315; + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (SiS_Pr->SiS_LCDInfo & LCDPass11)) { + switch(resinfo) { + /* Correct those whose IndexGEN doesn't match VBVCLK array */ + case SIS_RI_720x480: VCLKIndex = VCLK_720x480; break; + case SIS_RI_720x576: VCLKIndex = VCLK_720x576; break; + case SIS_RI_768x576: VCLKIndex = VCLK_768x576; break; + case SIS_RI_848x480: VCLKIndex = VCLK_848x480; break; + case SIS_RI_856x480: VCLKIndex = VCLK_856x480; break; + case SIS_RI_800x480: VCLKIndex = VCLK_800x480; break; + case SIS_RI_1024x576: VCLKIndex = VCLK_1024x576; break; + case SIS_RI_1152x864: VCLKIndex = VCLK_1152x864; break; + case SIS_RI_1280x720: VCLKIndex = VCLK_1280x720; break; + case SIS_RI_1360x768: VCLKIndex = VCLK_1360x768; break; + default: VCLKIndex = VCLKIndexGEN; + } + + if(ModeNo <= 0x13) { + if(SiS_Pr->ChipType <= SIS_315PRO) { + if(SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC == 1) VCLKIndex = 0x42; + } else { + if(SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC == 1) VCLKIndex = 0x00; + } + } + if(SiS_Pr->ChipType <= SIS_315PRO) { + if(VCLKIndex == 0) VCLKIndex = 0x41; + if(VCLKIndex == 1) VCLKIndex = 0x43; + if(VCLKIndex == 4) VCLKIndex = 0x44; + } + } + } + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { /* TV */ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + if(SiS_Pr->SiS_TVMode & TVRPLLDIV2XO) VCLKIndex = HiTVVCLKDIV2; + else VCLKIndex = HiTVVCLK; + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) VCLKIndex = HiTVSimuVCLK; + } else if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) VCLKIndex = YPbPr750pVCLK; + else if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) VCLKIndex = TVVCLKDIV2; + else if(SiS_Pr->SiS_TVMode & TVRPLLDIV2XO) VCLKIndex = TVVCLKDIV2; + else VCLKIndex = TVVCLK; + + if(SiS_Pr->ChipType < SIS_315H) VCLKIndex += TVCLKBASE_300; + else VCLKIndex += TVCLKBASE_315; + + } else { /* VGA2 */ + + VCLKIndex = VCLKIndexGENCRT; + if(SiS_Pr->ChipType < SIS_315H) { + if(ModeNo > 0x13) { + if( (SiS_Pr->ChipType == SIS_630) && + (SiS_Pr->ChipRevision >= 0x30)) { + if(VCLKIndex == 0x14) VCLKIndex = 0x34; + } + /* Better VGA2 clock for 1280x1024@75 */ + if(VCLKIndex == 0x17) VCLKIndex = 0x45; + } + } + } + + } else { /* If not programming CRT2 */ + + VCLKIndex = VCLKIndexGENCRT; + if(SiS_Pr->ChipType < SIS_315H) { + if(ModeNo > 0x13) { + if( (SiS_Pr->ChipType != SIS_630) && + (SiS_Pr->ChipType != SIS_300) ) { + if(VCLKIndex == 0x1b) VCLKIndex = 0x48; + } + } + } + } + + } else { /* LVDS */ + + VCLKIndex = CRT2Index; + + if(SiS_Pr->SiS_SetFlag & ProgrammingCRT2) { + + if( (SiS_Pr->SiS_IF_DEF_CH70xx != 0) && (SiS_Pr->SiS_VBInfo & SetCRT2ToTV) ) { + + VCLKIndex &= 0x1f; + tempbx = 0; + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) tempbx += 1; + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + tempbx += 2; + if(SiS_Pr->SiS_ModeType > ModeVGA) { + if(SiS_Pr->SiS_CHSOverScan) tempbx = 8; + } + if(SiS_Pr->SiS_TVMode & TVSetPALM) { + tempbx = 4; + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) tempbx += 1; + } else if(SiS_Pr->SiS_TVMode & TVSetPALN) { + tempbx = 6; + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) tempbx += 1; + } + } + switch(tempbx) { + case 0: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKUNTSC; break; + case 1: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKONTSC; break; + case 2: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKUPAL; break; + case 3: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKOPAL; break; + case 4: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKUPALM; break; + case 5: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKOPALM; break; + case 6: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKUPALN; break; + case 7: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKOPALN; break; + case 8: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKSOPAL; break; + default: CHTVVCLKPtr = SiS_Pr->SiS_CHTVVCLKOPAL; break; + } + VCLKIndex = CHTVVCLKPtr[VCLKIndex]; + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + + if(SiS_Pr->ChipType < SIS_315H) { + VCLKIndex = SiS_Pr->PanelVCLKIdx300; + } else { + VCLKIndex = SiS_Pr->PanelVCLKIdx315; + } + +#ifdef CONFIG_FB_SIS_300 + /* Special Timing: Barco iQ Pro R series */ + if(SiS_Pr->SiS_CustomT == CUT_BARCO1366) VCLKIndex = 0x44; + + /* Special Timing: 848x480 and 856x480 parallel lvds panels */ + if(SiS_Pr->SiS_CustomT == CUT_PANEL848 || SiS_Pr->SiS_CustomT == CUT_PANEL856) { + if(SiS_Pr->ChipType < SIS_315H) { + VCLKIndex = VCLK34_300; + /* if(resinfo == SIS_RI_1360x768) VCLKIndex = ?; */ + } else { + VCLKIndex = VCLK34_315; + /* if(resinfo == SIS_RI_1360x768) VCLKIndex = ?; */ + } + } +#endif + + } else { + + VCLKIndex = VCLKIndexGENCRT; + if(SiS_Pr->ChipType < SIS_315H) { + if(ModeNo > 0x13) { + if( (SiS_Pr->ChipType == SIS_630) && + (SiS_Pr->ChipRevision >= 0x30) ) { + if(VCLKIndex == 0x14) VCLKIndex = 0x2e; + } + } + } + } + + } else { /* if not programming CRT2 */ + + VCLKIndex = VCLKIndexGENCRT; + if(SiS_Pr->ChipType < SIS_315H) { + if(ModeNo > 0x13) { + if( (SiS_Pr->ChipType != SIS_630) && + (SiS_Pr->ChipType != SIS_300) ) { + if(VCLKIndex == 0x1b) VCLKIndex = 0x48; + } +#if 0 + if(SiS_Pr->ChipType == SIS_730) { + if(VCLKIndex == 0x0b) VCLKIndex = 0x40; /* 1024x768-70 */ + if(VCLKIndex == 0x0d) VCLKIndex = 0x41; /* 1024x768-75 */ + } +#endif + } + } + + } + + } + + return VCLKIndex; +} + +/*********************************************/ +/* SET CRT2 MODE TYPE REGISTERS */ +/*********************************************/ + +static void +SiS_SetCRT2ModeRegs(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short i, j, modeflag, tempah=0; + short tempcl; +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) + unsigned short tempbl; +#endif +#ifdef CONFIG_FB_SIS_315 + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short tempah2, tempbl2; +#endif + + modeflag = SiS_GetModeFlag(SiS_Pr, ModeNo, ModeIdIndex); + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x00,0xAF,0x40); + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2E,0xF7); + + } else { + + for(i=0,j=4; i<3; i++,j++) SiS_SetReg(SiS_Pr->SiS_Part1Port,j,0); + if(SiS_Pr->ChipType >= SIS_315H) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x02,0x7F); + } + + tempcl = SiS_Pr->SiS_ModeType; + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* ---- 300 series ---- */ + + /* For 301BDH: (with LCD via LVDS) */ + if(SiS_Pr->SiS_VBType & VB_NoLCD) { + tempbl = SiS_GetReg(SiS_Pr->SiS_P3c4,0x32); + tempbl &= 0xef; + tempbl |= 0x02; + if((SiS_Pr->SiS_VBInfo & SetCRT2ToTV) || (SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC)) { + tempbl |= 0x10; + tempbl &= 0xfd; + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x32,tempbl); + } + + if(ModeNo > 0x13) { + tempcl -= ModeVGA; + if(tempcl >= 0) { + tempah = ((0x10 >> tempcl) | 0x80); + } + } else tempah = 0x80; + + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) tempah ^= 0xA0; + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* ------- 315/330 series ------ */ + + if(ModeNo > 0x13) { + tempcl -= ModeVGA; + if(tempcl >= 0) { + tempah = (0x08 >> tempcl); + if (tempah == 0) tempah = 1; + tempah |= 0x40; + } + } else tempah = 0x40; + + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) tempah ^= 0x50; + +#endif /* CONFIG_FB_SIS_315 */ + + } + + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) tempah = 0; + + if(SiS_Pr->ChipType < SIS_315H) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x00,tempah); + } else { +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x00,0xa0,tempah); + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(IS_SIS740) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x00,tempah); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x00,0xa0,tempah); + } + } +#endif + } + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + tempah = 0x01; + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + tempah |= 0x02; + } + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC)) { + tempah ^= 0x05; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD)) { + tempah ^= 0x01; + } + } + + if(SiS_Pr->ChipType < SIS_315H) { + + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) tempah = 0; + + tempah = (tempah << 5) & 0xFF; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x01,tempah); + tempah = (tempah >> 5) & 0xFF; + + } else { + + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) tempah = 0x08; + else if(!(SiS_IsDualEdge(SiS_Pr))) tempah |= 0x08; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2E,0xF0,tempah); + tempah &= ~0x08; + + } + + if((SiS_Pr->SiS_ModeType == ModeVGA) && (!(SiS_Pr->SiS_VBInfo & SetInSlaveMode))) { + tempah |= 0x10; + } + + tempah |= 0x80; + if(SiS_Pr->SiS_VBType & VB_SIS301) { + if(SiS_Pr->PanelXRes < 1280 && SiS_Pr->PanelYRes < 960) tempah &= ~0x80; + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(!(SiS_Pr->SiS_TVMode & (TVSetYPbPr750p | TVSetYPbPr525p))) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + tempah |= 0x20; + } + } + } + + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x0D,0x40,tempah); + + tempah = 0x80; + if(SiS_Pr->SiS_VBType & VB_SIS301) { + if(SiS_Pr->PanelXRes < 1280 && SiS_Pr->PanelYRes < 960) tempah = 0; + } + + if(SiS_IsDualLink(SiS_Pr)) tempah |= 0x40; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(SiS_Pr->SiS_TVMode & TVRPLLDIV2XO) { + tempah |= 0x40; + } + } + + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0C,tempah); + + } else { /* LVDS */ + + if(SiS_Pr->ChipType >= SIS_315H) { + +#ifdef CONFIG_FB_SIS_315 + /* LVDS can only be slave in 8bpp modes */ + tempah = 0x80; + if((modeflag & CRT2Mode) && (SiS_Pr->SiS_ModeType > ModeVGA)) { + if(SiS_Pr->SiS_VBInfo & DriverMode) { + tempah |= 0x02; + } + } + + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) tempah |= 0x02; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) tempah ^= 0x01; + + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) tempah = 1; + + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2e,0xF0,tempah); +#endif + + } else { + +#ifdef CONFIG_FB_SIS_300 + tempah = 0; + if( (!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) && (SiS_Pr->SiS_ModeType > ModeVGA) ) { + tempah |= 0x02; + } + tempah <<= 5; + + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) tempah = 0; + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x01,tempah); +#endif + + } + + } + + } /* LCDA */ + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->ChipType >= SIS_315H) { + +#ifdef CONFIG_FB_SIS_315 + /* unsigned char bridgerev = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x01); */ + + /* The following is nearly unpreditable and varies from machine + * to machine. Especially the 301DH seems to be a real trouble + * maker. Some BIOSes simply set the registers (like in the + * NoLCD-if-statements here), some set them according to the + * LCDA stuff. It is very likely that some machines are not + * treated correctly in the following, very case-orientated + * code. What do I do then...? + */ + + /* 740 variants match for 30xB, 301B-DH, 30xLV */ + + if(!(IS_SIS740)) { + tempah = 0x04; /* For all bridges */ + tempbl = 0xfb; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + tempah = 0x00; + if(SiS_IsDualEdge(SiS_Pr)) { + tempbl = 0xff; + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,tempbl,tempah); + } + + /* The following two are responsible for eventually wrong colors + * in TV output. The DH (VB_NoLCD) conditions are unknown; the + * b0 was found in some 651 machine (Pim; P4_23=0xe5); the b1 version + * in a 650 box (Jake). What is the criteria? + * Addendum: Another combination 651+301B-DH(b1) (Rapo) needs same + * treatment like the 651+301B-DH(b0) case. Seems more to be the + * chipset than the bridge revision. + */ + + if((IS_SIS740) || (SiS_Pr->ChipType >= SIS_661) || (SiS_Pr->SiS_ROMNew)) { + tempah = 0x30; + tempbl = 0xc0; + if((SiS_Pr->SiS_VBInfo & DisableCRT2Display) || + ((SiS_Pr->SiS_ROMNew) && (!(ROMAddr[0x5b] & 0x04)))) { + tempah = 0x00; + tempbl = 0x00; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2c,0xcf,tempah); + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x21,0x3f,tempbl); + } else if(SiS_Pr->SiS_VBType & VB_SIS301) { + /* Fixes "TV-blue-bug" on 315+301 */ + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2c,0xcf); /* For 301 */ + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x21,0x3f); + } else if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2c,0x30); /* For 30xLV */ + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x21,0xc0); + } else if(SiS_Pr->SiS_VBType & VB_NoLCD) { /* For 301B-DH */ + tempah = 0x30; tempah2 = 0xc0; + tempbl = 0xcf; tempbl2 = 0x3f; + if(SiS_Pr->SiS_TVBlue == 0) { + tempah = tempah2 = 0x00; + } else if(SiS_Pr->SiS_TVBlue == -1) { + /* Set on 651/M650, clear on 315/650 */ + if(!(IS_SIS65x)) /* (bridgerev != 0xb0) */ { + tempah = tempah2 = 0x00; + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2c,tempbl,tempah); + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x21,tempbl2,tempah2); + } else { + tempah = 0x30; tempah2 = 0xc0; /* For 30xB, 301C */ + tempbl = 0xcf; tempbl2 = 0x3f; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + tempah = tempah2 = 0x00; + if(SiS_IsDualEdge(SiS_Pr)) { + tempbl = tempbl2 = 0xff; + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2c,tempbl,tempah); + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x21,tempbl2,tempah2); + } + + if(IS_SIS740) { + tempah = 0x80; + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) tempah = 0x00; + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x23,0x7f,tempah); + } else { + tempah = 0x00; + tempbl = 0x7f; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + tempbl = 0xff; + if(!(SiS_IsDualEdge(SiS_Pr))) tempah = 0x80; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x23,tempbl,tempah); + } + +#endif /* CONFIG_FB_SIS_315 */ + + } else if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + +#ifdef CONFIG_FB_SIS_300 + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x21,0x3f); + + if((SiS_Pr->SiS_VBInfo & DisableCRT2Display) || + ((SiS_Pr->SiS_VBType & VB_NoLCD) && + (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD))) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x23,0x7F); + } else { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x23,0x80); + } +#endif + + } + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x0D,0x80); + if(SiS_Pr->SiS_VBType & VB_SIS30xCLV) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x3A,0xC0); + } + } + + } else { /* LVDS */ + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + + tempah = 0x04; + tempbl = 0xfb; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + tempah = 0x00; + if(SiS_IsDualEdge(SiS_Pr)) tempbl = 0xff; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,tempbl,tempah); + + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x13,0xfb); + } + + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2c,0x30); + + } else if(SiS_Pr->ChipType == SIS_550) { + + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x13,0xfb); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2c,0x30); + + } + + } +#endif + + } + +} + +/*********************************************/ +/* GET RESOLUTION DATA */ +/*********************************************/ + +unsigned short +SiS_GetResInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + if(ModeNo <= 0x13) + return ((unsigned short)SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ResInfo); + else + return ((unsigned short)SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO); +} + +static void +SiS_GetCRT2ResInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short xres, yres, modeflag=0, resindex; + + if(SiS_Pr->UseCustomMode) { + xres = SiS_Pr->CHDisplay; + if(SiS_Pr->CModeFlag & HalfDCLK) xres <<= 1; + SiS_Pr->SiS_VGAHDE = SiS_Pr->SiS_HDE = xres; + /* DoubleScanMode-check done in CheckCalcCustomMode()! */ + SiS_Pr->SiS_VGAVDE = SiS_Pr->SiS_VDE = SiS_Pr->CVDisplay; + return; + } + + resindex = SiS_GetResInfo(SiS_Pr,ModeNo,ModeIdIndex); + + if(ModeNo <= 0x13) { + xres = SiS_Pr->SiS_StResInfo[resindex].HTotal; + yres = SiS_Pr->SiS_StResInfo[resindex].VTotal; + } else { + xres = SiS_Pr->SiS_ModeResInfo[resindex].HTotal; + yres = SiS_Pr->SiS_ModeResInfo[resindex].VTotal; + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + } + + if(!SiS_Pr->SiS_IF_DEF_DSTN && !SiS_Pr->SiS_IF_DEF_FSTN) { + + if((SiS_Pr->ChipType >= SIS_315H) && (SiS_Pr->SiS_IF_DEF_LVDS == 1)) { + if((ModeNo != 0x03) && (SiS_Pr->SiS_SetFlag & SetDOSMode)) { + if(yres == 350) yres = 400; + } + if(SiS_GetReg(SiS_Pr->SiS_P3d4,0x3a) & 0x01) { + if(ModeNo == 0x12) yres = 400; + } + } + + if(modeflag & HalfDCLK) xres <<= 1; + if(modeflag & DoubleScanMode) yres <<= 1; + + } + + if((SiS_Pr->SiS_VBType & VB_SISVB) && (!(SiS_Pr->SiS_VBType & VB_NoLCD))) { + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_1024x768: + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) { + if(!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) { + if(yres == 350) yres = 357; + if(yres == 400) yres = 420; + if(yres == 480) yres = 525; + } + } + break; + case Panel_1280x1024: + if(!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) { + /* BIOS bug - does this regardless of scaling */ + if(yres == 400) yres = 405; + } + if(yres == 350) yres = 360; + if(SiS_Pr->SiS_SetFlag & LCDVESATiming) { + if(yres == 360) yres = 375; + } + break; + case Panel_1600x1200: + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) { + if(yres == 1024) yres = 1056; + } + break; + } + } + + } else { + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToHiVision)) { + if(xres == 720) xres = 640; + } + } else if(xres == 720) xres = 640; + + if(SiS_Pr->SiS_SetFlag & SetDOSMode) { + yres = 400; + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_GetReg(SiS_Pr->SiS_P3c4,0x17) & 0x80) yres = 480; + } else { + if(SiS_GetReg(SiS_Pr->SiS_P3c4,0x13) & 0x80) yres = 480; + } + if(SiS_Pr->SiS_IF_DEF_DSTN || SiS_Pr->SiS_IF_DEF_FSTN) yres = 480; + } + + } + SiS_Pr->SiS_VGAHDE = SiS_Pr->SiS_HDE = xres; + SiS_Pr->SiS_VGAVDE = SiS_Pr->SiS_VDE = yres; +} + +/*********************************************/ +/* GET CRT2 TIMING DATA */ +/*********************************************/ + +static void +SiS_GetCRT2Ptr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex, unsigned short *CRT2Index, + unsigned short *ResIndex) +{ + unsigned short tempbx=0, tempal=0, resinfo=0; + + if(ModeNo <= 0x13) { + tempal = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + } else { + tempal = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + } + + if((SiS_Pr->SiS_VBType & VB_SISVB) && (SiS_Pr->SiS_IF_DEF_LVDS == 0)) { + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { /* LCD */ + + tempbx = SiS_Pr->SiS_LCDResInfo; + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) tempbx += 32; + + /* patch index */ + if(SiS_Pr->SiS_LCDResInfo == Panel_1680x1050) { + if (resinfo == SIS_RI_1280x800) tempal = 9; + else if(resinfo == SIS_RI_1400x1050) tempal = 11; + } else if((SiS_Pr->SiS_LCDResInfo == Panel_1280x800) || + (SiS_Pr->SiS_LCDResInfo == Panel_1280x800_2) || + (SiS_Pr->SiS_LCDResInfo == Panel_1280x854)) { + if (resinfo == SIS_RI_1280x768) tempal = 9; + } + + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + /* Pass 1:1 only (center-screen handled outside) */ + /* This is never called for the panel's native resolution */ + /* since Pass1:1 will not be set in this case */ + tempbx = 100; + if(ModeNo >= 0x13) { + tempal = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC_NS; + } + } + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) { + if(!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) { + tempbx = 200; + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) tempbx++; + } + } + } +#endif + + } else { /* TV */ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + /* if(SiS_Pr->SiS_VGAVDE > 480) SiS_Pr->SiS_TVMode &= (~TVSetTVSimuMode); */ + tempbx = 2; + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + tempbx = 13; + if(!(SiS_Pr->SiS_TVMode & TVSetTVSimuMode)) tempbx = 14; + } + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) tempbx = 7; + else if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) tempbx = 6; + else tempbx = 5; + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) tempbx += 5; + } else { + if(SiS_Pr->SiS_TVMode & TVSetPAL) tempbx = 3; + else tempbx = 4; + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) tempbx += 5; + } + + } + + tempal &= 0x3F; + + if(ModeNo > 0x13) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTVNoHiVision) { + switch(resinfo) { + case SIS_RI_720x480: + tempal = 6; + if(SiS_Pr->SiS_TVMode & (TVSetPAL | TVSetPALN)) tempal = 9; + break; + case SIS_RI_720x576: + case SIS_RI_768x576: + case SIS_RI_1024x576: /* Not in NTSC or YPBPR mode (except 1080i)! */ + tempal = 6; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) tempal = 8; + } + break; + case SIS_RI_800x480: + tempal = 4; + break; + case SIS_RI_512x384: + case SIS_RI_1024x768: + tempal = 7; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) tempal = 8; + } + break; + case SIS_RI_1280x720: + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) tempal = 9; + } + break; + } + } + } + + *CRT2Index = tempbx; + *ResIndex = tempal; + + } else { /* LVDS, 301B-DH (if running on LCD) */ + + tempbx = 0; + if((SiS_Pr->SiS_IF_DEF_CH70xx) && (SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) { + + tempbx = 90; + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + tempbx = 92; + if(SiS_Pr->SiS_ModeType > ModeVGA) { + if(SiS_Pr->SiS_CHSOverScan) tempbx = 99; + } + if(SiS_Pr->SiS_TVMode & TVSetPALM) tempbx = 94; + else if(SiS_Pr->SiS_TVMode & TVSetPALN) tempbx = 96; + } + if(tempbx != 99) { + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) tempbx++; + } + + } else { + + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_640x480: tempbx = 12; break; + case Panel_320x240_1: tempbx = 10; break; + case Panel_320x240_2: + case Panel_320x240_3: tempbx = 14; break; + case Panel_800x600: tempbx = 16; break; + case Panel_1024x600: tempbx = 18; break; + case Panel_1152x768: + case Panel_1024x768: tempbx = 20; break; + case Panel_1280x768: tempbx = 22; break; + case Panel_1280x1024: tempbx = 24; break; + case Panel_1400x1050: tempbx = 26; break; + case Panel_1600x1200: tempbx = 28; break; +#ifdef CONFIG_FB_SIS_300 + case Panel_Barco1366: tempbx = 80; break; +#endif + } + + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_320x240_1: + case Panel_320x240_2: + case Panel_320x240_3: + case Panel_640x480: + break; + default: + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) tempbx++; + } + + if(SiS_Pr->SiS_LCDInfo & LCDPass11) tempbx = 30; + +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_CustomT == CUT_BARCO1024) { + tempbx = 82; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) tempbx++; + } else if(SiS_Pr->SiS_CustomT == CUT_PANEL848 || SiS_Pr->SiS_CustomT == CUT_PANEL856) { + tempbx = 84; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) tempbx++; + } +#endif + + } + + (*CRT2Index) = tempbx; + (*ResIndex) = tempal & 0x1F; + } +} + +static void +SiS_GetRAMDAC2DATA(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short tempax=0, tempbx=0, index, dotclock; + unsigned short temp1=0, modeflag=0, tempcx=0; + + SiS_Pr->SiS_RVBHCMAX = 1; + SiS_Pr->SiS_RVBHCFACT = 1; + + if(ModeNo <= 0x13) { + + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + index = SiS_GetModePtr(SiS_Pr,ModeNo,ModeIdIndex); + + tempax = SiS_Pr->SiS_StandTable[index].CRTC[0]; + tempbx = SiS_Pr->SiS_StandTable[index].CRTC[6]; + temp1 = SiS_Pr->SiS_StandTable[index].CRTC[7]; + + dotclock = (modeflag & Charx8Dot) ? 8 : 9; + + } else { + + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + index = SiS_GetRefCRT1CRTC(SiS_Pr, RefreshRateTableIndex, SiS_Pr->SiS_UseWideCRT2); + + tempax = SiS_Pr->SiS_CRT1Table[index].CR[0]; + tempax |= (SiS_Pr->SiS_CRT1Table[index].CR[14] << 8); + tempax &= 0x03FF; + tempbx = SiS_Pr->SiS_CRT1Table[index].CR[6]; + tempcx = SiS_Pr->SiS_CRT1Table[index].CR[13] << 8; + tempcx &= 0x0100; + tempcx <<= 2; + tempbx |= tempcx; + temp1 = SiS_Pr->SiS_CRT1Table[index].CR[7]; + + dotclock = 8; + + } + + if(temp1 & 0x01) tempbx |= 0x0100; + if(temp1 & 0x20) tempbx |= 0x0200; + + tempax += 5; + tempax *= dotclock; + if(modeflag & HalfDCLK) tempax <<= 1; + + tempbx++; + + SiS_Pr->SiS_VGAHT = SiS_Pr->SiS_HT = tempax; + SiS_Pr->SiS_VGAVT = SiS_Pr->SiS_VT = tempbx; +} + +static void +SiS_CalcPanelLinkTiming(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RefreshRateTableIndex) +{ + unsigned short ResIndex; + + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + if(SiS_Pr->SiS_LCDInfo & LCDPass11) { + if(SiS_Pr->UseCustomMode) { + ResIndex = SiS_Pr->CHTotal; + if(SiS_Pr->CModeFlag & HalfDCLK) ResIndex <<= 1; + SiS_Pr->SiS_VGAHT = SiS_Pr->SiS_HT = ResIndex; + SiS_Pr->SiS_VGAVT = SiS_Pr->SiS_VT = SiS_Pr->CVTotal; + } else { + if(ModeNo < 0x13) { + ResIndex = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + } else { + ResIndex = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC_NS; + } + if(ResIndex == 0x09) { + if(SiS_Pr->Alternate1600x1200) ResIndex = 0x20; /* 1600x1200 LCDA */ + else if(SiS_Pr->SiS_IF_DEF_LVDS == 1) ResIndex = 0x21; /* 1600x1200 LVDS */ + } + SiS_Pr->SiS_VGAHT = SiS_Pr->SiS_NoScaleData[ResIndex].VGAHT; + SiS_Pr->SiS_VGAVT = SiS_Pr->SiS_NoScaleData[ResIndex].VGAVT; + SiS_Pr->SiS_HT = SiS_Pr->SiS_NoScaleData[ResIndex].LCDHT; + SiS_Pr->SiS_VT = SiS_Pr->SiS_NoScaleData[ResIndex].LCDVT; + } + } else { + SiS_Pr->SiS_VGAHT = SiS_Pr->SiS_HT = SiS_Pr->PanelHT; + SiS_Pr->SiS_VGAVT = SiS_Pr->SiS_VT = SiS_Pr->PanelVT; + } + } else { + /* This handles custom modes and custom panels */ + SiS_Pr->SiS_HDE = SiS_Pr->PanelXRes; + SiS_Pr->SiS_VDE = SiS_Pr->PanelYRes; + SiS_Pr->SiS_HT = SiS_Pr->PanelHT; + SiS_Pr->SiS_VT = SiS_Pr->PanelVT; + SiS_Pr->SiS_VGAHT = SiS_Pr->PanelHT - (SiS_Pr->PanelXRes - SiS_Pr->SiS_VGAHDE); + SiS_Pr->SiS_VGAVT = SiS_Pr->PanelVT - (SiS_Pr->PanelYRes - SiS_Pr->SiS_VGAVDE); + } +} + +static void +SiS_GetCRT2DataLVDS(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short CRT2Index, ResIndex, backup; + const struct SiS_LVDSData *LVDSData = NULL; + + SiS_GetCRT2ResInfo(SiS_Pr, ModeNo, ModeIdIndex); + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + SiS_Pr->SiS_RVBHCMAX = 1; + SiS_Pr->SiS_RVBHCFACT = 1; + SiS_Pr->SiS_NewFlickerMode = 0; + SiS_Pr->SiS_RVBHRS = 50; + SiS_Pr->SiS_RY1COE = 0; + SiS_Pr->SiS_RY2COE = 0; + SiS_Pr->SiS_RY3COE = 0; + SiS_Pr->SiS_RY4COE = 0; + SiS_Pr->SiS_RVBHRS2 = 0; + } + + if((SiS_Pr->SiS_VBType & VB_SISVB) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + +#ifdef CONFIG_FB_SIS_315 + SiS_CalcPanelLinkTiming(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + SiS_CalcLCDACRT1Timing(SiS_Pr, ModeNo, ModeIdIndex); +#endif + + } else { + + /* 301BDH needs LVDS Data */ + backup = SiS_Pr->SiS_IF_DEF_LVDS; + if((SiS_Pr->SiS_VBType & VB_NoLCD) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD)) { + SiS_Pr->SiS_IF_DEF_LVDS = 1; + } + + SiS_GetCRT2Ptr(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex, + &CRT2Index, &ResIndex); + + SiS_Pr->SiS_IF_DEF_LVDS = backup; + + switch(CRT2Index) { + case 10: LVDSData = SiS_Pr->SiS_LVDS320x240Data_1; break; + case 14: LVDSData = SiS_Pr->SiS_LVDS320x240Data_2; break; + case 12: LVDSData = SiS_Pr->SiS_LVDS640x480Data_1; break; + case 16: LVDSData = SiS_Pr->SiS_LVDS800x600Data_1; break; + case 18: LVDSData = SiS_Pr->SiS_LVDS1024x600Data_1; break; + case 20: LVDSData = SiS_Pr->SiS_LVDS1024x768Data_1; break; +#ifdef CONFIG_FB_SIS_300 + case 80: LVDSData = SiS_Pr->SiS_LVDSBARCO1366Data_1; break; + case 81: LVDSData = SiS_Pr->SiS_LVDSBARCO1366Data_2; break; + case 82: LVDSData = SiS_Pr->SiS_LVDSBARCO1024Data_1; break; + case 84: LVDSData = SiS_Pr->SiS_LVDS848x480Data_1; break; + case 85: LVDSData = SiS_Pr->SiS_LVDS848x480Data_2; break; +#endif + case 90: LVDSData = SiS_Pr->SiS_CHTVUNTSCData; break; + case 91: LVDSData = SiS_Pr->SiS_CHTVONTSCData; break; + case 92: LVDSData = SiS_Pr->SiS_CHTVUPALData; break; + case 93: LVDSData = SiS_Pr->SiS_CHTVOPALData; break; + case 94: LVDSData = SiS_Pr->SiS_CHTVUPALMData; break; + case 95: LVDSData = SiS_Pr->SiS_CHTVOPALMData; break; + case 96: LVDSData = SiS_Pr->SiS_CHTVUPALNData; break; + case 97: LVDSData = SiS_Pr->SiS_CHTVOPALNData; break; + case 99: LVDSData = SiS_Pr->SiS_CHTVSOPALData; break; + } + + if(LVDSData) { + SiS_Pr->SiS_VGAHT = (LVDSData+ResIndex)->VGAHT; + SiS_Pr->SiS_VGAVT = (LVDSData+ResIndex)->VGAVT; + SiS_Pr->SiS_HT = (LVDSData+ResIndex)->LCDHT; + SiS_Pr->SiS_VT = (LVDSData+ResIndex)->LCDVT; + } else { + SiS_CalcPanelLinkTiming(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + + if( (!(SiS_Pr->SiS_VBType & VB_SISVB)) && + (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) && + (!(SiS_Pr->SiS_LCDInfo & LCDPass11)) ) { + if( (!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) || + (SiS_Pr->SiS_SetFlag & SetDOSMode) ) { + SiS_Pr->SiS_HDE = SiS_Pr->PanelXRes; + SiS_Pr->SiS_VDE = SiS_Pr->PanelYRes; +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_CustomT == CUT_BARCO1366) { + if(ResIndex < 0x08) { + SiS_Pr->SiS_HDE = 1280; + SiS_Pr->SiS_VDE = 1024; + } + } +#endif + } + } + } +} + +static void +SiS_GetCRT2Data301(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned char *ROMAddr = NULL; + unsigned short tempax, tempbx, modeflag, romptr=0; + unsigned short resinfo, CRT2Index, ResIndex; + const struct SiS_LCDData *LCDPtr = NULL; + const struct SiS_TVData *TVPtr = NULL; +#ifdef CONFIG_FB_SIS_315 + short resinfo661; +#endif + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + resinfo = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ResInfo; + } else if(SiS_Pr->UseCustomMode) { + modeflag = SiS_Pr->CModeFlag; + resinfo = 0; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; +#ifdef CONFIG_FB_SIS_315 + resinfo661 = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].ROMMODEIDX661; + if( (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) && + (SiS_Pr->SiS_SetFlag & LCDVESATiming) && + (resinfo661 >= 0) && + (SiS_Pr->SiS_NeedRomModeData) ) { + if((ROMAddr = GetLCDStructPtr661(SiS_Pr))) { + if((romptr = (SISGETROMW(21)))) { + romptr += (resinfo661 * 10); + ROMAddr = SiS_Pr->VirtualRomBase; + } + } + } +#endif + } + + SiS_Pr->SiS_NewFlickerMode = 0; + SiS_Pr->SiS_RVBHRS = 50; + SiS_Pr->SiS_RY1COE = 0; + SiS_Pr->SiS_RY2COE = 0; + SiS_Pr->SiS_RY3COE = 0; + SiS_Pr->SiS_RY4COE = 0; + SiS_Pr->SiS_RVBHRS2 = 0; + + SiS_GetCRT2ResInfo(SiS_Pr,ModeNo,ModeIdIndex); + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC) { + + if(SiS_Pr->UseCustomMode) { + + SiS_Pr->SiS_RVBHCMAX = 1; + SiS_Pr->SiS_RVBHCFACT = 1; + SiS_Pr->SiS_HDE = SiS_Pr->SiS_VGAHDE; + SiS_Pr->SiS_VDE = SiS_Pr->SiS_VGAVDE; + + tempax = SiS_Pr->CHTotal; + if(modeflag & HalfDCLK) tempax <<= 1; + SiS_Pr->SiS_VGAHT = SiS_Pr->SiS_HT = tempax; + SiS_Pr->SiS_VGAVT = SiS_Pr->SiS_VT = SiS_Pr->CVTotal; + + } else { + + SiS_GetRAMDAC2DATA(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + + } + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + + SiS_GetCRT2Ptr(SiS_Pr,ModeNo,ModeIdIndex,RefreshRateTableIndex, + &CRT2Index,&ResIndex); + + switch(CRT2Index) { + case 2: TVPtr = SiS_Pr->SiS_ExtHiTVData; break; + case 3: TVPtr = SiS_Pr->SiS_ExtPALData; break; + case 4: TVPtr = SiS_Pr->SiS_ExtNTSCData; break; + case 5: TVPtr = SiS_Pr->SiS_Ext525iData; break; + case 6: TVPtr = SiS_Pr->SiS_Ext525pData; break; + case 7: TVPtr = SiS_Pr->SiS_Ext750pData; break; + case 8: TVPtr = SiS_Pr->SiS_StPALData; break; + case 9: TVPtr = SiS_Pr->SiS_StNTSCData; break; + case 10: TVPtr = SiS_Pr->SiS_St525iData; break; + case 11: TVPtr = SiS_Pr->SiS_St525pData; break; + case 12: TVPtr = SiS_Pr->SiS_St750pData; break; + case 13: TVPtr = SiS_Pr->SiS_St1HiTVData; break; + case 14: TVPtr = SiS_Pr->SiS_St2HiTVData; break; + default: TVPtr = SiS_Pr->SiS_StPALData; break; + } + + SiS_Pr->SiS_RVBHCMAX = (TVPtr+ResIndex)->RVBHCMAX; + SiS_Pr->SiS_RVBHCFACT = (TVPtr+ResIndex)->RVBHCFACT; + SiS_Pr->SiS_VGAHT = (TVPtr+ResIndex)->VGAHT; + SiS_Pr->SiS_VGAVT = (TVPtr+ResIndex)->VGAVT; + SiS_Pr->SiS_HDE = (TVPtr+ResIndex)->TVHDE; + SiS_Pr->SiS_VDE = (TVPtr+ResIndex)->TVVDE; + SiS_Pr->SiS_RVBHRS2 = (TVPtr+ResIndex)->RVBHRS2 & 0x0fff; + if(modeflag & HalfDCLK) { + SiS_Pr->SiS_RVBHRS = (TVPtr+ResIndex)->HALFRVBHRS; + if(SiS_Pr->SiS_RVBHRS2) { + SiS_Pr->SiS_RVBHRS2 = ((SiS_Pr->SiS_RVBHRS2 + 3) >> 1) - 3; + tempax = ((TVPtr+ResIndex)->RVBHRS2 >> 12) & 0x07; + if((TVPtr+ResIndex)->RVBHRS2 & 0x8000) SiS_Pr->SiS_RVBHRS2 -= tempax; + else SiS_Pr->SiS_RVBHRS2 += tempax; + } + } else { + SiS_Pr->SiS_RVBHRS = (TVPtr+ResIndex)->RVBHRS; + } + SiS_Pr->SiS_NewFlickerMode = ((TVPtr+ResIndex)->FlickerMode) << 7; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + + if((resinfo == SIS_RI_960x600) || + (resinfo == SIS_RI_1024x768) || + (resinfo == SIS_RI_1280x1024) || + (resinfo == SIS_RI_1280x720)) { + SiS_Pr->SiS_NewFlickerMode = 0x40; + } + + if(SiS_Pr->SiS_VGAVDE == 350) SiS_Pr->SiS_TVMode |= TVSetTVSimuMode; + + SiS_Pr->SiS_HT = ExtHiTVHT; + SiS_Pr->SiS_VT = ExtHiTVVT; + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) { + SiS_Pr->SiS_HT = StHiTVHT; + SiS_Pr->SiS_VT = StHiTVVT; + } + } + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) { + SiS_Pr->SiS_HT = 1650; + SiS_Pr->SiS_VT = 750; + } else if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) { + SiS_Pr->SiS_HT = NTSCHT; + if(SiS_Pr->SiS_TVMode & TVSet525p1024) SiS_Pr->SiS_HT = NTSC2HT; + SiS_Pr->SiS_VT = NTSCVT; + } else { + SiS_Pr->SiS_HT = NTSCHT; + if(SiS_Pr->SiS_TVMode & TVSetNTSC1024) SiS_Pr->SiS_HT = NTSC2HT; + SiS_Pr->SiS_VT = NTSCVT; + } + + } else { + + SiS_Pr->SiS_RY1COE = (TVPtr+ResIndex)->RY1COE; + SiS_Pr->SiS_RY2COE = (TVPtr+ResIndex)->RY2COE; + SiS_Pr->SiS_RY3COE = (TVPtr+ResIndex)->RY3COE; + SiS_Pr->SiS_RY4COE = (TVPtr+ResIndex)->RY4COE; + + if(modeflag & HalfDCLK) { + SiS_Pr->SiS_RY1COE = 0x00; + SiS_Pr->SiS_RY2COE = 0xf4; + SiS_Pr->SiS_RY3COE = 0x10; + SiS_Pr->SiS_RY4COE = 0x38; + } + + if(!(SiS_Pr->SiS_TVMode & TVSetPAL)) { + SiS_Pr->SiS_HT = NTSCHT; + if(SiS_Pr->SiS_TVMode & TVSetNTSC1024) SiS_Pr->SiS_HT = NTSC2HT; + SiS_Pr->SiS_VT = NTSCVT; + } else { + SiS_Pr->SiS_HT = PALHT; + SiS_Pr->SiS_VT = PALVT; + } + + } + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + + SiS_Pr->SiS_RVBHCMAX = 1; + SiS_Pr->SiS_RVBHCFACT = 1; + + if(SiS_Pr->UseCustomMode) { + + SiS_Pr->SiS_HDE = SiS_Pr->SiS_VGAHDE; + SiS_Pr->SiS_VDE = SiS_Pr->SiS_VGAVDE; + + tempax = SiS_Pr->CHTotal; + if(modeflag & HalfDCLK) tempax <<= 1; + SiS_Pr->SiS_VGAHT = SiS_Pr->SiS_HT = tempax; + SiS_Pr->SiS_VGAVT = SiS_Pr->SiS_VT = SiS_Pr->CVTotal; + + } else { + + bool gotit = false; + + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (!(SiS_Pr->SiS_LCDInfo & LCDPass11))) { + + SiS_Pr->SiS_VGAHT = SiS_Pr->PanelHT; + SiS_Pr->SiS_VGAVT = SiS_Pr->PanelVT; + SiS_Pr->SiS_HT = SiS_Pr->PanelHT; + SiS_Pr->SiS_VT = SiS_Pr->PanelVT; + gotit = true; + + } else if( (!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) && (romptr) && (ROMAddr) ) { + +#ifdef CONFIG_FB_SIS_315 + SiS_Pr->SiS_RVBHCMAX = ROMAddr[romptr]; + SiS_Pr->SiS_RVBHCFACT = ROMAddr[romptr+1]; + SiS_Pr->SiS_VGAHT = ROMAddr[romptr+2] | ((ROMAddr[romptr+3] & 0x0f) << 8); + SiS_Pr->SiS_VGAVT = (ROMAddr[romptr+4] << 4) | ((ROMAddr[romptr+3] & 0xf0) >> 4); + SiS_Pr->SiS_HT = ROMAddr[romptr+5] | ((ROMAddr[romptr+6] & 0x0f) << 8); + SiS_Pr->SiS_VT = (ROMAddr[romptr+7] << 4) | ((ROMAddr[romptr+6] & 0xf0) >> 4); + SiS_Pr->SiS_RVBHRS2 = ROMAddr[romptr+8] | ((ROMAddr[romptr+9] & 0x0f) << 8); + if((SiS_Pr->SiS_RVBHRS2) && (modeflag & HalfDCLK)) { + SiS_Pr->SiS_RVBHRS2 = ((SiS_Pr->SiS_RVBHRS2 + 3) >> 1) - 3; + tempax = (ROMAddr[romptr+9] >> 4) & 0x07; + if(ROMAddr[romptr+9] & 0x80) SiS_Pr->SiS_RVBHRS2 -= tempax; + else SiS_Pr->SiS_RVBHRS2 += tempax; + } + if(SiS_Pr->SiS_VGAHT) gotit = true; + else { + SiS_Pr->SiS_LCDInfo |= DontExpandLCD; + SiS_Pr->SiS_LCDInfo &= ~LCDPass11; + SiS_Pr->SiS_RVBHCMAX = 1; + SiS_Pr->SiS_RVBHCFACT = 1; + SiS_Pr->SiS_VGAHT = SiS_Pr->PanelHT; + SiS_Pr->SiS_VGAVT = SiS_Pr->PanelVT; + SiS_Pr->SiS_HT = SiS_Pr->PanelHT; + SiS_Pr->SiS_VT = SiS_Pr->PanelVT; + SiS_Pr->SiS_RVBHRS2 = 0; + gotit = true; + } +#endif + + } + + if(!gotit) { + + SiS_GetCRT2Ptr(SiS_Pr,ModeNo,ModeIdIndex,RefreshRateTableIndex, + &CRT2Index,&ResIndex); + + switch(CRT2Index) { + case Panel_1024x768 : LCDPtr = SiS_Pr->SiS_ExtLCD1024x768Data; break; + case Panel_1024x768 + 32: LCDPtr = SiS_Pr->SiS_St2LCD1024x768Data; break; + case Panel_1280x720 : + case Panel_1280x720 + 32: LCDPtr = SiS_Pr->SiS_LCD1280x720Data; break; + case Panel_1280x768_2 : LCDPtr = SiS_Pr->SiS_ExtLCD1280x768_2Data; break; + case Panel_1280x768_2+ 32: LCDPtr = SiS_Pr->SiS_StLCD1280x768_2Data; break; + case Panel_1280x800 : + case Panel_1280x800 + 32: LCDPtr = SiS_Pr->SiS_LCD1280x800Data; break; + case Panel_1280x800_2 : + case Panel_1280x800_2+ 32: LCDPtr = SiS_Pr->SiS_LCD1280x800_2Data; break; + case Panel_1280x854 : + case Panel_1280x854 + 32: LCDPtr = SiS_Pr->SiS_LCD1280x854Data; break; + case Panel_1280x960 : + case Panel_1280x960 + 32: LCDPtr = SiS_Pr->SiS_LCD1280x960Data; break; + case Panel_1280x1024 : LCDPtr = SiS_Pr->SiS_ExtLCD1280x1024Data; break; + case Panel_1280x1024 + 32: LCDPtr = SiS_Pr->SiS_St2LCD1280x1024Data; break; + case Panel_1400x1050 : LCDPtr = SiS_Pr->SiS_ExtLCD1400x1050Data; break; + case Panel_1400x1050 + 32: LCDPtr = SiS_Pr->SiS_StLCD1400x1050Data; break; + case Panel_1600x1200 : LCDPtr = SiS_Pr->SiS_ExtLCD1600x1200Data; break; + case Panel_1600x1200 + 32: LCDPtr = SiS_Pr->SiS_StLCD1600x1200Data; break; + case Panel_1680x1050 : + case Panel_1680x1050 + 32: LCDPtr = SiS_Pr->SiS_LCD1680x1050Data; break; + case 100 : LCDPtr = SiS_Pr->SiS_NoScaleData; break; +#ifdef CONFIG_FB_SIS_315 + case 200 : LCDPtr = SiS310_ExtCompaq1280x1024Data; break; + case 201 : LCDPtr = SiS_Pr->SiS_St2LCD1280x1024Data; break; +#endif + default : LCDPtr = SiS_Pr->SiS_ExtLCD1024x768Data; break; + } + + SiS_Pr->SiS_RVBHCMAX = (LCDPtr+ResIndex)->RVBHCMAX; + SiS_Pr->SiS_RVBHCFACT = (LCDPtr+ResIndex)->RVBHCFACT; + SiS_Pr->SiS_VGAHT = (LCDPtr+ResIndex)->VGAHT; + SiS_Pr->SiS_VGAVT = (LCDPtr+ResIndex)->VGAVT; + SiS_Pr->SiS_HT = (LCDPtr+ResIndex)->LCDHT; + SiS_Pr->SiS_VT = (LCDPtr+ResIndex)->LCDVT; + + } + + tempax = SiS_Pr->PanelXRes; + tempbx = SiS_Pr->PanelYRes; + + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_1024x768: + if(SiS_Pr->SiS_SetFlag & LCDVESATiming) { + if(SiS_Pr->ChipType < SIS_315H) { + if (SiS_Pr->SiS_VGAVDE == 350) tempbx = 560; + else if(SiS_Pr->SiS_VGAVDE == 400) tempbx = 640; + } + } else { + if (SiS_Pr->SiS_VGAVDE == 357) tempbx = 527; + else if(SiS_Pr->SiS_VGAVDE == 420) tempbx = 620; + else if(SiS_Pr->SiS_VGAVDE == 525) tempbx = 775; + else if(SiS_Pr->SiS_VGAVDE == 600) tempbx = 775; + else if(SiS_Pr->SiS_VGAVDE == 350) tempbx = 560; + else if(SiS_Pr->SiS_VGAVDE == 400) tempbx = 640; + } + break; + case Panel_1280x960: + if (SiS_Pr->SiS_VGAVDE == 350) tempbx = 700; + else if(SiS_Pr->SiS_VGAVDE == 400) tempbx = 800; + else if(SiS_Pr->SiS_VGAVDE == 1024) tempbx = 960; + break; + case Panel_1280x1024: + if (SiS_Pr->SiS_VGAVDE == 360) tempbx = 768; + else if(SiS_Pr->SiS_VGAVDE == 375) tempbx = 800; + else if(SiS_Pr->SiS_VGAVDE == 405) tempbx = 864; + break; + case Panel_1600x1200: + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) { + if (SiS_Pr->SiS_VGAVDE == 350) tempbx = 875; + else if(SiS_Pr->SiS_VGAVDE == 400) tempbx = 1000; + } + break; + } + + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempax = SiS_Pr->SiS_VGAHDE; + tempbx = SiS_Pr->SiS_VGAVDE; + } + + SiS_Pr->SiS_HDE = tempax; + SiS_Pr->SiS_VDE = tempbx; + } + } +} + +static void +SiS_GetCRT2Data(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_GetCRT2DataLVDS(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } else { + if((SiS_Pr->SiS_VBType & VB_NoLCD) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD)) { + /* Need LVDS Data for LCD on 301B-DH */ + SiS_GetCRT2DataLVDS(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } else { + SiS_GetCRT2Data301(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + } + + } else { + + SiS_GetCRT2DataLVDS(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + + } +} + +/*********************************************/ +/* GET LVDS DES (SKEW) DATA */ +/*********************************************/ + +static const struct SiS_LVDSDes * +SiS_GetLVDSDesPtr(struct SiS_Private *SiS_Pr) +{ + const struct SiS_LVDSDes *PanelDesPtr = NULL; + +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_LCDTypeInfo == 4) { + if(SiS_Pr->SiS_CustomT == CUT_BARCO1366) { + PanelDesPtr = SiS_Pr->SiS_PanelType04_1a; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + PanelDesPtr = SiS_Pr->SiS_PanelType04_2a; + } + } else if(SiS_Pr->SiS_CustomT == CUT_BARCO1024) { + PanelDesPtr = SiS_Pr->SiS_PanelType04_1b; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + PanelDesPtr = SiS_Pr->SiS_PanelType04_2b; + } + } + } + } + } +#endif + return PanelDesPtr; +} + +static void +SiS_GetLVDSDesData(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short modeflag, ResIndex; + const struct SiS_LVDSDes *PanelDesPtr = NULL; + + SiS_Pr->SiS_LCDHDES = 0; + SiS_Pr->SiS_LCDVDES = 0; + + /* Some special cases */ + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + + /* Trumpion */ + if(SiS_Pr->SiS_IF_DEF_TRUMPION) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(SiS_Pr->SiS_VGAVDE == SiS_Pr->PanelYRes) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } + } + return; + } + + /* 640x480 on LVDS */ + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_LCDResInfo == Panel_640x480 && SiS_Pr->SiS_LCDTypeInfo == 3) { + SiS_Pr->SiS_LCDHDES = 8; + if (SiS_Pr->SiS_VGAVDE >= 480) SiS_Pr->SiS_LCDVDES = 512; + else if(SiS_Pr->SiS_VGAVDE >= 400) SiS_Pr->SiS_LCDVDES = 436; + else if(SiS_Pr->SiS_VGAVDE >= 350) SiS_Pr->SiS_LCDVDES = 440; + return; + } + } + + } /* LCD */ + + if( (SiS_Pr->UseCustomMode) || + (SiS_Pr->SiS_LCDResInfo == Panel_Custom) || + (SiS_Pr->SiS_CustomT == CUT_PANEL848) || + (SiS_Pr->SiS_CustomT == CUT_PANEL856) || + (SiS_Pr->SiS_LCDInfo & LCDPass11) ) { + return; + } + + if(ModeNo <= 0x13) ResIndex = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + else ResIndex = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + + if((SiS_Pr->SiS_VBType & VB_SIS30xBLV) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + /* non-pass 1:1 only, see above */ + if(SiS_Pr->SiS_VGAHDE != SiS_Pr->PanelXRes) { + SiS_Pr->SiS_LCDHDES = SiS_Pr->SiS_HT - ((SiS_Pr->PanelXRes - SiS_Pr->SiS_VGAHDE) / 2); + } + if(SiS_Pr->SiS_VGAVDE != SiS_Pr->PanelYRes) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->SiS_VT - ((SiS_Pr->PanelYRes - SiS_Pr->SiS_VGAVDE) / 2); + } + } + if(SiS_Pr->SiS_VGAVDE == SiS_Pr->PanelYRes) { + switch(SiS_Pr->SiS_CustomT) { + case CUT_UNIWILL1024: + case CUT_UNIWILL10242: + case CUT_CLEVO1400: + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } + break; + } + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_1280x1024: + if(SiS_Pr->SiS_CustomT != CUT_COMPAQ1280) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } + break; + case Panel_1280x800: /* Verified for Averatec 6240 */ + case Panel_1280x800_2: /* Verified for Asus A4L */ + case Panel_1280x854: /* Not verified yet FIXME */ + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + break; + } + } +#endif + + } else { + + if((SiS_Pr->SiS_IF_DEF_CH70xx != 0) && (SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) { + + if((SiS_Pr->SiS_TVMode & TVSetPAL) && (!(SiS_Pr->SiS_TVMode & TVSetPALM))) { + if(ResIndex <= 3) SiS_Pr->SiS_LCDHDES = 256; + } + + } else if((PanelDesPtr = SiS_GetLVDSDesPtr(SiS_Pr))) { + + SiS_Pr->SiS_LCDHDES = (PanelDesPtr+ResIndex)->LCDHDES; + SiS_Pr->SiS_LCDVDES = (PanelDesPtr+ResIndex)->LCDVDES; + + } else if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + + if(SiS_Pr->SiS_VGAHDE != SiS_Pr->PanelXRes) { + SiS_Pr->SiS_LCDHDES = SiS_Pr->SiS_HT - ((SiS_Pr->PanelXRes - SiS_Pr->SiS_VGAHDE) / 2); + } + if(SiS_Pr->SiS_VGAVDE != SiS_Pr->PanelYRes) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->SiS_VT - ((SiS_Pr->PanelYRes - SiS_Pr->SiS_VGAVDE) / 2); + } else { + if(SiS_Pr->ChipType < SIS_315H) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } else { + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_800x600: + case Panel_1024x768: + case Panel_1280x1024: + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT; + break; + case Panel_1400x1050: + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + break; + } + } + } + + } else { + + if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_800x600: + if(SiS_Pr->SiS_VGAVDE == SiS_Pr->PanelYRes) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } else { + SiS_Pr->SiS_LCDHDES = SiS_Pr->PanelHT + 3; + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT; + if(SiS_Pr->SiS_VGAVDE == 400) SiS_Pr->SiS_LCDVDES -= 2; + else SiS_Pr->SiS_LCDVDES -= 4; + } + break; + case Panel_1024x768: + if(SiS_Pr->SiS_VGAVDE == SiS_Pr->PanelYRes) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } else { + SiS_Pr->SiS_LCDHDES = SiS_Pr->PanelHT - 1; + if(SiS_Pr->SiS_VGAVDE <= 400) SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 8; + if(SiS_Pr->SiS_VGAVDE <= 350) SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 12; + } + break; + case Panel_1024x600: + default: + if( (SiS_Pr->SiS_VGAHDE == SiS_Pr->PanelXRes) && + (SiS_Pr->SiS_VGAVDE == SiS_Pr->PanelYRes) ) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } else { + SiS_Pr->SiS_LCDHDES = SiS_Pr->PanelHT - 1; + } + break; + } + + switch(SiS_Pr->SiS_LCDTypeInfo) { + case 1: + SiS_Pr->SiS_LCDHDES = SiS_Pr->SiS_LCDVDES = 0; + break; + case 3: /* 640x480 only? */ + SiS_Pr->SiS_LCDHDES = 8; + if (SiS_Pr->SiS_VGAVDE >= 480) SiS_Pr->SiS_LCDVDES = 512; + else if(SiS_Pr->SiS_VGAVDE >= 400) SiS_Pr->SiS_LCDVDES = 436; + else if(SiS_Pr->SiS_VGAVDE >= 350) SiS_Pr->SiS_LCDVDES = 440; + break; + } +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_1024x768: + case Panel_1280x1024: + if(SiS_Pr->SiS_VGAVDE == SiS_Pr->PanelYRes) { + SiS_Pr->SiS_LCDVDES = SiS_Pr->PanelVT - 1; + } + break; + case Panel_320x240_1: + case Panel_320x240_2: + case Panel_320x240_3: + SiS_Pr->SiS_LCDVDES = 524; + break; + } +#endif + } + } + + if((ModeNo <= 0x13) && (SiS_Pr->SiS_LCDInfo & DontExpandLCD)) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + if((SiS_Pr->SiS_VBType & VB_SIS30xBLV) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + if(!(modeflag & HalfDCLK)) SiS_Pr->SiS_LCDHDES = 632; + } else if(!(SiS_Pr->SiS_SetFlag & SetDOSMode)) { + if(SiS_Pr->SiS_LCDResInfo != Panel_1280x1024) { + if(SiS_Pr->SiS_LCDResInfo >= Panel_1024x768) { + if(SiS_Pr->ChipType < SIS_315H) { + if(!(modeflag & HalfDCLK)) SiS_Pr->SiS_LCDHDES = 320; + } else { +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) SiS_Pr->SiS_LCDHDES = 480; + if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) SiS_Pr->SiS_LCDHDES = 804; + if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) SiS_Pr->SiS_LCDHDES = 704; + if(!(modeflag & HalfDCLK)) { + SiS_Pr->SiS_LCDHDES = 320; + if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) SiS_Pr->SiS_LCDHDES = 632; + if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) SiS_Pr->SiS_LCDHDES = 542; + } +#endif + } + } + } + } + } + } +} + +/*********************************************/ +/* DISABLE VIDEO BRIDGE */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_315 +static int +SiS_HandlePWD(struct SiS_Private *SiS_Pr) +{ + int ret = 0; +#ifdef SET_PWD + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr = GetLCDStructPtr661_2(SiS_Pr); + unsigned char drivermode = SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) & 0x40; + unsigned short temp; + + if( (SiS_Pr->SiS_VBType & VB_SISPWD) && + (romptr) && + (SiS_Pr->SiS_PWDOffset) ) { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x2b,ROMAddr[romptr + SiS_Pr->SiS_PWDOffset + 0]); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x2c,ROMAddr[romptr + SiS_Pr->SiS_PWDOffset + 1]); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x2d,ROMAddr[romptr + SiS_Pr->SiS_PWDOffset + 2]); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x2e,ROMAddr[romptr + SiS_Pr->SiS_PWDOffset + 3]); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x2f,ROMAddr[romptr + SiS_Pr->SiS_PWDOffset + 4]); + temp = 0x00; + if((ROMAddr[romptr + 2] & (0x06 << 1)) && !drivermode) { + temp = 0x80; + ret = 1; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x27,0x7f,temp); + } +#endif + return ret; +} +#endif + +/* NEVER use any variables (VBInfo), this will be called + * from outside the context of modeswitch! + * MUST call getVBType before calling this + */ +void +SiS_DisableBridge(struct SiS_Private *SiS_Pr) +{ +#ifdef CONFIG_FB_SIS_315 + unsigned short tempah, pushax=0, modenum; +#endif + unsigned short temp=0; + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { /* ===== For 30xB/C/LV ===== */ + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* 300 series */ + + if(!(SiS_CR36BIOSWord23b(SiS_Pr))) { + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x26,0xFE); + } else { + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x08); + } + SiS_PanelDelay(SiS_Pr, 3); + } + if(SiS_Is301B(SiS_Pr)) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x1f,0x3f); + SiS_ShortDelay(SiS_Pr,1); + } + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x00,0xDF); + SiS_DisplayOff(SiS_Pr); + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x32,0xDF); + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x1E,0xDF); + SiS_UnLockCRT2(SiS_Pr); + if(!(SiS_Pr->SiS_VBType & VB_SISLVDS)) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x01,0x80); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x02,0x40); + } + if( (!(SiS_CRT2IsLCD(SiS_Pr))) || + (!(SiS_CR36BIOSWord23d(SiS_Pr))) ) { + SiS_PanelDelay(SiS_Pr, 2); + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x26,0xFD); + } else { + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x04); + } + } + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* 315 series */ + + int didpwd = 0; + bool custom1 = (SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) || + (SiS_Pr->SiS_CustomT == CUT_CLEVO1400); + + modenum = SiS_GetReg(SiS_Pr->SiS_P3d4,0x34) & 0x7f; + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + +#ifdef SET_EMI + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + if(SiS_Pr->SiS_CustomT != CUT_CLEVO1400) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x30,0x0c); + } + } +#endif + + didpwd = SiS_HandlePWD(SiS_Pr); + + if( (modenum <= 0x13) || + (SiS_IsVAMode(SiS_Pr)) || + (!(SiS_IsDualEdge(SiS_Pr))) ) { + if(!didpwd) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x26,0xfe); + if(custom1) SiS_PanelDelay(SiS_Pr, 3); + } else { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x26,0xfc); + } + } + + if(!custom1) { + SiS_DDC2Delay(SiS_Pr,0xff00); + SiS_DDC2Delay(SiS_Pr,0xe000); + SiS_SetRegByte(SiS_Pr->SiS_P3c6,0x00); + pushax = SiS_GetReg(SiS_Pr->SiS_P3c4,0x06); + if(IS_SIS740) { + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x06,0xE3); + } + SiS_PanelDelay(SiS_Pr, 3); + } + + } + + if(!(SiS_IsNotM650orLater(SiS_Pr))) { + /* if(SiS_Pr->ChipType < SIS_340) {*/ + tempah = 0xef; + if(SiS_IsVAMode(SiS_Pr)) tempah = 0xf7; + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x4c,tempah); + /*}*/ + } + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x1F,~0x10); + } + + tempah = 0x3f; + if(SiS_IsDualEdge(SiS_Pr)) { + tempah = 0x7f; + if(!(SiS_IsVAMode(SiS_Pr))) tempah = 0xbf; + } + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x1F,tempah); + + if((SiS_IsVAMode(SiS_Pr)) || + ((SiS_Pr->SiS_VBType & VB_SISLVDS) && (modenum <= 0x13))) { + + SiS_DisplayOff(SiS_Pr); + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_PanelDelay(SiS_Pr, 2); + } + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x32,0xDF); + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x1E,0xDF); + + } + + if((!(SiS_IsVAMode(SiS_Pr))) || + ((SiS_Pr->SiS_VBType & VB_SISLVDS) && (modenum <= 0x13))) { + + if(!(SiS_IsDualEdge(SiS_Pr))) { + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x00,0xdf); + SiS_DisplayOff(SiS_Pr); + } + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x00,0x80); + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_PanelDelay(SiS_Pr, 2); + } + + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x32,0xDF); + temp = SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x00,0x10); + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x1E,0xDF); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x00,temp); + + } + + if(SiS_IsNotM650orLater(SiS_Pr)) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2e,0x7f); + } + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + + if( (!(SiS_IsVAMode(SiS_Pr))) && + (!(SiS_CRT2IsLCD(SiS_Pr))) && + (!(SiS_IsDualEdge(SiS_Pr))) ) { + + if(custom1) SiS_PanelDelay(SiS_Pr, 2); + if(!didpwd) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x26,0xFD); + } + if(custom1) SiS_PanelDelay(SiS_Pr, 4); + } + + if(!custom1) { + SiS_SetReg(SiS_Pr->SiS_P3c4,0x06,pushax); + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + if(SiS_IsVAorLCD(SiS_Pr)) { + SiS_PanelDelayLoop(SiS_Pr, 3, 20); + } + } + } + + } + +#endif /* CONFIG_FB_SIS_315 */ + + } + + } else { /* ============ For 301 ================ */ + + if(SiS_Pr->ChipType < SIS_315H) { +#ifdef CONFIG_FB_SIS_300 + if(!(SiS_CR36BIOSWord23b(SiS_Pr))) { + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x08); + SiS_PanelDelay(SiS_Pr, 3); + } +#endif + } + + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x00,0xDF); /* disable VB */ + SiS_DisplayOff(SiS_Pr); + + if(SiS_Pr->ChipType >= SIS_315H) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x00,0x80); + } + + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x32,0xDF); /* disable lock mode */ + + if(SiS_Pr->ChipType >= SIS_315H) { + temp = SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x00,0x10); + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x00,temp); + } else { +#ifdef CONFIG_FB_SIS_300 + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x1E,0xDF); /* disable CRT2 */ + if( (!(SiS_CRT2IsLCD(SiS_Pr))) || + (!(SiS_CR36BIOSWord23d(SiS_Pr))) ) { + SiS_PanelDelay(SiS_Pr, 2); + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x04); + } +#endif + } + + } + + } else { /* ============ For LVDS =============*/ + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* 300 series */ + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 1) { + SiS_SetCH700x(SiS_Pr,0x0E,0x09); + } + + if(SiS_Pr->ChipType == SIS_730) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x11) & 0x08)) { + SiS_WaitVBRetrace(SiS_Pr); + } + if(!(SiS_CR36BIOSWord23b(SiS_Pr))) { + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x08); + SiS_PanelDelay(SiS_Pr, 3); + } + } else { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x11) & 0x08)) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x13) & 0x40)) { + if(!(SiS_CR36BIOSWord23b(SiS_Pr))) { + SiS_WaitVBRetrace(SiS_Pr); + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x06) & 0x1c)) { + SiS_DisplayOff(SiS_Pr); + } + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x08); + SiS_PanelDelay(SiS_Pr, 3); + } + } + } + } + + SiS_DisplayOff(SiS_Pr); + + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x32,0xDF); + + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x1E,0xDF); + SiS_UnLockCRT2(SiS_Pr); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x01,0x80); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x02,0x40); + + if( (!(SiS_CRT2IsLCD(SiS_Pr))) || + (!(SiS_CR36BIOSWord23d(SiS_Pr))) ) { + SiS_PanelDelay(SiS_Pr, 2); + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x04); + } + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* 315 series */ + + if(!(SiS_IsNotM650orLater(SiS_Pr))) { + /*if(SiS_Pr->ChipType < SIS_340) { */ /* XGI needs this */ + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x4c,~0x18); + /* } */ + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + + if(SiS_Pr->ChipType == SIS_740) { + temp = SiS_GetCH701x(SiS_Pr,0x61); + if(temp < 1) { + SiS_SetCH701x(SiS_Pr,0x76,0xac); + SiS_SetCH701x(SiS_Pr,0x66,0x00); + } + + if( (!(SiS_IsDualEdge(SiS_Pr))) || + (SiS_IsTVOrYPbPrOrScart(SiS_Pr)) ) { + SiS_SetCH701x(SiS_Pr,0x49,0x3e); + } + } + + if( (!(SiS_IsDualEdge(SiS_Pr))) || + (SiS_IsVAMode(SiS_Pr)) ) { + SiS_Chrontel701xBLOff(SiS_Pr); + SiS_Chrontel701xOff(SiS_Pr); + } + + if(SiS_Pr->ChipType != SIS_740) { + if( (!(SiS_IsDualEdge(SiS_Pr))) || + (SiS_IsTVOrYPbPrOrScart(SiS_Pr)) ) { + SiS_SetCH701x(SiS_Pr,0x49,0x01); + } + } + + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 0) { + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x08); + SiS_PanelDelay(SiS_Pr, 3); + } + + if( (SiS_Pr->SiS_IF_DEF_CH70xx == 0) || + (!(SiS_IsDualEdge(SiS_Pr))) || + (!(SiS_IsTVOrYPbPrOrScart(SiS_Pr))) ) { + SiS_DisplayOff(SiS_Pr); + } + + if( (SiS_Pr->SiS_IF_DEF_CH70xx == 0) || + (!(SiS_IsDualEdge(SiS_Pr))) || + (!(SiS_IsVAMode(SiS_Pr))) ) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x00,0x80); + } + + if(SiS_Pr->ChipType == SIS_740) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2e,0x7f); + } + + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x32,0xDF); + + if( (SiS_Pr->SiS_IF_DEF_CH70xx == 0) || + (!(SiS_IsDualEdge(SiS_Pr))) || + (!(SiS_IsVAMode(SiS_Pr))) ) { + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x1E,0xDF); + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 0) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x1e,0xdf); + if(SiS_Pr->ChipType == SIS_550) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x1e,0xbf); + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x1e,0xef); + } + } + } else { + if(SiS_Pr->ChipType == SIS_740) { + if(SiS_IsLCDOrLCDA(SiS_Pr)) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x1e,0xdf); + } + } else if(SiS_IsVAMode(SiS_Pr)) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x1e,0xdf); + } + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_IsDualEdge(SiS_Pr)) { + /* SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x13,0xff); */ + } else { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x13,0xfb); + } + } + + SiS_UnLockCRT2(SiS_Pr); + + if(SiS_Pr->ChipType == SIS_550) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x01,0x80); /* DirectDVD PAL?*/ + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x02,0x40); /* VB clock / 4 ? */ + } else if( (SiS_Pr->SiS_IF_DEF_CH70xx == 0) || + (!(SiS_IsDualEdge(SiS_Pr))) || + (!(SiS_IsVAMode(SiS_Pr))) ) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2e,0xf7); + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 0) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + if(!(SiS_WeHaveBacklightCtrl(SiS_Pr))) { + SiS_PanelDelay(SiS_Pr, 2); + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x04); + } + } + } + +#endif /* CONFIG_FB_SIS_315 */ + + } /* 315 series */ + + } /* LVDS */ + +} + +/*********************************************/ +/* ENABLE VIDEO BRIDGE */ +/*********************************************/ + +/* NEVER use any variables (VBInfo), this will be called + * from outside the context of a mode switch! + * MUST call getVBType before calling this + */ +static +void +SiS_EnableBridge(struct SiS_Private *SiS_Pr) +{ + unsigned short temp=0, tempah; +#ifdef CONFIG_FB_SIS_315 + unsigned short temp1, pushax=0; + bool delaylong = false; +#endif + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { /* ====== For 301B et al ====== */ + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* 300 series */ + + if(SiS_CRT2IsLCD(SiS_Pr)) { + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x26,0x02); + } else if(SiS_Pr->SiS_VBType & VB_NoLCD) { + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x00); + } + if(SiS_Pr->SiS_VBType & (VB_SISLVDS | VB_NoLCD)) { + if(!(SiS_CR36BIOSWord23d(SiS_Pr))) { + SiS_PanelDelay(SiS_Pr, 0); + } + } + } + + if((SiS_Pr->SiS_VBType & VB_NoLCD) && + (SiS_CRT2IsLCD(SiS_Pr))) { + + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); /* Enable CRT2 */ + SiS_DisplayOn(SiS_Pr); + SiS_UnLockCRT2(SiS_Pr); + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x02,0xBF); + if(SiS_BridgeInSlavemode(SiS_Pr)) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x01,0x1F); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x01,0x1F,0x40); + } + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x13) & 0x40)) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x16) & 0x10)) { + if(!(SiS_CR36BIOSWord23b(SiS_Pr))) { + SiS_PanelDelay(SiS_Pr, 1); + } + SiS_WaitVBRetrace(SiS_Pr); + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x00); + } + } + + } else { + + temp = SiS_GetReg(SiS_Pr->SiS_P3c4,0x32) & 0xDF; /* lock mode */ + if(SiS_BridgeInSlavemode(SiS_Pr)) { + tempah = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + if(!(tempah & SetCRT2ToRAMDAC)) temp |= 0x20; + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x32,temp); + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x00,0x1F,0x20); /* enable VB processor */ + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x1F,0xC0); + SiS_DisplayOn(SiS_Pr); + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x16) & 0x10)) { + if(!(SiS_CR36BIOSWord23b(SiS_Pr))) { + SiS_PanelDelay(SiS_Pr, 1); + } + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x26,0x01); + } + } + } + + } + + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* 315 series */ + +#ifdef SET_EMI + unsigned char r30=0, r31=0, r32=0, r33=0, cr36=0; + int didpwd = 0; + /* unsigned short emidelay=0; */ +#endif + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x1f,0xef); +#ifdef SET_EMI + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x30,0x0c); + } +#endif + } + + if(!(SiS_IsNotM650orLater(SiS_Pr))) { + /*if(SiS_Pr->ChipType < SIS_340) { */ + tempah = 0x10; + if(SiS_LCDAEnabled(SiS_Pr)) { + if(SiS_TVEnabled(SiS_Pr)) tempah = 0x18; + else tempah = 0x08; + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x4c,tempah); + /*}*/ + } + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + + SiS_SetRegByte(SiS_Pr->SiS_P3c6,0x00); + SiS_DisplayOff(SiS_Pr); + pushax = SiS_GetReg(SiS_Pr->SiS_P3c4,0x06); + if(IS_SIS740) { + SiS_SetRegAND(SiS_Pr->SiS_P3c4,0x06,0xE3); + } + + didpwd = SiS_HandlePWD(SiS_Pr); + + if(SiS_IsVAorLCD(SiS_Pr)) { + if(!didpwd) { + if(!(SiS_GetReg(SiS_Pr->SiS_Part4Port,0x26) & 0x02)) { + SiS_PanelDelayLoop(SiS_Pr, 3, 2); + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x26,0x02); + SiS_PanelDelayLoop(SiS_Pr, 3, 2); + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + SiS_GenericDelay(SiS_Pr, 17664); + } + } + } else { + SiS_PanelDelayLoop(SiS_Pr, 3, 2); + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + SiS_GenericDelay(SiS_Pr, 17664); + } + } + } + + if(!(SiS_GetReg(SiS_Pr->SiS_P3d4,0x31) & 0x40)) { + SiS_PanelDelayLoop(SiS_Pr, 3, 10); + delaylong = true; + } + + } + + if(!(SiS_IsVAMode(SiS_Pr))) { + + temp = SiS_GetReg(SiS_Pr->SiS_P3c4,0x32) & 0xDF; + if(SiS_BridgeInSlavemode(SiS_Pr)) { + tempah = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + if(!(tempah & SetCRT2ToRAMDAC)) { + if(!(SiS_LCDAEnabled(SiS_Pr))) temp |= 0x20; + } + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x32,temp); + + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); /* enable CRT2 */ + + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2e,0x7f); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2e,0x80); + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + SiS_PanelDelay(SiS_Pr, 2); + } + + } else { + + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1e,0x20); + + } + + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x00,0x1f,0x20); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2e,0x80); + + if(SiS_Pr->SiS_VBType & VB_SISPOWER) { + if( (SiS_LCDAEnabled(SiS_Pr)) || + (SiS_CRT2IsLCD(SiS_Pr)) ) { + /* Enable "LVDS PLL power on" (even on 301C) */ + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x2a,0x7f); + /* Enable "LVDS Driver Power on" (even on 301C) */ + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x30,0x7f); + } + } + + tempah = 0xc0; + if(SiS_IsDualEdge(SiS_Pr)) { + tempah = 0x80; + if(!(SiS_IsVAMode(SiS_Pr))) tempah = 0x40; + } + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x1F,tempah); + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + + SiS_PanelDelay(SiS_Pr, 2); + + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x1f,0x10); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2e,0x80); + + if(SiS_Pr->SiS_CustomT != CUT_CLEVO1400) { +#ifdef SET_EMI + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x30,0x0c); + SiS_GenericDelay(SiS_Pr, 2048); + } +#endif + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x27,0x0c); + + if(SiS_Pr->SiS_VBType & VB_SISEMI) { +#ifdef SET_EMI + cr36 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36); + + if(SiS_Pr->SiS_ROMNew) { + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr = GetLCDStructPtr661_2(SiS_Pr); + if(romptr) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x30,0x20); /* Reset */ + SiS_Pr->EMI_30 = 0; + SiS_Pr->EMI_31 = ROMAddr[romptr + SiS_Pr->SiS_EMIOffset + 0]; + SiS_Pr->EMI_32 = ROMAddr[romptr + SiS_Pr->SiS_EMIOffset + 1]; + SiS_Pr->EMI_33 = ROMAddr[romptr + SiS_Pr->SiS_EMIOffset + 2]; + if(ROMAddr[romptr + 1] & 0x10) SiS_Pr->EMI_30 = 0x40; + /* emidelay = SISGETROMW((romptr + 0x22)); */ + SiS_Pr->HaveEMI = SiS_Pr->HaveEMILCD = SiS_Pr->OverruleEMI = true; + } + } + + /* (P4_30|0x40) */ + /* Compal 1400x1050: 0x05, 0x60, 0x00 YES (1.10.7w; CR36=69) */ + /* Compal 1400x1050: 0x0d, 0x70, 0x40 YES (1.10.7x; CR36=69) */ + /* Acer 1280x1024: 0x12, 0xd0, 0x6b NO (1.10.9k; CR36=73) */ + /* Compaq 1280x1024: 0x0d, 0x70, 0x6b YES (1.12.04b; CR36=03) */ + /* Clevo 1024x768: 0x05, 0x60, 0x33 NO (1.10.8e; CR36=12, DL!) */ + /* Clevo 1024x768: 0x0d, 0x70, 0x40 (if type == 3) YES (1.10.8y; CR36=?2) */ + /* Clevo 1024x768: 0x05, 0x60, 0x33 (if type != 3) YES (1.10.8y; CR36=?2) */ + /* Asus 1024x768: ? ? (1.10.8o; CR36=?2) */ + /* Asus 1024x768: 0x08, 0x10, 0x3c (problematic) YES (1.10.8q; CR36=22) */ + + if(SiS_Pr->HaveEMI) { + r30 = SiS_Pr->EMI_30; r31 = SiS_Pr->EMI_31; + r32 = SiS_Pr->EMI_32; r33 = SiS_Pr->EMI_33; + } else { + r30 = 0; + } + + /* EMI_30 is read at driver start; however, the BIOS sets this + * (if it is used) only if the LCD is in use. In case we caught + * the machine while on TV output, this bit is not set and we + * don't know if it should be set - hence our detection is wrong. + * Work-around this here: + */ + + if((!SiS_Pr->HaveEMI) || (!SiS_Pr->HaveEMILCD)) { + switch((cr36 & 0x0f)) { + case 2: + r30 |= 0x40; + if(SiS_Pr->SiS_CustomT == CUT_CLEVO1024) r30 &= ~0x40; + if(!SiS_Pr->HaveEMI) { + r31 = 0x05; r32 = 0x60; r33 = 0x33; + if((cr36 & 0xf0) == 0x30) { + r31 = 0x0d; r32 = 0x70; r33 = 0x40; + } + } + break; + case 3: /* 1280x1024 */ + if(SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) r30 |= 0x40; + if(!SiS_Pr->HaveEMI) { + r31 = 0x12; r32 = 0xd0; r33 = 0x6b; + if(SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) { + r31 = 0x0d; r32 = 0x70; r33 = 0x6b; + } + } + break; + case 9: /* 1400x1050 */ + r30 |= 0x40; + if(!SiS_Pr->HaveEMI) { + r31 = 0x05; r32 = 0x60; r33 = 0x00; + if(SiS_Pr->SiS_CustomT == CUT_COMPAL1400_2) { + r31 = 0x0d; r32 = 0x70; r33 = 0x40; /* BIOS values */ + } + } + break; + case 11: /* 1600x1200 - unknown */ + r30 |= 0x40; + if(!SiS_Pr->HaveEMI) { + r31 = 0x05; r32 = 0x60; r33 = 0x00; + } + } + } + + /* BIOS values don't work so well sometimes */ + if(!SiS_Pr->OverruleEMI) { +#ifdef COMPAL_HACK + if(SiS_Pr->SiS_CustomT == CUT_COMPAL1400_2) { + if((cr36 & 0x0f) == 0x09) { + r30 = 0x60; r31 = 0x05; r32 = 0x60; r33 = 0x00; + } + } +#endif +#ifdef COMPAQ_HACK + if(SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) { + if((cr36 & 0x0f) == 0x03) { + r30 = 0x20; r31 = 0x12; r32 = 0xd0; r33 = 0x6b; + } + } +#endif +#ifdef ASUS_HACK + if(SiS_Pr->SiS_CustomT == CUT_ASUSA2H_2) { + if((cr36 & 0x0f) == 0x02) { + /* r30 = 0x60; r31 = 0x05; r32 = 0x60; r33 = 0x33; */ /* rev 2 */ + /* r30 = 0x20; r31 = 0x05; r32 = 0x60; r33 = 0x33; */ /* rev 3 */ + /* r30 = 0x60; r31 = 0x0d; r32 = 0x70; r33 = 0x40; */ /* rev 4 */ + /* r30 = 0x20; r31 = 0x0d; r32 = 0x70; r33 = 0x40; */ /* rev 5 */ + } + } +#endif + } + + if(!(SiS_Pr->OverruleEMI && (!r30) && (!r31) && (!r32) && (!r33))) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x30,0x20); /* Reset */ + SiS_GenericDelay(SiS_Pr, 2048); + } + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x31,r31); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x32,r32); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x33,r33); +#endif /* SET_EMI */ + + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x34,0x10); + +#ifdef SET_EMI + if( (SiS_LCDAEnabled(SiS_Pr)) || + (SiS_CRT2IsLCD(SiS_Pr)) ) { + if(r30 & 0x40) { + /*SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x2a,0x80);*/ + SiS_PanelDelayLoop(SiS_Pr, 3, 5); + if(delaylong) { + SiS_PanelDelayLoop(SiS_Pr, 3, 5); + delaylong = false; + } + SiS_WaitVBRetrace(SiS_Pr); + SiS_WaitVBRetrace(SiS_Pr); + if(SiS_Pr->SiS_CustomT == CUT_ASUSA2H_2) { + SiS_GenericDelay(SiS_Pr, 1280); + } + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x30,0x40); /* Enable */ + /*SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x2a,0x7f);*/ + } + } +#endif + } + } + + if(!(SiS_WeHaveBacklightCtrl(SiS_Pr))) { + if(SiS_IsVAorLCD(SiS_Pr)) { + SiS_PanelDelayLoop(SiS_Pr, 3, 10); + if(delaylong) { + SiS_PanelDelayLoop(SiS_Pr, 3, 10); + } + SiS_WaitVBRetrace(SiS_Pr); + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + SiS_GenericDelay(SiS_Pr, 2048); + SiS_WaitVBRetrace(SiS_Pr); + } + if(!didpwd) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x26,0x01); + } else { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x26,0x03); + } + } + } + + SiS_SetReg(SiS_Pr->SiS_P3c4,0x06,pushax); + SiS_DisplayOn(SiS_Pr); + SiS_SetRegByte(SiS_Pr->SiS_P3c6,0xff); + + } + + if(!(SiS_WeHaveBacklightCtrl(SiS_Pr))) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x00,0x7f); + } + +#endif /* CONFIG_FB_SIS_315 */ + + } + + } else { /* ============ For 301 ================ */ + + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x00); + SiS_PanelDelay(SiS_Pr, 0); + } + } + + temp = SiS_GetReg(SiS_Pr->SiS_P3c4,0x32) & 0xDF; /* lock mode */ + if(SiS_BridgeInSlavemode(SiS_Pr)) { + tempah = SiS_GetReg(SiS_Pr->SiS_P3d4,0x30); + if(!(tempah & SetCRT2ToRAMDAC)) temp |= 0x20; + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x32,temp); + + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); /* enable CRT2 */ + + if(SiS_Pr->ChipType >= SIS_315H) { + temp = SiS_GetReg(SiS_Pr->SiS_Part1Port,0x2E); + if(!(temp & 0x80)) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2E,0x80); /* BVBDOENABLE=1 */ + } + } + + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x00,0x1F,0x20); /* enable VB processor */ + + SiS_VBLongWait(SiS_Pr); + SiS_DisplayOn(SiS_Pr); + if(SiS_Pr->ChipType >= SIS_315H) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x00,0x7f); + } + SiS_VBLongWait(SiS_Pr); + + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + SiS_PanelDelay(SiS_Pr, 1); + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x00); + } + } + + } + + } else { /* =================== For LVDS ================== */ + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* 300 series */ + + if(SiS_CRT2IsLCD(SiS_Pr)) { + if(SiS_Pr->ChipType == SIS_730) { + SiS_PanelDelay(SiS_Pr, 1); + SiS_PanelDelay(SiS_Pr, 1); + SiS_PanelDelay(SiS_Pr, 1); + } + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x00); + if(!(SiS_CR36BIOSWord23d(SiS_Pr))) { + SiS_PanelDelay(SiS_Pr, 0); + } + } + + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); + SiS_DisplayOn(SiS_Pr); + SiS_UnLockCRT2(SiS_Pr); + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x02,0xBF); + if(SiS_BridgeInSlavemode(SiS_Pr)) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x01,0x1F); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x01,0x1F,0x40); + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 1) { + if(!(SiS_CRT2IsLCD(SiS_Pr))) { + SiS_WaitVBRetrace(SiS_Pr); + SiS_SetCH700x(SiS_Pr,0x0E,0x0B); + } + } + + if(SiS_CRT2IsLCD(SiS_Pr)) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x13) & 0x40)) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x16) & 0x10)) { + if(!(SiS_CR36BIOSWord23b(SiS_Pr))) { + SiS_PanelDelay(SiS_Pr, 1); + SiS_PanelDelay(SiS_Pr, 1); + } + SiS_WaitVBRetrace(SiS_Pr); + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x00); + } + } + } + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* 315 series */ + + if(!(SiS_IsNotM650orLater(SiS_Pr))) { + /*if(SiS_Pr->ChipType < SIS_340) {*/ /* XGI needs this */ + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x4c,0x18); + /*}*/ + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 0) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + SiS_SetRegSR11ANDOR(SiS_Pr,0xFB,0x00); + SiS_PanelDelay(SiS_Pr, 0); + } + } + + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); + SiS_UnLockCRT2(SiS_Pr); + + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2e,0xf7); + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + temp = SiS_GetCH701x(SiS_Pr,0x66); + temp &= 0x20; + SiS_Chrontel701xBLOff(SiS_Pr); + } + + if(SiS_Pr->ChipType != SIS_550) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2e,0x7f); + } + + if(SiS_Pr->ChipType == SIS_740) { + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(SiS_IsLCDOrLCDA(SiS_Pr)) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1E,0x20); + } + } + } + + temp1 = SiS_GetReg(SiS_Pr->SiS_Part1Port,0x2E); + if(!(temp1 & 0x80)) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2E,0x80); + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(temp) { + SiS_Chrontel701xBLOn(SiS_Pr); + } + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 0) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1E,0x20); + if(SiS_Pr->ChipType == SIS_550) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1E,0x40); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1E,0x10); + } + } + } else if(SiS_IsVAMode(SiS_Pr)) { + if(SiS_Pr->ChipType != SIS_740) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1E,0x20); + } + } + + if(!(SiS_WeHaveBacklightCtrl(SiS_Pr))) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x00,0x7f); + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(SiS_IsTVOrYPbPrOrScart(SiS_Pr)) { + SiS_Chrontel701xOn(SiS_Pr); + } + if( (SiS_IsVAMode(SiS_Pr)) || + (SiS_IsLCDOrLCDA(SiS_Pr)) ) { + SiS_ChrontelDoSomething1(SiS_Pr); + } + } + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(!(SiS_WeHaveBacklightCtrl(SiS_Pr))) { + if( (SiS_IsVAMode(SiS_Pr)) || + (SiS_IsLCDOrLCDA(SiS_Pr)) ) { + SiS_Chrontel701xBLOn(SiS_Pr); + SiS_ChrontelInitTVVSync(SiS_Pr); + } + } + } else if(SiS_Pr->SiS_IF_DEF_CH70xx == 0) { + if(!(SiS_WeHaveBacklightCtrl(SiS_Pr))) { + if(SiS_CRT2IsLCD(SiS_Pr)) { + SiS_PanelDelay(SiS_Pr, 1); + SiS_SetRegSR11ANDOR(SiS_Pr,0xF7,0x00); + } + } + } + +#endif /* CONFIG_FB_SIS_315 */ + + } /* 310 series */ + + } /* LVDS */ + +} + +/*********************************************/ +/* SET PART 1 REGISTER GROUP */ +/*********************************************/ + +/* Set CRT2 OFFSET / PITCH */ +static void +SiS_SetCRT2Offset(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RRTI) +{ + unsigned short offset; + unsigned char temp; + + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) return; + + offset = SiS_GetOffset(SiS_Pr,ModeNo,ModeIdIndex,RRTI); + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x07,(offset & 0xFF)); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x09,(offset >> 8)); + + temp = (unsigned char)(((offset >> 3) & 0xFF) + 1); + if(offset & 0x07) temp++; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x03,temp); +} + +/* Set CRT2 sync and PanelLink mode */ +static void +SiS_SetCRT2Sync(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short RefreshRateTableIndex) +{ + unsigned short tempah=0, tempbl, infoflag; + + tempbl = 0xC0; + + if(SiS_Pr->UseCustomMode) { + infoflag = SiS_Pr->CInfoFlag; + } else { + infoflag = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_InfoFlag; + } + + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { /* LVDS */ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + tempah = 0; + } else if((SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) && (SiS_Pr->SiS_LCDInfo & LCDSync)) { + tempah = SiS_Pr->SiS_LCDInfo; + } else tempah = infoflag >> 8; + tempah &= 0xC0; + tempah |= 0x20; + if(!(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit)) tempah |= 0x10; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if((SiS_Pr->SiS_CustomT == CUT_BARCO1366) || + (SiS_Pr->SiS_CustomT == CUT_BARCO1024)) { + tempah |= 0xf0; + } + if( (SiS_Pr->SiS_IF_DEF_FSTN) || + (SiS_Pr->SiS_IF_DEF_DSTN) || + (SiS_Pr->SiS_IF_DEF_TRUMPION) || + (SiS_Pr->SiS_CustomT == CUT_PANEL848) || + (SiS_Pr->SiS_CustomT == CUT_PANEL856) ) { + tempah |= 0x30; + } + if( (SiS_Pr->SiS_IF_DEF_FSTN) || + (SiS_Pr->SiS_IF_DEF_DSTN) ) { + tempah &= ~0xc0; + } + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(SiS_Pr->ChipType >= SIS_315H) { + tempah >>= 3; + tempah &= 0x18; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,0xE7,tempah); + /* Don't care about 12/18/24 bit mode - TV is via VGA, not PL */ + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0x0F,0xe0); + } + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0x0F,tempah); + } + + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* ---- 300 series --- */ + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { /* 630 - 301B(-DH) */ + + tempah = infoflag >> 8; + tempbl = 0; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->SiS_LCDInfo & LCDSync) { + tempah = SiS_Pr->SiS_LCDInfo; + tempbl = (tempah >> 6) & 0x03; + } + } + tempah &= 0xC0; + tempah |= 0x20; + if(!(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit)) tempah |= 0x10; + tempah |= 0xc0; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0x0F,tempah); + if((SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) && (!(SiS_Pr->SiS_VBType & VB_NoLCD))) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x1a,0xf0,tempbl); + } + + } else { /* 630 - 301 */ + + tempah = ((infoflag >> 8) & 0xc0) | 0x20; + if(!(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit)) tempah |= 0x10; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0x0F,tempah); + + } + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* ------- 315 series ------ */ + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { /* 315 - LVDS */ + + tempbl = 0; + if((SiS_Pr->SiS_CustomT == CUT_COMPAQ1280) && + (SiS_Pr->SiS_LCDResInfo == Panel_1280x1024)) { + tempah = infoflag >> 8; + if(SiS_Pr->SiS_LCDInfo & LCDSync) { + tempbl = ((SiS_Pr->SiS_LCDInfo & 0xc0) >> 6); + } + } else if((SiS_Pr->SiS_CustomT == CUT_CLEVO1400) && + (SiS_Pr->SiS_LCDResInfo == Panel_1400x1050)) { + tempah = infoflag >> 8; + tempbl = 0x03; + } else { + tempah = SiS_GetReg(SiS_Pr->SiS_P3d4,0x37); + tempbl = (tempah >> 6) & 0x03; + tempbl |= 0x08; + if(!(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit)) tempbl |= 0x04; + } + tempah &= 0xC0; + tempah |= 0x20; + if(!(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit)) tempah |= 0x10; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) tempah |= 0xc0; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0x0F,tempah); + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x1a,0xf0,tempbl); + } + } + + } else { /* 315 - TMDS */ + + tempah = tempbl = infoflag >> 8; + if(!SiS_Pr->UseCustomMode) { + tempbl = 0; + if((SiS_Pr->SiS_VBType & VB_SIS30xC) && (SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC)) { + if(ModeNo <= 0x13) { + tempah = SiS_GetRegByte((SiS_Pr->SiS_P3ca+0x02)); + } + } + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + if(SiS_Pr->SiS_LCDInfo & LCDSync) { + tempah = SiS_Pr->SiS_LCDInfo; + tempbl = (tempah >> 6) & 0x03; + } + } + } + } + tempah &= 0xC0; + tempah |= 0x20; + if(!(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit)) tempah |= 0x10; + if(SiS_Pr->SiS_VBType & VB_NoLCD) { + /* Imitate BIOS bug */ + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) tempah |= 0xc0; + } + if((SiS_Pr->SiS_VBType & VB_SIS30xC) && (SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC)) { + tempah >>= 3; + tempah &= 0x18; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,0xe7,tempah); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0x0F,tempah); + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x1a,0xf0,tempbl); + } + } + } + + } +#endif /* CONFIG_FB_SIS_315 */ + } + } +} + +/* Set CRT2 FIFO on 300/540/630/730 */ +#ifdef CONFIG_FB_SIS_300 +static void +SiS_SetCRT2FIFO_300(struct SiS_Private *SiS_Pr,unsigned short ModeNo) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short temp, index, modeidindex, refreshratetableindex; + unsigned short VCLK = 0, MCLK, colorth = 0, data2 = 0; + unsigned short tempbx, tempcl, CRT1ModeNo, CRT2ModeNo, SelectRate_backup; + unsigned int data, pci50, pciA0; + static const unsigned char colortharray[] = { + 1, 1, 2, 2, 3, 4 + }; + + SelectRate_backup = SiS_Pr->SiS_SelectCRT2Rate; + + if(!SiS_Pr->CRT1UsesCustomMode) { + + CRT1ModeNo = SiS_Pr->SiS_CRT1Mode; /* get CRT1 ModeNo */ + SiS_SearchModeID(SiS_Pr, &CRT1ModeNo, &modeidindex); + SiS_Pr->SiS_SetFlag &= (~ProgrammingCRT2); + SiS_Pr->SiS_SelectCRT2Rate = 0; + refreshratetableindex = SiS_GetRatePtr(SiS_Pr, CRT1ModeNo, modeidindex); + + if(CRT1ModeNo >= 0x13) { + /* Get VCLK */ + index = SiS_GetRefCRTVCLK(SiS_Pr, refreshratetableindex, SiS_Pr->SiS_UseWide); + VCLK = SiS_Pr->SiS_VCLKData[index].CLOCK; + + /* Get colordepth */ + colorth = SiS_GetColorDepth(SiS_Pr,CRT1ModeNo,modeidindex) >> 1; + if(!colorth) colorth++; + } + + } else { + + CRT1ModeNo = 0xfe; + + /* Get VCLK */ + VCLK = SiS_Pr->CSRClock_CRT1; + + /* Get color depth */ + colorth = colortharray[((SiS_Pr->CModeFlag_CRT1 & ModeTypeMask) - 2)]; + + } + + if(CRT1ModeNo >= 0x13) { + /* Get MCLK */ + if(SiS_Pr->ChipType == SIS_300) { + index = SiS_GetReg(SiS_Pr->SiS_P3c4,0x3A); + } else { + index = SiS_GetReg(SiS_Pr->SiS_P3c4,0x1A); + } + index &= 0x07; + MCLK = SiS_Pr->SiS_MCLKData_0[index].CLOCK; + + temp = ((SiS_GetReg(SiS_Pr->SiS_P3c4,0x14) >> 6) & 0x03) << 1; + if(!temp) temp++; + temp <<= 2; + + data2 = temp - ((colorth * VCLK) / MCLK); + + temp = (28 * 16) % data2; + data2 = (28 * 16) / data2; + if(temp) data2++; + + if(SiS_Pr->ChipType == SIS_300) { + + SiS_GetFIFOThresholdIndex300(SiS_Pr, &tempbx, &tempcl); + data = SiS_GetFIFOThresholdB300(tempbx, tempcl); + + } else { + + pci50 = sisfb_read_nbridge_pci_dword(SiS_Pr, 0x50); + pciA0 = sisfb_read_nbridge_pci_dword(SiS_Pr, 0xa0); + + if(SiS_Pr->ChipType == SIS_730) { + + index = (unsigned short)(((pciA0 >> 28) & 0x0f) * 3); + index += (unsigned short)(((pci50 >> 9)) & 0x03); + + /* BIOS BUG (2.04.5d, 2.04.6a use ah here, which is unset!) */ + index = 0; /* -- do it like the BIOS anyway... */ + + } else { + + pci50 >>= 24; + pciA0 >>= 24; + + index = (pci50 >> 1) & 0x07; + + if(pci50 & 0x01) index += 6; + if(!(pciA0 & 0x01)) index += 24; + + if(SiS_GetReg(SiS_Pr->SiS_P3c4,0x14) & 0x80) index += 12; + + } + + data = SiS_GetLatencyFactor630(SiS_Pr, index) + 15; + if(!(SiS_GetReg(SiS_Pr->SiS_P3c4,0x14) & 0x80)) data += 5; + + } + + data += data2; /* CRT1 Request Period */ + + SiS_Pr->SiS_SetFlag |= ProgrammingCRT2; + SiS_Pr->SiS_SelectCRT2Rate = SelectRate_backup; + + if(!SiS_Pr->UseCustomMode) { + + CRT2ModeNo = ModeNo; + SiS_SearchModeID(SiS_Pr, &CRT2ModeNo, &modeidindex); + + refreshratetableindex = SiS_GetRatePtr(SiS_Pr, CRT2ModeNo, modeidindex); + + /* Get VCLK */ + index = SiS_GetVCLK2Ptr(SiS_Pr, CRT2ModeNo, modeidindex, refreshratetableindex); + VCLK = SiS_Pr->SiS_VCLKData[index].CLOCK; + + if((SiS_Pr->SiS_CustomT == CUT_BARCO1366) || (SiS_Pr->SiS_CustomT == CUT_BARCO1024)) { + if(SiS_Pr->SiS_UseROM) { + if(ROMAddr[0x220] & 0x01) { + VCLK = ROMAddr[0x229] | (ROMAddr[0x22a] << 8); + } + } + } + + } else { + + /* Get VCLK */ + CRT2ModeNo = 0xfe; + VCLK = SiS_Pr->CSRClock; + + } + + /* Get colordepth */ + colorth = SiS_GetColorDepth(SiS_Pr,CRT2ModeNo,modeidindex) >> 1; + if(!colorth) colorth++; + + data = data * VCLK * colorth; + temp = data % (MCLK << 4); + data = data / (MCLK << 4); + if(temp) data++; + + if(data < 6) data = 6; + else if(data > 0x14) data = 0x14; + + if(SiS_Pr->ChipType == SIS_300) { + temp = 0x16; + if((data <= 0x0f) || (SiS_Pr->SiS_LCDResInfo == Panel_1280x1024)) + temp = 0x13; + } else { + temp = 0x16; + if(( (SiS_Pr->ChipType == SIS_630) || + (SiS_Pr->ChipType == SIS_730) ) && + (SiS_Pr->ChipRevision >= 0x30)) + temp = 0x1b; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x01,0xe0,temp); + + if((SiS_Pr->ChipType == SIS_630) && + (SiS_Pr->ChipRevision >= 0x30)) { + if(data > 0x13) data = 0x13; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x02,0xe0,data); + + } else { /* If mode <= 0x13, we just restore everything */ + + SiS_Pr->SiS_SetFlag |= ProgrammingCRT2; + SiS_Pr->SiS_SelectCRT2Rate = SelectRate_backup; + + } +} +#endif + +/* Set CRT2 FIFO on 315/330 series */ +#ifdef CONFIG_FB_SIS_315 +static void +SiS_SetCRT2FIFO_310(struct SiS_Private *SiS_Pr) +{ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x01,0x3B); + if( (SiS_Pr->ChipType == SIS_760) && + (SiS_Pr->SiS_SysFlags & SF_760LFB) && + (SiS_Pr->SiS_ModeType == Mode32Bpp) && + (SiS_Pr->SiS_VGAHDE >= 1280) && + (SiS_Pr->SiS_VGAVDE >= 1024) ) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2f,0x03); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x01,0x3b); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x4d,0xc0); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2f,0x01); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x4d,0xc0); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x02,0x6e); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x02,~0x3f,0x04); + } + +} +#endif + +static unsigned short +SiS_GetVGAHT2(struct SiS_Private *SiS_Pr) +{ + unsigned int tempax,tempbx; + + tempbx = (SiS_Pr->SiS_VGAVT - SiS_Pr->SiS_VGAVDE) * SiS_Pr->SiS_RVBHCMAX; + tempax = (SiS_Pr->SiS_VT - SiS_Pr->SiS_VDE) * SiS_Pr->SiS_RVBHCFACT; + tempax = (tempax * SiS_Pr->SiS_HT) / tempbx; + return (unsigned short)tempax; +} + +/* Set Part 1 / SiS bridge slave mode */ +static void +SiS_SetGroup1_301(struct SiS_Private *SiS_Pr, unsigned short ModeNo,unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short temp, modeflag, i, j, xres=0, VGAVDE; + static const unsigned short CRTranslation[] = { + /* CR0 CR1 CR2 CR3 CR4 CR5 CR6 CR7 */ + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + /* CR8 CR9 SR0A SR0B SR0C SR0D SR0E CR0F */ + 0x00, 0x0b, 0x17, 0x18, 0x19, 0x00, 0x1a, 0x00, + /* CR10 CR11 CR12 CR13 CR14 CR15 CR16 CR17 */ + 0x0c, 0x0d, 0x0e, 0x00, 0x0f, 0x10, 0x11, 0x00 + }; + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + } else if(SiS_Pr->UseCustomMode) { + modeflag = SiS_Pr->CModeFlag; + xres = SiS_Pr->CHDisplay; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + xres = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].XRes; + } + + /* The following is only done if bridge is in slave mode: */ + + if(SiS_Pr->ChipType >= SIS_315H) { + if(xres >= 1600) { /* BIOS: == 1600 */ + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x31,0x04); + } + } + + SiS_Pr->CHTotal = 8224; /* Max HT, 0x2020, results in 0x3ff in registers */ + + SiS_Pr->CHDisplay = SiS_Pr->SiS_VGAHDE; + if(modeflag & HalfDCLK) SiS_Pr->CHDisplay >>= 1; + + SiS_Pr->CHBlankStart = SiS_Pr->CHDisplay; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + SiS_Pr->CHBlankStart += 16; + } + + SiS_Pr->CHBlankEnd = 32; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(xres == 1600) SiS_Pr->CHBlankEnd += 80; + } + + temp = SiS_Pr->SiS_VGAHT - 96; + if(!(modeflag & HalfDCLK)) temp -= 32; + if(SiS_Pr->SiS_LCDInfo & LCDPass11) { + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x04); + temp |= ((SiS_GetReg(SiS_Pr->SiS_P3c4,0x0b) & 0xc0) << 2); + temp -= 3; + temp <<= 3; + } else { + if(SiS_Pr->SiS_RVBHRS2) temp = SiS_Pr->SiS_RVBHRS2; + } + SiS_Pr->CHSyncStart = temp; + + SiS_Pr->CHSyncEnd = 0xffe8; /* results in 0x2000 in registers */ + + SiS_Pr->CVTotal = 2049; /* Max VT, 0x0801, results in 0x7ff in registers */ + + VGAVDE = SiS_Pr->SiS_VGAVDE; + if (VGAVDE == 357) VGAVDE = 350; + else if(VGAVDE == 360) VGAVDE = 350; + else if(VGAVDE == 375) VGAVDE = 350; + else if(VGAVDE == 405) VGAVDE = 400; + else if(VGAVDE == 420) VGAVDE = 400; + else if(VGAVDE == 525) VGAVDE = 480; + else if(VGAVDE == 1056) VGAVDE = 1024; + SiS_Pr->CVDisplay = VGAVDE; + + SiS_Pr->CVBlankStart = SiS_Pr->CVDisplay; + + SiS_Pr->CVBlankEnd = 1; + if(ModeNo == 0x3c) SiS_Pr->CVBlankEnd = 226; + + temp = (SiS_Pr->SiS_VGAVT - VGAVDE) >> 1; + SiS_Pr->CVSyncStart = VGAVDE + temp; + + temp >>= 3; + SiS_Pr->CVSyncEnd = SiS_Pr->CVSyncStart + temp; + + SiS_CalcCRRegisters(SiS_Pr, 0); + SiS_Pr->CCRT1CRTC[16] &= ~0xE0; + + for(i = 0; i <= 7; i++) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,CRTranslation[i],SiS_Pr->CCRT1CRTC[i]); + } + for(i = 0x10, j = 8; i <= 0x12; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,CRTranslation[i],SiS_Pr->CCRT1CRTC[j]); + } + for(i = 0x15, j = 11; i <= 0x16; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,CRTranslation[i],SiS_Pr->CCRT1CRTC[j]); + } + for(i = 0x0a, j = 13; i <= 0x0c; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,CRTranslation[i],SiS_Pr->CCRT1CRTC[j]); + } + + temp = SiS_Pr->CCRT1CRTC[16] & 0xE0; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,CRTranslation[0x0E],0x1F,temp); + + temp = (SiS_Pr->CCRT1CRTC[16] & 0x01) << 5; + if(modeflag & DoubleScanMode) temp |= 0x80; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,CRTranslation[0x09],0x5F,temp); + + temp = 0; + temp |= (SiS_GetReg(SiS_Pr->SiS_P3c4,0x01) & 0x01); + if(modeflag & HalfDCLK) temp |= 0x08; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x16,temp); /* SR01: HalfDCLK[3], 8/9 div dotclock[0] */ + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0F,0x00); /* CR14: (text mode: underline location) */ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x12,0x00); /* CR17: n/a */ + + temp = 0; + if(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit) { + temp = (SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00) & 0x01) << 7; + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1A,temp); /* SR0E, dither[7] */ + + temp = SiS_GetRegByte((SiS_Pr->SiS_P3ca+0x02)); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,temp); /* ? */ +} + +/* Setup panel link + * This is used for LVDS, LCDA and Chrontel TV output + * 300/LVDS+TV, 300/301B-DH, 315/LVDS+TV, 315/LCDA + */ +static void +SiS_SetGroup1_LVDS(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short modeflag, resinfo = 0; + unsigned short push2, tempax, tempbx, tempcx, temp; + unsigned int tempeax = 0, tempebx, tempecx, tempvcfact = 0; + bool islvds = false, issis = false, chkdclkfirst = false; +#ifdef CONFIG_FB_SIS_300 + unsigned short crt2crtc = 0; +#endif +#ifdef CONFIG_FB_SIS_315 + unsigned short pushcx; +#endif + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + resinfo = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ResInfo; +#ifdef CONFIG_FB_SIS_300 + crt2crtc = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; +#endif + } else if(SiS_Pr->UseCustomMode) { + modeflag = SiS_Pr->CModeFlag; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; +#ifdef CONFIG_FB_SIS_300 + crt2crtc = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; +#endif + } + + /* is lvds if really LVDS, or 301B-DH with external LVDS transmitter */ + if((SiS_Pr->SiS_IF_DEF_LVDS == 1) || (SiS_Pr->SiS_VBType & VB_NoLCD)) { + islvds = true; + } + + /* is really sis if sis bridge, but not 301B-DH */ + if((SiS_Pr->SiS_VBType & VB_SISVB) && (!(SiS_Pr->SiS_VBType & VB_NoLCD))) { + issis = true; + } + + if((SiS_Pr->ChipType >= SIS_315H) && (islvds) && (!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA))) { + if((!SiS_Pr->SiS_IF_DEF_FSTN) && (!SiS_Pr->SiS_IF_DEF_DSTN)) { + chkdclkfirst = true; + } + } + +#ifdef CONFIG_FB_SIS_315 + if((SiS_Pr->ChipType >= SIS_315H) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + if(IS_SIS330) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2D,0x10); + } else if(IS_SIS740) { + if(islvds) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,0xfb,0x04); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2D,0x03); + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2D,0x10); + } + } else { + if(islvds) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,0xfb,0x04); + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2D,0x00); + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x2D,0x0f); + if(SiS_Pr->SiS_VBType & VB_SIS30xC) { + if((SiS_Pr->SiS_LCDResInfo == Panel_1024x768) || + (SiS_Pr->SiS_LCDResInfo == Panel_1280x1024)) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x2D,0x20); + } + } + } + } + } +#endif + + /* Horizontal */ + + tempax = SiS_Pr->SiS_LCDHDES; + if(islvds) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!SiS_Pr->SiS_IF_DEF_FSTN && !SiS_Pr->SiS_IF_DEF_DSTN) { + if((SiS_Pr->SiS_LCDResInfo == Panel_640x480) && + (!(SiS_Pr->SiS_VBInfo & SetInSlaveMode))) { + tempax -= 8; + } + } + } + } + + temp = (tempax & 0x0007); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1A,temp); /* BPLHDESKEW[2:0] */ + temp = (tempax >> 3) & 0x00FF; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x16,temp); /* BPLHDESKEW[10:3] */ + + tempbx = SiS_Pr->SiS_HDE; + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + tempbx = SiS_Pr->PanelXRes; + } + if((SiS_Pr->SiS_LCDResInfo == Panel_320x240_1) || + (SiS_Pr->SiS_LCDResInfo == Panel_320x240_2) || + (SiS_Pr->SiS_LCDResInfo == Panel_320x240_3)) { + tempbx >>= 1; + } + } + + tempax += tempbx; + if(tempax >= SiS_Pr->SiS_HT) tempax -= SiS_Pr->SiS_HT; + + temp = tempax; + if(temp & 0x07) temp += 8; + temp >>= 3; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x17,temp); /* BPLHDEE */ + + tempcx = (SiS_Pr->SiS_HT - tempbx) >> 2; + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + if(SiS_Pr->PanelHRS != 999) tempcx = SiS_Pr->PanelHRS; + } + } + + tempcx += tempax; + if(tempcx >= SiS_Pr->SiS_HT) tempcx -= SiS_Pr->SiS_HT; + + temp = (tempcx >> 3) & 0x00FF; + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(SiS_Pr->SiS_IF_DEF_TRUMPION) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + switch(ModeNo) { + case 0x04: + case 0x05: + case 0x0d: temp = 0x56; break; + case 0x10: temp = 0x60; break; + case 0x13: temp = 0x5f; break; + case 0x40: + case 0x41: + case 0x4f: + case 0x43: + case 0x44: + case 0x62: + case 0x56: + case 0x53: + case 0x5d: + case 0x5e: temp = 0x54; break; + } + } + } + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x14,temp); /* BPLHRS */ + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + temp += 2; + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + temp += 8; + if(SiS_Pr->PanelHRE != 999) { + temp = tempcx + SiS_Pr->PanelHRE; + if(temp >= SiS_Pr->SiS_HT) temp -= SiS_Pr->SiS_HT; + temp >>= 3; + } + } + } else { + temp += 10; + } + + temp &= 0x1F; + temp |= ((tempcx & 0x07) << 5); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x15,temp); /* BPLHRE */ + + /* Vertical */ + + tempax = SiS_Pr->SiS_VGAVDE; + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + tempax = SiS_Pr->PanelYRes; + } + } + + tempbx = SiS_Pr->SiS_LCDVDES + tempax; + if(tempbx >= SiS_Pr->SiS_VT) tempbx -= SiS_Pr->SiS_VT; + + push2 = tempbx; + + tempcx = SiS_Pr->SiS_VGAVT - SiS_Pr->SiS_VGAVDE; + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + tempcx = SiS_Pr->SiS_VGAVT - SiS_Pr->PanelYRes; + } + } + } + if(islvds) tempcx >>= 1; + else tempcx >>= 2; + + if( (SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) && + (!(SiS_Pr->SiS_LCDInfo & LCDPass11)) && + (SiS_Pr->PanelVRS != 999) ) { + tempcx = SiS_Pr->PanelVRS; + tempbx += tempcx; + if(issis) tempbx++; + } else { + tempbx += tempcx; + if(SiS_Pr->ChipType < SIS_315H) tempbx++; + else if(issis) tempbx++; + } + + if(tempbx >= SiS_Pr->SiS_VT) tempbx -= SiS_Pr->SiS_VT; + + temp = tempbx & 0x00FF; + if(SiS_Pr->SiS_IF_DEF_TRUMPION) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(ModeNo == 0x10) temp = 0xa9; + } + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,temp); /* BPLVRS */ + + tempcx >>= 3; + tempcx++; + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + if(SiS_Pr->PanelVRE != 999) tempcx = SiS_Pr->PanelVRE; + } + } + + tempcx += tempbx; + temp = tempcx & 0x000F; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0xF0,temp); /* BPLVRE */ + + temp = ((tempbx >> 8) & 0x07) << 3; + if(SiS_Pr->SiS_IF_DEF_FSTN || SiS_Pr->SiS_IF_DEF_DSTN) { + if(SiS_Pr->SiS_HDE != 640) { + if(SiS_Pr->SiS_VGAVDE != SiS_Pr->SiS_VDE) temp |= 0x40; + } + } else if(SiS_Pr->SiS_VGAVDE != SiS_Pr->SiS_VDE) temp |= 0x40; + if(SiS_Pr->SiS_SetFlag & EnableLVDSDDA) temp |= 0x40; + tempbx = 0x87; + if((SiS_Pr->ChipType >= SIS_315H) || + (SiS_Pr->ChipRevision >= 0x30)) { + tempbx = 0x07; + if((SiS_Pr->SiS_IF_DEF_CH70xx == 1) && (SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) { + if(SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00) & 0x03) temp |= 0x80; + } + /* Chrontel 701x operates in 24bit mode (8-8-8, 2x12bit multiplexed) via VGA2 */ + if(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + if(SiS_GetReg(SiS_Pr->SiS_P3c4,0x06) & 0x10) temp |= 0x80; + } else { + if(SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00) & 0x01) temp |= 0x80; + } + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x1A,tempbx,temp); + + tempbx = push2; /* BPLVDEE */ + + tempcx = SiS_Pr->SiS_LCDVDES; /* BPLVDES */ + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_640x480: + tempbx = SiS_Pr->SiS_VGAVDE - 1; + tempcx = SiS_Pr->SiS_VGAVDE; + break; + case Panel_800x600: + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + if(resinfo == SIS_RI_800x600) tempcx++; + } + break; + case Panel_1024x600: + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + if(resinfo == SIS_RI_1024x600) tempcx++; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + if(resinfo == SIS_RI_800x600) tempcx++; + } + } + break; + case Panel_1024x768: + if(SiS_Pr->ChipType < SIS_315H) { + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + if(resinfo == SIS_RI_1024x768) tempcx++; + } + } + break; + } + } + + temp = ((tempbx >> 8) & 0x07) << 3; + temp |= ((tempcx >> 8) & 0x07); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1D,temp); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1C,tempbx); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1B,tempcx); + + /* Vertical scaling */ + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* 300 series */ + tempeax = SiS_Pr->SiS_VGAVDE << 6; + temp = (tempeax % (unsigned int)SiS_Pr->SiS_VDE); + tempeax = tempeax / (unsigned int)SiS_Pr->SiS_VDE; + if(temp) tempeax++; + + if(SiS_Pr->SiS_SetFlag & EnableLVDSDDA) tempeax = 0x3F; + + temp = (unsigned short)(tempeax & 0x00FF); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1E,temp); /* BPLVCFACT */ + tempvcfact = temp; +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* 315 series */ + tempeax = SiS_Pr->SiS_VGAVDE << 18; + tempebx = SiS_Pr->SiS_VDE; + temp = (tempeax % tempebx); + tempeax = tempeax / tempebx; + if(temp) tempeax++; + tempvcfact = tempeax; + + temp = (unsigned short)(tempeax & 0x00FF); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x37,temp); + temp = (unsigned short)((tempeax & 0x00FF00) >> 8); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x36,temp); + temp = (unsigned short)((tempeax & 0x00030000) >> 16); + if(SiS_Pr->SiS_VDE == SiS_Pr->SiS_VGAVDE) temp |= 0x04; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x35,temp); + + if(SiS_Pr->SiS_VBType & VB_SISPART4SCALER) { + temp = (unsigned short)(tempeax & 0x00FF); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x3c,temp); + temp = (unsigned short)((tempeax & 0x00FF00) >> 8); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x3b,temp); + temp = (unsigned short)(((tempeax & 0x00030000) >> 16) << 6); + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x3a,0x3f,temp); + temp = 0; + if(SiS_Pr->SiS_VDE != SiS_Pr->SiS_VGAVDE) temp |= 0x08; + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x30,0xf3,temp); + } +#endif + + } + + /* Horizontal scaling */ + + tempeax = SiS_Pr->SiS_VGAHDE; /* 1f = ( (VGAHDE * 65536) / ( (VGAHDE * 65536) / HDE ) ) - 1*/ + if(chkdclkfirst) { + if(modeflag & HalfDCLK) tempeax >>= 1; + } + tempebx = tempeax << 16; + if(SiS_Pr->SiS_HDE == tempeax) { + tempecx = 0xFFFF; + } else { + tempecx = tempebx / SiS_Pr->SiS_HDE; + if(SiS_Pr->ChipType >= SIS_315H) { + if(tempebx % SiS_Pr->SiS_HDE) tempecx++; + } + } + + if(SiS_Pr->ChipType >= SIS_315H) { + tempeax = (tempebx / tempecx) - 1; + } else { + tempeax = ((SiS_Pr->SiS_VGAHT << 16) / tempecx) - 1; + } + tempecx = (tempecx << 16) | (tempeax & 0xFFFF); + temp = (unsigned short)(tempecx & 0x00FF); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1F,temp); + + if(SiS_Pr->ChipType >= SIS_315H) { + tempeax = (SiS_Pr->SiS_VGAVDE << 18) / tempvcfact; + tempbx = (unsigned short)(tempeax & 0xFFFF); + } else { + tempeax = SiS_Pr->SiS_VGAVDE << 6; + tempbx = tempvcfact & 0x3f; + if(tempbx == 0) tempbx = 64; + tempeax /= tempbx; + tempbx = (unsigned short)(tempeax & 0xFFFF); + } + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) tempbx--; + if(SiS_Pr->SiS_SetFlag & EnableLVDSDDA) { + if((!SiS_Pr->SiS_IF_DEF_FSTN) && (!SiS_Pr->SiS_IF_DEF_DSTN)) tempbx = 1; + else if(SiS_Pr->SiS_LCDResInfo != Panel_640x480) tempbx = 1; + } + + temp = ((tempbx >> 8) & 0x07) << 3; + temp = temp | ((tempecx >> 8) & 0x07); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x20,temp); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x21,tempbx); + + tempecx >>= 16; /* BPLHCFACT */ + if(!chkdclkfirst) { + if(modeflag & HalfDCLK) tempecx >>= 1; + } + temp = (unsigned short)((tempecx & 0xFF00) >> 8); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x22,temp); + temp = (unsigned short)(tempecx & 0x00FF); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x23,temp); + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + if((islvds) || (SiS_Pr->SiS_VBInfo & VB_SISLVDS)) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1e,0x20); + } + } else { + if(islvds) { + if(SiS_Pr->ChipType == SIS_740) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1e,0x03); + } else { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1e,0x23); + } + } + } + } +#endif + +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->SiS_IF_DEF_TRUMPION) { + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned char *trumpdata; + int i, j = crt2crtc; + unsigned char TrumpMode13[4] = { 0x01, 0x10, 0x2c, 0x00 }; + unsigned char TrumpMode10_1[4] = { 0x01, 0x10, 0x27, 0x00 }; + unsigned char TrumpMode10_2[4] = { 0x01, 0x16, 0x10, 0x00 }; + + if(SiS_Pr->SiS_UseROM) { + trumpdata = &ROMAddr[0x8001 + (j * 80)]; + } else { + if(SiS_Pr->SiS_LCDTypeInfo == 0x0e) j += 7; + trumpdata = &SiS300_TrumpionData[j][0]; + } + + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x02,0xbf); + for(i=0; i<5; i++) { + SiS_SetTrumpionBlock(SiS_Pr, trumpdata); + } + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(ModeNo == 0x13) { + for(i=0; i<4; i++) { + SiS_SetTrumpionBlock(SiS_Pr, &TrumpMode13[0]); + } + } else if(ModeNo == 0x10) { + for(i=0; i<4; i++) { + SiS_SetTrumpionBlock(SiS_Pr, &TrumpMode10_1[0]); + SiS_SetTrumpionBlock(SiS_Pr, &TrumpMode10_2[0]); + } + } + } + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x02,0x40); + } +#endif + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->SiS_IF_DEF_FSTN || SiS_Pr->SiS_IF_DEF_DSTN) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x25,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x26,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x27,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x28,0x87); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x29,0x5A); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2A,0x4B); + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x44,~0x07,0x03); + tempax = SiS_Pr->SiS_HDE; /* Blps = lcdhdee(lcdhdes+HDE) + 64 */ + if(SiS_Pr->SiS_LCDResInfo == Panel_320x240_1 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_2 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_3) tempax >>= 1; + tempax += 64; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x38,tempax & 0xff); + temp = (tempax >> 8) << 3; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x35,~0x078,temp); + tempax += 32; /* Blpe = lBlps+32 */ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x39,tempax & 0xff); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3A,0x00); /* Bflml = 0 */ + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x3C,~0x007); + + tempax = SiS_Pr->SiS_VDE; + if(SiS_Pr->SiS_LCDResInfo == Panel_320x240_1 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_2 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_3) tempax >>= 1; + tempax >>= 1; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3B,tempax & 0xff); + temp = (tempax >> 8) << 3; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x3C,~0x038,temp); + + tempeax = SiS_Pr->SiS_HDE; + if(SiS_Pr->SiS_LCDResInfo == Panel_320x240_1 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_2 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_3) tempeax >>= 1; + tempeax <<= 2; /* BDxFIFOSTOP = (HDE*4)/128 */ + temp = tempeax & 0x7f; + tempeax >>= 7; + if(temp) tempeax++; + temp = tempeax & 0x3f; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x45,temp); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3F,0x00); /* BDxWadrst0 */ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3E,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3D,0x10); + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x3C,~0x040); + + tempax = SiS_Pr->SiS_HDE; + if(SiS_Pr->SiS_LCDResInfo == Panel_320x240_1 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_2 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_3) tempax >>= 1; + tempax >>= 4; /* BDxWadroff = HDE*4/8/8 */ + pushcx = tempax; + temp = tempax & 0x00FF; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x43,temp); + temp = ((tempax & 0xFF00) >> 8) << 3; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port, 0x44, 0x07, temp); + + tempax = SiS_Pr->SiS_VDE; /* BDxWadrst1 = BDxWadrst0 + BDxWadroff * VDE */ + if(SiS_Pr->SiS_LCDResInfo == Panel_320x240_1 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_2 || + SiS_Pr->SiS_LCDResInfo == Panel_320x240_3) tempax >>= 1; + tempeax = tempax * pushcx; + temp = tempeax & 0xFF; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x42,temp); + temp = (tempeax & 0xFF00) >> 8; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x41,temp); + temp = ((tempeax & 0xFF0000) >> 16) | 0x10; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x40,temp); + temp = ((tempeax & 0x01000000) >> 24) << 7; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port, 0x3C, 0x7F, temp); + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2F,0x03); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x03,0x50); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x04,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2F,0x01); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x19,0x38); + + if(SiS_Pr->SiS_IF_DEF_FSTN) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2b,0x02); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2c,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2d,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x35,0x0c); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x36,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x37,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x38,0x80); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x39,0xA0); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3a,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3b,0xf0); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3c,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3d,0x10); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3e,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x3f,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x40,0x10); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x41,0x25); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x42,0x80); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x43,0x14); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x44,0x03); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x45,0x0a); + } + } +#endif /* CONFIG_FB_SIS_315 */ +} + +/* Set Part 1 */ +static void +SiS_SetGroup1(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; +#endif + unsigned short temp=0, tempax=0, tempbx=0, tempcx=0, bridgeadd=0; + unsigned short pushbx=0, CRT1Index=0, modeflag, resinfo=0; +#ifdef CONFIG_FB_SIS_315 + unsigned short tempbl=0; +#endif + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_SetGroup1_LVDS(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + return; + } + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + } else if(SiS_Pr->UseCustomMode) { + modeflag = SiS_Pr->CModeFlag; + } else { + CRT1Index = SiS_GetRefCRT1CRTC(SiS_Pr, RefreshRateTableIndex, SiS_Pr->SiS_UseWideCRT2); + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + } + + SiS_SetCRT2Offset(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + + if( ! ((SiS_Pr->ChipType >= SIS_315H) && + (SiS_Pr->SiS_IF_DEF_LVDS == 1) && + (SiS_Pr->SiS_VBInfo & SetInSlaveMode)) ) { + + if(SiS_Pr->ChipType < SIS_315H ) { +#ifdef CONFIG_FB_SIS_300 + SiS_SetCRT2FIFO_300(SiS_Pr, ModeNo); +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + SiS_SetCRT2FIFO_310(SiS_Pr); +#endif + } + + /* 1. Horizontal setup */ + + if(SiS_Pr->ChipType < SIS_315H ) { + +#ifdef CONFIG_FB_SIS_300 /* ------------- 300 series --------------*/ + + temp = (SiS_Pr->SiS_VGAHT - 1) & 0x0FF; /* BTVGA2HT 0x08,0x09 */ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x08,temp); /* CRT2 Horizontal Total */ + + temp = (((SiS_Pr->SiS_VGAHT - 1) & 0xFF00) >> 8) << 4; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x09,0x0f,temp); /* CRT2 Horizontal Total Overflow [7:4] */ + + temp = (SiS_Pr->SiS_VGAHDE + 12) & 0x0FF; /* BTVGA2HDEE 0x0A,0x0C */ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0A,temp); /* CRT2 Horizontal Display Enable End */ + + pushbx = SiS_Pr->SiS_VGAHDE + 12; /* bx BTVGA2HRS 0x0B,0x0C */ + tempcx = (SiS_Pr->SiS_VGAHT - SiS_Pr->SiS_VGAHDE) >> 2; + tempbx = pushbx + tempcx; + tempcx <<= 1; + tempcx += tempbx; + + bridgeadd = 12; + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* ------------------- 315/330 series --------------- */ + + tempcx = SiS_Pr->SiS_VGAHT; /* BTVGA2HT 0x08,0x09 */ + if(modeflag & HalfDCLK) { + if(SiS_Pr->SiS_VBType & VB_SISVB) { + tempcx >>= 1; + } else { + tempax = SiS_Pr->SiS_VGAHDE >> 1; + tempcx = SiS_Pr->SiS_HT - SiS_Pr->SiS_HDE + tempax; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + tempcx = SiS_Pr->SiS_HT - tempax; + } + } + } + tempcx--; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x08,tempcx); /* CRT2 Horizontal Total */ + temp = (tempcx >> 4) & 0xF0; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x09,0x0F,temp); /* CRT2 Horizontal Total Overflow [7:4] */ + + tempcx = SiS_Pr->SiS_VGAHT; /* BTVGA2HDEE 0x0A,0x0C */ + tempbx = SiS_Pr->SiS_VGAHDE; + tempcx -= tempbx; + tempcx >>= 2; + if(modeflag & HalfDCLK) { + tempbx >>= 1; + tempcx >>= 1; + } + tempbx += 16; + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0A,tempbx); /* CRT2 Horizontal Display Enable End */ + + pushbx = tempbx; + tempcx >>= 1; + tempbx += tempcx; + tempcx += tempbx; + + bridgeadd = 16; + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->ChipType >= SIS_661) { + if((SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) || + (SiS_Pr->SiS_LCDResInfo == Panel_1280x1024)) { + if(resinfo == SIS_RI_1280x1024) { + tempcx = (tempcx & 0xff00) | 0x30; + } else if(resinfo == SIS_RI_1600x1200) { + tempcx = (tempcx & 0xff00) | 0xff; + } + } + } + } + +#endif /* CONFIG_FB_SIS_315 */ + + } /* 315/330 series */ + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->UseCustomMode) { + tempbx = SiS_Pr->CHSyncStart + bridgeadd; + tempcx = SiS_Pr->CHSyncEnd + bridgeadd; + tempax = SiS_Pr->SiS_VGAHT; + if(modeflag & HalfDCLK) tempax >>= 1; + tempax--; + if(tempcx > tempax) tempcx = tempax; + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC) { + unsigned char cr4, cr14, cr5, cr15; + if(SiS_Pr->UseCustomMode) { + cr4 = SiS_Pr->CCRT1CRTC[4]; + cr14 = SiS_Pr->CCRT1CRTC[14]; + cr5 = SiS_Pr->CCRT1CRTC[5]; + cr15 = SiS_Pr->CCRT1CRTC[15]; + } else { + cr4 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[4]; + cr14 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[14]; + cr5 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[5]; + cr15 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[15]; + } + tempbx = ((cr4 | ((cr14 & 0xC0) << 2)) - 3) << 3; /* (VGAHRS-3)*8 */ + tempcx = (((cr5 & 0x1f) | ((cr15 & 0x04) << (5-2))) - 3) << 3; /* (VGAHRE-3)*8 */ + tempcx &= 0x00FF; + tempcx |= (tempbx & 0xFF00); + tempbx += bridgeadd; + tempcx += bridgeadd; + tempax = SiS_Pr->SiS_VGAHT; + if(modeflag & HalfDCLK) tempax >>= 1; + tempax--; + if(tempcx > tempax) tempcx = tempax; + } + + if(SiS_Pr->SiS_TVMode & (TVSetNTSC1024 | TVSet525p1024)) { + tempbx = 1040; + tempcx = 1044; /* HWCursor bug! */ + } + + } + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0B,tempbx); /* CRT2 Horizontal Retrace Start */ + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0D,tempcx); /* CRT2 Horizontal Retrace End */ + + temp = ((tempbx >> 8) & 0x0F) | ((pushbx >> 4) & 0xF0); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0C,temp); /* Overflow */ + + /* 2. Vertical setup */ + + tempcx = SiS_Pr->SiS_VGAVT - 1; + temp = tempcx & 0x00FF; + + if(SiS_Pr->ChipType < SIS_661) { + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToSVIDEO | SetCRT2ToAVIDEO)) { + temp--; + } + } + } else { + temp--; + } + } else if(SiS_Pr->ChipType >= SIS_315H) { + temp--; + } + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0E,temp); /* CRT2 Vertical Total */ + + tempbx = SiS_Pr->SiS_VGAVDE - 1; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x0F,tempbx); /* CRT2 Vertical Display Enable End */ + + temp = ((tempbx >> 5) & 0x38) | ((tempcx >> 8) & 0x07); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x12,temp); /* Overflow */ + + if((SiS_Pr->ChipType >= SIS_315H) && (SiS_Pr->ChipType < SIS_661)) { + tempbx++; + tempax = tempbx; + tempcx++; + tempcx -= tempax; + tempcx >>= 2; + tempbx += tempcx; + if(tempcx < 4) tempcx = 4; + tempcx >>= 2; + tempcx += tempbx; + tempcx++; + } else { + tempbx = (SiS_Pr->SiS_VGAVT + SiS_Pr->SiS_VGAVDE) >> 1; /* BTVGA2VRS 0x10,0x11 */ + tempcx = ((SiS_Pr->SiS_VGAVT - SiS_Pr->SiS_VGAVDE) >> 4) + tempbx + 1; /* BTVGA2VRE 0x11 */ + } + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->UseCustomMode) { + tempbx = SiS_Pr->CVSyncStart; + tempcx = SiS_Pr->CVSyncEnd; + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC) { + unsigned char cr8, cr7, cr13; + if(SiS_Pr->UseCustomMode) { + cr8 = SiS_Pr->CCRT1CRTC[8]; + cr7 = SiS_Pr->CCRT1CRTC[7]; + cr13 = SiS_Pr->CCRT1CRTC[13]; + tempcx = SiS_Pr->CCRT1CRTC[9]; + } else { + cr8 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[8]; + cr7 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[7]; + cr13 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[13]; + tempcx = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[9]; + } + tempbx = cr8; + if(cr7 & 0x04) tempbx |= 0x0100; + if(cr7 & 0x80) tempbx |= 0x0200; + if(cr13 & 0x08) tempbx |= 0x0400; + } + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x10,tempbx); /* CRT2 Vertical Retrace Start */ + + temp = ((tempbx >> 4) & 0x70) | (tempcx & 0x0F); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x11,temp); /* CRT2 Vert. Retrace End; Overflow */ + + /* 3. Panel delay compensation */ + + if(SiS_Pr->ChipType < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 /* ---------- 300 series -------------- */ + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + temp = 0x20; + if(SiS_Pr->ChipType == SIS_300) { + temp = 0x10; + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) temp = 0x2c; + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) temp = 0x20; + } + if(SiS_Pr->SiS_VBType & VB_SIS301) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) temp = 0x20; + } + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x960) temp = 0x24; + if(SiS_Pr->SiS_LCDResInfo == Panel_Custom) temp = 0x2c; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) temp = 0x08; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) temp = 0x2c; + else temp = 0x20; + } + if(SiS_Pr->SiS_UseROM) { + if(ROMAddr[0x220] & 0x80) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTVNoYPbPrHiVision) + temp = ROMAddr[0x221]; + else if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) + temp = ROMAddr[0x222]; + else if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) + temp = ROMAddr[0x223]; + else + temp = ROMAddr[0x224]; + } + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->PDC != -1) temp = SiS_Pr->PDC; + } + + } else { + temp = 0x20; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) { + if(SiS_Pr->SiS_LCDResInfo == Panel_640x480) temp = 0x04; + } + if(SiS_Pr->SiS_UseROM) { + if(ROMAddr[0x220] & 0x80) { + temp = ROMAddr[0x220]; + } + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->PDC != -1) temp = SiS_Pr->PDC; + } + } + + temp &= 0x3c; + + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,~0x3C,temp); /* Panel Link Delay Compensation; (Software Command Reset; Power Saving) */ + +#endif /* CONFIG_FB_SIS_300 */ + + } else { + +#ifdef CONFIG_FB_SIS_315 /* --------------- 315/330 series ---------------*/ + + if(SiS_Pr->ChipType < SIS_661) { + + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + + if(SiS_Pr->ChipType == SIS_740) temp = 0x03; + else temp = 0x00; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) temp = 0x0a; + tempbl = 0xF0; + if(SiS_Pr->ChipType == SIS_650) { + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) tempbl = 0x0F; + } + } + + if(SiS_Pr->SiS_IF_DEF_DSTN || SiS_Pr->SiS_IF_DEF_FSTN) { + temp = 0x08; + tempbl = 0; + if((SiS_Pr->SiS_UseROM) && (!(SiS_Pr->SiS_ROMNew))) { + if(ROMAddr[0x13c] & 0x80) tempbl = 0xf0; + } + } + + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2D,tempbl,temp); /* Panel Link Delay Compensation */ + } + + } /* < 661 */ + + tempax = 0; + if(modeflag & DoubleScanMode) tempax |= 0x80; + if(modeflag & HalfDCLK) tempax |= 0x40; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2C,0x3f,tempax); + +#endif /* CONFIG_FB_SIS_315 */ + + } + + } /* Slavemode */ + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if((SiS_Pr->SiS_VBType & VB_NoLCD) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD)) { + /* For 301BDH with LCD, we set up the Panel Link */ + SiS_SetGroup1_LVDS(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } else if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + SiS_SetGroup1_301(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + } else { + if(SiS_Pr->ChipType < SIS_315H) { + SiS_SetGroup1_LVDS(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } else { + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if((!(SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) || (SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + SiS_SetGroup1_LVDS(SiS_Pr, ModeNo,ModeIdIndex,RefreshRateTableIndex); + } + } else { + SiS_SetGroup1_LVDS(SiS_Pr, ModeNo,ModeIdIndex,RefreshRateTableIndex); + } + } + } +} + +/*********************************************/ +/* SET PART 2 REGISTER GROUP */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_315 +static unsigned char * +SiS_GetGroup2CLVXPtr(struct SiS_Private *SiS_Pr, int tabletype) +{ + const unsigned char *tableptr = NULL; + unsigned short a, b, p = 0; + + a = SiS_Pr->SiS_VGAHDE; + b = SiS_Pr->SiS_HDE; + if(tabletype) { + a = SiS_Pr->SiS_VGAVDE; + b = SiS_Pr->SiS_VDE; + } + + if(a < b) { + tableptr = SiS_Part2CLVX_1; + } else if(a == b) { + tableptr = SiS_Part2CLVX_2; + } else { + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + tableptr = SiS_Part2CLVX_4; + } else { + tableptr = SiS_Part2CLVX_3; + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525i) tableptr = SiS_Part2CLVX_3; + else if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) tableptr = SiS_Part2CLVX_3; + else tableptr = SiS_Part2CLVX_5; + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + tableptr = SiS_Part2CLVX_6; + } + do { + if((tableptr[p] | tableptr[p+1] << 8) == a) break; + p += 0x42; + } while((tableptr[p] | tableptr[p+1] << 8) != 0xffff); + if((tableptr[p] | tableptr[p+1] << 8) == 0xffff) p -= 0x42; + } + p += 2; + return ((unsigned char *)&tableptr[p]); +} + +static void +SiS_SetGroup2_C_ELV(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned char *tableptr; + unsigned char temp; + int i, j; + + if(!(SiS_Pr->SiS_VBType & VB_SISTAP4SCALER)) return; + + tableptr = SiS_GetGroup2CLVXPtr(SiS_Pr, 0); + for(i = 0x80, j = 0; i <= 0xbf; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port, i, tableptr[j]); + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + tableptr = SiS_GetGroup2CLVXPtr(SiS_Pr, 1); + for(i = 0xc0, j = 0; i <= 0xff; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port, i, tableptr[j]); + } + } + temp = 0x10; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) temp |= 0x04; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x4e,0xeb,temp); +} + +static bool +SiS_GetCRT2Part2Ptr(struct SiS_Private *SiS_Pr,unsigned short ModeNo,unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex,unsigned short *CRT2Index, + unsigned short *ResIndex) +{ + + if(SiS_Pr->ChipType < SIS_315H) return false; + + if(ModeNo <= 0x13) + (*ResIndex) = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + else + (*ResIndex) = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + + (*ResIndex) &= 0x3f; + (*CRT2Index) = 0; + + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) { + (*CRT2Index) = 200; + } + } + + if(SiS_Pr->SiS_CustomT == CUT_ASUSA2H_2) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(SiS_Pr->SiS_SetFlag & LCDVESATiming) (*CRT2Index) = 206; + } + } + return (((*CRT2Index) != 0)); +} +#endif + +#ifdef CONFIG_FB_SIS_300 +static void +SiS_Group2LCDSpecial(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short crt2crtc) +{ + unsigned short tempcx; + static const unsigned char atable[] = { + 0xc3,0x9e,0xc3,0x9e,0x02,0x02,0x02, + 0xab,0x87,0xab,0x9e,0xe7,0x02,0x02 + }; + + if(!SiS_Pr->UseCustomMode) { + if( ( ( (SiS_Pr->ChipType == SIS_630) || + (SiS_Pr->ChipType == SIS_730) ) && + (SiS_Pr->ChipRevision > 2) ) && + (SiS_Pr->SiS_LCDResInfo == Panel_1024x768) && + (!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) && + (!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) ) { + if(ModeNo == 0x13) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x04,0xB9); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x05,0xCC); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x06,0xA6); + } else if((crt2crtc & 0x3F) == 4) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x2B); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x13); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x04,0xE5); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x05,0x08); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x06,0xE2); + } + } + + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_LCDTypeInfo == 0x0c) { + crt2crtc &= 0x1f; + tempcx = 0; + if(!(SiS_Pr->SiS_VBInfo & SetNotSimuMode)) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + tempcx += 7; + } + } + tempcx += crt2crtc; + if(crt2crtc >= 4) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x06,0xff); + } + + if(!(SiS_Pr->SiS_VBInfo & SetNotSimuMode)) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(crt2crtc == 4) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x28); + } + } + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x18); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x04,atable[tempcx]); + } + } + } +} + +/* For ECS A907. Highly preliminary. */ +static void +SiS_Set300Part2Regs(struct SiS_Private *SiS_Pr, unsigned short ModeIdIndex, unsigned short RefreshRateTableIndex, + unsigned short ModeNo) +{ + const struct SiS_Part2PortTbl *CRT2Part2Ptr = NULL; + unsigned short crt2crtc, resindex; + int i, j; + + if(SiS_Pr->ChipType != SIS_300) return; + if(!(SiS_Pr->SiS_VBType & VB_SIS30xBLV)) return; + if(SiS_Pr->UseCustomMode) return; + + if(ModeNo <= 0x13) { + crt2crtc = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + } else { + crt2crtc = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + } + + resindex = crt2crtc & 0x3F; + if(SiS_Pr->SiS_SetFlag & LCDVESATiming) CRT2Part2Ptr = SiS_Pr->SiS_CRT2Part2_1024x768_1; + else CRT2Part2Ptr = SiS_Pr->SiS_CRT2Part2_1024x768_2; + + /* The BIOS code (1.16.51,56) is obviously a fragment! */ + if(ModeNo > 0x13) { + CRT2Part2Ptr = SiS_Pr->SiS_CRT2Part2_1024x768_1; + resindex = 4; + } + + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x01,0x80,(CRT2Part2Ptr+resindex)->CR[0]); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x02,0x80,(CRT2Part2Ptr+resindex)->CR[1]); + for(i = 2, j = 0x04; j <= 0x06; i++, j++ ) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,j,(CRT2Part2Ptr+resindex)->CR[i]); + } + for(j = 0x1c; j <= 0x1d; i++, j++ ) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,j,(CRT2Part2Ptr+resindex)->CR[i]); + } + for(j = 0x1f; j <= 0x21; i++, j++ ) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,j,(CRT2Part2Ptr+resindex)->CR[i]); + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x23,(CRT2Part2Ptr+resindex)->CR[10]); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x25,0x0f,(CRT2Part2Ptr+resindex)->CR[11]); +} +#endif + +static void +SiS_SetTVSpecial(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + if(!(SiS_Pr->SiS_VBType & VB_SIS30xBLV)) return; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToTVNoHiVision)) return; + if(SiS_Pr->SiS_TVMode & (TVSetYPbPr525p | TVSetYPbPr750p)) return; + + if(!(SiS_Pr->SiS_TVMode & TVSetPAL)) { + if(SiS_Pr->SiS_TVMode & TVSetNTSC1024) { + const unsigned char specialtv[] = { + 0xa7,0x07,0xf2,0x6e,0x17,0x8b,0x73,0x53, + 0x13,0x40,0x34,0xf4,0x63,0xbb,0xcc,0x7a, + 0x58,0xe4,0x73,0xda,0x13 + }; + int i, j; + for(i = 0x1c, j = 0; i <= 0x30; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,specialtv[j]); + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x43,0x72); + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750)) { + if(SiS_Pr->SiS_TVMode & TVSetPALM) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x14); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x1b); + } else { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x14); /* 15 */ + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x1a); /* 1b */ + } + } + } + } else { + if((ModeNo == 0x38) || (ModeNo == 0x4a) || (ModeNo == 0x64) || + (ModeNo == 0x52) || (ModeNo == 0x58) || (ModeNo == 0x5c)) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x1b); /* 21 */ + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x54); /* 5a */ + } else { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x1a); /* 21 */ + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x53); /* 5a */ + } + } +} + +static void +SiS_SetGroup2_Tail(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned short temp; + + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) { + if(SiS_Pr->SiS_VGAVDE == 525) { + temp = 0xc3; + if(SiS_Pr->SiS_ModeType <= ModeVGA) { + temp++; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) temp += 2; + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x2f,temp); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x30,0xb3); + } else if(SiS_Pr->SiS_VGAVDE == 420) { + temp = 0x4d; + if(SiS_Pr->SiS_ModeType <= ModeVGA) { + temp++; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) temp++; + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x2f,temp); + } + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) { + if(SiS_Pr->SiS_VBType & VB_SIS30xB) { + SiS_SetRegOR(SiS_Pr->SiS_Part2Port,0x1a,0x03); + /* Not always for LV, see SetGrp2 */ + } + temp = 1; + if(ModeNo <= 0x13) temp = 3; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x0b,temp); + } +#if 0 + /* 651+301C, for 1280x768 - do I really need that? */ + if((SiS_Pr->SiS_PanelXRes == 1280) && (SiS_Pr->SiS_PanelYRes == 768)) { + if(SiS_Pr->SiS_VBInfo & SetSimuScanMode) { + if(((SiS_Pr->SiS_HDE == 640) && (SiS_Pr->SiS_VDE == 480)) || + ((SiS_Pr->SiS_HDE == 320) && (SiS_Pr->SiS_VDE == 240))) { + SiS_SetReg(SiS_Part2Port,0x01,0x2b); + SiS_SetReg(SiS_Part2Port,0x02,0x13); + SiS_SetReg(SiS_Part2Port,0x04,0xe5); + SiS_SetReg(SiS_Part2Port,0x05,0x08); + SiS_SetReg(SiS_Part2Port,0x06,0xe2); + SiS_SetReg(SiS_Part2Port,0x1c,0x21); + SiS_SetReg(SiS_Part2Port,0x1d,0x45); + SiS_SetReg(SiS_Part2Port,0x1f,0x0b); + SiS_SetReg(SiS_Part2Port,0x20,0x00); + SiS_SetReg(SiS_Part2Port,0x21,0xa9); + SiS_SetReg(SiS_Part2Port,0x23,0x0b); + SiS_SetReg(SiS_Part2Port,0x25,0x04); + } + } + } +#endif + } +} + +static void +SiS_SetGroup2(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short i, j, tempax, tempbx, tempcx, tempch, tempcl, temp; + unsigned short push2, modeflag, crt2crtc, bridgeoffset; + unsigned int longtemp, PhaseIndex; + bool newtvphase; + const unsigned char *TimingPoint; +#ifdef CONFIG_FB_SIS_315 + unsigned short resindex, CRT2Index; + const struct SiS_Part2PortTbl *CRT2Part2Ptr = NULL; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) return; +#endif + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + crt2crtc = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + } else if(SiS_Pr->UseCustomMode) { + modeflag = SiS_Pr->CModeFlag; + crt2crtc = 0; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + crt2crtc = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + } + + temp = 0; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToAVIDEO)) temp |= 0x08; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToSVIDEO)) temp |= 0x04; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToSCART) temp |= 0x02; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) temp |= 0x01; + + if(!(SiS_Pr->SiS_TVMode & TVSetPAL)) temp |= 0x10; + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x00,temp); + + PhaseIndex = 0x01; /* SiS_PALPhase */ + TimingPoint = SiS_Pr->SiS_PALTiming; + + newtvphase = false; + if( (SiS_Pr->SiS_VBType & VB_SIS30xBLV) && + ( (!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) || + (SiS_Pr->SiS_TVMode & TVSetTVSimuMode) ) ) { + newtvphase = true; + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + + TimingPoint = SiS_Pr->SiS_HiTVExtTiming; + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + TimingPoint = SiS_Pr->SiS_HiTVSt2Timing; + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) { + TimingPoint = SiS_Pr->SiS_HiTVSt1Timing; + } + } + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + + i = 0; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) i = 2; + else if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) i = 1; + + TimingPoint = &SiS_YPbPrTable[i][0]; + + PhaseIndex = 0x00; /* SiS_NTSCPhase */ + + } else if(SiS_Pr->SiS_TVMode & TVSetPAL) { + + if(newtvphase) PhaseIndex = 0x09; /* SiS_PALPhase2 */ + + } else { + + TimingPoint = SiS_Pr->SiS_NTSCTiming; + PhaseIndex = (SiS_Pr->SiS_TVMode & TVSetNTSCJ) ? 0x01 : 0x00; /* SiS_PALPhase : SiS_NTSCPhase */ + if(newtvphase) PhaseIndex += 8; /* SiS_PALPhase2 : SiS_NTSCPhase2 */ + + } + + if(SiS_Pr->SiS_TVMode & (TVSetPALM | TVSetPALN)) { + PhaseIndex = (SiS_Pr->SiS_TVMode & TVSetPALM) ? 0x02 : 0x03; /* SiS_PALMPhase : SiS_PALNPhase */ + if(newtvphase) PhaseIndex += 8; /* SiS_PALMPhase2 : SiS_PALNPhase2 */ + } + + if(SiS_Pr->SiS_TVMode & TVSetNTSC1024) { + if(SiS_Pr->SiS_TVMode & TVSetPALM) { + PhaseIndex = 0x05; /* SiS_SpecialPhaseM */ + } else if(SiS_Pr->SiS_TVMode & TVSetNTSCJ) { + PhaseIndex = 0x11; /* SiS_SpecialPhaseJ */ + } else { + PhaseIndex = 0x10; /* SiS_SpecialPhase */ + } + } + + for(i = 0x31, j = 0; i <= 0x34; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS_TVPhase[(PhaseIndex * 4) + j]); + } + + for(i = 0x01, j = 0; i <= 0x2D; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,TimingPoint[j]); + } + for(i = 0x39; i <= 0x45; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,TimingPoint[j]); + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(SiS_Pr->SiS_ModeType != ModeText) { + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x3A,0x1F); + } + } + + SiS_SetRegOR(SiS_Pr->SiS_Part2Port,0x0A,SiS_Pr->SiS_NewFlickerMode); + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x35,SiS_Pr->SiS_RY1COE); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x36,SiS_Pr->SiS_RY2COE); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x37,SiS_Pr->SiS_RY3COE); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x38,SiS_Pr->SiS_RY4COE); + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) tempax = 950; + else if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) tempax = 680; + else if(SiS_Pr->SiS_TVMode & TVSetPAL) tempax = 520; + else tempax = 440; /* NTSC, YPbPr 525 */ + + if( ((SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) && (SiS_Pr->SiS_VDE <= tempax)) || + ( (SiS_Pr->SiS_VBInfo & SetCRT2ToTVNoHiVision) && + ((SiS_Pr->SiS_VGAHDE == 1024) || (SiS_Pr->SiS_VDE <= tempax)) ) ) { + + tempax -= SiS_Pr->SiS_VDE; + tempax >>= 1; + if(!(SiS_Pr->SiS_TVMode & (TVSetYPbPr525p | TVSetYPbPr750p))) { + tempax >>= 1; + } + tempax &= 0x00ff; + + temp = tempax + (unsigned short)TimingPoint[0]; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,temp); + + temp = tempax + (unsigned short)TimingPoint[1]; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,temp); + + if((SiS_Pr->SiS_VBInfo & SetCRT2ToTVNoYPbPrHiVision) && (SiS_Pr->SiS_VGAHDE >= 1024)) { + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x1b); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x54); + } else { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,0x17); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,0x1d); + } + } + + } + + tempcx = SiS_Pr->SiS_HT; + if(SiS_IsDualLink(SiS_Pr)) tempcx >>= 1; + tempcx--; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) tempcx--; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x1B,tempcx); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x1D,0xF0,((tempcx >> 8) & 0x0f)); + + tempcx = SiS_Pr->SiS_HT >> 1; + if(SiS_IsDualLink(SiS_Pr)) tempcx >>= 1; + tempcx += 7; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) tempcx -= 4; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x22,0x0F,((tempcx << 4) & 0xf0)); + + tempbx = TimingPoint[j] | (TimingPoint[j+1] << 8); + tempbx += tempcx; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x24,tempbx); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x25,0x0F,((tempbx >> 4) & 0xf0)); + + tempbx += 8; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + tempbx -= 4; + tempcx = tempbx; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x29,0x0F,((tempbx << 4) & 0xf0)); + + j += 2; + tempcx += (TimingPoint[j] | (TimingPoint[j+1] << 8)); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x27,tempcx); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x28,0x0F,((tempcx >> 4) & 0xf0)); + + tempcx += 8; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) tempcx -= 4; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x2A,0x0F,((tempcx << 4) & 0xf0)); + + tempcx = SiS_Pr->SiS_HT >> 1; + if(SiS_IsDualLink(SiS_Pr)) tempcx >>= 1; + j += 2; + tempcx -= (TimingPoint[j] | ((TimingPoint[j+1]) << 8)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x2D,0x0F,((tempcx << 4) & 0xf0)); + + tempcx -= 11; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) { + tempcx = SiS_GetVGAHT2(SiS_Pr) - 1; + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x2E,tempcx); + + tempbx = SiS_Pr->SiS_VDE; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->SiS_VGAVDE == 360) tempbx = 746; + if(SiS_Pr->SiS_VGAVDE == 375) tempbx = 746; + if(SiS_Pr->SiS_VGAVDE == 405) tempbx = 853; + } else if( (SiS_Pr->SiS_VBInfo & SetCRT2ToTV) && + (!(SiS_Pr->SiS_TVMode & (TVSetYPbPr525p|TVSetYPbPr750p))) ) { + tempbx >>= 1; + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) { + if((ModeNo <= 0x13) && (crt2crtc == 1)) tempbx++; + } else if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(SiS_Pr->SiS_ModeType <= ModeVGA) { + if(crt2crtc == 4) tempbx++; + } + } + } + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + if((ModeNo == 0x2f) || (ModeNo == 0x5d) || (ModeNo == 0x5e)) tempbx++; + } + if(!(SiS_Pr->SiS_TVMode & TVSetPAL)) { + if(ModeNo == 0x03) tempbx++; /* From 1.10.7w - doesn't make sense */ + } + } + } + tempbx -= 2; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x2F,tempbx); + + temp = (tempcx >> 8) & 0x0F; + temp |= ((tempbx >> 2) & 0xC0); + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToSVIDEO | SetCRT2ToAVIDEO)) { + temp |= 0x10; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToAVIDEO) temp |= 0x20; + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x30,temp); + + if(SiS_Pr->SiS_VBType & VB_SISPART4OVERFLOW) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x10,0xdf,((tempbx & 0x0400) >> 5)); + } + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + tempbx = SiS_Pr->SiS_VDE; + if( (SiS_Pr->SiS_VBInfo & SetCRT2ToTV) && + (!(SiS_Pr->SiS_TVMode & (TVSetYPbPr525p | TVSetYPbPr750p))) ) { + tempbx >>= 1; + } + tempbx -= 3; + temp = ((tempbx >> 3) & 0x60) | 0x18; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x46,temp); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x47,tempbx); + + if(SiS_Pr->SiS_VBType & VB_SISPART4OVERFLOW) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x10,0xbf,((tempbx & 0x0400) >> 4)); + } + } + + tempbx = 0; + if(!(modeflag & HalfDCLK)) { + if(SiS_Pr->SiS_VGAHDE >= SiS_Pr->SiS_HDE) { + tempax = 0; + tempbx |= 0x20; + } + } + + tempch = tempcl = 0x01; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(SiS_Pr->SiS_VGAHDE >= 960) { + if((!(modeflag & HalfDCLK)) || (SiS_Pr->ChipType < SIS_315H)) { + tempcl = 0x20; + if(SiS_Pr->SiS_VGAHDE >= 1280) { + tempch = 20; + tempbx &= ~0x20; + } else if(SiS_Pr->SiS_VGAHDE >= 1024) { + tempch = 25; + } else { + tempch = 25; /* OK */ + } + } + } + } + + if(!(tempbx & 0x20)) { + if(modeflag & HalfDCLK) tempcl <<= 1; + longtemp = ((SiS_Pr->SiS_VGAHDE * tempch) / tempcl) << 13; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) longtemp <<= 3; + tempax = longtemp / SiS_Pr->SiS_HDE; + if(longtemp % SiS_Pr->SiS_HDE) tempax++; + tempbx |= ((tempax >> 8) & 0x1F); + tempcx = tempax >> 13; + } + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x44,tempax); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x45,0xC0,tempbx); + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + + tempcx &= 0x07; + if(tempbx & 0x20) tempcx = 0; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x46,0xF8,tempcx); + + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + tempbx = 0x0382; + tempcx = 0x007e; + } else { + tempbx = 0x0369; + tempcx = 0x0061; + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x4B,tempbx); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x4C,tempcx); + temp = (tempcx & 0x0300) >> 6; + temp |= ((tempbx >> 8) & 0x03); + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + temp |= 0x10; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) temp |= 0x20; + else if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) temp |= 0x40; + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x4D,temp); + + temp = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x43); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x43,(temp - 3)); + + SiS_SetTVSpecial(SiS_Pr, ModeNo); + + if(SiS_Pr->SiS_VBType & VB_SIS30xCLV) { + temp = 0; + if(SiS_Pr->SiS_TVMode & TVSetPALM) temp = 8; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x4e,0xf7,temp); + } + + } + + if(SiS_Pr->SiS_TVMode & TVSetPALM) { + if(!(SiS_Pr->SiS_TVMode & TVSetNTSC1024)) { + temp = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x01); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,(temp - 1)); + } + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x00,0xEF); + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x0B,0x00); + } + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) return; + + /* From here: Part2 LCD setup */ + + tempbx = SiS_Pr->SiS_HDE; + if(SiS_IsDualLink(SiS_Pr)) tempbx >>= 1; + tempbx--; /* RHACTE = HDE - 1 */ + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x2C,tempbx); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x2B,0x0F,((tempbx >> 4) & 0xf0)); + + temp = 0x01; + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) { + if(SiS_Pr->SiS_ModeType == ModeEGA) { + if(SiS_Pr->SiS_VGAHDE >= 1024) { + temp = 0x02; + if(SiS_Pr->SiS_SetFlag & LCDVESATiming) { + temp = 0x01; + } + } + } + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x0B,temp); + + tempbx = SiS_Pr->SiS_VDE - 1; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x03,tempbx); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x0C,0xF8,((tempbx >> 8) & 0x07)); + + tempcx = SiS_Pr->SiS_VT - 1; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x19,tempcx); + temp = (tempcx >> 3) & 0xE0; + if(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit) { + /* Enable dithering; only do this for 32bpp mode */ + if(SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00) & 0x01) { + temp |= 0x10; + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x1A,0x0f,temp); + + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x09,0xF0); + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x0A,0xF0); + + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x17,0xFB); + SiS_SetRegAND(SiS_Pr->SiS_Part2Port,0x18,0xDF); + +#ifdef CONFIG_FB_SIS_315 + if(SiS_GetCRT2Part2Ptr(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex, + &CRT2Index, &resindex)) { + switch(CRT2Index) { + case 206: CRT2Part2Ptr = SiS310_CRT2Part2_Asus1024x768_3; break; + default: + case 200: CRT2Part2Ptr = SiS_Pr->SiS_CRT2Part2_1024x768_1; break; + } + + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x01,0x80,(CRT2Part2Ptr+resindex)->CR[0]); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x02,0x80,(CRT2Part2Ptr+resindex)->CR[1]); + for(i = 2, j = 0x04; j <= 0x06; i++, j++ ) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,j,(CRT2Part2Ptr+resindex)->CR[i]); + } + for(j = 0x1c; j <= 0x1d; i++, j++ ) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,j,(CRT2Part2Ptr+resindex)->CR[i]); + } + for(j = 0x1f; j <= 0x21; i++, j++ ) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,j,(CRT2Part2Ptr+resindex)->CR[i]); + } + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x23,(CRT2Part2Ptr+resindex)->CR[10]); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x25,0x0f,(CRT2Part2Ptr+resindex)->CR[11]); + + SiS_SetGroup2_Tail(SiS_Pr, ModeNo); + + } else { +#endif + + /* Checked for 1024x768, 1280x1024, 1400x1050, 1600x1200 */ + /* Clevo dual-link 1024x768 */ + /* Compaq 1280x1024 has HT 1696 sometimes (calculation OK, if given HT is correct) */ + /* Acer: OK, but uses different setting for VESA timing at 640/800/1024 and 640x400 */ + + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + if((SiS_Pr->SiS_LCDInfo & LCDPass11) || (SiS_Pr->PanelYRes == SiS_Pr->SiS_VDE)) { + tempbx = SiS_Pr->SiS_VDE - 1; + tempcx = SiS_Pr->SiS_VT - 1; + } else { + tempbx = SiS_Pr->SiS_VDE + ((SiS_Pr->PanelYRes - SiS_Pr->SiS_VDE) / 2); + tempcx = SiS_Pr->SiS_VT - ((SiS_Pr->PanelYRes - SiS_Pr->SiS_VDE) / 2); + } + } else { + tempbx = SiS_Pr->PanelYRes; + tempcx = SiS_Pr->SiS_VT; + tempax = 1; + if(SiS_Pr->PanelYRes != SiS_Pr->SiS_VDE) { + tempax = SiS_Pr->PanelYRes; + /* if(SiS_Pr->SiS_VGAVDE == 525) tempax += 0x3c; */ /* 651+301C */ + if(SiS_Pr->PanelYRes < SiS_Pr->SiS_VDE) { + tempax = tempcx = 0; + } else { + tempax -= SiS_Pr->SiS_VDE; + } + tempax >>= 1; + } + tempcx -= tempax; /* lcdvdes */ + tempbx -= tempax; /* lcdvdee */ + } + + /* Non-expanding: lcdvdes = tempcx = VT-1; lcdvdee = tempbx = VDE-1 */ + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x05,tempcx); /* lcdvdes */ + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x06,tempbx); /* lcdvdee */ + + temp = (tempbx >> 5) & 0x38; + temp |= ((tempcx >> 8) & 0x07); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x02,temp); + + tempax = SiS_Pr->SiS_VDE; + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (!(SiS_Pr->SiS_LCDInfo & LCDPass11))) { + tempax = SiS_Pr->PanelYRes; + } + tempcx = (SiS_Pr->SiS_VT - tempax) >> 4; + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (!(SiS_Pr->SiS_LCDInfo & LCDPass11))) { + if(SiS_Pr->PanelYRes != SiS_Pr->SiS_VDE) { + tempcx = (SiS_Pr->SiS_VT - tempax) / 10; + } + } + + tempbx = ((SiS_Pr->SiS_VT + SiS_Pr->SiS_VDE) >> 1) - 1; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + if(SiS_Pr->PanelYRes != SiS_Pr->SiS_VDE) { + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { /* ? */ + tempax = SiS_Pr->SiS_VT - SiS_Pr->PanelYRes; + if(tempax % 4) { tempax >>= 2; tempax++; } + else { tempax >>= 2; } + tempbx -= (tempax - 1); + } else { + tempbx -= 10; + if(tempbx <= SiS_Pr->SiS_VDE) tempbx = SiS_Pr->SiS_VDE + 1; + } + } + } + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + tempbx++; + if((!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) || (crt2crtc == 6)) { + if(SiS_Pr->SiS_SetFlag & LCDVESATiming) { + tempbx = 770; + tempcx = 3; + } + } + } + + /* non-expanding: lcdvrs = ((VT + VDE) / 2) - 10 */ + + if(SiS_Pr->UseCustomMode) { + tempbx = SiS_Pr->CVSyncStart; + } + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x04,tempbx); /* lcdvrs */ + + temp = (tempbx >> 4) & 0xF0; + tempbx += (tempcx + 1); + temp |= (tempbx & 0x0F); + + if(SiS_Pr->UseCustomMode) { + temp &= 0xf0; + temp |= (SiS_Pr->CVSyncEnd & 0x0f); + } + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x01,temp); + +#ifdef CONFIG_FB_SIS_300 + SiS_Group2LCDSpecial(SiS_Pr, ModeNo, crt2crtc); +#endif + + bridgeoffset = 7; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) bridgeoffset += 2; + if(SiS_Pr->SiS_VBType & VB_SIS30xCLV) bridgeoffset += 2; /* OK for Averatec 1280x800 (301C) */ + if(SiS_IsDualLink(SiS_Pr)) bridgeoffset++; + else if(SiS_Pr->SiS_VBType & VB_SIS302LV) bridgeoffset++; /* OK for Asus A4L 1280x800 */ + /* Higher bridgeoffset shifts to the LEFT */ + + temp = 0; + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (!(SiS_Pr->SiS_LCDInfo & LCDPass11))) { + if(SiS_Pr->PanelXRes != SiS_Pr->SiS_HDE) { + temp = SiS_Pr->SiS_HT - ((SiS_Pr->PanelXRes - SiS_Pr->SiS_HDE) / 2); + if(SiS_IsDualLink(SiS_Pr)) temp >>= 1; + } + } + temp += bridgeoffset; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x1F,temp); /* lcdhdes */ + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x20,0x0F,((temp >> 4) & 0xf0)); + + tempcx = SiS_Pr->SiS_HT; + tempax = tempbx = SiS_Pr->SiS_HDE; + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (!(SiS_Pr->SiS_LCDInfo & LCDPass11))) { + if(SiS_Pr->PanelXRes != SiS_Pr->SiS_HDE) { + tempax = SiS_Pr->PanelXRes; + tempbx = SiS_Pr->PanelXRes - ((SiS_Pr->PanelXRes - SiS_Pr->SiS_HDE) / 2); + } + } + if(SiS_IsDualLink(SiS_Pr)) { + tempcx >>= 1; + tempbx >>= 1; + tempax >>= 1; + } + + tempbx += bridgeoffset; + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x23,tempbx); /* lcdhdee */ + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x25,0xF0,((tempbx >> 8) & 0x0f)); + + tempcx = (tempcx - tempax) >> 2; + + tempbx += tempcx; + push2 = tempbx; + + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) { + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) { + if(SiS_Pr->SiS_LCDInfo & LCDPass11) { + if(SiS_Pr->SiS_HDE == 1280) tempbx = (tempbx & 0xff00) | 0x47; + } + } + } + + if(SiS_Pr->UseCustomMode) { + tempbx = SiS_Pr->CHSyncStart; + if(modeflag & HalfDCLK) tempbx <<= 1; + if(SiS_IsDualLink(SiS_Pr)) tempbx >>= 1; + tempbx += bridgeoffset; + } + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x1C,tempbx); /* lcdhrs */ + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x1D,0x0F,((tempbx >> 4) & 0xf0)); + + tempbx = push2; + + tempcx <<= 1; + if((SiS_Pr->SiS_LCDInfo & DontExpandLCD) && (!(SiS_Pr->SiS_LCDInfo & LCDPass11))) { + if(SiS_Pr->PanelXRes != SiS_Pr->SiS_HDE) tempcx >>= 2; + } + tempbx += tempcx; + + if(SiS_Pr->UseCustomMode) { + tempbx = SiS_Pr->CHSyncEnd; + if(modeflag & HalfDCLK) tempbx <<= 1; + if(SiS_IsDualLink(SiS_Pr)) tempbx >>= 1; + tempbx += bridgeoffset; + } + + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x21,tempbx); /* lcdhre */ + + SiS_SetGroup2_Tail(SiS_Pr, ModeNo); + +#ifdef CONFIG_FB_SIS_300 + SiS_Set300Part2Regs(SiS_Pr, ModeIdIndex, RefreshRateTableIndex, ModeNo); +#endif +#ifdef CONFIG_FB_SIS_315 + } /* CRT2-LCD from table */ +#endif +} + +/*********************************************/ +/* SET PART 3 REGISTER GROUP */ +/*********************************************/ + +static void +SiS_SetGroup3(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short i; + const unsigned char *tempdi; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) return; + +#ifndef SIS_CP + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x00,0x00); +#else + SIS_CP_INIT301_CP +#endif + + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x13,0xFA); + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x14,0xC8); + } else { + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x13,0xF5); + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x14,0xB7); + } + + if(SiS_Pr->SiS_TVMode & TVSetPALM) { + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x13,0xFA); + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x14,0xC8); + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x3D,0xA8); + } + + tempdi = NULL; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + tempdi = SiS_Pr->SiS_HiTVGroup3Data; + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) { + tempdi = SiS_Pr->SiS_HiTVGroup3Simu; + } + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) { + if(!(SiS_Pr->SiS_TVMode & TVSetYPbPr525i)) { + tempdi = SiS_HiTVGroup3_1; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) tempdi = SiS_HiTVGroup3_2; + } + } + if(tempdi) { + for(i=0; i<=0x3E; i++) { + SiS_SetReg(SiS_Pr->SiS_Part3Port,i,tempdi[i]); + } + if(SiS_Pr->SiS_VBType & VB_SIS30xCLV) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) { + SiS_SetReg(SiS_Pr->SiS_Part3Port,0x28,0x3f); + } + } + } + +#ifdef SIS_CP + SIS_CP_INIT301_CP2 +#endif +} + +/*********************************************/ +/* SET PART 4 REGISTER GROUP */ +/*********************************************/ + +#ifdef CONFIG_FB_SIS_315 +#if 0 +static void +SiS_ShiftXPos(struct SiS_Private *SiS_Pr, int shift) +{ + unsigned short temp, temp1, temp2; + + temp1 = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x1f); + temp2 = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x20); + temp = (unsigned short)((int)((temp1 | ((temp2 & 0xf0) << 4))) + shift); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x1f,temp); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x20,0x0f,((temp >> 4) & 0xf0)); + temp = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x2b) & 0x0f; + temp = (unsigned short)((int)(temp) + shift); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x2b,0xf0,(temp & 0x0f)); + temp1 = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x43); + temp2 = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x42); + temp = (unsigned short)((int)((temp1 | ((temp2 & 0xf0) << 4))) + shift); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x43,temp); + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x42,0x0f,((temp >> 4) & 0xf0)); +} +#endif + +static void +SiS_SetGroup4_C_ELV(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short temp, temp1, resinfo = 0; + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + + if(!(SiS_Pr->SiS_VBType & VB_SIS30xCLV)) return; + if(!(SiS_Pr->SiS_VBInfo & (SetCRT2ToHiVision | SetCRT2ToYPbPr525750))) return; + + if(SiS_Pr->ChipType >= XGI_20) return; + + if((SiS_Pr->ChipType >= SIS_661) && (SiS_Pr->SiS_ROMNew)) { + if(!(ROMAddr[0x61] & 0x04)) return; + } + + if(ModeNo > 0x13) { + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + } + + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x3a,0x08); + temp = SiS_GetReg(SiS_Pr->SiS_Part4Port,0x3a); + if(!(temp & 0x01)) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x3a,0xdf); + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x25,0xfc); + if((SiS_Pr->ChipType < SIS_661) && (!(SiS_Pr->SiS_ROMNew))) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x25,0xf8); + } + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x0f,0xfb); + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) temp = 0x0000; + else if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) temp = 0x0002; + else if(SiS_Pr->SiS_TVMode & TVSetHiVision) temp = 0x0400; + else temp = 0x0402; + if((SiS_Pr->ChipType >= SIS_661) || (SiS_Pr->SiS_ROMNew)) { + temp1 = 0; + if(SiS_Pr->SiS_TVMode & TVAspect43) temp1 = 4; + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x0f,0xfb,temp1); + if(SiS_Pr->SiS_TVMode & TVAspect43LB) temp |= 0x01; + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x26,0x7c,(temp & 0xff)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x3a,0xfb,(temp >> 8)); + if(ModeNo > 0x13) { + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x39,0xfd); + } + } else { + temp1 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x3b) & 0x03; + if(temp1 == 0x01) temp |= 0x01; + if(temp1 == 0x03) temp |= 0x04; /* ? why not 0x10? */ + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x26,0xf8,(temp & 0xff)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x3a,0xfb,(temp >> 8)); + if(ModeNo > 0x13) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x3b,0xfd); + } + } + +#if 0 + if(SiS_Pr->ChipType >= SIS_661) { /* ? */ + if(SiS_Pr->SiS_TVMode & TVAspect43) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) { + if(resinfo == SIS_RI_1024x768) { + SiS_ShiftXPos(SiS_Pr, 97); + } else { + SiS_ShiftXPos(SiS_Pr, 111); + } + } else if(SiS_Pr->SiS_TVMode & TVSetHiVision) { + SiS_ShiftXPos(SiS_Pr, 136); + } + } + } +#endif + + } + +} +#endif + +static void +SiS_SetCRT2VCLK(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short vclkindex, temp, reg1, reg2; + + if(SiS_Pr->UseCustomMode) { + reg1 = SiS_Pr->CSR2B; + reg2 = SiS_Pr->CSR2C; + } else { + vclkindex = SiS_GetVCLK2Ptr(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + reg1 = SiS_Pr->SiS_VBVCLKData[vclkindex].Part4_A; + reg2 = SiS_Pr->SiS_VBVCLKData[vclkindex].Part4_B; + } + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(SiS_Pr->SiS_TVMode & (TVSetNTSC1024 | TVSet525p1024)) { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0a,0x57); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0b,0x46); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x1f,0xf6); + } else { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0a,reg1); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0b,reg2); + } + } else { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0a,0x01); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0b,reg2); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x0a,reg1); + } + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x12,0x00); + temp = 0x08; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC) temp |= 0x20; + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x12,temp); +} + +static void +SiS_SetDualLinkEtc(struct SiS_Private *SiS_Pr) +{ + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_VBType & VB_SISDUALLINK) { + if((SiS_CRT2IsLCD(SiS_Pr)) || + (SiS_IsVAMode(SiS_Pr))) { + if(SiS_Pr->SiS_LCDInfo & LCDDualLink) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x27,0x2c); + } else { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x27,~0x20); + } + } + } + } + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x2a,0x00); +#ifdef SET_EMI + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x30,0x0c); +#endif + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x34,0x10); + } +} + +static void +SiS_SetGroup4(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short tempax, tempcx, tempbx, modeflag, temp, resinfo; + unsigned int tempebx, tempeax, templong; + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + resinfo = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ResInfo; + } else if(SiS_Pr->UseCustomMode) { + modeflag = SiS_Pr->CModeFlag; + resinfo = 0; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + } + + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x24,0x0e); + } + } + } + + if(SiS_Pr->SiS_VBType & (VB_SIS30xCLV | VB_SIS302LV)) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x10,0x9f); + } + } + + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_SetDualLinkEtc(SiS_Pr); + return; + } + } + + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x13,SiS_Pr->SiS_RVBHCFACT); + + tempbx = SiS_Pr->SiS_RVBHCMAX; + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x14,tempbx); + + temp = (tempbx >> 1) & 0x80; + + tempcx = SiS_Pr->SiS_VGAHT - 1; + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x16,tempcx); + + temp |= ((tempcx >> 5) & 0x78); + + tempcx = SiS_Pr->SiS_VGAVT - 1; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) tempcx -= 5; + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x17,tempcx); + + temp |= ((tempcx >> 8) & 0x07); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x15,temp); + + tempbx = SiS_Pr->SiS_VGAHDE; + if(modeflag & HalfDCLK) tempbx >>= 1; + if(SiS_IsDualLink(SiS_Pr)) tempbx >>= 1; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + temp = 0; + if(tempbx > 800) temp = 0x60; + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + temp = 0; + if(tempbx > 1024) temp = 0xC0; + else if(tempbx >= 960) temp = 0xA0; + } else if(SiS_Pr->SiS_TVMode & (TVSetYPbPr525p | TVSetYPbPr750p)) { + temp = 0; + if(tempbx >= 1280) temp = 0x40; + else if(tempbx >= 1024) temp = 0x20; + } else { + temp = 0x80; + if(tempbx >= 1024) temp = 0xA0; + } + + temp |= SiS_Pr->Init_P4_0E; + + if(SiS_Pr->SiS_VBType & VB_SIS301) { + if(SiS_Pr->SiS_LCDResInfo != Panel_1280x1024) { + temp &= 0xf0; + temp |= 0x0A; + } + } + + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x0E,0x10,temp); + + tempeax = SiS_Pr->SiS_VGAVDE; + tempebx = SiS_Pr->SiS_VDE; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + if(!(temp & 0xE0)) tempebx >>=1; + } + + tempcx = SiS_Pr->SiS_RVBHRS; + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x18,tempcx); + tempcx >>= 8; + tempcx |= 0x40; + + if(tempeax <= tempebx) { + tempcx ^= 0x40; + } else { + tempeax -= tempebx; + } + + tempeax *= (256 * 1024); + templong = tempeax % tempebx; + tempeax /= tempebx; + if(templong) tempeax++; + + temp = (unsigned short)(tempeax & 0x000000FF); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x1B,temp); + temp = (unsigned short)((tempeax & 0x0000FF00) >> 8); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x1A,temp); + temp = (unsigned short)((tempeax >> 12) & 0x70); /* sic! */ + temp |= (tempcx & 0x4F); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x19,temp); + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x1C,0x28); + + /* Calc Linebuffer max address and set/clear decimode */ + tempbx = 0; + if(SiS_Pr->SiS_TVMode & (TVSetHiVision | TVSetYPbPr750p)) tempbx = 0x08; + tempax = SiS_Pr->SiS_VGAHDE; + if(modeflag & HalfDCLK) tempax >>= 1; + if(SiS_IsDualLink(SiS_Pr)) tempax >>= 1; + if(tempax > 800) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + tempax -= 800; + } else { + tempbx = 0x08; + if(tempax == 960) tempax *= 25; /* Correct */ + else if(tempax == 1024) tempax *= 25; + else tempax *= 20; + temp = tempax % 32; + tempax /= 32; + if(temp) tempax++; + tempax++; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(resinfo == SIS_RI_1024x768 || + resinfo == SIS_RI_1024x576 || + resinfo == SIS_RI_1280x1024 || + resinfo == SIS_RI_1280x720) { + /* Otherwise white line or garbage at right edge */ + tempax = (tempax & 0xff00) | 0x20; + } + } + } + } + tempax--; + temp = ((tempax >> 4) & 0x30) | tempbx; + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x1D,tempax); + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x1E,temp); + + temp = 0x0036; tempbx = 0xD0; + if((SiS_Pr->ChipType >= SIS_315H) && (SiS_Pr->SiS_VBType & VB_SISLVDS)) { + temp = 0x0026; tempbx = 0xC0; /* See En/DisableBridge() */ + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + if(!(SiS_Pr->SiS_TVMode & (TVSetNTSC1024 | TVSetHiVision | TVSetYPbPr750p | TVSetYPbPr525p))) { + temp |= 0x01; + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + if(!(SiS_Pr->SiS_TVMode & TVSetTVSimuMode)) { + temp &= ~0x01; + } + } + } + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x1F,tempbx,temp); + + tempbx = SiS_Pr->SiS_HT >> 1; + if(SiS_IsDualLink(SiS_Pr)) tempbx >>= 1; + tempbx -= 2; + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x22,tempbx); + temp = (tempbx >> 5) & 0x38; + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x21,0xC0,temp); + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x24,0x0e); + /* LCD-too-dark-error-source, see FinalizeLCD() */ + } + } + + SiS_SetDualLinkEtc(SiS_Pr); + + } /* 301B */ + + SiS_SetCRT2VCLK(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); +} + +/*********************************************/ +/* SET PART 5 REGISTER GROUP */ +/*********************************************/ + +static void +SiS_SetGroup5(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) return; + + if(SiS_Pr->SiS_ModeType == ModeVGA) { + if(!(SiS_Pr->SiS_VBInfo & (SetInSlaveMode | LoadDACFlag))) { + SiS_SetRegOR(SiS_Pr->SiS_P3c4,0x1E,0x20); + SiS_LoadDAC(SiS_Pr, ModeNo, ModeIdIndex); + } + } +} + +/*********************************************/ +/* MODIFY CRT1 GROUP FOR SLAVE MODE */ +/*********************************************/ + +static bool +SiS_GetLVDSCRT1Ptr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex, unsigned short *ResIndex, + unsigned short *DisplayType) + { + unsigned short modeflag = 0; + bool checkhd = true; + + /* Pass 1:1 not supported here */ + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + (*ResIndex) = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + (*ResIndex) = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + } + + (*ResIndex) &= 0x3F; + + if((SiS_Pr->SiS_IF_DEF_CH70xx) && (SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) { + + (*DisplayType) = 80; + if((SiS_Pr->SiS_TVMode & TVSetPAL) && (!(SiS_Pr->SiS_TVMode & TVSetPALM))) { + (*DisplayType) = 82; + if(SiS_Pr->SiS_ModeType > ModeVGA) { + if(SiS_Pr->SiS_CHSOverScan) (*DisplayType) = 84; + } + } + if((*DisplayType) != 84) { + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) (*DisplayType)++; + } + + } else { + + (*DisplayType = 0); + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_320x240_1: (*DisplayType) = 50; + checkhd = false; + break; + case Panel_320x240_2: (*DisplayType) = 14; + break; + case Panel_320x240_3: (*DisplayType) = 18; + break; + case Panel_640x480: (*DisplayType) = 10; + break; + case Panel_1024x600: (*DisplayType) = 26; + break; + default: return true; + } + + if(checkhd) { + if(modeflag & HalfDCLK) (*DisplayType)++; + } + + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x600) { + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) (*DisplayType) += 2; + } + + } + + return true; +} + +static void +SiS_ModCRT1CRTC(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short tempah, i, modeflag, j, ResIndex, DisplayType; + const struct SiS_LVDSCRT1Data *LVDSCRT1Ptr=NULL; + static const unsigned short CRIdx[] = { + 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x10, 0x11, 0x15, 0x16 + }; + + if((SiS_Pr->SiS_CustomT == CUT_BARCO1366) || + (SiS_Pr->SiS_CustomT == CUT_BARCO1024) || + (SiS_Pr->SiS_CustomT == CUT_PANEL848) || + (SiS_Pr->SiS_CustomT == CUT_PANEL856) ) + return; + + if(SiS_Pr->SiS_IF_DEF_LVDS) { + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) return; + } + } else if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) return; + } else return; + + if(SiS_Pr->SiS_LCDInfo & LCDPass11) return; + + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_SetFlag & SetDOSMode) return; + } + + if(!(SiS_GetLVDSCRT1Ptr(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex, + &ResIndex, &DisplayType))) { + return; + } + + switch(DisplayType) { + case 50: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT1320x240_1; break; /* xSTN */ + case 14: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT1320x240_2; break; /* xSTN */ + case 15: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT1320x240_2_H; break; /* xSTN */ + case 18: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT1320x240_3; break; /* xSTN */ + case 19: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT1320x240_3_H; break; /* xSTN */ + case 10: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT1640x480_1; break; + case 11: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT1640x480_1_H; break; +#if 0 /* Works better with calculated numbers */ + case 26: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT11024x600_1; break; + case 27: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT11024x600_1_H; break; + case 28: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT11024x600_2; break; + case 29: LVDSCRT1Ptr = SiS_Pr->SiS_LVDSCRT11024x600_2_H; break; +#endif + case 80: LVDSCRT1Ptr = SiS_Pr->SiS_CHTVCRT1UNTSC; break; + case 81: LVDSCRT1Ptr = SiS_Pr->SiS_CHTVCRT1ONTSC; break; + case 82: LVDSCRT1Ptr = SiS_Pr->SiS_CHTVCRT1UPAL; break; + case 83: LVDSCRT1Ptr = SiS_Pr->SiS_CHTVCRT1OPAL; break; + case 84: LVDSCRT1Ptr = SiS_Pr->SiS_CHTVCRT1SOPAL; break; + } + + if(LVDSCRT1Ptr) { + + SiS_SetRegAND(SiS_Pr->SiS_P3d4,0x11,0x7f); + + for(i = 0; i <= 10; i++) { + tempah = (LVDSCRT1Ptr + ResIndex)->CR[i]; + SiS_SetReg(SiS_Pr->SiS_P3d4,CRIdx[i],tempah); + } + + for(i = 0x0A, j = 11; i <= 0x0C; i++, j++) { + tempah = (LVDSCRT1Ptr + ResIndex)->CR[j]; + SiS_SetReg(SiS_Pr->SiS_P3c4,i,tempah); + } + + tempah = (LVDSCRT1Ptr + ResIndex)->CR[14] & 0xE0; + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x0E,0x1f,tempah); + + if(ModeNo <= 0x13) modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + else modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + + tempah = ((LVDSCRT1Ptr + ResIndex)->CR[14] & 0x01) << 5; + if(modeflag & DoubleScanMode) tempah |= 0x80; + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x09,~0x020,tempah); + + } else { + + SiS_CalcLCDACRT1Timing(SiS_Pr, ModeNo, ModeIdIndex); + + } +} + +/*********************************************/ +/* SET CRT2 ECLK */ +/*********************************************/ + +static void +SiS_SetCRT2ECLK(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short clkbase, vclkindex = 0; + unsigned char sr2b, sr2c; + + if(SiS_Pr->SiS_LCDInfo & LCDPass11) { + SiS_Pr->SiS_SetFlag &= (~ProgrammingCRT2); + if(SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRTVCLK == 2) { + RefreshRateTableIndex--; + } + vclkindex = SiS_GetVCLK2Ptr(SiS_Pr, ModeNo, ModeIdIndex, + RefreshRateTableIndex); + SiS_Pr->SiS_SetFlag |= ProgrammingCRT2; + } else { + vclkindex = SiS_GetVCLK2Ptr(SiS_Pr, ModeNo, ModeIdIndex, + RefreshRateTableIndex); + } + + sr2b = SiS_Pr->SiS_VCLKData[vclkindex].SR2B; + sr2c = SiS_Pr->SiS_VCLKData[vclkindex].SR2C; + + if((SiS_Pr->SiS_CustomT == CUT_BARCO1366) || (SiS_Pr->SiS_CustomT == CUT_BARCO1024)) { + if(SiS_Pr->SiS_UseROM) { + if(ROMAddr[0x220] & 0x01) { + sr2b = ROMAddr[0x227]; + sr2c = ROMAddr[0x228]; + } + } + } + + clkbase = 0x02B; + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA)) { + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) { + clkbase += 3; + } + } + + SiS_SetReg(SiS_Pr->SiS_P3c4,0x31,0x20); + SiS_SetReg(SiS_Pr->SiS_P3c4,clkbase,sr2b); + SiS_SetReg(SiS_Pr->SiS_P3c4,clkbase+1,sr2c); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x31,0x10); + SiS_SetReg(SiS_Pr->SiS_P3c4,clkbase,sr2b); + SiS_SetReg(SiS_Pr->SiS_P3c4,clkbase+1,sr2c); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x31,0x00); + SiS_SetReg(SiS_Pr->SiS_P3c4,clkbase,sr2b); + SiS_SetReg(SiS_Pr->SiS_P3c4,clkbase+1,sr2c); +} + +/*********************************************/ +/* SET UP CHRONTEL CHIPS */ +/*********************************************/ + +static void +SiS_SetCHTVReg(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex) +{ + unsigned short TVType, resindex; + const struct SiS_CHTVRegData *CHTVRegData = NULL; + + if(ModeNo <= 0x13) + resindex = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + else + resindex = SiS_Pr->SiS_RefIndex[RefreshRateTableIndex].Ext_CRT2CRTC; + + resindex &= 0x3F; + + TVType = 0; + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) TVType += 1; + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + TVType += 2; + if(SiS_Pr->SiS_ModeType > ModeVGA) { + if(SiS_Pr->SiS_CHSOverScan) TVType = 8; + } + if(SiS_Pr->SiS_TVMode & TVSetPALM) { + TVType = 4; + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) TVType += 1; + } else if(SiS_Pr->SiS_TVMode & TVSetPALN) { + TVType = 6; + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) TVType += 1; + } + } + + switch(TVType) { + case 0: CHTVRegData = SiS_Pr->SiS_CHTVReg_UNTSC; break; + case 1: CHTVRegData = SiS_Pr->SiS_CHTVReg_ONTSC; break; + case 2: CHTVRegData = SiS_Pr->SiS_CHTVReg_UPAL; break; + case 3: CHTVRegData = SiS_Pr->SiS_CHTVReg_OPAL; break; + case 4: CHTVRegData = SiS_Pr->SiS_CHTVReg_UPALM; break; + case 5: CHTVRegData = SiS_Pr->SiS_CHTVReg_OPALM; break; + case 6: CHTVRegData = SiS_Pr->SiS_CHTVReg_UPALN; break; + case 7: CHTVRegData = SiS_Pr->SiS_CHTVReg_OPALN; break; + case 8: CHTVRegData = SiS_Pr->SiS_CHTVReg_SOPAL; break; + default: CHTVRegData = SiS_Pr->SiS_CHTVReg_OPAL; break; + } + + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 1) { + +#ifdef CONFIG_FB_SIS_300 + + /* Chrontel 7005 - I assume that it does not come with a 315 series chip */ + + /* We don't support modes >800x600 */ + if (resindex > 5) return; + + if(SiS_Pr->SiS_TVMode & TVSetPAL) { + SiS_SetCH700x(SiS_Pr,0x04,0x43); /* 0x40=76uA (PAL); 0x03=15bit non-multi RGB*/ + SiS_SetCH700x(SiS_Pr,0x09,0x69); /* Black level for PAL (105)*/ + } else { + SiS_SetCH700x(SiS_Pr,0x04,0x03); /* upper nibble=71uA (NTSC), 0x03=15bit non-multi RGB*/ + SiS_SetCH700x(SiS_Pr,0x09,0x71); /* Black level for NTSC (113)*/ + } + + SiS_SetCH700x(SiS_Pr,0x00,CHTVRegData[resindex].Reg[0]); /* Mode register */ + SiS_SetCH700x(SiS_Pr,0x07,CHTVRegData[resindex].Reg[1]); /* Start active video register */ + SiS_SetCH700x(SiS_Pr,0x08,CHTVRegData[resindex].Reg[2]); /* Position overflow register */ + SiS_SetCH700x(SiS_Pr,0x0a,CHTVRegData[resindex].Reg[3]); /* Horiz Position register */ + SiS_SetCH700x(SiS_Pr,0x0b,CHTVRegData[resindex].Reg[4]); /* Vertical Position register */ + + /* Set minimum flicker filter for Luma channel (SR1-0=00), + minimum text enhancement (S3-2=10), + maximum flicker filter for Chroma channel (S5-4=10) + =00101000=0x28 (When reading, S1-0->S3-2, and S3-2->S1-0!) + */ + SiS_SetCH700x(SiS_Pr,0x01,0x28); + + /* Set video bandwidth + High bandwidth Luma composite video filter(S0=1) + low bandwidth Luma S-video filter (S2-1=00) + disable peak filter in S-video channel (S3=0) + high bandwidth Chroma Filter (S5-4=11) + =00110001=0x31 + */ + SiS_SetCH700x(SiS_Pr,0x03,0xb1); /* old: 3103 */ + + /* Register 0x3D does not exist in non-macrovision register map + (Maybe this is a macrovision register?) + */ +#ifndef SIS_CP + SiS_SetCH70xx(SiS_Pr,0x3d,0x00); +#endif + + /* Register 0x10 only contains 1 writable bit (S0) for sensing, + all other bits a read-only. Macrovision? + */ + SiS_SetCH70xxANDOR(SiS_Pr,0x10,0x00,0x1F); + + /* Register 0x11 only contains 3 writable bits (S0-S2) for + contrast enhancement (set to 010 -> gain 1 Yout = 17/16*(Yin-30) ) + */ + SiS_SetCH70xxANDOR(SiS_Pr,0x11,0x02,0xF8); + + /* Clear DSEN + */ + SiS_SetCH70xxANDOR(SiS_Pr,0x1c,0x00,0xEF); + + if(!(SiS_Pr->SiS_TVMode & TVSetPAL)) { /* ---- NTSC ---- */ + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) { + if(resindex == 0x04) { /* 640x480 overscan: Mode 16 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x20,0x00,0xEF); /* loop filter off */ + SiS_SetCH70xxANDOR(SiS_Pr,0x21,0x01,0xFE); /* ACIV on, no need to set FSCI */ + } else if(resindex == 0x05) { /* 800x600 overscan: Mode 23 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x18,0x01,0xF0); /* 0x18-0x1f: FSCI 469,762,048 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x19,0x0C,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1a,0x00,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1b,0x00,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1c,0x00,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1d,0x00,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1e,0x00,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1f,0x00,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x20,0x01,0xEF); /* Loop filter on for mode 23 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x21,0x00,0xFE); /* ACIV off, need to set FSCI */ + } + } else { + if(resindex == 0x04) { /* ----- 640x480 underscan; Mode 17 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x20,0x00,0xEF); /* loop filter off */ + SiS_SetCH70xxANDOR(SiS_Pr,0x21,0x01,0xFE); + } else if(resindex == 0x05) { /* ----- 800x600 underscan: Mode 24 */ +#if 0 + SiS_SetCH70xxANDOR(SiS_Pr,0x18,0x01,0xF0); /* (FSCI was 0x1f1c71c7 - this is for mode 22) */ + SiS_SetCH70xxANDOR(SiS_Pr,0x19,0x09,0xF0); /* FSCI for mode 24 is 428,554,851 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x1a,0x08,0xF0); /* 198b3a63 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x1b,0x0b,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1c,0x04,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1d,0x01,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1e,0x06,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x1f,0x05,0xF0); + SiS_SetCH70xxANDOR(SiS_Pr,0x20,0x00,0xEF); /* loop filter off for mode 24 */ + SiS_SetCH70xxANDOR(SiS_Pr,0x21,0x00,0xFE); * ACIV off, need to set FSCI */ +#endif /* All alternatives wrong (datasheet wrong?), don't use FSCI */ + SiS_SetCH70xxANDOR(SiS_Pr,0x20,0x00,0xEF); /* loop filter off */ + SiS_SetCH70xxANDOR(SiS_Pr,0x21,0x01,0xFE); + } + } + } else { /* ---- PAL ---- */ + /* We don't play around with FSCI in PAL mode */ + if(resindex == 0x04) { + SiS_SetCH70xxANDOR(SiS_Pr,0x20,0x00,0xEF); /* loop filter off */ + SiS_SetCH70xxANDOR(SiS_Pr,0x21,0x01,0xFE); /* ACIV on */ + } else { + SiS_SetCH70xxANDOR(SiS_Pr,0x20,0x00,0xEF); /* loop filter off */ + SiS_SetCH70xxANDOR(SiS_Pr,0x21,0x01,0xFE); /* ACIV on */ + } + } + +#endif /* 300 */ + + } else { + + /* Chrontel 7019 - assumed that it does not come with a 300 series chip */ + +#ifdef CONFIG_FB_SIS_315 + + unsigned short temp; + + /* We don't support modes >1024x768 */ + if (resindex > 6) return; + + temp = CHTVRegData[resindex].Reg[0]; + if(SiS_Pr->SiS_TVMode & TVSetNTSCJ) temp |= 0x10; + SiS_SetCH701x(SiS_Pr,0x00,temp); + + SiS_SetCH701x(SiS_Pr,0x01,CHTVRegData[resindex].Reg[1]); + SiS_SetCH701x(SiS_Pr,0x02,CHTVRegData[resindex].Reg[2]); + SiS_SetCH701x(SiS_Pr,0x04,CHTVRegData[resindex].Reg[3]); + SiS_SetCH701x(SiS_Pr,0x03,CHTVRegData[resindex].Reg[4]); + SiS_SetCH701x(SiS_Pr,0x05,CHTVRegData[resindex].Reg[5]); + SiS_SetCH701x(SiS_Pr,0x06,CHTVRegData[resindex].Reg[6]); + + temp = CHTVRegData[resindex].Reg[7]; + if(SiS_Pr->SiS_TVMode & TVSetNTSCJ) temp = 0x66; + SiS_SetCH701x(SiS_Pr,0x07,temp); + + SiS_SetCH701x(SiS_Pr,0x08,CHTVRegData[resindex].Reg[8]); + SiS_SetCH701x(SiS_Pr,0x15,CHTVRegData[resindex].Reg[9]); + SiS_SetCH701x(SiS_Pr,0x1f,CHTVRegData[resindex].Reg[10]); + SiS_SetCH701x(SiS_Pr,0x0c,CHTVRegData[resindex].Reg[11]); + SiS_SetCH701x(SiS_Pr,0x0d,CHTVRegData[resindex].Reg[12]); + SiS_SetCH701x(SiS_Pr,0x0e,CHTVRegData[resindex].Reg[13]); + SiS_SetCH701x(SiS_Pr,0x0f,CHTVRegData[resindex].Reg[14]); + SiS_SetCH701x(SiS_Pr,0x10,CHTVRegData[resindex].Reg[15]); + + temp = SiS_GetCH701x(SiS_Pr,0x21) & ~0x02; + /* D1 should be set for PAL, PAL-N and NTSC-J, + but I won't do that for PAL unless somebody + tells me to do so. Since the BIOS uses + non-default CIV values and blacklevels, + this might be compensated anyway. + */ + if(SiS_Pr->SiS_TVMode & (TVSetPALN | TVSetNTSCJ)) temp |= 0x02; + SiS_SetCH701x(SiS_Pr,0x21,temp); + +#endif /* 315 */ + + } + +#ifdef SIS_CP + SIS_CP_INIT301_CP3 +#endif + +} + +#ifdef CONFIG_FB_SIS_315 /* ----------- 315 series only ---------- */ + +void +SiS_Chrontel701xBLOn(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + /* Enable Chrontel 7019 LCD panel backlight */ + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(SiS_Pr->ChipType == SIS_740) { + SiS_SetCH701x(SiS_Pr,0x66,0x65); + } else { + temp = SiS_GetCH701x(SiS_Pr,0x66); + temp |= 0x20; + SiS_SetCH701x(SiS_Pr,0x66,temp); + } + } +} + +void +SiS_Chrontel701xBLOff(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + /* Disable Chrontel 7019 LCD panel backlight */ + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + temp = SiS_GetCH701x(SiS_Pr,0x66); + temp &= 0xDF; + SiS_SetCH701x(SiS_Pr,0x66,temp); + } +} + +static void +SiS_ChrontelPowerSequencing(struct SiS_Private *SiS_Pr) +{ + static const unsigned char regtable[] = { 0x67, 0x68, 0x69, 0x6a, 0x6b }; + static const unsigned char table1024_740[] = { 0x01, 0x02, 0x01, 0x01, 0x01 }; + static const unsigned char table1400_740[] = { 0x01, 0x6e, 0x01, 0x01, 0x01 }; + static const unsigned char asus1024_740[] = { 0x19, 0x6e, 0x01, 0x19, 0x09 }; + static const unsigned char asus1400_740[] = { 0x19, 0x6e, 0x01, 0x19, 0x09 }; + static const unsigned char table1024_650[] = { 0x01, 0x02, 0x01, 0x01, 0x02 }; + static const unsigned char table1400_650[] = { 0x01, 0x02, 0x01, 0x01, 0x02 }; + const unsigned char *tableptr = NULL; + int i; + + /* Set up Power up/down timing */ + + if(SiS_Pr->ChipType == SIS_740) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(SiS_Pr->SiS_CustomT == CUT_ASUSL3000D) tableptr = asus1024_740; + else tableptr = table1024_740; + } else if((SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) || + (SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) || + (SiS_Pr->SiS_LCDResInfo == Panel_1600x1200)) { + if(SiS_Pr->SiS_CustomT == CUT_ASUSL3000D) tableptr = asus1400_740; + else tableptr = table1400_740; + } else return; + } else { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + tableptr = table1024_650; + } else if((SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) || + (SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) || + (SiS_Pr->SiS_LCDResInfo == Panel_1600x1200)) { + tableptr = table1400_650; + } else return; + } + + for(i=0; i<5; i++) { + SiS_SetCH701x(SiS_Pr, regtable[i], tableptr[i]); + } +} + +static void +SiS_SetCH701xForLCD(struct SiS_Private *SiS_Pr) +{ + const unsigned char *tableptr = NULL; + unsigned short tempbh; + int i; + static const unsigned char regtable[] = { + 0x1c, 0x5f, 0x64, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x76, 0x78, 0x7d, 0x66 + }; + static const unsigned char table1024_740[] = { + 0x60, 0x02, 0x00, 0x07, 0x40, 0xed, + 0xa3, 0xc8, 0xc7, 0xac, 0xe0, 0x02, 0x44 + }; + static const unsigned char table1280_740[] = { + 0x60, 0x03, 0x11, 0x00, 0x40, 0xe3, + 0xad, 0xdb, 0xf6, 0xac, 0xe0, 0x02, 0x44 + }; + static const unsigned char table1400_740[] = { + 0x60, 0x03, 0x11, 0x00, 0x40, 0xe3, + 0xad, 0xdb, 0xf6, 0xac, 0xe0, 0x02, 0x44 + }; + static const unsigned char table1600_740[] = { + 0x60, 0x04, 0x11, 0x00, 0x40, 0xe3, + 0xad, 0xde, 0xf6, 0xac, 0x60, 0x1a, 0x44 + }; + static const unsigned char table1024_650[] = { + 0x60, 0x02, 0x00, 0x07, 0x40, 0xed, + 0xa3, 0xc8, 0xc7, 0xac, 0x60, 0x02 + }; + static const unsigned char table1280_650[] = { + 0x60, 0x03, 0x11, 0x00, 0x40, 0xe3, + 0xad, 0xdb, 0xf6, 0xac, 0xe0, 0x02 + }; + static const unsigned char table1400_650[] = { + 0x60, 0x03, 0x11, 0x00, 0x40, 0xef, + 0xad, 0xdb, 0xf6, 0xac, 0x60, 0x02 + }; + static const unsigned char table1600_650[] = { + 0x60, 0x04, 0x11, 0x00, 0x40, 0xe3, + 0xad, 0xde, 0xf6, 0xac, 0x60, 0x1a + }; + + if(SiS_Pr->ChipType == SIS_740) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) tableptr = table1024_740; + else if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) tableptr = table1280_740; + else if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) tableptr = table1400_740; + else if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) tableptr = table1600_740; + else return; + } else { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) tableptr = table1024_650; + else if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) tableptr = table1280_650; + else if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) tableptr = table1400_650; + else if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) tableptr = table1600_650; + else return; + } + + tempbh = SiS_GetCH701x(SiS_Pr,0x74); + if((tempbh == 0xf6) || (tempbh == 0xc7)) { + tempbh = SiS_GetCH701x(SiS_Pr,0x73); + if(tempbh == 0xc8) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) return; + } else if(tempbh == 0xdb) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) return; + if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) return; + } else if(tempbh == 0xde) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) return; + } + } + + if(SiS_Pr->ChipType == SIS_740) tempbh = 0x0d; + else tempbh = 0x0c; + + for(i = 0; i < tempbh; i++) { + SiS_SetCH701x(SiS_Pr, regtable[i], tableptr[i]); + } + SiS_ChrontelPowerSequencing(SiS_Pr); + tempbh = SiS_GetCH701x(SiS_Pr,0x1e); + tempbh |= 0xc0; + SiS_SetCH701x(SiS_Pr,0x1e,tempbh); + + if(SiS_Pr->ChipType == SIS_740) { + tempbh = SiS_GetCH701x(SiS_Pr,0x1c); + tempbh &= 0xfb; + SiS_SetCH701x(SiS_Pr,0x1c,tempbh); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2d,0x03); + tempbh = SiS_GetCH701x(SiS_Pr,0x64); + tempbh |= 0x40; + SiS_SetCH701x(SiS_Pr,0x64,tempbh); + tempbh = SiS_GetCH701x(SiS_Pr,0x03); + tempbh &= 0x3f; + SiS_SetCH701x(SiS_Pr,0x03,tempbh); + } +} + +static void +SiS_ChrontelResetVSync(struct SiS_Private *SiS_Pr) +{ + unsigned char temp, temp1; + + temp1 = SiS_GetCH701x(SiS_Pr,0x49); + SiS_SetCH701x(SiS_Pr,0x49,0x3e); + temp = SiS_GetCH701x(SiS_Pr,0x47); + temp &= 0x7f; /* Use external VSYNC */ + SiS_SetCH701x(SiS_Pr,0x47,temp); + SiS_LongDelay(SiS_Pr, 3); + temp = SiS_GetCH701x(SiS_Pr,0x47); + temp |= 0x80; /* Use internal VSYNC */ + SiS_SetCH701x(SiS_Pr,0x47,temp); + SiS_SetCH701x(SiS_Pr,0x49,temp1); +} + +static void +SiS_Chrontel701xOn(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(SiS_Pr->ChipType == SIS_740) { + temp = SiS_GetCH701x(SiS_Pr,0x1c); + temp |= 0x04; /* Invert XCLK phase */ + SiS_SetCH701x(SiS_Pr,0x1c,temp); + } + if(SiS_IsYPbPr(SiS_Pr)) { + temp = SiS_GetCH701x(SiS_Pr,0x01); + temp &= 0x3f; + temp |= 0x80; /* Enable YPrPb (HDTV) */ + SiS_SetCH701x(SiS_Pr,0x01,temp); + } + if(SiS_IsChScart(SiS_Pr)) { + temp = SiS_GetCH701x(SiS_Pr,0x01); + temp &= 0x3f; + temp |= 0xc0; /* Enable SCART + CVBS */ + SiS_SetCH701x(SiS_Pr,0x01,temp); + } + if(SiS_Pr->ChipType == SIS_740) { + SiS_ChrontelResetVSync(SiS_Pr); + SiS_SetCH701x(SiS_Pr,0x49,0x20); /* Enable TV path */ + } else { + SiS_SetCH701x(SiS_Pr,0x49,0x20); /* Enable TV path */ + temp = SiS_GetCH701x(SiS_Pr,0x49); + if(SiS_IsYPbPr(SiS_Pr)) { + temp = SiS_GetCH701x(SiS_Pr,0x73); + temp |= 0x60; + SiS_SetCH701x(SiS_Pr,0x73,temp); + } + temp = SiS_GetCH701x(SiS_Pr,0x47); + temp &= 0x7f; + SiS_SetCH701x(SiS_Pr,0x47,temp); + SiS_LongDelay(SiS_Pr, 2); + temp = SiS_GetCH701x(SiS_Pr,0x47); + temp |= 0x80; + SiS_SetCH701x(SiS_Pr,0x47,temp); + } + } +} + +static void +SiS_Chrontel701xOff(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + /* Complete power down of LVDS */ + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { + if(SiS_Pr->ChipType == SIS_740) { + SiS_LongDelay(SiS_Pr, 1); + SiS_GenericDelay(SiS_Pr, 5887); + SiS_SetCH701x(SiS_Pr,0x76,0xac); + SiS_SetCH701x(SiS_Pr,0x66,0x00); + } else { + SiS_LongDelay(SiS_Pr, 2); + temp = SiS_GetCH701x(SiS_Pr,0x76); + temp &= 0xfc; + SiS_SetCH701x(SiS_Pr,0x76,temp); + SiS_SetCH701x(SiS_Pr,0x66,0x00); + } + } +} + +static void +SiS_ChrontelResetDB(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + if(SiS_Pr->ChipType == SIS_740) { + + temp = SiS_GetCH701x(SiS_Pr,0x4a); /* Version ID */ + temp &= 0x01; + if(!temp) { + + if(SiS_WeHaveBacklightCtrl(SiS_Pr)) { + temp = SiS_GetCH701x(SiS_Pr,0x49); + SiS_SetCH701x(SiS_Pr,0x49,0x3e); + } + + /* Reset Chrontel 7019 datapath */ + SiS_SetCH701x(SiS_Pr,0x48,0x10); + SiS_LongDelay(SiS_Pr, 1); + SiS_SetCH701x(SiS_Pr,0x48,0x18); + + if(SiS_WeHaveBacklightCtrl(SiS_Pr)) { + SiS_ChrontelResetVSync(SiS_Pr); + SiS_SetCH701x(SiS_Pr,0x49,temp); + } + + } else { + + /* Clear/set/clear GPIO */ + temp = SiS_GetCH701x(SiS_Pr,0x5c); + temp &= 0xef; + SiS_SetCH701x(SiS_Pr,0x5c,temp); + temp = SiS_GetCH701x(SiS_Pr,0x5c); + temp |= 0x10; + SiS_SetCH701x(SiS_Pr,0x5c,temp); + temp = SiS_GetCH701x(SiS_Pr,0x5c); + temp &= 0xef; + SiS_SetCH701x(SiS_Pr,0x5c,temp); + temp = SiS_GetCH701x(SiS_Pr,0x61); + if(!temp) { + SiS_SetCH701xForLCD(SiS_Pr); + } + } + + } else { /* 650 */ + /* Reset Chrontel 7019 datapath */ + SiS_SetCH701x(SiS_Pr,0x48,0x10); + SiS_LongDelay(SiS_Pr, 1); + SiS_SetCH701x(SiS_Pr,0x48,0x18); + } +} + +static void +SiS_ChrontelInitTVVSync(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + if(SiS_Pr->ChipType == SIS_740) { + + if(SiS_WeHaveBacklightCtrl(SiS_Pr)) { + SiS_ChrontelResetVSync(SiS_Pr); + } + + } else { + + SiS_SetCH701x(SiS_Pr,0x76,0xaf); /* Power up LVDS block */ + temp = SiS_GetCH701x(SiS_Pr,0x49); + temp &= 1; + if(temp != 1) { /* TV block powered? (0 = yes, 1 = no) */ + temp = SiS_GetCH701x(SiS_Pr,0x47); + temp &= 0x70; + SiS_SetCH701x(SiS_Pr,0x47,temp); /* enable VSYNC */ + SiS_LongDelay(SiS_Pr, 3); + temp = SiS_GetCH701x(SiS_Pr,0x47); + temp |= 0x80; + SiS_SetCH701x(SiS_Pr,0x47,temp); /* disable VSYNC */ + } + + } +} + +static void +SiS_ChrontelDoSomething3(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned short temp,temp1; + + if(SiS_Pr->ChipType == SIS_740) { + + temp = SiS_GetCH701x(SiS_Pr,0x61); + if(temp < 1) { + temp++; + SiS_SetCH701x(SiS_Pr,0x61,temp); + } + SiS_SetCH701x(SiS_Pr,0x66,0x45); /* Panel power on */ + SiS_SetCH701x(SiS_Pr,0x76,0xaf); /* All power on */ + SiS_LongDelay(SiS_Pr, 1); + SiS_GenericDelay(SiS_Pr, 5887); + + } else { /* 650 */ + + temp1 = 0; + temp = SiS_GetCH701x(SiS_Pr,0x61); + if(temp < 2) { + temp++; + SiS_SetCH701x(SiS_Pr,0x61,temp); + temp1 = 1; + } + SiS_SetCH701x(SiS_Pr,0x76,0xac); + temp = SiS_GetCH701x(SiS_Pr,0x66); + temp |= 0x5f; + SiS_SetCH701x(SiS_Pr,0x66,temp); + if(ModeNo > 0x13) { + if(SiS_WeHaveBacklightCtrl(SiS_Pr)) { + SiS_GenericDelay(SiS_Pr, 1023); + } else { + SiS_GenericDelay(SiS_Pr, 767); + } + } else { + if(!temp1) + SiS_GenericDelay(SiS_Pr, 767); + } + temp = SiS_GetCH701x(SiS_Pr,0x76); + temp |= 0x03; + SiS_SetCH701x(SiS_Pr,0x76,temp); + temp = SiS_GetCH701x(SiS_Pr,0x66); + temp &= 0x7f; + SiS_SetCH701x(SiS_Pr,0x66,temp); + SiS_LongDelay(SiS_Pr, 1); + + } +} + +static void +SiS_ChrontelDoSomething2(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + SiS_LongDelay(SiS_Pr, 1); + + do { + temp = SiS_GetCH701x(SiS_Pr,0x66); + temp &= 0x04; /* PLL stable? -> bail out */ + if(temp == 0x04) break; + + if(SiS_Pr->ChipType == SIS_740) { + /* Power down LVDS output, PLL normal operation */ + SiS_SetCH701x(SiS_Pr,0x76,0xac); + } + + SiS_SetCH701xForLCD(SiS_Pr); + + temp = SiS_GetCH701x(SiS_Pr,0x76); + temp &= 0xfb; /* Reset PLL */ + SiS_SetCH701x(SiS_Pr,0x76,temp); + SiS_LongDelay(SiS_Pr, 2); + temp = SiS_GetCH701x(SiS_Pr,0x76); + temp |= 0x04; /* PLL normal operation */ + SiS_SetCH701x(SiS_Pr,0x76,temp); + if(SiS_Pr->ChipType == SIS_740) { + SiS_SetCH701x(SiS_Pr,0x78,0xe0); /* PLL loop filter */ + } else { + SiS_SetCH701x(SiS_Pr,0x78,0x60); + } + SiS_LongDelay(SiS_Pr, 2); + } while(0); + + SiS_SetCH701x(SiS_Pr,0x77,0x00); /* MV? */ +} + +static void +SiS_ChrontelDoSomething1(struct SiS_Private *SiS_Pr) +{ + unsigned short temp; + + temp = SiS_GetCH701x(SiS_Pr,0x03); + temp |= 0x80; /* Set datapath 1 to TV */ + temp &= 0xbf; /* Set datapath 2 to LVDS */ + SiS_SetCH701x(SiS_Pr,0x03,temp); + + if(SiS_Pr->ChipType == SIS_740) { + + temp = SiS_GetCH701x(SiS_Pr,0x1c); + temp &= 0xfb; /* Normal XCLK phase */ + SiS_SetCH701x(SiS_Pr,0x1c,temp); + + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2d,0x03); + + temp = SiS_GetCH701x(SiS_Pr,0x64); + temp |= 0x40; /* ? Bit not defined */ + SiS_SetCH701x(SiS_Pr,0x64,temp); + + temp = SiS_GetCH701x(SiS_Pr,0x03); + temp &= 0x3f; /* D1 input to both LVDS and TV */ + SiS_SetCH701x(SiS_Pr,0x03,temp); + + if(SiS_Pr->SiS_CustomT == CUT_ASUSL3000D) { + SiS_SetCH701x(SiS_Pr,0x63,0x40); /* LVDS off */ + SiS_LongDelay(SiS_Pr, 1); + SiS_SetCH701x(SiS_Pr,0x63,0x00); /* LVDS on */ + SiS_ChrontelResetDB(SiS_Pr); + SiS_ChrontelDoSomething2(SiS_Pr); + SiS_ChrontelDoSomething3(SiS_Pr, 0); + } else { + temp = SiS_GetCH701x(SiS_Pr,0x66); + if(temp != 0x45) { + SiS_ChrontelResetDB(SiS_Pr); + SiS_ChrontelDoSomething2(SiS_Pr); + SiS_ChrontelDoSomething3(SiS_Pr, 0); + } + } + + } else { /* 650 */ + + SiS_ChrontelResetDB(SiS_Pr); + SiS_ChrontelDoSomething2(SiS_Pr); + temp = SiS_GetReg(SiS_Pr->SiS_P3d4,0x34); + SiS_ChrontelDoSomething3(SiS_Pr,temp); + SiS_SetCH701x(SiS_Pr,0x76,0xaf); /* All power on, LVDS normal operation */ + + } + +} +#endif /* 315 series */ + +/*********************************************/ +/* MAIN: SET CRT2 REGISTER GROUP */ +/*********************************************/ + +bool +SiS_SetCRT2Group(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ +#ifdef CONFIG_FB_SIS_300 + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; +#endif + unsigned short ModeIdIndex, RefreshRateTableIndex; + + SiS_Pr->SiS_SetFlag |= ProgrammingCRT2; + + if(!SiS_Pr->UseCustomMode) { + SiS_SearchModeID(SiS_Pr, &ModeNo, &ModeIdIndex); + } else { + ModeIdIndex = 0; + } + + /* Used for shifting CR33 */ + SiS_Pr->SiS_SelectCRT2Rate = 4; + + SiS_UnLockCRT2(SiS_Pr); + + RefreshRateTableIndex = SiS_GetRatePtr(SiS_Pr, ModeNo, ModeIdIndex); + + SiS_SaveCRT2Info(SiS_Pr,ModeNo); + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + SiS_DisableBridge(SiS_Pr); + if((SiS_Pr->SiS_IF_DEF_LVDS == 1) && (SiS_Pr->ChipType == SIS_730)) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x00,0x80); + } + SiS_SetCRT2ModeRegs(SiS_Pr, ModeNo, ModeIdIndex); + } + + if(SiS_Pr->SiS_VBInfo & DisableCRT2Display) { + SiS_LockCRT2(SiS_Pr); + SiS_DisplayOn(SiS_Pr); + return true; + } + + SiS_GetCRT2Data(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + + /* Set up Panel Link for LVDS and LCDA */ + SiS_Pr->SiS_LCDHDES = SiS_Pr->SiS_LCDVDES = 0; + if( (SiS_Pr->SiS_IF_DEF_LVDS == 1) || + ((SiS_Pr->SiS_VBType & VB_NoLCD) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD)) || + ((SiS_Pr->ChipType >= SIS_315H) && (SiS_Pr->SiS_VBType & VB_SIS30xBLV)) ) { + SiS_GetLVDSDesData(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + SiS_SetGroup1(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + + SiS_SetGroup2(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); +#ifdef CONFIG_FB_SIS_315 + SiS_SetGroup2_C_ELV(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); +#endif + SiS_SetGroup3(SiS_Pr, ModeNo, ModeIdIndex); + SiS_SetGroup4(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); +#ifdef CONFIG_FB_SIS_315 + SiS_SetGroup4_C_ELV(SiS_Pr, ModeNo, ModeIdIndex); +#endif + SiS_SetGroup5(SiS_Pr, ModeNo, ModeIdIndex); + + SiS_SetCRT2Sync(SiS_Pr, ModeNo, RefreshRateTableIndex); + + /* For 301BDH (Panel link initialization): */ + if((SiS_Pr->SiS_VBType & VB_NoLCD) && (SiS_Pr->SiS_VBInfo & SetCRT2ToLCD)) { + + if(!((SiS_Pr->SiS_SetFlag & SetDOSMode) && ((ModeNo == 0x03) || (ModeNo == 0x10)))) { + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) { + SiS_ModCRT1CRTC(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + } + SiS_SetCRT2ECLK(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + } + + } else { + + SiS_SetCRT2Sync(SiS_Pr, ModeNo, RefreshRateTableIndex); + + SiS_ModCRT1CRTC(SiS_Pr,ModeNo,ModeIdIndex,RefreshRateTableIndex); + + SiS_SetCRT2ECLK(SiS_Pr,ModeNo,ModeIdIndex,RefreshRateTableIndex); + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + if(SiS_Pr->SiS_IF_DEF_CH70xx != 0) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(SiS_Pr->SiS_IF_DEF_CH70xx == 2) { +#ifdef CONFIG_FB_SIS_315 + SiS_SetCH701xForLCD(SiS_Pr); +#endif + } + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + SiS_SetCHTVReg(SiS_Pr,ModeNo,ModeIdIndex,RefreshRateTableIndex); + } + } + } + + } + +#ifdef CONFIG_FB_SIS_300 + if(SiS_Pr->ChipType < SIS_315H) { + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + if(SiS_Pr->SiS_UseOEM) { + if((SiS_Pr->SiS_UseROM) && (SiS_Pr->SiS_UseOEM == -1)) { + if((ROMAddr[0x233] == 0x12) && (ROMAddr[0x234] == 0x34)) { + SiS_OEM300Setting(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + } else { + SiS_OEM300Setting(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + } + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + if((SiS_Pr->SiS_CustomT == CUT_BARCO1366) || + (SiS_Pr->SiS_CustomT == CUT_BARCO1024)) { + SetOEMLCDData2(SiS_Pr, ModeNo, ModeIdIndex,RefreshRateTableIndex); + } + SiS_DisplayOn(SiS_Pr); + } + } + } +#endif + +#ifdef CONFIG_FB_SIS_315 + if(SiS_Pr->ChipType >= SIS_315H) { + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + if(SiS_Pr->ChipType < SIS_661) { + SiS_FinalizeLCD(SiS_Pr, ModeNo, ModeIdIndex); + SiS_OEM310Setting(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } else { + SiS_OEM661Setting(SiS_Pr, ModeNo, ModeIdIndex, RefreshRateTableIndex); + } + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x01,0x40); + } + } +#endif + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + SiS_EnableBridge(SiS_Pr); + } + + SiS_DisplayOn(SiS_Pr); + + if(SiS_Pr->SiS_IF_DEF_CH70xx == 1) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + /* Disable LCD panel when using TV */ + SiS_SetRegSR11ANDOR(SiS_Pr,0xFF,0x0C); + } else { + /* Disable TV when using LCD */ + SiS_SetCH70xxANDOR(SiS_Pr,0x0e,0x01,0xf8); + } + } + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + SiS_LockCRT2(SiS_Pr); + } + + return true; +} + + +/*********************************************/ +/* ENABLE/DISABLE LCD BACKLIGHT (SIS) */ +/*********************************************/ + +void +SiS_SiS30xBLOn(struct SiS_Private *SiS_Pr) +{ + /* Switch on LCD backlight on SiS30xLV */ + SiS_DDC2Delay(SiS_Pr,0xff00); + if(!(SiS_GetReg(SiS_Pr->SiS_Part4Port,0x26) & 0x02)) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x26,0x02); + SiS_WaitVBRetrace(SiS_Pr); + } + if(!(SiS_GetReg(SiS_Pr->SiS_Part4Port,0x26) & 0x01)) { + SiS_SetRegOR(SiS_Pr->SiS_Part4Port,0x26,0x01); + } +} + +void +SiS_SiS30xBLOff(struct SiS_Private *SiS_Pr) +{ + /* Switch off LCD backlight on SiS30xLV */ + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x26,0xFE); + SiS_DDC2Delay(SiS_Pr,0xff00); +} + +/*********************************************/ +/* DDC RELATED FUNCTIONS */ +/*********************************************/ + +static void +SiS_SetupDDCN(struct SiS_Private *SiS_Pr) +{ + SiS_Pr->SiS_DDC_NData = ~SiS_Pr->SiS_DDC_Data; + SiS_Pr->SiS_DDC_NClk = ~SiS_Pr->SiS_DDC_Clk; + if((SiS_Pr->SiS_DDC_Index == 0x11) && (SiS_Pr->SiS_SensibleSR11)) { + SiS_Pr->SiS_DDC_NData &= 0x0f; + SiS_Pr->SiS_DDC_NClk &= 0x0f; + } +} + +#ifdef CONFIG_FB_SIS_300 +static unsigned char * +SiS_SetTrumpBlockLoop(struct SiS_Private *SiS_Pr, unsigned char *dataptr) +{ + int i, j, num; + unsigned short tempah,temp; + unsigned char *mydataptr; + + for(i=0; i<20; i++) { /* Do 20 attempts to write */ + mydataptr = dataptr; + num = *mydataptr++; + if(!num) return mydataptr; + if(i) { + SiS_SetStop(SiS_Pr); + SiS_DDC2Delay(SiS_Pr,SiS_I2CDELAYSHORT * 2); + } + if(SiS_SetStart(SiS_Pr)) continue; /* Set start condition */ + tempah = SiS_Pr->SiS_DDC_DeviceAddr; + temp = SiS_WriteDDC2Data(SiS_Pr,tempah); /* Write DAB (S0=0=write) */ + if(temp) continue; /* (ERROR: no ack) */ + tempah = *mydataptr++; + temp = SiS_WriteDDC2Data(SiS_Pr,tempah); /* Write register number */ + if(temp) continue; /* (ERROR: no ack) */ + for(j=0; j<num; j++) { + tempah = *mydataptr++; + temp = SiS_WriteDDC2Data(SiS_Pr,tempah);/* Write DAB (S0=0=write) */ + if(temp) break; + } + if(temp) continue; + if(SiS_SetStop(SiS_Pr)) continue; + return mydataptr; + } + return NULL; +} + +static bool +SiS_SetTrumpionBlock(struct SiS_Private *SiS_Pr, unsigned char *dataptr) +{ + SiS_Pr->SiS_DDC_DeviceAddr = 0xF0; /* DAB (Device Address Byte) */ + SiS_Pr->SiS_DDC_Index = 0x11; /* Bit 0 = SC; Bit 1 = SD */ + SiS_Pr->SiS_DDC_Data = 0x02; /* Bitmask in IndexReg for Data */ + SiS_Pr->SiS_DDC_Clk = 0x01; /* Bitmask in IndexReg for Clk */ + SiS_SetupDDCN(SiS_Pr); + + SiS_SetSwitchDDC2(SiS_Pr); + + while(*dataptr) { + dataptr = SiS_SetTrumpBlockLoop(SiS_Pr, dataptr); + if(!dataptr) return false; + } + return true; +} +#endif + +/* The Chrontel 700x is connected to the 630/730 via + * the 630/730's DDC/I2C port. + * + * On 630(S)T chipset, the index changed from 0x11 to + * 0x0a, possibly for working around the DDC problems + */ + +static bool +SiS_SetChReg(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val, unsigned short myor) +{ + unsigned short temp, i; + + for(i=0; i<20; i++) { /* Do 20 attempts to write */ + if(i) { + SiS_SetStop(SiS_Pr); + SiS_DDC2Delay(SiS_Pr,SiS_I2CDELAYSHORT * 4); + } + if(SiS_SetStart(SiS_Pr)) continue; /* Set start condition */ + temp = SiS_WriteDDC2Data(SiS_Pr, SiS_Pr->SiS_DDC_DeviceAddr); /* Write DAB (S0=0=write) */ + if(temp) continue; /* (ERROR: no ack) */ + temp = SiS_WriteDDC2Data(SiS_Pr, (reg | myor)); /* Write RAB (700x: set bit 7, see datasheet) */ + if(temp) continue; /* (ERROR: no ack) */ + temp = SiS_WriteDDC2Data(SiS_Pr, val); /* Write data */ + if(temp) continue; /* (ERROR: no ack) */ + if(SiS_SetStop(SiS_Pr)) continue; /* Set stop condition */ + SiS_Pr->SiS_ChrontelInit = 1; + return true; + } + return false; +} + +/* Write to Chrontel 700x */ +void +SiS_SetCH700x(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val) +{ + SiS_Pr->SiS_DDC_DeviceAddr = 0xEA; /* DAB (Device Address Byte) */ + + SiS_DDC2Delay(SiS_Pr,SiS_I2CDELAYSHORT); + + if(!(SiS_Pr->SiS_ChrontelInit)) { + SiS_Pr->SiS_DDC_Index = 0x11; /* Bit 0 = SC; Bit 1 = SD */ + SiS_Pr->SiS_DDC_Data = 0x02; /* Bitmask in IndexReg for Data */ + SiS_Pr->SiS_DDC_Clk = 0x01; /* Bitmask in IndexReg for Clk */ + SiS_SetupDDCN(SiS_Pr); + } + + if( (!(SiS_SetChReg(SiS_Pr, reg, val, 0x80))) && + (!(SiS_Pr->SiS_ChrontelInit)) ) { + SiS_Pr->SiS_DDC_Index = 0x0a; + SiS_Pr->SiS_DDC_Data = 0x80; + SiS_Pr->SiS_DDC_Clk = 0x40; + SiS_SetupDDCN(SiS_Pr); + + SiS_SetChReg(SiS_Pr, reg, val, 0x80); + } +} + +/* Write to Chrontel 701x */ +/* Parameter is [Data (S15-S8) | Register no (S7-S0)] */ +void +SiS_SetCH701x(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val) +{ + SiS_Pr->SiS_DDC_Index = 0x11; /* Bit 0 = SC; Bit 1 = SD */ + SiS_Pr->SiS_DDC_Data = 0x08; /* Bitmask in IndexReg for Data */ + SiS_Pr->SiS_DDC_Clk = 0x04; /* Bitmask in IndexReg for Clk */ + SiS_SetupDDCN(SiS_Pr); + SiS_Pr->SiS_DDC_DeviceAddr = 0xEA; /* DAB (Device Address Byte) */ + SiS_SetChReg(SiS_Pr, reg, val, 0); +} + +static +void +SiS_SetCH70xx(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val) +{ + if(SiS_Pr->SiS_IF_DEF_CH70xx == 1) + SiS_SetCH700x(SiS_Pr, reg, val); + else + SiS_SetCH701x(SiS_Pr, reg, val); +} + +static unsigned short +SiS_GetChReg(struct SiS_Private *SiS_Pr, unsigned short myor) +{ + unsigned short tempah, temp, i; + + for(i=0; i<20; i++) { /* Do 20 attempts to read */ + if(i) { + SiS_SetStop(SiS_Pr); + SiS_DDC2Delay(SiS_Pr,SiS_I2CDELAYSHORT * 4); + } + if(SiS_SetStart(SiS_Pr)) continue; /* Set start condition */ + temp = SiS_WriteDDC2Data(SiS_Pr,SiS_Pr->SiS_DDC_DeviceAddr); /* Write DAB (S0=0=write) */ + if(temp) continue; /* (ERROR: no ack) */ + temp = SiS_WriteDDC2Data(SiS_Pr,SiS_Pr->SiS_DDC_ReadAddr | myor); /* Write RAB (700x: | 0x80) */ + if(temp) continue; /* (ERROR: no ack) */ + if (SiS_SetStart(SiS_Pr)) continue; /* Re-start */ + temp = SiS_WriteDDC2Data(SiS_Pr,SiS_Pr->SiS_DDC_DeviceAddr | 0x01);/* DAB (S0=1=read) */ + if(temp) continue; /* (ERROR: no ack) */ + tempah = SiS_ReadDDC2Data(SiS_Pr); /* Read byte */ + if(SiS_SetStop(SiS_Pr)) continue; /* Stop condition */ + SiS_Pr->SiS_ChrontelInit = 1; + return tempah; + } + return 0xFFFF; +} + +/* Read from Chrontel 700x */ +/* Parameter is [Register no (S7-S0)] */ +unsigned short +SiS_GetCH700x(struct SiS_Private *SiS_Pr, unsigned short tempbx) +{ + unsigned short result; + + SiS_Pr->SiS_DDC_DeviceAddr = 0xEA; /* DAB */ + + SiS_DDC2Delay(SiS_Pr,SiS_I2CDELAYSHORT); + + if(!(SiS_Pr->SiS_ChrontelInit)) { + SiS_Pr->SiS_DDC_Index = 0x11; /* Bit 0 = SC; Bit 1 = SD */ + SiS_Pr->SiS_DDC_Data = 0x02; /* Bitmask in IndexReg for Data */ + SiS_Pr->SiS_DDC_Clk = 0x01; /* Bitmask in IndexReg for Clk */ + SiS_SetupDDCN(SiS_Pr); + } + + SiS_Pr->SiS_DDC_ReadAddr = tempbx; + + if( ((result = SiS_GetChReg(SiS_Pr,0x80)) == 0xFFFF) && + (!SiS_Pr->SiS_ChrontelInit) ) { + + SiS_Pr->SiS_DDC_Index = 0x0a; + SiS_Pr->SiS_DDC_Data = 0x80; + SiS_Pr->SiS_DDC_Clk = 0x40; + SiS_SetupDDCN(SiS_Pr); + + result = SiS_GetChReg(SiS_Pr,0x80); + } + return result; +} + +/* Read from Chrontel 701x */ +/* Parameter is [Register no (S7-S0)] */ +unsigned short +SiS_GetCH701x(struct SiS_Private *SiS_Pr, unsigned short tempbx) +{ + SiS_Pr->SiS_DDC_Index = 0x11; /* Bit 0 = SC; Bit 1 = SD */ + SiS_Pr->SiS_DDC_Data = 0x08; /* Bitmask in IndexReg for Data */ + SiS_Pr->SiS_DDC_Clk = 0x04; /* Bitmask in IndexReg for Clk */ + SiS_SetupDDCN(SiS_Pr); + SiS_Pr->SiS_DDC_DeviceAddr = 0xEA; /* DAB */ + + SiS_Pr->SiS_DDC_ReadAddr = tempbx; + + return SiS_GetChReg(SiS_Pr,0); +} + +/* Read from Chrontel 70xx */ +/* Parameter is [Register no (S7-S0)] */ +static +unsigned short +SiS_GetCH70xx(struct SiS_Private *SiS_Pr, unsigned short tempbx) +{ + if(SiS_Pr->SiS_IF_DEF_CH70xx == 1) + return SiS_GetCH700x(SiS_Pr, tempbx); + else + return SiS_GetCH701x(SiS_Pr, tempbx); +} + +void +SiS_SetCH70xxANDOR(struct SiS_Private *SiS_Pr, unsigned short reg, + unsigned char myor, unsigned short myand) +{ + unsigned short tempbl; + + tempbl = (SiS_GetCH70xx(SiS_Pr, (reg & 0xFF)) & myand) | myor; + SiS_SetCH70xx(SiS_Pr, reg, tempbl); +} + +/* Our own DDC functions */ +static +unsigned short +SiS_InitDDCRegs(struct SiS_Private *SiS_Pr, unsigned int VBFlags, int VGAEngine, + unsigned short adaptnum, unsigned short DDCdatatype, bool checkcr32, + unsigned int VBFlags2) +{ + unsigned char ddcdtype[] = { 0xa0, 0xa0, 0xa0, 0xa2, 0xa6 }; + unsigned char flag, cr32; + unsigned short temp = 0, myadaptnum = adaptnum; + + if(adaptnum != 0) { + if(!(VBFlags2 & VB2_SISTMDSBRIDGE)) return 0xFFFF; + if((VBFlags2 & VB2_30xBDH) && (adaptnum == 1)) return 0xFFFF; + } + + /* adapternum for SiS bridges: 0 = CRT1, 1 = LCD, 2 = VGA2 */ + + SiS_Pr->SiS_ChrontelInit = 0; /* force re-detection! */ + + SiS_Pr->SiS_DDC_SecAddr = 0; + SiS_Pr->SiS_DDC_DeviceAddr = ddcdtype[DDCdatatype]; + SiS_Pr->SiS_DDC_Port = SiS_Pr->SiS_P3c4; + SiS_Pr->SiS_DDC_Index = 0x11; + flag = 0xff; + + cr32 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x32); + +#if 0 + if(VBFlags2 & VB2_SISBRIDGE) { + if(myadaptnum == 0) { + if(!(cr32 & 0x20)) { + myadaptnum = 2; + if(!(cr32 & 0x10)) { + myadaptnum = 1; + if(!(cr32 & 0x08)) { + myadaptnum = 0; + } + } + } + } + } +#endif + + if(VGAEngine == SIS_300_VGA) { /* 300 series */ + + if(myadaptnum != 0) { + flag = 0; + if(VBFlags2 & VB2_SISBRIDGE) { + SiS_Pr->SiS_DDC_Port = SiS_Pr->SiS_Part4Port; + SiS_Pr->SiS_DDC_Index = 0x0f; + } + } + + if(!(VBFlags2 & VB2_301)) { + if((cr32 & 0x80) && (checkcr32)) { + if(myadaptnum >= 1) { + if(!(cr32 & 0x08)) { + myadaptnum = 1; + if(!(cr32 & 0x10)) return 0xFFFF; + } + } + } + } + + temp = 4 - (myadaptnum * 2); + if(flag) temp = 0; + + } else { /* 315/330 series */ + + /* here we simplify: 0 = CRT1, 1 = CRT2 (VGA, LCD) */ + + if(VBFlags2 & VB2_SISBRIDGE) { + if(myadaptnum == 2) { + myadaptnum = 1; + } + } + + if(myadaptnum == 1) { + flag = 0; + if(VBFlags2 & VB2_SISBRIDGE) { + SiS_Pr->SiS_DDC_Port = SiS_Pr->SiS_Part4Port; + SiS_Pr->SiS_DDC_Index = 0x0f; + } + } + + if((cr32 & 0x80) && (checkcr32)) { + if(myadaptnum >= 1) { + if(!(cr32 & 0x08)) { + myadaptnum = 1; + if(!(cr32 & 0x10)) return 0xFFFF; + } + } + } + + temp = myadaptnum; + if(myadaptnum == 1) { + temp = 0; + if(VBFlags2 & VB2_LVDS) flag = 0xff; + } + + if(flag) temp = 0; + } + + SiS_Pr->SiS_DDC_Data = 0x02 << temp; + SiS_Pr->SiS_DDC_Clk = 0x01 << temp; + + SiS_SetupDDCN(SiS_Pr); + + return 0; +} + +static unsigned short +SiS_WriteDABDDC(struct SiS_Private *SiS_Pr) +{ + if(SiS_SetStart(SiS_Pr)) return 0xFFFF; + if(SiS_WriteDDC2Data(SiS_Pr, SiS_Pr->SiS_DDC_DeviceAddr)) { + return 0xFFFF; + } + if(SiS_WriteDDC2Data(SiS_Pr, SiS_Pr->SiS_DDC_SecAddr)) { + return 0xFFFF; + } + return 0; +} + +static unsigned short +SiS_PrepareReadDDC(struct SiS_Private *SiS_Pr) +{ + if(SiS_SetStart(SiS_Pr)) return 0xFFFF; + if(SiS_WriteDDC2Data(SiS_Pr, (SiS_Pr->SiS_DDC_DeviceAddr | 0x01))) { + return 0xFFFF; + } + return 0; +} + +static unsigned short +SiS_PrepareDDC(struct SiS_Private *SiS_Pr) +{ + if(SiS_WriteDABDDC(SiS_Pr)) SiS_WriteDABDDC(SiS_Pr); + if(SiS_PrepareReadDDC(SiS_Pr)) return (SiS_PrepareReadDDC(SiS_Pr)); + return 0; +} + +static void +SiS_SendACK(struct SiS_Private *SiS_Pr, unsigned short yesno) +{ + SiS_SetSCLKLow(SiS_Pr); + if(yesno) { + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + SiS_Pr->SiS_DDC_Data); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + 0); + } + SiS_SetSCLKHigh(SiS_Pr); +} + +static unsigned short +SiS_DoProbeDDC(struct SiS_Private *SiS_Pr) +{ + unsigned char mask, value; + unsigned short temp, ret=0; + bool failed = false; + + SiS_SetSwitchDDC2(SiS_Pr); + if(SiS_PrepareDDC(SiS_Pr)) { + SiS_SetStop(SiS_Pr); + return 0xFFFF; + } + mask = 0xf0; + value = 0x20; + if(SiS_Pr->SiS_DDC_DeviceAddr == 0xa0) { + temp = (unsigned char)SiS_ReadDDC2Data(SiS_Pr); + SiS_SendACK(SiS_Pr, 0); + if(temp == 0) { + mask = 0xff; + value = 0xff; + } else { + failed = true; + ret = 0xFFFF; + } + } + if(!failed) { + temp = (unsigned char)SiS_ReadDDC2Data(SiS_Pr); + SiS_SendACK(SiS_Pr, 1); + temp &= mask; + if(temp == value) ret = 0; + else { + ret = 0xFFFF; + if(SiS_Pr->SiS_DDC_DeviceAddr == 0xa0) { + if(temp == 0x30) ret = 0; + } + } + } + SiS_SetStop(SiS_Pr); + return ret; +} + +static +unsigned short +SiS_ProbeDDC(struct SiS_Private *SiS_Pr) +{ + unsigned short flag; + + flag = 0x180; + SiS_Pr->SiS_DDC_DeviceAddr = 0xa0; + if(!(SiS_DoProbeDDC(SiS_Pr))) flag |= 0x02; + SiS_Pr->SiS_DDC_DeviceAddr = 0xa2; + if(!(SiS_DoProbeDDC(SiS_Pr))) flag |= 0x08; + SiS_Pr->SiS_DDC_DeviceAddr = 0xa6; + if(!(SiS_DoProbeDDC(SiS_Pr))) flag |= 0x10; + if(!(flag & 0x1a)) flag = 0; + return flag; +} + +static +unsigned short +SiS_ReadDDC(struct SiS_Private *SiS_Pr, unsigned short DDCdatatype, unsigned char *buffer) +{ + unsigned short flag, length, i; + unsigned char chksum,gotcha; + + if(DDCdatatype > 4) return 0xFFFF; + + flag = 0; + SiS_SetSwitchDDC2(SiS_Pr); + if(!(SiS_PrepareDDC(SiS_Pr))) { + length = 127; + if(DDCdatatype != 1) length = 255; + chksum = 0; + gotcha = 0; + for(i=0; i<length; i++) { + buffer[i] = (unsigned char)SiS_ReadDDC2Data(SiS_Pr); + chksum += buffer[i]; + gotcha |= buffer[i]; + SiS_SendACK(SiS_Pr, 0); + } + buffer[i] = (unsigned char)SiS_ReadDDC2Data(SiS_Pr); + chksum += buffer[i]; + SiS_SendACK(SiS_Pr, 1); + if(gotcha) flag = (unsigned short)chksum; + else flag = 0xFFFF; + } else { + flag = 0xFFFF; + } + SiS_SetStop(SiS_Pr); + return flag; +} + +/* Our private DDC functions + + It complies somewhat with the corresponding VESA function + in arguments and return values. + + Since this is probably called before the mode is changed, + we use our pre-detected pSiS-values instead of SiS_Pr as + regards chipset and video bridge type. + + Arguments: + adaptnum: 0=CRT1(analog), 1=CRT2/LCD(digital), 2=CRT2/VGA2(analog) + CRT2 DDC is only supported on SiS301, 301B, 301C, 302B. + LCDA is CRT1, but DDC is read from CRT2 port. + DDCdatatype: 0=Probe, 1=EDID, 2=EDID+VDIF, 3=EDID V2 (P&D), 4=EDID V2 (FPDI-2) + buffer: ptr to 256 data bytes which will be filled with read data. + + Returns 0xFFFF if error, otherwise + if DDCdatatype > 0: Returns 0 if reading OK (included a correct checksum) + if DDCdatatype = 0: Returns supported DDC modes + + */ +unsigned short +SiS_HandleDDC(struct SiS_Private *SiS_Pr, unsigned int VBFlags, int VGAEngine, + unsigned short adaptnum, unsigned short DDCdatatype, unsigned char *buffer, + unsigned int VBFlags2) +{ + unsigned char sr1f, cr17=1; + unsigned short result; + + if(adaptnum > 2) + return 0xFFFF; + + if(DDCdatatype > 4) + return 0xFFFF; + + if((!(VBFlags2 & VB2_VIDEOBRIDGE)) && (adaptnum > 0)) + return 0xFFFF; + + if(SiS_InitDDCRegs(SiS_Pr, VBFlags, VGAEngine, adaptnum, DDCdatatype, false, VBFlags2) == 0xFFFF) + return 0xFFFF; + + sr1f = SiS_GetReg(SiS_Pr->SiS_P3c4,0x1f); + SiS_SetRegANDOR(SiS_Pr->SiS_P3c4,0x1f,0x3f,0x04); + if(VGAEngine == SIS_300_VGA) { + cr17 = SiS_GetReg(SiS_Pr->SiS_P3d4,0x17) & 0x80; + if(!cr17) { + SiS_SetRegOR(SiS_Pr->SiS_P3d4,0x17,0x80); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x00,0x01); + SiS_SetReg(SiS_Pr->SiS_P3c4,0x00,0x03); + } + } + if((sr1f) || (!cr17)) { + SiS_WaitRetrace1(SiS_Pr); + SiS_WaitRetrace1(SiS_Pr); + SiS_WaitRetrace1(SiS_Pr); + SiS_WaitRetrace1(SiS_Pr); + } + + if(DDCdatatype == 0) { + result = SiS_ProbeDDC(SiS_Pr); + } else { + result = SiS_ReadDDC(SiS_Pr, DDCdatatype, buffer); + if((!result) && (DDCdatatype == 1)) { + if((buffer[0] == 0x00) && (buffer[1] == 0xff) && + (buffer[2] == 0xff) && (buffer[3] == 0xff) && + (buffer[4] == 0xff) && (buffer[5] == 0xff) && + (buffer[6] == 0xff) && (buffer[7] == 0x00) && + (buffer[0x12] == 1)) { + if(!SiS_Pr->DDCPortMixup) { + if(adaptnum == 1) { + if(!(buffer[0x14] & 0x80)) result = 0xFFFE; + } else { + if(buffer[0x14] & 0x80) result = 0xFFFE; + } + } + } + } + } + SiS_SetReg(SiS_Pr->SiS_P3c4,0x1f,sr1f); + if(VGAEngine == SIS_300_VGA) { + SiS_SetRegANDOR(SiS_Pr->SiS_P3d4,0x17,0x7f,cr17); + } + return result; +} + +/* Generic I2C functions for Chrontel & DDC --------- */ + +static void +SiS_SetSwitchDDC2(struct SiS_Private *SiS_Pr) +{ + SiS_SetSCLKHigh(SiS_Pr); + SiS_WaitRetrace1(SiS_Pr); + + SiS_SetSCLKLow(SiS_Pr); + SiS_WaitRetrace1(SiS_Pr); +} + +unsigned short +SiS_ReadDDC1Bit(struct SiS_Private *SiS_Pr) +{ + SiS_WaitRetrace1(SiS_Pr); + return ((SiS_GetReg(SiS_Pr->SiS_P3c4,0x11) & 0x02) >> 1); +} + +/* Set I2C start condition */ +/* This is done by a SD high-to-low transition while SC is high */ +static unsigned short +SiS_SetStart(struct SiS_Private *SiS_Pr) +{ + if(SiS_SetSCLKLow(SiS_Pr)) return 0xFFFF; /* (SC->low) */ + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + SiS_Pr->SiS_DDC_Data); /* SD->high */ + if(SiS_SetSCLKHigh(SiS_Pr)) return 0xFFFF; /* SC->high */ + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + 0x00); /* SD->low = start condition */ + if(SiS_SetSCLKHigh(SiS_Pr)) return 0xFFFF; /* (SC->low) */ + return 0; +} + +/* Set I2C stop condition */ +/* This is done by a SD low-to-high transition while SC is high */ +static unsigned short +SiS_SetStop(struct SiS_Private *SiS_Pr) +{ + if(SiS_SetSCLKLow(SiS_Pr)) return 0xFFFF; /* (SC->low) */ + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + 0x00); /* SD->low */ + if(SiS_SetSCLKHigh(SiS_Pr)) return 0xFFFF; /* SC->high */ + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + SiS_Pr->SiS_DDC_Data); /* SD->high = stop condition */ + if(SiS_SetSCLKHigh(SiS_Pr)) return 0xFFFF; /* (SC->high) */ + return 0; +} + +/* Write 8 bits of data */ +static unsigned short +SiS_WriteDDC2Data(struct SiS_Private *SiS_Pr, unsigned short tempax) +{ + unsigned short i,flag,temp; + + flag = 0x80; + for(i = 0; i < 8; i++) { + SiS_SetSCLKLow(SiS_Pr); /* SC->low */ + if(tempax & flag) { + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + SiS_Pr->SiS_DDC_Data); /* Write bit (1) to SD */ + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + 0x00); /* Write bit (0) to SD */ + } + SiS_SetSCLKHigh(SiS_Pr); /* SC->high */ + flag >>= 1; + } + temp = SiS_CheckACK(SiS_Pr); /* Check acknowledge */ + return temp; +} + +static unsigned short +SiS_ReadDDC2Data(struct SiS_Private *SiS_Pr) +{ + unsigned short i, temp, getdata; + + getdata = 0; + for(i = 0; i < 8; i++) { + getdata <<= 1; + SiS_SetSCLKLow(SiS_Pr); + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + SiS_Pr->SiS_DDC_Data); + SiS_SetSCLKHigh(SiS_Pr); + temp = SiS_GetReg(SiS_Pr->SiS_DDC_Port,SiS_Pr->SiS_DDC_Index); + if(temp & SiS_Pr->SiS_DDC_Data) getdata |= 0x01; + } + return getdata; +} + +static unsigned short +SiS_SetSCLKLow(struct SiS_Private *SiS_Pr) +{ + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NClk, + 0x00); /* SetSCLKLow() */ + SiS_DDC2Delay(SiS_Pr,SiS_I2CDELAYSHORT); + return 0; +} + +static unsigned short +SiS_SetSCLKHigh(struct SiS_Private *SiS_Pr) +{ + unsigned short temp, watchdog=1000; + + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NClk, + SiS_Pr->SiS_DDC_Clk); /* SetSCLKHigh() */ + do { + temp = SiS_GetReg(SiS_Pr->SiS_DDC_Port,SiS_Pr->SiS_DDC_Index); + } while((!(temp & SiS_Pr->SiS_DDC_Clk)) && --watchdog); + if (!watchdog) { + return 0xFFFF; + } + SiS_DDC2Delay(SiS_Pr,SiS_I2CDELAYSHORT); + return 0; +} + +/* Check I2C acknowledge */ +/* Returns 0 if ack ok, non-0 if ack not ok */ +static unsigned short +SiS_CheckACK(struct SiS_Private *SiS_Pr) +{ + unsigned short tempah; + + SiS_SetSCLKLow(SiS_Pr); /* (SC->low) */ + SiS_SetRegANDOR(SiS_Pr->SiS_DDC_Port, + SiS_Pr->SiS_DDC_Index, + SiS_Pr->SiS_DDC_NData, + SiS_Pr->SiS_DDC_Data); /* (SD->high) */ + SiS_SetSCLKHigh(SiS_Pr); /* SC->high = clock impulse for ack */ + tempah = SiS_GetReg(SiS_Pr->SiS_DDC_Port,SiS_Pr->SiS_DDC_Index); /* Read SD */ + SiS_SetSCLKLow(SiS_Pr); /* SC->low = end of clock impulse */ + if(tempah & SiS_Pr->SiS_DDC_Data) return 1; /* Ack OK if bit = 0 */ + return 0; +} + +/* End of I2C functions ----------------------- */ + + +/* =============== SiS 315/330 O.E.M. ================= */ + +#ifdef CONFIG_FB_SIS_315 + +static unsigned short +GetRAMDACromptr(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr; + + if(SiS_Pr->ChipType < SIS_330) { + romptr = SISGETROMW(0x128); + if(SiS_Pr->SiS_VBType & VB_SIS30xB) + romptr = SISGETROMW(0x12a); + } else { + romptr = SISGETROMW(0x1a8); + if(SiS_Pr->SiS_VBType & VB_SIS30xB) + romptr = SISGETROMW(0x1aa); + } + return romptr; +} + +static unsigned short +GetLCDromptr(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr; + + if(SiS_Pr->ChipType < SIS_330) { + romptr = SISGETROMW(0x120); + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) + romptr = SISGETROMW(0x122); + } else { + romptr = SISGETROMW(0x1a0); + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) + romptr = SISGETROMW(0x1a2); + } + return romptr; +} + +static unsigned short +GetTVromptr(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr; + + if(SiS_Pr->ChipType < SIS_330) { + romptr = SISGETROMW(0x114); + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) + romptr = SISGETROMW(0x11a); + } else { + romptr = SISGETROMW(0x194); + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) + romptr = SISGETROMW(0x19a); + } + return romptr; +} + +static unsigned short +GetLCDPtrIndexBIOS(struct SiS_Private *SiS_Pr) +{ + unsigned short index; + + if((IS_SIS650) && (SiS_Pr->SiS_VBType & VB_SISLVDS)) { + if(!(SiS_IsNotM650orLater(SiS_Pr))) { + if((index = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) & 0xf0)) { + index >>= 4; + index *= 3; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) index += 2; + else if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) index++; + return index; + } + } + } + + index = SiS_GetBIOSLCDResInfo(SiS_Pr) & 0x0F; + if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) index -= 5; + if(SiS_Pr->SiS_VBType & VB_SIS301C) { /* 1.15.20 and later (not VB specific) */ + if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) index -= 5; + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x768) index -= 5; + } else { + if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) index -= 6; + } + index--; + index *= 3; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) index += 2; + else if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) index++; + return index; +} + +static unsigned short +GetLCDPtrIndex(struct SiS_Private *SiS_Pr) +{ + unsigned short index; + + index = ((SiS_GetBIOSLCDResInfo(SiS_Pr) & 0x0F) - 1) * 3; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) index += 2; + else if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) index++; + return index; +} + +static unsigned short +GetTVPtrIndex(struct SiS_Private *SiS_Pr) +{ + unsigned short index; + + index = 0; + if(SiS_Pr->SiS_TVMode & TVSetPAL) index = 1; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) index = 2; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToYPbPr525750) index = 0; + + index <<= 1; + + if((SiS_Pr->SiS_VBInfo & SetInSlaveMode) && + (SiS_Pr->SiS_TVMode & TVSetTVSimuMode)) { + index++; + } + + return index; +} + +static unsigned int +GetOEMTVPtr661_2_GEN(struct SiS_Private *SiS_Pr, int addme) +{ + unsigned short index = 0, temp = 0; + + if(SiS_Pr->SiS_TVMode & TVSetPAL) index = 1; + if(SiS_Pr->SiS_TVMode & TVSetPALM) index = 2; + if(SiS_Pr->SiS_TVMode & TVSetPALN) index = 3; + if(SiS_Pr->SiS_TVMode & TVSetNTSCJ) index = 6; + if(SiS_Pr->SiS_TVMode & TVSetNTSC1024) { + index = 4; + if(SiS_Pr->SiS_TVMode & TVSetPALM) index++; + if(SiS_Pr->SiS_TVMode & TVSetNTSCJ) index = 7; + } + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if((!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) || + (SiS_Pr->SiS_TVMode & TVSetTVSimuMode)) { + index += addme; + temp++; + } + temp += 0x0100; + } + return (unsigned int)(index | (temp << 16)); +} + +static unsigned int +GetOEMTVPtr661_2_OLD(struct SiS_Private *SiS_Pr) +{ + return (GetOEMTVPtr661_2_GEN(SiS_Pr, 8)); +} + +#if 0 +static unsigned int +GetOEMTVPtr661_2_NEW(struct SiS_Private *SiS_Pr) +{ + return (GetOEMTVPtr661_2_GEN(SiS_Pr, 6)); +} +#endif + +static int +GetOEMTVPtr661(struct SiS_Private *SiS_Pr) +{ + int index = 0; + + if(SiS_Pr->SiS_TVMode & TVSetPAL) index = 2; + if(SiS_Pr->SiS_ROMNew) { + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525i) index = 4; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) index = 6; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) index = 8; + if(SiS_Pr->SiS_TVMode & TVSetHiVision) index = 10; + } else { + if(SiS_Pr->SiS_TVMode & TVSetHiVision) index = 4; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525i) index = 6; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr525p) index = 8; + if(SiS_Pr->SiS_TVMode & TVSetYPbPr750p) index = 10; + } + + if(SiS_Pr->SiS_TVMode & TVSetTVSimuMode) index++; + + return index; +} + +static void +SetDelayComp(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short delay=0,index,myindex,temp,romptr=0; + bool dochiptest = true; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x20,0xbf); + } else { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x35,0x7f); + } + + /* Find delay (from ROM, internal tables, PCI subsystem) */ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC) { /* ------------ VGA */ + + if((SiS_Pr->SiS_UseROM) && (!(SiS_Pr->SiS_ROMNew))) { + romptr = GetRAMDACromptr(SiS_Pr); + } + if(romptr) delay = ROMAddr[romptr]; + else { + delay = 0x04; + if(SiS_Pr->SiS_VBType & VB_SIS30xB) { + if(IS_SIS650) { + delay = 0x0a; + } else if(IS_SIS740) { + delay = 0x00; + } else if(SiS_Pr->ChipType < SIS_330) { + delay = 0x0c; + } else { + delay = 0x0c; + } + } else if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + delay = 0x00; + } + } + + } else if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD|SetCRT2ToLCDA)) { /* ---------- LCD/LCDA */ + + bool gotitfrompci = false; + + /* Could we detect a PDC for LCD or did we get a user-defined? If yes, use it */ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + if(SiS_Pr->PDC != -1) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0xf0,((SiS_Pr->PDC >> 1) & 0x0f)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x35,0x7f,((SiS_Pr->PDC & 0x01) << 7)); + return; + } + } else { + if(SiS_Pr->PDCA != -1) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0x0f,((SiS_Pr->PDCA << 3) & 0xf0)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x20,0xbf,((SiS_Pr->PDCA & 0x01) << 6)); + return; + } + } + + /* Custom Panel? */ + + if(SiS_Pr->SiS_LCDResInfo == Panel_Custom) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + delay = 0x00; + if((SiS_Pr->PanelXRes <= 1280) && (SiS_Pr->PanelYRes <= 1024)) { + delay = 0x20; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0x0f,delay); + } else { + delay = 0x0c; + if(SiS_Pr->SiS_VBType & VB_SIS301C) { + delay = 0x03; + if((SiS_Pr->PanelXRes > 1280) && (SiS_Pr->PanelYRes > 1024)) { + delay = 0x00; + } + } else if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + if(IS_SIS740) delay = 0x01; + else delay = 0x03; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0xf0,delay); + } + return; + } + + /* This is a piece of typical SiS crap: They code the OEM LCD + * delay into the code, at no defined place in the BIOS. + * We now have to start doing a PCI subsystem check here. + */ + + switch(SiS_Pr->SiS_CustomT) { + case CUT_COMPAQ1280: + case CUT_COMPAQ12802: + if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) { + gotitfrompci = true; + dochiptest = false; + delay = 0x03; + } + break; + case CUT_CLEVO1400: + case CUT_CLEVO14002: + gotitfrompci = true; + dochiptest = false; + delay = 0x02; + break; + case CUT_CLEVO1024: + case CUT_CLEVO10242: + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + gotitfrompci = true; + dochiptest = false; + delay = 0x33; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2D,delay); + delay &= 0x0f; + } + break; + } + + /* Could we find it through the PCI ID? If no, use ROM or table */ + + if(!gotitfrompci) { + + index = GetLCDPtrIndexBIOS(SiS_Pr); + myindex = GetLCDPtrIndex(SiS_Pr); + + if(IS_SIS650 && (SiS_Pr->SiS_VBType & VB_SISLVDS)) { + + if(SiS_IsNotM650orLater(SiS_Pr)) { + + if((SiS_Pr->SiS_UseROM) && (!(SiS_Pr->SiS_ROMNew))) { + /* Always use the second pointer on 650; some BIOSes */ + /* still carry old 301 data at the first location */ + /* romptr = SISGETROMW(0x120); */ + /* if(SiS_Pr->SiS_VBType & VB_SIS302LV) */ + romptr = SISGETROMW(0x122); + if(!romptr) return; + delay = ROMAddr[(romptr + index)]; + } else { + delay = SiS310_LCDDelayCompensation_650301LV[myindex]; + } + + } else { + + delay = SiS310_LCDDelayCompensation_651301LV[myindex]; + if(SiS_Pr->SiS_VBType & (VB_SIS302LV | VB_SIS302ELV)) + delay = SiS310_LCDDelayCompensation_651302LV[myindex]; + + } + + } else if(SiS_Pr->SiS_UseROM && + (!(SiS_Pr->SiS_ROMNew)) && + (SiS_Pr->SiS_LCDResInfo != Panel_1280x1024) && + (SiS_Pr->SiS_LCDResInfo != Panel_1280x768) && + (SiS_Pr->SiS_LCDResInfo != Panel_1280x960) && + (SiS_Pr->SiS_LCDResInfo != Panel_1600x1200) && + ((romptr = GetLCDromptr(SiS_Pr)))) { + + /* Data for 1280x1024 wrong in 301B BIOS */ + /* Data for 1600x1200 wrong in 301C BIOS */ + delay = ROMAddr[(romptr + index)]; + + } else if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + + if(IS_SIS740) delay = 0x03; + else delay = 0x00; + + } else { + + delay = SiS310_LCDDelayCompensation_301[myindex]; + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + if(IS_SIS740) delay = 0x01; + else if(SiS_Pr->ChipType <= SIS_315PRO) delay = SiS310_LCDDelayCompensation_3xx301LV[myindex]; + else delay = SiS310_LCDDelayCompensation_650301LV[myindex]; + } else if(SiS_Pr->SiS_VBType & VB_SIS301C) { + if(IS_SIS740) delay = 0x01; /* ? */ + else delay = 0x03; + if(SiS_Pr->SiS_LCDResInfo == Panel_1600x1200) delay = 0x00; /* experience */ + } else if(SiS_Pr->SiS_VBType & VB_SIS30xB) { + if(IS_SIS740) delay = 0x01; + else delay = SiS310_LCDDelayCompensation_3xx301B[myindex]; + } + + } + + } /* got it from PCI */ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2D,0x0F,((delay << 4) & 0xf0)); + dochiptest = false; + } + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { /* ------------ TV */ + + index = GetTVPtrIndex(SiS_Pr); + + if(IS_SIS650 && (SiS_Pr->SiS_VBType & VB_SISLVDS)) { + + if(SiS_IsNotM650orLater(SiS_Pr)) { + + if((SiS_Pr->SiS_UseROM) && (!(SiS_Pr->SiS_ROMNew))) { + /* Always use the second pointer on 650; some BIOSes */ + /* still carry old 301 data at the first location */ + /* romptr = SISGETROMW(0x114); */ + /* if(SiS_Pr->SiS_VBType & VB_SIS302LV) */ + romptr = SISGETROMW(0x11a); + if(!romptr) return; + delay = ROMAddr[romptr + index]; + + } else { + + delay = SiS310_TVDelayCompensation_301B[index]; + + } + + } else { + + switch(SiS_Pr->SiS_CustomT) { + case CUT_COMPAQ1280: + case CUT_COMPAQ12802: + case CUT_CLEVO1400: + case CUT_CLEVO14002: + delay = 0x02; + dochiptest = false; + break; + case CUT_CLEVO1024: + case CUT_CLEVO10242: + delay = 0x03; + dochiptest = false; + break; + default: + delay = SiS310_TVDelayCompensation_651301LV[index]; + if(SiS_Pr->SiS_VBType & VB_SIS302LV) { + delay = SiS310_TVDelayCompensation_651302LV[index]; + } + } + } + + } else if((SiS_Pr->SiS_UseROM) && (!(SiS_Pr->SiS_ROMNew))) { + + romptr = GetTVromptr(SiS_Pr); + if(!romptr) return; + delay = ROMAddr[romptr + index]; + + } else if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + + delay = SiS310_TVDelayCompensation_LVDS[index]; + + } else { + + delay = SiS310_TVDelayCompensation_301[index]; + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + if(IS_SIS740) { + delay = SiS310_TVDelayCompensation_740301B[index]; + /* LV: use 301 data? BIOS bug? */ + } else { + delay = SiS310_TVDelayCompensation_301B[index]; + if(SiS_Pr->SiS_VBType & VB_SIS301C) delay = 0x02; + } + } + + } + + if(SiS_LCDAEnabled(SiS_Pr)) { + delay &= 0x0f; + dochiptest = false; + } + + } else return; + + /* Write delay */ + + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + if(IS_SIS650 && (SiS_Pr->SiS_VBType & VB_SISLVDS) && dochiptest) { + + temp = (SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) & 0xf0) >> 4; + if(temp == 8) { /* 1400x1050 BIOS (COMPAL) */ + delay &= 0x0f; + delay |= 0xb0; + } else if(temp == 6) { + delay &= 0x0f; + delay |= 0xc0; + } else if(temp > 7) { /* 1280x1024 BIOS (which one?) */ + delay = 0x35; + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x2D,delay); + + } else { + + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2D,0xF0,delay); + + } + + } else { /* LVDS */ + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2D,0xF0,delay); + } else { + if(IS_SIS650 && (SiS_Pr->SiS_IF_DEF_CH70xx != 0)) { + delay <<= 4; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2D,0x0F,delay); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2D,0xF0,delay); + } + } + + } + +} + +static void +SetAntiFlicker(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp,temp1,romptr=0; + + if(SiS_Pr->SiS_TVMode & (TVSetYPbPr750p|TVSetYPbPr525p)) return; + + if(ModeNo<=0x13) + index = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].VB_StTVFlickerIndex; + else + index = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].VB_ExtTVFlickerIndex; + + temp = GetTVPtrIndex(SiS_Pr); + temp >>= 1; /* 0: NTSC/YPbPr, 1: PAL, 2: HiTV */ + temp1 = temp; + + if(SiS_Pr->SiS_UseROM && (!(SiS_Pr->SiS_ROMNew))) { + if(SiS_Pr->ChipType >= SIS_661) { + temp1 = GetOEMTVPtr661(SiS_Pr); + temp1 >>= 1; + romptr = SISGETROMW(0x260); + if(SiS_Pr->ChipType >= SIS_760) { + romptr = SISGETROMW(0x360); + } + } else if(SiS_Pr->ChipType >= SIS_330) { + romptr = SISGETROMW(0x192); + } else { + romptr = SISGETROMW(0x112); + } + } + + if(romptr) { + temp1 <<= 1; + temp = ROMAddr[romptr + temp1 + index]; + } else { + temp = SiS310_TVAntiFlick1[temp][index]; + } + temp <<= 4; + + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x0A,0x8f,temp); /* index 0A D[6:4] */ +} + +static void +SetEdgeEnhance(struct SiS_Private *SiS_Pr, unsigned short ModeNo,unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp,temp1,romptr=0; + + temp = temp1 = GetTVPtrIndex(SiS_Pr) >> 1; /* 0: NTSC/YPbPr, 1: PAL, 2: HiTV */ + + if(ModeNo <= 0x13) + index = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].VB_StTVEdgeIndex; + else + index = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].VB_ExtTVEdgeIndex; + + if(SiS_Pr->SiS_UseROM && (!(SiS_Pr->SiS_ROMNew))) { + if(SiS_Pr->ChipType >= SIS_661) { + romptr = SISGETROMW(0x26c); + if(SiS_Pr->ChipType >= SIS_760) { + romptr = SISGETROMW(0x36c); + } + temp1 = GetOEMTVPtr661(SiS_Pr); + temp1 >>= 1; + } else if(SiS_Pr->ChipType >= SIS_330) { + romptr = SISGETROMW(0x1a4); + } else { + romptr = SISGETROMW(0x124); + } + } + + if(romptr) { + temp1 <<= 1; + temp = ROMAddr[romptr + temp1 + index]; + } else { + temp = SiS310_TVEdge1[temp][index]; + } + temp <<= 5; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x3A,0x1F,temp); /* index 0A D[7:5] */ +} + +static void +SetYFilter(struct SiS_Private *SiS_Pr, unsigned short ModeNo,unsigned short ModeIdIndex) +{ + unsigned short index, temp, i, j; + + if(ModeNo <= 0x13) { + index = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].VB_StTVYFilterIndex; + } else { + index = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].VB_ExtTVYFilterIndex; + } + + temp = GetTVPtrIndex(SiS_Pr) >> 1; /* 0: NTSC/YPbPr, 1: PAL, 2: HiTV */ + + if(SiS_Pr->SiS_TVMode & TVSetNTSCJ) temp = 1; /* NTSC-J uses PAL */ + else if(SiS_Pr->SiS_TVMode & TVSetPALM) temp = 3; /* PAL-M */ + else if(SiS_Pr->SiS_TVMode & TVSetPALN) temp = 4; /* PAL-N */ + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) temp = 1; /* HiVision uses PAL */ + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + for(i=0x35, j=0; i<=0x38; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS310_TVYFilter2[temp][index][j]); + } + for(i=0x48; i<=0x4A; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS310_TVYFilter2[temp][index][j]); + } + } else { + for(i=0x35, j=0; i<=0x38; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS310_TVYFilter1[temp][index][j]); + } + } +} + +static void +SetPhaseIncr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp,i,j,resinfo,romptr=0; + unsigned int lindex; + + if(!(SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) return; + + /* NTSC-J data not in BIOS, and already set in SetGroup2 */ + if(SiS_Pr->SiS_TVMode & TVSetNTSCJ) return; + + if((SiS_Pr->ChipType >= SIS_661) || SiS_Pr->SiS_ROMNew) { + lindex = GetOEMTVPtr661_2_OLD(SiS_Pr) & 0xffff; + lindex <<= 2; + for(j=0, i=0x31; i<=0x34; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS_TVPhase[lindex + j]); + } + return; + } + + /* PAL-M, PAL-N not in BIOS, and already set in SetGroup2 */ + if(SiS_Pr->SiS_TVMode & (TVSetPALM | TVSetPALN)) return; + + if(ModeNo<=0x13) { + resinfo = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ResInfo; + } else { + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + } + + temp = GetTVPtrIndex(SiS_Pr); + /* 0: NTSC Graphics, 1: NTSC Text, 2: PAL Graphics, + * 3: PAL Text, 4: HiTV Graphics 5: HiTV Text + */ + if(SiS_Pr->SiS_UseROM) { + romptr = SISGETROMW(0x116); + if(SiS_Pr->ChipType >= SIS_330) { + romptr = SISGETROMW(0x196); + } + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + romptr = SISGETROMW(0x11c); + if(SiS_Pr->ChipType >= SIS_330) { + romptr = SISGETROMW(0x19c); + } + if((SiS_Pr->SiS_VBInfo & SetInSlaveMode) && (!(SiS_Pr->SiS_TVMode & TVSetTVSimuMode))) { + romptr = SISGETROMW(0x116); + if(SiS_Pr->ChipType >= SIS_330) { + romptr = SISGETROMW(0x196); + } + } + } + } + if(romptr) { + romptr += (temp << 2); + for(j=0, i=0x31; i<=0x34; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,ROMAddr[romptr + j]); + } + } else { + index = temp % 2; + temp >>= 1; /* 0:NTSC, 1:PAL, 2:HiTV */ + for(j=0, i=0x31; i<=0x34; i++, j++) { + if(!(SiS_Pr->SiS_VBType & VB_SIS30xBLV)) + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS310_TVPhaseIncr1[temp][index][j]); + else if((!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) || (SiS_Pr->SiS_TVMode & TVSetTVSimuMode)) + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS310_TVPhaseIncr2[temp][index][j]); + else + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS310_TVPhaseIncr1[temp][index][j]); + } + } + + if((SiS_Pr->SiS_VBType & VB_SIS30xBLV) && (!(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision))) { + if((!(SiS_Pr->SiS_TVMode & (TVSetPAL | TVSetYPbPr525p | TVSetYPbPr750p))) && (ModeNo > 0x13)) { + if((resinfo == SIS_RI_640x480) || + (resinfo == SIS_RI_800x600)) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x31,0x21); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x32,0xf0); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x33,0xf5); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x34,0x7f); + } else if(resinfo == SIS_RI_1024x768) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x31,0x1e); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x32,0x8b); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x33,0xfb); + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x34,0x7b); + } + } + } +} + +static void +SetDelayComp661(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RTI) +{ + unsigned short delay = 0, romptr = 0, index, lcdpdcindex; + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + + if(!(SiS_Pr->SiS_VBInfo & (SetCRT2ToTV | SetCRT2ToLCD | SetCRT2ToLCDA | SetCRT2ToRAMDAC))) + return; + + /* 1. New ROM: VGA2 and LCD/LCDA-Pass1:1 */ + /* (If a custom mode is used, Pass1:1 is always set; hence we do this:) */ + + if(SiS_Pr->SiS_ROMNew) { + if((SiS_Pr->SiS_VBInfo & SetCRT2ToRAMDAC) || + ((SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) && + (SiS_Pr->SiS_LCDInfo & LCDPass11))) { + index = 25; + if(SiS_Pr->UseCustomMode) { + index = SiS_Pr->CSRClock; + } else if(ModeNo > 0x13) { + index = SiS_GetVCLK2Ptr(SiS_Pr,ModeNo,ModeIdIndex,RTI); + index = SiS_Pr->SiS_VCLKData[index].CLOCK; + } + if(index < 25) index = 25; + index = ((index / 25) - 1) << 1; + if((ROMAddr[0x5b] & 0x80) || (SiS_Pr->SiS_VBInfo & (SetCRT2ToRAMDAC | SetCRT2ToLCD))) { + index++; + } + romptr = SISGETROMW(0x104); + delay = ROMAddr[romptr + index]; + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToRAMDAC | SetCRT2ToLCD)) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0xf0,((delay >> 1) & 0x0f)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x35,0x7f,((delay & 0x01) << 7)); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0x0f,((delay << 3) & 0xf0)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x20,0xbf,((delay & 0x01) << 6)); + } + return; + } + } + + /* 2. Old ROM: VGA2 and LCD/LCDA-Pass 1:1 */ + + if(SiS_Pr->UseCustomMode) delay = 0x04; + else if(ModeNo <= 0x13) delay = 0x04; + else delay = (SiS_Pr->SiS_RefIndex[RTI].Ext_PDC >> 4); + delay |= (delay << 8); + + if(SiS_Pr->ChipType >= XGI_20) { + + delay = 0x0606; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + + delay = 0x0404; + if(SiS_Pr->SiS_XGIROM) { + index = GetTVPtrIndex(SiS_Pr); + if((romptr = SISGETROMW(0x35e))) { + delay = (ROMAddr[romptr + index] & 0x0f) << 1; + delay |= (delay << 8); + } + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) { + if(SiS_Pr->ChipType == XGI_40 && SiS_Pr->ChipRevision == 0x02) { + delay -= 0x0404; + } + } + } + + } else if(SiS_Pr->ChipType >= SIS_340) { + + delay = 0x0606; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + delay = 0x0404; + } + /* TODO (eventually) */ + + } else if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + + /* 3. TV */ + + index = GetOEMTVPtr661(SiS_Pr); + if(SiS_Pr->SiS_ROMNew) { + romptr = SISGETROMW(0x106); + if(SiS_Pr->SiS_VBType & VB_UMC) romptr += 12; + delay = ROMAddr[romptr + index]; + } else { + delay = 0x04; + if(index > 3) delay = 0; + } + + } else if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + + /* 4. LCD, LCDA (for new ROM only LV and non-Pass 1:1) */ + + if( (SiS_Pr->SiS_LCDResInfo != Panel_Custom) && + ((romptr = GetLCDStructPtr661_2(SiS_Pr))) ) { + + lcdpdcindex = (SiS_Pr->SiS_VBType & VB_UMC) ? 14 : 12; + + /* For LVDS (and sometimes TMDS), the BIOS must know about the correct value */ + delay = ROMAddr[romptr + lcdpdcindex + 1]; /* LCD */ + delay |= (ROMAddr[romptr + lcdpdcindex] << 8); /* LCDA */ + + } else { + + /* TMDS: Set our own, since BIOS has no idea */ + /* (This is done on >=661 only, since <661 is calling this only for LVDS) */ + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + switch(SiS_Pr->SiS_LCDResInfo) { + case Panel_1024x768: delay = 0x0008; break; + case Panel_1280x720: delay = 0x0004; break; + case Panel_1280x768: + case Panel_1280x768_2:delay = 0x0004; break; + case Panel_1280x800: + case Panel_1280x800_2:delay = 0x0004; break; /* Verified for 1280x800 */ + case Panel_1280x854: delay = 0x0004; break; /* FIXME */ + case Panel_1280x1024: delay = 0x1e04; break; + case Panel_1400x1050: delay = 0x0004; break; + case Panel_1600x1200: delay = 0x0400; break; + case Panel_1680x1050: delay = 0x0e04; break; + default: + if((SiS_Pr->PanelXRes <= 1024) && (SiS_Pr->PanelYRes <= 768)) { + delay = 0x0008; + } else if((SiS_Pr->PanelXRes == 1280) && (SiS_Pr->PanelYRes == 1024)) { + delay = 0x1e04; + } else if((SiS_Pr->PanelXRes <= 1400) && (SiS_Pr->PanelYRes <= 1050)) { + delay = 0x0004; + } else if((SiS_Pr->PanelXRes <= 1600) && (SiS_Pr->PanelYRes <= 1200)) { + delay = 0x0400; + } else + delay = 0x0e04; + break; + } + } + + /* Override by detected or user-set values */ + /* (but only if, for some reason, we can't read value from BIOS) */ + if((SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) && (SiS_Pr->PDC != -1)) { + delay = SiS_Pr->PDC & 0x1f; + } + if((SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) && (SiS_Pr->PDCA != -1)) { + delay = (SiS_Pr->PDCA & 0x1f) << 8; + } + + } + + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + delay >>= 8; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0x0f,((delay << 3) & 0xf0)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x20,0xbf,((delay & 0x01) << 6)); + } else { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x2d,0xf0,((delay >> 1) & 0x0f)); + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x35,0x7f,((delay & 0x01) << 7)); + } +} + +static void +SetCRT2SyncDither661(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short RTI) +{ + unsigned short infoflag; + unsigned char temp; + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + + if(ModeNo <= 0x13) { + infoflag = SiS_GetRegByte(SiS_Pr->SiS_P3ca+2); + } else if(SiS_Pr->UseCustomMode) { + infoflag = SiS_Pr->CInfoFlag; + } else { + infoflag = SiS_Pr->SiS_RefIndex[RTI].Ext_InfoFlag; + } + + if(!(SiS_Pr->SiS_LCDInfo & LCDPass11)) { + infoflag = SiS_GetReg(SiS_Pr->SiS_P3d4,0x37); /* No longer check D5 */ + } + + infoflag &= 0xc0; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + temp = (infoflag >> 6) | 0x0c; + if(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit) { + temp ^= 0x04; + if(SiS_Pr->SiS_ModeType >= Mode24Bpp) temp |= 0x10; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x1a,0xe0,temp); + } else { + temp = 0x30; + if(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit) temp = 0x20; + temp |= infoflag; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x19,0x0f,temp); + temp = 0; + if(SiS_Pr->SiS_LCDInfo & LCDRGB18Bit) { + if(SiS_Pr->SiS_ModeType >= Mode24Bpp) temp |= 0x80; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x1a,0x7f,temp); + } + + } +} + +static void +SetPanelParms661(struct SiS_Private *SiS_Pr) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short romptr, temp1, temp2; + + if(SiS_Pr->SiS_VBType & (VB_SISLVDS | VB_SIS30xC)) { + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x24,0x0f); + } + + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + if(SiS_Pr->LVDSHL != -1) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x24,0xfc,SiS_Pr->LVDSHL); + } + } + + if(SiS_Pr->SiS_ROMNew) { + + if((romptr = GetLCDStructPtr661_2(SiS_Pr))) { + if(SiS_Pr->SiS_VBType & VB_SISLVDS) { + temp1 = (ROMAddr[romptr] & 0x03) | 0x0c; + temp2 = 0xfc; + if(SiS_Pr->LVDSHL != -1) { + temp1 &= 0xfc; + temp2 = 0xf3; + } + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x24,temp2,temp1); + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + temp1 = (ROMAddr[romptr + 1] & 0x80) >> 1; + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x0d,0xbf,temp1); + } + } + + } +} + +static void +SiS_OEM310Setting(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, unsigned short RRTI) +{ + if((SiS_Pr->SiS_ROMNew) && (SiS_Pr->SiS_VBType & VB_SISLVDS)) { + SetDelayComp661(SiS_Pr, ModeNo, ModeIdIndex, RRTI); + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + SetCRT2SyncDither661(SiS_Pr, ModeNo, RRTI); + SetPanelParms661(SiS_Pr); + } + } else { + SetDelayComp(SiS_Pr,ModeNo); + } + + if((SiS_Pr->SiS_VBType & VB_SISVB) && (SiS_Pr->SiS_VBInfo & SetCRT2ToTV)) { + SetAntiFlicker(SiS_Pr,ModeNo,ModeIdIndex); + SetPhaseIncr(SiS_Pr,ModeNo,ModeIdIndex); + SetYFilter(SiS_Pr,ModeNo,ModeIdIndex); + if(SiS_Pr->SiS_VBType & VB_SIS301) { + SetEdgeEnhance(SiS_Pr,ModeNo,ModeIdIndex); + } + } +} + +static void +SiS_OEM661Setting(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short RRTI) +{ + if(SiS_Pr->SiS_VBType & VB_SISVB) { + + SetDelayComp661(SiS_Pr, ModeNo, ModeIdIndex, RRTI); + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + SetCRT2SyncDither661(SiS_Pr, ModeNo, RRTI); + SetPanelParms661(SiS_Pr); + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + SetPhaseIncr(SiS_Pr, ModeNo, ModeIdIndex); + SetYFilter(SiS_Pr, ModeNo, ModeIdIndex); + SetAntiFlicker(SiS_Pr, ModeNo, ModeIdIndex); + if(SiS_Pr->SiS_VBType & VB_SIS301) { + SetEdgeEnhance(SiS_Pr, ModeNo, ModeIdIndex); + } + } + } +} + +/* FinalizeLCD + * This finalizes some CRT2 registers for the very panel used. + * If we have a backup if these registers, we use it; otherwise + * we set the register according to most BIOSes. However, this + * function looks quite different in every BIOS, so you better + * pray that we have a backup... + */ +static void +SiS_FinalizeLCD(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned short tempcl,tempch,tempbl,tempbh,tempbx,tempax,temp; + unsigned short resinfo,modeflag; + + if(!(SiS_Pr->SiS_VBType & VB_SISLVDS)) return; + if(SiS_Pr->SiS_ROMNew) return; + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(SiS_Pr->LVDSHL != -1) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x24,0xfc,SiS_Pr->LVDSHL); + } + } + + if(SiS_Pr->SiS_LCDResInfo == Panel_Custom) return; + if(SiS_Pr->UseCustomMode) return; + + switch(SiS_Pr->SiS_CustomT) { + case CUT_COMPAQ1280: + case CUT_COMPAQ12802: + case CUT_CLEVO1400: + case CUT_CLEVO14002: + return; + } + + if(ModeNo <= 0x13) { + resinfo = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ResInfo; + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + } else { + resinfo = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_RESINFO; + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + } + + if(IS_SIS650) { + if(!(SiS_GetReg(SiS_Pr->SiS_P3d4, 0x5f) & 0xf0)) { + if(SiS_Pr->SiS_CustomT == CUT_CLEVO1024) { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1e,0x02); + } else { + SiS_SetRegOR(SiS_Pr->SiS_Part1Port,0x1e,0x03); + } + } + } + + if(SiS_Pr->SiS_CustomT == CUT_CLEVO1024) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + /* Maybe all panels? */ + if(SiS_Pr->LVDSHL == -1) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x24,0xfc,0x01); + } + return; + } + } + + if(SiS_Pr->SiS_CustomT == CUT_CLEVO10242) { + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(SiS_Pr->LVDSHL == -1) { + /* Maybe all panels? */ + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x24,0xfc,0x01); + } + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + tempch = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) >> 4; + if(tempch == 3) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x02); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,0x25); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1c,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1d,0x1b); + } + } + return; + } + } + } + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToLCD | SetCRT2ToLCDA)) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(SiS_Pr->SiS_VBType & VB_SISEMI) { + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x2a,0x00); +#ifdef SET_EMI + SiS_SetRegAND(SiS_Pr->SiS_Part4Port,0x30,0x0c); +#endif + SiS_SetReg(SiS_Pr->SiS_Part4Port,0x34,0x10); + } + } else if(SiS_Pr->SiS_LCDResInfo == Panel_1280x1024) { + if(SiS_Pr->LVDSHL == -1) { + /* Maybe ACER only? */ + SiS_SetRegANDOR(SiS_Pr->SiS_Part4Port,0x24,0xfc,0x01); + } + } + tempch = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) >> 4; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCDA) { + if(SiS_Pr->SiS_LCDResInfo == Panel_1400x1050) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1f,0x76); + } else if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(tempch == 0x03) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x02); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,0x25); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1c,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1d,0x1b); + } + if(SiS_Pr->Backup && (SiS_Pr->Backup_Mode == ModeNo)) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x14,SiS_Pr->Backup_14); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x15,SiS_Pr->Backup_15); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x16,SiS_Pr->Backup_16); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x17,SiS_Pr->Backup_17); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,SiS_Pr->Backup_18); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x19,SiS_Pr->Backup_19); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1a,SiS_Pr->Backup_1a); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,SiS_Pr->Backup_1b); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1c,SiS_Pr->Backup_1c); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1d,SiS_Pr->Backup_1d); + } else if(!(SiS_Pr->SiS_LCDInfo & DontExpandLCD)) { /* 1.10.8w */ + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x14,0x90); + if(ModeNo <= 0x13) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x11); + if((resinfo == 0) || (resinfo == 2)) return; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x18); + if((resinfo == 1) || (resinfo == 3)) return; + } + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x02); + if((ModeNo > 0x13) && (resinfo == SIS_RI_1024x768)) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x02); /* 1.10.7u */ +#if 0 + tempbx = 806; /* 0x326 */ /* other older BIOSes */ + tempbx--; + temp = tempbx & 0xff; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,temp); + temp = (tempbx >> 8) & 0x03; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x1d,0xf8,temp); +#endif + } + } else if(ModeNo <= 0x13) { + if(ModeNo <= 1) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x70); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x19,0xff); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,0x48); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1d,0x12); + } + if(!(modeflag & HalfDCLK)) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x14,0x20); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x15,0x1a); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x16,0x28); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x17,0x00); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x4c); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x19,0xdc); + if(ModeNo == 0x12) { + switch(tempch) { + case 0: + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x95); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x19,0xdc); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1a,0x10); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,0x95); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1c,0x48); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1d,0x12); + break; + case 2: + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,0x95); + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,0x48); + break; + case 3: + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x1b,0x95); + break; + } + } + } + } + } + } else { + tempcl = tempbh = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x01); + tempcl &= 0x0f; + tempbh &= 0x70; + tempbh >>= 4; + tempbl = SiS_GetReg(SiS_Pr->SiS_Part2Port,0x04); + tempbx = (tempbh << 8) | tempbl; + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if((resinfo == SIS_RI_1024x768) || (!(SiS_Pr->SiS_LCDInfo & DontExpandLCD))) { + if(SiS_Pr->SiS_SetFlag & LCDVESATiming) { + tempbx = 770; + } else { + if(tempbx > 770) tempbx = 770; + if(SiS_Pr->SiS_VGAVDE < 600) { + tempax = 768 - SiS_Pr->SiS_VGAVDE; + tempax >>= 4; /* 1.10.7w; 1.10.6s: 3; */ + if(SiS_Pr->SiS_VGAVDE <= 480) tempax >>= 4; /* 1.10.7w; 1.10.6s: < 480; >>=1; */ + tempbx -= tempax; + } + } + } else return; + } + temp = tempbx & 0xff; + SiS_SetReg(SiS_Pr->SiS_Part2Port,0x04,temp); + temp = ((tempbx & 0xff00) >> 4) | tempcl; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x01,0x80,temp); + } + } +} + +#endif + +/* ================= SiS 300 O.E.M. ================== */ + +#ifdef CONFIG_FB_SIS_300 + +static void +SetOEMLCDData2(struct SiS_Private *SiS_Pr, unsigned short ModeNo,unsigned short ModeIdIndex, + unsigned short RefTabIndex) +{ + unsigned short crt2crtc=0, modeflag, myindex=0; + unsigned char temp; + int i; + + if(ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + crt2crtc = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_CRT2CRTC; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + crt2crtc = SiS_Pr->SiS_RefIndex[RefTabIndex].Ext_CRT2CRTC; + } + + crt2crtc &= 0x3f; + + if(SiS_Pr->SiS_CustomT == CUT_BARCO1024) { + SiS_SetRegAND(SiS_Pr->SiS_Part1Port,0x13,0xdf); + } + + if(SiS_Pr->SiS_CustomT == CUT_BARCO1366) { + if(modeflag & HalfDCLK) myindex = 1; + + if(SiS_Pr->SiS_SetFlag & LowModeTests) { + for(i=0; i<7; i++) { + if(barco_p1[myindex][crt2crtc][i][0]) { + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port, + barco_p1[myindex][crt2crtc][i][0], + barco_p1[myindex][crt2crtc][i][2], + barco_p1[myindex][crt2crtc][i][1]); + } + } + } + temp = SiS_GetReg(SiS_Pr->SiS_Part1Port,0x00); + if(temp & 0x80) { + temp = SiS_GetReg(SiS_Pr->SiS_Part1Port,0x18); + temp++; + SiS_SetReg(SiS_Pr->SiS_Part1Port,0x18,temp); + } + } +} + +static unsigned short +GetOEMLCDPtr(struct SiS_Private *SiS_Pr, int Flag) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short tempbx=0,romptr=0; + static const unsigned char customtable300[] = { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff + }; + static const unsigned char customtable630[] = { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff + }; + + if(SiS_Pr->ChipType == SIS_300) { + + tempbx = SiS_GetReg(SiS_Pr->SiS_P3d4,0x36) & 0x0f; + if(SiS_Pr->SiS_VBType & VB_SIS301) tempbx &= 0x07; + tempbx -= 2; + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) tempbx += 4; + if(SiS_Pr->SiS_LCDResInfo == Panel_1024x768) { + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) tempbx += 3; + } + if(SiS_Pr->SiS_UseROM) { + if(ROMAddr[0x235] & 0x80) { + tempbx = SiS_Pr->SiS_LCDTypeInfo; + if(Flag) { + romptr = SISGETROMW(0x255); + if(romptr) tempbx = ROMAddr[romptr + SiS_Pr->SiS_LCDTypeInfo]; + else tempbx = customtable300[SiS_Pr->SiS_LCDTypeInfo]; + if(tempbx == 0xFF) return 0xFFFF; + } + tempbx <<= 1; + if(!(SiS_Pr->SiS_SetFlag & LCDVESATiming)) tempbx++; + } + } + + } else { + + if(Flag) { + if(SiS_Pr->SiS_UseROM) { + romptr = SISGETROMW(0x255); + if(romptr) tempbx = ROMAddr[romptr + SiS_Pr->SiS_LCDTypeInfo]; + else tempbx = 0xff; + } else { + tempbx = customtable630[SiS_Pr->SiS_LCDTypeInfo]; + } + if(tempbx == 0xFF) return 0xFFFF; + tempbx <<= 2; + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) tempbx += 2; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) tempbx++; + return tempbx; + } + tempbx = SiS_Pr->SiS_LCDTypeInfo << 2; + if(SiS_Pr->SiS_VBInfo & SetInSlaveMode) tempbx += 2; + if(SiS_Pr->SiS_LCDInfo & DontExpandLCD) tempbx++; + + } + + return tempbx; +} + +static void +SetOEMLCDDelay(struct SiS_Private *SiS_Pr, unsigned short ModeNo,unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp,romptr=0; + + if(SiS_Pr->SiS_LCDResInfo == Panel_Custom) return; + + if(SiS_Pr->SiS_UseROM) { + if(!(ROMAddr[0x237] & 0x01)) return; + if(!(ROMAddr[0x237] & 0x02)) return; + romptr = SISGETROMW(0x24b); + } + + /* The Panel Compensation Delay should be set according to tables + * here. Unfortunately, various BIOS versions don't care about + * a uniform way using eg. ROM byte 0x220, but use different + * hard coded delays (0x04, 0x20, 0x18) in SetGroup1(). + * Thus we don't set this if the user selected a custom pdc or if + * we otherwise detected a valid pdc. + */ + if(SiS_Pr->PDC != -1) return; + + temp = GetOEMLCDPtr(SiS_Pr, 0); + + if(SiS_Pr->UseCustomMode) + index = 0; + else + index = SiS_Pr->SiS_VBModeIDTable[ModeIdIndex].VB_LCDDelayIndex; + + if(SiS_Pr->ChipType != SIS_300) { + if(romptr) { + romptr += (temp * 2); + romptr = SISGETROMW(romptr); + romptr += index; + temp = ROMAddr[romptr]; + } else { + if(SiS_Pr->SiS_VBType & VB_SISVB) { + temp = SiS300_OEMLCDDelay2[temp][index]; + } else { + temp = SiS300_OEMLCDDelay3[temp][index]; + } + } + } else { + if(SiS_Pr->SiS_UseROM && (ROMAddr[0x235] & 0x80)) { + if(romptr) { + romptr += (temp * 2); + romptr = SISGETROMW(romptr); + romptr += index; + temp = ROMAddr[romptr]; + } else { + temp = SiS300_OEMLCDDelay5[temp][index]; + } + } else { + if(SiS_Pr->SiS_UseROM) { + romptr = ROMAddr[0x249] | (ROMAddr[0x24a] << 8); + if(romptr) { + romptr += (temp * 2); + romptr = SISGETROMW(romptr); + romptr += index; + temp = ROMAddr[romptr]; + } else { + temp = SiS300_OEMLCDDelay4[temp][index]; + } + } else { + temp = SiS300_OEMLCDDelay4[temp][index]; + } + } + } + temp &= 0x3c; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,~0x3C,temp); /* index 0A D[6:4] */ +} + +static void +SetOEMLCDData(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ +#if 0 /* Unfinished; Data table missing */ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp; + + if((SiS_Pr->SiS_UseROM) { + if(!(ROMAddr[0x237] & 0x01)) return; + if(!(ROMAddr[0x237] & 0x04)) return; + /* No rom pointer in BIOS header! */ + } + + temp = GetOEMLCDPtr(SiS_Pr, 1); + if(temp == 0xFFFF) return; + + index = SiS_Pr->SiS_VBModeIDTable[ModeIdIndex]._VB_LCDHIndex; + for(i=0x14, j=0; i<=0x17; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,i,SiS300_LCDHData[temp][index][j]); + } + SiS_SetRegANDOR(SiS_SiS_Part1Port,0x1a, 0xf8, (SiS300_LCDHData[temp][index][j] & 0x07)); + + index = SiS_Pr->SiS_VBModeIDTable[ModeIdIndex]._VB_LCDVIndex; + SiS_SetReg(SiS_SiS_Part1Port,0x18, SiS300_LCDVData[temp][index][0]); + SiS_SetRegANDOR(SiS_SiS_Part1Port,0x19, 0xF0, SiS300_LCDVData[temp][index][1]); + SiS_SetRegANDOR(SiS_SiS_Part1Port,0x1A, 0xC7, (SiS300_LCDVData[temp][index][2] & 0x38)); + for(i=0x1b, j=3; i<=0x1d; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part1Port,i,SiS300_LCDVData[temp][index][j]); + } +#endif +} + +static unsigned short +GetOEMTVPtr(struct SiS_Private *SiS_Pr) +{ + unsigned short index; + + index = 0; + if(!(SiS_Pr->SiS_VBInfo & SetInSlaveMode)) index += 4; + if(SiS_Pr->SiS_VBType & VB_SISVB) { + if(SiS_Pr->SiS_VBInfo & SetCRT2ToSCART) index += 2; + else if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) index += 3; + else if(SiS_Pr->SiS_TVMode & TVSetPAL) index += 1; + } else { + if(SiS_Pr->SiS_TVMode & TVSetCHOverScan) index += 2; + if(SiS_Pr->SiS_TVMode & TVSetPAL) index += 1; + } + return index; +} + +static void +SetOEMTVDelay(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp,romptr=0; + + if(SiS_Pr->SiS_UseROM) { + if(!(ROMAddr[0x238] & 0x01)) return; + if(!(ROMAddr[0x238] & 0x02)) return; + romptr = SISGETROMW(0x241); + } + + temp = GetOEMTVPtr(SiS_Pr); + + index = SiS_Pr->SiS_VBModeIDTable[ModeIdIndex].VB_TVDelayIndex; + + if(romptr) { + romptr += (temp * 2); + romptr = SISGETROMW(romptr); + romptr += index; + temp = ROMAddr[romptr]; + } else { + if(SiS_Pr->SiS_VBType & VB_SISVB) { + temp = SiS300_OEMTVDelay301[temp][index]; + } else { + temp = SiS300_OEMTVDelayLVDS[temp][index]; + } + } + temp &= 0x3c; + SiS_SetRegANDOR(SiS_Pr->SiS_Part1Port,0x13,~0x3C,temp); +} + +static void +SetOEMAntiFlicker(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp,romptr=0; + + if(SiS_Pr->SiS_UseROM) { + if(!(ROMAddr[0x238] & 0x01)) return; + if(!(ROMAddr[0x238] & 0x04)) return; + romptr = SISGETROMW(0x243); + } + + temp = GetOEMTVPtr(SiS_Pr); + + index = SiS_Pr->SiS_VBModeIDTable[ModeIdIndex].VB_TVFlickerIndex; + + if(romptr) { + romptr += (temp * 2); + romptr = SISGETROMW(romptr); + romptr += index; + temp = ROMAddr[romptr]; + } else { + temp = SiS300_OEMTVFlicker[temp][index]; + } + temp &= 0x70; + SiS_SetRegANDOR(SiS_Pr->SiS_Part2Port,0x0A,0x8F,temp); +} + +static void +SetOEMPhaseIncr(struct SiS_Private *SiS_Pr, unsigned short ModeNo,unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,i,j,temp,romptr=0; + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToHiVision) return; + + if(SiS_Pr->SiS_TVMode & (TVSetNTSC1024 | TVSetNTSCJ | TVSetPALM | TVSetPALN)) return; + + if(SiS_Pr->SiS_UseROM) { + if(!(ROMAddr[0x238] & 0x01)) return; + if(!(ROMAddr[0x238] & 0x08)) return; + romptr = SISGETROMW(0x245); + } + + temp = GetOEMTVPtr(SiS_Pr); + + index = SiS_Pr->SiS_VBModeIDTable[ModeIdIndex].VB_TVPhaseIndex; + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + for(i=0x31, j=0; i<=0x34; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS300_Phase2[temp][index][j]); + } + } else { + if(romptr) { + romptr += (temp * 2); + romptr = SISGETROMW(romptr); + romptr += (index * 4); + for(i=0x31, j=0; i<=0x34; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,ROMAddr[romptr + j]); + } + } else { + for(i=0x31, j=0; i<=0x34; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS300_Phase1[temp][index][j]); + } + } + } +} + +static void +SetOEMYFilter(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex) +{ + unsigned char *ROMAddr = SiS_Pr->VirtualRomBase; + unsigned short index,temp,i,j,romptr=0; + + if(SiS_Pr->SiS_VBInfo & (SetCRT2ToSCART | SetCRT2ToHiVision | SetCRT2ToYPbPr525750)) return; + + if(SiS_Pr->SiS_UseROM) { + if(!(ROMAddr[0x238] & 0x01)) return; + if(!(ROMAddr[0x238] & 0x10)) return; + romptr = SISGETROMW(0x247); + } + + temp = GetOEMTVPtr(SiS_Pr); + + if(SiS_Pr->SiS_TVMode & TVSetPALM) temp = 8; + else if(SiS_Pr->SiS_TVMode & TVSetPALN) temp = 9; + /* NTSCJ uses NTSC filters */ + + index = SiS_Pr->SiS_VBModeIDTable[ModeIdIndex].VB_TVYFilterIndex; + + if(SiS_Pr->SiS_VBType & VB_SIS30xBLV) { + for(i=0x35, j=0; i<=0x38; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS300_Filter2[temp][index][j]); + } + for(i=0x48; i<=0x4A; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS300_Filter2[temp][index][j]); + } + } else { + if((romptr) && (!(SiS_Pr->SiS_TVMode & (TVSetPALM|TVSetPALN)))) { + romptr += (temp * 2); + romptr = SISGETROMW(romptr); + romptr += (index * 4); + for(i=0x35, j=0; i<=0x38; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,ROMAddr[romptr + j]); + } + } else { + for(i=0x35, j=0; i<=0x38; i++, j++) { + SiS_SetReg(SiS_Pr->SiS_Part2Port,i,SiS300_Filter1[temp][index][j]); + } + } + } +} + +static unsigned short +SiS_SearchVBModeID(struct SiS_Private *SiS_Pr, unsigned short *ModeNo) +{ + unsigned short ModeIdIndex; + unsigned char VGAINFO = SiS_Pr->SiS_VGAINFO; + + if(*ModeNo <= 5) *ModeNo |= 1; + + for(ModeIdIndex=0; ; ModeIdIndex++) { + if(SiS_Pr->SiS_VBModeIDTable[ModeIdIndex].ModeID == *ModeNo) break; + if(SiS_Pr->SiS_VBModeIDTable[ModeIdIndex].ModeID == 0xFF) return 0; + } + + if(*ModeNo != 0x07) { + if(*ModeNo > 0x03) return ModeIdIndex; + if(VGAINFO & 0x80) return ModeIdIndex; + ModeIdIndex++; + } + + if(VGAINFO & 0x10) ModeIdIndex++; /* 400 lines */ + /* else 350 lines */ + return ModeIdIndex; +} + +static void +SiS_OEM300Setting(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefTableIndex) +{ + unsigned short OEMModeIdIndex = 0; + + if(!SiS_Pr->UseCustomMode) { + OEMModeIdIndex = SiS_SearchVBModeID(SiS_Pr,&ModeNo); + if(!(OEMModeIdIndex)) return; + } + + if(SiS_Pr->SiS_VBInfo & SetCRT2ToLCD) { + SetOEMLCDDelay(SiS_Pr, ModeNo, OEMModeIdIndex); + if(SiS_Pr->SiS_IF_DEF_LVDS == 1) { + SetOEMLCDData(SiS_Pr, ModeNo, OEMModeIdIndex); + } + } + if(SiS_Pr->UseCustomMode) return; + if(SiS_Pr->SiS_VBInfo & SetCRT2ToTV) { + SetOEMTVDelay(SiS_Pr, ModeNo,OEMModeIdIndex); + if(SiS_Pr->SiS_VBType & VB_SISVB) { + SetOEMAntiFlicker(SiS_Pr, ModeNo, OEMModeIdIndex); + SetOEMPhaseIncr(SiS_Pr, ModeNo, OEMModeIdIndex); + SetOEMYFilter(SiS_Pr, ModeNo, OEMModeIdIndex); + } + } +} +#endif + diff --git a/drivers/video/fbdev/sis/init301.h b/drivers/video/fbdev/sis/init301.h new file mode 100644 index 000000000000..2112d6d7feda --- /dev/null +++ b/drivers/video/fbdev/sis/init301.h @@ -0,0 +1,456 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Data and prototypes for init301.c + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +#ifndef _INIT301_H_ +#define _INIT301_H_ + +#include "initdef.h" + +#include "vgatypes.h" +#include "vstruct.h" +#ifdef SIS_CP +#undef SIS_CP +#endif +#include <linux/types.h> +#include <asm/io.h> +#include <linux/fb.h> +#include "sis.h" +#include <video/sisfb.h> + +static const unsigned char SiS_YPbPrTable[3][64] = { + { + 0x17,0x1d,0x03,0x09,0x05,0x06,0x0c,0x0c, + 0x94,0x49,0x01,0x0a,0x06,0x0d,0x04,0x0a, + 0x06,0x14,0x0d,0x04,0x0a,0x00,0x85,0x1b, + 0x0c,0x50,0x00,0x97,0x00,0xda,0x4a,0x17, + 0x7d,0x05,0x4b,0x00,0x00,0xe2,0x00,0x02, + 0x03,0x0a,0x65,0x9d /*0x8d*/,0x08,0x92,0x8f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x53 /*0x50*/, + 0x00,0x40,0x44,0x00,0xdb,0x02,0x3b,0x00 + }, + { + 0x33,0x06,0x06,0x09,0x0b,0x0c,0x0c,0x0c, + 0x98,0x0a,0x01,0x0d,0x06,0x0d,0x04,0x0a, + 0x06,0x14,0x0d,0x04,0x0a,0x00,0x85,0x3f, + 0x0c,0x50,0xb2,0x9f,0x16,0x59,0x4f,0x13, + 0xad,0x11,0xad,0x1d,0x40,0x8a,0x3d,0xb8, + 0x51,0x5e,0x60,0x49,0x7d,0x92,0x0f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x4e, + 0x43,0x41,0x11,0x00,0xfc,0xff,0x32,0x00 + }, + { +#if 0 /* OK, but sticks to left edge */ + 0x13,0x1d,0xe8,0x09,0x09,0xed,0x0c,0x0c, + 0x98,0x0a,0x01,0x0c,0x06,0x0d,0x04,0x0a, + 0x06,0x14,0x0d,0x04,0x0a,0x00,0x85,0x3f, + 0xed,0x50,0x70,0x9f,0x16,0x59,0x21 /*0x2b*/,0x13, + 0x27,0x0b,0x27,0xfc,0x30,0x27,0x1c,0xb0, + 0x4b,0x4b,0x65 /*0x6f*/,0x2f,0x63,0x92,0x0f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x27, + 0x00,0x40,0x11,0x00,0xfc,0xff,0x32,0x00 +#endif +#if 1 /* Perfect */ + 0x23,0x2d,0xe8,0x09,0x09,0xed,0x0c,0x0c, + 0x98,0x0a,0x01,0x0c,0x06,0x0d,0x04,0x0a, + 0x06,0x14,0x0d,0x04,0x0a,0x00,0x85,0x3f, + 0xed,0x50,0x70,0x9f,0x16,0x59,0x60,0x13, + 0x27,0x0b,0x27,0xfc,0x30,0x27,0x1c,0xb0, + 0x4b,0x4b,0x6f,0x2f,0x63,0x92,0x0f,0x40, + 0x60,0x80,0x14,0x90,0x8c,0x60,0x14,0x73, + 0x00,0x40,0x11,0x00,0xfc,0xff,0x32,0x00 +#endif + } +}; + +static const unsigned char SiS_TVPhase[] = +{ + 0x21,0xED,0xBA,0x08, /* 0x00 SiS_NTSCPhase */ + 0x2A,0x05,0xE3,0x00, /* 0x01 SiS_PALPhase */ + 0x21,0xE4,0x2E,0x9B, /* 0x02 SiS_PALMPhase */ + 0x21,0xF4,0x3E,0xBA, /* 0x03 SiS_PALNPhase */ + 0x1E,0x8B,0xA2,0xA7, + 0x1E,0x83,0x0A,0xE0, /* 0x05 SiS_SpecialPhaseM */ + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x21,0xF0,0x7B,0xD6, /* 0x08 SiS_NTSCPhase2 */ + 0x2A,0x09,0x86,0xE9, /* 0x09 SiS_PALPhase2 */ + 0x21,0xE6,0xEF,0xA4, /* 0x0a SiS_PALMPhase2 */ + 0x21,0xF6,0x94,0x46, /* 0x0b SiS_PALNPhase2 */ + 0x1E,0x8B,0xA2,0xA7, + 0x1E,0x83,0x0A,0xE0, /* 0x0d SiS_SpecialPhaseM */ + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x1e,0x8c,0x5c,0x7a, /* 0x10 SiS_SpecialPhase */ + 0x25,0xd4,0xfd,0x5e /* 0x11 SiS_SpecialPhaseJ */ +}; + +static const unsigned char SiS_HiTVGroup3_1[] = { + 0x00, 0x14, 0x15, 0x25, 0x55, 0x15, 0x0b, 0x13, + 0xb1, 0x41, 0x62, 0x62, 0xff, 0xf4, 0x45, 0xa6, + 0x25, 0x2f, 0x67, 0xf6, 0xbf, 0xff, 0x8e, 0x20, + 0xac, 0xda, 0x60, 0xfe, 0x6a, 0x9a, 0x06, 0x10, + 0xd1, 0x04, 0x18, 0x0a, 0xff, 0x80, 0x00, 0x80, + 0x3b, 0x77, 0x00, 0xef, 0xe0, 0x10, 0xb0, 0xe0, + 0x10, 0x4f, 0x0f, 0x0f, 0x05, 0x0f, 0x08, 0x6e, + 0x1a, 0x1f, 0x25, 0x2a, 0x4c, 0xaa, 0x01 +}; + +static const unsigned char SiS_HiTVGroup3_2[] = { + 0x00, 0x14, 0x15, 0x25, 0x55, 0x15, 0x0b, 0x7a, + 0x54, 0x41, 0xe7, 0xe7, 0xff, 0xf4, 0x45, 0xa6, + 0x25, 0x2f, 0x67, 0xf6, 0xbf, 0xff, 0x8e, 0x20, + 0xac, 0x6a, 0x60, 0x2b, 0x52, 0xcd, 0x61, 0x10, + 0x51, 0x04, 0x18, 0x0a, 0x1f, 0x80, 0x00, 0x80, + 0xff, 0xa4, 0x04, 0x2b, 0x94, 0x21, 0x72, 0x94, + 0x26, 0x05, 0x01, 0x0f, 0xed, 0x0f, 0x0a, 0x64, + 0x18, 0x1d, 0x23, 0x28, 0x4c, 0xaa, 0x01 +}; + +/* 301C / 302ELV extended Part2 TV registers (4 tap scaler) */ + +static const unsigned char SiS_Part2CLVX_1[] = { + 0x00,0x00, + 0x00,0x20,0x00,0x00,0x7F,0x20,0x02,0x7F,0x7D,0x20,0x04,0x7F,0x7D,0x1F,0x06,0x7E, + 0x7C,0x1D,0x09,0x7E,0x7C,0x1B,0x0B,0x7E,0x7C,0x19,0x0E,0x7D,0x7C,0x17,0x11,0x7C, + 0x7C,0x14,0x14,0x7C,0x7C,0x11,0x17,0x7C,0x7D,0x0E,0x19,0x7C,0x7E,0x0B,0x1B,0x7C, + 0x7E,0x09,0x1D,0x7C,0x7F,0x06,0x1F,0x7C,0x7F,0x04,0x20,0x7D,0x00,0x02,0x20,0x7E +}; + +static const unsigned char SiS_Part2CLVX_2[] = { + 0x00,0x00, + 0x00,0x20,0x00,0x00,0x7F,0x20,0x02,0x7F,0x7D,0x20,0x04,0x7F,0x7D,0x1F,0x06,0x7E, + 0x7C,0x1D,0x09,0x7E,0x7C,0x1B,0x0B,0x7E,0x7C,0x19,0x0E,0x7D,0x7C,0x17,0x11,0x7C, + 0x7C,0x14,0x14,0x7C,0x7C,0x11,0x17,0x7C,0x7D,0x0E,0x19,0x7C,0x7E,0x0B,0x1B,0x7C, + 0x7E,0x09,0x1D,0x7C,0x7F,0x06,0x1F,0x7C,0x7F,0x04,0x20,0x7D,0x00,0x02,0x20,0x7E +}; + +static const unsigned char SiS_Part2CLVX_3[] = { /* NTSC, 525i, 525p */ + 0xE0,0x01, + 0x04,0x1A,0x04,0x7E,0x03,0x1A,0x06,0x7D,0x01,0x1A,0x08,0x7D,0x00,0x19,0x0A,0x7D, + 0x7F,0x19,0x0C,0x7C,0x7E,0x18,0x0E,0x7C,0x7E,0x17,0x10,0x7B,0x7D,0x15,0x12,0x7C, + 0x7D,0x13,0x13,0x7D,0x7C,0x12,0x15,0x7D,0x7C,0x10,0x17,0x7D,0x7C,0x0E,0x18,0x7E, + 0x7D,0x0C,0x19,0x7E,0x7D,0x0A,0x19,0x00,0x7D,0x08,0x1A,0x01,0x7E,0x06,0x1A,0x02, + 0x58,0x02, + 0x07,0x14,0x07,0x7E,0x06,0x14,0x09,0x7D,0x05,0x14,0x0A,0x7D,0x04,0x13,0x0B,0x7E, + 0x03,0x13,0x0C,0x7E,0x02,0x12,0x0D,0x7F,0x01,0x12,0x0E,0x7F,0x01,0x11,0x0F,0x7F, + 0x00,0x10,0x10,0x00,0x7F,0x0F,0x11,0x01,0x7F,0x0E,0x12,0x01,0x7E,0x0D,0x12,0x03, + 0x7E,0x0C,0x13,0x03,0x7E,0x0B,0x13,0x04,0x7E,0x0A,0x14,0x04,0x7D,0x09,0x14,0x06, + 0x00,0x03, + 0x09,0x0F,0x09,0x7F,0x08,0x0F,0x09,0x00,0x07,0x0F,0x0A,0x00,0x06,0x0F,0x0A,0x01, + 0x06,0x0E,0x0B,0x01,0x05,0x0E,0x0B,0x02,0x04,0x0E,0x0C,0x02,0x04,0x0D,0x0C,0x03, + 0x03,0x0D,0x0D,0x03,0x02,0x0C,0x0D,0x05,0x02,0x0C,0x0E,0x04,0x01,0x0B,0x0E,0x06, + 0x01,0x0B,0x0E,0x06,0x00,0x0A,0x0F,0x07,0x00,0x0A,0x0F,0x07,0x00,0x09,0x0F,0x08, + 0xFF,0xFF +}; + +static const unsigned char SiS_Part2CLVX_4[] = { /* PAL */ + 0x58,0x02, + 0x05,0x19,0x05,0x7D,0x03,0x19,0x06,0x7E,0x02,0x19,0x08,0x7D,0x01,0x18,0x0A,0x7D, + 0x00,0x18,0x0C,0x7C,0x7F,0x17,0x0E,0x7C,0x7E,0x16,0x0F,0x7D,0x7E,0x14,0x11,0x7D, + 0x7D,0x13,0x13,0x7D,0x7D,0x11,0x14,0x7E,0x7D,0x0F,0x16,0x7E,0x7D,0x0E,0x17,0x7E, + 0x7D,0x0C,0x18,0x7F,0x7D,0x0A,0x18,0x01,0x7D,0x08,0x19,0x02,0x7D,0x06,0x19,0x04, + 0x00,0x03, + 0x08,0x12,0x08,0x7E,0x07,0x12,0x09,0x7E,0x06,0x12,0x0A,0x7E,0x05,0x11,0x0B,0x7F, + 0x04,0x11,0x0C,0x7F,0x03,0x11,0x0C,0x00,0x03,0x10,0x0D,0x00,0x02,0x0F,0x0E,0x01, + 0x01,0x0F,0x0F,0x01,0x01,0x0E,0x0F,0x02,0x00,0x0D,0x10,0x03,0x7F,0x0C,0x11,0x04, + 0x7F,0x0C,0x11,0x04,0x7F,0x0B,0x11,0x05,0x7E,0x0A,0x12,0x06,0x7E,0x09,0x12,0x07, + 0x40,0x02, + 0x04,0x1A,0x04,0x7E,0x02,0x1B,0x05,0x7E,0x01,0x1A,0x07,0x7E,0x00,0x1A,0x09,0x7D, + 0x7F,0x19,0x0B,0x7D,0x7E,0x18,0x0D,0x7D,0x7D,0x17,0x10,0x7C,0x7D,0x15,0x12,0x7C, + 0x7C,0x14,0x14,0x7C,0x7C,0x12,0x15,0x7D,0x7C,0x10,0x17,0x7D,0x7C,0x0D,0x18,0x7F, + 0x7D,0x0B,0x19,0x7F,0x7D,0x09,0x1A,0x00,0x7D,0x07,0x1A,0x02,0x7E,0x05,0x1B,0x02, + 0xFF,0xFF +}; + +static const unsigned char SiS_Part2CLVX_5[] = { /* 750p */ + 0x00,0x03, + 0x05,0x19,0x05,0x7D,0x03,0x19,0x06,0x7E,0x02,0x19,0x08,0x7D,0x01,0x18,0x0A,0x7D, + 0x00,0x18,0x0C,0x7C,0x7F,0x17,0x0E,0x7C,0x7E,0x16,0x0F,0x7D,0x7E,0x14,0x11,0x7D, + 0x7D,0x13,0x13,0x7D,0x7D,0x11,0x14,0x7E,0x7D,0x0F,0x16,0x7E,0x7D,0x0E,0x17,0x7E, + 0x7D,0x0C,0x18,0x7F,0x7D,0x0A,0x18,0x01,0x7D,0x08,0x19,0x02,0x7D,0x06,0x19,0x04, + 0xFF,0xFF +}; + +static const unsigned char SiS_Part2CLVX_6[] = { /* 1080i */ + 0x00,0x04, + 0x04,0x1A,0x04,0x7E,0x02,0x1B,0x05,0x7E,0x01,0x1A,0x07,0x7E,0x00,0x1A,0x09,0x7D, + 0x7F,0x19,0x0B,0x7D,0x7E,0x18,0x0D,0x7D,0x7D,0x17,0x10,0x7C,0x7D,0x15,0x12,0x7C, + 0x7C,0x14,0x14,0x7C,0x7C,0x12,0x15,0x7D,0x7C,0x10,0x17,0x7D,0x7C,0x0D,0x18,0x7F, + 0x7D,0x0B,0x19,0x7F,0x7D,0x09,0x1A,0x00,0x7D,0x07,0x1A,0x02,0x7E,0x05,0x1B,0x02, + 0xFF,0xFF, +}; + +#ifdef CONFIG_FB_SIS_315 +/* 661 et al LCD data structure (2.03.00) */ +static const unsigned char SiS_LCDStruct661[] = { + /* 1024x768 */ +/* type|CR37| HDE | VDE | HT | VT | hss | hse */ + 0x02,0xC0,0x00,0x04,0x00,0x03,0x40,0x05,0x26,0x03,0x10,0x00,0x88, + 0x00,0x02,0x00,0x06,0x00,0x41,0x5A,0x64,0x00,0x00,0x00,0x00,0x04, + /* | vss | vse |clck| clock |CRT2DataP|CRT2DataP|idx */ + /* VESA non-VESA noscale */ + /* 1280x1024 */ + 0x03,0xC0,0x00,0x05,0x00,0x04,0x98,0x06,0x2A,0x04,0x30,0x00,0x70, + 0x00,0x01,0x00,0x03,0x00,0x6C,0xF8,0x2F,0x00,0x00,0x00,0x00,0x08, + /* 1400x1050 */ + 0x09,0x20,0x78,0x05,0x1A,0x04,0x98,0x06,0x2A,0x04,0x18,0x00,0x38, + 0x00,0x01,0x00,0x03,0x00,0x6C,0xF8,0x2F,0x00,0x00,0x00,0x00,0x09, + /* 1600x1200 */ + 0x0B,0xE0,0x40,0x06,0xB0,0x04,0x70,0x08,0xE2,0x04,0x40,0x00,0xC0, + 0x00,0x01,0x00,0x03,0x00,0xA2,0x70,0x24,0x00,0x00,0x00,0x00,0x0A, + /* 1280x768 (_2) */ + 0x0A,0xE0,0x00,0x05,0x00,0x03,0x7C,0x06,0x26,0x03,0x30,0x00,0x70, + 0x00,0x03,0x00,0x06,0x00,0x4D,0xC8,0x48,0x00,0x00,0x00,0x00,0x06, + /* 1280x720 */ + 0x0E,0xE0,0x00,0x05,0xD0,0x02,0x80,0x05,0x26,0x03,0x10,0x00,0x20, + 0x00,0x01,0x00,0x06,0x00,0x45,0x9C,0x62,0x00,0x00,0x00,0x00,0x05, + /* 1280x800 (_2) */ + 0x0C,0xE0,0x00,0x05,0x20,0x03,0x10,0x06,0x2C,0x03,0x30,0x00,0x70, + 0x00,0x04,0x00,0x03,0x00,0x49,0xCE,0x1E,0x00,0x00,0x00,0x00,0x09, + /* 1680x1050 */ + 0x0D,0xE0,0x90,0x06,0x1A,0x04,0x6C,0x07,0x2A,0x04,0x1A,0x00,0x4C, + 0x00,0x03,0x00,0x06,0x00,0x79,0xBE,0x44,0x00,0x00,0x00,0x00,0x06, + /* 1280x800_3 */ + 0x0C,0xE0,0x00,0x05,0x20,0x03,0xAA,0x05,0x2E,0x03,0x30,0x00,0x50, + 0x00,0x04,0x00,0x03,0x00,0x47,0xA9,0x10,0x00,0x00,0x00,0x00,0x07, + /* 800x600 */ + 0x01,0xC0,0x20,0x03,0x58,0x02,0x20,0x04,0x74,0x02,0x2A,0x00,0x80, + 0x00,0x06,0x00,0x04,0x00,0x28,0x63,0x4B,0x00,0x00,0x00,0x00,0x00, + /* 1280x854 */ + 0x08,0xE0,0x00,0x05,0x56,0x03,0x80,0x06,0x5d,0x03,0x10,0x00,0x70, + 0x00,0x01,0x00,0x03,0x00,0x54,0x75,0x13,0x00,0x00,0x00,0x00,0x08 +}; +#endif + +#ifdef CONFIG_FB_SIS_300 +static unsigned char SiS300_TrumpionData[14][80] = { + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x0D,0x00,0x0D,0x10,0x7F,0x00,0x80,0x02, + 0x20,0x03,0x0B,0x00,0x90,0x01,0xC1,0x01,0x60,0x0C,0x30,0x10,0x00,0x00,0x04,0x23, + 0x00,0x00,0x03,0x28,0x03,0x10,0x05,0x08,0x40,0x10,0x00,0x10,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xBC,0x01,0xFF,0x03,0xFF,0x19,0x01,0x00,0x05,0x09,0x04,0x04,0x05, + 0x04,0x0C,0x09,0x05,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x5A,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x0D,0x00,0x0D,0x10,0x27,0x00,0x80,0x02, + 0x20,0x03,0x07,0x00,0x5E,0x01,0x0D,0x02,0x60,0x0C,0x30,0x11,0x00,0x00,0x04,0x23, + 0x00,0x00,0x03,0x80,0x03,0x28,0x06,0x08,0x40,0x11,0x00,0x11,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0x90,0x01,0xFF,0x0F,0xF4,0x19,0x01,0x00,0x05,0x01,0x00,0x04,0x05, + 0x04,0x0C,0x02,0x01,0x02,0xB0,0x00,0x00,0x02,0xBA,0xEC,0x57,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x0D,0x00,0x0D,0x10,0x8A,0x00,0xD8,0x02, + 0x84,0x03,0x16,0x00,0x90,0x01,0xC1,0x01,0x60,0x0C,0x30,0x1C,0x00,0x20,0x04,0x23, + 0x00,0x01,0x03,0x53,0x03,0x28,0x06,0x08,0x40,0x1C,0x00,0x16,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xD9,0x01,0xFF,0x0F,0xF4,0x18,0x07,0x05,0x05,0x13,0x04,0x04,0x05, + 0x01,0x0B,0x13,0x0A,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x59,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x0D,0x00,0x0D,0x10,0x72,0x00,0xD8,0x02, + 0x84,0x03,0x16,0x00,0x90,0x01,0xC1,0x01,0x60,0x0C,0x30,0x1C,0x00,0x20,0x04,0x23, + 0x00,0x01,0x03,0x53,0x03,0x28,0x06,0x08,0x40,0x1C,0x00,0x16,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xDA,0x01,0xFF,0x0F,0xF4,0x18,0x07,0x05,0x05,0x13,0x04,0x04,0x05, + 0x01,0x0B,0x13,0x0A,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x55,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x02,0x00,0x04,0x01,0x00,0x03,0x0D,0x00,0x0D,0x10,0x7F,0x00,0x80,0x02, + 0x20,0x03,0x16,0x00,0xE0,0x01,0x0D,0x02,0x60,0x0C,0x30,0x98,0x00,0x00,0x04,0x23, + 0x00,0x01,0x03,0x45,0x03,0x48,0x06,0x08,0x40,0x98,0x00,0x98,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xF4,0x01,0xFF,0x0F,0xF4,0x18,0x01,0x00,0x05,0x01,0x00,0x05,0x05, + 0x04,0x0C,0x08,0x05,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x5B,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x02,0x01,0x04,0x01,0x00,0x03,0x0D,0x00,0x0D,0x10,0xBF,0x00,0x20,0x03, + 0x20,0x04,0x0D,0x00,0x58,0x02,0x71,0x02,0x80,0x0C,0x30,0x9A,0x00,0xFA,0x03,0x1D, + 0x00,0x01,0x03,0x22,0x03,0x28,0x06,0x08,0x40,0x98,0x00,0x98,0x04,0x1D,0x00,0x1D, + 0x03,0x11,0x60,0x39,0x03,0x40,0x05,0xF4,0x18,0x07,0x02,0x06,0x04,0x01,0x06,0x0B, + 0x02,0x0A,0x20,0x19,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x5B,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x0D,0x00,0x0D,0x10,0xEF,0x00,0x00,0x04, + 0x40,0x05,0x13,0x00,0x00,0x03,0x26,0x03,0x88,0x0C,0x30,0x90,0x00,0x00,0x04,0x23, + 0x00,0x01,0x03,0x24,0x03,0x28,0x06,0x08,0x40,0x90,0x00,0x90,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0x40,0x05,0xFF,0x0F,0xF4,0x18,0x01,0x00,0x08,0x01,0x00,0x08,0x01, + 0x00,0x08,0x01,0x01,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x5B,0x01,0xBE,0x01,0x00 }, + /* variant 2 */ + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x11,0x00,0x0D,0x10,0x7F,0x00,0x80,0x02, + 0x20,0x03,0x15,0x00,0x90,0x01,0xC1,0x01,0x60,0x0C,0x30,0x18,0x00,0x00,0x04,0x23, + 0x00,0x01,0x03,0x44,0x03,0x28,0x06,0x08,0x40,0x18,0x00,0x18,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xA6,0x01,0xFF,0x03,0xFF,0x19,0x01,0x00,0x05,0x13,0x04,0x04,0x05, + 0x04,0x0C,0x13,0x0A,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x55,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x11,0x00,0x0D,0x10,0x7F,0x00,0x80,0x02, + 0x20,0x03,0x15,0x00,0x90,0x01,0xC1,0x01,0x60,0x0C,0x30,0x18,0x00,0x00,0x04,0x23, + 0x00,0x01,0x03,0x44,0x03,0x28,0x06,0x08,0x40,0x18,0x00,0x18,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xA6,0x01,0xFF,0x03,0xFF,0x19,0x01,0x00,0x05,0x13,0x04,0x04,0x05, + 0x04,0x0C,0x13,0x0A,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x55,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x11,0x00,0x0D,0x10,0x8A,0x00,0xD8,0x02, + 0x84,0x03,0x16,0x00,0x90,0x01,0xC1,0x01,0x60,0x0C,0x30,0x1C,0x00,0x20,0x04,0x23, + 0x00,0x01,0x03,0x53,0x03,0x28,0x06,0x08,0x40,0x1C,0x00,0x16,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xDA,0x01,0xFF,0x0F,0xF4,0x18,0x07,0x05,0x05,0x13,0x04,0x04,0x05, + 0x01,0x0B,0x13,0x0A,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x55,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x11,0x00,0x0D,0x10,0x72,0x00,0xD8,0x02, + 0x84,0x03,0x16,0x00,0x90,0x01,0xC1,0x01,0x60,0x0C,0x30,0x1C,0x00,0x20,0x04,0x23, + 0x00,0x01,0x03,0x53,0x03,0x28,0x06,0x08,0x40,0x1C,0x00,0x16,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xDA,0x01,0xFF,0x0F,0xF4,0x18,0x07,0x05,0x05,0x13,0x04,0x04,0x05, + 0x01,0x0B,0x13,0x0A,0x02,0xB0,0x00,0x00,0x02,0xBA,0xF0,0x55,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x02,0x00,0x04,0x01,0x00,0x03,0x11,0x00,0x0D,0x10,0x7F,0x00,0x80,0x02, + 0x20,0x03,0x16,0x00,0xE0,0x01,0x0D,0x02,0x60,0x0C,0x30,0x98,0x00,0x00,0x04,0x23, + 0x00,0x01,0x03,0x45,0x03,0x48,0x06,0x08,0x40,0x98,0x00,0x98,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0xF4,0x01,0xFF,0x0F,0xF4,0x18,0x01,0x00,0x05,0x01,0x00,0x05,0x05, + 0x04,0x0C,0x08,0x05,0x02,0xB0,0x00,0x00,0x02,0xBA,0xEA,0x58,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x02,0x01,0x04,0x01,0x00,0x03,0x11,0x00,0x0D,0x10,0xBF,0x00,0x20,0x03, + 0x20,0x04,0x0D,0x00,0x58,0x02,0x71,0x02,0x80,0x0C,0x30,0x9A,0x00,0xFA,0x03,0x1D, + 0x00,0x01,0x03,0x22,0x03,0x28,0x06,0x08,0x40,0x98,0x00,0x98,0x04,0x1D,0x00,0x1D, + 0x03,0x11,0x60,0x39,0x03,0x40,0x05,0xF4,0x18,0x07,0x02,0x06,0x04,0x01,0x06,0x0B, + 0x02,0x0A,0x20,0x19,0x02,0xB0,0x00,0x00,0x02,0xBA,0xEA,0x58,0x01,0xBE,0x01,0x00 }, + { 0x02,0x0A,0x0A,0x01,0x04,0x01,0x00,0x03,0x11,0x00,0x0D,0x10,0xEF,0x00,0x00,0x04, + 0x40,0x05,0x13,0x00,0x00,0x03,0x26,0x03,0x88,0x0C,0x30,0x90,0x00,0x00,0x04,0x23, + 0x00,0x01,0x03,0x24,0x03,0x28,0x06,0x08,0x40,0x90,0x00,0x90,0x04,0x23,0x00,0x23, + 0x03,0x11,0x60,0x40,0x05,0xFF,0x0F,0xF4,0x18,0x01,0x00,0x08,0x01,0x00,0x08,0x01, + 0x00,0x08,0x01,0x01,0x02,0xB0,0x00,0x00,0x02,0xBA,0xEA,0x58,0x01,0xBE,0x01,0x00 } +}; +#endif + +void SiS_UnLockCRT2(struct SiS_Private *SiS_Pr); +void SiS_EnableCRT2(struct SiS_Private *SiS_Pr); +unsigned short SiS_GetRatePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex); +void SiS_WaitRetrace1(struct SiS_Private *SiS_Pr); +bool SiS_IsDualEdge(struct SiS_Private *SiS_Pr); +bool SiS_IsVAMode(struct SiS_Private *SiS_Pr); +void SiS_GetVBInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, int checkcrt2mode); +void SiS_SetYPbPr(struct SiS_Private *SiS_Pr); +void SiS_SetTVMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +void SiS_GetLCDResInfo(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +unsigned short SiS_GetVCLK2Ptr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex); +unsigned short SiS_GetResInfo(struct SiS_Private *SiS_Pr,unsigned short ModeNo,unsigned short ModeIdIndex); +void SiS_DisableBridge(struct SiS_Private *SiS_Pr); +bool SiS_SetCRT2Group(struct SiS_Private *SiS_Pr, unsigned short ModeNo); +void SiS_SiS30xBLOn(struct SiS_Private *SiS_Pr); +void SiS_SiS30xBLOff(struct SiS_Private *SiS_Pr); + +void SiS_SetCH700x(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val); +unsigned short SiS_GetCH700x(struct SiS_Private *SiS_Pr, unsigned short tempax); +void SiS_SetCH701x(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val); +unsigned short SiS_GetCH701x(struct SiS_Private *SiS_Pr, unsigned short tempax); +void SiS_SetCH70xxANDOR(struct SiS_Private *SiS_Pr, unsigned short reg, + unsigned char orval,unsigned short andval); +#ifdef CONFIG_FB_SIS_315 +static void SiS_Chrontel701xOn(struct SiS_Private *SiS_Pr); +static void SiS_Chrontel701xOff(struct SiS_Private *SiS_Pr); +static void SiS_ChrontelInitTVVSync(struct SiS_Private *SiS_Pr); +static void SiS_ChrontelDoSomething1(struct SiS_Private *SiS_Pr); +void SiS_Chrontel701xBLOn(struct SiS_Private *SiS_Pr); +void SiS_Chrontel701xBLOff(struct SiS_Private *SiS_Pr); +#endif /* 315 */ + +#ifdef CONFIG_FB_SIS_300 +static bool SiS_SetTrumpionBlock(struct SiS_Private *SiS_Pr, unsigned char *dataptr); +void SiS_SetChrontelGPIO(struct SiS_Private *SiS_Pr, unsigned short myvbinfo); +#endif + +void SiS_DDC2Delay(struct SiS_Private *SiS_Pr, unsigned int delaytime); +unsigned short SiS_ReadDDC1Bit(struct SiS_Private *SiS_Pr); +unsigned short SiS_HandleDDC(struct SiS_Private *SiS_Pr, unsigned int VBFlags, int VGAEngine, + unsigned short adaptnum, unsigned short DDCdatatype, + unsigned char *buffer, unsigned int VBFlags2); + +static unsigned short SiS_InitDDCRegs(struct SiS_Private *SiS_Pr, unsigned int VBFlags, + int VGAEngine, unsigned short adaptnum, unsigned short DDCdatatype, + bool checkcr32, unsigned int VBFlags2); +static unsigned short SiS_ProbeDDC(struct SiS_Private *SiS_Pr); +static unsigned short SiS_ReadDDC(struct SiS_Private *SiS_Pr, unsigned short DDCdatatype, + unsigned char *buffer); +static void SiS_SetSwitchDDC2(struct SiS_Private *SiS_Pr); +static unsigned short SiS_SetStart(struct SiS_Private *SiS_Pr); +static unsigned short SiS_SetStop(struct SiS_Private *SiS_Pr); +static unsigned short SiS_SetSCLKLow(struct SiS_Private *SiS_Pr); +static unsigned short SiS_SetSCLKHigh(struct SiS_Private *SiS_Pr); +static unsigned short SiS_ReadDDC2Data(struct SiS_Private *SiS_Pr); +static unsigned short SiS_WriteDDC2Data(struct SiS_Private *SiS_Pr, unsigned short tempax); +static unsigned short SiS_CheckACK(struct SiS_Private *SiS_Pr); +static unsigned short SiS_WriteDABDDC(struct SiS_Private *SiS_Pr); +static unsigned short SiS_PrepareReadDDC(struct SiS_Private *SiS_Pr); +static unsigned short SiS_PrepareDDC(struct SiS_Private *SiS_Pr); +static void SiS_SendACK(struct SiS_Private *SiS_Pr, unsigned short yesno); +static unsigned short SiS_DoProbeDDC(struct SiS_Private *SiS_Pr); + +#ifdef CONFIG_FB_SIS_300 +static void SiS_OEM300Setting(struct SiS_Private *SiS_Pr, + unsigned short ModeNo, unsigned short ModeIdIndex, unsigned short RefTabindex); +static void SetOEMLCDData2(struct SiS_Private *SiS_Pr, + unsigned short ModeNo, unsigned short ModeIdIndex,unsigned short RefTableIndex); +#endif +#ifdef CONFIG_FB_SIS_315 +static void SiS_OEM310Setting(struct SiS_Private *SiS_Pr, + unsigned short ModeNo,unsigned short ModeIdIndex, unsigned short RRTI); +static void SiS_OEM661Setting(struct SiS_Private *SiS_Pr, + unsigned short ModeNo,unsigned short ModeIdIndex, unsigned short RRTI); +static void SiS_FinalizeLCD(struct SiS_Private *, unsigned short, unsigned short); +#endif + +extern void SiS_DisplayOff(struct SiS_Private *SiS_Pr); +extern void SiS_DisplayOn(struct SiS_Private *SiS_Pr); +extern bool SiS_SearchModeID(struct SiS_Private *, unsigned short *, unsigned short *); +extern unsigned short SiS_GetModeFlag(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +extern unsigned short SiS_GetModePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex); +extern unsigned short SiS_GetColorDepth(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex); +extern unsigned short SiS_GetOffset(struct SiS_Private *SiS_Pr, unsigned short ModeNo, unsigned short ModeIdIndex, + unsigned short RefreshRateTableIndex); +extern void SiS_LoadDAC(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +extern void SiS_CalcLCDACRT1Timing(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex); +extern void SiS_CalcCRRegisters(struct SiS_Private *SiS_Pr, int depth); +extern unsigned short SiS_GetRefCRTVCLK(struct SiS_Private *SiS_Pr, unsigned short Index, int UseWide); +extern unsigned short SiS_GetRefCRT1CRTC(struct SiS_Private *SiS_Pr, unsigned short Index, int UseWide); +#ifdef CONFIG_FB_SIS_300 +extern void SiS_GetFIFOThresholdIndex300(struct SiS_Private *SiS_Pr, unsigned short *tempbx, + unsigned short *tempcl); +extern unsigned short SiS_GetFIFOThresholdB300(unsigned short tempbx, unsigned short tempcl); +extern unsigned short SiS_GetLatencyFactor630(struct SiS_Private *SiS_Pr, unsigned short index); +extern unsigned int sisfb_read_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg); +extern unsigned int sisfb_read_lpc_pci_dword(struct SiS_Private *SiS_Pr, int reg); +#endif + +#endif diff --git a/drivers/video/fbdev/sis/initdef.h b/drivers/video/fbdev/sis/initdef.h new file mode 100644 index 000000000000..264b55a5947b --- /dev/null +++ b/drivers/video/fbdev/sis/initdef.h @@ -0,0 +1,708 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Global definitions for init.c and init301.c + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +#ifndef _INITDEF_ +#define _INITDEF_ + +#define IS_SIS330 (SiS_Pr->ChipType == SIS_330) +#define IS_SIS550 (SiS_Pr->ChipType == SIS_550) +#define IS_SIS650 (SiS_Pr->ChipType == SIS_650) /* All versions, incl 651, M65x */ +#define IS_SIS740 (SiS_Pr->ChipType == SIS_740) +#define IS_SIS651 (SiS_Pr->SiS_SysFlags & (SF_Is651 | SF_Is652)) +#define IS_SISM650 (SiS_Pr->SiS_SysFlags & (SF_IsM650 | SF_IsM652 | SF_IsM653)) +#define IS_SIS65x (IS_SIS651 || IS_SISM650) /* Only special versions of 65x */ +#define IS_SIS661 (SiS_Pr->ChipType == SIS_661) +#define IS_SIS741 (SiS_Pr->ChipType == SIS_741) +#define IS_SIS660 (SiS_Pr->ChipType == SIS_660) +#define IS_SIS760 (SiS_Pr->ChipType == SIS_760) +#define IS_SIS761 (SiS_Pr->ChipType == SIS_761) +#define IS_SIS661741660760 (IS_SIS661 || IS_SIS741 || IS_SIS660 || IS_SIS760 || IS_SIS761) +#define IS_SIS650740 ((SiS_Pr->ChipType >= SIS_650) && (SiS_Pr->ChipType < SIS_330)) +#define IS_SIS550650740 (IS_SIS550 || IS_SIS650740) +#define IS_SIS650740660 (IS_SIS650 || IS_SIS740 || IS_SIS661741660760) +#define IS_SIS550650740660 (IS_SIS550 || IS_SIS650740660) + +#define SISGETROMW(x) (ROMAddr[(x)] | (ROMAddr[(x)+1] << 8)) + +/* SiS_VBType */ +#define VB_SIS301 0x0001 +#define VB_SIS301B 0x0002 +#define VB_SIS302B 0x0004 +#define VB_SIS301LV 0x0008 +#define VB_SIS302LV 0x0010 +#define VB_SIS302ELV 0x0020 +#define VB_SIS301C 0x0040 +#define VB_SIS307T 0x0080 +#define VB_SIS307LV 0x0100 +#define VB_UMC 0x4000 +#define VB_NoLCD 0x8000 +#define VB_SIS30xB (VB_SIS301B | VB_SIS301C | VB_SIS302B | VB_SIS307T) +#define VB_SIS30xC (VB_SIS301C | VB_SIS307T) +#define VB_SISTMDS (VB_SIS301 | VB_SIS301B | VB_SIS301C | VB_SIS302B | VB_SIS307T) +#define VB_SISLVDS (VB_SIS301LV | VB_SIS302LV | VB_SIS302ELV | VB_SIS307LV) +#define VB_SIS30xBLV (VB_SIS30xB | VB_SISLVDS) +#define VB_SIS30xCLV (VB_SIS30xC | VB_SIS302ELV | VB_SIS307LV) +#define VB_SISVB (VB_SIS301 | VB_SIS30xBLV) +#define VB_SISLCDA (VB_SIS302B | VB_SIS301C | VB_SIS307T | VB_SISLVDS) +#define VB_SISTMDSLCDA (VB_SIS301C | VB_SIS307T) +#define VB_SISPART4SCALER (VB_SIS301C | VB_SIS307T | VB_SIS302ELV | VB_SIS307LV) +#define VB_SISHIVISION (VB_SIS301 | VB_SIS301B | VB_SIS302B) +#define VB_SISYPBPR (VB_SIS301C | VB_SIS307T | VB_SIS301LV | VB_SIS302LV | VB_SIS302ELV | VB_SIS307LV) +#define VB_SISTAP4SCALER (VB_SIS301C | VB_SIS307T | VB_SIS302ELV | VB_SIS307LV) +#define VB_SISPART4OVERFLOW (VB_SIS301C | VB_SIS307T | VB_SIS302LV | VB_SIS302ELV | VB_SIS307LV) +#define VB_SISPWD (VB_SIS301C | VB_SIS307T | VB_SISLVDS) +#define VB_SISEMI (VB_SIS302LV | VB_SIS302ELV | VB_SIS307LV) +#define VB_SISPOWER (VB_SIS301C | VB_SIS307T | VB_SIS302LV | VB_SIS302ELV | VB_SIS307LV) +#define VB_SISDUALLINK (VB_SIS302LV | VB_SIS302ELV | VB_SIS307T | VB_SIS307LV) +#define VB_SISVGA2 VB_SISTMDS +#define VB_SISRAMDAC202 (VB_SIS301C | VB_SIS307T) + +/* VBInfo */ +#define SetSimuScanMode 0x0001 /* CR 30 */ +#define SwitchCRT2 0x0002 +#define SetCRT2ToAVIDEO 0x0004 +#define SetCRT2ToSVIDEO 0x0008 +#define SetCRT2ToSCART 0x0010 +#define SetCRT2ToLCD 0x0020 +#define SetCRT2ToRAMDAC 0x0040 +#define SetCRT2ToHiVision 0x0080 /* for SiS bridge */ +#define SetCRT2ToCHYPbPr SetCRT2ToHiVision /* for Chrontel */ +#define SetNTSCTV 0x0000 /* CR 31 */ +#define SetPALTV 0x0100 /* Deprecated here, now in TVMode */ +#define SetInSlaveMode 0x0200 +#define SetNotSimuMode 0x0400 +#define SetNotSimuTVMode SetNotSimuMode +#define SetDispDevSwitch 0x0800 +#define SetCRT2ToYPbPr525750 0x0800 +#define LoadDACFlag 0x1000 +#define DisableCRT2Display 0x2000 +#define DriverMode 0x4000 +#define HotKeySwitch 0x8000 +#define SetCRT2ToLCDA 0x8000 + +/* v-- Needs change in sis_vga.c if changed (GPIO) --v */ +#define SetCRT2ToTV (SetCRT2ToYPbPr525750|SetCRT2ToHiVision|SetCRT2ToSCART|SetCRT2ToSVIDEO|SetCRT2ToAVIDEO) +#define SetCRT2ToTVNoYPbPrHiVision (SetCRT2ToSCART | SetCRT2ToSVIDEO | SetCRT2ToAVIDEO) +#define SetCRT2ToTVNoHiVision (SetCRT2ToYPbPr525750 | SetCRT2ToSCART | SetCRT2ToSVIDEO | SetCRT2ToAVIDEO) + +/* SiS_ModeType */ +#define ModeText 0x00 +#define ModeCGA 0x01 +#define ModeEGA 0x02 +#define ModeVGA 0x03 +#define Mode15Bpp 0x04 +#define Mode16Bpp 0x05 +#define Mode24Bpp 0x06 +#define Mode32Bpp 0x07 + +#define ModeTypeMask 0x07 +#define IsTextMode 0x07 + +#define DACInfoFlag 0x0018 +#define MemoryInfoFlag 0x01E0 +#define MemorySizeShift 5 + +/* modeflag */ +#define Charx8Dot 0x0200 +#define LineCompareOff 0x0400 +#define CRT2Mode 0x0800 +#define HalfDCLK 0x1000 +#define NoSupportSimuTV 0x2000 +#define NoSupportLCDScale 0x4000 /* SiS bridge: No scaling possible (no matter what panel) */ +#define DoubleScanMode 0x8000 + +/* Infoflag */ +#define SupportTV 0x0008 +#define SupportTV1024 0x0800 +#define SupportCHTV 0x0800 +#define Support64048060Hz 0x0800 /* Special for 640x480 LCD */ +#define SupportHiVision 0x0010 +#define SupportYPbPr750p 0x1000 +#define SupportLCD 0x0020 +#define SupportRAMDAC2 0x0040 /* All (<= 100Mhz) */ +#define SupportRAMDAC2_135 0x0100 /* All except DH (<= 135Mhz) */ +#define SupportRAMDAC2_162 0x0200 /* B, C (<= 162Mhz) */ +#define SupportRAMDAC2_202 0x0400 /* C (<= 202Mhz) */ +#define InterlaceMode 0x0080 +#define SyncPP 0x0000 +#define HaveWideTiming 0x2000 /* Have specific wide- and non-wide timing */ +#define SyncPN 0x4000 +#define SyncNP 0x8000 +#define SyncNN 0xc000 + +/* SetFlag */ +#define ProgrammingCRT2 0x0001 +#define LowModeTests 0x0002 +/* #define TVSimuMode 0x0002 - deprecated */ +/* #define RPLLDIV2XO 0x0004 - deprecated */ +#define LCDVESATiming 0x0008 +#define EnableLVDSDDA 0x0010 +#define SetDispDevSwitchFlag 0x0020 +#define CheckWinDos 0x0040 +#define SetDOSMode 0x0080 + +/* TVMode flag */ +#define TVSetPAL 0x0001 +#define TVSetNTSCJ 0x0002 +#define TVSetPALM 0x0004 +#define TVSetPALN 0x0008 +#define TVSetCHOverScan 0x0010 +#define TVSetYPbPr525i 0x0020 /* new 0x10 */ +#define TVSetYPbPr525p 0x0040 /* new 0x20 */ +#define TVSetYPbPr750p 0x0080 /* new 0x40 */ +#define TVSetHiVision 0x0100 /* new 0x80; = 1080i, software-wise identical */ +#define TVSetTVSimuMode 0x0200 /* new 0x200, prev. 0x800 */ +#define TVRPLLDIV2XO 0x0400 /* prev 0x1000 */ +#define TVSetNTSC1024 0x0800 /* new 0x100, prev. 0x2000 */ +#define TVSet525p1024 0x1000 /* TW */ +#define TVAspect43 0x2000 +#define TVAspect169 0x4000 +#define TVAspect43LB 0x8000 + +/* YPbPr flag (>=315, <661; converted to TVMode) */ +#define YPbPr525p 0x0001 +#define YPbPr750p 0x0002 +#define YPbPr525i 0x0004 +#define YPbPrHiVision 0x0008 +#define YPbPrModeMask (YPbPr750p | YPbPr525p | YPbPr525i | YPbPrHiVision) + +/* SysFlags (to identify special versions) */ +#define SF_Is651 0x0001 +#define SF_IsM650 0x0002 +#define SF_Is652 0x0004 +#define SF_IsM652 0x0008 +#define SF_IsM653 0x0010 +#define SF_IsM661 0x0020 +#define SF_IsM741 0x0040 +#define SF_IsM760 0x0080 +#define SF_760UMA 0x4000 /* 76x: We have UMA */ +#define SF_760LFB 0x8000 /* 76x: We have LFB */ + +/* CR32 (Newer 630, and 315 series) + + [0] VB connected with CVBS + [1] VB connected with SVHS + [2] VB connected with SCART + [3] VB connected with LCD + [4] VB connected with CRT2 (secondary VGA) + [5] CRT1 monitor is connected + [6] VB connected with Hi-Vision TV + [7] <= 330: VB connected with DVI combo connector + >= 661: VB connected to YPbPr +*/ + +/* CR35 (300 series only) */ +#define TVOverScan 0x10 +#define TVOverScanShift 4 + +/* CR35 (661 series only) + [0] 1 = PAL, 0 = NTSC + [1] 1 = NTSC-J (if D0 = 0) + [2] 1 = PALM (if D0 = 1) + [3] 1 = PALN (if D0 = 1) + [4] 1 = Overscan (Chrontel only) + [7:5] (only if D2 in CR38 is set) + 000 525i + 001 525p + 010 750p + 011 1080i (or HiVision on 301, 301B) +*/ + +/* CR37 + [0] Set 24/18 bit (0/1) RGB to LVDS/TMDS transmitter (set by BIOS) + [3:1] External chip + 300 series: + 001 SiS301 (never seen) + 010 LVDS + 011 LVDS + Tumpion Zurac + 100 LVDS + Chrontel 7005 + 110 Chrontel 7005 + 315/330 series + 001 SiS30x (never seen) + 010 LVDS + 011 LVDS + Chrontel 7019 + 660 series [2:1] only: + reserved (chip type now in CR38) + All other combinations reserved + [3] 661 only: Pass 1:1 data + [4] LVDS: 0: Panel Link expands / 1: Panel Link does not expand + 30x: 0: Bridge scales / 1: Bridge does not scale = Panel scales (if possible) + [5] LCD polarity select + 0: VESA DMT Standard + 1: EDID 2.x defined + [6] LCD horizontal polarity select + 0: High active + 1: Low active + [7] LCD vertical polarity select + 0: High active + 1: Low active +*/ + +/* CR37: LCDInfo */ +#define LCDRGB18Bit 0x0001 +#define LCDNonExpanding 0x0010 +#define LCDSync 0x0020 +#define LCDPass11 0x0100 /* 0: center screen, 1: Pass 1:1 data */ +#define LCDDualLink 0x0200 + +#define DontExpandLCD LCDNonExpanding +#define LCDNonExpandingShift 4 +#define DontExpandLCDShift LCDNonExpandingShift +#define LCDSyncBit 0x00e0 +#define LCDSyncShift 6 + +/* CR38 (315 series) */ +#define EnableDualEdge 0x01 +#define SetToLCDA 0x02 /* LCD channel A (301C/302B/30x(E)LV and 650+LVDS only) */ +#define EnableCHScart 0x04 /* Scart on Ch7019 (unofficial definition - TW) */ +#define EnableCHYPbPr 0x08 /* YPbPr on Ch7019 (480i HDTV); only on 650/Ch7019 systems */ +#define EnableSiSYPbPr 0x08 /* Enable YPbPr mode (30xLV/301C only) */ +#define EnableYPbPr525i 0x00 /* Enable 525i YPbPr mode (30xLV/301C only) (mask 0x30) */ +#define EnableYPbPr525p 0x10 /* Enable 525p YPbPr mode (30xLV/301C only) (mask 0x30) */ +#define EnableYPbPr750p 0x20 /* Enable 750p YPbPr mode (30xLV/301C only) (mask 0x30) */ +#define EnableYPbPr1080i 0x30 /* Enable 1080i YPbPr mode (30xLV/301C only) (mask 0x30) */ +#define EnablePALM 0x40 /* 1 = Set PALM */ +#define EnablePALN 0x80 /* 1 = Set PALN */ +#define EnableNTSCJ EnablePALM /* Not BIOS */ + +/* CR38 (661 and later) + D[7:5] 000 No VB + 001 301 series VB + 010 LVDS + 011 Chrontel 7019 + 100 Conexant + D2 Enable YPbPr output (see CR35) + D[1:0] LCDA (like before) +*/ + +#define EnablePALMN 0x40 /* Romflag: 1 = Allow PALM/PALN */ + +/* CR39 (650 only) */ +#define LCDPass1_1 0x01 /* 0: center screen, 1: pass 1:1 data output */ +#define Enable302LV_DualLink 0x04 /* 302LV only; enable dual link */ + +/* CR39 (661 and later) + D[7] LVDS (SiS or third party) + D[1:0] YPbPr Aspect Ratio + 00 4:3 letterbox + 01 4:3 + 10 16:9 + 11 4:3 +*/ + +/* CR3B (651+301C) + D[1:0] YPbPr Aspect Ratio + ? +*/ + +/* CR79 (315/330 series only; not 661 and later) + [3-0] Notify driver + 0001 Mode Switch event (set by BIOS) + 0010 Epansion On/Off event + 0011 TV UnderScan/OverScan event + 0100 Set Brightness event + 0101 Set Contrast event + 0110 Set Mute event + 0111 Set Volume Up/Down event + [4] Enable Backlight Control by BIOS/driver + (set by driver; set means that the BIOS should + not touch the backlight registers because eg. + the driver already switched off the backlight) + [5] PAL/NTSC (set by BIOS) + [6] Expansion On/Off (set by BIOS; copied to CR32[4]) + [7] TV UnderScan/OverScan (set by BIOS) +*/ + +/* CR7C - 661 and later + [7] DualEdge enabled (or: to be enabled) + [6] CRT2 = TV/LCD/VGA enabled (or: to be enabled) + [5] Init done (set at end of SiS_Init) + {4] LVDS LCD capabilities + [3] LVDS LCD capabilities + [2] LVDS LCD capabilities (PWD) + [1] LVDS LCD capabilities (PWD) + [0] LVDS=1, TMDS=0 (SiS or third party) +*/ + +/* CR7E - 661 and later + VBType: + [7] LVDS (third party) + [3] 301C + [2] 302LV + [1] 301LV + [0] 301B +*/ + +/* LCDResInfo */ +#define Panel300_800x600 0x01 /* CR36 */ +#define Panel300_1024x768 0x02 +#define Panel300_1280x1024 0x03 +#define Panel300_1280x960 0x04 +#define Panel300_640x480 0x05 +#define Panel300_1024x600 0x06 +#define Panel300_1152x768 0x07 +#define Panel300_1280x768 0x0a +#define Panel300_Custom 0x0f +#define Panel300_Barco1366 0x10 + +#define Panel310_800x600 0x01 +#define Panel310_1024x768 0x02 +#define Panel310_1280x1024 0x03 +#define Panel310_640x480 0x04 +#define Panel310_1024x600 0x05 +#define Panel310_1152x864 0x06 +#define Panel310_1280x960 0x07 +#define Panel310_1152x768 0x08 /* LVDS only */ +#define Panel310_1400x1050 0x09 +#define Panel310_1280x768 0x0a +#define Panel310_1600x1200 0x0b +#define Panel310_320x240_2 0x0c /* xSTN */ +#define Panel310_320x240_3 0x0d /* xSTN */ +#define Panel310_320x240_1 0x0e /* xSTN - This is fake, can be any */ +#define Panel310_Custom 0x0f + +#define Panel661_800x600 0x01 +#define Panel661_1024x768 0x02 +#define Panel661_1280x1024 0x03 +#define Panel661_640x480 0x04 +#define Panel661_1024x600 0x05 +#define Panel661_1152x864 0x06 +#define Panel661_1280x960 0x07 +#define Panel661_1280x854 0x08 +#define Panel661_1400x1050 0x09 +#define Panel661_1280x768 0x0a +#define Panel661_1600x1200 0x0b +#define Panel661_1280x800 0x0c +#define Panel661_1680x1050 0x0d +#define Panel661_1280x720 0x0e +#define Panel661_Custom 0x0f + +#define Panel_800x600 0x01 /* Unified values */ +#define Panel_1024x768 0x02 /* MUST match BIOS values from 0-e */ +#define Panel_1280x1024 0x03 +#define Panel_640x480 0x04 +#define Panel_1024x600 0x05 +#define Panel_1152x864 0x06 +#define Panel_1280x960 0x07 +#define Panel_1152x768 0x08 /* LVDS only */ +#define Panel_1400x1050 0x09 +#define Panel_1280x768 0x0a /* 30xB/C and LVDS only (BIOS: all) */ +#define Panel_1600x1200 0x0b +#define Panel_1280x800 0x0c /* 661etc (TMDS) */ +#define Panel_1680x1050 0x0d /* 661etc */ +#define Panel_1280x720 0x0e /* 661etc */ +#define Panel_Custom 0x0f /* MUST BE 0x0f (for DVI DDC detection) */ +#define Panel_320x240_1 0x10 /* SiS 550 xSTN */ +#define Panel_Barco1366 0x11 +#define Panel_848x480 0x12 +#define Panel_320x240_2 0x13 /* SiS 550 xSTN */ +#define Panel_320x240_3 0x14 /* SiS 550 xSTN */ +#define Panel_1280x768_2 0x15 /* 30xLV */ +#define Panel_1280x768_3 0x16 /* (unused) */ +#define Panel_1280x800_2 0x17 /* 30xLV */ +#define Panel_856x480 0x18 +#define Panel_1280x854 0x19 /* 661etc */ + +/* Index in ModeResInfo table */ +#define SIS_RI_320x200 0 +#define SIS_RI_320x240 1 +#define SIS_RI_320x400 2 +#define SIS_RI_400x300 3 +#define SIS_RI_512x384 4 +#define SIS_RI_640x400 5 +#define SIS_RI_640x480 6 +#define SIS_RI_800x600 7 +#define SIS_RI_1024x768 8 +#define SIS_RI_1280x1024 9 +#define SIS_RI_1600x1200 10 +#define SIS_RI_1920x1440 11 +#define SIS_RI_2048x1536 12 +#define SIS_RI_720x480 13 +#define SIS_RI_720x576 14 +#define SIS_RI_1280x960 15 +#define SIS_RI_800x480 16 +#define SIS_RI_1024x576 17 +#define SIS_RI_1280x720 18 +#define SIS_RI_856x480 19 +#define SIS_RI_1280x768 20 +#define SIS_RI_1400x1050 21 +#define SIS_RI_1152x864 22 /* Up to here SiS conforming */ +#define SIS_RI_848x480 23 +#define SIS_RI_1360x768 24 +#define SIS_RI_1024x600 25 +#define SIS_RI_1152x768 26 +#define SIS_RI_768x576 27 +#define SIS_RI_1360x1024 28 +#define SIS_RI_1680x1050 29 +#define SIS_RI_1280x800 30 +#define SIS_RI_1920x1080 31 +#define SIS_RI_960x540 32 +#define SIS_RI_960x600 33 +#define SIS_RI_1280x854 34 + +/* CR5F */ +#define IsM650 0x80 + +/* Timing data */ +#define NTSCHT 1716 +#define NTSC2HT 1920 +#define NTSCVT 525 +#define PALHT 1728 +#define PALVT 625 +#define StHiTVHT 892 +#define StHiTVVT 1126 +#define StHiTextTVHT 1000 +#define StHiTextTVVT 1126 +#define ExtHiTVHT 2100 +#define ExtHiTVVT 1125 + +/* Indices in (VB)VCLKData tables */ + +#define VCLK28 0x00 /* Index in VCLKData table (300 and 315) */ +#define VCLK40 0x04 /* Index in VCLKData table (300 and 315) */ +#define VCLK65_300 0x09 /* Index in VCLKData table (300) */ +#define VCLK108_2_300 0x14 /* Index in VCLKData table (300) */ +#define VCLK81_300 0x3f /* Index in VCLKData table (300) */ +#define VCLK108_3_300 0x42 /* Index in VCLKData table (300) */ +#define VCLK100_300 0x43 /* Index in VCLKData table (300) */ +#define VCLK34_300 0x3d /* Index in VCLKData table (300) */ +#define VCLK_CUSTOM_300 0x47 + +#define VCLK65_315 0x0b /* Indices in (VB)VCLKData table (315) */ +#define VCLK108_2_315 0x19 +#define VCLK81_315 0x5b +#define VCLK162_315 0x5e +#define VCLK108_3_315 0x45 +#define VCLK100_315 0x46 +#define VCLK34_315 0x55 +#define VCLK68_315 0x0d +#define VCLK_1280x800_315_2 0x5c +#define VCLK121_315 0x5d +#define VCLK130_315 0x72 +#define VCLK_1280x720 0x5f +#define VCLK_1280x768_2 0x60 +#define VCLK_1280x768_3 0x61 /* (unused?) */ +#define VCLK_CUSTOM_315 0x62 +#define VCLK_1280x720_2 0x63 +#define VCLK_720x480 0x67 +#define VCLK_720x576 0x68 +#define VCLK_768x576 0x68 +#define VCLK_848x480 0x65 +#define VCLK_856x480 0x66 +#define VCLK_800x480 0x65 +#define VCLK_1024x576 0x51 +#define VCLK_1152x864 0x64 +#define VCLK_1360x768 0x58 +#define VCLK_1280x800_315 0x6c +#define VCLK_1280x854 0x76 + +#define TVCLKBASE_300 0x21 /* Indices on TV clocks in VCLKData table (300) */ +#define TVCLKBASE_315 0x3a /* Indices on TV clocks in (VB)VCLKData table (315) */ +#define TVVCLKDIV2 0x00 /* Index relative to TVCLKBASE */ +#define TVVCLK 0x01 /* Index relative to TVCLKBASE */ +#define HiTVVCLKDIV2 0x02 /* Index relative to TVCLKBASE */ +#define HiTVVCLK 0x03 /* Index relative to TVCLKBASE */ +#define HiTVSimuVCLK 0x04 /* Index relative to TVCLKBASE */ +#define HiTVTextVCLK 0x05 /* Index relative to TVCLKBASE */ +#define YPbPr750pVCLK 0x25 /* Index relative to TVCLKBASE; was 0x0f NOT relative */ + +/* ------------------------------ */ + +#define SetSCARTOutput 0x01 + +#define HotPlugFunction 0x08 + +#define StStructSize 0x06 + +#define SIS_VIDEO_CAPTURE 0x00 - 0x30 +#define SIS_VIDEO_PLAYBACK 0x02 - 0x30 +#define SIS_CRT2_PORT_04 0x04 - 0x30 +#define SIS_CRT2_PORT_10 0x10 - 0x30 +#define SIS_CRT2_PORT_12 0x12 - 0x30 +#define SIS_CRT2_PORT_14 0x14 - 0x30 + +#define ADR_CRT2PtrData 0x20E +#define offset_Zurac 0x210 /* TW: Trumpion Zurac data pointer */ +#define ADR_LVDSDesPtrData 0x212 +#define ADR_LVDSCRT1DataPtr 0x214 +#define ADR_CHTVVCLKPtr 0x216 +#define ADR_CHTVRegDataPtr 0x218 + +#define LCDDataLen 8 +#define HiTVDataLen 12 +#define TVDataLen 16 + +#define LVDSDataLen 6 +#define LVDSDesDataLen 3 +#define ActiveNonExpanding 0x40 +#define ActiveNonExpandingShift 6 +#define ActivePAL 0x20 +#define ActivePALShift 5 +#define ModeSwitchStatus 0x0F +#define SoftTVType 0x40 +#define SoftSettingAddr 0x52 +#define ModeSettingAddr 0x53 + +#define _PanelType00 0x00 +#define _PanelType01 0x08 +#define _PanelType02 0x10 +#define _PanelType03 0x18 +#define _PanelType04 0x20 +#define _PanelType05 0x28 +#define _PanelType06 0x30 +#define _PanelType07 0x38 +#define _PanelType08 0x40 +#define _PanelType09 0x48 +#define _PanelType0A 0x50 +#define _PanelType0B 0x58 +#define _PanelType0C 0x60 +#define _PanelType0D 0x68 +#define _PanelType0E 0x70 +#define _PanelType0F 0x78 + +#define PRIMARY_VGA 0 /* 1: SiS is primary vga 0:SiS is secondary vga */ + +#define BIOSIDCodeAddr 0x235 /* Offsets to ptrs in BIOS image */ +#define OEMUtilIDCodeAddr 0x237 +#define VBModeIDTableAddr 0x239 +#define OEMTVPtrAddr 0x241 +#define PhaseTableAddr 0x243 +#define NTSCFilterTableAddr 0x245 +#define PALFilterTableAddr 0x247 +#define OEMLCDPtr_1Addr 0x249 +#define OEMLCDPtr_2Addr 0x24B +#define LCDHPosTable_1Addr 0x24D +#define LCDHPosTable_2Addr 0x24F +#define LCDVPosTable_1Addr 0x251 +#define LCDVPosTable_2Addr 0x253 +#define OEMLCDPIDTableAddr 0x255 + +#define VBModeStructSize 5 +#define PhaseTableSize 4 +#define FilterTableSize 4 +#define LCDHPosTableSize 7 +#define LCDVPosTableSize 5 +#define OEMLVDSPIDTableSize 4 +#define LVDSHPosTableSize 4 +#define LVDSVPosTableSize 6 + +#define VB_ModeID 0 +#define VB_TVTableIndex 1 +#define VB_LCDTableIndex 2 +#define VB_LCDHIndex 3 +#define VB_LCDVIndex 4 + +#define OEMLCDEnable 0x0001 +#define OEMLCDDelayEnable 0x0002 +#define OEMLCDPOSEnable 0x0004 +#define OEMTVEnable 0x0100 +#define OEMTVDelayEnable 0x0200 +#define OEMTVFlickerEnable 0x0400 +#define OEMTVPhaseEnable 0x0800 +#define OEMTVFilterEnable 0x1000 + +#define OEMLCDPanelIDSupport 0x0080 + +/* + ============================================================= + for 315 series (old data layout) + ============================================================= +*/ +#define SoftDRAMType 0x80 +#define SoftSetting_OFFSET 0x52 +#define SR07_OFFSET 0x7C +#define SR15_OFFSET 0x7D +#define SR16_OFFSET 0x81 +#define SR17_OFFSET 0x85 +#define SR19_OFFSET 0x8D +#define SR1F_OFFSET 0x99 +#define SR21_OFFSET 0x9A +#define SR22_OFFSET 0x9B +#define SR23_OFFSET 0x9C +#define SR24_OFFSET 0x9D +#define SR25_OFFSET 0x9E +#define SR31_OFFSET 0x9F +#define SR32_OFFSET 0xA0 +#define SR33_OFFSET 0xA1 + +#define CR40_OFFSET 0xA2 +#define SR25_1_OFFSET 0xF6 +#define CR49_OFFSET 0xF7 + +#define VB310Data_1_2_Offset 0xB6 +#define VB310Data_4_D_Offset 0xB7 +#define VB310Data_4_E_Offset 0xB8 +#define VB310Data_4_10_Offset 0xBB + +#define RGBSenseDataOffset 0xBD +#define YCSenseDataOffset 0xBF +#define VideoSenseDataOffset 0xC1 +#define OutputSelectOffset 0xF3 + +#define ECLK_MCLK_DISTANCE 0x14 +#define VBIOSTablePointerStart 0x100 +#define StandTablePtrOffset VBIOSTablePointerStart+0x02 +#define EModeIDTablePtrOffset VBIOSTablePointerStart+0x04 +#define CRT1TablePtrOffset VBIOSTablePointerStart+0x06 +#define ScreenOffsetPtrOffset VBIOSTablePointerStart+0x08 +#define VCLKDataPtrOffset VBIOSTablePointerStart+0x0A +#define MCLKDataPtrOffset VBIOSTablePointerStart+0x0E +#define CRT2PtrDataPtrOffset VBIOSTablePointerStart+0x10 +#define TVAntiFlickPtrOffset VBIOSTablePointerStart+0x12 +#define TVDelayPtr1Offset VBIOSTablePointerStart+0x14 +#define TVPhaseIncrPtr1Offset VBIOSTablePointerStart+0x16 +#define TVYFilterPtr1Offset VBIOSTablePointerStart+0x18 +#define LCDDelayPtr1Offset VBIOSTablePointerStart+0x20 +#define TVEdgePtr1Offset VBIOSTablePointerStart+0x24 +#define CRT2Delay1Offset VBIOSTablePointerStart+0x28 + +#endif diff --git a/drivers/video/fbdev/sis/initextlfb.c b/drivers/video/fbdev/sis/initextlfb.c new file mode 100644 index 000000000000..3ab18f5a3759 --- /dev/null +++ b/drivers/video/fbdev/sis/initextlfb.c @@ -0,0 +1,231 @@ +/* + * SiS 300/540/630[S]/730[S] + * SiS 315[E|PRO]/550/[M]65x/[M]66x[F|M|G]X/[M]74x[GX]/330/[M]76x[GX] + * XGI V3XT/V5/V8, Z7 + * frame buffer driver for Linux kernels >= 2.4.14 and >=2.6.3 + * + * Linux kernel specific extensions to init.c/init301.c + * + * Copyright (C) 2001-2005 Thomas Winischhofer, Vienna, Austria. + * + * 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 named License, + * or 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 + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + */ + +#include "initdef.h" +#include "vgatypes.h" +#include "vstruct.h" + +#include <linux/types.h> +#include <linux/fb.h> + +int sisfb_mode_rate_to_dclock(struct SiS_Private *SiS_Pr, + unsigned char modeno, unsigned char rateindex); +int sisfb_mode_rate_to_ddata(struct SiS_Private *SiS_Pr, unsigned char modeno, + unsigned char rateindex, struct fb_var_screeninfo *var); +bool sisfb_gettotalfrommode(struct SiS_Private *SiS_Pr, unsigned char modeno, + int *htotal, int *vtotal, unsigned char rateindex); + +extern bool SiSInitPtr(struct SiS_Private *SiS_Pr); +extern bool SiS_SearchModeID(struct SiS_Private *SiS_Pr, unsigned short *ModeNo, + unsigned short *ModeIdIndex); +extern void SiS_Generic_ConvertCRData(struct SiS_Private *SiS_Pr, unsigned char *crdata, + int xres, int yres, struct fb_var_screeninfo *var, bool writeres); + +int +sisfb_mode_rate_to_dclock(struct SiS_Private *SiS_Pr, unsigned char modeno, + unsigned char rateindex) +{ + unsigned short ModeNo = modeno; + unsigned short ModeIdIndex = 0, ClockIndex = 0; + unsigned short RRTI = 0; + int Clock; + + if(!SiSInitPtr(SiS_Pr)) return 65000; + + if(rateindex > 0) rateindex--; + +#ifdef CONFIG_FB_SIS_315 + switch(ModeNo) { + case 0x5a: ModeNo = 0x50; break; + case 0x5b: ModeNo = 0x56; + } +#endif + + if(!(SiS_SearchModeID(SiS_Pr, &ModeNo, &ModeIdIndex))) { + printk(KERN_ERR "Could not find mode %x\n", ModeNo); + return 65000; + } + + RRTI = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].REFindex; + + if(SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag & HaveWideTiming) { + if(SiS_Pr->SiS_UseWide == 1) { + /* Wide screen: Ignore rateindex */ + ClockIndex = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRTVCLK_WIDE; + } else { + RRTI += rateindex; + ClockIndex = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRTVCLK_NORM; + } + } else { + RRTI += rateindex; + ClockIndex = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRTVCLK; + } + + Clock = SiS_Pr->SiS_VCLKData[ClockIndex].CLOCK * 1000; + + return Clock; +} + +int +sisfb_mode_rate_to_ddata(struct SiS_Private *SiS_Pr, unsigned char modeno, + unsigned char rateindex, struct fb_var_screeninfo *var) +{ + unsigned short ModeNo = modeno; + unsigned short ModeIdIndex = 0, index = 0, RRTI = 0; + int j; + + if(!SiSInitPtr(SiS_Pr)) return 0; + + if(rateindex > 0) rateindex--; + +#ifdef CONFIG_FB_SIS_315 + switch(ModeNo) { + case 0x5a: ModeNo = 0x50; break; + case 0x5b: ModeNo = 0x56; + } +#endif + + if(!(SiS_SearchModeID(SiS_Pr, &ModeNo, &ModeIdIndex))) return 0; + + RRTI = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].REFindex; + if(SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag & HaveWideTiming) { + if(SiS_Pr->SiS_UseWide == 1) { + /* Wide screen: Ignore rateindex */ + index = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRT1CRTC_WIDE; + } else { + RRTI += rateindex; + index = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRT1CRTC_NORM; + } + } else { + RRTI += rateindex; + index = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRT1CRTC; + } + + SiS_Generic_ConvertCRData(SiS_Pr, + (unsigned char *)&SiS_Pr->SiS_CRT1Table[index].CR[0], + SiS_Pr->SiS_RefIndex[RRTI].XRes, + SiS_Pr->SiS_RefIndex[RRTI].YRes, + var, false); + + if(SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag & 0x8000) + var->sync &= ~FB_SYNC_VERT_HIGH_ACT; + else + var->sync |= FB_SYNC_VERT_HIGH_ACT; + + if(SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag & 0x4000) + var->sync &= ~FB_SYNC_HOR_HIGH_ACT; + else + var->sync |= FB_SYNC_HOR_HIGH_ACT; + + var->vmode = FB_VMODE_NONINTERLACED; + if(SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag & 0x0080) + var->vmode = FB_VMODE_INTERLACED; + else { + j = 0; + while(SiS_Pr->SiS_EModeIDTable[j].Ext_ModeID != 0xff) { + if(SiS_Pr->SiS_EModeIDTable[j].Ext_ModeID == + SiS_Pr->SiS_RefIndex[RRTI].ModeID) { + if(SiS_Pr->SiS_EModeIDTable[j].Ext_ModeFlag & DoubleScanMode) { + var->vmode = FB_VMODE_DOUBLE; + } + break; + } + j++; + } + } + + if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { +#if 0 /* Do this? */ + var->upper_margin <<= 1; + var->lower_margin <<= 1; + var->vsync_len <<= 1; +#endif + } else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) { + var->upper_margin >>= 1; + var->lower_margin >>= 1; + var->vsync_len >>= 1; + } + + return 1; +} + +bool +sisfb_gettotalfrommode(struct SiS_Private *SiS_Pr, unsigned char modeno, int *htotal, + int *vtotal, unsigned char rateindex) +{ + unsigned short ModeNo = modeno; + unsigned short ModeIdIndex = 0, CRT1Index = 0; + unsigned short RRTI = 0; + unsigned char sr_data, cr_data, cr_data2; + + if(!SiSInitPtr(SiS_Pr)) return false; + + if(rateindex > 0) rateindex--; + +#ifdef CONFIG_FB_SIS_315 + switch(ModeNo) { + case 0x5a: ModeNo = 0x50; break; + case 0x5b: ModeNo = 0x56; + } +#endif + + if(!(SiS_SearchModeID(SiS_Pr, &ModeNo, &ModeIdIndex))) return false; + + RRTI = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].REFindex; + if(SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag & HaveWideTiming) { + if(SiS_Pr->SiS_UseWide == 1) { + /* Wide screen: Ignore rateindex */ + CRT1Index = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRT1CRTC_WIDE; + } else { + RRTI += rateindex; + CRT1Index = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRT1CRTC_NORM; + } + } else { + RRTI += rateindex; + CRT1Index = SiS_Pr->SiS_RefIndex[RRTI].Ext_CRT1CRTC; + } + + sr_data = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[14]; + cr_data = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[0]; + *htotal = (((cr_data & 0xff) | ((unsigned short) (sr_data & 0x03) << 8)) + 5) * 8; + + sr_data = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[13]; + cr_data = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[6]; + cr_data2 = SiS_Pr->SiS_CRT1Table[CRT1Index].CR[7]; + *vtotal = ((cr_data & 0xFF) | + ((unsigned short)(cr_data2 & 0x01) << 8) | + ((unsigned short)(cr_data2 & 0x20) << 4) | + ((unsigned short)(sr_data & 0x01) << 10)) + 2; + + if(SiS_Pr->SiS_RefIndex[RRTI].Ext_InfoFlag & InterlaceMode) + *vtotal *= 2; + + return true; +} + + + diff --git a/drivers/video/fbdev/sis/oem300.h b/drivers/video/fbdev/sis/oem300.h new file mode 100644 index 000000000000..b73f26840143 --- /dev/null +++ b/drivers/video/fbdev/sis/oem300.h @@ -0,0 +1,840 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * OEM Data for 300 series + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +static const unsigned char SiS300_OEMTVDelay301[8][4] = +{ + {0x08,0x08,0x08,0x08}, + {0x08,0x08,0x08,0x08}, + {0x08,0x08,0x08,0x08}, + {0x2c,0x2c,0x2c,0x2c}, + {0x08,0x08,0x08,0x08}, + {0x08,0x08,0x08,0x08}, + {0x08,0x08,0x08,0x08}, + {0x20,0x20,0x20,0x20} +}; + +static const unsigned char SiS300_OEMTVDelayLVDS[8][4] = +{ + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20} +}; + +static const unsigned char SiS300_OEMTVFlicker[8][4] = +{ + {0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00} +}; + +static const unsigned char SiS300_OEMLCDDelay2[64][4] = /* for 301/301b/302b/301LV/302LV */ +{ + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20} +}; + +static const unsigned char SiS300_OEMLCDDelay4[12][4] = +{ + {0x2c,0x2c,0x2c,0x2c}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x2c,0x2c,0x2c,0x2c}, + {0x2c,0x2c,0x2c,0x2c}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x24,0x24,0x24,0x24}, + {0x24,0x24,0x24,0x24}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x24,0x24,0x24,0x24} +}; + +static const unsigned char SiS300_OEMLCDDelay5[32][4] = +{ + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, +}; + +static const unsigned char SiS300_OEMLCDDelay3[64][4] = /* For LVDS */ +{ + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20}, + {0x20,0x20,0x20,0x20} +}; + +static const unsigned char SiS300_Phase1[8][5][4] = +{ + { + {0x21,0xed,0x00,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x21,0xed,0x00,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + } +}; + +static const unsigned char SiS300_Phase2[8][5][4] = +{ + { + {0x21,0xed,0x00,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x21,0xed,0x00,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08}, + {0x21,0xed,0x8a,0x08} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + } +}; + +static const unsigned char SiS300_Filter1[10][16][4] = +{ + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x10,0x18}, + {0xf7,0x06,0x19,0x14}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x15,0x25,0xf6}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x10,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xfc,0xfb,0x14,0x2a}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x10,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xfc,0xfb,0x14,0x2a}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x10,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xfc,0xfb,0x14,0x2a}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x10,0x18}, + {0xf7,0x06,0x19,0x14}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x15,0x25,0xf6}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x10,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xfc,0xfb,0x14,0x2a}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x10,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xfc,0xfb,0x14,0x2a}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x10,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xfc,0xfb,0x14,0x2a}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32}, + {0xf1,0xf7,0x1f,0x32} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x10,0x18}, + {0xf7,0x06,0x19,0x14}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x15,0x25,0xf6}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18} + }, + { + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x10,0x18}, + {0xf7,0x06,0x19,0x14}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x15,0x25,0xf6}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18} + }, +}; + +static const unsigned char SiS300_Filter2[10][9][7] = +{ + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + } +}; + +/* Custom data for Barco iQ Pro R300 */ +static const unsigned char barco_p1[2][9][7][3] = +{ + { + { + { 0x16, 0xcf, 0x00 }, + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x19, 0x00 } + }, + { + { 0x16, 0xcf, 0x00 }, + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x1e, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x16, 0x00 } + }, + { + { 0x16, 0xcf, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x19, 0x00 }, + { 0, 0, 0 } + }, + { + { 0, 0, 0 } + }, + { + { 0x16, 0xcf, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x1e, 0x00 }, + { 0, 0, 0 } + }, + { + { 0x16, 0xd1, 0x00 }, + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x11, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x26, 0x00 } + }, + { + { 0x16, 0xd1, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x30, 0x00 }, + { 0, 0, 0 } + }, + { + { 0x16, 0x00, 0x00 }, + { 0x17, 0xa0, 0x00 }, + { 0x1a, 0xa0, 0x00 }, + { 0x1b, 0x2a, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0, 0, 0 } + }, + { + { 0x16, 0x00, 0x00 }, + { 0x17, 0xaa, 0x00 }, + { 0x1a, 0xa0, 0x00 }, + { 0x1b, 0x2a, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0, 0, 0 } + } + }, + { + { + { 0x16, 0xcf, 0x00 }, + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x19, 0x00 } + }, + { + { 0, 0, 0 } + }, + { + { 0x16, 0xcf, 0x00 }, + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x19, 0x00 }, + }, + { + { 0, 0, 0 } + }, + { + { 0x16, 0xcf, 0x00 }, + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe7, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x1e, 0x00 } + }, + { + { 0x16, 0xd1, 0x00 }, + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe6, 0x00 }, + { 0x1b, 0x11, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x26, 0x00 } + }, + { + { 0x18, 0x00, 0x00 }, + { 0x1a, 0xe0, 0x00 }, + { 0x1b, 0x26, 0x00 }, + { 0x1c, 0xff, 0x00 }, + { 0x1d, 0x1c, 0x00 }, + { 0x1e, 0x30, 0x00 }, + { 0, 0, 0 } + }, + { + { 0, 0, 0 } + }, + { + { 0, 0, 0 } + } + } +}; + + + + + + diff --git a/drivers/video/fbdev/sis/oem310.h b/drivers/video/fbdev/sis/oem310.h new file mode 100644 index 000000000000..8fce56e4482c --- /dev/null +++ b/drivers/video/fbdev/sis/oem310.h @@ -0,0 +1,430 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * OEM Data for 315/330/340 series + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +static const unsigned char SiS310_LCDDelayCompensation_301[] = /* 301 */ +{ + 0x00,0x00,0x00, /* 800x600 */ + 0x0b,0x0b,0x0b, /* 1024x768 */ + 0x08,0x08,0x08, /* 1280x1024 */ + 0x00,0x00,0x00, /* 640x480 (unknown) */ + 0x00,0x00,0x00, /* 1024x600 (unknown) */ + 0x00,0x00,0x00, /* 1152x864 (unknown) */ + 0x08,0x08,0x08, /* 1280x960 (guessed) */ + 0x00,0x00,0x00, /* 1152x768 (unknown) */ + 0x08,0x08,0x08, /* 1400x1050 */ + 0x08,0x08,0x08, /* 1280x768 (guessed) */ + 0x00,0x00,0x00, /* 1600x1200 */ + 0x00,0x00,0x00, /* 320x480 (unknown) */ + 0x00,0x00,0x00, + 0x00,0x00,0x00, + 0x00,0x00,0x00 +}; + +/* This is contained in 650+301B BIOSes, but it is wrong - so we don't use it */ +static const unsigned char SiS310_LCDDelayCompensation_650301LV[] = /* 650 + 30xLV */ +{ + 0x01,0x01,0x01, /* 800x600 */ + 0x01,0x01,0x01, /* 1024x768 */ + 0x01,0x01,0x01, /* 1280x1024 */ + 0x01,0x01,0x01, /* 640x480 (unknown) */ + 0x01,0x01,0x01, /* 1024x600 (unknown) */ + 0x01,0x01,0x01, /* 1152x864 (unknown) */ + 0x01,0x01,0x01, /* 1280x960 (guessed) */ + 0x01,0x01,0x01, /* 1152x768 (unknown) */ + 0x01,0x01,0x01, /* 1400x1050 */ + 0x01,0x01,0x01, /* 1280x768 (guessed) */ + 0x01,0x01,0x01, /* 1600x1200 */ + 0x02,0x02,0x02, + 0x02,0x02,0x02, + 0x02,0x02,0x02, + 0x02,0x02,0x02 +}; + +static const unsigned char SiS310_LCDDelayCompensation_651301LV[] = /* M650/651 301LV */ +{ + 0x33,0x33,0x33, /* 800x600 (guessed) - new: PanelType, not PanelRes ! */ + 0x33,0x33,0x33, /* 1024x768 */ + 0x33,0x33,0x33, /* 1280x1024 */ + 0x33,0x33,0x33, /* 640x480 (unknown) */ + 0x33,0x33,0x33, /* 1024x600 (unknown) */ + 0x33,0x33,0x33, /* 1152x864 (unknown) */ + 0x33,0x33,0x33, /* 1280x960 (guessed) */ + 0x33,0x33,0x33, /* 1152x768 (unknown) */ + 0x33,0x33,0x33, /* 1400x1050 */ + 0x33,0x33,0x33, /* 1280x768 (guessed) */ + 0x33,0x33,0x33, /* 1600x1200 */ + 0x33,0x33,0x33, + 0x33,0x33,0x33, + 0x33,0x33,0x33, + 0x33,0x33,0x33 +}; + +static const unsigned char SiS310_LCDDelayCompensation_651302LV[] = /* M650/651 302LV */ +{ + 0x33,0x33,0x33, /* 800x600 (guessed) */ + 0x33,0x33,0x33, /* 1024x768 */ + 0x33,0x33,0x33, /* 1280x1024 */ + 0x33,0x33,0x33, /* 640x480 (unknown) */ + 0x33,0x33,0x33, /* 1024x600 (unknown) */ + 0x33,0x33,0x33, /* 1152x864 (unknown) */ + 0x33,0x33,0x33, /* 1280x960 (guessed) */ + 0x33,0x33,0x33, /* 1152x768 (unknown) */ + 0x33,0x33,0x33, /* 1400x1050 */ + 0x33,0x33,0x33, /* 1280x768 (guessed) */ + 0x33,0x33,0x33, /* 1600x1200 */ + 0x33,0x33,0x33, + 0x33,0x33,0x33, + 0x33,0x33,0x33, + 0x33,0x33,0x33 +}; + +static const unsigned char SiS310_LCDDelayCompensation_3xx301B[] = /* 30xB */ +{ + 0x01,0x01,0x01, /* 800x600 */ + 0x0C,0x0C,0x0C, /* 1024x768 */ + 0x0C,0x0C,0x0C, /* 1280x1024 */ + 0x08,0x08,0x08, /* 640x480 */ + 0x0C,0x0C,0x0C, /* 1024x600 (guessed) */ + 0x0C,0x0C,0x0C, /* 1152x864 (guessed) */ + 0x0C,0x0C,0x0C, /* 1280x960 (guessed) */ + 0x0C,0x0C,0x0C, /* 1152x768 (guessed) */ + 0x0C,0x0C,0x0C, /* 1400x1050 (guessed) */ + 0x0C,0x0C,0x0C, /* 1280x768 (guessed) */ + 0x0C,0x0C,0x0C, /* 1600x1200 (guessed) */ + 0x02,0x02,0x02, + 0x02,0x02,0x02, + 0x02,0x02,0x02, + 0x02,0x02,0x02 +}; + +static const unsigned char SiS310_LCDDelayCompensation_3xx301LV[] = /* 315+30xLV */ +{ + 0x01,0x01,0x01, /* 800x600 */ + 0x04,0x04,0x04, /* 1024x768 (A531/BIOS 1.14.05f: 4 - works with 6 */ + 0x0C,0x0C,0x0C, /* 1280x1024 */ + 0x08,0x08,0x08, /* 640x480 */ + 0x0C,0x0C,0x0C, /* 1024x600 (guessed) */ + 0x0C,0x0C,0x0C, /* 1152x864 (guessed) */ + 0x0C,0x0C,0x0C, /* 1280x960 (guessed) */ + 0x0C,0x0C,0x0C, /* 1152x768 (guessed) */ + 0x0C,0x0C,0x0C, /* 1400x1050 (guessed) */ + 0x0C,0x0C,0x0C, /* 1280x768 (guessed) */ + 0x0C,0x0C,0x0C, /* 1600x1200 (guessed) */ + 0x02,0x02,0x02, + 0x02,0x02,0x02, + 0x02,0x02,0x02, + 0x02,0x02,0x02 +}; + +static const unsigned char SiS310_TVDelayCompensation_301[] = /* 301 */ +{ + 0x02,0x02, /* NTSC Enhanced, Standard */ + 0x02,0x02, /* PAL */ + 0x08,0x0b /* HiVision */ +}; + +static const unsigned char SiS310_TVDelayCompensation_301B[] = /* 30xB, 30xLV */ +{ + 0x03,0x03, + 0x03,0x03, + 0x03,0x03 +}; + +static const unsigned char SiS310_TVDelayCompensation_740301B[] = /* 740 + 30xB (30xLV?) */ +{ + 0x05,0x05, + 0x05,0x05, + 0x05,0x05 +}; + +static const unsigned char SiS310_TVDelayCompensation_651301LV[] = /* M650, 651, 301LV */ +{ + 0x33,0x33, + 0x33,0x33, + 0x33,0x33 +}; + +static const unsigned char SiS310_TVDelayCompensation_651302LV[] = /* M650, 651, 302LV */ +{ + 0x33,0x33, + 0x33,0x33, + 0x33,0x33 +}; + +static const unsigned char SiS_TVDelay661_301[] = /* 661, 301 */ +{ + 0x44,0x44, + 0x44,0x44, + 0x00,0x00, + 0x44,0x44, + 0x44,0x44, + 0x44,0x44 +}; + +static const unsigned char SiS_TVDelay661_301B[] = /* 661, 301B et al */ +{ + 0x44,0x44, + 0x44,0x44, + 0x00,0x00, + 0x44,0x44, + 0x44,0x44, + 0x44,0x44 +}; + +static const unsigned char SiS310_TVDelayCompensation_LVDS[] = /* LVDS */ +{ + 0x0a,0x0a, + 0x0a,0x0a, + 0x0a,0x0a +}; + +static const unsigned char SiS310_TVAntiFlick1[6][2] = +{ + {0x4,0x0}, + {0x4,0x8}, + {0x0,0x0}, + {0x0,0x0}, + {0x0,0x0}, + {0x0,0x0} +}; + +static const unsigned char SiS310_TVEdge1[6][2] = +{ + {0x0,0x4}, + {0x0,0x4}, + {0x0,0x0}, + {0x0,0x0}, + {0x0,0x0}, + {0x0,0x0} +}; + +static const unsigned char SiS310_TVYFilter1[5][8][4] = +{ + { + {0x00,0xf4,0x10,0x38}, /* NTSC */ + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xf1,0x04,0x1f,0x18}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xee,0x0c,0x22,0x08}, + {0xeb,0x15,0x25,0xf6} + }, + { + {0x00,0xf4,0x10,0x38}, /* PAL */ + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0x00,0xf4,0x10,0x38}, + {0xf1,0xf7,0x1f,0x32}, + {0xf3,0x00,0x1d,0x20}, + {0xfc,0xfb,0x14,0x2a} + }, + { + {0x00,0x00,0x00,0x00}, /* HiVision */ + {0x00,0xf4,0x10,0x38}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xf7,0x06,0x19,0x14}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xee,0x0c,0x22,0x08} + }, + { + {0x00,0xf4,0x10,0x38}, /* PAL-M */ + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x10,0x18}, + {0xf7,0x06,0x19,0x14}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x15,0x25,0xf6} + }, + { + {0x00,0xf4,0x10,0x38}, /* PAL-N */ + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x10,0x18}, + {0xf7,0x06,0x19,0x14}, + {0x00,0xf4,0x10,0x38}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x04,0x25,0x18}, + {0xeb,0x15,0x25,0xf6} + } +}; + +static const unsigned char SiS310_TVYFilter2[5][9][7] = +{ + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, /* NTSC */ + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, /* PAL */ + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, /* HiVision */ + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22}, + {0x00,0x00,0x00,0xF4,0xFF,0x1C,0x22} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, /* PAL-M */ + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + }, + { + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, /* PAL-N */ + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0xFF,0x03,0x02,0xF6,0xFC,0x27,0x46}, + {0x01,0x02,0xFE,0xF7,0x03,0x27,0x3C}, + {0x01,0x01,0xFC,0xF8,0x08,0x26,0x38}, + {0xFF,0xFF,0xFC,0x00,0x0F,0x22,0x28} + } +}; + +static const unsigned char SiS310_TVPhaseIncr1[3][2][4] = +{ + { + {0x21,0xed,0xba,0x08}, + {0x21,0xed,0xba,0x08} + }, + { + {0x2a,0x05,0xe3,0x00}, + {0x2a,0x05,0xe3,0x00} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + } +}; + +static const unsigned char SiS310_TVPhaseIncr2[3][2][4] = +{ + { + {0x21,0xf0,0x7b,0xd6}, + {0x21,0xf0,0x7b,0xd6} + }, + { + {0x2a,0x0a,0x41,0xe9}, + {0x2a,0x0a,0x41,0xe9} + }, + { + {0x2a,0x05,0xd3,0x00}, + {0x2a,0x05,0xd3,0x00} + } +}; + +/**************************************************************/ +/* CUSTOM TIMING DATA --------------------------------------- */ +/**************************************************************/ + +/* Inventec / Compaq Presario 3045US, 3017 */ + +static const struct SiS_LCDData SiS310_ExtCompaq1280x1024Data[] = +{ + { 211, 60,1024, 501,1688,1066}, + { 211, 60,1024, 508,1688,1066}, + { 211, 60,1024, 501,1688,1066}, + { 211, 60,1024, 508,1688,1066}, + { 32, 15,1696, 501,1696,1066}, + { 212, 75,1024, 621,1696,1066}, + { 4, 3,1696, 810,1696,1066}, + { 1, 1,1696,1066,1696,1066} +}; + +/* Asus A2xxxH _2 */ + +static const struct SiS_Part2PortTbl SiS310_CRT2Part2_Asus1024x768_3[] = +{ + {{0x25,0x13,0xc9,0x25,0xff,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x2c,0x13,0x9a,0x25,0xff,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x25,0x13,0xc9,0x25,0xff,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + {{0x38,0x13,0x13,0x25,0xff,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}}, + {{0x38,0x13,0x16,0x25,0xff,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x36,0x13,0x13,0x25,0xff,0x5a,0x45,0x0a,0x07,0xfa,0x0a,0x24}}, + {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + {{0x25,0x13,0xc9,0x25,0xff,0x59,0x45,0x09,0x07,0xf9,0x09,0x24}} +}; + + + + diff --git a/drivers/video/fbdev/sis/sis.h b/drivers/video/fbdev/sis/sis.h new file mode 100644 index 000000000000..1987f1b7212f --- /dev/null +++ b/drivers/video/fbdev/sis/sis.h @@ -0,0 +1,586 @@ +/* + * SiS 300/540/630[S]/730[S], + * SiS 315[E|PRO]/550/[M]65x/[M]661[F|M]X/740/[M]741[GX]/330/[M]76x[GX], + * XGI V3XT/V5/V8, Z7 + * frame buffer driver for Linux kernels >=2.4.14 and >=2.6.3 + * + * Copyright (C) 2001-2005 Thomas Winischhofer, Vienna, Austria. + * + * 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 named License, + * or 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 + */ + +#ifndef _SIS_H_ +#define _SIS_H_ + +#include <video/sisfb.h> + +#include "vgatypes.h" +#include "vstruct.h" + +#define VER_MAJOR 1 +#define VER_MINOR 8 +#define VER_LEVEL 9 + +#include <linux/spinlock.h> + +#ifdef CONFIG_COMPAT +#define SIS_NEW_CONFIG_COMPAT +#endif /* CONFIG_COMPAT */ + +#undef SISFBDEBUG + +#ifdef SISFBDEBUG +#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__ , ## args) +#define TWDEBUG(x) printk(KERN_INFO x "\n"); +#else +#define DPRINTK(fmt, args...) +#define TWDEBUG(x) +#endif + +#define SISFAIL(x) do { printk(x "\n"); return -EINVAL; } while(0) + +/* To be included in pci_ids.h */ +#ifndef PCI_DEVICE_ID_SI_650_VGA +#define PCI_DEVICE_ID_SI_650_VGA 0x6325 +#endif +#ifndef PCI_DEVICE_ID_SI_650 +#define PCI_DEVICE_ID_SI_650 0x0650 +#endif +#ifndef PCI_DEVICE_ID_SI_651 +#define PCI_DEVICE_ID_SI_651 0x0651 +#endif +#ifndef PCI_DEVICE_ID_SI_740 +#define PCI_DEVICE_ID_SI_740 0x0740 +#endif +#ifndef PCI_DEVICE_ID_SI_330 +#define PCI_DEVICE_ID_SI_330 0x0330 +#endif +#ifndef PCI_DEVICE_ID_SI_660_VGA +#define PCI_DEVICE_ID_SI_660_VGA 0x6330 +#endif +#ifndef PCI_DEVICE_ID_SI_661 +#define PCI_DEVICE_ID_SI_661 0x0661 +#endif +#ifndef PCI_DEVICE_ID_SI_741 +#define PCI_DEVICE_ID_SI_741 0x0741 +#endif +#ifndef PCI_DEVICE_ID_SI_660 +#define PCI_DEVICE_ID_SI_660 0x0660 +#endif +#ifndef PCI_DEVICE_ID_SI_760 +#define PCI_DEVICE_ID_SI_760 0x0760 +#endif +#ifndef PCI_DEVICE_ID_SI_761 +#define PCI_DEVICE_ID_SI_761 0x0761 +#endif + +#ifndef PCI_VENDOR_ID_XGI +#define PCI_VENDOR_ID_XGI 0x18ca +#endif + +#ifndef PCI_DEVICE_ID_XGI_20 +#define PCI_DEVICE_ID_XGI_20 0x0020 +#endif + +#ifndef PCI_DEVICE_ID_XGI_40 +#define PCI_DEVICE_ID_XGI_40 0x0040 +#endif + +/* To be included in fb.h */ +#ifndef FB_ACCEL_SIS_GLAMOUR_2 +#define FB_ACCEL_SIS_GLAMOUR_2 40 /* SiS 315, 65x, 740, 661, 741 */ +#endif +#ifndef FB_ACCEL_SIS_XABRE +#define FB_ACCEL_SIS_XABRE 41 /* SiS 330 ("Xabre"), 76x */ +#endif +#ifndef FB_ACCEL_XGI_VOLARI_V +#define FB_ACCEL_XGI_VOLARI_V 47 /* XGI Volari Vx (V3XT, V5, V8) */ +#endif +#ifndef FB_ACCEL_XGI_VOLARI_Z +#define FB_ACCEL_XGI_VOLARI_Z 48 /* XGI Volari Z7 */ +#endif + +/* ivideo->caps */ +#define HW_CURSOR_CAP 0x80 +#define TURBO_QUEUE_CAP 0x40 +#define AGP_CMD_QUEUE_CAP 0x20 +#define VM_CMD_QUEUE_CAP 0x10 +#define MMIO_CMD_QUEUE_CAP 0x08 + +/* For 300 series */ +#define TURBO_QUEUE_AREA_SIZE (512 * 1024) /* 512K */ +#define HW_CURSOR_AREA_SIZE_300 4096 /* 4K */ + +/* For 315/Xabre series */ +#define COMMAND_QUEUE_AREA_SIZE (512 * 1024) /* 512K */ +#define COMMAND_QUEUE_AREA_SIZE_Z7 (128 * 1024) /* 128k for XGI Z7 */ +#define HW_CURSOR_AREA_SIZE_315 16384 /* 16K */ +#define COMMAND_QUEUE_THRESHOLD 0x1F + +#define SIS_OH_ALLOC_SIZE 4000 +#define SENTINEL 0x7fffffff + +#define SEQ_ADR 0x14 +#define SEQ_DATA 0x15 +#define DAC_ADR 0x18 +#define DAC_DATA 0x19 +#define CRTC_ADR 0x24 +#define CRTC_DATA 0x25 +#define DAC2_ADR (0x16-0x30) +#define DAC2_DATA (0x17-0x30) +#define VB_PART1_ADR (0x04-0x30) +#define VB_PART1_DATA (0x05-0x30) +#define VB_PART2_ADR (0x10-0x30) +#define VB_PART2_DATA (0x11-0x30) +#define VB_PART3_ADR (0x12-0x30) +#define VB_PART3_DATA (0x13-0x30) +#define VB_PART4_ADR (0x14-0x30) +#define VB_PART4_DATA (0x15-0x30) + +#define SISSR ivideo->SiS_Pr.SiS_P3c4 +#define SISCR ivideo->SiS_Pr.SiS_P3d4 +#define SISDACA ivideo->SiS_Pr.SiS_P3c8 +#define SISDACD ivideo->SiS_Pr.SiS_P3c9 +#define SISPART1 ivideo->SiS_Pr.SiS_Part1Port +#define SISPART2 ivideo->SiS_Pr.SiS_Part2Port +#define SISPART3 ivideo->SiS_Pr.SiS_Part3Port +#define SISPART4 ivideo->SiS_Pr.SiS_Part4Port +#define SISPART5 ivideo->SiS_Pr.SiS_Part5Port +#define SISDAC2A SISPART5 +#define SISDAC2D (SISPART5 + 1) +#define SISMISCR (ivideo->SiS_Pr.RelIO + 0x1c) +#define SISMISCW ivideo->SiS_Pr.SiS_P3c2 +#define SISINPSTAT (ivideo->SiS_Pr.RelIO + 0x2a) +#define SISPEL ivideo->SiS_Pr.SiS_P3c6 +#define SISVGAENABLE (ivideo->SiS_Pr.RelIO + 0x13) +#define SISVID (ivideo->SiS_Pr.RelIO + 0x02 - 0x30) +#define SISCAP (ivideo->SiS_Pr.RelIO + 0x00 - 0x30) + +#define IND_SIS_PASSWORD 0x05 /* SRs */ +#define IND_SIS_COLOR_MODE 0x06 +#define IND_SIS_RAMDAC_CONTROL 0x07 +#define IND_SIS_DRAM_SIZE 0x14 +#define IND_SIS_MODULE_ENABLE 0x1E +#define IND_SIS_PCI_ADDRESS_SET 0x20 +#define IND_SIS_TURBOQUEUE_ADR 0x26 +#define IND_SIS_TURBOQUEUE_SET 0x27 +#define IND_SIS_POWER_ON_TRAP 0x38 +#define IND_SIS_POWER_ON_TRAP2 0x39 +#define IND_SIS_CMDQUEUE_SET 0x26 +#define IND_SIS_CMDQUEUE_THRESHOLD 0x27 + +#define IND_SIS_AGP_IO_PAD 0x48 + +#define SIS_CRT2_WENABLE_300 0x24 /* Part1 */ +#define SIS_CRT2_WENABLE_315 0x2F + +#define SIS_PASSWORD 0x86 /* SR05 */ + +#define SIS_INTERLACED_MODE 0x20 /* SR06 */ +#define SIS_8BPP_COLOR_MODE 0x0 +#define SIS_15BPP_COLOR_MODE 0x1 +#define SIS_16BPP_COLOR_MODE 0x2 +#define SIS_32BPP_COLOR_MODE 0x4 + +#define SIS_ENABLE_2D 0x40 /* SR1E */ + +#define SIS_MEM_MAP_IO_ENABLE 0x01 /* SR20 */ +#define SIS_PCI_ADDR_ENABLE 0x80 + +#define SIS_AGP_CMDQUEUE_ENABLE 0x80 /* 315/330/340 series SR26 */ +#define SIS_VRAM_CMDQUEUE_ENABLE 0x40 +#define SIS_MMIO_CMD_ENABLE 0x20 +#define SIS_CMD_QUEUE_SIZE_512k 0x00 +#define SIS_CMD_QUEUE_SIZE_1M 0x04 +#define SIS_CMD_QUEUE_SIZE_2M 0x08 +#define SIS_CMD_QUEUE_SIZE_4M 0x0C +#define SIS_CMD_QUEUE_RESET 0x01 +#define SIS_CMD_AUTO_CORR 0x02 + +#define SIS_CMD_QUEUE_SIZE_Z7_64k 0x00 /* XGI Z7 */ +#define SIS_CMD_QUEUE_SIZE_Z7_128k 0x04 + +#define SIS_SIMULTANEOUS_VIEW_ENABLE 0x01 /* CR30 */ +#define SIS_MODE_SELECT_CRT2 0x02 +#define SIS_VB_OUTPUT_COMPOSITE 0x04 +#define SIS_VB_OUTPUT_SVIDEO 0x08 +#define SIS_VB_OUTPUT_SCART 0x10 +#define SIS_VB_OUTPUT_LCD 0x20 +#define SIS_VB_OUTPUT_CRT2 0x40 +#define SIS_VB_OUTPUT_HIVISION 0x80 + +#define SIS_VB_OUTPUT_DISABLE 0x20 /* CR31 */ +#define SIS_DRIVER_MODE 0x40 + +#define SIS_VB_COMPOSITE 0x01 /* CR32 */ +#define SIS_VB_SVIDEO 0x02 +#define SIS_VB_SCART 0x04 +#define SIS_VB_LCD 0x08 +#define SIS_VB_CRT2 0x10 +#define SIS_CRT1 0x20 +#define SIS_VB_HIVISION 0x40 +#define SIS_VB_YPBPR 0x80 +#define SIS_VB_TV (SIS_VB_COMPOSITE | SIS_VB_SVIDEO | \ + SIS_VB_SCART | SIS_VB_HIVISION | SIS_VB_YPBPR) + +#define SIS_EXTERNAL_CHIP_MASK 0x0E /* CR37 (< SiS 660) */ +#define SIS_EXTERNAL_CHIP_SIS301 0x01 /* in CR37 << 1 ! */ +#define SIS_EXTERNAL_CHIP_LVDS 0x02 +#define SIS_EXTERNAL_CHIP_TRUMPION 0x03 +#define SIS_EXTERNAL_CHIP_LVDS_CHRONTEL 0x04 +#define SIS_EXTERNAL_CHIP_CHRONTEL 0x05 +#define SIS310_EXTERNAL_CHIP_LVDS 0x02 +#define SIS310_EXTERNAL_CHIP_LVDS_CHRONTEL 0x03 + +#define SIS_AGP_2X 0x20 /* CR48 */ + +/* vbflags, private entries (others in sisfb.h) */ +#define VB_CONEXANT 0x00000800 /* 661 series only */ +#define VB_TRUMPION VB_CONEXANT /* 300 series only */ +#define VB_302ELV 0x00004000 +#define VB_301 0x00100000 /* Video bridge type */ +#define VB_301B 0x00200000 +#define VB_302B 0x00400000 +#define VB_30xBDH 0x00800000 /* 30xB DH version (w/o LCD support) */ +#define VB_LVDS 0x01000000 +#define VB_CHRONTEL 0x02000000 +#define VB_301LV 0x04000000 +#define VB_302LV 0x08000000 +#define VB_301C 0x10000000 + +#define VB_SISBRIDGE (VB_301|VB_301B|VB_301C|VB_302B|VB_301LV|VB_302LV|VB_302ELV) +#define VB_VIDEOBRIDGE (VB_SISBRIDGE | VB_LVDS | VB_CHRONTEL | VB_CONEXANT) + +/* vbflags2 (static stuff only!) */ +#define VB2_SISUMC 0x00000001 +#define VB2_301 0x00000002 /* Video bridge type */ +#define VB2_301B 0x00000004 +#define VB2_301C 0x00000008 +#define VB2_307T 0x00000010 +#define VB2_302B 0x00000800 +#define VB2_301LV 0x00001000 +#define VB2_302LV 0x00002000 +#define VB2_302ELV 0x00004000 +#define VB2_307LV 0x00008000 +#define VB2_30xBDH 0x08000000 /* 30xB DH version (w/o LCD support) */ +#define VB2_CONEXANT 0x10000000 +#define VB2_TRUMPION 0x20000000 +#define VB2_LVDS 0x40000000 +#define VB2_CHRONTEL 0x80000000 + +#define VB2_SISLVDSBRIDGE (VB2_301LV | VB2_302LV | VB2_302ELV | VB2_307LV) +#define VB2_SISTMDSBRIDGE (VB2_301 | VB2_301B | VB2_301C | VB2_302B | VB2_307T) +#define VB2_SISBRIDGE (VB2_SISLVDSBRIDGE | VB2_SISTMDSBRIDGE) + +#define VB2_SISTMDSLCDABRIDGE (VB2_301C | VB2_307T) +#define VB2_SISLCDABRIDGE (VB2_SISTMDSLCDABRIDGE | VB2_301LV | VB2_302LV | VB2_302ELV | VB2_307LV) + +#define VB2_SISHIVISIONBRIDGE (VB2_301 | VB2_301B | VB2_302B) +#define VB2_SISYPBPRBRIDGE (VB2_301C | VB2_307T | VB2_SISLVDSBRIDGE) +#define VB2_SISYPBPRARBRIDGE (VB2_301C | VB2_307T | VB2_307LV) +#define VB2_SISTAP4SCALER (VB2_301C | VB2_307T | VB2_302ELV | VB2_307LV) +#define VB2_SISTVBRIDGE (VB2_SISHIVISIONBRIDGE | VB2_SISYPBPRBRIDGE) + +#define VB2_SISVGA2BRIDGE (VB2_301 | VB2_301B | VB2_301C | VB2_302B | VB2_307T) + +#define VB2_VIDEOBRIDGE (VB2_SISBRIDGE | VB2_LVDS | VB2_CHRONTEL | VB2_CONEXANT) + +#define VB2_30xB (VB2_301B | VB2_301C | VB2_302B | VB2_307T) +#define VB2_30xBLV (VB2_30xB | VB2_SISLVDSBRIDGE) +#define VB2_30xC (VB2_301C | VB2_307T) +#define VB2_30xCLV (VB2_301C | VB2_307T | VB2_302ELV| VB2_307LV) +#define VB2_SISEMIBRIDGE (VB2_302LV | VB2_302ELV | VB2_307LV) +#define VB2_LCD162MHZBRIDGE (VB2_301C | VB2_307T) +#define VB2_LCDOVER1280BRIDGE (VB2_301C | VB2_307T | VB2_302LV | VB2_302ELV | VB2_307LV) +#define VB2_LCDOVER1600BRIDGE (VB2_307T | VB2_307LV) +#define VB2_RAMDAC202MHZBRIDGE (VB2_301C | VB2_307T) + +/* I/O port access functions */ + +void SiS_SetReg(SISIOADDRESS, u8, u8); +void SiS_SetRegByte(SISIOADDRESS, u8); +void SiS_SetRegShort(SISIOADDRESS, u16); +void SiS_SetRegLong(SISIOADDRESS, u32); +void SiS_SetRegANDOR(SISIOADDRESS, u8, u8, u8); +void SiS_SetRegAND(SISIOADDRESS, u8, u8); +void SiS_SetRegOR(SISIOADDRESS, u8, u8); +u8 SiS_GetReg(SISIOADDRESS, u8); +u8 SiS_GetRegByte(SISIOADDRESS); +u16 SiS_GetRegShort(SISIOADDRESS); +u32 SiS_GetRegLong(SISIOADDRESS); + +/* MMIO access macros */ +#define MMIO_IN8(base, offset) readb((base+offset)) +#define MMIO_IN16(base, offset) readw((base+offset)) +#define MMIO_IN32(base, offset) readl((base+offset)) + +#define MMIO_OUT8(base, offset, val) writeb(((u8)(val)), (base+offset)) +#define MMIO_OUT16(base, offset, val) writew(((u16)(val)), (base+offset)) +#define MMIO_OUT32(base, offset, val) writel(((u32)(val)), (base+offset)) + +/* Queue control MMIO registers */ +#define Q_BASE_ADDR 0x85C0 /* Base address of software queue */ +#define Q_WRITE_PTR 0x85C4 /* Current write pointer */ +#define Q_READ_PTR 0x85C8 /* Current read pointer */ +#define Q_STATUS 0x85CC /* queue status */ + +#define MMIO_QUEUE_PHYBASE Q_BASE_ADDR +#define MMIO_QUEUE_WRITEPORT Q_WRITE_PTR +#define MMIO_QUEUE_READPORT Q_READ_PTR + +#ifndef FB_BLANK_UNBLANK +#define FB_BLANK_UNBLANK 0 +#endif +#ifndef FB_BLANK_NORMAL +#define FB_BLANK_NORMAL 1 +#endif +#ifndef FB_BLANK_VSYNC_SUSPEND +#define FB_BLANK_VSYNC_SUSPEND 2 +#endif +#ifndef FB_BLANK_HSYNC_SUSPEND +#define FB_BLANK_HSYNC_SUSPEND 3 +#endif +#ifndef FB_BLANK_POWERDOWN +#define FB_BLANK_POWERDOWN 4 +#endif + +enum _SIS_LCD_TYPE { + LCD_INVALID = 0, + LCD_800x600, + LCD_1024x768, + LCD_1280x1024, + LCD_1280x960, + LCD_640x480, + LCD_1600x1200, + LCD_1920x1440, + LCD_2048x1536, + LCD_320x240, /* FSTN */ + LCD_1400x1050, + LCD_1152x864, + LCD_1152x768, + LCD_1280x768, + LCD_1024x600, + LCD_320x240_2, /* DSTN */ + LCD_320x240_3, /* DSTN */ + LCD_848x480, + LCD_1280x800, + LCD_1680x1050, + LCD_1280x720, + LCD_1280x854, + LCD_CUSTOM, + LCD_UNKNOWN +}; + +enum _SIS_CMDTYPE { + MMIO_CMD = 0, + AGP_CMD_QUEUE, + VM_CMD_QUEUE, +}; + +struct SIS_OH { + struct SIS_OH *poh_next; + struct SIS_OH *poh_prev; + u32 offset; + u32 size; +}; + +struct SIS_OHALLOC { + struct SIS_OHALLOC *poha_next; + struct SIS_OH aoh[1]; +}; + +struct SIS_HEAP { + struct SIS_OH oh_free; + struct SIS_OH oh_used; + struct SIS_OH *poh_freelist; + struct SIS_OHALLOC *poha_chain; + u32 max_freesize; + struct sis_video_info *vinfo; +}; + +/* Our "par" */ +struct sis_video_info { + int cardnumber; + struct fb_info *memyselfandi; + + struct SiS_Private SiS_Pr; + + struct sisfb_info sisfbinfo; /* For ioctl SISFB_GET_INFO */ + + struct fb_var_screeninfo default_var; + + struct fb_fix_screeninfo sisfb_fix; + u32 pseudo_palette[16]; + + struct sisfb_monitor { + u16 hmin; + u16 hmax; + u16 vmin; + u16 vmax; + u32 dclockmax; + u8 feature; + bool datavalid; + } sisfb_thismonitor; + + unsigned short chip_id; /* PCI ID of chip */ + unsigned short chip_vendor; /* PCI ID of vendor */ + char myid[40]; + + struct pci_dev *nbridge; + struct pci_dev *lpcdev; + + int mni; /* Mode number index */ + + unsigned long video_size; + unsigned long video_base; + unsigned long mmio_size; + unsigned long mmio_base; + unsigned long vga_base; + + unsigned long video_offset; + + unsigned long UMAsize, LFBsize; + + void __iomem *video_vbase; + void __iomem *mmio_vbase; + + unsigned char *bios_abase; + + int mtrr; + + u32 sisfb_mem; + + u32 sisfb_parm_mem; + int sisfb_accel; + int sisfb_ypan; + int sisfb_max; + int sisfb_userom; + int sisfb_useoem; + int sisfb_mode_idx; + int sisfb_parm_rate; + int sisfb_crt1off; + int sisfb_forcecrt1; + int sisfb_crt2type; + int sisfb_crt2flags; + int sisfb_dstn; + int sisfb_fstn; + int sisfb_tvplug; + int sisfb_tvstd; + int sisfb_nocrt2rate; + + u32 heapstart; /* offset */ + void __iomem *sisfb_heap_start; /* address */ + void __iomem *sisfb_heap_end; /* address */ + u32 sisfb_heap_size; + int havenoheap; + + struct SIS_HEAP sisfb_heap; /* This card's vram heap */ + + int video_bpp; + int video_cmap_len; + int video_width; + int video_height; + unsigned int refresh_rate; + + unsigned int chip; + unsigned int chip_real_id; + u8 revision_id; + int sisvga_enabled; /* PCI device was enabled */ + + int video_linelength; /* real pitch */ + int scrnpitchCRT1; /* pitch regarding interlace */ + + u16 DstColor; /* For 2d acceleration */ + u32 SiS310_AccelDepth; + u32 CommandReg; + int cmdqueuelength; /* Current (for accel) */ + u32 cmdQueueSize; /* Total size in KB */ + + spinlock_t lockaccel; /* Do not use outside of kernel! */ + + unsigned int pcibus; + unsigned int pcislot; + unsigned int pcifunc; + + int accel; + int engineok; + + u16 subsysvendor; + u16 subsysdevice; + + u32 vbflags; /* Replacing deprecated stuff from above */ + u32 currentvbflags; + u32 vbflags2; + + int lcdxres, lcdyres; + int lcddefmodeidx, tvdefmodeidx, defmodeidx; + u32 CRT2LCDType; /* defined in "SIS_LCD_TYPE" */ + u32 curFSTN, curDSTN; + + int current_bpp; + int current_width; + int current_height; + int current_htotal; + int current_vtotal; + int current_linelength; + __u32 current_pixclock; + int current_refresh_rate; + + unsigned int current_base; + + u8 mode_no; + u8 rate_idx; + int modechanged; + unsigned char modeprechange; + + u8 sisfb_lastrates[128]; + + int newrom; + int haveXGIROM; + int registered; + int warncount; + + int sisvga_engine; + int hwcursor_size; + int CRT2_write_enable; + u8 caps; + + u8 detectedpdc; + u8 detectedpdca; + u8 detectedlcda; + + void __iomem *hwcursor_vbase; + + int chronteltype; + int tvxpos, tvypos; + u8 p2_1f,p2_20,p2_2b,p2_42,p2_43,p2_01,p2_02; + int tvx, tvy; + + u8 sisfblocked; + + struct sisfb_info sisfb_infoblock; + + struct sisfb_cmd sisfb_command; + + u32 sisfb_id; + + u8 sisfb_can_post; + u8 sisfb_card_posted; + u8 sisfb_was_boot_device; + + struct sis_video_info *next; +}; + +#endif diff --git a/drivers/video/fbdev/sis/sis_accel.c b/drivers/video/fbdev/sis/sis_accel.c new file mode 100644 index 000000000000..ceb434c95c0d --- /dev/null +++ b/drivers/video/fbdev/sis/sis_accel.c @@ -0,0 +1,423 @@ +/* + * SiS 300/540/630[S]/730[S], + * SiS 315[E|PRO]/550/[M]650/651/[M]661[F|M]X/740/[M]741[GX]/330/[M]760[GX], + * XGI V3XT/V5/V8, Z7 + * frame buffer driver for Linux kernels >= 2.4.14 and >=2.6.3 + * + * 2D acceleration part + * + * 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 named License, + * or 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 + * + * Based on the XFree86/X.org driver which is + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * (see http://www.winischhofer.net/ + * for more information and updates) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <asm/io.h> + +#include "sis.h" +#include "sis_accel.h" + +static const u8 sisALUConv[] = +{ + 0x00, /* dest = 0; 0, GXclear, 0 */ + 0x88, /* dest &= src; DSa, GXand, 0x1 */ + 0x44, /* dest = src & ~dest; SDna, GXandReverse, 0x2 */ + 0xCC, /* dest = src; S, GXcopy, 0x3 */ + 0x22, /* dest &= ~src; DSna, GXandInverted, 0x4 */ + 0xAA, /* dest = dest; D, GXnoop, 0x5 */ + 0x66, /* dest = ^src; DSx, GXxor, 0x6 */ + 0xEE, /* dest |= src; DSo, GXor, 0x7 */ + 0x11, /* dest = ~src & ~dest; DSon, GXnor, 0x8 */ + 0x99, /* dest ^= ~src ; DSxn, GXequiv, 0x9 */ + 0x55, /* dest = ~dest; Dn, GXInvert, 0xA */ + 0xDD, /* dest = src|~dest ; SDno, GXorReverse, 0xB */ + 0x33, /* dest = ~src; Sn, GXcopyInverted, 0xC */ + 0xBB, /* dest |= ~src; DSno, GXorInverted, 0xD */ + 0x77, /* dest = ~src|~dest; DSan, GXnand, 0xE */ + 0xFF, /* dest = 0xFF; 1, GXset, 0xF */ +}; +/* same ROP but with Pattern as Source */ +static const u8 sisPatALUConv[] = +{ + 0x00, /* dest = 0; 0, GXclear, 0 */ + 0xA0, /* dest &= src; DPa, GXand, 0x1 */ + 0x50, /* dest = src & ~dest; PDna, GXandReverse, 0x2 */ + 0xF0, /* dest = src; P, GXcopy, 0x3 */ + 0x0A, /* dest &= ~src; DPna, GXandInverted, 0x4 */ + 0xAA, /* dest = dest; D, GXnoop, 0x5 */ + 0x5A, /* dest = ^src; DPx, GXxor, 0x6 */ + 0xFA, /* dest |= src; DPo, GXor, 0x7 */ + 0x05, /* dest = ~src & ~dest; DPon, GXnor, 0x8 */ + 0xA5, /* dest ^= ~src ; DPxn, GXequiv, 0x9 */ + 0x55, /* dest = ~dest; Dn, GXInvert, 0xA */ + 0xF5, /* dest = src|~dest ; PDno, GXorReverse, 0xB */ + 0x0F, /* dest = ~src; Pn, GXcopyInverted, 0xC */ + 0xAF, /* dest |= ~src; DPno, GXorInverted, 0xD */ + 0x5F, /* dest = ~src|~dest; DPan, GXnand, 0xE */ + 0xFF, /* dest = 0xFF; 1, GXset, 0xF */ +}; + +static const int myrops[] = { + 3, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 +}; + +/* 300 series ----------------------------------------------------- */ +#ifdef CONFIG_FB_SIS_300 +static void +SiS300Sync(struct sis_video_info *ivideo) +{ + SiS300Idle +} + +static void +SiS300SetupForScreenToScreenCopy(struct sis_video_info *ivideo, int xdir, int ydir, + int rop, int trans_color) +{ + SiS300SetupDSTColorDepth(ivideo->DstColor); + SiS300SetupSRCPitch(ivideo->video_linelength) + SiS300SetupDSTRect(ivideo->video_linelength, 0xffff) + + if(trans_color != -1) { + SiS300SetupROP(0x0A) + SiS300SetupSRCTrans(trans_color) + SiS300SetupCMDFlag(TRANSPARENT_BITBLT) + } else { + SiS300SetupROP(sisALUConv[rop]) + } + if(xdir > 0) { + SiS300SetupCMDFlag(X_INC) + } + if(ydir > 0) { + SiS300SetupCMDFlag(Y_INC) + } +} + +static void +SiS300SubsequentScreenToScreenCopy(struct sis_video_info *ivideo, int src_x, + int src_y, int dst_x, int dst_y, int width, int height) +{ + u32 srcbase = 0, dstbase = 0; + + if(src_y >= 2048) { + srcbase = ivideo->video_linelength * src_y; + src_y = 0; + } + if(dst_y >= 2048) { + dstbase = ivideo->video_linelength * dst_y; + dst_y = 0; + } + + SiS300SetupSRCBase(srcbase); + SiS300SetupDSTBase(dstbase); + + if(!(ivideo->CommandReg & X_INC)) { + src_x += width-1; + dst_x += width-1; + } + if(!(ivideo->CommandReg & Y_INC)) { + src_y += height-1; + dst_y += height-1; + } + SiS300SetupRect(width, height) + SiS300SetupSRCXY(src_x, src_y) + SiS300SetupDSTXY(dst_x, dst_y) + SiS300DoCMD +} + +static void +SiS300SetupForSolidFill(struct sis_video_info *ivideo, u32 color, int rop) +{ + SiS300SetupPATFG(color) + SiS300SetupDSTRect(ivideo->video_linelength, 0xffff) + SiS300SetupDSTColorDepth(ivideo->DstColor); + SiS300SetupROP(sisPatALUConv[rop]) + SiS300SetupCMDFlag(PATFG) +} + +static void +SiS300SubsequentSolidFillRect(struct sis_video_info *ivideo, int x, int y, int w, int h) +{ + u32 dstbase = 0; + + if(y >= 2048) { + dstbase = ivideo->video_linelength * y; + y = 0; + } + SiS300SetupDSTBase(dstbase) + SiS300SetupDSTXY(x,y) + SiS300SetupRect(w,h) + SiS300SetupCMDFlag(X_INC | Y_INC | BITBLT) + SiS300DoCMD +} +#endif + +/* 315/330/340 series ---------------------------------------------- */ + +#ifdef CONFIG_FB_SIS_315 +static void +SiS310Sync(struct sis_video_info *ivideo) +{ + SiS310Idle +} + +static void +SiS310SetupForScreenToScreenCopy(struct sis_video_info *ivideo, int rop, int trans_color) +{ + SiS310SetupDSTColorDepth(ivideo->DstColor); + SiS310SetupSRCPitch(ivideo->video_linelength) + SiS310SetupDSTRect(ivideo->video_linelength, 0x0fff) + if(trans_color != -1) { + SiS310SetupROP(0x0A) + SiS310SetupSRCTrans(trans_color) + SiS310SetupCMDFlag(TRANSPARENT_BITBLT) + } else { + SiS310SetupROP(sisALUConv[rop]) + /* Set command - not needed, both 0 */ + /* SiSSetupCMDFlag(BITBLT | SRCVIDEO) */ + } + SiS310SetupCMDFlag(ivideo->SiS310_AccelDepth) + /* The chip is smart enough to know the direction */ +} + +static void +SiS310SubsequentScreenToScreenCopy(struct sis_video_info *ivideo, int src_x, int src_y, + int dst_x, int dst_y, int width, int height) +{ + u32 srcbase = 0, dstbase = 0; + int mymin = min(src_y, dst_y); + int mymax = max(src_y, dst_y); + + /* Although the chip knows the direction to use + * if the source and destination areas overlap, + * that logic fails if we fiddle with the bitmap + * addresses. Therefore, we check if the source + * and destination blitting areas overlap and + * adapt the bitmap addresses synchronously + * if the coordinates exceed the valid range. + * The the areas do not overlap, we do our + * normal check. + */ + if((mymax - mymin) < height) { + if((src_y >= 2048) || (dst_y >= 2048)) { + srcbase = ivideo->video_linelength * mymin; + dstbase = ivideo->video_linelength * mymin; + src_y -= mymin; + dst_y -= mymin; + } + } else { + if(src_y >= 2048) { + srcbase = ivideo->video_linelength * src_y; + src_y = 0; + } + if(dst_y >= 2048) { + dstbase = ivideo->video_linelength * dst_y; + dst_y = 0; + } + } + + srcbase += ivideo->video_offset; + dstbase += ivideo->video_offset; + + SiS310SetupSRCBase(srcbase); + SiS310SetupDSTBase(dstbase); + SiS310SetupRect(width, height) + SiS310SetupSRCXY(src_x, src_y) + SiS310SetupDSTXY(dst_x, dst_y) + SiS310DoCMD +} + +static void +SiS310SetupForSolidFill(struct sis_video_info *ivideo, u32 color, int rop) +{ + SiS310SetupPATFG(color) + SiS310SetupDSTRect(ivideo->video_linelength, 0x0fff) + SiS310SetupDSTColorDepth(ivideo->DstColor); + SiS310SetupROP(sisPatALUConv[rop]) + SiS310SetupCMDFlag(PATFG | ivideo->SiS310_AccelDepth) +} + +static void +SiS310SubsequentSolidFillRect(struct sis_video_info *ivideo, int x, int y, int w, int h) +{ + u32 dstbase = 0; + + if(y >= 2048) { + dstbase = ivideo->video_linelength * y; + y = 0; + } + dstbase += ivideo->video_offset; + SiS310SetupDSTBase(dstbase) + SiS310SetupDSTXY(x,y) + SiS310SetupRect(w,h) + SiS310SetupCMDFlag(BITBLT) + SiS310DoCMD +} +#endif + +/* --------------------------------------------------------------------- */ + +/* The exported routines */ + +int sisfb_initaccel(struct sis_video_info *ivideo) +{ +#ifdef SISFB_USE_SPINLOCKS + spin_lock_init(&ivideo->lockaccel); +#endif + return 0; +} + +void sisfb_syncaccel(struct sis_video_info *ivideo) +{ + if(ivideo->sisvga_engine == SIS_300_VGA) { +#ifdef CONFIG_FB_SIS_300 + SiS300Sync(ivideo); +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + SiS310Sync(ivideo); +#endif + } +} + +int fbcon_sis_sync(struct fb_info *info) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + CRITFLAGS + + if((!ivideo->accel) || (!ivideo->engineok)) + return 0; + + CRITBEGIN + sisfb_syncaccel(ivideo); + CRITEND + + return 0; +} + +void fbcon_sis_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + u32 col = 0; + u32 vxres = info->var.xres_virtual; + u32 vyres = info->var.yres_virtual; + int width, height; + CRITFLAGS + + if(info->state != FBINFO_STATE_RUNNING) + return; + + if((!ivideo->accel) || (!ivideo->engineok)) { + cfb_fillrect(info, rect); + return; + } + + if(!rect->width || !rect->height || rect->dx >= vxres || rect->dy >= vyres) + return; + + /* Clipping */ + width = ((rect->dx + rect->width) > vxres) ? (vxres - rect->dx) : rect->width; + height = ((rect->dy + rect->height) > vyres) ? (vyres - rect->dy) : rect->height; + + switch(info->var.bits_per_pixel) { + case 8: col = rect->color; + break; + case 16: + case 32: col = ((u32 *)(info->pseudo_palette))[rect->color]; + break; + } + + if(ivideo->sisvga_engine == SIS_300_VGA) { +#ifdef CONFIG_FB_SIS_300 + CRITBEGIN + SiS300SetupForSolidFill(ivideo, col, myrops[rect->rop]); + SiS300SubsequentSolidFillRect(ivideo, rect->dx, rect->dy, width, height); + CRITEND +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + CRITBEGIN + SiS310SetupForSolidFill(ivideo, col, myrops[rect->rop]); + SiS310SubsequentSolidFillRect(ivideo, rect->dx, rect->dy, width, height); + CRITEND +#endif + } + + sisfb_syncaccel(ivideo); +} + +void fbcon_sis_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + u32 vxres = info->var.xres_virtual; + u32 vyres = info->var.yres_virtual; + int width = area->width; + int height = area->height; + CRITFLAGS + + if(info->state != FBINFO_STATE_RUNNING) + return; + + if((!ivideo->accel) || (!ivideo->engineok)) { + cfb_copyarea(info, area); + return; + } + + if(!width || !height || + area->sx >= vxres || area->sy >= vyres || + area->dx >= vxres || area->dy >= vyres) + return; + + /* Clipping */ + if((area->sx + width) > vxres) width = vxres - area->sx; + if((area->dx + width) > vxres) width = vxres - area->dx; + if((area->sy + height) > vyres) height = vyres - area->sy; + if((area->dy + height) > vyres) height = vyres - area->dy; + + if(ivideo->sisvga_engine == SIS_300_VGA) { +#ifdef CONFIG_FB_SIS_300 + int xdir, ydir; + + if(area->sx < area->dx) xdir = 0; + else xdir = 1; + if(area->sy < area->dy) ydir = 0; + else ydir = 1; + + CRITBEGIN + SiS300SetupForScreenToScreenCopy(ivideo, xdir, ydir, 3, -1); + SiS300SubsequentScreenToScreenCopy(ivideo, area->sx, area->sy, + area->dx, area->dy, width, height); + CRITEND +#endif + } else { +#ifdef CONFIG_FB_SIS_315 + CRITBEGIN + SiS310SetupForScreenToScreenCopy(ivideo, 3, -1); + SiS310SubsequentScreenToScreenCopy(ivideo, area->sx, area->sy, + area->dx, area->dy, width, height); + CRITEND +#endif + } + + sisfb_syncaccel(ivideo); +} diff --git a/drivers/video/fbdev/sis/sis_accel.h b/drivers/video/fbdev/sis/sis_accel.h new file mode 100644 index 000000000000..30e03cdf6b85 --- /dev/null +++ b/drivers/video/fbdev/sis/sis_accel.h @@ -0,0 +1,400 @@ +/* + * SiS 300/540/630[S]/730[S], + * SiS 315[E|PRO]/550/[M]650/651/[M]661[F|M]X/740/[M]741[GX]/330/[M]760[GX], + * XGI V3XT/V5/V8, Z7 + * frame buffer driver for Linux kernels >= 2.4.14 and >=2.6.3 + * + * 2D acceleration part + * + * 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 named License, + * or 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 + * + * Based on the X driver's sis300_accel.h which is + * Copyright (C) 2001-2004 by Thomas Winischhofer, Vienna, Austria + * and sis310_accel.h which is + * Copyright (C) 2001-2004 by Thomas Winischhofer, Vienna, Austria + * + * Author: Thomas Winischhofer <thomas@winischhofer.net>: + * (see http://www.winischhofer.net/ + * for more information and updates) + */ + +#ifndef _SISFB_ACCEL_H +#define _SISFB_ACCEL_H + +/* Guard accelerator accesses with spin_lock_irqsave? Works well without. */ +#undef SISFB_USE_SPINLOCKS + +#ifdef SISFB_USE_SPINLOCKS +#include <linux/spinlock.h> +#define CRITBEGIN spin_lock_irqsave(&ivideo->lockaccel, critflags); +#define CRITEND spin_unlock_irqrestore(&ivideo->lockaccel, critflags); +#define CRITFLAGS unsigned long critflags; +#else +#define CRITBEGIN +#define CRITEND +#define CRITFLAGS +#endif + +/* Definitions for the SIS engine communication. */ + +#define PATREGSIZE 384 /* Pattern register size. 384 bytes @ 0x8300 */ +#define BR(x) (0x8200 | (x) << 2) +#define PBR(x) (0x8300 | (x) << 2) + +/* SiS300 engine commands */ +#define BITBLT 0x00000000 /* Blit */ +#define COLOREXP 0x00000001 /* Color expand */ +#define ENCOLOREXP 0x00000002 /* Enhanced color expand */ +#define MULTIPLE_SCANLINE 0x00000003 /* ? */ +#define LINE 0x00000004 /* Draw line */ +#define TRAPAZOID_FILL 0x00000005 /* Fill trapezoid */ +#define TRANSPARENT_BITBLT 0x00000006 /* Transparent Blit */ + +/* Additional engine commands for 315 */ +#define ALPHA_BLEND 0x00000007 /* Alpha blend ? */ +#define A3D_FUNCTION 0x00000008 /* 3D command ? */ +#define CLEAR_Z_BUFFER 0x00000009 /* ? */ +#define GRADIENT_FILL 0x0000000A /* Gradient fill */ + +/* source select */ +#define SRCVIDEO 0x00000000 /* source is video RAM */ +#define SRCSYSTEM 0x00000010 /* source is system memory */ +#define SRCCPUBLITBUF SRCSYSTEM /* source is CPU-driven BitBuffer (for color expand) */ +#define SRCAGP 0x00000020 /* source is AGP memory (?) */ + +/* Pattern flags */ +#define PATFG 0x00000000 /* foreground color */ +#define PATPATREG 0x00000040 /* pattern in pattern buffer (0x8300) */ +#define PATMONO 0x00000080 /* mono pattern */ + +/* blitting direction (300 series only) */ +#define X_INC 0x00010000 +#define X_DEC 0x00000000 +#define Y_INC 0x00020000 +#define Y_DEC 0x00000000 + +/* Clipping flags */ +#define NOCLIP 0x00000000 +#define NOMERGECLIP 0x04000000 +#define CLIPENABLE 0x00040000 +#define CLIPWITHOUTMERGE 0x04040000 + +/* Transparency */ +#define OPAQUE 0x00000000 +#define TRANSPARENT 0x00100000 + +/* ? */ +#define DSTAGP 0x02000000 +#define DSTVIDEO 0x02000000 + +/* Subfunctions for Color/Enhanced Color Expansion (315 only) */ +#define COLOR_TO_MONO 0x00100000 +#define AA_TEXT 0x00200000 + +/* Some general registers for 315 series */ +#define SRC_ADDR 0x8200 +#define SRC_PITCH 0x8204 +#define AGP_BASE 0x8206 /* color-depth dependent value */ +#define SRC_Y 0x8208 +#define SRC_X 0x820A +#define DST_Y 0x820C +#define DST_X 0x820E +#define DST_ADDR 0x8210 +#define DST_PITCH 0x8214 +#define DST_HEIGHT 0x8216 +#define RECT_WIDTH 0x8218 +#define RECT_HEIGHT 0x821A +#define PAT_FGCOLOR 0x821C +#define PAT_BGCOLOR 0x8220 +#define SRC_FGCOLOR 0x8224 +#define SRC_BGCOLOR 0x8228 +#define MONO_MASK 0x822C +#define LEFT_CLIP 0x8234 +#define TOP_CLIP 0x8236 +#define RIGHT_CLIP 0x8238 +#define BOTTOM_CLIP 0x823A +#define COMMAND_READY 0x823C +#define FIRE_TRIGGER 0x8240 + +#define PATTERN_REG 0x8300 /* 384 bytes pattern buffer */ + +/* Transparent bitblit registers */ +#define TRANS_DST_KEY_HIGH PAT_FGCOLOR +#define TRANS_DST_KEY_LOW PAT_BGCOLOR +#define TRANS_SRC_KEY_HIGH SRC_FGCOLOR +#define TRANS_SRC_KEY_LOW SRC_BGCOLOR + +/* Store queue length in par */ +#define CmdQueLen ivideo->cmdqueuelength + +/* ------------- SiS 300 series -------------- */ + +/* BR(16) (0x8240): + + bit 31 2D engine: 1 is idle, + bit 30 3D engine: 1 is idle, + bit 29 Command queue: 1 is empty + bits 28:24: Current CPU driven BitBlt buffer stage bit[4:0] + bits 15:0: Current command queue length + +*/ + +#define SiS300Idle \ + { \ + while((MMIO_IN16(ivideo->mmio_vbase, BR(16)+2) & 0xE000) != 0xE000){}; \ + while((MMIO_IN16(ivideo->mmio_vbase, BR(16)+2) & 0xE000) != 0xE000){}; \ + while((MMIO_IN16(ivideo->mmio_vbase, BR(16)+2) & 0xE000) != 0xE000){}; \ + CmdQueLen = MMIO_IN16(ivideo->mmio_vbase, 0x8240); \ + } +/* (do three times, because 2D engine seems quite unsure about whether or not it's idle) */ + +#define SiS300SetupSRCBase(base) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(0), base);\ + CmdQueLen--; + +#define SiS300SetupSRCPitch(pitch) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT16(ivideo->mmio_vbase, BR(1), pitch);\ + CmdQueLen--; + +#define SiS300SetupSRCXY(x,y) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(2), (x)<<16 | (y) );\ + CmdQueLen--; + +#define SiS300SetupDSTBase(base) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(4), base);\ + CmdQueLen--; + +#define SiS300SetupDSTXY(x,y) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(3), (x)<<16 | (y) );\ + CmdQueLen--; + +#define SiS300SetupDSTRect(x,y) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(5), (y)<<16 | (x) );\ + CmdQueLen--; + +#define SiS300SetupDSTColorDepth(bpp) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT16(ivideo->mmio_vbase, BR(1)+2, bpp);\ + CmdQueLen--; + +#define SiS300SetupRect(w,h) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(6), (h)<<16 | (w) );\ + CmdQueLen--; + +#define SiS300SetupPATFG(color) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(7), color);\ + CmdQueLen--; + +#define SiS300SetupPATBG(color) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(8), color);\ + CmdQueLen--; + +#define SiS300SetupSRCFG(color) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(9), color);\ + CmdQueLen--; + +#define SiS300SetupSRCBG(color) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(10), color);\ + CmdQueLen--; + +/* 0x8224 src colorkey high */ +/* 0x8228 src colorkey low */ +/* 0x821c dest colorkey high */ +/* 0x8220 dest colorkey low */ +#define SiS300SetupSRCTrans(color) \ + if(CmdQueLen <= 1) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, 0x8224, color);\ + MMIO_OUT32(ivideo->mmio_vbase, 0x8228, color);\ + CmdQueLen -= 2; + +#define SiS300SetupDSTTrans(color) \ + if(CmdQueLen <= 1) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, 0x821C, color); \ + MMIO_OUT32(ivideo->mmio_vbase, 0x8220, color); \ + CmdQueLen -= 2; + +#define SiS300SetupMONOPAT(p0,p1) \ + if(CmdQueLen <= 1) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(11), p0);\ + MMIO_OUT32(ivideo->mmio_vbase, BR(12), p1);\ + CmdQueLen -= 2; + +#define SiS300SetupClipLT(left,top) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(13), ((left) & 0xFFFF) | (top)<<16 );\ + CmdQueLen--; + +#define SiS300SetupClipRB(right,bottom) \ + if(CmdQueLen <= 0) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(14), ((right) & 0xFFFF) | (bottom)<<16 );\ + CmdQueLen--; + +/* General */ +#define SiS300SetupROP(rop) \ + ivideo->CommandReg = (rop) << 8; + +#define SiS300SetupCMDFlag(flags) \ + ivideo->CommandReg |= (flags); + +#define SiS300DoCMD \ + if(CmdQueLen <= 1) SiS300Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, BR(15), ivideo->CommandReg); \ + MMIO_OUT32(ivideo->mmio_vbase, BR(16), 0);\ + CmdQueLen -= 2; + +/* -------------- SiS 315/330 series --------------- */ + +/* Q_STATUS: + bit 31 = 1: All engines idle and all queues empty + bit 30 = 1: Hardware Queue (=HW CQ, 2D queue, 3D queue) empty + bit 29 = 1: 2D engine is idle + bit 28 = 1: 3D engine is idle + bit 27 = 1: HW command queue empty + bit 26 = 1: 2D queue empty + bit 25 = 1: 3D queue empty + bit 24 = 1: SW command queue empty + bits 23:16: 2D counter 3 + bits 15:8: 2D counter 2 + bits 7:0: 2D counter 1 +*/ + +#define SiS310Idle \ + { \ + while( (MMIO_IN16(ivideo->mmio_vbase, Q_STATUS+2) & 0x8000) != 0x8000){}; \ + while( (MMIO_IN16(ivideo->mmio_vbase, Q_STATUS+2) & 0x8000) != 0x8000){}; \ + while( (MMIO_IN16(ivideo->mmio_vbase, Q_STATUS+2) & 0x8000) != 0x8000){}; \ + while( (MMIO_IN16(ivideo->mmio_vbase, Q_STATUS+2) & 0x8000) != 0x8000){}; \ + CmdQueLen = 0; \ + } + +#define SiS310SetupSRCBase(base) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, SRC_ADDR, base);\ + CmdQueLen--; + +#define SiS310SetupSRCPitch(pitch) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT16(ivideo->mmio_vbase, SRC_PITCH, pitch);\ + CmdQueLen--; + +#define SiS310SetupSRCXY(x,y) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, SRC_Y, (x)<<16 | (y) );\ + CmdQueLen--; + +#define SiS310SetupDSTBase(base) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, DST_ADDR, base);\ + CmdQueLen--; + +#define SiS310SetupDSTXY(x,y) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, DST_Y, (x)<<16 | (y) );\ + CmdQueLen--; + +#define SiS310SetupDSTRect(x,y) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, DST_PITCH, (y)<<16 | (x) );\ + CmdQueLen--; + +#define SiS310SetupDSTColorDepth(bpp) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT16(ivideo->mmio_vbase, AGP_BASE, bpp);\ + CmdQueLen--; + +#define SiS310SetupRect(w,h) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, RECT_WIDTH, (h)<<16 | (w) );\ + CmdQueLen--; + +#define SiS310SetupPATFG(color) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, PAT_FGCOLOR, color);\ + CmdQueLen--; + +#define SiS310SetupPATBG(color) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, PAT_BGCOLOR, color);\ + CmdQueLen--; + +#define SiS310SetupSRCFG(color) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, SRC_FGCOLOR, color);\ + CmdQueLen--; + +#define SiS310SetupSRCBG(color) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, SRC_BGCOLOR, color);\ + CmdQueLen--; + +#define SiS310SetupSRCTrans(color) \ + if(CmdQueLen <= 1) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, TRANS_SRC_KEY_HIGH, color);\ + MMIO_OUT32(ivideo->mmio_vbase, TRANS_SRC_KEY_LOW, color);\ + CmdQueLen -= 2; + +#define SiS310SetupDSTTrans(color) \ + if(CmdQueLen <= 1) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, TRANS_DST_KEY_HIGH, color); \ + MMIO_OUT32(ivideo->mmio_vbase, TRANS_DST_KEY_LOW, color); \ + CmdQueLen -= 2; + +#define SiS310SetupMONOPAT(p0,p1) \ + if(CmdQueLen <= 1) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, MONO_MASK, p0);\ + MMIO_OUT32(ivideo->mmio_vbase, MONO_MASK+4, p1);\ + CmdQueLen -= 2; + +#define SiS310SetupClipLT(left,top) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, LEFT_CLIP, ((left) & 0xFFFF) | (top)<<16 );\ + CmdQueLen--; + +#define SiS310SetupClipRB(right,bottom) \ + if(CmdQueLen <= 0) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, RIGHT_CLIP, ((right) & 0xFFFF) | (bottom)<<16 );\ + CmdQueLen--; + +#define SiS310SetupROP(rop) \ + ivideo->CommandReg = (rop) << 8; + +#define SiS310SetupCMDFlag(flags) \ + ivideo->CommandReg |= (flags); + +#define SiS310DoCMD \ + if(CmdQueLen <= 1) SiS310Idle;\ + MMIO_OUT32(ivideo->mmio_vbase, COMMAND_READY, ivideo->CommandReg); \ + MMIO_OUT32(ivideo->mmio_vbase, FIRE_TRIGGER, 0); \ + CmdQueLen -= 2; + +int sisfb_initaccel(struct sis_video_info *ivideo); +void sisfb_syncaccel(struct sis_video_info *ivideo); + +int fbcon_sis_sync(struct fb_info *info); +void fbcon_sis_fillrect(struct fb_info *info, const struct fb_fillrect *rect); +void fbcon_sis_copyarea(struct fb_info *info, const struct fb_copyarea *area); + +#endif diff --git a/drivers/video/fbdev/sis/sis_main.c b/drivers/video/fbdev/sis/sis_main.c new file mode 100644 index 000000000000..22ad028bf123 --- /dev/null +++ b/drivers/video/fbdev/sis/sis_main.c @@ -0,0 +1,6844 @@ +/* + * SiS 300/540/630[S]/730[S], + * SiS 315[E|PRO]/550/[M]65x/[M]66x[F|M|G]X/[M]74x[GX]/330/[M]76x[GX], + * XGI V3XT/V5/V8, Z7 + * frame buffer driver for Linux kernels >= 2.4.14 and >=2.6.3 + * + * Copyright (C) 2001-2005 Thomas Winischhofer, Vienna, Austria. + * + * 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 named License, + * or 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 + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + * Author of (practically wiped) code base: + * SiS (www.sis.com) + * Copyright (C) 1999 Silicon Integrated Systems, Inc. + * + * See http://www.winischhofer.net/ for more information and updates + * + * Originally based on the VBE 2.0 compliant graphic boards framebuffer driver, + * which is (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/screen_info.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/selection.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/capability.h> +#include <linux/fs.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <asm/io.h> +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include "sis.h" +#include "sis_main.h" + +#if !defined(CONFIG_FB_SIS_300) && !defined(CONFIG_FB_SIS_315) +#warning Neither CONFIG_FB_SIS_300 nor CONFIG_FB_SIS_315 is set +#warning sisfb will not work! +#endif + +static void sisfb_handle_command(struct sis_video_info *ivideo, + struct sisfb_cmd *sisfb_command); + +/* ------------------ Internal helper routines ----------------- */ + +static void __init +sisfb_setdefaultparms(void) +{ + sisfb_off = 0; + sisfb_parm_mem = 0; + sisfb_accel = -1; + sisfb_ypan = -1; + sisfb_max = -1; + sisfb_userom = -1; + sisfb_useoem = -1; + sisfb_mode_idx = -1; + sisfb_parm_rate = -1; + sisfb_crt1off = 0; + sisfb_forcecrt1 = -1; + sisfb_crt2type = -1; + sisfb_crt2flags = 0; + sisfb_pdc = 0xff; + sisfb_pdca = 0xff; + sisfb_scalelcd = -1; + sisfb_specialtiming = CUT_NONE; + sisfb_lvdshl = -1; + sisfb_dstn = 0; + sisfb_fstn = 0; + sisfb_tvplug = -1; + sisfb_tvstd = -1; + sisfb_tvxposoffset = 0; + sisfb_tvyposoffset = 0; + sisfb_nocrt2rate = 0; +#if !defined(__i386__) && !defined(__x86_64__) + sisfb_resetcard = 0; + sisfb_videoram = 0; +#endif +} + +/* ------------- Parameter parsing -------------- */ + +static void sisfb_search_vesamode(unsigned int vesamode, bool quiet) +{ + int i = 0, j = 0; + + /* We don't know the hardware specs yet and there is no ivideo */ + + if(vesamode == 0) { + if(!quiet) + printk(KERN_ERR "sisfb: Invalid mode. Using default.\n"); + + sisfb_mode_idx = DEFAULT_MODE; + + return; + } + + vesamode &= 0x1dff; /* Clean VESA mode number from other flags */ + + while(sisbios_mode[i++].mode_no[0] != 0) { + if( (sisbios_mode[i-1].vesa_mode_no_1 == vesamode) || + (sisbios_mode[i-1].vesa_mode_no_2 == vesamode) ) { + if(sisfb_fstn) { + if(sisbios_mode[i-1].mode_no[1] == 0x50 || + sisbios_mode[i-1].mode_no[1] == 0x56 || + sisbios_mode[i-1].mode_no[1] == 0x53) + continue; + } else { + if(sisbios_mode[i-1].mode_no[1] == 0x5a || + sisbios_mode[i-1].mode_no[1] == 0x5b) + continue; + } + sisfb_mode_idx = i - 1; + j = 1; + break; + } + } + if((!j) && !quiet) + printk(KERN_ERR "sisfb: Invalid VESA mode 0x%x'\n", vesamode); +} + +static void sisfb_search_mode(char *name, bool quiet) +{ + unsigned int j = 0, xres = 0, yres = 0, depth = 0, rate = 0; + int i = 0; + char strbuf[16], strbuf1[20]; + char *nameptr = name; + + /* We don't know the hardware specs yet and there is no ivideo */ + + if(name == NULL) { + if(!quiet) + printk(KERN_ERR "sisfb: Internal error, using default mode.\n"); + + sisfb_mode_idx = DEFAULT_MODE; + return; + } + + if(!strnicmp(name, sisbios_mode[MODE_INDEX_NONE].name, strlen(name))) { + if(!quiet) + printk(KERN_ERR "sisfb: Mode 'none' not supported anymore. Using default.\n"); + + sisfb_mode_idx = DEFAULT_MODE; + return; + } + + if(strlen(name) <= 19) { + strcpy(strbuf1, name); + for(i = 0; i < strlen(strbuf1); i++) { + if(strbuf1[i] < '0' || strbuf1[i] > '9') strbuf1[i] = ' '; + } + + /* This does some fuzzy mode naming detection */ + if(sscanf(strbuf1, "%u %u %u %u", &xres, &yres, &depth, &rate) == 4) { + if((rate <= 32) || (depth > 32)) { + j = rate; rate = depth; depth = j; + } + sprintf(strbuf, "%ux%ux%u", xres, yres, depth); + nameptr = strbuf; + sisfb_parm_rate = rate; + } else if(sscanf(strbuf1, "%u %u %u", &xres, &yres, &depth) == 3) { + sprintf(strbuf, "%ux%ux%u", xres, yres, depth); + nameptr = strbuf; + } else { + xres = 0; + if((sscanf(strbuf1, "%u %u", &xres, &yres) == 2) && (xres != 0)) { + sprintf(strbuf, "%ux%ux8", xres, yres); + nameptr = strbuf; + } else { + sisfb_search_vesamode(simple_strtoul(name, NULL, 0), quiet); + return; + } + } + } + + i = 0; j = 0; + while(sisbios_mode[i].mode_no[0] != 0) { + if(!strnicmp(nameptr, sisbios_mode[i++].name, strlen(nameptr))) { + if(sisfb_fstn) { + if(sisbios_mode[i-1].mode_no[1] == 0x50 || + sisbios_mode[i-1].mode_no[1] == 0x56 || + sisbios_mode[i-1].mode_no[1] == 0x53) + continue; + } else { + if(sisbios_mode[i-1].mode_no[1] == 0x5a || + sisbios_mode[i-1].mode_no[1] == 0x5b) + continue; + } + sisfb_mode_idx = i - 1; + j = 1; + break; + } + } + + if((!j) && !quiet) + printk(KERN_ERR "sisfb: Invalid mode '%s'\n", nameptr); +} + +#ifndef MODULE +static void sisfb_get_vga_mode_from_kernel(void) +{ +#ifdef CONFIG_X86 + char mymode[32]; + int mydepth = screen_info.lfb_depth; + + if(screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) return; + + if( (screen_info.lfb_width >= 320) && (screen_info.lfb_width <= 2048) && + (screen_info.lfb_height >= 200) && (screen_info.lfb_height <= 1536) && + (mydepth >= 8) && (mydepth <= 32) ) { + + if(mydepth == 24) mydepth = 32; + + sprintf(mymode, "%ux%ux%u", screen_info.lfb_width, + screen_info.lfb_height, + mydepth); + + printk(KERN_DEBUG + "sisfb: Using vga mode %s pre-set by kernel as default\n", + mymode); + + sisfb_search_mode(mymode, true); + } +#endif + return; +} +#endif + +static void __init +sisfb_search_crt2type(const char *name) +{ + int i = 0; + + /* We don't know the hardware specs yet and there is no ivideo */ + + if(name == NULL) return; + + while(sis_crt2type[i].type_no != -1) { + if(!strnicmp(name, sis_crt2type[i].name, strlen(sis_crt2type[i].name))) { + sisfb_crt2type = sis_crt2type[i].type_no; + sisfb_tvplug = sis_crt2type[i].tvplug_no; + sisfb_crt2flags = sis_crt2type[i].flags; + break; + } + i++; + } + + sisfb_dstn = (sisfb_crt2flags & FL_550_DSTN) ? 1 : 0; + sisfb_fstn = (sisfb_crt2flags & FL_550_FSTN) ? 1 : 0; + + if(sisfb_crt2type < 0) + printk(KERN_ERR "sisfb: Invalid CRT2 type: %s\n", name); +} + +static void __init +sisfb_search_tvstd(const char *name) +{ + int i = 0; + + /* We don't know the hardware specs yet and there is no ivideo */ + + if(name == NULL) + return; + + while(sis_tvtype[i].type_no != -1) { + if(!strnicmp(name, sis_tvtype[i].name, strlen(sis_tvtype[i].name))) { + sisfb_tvstd = sis_tvtype[i].type_no; + break; + } + i++; + } +} + +static void __init +sisfb_search_specialtiming(const char *name) +{ + int i = 0; + bool found = false; + + /* We don't know the hardware specs yet and there is no ivideo */ + + if(name == NULL) + return; + + if(!strnicmp(name, "none", 4)) { + sisfb_specialtiming = CUT_FORCENONE; + printk(KERN_DEBUG "sisfb: Special timing disabled\n"); + } else { + while(mycustomttable[i].chipID != 0) { + if(!strnicmp(name,mycustomttable[i].optionName, + strlen(mycustomttable[i].optionName))) { + sisfb_specialtiming = mycustomttable[i].SpecialID; + found = true; + printk(KERN_INFO "sisfb: Special timing for %s %s forced (\"%s\")\n", + mycustomttable[i].vendorName, + mycustomttable[i].cardName, + mycustomttable[i].optionName); + break; + } + i++; + } + if(!found) { + printk(KERN_WARNING "sisfb: Invalid SpecialTiming parameter, valid are:"); + printk(KERN_WARNING "\t\"none\" (to disable special timings)\n"); + i = 0; + while(mycustomttable[i].chipID != 0) { + printk(KERN_WARNING "\t\"%s\" (for %s %s)\n", + mycustomttable[i].optionName, + mycustomttable[i].vendorName, + mycustomttable[i].cardName); + i++; + } + } + } +} + +/* ----------- Various detection routines ----------- */ + +static void sisfb_detect_custom_timing(struct sis_video_info *ivideo) +{ + unsigned char *biosver = NULL; + unsigned char *biosdate = NULL; + bool footprint; + u32 chksum = 0; + int i, j; + + if(ivideo->SiS_Pr.UseROM) { + biosver = ivideo->SiS_Pr.VirtualRomBase + 0x06; + biosdate = ivideo->SiS_Pr.VirtualRomBase + 0x2c; + for(i = 0; i < 32768; i++) + chksum += ivideo->SiS_Pr.VirtualRomBase[i]; + } + + i = 0; + do { + if( (mycustomttable[i].chipID == ivideo->chip) && + ((!strlen(mycustomttable[i].biosversion)) || + (ivideo->SiS_Pr.UseROM && + (!strncmp(mycustomttable[i].biosversion, biosver, + strlen(mycustomttable[i].biosversion))))) && + ((!strlen(mycustomttable[i].biosdate)) || + (ivideo->SiS_Pr.UseROM && + (!strncmp(mycustomttable[i].biosdate, biosdate, + strlen(mycustomttable[i].biosdate))))) && + ((!mycustomttable[i].bioschksum) || + (ivideo->SiS_Pr.UseROM && + (mycustomttable[i].bioschksum == chksum))) && + (mycustomttable[i].pcisubsysvendor == ivideo->subsysvendor) && + (mycustomttable[i].pcisubsyscard == ivideo->subsysdevice) ) { + footprint = true; + for(j = 0; j < 5; j++) { + if(mycustomttable[i].biosFootprintAddr[j]) { + if(ivideo->SiS_Pr.UseROM) { + if(ivideo->SiS_Pr.VirtualRomBase[mycustomttable[i].biosFootprintAddr[j]] != + mycustomttable[i].biosFootprintData[j]) { + footprint = false; + } + } else + footprint = false; + } + } + if(footprint) { + ivideo->SiS_Pr.SiS_CustomT = mycustomttable[i].SpecialID; + printk(KERN_DEBUG "sisfb: Identified [%s %s], special timing applies\n", + mycustomttable[i].vendorName, + mycustomttable[i].cardName); + printk(KERN_DEBUG "sisfb: [specialtiming parameter name: %s]\n", + mycustomttable[i].optionName); + break; + } + } + i++; + } while(mycustomttable[i].chipID); +} + +static bool sisfb_interpret_edid(struct sisfb_monitor *monitor, u8 *buffer) +{ + int i, j, xres, yres, refresh, index; + u32 emodes; + + if(buffer[0] != 0x00 || buffer[1] != 0xff || + buffer[2] != 0xff || buffer[3] != 0xff || + buffer[4] != 0xff || buffer[5] != 0xff || + buffer[6] != 0xff || buffer[7] != 0x00) { + printk(KERN_DEBUG "sisfb: Bad EDID header\n"); + return false; + } + + if(buffer[0x12] != 0x01) { + printk(KERN_INFO "sisfb: EDID version %d not supported\n", + buffer[0x12]); + return false; + } + + monitor->feature = buffer[0x18]; + + if(!(buffer[0x14] & 0x80)) { + if(!(buffer[0x14] & 0x08)) { + printk(KERN_INFO + "sisfb: WARNING: Monitor does not support separate syncs\n"); + } + } + + if(buffer[0x13] >= 0x01) { + /* EDID V1 rev 1 and 2: Search for monitor descriptor + * to extract ranges + */ + j = 0x36; + for(i=0; i<4; i++) { + if(buffer[j] == 0x00 && buffer[j + 1] == 0x00 && + buffer[j + 2] == 0x00 && buffer[j + 3] == 0xfd && + buffer[j + 4] == 0x00) { + monitor->hmin = buffer[j + 7]; + monitor->hmax = buffer[j + 8]; + monitor->vmin = buffer[j + 5]; + monitor->vmax = buffer[j + 6]; + monitor->dclockmax = buffer[j + 9] * 10 * 1000; + monitor->datavalid = true; + break; + } + j += 18; + } + } + + if(!monitor->datavalid) { + /* Otherwise: Get a range from the list of supported + * Estabished Timings. This is not entirely accurate, + * because fixed frequency monitors are not supported + * that way. + */ + monitor->hmin = 65535; monitor->hmax = 0; + monitor->vmin = 65535; monitor->vmax = 0; + monitor->dclockmax = 0; + emodes = buffer[0x23] | (buffer[0x24] << 8) | (buffer[0x25] << 16); + for(i = 0; i < 13; i++) { + if(emodes & sisfb_ddcsmodes[i].mask) { + if(monitor->hmin > sisfb_ddcsmodes[i].h) monitor->hmin = sisfb_ddcsmodes[i].h; + if(monitor->hmax < sisfb_ddcsmodes[i].h) monitor->hmax = sisfb_ddcsmodes[i].h + 1; + if(monitor->vmin > sisfb_ddcsmodes[i].v) monitor->vmin = sisfb_ddcsmodes[i].v; + if(monitor->vmax < sisfb_ddcsmodes[i].v) monitor->vmax = sisfb_ddcsmodes[i].v; + if(monitor->dclockmax < sisfb_ddcsmodes[i].d) monitor->dclockmax = sisfb_ddcsmodes[i].d; + } + } + index = 0x26; + for(i = 0; i < 8; i++) { + xres = (buffer[index] + 31) * 8; + switch(buffer[index + 1] & 0xc0) { + case 0xc0: yres = (xres * 9) / 16; break; + case 0x80: yres = (xres * 4) / 5; break; + case 0x40: yres = (xres * 3) / 4; break; + default: yres = xres; break; + } + refresh = (buffer[index + 1] & 0x3f) + 60; + if((xres >= 640) && (yres >= 480)) { + for(j = 0; j < 8; j++) { + if((xres == sisfb_ddcfmodes[j].x) && + (yres == sisfb_ddcfmodes[j].y) && + (refresh == sisfb_ddcfmodes[j].v)) { + if(monitor->hmin > sisfb_ddcfmodes[j].h) monitor->hmin = sisfb_ddcfmodes[j].h; + if(monitor->hmax < sisfb_ddcfmodes[j].h) monitor->hmax = sisfb_ddcfmodes[j].h + 1; + if(monitor->vmin > sisfb_ddcsmodes[j].v) monitor->vmin = sisfb_ddcsmodes[j].v; + if(monitor->vmax < sisfb_ddcsmodes[j].v) monitor->vmax = sisfb_ddcsmodes[j].v; + if(monitor->dclockmax < sisfb_ddcsmodes[j].d) monitor->dclockmax = sisfb_ddcsmodes[j].d; + } + } + } + index += 2; + } + if((monitor->hmin <= monitor->hmax) && (monitor->vmin <= monitor->vmax)) { + monitor->datavalid = true; + } + } + + return monitor->datavalid; +} + +static void sisfb_handle_ddc(struct sis_video_info *ivideo, + struct sisfb_monitor *monitor, int crtno) +{ + unsigned short temp, i, realcrtno = crtno; + unsigned char buffer[256]; + + monitor->datavalid = false; + + if(crtno) { + if(ivideo->vbflags & CRT2_LCD) realcrtno = 1; + else if(ivideo->vbflags & CRT2_VGA) realcrtno = 2; + else return; + } + + if((ivideo->sisfb_crt1off) && (!crtno)) + return; + + temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, ivideo->sisvga_engine, + realcrtno, 0, &buffer[0], ivideo->vbflags2); + if((!temp) || (temp == 0xffff)) { + printk(KERN_INFO "sisfb: CRT%d DDC probing failed\n", crtno + 1); + return; + } else { + printk(KERN_INFO "sisfb: CRT%d DDC supported\n", crtno + 1); + printk(KERN_INFO "sisfb: CRT%d DDC level: %s%s%s%s\n", + crtno + 1, + (temp & 0x1a) ? "" : "[none of the supported]", + (temp & 0x02) ? "2 " : "", + (temp & 0x08) ? "D&P" : "", + (temp & 0x10) ? "FPDI-2" : ""); + if(temp & 0x02) { + i = 3; /* Number of retrys */ + do { + temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, ivideo->sisvga_engine, + realcrtno, 1, &buffer[0], ivideo->vbflags2); + } while((temp) && i--); + if(!temp) { + if(sisfb_interpret_edid(monitor, &buffer[0])) { + printk(KERN_INFO "sisfb: Monitor range H %d-%dKHz, V %d-%dHz, Max. dotclock %dMHz\n", + monitor->hmin, monitor->hmax, monitor->vmin, monitor->vmax, + monitor->dclockmax / 1000); + } else { + printk(KERN_INFO "sisfb: CRT%d DDC EDID corrupt\n", crtno + 1); + } + } else { + printk(KERN_INFO "sisfb: CRT%d DDC reading failed\n", crtno + 1); + } + } else { + printk(KERN_INFO "sisfb: VESA D&P and FPDI-2 not supported yet\n"); + } + } +} + +/* -------------- Mode validation --------------- */ + +static bool +sisfb_verify_rate(struct sis_video_info *ivideo, struct sisfb_monitor *monitor, + int mode_idx, int rate_idx, int rate) +{ + int htotal, vtotal; + unsigned int dclock, hsync; + + if(!monitor->datavalid) + return true; + + if(mode_idx < 0) + return false; + + /* Skip for 320x200, 320x240, 640x400 */ + switch(sisbios_mode[mode_idx].mode_no[ivideo->mni]) { + case 0x59: + case 0x41: + case 0x4f: + case 0x50: + case 0x56: + case 0x53: + case 0x2f: + case 0x5d: + case 0x5e: + return true; +#ifdef CONFIG_FB_SIS_315 + case 0x5a: + case 0x5b: + if(ivideo->sisvga_engine == SIS_315_VGA) return true; +#endif + } + + if(rate < (monitor->vmin - 1)) + return false; + if(rate > (monitor->vmax + 1)) + return false; + + if(sisfb_gettotalfrommode(&ivideo->SiS_Pr, + sisbios_mode[mode_idx].mode_no[ivideo->mni], + &htotal, &vtotal, rate_idx)) { + dclock = (htotal * vtotal * rate) / 1000; + if(dclock > (monitor->dclockmax + 1000)) + return false; + hsync = dclock / htotal; + if(hsync < (monitor->hmin - 1)) + return false; + if(hsync > (monitor->hmax + 1)) + return false; + } else { + return false; + } + return true; +} + +static int +sisfb_validate_mode(struct sis_video_info *ivideo, int myindex, u32 vbflags) +{ + u16 xres=0, yres, myres; + +#ifdef CONFIG_FB_SIS_300 + if(ivideo->sisvga_engine == SIS_300_VGA) { + if(!(sisbios_mode[myindex].chipset & MD_SIS300)) + return -1 ; + } +#endif +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + if(!(sisbios_mode[myindex].chipset & MD_SIS315)) + return -1; + } +#endif + + myres = sisbios_mode[myindex].yres; + + switch(vbflags & VB_DISPTYPE_DISP2) { + + case CRT2_LCD: + xres = ivideo->lcdxres; yres = ivideo->lcdyres; + + if((ivideo->SiS_Pr.SiS_CustomT != CUT_PANEL848) && + (ivideo->SiS_Pr.SiS_CustomT != CUT_PANEL856)) { + if(sisbios_mode[myindex].xres > xres) + return -1; + if(myres > yres) + return -1; + } + + if(ivideo->sisfb_fstn) { + if(sisbios_mode[myindex].xres == 320) { + if(myres == 240) { + switch(sisbios_mode[myindex].mode_no[1]) { + case 0x50: myindex = MODE_FSTN_8; break; + case 0x56: myindex = MODE_FSTN_16; break; + case 0x53: return -1; + } + } + } + } + + if(SiS_GetModeID_LCD(ivideo->sisvga_engine, vbflags, sisbios_mode[myindex].xres, + sisbios_mode[myindex].yres, 0, ivideo->sisfb_fstn, + ivideo->SiS_Pr.SiS_CustomT, xres, yres, ivideo->vbflags2) < 0x14) { + return -1; + } + break; + + case CRT2_TV: + if(SiS_GetModeID_TV(ivideo->sisvga_engine, vbflags, sisbios_mode[myindex].xres, + sisbios_mode[myindex].yres, 0, ivideo->vbflags2) < 0x14) { + return -1; + } + break; + + case CRT2_VGA: + if(SiS_GetModeID_VGA2(ivideo->sisvga_engine, vbflags, sisbios_mode[myindex].xres, + sisbios_mode[myindex].yres, 0, ivideo->vbflags2) < 0x14) { + return -1; + } + break; + } + + return myindex; +} + +static u8 +sisfb_search_refresh_rate(struct sis_video_info *ivideo, unsigned int rate, int mode_idx) +{ + int i = 0; + u16 xres = sisbios_mode[mode_idx].xres; + u16 yres = sisbios_mode[mode_idx].yres; + + ivideo->rate_idx = 0; + while((sisfb_vrate[i].idx != 0) && (sisfb_vrate[i].xres <= xres)) { + if((sisfb_vrate[i].xres == xres) && (sisfb_vrate[i].yres == yres)) { + if(sisfb_vrate[i].refresh == rate) { + ivideo->rate_idx = sisfb_vrate[i].idx; + break; + } else if(sisfb_vrate[i].refresh > rate) { + if((sisfb_vrate[i].refresh - rate) <= 3) { + DPRINTK("sisfb: Adjusting rate from %d up to %d\n", + rate, sisfb_vrate[i].refresh); + ivideo->rate_idx = sisfb_vrate[i].idx; + ivideo->refresh_rate = sisfb_vrate[i].refresh; + } else if((sisfb_vrate[i].idx != 1) && + ((rate - sisfb_vrate[i-1].refresh) <= 2)) { + DPRINTK("sisfb: Adjusting rate from %d down to %d\n", + rate, sisfb_vrate[i-1].refresh); + ivideo->rate_idx = sisfb_vrate[i-1].idx; + ivideo->refresh_rate = sisfb_vrate[i-1].refresh; + } + break; + } else if((rate - sisfb_vrate[i].refresh) <= 2) { + DPRINTK("sisfb: Adjusting rate from %d down to %d\n", + rate, sisfb_vrate[i].refresh); + ivideo->rate_idx = sisfb_vrate[i].idx; + break; + } + } + i++; + } + if(ivideo->rate_idx > 0) { + return ivideo->rate_idx; + } else { + printk(KERN_INFO "sisfb: Unsupported rate %d for %dx%d\n", + rate, xres, yres); + return 0; + } +} + +static bool +sisfb_bridgeisslave(struct sis_video_info *ivideo) +{ + unsigned char P1_00; + + if(!(ivideo->vbflags2 & VB2_VIDEOBRIDGE)) + return false; + + P1_00 = SiS_GetReg(SISPART1, 0x00); + if( ((ivideo->sisvga_engine == SIS_300_VGA) && (P1_00 & 0xa0) == 0x20) || + ((ivideo->sisvga_engine == SIS_315_VGA) && (P1_00 & 0x50) == 0x10) ) { + return true; + } else { + return false; + } +} + +static bool +sisfballowretracecrt1(struct sis_video_info *ivideo) +{ + u8 temp; + + temp = SiS_GetReg(SISCR, 0x17); + if(!(temp & 0x80)) + return false; + + temp = SiS_GetReg(SISSR, 0x1f); + if(temp & 0xc0) + return false; + + return true; +} + +static bool +sisfbcheckvretracecrt1(struct sis_video_info *ivideo) +{ + if(!sisfballowretracecrt1(ivideo)) + return false; + + if (SiS_GetRegByte(SISINPSTAT) & 0x08) + return true; + else + return false; +} + +static void +sisfbwaitretracecrt1(struct sis_video_info *ivideo) +{ + int watchdog; + + if(!sisfballowretracecrt1(ivideo)) + return; + + watchdog = 65536; + while ((!(SiS_GetRegByte(SISINPSTAT) & 0x08)) && --watchdog); + watchdog = 65536; + while ((SiS_GetRegByte(SISINPSTAT) & 0x08) && --watchdog); +} + +static bool +sisfbcheckvretracecrt2(struct sis_video_info *ivideo) +{ + unsigned char temp, reg; + + switch(ivideo->sisvga_engine) { + case SIS_300_VGA: reg = 0x25; break; + case SIS_315_VGA: reg = 0x30; break; + default: return false; + } + + temp = SiS_GetReg(SISPART1, reg); + if(temp & 0x02) + return true; + else + return false; +} + +static bool +sisfb_CheckVBRetrace(struct sis_video_info *ivideo) +{ + if(ivideo->currentvbflags & VB_DISPTYPE_DISP2) { + if(!sisfb_bridgeisslave(ivideo)) { + return sisfbcheckvretracecrt2(ivideo); + } + } + return sisfbcheckvretracecrt1(ivideo); +} + +static u32 +sisfb_setupvbblankflags(struct sis_video_info *ivideo, u32 *vcount, u32 *hcount) +{ + u8 idx, reg1, reg2, reg3, reg4; + u32 ret = 0; + + (*vcount) = (*hcount) = 0; + + if((ivideo->currentvbflags & VB_DISPTYPE_DISP2) && (!(sisfb_bridgeisslave(ivideo)))) { + + ret |= (FB_VBLANK_HAVE_VSYNC | + FB_VBLANK_HAVE_HBLANK | + FB_VBLANK_HAVE_VBLANK | + FB_VBLANK_HAVE_VCOUNT | + FB_VBLANK_HAVE_HCOUNT); + switch(ivideo->sisvga_engine) { + case SIS_300_VGA: idx = 0x25; break; + default: + case SIS_315_VGA: idx = 0x30; break; + } + reg1 = SiS_GetReg(SISPART1, (idx+0)); /* 30 */ + reg2 = SiS_GetReg(SISPART1, (idx+1)); /* 31 */ + reg3 = SiS_GetReg(SISPART1, (idx+2)); /* 32 */ + reg4 = SiS_GetReg(SISPART1, (idx+3)); /* 33 */ + if(reg1 & 0x01) ret |= FB_VBLANK_VBLANKING; + if(reg1 & 0x02) ret |= FB_VBLANK_VSYNCING; + if(reg4 & 0x80) ret |= FB_VBLANK_HBLANKING; + (*vcount) = reg3 | ((reg4 & 0x70) << 4); + (*hcount) = reg2 | ((reg4 & 0x0f) << 8); + + } else if(sisfballowretracecrt1(ivideo)) { + + ret |= (FB_VBLANK_HAVE_VSYNC | + FB_VBLANK_HAVE_VBLANK | + FB_VBLANK_HAVE_VCOUNT | + FB_VBLANK_HAVE_HCOUNT); + reg1 = SiS_GetRegByte(SISINPSTAT); + if(reg1 & 0x08) ret |= FB_VBLANK_VSYNCING; + if(reg1 & 0x01) ret |= FB_VBLANK_VBLANKING; + reg1 = SiS_GetReg(SISCR, 0x20); + reg1 = SiS_GetReg(SISCR, 0x1b); + reg2 = SiS_GetReg(SISCR, 0x1c); + reg3 = SiS_GetReg(SISCR, 0x1d); + (*vcount) = reg2 | ((reg3 & 0x07) << 8); + (*hcount) = (reg1 | ((reg3 & 0x10) << 4)) << 3; + } + + return ret; +} + +static int +sisfb_myblank(struct sis_video_info *ivideo, int blank) +{ + u8 sr01, sr11, sr1f, cr63=0, p2_0, p1_13; + bool backlight = true; + + switch(blank) { + case FB_BLANK_UNBLANK: /* on */ + sr01 = 0x00; + sr11 = 0x00; + sr1f = 0x00; + cr63 = 0x00; + p2_0 = 0x20; + p1_13 = 0x00; + backlight = true; + break; + case FB_BLANK_NORMAL: /* blank */ + sr01 = 0x20; + sr11 = 0x00; + sr1f = 0x00; + cr63 = 0x00; + p2_0 = 0x20; + p1_13 = 0x00; + backlight = true; + break; + case FB_BLANK_VSYNC_SUSPEND: /* no vsync */ + sr01 = 0x20; + sr11 = 0x08; + sr1f = 0x80; + cr63 = 0x40; + p2_0 = 0x40; + p1_13 = 0x80; + backlight = false; + break; + case FB_BLANK_HSYNC_SUSPEND: /* no hsync */ + sr01 = 0x20; + sr11 = 0x08; + sr1f = 0x40; + cr63 = 0x40; + p2_0 = 0x80; + p1_13 = 0x40; + backlight = false; + break; + case FB_BLANK_POWERDOWN: /* off */ + sr01 = 0x20; + sr11 = 0x08; + sr1f = 0xc0; + cr63 = 0x40; + p2_0 = 0xc0; + p1_13 = 0xc0; + backlight = false; + break; + default: + return 1; + } + + if(ivideo->currentvbflags & VB_DISPTYPE_CRT1) { + + if( (!ivideo->sisfb_thismonitor.datavalid) || + ((ivideo->sisfb_thismonitor.datavalid) && + (ivideo->sisfb_thismonitor.feature & 0xe0))) { + + if(ivideo->sisvga_engine == SIS_315_VGA) { + SiS_SetRegANDOR(SISCR, ivideo->SiS_Pr.SiS_MyCR63, 0xbf, cr63); + } + + if(!(sisfb_bridgeisslave(ivideo))) { + SiS_SetRegANDOR(SISSR, 0x01, ~0x20, sr01); + SiS_SetRegANDOR(SISSR, 0x1f, 0x3f, sr1f); + } + } + + } + + if(ivideo->currentvbflags & CRT2_LCD) { + + if(ivideo->vbflags2 & VB2_SISLVDSBRIDGE) { + if(backlight) { + SiS_SiS30xBLOn(&ivideo->SiS_Pr); + } else { + SiS_SiS30xBLOff(&ivideo->SiS_Pr); + } + } else if(ivideo->sisvga_engine == SIS_315_VGA) { +#ifdef CONFIG_FB_SIS_315 + if(ivideo->vbflags2 & VB2_CHRONTEL) { + if(backlight) { + SiS_Chrontel701xBLOn(&ivideo->SiS_Pr); + } else { + SiS_Chrontel701xBLOff(&ivideo->SiS_Pr); + } + } +#endif + } + + if(((ivideo->sisvga_engine == SIS_300_VGA) && + (ivideo->vbflags2 & (VB2_301|VB2_30xBDH|VB2_LVDS))) || + ((ivideo->sisvga_engine == SIS_315_VGA) && + ((ivideo->vbflags2 & (VB2_LVDS | VB2_CHRONTEL)) == VB2_LVDS))) { + SiS_SetRegANDOR(SISSR, 0x11, ~0x0c, sr11); + } + + if(ivideo->sisvga_engine == SIS_300_VGA) { + if((ivideo->vbflags2 & VB2_30xB) && + (!(ivideo->vbflags2 & VB2_30xBDH))) { + SiS_SetRegANDOR(SISPART1, 0x13, 0x3f, p1_13); + } + } else if(ivideo->sisvga_engine == SIS_315_VGA) { + if((ivideo->vbflags2 & VB2_30xB) && + (!(ivideo->vbflags2 & VB2_30xBDH))) { + SiS_SetRegANDOR(SISPART2, 0x00, 0x1f, p2_0); + } + } + + } else if(ivideo->currentvbflags & CRT2_VGA) { + + if(ivideo->vbflags2 & VB2_30xB) { + SiS_SetRegANDOR(SISPART2, 0x00, 0x1f, p2_0); + } + + } + + return 0; +} + +/* ------------- Callbacks from init.c/init301.c -------------- */ + +#ifdef CONFIG_FB_SIS_300 +unsigned int +sisfb_read_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo; + u32 val = 0; + + pci_read_config_dword(ivideo->nbridge, reg, &val); + return (unsigned int)val; +} + +void +sisfb_write_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg, unsigned int val) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo; + + pci_write_config_dword(ivideo->nbridge, reg, (u32)val); +} + +unsigned int +sisfb_read_lpc_pci_dword(struct SiS_Private *SiS_Pr, int reg) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo; + u32 val = 0; + + if(!ivideo->lpcdev) return 0; + + pci_read_config_dword(ivideo->lpcdev, reg, &val); + return (unsigned int)val; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +void +sisfb_write_nbridge_pci_byte(struct SiS_Private *SiS_Pr, int reg, unsigned char val) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo; + + pci_write_config_byte(ivideo->nbridge, reg, (u8)val); +} + +unsigned int +sisfb_read_mio_pci_word(struct SiS_Private *SiS_Pr, int reg) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo; + u16 val = 0; + + if(!ivideo->lpcdev) return 0; + + pci_read_config_word(ivideo->lpcdev, reg, &val); + return (unsigned int)val; +} +#endif + +/* ----------- FBDev related routines for all series ----------- */ + +static int +sisfb_get_cmap_len(const struct fb_var_screeninfo *var) +{ + return (var->bits_per_pixel == 8) ? 256 : 16; +} + +static void +sisfb_set_vparms(struct sis_video_info *ivideo) +{ + switch(ivideo->video_bpp) { + case 8: + ivideo->DstColor = 0x0000; + ivideo->SiS310_AccelDepth = 0x00000000; + ivideo->video_cmap_len = 256; + break; + case 16: + ivideo->DstColor = 0x8000; + ivideo->SiS310_AccelDepth = 0x00010000; + ivideo->video_cmap_len = 16; + break; + case 32: + ivideo->DstColor = 0xC000; + ivideo->SiS310_AccelDepth = 0x00020000; + ivideo->video_cmap_len = 16; + break; + default: + ivideo->video_cmap_len = 16; + printk(KERN_ERR "sisfb: Unsupported depth %d", ivideo->video_bpp); + ivideo->accel = 0; + } +} + +static int +sisfb_calc_maxyres(struct sis_video_info *ivideo, struct fb_var_screeninfo *var) +{ + int maxyres = ivideo->sisfb_mem / (var->xres_virtual * (var->bits_per_pixel >> 3)); + + if(maxyres > 32767) maxyres = 32767; + + return maxyres; +} + +static void +sisfb_calc_pitch(struct sis_video_info *ivideo, struct fb_var_screeninfo *var) +{ + ivideo->video_linelength = var->xres_virtual * (var->bits_per_pixel >> 3); + ivideo->scrnpitchCRT1 = ivideo->video_linelength; + if(!(ivideo->currentvbflags & CRT1_LCDA)) { + if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + ivideo->scrnpitchCRT1 <<= 1; + } + } +} + +static void +sisfb_set_pitch(struct sis_video_info *ivideo) +{ + bool isslavemode = false; + unsigned short HDisplay1 = ivideo->scrnpitchCRT1 >> 3; + unsigned short HDisplay2 = ivideo->video_linelength >> 3; + + if(sisfb_bridgeisslave(ivideo)) isslavemode = true; + + /* We need to set pitch for CRT1 if bridge is in slave mode, too */ + if((ivideo->currentvbflags & VB_DISPTYPE_DISP1) || (isslavemode)) { + SiS_SetReg(SISCR, 0x13, (HDisplay1 & 0xFF)); + SiS_SetRegANDOR(SISSR, 0x0E, 0xF0, (HDisplay1 >> 8)); + } + + /* We must not set the pitch for CRT2 if bridge is in slave mode */ + if((ivideo->currentvbflags & VB_DISPTYPE_DISP2) && (!isslavemode)) { + SiS_SetRegOR(SISPART1, ivideo->CRT2_write_enable, 0x01); + SiS_SetReg(SISPART1, 0x07, (HDisplay2 & 0xFF)); + SiS_SetRegANDOR(SISPART1, 0x09, 0xF0, (HDisplay2 >> 8)); + } +} + +static void +sisfb_bpp_to_var(struct sis_video_info *ivideo, struct fb_var_screeninfo *var) +{ + ivideo->video_cmap_len = sisfb_get_cmap_len(var); + + switch(var->bits_per_pixel) { + case 8: + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + break; + case 16: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } +} + +static int +sisfb_set_mode(struct sis_video_info *ivideo, int clrscrn) +{ + unsigned short modeno = ivideo->mode_no; + + /* >=2.6.12's fbcon clears the screen anyway */ + modeno |= 0x80; + + SiS_SetReg(SISSR, IND_SIS_PASSWORD, SIS_PASSWORD); + + sisfb_pre_setmode(ivideo); + + if(!SiSSetMode(&ivideo->SiS_Pr, modeno)) { + printk(KERN_ERR "sisfb: Setting mode[0x%x] failed\n", ivideo->mode_no); + return -EINVAL; + } + + SiS_SetReg(SISSR, IND_SIS_PASSWORD, SIS_PASSWORD); + + sisfb_post_setmode(ivideo); + + return 0; +} + + +static int +sisfb_do_set_var(struct fb_var_screeninfo *var, int isactive, struct fb_info *info) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + unsigned int htotal = 0, vtotal = 0; + unsigned int drate = 0, hrate = 0; + int found_mode = 0, ret; + int old_mode; + u32 pixclock; + + htotal = var->left_margin + var->xres + var->right_margin + var->hsync_len; + + vtotal = var->upper_margin + var->lower_margin + var->vsync_len; + + pixclock = var->pixclock; + + if((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) { + vtotal += var->yres; + vtotal <<= 1; + } else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) { + vtotal += var->yres; + vtotal <<= 2; + } else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + vtotal += var->yres; + vtotal <<= 1; + } else vtotal += var->yres; + + if(!(htotal) || !(vtotal)) { + DPRINTK("sisfb: Invalid 'var' information\n"); + return -EINVAL; + } + + if(pixclock && htotal && vtotal) { + drate = 1000000000 / pixclock; + hrate = (drate * 1000) / htotal; + ivideo->refresh_rate = (unsigned int) (hrate * 2 / vtotal); + } else { + ivideo->refresh_rate = 60; + } + + old_mode = ivideo->sisfb_mode_idx; + ivideo->sisfb_mode_idx = 0; + + while( (sisbios_mode[ivideo->sisfb_mode_idx].mode_no[0] != 0) && + (sisbios_mode[ivideo->sisfb_mode_idx].xres <= var->xres) ) { + if( (sisbios_mode[ivideo->sisfb_mode_idx].xres == var->xres) && + (sisbios_mode[ivideo->sisfb_mode_idx].yres == var->yres) && + (sisbios_mode[ivideo->sisfb_mode_idx].bpp == var->bits_per_pixel)) { + ivideo->mode_no = sisbios_mode[ivideo->sisfb_mode_idx].mode_no[ivideo->mni]; + found_mode = 1; + break; + } + ivideo->sisfb_mode_idx++; + } + + if(found_mode) { + ivideo->sisfb_mode_idx = sisfb_validate_mode(ivideo, + ivideo->sisfb_mode_idx, ivideo->currentvbflags); + } else { + ivideo->sisfb_mode_idx = -1; + } + + if(ivideo->sisfb_mode_idx < 0) { + printk(KERN_ERR "sisfb: Mode %dx%dx%d not supported\n", var->xres, + var->yres, var->bits_per_pixel); + ivideo->sisfb_mode_idx = old_mode; + return -EINVAL; + } + + ivideo->mode_no = sisbios_mode[ivideo->sisfb_mode_idx].mode_no[ivideo->mni]; + + if(sisfb_search_refresh_rate(ivideo, ivideo->refresh_rate, ivideo->sisfb_mode_idx) == 0) { + ivideo->rate_idx = sisbios_mode[ivideo->sisfb_mode_idx].rate_idx; + ivideo->refresh_rate = 60; + } + + if(isactive) { + /* If acceleration to be used? Need to know + * before pre/post_set_mode() + */ + ivideo->accel = 0; +#if defined(FBINFO_HWACCEL_DISABLED) && defined(FBINFO_HWACCEL_XPAN) +#ifdef STUPID_ACCELF_TEXT_SHIT + if(var->accel_flags & FB_ACCELF_TEXT) { + info->flags &= ~FBINFO_HWACCEL_DISABLED; + } else { + info->flags |= FBINFO_HWACCEL_DISABLED; + } +#endif + if(!(info->flags & FBINFO_HWACCEL_DISABLED)) ivideo->accel = -1; +#else + if(var->accel_flags & FB_ACCELF_TEXT) ivideo->accel = -1; +#endif + + if((ret = sisfb_set_mode(ivideo, 1))) { + return ret; + } + + ivideo->video_bpp = sisbios_mode[ivideo->sisfb_mode_idx].bpp; + ivideo->video_width = sisbios_mode[ivideo->sisfb_mode_idx].xres; + ivideo->video_height = sisbios_mode[ivideo->sisfb_mode_idx].yres; + + sisfb_calc_pitch(ivideo, var); + sisfb_set_pitch(ivideo); + + sisfb_set_vparms(ivideo); + + ivideo->current_width = ivideo->video_width; + ivideo->current_height = ivideo->video_height; + ivideo->current_bpp = ivideo->video_bpp; + ivideo->current_htotal = htotal; + ivideo->current_vtotal = vtotal; + ivideo->current_linelength = ivideo->video_linelength; + ivideo->current_pixclock = var->pixclock; + ivideo->current_refresh_rate = ivideo->refresh_rate; + ivideo->sisfb_lastrates[ivideo->mode_no] = ivideo->refresh_rate; + } + + return 0; +} + +static void +sisfb_set_base_CRT1(struct sis_video_info *ivideo, unsigned int base) +{ + SiS_SetReg(SISSR, IND_SIS_PASSWORD, SIS_PASSWORD); + + SiS_SetReg(SISCR, 0x0D, base & 0xFF); + SiS_SetReg(SISCR, 0x0C, (base >> 8) & 0xFF); + SiS_SetReg(SISSR, 0x0D, (base >> 16) & 0xFF); + if(ivideo->sisvga_engine == SIS_315_VGA) { + SiS_SetRegANDOR(SISSR, 0x37, 0xFE, (base >> 24) & 0x01); + } +} + +static void +sisfb_set_base_CRT2(struct sis_video_info *ivideo, unsigned int base) +{ + if(ivideo->currentvbflags & VB_DISPTYPE_DISP2) { + SiS_SetRegOR(SISPART1, ivideo->CRT2_write_enable, 0x01); + SiS_SetReg(SISPART1, 0x06, (base & 0xFF)); + SiS_SetReg(SISPART1, 0x05, ((base >> 8) & 0xFF)); + SiS_SetReg(SISPART1, 0x04, ((base >> 16) & 0xFF)); + if(ivideo->sisvga_engine == SIS_315_VGA) { + SiS_SetRegANDOR(SISPART1, 0x02, 0x7F, ((base >> 24) & 0x01) << 7); + } + } +} + +static int +sisfb_pan_var(struct sis_video_info *ivideo, struct fb_info *info, + struct fb_var_screeninfo *var) +{ + ivideo->current_base = var->yoffset * info->var.xres_virtual + + var->xoffset; + + /* calculate base bpp dep. */ + switch (info->var.bits_per_pixel) { + case 32: + break; + case 16: + ivideo->current_base >>= 1; + break; + case 8: + default: + ivideo->current_base >>= 2; + break; + } + + ivideo->current_base += (ivideo->video_offset >> 2); + + sisfb_set_base_CRT1(ivideo, ivideo->current_base); + sisfb_set_base_CRT2(ivideo, ivideo->current_base); + + return 0; +} + +static int +sisfb_open(struct fb_info *info, int user) +{ + return 0; +} + +static int +sisfb_release(struct fb_info *info, int user) +{ + return 0; +} + +static int +sisfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + + if(regno >= sisfb_get_cmap_len(&info->var)) + return 1; + + switch(info->var.bits_per_pixel) { + case 8: + SiS_SetRegByte(SISDACA, regno); + SiS_SetRegByte(SISDACD, (red >> 10)); + SiS_SetRegByte(SISDACD, (green >> 10)); + SiS_SetRegByte(SISDACD, (blue >> 10)); + if(ivideo->currentvbflags & VB_DISPTYPE_DISP2) { + SiS_SetRegByte(SISDAC2A, regno); + SiS_SetRegByte(SISDAC2D, (red >> 8)); + SiS_SetRegByte(SISDAC2D, (green >> 8)); + SiS_SetRegByte(SISDAC2D, (blue >> 8)); + } + break; + case 16: + if (regno >= 16) + break; + + ((u32 *)(info->pseudo_palette))[regno] = + (red & 0xf800) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + case 32: + if (regno >= 16) + break; + + red >>= 8; + green >>= 8; + blue >>= 8; + ((u32 *)(info->pseudo_palette))[regno] = + (red << 16) | (green << 8) | (blue); + break; + } + return 0; +} + +static int +sisfb_set_par(struct fb_info *info) +{ + int err; + + if((err = sisfb_do_set_var(&info->var, 1, info))) + return err; + + sisfb_get_fix(&info->fix, -1, info); + + return 0; +} + +static int +sisfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + unsigned int htotal = 0, vtotal = 0, myrateindex = 0; + unsigned int drate = 0, hrate = 0, maxyres; + int found_mode = 0; + int refresh_rate, search_idx, tidx; + bool recalc_clock = false; + u32 pixclock; + + htotal = var->left_margin + var->xres + var->right_margin + var->hsync_len; + + vtotal = var->upper_margin + var->lower_margin + var->vsync_len; + + pixclock = var->pixclock; + + if((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) { + vtotal += var->yres; + vtotal <<= 1; + } else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) { + vtotal += var->yres; + vtotal <<= 2; + } else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) { + vtotal += var->yres; + vtotal <<= 1; + } else + vtotal += var->yres; + + if(!(htotal) || !(vtotal)) { + SISFAIL("sisfb: no valid timing data"); + } + + search_idx = 0; + while( (sisbios_mode[search_idx].mode_no[0] != 0) && + (sisbios_mode[search_idx].xres <= var->xres) ) { + if( (sisbios_mode[search_idx].xres == var->xres) && + (sisbios_mode[search_idx].yres == var->yres) && + (sisbios_mode[search_idx].bpp == var->bits_per_pixel)) { + if((tidx = sisfb_validate_mode(ivideo, search_idx, + ivideo->currentvbflags)) > 0) { + found_mode = 1; + search_idx = tidx; + break; + } + } + search_idx++; + } + + if(!found_mode) { + search_idx = 0; + while(sisbios_mode[search_idx].mode_no[0] != 0) { + if( (var->xres <= sisbios_mode[search_idx].xres) && + (var->yres <= sisbios_mode[search_idx].yres) && + (var->bits_per_pixel == sisbios_mode[search_idx].bpp) ) { + if((tidx = sisfb_validate_mode(ivideo,search_idx, + ivideo->currentvbflags)) > 0) { + found_mode = 1; + search_idx = tidx; + break; + } + } + search_idx++; + } + if(found_mode) { + printk(KERN_DEBUG + "sisfb: Adapted from %dx%dx%d to %dx%dx%d\n", + var->xres, var->yres, var->bits_per_pixel, + sisbios_mode[search_idx].xres, + sisbios_mode[search_idx].yres, + var->bits_per_pixel); + var->xres = sisbios_mode[search_idx].xres; + var->yres = sisbios_mode[search_idx].yres; + } else { + printk(KERN_ERR + "sisfb: Failed to find supported mode near %dx%dx%d\n", + var->xres, var->yres, var->bits_per_pixel); + return -EINVAL; + } + } + + if( ((ivideo->vbflags2 & VB2_LVDS) || + ((ivideo->vbflags2 & VB2_30xBDH) && (ivideo->currentvbflags & CRT2_LCD))) && + (var->bits_per_pixel == 8) ) { + /* Slave modes on LVDS and 301B-DH */ + refresh_rate = 60; + recalc_clock = true; + } else if( (ivideo->current_htotal == htotal) && + (ivideo->current_vtotal == vtotal) && + (ivideo->current_pixclock == pixclock) ) { + /* x=x & y=y & c=c -> assume depth change */ + drate = 1000000000 / pixclock; + hrate = (drate * 1000) / htotal; + refresh_rate = (unsigned int) (hrate * 2 / vtotal); + } else if( ( (ivideo->current_htotal != htotal) || + (ivideo->current_vtotal != vtotal) ) && + (ivideo->current_pixclock == var->pixclock) ) { + /* x!=x | y!=y & c=c -> invalid pixclock */ + if(ivideo->sisfb_lastrates[sisbios_mode[search_idx].mode_no[ivideo->mni]]) { + refresh_rate = + ivideo->sisfb_lastrates[sisbios_mode[search_idx].mode_no[ivideo->mni]]; + } else if(ivideo->sisfb_parm_rate != -1) { + /* Sic, sisfb_parm_rate - want to know originally desired rate here */ + refresh_rate = ivideo->sisfb_parm_rate; + } else { + refresh_rate = 60; + } + recalc_clock = true; + } else if((pixclock) && (htotal) && (vtotal)) { + drate = 1000000000 / pixclock; + hrate = (drate * 1000) / htotal; + refresh_rate = (unsigned int) (hrate * 2 / vtotal); + } else if(ivideo->current_refresh_rate) { + refresh_rate = ivideo->current_refresh_rate; + recalc_clock = true; + } else { + refresh_rate = 60; + recalc_clock = true; + } + + myrateindex = sisfb_search_refresh_rate(ivideo, refresh_rate, search_idx); + + /* Eventually recalculate timing and clock */ + if(recalc_clock) { + if(!myrateindex) myrateindex = sisbios_mode[search_idx].rate_idx; + var->pixclock = (u32) (1000000000 / sisfb_mode_rate_to_dclock(&ivideo->SiS_Pr, + sisbios_mode[search_idx].mode_no[ivideo->mni], + myrateindex)); + sisfb_mode_rate_to_ddata(&ivideo->SiS_Pr, + sisbios_mode[search_idx].mode_no[ivideo->mni], + myrateindex, var); + if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) { + var->pixclock <<= 1; + } + } + + if(ivideo->sisfb_thismonitor.datavalid) { + if(!sisfb_verify_rate(ivideo, &ivideo->sisfb_thismonitor, search_idx, + myrateindex, refresh_rate)) { + printk(KERN_INFO + "sisfb: WARNING: Refresh rate exceeds monitor specs!\n"); + } + } + + /* Adapt RGB settings */ + sisfb_bpp_to_var(ivideo, var); + + /* Sanity check for offsets */ + if(var->xoffset < 0) var->xoffset = 0; + if(var->yoffset < 0) var->yoffset = 0; + + if(var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if(ivideo->sisfb_ypan) { + maxyres = sisfb_calc_maxyres(ivideo, var); + if(ivideo->sisfb_max) { + var->yres_virtual = maxyres; + } else { + if(var->yres_virtual > maxyres) { + var->yres_virtual = maxyres; + } + } + if(var->yres_virtual <= var->yres) { + var->yres_virtual = var->yres; + } + } else { + if(var->yres != var->yres_virtual) { + var->yres_virtual = var->yres; + } + var->xoffset = 0; + var->yoffset = 0; + } + + /* Truncate offsets to maximum if too high */ + if(var->xoffset > var->xres_virtual - var->xres) { + var->xoffset = var->xres_virtual - var->xres - 1; + } + + if(var->yoffset > var->yres_virtual - var->yres) { + var->yoffset = var->yres_virtual - var->yres - 1; + } + + /* Set everything else to 0 */ + var->red.msb_right = + var->green.msb_right = + var->blue.msb_right = + var->transp.offset = + var->transp.length = + var->transp.msb_right = 0; + + return 0; +} + +static int +sisfb_pan_display(struct fb_var_screeninfo *var, struct fb_info* info) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + int err; + + if (var->vmode & FB_VMODE_YWRAP) + return -EINVAL; + + if (var->xoffset + info->var.xres > info->var.xres_virtual || + var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + err = sisfb_pan_var(ivideo, info, var); + if (err < 0) + return err; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + return 0; +} + +static int +sisfb_blank(int blank, struct fb_info *info) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + + return sisfb_myblank(ivideo, blank); +} + +/* ----------- FBDev related routines for all series ---------- */ + +static int sisfb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + struct sis_memreq sismemreq; + struct fb_vblank sisvbblank; + u32 gpu32 = 0; +#ifndef __user +#define __user +#endif + u32 __user *argp = (u32 __user *)arg; + + switch(cmd) { + case FBIO_ALLOC: + if(!capable(CAP_SYS_RAWIO)) + return -EPERM; + + if(copy_from_user(&sismemreq, (void __user *)arg, sizeof(sismemreq))) + return -EFAULT; + + sis_malloc(&sismemreq); + + if(copy_to_user((void __user *)arg, &sismemreq, sizeof(sismemreq))) { + sis_free((u32)sismemreq.offset); + return -EFAULT; + } + break; + + case FBIO_FREE: + if(!capable(CAP_SYS_RAWIO)) + return -EPERM; + + if(get_user(gpu32, argp)) + return -EFAULT; + + sis_free(gpu32); + break; + + case FBIOGET_VBLANK: + + memset(&sisvbblank, 0, sizeof(struct fb_vblank)); + + sisvbblank.count = 0; + sisvbblank.flags = sisfb_setupvbblankflags(ivideo, &sisvbblank.vcount, &sisvbblank.hcount); + + if(copy_to_user((void __user *)arg, &sisvbblank, sizeof(sisvbblank))) + return -EFAULT; + + break; + + case SISFB_GET_INFO_SIZE: + return put_user(sizeof(struct sisfb_info), argp); + + case SISFB_GET_INFO_OLD: + if(ivideo->warncount++ < 10) + printk(KERN_INFO + "sisfb: Deprecated ioctl call received - update your application!\n"); + case SISFB_GET_INFO: /* For communication with X driver */ + ivideo->sisfb_infoblock.sisfb_id = SISFB_ID; + ivideo->sisfb_infoblock.sisfb_version = VER_MAJOR; + ivideo->sisfb_infoblock.sisfb_revision = VER_MINOR; + ivideo->sisfb_infoblock.sisfb_patchlevel = VER_LEVEL; + ivideo->sisfb_infoblock.chip_id = ivideo->chip_id; + ivideo->sisfb_infoblock.sisfb_pci_vendor = ivideo->chip_vendor; + ivideo->sisfb_infoblock.memory = ivideo->video_size / 1024; + ivideo->sisfb_infoblock.heapstart = ivideo->heapstart / 1024; + if(ivideo->modechanged) { + ivideo->sisfb_infoblock.fbvidmode = ivideo->mode_no; + } else { + ivideo->sisfb_infoblock.fbvidmode = ivideo->modeprechange; + } + ivideo->sisfb_infoblock.sisfb_caps = ivideo->caps; + ivideo->sisfb_infoblock.sisfb_tqlen = ivideo->cmdQueueSize / 1024; + ivideo->sisfb_infoblock.sisfb_pcibus = ivideo->pcibus; + ivideo->sisfb_infoblock.sisfb_pcislot = ivideo->pcislot; + ivideo->sisfb_infoblock.sisfb_pcifunc = ivideo->pcifunc; + ivideo->sisfb_infoblock.sisfb_lcdpdc = ivideo->detectedpdc; + ivideo->sisfb_infoblock.sisfb_lcdpdca = ivideo->detectedpdca; + ivideo->sisfb_infoblock.sisfb_lcda = ivideo->detectedlcda; + ivideo->sisfb_infoblock.sisfb_vbflags = ivideo->vbflags; + ivideo->sisfb_infoblock.sisfb_currentvbflags = ivideo->currentvbflags; + ivideo->sisfb_infoblock.sisfb_scalelcd = ivideo->SiS_Pr.UsePanelScaler; + ivideo->sisfb_infoblock.sisfb_specialtiming = ivideo->SiS_Pr.SiS_CustomT; + ivideo->sisfb_infoblock.sisfb_haveemi = ivideo->SiS_Pr.HaveEMI ? 1 : 0; + ivideo->sisfb_infoblock.sisfb_haveemilcd = ivideo->SiS_Pr.HaveEMILCD ? 1 : 0; + ivideo->sisfb_infoblock.sisfb_emi30 = ivideo->SiS_Pr.EMI_30; + ivideo->sisfb_infoblock.sisfb_emi31 = ivideo->SiS_Pr.EMI_31; + ivideo->sisfb_infoblock.sisfb_emi32 = ivideo->SiS_Pr.EMI_32; + ivideo->sisfb_infoblock.sisfb_emi33 = ivideo->SiS_Pr.EMI_33; + ivideo->sisfb_infoblock.sisfb_tvxpos = (u16)(ivideo->tvxpos + 32); + ivideo->sisfb_infoblock.sisfb_tvypos = (u16)(ivideo->tvypos + 32); + ivideo->sisfb_infoblock.sisfb_heapsize = ivideo->sisfb_heap_size / 1024; + ivideo->sisfb_infoblock.sisfb_videooffset = ivideo->video_offset; + ivideo->sisfb_infoblock.sisfb_curfstn = ivideo->curFSTN; + ivideo->sisfb_infoblock.sisfb_curdstn = ivideo->curDSTN; + ivideo->sisfb_infoblock.sisfb_vbflags2 = ivideo->vbflags2; + ivideo->sisfb_infoblock.sisfb_can_post = ivideo->sisfb_can_post ? 1 : 0; + ivideo->sisfb_infoblock.sisfb_card_posted = ivideo->sisfb_card_posted ? 1 : 0; + ivideo->sisfb_infoblock.sisfb_was_boot_device = ivideo->sisfb_was_boot_device ? 1 : 0; + + if(copy_to_user((void __user *)arg, &ivideo->sisfb_infoblock, + sizeof(ivideo->sisfb_infoblock))) + return -EFAULT; + + break; + + case SISFB_GET_VBRSTATUS_OLD: + if(ivideo->warncount++ < 10) + printk(KERN_INFO + "sisfb: Deprecated ioctl call received - update your application!\n"); + case SISFB_GET_VBRSTATUS: + if(sisfb_CheckVBRetrace(ivideo)) + return put_user((u32)1, argp); + else + return put_user((u32)0, argp); + + case SISFB_GET_AUTOMAXIMIZE_OLD: + if(ivideo->warncount++ < 10) + printk(KERN_INFO + "sisfb: Deprecated ioctl call received - update your application!\n"); + case SISFB_GET_AUTOMAXIMIZE: + if(ivideo->sisfb_max) + return put_user((u32)1, argp); + else + return put_user((u32)0, argp); + + case SISFB_SET_AUTOMAXIMIZE_OLD: + if(ivideo->warncount++ < 10) + printk(KERN_INFO + "sisfb: Deprecated ioctl call received - update your application!\n"); + case SISFB_SET_AUTOMAXIMIZE: + if(get_user(gpu32, argp)) + return -EFAULT; + + ivideo->sisfb_max = (gpu32) ? 1 : 0; + break; + + case SISFB_SET_TVPOSOFFSET: + if(get_user(gpu32, argp)) + return -EFAULT; + + sisfb_set_TVxposoffset(ivideo, ((int)(gpu32 >> 16)) - 32); + sisfb_set_TVyposoffset(ivideo, ((int)(gpu32 & 0xffff)) - 32); + break; + + case SISFB_GET_TVPOSOFFSET: + return put_user((u32)(((ivideo->tvxpos+32)<<16)|((ivideo->tvypos+32)&0xffff)), + argp); + + case SISFB_COMMAND: + if(copy_from_user(&ivideo->sisfb_command, (void __user *)arg, + sizeof(struct sisfb_cmd))) + return -EFAULT; + + sisfb_handle_command(ivideo, &ivideo->sisfb_command); + + if(copy_to_user((void __user *)arg, &ivideo->sisfb_command, + sizeof(struct sisfb_cmd))) + return -EFAULT; + + break; + + case SISFB_SET_LOCK: + if(get_user(gpu32, argp)) + return -EFAULT; + + ivideo->sisfblocked = (gpu32) ? 1 : 0; + break; + + default: +#ifdef SIS_NEW_CONFIG_COMPAT + return -ENOIOCTLCMD; +#else + return -EINVAL; +#endif + } + return 0; +} + +static int +sisfb_get_fix(struct fb_fix_screeninfo *fix, int con, struct fb_info *info) +{ + struct sis_video_info *ivideo = (struct sis_video_info *)info->par; + + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + + strlcpy(fix->id, ivideo->myid, sizeof(fix->id)); + + mutex_lock(&info->mm_lock); + fix->smem_start = ivideo->video_base + ivideo->video_offset; + fix->smem_len = ivideo->sisfb_mem; + mutex_unlock(&info->mm_lock); + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + fix->visual = (ivideo->video_bpp == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = (ivideo->sisfb_ypan) ? 1 : 0; + fix->ywrapstep = 0; + fix->line_length = ivideo->video_linelength; + fix->mmio_start = ivideo->mmio_base; + fix->mmio_len = ivideo->mmio_size; + if(ivideo->sisvga_engine == SIS_300_VGA) { + fix->accel = FB_ACCEL_SIS_GLAMOUR; + } else if((ivideo->chip == SIS_330) || + (ivideo->chip == SIS_760) || + (ivideo->chip == SIS_761)) { + fix->accel = FB_ACCEL_SIS_XABRE; + } else if(ivideo->chip == XGI_20) { + fix->accel = FB_ACCEL_XGI_VOLARI_Z; + } else if(ivideo->chip >= XGI_40) { + fix->accel = FB_ACCEL_XGI_VOLARI_V; + } else { + fix->accel = FB_ACCEL_SIS_GLAMOUR_2; + } + + return 0; +} + +/* ---------------- fb_ops structures ----------------- */ + +static struct fb_ops sisfb_ops = { + .owner = THIS_MODULE, + .fb_open = sisfb_open, + .fb_release = sisfb_release, + .fb_check_var = sisfb_check_var, + .fb_set_par = sisfb_set_par, + .fb_setcolreg = sisfb_setcolreg, + .fb_pan_display = sisfb_pan_display, + .fb_blank = sisfb_blank, + .fb_fillrect = fbcon_sis_fillrect, + .fb_copyarea = fbcon_sis_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_sync = fbcon_sis_sync, +#ifdef SIS_NEW_CONFIG_COMPAT + .fb_compat_ioctl= sisfb_ioctl, +#endif + .fb_ioctl = sisfb_ioctl +}; + +/* ---------------- Chip generation dependent routines ---------------- */ + +static struct pci_dev *sisfb_get_northbridge(int basechipid) +{ + struct pci_dev *pdev = NULL; + int nbridgenum, nbridgeidx, i; + static const unsigned short nbridgeids[] = { + PCI_DEVICE_ID_SI_540, /* for SiS 540 VGA */ + PCI_DEVICE_ID_SI_630, /* for SiS 630/730 VGA */ + PCI_DEVICE_ID_SI_730, + PCI_DEVICE_ID_SI_550, /* for SiS 550 VGA */ + PCI_DEVICE_ID_SI_650, /* for SiS 650/651/740 VGA */ + PCI_DEVICE_ID_SI_651, + PCI_DEVICE_ID_SI_740, + PCI_DEVICE_ID_SI_661, /* for SiS 661/741/660/760/761 VGA */ + PCI_DEVICE_ID_SI_741, + PCI_DEVICE_ID_SI_660, + PCI_DEVICE_ID_SI_760, + PCI_DEVICE_ID_SI_761 + }; + + switch(basechipid) { +#ifdef CONFIG_FB_SIS_300 + case SIS_540: nbridgeidx = 0; nbridgenum = 1; break; + case SIS_630: nbridgeidx = 1; nbridgenum = 2; break; +#endif +#ifdef CONFIG_FB_SIS_315 + case SIS_550: nbridgeidx = 3; nbridgenum = 1; break; + case SIS_650: nbridgeidx = 4; nbridgenum = 3; break; + case SIS_660: nbridgeidx = 7; nbridgenum = 5; break; +#endif + default: return NULL; + } + for(i = 0; i < nbridgenum; i++) { + if((pdev = pci_get_device(PCI_VENDOR_ID_SI, + nbridgeids[nbridgeidx+i], NULL))) + break; + } + return pdev; +} + +static int sisfb_get_dram_size(struct sis_video_info *ivideo) +{ +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) + u8 reg; +#endif + + ivideo->video_size = 0; + ivideo->UMAsize = ivideo->LFBsize = 0; + + switch(ivideo->chip) { +#ifdef CONFIG_FB_SIS_300 + case SIS_300: + reg = SiS_GetReg(SISSR, 0x14); + ivideo->video_size = ((reg & 0x3F) + 1) << 20; + break; + case SIS_540: + case SIS_630: + case SIS_730: + if(!ivideo->nbridge) + return -1; + pci_read_config_byte(ivideo->nbridge, 0x63, ®); + ivideo->video_size = 1 << (((reg & 0x70) >> 4) + 21); + break; +#endif +#ifdef CONFIG_FB_SIS_315 + case SIS_315H: + case SIS_315PRO: + case SIS_315: + reg = SiS_GetReg(SISSR, 0x14); + ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20; + switch((reg >> 2) & 0x03) { + case 0x01: + case 0x03: + ivideo->video_size <<= 1; + break; + case 0x02: + ivideo->video_size += (ivideo->video_size/2); + } + break; + case SIS_330: + reg = SiS_GetReg(SISSR, 0x14); + ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20; + if(reg & 0x0c) ivideo->video_size <<= 1; + break; + case SIS_550: + case SIS_650: + case SIS_740: + reg = SiS_GetReg(SISSR, 0x14); + ivideo->video_size = (((reg & 0x3f) + 1) << 2) << 20; + break; + case SIS_661: + case SIS_741: + reg = SiS_GetReg(SISCR, 0x79); + ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20; + break; + case SIS_660: + case SIS_760: + case SIS_761: + reg = SiS_GetReg(SISCR, 0x79); + reg = (reg & 0xf0) >> 4; + if(reg) { + ivideo->video_size = (1 << reg) << 20; + ivideo->UMAsize = ivideo->video_size; + } + reg = SiS_GetReg(SISCR, 0x78); + reg &= 0x30; + if(reg) { + if(reg == 0x10) { + ivideo->LFBsize = (32 << 20); + } else { + ivideo->LFBsize = (64 << 20); + } + ivideo->video_size += ivideo->LFBsize; + } + break; + case SIS_340: + case XGI_20: + case XGI_40: + reg = SiS_GetReg(SISSR, 0x14); + ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20; + if(ivideo->chip != XGI_20) { + reg = (reg & 0x0c) >> 2; + if(ivideo->revision_id == 2) { + if(reg & 0x01) reg = 0x02; + else reg = 0x00; + } + if(reg == 0x02) ivideo->video_size <<= 1; + else if(reg == 0x03) ivideo->video_size <<= 2; + } + break; +#endif + default: + return -1; + } + return 0; +} + +/* -------------- video bridge device detection --------------- */ + +static void sisfb_detect_VB_connect(struct sis_video_info *ivideo) +{ + u8 cr32, temp; + + /* No CRT2 on XGI Z7 */ + if(ivideo->chip == XGI_20) { + ivideo->sisfb_crt1off = 0; + return; + } + +#ifdef CONFIG_FB_SIS_300 + if(ivideo->sisvga_engine == SIS_300_VGA) { + temp = SiS_GetReg(SISSR, 0x17); + if((temp & 0x0F) && (ivideo->chip != SIS_300)) { + /* PAL/NTSC is stored on SR16 on such machines */ + if(!(ivideo->vbflags & (TV_PAL | TV_NTSC | TV_PALM | TV_PALN))) { + temp = SiS_GetReg(SISSR, 0x16); + if(temp & 0x20) + ivideo->vbflags |= TV_PAL; + else + ivideo->vbflags |= TV_NTSC; + } + } + } +#endif + + cr32 = SiS_GetReg(SISCR, 0x32); + + if(cr32 & SIS_CRT1) { + ivideo->sisfb_crt1off = 0; + } else { + ivideo->sisfb_crt1off = (cr32 & 0xDF) ? 1 : 0; + } + + ivideo->vbflags &= ~(CRT2_TV | CRT2_LCD | CRT2_VGA); + + if(cr32 & SIS_VB_TV) ivideo->vbflags |= CRT2_TV; + if(cr32 & SIS_VB_LCD) ivideo->vbflags |= CRT2_LCD; + if(cr32 & SIS_VB_CRT2) ivideo->vbflags |= CRT2_VGA; + + /* Check given parms for hardware compatibility. + * (Cannot do this in the search_xx routines since we don't + * know what hardware we are running on then) + */ + + if(ivideo->chip != SIS_550) { + ivideo->sisfb_dstn = ivideo->sisfb_fstn = 0; + } + + if(ivideo->sisfb_tvplug != -1) { + if( (ivideo->sisvga_engine != SIS_315_VGA) || + (!(ivideo->vbflags2 & VB2_SISYPBPRBRIDGE)) ) { + if(ivideo->sisfb_tvplug & TV_YPBPR) { + ivideo->sisfb_tvplug = -1; + printk(KERN_ERR "sisfb: YPbPr not supported\n"); + } + } + } + if(ivideo->sisfb_tvplug != -1) { + if( (ivideo->sisvga_engine != SIS_315_VGA) || + (!(ivideo->vbflags2 & VB2_SISHIVISIONBRIDGE)) ) { + if(ivideo->sisfb_tvplug & TV_HIVISION) { + ivideo->sisfb_tvplug = -1; + printk(KERN_ERR "sisfb: HiVision not supported\n"); + } + } + } + if(ivideo->sisfb_tvstd != -1) { + if( (!(ivideo->vbflags2 & VB2_SISBRIDGE)) && + (!((ivideo->sisvga_engine == SIS_315_VGA) && + (ivideo->vbflags2 & VB2_CHRONTEL))) ) { + if(ivideo->sisfb_tvstd & (TV_PALM | TV_PALN | TV_NTSCJ)) { + ivideo->sisfb_tvstd = -1; + printk(KERN_ERR "sisfb: PALM/PALN/NTSCJ not supported\n"); + } + } + } + + /* Detect/set TV plug & type */ + if(ivideo->sisfb_tvplug != -1) { + ivideo->vbflags |= ivideo->sisfb_tvplug; + } else { + if(cr32 & SIS_VB_YPBPR) ivideo->vbflags |= (TV_YPBPR|TV_YPBPR525I); /* default: 480i */ + else if(cr32 & SIS_VB_HIVISION) ivideo->vbflags |= TV_HIVISION; + else if(cr32 & SIS_VB_SCART) ivideo->vbflags |= TV_SCART; + else { + if(cr32 & SIS_VB_SVIDEO) ivideo->vbflags |= TV_SVIDEO; + if(cr32 & SIS_VB_COMPOSITE) ivideo->vbflags |= TV_AVIDEO; + } + } + + if(!(ivideo->vbflags & (TV_YPBPR | TV_HIVISION))) { + if(ivideo->sisfb_tvstd != -1) { + ivideo->vbflags &= ~(TV_NTSC | TV_PAL | TV_PALM | TV_PALN | TV_NTSCJ); + ivideo->vbflags |= ivideo->sisfb_tvstd; + } + if(ivideo->vbflags & TV_SCART) { + ivideo->vbflags &= ~(TV_NTSC | TV_PALM | TV_PALN | TV_NTSCJ); + ivideo->vbflags |= TV_PAL; + } + if(!(ivideo->vbflags & (TV_PAL | TV_NTSC | TV_PALM | TV_PALN | TV_NTSCJ))) { + if(ivideo->sisvga_engine == SIS_300_VGA) { + temp = SiS_GetReg(SISSR, 0x38); + if(temp & 0x01) ivideo->vbflags |= TV_PAL; + else ivideo->vbflags |= TV_NTSC; + } else if((ivideo->chip <= SIS_315PRO) || (ivideo->chip >= SIS_330)) { + temp = SiS_GetReg(SISSR, 0x38); + if(temp & 0x01) ivideo->vbflags |= TV_PAL; + else ivideo->vbflags |= TV_NTSC; + } else { + temp = SiS_GetReg(SISCR, 0x79); + if(temp & 0x20) ivideo->vbflags |= TV_PAL; + else ivideo->vbflags |= TV_NTSC; + } + } + } + + /* Copy forceCRT1 option to CRT1off if option is given */ + if(ivideo->sisfb_forcecrt1 != -1) { + ivideo->sisfb_crt1off = (ivideo->sisfb_forcecrt1) ? 0 : 1; + } +} + +/* ------------------ Sensing routines ------------------ */ + +static bool sisfb_test_DDC1(struct sis_video_info *ivideo) +{ + unsigned short old; + int count = 48; + + old = SiS_ReadDDC1Bit(&ivideo->SiS_Pr); + do { + if(old != SiS_ReadDDC1Bit(&ivideo->SiS_Pr)) break; + } while(count--); + return (count != -1); +} + +static void sisfb_sense_crt1(struct sis_video_info *ivideo) +{ + bool mustwait = false; + u8 sr1F, cr17; +#ifdef CONFIG_FB_SIS_315 + u8 cr63=0; +#endif + u16 temp = 0xffff; + int i; + + sr1F = SiS_GetReg(SISSR, 0x1F); + SiS_SetRegOR(SISSR, 0x1F, 0x04); + SiS_SetRegAND(SISSR, 0x1F, 0x3F); + if(sr1F & 0xc0) mustwait = true; + +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + cr63 = SiS_GetReg(SISCR, ivideo->SiS_Pr.SiS_MyCR63); + cr63 &= 0x40; + SiS_SetRegAND(SISCR, ivideo->SiS_Pr.SiS_MyCR63, 0xBF); + } +#endif + + cr17 = SiS_GetReg(SISCR, 0x17); + cr17 &= 0x80; + if(!cr17) { + SiS_SetRegOR(SISCR, 0x17, 0x80); + mustwait = true; + SiS_SetReg(SISSR, 0x00, 0x01); + SiS_SetReg(SISSR, 0x00, 0x03); + } + + if(mustwait) { + for(i=0; i < 10; i++) sisfbwaitretracecrt1(ivideo); + } + +#ifdef CONFIG_FB_SIS_315 + if(ivideo->chip >= SIS_330) { + SiS_SetRegAND(SISCR, 0x32, ~0x20); + if(ivideo->chip >= SIS_340) { + SiS_SetReg(SISCR, 0x57, 0x4a); + } else { + SiS_SetReg(SISCR, 0x57, 0x5f); + } + SiS_SetRegOR(SISCR, 0x53, 0x02); + while ((SiS_GetRegByte(SISINPSTAT)) & 0x01) break; + while (!((SiS_GetRegByte(SISINPSTAT)) & 0x01)) break; + if ((SiS_GetRegByte(SISMISCW)) & 0x10) temp = 1; + SiS_SetRegAND(SISCR, 0x53, 0xfd); + SiS_SetRegAND(SISCR, 0x57, 0x00); + } +#endif + + if(temp == 0xffff) { + i = 3; + do { + temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, + ivideo->sisvga_engine, 0, 0, NULL, ivideo->vbflags2); + } while(((temp == 0) || (temp == 0xffff)) && i--); + + if((temp == 0) || (temp == 0xffff)) { + if(sisfb_test_DDC1(ivideo)) temp = 1; + } + } + + if((temp) && (temp != 0xffff)) { + SiS_SetRegOR(SISCR, 0x32, 0x20); + } + +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + SiS_SetRegANDOR(SISCR, ivideo->SiS_Pr.SiS_MyCR63, 0xBF, cr63); + } +#endif + + SiS_SetRegANDOR(SISCR, 0x17, 0x7F, cr17); + + SiS_SetReg(SISSR, 0x1F, sr1F); +} + +/* Determine and detect attached devices on SiS30x */ +static void SiS_SenseLCD(struct sis_video_info *ivideo) +{ + unsigned char buffer[256]; + unsigned short temp, realcrtno, i; + u8 reg, cr37 = 0, paneltype = 0; + u16 xres, yres; + + ivideo->SiS_Pr.PanelSelfDetected = false; + + /* LCD detection only for TMDS bridges */ + if(!(ivideo->vbflags2 & VB2_SISTMDSBRIDGE)) + return; + if(ivideo->vbflags2 & VB2_30xBDH) + return; + + /* If LCD already set up by BIOS, skip it */ + reg = SiS_GetReg(SISCR, 0x32); + if(reg & 0x08) + return; + + realcrtno = 1; + if(ivideo->SiS_Pr.DDCPortMixup) + realcrtno = 0; + + /* Check DDC capabilities */ + temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, ivideo->sisvga_engine, + realcrtno, 0, &buffer[0], ivideo->vbflags2); + + if((!temp) || (temp == 0xffff) || (!(temp & 0x02))) + return; + + /* Read DDC data */ + i = 3; /* Number of retrys */ + do { + temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, + ivideo->sisvga_engine, realcrtno, 1, + &buffer[0], ivideo->vbflags2); + } while((temp) && i--); + + if(temp) + return; + + /* No digital device */ + if(!(buffer[0x14] & 0x80)) + return; + + /* First detailed timing preferred timing? */ + if(!(buffer[0x18] & 0x02)) + return; + + xres = buffer[0x38] | ((buffer[0x3a] & 0xf0) << 4); + yres = buffer[0x3b] | ((buffer[0x3d] & 0xf0) << 4); + + switch(xres) { + case 1024: + if(yres == 768) + paneltype = 0x02; + break; + case 1280: + if(yres == 1024) + paneltype = 0x03; + break; + case 1600: + if((yres == 1200) && (ivideo->vbflags2 & VB2_30xC)) + paneltype = 0x0b; + break; + } + + if(!paneltype) + return; + + if(buffer[0x23]) + cr37 |= 0x10; + + if((buffer[0x47] & 0x18) == 0x18) + cr37 |= ((((buffer[0x47] & 0x06) ^ 0x06) << 5) | 0x20); + else + cr37 |= 0xc0; + + SiS_SetReg(SISCR, 0x36, paneltype); + cr37 &= 0xf1; + SiS_SetRegANDOR(SISCR, 0x37, 0x0c, cr37); + SiS_SetRegOR(SISCR, 0x32, 0x08); + + ivideo->SiS_Pr.PanelSelfDetected = true; +} + +static int SISDoSense(struct sis_video_info *ivideo, u16 type, u16 test) +{ + int temp, mytest, result, i, j; + + for(j = 0; j < 10; j++) { + result = 0; + for(i = 0; i < 3; i++) { + mytest = test; + SiS_SetReg(SISPART4, 0x11, (type & 0x00ff)); + temp = (type >> 8) | (mytest & 0x00ff); + SiS_SetRegANDOR(SISPART4, 0x10, 0xe0, temp); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x1500); + mytest >>= 8; + mytest &= 0x7f; + temp = SiS_GetReg(SISPART4, 0x03); + temp ^= 0x0e; + temp &= mytest; + if(temp == mytest) result++; +#if 1 + SiS_SetReg(SISPART4, 0x11, 0x00); + SiS_SetRegAND(SISPART4, 0x10, 0xe0); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x1000); +#endif + } + if((result == 0) || (result >= 2)) break; + } + return result; +} + +static void SiS_Sense30x(struct sis_video_info *ivideo) +{ + u8 backupP4_0d,backupP2_00,backupP2_4d,backupSR_1e,biosflag=0; + u16 svhs=0, svhs_c=0; + u16 cvbs=0, cvbs_c=0; + u16 vga2=0, vga2_c=0; + int myflag, result; + char stdstr[] = "sisfb: Detected"; + char tvstr[] = "TV connected to"; + + if(ivideo->vbflags2 & VB2_301) { + svhs = 0x00b9; cvbs = 0x00b3; vga2 = 0x00d1; + myflag = SiS_GetReg(SISPART4, 0x01); + if(myflag & 0x04) { + svhs = 0x00dd; cvbs = 0x00ee; vga2 = 0x00fd; + } + } else if(ivideo->vbflags2 & (VB2_301B | VB2_302B)) { + svhs = 0x016b; cvbs = 0x0174; vga2 = 0x0190; + } else if(ivideo->vbflags2 & (VB2_301LV | VB2_302LV)) { + svhs = 0x0200; cvbs = 0x0100; + } else if(ivideo->vbflags2 & (VB2_301C | VB2_302ELV | VB2_307T | VB2_307LV)) { + svhs = 0x016b; cvbs = 0x0110; vga2 = 0x0190; + } else + return; + + vga2_c = 0x0e08; svhs_c = 0x0404; cvbs_c = 0x0804; + if(ivideo->vbflags & (VB2_301LV|VB2_302LV|VB2_302ELV|VB2_307LV)) { + svhs_c = 0x0408; cvbs_c = 0x0808; + } + + biosflag = 2; + if(ivideo->haveXGIROM) { + biosflag = ivideo->bios_abase[0x58] & 0x03; + } else if(ivideo->newrom) { + if(ivideo->bios_abase[0x5d] & 0x04) biosflag |= 0x01; + } else if(ivideo->sisvga_engine == SIS_300_VGA) { + if(ivideo->bios_abase) { + biosflag = ivideo->bios_abase[0xfe] & 0x03; + } + } + + if(ivideo->chip == SIS_300) { + myflag = SiS_GetReg(SISSR, 0x3b); + if(!(myflag & 0x01)) vga2 = vga2_c = 0; + } + + if(!(ivideo->vbflags2 & VB2_SISVGA2BRIDGE)) { + vga2 = vga2_c = 0; + } + + backupSR_1e = SiS_GetReg(SISSR, 0x1e); + SiS_SetRegOR(SISSR, 0x1e, 0x20); + + backupP4_0d = SiS_GetReg(SISPART4, 0x0d); + if(ivideo->vbflags2 & VB2_30xC) { + SiS_SetRegANDOR(SISPART4, 0x0d, ~0x07, 0x01); + } else { + SiS_SetRegOR(SISPART4, 0x0d, 0x04); + } + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x2000); + + backupP2_00 = SiS_GetReg(SISPART2, 0x00); + SiS_SetReg(SISPART2, 0x00, ((backupP2_00 | 0x1c) & 0xfc)); + + backupP2_4d = SiS_GetReg(SISPART2, 0x4d); + if(ivideo->vbflags2 & VB2_SISYPBPRBRIDGE) { + SiS_SetReg(SISPART2, 0x4d, (backupP2_4d & ~0x10)); + } + + if(!(ivideo->vbflags2 & VB2_30xCLV)) { + SISDoSense(ivideo, 0, 0); + } + + SiS_SetRegAND(SISCR, 0x32, ~0x14); + + if(vga2_c || vga2) { + if(SISDoSense(ivideo, vga2, vga2_c)) { + if(biosflag & 0x01) { + printk(KERN_INFO "%s %s SCART output\n", stdstr, tvstr); + SiS_SetRegOR(SISCR, 0x32, 0x04); + } else { + printk(KERN_INFO "%s secondary VGA connection\n", stdstr); + SiS_SetRegOR(SISCR, 0x32, 0x10); + } + } + } + + SiS_SetRegAND(SISCR, 0x32, 0x3f); + + if(ivideo->vbflags2 & VB2_30xCLV) { + SiS_SetRegOR(SISPART4, 0x0d, 0x04); + } + + if((ivideo->sisvga_engine == SIS_315_VGA) && (ivideo->vbflags2 & VB2_SISYPBPRBRIDGE)) { + SiS_SetReg(SISPART2, 0x4d, (backupP2_4d | 0x10)); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x2000); + if((result = SISDoSense(ivideo, svhs, 0x0604))) { + if((result = SISDoSense(ivideo, cvbs, 0x0804))) { + printk(KERN_INFO "%s %s YPbPr component output\n", stdstr, tvstr); + SiS_SetRegOR(SISCR, 0x32, 0x80); + } + } + SiS_SetReg(SISPART2, 0x4d, backupP2_4d); + } + + SiS_SetRegAND(SISCR, 0x32, ~0x03); + + if(!(ivideo->vbflags & TV_YPBPR)) { + if((result = SISDoSense(ivideo, svhs, svhs_c))) { + printk(KERN_INFO "%s %s SVIDEO output\n", stdstr, tvstr); + SiS_SetRegOR(SISCR, 0x32, 0x02); + } + if((biosflag & 0x02) || (!result)) { + if(SISDoSense(ivideo, cvbs, cvbs_c)) { + printk(KERN_INFO "%s %s COMPOSITE output\n", stdstr, tvstr); + SiS_SetRegOR(SISCR, 0x32, 0x01); + } + } + } + + SISDoSense(ivideo, 0, 0); + + SiS_SetReg(SISPART2, 0x00, backupP2_00); + SiS_SetReg(SISPART4, 0x0d, backupP4_0d); + SiS_SetReg(SISSR, 0x1e, backupSR_1e); + + if(ivideo->vbflags2 & VB2_30xCLV) { + biosflag = SiS_GetReg(SISPART2, 0x00); + if(biosflag & 0x20) { + for(myflag = 2; myflag > 0; myflag--) { + biosflag ^= 0x20; + SiS_SetReg(SISPART2, 0x00, biosflag); + } + } + } + + SiS_SetReg(SISPART2, 0x00, backupP2_00); +} + +/* Determine and detect attached TV's on Chrontel */ +static void SiS_SenseCh(struct sis_video_info *ivideo) +{ +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) + u8 temp1, temp2; + char stdstr[] = "sisfb: Chrontel: Detected TV connected to"; +#endif +#ifdef CONFIG_FB_SIS_300 + unsigned char test[3]; + int i; +#endif + + if(ivideo->chip < SIS_315H) { + +#ifdef CONFIG_FB_SIS_300 + ivideo->SiS_Pr.SiS_IF_DEF_CH70xx = 1; /* Chrontel 700x */ + SiS_SetChrontelGPIO(&ivideo->SiS_Pr, 0x9c); /* Set general purpose IO for Chrontel communication */ + SiS_DDC2Delay(&ivideo->SiS_Pr, 1000); + temp1 = SiS_GetCH700x(&ivideo->SiS_Pr, 0x25); + /* See Chrontel TB31 for explanation */ + temp2 = SiS_GetCH700x(&ivideo->SiS_Pr, 0x0e); + if(((temp2 & 0x07) == 0x01) || (temp2 & 0x04)) { + SiS_SetCH700x(&ivideo->SiS_Pr, 0x0e, 0x0b); + SiS_DDC2Delay(&ivideo->SiS_Pr, 300); + } + temp2 = SiS_GetCH700x(&ivideo->SiS_Pr, 0x25); + if(temp2 != temp1) temp1 = temp2; + + if((temp1 >= 0x22) && (temp1 <= 0x50)) { + /* Read power status */ + temp1 = SiS_GetCH700x(&ivideo->SiS_Pr, 0x0e); + if((temp1 & 0x03) != 0x03) { + /* Power all outputs */ + SiS_SetCH700x(&ivideo->SiS_Pr, 0x0e,0x0b); + SiS_DDC2Delay(&ivideo->SiS_Pr, 300); + } + /* Sense connected TV devices */ + for(i = 0; i < 3; i++) { + SiS_SetCH700x(&ivideo->SiS_Pr, 0x10, 0x01); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x96); + SiS_SetCH700x(&ivideo->SiS_Pr, 0x10, 0x00); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x96); + temp1 = SiS_GetCH700x(&ivideo->SiS_Pr, 0x10); + if(!(temp1 & 0x08)) test[i] = 0x02; + else if(!(temp1 & 0x02)) test[i] = 0x01; + else test[i] = 0; + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x96); + } + + if(test[0] == test[1]) temp1 = test[0]; + else if(test[0] == test[2]) temp1 = test[0]; + else if(test[1] == test[2]) temp1 = test[1]; + else { + printk(KERN_INFO + "sisfb: TV detection unreliable - test results varied\n"); + temp1 = test[2]; + } + if(temp1 == 0x02) { + printk(KERN_INFO "%s SVIDEO output\n", stdstr); + ivideo->vbflags |= TV_SVIDEO; + SiS_SetRegOR(SISCR, 0x32, 0x02); + SiS_SetRegAND(SISCR, 0x32, ~0x05); + } else if (temp1 == 0x01) { + printk(KERN_INFO "%s CVBS output\n", stdstr); + ivideo->vbflags |= TV_AVIDEO; + SiS_SetRegOR(SISCR, 0x32, 0x01); + SiS_SetRegAND(SISCR, 0x32, ~0x06); + } else { + SiS_SetCH70xxANDOR(&ivideo->SiS_Pr, 0x0e, 0x01, 0xF8); + SiS_SetRegAND(SISCR, 0x32, ~0x07); + } + } else if(temp1 == 0) { + SiS_SetCH70xxANDOR(&ivideo->SiS_Pr, 0x0e, 0x01, 0xF8); + SiS_SetRegAND(SISCR, 0x32, ~0x07); + } + /* Set general purpose IO for Chrontel communication */ + SiS_SetChrontelGPIO(&ivideo->SiS_Pr, 0x00); +#endif + + } else { + +#ifdef CONFIG_FB_SIS_315 + ivideo->SiS_Pr.SiS_IF_DEF_CH70xx = 2; /* Chrontel 7019 */ + temp1 = SiS_GetCH701x(&ivideo->SiS_Pr, 0x49); + SiS_SetCH701x(&ivideo->SiS_Pr, 0x49, 0x20); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x96); + temp2 = SiS_GetCH701x(&ivideo->SiS_Pr, 0x20); + temp2 |= 0x01; + SiS_SetCH701x(&ivideo->SiS_Pr, 0x20, temp2); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x96); + temp2 ^= 0x01; + SiS_SetCH701x(&ivideo->SiS_Pr, 0x20, temp2); + SiS_DDC2Delay(&ivideo->SiS_Pr, 0x96); + temp2 = SiS_GetCH701x(&ivideo->SiS_Pr, 0x20); + SiS_SetCH701x(&ivideo->SiS_Pr, 0x49, temp1); + temp1 = 0; + if(temp2 & 0x02) temp1 |= 0x01; + if(temp2 & 0x10) temp1 |= 0x01; + if(temp2 & 0x04) temp1 |= 0x02; + if( (temp1 & 0x01) && (temp1 & 0x02) ) temp1 = 0x04; + switch(temp1) { + case 0x01: + printk(KERN_INFO "%s CVBS output\n", stdstr); + ivideo->vbflags |= TV_AVIDEO; + SiS_SetRegOR(SISCR, 0x32, 0x01); + SiS_SetRegAND(SISCR, 0x32, ~0x06); + break; + case 0x02: + printk(KERN_INFO "%s SVIDEO output\n", stdstr); + ivideo->vbflags |= TV_SVIDEO; + SiS_SetRegOR(SISCR, 0x32, 0x02); + SiS_SetRegAND(SISCR, 0x32, ~0x05); + break; + case 0x04: + printk(KERN_INFO "%s SCART output\n", stdstr); + SiS_SetRegOR(SISCR, 0x32, 0x04); + SiS_SetRegAND(SISCR, 0x32, ~0x03); + break; + default: + SiS_SetRegAND(SISCR, 0x32, ~0x07); + } +#endif + } +} + +static void sisfb_get_VB_type(struct sis_video_info *ivideo) +{ + char stdstr[] = "sisfb: Detected"; + char bridgestr[] = "video bridge"; + u8 vb_chipid; + u8 reg; + + /* No CRT2 on XGI Z7 */ + if(ivideo->chip == XGI_20) + return; + + vb_chipid = SiS_GetReg(SISPART4, 0x00); + switch(vb_chipid) { + case 0x01: + reg = SiS_GetReg(SISPART4, 0x01); + if(reg < 0xb0) { + ivideo->vbflags |= VB_301; /* Deprecated */ + ivideo->vbflags2 |= VB2_301; + printk(KERN_INFO "%s SiS301 %s\n", stdstr, bridgestr); + } else if(reg < 0xc0) { + ivideo->vbflags |= VB_301B; /* Deprecated */ + ivideo->vbflags2 |= VB2_301B; + reg = SiS_GetReg(SISPART4, 0x23); + if(!(reg & 0x02)) { + ivideo->vbflags |= VB_30xBDH; /* Deprecated */ + ivideo->vbflags2 |= VB2_30xBDH; + printk(KERN_INFO "%s SiS301B-DH %s\n", stdstr, bridgestr); + } else { + printk(KERN_INFO "%s SiS301B %s\n", stdstr, bridgestr); + } + } else if(reg < 0xd0) { + ivideo->vbflags |= VB_301C; /* Deprecated */ + ivideo->vbflags2 |= VB2_301C; + printk(KERN_INFO "%s SiS301C %s\n", stdstr, bridgestr); + } else if(reg < 0xe0) { + ivideo->vbflags |= VB_301LV; /* Deprecated */ + ivideo->vbflags2 |= VB2_301LV; + printk(KERN_INFO "%s SiS301LV %s\n", stdstr, bridgestr); + } else if(reg <= 0xe1) { + reg = SiS_GetReg(SISPART4, 0x39); + if(reg == 0xff) { + ivideo->vbflags |= VB_302LV; /* Deprecated */ + ivideo->vbflags2 |= VB2_302LV; + printk(KERN_INFO "%s SiS302LV %s\n", stdstr, bridgestr); + } else { + ivideo->vbflags |= VB_301C; /* Deprecated */ + ivideo->vbflags2 |= VB2_301C; + printk(KERN_INFO "%s SiS301C(P4) %s\n", stdstr, bridgestr); +#if 0 + ivideo->vbflags |= VB_302ELV; /* Deprecated */ + ivideo->vbflags2 |= VB2_302ELV; + printk(KERN_INFO "%s SiS302ELV %s\n", stdstr, bridgestr); +#endif + } + } + break; + case 0x02: + ivideo->vbflags |= VB_302B; /* Deprecated */ + ivideo->vbflags2 |= VB2_302B; + printk(KERN_INFO "%s SiS302B %s\n", stdstr, bridgestr); + break; + } + + if((!(ivideo->vbflags2 & VB2_VIDEOBRIDGE)) && (ivideo->chip != SIS_300)) { + reg = SiS_GetReg(SISCR, 0x37); + reg &= SIS_EXTERNAL_CHIP_MASK; + reg >>= 1; + if(ivideo->sisvga_engine == SIS_300_VGA) { +#ifdef CONFIG_FB_SIS_300 + switch(reg) { + case SIS_EXTERNAL_CHIP_LVDS: + ivideo->vbflags |= VB_LVDS; /* Deprecated */ + ivideo->vbflags2 |= VB2_LVDS; + break; + case SIS_EXTERNAL_CHIP_TRUMPION: + ivideo->vbflags |= (VB_LVDS | VB_TRUMPION); /* Deprecated */ + ivideo->vbflags2 |= (VB2_LVDS | VB2_TRUMPION); + break; + case SIS_EXTERNAL_CHIP_CHRONTEL: + ivideo->vbflags |= VB_CHRONTEL; /* Deprecated */ + ivideo->vbflags2 |= VB2_CHRONTEL; + break; + case SIS_EXTERNAL_CHIP_LVDS_CHRONTEL: + ivideo->vbflags |= (VB_LVDS | VB_CHRONTEL); /* Deprecated */ + ivideo->vbflags2 |= (VB2_LVDS | VB2_CHRONTEL); + break; + } + if(ivideo->vbflags2 & VB2_CHRONTEL) ivideo->chronteltype = 1; +#endif + } else if(ivideo->chip < SIS_661) { +#ifdef CONFIG_FB_SIS_315 + switch (reg) { + case SIS310_EXTERNAL_CHIP_LVDS: + ivideo->vbflags |= VB_LVDS; /* Deprecated */ + ivideo->vbflags2 |= VB2_LVDS; + break; + case SIS310_EXTERNAL_CHIP_LVDS_CHRONTEL: + ivideo->vbflags |= (VB_LVDS | VB_CHRONTEL); /* Deprecated */ + ivideo->vbflags2 |= (VB2_LVDS | VB2_CHRONTEL); + break; + } + if(ivideo->vbflags2 & VB2_CHRONTEL) ivideo->chronteltype = 2; +#endif + } else if(ivideo->chip >= SIS_661) { +#ifdef CONFIG_FB_SIS_315 + reg = SiS_GetReg(SISCR, 0x38); + reg >>= 5; + switch(reg) { + case 0x02: + ivideo->vbflags |= VB_LVDS; /* Deprecated */ + ivideo->vbflags2 |= VB2_LVDS; + break; + case 0x03: + ivideo->vbflags |= (VB_LVDS | VB_CHRONTEL); /* Deprecated */ + ivideo->vbflags2 |= (VB2_LVDS | VB2_CHRONTEL); + break; + case 0x04: + ivideo->vbflags |= (VB_LVDS | VB_CONEXANT); /* Deprecated */ + ivideo->vbflags2 |= (VB2_LVDS | VB2_CONEXANT); + break; + } + if(ivideo->vbflags2 & VB2_CHRONTEL) ivideo->chronteltype = 2; +#endif + } + if(ivideo->vbflags2 & VB2_LVDS) { + printk(KERN_INFO "%s LVDS transmitter\n", stdstr); + } + if((ivideo->sisvga_engine == SIS_300_VGA) && (ivideo->vbflags2 & VB2_TRUMPION)) { + printk(KERN_INFO "%s Trumpion Zurac LCD scaler\n", stdstr); + } + if(ivideo->vbflags2 & VB2_CHRONTEL) { + printk(KERN_INFO "%s Chrontel TV encoder\n", stdstr); + } + if((ivideo->chip >= SIS_661) && (ivideo->vbflags2 & VB2_CONEXANT)) { + printk(KERN_INFO "%s Conexant external device\n", stdstr); + } + } + + if(ivideo->vbflags2 & VB2_SISBRIDGE) { + SiS_SenseLCD(ivideo); + SiS_Sense30x(ivideo); + } else if(ivideo->vbflags2 & VB2_CHRONTEL) { + SiS_SenseCh(ivideo); + } +} + +/* ---------- Engine initialization routines ------------ */ + +static void +sisfb_engine_init(struct sis_video_info *ivideo) +{ + + /* Initialize command queue (we use MMIO only) */ + + /* BEFORE THIS IS CALLED, THE ENGINES *MUST* BE SYNC'ED */ + + ivideo->caps &= ~(TURBO_QUEUE_CAP | + MMIO_CMD_QUEUE_CAP | + VM_CMD_QUEUE_CAP | + AGP_CMD_QUEUE_CAP); + +#ifdef CONFIG_FB_SIS_300 + if(ivideo->sisvga_engine == SIS_300_VGA) { + u32 tqueue_pos; + u8 tq_state; + + tqueue_pos = (ivideo->video_size - ivideo->cmdQueueSize) / (64 * 1024); + + tq_state = SiS_GetReg(SISSR, IND_SIS_TURBOQUEUE_SET); + tq_state |= 0xf0; + tq_state &= 0xfc; + tq_state |= (u8)(tqueue_pos >> 8); + SiS_SetReg(SISSR, IND_SIS_TURBOQUEUE_SET, tq_state); + + SiS_SetReg(SISSR, IND_SIS_TURBOQUEUE_ADR, (u8)(tqueue_pos & 0xff)); + + ivideo->caps |= TURBO_QUEUE_CAP; + } +#endif + +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + u32 tempq = 0, templ; + u8 temp; + + if(ivideo->chip == XGI_20) { + switch(ivideo->cmdQueueSize) { + case (64 * 1024): + temp = SIS_CMD_QUEUE_SIZE_Z7_64k; + break; + case (128 * 1024): + default: + temp = SIS_CMD_QUEUE_SIZE_Z7_128k; + } + } else { + switch(ivideo->cmdQueueSize) { + case (4 * 1024 * 1024): + temp = SIS_CMD_QUEUE_SIZE_4M; + break; + case (2 * 1024 * 1024): + temp = SIS_CMD_QUEUE_SIZE_2M; + break; + case (1 * 1024 * 1024): + temp = SIS_CMD_QUEUE_SIZE_1M; + break; + default: + case (512 * 1024): + temp = SIS_CMD_QUEUE_SIZE_512k; + } + } + + SiS_SetReg(SISSR, IND_SIS_CMDQUEUE_THRESHOLD, COMMAND_QUEUE_THRESHOLD); + SiS_SetReg(SISSR, IND_SIS_CMDQUEUE_SET, SIS_CMD_QUEUE_RESET); + + if((ivideo->chip >= XGI_40) && ivideo->modechanged) { + /* Must disable dual pipe on XGI_40. Can't do + * this in MMIO mode, because it requires + * setting/clearing a bit in the MMIO fire trigger + * register. + */ + if(!((templ = MMIO_IN32(ivideo->mmio_vbase, 0x8240)) & (1 << 10))) { + + MMIO_OUT32(ivideo->mmio_vbase, Q_WRITE_PTR, 0); + + SiS_SetReg(SISSR, IND_SIS_CMDQUEUE_SET, (temp | SIS_VRAM_CMDQUEUE_ENABLE)); + + tempq = MMIO_IN32(ivideo->mmio_vbase, Q_READ_PTR); + MMIO_OUT32(ivideo->mmio_vbase, Q_WRITE_PTR, tempq); + + tempq = (u32)(ivideo->video_size - ivideo->cmdQueueSize); + MMIO_OUT32(ivideo->mmio_vbase, Q_BASE_ADDR, tempq); + + writel(0x16800000 + 0x8240, ivideo->video_vbase + tempq); + writel(templ | (1 << 10), ivideo->video_vbase + tempq + 4); + writel(0x168F0000, ivideo->video_vbase + tempq + 8); + writel(0x168F0000, ivideo->video_vbase + tempq + 12); + + MMIO_OUT32(ivideo->mmio_vbase, Q_WRITE_PTR, (tempq + 16)); + + sisfb_syncaccel(ivideo); + + SiS_SetReg(SISSR, IND_SIS_CMDQUEUE_SET, SIS_CMD_QUEUE_RESET); + + } + } + + tempq = MMIO_IN32(ivideo->mmio_vbase, MMIO_QUEUE_READPORT); + MMIO_OUT32(ivideo->mmio_vbase, MMIO_QUEUE_WRITEPORT, tempq); + + temp |= (SIS_MMIO_CMD_ENABLE | SIS_CMD_AUTO_CORR); + SiS_SetReg(SISSR, IND_SIS_CMDQUEUE_SET, temp); + + tempq = (u32)(ivideo->video_size - ivideo->cmdQueueSize); + MMIO_OUT32(ivideo->mmio_vbase, MMIO_QUEUE_PHYBASE, tempq); + + ivideo->caps |= MMIO_CMD_QUEUE_CAP; + } +#endif + + ivideo->engineok = 1; +} + +static void sisfb_detect_lcd_type(struct sis_video_info *ivideo) +{ + u8 reg; + int i; + + reg = SiS_GetReg(SISCR, 0x36); + reg &= 0x0f; + if(ivideo->sisvga_engine == SIS_300_VGA) { + ivideo->CRT2LCDType = sis300paneltype[reg]; + } else if(ivideo->chip >= SIS_661) { + ivideo->CRT2LCDType = sis661paneltype[reg]; + } else { + ivideo->CRT2LCDType = sis310paneltype[reg]; + if((ivideo->chip == SIS_550) && (sisfb_fstn)) { + if((ivideo->CRT2LCDType != LCD_320x240_2) && + (ivideo->CRT2LCDType != LCD_320x240_3)) { + ivideo->CRT2LCDType = LCD_320x240; + } + } + } + + if(ivideo->CRT2LCDType == LCD_UNKNOWN) { + /* For broken BIOSes: Assume 1024x768, RGB18 */ + ivideo->CRT2LCDType = LCD_1024x768; + SiS_SetRegANDOR(SISCR, 0x36, 0xf0, 0x02); + SiS_SetRegANDOR(SISCR, 0x37, 0xee, 0x01); + printk(KERN_DEBUG "sisfb: Invalid panel ID (%02x), assuming 1024x768, RGB18\n", reg); + } + + for(i = 0; i < SIS_LCD_NUMBER; i++) { + if(ivideo->CRT2LCDType == sis_lcd_data[i].lcdtype) { + ivideo->lcdxres = sis_lcd_data[i].xres; + ivideo->lcdyres = sis_lcd_data[i].yres; + ivideo->lcddefmodeidx = sis_lcd_data[i].default_mode_idx; + break; + } + } + +#ifdef CONFIG_FB_SIS_300 + if(ivideo->SiS_Pr.SiS_CustomT == CUT_BARCO1366) { + ivideo->lcdxres = 1360; ivideo->lcdyres = 1024; + ivideo->lcddefmodeidx = DEFAULT_MODE_1360; + } else if(ivideo->SiS_Pr.SiS_CustomT == CUT_PANEL848) { + ivideo->lcdxres = 848; ivideo->lcdyres = 480; + ivideo->lcddefmodeidx = DEFAULT_MODE_848; + } else if(ivideo->SiS_Pr.SiS_CustomT == CUT_PANEL856) { + ivideo->lcdxres = 856; ivideo->lcdyres = 480; + ivideo->lcddefmodeidx = DEFAULT_MODE_856; + } +#endif + + printk(KERN_DEBUG "sisfb: Detected %dx%d flat panel\n", + ivideo->lcdxres, ivideo->lcdyres); +} + +static void sisfb_save_pdc_emi(struct sis_video_info *ivideo) +{ +#ifdef CONFIG_FB_SIS_300 + /* Save the current PanelDelayCompensation if the LCD is currently used */ + if(ivideo->sisvga_engine == SIS_300_VGA) { + if(ivideo->vbflags2 & (VB2_LVDS | VB2_30xBDH)) { + int tmp; + tmp = SiS_GetReg(SISCR, 0x30); + if(tmp & 0x20) { + /* Currently on LCD? If yes, read current pdc */ + ivideo->detectedpdc = SiS_GetReg(SISPART1, 0x13); + ivideo->detectedpdc &= 0x3c; + if(ivideo->SiS_Pr.PDC == -1) { + /* Let option override detection */ + ivideo->SiS_Pr.PDC = ivideo->detectedpdc; + } + printk(KERN_INFO "sisfb: Detected LCD PDC 0x%02x\n", + ivideo->detectedpdc); + } + if((ivideo->SiS_Pr.PDC != -1) && + (ivideo->SiS_Pr.PDC != ivideo->detectedpdc)) { + printk(KERN_INFO "sisfb: Using LCD PDC 0x%02x\n", + ivideo->SiS_Pr.PDC); + } + } + } +#endif + +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + + /* Try to find about LCDA */ + if(ivideo->vbflags2 & VB2_SISLCDABRIDGE) { + int tmp; + tmp = SiS_GetReg(SISPART1, 0x13); + if(tmp & 0x04) { + ivideo->SiS_Pr.SiS_UseLCDA = true; + ivideo->detectedlcda = 0x03; + } + } + + /* Save PDC */ + if(ivideo->vbflags2 & VB2_SISLVDSBRIDGE) { + int tmp; + tmp = SiS_GetReg(SISCR, 0x30); + if((tmp & 0x20) || (ivideo->detectedlcda != 0xff)) { + /* Currently on LCD? If yes, read current pdc */ + u8 pdc; + pdc = SiS_GetReg(SISPART1, 0x2D); + ivideo->detectedpdc = (pdc & 0x0f) << 1; + ivideo->detectedpdca = (pdc & 0xf0) >> 3; + pdc = SiS_GetReg(SISPART1, 0x35); + ivideo->detectedpdc |= ((pdc >> 7) & 0x01); + pdc = SiS_GetReg(SISPART1, 0x20); + ivideo->detectedpdca |= ((pdc >> 6) & 0x01); + if(ivideo->newrom) { + /* New ROM invalidates other PDC resp. */ + if(ivideo->detectedlcda != 0xff) { + ivideo->detectedpdc = 0xff; + } else { + ivideo->detectedpdca = 0xff; + } + } + if(ivideo->SiS_Pr.PDC == -1) { + if(ivideo->detectedpdc != 0xff) { + ivideo->SiS_Pr.PDC = ivideo->detectedpdc; + } + } + if(ivideo->SiS_Pr.PDCA == -1) { + if(ivideo->detectedpdca != 0xff) { + ivideo->SiS_Pr.PDCA = ivideo->detectedpdca; + } + } + if(ivideo->detectedpdc != 0xff) { + printk(KERN_INFO + "sisfb: Detected LCD PDC 0x%02x (for LCD=CRT2)\n", + ivideo->detectedpdc); + } + if(ivideo->detectedpdca != 0xff) { + printk(KERN_INFO + "sisfb: Detected LCD PDC1 0x%02x (for LCD=CRT1)\n", + ivideo->detectedpdca); + } + } + + /* Save EMI */ + if(ivideo->vbflags2 & VB2_SISEMIBRIDGE) { + ivideo->SiS_Pr.EMI_30 = SiS_GetReg(SISPART4, 0x30); + ivideo->SiS_Pr.EMI_31 = SiS_GetReg(SISPART4, 0x31); + ivideo->SiS_Pr.EMI_32 = SiS_GetReg(SISPART4, 0x32); + ivideo->SiS_Pr.EMI_33 = SiS_GetReg(SISPART4, 0x33); + ivideo->SiS_Pr.HaveEMI = true; + if((tmp & 0x20) || (ivideo->detectedlcda != 0xff)) { + ivideo->SiS_Pr.HaveEMILCD = true; + } + } + } + + /* Let user override detected PDCs (all bridges) */ + if(ivideo->vbflags2 & VB2_30xBLV) { + if((ivideo->SiS_Pr.PDC != -1) && + (ivideo->SiS_Pr.PDC != ivideo->detectedpdc)) { + printk(KERN_INFO "sisfb: Using LCD PDC 0x%02x (for LCD=CRT2)\n", + ivideo->SiS_Pr.PDC); + } + if((ivideo->SiS_Pr.PDCA != -1) && + (ivideo->SiS_Pr.PDCA != ivideo->detectedpdca)) { + printk(KERN_INFO "sisfb: Using LCD PDC1 0x%02x (for LCD=CRT1)\n", + ivideo->SiS_Pr.PDCA); + } + } + + } +#endif +} + +/* -------------------- Memory manager routines ---------------------- */ + +static u32 sisfb_getheapstart(struct sis_video_info *ivideo) +{ + u32 ret = ivideo->sisfb_parm_mem * 1024; + u32 maxoffs = ivideo->video_size - ivideo->hwcursor_size - ivideo->cmdQueueSize; + u32 def; + + /* Calculate heap start = end of memory for console + * + * CCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDHHHHQQQQQQQQQQ + * C = console, D = heap, H = HWCursor, Q = cmd-queue + * + * On 76x in UMA+LFB mode, the layout is as follows: + * DDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCHHHHQQQQQQQQQQQ + * where the heap is the entire UMA area, eventually + * into the LFB area if the given mem parameter is + * higher than the size of the UMA memory. + * + * Basically given by "mem" parameter + * + * maximum = videosize - cmd_queue - hwcursor + * (results in a heap of size 0) + * default = SiS 300: depends on videosize + * SiS 315/330/340/XGI: 32k below max + */ + + if(ivideo->sisvga_engine == SIS_300_VGA) { + if(ivideo->video_size > 0x1000000) { + def = 0xc00000; + } else if(ivideo->video_size > 0x800000) { + def = 0x800000; + } else { + def = 0x400000; + } + } else if(ivideo->UMAsize && ivideo->LFBsize) { + ret = def = 0; + } else { + def = maxoffs - 0x8000; + } + + /* Use default for secondary card for now (FIXME) */ + if((!ret) || (ret > maxoffs) || (ivideo->cardnumber != 0)) + ret = def; + + return ret; +} + +static u32 sisfb_getheapsize(struct sis_video_info *ivideo) +{ + u32 max = ivideo->video_size - ivideo->hwcursor_size - ivideo->cmdQueueSize; + u32 ret = 0; + + if(ivideo->UMAsize && ivideo->LFBsize) { + if( (!ivideo->sisfb_parm_mem) || + ((ivideo->sisfb_parm_mem * 1024) > max) || + ((max - (ivideo->sisfb_parm_mem * 1024)) < ivideo->UMAsize) ) { + ret = ivideo->UMAsize; + max -= ivideo->UMAsize; + } else { + ret = max - (ivideo->sisfb_parm_mem * 1024); + max = ivideo->sisfb_parm_mem * 1024; + } + ivideo->video_offset = ret; + ivideo->sisfb_mem = max; + } else { + ret = max - ivideo->heapstart; + ivideo->sisfb_mem = ivideo->heapstart; + } + + return ret; +} + +static int sisfb_heap_init(struct sis_video_info *ivideo) +{ + struct SIS_OH *poh; + + ivideo->video_offset = 0; + if(ivideo->sisfb_parm_mem) { + if( (ivideo->sisfb_parm_mem < (2 * 1024 * 1024)) || + (ivideo->sisfb_parm_mem > ivideo->video_size) ) { + ivideo->sisfb_parm_mem = 0; + } + } + + ivideo->heapstart = sisfb_getheapstart(ivideo); + ivideo->sisfb_heap_size = sisfb_getheapsize(ivideo); + + ivideo->sisfb_heap_start = ivideo->video_vbase + ivideo->heapstart; + ivideo->sisfb_heap_end = ivideo->sisfb_heap_start + ivideo->sisfb_heap_size; + + printk(KERN_INFO "sisfb: Memory heap starting at %dK, size %dK\n", + (int)(ivideo->heapstart / 1024), (int)(ivideo->sisfb_heap_size / 1024)); + + ivideo->sisfb_heap.vinfo = ivideo; + + ivideo->sisfb_heap.poha_chain = NULL; + ivideo->sisfb_heap.poh_freelist = NULL; + + poh = sisfb_poh_new_node(&ivideo->sisfb_heap); + if(poh == NULL) + return 1; + + poh->poh_next = &ivideo->sisfb_heap.oh_free; + poh->poh_prev = &ivideo->sisfb_heap.oh_free; + poh->size = ivideo->sisfb_heap_size; + poh->offset = ivideo->heapstart; + + ivideo->sisfb_heap.oh_free.poh_next = poh; + ivideo->sisfb_heap.oh_free.poh_prev = poh; + ivideo->sisfb_heap.oh_free.size = 0; + ivideo->sisfb_heap.max_freesize = poh->size; + + ivideo->sisfb_heap.oh_used.poh_next = &ivideo->sisfb_heap.oh_used; + ivideo->sisfb_heap.oh_used.poh_prev = &ivideo->sisfb_heap.oh_used; + ivideo->sisfb_heap.oh_used.size = SENTINEL; + + if(ivideo->cardnumber == 0) { + /* For the first card, make this heap the "global" one + * for old DRM (which could handle only one card) + */ + sisfb_heap = &ivideo->sisfb_heap; + } + + return 0; +} + +static struct SIS_OH * +sisfb_poh_new_node(struct SIS_HEAP *memheap) +{ + struct SIS_OHALLOC *poha; + struct SIS_OH *poh; + unsigned long cOhs; + int i; + + if(memheap->poh_freelist == NULL) { + poha = kmalloc(SIS_OH_ALLOC_SIZE, GFP_KERNEL); + if(!poha) + return NULL; + + poha->poha_next = memheap->poha_chain; + memheap->poha_chain = poha; + + cOhs = (SIS_OH_ALLOC_SIZE - sizeof(struct SIS_OHALLOC)) / sizeof(struct SIS_OH) + 1; + + poh = &poha->aoh[0]; + for(i = cOhs - 1; i != 0; i--) { + poh->poh_next = poh + 1; + poh = poh + 1; + } + + poh->poh_next = NULL; + memheap->poh_freelist = &poha->aoh[0]; + } + + poh = memheap->poh_freelist; + memheap->poh_freelist = poh->poh_next; + + return poh; +} + +static struct SIS_OH * +sisfb_poh_allocate(struct SIS_HEAP *memheap, u32 size) +{ + struct SIS_OH *pohThis; + struct SIS_OH *pohRoot; + int bAllocated = 0; + + if(size > memheap->max_freesize) { + DPRINTK("sisfb: Can't allocate %dk video memory\n", + (unsigned int) size / 1024); + return NULL; + } + + pohThis = memheap->oh_free.poh_next; + + while(pohThis != &memheap->oh_free) { + if(size <= pohThis->size) { + bAllocated = 1; + break; + } + pohThis = pohThis->poh_next; + } + + if(!bAllocated) { + DPRINTK("sisfb: Can't allocate %dk video memory\n", + (unsigned int) size / 1024); + return NULL; + } + + if(size == pohThis->size) { + pohRoot = pohThis; + sisfb_delete_node(pohThis); + } else { + pohRoot = sisfb_poh_new_node(memheap); + if(pohRoot == NULL) + return NULL; + + pohRoot->offset = pohThis->offset; + pohRoot->size = size; + + pohThis->offset += size; + pohThis->size -= size; + } + + memheap->max_freesize -= size; + + pohThis = &memheap->oh_used; + sisfb_insert_node(pohThis, pohRoot); + + return pohRoot; +} + +static void +sisfb_delete_node(struct SIS_OH *poh) +{ + poh->poh_prev->poh_next = poh->poh_next; + poh->poh_next->poh_prev = poh->poh_prev; +} + +static void +sisfb_insert_node(struct SIS_OH *pohList, struct SIS_OH *poh) +{ + struct SIS_OH *pohTemp = pohList->poh_next; + + pohList->poh_next = poh; + pohTemp->poh_prev = poh; + + poh->poh_prev = pohList; + poh->poh_next = pohTemp; +} + +static struct SIS_OH * +sisfb_poh_free(struct SIS_HEAP *memheap, u32 base) +{ + struct SIS_OH *pohThis; + struct SIS_OH *poh_freed; + struct SIS_OH *poh_prev; + struct SIS_OH *poh_next; + u32 ulUpper; + u32 ulLower; + int foundNode = 0; + + poh_freed = memheap->oh_used.poh_next; + + while(poh_freed != &memheap->oh_used) { + if(poh_freed->offset == base) { + foundNode = 1; + break; + } + + poh_freed = poh_freed->poh_next; + } + + if(!foundNode) + return NULL; + + memheap->max_freesize += poh_freed->size; + + poh_prev = poh_next = NULL; + ulUpper = poh_freed->offset + poh_freed->size; + ulLower = poh_freed->offset; + + pohThis = memheap->oh_free.poh_next; + + while(pohThis != &memheap->oh_free) { + if(pohThis->offset == ulUpper) { + poh_next = pohThis; + } else if((pohThis->offset + pohThis->size) == ulLower) { + poh_prev = pohThis; + } + pohThis = pohThis->poh_next; + } + + sisfb_delete_node(poh_freed); + + if(poh_prev && poh_next) { + poh_prev->size += (poh_freed->size + poh_next->size); + sisfb_delete_node(poh_next); + sisfb_free_node(memheap, poh_freed); + sisfb_free_node(memheap, poh_next); + return poh_prev; + } + + if(poh_prev) { + poh_prev->size += poh_freed->size; + sisfb_free_node(memheap, poh_freed); + return poh_prev; + } + + if(poh_next) { + poh_next->size += poh_freed->size; + poh_next->offset = poh_freed->offset; + sisfb_free_node(memheap, poh_freed); + return poh_next; + } + + sisfb_insert_node(&memheap->oh_free, poh_freed); + + return poh_freed; +} + +static void +sisfb_free_node(struct SIS_HEAP *memheap, struct SIS_OH *poh) +{ + if(poh == NULL) + return; + + poh->poh_next = memheap->poh_freelist; + memheap->poh_freelist = poh; +} + +static void +sis_int_malloc(struct sis_video_info *ivideo, struct sis_memreq *req) +{ + struct SIS_OH *poh = NULL; + + if((ivideo) && (ivideo->sisfb_id == SISFB_ID) && (!ivideo->havenoheap)) + poh = sisfb_poh_allocate(&ivideo->sisfb_heap, (u32)req->size); + + if(poh == NULL) { + req->offset = req->size = 0; + DPRINTK("sisfb: Video RAM allocation failed\n"); + } else { + req->offset = poh->offset; + req->size = poh->size; + DPRINTK("sisfb: Video RAM allocation succeeded: 0x%lx\n", + (poh->offset + ivideo->video_vbase)); + } +} + +void +sis_malloc(struct sis_memreq *req) +{ + struct sis_video_info *ivideo = sisfb_heap->vinfo; + + if(&ivideo->sisfb_heap == sisfb_heap) + sis_int_malloc(ivideo, req); + else + req->offset = req->size = 0; +} + +void +sis_malloc_new(struct pci_dev *pdev, struct sis_memreq *req) +{ + struct sis_video_info *ivideo = pci_get_drvdata(pdev); + + sis_int_malloc(ivideo, req); +} + +/* sis_free: u32 because "base" is offset inside video ram, can never be >4GB */ + +static void +sis_int_free(struct sis_video_info *ivideo, u32 base) +{ + struct SIS_OH *poh; + + if((!ivideo) || (ivideo->sisfb_id != SISFB_ID) || (ivideo->havenoheap)) + return; + + poh = sisfb_poh_free(&ivideo->sisfb_heap, base); + + if(poh == NULL) { + DPRINTK("sisfb: sisfb_poh_free() failed at base 0x%x\n", + (unsigned int) base); + } +} + +void +sis_free(u32 base) +{ + struct sis_video_info *ivideo = sisfb_heap->vinfo; + + sis_int_free(ivideo, base); +} + +void +sis_free_new(struct pci_dev *pdev, u32 base) +{ + struct sis_video_info *ivideo = pci_get_drvdata(pdev); + + sis_int_free(ivideo, base); +} + +/* --------------------- SetMode routines ------------------------- */ + +static void +sisfb_check_engine_and_sync(struct sis_video_info *ivideo) +{ + u8 cr30, cr31; + + /* Check if MMIO and engines are enabled, + * and sync in case they are. Can't use + * ivideo->accel here, as this might have + * been changed before this is called. + */ + cr30 = SiS_GetReg(SISSR, IND_SIS_PCI_ADDRESS_SET); + cr31 = SiS_GetReg(SISSR, IND_SIS_MODULE_ENABLE); + /* MMIO and 2D/3D engine enabled? */ + if((cr30 & SIS_MEM_MAP_IO_ENABLE) && (cr31 & 0x42)) { +#ifdef CONFIG_FB_SIS_300 + if(ivideo->sisvga_engine == SIS_300_VGA) { + /* Don't care about TurboQueue. It's + * enough to know that the engines + * are enabled + */ + sisfb_syncaccel(ivideo); + } +#endif +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + /* Check that any queue mode is + * enabled, and that the queue + * is not in the state of "reset" + */ + cr30 = SiS_GetReg(SISSR, 0x26); + if((cr30 & 0xe0) && (!(cr30 & 0x01))) { + sisfb_syncaccel(ivideo); + } + } +#endif + } +} + +static void +sisfb_pre_setmode(struct sis_video_info *ivideo) +{ + u8 cr30 = 0, cr31 = 0, cr33 = 0, cr35 = 0, cr38 = 0; + int tvregnum = 0; + + ivideo->currentvbflags &= (VB_VIDEOBRIDGE | VB_DISPTYPE_DISP2); + + SiS_SetReg(SISSR, 0x05, 0x86); + + cr31 = SiS_GetReg(SISCR, 0x31); + cr31 &= ~0x60; + cr31 |= 0x04; + + cr33 = ivideo->rate_idx & 0x0F; + +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + if(ivideo->chip >= SIS_661) { + cr38 = SiS_GetReg(SISCR, 0x38); + cr38 &= ~0x07; /* Clear LCDA/DualEdge and YPbPr bits */ + } else { + tvregnum = 0x38; + cr38 = SiS_GetReg(SISCR, tvregnum); + cr38 &= ~0x3b; /* Clear LCDA/DualEdge and YPbPr bits */ + } + } +#endif +#ifdef CONFIG_FB_SIS_300 + if(ivideo->sisvga_engine == SIS_300_VGA) { + tvregnum = 0x35; + cr38 = SiS_GetReg(SISCR, tvregnum); + } +#endif + + SiS_SetEnableDstn(&ivideo->SiS_Pr, false); + SiS_SetEnableFstn(&ivideo->SiS_Pr, false); + ivideo->curFSTN = ivideo->curDSTN = 0; + + switch(ivideo->currentvbflags & VB_DISPTYPE_DISP2) { + + case CRT2_TV: + cr38 &= ~0xc0; /* Clear PAL-M / PAL-N bits */ + if((ivideo->vbflags & TV_YPBPR) && (ivideo->vbflags2 & VB2_SISYPBPRBRIDGE)) { +#ifdef CONFIG_FB_SIS_315 + if(ivideo->chip >= SIS_661) { + cr38 |= 0x04; + if(ivideo->vbflags & TV_YPBPR525P) cr35 |= 0x20; + else if(ivideo->vbflags & TV_YPBPR750P) cr35 |= 0x40; + else if(ivideo->vbflags & TV_YPBPR1080I) cr35 |= 0x60; + cr30 |= SIS_SIMULTANEOUS_VIEW_ENABLE; + cr35 &= ~0x01; + ivideo->currentvbflags |= (TV_YPBPR | (ivideo->vbflags & TV_YPBPRALL)); + } else if(ivideo->sisvga_engine == SIS_315_VGA) { + cr30 |= (0x80 | SIS_SIMULTANEOUS_VIEW_ENABLE); + cr38 |= 0x08; + if(ivideo->vbflags & TV_YPBPR525P) cr38 |= 0x10; + else if(ivideo->vbflags & TV_YPBPR750P) cr38 |= 0x20; + else if(ivideo->vbflags & TV_YPBPR1080I) cr38 |= 0x30; + cr31 &= ~0x01; + ivideo->currentvbflags |= (TV_YPBPR | (ivideo->vbflags & TV_YPBPRALL)); + } +#endif + } else if((ivideo->vbflags & TV_HIVISION) && + (ivideo->vbflags2 & VB2_SISHIVISIONBRIDGE)) { + if(ivideo->chip >= SIS_661) { + cr38 |= 0x04; + cr35 |= 0x60; + } else { + cr30 |= 0x80; + } + cr30 |= SIS_SIMULTANEOUS_VIEW_ENABLE; + cr31 |= 0x01; + cr35 |= 0x01; + ivideo->currentvbflags |= TV_HIVISION; + } else if(ivideo->vbflags & TV_SCART) { + cr30 = (SIS_VB_OUTPUT_SCART | SIS_SIMULTANEOUS_VIEW_ENABLE); + cr31 |= 0x01; + cr35 |= 0x01; + ivideo->currentvbflags |= TV_SCART; + } else { + if(ivideo->vbflags & TV_SVIDEO) { + cr30 = (SIS_VB_OUTPUT_SVIDEO | SIS_SIMULTANEOUS_VIEW_ENABLE); + ivideo->currentvbflags |= TV_SVIDEO; + } + if(ivideo->vbflags & TV_AVIDEO) { + cr30 = (SIS_VB_OUTPUT_COMPOSITE | SIS_SIMULTANEOUS_VIEW_ENABLE); + ivideo->currentvbflags |= TV_AVIDEO; + } + } + cr31 |= SIS_DRIVER_MODE; + + if(ivideo->vbflags & (TV_AVIDEO | TV_SVIDEO)) { + if(ivideo->vbflags & TV_PAL) { + cr31 |= 0x01; cr35 |= 0x01; + ivideo->currentvbflags |= TV_PAL; + if(ivideo->vbflags & TV_PALM) { + cr38 |= 0x40; cr35 |= 0x04; + ivideo->currentvbflags |= TV_PALM; + } else if(ivideo->vbflags & TV_PALN) { + cr38 |= 0x80; cr35 |= 0x08; + ivideo->currentvbflags |= TV_PALN; + } + } else { + cr31 &= ~0x01; cr35 &= ~0x01; + ivideo->currentvbflags |= TV_NTSC; + if(ivideo->vbflags & TV_NTSCJ) { + cr38 |= 0x40; cr35 |= 0x02; + ivideo->currentvbflags |= TV_NTSCJ; + } + } + } + break; + + case CRT2_LCD: + cr30 = (SIS_VB_OUTPUT_LCD | SIS_SIMULTANEOUS_VIEW_ENABLE); + cr31 |= SIS_DRIVER_MODE; + SiS_SetEnableDstn(&ivideo->SiS_Pr, ivideo->sisfb_dstn); + SiS_SetEnableFstn(&ivideo->SiS_Pr, ivideo->sisfb_fstn); + ivideo->curFSTN = ivideo->sisfb_fstn; + ivideo->curDSTN = ivideo->sisfb_dstn; + break; + + case CRT2_VGA: + cr30 = (SIS_VB_OUTPUT_CRT2 | SIS_SIMULTANEOUS_VIEW_ENABLE); + cr31 |= SIS_DRIVER_MODE; + if(ivideo->sisfb_nocrt2rate) { + cr33 |= (sisbios_mode[ivideo->sisfb_mode_idx].rate_idx << 4); + } else { + cr33 |= ((ivideo->rate_idx & 0x0F) << 4); + } + break; + + default: /* disable CRT2 */ + cr30 = 0x00; + cr31 |= (SIS_DRIVER_MODE | SIS_VB_OUTPUT_DISABLE); + } + + SiS_SetReg(SISCR, 0x30, cr30); + SiS_SetReg(SISCR, 0x33, cr33); + + if(ivideo->chip >= SIS_661) { +#ifdef CONFIG_FB_SIS_315 + cr31 &= ~0x01; /* Clear PAL flag (now in CR35) */ + SiS_SetRegANDOR(SISCR, 0x35, ~0x10, cr35); /* Leave overscan bit alone */ + cr38 &= 0x07; /* Use only LCDA and HiVision/YPbPr bits */ + SiS_SetRegANDOR(SISCR, 0x38, 0xf8, cr38); +#endif + } else if(ivideo->chip != SIS_300) { + SiS_SetReg(SISCR, tvregnum, cr38); + } + SiS_SetReg(SISCR, 0x31, cr31); + + ivideo->SiS_Pr.SiS_UseOEM = ivideo->sisfb_useoem; + + sisfb_check_engine_and_sync(ivideo); +} + +/* Fix SR11 for 661 and later */ +#ifdef CONFIG_FB_SIS_315 +static void +sisfb_fixup_SR11(struct sis_video_info *ivideo) +{ + u8 tmpreg; + + if(ivideo->chip >= SIS_661) { + tmpreg = SiS_GetReg(SISSR, 0x11); + if(tmpreg & 0x20) { + tmpreg = SiS_GetReg(SISSR, 0x3e); + tmpreg = (tmpreg + 1) & 0xff; + SiS_SetReg(SISSR, 0x3e, tmpreg); + tmpreg = SiS_GetReg(SISSR, 0x11); + } + if(tmpreg & 0xf0) { + SiS_SetRegAND(SISSR, 0x11, 0x0f); + } + } +} +#endif + +static void +sisfb_set_TVxposoffset(struct sis_video_info *ivideo, int val) +{ + if(val > 32) val = 32; + if(val < -32) val = -32; + ivideo->tvxpos = val; + + if(ivideo->sisfblocked) return; + if(!ivideo->modechanged) return; + + if(ivideo->currentvbflags & CRT2_TV) { + + if(ivideo->vbflags2 & VB2_CHRONTEL) { + + int x = ivideo->tvx; + + switch(ivideo->chronteltype) { + case 1: + x += val; + if(x < 0) x = 0; + SiS_SetReg(SISSR, 0x05, 0x86); + SiS_SetCH700x(&ivideo->SiS_Pr, 0x0a, (x & 0xff)); + SiS_SetCH70xxANDOR(&ivideo->SiS_Pr, 0x08, ((x & 0x0100) >> 7), 0xFD); + break; + case 2: + /* Not supported by hardware */ + break; + } + + } else if(ivideo->vbflags2 & VB2_SISBRIDGE) { + + u8 p2_1f,p2_20,p2_2b,p2_42,p2_43; + unsigned short temp; + + p2_1f = ivideo->p2_1f; + p2_20 = ivideo->p2_20; + p2_2b = ivideo->p2_2b; + p2_42 = ivideo->p2_42; + p2_43 = ivideo->p2_43; + + temp = p2_1f | ((p2_20 & 0xf0) << 4); + temp += (val * 2); + p2_1f = temp & 0xff; + p2_20 = (temp & 0xf00) >> 4; + p2_2b = ((p2_2b & 0x0f) + (val * 2)) & 0x0f; + temp = p2_43 | ((p2_42 & 0xf0) << 4); + temp += (val * 2); + p2_43 = temp & 0xff; + p2_42 = (temp & 0xf00) >> 4; + SiS_SetReg(SISPART2, 0x1f, p2_1f); + SiS_SetRegANDOR(SISPART2, 0x20, 0x0F, p2_20); + SiS_SetRegANDOR(SISPART2, 0x2b, 0xF0, p2_2b); + SiS_SetRegANDOR(SISPART2, 0x42, 0x0F, p2_42); + SiS_SetReg(SISPART2, 0x43, p2_43); + } + } +} + +static void +sisfb_set_TVyposoffset(struct sis_video_info *ivideo, int val) +{ + if(val > 32) val = 32; + if(val < -32) val = -32; + ivideo->tvypos = val; + + if(ivideo->sisfblocked) return; + if(!ivideo->modechanged) return; + + if(ivideo->currentvbflags & CRT2_TV) { + + if(ivideo->vbflags2 & VB2_CHRONTEL) { + + int y = ivideo->tvy; + + switch(ivideo->chronteltype) { + case 1: + y -= val; + if(y < 0) y = 0; + SiS_SetReg(SISSR, 0x05, 0x86); + SiS_SetCH700x(&ivideo->SiS_Pr, 0x0b, (y & 0xff)); + SiS_SetCH70xxANDOR(&ivideo->SiS_Pr, 0x08, ((y & 0x0100) >> 8), 0xFE); + break; + case 2: + /* Not supported by hardware */ + break; + } + + } else if(ivideo->vbflags2 & VB2_SISBRIDGE) { + + char p2_01, p2_02; + val /= 2; + p2_01 = ivideo->p2_01; + p2_02 = ivideo->p2_02; + + p2_01 += val; + p2_02 += val; + if(!(ivideo->currentvbflags & (TV_HIVISION | TV_YPBPR))) { + while((p2_01 <= 0) || (p2_02 <= 0)) { + p2_01 += 2; + p2_02 += 2; + } + } + SiS_SetReg(SISPART2, 0x01, p2_01); + SiS_SetReg(SISPART2, 0x02, p2_02); + } + } +} + +static void +sisfb_post_setmode(struct sis_video_info *ivideo) +{ + bool crt1isoff = false; + bool doit = true; +#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315) + u8 reg; +#endif +#ifdef CONFIG_FB_SIS_315 + u8 reg1; +#endif + + SiS_SetReg(SISSR, 0x05, 0x86); + +#ifdef CONFIG_FB_SIS_315 + sisfb_fixup_SR11(ivideo); +#endif + + /* Now we actually HAVE changed the display mode */ + ivideo->modechanged = 1; + + /* We can't switch off CRT1 if bridge is in slave mode */ + if(ivideo->vbflags2 & VB2_VIDEOBRIDGE) { + if(sisfb_bridgeisslave(ivideo)) doit = false; + } else + ivideo->sisfb_crt1off = 0; + +#ifdef CONFIG_FB_SIS_300 + if(ivideo->sisvga_engine == SIS_300_VGA) { + if((ivideo->sisfb_crt1off) && (doit)) { + crt1isoff = true; + reg = 0x00; + } else { + crt1isoff = false; + reg = 0x80; + } + SiS_SetRegANDOR(SISCR, 0x17, 0x7f, reg); + } +#endif +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + if((ivideo->sisfb_crt1off) && (doit)) { + crt1isoff = true; + reg = 0x40; + reg1 = 0xc0; + } else { + crt1isoff = false; + reg = 0x00; + reg1 = 0x00; + } + SiS_SetRegANDOR(SISCR, ivideo->SiS_Pr.SiS_MyCR63, ~0x40, reg); + SiS_SetRegANDOR(SISSR, 0x1f, 0x3f, reg1); + } +#endif + + if(crt1isoff) { + ivideo->currentvbflags &= ~VB_DISPTYPE_CRT1; + ivideo->currentvbflags |= VB_SINGLE_MODE; + } else { + ivideo->currentvbflags |= VB_DISPTYPE_CRT1; + if(ivideo->currentvbflags & VB_DISPTYPE_CRT2) { + ivideo->currentvbflags |= VB_MIRROR_MODE; + } else { + ivideo->currentvbflags |= VB_SINGLE_MODE; + } + } + + SiS_SetRegAND(SISSR, IND_SIS_RAMDAC_CONTROL, ~0x04); + + if(ivideo->currentvbflags & CRT2_TV) { + if(ivideo->vbflags2 & VB2_SISBRIDGE) { + ivideo->p2_1f = SiS_GetReg(SISPART2, 0x1f); + ivideo->p2_20 = SiS_GetReg(SISPART2, 0x20); + ivideo->p2_2b = SiS_GetReg(SISPART2, 0x2b); + ivideo->p2_42 = SiS_GetReg(SISPART2, 0x42); + ivideo->p2_43 = SiS_GetReg(SISPART2, 0x43); + ivideo->p2_01 = SiS_GetReg(SISPART2, 0x01); + ivideo->p2_02 = SiS_GetReg(SISPART2, 0x02); + } else if(ivideo->vbflags2 & VB2_CHRONTEL) { + if(ivideo->chronteltype == 1) { + ivideo->tvx = SiS_GetCH700x(&ivideo->SiS_Pr, 0x0a); + ivideo->tvx |= (((SiS_GetCH700x(&ivideo->SiS_Pr, 0x08) & 0x02) >> 1) << 8); + ivideo->tvy = SiS_GetCH700x(&ivideo->SiS_Pr, 0x0b); + ivideo->tvy |= ((SiS_GetCH700x(&ivideo->SiS_Pr, 0x08) & 0x01) << 8); + } + } + } + + if(ivideo->tvxpos) { + sisfb_set_TVxposoffset(ivideo, ivideo->tvxpos); + } + if(ivideo->tvypos) { + sisfb_set_TVyposoffset(ivideo, ivideo->tvypos); + } + + /* Eventually sync engines */ + sisfb_check_engine_and_sync(ivideo); + + /* (Re-)Initialize chip engines */ + if(ivideo->accel) { + sisfb_engine_init(ivideo); + } else { + ivideo->engineok = 0; + } +} + +static int +sisfb_reset_mode(struct sis_video_info *ivideo) +{ + if(sisfb_set_mode(ivideo, 0)) + return 1; + + sisfb_set_pitch(ivideo); + sisfb_set_base_CRT1(ivideo, ivideo->current_base); + sisfb_set_base_CRT2(ivideo, ivideo->current_base); + + return 0; +} + +static void +sisfb_handle_command(struct sis_video_info *ivideo, struct sisfb_cmd *sisfb_command) +{ + int mycrt1off; + + switch(sisfb_command->sisfb_cmd) { + case SISFB_CMD_GETVBFLAGS: + if(!ivideo->modechanged) { + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_EARLY; + } else { + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_OK; + sisfb_command->sisfb_result[1] = ivideo->currentvbflags; + sisfb_command->sisfb_result[2] = ivideo->vbflags2; + } + break; + case SISFB_CMD_SWITCHCRT1: + /* arg[0]: 0 = off, 1 = on, 99 = query */ + if(!ivideo->modechanged) { + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_EARLY; + } else if(sisfb_command->sisfb_arg[0] == 99) { + /* Query */ + sisfb_command->sisfb_result[1] = ivideo->sisfb_crt1off ? 0 : 1; + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_OK; + } else if(ivideo->sisfblocked) { + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_LOCKED; + } else if((!(ivideo->currentvbflags & CRT2_ENABLE)) && + (sisfb_command->sisfb_arg[0] == 0)) { + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_NOCRT2; + } else { + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_OK; + mycrt1off = sisfb_command->sisfb_arg[0] ? 0 : 1; + if( ((ivideo->currentvbflags & VB_DISPTYPE_CRT1) && mycrt1off) || + ((!(ivideo->currentvbflags & VB_DISPTYPE_CRT1)) && !mycrt1off) ) { + ivideo->sisfb_crt1off = mycrt1off; + if(sisfb_reset_mode(ivideo)) { + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_OTHER; + } + } + sisfb_command->sisfb_result[1] = ivideo->sisfb_crt1off ? 0 : 1; + } + break; + /* more to come */ + default: + sisfb_command->sisfb_result[0] = SISFB_CMD_ERR_UNKNOWN; + printk(KERN_ERR "sisfb: Unknown command 0x%x\n", + sisfb_command->sisfb_cmd); + } +} + +#ifndef MODULE +static int __init sisfb_setup(char *options) +{ + char *this_opt; + + sisfb_setdefaultparms(); + + if(!options || !(*options)) + return 0; + + while((this_opt = strsep(&options, ",")) != NULL) { + + if(!(*this_opt)) continue; + + if(!strnicmp(this_opt, "off", 3)) { + sisfb_off = 1; + } else if(!strnicmp(this_opt, "forcecrt2type:", 14)) { + /* Need to check crt2 type first for fstn/dstn */ + sisfb_search_crt2type(this_opt + 14); + } else if(!strnicmp(this_opt, "tvmode:",7)) { + sisfb_search_tvstd(this_opt + 7); + } else if(!strnicmp(this_opt, "tvstandard:",11)) { + sisfb_search_tvstd(this_opt + 11); + } else if(!strnicmp(this_opt, "mode:", 5)) { + sisfb_search_mode(this_opt + 5, false); + } else if(!strnicmp(this_opt, "vesa:", 5)) { + sisfb_search_vesamode(simple_strtoul(this_opt + 5, NULL, 0), false); + } else if(!strnicmp(this_opt, "rate:", 5)) { + sisfb_parm_rate = simple_strtoul(this_opt + 5, NULL, 0); + } else if(!strnicmp(this_opt, "forcecrt1:", 10)) { + sisfb_forcecrt1 = (int)simple_strtoul(this_opt + 10, NULL, 0); + } else if(!strnicmp(this_opt, "mem:",4)) { + sisfb_parm_mem = simple_strtoul(this_opt + 4, NULL, 0); + } else if(!strnicmp(this_opt, "pdc:", 4)) { + sisfb_pdc = simple_strtoul(this_opt + 4, NULL, 0); + } else if(!strnicmp(this_opt, "pdc1:", 5)) { + sisfb_pdca = simple_strtoul(this_opt + 5, NULL, 0); + } else if(!strnicmp(this_opt, "noaccel", 7)) { + sisfb_accel = 0; + } else if(!strnicmp(this_opt, "accel", 5)) { + sisfb_accel = -1; + } else if(!strnicmp(this_opt, "noypan", 6)) { + sisfb_ypan = 0; + } else if(!strnicmp(this_opt, "ypan", 4)) { + sisfb_ypan = -1; + } else if(!strnicmp(this_opt, "nomax", 5)) { + sisfb_max = 0; + } else if(!strnicmp(this_opt, "max", 3)) { + sisfb_max = -1; + } else if(!strnicmp(this_opt, "userom:", 7)) { + sisfb_userom = (int)simple_strtoul(this_opt + 7, NULL, 0); + } else if(!strnicmp(this_opt, "useoem:", 7)) { + sisfb_useoem = (int)simple_strtoul(this_opt + 7, NULL, 0); + } else if(!strnicmp(this_opt, "nocrt2rate", 10)) { + sisfb_nocrt2rate = 1; + } else if(!strnicmp(this_opt, "scalelcd:", 9)) { + unsigned long temp = 2; + temp = simple_strtoul(this_opt + 9, NULL, 0); + if((temp == 0) || (temp == 1)) { + sisfb_scalelcd = temp ^ 1; + } + } else if(!strnicmp(this_opt, "tvxposoffset:", 13)) { + int temp = 0; + temp = (int)simple_strtol(this_opt + 13, NULL, 0); + if((temp >= -32) && (temp <= 32)) { + sisfb_tvxposoffset = temp; + } + } else if(!strnicmp(this_opt, "tvyposoffset:", 13)) { + int temp = 0; + temp = (int)simple_strtol(this_opt + 13, NULL, 0); + if((temp >= -32) && (temp <= 32)) { + sisfb_tvyposoffset = temp; + } + } else if(!strnicmp(this_opt, "specialtiming:", 14)) { + sisfb_search_specialtiming(this_opt + 14); + } else if(!strnicmp(this_opt, "lvdshl:", 7)) { + int temp = 4; + temp = simple_strtoul(this_opt + 7, NULL, 0); + if((temp >= 0) && (temp <= 3)) { + sisfb_lvdshl = temp; + } + } else if(this_opt[0] >= '0' && this_opt[0] <= '9') { + sisfb_search_mode(this_opt, true); +#if !defined(__i386__) && !defined(__x86_64__) + } else if(!strnicmp(this_opt, "resetcard", 9)) { + sisfb_resetcard = 1; + } else if(!strnicmp(this_opt, "videoram:", 9)) { + sisfb_videoram = simple_strtoul(this_opt + 9, NULL, 0); +#endif + } else { + printk(KERN_INFO "sisfb: Invalid option %s\n", this_opt); + } + + } + + return 0; +} +#endif + +static int sisfb_check_rom(void __iomem *rom_base, + struct sis_video_info *ivideo) +{ + void __iomem *rom; + int romptr; + + if((readb(rom_base) != 0x55) || (readb(rom_base + 1) != 0xaa)) + return 0; + + romptr = (readb(rom_base + 0x18) | (readb(rom_base + 0x19) << 8)); + if(romptr > (0x10000 - 8)) + return 0; + + rom = rom_base + romptr; + + if((readb(rom) != 'P') || (readb(rom + 1) != 'C') || + (readb(rom + 2) != 'I') || (readb(rom + 3) != 'R')) + return 0; + + if((readb(rom + 4) | (readb(rom + 5) << 8)) != ivideo->chip_vendor) + return 0; + + if((readb(rom + 6) | (readb(rom + 7) << 8)) != ivideo->chip_id) + return 0; + + return 1; +} + +static unsigned char *sisfb_find_rom(struct pci_dev *pdev) +{ + struct sis_video_info *ivideo = pci_get_drvdata(pdev); + void __iomem *rom_base; + unsigned char *myrombase = NULL; + size_t romsize; + + /* First, try the official pci ROM functions (except + * on integrated chipsets which have no ROM). + */ + + if(!ivideo->nbridge) { + + if((rom_base = pci_map_rom(pdev, &romsize))) { + + if(sisfb_check_rom(rom_base, ivideo)) { + + if((myrombase = vmalloc(65536))) { + memcpy_fromio(myrombase, rom_base, + (romsize > 65536) ? 65536 : romsize); + } + } + pci_unmap_rom(pdev, rom_base); + } + } + + if(myrombase) return myrombase; + + /* Otherwise do it the conventional way. */ + +#if defined(__i386__) || defined(__x86_64__) + { + u32 temp; + + for (temp = 0x000c0000; temp < 0x000f0000; temp += 0x00001000) { + + rom_base = ioremap(temp, 65536); + if (!rom_base) + continue; + + if (!sisfb_check_rom(rom_base, ivideo)) { + iounmap(rom_base); + continue; + } + + if ((myrombase = vmalloc(65536))) + memcpy_fromio(myrombase, rom_base, 65536); + + iounmap(rom_base); + break; + + } + + } +#endif + + return myrombase; +} + +static void sisfb_post_map_vram(struct sis_video_info *ivideo, + unsigned int *mapsize, unsigned int min) +{ + if (*mapsize < (min << 20)) + return; + + ivideo->video_vbase = ioremap(ivideo->video_base, (*mapsize)); + + if(!ivideo->video_vbase) { + printk(KERN_ERR + "sisfb: Unable to map maximum video RAM for size detection\n"); + (*mapsize) >>= 1; + while((!(ivideo->video_vbase = ioremap(ivideo->video_base, (*mapsize))))) { + (*mapsize) >>= 1; + if((*mapsize) < (min << 20)) + break; + } + if(ivideo->video_vbase) { + printk(KERN_ERR + "sisfb: Video RAM size detection limited to %dMB\n", + (int)((*mapsize) >> 20)); + } + } +} + +#ifdef CONFIG_FB_SIS_300 +static int sisfb_post_300_buswidth(struct sis_video_info *ivideo) +{ + void __iomem *FBAddress = ivideo->video_vbase; + unsigned short temp; + unsigned char reg; + int i, j; + + SiS_SetRegAND(SISSR, 0x15, 0xFB); + SiS_SetRegOR(SISSR, 0x15, 0x04); + SiS_SetReg(SISSR, 0x13, 0x00); + SiS_SetReg(SISSR, 0x14, 0xBF); + + for(i = 0; i < 2; i++) { + temp = 0x1234; + for(j = 0; j < 4; j++) { + writew(temp, FBAddress); + if(readw(FBAddress) == temp) + break; + SiS_SetRegOR(SISSR, 0x3c, 0x01); + reg = SiS_GetReg(SISSR, 0x05); + reg = SiS_GetReg(SISSR, 0x05); + SiS_SetRegAND(SISSR, 0x3c, 0xfe); + reg = SiS_GetReg(SISSR, 0x05); + reg = SiS_GetReg(SISSR, 0x05); + temp++; + } + } + + writel(0x01234567L, FBAddress); + writel(0x456789ABL, (FBAddress + 4)); + writel(0x89ABCDEFL, (FBAddress + 8)); + writel(0xCDEF0123L, (FBAddress + 12)); + + reg = SiS_GetReg(SISSR, 0x3b); + if(reg & 0x01) { + if(readl((FBAddress + 12)) == 0xCDEF0123L) + return 4; /* Channel A 128bit */ + } + + if(readl((FBAddress + 4)) == 0x456789ABL) + return 2; /* Channel B 64bit */ + + return 1; /* 32bit */ +} + +static const unsigned short SiS_DRAMType[17][5] = { + {0x0C,0x0A,0x02,0x40,0x39}, + {0x0D,0x0A,0x01,0x40,0x48}, + {0x0C,0x09,0x02,0x20,0x35}, + {0x0D,0x09,0x01,0x20,0x44}, + {0x0C,0x08,0x02,0x10,0x31}, + {0x0D,0x08,0x01,0x10,0x40}, + {0x0C,0x0A,0x01,0x20,0x34}, + {0x0C,0x09,0x01,0x08,0x32}, + {0x0B,0x08,0x02,0x08,0x21}, + {0x0C,0x08,0x01,0x08,0x30}, + {0x0A,0x08,0x02,0x04,0x11}, + {0x0B,0x0A,0x01,0x10,0x28}, + {0x09,0x08,0x02,0x02,0x01}, + {0x0B,0x09,0x01,0x08,0x24}, + {0x0B,0x08,0x01,0x04,0x20}, + {0x0A,0x08,0x01,0x02,0x10}, + {0x09,0x08,0x01,0x01,0x00} +}; + +static int sisfb_post_300_rwtest(struct sis_video_info *ivideo, int iteration, + int buswidth, int PseudoRankCapacity, + int PseudoAdrPinCount, unsigned int mapsize) +{ + void __iomem *FBAddr = ivideo->video_vbase; + unsigned short sr14; + unsigned int k, RankCapacity, PageCapacity, BankNumHigh, BankNumMid; + unsigned int PhysicalAdrOtherPage, PhysicalAdrHigh, PhysicalAdrHalfPage; + + for(k = 0; k < ARRAY_SIZE(SiS_DRAMType); k++) { + + RankCapacity = buswidth * SiS_DRAMType[k][3]; + + if(RankCapacity != PseudoRankCapacity) + continue; + + if((SiS_DRAMType[k][2] + SiS_DRAMType[k][0]) > PseudoAdrPinCount) + continue; + + BankNumHigh = RankCapacity * 16 * iteration - 1; + if(iteration == 3) { /* Rank No */ + BankNumMid = RankCapacity * 16 - 1; + } else { + BankNumMid = RankCapacity * 16 * iteration / 2 - 1; + } + + PageCapacity = (1 << SiS_DRAMType[k][1]) * buswidth * 4; + PhysicalAdrHigh = BankNumHigh; + PhysicalAdrHalfPage = (PageCapacity / 2 + PhysicalAdrHigh) % PageCapacity; + PhysicalAdrOtherPage = PageCapacity * SiS_DRAMType[k][2] + PhysicalAdrHigh; + + SiS_SetRegAND(SISSR, 0x15, 0xFB); /* Test */ + SiS_SetRegOR(SISSR, 0x15, 0x04); /* Test */ + sr14 = (SiS_DRAMType[k][3] * buswidth) - 1; + if(buswidth == 4) sr14 |= 0x80; + else if(buswidth == 2) sr14 |= 0x40; + SiS_SetReg(SISSR, 0x13, SiS_DRAMType[k][4]); + SiS_SetReg(SISSR, 0x14, sr14); + + BankNumHigh <<= 16; + BankNumMid <<= 16; + + if((BankNumHigh + PhysicalAdrHigh >= mapsize) || + (BankNumMid + PhysicalAdrHigh >= mapsize) || + (BankNumHigh + PhysicalAdrHalfPage >= mapsize) || + (BankNumHigh + PhysicalAdrOtherPage >= mapsize)) + continue; + + /* Write data */ + writew(((unsigned short)PhysicalAdrHigh), + (FBAddr + BankNumHigh + PhysicalAdrHigh)); + writew(((unsigned short)BankNumMid), + (FBAddr + BankNumMid + PhysicalAdrHigh)); + writew(((unsigned short)PhysicalAdrHalfPage), + (FBAddr + BankNumHigh + PhysicalAdrHalfPage)); + writew(((unsigned short)PhysicalAdrOtherPage), + (FBAddr + BankNumHigh + PhysicalAdrOtherPage)); + + /* Read data */ + if(readw(FBAddr + BankNumHigh + PhysicalAdrHigh) == PhysicalAdrHigh) + return 1; + } + + return 0; +} + +static void sisfb_post_300_ramsize(struct pci_dev *pdev, unsigned int mapsize) +{ + struct sis_video_info *ivideo = pci_get_drvdata(pdev); + int i, j, buswidth; + int PseudoRankCapacity, PseudoAdrPinCount; + + buswidth = sisfb_post_300_buswidth(ivideo); + + for(i = 6; i >= 0; i--) { + PseudoRankCapacity = 1 << i; + for(j = 4; j >= 1; j--) { + PseudoAdrPinCount = 15 - j; + if((PseudoRankCapacity * j) <= 64) { + if(sisfb_post_300_rwtest(ivideo, + j, + buswidth, + PseudoRankCapacity, + PseudoAdrPinCount, + mapsize)) + return; + } + } + } +} + +static void sisfb_post_sis300(struct pci_dev *pdev) +{ + struct sis_video_info *ivideo = pci_get_drvdata(pdev); + unsigned char *bios = ivideo->SiS_Pr.VirtualRomBase; + u8 reg, v1, v2, v3, v4, v5, v6, v7, v8; + u16 index, rindex, memtype = 0; + unsigned int mapsize; + + if(!ivideo->SiS_Pr.UseROM) + bios = NULL; + + SiS_SetReg(SISSR, 0x05, 0x86); + + if(bios) { + if(bios[0x52] & 0x80) { + memtype = bios[0x52]; + } else { + memtype = SiS_GetReg(SISSR, 0x3a); + } + memtype &= 0x07; + } + + v3 = 0x80; v6 = 0x80; + if(ivideo->revision_id <= 0x13) { + v1 = 0x44; v2 = 0x42; + v4 = 0x44; v5 = 0x42; + } else { + v1 = 0x68; v2 = 0x43; /* Assume 125Mhz MCLK */ + v4 = 0x68; v5 = 0x43; /* Assume 125Mhz ECLK */ + if(bios) { + index = memtype * 5; + rindex = index + 0x54; + v1 = bios[rindex++]; + v2 = bios[rindex++]; + v3 = bios[rindex++]; + rindex = index + 0x7c; + v4 = bios[rindex++]; + v5 = bios[rindex++]; + v6 = bios[rindex++]; + } + } + SiS_SetReg(SISSR, 0x28, v1); + SiS_SetReg(SISSR, 0x29, v2); + SiS_SetReg(SISSR, 0x2a, v3); + SiS_SetReg(SISSR, 0x2e, v4); + SiS_SetReg(SISSR, 0x2f, v5); + SiS_SetReg(SISSR, 0x30, v6); + + v1 = 0x10; + if(bios) + v1 = bios[0xa4]; + SiS_SetReg(SISSR, 0x07, v1); /* DAC speed */ + + SiS_SetReg(SISSR, 0x11, 0x0f); /* DDC, power save */ + + v1 = 0x01; v2 = 0x43; v3 = 0x1e; v4 = 0x2a; + v5 = 0x06; v6 = 0x00; v7 = 0x00; v8 = 0x00; + if(bios) { + memtype += 0xa5; + v1 = bios[memtype]; + v2 = bios[memtype + 8]; + v3 = bios[memtype + 16]; + v4 = bios[memtype + 24]; + v5 = bios[memtype + 32]; + v6 = bios[memtype + 40]; + v7 = bios[memtype + 48]; + v8 = bios[memtype + 56]; + } + if(ivideo->revision_id >= 0x80) + v3 &= 0xfd; + SiS_SetReg(SISSR, 0x15, v1); /* Ram type (assuming 0, BIOS 0xa5 step 8) */ + SiS_SetReg(SISSR, 0x16, v2); + SiS_SetReg(SISSR, 0x17, v3); + SiS_SetReg(SISSR, 0x18, v4); + SiS_SetReg(SISSR, 0x19, v5); + SiS_SetReg(SISSR, 0x1a, v6); + SiS_SetReg(SISSR, 0x1b, v7); + SiS_SetReg(SISSR, 0x1c, v8); /* ---- */ + SiS_SetRegAND(SISSR, 0x15, 0xfb); + SiS_SetRegOR(SISSR, 0x15, 0x04); + if(bios) { + if(bios[0x53] & 0x02) { + SiS_SetRegOR(SISSR, 0x19, 0x20); + } + } + v1 = 0x04; /* DAC pedestal (BIOS 0xe5) */ + if(ivideo->revision_id >= 0x80) + v1 |= 0x01; + SiS_SetReg(SISSR, 0x1f, v1); + SiS_SetReg(SISSR, 0x20, 0xa4); /* linear & relocated io & disable a0000 */ + v1 = 0xf6; v2 = 0x0d; v3 = 0x00; + if(bios) { + v1 = bios[0xe8]; + v2 = bios[0xe9]; + v3 = bios[0xea]; + } + SiS_SetReg(SISSR, 0x23, v1); + SiS_SetReg(SISSR, 0x24, v2); + SiS_SetReg(SISSR, 0x25, v3); + SiS_SetReg(SISSR, 0x21, 0x84); + SiS_SetReg(SISSR, 0x22, 0x00); + SiS_SetReg(SISCR, 0x37, 0x00); + SiS_SetRegOR(SISPART1, 0x24, 0x01); /* unlock crt2 */ + SiS_SetReg(SISPART1, 0x00, 0x00); + v1 = 0x40; v2 = 0x11; + if(bios) { + v1 = bios[0xec]; + v2 = bios[0xeb]; + } + SiS_SetReg(SISPART1, 0x02, v1); + + if(ivideo->revision_id >= 0x80) + v2 &= ~0x01; + + reg = SiS_GetReg(SISPART4, 0x00); + if((reg == 1) || (reg == 2)) { + SiS_SetReg(SISCR, 0x37, 0x02); + SiS_SetReg(SISPART2, 0x00, 0x1c); + v4 = 0x00; v5 = 0x00; v6 = 0x10; + if(ivideo->SiS_Pr.UseROM) { + v4 = bios[0xf5]; + v5 = bios[0xf6]; + v6 = bios[0xf7]; + } + SiS_SetReg(SISPART4, 0x0d, v4); + SiS_SetReg(SISPART4, 0x0e, v5); + SiS_SetReg(SISPART4, 0x10, v6); + SiS_SetReg(SISPART4, 0x0f, 0x3f); + reg = SiS_GetReg(SISPART4, 0x01); + if(reg >= 0xb0) { + reg = SiS_GetReg(SISPART4, 0x23); + reg &= 0x20; + reg <<= 1; + SiS_SetReg(SISPART4, 0x23, reg); + } + } else { + v2 &= ~0x10; + } + SiS_SetReg(SISSR, 0x32, v2); + + SiS_SetRegAND(SISPART1, 0x24, 0xfe); /* Lock CRT2 */ + + reg = SiS_GetReg(SISSR, 0x16); + reg &= 0xc3; + SiS_SetReg(SISCR, 0x35, reg); + SiS_SetReg(SISCR, 0x83, 0x00); +#if !defined(__i386__) && !defined(__x86_64__) + if(sisfb_videoram) { + SiS_SetReg(SISSR, 0x13, 0x28); /* ? */ + reg = ((sisfb_videoram >> 10) - 1) | 0x40; + SiS_SetReg(SISSR, 0x14, reg); + } else { +#endif + /* Need to map max FB size for finding out about RAM size */ + mapsize = ivideo->video_size; + sisfb_post_map_vram(ivideo, &mapsize, 4); + + if(ivideo->video_vbase) { + sisfb_post_300_ramsize(pdev, mapsize); + iounmap(ivideo->video_vbase); + } else { + printk(KERN_DEBUG + "sisfb: Failed to map memory for size detection, assuming 8MB\n"); + SiS_SetReg(SISSR, 0x13, 0x28); /* ? */ + SiS_SetReg(SISSR, 0x14, 0x47); /* 8MB, 64bit default */ + } +#if !defined(__i386__) && !defined(__x86_64__) + } +#endif + if(bios) { + v1 = bios[0xe6]; + v2 = bios[0xe7]; + } else { + reg = SiS_GetReg(SISSR, 0x3a); + if((reg & 0x30) == 0x30) { + v1 = 0x04; /* PCI */ + v2 = 0x92; + } else { + v1 = 0x14; /* AGP */ + v2 = 0xb2; + } + } + SiS_SetReg(SISSR, 0x21, v1); + SiS_SetReg(SISSR, 0x22, v2); + + /* Sense CRT1 */ + sisfb_sense_crt1(ivideo); + + /* Set default mode, don't clear screen */ + ivideo->SiS_Pr.SiS_UseOEM = false; + SiS_SetEnableDstn(&ivideo->SiS_Pr, false); + SiS_SetEnableFstn(&ivideo->SiS_Pr, false); + ivideo->curFSTN = ivideo->curDSTN = 0; + ivideo->SiS_Pr.VideoMemorySize = 8 << 20; + SiSSetMode(&ivideo->SiS_Pr, 0x2e | 0x80); + + SiS_SetReg(SISSR, 0x05, 0x86); + + /* Display off */ + SiS_SetRegOR(SISSR, 0x01, 0x20); + + /* Save mode number in CR34 */ + SiS_SetReg(SISCR, 0x34, 0x2e); + + /* Let everyone know what the current mode is */ + ivideo->modeprechange = 0x2e; +} +#endif + +#ifdef CONFIG_FB_SIS_315 +#if 0 +static void sisfb_post_sis315330(struct pci_dev *pdev) +{ + /* TODO */ +} +#endif + +static inline int sisfb_xgi_is21(struct sis_video_info *ivideo) +{ + return ivideo->chip_real_id == XGI_21; +} + +static void sisfb_post_xgi_delay(struct sis_video_info *ivideo, int delay) +{ + unsigned int i; + u8 reg; + + for(i = 0; i <= (delay * 10 * 36); i++) { + reg = SiS_GetReg(SISSR, 0x05); + reg++; + } +} + +static int sisfb_find_host_bridge(struct sis_video_info *ivideo, + struct pci_dev *mypdev, + unsigned short pcivendor) +{ + struct pci_dev *pdev = NULL; + unsigned short temp; + int ret = 0; + + while((pdev = pci_get_class(PCI_CLASS_BRIDGE_HOST, pdev))) { + temp = pdev->vendor; + if(temp == pcivendor) { + ret = 1; + pci_dev_put(pdev); + break; + } + } + + return ret; +} + +static int sisfb_post_xgi_rwtest(struct sis_video_info *ivideo, int starta, + unsigned int enda, unsigned int mapsize) +{ + unsigned int pos; + int i; + + writel(0, ivideo->video_vbase); + + for(i = starta; i <= enda; i++) { + pos = 1 << i; + if(pos < mapsize) + writel(pos, ivideo->video_vbase + pos); + } + + sisfb_post_xgi_delay(ivideo, 150); + + if(readl(ivideo->video_vbase) != 0) + return 0; + + for(i = starta; i <= enda; i++) { + pos = 1 << i; + if(pos < mapsize) { + if(readl(ivideo->video_vbase + pos) != pos) + return 0; + } else + return 0; + } + + return 1; +} + +static int sisfb_post_xgi_ramsize(struct sis_video_info *ivideo) +{ + unsigned int buswidth, ranksize, channelab, mapsize; + int i, j, k, l, status; + u8 reg, sr14; + static const u8 dramsr13[12 * 5] = { + 0x02, 0x0e, 0x0b, 0x80, 0x5d, + 0x02, 0x0e, 0x0a, 0x40, 0x59, + 0x02, 0x0d, 0x0b, 0x40, 0x4d, + 0x02, 0x0e, 0x09, 0x20, 0x55, + 0x02, 0x0d, 0x0a, 0x20, 0x49, + 0x02, 0x0c, 0x0b, 0x20, 0x3d, + 0x02, 0x0e, 0x08, 0x10, 0x51, + 0x02, 0x0d, 0x09, 0x10, 0x45, + 0x02, 0x0c, 0x0a, 0x10, 0x39, + 0x02, 0x0d, 0x08, 0x08, 0x41, + 0x02, 0x0c, 0x09, 0x08, 0x35, + 0x02, 0x0c, 0x08, 0x04, 0x31 + }; + static const u8 dramsr13_4[4 * 5] = { + 0x02, 0x0d, 0x09, 0x40, 0x45, + 0x02, 0x0c, 0x09, 0x20, 0x35, + 0x02, 0x0c, 0x08, 0x10, 0x31, + 0x02, 0x0b, 0x08, 0x08, 0x21 + }; + + /* Enable linear mode, disable 0xa0000 address decoding */ + /* We disable a0000 address decoding, because + * - if running on x86, if the card is disabled, it means + * that another card is in the system. We don't want + * to interphere with that primary card's textmode. + * - if running on non-x86, there usually is no VGA window + * at a0000. + */ + SiS_SetRegOR(SISSR, 0x20, (0x80 | 0x04)); + + /* Need to map max FB size for finding out about RAM size */ + mapsize = ivideo->video_size; + sisfb_post_map_vram(ivideo, &mapsize, 32); + + if(!ivideo->video_vbase) { + printk(KERN_ERR "sisfb: Unable to detect RAM size. Setting default.\n"); + SiS_SetReg(SISSR, 0x13, 0x35); + SiS_SetReg(SISSR, 0x14, 0x41); + /* TODO */ + return -ENOMEM; + } + + /* Non-interleaving */ + SiS_SetReg(SISSR, 0x15, 0x00); + /* No tiling */ + SiS_SetReg(SISSR, 0x1c, 0x00); + + if(ivideo->chip == XGI_20) { + + channelab = 1; + reg = SiS_GetReg(SISCR, 0x97); + if(!(reg & 0x01)) { /* Single 32/16 */ + buswidth = 32; + SiS_SetReg(SISSR, 0x13, 0xb1); + SiS_SetReg(SISSR, 0x14, 0x52); + sisfb_post_xgi_delay(ivideo, 1); + sr14 = 0x02; + if(sisfb_post_xgi_rwtest(ivideo, 23, 24, mapsize)) + goto bail_out; + + SiS_SetReg(SISSR, 0x13, 0x31); + SiS_SetReg(SISSR, 0x14, 0x42); + sisfb_post_xgi_delay(ivideo, 1); + if(sisfb_post_xgi_rwtest(ivideo, 23, 23, mapsize)) + goto bail_out; + + buswidth = 16; + SiS_SetReg(SISSR, 0x13, 0xb1); + SiS_SetReg(SISSR, 0x14, 0x41); + sisfb_post_xgi_delay(ivideo, 1); + sr14 = 0x01; + if(sisfb_post_xgi_rwtest(ivideo, 22, 23, mapsize)) + goto bail_out; + else + SiS_SetReg(SISSR, 0x13, 0x31); + } else { /* Dual 16/8 */ + buswidth = 16; + SiS_SetReg(SISSR, 0x13, 0xb1); + SiS_SetReg(SISSR, 0x14, 0x41); + sisfb_post_xgi_delay(ivideo, 1); + sr14 = 0x01; + if(sisfb_post_xgi_rwtest(ivideo, 22, 23, mapsize)) + goto bail_out; + + SiS_SetReg(SISSR, 0x13, 0x31); + SiS_SetReg(SISSR, 0x14, 0x31); + sisfb_post_xgi_delay(ivideo, 1); + if(sisfb_post_xgi_rwtest(ivideo, 22, 22, mapsize)) + goto bail_out; + + buswidth = 8; + SiS_SetReg(SISSR, 0x13, 0xb1); + SiS_SetReg(SISSR, 0x14, 0x30); + sisfb_post_xgi_delay(ivideo, 1); + sr14 = 0x00; + if(sisfb_post_xgi_rwtest(ivideo, 21, 22, mapsize)) + goto bail_out; + else + SiS_SetReg(SISSR, 0x13, 0x31); + } + + } else { /* XGI_40 */ + + reg = SiS_GetReg(SISCR, 0x97); + if(!(reg & 0x10)) { + reg = SiS_GetReg(SISSR, 0x39); + reg >>= 1; + } + + if(reg & 0x01) { /* DDRII */ + buswidth = 32; + if(ivideo->revision_id == 2) { + channelab = 2; + SiS_SetReg(SISSR, 0x13, 0xa1); + SiS_SetReg(SISSR, 0x14, 0x44); + sr14 = 0x04; + sisfb_post_xgi_delay(ivideo, 1); + if(sisfb_post_xgi_rwtest(ivideo, 23, 24, mapsize)) + goto bail_out; + + SiS_SetReg(SISSR, 0x13, 0x21); + SiS_SetReg(SISSR, 0x14, 0x34); + if(sisfb_post_xgi_rwtest(ivideo, 22, 23, mapsize)) + goto bail_out; + + channelab = 1; + SiS_SetReg(SISSR, 0x13, 0xa1); + SiS_SetReg(SISSR, 0x14, 0x40); + sr14 = 0x00; + if(sisfb_post_xgi_rwtest(ivideo, 22, 23, mapsize)) + goto bail_out; + + SiS_SetReg(SISSR, 0x13, 0x21); + SiS_SetReg(SISSR, 0x14, 0x30); + } else { + channelab = 3; + SiS_SetReg(SISSR, 0x13, 0xa1); + SiS_SetReg(SISSR, 0x14, 0x4c); + sr14 = 0x0c; + sisfb_post_xgi_delay(ivideo, 1); + if(sisfb_post_xgi_rwtest(ivideo, 23, 25, mapsize)) + goto bail_out; + + channelab = 2; + SiS_SetReg(SISSR, 0x14, 0x48); + sisfb_post_xgi_delay(ivideo, 1); + sr14 = 0x08; + if(sisfb_post_xgi_rwtest(ivideo, 23, 24, mapsize)) + goto bail_out; + + SiS_SetReg(SISSR, 0x13, 0x21); + SiS_SetReg(SISSR, 0x14, 0x3c); + sr14 = 0x0c; + + if(sisfb_post_xgi_rwtest(ivideo, 23, 24, mapsize)) { + channelab = 3; + } else { + channelab = 2; + SiS_SetReg(SISSR, 0x14, 0x38); + sr14 = 0x08; + } + } + sisfb_post_xgi_delay(ivideo, 1); + + } else { /* DDR */ + + buswidth = 64; + if(ivideo->revision_id == 2) { + channelab = 1; + SiS_SetReg(SISSR, 0x13, 0xa1); + SiS_SetReg(SISSR, 0x14, 0x52); + sisfb_post_xgi_delay(ivideo, 1); + sr14 = 0x02; + if(sisfb_post_xgi_rwtest(ivideo, 23, 24, mapsize)) + goto bail_out; + + SiS_SetReg(SISSR, 0x13, 0x21); + SiS_SetReg(SISSR, 0x14, 0x42); + } else { + channelab = 2; + SiS_SetReg(SISSR, 0x13, 0xa1); + SiS_SetReg(SISSR, 0x14, 0x5a); + sisfb_post_xgi_delay(ivideo, 1); + sr14 = 0x0a; + if(sisfb_post_xgi_rwtest(ivideo, 24, 25, mapsize)) + goto bail_out; + + SiS_SetReg(SISSR, 0x13, 0x21); + SiS_SetReg(SISSR, 0x14, 0x4a); + } + sisfb_post_xgi_delay(ivideo, 1); + + } + } + +bail_out: + SiS_SetRegANDOR(SISSR, 0x14, 0xf0, sr14); + sisfb_post_xgi_delay(ivideo, 1); + + j = (ivideo->chip == XGI_20) ? 5 : 9; + k = (ivideo->chip == XGI_20) ? 12 : 4; + status = -EIO; + + for(i = 0; i < k; i++) { + + reg = (ivideo->chip == XGI_20) ? + dramsr13[(i * 5) + 4] : dramsr13_4[(i * 5) + 4]; + SiS_SetRegANDOR(SISSR, 0x13, 0x80, reg); + sisfb_post_xgi_delay(ivideo, 50); + + ranksize = (ivideo->chip == XGI_20) ? + dramsr13[(i * 5) + 3] : dramsr13_4[(i * 5) + 3]; + + reg = SiS_GetReg(SISSR, 0x13); + if(reg & 0x80) ranksize <<= 1; + + if(ivideo->chip == XGI_20) { + if(buswidth == 16) ranksize <<= 1; + else if(buswidth == 32) ranksize <<= 2; + } else { + if(buswidth == 64) ranksize <<= 1; + } + + reg = 0; + l = channelab; + if(l == 3) l = 4; + if((ranksize * l) <= 256) { + while((ranksize >>= 1)) reg += 0x10; + } + + if(!reg) continue; + + SiS_SetRegANDOR(SISSR, 0x14, 0x0f, (reg & 0xf0)); + sisfb_post_xgi_delay(ivideo, 1); + + if (sisfb_post_xgi_rwtest(ivideo, j, ((reg >> 4) + channelab - 2 + 20), mapsize)) { + status = 0; + break; + } + } + + iounmap(ivideo->video_vbase); + + return status; +} + +static void sisfb_post_xgi_setclocks(struct sis_video_info *ivideo, u8 regb) +{ + u8 v1, v2, v3; + int index; + static const u8 cs90[8 * 3] = { + 0x16, 0x01, 0x01, + 0x3e, 0x03, 0x01, + 0x7c, 0x08, 0x01, + 0x79, 0x06, 0x01, + 0x29, 0x01, 0x81, + 0x5c, 0x23, 0x01, + 0x5c, 0x23, 0x01, + 0x5c, 0x23, 0x01 + }; + static const u8 csb8[8 * 3] = { + 0x5c, 0x23, 0x01, + 0x29, 0x01, 0x01, + 0x7c, 0x08, 0x01, + 0x79, 0x06, 0x01, + 0x29, 0x01, 0x81, + 0x5c, 0x23, 0x01, + 0x5c, 0x23, 0x01, + 0x5c, 0x23, 0x01 + }; + + regb = 0; /* ! */ + + index = regb * 3; + v1 = cs90[index]; v2 = cs90[index + 1]; v3 = cs90[index + 2]; + if(ivideo->haveXGIROM) { + v1 = ivideo->bios_abase[0x90 + index]; + v2 = ivideo->bios_abase[0x90 + index + 1]; + v3 = ivideo->bios_abase[0x90 + index + 2]; + } + SiS_SetReg(SISSR, 0x28, v1); + SiS_SetReg(SISSR, 0x29, v2); + SiS_SetReg(SISSR, 0x2a, v3); + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); + index = regb * 3; + v1 = csb8[index]; v2 = csb8[index + 1]; v3 = csb8[index + 2]; + if(ivideo->haveXGIROM) { + v1 = ivideo->bios_abase[0xb8 + index]; + v2 = ivideo->bios_abase[0xb8 + index + 1]; + v3 = ivideo->bios_abase[0xb8 + index + 2]; + } + SiS_SetReg(SISSR, 0x2e, v1); + SiS_SetReg(SISSR, 0x2f, v2); + SiS_SetReg(SISSR, 0x30, v3); + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); +} + +static void sisfb_post_xgi_ddr2_mrs_default(struct sis_video_info *ivideo, + u8 regb) +{ + unsigned char *bios = ivideo->bios_abase; + u8 v1; + + SiS_SetReg(SISSR, 0x28, 0x64); + SiS_SetReg(SISSR, 0x29, 0x63); + sisfb_post_xgi_delay(ivideo, 15); + SiS_SetReg(SISSR, 0x18, 0x00); + SiS_SetReg(SISSR, 0x19, 0x20); + SiS_SetReg(SISSR, 0x16, 0x00); + SiS_SetReg(SISSR, 0x16, 0x80); + SiS_SetReg(SISSR, 0x18, 0xc5); + SiS_SetReg(SISSR, 0x19, 0x23); + SiS_SetReg(SISSR, 0x16, 0x00); + SiS_SetReg(SISSR, 0x16, 0x80); + sisfb_post_xgi_delay(ivideo, 1); + SiS_SetReg(SISCR, 0x97, 0x11); + sisfb_post_xgi_setclocks(ivideo, regb); + sisfb_post_xgi_delay(ivideo, 0x46); + SiS_SetReg(SISSR, 0x18, 0xc5); + SiS_SetReg(SISSR, 0x19, 0x23); + SiS_SetReg(SISSR, 0x16, 0x00); + SiS_SetReg(SISSR, 0x16, 0x80); + sisfb_post_xgi_delay(ivideo, 1); + SiS_SetReg(SISSR, 0x1b, 0x04); + sisfb_post_xgi_delay(ivideo, 1); + SiS_SetReg(SISSR, 0x1b, 0x00); + sisfb_post_xgi_delay(ivideo, 1); + v1 = 0x31; + if (ivideo->haveXGIROM) { + v1 = bios[0xf0]; + } + SiS_SetReg(SISSR, 0x18, v1); + SiS_SetReg(SISSR, 0x19, 0x06); + SiS_SetReg(SISSR, 0x16, 0x04); + SiS_SetReg(SISSR, 0x16, 0x84); + sisfb_post_xgi_delay(ivideo, 1); +} + +static void sisfb_post_xgi_ddr2_mrs_xg21(struct sis_video_info *ivideo) +{ + sisfb_post_xgi_setclocks(ivideo, 1); + + SiS_SetReg(SISCR, 0x97, 0x11); + sisfb_post_xgi_delay(ivideo, 0x46); + + SiS_SetReg(SISSR, 0x18, 0x00); /* EMRS2 */ + SiS_SetReg(SISSR, 0x19, 0x80); + SiS_SetReg(SISSR, 0x16, 0x05); + SiS_SetReg(SISSR, 0x16, 0x85); + + SiS_SetReg(SISSR, 0x18, 0x00); /* EMRS3 */ + SiS_SetReg(SISSR, 0x19, 0xc0); + SiS_SetReg(SISSR, 0x16, 0x05); + SiS_SetReg(SISSR, 0x16, 0x85); + + SiS_SetReg(SISSR, 0x18, 0x00); /* EMRS1 */ + SiS_SetReg(SISSR, 0x19, 0x40); + SiS_SetReg(SISSR, 0x16, 0x05); + SiS_SetReg(SISSR, 0x16, 0x85); + + SiS_SetReg(SISSR, 0x18, 0x42); /* MRS1 */ + SiS_SetReg(SISSR, 0x19, 0x02); + SiS_SetReg(SISSR, 0x16, 0x05); + SiS_SetReg(SISSR, 0x16, 0x85); + sisfb_post_xgi_delay(ivideo, 1); + + SiS_SetReg(SISSR, 0x1b, 0x04); + sisfb_post_xgi_delay(ivideo, 1); + + SiS_SetReg(SISSR, 0x1b, 0x00); + sisfb_post_xgi_delay(ivideo, 1); + + SiS_SetReg(SISSR, 0x18, 0x42); /* MRS1 */ + SiS_SetReg(SISSR, 0x19, 0x00); + SiS_SetReg(SISSR, 0x16, 0x05); + SiS_SetReg(SISSR, 0x16, 0x85); + sisfb_post_xgi_delay(ivideo, 1); +} + +static void sisfb_post_xgi_ddr2(struct sis_video_info *ivideo, u8 regb) +{ + unsigned char *bios = ivideo->bios_abase; + static const u8 cs158[8] = { + 0x88, 0xaa, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs160[8] = { + 0x44, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs168[8] = { + 0x48, 0x78, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + u8 reg; + u8 v1; + u8 v2; + u8 v3; + + SiS_SetReg(SISCR, 0xb0, 0x80); /* DDR2 dual frequency mode */ + SiS_SetReg(SISCR, 0x82, 0x77); + SiS_SetReg(SISCR, 0x86, 0x00); + reg = SiS_GetReg(SISCR, 0x86); + SiS_SetReg(SISCR, 0x86, 0x88); + reg = SiS_GetReg(SISCR, 0x86); + v1 = cs168[regb]; v2 = cs160[regb]; v3 = cs158[regb]; + if (ivideo->haveXGIROM) { + v1 = bios[regb + 0x168]; + v2 = bios[regb + 0x160]; + v3 = bios[regb + 0x158]; + } + SiS_SetReg(SISCR, 0x86, v1); + SiS_SetReg(SISCR, 0x82, 0x77); + SiS_SetReg(SISCR, 0x85, 0x00); + reg = SiS_GetReg(SISCR, 0x85); + SiS_SetReg(SISCR, 0x85, 0x88); + reg = SiS_GetReg(SISCR, 0x85); + SiS_SetReg(SISCR, 0x85, v2); + SiS_SetReg(SISCR, 0x82, v3); + SiS_SetReg(SISCR, 0x98, 0x01); + SiS_SetReg(SISCR, 0x9a, 0x02); + if (sisfb_xgi_is21(ivideo)) + sisfb_post_xgi_ddr2_mrs_xg21(ivideo); + else + sisfb_post_xgi_ddr2_mrs_default(ivideo, regb); +} + +static u8 sisfb_post_xgi_ramtype(struct sis_video_info *ivideo) +{ + unsigned char *bios = ivideo->bios_abase; + u8 ramtype; + u8 reg; + u8 v1; + + ramtype = 0x00; v1 = 0x10; + if (ivideo->haveXGIROM) { + ramtype = bios[0x62]; + v1 = bios[0x1d2]; + } + if (!(ramtype & 0x80)) { + if (sisfb_xgi_is21(ivideo)) { + SiS_SetRegAND(SISCR, 0xb4, 0xfd); /* GPIO control */ + SiS_SetRegOR(SISCR, 0x4a, 0x80); /* GPIOH EN */ + reg = SiS_GetReg(SISCR, 0x48); + SiS_SetRegOR(SISCR, 0xb4, 0x02); + ramtype = reg & 0x01; /* GPIOH */ + } else if (ivideo->chip == XGI_20) { + SiS_SetReg(SISCR, 0x97, v1); + reg = SiS_GetReg(SISCR, 0x97); + if (reg & 0x10) { + ramtype = (reg & 0x01) << 1; + } + } else { + reg = SiS_GetReg(SISSR, 0x39); + ramtype = reg & 0x02; + if (!(ramtype)) { + reg = SiS_GetReg(SISSR, 0x3a); + ramtype = (reg >> 1) & 0x01; + } + } + } + ramtype &= 0x07; + + return ramtype; +} + +static int sisfb_post_xgi(struct pci_dev *pdev) +{ + struct sis_video_info *ivideo = pci_get_drvdata(pdev); + unsigned char *bios = ivideo->bios_abase; + struct pci_dev *mypdev = NULL; + const u8 *ptr, *ptr2; + u8 v1, v2, v3, v4, v5, reg, ramtype; + u32 rega, regb, regd; + int i, j, k, index; + static const u8 cs78[3] = { 0xf6, 0x0d, 0x00 }; + static const u8 cs76[2] = { 0xa3, 0xfb }; + static const u8 cs7b[3] = { 0xc0, 0x11, 0x00 }; + static const u8 cs158[8] = { + 0x88, 0xaa, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs160[8] = { + 0x44, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs168[8] = { + 0x48, 0x78, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs128[3 * 8] = { + 0x90, 0x28, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs148[2 * 8] = { + 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs31a[8 * 4] = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs33a[8 * 4] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs45a[8 * 2] = { + 0x00, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs170[7 * 8] = { + 0x54, 0x32, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, 0x43, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x05, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x34, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x11, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs1a8[3 * 8] = { + 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 cs100[2 * 8] = { + 0xc4, 0x04, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc4, 0x04, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + /* VGA enable */ + reg = SiS_GetRegByte(SISVGAENABLE) | 0x01; + SiS_SetRegByte(SISVGAENABLE, reg); + + /* Misc */ + reg = SiS_GetRegByte(SISMISCR) | 0x01; + SiS_SetRegByte(SISMISCW, reg); + + /* Unlock SR */ + SiS_SetReg(SISSR, 0x05, 0x86); + reg = SiS_GetReg(SISSR, 0x05); + if(reg != 0xa1) + return 0; + + /* Clear some regs */ + for(i = 0; i < 0x22; i++) { + if(0x06 + i == 0x20) continue; + SiS_SetReg(SISSR, 0x06 + i, 0x00); + } + for(i = 0; i < 0x0b; i++) { + SiS_SetReg(SISSR, 0x31 + i, 0x00); + } + for(i = 0; i < 0x10; i++) { + SiS_SetReg(SISCR, 0x30 + i, 0x00); + } + + ptr = cs78; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x78]; + } + for(i = 0; i < 3; i++) { + SiS_SetReg(SISSR, 0x23 + i, ptr[i]); + } + + ptr = cs76; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x76]; + } + for(i = 0; i < 2; i++) { + SiS_SetReg(SISSR, 0x21 + i, ptr[i]); + } + + v1 = 0x18; v2 = 0x00; + if(ivideo->haveXGIROM) { + v1 = bios[0x74]; + v2 = bios[0x75]; + } + SiS_SetReg(SISSR, 0x07, v1); + SiS_SetReg(SISSR, 0x11, 0x0f); + SiS_SetReg(SISSR, 0x1f, v2); + /* PCI linear mode, RelIO enabled, A0000 decoding disabled */ + SiS_SetReg(SISSR, 0x20, 0x80 | 0x20 | 0x04); + SiS_SetReg(SISSR, 0x27, 0x74); + + ptr = cs7b; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x7b]; + } + for(i = 0; i < 3; i++) { + SiS_SetReg(SISSR, 0x31 + i, ptr[i]); + } + + if(ivideo->chip == XGI_40) { + if(ivideo->revision_id == 2) { + SiS_SetRegANDOR(SISSR, 0x3b, 0x3f, 0xc0); + } + SiS_SetReg(SISCR, 0x7d, 0xfe); + SiS_SetReg(SISCR, 0x7e, 0x0f); + } + if(ivideo->revision_id == 0) { /* 40 *and* 20? */ + SiS_SetRegAND(SISCR, 0x58, 0xd7); + reg = SiS_GetReg(SISCR, 0xcb); + if(reg & 0x20) { + SiS_SetRegANDOR(SISCR, 0x58, 0xd7, (reg & 0x10) ? 0x08 : 0x20); /* =0x28 Z7 ? */ + } + } + + reg = (ivideo->chip == XGI_40) ? 0x20 : 0x00; + SiS_SetRegANDOR(SISCR, 0x38, 0x1f, reg); + + if(ivideo->chip == XGI_20) { + SiS_SetReg(SISSR, 0x36, 0x70); + } else { + SiS_SetReg(SISVID, 0x00, 0x86); + SiS_SetReg(SISVID, 0x32, 0x00); + SiS_SetReg(SISVID, 0x30, 0x00); + SiS_SetReg(SISVID, 0x32, 0x01); + SiS_SetReg(SISVID, 0x30, 0x00); + SiS_SetRegAND(SISVID, 0x2f, 0xdf); + SiS_SetRegAND(SISCAP, 0x00, 0x3f); + + SiS_SetReg(SISPART1, 0x2f, 0x01); + SiS_SetReg(SISPART1, 0x00, 0x00); + SiS_SetReg(SISPART1, 0x02, bios[0x7e]); + SiS_SetReg(SISPART1, 0x2e, 0x08); + SiS_SetRegAND(SISPART1, 0x35, 0x7f); + SiS_SetRegAND(SISPART1, 0x50, 0xfe); + + reg = SiS_GetReg(SISPART4, 0x00); + if(reg == 1 || reg == 2) { + SiS_SetReg(SISPART2, 0x00, 0x1c); + SiS_SetReg(SISPART4, 0x0d, bios[0x7f]); + SiS_SetReg(SISPART4, 0x0e, bios[0x80]); + SiS_SetReg(SISPART4, 0x10, bios[0x81]); + SiS_SetRegAND(SISPART4, 0x0f, 0x3f); + + reg = SiS_GetReg(SISPART4, 0x01); + if((reg & 0xf0) >= 0xb0) { + reg = SiS_GetReg(SISPART4, 0x23); + if(reg & 0x20) reg |= 0x40; + SiS_SetReg(SISPART4, 0x23, reg); + reg = (reg & 0x20) ? 0x02 : 0x00; + SiS_SetRegANDOR(SISPART1, 0x1e, 0xfd, reg); + } + } + + v1 = bios[0x77]; + + reg = SiS_GetReg(SISSR, 0x3b); + if(reg & 0x02) { + reg = SiS_GetReg(SISSR, 0x3a); + v2 = (reg & 0x30) >> 3; + if(!(v2 & 0x04)) v2 ^= 0x02; + reg = SiS_GetReg(SISSR, 0x39); + if(reg & 0x80) v2 |= 0x80; + v2 |= 0x01; + + if((mypdev = pci_get_device(PCI_VENDOR_ID_SI, 0x0730, NULL))) { + pci_dev_put(mypdev); + if(((v2 & 0x06) == 2) || ((v2 & 0x06) == 4)) + v2 &= 0xf9; + v2 |= 0x08; + v1 &= 0xfe; + } else { + mypdev = pci_get_device(PCI_VENDOR_ID_SI, 0x0735, NULL); + if(!mypdev) + mypdev = pci_get_device(PCI_VENDOR_ID_SI, 0x0645, NULL); + if(!mypdev) + mypdev = pci_get_device(PCI_VENDOR_ID_SI, 0x0650, NULL); + if(mypdev) { + pci_read_config_dword(mypdev, 0x94, ®d); + regd &= 0xfffffeff; + pci_write_config_dword(mypdev, 0x94, regd); + v1 &= 0xfe; + pci_dev_put(mypdev); + } else if(sisfb_find_host_bridge(ivideo, pdev, PCI_VENDOR_ID_SI)) { + v1 &= 0xfe; + } else if(sisfb_find_host_bridge(ivideo, pdev, 0x1106) || + sisfb_find_host_bridge(ivideo, pdev, 0x1022) || + sisfb_find_host_bridge(ivideo, pdev, 0x700e) || + sisfb_find_host_bridge(ivideo, pdev, 0x10de)) { + if((v2 & 0x06) == 4) + v2 ^= 0x06; + v2 |= 0x08; + } + } + SiS_SetRegANDOR(SISCR, 0x5f, 0xf0, v2); + } + SiS_SetReg(SISSR, 0x22, v1); + + if(ivideo->revision_id == 2) { + v1 = SiS_GetReg(SISSR, 0x3b); + v2 = SiS_GetReg(SISSR, 0x3a); + regd = bios[0x90 + 3] | (bios[0x90 + 4] << 8); + if( (!(v1 & 0x02)) && (v2 & 0x30) && (regd < 0xcf) ) + SiS_SetRegANDOR(SISCR, 0x5f, 0xf1, 0x01); + + if((mypdev = pci_get_device(0x10de, 0x01e0, NULL))) { + /* TODO: set CR5f &0xf1 | 0x01 for version 6570 + * of nforce 2 ROM + */ + if(0) + SiS_SetRegANDOR(SISCR, 0x5f, 0xf1, 0x01); + pci_dev_put(mypdev); + } + } + + v1 = 0x30; + reg = SiS_GetReg(SISSR, 0x3b); + v2 = SiS_GetReg(SISCR, 0x5f); + if((!(reg & 0x02)) && (v2 & 0x0e)) + v1 |= 0x08; + SiS_SetReg(SISSR, 0x27, v1); + + if(bios[0x64] & 0x01) { + SiS_SetRegANDOR(SISCR, 0x5f, 0xf0, bios[0x64]); + } + + v1 = bios[0x4f7]; + pci_read_config_dword(pdev, 0x50, ®d); + regd = (regd >> 20) & 0x0f; + if(regd == 1) { + v1 &= 0xfc; + SiS_SetRegOR(SISCR, 0x5f, 0x08); + } + SiS_SetReg(SISCR, 0x48, v1); + + SiS_SetRegANDOR(SISCR, 0x47, 0x04, bios[0x4f6] & 0xfb); + SiS_SetRegANDOR(SISCR, 0x49, 0xf0, bios[0x4f8] & 0x0f); + SiS_SetRegANDOR(SISCR, 0x4a, 0x60, bios[0x4f9] & 0x9f); + SiS_SetRegANDOR(SISCR, 0x4b, 0x08, bios[0x4fa] & 0xf7); + SiS_SetRegANDOR(SISCR, 0x4c, 0x80, bios[0x4fb] & 0x7f); + SiS_SetReg(SISCR, 0x70, bios[0x4fc]); + SiS_SetRegANDOR(SISCR, 0x71, 0xf0, bios[0x4fd] & 0x0f); + SiS_SetReg(SISCR, 0x74, 0xd0); + SiS_SetRegANDOR(SISCR, 0x74, 0xcf, bios[0x4fe] & 0x30); + SiS_SetRegANDOR(SISCR, 0x75, 0xe0, bios[0x4ff] & 0x1f); + SiS_SetRegANDOR(SISCR, 0x76, 0xe0, bios[0x500] & 0x1f); + v1 = bios[0x501]; + if((mypdev = pci_get_device(0x8086, 0x2530, NULL))) { + v1 = 0xf0; + pci_dev_put(mypdev); + } + SiS_SetReg(SISCR, 0x77, v1); + } + + /* RAM type: + * + * 0 == DDR1, 1 == DDR2, 2..7 == reserved? + * + * The code seems to written so that regb should equal ramtype, + * however, so far it has been hardcoded to 0. Enable other values only + * on XGI Z9, as it passes the POST, and add a warning for others. + */ + ramtype = sisfb_post_xgi_ramtype(ivideo); + if (!sisfb_xgi_is21(ivideo) && ramtype) { + dev_warn(&pdev->dev, + "RAM type something else than expected: %d\n", + ramtype); + regb = 0; + } else { + regb = ramtype; + } + + v1 = 0xff; + if(ivideo->haveXGIROM) { + v1 = bios[0x140 + regb]; + } + SiS_SetReg(SISCR, 0x6d, v1); + + ptr = cs128; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x128]; + } + for(i = 0, j = 0; i < 3; i++, j += 8) { + SiS_SetReg(SISCR, 0x68 + i, ptr[j + regb]); + } + + ptr = cs31a; + ptr2 = cs33a; + if(ivideo->haveXGIROM) { + index = (ivideo->chip == XGI_20) ? 0x31a : 0x3a6; + ptr = (const u8 *)&bios[index]; + ptr2 = (const u8 *)&bios[index + 0x20]; + } + for(i = 0; i < 2; i++) { + if(i == 0) { + regd = le32_to_cpu(((u32 *)ptr)[regb]); + rega = 0x6b; + } else { + regd = le32_to_cpu(((u32 *)ptr2)[regb]); + rega = 0x6e; + } + reg = 0x00; + for(j = 0; j < 16; j++) { + reg &= 0xf3; + if(regd & 0x01) reg |= 0x04; + if(regd & 0x02) reg |= 0x08; + regd >>= 2; + SiS_SetReg(SISCR, rega, reg); + reg = SiS_GetReg(SISCR, rega); + reg = SiS_GetReg(SISCR, rega); + reg += 0x10; + } + } + + SiS_SetRegAND(SISCR, 0x6e, 0xfc); + + ptr = NULL; + if(ivideo->haveXGIROM) { + index = (ivideo->chip == XGI_20) ? 0x35a : 0x3e6; + ptr = (const u8 *)&bios[index]; + } + for(i = 0; i < 4; i++) { + SiS_SetRegANDOR(SISCR, 0x6e, 0xfc, i); + reg = 0x00; + for(j = 0; j < 2; j++) { + regd = 0; + if(ptr) { + regd = le32_to_cpu(((u32 *)ptr)[regb * 8]); + ptr += 4; + } + /* reg = 0x00; */ + for(k = 0; k < 16; k++) { + reg &= 0xfc; + if(regd & 0x01) reg |= 0x01; + if(regd & 0x02) reg |= 0x02; + regd >>= 2; + SiS_SetReg(SISCR, 0x6f, reg); + reg = SiS_GetReg(SISCR, 0x6f); + reg = SiS_GetReg(SISCR, 0x6f); + reg += 0x08; + } + } + } + + ptr = cs148; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x148]; + } + for(i = 0, j = 0; i < 2; i++, j += 8) { + SiS_SetReg(SISCR, 0x80 + i, ptr[j + regb]); + } + + SiS_SetRegAND(SISCR, 0x89, 0x8f); + + ptr = cs45a; + if(ivideo->haveXGIROM) { + index = (ivideo->chip == XGI_20) ? 0x45a : 0x4e6; + ptr = (const u8 *)&bios[index]; + } + regd = le16_to_cpu(((const u16 *)ptr)[regb]); + reg = 0x80; + for(i = 0; i < 5; i++) { + reg &= 0xfc; + if(regd & 0x01) reg |= 0x01; + if(regd & 0x02) reg |= 0x02; + regd >>= 2; + SiS_SetReg(SISCR, 0x89, reg); + reg = SiS_GetReg(SISCR, 0x89); + reg = SiS_GetReg(SISCR, 0x89); + reg += 0x10; + } + + v1 = 0xb5; v2 = 0x20; v3 = 0xf0; v4 = 0x13; + if(ivideo->haveXGIROM) { + v1 = bios[0x118 + regb]; + v2 = bios[0xf8 + regb]; + v3 = bios[0x120 + regb]; + v4 = bios[0x1ca]; + } + SiS_SetReg(SISCR, 0x45, v1 & 0x0f); + SiS_SetReg(SISCR, 0x99, (v1 >> 4) & 0x07); + SiS_SetRegOR(SISCR, 0x40, v1 & 0x80); + SiS_SetReg(SISCR, 0x41, v2); + + ptr = cs170; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x170]; + } + for(i = 0, j = 0; i < 7; i++, j += 8) { + SiS_SetReg(SISCR, 0x90 + i, ptr[j + regb]); + } + + SiS_SetReg(SISCR, 0x59, v3); + + ptr = cs1a8; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x1a8]; + } + for(i = 0, j = 0; i < 3; i++, j += 8) { + SiS_SetReg(SISCR, 0xc3 + i, ptr[j + regb]); + } + + ptr = cs100; + if(ivideo->haveXGIROM) { + ptr = (const u8 *)&bios[0x100]; + } + for(i = 0, j = 0; i < 2; i++, j += 8) { + SiS_SetReg(SISCR, 0x8a + i, ptr[j + regb]); + } + + SiS_SetReg(SISCR, 0xcf, v4); + + SiS_SetReg(SISCR, 0x83, 0x09); + SiS_SetReg(SISCR, 0x87, 0x00); + + if(ivideo->chip == XGI_40) { + if( (ivideo->revision_id == 1) || + (ivideo->revision_id == 2) ) { + SiS_SetReg(SISCR, 0x8c, 0x87); + } + } + + if (regb == 1) + SiS_SetReg(SISSR, 0x17, 0x80); /* DDR2 */ + else + SiS_SetReg(SISSR, 0x17, 0x00); /* DDR1 */ + SiS_SetReg(SISSR, 0x1a, 0x87); + + if(ivideo->chip == XGI_20) { + SiS_SetReg(SISSR, 0x15, 0x00); + SiS_SetReg(SISSR, 0x1c, 0x00); + } + + switch(ramtype) { + case 0: + sisfb_post_xgi_setclocks(ivideo, regb); + if((ivideo->chip == XGI_20) || + (ivideo->revision_id == 1) || + (ivideo->revision_id == 2)) { + v1 = cs158[regb]; v2 = cs160[regb]; v3 = cs168[regb]; + if(ivideo->haveXGIROM) { + v1 = bios[regb + 0x158]; + v2 = bios[regb + 0x160]; + v3 = bios[regb + 0x168]; + } + SiS_SetReg(SISCR, 0x82, v1); + SiS_SetReg(SISCR, 0x85, v2); + SiS_SetReg(SISCR, 0x86, v3); + } else { + SiS_SetReg(SISCR, 0x82, 0x88); + SiS_SetReg(SISCR, 0x86, 0x00); + reg = SiS_GetReg(SISCR, 0x86); + SiS_SetReg(SISCR, 0x86, 0x88); + reg = SiS_GetReg(SISCR, 0x86); + SiS_SetReg(SISCR, 0x86, bios[regb + 0x168]); + SiS_SetReg(SISCR, 0x82, 0x77); + SiS_SetReg(SISCR, 0x85, 0x00); + reg = SiS_GetReg(SISCR, 0x85); + SiS_SetReg(SISCR, 0x85, 0x88); + reg = SiS_GetReg(SISCR, 0x85); + SiS_SetReg(SISCR, 0x85, bios[regb + 0x160]); + SiS_SetReg(SISCR, 0x82, bios[regb + 0x158]); + } + if(ivideo->chip == XGI_40) { + SiS_SetReg(SISCR, 0x97, 0x00); + } + SiS_SetReg(SISCR, 0x98, 0x01); + SiS_SetReg(SISCR, 0x9a, 0x02); + + SiS_SetReg(SISSR, 0x18, 0x01); + if((ivideo->chip == XGI_20) || + (ivideo->revision_id == 2)) { + SiS_SetReg(SISSR, 0x19, 0x40); + } else { + SiS_SetReg(SISSR, 0x19, 0x20); + } + SiS_SetReg(SISSR, 0x16, 0x00); + SiS_SetReg(SISSR, 0x16, 0x80); + if((ivideo->chip == XGI_20) || (bios[0x1cb] != 0x0c)) { + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); + SiS_SetReg(SISSR, 0x18, 0x00); + if((ivideo->chip == XGI_20) || + (ivideo->revision_id == 2)) { + SiS_SetReg(SISSR, 0x19, 0x40); + } else { + SiS_SetReg(SISSR, 0x19, 0x20); + } + } else if((ivideo->chip == XGI_40) && (bios[0x1cb] == 0x0c)) { + /* SiS_SetReg(SISSR, 0x16, 0x0c); */ /* ? */ + } + SiS_SetReg(SISSR, 0x16, 0x00); + SiS_SetReg(SISSR, 0x16, 0x80); + sisfb_post_xgi_delay(ivideo, 4); + v1 = 0x31; v2 = 0x03; v3 = 0x83; v4 = 0x03; v5 = 0x83; + if(ivideo->haveXGIROM) { + v1 = bios[0xf0]; + index = (ivideo->chip == XGI_20) ? 0x4b2 : 0x53e; + v2 = bios[index]; + v3 = bios[index + 1]; + v4 = bios[index + 2]; + v5 = bios[index + 3]; + } + SiS_SetReg(SISSR, 0x18, v1); + SiS_SetReg(SISSR, 0x19, ((ivideo->chip == XGI_20) ? 0x02 : 0x01)); + SiS_SetReg(SISSR, 0x16, v2); + SiS_SetReg(SISSR, 0x16, v3); + sisfb_post_xgi_delay(ivideo, 0x43); + SiS_SetReg(SISSR, 0x1b, 0x03); + sisfb_post_xgi_delay(ivideo, 0x22); + SiS_SetReg(SISSR, 0x18, v1); + SiS_SetReg(SISSR, 0x19, 0x00); + SiS_SetReg(SISSR, 0x16, v4); + SiS_SetReg(SISSR, 0x16, v5); + SiS_SetReg(SISSR, 0x1b, 0x00); + break; + case 1: + sisfb_post_xgi_ddr2(ivideo, regb); + break; + default: + sisfb_post_xgi_setclocks(ivideo, regb); + if((ivideo->chip == XGI_40) && + ((ivideo->revision_id == 1) || + (ivideo->revision_id == 2))) { + SiS_SetReg(SISCR, 0x82, bios[regb + 0x158]); + SiS_SetReg(SISCR, 0x85, bios[regb + 0x160]); + SiS_SetReg(SISCR, 0x86, bios[regb + 0x168]); + } else { + SiS_SetReg(SISCR, 0x82, 0x88); + SiS_SetReg(SISCR, 0x86, 0x00); + reg = SiS_GetReg(SISCR, 0x86); + SiS_SetReg(SISCR, 0x86, 0x88); + SiS_SetReg(SISCR, 0x82, 0x77); + SiS_SetReg(SISCR, 0x85, 0x00); + reg = SiS_GetReg(SISCR, 0x85); + SiS_SetReg(SISCR, 0x85, 0x88); + reg = SiS_GetReg(SISCR, 0x85); + v1 = cs160[regb]; v2 = cs158[regb]; + if(ivideo->haveXGIROM) { + v1 = bios[regb + 0x160]; + v2 = bios[regb + 0x158]; + } + SiS_SetReg(SISCR, 0x85, v1); + SiS_SetReg(SISCR, 0x82, v2); + } + if(ivideo->chip == XGI_40) { + SiS_SetReg(SISCR, 0x97, 0x11); + } + if((ivideo->chip == XGI_40) && (ivideo->revision_id == 2)) { + SiS_SetReg(SISCR, 0x98, 0x01); + } else { + SiS_SetReg(SISCR, 0x98, 0x03); + } + SiS_SetReg(SISCR, 0x9a, 0x02); + + if(ivideo->chip == XGI_40) { + SiS_SetReg(SISSR, 0x18, 0x01); + } else { + SiS_SetReg(SISSR, 0x18, 0x00); + } + SiS_SetReg(SISSR, 0x19, 0x40); + SiS_SetReg(SISSR, 0x16, 0x00); + SiS_SetReg(SISSR, 0x16, 0x80); + if((ivideo->chip == XGI_40) && (bios[0x1cb] != 0x0c)) { + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); + sisfb_post_xgi_delay(ivideo, 0x43); + SiS_SetReg(SISSR, 0x18, 0x00); + SiS_SetReg(SISSR, 0x19, 0x40); + SiS_SetReg(SISSR, 0x16, 0x00); + SiS_SetReg(SISSR, 0x16, 0x80); + } + sisfb_post_xgi_delay(ivideo, 4); + v1 = 0x31; + if(ivideo->haveXGIROM) { + v1 = bios[0xf0]; + } + SiS_SetReg(SISSR, 0x18, v1); + SiS_SetReg(SISSR, 0x19, 0x01); + if(ivideo->chip == XGI_40) { + SiS_SetReg(SISSR, 0x16, bios[0x53e]); + SiS_SetReg(SISSR, 0x16, bios[0x53f]); + } else { + SiS_SetReg(SISSR, 0x16, 0x05); + SiS_SetReg(SISSR, 0x16, 0x85); + } + sisfb_post_xgi_delay(ivideo, 0x43); + if(ivideo->chip == XGI_40) { + SiS_SetReg(SISSR, 0x1b, 0x01); + } else { + SiS_SetReg(SISSR, 0x1b, 0x03); + } + sisfb_post_xgi_delay(ivideo, 0x22); + SiS_SetReg(SISSR, 0x18, v1); + SiS_SetReg(SISSR, 0x19, 0x00); + if(ivideo->chip == XGI_40) { + SiS_SetReg(SISSR, 0x16, bios[0x540]); + SiS_SetReg(SISSR, 0x16, bios[0x541]); + } else { + SiS_SetReg(SISSR, 0x16, 0x05); + SiS_SetReg(SISSR, 0x16, 0x85); + } + SiS_SetReg(SISSR, 0x1b, 0x00); + } + + regb = 0; /* ! */ + v1 = 0x03; + if(ivideo->haveXGIROM) { + v1 = bios[0x110 + regb]; + } + SiS_SetReg(SISSR, 0x1b, v1); + + /* RAM size */ + v1 = 0x00; v2 = 0x00; + if(ivideo->haveXGIROM) { + v1 = bios[0x62]; + v2 = bios[0x63]; + } + regb = 0; /* ! */ + regd = 1 << regb; + if((v1 & 0x40) && (v2 & regd) && ivideo->haveXGIROM) { + + SiS_SetReg(SISSR, 0x13, bios[regb + 0xe0]); + SiS_SetReg(SISSR, 0x14, bios[regb + 0xe0 + 8]); + + } else { + int err; + + /* Set default mode, don't clear screen */ + ivideo->SiS_Pr.SiS_UseOEM = false; + SiS_SetEnableDstn(&ivideo->SiS_Pr, false); + SiS_SetEnableFstn(&ivideo->SiS_Pr, false); + ivideo->curFSTN = ivideo->curDSTN = 0; + ivideo->SiS_Pr.VideoMemorySize = 8 << 20; + SiSSetMode(&ivideo->SiS_Pr, 0x2e | 0x80); + + SiS_SetReg(SISSR, 0x05, 0x86); + + /* Disable read-cache */ + SiS_SetRegAND(SISSR, 0x21, 0xdf); + err = sisfb_post_xgi_ramsize(ivideo); + /* Enable read-cache */ + SiS_SetRegOR(SISSR, 0x21, 0x20); + + if (err) { + dev_err(&pdev->dev, + "%s: RAM size detection failed: %d\n", + __func__, err); + return 0; + } + } + +#if 0 + printk(KERN_DEBUG "-----------------\n"); + for(i = 0; i < 0xff; i++) { + reg = SiS_GetReg(SISCR, i); + printk(KERN_DEBUG "CR%02x(%x) = 0x%02x\n", i, SISCR, reg); + } + for(i = 0; i < 0x40; i++) { + reg = SiS_GetReg(SISSR, i); + printk(KERN_DEBUG "SR%02x(%x) = 0x%02x\n", i, SISSR, reg); + } + printk(KERN_DEBUG "-----------------\n"); +#endif + + /* Sense CRT1 */ + if(ivideo->chip == XGI_20) { + SiS_SetRegOR(SISCR, 0x32, 0x20); + } else { + reg = SiS_GetReg(SISPART4, 0x00); + if((reg == 1) || (reg == 2)) { + sisfb_sense_crt1(ivideo); + } else { + SiS_SetRegOR(SISCR, 0x32, 0x20); + } + } + + /* Set default mode, don't clear screen */ + ivideo->SiS_Pr.SiS_UseOEM = false; + SiS_SetEnableDstn(&ivideo->SiS_Pr, false); + SiS_SetEnableFstn(&ivideo->SiS_Pr, false); + ivideo->curFSTN = ivideo->curDSTN = 0; + SiSSetMode(&ivideo->SiS_Pr, 0x2e | 0x80); + + SiS_SetReg(SISSR, 0x05, 0x86); + + /* Display off */ + SiS_SetRegOR(SISSR, 0x01, 0x20); + + /* Save mode number in CR34 */ + SiS_SetReg(SISCR, 0x34, 0x2e); + + /* Let everyone know what the current mode is */ + ivideo->modeprechange = 0x2e; + + if(ivideo->chip == XGI_40) { + reg = SiS_GetReg(SISCR, 0xca); + v1 = SiS_GetReg(SISCR, 0xcc); + if((reg & 0x10) && (!(v1 & 0x04))) { + printk(KERN_ERR + "sisfb: Please connect power to the card.\n"); + return 0; + } + } + + return 1; +} +#endif + +static int sisfb_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct sisfb_chip_info *chipinfo = &sisfb_chip_info[ent->driver_data]; + struct sis_video_info *ivideo = NULL; + struct fb_info *sis_fb_info = NULL; + u16 reg16; + u8 reg; + int i, ret; + + if(sisfb_off) + return -ENXIO; + + sis_fb_info = framebuffer_alloc(sizeof(*ivideo), &pdev->dev); + if(!sis_fb_info) + return -ENOMEM; + + ivideo = (struct sis_video_info *)sis_fb_info->par; + ivideo->memyselfandi = sis_fb_info; + + ivideo->sisfb_id = SISFB_ID; + + if(card_list == NULL) { + ivideo->cardnumber = 0; + } else { + struct sis_video_info *countvideo = card_list; + ivideo->cardnumber = 1; + while((countvideo = countvideo->next) != NULL) + ivideo->cardnumber++; + } + + strncpy(ivideo->myid, chipinfo->chip_name, 30); + + ivideo->warncount = 0; + ivideo->chip_id = pdev->device; + ivideo->chip_vendor = pdev->vendor; + ivideo->revision_id = pdev->revision; + ivideo->SiS_Pr.ChipRevision = ivideo->revision_id; + pci_read_config_word(pdev, PCI_COMMAND, ®16); + ivideo->sisvga_enabled = reg16 & 0x01; + ivideo->pcibus = pdev->bus->number; + ivideo->pcislot = PCI_SLOT(pdev->devfn); + ivideo->pcifunc = PCI_FUNC(pdev->devfn); + ivideo->subsysvendor = pdev->subsystem_vendor; + ivideo->subsysdevice = pdev->subsystem_device; + +#ifndef MODULE + if(sisfb_mode_idx == -1) { + sisfb_get_vga_mode_from_kernel(); + } +#endif + + ivideo->chip = chipinfo->chip; + ivideo->chip_real_id = chipinfo->chip; + ivideo->sisvga_engine = chipinfo->vgaengine; + ivideo->hwcursor_size = chipinfo->hwcursor_size; + ivideo->CRT2_write_enable = chipinfo->CRT2_write_enable; + ivideo->mni = chipinfo->mni; + + ivideo->detectedpdc = 0xff; + ivideo->detectedpdca = 0xff; + ivideo->detectedlcda = 0xff; + + ivideo->sisfb_thismonitor.datavalid = false; + + ivideo->current_base = 0; + + ivideo->engineok = 0; + + ivideo->sisfb_was_boot_device = 0; + + if(pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) { + if(ivideo->sisvga_enabled) + ivideo->sisfb_was_boot_device = 1; + else { + printk(KERN_DEBUG "sisfb: PCI device is disabled, " + "but marked as boot video device ???\n"); + printk(KERN_DEBUG "sisfb: I will not accept this " + "as the primary VGA device\n"); + } + } + + ivideo->sisfb_parm_mem = sisfb_parm_mem; + ivideo->sisfb_accel = sisfb_accel; + ivideo->sisfb_ypan = sisfb_ypan; + ivideo->sisfb_max = sisfb_max; + ivideo->sisfb_userom = sisfb_userom; + ivideo->sisfb_useoem = sisfb_useoem; + ivideo->sisfb_mode_idx = sisfb_mode_idx; + ivideo->sisfb_parm_rate = sisfb_parm_rate; + ivideo->sisfb_crt1off = sisfb_crt1off; + ivideo->sisfb_forcecrt1 = sisfb_forcecrt1; + ivideo->sisfb_crt2type = sisfb_crt2type; + ivideo->sisfb_crt2flags = sisfb_crt2flags; + /* pdc(a), scalelcd, special timing, lvdshl handled below */ + ivideo->sisfb_dstn = sisfb_dstn; + ivideo->sisfb_fstn = sisfb_fstn; + ivideo->sisfb_tvplug = sisfb_tvplug; + ivideo->sisfb_tvstd = sisfb_tvstd; + ivideo->tvxpos = sisfb_tvxposoffset; + ivideo->tvypos = sisfb_tvyposoffset; + ivideo->sisfb_nocrt2rate = sisfb_nocrt2rate; + ivideo->refresh_rate = 0; + if(ivideo->sisfb_parm_rate != -1) { + ivideo->refresh_rate = ivideo->sisfb_parm_rate; + } + + ivideo->SiS_Pr.UsePanelScaler = sisfb_scalelcd; + ivideo->SiS_Pr.CenterScreen = -1; + ivideo->SiS_Pr.SiS_CustomT = sisfb_specialtiming; + ivideo->SiS_Pr.LVDSHL = sisfb_lvdshl; + + ivideo->SiS_Pr.SiS_Backup70xx = 0xff; + ivideo->SiS_Pr.SiS_CHOverScan = -1; + ivideo->SiS_Pr.SiS_ChSW = false; + ivideo->SiS_Pr.SiS_UseLCDA = false; + ivideo->SiS_Pr.HaveEMI = false; + ivideo->SiS_Pr.HaveEMILCD = false; + ivideo->SiS_Pr.OverruleEMI = false; + ivideo->SiS_Pr.SiS_SensibleSR11 = false; + ivideo->SiS_Pr.SiS_MyCR63 = 0x63; + ivideo->SiS_Pr.PDC = -1; + ivideo->SiS_Pr.PDCA = -1; + ivideo->SiS_Pr.DDCPortMixup = false; +#ifdef CONFIG_FB_SIS_315 + if(ivideo->chip >= SIS_330) { + ivideo->SiS_Pr.SiS_MyCR63 = 0x53; + if(ivideo->chip >= SIS_661) { + ivideo->SiS_Pr.SiS_SensibleSR11 = true; + } + } +#endif + + memcpy(&ivideo->default_var, &my_default_var, sizeof(my_default_var)); + + pci_set_drvdata(pdev, ivideo); + + /* Patch special cases */ + if((ivideo->nbridge = sisfb_get_northbridge(ivideo->chip))) { + switch(ivideo->nbridge->device) { +#ifdef CONFIG_FB_SIS_300 + case PCI_DEVICE_ID_SI_730: + ivideo->chip = SIS_730; + strcpy(ivideo->myid, "SiS 730"); + break; +#endif +#ifdef CONFIG_FB_SIS_315 + case PCI_DEVICE_ID_SI_651: + /* ivideo->chip is ok */ + strcpy(ivideo->myid, "SiS 651"); + break; + case PCI_DEVICE_ID_SI_740: + ivideo->chip = SIS_740; + strcpy(ivideo->myid, "SiS 740"); + break; + case PCI_DEVICE_ID_SI_661: + ivideo->chip = SIS_661; + strcpy(ivideo->myid, "SiS 661"); + break; + case PCI_DEVICE_ID_SI_741: + ivideo->chip = SIS_741; + strcpy(ivideo->myid, "SiS 741"); + break; + case PCI_DEVICE_ID_SI_760: + ivideo->chip = SIS_760; + strcpy(ivideo->myid, "SiS 760"); + break; + case PCI_DEVICE_ID_SI_761: + ivideo->chip = SIS_761; + strcpy(ivideo->myid, "SiS 761"); + break; +#endif + default: + break; + } + } + + ivideo->SiS_Pr.ChipType = ivideo->chip; + + ivideo->SiS_Pr.ivideo = (void *)ivideo; + +#ifdef CONFIG_FB_SIS_315 + if((ivideo->SiS_Pr.ChipType == SIS_315PRO) || + (ivideo->SiS_Pr.ChipType == SIS_315)) { + ivideo->SiS_Pr.ChipType = SIS_315H; + } +#endif + + if(!ivideo->sisvga_enabled) { + if(pci_enable_device(pdev)) { + if(ivideo->nbridge) pci_dev_put(ivideo->nbridge); + framebuffer_release(sis_fb_info); + return -EIO; + } + } + + ivideo->video_base = pci_resource_start(pdev, 0); + ivideo->video_size = pci_resource_len(pdev, 0); + ivideo->mmio_base = pci_resource_start(pdev, 1); + ivideo->mmio_size = pci_resource_len(pdev, 1); + ivideo->SiS_Pr.RelIO = pci_resource_start(pdev, 2) + 0x30; + ivideo->SiS_Pr.IOAddress = ivideo->vga_base = ivideo->SiS_Pr.RelIO; + + SiSRegInit(&ivideo->SiS_Pr, ivideo->SiS_Pr.IOAddress); + +#ifdef CONFIG_FB_SIS_300 + /* Find PCI systems for Chrontel/GPIO communication setup */ + if(ivideo->chip == SIS_630) { + i = 0; + do { + if(mychswtable[i].subsysVendor == ivideo->subsysvendor && + mychswtable[i].subsysCard == ivideo->subsysdevice) { + ivideo->SiS_Pr.SiS_ChSW = true; + printk(KERN_DEBUG "sisfb: Identified [%s %s] " + "requiring Chrontel/GPIO setup\n", + mychswtable[i].vendorName, + mychswtable[i].cardName); + ivideo->lpcdev = pci_get_device(PCI_VENDOR_ID_SI, 0x0008, NULL); + break; + } + i++; + } while(mychswtable[i].subsysVendor != 0); + } +#endif + +#ifdef CONFIG_FB_SIS_315 + if((ivideo->chip == SIS_760) && (ivideo->nbridge)) { + ivideo->lpcdev = pci_get_slot(ivideo->nbridge->bus, (2 << 3)); + } +#endif + + SiS_SetReg(SISSR, 0x05, 0x86); + + if( (!ivideo->sisvga_enabled) +#if !defined(__i386__) && !defined(__x86_64__) + || (sisfb_resetcard) +#endif + ) { + for(i = 0x30; i <= 0x3f; i++) { + SiS_SetReg(SISCR, i, 0x00); + } + } + + /* Find out about current video mode */ + ivideo->modeprechange = 0x03; + reg = SiS_GetReg(SISCR, 0x34); + if(reg & 0x7f) { + ivideo->modeprechange = reg & 0x7f; + } else if(ivideo->sisvga_enabled) { +#if defined(__i386__) || defined(__x86_64__) + unsigned char __iomem *tt = ioremap(0x400, 0x100); + if(tt) { + ivideo->modeprechange = readb(tt + 0x49); + iounmap(tt); + } +#endif + } + + /* Search and copy ROM image */ + ivideo->bios_abase = NULL; + ivideo->SiS_Pr.VirtualRomBase = NULL; + ivideo->SiS_Pr.UseROM = false; + ivideo->haveXGIROM = ivideo->SiS_Pr.SiS_XGIROM = false; + if(ivideo->sisfb_userom) { + ivideo->SiS_Pr.VirtualRomBase = sisfb_find_rom(pdev); + ivideo->bios_abase = ivideo->SiS_Pr.VirtualRomBase; + ivideo->SiS_Pr.UseROM = (bool)(ivideo->SiS_Pr.VirtualRomBase); + printk(KERN_INFO "sisfb: Video ROM %sfound\n", + ivideo->SiS_Pr.UseROM ? "" : "not "); + if((ivideo->SiS_Pr.UseROM) && (ivideo->chip >= XGI_20)) { + ivideo->SiS_Pr.UseROM = false; + ivideo->haveXGIROM = ivideo->SiS_Pr.SiS_XGIROM = true; + if( (ivideo->revision_id == 2) && + (!(ivideo->bios_abase[0x1d1] & 0x01)) ) { + ivideo->SiS_Pr.DDCPortMixup = true; + } + } + } else { + printk(KERN_INFO "sisfb: Video ROM usage disabled\n"); + } + + /* Find systems for special custom timing */ + if(ivideo->SiS_Pr.SiS_CustomT == CUT_NONE) { + sisfb_detect_custom_timing(ivideo); + } + +#ifdef CONFIG_FB_SIS_315 + if (ivideo->chip == XGI_20) { + /* Check if our Z7 chip is actually Z9 */ + SiS_SetRegOR(SISCR, 0x4a, 0x40); /* GPIOG EN */ + reg = SiS_GetReg(SISCR, 0x48); + if (reg & 0x02) { /* GPIOG */ + ivideo->chip_real_id = XGI_21; + dev_info(&pdev->dev, "Z9 detected\n"); + } + } +#endif + + /* POST card in case this has not been done by the BIOS */ + if( (!ivideo->sisvga_enabled) +#if !defined(__i386__) && !defined(__x86_64__) + || (sisfb_resetcard) +#endif + ) { +#ifdef CONFIG_FB_SIS_300 + if(ivideo->sisvga_engine == SIS_300_VGA) { + if(ivideo->chip == SIS_300) { + sisfb_post_sis300(pdev); + ivideo->sisfb_can_post = 1; + } + } +#endif + +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + int result = 1; + /* if((ivideo->chip == SIS_315H) || + (ivideo->chip == SIS_315) || + (ivideo->chip == SIS_315PRO) || + (ivideo->chip == SIS_330)) { + sisfb_post_sis315330(pdev); + } else */ if(ivideo->chip == XGI_20) { + result = sisfb_post_xgi(pdev); + ivideo->sisfb_can_post = 1; + } else if((ivideo->chip == XGI_40) && ivideo->haveXGIROM) { + result = sisfb_post_xgi(pdev); + ivideo->sisfb_can_post = 1; + } else { + printk(KERN_INFO "sisfb: Card is not " + "POSTed and sisfb can't do this either.\n"); + } + if(!result) { + printk(KERN_ERR "sisfb: Failed to POST card\n"); + ret = -ENODEV; + goto error_3; + } + } +#endif + } + + ivideo->sisfb_card_posted = 1; + + /* Find out about RAM size */ + if(sisfb_get_dram_size(ivideo)) { + printk(KERN_INFO "sisfb: Fatal error: Unable to determine VRAM size.\n"); + ret = -ENODEV; + goto error_3; + } + + + /* Enable PCI addressing and MMIO */ + if((ivideo->sisfb_mode_idx < 0) || + ((sisbios_mode[ivideo->sisfb_mode_idx].mode_no[ivideo->mni]) != 0xFF)) { + /* Enable PCI_LINEAR_ADDRESSING and MMIO_ENABLE */ + SiS_SetRegOR(SISSR, IND_SIS_PCI_ADDRESS_SET, (SIS_PCI_ADDR_ENABLE | SIS_MEM_MAP_IO_ENABLE)); + /* Enable 2D accelerator engine */ + SiS_SetRegOR(SISSR, IND_SIS_MODULE_ENABLE, SIS_ENABLE_2D); + } + + if(sisfb_pdc != 0xff) { + if(ivideo->sisvga_engine == SIS_300_VGA) + sisfb_pdc &= 0x3c; + else + sisfb_pdc &= 0x1f; + ivideo->SiS_Pr.PDC = sisfb_pdc; + } +#ifdef CONFIG_FB_SIS_315 + if(ivideo->sisvga_engine == SIS_315_VGA) { + if(sisfb_pdca != 0xff) + ivideo->SiS_Pr.PDCA = sisfb_pdca & 0x1f; + } +#endif + + if(!request_mem_region(ivideo->video_base, ivideo->video_size, "sisfb FB")) { + printk(KERN_ERR "sisfb: Fatal error: Unable to reserve %dMB framebuffer memory\n", + (int)(ivideo->video_size >> 20)); + printk(KERN_ERR "sisfb: Is there another framebuffer driver active?\n"); + ret = -ENODEV; + goto error_3; + } + + if(!request_mem_region(ivideo->mmio_base, ivideo->mmio_size, "sisfb MMIO")) { + printk(KERN_ERR "sisfb: Fatal error: Unable to reserve MMIO region\n"); + ret = -ENODEV; + goto error_2; + } + + ivideo->video_vbase = ioremap(ivideo->video_base, ivideo->video_size); + ivideo->SiS_Pr.VideoMemoryAddress = ivideo->video_vbase; + if(!ivideo->video_vbase) { + printk(KERN_ERR "sisfb: Fatal error: Unable to map framebuffer memory\n"); + ret = -ENODEV; + goto error_1; + } + + ivideo->mmio_vbase = ioremap(ivideo->mmio_base, ivideo->mmio_size); + if(!ivideo->mmio_vbase) { + printk(KERN_ERR "sisfb: Fatal error: Unable to map MMIO region\n"); + ret = -ENODEV; +error_0: iounmap(ivideo->video_vbase); +error_1: release_mem_region(ivideo->video_base, ivideo->video_size); +error_2: release_mem_region(ivideo->mmio_base, ivideo->mmio_size); +error_3: vfree(ivideo->bios_abase); + if(ivideo->lpcdev) + pci_dev_put(ivideo->lpcdev); + if(ivideo->nbridge) + pci_dev_put(ivideo->nbridge); + if(!ivideo->sisvga_enabled) + pci_disable_device(pdev); + framebuffer_release(sis_fb_info); + return ret; + } + + printk(KERN_INFO "sisfb: Video RAM at 0x%lx, mapped to 0x%lx, size %ldk\n", + ivideo->video_base, (unsigned long)ivideo->video_vbase, ivideo->video_size / 1024); + + if(ivideo->video_offset) { + printk(KERN_INFO "sisfb: Viewport offset %ldk\n", + ivideo->video_offset / 1024); + } + + printk(KERN_INFO "sisfb: MMIO at 0x%lx, mapped to 0x%lx, size %ldk\n", + ivideo->mmio_base, (unsigned long)ivideo->mmio_vbase, ivideo->mmio_size / 1024); + + + /* Determine the size of the command queue */ + if(ivideo->sisvga_engine == SIS_300_VGA) { + ivideo->cmdQueueSize = TURBO_QUEUE_AREA_SIZE; + } else { + if(ivideo->chip == XGI_20) { + ivideo->cmdQueueSize = COMMAND_QUEUE_AREA_SIZE_Z7; + } else { + ivideo->cmdQueueSize = COMMAND_QUEUE_AREA_SIZE; + } + } + + /* Engines are no longer initialized here; this is + * now done after the first mode-switch (if the + * submitted var has its acceleration flags set). + */ + + /* Calculate the base of the (unused) hw cursor */ + ivideo->hwcursor_vbase = ivideo->video_vbase + + ivideo->video_size + - ivideo->cmdQueueSize + - ivideo->hwcursor_size; + ivideo->caps |= HW_CURSOR_CAP; + + /* Initialize offscreen memory manager */ + if((ivideo->havenoheap = sisfb_heap_init(ivideo))) { + printk(KERN_WARNING "sisfb: Failed to initialize offscreen memory heap\n"); + } + + /* Used for clearing the screen only, therefore respect our mem limit */ + ivideo->SiS_Pr.VideoMemoryAddress += ivideo->video_offset; + ivideo->SiS_Pr.VideoMemorySize = ivideo->sisfb_mem; + + ivideo->mtrr = -1; + + ivideo->vbflags = 0; + ivideo->lcddefmodeidx = DEFAULT_LCDMODE; + ivideo->tvdefmodeidx = DEFAULT_TVMODE; + ivideo->defmodeidx = DEFAULT_MODE; + + ivideo->newrom = 0; + if(ivideo->chip < XGI_20) { + if(ivideo->bios_abase) { + ivideo->newrom = SiSDetermineROMLayout661(&ivideo->SiS_Pr); + } + } + + if((ivideo->sisfb_mode_idx < 0) || + ((sisbios_mode[ivideo->sisfb_mode_idx].mode_no[ivideo->mni]) != 0xFF)) { + + sisfb_sense_crt1(ivideo); + + sisfb_get_VB_type(ivideo); + + if(ivideo->vbflags2 & VB2_VIDEOBRIDGE) { + sisfb_detect_VB_connect(ivideo); + } + + ivideo->currentvbflags = ivideo->vbflags & (VB_VIDEOBRIDGE | TV_STANDARD); + + /* Decide on which CRT2 device to use */ + if(ivideo->vbflags2 & VB2_VIDEOBRIDGE) { + if(ivideo->sisfb_crt2type != -1) { + if((ivideo->sisfb_crt2type == CRT2_LCD) && + (ivideo->vbflags & CRT2_LCD)) { + ivideo->currentvbflags |= CRT2_LCD; + } else if(ivideo->sisfb_crt2type != CRT2_LCD) { + ivideo->currentvbflags |= ivideo->sisfb_crt2type; + } + } else { + /* Chrontel 700x TV detection often unreliable, therefore + * use a different default order on such machines + */ + if((ivideo->sisvga_engine == SIS_300_VGA) && + (ivideo->vbflags2 & VB2_CHRONTEL)) { + if(ivideo->vbflags & CRT2_LCD) + ivideo->currentvbflags |= CRT2_LCD; + else if(ivideo->vbflags & CRT2_TV) + ivideo->currentvbflags |= CRT2_TV; + else if(ivideo->vbflags & CRT2_VGA) + ivideo->currentvbflags |= CRT2_VGA; + } else { + if(ivideo->vbflags & CRT2_TV) + ivideo->currentvbflags |= CRT2_TV; + else if(ivideo->vbflags & CRT2_LCD) + ivideo->currentvbflags |= CRT2_LCD; + else if(ivideo->vbflags & CRT2_VGA) + ivideo->currentvbflags |= CRT2_VGA; + } + } + } + + if(ivideo->vbflags & CRT2_LCD) { + sisfb_detect_lcd_type(ivideo); + } + + sisfb_save_pdc_emi(ivideo); + + if(!ivideo->sisfb_crt1off) { + sisfb_handle_ddc(ivideo, &ivideo->sisfb_thismonitor, 0); + } else { + if((ivideo->vbflags2 & VB2_SISTMDSBRIDGE) && + (ivideo->vbflags & (CRT2_VGA | CRT2_LCD))) { + sisfb_handle_ddc(ivideo, &ivideo->sisfb_thismonitor, 1); + } + } + + if(ivideo->sisfb_mode_idx >= 0) { + int bu = ivideo->sisfb_mode_idx; + ivideo->sisfb_mode_idx = sisfb_validate_mode(ivideo, + ivideo->sisfb_mode_idx, ivideo->currentvbflags); + if(bu != ivideo->sisfb_mode_idx) { + printk(KERN_ERR "Mode %dx%dx%d failed validation\n", + sisbios_mode[bu].xres, + sisbios_mode[bu].yres, + sisbios_mode[bu].bpp); + } + } + + if(ivideo->sisfb_mode_idx < 0) { + switch(ivideo->currentvbflags & VB_DISPTYPE_DISP2) { + case CRT2_LCD: + ivideo->sisfb_mode_idx = ivideo->lcddefmodeidx; + break; + case CRT2_TV: + ivideo->sisfb_mode_idx = ivideo->tvdefmodeidx; + break; + default: + ivideo->sisfb_mode_idx = ivideo->defmodeidx; + break; + } + } + + ivideo->mode_no = sisbios_mode[ivideo->sisfb_mode_idx].mode_no[ivideo->mni]; + + if(ivideo->refresh_rate != 0) { + sisfb_search_refresh_rate(ivideo, ivideo->refresh_rate, + ivideo->sisfb_mode_idx); + } + + if(ivideo->rate_idx == 0) { + ivideo->rate_idx = sisbios_mode[ivideo->sisfb_mode_idx].rate_idx; + ivideo->refresh_rate = 60; + } + + if(ivideo->sisfb_thismonitor.datavalid) { + if(!sisfb_verify_rate(ivideo, &ivideo->sisfb_thismonitor, + ivideo->sisfb_mode_idx, + ivideo->rate_idx, + ivideo->refresh_rate)) { + printk(KERN_INFO "sisfb: WARNING: Refresh rate " + "exceeds monitor specs!\n"); + } + } + + ivideo->video_bpp = sisbios_mode[ivideo->sisfb_mode_idx].bpp; + ivideo->video_width = sisbios_mode[ivideo->sisfb_mode_idx].xres; + ivideo->video_height = sisbios_mode[ivideo->sisfb_mode_idx].yres; + + sisfb_set_vparms(ivideo); + + printk(KERN_INFO "sisfb: Default mode is %dx%dx%d (%dHz)\n", + ivideo->video_width, ivideo->video_height, ivideo->video_bpp, + ivideo->refresh_rate); + + /* Set up the default var according to chosen default display mode */ + ivideo->default_var.xres = ivideo->default_var.xres_virtual = ivideo->video_width; + ivideo->default_var.yres = ivideo->default_var.yres_virtual = ivideo->video_height; + ivideo->default_var.bits_per_pixel = ivideo->video_bpp; + + sisfb_bpp_to_var(ivideo, &ivideo->default_var); + + ivideo->default_var.pixclock = (u32) (1000000000 / + sisfb_mode_rate_to_dclock(&ivideo->SiS_Pr, ivideo->mode_no, ivideo->rate_idx)); + + if(sisfb_mode_rate_to_ddata(&ivideo->SiS_Pr, ivideo->mode_no, + ivideo->rate_idx, &ivideo->default_var)) { + if((ivideo->default_var.vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) { + ivideo->default_var.pixclock <<= 1; + } + } + + if(ivideo->sisfb_ypan) { + /* Maximize regardless of sisfb_max at startup */ + ivideo->default_var.yres_virtual = + sisfb_calc_maxyres(ivideo, &ivideo->default_var); + if(ivideo->default_var.yres_virtual < ivideo->default_var.yres) { + ivideo->default_var.yres_virtual = ivideo->default_var.yres; + } + } + + sisfb_calc_pitch(ivideo, &ivideo->default_var); + + ivideo->accel = 0; + if(ivideo->sisfb_accel) { + ivideo->accel = -1; +#ifdef STUPID_ACCELF_TEXT_SHIT + ivideo->default_var.accel_flags |= FB_ACCELF_TEXT; +#endif + } + sisfb_initaccel(ivideo); + +#if defined(FBINFO_HWACCEL_DISABLED) && defined(FBINFO_HWACCEL_XPAN) + sis_fb_info->flags = FBINFO_DEFAULT | + FBINFO_HWACCEL_YPAN | + FBINFO_HWACCEL_XPAN | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT | + ((ivideo->accel) ? 0 : FBINFO_HWACCEL_DISABLED); +#else + sis_fb_info->flags = FBINFO_FLAG_DEFAULT; +#endif + sis_fb_info->var = ivideo->default_var; + sis_fb_info->fix = ivideo->sisfb_fix; + sis_fb_info->screen_base = ivideo->video_vbase + ivideo->video_offset; + sis_fb_info->fbops = &sisfb_ops; + sis_fb_info->pseudo_palette = ivideo->pseudo_palette; + + fb_alloc_cmap(&sis_fb_info->cmap, 256 , 0); + + printk(KERN_DEBUG "sisfb: Initial vbflags 0x%x\n", (int)ivideo->vbflags); + +#ifdef CONFIG_MTRR + ivideo->mtrr = mtrr_add(ivideo->video_base, ivideo->video_size, + MTRR_TYPE_WRCOMB, 1); + if(ivideo->mtrr < 0) { + printk(KERN_DEBUG "sisfb: Failed to add MTRRs\n"); + } +#endif + + if(register_framebuffer(sis_fb_info) < 0) { + printk(KERN_ERR "sisfb: Fatal error: Failed to register framebuffer\n"); + ret = -EINVAL; + iounmap(ivideo->mmio_vbase); + goto error_0; + } + + ivideo->registered = 1; + + /* Enlist us */ + ivideo->next = card_list; + card_list = ivideo; + + printk(KERN_INFO "sisfb: 2D acceleration is %s, y-panning %s\n", + ivideo->sisfb_accel ? "enabled" : "disabled", + ivideo->sisfb_ypan ? + (ivideo->sisfb_max ? "enabled (auto-max)" : + "enabled (no auto-max)") : + "disabled"); + + + fb_info(sis_fb_info, "%s frame buffer device version %d.%d.%d\n", + ivideo->myid, VER_MAJOR, VER_MINOR, VER_LEVEL); + + printk(KERN_INFO "sisfb: Copyright (C) 2001-2005 Thomas Winischhofer\n"); + + } /* if mode = "none" */ + + return 0; +} + +/*****************************************************/ +/* PCI DEVICE HANDLING */ +/*****************************************************/ + +static void sisfb_remove(struct pci_dev *pdev) +{ + struct sis_video_info *ivideo = pci_get_drvdata(pdev); + struct fb_info *sis_fb_info = ivideo->memyselfandi; + int registered = ivideo->registered; + int modechanged = ivideo->modechanged; + + /* Unmap */ + iounmap(ivideo->mmio_vbase); + iounmap(ivideo->video_vbase); + + /* Release mem regions */ + release_mem_region(ivideo->video_base, ivideo->video_size); + release_mem_region(ivideo->mmio_base, ivideo->mmio_size); + + vfree(ivideo->bios_abase); + + if(ivideo->lpcdev) + pci_dev_put(ivideo->lpcdev); + + if(ivideo->nbridge) + pci_dev_put(ivideo->nbridge); + +#ifdef CONFIG_MTRR + /* Release MTRR region */ + if(ivideo->mtrr >= 0) + mtrr_del(ivideo->mtrr, ivideo->video_base, ivideo->video_size); +#endif + + /* If device was disabled when starting, disable + * it when quitting. + */ + if(!ivideo->sisvga_enabled) + pci_disable_device(pdev); + + /* Unregister the framebuffer */ + if(ivideo->registered) { + unregister_framebuffer(sis_fb_info); + framebuffer_release(sis_fb_info); + } + + /* OK, our ivideo is gone for good from here. */ + + /* TODO: Restore the initial mode + * This sounds easy but is as good as impossible + * on many machines with SiS chip and video bridge + * since text modes are always set up differently + * from machine to machine. Depends on the type + * of integration between chipset and bridge. + */ + if(registered && modechanged) + printk(KERN_INFO + "sisfb: Restoring of text mode not supported yet\n"); +}; + +static struct pci_driver sisfb_driver = { + .name = "sisfb", + .id_table = sisfb_pci_table, + .probe = sisfb_probe, + .remove = sisfb_remove, +}; + +static int __init sisfb_init(void) +{ +#ifndef MODULE + char *options = NULL; + + if(fb_get_options("sisfb", &options)) + return -ENODEV; + + sisfb_setup(options); +#endif + return pci_register_driver(&sisfb_driver); +} + +#ifndef MODULE +module_init(sisfb_init); +#endif + +/*****************************************************/ +/* MODULE */ +/*****************************************************/ + +#ifdef MODULE + +static char *mode = NULL; +static int vesa = -1; +static unsigned int rate = 0; +static unsigned int crt1off = 1; +static unsigned int mem = 0; +static char *forcecrt2type = NULL; +static int forcecrt1 = -1; +static int pdc = -1; +static int pdc1 = -1; +static int noaccel = -1; +static int noypan = -1; +static int nomax = -1; +static int userom = -1; +static int useoem = -1; +static char *tvstandard = NULL; +static int nocrt2rate = 0; +static int scalelcd = -1; +static char *specialtiming = NULL; +static int lvdshl = -1; +static int tvxposoffset = 0, tvyposoffset = 0; +#if !defined(__i386__) && !defined(__x86_64__) +static int resetcard = 0; +static int videoram = 0; +#endif + +static int __init sisfb_init_module(void) +{ + sisfb_setdefaultparms(); + + if(rate) + sisfb_parm_rate = rate; + + if((scalelcd == 0) || (scalelcd == 1)) + sisfb_scalelcd = scalelcd ^ 1; + + /* Need to check crt2 type first for fstn/dstn */ + + if(forcecrt2type) + sisfb_search_crt2type(forcecrt2type); + + if(tvstandard) + sisfb_search_tvstd(tvstandard); + + if(mode) + sisfb_search_mode(mode, false); + else if(vesa != -1) + sisfb_search_vesamode(vesa, false); + + sisfb_crt1off = (crt1off == 0) ? 1 : 0; + + sisfb_forcecrt1 = forcecrt1; + if(forcecrt1 == 1) + sisfb_crt1off = 0; + else if(forcecrt1 == 0) + sisfb_crt1off = 1; + + if(noaccel == 1) + sisfb_accel = 0; + else if(noaccel == 0) + sisfb_accel = 1; + + if(noypan == 1) + sisfb_ypan = 0; + else if(noypan == 0) + sisfb_ypan = 1; + + if(nomax == 1) + sisfb_max = 0; + else if(nomax == 0) + sisfb_max = 1; + + if(mem) + sisfb_parm_mem = mem; + + if(userom != -1) + sisfb_userom = userom; + + if(useoem != -1) + sisfb_useoem = useoem; + + if(pdc != -1) + sisfb_pdc = (pdc & 0x7f); + + if(pdc1 != -1) + sisfb_pdca = (pdc1 & 0x1f); + + sisfb_nocrt2rate = nocrt2rate; + + if(specialtiming) + sisfb_search_specialtiming(specialtiming); + + if((lvdshl >= 0) && (lvdshl <= 3)) + sisfb_lvdshl = lvdshl; + + sisfb_tvxposoffset = tvxposoffset; + sisfb_tvyposoffset = tvyposoffset; + +#if !defined(__i386__) && !defined(__x86_64__) + sisfb_resetcard = (resetcard) ? 1 : 0; + if(videoram) + sisfb_videoram = videoram; +#endif + + return sisfb_init(); +} + +static void __exit sisfb_remove_module(void) +{ + pci_unregister_driver(&sisfb_driver); + printk(KERN_DEBUG "sisfb: Module unloaded\n"); +} + +module_init(sisfb_init_module); +module_exit(sisfb_remove_module); + +MODULE_DESCRIPTION("SiS 300/540/630/730/315/55x/65x/661/74x/330/76x/34x, XGI V3XT/V5/V8/Z7 framebuffer device driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Thomas Winischhofer <thomas@winischhofer.net>, Others"); + +module_param(mem, int, 0); +module_param(noaccel, int, 0); +module_param(noypan, int, 0); +module_param(nomax, int, 0); +module_param(userom, int, 0); +module_param(useoem, int, 0); +module_param(mode, charp, 0); +module_param(vesa, int, 0); +module_param(rate, int, 0); +module_param(forcecrt1, int, 0); +module_param(forcecrt2type, charp, 0); +module_param(scalelcd, int, 0); +module_param(pdc, int, 0); +module_param(pdc1, int, 0); +module_param(specialtiming, charp, 0); +module_param(lvdshl, int, 0); +module_param(tvstandard, charp, 0); +module_param(tvxposoffset, int, 0); +module_param(tvyposoffset, int, 0); +module_param(nocrt2rate, int, 0); +#if !defined(__i386__) && !defined(__x86_64__) +module_param(resetcard, int, 0); +module_param(videoram, int, 0); +#endif + +MODULE_PARM_DESC(mem, + "\nDetermines the beginning of the video memory heap in KB. This heap is used\n" + "for video RAM management for eg. DRM/DRI. On 300 series, the default depends\n" + "on the amount of video RAM available. If 8MB of video RAM or less is available,\n" + "the heap starts at 4096KB, if between 8 and 16MB are available at 8192KB,\n" + "otherwise at 12288KB. On 315/330/340 series, the heap size is 32KB by default.\n" + "The value is to be specified without 'KB'.\n"); + +MODULE_PARM_DESC(noaccel, + "\nIf set to anything other than 0, 2D acceleration will be disabled.\n" + "(default: 0)\n"); + +MODULE_PARM_DESC(noypan, + "\nIf set to anything other than 0, y-panning will be disabled and scrolling\n" + "will be performed by redrawing the screen. (default: 0)\n"); + +MODULE_PARM_DESC(nomax, + "\nIf y-panning is enabled, sisfb will by default use the entire available video\n" + "memory for the virtual screen in order to optimize scrolling performance. If\n" + "this is set to anything other than 0, sisfb will not do this and thereby \n" + "enable the user to positively specify a virtual Y size of the screen using\n" + "fbset. (default: 0)\n"); + +MODULE_PARM_DESC(mode, + "\nSelects the desired default display mode in the format XxYxDepth,\n" + "eg. 1024x768x16. Other formats supported include XxY-Depth and\n" + "XxY-Depth@Rate. If the parameter is only one (decimal or hexadecimal)\n" + "number, it will be interpreted as a VESA mode number. (default: 800x600x8)\n"); + +MODULE_PARM_DESC(vesa, + "\nSelects the desired default display mode by VESA defined mode number, eg.\n" + "0x117 (default: 0x0103)\n"); + +MODULE_PARM_DESC(rate, + "\nSelects the desired vertical refresh rate for CRT1 (external VGA) in Hz.\n" + "If the mode is specified in the format XxY-Depth@Rate, this parameter\n" + "will be ignored (default: 60)\n"); + +MODULE_PARM_DESC(forcecrt1, + "\nNormally, the driver autodetects whether or not CRT1 (external VGA) is \n" + "connected. With this option, the detection can be overridden (1=CRT1 ON,\n" + "0=CRT1 OFF) (default: [autodetected])\n"); + +MODULE_PARM_DESC(forcecrt2type, + "\nIf this option is omitted, the driver autodetects CRT2 output devices, such as\n" + "LCD, TV or secondary VGA. With this option, this autodetection can be\n" + "overridden. Possible parameters are LCD, TV, VGA or NONE. NONE disables CRT2.\n" + "On systems with a SiS video bridge, parameters SVIDEO, COMPOSITE or SCART can\n" + "be used instead of TV to override the TV detection. Furthermore, on systems\n" + "with a SiS video bridge, SVIDEO+COMPOSITE, HIVISION, YPBPR480I, YPBPR480P,\n" + "YPBPR720P and YPBPR1080I are understood. However, whether or not these work\n" + "depends on the very hardware in use. (default: [autodetected])\n"); + +MODULE_PARM_DESC(scalelcd, + "\nSetting this to 1 will force the driver to scale the LCD image to the panel's\n" + "native resolution. Setting it to 0 will disable scaling; LVDS panels will\n" + "show black bars around the image, TMDS panels will probably do the scaling\n" + "themselves. Default: 1 on LVDS panels, 0 on TMDS panels\n"); + +MODULE_PARM_DESC(pdc, + "\nThis is for manually selecting the LCD panel delay compensation. The driver\n" + "should detect this correctly in most cases; however, sometimes this is not\n" + "possible. If you see 'small waves' on the LCD, try setting this to 4, 32 or 24\n" + "on a 300 series chipset; 6 on other chipsets. If the problem persists, try\n" + "other values (on 300 series: between 4 and 60 in steps of 4; otherwise: any\n" + "value from 0 to 31). (default: autodetected, if LCD is active during start)\n"); + +#ifdef CONFIG_FB_SIS_315 +MODULE_PARM_DESC(pdc1, + "\nThis is same as pdc, but for LCD-via CRT1. Hence, this is for the 315/330/340\n" + "series only. (default: autodetected if LCD is in LCD-via-CRT1 mode during\n" + "startup) - Note: currently, this has no effect because LCD-via-CRT1 is not\n" + "implemented yet.\n"); +#endif + +MODULE_PARM_DESC(specialtiming, + "\nPlease refer to documentation for more information on this option.\n"); + +MODULE_PARM_DESC(lvdshl, + "\nPlease refer to documentation for more information on this option.\n"); + +MODULE_PARM_DESC(tvstandard, + "\nThis allows overriding the BIOS default for the TV standard. Valid choices are\n" + "pal, ntsc, palm and paln. (default: [auto; pal or ntsc only])\n"); + +MODULE_PARM_DESC(tvxposoffset, + "\nRelocate TV output horizontally. Possible parameters: -32 through 32.\n" + "Default: 0\n"); + +MODULE_PARM_DESC(tvyposoffset, + "\nRelocate TV output vertically. Possible parameters: -32 through 32.\n" + "Default: 0\n"); + +MODULE_PARM_DESC(nocrt2rate, + "\nSetting this to 1 will force the driver to use the default refresh rate for\n" + "CRT2 if CRT2 type is VGA. (default: 0, use same rate as CRT1)\n"); + +#if !defined(__i386__) && !defined(__x86_64__) +#ifdef CONFIG_FB_SIS_300 +MODULE_PARM_DESC(resetcard, + "\nSet this to 1 in order to reset (POST) the card on non-x86 machines where\n" + "the BIOS did not POST the card (only supported for SiS 300/305 and XGI cards\n" + "currently). Default: 0\n"); + +MODULE_PARM_DESC(videoram, + "\nSet this to the amount of video RAM (in kilobyte) the card has. Required on\n" + "some non-x86 architectures where the memory auto detection fails. Only\n" + "relevant if resetcard is set, too. SiS300/305 only. Default: [auto-detect]\n"); +#endif +#endif + +#endif /* /MODULE */ + +/* _GPL only for new symbols. */ +EXPORT_SYMBOL(sis_malloc); +EXPORT_SYMBOL(sis_free); +EXPORT_SYMBOL_GPL(sis_malloc_new); +EXPORT_SYMBOL_GPL(sis_free_new); + + + diff --git a/drivers/video/fbdev/sis/sis_main.h b/drivers/video/fbdev/sis/sis_main.h new file mode 100644 index 000000000000..32e23c209430 --- /dev/null +++ b/drivers/video/fbdev/sis/sis_main.h @@ -0,0 +1,781 @@ +/* + * SiS 300/305/540/630(S)/730(S), + * SiS 315[E|PRO]/550/[M]65x/[M]66x[F|M|G]X/[M]74x[GX]/330/[M]76x[GX], + * XGI V3XT/V5/V8, Z7 + * frame buffer driver for Linux kernels >=2.4.14 and >=2.6.3 + * + * Copyright (C) 2001-2005 Thomas Winischhofer, Vienna, Austria. + * + * 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 named License, + * or 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 + */ + +#ifndef _SISFB_MAIN +#define _SISFB_MAIN + +#include "vstruct.h" +#include "sis.h" + +/* Fbcon stuff */ +static struct fb_var_screeninfo my_default_var = { + .xres = 0, + .yres = 0, + .xres_virtual = 0, + .yres_virtual = 0, + .xoffset = 0, + .yoffset = 0, + .bits_per_pixel = 0, + .grayscale = 0, + .red = {0, 8, 0}, + .green = {0, 8, 0}, + .blue = {0, 8, 0}, + .transp = {0, 0, 0}, + .nonstd = 0, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .accel_flags = 0, + .pixclock = 0, + .left_margin = 0, + .right_margin = 0, + .upper_margin = 0, + .lower_margin = 0, + .hsync_len = 0, + .vsync_len = 0, + .sync = 0, + .vmode = FB_VMODE_NONINTERLACED, +}; + +#define MODE_INDEX_NONE 0 /* index for mode=none */ + +/* Boot-time parameters */ +static int sisfb_off = 0; +static int sisfb_parm_mem = 0; +static int sisfb_accel = -1; +static int sisfb_ypan = -1; +static int sisfb_max = -1; +static int sisfb_userom = 1; +static int sisfb_useoem = -1; +static int sisfb_mode_idx = -1; /* Use a default mode if we are inside the kernel */ +static int sisfb_parm_rate = -1; +static int sisfb_crt1off = 0; +static int sisfb_forcecrt1 = -1; +static int sisfb_crt2type = -1; /* CRT2 type (for overriding autodetection) */ +static int sisfb_crt2flags = 0; +static int sisfb_pdc = 0xff; +static int sisfb_pdca = 0xff; +static int sisfb_scalelcd = -1; +static int sisfb_specialtiming = CUT_NONE; +static int sisfb_lvdshl = -1; +static int sisfb_dstn = 0; +static int sisfb_fstn = 0; +static int sisfb_tvplug = -1; /* Tv plug type (for overriding autodetection) */ +static int sisfb_tvstd = -1; +static int sisfb_tvxposoffset = 0; +static int sisfb_tvyposoffset = 0; +static int sisfb_nocrt2rate = 0; +#if !defined(__i386__) && !defined(__x86_64__) +static int sisfb_resetcard = 0; +static int sisfb_videoram = 0; +#endif + +/* List of supported chips */ +static struct sisfb_chip_info { + int chip; + int vgaengine; + int mni; + int hwcursor_size; + int CRT2_write_enable; + const char *chip_name; +} sisfb_chip_info[] = { + { SIS_300, SIS_300_VGA, 0, HW_CURSOR_AREA_SIZE_300 * 2, SIS_CRT2_WENABLE_300, "SiS 300/305" }, + { SIS_540, SIS_300_VGA, 0, HW_CURSOR_AREA_SIZE_300 * 2, SIS_CRT2_WENABLE_300, "SiS 540" }, + { SIS_630, SIS_300_VGA, 0, HW_CURSOR_AREA_SIZE_300 * 2, SIS_CRT2_WENABLE_300, "SiS 630" }, + { SIS_315H, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "SiS 315H" }, + { SIS_315, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "SiS 315" }, + { SIS_315PRO, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "SiS 315PRO" }, + { SIS_550, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "SiS 55x" }, + { SIS_650, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "SiS 650" }, + { SIS_330, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "SiS 330" }, + { SIS_660, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "SiS 660" }, + { XGI_20, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "XGI Z7" }, + { XGI_40, SIS_315_VGA, 1, HW_CURSOR_AREA_SIZE_315 * 4, SIS_CRT2_WENABLE_315, "XGI V3XT/V5/V8" }, +}; + +static struct pci_device_id sisfb_pci_table[] = { +#ifdef CONFIG_FB_SIS_300 + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_540_VGA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_630_VGA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 2}, +#endif +#ifdef CONFIG_FB_SIS_315 + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_315H, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 3}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_315, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 4}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_315PRO, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 5}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_550_VGA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 6}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_650_VGA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 7}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_330, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 8}, + { PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_660_VGA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 9}, + { PCI_VENDOR_ID_XGI,PCI_DEVICE_ID_XGI_20, PCI_ANY_ID, PCI_ANY_ID, 0, 0,10}, + { PCI_VENDOR_ID_XGI,PCI_DEVICE_ID_XGI_40, PCI_ANY_ID, PCI_ANY_ID, 0, 0,11}, +#endif + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, sisfb_pci_table); + +static struct sis_video_info *card_list = NULL; + +/* The memory heap is now handled card-wise, by using + sis_malloc_new/sis_free_new. However, the DRM does + not do this yet. Until it does, we keep a "global" + heap which is actually the first card's one. + */ +static struct SIS_HEAP *sisfb_heap; + +#define MD_SIS300 1 +#define MD_SIS315 2 + +/* Mode table */ +static const struct _sisbios_mode { + char name[15]; + u8 mode_no[2]; + u16 vesa_mode_no_1; /* "SiS defined" VESA mode number */ + u16 vesa_mode_no_2; /* Real VESA mode numbers */ + u16 xres; + u16 yres; + u16 bpp; + u16 rate_idx; + u16 cols; + u16 rows; + u8 chipset; +} sisbios_mode[] = { +/*0*/ {"none", {0xff,0xff}, 0x0000, 0x0000, 0, 0, 0, 0, 0, 0, MD_SIS300|MD_SIS315}, + {"320x200x8", {0x59,0x59}, 0x0138, 0x0000, 320, 200, 8, 1, 40, 12, MD_SIS300|MD_SIS315}, + {"320x200x16", {0x41,0x41}, 0x010e, 0x0000, 320, 200, 16, 1, 40, 12, MD_SIS300|MD_SIS315}, + {"320x200x24", {0x4f,0x4f}, 0x0000, 0x0000, 320, 200, 32, 1, 40, 12, MD_SIS300|MD_SIS315}, /* That's for people who mix up color- and fb depth */ + {"320x200x32", {0x4f,0x4f}, 0x0000, 0x0000, 320, 200, 32, 1, 40, 12, MD_SIS300|MD_SIS315}, + {"320x240x8", {0x50,0x50}, 0x0132, 0x0000, 320, 240, 8, 1, 40, 15, MD_SIS300|MD_SIS315}, + {"320x240x16", {0x56,0x56}, 0x0135, 0x0000, 320, 240, 16, 1, 40, 15, MD_SIS300|MD_SIS315}, + {"320x240x24", {0x53,0x53}, 0x0000, 0x0000, 320, 240, 32, 1, 40, 15, MD_SIS300|MD_SIS315}, + {"320x240x32", {0x53,0x53}, 0x0000, 0x0000, 320, 240, 32, 1, 40, 15, MD_SIS300|MD_SIS315}, +#define MODE_FSTN_8 9 +#define MODE_FSTN_16 10 + {"320x240x8", {0x5a,0x5a}, 0x0132, 0x0000, 320, 240, 8, 1, 40, 15, MD_SIS315}, /* FSTN */ +/*10*/ {"320x240x16", {0x5b,0x5b}, 0x0135, 0x0000, 320, 240, 16, 1, 40, 15, MD_SIS315}, /* FSTN */ + {"400x300x8", {0x51,0x51}, 0x0133, 0x0000, 400, 300, 8, 1, 50, 18, MD_SIS300|MD_SIS315}, + {"400x300x16", {0x57,0x57}, 0x0136, 0x0000, 400, 300, 16, 1, 50, 18, MD_SIS300|MD_SIS315}, + {"400x300x24", {0x54,0x54}, 0x0000, 0x0000, 400, 300, 32, 1, 50, 18, MD_SIS300|MD_SIS315}, + {"400x300x32", {0x54,0x54}, 0x0000, 0x0000, 400, 300, 32, 1, 50, 18, MD_SIS300|MD_SIS315}, + {"512x384x8", {0x52,0x52}, 0x0000, 0x0000, 512, 384, 8, 1, 64, 24, MD_SIS300|MD_SIS315}, + {"512x384x16", {0x58,0x58}, 0x0000, 0x0000, 512, 384, 16, 1, 64, 24, MD_SIS300|MD_SIS315}, + {"512x384x24", {0x5c,0x5c}, 0x0000, 0x0000, 512, 384, 32, 1, 64, 24, MD_SIS300|MD_SIS315}, + {"512x384x32", {0x5c,0x5c}, 0x0000, 0x0000, 512, 384, 32, 1, 64, 24, MD_SIS300|MD_SIS315}, + {"640x400x8", {0x2f,0x2f}, 0x0000, 0x0000, 640, 400, 8, 1, 80, 25, MD_SIS300|MD_SIS315}, +/*20*/ {"640x400x16", {0x5d,0x5d}, 0x0000, 0x0000, 640, 400, 16, 1, 80, 25, MD_SIS300|MD_SIS315}, + {"640x400x24", {0x5e,0x5e}, 0x0000, 0x0000, 640, 400, 32, 1, 80, 25, MD_SIS300|MD_SIS315}, + {"640x400x32", {0x5e,0x5e}, 0x0000, 0x0000, 640, 400, 32, 1, 80, 25, MD_SIS300|MD_SIS315}, + {"640x480x8", {0x2e,0x2e}, 0x0101, 0x0101, 640, 480, 8, 1, 80, 30, MD_SIS300|MD_SIS315}, + {"640x480x16", {0x44,0x44}, 0x0111, 0x0111, 640, 480, 16, 1, 80, 30, MD_SIS300|MD_SIS315}, + {"640x480x24", {0x62,0x62}, 0x013a, 0x0112, 640, 480, 32, 1, 80, 30, MD_SIS300|MD_SIS315}, + {"640x480x32", {0x62,0x62}, 0x013a, 0x0112, 640, 480, 32, 1, 80, 30, MD_SIS300|MD_SIS315}, + {"720x480x8", {0x31,0x31}, 0x0000, 0x0000, 720, 480, 8, 1, 90, 30, MD_SIS300|MD_SIS315}, + {"720x480x16", {0x33,0x33}, 0x0000, 0x0000, 720, 480, 16, 1, 90, 30, MD_SIS300|MD_SIS315}, + {"720x480x24", {0x35,0x35}, 0x0000, 0x0000, 720, 480, 32, 1, 90, 30, MD_SIS300|MD_SIS315}, +/*30*/ {"720x480x32", {0x35,0x35}, 0x0000, 0x0000, 720, 480, 32, 1, 90, 30, MD_SIS300|MD_SIS315}, + {"720x576x8", {0x32,0x32}, 0x0000, 0x0000, 720, 576, 8, 1, 90, 36, MD_SIS300|MD_SIS315}, + {"720x576x16", {0x34,0x34}, 0x0000, 0x0000, 720, 576, 16, 1, 90, 36, MD_SIS300|MD_SIS315}, + {"720x576x24", {0x36,0x36}, 0x0000, 0x0000, 720, 576, 32, 1, 90, 36, MD_SIS300|MD_SIS315}, + {"720x576x32", {0x36,0x36}, 0x0000, 0x0000, 720, 576, 32, 1, 90, 36, MD_SIS300|MD_SIS315}, + {"768x576x8", {0x5f,0x5f}, 0x0000, 0x0000, 768, 576, 8, 1, 96, 36, MD_SIS300|MD_SIS315}, + {"768x576x16", {0x60,0x60}, 0x0000, 0x0000, 768, 576, 16, 1, 96, 36, MD_SIS300|MD_SIS315}, + {"768x576x24", {0x61,0x61}, 0x0000, 0x0000, 768, 576, 32, 1, 96, 36, MD_SIS300|MD_SIS315}, + {"768x576x32", {0x61,0x61}, 0x0000, 0x0000, 768, 576, 32, 1, 96, 36, MD_SIS300|MD_SIS315}, + {"800x480x8", {0x70,0x70}, 0x0000, 0x0000, 800, 480, 8, 1, 100, 30, MD_SIS300|MD_SIS315}, +/*40*/ {"800x480x16", {0x7a,0x7a}, 0x0000, 0x0000, 800, 480, 16, 1, 100, 30, MD_SIS300|MD_SIS315}, + {"800x480x24", {0x76,0x76}, 0x0000, 0x0000, 800, 480, 32, 1, 100, 30, MD_SIS300|MD_SIS315}, + {"800x480x32", {0x76,0x76}, 0x0000, 0x0000, 800, 480, 32, 1, 100, 30, MD_SIS300|MD_SIS315}, +#define DEFAULT_MODE 43 /* index for 800x600x8 */ +#define DEFAULT_LCDMODE 43 /* index for 800x600x8 */ +#define DEFAULT_TVMODE 43 /* index for 800x600x8 */ + {"800x600x8", {0x30,0x30}, 0x0103, 0x0103, 800, 600, 8, 2, 100, 37, MD_SIS300|MD_SIS315}, + {"800x600x16", {0x47,0x47}, 0x0114, 0x0114, 800, 600, 16, 2, 100, 37, MD_SIS300|MD_SIS315}, + {"800x600x24", {0x63,0x63}, 0x013b, 0x0115, 800, 600, 32, 2, 100, 37, MD_SIS300|MD_SIS315}, + {"800x600x32", {0x63,0x63}, 0x013b, 0x0115, 800, 600, 32, 2, 100, 37, MD_SIS300|MD_SIS315}, + {"848x480x8", {0x39,0x39}, 0x0000, 0x0000, 848, 480, 8, 2, 106, 30, MD_SIS300|MD_SIS315}, +#define DEFAULT_MODE_848 48 + {"848x480x16", {0x3b,0x3b}, 0x0000, 0x0000, 848, 480, 16, 2, 106, 30, MD_SIS300|MD_SIS315}, + {"848x480x24", {0x3e,0x3e}, 0x0000, 0x0000, 848, 480, 32, 2, 106, 30, MD_SIS300|MD_SIS315}, +/*50*/ {"848x480x32", {0x3e,0x3e}, 0x0000, 0x0000, 848, 480, 32, 2, 106, 30, MD_SIS300|MD_SIS315}, + {"856x480x8", {0x3f,0x3f}, 0x0000, 0x0000, 856, 480, 8, 2, 107, 30, MD_SIS300|MD_SIS315}, +#define DEFAULT_MODE_856 52 + {"856x480x16", {0x42,0x42}, 0x0000, 0x0000, 856, 480, 16, 2, 107, 30, MD_SIS300|MD_SIS315}, + {"856x480x24", {0x45,0x45}, 0x0000, 0x0000, 856, 480, 32, 2, 107, 30, MD_SIS300|MD_SIS315}, + {"856x480x32", {0x45,0x45}, 0x0000, 0x0000, 856, 480, 32, 2, 107, 30, MD_SIS300|MD_SIS315}, + {"960x540x8", {0x1d,0x1d}, 0x0000, 0x0000, 960, 540, 8, 1, 120, 33, MD_SIS315}, + {"960x540x16", {0x1e,0x1e}, 0x0000, 0x0000, 960, 540, 16, 1, 120, 33, MD_SIS315}, + {"960x540x24", {0x1f,0x1f}, 0x0000, 0x0000, 960, 540, 32, 1, 120, 33, MD_SIS315}, + {"960x540x32", {0x1f,0x1f}, 0x0000, 0x0000, 960, 540, 32, 1, 120, 33, MD_SIS315}, + {"960x600x8", {0x20,0x20}, 0x0000, 0x0000, 960, 600, 8, 1, 120, 37, MD_SIS315}, +/*60*/ {"960x600x16", {0x21,0x21}, 0x0000, 0x0000, 960, 600, 16, 1, 120, 37, MD_SIS315}, + {"960x600x24", {0x22,0x22}, 0x0000, 0x0000, 960, 600, 32, 1, 120, 37, MD_SIS315}, + {"960x600x32", {0x22,0x22}, 0x0000, 0x0000, 960, 600, 32, 1, 120, 37, MD_SIS315}, + {"1024x576x8", {0x71,0x71}, 0x0000, 0x0000, 1024, 576, 8, 1, 128, 36, MD_SIS300|MD_SIS315}, + {"1024x576x16", {0x74,0x74}, 0x0000, 0x0000, 1024, 576, 16, 1, 128, 36, MD_SIS300|MD_SIS315}, + {"1024x576x24", {0x77,0x77}, 0x0000, 0x0000, 1024, 576, 32, 1, 128, 36, MD_SIS300|MD_SIS315}, + {"1024x576x32", {0x77,0x77}, 0x0000, 0x0000, 1024, 576, 32, 1, 128, 36, MD_SIS300|MD_SIS315}, + {"1024x600x8", {0x20,0x20}, 0x0000, 0x0000, 1024, 600, 8, 1, 128, 37, MD_SIS300 }, + {"1024x600x16", {0x21,0x21}, 0x0000, 0x0000, 1024, 600, 16, 1, 128, 37, MD_SIS300 }, + {"1024x600x24", {0x22,0x22}, 0x0000, 0x0000, 1024, 600, 32, 1, 128, 37, MD_SIS300 }, +/*70*/ {"1024x600x32", {0x22,0x22}, 0x0000, 0x0000, 1024, 600, 32, 1, 128, 37, MD_SIS300 }, + {"1024x768x8", {0x38,0x38}, 0x0105, 0x0105, 1024, 768, 8, 2, 128, 48, MD_SIS300|MD_SIS315}, + {"1024x768x16", {0x4a,0x4a}, 0x0117, 0x0117, 1024, 768, 16, 2, 128, 48, MD_SIS300|MD_SIS315}, + {"1024x768x24", {0x64,0x64}, 0x013c, 0x0118, 1024, 768, 32, 2, 128, 48, MD_SIS300|MD_SIS315}, + {"1024x768x32", {0x64,0x64}, 0x013c, 0x0118, 1024, 768, 32, 2, 128, 48, MD_SIS300|MD_SIS315}, + {"1152x768x8", {0x23,0x23}, 0x0000, 0x0000, 1152, 768, 8, 1, 144, 48, MD_SIS300 }, + {"1152x768x16", {0x24,0x24}, 0x0000, 0x0000, 1152, 768, 16, 1, 144, 48, MD_SIS300 }, + {"1152x768x24", {0x25,0x25}, 0x0000, 0x0000, 1152, 768, 32, 1, 144, 48, MD_SIS300 }, + {"1152x768x32", {0x25,0x25}, 0x0000, 0x0000, 1152, 768, 32, 1, 144, 48, MD_SIS300 }, + {"1152x864x8", {0x29,0x29}, 0x0000, 0x0000, 1152, 864, 8, 1, 144, 54, MD_SIS300|MD_SIS315}, +/*80*/ {"1152x864x16", {0x2a,0x2a}, 0x0000, 0x0000, 1152, 864, 16, 1, 144, 54, MD_SIS300|MD_SIS315}, + {"1152x864x24", {0x2b,0x2b}, 0x0000, 0x0000, 1152, 864, 32, 1, 144, 54, MD_SIS300|MD_SIS315}, + {"1152x864x32", {0x2b,0x2b}, 0x0000, 0x0000, 1152, 864, 32, 1, 144, 54, MD_SIS300|MD_SIS315}, + {"1280x720x8", {0x79,0x79}, 0x0000, 0x0000, 1280, 720, 8, 1, 160, 45, MD_SIS300|MD_SIS315}, + {"1280x720x16", {0x75,0x75}, 0x0000, 0x0000, 1280, 720, 16, 1, 160, 45, MD_SIS300|MD_SIS315}, + {"1280x720x24", {0x78,0x78}, 0x0000, 0x0000, 1280, 720, 32, 1, 160, 45, MD_SIS300|MD_SIS315}, + {"1280x720x32", {0x78,0x78}, 0x0000, 0x0000, 1280, 720, 32, 1, 160, 45, MD_SIS300|MD_SIS315}, + {"1280x768x8", {0x55,0x23}, 0x0000, 0x0000, 1280, 768, 8, 1, 160, 48, MD_SIS300|MD_SIS315}, + {"1280x768x16", {0x5a,0x24}, 0x0000, 0x0000, 1280, 768, 16, 1, 160, 48, MD_SIS300|MD_SIS315}, + {"1280x768x24", {0x5b,0x25}, 0x0000, 0x0000, 1280, 768, 32, 1, 160, 48, MD_SIS300|MD_SIS315}, +/*90*/ {"1280x768x32", {0x5b,0x25}, 0x0000, 0x0000, 1280, 768, 32, 1, 160, 48, MD_SIS300|MD_SIS315}, + {"1280x800x8", {0x14,0x14}, 0x0000, 0x0000, 1280, 800, 8, 1, 160, 50, MD_SIS315}, + {"1280x800x16", {0x15,0x15}, 0x0000, 0x0000, 1280, 800, 16, 1, 160, 50, MD_SIS315}, + {"1280x800x24", {0x16,0x16}, 0x0000, 0x0000, 1280, 800, 32, 1, 160, 50, MD_SIS315}, + {"1280x800x32", {0x16,0x16}, 0x0000, 0x0000, 1280, 800, 32, 1, 160, 50, MD_SIS315}, + {"1280x854x8", {0x14,0x14}, 0x0000, 0x0000, 1280, 854, 8, 1, 160, 53, MD_SIS315}, + {"1280x854x16", {0x15,0x15}, 0x0000, 0x0000, 1280, 854, 16, 1, 160, 53, MD_SIS315}, + {"1280x854x24", {0x16,0x16}, 0x0000, 0x0000, 1280, 854, 32, 1, 160, 53, MD_SIS315}, + {"1280x854x32", {0x16,0x16}, 0x0000, 0x0000, 1280, 854, 32, 1, 160, 53, MD_SIS315}, + {"1280x960x8", {0x7c,0x7c}, 0x0000, 0x0000, 1280, 960, 8, 1, 160, 60, MD_SIS300|MD_SIS315}, +/*100*/ {"1280x960x16", {0x7d,0x7d}, 0x0000, 0x0000, 1280, 960, 16, 1, 160, 60, MD_SIS300|MD_SIS315}, + {"1280x960x24", {0x7e,0x7e}, 0x0000, 0x0000, 1280, 960, 32, 1, 160, 60, MD_SIS300|MD_SIS315}, + {"1280x960x32", {0x7e,0x7e}, 0x0000, 0x0000, 1280, 960, 32, 1, 160, 60, MD_SIS300|MD_SIS315}, + {"1280x1024x8", {0x3a,0x3a}, 0x0107, 0x0107, 1280, 1024, 8, 2, 160, 64, MD_SIS300|MD_SIS315}, + {"1280x1024x16", {0x4d,0x4d}, 0x011a, 0x011a, 1280, 1024, 16, 2, 160, 64, MD_SIS300|MD_SIS315}, + {"1280x1024x24", {0x65,0x65}, 0x013d, 0x011b, 1280, 1024, 32, 2, 160, 64, MD_SIS300|MD_SIS315}, + {"1280x1024x32", {0x65,0x65}, 0x013d, 0x011b, 1280, 1024, 32, 2, 160, 64, MD_SIS300|MD_SIS315}, + {"1360x768x8", {0x48,0x48}, 0x0000, 0x0000, 1360, 768, 8, 1, 170, 48, MD_SIS300|MD_SIS315}, + {"1360x768x16", {0x4b,0x4b}, 0x0000, 0x0000, 1360, 768, 16, 1, 170, 48, MD_SIS300|MD_SIS315}, + {"1360x768x24", {0x4e,0x4e}, 0x0000, 0x0000, 1360, 768, 32, 1, 170, 48, MD_SIS300|MD_SIS315}, +/*110*/ {"1360x768x32", {0x4e,0x4e}, 0x0000, 0x0000, 1360, 768, 32, 1, 170, 48, MD_SIS300|MD_SIS315}, + {"1360x1024x8", {0x67,0x67}, 0x0000, 0x0000, 1360, 1024, 8, 1, 170, 64, MD_SIS300 }, +#define DEFAULT_MODE_1360 112 + {"1360x1024x16", {0x6f,0x6f}, 0x0000, 0x0000, 1360, 1024, 16, 1, 170, 64, MD_SIS300 }, + {"1360x1024x24", {0x72,0x72}, 0x0000, 0x0000, 1360, 1024, 32, 1, 170, 64, MD_SIS300 }, + {"1360x1024x32", {0x72,0x72}, 0x0000, 0x0000, 1360, 1024, 32, 1, 170, 64, MD_SIS300 }, + {"1400x1050x8", {0x26,0x26}, 0x0000, 0x0000, 1400, 1050, 8, 1, 175, 65, MD_SIS315}, + {"1400x1050x16", {0x27,0x27}, 0x0000, 0x0000, 1400, 1050, 16, 1, 175, 65, MD_SIS315}, + {"1400x1050x24", {0x28,0x28}, 0x0000, 0x0000, 1400, 1050, 32, 1, 175, 65, MD_SIS315}, + {"1400x1050x32", {0x28,0x28}, 0x0000, 0x0000, 1400, 1050, 32, 1, 175, 65, MD_SIS315}, + {"1600x1200x8", {0x3c,0x3c}, 0x0130, 0x011c, 1600, 1200, 8, 1, 200, 75, MD_SIS300|MD_SIS315}, +/*120*/ {"1600x1200x16", {0x3d,0x3d}, 0x0131, 0x011e, 1600, 1200, 16, 1, 200, 75, MD_SIS300|MD_SIS315}, + {"1600x1200x24", {0x66,0x66}, 0x013e, 0x011f, 1600, 1200, 32, 1, 200, 75, MD_SIS300|MD_SIS315}, + {"1600x1200x32", {0x66,0x66}, 0x013e, 0x011f, 1600, 1200, 32, 1, 200, 75, MD_SIS300|MD_SIS315}, + {"1680x1050x8", {0x17,0x17}, 0x0000, 0x0000, 1680, 1050, 8, 1, 210, 65, MD_SIS315}, + {"1680x1050x16", {0x18,0x18}, 0x0000, 0x0000, 1680, 1050, 16, 1, 210, 65, MD_SIS315}, + {"1680x1050x24", {0x19,0x19}, 0x0000, 0x0000, 1680, 1050, 32, 1, 210, 65, MD_SIS315}, + {"1680x1050x32", {0x19,0x19}, 0x0000, 0x0000, 1680, 1050, 32, 1, 210, 65, MD_SIS315}, + {"1920x1080x8", {0x2c,0x2c}, 0x0000, 0x0000, 1920, 1080, 8, 1, 240, 67, MD_SIS315}, + {"1920x1080x16", {0x2d,0x2d}, 0x0000, 0x0000, 1920, 1080, 16, 1, 240, 67, MD_SIS315}, + {"1920x1080x24", {0x73,0x73}, 0x0000, 0x0000, 1920, 1080, 32, 1, 240, 67, MD_SIS315}, +/*130*/ {"1920x1080x32", {0x73,0x73}, 0x0000, 0x0000, 1920, 1080, 32, 1, 240, 67, MD_SIS315}, + {"1920x1440x8", {0x68,0x68}, 0x013f, 0x0000, 1920, 1440, 8, 1, 240, 75, MD_SIS300|MD_SIS315}, + {"1920x1440x16", {0x69,0x69}, 0x0140, 0x0000, 1920, 1440, 16, 1, 240, 75, MD_SIS300|MD_SIS315}, + {"1920x1440x24", {0x6b,0x6b}, 0x0141, 0x0000, 1920, 1440, 32, 1, 240, 75, MD_SIS300|MD_SIS315}, + {"1920x1440x32", {0x6b,0x6b}, 0x0141, 0x0000, 1920, 1440, 32, 1, 240, 75, MD_SIS300|MD_SIS315}, + {"2048x1536x8", {0x6c,0x6c}, 0x0000, 0x0000, 2048, 1536, 8, 1, 256, 96, MD_SIS315}, + {"2048x1536x16", {0x6d,0x6d}, 0x0000, 0x0000, 2048, 1536, 16, 1, 256, 96, MD_SIS315}, + {"2048x1536x24", {0x6e,0x6e}, 0x0000, 0x0000, 2048, 1536, 32, 1, 256, 96, MD_SIS315}, + {"2048x1536x32", {0x6e,0x6e}, 0x0000, 0x0000, 2048, 1536, 32, 1, 256, 96, MD_SIS315}, + {"\0", {0x00,0x00}, 0, 0, 0, 0, 0, 0, 0} +}; + +#define SIS_LCD_NUMBER 18 +static struct _sis_lcd_data { + u32 lcdtype; + u16 xres; + u16 yres; + u8 default_mode_idx; +} sis_lcd_data[] = { + { LCD_640x480, 640, 480, 23 }, + { LCD_800x600, 800, 600, 43 }, + { LCD_1024x600, 1024, 600, 67 }, + { LCD_1024x768, 1024, 768, 71 }, + { LCD_1152x768, 1152, 768, 75 }, + { LCD_1152x864, 1152, 864, 79 }, + { LCD_1280x720, 1280, 720, 83 }, + { LCD_1280x768, 1280, 768, 87 }, + { LCD_1280x800, 1280, 800, 91 }, + { LCD_1280x854, 1280, 854, 95 }, + { LCD_1280x960, 1280, 960, 99 }, + { LCD_1280x1024, 1280, 1024, 103 }, + { LCD_1400x1050, 1400, 1050, 115 }, + { LCD_1680x1050, 1680, 1050, 123 }, + { LCD_1600x1200, 1600, 1200, 119 }, + { LCD_320x240_2, 320, 240, 9 }, + { LCD_320x240_3, 320, 240, 9 }, + { LCD_320x240, 320, 240, 9 }, +}; + +/* CR36 evaluation */ +static unsigned short sis300paneltype[] = { + LCD_UNKNOWN, LCD_800x600, LCD_1024x768, LCD_1280x1024, + LCD_1280x960, LCD_640x480, LCD_1024x600, LCD_1152x768, + LCD_UNKNOWN, LCD_UNKNOWN, LCD_UNKNOWN, LCD_UNKNOWN, + LCD_UNKNOWN, LCD_UNKNOWN, LCD_UNKNOWN, LCD_UNKNOWN +}; + +static unsigned short sis310paneltype[] = { + LCD_UNKNOWN, LCD_800x600, LCD_1024x768, LCD_1280x1024, + LCD_640x480, LCD_1024x600, LCD_1152x864, LCD_1280x960, + LCD_1152x768, LCD_1400x1050, LCD_1280x768, LCD_1600x1200, + LCD_320x240_2, LCD_320x240_3, LCD_UNKNOWN, LCD_UNKNOWN +}; + +static unsigned short sis661paneltype[] = { + LCD_UNKNOWN, LCD_800x600, LCD_1024x768, LCD_1280x1024, + LCD_640x480, LCD_1024x600, LCD_1152x864, LCD_1280x960, + LCD_1280x854, LCD_1400x1050, LCD_1280x768, LCD_1600x1200, + LCD_1280x800, LCD_1680x1050, LCD_1280x720, LCD_UNKNOWN +}; + +#define FL_550_DSTN 0x01 +#define FL_550_FSTN 0x02 +#define FL_300 0x04 +#define FL_315 0x08 + +static struct _sis_crt2type { + char name[32]; + u32 type_no; + u32 tvplug_no; + u16 flags; +} sis_crt2type[] __initdata = { + {"NONE", 0, -1, FL_300|FL_315}, + {"LCD", CRT2_LCD, -1, FL_300|FL_315}, + {"TV", CRT2_TV, -1, FL_300|FL_315}, + {"VGA", CRT2_VGA, -1, FL_300|FL_315}, + {"SVIDEO", CRT2_TV, TV_SVIDEO, FL_300|FL_315}, + {"COMPOSITE", CRT2_TV, TV_AVIDEO, FL_300|FL_315}, + {"CVBS", CRT2_TV, TV_AVIDEO, FL_300|FL_315}, + {"SVIDEO+COMPOSITE", CRT2_TV, TV_AVIDEO|TV_SVIDEO, FL_300|FL_315}, + {"COMPOSITE+SVIDEO", CRT2_TV, TV_AVIDEO|TV_SVIDEO, FL_300|FL_315}, + {"SVIDEO+CVBS", CRT2_TV, TV_AVIDEO|TV_SVIDEO, FL_300|FL_315}, + {"CVBS+SVIDEO", CRT2_TV, TV_AVIDEO|TV_SVIDEO, FL_300|FL_315}, + {"SCART", CRT2_TV, TV_SCART, FL_300|FL_315}, + {"HIVISION", CRT2_TV, TV_HIVISION, FL_315}, + {"YPBPR480I", CRT2_TV, TV_YPBPR|TV_YPBPR525I, FL_315}, + {"YPBPR480P", CRT2_TV, TV_YPBPR|TV_YPBPR525P, FL_315}, + {"YPBPR720P", CRT2_TV, TV_YPBPR|TV_YPBPR750P, FL_315}, + {"YPBPR1080I", CRT2_TV, TV_YPBPR|TV_YPBPR1080I, FL_315}, + {"DSTN", CRT2_LCD, -1, FL_315|FL_550_DSTN}, + {"FSTN", CRT2_LCD, -1, FL_315|FL_550_FSTN}, + {"\0", -1, -1, 0} +}; + +/* TV standard */ +static struct _sis_tvtype { + char name[6]; + u32 type_no; +} sis_tvtype[] __initdata = { + {"PAL", TV_PAL}, + {"NTSC", TV_NTSC}, + {"PALM", TV_PAL|TV_PALM}, + {"PALN", TV_PAL|TV_PALN}, + {"NTSCJ", TV_NTSC|TV_NTSCJ}, + {"\0", -1} +}; + +static const struct _sis_vrate { + u16 idx; + u16 xres; + u16 yres; + u16 refresh; + bool SiS730valid32bpp; +} sisfb_vrate[] = { + {1, 320, 200, 70, true}, + {1, 320, 240, 60, true}, + {1, 400, 300, 60, true}, + {1, 512, 384, 60, true}, + {1, 640, 400, 72, true}, + {1, 640, 480, 60, true}, {2, 640, 480, 72, true}, {3, 640, 480, 75, true}, + {4, 640, 480, 85, true}, {5, 640, 480, 100, true}, {6, 640, 480, 120, true}, + {7, 640, 480, 160, true}, {8, 640, 480, 200, true}, + {1, 720, 480, 60, true}, + {1, 720, 576, 58, true}, + {1, 768, 576, 58, true}, + {1, 800, 480, 60, true}, {2, 800, 480, 75, true}, {3, 800, 480, 85, true}, + {1, 800, 600, 56, true}, {2, 800, 600, 60, true}, {3, 800, 600, 72, true}, + {4, 800, 600, 75, true}, {5, 800, 600, 85, true}, {6, 800, 600, 105, true}, + {7, 800, 600, 120, true}, {8, 800, 600, 160, true}, + {1, 848, 480, 39, true}, {2, 848, 480, 60, true}, + {1, 856, 480, 39, true}, {2, 856, 480, 60, true}, + {1, 960, 540, 60, true}, + {1, 960, 600, 60, true}, + {1, 1024, 576, 60, true}, {2, 1024, 576, 75, true}, {3, 1024, 576, 85, true}, + {1, 1024, 600, 60, true}, + {1, 1024, 768, 43, true}, {2, 1024, 768, 60, true}, {3, 1024, 768, 70, false}, + {4, 1024, 768, 75, false}, {5, 1024, 768, 85, true}, {6, 1024, 768, 100, true}, + {7, 1024, 768, 120, true}, + {1, 1152, 768, 60, true}, + {1, 1152, 864, 60, true}, {2, 1152, 864, 75, true}, {3, 1152, 864, 84, true}, + {1, 1280, 720, 60, true}, {2, 1280, 720, 75, true}, {3, 1280, 720, 85, true}, + {1, 1280, 768, 60, true}, + {1, 1280, 800, 60, true}, + {1, 1280, 854, 60, true}, + {1, 1280, 960, 60, true}, {2, 1280, 960, 85, true}, + {1, 1280, 1024, 43, true}, {2, 1280, 1024, 60, true}, {3, 1280, 1024, 75, true}, + {4, 1280, 1024, 85, true}, + {1, 1360, 768, 60, true}, + {1, 1360, 1024, 59, true}, + {1, 1400, 1050, 60, true}, {2, 1400, 1050, 75, true}, + {1, 1600, 1200, 60, true}, {2, 1600, 1200, 65, true}, {3, 1600, 1200, 70, true}, + {4, 1600, 1200, 75, true}, {5, 1600, 1200, 85, true}, {6, 1600, 1200, 100, true}, + {7, 1600, 1200, 120, true}, + {1, 1680, 1050, 60, true}, + {1, 1920, 1080, 30, true}, + {1, 1920, 1440, 60, true}, {2, 1920, 1440, 65, true}, {3, 1920, 1440, 70, true}, + {4, 1920, 1440, 75, true}, {5, 1920, 1440, 85, true}, {6, 1920, 1440, 100, true}, + {1, 2048, 1536, 60, true}, {2, 2048, 1536, 65, true}, {3, 2048, 1536, 70, true}, + {4, 2048, 1536, 75, true}, {5, 2048, 1536, 85, true}, + {0, 0, 0, 0, false} +}; + +static struct _sisfbddcsmodes { + u32 mask; + u16 h; + u16 v; + u32 d; +} sisfb_ddcsmodes[] = { + { 0x10000, 67, 75, 108000}, + { 0x08000, 48, 72, 50000}, + { 0x04000, 46, 75, 49500}, + { 0x01000, 35, 43, 44900}, + { 0x00800, 48, 60, 65000}, + { 0x00400, 56, 70, 75000}, + { 0x00200, 60, 75, 78800}, + { 0x00100, 80, 75, 135000}, + { 0x00020, 31, 60, 25200}, + { 0x00008, 38, 72, 31500}, + { 0x00004, 37, 75, 31500}, + { 0x00002, 35, 56, 36000}, + { 0x00001, 38, 60, 40000} +}; + +static struct _sisfbddcfmodes { + u16 x; + u16 y; + u16 v; + u16 h; + u32 d; +} sisfb_ddcfmodes[] = { + { 1280, 1024, 85, 92, 157500}, + { 1600, 1200, 60, 75, 162000}, + { 1600, 1200, 65, 82, 175500}, + { 1600, 1200, 70, 88, 189000}, + { 1600, 1200, 75, 94, 202500}, + { 1600, 1200, 85, 107,229500}, + { 1920, 1440, 60, 90, 234000}, + { 1920, 1440, 75, 113,297000} +}; + +#ifdef CONFIG_FB_SIS_300 +static struct _chswtable { + u16 subsysVendor; + u16 subsysCard; + char *vendorName; + char *cardName; +} mychswtable[] = { + { 0x1631, 0x1002, "Mitachi", "0x1002" }, + { 0x1071, 0x7521, "Mitac" , "7521P" }, + { 0, 0, "" , "" } +}; +#endif + +static struct _customttable { + u16 chipID; + char *biosversion; + char *biosdate; + u32 bioschksum; + u16 biosFootprintAddr[5]; + u8 biosFootprintData[5]; + u16 pcisubsysvendor; + u16 pcisubsyscard; + char *vendorName; + char *cardName; + u32 SpecialID; + char *optionName; +} mycustomttable[] = { + { SIS_630, "2.00.07", "09/27/2002-13:38:25", + 0x3240A8, + { 0x220, 0x227, 0x228, 0x229, 0x0ee }, + { 0x01, 0xe3, 0x9a, 0x6a, 0xef }, + 0x1039, 0x6300, + "Barco", "iQ R200L/300/400", CUT_BARCO1366, "BARCO_1366" + }, + { SIS_630, "2.00.07", "09/27/2002-13:38:25", + 0x323FBD, + { 0x220, 0x227, 0x228, 0x229, 0x0ee }, + { 0x00, 0x5a, 0x64, 0x41, 0xef }, + 0x1039, 0x6300, + "Barco", "iQ G200L/300/400/500", CUT_BARCO1024, "BARCO_1024" + }, + { SIS_650, "", "", + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x0e11, 0x083c, + "Inventec (Compaq)", "3017cl/3045US", CUT_COMPAQ12802, "COMPAQ_1280" + }, + { SIS_650, "", "", + 0, + { 0x00c, 0, 0, 0, 0 }, + { 'e' , 0, 0, 0, 0 }, + 0x1558, 0x0287, + "Clevo", "L285/L287 (Version 1)", CUT_CLEVO1024, "CLEVO_L28X_1" + }, + { SIS_650, "", "", + 0, + { 0x00c, 0, 0, 0, 0 }, + { 'y' , 0, 0, 0, 0 }, + 0x1558, 0x0287, + "Clevo", "L285/L287 (Version 2)", CUT_CLEVO10242, "CLEVO_L28X_2" + }, + { SIS_650, "", "", + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1558, 0x0400, /* possibly 401 and 402 as well; not panelsize specific (?) */ + "Clevo", "D400S/D410S/D400H/D410H", CUT_CLEVO1400, "CLEVO_D4X0" + }, + { SIS_650, "", "", + 0, /* Shift LCD in LCD-via-CRT1 mode */ + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1558, 0x2263, + "Clevo", "D22ES/D27ES", CUT_UNIWILL1024, "CLEVO_D2X0ES" + }, + { SIS_650, "", "", + 0, /* Shift LCD in LCD-via-CRT1 mode */ + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1734, 0x101f, + "Uniwill", "N243S9", CUT_UNIWILL1024, "UNIWILL_N243S9" + }, + { SIS_650, "", "", + 0, /* Shift LCD in LCD-via-CRT1 mode */ + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1584, 0x5103, + "Uniwill", "N35BS1", CUT_UNIWILL10242, "UNIWILL_N35BS1" + }, + { SIS_650, "1.09.2c", "", /* Other versions, too? */ + 0, /* Shift LCD in LCD-via-CRT1 mode */ + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1019, 0x0f05, + "ECS", "A928", CUT_UNIWILL1024, "ECS_A928" + }, + { SIS_740, "1.11.27a", "", + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1043, 0x1612, + "Asus", "L3000D/L3500D", CUT_ASUSL3000D, "ASUS_L3X00" + }, + { SIS_650, "1.10.9k", "", + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1025, 0x0028, + "Acer", "Aspire 1700", CUT_ACER1280, "ACER_ASPIRE1700" + }, + { SIS_650, "1.10.7w", "", + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x14c0, 0x0012, + "Compal", "??? (V1)", CUT_COMPAL1400_1, "COMPAL_1400_1" + }, + { SIS_650, "1.10.7x", "", + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x14c0, 0x0012, + "Compal", "??? (V2)", CUT_COMPAL1400_2, "COMPAL_1400_2" + }, + { SIS_650, "1.10.8o", "", + 0, /* For EMI (unknown) */ + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1043, 0x1612, + "Asus", "A2H (V1)", CUT_ASUSA2H_1, "ASUS_A2H_1" + }, + { SIS_650, "1.10.8q", "", + 0, /* For EMI */ + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0x1043, 0x1612, + "Asus", "A2H (V2)", CUT_ASUSA2H_2, "ASUS_A2H_2" + }, + { 4321, "", "", /* never autodetected */ + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0, 0, + "Generic", "LVDS/Parallel 848x480", CUT_PANEL848, "PANEL848x480" + }, + { 4322, "", "", /* never autodetected */ + 0, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + 0, 0, + "Generic", "LVDS/Parallel 856x480", CUT_PANEL856, "PANEL856x480" + }, + { 0, "", "", + 0, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + 0, 0, + "", "", CUT_NONE, "" + } +}; + +/* ---------------------- Prototypes ------------------------- */ + +/* Interface used by the world */ +#ifndef MODULE +static int sisfb_setup(char *options); +#endif + +/* Interface to the low level console driver */ +static int sisfb_init(void); + +/* fbdev routines */ +static int sisfb_get_fix(struct fb_fix_screeninfo *fix, int con, + struct fb_info *info); + +static int sisfb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); +static int sisfb_set_par(struct fb_info *info); +static int sisfb_blank(int blank, + struct fb_info *info); +extern void fbcon_sis_fillrect(struct fb_info *info, + const struct fb_fillrect *rect); +extern void fbcon_sis_copyarea(struct fb_info *info, + const struct fb_copyarea *area); +extern int fbcon_sis_sync(struct fb_info *info); + +/* Internal 2D accelerator functions */ +extern int sisfb_initaccel(struct sis_video_info *ivideo); +extern void sisfb_syncaccel(struct sis_video_info *ivideo); + +/* Internal general routines */ +static void sisfb_search_mode(char *name, bool quiet); +static int sisfb_validate_mode(struct sis_video_info *ivideo, int modeindex, u32 vbflags); +static u8 sisfb_search_refresh_rate(struct sis_video_info *ivideo, unsigned int rate, + int index); +static int sisfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *fb_info); +static int sisfb_do_set_var(struct fb_var_screeninfo *var, int isactive, + struct fb_info *info); +static void sisfb_pre_setmode(struct sis_video_info *ivideo); +static void sisfb_post_setmode(struct sis_video_info *ivideo); +static bool sisfb_CheckVBRetrace(struct sis_video_info *ivideo); +static bool sisfbcheckvretracecrt2(struct sis_video_info *ivideo); +static bool sisfbcheckvretracecrt1(struct sis_video_info *ivideo); +static bool sisfb_bridgeisslave(struct sis_video_info *ivideo); +static void sisfb_detect_VB_connect(struct sis_video_info *ivideo); +static void sisfb_get_VB_type(struct sis_video_info *ivideo); +static void sisfb_set_TVxposoffset(struct sis_video_info *ivideo, int val); +static void sisfb_set_TVyposoffset(struct sis_video_info *ivideo, int val); +#ifdef CONFIG_FB_SIS_300 +unsigned int sisfb_read_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg); +void sisfb_write_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg, unsigned int val); +unsigned int sisfb_read_lpc_pci_dword(struct SiS_Private *SiS_Pr, int reg); +#endif +#ifdef CONFIG_FB_SIS_315 +void sisfb_write_nbridge_pci_byte(struct SiS_Private *SiS_Pr, int reg, unsigned char val); +unsigned int sisfb_read_mio_pci_word(struct SiS_Private *SiS_Pr, int reg); +#endif + +/* SiS-specific exported functions */ +void sis_malloc(struct sis_memreq *req); +void sis_malloc_new(struct pci_dev *pdev, struct sis_memreq *req); +void sis_free(u32 base); +void sis_free_new(struct pci_dev *pdev, u32 base); + +/* Internal heap routines */ +static int sisfb_heap_init(struct sis_video_info *ivideo); +static struct SIS_OH * sisfb_poh_new_node(struct SIS_HEAP *memheap); +static struct SIS_OH * sisfb_poh_allocate(struct SIS_HEAP *memheap, u32 size); +static void sisfb_delete_node(struct SIS_OH *poh); +static void sisfb_insert_node(struct SIS_OH *pohList, struct SIS_OH *poh); +static struct SIS_OH * sisfb_poh_free(struct SIS_HEAP *memheap, u32 base); +static void sisfb_free_node(struct SIS_HEAP *memheap, struct SIS_OH *poh); + +/* Routines from init.c/init301.c */ +extern unsigned short SiS_GetModeID_LCD(int VGAEngine, unsigned int VBFlags, int HDisplay, + int VDisplay, int Depth, bool FSTN, unsigned short CustomT, + int LCDwith, int LCDheight, unsigned int VBFlags2); +extern unsigned short SiS_GetModeID_TV(int VGAEngine, unsigned int VBFlags, int HDisplay, + int VDisplay, int Depth, unsigned int VBFlags2); +extern unsigned short SiS_GetModeID_VGA2(int VGAEngine, unsigned int VBFlags, int HDisplay, + int VDisplay, int Depth, unsigned int VBFlags2); +extern void SiSRegInit(struct SiS_Private *SiS_Pr, SISIOADDRESS BaseAddr); +extern bool SiSSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo); +extern void SiS_SetEnableDstn(struct SiS_Private *SiS_Pr, int enable); +extern void SiS_SetEnableFstn(struct SiS_Private *SiS_Pr, int enable); + +extern bool SiSDetermineROMLayout661(struct SiS_Private *SiS_Pr); + +extern bool sisfb_gettotalfrommode(struct SiS_Private *SiS_Pr, unsigned char modeno, + int *htotal, int *vtotal, unsigned char rateindex); +extern int sisfb_mode_rate_to_dclock(struct SiS_Private *SiS_Pr, + unsigned char modeno, unsigned char rateindex); +extern int sisfb_mode_rate_to_ddata(struct SiS_Private *SiS_Pr, unsigned char modeno, + unsigned char rateindex, struct fb_var_screeninfo *var); + +/* Chrontel TV, DDC and DPMS functions */ +extern unsigned short SiS_GetCH700x(struct SiS_Private *SiS_Pr, unsigned short reg); +extern void SiS_SetCH700x(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val); +extern unsigned short SiS_GetCH701x(struct SiS_Private *SiS_Pr, unsigned short reg); +extern void SiS_SetCH701x(struct SiS_Private *SiS_Pr, unsigned short reg, unsigned char val); +extern void SiS_SetCH70xxANDOR(struct SiS_Private *SiS_Pr, unsigned short reg, + unsigned char myor, unsigned char myand); +extern void SiS_DDC2Delay(struct SiS_Private *SiS_Pr, unsigned int delaytime); +extern void SiS_SetChrontelGPIO(struct SiS_Private *SiS_Pr, unsigned short myvbinfo); +extern unsigned short SiS_HandleDDC(struct SiS_Private *SiS_Pr, unsigned int VBFlags, int VGAEngine, + unsigned short adaptnum, unsigned short DDCdatatype, unsigned char *buffer, + unsigned int VBFlags2); +extern unsigned short SiS_ReadDDC1Bit(struct SiS_Private *SiS_Pr); +#ifdef CONFIG_FB_SIS_315 +extern void SiS_Chrontel701xBLOn(struct SiS_Private *SiS_Pr); +extern void SiS_Chrontel701xBLOff(struct SiS_Private *SiS_Pr); +#endif +extern void SiS_SiS30xBLOn(struct SiS_Private *SiS_Pr); +extern void SiS_SiS30xBLOff(struct SiS_Private *SiS_Pr); +#endif + + diff --git a/drivers/video/fbdev/sis/vgatypes.h b/drivers/video/fbdev/sis/vgatypes.h new file mode 100644 index 000000000000..e3f9976cfef0 --- /dev/null +++ b/drivers/video/fbdev/sis/vgatypes.h @@ -0,0 +1,97 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * General type definitions for universal mode switching modules + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +#ifndef _VGATYPES_H_ +#define _VGATYPES_H_ + +#define SISIOMEMTYPE + +typedef unsigned long SISIOADDRESS; +#include <linux/types.h> /* Need __iomem */ +#undef SISIOMEMTYPE +#define SISIOMEMTYPE __iomem + +typedef enum _SIS_CHIP_TYPE { + SIS_VGALegacy = 0, + SIS_530, + SIS_OLD, + SIS_300, + SIS_630, + SIS_730, + SIS_540, + SIS_315H, /* SiS 310 */ + SIS_315, + SIS_315PRO, /* SiS 325 */ + SIS_550, + SIS_650, + SIS_740, + SIS_330, + SIS_661, + SIS_741, + SIS_670, + SIS_660 = 35, + SIS_760, + SIS_761, + SIS_762, + SIS_770, + SIS_340 = 55, + SIS_341, + SIS_342, + XGI_20 = 75, + XGI_21, + XGI_40, + MAX_SIS_CHIP +} SIS_CHIP_TYPE; + + +#endif + diff --git a/drivers/video/fbdev/sis/vstruct.h b/drivers/video/fbdev/sis/vstruct.h new file mode 100644 index 000000000000..ea94d214dcff --- /dev/null +++ b/drivers/video/fbdev/sis/vstruct.h @@ -0,0 +1,551 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * General structure definitions for universal mode switching modules + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * 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 named License, + * * or 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 + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer <thomas@winischhofer.net> + * + */ + +#ifndef _VSTRUCT_H_ +#define _VSTRUCT_H_ + +struct SiS_PanelDelayTbl { + unsigned char timer[2]; +}; + +struct SiS_LCDData { + unsigned short RVBHCMAX; + unsigned short RVBHCFACT; + unsigned short VGAHT; + unsigned short VGAVT; + unsigned short LCDHT; + unsigned short LCDVT; +}; + +struct SiS_TVData { + unsigned short RVBHCMAX; + unsigned short RVBHCFACT; + unsigned short VGAHT; + unsigned short VGAVT; + unsigned short TVHDE; + unsigned short TVVDE; + unsigned short RVBHRS; + unsigned char FlickerMode; + unsigned short HALFRVBHRS; + unsigned short RVBHRS2; + unsigned char RY1COE; + unsigned char RY2COE; + unsigned char RY3COE; + unsigned char RY4COE; +}; + +struct SiS_LVDSData { + unsigned short VGAHT; + unsigned short VGAVT; + unsigned short LCDHT; + unsigned short LCDVT; +}; + +struct SiS_LVDSDes { + unsigned short LCDHDES; + unsigned short LCDVDES; +}; + +struct SiS_LVDSCRT1Data { + unsigned char CR[15]; +}; + +struct SiS_CHTVRegData { + unsigned char Reg[16]; +}; + +struct SiS_St { + unsigned char St_ModeID; + unsigned short St_ModeFlag; + unsigned char St_StTableIndex; + unsigned char St_CRT2CRTC; + unsigned char St_ResInfo; + unsigned char VB_StTVFlickerIndex; + unsigned char VB_StTVEdgeIndex; + unsigned char VB_StTVYFilterIndex; + unsigned char St_PDC; +}; + +struct SiS_VBMode { + unsigned char ModeID; + unsigned char VB_TVDelayIndex; + unsigned char VB_TVFlickerIndex; + unsigned char VB_TVPhaseIndex; + unsigned char VB_TVYFilterIndex; + unsigned char VB_LCDDelayIndex; + unsigned char _VB_LCDHIndex; + unsigned char _VB_LCDVIndex; +}; + +struct SiS_StandTable_S { + unsigned char CRT_COLS; + unsigned char ROWS; + unsigned char CHAR_HEIGHT; + unsigned short CRT_LEN; + unsigned char SR[4]; + unsigned char MISC; + unsigned char CRTC[0x19]; + unsigned char ATTR[0x14]; + unsigned char GRC[9]; +}; + +struct SiS_Ext { + unsigned char Ext_ModeID; + unsigned short Ext_ModeFlag; + unsigned short Ext_VESAID; + unsigned char Ext_RESINFO; + unsigned char VB_ExtTVFlickerIndex; + unsigned char VB_ExtTVEdgeIndex; + unsigned char VB_ExtTVYFilterIndex; + unsigned char VB_ExtTVYFilterIndexROM661; + unsigned char REFindex; + char ROMMODEIDX661; +}; + +struct SiS_Ext2 { + unsigned short Ext_InfoFlag; + unsigned char Ext_CRT1CRTC; + unsigned char Ext_CRTVCLK; + unsigned char Ext_CRT2CRTC; + unsigned char Ext_CRT2CRTC_NS; + unsigned char ModeID; + unsigned short XRes; + unsigned short YRes; + unsigned char Ext_PDC; + unsigned char Ext_FakeCRT2CRTC; + unsigned char Ext_FakeCRT2Clk; + unsigned char Ext_CRT1CRTC_NORM; + unsigned char Ext_CRTVCLK_NORM; + unsigned char Ext_CRT1CRTC_WIDE; + unsigned char Ext_CRTVCLK_WIDE; +}; + +struct SiS_Part2PortTbl { + unsigned char CR[12]; +}; + +struct SiS_CRT1Table { + unsigned char CR[17]; +}; + +struct SiS_MCLKData { + unsigned char SR28,SR29,SR2A; + unsigned short CLOCK; +}; + +struct SiS_VCLKData { + unsigned char SR2B,SR2C; + unsigned short CLOCK; +}; + +struct SiS_VBVCLKData { + unsigned char Part4_A,Part4_B; + unsigned short CLOCK; +}; + +struct SiS_StResInfo_S { + unsigned short HTotal; + unsigned short VTotal; +}; + +struct SiS_ModeResInfo_S { + unsigned short HTotal; + unsigned short VTotal; + unsigned char XChar; + unsigned char YChar; +}; + +/* Defines for SiS_CustomT */ +/* Never change these for sisfb compatibility */ +#define CUT_NONE 0 +#define CUT_FORCENONE 1 +#define CUT_BARCO1366 2 +#define CUT_BARCO1024 3 +#define CUT_COMPAQ1280 4 +#define CUT_COMPAQ12802 5 +#define CUT_PANEL848 6 +#define CUT_CLEVO1024 7 +#define CUT_CLEVO10242 8 +#define CUT_CLEVO1400 9 +#define CUT_CLEVO14002 10 +#define CUT_UNIWILL1024 11 +#define CUT_ASUSL3000D 12 +#define CUT_UNIWILL10242 13 +#define CUT_ACER1280 14 +#define CUT_COMPAL1400_1 15 +#define CUT_COMPAL1400_2 16 +#define CUT_ASUSA2H_1 17 +#define CUT_ASUSA2H_2 18 +#define CUT_UNKNOWNLCD 19 +#define CUT_AOP8060 20 +#define CUT_PANEL856 21 + +struct SiS_Private +{ + unsigned char ChipType; + unsigned char ChipRevision; + void *ivideo; + unsigned char *VirtualRomBase; + bool UseROM; + unsigned char SISIOMEMTYPE *VideoMemoryAddress; + unsigned int VideoMemorySize; + SISIOADDRESS IOAddress; + SISIOADDRESS IOAddress2; /* For dual chip XGI volari */ + + SISIOADDRESS RelIO; + SISIOADDRESS SiS_P3c4; + SISIOADDRESS SiS_P3d4; + SISIOADDRESS SiS_P3c0; + SISIOADDRESS SiS_P3ce; + SISIOADDRESS SiS_P3c2; + SISIOADDRESS SiS_P3ca; + SISIOADDRESS SiS_P3c6; + SISIOADDRESS SiS_P3c7; + SISIOADDRESS SiS_P3c8; + SISIOADDRESS SiS_P3c9; + SISIOADDRESS SiS_P3cb; + SISIOADDRESS SiS_P3cc; + SISIOADDRESS SiS_P3cd; + SISIOADDRESS SiS_P3da; + SISIOADDRESS SiS_Part1Port; + SISIOADDRESS SiS_Part2Port; + SISIOADDRESS SiS_Part3Port; + SISIOADDRESS SiS_Part4Port; + SISIOADDRESS SiS_Part5Port; + SISIOADDRESS SiS_VidCapt; + SISIOADDRESS SiS_VidPlay; + unsigned short SiS_IF_DEF_LVDS; + unsigned short SiS_IF_DEF_CH70xx; + unsigned short SiS_IF_DEF_CONEX; + unsigned short SiS_IF_DEF_TRUMPION; + unsigned short SiS_IF_DEF_DSTN; + unsigned short SiS_IF_DEF_FSTN; + unsigned short SiS_SysFlags; + unsigned char SiS_VGAINFO; + bool SiS_UseROM; + bool SiS_ROMNew; + bool SiS_XGIROM; + bool SiS_NeedRomModeData; + bool PanelSelfDetected; + bool DDCPortMixup; + int SiS_CHOverScan; + bool SiS_CHSOverScan; + bool SiS_ChSW; + bool SiS_UseLCDA; + int SiS_UseOEM; + unsigned int SiS_CustomT; + int SiS_UseWide, SiS_UseWideCRT2; + int SiS_TVBlue; + unsigned short SiS_Backup70xx; + bool HaveEMI; + bool HaveEMILCD; + bool OverruleEMI; + unsigned char EMI_30,EMI_31,EMI_32,EMI_33; + unsigned short SiS_EMIOffset; + unsigned short SiS_PWDOffset; + short PDC, PDCA; + unsigned char SiS_MyCR63; + unsigned short SiS_CRT1Mode; + unsigned short SiS_flag_clearbuffer; + int SiS_RAMType; + unsigned char SiS_ChannelAB; + unsigned char SiS_DataBusWidth; + unsigned short SiS_ModeType; + unsigned short SiS_VBInfo; + unsigned short SiS_TVMode; + unsigned short SiS_LCDResInfo; + unsigned short SiS_LCDTypeInfo; + unsigned short SiS_LCDInfo; + unsigned short SiS_LCDInfo661; + unsigned short SiS_VBType; + unsigned short SiS_VBExtInfo; + unsigned short SiS_YPbPr; + unsigned short SiS_SelectCRT2Rate; + unsigned short SiS_SetFlag; + unsigned short SiS_RVBHCFACT; + unsigned short SiS_RVBHCMAX; + unsigned short SiS_RVBHRS; + unsigned short SiS_RVBHRS2; + unsigned short SiS_VGAVT; + unsigned short SiS_VGAHT; + unsigned short SiS_VT; + unsigned short SiS_HT; + unsigned short SiS_VGAVDE; + unsigned short SiS_VGAHDE; + unsigned short SiS_VDE; + unsigned short SiS_HDE; + unsigned short SiS_NewFlickerMode; + unsigned short SiS_RY1COE; + unsigned short SiS_RY2COE; + unsigned short SiS_RY3COE; + unsigned short SiS_RY4COE; + unsigned short SiS_LCDHDES; + unsigned short SiS_LCDVDES; + SISIOADDRESS SiS_DDC_Port; + unsigned short SiS_DDC_Index; + unsigned short SiS_DDC_Data; + unsigned short SiS_DDC_NData; + unsigned short SiS_DDC_Clk; + unsigned short SiS_DDC_NClk; + unsigned short SiS_DDC_DeviceAddr; + unsigned short SiS_DDC_ReadAddr; + unsigned short SiS_DDC_SecAddr; + unsigned short SiS_ChrontelInit; + bool SiS_SensibleSR11; + unsigned short SiS661LCD2TableSize; + + unsigned short SiS_PanelMinLVDS; + unsigned short SiS_PanelMin301; + + const struct SiS_St *SiS_SModeIDTable; + const struct SiS_StandTable_S *SiS_StandTable; + const struct SiS_Ext *SiS_EModeIDTable; + const struct SiS_Ext2 *SiS_RefIndex; + const struct SiS_VBMode *SiS_VBModeIDTable; + const struct SiS_CRT1Table *SiS_CRT1Table; + const struct SiS_MCLKData *SiS_MCLKData_0; + const struct SiS_MCLKData *SiS_MCLKData_1; + struct SiS_VCLKData *SiS_VCLKData; + struct SiS_VBVCLKData *SiS_VBVCLKData; + const struct SiS_StResInfo_S *SiS_StResInfo; + const struct SiS_ModeResInfo_S *SiS_ModeResInfo; + + const unsigned char *pSiS_OutputSelect; + const unsigned char *pSiS_SoftSetting; + + const unsigned char *SiS_SR15; + + const struct SiS_PanelDelayTbl *SiS_PanelDelayTbl; + const struct SiS_PanelDelayTbl *SiS_PanelDelayTblLVDS; + + /* SiS bridge */ + + const struct SiS_LCDData *SiS_ExtLCD1024x768Data; + const struct SiS_LCDData *SiS_St2LCD1024x768Data; + const struct SiS_LCDData *SiS_LCD1280x720Data; + const struct SiS_LCDData *SiS_StLCD1280x768_2Data; + const struct SiS_LCDData *SiS_ExtLCD1280x768_2Data; + const struct SiS_LCDData *SiS_LCD1280x800Data; + const struct SiS_LCDData *SiS_LCD1280x800_2Data; + const struct SiS_LCDData *SiS_LCD1280x854Data; + const struct SiS_LCDData *SiS_LCD1280x960Data; + const struct SiS_LCDData *SiS_ExtLCD1280x1024Data; + const struct SiS_LCDData *SiS_St2LCD1280x1024Data; + const struct SiS_LCDData *SiS_StLCD1400x1050Data; + const struct SiS_LCDData *SiS_ExtLCD1400x1050Data; + const struct SiS_LCDData *SiS_StLCD1600x1200Data; + const struct SiS_LCDData *SiS_ExtLCD1600x1200Data; + const struct SiS_LCDData *SiS_LCD1680x1050Data; + const struct SiS_LCDData *SiS_NoScaleData; + const struct SiS_TVData *SiS_StPALData; + const struct SiS_TVData *SiS_ExtPALData; + const struct SiS_TVData *SiS_StNTSCData; + const struct SiS_TVData *SiS_ExtNTSCData; + const struct SiS_TVData *SiS_St1HiTVData; + const struct SiS_TVData *SiS_St2HiTVData; + const struct SiS_TVData *SiS_ExtHiTVData; + const struct SiS_TVData *SiS_St525iData; + const struct SiS_TVData *SiS_St525pData; + const struct SiS_TVData *SiS_St750pData; + const struct SiS_TVData *SiS_Ext525iData; + const struct SiS_TVData *SiS_Ext525pData; + const struct SiS_TVData *SiS_Ext750pData; + const unsigned char *SiS_NTSCTiming; + const unsigned char *SiS_PALTiming; + const unsigned char *SiS_HiTVExtTiming; + const unsigned char *SiS_HiTVSt1Timing; + const unsigned char *SiS_HiTVSt2Timing; + const unsigned char *SiS_HiTVGroup3Data; + const unsigned char *SiS_HiTVGroup3Simu; +#if 0 + const unsigned char *SiS_HiTVTextTiming; + const unsigned char *SiS_HiTVGroup3Text; +#endif + + const struct SiS_Part2PortTbl *SiS_CRT2Part2_1024x768_1; + const struct SiS_Part2PortTbl *SiS_CRT2Part2_1024x768_2; + const struct SiS_Part2PortTbl *SiS_CRT2Part2_1024x768_3; + + /* LVDS, Chrontel */ + + const struct SiS_LVDSData *SiS_LVDS320x240Data_1; + const struct SiS_LVDSData *SiS_LVDS320x240Data_2; + const struct SiS_LVDSData *SiS_LVDS640x480Data_1; + const struct SiS_LVDSData *SiS_LVDS800x600Data_1; + const struct SiS_LVDSData *SiS_LVDS1024x600Data_1; + const struct SiS_LVDSData *SiS_LVDS1024x768Data_1; + const struct SiS_LVDSData *SiS_LVDSBARCO1366Data_1; + const struct SiS_LVDSData *SiS_LVDSBARCO1366Data_2; + const struct SiS_LVDSData *SiS_LVDSBARCO1024Data_1; + const struct SiS_LVDSData *SiS_LVDS848x480Data_1; + const struct SiS_LVDSData *SiS_LVDS848x480Data_2; + const struct SiS_LVDSData *SiS_CHTVUNTSCData; + const struct SiS_LVDSData *SiS_CHTVONTSCData; + const struct SiS_LVDSData *SiS_CHTVUPALData; + const struct SiS_LVDSData *SiS_CHTVOPALData; + const struct SiS_LVDSData *SiS_CHTVUPALMData; + const struct SiS_LVDSData *SiS_CHTVOPALMData; + const struct SiS_LVDSData *SiS_CHTVUPALNData; + const struct SiS_LVDSData *SiS_CHTVOPALNData; + const struct SiS_LVDSData *SiS_CHTVSOPALData; + + const struct SiS_LVDSDes *SiS_PanelType04_1a; + const struct SiS_LVDSDes *SiS_PanelType04_2a; + const struct SiS_LVDSDes *SiS_PanelType04_1b; + const struct SiS_LVDSDes *SiS_PanelType04_2b; + + const struct SiS_LVDSCRT1Data *SiS_LVDSCRT1320x240_1; + const struct SiS_LVDSCRT1Data *SiS_LVDSCRT1320x240_2; + const struct SiS_LVDSCRT1Data *SiS_LVDSCRT1320x240_2_H; + const struct SiS_LVDSCRT1Data *SiS_LVDSCRT1320x240_3; + const struct SiS_LVDSCRT1Data *SiS_LVDSCRT1320x240_3_H; + const struct SiS_LVDSCRT1Data *SiS_LVDSCRT1640x480_1; + const struct SiS_LVDSCRT1Data *SiS_LVDSCRT1640x480_1_H; + const struct SiS_LVDSCRT1Data *SiS_CHTVCRT1UNTSC; + const struct SiS_LVDSCRT1Data *SiS_CHTVCRT1ONTSC; + const struct SiS_LVDSCRT1Data *SiS_CHTVCRT1UPAL; + const struct SiS_LVDSCRT1Data *SiS_CHTVCRT1OPAL; + const struct SiS_LVDSCRT1Data *SiS_CHTVCRT1SOPAL; + + const struct SiS_CHTVRegData *SiS_CHTVReg_UNTSC; + const struct SiS_CHTVRegData *SiS_CHTVReg_ONTSC; + const struct SiS_CHTVRegData *SiS_CHTVReg_UPAL; + const struct SiS_CHTVRegData *SiS_CHTVReg_OPAL; + const struct SiS_CHTVRegData *SiS_CHTVReg_UPALM; + const struct SiS_CHTVRegData *SiS_CHTVReg_OPALM; + const struct SiS_CHTVRegData *SiS_CHTVReg_UPALN; + const struct SiS_CHTVRegData *SiS_CHTVReg_OPALN; + const struct SiS_CHTVRegData *SiS_CHTVReg_SOPAL; + + const unsigned char *SiS_CHTVVCLKUNTSC; + const unsigned char *SiS_CHTVVCLKONTSC; + const unsigned char *SiS_CHTVVCLKUPAL; + const unsigned char *SiS_CHTVVCLKOPAL; + const unsigned char *SiS_CHTVVCLKUPALM; + const unsigned char *SiS_CHTVVCLKOPALM; + const unsigned char *SiS_CHTVVCLKUPALN; + const unsigned char *SiS_CHTVVCLKOPALN; + const unsigned char *SiS_CHTVVCLKSOPAL; + + unsigned short PanelXRes, PanelHT; + unsigned short PanelYRes, PanelVT; + unsigned short PanelHRS, PanelHRE; + unsigned short PanelVRS, PanelVRE; + unsigned short PanelVCLKIdx300; + unsigned short PanelVCLKIdx315; + bool Alternate1600x1200; + + bool UseCustomMode; + bool CRT1UsesCustomMode; + unsigned short CHDisplay; + unsigned short CHSyncStart; + unsigned short CHSyncEnd; + unsigned short CHTotal; + unsigned short CHBlankStart; + unsigned short CHBlankEnd; + unsigned short CVDisplay; + unsigned short CVSyncStart; + unsigned short CVSyncEnd; + unsigned short CVTotal; + unsigned short CVBlankStart; + unsigned short CVBlankEnd; + unsigned int CDClock; + unsigned int CFlags; + unsigned char CCRT1CRTC[17]; + unsigned char CSR2B; + unsigned char CSR2C; + unsigned short CSRClock; + unsigned short CSRClock_CRT1; + unsigned short CModeFlag; + unsigned short CModeFlag_CRT1; + unsigned short CInfoFlag; + + int LVDSHL; + + bool Backup; + unsigned char Backup_Mode; + unsigned char Backup_14; + unsigned char Backup_15; + unsigned char Backup_16; + unsigned char Backup_17; + unsigned char Backup_18; + unsigned char Backup_19; + unsigned char Backup_1a; + unsigned char Backup_1b; + unsigned char Backup_1c; + unsigned char Backup_1d; + + unsigned char Init_P4_0E; + + int UsePanelScaler; + int CenterScreen; + + unsigned short CP_Vendor, CP_Product; + bool CP_HaveCustomData; + int CP_PreferredX, CP_PreferredY, CP_PreferredIndex; + int CP_MaxX, CP_MaxY, CP_MaxClock; + unsigned char CP_PrefSR2B, CP_PrefSR2C; + unsigned short CP_PrefClock; + bool CP_Supports64048075; + int CP_HDisplay[7], CP_VDisplay[7]; /* For Custom LCD panel dimensions */ + int CP_HTotal[7], CP_VTotal[7]; + int CP_HSyncStart[7], CP_VSyncStart[7]; + int CP_HSyncEnd[7], CP_VSyncEnd[7]; + int CP_HBlankStart[7], CP_VBlankStart[7]; + int CP_HBlankEnd[7], CP_VBlankEnd[7]; + int CP_Clock[7]; + bool CP_DataValid[7]; + bool CP_HSync_P[7], CP_VSync_P[7], CP_SyncValid[7]; +}; + +#endif + diff --git a/drivers/video/fbdev/skeletonfb.c b/drivers/video/fbdev/skeletonfb.c new file mode 100644 index 000000000000..fefde7c6add7 --- /dev/null +++ b/drivers/video/fbdev/skeletonfb.c @@ -0,0 +1,1037 @@ +/* + * linux/drivers/video/skeletonfb.c -- Skeleton for a frame buffer device + * + * Modified to new api Jan 2001 by James Simmons (jsimmons@transvirtual.com) + * + * Created 28 Dec 1997 by Geert Uytterhoeven + * + * + * I have started rewriting this driver as a example of the upcoming new API + * The primary goal is to remove the console code from fbdev and place it + * into fbcon.c. This reduces the code and makes writing a new fbdev driver + * easy since the author doesn't need to worry about console internals. It + * also allows the ability to run fbdev without a console/tty system on top + * of it. + * + * First the roles of struct fb_info and struct display have changed. Struct + * display will go away. The way the new framebuffer console code will + * work is that it will act to translate data about the tty/console in + * struct vc_data to data in a device independent way in struct fb_info. Then + * various functions in struct fb_ops will be called to store the device + * dependent state in the par field in struct fb_info and to change the + * hardware to that state. This allows a very clean separation of the fbdev + * layer from the console layer. It also allows one to use fbdev on its own + * which is a bounus for embedded devices. The reason this approach works is + * for each framebuffer device when used as a tty/console device is allocated + * a set of virtual terminals to it. Only one virtual terminal can be active + * per framebuffer device. We already have all the data we need in struct + * vc_data so why store a bunch of colormaps and other fbdev specific data + * per virtual terminal. + * + * As you can see doing this makes the con parameter pretty much useless + * for struct fb_ops functions, as it should be. Also having struct + * fb_var_screeninfo and other data in fb_info pretty much eliminates the + * need for get_fix and get_var. Once all drivers use the fix, var, and cmap + * fbcon can be written around these fields. This will also eliminate the + * need to regenerate struct fb_var_screeninfo, struct fb_fix_screeninfo + * struct fb_cmap every time get_var, get_fix, get_cmap functions are called + * as many drivers do now. + * + * 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> + + /* + * This is just simple sample code. + * + * No warranty that it actually compiles. + * Even less warranty that it actually works :-) + */ + +/* + * Driver data + */ +static char *mode_option; + +/* + * If your driver supports multiple boards, you should make the + * below data types arrays, or allocate them dynamically (using kmalloc()). + */ + +/* + * 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 xxx_par; + +/* + * 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 xxxfb_init how to use it + * to get a fb_var_screeninfo. Otherwise define a default var as well. + */ +static struct fb_fix_screeninfo xxxfb_fix = { + .id = "FB's name", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 1, + .accel = FB_ACCEL_NONE, +}; + + /* + * Modern graphical hardware not only supports pipelines but some + * also support multiple monitors where each display can have its + * its own unique data. In this case each display could be + * represented by a separate framebuffer device thus a separate + * struct fb_info. Now the struct xxx_par represents the graphics + * hardware state thus only one exist per card. In this case the + * struct xxx_par for each graphics card would be shared between + * every struct fb_info that represents a framebuffer on that card. + * This allows when one display changes it video resolution (info->var) + * the other displays know instantly. Each display can always be + * aware of the entire hardware state that affects it because they share + * the same xxx_par struct. The other side of the coin is multiple + * graphics cards that pass data around until it is finally displayed + * on one monitor. Such examples are the voodoo 1 cards and high end + * NUMA graphics servers. For this case we have a bunch of pars, each + * one that represents a graphics state, that belong to one struct + * fb_info. Their you would want to have *par point to a array of device + * states and have each struct fb_ops function deal with all those + * states. I hope this covers every possible hardware design. If not + * feel free to send your ideas at jsimmons@users.sf.net + */ + + /* + * If your driver supports multiple boards or it supports multiple + * framebuffers, you should make these arrays, or allocate them + * dynamically using framebuffer_alloc() and free them with + * framebuffer_release(). + */ +static struct fb_info info; + + /* + * Each one represents the state of the hardware. Most hardware have + * just one hardware state. These here represent the default state(s). + */ +static struct xxx_par __initdata current_par; + +int xxxfb_init(void); + +/** + * xxxfb_open - Optional function. Called when the framebuffer is + * first accessed. + * @info: frame buffer structure that represents a single frame buffer + * @user: tell us if the userland (value=1) or the console is accessing + * the framebuffer. + * + * This function is the first function called in the framebuffer api. + * Usually you don't need to provide this function. The case where it + * is used is to change from a text mode hardware state to a graphics + * mode state. + * + * Returns negative errno on error, or zero on success. + */ +static int xxxfb_open(struct fb_info *info, int user) +{ + return 0; +} + +/** + * xxxfb_release - Optional function. Called when the framebuffer + * device is closed. + * @info: frame buffer structure that represents a single frame buffer + * @user: tell us if the userland (value=1) or the console is accessing + * the framebuffer. + * + * Thus function is called when we close /dev/fb or the framebuffer + * console system is released. Usually you don't need this function. + * The case where it is usually used is to go from a graphics state + * to a text mode state. + * + * Returns negative errno on error, or zero on success. + */ +static int xxxfb_release(struct fb_info *info, int user) +{ + return 0; +} + +/** + * xxxfb_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 + * + * Checks to see if the hardware supports the state requested by + * var passed in. This function does not alter the hardware state!!! + * This means the data stored in struct fb_info and struct xxx_par do + * not change. This includes the var inside of struct fb_info. + * Do NOT change these. This function can be called on its own if we + * intent to only test a mode and not actually set it. The stuff in + * modedb.c is a example of this. If the var passed in is slightly + * off by what the hardware can support then we alter the var PASSED in + * to what we can do. + * + * For values that are off, this function must round them _up_ to the + * next value that is supported by the hardware. If the value is + * greater than the highest value supported by the hardware, then this + * function must return -EINVAL. + * + * Exception to the above rule: Some drivers have a fixed mode, ie, + * the hardware is already set at boot up, and cannot be changed. In + * this case, it is more acceptable that this function just return + * a copy of the currently working var (info->var). Better is to not + * implement this function, as the upper layer will do the copying + * of the current var for you. + * + * Note: This is the only function where the contents of var can be + * freely adjusted after the driver has been registered. If you find + * that you have code outside of this function that alters the content + * of var, then you are doing something wrong. Note also that the + * contents of info->var must be left untouched at all times after + * driver registration. + * + * Returns negative errno on error, or zero on success. + */ +static int xxxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + /* ... */ + return 0; +} + +/** + * xxxfb_set_par - Optional function. Alters the hardware state. + * @info: frame buffer structure that represents a single frame buffer + * + * Using the fb_var_screeninfo in fb_info we set the resolution of the + * this particular framebuffer. This function alters the par AND the + * fb_fix_screeninfo stored in fb_info. It doesn't not alter var in + * fb_info since we are using that data. This means we depend on the + * data in var inside fb_info to be supported by the hardware. + * + * This function is also used to recover/restore the hardware to a + * known working state. + * + * xxxfb_check_var is always called before xxxfb_set_par to ensure that + * the contents of var is always valid. + * + * Again if you can't change the resolution you don't need this function. + * + * However, even if your hardware does not support mode changing, + * a set_par might be needed to at least initialize the hardware to + * a known working state, especially if it came back from another + * process that also modifies the same hardware, such as X. + * + * If this is the case, a combination such as the following should work: + * + * static int xxxfb_check_var(struct fb_var_screeninfo *var, + * struct fb_info *info) + * { + * *var = info->var; + * return 0; + * } + * + * static int xxxfb_set_par(struct fb_info *info) + * { + * init your hardware here + * } + * + * Returns negative errno on error, or zero on success. + */ +static int xxxfb_set_par(struct fb_info *info) +{ + struct xxx_par *par = info->par; + /* ... */ + return 0; +} + +/** + * xxxfb_setcolreg - Optional function. Sets a color register. + * @regno: Which register in the CLUT we are programming + * @red: The red value which can be up to 16 bits wide + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported, the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + * + * Set a single color register. The values supplied have a 16 bit + * magnitude which needs to be scaled in this function for the hardware. + * Things to take into consideration are how many color registers, if + * any, are supported with the current color visual. With truecolor mode + * no color palettes are supported. Here a pseudo palette is created + * which we store the value in pseudo_palette in struct fb_info. For + * pseudocolor mode we have a limited color palette. To deal with this + * we can program what color is displayed for a particular pixel value. + * DirectColor is similar in that we can program each color field. If + * we have a static colormap we don't need to implement this function. + * + * Returns negative errno on error, or zero on success. + */ +static int xxxfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return -EINVAL; + /* + * Program hardware... do anything you want with transp + */ + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* Directcolor: + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * {hardwarespecific} contains width of DAC + * pseudo_palette[X] is programmed to (X << red.offset) | + * (X << green.offset) | + * (X << blue.offset) + * RAMDAC[X] is programmed to (red, green, blue) + * color depth = SUM(var->{color}.length) + * + * Pseudocolor: + * var->{color}.offset is 0 unless the palette index takes less than + * bits_per_pixel bits and is stored in the upper + * bits of the pixel value + * var->{color}.length is set so that 1 << length is the number of + * available palette entries + * pseudo_palette is not used + * RAMDAC[X] is programmed to (red, green, blue) + * color depth = var->{color}.length + * + * Static pseudocolor: + * same as Pseudocolor, but the RAMDAC is not programmed (read-only) + * + * Mono01/Mono10: + * Has only 2 values, black on white or white on black (fg on bg), + * var->{color}.offset is 0 + * white = (1 << var->{color}.length) - 1, black = 0 + * pseudo_palette is not used + * RAMDAC does not exist + * color depth is always 2 + * + * Truecolor: + * does not use RAMDAC (usually has 3 of them). + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * pseudo_palette is programmed to (red << red.offset) | + * (green << green.offset) | + * (blue << blue.offset) | + * (transp << transp.offset) + * RAMDAC does not exist + * color depth = SUM(var->{color}.length}) + * + * The color depth is used by fbcon for choosing the logo and also + * for color palette transformation if color depth < 4 + * + * As can be seen from the above, the field bits_per_pixel is _NOT_ + * a criteria for describing the color visual. + * + * A common mistake is assuming that bits_per_pixel <= 8 is pseudocolor, + * and higher than that, true/directcolor. This is incorrect, one needs + * to look at the fix->visual. + * + * Another common mistake is using bits_per_pixel to calculate the color + * depth. The bits_per_pixel field does not directly translate to color + * depth. You have to compute for the color depth (using the color + * bitfields) and fix->visual as seen above. + */ + + /* + * This is the point where the color is converted to something that + * is acceptable by the hardware. + */ +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); +#undef CNVT_TOHW + /* + * This is the point where the function feeds the color to the hardware + * palette after converting the colors to something acceptable by + * the hardware. Note, only FB_VISUAL_DIRECTCOLOR and + * FB_VISUAL_PSEUDOCOLOR visuals need to write to the hardware palette. + * If you have code that writes to the hardware CLUT, and it's not + * any of the above visuals, then you are doing something wrong. + */ + if (info->fix.visual == FB_VISUAL_DIRECTCOLOR || + info->fix.visual == FB_VISUAL_TRUECOLOR) + write_{red|green|blue|transp}_to_clut(); + + /* This is the point were you need to fill up the contents of + * info->pseudo_palette. This structure is used _only_ by fbcon, thus + * it only contains 16 entries to match the number of colors supported + * by the console. The pseudo_palette is used only if the visual is + * in directcolor or truecolor mode. With other visuals, the + * pseudo_palette is not used. (This might change in the future.) + * + * The contents of the pseudo_palette is in raw pixel format. Ie, each + * entry can be written directly to the framebuffer without any conversion. + * The pseudo_palette is (void *). However, if using the generic + * drawing functions (cfb_imageblit, cfb_fillrect), the pseudo_palette + * must be casted to (u32 *) _regardless_ of the bits per pixel. If the + * driver is using its own drawing functions, then it can use whatever + * size it wants. + */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + u32 v; + + if (regno >= 16) + return -EINVAL; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + ((u32*)(info->pseudo_palette))[regno] = v; + } + + /* ... */ + return 0; +} + +/** + * xxxfb_pan_display - NOT a required function. Pans the display. + * @var: frame buffer variable screen structure + * @info: frame buffer structure that represents a single frame buffer + * + * Pan (or wrap, depending on the `vmode' field) the display using the + * `xoffset' and `yoffset' fields of the `var' structure. + * If the values don't fit, return -EINVAL. + * + * Returns negative errno on error, or zero on success. + */ +static int xxxfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + /* + * If your hardware does not support panning, _do_ _not_ implement this + * function. Creating a dummy function will just confuse user apps. + */ + + /* + * Note that even if this function is fully functional, a setting of + * 0 in both xpanstep and ypanstep means that this function will never + * get called. + */ + + /* ... */ + return 0; +} + +/** + * xxxfb_blank - NOT a required function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + * + * Blank the screen if blank_mode != FB_BLANK_UNBLANK, else unblank. + * Return 0 if blanking succeeded, != 0 if un-/blanking failed due to + * e.g. a video mode which doesn't support it. + * + * Implements VESA suspend and powerdown modes on hardware that supports + * disabling hsync/vsync: + * + * FB_BLANK_NORMAL = display is blanked, syncs are on. + * FB_BLANK_HSYNC_SUSPEND = hsync off + * FB_BLANK_VSYNC_SUSPEND = vsync off + * FB_BLANK_POWERDOWN = hsync and vsync off + * + * If implementing this function, at least support FB_BLANK_UNBLANK. + * Return !0 for any modes that are unimplemented. + * + */ +static int xxxfb_blank(int blank_mode, struct fb_info *info) +{ + /* ... */ + return 0; +} + +/* ------------ Accelerated Functions --------------------- */ + +/* + * We provide our own functions if we have hardware acceleration + * or non packed pixel format layouts. If we have no hardware + * acceleration, we can use a generic unaccelerated function. If using + * a pack pixel format just use the functions in cfb_*.c. Each file + * has one of the three different accel functions we support. + */ + +/** + * xxxfb_fillrect - REQUIRED function. Can use generic routines if + * non acclerated hardware and packed pixel based. + * Draws a rectangle on the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @region: The structure representing the rectangular region we + * wish to draw to. + * + * This drawing operation places/removes a retangle on the screen + * depending on the rastering operation with the value of color which + * is in the current color depth format. + */ +void xxxfb_fillrect(struct fb_info *p, const struct fb_fillrect *region) +{ +/* Meaning of struct fb_fillrect + * + * @dx: The x and y corrdinates of the upper left hand corner of the + * @dy: area we want to draw to. + * @width: How wide the rectangle is we want to draw. + * @height: How tall the rectangle is we want to draw. + * @color: The color to fill in the rectangle with. + * @rop: The raster operation. We can draw the rectangle with a COPY + * of XOR which provides erasing effect. + */ +} + +/** + * xxxfb_copyarea - REQUIRED function. Can use generic routines if + * non acclerated hardware and packed pixel based. + * Copies one area of the screen to another area. + * + * @info: frame buffer structure that represents a single frame buffer + * @area: Structure providing the data to copy the framebuffer contents + * from one region to another. + * + * This drawing operation copies a rectangular area from one area of the + * screen to another area. + */ +void xxxfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ +/* + * @dx: The x and y coordinates of the upper left hand corner of the + * @dy: destination area on the screen. + * @width: How wide the rectangle is we want to copy. + * @height: How tall the rectangle is we want to copy. + * @sx: The x and y coordinates of the upper left hand corner of the + * @sy: source area on the screen. + */ +} + + +/** + * xxxfb_imageblit - REQUIRED function. Can use generic routines if + * non acclerated hardware and packed pixel based. + * Copies a image from system memory to the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @image: structure defining the image. + * + * This drawing operation draws a image on the screen. It can be a + * mono image (needed for font handling) or a color image (needed for + * tux). + */ +void xxxfb_imageblit(struct fb_info *p, const struct fb_image *image) +{ +/* + * @dx: The x and y coordinates of the upper left hand corner of the + * @dy: destination area to place the image on the screen. + * @width: How wide the image is we want to copy. + * @height: How tall the image is we want to copy. + * @fg_color: For mono bitmap images this is color data for + * @bg_color: the foreground and background of the image to + * write directly to the frmaebuffer. + * @depth: How many bits represent a single pixel for this image. + * @data: The actual data used to construct the image on the display. + * @cmap: The colormap used for color images. + */ + +/* + * The generic function, cfb_imageblit, expects that the bitmap scanlines are + * padded to the next byte. Most hardware accelerators may require padding to + * the next u16 or the next u32. If that is the case, the driver can specify + * this by setting info->pixmap.scan_align = 2 or 4. See a more + * comprehensive description of the pixmap below. + */ +} + +/** + * xxxfb_cursor - OPTIONAL. If your hardware lacks support + * for a cursor, leave this field NULL. + * + * @info: frame buffer structure that represents a single frame buffer + * @cursor: structure defining the cursor to draw. + * + * This operation is used to set or alter the properities of the + * cursor. + * + * Returns negative errno on error, or zero on success. + */ +int xxxfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ +/* + * @set: Which fields we are altering in struct fb_cursor + * @enable: Disable or enable the cursor + * @rop: The bit operation we want to do. + * @mask: This is the cursor mask bitmap. + * @dest: A image of the area we are going to display the cursor. + * Used internally by the driver. + * @hot: The hot spot. + * @image: The actual data for the cursor image. + * + * NOTES ON FLAGS (cursor->set): + * + * FB_CUR_SETIMAGE - the cursor image has changed (cursor->image.data) + * FB_CUR_SETPOS - the cursor position has changed (cursor->image.dx|dy) + * FB_CUR_SETHOT - the cursor hot spot has changed (cursor->hot.dx|dy) + * FB_CUR_SETCMAP - the cursor colors has changed (cursor->fg_color|bg_color) + * FB_CUR_SETSHAPE - the cursor bitmask has changed (cursor->mask) + * FB_CUR_SETSIZE - the cursor size has changed (cursor->width|height) + * FB_CUR_SETALL - everything has changed + * + * NOTES ON ROPs (cursor->rop, Raster Operation) + * + * ROP_XOR - cursor->image.data XOR cursor->mask + * ROP_COPY - curosr->image.data AND cursor->mask + * + * OTHER NOTES: + * + * - fbcon only supports a 2-color cursor (cursor->image.depth = 1) + * - The fb_cursor structure, @cursor, _will_ always contain valid + * fields, whether any particular bitfields in cursor->set is set + * or not. + */ +} + +/** + * xxxfb_rotate - NOT a required function. If your hardware + * supports rotation the whole screen then + * you would provide a hook for this. + * + * @info: frame buffer structure that represents a single frame buffer + * @angle: The angle we rotate the screen. + * + * This operation is used to set or alter the properities of the + * cursor. + */ +void xxxfb_rotate(struct fb_info *info, int angle) +{ +/* Will be deprecated */ +} + +/** + * xxxfb_sync - NOT a required function. Normally the accel engine + * for a graphics card take a specific amount of time. + * Often we have to wait for the accelerator to finish + * its operation before we can write to the framebuffer + * so we can have consistent display output. + * + * @info: frame buffer structure that represents a single frame buffer + * + * If the driver has implemented its own hardware-based drawing function, + * implementing this function is highly recommended. + */ +int xxxfb_sync(struct fb_info *info) +{ + return 0; +} + + /* + * Frame buffer operations + */ + +static struct fb_ops xxxfb_ops = { + .owner = THIS_MODULE, + .fb_open = xxxfb_open, + .fb_read = xxxfb_read, + .fb_write = xxxfb_write, + .fb_release = xxxfb_release, + .fb_check_var = xxxfb_check_var, + .fb_set_par = xxxfb_set_par, + .fb_setcolreg = xxxfb_setcolreg, + .fb_blank = xxxfb_blank, + .fb_pan_display = xxxfb_pan_display, + .fb_fillrect = xxxfb_fillrect, /* Needed !!! */ + .fb_copyarea = xxxfb_copyarea, /* Needed !!! */ + .fb_imageblit = xxxfb_imageblit, /* Needed !!! */ + .fb_cursor = xxxfb_cursor, /* Optional !!! */ + .fb_rotate = xxxfb_rotate, + .fb_sync = xxxfb_sync, + .fb_ioctl = xxxfb_ioctl, + .fb_mmap = xxxfb_mmap, +}; + +/* ------------------------------------------------------------------------- */ + + /* + * Initialization + */ + +/* static int __init xxfb_probe (struct platform_device *pdev) -- for platform devs */ +static int xxxfb_probe(struct pci_dev *dev, const struct pci_device_id *ent) +{ + struct fb_info *info; + struct xxx_par *par; + struct device *device = &dev->dev; /* or &pdev->dev */ + int cmap_len, retval; + + /* + * Dynamically allocate info and par + */ + info = framebuffer_alloc(sizeof(struct xxx_par), device); + + if (!info) { + /* goto error path */ + } + + par = info->par; + + /* + * Here we set the screen_base to the virtual memory address + * for the framebuffer. Usually we obtain the resource address + * from the bus layer and then translate it to virtual memory + * space via ioremap. Consult ioport.h. + */ + info->screen_base = framebuffer_virtual_memory; + info->fbops = &xxxfb_ops; + info->fix = xxxfb_fix; + info->pseudo_palette = pseudo_palette; /* The pseudopalette is an + * 16-member array + */ + /* + * Set up flags to indicate what sort of acceleration your + * driver can provide (pan/wrap/copyarea/etc.) and whether it + * is a module -- see FBINFO_* in include/linux/fb.h + * + * If your hardware can support any of the hardware accelerated functions + * fbcon performance will improve if info->flags is set properly. + * + * FBINFO_HWACCEL_COPYAREA - hardware moves + * FBINFO_HWACCEL_FILLRECT - hardware fills + * FBINFO_HWACCEL_IMAGEBLIT - hardware mono->color expansion + * FBINFO_HWACCEL_YPAN - hardware can pan display in y-axis + * FBINFO_HWACCEL_YWRAP - hardware can wrap display in y-axis + * FBINFO_HWACCEL_DISABLED - supports hardware accels, but disabled + * FBINFO_READS_FAST - if set, prefer moves over mono->color expansion + * FBINFO_MISC_TILEBLITTING - hardware can do tile blits + * + * NOTE: These are for fbcon use only. + */ + info->flags = FBINFO_DEFAULT; + +/********************* This stage is optional ******************************/ + /* + * The struct pixmap is a scratch pad for the drawing functions. This + * is where the monochrome bitmap is constructed by the higher layers + * and then passed to the accelerator. For drivers that uses + * cfb_imageblit, you can skip this part. For those that have a more + * rigorous requirement, this stage is needed + */ + + /* PIXMAP_SIZE should be small enough to optimize drawing, but not + * large enough that memory is wasted. A safe size is + * (max_xres * max_font_height/8). max_xres is driver dependent, + * max_font_height is 32. + */ + info->pixmap.addr = kmalloc(PIXMAP_SIZE, GFP_KERNEL); + if (!info->pixmap.addr) { + /* goto error */ + } + + info->pixmap.size = PIXMAP_SIZE; + + /* + * FB_PIXMAP_SYSTEM - memory is in system ram + * FB_PIXMAP_IO - memory is iomapped + * FB_PIXMAP_SYNC - if set, will call fb_sync() per access to pixmap, + * usually if FB_PIXMAP_IO is set. + * + * Currently, FB_PIXMAP_IO is unimplemented. + */ + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + /* + * scan_align is the number of padding for each scanline. It is in bytes. + * Thus for accelerators that need padding to the next u32, put 4 here. + */ + info->pixmap.scan_align = 4; + + /* + * buf_align is the amount to be padded for the buffer. For example, + * the i810fb needs a scan_align of 2 but expects it to be fed with + * dwords, so a buf_align = 4 is required. + */ + info->pixmap.buf_align = 4; + + /* access_align is how many bits can be accessed from the framebuffer + * ie. some epson cards allow 16-bit access only. Most drivers will + * be safe with u32 here. + * + * NOTE: This field is currently unused. + */ + info->pixmap.access_align = 32; +/***************************** End optional stage ***************************/ + + /* + * This should give a reasonable default video mode. The following is + * done when we can set a video mode. + */ + if (!mode_option) + mode_option = "640x480@60"; + + retval = fb_find_mode(&info->var, info, mode_option, NULL, 0, NULL, 8); + + if (!retval || retval == 4) + return -EINVAL; + + /* This has to be done! */ + if (fb_alloc_cmap(&info->cmap, cmap_len, 0)) + return -ENOMEM; + + /* + * The following is done in the case of having hardware with a static + * mode. If we are setting the mode ourselves we don't call this. + */ + info->var = xxxfb_var; + + /* + * For drivers that can... + */ + xxxfb_check_var(&info->var, info); + + /* + * Does a call to fb_set_par() before register_framebuffer needed? This + * will depend on you and the hardware. If you are sure that your driver + * is the only device in the system, a call to fb_set_par() is safe. + * + * Hardware in x86 systems has a VGA core. Calling set_par() at this + * point will corrupt the VGA console, so it might be safer to skip a + * call to set_par here and just allow fbcon to do it for you. + */ + /* xxxfb_set_par(info); */ + + if (register_framebuffer(info) < 0) { + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + fb_info(info, "%s frame buffer device\n", info->fix.id); + pci_set_drvdata(dev, info); /* or platform_set_drvdata(pdev, info) */ + return 0; +} + + /* + * Cleanup + */ +/* static void xxxfb_remove(struct platform_device *pdev) */ +static void xxxfb_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + /* or platform_get_drvdata(pdev); */ + + if (info) { + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + /* ... */ + framebuffer_release(info); + } +} + +#ifdef CONFIG_PCI +#ifdef CONFIG_PM +/** + * xxxfb_suspend - Optional but recommended function. Suspend the device. + * @dev: PCI device + * @msg: the suspend event code. + * + * See Documentation/power/devices.txt for more information + */ +static int xxxfb_suspend(struct pci_dev *dev, pm_message_t msg) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct xxxfb_par *par = info->par; + + /* suspend here */ + return 0; +} + +/** + * xxxfb_resume - Optional but recommended function. Resume the device. + * @dev: PCI device + * + * See Documentation/power/devices.txt for more information + */ +static int xxxfb_resume(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct xxxfb_par *par = info->par; + + /* resume here */ + return 0; +} +#else +#define xxxfb_suspend NULL +#define xxxfb_resume NULL +#endif /* CONFIG_PM */ + +static struct pci_device_id xxxfb_id_table[] = { + { PCI_VENDOR_ID_XXX, PCI_DEVICE_ID_XXX, + PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, + PCI_CLASS_MASK, 0 }, + { 0, } +}; + +/* For PCI drivers */ +static struct pci_driver xxxfb_driver = { + .name = "xxxfb", + .id_table = xxxfb_id_table, + .probe = xxxfb_probe, + .remove = xxxfb_remove, + .suspend = xxxfb_suspend, /* optional but recommended */ + .resume = xxxfb_resume, /* optional but recommended */ +}; + +MODULE_DEVICE_TABLE(pci, xxxfb_id_table); + +int __init xxxfb_init(void) +{ + /* + * For kernel boot options (in 'video=xxxfb:<options>' format) + */ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("xxxfb", &option)) + return -ENODEV; + xxxfb_setup(option); +#endif + + return pci_register_driver(&xxxfb_driver); +} + +static void __exit xxxfb_exit(void) +{ + pci_unregister_driver(&xxxfb_driver); +} +#else /* non PCI, platform drivers */ +#include <linux/platform_device.h> +/* for platform devices */ + +#ifdef CONFIG_PM +/** + * xxxfb_suspend - Optional but recommended function. Suspend the device. + * @dev: platform device + * @msg: the suspend event code. + * + * See Documentation/power/devices.txt for more information + */ +static int xxxfb_suspend(struct platform_device *dev, pm_message_t msg) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct xxxfb_par *par = info->par; + + /* suspend here */ + return 0; +} + +/** + * xxxfb_resume - Optional but recommended function. Resume the device. + * @dev: platform device + * + * See Documentation/power/devices.txt for more information + */ +static int xxxfb_resume(struct platform_dev *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct xxxfb_par *par = info->par; + + /* resume here */ + return 0; +} +#else +#define xxxfb_suspend NULL +#define xxxfb_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_device_driver xxxfb_driver = { + .probe = xxxfb_probe, + .remove = xxxfb_remove, + .suspend = xxxfb_suspend, /* optional but recommended */ + .resume = xxxfb_resume, /* optional but recommended */ + .driver = { + .name = "xxxfb", + }, +}; + +static struct platform_device *xxxfb_device; + +#ifndef MODULE + /* + * Setup + */ + +/* + * Only necessary if your driver takes special options, + * otherwise we fall back on the generic fb_setup(). + */ +int __init xxxfb_setup(char *options) +{ + /* Parse user specified options (`video=xxxfb:') */ +} +#endif /* MODULE */ + +static int __init xxxfb_init(void) +{ + int ret; + /* + * For kernel boot options (in 'video=xxxfb:<options>' format) + */ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("xxxfb", &option)) + return -ENODEV; + xxxfb_setup(option); +#endif + ret = platform_driver_register(&xxxfb_driver); + + if (!ret) { + xxxfb_device = platform_device_register_simple("xxxfb", 0, + NULL, 0); + + if (IS_ERR(xxxfb_device)) { + platform_driver_unregister(&xxxfb_driver); + ret = PTR_ERR(xxxfb_device); + } + } + + return ret; +} + +static void __exit xxxfb_exit(void) +{ + platform_device_unregister(xxxfb_device); + platform_driver_unregister(&xxxfb_driver); +} +#endif /* CONFIG_PCI */ + +/* ------------------------------------------------------------------------- */ + + + /* + * Modularization + */ + +module_init(xxxfb_init); +module_exit(xxxfb_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/sm501fb.c b/drivers/video/fbdev/sm501fb.c new file mode 100644 index 000000000000..1501979099dc --- /dev/null +++ b/drivers/video/fbdev/sm501fb.c @@ -0,0 +1,2240 @@ +/* linux/drivers/video/sm501fb.c + * + * Copyright (c) 2006 Simtec Electronics + * Vincent Sanders <vince@simtec.co.uk> + * Ben Dooks <ben@simtec.co.uk> + * + * 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. + * + * Framebuffer driver for the Silicon Motion SM501 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> + +#include <asm/uaccess.h> +#include <asm/div64.h> + +#ifdef CONFIG_PM +#include <linux/pm.h> +#endif + +#include <linux/sm501.h> +#include <linux/sm501-regs.h> + +#include "edid.h" + +static char *fb_mode = "640x480-16@60"; +static unsigned long default_bpp = 16; + +static struct fb_videomode sm501_default_mode = { + .refresh = 60, + .xres = 640, + .yres = 480, + .pixclock = 20833, + .left_margin = 142, + .right_margin = 13, + .upper_margin = 21, + .lower_margin = 1, + .hsync_len = 69, + .vsync_len = 3, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED +}; + +#define NR_PALETTE 256 + +enum sm501_controller { + HEAD_CRT = 0, + HEAD_PANEL = 1, +}; + +/* SM501 memory address. + * + * This structure is used to track memory usage within the SM501 framebuffer + * allocation. The sm_addr field is stored as an offset as it is often used + * against both the physical and mapped addresses. + */ +struct sm501_mem { + unsigned long size; + unsigned long sm_addr; /* offset from base of sm501 fb. */ + void __iomem *k_addr; +}; + +/* private data that is shared between all frambuffers* */ +struct sm501fb_info { + struct device *dev; + struct fb_info *fb[2]; /* fb info for both heads */ + struct resource *fbmem_res; /* framebuffer resource */ + struct resource *regs_res; /* registers resource */ + struct resource *regs2d_res; /* 2d registers resource */ + struct sm501_platdata_fb *pdata; /* our platform data */ + + unsigned long pm_crt_ctrl; /* pm: crt ctrl save */ + + int irq; + int swap_endian; /* set to swap rgb=>bgr */ + void __iomem *regs; /* remapped registers */ + void __iomem *regs2d; /* 2d remapped registers */ + void __iomem *fbmem; /* remapped framebuffer */ + size_t fbmem_len; /* length of remapped region */ + u8 *edid_data; +}; + +/* per-framebuffer private data */ +struct sm501fb_par { + u32 pseudo_palette[16]; + + enum sm501_controller head; + struct sm501_mem cursor; + struct sm501_mem screen; + struct fb_ops ops; + + void *store_fb; + void *store_cursor; + void __iomem *cursor_regs; + struct sm501fb_info *info; +}; + +/* Helper functions */ + +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; +} + +/* sm501fb_sync_regs() + * + * This call is mainly for PCI bus systems where we need to + * ensure that any writes to the bus are completed before the + * next phase, or after completing a function. +*/ + +static inline void sm501fb_sync_regs(struct sm501fb_info *info) +{ + smc501_readl(info->regs); +} + +/* sm501_alloc_mem + * + * This is an attempt to lay out memory for the two framebuffers and + * everything else + * + * |fbmem_res->start fbmem_res->end| + * | | + * |fb[0].fix.smem_start | |fb[1].fix.smem_start | 2K | + * |-> fb[0].fix.smem_len <-| spare |-> fb[1].fix.smem_len <-|-> cursors <-| + * + * The "spare" space is for the 2d engine data + * the fixed is space for the cursors (2x1Kbyte) + * + * we need to allocate memory for the 2D acceleration engine + * command list and the data for the engine to deal with. + * + * - all allocations must be 128bit aligned + * - cursors are 64x64x2 bits (1Kbyte) + * + */ + +#define SM501_MEMF_CURSOR (1) +#define SM501_MEMF_PANEL (2) +#define SM501_MEMF_CRT (4) +#define SM501_MEMF_ACCEL (8) + +static int sm501_alloc_mem(struct sm501fb_info *inf, struct sm501_mem *mem, + unsigned int why, size_t size, u32 smem_len) +{ + struct sm501fb_par *par; + struct fb_info *fbi; + unsigned int ptr; + unsigned int end; + + switch (why) { + case SM501_MEMF_CURSOR: + ptr = inf->fbmem_len - size; + inf->fbmem_len = ptr; /* adjust available memory. */ + break; + + case SM501_MEMF_PANEL: + if (size > inf->fbmem_len) + return -ENOMEM; + + ptr = inf->fbmem_len - size; + fbi = inf->fb[HEAD_CRT]; + + /* round down, some programs such as directfb do not draw + * 0,0 correctly unless the start is aligned to a page start. + */ + + if (ptr > 0) + ptr &= ~(PAGE_SIZE - 1); + + if (fbi && ptr < smem_len) + return -ENOMEM; + + break; + + case SM501_MEMF_CRT: + ptr = 0; + + /* check to see if we have panel memory allocated + * which would put an limit on available memory. */ + + fbi = inf->fb[HEAD_PANEL]; + if (fbi) { + par = fbi->par; + end = par->screen.k_addr ? par->screen.sm_addr : inf->fbmem_len; + } else + end = inf->fbmem_len; + + if ((ptr + size) > end) + return -ENOMEM; + + break; + + case SM501_MEMF_ACCEL: + fbi = inf->fb[HEAD_CRT]; + ptr = fbi ? smem_len : 0; + + fbi = inf->fb[HEAD_PANEL]; + if (fbi) { + par = fbi->par; + end = par->screen.sm_addr; + } else + end = inf->fbmem_len; + + if ((ptr + size) > end) + return -ENOMEM; + + break; + + default: + return -EINVAL; + } + + mem->size = size; + mem->sm_addr = ptr; + mem->k_addr = inf->fbmem + ptr; + + dev_dbg(inf->dev, "%s: result %08lx, %p - %u, %zd\n", + __func__, mem->sm_addr, mem->k_addr, why, size); + + return 0; +} + +/* sm501fb_ps_to_hz + * + * Converts a period in picoseconds to Hz. + * + * Note, we try to keep this in Hz to minimise rounding with + * the limited PLL settings on the SM501. +*/ + +static unsigned long sm501fb_ps_to_hz(unsigned long psvalue) +{ + unsigned long long numerator=1000000000000ULL; + + /* 10^12 / picosecond period gives frequency in Hz */ + do_div(numerator, psvalue); + return (unsigned long)numerator; +} + +/* sm501fb_hz_to_ps is identical to the opposite transform */ + +#define sm501fb_hz_to_ps(x) sm501fb_ps_to_hz(x) + +/* sm501fb_setup_gamma + * + * Programs a linear 1.0 gamma ramp in case the gamma + * correction is enabled without programming anything else. +*/ + +static void sm501fb_setup_gamma(struct sm501fb_info *fbi, + unsigned long palette) +{ + unsigned long value = 0; + int offset; + + /* set gamma values */ + for (offset = 0; offset < 256 * 4; offset += 4) { + smc501_writel(value, fbi->regs + palette + offset); + value += 0x010101; /* Advance RGB by 1,1,1.*/ + } +} + +/* sm501fb_check_var + * + * check common variables for both panel and crt +*/ + +static int sm501fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *sm = par->info; + unsigned long tmp; + + /* check we can fit these values into the registers */ + + if (var->hsync_len > 255 || var->vsync_len > 63) + return -EINVAL; + + /* hdisplay end and hsync start */ + if ((var->xres + var->right_margin) > 4096) + return -EINVAL; + + /* vdisplay end and vsync start */ + if ((var->yres + var->lower_margin) > 2048) + return -EINVAL; + + /* hard limits of device */ + + if (h_total(var) > 4096 || v_total(var) > 2048) + return -EINVAL; + + /* check our line length is going to be 128 bit aligned */ + + tmp = (var->xres * var->bits_per_pixel) / 8; + if ((tmp & 15) != 0) + return -EINVAL; + + /* check the virtual size */ + + if (var->xres_virtual > 4096 || var->yres_virtual > 2048) + return -EINVAL; + + /* can cope with 8,16 or 32bpp */ + + 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 == 24) + var->bits_per_pixel = 32; + + /* set r/g/b positions and validate bpp */ + switch(var->bits_per_pixel) { + case 8: + var->red.length = var->bits_per_pixel; + var->red.offset = 0; + var->green.length = var->bits_per_pixel; + var->green.offset = 0; + var->blue.length = var->bits_per_pixel; + var->blue.offset = 0; + var->transp.length = 0; + var->transp.offset = 0; + + break; + + case 16: + if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) { + var->blue.offset = 11; + var->green.offset = 5; + var->red.offset = 0; + } else { + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + } + var->transp.offset = 0; + + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + break; + + case 32: + if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) { + var->transp.offset = 0; + var->red.offset = 8; + var->green.offset = 16; + var->blue.offset = 24; + } else { + var->transp.offset = 24; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + } + + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * sm501fb_check_var_crt(): + * + * check the parameters for the CRT head, and either bring them + * back into range, or return -EINVAL. +*/ + +static int sm501fb_check_var_crt(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + return sm501fb_check_var(var, info); +} + +/* sm501fb_check_var_pnl(): + * + * check the parameters for the CRT head, and either bring them + * back into range, or return -EINVAL. +*/ + +static int sm501fb_check_var_pnl(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + return sm501fb_check_var(var, info); +} + +/* sm501fb_set_par_common + * + * set common registers for framebuffers +*/ + +static int sm501fb_set_par_common(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + unsigned long pixclock; /* pixelclock in Hz */ + unsigned long sm501pixclock; /* pixelclock the 501 can achieve in Hz */ + unsigned int mem_type; + unsigned int clock_type; + unsigned int head_addr; + unsigned int smem_len; + + dev_dbg(fbi->dev, "%s: %dx%d, bpp = %d, virtual %dx%d\n", + __func__, var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual); + + switch (par->head) { + case HEAD_CRT: + mem_type = SM501_MEMF_CRT; + clock_type = SM501_CLOCK_V2XCLK; + head_addr = SM501_DC_CRT_FB_ADDR; + break; + + case HEAD_PANEL: + mem_type = SM501_MEMF_PANEL; + clock_type = SM501_CLOCK_P2XCLK; + head_addr = SM501_DC_PANEL_FB_ADDR; + break; + + default: + mem_type = 0; /* stop compiler warnings */ + head_addr = 0; + clock_type = 0; + } + + switch (var->bits_per_pixel) { + case 8: + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + + case 16: + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + + case 32: + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + } + + /* allocate fb memory within 501 */ + info->fix.line_length = (var->xres_virtual * var->bits_per_pixel)/8; + smem_len = info->fix.line_length * var->yres_virtual; + + dev_dbg(fbi->dev, "%s: line length = %u\n", __func__, + info->fix.line_length); + + if (sm501_alloc_mem(fbi, &par->screen, mem_type, smem_len, smem_len)) { + dev_err(fbi->dev, "no memory available\n"); + return -ENOMEM; + } + + mutex_lock(&info->mm_lock); + info->fix.smem_start = fbi->fbmem_res->start + par->screen.sm_addr; + info->fix.smem_len = smem_len; + mutex_unlock(&info->mm_lock); + + info->screen_base = fbi->fbmem + par->screen.sm_addr; + info->screen_size = info->fix.smem_len; + + /* set start of framebuffer to the screen */ + + smc501_writel(par->screen.sm_addr | SM501_ADDR_FLIP, + fbi->regs + head_addr); + + /* program CRT clock */ + + pixclock = sm501fb_ps_to_hz(var->pixclock); + + sm501pixclock = sm501_set_clock(fbi->dev->parent, clock_type, + pixclock); + + /* update fb layer with actual clock used */ + var->pixclock = sm501fb_hz_to_ps(sm501pixclock); + + dev_dbg(fbi->dev, "%s: pixclock(ps) = %u, pixclock(Hz) = %lu, " + "sm501pixclock = %lu, error = %ld%%\n", + __func__, var->pixclock, pixclock, sm501pixclock, + ((pixclock - sm501pixclock)*100)/pixclock); + + return 0; +} + +/* sm501fb_set_par_geometry + * + * set the geometry registers for specified framebuffer. +*/ + +static void sm501fb_set_par_geometry(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + void __iomem *base = fbi->regs; + unsigned long reg; + + if (par->head == HEAD_CRT) + base += SM501_DC_CRT_H_TOT; + else + base += SM501_DC_PANEL_H_TOT; + + /* set framebuffer width and display width */ + + reg = info->fix.line_length; + reg |= ((var->xres * var->bits_per_pixel)/8) << 16; + + smc501_writel(reg, fbi->regs + (par->head == HEAD_CRT ? + SM501_DC_CRT_FB_OFFSET : SM501_DC_PANEL_FB_OFFSET)); + + /* program horizontal total */ + + reg = (h_total(var) - 1) << 16; + reg |= (var->xres - 1); + + smc501_writel(reg, base + SM501_OFF_DC_H_TOT); + + /* program horizontal sync */ + + reg = var->hsync_len << 16; + reg |= var->xres + var->right_margin - 1; + + smc501_writel(reg, base + SM501_OFF_DC_H_SYNC); + + /* program vertical total */ + + reg = (v_total(var) - 1) << 16; + reg |= (var->yres - 1); + + smc501_writel(reg, base + SM501_OFF_DC_V_TOT); + + /* program vertical sync */ + reg = var->vsync_len << 16; + reg |= var->yres + var->lower_margin - 1; + + smc501_writel(reg, base + SM501_OFF_DC_V_SYNC); +} + +/* sm501fb_pan_crt + * + * pan the CRT display output within an virtual framebuffer +*/ + +static int sm501fb_pan_crt(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + unsigned int bytes_pixel = info->var.bits_per_pixel / 8; + unsigned long reg; + unsigned long xoffs; + + xoffs = var->xoffset * bytes_pixel; + + reg = smc501_readl(fbi->regs + SM501_DC_CRT_CONTROL); + + reg &= ~SM501_DC_CRT_CONTROL_PIXEL_MASK; + reg |= ((xoffs & 15) / bytes_pixel) << 4; + smc501_writel(reg, fbi->regs + SM501_DC_CRT_CONTROL); + + reg = (par->screen.sm_addr + xoffs + + var->yoffset * info->fix.line_length); + smc501_writel(reg | SM501_ADDR_FLIP, fbi->regs + SM501_DC_CRT_FB_ADDR); + + sm501fb_sync_regs(fbi); + return 0; +} + +/* sm501fb_pan_pnl + * + * pan the panel display output within an virtual framebuffer +*/ + +static int sm501fb_pan_pnl(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + unsigned long reg; + + reg = var->xoffset | (info->var.xres_virtual << 16); + smc501_writel(reg, fbi->regs + SM501_DC_PANEL_FB_WIDTH); + + reg = var->yoffset | (info->var.yres_virtual << 16); + smc501_writel(reg, fbi->regs + SM501_DC_PANEL_FB_HEIGHT); + + sm501fb_sync_regs(fbi); + return 0; +} + +/* sm501fb_set_par_crt + * + * Set the CRT video mode from the fb_info structure +*/ + +static int sm501fb_set_par_crt(struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + struct fb_var_screeninfo *var = &info->var; + unsigned long control; /* control register */ + int ret; + + /* activate new configuration */ + + dev_dbg(fbi->dev, "%s(%p)\n", __func__, info); + + /* enable CRT DAC - note 0 is on!*/ + sm501_misc_control(fbi->dev->parent, 0, SM501_MISC_DAC_POWER); + + control = smc501_readl(fbi->regs + SM501_DC_CRT_CONTROL); + + control &= (SM501_DC_CRT_CONTROL_PIXEL_MASK | + SM501_DC_CRT_CONTROL_GAMMA | + SM501_DC_CRT_CONTROL_BLANK | + SM501_DC_CRT_CONTROL_SEL | + SM501_DC_CRT_CONTROL_CP | + SM501_DC_CRT_CONTROL_TVP); + + /* set the sync polarities before we check data source */ + + if ((var->sync & FB_SYNC_HOR_HIGH_ACT) == 0) + control |= SM501_DC_CRT_CONTROL_HSP; + + if ((var->sync & FB_SYNC_VERT_HIGH_ACT) == 0) + control |= SM501_DC_CRT_CONTROL_VSP; + + if ((control & SM501_DC_CRT_CONTROL_SEL) == 0) { + /* the head is displaying panel data... */ + + sm501_alloc_mem(fbi, &par->screen, SM501_MEMF_CRT, 0, + info->fix.smem_len); + goto out_update; + } + + ret = sm501fb_set_par_common(info, var); + if (ret) { + dev_err(fbi->dev, "failed to set common parameters\n"); + return ret; + } + + sm501fb_pan_crt(var, info); + sm501fb_set_par_geometry(info, var); + + control |= SM501_FIFO_3; /* fill if >3 free slots */ + + switch(var->bits_per_pixel) { + case 8: + control |= SM501_DC_CRT_CONTROL_8BPP; + break; + + case 16: + control |= SM501_DC_CRT_CONTROL_16BPP; + sm501fb_setup_gamma(fbi, SM501_DC_CRT_PALETTE); + break; + + case 32: + control |= SM501_DC_CRT_CONTROL_32BPP; + sm501fb_setup_gamma(fbi, SM501_DC_CRT_PALETTE); + break; + + default: + BUG(); + } + + control |= SM501_DC_CRT_CONTROL_SEL; /* CRT displays CRT data */ + control |= SM501_DC_CRT_CONTROL_TE; /* enable CRT timing */ + control |= SM501_DC_CRT_CONTROL_ENABLE; /* enable CRT plane */ + + out_update: + dev_dbg(fbi->dev, "new control is %08lx\n", control); + + smc501_writel(control, fbi->regs + SM501_DC_CRT_CONTROL); + sm501fb_sync_regs(fbi); + + return 0; +} + +static void sm501fb_panel_power(struct sm501fb_info *fbi, int to) +{ + unsigned long control; + void __iomem *ctrl_reg = fbi->regs + SM501_DC_PANEL_CONTROL; + struct sm501_platdata_fbsub *pd = fbi->pdata->fb_pnl; + + control = smc501_readl(ctrl_reg); + + if (to && (control & SM501_DC_PANEL_CONTROL_VDD) == 0) { + /* enable panel power */ + + control |= SM501_DC_PANEL_CONTROL_VDD; /* FPVDDEN */ + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + + control |= SM501_DC_PANEL_CONTROL_DATA; /* DATA */ + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + + /* VBIASEN */ + + if (!(pd->flags & SM501FB_FLAG_PANEL_NO_VBIASEN)) { + if (pd->flags & SM501FB_FLAG_PANEL_INV_VBIASEN) + control &= ~SM501_DC_PANEL_CONTROL_BIAS; + else + control |= SM501_DC_PANEL_CONTROL_BIAS; + + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + } + + if (!(pd->flags & SM501FB_FLAG_PANEL_NO_FPEN)) { + if (pd->flags & SM501FB_FLAG_PANEL_INV_FPEN) + control &= ~SM501_DC_PANEL_CONTROL_FPEN; + else + control |= SM501_DC_PANEL_CONTROL_FPEN; + + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + } + } else if (!to && (control & SM501_DC_PANEL_CONTROL_VDD) != 0) { + /* disable panel power */ + if (!(pd->flags & SM501FB_FLAG_PANEL_NO_FPEN)) { + if (pd->flags & SM501FB_FLAG_PANEL_INV_FPEN) + control |= SM501_DC_PANEL_CONTROL_FPEN; + else + control &= ~SM501_DC_PANEL_CONTROL_FPEN; + + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + } + + if (!(pd->flags & SM501FB_FLAG_PANEL_NO_VBIASEN)) { + if (pd->flags & SM501FB_FLAG_PANEL_INV_VBIASEN) + control |= SM501_DC_PANEL_CONTROL_BIAS; + else + control &= ~SM501_DC_PANEL_CONTROL_BIAS; + + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + } + + control &= ~SM501_DC_PANEL_CONTROL_DATA; + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + + control &= ~SM501_DC_PANEL_CONTROL_VDD; + smc501_writel(control, ctrl_reg); + sm501fb_sync_regs(fbi); + mdelay(10); + } + + sm501fb_sync_regs(fbi); +} + +/* sm501fb_set_par_pnl + * + * Set the panel video mode from the fb_info structure +*/ + +static int sm501fb_set_par_pnl(struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + struct fb_var_screeninfo *var = &info->var; + unsigned long control; + unsigned long reg; + int ret; + + dev_dbg(fbi->dev, "%s(%p)\n", __func__, info); + + /* activate this new configuration */ + + ret = sm501fb_set_par_common(info, var); + if (ret) + return ret; + + sm501fb_pan_pnl(var, info); + sm501fb_set_par_geometry(info, var); + + /* update control register */ + + control = smc501_readl(fbi->regs + SM501_DC_PANEL_CONTROL); + control &= (SM501_DC_PANEL_CONTROL_GAMMA | + SM501_DC_PANEL_CONTROL_VDD | + SM501_DC_PANEL_CONTROL_DATA | + SM501_DC_PANEL_CONTROL_BIAS | + SM501_DC_PANEL_CONTROL_FPEN | + SM501_DC_PANEL_CONTROL_CP | + SM501_DC_PANEL_CONTROL_CK | + SM501_DC_PANEL_CONTROL_HP | + SM501_DC_PANEL_CONTROL_VP | + SM501_DC_PANEL_CONTROL_HPD | + SM501_DC_PANEL_CONTROL_VPD); + + control |= SM501_FIFO_3; /* fill if >3 free slots */ + + switch(var->bits_per_pixel) { + case 8: + control |= SM501_DC_PANEL_CONTROL_8BPP; + break; + + case 16: + control |= SM501_DC_PANEL_CONTROL_16BPP; + sm501fb_setup_gamma(fbi, SM501_DC_PANEL_PALETTE); + break; + + case 32: + control |= SM501_DC_PANEL_CONTROL_32BPP; + sm501fb_setup_gamma(fbi, SM501_DC_PANEL_PALETTE); + break; + + default: + BUG(); + } + + smc501_writel(0x0, fbi->regs + SM501_DC_PANEL_PANNING_CONTROL); + + /* panel plane top left and bottom right location */ + + smc501_writel(0x00, fbi->regs + SM501_DC_PANEL_TL_LOC); + + reg = var->xres - 1; + reg |= (var->yres - 1) << 16; + + smc501_writel(reg, fbi->regs + SM501_DC_PANEL_BR_LOC); + + /* program panel control register */ + + control |= SM501_DC_PANEL_CONTROL_TE; /* enable PANEL timing */ + control |= SM501_DC_PANEL_CONTROL_EN; /* enable PANEL gfx plane */ + + if ((var->sync & FB_SYNC_HOR_HIGH_ACT) == 0) + control |= SM501_DC_PANEL_CONTROL_HSP; + + if ((var->sync & FB_SYNC_VERT_HIGH_ACT) == 0) + control |= SM501_DC_PANEL_CONTROL_VSP; + + smc501_writel(control, fbi->regs + SM501_DC_PANEL_CONTROL); + sm501fb_sync_regs(fbi); + + /* ensure the panel interface is not tristated at this point */ + + sm501_modify_reg(fbi->dev->parent, SM501_SYSTEM_CONTROL, + 0, SM501_SYSCTRL_PANEL_TRISTATE); + + /* power the panel up */ + sm501fb_panel_power(fbi, 1); + return 0; +} + + +/* chan_to_field + * + * convert a colour value into a field position + * + * from pxafb.c +*/ + +static inline unsigned int chan_to_field(unsigned int chan, + struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +/* sm501fb_setcolreg + * + * set the colour mapping for modes that support palettised data +*/ + +static int sm501fb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + void __iomem *base = fbi->regs; + unsigned int val; + + if (par->head == HEAD_CRT) + base += SM501_DC_CRT_PALETTE; + else + base += SM501_DC_PANEL_PALETTE; + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* true-colour, use pseuo-palette */ + + if (regno < 16) { + u32 *pal = par->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + } + break; + + case FB_VISUAL_PSEUDOCOLOR: + if (regno < 256) { + val = (red >> 8) << 16; + val |= (green >> 8) << 8; + val |= blue >> 8; + + smc501_writel(val, base + (regno * 4)); + } + + break; + + default: + return 1; /* unknown type */ + } + + return 0; +} + +/* sm501fb_blank_pnl + * + * Blank or un-blank the panel interface +*/ + +static int sm501fb_blank_pnl(int blank_mode, struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + + dev_dbg(fbi->dev, "%s(mode=%d, %p)\n", __func__, blank_mode, info); + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + sm501fb_panel_power(fbi, 0); + break; + + case FB_BLANK_UNBLANK: + sm501fb_panel_power(fbi, 1); + break; + + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + default: + return 1; + } + + return 0; +} + +/* sm501fb_blank_crt + * + * Blank or un-blank the crt interface +*/ + +static int sm501fb_blank_crt(int blank_mode, struct fb_info *info) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + unsigned long ctrl; + + dev_dbg(fbi->dev, "%s(mode=%d, %p)\n", __func__, blank_mode, info); + + ctrl = smc501_readl(fbi->regs + SM501_DC_CRT_CONTROL); + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + ctrl &= ~SM501_DC_CRT_CONTROL_ENABLE; + sm501_misc_control(fbi->dev->parent, SM501_MISC_DAC_POWER, 0); + + case FB_BLANK_NORMAL: + ctrl |= SM501_DC_CRT_CONTROL_BLANK; + break; + + case FB_BLANK_UNBLANK: + ctrl &= ~SM501_DC_CRT_CONTROL_BLANK; + ctrl |= SM501_DC_CRT_CONTROL_ENABLE; + sm501_misc_control(fbi->dev->parent, 0, SM501_MISC_DAC_POWER); + break; + + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + default: + return 1; + + } + + smc501_writel(ctrl, fbi->regs + SM501_DC_CRT_CONTROL); + sm501fb_sync_regs(fbi); + + return 0; +} + +/* sm501fb_cursor + * + * set or change the hardware cursor parameters +*/ + +static int sm501fb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + void __iomem *base = fbi->regs; + unsigned long hwc_addr; + unsigned long fg, bg; + + dev_dbg(fbi->dev, "%s(%p,%p)\n", __func__, info, cursor); + + if (par->head == HEAD_CRT) + base += SM501_DC_CRT_HWC_BASE; + else + base += SM501_DC_PANEL_HWC_BASE; + + /* check not being asked to exceed capabilities */ + + if (cursor->image.width > 64) + return -EINVAL; + + if (cursor->image.height > 64) + return -EINVAL; + + if (cursor->image.depth > 1) + return -EINVAL; + + hwc_addr = smc501_readl(base + SM501_OFF_HWC_ADDR); + + if (cursor->enable) + smc501_writel(hwc_addr | SM501_HWC_EN, + base + SM501_OFF_HWC_ADDR); + else + smc501_writel(hwc_addr & ~SM501_HWC_EN, + base + SM501_OFF_HWC_ADDR); + + /* set data */ + if (cursor->set & FB_CUR_SETPOS) { + unsigned int x = cursor->image.dx; + unsigned int y = cursor->image.dy; + + if (x >= 2048 || y >= 2048 ) + return -EINVAL; + + dev_dbg(fbi->dev, "set position %d,%d\n", x, y); + + //y += cursor->image.height; + + smc501_writel(x | (y << 16), base + SM501_OFF_HWC_LOC); + } + + if (cursor->set & FB_CUR_SETCMAP) { + unsigned int bg_col = cursor->image.bg_color; + unsigned int fg_col = cursor->image.fg_color; + + dev_dbg(fbi->dev, "%s: update cmap (%08x,%08x)\n", + __func__, bg_col, fg_col); + + bg = ((info->cmap.red[bg_col] & 0xF8) << 8) | + ((info->cmap.green[bg_col] & 0xFC) << 3) | + ((info->cmap.blue[bg_col] & 0xF8) >> 3); + + fg = ((info->cmap.red[fg_col] & 0xF8) << 8) | + ((info->cmap.green[fg_col] & 0xFC) << 3) | + ((info->cmap.blue[fg_col] & 0xF8) >> 3); + + dev_dbg(fbi->dev, "fgcol %08lx, bgcol %08lx\n", fg, bg); + + smc501_writel(bg, base + SM501_OFF_HWC_COLOR_1_2); + smc501_writel(fg, base + SM501_OFF_HWC_COLOR_3); + } + + if (cursor->set & FB_CUR_SETSIZE || + cursor->set & (FB_CUR_SETIMAGE | FB_CUR_SETSHAPE)) { + /* SM501 cursor is a two bpp 64x64 bitmap this routine + * clears it to transparent then combines the cursor + * shape plane with the colour plane to set the + * cursor */ + int x, y; + const unsigned char *pcol = cursor->image.data; + const unsigned char *pmsk = cursor->mask; + void __iomem *dst = par->cursor.k_addr; + unsigned char dcol = 0; + unsigned char dmsk = 0; + unsigned int op; + + dev_dbg(fbi->dev, "%s: setting shape (%d,%d)\n", + __func__, cursor->image.width, cursor->image.height); + + for (op = 0; op < (64*64*2)/8; op+=4) + smc501_writel(0x0, dst + op); + + for (y = 0; y < cursor->image.height; y++) { + for (x = 0; x < cursor->image.width; x++) { + if ((x % 8) == 0) { + dcol = *pcol++; + dmsk = *pmsk++; + } else { + dcol >>= 1; + dmsk >>= 1; + } + + if (dmsk & 1) { + op = (dcol & 1) ? 1 : 3; + op <<= ((x % 4) * 2); + + op |= readb(dst + (x / 4)); + writeb(op, dst + (x / 4)); + } + } + dst += (64*2)/8; + } + } + + sm501fb_sync_regs(fbi); /* ensure cursor data flushed */ + return 0; +} + +/* sm501fb_crtsrc_show + * + * device attribute code to show where the crt output is sourced from +*/ + +static ssize_t sm501fb_crtsrc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sm501fb_info *info = dev_get_drvdata(dev); + unsigned long ctrl; + + ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL); + ctrl &= SM501_DC_CRT_CONTROL_SEL; + + return snprintf(buf, PAGE_SIZE, "%s\n", ctrl ? "crt" : "panel"); +} + +/* sm501fb_crtsrc_show + * + * device attribute code to set where the crt output is sourced from +*/ + +static ssize_t sm501fb_crtsrc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct sm501fb_info *info = dev_get_drvdata(dev); + enum sm501_controller head; + unsigned long ctrl; + + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "crt", 3) == 0) + head = HEAD_CRT; + else if (strnicmp(buf, "panel", 5) == 0) + head = HEAD_PANEL; + else + return -EINVAL; + + dev_info(dev, "setting crt source to head %d\n", head); + + ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL); + + if (head == HEAD_CRT) { + ctrl |= SM501_DC_CRT_CONTROL_SEL; + ctrl |= SM501_DC_CRT_CONTROL_ENABLE; + ctrl |= SM501_DC_CRT_CONTROL_TE; + } else { + ctrl &= ~SM501_DC_CRT_CONTROL_SEL; + ctrl &= ~SM501_DC_CRT_CONTROL_ENABLE; + ctrl &= ~SM501_DC_CRT_CONTROL_TE; + } + + smc501_writel(ctrl, info->regs + SM501_DC_CRT_CONTROL); + sm501fb_sync_regs(info); + + return len; +} + +/* Prepare the device_attr for registration with sysfs later */ +static DEVICE_ATTR(crt_src, 0666, sm501fb_crtsrc_show, sm501fb_crtsrc_store); + +/* sm501fb_show_regs + * + * show the primary sm501 registers +*/ +static int sm501fb_show_regs(struct sm501fb_info *info, char *ptr, + unsigned int start, unsigned int len) +{ + void __iomem *mem = info->regs; + char *buf = ptr; + unsigned int reg; + + for (reg = start; reg < (len + start); reg += 4) + ptr += sprintf(ptr, "%08x = %08x\n", reg, + smc501_readl(mem + reg)); + + return ptr - buf; +} + +/* sm501fb_debug_show_crt + * + * show the crt control and cursor registers +*/ + +static ssize_t sm501fb_debug_show_crt(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sm501fb_info *info = dev_get_drvdata(dev); + char *ptr = buf; + + ptr += sm501fb_show_regs(info, ptr, SM501_DC_CRT_CONTROL, 0x40); + ptr += sm501fb_show_regs(info, ptr, SM501_DC_CRT_HWC_BASE, 0x10); + + return ptr - buf; +} + +static DEVICE_ATTR(fbregs_crt, 0444, sm501fb_debug_show_crt, NULL); + +/* sm501fb_debug_show_pnl + * + * show the panel control and cursor registers +*/ + +static ssize_t sm501fb_debug_show_pnl(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sm501fb_info *info = dev_get_drvdata(dev); + char *ptr = buf; + + ptr += sm501fb_show_regs(info, ptr, 0x0, 0x40); + ptr += sm501fb_show_regs(info, ptr, SM501_DC_PANEL_HWC_BASE, 0x10); + + return ptr - buf; +} + +static DEVICE_ATTR(fbregs_pnl, 0444, sm501fb_debug_show_pnl, NULL); + +/* acceleration operations */ +static int sm501fb_sync(struct fb_info *info) +{ + int count = 1000000; + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + + /* wait for the 2d engine to be ready */ + while ((count > 0) && + (smc501_readl(fbi->regs + SM501_SYSTEM_CONTROL) & + SM501_SYSCTRL_2D_ENGINE_STATUS) != 0) + count--; + + if (count <= 0) { + dev_err(info->dev, "Timeout waiting for 2d engine sync\n"); + return 1; + } + return 0; +} + +static void sm501fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + int width = area->width; + int height = area->height; + int sx = area->sx; + int sy = area->sy; + int dx = area->dx; + int dy = area->dy; + unsigned long rtl = 0; + + /* source clip */ + if ((sx >= info->var.xres_virtual) || + (sy >= info->var.yres_virtual)) + /* source Area not within virtual screen, skipping */ + return; + if ((sx + width) >= info->var.xres_virtual) + width = info->var.xres_virtual - sx - 1; + if ((sy + height) >= info->var.yres_virtual) + height = info->var.yres_virtual - sy - 1; + + /* dest clip */ + if ((dx >= info->var.xres_virtual) || + (dy >= info->var.yres_virtual)) + /* Destination Area not within virtual screen, skipping */ + return; + if ((dx + width) >= info->var.xres_virtual) + width = info->var.xres_virtual - dx - 1; + if ((dy + height) >= info->var.yres_virtual) + height = info->var.yres_virtual - dy - 1; + + if ((sx < dx) || (sy < dy)) { + rtl = 1 << 27; + sx += width - 1; + dx += width - 1; + sy += height - 1; + dy += height - 1; + } + + if (sm501fb_sync(info)) + return; + + /* set the base addresses */ + smc501_writel(par->screen.sm_addr, fbi->regs2d + SM501_2D_SOURCE_BASE); + smc501_writel(par->screen.sm_addr, + fbi->regs2d + SM501_2D_DESTINATION_BASE); + + /* set the window width */ + smc501_writel((info->var.xres << 16) | info->var.xres, + fbi->regs2d + SM501_2D_WINDOW_WIDTH); + + /* set window stride */ + smc501_writel((info->var.xres_virtual << 16) | info->var.xres_virtual, + fbi->regs2d + SM501_2D_PITCH); + + /* set data format */ + switch (info->var.bits_per_pixel) { + case 8: + smc501_writel(0, fbi->regs2d + SM501_2D_STRETCH); + break; + case 16: + smc501_writel(0x00100000, fbi->regs2d + SM501_2D_STRETCH); + break; + case 32: + smc501_writel(0x00200000, fbi->regs2d + SM501_2D_STRETCH); + break; + } + + /* 2d compare mask */ + smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_COLOR_COMPARE_MASK); + + /* 2d mask */ + smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_MASK); + + /* source and destination x y */ + smc501_writel((sx << 16) | sy, fbi->regs2d + SM501_2D_SOURCE); + smc501_writel((dx << 16) | dy, fbi->regs2d + SM501_2D_DESTINATION); + + /* w/h */ + smc501_writel((width << 16) | height, fbi->regs2d + SM501_2D_DIMENSION); + + /* do area move */ + smc501_writel(0x800000cc | rtl, fbi->regs2d + SM501_2D_CONTROL); +} + +static void sm501fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct sm501fb_par *par = info->par; + struct sm501fb_info *fbi = par->info; + int width = rect->width, height = rect->height; + + if ((rect->dx >= info->var.xres_virtual) || + (rect->dy >= info->var.yres_virtual)) + /* Rectangle not within virtual screen, skipping */ + return; + if ((rect->dx + width) >= info->var.xres_virtual) + width = info->var.xres_virtual - rect->dx - 1; + if ((rect->dy + height) >= info->var.yres_virtual) + height = info->var.yres_virtual - rect->dy - 1; + + if (sm501fb_sync(info)) + return; + + /* set the base addresses */ + smc501_writel(par->screen.sm_addr, fbi->regs2d + SM501_2D_SOURCE_BASE); + smc501_writel(par->screen.sm_addr, + fbi->regs2d + SM501_2D_DESTINATION_BASE); + + /* set the window width */ + smc501_writel((info->var.xres << 16) | info->var.xres, + fbi->regs2d + SM501_2D_WINDOW_WIDTH); + + /* set window stride */ + smc501_writel((info->var.xres_virtual << 16) | info->var.xres_virtual, + fbi->regs2d + SM501_2D_PITCH); + + /* set data format */ + switch (info->var.bits_per_pixel) { + case 8: + smc501_writel(0, fbi->regs2d + SM501_2D_STRETCH); + break; + case 16: + smc501_writel(0x00100000, fbi->regs2d + SM501_2D_STRETCH); + break; + case 32: + smc501_writel(0x00200000, fbi->regs2d + SM501_2D_STRETCH); + break; + } + + /* 2d compare mask */ + smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_COLOR_COMPARE_MASK); + + /* 2d mask */ + smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_MASK); + + /* colour */ + smc501_writel(rect->color, fbi->regs2d + SM501_2D_FOREGROUND); + + /* x y */ + smc501_writel((rect->dx << 16) | rect->dy, + fbi->regs2d + SM501_2D_DESTINATION); + + /* w/h */ + smc501_writel((width << 16) | height, fbi->regs2d + SM501_2D_DIMENSION); + + /* do rectangle fill */ + smc501_writel(0x800100cc, fbi->regs2d + SM501_2D_CONTROL); +} + + +static struct fb_ops sm501fb_ops_crt = { + .owner = THIS_MODULE, + .fb_check_var = sm501fb_check_var_crt, + .fb_set_par = sm501fb_set_par_crt, + .fb_blank = sm501fb_blank_crt, + .fb_setcolreg = sm501fb_setcolreg, + .fb_pan_display = sm501fb_pan_crt, + .fb_cursor = sm501fb_cursor, + .fb_fillrect = sm501fb_fillrect, + .fb_copyarea = sm501fb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_sync = sm501fb_sync, +}; + +static struct fb_ops sm501fb_ops_pnl = { + .owner = THIS_MODULE, + .fb_check_var = sm501fb_check_var_pnl, + .fb_set_par = sm501fb_set_par_pnl, + .fb_pan_display = sm501fb_pan_pnl, + .fb_blank = sm501fb_blank_pnl, + .fb_setcolreg = sm501fb_setcolreg, + .fb_cursor = sm501fb_cursor, + .fb_fillrect = sm501fb_fillrect, + .fb_copyarea = sm501fb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_sync = sm501fb_sync, +}; + +/* sm501_init_cursor + * + * initialise hw cursor parameters +*/ + +static int sm501_init_cursor(struct fb_info *fbi, unsigned int reg_base) +{ + struct sm501fb_par *par; + struct sm501fb_info *info; + int ret; + + if (fbi == NULL) + return 0; + + par = fbi->par; + info = par->info; + + par->cursor_regs = info->regs + reg_base; + + ret = sm501_alloc_mem(info, &par->cursor, SM501_MEMF_CURSOR, 1024, + fbi->fix.smem_len); + if (ret < 0) + return ret; + + /* initialise the colour registers */ + + smc501_writel(par->cursor.sm_addr, + par->cursor_regs + SM501_OFF_HWC_ADDR); + + smc501_writel(0x00, par->cursor_regs + SM501_OFF_HWC_LOC); + smc501_writel(0x00, par->cursor_regs + SM501_OFF_HWC_COLOR_1_2); + smc501_writel(0x00, par->cursor_regs + SM501_OFF_HWC_COLOR_3); + sm501fb_sync_regs(info); + + return 0; +} + +/* sm501fb_info_start + * + * fills the par structure claiming resources and remapping etc. +*/ + +static int sm501fb_start(struct sm501fb_info *info, + struct platform_device *pdev) +{ + struct resource *res; + struct device *dev = &pdev->dev; + int k; + int ret; + + info->irq = ret = platform_get_irq(pdev, 0); + if (ret < 0) { + /* we currently do not use the IRQ */ + dev_warn(dev, "no irq for device\n"); + } + + /* allocate, reserve and remap resources for display + * controller registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(dev, "no resource definition for registers\n"); + ret = -ENOENT; + goto err_release; + } + + info->regs_res = request_mem_region(res->start, + resource_size(res), + pdev->name); + + if (info->regs_res == NULL) { + dev_err(dev, "cannot claim registers\n"); + ret = -ENXIO; + goto err_release; + } + + info->regs = ioremap(res->start, resource_size(res)); + if (info->regs == NULL) { + dev_err(dev, "cannot remap registers\n"); + ret = -ENXIO; + goto err_regs_res; + } + + /* allocate, reserve and remap resources for 2d + * controller registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res == NULL) { + dev_err(dev, "no resource definition for 2d registers\n"); + ret = -ENOENT; + goto err_regs_map; + } + + info->regs2d_res = request_mem_region(res->start, + resource_size(res), + pdev->name); + + if (info->regs2d_res == NULL) { + dev_err(dev, "cannot claim registers\n"); + ret = -ENXIO; + goto err_regs_map; + } + + info->regs2d = ioremap(res->start, resource_size(res)); + if (info->regs2d == NULL) { + dev_err(dev, "cannot remap registers\n"); + ret = -ENXIO; + goto err_regs2d_res; + } + + /* allocate, reserve resources for framebuffer */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (res == NULL) { + dev_err(dev, "no memory resource defined\n"); + ret = -ENXIO; + goto err_regs2d_map; + } + + info->fbmem_res = request_mem_region(res->start, + resource_size(res), + pdev->name); + if (info->fbmem_res == NULL) { + dev_err(dev, "cannot claim framebuffer\n"); + ret = -ENXIO; + goto err_regs2d_map; + } + + info->fbmem = ioremap(res->start, resource_size(res)); + if (info->fbmem == NULL) { + dev_err(dev, "cannot remap framebuffer\n"); + goto err_mem_res; + } + + info->fbmem_len = resource_size(res); + + /* clear framebuffer memory - avoids garbage data on unused fb */ + memset(info->fbmem, 0, info->fbmem_len); + + /* clear palette ram - undefined at power on */ + for (k = 0; k < (256 * 3); k++) + smc501_writel(0, info->regs + SM501_DC_PANEL_PALETTE + (k * 4)); + + /* enable display controller */ + sm501_unit_power(dev->parent, SM501_GATE_DISPLAY, 1); + + /* enable 2d controller */ + sm501_unit_power(dev->parent, SM501_GATE_2D_ENGINE, 1); + + /* setup cursors */ + sm501_init_cursor(info->fb[HEAD_CRT], SM501_DC_CRT_HWC_ADDR); + sm501_init_cursor(info->fb[HEAD_PANEL], SM501_DC_PANEL_HWC_ADDR); + + return 0; /* everything is setup */ + + err_mem_res: + release_mem_region(info->fbmem_res->start, + resource_size(info->fbmem_res)); + + err_regs2d_map: + iounmap(info->regs2d); + + err_regs2d_res: + release_mem_region(info->regs2d_res->start, + resource_size(info->regs2d_res)); + + err_regs_map: + iounmap(info->regs); + + err_regs_res: + release_mem_region(info->regs_res->start, + resource_size(info->regs_res)); + + err_release: + return ret; +} + +static void sm501fb_stop(struct sm501fb_info *info) +{ + /* disable display controller */ + sm501_unit_power(info->dev->parent, SM501_GATE_DISPLAY, 0); + + iounmap(info->fbmem); + release_mem_region(info->fbmem_res->start, + resource_size(info->fbmem_res)); + + iounmap(info->regs2d); + release_mem_region(info->regs2d_res->start, + resource_size(info->regs2d_res)); + + iounmap(info->regs); + release_mem_region(info->regs_res->start, + resource_size(info->regs_res)); +} + +static int sm501fb_init_fb(struct fb_info *fb, enum sm501_controller head, + const char *fbname) +{ + struct sm501_platdata_fbsub *pd; + struct sm501fb_par *par = fb->par; + struct sm501fb_info *info = par->info; + unsigned long ctrl; + unsigned int enable; + int ret; + + switch (head) { + case HEAD_CRT: + pd = info->pdata->fb_crt; + ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL); + enable = (ctrl & SM501_DC_CRT_CONTROL_ENABLE) ? 1 : 0; + + /* ensure we set the correct source register */ + if (info->pdata->fb_route != SM501_FB_CRT_PANEL) { + ctrl |= SM501_DC_CRT_CONTROL_SEL; + smc501_writel(ctrl, info->regs + SM501_DC_CRT_CONTROL); + } + + break; + + case HEAD_PANEL: + pd = info->pdata->fb_pnl; + ctrl = smc501_readl(info->regs + SM501_DC_PANEL_CONTROL); + enable = (ctrl & SM501_DC_PANEL_CONTROL_EN) ? 1 : 0; + break; + + default: + pd = NULL; /* stop compiler warnings */ + ctrl = 0; + enable = 0; + BUG(); + } + + dev_info(info->dev, "fb %s %sabled at start\n", + fbname, enable ? "en" : "dis"); + + /* check to see if our routing allows this */ + + if (head == HEAD_CRT && info->pdata->fb_route == SM501_FB_CRT_PANEL) { + ctrl &= ~SM501_DC_CRT_CONTROL_SEL; + smc501_writel(ctrl, info->regs + SM501_DC_CRT_CONTROL); + enable = 0; + } + + strlcpy(fb->fix.id, fbname, sizeof(fb->fix.id)); + + memcpy(&par->ops, + (head == HEAD_CRT) ? &sm501fb_ops_crt : &sm501fb_ops_pnl, + sizeof(struct fb_ops)); + + /* update ops dependent on what we've been passed */ + + if ((pd->flags & SM501FB_FLAG_USE_HWCURSOR) == 0) + par->ops.fb_cursor = NULL; + + fb->fbops = &par->ops; + fb->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST | + FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; + +#if defined(CONFIG_OF) +#ifdef __BIG_ENDIAN + if (of_get_property(info->dev->parent->of_node, "little-endian", NULL)) + fb->flags |= FBINFO_FOREIGN_ENDIAN; +#else + if (of_get_property(info->dev->parent->of_node, "big-endian", NULL)) + fb->flags |= FBINFO_FOREIGN_ENDIAN; +#endif +#endif + /* fixed data */ + + fb->fix.type = FB_TYPE_PACKED_PIXELS; + fb->fix.type_aux = 0; + fb->fix.xpanstep = 1; + fb->fix.ypanstep = 1; + fb->fix.ywrapstep = 0; + fb->fix.accel = FB_ACCEL_NONE; + + /* screenmode */ + + fb->var.nonstd = 0; + fb->var.activate = FB_ACTIVATE_NOW; + fb->var.accel_flags = 0; + fb->var.vmode = FB_VMODE_NONINTERLACED; + fb->var.bits_per_pixel = 16; + + if (info->edid_data) { + /* Now build modedb from EDID */ + fb_edid_to_monspecs(info->edid_data, &fb->monspecs); + fb_videomode_to_modelist(fb->monspecs.modedb, + fb->monspecs.modedb_len, + &fb->modelist); + } + + if (enable && (pd->flags & SM501FB_FLAG_USE_INIT_MODE) && 0) { + /* TODO read the mode from the current display */ + } else { + if (pd->def_mode) { + dev_info(info->dev, "using supplied mode\n"); + fb_videomode_to_var(&fb->var, pd->def_mode); + + fb->var.bits_per_pixel = pd->def_bpp ? pd->def_bpp : 8; + fb->var.xres_virtual = fb->var.xres; + fb->var.yres_virtual = fb->var.yres; + } else { + if (info->edid_data) { + ret = fb_find_mode(&fb->var, fb, fb_mode, + fb->monspecs.modedb, + fb->monspecs.modedb_len, + &sm501_default_mode, default_bpp); + /* edid_data is no longer needed, free it */ + kfree(info->edid_data); + } else { + ret = fb_find_mode(&fb->var, fb, + NULL, NULL, 0, NULL, 8); + } + + switch (ret) { + case 1: + dev_info(info->dev, "using mode specified in " + "@mode\n"); + break; + case 2: + dev_info(info->dev, "using mode specified in " + "@mode with ignored refresh rate\n"); + break; + case 3: + dev_info(info->dev, "using mode default " + "mode\n"); + break; + case 4: + dev_info(info->dev, "using mode from list\n"); + break; + default: + dev_info(info->dev, "ret = %d\n", ret); + dev_info(info->dev, "failed to find mode\n"); + return -EINVAL; + } + } + } + + /* initialise and set the palette */ + if (fb_alloc_cmap(&fb->cmap, NR_PALETTE, 0)) { + dev_err(info->dev, "failed to allocate cmap memory\n"); + return -ENOMEM; + } + fb_set_cmap(&fb->cmap, fb); + + ret = (fb->fbops->fb_check_var)(&fb->var, fb); + if (ret) + dev_err(info->dev, "check_var() failed on initial setup?\n"); + + return 0; +} + +/* default platform data if none is supplied (ie, PCI device) */ + +static struct sm501_platdata_fbsub sm501fb_pdata_crt = { + .flags = (SM501FB_FLAG_USE_INIT_MODE | + SM501FB_FLAG_USE_HWCURSOR | + SM501FB_FLAG_USE_HWACCEL | + SM501FB_FLAG_DISABLE_AT_EXIT), + +}; + +static struct sm501_platdata_fbsub sm501fb_pdata_pnl = { + .flags = (SM501FB_FLAG_USE_INIT_MODE | + SM501FB_FLAG_USE_HWCURSOR | + SM501FB_FLAG_USE_HWACCEL | + SM501FB_FLAG_DISABLE_AT_EXIT), +}; + +static struct sm501_platdata_fb sm501fb_def_pdata = { + .fb_route = SM501_FB_OWN, + .fb_crt = &sm501fb_pdata_crt, + .fb_pnl = &sm501fb_pdata_pnl, +}; + +static char driver_name_crt[] = "sm501fb-crt"; +static char driver_name_pnl[] = "sm501fb-panel"; + +static int sm501fb_probe_one(struct sm501fb_info *info, + enum sm501_controller head) +{ + unsigned char *name = (head == HEAD_CRT) ? "crt" : "panel"; + struct sm501_platdata_fbsub *pd; + struct sm501fb_par *par; + struct fb_info *fbi; + + pd = (head == HEAD_CRT) ? info->pdata->fb_crt : info->pdata->fb_pnl; + + /* Do not initialise if we've not been given any platform data */ + if (pd == NULL) { + dev_info(info->dev, "no data for fb %s (disabled)\n", name); + return 0; + } + + fbi = framebuffer_alloc(sizeof(struct sm501fb_par), info->dev); + if (fbi == NULL) { + dev_err(info->dev, "cannot allocate %s framebuffer\n", name); + return -ENOMEM; + } + + par = fbi->par; + par->info = info; + par->head = head; + fbi->pseudo_palette = &par->pseudo_palette; + + info->fb[head] = fbi; + + return 0; +} + +/* Free up anything allocated by sm501fb_init_fb */ + +static void sm501_free_init_fb(struct sm501fb_info *info, + enum sm501_controller head) +{ + struct fb_info *fbi = info->fb[head]; + + fb_dealloc_cmap(&fbi->cmap); +} + +static int sm501fb_start_one(struct sm501fb_info *info, + enum sm501_controller head, const char *drvname) +{ + struct fb_info *fbi = info->fb[head]; + int ret; + + if (!fbi) + return 0; + + mutex_init(&info->fb[head]->mm_lock); + + ret = sm501fb_init_fb(info->fb[head], head, drvname); + if (ret) { + dev_err(info->dev, "cannot initialise fb %s\n", drvname); + return ret; + } + + ret = register_framebuffer(info->fb[head]); + if (ret) { + dev_err(info->dev, "failed to register fb %s\n", drvname); + sm501_free_init_fb(info, head); + return ret; + } + + dev_info(info->dev, "fb%d: %s frame buffer\n", fbi->node, fbi->fix.id); + + return 0; +} + +static int sm501fb_probe(struct platform_device *pdev) +{ + struct sm501fb_info *info; + struct device *dev = &pdev->dev; + int ret; + + /* allocate our framebuffers */ + + info = kzalloc(sizeof(struct sm501fb_info), GFP_KERNEL); + if (!info) { + dev_err(dev, "failed to allocate state\n"); + return -ENOMEM; + } + + info->dev = dev = &pdev->dev; + platform_set_drvdata(pdev, info); + + if (dev->parent->platform_data) { + struct sm501_platdata *pd = dev->parent->platform_data; + info->pdata = pd->fb; + } + + if (info->pdata == NULL) { + int found = 0; +#if defined(CONFIG_OF) + struct device_node *np = pdev->dev.parent->of_node; + const u8 *prop; + const char *cp; + int len; + + info->pdata = &sm501fb_def_pdata; + if (np) { + /* Get EDID */ + cp = of_get_property(np, "mode", &len); + if (cp) + strcpy(fb_mode, cp); + prop = of_get_property(np, "edid", &len); + if (prop && len == EDID_LENGTH) { + info->edid_data = kmemdup(prop, EDID_LENGTH, + GFP_KERNEL); + if (info->edid_data) + found = 1; + } + } +#endif + if (!found) { + dev_info(dev, "using default configuration data\n"); + info->pdata = &sm501fb_def_pdata; + } + } + + /* probe for the presence of each panel */ + + ret = sm501fb_probe_one(info, HEAD_CRT); + if (ret < 0) { + dev_err(dev, "failed to probe CRT\n"); + goto err_alloc; + } + + ret = sm501fb_probe_one(info, HEAD_PANEL); + if (ret < 0) { + dev_err(dev, "failed to probe PANEL\n"); + goto err_probed_crt; + } + + if (info->fb[HEAD_PANEL] == NULL && + info->fb[HEAD_CRT] == NULL) { + dev_err(dev, "no framebuffers found\n"); + goto err_alloc; + } + + /* get the resources for both of the framebuffers */ + + ret = sm501fb_start(info, pdev); + if (ret) { + dev_err(dev, "cannot initialise SM501\n"); + goto err_probed_panel; + } + + ret = sm501fb_start_one(info, HEAD_CRT, driver_name_crt); + if (ret) { + dev_err(dev, "failed to start CRT\n"); + goto err_started; + } + + ret = sm501fb_start_one(info, HEAD_PANEL, driver_name_pnl); + if (ret) { + dev_err(dev, "failed to start Panel\n"); + goto err_started_crt; + } + + /* create device files */ + + ret = device_create_file(dev, &dev_attr_crt_src); + if (ret) + goto err_started_panel; + + ret = device_create_file(dev, &dev_attr_fbregs_pnl); + if (ret) + goto err_attached_crtsrc_file; + + ret = device_create_file(dev, &dev_attr_fbregs_crt); + if (ret) + goto err_attached_pnlregs_file; + + /* we registered, return ok */ + return 0; + +err_attached_pnlregs_file: + device_remove_file(dev, &dev_attr_fbregs_pnl); + +err_attached_crtsrc_file: + device_remove_file(dev, &dev_attr_crt_src); + +err_started_panel: + unregister_framebuffer(info->fb[HEAD_PANEL]); + sm501_free_init_fb(info, HEAD_PANEL); + +err_started_crt: + unregister_framebuffer(info->fb[HEAD_CRT]); + sm501_free_init_fb(info, HEAD_CRT); + +err_started: + sm501fb_stop(info); + +err_probed_panel: + framebuffer_release(info->fb[HEAD_PANEL]); + +err_probed_crt: + framebuffer_release(info->fb[HEAD_CRT]); + +err_alloc: + kfree(info); + + return ret; +} + + +/* + * Cleanup + */ +static int sm501fb_remove(struct platform_device *pdev) +{ + struct sm501fb_info *info = platform_get_drvdata(pdev); + struct fb_info *fbinfo_crt = info->fb[0]; + struct fb_info *fbinfo_pnl = info->fb[1]; + + device_remove_file(&pdev->dev, &dev_attr_fbregs_crt); + device_remove_file(&pdev->dev, &dev_attr_fbregs_pnl); + device_remove_file(&pdev->dev, &dev_attr_crt_src); + + sm501_free_init_fb(info, HEAD_CRT); + sm501_free_init_fb(info, HEAD_PANEL); + + unregister_framebuffer(fbinfo_crt); + unregister_framebuffer(fbinfo_pnl); + + sm501fb_stop(info); + kfree(info); + + framebuffer_release(fbinfo_pnl); + framebuffer_release(fbinfo_crt); + + return 0; +} + +#ifdef CONFIG_PM + +static int sm501fb_suspend_fb(struct sm501fb_info *info, + enum sm501_controller head) +{ + struct fb_info *fbi = info->fb[head]; + struct sm501fb_par *par = fbi->par; + + if (par->screen.size == 0) + return 0; + + /* blank the relevant interface to ensure unit power minimised */ + (par->ops.fb_blank)(FB_BLANK_POWERDOWN, fbi); + + /* tell console/fb driver we are suspending */ + + console_lock(); + fb_set_suspend(fbi, 1); + console_unlock(); + + /* backup copies in case chip is powered down over suspend */ + + par->store_fb = vmalloc(par->screen.size); + if (par->store_fb == NULL) { + dev_err(info->dev, "no memory to store screen\n"); + return -ENOMEM; + } + + par->store_cursor = vmalloc(par->cursor.size); + if (par->store_cursor == NULL) { + dev_err(info->dev, "no memory to store cursor\n"); + goto err_nocursor; + } + + dev_dbg(info->dev, "suspending screen to %p\n", par->store_fb); + dev_dbg(info->dev, "suspending cursor to %p\n", par->store_cursor); + + memcpy_fromio(par->store_fb, par->screen.k_addr, par->screen.size); + memcpy_fromio(par->store_cursor, par->cursor.k_addr, par->cursor.size); + + return 0; + + err_nocursor: + vfree(par->store_fb); + par->store_fb = NULL; + + return -ENOMEM; +} + +static void sm501fb_resume_fb(struct sm501fb_info *info, + enum sm501_controller head) +{ + struct fb_info *fbi = info->fb[head]; + struct sm501fb_par *par = fbi->par; + + if (par->screen.size == 0) + return; + + /* re-activate the configuration */ + + (par->ops.fb_set_par)(fbi); + + /* restore the data */ + + dev_dbg(info->dev, "restoring screen from %p\n", par->store_fb); + dev_dbg(info->dev, "restoring cursor from %p\n", par->store_cursor); + + if (par->store_fb) + memcpy_toio(par->screen.k_addr, par->store_fb, + par->screen.size); + + if (par->store_cursor) + memcpy_toio(par->cursor.k_addr, par->store_cursor, + par->cursor.size); + + console_lock(); + fb_set_suspend(fbi, 0); + console_unlock(); + + vfree(par->store_fb); + vfree(par->store_cursor); +} + + +/* suspend and resume support */ + +static int sm501fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct sm501fb_info *info = platform_get_drvdata(pdev); + + /* store crt control to resume with */ + info->pm_crt_ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL); + + sm501fb_suspend_fb(info, HEAD_CRT); + sm501fb_suspend_fb(info, HEAD_PANEL); + + /* turn off the clocks, in case the device is not powered down */ + sm501_unit_power(info->dev->parent, SM501_GATE_DISPLAY, 0); + + return 0; +} + +#define SM501_CRT_CTRL_SAVE (SM501_DC_CRT_CONTROL_TVP | \ + SM501_DC_CRT_CONTROL_SEL) + + +static int sm501fb_resume(struct platform_device *pdev) +{ + struct sm501fb_info *info = platform_get_drvdata(pdev); + unsigned long crt_ctrl; + + sm501_unit_power(info->dev->parent, SM501_GATE_DISPLAY, 1); + + /* restore the items we want to be saved for crt control */ + + crt_ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL); + crt_ctrl &= ~SM501_CRT_CTRL_SAVE; + crt_ctrl |= info->pm_crt_ctrl & SM501_CRT_CTRL_SAVE; + smc501_writel(crt_ctrl, info->regs + SM501_DC_CRT_CONTROL); + + sm501fb_resume_fb(info, HEAD_CRT); + sm501fb_resume_fb(info, HEAD_PANEL); + + return 0; +} + +#else +#define sm501fb_suspend NULL +#define sm501fb_resume NULL +#endif + +static struct platform_driver sm501fb_driver = { + .probe = sm501fb_probe, + .remove = sm501fb_remove, + .suspend = sm501fb_suspend, + .resume = sm501fb_resume, + .driver = { + .name = "sm501-fb", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(sm501fb_driver); + +module_param_named(mode, fb_mode, charp, 0); +MODULE_PARM_DESC(mode, + "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); +module_param_named(bpp, default_bpp, ulong, 0); +MODULE_PARM_DESC(bpp, "Specify bit-per-pixel if not specified mode"); +MODULE_AUTHOR("Ben Dooks, Vincent Sanders"); +MODULE_DESCRIPTION("SM501 Framebuffer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/fbdev/smscufx.c b/drivers/video/fbdev/smscufx.c new file mode 100644 index 000000000000..d513ed6a49f2 --- /dev/null +++ b/drivers/video/fbdev/smscufx.c @@ -0,0 +1,1980 @@ +/* + * smscufx.c -- Framebuffer driver for SMSC UFX USB controller + * + * Copyright (C) 2011 Steve Glendinning <steve.glendinning@shawell.net> + * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it> + * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com> + * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + * + * Based on udlfb, with work from Florian Echtler, Henrik Bjerregaard Pedersen, + * and others. + * + * Works well with Bernie Thompson's X DAMAGE patch to xf86-video-fbdev + * available from http://git.plugable.com + * + * Layout is based on skeletonfb by James Simmons and Geert Uytterhoeven, + * usb-skeleton by GregKH. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/uaccess.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "edid.h" + +#define check_warn(status, fmt, args...) \ + ({ if (status < 0) pr_warn(fmt, ##args); }) + +#define check_warn_return(status, fmt, args...) \ + ({ if (status < 0) { pr_warn(fmt, ##args); return status; } }) + +#define check_warn_goto_error(status, fmt, args...) \ + ({ if (status < 0) { pr_warn(fmt, ##args); goto error; } }) + +#define all_bits_set(x, bits) (((x) & (bits)) == (bits)) + +#define USB_VENDOR_REQUEST_WRITE_REGISTER 0xA0 +#define USB_VENDOR_REQUEST_READ_REGISTER 0xA1 + +/* + * TODO: Propose standard fb.h ioctl for reporting damage, + * using _IOWR() and one of the existing area structs from fb.h + * Consider these ioctls deprecated, but they're still used by the + * DisplayLink X server as yet - need both to be modified in tandem + * when new ioctl(s) are ready. + */ +#define UFX_IOCTL_RETURN_EDID (0xAD) +#define UFX_IOCTL_REPORT_DAMAGE (0xAA) + +/* -BULK_SIZE as per usb-skeleton. Can we get full page and avoid overhead? */ +#define BULK_SIZE (512) +#define MAX_TRANSFER (PAGE_SIZE*16 - BULK_SIZE) +#define WRITES_IN_FLIGHT (4) + +#define GET_URB_TIMEOUT (HZ) +#define FREE_URB_TIMEOUT (HZ*2) + +#define BPP 2 + +#define UFX_DEFIO_WRITE_DELAY 5 /* fb_deferred_io.delay in jiffies */ +#define UFX_DEFIO_WRITE_DISABLE (HZ*60) /* "disable" with long delay */ + +struct dloarea { + int x, y; + int w, h; +}; + +struct urb_node { + struct list_head entry; + struct ufx_data *dev; + struct delayed_work release_urb_work; + struct urb *urb; +}; + +struct urb_list { + struct list_head list; + spinlock_t lock; + struct semaphore limit_sem; + int available; + int count; + size_t size; +}; + +struct ufx_data { + struct usb_device *udev; + struct device *gdev; /* &udev->dev */ + struct fb_info *info; + struct urb_list urbs; + struct kref kref; + int fb_count; + bool virtualized; /* true when physical usb device not present */ + struct delayed_work free_framebuffer_work; + atomic_t usb_active; /* 0 = update virtual buffer, but no usb traffic */ + atomic_t lost_pixels; /* 1 = a render op failed. Need screen refresh */ + u8 *edid; /* null until we read edid from hw or get from sysfs */ + size_t edid_size; + u32 pseudo_palette[256]; +}; + +static struct fb_fix_screeninfo ufx_fix = { + .id = "smscufx", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static const u32 smscufx_info_flags = FBINFO_DEFAULT | FBINFO_READS_FAST | + FBINFO_VIRTFB | FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_COPYAREA | FBINFO_MISC_ALWAYS_SETPAR; + +static struct usb_device_id id_table[] = { + {USB_DEVICE(0x0424, 0x9d00),}, + {USB_DEVICE(0x0424, 0x9d01),}, + {}, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* module options */ +static bool console; /* Optionally allow fbcon to consume first framebuffer */ +static bool fb_defio = true; /* Optionally enable fb_defio mmap support */ + +/* ufx keeps a list of urbs for efficient bulk transfers */ +static void ufx_urb_completion(struct urb *urb); +static struct urb *ufx_get_urb(struct ufx_data *dev); +static int ufx_submit_urb(struct ufx_data *dev, struct urb * urb, size_t len); +static int ufx_alloc_urb_list(struct ufx_data *dev, int count, size_t size); +static void ufx_free_urb_list(struct ufx_data *dev); + +/* reads a control register */ +static int ufx_reg_read(struct ufx_data *dev, u32 index, u32 *data) +{ + u32 *buf = kmalloc(4, GFP_KERNEL); + int ret; + + BUG_ON(!dev); + + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + USB_VENDOR_REQUEST_READ_REGISTER, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, index, buf, 4, USB_CTRL_GET_TIMEOUT); + + le32_to_cpus(buf); + *data = *buf; + kfree(buf); + + if (unlikely(ret < 0)) + pr_warn("Failed to read register index 0x%08x\n", index); + + return ret; +} + +/* writes a control register */ +static int ufx_reg_write(struct ufx_data *dev, u32 index, u32 data) +{ + u32 *buf = kmalloc(4, GFP_KERNEL); + int ret; + + BUG_ON(!dev); + + if (!buf) + return -ENOMEM; + + *buf = data; + cpu_to_le32s(buf); + + ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + USB_VENDOR_REQUEST_WRITE_REGISTER, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, index, buf, 4, USB_CTRL_SET_TIMEOUT); + + kfree(buf); + + if (unlikely(ret < 0)) + pr_warn("Failed to write register index 0x%08x with value " + "0x%08x\n", index, data); + + return ret; +} + +static int ufx_reg_clear_and_set_bits(struct ufx_data *dev, u32 index, + u32 bits_to_clear, u32 bits_to_set) +{ + u32 data; + int status = ufx_reg_read(dev, index, &data); + check_warn_return(status, "ufx_reg_clear_and_set_bits error reading " + "0x%x", index); + + data &= (~bits_to_clear); + data |= bits_to_set; + + status = ufx_reg_write(dev, index, data); + check_warn_return(status, "ufx_reg_clear_and_set_bits error writing " + "0x%x", index); + + return 0; +} + +static int ufx_reg_set_bits(struct ufx_data *dev, u32 index, u32 bits) +{ + return ufx_reg_clear_and_set_bits(dev, index, 0, bits); +} + +static int ufx_reg_clear_bits(struct ufx_data *dev, u32 index, u32 bits) +{ + return ufx_reg_clear_and_set_bits(dev, index, bits, 0); +} + +static int ufx_lite_reset(struct ufx_data *dev) +{ + int status; + u32 value; + + status = ufx_reg_write(dev, 0x3008, 0x00000001); + check_warn_return(status, "ufx_lite_reset error writing 0x3008"); + + status = ufx_reg_read(dev, 0x3008, &value); + check_warn_return(status, "ufx_lite_reset error reading 0x3008"); + + return (value == 0) ? 0 : -EIO; +} + +/* If display is unblanked, then blank it */ +static int ufx_blank(struct ufx_data *dev, bool wait) +{ + u32 dc_ctrl, dc_sts; + int i; + + int status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_blank error reading 0x2004"); + + status = ufx_reg_read(dev, 0x2000, &dc_ctrl); + check_warn_return(status, "ufx_blank error reading 0x2000"); + + /* return success if display is already blanked */ + if ((dc_sts & 0x00000100) || (dc_ctrl & 0x00000100)) + return 0; + + /* request the DC to blank the display */ + dc_ctrl |= 0x00000100; + status = ufx_reg_write(dev, 0x2000, dc_ctrl); + check_warn_return(status, "ufx_blank error writing 0x2000"); + + /* return success immediately if we don't have to wait */ + if (!wait) + return 0; + + for (i = 0; i < 250; i++) { + status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_blank error reading 0x2004"); + + if (dc_sts & 0x00000100) + return 0; + } + + /* timed out waiting for display to blank */ + return -EIO; +} + +/* If display is blanked, then unblank it */ +static int ufx_unblank(struct ufx_data *dev, bool wait) +{ + u32 dc_ctrl, dc_sts; + int i; + + int status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_unblank error reading 0x2004"); + + status = ufx_reg_read(dev, 0x2000, &dc_ctrl); + check_warn_return(status, "ufx_unblank error reading 0x2000"); + + /* return success if display is already unblanked */ + if (((dc_sts & 0x00000100) == 0) || ((dc_ctrl & 0x00000100) == 0)) + return 0; + + /* request the DC to unblank the display */ + dc_ctrl &= ~0x00000100; + status = ufx_reg_write(dev, 0x2000, dc_ctrl); + check_warn_return(status, "ufx_unblank error writing 0x2000"); + + /* return success immediately if we don't have to wait */ + if (!wait) + return 0; + + for (i = 0; i < 250; i++) { + status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_unblank error reading 0x2004"); + + if ((dc_sts & 0x00000100) == 0) + return 0; + } + + /* timed out waiting for display to unblank */ + return -EIO; +} + +/* If display is enabled, then disable it */ +static int ufx_disable(struct ufx_data *dev, bool wait) +{ + u32 dc_ctrl, dc_sts; + int i; + + int status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_disable error reading 0x2004"); + + status = ufx_reg_read(dev, 0x2000, &dc_ctrl); + check_warn_return(status, "ufx_disable error reading 0x2000"); + + /* return success if display is already disabled */ + if (((dc_sts & 0x00000001) == 0) || ((dc_ctrl & 0x00000001) == 0)) + return 0; + + /* request the DC to disable the display */ + dc_ctrl &= ~(0x00000001); + status = ufx_reg_write(dev, 0x2000, dc_ctrl); + check_warn_return(status, "ufx_disable error writing 0x2000"); + + /* return success immediately if we don't have to wait */ + if (!wait) + return 0; + + for (i = 0; i < 250; i++) { + status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_disable error reading 0x2004"); + + if ((dc_sts & 0x00000001) == 0) + return 0; + } + + /* timed out waiting for display to disable */ + return -EIO; +} + +/* If display is disabled, then enable it */ +static int ufx_enable(struct ufx_data *dev, bool wait) +{ + u32 dc_ctrl, dc_sts; + int i; + + int status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_enable error reading 0x2004"); + + status = ufx_reg_read(dev, 0x2000, &dc_ctrl); + check_warn_return(status, "ufx_enable error reading 0x2000"); + + /* return success if display is already enabled */ + if ((dc_sts & 0x00000001) || (dc_ctrl & 0x00000001)) + return 0; + + /* request the DC to enable the display */ + dc_ctrl |= 0x00000001; + status = ufx_reg_write(dev, 0x2000, dc_ctrl); + check_warn_return(status, "ufx_enable error writing 0x2000"); + + /* return success immediately if we don't have to wait */ + if (!wait) + return 0; + + for (i = 0; i < 250; i++) { + status = ufx_reg_read(dev, 0x2004, &dc_sts); + check_warn_return(status, "ufx_enable error reading 0x2004"); + + if (dc_sts & 0x00000001) + return 0; + } + + /* timed out waiting for display to enable */ + return -EIO; +} + +static int ufx_config_sys_clk(struct ufx_data *dev) +{ + int status = ufx_reg_write(dev, 0x700C, 0x8000000F); + check_warn_return(status, "error writing 0x700C"); + + status = ufx_reg_write(dev, 0x7014, 0x0010024F); + check_warn_return(status, "error writing 0x7014"); + + status = ufx_reg_write(dev, 0x7010, 0x00000000); + check_warn_return(status, "error writing 0x7010"); + + status = ufx_reg_clear_bits(dev, 0x700C, 0x0000000A); + check_warn_return(status, "error clearing PLL1 bypass in 0x700C"); + msleep(1); + + status = ufx_reg_clear_bits(dev, 0x700C, 0x80000000); + check_warn_return(status, "error clearing output gate in 0x700C"); + + return 0; +} + +static int ufx_config_ddr2(struct ufx_data *dev) +{ + int status, i = 0; + u32 tmp; + + status = ufx_reg_write(dev, 0x0004, 0x001F0F77); + check_warn_return(status, "error writing 0x0004"); + + status = ufx_reg_write(dev, 0x0008, 0xFFF00000); + check_warn_return(status, "error writing 0x0008"); + + status = ufx_reg_write(dev, 0x000C, 0x0FFF2222); + check_warn_return(status, "error writing 0x000C"); + + status = ufx_reg_write(dev, 0x0010, 0x00030814); + check_warn_return(status, "error writing 0x0010"); + + status = ufx_reg_write(dev, 0x0014, 0x00500019); + check_warn_return(status, "error writing 0x0014"); + + status = ufx_reg_write(dev, 0x0018, 0x020D0F15); + check_warn_return(status, "error writing 0x0018"); + + status = ufx_reg_write(dev, 0x001C, 0x02532305); + check_warn_return(status, "error writing 0x001C"); + + status = ufx_reg_write(dev, 0x0020, 0x0B030905); + check_warn_return(status, "error writing 0x0020"); + + status = ufx_reg_write(dev, 0x0024, 0x00000827); + check_warn_return(status, "error writing 0x0024"); + + status = ufx_reg_write(dev, 0x0028, 0x00000000); + check_warn_return(status, "error writing 0x0028"); + + status = ufx_reg_write(dev, 0x002C, 0x00000042); + check_warn_return(status, "error writing 0x002C"); + + status = ufx_reg_write(dev, 0x0030, 0x09520000); + check_warn_return(status, "error writing 0x0030"); + + status = ufx_reg_write(dev, 0x0034, 0x02223314); + check_warn_return(status, "error writing 0x0034"); + + status = ufx_reg_write(dev, 0x0038, 0x00430043); + check_warn_return(status, "error writing 0x0038"); + + status = ufx_reg_write(dev, 0x003C, 0xF00F000F); + check_warn_return(status, "error writing 0x003C"); + + status = ufx_reg_write(dev, 0x0040, 0xF380F00F); + check_warn_return(status, "error writing 0x0040"); + + status = ufx_reg_write(dev, 0x0044, 0xF00F0496); + check_warn_return(status, "error writing 0x0044"); + + status = ufx_reg_write(dev, 0x0048, 0x03080406); + check_warn_return(status, "error writing 0x0048"); + + status = ufx_reg_write(dev, 0x004C, 0x00001000); + check_warn_return(status, "error writing 0x004C"); + + status = ufx_reg_write(dev, 0x005C, 0x00000007); + check_warn_return(status, "error writing 0x005C"); + + status = ufx_reg_write(dev, 0x0100, 0x54F00012); + check_warn_return(status, "error writing 0x0100"); + + status = ufx_reg_write(dev, 0x0104, 0x00004012); + check_warn_return(status, "error writing 0x0104"); + + status = ufx_reg_write(dev, 0x0118, 0x40404040); + check_warn_return(status, "error writing 0x0118"); + + status = ufx_reg_write(dev, 0x0000, 0x00000001); + check_warn_return(status, "error writing 0x0000"); + + while (i++ < 500) { + status = ufx_reg_read(dev, 0x0000, &tmp); + check_warn_return(status, "error reading 0x0000"); + + if (all_bits_set(tmp, 0xC0000000)) + return 0; + } + + pr_err("DDR2 initialisation timed out, reg 0x0000=0x%08x", tmp); + return -ETIMEDOUT; +} + +struct pll_values { + u32 div_r0; + u32 div_f0; + u32 div_q0; + u32 range0; + u32 div_r1; + u32 div_f1; + u32 div_q1; + u32 range1; +}; + +static u32 ufx_calc_range(u32 ref_freq) +{ + if (ref_freq >= 88000000) + return 7; + + if (ref_freq >= 54000000) + return 6; + + if (ref_freq >= 34000000) + return 5; + + if (ref_freq >= 21000000) + return 4; + + if (ref_freq >= 13000000) + return 3; + + if (ref_freq >= 8000000) + return 2; + + return 1; +} + +/* calculates PLL divider settings for a desired target frequency */ +static void ufx_calc_pll_values(const u32 clk_pixel_pll, struct pll_values *asic_pll) +{ + const u32 ref_clk = 25000000; + u32 div_r0, div_f0, div_q0, div_r1, div_f1, div_q1; + u32 min_error = clk_pixel_pll; + + for (div_r0 = 1; div_r0 <= 32; div_r0++) { + u32 ref_freq0 = ref_clk / div_r0; + if (ref_freq0 < 5000000) + break; + + if (ref_freq0 > 200000000) + continue; + + for (div_f0 = 1; div_f0 <= 256; div_f0++) { + u32 vco_freq0 = ref_freq0 * div_f0; + + if (vco_freq0 < 350000000) + continue; + + if (vco_freq0 > 700000000) + break; + + for (div_q0 = 0; div_q0 < 7; div_q0++) { + u32 pllout_freq0 = vco_freq0 / (1 << div_q0); + + if (pllout_freq0 < 5000000) + break; + + if (pllout_freq0 > 200000000) + continue; + + for (div_r1 = 1; div_r1 <= 32; div_r1++) { + u32 ref_freq1 = pllout_freq0 / div_r1; + + if (ref_freq1 < 5000000) + break; + + for (div_f1 = 1; div_f1 <= 256; div_f1++) { + u32 vco_freq1 = ref_freq1 * div_f1; + + if (vco_freq1 < 350000000) + continue; + + if (vco_freq1 > 700000000) + break; + + for (div_q1 = 0; div_q1 < 7; div_q1++) { + u32 pllout_freq1 = vco_freq1 / (1 << div_q1); + int error = abs(pllout_freq1 - clk_pixel_pll); + + if (pllout_freq1 < 5000000) + break; + + if (pllout_freq1 > 700000000) + continue; + + if (error < min_error) { + min_error = error; + + /* final returned value is equal to calculated value - 1 + * because a value of 0 = divide by 1 */ + asic_pll->div_r0 = div_r0 - 1; + asic_pll->div_f0 = div_f0 - 1; + asic_pll->div_q0 = div_q0; + asic_pll->div_r1 = div_r1 - 1; + asic_pll->div_f1 = div_f1 - 1; + asic_pll->div_q1 = div_q1; + + asic_pll->range0 = ufx_calc_range(ref_freq0); + asic_pll->range1 = ufx_calc_range(ref_freq1); + + if (min_error == 0) + return; + } + } + } + } + } + } + } +} + +/* sets analog bit PLL configuration values */ +static int ufx_config_pix_clk(struct ufx_data *dev, u32 pixclock) +{ + struct pll_values asic_pll = {0}; + u32 value, clk_pixel, clk_pixel_pll; + int status; + + /* convert pixclock (in ps) to frequency (in Hz) */ + clk_pixel = PICOS2KHZ(pixclock) * 1000; + pr_debug("pixclock %d ps = clk_pixel %d Hz", pixclock, clk_pixel); + + /* clk_pixel = 1/2 clk_pixel_pll */ + clk_pixel_pll = clk_pixel * 2; + + ufx_calc_pll_values(clk_pixel_pll, &asic_pll); + + /* Keep BYPASS and RESET signals asserted until configured */ + status = ufx_reg_write(dev, 0x7000, 0x8000000F); + check_warn_return(status, "error writing 0x7000"); + + value = (asic_pll.div_f1 | (asic_pll.div_r1 << 8) | + (asic_pll.div_q1 << 16) | (asic_pll.range1 << 20)); + status = ufx_reg_write(dev, 0x7008, value); + check_warn_return(status, "error writing 0x7008"); + + value = (asic_pll.div_f0 | (asic_pll.div_r0 << 8) | + (asic_pll.div_q0 << 16) | (asic_pll.range0 << 20)); + status = ufx_reg_write(dev, 0x7004, value); + check_warn_return(status, "error writing 0x7004"); + + status = ufx_reg_clear_bits(dev, 0x7000, 0x00000005); + check_warn_return(status, + "error clearing PLL0 bypass bits in 0x7000"); + msleep(1); + + status = ufx_reg_clear_bits(dev, 0x7000, 0x0000000A); + check_warn_return(status, + "error clearing PLL1 bypass bits in 0x7000"); + msleep(1); + + status = ufx_reg_clear_bits(dev, 0x7000, 0x80000000); + check_warn_return(status, "error clearing gate bits in 0x7000"); + + return 0; +} + +static int ufx_set_vid_mode(struct ufx_data *dev, struct fb_var_screeninfo *var) +{ + u32 temp; + u16 h_total, h_active, h_blank_start, h_blank_end, h_sync_start, h_sync_end; + u16 v_total, v_active, v_blank_start, v_blank_end, v_sync_start, v_sync_end; + + int status = ufx_reg_write(dev, 0x8028, 0); + check_warn_return(status, "ufx_set_vid_mode error disabling RGB pad"); + + status = ufx_reg_write(dev, 0x8024, 0); + check_warn_return(status, "ufx_set_vid_mode error disabling VDAC"); + + /* shut everything down before changing timing */ + status = ufx_blank(dev, true); + check_warn_return(status, "ufx_set_vid_mode error blanking display"); + + status = ufx_disable(dev, true); + check_warn_return(status, "ufx_set_vid_mode error disabling display"); + + status = ufx_config_pix_clk(dev, var->pixclock); + check_warn_return(status, "ufx_set_vid_mode error configuring pixclock"); + + status = ufx_reg_write(dev, 0x2000, 0x00000104); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2000"); + + /* set horizontal timings */ + h_total = var->xres + var->right_margin + var->hsync_len + var->left_margin; + h_active = var->xres; + h_blank_start = var->xres + var->right_margin; + h_blank_end = var->xres + var->right_margin + var->hsync_len; + h_sync_start = var->xres + var->right_margin; + h_sync_end = var->xres + var->right_margin + var->hsync_len; + + temp = ((h_total - 1) << 16) | (h_active - 1); + status = ufx_reg_write(dev, 0x2008, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2008"); + + temp = ((h_blank_start - 1) << 16) | (h_blank_end - 1); + status = ufx_reg_write(dev, 0x200C, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x200C"); + + temp = ((h_sync_start - 1) << 16) | (h_sync_end - 1); + status = ufx_reg_write(dev, 0x2010, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2010"); + + /* set vertical timings */ + v_total = var->upper_margin + var->yres + var->lower_margin + var->vsync_len; + v_active = var->yres; + v_blank_start = var->yres + var->lower_margin; + v_blank_end = var->yres + var->lower_margin + var->vsync_len; + v_sync_start = var->yres + var->lower_margin; + v_sync_end = var->yres + var->lower_margin + var->vsync_len; + + temp = ((v_total - 1) << 16) | (v_active - 1); + status = ufx_reg_write(dev, 0x2014, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2014"); + + temp = ((v_blank_start - 1) << 16) | (v_blank_end - 1); + status = ufx_reg_write(dev, 0x2018, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2018"); + + temp = ((v_sync_start - 1) << 16) | (v_sync_end - 1); + status = ufx_reg_write(dev, 0x201C, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x201C"); + + status = ufx_reg_write(dev, 0x2020, 0x00000000); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2020"); + + status = ufx_reg_write(dev, 0x2024, 0x00000000); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2024"); + + /* Set the frame length register (#pix * 2 bytes/pixel) */ + temp = var->xres * var->yres * 2; + temp = (temp + 7) & (~0x7); + status = ufx_reg_write(dev, 0x2028, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2028"); + + /* enable desired output interface & disable others */ + status = ufx_reg_write(dev, 0x2040, 0); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2040"); + + status = ufx_reg_write(dev, 0x2044, 0); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2044"); + + status = ufx_reg_write(dev, 0x2048, 0); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2048"); + + /* set the sync polarities & enable bit */ + temp = 0x00000001; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + temp |= 0x00000010; + + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + temp |= 0x00000008; + + status = ufx_reg_write(dev, 0x2040, temp); + check_warn_return(status, "ufx_set_vid_mode error writing 0x2040"); + + /* start everything back up */ + status = ufx_enable(dev, true); + check_warn_return(status, "ufx_set_vid_mode error enabling display"); + + /* Unblank the display */ + status = ufx_unblank(dev, true); + check_warn_return(status, "ufx_set_vid_mode error unblanking display"); + + /* enable RGB pad */ + status = ufx_reg_write(dev, 0x8028, 0x00000003); + check_warn_return(status, "ufx_set_vid_mode error enabling RGB pad"); + + /* enable VDAC */ + status = ufx_reg_write(dev, 0x8024, 0x00000007); + check_warn_return(status, "ufx_set_vid_mode error enabling VDAC"); + + return 0; +} + +static int ufx_ops_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + if (size > info->fix.smem_len) + return -EINVAL; + if (offset > info->fix.smem_len - size) + return -EINVAL; + + pos = (unsigned long)info->fix.smem_start + offset; + + pr_debug("mmap() framebuffer addr:%lu size:%lu\n", + pos, size); + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +static void ufx_raw_rect(struct ufx_data *dev, u16 *cmd, int x, int y, + int width, int height) +{ + size_t packed_line_len = ALIGN((width * 2), 4); + size_t packed_rect_len = packed_line_len * height; + int line; + + BUG_ON(!dev); + BUG_ON(!dev->info); + + /* command word */ + *((u32 *)&cmd[0]) = cpu_to_le32(0x01); + + /* length word */ + *((u32 *)&cmd[2]) = cpu_to_le32(packed_rect_len + 16); + + cmd[4] = cpu_to_le16(x); + cmd[5] = cpu_to_le16(y); + cmd[6] = cpu_to_le16(width); + cmd[7] = cpu_to_le16(height); + + /* frame base address */ + *((u32 *)&cmd[8]) = cpu_to_le32(0); + + /* color mode and horizontal resolution */ + cmd[10] = cpu_to_le16(0x4000 | dev->info->var.xres); + + /* vertical resolution */ + cmd[11] = cpu_to_le16(dev->info->var.yres); + + /* packed data */ + for (line = 0; line < height; line++) { + const int line_offset = dev->info->fix.line_length * (y + line); + const int byte_offset = line_offset + (x * BPP); + memcpy(&cmd[(24 + (packed_line_len * line)) / 2], + (char *)dev->info->fix.smem_start + byte_offset, width * BPP); + } +} + +static int ufx_handle_damage(struct ufx_data *dev, int x, int y, + int width, int height) +{ + size_t packed_line_len = ALIGN((width * 2), 4); + int len, status, urb_lines, start_line = 0; + + if ((width <= 0) || (height <= 0) || + (x + width > dev->info->var.xres) || + (y + height > dev->info->var.yres)) + return -EINVAL; + + if (!atomic_read(&dev->usb_active)) + return 0; + + while (start_line < height) { + struct urb *urb = ufx_get_urb(dev); + if (!urb) { + pr_warn("ufx_handle_damage unable to get urb"); + return 0; + } + + /* assume we have enough space to transfer at least one line */ + BUG_ON(urb->transfer_buffer_length < (24 + (width * 2))); + + /* calculate the maximum number of lines we could fit in */ + urb_lines = (urb->transfer_buffer_length - 24) / packed_line_len; + + /* but we might not need this many */ + urb_lines = min(urb_lines, (height - start_line)); + + memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); + + ufx_raw_rect(dev, urb->transfer_buffer, x, (y + start_line), width, urb_lines); + len = 24 + (packed_line_len * urb_lines); + + status = ufx_submit_urb(dev, urb, len); + check_warn_return(status, "Error submitting URB"); + + start_line += urb_lines; + } + + return 0; +} + +/* Path triggered by usermode clients who write to filesystem + * e.g. cat filename > /dev/fb1 + * Not used by X Windows or text-mode console. But useful for testing. + * Slow because of extra copy and we must assume all pixels dirty. */ +static ssize_t ufx_ops_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t result; + struct ufx_data *dev = info->par; + u32 offset = (u32) *ppos; + + result = fb_sys_write(info, buf, count, ppos); + + if (result > 0) { + int start = max((int)(offset / info->fix.line_length), 0); + int lines = min((u32)((result / info->fix.line_length) + 1), + (u32)info->var.yres); + + ufx_handle_damage(dev, 0, start, info->var.xres, lines); + } + + return result; +} + +static void ufx_ops_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + + struct ufx_data *dev = info->par; + + sys_copyarea(info, area); + + ufx_handle_damage(dev, area->dx, area->dy, + area->width, area->height); +} + +static void ufx_ops_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct ufx_data *dev = info->par; + + sys_imageblit(info, image); + + ufx_handle_damage(dev, image->dx, image->dy, + image->width, image->height); +} + +static void ufx_ops_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct ufx_data *dev = info->par; + + sys_fillrect(info, rect); + + ufx_handle_damage(dev, rect->dx, rect->dy, rect->width, + rect->height); +} + +/* NOTE: fb_defio.c is holding info->fbdefio.mutex + * Touching ANY framebuffer memory that triggers a page fault + * in fb_defio will cause a deadlock, when it also tries to + * grab the same mutex. */ +static void ufx_dpy_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct page *cur; + struct fb_deferred_io *fbdefio = info->fbdefio; + struct ufx_data *dev = info->par; + + if (!fb_defio) + return; + + if (!atomic_read(&dev->usb_active)) + return; + + /* walk the written page list and render each to device */ + list_for_each_entry(cur, &fbdefio->pagelist, lru) { + /* create a rectangle of full screen width that encloses the + * entire dirty framebuffer page */ + const int x = 0; + const int width = dev->info->var.xres; + const int y = (cur->index << PAGE_SHIFT) / (width * 2); + int height = (PAGE_SIZE / (width * 2)) + 1; + height = min(height, (int)(dev->info->var.yres - y)); + + BUG_ON(y >= dev->info->var.yres); + BUG_ON((y + height) > dev->info->var.yres); + + ufx_handle_damage(dev, x, y, width, height); + } +} + +static int ufx_ops_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct ufx_data *dev = info->par; + struct dloarea *area = NULL; + + if (!atomic_read(&dev->usb_active)) + return 0; + + /* TODO: Update X server to get this from sysfs instead */ + if (cmd == UFX_IOCTL_RETURN_EDID) { + u8 __user *edid = (u8 __user *)arg; + if (copy_to_user(edid, dev->edid, dev->edid_size)) + return -EFAULT; + return 0; + } + + /* TODO: Help propose a standard fb.h ioctl to report mmap damage */ + if (cmd == UFX_IOCTL_REPORT_DAMAGE) { + /* If we have a damage-aware client, turn fb_defio "off" + * To avoid perf imact of unnecessary page fault handling. + * Done by resetting the delay for this fb_info to a very + * long period. Pages will become writable and stay that way. + * Reset to normal value when all clients have closed this fb. + */ + if (info->fbdefio) + info->fbdefio->delay = UFX_DEFIO_WRITE_DISABLE; + + area = (struct dloarea *)arg; + + if (area->x < 0) + area->x = 0; + + if (area->x > info->var.xres) + area->x = info->var.xres; + + if (area->y < 0) + area->y = 0; + + if (area->y > info->var.yres) + area->y = info->var.yres; + + ufx_handle_damage(dev, area->x, area->y, area->w, area->h); + } + + return 0; +} + +/* taken from vesafb */ +static int +ufx_ops_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + int err = 0; + + if (regno >= info->cmap.len) + return 1; + + if (regno < 16) { + if (info->var.red.offset == 10) { + /* 1:5:5:5 */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | ((blue & 0xf800) >> 11); + } else { + /* 0:5:6:5 */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800)) | + ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11); + } + } + + return err; +} + +/* It's common for several clients to have framebuffer open simultaneously. + * e.g. both fbcon and X. Makes things interesting. + * Assumes caller is holding info->lock (for open and release at least) */ +static int ufx_ops_open(struct fb_info *info, int user) +{ + struct ufx_data *dev = info->par; + + /* fbcon aggressively connects to first framebuffer it finds, + * preventing other clients (X) from working properly. Usually + * not what the user wants. Fail by default with option to enable. */ + if (user == 0 && !console) + return -EBUSY; + + /* If the USB device is gone, we don't accept new opens */ + if (dev->virtualized) + return -ENODEV; + + dev->fb_count++; + + kref_get(&dev->kref); + + if (fb_defio && (info->fbdefio == NULL)) { + /* enable defio at last moment if not disabled by client */ + + struct fb_deferred_io *fbdefio; + + fbdefio = kzalloc(sizeof(struct fb_deferred_io), GFP_KERNEL); + + if (fbdefio) { + fbdefio->delay = UFX_DEFIO_WRITE_DELAY; + fbdefio->deferred_io = ufx_dpy_deferred_io; + } + + info->fbdefio = fbdefio; + fb_deferred_io_init(info); + } + + pr_debug("open /dev/fb%d user=%d fb_info=%p count=%d", + info->node, user, info, dev->fb_count); + + return 0; +} + +/* + * Called when all client interfaces to start transactions have been disabled, + * and all references to our device instance (ufx_data) are released. + * Every transaction must have a reference, so we know are fully spun down + */ +static void ufx_free(struct kref *kref) +{ + struct ufx_data *dev = container_of(kref, struct ufx_data, kref); + + /* this function will wait for all in-flight urbs to complete */ + if (dev->urbs.count > 0) + ufx_free_urb_list(dev); + + pr_debug("freeing ufx_data %p", dev); + + kfree(dev); +} + +static void ufx_release_urb_work(struct work_struct *work) +{ + struct urb_node *unode = container_of(work, struct urb_node, + release_urb_work.work); + + up(&unode->dev->urbs.limit_sem); +} + +static void ufx_free_framebuffer_work(struct work_struct *work) +{ + struct ufx_data *dev = container_of(work, struct ufx_data, + free_framebuffer_work.work); + struct fb_info *info = dev->info; + int node = info->node; + + unregister_framebuffer(info); + + if (info->cmap.len != 0) + fb_dealloc_cmap(&info->cmap); + if (info->monspecs.modedb) + fb_destroy_modedb(info->monspecs.modedb); + if (info->screen_base) + vfree(info->screen_base); + + fb_destroy_modelist(&info->modelist); + + dev->info = NULL; + + /* Assume info structure is freed after this point */ + framebuffer_release(info); + + pr_debug("fb_info for /dev/fb%d has been freed", node); + + /* ref taken in probe() as part of registering framebfufer */ + kref_put(&dev->kref, ufx_free); +} + +/* + * Assumes caller is holding info->lock mutex (for open and release at least) + */ +static int ufx_ops_release(struct fb_info *info, int user) +{ + struct ufx_data *dev = info->par; + + dev->fb_count--; + + /* We can't free fb_info here - fbmem will touch it when we return */ + if (dev->virtualized && (dev->fb_count == 0)) + schedule_delayed_work(&dev->free_framebuffer_work, HZ); + + if ((dev->fb_count == 0) && (info->fbdefio)) { + fb_deferred_io_cleanup(info); + kfree(info->fbdefio); + info->fbdefio = NULL; + info->fbops->fb_mmap = ufx_ops_mmap; + } + + pr_debug("released /dev/fb%d user=%d count=%d", + info->node, user, dev->fb_count); + + kref_put(&dev->kref, ufx_free); + + return 0; +} + +/* Check whether a video mode is supported by the chip + * We start from monitor's modes, so don't need to filter that here */ +static int ufx_is_valid_mode(struct fb_videomode *mode, + struct fb_info *info) +{ + if ((mode->xres * mode->yres) > (2048 * 1152)) { + pr_debug("%dx%d too many pixels", + mode->xres, mode->yres); + return 0; + } + + if (mode->pixclock < 5000) { + pr_debug("%dx%d %dps pixel clock too fast", + mode->xres, mode->yres, mode->pixclock); + return 0; + } + + pr_debug("%dx%d (pixclk %dps %dMHz) valid mode", mode->xres, mode->yres, + mode->pixclock, (1000000 / mode->pixclock)); + return 1; +} + +static void ufx_var_color_format(struct fb_var_screeninfo *var) +{ + const struct fb_bitfield red = { 11, 5, 0 }; + const struct fb_bitfield green = { 5, 6, 0 }; + const struct fb_bitfield blue = { 0, 5, 0 }; + + var->bits_per_pixel = 16; + var->red = red; + var->green = green; + var->blue = blue; +} + +static int ufx_ops_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct fb_videomode mode; + + /* TODO: support dynamically changing framebuffer size */ + if ((var->xres * var->yres * 2) > info->fix.smem_len) + return -EINVAL; + + /* set device-specific elements of var unrelated to mode */ + ufx_var_color_format(var); + + fb_var_to_videomode(&mode, var); + + if (!ufx_is_valid_mode(&mode, info)) + return -EINVAL; + + return 0; +} + +static int ufx_ops_set_par(struct fb_info *info) +{ + struct ufx_data *dev = info->par; + int result; + u16 *pix_framebuffer; + int i; + + pr_debug("set_par mode %dx%d", info->var.xres, info->var.yres); + result = ufx_set_vid_mode(dev, &info->var); + + if ((result == 0) && (dev->fb_count == 0)) { + /* paint greenscreen */ + pix_framebuffer = (u16 *) info->screen_base; + for (i = 0; i < info->fix.smem_len / 2; i++) + pix_framebuffer[i] = 0x37e6; + + ufx_handle_damage(dev, 0, 0, info->var.xres, info->var.yres); + } + + /* re-enable defio if previously disabled by damage tracking */ + if (info->fbdefio) + info->fbdefio->delay = UFX_DEFIO_WRITE_DELAY; + + return result; +} + +/* In order to come back from full DPMS off, we need to set the mode again */ +static int ufx_ops_blank(int blank_mode, struct fb_info *info) +{ + struct ufx_data *dev = info->par; + ufx_set_vid_mode(dev, &info->var); + return 0; +} + +static struct fb_ops ufx_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = ufx_ops_write, + .fb_setcolreg = ufx_ops_setcolreg, + .fb_fillrect = ufx_ops_fillrect, + .fb_copyarea = ufx_ops_copyarea, + .fb_imageblit = ufx_ops_imageblit, + .fb_mmap = ufx_ops_mmap, + .fb_ioctl = ufx_ops_ioctl, + .fb_open = ufx_ops_open, + .fb_release = ufx_ops_release, + .fb_blank = ufx_ops_blank, + .fb_check_var = ufx_ops_check_var, + .fb_set_par = ufx_ops_set_par, +}; + +/* Assumes &info->lock held by caller + * Assumes no active clients have framebuffer open */ +static int ufx_realloc_framebuffer(struct ufx_data *dev, struct fb_info *info) +{ + int retval = -ENOMEM; + int old_len = info->fix.smem_len; + int new_len; + unsigned char *old_fb = info->screen_base; + unsigned char *new_fb; + + pr_debug("Reallocating framebuffer. Addresses will change!"); + + new_len = info->fix.line_length * info->var.yres; + + if (PAGE_ALIGN(new_len) > old_len) { + /* + * Alloc system memory for virtual framebuffer + */ + new_fb = vmalloc(new_len); + if (!new_fb) { + pr_err("Virtual framebuffer alloc failed"); + goto error; + } + + if (info->screen_base) { + memcpy(new_fb, old_fb, old_len); + vfree(info->screen_base); + } + + info->screen_base = new_fb; + info->fix.smem_len = PAGE_ALIGN(new_len); + info->fix.smem_start = (unsigned long) new_fb; + info->flags = smscufx_info_flags; + } + + retval = 0; + +error: + return retval; +} + +/* sets up I2C Controller for 100 Kbps, std. speed, 7-bit addr, master, + * restart enabled, but no start byte, enable controller */ +static int ufx_i2c_init(struct ufx_data *dev) +{ + u32 tmp; + + /* disable the controller before it can be reprogrammed */ + int status = ufx_reg_write(dev, 0x106C, 0x00); + check_warn_return(status, "failed to disable I2C"); + + /* Setup the clock count registers + * (12+1) = 13 clks @ 2.5 MHz = 5.2 uS */ + status = ufx_reg_write(dev, 0x1018, 12); + check_warn_return(status, "error writing 0x1018"); + + /* (6+8) = 14 clks @ 2.5 MHz = 5.6 uS */ + status = ufx_reg_write(dev, 0x1014, 6); + check_warn_return(status, "error writing 0x1014"); + + status = ufx_reg_read(dev, 0x1000, &tmp); + check_warn_return(status, "error reading 0x1000"); + + /* set speed to std mode */ + tmp &= ~(0x06); + tmp |= 0x02; + + /* 7-bit (not 10-bit) addressing */ + tmp &= ~(0x10); + + /* enable restart conditions and master mode */ + tmp |= 0x21; + + status = ufx_reg_write(dev, 0x1000, tmp); + check_warn_return(status, "error writing 0x1000"); + + /* Set normal tx using target address 0 */ + status = ufx_reg_clear_and_set_bits(dev, 0x1004, 0xC00, 0x000); + check_warn_return(status, "error setting TX mode bits in 0x1004"); + + /* Enable the controller */ + status = ufx_reg_write(dev, 0x106C, 0x01); + check_warn_return(status, "failed to enable I2C"); + + return 0; +} + +/* sets the I2C port mux and target address */ +static int ufx_i2c_configure(struct ufx_data *dev) +{ + int status = ufx_reg_write(dev, 0x106C, 0x00); + check_warn_return(status, "failed to disable I2C"); + + status = ufx_reg_write(dev, 0x3010, 0x00000000); + check_warn_return(status, "failed to write 0x3010"); + + /* A0h is std for any EDID, right shifted by one */ + status = ufx_reg_clear_and_set_bits(dev, 0x1004, 0x3FF, (0xA0 >> 1)); + check_warn_return(status, "failed to set TAR bits in 0x1004"); + + status = ufx_reg_write(dev, 0x106C, 0x01); + check_warn_return(status, "failed to enable I2C"); + + return 0; +} + +/* wait for BUSY to clear, with a timeout of 50ms with 10ms sleeps. if no + * monitor is connected, there is no error except for timeout */ +static int ufx_i2c_wait_busy(struct ufx_data *dev) +{ + u32 tmp; + int i, status; + + for (i = 0; i < 15; i++) { + status = ufx_reg_read(dev, 0x1100, &tmp); + check_warn_return(status, "0x1100 read failed"); + + /* if BUSY is clear, check for error */ + if ((tmp & 0x80000000) == 0) { + if (tmp & 0x20000000) { + pr_warn("I2C read failed, 0x1100=0x%08x", tmp); + return -EIO; + } + + return 0; + } + + /* perform the first 10 retries without delay */ + if (i >= 10) + msleep(10); + } + + pr_warn("I2C access timed out, resetting I2C hardware"); + status = ufx_reg_write(dev, 0x1100, 0x40000000); + check_warn_return(status, "0x1100 write failed"); + + return -ETIMEDOUT; +} + +/* reads a 128-byte EDID block from the currently selected port and TAR */ +static int ufx_read_edid(struct ufx_data *dev, u8 *edid, int edid_len) +{ + int i, j, status; + u32 *edid_u32 = (u32 *)edid; + + BUG_ON(edid_len != EDID_LENGTH); + + status = ufx_i2c_configure(dev); + if (status < 0) { + pr_err("ufx_i2c_configure failed"); + return status; + } + + memset(edid, 0xff, EDID_LENGTH); + + /* Read the 128-byte EDID as 2 bursts of 64 bytes */ + for (i = 0; i < 2; i++) { + u32 temp = 0x28070000 | (63 << 20) | (((u32)(i * 64)) << 8); + status = ufx_reg_write(dev, 0x1100, temp); + check_warn_return(status, "Failed to write 0x1100"); + + temp |= 0x80000000; + status = ufx_reg_write(dev, 0x1100, temp); + check_warn_return(status, "Failed to write 0x1100"); + + status = ufx_i2c_wait_busy(dev); + check_warn_return(status, "Timeout waiting for I2C BUSY to clear"); + + for (j = 0; j < 16; j++) { + u32 data_reg_addr = 0x1110 + (j * 4); + status = ufx_reg_read(dev, data_reg_addr, edid_u32++); + check_warn_return(status, "Error reading i2c data"); + } + } + + /* all FF's in the first 16 bytes indicates nothing is connected */ + for (i = 0; i < 16; i++) { + if (edid[i] != 0xFF) { + pr_debug("edid data read successfully"); + return EDID_LENGTH; + } + } + + pr_warn("edid data contains all 0xff"); + return -ETIMEDOUT; +} + +/* 1) use sw default + * 2) Parse into various fb_info structs + * 3) Allocate virtual framebuffer memory to back highest res mode + * + * Parses EDID into three places used by various parts of fbdev: + * fb_var_screeninfo contains the timing of the monitor's preferred mode + * fb_info.monspecs is full parsed EDID info, including monspecs.modedb + * fb_info.modelist is a linked list of all monitor & VESA modes which work + * + * If EDID is not readable/valid, then modelist is all VESA modes, + * monspecs is NULL, and fb_var_screeninfo is set to safe VESA mode + * Returns 0 if successful */ +static int ufx_setup_modes(struct ufx_data *dev, struct fb_info *info, + char *default_edid, size_t default_edid_size) +{ + const struct fb_videomode *default_vmode = NULL; + u8 *edid; + int i, result = 0, tries = 3; + + if (info->dev) /* only use mutex if info has been registered */ + mutex_lock(&info->lock); + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) { + result = -ENOMEM; + goto error; + } + + fb_destroy_modelist(&info->modelist); + memset(&info->monspecs, 0, sizeof(info->monspecs)); + + /* Try to (re)read EDID from hardware first + * EDID data may return, but not parse as valid + * Try again a few times, in case of e.g. analog cable noise */ + while (tries--) { + i = ufx_read_edid(dev, edid, EDID_LENGTH); + + if (i >= EDID_LENGTH) + fb_edid_to_monspecs(edid, &info->monspecs); + + if (info->monspecs.modedb_len > 0) { + dev->edid = edid; + dev->edid_size = i; + break; + } + } + + /* If that fails, use a previously returned EDID if available */ + if (info->monspecs.modedb_len == 0) { + pr_err("Unable to get valid EDID from device/display\n"); + + if (dev->edid) { + fb_edid_to_monspecs(dev->edid, &info->monspecs); + if (info->monspecs.modedb_len > 0) + pr_err("Using previously queried EDID\n"); + } + } + + /* If that fails, use the default EDID we were handed */ + if (info->monspecs.modedb_len == 0) { + if (default_edid_size >= EDID_LENGTH) { + fb_edid_to_monspecs(default_edid, &info->monspecs); + if (info->monspecs.modedb_len > 0) { + memcpy(edid, default_edid, default_edid_size); + dev->edid = edid; + dev->edid_size = default_edid_size; + pr_err("Using default/backup EDID\n"); + } + } + } + + /* If we've got modes, let's pick a best default mode */ + if (info->monspecs.modedb_len > 0) { + + for (i = 0; i < info->monspecs.modedb_len; i++) { + if (ufx_is_valid_mode(&info->monspecs.modedb[i], info)) + fb_add_videomode(&info->monspecs.modedb[i], + &info->modelist); + else /* if we've removed top/best mode */ + info->monspecs.misc &= ~FB_MISC_1ST_DETAIL; + } + + default_vmode = fb_find_best_display(&info->monspecs, + &info->modelist); + } + + /* If everything else has failed, fall back to safe default mode */ + if (default_vmode == NULL) { + + struct fb_videomode fb_vmode = {0}; + + /* Add the standard VESA modes to our modelist + * Since we don't have EDID, there may be modes that + * overspec monitor and/or are incorrect aspect ratio, etc. + * But at least the user has a chance to choose + */ + for (i = 0; i < VESA_MODEDB_SIZE; i++) { + if (ufx_is_valid_mode((struct fb_videomode *) + &vesa_modes[i], info)) + fb_add_videomode(&vesa_modes[i], + &info->modelist); + } + + /* default to resolution safe for projectors + * (since they are most common case without EDID) + */ + fb_vmode.xres = 800; + fb_vmode.yres = 600; + fb_vmode.refresh = 60; + default_vmode = fb_find_nearest_mode(&fb_vmode, + &info->modelist); + } + + /* If we have good mode and no active clients */ + if ((default_vmode != NULL) && (dev->fb_count == 0)) { + + fb_videomode_to_var(&info->var, default_vmode); + ufx_var_color_format(&info->var); + + /* with mode size info, we can now alloc our framebuffer */ + memcpy(&info->fix, &ufx_fix, sizeof(ufx_fix)); + info->fix.line_length = info->var.xres * + (info->var.bits_per_pixel / 8); + + result = ufx_realloc_framebuffer(dev, info); + + } else + result = -EINVAL; + +error: + if (edid && (dev->edid != edid)) + kfree(edid); + + if (info->dev) + mutex_unlock(&info->lock); + + return result; +} + +static int ufx_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + struct ufx_data *dev; + struct fb_info *info = NULL; + int retval = -ENOMEM; + u32 id_rev, fpga_rev; + + /* usb initialization */ + usbdev = interface_to_usbdev(interface); + BUG_ON(!usbdev); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&usbdev->dev, "ufx_usb_probe: failed alloc of dev struct\n"); + goto error; + } + + /* we need to wait for both usb and fbdev to spin down on disconnect */ + kref_init(&dev->kref); /* matching kref_put in usb .disconnect fn */ + kref_get(&dev->kref); /* matching kref_put in free_framebuffer_work */ + + dev->udev = usbdev; + dev->gdev = &usbdev->dev; /* our generic struct device * */ + usb_set_intfdata(interface, dev); + + dev_dbg(dev->gdev, "%s %s - serial #%s\n", + usbdev->manufacturer, usbdev->product, usbdev->serial); + dev_dbg(dev->gdev, "vid_%04x&pid_%04x&rev_%04x driver's ufx_data struct at %p\n", + usbdev->descriptor.idVendor, usbdev->descriptor.idProduct, + usbdev->descriptor.bcdDevice, dev); + dev_dbg(dev->gdev, "console enable=%d\n", console); + dev_dbg(dev->gdev, "fb_defio enable=%d\n", fb_defio); + + if (!ufx_alloc_urb_list(dev, WRITES_IN_FLIGHT, MAX_TRANSFER)) { + retval = -ENOMEM; + dev_err(dev->gdev, "ufx_alloc_urb_list failed\n"); + goto error; + } + + /* We don't register a new USB class. Our client interface is fbdev */ + + /* allocates framebuffer driver structure, not framebuffer memory */ + info = framebuffer_alloc(0, &usbdev->dev); + if (!info) { + retval = -ENOMEM; + dev_err(dev->gdev, "framebuffer_alloc failed\n"); + goto error; + } + + dev->info = info; + info->par = dev; + info->pseudo_palette = dev->pseudo_palette; + info->fbops = &ufx_ops; + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) { + dev_err(dev->gdev, "fb_alloc_cmap failed %x\n", retval); + goto error; + } + + INIT_DELAYED_WORK(&dev->free_framebuffer_work, + ufx_free_framebuffer_work); + + INIT_LIST_HEAD(&info->modelist); + + retval = ufx_reg_read(dev, 0x3000, &id_rev); + check_warn_goto_error(retval, "error %d reading 0x3000 register from device", retval); + dev_dbg(dev->gdev, "ID_REV register value 0x%08x", id_rev); + + retval = ufx_reg_read(dev, 0x3004, &fpga_rev); + check_warn_goto_error(retval, "error %d reading 0x3004 register from device", retval); + dev_dbg(dev->gdev, "FPGA_REV register value 0x%08x", fpga_rev); + + dev_dbg(dev->gdev, "resetting device"); + retval = ufx_lite_reset(dev); + check_warn_goto_error(retval, "error %d resetting device", retval); + + dev_dbg(dev->gdev, "configuring system clock"); + retval = ufx_config_sys_clk(dev); + check_warn_goto_error(retval, "error %d configuring system clock", retval); + + dev_dbg(dev->gdev, "configuring DDR2 controller"); + retval = ufx_config_ddr2(dev); + check_warn_goto_error(retval, "error %d initialising DDR2 controller", retval); + + dev_dbg(dev->gdev, "configuring I2C controller"); + retval = ufx_i2c_init(dev); + check_warn_goto_error(retval, "error %d initialising I2C controller", retval); + + dev_dbg(dev->gdev, "selecting display mode"); + retval = ufx_setup_modes(dev, info, NULL, 0); + check_warn_goto_error(retval, "unable to find common mode for display and adapter"); + + retval = ufx_reg_set_bits(dev, 0x4000, 0x00000001); + check_warn_goto_error(retval, "error %d enabling graphics engine", retval); + + /* ready to begin using device */ + atomic_set(&dev->usb_active, 1); + + dev_dbg(dev->gdev, "checking var"); + retval = ufx_ops_check_var(&info->var, info); + check_warn_goto_error(retval, "error %d ufx_ops_check_var", retval); + + dev_dbg(dev->gdev, "setting par"); + retval = ufx_ops_set_par(info); + check_warn_goto_error(retval, "error %d ufx_ops_set_par", retval); + + dev_dbg(dev->gdev, "registering framebuffer"); + retval = register_framebuffer(info); + check_warn_goto_error(retval, "error %d register_framebuffer", retval); + + dev_info(dev->gdev, "SMSC UDX USB device /dev/fb%d attached. %dx%d resolution." + " Using %dK framebuffer memory\n", info->node, + info->var.xres, info->var.yres, info->fix.smem_len >> 10); + + return 0; + +error: + if (dev) { + if (info) { + if (info->cmap.len != 0) + fb_dealloc_cmap(&info->cmap); + if (info->monspecs.modedb) + fb_destroy_modedb(info->monspecs.modedb); + if (info->screen_base) + vfree(info->screen_base); + + fb_destroy_modelist(&info->modelist); + + framebuffer_release(info); + } + + kref_put(&dev->kref, ufx_free); /* ref for framebuffer */ + kref_put(&dev->kref, ufx_free); /* last ref from kref_init */ + + /* dev has been deallocated. Do not dereference */ + } + + return retval; +} + +static void ufx_usb_disconnect(struct usb_interface *interface) +{ + struct ufx_data *dev; + struct fb_info *info; + + dev = usb_get_intfdata(interface); + info = dev->info; + + pr_debug("USB disconnect starting\n"); + + /* we virtualize until all fb clients release. Then we free */ + dev->virtualized = true; + + /* When non-active we'll update virtual framebuffer, but no new urbs */ + atomic_set(&dev->usb_active, 0); + + usb_set_intfdata(interface, NULL); + + /* if clients still have us open, will be freed on last close */ + if (dev->fb_count == 0) + schedule_delayed_work(&dev->free_framebuffer_work, 0); + + /* release reference taken by kref_init in probe() */ + kref_put(&dev->kref, ufx_free); + + /* consider ufx_data freed */ +} + +static struct usb_driver ufx_driver = { + .name = "smscufx", + .probe = ufx_usb_probe, + .disconnect = ufx_usb_disconnect, + .id_table = id_table, +}; + +module_usb_driver(ufx_driver); + +static void ufx_urb_completion(struct urb *urb) +{ + struct urb_node *unode = urb->context; + struct ufx_data *dev = unode->dev; + unsigned long flags; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) { + pr_err("%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + atomic_set(&dev->lost_pixels, 1); + } + } + + urb->transfer_buffer_length = dev->urbs.size; /* reset to actual */ + + spin_lock_irqsave(&dev->urbs.lock, flags); + list_add_tail(&unode->entry, &dev->urbs.list); + dev->urbs.available++; + spin_unlock_irqrestore(&dev->urbs.lock, flags); + + /* When using fb_defio, we deadlock if up() is called + * while another is waiting. So queue to another process */ + if (fb_defio) + schedule_delayed_work(&unode->release_urb_work, 0); + else + up(&dev->urbs.limit_sem); +} + +static void ufx_free_urb_list(struct ufx_data *dev) +{ + int count = dev->urbs.count; + struct list_head *node; + struct urb_node *unode; + struct urb *urb; + int ret; + unsigned long flags; + + pr_debug("Waiting for completes and freeing all render urbs\n"); + + /* keep waiting and freeing, until we've got 'em all */ + while (count--) { + /* Getting interrupted means a leak, but ok at shutdown*/ + ret = down_interruptible(&dev->urbs.limit_sem); + if (ret) + break; + + spin_lock_irqsave(&dev->urbs.lock, flags); + + node = dev->urbs.list.next; /* have reserved one with sem */ + list_del_init(node); + + spin_unlock_irqrestore(&dev->urbs.lock, flags); + + unode = list_entry(node, struct urb_node, entry); + urb = unode->urb; + + /* Free each separately allocated piece */ + usb_free_coherent(urb->dev, dev->urbs.size, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + kfree(node); + } +} + +static int ufx_alloc_urb_list(struct ufx_data *dev, int count, size_t size) +{ + int i = 0; + struct urb *urb; + struct urb_node *unode; + char *buf; + + spin_lock_init(&dev->urbs.lock); + + dev->urbs.size = size; + INIT_LIST_HEAD(&dev->urbs.list); + + while (i < count) { + unode = kzalloc(sizeof(struct urb_node), GFP_KERNEL); + if (!unode) + break; + unode->dev = dev; + + INIT_DELAYED_WORK(&unode->release_urb_work, + ufx_release_urb_work); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + kfree(unode); + break; + } + unode->urb = urb; + + buf = usb_alloc_coherent(dev->udev, size, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + kfree(unode); + usb_free_urb(urb); + break; + } + + /* urb->transfer_buffer_length set to actual before submit */ + usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, 1), + buf, size, ufx_urb_completion, unode); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + list_add_tail(&unode->entry, &dev->urbs.list); + + i++; + } + + sema_init(&dev->urbs.limit_sem, i); + dev->urbs.count = i; + dev->urbs.available = i; + + pr_debug("allocated %d %d byte urbs\n", i, (int) size); + + return i; +} + +static struct urb *ufx_get_urb(struct ufx_data *dev) +{ + int ret = 0; + struct list_head *entry; + struct urb_node *unode; + struct urb *urb = NULL; + unsigned long flags; + + /* Wait for an in-flight buffer to complete and get re-queued */ + ret = down_timeout(&dev->urbs.limit_sem, GET_URB_TIMEOUT); + if (ret) { + atomic_set(&dev->lost_pixels, 1); + pr_warn("wait for urb interrupted: %x available: %d\n", + ret, dev->urbs.available); + goto error; + } + + spin_lock_irqsave(&dev->urbs.lock, flags); + + BUG_ON(list_empty(&dev->urbs.list)); /* reserved one with limit_sem */ + entry = dev->urbs.list.next; + list_del_init(entry); + dev->urbs.available--; + + spin_unlock_irqrestore(&dev->urbs.lock, flags); + + unode = list_entry(entry, struct urb_node, entry); + urb = unode->urb; + +error: + return urb; +} + +static int ufx_submit_urb(struct ufx_data *dev, struct urb *urb, size_t len) +{ + int ret; + + BUG_ON(len > dev->urbs.size); + + urb->transfer_buffer_length = len; /* set to actual payload len */ + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + ufx_urb_completion(urb); /* because no one else will */ + atomic_set(&dev->lost_pixels, 1); + pr_err("usb_submit_urb error %x\n", ret); + } + return ret; +} + +module_param(console, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP); +MODULE_PARM_DESC(console, "Allow fbcon to be used on this display"); + +module_param(fb_defio, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP); +MODULE_PARM_DESC(fb_defio, "Enable fb_defio mmap support"); + +MODULE_AUTHOR("Steve Glendinning <steve.glendinning@shawell.net>"); +MODULE_DESCRIPTION("SMSC UFX kernel framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c new file mode 100644 index 000000000000..f4daa59f0a80 --- /dev/null +++ b/drivers/video/fbdev/ssd1307fb.c @@ -0,0 +1,581 @@ +/* + * Driver for the Solomon SSD1307 OLED controller + * + * Copyright 2012 Free Electrons + * + * Licensed under the GPLv2 or later. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/fb.h> +#include <linux/uaccess.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pwm.h> +#include <linux/delay.h> + +#define SSD1307FB_DATA 0x40 +#define SSD1307FB_COMMAND 0x80 + +#define SSD1307FB_SET_ADDRESS_MODE 0x20 +#define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL (0x00) +#define SSD1307FB_SET_ADDRESS_MODE_VERTICAL (0x01) +#define SSD1307FB_SET_ADDRESS_MODE_PAGE (0x02) +#define SSD1307FB_SET_COL_RANGE 0x21 +#define SSD1307FB_SET_PAGE_RANGE 0x22 +#define SSD1307FB_CONTRAST 0x81 +#define SSD1307FB_CHARGE_PUMP 0x8d +#define SSD1307FB_SEG_REMAP_ON 0xa1 +#define SSD1307FB_DISPLAY_OFF 0xae +#define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8 +#define SSD1307FB_DISPLAY_ON 0xaf +#define SSD1307FB_START_PAGE_ADDRESS 0xb0 +#define SSD1307FB_SET_DISPLAY_OFFSET 0xd3 +#define SSD1307FB_SET_CLOCK_FREQ 0xd5 +#define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9 +#define SSD1307FB_SET_COM_PINS_CONFIG 0xda +#define SSD1307FB_SET_VCOMH 0xdb + +struct ssd1307fb_par; + +struct ssd1307fb_ops { + int (*init)(struct ssd1307fb_par *); + int (*remove)(struct ssd1307fb_par *); +}; + +struct ssd1307fb_par { + struct i2c_client *client; + u32 height; + struct fb_info *info; + struct ssd1307fb_ops *ops; + u32 page_offset; + struct pwm_device *pwm; + u32 pwm_period; + int reset; + u32 width; +}; + +struct ssd1307fb_array { + u8 type; + u8 data[0]; +}; + +static struct fb_fix_screeninfo ssd1307fb_fix = { + .id = "Solomon SSD1307", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_MONO10, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo ssd1307fb_var = { + .bits_per_pixel = 1, +}; + +static struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type) +{ + struct ssd1307fb_array *array; + + array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL); + if (!array) + return NULL; + + array->type = type; + + return array; +} + +static int ssd1307fb_write_array(struct i2c_client *client, + struct ssd1307fb_array *array, u32 len) +{ + int ret; + + len += sizeof(struct ssd1307fb_array); + + ret = i2c_master_send(client, (u8 *)array, len); + if (ret != len) { + dev_err(&client->dev, "Couldn't send I2C command.\n"); + return ret; + } + + return 0; +} + +static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd) +{ + struct ssd1307fb_array *array; + int ret; + + array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND); + if (!array) + return -ENOMEM; + + array->data[0] = cmd; + + ret = ssd1307fb_write_array(client, array, 1); + kfree(array); + + return ret; +} + +static inline int ssd1307fb_write_data(struct i2c_client *client, u8 data) +{ + struct ssd1307fb_array *array; + int ret; + + array = ssd1307fb_alloc_array(1, SSD1307FB_DATA); + if (!array) + return -ENOMEM; + + array->data[0] = data; + + ret = ssd1307fb_write_array(client, array, 1); + kfree(array); + + return ret; +} + +static void ssd1307fb_update_display(struct ssd1307fb_par *par) +{ + struct ssd1307fb_array *array; + u8 *vmem = par->info->screen_base; + int i, j, k; + + array = ssd1307fb_alloc_array(par->width * par->height / 8, + SSD1307FB_DATA); + if (!array) + return; + + /* + * The screen is divided in pages, each having a height of 8 + * pixels, and the width of the screen. When sending a byte of + * data to the controller, it gives the 8 bits for the current + * column. I.e, the first byte are the 8 bits of the first + * column, then the 8 bits for the second column, etc. + * + * + * Representation of the screen, assuming it is 5 bits + * wide. Each letter-number combination is a bit that controls + * one pixel. + * + * A0 A1 A2 A3 A4 + * B0 B1 B2 B3 B4 + * C0 C1 C2 C3 C4 + * D0 D1 D2 D3 D4 + * E0 E1 E2 E3 E4 + * F0 F1 F2 F3 F4 + * G0 G1 G2 G3 G4 + * H0 H1 H2 H3 H4 + * + * If you want to update this screen, you need to send 5 bytes: + * (1) A0 B0 C0 D0 E0 F0 G0 H0 + * (2) A1 B1 C1 D1 E1 F1 G1 H1 + * (3) A2 B2 C2 D2 E2 F2 G2 H2 + * (4) A3 B3 C3 D3 E3 F3 G3 H3 + * (5) A4 B4 C4 D4 E4 F4 G4 H4 + */ + + for (i = 0; i < (par->height / 8); i++) { + for (j = 0; j < par->width; j++) { + u32 array_idx = i * par->width + j; + array->data[array_idx] = 0; + for (k = 0; k < 8; k++) { + u32 page_length = par->width * i; + u32 index = page_length + (par->width * k + j) / 8; + u8 byte = *(vmem + index); + u8 bit = byte & (1 << (j % 8)); + bit = bit >> (j % 8); + array->data[array_idx] |= bit << k; + } + } + } + + ssd1307fb_write_array(par->client, array, par->width * par->height / 8); + kfree(array); +} + + +static ssize_t ssd1307fb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct ssd1307fb_par *par = info->par; + unsigned long total_size; + unsigned long p = *ppos; + u8 __iomem *dst; + + total_size = info->fix.smem_len; + + if (p > total_size) + return -EINVAL; + + if (count + p > total_size) + count = total_size - p; + + if (!count) + return -EINVAL; + + dst = (void __force *) (info->screen_base + p); + + if (copy_from_user(dst, buf, count)) + return -EFAULT; + + ssd1307fb_update_display(par); + + *ppos += count; + + return count; +} + +static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct ssd1307fb_par *par = info->par; + sys_fillrect(info, rect); + ssd1307fb_update_display(par); +} + +static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct ssd1307fb_par *par = info->par; + sys_copyarea(info, area); + ssd1307fb_update_display(par); +} + +static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct ssd1307fb_par *par = info->par; + sys_imageblit(info, image); + ssd1307fb_update_display(par); +} + +static struct fb_ops ssd1307fb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = ssd1307fb_write, + .fb_fillrect = ssd1307fb_fillrect, + .fb_copyarea = ssd1307fb_copyarea, + .fb_imageblit = ssd1307fb_imageblit, +}; + +static void ssd1307fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + ssd1307fb_update_display(info->par); +} + +static struct fb_deferred_io ssd1307fb_defio = { + .delay = HZ, + .deferred_io = ssd1307fb_deferred_io, +}; + +static int ssd1307fb_ssd1307_init(struct ssd1307fb_par *par) +{ + int ret; + + par->pwm = pwm_get(&par->client->dev, NULL); + if (IS_ERR(par->pwm)) { + dev_err(&par->client->dev, "Could not get PWM from device tree!\n"); + return PTR_ERR(par->pwm); + } + + par->pwm_period = pwm_get_period(par->pwm); + /* Enable the PWM */ + pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period); + pwm_enable(par->pwm); + + dev_dbg(&par->client->dev, "Using PWM%d with a %dns period.\n", + par->pwm->pwm, par->pwm_period); + + /* Map column 127 of the OLED to segment 0 */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); + if (ret < 0) + return ret; + + /* Turn on the display */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); + if (ret < 0) + return ret; + + return 0; +} + +static int ssd1307fb_ssd1307_remove(struct ssd1307fb_par *par) +{ + pwm_disable(par->pwm); + pwm_put(par->pwm); + return 0; +} + +static struct ssd1307fb_ops ssd1307fb_ssd1307_ops = { + .init = ssd1307fb_ssd1307_init, + .remove = ssd1307fb_ssd1307_remove, +}; + +static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) +{ + int ret; + + /* Set initial contrast */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); + ret = ret & ssd1307fb_write_cmd(par->client, 0x7f); + if (ret < 0) + return ret; + + /* Set COM direction */ + ret = ssd1307fb_write_cmd(par->client, 0xc8); + if (ret < 0) + return ret; + + /* Set segment re-map */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); + if (ret < 0) + return ret; + + /* Set multiplex ratio value */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO); + ret = ret & ssd1307fb_write_cmd(par->client, par->height - 1); + if (ret < 0) + return ret; + + /* set display offset value */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET); + ret = ssd1307fb_write_cmd(par->client, 0x20); + if (ret < 0) + return ret; + + /* Set clock frequency */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ); + ret = ret & ssd1307fb_write_cmd(par->client, 0xf0); + if (ret < 0) + return ret; + + /* Set precharge period in number of ticks from the internal clock */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD); + ret = ret & ssd1307fb_write_cmd(par->client, 0x22); + if (ret < 0) + return ret; + + /* Set COM pins configuration */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG); + ret = ret & ssd1307fb_write_cmd(par->client, 0x22); + if (ret < 0) + return ret; + + /* Set VCOMH */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH); + ret = ret & ssd1307fb_write_cmd(par->client, 0x49); + if (ret < 0) + return ret; + + /* Turn on the DC-DC Charge Pump */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP); + ret = ret & ssd1307fb_write_cmd(par->client, 0x14); + if (ret < 0) + return ret; + + /* Switch to horizontal addressing mode */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); + ret = ret & ssd1307fb_write_cmd(par->client, + SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); + ret = ret & ssd1307fb_write_cmd(par->client, 0x0); + ret = ret & ssd1307fb_write_cmd(par->client, par->width - 1); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); + ret = ret & ssd1307fb_write_cmd(par->client, 0x0); + ret = ret & ssd1307fb_write_cmd(par->client, + par->page_offset + (par->height / 8) - 1); + if (ret < 0) + return ret; + + /* Turn on the display */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); + if (ret < 0) + return ret; + + return 0; +} + +static struct ssd1307fb_ops ssd1307fb_ssd1306_ops = { + .init = ssd1307fb_ssd1306_init, +}; + +static const struct of_device_id ssd1307fb_of_match[] = { + { + .compatible = "solomon,ssd1306fb-i2c", + .data = (void *)&ssd1307fb_ssd1306_ops, + }, + { + .compatible = "solomon,ssd1307fb-i2c", + .data = (void *)&ssd1307fb_ssd1307_ops, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ssd1307fb_of_match); + +static int ssd1307fb_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct fb_info *info; + struct device_node *node = client->dev.of_node; + u32 vmem_size; + struct ssd1307fb_par *par; + u8 *vmem; + int ret; + + if (!node) { + dev_err(&client->dev, "No device tree data found!\n"); + return -EINVAL; + } + + info = framebuffer_alloc(sizeof(struct ssd1307fb_par), &client->dev); + if (!info) { + dev_err(&client->dev, "Couldn't allocate framebuffer.\n"); + return -ENOMEM; + } + + par = info->par; + par->info = info; + par->client = client; + + par->ops = (struct ssd1307fb_ops *)of_match_device(ssd1307fb_of_match, + &client->dev)->data; + + par->reset = of_get_named_gpio(client->dev.of_node, + "reset-gpios", 0); + if (!gpio_is_valid(par->reset)) { + ret = -EINVAL; + goto fb_alloc_error; + } + + if (of_property_read_u32(node, "solomon,width", &par->width)) + par->width = 96; + + if (of_property_read_u32(node, "solomon,height", &par->height)) + par->width = 16; + + if (of_property_read_u32(node, "solomon,page-offset", &par->page_offset)) + par->page_offset = 1; + + vmem_size = par->width * par->height / 8; + + vmem = devm_kzalloc(&client->dev, vmem_size, GFP_KERNEL); + if (!vmem) { + dev_err(&client->dev, "Couldn't allocate graphical memory.\n"); + ret = -ENOMEM; + goto fb_alloc_error; + } + + info->fbops = &ssd1307fb_ops; + info->fix = ssd1307fb_fix; + info->fix.line_length = par->width / 8; + info->fbdefio = &ssd1307fb_defio; + + info->var = ssd1307fb_var; + info->var.xres = par->width; + info->var.xres_virtual = par->width; + info->var.yres = par->height; + info->var.yres_virtual = par->height; + + info->var.red.length = 1; + info->var.red.offset = 0; + info->var.green.length = 1; + info->var.green.offset = 0; + info->var.blue.length = 1; + info->var.blue.offset = 0; + + info->screen_base = (u8 __force __iomem *)vmem; + info->fix.smem_start = (unsigned long)vmem; + info->fix.smem_len = vmem_size; + + fb_deferred_io_init(info); + + ret = devm_gpio_request_one(&client->dev, par->reset, + GPIOF_OUT_INIT_HIGH, + "oled-reset"); + if (ret) { + dev_err(&client->dev, + "failed to request gpio %d: %d\n", + par->reset, ret); + goto reset_oled_error; + } + + i2c_set_clientdata(client, info); + + /* Reset the screen */ + gpio_set_value(par->reset, 0); + udelay(4); + gpio_set_value(par->reset, 1); + udelay(4); + + if (par->ops->init) { + ret = par->ops->init(par); + if (ret) + goto reset_oled_error; + } + + ret = register_framebuffer(info); + if (ret) { + dev_err(&client->dev, "Couldn't register the framebuffer\n"); + goto panel_init_error; + } + + dev_info(&client->dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size); + + return 0; + +panel_init_error: + if (par->ops->remove) + par->ops->remove(par); +reset_oled_error: + fb_deferred_io_cleanup(info); +fb_alloc_error: + framebuffer_release(info); + return ret; +} + +static int ssd1307fb_remove(struct i2c_client *client) +{ + struct fb_info *info = i2c_get_clientdata(client); + struct ssd1307fb_par *par = info->par; + + unregister_framebuffer(info); + if (par->ops->remove) + par->ops->remove(par); + fb_deferred_io_cleanup(info); + framebuffer_release(info); + + return 0; +} + +static const struct i2c_device_id ssd1307fb_i2c_id[] = { + { "ssd1306fb", 0 }, + { "ssd1307fb", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id); + +static struct i2c_driver ssd1307fb_driver = { + .probe = ssd1307fb_probe, + .remove = ssd1307fb_remove, + .id_table = ssd1307fb_i2c_id, + .driver = { + .name = "ssd1307fb", + .of_match_table = ssd1307fb_of_match, + .owner = THIS_MODULE, + }, +}; + +module_i2c_driver(ssd1307fb_driver); + +MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controller"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/sstfb.c b/drivers/video/fbdev/sstfb.c new file mode 100644 index 000000000000..f0cb279ef333 --- /dev/null +++ b/drivers/video/fbdev/sstfb.c @@ -0,0 +1,1532 @@ +/* + * 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 <asm/io.h> +#include <linux/uaccess.h> +#include <video/sstfb.h> + + +/* initialized by setup */ + +static bool vgapass; /* enable VGA passthrough cable */ +static int mem; /* mem size in MB, 0 = autodetect */ +static bool clipping = 1; /* use clipping (slower, safer) */ +static int gfxclk; /* force FBI freq in Mhz . Dangerous */ +static bool 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 = DEFAULT_VIDEO_MODE; + +enum { + ID_VOODOO1 = 0, + ID_VOODOO2 = 1, +}; + +#define IS_VOODOO2(par) ((par)->type == ID_VOODOO2) + +static struct sst_spec voodoo_spec[] = { + { .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 basically, 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 strange 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; + } + + f_ddprintk("hsync_len hSyncOff vsync_len vSyncOff\n"); + f_ddprintk("%-7d %-8d %-7d %-8d\n", + info->var.hsync_len, par->hSyncOff, + par->vSyncOn, par->vSyncOff); + f_ddprintk("left_margin upper_margin xres yres Freq\n"); + f_ddprintk("%-10d %-10d %-4d %-4d %-8ld\n", + info->var.left_margin, info->var.upper_margin, + info->var.xres, info->var.yres, PICOS2KHZ(info->var.pixclock)); + + sst_write(NOPCMD, 0); + sst_wait_idle(); + pci_write_config_dword(sst_dev, PCI_INIT_ENABLE, PCI_EN_INIT_WR); + sst_set_bits(FBIINIT1, VIDEO_RESET); + sst_set_bits(FBIINIT0, FBI_RESET | FIFO_RESET); + sst_unset_bits(FBIINIT2, EN_DRAM_REFRESH); + sst_wait_idle(); + + /*sst_unset_bits (FBIINIT0, FBI_RESET); / reenable FBI ? */ + + sst_write(BACKPORCH, par->vBackPorch << 16 | (info->var.left_margin - 2)); + sst_write(VIDEODIMENSIONS, par->yDim << 16 | (info->var.xres - 1)); + sst_write(HSYNC, (par->hSyncOff - 1) << 16 | (info->var.hsync_len - 1)); + sst_write(VSYNC, par->vSyncOff << 16 | par->vSyncOn); + + fbiinit2 = sst_read(FBIINIT2); + fbiinit3 = sst_read(FBIINIT3); + + /* everything is reset. we enable fbiinit2/3 remap : dac access ok */ + pci_write_config_dword(sst_dev, PCI_INIT_ENABLE, + PCI_EN_INIT_WR | PCI_REMAP_DAC ); + + par->dac_sw.set_vidmod(info, info->var.bits_per_pixel); + + /* set video clock */ + par->dac_sw.set_pll(info, &par->pll, VID_CLOCK); + + /* disable fbiinit2/3 remap */ + pci_write_config_dword(sst_dev, PCI_INIT_ENABLE, + PCI_EN_INIT_WR); + + /* restore fbiinit2/3 */ + sst_write(FBIINIT2,fbiinit2); + sst_write(FBIINIT3,fbiinit3); + + fbiinit1 = (sst_read(FBIINIT1) & VIDEO_MASK) + | EN_DATA_OE + | EN_BLANK_OE + | EN_HVSYNC_OE + | EN_DCLK_OE + /* | (15 << TILES_IN_X_SHIFT) */ + | SEL_INPUT_VCLK_2X + /* | (2 << VCLK_2X_SEL_DEL_SHIFT) + | (2 << VCLK_DEL_SHIFT) */; +/* try with vclk_in_delay =0 (bits 29:30) , vclk_out_delay =0 (bits(27:28) + in (near) future set them accordingly to revision + resolution (cf glide) + first understand what it stands for :) + FIXME: there are some artefacts... check for the vclk_in_delay + lets try with 6ns delay in both vclk_out & in... + doh... they're still there :\ +*/ + + ntiles = par->tiles_in_X; + if (IS_VOODOO2(par)) { + fbiinit1 |= ((ntiles & 0x20) >> 5) << TILES_IN_X_MSB_SHIFT + | ((ntiles & 0x1e) >> 1) << TILES_IN_X_SHIFT; +/* as the only value of importance for us in fbiinit6 is tiles in X (lsb), + and as reading fbinit 6 will return crap (see FBIINIT6_DEFAULT) we just + write our value. BTW due to the dac unable to read odd number of tiles, this + field is always null ... */ + fbiinit6 = (ntiles & 0x1) << TILES_IN_X_LSB_SHIFT; + } + else + fbiinit1 |= ntiles << TILES_IN_X_SHIFT; + + switch (info->var.bits_per_pixel) { + case 16: + fbiinit1 |= SEL_SOURCE_VCLK_2X_SEL; + break; + default: + return -EINVAL; + } + sst_write(FBIINIT1, fbiinit1); + if (IS_VOODOO2(par)) { + sst_write(FBIINIT6, fbiinit6); + fbiinit5=sst_read(FBIINIT5) & FBIINIT5_MASK ; + if (info->var.vmode & FB_VMODE_INTERLACED) + fbiinit5 |= INTERLACE; + if (info->var.vmode & FB_VMODE_DOUBLE) + fbiinit5 |= VDOUBLESCAN; + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) + fbiinit5 |= HSYNC_HIGH; + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) + fbiinit5 |= VSYNC_HIGH; + sst_write(FBIINIT5, fbiinit5); + } + sst_wait_idle(); + sst_unset_bits(FBIINIT1, VIDEO_RESET); + sst_unset_bits(FBIINIT0, FBI_RESET | FIFO_RESET); + sst_set_bits(FBIINIT2, EN_DRAM_REFRESH); + /* disables fbiinit writes */ + pci_write_config_dword(sst_dev, PCI_INIT_ENABLE, PCI_EN_FIFO_WR); + + /* set lfbmode : set mode + front buffer for reads/writes + + disable pipeline */ + switch (info->var.bits_per_pixel) { + case 16: + lfbmode = LFB_565; + break; + default: + return -EINVAL; + } + +#if defined(__BIG_ENDIAN) + /* Enable byte-swizzle functionality in hardware. + * With this enabled, all our read- and write-accesses to + * the voodoo framebuffer can be done in native format, and + * the hardware will automatically convert it to little-endian. + * - tested on HP-PARISC, Helge Deller <deller@gmx.de> */ + lfbmode |= ( LFB_WORD_SWIZZLE_WR | LFB_BYTE_SWIZZLE_WR | + LFB_WORD_SWIZZLE_RD | LFB_BYTE_SWIZZLE_RD ); +#endif + + if (clipping) { + sst_write(LFBMODE, lfbmode | EN_PXL_PIPELINE); + /* + * Set "clipping" dimensions. If clipping is disabled and + * writes to offscreen areas of the framebuffer are performed, + * the "behaviour is undefined" (_very_ undefined) - Urs + */ + /* btw, it requires enabling pixel pipeline in LFBMODE . + off screen read/writes will just wrap and read/print pixels + on screen. Ugly but not that dangerous */ + f_ddprintk("setting clipping dimensions 0..%d, 0..%d\n", + info->var.xres - 1, par->yDim - 1); + + sst_write(CLIP_LEFT_RIGHT, info->var.xres); + sst_write(CLIP_LOWY_HIGHY, par->yDim); + sst_set_bits(FBZMODE, EN_CLIPPING | EN_RGB_WRITE); + } else { + /* no clipping : direct access, no pipeline */ + sst_write(LFBMODE, lfbmode); + } + return 0; +} + +/** + * sstfb_setcolreg - Optional function. Sets a color register. + * @regno: hardware colormap register + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int sstfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct sstfb_par *par = info->par; + u32 col; + + f_dddprintk("sstfb_setcolreg\n"); + f_dddprintk("%-2d rgbt: %#x, %#x, %#x, %#x\n", + regno, red, green, blue, transp); + if (regno > 15) + return 0; + + red >>= (16 - info->var.red.length); + green >>= (16 - info->var.green.length); + blue >>= (16 - info->var.blue.length); + transp >>= (16 - info->var.transp.length); + col = (red << info->var.red.offset) + | (green << info->var.green.offset) + | (blue << info->var.blue.offset) + | (transp << info->var.transp.offset); + + par->palette[regno] = col; + + return 0; +} + +static void sstfb_setvgapass( struct fb_info *info, int enable ) +{ + struct sstfb_par *par = info->par; + struct pci_dev *sst_dev = par->dev; + u32 fbiinit0, tmp; + + enable = enable ? 1:0; + if (par->vgapass == enable) + return; + par->vgapass = enable; + + pci_read_config_dword(sst_dev, PCI_INIT_ENABLE, &tmp); + pci_write_config_dword(sst_dev, PCI_INIT_ENABLE, + tmp | PCI_EN_INIT_WR ); + fbiinit0 = sst_read (FBIINIT0); + if (par->vgapass) { + sst_write(FBIINIT0, fbiinit0 & ~DIS_VGA_PASSTHROUGH); + fb_info(info, "Enabling VGA pass-through\n"); + } else { + sst_write(FBIINIT0, fbiinit0 | DIS_VGA_PASSTHROUGH); + fb_info(info, "Disabling VGA pass-through\n"); + } + pci_write_config_dword(sst_dev, PCI_INIT_ENABLE, tmp); +} + +static ssize_t store_vgapass(struct device *device, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(device); + char ** last = NULL; + int val; + + val = simple_strtoul(buf, last, 0); + sstfb_setvgapass(info, val); + + return count; +} + +static ssize_t show_vgapass(struct device *device, struct device_attribute *attr, + char *buf) +{ + struct fb_info *info = dev_get_drvdata(device); + struct sstfb_par *par = info->par; + return snprintf(buf, PAGE_SIZE, "%d\n", par->vgapass); +} + +static struct device_attribute device_attrs[] = { + __ATTR(vgapass, S_IRUGO|S_IWUSR, show_vgapass, store_vgapass) + }; + +static int sstfb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct sstfb_par *par; + u32 val; + + switch (cmd) { + /* set/get VGA pass_through mode */ + case SSTFB_SET_VGAPASS: + if (copy_from_user(&val, (void __user *)arg, sizeof(val))) + return -EFAULT; + sstfb_setvgapass(info, val); + return 0; + case SSTFB_GET_VGAPASS: + par = info->par; + val = par->vgapass; + if (copy_to_user((void __user *)arg, &val, sizeof(val))) + return -EFAULT; + return 0; + } + + return -EINVAL; +} + + +/* + * Screen-to-Screen BitBlt 2D command (for the bmove fb op.) - Voodoo2 only + */ +#if 0 +static void sstfb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct sstfb_par *par = info->par; + u32 stride = info->fix.line_length; + + if (!IS_VOODOO2(par)) + return; + + sst_write(BLTSRCBASEADDR, 0); + sst_write(BLTDSTBASEADDR, 0); + sst_write(BLTROP, BLTROP_COPY); + sst_write(BLTXYSTRIDES, stride | (stride << 16)); + sst_write(BLTSRCXY, area->sx | (area->sy << 16)); + sst_write(BLTDSTXY, area->dx | (area->dy << 16)); + sst_write(BLTSIZE, area->width | (area->height << 16)); + sst_write(BLTCOMMAND, BLT_SCR2SCR_BITBLT | LAUNCH_BITBLT | + (BLT_16BPP_FMT << 3) /* | BIT(14) */ | BIT(15) ); + sst_wait_idle(); +} +#endif + + +/* + * FillRect 2D command (solidfill or invert (via ROP_XOR)) - Voodoo2 only + */ +#if 0 +static void sstfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct sstfb_par *par = info->par; + u32 stride = info->fix.line_length; + + if (!IS_VOODOO2(par)) + return; + + sst_write(BLTCLIPX, info->var.xres); + sst_write(BLTCLIPY, info->var.yres); + + sst_write(BLTDSTBASEADDR, 0); + sst_write(BLTCOLOR, rect->color); + sst_write(BLTROP, rect->rop == ROP_COPY ? BLTROP_COPY : BLTROP_XOR); + sst_write(BLTXYSTRIDES, stride | (stride << 16)); + sst_write(BLTDSTXY, rect->dx | (rect->dy << 16)); + sst_write(BLTSIZE, rect->width | (rect->height << 16)); + sst_write(BLTCOMMAND, BLT_RECFILL_BITBLT | LAUNCH_BITBLT + | (BLT_16BPP_FMT << 3) /* | BIT(14) */ | BIT(15) | BIT(16) ); + sst_wait_idle(); +} +#endif + + + +/* + * get lfb size + */ +static int sst_get_memsize(struct fb_info *info, __u32 *memsize) +{ + u8 __iomem *fbbase_virt = info->screen_base; + + /* force memsize */ + if (mem >= 1 && mem <= 4) { + *memsize = (mem * 0x100000); + printk(KERN_INFO "supplied memsize: %#x\n", *memsize); + return 1; + } + + writel(0xdeadbeef, fbbase_virt); + writel(0xdeadbeef, fbbase_virt+0x100000); + writel(0xdeadbeef, fbbase_virt+0x200000); + f_ddprintk("0MB: %#x, 1MB: %#x, 2MB: %#x\n", + readl(fbbase_virt), readl(fbbase_virt + 0x100000), + readl(fbbase_virt + 0x200000)); + + writel(0xabcdef01, fbbase_virt); + + f_ddprintk("0MB: %#x, 1MB: %#x, 2MB: %#x\n", + readl(fbbase_virt), readl(fbbase_virt + 0x100000), + readl(fbbase_virt + 0x200000)); + + /* checks for 4mb lfb, then 2, then defaults to 1 */ + if (readl(fbbase_virt + 0x200000) == 0xdeadbeef) + *memsize = 0x400000; + else if (readl(fbbase_virt + 0x100000) == 0xdeadbeef) + *memsize = 0x200000; + else + *memsize = 0x100000; + f_ddprintk("detected memsize: %dMB\n", *memsize >> 20); + return 1; +} + + +/* + * DAC detection routines + */ + +/* fbi should be idle, and fifo emty and mem disabled */ +/* supposed to detect AT&T ATT20C409 and Ti TVP3409 ramdacs */ + +static int sst_detect_att(struct fb_info *info) +{ + struct sstfb_par *par = info->par; + int i, mir, dir; + + for (i = 0; i < 3; i++) { + sst_dac_write(DACREG_WMA, 0); /* backdoor */ + sst_dac_read(DACREG_RMR); /* read 4 times RMR */ + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + /* the fifth time, CR0 is read */ + sst_dac_read(DACREG_RMR); + /* the 6th, manufacturer id register */ + mir = sst_dac_read(DACREG_RMR); + /*the 7th, device ID register */ + dir = sst_dac_read(DACREG_RMR); + f_ddprintk("mir: %#x, dir: %#x\n", mir, dir); + if (mir == DACREG_MIR_ATT && dir == DACREG_DIR_ATT) { + return 1; + } + } + return 0; +} + +static int sst_detect_ti(struct fb_info *info) +{ + struct sstfb_par *par = info->par; + int i, mir, dir; + + for (i = 0; i<3; i++) { + sst_dac_write(DACREG_WMA, 0); /* backdoor */ + sst_dac_read(DACREG_RMR); /* read 4 times RMR */ + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + /* the fifth time, CR0 is read */ + sst_dac_read(DACREG_RMR); + /* the 6th, manufacturer id register */ + mir = sst_dac_read(DACREG_RMR); + /*the 7th, device ID register */ + dir = sst_dac_read(DACREG_RMR); + f_ddprintk("mir: %#x, dir: %#x\n", mir, dir); + if ((mir == DACREG_MIR_TI ) && (dir == DACREG_DIR_TI)) { + return 1; + } + } + return 0; +} + +/* + * try to detect ICS5342 ramdac + * we get the 1st byte (M value) of preset f1,f7 and fB + * why those 3 ? mmmh... for now, i'll do it the glide way... + * and ask questions later. anyway, it seems that all the freq registers are + * really at their default state (cf specs) so i ask again, why those 3 regs ? + * mmmmh.. it seems that's much more ugly than i thought. we use f0 and fA for + * pll programming, so in fact, we *hope* that the f1, f7 & fB won't be + * touched... + * is it really safe ? how can i reset this ramdac ? geee... + */ +static int sst_detect_ics(struct fb_info *info) +{ + struct sstfb_par *par = info->par; + int m_clk0_1, m_clk0_7, m_clk1_b; + int n_clk0_1, n_clk0_7, n_clk1_b; + int i; + + for (i = 0; i<5; i++ ) { + sst_dac_write(DACREG_ICS_PLLRMA, 0x1); /* f1 */ + m_clk0_1 = sst_dac_read(DACREG_ICS_PLLDATA); + n_clk0_1 = sst_dac_read(DACREG_ICS_PLLDATA); + sst_dac_write(DACREG_ICS_PLLRMA, 0x7); /* f7 */ + m_clk0_7 = sst_dac_read(DACREG_ICS_PLLDATA); + n_clk0_7 = sst_dac_read(DACREG_ICS_PLLDATA); + sst_dac_write(DACREG_ICS_PLLRMA, 0xb); /* fB */ + m_clk1_b= sst_dac_read(DACREG_ICS_PLLDATA); + n_clk1_b= sst_dac_read(DACREG_ICS_PLLDATA); + f_ddprintk("m_clk0_1: %#x, m_clk0_7: %#x, m_clk1_b: %#x\n", + m_clk0_1, m_clk0_7, m_clk1_b); + f_ddprintk("n_clk0_1: %#x, n_clk0_7: %#x, n_clk1_b: %#x\n", + n_clk0_1, n_clk0_7, n_clk1_b); + if (( m_clk0_1 == DACREG_ICS_PLL_CLK0_1_INI) + && (m_clk0_7 == DACREG_ICS_PLL_CLK0_7_INI) + && (m_clk1_b == DACREG_ICS_PLL_CLK1_B_INI)) { + return 1; + } + } + return 0; +} + + +/* + * gfx, video, pci fifo should be reset, dram refresh disabled + * see detect_dac + */ + +static int sst_set_pll_att_ti(struct fb_info *info, + const struct pll_timing *t, const int clock) +{ + struct sstfb_par *par = info->par; + u8 cr0, cc; + + /* enable indexed mode */ + sst_dac_write(DACREG_WMA, 0); /* backdoor */ + sst_dac_read(DACREG_RMR); /* 1 time: RMR */ + sst_dac_read(DACREG_RMR); /* 2 RMR */ + sst_dac_read(DACREG_RMR); /* 3 // */ + sst_dac_read(DACREG_RMR); /* 4 // */ + cr0 = sst_dac_read(DACREG_RMR); /* 5 CR0 */ + + sst_dac_write(DACREG_WMA, 0); + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + sst_dac_write(DACREG_RMR, (cr0 & 0xf0) + | DACREG_CR0_EN_INDEXED + | DACREG_CR0_8BIT + | DACREG_CR0_PWDOWN ); + /* so, now we are in indexed mode . dunno if its common, but + i find this way of doing things a little bit weird :p */ + + udelay(300); + cc = dac_i_read(DACREG_CC_I); + switch (clock) { + case VID_CLOCK: + dac_i_write(DACREG_AC0_I, t->m); + dac_i_write(DACREG_AC1_I, t->p << 6 | t->n); + dac_i_write(DACREG_CC_I, + (cc & 0x0f) | DACREG_CC_CLKA | DACREG_CC_CLKA_C); + break; + case GFX_CLOCK: + dac_i_write(DACREG_BD0_I, t->m); + dac_i_write(DACREG_BD1_I, t->p << 6 | t->n); + dac_i_write(DACREG_CC_I, + (cc & 0xf0) | DACREG_CC_CLKB | DACREG_CC_CLKB_D); + break; + default: + dprintk("%s: wrong clock code '%d'\n", + __func__, clock); + return 0; + } + udelay(300); + + /* power up the dac & return to "normal" non-indexed mode */ + dac_i_write(DACREG_CR0_I, + cr0 & ~DACREG_CR0_PWDOWN & ~DACREG_CR0_EN_INDEXED); + return 1; +} + +static int sst_set_pll_ics(struct fb_info *info, + const struct pll_timing *t, const int clock) +{ + struct sstfb_par *par = info->par; + u8 pll_ctrl; + + sst_dac_write(DACREG_ICS_PLLRMA, DACREG_ICS_PLL_CTRL); + pll_ctrl = sst_dac_read(DACREG_ICS_PLLDATA); + switch(clock) { + case VID_CLOCK: + sst_dac_write(DACREG_ICS_PLLWMA, 0x0); /* CLK0, f0 */ + sst_dac_write(DACREG_ICS_PLLDATA, t->m); + sst_dac_write(DACREG_ICS_PLLDATA, t->p << 5 | t->n); + /* selects freq f0 for clock 0 */ + sst_dac_write(DACREG_ICS_PLLWMA, DACREG_ICS_PLL_CTRL); + sst_dac_write(DACREG_ICS_PLLDATA, + (pll_ctrl & 0xd8) + | DACREG_ICS_CLK0 + | DACREG_ICS_CLK0_0); + break; + case GFX_CLOCK : + sst_dac_write(DACREG_ICS_PLLWMA, 0xa); /* CLK1, fA */ + sst_dac_write(DACREG_ICS_PLLDATA, t->m); + sst_dac_write(DACREG_ICS_PLLDATA, t->p << 5 | t->n); + /* selects freq fA for clock 1 */ + sst_dac_write(DACREG_ICS_PLLWMA, DACREG_ICS_PLL_CTRL); + sst_dac_write(DACREG_ICS_PLLDATA, + (pll_ctrl & 0xef) | DACREG_ICS_CLK1_A); + break; + default: + dprintk("%s: wrong clock code '%d'\n", + __func__, clock); + return 0; + } + udelay(300); + return 1; +} + +static void sst_set_vidmod_att_ti(struct fb_info *info, const int bpp) +{ + struct sstfb_par *par = info->par; + u8 cr0; + + sst_dac_write(DACREG_WMA, 0); /* backdoor */ + sst_dac_read(DACREG_RMR); /* read 4 times RMR */ + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + /* the fifth time, CR0 is read */ + cr0 = sst_dac_read(DACREG_RMR); + + sst_dac_write(DACREG_WMA, 0); /* backdoor */ + sst_dac_read(DACREG_RMR); /* read 4 times RMR */ + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + sst_dac_read(DACREG_RMR); + /* cr0 */ + switch(bpp) { + case 16: + sst_dac_write(DACREG_RMR, (cr0 & 0x0f) | DACREG_CR0_16BPP); + break; + default: + dprintk("%s: bad depth '%u'\n", __func__, bpp); + break; + } +} + +static void sst_set_vidmod_ics(struct fb_info *info, const int bpp) +{ + struct sstfb_par *par = info->par; + + switch(bpp) { + case 16: + sst_dac_write(DACREG_ICS_CMD, DACREG_ICS_CMD_16BPP); + break; + default: + dprintk("%s: bad depth '%u'\n", __func__, bpp); + break; + } +} + +/* + * detect dac type + * prerequisite : write to FbiInitx enabled, video and fbi and pci fifo reset, + * dram refresh disabled, FbiInit remaped. + * TODO: mmh.. maybe i should put the "prerequisite" in the func ... + */ + + +static struct dac_switch dacs[] = { + { .name = "TI TVP3409", + .detect = sst_detect_ti, + .set_pll = sst_set_pll_att_ti, + .set_vidmod = sst_set_vidmod_att_ti }, + + { .name = "AT&T ATT20C409", + .detect = sst_detect_att, + .set_pll = sst_set_pll_att_ti, + .set_vidmod = sst_set_vidmod_att_ti }, + { .name = "ICS ICS5342", + .detect = sst_detect_ics, + .set_pll = sst_set_pll_ics, + .set_vidmod = sst_set_vidmod_ics }, +}; + +static int sst_detect_dactype(struct fb_info *info, struct sstfb_par *par) +{ + int i, ret = 0; + + for (i = 0; i < ARRAY_SIZE(dacs); i++) { + ret = dacs[i].detect(info); + if (ret) + break; + } + if (!ret) + return 0; + f_dprintk("%s found %s\n", __func__, dacs[i].name); + par->dac_sw = dacs[i]; + return 1; +} + +/* + * Internal Routines + */ +static int sst_init(struct fb_info *info, struct sstfb_par *par) +{ + u32 fbiinit0, fbiinit1, fbiinit4; + struct pci_dev *dev = par->dev; + struct pll_timing gfx_timings; + struct sst_spec *spec; + int Fout; + int gfx_clock; + + spec = &voodoo_spec[par->type]; + f_ddprintk(" fbiinit0 fbiinit1 fbiinit2 fbiinit3 fbiinit4 " + " fbiinit6\n"); + f_ddprintk("%0#10x %0#10x %0#10x %0#10x %0#10x %0#10x\n", + sst_read(FBIINIT0), sst_read(FBIINIT1), sst_read(FBIINIT2), + sst_read(FBIINIT3), sst_read(FBIINIT4), sst_read(FBIINIT6)); + /* disable video clock */ + pci_write_config_dword(dev, PCI_VCLK_DISABLE, 0); + + /* enable writing to init registers, disable pci fifo */ + pci_write_config_dword(dev, PCI_INIT_ENABLE, PCI_EN_INIT_WR); + /* reset video */ + sst_set_bits(FBIINIT1, VIDEO_RESET); + sst_wait_idle(); + /* reset gfx + pci fifo */ + sst_set_bits(FBIINIT0, FBI_RESET | FIFO_RESET); + sst_wait_idle(); + + /* unreset fifo */ + /*sst_unset_bits(FBIINIT0, FIFO_RESET); + sst_wait_idle();*/ + /* unreset FBI */ + /*sst_unset_bits(FBIINIT0, FBI_RESET); + sst_wait_idle();*/ + + /* disable dram refresh */ + sst_unset_bits(FBIINIT2, EN_DRAM_REFRESH); + sst_wait_idle(); + /* remap fbinit2/3 to dac */ + pci_write_config_dword(dev, PCI_INIT_ENABLE, + PCI_EN_INIT_WR | PCI_REMAP_DAC ); + /* detect dac type */ + if (!sst_detect_dactype(info, par)) { + printk(KERN_ERR "sstfb: unknown dac type.\n"); + //FIXME watch it: we are not in a safe state, bad bad bad. + return 0; + } + + /* set graphic clock */ + gfx_clock = spec->default_gfx_clock; + if ((gfxclk >10 ) && (gfxclk < spec->max_gfxclk)) { + printk(KERN_INFO "sstfb: Using supplied graphic freq : %dMHz\n", gfxclk); + gfx_clock = gfxclk *1000; + } else if (gfxclk) { + printk(KERN_WARNING "sstfb: %dMhz is way out of spec! Using default\n", gfxclk); + } + + sst_calc_pll(gfx_clock, &Fout, &gfx_timings); + par->dac_sw.set_pll(info, &gfx_timings, GFX_CLOCK); + + /* disable fbiinit remap */ + pci_write_config_dword(dev, PCI_INIT_ENABLE, + PCI_EN_INIT_WR| PCI_EN_FIFO_WR ); + /* defaults init registers */ + /* FbiInit0: unreset gfx, unreset fifo */ + fbiinit0 = FBIINIT0_DEFAULT; + fbiinit1 = FBIINIT1_DEFAULT; + fbiinit4 = FBIINIT4_DEFAULT; + par->vgapass = vgapass; + if (par->vgapass) + fbiinit0 &= ~DIS_VGA_PASSTHROUGH; + else + fbiinit0 |= DIS_VGA_PASSTHROUGH; + if (slowpci) { + fbiinit1 |= SLOW_PCI_WRITES; + fbiinit4 |= SLOW_PCI_READS; + } else { + fbiinit1 &= ~SLOW_PCI_WRITES; + fbiinit4 &= ~SLOW_PCI_READS; + } + sst_write(FBIINIT0, fbiinit0); + sst_wait_idle(); + sst_write(FBIINIT1, fbiinit1); + sst_wait_idle(); + sst_write(FBIINIT2, FBIINIT2_DEFAULT); + sst_wait_idle(); + sst_write(FBIINIT3, FBIINIT3_DEFAULT); + sst_wait_idle(); + sst_write(FBIINIT4, fbiinit4); + sst_wait_idle(); + if (IS_VOODOO2(par)) { + sst_write(FBIINIT6, FBIINIT6_DEFAULT); + sst_wait_idle(); + } + + pci_write_config_dword(dev, PCI_INIT_ENABLE, PCI_EN_FIFO_WR); + pci_write_config_dword(dev, PCI_VCLK_ENABLE, 0); + return 1; +} + +static void sst_shutdown(struct fb_info *info) +{ + struct sstfb_par *par = info->par; + struct pci_dev *dev = par->dev; + struct pll_timing gfx_timings; + int Fout; + + /* reset video, gfx, fifo, disable dram + remap fbiinit2/3 */ + pci_write_config_dword(dev, PCI_INIT_ENABLE, PCI_EN_INIT_WR); + sst_set_bits(FBIINIT1, VIDEO_RESET | EN_BLANKING); + sst_unset_bits(FBIINIT2, EN_DRAM_REFRESH); + sst_set_bits(FBIINIT0, FBI_RESET | FIFO_RESET); + sst_wait_idle(); + pci_write_config_dword(dev, PCI_INIT_ENABLE, + PCI_EN_INIT_WR | PCI_REMAP_DAC); + /* set 20Mhz gfx clock */ + sst_calc_pll(20000, &Fout, &gfx_timings); + par->dac_sw.set_pll(info, &gfx_timings, GFX_CLOCK); + /* TODO maybe shutdown the dac, vrefresh and so on... */ + pci_write_config_dword(dev, PCI_INIT_ENABLE, + PCI_EN_INIT_WR); + sst_unset_bits(FBIINIT0, FBI_RESET | FIFO_RESET | DIS_VGA_PASSTHROUGH); + pci_write_config_dword(dev, PCI_VCLK_DISABLE,0); + /* maybe keep fbiinit* and PCI_INIT_enable in the fb_info struct + * from start ? */ + pci_write_config_dword(dev, PCI_INIT_ENABLE, 0); + +} + +/* + * Interface to the world + */ +static int sstfb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + + f_ddprintk("option %s\n", this_opt); + + if (!strcmp(this_opt, "vganopass")) + vgapass = 0; + else if (!strcmp(this_opt, "vgapass")) + vgapass = 1; + else if (!strcmp(this_opt, "clipping")) + clipping = 1; + else if (!strcmp(this_opt, "noclipping")) + clipping = 0; + else if (!strcmp(this_opt, "fastpci")) + slowpci = 0; + else if (!strcmp(this_opt, "slowpci")) + slowpci = 1; + else if (!strncmp(this_opt, "mem:",4)) + mem = simple_strtoul (this_opt+4, NULL, 0); + else if (!strncmp(this_opt, "gfxclk:",7)) + gfxclk = simple_strtoul (this_opt+7, NULL, 0); + else + mode_option = this_opt; + } + return 0; +} + + +static struct fb_ops sstfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = sstfb_check_var, + .fb_set_par = sstfb_set_par, + .fb_setcolreg = sstfb_setcolreg, + .fb_fillrect = cfb_fillrect, /* sstfb_fillrect */ + .fb_copyarea = cfb_copyarea, /* sstfb_copyarea */ + .fb_imageblit = cfb_imageblit, + .fb_ioctl = sstfb_ioctl, +}; + +static int sstfb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct fb_info *info; + struct fb_fix_screeninfo *fix; + struct sstfb_par *par; + struct sst_spec *spec; + int err; + + /* Enable device in PCI config. */ + if ((err=pci_enable_device(pdev))) { + printk(KERN_ERR "cannot enable device\n"); + return err; + } + + /* Allocate the fb and par structures. */ + info = framebuffer_alloc(sizeof(struct sstfb_par), &pdev->dev); + if (!info) + return -ENOMEM; + + pci_set_drvdata(pdev, info); + + par = info->par; + fix = &info->fix; + + par->type = id->driver_data; + spec = &voodoo_spec[par->type]; + f_ddprintk("found device : %s\n", spec->name); + + par->dev = pdev; + par->revision = pdev->revision; + + fix->mmio_start = pci_resource_start(pdev,0); + fix->mmio_len = 0x400000; + fix->smem_start = fix->mmio_start + 0x400000; + + if (!request_mem_region(fix->mmio_start, fix->mmio_len, "sstfb MMIO")) { + printk(KERN_ERR "sstfb: cannot reserve mmio memory\n"); + goto fail_mmio_mem; + } + + if (!request_mem_region(fix->smem_start, 0x400000,"sstfb FB")) { + printk(KERN_ERR "sstfb: cannot reserve fb memory\n"); + goto fail_fb_mem; + } + + par->mmio_vbase = ioremap_nocache(fix->mmio_start, + fix->mmio_len); + if (!par->mmio_vbase) { + printk(KERN_ERR "sstfb: cannot remap register area %#lx\n", + fix->mmio_start); + goto fail_mmio_remap; + } + info->screen_base = ioremap_nocache(fix->smem_start, 0x400000); + if (!info->screen_base) { + printk(KERN_ERR "sstfb: cannot remap framebuffer %#lx\n", + fix->smem_start); + goto fail_fb_remap; + } + + if (!sst_init(info, par)) { + printk(KERN_ERR "sstfb: Init failed\n"); + goto fail; + } + sst_get_memsize(info, &fix->smem_len); + strlcpy(fix->id, spec->name, sizeof(fix->id)); + + printk(KERN_INFO "%s (revision %d) with %s dac\n", + fix->id, par->revision, par->dac_sw.name); + printk(KERN_INFO "framebuffer at %#lx, mapped to 0x%p, size %dMB\n", + fix->smem_start, info->screen_base, + fix->smem_len >> 20); + + f_ddprintk("regbase_virt: %#lx\n", par->mmio_vbase); + f_ddprintk("membase_phys: %#lx\n", fix->smem_start); + f_ddprintk("fbbase_virt: %p\n", info->screen_base); + + info->flags = FBINFO_DEFAULT; + info->fbops = &sstfb_ops; + info->pseudo_palette = par->palette; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->accel = FB_ACCEL_NONE; /* FIXME */ + /* + * According to the specs, the linelength must be of 1024 *pixels* + * and the 24bpp mode is in fact a 32 bpp mode (and both are in + * fact dithered to 16bit). + */ + fix->line_length = 2048; /* default value, for 24 or 32bit: 4096 */ + + fb_find_mode(&info->var, info, mode_option, NULL, 0, NULL, 16); + + if (sstfb_check_var(&info->var, info)) { + printk(KERN_ERR "sstfb: invalid video mode.\n"); + goto fail; + } + + if (sstfb_set_par(info)) { + printk(KERN_ERR "sstfb: can't set default video mode.\n"); + goto fail; + } + + if (fb_alloc_cmap(&info->cmap, 256, 0)) { + printk(KERN_ERR "sstfb: can't alloc cmap memory.\n"); + goto fail; + } + + /* register fb */ + info->device = &pdev->dev; + if (register_framebuffer(info) < 0) { + printk(KERN_ERR "sstfb: can't register framebuffer.\n"); + goto fail_register; + } + + sstfb_clear_screen(info); + + if (device_create_file(info->dev, &device_attrs[0])) + printk(KERN_WARNING "sstfb: can't create sysfs entry.\n"); + + + fb_info(info, "%s frame buffer device at 0x%p\n", + fix->id, info->screen_base); + + return 0; + +fail_register: + fb_dealloc_cmap(&info->cmap); +fail: + iounmap(info->screen_base); +fail_fb_remap: + iounmap(par->mmio_vbase); +fail_mmio_remap: + release_mem_region(fix->smem_start, 0x400000); +fail_fb_mem: + release_mem_region(fix->mmio_start, info->fix.mmio_len); +fail_mmio_mem: + framebuffer_release(info); + return -ENXIO; /* no voodoo detected */ +} + +static void sstfb_remove(struct pci_dev *pdev) +{ + struct sstfb_par *par; + struct fb_info *info; + + info = pci_get_drvdata(pdev); + par = info->par; + + device_remove_file(info->dev, &device_attrs[0]); + sst_shutdown(info); + iounmap(info->screen_base); + iounmap(par->mmio_vbase); + release_mem_region(info->fix.smem_start, 0x400000); + release_mem_region(info->fix.mmio_start, info->fix.mmio_len); + fb_dealloc_cmap(&info->cmap); + unregister_framebuffer(info); + framebuffer_release(info); +} + + +static const struct pci_device_id sstfb_id_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_3DFX, PCI_DEVICE_ID_3DFX_VOODOO ), + .driver_data = ID_VOODOO1, }, + { PCI_DEVICE(PCI_VENDOR_ID_3DFX, PCI_DEVICE_ID_3DFX_VOODOO2), + .driver_data = ID_VOODOO2, }, + { 0 }, +}; + +static struct pci_driver sstfb_driver = { + .name = "sstfb", + .id_table = sstfb_id_tbl, + .probe = sstfb_probe, + .remove = sstfb_remove, +}; + + +static int sstfb_init(void) +{ + char *option = NULL; + + if (fb_get_options("sstfb", &option)) + return -ENODEV; + sstfb_setup(option); + + return pci_register_driver(&sstfb_driver); +} + +static void sstfb_exit(void) +{ + pci_unregister_driver(&sstfb_driver); +} + + +module_init(sstfb_init); +module_exit(sstfb_exit); + +MODULE_AUTHOR("(c) 2000,2002 Ghozlane Toumi <gtoumi@laposte.net>"); +MODULE_DESCRIPTION("FBDev driver for 3dfx Voodoo Graphics and Voodoo2 based video boards"); +MODULE_LICENSE("GPL"); + +module_param(mem, int, 0); +MODULE_PARM_DESC(mem, "Size of frame buffer memory in MB (1, 2, 4 MB, default=autodetect)"); +module_param(vgapass, bool, 0); +MODULE_PARM_DESC(vgapass, "Enable VGA PassThrough mode (0 or 1) (default=0)"); +module_param(clipping, bool, 0); +MODULE_PARM_DESC(clipping, "Enable clipping (slower, safer) (0 or 1) (default=1)"); +module_param(gfxclk, int, 0); +MODULE_PARM_DESC(gfxclk, "Force graphic chip frequency in MHz. DANGEROUS. (default=auto)"); +module_param(slowpci, bool, 0); +MODULE_PARM_DESC(slowpci, "Uses slow PCI settings (0 or 1) (default=0)"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Initial video mode (default=" DEFAULT_VIDEO_MODE ")"); + diff --git a/drivers/video/fbdev/sticore.h b/drivers/video/fbdev/sticore.h new file mode 100644 index 000000000000..af1619536ac8 --- /dev/null +++ b/drivers/video/fbdev/sticore.h @@ -0,0 +1,401 @@ +#ifndef STICORE_H +#define STICORE_H + +/* generic STI structures & functions */ + +#if 0 +#define DPRINTK(x) printk x +#else +#define DPRINTK(x) +#endif + +#define MAX_STI_ROMS 4 /* max no. of ROMs which this driver handles */ + +#define STI_REGION_MAX 8 /* hardcoded STI constants */ +#define STI_DEV_NAME_LENGTH 32 +#define STI_MONITOR_MAX 256 + +#define STI_FONT_HPROMAN8 1 +#define STI_FONT_KANA8 2 + +#define ALT_CODE_TYPE_UNKNOWN 0x00 /* alt code type values */ +#define ALT_CODE_TYPE_PA_RISC_64 0x01 + +/* The latency of the STI functions cannot really be reduced by setting + * this to 0; STI doesn't seem to be designed to allow calling a different + * function (or the same function with different arguments) after a + * function exited with 1 as return value. + * + * As all of the functions below could be called from interrupt context, + * we have to spin_lock_irqsave around the do { ret = bla(); } while(ret==1) + * block. Really bad latency there. + * + * Probably the best solution to all this is have the generic code manage + * the screen buffer and a kernel thread to call STI occasionally. + * + * Luckily, the frame buffer guys have the same problem so we can just wait + * for them to fix it and steal their solution. prumpf + */ + +#include <asm/io.h> + +#define STI_WAIT 1 + +#define STI_PTR(p) ( virt_to_phys(p) ) +#define PTR_STI(p) ( phys_to_virt((unsigned long)p) ) + +#define sti_onscreen_x(sti) (sti->glob_cfg->onscreen_x) +#define sti_onscreen_y(sti) (sti->glob_cfg->onscreen_y) + +/* sti_font_xy() use the native font ROM ! */ +#define sti_font_x(sti) (PTR_STI(sti->font)->width) +#define sti_font_y(sti) (PTR_STI(sti->font)->height) + +#ifdef CONFIG_64BIT +#define STI_LOWMEM (GFP_KERNEL | GFP_DMA) +#else +#define STI_LOWMEM (GFP_KERNEL) +#endif + + +/* STI function configuration structs */ + +typedef union region { + struct { + u32 offset : 14; /* offset in 4kbyte page */ + u32 sys_only : 1; /* don't map to user space */ + u32 cache : 1; /* map to data cache */ + u32 btlb : 1; /* map to block tlb */ + u32 last : 1; /* last region in list */ + u32 length : 14; /* length in 4kbyte page */ + } region_desc; + + u32 region; /* complete region value */ +} region_t; + +#define REGION_OFFSET_TO_PHYS( rt, hpa ) \ + (((rt).region_desc.offset << 12) + (hpa)) + +struct sti_glob_cfg_ext { + u8 curr_mon; /* current monitor configured */ + u8 friendly_boot; /* in friendly boot mode */ + s16 power; /* power calculation (in Watts) */ + s32 freq_ref; /* frequency reference */ + u32 sti_mem_addr; /* pointer to global sti memory (size=sti_mem_request) */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_glob_cfg { + s32 text_planes; /* number of planes used for text */ + s16 onscreen_x; /* screen width in pixels */ + s16 onscreen_y; /* screen height in pixels */ + s16 offscreen_x; /* offset width in pixels */ + s16 offscreen_y; /* offset height in pixels */ + s16 total_x; /* frame buffer width in pixels */ + s16 total_y; /* frame buffer height in pixels */ + u32 region_ptrs[STI_REGION_MAX]; /* region pointers */ + s32 reent_lvl; /* storage for reentry level value */ + u32 save_addr; /* where to save or restore reentrant state */ + u32 ext_ptr; /* pointer to extended glob_cfg data structure */ +}; + + +/* STI init function structs */ + +struct sti_init_flags { + u32 wait : 1; /* should routine idle wait or not */ + u32 reset : 1; /* hard reset the device? */ + u32 text : 1; /* turn on text display planes? */ + u32 nontext : 1; /* turn on non-text display planes? */ + u32 clear : 1; /* clear text display planes? */ + u32 cmap_blk : 1; /* non-text planes cmap black? */ + u32 enable_be_timer : 1; /* enable bus error timer */ + u32 enable_be_int : 1; /* enable bus error timer interrupt */ + u32 no_chg_tx : 1; /* don't change text settings */ + u32 no_chg_ntx : 1; /* don't change non-text settings */ + u32 no_chg_bet : 1; /* don't change berr timer settings */ + u32 no_chg_bei : 1; /* don't change berr int settings */ + u32 init_cmap_tx : 1; /* initialize cmap for text planes */ + u32 cmt_chg : 1; /* change current monitor type */ + u32 retain_ie : 1; /* don't allow reset to clear int enables */ + u32 caller_bootrom : 1; /* set only by bootrom for each call */ + u32 caller_kernel : 1; /* set only by kernel for each call */ + u32 caller_other : 1; /* set only by non-[BR/K] caller */ + u32 pad : 14; /* pad to word boundary */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_init_inptr_ext { + u8 config_mon_type; /* configure to monitor type */ + u8 pad[1]; /* pad to word boundary */ + u16 inflight_data; /* inflight data possible on PCI */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_init_inptr { + s32 text_planes; /* number of planes to use for text */ + u32 ext_ptr; /* pointer to extended init_graph inptr data structure*/ +}; + + +struct sti_init_outptr { + s32 errno; /* error number on failure */ + s32 text_planes; /* number of planes used for text */ + u32 future_ptr; /* pointer to future data */ +}; + + + +/* STI configuration function structs */ + +struct sti_conf_flags { + u32 wait : 1; /* should routine idle wait or not */ + u32 pad : 31; /* pad to word boundary */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_conf_inptr { + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_conf_outptr_ext { + u32 crt_config[3]; /* hardware specific X11/OGL information */ + u32 crt_hdw[3]; + u32 future_ptr; +}; + +struct sti_conf_outptr { + s32 errno; /* error number on failure */ + s16 onscreen_x; /* screen width in pixels */ + s16 onscreen_y; /* screen height in pixels */ + s16 offscreen_x; /* offscreen width in pixels */ + s16 offscreen_y; /* offscreen height in pixels */ + s16 total_x; /* frame buffer width in pixels */ + s16 total_y; /* frame buffer height in pixels */ + s32 bits_per_pixel; /* bits/pixel device has configured */ + s32 bits_used; /* bits which can be accessed */ + s32 planes; /* number of fb planes in system */ + u8 dev_name[STI_DEV_NAME_LENGTH]; /* null terminated product name */ + u32 attributes; /* flags denoting attributes */ + u32 ext_ptr; /* pointer to future data */ +}; + +struct sti_rom { + u8 type[4]; + u8 res004; + u8 num_mons; + u8 revno[2]; + u32 graphics_id[2]; + + u32 font_start; + u32 statesize; + u32 last_addr; + u32 region_list; + + u16 reentsize; + u16 maxtime; + u32 mon_tbl_addr; + u32 user_data_addr; + u32 sti_mem_req; + + u32 user_data_size; + u16 power; + u8 bus_support; + u8 ext_bus_support; + u8 alt_code_type; + u8 ext_dd_struct[3]; + u32 cfb_addr; + + u32 init_graph; + u32 state_mgmt; + u32 font_unpmv; + u32 block_move; + u32 self_test; + u32 excep_hdlr; + u32 inq_conf; + u32 set_cm_entry; + u32 dma_ctrl; + u8 res040[7 * 4]; + + u32 init_graph_addr; + u32 state_mgmt_addr; + u32 font_unp_addr; + u32 block_move_addr; + u32 self_test_addr; + u32 excep_hdlr_addr; + u32 inq_conf_addr; + u32 set_cm_entry_addr; + u32 image_unpack_addr; + u32 pa_risx_addrs[7]; +}; + +struct sti_rom_font { + u16 first_char; + u16 last_char; + u8 width; + u8 height; + u8 font_type; /* language type */ + u8 bytes_per_char; + u32 next_font; + u8 underline_height; + u8 underline_pos; + u8 res008[2]; +}; + +/* sticore internal font handling */ + +struct sti_cooked_font { + struct sti_rom_font *raw; + struct sti_cooked_font *next_font; +}; + +struct sti_cooked_rom { + struct sti_rom *raw; + struct sti_cooked_font *font_start; +}; + +/* STI font printing function structs */ + +struct sti_font_inptr { + u32 font_start_addr; /* address of font start */ + s16 index; /* index into font table of character */ + u8 fg_color; /* foreground color of character */ + u8 bg_color; /* background color of character */ + s16 dest_x; /* X location of character upper left */ + s16 dest_y; /* Y location of character upper left */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_font_flags { + u32 wait : 1; /* should routine idle wait or not */ + u32 non_text : 1; /* font unpack/move in non_text planes =1, text =0 */ + u32 pad : 30; /* pad to word boundary */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_font_outptr { + s32 errno; /* error number on failure */ + u32 future_ptr; /* pointer to future data */ +}; + +/* STI blockmove structs */ + +struct sti_blkmv_flags { + u32 wait : 1; /* should routine idle wait or not */ + u32 color : 1; /* change color during move? */ + u32 clear : 1; /* clear during move? */ + u32 non_text : 1; /* block move in non_text planes =1, text =0 */ + u32 pad : 28; /* pad to word boundary */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_blkmv_inptr { + u8 fg_color; /* foreground color after move */ + u8 bg_color; /* background color after move */ + s16 src_x; /* source upper left pixel x location */ + s16 src_y; /* source upper left pixel y location */ + s16 dest_x; /* dest upper left pixel x location */ + s16 dest_y; /* dest upper left pixel y location */ + s16 width; /* block width in pixels */ + s16 height; /* block height in pixels */ + u32 future_ptr; /* pointer to future data */ +}; + +struct sti_blkmv_outptr { + s32 errno; /* error number on failure */ + u32 future_ptr; /* pointer to future data */ +}; + + +/* sti_all_data is an internal struct which needs to be allocated in + * low memory (< 4GB) if STI is used with 32bit STI on a 64bit kernel */ + +struct sti_all_data { + struct sti_glob_cfg glob_cfg; + struct sti_glob_cfg_ext glob_cfg_ext; + + struct sti_conf_inptr inq_inptr; + struct sti_conf_outptr inq_outptr; /* configuration */ + struct sti_conf_outptr_ext inq_outptr_ext; + + struct sti_init_inptr_ext init_inptr_ext; + struct sti_init_inptr init_inptr; + struct sti_init_outptr init_outptr; + + struct sti_blkmv_inptr blkmv_inptr; + struct sti_blkmv_outptr blkmv_outptr; + + struct sti_font_inptr font_inptr; + struct sti_font_outptr font_outptr; + + /* leave as last entries */ + unsigned long save_addr[1024 / sizeof(unsigned long)]; + /* min 256 bytes which is STI default, max sti->sti_mem_request */ + unsigned long sti_mem_addr[256 / sizeof(unsigned long)]; + /* do not add something below here ! */ +}; + +/* internal generic STI struct */ + +struct sti_struct { + spinlock_t lock; + + /* the following fields needs to be filled in by the word/byte routines */ + int font_width; + int font_height; + /* char **mon_strings; */ + int sti_mem_request; + u32 graphics_id[2]; + + struct sti_cooked_rom *rom; + + unsigned long font_unpmv; + unsigned long block_move; + unsigned long init_graph; + unsigned long inq_conf; + + /* all following fields are initialized by the generic routines */ + int text_planes; + region_t regions[STI_REGION_MAX]; + unsigned long regions_phys[STI_REGION_MAX]; + + struct sti_glob_cfg *glob_cfg; /* points into sti_all_data */ + + struct sti_cooked_font *font; /* ptr to selected font (cooked) */ + + struct pci_dev *pd; + + /* PCI data structures (pg. 17ff from sti.pdf) */ + u8 rm_entry[16]; /* pci region mapper array == pci config space offset */ + + /* pointer to the fb_info where this STI device is used */ + struct fb_info *info; + + /* pointer to all internal data */ + struct sti_all_data *sti_data; +}; + + +/* sticore interface functions */ + +struct sti_struct *sti_get_rom(unsigned int index); /* 0: default sti */ + + +/* sticore main function to call STI firmware */ + +int sti_call(const struct sti_struct *sti, unsigned long func, + const void *flags, void *inptr, void *outptr, + struct sti_glob_cfg *glob_cfg); + + +/* functions to call the STI ROM directly */ + +void sti_putc(struct sti_struct *sti, int c, int y, int x); +void sti_set(struct sti_struct *sti, int src_y, int src_x, + int height, int width, u8 color); +void sti_clear(struct sti_struct *sti, int src_y, int src_x, + int height, int width, int c); +void sti_bmove(struct sti_struct *sti, int src_y, int src_x, + int dst_y, int dst_x, int height, int width); + +#endif /* STICORE_H */ diff --git a/drivers/video/fbdev/stifb.c b/drivers/video/fbdev/stifb.c new file mode 100644 index 000000000000..cfe8a2f905c5 --- /dev/null +++ b/drivers/video/fbdev/stifb.c @@ -0,0 +1,1417 @@ +/* + * 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 <asm/grfioctl.h> /* for HP-UX compatibility */ +#include <asm/uaccess.h> + +#include "sticore.h" + +/* REGION_BASE(fb_info, index) returns the virtual address for region <index> */ +#define REGION_BASE(fb_info, index) \ + F_EXTEND(fb_info->sti->glob_cfg->region_ptrs[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_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_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) + +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) + memset(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_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + struct stifb_info *fb = (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 = (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: /* fall through */ + case S9000_ID_TIMBER: + case CRX24_OVERLAY_PLANES: + default: + ENABLE_DISABLE_DISPLAY(fb, enable); + break; + } + + SETUP_FB(fb); + return 0; +} + +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 struct fb_ops stifb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = stifb_setcolreg, + .fb_blank = stifb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_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 *dev_name; + int bpp, xres, yres; + + fb = kzalloc(sizeof(*fb), GFP_ATOMIC); + if (!fb) { + printk(KERN_ERR "stifb: Could not allocate stifb structure\n"); + return -ENODEV; + } + + 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; + } + /* fall though */ + 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_nocache(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 = yres*fix->line_length; + + 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_nocache(REGION_BASE(fb,1), fix->smem_len); + info->screen_size = fix->smem_len; + info->flags = FBINFO_DEFAULT; + info->pseudo_palette = &fb->pseudo_palette; + + /* 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; + } + + if (register_framebuffer(&fb->info) < 0) + goto out_err4; + + sti->info = info; /* save for unregister_framebuffer() */ + + 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); + 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"); diff --git a/drivers/video/fbdev/sunxvr1000.c b/drivers/video/fbdev/sunxvr1000.c new file mode 100644 index 000000000000..58241b47a96d --- /dev/null +++ b/drivers/video/fbdev/sunxvr1000.c @@ -0,0 +1,229 @@ +/* sunxvr1000.c: Sun XVR-1000 driver for sparc64 systems + * + * Copyright (C) 2010 David S. Miller (davem@davemloft.net) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/of_device.h> + +struct gfb_info { + struct fb_info *info; + + char __iomem *fb_base; + unsigned long fb_base_phys; + + struct device_node *of_node; + + unsigned int width; + unsigned int height; + unsigned int depth; + unsigned int fb_size; + + u32 pseudo_palette[16]; +}; + +static int gfb_get_props(struct gfb_info *gp) +{ + gp->width = of_getintprop_default(gp->of_node, "width", 0); + gp->height = of_getintprop_default(gp->of_node, "height", 0); + gp->depth = of_getintprop_default(gp->of_node, "depth", 32); + + if (!gp->width || !gp->height) { + printk(KERN_ERR "gfb: Critical properties missing for %s\n", + gp->of_node->full_name); + return -EINVAL; + } + + return 0; +} + +static int gfb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + u32 value; + + if (regno < 16) { + red >>= 8; + green >>= 8; + blue >>= 8; + + value = (blue << 16) | (green << 8) | red; + ((u32 *)info->pseudo_palette)[regno] = value; + } + + return 0; +} + +static struct fb_ops gfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = gfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int gfb_set_fbinfo(struct gfb_info *gp) +{ + struct fb_info *info = gp->info; + struct fb_var_screeninfo *var = &info->var; + + info->flags = FBINFO_DEFAULT; + info->fbops = &gfb_ops; + info->screen_base = gp->fb_base; + info->screen_size = gp->fb_size; + + info->pseudo_palette = gp->pseudo_palette; + + /* Fill fix common fields */ + strlcpy(info->fix.id, "gfb", sizeof(info->fix.id)); + info->fix.smem_start = gp->fb_base_phys; + info->fix.smem_len = gp->fb_size; + info->fix.type = FB_TYPE_PACKED_PIXELS; + if (gp->depth == 32 || gp->depth == 24) + info->fix.visual = FB_VISUAL_TRUECOLOR; + else + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + var->xres = gp->width; + var->yres = gp->height; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + var->bits_per_pixel = gp->depth; + + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + + if (fb_alloc_cmap(&info->cmap, 256, 0)) { + printk(KERN_ERR "gfb: Cannot allocate color map.\n"); + return -ENOMEM; + } + + return 0; +} + +static int gfb_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct gfb_info *gp; + int err; + + info = framebuffer_alloc(sizeof(struct gfb_info), &op->dev); + if (!info) { + printk(KERN_ERR "gfb: Cannot allocate fb_info\n"); + err = -ENOMEM; + goto err_out; + } + + gp = info->par; + gp->info = info; + gp->of_node = dp; + + gp->fb_base_phys = op->resource[6].start; + + err = gfb_get_props(gp); + if (err) + goto err_release_fb; + + /* Framebuffer length is the same regardless of resolution. */ + info->fix.line_length = 16384; + gp->fb_size = info->fix.line_length * gp->height; + + gp->fb_base = of_ioremap(&op->resource[6], 0, + gp->fb_size, "gfb fb"); + if (!gp->fb_base) { + err = -ENOMEM; + goto err_release_fb; + } + + err = gfb_set_fbinfo(gp); + if (err) + goto err_unmap_fb; + + printk("gfb: Found device at %s\n", dp->full_name); + + err = register_framebuffer(info); + if (err < 0) { + printk(KERN_ERR "gfb: Could not register framebuffer %s\n", + dp->full_name); + goto err_unmap_fb; + } + + dev_set_drvdata(&op->dev, info); + + return 0; + +err_unmap_fb: + of_iounmap(&op->resource[6], gp->fb_base, gp->fb_size); + +err_release_fb: + framebuffer_release(info); + +err_out: + return err; +} + +static int gfb_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct gfb_info *gp = info->par; + + unregister_framebuffer(info); + + iounmap(gp->fb_base); + + of_iounmap(&op->resource[6], gp->fb_base, gp->fb_size); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id gfb_match[] = { + { + .name = "SUNW,gfb", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ffb_match); + +static struct platform_driver gfb_driver = { + .probe = gfb_probe, + .remove = gfb_remove, + .driver = { + .name = "gfb", + .owner = THIS_MODULE, + .of_match_table = gfb_match, + }, +}; + +static int __init gfb_init(void) +{ + if (fb_get_options("gfb", NULL)) + return -ENODEV; + + return platform_driver_register(&gfb_driver); +} + +static void __exit gfb_exit(void) +{ + platform_driver_unregister(&gfb_driver); +} + +module_init(gfb_init); +module_exit(gfb_exit); + +MODULE_DESCRIPTION("framebuffer driver for Sun XVR-1000 graphics"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/sunxvr2500.c b/drivers/video/fbdev/sunxvr2500.c new file mode 100644 index 000000000000..843b6bab0483 --- /dev/null +++ b/drivers/video/fbdev/sunxvr2500.c @@ -0,0 +1,276 @@ +/* s3d.c: Sun 3DLABS XVR-2500 et al. driver for sparc64 systems + * + * Copyright (C) 2007 David S. Miller (davem@davemloft.net) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/of_device.h> + +#include <asm/io.h> + +struct s3d_info { + struct fb_info *info; + struct pci_dev *pdev; + + char __iomem *fb_base; + unsigned long fb_base_phys; + + struct device_node *of_node; + + unsigned int width; + unsigned int height; + unsigned int depth; + unsigned int fb_size; + + u32 pseudo_palette[16]; +}; + +static int s3d_get_props(struct s3d_info *sp) +{ + sp->width = of_getintprop_default(sp->of_node, "width", 0); + sp->height = of_getintprop_default(sp->of_node, "height", 0); + sp->depth = of_getintprop_default(sp->of_node, "depth", 8); + + if (!sp->width || !sp->height) { + printk(KERN_ERR "s3d: Critical properties missing for %s\n", + pci_name(sp->pdev)); + return -EINVAL; + } + + return 0; +} + +static int s3d_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + u32 value; + + if (regno < 16) { + red >>= 8; + green >>= 8; + blue >>= 8; + + value = (blue << 24) | (green << 16) | (red << 8); + ((u32 *)info->pseudo_palette)[regno] = value; + } + + return 0; +} + +static struct fb_ops s3d_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = s3d_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int s3d_set_fbinfo(struct s3d_info *sp) +{ + struct fb_info *info = sp->info; + struct fb_var_screeninfo *var = &info->var; + + info->flags = FBINFO_DEFAULT; + info->fbops = &s3d_ops; + info->screen_base = sp->fb_base; + info->screen_size = sp->fb_size; + + info->pseudo_palette = sp->pseudo_palette; + + /* Fill fix common fields */ + strlcpy(info->fix.id, "s3d", sizeof(info->fix.id)); + info->fix.smem_start = sp->fb_base_phys; + info->fix.smem_len = sp->fb_size; + info->fix.type = FB_TYPE_PACKED_PIXELS; + if (sp->depth == 32 || sp->depth == 24) + info->fix.visual = FB_VISUAL_TRUECOLOR; + else + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + var->xres = sp->width; + var->yres = sp->height; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + var->bits_per_pixel = sp->depth; + + var->red.offset = 8; + var->red.length = 8; + var->green.offset = 16; + var->green.length = 8; + var->blue.offset = 24; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + + if (fb_alloc_cmap(&info->cmap, 256, 0)) { + printk(KERN_ERR "s3d: Cannot allocate color map.\n"); + return -ENOMEM; + } + + return 0; +} + +static int s3d_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct fb_info *info; + struct s3d_info *sp; + int err; + + err = pci_enable_device(pdev); + if (err < 0) { + printk(KERN_ERR "s3d: Cannot enable PCI device %s\n", + pci_name(pdev)); + goto err_out; + } + + info = framebuffer_alloc(sizeof(struct s3d_info), &pdev->dev); + if (!info) { + printk(KERN_ERR "s3d: Cannot allocate fb_info\n"); + err = -ENOMEM; + goto err_disable; + } + + sp = info->par; + sp->info = info; + sp->pdev = pdev; + sp->of_node = pci_device_to_OF_node(pdev); + if (!sp->of_node) { + printk(KERN_ERR "s3d: Cannot find OF node of %s\n", + pci_name(pdev)); + err = -ENODEV; + goto err_release_fb; + } + + sp->fb_base_phys = pci_resource_start (pdev, 1); + + err = pci_request_region(pdev, 1, "s3d framebuffer"); + if (err < 0) { + printk("s3d: Cannot request region 1 for %s\n", + pci_name(pdev)); + goto err_release_fb; + } + + err = s3d_get_props(sp); + if (err) + goto err_release_pci; + + /* XXX 'linebytes' is often wrong, it is equal to the width + * XXX with depth of 32 on my XVR-2500 which is clearly not + * XXX right. So we don't try to use it. + */ + switch (sp->depth) { + case 8: + info->fix.line_length = sp->width; + break; + case 16: + info->fix.line_length = sp->width * 2; + break; + case 24: + info->fix.line_length = sp->width * 3; + break; + case 32: + info->fix.line_length = sp->width * 4; + break; + } + sp->fb_size = info->fix.line_length * sp->height; + + sp->fb_base = ioremap(sp->fb_base_phys, sp->fb_size); + if (!sp->fb_base) { + err = -ENOMEM; + goto err_release_pci; + } + + err = s3d_set_fbinfo(sp); + if (err) + goto err_unmap_fb; + + pci_set_drvdata(pdev, info); + + printk("s3d: Found device at %s\n", pci_name(pdev)); + + err = register_framebuffer(info); + if (err < 0) { + printk(KERN_ERR "s3d: Could not register framebuffer %s\n", + pci_name(pdev)); + goto err_unmap_fb; + } + + return 0; + +err_unmap_fb: + iounmap(sp->fb_base); + +err_release_pci: + pci_release_region(pdev, 1); + +err_release_fb: + framebuffer_release(info); + +err_disable: + pci_disable_device(pdev); + +err_out: + return err; +} + +static void s3d_pci_unregister(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct s3d_info *sp = info->par; + + unregister_framebuffer(info); + + iounmap(sp->fb_base); + + pci_release_region(pdev, 1); + + framebuffer_release(info); + + pci_disable_device(pdev); +} + +static struct pci_device_id s3d_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002c), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002d), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002e), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002f), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0030), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0031), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0032), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0033), }, + { 0, } +}; + +static struct pci_driver s3d_driver = { + .name = "s3d", + .id_table = s3d_pci_table, + .probe = s3d_pci_register, + .remove = s3d_pci_unregister, +}; + +static int __init s3d_init(void) +{ + if (fb_get_options("s3d", NULL)) + return -ENODEV; + + return pci_register_driver(&s3d_driver); +} + +static void __exit s3d_exit(void) +{ + pci_unregister_driver(&s3d_driver); +} + +module_init(s3d_init); +module_exit(s3d_exit); + +MODULE_DESCRIPTION("framebuffer driver for Sun XVR-2500 graphics"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/sunxvr500.c b/drivers/video/fbdev/sunxvr500.c new file mode 100644 index 000000000000..387350d004df --- /dev/null +++ b/drivers/video/fbdev/sunxvr500.c @@ -0,0 +1,462 @@ +/* sunxvr500.c: Sun 3DLABS XVR-500 Expert3D driver for sparc64 systems + * + * Copyright (C) 2007 David S. Miller (davem@davemloft.net) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/of_device.h> + +#include <asm/io.h> + +/* XXX This device has a 'dev-comm' property which apparently is + * XXX a pointer into the openfirmware's address space which is + * XXX a shared area the kernel driver can use to keep OBP + * XXX informed about the current resolution setting. The idea + * XXX is that the kernel can change resolutions, and as long + * XXX as the values in the 'dev-comm' area are accurate then + * XXX OBP can still render text properly to the console. + * XXX + * XXX I'm still working out the layout of this and whether there + * XXX are any signatures we need to look for etc. + */ +struct e3d_info { + struct fb_info *info; + struct pci_dev *pdev; + + spinlock_t lock; + + char __iomem *fb_base; + unsigned long fb_base_phys; + + unsigned long fb8_buf_diff; + unsigned long regs_base_phys; + + void __iomem *ramdac; + + struct device_node *of_node; + + unsigned int width; + unsigned int height; + unsigned int depth; + unsigned int fb_size; + + u32 fb_base_reg; + u32 fb8_0_off; + u32 fb8_1_off; + + u32 pseudo_palette[16]; +}; + +static int e3d_get_props(struct e3d_info *ep) +{ + ep->width = of_getintprop_default(ep->of_node, "width", 0); + ep->height = of_getintprop_default(ep->of_node, "height", 0); + ep->depth = of_getintprop_default(ep->of_node, "depth", 8); + + if (!ep->width || !ep->height) { + printk(KERN_ERR "e3d: Critical properties missing for %s\n", + pci_name(ep->pdev)); + return -EINVAL; + } + + return 0; +} + +/* My XVR-500 comes up, at 1280x768 and a FB base register value of + * 0x04000000, the following video layout register values: + * + * RAMDAC_VID_WH 0x03ff04ff + * RAMDAC_VID_CFG 0x1a0b0088 + * RAMDAC_VID_32FB_0 0x04000000 + * RAMDAC_VID_32FB_1 0x04800000 + * RAMDAC_VID_8FB_0 0x05000000 + * RAMDAC_VID_8FB_1 0x05200000 + * RAMDAC_VID_XXXFB 0x05400000 + * RAMDAC_VID_YYYFB 0x05c00000 + * RAMDAC_VID_ZZZFB 0x05e00000 + */ +/* Video layout registers */ +#define RAMDAC_VID_WH 0x00000070UL /* (height-1)<<16 | (width-1) */ +#define RAMDAC_VID_CFG 0x00000074UL /* 0x1a000088|(linesz_log2<<16) */ +#define RAMDAC_VID_32FB_0 0x00000078UL /* PCI base 32bpp FB buffer 0 */ +#define RAMDAC_VID_32FB_1 0x0000007cUL /* PCI base 32bpp FB buffer 1 */ +#define RAMDAC_VID_8FB_0 0x00000080UL /* PCI base 8bpp FB buffer 0 */ +#define RAMDAC_VID_8FB_1 0x00000084UL /* PCI base 8bpp FB buffer 1 */ +#define RAMDAC_VID_XXXFB 0x00000088UL /* PCI base of XXX FB */ +#define RAMDAC_VID_YYYFB 0x0000008cUL /* PCI base of YYY FB */ +#define RAMDAC_VID_ZZZFB 0x00000090UL /* PCI base of ZZZ FB */ + +/* CLUT registers */ +#define RAMDAC_INDEX 0x000000bcUL +#define RAMDAC_DATA 0x000000c0UL + +static void e3d_clut_write(struct e3d_info *ep, int index, u32 val) +{ + void __iomem *ramdac = ep->ramdac; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + writel(index, ramdac + RAMDAC_INDEX); + writel(val, ramdac + RAMDAC_DATA); + + spin_unlock_irqrestore(&ep->lock, flags); +} + +static int e3d_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct e3d_info *ep = info->par; + u32 red_8, green_8, blue_8; + u32 red_10, green_10, blue_10; + u32 value; + + if (regno >= 256) + return 1; + + red_8 = red >> 8; + green_8 = green >> 8; + blue_8 = blue >> 8; + + value = (blue_8 << 24) | (green_8 << 16) | (red_8 << 8); + + if (info->fix.visual == FB_VISUAL_TRUECOLOR && regno < 16) + ((u32 *)info->pseudo_palette)[regno] = value; + + + red_10 = red >> 6; + green_10 = green >> 6; + blue_10 = blue >> 6; + + value = (blue_10 << 20) | (green_10 << 10) | (red_10 << 0); + e3d_clut_write(ep, regno, value); + + return 0; +} + +/* XXX This is a bit of a hack. I can't figure out exactly how the + * XXX two 8bpp areas of the framebuffer work. I imagine there is + * XXX a WID attribute somewhere else in the framebuffer which tells + * XXX the ramdac which of the two 8bpp framebuffer regions to take + * XXX the pixel from. So, for now, render into both regions to make + * XXX sure the pixel shows up. + */ +static void e3d_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct e3d_info *ep = info->par; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + cfb_imageblit(info, image); + info->screen_base += ep->fb8_buf_diff; + cfb_imageblit(info, image); + info->screen_base -= ep->fb8_buf_diff; + spin_unlock_irqrestore(&ep->lock, flags); +} + +static void e3d_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct e3d_info *ep = info->par; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + cfb_fillrect(info, rect); + info->screen_base += ep->fb8_buf_diff; + cfb_fillrect(info, rect); + info->screen_base -= ep->fb8_buf_diff; + spin_unlock_irqrestore(&ep->lock, flags); +} + +static void e3d_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct e3d_info *ep = info->par; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + cfb_copyarea(info, area); + info->screen_base += ep->fb8_buf_diff; + cfb_copyarea(info, area); + info->screen_base -= ep->fb8_buf_diff; + spin_unlock_irqrestore(&ep->lock, flags); +} + +static struct fb_ops e3d_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = e3d_setcolreg, + .fb_fillrect = e3d_fillrect, + .fb_copyarea = e3d_copyarea, + .fb_imageblit = e3d_imageblit, +}; + +static int e3d_set_fbinfo(struct e3d_info *ep) +{ + struct fb_info *info = ep->info; + struct fb_var_screeninfo *var = &info->var; + + info->flags = FBINFO_DEFAULT; + info->fbops = &e3d_ops; + info->screen_base = ep->fb_base; + info->screen_size = ep->fb_size; + + info->pseudo_palette = ep->pseudo_palette; + + /* Fill fix common fields */ + strlcpy(info->fix.id, "e3d", sizeof(info->fix.id)); + info->fix.smem_start = ep->fb_base_phys; + info->fix.smem_len = ep->fb_size; + info->fix.type = FB_TYPE_PACKED_PIXELS; + if (ep->depth == 32 || ep->depth == 24) + info->fix.visual = FB_VISUAL_TRUECOLOR; + else + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + var->xres = ep->width; + var->yres = ep->height; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + var->bits_per_pixel = ep->depth; + + var->red.offset = 8; + var->red.length = 8; + var->green.offset = 16; + var->green.length = 8; + var->blue.offset = 24; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + + if (fb_alloc_cmap(&info->cmap, 256, 0)) { + printk(KERN_ERR "e3d: Cannot allocate color map.\n"); + return -ENOMEM; + } + + return 0; +} + +static int e3d_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct device_node *of_node; + const char *device_type; + struct fb_info *info; + struct e3d_info *ep; + unsigned int line_length; + int err; + + of_node = pci_device_to_OF_node(pdev); + if (!of_node) { + printk(KERN_ERR "e3d: Cannot find OF node of %s\n", + pci_name(pdev)); + return -ENODEV; + } + + device_type = of_get_property(of_node, "device_type", NULL); + if (!device_type) { + printk(KERN_INFO "e3d: Ignoring secondary output device " + "at %s\n", pci_name(pdev)); + return -ENODEV; + } + + err = pci_enable_device(pdev); + if (err < 0) { + printk(KERN_ERR "e3d: Cannot enable PCI device %s\n", + pci_name(pdev)); + goto err_out; + } + + info = framebuffer_alloc(sizeof(struct e3d_info), &pdev->dev); + if (!info) { + printk(KERN_ERR "e3d: Cannot allocate fb_info\n"); + err = -ENOMEM; + goto err_disable; + } + + ep = info->par; + ep->info = info; + ep->pdev = pdev; + spin_lock_init(&ep->lock); + ep->of_node = of_node; + + /* Read the PCI base register of the frame buffer, which we + * need in order to interpret the RAMDAC_VID_*FB* values in + * the ramdac correctly. + */ + pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, + &ep->fb_base_reg); + ep->fb_base_reg &= PCI_BASE_ADDRESS_MEM_MASK; + + ep->regs_base_phys = pci_resource_start (pdev, 1); + err = pci_request_region(pdev, 1, "e3d regs"); + if (err < 0) { + printk("e3d: Cannot request region 1 for %s\n", + pci_name(pdev)); + goto err_release_fb; + } + ep->ramdac = ioremap(ep->regs_base_phys + 0x8000, 0x1000); + if (!ep->ramdac) { + err = -ENOMEM; + goto err_release_pci1; + } + + ep->fb8_0_off = readl(ep->ramdac + RAMDAC_VID_8FB_0); + ep->fb8_0_off -= ep->fb_base_reg; + + ep->fb8_1_off = readl(ep->ramdac + RAMDAC_VID_8FB_1); + ep->fb8_1_off -= ep->fb_base_reg; + + ep->fb8_buf_diff = ep->fb8_1_off - ep->fb8_0_off; + + ep->fb_base_phys = pci_resource_start (pdev, 0); + ep->fb_base_phys += ep->fb8_0_off; + + err = pci_request_region(pdev, 0, "e3d framebuffer"); + if (err < 0) { + printk("e3d: Cannot request region 0 for %s\n", + pci_name(pdev)); + goto err_unmap_ramdac; + } + + err = e3d_get_props(ep); + if (err) + goto err_release_pci0; + + line_length = (readl(ep->ramdac + RAMDAC_VID_CFG) >> 16) & 0xff; + line_length = 1 << line_length; + + switch (ep->depth) { + case 8: + info->fix.line_length = line_length; + break; + case 16: + info->fix.line_length = line_length * 2; + break; + case 24: + info->fix.line_length = line_length * 3; + break; + case 32: + info->fix.line_length = line_length * 4; + break; + } + ep->fb_size = info->fix.line_length * ep->height; + + ep->fb_base = ioremap(ep->fb_base_phys, ep->fb_size); + if (!ep->fb_base) { + err = -ENOMEM; + goto err_release_pci0; + } + + err = e3d_set_fbinfo(ep); + if (err) + goto err_unmap_fb; + + pci_set_drvdata(pdev, info); + + printk("e3d: Found device at %s\n", pci_name(pdev)); + + err = register_framebuffer(info); + if (err < 0) { + printk(KERN_ERR "e3d: Could not register framebuffer %s\n", + pci_name(pdev)); + goto err_free_cmap; + } + + return 0; + +err_free_cmap: + fb_dealloc_cmap(&info->cmap); + +err_unmap_fb: + iounmap(ep->fb_base); + +err_release_pci0: + pci_release_region(pdev, 0); + +err_unmap_ramdac: + iounmap(ep->ramdac); + +err_release_pci1: + pci_release_region(pdev, 1); + +err_release_fb: + framebuffer_release(info); + +err_disable: + pci_disable_device(pdev); + +err_out: + return err; +} + +static void e3d_pci_unregister(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct e3d_info *ep = info->par; + + unregister_framebuffer(info); + + iounmap(ep->ramdac); + iounmap(ep->fb_base); + + pci_release_region(pdev, 0); + pci_release_region(pdev, 1); + + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + + pci_disable_device(pdev); +} + +static struct pci_device_id e3d_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x7a0), }, + { PCI_DEVICE(0x1091, 0x7a0), }, + { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x7a2), }, + { .vendor = PCI_VENDOR_ID_3DLABS, + .device = PCI_ANY_ID, + .subvendor = PCI_VENDOR_ID_3DLABS, + .subdevice = 0x0108, + }, + { .vendor = PCI_VENDOR_ID_3DLABS, + .device = PCI_ANY_ID, + .subvendor = PCI_VENDOR_ID_3DLABS, + .subdevice = 0x0140, + }, + { .vendor = PCI_VENDOR_ID_3DLABS, + .device = PCI_ANY_ID, + .subvendor = PCI_VENDOR_ID_3DLABS, + .subdevice = 0x1024, + }, + { 0, } +}; + +static struct pci_driver e3d_driver = { + .name = "e3d", + .id_table = e3d_pci_table, + .probe = e3d_pci_register, + .remove = e3d_pci_unregister, +}; + +static int __init e3d_init(void) +{ + if (fb_get_options("e3d", NULL)) + return -ENODEV; + + return pci_register_driver(&e3d_driver); +} + +static void __exit e3d_exit(void) +{ + pci_unregister_driver(&e3d_driver); +} + +module_init(e3d_init); +module_exit(e3d_exit); + +MODULE_DESCRIPTION("framebuffer driver for Sun XVR-500 graphics"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/svgalib.c b/drivers/video/fbdev/svgalib.c new file mode 100644 index 000000000000..9e01322fabe3 --- /dev/null +++ b/drivers/video/fbdev/svgalib.c @@ -0,0 +1,672 @@ +/* + * Common utility functions for VGA-based graphics cards. + * + * Copyright (c) 2006-2007 Ondrej Zajicek <santiago@crfreenet.org> + * + * 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. + * + * Some parts are based on David Boucher's viafb (http://davesdomain.org.uk/viafb/) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/svga.h> +#include <asm/types.h> +#include <asm/io.h> + + +/* Write a CRT register value spread across multiple registers */ +void svga_wcrt_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value) +{ + u8 regval, bitval, bitnum; + + while (regset->regnum != VGA_REGSET_END_VAL) { + regval = vga_rcrt(regbase, regset->regnum); + bitnum = regset->lowbit; + while (bitnum <= regset->highbit) { + bitval = 1 << bitnum; + regval = regval & ~bitval; + if (value & 1) regval = regval | bitval; + bitnum ++; + value = value >> 1; + } + vga_wcrt(regbase, regset->regnum, regval); + regset ++; + } +} + +/* Write a sequencer register value spread across multiple registers */ +void svga_wseq_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value) +{ + u8 regval, bitval, bitnum; + + while (regset->regnum != VGA_REGSET_END_VAL) { + regval = vga_rseq(regbase, regset->regnum); + bitnum = regset->lowbit; + while (bitnum <= regset->highbit) { + bitval = 1 << bitnum; + regval = regval & ~bitval; + if (value & 1) regval = regval | bitval; + bitnum ++; + value = value >> 1; + } + vga_wseq(regbase, regset->regnum, regval); + regset ++; + } +} + +static unsigned int svga_regset_size(const struct vga_regset *regset) +{ + u8 count = 0; + + while (regset->regnum != VGA_REGSET_END_VAL) { + count += regset->highbit - regset->lowbit + 1; + regset ++; + } + return 1 << count; +} + + +/* ------------------------------------------------------------------------- */ + + +/* Set graphics controller registers to sane values */ +void svga_set_default_gfx_regs(void __iomem *regbase) +{ + /* All standard GFX registers (GR00 - GR08) */ + vga_wgfx(regbase, VGA_GFX_SR_VALUE, 0x00); + vga_wgfx(regbase, VGA_GFX_SR_ENABLE, 0x00); + vga_wgfx(regbase, VGA_GFX_COMPARE_VALUE, 0x00); + vga_wgfx(regbase, VGA_GFX_DATA_ROTATE, 0x00); + vga_wgfx(regbase, VGA_GFX_PLANE_READ, 0x00); + vga_wgfx(regbase, VGA_GFX_MODE, 0x00); +/* vga_wgfx(regbase, VGA_GFX_MODE, 0x20); */ +/* vga_wgfx(regbase, VGA_GFX_MODE, 0x40); */ + vga_wgfx(regbase, VGA_GFX_MISC, 0x05); +/* vga_wgfx(regbase, VGA_GFX_MISC, 0x01); */ + vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x0F); + vga_wgfx(regbase, VGA_GFX_BIT_MASK, 0xFF); +} + +/* Set attribute controller registers to sane values */ +void svga_set_default_atc_regs(void __iomem *regbase) +{ + u8 count; + + vga_r(regbase, 0x3DA); + vga_w(regbase, VGA_ATT_W, 0x00); + + /* All standard ATC registers (AR00 - AR14) */ + for (count = 0; count <= 0xF; count ++) + svga_wattr(regbase, count, count); + + svga_wattr(regbase, VGA_ATC_MODE, 0x01); +/* svga_wattr(regbase, VGA_ATC_MODE, 0x41); */ + svga_wattr(regbase, VGA_ATC_OVERSCAN, 0x00); + svga_wattr(regbase, VGA_ATC_PLANE_ENABLE, 0x0F); + svga_wattr(regbase, VGA_ATC_PEL, 0x00); + svga_wattr(regbase, VGA_ATC_COLOR_PAGE, 0x00); + + vga_r(regbase, 0x3DA); + vga_w(regbase, VGA_ATT_W, 0x20); +} + +/* Set sequencer registers to sane values */ +void svga_set_default_seq_regs(void __iomem *regbase) +{ + /* Standard sequencer registers (SR01 - SR04), SR00 is not set */ + vga_wseq(regbase, VGA_SEQ_CLOCK_MODE, VGA_SR01_CHAR_CLK_8DOTS); + vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, VGA_SR02_ALL_PLANES); + vga_wseq(regbase, VGA_SEQ_CHARACTER_MAP, 0x00); +/* vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE | VGA_SR04_CHN_4M); */ + vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE); +} + +/* Set CRTC registers to sane values */ +void svga_set_default_crt_regs(void __iomem *regbase) +{ + /* Standard CRT registers CR03 CR08 CR09 CR14 CR17 */ + svga_wcrt_mask(regbase, 0x03, 0x80, 0x80); /* Enable vertical retrace EVRA */ + vga_wcrt(regbase, VGA_CRTC_PRESET_ROW, 0); + svga_wcrt_mask(regbase, VGA_CRTC_MAX_SCAN, 0, 0x1F); + vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0); + vga_wcrt(regbase, VGA_CRTC_MODE, 0xE3); +} + +void svga_set_textmode_vga_regs(void __iomem *regbase) +{ + /* svga_wseq_mask(regbase, 0x1, 0x00, 0x01); */ /* Switch 8/9 pixel per char */ + vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM); + vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, 0x03); + + vga_wcrt(regbase, VGA_CRTC_MAX_SCAN, 0x0f); /* 0x4f */ + vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0x1f); + svga_wcrt_mask(regbase, VGA_CRTC_MODE, 0x23, 0x7f); + + vga_wcrt(regbase, VGA_CRTC_CURSOR_START, 0x0d); + vga_wcrt(regbase, VGA_CRTC_CURSOR_END, 0x0e); + vga_wcrt(regbase, VGA_CRTC_CURSOR_HI, 0x00); + vga_wcrt(regbase, VGA_CRTC_CURSOR_LO, 0x00); + + vga_wgfx(regbase, VGA_GFX_MODE, 0x10); /* Odd/even memory mode */ + vga_wgfx(regbase, VGA_GFX_MISC, 0x0E); /* Misc graphics register - text mode enable */ + vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x00); + + vga_r(regbase, 0x3DA); + vga_w(regbase, VGA_ATT_W, 0x00); + + svga_wattr(regbase, 0x10, 0x0C); /* Attribute Mode Control Register - text mode, blinking and line graphics */ + svga_wattr(regbase, 0x13, 0x08); /* Horizontal Pixel Panning Register */ + + vga_r(regbase, 0x3DA); + vga_w(regbase, VGA_ATT_W, 0x20); +} + +#if 0 +void svga_dump_var(struct fb_var_screeninfo *var, int node) +{ + pr_debug("fb%d: var.vmode : 0x%X\n", node, var->vmode); + pr_debug("fb%d: var.xres : %d\n", node, var->xres); + pr_debug("fb%d: var.yres : %d\n", node, var->yres); + pr_debug("fb%d: var.bits_per_pixel: %d\n", node, var->bits_per_pixel); + pr_debug("fb%d: var.xres_virtual : %d\n", node, var->xres_virtual); + pr_debug("fb%d: var.yres_virtual : %d\n", node, var->yres_virtual); + pr_debug("fb%d: var.left_margin : %d\n", node, var->left_margin); + pr_debug("fb%d: var.right_margin : %d\n", node, var->right_margin); + pr_debug("fb%d: var.upper_margin : %d\n", node, var->upper_margin); + pr_debug("fb%d: var.lower_margin : %d\n", node, var->lower_margin); + pr_debug("fb%d: var.hsync_len : %d\n", node, var->hsync_len); + pr_debug("fb%d: var.vsync_len : %d\n", node, var->vsync_len); + pr_debug("fb%d: var.sync : 0x%X\n", node, var->sync); + pr_debug("fb%d: var.pixclock : %d\n\n", node, var->pixclock); +} +#endif /* 0 */ + + +/* ------------------------------------------------------------------------- */ + + +void svga_settile(struct fb_info *info, struct fb_tilemap *map) +{ + const u8 *font = map->data; + u8 __iomem *fb = (u8 __iomem *)info->screen_base; + int i, c; + + if ((map->width != 8) || (map->height != 16) || + (map->depth != 1) || (map->length != 256)) { + fb_err(info, "unsupported font parameters: width %d, height %d, depth %d, length %d\n", + map->width, map->height, map->depth, map->length); + return; + } + + fb += 2; + for (c = 0; c < map->length; c++) { + for (i = 0; i < map->height; i++) { + fb_writeb(font[i], fb + i * 4); +// fb[i * 4] = font[i]; + } + fb += 128; + font += map->height; + } +} + +/* Copy area in text (tileblit) mode */ +void svga_tilecopy(struct fb_info *info, struct fb_tilearea *area) +{ + int dx, dy; + /* colstride is halved in this function because u16 are used */ + int colstride = 1 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK); + int rowstride = colstride * (info->var.xres_virtual / 8); + u16 __iomem *fb = (u16 __iomem *) info->screen_base; + u16 __iomem *src, *dst; + + if ((area->sy > area->dy) || + ((area->sy == area->dy) && (area->sx > area->dx))) { + src = fb + area->sx * colstride + area->sy * rowstride; + dst = fb + area->dx * colstride + area->dy * rowstride; + } else { + src = fb + (area->sx + area->width - 1) * colstride + + (area->sy + area->height - 1) * rowstride; + dst = fb + (area->dx + area->width - 1) * colstride + + (area->dy + area->height - 1) * rowstride; + + colstride = -colstride; + rowstride = -rowstride; + } + + for (dy = 0; dy < area->height; dy++) { + u16 __iomem *src2 = src; + u16 __iomem *dst2 = dst; + for (dx = 0; dx < area->width; dx++) { + fb_writew(fb_readw(src2), dst2); +// *dst2 = *src2; + src2 += colstride; + dst2 += colstride; + } + src += rowstride; + dst += rowstride; + } +} + +/* Fill area in text (tileblit) mode */ +void svga_tilefill(struct fb_info *info, struct fb_tilerect *rect) +{ + int dx, dy; + int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK); + int rowstride = colstride * (info->var.xres_virtual / 8); + int attr = (0x0F & rect->bg) << 4 | (0x0F & rect->fg); + u8 __iomem *fb = (u8 __iomem *)info->screen_base; + fb += rect->sx * colstride + rect->sy * rowstride; + + for (dy = 0; dy < rect->height; dy++) { + u8 __iomem *fb2 = fb; + for (dx = 0; dx < rect->width; dx++) { + fb_writeb(rect->index, fb2); + fb_writeb(attr, fb2 + 1); + fb2 += colstride; + } + fb += rowstride; + } +} + +/* Write text in text (tileblit) mode */ +void svga_tileblit(struct fb_info *info, struct fb_tileblit *blit) +{ + int dx, dy, i; + int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK); + int rowstride = colstride * (info->var.xres_virtual / 8); + int attr = (0x0F & blit->bg) << 4 | (0x0F & blit->fg); + u8 __iomem *fb = (u8 __iomem *)info->screen_base; + fb += blit->sx * colstride + blit->sy * rowstride; + + i=0; + for (dy=0; dy < blit->height; dy ++) { + u8 __iomem *fb2 = fb; + for (dx = 0; dx < blit->width; dx ++) { + fb_writeb(blit->indices[i], fb2); + fb_writeb(attr, fb2 + 1); + fb2 += colstride; + i ++; + if (i == blit->length) return; + } + fb += rowstride; + } + +} + +/* Set cursor in text (tileblit) mode */ +void svga_tilecursor(void __iomem *regbase, struct fb_info *info, struct fb_tilecursor *cursor) +{ + u8 cs = 0x0d; + u8 ce = 0x0e; + u16 pos = cursor->sx + (info->var.xoffset / 8) + + (cursor->sy + (info->var.yoffset / 16)) + * (info->var.xres_virtual / 8); + + if (! cursor -> mode) + return; + + svga_wcrt_mask(regbase, 0x0A, 0x20, 0x20); /* disable cursor */ + + if (cursor -> shape == FB_TILE_CURSOR_NONE) + return; + + switch (cursor -> shape) { + case FB_TILE_CURSOR_UNDERLINE: + cs = 0x0d; + break; + case FB_TILE_CURSOR_LOWER_THIRD: + cs = 0x09; + break; + case FB_TILE_CURSOR_LOWER_HALF: + cs = 0x07; + break; + case FB_TILE_CURSOR_TWO_THIRDS: + cs = 0x05; + break; + case FB_TILE_CURSOR_BLOCK: + cs = 0x01; + break; + } + + /* set cursor position */ + vga_wcrt(regbase, 0x0E, pos >> 8); + vga_wcrt(regbase, 0x0F, pos & 0xFF); + + vga_wcrt(regbase, 0x0B, ce); /* set cursor end */ + vga_wcrt(regbase, 0x0A, cs); /* set cursor start and enable it */ +} + +int svga_get_tilemax(struct fb_info *info) +{ + return 256; +} + +/* Get capabilities of accelerator based on the mode */ + +void svga_get_caps(struct fb_info *info, struct fb_blit_caps *caps, + struct fb_var_screeninfo *var) +{ + if (var->bits_per_pixel == 0) { + /* can only support 256 8x16 bitmap */ + caps->x = 1 << (8 - 1); + caps->y = 1 << (16 - 1); + caps->len = 256; + } else { + caps->x = (var->bits_per_pixel == 4) ? 1 << (8 - 1) : ~(u32)0; + caps->y = ~(u32)0; + caps->len = ~(u32)0; + } +} +EXPORT_SYMBOL(svga_get_caps); + +/* ------------------------------------------------------------------------- */ + + +/* + * Compute PLL settings (M, N, R) + * F_VCO = (F_BASE * M) / N + * F_OUT = F_VCO / (2^R) + */ + +static inline u32 abs_diff(u32 a, u32 b) +{ + return (a > b) ? (a - b) : (b - a); +} + +int svga_compute_pll(const struct svga_pll *pll, u32 f_wanted, u16 *m, u16 *n, u16 *r, int node) +{ + u16 am, an, ar; + u32 f_vco, f_current, delta_current, delta_best; + + pr_debug("fb%d: ideal frequency: %d kHz\n", node, (unsigned int) f_wanted); + + ar = pll->r_max; + f_vco = f_wanted << ar; + + /* overflow check */ + if ((f_vco >> ar) != f_wanted) + return -EINVAL; + + /* It is usually better to have greater VCO clock + because of better frequency stability. + So first try r_max, then r smaller. */ + while ((ar > pll->r_min) && (f_vco > pll->f_vco_max)) { + ar--; + f_vco = f_vco >> 1; + } + + /* VCO bounds check */ + if ((f_vco < pll->f_vco_min) || (f_vco > pll->f_vco_max)) + return -EINVAL; + + delta_best = 0xFFFFFFFF; + *m = 0; + *n = 0; + *r = ar; + + am = pll->m_min; + an = pll->n_min; + + while ((am <= pll->m_max) && (an <= pll->n_max)) { + f_current = (pll->f_base * am) / an; + delta_current = abs_diff (f_current, f_vco); + + if (delta_current < delta_best) { + delta_best = delta_current; + *m = am; + *n = an; + } + + if (f_current <= f_vco) { + am ++; + } else { + an ++; + } + } + + f_current = (pll->f_base * *m) / *n; + pr_debug("fb%d: found frequency: %d kHz (VCO %d kHz)\n", node, (int) (f_current >> ar), (int) f_current); + pr_debug("fb%d: m = %d n = %d r = %d\n", node, (unsigned int) *m, (unsigned int) *n, (unsigned int) *r); + return 0; +} + + +/* ------------------------------------------------------------------------- */ + + +/* Check CRT timing values */ +int svga_check_timings(const struct svga_timing_regs *tm, struct fb_var_screeninfo *var, int node) +{ + u32 value; + + var->xres = (var->xres+7)&~7; + var->left_margin = (var->left_margin+7)&~7; + var->right_margin = (var->right_margin+7)&~7; + var->hsync_len = (var->hsync_len+7)&~7; + + /* Check horizontal total */ + value = var->xres + var->left_margin + var->right_margin + var->hsync_len; + if (((value / 8) - 5) >= svga_regset_size (tm->h_total_regs)) + return -EINVAL; + + /* Check horizontal display and blank start */ + value = var->xres; + if (((value / 8) - 1) >= svga_regset_size (tm->h_display_regs)) + return -EINVAL; + if (((value / 8) - 1) >= svga_regset_size (tm->h_blank_start_regs)) + return -EINVAL; + + /* Check horizontal sync start */ + value = var->xres + var->right_margin; + if (((value / 8) - 1) >= svga_regset_size (tm->h_sync_start_regs)) + return -EINVAL; + + /* Check horizontal blank end (or length) */ + value = var->left_margin + var->right_margin + var->hsync_len; + if ((value == 0) || ((value / 8) >= svga_regset_size (tm->h_blank_end_regs))) + return -EINVAL; + + /* Check horizontal sync end (or length) */ + value = var->hsync_len; + if ((value == 0) || ((value / 8) >= svga_regset_size (tm->h_sync_end_regs))) + return -EINVAL; + + /* Check vertical total */ + value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; + if ((value - 1) >= svga_regset_size(tm->v_total_regs)) + return -EINVAL; + + /* Check vertical display and blank start */ + value = var->yres; + if ((value - 1) >= svga_regset_size(tm->v_display_regs)) + return -EINVAL; + if ((value - 1) >= svga_regset_size(tm->v_blank_start_regs)) + return -EINVAL; + + /* Check vertical sync start */ + value = var->yres + var->lower_margin; + if ((value - 1) >= svga_regset_size(tm->v_sync_start_regs)) + return -EINVAL; + + /* Check vertical blank end (or length) */ + value = var->upper_margin + var->lower_margin + var->vsync_len; + if ((value == 0) || (value >= svga_regset_size (tm->v_blank_end_regs))) + return -EINVAL; + + /* Check vertical sync end (or length) */ + value = var->vsync_len; + if ((value == 0) || (value >= svga_regset_size (tm->v_sync_end_regs))) + return -EINVAL; + + return 0; +} + +/* Set CRT timing registers */ +void svga_set_timings(void __iomem *regbase, const struct svga_timing_regs *tm, + struct fb_var_screeninfo *var, + u32 hmul, u32 hdiv, u32 vmul, u32 vdiv, u32 hborder, int node) +{ + u8 regval; + u32 value; + + value = var->xres + var->left_margin + var->right_margin + var->hsync_len; + value = (value * hmul) / hdiv; + pr_debug("fb%d: horizontal total : %d\n", node, value); + svga_wcrt_multi(regbase, tm->h_total_regs, (value / 8) - 5); + + value = var->xres; + value = (value * hmul) / hdiv; + pr_debug("fb%d: horizontal display : %d\n", node, value); + svga_wcrt_multi(regbase, tm->h_display_regs, (value / 8) - 1); + + value = var->xres; + value = (value * hmul) / hdiv; + pr_debug("fb%d: horizontal blank start: %d\n", node, value); + svga_wcrt_multi(regbase, tm->h_blank_start_regs, (value / 8) - 1 + hborder); + + value = var->xres + var->left_margin + var->right_margin + var->hsync_len; + value = (value * hmul) / hdiv; + pr_debug("fb%d: horizontal blank end : %d\n", node, value); + svga_wcrt_multi(regbase, tm->h_blank_end_regs, (value / 8) - 1 - hborder); + + value = var->xres + var->right_margin; + value = (value * hmul) / hdiv; + pr_debug("fb%d: horizontal sync start : %d\n", node, value); + svga_wcrt_multi(regbase, tm->h_sync_start_regs, (value / 8)); + + value = var->xres + var->right_margin + var->hsync_len; + value = (value * hmul) / hdiv; + pr_debug("fb%d: horizontal sync end : %d\n", node, value); + svga_wcrt_multi(regbase, tm->h_sync_end_regs, (value / 8)); + + value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; + value = (value * vmul) / vdiv; + pr_debug("fb%d: vertical total : %d\n", node, value); + svga_wcrt_multi(regbase, tm->v_total_regs, value - 2); + + value = var->yres; + value = (value * vmul) / vdiv; + pr_debug("fb%d: vertical display : %d\n", node, value); + svga_wcrt_multi(regbase, tm->v_display_regs, value - 1); + + value = var->yres; + value = (value * vmul) / vdiv; + pr_debug("fb%d: vertical blank start : %d\n", node, value); + svga_wcrt_multi(regbase, tm->v_blank_start_regs, value); + + value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; + value = (value * vmul) / vdiv; + pr_debug("fb%d: vertical blank end : %d\n", node, value); + svga_wcrt_multi(regbase, tm->v_blank_end_regs, value - 2); + + value = var->yres + var->lower_margin; + value = (value * vmul) / vdiv; + pr_debug("fb%d: vertical sync start : %d\n", node, value); + svga_wcrt_multi(regbase, tm->v_sync_start_regs, value); + + value = var->yres + var->lower_margin + var->vsync_len; + value = (value * vmul) / vdiv; + pr_debug("fb%d: vertical sync end : %d\n", node, value); + svga_wcrt_multi(regbase, tm->v_sync_end_regs, value); + + /* Set horizontal and vertical sync pulse polarity in misc register */ + + regval = vga_r(regbase, VGA_MIS_R); + if (var->sync & FB_SYNC_HOR_HIGH_ACT) { + pr_debug("fb%d: positive horizontal sync\n", node); + regval = regval & ~0x80; + } else { + pr_debug("fb%d: negative horizontal sync\n", node); + regval = regval | 0x80; + } + if (var->sync & FB_SYNC_VERT_HIGH_ACT) { + pr_debug("fb%d: positive vertical sync\n", node); + regval = regval & ~0x40; + } else { + pr_debug("fb%d: negative vertical sync\n\n", node); + regval = regval | 0x40; + } + vga_w(regbase, VGA_MIS_W, regval); +} + + +/* ------------------------------------------------------------------------- */ + + +static inline int match_format(const struct svga_fb_format *frm, + struct fb_var_screeninfo *var) +{ + int i = 0; + int stored = -EINVAL; + + while (frm->bits_per_pixel != SVGA_FORMAT_END_VAL) + { + if ((var->bits_per_pixel == frm->bits_per_pixel) && + (var->red.length <= frm->red.length) && + (var->green.length <= frm->green.length) && + (var->blue.length <= frm->blue.length) && + (var->transp.length <= frm->transp.length) && + (var->nonstd == frm->nonstd)) + return i; + if (var->bits_per_pixel == frm->bits_per_pixel) + stored = i; + i++; + frm++; + } + return stored; +} + +int svga_match_format(const struct svga_fb_format *frm, + struct fb_var_screeninfo *var, + struct fb_fix_screeninfo *fix) +{ + int i = match_format(frm, var); + + if (i >= 0) { + var->bits_per_pixel = frm[i].bits_per_pixel; + var->red = frm[i].red; + var->green = frm[i].green; + var->blue = frm[i].blue; + var->transp = frm[i].transp; + var->nonstd = frm[i].nonstd; + if (fix != NULL) { + fix->type = frm[i].type; + fix->type_aux = frm[i].type_aux; + fix->visual = frm[i].visual; + fix->xpanstep = frm[i].xpanstep; + } + } + + return i; +} + + +EXPORT_SYMBOL(svga_wcrt_multi); +EXPORT_SYMBOL(svga_wseq_multi); + +EXPORT_SYMBOL(svga_set_default_gfx_regs); +EXPORT_SYMBOL(svga_set_default_atc_regs); +EXPORT_SYMBOL(svga_set_default_seq_regs); +EXPORT_SYMBOL(svga_set_default_crt_regs); +EXPORT_SYMBOL(svga_set_textmode_vga_regs); + +EXPORT_SYMBOL(svga_settile); +EXPORT_SYMBOL(svga_tilecopy); +EXPORT_SYMBOL(svga_tilefill); +EXPORT_SYMBOL(svga_tileblit); +EXPORT_SYMBOL(svga_tilecursor); +EXPORT_SYMBOL(svga_get_tilemax); + +EXPORT_SYMBOL(svga_compute_pll); +EXPORT_SYMBOL(svga_check_timings); +EXPORT_SYMBOL(svga_set_timings); +EXPORT_SYMBOL(svga_match_format); + +MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>"); +MODULE_DESCRIPTION("Common utility functions for VGA-based graphics cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/syscopyarea.c b/drivers/video/fbdev/syscopyarea.c new file mode 100644 index 000000000000..844a32fd38ed --- /dev/null +++ b/drivers/video/fbdev/syscopyarea.c @@ -0,0 +1,377 @@ +/* + * Generic Bit Block Transfer for frame buffers located in system RAM with + * packed pixels of any depth. + * + * Based almost entirely from cfbcopyarea.c (which is based almost entirely + * on Geert Uytterhoeven's copyarea routine) + * + * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net> + * + * 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/string.h> +#include <linux/fb.h> +#include <asm/types.h> +#include <asm/io.h> +#include "fb_draw.h" + + /* + * Generic bitwise copy algorithm + */ + +static void +bitcpy(struct fb_info *p, unsigned long *dst, int dst_idx, + const unsigned long *src, int src_idx, int bits, unsigned n) +{ + unsigned long first, last; + int const shift = dst_idx-src_idx; + int left, right; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (!shift) { + /* Same alignment for source and dest */ + if (dst_idx+n <= bits) { + /* Single word */ + if (last) + first &= last; + *dst = comp(*src, *dst, first); + } else { + /* Multiple destination words */ + /* Leading bits */ + if (first != ~0UL) { + *dst = comp(*src, *dst, first); + dst++; + src++; + n -= bits - dst_idx; + } + + /* Main chunk */ + n /= bits; + while (n >= 8) { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + n -= 8; + } + while (n--) + *dst++ = *src++; + + /* Trailing bits */ + if (last) + *dst = comp(*src, *dst, last); + } + } else { + unsigned long d0, d1; + int m; + + /* Different alignment for source and dest */ + right = shift & (bits - 1); + left = -shift & (bits - 1); + + if (dst_idx+n <= bits) { + /* Single destination word */ + if (last) + first &= last; + if (shift > 0) { + /* Single source word */ + *dst = comp(*src >> right, *dst, first); + } else if (src_idx+n <= bits) { + /* Single source word */ + *dst = comp(*src << left, *dst, first); + } else { + /* 2 source words */ + d0 = *src++; + d1 = *src; + *dst = comp(d0 << left | d1 >> right, *dst, + first); + } + } else { + /* Multiple destination words */ + /** We must always remember the last value read, + because in case SRC and DST overlap bitwise (e.g. + when moving just one pixel in 1bpp), we always + collect one full long for DST and that might + overlap with the current long from SRC. We store + this value in 'd0'. */ + d0 = *src++; + /* Leading bits */ + if (shift > 0) { + /* Single source word */ + *dst = comp(d0 >> right, *dst, first); + dst++; + n -= bits - dst_idx; + } else { + /* 2 source words */ + d1 = *src++; + *dst = comp(d0 << left | *dst >> right, *dst, first); + d0 = d1; + dst++; + n -= bits - dst_idx; + } + + /* Main chunk */ + m = n % bits; + n /= bits; + while (n >= 4) { + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + n -= 4; + } + while (n--) { + d1 = *src++; + *dst++ = d0 << left | d1 >> right; + d0 = d1; + } + + /* Trailing bits */ + if (last) { + if (m <= right) { + /* Single source word */ + *dst = comp(d0 << left, *dst, last); + } else { + /* 2 source words */ + d1 = *src; + *dst = comp(d0 << left | d1 >> right, + *dst, last); + } + } + } + } +} + + /* + * Generic bitwise copy algorithm, operating backward + */ + +static void +bitcpy_rev(struct fb_info *p, unsigned long *dst, int dst_idx, + const unsigned long *src, int src_idx, int bits, unsigned n) +{ + unsigned long first, last; + int shift; + + dst += (n-1)/bits; + src += (n-1)/bits; + if ((n-1) % bits) { + dst_idx += (n-1) % bits; + dst += dst_idx >> (ffs(bits) - 1); + dst_idx &= bits - 1; + src_idx += (n-1) % bits; + src += src_idx >> (ffs(bits) - 1); + src_idx &= bits - 1; + } + + shift = dst_idx-src_idx; + + first = FB_SHIFT_LOW(p, ~0UL, bits - 1 - dst_idx); + last = ~(FB_SHIFT_LOW(p, ~0UL, bits - 1 - ((dst_idx-n) % bits))); + + if (!shift) { + /* Same alignment for source and dest */ + if ((unsigned long)dst_idx+1 >= n) { + /* Single word */ + if (last) + first &= last; + *dst = comp(*src, *dst, first); + } else { + /* Multiple destination words */ + + /* Leading bits */ + if (first != ~0UL) { + *dst = comp(*src, *dst, first); + dst--; + src--; + n -= dst_idx+1; + } + + /* Main chunk */ + n /= bits; + while (n >= 8) { + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + *dst-- = *src--; + n -= 8; + } + while (n--) + *dst-- = *src--; + /* Trailing bits */ + if (last) + *dst = comp(*src, *dst, last); + } + } else { + /* Different alignment for source and dest */ + + int const left = -shift & (bits-1); + int const right = shift & (bits-1); + + if ((unsigned long)dst_idx+1 >= n) { + /* Single destination word */ + if (last) + first &= last; + if (shift < 0) { + /* Single source word */ + *dst = comp(*src << left, *dst, first); + } else if (1+(unsigned long)src_idx >= n) { + /* Single source word */ + *dst = comp(*src >> right, *dst, first); + } else { + /* 2 source words */ + *dst = comp(*src >> right | *(src-1) << left, + *dst, first); + } + } else { + /* Multiple destination words */ + /** We must always remember the last value read, + because in case SRC and DST overlap bitwise (e.g. + when moving just one pixel in 1bpp), we always + collect one full long for DST and that might + overlap with the current long from SRC. We store + this value in 'd0'. */ + unsigned long d0, d1; + int m; + + d0 = *src--; + /* Leading bits */ + if (shift < 0) { + /* Single source word */ + *dst = comp(d0 << left, *dst, first); + } else { + /* 2 source words */ + d1 = *src--; + *dst = comp(d0 >> right | d1 << left, *dst, + first); + d0 = d1; + } + dst--; + n -= dst_idx+1; + + /* Main chunk */ + m = n % bits; + n /= bits; + while (n >= 4) { + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + n -= 4; + } + while (n--) { + d1 = *src--; + *dst-- = d0 >> right | d1 << left; + d0 = d1; + } + + /* Trailing bits */ + if (last) { + if (m <= left) { + /* Single source word */ + *dst = comp(d0 >> right, *dst, last); + } else { + /* 2 source words */ + d1 = *src; + *dst = comp(d0 >> right | d1 << left, + *dst, last); + } + } + } + } +} + +void sys_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy; + u32 height = area->height, width = area->width; + unsigned long const bits_per_line = p->fix.line_length*8u; + unsigned long *dst = NULL, *src = NULL; + int bits = BITS_PER_LONG, bytes = bits >> 3; + int dst_idx = 0, src_idx = 0, rev_copy = 0; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + /* if the beginning of the target area might overlap with the end of + the source area, be have to copy the area reverse. */ + if ((dy == sy && dx > sx) || (dy > sy)) { + dy += height; + sy += height; + rev_copy = 1; + } + + /* split the base of the framebuffer into a long-aligned address and + the index of the first bit */ + dst = src = (unsigned long *)((unsigned long)p->screen_base & + ~(bytes-1)); + dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1)); + /* add offset of source and target area */ + dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel; + src_idx += sy*bits_per_line + sx*p->var.bits_per_pixel; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + if (rev_copy) { + while (height--) { + dst_idx -= bits_per_line; + src_idx -= bits_per_line; + dst += dst_idx >> (ffs(bits) - 1); + dst_idx &= (bytes - 1); + src += src_idx >> (ffs(bits) - 1); + src_idx &= (bytes - 1); + bitcpy_rev(p, dst, dst_idx, src, src_idx, bits, + width*p->var.bits_per_pixel); + } + } else { + while (height--) { + dst += dst_idx >> (ffs(bits) - 1); + dst_idx &= (bytes - 1); + src += src_idx >> (ffs(bits) - 1); + src_idx &= (bytes - 1); + bitcpy(p, dst, dst_idx, src, src_idx, bits, + width*p->var.bits_per_pixel); + dst_idx += bits_per_line; + src_idx += bits_per_line; + } + } +} + +EXPORT_SYMBOL(sys_copyarea); + +MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); +MODULE_DESCRIPTION("Generic copyarea (sys-to-sys)"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/sysfillrect.c b/drivers/video/fbdev/sysfillrect.c new file mode 100644 index 000000000000..33ee3d34f9d2 --- /dev/null +++ b/drivers/video/fbdev/sysfillrect.c @@ -0,0 +1,335 @@ +/* + * Generic fillrect for frame buffers in system RAM with packed pixels of + * any depth. + * + * Based almost entirely from cfbfillrect.c (which is based almost entirely + * on Geert Uytterhoeven's fillrect routine) + * + * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net> + * + * 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/string.h> +#include <linux/fb.h> +#include <asm/types.h> +#include "fb_draw.h" + + /* + * Aligned pattern fill using 32/64-bit memory accesses + */ + +static void +bitfill_aligned(struct fb_info *p, unsigned long *dst, int dst_idx, + unsigned long pat, unsigned n, int bits) +{ + unsigned long first, last; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + /* Single word */ + if (last) + first &= last; + *dst = comp(pat, *dst, first); + } else { + /* Multiple destination words */ + + /* Leading bits */ + if (first!= ~0UL) { + *dst = comp(pat, *dst, first); + dst++; + n -= bits - dst_idx; + } + + /* Main chunk */ + n /= bits; + while (n >= 8) { + *dst++ = pat; + *dst++ = pat; + *dst++ = pat; + *dst++ = pat; + *dst++ = pat; + *dst++ = pat; + *dst++ = pat; + *dst++ = pat; + n -= 8; + } + while (n--) + *dst++ = pat; + /* Trailing bits */ + if (last) + *dst = comp(pat, *dst, last); + } +} + + + /* + * Unaligned generic pattern fill using 32/64-bit memory accesses + * The pattern must have been expanded to a full 32/64-bit value + * Left/right are the appropriate shifts to convert to the pattern to be + * used for the next 32/64-bit word + */ + +static void +bitfill_unaligned(struct fb_info *p, unsigned long *dst, int dst_idx, + unsigned long pat, int left, int right, unsigned n, int bits) +{ + unsigned long first, last; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + /* Single word */ + if (last) + first &= last; + *dst = comp(pat, *dst, first); + } else { + /* Multiple destination words */ + /* Leading bits */ + if (first) { + *dst = comp(pat, *dst, first); + dst++; + pat = pat << left | pat >> right; + n -= bits - dst_idx; + } + + /* Main chunk */ + n /= bits; + while (n >= 4) { + *dst++ = pat; + pat = pat << left | pat >> right; + *dst++ = pat; + pat = pat << left | pat >> right; + *dst++ = pat; + pat = pat << left | pat >> right; + *dst++ = pat; + pat = pat << left | pat >> right; + n -= 4; + } + while (n--) { + *dst++ = pat; + pat = pat << left | pat >> right; + } + + /* Trailing bits */ + if (last) + *dst = comp(pat, *dst, last); + } +} + + /* + * Aligned pattern invert using 32/64-bit memory accesses + */ +static void +bitfill_aligned_rev(struct fb_info *p, unsigned long *dst, int dst_idx, + unsigned long pat, unsigned n, int bits) +{ + unsigned long val = pat; + unsigned long first, last; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + /* Single word */ + if (last) + first &= last; + *dst = comp(*dst ^ val, *dst, first); + } else { + /* Multiple destination words */ + /* Leading bits */ + if (first!=0UL) { + *dst = comp(*dst ^ val, *dst, first); + dst++; + n -= bits - dst_idx; + } + + /* Main chunk */ + n /= bits; + while (n >= 8) { + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + *dst++ ^= val; + n -= 8; + } + while (n--) + *dst++ ^= val; + /* Trailing bits */ + if (last) + *dst = comp(*dst ^ val, *dst, last); + } +} + + + /* + * Unaligned generic pattern invert using 32/64-bit memory accesses + * The pattern must have been expanded to a full 32/64-bit value + * Left/right are the appropriate shifts to convert to the pattern to be + * used for the next 32/64-bit word + */ + +static void +bitfill_unaligned_rev(struct fb_info *p, unsigned long *dst, int dst_idx, + unsigned long pat, int left, int right, unsigned n, + int bits) +{ + unsigned long first, last; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + /* Single word */ + if (last) + first &= last; + *dst = comp(*dst ^ pat, *dst, first); + } else { + /* Multiple destination words */ + + /* Leading bits */ + if (first != 0UL) { + *dst = comp(*dst ^ pat, *dst, first); + dst++; + pat = pat << left | pat >> right; + n -= bits - dst_idx; + } + + /* Main chunk */ + n /= bits; + while (n >= 4) { + *dst++ ^= pat; + pat = pat << left | pat >> right; + *dst++ ^= pat; + pat = pat << left | pat >> right; + *dst++ ^= pat; + pat = pat << left | pat >> right; + *dst++ ^= pat; + pat = pat << left | pat >> right; + n -= 4; + } + while (n--) { + *dst ^= pat; + pat = pat << left | pat >> right; + } + + /* Trailing bits */ + if (last) + *dst = comp(*dst ^ pat, *dst, last); + } +} + +void sys_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + unsigned long pat, pat2, fg; + unsigned long width = rect->width, height = rect->height; + int bits = BITS_PER_LONG, bytes = bits >> 3; + u32 bpp = p->var.bits_per_pixel; + unsigned long *dst; + int dst_idx, left; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR ) + fg = ((u32 *) (p->pseudo_palette))[rect->color]; + else + fg = rect->color; + + pat = pixel_to_pat( bpp, fg); + + dst = (unsigned long *)((unsigned long)p->screen_base & ~(bytes-1)); + dst_idx = ((unsigned long)p->screen_base & (bytes - 1))*8; + dst_idx += rect->dy*p->fix.line_length*8+rect->dx*bpp; + /* FIXME For now we support 1-32 bpp only */ + left = bits % bpp; + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + if (!left) { + void (*fill_op32)(struct fb_info *p, unsigned long *dst, + int dst_idx, unsigned long pat, unsigned n, + int bits) = NULL; + + switch (rect->rop) { + case ROP_XOR: + fill_op32 = bitfill_aligned_rev; + break; + case ROP_COPY: + fill_op32 = bitfill_aligned; + break; + default: + printk( KERN_ERR "cfb_fillrect(): unknown rop, " + "defaulting to ROP_COPY\n"); + fill_op32 = bitfill_aligned; + break; + } + while (height--) { + dst += dst_idx >> (ffs(bits) - 1); + dst_idx &= (bits - 1); + fill_op32(p, dst, dst_idx, pat, width*bpp, bits); + dst_idx += p->fix.line_length*8; + } + } else { + int right, r; + void (*fill_op)(struct fb_info *p, unsigned long *dst, + int dst_idx, unsigned long pat, int left, + int right, unsigned n, int bits) = NULL; +#ifdef __LITTLE_ENDIAN + right = left; + left = bpp - right; +#else + right = bpp - left; +#endif + switch (rect->rop) { + case ROP_XOR: + fill_op = bitfill_unaligned_rev; + break; + case ROP_COPY: + fill_op = bitfill_unaligned; + break; + default: + printk(KERN_ERR "sys_fillrect(): unknown rop, " + "defaulting to ROP_COPY\n"); + fill_op = bitfill_unaligned; + break; + } + while (height--) { + dst += dst_idx / bits; + dst_idx &= (bits - 1); + r = dst_idx % bpp; + /* rotate pattern to the correct start position */ + pat2 = le_long_to_cpu(rolx(cpu_to_le_long(pat), r, bpp)); + fill_op(p, dst, dst_idx, pat2, left, right, + width*bpp, bits); + dst_idx += p->fix.line_length*8; + } + } +} + +EXPORT_SYMBOL(sys_fillrect); + +MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); +MODULE_DESCRIPTION("Generic fill rectangle (sys-to-sys)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/sysimgblt.c b/drivers/video/fbdev/sysimgblt.c new file mode 100644 index 000000000000..a4d05b1b17d7 --- /dev/null +++ b/drivers/video/fbdev/sysimgblt.c @@ -0,0 +1,288 @@ +/* + * Generic 1-bit or 8-bit source to 1-32 bit destination expansion + * for frame buffer located in system RAM with packed pixels of any depth. + * + * Based almost entirely on cfbimgblt.c + * + * Copyright (C) April 2007 Antonino Daplas <adaplas@pol.net> + * + * 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/string.h> +#include <linux/fb.h> +#include <asm/types.h> + +#define DEBUG + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt,__func__,## args) +#else +#define DPRINTK(fmt, args...) +#endif + +static const u32 cfb_tab8_be[] = { + 0x00000000,0x000000ff,0x0000ff00,0x0000ffff, + 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff, + 0xff000000,0xff0000ff,0xff00ff00,0xff00ffff, + 0xffff0000,0xffff00ff,0xffffff00,0xffffffff +}; + +static const u32 cfb_tab8_le[] = { + 0x00000000,0xff000000,0x00ff0000,0xffff0000, + 0x0000ff00,0xff00ff00,0x00ffff00,0xffffff00, + 0x000000ff,0xff0000ff,0x00ff00ff,0xffff00ff, + 0x0000ffff,0xff00ffff,0x00ffffff,0xffffffff +}; + +static const u32 cfb_tab16_be[] = { + 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff +}; + +static const u32 cfb_tab16_le[] = { + 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff +}; + +static const u32 cfb_tab32[] = { + 0x00000000, 0xffffffff +}; + +static void color_imageblit(const struct fb_image *image, struct fb_info *p, + void *dst1, u32 start_index, u32 pitch_index) +{ + /* Draw the penguin */ + u32 *dst, *dst2; + u32 color = 0, val, shift; + int i, n, bpp = p->var.bits_per_pixel; + u32 null_bits = 32 - bpp; + u32 *palette = (u32 *) p->pseudo_palette; + const u8 *src = image->data; + + dst2 = dst1; + for (i = image->height; i--; ) { + n = image->width; + dst = dst1; + shift = 0; + val = 0; + + if (start_index) { + u32 start_mask = ~(FB_SHIFT_HIGH(p, ~(u32)0, + start_index)); + val = *dst & start_mask; + shift = start_index; + } + while (n--) { + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR ) + color = palette[*src]; + else + color = *src; + color <<= FB_LEFT_POS(p, bpp); + val |= FB_SHIFT_HIGH(p, color, shift); + if (shift >= null_bits) { + *dst++ = val; + + val = (shift == null_bits) ? 0 : + FB_SHIFT_LOW(p, color, 32 - shift); + } + shift += bpp; + shift &= (32 - 1); + src++; + } + if (shift) { + u32 end_mask = FB_SHIFT_HIGH(p, ~(u32)0, shift); + + *dst &= end_mask; + *dst |= val; + } + dst1 += p->fix.line_length; + if (pitch_index) { + dst2 += p->fix.line_length; + dst1 = (u8 *)((long)dst2 & ~(sizeof(u32) - 1)); + + start_index += pitch_index; + start_index &= 32 - 1; + } + } +} + +static void slow_imageblit(const struct fb_image *image, struct fb_info *p, + void *dst1, u32 fgcolor, u32 bgcolor, + u32 start_index, u32 pitch_index) +{ + u32 shift, color = 0, bpp = p->var.bits_per_pixel; + u32 *dst, *dst2; + u32 val, pitch = p->fix.line_length; + u32 null_bits = 32 - bpp; + u32 spitch = (image->width+7)/8; + const u8 *src = image->data, *s; + u32 i, j, l; + + dst2 = dst1; + fgcolor <<= FB_LEFT_POS(p, bpp); + bgcolor <<= FB_LEFT_POS(p, bpp); + + for (i = image->height; i--; ) { + shift = val = 0; + l = 8; + j = image->width; + dst = dst1; + s = src; + + /* write leading bits */ + if (start_index) { + u32 start_mask = ~(FB_SHIFT_HIGH(p, ~(u32)0, + start_index)); + val = *dst & start_mask; + shift = start_index; + } + + while (j--) { + l--; + color = (*s & (1 << l)) ? fgcolor : bgcolor; + val |= FB_SHIFT_HIGH(p, color, shift); + + /* Did the bitshift spill bits to the next long? */ + if (shift >= null_bits) { + *dst++ = val; + val = (shift == null_bits) ? 0 : + FB_SHIFT_LOW(p, color, 32 - shift); + } + shift += bpp; + shift &= (32 - 1); + if (!l) { l = 8; s++; } + } + + /* write trailing bits */ + if (shift) { + u32 end_mask = FB_SHIFT_HIGH(p, ~(u32)0, shift); + + *dst &= end_mask; + *dst |= val; + } + + dst1 += pitch; + src += spitch; + if (pitch_index) { + dst2 += pitch; + dst1 = (u8 *)((long)dst2 & ~(sizeof(u32) - 1)); + start_index += pitch_index; + start_index &= 32 - 1; + } + + } +} + +/* + * fast_imageblit - optimized monochrome color expansion + * + * Only if: bits_per_pixel == 8, 16, or 32 + * image->width is divisible by pixel/dword (ppw); + * fix->line_legth is divisible by 4; + * beginning and end of a scanline is dword aligned + */ +static void fast_imageblit(const struct fb_image *image, struct fb_info *p, + void *dst1, u32 fgcolor, u32 bgcolor) +{ + u32 fgx = fgcolor, bgx = bgcolor, bpp = p->var.bits_per_pixel; + u32 ppw = 32/bpp, spitch = (image->width + 7)/8; + u32 bit_mask, end_mask, eorx, shift; + const char *s = image->data, *src; + u32 *dst; + const u32 *tab = NULL; + int i, j, k; + + switch (bpp) { + case 8: + tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le; + break; + case 16: + tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le; + break; + case 32: + default: + tab = cfb_tab32; + break; + } + + for (i = ppw-1; i--; ) { + fgx <<= bpp; + bgx <<= bpp; + fgx |= fgcolor; + bgx |= bgcolor; + } + + bit_mask = (1 << ppw) - 1; + eorx = fgx ^ bgx; + k = image->width/ppw; + + for (i = image->height; i--; ) { + dst = dst1; + shift = 8; + src = s; + + for (j = k; j--; ) { + shift -= ppw; + end_mask = tab[(*src >> shift) & bit_mask]; + *dst++ = (end_mask & eorx) ^ bgx; + if (!shift) { + shift = 8; + src++; + } + } + dst1 += p->fix.line_length; + s += spitch; + } +} + +void sys_imageblit(struct fb_info *p, const struct fb_image *image) +{ + u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0; + u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel; + u32 width = image->width; + u32 dx = image->dx, dy = image->dy; + void *dst1; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + bitstart = (dy * p->fix.line_length * 8) + (dx * bpp); + start_index = bitstart & (32 - 1); + pitch_index = (p->fix.line_length & (bpl - 1)) * 8; + + bitstart /= 8; + bitstart &= ~(bpl - 1); + dst1 = (void __force *)p->screen_base + bitstart; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + if (image->depth == 1) { + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR) { + fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color]; + bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color]; + } else { + fgcolor = image->fg_color; + bgcolor = image->bg_color; + } + + if (32 % bpp == 0 && !start_index && !pitch_index && + ((width & (32/bpp-1)) == 0) && + bpp >= 8 && bpp <= 32) + fast_imageblit(image, p, dst1, fgcolor, bgcolor); + else + slow_imageblit(image, p, dst1, fgcolor, bgcolor, + start_index, pitch_index); + } else + color_imageblit(image, p, dst1, start_index, pitch_index); +} + +EXPORT_SYMBOL(sys_imageblit); + +MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); +MODULE_DESCRIPTION("1-bit/8-bit to 1-32 bit color expansion (sys-to-sys)"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/tcx.c b/drivers/video/fbdev/tcx.c new file mode 100644 index 000000000000..7fb2d696fac7 --- /dev/null +++ b/drivers/video/fbdev/tcx.c @@ -0,0 +1,541 @@ +/* tcx.c: TCX frame buffer driver + * + * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * + * Driver layout based loosely on tgafb.c, see that file for credits. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/mm.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/fbio.h> + +#include "sbuslib.h" + +/* + * Local functions. + */ + +static int tcx_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); +static int tcx_blank(int, struct fb_info *); + +static int tcx_mmap(struct fb_info *, struct vm_area_struct *); +static int tcx_ioctl(struct fb_info *, unsigned int, unsigned long); +static int tcx_pan_display(struct fb_var_screeninfo *, struct fb_info *); + +/* + * Frame buffer operations + */ + +static struct fb_ops tcx_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = tcx_setcolreg, + .fb_blank = tcx_blank, + .fb_pan_display = tcx_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = tcx_mmap, + .fb_ioctl = tcx_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = sbusfb_compat_ioctl, +#endif +}; + +/* THC definitions */ +#define TCX_THC_MISC_REV_SHIFT 16 +#define TCX_THC_MISC_REV_MASK 15 +#define TCX_THC_MISC_VSYNC_DIS (1 << 25) +#define TCX_THC_MISC_HSYNC_DIS (1 << 24) +#define TCX_THC_MISC_RESET (1 << 12) +#define TCX_THC_MISC_VIDEO (1 << 10) +#define TCX_THC_MISC_SYNC (1 << 9) +#define TCX_THC_MISC_VSYNC (1 << 8) +#define TCX_THC_MISC_SYNC_ENAB (1 << 7) +#define TCX_THC_MISC_CURS_RES (1 << 6) +#define TCX_THC_MISC_INT_ENAB (1 << 5) +#define TCX_THC_MISC_INT (1 << 4) +#define TCX_THC_MISC_INIT 0x9f +#define TCX_THC_REV_REV_SHIFT 20 +#define TCX_THC_REV_REV_MASK 15 +#define TCX_THC_REV_MINREV_SHIFT 28 +#define TCX_THC_REV_MINREV_MASK 15 + +/* The contents are unknown */ +struct tcx_tec { + u32 tec_matrix; + u32 tec_clip; + u32 tec_vdc; +}; + +struct tcx_thc { + u32 thc_rev; + u32 thc_pad0[511]; + u32 thc_hs; /* hsync timing */ + u32 thc_hsdvs; + u32 thc_hd; + u32 thc_vs; /* vsync timing */ + u32 thc_vd; + u32 thc_refresh; + u32 thc_misc; + u32 thc_pad1[56]; + u32 thc_cursxy; /* cursor x,y position (16 bits each) */ + u32 thc_cursmask[32]; /* cursor mask bits */ + u32 thc_cursbits[32]; /* what to show where mask enabled */ +}; + +struct bt_regs { + u32 addr; + u32 color_map; + u32 control; + u32 cursor; +}; + +#define TCX_MMAP_ENTRIES 14 + +struct tcx_par { + spinlock_t lock; + struct bt_regs __iomem *bt; + struct tcx_thc __iomem *thc; + struct tcx_tec __iomem *tec; + u32 __iomem *cplane; + + u32 flags; +#define TCX_FLAG_BLANKED 0x00000001 + + unsigned long which_io; + + struct sbus_mmap_map mmap_map[TCX_MMAP_ENTRIES]; + int lowdepth; +}; + +/* Reset control plane so that WID is 8-bit plane. */ +static void __tcx_set_control_plane(struct fb_info *info) +{ + struct tcx_par *par = info->par; + u32 __iomem *p, *pend; + + if (par->lowdepth) + return; + + p = par->cplane; + if (p == NULL) + return; + for (pend = p + info->fix.smem_len; p < pend; p++) { + u32 tmp = sbus_readl(p); + + tmp &= 0xffffff; + sbus_writel(tmp, p); + } +} + +static void tcx_reset(struct fb_info *info) +{ + struct tcx_par *par = (struct tcx_par *) info->par; + unsigned long flags; + + spin_lock_irqsave(&par->lock, flags); + __tcx_set_control_plane(info); + spin_unlock_irqrestore(&par->lock, flags); +} + +static int tcx_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + tcx_reset(info); + return 0; +} + +/** + * tcx_setcolreg - Optional function. Sets a color register. + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int tcx_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct tcx_par *par = (struct tcx_par *) info->par; + struct bt_regs __iomem *bt = par->bt; + unsigned long flags; + + if (regno >= 256) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + spin_lock_irqsave(&par->lock, flags); + + sbus_writel(regno << 24, &bt->addr); + sbus_writel(red << 24, &bt->color_map); + sbus_writel(green << 24, &bt->color_map); + sbus_writel(blue << 24, &bt->color_map); + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +/** + * tcx_blank - Optional function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int +tcx_blank(int blank, struct fb_info *info) +{ + struct tcx_par *par = (struct tcx_par *) info->par; + struct tcx_thc __iomem *thc = par->thc; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&par->lock, flags); + + val = sbus_readl(&thc->thc_misc); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + val &= ~(TCX_THC_MISC_VSYNC_DIS | + TCX_THC_MISC_HSYNC_DIS); + val |= TCX_THC_MISC_VIDEO; + par->flags &= ~TCX_FLAG_BLANKED; + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + val &= ~TCX_THC_MISC_VIDEO; + par->flags |= TCX_FLAG_BLANKED; + break; + + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + val |= TCX_THC_MISC_VSYNC_DIS; + break; + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + val |= TCX_THC_MISC_HSYNC_DIS; + break; + + case FB_BLANK_POWERDOWN: /* Poweroff */ + break; + } + + sbus_writel(val, &thc->thc_misc); + + spin_unlock_irqrestore(&par->lock, flags); + + return 0; +} + +static struct sbus_mmap_map __tcx_mmap_map[TCX_MMAP_ENTRIES] = { + { + .voff = TCX_RAM8BIT, + .size = SBUS_MMAP_FBSIZE(1) + }, + { + .voff = TCX_RAM24BIT, + .size = SBUS_MMAP_FBSIZE(4) + }, + { + .voff = TCX_UNK3, + .size = SBUS_MMAP_FBSIZE(8) + }, + { + .voff = TCX_UNK4, + .size = SBUS_MMAP_FBSIZE(8) + }, + { + .voff = TCX_CONTROLPLANE, + .size = SBUS_MMAP_FBSIZE(4) + }, + { + .voff = TCX_UNK6, + .size = SBUS_MMAP_FBSIZE(8) + }, + { + .voff = TCX_UNK7, + .size = SBUS_MMAP_FBSIZE(8) + }, + { + .voff = TCX_TEC, + .size = PAGE_SIZE + }, + { + .voff = TCX_BTREGS, + .size = PAGE_SIZE + }, + { + .voff = TCX_THC, + .size = PAGE_SIZE + }, + { + .voff = TCX_DHC, + .size = PAGE_SIZE + }, + { + .voff = TCX_ALT, + .size = PAGE_SIZE + }, + { + .voff = TCX_UNK2, + .size = 0x20000 + }, + { .size = 0 } +}; + +static int tcx_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct tcx_par *par = (struct tcx_par *)info->par; + + return sbusfb_mmap_helper(par->mmap_map, + info->fix.smem_start, info->fix.smem_len, + par->which_io, vma); +} + +static int tcx_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct tcx_par *par = (struct tcx_par *) info->par; + + return sbusfb_ioctl_helper(cmd, arg, info, + FBTYPE_TCXCOLOR, + (par->lowdepth ? 8 : 24), + info->fix.smem_len); +} + +/* + * Initialisation + */ + +static void +tcx_init_fix(struct fb_info *info, int linebytes) +{ + struct tcx_par *par = (struct tcx_par *)info->par; + const char *tcx_name; + + if (par->lowdepth) + tcx_name = "TCX8"; + else + tcx_name = "TCX24"; + + strlcpy(info->fix.id, tcx_name, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + + info->fix.line_length = linebytes; + + info->fix.accel = FB_ACCEL_SUN_TCX; +} + +static void tcx_unmap_regs(struct platform_device *op, struct fb_info *info, + struct tcx_par *par) +{ + if (par->tec) + of_iounmap(&op->resource[7], + par->tec, sizeof(struct tcx_tec)); + if (par->thc) + of_iounmap(&op->resource[9], + par->thc, sizeof(struct tcx_thc)); + if (par->bt) + of_iounmap(&op->resource[8], + par->bt, sizeof(struct bt_regs)); + if (par->cplane) + of_iounmap(&op->resource[4], + par->cplane, info->fix.smem_len * sizeof(u32)); + if (info->screen_base) + of_iounmap(&op->resource[0], + info->screen_base, info->fix.smem_len); +} + +static int tcx_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct fb_info *info; + struct tcx_par *par; + int linebytes, i, err; + + info = framebuffer_alloc(sizeof(struct tcx_par), &op->dev); + + err = -ENOMEM; + if (!info) + goto out_err; + par = info->par; + + spin_lock_init(&par->lock); + + par->lowdepth = + (of_find_property(dp, "tcx-8-bit", NULL) != NULL); + + sbusfb_fill_var(&info->var, dp, 8); + info->var.red.length = 8; + info->var.green.length = 8; + info->var.blue.length = 8; + + linebytes = of_getintprop_default(dp, "linebytes", + info->var.xres); + info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); + + par->tec = of_ioremap(&op->resource[7], 0, + sizeof(struct tcx_tec), "tcx tec"); + par->thc = of_ioremap(&op->resource[9], 0, + sizeof(struct tcx_thc), "tcx thc"); + par->bt = of_ioremap(&op->resource[8], 0, + sizeof(struct bt_regs), "tcx dac"); + info->screen_base = of_ioremap(&op->resource[0], 0, + info->fix.smem_len, "tcx ram"); + if (!par->tec || !par->thc || + !par->bt || !info->screen_base) + goto out_unmap_regs; + + memcpy(&par->mmap_map, &__tcx_mmap_map, sizeof(par->mmap_map)); + if (!par->lowdepth) { + par->cplane = of_ioremap(&op->resource[4], 0, + info->fix.smem_len * sizeof(u32), + "tcx cplane"); + if (!par->cplane) + goto out_unmap_regs; + } else { + par->mmap_map[1].size = SBUS_MMAP_EMPTY; + par->mmap_map[4].size = SBUS_MMAP_EMPTY; + par->mmap_map[5].size = SBUS_MMAP_EMPTY; + par->mmap_map[6].size = SBUS_MMAP_EMPTY; + } + + info->fix.smem_start = op->resource[0].start; + par->which_io = op->resource[0].flags & IORESOURCE_BITS; + + for (i = 0; i < TCX_MMAP_ENTRIES; i++) { + int j; + + switch (i) { + case 10: + j = 12; + break; + + case 11: case 12: + j = i - 1; + break; + + default: + j = i; + break; + } + par->mmap_map[i].poff = op->resource[j].start; + } + + info->flags = FBINFO_DEFAULT; + info->fbops = &tcx_ops; + + /* Initialize brooktree DAC. */ + sbus_writel(0x04 << 24, &par->bt->addr); /* color planes */ + sbus_writel(0xff << 24, &par->bt->control); + sbus_writel(0x05 << 24, &par->bt->addr); + sbus_writel(0x00 << 24, &par->bt->control); + sbus_writel(0x06 << 24, &par->bt->addr); /* overlay plane */ + sbus_writel(0x73 << 24, &par->bt->control); + sbus_writel(0x07 << 24, &par->bt->addr); + sbus_writel(0x00 << 24, &par->bt->control); + + tcx_reset(info); + + tcx_blank(FB_BLANK_UNBLANK, info); + + if (fb_alloc_cmap(&info->cmap, 256, 0)) + goto out_unmap_regs; + + fb_set_cmap(&info->cmap, info); + tcx_init_fix(info, linebytes); + + err = register_framebuffer(info); + if (err < 0) + goto out_dealloc_cmap; + + dev_set_drvdata(&op->dev, info); + + printk(KERN_INFO "%s: TCX at %lx:%lx, %s\n", + dp->full_name, + par->which_io, + info->fix.smem_start, + par->lowdepth ? "8-bit only" : "24-bit depth"); + + return 0; + +out_dealloc_cmap: + fb_dealloc_cmap(&info->cmap); + +out_unmap_regs: + tcx_unmap_regs(op, info, par); + framebuffer_release(info); + +out_err: + return err; +} + +static int tcx_remove(struct platform_device *op) +{ + struct fb_info *info = dev_get_drvdata(&op->dev); + struct tcx_par *par = info->par; + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + tcx_unmap_regs(op, info, par); + + framebuffer_release(info); + + return 0; +} + +static const struct of_device_id tcx_match[] = { + { + .name = "SUNW,tcx", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, tcx_match); + +static struct platform_driver tcx_driver = { + .driver = { + .name = "tcx", + .owner = THIS_MODULE, + .of_match_table = tcx_match, + }, + .probe = tcx_probe, + .remove = tcx_remove, +}; + +static int __init tcx_init(void) +{ + if (fb_get_options("tcxfb", NULL)) + return -ENODEV; + + return platform_driver_register(&tcx_driver); +} + +static void __exit tcx_exit(void) +{ + platform_driver_unregister(&tcx_driver); +} + +module_init(tcx_init); +module_exit(tcx_exit); + +MODULE_DESCRIPTION("framebuffer driver for TCX chipsets"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c new file mode 100644 index 000000000000..f761fe375f5b --- /dev/null +++ b/drivers/video/fbdev/tdfxfb.c @@ -0,0 +1,1686 @@ +/* + * + * tdfxfb.c + * + * Author: Hannu Mallat <hmallat@cc.hut.fi> + * + * Copyright © 1999 Hannu Mallat + * All rights reserved + * + * Created : Thu Sep 23 18:17:43 1999, hmallat + * Last modified: Tue Nov 2 21:19:47 1999, hmallat + * + * I2C part copied from the i2c-voodoo3.c driver by: + * Frodo Looijaard <frodol@dds.nl>, + * Philip Edelbrock <phil@netroedge.com>, + * Ralph Metzler <rjkm@thp.uni-koeln.de>, and + * Mark D. Studebaker <mdsxyz123@yahoo.com> + * + * Lots of the information here comes from the Daryll Strauss' Banshee + * patches to the XF86 server, and the rest comes from the 3dfx + * Banshee specification. I'm very much indebted to Daryll for his + * work on the X server. + * + * Voodoo3 support was contributed Harold Oga. Lots of additions + * (proper acceleration, 24 bpp, hardware cursor) and bug fixes by Attila + * Kesmarki. Thanks guys! + * + * Voodoo1 and Voodoo2 support aren't relevant to this driver as they + * behave very differently from the Voodoo3/4/5. For anyone wanting to + * use frame buffer on the Voodoo1/2, see the sstfb driver (which is + * located at http://www.sourceforge.net/projects/sstfb). + * + * While I _am_ grateful to 3Dfx for releasing the specs for Banshee, + * I do wish the next version is a bit more complete. Without the XF86 + * patches I couldn't have gotten even this far... for instance, the + * extensions to the VGA register set go completely unmentioned in the + * spec! Also, lots of references are made to the 'SST core', but no + * spec is publicly available, AFAIK. + * + * The structure of this driver comes pretty much from the Permedia + * driver by Ilario Nardinocchi, which in turn is based on skeletonfb. + * + * TODO: + * - multihead support (basically need to support an array of fb_infos) + * - support other architectures (PPC, Alpha); does the fact that the VGA + * core can be accessed only thru I/O (not memory mapped) complicate + * things? + * + * Version history: + * + * 0.1.4 (released 2002-05-28) ported over to new fbdev api by James Simmons + * + * 0.1.3 (released 1999-11-02) added Attila's panning support, code + * reorg, hwcursor address page size alignment + * (for mmapping both frame buffer and regs), + * and my changes to get rid of hardcoded + * VGA i/o register locations (uses PCI + * configuration info now) + * 0.1.2 (released 1999-10-19) added Attila Kesmarki's bug fixes and + * improvements + * 0.1.1 (released 1999-10-07) added Voodoo3 support by Harold Oga. + * 0.1.0 (released 1999-10-06) initial version + * + */ + +#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/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <asm/io.h> + +#include <video/tdfx.h> + +#define DPRINTK(a, b...) pr_debug("fb: %s: " a, __func__ , ## b) + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#else +/* duplicate asm/mtrr.h defines to work on archs without mtrr */ +#define MTRR_TYPE_WRCOMB 1 + +static inline int mtrr_add(unsigned long base, unsigned long size, + unsigned int type, char increment) +{ + return -ENODEV; +} +static inline int mtrr_del(int reg, unsigned long base, + unsigned long size) +{ + return -ENODEV; +} +#endif + +#define BANSHEE_MAX_PIXCLOCK 270000 +#define VOODOO3_MAX_PIXCLOCK 300000 +#define VOODOO5_MAX_PIXCLOCK 350000 + +static struct fb_fix_screeninfo tdfx_fix = { + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .ypanstep = 1, + .ywrapstep = 1, + .accel = FB_ACCEL_3DFX_BANSHEE +}; + +static struct fb_var_screeninfo tdfx_var = { + /* "640x480, 8 bpp @ 60 Hz */ + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 1024, + .bits_per_pixel = 8, + .red = {0, 8, 0}, + .blue = {0, 8, 0}, + .green = {0, 8, 0}, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .accel_flags = FB_ACCELF_TEXT, + .pixclock = 39722, + .left_margin = 40, + .right_margin = 24, + .upper_margin = 32, + .lower_margin = 11, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED +}; + +/* + * PCI driver prototypes + */ +static int tdfxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id); +static void tdfxfb_remove(struct pci_dev *pdev); + +static struct pci_device_id tdfxfb_id_table[] = { + { PCI_VENDOR_ID_3DFX, PCI_DEVICE_ID_3DFX_BANSHEE, + PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, + 0xff0000, 0 }, + { PCI_VENDOR_ID_3DFX, PCI_DEVICE_ID_3DFX_VOODOO3, + PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, + 0xff0000, 0 }, + { PCI_VENDOR_ID_3DFX, PCI_DEVICE_ID_3DFX_VOODOO5, + PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, + 0xff0000, 0 }, + { 0, } +}; + +static struct pci_driver tdfxfb_driver = { + .name = "tdfxfb", + .id_table = tdfxfb_id_table, + .probe = tdfxfb_probe, + .remove = tdfxfb_remove, +}; + +MODULE_DEVICE_TABLE(pci, tdfxfb_id_table); + +/* + * Driver data + */ +static int nopan; +static int nowrap = 1; /* not implemented (yet) */ +static int hwcursor = 1; +static char *mode_option; +/* mtrr option */ +static bool nomtrr; + +/* ------------------------------------------------------------------------- + * Hardware-specific funcions + * ------------------------------------------------------------------------- */ + +static inline u8 vga_inb(struct tdfx_par *par, u32 reg) +{ + return inb(par->iobase + reg - 0x300); +} + +static inline void vga_outb(struct tdfx_par *par, u32 reg, u8 val) +{ + outb(val, par->iobase + reg - 0x300); +} + +static inline void gra_outb(struct tdfx_par *par, u32 idx, u8 val) +{ + vga_outb(par, GRA_I, idx); + wmb(); + vga_outb(par, GRA_D, val); + wmb(); +} + +static inline void seq_outb(struct tdfx_par *par, u32 idx, u8 val) +{ + vga_outb(par, SEQ_I, idx); + wmb(); + vga_outb(par, SEQ_D, val); + wmb(); +} + +static inline u8 seq_inb(struct tdfx_par *par, u32 idx) +{ + vga_outb(par, SEQ_I, idx); + mb(); + return vga_inb(par, SEQ_D); +} + +static inline void crt_outb(struct tdfx_par *par, u32 idx, u8 val) +{ + vga_outb(par, CRT_I, idx); + wmb(); + vga_outb(par, CRT_D, val); + wmb(); +} + +static inline u8 crt_inb(struct tdfx_par *par, u32 idx) +{ + vga_outb(par, CRT_I, idx); + mb(); + return vga_inb(par, CRT_D); +} + +static inline void att_outb(struct tdfx_par *par, u32 idx, u8 val) +{ + unsigned char tmp; + + tmp = vga_inb(par, IS1_R); + vga_outb(par, ATT_IW, idx); + vga_outb(par, ATT_IW, val); +} + +static inline void vga_disable_video(struct tdfx_par *par) +{ + unsigned char s; + + s = seq_inb(par, 0x01) | 0x20; + seq_outb(par, 0x00, 0x01); + seq_outb(par, 0x01, s); + seq_outb(par, 0x00, 0x03); +} + +static inline void vga_enable_video(struct tdfx_par *par) +{ + unsigned char s; + + s = seq_inb(par, 0x01) & 0xdf; + seq_outb(par, 0x00, 0x01); + seq_outb(par, 0x01, s); + seq_outb(par, 0x00, 0x03); +} + +static inline void vga_enable_palette(struct tdfx_par *par) +{ + vga_inb(par, IS1_R); + mb(); + vga_outb(par, ATT_IW, 0x20); +} + +static inline u32 tdfx_inl(struct tdfx_par *par, unsigned int reg) +{ + return readl(par->regbase_virt + reg); +} + +static inline void tdfx_outl(struct tdfx_par *par, unsigned int reg, u32 val) +{ + writel(val, par->regbase_virt + reg); +} + +static inline void banshee_make_room(struct tdfx_par *par, int size) +{ + /* Note: The Voodoo3's onboard FIFO has 32 slots. This loop + * won't quit if you ask for more. */ + while ((tdfx_inl(par, STATUS) & 0x1f) < size - 1) + cpu_relax(); +} + +static int banshee_wait_idle(struct fb_info *info) +{ + struct tdfx_par *par = info->par; + int i = 0; + + banshee_make_room(par, 1); + tdfx_outl(par, COMMAND_3D, COMMAND_3D_NOP); + + do { + if ((tdfx_inl(par, STATUS) & STATUS_BUSY) == 0) + i++; + } while (i < 3); + + return 0; +} + +/* + * Set the color of a palette entry in 8bpp mode + */ +static inline void do_setpalentry(struct tdfx_par *par, unsigned regno, u32 c) +{ + banshee_make_room(par, 2); + tdfx_outl(par, DACADDR, regno); + /* read after write makes it working */ + tdfx_inl(par, DACADDR); + tdfx_outl(par, DACDATA, c); +} + +static u32 do_calc_pll(int freq, int *freq_out) +{ + int m, n, k, best_m, best_n, best_k, best_error; + int fref = 14318; + + best_error = freq; + best_n = best_m = best_k = 0; + + for (k = 3; k >= 0; k--) { + for (m = 63; m >= 0; m--) { + /* + * Estimate value of n that produces target frequency + * with current m and k + */ + int n_estimated = ((freq * (m + 2) << k) / fref) - 2; + + /* Search neighborhood of estimated n */ + for (n = max(0, n_estimated); + n <= min(255, n_estimated + 1); + n++) { + /* + * Calculate PLL freqency with current m, k and + * estimated n + */ + int f = (fref * (n + 2) / (m + 2)) >> k; + int error = abs(f - freq); + + /* + * If this is the closest we've come to the + * target frequency then remember n, m and k + */ + if (error < best_error) { + best_error = error; + best_n = n; + best_m = m; + best_k = k; + } + } + } + } + + n = best_n; + m = best_m; + k = best_k; + *freq_out = (fref * (n + 2) / (m + 2)) >> k; + + return (n << 8) | (m << 2) | k; +} + +static void do_write_regs(struct fb_info *info, struct banshee_reg *reg) +{ + struct tdfx_par *par = info->par; + int i; + + banshee_wait_idle(info); + + tdfx_outl(par, MISCINIT1, tdfx_inl(par, MISCINIT1) | 0x01); + + crt_outb(par, 0x11, crt_inb(par, 0x11) & 0x7f); /* CRT unprotect */ + + banshee_make_room(par, 3); + tdfx_outl(par, VGAINIT1, reg->vgainit1 & 0x001FFFFF); + tdfx_outl(par, VIDPROCCFG, reg->vidcfg & ~0x00000001); +#if 0 + tdfx_outl(par, PLLCTRL1, reg->mempll); + tdfx_outl(par, PLLCTRL2, reg->gfxpll); +#endif + tdfx_outl(par, PLLCTRL0, reg->vidpll); + + vga_outb(par, MISC_W, reg->misc[0x00] | 0x01); + + for (i = 0; i < 5; i++) + seq_outb(par, i, reg->seq[i]); + + for (i = 0; i < 25; i++) + crt_outb(par, i, reg->crt[i]); + + for (i = 0; i < 9; i++) + gra_outb(par, i, reg->gra[i]); + + for (i = 0; i < 21; i++) + att_outb(par, i, reg->att[i]); + + crt_outb(par, 0x1a, reg->ext[0]); + crt_outb(par, 0x1b, reg->ext[1]); + + vga_enable_palette(par); + vga_enable_video(par); + + banshee_make_room(par, 9); + tdfx_outl(par, VGAINIT0, reg->vgainit0); + tdfx_outl(par, DACMODE, reg->dacmode); + tdfx_outl(par, VIDDESKSTRIDE, reg->stride); + tdfx_outl(par, HWCURPATADDR, reg->curspataddr); + + tdfx_outl(par, VIDSCREENSIZE, reg->screensize); + tdfx_outl(par, VIDDESKSTART, reg->startaddr); + tdfx_outl(par, VIDPROCCFG, reg->vidcfg); + tdfx_outl(par, VGAINIT1, reg->vgainit1); + tdfx_outl(par, MISCINIT0, reg->miscinit0); + + banshee_make_room(par, 8); + tdfx_outl(par, SRCBASE, reg->startaddr); + tdfx_outl(par, DSTBASE, reg->startaddr); + tdfx_outl(par, COMMANDEXTRA_2D, 0); + tdfx_outl(par, CLIP0MIN, 0); + tdfx_outl(par, CLIP0MAX, 0x0fff0fff); + tdfx_outl(par, CLIP1MIN, 0); + tdfx_outl(par, CLIP1MAX, 0x0fff0fff); + tdfx_outl(par, SRCXY, 0); + + banshee_wait_idle(info); +} + +static unsigned long do_lfb_size(struct tdfx_par *par, unsigned short dev_id) +{ + u32 draminit0 = tdfx_inl(par, DRAMINIT0); + u32 draminit1 = tdfx_inl(par, DRAMINIT1); + u32 miscinit1; + int num_chips = (draminit0 & DRAMINIT0_SGRAM_NUM) ? 8 : 4; + int chip_size; /* in MB */ + int has_sgram = draminit1 & DRAMINIT1_MEM_SDRAM; + + if (dev_id < PCI_DEVICE_ID_3DFX_VOODOO5) { + /* Banshee/Voodoo3 */ + chip_size = 2; + if (has_sgram && !(draminit0 & DRAMINIT0_SGRAM_TYPE)) + chip_size = 1; + } else { + /* Voodoo4/5 */ + has_sgram = 0; + chip_size = draminit0 & DRAMINIT0_SGRAM_TYPE_MASK; + chip_size = 1 << (chip_size >> DRAMINIT0_SGRAM_TYPE_SHIFT); + } + + /* disable block writes for SDRAM */ + miscinit1 = tdfx_inl(par, MISCINIT1); + miscinit1 |= has_sgram ? 0 : MISCINIT1_2DBLOCK_DIS; + miscinit1 |= MISCINIT1_CLUT_INV; + + banshee_make_room(par, 1); + tdfx_outl(par, MISCINIT1, miscinit1); + return num_chips * chip_size * 1024l * 1024; +} + +/* ------------------------------------------------------------------------- */ + +static int tdfxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct tdfx_par *par = info->par; + u32 lpitch; + + if (var->bits_per_pixel != 8 && var->bits_per_pixel != 16 && + var->bits_per_pixel != 24 && var->bits_per_pixel != 32) { + DPRINTK("depth not supported: %u\n", var->bits_per_pixel); + return -EINVAL; + } + + if (var->xres != var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + if (var->xoffset) { + DPRINTK("xoffset not supported\n"); + return -EINVAL; + } + var->yoffset = 0; + + /* + * Banshee doesn't support interlace, but Voodoo4/5 and probably + * Voodoo3 do. + * no direct information about device id now? + * use max_pixclock for this... + */ + if (((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) && + (par->max_pixclock < VOODOO3_MAX_PIXCLOCK)) { + DPRINTK("interlace not supported\n"); + return -EINVAL; + } + + if (info->monspecs.hfmax && info->monspecs.vfmax && + info->monspecs.dclkmax && fb_validate_mode(var, info) < 0) { + DPRINTK("mode outside monitor's specs\n"); + return -EINVAL; + } + + var->xres = (var->xres + 15) & ~15; /* could sometimes be 8 */ + lpitch = var->xres * ((var->bits_per_pixel + 7) >> 3); + + if (var->xres < 320 || var->xres > 2048) { + DPRINTK("width not supported: %u\n", var->xres); + return -EINVAL; + } + + if (var->yres < 200 || var->yres > 2048) { + DPRINTK("height not supported: %u\n", var->yres); + return -EINVAL; + } + + if (lpitch * var->yres_virtual > info->fix.smem_len) { + var->yres_virtual = info->fix.smem_len / lpitch; + if (var->yres_virtual < var->yres) { + DPRINTK("no memory for screen (%ux%ux%u)\n", + var->xres, var->yres_virtual, + var->bits_per_pixel); + return -EINVAL; + } + } + + if (PICOS2KHZ(var->pixclock) > par->max_pixclock) { + DPRINTK("pixclock too high (%ldKHz)\n", + PICOS2KHZ(var->pixclock)); + return -EINVAL; + } + + var->transp.offset = 0; + var->transp.length = 0; + switch (var->bits_per_pixel) { + case 8: + var->red.length = 8; + var->red.offset = 0; + var->green = var->red; + var->blue = var->red; + break; + case 16: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + case 32: + var->transp.offset = 24; + var->transp.length = 8; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + break; + } + var->width = -1; + var->height = -1; + + var->accel_flags = FB_ACCELF_TEXT; + + DPRINTK("Checking graphics mode at %dx%d depth %d\n", + var->xres, var->yres, var->bits_per_pixel); + return 0; +} + +static int tdfxfb_set_par(struct fb_info *info) +{ + struct tdfx_par *par = info->par; + u32 hdispend = info->var.xres; + u32 hsyncsta = hdispend + info->var.right_margin; + u32 hsyncend = hsyncsta + info->var.hsync_len; + u32 htotal = hsyncend + info->var.left_margin; + u32 hd, hs, he, ht, hbs, hbe; + u32 vd, vs, ve, vt, vbs, vbe; + struct banshee_reg reg; + int fout, freq; + u32 wd; + u32 cpp = (info->var.bits_per_pixel + 7) >> 3; + + memset(®, 0, sizeof(reg)); + + reg.vidcfg = VIDCFG_VIDPROC_ENABLE | VIDCFG_DESK_ENABLE | + VIDCFG_CURS_X11 | + ((cpp - 1) << VIDCFG_PIXFMT_SHIFT) | + (cpp != 1 ? VIDCFG_CLUT_BYPASS : 0); + + /* PLL settings */ + freq = PICOS2KHZ(info->var.pixclock); + + reg.vidcfg &= ~VIDCFG_2X; + + if (freq > par->max_pixclock / 2) { + freq = freq > par->max_pixclock ? par->max_pixclock : freq; + reg.dacmode |= DACMODE_2X; + reg.vidcfg |= VIDCFG_2X; + hdispend >>= 1; + hsyncsta >>= 1; + hsyncend >>= 1; + htotal >>= 1; + } + + wd = (hdispend >> 3) - 1; + hd = wd; + hs = (hsyncsta >> 3) - 1; + he = (hsyncend >> 3) - 1; + ht = (htotal >> 3) - 1; + hbs = hd; + hbe = ht; + + if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) { + vd = (info->var.yres << 1) - 1; + vs = vd + (info->var.lower_margin << 1); + ve = vs + (info->var.vsync_len << 1); + vt = ve + (info->var.upper_margin << 1) - 1; + reg.screensize = info->var.xres | (info->var.yres << 13); + reg.vidcfg |= VIDCFG_HALF_MODE; + reg.crt[0x09] = 0x80; + } else { + vd = info->var.yres - 1; + vs = vd + info->var.lower_margin; + ve = vs + info->var.vsync_len; + vt = ve + info->var.upper_margin - 1; + reg.screensize = info->var.xres | (info->var.yres << 12); + reg.vidcfg &= ~VIDCFG_HALF_MODE; + } + vbs = vd; + vbe = vt; + + /* this is all pretty standard VGA register stuffing */ + reg.misc[0x00] = 0x0f | + (info->var.xres < 400 ? 0xa0 : + info->var.xres < 480 ? 0x60 : + info->var.xres < 768 ? 0xe0 : 0x20); + + reg.gra[0x05] = 0x40; + reg.gra[0x06] = 0x05; + reg.gra[0x07] = 0x0f; + reg.gra[0x08] = 0xff; + + reg.att[0x00] = 0x00; + reg.att[0x01] = 0x01; + reg.att[0x02] = 0x02; + reg.att[0x03] = 0x03; + reg.att[0x04] = 0x04; + reg.att[0x05] = 0x05; + reg.att[0x06] = 0x06; + reg.att[0x07] = 0x07; + reg.att[0x08] = 0x08; + reg.att[0x09] = 0x09; + reg.att[0x0a] = 0x0a; + reg.att[0x0b] = 0x0b; + reg.att[0x0c] = 0x0c; + reg.att[0x0d] = 0x0d; + reg.att[0x0e] = 0x0e; + reg.att[0x0f] = 0x0f; + reg.att[0x10] = 0x41; + reg.att[0x12] = 0x0f; + + reg.seq[0x00] = 0x03; + reg.seq[0x01] = 0x01; /* fixme: clkdiv2? */ + reg.seq[0x02] = 0x0f; + reg.seq[0x03] = 0x00; + reg.seq[0x04] = 0x0e; + + reg.crt[0x00] = ht - 4; + reg.crt[0x01] = hd; + reg.crt[0x02] = hbs; + reg.crt[0x03] = 0x80 | (hbe & 0x1f); + reg.crt[0x04] = hs; + reg.crt[0x05] = ((hbe & 0x20) << 2) | (he & 0x1f); + reg.crt[0x06] = vt; + reg.crt[0x07] = ((vs & 0x200) >> 2) | + ((vd & 0x200) >> 3) | + ((vt & 0x200) >> 4) | 0x10 | + ((vbs & 0x100) >> 5) | + ((vs & 0x100) >> 6) | + ((vd & 0x100) >> 7) | + ((vt & 0x100) >> 8); + reg.crt[0x09] |= 0x40 | ((vbs & 0x200) >> 4); + reg.crt[0x10] = vs; + reg.crt[0x11] = (ve & 0x0f) | 0x20; + reg.crt[0x12] = vd; + reg.crt[0x13] = wd; + reg.crt[0x15] = vbs; + reg.crt[0x16] = vbe + 1; + reg.crt[0x17] = 0xc3; + reg.crt[0x18] = 0xff; + + /* Banshee's nonvga stuff */ + reg.ext[0x00] = (((ht & 0x100) >> 8) | + ((hd & 0x100) >> 6) | + ((hbs & 0x100) >> 4) | + ((hbe & 0x40) >> 1) | + ((hs & 0x100) >> 2) | + ((he & 0x20) << 2)); + reg.ext[0x01] = (((vt & 0x400) >> 10) | + ((vd & 0x400) >> 8) | + ((vbs & 0x400) >> 6) | + ((vbe & 0x400) >> 4)); + + reg.vgainit0 = VGAINIT0_8BIT_DAC | + VGAINIT0_EXT_ENABLE | + VGAINIT0_WAKEUP_3C3 | + VGAINIT0_ALT_READBACK | + VGAINIT0_EXTSHIFTOUT; + reg.vgainit1 = tdfx_inl(par, VGAINIT1) & 0x1fffff; + + if (hwcursor) + reg.curspataddr = info->fix.smem_len; + + reg.cursloc = 0; + + reg.cursc0 = 0; + reg.cursc1 = 0xffffff; + + reg.stride = info->var.xres * cpp; + reg.startaddr = info->var.yoffset * reg.stride + + info->var.xoffset * cpp; + + reg.vidpll = do_calc_pll(freq, &fout); +#if 0 + reg.mempll = do_calc_pll(..., &fout); + reg.gfxpll = do_calc_pll(..., &fout); +#endif + + if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) + reg.vidcfg |= VIDCFG_INTERLACE; + reg.miscinit0 = tdfx_inl(par, MISCINIT0); + +#if defined(__BIG_ENDIAN) + switch (info->var.bits_per_pixel) { + case 8: + case 24: + reg.miscinit0 &= ~(1 << 30); + reg.miscinit0 &= ~(1 << 31); + break; + case 16: + reg.miscinit0 |= (1 << 30); + reg.miscinit0 |= (1 << 31); + break; + case 32: + reg.miscinit0 |= (1 << 30); + reg.miscinit0 &= ~(1 << 31); + break; + } +#endif + do_write_regs(info, ®); + + /* Now change fb_fix_screeninfo according to changes in par */ + info->fix.line_length = reg.stride; + info->fix.visual = (info->var.bits_per_pixel == 8) + ? FB_VISUAL_PSEUDOCOLOR + : FB_VISUAL_TRUECOLOR; + DPRINTK("Graphics mode is now set at %dx%d depth %d\n", + info->var.xres, info->var.yres, info->var.bits_per_pixel); + return 0; +} + +/* A handy macro shamelessly pinched from matroxfb */ +#define CNVT_TOHW(val, width) ((((val) << (width)) + 0x7FFF - (val)) >> 16) + +static int tdfxfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct tdfx_par *par = info->par; + u32 rgbcol; + + if (regno >= info->cmap.len || regno > 255) + return 1; + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + blue = (red * 77 + green * 151 + blue * 28) >> 8; + green = blue; + red = blue; + } + + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + rgbcol = (((u32)red & 0xff00) << 8) | + (((u32)green & 0xff00) << 0) | + (((u32)blue & 0xff00) >> 8); + do_setpalentry(par, regno, rgbcol); + break; + /* Truecolor has no hardware color palettes. */ + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + rgbcol = (CNVT_TOHW(red, info->var.red.length) << + info->var.red.offset) | + (CNVT_TOHW(green, info->var.green.length) << + info->var.green.offset) | + (CNVT_TOHW(blue, info->var.blue.length) << + info->var.blue.offset) | + (CNVT_TOHW(transp, info->var.transp.length) << + info->var.transp.offset); + par->palette[regno] = rgbcol; + } + + break; + default: + DPRINTK("bad depth %u\n", info->var.bits_per_pixel); + break; + } + + return 0; +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ +static int tdfxfb_blank(int blank, struct fb_info *info) +{ + struct tdfx_par *par = info->par; + int vgablank = 1; + u32 dacmode = tdfx_inl(par, DACMODE); + + dacmode &= ~(BIT(1) | BIT(3)); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Screen: On; HSync: On, VSync: On */ + vgablank = 0; + break; + case FB_BLANK_NORMAL: /* Screen: Off; HSync: On, VSync: On */ + break; + case FB_BLANK_VSYNC_SUSPEND: /* Screen: Off; HSync: On, VSync: Off */ + dacmode |= BIT(3); + break; + case FB_BLANK_HSYNC_SUSPEND: /* Screen: Off; HSync: Off, VSync: On */ + dacmode |= BIT(1); + break; + case FB_BLANK_POWERDOWN: /* Screen: Off; HSync: Off, VSync: Off */ + dacmode |= BIT(1) | BIT(3); + break; + } + + banshee_make_room(par, 1); + tdfx_outl(par, DACMODE, dacmode); + if (vgablank) + vga_disable_video(par); + else + vga_enable_video(par); + return 0; +} + +/* + * Set the starting position of the visible screen to var->yoffset + */ +static int tdfxfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct tdfx_par *par = info->par; + u32 addr = var->yoffset * info->fix.line_length; + + if (nopan || var->xoffset) + return -EINVAL; + + banshee_make_room(par, 1); + tdfx_outl(par, VIDDESKSTART, addr); + + return 0; +} + +#ifdef CONFIG_FB_3DFX_ACCEL +/* + * FillRect 2D command (solidfill or invert (via ROP_XOR)) + */ +static void tdfxfb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct tdfx_par *par = info->par; + u32 bpp = info->var.bits_per_pixel; + u32 stride = info->fix.line_length; + u32 fmt = stride | ((bpp + ((bpp == 8) ? 0 : 8)) << 13); + int tdfx_rop; + u32 dx = rect->dx; + u32 dy = rect->dy; + u32 dstbase = 0; + + if (rect->rop == ROP_COPY) + tdfx_rop = TDFX_ROP_COPY; + else + tdfx_rop = TDFX_ROP_XOR; + + /* assume always rect->height < 4096 */ + if (dy + rect->height > 4095) { + dstbase = stride * dy; + dy = 0; + } + /* assume always rect->width < 4096 */ + if (dx + rect->width > 4095) { + dstbase += dx * bpp >> 3; + dx = 0; + } + banshee_make_room(par, 6); + tdfx_outl(par, DSTFORMAT, fmt); + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) { + tdfx_outl(par, COLORFORE, rect->color); + } else { /* FB_VISUAL_TRUECOLOR */ + tdfx_outl(par, COLORFORE, par->palette[rect->color]); + } + tdfx_outl(par, COMMAND_2D, COMMAND_2D_FILLRECT | (tdfx_rop << 24)); + tdfx_outl(par, DSTBASE, dstbase); + tdfx_outl(par, DSTSIZE, rect->width | (rect->height << 16)); + tdfx_outl(par, LAUNCH_2D, dx | (dy << 16)); +} + +/* + * Screen-to-Screen BitBlt 2D command (for the bmove fb op.) + */ +static void tdfxfb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct tdfx_par *par = info->par; + u32 sx = area->sx, sy = area->sy, dx = area->dx, dy = area->dy; + u32 bpp = info->var.bits_per_pixel; + u32 stride = info->fix.line_length; + u32 blitcmd = COMMAND_2D_S2S_BITBLT | (TDFX_ROP_COPY << 24); + u32 fmt = stride | ((bpp + ((bpp == 8) ? 0 : 8)) << 13); + u32 dstbase = 0; + u32 srcbase = 0; + + /* assume always area->height < 4096 */ + if (sy + area->height > 4095) { + srcbase = stride * sy; + sy = 0; + } + /* assume always area->width < 4096 */ + if (sx + area->width > 4095) { + srcbase += sx * bpp >> 3; + sx = 0; + } + /* assume always area->height < 4096 */ + if (dy + area->height > 4095) { + dstbase = stride * dy; + dy = 0; + } + /* assume always area->width < 4096 */ + if (dx + area->width > 4095) { + dstbase += dx * bpp >> 3; + dx = 0; + } + + if (area->sx <= area->dx) { + /* -X */ + blitcmd |= BIT(14); + sx += area->width - 1; + dx += area->width - 1; + } + if (area->sy <= area->dy) { + /* -Y */ + blitcmd |= BIT(15); + sy += area->height - 1; + dy += area->height - 1; + } + + banshee_make_room(par, 8); + + tdfx_outl(par, SRCFORMAT, fmt); + tdfx_outl(par, DSTFORMAT, fmt); + tdfx_outl(par, COMMAND_2D, blitcmd); + tdfx_outl(par, DSTSIZE, area->width | (area->height << 16)); + tdfx_outl(par, DSTXY, dx | (dy << 16)); + tdfx_outl(par, SRCBASE, srcbase); + tdfx_outl(par, DSTBASE, dstbase); + tdfx_outl(par, LAUNCH_2D, sx | (sy << 16)); +} + +static void tdfxfb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct tdfx_par *par = info->par; + int size = image->height * ((image->width * image->depth + 7) >> 3); + int fifo_free; + int i, stride = info->fix.line_length; + u32 bpp = info->var.bits_per_pixel; + u32 dstfmt = stride | ((bpp + ((bpp == 8) ? 0 : 8)) << 13); + u8 *chardata = (u8 *) image->data; + u32 srcfmt; + u32 dx = image->dx; + u32 dy = image->dy; + u32 dstbase = 0; + + if (image->depth != 1) { +#ifdef BROKEN_CODE + banshee_make_room(par, 6 + ((size + 3) >> 2)); + srcfmt = stride | ((bpp + ((bpp == 8) ? 0 : 8)) << 13) | + 0x400000; +#else + cfb_imageblit(info, image); +#endif + return; + } + banshee_make_room(par, 9); + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + tdfx_outl(par, COLORFORE, image->fg_color); + tdfx_outl(par, COLORBACK, image->bg_color); + break; + case FB_VISUAL_TRUECOLOR: + default: + tdfx_outl(par, COLORFORE, + par->palette[image->fg_color]); + tdfx_outl(par, COLORBACK, + par->palette[image->bg_color]); + } +#ifdef __BIG_ENDIAN + srcfmt = 0x400000 | BIT(20); +#else + srcfmt = 0x400000; +#endif + /* assume always image->height < 4096 */ + if (dy + image->height > 4095) { + dstbase = stride * dy; + dy = 0; + } + /* assume always image->width < 4096 */ + if (dx + image->width > 4095) { + dstbase += dx * bpp >> 3; + dx = 0; + } + + tdfx_outl(par, DSTBASE, dstbase); + tdfx_outl(par, SRCXY, 0); + tdfx_outl(par, DSTXY, dx | (dy << 16)); + tdfx_outl(par, COMMAND_2D, + COMMAND_2D_H2S_BITBLT | (TDFX_ROP_COPY << 24)); + tdfx_outl(par, SRCFORMAT, srcfmt); + tdfx_outl(par, DSTFORMAT, dstfmt); + tdfx_outl(par, DSTSIZE, image->width | (image->height << 16)); + + /* A count of how many free FIFO entries we've requested. + * When this goes negative, we need to request more. */ + fifo_free = 0; + + /* Send four bytes at a time of data */ + for (i = (size >> 2); i > 0; i--) { + if (--fifo_free < 0) { + fifo_free = 31; + banshee_make_room(par, fifo_free); + } + tdfx_outl(par, LAUNCH_2D, *(u32 *)chardata); + chardata += 4; + } + + /* Send the leftovers now */ + banshee_make_room(par, 3); + switch (size % 4) { + case 0: + break; + case 1: + tdfx_outl(par, LAUNCH_2D, *chardata); + break; + case 2: + tdfx_outl(par, LAUNCH_2D, *(u16 *)chardata); + break; + case 3: + tdfx_outl(par, LAUNCH_2D, + *(u16 *)chardata | (chardata[3] << 24)); + break; + } +} +#endif /* CONFIG_FB_3DFX_ACCEL */ + +static int tdfxfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct tdfx_par *par = info->par; + u32 vidcfg; + + if (!hwcursor) + return -EINVAL; /* just to force soft_cursor() call */ + + /* Too large of a cursor or wrong bpp :-( */ + if (cursor->image.width > 64 || + cursor->image.height > 64 || + cursor->image.depth > 1) + return -EINVAL; + + vidcfg = tdfx_inl(par, VIDPROCCFG); + if (cursor->enable) + tdfx_outl(par, VIDPROCCFG, vidcfg | VIDCFG_HWCURSOR_ENABLE); + else + tdfx_outl(par, VIDPROCCFG, vidcfg & ~VIDCFG_HWCURSOR_ENABLE); + + /* + * If the cursor is not be changed this means either we want the + * current cursor state (if enable is set) or we want to query what + * we can do with the cursor (if enable is not set) + */ + if (!cursor->set) + return 0; + + /* fix cursor color - XFree86 forgets to restore it properly */ + if (cursor->set & FB_CUR_SETCMAP) { + struct fb_cmap cmap = info->cmap; + u32 bg_idx = cursor->image.bg_color; + u32 fg_idx = cursor->image.fg_color; + unsigned long bg_color, fg_color; + + fg_color = (((u32)cmap.red[fg_idx] & 0xff00) << 8) | + (((u32)cmap.green[fg_idx] & 0xff00) << 0) | + (((u32)cmap.blue[fg_idx] & 0xff00) >> 8); + bg_color = (((u32)cmap.red[bg_idx] & 0xff00) << 8) | + (((u32)cmap.green[bg_idx] & 0xff00) << 0) | + (((u32)cmap.blue[bg_idx] & 0xff00) >> 8); + banshee_make_room(par, 2); + tdfx_outl(par, HWCURC0, bg_color); + tdfx_outl(par, HWCURC1, fg_color); + } + + if (cursor->set & FB_CUR_SETPOS) { + int x = cursor->image.dx; + int y = cursor->image.dy - info->var.yoffset; + + x += 63; + y += 63; + banshee_make_room(par, 1); + tdfx_outl(par, HWCURLOC, (y << 16) + x); + } + if (cursor->set & (FB_CUR_SETIMAGE | FB_CUR_SETSHAPE)) { + /* + * Voodoo 3 and above cards use 2 monochrome cursor patterns. + * The reason is so the card can fetch 8 words at a time + * and are stored on chip for use for the next 8 scanlines. + * This reduces the number of times for access to draw the + * cursor for each screen refresh. + * Each pattern is a bitmap of 64 bit wide and 64 bit high + * (total of 8192 bits or 1024 bytes). The two patterns are + * stored in such a way that pattern 0 always resides in the + * lower half (least significant 64 bits) of a 128 bit word + * and pattern 1 the upper half. If you examine the data of + * the cursor image the graphics card uses then from the + * beginning you see line one of pattern 0, line one of + * pattern 1, line two of pattern 0, line two of pattern 1, + * etc etc. The linear stride for the cursor is always 16 bytes + * (128 bits) which is the maximum cursor width times two for + * the two monochrome patterns. + */ + u8 __iomem *cursorbase = info->screen_base + info->fix.smem_len; + u8 *bitmap = (u8 *)cursor->image.data; + u8 *mask = (u8 *)cursor->mask; + int i; + + fb_memset(cursorbase, 0, 1024); + + for (i = 0; i < cursor->image.height; i++) { + int h = 0; + int j = (cursor->image.width + 7) >> 3; + + for (; j > 0; j--) { + u8 data = *mask ^ *bitmap; + if (cursor->rop == ROP_COPY) + data = *mask & *bitmap; + /* Pattern 0. Copy the cursor mask to it */ + fb_writeb(*mask, cursorbase + h); + mask++; + /* Pattern 1. Copy the cursor bitmap to it */ + fb_writeb(data, cursorbase + h + 8); + bitmap++; + h++; + } + cursorbase += 16; + } + } + return 0; +} + +static struct fb_ops tdfxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = tdfxfb_check_var, + .fb_set_par = tdfxfb_set_par, + .fb_setcolreg = tdfxfb_setcolreg, + .fb_blank = tdfxfb_blank, + .fb_pan_display = tdfxfb_pan_display, + .fb_sync = banshee_wait_idle, + .fb_cursor = tdfxfb_cursor, +#ifdef CONFIG_FB_3DFX_ACCEL + .fb_fillrect = tdfxfb_fillrect, + .fb_copyarea = tdfxfb_copyarea, + .fb_imageblit = tdfxfb_imageblit, +#else + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +#endif +}; + +#ifdef CONFIG_FB_3DFX_I2C +/* The voo GPIO registers don't have individual masks for each bit + so we always have to read before writing. */ + +static void tdfxfb_i2c_setscl(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= I2C_SCL_OUT; + else + r &= ~I2C_SCL_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +static void tdfxfb_i2c_setsda(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= I2C_SDA_OUT; + else + r &= ~I2C_SDA_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +/* The GPIO pins are open drain, so the pins always remain outputs. + We rely on the i2c-algo-bit routines to set the pins high before + reading the input from other chips. */ + +static int tdfxfb_i2c_getscl(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & I2C_SCL_IN)); +} + +static int tdfxfb_i2c_getsda(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & I2C_SDA_IN)); +} + +static void tdfxfb_ddc_setscl(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= DDC_SCL_OUT; + else + r &= ~DDC_SCL_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +static void tdfxfb_ddc_setsda(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= DDC_SDA_OUT; + else + r &= ~DDC_SDA_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +static int tdfxfb_ddc_getscl(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & DDC_SCL_IN)); +} + +static int tdfxfb_ddc_getsda(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & DDC_SDA_IN)); +} + +static int tdfxfb_setup_ddc_bus(struct tdfxfb_i2c_chan *chan, const char *name, + struct device *dev) +{ + int rc; + + strlcpy(chan->adapter.name, name, sizeof(chan->adapter.name)); + chan->adapter.owner = THIS_MODULE; + chan->adapter.class = I2C_CLASS_DDC; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = dev; + chan->algo.setsda = tdfxfb_ddc_setsda; + chan->algo.setscl = tdfxfb_ddc_setscl; + chan->algo.getsda = tdfxfb_ddc_getsda; + chan->algo.getscl = tdfxfb_ddc_getscl; + chan->algo.udelay = 10; + chan->algo.timeout = msecs_to_jiffies(500); + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + DPRINTK("I2C bus %s registered.\n", name); + else + chan->par = NULL; + + return rc; +} + +static int tdfxfb_setup_i2c_bus(struct tdfxfb_i2c_chan *chan, const char *name, + struct device *dev) +{ + int rc; + + strlcpy(chan->adapter.name, name, sizeof(chan->adapter.name)); + chan->adapter.owner = THIS_MODULE; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = dev; + chan->algo.setsda = tdfxfb_i2c_setsda; + chan->algo.setscl = tdfxfb_i2c_setscl; + chan->algo.getsda = tdfxfb_i2c_getsda; + chan->algo.getscl = tdfxfb_i2c_getscl; + chan->algo.udelay = 10; + chan->algo.timeout = msecs_to_jiffies(500); + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + DPRINTK("I2C bus %s registered.\n", name); + else + chan->par = NULL; + + return rc; +} + +static void tdfxfb_create_i2c_busses(struct fb_info *info) +{ + struct tdfx_par *par = info->par; + + tdfx_outl(par, VIDINFORMAT, 0x8160); + tdfx_outl(par, VIDSERPARPORT, 0xcffc0020); + + par->chan[0].par = par; + par->chan[1].par = par; + + tdfxfb_setup_ddc_bus(&par->chan[0], "Voodoo3-DDC", info->dev); + tdfxfb_setup_i2c_bus(&par->chan[1], "Voodoo3-I2C", info->dev); +} + +static void tdfxfb_delete_i2c_busses(struct tdfx_par *par) +{ + if (par->chan[0].par) + i2c_del_adapter(&par->chan[0].adapter); + par->chan[0].par = NULL; + + if (par->chan[1].par) + i2c_del_adapter(&par->chan[1].adapter); + par->chan[1].par = NULL; +} + +static int tdfxfb_probe_i2c_connector(struct tdfx_par *par, + struct fb_monspecs *specs) +{ + u8 *edid = NULL; + + DPRINTK("Probe DDC Bus\n"); + if (par->chan[0].par) + edid = fb_ddc_read(&par->chan[0].adapter); + + if (edid) { + fb_edid_to_monspecs(edid, specs); + kfree(edid); + return 0; + } + return 1; +} +#endif /* CONFIG_FB_3DFX_I2C */ + +/** + * tdfxfb_probe - Device Initializiation + * + * @pdev: PCI Device to initialize + * @id: PCI Device ID + * + * Initializes and allocates resources for PCI device @pdev. + * + */ +static int tdfxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct tdfx_par *default_par; + struct fb_info *info; + int err, lpitch; + struct fb_monspecs *specs; + bool found; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR "tdfxfb: Can't enable pdev: %d\n", err); + return err; + } + + info = framebuffer_alloc(sizeof(struct tdfx_par), &pdev->dev); + + if (!info) + return -ENOMEM; + + default_par = info->par; + info->fix = tdfx_fix; + + /* Configure the default fb_fix_screeninfo first */ + switch (pdev->device) { + case PCI_DEVICE_ID_3DFX_BANSHEE: + strcpy(info->fix.id, "3Dfx Banshee"); + default_par->max_pixclock = BANSHEE_MAX_PIXCLOCK; + break; + case PCI_DEVICE_ID_3DFX_VOODOO3: + strcpy(info->fix.id, "3Dfx Voodoo3"); + default_par->max_pixclock = VOODOO3_MAX_PIXCLOCK; + break; + case PCI_DEVICE_ID_3DFX_VOODOO5: + strcpy(info->fix.id, "3Dfx Voodoo5"); + default_par->max_pixclock = VOODOO5_MAX_PIXCLOCK; + break; + } + + info->fix.mmio_start = pci_resource_start(pdev, 0); + info->fix.mmio_len = pci_resource_len(pdev, 0); + if (!request_mem_region(info->fix.mmio_start, info->fix.mmio_len, + "tdfx regbase")) { + printk(KERN_ERR "tdfxfb: Can't reserve regbase\n"); + goto out_err; + } + + default_par->regbase_virt = + ioremap_nocache(info->fix.mmio_start, info->fix.mmio_len); + if (!default_par->regbase_virt) { + printk(KERN_ERR "fb: Can't remap %s register area.\n", + info->fix.id); + goto out_err_regbase; + } + + info->fix.smem_start = pci_resource_start(pdev, 1); + info->fix.smem_len = do_lfb_size(default_par, pdev->device); + if (!info->fix.smem_len) { + printk(KERN_ERR "fb: Can't count %s memory.\n", info->fix.id); + goto out_err_regbase; + } + + if (!request_mem_region(info->fix.smem_start, + pci_resource_len(pdev, 1), "tdfx smem")) { + printk(KERN_ERR "tdfxfb: Can't reserve smem\n"); + goto out_err_regbase; + } + + info->screen_base = ioremap_nocache(info->fix.smem_start, + info->fix.smem_len); + if (!info->screen_base) { + printk(KERN_ERR "fb: Can't remap %s framebuffer.\n", + info->fix.id); + goto out_err_screenbase; + } + + default_par->iobase = pci_resource_start(pdev, 2); + + if (!request_region(pci_resource_start(pdev, 2), + pci_resource_len(pdev, 2), "tdfx iobase")) { + printk(KERN_ERR "tdfxfb: Can't reserve iobase\n"); + goto out_err_screenbase; + } + + printk(KERN_INFO "fb: %s memory = %dK\n", info->fix.id, + info->fix.smem_len >> 10); + + default_par->mtrr_handle = -1; + if (!nomtrr) + default_par->mtrr_handle = + mtrr_add(info->fix.smem_start, info->fix.smem_len, + MTRR_TYPE_WRCOMB, 1); + + info->fix.ypanstep = nopan ? 0 : 1; + info->fix.ywrapstep = nowrap ? 0 : 1; + + info->fbops = &tdfxfb_ops; + info->pseudo_palette = default_par->palette; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; +#ifdef CONFIG_FB_3DFX_ACCEL + info->flags |= FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_IMAGEBLIT | + FBINFO_READS_FAST; +#endif + /* reserve 8192 bits for cursor */ + /* the 2.4 driver says PAGE_MASK boundary is not enough for Voodoo4 */ + if (hwcursor) + info->fix.smem_len = (info->fix.smem_len - 1024) & + (PAGE_MASK << 1); + specs = &info->monspecs; + found = false; + info->var.bits_per_pixel = 8; +#ifdef CONFIG_FB_3DFX_I2C + tdfxfb_create_i2c_busses(info); + err = tdfxfb_probe_i2c_connector(default_par, specs); + + if (!err) { + if (specs->modedb == NULL) + DPRINTK("Unable to get Mode Database\n"); + else { + const struct fb_videomode *m; + + fb_videomode_to_modelist(specs->modedb, + specs->modedb_len, + &info->modelist); + m = fb_find_best_display(specs, &info->modelist); + if (m) { + fb_videomode_to_var(&info->var, m); + /* fill all other info->var's fields */ + if (tdfxfb_check_var(&info->var, info) < 0) + info->var = tdfx_var; + else + found = true; + } + } + } +#endif + if (!mode_option && !found) + mode_option = "640x480@60"; + + if (mode_option) { + err = fb_find_mode(&info->var, info, mode_option, + specs->modedb, specs->modedb_len, + NULL, info->var.bits_per_pixel); + if (!err || err == 4) + info->var = tdfx_var; + } + + if (found) { + fb_destroy_modedb(specs->modedb); + specs->modedb = NULL; + } + + /* maximize virtual vertical length */ + lpitch = info->var.xres_virtual * ((info->var.bits_per_pixel + 7) >> 3); + info->var.yres_virtual = info->fix.smem_len / lpitch; + if (info->var.yres_virtual < info->var.yres) + goto out_err_iobase; + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + printk(KERN_ERR "tdfxfb: Can't allocate color map\n"); + goto out_err_iobase; + } + + if (register_framebuffer(info) < 0) { + printk(KERN_ERR "tdfxfb: can't register framebuffer\n"); + fb_dealloc_cmap(&info->cmap); + goto out_err_iobase; + } + /* + * Our driver data + */ + pci_set_drvdata(pdev, info); + return 0; + +out_err_iobase: +#ifdef CONFIG_FB_3DFX_I2C + tdfxfb_delete_i2c_busses(default_par); +#endif + if (default_par->mtrr_handle >= 0) + mtrr_del(default_par->mtrr_handle, info->fix.smem_start, + info->fix.smem_len); + release_region(pci_resource_start(pdev, 2), + pci_resource_len(pdev, 2)); +out_err_screenbase: + if (info->screen_base) + iounmap(info->screen_base); + release_mem_region(info->fix.smem_start, pci_resource_len(pdev, 1)); +out_err_regbase: + /* + * Cleanup after anything that was remapped/allocated. + */ + if (default_par->regbase_virt) + iounmap(default_par->regbase_virt); + release_mem_region(info->fix.mmio_start, info->fix.mmio_len); +out_err: + framebuffer_release(info); + return -ENXIO; +} + +#ifndef MODULE +static void __init tdfxfb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + if (!strcmp(this_opt, "nopan")) { + nopan = 1; + } else if (!strcmp(this_opt, "nowrap")) { + nowrap = 1; + } else if (!strncmp(this_opt, "hwcursor=", 9)) { + hwcursor = simple_strtoul(this_opt + 9, NULL, 0); +#ifdef CONFIG_MTRR + } else if (!strncmp(this_opt, "nomtrr", 6)) { + nomtrr = 1; +#endif + } else { + mode_option = this_opt; + } + } +} +#endif + +/** + * tdfxfb_remove - Device removal + * + * @pdev: PCI Device to cleanup + * + * Releases all resources allocated during the course of the driver's + * lifetime for the PCI device @pdev. + * + */ +static void tdfxfb_remove(struct pci_dev *pdev) +{ + struct fb_info *info = pci_get_drvdata(pdev); + struct tdfx_par *par = info->par; + + unregister_framebuffer(info); +#ifdef CONFIG_FB_3DFX_I2C + tdfxfb_delete_i2c_busses(par); +#endif + if (par->mtrr_handle >= 0) + mtrr_del(par->mtrr_handle, info->fix.smem_start, + info->fix.smem_len); + iounmap(par->regbase_virt); + iounmap(info->screen_base); + + /* Clean up after reserved regions */ + release_region(pci_resource_start(pdev, 2), + pci_resource_len(pdev, 2)); + release_mem_region(pci_resource_start(pdev, 1), + pci_resource_len(pdev, 1)); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + +static int __init tdfxfb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("tdfxfb", &option)) + return -ENODEV; + + tdfxfb_setup(option); +#endif + return pci_register_driver(&tdfxfb_driver); +} + +static void __exit tdfxfb_exit(void) +{ + pci_unregister_driver(&tdfxfb_driver); +} + +MODULE_AUTHOR("Hannu Mallat <hmallat@cc.hut.fi>"); +MODULE_DESCRIPTION("3Dfx framebuffer device driver"); +MODULE_LICENSE("GPL"); + +module_param(hwcursor, int, 0644); +MODULE_PARM_DESC(hwcursor, "Enable hardware cursor " + "(1=enable, 0=disable, default=1)"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Initial video mode e.g. '648x480-8@60'"); +#ifdef CONFIG_MTRR +module_param(nomtrr, bool, 0); +MODULE_PARM_DESC(nomtrr, "Disable MTRR support (default: enabled)"); +#endif + +module_init(tdfxfb_init); +module_exit(tdfxfb_exit); diff --git a/drivers/video/fbdev/tgafb.c b/drivers/video/fbdev/tgafb.c new file mode 100644 index 000000000000..65ba9921506e --- /dev/null +++ b/drivers/video/fbdev/tgafb.c @@ -0,0 +1,1611 @@ +/* + * linux/drivers/video/tgafb.c -- DEC 21030 TGA frame buffer device + * + * Copyright (C) 1995 Jay Estabrook + * Copyright (C) 1997 Geert Uytterhoeven + * Copyright (C) 1999,2000 Martin Lucina, Tom Zerucha + * Copyright (C) 2002 Richard Henderson + * Copyright (C) 2006, 2007 Maciej W. Rozycki + * + * 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/bitrev.h> +#include <linux/compiler.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/selection.h> +#include <linux/string.h> +#include <linux/tc.h> + +#include <asm/io.h> + +#include <video/tgafb.h> + +#ifdef CONFIG_TC +#define TGA_BUS_TC(dev) (dev->bus == &tc_bus_type) +#else +#define TGA_BUS_TC(dev) 0 +#endif + +/* + * Local functions. + */ + +static int tgafb_check_var(struct fb_var_screeninfo *, struct fb_info *); +static int tgafb_set_par(struct fb_info *); +static void tgafb_set_pll(struct tga_par *, int); +static int tgafb_setcolreg(unsigned, unsigned, unsigned, unsigned, + unsigned, struct fb_info *); +static int tgafb_blank(int, struct fb_info *); +static void tgafb_init_fix(struct fb_info *); + +static void tgafb_imageblit(struct fb_info *, const struct fb_image *); +static void tgafb_fillrect(struct fb_info *, const struct fb_fillrect *); +static void tgafb_copyarea(struct fb_info *, const struct fb_copyarea *); +static int tgafb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); + +static int tgafb_register(struct device *dev); +static void tgafb_unregister(struct device *dev); + +static const char *mode_option; +static const char *mode_option_pci = "640x480@60"; +static const char *mode_option_tc = "1280x1024@72"; + + +static struct pci_driver tgafb_pci_driver; +static struct tc_driver tgafb_tc_driver; + +/* + * Frame buffer operations + */ + +static struct fb_ops tgafb_ops = { + .owner = THIS_MODULE, + .fb_check_var = tgafb_check_var, + .fb_set_par = tgafb_set_par, + .fb_setcolreg = tgafb_setcolreg, + .fb_blank = tgafb_blank, + .fb_pan_display = tgafb_pan_display, + .fb_fillrect = tgafb_fillrect, + .fb_copyarea = tgafb_copyarea, + .fb_imageblit = tgafb_imageblit, +}; + + +#ifdef CONFIG_PCI +/* + * PCI registration operations + */ +static int tgafb_pci_register(struct pci_dev *, const struct pci_device_id *); +static void tgafb_pci_unregister(struct pci_dev *); + +static struct pci_device_id const tgafb_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TGA) }, + { } +}; +MODULE_DEVICE_TABLE(pci, tgafb_pci_table); + +static struct pci_driver tgafb_pci_driver = { + .name = "tgafb", + .id_table = tgafb_pci_table, + .probe = tgafb_pci_register, + .remove = tgafb_pci_unregister, +}; + +static int tgafb_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + return tgafb_register(&pdev->dev); +} + +static void tgafb_pci_unregister(struct pci_dev *pdev) +{ + tgafb_unregister(&pdev->dev); +} +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_TC +/* + * TC registration operations + */ +static int tgafb_tc_register(struct device *); +static int tgafb_tc_unregister(struct device *); + +static struct tc_device_id const tgafb_tc_table[] = { + { "DEC ", "PMAGD-AA" }, + { "DEC ", "PMAGD " }, + { } +}; +MODULE_DEVICE_TABLE(tc, tgafb_tc_table); + +static struct tc_driver tgafb_tc_driver = { + .id_table = tgafb_tc_table, + .driver = { + .name = "tgafb", + .bus = &tc_bus_type, + .probe = tgafb_tc_register, + .remove = tgafb_tc_unregister, + }, +}; + +static int tgafb_tc_register(struct device *dev) +{ + int status = tgafb_register(dev); + if (!status) + get_device(dev); + return status; +} + +static int tgafb_tc_unregister(struct device *dev) +{ + put_device(dev); + tgafb_unregister(dev); + return 0; +} +#endif /* CONFIG_TC */ + + +/** + * tgafb_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 + */ +static int +tgafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct tga_par *par = (struct tga_par *)info->par; + + if (par->tga_type == TGA_TYPE_8PLANE) { + if (var->bits_per_pixel != 8) + return -EINVAL; + } else { + if (var->bits_per_pixel != 32) + return -EINVAL; + } + var->red.length = var->green.length = var->blue.length = 8; + if (var->bits_per_pixel == 32) { + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + } + + if (var->xres_virtual != var->xres || var->yres_virtual != var->yres) + return -EINVAL; + if (var->xres * var->yres * (var->bits_per_pixel >> 3) > info->fix.smem_len) + return -EINVAL; + if (var->nonstd) + return -EINVAL; + if (1000000000 / var->pixclock > TGA_PLL_MAX_FREQ) + return -EINVAL; + if ((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) + return -EINVAL; + + /* Some of the acceleration routines assume the line width is + a multiple of 8 bytes. */ + if (var->xres * (par->tga_type == TGA_TYPE_8PLANE ? 1 : 4) % 8) + return -EINVAL; + + return 0; +} + +/** + * tgafb_set_par - Optional function. Alters the hardware state. + * @info: frame buffer structure that represents a single frame buffer + */ +static int +tgafb_set_par(struct fb_info *info) +{ + static unsigned int const deep_presets[4] = { + 0x00004000, + 0x0000440d, + 0xffffffff, + 0x0000441d + }; + static unsigned int const rasterop_presets[4] = { + 0x00000003, + 0x00000303, + 0xffffffff, + 0x00000303 + }; + static unsigned int const mode_presets[4] = { + 0x00000000, + 0x00000300, + 0xffffffff, + 0x00000300 + }; + static unsigned int const base_addr_presets[4] = { + 0x00000000, + 0x00000001, + 0xffffffff, + 0x00000001 + }; + + struct tga_par *par = (struct tga_par *) info->par; + int tga_bus_pci = dev_is_pci(par->dev); + int tga_bus_tc = TGA_BUS_TC(par->dev); + u32 htimings, vtimings, pll_freq; + u8 tga_type; + int i; + + /* Encode video timings. */ + htimings = (((info->var.xres/4) & TGA_HORIZ_ACT_LSB) + | (((info->var.xres/4) & 0x600 << 19) & TGA_HORIZ_ACT_MSB)); + vtimings = (info->var.yres & TGA_VERT_ACTIVE); + htimings |= ((info->var.right_margin/4) << 9) & TGA_HORIZ_FP; + vtimings |= (info->var.lower_margin << 11) & TGA_VERT_FP; + htimings |= ((info->var.hsync_len/4) << 14) & TGA_HORIZ_SYNC; + vtimings |= (info->var.vsync_len << 16) & TGA_VERT_SYNC; + htimings |= ((info->var.left_margin/4) << 21) & TGA_HORIZ_BP; + vtimings |= (info->var.upper_margin << 22) & TGA_VERT_BP; + + if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) + htimings |= TGA_HORIZ_POLARITY; + if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) + vtimings |= TGA_VERT_POLARITY; + + par->htimings = htimings; + par->vtimings = vtimings; + + par->sync_on_green = !!(info->var.sync & FB_SYNC_ON_GREEN); + + /* Store other useful values in par. */ + par->xres = info->var.xres; + par->yres = info->var.yres; + par->pll_freq = pll_freq = 1000000000 / info->var.pixclock; + par->bits_per_pixel = info->var.bits_per_pixel; + info->fix.line_length = par->xres * (par->bits_per_pixel >> 3); + + tga_type = par->tga_type; + + /* First, disable video. */ + TGA_WRITE_REG(par, TGA_VALID_VIDEO | TGA_VALID_BLANK, TGA_VALID_REG); + + /* Write the DEEP register. */ + while (TGA_READ_REG(par, TGA_CMD_STAT_REG) & 1) /* wait for not busy */ + continue; + mb(); + TGA_WRITE_REG(par, deep_presets[tga_type] | + (par->sync_on_green ? 0x0 : 0x00010000), + TGA_DEEP_REG); + while (TGA_READ_REG(par, TGA_CMD_STAT_REG) & 1) /* wait for not busy */ + continue; + mb(); + + /* Write some more registers. */ + TGA_WRITE_REG(par, rasterop_presets[tga_type], TGA_RASTEROP_REG); + TGA_WRITE_REG(par, mode_presets[tga_type], TGA_MODE_REG); + TGA_WRITE_REG(par, base_addr_presets[tga_type], TGA_BASE_ADDR_REG); + + /* Calculate & write the PLL. */ + tgafb_set_pll(par, pll_freq); + + /* Write some more registers. */ + TGA_WRITE_REG(par, 0xffffffff, TGA_PLANEMASK_REG); + TGA_WRITE_REG(par, 0xffffffff, TGA_PIXELMASK_REG); + + /* Init video timing regs. */ + TGA_WRITE_REG(par, htimings, TGA_HORIZ_REG); + TGA_WRITE_REG(par, vtimings, TGA_VERT_REG); + + /* Initialise RAMDAC. */ + if (tga_type == TGA_TYPE_8PLANE && tga_bus_pci) { + + /* Init BT485 RAMDAC registers. */ + BT485_WRITE(par, 0xa2 | (par->sync_on_green ? 0x8 : 0x0), + BT485_CMD_0); + BT485_WRITE(par, 0x01, BT485_ADDR_PAL_WRITE); + BT485_WRITE(par, 0x14, BT485_CMD_3); /* cursor 64x64 */ + BT485_WRITE(par, 0x40, BT485_CMD_1); + BT485_WRITE(par, 0x20, BT485_CMD_2); /* cursor off, for now */ + BT485_WRITE(par, 0xff, BT485_PIXEL_MASK); + + /* Fill palette registers. */ + BT485_WRITE(par, 0x00, BT485_ADDR_PAL_WRITE); + TGA_WRITE_REG(par, BT485_DATA_PAL, TGA_RAMDAC_SETUP_REG); + + for (i = 0; i < 256 * 3; i += 4) { + TGA_WRITE_REG(par, 0x55 | (BT485_DATA_PAL << 8), + TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00 | (BT485_DATA_PAL << 8), + TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00 | (BT485_DATA_PAL << 8), + TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00 | (BT485_DATA_PAL << 8), + TGA_RAMDAC_REG); + } + + } else if (tga_type == TGA_TYPE_8PLANE && tga_bus_tc) { + + /* Init BT459 RAMDAC registers. */ + BT459_WRITE(par, BT459_REG_ACC, BT459_CMD_REG_0, 0x40); + BT459_WRITE(par, BT459_REG_ACC, BT459_CMD_REG_1, 0x00); + BT459_WRITE(par, BT459_REG_ACC, BT459_CMD_REG_2, + (par->sync_on_green ? 0xc0 : 0x40)); + + BT459_WRITE(par, BT459_REG_ACC, BT459_CUR_CMD_REG, 0x00); + + /* Fill the palette. */ + BT459_LOAD_ADDR(par, 0x0000); + TGA_WRITE_REG(par, BT459_PALETTE << 2, TGA_RAMDAC_SETUP_REG); + + for (i = 0; i < 256 * 3; i += 4) { + TGA_WRITE_REG(par, 0x55, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + } + + } else { /* 24-plane or 24plusZ */ + + /* Init BT463 RAMDAC registers. */ + BT463_WRITE(par, BT463_REG_ACC, BT463_CMD_REG_0, 0x40); + BT463_WRITE(par, BT463_REG_ACC, BT463_CMD_REG_1, 0x08); + BT463_WRITE(par, BT463_REG_ACC, BT463_CMD_REG_2, + (par->sync_on_green ? 0xc0 : 0x40)); + + BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_0, 0xff); + BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_1, 0xff); + BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_2, 0xff); + BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_3, 0x0f); + + BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_0, 0x00); + BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_1, 0x00); + BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_2, 0x00); + BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_3, 0x00); + + /* Fill the palette. */ + BT463_LOAD_ADDR(par, 0x0000); + TGA_WRITE_REG(par, BT463_PALETTE << 2, TGA_RAMDAC_SETUP_REG); + +#ifdef CONFIG_HW_CONSOLE + for (i = 0; i < 16; i++) { + int j = color_table[i]; + + TGA_WRITE_REG(par, default_red[j], TGA_RAMDAC_REG); + TGA_WRITE_REG(par, default_grn[j], TGA_RAMDAC_REG); + TGA_WRITE_REG(par, default_blu[j], TGA_RAMDAC_REG); + } + for (i = 0; i < 512 * 3; i += 4) { +#else + for (i = 0; i < 528 * 3; i += 4) { +#endif + TGA_WRITE_REG(par, 0x55, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + } + + /* Fill window type table after start of vertical retrace. */ + while (!(TGA_READ_REG(par, TGA_INTR_STAT_REG) & 0x01)) + continue; + TGA_WRITE_REG(par, 0x01, TGA_INTR_STAT_REG); + mb(); + while (!(TGA_READ_REG(par, TGA_INTR_STAT_REG) & 0x01)) + continue; + TGA_WRITE_REG(par, 0x01, TGA_INTR_STAT_REG); + + BT463_LOAD_ADDR(par, BT463_WINDOW_TYPE_BASE); + TGA_WRITE_REG(par, BT463_REG_ACC << 2, TGA_RAMDAC_SETUP_REG); + + for (i = 0; i < 16; i++) { + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x01, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); + } + + } + + /* Finally, enable video scan (and pray for the monitor... :-) */ + TGA_WRITE_REG(par, TGA_VALID_VIDEO, TGA_VALID_REG); + + return 0; +} + +#define DIFFCHECK(X) \ +do { \ + if (m <= 0x3f) { \ + int delta = f - (TGA_PLL_BASE_FREQ * (X)) / (r << shift); \ + if (delta < 0) \ + delta = -delta; \ + if (delta < min_diff) \ + min_diff = delta, vm = m, va = a, vr = r; \ + } \ +} while (0) + +static void +tgafb_set_pll(struct tga_par *par, int f) +{ + int n, shift, base, min_diff, target; + int r,a,m,vm = 34, va = 1, vr = 30; + + for (r = 0 ; r < 12 ; r++) + TGA_WRITE_REG(par, !r, TGA_CLOCK_REG); + + if (f > TGA_PLL_MAX_FREQ) + f = TGA_PLL_MAX_FREQ; + + if (f >= TGA_PLL_MAX_FREQ / 2) + shift = 0; + else if (f >= TGA_PLL_MAX_FREQ / 4) + shift = 1; + else + shift = 2; + + TGA_WRITE_REG(par, shift & 1, TGA_CLOCK_REG); + TGA_WRITE_REG(par, shift >> 1, TGA_CLOCK_REG); + + for (r = 0 ; r < 10 ; r++) + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + + if (f <= 120000) { + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + } + else if (f <= 200000) { + TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + } + else { + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); + } + + TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); + TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); + + target = (f << shift) / TGA_PLL_BASE_FREQ; + min_diff = TGA_PLL_MAX_FREQ; + + r = 7 / target; + if (!r) r = 1; + + base = target * r; + while (base < 449) { + for (n = base < 7 ? 7 : base; n < base + target && n < 449; n++) { + m = ((n + 3) / 7) - 1; + a = 0; + DIFFCHECK((m + 1) * 7); + m++; + DIFFCHECK((m + 1) * 7); + m = (n / 6) - 1; + if ((a = n % 6)) + DIFFCHECK(n); + } + r++; + base += target; + } + + vr--; + + for (r = 0; r < 8; r++) + TGA_WRITE_REG(par, (vm >> r) & 1, TGA_CLOCK_REG); + for (r = 0; r < 8 ; r++) + TGA_WRITE_REG(par, (va >> r) & 1, TGA_CLOCK_REG); + for (r = 0; r < 7 ; r++) + TGA_WRITE_REG(par, (vr >> r) & 1, TGA_CLOCK_REG); + TGA_WRITE_REG(par, ((vr >> 7) & 1)|2, TGA_CLOCK_REG); +} + + +/** + * tgafb_setcolreg - Optional function. Sets a color register. + * @regno: boolean, 0 copy local, 1 get_user() function + * @red: frame buffer colormap structure + * @green: The green value which can be up to 16 bits wide + * @blue: The blue value which can be up to 16 bits wide. + * @transp: If supported the alpha value which can be up to 16 bits wide. + * @info: frame buffer info structure + */ +static int +tgafb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct tga_par *par = (struct tga_par *) info->par; + int tga_bus_pci = dev_is_pci(par->dev); + int tga_bus_tc = TGA_BUS_TC(par->dev); + + if (regno > 255) + return 1; + red >>= 8; + green >>= 8; + blue >>= 8; + + if (par->tga_type == TGA_TYPE_8PLANE && tga_bus_pci) { + BT485_WRITE(par, regno, BT485_ADDR_PAL_WRITE); + TGA_WRITE_REG(par, BT485_DATA_PAL, TGA_RAMDAC_SETUP_REG); + TGA_WRITE_REG(par, red|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); + TGA_WRITE_REG(par, green|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); + TGA_WRITE_REG(par, blue|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); + } else if (par->tga_type == TGA_TYPE_8PLANE && tga_bus_tc) { + BT459_LOAD_ADDR(par, regno); + TGA_WRITE_REG(par, BT459_PALETTE << 2, TGA_RAMDAC_SETUP_REG); + TGA_WRITE_REG(par, red, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, green, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, blue, TGA_RAMDAC_REG); + } else { + if (regno < 16) { + u32 value = (regno << 16) | (regno << 8) | regno; + ((u32 *)info->pseudo_palette)[regno] = value; + } + BT463_LOAD_ADDR(par, regno); + TGA_WRITE_REG(par, BT463_PALETTE << 2, TGA_RAMDAC_SETUP_REG); + TGA_WRITE_REG(par, red, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, green, TGA_RAMDAC_REG); + TGA_WRITE_REG(par, blue, TGA_RAMDAC_REG); + } + + return 0; +} + + +/** + * tgafb_blank - Optional function. Blanks the display. + * @blank_mode: the blank mode we want. + * @info: frame buffer structure that represents a single frame buffer + */ +static int +tgafb_blank(int blank, struct fb_info *info) +{ + struct tga_par *par = (struct tga_par *) info->par; + u32 vhcr, vvcr, vvvr; + unsigned long flags; + + local_irq_save(flags); + + vhcr = TGA_READ_REG(par, TGA_HORIZ_REG); + vvcr = TGA_READ_REG(par, TGA_VERT_REG); + vvvr = TGA_READ_REG(par, TGA_VALID_REG); + vvvr &= ~(TGA_VALID_VIDEO | TGA_VALID_BLANK); + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblanking */ + if (par->vesa_blanked) { + TGA_WRITE_REG(par, vhcr & 0xbfffffff, TGA_HORIZ_REG); + TGA_WRITE_REG(par, vvcr & 0xbfffffff, TGA_VERT_REG); + par->vesa_blanked = 0; + } + TGA_WRITE_REG(par, vvvr | TGA_VALID_VIDEO, TGA_VALID_REG); + break; + + case FB_BLANK_NORMAL: /* Normal blanking */ + TGA_WRITE_REG(par, vvvr | TGA_VALID_VIDEO | TGA_VALID_BLANK, + TGA_VALID_REG); + break; + + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + TGA_WRITE_REG(par, vvcr | 0x40000000, TGA_VERT_REG); + TGA_WRITE_REG(par, vvvr | TGA_VALID_BLANK, TGA_VALID_REG); + par->vesa_blanked = 1; + break; + + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + TGA_WRITE_REG(par, vhcr | 0x40000000, TGA_HORIZ_REG); + TGA_WRITE_REG(par, vvvr | TGA_VALID_BLANK, TGA_VALID_REG); + par->vesa_blanked = 1; + break; + + case FB_BLANK_POWERDOWN: /* Poweroff */ + TGA_WRITE_REG(par, vhcr | 0x40000000, TGA_HORIZ_REG); + TGA_WRITE_REG(par, vvcr | 0x40000000, TGA_VERT_REG); + TGA_WRITE_REG(par, vvvr | TGA_VALID_BLANK, TGA_VALID_REG); + par->vesa_blanked = 1; + break; + } + + local_irq_restore(flags); + return 0; +} + + +/* + * Acceleration. + */ + +static void +tgafb_mono_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct tga_par *par = (struct tga_par *) info->par; + u32 fgcolor, bgcolor, dx, dy, width, height, vxres, vyres, pixelmask; + unsigned long rincr, line_length, shift, pos, is8bpp; + unsigned long i, j; + const unsigned char *data; + void __iomem *regs_base; + void __iomem *fb_base; + + is8bpp = info->var.bits_per_pixel == 8; + + dx = image->dx; + dy = image->dy; + width = image->width; + height = image->height; + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + line_length = info->fix.line_length; + rincr = (width + 7) / 8; + + /* A shift below cannot cope with. */ + if (unlikely(width == 0)) + return; + /* Crop the image to the screen. */ + if (dx > vxres || dy > vyres) + return; + if (dx + width > vxres) + width = vxres - dx; + if (dy + height > vyres) + height = vyres - dy; + + regs_base = par->tga_regs_base; + fb_base = par->tga_fb_base; + + /* Expand the color values to fill 32-bits. */ + /* ??? Would be nice to notice colour changes elsewhere, so + that we can do this only when necessary. */ + fgcolor = image->fg_color; + bgcolor = image->bg_color; + if (is8bpp) { + fgcolor |= fgcolor << 8; + fgcolor |= fgcolor << 16; + bgcolor |= bgcolor << 8; + bgcolor |= bgcolor << 16; + } else { + if (fgcolor < 16) + fgcolor = ((u32 *)info->pseudo_palette)[fgcolor]; + if (bgcolor < 16) + bgcolor = ((u32 *)info->pseudo_palette)[bgcolor]; + } + __raw_writel(fgcolor, regs_base + TGA_FOREGROUND_REG); + __raw_writel(bgcolor, regs_base + TGA_BACKGROUND_REG); + + /* Acquire proper alignment; set up the PIXELMASK register + so that we only write the proper character cell. */ + pos = dy * line_length; + if (is8bpp) { + pos += dx; + shift = pos & 3; + pos &= -4; + } else { + pos += dx * 4; + shift = (pos & 7) >> 2; + pos &= -8; + } + + data = (const unsigned char *) image->data; + + /* Enable opaque stipple mode. */ + __raw_writel((is8bpp + ? TGA_MODE_SBM_8BPP | TGA_MODE_OPAQUE_STIPPLE + : TGA_MODE_SBM_24BPP | TGA_MODE_OPAQUE_STIPPLE), + regs_base + TGA_MODE_REG); + + if (width + shift <= 32) { + unsigned long bwidth; + + /* Handle common case of imaging a single character, in + a font less than or 32 pixels wide. */ + + /* Avoid a shift by 32; width > 0 implied. */ + pixelmask = (2ul << (width - 1)) - 1; + pixelmask <<= shift; + __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); + wmb(); + + bwidth = (width + 7) / 8; + + for (i = 0; i < height; ++i) { + u32 mask = 0; + + /* The image data is bit big endian; we need + little endian. */ + for (j = 0; j < bwidth; ++j) + mask |= bitrev8(data[j]) << (j * 8); + + __raw_writel(mask << shift, fb_base + pos); + + pos += line_length; + data += rincr; + } + wmb(); + __raw_writel(0xffffffff, regs_base + TGA_PIXELMASK_REG); + } else if (shift == 0) { + unsigned long pos0 = pos; + const unsigned char *data0 = data; + unsigned long bincr = (is8bpp ? 8 : 8*4); + unsigned long bwidth; + + /* Handle another common case in which accel_putcs + generates a large bitmap, which happens to be aligned. + Allow the tail to be misaligned. This case is + interesting because we've not got to hold partial + bytes across the words being written. */ + + wmb(); + + bwidth = (width / 8) & -4; + for (i = 0; i < height; ++i) { + for (j = 0; j < bwidth; j += 4) { + u32 mask = 0; + mask |= bitrev8(data[j+0]) << (0 * 8); + mask |= bitrev8(data[j+1]) << (1 * 8); + mask |= bitrev8(data[j+2]) << (2 * 8); + mask |= bitrev8(data[j+3]) << (3 * 8); + __raw_writel(mask, fb_base + pos + j*bincr); + } + pos += line_length; + data += rincr; + } + wmb(); + + pixelmask = (1ul << (width & 31)) - 1; + if (pixelmask) { + __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); + wmb(); + + pos = pos0 + bwidth*bincr; + data = data0 + bwidth; + bwidth = ((width & 31) + 7) / 8; + + for (i = 0; i < height; ++i) { + u32 mask = 0; + for (j = 0; j < bwidth; ++j) + mask |= bitrev8(data[j]) << (j * 8); + __raw_writel(mask, fb_base + pos); + pos += line_length; + data += rincr; + } + wmb(); + __raw_writel(0xffffffff, regs_base + TGA_PIXELMASK_REG); + } + } else { + unsigned long pos0 = pos; + const unsigned char *data0 = data; + unsigned long bincr = (is8bpp ? 8 : 8*4); + unsigned long bwidth; + + /* Finally, handle the generic case of misaligned start. + Here we split the write into 16-bit spans. This allows + us to use only one pixel mask, instead of four as would + be required by writing 24-bit spans. */ + + pixelmask = 0xffff << shift; + __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); + wmb(); + + bwidth = (width / 8) & -2; + for (i = 0; i < height; ++i) { + for (j = 0; j < bwidth; j += 2) { + u32 mask = 0; + mask |= bitrev8(data[j+0]) << (0 * 8); + mask |= bitrev8(data[j+1]) << (1 * 8); + mask <<= shift; + __raw_writel(mask, fb_base + pos + j*bincr); + } + pos += line_length; + data += rincr; + } + wmb(); + + pixelmask = ((1ul << (width & 15)) - 1) << shift; + if (pixelmask) { + __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); + wmb(); + + pos = pos0 + bwidth*bincr; + data = data0 + bwidth; + bwidth = (width & 15) > 8; + + for (i = 0; i < height; ++i) { + u32 mask = bitrev8(data[0]); + if (bwidth) + mask |= bitrev8(data[1]) << 8; + mask <<= shift; + __raw_writel(mask, fb_base + pos); + pos += line_length; + data += rincr; + } + wmb(); + } + __raw_writel(0xffffffff, regs_base + TGA_PIXELMASK_REG); + } + + /* Disable opaque stipple mode. */ + __raw_writel((is8bpp + ? TGA_MODE_SBM_8BPP | TGA_MODE_SIMPLE + : TGA_MODE_SBM_24BPP | TGA_MODE_SIMPLE), + regs_base + TGA_MODE_REG); +} + +static void +tgafb_clut_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct tga_par *par = (struct tga_par *) info->par; + u32 color, dx, dy, width, height, vxres, vyres; + u32 *palette = ((u32 *)info->pseudo_palette); + unsigned long pos, line_length, i, j; + const unsigned char *data; + void __iomem *regs_base, *fb_base; + + dx = image->dx; + dy = image->dy; + width = image->width; + height = image->height; + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + line_length = info->fix.line_length; + + /* Crop the image to the screen. */ + if (dx > vxres || dy > vyres) + return; + if (dx + width > vxres) + width = vxres - dx; + if (dy + height > vyres) + height = vyres - dy; + + regs_base = par->tga_regs_base; + fb_base = par->tga_fb_base; + + pos = dy * line_length + (dx * 4); + data = image->data; + + /* Now copy the image, color_expanding via the palette. */ + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { + color = palette[*data++]; + __raw_writel(color, fb_base + pos + j*4); + } + pos += line_length; + } +} + +/** + * tgafb_imageblit - REQUIRED function. Can use generic routines if + * non acclerated hardware and packed pixel based. + * Copies a image from system memory to the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @image: structure defining the image. + */ +static void +tgafb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + unsigned int is8bpp = info->var.bits_per_pixel == 8; + + /* If a mono image, regardless of FB depth, go do it. */ + if (image->depth == 1) { + tgafb_mono_imageblit(info, image); + return; + } + + /* For copies that aren't pixel expansion, there's little we + can do better than the generic code. */ + /* ??? There is a DMA write mode; I wonder if that could be + made to pull the data from the image buffer... */ + if (image->depth == info->var.bits_per_pixel) { + cfb_imageblit(info, image); + return; + } + + /* If 24-plane FB and the image is 8-plane with CLUT, we can do it. */ + if (!is8bpp && image->depth == 8) { + tgafb_clut_imageblit(info, image); + return; + } + + /* Silently return... */ +} + +/** + * tgafb_fillrect - REQUIRED function. Can use generic routines if + * non acclerated hardware and packed pixel based. + * Draws a rectangle on the screen. + * + * @info: frame buffer structure that represents a single frame buffer + * @rect: structure defining the rectagle and operation. + */ +static void +tgafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct tga_par *par = (struct tga_par *) info->par; + int is8bpp = info->var.bits_per_pixel == 8; + u32 dx, dy, width, height, vxres, vyres, color; + unsigned long pos, align, line_length, i, j; + void __iomem *regs_base; + void __iomem *fb_base; + + dx = rect->dx; + dy = rect->dy; + width = rect->width; + height = rect->height; + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + line_length = info->fix.line_length; + regs_base = par->tga_regs_base; + fb_base = par->tga_fb_base; + + /* Crop the rectangle to the screen. */ + if (dx > vxres || dy > vyres || !width || !height) + return; + if (dx + width > vxres) + width = vxres - dx; + if (dy + height > vyres) + height = vyres - dy; + + pos = dy * line_length + dx * (is8bpp ? 1 : 4); + + /* ??? We could implement ROP_XOR with opaque fill mode + and a RasterOp setting of GXxor, but as far as I can + tell, this mode is not actually used in the kernel. + Thus I am ignoring it for now. */ + if (rect->rop != ROP_COPY) { + cfb_fillrect(info, rect); + return; + } + + /* Expand the color value to fill 8 pixels. */ + color = rect->color; + if (is8bpp) { + color |= color << 8; + color |= color << 16; + __raw_writel(color, regs_base + TGA_BLOCK_COLOR0_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR1_REG); + } else { + if (color < 16) + color = ((u32 *)info->pseudo_palette)[color]; + __raw_writel(color, regs_base + TGA_BLOCK_COLOR0_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR1_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR2_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR3_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR4_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR5_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR6_REG); + __raw_writel(color, regs_base + TGA_BLOCK_COLOR7_REG); + } + + /* The DATA register holds the fill mask for block fill mode. + Since we're not stippling, this is all ones. */ + __raw_writel(0xffffffff, regs_base + TGA_DATA_REG); + + /* Enable block fill mode. */ + __raw_writel((is8bpp + ? TGA_MODE_SBM_8BPP | TGA_MODE_BLOCK_FILL + : TGA_MODE_SBM_24BPP | TGA_MODE_BLOCK_FILL), + regs_base + TGA_MODE_REG); + wmb(); + + /* We can fill 2k pixels per operation. Notice blocks that fit + the width of the screen so that we can take advantage of this + and fill more than one line per write. */ + if (width == line_length) + width *= height, height = 1; + + /* The write into the frame buffer must be aligned to 4 bytes, + but we are allowed to encode the offset within the word in + the data word written. */ + align = (pos & 3) << 16; + pos &= -4; + + if (width <= 2048) { + u32 data; + + data = (width - 1) | align; + + for (i = 0; i < height; ++i) { + __raw_writel(data, fb_base + pos); + pos += line_length; + } + } else { + unsigned long Bpp = (is8bpp ? 1 : 4); + unsigned long nwidth = width & -2048; + u32 fdata, ldata; + + fdata = (2048 - 1) | align; + ldata = ((width & 2047) - 1) | align; + + for (i = 0; i < height; ++i) { + for (j = 0; j < nwidth; j += 2048) + __raw_writel(fdata, fb_base + pos + j*Bpp); + if (j < width) + __raw_writel(ldata, fb_base + pos + j*Bpp); + pos += line_length; + } + } + wmb(); + + /* Disable block fill mode. */ + __raw_writel((is8bpp + ? TGA_MODE_SBM_8BPP | TGA_MODE_SIMPLE + : TGA_MODE_SBM_24BPP | TGA_MODE_SIMPLE), + regs_base + TGA_MODE_REG); +} + +/** + * tgafb_copyarea - REQUIRED function. Can use generic routines if + * non acclerated hardware and packed pixel based. + * Copies on area of the screen to another area. + * + * @info: frame buffer structure that represents a single frame buffer + * @area: structure defining the source and destination. + */ + +/* Handle the special case of copying entire lines, e.g. during scrolling. + We can avoid a lot of needless computation in this case. In the 8bpp + case we need to use the COPY64 registers instead of mask writes into + the frame buffer to achieve maximum performance. */ + +static inline void +copyarea_line_8bpp(struct fb_info *info, u32 dy, u32 sy, + u32 height, u32 width) +{ + struct tga_par *par = (struct tga_par *) info->par; + void __iomem *tga_regs = par->tga_regs_base; + unsigned long dpos, spos, i, n64; + + /* Set up the MODE and PIXELSHIFT registers. */ + __raw_writel(TGA_MODE_SBM_8BPP | TGA_MODE_COPY, tga_regs+TGA_MODE_REG); + __raw_writel(0, tga_regs+TGA_PIXELSHIFT_REG); + wmb(); + + n64 = (height * width) / 64; + + if (sy < dy) { + spos = (sy + height) * width; + dpos = (dy + height) * width; + + for (i = 0; i < n64; ++i) { + spos -= 64; + dpos -= 64; + __raw_writel(spos, tga_regs+TGA_COPY64_SRC); + wmb(); + __raw_writel(dpos, tga_regs+TGA_COPY64_DST); + wmb(); + } + } else { + spos = sy * width; + dpos = dy * width; + + for (i = 0; i < n64; ++i) { + __raw_writel(spos, tga_regs+TGA_COPY64_SRC); + wmb(); + __raw_writel(dpos, tga_regs+TGA_COPY64_DST); + wmb(); + spos += 64; + dpos += 64; + } + } + + /* Reset the MODE register to normal. */ + __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG); +} + +static inline void +copyarea_line_32bpp(struct fb_info *info, u32 dy, u32 sy, + u32 height, u32 width) +{ + struct tga_par *par = (struct tga_par *) info->par; + void __iomem *tga_regs = par->tga_regs_base; + void __iomem *tga_fb = par->tga_fb_base; + void __iomem *src; + void __iomem *dst; + unsigned long i, n16; + + /* Set up the MODE and PIXELSHIFT registers. */ + __raw_writel(TGA_MODE_SBM_24BPP | TGA_MODE_COPY, tga_regs+TGA_MODE_REG); + __raw_writel(0, tga_regs+TGA_PIXELSHIFT_REG); + wmb(); + + n16 = (height * width) / 16; + + if (sy < dy) { + src = tga_fb + (sy + height) * width * 4; + dst = tga_fb + (dy + height) * width * 4; + + for (i = 0; i < n16; ++i) { + src -= 64; + dst -= 64; + __raw_writel(0xffff, src); + wmb(); + __raw_writel(0xffff, dst); + wmb(); + } + } else { + src = tga_fb + sy * width * 4; + dst = tga_fb + dy * width * 4; + + for (i = 0; i < n16; ++i) { + __raw_writel(0xffff, src); + wmb(); + __raw_writel(0xffff, dst); + wmb(); + src += 64; + dst += 64; + } + } + + /* Reset the MODE register to normal. */ + __raw_writel(TGA_MODE_SBM_24BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG); +} + +/* The (almost) general case of backward copy in 8bpp mode. */ +static inline void +copyarea_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy, + u32 height, u32 width, u32 line_length, + const struct fb_copyarea *area) +{ + struct tga_par *par = (struct tga_par *) info->par; + unsigned i, yincr; + int depos, sepos, backward, last_step, step; + u32 mask_last; + unsigned n32; + void __iomem *tga_regs; + void __iomem *tga_fb; + + /* Do acceleration only if we are aligned on 8 pixels */ + if ((dx | sx | width) & 7) { + cfb_copyarea(info, area); + return; + } + + yincr = line_length; + if (dy > sy) { + dy += height - 1; + sy += height - 1; + yincr = -yincr; + } + backward = dy == sy && dx > sx && dx < sx + width; + + /* Compute the offsets and alignments in the frame buffer. + More than anything else, these control how we do copies. */ + depos = dy * line_length + dx; + sepos = sy * line_length + sx; + if (backward) + depos += width, sepos += width; + + /* Next copy full words at a time. */ + n32 = width / 32; + last_step = width % 32; + + /* Finally copy the unaligned head of the span. */ + mask_last = (1ul << last_step) - 1; + + if (!backward) { + step = 32; + last_step = 32; + } else { + step = -32; + last_step = -last_step; + sepos -= 32; + depos -= 32; + } + + tga_regs = par->tga_regs_base; + tga_fb = par->tga_fb_base; + + /* Set up the MODE and PIXELSHIFT registers. */ + __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_COPY, tga_regs+TGA_MODE_REG); + __raw_writel(0, tga_regs+TGA_PIXELSHIFT_REG); + wmb(); + + for (i = 0; i < height; ++i) { + unsigned long j; + void __iomem *sfb; + void __iomem *dfb; + + sfb = tga_fb + sepos; + dfb = tga_fb + depos; + + for (j = 0; j < n32; j++) { + if (j < 2 && j + 1 < n32 && !backward && + !(((unsigned long)sfb | (unsigned long)dfb) & 63)) { + do { + __raw_writel(sfb - tga_fb, tga_regs+TGA_COPY64_SRC); + wmb(); + __raw_writel(dfb - tga_fb, tga_regs+TGA_COPY64_DST); + wmb(); + sfb += 64; + dfb += 64; + j += 2; + } while (j + 1 < n32); + j--; + continue; + } + __raw_writel(0xffffffff, sfb); + wmb(); + __raw_writel(0xffffffff, dfb); + wmb(); + sfb += step; + dfb += step; + } + + if (mask_last) { + sfb += last_step - step; + dfb += last_step - step; + __raw_writel(mask_last, sfb); + wmb(); + __raw_writel(mask_last, dfb); + wmb(); + } + + sepos += yincr; + depos += yincr; + } + + /* Reset the MODE register to normal. */ + __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG); +} + +static void +tgafb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + unsigned long dx, dy, width, height, sx, sy, vxres, vyres; + unsigned long line_length, bpp; + + dx = area->dx; + dy = area->dy; + width = area->width; + height = area->height; + sx = area->sx; + sy = area->sy; + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + line_length = info->fix.line_length; + + /* The top left corners must be in the virtual screen. */ + if (dx > vxres || sx > vxres || dy > vyres || sy > vyres) + return; + + /* Clip the destination. */ + if (dx + width > vxres) + width = vxres - dx; + if (dy + height > vyres) + height = vyres - dy; + + /* The source must be completely inside the virtual screen. */ + if (sx + width > vxres || sy + height > vyres) + return; + + bpp = info->var.bits_per_pixel; + + /* Detect copies of the entire line. */ + if (!(line_length & 63) && width * (bpp >> 3) == line_length) { + if (bpp == 8) + copyarea_line_8bpp(info, dy, sy, height, width); + else + copyarea_line_32bpp(info, dy, sy, height, width); + } + + /* ??? The documentation is unclear to me exactly how the pixelshift + register works in 32bpp mode. Since I don't have hardware to test, + give up for now and fall back on the generic routines. */ + else if (bpp == 32) + cfb_copyarea(info, area); + + else + copyarea_8bpp(info, dx, dy, sx, sy, height, + width, line_length, area); +} + + +/* + * Initialisation + */ + +static void +tgafb_init_fix(struct fb_info *info) +{ + struct tga_par *par = (struct tga_par *)info->par; + int tga_bus_pci = dev_is_pci(par->dev); + int tga_bus_tc = TGA_BUS_TC(par->dev); + u8 tga_type = par->tga_type; + const char *tga_type_name = NULL; + unsigned memory_size; + + switch (tga_type) { + case TGA_TYPE_8PLANE: + if (tga_bus_pci) + tga_type_name = "Digital ZLXp-E1"; + if (tga_bus_tc) + tga_type_name = "Digital ZLX-E1"; + memory_size = 2097152; + break; + case TGA_TYPE_24PLANE: + if (tga_bus_pci) + tga_type_name = "Digital ZLXp-E2"; + if (tga_bus_tc) + tga_type_name = "Digital ZLX-E2"; + memory_size = 8388608; + break; + case TGA_TYPE_24PLUSZ: + if (tga_bus_pci) + tga_type_name = "Digital ZLXp-E3"; + if (tga_bus_tc) + tga_type_name = "Digital ZLX-E3"; + memory_size = 16777216; + break; + } + if (!tga_type_name) { + tga_type_name = "Unknown"; + memory_size = 16777216; + } + + strlcpy(info->fix.id, tga_type_name, sizeof(info->fix.id)); + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.visual = (tga_type == TGA_TYPE_8PLANE + ? FB_VISUAL_PSEUDOCOLOR + : FB_VISUAL_DIRECTCOLOR); + + info->fix.smem_start = (size_t) par->tga_fb_base; + info->fix.smem_len = memory_size; + info->fix.mmio_start = (size_t) par->tga_regs_base; + info->fix.mmio_len = 512; + + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + + info->fix.accel = FB_ACCEL_DEC_TGA; + + /* + * These are needed by fb_set_logo_truepalette(), so we + * set them here for 24-plane cards. + */ + if (tga_type != TGA_TYPE_8PLANE) { + info->var.red.length = 8; + info->var.green.length = 8; + info->var.blue.length = 8; + info->var.red.offset = 16; + info->var.green.offset = 8; + info->var.blue.offset = 0; + } +} + +static int tgafb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + /* We just use this to catch switches out of graphics mode. */ + tgafb_set_par(info); /* A bit of overkill for BASE_ADDR reset. */ + return 0; +} + +static int tgafb_register(struct device *dev) +{ + static const struct fb_videomode modedb_tc = { + /* 1280x1024 @ 72 Hz, 76.8 kHz hsync */ + "1280x1024@72", 0, 1280, 1024, 7645, 224, 28, 33, 3, 160, 3, + FB_SYNC_ON_GREEN, FB_VMODE_NONINTERLACED + }; + + static unsigned int const fb_offset_presets[4] = { + TGA_8PLANE_FB_OFFSET, + TGA_24PLANE_FB_OFFSET, + 0xffffffff, + TGA_24PLUSZ_FB_OFFSET + }; + + const struct fb_videomode *modedb_tga = NULL; + resource_size_t bar0_start = 0, bar0_len = 0; + const char *mode_option_tga = NULL; + int tga_bus_pci = dev_is_pci(dev); + int tga_bus_tc = TGA_BUS_TC(dev); + unsigned int modedbsize_tga = 0; + void __iomem *mem_base; + struct fb_info *info; + struct tga_par *par; + u8 tga_type; + int ret = 0; + + /* Enable device in PCI config. */ + if (tga_bus_pci && pci_enable_device(to_pci_dev(dev))) { + printk(KERN_ERR "tgafb: Cannot enable PCI device\n"); + return -ENODEV; + } + + /* Allocate the fb and par structures. */ + info = framebuffer_alloc(sizeof(struct tga_par), dev); + if (!info) { + printk(KERN_ERR "tgafb: Cannot allocate memory\n"); + return -ENOMEM; + } + + par = info->par; + dev_set_drvdata(dev, info); + + /* Request the mem regions. */ + ret = -ENODEV; + if (tga_bus_pci) { + bar0_start = pci_resource_start(to_pci_dev(dev), 0); + bar0_len = pci_resource_len(to_pci_dev(dev), 0); + } + if (tga_bus_tc) { + bar0_start = to_tc_dev(dev)->resource.start; + bar0_len = to_tc_dev(dev)->resource.end - bar0_start + 1; + } + if (!request_mem_region (bar0_start, bar0_len, "tgafb")) { + printk(KERN_ERR "tgafb: cannot reserve FB region\n"); + goto err0; + } + + /* Map the framebuffer. */ + mem_base = ioremap_nocache(bar0_start, bar0_len); + if (!mem_base) { + printk(KERN_ERR "tgafb: Cannot map MMIO\n"); + goto err1; + } + + /* Grab info about the card. */ + tga_type = (readl(mem_base) >> 12) & 0x0f; + par->dev = dev; + par->tga_mem_base = mem_base; + par->tga_fb_base = mem_base + fb_offset_presets[tga_type]; + par->tga_regs_base = mem_base + TGA_REGS_OFFSET; + par->tga_type = tga_type; + if (tga_bus_pci) + par->tga_chip_rev = (to_pci_dev(dev))->revision; + if (tga_bus_tc) + par->tga_chip_rev = TGA_READ_REG(par, TGA_START_REG) & 0xff; + + /* Setup framebuffer. */ + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT; + info->fbops = &tgafb_ops; + info->screen_base = par->tga_fb_base; + info->pseudo_palette = par->palette; + + /* This should give a reasonable default video mode. */ + if (tga_bus_pci) { + mode_option_tga = mode_option_pci; + } + if (tga_bus_tc) { + mode_option_tga = mode_option_tc; + modedb_tga = &modedb_tc; + modedbsize_tga = 1; + } + + tgafb_init_fix(info); + + ret = fb_find_mode(&info->var, info, + mode_option ? mode_option : mode_option_tga, + modedb_tga, modedbsize_tga, NULL, + tga_type == TGA_TYPE_8PLANE ? 8 : 32); + if (ret == 0 || ret == 4) { + printk(KERN_ERR "tgafb: Could not find valid video mode\n"); + ret = -EINVAL; + goto err1; + } + + if (fb_alloc_cmap(&info->cmap, 256, 0)) { + printk(KERN_ERR "tgafb: Could not allocate color map\n"); + ret = -ENOMEM; + goto err1; + } + + tgafb_set_par(info); + + if (register_framebuffer(info) < 0) { + printk(KERN_ERR "tgafb: Could not register framebuffer\n"); + ret = -EINVAL; + goto err2; + } + + if (tga_bus_pci) { + pr_info("tgafb: DC21030 [TGA] detected, rev=0x%02x\n", + par->tga_chip_rev); + pr_info("tgafb: at PCI bus %d, device %d, function %d\n", + to_pci_dev(dev)->bus->number, + PCI_SLOT(to_pci_dev(dev)->devfn), + PCI_FUNC(to_pci_dev(dev)->devfn)); + } + if (tga_bus_tc) + pr_info("tgafb: SFB+ detected, rev=0x%02x\n", + par->tga_chip_rev); + fb_info(info, "%s frame buffer device at 0x%lx\n", + info->fix.id, (long)bar0_start); + + return 0; + + err2: + fb_dealloc_cmap(&info->cmap); + err1: + if (mem_base) + iounmap(mem_base); + release_mem_region(bar0_start, bar0_len); + err0: + framebuffer_release(info); + return ret; +} + +static void tgafb_unregister(struct device *dev) +{ + resource_size_t bar0_start = 0, bar0_len = 0; + int tga_bus_pci = dev_is_pci(dev); + int tga_bus_tc = TGA_BUS_TC(dev); + struct fb_info *info = NULL; + struct tga_par *par; + + info = dev_get_drvdata(dev); + if (!info) + return; + + par = info->par; + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + iounmap(par->tga_mem_base); + if (tga_bus_pci) { + bar0_start = pci_resource_start(to_pci_dev(dev), 0); + bar0_len = pci_resource_len(to_pci_dev(dev), 0); + } + if (tga_bus_tc) { + bar0_start = to_tc_dev(dev)->resource.start; + bar0_len = to_tc_dev(dev)->resource.end - bar0_start + 1; + } + release_mem_region(bar0_start, bar0_len); + framebuffer_release(info); +} + +static void tgafb_exit(void) +{ + tc_unregister_driver(&tgafb_tc_driver); + pci_unregister_driver(&tgafb_pci_driver); +} + +#ifndef MODULE +static int tgafb_setup(char *arg) +{ + char *this_opt; + + if (arg && *arg) { + while ((this_opt = strsep(&arg, ","))) { + if (!*this_opt) + continue; + if (!strncmp(this_opt, "mode:", 5)) + mode_option = this_opt+5; + else + printk(KERN_ERR + "tgafb: unknown parameter %s\n", + this_opt); + } + } + + return 0; +} +#endif /* !MODULE */ + +static int tgafb_init(void) +{ + int status; +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("tgafb", &option)) + return -ENODEV; + tgafb_setup(option); +#endif + status = pci_register_driver(&tgafb_pci_driver); + if (!status) + status = tc_register_driver(&tgafb_tc_driver); + return status; +} + +/* + * Modularisation + */ + +module_init(tgafb_init); +module_exit(tgafb_exit); + +MODULE_DESCRIPTION("Framebuffer driver for TGA/SFB+ chipset"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/tmiofb.c b/drivers/video/fbdev/tmiofb.c new file mode 100644 index 000000000000..7fb4e321a431 --- /dev/null +++ b/drivers/video/fbdev/tmiofb.c @@ -0,0 +1,1048 @@ +/* + * Frame Buffer Device for Toshiba Mobile IO(TMIO) controller + * + * Copyright(C) 2005-2006 Chris Humbert + * Copyright(C) 2005 Dirk Opfer + * Copytight(C) 2007,2008 Dmitry Baryshkov + * + * Based on: + * drivers/video/w100fb.c + * code written by Sharp/Lineo for 2.4 kernels + * + * 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; + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +/* Why should fb driver call console functions? because console_lock() */ +#include <linux/console.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/uaccess.h> + +/* + * accelerator commands + */ +#define TMIOFB_ACC_CSADR(x) (0x00000000 | ((x) & 0x001ffffe)) +#define TMIOFB_ACC_CHPIX(x) (0x01000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_CVPIX(x) (0x02000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_PSADR(x) (0x03000000 | ((x) & 0x00fffffe)) +#define TMIOFB_ACC_PHPIX(x) (0x04000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_PVPIX(x) (0x05000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_PHOFS(x) (0x06000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_PVOFS(x) (0x07000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_POADR(x) (0x08000000 | ((x) & 0x00fffffe)) +#define TMIOFB_ACC_RSTR(x) (0x09000000 | ((x) & 0x000000ff)) +#define TMIOFB_ACC_TCLOR(x) (0x0A000000 | ((x) & 0x0000ffff)) +#define TMIOFB_ACC_FILL(x) (0x0B000000 | ((x) & 0x0000ffff)) +#define TMIOFB_ACC_DSADR(x) (0x0C000000 | ((x) & 0x00fffffe)) +#define TMIOFB_ACC_SSADR(x) (0x0D000000 | ((x) & 0x00fffffe)) +#define TMIOFB_ACC_DHPIX(x) (0x0E000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_DVPIX(x) (0x0F000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_SHPIX(x) (0x10000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_SVPIX(x) (0x11000000 | ((x) & 0x000003ff)) +#define TMIOFB_ACC_LBINI(x) (0x12000000 | ((x) & 0x0000ffff)) +#define TMIOFB_ACC_LBK2(x) (0x13000000 | ((x) & 0x0000ffff)) +#define TMIOFB_ACC_SHBINI(x) (0x14000000 | ((x) & 0x0000ffff)) +#define TMIOFB_ACC_SHBK2(x) (0x15000000 | ((x) & 0x0000ffff)) +#define TMIOFB_ACC_SVBINI(x) (0x16000000 | ((x) & 0x0000ffff)) +#define TMIOFB_ACC_SVBK2(x) (0x17000000 | ((x) & 0x0000ffff)) + +#define TMIOFB_ACC_CMGO 0x20000000 +#define TMIOFB_ACC_CMGO_CEND 0x00000001 +#define TMIOFB_ACC_CMGO_INT 0x00000002 +#define TMIOFB_ACC_CMGO_CMOD 0x00000010 +#define TMIOFB_ACC_CMGO_CDVRV 0x00000020 +#define TMIOFB_ACC_CMGO_CDHRV 0x00000040 +#define TMIOFB_ACC_CMGO_RUND 0x00008000 +#define TMIOFB_ACC_SCGO 0x21000000 +#define TMIOFB_ACC_SCGO_CEND 0x00000001 +#define TMIOFB_ACC_SCGO_INT 0x00000002 +#define TMIOFB_ACC_SCGO_ROP3 0x00000004 +#define TMIOFB_ACC_SCGO_TRNS 0x00000008 +#define TMIOFB_ACC_SCGO_DVRV 0x00000010 +#define TMIOFB_ACC_SCGO_DHRV 0x00000020 +#define TMIOFB_ACC_SCGO_SVRV 0x00000040 +#define TMIOFB_ACC_SCGO_SHRV 0x00000080 +#define TMIOFB_ACC_SCGO_DSTXY 0x00008000 +#define TMIOFB_ACC_SBGO 0x22000000 +#define TMIOFB_ACC_SBGO_CEND 0x00000001 +#define TMIOFB_ACC_SBGO_INT 0x00000002 +#define TMIOFB_ACC_SBGO_DVRV 0x00000010 +#define TMIOFB_ACC_SBGO_DHRV 0x00000020 +#define TMIOFB_ACC_SBGO_SVRV 0x00000040 +#define TMIOFB_ACC_SBGO_SHRV 0x00000080 +#define TMIOFB_ACC_SBGO_SBMD 0x00000100 +#define TMIOFB_ACC_FLGO 0x23000000 +#define TMIOFB_ACC_FLGO_CEND 0x00000001 +#define TMIOFB_ACC_FLGO_INT 0x00000002 +#define TMIOFB_ACC_FLGO_ROP3 0x00000004 +#define TMIOFB_ACC_LDGO 0x24000000 +#define TMIOFB_ACC_LDGO_CEND 0x00000001 +#define TMIOFB_ACC_LDGO_INT 0x00000002 +#define TMIOFB_ACC_LDGO_ROP3 0x00000004 +#define TMIOFB_ACC_LDGO_ENDPX 0x00000008 +#define TMIOFB_ACC_LDGO_LVRV 0x00000010 +#define TMIOFB_ACC_LDGO_LHRV 0x00000020 +#define TMIOFB_ACC_LDGO_LDMOD 0x00000040 + +/* a FIFO is always allocated, even if acceleration is not used */ +#define TMIOFB_FIFO_SIZE 512 + +/* + * LCD Host Controller Configuration Register + * + * This iomem area supports only 16-bit IO. + */ +#define CCR_CMD 0x04 /* Command */ +#define CCR_REVID 0x08 /* Revision ID */ +#define CCR_BASEL 0x10 /* LCD Control Reg Base Addr Low */ +#define CCR_BASEH 0x12 /* LCD Control Reg Base Addr High */ +#define CCR_UGCC 0x40 /* Unified Gated Clock Control */ +#define CCR_GCC 0x42 /* Gated Clock Control */ +#define CCR_USC 0x50 /* Unified Software Clear */ +#define CCR_VRAMRTC 0x60 /* VRAM Timing Control */ + /* 0x61 VRAM Refresh Control */ +#define CCR_VRAMSAC 0x62 /* VRAM Access Control */ + /* 0x63 VRAM Status */ +#define CCR_VRAMBC 0x64 /* VRAM Block Control */ + +/* + * LCD Control Register + * + * This iomem area supports only 16-bit IO. + */ +#define LCR_UIS 0x000 /* Unified Interrupt Status */ +#define LCR_VHPN 0x008 /* VRAM Horizontal Pixel Number */ +#define LCR_CFSAL 0x00a /* Command FIFO Start Address Low */ +#define LCR_CFSAH 0x00c /* Command FIFO Start Address High */ +#define LCR_CFS 0x00e /* Command FIFO Size */ +#define LCR_CFWS 0x010 /* Command FIFO Writeable Size */ +#define LCR_BBIE 0x012 /* BitBLT Interrupt Enable */ +#define LCR_BBISC 0x014 /* BitBLT Interrupt Status and Clear */ +#define LCR_CCS 0x016 /* Command Count Status */ +#define LCR_BBES 0x018 /* BitBLT Execution Status */ +#define LCR_CMDL 0x01c /* Command Low */ +#define LCR_CMDH 0x01e /* Command High */ +#define LCR_CFC 0x022 /* Command FIFO Clear */ +#define LCR_CCIFC 0x024 /* CMOS Camera IF Control */ +#define LCR_HWT 0x026 /* Hardware Test */ +#define LCR_LCDCCRC 0x100 /* LCDC Clock and Reset Control */ +#define LCR_LCDCC 0x102 /* LCDC Control */ +#define LCR_LCDCOPC 0x104 /* LCDC Output Pin Control */ +#define LCR_LCDIS 0x108 /* LCD Interrupt Status */ +#define LCR_LCDIM 0x10a /* LCD Interrupt Mask */ +#define LCR_LCDIE 0x10c /* LCD Interrupt Enable */ +#define LCR_GDSAL 0x122 /* Graphics Display Start Address Low */ +#define LCR_GDSAH 0x124 /* Graphics Display Start Address High */ +#define LCR_VHPCL 0x12a /* VRAM Horizontal Pixel Count Low */ +#define LCR_VHPCH 0x12c /* VRAM Horizontal Pixel Count High */ +#define LCR_GM 0x12e /* Graphic Mode(VRAM access enable) */ +#define LCR_HT 0x140 /* Horizontal Total */ +#define LCR_HDS 0x142 /* Horizontal Display Start */ +#define LCR_HSS 0x144 /* H-Sync Start */ +#define LCR_HSE 0x146 /* H-Sync End */ +#define LCR_HNP 0x14c /* Horizontal Number of Pixels */ +#define LCR_VT 0x150 /* Vertical Total */ +#define LCR_VDS 0x152 /* Vertical Display Start */ +#define LCR_VSS 0x154 /* V-Sync Start */ +#define LCR_VSE 0x156 /* V-Sync End */ +#define LCR_CDLN 0x160 /* Current Display Line Number */ +#define LCR_ILN 0x162 /* Interrupt Line Number */ +#define LCR_SP 0x164 /* Sync Polarity */ +#define LCR_MISC 0x166 /* MISC(RGB565 mode) */ +#define LCR_VIHSS 0x16a /* Video Interface H-Sync Start */ +#define LCR_VIVS 0x16c /* Video Interface Vertical Start */ +#define LCR_VIVE 0x16e /* Video Interface Vertical End */ +#define LCR_VIVSS 0x170 /* Video Interface V-Sync Start */ +#define LCR_VCCIS 0x17e /* Video / CMOS Camera Interface Select */ +#define LCR_VIDWSAL 0x180 /* VI Data Write Start Address Low */ +#define LCR_VIDWSAH 0x182 /* VI Data Write Start Address High */ +#define LCR_VIDRSAL 0x184 /* VI Data Read Start Address Low */ +#define LCR_VIDRSAH 0x186 /* VI Data Read Start Address High */ +#define LCR_VIPDDST 0x188 /* VI Picture Data Display Start Timing */ +#define LCR_VIPDDET 0x186 /* VI Picture Data Display End Timing */ +#define LCR_VIE 0x18c /* Video Interface Enable */ +#define LCR_VCS 0x18e /* Video/Camera Select */ +#define LCR_VPHWC 0x194 /* Video Picture Horizontal Wait Count */ +#define LCR_VPHS 0x196 /* Video Picture Horizontal Size */ +#define LCR_VPVWC 0x198 /* Video Picture Vertical Wait Count */ +#define LCR_VPVS 0x19a /* Video Picture Vertical Size */ +#define LCR_PLHPIX 0x1a0 /* PLHPIX */ +#define LCR_XS 0x1a2 /* XStart */ +#define LCR_XCKHW 0x1a4 /* XCK High Width */ +#define LCR_STHS 0x1a8 /* STH Start */ +#define LCR_VT2 0x1aa /* Vertical Total */ +#define LCR_YCKSW 0x1ac /* YCK Start Wait */ +#define LCR_YSTS 0x1ae /* YST Start */ +#define LCR_PPOLS 0x1b0 /* #PPOL Start */ +#define LCR_PRECW 0x1b2 /* PREC Width */ +#define LCR_VCLKHW 0x1b4 /* VCLK High Width */ +#define LCR_OC 0x1b6 /* Output Control */ + +static char *mode_option; + +struct tmiofb_par { + u32 pseudo_palette[16]; + +#ifdef CONFIG_FB_TMIO_ACCELL + wait_queue_head_t wait_acc; + bool use_polling; +#endif + + void __iomem *ccr; + void __iomem *lcr; +}; + +/*--------------------------------------------------------------------------*/ + +/* + * reasons for an interrupt: + * uis bbisc lcdis + * 0100 0001 accelerator command completed + * 2000 0001 vsync start + * 2000 0002 display start + * 2000 0004 line number match(0x1ff mask???) + */ +static irqreturn_t tmiofb_irq(int irq, void *__info) +{ + struct fb_info *info = __info; + struct tmiofb_par *par = info->par; + unsigned int bbisc = tmio_ioread16(par->lcr + LCR_BBISC); + + + tmio_iowrite16(bbisc, par->lcr + LCR_BBISC); + +#ifdef CONFIG_FB_TMIO_ACCELL + /* + * We were in polling mode and now we got correct irq. + * Switch back to IRQ-based sync of command FIFO + */ + if (unlikely(par->use_polling && irq != -1)) { + printk(KERN_INFO "tmiofb: switching to waitq\n"); + par->use_polling = false; + } + + if (bbisc & 1) + wake_up(&par->wait_acc); +#endif + + return IRQ_HANDLED; +} + + +/*--------------------------------------------------------------------------*/ + + +/* + * Turns off the LCD controller and LCD host controller. + */ +static int tmiofb_hw_stop(struct platform_device *dev) +{ + struct tmio_fb_data *data = dev_get_platdata(&dev->dev); + struct fb_info *info = platform_get_drvdata(dev); + struct tmiofb_par *par = info->par; + + tmio_iowrite16(0, par->ccr + CCR_UGCC); + tmio_iowrite16(0, par->lcr + LCR_GM); + data->lcd_set_power(dev, 0); + tmio_iowrite16(0x0010, par->lcr + LCR_LCDCCRC); + + return 0; +} + +/* + * Initializes the LCD host controller. + */ +static int tmiofb_hw_init(struct platform_device *dev) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + struct fb_info *info = platform_get_drvdata(dev); + struct tmiofb_par *par = info->par; + const struct resource *nlcr = &cell->resources[0]; + const struct resource *vram = &cell->resources[2]; + unsigned long base; + + if (nlcr == NULL || vram == NULL) + return -EINVAL; + + base = nlcr->start; + + tmio_iowrite16(0x003a, par->ccr + CCR_UGCC); + tmio_iowrite16(0x003a, par->ccr + CCR_GCC); + tmio_iowrite16(0x3f00, par->ccr + CCR_USC); + + msleep(2); /* wait for device to settle */ + + tmio_iowrite16(0x0000, par->ccr + CCR_USC); + tmio_iowrite16(base >> 16, par->ccr + CCR_BASEH); + tmio_iowrite16(base, par->ccr + CCR_BASEL); + tmio_iowrite16(0x0002, par->ccr + CCR_CMD); /* base address enable */ + tmio_iowrite16(0x40a8, par->ccr + CCR_VRAMRTC); /* VRAMRC, VRAMTC */ + tmio_iowrite16(0x0018, par->ccr + CCR_VRAMSAC); /* VRAMSTS, VRAMAC */ + tmio_iowrite16(0x0002, par->ccr + CCR_VRAMBC); + msleep(2); /* wait for device to settle */ + tmio_iowrite16(0x000b, par->ccr + CCR_VRAMBC); + + base = vram->start + info->screen_size; + tmio_iowrite16(base >> 16, par->lcr + LCR_CFSAH); + tmio_iowrite16(base, par->lcr + LCR_CFSAL); + tmio_iowrite16(TMIOFB_FIFO_SIZE - 1, par->lcr + LCR_CFS); + tmio_iowrite16(1, par->lcr + LCR_CFC); + tmio_iowrite16(1, par->lcr + LCR_BBIE); + tmio_iowrite16(0, par->lcr + LCR_CFWS); + + return 0; +} + +/* + * Sets the LCD controller's output resolution and pixel clock + */ +static void tmiofb_hw_mode(struct platform_device *dev) +{ + struct tmio_fb_data *data = dev_get_platdata(&dev->dev); + struct fb_info *info = platform_get_drvdata(dev); + struct fb_videomode *mode = info->mode; + struct tmiofb_par *par = info->par; + unsigned int i; + + tmio_iowrite16(0, par->lcr + LCR_GM); + data->lcd_set_power(dev, 0); + tmio_iowrite16(0x0010, par->lcr + LCR_LCDCCRC); + data->lcd_mode(dev, mode); + data->lcd_set_power(dev, 1); + + tmio_iowrite16(info->fix.line_length, par->lcr + LCR_VHPN); + tmio_iowrite16(0, par->lcr + LCR_GDSAH); + tmio_iowrite16(0, par->lcr + LCR_GDSAL); + tmio_iowrite16(info->fix.line_length >> 16, par->lcr + LCR_VHPCH); + tmio_iowrite16(info->fix.line_length, par->lcr + LCR_VHPCL); + tmio_iowrite16(i = 0, par->lcr + LCR_HSS); + tmio_iowrite16(i += mode->hsync_len, par->lcr + LCR_HSE); + tmio_iowrite16(i += mode->left_margin, par->lcr + LCR_HDS); + tmio_iowrite16(i += mode->xres + mode->right_margin, par->lcr + LCR_HT); + tmio_iowrite16(mode->xres, par->lcr + LCR_HNP); + tmio_iowrite16(i = 0, par->lcr + LCR_VSS); + tmio_iowrite16(i += mode->vsync_len, par->lcr + LCR_VSE); + tmio_iowrite16(i += mode->upper_margin, par->lcr + LCR_VDS); + tmio_iowrite16(i += mode->yres, par->lcr + LCR_ILN); + tmio_iowrite16(i += mode->lower_margin, par->lcr + LCR_VT); + tmio_iowrite16(3, par->lcr + LCR_MISC); /* RGB565 mode */ + tmio_iowrite16(1, par->lcr + LCR_GM); /* VRAM enable */ + tmio_iowrite16(0x4007, par->lcr + LCR_LCDCC); + tmio_iowrite16(3, par->lcr + LCR_SP); /* sync polarity */ + + tmio_iowrite16(0x0010, par->lcr + LCR_LCDCCRC); + msleep(5); /* wait for device to settle */ + tmio_iowrite16(0x0014, par->lcr + LCR_LCDCCRC); /* STOP_CKP */ + msleep(5); /* wait for device to settle */ + tmio_iowrite16(0x0015, par->lcr + LCR_LCDCCRC); /* STOP_CKP|SOFT_RESET*/ + tmio_iowrite16(0xfffa, par->lcr + LCR_VCS); +} + +/*--------------------------------------------------------------------------*/ + +#ifdef CONFIG_FB_TMIO_ACCELL +static int __must_check +tmiofb_acc_wait(struct fb_info *info, unsigned int ccs) +{ + struct tmiofb_par *par = info->par; + /* + * This code can be called with interrupts disabled. + * So instead of relaying on irq to trigger the event, + * poll the state till the necessary command is executed. + */ + if (irqs_disabled() || par->use_polling) { + int i = 0; + while (tmio_ioread16(par->lcr + LCR_CCS) > ccs) { + udelay(1); + i++; + if (i > 10000) { + pr_err("tmiofb: timeout waiting for %d\n", + ccs); + return -ETIMEDOUT; + } + tmiofb_irq(-1, info); + } + } else { + if (!wait_event_interruptible_timeout(par->wait_acc, + tmio_ioread16(par->lcr + LCR_CCS) <= ccs, + 1000)) { + pr_err("tmiofb: timeout waiting for %d\n", ccs); + return -ETIMEDOUT; + } + } + + return 0; +} + +/* + * Writes an accelerator command to the accelerator's FIFO. + */ +static int +tmiofb_acc_write(struct fb_info *info, const u32 *cmd, unsigned int count) +{ + struct tmiofb_par *par = info->par; + int ret; + + ret = tmiofb_acc_wait(info, TMIOFB_FIFO_SIZE - count); + if (ret) + return ret; + + for (; count; count--, cmd++) { + tmio_iowrite16(*cmd >> 16, par->lcr + LCR_CMDH); + tmio_iowrite16(*cmd, par->lcr + LCR_CMDL); + } + + return ret; +} + +/* + * Wait for the accelerator to finish its operations before writing + * to the framebuffer for consistent display output. + */ +static int tmiofb_sync(struct fb_info *fbi) +{ + struct tmiofb_par *par = fbi->par; + + int ret; + int i = 0; + + ret = tmiofb_acc_wait(fbi, 0); + + while (tmio_ioread16(par->lcr + LCR_BBES) & 2) { /* blit active */ + udelay(1); + i++ ; + if (i > 10000) { + printk(KERN_ERR "timeout waiting for blit to end!\n"); + return -ETIMEDOUT; + } + } + + return ret; +} + +static void +tmiofb_fillrect(struct fb_info *fbi, const struct fb_fillrect *rect) +{ + const u32 cmd[] = { + TMIOFB_ACC_DSADR((rect->dy * fbi->mode->xres + rect->dx) * 2), + TMIOFB_ACC_DHPIX(rect->width - 1), + TMIOFB_ACC_DVPIX(rect->height - 1), + TMIOFB_ACC_FILL(rect->color), + TMIOFB_ACC_FLGO, + }; + + if (fbi->state != FBINFO_STATE_RUNNING || + fbi->flags & FBINFO_HWACCEL_DISABLED) { + cfb_fillrect(fbi, rect); + return; + } + + tmiofb_acc_write(fbi, cmd, ARRAY_SIZE(cmd)); +} + +static void +tmiofb_copyarea(struct fb_info *fbi, const struct fb_copyarea *area) +{ + const u32 cmd[] = { + TMIOFB_ACC_DSADR((area->dy * fbi->mode->xres + area->dx) * 2), + TMIOFB_ACC_DHPIX(area->width - 1), + TMIOFB_ACC_DVPIX(area->height - 1), + TMIOFB_ACC_SSADR((area->sy * fbi->mode->xres + area->sx) * 2), + TMIOFB_ACC_SCGO, + }; + + if (fbi->state != FBINFO_STATE_RUNNING || + fbi->flags & FBINFO_HWACCEL_DISABLED) { + cfb_copyarea(fbi, area); + return; + } + + tmiofb_acc_write(fbi, cmd, ARRAY_SIZE(cmd)); +} +#endif + +static void tmiofb_clearscreen(struct fb_info *info) +{ + const struct fb_fillrect rect = { + .dx = 0, + .dy = 0, + .width = info->mode->xres, + .height = info->mode->yres, + .color = 0, + .rop = ROP_COPY, + }; + + info->fbops->fb_fillrect(info, &rect); +} + +static int tmiofb_vblank(struct fb_info *fbi, struct fb_vblank *vblank) +{ + struct tmiofb_par *par = fbi->par; + struct fb_videomode *mode = fbi->mode; + unsigned int vcount = tmio_ioread16(par->lcr + LCR_CDLN); + unsigned int vds = mode->vsync_len + mode->upper_margin; + + vblank->vcount = vcount; + vblank->flags = FB_VBLANK_HAVE_VBLANK | FB_VBLANK_HAVE_VCOUNT + | FB_VBLANK_HAVE_VSYNC; + + if (vcount < mode->vsync_len) + vblank->flags |= FB_VBLANK_VSYNCING; + + if (vcount < vds || vcount > vds + mode->yres) + vblank->flags |= FB_VBLANK_VBLANKING; + + return 0; +} + + +static int tmiofb_ioctl(struct fb_info *fbi, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FBIOGET_VBLANK: { + struct fb_vblank vblank = {0}; + void __user *argp = (void __user *) arg; + + tmiofb_vblank(fbi, &vblank); + if (copy_to_user(argp, &vblank, sizeof vblank)) + return -EFAULT; + return 0; + } + +#ifdef CONFIG_FB_TMIO_ACCELL + case FBIO_TMIO_ACC_SYNC: + tmiofb_sync(fbi); + return 0; + + case FBIO_TMIO_ACC_WRITE: { + u32 __user *argp = (void __user *) arg; + u32 len; + u32 acc[16]; + + if (get_user(len, argp)) + return -EFAULT; + if (len > ARRAY_SIZE(acc)) + return -EINVAL; + if (copy_from_user(acc, argp + 1, sizeof(u32) * len)) + return -EFAULT; + + return tmiofb_acc_write(fbi, acc, len); + } +#endif + } + + return -ENOTTY; +} + +/*--------------------------------------------------------------------------*/ + +/* Select the smallest mode that allows the desired resolution to be + * displayed. If desired, the x and y parameters can be rounded up to + * match the selected mode. + */ +static struct fb_videomode * +tmiofb_find_mode(struct fb_info *info, struct fb_var_screeninfo *var) +{ + struct tmio_fb_data *data = dev_get_platdata(info->device); + struct fb_videomode *best = NULL; + int i; + + for (i = 0; i < data->num_modes; i++) { + struct fb_videomode *mode = data->modes + i; + + if (mode->xres >= var->xres && mode->yres >= var->yres + && (!best || (mode->xres < best->xres + && mode->yres < best->yres))) + best = mode; + } + + return best; +} + +static int tmiofb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + + struct fb_videomode *mode; + struct tmio_fb_data *data = dev_get_platdata(info->device); + + mode = tmiofb_find_mode(info, var); + if (!mode || var->bits_per_pixel > 16) + return -EINVAL; + + fb_videomode_to_var(var, mode); + + var->xres_virtual = mode->xres; + var->yres_virtual = info->screen_size / (mode->xres * 2); + + if (var->yres_virtual < var->yres) + return -EINVAL; + + var->xoffset = 0; + var->yoffset = 0; + var->bits_per_pixel = 16; + var->grayscale = 0; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + var->nonstd = 0; + var->height = data->height; /* mm */ + var->width = data->width; /* mm */ + var->rotate = 0; + return 0; +} + +static int tmiofb_set_par(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct fb_videomode *mode; + + mode = tmiofb_find_mode(info, var); + if (!mode) + return -EINVAL; + + info->mode = mode; + info->fix.line_length = info->mode->xres * + var->bits_per_pixel / 8; + + tmiofb_hw_mode(to_platform_device(info->device)); + tmiofb_clearscreen(info); + return 0; +} + +static int tmiofb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct tmiofb_par *par = info->par; + + if (regno < ARRAY_SIZE(par->pseudo_palette)) { + par->pseudo_palette[regno] = + ((red & 0xf800)) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + return 0; + } + + return -EINVAL; +} + +static int tmiofb_blank(int blank, struct fb_info *info) +{ + /* + * everything is done in lcd/bl drivers. + * this is purely to make sysfs happy and work. + */ + return 0; +} + +static struct fb_ops tmiofb_ops = { + .owner = THIS_MODULE, + + .fb_ioctl = tmiofb_ioctl, + .fb_check_var = tmiofb_check_var, + .fb_set_par = tmiofb_set_par, + .fb_setcolreg = tmiofb_setcolreg, + .fb_blank = tmiofb_blank, + .fb_imageblit = cfb_imageblit, +#ifdef CONFIG_FB_TMIO_ACCELL + .fb_sync = tmiofb_sync, + .fb_fillrect = tmiofb_fillrect, + .fb_copyarea = tmiofb_copyarea, +#else + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, +#endif +}; + +/*--------------------------------------------------------------------------*/ + +static int tmiofb_probe(struct platform_device *dev) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + struct tmio_fb_data *data = dev_get_platdata(&dev->dev); + struct resource *ccr = platform_get_resource(dev, IORESOURCE_MEM, 1); + struct resource *lcr = platform_get_resource(dev, IORESOURCE_MEM, 0); + struct resource *vram = platform_get_resource(dev, IORESOURCE_MEM, 2); + int irq = platform_get_irq(dev, 0); + struct fb_info *info; + struct tmiofb_par *par; + int retval; + + /* + * This is the only way ATM to disable the fb + */ + if (data == NULL) { + dev_err(&dev->dev, "NULL platform data!\n"); + return -EINVAL; + } + if (ccr == NULL || lcr == NULL || vram == NULL || irq < 0) { + dev_err(&dev->dev, "missing resources\n"); + return -EINVAL; + } + + info = framebuffer_alloc(sizeof(struct tmiofb_par), &dev->dev); + + if (!info) + return -ENOMEM; + + par = info->par; + +#ifdef CONFIG_FB_TMIO_ACCELL + init_waitqueue_head(&par->wait_acc); + + par->use_polling = true; + + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_FILLRECT; +#else + info->flags = FBINFO_DEFAULT; +#endif + + info->fbops = &tmiofb_ops; + + strcpy(info->fix.id, "tmio-fb"); + info->fix.smem_start = vram->start; + info->fix.smem_len = resource_size(vram); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.mmio_start = lcr->start; + info->fix.mmio_len = resource_size(lcr); + info->fix.accel = FB_ACCEL_NONE; + info->screen_size = info->fix.smem_len - (4 * TMIOFB_FIFO_SIZE); + info->pseudo_palette = par->pseudo_palette; + + par->ccr = ioremap(ccr->start, resource_size(ccr)); + if (!par->ccr) { + retval = -ENOMEM; + goto err_ioremap_ccr; + } + + par->lcr = ioremap(info->fix.mmio_start, info->fix.mmio_len); + if (!par->lcr) { + retval = -ENOMEM; + goto err_ioremap_lcr; + } + + info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); + if (!info->screen_base) { + retval = -ENOMEM; + goto err_ioremap_vram; + } + + retval = request_irq(irq, &tmiofb_irq, 0, + dev_name(&dev->dev), info); + + if (retval) + goto err_request_irq; + + platform_set_drvdata(dev, info); + + retval = fb_find_mode(&info->var, info, mode_option, + data->modes, data->num_modes, + data->modes, 16); + if (!retval) { + retval = -EINVAL; + goto err_find_mode; + } + + if (cell->enable) { + retval = cell->enable(dev); + if (retval) + goto err_enable; + } + + retval = tmiofb_hw_init(dev); + if (retval) + goto err_hw_init; + + fb_videomode_to_modelist(data->modes, data->num_modes, + &info->modelist); + + retval = register_framebuffer(info); + if (retval < 0) + goto err_register_framebuffer; + + fb_info(info, "%s frame buffer device\n", info->fix.id); + + return 0; + +err_register_framebuffer: +/*err_set_par:*/ + tmiofb_hw_stop(dev); +err_hw_init: + if (cell->disable) + cell->disable(dev); +err_enable: +err_find_mode: + free_irq(irq, info); +err_request_irq: + iounmap(info->screen_base); +err_ioremap_vram: + iounmap(par->lcr); +err_ioremap_lcr: + iounmap(par->ccr); +err_ioremap_ccr: + framebuffer_release(info); + return retval; +} + +static int tmiofb_remove(struct platform_device *dev) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + struct fb_info *info = platform_get_drvdata(dev); + int irq = platform_get_irq(dev, 0); + struct tmiofb_par *par; + + if (info) { + par = info->par; + unregister_framebuffer(info); + + tmiofb_hw_stop(dev); + + if (cell->disable) + cell->disable(dev); + + free_irq(irq, info); + + iounmap(info->screen_base); + iounmap(par->lcr); + iounmap(par->ccr); + + framebuffer_release(info); + } + + return 0; +} + +#ifdef DEBUG +static void tmiofb_dump_regs(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct tmiofb_par *par = info->par; + + printk(KERN_DEBUG "lhccr:\n"); +#define CCR_PR(n) printk(KERN_DEBUG "\t" #n " = \t%04x\n",\ + tmio_ioread16(par->ccr + CCR_ ## n)); + CCR_PR(CMD); + CCR_PR(REVID); + CCR_PR(BASEL); + CCR_PR(BASEH); + CCR_PR(UGCC); + CCR_PR(GCC); + CCR_PR(USC); + CCR_PR(VRAMRTC); + CCR_PR(VRAMSAC); + CCR_PR(VRAMBC); +#undef CCR_PR + + printk(KERN_DEBUG "lcr: \n"); +#define LCR_PR(n) printk(KERN_DEBUG "\t" #n " = \t%04x\n",\ + tmio_ioread16(par->lcr + LCR_ ## n)); + LCR_PR(UIS); + LCR_PR(VHPN); + LCR_PR(CFSAL); + LCR_PR(CFSAH); + LCR_PR(CFS); + LCR_PR(CFWS); + LCR_PR(BBIE); + LCR_PR(BBISC); + LCR_PR(CCS); + LCR_PR(BBES); + LCR_PR(CMDL); + LCR_PR(CMDH); + LCR_PR(CFC); + LCR_PR(CCIFC); + LCR_PR(HWT); + LCR_PR(LCDCCRC); + LCR_PR(LCDCC); + LCR_PR(LCDCOPC); + LCR_PR(LCDIS); + LCR_PR(LCDIM); + LCR_PR(LCDIE); + LCR_PR(GDSAL); + LCR_PR(GDSAH); + LCR_PR(VHPCL); + LCR_PR(VHPCH); + LCR_PR(GM); + LCR_PR(HT); + LCR_PR(HDS); + LCR_PR(HSS); + LCR_PR(HSE); + LCR_PR(HNP); + LCR_PR(VT); + LCR_PR(VDS); + LCR_PR(VSS); + LCR_PR(VSE); + LCR_PR(CDLN); + LCR_PR(ILN); + LCR_PR(SP); + LCR_PR(MISC); + LCR_PR(VIHSS); + LCR_PR(VIVS); + LCR_PR(VIVE); + LCR_PR(VIVSS); + LCR_PR(VCCIS); + LCR_PR(VIDWSAL); + LCR_PR(VIDWSAH); + LCR_PR(VIDRSAL); + LCR_PR(VIDRSAH); + LCR_PR(VIPDDST); + LCR_PR(VIPDDET); + LCR_PR(VIE); + LCR_PR(VCS); + LCR_PR(VPHWC); + LCR_PR(VPHS); + LCR_PR(VPVWC); + LCR_PR(VPVS); + LCR_PR(PLHPIX); + LCR_PR(XS); + LCR_PR(XCKHW); + LCR_PR(STHS); + LCR_PR(VT2); + LCR_PR(YCKSW); + LCR_PR(YSTS); + LCR_PR(PPOLS); + LCR_PR(PRECW); + LCR_PR(VCLKHW); + LCR_PR(OC); +#undef LCR_PR +} +#endif + +#ifdef CONFIG_PM +static int tmiofb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct fb_info *info = platform_get_drvdata(dev); +#ifdef CONFIG_FB_TMIO_ACCELL + struct tmiofb_par *par = info->par; +#endif + const struct mfd_cell *cell = mfd_get_cell(dev); + int retval = 0; + + console_lock(); + + fb_set_suspend(info, 1); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + +#ifdef CONFIG_FB_TMIO_ACCELL + /* + * The fb should be usable even if interrupts are disabled (and they are + * during suspend/resume). Switch temporary to forced polling. + */ + printk(KERN_INFO "tmiofb: switching to polling\n"); + par->use_polling = true; +#endif + tmiofb_hw_stop(dev); + + if (cell->suspend) + retval = cell->suspend(dev); + + console_unlock(); + + return retval; +} + +static int tmiofb_resume(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + const struct mfd_cell *cell = mfd_get_cell(dev); + int retval = 0; + + console_lock(); + + if (cell->resume) { + retval = cell->resume(dev); + if (retval) + goto out; + } + + tmiofb_irq(-1, info); + + tmiofb_hw_init(dev); + + tmiofb_hw_mode(dev); + + fb_set_suspend(info, 0); +out: + console_unlock(); + return retval; +} +#else +#define tmiofb_suspend NULL +#define tmiofb_resume NULL +#endif + +static struct platform_driver tmiofb_driver = { + .driver.name = "tmio-fb", + .driver.owner = THIS_MODULE, + .probe = tmiofb_probe, + .remove = tmiofb_remove, + .suspend = tmiofb_suspend, + .resume = tmiofb_resume, +}; + +/*--------------------------------------------------------------------------*/ + +#ifndef MODULE +static void __init tmiofb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + /* + * FIXME + */ + } +} +#endif + +static int __init tmiofb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("tmiofb", &option)) + return -ENODEV; + tmiofb_setup(option); +#endif + return platform_driver_register(&tmiofb_driver); +} + +static void __exit tmiofb_cleanup(void) +{ + platform_driver_unregister(&tmiofb_driver); +} + +module_init(tmiofb_init); +module_exit(tmiofb_cleanup); + +MODULE_DESCRIPTION("TMIO framebuffer driver"); +MODULE_AUTHOR("Chris Humbert, Dirk Opfer, Dmitry Baryshkov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/tridentfb.c b/drivers/video/fbdev/tridentfb.c new file mode 100644 index 000000000000..7ed9a227f5ea --- /dev/null +++ b/drivers/video/fbdev/tridentfb.c @@ -0,0 +1,1659 @@ +/* + * Frame buffer driver for Trident TGUI, Blade and Image series + * + * Copyright 2001, 2002 - Jani Monoses <jani@iv.ro> + * Copyright 2009 Krzysztof Helt <krzysztof.h1@wp.pl> + * + * CREDITS:(in order of appearance) + * skeletonfb.c by Geert Uytterhoeven and other fb code in drivers/video + * Special thanks ;) to Mattia Crivellini <tia@mclink.it> + * much inspired by the XFree86 4.x Trident driver sources + * by Alan Hourihane the FreeVGA project + * Francesco Salvestrini <salvestrini@users.sf.net> XP support, + * code, suggestions + * TODO: + * timing value tweaking so it looks good on every monitor in every mode + */ + +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/slab.h> + +#include <linux/delay.h> +#include <video/vga.h> +#include <video/trident.h> + +struct tridentfb_par { + void __iomem *io_virt; /* iospace virtual memory address */ + u32 pseudo_pal[16]; + int chip_id; + int flatpanel; + void (*init_accel) (struct tridentfb_par *, int, int); + void (*wait_engine) (struct tridentfb_par *); + void (*fill_rect) + (struct tridentfb_par *par, u32, u32, u32, u32, u32, u32); + void (*copy_rect) + (struct tridentfb_par *par, u32, u32, u32, u32, u32, u32); + void (*image_blit) + (struct tridentfb_par *par, const char*, + u32, u32, u32, u32, u32, u32); + unsigned char eng_oper; /* engine operation... */ +}; + +static struct fb_fix_screeninfo tridentfb_fix = { + .id = "Trident", + .type = FB_TYPE_PACKED_PIXELS, + .ypanstep = 1, + .visual = FB_VISUAL_PSEUDOCOLOR, + .accel = FB_ACCEL_NONE, +}; + +/* defaults which are normally overriden by user values */ + +/* video mode */ +static char *mode_option = "640x480-8@60"; +static int bpp = 8; + +static int noaccel; + +static int center; +static int stretch; + +static int fp; +static int crt; + +static int memsize; +static int memdiff; +static int nativex; + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Initial video mode e.g. '648x480-8@60'"); +module_param_named(mode, mode_option, charp, 0); +MODULE_PARM_DESC(mode, "Initial video mode e.g. '648x480-8@60' (deprecated)"); +module_param(bpp, int, 0); +module_param(center, int, 0); +module_param(stretch, int, 0); +module_param(noaccel, int, 0); +module_param(memsize, int, 0); +module_param(memdiff, int, 0); +module_param(nativex, int, 0); +module_param(fp, int, 0); +MODULE_PARM_DESC(fp, "Define if flatpanel is connected"); +module_param(crt, int, 0); +MODULE_PARM_DESC(crt, "Define if CRT is connected"); + +static inline int is_oldclock(int id) +{ + return (id == TGUI9440) || + (id == TGUI9660) || + (id == CYBER9320); +} + +static inline int is_oldprotect(int id) +{ + return is_oldclock(id) || + (id == PROVIDIA9685) || + (id == CYBER9382) || + (id == CYBER9385); +} + +static inline int is_blade(int id) +{ + return (id == BLADE3D) || + (id == CYBERBLADEE4) || + (id == CYBERBLADEi7) || + (id == CYBERBLADEi7D) || + (id == CYBERBLADEi1) || + (id == CYBERBLADEi1D) || + (id == CYBERBLADEAi1) || + (id == CYBERBLADEAi1D); +} + +static inline int is_xp(int id) +{ + return (id == CYBERBLADEXPAi1) || + (id == CYBERBLADEXPm8) || + (id == CYBERBLADEXPm16); +} + +static inline int is3Dchip(int id) +{ + return is_blade(id) || is_xp(id) || + (id == CYBER9397) || (id == CYBER9397DVD) || + (id == CYBER9520) || (id == CYBER9525DVD) || + (id == IMAGE975) || (id == IMAGE985); +} + +static inline int iscyber(int id) +{ + switch (id) { + case CYBER9388: + case CYBER9382: + case CYBER9385: + case CYBER9397: + case CYBER9397DVD: + case CYBER9520: + case CYBER9525DVD: + case CYBERBLADEE4: + case CYBERBLADEi7D: + case CYBERBLADEi1: + case CYBERBLADEi1D: + case CYBERBLADEAi1: + case CYBERBLADEAi1D: + case CYBERBLADEXPAi1: + return 1; + + case CYBER9320: + case CYBERBLADEi7: /* VIA MPV4 integrated version */ + default: + /* case CYBERBLDAEXPm8: Strange */ + /* case CYBERBLDAEXPm16: Strange */ + return 0; + } +} + +static inline void t_outb(struct tridentfb_par *p, u8 val, u16 reg) +{ + fb_writeb(val, p->io_virt + reg); +} + +static inline u8 t_inb(struct tridentfb_par *p, u16 reg) +{ + return fb_readb(p->io_virt + reg); +} + +static inline void writemmr(struct tridentfb_par *par, u16 r, u32 v) +{ + fb_writel(v, par->io_virt + r); +} + +static inline u32 readmmr(struct tridentfb_par *par, u16 r) +{ + return fb_readl(par->io_virt + r); +} + +/* + * Blade specific acceleration. + */ + +#define point(x, y) ((y) << 16 | (x)) + +static void blade_init_accel(struct tridentfb_par *par, int pitch, int bpp) +{ + int v1 = (pitch >> 3) << 20; + int tmp = bpp == 24 ? 2 : (bpp >> 4); + int v2 = v1 | (tmp << 29); + + writemmr(par, 0x21C0, v2); + writemmr(par, 0x21C4, v2); + writemmr(par, 0x21B8, v2); + writemmr(par, 0x21BC, v2); + writemmr(par, 0x21D0, v1); + writemmr(par, 0x21D4, v1); + writemmr(par, 0x21C8, v1); + writemmr(par, 0x21CC, v1); + writemmr(par, 0x216C, 0); +} + +static void blade_wait_engine(struct tridentfb_par *par) +{ + while (readmmr(par, STATUS) & 0xFA800000) + cpu_relax(); +} + +static void blade_fill_rect(struct tridentfb_par *par, + u32 x, u32 y, u32 w, u32 h, u32 c, u32 rop) +{ + writemmr(par, COLOR, c); + writemmr(par, ROP, rop ? ROP_X : ROP_S); + writemmr(par, CMD, 0x20000000 | 1 << 19 | 1 << 4 | 2 << 2); + + writemmr(par, DST1, point(x, y)); + writemmr(par, DST2, point(x + w - 1, y + h - 1)); +} + +static void blade_image_blit(struct tridentfb_par *par, const char *data, + u32 x, u32 y, u32 w, u32 h, u32 c, u32 b) +{ + unsigned size = ((w + 31) >> 5) * h; + + writemmr(par, COLOR, c); + writemmr(par, BGCOLOR, b); + writemmr(par, CMD, 0xa0000000 | 3 << 19); + + writemmr(par, DST1, point(x, y)); + writemmr(par, DST2, point(x + w - 1, y + h - 1)); + + memcpy(par->io_virt + 0x10000, data, 4 * size); +} + +static void blade_copy_rect(struct tridentfb_par *par, + u32 x1, u32 y1, u32 x2, u32 y2, u32 w, u32 h) +{ + int direction = 2; + u32 s1 = point(x1, y1); + u32 s2 = point(x1 + w - 1, y1 + h - 1); + u32 d1 = point(x2, y2); + u32 d2 = point(x2 + w - 1, y2 + h - 1); + + if ((y1 > y2) || ((y1 == y2) && (x1 > x2))) + direction = 0; + + writemmr(par, ROP, ROP_S); + writemmr(par, CMD, 0xE0000000 | 1 << 19 | 1 << 4 | 1 << 2 | direction); + + writemmr(par, SRC1, direction ? s2 : s1); + writemmr(par, SRC2, direction ? s1 : s2); + writemmr(par, DST1, direction ? d2 : d1); + writemmr(par, DST2, direction ? d1 : d2); +} + +/* + * BladeXP specific acceleration functions + */ + +static void xp_init_accel(struct tridentfb_par *par, int pitch, int bpp) +{ + unsigned char x = bpp == 24 ? 3 : (bpp >> 4); + int v1 = pitch << (bpp == 24 ? 20 : (18 + x)); + + switch (pitch << (bpp >> 3)) { + case 8192: + case 512: + x |= 0x00; + break; + case 1024: + x |= 0x04; + break; + case 2048: + x |= 0x08; + break; + case 4096: + x |= 0x0C; + break; + } + + t_outb(par, x, 0x2125); + + par->eng_oper = x | 0x40; + + writemmr(par, 0x2154, v1); + writemmr(par, 0x2150, v1); + t_outb(par, 3, 0x2126); +} + +static void xp_wait_engine(struct tridentfb_par *par) +{ + int count = 0; + int timeout = 0; + + while (t_inb(par, STATUS) & 0x80) { + count++; + if (count == 10000000) { + /* Timeout */ + count = 9990000; + timeout++; + if (timeout == 8) { + /* Reset engine */ + t_outb(par, 0x00, STATUS); + return; + } + } + cpu_relax(); + } +} + +static void xp_fill_rect(struct tridentfb_par *par, + u32 x, u32 y, u32 w, u32 h, u32 c, u32 rop) +{ + writemmr(par, 0x2127, ROP_P); + writemmr(par, 0x2158, c); + writemmr(par, DRAWFL, 0x4000); + writemmr(par, OLDDIM, point(h, w)); + writemmr(par, OLDDST, point(y, x)); + t_outb(par, 0x01, OLDCMD); + t_outb(par, par->eng_oper, 0x2125); +} + +static void xp_copy_rect(struct tridentfb_par *par, + u32 x1, u32 y1, u32 x2, u32 y2, u32 w, u32 h) +{ + u32 x1_tmp, x2_tmp, y1_tmp, y2_tmp; + int direction = 0x0004; + + if ((x1 < x2) && (y1 == y2)) { + direction |= 0x0200; + x1_tmp = x1 + w - 1; + x2_tmp = x2 + w - 1; + } else { + x1_tmp = x1; + x2_tmp = x2; + } + + if (y1 < y2) { + direction |= 0x0100; + y1_tmp = y1 + h - 1; + y2_tmp = y2 + h - 1; + } else { + y1_tmp = y1; + y2_tmp = y2; + } + + writemmr(par, DRAWFL, direction); + t_outb(par, ROP_S, 0x2127); + writemmr(par, OLDSRC, point(y1_tmp, x1_tmp)); + writemmr(par, OLDDST, point(y2_tmp, x2_tmp)); + writemmr(par, OLDDIM, point(h, w)); + t_outb(par, 0x01, OLDCMD); +} + +/* + * Image specific acceleration functions + */ +static void image_init_accel(struct tridentfb_par *par, int pitch, int bpp) +{ + int tmp = bpp == 24 ? 2: (bpp >> 4); + + writemmr(par, 0x2120, 0xF0000000); + writemmr(par, 0x2120, 0x40000000 | tmp); + writemmr(par, 0x2120, 0x80000000); + writemmr(par, 0x2144, 0x00000000); + writemmr(par, 0x2148, 0x00000000); + writemmr(par, 0x2150, 0x00000000); + writemmr(par, 0x2154, 0x00000000); + writemmr(par, 0x2120, 0x60000000 | (pitch << 16) | pitch); + writemmr(par, 0x216C, 0x00000000); + writemmr(par, 0x2170, 0x00000000); + writemmr(par, 0x217C, 0x00000000); + writemmr(par, 0x2120, 0x10000000); + writemmr(par, 0x2130, (2047 << 16) | 2047); +} + +static void image_wait_engine(struct tridentfb_par *par) +{ + while (readmmr(par, 0x2164) & 0xF0000000) + cpu_relax(); +} + +static void image_fill_rect(struct tridentfb_par *par, + u32 x, u32 y, u32 w, u32 h, u32 c, u32 rop) +{ + writemmr(par, 0x2120, 0x80000000); + writemmr(par, 0x2120, 0x90000000 | ROP_S); + + writemmr(par, 0x2144, c); + + writemmr(par, DST1, point(x, y)); + writemmr(par, DST2, point(x + w - 1, y + h - 1)); + + writemmr(par, 0x2124, 0x80000000 | 3 << 22 | 1 << 10 | 1 << 9); +} + +static void image_copy_rect(struct tridentfb_par *par, + u32 x1, u32 y1, u32 x2, u32 y2, u32 w, u32 h) +{ + int direction = 0x4; + u32 s1 = point(x1, y1); + u32 s2 = point(x1 + w - 1, y1 + h - 1); + u32 d1 = point(x2, y2); + u32 d2 = point(x2 + w - 1, y2 + h - 1); + + if ((y1 > y2) || ((y1 == y2) && (x1 > x2))) + direction = 0; + + writemmr(par, 0x2120, 0x80000000); + writemmr(par, 0x2120, 0x90000000 | ROP_S); + + writemmr(par, SRC1, direction ? s2 : s1); + writemmr(par, SRC2, direction ? s1 : s2); + writemmr(par, DST1, direction ? d2 : d1); + writemmr(par, DST2, direction ? d1 : d2); + writemmr(par, 0x2124, + 0x80000000 | 1 << 22 | 1 << 10 | 1 << 7 | direction); +} + +/* + * TGUI 9440/96XX acceleration + */ + +static void tgui_init_accel(struct tridentfb_par *par, int pitch, int bpp) +{ + unsigned char x = bpp == 24 ? 3 : (bpp >> 4); + + /* disable clipping */ + writemmr(par, 0x2148, 0); + writemmr(par, 0x214C, point(4095, 2047)); + + switch ((pitch * bpp) / 8) { + case 8192: + case 512: + x |= 0x00; + break; + case 1024: + x |= 0x04; + break; + case 2048: + x |= 0x08; + break; + case 4096: + x |= 0x0C; + break; + } + + fb_writew(x, par->io_virt + 0x2122); +} + +static void tgui_fill_rect(struct tridentfb_par *par, + u32 x, u32 y, u32 w, u32 h, u32 c, u32 rop) +{ + t_outb(par, ROP_P, 0x2127); + writemmr(par, OLDCLR, c); + writemmr(par, DRAWFL, 0x4020); + writemmr(par, OLDDIM, point(w - 1, h - 1)); + writemmr(par, OLDDST, point(x, y)); + t_outb(par, 1, OLDCMD); +} + +static void tgui_copy_rect(struct tridentfb_par *par, + u32 x1, u32 y1, u32 x2, u32 y2, u32 w, u32 h) +{ + int flags = 0; + u16 x1_tmp, x2_tmp, y1_tmp, y2_tmp; + + if ((x1 < x2) && (y1 == y2)) { + flags |= 0x0200; + x1_tmp = x1 + w - 1; + x2_tmp = x2 + w - 1; + } else { + x1_tmp = x1; + x2_tmp = x2; + } + + if (y1 < y2) { + flags |= 0x0100; + y1_tmp = y1 + h - 1; + y2_tmp = y2 + h - 1; + } else { + y1_tmp = y1; + y2_tmp = y2; + } + + writemmr(par, DRAWFL, 0x4 | flags); + t_outb(par, ROP_S, 0x2127); + writemmr(par, OLDSRC, point(x1_tmp, y1_tmp)); + writemmr(par, OLDDST, point(x2_tmp, y2_tmp)); + writemmr(par, OLDDIM, point(w - 1, h - 1)); + t_outb(par, 1, OLDCMD); +} + +/* + * Accel functions called by the upper layers + */ +static void tridentfb_fillrect(struct fb_info *info, + const struct fb_fillrect *fr) +{ + struct tridentfb_par *par = info->par; + int col; + + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_fillrect(info, fr); + return; + } + if (info->var.bits_per_pixel == 8) { + col = fr->color; + col |= col << 8; + col |= col << 16; + } else + col = ((u32 *)(info->pseudo_palette))[fr->color]; + + par->wait_engine(par); + par->fill_rect(par, fr->dx, fr->dy, fr->width, + fr->height, col, fr->rop); +} + +static void tridentfb_imageblit(struct fb_info *info, + const struct fb_image *img) +{ + struct tridentfb_par *par = info->par; + int col, bgcol; + + if ((info->flags & FBINFO_HWACCEL_DISABLED) || img->depth != 1) { + cfb_imageblit(info, img); + return; + } + if (info->var.bits_per_pixel == 8) { + col = img->fg_color; + col |= col << 8; + col |= col << 16; + bgcol = img->bg_color; + bgcol |= bgcol << 8; + bgcol |= bgcol << 16; + } else { + col = ((u32 *)(info->pseudo_palette))[img->fg_color]; + bgcol = ((u32 *)(info->pseudo_palette))[img->bg_color]; + } + + par->wait_engine(par); + if (par->image_blit) + par->image_blit(par, img->data, img->dx, img->dy, + img->width, img->height, col, bgcol); + else + cfb_imageblit(info, img); +} + +static void tridentfb_copyarea(struct fb_info *info, + const struct fb_copyarea *ca) +{ + struct tridentfb_par *par = info->par; + + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_copyarea(info, ca); + return; + } + par->wait_engine(par); + par->copy_rect(par, ca->sx, ca->sy, ca->dx, ca->dy, + ca->width, ca->height); +} + +static int tridentfb_sync(struct fb_info *info) +{ + struct tridentfb_par *par = info->par; + + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) + par->wait_engine(par); + return 0; +} + +/* + * Hardware access functions + */ + +static inline unsigned char read3X4(struct tridentfb_par *par, int reg) +{ + return vga_mm_rcrt(par->io_virt, reg); +} + +static inline void write3X4(struct tridentfb_par *par, int reg, + unsigned char val) +{ + vga_mm_wcrt(par->io_virt, reg, val); +} + +static inline unsigned char read3CE(struct tridentfb_par *par, + unsigned char reg) +{ + return vga_mm_rgfx(par->io_virt, reg); +} + +static inline void writeAttr(struct tridentfb_par *par, int reg, + unsigned char val) +{ + fb_readb(par->io_virt + VGA_IS1_RC); /* flip-flop to index */ + vga_mm_wattr(par->io_virt, reg, val); +} + +static inline void write3CE(struct tridentfb_par *par, int reg, + unsigned char val) +{ + vga_mm_wgfx(par->io_virt, reg, val); +} + +static void enable_mmio(struct tridentfb_par *par) +{ + /* Goto New Mode */ + vga_io_rseq(0x0B); + + /* Unprotect registers */ + vga_io_wseq(NewMode1, 0x80); + if (!is_oldprotect(par->chip_id)) + vga_io_wseq(Protection, 0x92); + + /* Enable MMIO */ + outb(PCIReg, 0x3D4); + outb(inb(0x3D5) | 0x01, 0x3D5); +} + +static void disable_mmio(struct tridentfb_par *par) +{ + /* Goto New Mode */ + vga_mm_rseq(par->io_virt, 0x0B); + + /* Unprotect registers */ + vga_mm_wseq(par->io_virt, NewMode1, 0x80); + if (!is_oldprotect(par->chip_id)) + vga_mm_wseq(par->io_virt, Protection, 0x92); + + /* Disable MMIO */ + t_outb(par, PCIReg, 0x3D4); + t_outb(par, t_inb(par, 0x3D5) & ~0x01, 0x3D5); +} + +static inline void crtc_unlock(struct tridentfb_par *par) +{ + write3X4(par, VGA_CRTC_V_SYNC_END, + read3X4(par, VGA_CRTC_V_SYNC_END) & 0x7F); +} + +/* Return flat panel's maximum x resolution */ +static int get_nativex(struct tridentfb_par *par) +{ + int x, y, tmp; + + if (nativex) + return nativex; + + tmp = (read3CE(par, VertStretch) >> 4) & 3; + + switch (tmp) { + case 0: + x = 1280; y = 1024; + break; + case 2: + x = 1024; y = 768; + break; + case 3: + x = 800; y = 600; + break; + case 4: + x = 1400; y = 1050; + break; + case 1: + default: + x = 640; y = 480; + break; + } + + output("%dx%d flat panel found\n", x, y); + return x; +} + +/* Set pitch */ +static inline void set_lwidth(struct tridentfb_par *par, int width) +{ + write3X4(par, VGA_CRTC_OFFSET, width & 0xFF); + write3X4(par, AddColReg, + (read3X4(par, AddColReg) & 0xCF) | ((width & 0x300) >> 4)); +} + +/* For resolutions smaller than FP resolution stretch */ +static void screen_stretch(struct tridentfb_par *par) +{ + if (par->chip_id != CYBERBLADEXPAi1) + write3CE(par, BiosReg, 0); + else + write3CE(par, BiosReg, 8); + write3CE(par, VertStretch, (read3CE(par, VertStretch) & 0x7C) | 1); + write3CE(par, HorStretch, (read3CE(par, HorStretch) & 0x7C) | 1); +} + +/* For resolutions smaller than FP resolution center */ +static inline void screen_center(struct tridentfb_par *par) +{ + write3CE(par, VertStretch, (read3CE(par, VertStretch) & 0x7C) | 0x80); + write3CE(par, HorStretch, (read3CE(par, HorStretch) & 0x7C) | 0x80); +} + +/* Address of first shown pixel in display memory */ +static void set_screen_start(struct tridentfb_par *par, int base) +{ + u8 tmp; + write3X4(par, VGA_CRTC_START_LO, base & 0xFF); + write3X4(par, VGA_CRTC_START_HI, (base & 0xFF00) >> 8); + tmp = read3X4(par, CRTCModuleTest) & 0xDF; + write3X4(par, CRTCModuleTest, tmp | ((base & 0x10000) >> 11)); + tmp = read3X4(par, CRTHiOrd) & 0xF8; + write3X4(par, CRTHiOrd, tmp | ((base & 0xE0000) >> 17)); +} + +/* Set dotclock frequency */ +static void set_vclk(struct tridentfb_par *par, unsigned long freq) +{ + int m, n, k; + unsigned long fi, d, di; + unsigned char best_m = 0, best_n = 0, best_k = 0; + unsigned char hi, lo; + unsigned char shift = !is_oldclock(par->chip_id) ? 2 : 1; + + d = 20000; + for (k = shift; k >= 0; k--) + for (m = 1; m < 32; m++) { + n = ((m + 2) << shift) - 8; + for (n = (n < 0 ? 0 : n); n < 122; n++) { + fi = ((14318l * (n + 8)) / (m + 2)) >> k; + di = abs(fi - freq); + if (di < d || (di == d && k == best_k)) { + d = di; + best_n = n; + best_m = m; + best_k = k; + } + if (fi > freq) + break; + } + } + + if (is_oldclock(par->chip_id)) { + lo = best_n | (best_m << 7); + hi = (best_m >> 1) | (best_k << 4); + } else { + lo = best_n; + hi = best_m | (best_k << 6); + } + + if (is3Dchip(par->chip_id)) { + vga_mm_wseq(par->io_virt, ClockHigh, hi); + vga_mm_wseq(par->io_virt, ClockLow, lo); + } else { + t_outb(par, lo, 0x43C8); + t_outb(par, hi, 0x43C9); + } + debug("VCLK = %X %X\n", hi, lo); +} + +/* Set number of lines for flat panels*/ +static void set_number_of_lines(struct tridentfb_par *par, int lines) +{ + int tmp = read3CE(par, CyberEnhance) & 0x8F; + if (lines > 1024) + tmp |= 0x50; + else if (lines > 768) + tmp |= 0x30; + else if (lines > 600) + tmp |= 0x20; + else if (lines > 480) + tmp |= 0x10; + write3CE(par, CyberEnhance, tmp); +} + +/* + * If we see that FP is active we assume we have one. + * Otherwise we have a CRT display. User can override. + */ +static int is_flatpanel(struct tridentfb_par *par) +{ + if (fp) + return 1; + if (crt || !iscyber(par->chip_id)) + return 0; + return (read3CE(par, FPConfig) & 0x10) ? 1 : 0; +} + +/* Try detecting the video memory size */ +static unsigned int get_memsize(struct tridentfb_par *par) +{ + unsigned char tmp, tmp2; + unsigned int k; + + /* If memory size provided by user */ + if (memsize) + k = memsize * Kb; + else + switch (par->chip_id) { + case CYBER9525DVD: + k = 2560 * Kb; + break; + default: + tmp = read3X4(par, SPR) & 0x0F; + switch (tmp) { + + case 0x01: + k = 512 * Kb; + break; + case 0x02: + k = 6 * Mb; /* XP */ + break; + case 0x03: + k = 1 * Mb; + break; + case 0x04: + k = 8 * Mb; + break; + case 0x06: + k = 10 * Mb; /* XP */ + break; + case 0x07: + k = 2 * Mb; + break; + case 0x08: + k = 12 * Mb; /* XP */ + break; + case 0x0A: + k = 14 * Mb; /* XP */ + break; + case 0x0C: + k = 16 * Mb; /* XP */ + break; + case 0x0E: /* XP */ + + tmp2 = vga_mm_rseq(par->io_virt, 0xC1); + switch (tmp2) { + case 0x00: + k = 20 * Mb; + break; + case 0x01: + k = 24 * Mb; + break; + case 0x10: + k = 28 * Mb; + break; + case 0x11: + k = 32 * Mb; + break; + default: + k = 1 * Mb; + break; + } + break; + + case 0x0F: + k = 4 * Mb; + break; + default: + k = 1 * Mb; + break; + } + } + + k -= memdiff * Kb; + output("framebuffer size = %d Kb\n", k / Kb); + return k; +} + +/* See if we can handle the video mode described in var */ +static int tridentfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct tridentfb_par *par = info->par; + int bpp = var->bits_per_pixel; + int line_length; + int ramdac = 230000; /* 230MHz for most 3D chips */ + debug("enter\n"); + + /* check color depth */ + if (bpp == 24) + bpp = var->bits_per_pixel = 32; + if (bpp != 8 && bpp != 16 && bpp != 32) + return -EINVAL; + if (par->chip_id == TGUI9440 && bpp == 32) + return -EINVAL; + /* check whether resolution fits on panel and in memory */ + if (par->flatpanel && nativex && var->xres > nativex) + return -EINVAL; + /* various resolution checks */ + var->xres = (var->xres + 7) & ~0x7; + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + if (var->xres_virtual > 4095 || var->yres > 2048) + return -EINVAL; + /* prevent from position overflow for acceleration */ + if (var->yres_virtual > 0xffff) + return -EINVAL; + line_length = var->xres_virtual * bpp / 8; + + if (!is3Dchip(par->chip_id) && + !(info->flags & FBINFO_HWACCEL_DISABLED)) { + /* acceleration requires line length to be power of 2 */ + if (line_length <= 512) + var->xres_virtual = 512 * 8 / bpp; + else if (line_length <= 1024) + var->xres_virtual = 1024 * 8 / bpp; + else if (line_length <= 2048) + var->xres_virtual = 2048 * 8 / bpp; + else if (line_length <= 4096) + var->xres_virtual = 4096 * 8 / bpp; + else if (line_length <= 8192) + var->xres_virtual = 8192 * 8 / bpp; + else + return -EINVAL; + + line_length = var->xres_virtual * bpp / 8; + } + + /* datasheet specifies how to set panning only up to 4 MB */ + if (line_length * (var->yres_virtual - var->yres) > (4 << 20)) + var->yres_virtual = ((4 << 20) / line_length) + var->yres; + + if (line_length * var->yres_virtual > info->fix.smem_len) + return -EINVAL; + + switch (bpp) { + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green = var->red; + var->blue = var->red; + break; + case 16: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + break; + case 32: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + default: + return -EINVAL; + } + + if (is_xp(par->chip_id)) + ramdac = 350000; + + switch (par->chip_id) { + case TGUI9440: + ramdac = (bpp >= 16) ? 45000 : 90000; + break; + case CYBER9320: + case TGUI9660: + ramdac = 135000; + break; + case PROVIDIA9685: + case CYBER9388: + case CYBER9382: + case CYBER9385: + ramdac = 170000; + break; + } + + /* The clock is doubled for 32 bpp */ + if (bpp == 32) + ramdac /= 2; + + if (PICOS2KHZ(var->pixclock) > ramdac) + return -EINVAL; + + debug("exit\n"); + + return 0; + +} + +/* Pan the display */ +static int tridentfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct tridentfb_par *par = info->par; + unsigned int offset; + + debug("enter\n"); + offset = (var->xoffset + (var->yoffset * info->var.xres_virtual)) + * info->var.bits_per_pixel / 32; + set_screen_start(par, offset); + debug("exit\n"); + return 0; +} + +static inline void shadowmode_on(struct tridentfb_par *par) +{ + write3CE(par, CyberControl, read3CE(par, CyberControl) | 0x81); +} + +static inline void shadowmode_off(struct tridentfb_par *par) +{ + write3CE(par, CyberControl, read3CE(par, CyberControl) & 0x7E); +} + +/* Set the hardware to the requested video mode */ +static int tridentfb_set_par(struct fb_info *info) +{ + struct tridentfb_par *par = info->par; + u32 htotal, hdispend, hsyncstart, hsyncend, hblankstart, hblankend; + u32 vtotal, vdispend, vsyncstart, vsyncend, vblankstart, vblankend; + struct fb_var_screeninfo *var = &info->var; + int bpp = var->bits_per_pixel; + unsigned char tmp; + unsigned long vclk; + + debug("enter\n"); + hdispend = var->xres / 8 - 1; + hsyncstart = (var->xres + var->right_margin) / 8; + hsyncend = (var->xres + var->right_margin + var->hsync_len) / 8; + htotal = (var->xres + var->left_margin + var->right_margin + + var->hsync_len) / 8 - 5; + hblankstart = hdispend + 1; + hblankend = htotal + 3; + + vdispend = var->yres - 1; + vsyncstart = var->yres + var->lower_margin; + vsyncend = vsyncstart + var->vsync_len; + vtotal = var->upper_margin + vsyncend - 2; + vblankstart = vdispend + 1; + vblankend = vtotal; + + if (info->var.vmode & FB_VMODE_INTERLACED) { + vtotal /= 2; + vdispend /= 2; + vsyncstart /= 2; + vsyncend /= 2; + vblankstart /= 2; + vblankend /= 2; + } + + enable_mmio(par); + crtc_unlock(par); + write3CE(par, CyberControl, 8); + tmp = 0xEB; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + tmp &= ~0x40; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + tmp &= ~0x80; + + if (par->flatpanel && var->xres < nativex) { + /* + * on flat panels with native size larger + * than requested resolution decide whether + * we stretch or center + */ + t_outb(par, tmp | 0xC0, VGA_MIS_W); + + shadowmode_on(par); + + if (center) + screen_center(par); + else if (stretch) + screen_stretch(par); + + } else { + t_outb(par, tmp, VGA_MIS_W); + write3CE(par, CyberControl, 8); + } + + /* vertical timing values */ + write3X4(par, VGA_CRTC_V_TOTAL, vtotal & 0xFF); + write3X4(par, VGA_CRTC_V_DISP_END, vdispend & 0xFF); + write3X4(par, VGA_CRTC_V_SYNC_START, vsyncstart & 0xFF); + write3X4(par, VGA_CRTC_V_SYNC_END, (vsyncend & 0x0F)); + write3X4(par, VGA_CRTC_V_BLANK_START, vblankstart & 0xFF); + write3X4(par, VGA_CRTC_V_BLANK_END, vblankend & 0xFF); + + /* horizontal timing values */ + write3X4(par, VGA_CRTC_H_TOTAL, htotal & 0xFF); + write3X4(par, VGA_CRTC_H_DISP, hdispend & 0xFF); + write3X4(par, VGA_CRTC_H_SYNC_START, hsyncstart & 0xFF); + write3X4(par, VGA_CRTC_H_SYNC_END, + (hsyncend & 0x1F) | ((hblankend & 0x20) << 2)); + write3X4(par, VGA_CRTC_H_BLANK_START, hblankstart & 0xFF); + write3X4(par, VGA_CRTC_H_BLANK_END, hblankend & 0x1F); + + /* higher bits of vertical timing values */ + tmp = 0x10; + if (vtotal & 0x100) tmp |= 0x01; + if (vdispend & 0x100) tmp |= 0x02; + if (vsyncstart & 0x100) tmp |= 0x04; + if (vblankstart & 0x100) tmp |= 0x08; + + if (vtotal & 0x200) tmp |= 0x20; + if (vdispend & 0x200) tmp |= 0x40; + if (vsyncstart & 0x200) tmp |= 0x80; + write3X4(par, VGA_CRTC_OVERFLOW, tmp); + + tmp = read3X4(par, CRTHiOrd) & 0x07; + tmp |= 0x08; /* line compare bit 10 */ + if (vtotal & 0x400) tmp |= 0x80; + if (vblankstart & 0x400) tmp |= 0x40; + if (vsyncstart & 0x400) tmp |= 0x20; + if (vdispend & 0x400) tmp |= 0x10; + write3X4(par, CRTHiOrd, tmp); + + tmp = (htotal >> 8) & 0x01; + tmp |= (hdispend >> 7) & 0x02; + tmp |= (hsyncstart >> 5) & 0x08; + tmp |= (hblankstart >> 4) & 0x10; + write3X4(par, HorizOverflow, tmp); + + tmp = 0x40; + if (vblankstart & 0x200) tmp |= 0x20; +//FIXME if (info->var.vmode & FB_VMODE_DOUBLE) tmp |= 0x80; /* double scan for 200 line modes */ + write3X4(par, VGA_CRTC_MAX_SCAN, tmp); + + write3X4(par, VGA_CRTC_LINE_COMPARE, 0xFF); + write3X4(par, VGA_CRTC_PRESET_ROW, 0); + write3X4(par, VGA_CRTC_MODE, 0xC3); + + write3X4(par, LinearAddReg, 0x20); /* enable linear addressing */ + + tmp = (info->var.vmode & FB_VMODE_INTERLACED) ? 0x84 : 0x80; + /* enable access extended memory */ + write3X4(par, CRTCModuleTest, tmp); + tmp = read3CE(par, MiscIntContReg) & ~0x4; + if (info->var.vmode & FB_VMODE_INTERLACED) + tmp |= 0x4; + write3CE(par, MiscIntContReg, tmp); + + /* enable GE for text acceleration */ + write3X4(par, GraphEngReg, 0x80); + + switch (bpp) { + case 8: + tmp = 0x00; + break; + case 16: + tmp = 0x05; + break; + case 24: + tmp = 0x29; + break; + case 32: + tmp = 0x09; + break; + } + + write3X4(par, PixelBusReg, tmp); + + tmp = read3X4(par, DRAMControl); + if (!is_oldprotect(par->chip_id)) + tmp |= 0x10; + if (iscyber(par->chip_id)) + tmp |= 0x20; + write3X4(par, DRAMControl, tmp); /* both IO, linear enable */ + + write3X4(par, InterfaceSel, read3X4(par, InterfaceSel) | 0x40); + if (!is_xp(par->chip_id)) + write3X4(par, Performance, read3X4(par, Performance) | 0x10); + /* MMIO & PCI read and write burst enable */ + if (par->chip_id != TGUI9440 && par->chip_id != IMAGE975) + write3X4(par, PCIReg, read3X4(par, PCIReg) | 0x06); + + vga_mm_wseq(par->io_virt, 0, 3); + vga_mm_wseq(par->io_virt, 1, 1); /* set char clock 8 dots wide */ + /* enable 4 maps because needed in chain4 mode */ + vga_mm_wseq(par->io_virt, 2, 0x0F); + vga_mm_wseq(par->io_virt, 3, 0); + vga_mm_wseq(par->io_virt, 4, 0x0E); /* memory mode enable bitmaps ?? */ + + /* convert from picoseconds to kHz */ + vclk = PICOS2KHZ(info->var.pixclock); + + /* divide clock by 2 if 32bpp chain4 mode display and CPU path */ + tmp = read3CE(par, MiscExtFunc) & 0xF0; + if (bpp == 32 || (par->chip_id == TGUI9440 && bpp == 16)) { + tmp |= 8; + vclk *= 2; + } + set_vclk(par, vclk); + write3CE(par, MiscExtFunc, tmp | 0x12); + write3CE(par, 0x5, 0x40); /* no CGA compat, allow 256 col */ + write3CE(par, 0x6, 0x05); /* graphics mode */ + write3CE(par, 0x7, 0x0F); /* planes? */ + + /* graphics mode and support 256 color modes */ + writeAttr(par, 0x10, 0x41); + writeAttr(par, 0x12, 0x0F); /* planes */ + writeAttr(par, 0x13, 0); /* horizontal pel panning */ + + /* colors */ + for (tmp = 0; tmp < 0x10; tmp++) + writeAttr(par, tmp, tmp); + fb_readb(par->io_virt + VGA_IS1_RC); /* flip-flop to index */ + t_outb(par, 0x20, VGA_ATT_W); /* enable attr */ + + switch (bpp) { + case 8: + tmp = 0; + break; + case 16: + tmp = 0x30; + break; + case 24: + case 32: + tmp = 0xD0; + break; + } + + t_inb(par, VGA_PEL_IW); + t_inb(par, VGA_PEL_MSK); + t_inb(par, VGA_PEL_MSK); + t_inb(par, VGA_PEL_MSK); + t_inb(par, VGA_PEL_MSK); + t_outb(par, tmp, VGA_PEL_MSK); + t_inb(par, VGA_PEL_IW); + + if (par->flatpanel) + set_number_of_lines(par, info->var.yres); + info->fix.line_length = info->var.xres_virtual * bpp / 8; + set_lwidth(par, info->fix.line_length / 8); + + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) + par->init_accel(par, info->var.xres_virtual, bpp); + + info->fix.visual = (bpp == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->cmap.len = (bpp == 8) ? 256 : 16; + debug("exit\n"); + return 0; +} + +/* Set one color register */ +static int tridentfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + int bpp = info->var.bits_per_pixel; + struct tridentfb_par *par = info->par; + + if (regno >= info->cmap.len) + return 1; + + if (bpp == 8) { + t_outb(par, 0xFF, VGA_PEL_MSK); + t_outb(par, regno, VGA_PEL_IW); + + t_outb(par, red >> 10, VGA_PEL_D); + t_outb(par, green >> 10, VGA_PEL_D); + t_outb(par, blue >> 10, VGA_PEL_D); + + } else if (regno < 16) { + if (bpp == 16) { /* RGB 565 */ + u32 col; + + col = (red & 0xF800) | ((green & 0xFC00) >> 5) | + ((blue & 0xF800) >> 11); + col |= col << 16; + ((u32 *)(info->pseudo_palette))[regno] = col; + } else if (bpp == 32) /* ARGB 8888 */ + ((u32 *)info->pseudo_palette)[regno] = + ((transp & 0xFF00) << 16) | + ((red & 0xFF00) << 8) | + ((green & 0xFF00)) | + ((blue & 0xFF00) >> 8); + } + + return 0; +} + +/* Try blanking the screen. For flat panels it does nothing */ +static int tridentfb_blank(int blank_mode, struct fb_info *info) +{ + unsigned char PMCont, DPMSCont; + struct tridentfb_par *par = info->par; + + debug("enter\n"); + if (par->flatpanel) + return 0; + t_outb(par, 0x04, 0x83C8); /* Read DPMS Control */ + PMCont = t_inb(par, 0x83C6) & 0xFC; + DPMSCont = read3CE(par, PowerStatus) & 0xFC; + switch (blank_mode) { + case FB_BLANK_UNBLANK: + /* Screen: On, HSync: On, VSync: On */ + case FB_BLANK_NORMAL: + /* Screen: Off, HSync: On, VSync: On */ + PMCont |= 0x03; + DPMSCont |= 0x00; + break; + case FB_BLANK_HSYNC_SUSPEND: + /* Screen: Off, HSync: Off, VSync: On */ + PMCont |= 0x02; + DPMSCont |= 0x01; + break; + case FB_BLANK_VSYNC_SUSPEND: + /* Screen: Off, HSync: On, VSync: Off */ + PMCont |= 0x02; + DPMSCont |= 0x02; + break; + case FB_BLANK_POWERDOWN: + /* Screen: Off, HSync: Off, VSync: Off */ + PMCont |= 0x00; + DPMSCont |= 0x03; + break; + } + + write3CE(par, PowerStatus, DPMSCont); + t_outb(par, 4, 0x83C8); + t_outb(par, PMCont, 0x83C6); + + debug("exit\n"); + + /* let fbcon do a softblank for us */ + return (blank_mode == FB_BLANK_NORMAL) ? 1 : 0; +} + +static struct fb_ops tridentfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = tridentfb_setcolreg, + .fb_pan_display = tridentfb_pan_display, + .fb_blank = tridentfb_blank, + .fb_check_var = tridentfb_check_var, + .fb_set_par = tridentfb_set_par, + .fb_fillrect = tridentfb_fillrect, + .fb_copyarea = tridentfb_copyarea, + .fb_imageblit = tridentfb_imageblit, + .fb_sync = tridentfb_sync, +}; + +static int trident_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + int err; + unsigned char revision; + struct fb_info *info; + struct tridentfb_par *default_par; + int chip3D; + int chip_id; + + err = pci_enable_device(dev); + if (err) + return err; + + info = framebuffer_alloc(sizeof(struct tridentfb_par), &dev->dev); + if (!info) + return -ENOMEM; + default_par = info->par; + + chip_id = id->device; + + /* If PCI id is 0x9660 then further detect chip type */ + + if (chip_id == TGUI9660) { + revision = vga_io_rseq(RevisionID); + + switch (revision) { + case 0x21: + chip_id = PROVIDIA9685; + break; + case 0x22: + case 0x23: + chip_id = CYBER9397; + break; + case 0x2A: + chip_id = CYBER9397DVD; + break; + case 0x30: + case 0x33: + case 0x34: + case 0x35: + case 0x38: + case 0x3A: + case 0xB3: + chip_id = CYBER9385; + break; + case 0x40 ... 0x43: + chip_id = CYBER9382; + break; + case 0x4A: + chip_id = CYBER9388; + break; + default: + break; + } + } + + chip3D = is3Dchip(chip_id); + + if (is_xp(chip_id)) { + default_par->init_accel = xp_init_accel; + default_par->wait_engine = xp_wait_engine; + default_par->fill_rect = xp_fill_rect; + default_par->copy_rect = xp_copy_rect; + tridentfb_fix.accel = FB_ACCEL_TRIDENT_BLADEXP; + } else if (is_blade(chip_id)) { + default_par->init_accel = blade_init_accel; + default_par->wait_engine = blade_wait_engine; + default_par->fill_rect = blade_fill_rect; + default_par->copy_rect = blade_copy_rect; + default_par->image_blit = blade_image_blit; + tridentfb_fix.accel = FB_ACCEL_TRIDENT_BLADE3D; + } else if (chip3D) { /* 3DImage family left */ + default_par->init_accel = image_init_accel; + default_par->wait_engine = image_wait_engine; + default_par->fill_rect = image_fill_rect; + default_par->copy_rect = image_copy_rect; + tridentfb_fix.accel = FB_ACCEL_TRIDENT_3DIMAGE; + } else { /* TGUI 9440/96XX family */ + default_par->init_accel = tgui_init_accel; + default_par->wait_engine = xp_wait_engine; + default_par->fill_rect = tgui_fill_rect; + default_par->copy_rect = tgui_copy_rect; + tridentfb_fix.accel = FB_ACCEL_TRIDENT_TGUI; + } + + default_par->chip_id = chip_id; + + /* setup MMIO region */ + tridentfb_fix.mmio_start = pci_resource_start(dev, 1); + tridentfb_fix.mmio_len = pci_resource_len(dev, 1); + + if (!request_mem_region(tridentfb_fix.mmio_start, + tridentfb_fix.mmio_len, "tridentfb")) { + debug("request_region failed!\n"); + framebuffer_release(info); + return -1; + } + + default_par->io_virt = ioremap_nocache(tridentfb_fix.mmio_start, + tridentfb_fix.mmio_len); + + if (!default_par->io_virt) { + debug("ioremap failed\n"); + err = -1; + goto out_unmap1; + } + + enable_mmio(default_par); + + /* setup framebuffer memory */ + tridentfb_fix.smem_start = pci_resource_start(dev, 0); + tridentfb_fix.smem_len = get_memsize(default_par); + + if (!request_mem_region(tridentfb_fix.smem_start, + tridentfb_fix.smem_len, "tridentfb")) { + debug("request_mem_region failed!\n"); + disable_mmio(info->par); + err = -1; + goto out_unmap1; + } + + info->screen_base = ioremap_nocache(tridentfb_fix.smem_start, + tridentfb_fix.smem_len); + + if (!info->screen_base) { + debug("ioremap failed\n"); + err = -1; + goto out_unmap2; + } + + default_par->flatpanel = is_flatpanel(default_par); + + if (default_par->flatpanel) + nativex = get_nativex(default_par); + + info->fix = tridentfb_fix; + info->fbops = &tridentfb_ops; + info->pseudo_palette = default_par->pseudo_pal; + + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + if (!noaccel && default_par->init_accel) { + info->flags &= ~FBINFO_HWACCEL_DISABLED; + info->flags |= FBINFO_HWACCEL_COPYAREA; + info->flags |= FBINFO_HWACCEL_FILLRECT; + } else + info->flags |= FBINFO_HWACCEL_DISABLED; + + if (is_blade(chip_id) && chip_id != BLADE3D) + info->flags |= FBINFO_READS_FAST; + + info->pixmap.addr = kmalloc(4096, GFP_KERNEL); + if (!info->pixmap.addr) { + err = -ENOMEM; + goto out_unmap2; + } + + info->pixmap.size = 4096; + info->pixmap.buf_align = 4; + info->pixmap.scan_align = 1; + info->pixmap.access_align = 32; + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + if (default_par->image_blit) { + info->flags |= FBINFO_HWACCEL_IMAGEBLIT; + info->pixmap.scan_align = 4; + } + + if (noaccel) { + printk(KERN_DEBUG "disabling acceleration\n"); + info->flags |= FBINFO_HWACCEL_DISABLED; + info->pixmap.scan_align = 1; + } + + if (!fb_find_mode(&info->var, info, + mode_option, NULL, 0, NULL, bpp)) { + err = -EINVAL; + goto out_unmap2; + } + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err < 0) + goto out_unmap2; + + info->var.activate |= FB_ACTIVATE_NOW; + info->device = &dev->dev; + if (register_framebuffer(info) < 0) { + printk(KERN_ERR "tridentfb: could not register framebuffer\n"); + fb_dealloc_cmap(&info->cmap); + err = -EINVAL; + goto out_unmap2; + } + output("fb%d: %s frame buffer device %dx%d-%dbpp\n", + info->node, info->fix.id, info->var.xres, + info->var.yres, info->var.bits_per_pixel); + + pci_set_drvdata(dev, info); + return 0; + +out_unmap2: + kfree(info->pixmap.addr); + if (info->screen_base) + iounmap(info->screen_base); + release_mem_region(tridentfb_fix.smem_start, tridentfb_fix.smem_len); + disable_mmio(info->par); +out_unmap1: + if (default_par->io_virt) + iounmap(default_par->io_virt); + release_mem_region(tridentfb_fix.mmio_start, tridentfb_fix.mmio_len); + framebuffer_release(info); + return err; +} + +static void trident_pci_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct tridentfb_par *par = info->par; + + unregister_framebuffer(info); + iounmap(par->io_virt); + iounmap(info->screen_base); + release_mem_region(tridentfb_fix.smem_start, tridentfb_fix.smem_len); + release_mem_region(tridentfb_fix.mmio_start, tridentfb_fix.mmio_len); + kfree(info->pixmap.addr); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + +/* List of boards that we are trying to support */ +static struct pci_device_id trident_devices[] = { + {PCI_VENDOR_ID_TRIDENT, BLADE3D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEi7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEi7D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEi1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEi1D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEAi1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEAi1D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEE4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, TGUI9440, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, TGUI9660, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, IMAGE975, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, IMAGE985, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBER9320, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBER9388, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBER9520, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBER9525DVD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBER9397, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBER9397DVD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEXPAi1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEXPm8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_TRIDENT, CYBERBLADEXPm16, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, trident_devices); + +static struct pci_driver tridentfb_pci_driver = { + .name = "tridentfb", + .id_table = trident_devices, + .probe = trident_pci_probe, + .remove = trident_pci_remove, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +#ifndef MODULE +static int __init tridentfb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "noaccel", 7)) + noaccel = 1; + else if (!strncmp(opt, "fp", 2)) + fp = 1; + else if (!strncmp(opt, "crt", 3)) + fp = 0; + else if (!strncmp(opt, "bpp=", 4)) + bpp = simple_strtoul(opt + 4, NULL, 0); + else if (!strncmp(opt, "center", 6)) + center = 1; + else if (!strncmp(opt, "stretch", 7)) + stretch = 1; + else if (!strncmp(opt, "memsize=", 8)) + memsize = simple_strtoul(opt + 8, NULL, 0); + else if (!strncmp(opt, "memdiff=", 8)) + memdiff = simple_strtoul(opt + 8, NULL, 0); + else if (!strncmp(opt, "nativex=", 8)) + nativex = simple_strtoul(opt + 8, NULL, 0); + else + mode_option = opt; + } + return 0; +} +#endif + +static int __init tridentfb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("tridentfb", &option)) + return -ENODEV; + tridentfb_setup(option); +#endif + return pci_register_driver(&tridentfb_pci_driver); +} + +static void __exit tridentfb_exit(void) +{ + pci_unregister_driver(&tridentfb_pci_driver); +} + +module_init(tridentfb_init); +module_exit(tridentfb_exit); + +MODULE_AUTHOR("Jani Monoses <jani@iv.ro>"); +MODULE_DESCRIPTION("Framebuffer driver for Trident cards"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cyblafb"); + diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c new file mode 100644 index 000000000000..77b890e4d296 --- /dev/null +++ b/drivers/video/fbdev/udlfb.c @@ -0,0 +1,1985 @@ +/* + * udlfb.c -- Framebuffer driver for DisplayLink USB controller + * + * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it> + * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com> + * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file COPYING in the main directory of this archive for + * more details. + * + * Layout is based on skeletonfb by James Simmons and Geert Uytterhoeven, + * usb-skeleton by GregKH. + * + * Device-specific portions based on information from Displaylink, with work + * from Florian Echtler, Henrik Bjerregaard Pedersen, and others. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/uaccess.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/prefetch.h> +#include <linux/delay.h> +#include <video/udlfb.h> +#include "edid.h" + +static struct fb_fix_screeninfo dlfb_fix = { + .id = "udlfb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +static const u32 udlfb_info_flags = FBINFO_DEFAULT | FBINFO_READS_FAST | + FBINFO_VIRTFB | + FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_COPYAREA | FBINFO_MISC_ALWAYS_SETPAR; + +/* + * There are many DisplayLink-based graphics products, all with unique PIDs. + * So we match on DisplayLink's VID + Vendor-Defined Interface Class (0xff) + * We also require a match on SubClass (0x00) and Protocol (0x00), + * which is compatible with all known USB 2.0 era graphics chips and firmware, + * but allows DisplayLink to increment those for any future incompatible chips + */ +static struct usb_device_id id_table[] = { + {.idVendor = 0x17e9, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL, + }, + {}, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* module options */ +static bool console = 1; /* Allow fbcon to open framebuffer */ +static bool fb_defio = 1; /* Detect mmap writes using page faults */ +static bool shadow = 1; /* Optionally disable shadow framebuffer */ +static int pixel_limit; /* Optionally force a pixel resolution limit */ + +/* dlfb keeps a list of urbs for efficient bulk transfers */ +static void dlfb_urb_completion(struct urb *urb); +static struct urb *dlfb_get_urb(struct dlfb_data *dev); +static int dlfb_submit_urb(struct dlfb_data *dev, struct urb * urb, size_t len); +static int dlfb_alloc_urb_list(struct dlfb_data *dev, int count, size_t size); +static void dlfb_free_urb_list(struct dlfb_data *dev); + +/* + * All DisplayLink bulk operations start with 0xAF, followed by specific code + * All operations are written to buffers which then later get sent to device + */ +static char *dlfb_set_register(char *buf, u8 reg, u8 val) +{ + *buf++ = 0xAF; + *buf++ = 0x20; + *buf++ = reg; + *buf++ = val; + return buf; +} + +static char *dlfb_vidreg_lock(char *buf) +{ + return dlfb_set_register(buf, 0xFF, 0x00); +} + +static char *dlfb_vidreg_unlock(char *buf) +{ + return dlfb_set_register(buf, 0xFF, 0xFF); +} + +/* + * Map FB_BLANK_* to DisplayLink register + * DLReg FB_BLANK_* + * ----- ----------------------------- + * 0x00 FB_BLANK_UNBLANK (0) + * 0x01 FB_BLANK (1) + * 0x03 FB_BLANK_VSYNC_SUSPEND (2) + * 0x05 FB_BLANK_HSYNC_SUSPEND (3) + * 0x07 FB_BLANK_POWERDOWN (4) Note: requires modeset to come back + */ +static char *dlfb_blanking(char *buf, int fb_blank) +{ + u8 reg; + + switch (fb_blank) { + case FB_BLANK_POWERDOWN: + reg = 0x07; + break; + case FB_BLANK_HSYNC_SUSPEND: + reg = 0x05; + break; + case FB_BLANK_VSYNC_SUSPEND: + reg = 0x03; + break; + case FB_BLANK_NORMAL: + reg = 0x01; + break; + default: + reg = 0x00; + } + + buf = dlfb_set_register(buf, 0x1F, reg); + + return buf; +} + +static char *dlfb_set_color_depth(char *buf, u8 selection) +{ + return dlfb_set_register(buf, 0x00, selection); +} + +static char *dlfb_set_base16bpp(char *wrptr, u32 base) +{ + /* the base pointer is 16 bits wide, 0x20 is hi byte. */ + wrptr = dlfb_set_register(wrptr, 0x20, base >> 16); + wrptr = dlfb_set_register(wrptr, 0x21, base >> 8); + return dlfb_set_register(wrptr, 0x22, base); +} + +/* + * DisplayLink HW has separate 16bpp and 8bpp framebuffers. + * In 24bpp modes, the low 323 RGB bits go in the 8bpp framebuffer + */ +static char *dlfb_set_base8bpp(char *wrptr, u32 base) +{ + wrptr = dlfb_set_register(wrptr, 0x26, base >> 16); + wrptr = dlfb_set_register(wrptr, 0x27, base >> 8); + return dlfb_set_register(wrptr, 0x28, base); +} + +static char *dlfb_set_register_16(char *wrptr, u8 reg, u16 value) +{ + wrptr = dlfb_set_register(wrptr, reg, value >> 8); + return dlfb_set_register(wrptr, reg+1, value); +} + +/* + * This is kind of weird because the controller takes some + * register values in a different byte order than other registers. + */ +static char *dlfb_set_register_16be(char *wrptr, u8 reg, u16 value) +{ + wrptr = dlfb_set_register(wrptr, reg, value); + return dlfb_set_register(wrptr, reg+1, value >> 8); +} + +/* + * LFSR is linear feedback shift register. The reason we have this is + * because the display controller needs to minimize the clock depth of + * various counters used in the display path. So this code reverses the + * provided value into the lfsr16 value by counting backwards to get + * the value that needs to be set in the hardware comparator to get the + * same actual count. This makes sense once you read above a couple of + * times and think about it from a hardware perspective. + */ +static u16 dlfb_lfsr16(u16 actual_count) +{ + u32 lv = 0xFFFF; /* This is the lfsr value that the hw starts with */ + + while (actual_count--) { + lv = ((lv << 1) | + (((lv >> 15) ^ (lv >> 4) ^ (lv >> 2) ^ (lv >> 1)) & 1)) + & 0xFFFF; + } + + return (u16) lv; +} + +/* + * This does LFSR conversion on the value that is to be written. + * See LFSR explanation above for more detail. + */ +static char *dlfb_set_register_lfsr16(char *wrptr, u8 reg, u16 value) +{ + return dlfb_set_register_16(wrptr, reg, dlfb_lfsr16(value)); +} + +/* + * This takes a standard fbdev screeninfo struct and all of its monitor mode + * details and converts them into the DisplayLink equivalent register commands. + */ +static char *dlfb_set_vid_cmds(char *wrptr, struct fb_var_screeninfo *var) +{ + u16 xds, yds; + u16 xde, yde; + u16 yec; + + /* x display start */ + xds = var->left_margin + var->hsync_len; + wrptr = dlfb_set_register_lfsr16(wrptr, 0x01, xds); + /* x display end */ + xde = xds + var->xres; + wrptr = dlfb_set_register_lfsr16(wrptr, 0x03, xde); + + /* y display start */ + yds = var->upper_margin + var->vsync_len; + wrptr = dlfb_set_register_lfsr16(wrptr, 0x05, yds); + /* y display end */ + yde = yds + var->yres; + wrptr = dlfb_set_register_lfsr16(wrptr, 0x07, yde); + + /* x end count is active + blanking - 1 */ + wrptr = dlfb_set_register_lfsr16(wrptr, 0x09, + xde + var->right_margin - 1); + + /* libdlo hardcodes hsync start to 1 */ + wrptr = dlfb_set_register_lfsr16(wrptr, 0x0B, 1); + + /* hsync end is width of sync pulse + 1 */ + wrptr = dlfb_set_register_lfsr16(wrptr, 0x0D, var->hsync_len + 1); + + /* hpixels is active pixels */ + wrptr = dlfb_set_register_16(wrptr, 0x0F, var->xres); + + /* yendcount is vertical active + vertical blanking */ + yec = var->yres + var->upper_margin + var->lower_margin + + var->vsync_len; + wrptr = dlfb_set_register_lfsr16(wrptr, 0x11, yec); + + /* libdlo hardcodes vsync start to 0 */ + wrptr = dlfb_set_register_lfsr16(wrptr, 0x13, 0); + + /* vsync end is width of vsync pulse */ + wrptr = dlfb_set_register_lfsr16(wrptr, 0x15, var->vsync_len); + + /* vpixels is active pixels */ + wrptr = dlfb_set_register_16(wrptr, 0x17, var->yres); + + /* convert picoseconds to 5kHz multiple for pclk5k = x * 1E12/5k */ + wrptr = dlfb_set_register_16be(wrptr, 0x1B, + 200*1000*1000/var->pixclock); + + return wrptr; +} + +/* + * This takes a standard fbdev screeninfo struct that was fetched or prepared + * and then generates the appropriate command sequence that then drives the + * display controller. + */ +static int dlfb_set_video_mode(struct dlfb_data *dev, + struct fb_var_screeninfo *var) +{ + char *buf; + char *wrptr; + int retval = 0; + int writesize; + struct urb *urb; + + if (!atomic_read(&dev->usb_active)) + return -EPERM; + + urb = dlfb_get_urb(dev); + if (!urb) + return -ENOMEM; + + buf = (char *) urb->transfer_buffer; + + /* + * This first section has to do with setting the base address on the + * controller * associated with the display. There are 2 base + * pointers, currently, we only * use the 16 bpp segment. + */ + wrptr = dlfb_vidreg_lock(buf); + wrptr = dlfb_set_color_depth(wrptr, 0x00); + /* set base for 16bpp segment to 0 */ + wrptr = dlfb_set_base16bpp(wrptr, 0); + /* set base for 8bpp segment to end of fb */ + wrptr = dlfb_set_base8bpp(wrptr, dev->info->fix.smem_len); + + wrptr = dlfb_set_vid_cmds(wrptr, var); + wrptr = dlfb_blanking(wrptr, FB_BLANK_UNBLANK); + wrptr = dlfb_vidreg_unlock(wrptr); + + writesize = wrptr - buf; + + retval = dlfb_submit_urb(dev, urb, writesize); + + dev->blank_mode = FB_BLANK_UNBLANK; + + return retval; +} + +static int dlfb_ops_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + if (size > info->fix.smem_len) + return -EINVAL; + if (offset > info->fix.smem_len - size) + return -EINVAL; + + pos = (unsigned long)info->fix.smem_start + offset; + + pr_notice("mmap() framebuffer addr:%lu size:%lu\n", + pos, size); + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +/* + * Trims identical data from front and back of line + * Sets new front buffer address and width + * And returns byte count of identical pixels + * Assumes CPU natural alignment (unsigned long) + * for back and front buffer ptrs and width + */ +static int dlfb_trim_hline(const u8 *bback, const u8 **bfront, int *width_bytes) +{ + int j, k; + const unsigned long *back = (const unsigned long *) bback; + const unsigned long *front = (const unsigned long *) *bfront; + const int width = *width_bytes / sizeof(unsigned long); + int identical = width; + int start = width; + int end = width; + + prefetch((void *) front); + prefetch((void *) back); + + for (j = 0; j < width; j++) { + if (back[j] != front[j]) { + start = j; + break; + } + } + + for (k = width - 1; k > j; k--) { + if (back[k] != front[k]) { + end = k+1; + break; + } + } + + identical = start + (width - end); + *bfront = (u8 *) &front[start]; + *width_bytes = (end - start) * sizeof(unsigned long); + + return identical * sizeof(unsigned long); +} + +/* + * Render a command stream for an encoded horizontal line segment of pixels. + * + * A command buffer holds several commands. + * It always begins with a fresh command header + * (the protocol doesn't require this, but we enforce it to allow + * multiple buffers to be potentially encoded and sent in parallel). + * A single command encodes one contiguous horizontal line of pixels + * + * The function relies on the client to do all allocation, so that + * rendering can be done directly to output buffers (e.g. USB URBs). + * The function fills the supplied command buffer, providing information + * on where it left off, so the client may call in again with additional + * buffers if the line will take several buffers to complete. + * + * A single command can transmit a maximum of 256 pixels, + * regardless of the compression ratio (protocol design limit). + * To the hardware, 0 for a size byte means 256 + * + * Rather than 256 pixel commands which are either rl or raw encoded, + * the rlx command simply assumes alternating raw and rl spans within one cmd. + * This has a slightly larger header overhead, but produces more even results. + * It also processes all data (read and write) in a single pass. + * Performance benchmarks of common cases show it having just slightly better + * compression than 256 pixel raw or rle commands, with similar CPU consumpion. + * But for very rl friendly data, will compress not quite as well. + */ +static void dlfb_compress_hline( + const uint16_t **pixel_start_ptr, + const uint16_t *const pixel_end, + uint32_t *device_address_ptr, + uint8_t **command_buffer_ptr, + const uint8_t *const cmd_buffer_end) +{ + const uint16_t *pixel = *pixel_start_ptr; + uint32_t dev_addr = *device_address_ptr; + uint8_t *cmd = *command_buffer_ptr; + const int bpp = 2; + + while ((pixel_end > pixel) && + (cmd_buffer_end - MIN_RLX_CMD_BYTES > cmd)) { + uint8_t *raw_pixels_count_byte = NULL; + uint8_t *cmd_pixels_count_byte = NULL; + const uint16_t *raw_pixel_start = NULL; + const uint16_t *cmd_pixel_start, *cmd_pixel_end = NULL; + + prefetchw((void *) cmd); /* pull in one cache line at least */ + + *cmd++ = 0xAF; + *cmd++ = 0x6B; + *cmd++ = (uint8_t) ((dev_addr >> 16) & 0xFF); + *cmd++ = (uint8_t) ((dev_addr >> 8) & 0xFF); + *cmd++ = (uint8_t) ((dev_addr) & 0xFF); + + cmd_pixels_count_byte = cmd++; /* we'll know this later */ + cmd_pixel_start = pixel; + + raw_pixels_count_byte = cmd++; /* we'll know this later */ + raw_pixel_start = pixel; + + cmd_pixel_end = pixel + min(MAX_CMD_PIXELS + 1, + min((int)(pixel_end - pixel), + (int)(cmd_buffer_end - cmd) / bpp)); + + prefetch_range((void *) pixel, (cmd_pixel_end - pixel) * bpp); + + while (pixel < cmd_pixel_end) { + const uint16_t * const repeating_pixel = pixel; + + *(uint16_t *)cmd = cpu_to_be16p(pixel); + cmd += 2; + pixel++; + + if (unlikely((pixel < cmd_pixel_end) && + (*pixel == *repeating_pixel))) { + /* go back and fill in raw pixel count */ + *raw_pixels_count_byte = ((repeating_pixel - + raw_pixel_start) + 1) & 0xFF; + + while ((pixel < cmd_pixel_end) + && (*pixel == *repeating_pixel)) { + pixel++; + } + + /* immediately after raw data is repeat byte */ + *cmd++ = ((pixel - repeating_pixel) - 1) & 0xFF; + + /* Then start another raw pixel span */ + raw_pixel_start = pixel; + raw_pixels_count_byte = cmd++; + } + } + + if (pixel > raw_pixel_start) { + /* finalize last RAW span */ + *raw_pixels_count_byte = (pixel-raw_pixel_start) & 0xFF; + } + + *cmd_pixels_count_byte = (pixel - cmd_pixel_start) & 0xFF; + dev_addr += (pixel - cmd_pixel_start) * bpp; + } + + if (cmd_buffer_end <= MIN_RLX_CMD_BYTES + cmd) { + /* Fill leftover bytes with no-ops */ + if (cmd_buffer_end > cmd) + memset(cmd, 0xAF, cmd_buffer_end - cmd); + cmd = (uint8_t *) cmd_buffer_end; + } + + *command_buffer_ptr = cmd; + *pixel_start_ptr = pixel; + *device_address_ptr = dev_addr; + + return; +} + +/* + * There are 3 copies of every pixel: The front buffer that the fbdev + * client renders to, the actual framebuffer across the USB bus in hardware + * (that we can only write to, slowly, and can never read), and (optionally) + * our shadow copy that tracks what's been sent to that hardware buffer. + */ +static int dlfb_render_hline(struct dlfb_data *dev, struct urb **urb_ptr, + const char *front, char **urb_buf_ptr, + u32 byte_offset, u32 byte_width, + int *ident_ptr, int *sent_ptr) +{ + const u8 *line_start, *line_end, *next_pixel; + u32 dev_addr = dev->base16 + byte_offset; + struct urb *urb = *urb_ptr; + u8 *cmd = *urb_buf_ptr; + u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length; + + line_start = (u8 *) (front + byte_offset); + next_pixel = line_start; + line_end = next_pixel + byte_width; + + if (dev->backing_buffer) { + int offset; + const u8 *back_start = (u8 *) (dev->backing_buffer + + byte_offset); + + *ident_ptr += dlfb_trim_hline(back_start, &next_pixel, + &byte_width); + + offset = next_pixel - line_start; + line_end = next_pixel + byte_width; + dev_addr += offset; + back_start += offset; + line_start += offset; + + memcpy((char *)back_start, (char *) line_start, + byte_width); + } + + while (next_pixel < line_end) { + + dlfb_compress_hline((const uint16_t **) &next_pixel, + (const uint16_t *) line_end, &dev_addr, + (u8 **) &cmd, (u8 *) cmd_end); + + if (cmd >= cmd_end) { + int len = cmd - (u8 *) urb->transfer_buffer; + if (dlfb_submit_urb(dev, urb, len)) + return 1; /* lost pixels is set */ + *sent_ptr += len; + urb = dlfb_get_urb(dev); + if (!urb) + return 1; /* lost_pixels is set */ + *urb_ptr = urb; + cmd = urb->transfer_buffer; + cmd_end = &cmd[urb->transfer_buffer_length]; + } + } + + *urb_buf_ptr = cmd; + + return 0; +} + +static int dlfb_handle_damage(struct dlfb_data *dev, int x, int y, + int width, int height, char *data) +{ + int i, ret; + char *cmd; + cycles_t start_cycles, end_cycles; + int bytes_sent = 0; + int bytes_identical = 0; + struct urb *urb; + int aligned_x; + + start_cycles = get_cycles(); + + aligned_x = DL_ALIGN_DOWN(x, sizeof(unsigned long)); + width = DL_ALIGN_UP(width + (x-aligned_x), sizeof(unsigned long)); + x = aligned_x; + + if ((width <= 0) || + (x + width > dev->info->var.xres) || + (y + height > dev->info->var.yres)) + return -EINVAL; + + if (!atomic_read(&dev->usb_active)) + return 0; + + urb = dlfb_get_urb(dev); + if (!urb) + return 0; + cmd = urb->transfer_buffer; + + for (i = y; i < y + height ; i++) { + const int line_offset = dev->info->fix.line_length * i; + const int byte_offset = line_offset + (x * BPP); + + if (dlfb_render_hline(dev, &urb, + (char *) dev->info->fix.smem_start, + &cmd, byte_offset, width * BPP, + &bytes_identical, &bytes_sent)) + goto error; + } + + if (cmd > (char *) urb->transfer_buffer) { + /* Send partial buffer remaining before exiting */ + int len = cmd - (char *) urb->transfer_buffer; + ret = dlfb_submit_urb(dev, urb, len); + bytes_sent += len; + } else + dlfb_urb_completion(urb); + +error: + atomic_add(bytes_sent, &dev->bytes_sent); + atomic_add(bytes_identical, &dev->bytes_identical); + atomic_add(width*height*2, &dev->bytes_rendered); + end_cycles = get_cycles(); + atomic_add(((unsigned int) ((end_cycles - start_cycles) + >> 10)), /* Kcycles */ + &dev->cpu_kcycles_used); + + return 0; +} + +/* + * Path triggered by usermode clients who write to filesystem + * e.g. cat filename > /dev/fb1 + * Not used by X Windows or text-mode console. But useful for testing. + * Slow because of extra copy and we must assume all pixels dirty. + */ +static ssize_t dlfb_ops_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t result; + struct dlfb_data *dev = info->par; + u32 offset = (u32) *ppos; + + result = fb_sys_write(info, buf, count, ppos); + + if (result > 0) { + int start = max((int)(offset / info->fix.line_length), 0); + int lines = min((u32)((result / info->fix.line_length) + 1), + (u32)info->var.yres); + + dlfb_handle_damage(dev, 0, start, info->var.xres, + lines, info->screen_base); + } + + return result; +} + +/* hardware has native COPY command (see libdlo), but not worth it for fbcon */ +static void dlfb_ops_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + + struct dlfb_data *dev = info->par; + + sys_copyarea(info, area); + + dlfb_handle_damage(dev, area->dx, area->dy, + area->width, area->height, info->screen_base); +} + +static void dlfb_ops_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct dlfb_data *dev = info->par; + + sys_imageblit(info, image); + + dlfb_handle_damage(dev, image->dx, image->dy, + image->width, image->height, info->screen_base); +} + +static void dlfb_ops_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct dlfb_data *dev = info->par; + + sys_fillrect(info, rect); + + dlfb_handle_damage(dev, rect->dx, rect->dy, rect->width, + rect->height, info->screen_base); +} + +/* + * NOTE: fb_defio.c is holding info->fbdefio.mutex + * Touching ANY framebuffer memory that triggers a page fault + * in fb_defio will cause a deadlock, when it also tries to + * grab the same mutex. + */ +static void dlfb_dpy_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct page *cur; + struct fb_deferred_io *fbdefio = info->fbdefio; + struct dlfb_data *dev = info->par; + struct urb *urb; + char *cmd; + cycles_t start_cycles, end_cycles; + int bytes_sent = 0; + int bytes_identical = 0; + int bytes_rendered = 0; + + if (!fb_defio) + return; + + if (!atomic_read(&dev->usb_active)) + return; + + start_cycles = get_cycles(); + + urb = dlfb_get_urb(dev); + if (!urb) + return; + + cmd = urb->transfer_buffer; + + /* walk the written page list and render each to device */ + list_for_each_entry(cur, &fbdefio->pagelist, lru) { + + if (dlfb_render_hline(dev, &urb, (char *) info->fix.smem_start, + &cmd, cur->index << PAGE_SHIFT, + PAGE_SIZE, &bytes_identical, &bytes_sent)) + goto error; + bytes_rendered += PAGE_SIZE; + } + + if (cmd > (char *) urb->transfer_buffer) { + /* Send partial buffer remaining before exiting */ + int len = cmd - (char *) urb->transfer_buffer; + dlfb_submit_urb(dev, urb, len); + bytes_sent += len; + } else + dlfb_urb_completion(urb); + +error: + atomic_add(bytes_sent, &dev->bytes_sent); + atomic_add(bytes_identical, &dev->bytes_identical); + atomic_add(bytes_rendered, &dev->bytes_rendered); + end_cycles = get_cycles(); + atomic_add(((unsigned int) ((end_cycles - start_cycles) + >> 10)), /* Kcycles */ + &dev->cpu_kcycles_used); +} + +static int dlfb_get_edid(struct dlfb_data *dev, char *edid, int len) +{ + int i; + int ret; + char *rbuf; + + rbuf = kmalloc(2, GFP_KERNEL); + if (!rbuf) + return 0; + + for (i = 0; i < len; i++) { + ret = usb_control_msg(dev->udev, + usb_rcvctrlpipe(dev->udev, 0), (0x02), + (0x80 | (0x02 << 5)), i << 8, 0xA1, rbuf, 2, + HZ); + if (ret < 1) { + pr_err("Read EDID byte %d failed err %x\n", i, ret); + i--; + break; + } + edid[i] = rbuf[1]; + } + + kfree(rbuf); + + return i; +} + +static int dlfb_ops_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + + struct dlfb_data *dev = info->par; + + if (!atomic_read(&dev->usb_active)) + return 0; + + /* TODO: Update X server to get this from sysfs instead */ + if (cmd == DLFB_IOCTL_RETURN_EDID) { + void __user *edid = (void __user *)arg; + if (copy_to_user(edid, dev->edid, dev->edid_size)) + return -EFAULT; + return 0; + } + + /* TODO: Help propose a standard fb.h ioctl to report mmap damage */ + if (cmd == DLFB_IOCTL_REPORT_DAMAGE) { + struct dloarea area; + + if (copy_from_user(&area, (void __user *)arg, + sizeof(struct dloarea))) + return -EFAULT; + + /* + * If we have a damage-aware client, turn fb_defio "off" + * To avoid perf imact of unnecessary page fault handling. + * Done by resetting the delay for this fb_info to a very + * long period. Pages will become writable and stay that way. + * Reset to normal value when all clients have closed this fb. + */ + if (info->fbdefio) + info->fbdefio->delay = DL_DEFIO_WRITE_DISABLE; + + if (area.x < 0) + area.x = 0; + + if (area.x > info->var.xres) + area.x = info->var.xres; + + if (area.y < 0) + area.y = 0; + + if (area.y > info->var.yres) + area.y = info->var.yres; + + dlfb_handle_damage(dev, area.x, area.y, area.w, area.h, + info->screen_base); + } + + return 0; +} + +/* taken from vesafb */ +static int +dlfb_ops_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + int err = 0; + + if (regno >= info->cmap.len) + return 1; + + if (regno < 16) { + if (info->var.red.offset == 10) { + /* 1:5:5:5 */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | ((blue & 0xf800) >> 11); + } else { + /* 0:5:6:5 */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800)) | + ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11); + } + } + + return err; +} + +/* + * It's common for several clients to have framebuffer open simultaneously. + * e.g. both fbcon and X. Makes things interesting. + * Assumes caller is holding info->lock (for open and release at least) + */ +static int dlfb_ops_open(struct fb_info *info, int user) +{ + struct dlfb_data *dev = info->par; + + /* + * fbcon aggressively connects to first framebuffer it finds, + * preventing other clients (X) from working properly. Usually + * not what the user wants. Fail by default with option to enable. + */ + if ((user == 0) && (!console)) + return -EBUSY; + + /* If the USB device is gone, we don't accept new opens */ + if (dev->virtualized) + return -ENODEV; + + dev->fb_count++; + + kref_get(&dev->kref); + + if (fb_defio && (info->fbdefio == NULL)) { + /* enable defio at last moment if not disabled by client */ + + struct fb_deferred_io *fbdefio; + + fbdefio = kzalloc(sizeof(struct fb_deferred_io), GFP_KERNEL); + + if (fbdefio) { + fbdefio->delay = DL_DEFIO_WRITE_DELAY; + fbdefio->deferred_io = dlfb_dpy_deferred_io; + } + + info->fbdefio = fbdefio; + fb_deferred_io_init(info); + } + + pr_notice("open /dev/fb%d user=%d fb_info=%p count=%d\n", + info->node, user, info, dev->fb_count); + + return 0; +} + +/* + * Called when all client interfaces to start transactions have been disabled, + * and all references to our device instance (dlfb_data) are released. + * Every transaction must have a reference, so we know are fully spun down + */ +static void dlfb_free(struct kref *kref) +{ + struct dlfb_data *dev = container_of(kref, struct dlfb_data, kref); + + if (dev->backing_buffer) + vfree(dev->backing_buffer); + + kfree(dev->edid); + + pr_warn("freeing dlfb_data %p\n", dev); + + kfree(dev); +} + +static void dlfb_release_urb_work(struct work_struct *work) +{ + struct urb_node *unode = container_of(work, struct urb_node, + release_urb_work.work); + + up(&unode->dev->urbs.limit_sem); +} + +static void dlfb_free_framebuffer(struct dlfb_data *dev) +{ + struct fb_info *info = dev->info; + + if (info) { + int node = info->node; + + unregister_framebuffer(info); + + if (info->cmap.len != 0) + fb_dealloc_cmap(&info->cmap); + if (info->monspecs.modedb) + fb_destroy_modedb(info->monspecs.modedb); + if (info->screen_base) + vfree(info->screen_base); + + fb_destroy_modelist(&info->modelist); + + dev->info = NULL; + + /* Assume info structure is freed after this point */ + framebuffer_release(info); + + pr_warn("fb_info for /dev/fb%d has been freed\n", node); + } + + /* ref taken in probe() as part of registering framebfufer */ + kref_put(&dev->kref, dlfb_free); +} + +static void dlfb_free_framebuffer_work(struct work_struct *work) +{ + struct dlfb_data *dev = container_of(work, struct dlfb_data, + free_framebuffer_work.work); + dlfb_free_framebuffer(dev); +} +/* + * Assumes caller is holding info->lock mutex (for open and release at least) + */ +static int dlfb_ops_release(struct fb_info *info, int user) +{ + struct dlfb_data *dev = info->par; + + dev->fb_count--; + + /* We can't free fb_info here - fbmem will touch it when we return */ + if (dev->virtualized && (dev->fb_count == 0)) + schedule_delayed_work(&dev->free_framebuffer_work, HZ); + + if ((dev->fb_count == 0) && (info->fbdefio)) { + fb_deferred_io_cleanup(info); + kfree(info->fbdefio); + info->fbdefio = NULL; + info->fbops->fb_mmap = dlfb_ops_mmap; + } + + pr_warn("released /dev/fb%d user=%d count=%d\n", + info->node, user, dev->fb_count); + + kref_put(&dev->kref, dlfb_free); + + return 0; +} + +/* + * Check whether a video mode is supported by the DisplayLink chip + * We start from monitor's modes, so don't need to filter that here + */ +static int dlfb_is_valid_mode(struct fb_videomode *mode, + struct fb_info *info) +{ + struct dlfb_data *dev = info->par; + + if (mode->xres * mode->yres > dev->sku_pixel_limit) { + pr_warn("%dx%d beyond chip capabilities\n", + mode->xres, mode->yres); + return 0; + } + + pr_info("%dx%d @ %d Hz valid mode\n", mode->xres, mode->yres, + mode->refresh); + + return 1; +} + +static void dlfb_var_color_format(struct fb_var_screeninfo *var) +{ + const struct fb_bitfield red = { 11, 5, 0 }; + const struct fb_bitfield green = { 5, 6, 0 }; + const struct fb_bitfield blue = { 0, 5, 0 }; + + var->bits_per_pixel = 16; + var->red = red; + var->green = green; + var->blue = blue; +} + +static int dlfb_ops_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct fb_videomode mode; + + /* TODO: support dynamically changing framebuffer size */ + if ((var->xres * var->yres * 2) > info->fix.smem_len) + return -EINVAL; + + /* set device-specific elements of var unrelated to mode */ + dlfb_var_color_format(var); + + fb_var_to_videomode(&mode, var); + + if (!dlfb_is_valid_mode(&mode, info)) + return -EINVAL; + + return 0; +} + +static int dlfb_ops_set_par(struct fb_info *info) +{ + struct dlfb_data *dev = info->par; + int result; + u16 *pix_framebuffer; + int i; + + pr_notice("set_par mode %dx%d\n", info->var.xres, info->var.yres); + + result = dlfb_set_video_mode(dev, &info->var); + + if ((result == 0) && (dev->fb_count == 0)) { + + /* paint greenscreen */ + + pix_framebuffer = (u16 *) info->screen_base; + for (i = 0; i < info->fix.smem_len / 2; i++) + pix_framebuffer[i] = 0x37e6; + + dlfb_handle_damage(dev, 0, 0, info->var.xres, info->var.yres, + info->screen_base); + } + + return result; +} + +/* To fonzi the jukebox (e.g. make blanking changes take effect) */ +static char *dlfb_dummy_render(char *buf) +{ + *buf++ = 0xAF; + *buf++ = 0x6A; /* copy */ + *buf++ = 0x00; /* from address*/ + *buf++ = 0x00; + *buf++ = 0x00; + *buf++ = 0x01; /* one pixel */ + *buf++ = 0x00; /* to address */ + *buf++ = 0x00; + *buf++ = 0x00; + return buf; +} + +/* + * In order to come back from full DPMS off, we need to set the mode again + */ +static int dlfb_ops_blank(int blank_mode, struct fb_info *info) +{ + struct dlfb_data *dev = info->par; + char *bufptr; + struct urb *urb; + + pr_info("/dev/fb%d FB_BLANK mode %d --> %d\n", + info->node, dev->blank_mode, blank_mode); + + if ((dev->blank_mode == FB_BLANK_POWERDOWN) && + (blank_mode != FB_BLANK_POWERDOWN)) { + + /* returning from powerdown requires a fresh modeset */ + dlfb_set_video_mode(dev, &info->var); + } + + urb = dlfb_get_urb(dev); + if (!urb) + return 0; + + bufptr = (char *) urb->transfer_buffer; + bufptr = dlfb_vidreg_lock(bufptr); + bufptr = dlfb_blanking(bufptr, blank_mode); + bufptr = dlfb_vidreg_unlock(bufptr); + + /* seems like a render op is needed to have blank change take effect */ + bufptr = dlfb_dummy_render(bufptr); + + dlfb_submit_urb(dev, urb, bufptr - + (char *) urb->transfer_buffer); + + dev->blank_mode = blank_mode; + + return 0; +} + +static struct fb_ops dlfb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = dlfb_ops_write, + .fb_setcolreg = dlfb_ops_setcolreg, + .fb_fillrect = dlfb_ops_fillrect, + .fb_copyarea = dlfb_ops_copyarea, + .fb_imageblit = dlfb_ops_imageblit, + .fb_mmap = dlfb_ops_mmap, + .fb_ioctl = dlfb_ops_ioctl, + .fb_open = dlfb_ops_open, + .fb_release = dlfb_ops_release, + .fb_blank = dlfb_ops_blank, + .fb_check_var = dlfb_ops_check_var, + .fb_set_par = dlfb_ops_set_par, +}; + + +/* + * Assumes &info->lock held by caller + * Assumes no active clients have framebuffer open + */ +static int dlfb_realloc_framebuffer(struct dlfb_data *dev, struct fb_info *info) +{ + int retval = -ENOMEM; + int old_len = info->fix.smem_len; + int new_len; + unsigned char *old_fb = info->screen_base; + unsigned char *new_fb; + unsigned char *new_back = NULL; + + pr_warn("Reallocating framebuffer. Addresses will change!\n"); + + new_len = info->fix.line_length * info->var.yres; + + if (PAGE_ALIGN(new_len) > old_len) { + /* + * Alloc system memory for virtual framebuffer + */ + new_fb = vmalloc(new_len); + if (!new_fb) { + pr_err("Virtual framebuffer alloc failed\n"); + goto error; + } + + if (info->screen_base) { + memcpy(new_fb, old_fb, old_len); + vfree(info->screen_base); + } + + info->screen_base = new_fb; + info->fix.smem_len = PAGE_ALIGN(new_len); + info->fix.smem_start = (unsigned long) new_fb; + info->flags = udlfb_info_flags; + + /* + * Second framebuffer copy to mirror the framebuffer state + * on the physical USB device. We can function without this. + * But with imperfect damage info we may send pixels over USB + * that were, in fact, unchanged - wasting limited USB bandwidth + */ + if (shadow) + new_back = vzalloc(new_len); + if (!new_back) + pr_info("No shadow/backing buffer allocated\n"); + else { + if (dev->backing_buffer) + vfree(dev->backing_buffer); + dev->backing_buffer = new_back; + } + } + + retval = 0; + +error: + return retval; +} + +/* + * 1) Get EDID from hw, or use sw default + * 2) Parse into various fb_info structs + * 3) Allocate virtual framebuffer memory to back highest res mode + * + * Parses EDID into three places used by various parts of fbdev: + * fb_var_screeninfo contains the timing of the monitor's preferred mode + * fb_info.monspecs is full parsed EDID info, including monspecs.modedb + * fb_info.modelist is a linked list of all monitor & VESA modes which work + * + * If EDID is not readable/valid, then modelist is all VESA modes, + * monspecs is NULL, and fb_var_screeninfo is set to safe VESA mode + * Returns 0 if successful + */ +static int dlfb_setup_modes(struct dlfb_data *dev, + struct fb_info *info, + char *default_edid, size_t default_edid_size) +{ + int i; + const struct fb_videomode *default_vmode = NULL; + int result = 0; + char *edid; + int tries = 3; + + if (info->dev) /* only use mutex if info has been registered */ + mutex_lock(&info->lock); + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) { + result = -ENOMEM; + goto error; + } + + fb_destroy_modelist(&info->modelist); + memset(&info->monspecs, 0, sizeof(info->monspecs)); + + /* + * Try to (re)read EDID from hardware first + * EDID data may return, but not parse as valid + * Try again a few times, in case of e.g. analog cable noise + */ + while (tries--) { + + i = dlfb_get_edid(dev, edid, EDID_LENGTH); + + if (i >= EDID_LENGTH) + fb_edid_to_monspecs(edid, &info->monspecs); + + if (info->monspecs.modedb_len > 0) { + dev->edid = edid; + dev->edid_size = i; + break; + } + } + + /* If that fails, use a previously returned EDID if available */ + if (info->monspecs.modedb_len == 0) { + + pr_err("Unable to get valid EDID from device/display\n"); + + if (dev->edid) { + fb_edid_to_monspecs(dev->edid, &info->monspecs); + if (info->monspecs.modedb_len > 0) + pr_err("Using previously queried EDID\n"); + } + } + + /* If that fails, use the default EDID we were handed */ + if (info->monspecs.modedb_len == 0) { + if (default_edid_size >= EDID_LENGTH) { + fb_edid_to_monspecs(default_edid, &info->monspecs); + if (info->monspecs.modedb_len > 0) { + memcpy(edid, default_edid, default_edid_size); + dev->edid = edid; + dev->edid_size = default_edid_size; + pr_err("Using default/backup EDID\n"); + } + } + } + + /* If we've got modes, let's pick a best default mode */ + if (info->monspecs.modedb_len > 0) { + + for (i = 0; i < info->monspecs.modedb_len; i++) { + if (dlfb_is_valid_mode(&info->monspecs.modedb[i], info)) + fb_add_videomode(&info->monspecs.modedb[i], + &info->modelist); + else { + if (i == 0) + /* if we've removed top/best mode */ + info->monspecs.misc + &= ~FB_MISC_1ST_DETAIL; + } + } + + default_vmode = fb_find_best_display(&info->monspecs, + &info->modelist); + } + + /* If everything else has failed, fall back to safe default mode */ + if (default_vmode == NULL) { + + struct fb_videomode fb_vmode = {0}; + + /* + * Add the standard VESA modes to our modelist + * Since we don't have EDID, there may be modes that + * overspec monitor and/or are incorrect aspect ratio, etc. + * But at least the user has a chance to choose + */ + for (i = 0; i < VESA_MODEDB_SIZE; i++) { + if (dlfb_is_valid_mode((struct fb_videomode *) + &vesa_modes[i], info)) + fb_add_videomode(&vesa_modes[i], + &info->modelist); + } + + /* + * default to resolution safe for projectors + * (since they are most common case without EDID) + */ + fb_vmode.xres = 800; + fb_vmode.yres = 600; + fb_vmode.refresh = 60; + default_vmode = fb_find_nearest_mode(&fb_vmode, + &info->modelist); + } + + /* If we have good mode and no active clients*/ + if ((default_vmode != NULL) && (dev->fb_count == 0)) { + + fb_videomode_to_var(&info->var, default_vmode); + dlfb_var_color_format(&info->var); + + /* + * with mode size info, we can now alloc our framebuffer. + */ + memcpy(&info->fix, &dlfb_fix, sizeof(dlfb_fix)); + info->fix.line_length = info->var.xres * + (info->var.bits_per_pixel / 8); + + result = dlfb_realloc_framebuffer(dev, info); + + } else + result = -EINVAL; + +error: + if (edid && (dev->edid != edid)) + kfree(edid); + + if (info->dev) + mutex_unlock(&info->lock); + + return result; +} + +static ssize_t metrics_bytes_rendered_show(struct device *fbdev, + struct device_attribute *a, char *buf) { + struct fb_info *fb_info = dev_get_drvdata(fbdev); + struct dlfb_data *dev = fb_info->par; + return snprintf(buf, PAGE_SIZE, "%u\n", + atomic_read(&dev->bytes_rendered)); +} + +static ssize_t metrics_bytes_identical_show(struct device *fbdev, + struct device_attribute *a, char *buf) { + struct fb_info *fb_info = dev_get_drvdata(fbdev); + struct dlfb_data *dev = fb_info->par; + return snprintf(buf, PAGE_SIZE, "%u\n", + atomic_read(&dev->bytes_identical)); +} + +static ssize_t metrics_bytes_sent_show(struct device *fbdev, + struct device_attribute *a, char *buf) { + struct fb_info *fb_info = dev_get_drvdata(fbdev); + struct dlfb_data *dev = fb_info->par; + return snprintf(buf, PAGE_SIZE, "%u\n", + atomic_read(&dev->bytes_sent)); +} + +static ssize_t metrics_cpu_kcycles_used_show(struct device *fbdev, + struct device_attribute *a, char *buf) { + struct fb_info *fb_info = dev_get_drvdata(fbdev); + struct dlfb_data *dev = fb_info->par; + return snprintf(buf, PAGE_SIZE, "%u\n", + atomic_read(&dev->cpu_kcycles_used)); +} + +static ssize_t edid_show( + struct file *filp, + struct kobject *kobj, struct bin_attribute *a, + char *buf, loff_t off, size_t count) { + struct device *fbdev = container_of(kobj, struct device, kobj); + struct fb_info *fb_info = dev_get_drvdata(fbdev); + struct dlfb_data *dev = fb_info->par; + + if (dev->edid == NULL) + return 0; + + if ((off >= dev->edid_size) || (count > dev->edid_size)) + return 0; + + if (off + count > dev->edid_size) + count = dev->edid_size - off; + + pr_info("sysfs edid copy %p to %p, %d bytes\n", + dev->edid, buf, (int) count); + + memcpy(buf, dev->edid, count); + + return count; +} + +static ssize_t edid_store( + struct file *filp, + struct kobject *kobj, struct bin_attribute *a, + char *src, loff_t src_off, size_t src_size) { + struct device *fbdev = container_of(kobj, struct device, kobj); + struct fb_info *fb_info = dev_get_drvdata(fbdev); + struct dlfb_data *dev = fb_info->par; + int ret; + + /* We only support write of entire EDID at once, no offset*/ + if ((src_size != EDID_LENGTH) || (src_off != 0)) + return -EINVAL; + + ret = dlfb_setup_modes(dev, fb_info, src, src_size); + if (ret) + return ret; + + if (!dev->edid || memcmp(src, dev->edid, src_size)) + return -EINVAL; + + pr_info("sysfs written EDID is new default\n"); + dlfb_ops_set_par(fb_info); + return src_size; +} + +static ssize_t metrics_reset_store(struct device *fbdev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(fbdev); + struct dlfb_data *dev = fb_info->par; + + atomic_set(&dev->bytes_rendered, 0); + atomic_set(&dev->bytes_identical, 0); + atomic_set(&dev->bytes_sent, 0); + atomic_set(&dev->cpu_kcycles_used, 0); + + return count; +} + +static struct bin_attribute edid_attr = { + .attr.name = "edid", + .attr.mode = 0666, + .size = EDID_LENGTH, + .read = edid_show, + .write = edid_store +}; + +static struct device_attribute fb_device_attrs[] = { + __ATTR_RO(metrics_bytes_rendered), + __ATTR_RO(metrics_bytes_identical), + __ATTR_RO(metrics_bytes_sent), + __ATTR_RO(metrics_cpu_kcycles_used), + __ATTR(metrics_reset, S_IWUSR, NULL, metrics_reset_store), +}; + +/* + * This is necessary before we can communicate with the display controller. + */ +static int dlfb_select_std_channel(struct dlfb_data *dev) +{ + int ret; + u8 set_def_chn[] = { 0x57, 0xCD, 0xDC, 0xA7, + 0x1C, 0x88, 0x5E, 0x15, + 0x60, 0xFE, 0xC6, 0x97, + 0x16, 0x3D, 0x47, 0xF2 }; + + ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + NR_USB_REQUEST_CHANNEL, + (USB_DIR_OUT | USB_TYPE_VENDOR), 0, 0, + set_def_chn, sizeof(set_def_chn), USB_CTRL_SET_TIMEOUT); + return ret; +} + +static int dlfb_parse_vendor_descriptor(struct dlfb_data *dev, + struct usb_interface *interface) +{ + char *desc; + char *buf; + char *desc_end; + + int total_len = 0; + + buf = kzalloc(MAX_VENDOR_DESCRIPTOR_SIZE, GFP_KERNEL); + if (!buf) + return false; + desc = buf; + + total_len = usb_get_descriptor(interface_to_usbdev(interface), + 0x5f, /* vendor specific */ + 0, desc, MAX_VENDOR_DESCRIPTOR_SIZE); + + /* if not found, look in configuration descriptor */ + if (total_len < 0) { + if (0 == usb_get_extra_descriptor(interface->cur_altsetting, + 0x5f, &desc)) + total_len = (int) desc[0]; + } + + if (total_len > 5) { + pr_info("vendor descriptor length:%x data:%02x %02x %02x %02x" \ + "%02x %02x %02x %02x %02x %02x %02x\n", + total_len, desc[0], + desc[1], desc[2], desc[3], desc[4], desc[5], desc[6], + desc[7], desc[8], desc[9], desc[10]); + + if ((desc[0] != total_len) || /* descriptor length */ + (desc[1] != 0x5f) || /* vendor descriptor type */ + (desc[2] != 0x01) || /* version (2 bytes) */ + (desc[3] != 0x00) || + (desc[4] != total_len - 2)) /* length after type */ + goto unrecognized; + + desc_end = desc + total_len; + desc += 5; /* the fixed header we've already parsed */ + + while (desc < desc_end) { + u8 length; + u16 key; + + key = le16_to_cpu(*((u16 *) desc)); + desc += sizeof(u16); + length = *desc; + desc++; + + switch (key) { + case 0x0200: { /* max_area */ + u32 max_area; + max_area = le32_to_cpu(*((u32 *)desc)); + pr_warn("DL chip limited to %d pixel modes\n", + max_area); + dev->sku_pixel_limit = max_area; + break; + } + default: + break; + } + desc += length; + } + } else { + pr_info("vendor descriptor not available (%d)\n", total_len); + } + + goto success; + +unrecognized: + /* allow udlfb to load for now even if firmware unrecognized */ + pr_err("Unrecognized vendor firmware descriptor\n"); + +success: + kfree(buf); + return true; +} + +static void dlfb_init_framebuffer_work(struct work_struct *work); + +static int dlfb_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + struct dlfb_data *dev = NULL; + int retval = -ENOMEM; + + /* usb initialization */ + + usbdev = interface_to_usbdev(interface); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "dlfb_usb_probe: failed alloc of dev struct\n"); + goto error; + } + + kref_init(&dev->kref); /* matching kref_put in usb .disconnect fn */ + + dev->udev = usbdev; + dev->gdev = &usbdev->dev; /* our generic struct device * */ + usb_set_intfdata(interface, dev); + + pr_info("%s %s - serial #%s\n", + usbdev->manufacturer, usbdev->product, usbdev->serial); + pr_info("vid_%04x&pid_%04x&rev_%04x driver's dlfb_data struct at %p\n", + usbdev->descriptor.idVendor, usbdev->descriptor.idProduct, + usbdev->descriptor.bcdDevice, dev); + pr_info("console enable=%d\n", console); + pr_info("fb_defio enable=%d\n", fb_defio); + pr_info("shadow enable=%d\n", shadow); + + dev->sku_pixel_limit = 2048 * 1152; /* default to maximum */ + + if (!dlfb_parse_vendor_descriptor(dev, interface)) { + pr_err("firmware not recognized. Assume incompatible device\n"); + goto error; + } + + if (pixel_limit) { + pr_warn("DL chip limit of %d overridden" + " by module param to %d\n", + dev->sku_pixel_limit, pixel_limit); + dev->sku_pixel_limit = pixel_limit; + } + + + if (!dlfb_alloc_urb_list(dev, WRITES_IN_FLIGHT, MAX_TRANSFER)) { + retval = -ENOMEM; + pr_err("dlfb_alloc_urb_list failed\n"); + goto error; + } + + kref_get(&dev->kref); /* matching kref_put in free_framebuffer_work */ + + /* We don't register a new USB class. Our client interface is fbdev */ + + /* Workitem keep things fast & simple during USB enumeration */ + INIT_DELAYED_WORK(&dev->init_framebuffer_work, + dlfb_init_framebuffer_work); + schedule_delayed_work(&dev->init_framebuffer_work, 0); + + return 0; + +error: + if (dev) { + + kref_put(&dev->kref, dlfb_free); /* ref for framebuffer */ + kref_put(&dev->kref, dlfb_free); /* last ref from kref_init */ + + /* dev has been deallocated. Do not dereference */ + } + + return retval; +} + +static void dlfb_init_framebuffer_work(struct work_struct *work) +{ + struct dlfb_data *dev = container_of(work, struct dlfb_data, + init_framebuffer_work.work); + struct fb_info *info; + int retval; + int i; + + /* allocates framebuffer driver structure, not framebuffer memory */ + info = framebuffer_alloc(0, dev->gdev); + if (!info) { + retval = -ENOMEM; + pr_err("framebuffer_alloc failed\n"); + goto error; + } + + dev->info = info; + info->par = dev; + info->pseudo_palette = dev->pseudo_palette; + info->fbops = &dlfb_ops; + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) { + pr_err("fb_alloc_cmap failed %x\n", retval); + goto error; + } + + INIT_DELAYED_WORK(&dev->free_framebuffer_work, + dlfb_free_framebuffer_work); + + INIT_LIST_HEAD(&info->modelist); + + retval = dlfb_setup_modes(dev, info, NULL, 0); + if (retval != 0) { + pr_err("unable to find common mode for display and adapter\n"); + goto error; + } + + /* ready to begin using device */ + + atomic_set(&dev->usb_active, 1); + dlfb_select_std_channel(dev); + + dlfb_ops_check_var(&info->var, info); + dlfb_ops_set_par(info); + + retval = register_framebuffer(info); + if (retval < 0) { + pr_err("register_framebuffer failed %d\n", retval); + goto error; + } + + for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) { + retval = device_create_file(info->dev, &fb_device_attrs[i]); + if (retval) { + pr_warn("device_create_file failed %d\n", retval); + } + } + + retval = device_create_bin_file(info->dev, &edid_attr); + if (retval) { + pr_warn("device_create_bin_file failed %d\n", retval); + } + + pr_info("DisplayLink USB device /dev/fb%d attached. %dx%d resolution." + " Using %dK framebuffer memory\n", info->node, + info->var.xres, info->var.yres, + ((dev->backing_buffer) ? + info->fix.smem_len * 2 : info->fix.smem_len) >> 10); + return; + +error: + dlfb_free_framebuffer(dev); +} + +static void dlfb_usb_disconnect(struct usb_interface *interface) +{ + struct dlfb_data *dev; + struct fb_info *info; + int i; + + dev = usb_get_intfdata(interface); + info = dev->info; + + pr_info("USB disconnect starting\n"); + + /* we virtualize until all fb clients release. Then we free */ + dev->virtualized = true; + + /* When non-active we'll update virtual framebuffer, but no new urbs */ + atomic_set(&dev->usb_active, 0); + + /* this function will wait for all in-flight urbs to complete */ + dlfb_free_urb_list(dev); + + if (info) { + /* remove udlfb's sysfs interfaces */ + for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) + device_remove_file(info->dev, &fb_device_attrs[i]); + device_remove_bin_file(info->dev, &edid_attr); + unlink_framebuffer(info); + } + + usb_set_intfdata(interface, NULL); + dev->udev = NULL; + dev->gdev = NULL; + + /* if clients still have us open, will be freed on last close */ + if (dev->fb_count == 0) + schedule_delayed_work(&dev->free_framebuffer_work, 0); + + /* release reference taken by kref_init in probe() */ + kref_put(&dev->kref, dlfb_free); + + /* consider dlfb_data freed */ + + return; +} + +static struct usb_driver dlfb_driver = { + .name = "udlfb", + .probe = dlfb_usb_probe, + .disconnect = dlfb_usb_disconnect, + .id_table = id_table, +}; + +module_usb_driver(dlfb_driver); + +static void dlfb_urb_completion(struct urb *urb) +{ + struct urb_node *unode = urb->context; + struct dlfb_data *dev = unode->dev; + unsigned long flags; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) { + pr_err("%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + atomic_set(&dev->lost_pixels, 1); + } + } + + urb->transfer_buffer_length = dev->urbs.size; /* reset to actual */ + + spin_lock_irqsave(&dev->urbs.lock, flags); + list_add_tail(&unode->entry, &dev->urbs.list); + dev->urbs.available++; + spin_unlock_irqrestore(&dev->urbs.lock, flags); + + /* + * When using fb_defio, we deadlock if up() is called + * while another is waiting. So queue to another process. + */ + if (fb_defio) + schedule_delayed_work(&unode->release_urb_work, 0); + else + up(&dev->urbs.limit_sem); +} + +static void dlfb_free_urb_list(struct dlfb_data *dev) +{ + int count = dev->urbs.count; + struct list_head *node; + struct urb_node *unode; + struct urb *urb; + int ret; + unsigned long flags; + + pr_notice("Freeing all render urbs\n"); + + /* keep waiting and freeing, until we've got 'em all */ + while (count--) { + + /* Getting interrupted means a leak, but ok at disconnect */ + ret = down_interruptible(&dev->urbs.limit_sem); + if (ret) + break; + + spin_lock_irqsave(&dev->urbs.lock, flags); + + node = dev->urbs.list.next; /* have reserved one with sem */ + list_del_init(node); + + spin_unlock_irqrestore(&dev->urbs.lock, flags); + + unode = list_entry(node, struct urb_node, entry); + urb = unode->urb; + + /* Free each separately allocated piece */ + usb_free_coherent(urb->dev, dev->urbs.size, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + kfree(node); + } + + dev->urbs.count = 0; +} + +static int dlfb_alloc_urb_list(struct dlfb_data *dev, int count, size_t size) +{ + int i = 0; + struct urb *urb; + struct urb_node *unode; + char *buf; + + spin_lock_init(&dev->urbs.lock); + + dev->urbs.size = size; + INIT_LIST_HEAD(&dev->urbs.list); + + while (i < count) { + unode = kzalloc(sizeof(struct urb_node), GFP_KERNEL); + if (!unode) + break; + unode->dev = dev; + + INIT_DELAYED_WORK(&unode->release_urb_work, + dlfb_release_urb_work); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + kfree(unode); + break; + } + unode->urb = urb; + + buf = usb_alloc_coherent(dev->udev, MAX_TRANSFER, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + kfree(unode); + usb_free_urb(urb); + break; + } + + /* urb->transfer_buffer_length set to actual before submit */ + usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, 1), + buf, size, dlfb_urb_completion, unode); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + list_add_tail(&unode->entry, &dev->urbs.list); + + i++; + } + + sema_init(&dev->urbs.limit_sem, i); + dev->urbs.count = i; + dev->urbs.available = i; + + pr_notice("allocated %d %d byte urbs\n", i, (int) size); + + return i; +} + +static struct urb *dlfb_get_urb(struct dlfb_data *dev) +{ + int ret = 0; + struct list_head *entry; + struct urb_node *unode; + struct urb *urb = NULL; + unsigned long flags; + + /* Wait for an in-flight buffer to complete and get re-queued */ + ret = down_timeout(&dev->urbs.limit_sem, GET_URB_TIMEOUT); + if (ret) { + atomic_set(&dev->lost_pixels, 1); + pr_warn("wait for urb interrupted: %x available: %d\n", + ret, dev->urbs.available); + goto error; + } + + spin_lock_irqsave(&dev->urbs.lock, flags); + + BUG_ON(list_empty(&dev->urbs.list)); /* reserved one with limit_sem */ + entry = dev->urbs.list.next; + list_del_init(entry); + dev->urbs.available--; + + spin_unlock_irqrestore(&dev->urbs.lock, flags); + + unode = list_entry(entry, struct urb_node, entry); + urb = unode->urb; + +error: + return urb; +} + +static int dlfb_submit_urb(struct dlfb_data *dev, struct urb *urb, size_t len) +{ + int ret; + + BUG_ON(len > dev->urbs.size); + + urb->transfer_buffer_length = len; /* set to actual payload len */ + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dlfb_urb_completion(urb); /* because no one else will */ + atomic_set(&dev->lost_pixels, 1); + pr_err("usb_submit_urb error %x\n", ret); + } + return ret; +} + +module_param(console, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP); +MODULE_PARM_DESC(console, "Allow fbcon to open framebuffer"); + +module_param(fb_defio, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP); +MODULE_PARM_DESC(fb_defio, "Page fault detection of mmap writes"); + +module_param(shadow, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP); +MODULE_PARM_DESC(shadow, "Shadow vid mem. Disable to save mem but lose perf"); + +module_param(pixel_limit, int, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP); +MODULE_PARM_DESC(pixel_limit, "Force limit on max mode (in x*y pixels)"); + +MODULE_AUTHOR("Roberto De Ioris <roberto@unbit.it>, " + "Jaya Kumar <jayakumar.lkml@gmail.com>, " + "Bernie Thompson <bernie@plugable.com>"); +MODULE_DESCRIPTION("DisplayLink kernel framebuffer driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/uvesafb.c b/drivers/video/fbdev/uvesafb.c new file mode 100644 index 000000000000..509d452e8f91 --- /dev/null +++ b/drivers/video/fbdev/uvesafb.c @@ -0,0 +1,2028 @@ +/* + * A framebuffer driver for VBE 2.0+ compliant video cards + * + * (c) 2007 Michal Januszewski <spock@gentoo.org> + * Loosely based upon the vesafb driver. + * + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/skbuff.h> +#include <linux/timer.h> +#include <linux/completion.h> +#include <linux/connector.h> +#include <linux/random.h> +#include <linux/platform_device.h> +#include <linux/limits.h> +#include <linux/fb.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <video/edid.h> +#include <video/uvesafb.h> +#ifdef CONFIG_X86 +#include <video/vga.h> +#endif +#include "edid.h" + +static struct cb_id uvesafb_cn_id = { + .idx = CN_IDX_V86D, + .val = CN_VAL_V86D_UVESAFB +}; +static char v86d_path[PATH_MAX] = "/sbin/v86d"; +static char v86d_started; /* has v86d been started by uvesafb? */ + +static struct fb_fix_screeninfo uvesafb_fix = { + .id = "VESA VGA", + .type = FB_TYPE_PACKED_PIXELS, + .accel = FB_ACCEL_NONE, + .visual = FB_VISUAL_TRUECOLOR, +}; + +static int mtrr = 3; /* enable mtrr by default */ +static bool blank = 1; /* enable blanking by default */ +static int ypan = 1; /* 0: scroll, 1: ypan, 2: ywrap */ +static bool pmi_setpal = true; /* use PMI for palette changes */ +static bool nocrtc; /* ignore CRTC settings */ +static bool noedid; /* don't try DDC transfers */ +static int vram_remap; /* set amt. of memory to be used */ +static int vram_total; /* set total amount of memory */ +static u16 maxclk; /* maximum pixel clock */ +static u16 maxvf; /* maximum vertical frequency */ +static u16 maxhf; /* maximum horizontal frequency */ +static u16 vbemode; /* force use of a specific VBE mode */ +static char *mode_option; +static u8 dac_width = 6; + +static struct uvesafb_ktask *uvfb_tasks[UVESAFB_TASKS_MAX]; +static DEFINE_MUTEX(uvfb_lock); + +/* + * A handler for replies from userspace. + * + * Make sure each message passes consistency checks and if it does, + * find the kernel part of the task struct, copy the registers and + * the buffer contents and then complete the task. + */ +static void uvesafb_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) +{ + struct uvesafb_task *utask; + struct uvesafb_ktask *task; + + if (!capable(CAP_SYS_ADMIN)) + return; + + if (msg->seq >= UVESAFB_TASKS_MAX) + return; + + mutex_lock(&uvfb_lock); + task = uvfb_tasks[msg->seq]; + + if (!task || msg->ack != task->ack) { + mutex_unlock(&uvfb_lock); + return; + } + + utask = (struct uvesafb_task *)msg->data; + + /* Sanity checks for the buffer length. */ + if (task->t.buf_len < utask->buf_len || + utask->buf_len > msg->len - sizeof(*utask)) { + mutex_unlock(&uvfb_lock); + return; + } + + uvfb_tasks[msg->seq] = NULL; + mutex_unlock(&uvfb_lock); + + memcpy(&task->t, utask, sizeof(*utask)); + + if (task->t.buf_len && task->buf) + memcpy(task->buf, utask + 1, task->t.buf_len); + + complete(task->done); + return; +} + +static int uvesafb_helper_start(void) +{ + char *envp[] = { + "HOME=/", + "PATH=/sbin:/bin", + NULL, + }; + + char *argv[] = { + v86d_path, + NULL, + }; + + return call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC); +} + +/* + * Execute a uvesafb task. + * + * Returns 0 if the task is executed successfully. + * + * A message sent to the userspace consists of the uvesafb_task + * struct and (optionally) a buffer. The uvesafb_task struct is + * a simplified version of uvesafb_ktask (its kernel counterpart) + * containing only the register values, flags and the length of + * the buffer. + * + * Each message is assigned a sequence number (increased linearly) + * and a random ack number. The sequence number is used as a key + * for the uvfb_tasks array which holds pointers to uvesafb_ktask + * structs for all requests. + */ +static int uvesafb_exec(struct uvesafb_ktask *task) +{ + static int seq; + struct cn_msg *m; + int err; + int len = sizeof(task->t) + task->t.buf_len; + + /* + * Check whether the message isn't longer than the maximum + * allowed by connector. + */ + if (sizeof(*m) + len > CONNECTOR_MAX_MSG_SIZE) { + printk(KERN_WARNING "uvesafb: message too long (%d), " + "can't execute task\n", (int)(sizeof(*m) + len)); + return -E2BIG; + } + + m = kzalloc(sizeof(*m) + len, GFP_KERNEL); + if (!m) + return -ENOMEM; + + init_completion(task->done); + + memcpy(&m->id, &uvesafb_cn_id, sizeof(m->id)); + m->seq = seq; + m->len = len; + m->ack = prandom_u32(); + + /* uvesafb_task structure */ + memcpy(m + 1, &task->t, sizeof(task->t)); + + /* Buffer */ + memcpy((u8 *)(m + 1) + sizeof(task->t), task->buf, task->t.buf_len); + + /* + * Save the message ack number so that we can find the kernel + * part of this task when a reply is received from userspace. + */ + task->ack = m->ack; + + mutex_lock(&uvfb_lock); + + /* If all slots are taken -- bail out. */ + if (uvfb_tasks[seq]) { + mutex_unlock(&uvfb_lock); + err = -EBUSY; + goto out; + } + + /* Save a pointer to the kernel part of the task struct. */ + uvfb_tasks[seq] = task; + mutex_unlock(&uvfb_lock); + + err = cn_netlink_send(m, 0, 0, GFP_KERNEL); + if (err == -ESRCH) { + /* + * Try to start the userspace helper if sending + * the request failed the first time. + */ + err = uvesafb_helper_start(); + if (err) { + printk(KERN_ERR "uvesafb: failed to execute %s\n", + v86d_path); + printk(KERN_ERR "uvesafb: make sure that the v86d " + "helper is installed and executable\n"); + } else { + v86d_started = 1; + err = cn_netlink_send(m, 0, 0, gfp_any()); + if (err == -ENOBUFS) + err = 0; + } + } else if (err == -ENOBUFS) + err = 0; + + if (!err && !(task->t.flags & TF_EXIT)) + err = !wait_for_completion_timeout(task->done, + msecs_to_jiffies(UVESAFB_TIMEOUT)); + + mutex_lock(&uvfb_lock); + uvfb_tasks[seq] = NULL; + mutex_unlock(&uvfb_lock); + + seq++; + if (seq >= UVESAFB_TASKS_MAX) + seq = 0; +out: + kfree(m); + return err; +} + +/* + * Free a uvesafb_ktask struct. + */ +static void uvesafb_free(struct uvesafb_ktask *task) +{ + if (task) { + kfree(task->done); + kfree(task); + } +} + +/* + * Prepare a uvesafb_ktask struct to be used again. + */ +static void uvesafb_reset(struct uvesafb_ktask *task) +{ + struct completion *cpl = task->done; + + memset(task, 0, sizeof(*task)); + task->done = cpl; +} + +/* + * Allocate and prepare a uvesafb_ktask struct. + */ +static struct uvesafb_ktask *uvesafb_prep(void) +{ + struct uvesafb_ktask *task; + + task = kzalloc(sizeof(*task), GFP_KERNEL); + if (task) { + task->done = kzalloc(sizeof(*task->done), GFP_KERNEL); + if (!task->done) { + kfree(task); + task = NULL; + } + } + return task; +} + +static void uvesafb_setup_var(struct fb_var_screeninfo *var, + struct fb_info *info, struct vbe_mode_ib *mode) +{ + struct uvesafb_par *par = info->par; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = FB_SYNC_VERT_HIGH_ACT; + + var->xres = mode->x_res; + var->yres = mode->y_res; + var->xres_virtual = mode->x_res; + var->yres_virtual = (par->ypan) ? + info->fix.smem_len / mode->bytes_per_scan_line : + mode->y_res; + var->xoffset = 0; + var->yoffset = 0; + var->bits_per_pixel = mode->bits_per_pixel; + + if (var->bits_per_pixel == 15) + var->bits_per_pixel = 16; + + if (var->bits_per_pixel > 8) { + var->red.offset = mode->red_off; + var->red.length = mode->red_len; + var->green.offset = mode->green_off; + var->green.length = mode->green_len; + var->blue.offset = mode->blue_off; + var->blue.length = mode->blue_len; + var->transp.offset = mode->rsvd_off; + var->transp.length = mode->rsvd_len; + } else { + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->transp.offset = 0; + + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + } +} + +static int uvesafb_vbe_find_mode(struct uvesafb_par *par, + int xres, int yres, int depth, unsigned char flags) +{ + int i, match = -1, h = 0, d = 0x7fffffff; + + for (i = 0; i < par->vbe_modes_cnt; i++) { + h = abs(par->vbe_modes[i].x_res - xres) + + abs(par->vbe_modes[i].y_res - yres) + + abs(depth - par->vbe_modes[i].depth); + + /* + * We have an exact match in terms of resolution + * and depth. + */ + if (h == 0) + return i; + + if (h < d || (h == d && par->vbe_modes[i].depth > depth)) { + d = h; + match = i; + } + } + i = 1; + + if (flags & UVESAFB_EXACT_DEPTH && + par->vbe_modes[match].depth != depth) + i = 0; + + if (flags & UVESAFB_EXACT_RES && d > 24) + i = 0; + + if (i != 0) + return match; + else + return -1; +} + +static u8 *uvesafb_vbe_state_save(struct uvesafb_par *par) +{ + struct uvesafb_ktask *task; + u8 *state; + int err; + + if (!par->vbe_state_size) + return NULL; + + state = kmalloc(par->vbe_state_size, GFP_KERNEL); + if (!state) + return ERR_PTR(-ENOMEM); + + task = uvesafb_prep(); + if (!task) { + kfree(state); + return NULL; + } + + task->t.regs.eax = 0x4f04; + task->t.regs.ecx = 0x000f; + task->t.regs.edx = 0x0001; + task->t.flags = TF_BUF_RET | TF_BUF_ESBX; + task->t.buf_len = par->vbe_state_size; + task->buf = state; + err = uvesafb_exec(task); + + if (err || (task->t.regs.eax & 0xffff) != 0x004f) { + printk(KERN_WARNING "uvesafb: VBE get state call " + "failed (eax=0x%x, err=%d)\n", + task->t.regs.eax, err); + kfree(state); + state = NULL; + } + + uvesafb_free(task); + return state; +} + +static void uvesafb_vbe_state_restore(struct uvesafb_par *par, u8 *state_buf) +{ + struct uvesafb_ktask *task; + int err; + + if (!state_buf) + return; + + task = uvesafb_prep(); + if (!task) + return; + + task->t.regs.eax = 0x4f04; + task->t.regs.ecx = 0x000f; + task->t.regs.edx = 0x0002; + task->t.buf_len = par->vbe_state_size; + task->t.flags = TF_BUF_ESBX; + task->buf = state_buf; + + err = uvesafb_exec(task); + if (err || (task->t.regs.eax & 0xffff) != 0x004f) + printk(KERN_WARNING "uvesafb: VBE state restore call " + "failed (eax=0x%x, err=%d)\n", + task->t.regs.eax, err); + + uvesafb_free(task); +} + +static int uvesafb_vbe_getinfo(struct uvesafb_ktask *task, + struct uvesafb_par *par) +{ + int err; + + task->t.regs.eax = 0x4f00; + task->t.flags = TF_VBEIB; + task->t.buf_len = sizeof(struct vbe_ib); + task->buf = &par->vbe_ib; + strncpy(par->vbe_ib.vbe_signature, "VBE2", 4); + + err = uvesafb_exec(task); + if (err || (task->t.regs.eax & 0xffff) != 0x004f) { + printk(KERN_ERR "uvesafb: Getting VBE info block failed " + "(eax=0x%x, err=%d)\n", (u32)task->t.regs.eax, + err); + return -EINVAL; + } + + if (par->vbe_ib.vbe_version < 0x0200) { + printk(KERN_ERR "uvesafb: Sorry, pre-VBE 2.0 cards are " + "not supported.\n"); + return -EINVAL; + } + + if (!par->vbe_ib.mode_list_ptr) { + printk(KERN_ERR "uvesafb: Missing mode list!\n"); + return -EINVAL; + } + + printk(KERN_INFO "uvesafb: "); + + /* + * Convert string pointers and the mode list pointer into + * usable addresses. Print informational messages about the + * video adapter and its vendor. + */ + if (par->vbe_ib.oem_vendor_name_ptr) + printk("%s, ", + ((char *)task->buf) + par->vbe_ib.oem_vendor_name_ptr); + + if (par->vbe_ib.oem_product_name_ptr) + printk("%s, ", + ((char *)task->buf) + par->vbe_ib.oem_product_name_ptr); + + if (par->vbe_ib.oem_product_rev_ptr) + printk("%s, ", + ((char *)task->buf) + par->vbe_ib.oem_product_rev_ptr); + + if (par->vbe_ib.oem_string_ptr) + printk("OEM: %s, ", + ((char *)task->buf) + par->vbe_ib.oem_string_ptr); + + printk("VBE v%d.%d\n", ((par->vbe_ib.vbe_version & 0xff00) >> 8), + par->vbe_ib.vbe_version & 0xff); + + return 0; +} + +static int uvesafb_vbe_getmodes(struct uvesafb_ktask *task, + struct uvesafb_par *par) +{ + int off = 0, err; + u16 *mode; + + par->vbe_modes_cnt = 0; + + /* Count available modes. */ + mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr); + while (*mode != 0xffff) { + par->vbe_modes_cnt++; + mode++; + } + + par->vbe_modes = kzalloc(sizeof(struct vbe_mode_ib) * + par->vbe_modes_cnt, GFP_KERNEL); + if (!par->vbe_modes) + return -ENOMEM; + + /* Get info about all available modes. */ + mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr); + while (*mode != 0xffff) { + struct vbe_mode_ib *mib; + + uvesafb_reset(task); + task->t.regs.eax = 0x4f01; + task->t.regs.ecx = (u32) *mode; + task->t.flags = TF_BUF_RET | TF_BUF_ESDI; + task->t.buf_len = sizeof(struct vbe_mode_ib); + task->buf = par->vbe_modes + off; + + err = uvesafb_exec(task); + if (err || (task->t.regs.eax & 0xffff) != 0x004f) { + printk(KERN_WARNING "uvesafb: Getting mode info block " + "for mode 0x%x failed (eax=0x%x, err=%d)\n", + *mode, (u32)task->t.regs.eax, err); + mode++; + par->vbe_modes_cnt--; + continue; + } + + mib = task->buf; + mib->mode_id = *mode; + + /* + * We only want modes that are supported with the current + * hardware configuration, color, graphics and that have + * support for the LFB. + */ + if ((mib->mode_attr & VBE_MODE_MASK) == VBE_MODE_MASK && + mib->bits_per_pixel >= 8) + off++; + else + par->vbe_modes_cnt--; + + mode++; + mib->depth = mib->red_len + mib->green_len + mib->blue_len; + + /* + * Handle 8bpp modes and modes with broken color component + * lengths. + */ + if (mib->depth == 0 || (mib->depth == 24 && + mib->bits_per_pixel == 32)) + mib->depth = mib->bits_per_pixel; + } + + if (par->vbe_modes_cnt > 0) + return 0; + else + return -EINVAL; +} + +/* + * The Protected Mode Interface is 32-bit x86 code, so we only run it on + * x86 and not x86_64. + */ +#ifdef CONFIG_X86_32 +static int uvesafb_vbe_getpmi(struct uvesafb_ktask *task, + struct uvesafb_par *par) +{ + int i, err; + + uvesafb_reset(task); + task->t.regs.eax = 0x4f0a; + task->t.regs.ebx = 0x0; + err = uvesafb_exec(task); + + if ((task->t.regs.eax & 0xffff) != 0x4f || task->t.regs.es < 0xc000) { + par->pmi_setpal = par->ypan = 0; + } else { + par->pmi_base = (u16 *)phys_to_virt(((u32)task->t.regs.es << 4) + + task->t.regs.edi); + par->pmi_start = (u8 *)par->pmi_base + par->pmi_base[1]; + par->pmi_pal = (u8 *)par->pmi_base + par->pmi_base[2]; + printk(KERN_INFO "uvesafb: protected mode interface info at " + "%04x:%04x\n", + (u16)task->t.regs.es, (u16)task->t.regs.edi); + printk(KERN_INFO "uvesafb: pmi: set display start = %p, " + "set palette = %p\n", par->pmi_start, + par->pmi_pal); + + if (par->pmi_base[3]) { + printk(KERN_INFO "uvesafb: pmi: ports = "); + for (i = par->pmi_base[3]/2; + par->pmi_base[i] != 0xffff; i++) + printk("%x ", par->pmi_base[i]); + printk("\n"); + + if (par->pmi_base[i] != 0xffff) { + printk(KERN_INFO "uvesafb: can't handle memory" + " requests, pmi disabled\n"); + par->ypan = par->pmi_setpal = 0; + } + } + } + return 0; +} +#endif /* CONFIG_X86_32 */ + +/* + * Check whether a video mode is supported by the Video BIOS and is + * compatible with the monitor limits. + */ +static int uvesafb_is_valid_mode(struct fb_videomode *mode, + struct fb_info *info) +{ + if (info->monspecs.gtf) { + fb_videomode_to_var(&info->var, mode); + if (fb_validate_mode(&info->var, info)) + return 0; + } + + if (uvesafb_vbe_find_mode(info->par, mode->xres, mode->yres, 8, + UVESAFB_EXACT_RES) == -1) + return 0; + + return 1; +} + +static int uvesafb_vbe_getedid(struct uvesafb_ktask *task, struct fb_info *info) +{ + struct uvesafb_par *par = info->par; + int err = 0; + + if (noedid || par->vbe_ib.vbe_version < 0x0300) + return -EINVAL; + + task->t.regs.eax = 0x4f15; + task->t.regs.ebx = 0; + task->t.regs.ecx = 0; + task->t.buf_len = 0; + task->t.flags = 0; + + err = uvesafb_exec(task); + + if ((task->t.regs.eax & 0xffff) != 0x004f || err) + return -EINVAL; + + if ((task->t.regs.ebx & 0x3) == 3) { + printk(KERN_INFO "uvesafb: VBIOS/hardware supports both " + "DDC1 and DDC2 transfers\n"); + } else if ((task->t.regs.ebx & 0x3) == 2) { + printk(KERN_INFO "uvesafb: VBIOS/hardware supports DDC2 " + "transfers\n"); + } else if ((task->t.regs.ebx & 0x3) == 1) { + printk(KERN_INFO "uvesafb: VBIOS/hardware supports DDC1 " + "transfers\n"); + } else { + printk(KERN_INFO "uvesafb: VBIOS/hardware doesn't support " + "DDC transfers\n"); + return -EINVAL; + } + + task->t.regs.eax = 0x4f15; + task->t.regs.ebx = 1; + task->t.regs.ecx = task->t.regs.edx = 0; + task->t.flags = TF_BUF_RET | TF_BUF_ESDI; + task->t.buf_len = EDID_LENGTH; + task->buf = kzalloc(EDID_LENGTH, GFP_KERNEL); + if (!task->buf) + return -ENOMEM; + + err = uvesafb_exec(task); + + if ((task->t.regs.eax & 0xffff) == 0x004f && !err) { + fb_edid_to_monspecs(task->buf, &info->monspecs); + + if (info->monspecs.vfmax && info->monspecs.hfmax) { + /* + * If the maximum pixel clock wasn't specified in + * the EDID block, set it to 300 MHz. + */ + if (info->monspecs.dclkmax == 0) + info->monspecs.dclkmax = 300 * 1000000; + info->monspecs.gtf = 1; + } + } else { + err = -EINVAL; + } + + kfree(task->buf); + return err; +} + +static void uvesafb_vbe_getmonspecs(struct uvesafb_ktask *task, + struct fb_info *info) +{ + struct uvesafb_par *par = info->par; + int i; + + memset(&info->monspecs, 0, sizeof(info->monspecs)); + + /* + * If we don't get all necessary data from the EDID block, + * mark it as incompatible with the GTF and set nocrtc so + * that we always use the default BIOS refresh rate. + */ + if (uvesafb_vbe_getedid(task, info)) { + info->monspecs.gtf = 0; + par->nocrtc = 1; + } + + /* Kernel command line overrides. */ + if (maxclk) + info->monspecs.dclkmax = maxclk * 1000000; + if (maxvf) + info->monspecs.vfmax = maxvf; + if (maxhf) + info->monspecs.hfmax = maxhf * 1000; + + /* + * In case DDC transfers are not supported, the user can provide + * monitor limits manually. Lower limits are set to "safe" values. + */ + if (info->monspecs.gtf == 0 && maxclk && maxvf && maxhf) { + info->monspecs.dclkmin = 0; + info->monspecs.vfmin = 60; + info->monspecs.hfmin = 29000; + info->monspecs.gtf = 1; + par->nocrtc = 0; + } + + if (info->monspecs.gtf) + printk(KERN_INFO + "uvesafb: monitor limits: vf = %d Hz, hf = %d kHz, " + "clk = %d MHz\n", info->monspecs.vfmax, + (int)(info->monspecs.hfmax / 1000), + (int)(info->monspecs.dclkmax / 1000000)); + else + printk(KERN_INFO "uvesafb: no monitor limits have been set, " + "default refresh rate will be used\n"); + + /* Add VBE modes to the modelist. */ + for (i = 0; i < par->vbe_modes_cnt; i++) { + struct fb_var_screeninfo var; + struct vbe_mode_ib *mode; + struct fb_videomode vmode; + + mode = &par->vbe_modes[i]; + memset(&var, 0, sizeof(var)); + + var.xres = mode->x_res; + var.yres = mode->y_res; + + fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, &var, info); + fb_var_to_videomode(&vmode, &var); + fb_add_videomode(&vmode, &info->modelist); + } + + /* Add valid VESA modes to our modelist. */ + for (i = 0; i < VESA_MODEDB_SIZE; i++) { + if (uvesafb_is_valid_mode((struct fb_videomode *) + &vesa_modes[i], info)) + fb_add_videomode(&vesa_modes[i], &info->modelist); + } + + for (i = 0; i < info->monspecs.modedb_len; i++) { + if (uvesafb_is_valid_mode(&info->monspecs.modedb[i], info)) + fb_add_videomode(&info->monspecs.modedb[i], + &info->modelist); + } + + return; +} + +static void uvesafb_vbe_getstatesize(struct uvesafb_ktask *task, + struct uvesafb_par *par) +{ + int err; + + uvesafb_reset(task); + + /* + * Get the VBE state buffer size. We want all available + * hardware state data (CL = 0x0f). + */ + task->t.regs.eax = 0x4f04; + task->t.regs.ecx = 0x000f; + task->t.regs.edx = 0x0000; + task->t.flags = 0; + + err = uvesafb_exec(task); + + if (err || (task->t.regs.eax & 0xffff) != 0x004f) { + printk(KERN_WARNING "uvesafb: VBE state buffer size " + "cannot be determined (eax=0x%x, err=%d)\n", + task->t.regs.eax, err); + par->vbe_state_size = 0; + return; + } + + par->vbe_state_size = 64 * (task->t.regs.ebx & 0xffff); +} + +static int uvesafb_vbe_init(struct fb_info *info) +{ + struct uvesafb_ktask *task = NULL; + struct uvesafb_par *par = info->par; + int err; + + task = uvesafb_prep(); + if (!task) + return -ENOMEM; + + err = uvesafb_vbe_getinfo(task, par); + if (err) + goto out; + + err = uvesafb_vbe_getmodes(task, par); + if (err) + goto out; + + par->nocrtc = nocrtc; +#ifdef CONFIG_X86_32 + par->pmi_setpal = pmi_setpal; + par->ypan = ypan; + + if (par->pmi_setpal || par->ypan) { + if (__supported_pte_mask & _PAGE_NX) { + par->pmi_setpal = par->ypan = 0; + printk(KERN_WARNING "uvesafb: NX protection is active, " + "better not use the PMI.\n"); + } else { + uvesafb_vbe_getpmi(task, par); + } + } +#else + /* The protected mode interface is not available on non-x86. */ + par->pmi_setpal = par->ypan = 0; +#endif + + INIT_LIST_HEAD(&info->modelist); + uvesafb_vbe_getmonspecs(task, info); + uvesafb_vbe_getstatesize(task, par); + +out: uvesafb_free(task); + return err; +} + +static int uvesafb_vbe_init_mode(struct fb_info *info) +{ + struct list_head *pos; + struct fb_modelist *modelist; + struct fb_videomode *mode; + struct uvesafb_par *par = info->par; + int i, modeid; + + /* Has the user requested a specific VESA mode? */ + if (vbemode) { + for (i = 0; i < par->vbe_modes_cnt; i++) { + if (par->vbe_modes[i].mode_id == vbemode) { + modeid = i; + uvesafb_setup_var(&info->var, info, + &par->vbe_modes[modeid]); + fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, + &info->var, info); + /* + * With pixclock set to 0, the default BIOS + * timings will be used in set_par(). + */ + info->var.pixclock = 0; + goto gotmode; + } + } + printk(KERN_INFO "uvesafb: requested VBE mode 0x%x is " + "unavailable\n", vbemode); + vbemode = 0; + } + + /* Count the modes in the modelist */ + i = 0; + list_for_each(pos, &info->modelist) + i++; + + /* + * Convert the modelist into a modedb so that we can use it with + * fb_find_mode(). + */ + mode = kzalloc(i * sizeof(*mode), GFP_KERNEL); + if (mode) { + i = 0; + list_for_each(pos, &info->modelist) { + modelist = list_entry(pos, struct fb_modelist, list); + mode[i] = modelist->mode; + i++; + } + + if (!mode_option) + mode_option = UVESAFB_DEFAULT_MODE; + + i = fb_find_mode(&info->var, info, mode_option, mode, i, + NULL, 8); + + kfree(mode); + } + + /* fb_find_mode() failed */ + if (i == 0) { + info->var.xres = 640; + info->var.yres = 480; + mode = (struct fb_videomode *) + fb_find_best_mode(&info->var, &info->modelist); + + if (mode) { + fb_videomode_to_var(&info->var, mode); + } else { + modeid = par->vbe_modes[0].mode_id; + uvesafb_setup_var(&info->var, info, + &par->vbe_modes[modeid]); + fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, + &info->var, info); + + goto gotmode; + } + } + + /* Look for a matching VBE mode. */ + modeid = uvesafb_vbe_find_mode(par, info->var.xres, info->var.yres, + info->var.bits_per_pixel, UVESAFB_EXACT_RES); + + if (modeid == -1) + return -EINVAL; + + uvesafb_setup_var(&info->var, info, &par->vbe_modes[modeid]); + +gotmode: + /* + * If we are not VBE3.0+ compliant, we're done -- the BIOS will + * ignore our timings anyway. + */ + if (par->vbe_ib.vbe_version < 0x0300 || par->nocrtc) + fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, + &info->var, info); + + return modeid; +} + +static int uvesafb_setpalette(struct uvesafb_pal_entry *entries, int count, + int start, struct fb_info *info) +{ + struct uvesafb_ktask *task; +#ifdef CONFIG_X86 + struct uvesafb_par *par = info->par; + int i = par->mode_idx; +#endif + int err = 0; + + /* + * We support palette modifications for 8 bpp modes only, so + * there can never be more than 256 entries. + */ + if (start + count > 256) + return -EINVAL; + +#ifdef CONFIG_X86 + /* Use VGA registers if mode is VGA-compatible. */ + if (i >= 0 && i < par->vbe_modes_cnt && + par->vbe_modes[i].mode_attr & VBE_MODE_VGACOMPAT) { + for (i = 0; i < count; i++) { + outb_p(start + i, dac_reg); + outb_p(entries[i].red, dac_val); + outb_p(entries[i].green, dac_val); + outb_p(entries[i].blue, dac_val); + } + } +#ifdef CONFIG_X86_32 + else if (par->pmi_setpal) { + __asm__ __volatile__( + "call *(%%esi)" + : /* no return value */ + : "a" (0x4f09), /* EAX */ + "b" (0), /* EBX */ + "c" (count), /* ECX */ + "d" (start), /* EDX */ + "D" (entries), /* EDI */ + "S" (&par->pmi_pal)); /* ESI */ + } +#endif /* CONFIG_X86_32 */ + else +#endif /* CONFIG_X86 */ + { + task = uvesafb_prep(); + if (!task) + return -ENOMEM; + + task->t.regs.eax = 0x4f09; + task->t.regs.ebx = 0x0; + task->t.regs.ecx = count; + task->t.regs.edx = start; + task->t.flags = TF_BUF_ESDI; + task->t.buf_len = sizeof(struct uvesafb_pal_entry) * count; + task->buf = entries; + + err = uvesafb_exec(task); + if ((task->t.regs.eax & 0xffff) != 0x004f) + err = 1; + + uvesafb_free(task); + } + return err; +} + +static int uvesafb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct uvesafb_pal_entry entry; + int shift = 16 - dac_width; + int err = 0; + + if (regno >= info->cmap.len) + return -EINVAL; + + if (info->var.bits_per_pixel == 8) { + entry.red = red >> shift; + entry.green = green >> shift; + entry.blue = blue >> shift; + entry.pad = 0; + + err = uvesafb_setpalette(&entry, 1, regno, info); + } else if (regno < 16) { + switch (info->var.bits_per_pixel) { + case 16: + if (info->var.red.offset == 10) { + /* 1:5:5:5 */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + } else { + /* 0:5:6:5 */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800) ) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + } + break; + + case 24: + case 32: + red >>= 8; + green >>= 8; + blue >>= 8; + ((u32 *)(info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + break; + } + } + return err; +} + +static int uvesafb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + struct uvesafb_pal_entry *entries; + int shift = 16 - dac_width; + int i, err = 0; + + if (info->var.bits_per_pixel == 8) { + if (cmap->start + cmap->len > info->cmap.start + + info->cmap.len || cmap->start < info->cmap.start) + return -EINVAL; + + entries = kmalloc(sizeof(*entries) * cmap->len, GFP_KERNEL); + if (!entries) + return -ENOMEM; + + for (i = 0; i < cmap->len; i++) { + entries[i].red = cmap->red[i] >> shift; + entries[i].green = cmap->green[i] >> shift; + entries[i].blue = cmap->blue[i] >> shift; + entries[i].pad = 0; + } + err = uvesafb_setpalette(entries, cmap->len, cmap->start, info); + kfree(entries); + } else { + /* + * For modes with bpp > 8, we only set the pseudo palette in + * the fb_info struct. We rely on uvesafb_setcolreg to do all + * sanity checking. + */ + for (i = 0; i < cmap->len; i++) { + err |= uvesafb_setcolreg(cmap->start + i, cmap->red[i], + cmap->green[i], cmap->blue[i], + 0, info); + } + } + return err; +} + +static int uvesafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ +#ifdef CONFIG_X86_32 + int offset; + struct uvesafb_par *par = info->par; + + offset = (var->yoffset * info->fix.line_length + var->xoffset) / 4; + + /* + * It turns out it's not the best idea to do panning via vm86, + * so we only allow it if we have a PMI. + */ + if (par->pmi_start) { + __asm__ __volatile__( + "call *(%%edi)" + : /* no return value */ + : "a" (0x4f07), /* EAX */ + "b" (0), /* EBX */ + "c" (offset), /* ECX */ + "d" (offset >> 16), /* EDX */ + "D" (&par->pmi_start)); /* EDI */ + } +#endif + return 0; +} + +static int uvesafb_blank(int blank, struct fb_info *info) +{ + struct uvesafb_ktask *task; + int err = 1; +#ifdef CONFIG_X86 + struct uvesafb_par *par = info->par; + + if (par->vbe_ib.capabilities & VBE_CAP_VGACOMPAT) { + int loop = 10000; + u8 seq = 0, crtc17 = 0; + + if (blank == FB_BLANK_POWERDOWN) { + seq = 0x20; + crtc17 = 0x00; + err = 0; + } else { + seq = 0x00; + crtc17 = 0x80; + err = (blank == FB_BLANK_UNBLANK) ? 0 : -EINVAL; + } + + vga_wseq(NULL, 0x00, 0x01); + seq |= vga_rseq(NULL, 0x01) & ~0x20; + vga_wseq(NULL, 0x00, seq); + + crtc17 |= vga_rcrt(NULL, 0x17) & ~0x80; + while (loop--); + vga_wcrt(NULL, 0x17, crtc17); + vga_wseq(NULL, 0x00, 0x03); + } else +#endif /* CONFIG_X86 */ + { + task = uvesafb_prep(); + if (!task) + return -ENOMEM; + + task->t.regs.eax = 0x4f10; + switch (blank) { + case FB_BLANK_UNBLANK: + task->t.regs.ebx = 0x0001; + break; + case FB_BLANK_NORMAL: + task->t.regs.ebx = 0x0101; /* standby */ + break; + case FB_BLANK_POWERDOWN: + task->t.regs.ebx = 0x0401; /* powerdown */ + break; + default: + goto out; + } + + err = uvesafb_exec(task); + if (err || (task->t.regs.eax & 0xffff) != 0x004f) + err = 1; +out: uvesafb_free(task); + } + return err; +} + +static int uvesafb_open(struct fb_info *info, int user) +{ + struct uvesafb_par *par = info->par; + int cnt = atomic_read(&par->ref_count); + u8 *buf = NULL; + + if (!cnt && par->vbe_state_size) { + buf = uvesafb_vbe_state_save(par); + if (IS_ERR(buf)) { + printk(KERN_WARNING "uvesafb: save hardware state" + "failed, error code is %ld!\n", PTR_ERR(buf)); + } else { + par->vbe_state_orig = buf; + } + } + + atomic_inc(&par->ref_count); + return 0; +} + +static int uvesafb_release(struct fb_info *info, int user) +{ + struct uvesafb_ktask *task = NULL; + struct uvesafb_par *par = info->par; + int cnt = atomic_read(&par->ref_count); + + if (!cnt) + return -EINVAL; + + if (cnt != 1) + goto out; + + task = uvesafb_prep(); + if (!task) + goto out; + + /* First, try to set the standard 80x25 text mode. */ + task->t.regs.eax = 0x0003; + uvesafb_exec(task); + + /* + * Now try to restore whatever hardware state we might have + * saved when the fb device was first opened. + */ + uvesafb_vbe_state_restore(par, par->vbe_state_orig); +out: + atomic_dec(&par->ref_count); + if (task) + uvesafb_free(task); + return 0; +} + +static int uvesafb_set_par(struct fb_info *info) +{ + struct uvesafb_par *par = info->par; + struct uvesafb_ktask *task = NULL; + struct vbe_crtc_ib *crtc = NULL; + struct vbe_mode_ib *mode = NULL; + int i, err = 0, depth = info->var.bits_per_pixel; + + if (depth > 8 && depth != 32) + depth = info->var.red.length + info->var.green.length + + info->var.blue.length; + + i = uvesafb_vbe_find_mode(par, info->var.xres, info->var.yres, depth, + UVESAFB_EXACT_RES | UVESAFB_EXACT_DEPTH); + if (i >= 0) + mode = &par->vbe_modes[i]; + else + return -EINVAL; + + task = uvesafb_prep(); + if (!task) + return -ENOMEM; +setmode: + task->t.regs.eax = 0x4f02; + task->t.regs.ebx = mode->mode_id | 0x4000; /* use LFB */ + + if (par->vbe_ib.vbe_version >= 0x0300 && !par->nocrtc && + info->var.pixclock != 0) { + task->t.regs.ebx |= 0x0800; /* use CRTC data */ + task->t.flags = TF_BUF_ESDI; + crtc = kzalloc(sizeof(struct vbe_crtc_ib), GFP_KERNEL); + if (!crtc) { + err = -ENOMEM; + goto out; + } + crtc->horiz_start = info->var.xres + info->var.right_margin; + crtc->horiz_end = crtc->horiz_start + info->var.hsync_len; + crtc->horiz_total = crtc->horiz_end + info->var.left_margin; + + crtc->vert_start = info->var.yres + info->var.lower_margin; + crtc->vert_end = crtc->vert_start + info->var.vsync_len; + crtc->vert_total = crtc->vert_end + info->var.upper_margin; + + crtc->pixel_clock = PICOS2KHZ(info->var.pixclock) * 1000; + crtc->refresh_rate = (u16)(100 * (crtc->pixel_clock / + (crtc->vert_total * crtc->horiz_total))); + + if (info->var.vmode & FB_VMODE_DOUBLE) + crtc->flags |= 0x1; + if (info->var.vmode & FB_VMODE_INTERLACED) + crtc->flags |= 0x2; + if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT)) + crtc->flags |= 0x4; + if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT)) + crtc->flags |= 0x8; + memcpy(&par->crtc, crtc, sizeof(*crtc)); + } else { + memset(&par->crtc, 0, sizeof(*crtc)); + } + + task->t.buf_len = sizeof(struct vbe_crtc_ib); + task->buf = &par->crtc; + + err = uvesafb_exec(task); + if (err || (task->t.regs.eax & 0xffff) != 0x004f) { + /* + * The mode switch might have failed because we tried to + * use our own timings. Try again with the default timings. + */ + if (crtc != NULL) { + printk(KERN_WARNING "uvesafb: mode switch failed " + "(eax=0x%x, err=%d). Trying again with " + "default timings.\n", task->t.regs.eax, err); + uvesafb_reset(task); + kfree(crtc); + crtc = NULL; + info->var.pixclock = 0; + goto setmode; + } else { + printk(KERN_ERR "uvesafb: mode switch failed (eax=" + "0x%x, err=%d)\n", task->t.regs.eax, err); + err = -EINVAL; + goto out; + } + } + par->mode_idx = i; + + /* For 8bpp modes, always try to set the DAC to 8 bits. */ + if (par->vbe_ib.capabilities & VBE_CAP_CAN_SWITCH_DAC && + mode->bits_per_pixel <= 8) { + uvesafb_reset(task); + task->t.regs.eax = 0x4f08; + task->t.regs.ebx = 0x0800; + + err = uvesafb_exec(task); + if (err || (task->t.regs.eax & 0xffff) != 0x004f || + ((task->t.regs.ebx & 0xff00) >> 8) != 8) { + dac_width = 6; + } else { + dac_width = 8; + } + } + + info->fix.visual = (info->var.bits_per_pixel == 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->fix.line_length = mode->bytes_per_scan_line; + +out: + kfree(crtc); + uvesafb_free(task); + + return err; +} + +static void uvesafb_check_limits(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + const struct fb_videomode *mode; + struct uvesafb_par *par = info->par; + + /* + * If pixclock is set to 0, then we're using default BIOS timings + * and thus don't have to perform any checks here. + */ + if (!var->pixclock) + return; + + if (par->vbe_ib.vbe_version < 0x0300) { + fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, var, info); + return; + } + + if (!fb_validate_mode(var, info)) + return; + + mode = fb_find_best_mode(var, &info->modelist); + if (mode) { + if (mode->xres == var->xres && mode->yres == var->yres && + !(mode->vmode & (FB_VMODE_INTERLACED | FB_VMODE_DOUBLE))) { + fb_videomode_to_var(var, mode); + return; + } + } + + if (info->monspecs.gtf && !fb_get_mode(FB_MAXTIMINGS, 0, var, info)) + return; + /* Use default refresh rate */ + var->pixclock = 0; +} + +static int uvesafb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct uvesafb_par *par = info->par; + struct vbe_mode_ib *mode = NULL; + int match = -1; + int depth = var->red.length + var->green.length + var->blue.length; + + /* + * Various apps will use bits_per_pixel to set the color depth, + * which is theoretically incorrect, but which we'll try to handle + * here. + */ + if (depth == 0 || abs(depth - var->bits_per_pixel) >= 8) + depth = var->bits_per_pixel; + + match = uvesafb_vbe_find_mode(par, var->xres, var->yres, depth, + UVESAFB_EXACT_RES); + if (match == -1) + return -EINVAL; + + mode = &par->vbe_modes[match]; + uvesafb_setup_var(var, info, mode); + + /* + * Check whether we have remapped enough memory for this mode. + * We might be called at an early stage, when we haven't remapped + * any memory yet, in which case we simply skip the check. + */ + if (var->yres * mode->bytes_per_scan_line > info->fix.smem_len + && info->fix.smem_len) + return -EINVAL; + + if ((var->vmode & FB_VMODE_DOUBLE) && + !(par->vbe_modes[match].mode_attr & 0x100)) + var->vmode &= ~FB_VMODE_DOUBLE; + + if ((var->vmode & FB_VMODE_INTERLACED) && + !(par->vbe_modes[match].mode_attr & 0x200)) + var->vmode &= ~FB_VMODE_INTERLACED; + + uvesafb_check_limits(var, info); + + var->xres_virtual = var->xres; + var->yres_virtual = (par->ypan) ? + info->fix.smem_len / mode->bytes_per_scan_line : + var->yres; + return 0; +} + +static struct fb_ops uvesafb_ops = { + .owner = THIS_MODULE, + .fb_open = uvesafb_open, + .fb_release = uvesafb_release, + .fb_setcolreg = uvesafb_setcolreg, + .fb_setcmap = uvesafb_setcmap, + .fb_pan_display = uvesafb_pan_display, + .fb_blank = uvesafb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_check_var = uvesafb_check_var, + .fb_set_par = uvesafb_set_par, +}; + +static void uvesafb_init_info(struct fb_info *info, struct vbe_mode_ib *mode) +{ + unsigned int size_vmode; + unsigned int size_remap; + unsigned int size_total; + struct uvesafb_par *par = info->par; + int i, h; + + info->pseudo_palette = ((u8 *)info->par + sizeof(struct uvesafb_par)); + info->fix = uvesafb_fix; + info->fix.ypanstep = par->ypan ? 1 : 0; + info->fix.ywrapstep = (par->ypan > 1) ? 1 : 0; + + /* Disable blanking if the user requested so. */ + if (!blank) + info->fbops->fb_blank = NULL; + + /* + * Find out how much IO memory is required for the mode with + * the highest resolution. + */ + size_remap = 0; + for (i = 0; i < par->vbe_modes_cnt; i++) { + h = par->vbe_modes[i].bytes_per_scan_line * + par->vbe_modes[i].y_res; + if (h > size_remap) + size_remap = h; + } + size_remap *= 2; + + /* + * size_vmode -- that is the amount of memory needed for the + * used video mode, i.e. the minimum amount of + * memory we need. + */ + size_vmode = info->var.yres * mode->bytes_per_scan_line; + + /* + * size_total -- all video memory we have. Used for mtrr + * entries, resource allocation and bounds + * checking. + */ + size_total = par->vbe_ib.total_memory * 65536; + if (vram_total) + size_total = vram_total * 1024 * 1024; + if (size_total < size_vmode) + size_total = size_vmode; + + /* + * size_remap -- the amount of video memory we are going to + * use for vesafb. With modern cards it is no + * option to simply use size_total as th + * wastes plenty of kernel address space. + */ + if (vram_remap) + size_remap = vram_remap * 1024 * 1024; + if (size_remap < size_vmode) + size_remap = size_vmode; + if (size_remap > size_total) + size_remap = size_total; + + info->fix.smem_len = size_remap; + info->fix.smem_start = mode->phys_base_ptr; + + /* + * We have to set yres_virtual here because when setup_var() was + * called, smem_len wasn't defined yet. + */ + info->var.yres_virtual = info->fix.smem_len / + mode->bytes_per_scan_line; + + if (par->ypan && info->var.yres_virtual > info->var.yres) { + printk(KERN_INFO "uvesafb: scrolling: %s " + "using protected mode interface, " + "yres_virtual=%d\n", + (par->ypan > 1) ? "ywrap" : "ypan", + info->var.yres_virtual); + } else { + printk(KERN_INFO "uvesafb: scrolling: redraw\n"); + info->var.yres_virtual = info->var.yres; + par->ypan = 0; + } + + info->flags = FBINFO_FLAG_DEFAULT | + (par->ypan ? FBINFO_HWACCEL_YPAN : 0); + + if (!par->ypan) + info->fbops->fb_pan_display = NULL; +} + +static void uvesafb_init_mtrr(struct fb_info *info) +{ + struct uvesafb_par *par = info->par; + + if (mtrr && !(info->fix.smem_start & (PAGE_SIZE - 1))) { + int temp_size = info->fix.smem_len; + + int rc; + + /* Find the largest power-of-two */ + temp_size = roundup_pow_of_two(temp_size); + + /* Try and find a power of two to add */ + do { + rc = arch_phys_wc_add(info->fix.smem_start, temp_size); + temp_size >>= 1; + } while (temp_size >= PAGE_SIZE && rc == -EINVAL); + + if (rc >= 0) + par->mtrr_handle = rc; + } +} + +static void uvesafb_ioremap(struct fb_info *info) +{ + info->screen_base = ioremap_wc(info->fix.smem_start, info->fix.smem_len); +} + +static ssize_t uvesafb_show_vbe_ver(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + + return snprintf(buf, PAGE_SIZE, "%.4x\n", par->vbe_ib.vbe_version); +} + +static DEVICE_ATTR(vbe_version, S_IRUGO, uvesafb_show_vbe_ver, NULL); + +static ssize_t uvesafb_show_vbe_modes(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + int ret = 0, i; + + for (i = 0; i < par->vbe_modes_cnt && ret < PAGE_SIZE; i++) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "%dx%d-%d, 0x%.4x\n", + par->vbe_modes[i].x_res, par->vbe_modes[i].y_res, + par->vbe_modes[i].depth, par->vbe_modes[i].mode_id); + } + + return ret; +} + +static DEVICE_ATTR(vbe_modes, S_IRUGO, uvesafb_show_vbe_modes, NULL); + +static ssize_t uvesafb_show_vendor(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + + if (par->vbe_ib.oem_vendor_name_ptr) + return snprintf(buf, PAGE_SIZE, "%s\n", (char *) + (&par->vbe_ib) + par->vbe_ib.oem_vendor_name_ptr); + else + return 0; +} + +static DEVICE_ATTR(oem_vendor, S_IRUGO, uvesafb_show_vendor, NULL); + +static ssize_t uvesafb_show_product_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + + if (par->vbe_ib.oem_product_name_ptr) + return snprintf(buf, PAGE_SIZE, "%s\n", (char *) + (&par->vbe_ib) + par->vbe_ib.oem_product_name_ptr); + else + return 0; +} + +static DEVICE_ATTR(oem_product_name, S_IRUGO, uvesafb_show_product_name, NULL); + +static ssize_t uvesafb_show_product_rev(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + + if (par->vbe_ib.oem_product_rev_ptr) + return snprintf(buf, PAGE_SIZE, "%s\n", (char *) + (&par->vbe_ib) + par->vbe_ib.oem_product_rev_ptr); + else + return 0; +} + +static DEVICE_ATTR(oem_product_rev, S_IRUGO, uvesafb_show_product_rev, NULL); + +static ssize_t uvesafb_show_oem_string(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + + if (par->vbe_ib.oem_string_ptr) + return snprintf(buf, PAGE_SIZE, "%s\n", + (char *)(&par->vbe_ib) + par->vbe_ib.oem_string_ptr); + else + return 0; +} + +static DEVICE_ATTR(oem_string, S_IRUGO, uvesafb_show_oem_string, NULL); + +static ssize_t uvesafb_show_nocrtc(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + + return snprintf(buf, PAGE_SIZE, "%d\n", par->nocrtc); +} + +static ssize_t uvesafb_store_nocrtc(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fb_info *info = platform_get_drvdata(to_platform_device(dev)); + struct uvesafb_par *par = info->par; + + if (count > 0) { + if (buf[0] == '0') + par->nocrtc = 0; + else + par->nocrtc = 1; + } + return count; +} + +static DEVICE_ATTR(nocrtc, S_IRUGO | S_IWUSR, uvesafb_show_nocrtc, + uvesafb_store_nocrtc); + +static struct attribute *uvesafb_dev_attrs[] = { + &dev_attr_vbe_version.attr, + &dev_attr_vbe_modes.attr, + &dev_attr_oem_vendor.attr, + &dev_attr_oem_product_name.attr, + &dev_attr_oem_product_rev.attr, + &dev_attr_oem_string.attr, + &dev_attr_nocrtc.attr, + NULL, +}; + +static struct attribute_group uvesafb_dev_attgrp = { + .name = NULL, + .attrs = uvesafb_dev_attrs, +}; + +static int uvesafb_probe(struct platform_device *dev) +{ + struct fb_info *info; + struct vbe_mode_ib *mode = NULL; + struct uvesafb_par *par; + int err = 0, i; + + info = framebuffer_alloc(sizeof(*par) + sizeof(u32) * 256, &dev->dev); + if (!info) + return -ENOMEM; + + par = info->par; + + err = uvesafb_vbe_init(info); + if (err) { + printk(KERN_ERR "uvesafb: vbe_init() failed with %d\n", err); + goto out; + } + + info->fbops = &uvesafb_ops; + + i = uvesafb_vbe_init_mode(info); + if (i < 0) { + err = -EINVAL; + goto out; + } else { + mode = &par->vbe_modes[i]; + } + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + err = -ENXIO; + goto out; + } + + uvesafb_init_info(info, mode); + + if (!request_region(0x3c0, 32, "uvesafb")) { + printk(KERN_ERR "uvesafb: request region 0x3c0-0x3e0 failed\n"); + err = -EIO; + goto out_mode; + } + + if (!request_mem_region(info->fix.smem_start, info->fix.smem_len, + "uvesafb")) { + printk(KERN_ERR "uvesafb: cannot reserve video memory at " + "0x%lx\n", info->fix.smem_start); + err = -EIO; + goto out_reg; + } + + uvesafb_init_mtrr(info); + uvesafb_ioremap(info); + + if (!info->screen_base) { + printk(KERN_ERR + "uvesafb: abort, cannot ioremap 0x%x bytes of video " + "memory at 0x%lx\n", + info->fix.smem_len, info->fix.smem_start); + err = -EIO; + goto out_mem; + } + + platform_set_drvdata(dev, info); + + if (register_framebuffer(info) < 0) { + printk(KERN_ERR + "uvesafb: failed to register framebuffer device\n"); + err = -EINVAL; + goto out_unmap; + } + + printk(KERN_INFO "uvesafb: framebuffer at 0x%lx, mapped to 0x%p, " + "using %dk, total %dk\n", info->fix.smem_start, + info->screen_base, info->fix.smem_len/1024, + par->vbe_ib.total_memory * 64); + fb_info(info, "%s frame buffer device\n", info->fix.id); + + err = sysfs_create_group(&dev->dev.kobj, &uvesafb_dev_attgrp); + if (err != 0) + fb_warn(info, "failed to register attributes\n"); + + return 0; + +out_unmap: + iounmap(info->screen_base); +out_mem: + release_mem_region(info->fix.smem_start, info->fix.smem_len); +out_reg: + release_region(0x3c0, 32); +out_mode: + if (!list_empty(&info->modelist)) + fb_destroy_modelist(&info->modelist); + fb_destroy_modedb(info->monspecs.modedb); + fb_dealloc_cmap(&info->cmap); +out: + kfree(par->vbe_modes); + + framebuffer_release(info); + return err; +} + +static int uvesafb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + struct uvesafb_par *par = info->par; + + sysfs_remove_group(&dev->dev.kobj, &uvesafb_dev_attgrp); + unregister_framebuffer(info); + release_region(0x3c0, 32); + iounmap(info->screen_base); + arch_phys_wc_del(par->mtrr_handle); + release_mem_region(info->fix.smem_start, info->fix.smem_len); + fb_destroy_modedb(info->monspecs.modedb); + fb_dealloc_cmap(&info->cmap); + + kfree(par->vbe_modes); + kfree(par->vbe_state_orig); + kfree(par->vbe_state_saved); + + framebuffer_release(info); + } + return 0; +} + +static struct platform_driver uvesafb_driver = { + .probe = uvesafb_probe, + .remove = uvesafb_remove, + .driver = { + .name = "uvesafb", + }, +}; + +static struct platform_device *uvesafb_device; + +#ifndef MODULE +static int uvesafb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + + if (!strcmp(this_opt, "redraw")) + ypan = 0; + else if (!strcmp(this_opt, "ypan")) + ypan = 1; + else if (!strcmp(this_opt, "ywrap")) + ypan = 2; + else if (!strcmp(this_opt, "vgapal")) + pmi_setpal = 0; + else if (!strcmp(this_opt, "pmipal")) + pmi_setpal = 1; + else if (!strncmp(this_opt, "mtrr:", 5)) + mtrr = simple_strtoul(this_opt+5, NULL, 0); + else if (!strcmp(this_opt, "nomtrr")) + mtrr = 0; + else if (!strcmp(this_opt, "nocrtc")) + nocrtc = 1; + else if (!strcmp(this_opt, "noedid")) + noedid = 1; + else if (!strcmp(this_opt, "noblank")) + blank = 0; + else if (!strncmp(this_opt, "vtotal:", 7)) + vram_total = simple_strtoul(this_opt + 7, NULL, 0); + else if (!strncmp(this_opt, "vremap:", 7)) + vram_remap = simple_strtoul(this_opt + 7, NULL, 0); + else if (!strncmp(this_opt, "maxhf:", 6)) + maxhf = simple_strtoul(this_opt + 6, NULL, 0); + else if (!strncmp(this_opt, "maxvf:", 6)) + maxvf = simple_strtoul(this_opt + 6, NULL, 0); + else if (!strncmp(this_opt, "maxclk:", 7)) + maxclk = simple_strtoul(this_opt + 7, NULL, 0); + else if (!strncmp(this_opt, "vbemode:", 8)) + vbemode = simple_strtoul(this_opt + 8, NULL, 0); + else if (this_opt[0] >= '0' && this_opt[0] <= '9') { + mode_option = this_opt; + } else { + printk(KERN_WARNING + "uvesafb: unrecognized option %s\n", this_opt); + } + } + + if (mtrr != 3 && mtrr != 0) + pr_warn("uvesafb: mtrr should be set to 0 or 3; %d is unsupported", mtrr); + + return 0; +} +#endif /* !MODULE */ + +static ssize_t show_v86d(struct device_driver *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", v86d_path); +} + +static ssize_t store_v86d(struct device_driver *dev, const char *buf, + size_t count) +{ + strncpy(v86d_path, buf, PATH_MAX); + return count; +} + +static DRIVER_ATTR(v86d, S_IRUGO | S_IWUSR, show_v86d, store_v86d); + +static int uvesafb_init(void) +{ + int err; + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("uvesafb", &option)) + return -ENODEV; + uvesafb_setup(option); +#endif + err = cn_add_callback(&uvesafb_cn_id, "uvesafb", uvesafb_cn_callback); + if (err) + return err; + + err = platform_driver_register(&uvesafb_driver); + + if (!err) { + uvesafb_device = platform_device_alloc("uvesafb", 0); + if (uvesafb_device) + err = platform_device_add(uvesafb_device); + else + err = -ENOMEM; + + if (err) { + if (uvesafb_device) + platform_device_put(uvesafb_device); + platform_driver_unregister(&uvesafb_driver); + cn_del_callback(&uvesafb_cn_id); + return err; + } + + err = driver_create_file(&uvesafb_driver.driver, + &driver_attr_v86d); + if (err) { + printk(KERN_WARNING "uvesafb: failed to register " + "attributes\n"); + err = 0; + } + } + return err; +} + +module_init(uvesafb_init); + +static void uvesafb_exit(void) +{ + struct uvesafb_ktask *task; + + if (v86d_started) { + task = uvesafb_prep(); + if (task) { + task->t.flags = TF_EXIT; + uvesafb_exec(task); + uvesafb_free(task); + } + } + + cn_del_callback(&uvesafb_cn_id); + driver_remove_file(&uvesafb_driver.driver, &driver_attr_v86d); + platform_device_unregister(uvesafb_device); + platform_driver_unregister(&uvesafb_driver); +} + +module_exit(uvesafb_exit); + +static int param_set_scroll(const char *val, const struct kernel_param *kp) +{ + ypan = 0; + + if (!strcmp(val, "redraw")) + ypan = 0; + else if (!strcmp(val, "ypan")) + ypan = 1; + else if (!strcmp(val, "ywrap")) + ypan = 2; + else + return -EINVAL; + + return 0; +} +static struct kernel_param_ops param_ops_scroll = { + .set = param_set_scroll, +}; +#define param_check_scroll(name, p) __param_check(name, p, void) + +module_param_named(scroll, ypan, scroll, 0); +MODULE_PARM_DESC(scroll, + "Scrolling mode, set to 'redraw', 'ypan', or 'ywrap'"); +module_param_named(vgapal, pmi_setpal, invbool, 0); +MODULE_PARM_DESC(vgapal, "Set palette using VGA registers"); +module_param_named(pmipal, pmi_setpal, bool, 0); +MODULE_PARM_DESC(pmipal, "Set palette using PMI calls"); +module_param(mtrr, uint, 0); +MODULE_PARM_DESC(mtrr, + "Memory Type Range Registers setting. Use 0 to disable."); +module_param(blank, bool, 0); +MODULE_PARM_DESC(blank, "Enable hardware blanking"); +module_param(nocrtc, bool, 0); +MODULE_PARM_DESC(nocrtc, "Ignore CRTC timings when setting modes"); +module_param(noedid, bool, 0); +MODULE_PARM_DESC(noedid, + "Ignore EDID-provided monitor limits when setting modes"); +module_param(vram_remap, uint, 0); +MODULE_PARM_DESC(vram_remap, "Set amount of video memory to be used [MiB]"); +module_param(vram_total, uint, 0); +MODULE_PARM_DESC(vram_total, "Set total amount of video memoery [MiB]"); +module_param(maxclk, ushort, 0); +MODULE_PARM_DESC(maxclk, "Maximum pixelclock [MHz], overrides EDID data"); +module_param(maxhf, ushort, 0); +MODULE_PARM_DESC(maxhf, + "Maximum horizontal frequency [kHz], overrides EDID data"); +module_param(maxvf, ushort, 0); +MODULE_PARM_DESC(maxvf, + "Maximum vertical frequency [Hz], overrides EDID data"); +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, + "Specify initial video mode as \"<xres>x<yres>[-<bpp>][@<refresh>]\""); +module_param(vbemode, ushort, 0); +MODULE_PARM_DESC(vbemode, + "VBE mode number to set, overrides the 'mode' option"); +module_param_string(v86d, v86d_path, PATH_MAX, 0660); +MODULE_PARM_DESC(v86d, "Path to the v86d userspace helper."); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michal Januszewski <spock@gentoo.org>"); +MODULE_DESCRIPTION("Framebuffer driver for VBE2.0+ compliant graphics boards"); + diff --git a/drivers/video/fbdev/valkyriefb.c b/drivers/video/fbdev/valkyriefb.c new file mode 100644 index 000000000000..97cb9bd1d1dd --- /dev/null +++ b/drivers/video/fbdev/valkyriefb.c @@ -0,0 +1,589 @@ +/* + * valkyriefb.c -- frame buffer device for the PowerMac 'valkyrie' display + * + * Created 8 August 1998 by + * Martin Costabel <costabel@wanadoo.fr> and Kevin Schoedel + * + * Vmode-switching changes and vmode 15/17 modifications created 29 August + * 1998 by Barry K. Nathan <barryn@pobox.com>. + * + * Ported to m68k Macintosh by David Huggins-Daines <dhd@debian.org> + * + * Derived directly from: + * + * controlfb.c -- frame buffer device for the PowerMac 'control' display + * Copyright (C) 1998 Dan Jacobowitz <dan@debian.org> + * + * pmc-valkyrie.c -- Console support for PowerMac "valkyrie" display adaptor. + * Copyright (C) 1997 Paul Mackerras. + * + * and indirectly: + * + * Frame buffer structure from: + * drivers/video/chipsfb.c -- frame buffer device for + * Chips & Technologies 65550 chip. + * + * Copyright (C) 1998 Paul Mackerras + * + * This file is derived from the Powermac "chips" driver: + * Copyright (C) 1997 Fabio Riccardi. + * And from the frame buffer device for Open Firmware-initialized devices: + * Copyright (C) 1997 Geert Uytterhoeven. + * + * Hardware information from: + * control.c: Console support for PowerMac "control" display adaptor. + * Copyright (C) 1996 Paul Mackerras + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/selection.h> +#include <linux/init.h> +#include <linux/nvram.h> +#include <linux/adb.h> +#include <linux/cuda.h> +#include <asm/io.h> +#ifdef CONFIG_MAC +#include <asm/macintosh.h> +#else +#include <asm/prom.h> +#endif +#include <asm/pgtable.h> + +#include "macmodes.h" +#include "valkyriefb.h" + +#ifdef CONFIG_MAC +/* We don't yet have functions to read the PRAM... perhaps we can + adapt them from the PPC code? */ +static int default_vmode = VMODE_CHOOSE; +static int default_cmode = CMODE_8; +#else +static int default_vmode = VMODE_NVRAM; +static int default_cmode = CMODE_NVRAM; +#endif + +struct fb_par_valkyrie { + int vmode, cmode; + int xres, yres; + int vxres, vyres; + struct valkyrie_regvals *init; +}; + +struct fb_info_valkyrie { + struct fb_info info; + struct fb_par_valkyrie par; + struct cmap_regs __iomem *cmap_regs; + unsigned long cmap_regs_phys; + + struct valkyrie_regs __iomem *valkyrie_regs; + unsigned long valkyrie_regs_phys; + + __u8 __iomem *frame_buffer; + unsigned long frame_buffer_phys; + + int sense; + unsigned long total_vram; + + u32 pseudo_palette[16]; +}; + +/* + * Exported functions + */ +int valkyriefb_init(void); +int valkyriefb_setup(char*); + +static int valkyriefb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int valkyriefb_set_par(struct fb_info *info); +static int valkyriefb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int valkyriefb_blank(int blank_mode, struct fb_info *info); + +static int read_valkyrie_sense(struct fb_info_valkyrie *p); +static void set_valkyrie_clock(unsigned char *params); +static int valkyrie_var_to_par(struct fb_var_screeninfo *var, + struct fb_par_valkyrie *par, const struct fb_info *fb_info); + +static int valkyrie_init_info(struct fb_info *info, struct fb_info_valkyrie *p); +static void valkyrie_par_to_fix(struct fb_par_valkyrie *par, struct fb_fix_screeninfo *fix); +static void valkyrie_init_fix(struct fb_fix_screeninfo *fix, struct fb_info_valkyrie *p); + +static struct fb_ops valkyriefb_ops = { + .owner = THIS_MODULE, + .fb_check_var = valkyriefb_check_var, + .fb_set_par = valkyriefb_set_par, + .fb_setcolreg = valkyriefb_setcolreg, + .fb_blank = valkyriefb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* Sets the video mode according to info->var */ +static int valkyriefb_set_par(struct fb_info *info) +{ + struct fb_info_valkyrie *p = (struct fb_info_valkyrie *) info; + volatile struct valkyrie_regs __iomem *valkyrie_regs = p->valkyrie_regs; + struct fb_par_valkyrie *par = info->par; + struct valkyrie_regvals *init; + int err; + + if ((err = valkyrie_var_to_par(&info->var, par, info))) + return err; + + valkyrie_par_to_fix(par, &info->fix); + + /* Reset the valkyrie */ + out_8(&valkyrie_regs->status.r, 0); + udelay(100); + + /* Initialize display timing registers */ + init = par->init; + out_8(&valkyrie_regs->mode.r, init->mode | 0x80); + out_8(&valkyrie_regs->depth.r, par->cmode + 3); + set_valkyrie_clock(init->clock_params); + udelay(100); + + /* Turn on display */ + out_8(&valkyrie_regs->mode.r, init->mode); + + return 0; +} + +static inline int valkyrie_par_to_var(struct fb_par_valkyrie *par, + struct fb_var_screeninfo *var) +{ + return mac_vmode_to_var(par->vmode, par->cmode, var); +} + +static int +valkyriefb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int err; + struct fb_par_valkyrie par; + + if ((err = valkyrie_var_to_par(var, &par, info))) + return err; + valkyrie_par_to_var(&par, var); + return 0; +} + +/* + * Blank the screen if blank_mode != 0, else unblank. If blank_mode == NULL + * then the caller blanks by setting the CLUT (Color Look Up Table) to all + * black. Return 0 if blanking succeeded, != 0 if un-/blanking failed due + * to e.g. a video mode which doesn't support it. Implements VESA suspend + * and powerdown modes on hardware that supports disabling hsync/vsync: + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ +static int valkyriefb_blank(int blank_mode, struct fb_info *info) +{ + struct fb_info_valkyrie *p = (struct fb_info_valkyrie *) info; + struct fb_par_valkyrie *par = info->par; + struct valkyrie_regvals *init = par->init; + + if (init == NULL) + return 1; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: /* unblank */ + out_8(&p->valkyrie_regs->mode.r, init->mode); + break; + case FB_BLANK_NORMAL: + return 1; /* get caller to set CLUT to all black */ + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + /* + * [kps] Value extracted from MacOS. I don't know + * whether this bit disables hsync or vsync, or + * whether the hardware can do the other as well. + */ + out_8(&p->valkyrie_regs->mode.r, init->mode | 0x40); + break; + case FB_BLANK_POWERDOWN: + out_8(&p->valkyrie_regs->mode.r, 0x66); + break; + } + return 0; +} + +static int valkyriefb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct fb_info_valkyrie *p = (struct fb_info_valkyrie *) info; + volatile struct cmap_regs __iomem *cmap_regs = p->cmap_regs; + struct fb_par_valkyrie *par = info->par; + + if (regno > 255) + return 1; + red >>= 8; + green >>= 8; + blue >>= 8; + + /* tell clut which address to fill */ + out_8(&p->cmap_regs->addr, regno); + udelay(1); + /* send one color channel at a time */ + out_8(&cmap_regs->lut, red); + out_8(&cmap_regs->lut, green); + out_8(&cmap_regs->lut, blue); + + if (regno < 16 && par->cmode == CMODE_16) + ((u32 *)info->pseudo_palette)[regno] = + (regno << 10) | (regno << 5) | regno; + + return 0; +} + +static inline int valkyrie_vram_reqd(int video_mode, int color_mode) +{ + int pitch; + struct valkyrie_regvals *init = valkyrie_reg_init[video_mode-1]; + + if ((pitch = init->pitch[color_mode]) == 0) + pitch = 2 * init->pitch[0]; + return init->vres * pitch; +} + +static void set_valkyrie_clock(unsigned char *params) +{ + struct adb_request req; + int i; + +#ifdef CONFIG_ADB_CUDA + for (i = 0; i < 3; ++i) { + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, + 0x50, i + 1, params[i]); + while (!req.complete) + cuda_poll(); + } +#endif +} + +static void __init valkyrie_choose_mode(struct fb_info_valkyrie *p) +{ + p->sense = read_valkyrie_sense(p); + printk(KERN_INFO "Monitor sense value = 0x%x\n", p->sense); + + /* Try to pick a video mode out of NVRAM if we have one. */ +#if !defined(CONFIG_MAC) && defined(CONFIG_NVRAM) + if (default_vmode == VMODE_NVRAM) { + default_vmode = nvram_read_byte(NV_VMODE); + if (default_vmode <= 0 + || default_vmode > VMODE_MAX + || !valkyrie_reg_init[default_vmode - 1]) + default_vmode = VMODE_CHOOSE; + } +#endif + if (default_vmode == VMODE_CHOOSE) + default_vmode = mac_map_monitor_sense(p->sense); + if (!valkyrie_reg_init[default_vmode - 1]) + default_vmode = VMODE_640_480_67; +#if !defined(CONFIG_MAC) && defined(CONFIG_NVRAM) + if (default_cmode == CMODE_NVRAM) + default_cmode = nvram_read_byte(NV_CMODE); +#endif + + /* + * Reduce the pixel size if we don't have enough VRAM or bandwidth. + */ + if (default_cmode < CMODE_8 || default_cmode > CMODE_16 + || valkyrie_reg_init[default_vmode-1]->pitch[default_cmode] == 0 + || valkyrie_vram_reqd(default_vmode, default_cmode) > p->total_vram) + default_cmode = CMODE_8; + + printk(KERN_INFO "using video mode %d and color mode %d.\n", + default_vmode, default_cmode); +} + +int __init valkyriefb_init(void) +{ + struct fb_info_valkyrie *p; + unsigned long frame_buffer_phys, cmap_regs_phys, flags; + int err; + char *option = NULL; + + if (fb_get_options("valkyriefb", &option)) + return -ENODEV; + valkyriefb_setup(option); + +#ifdef CONFIG_MAC + if (!MACH_IS_MAC) + return -ENODEV; + if (!(mac_bi_data.id == MAC_MODEL_Q630 + /* I'm not sure about this one */ + || mac_bi_data.id == MAC_MODEL_P588)) + return -ENODEV; + + /* Hardcoded addresses... welcome to 68k Macintosh country :-) */ + frame_buffer_phys = 0xf9000000; + cmap_regs_phys = 0x50f24000; + flags = IOMAP_NOCACHE_SER; /* IOMAP_WRITETHROUGH?? */ +#else /* ppc (!CONFIG_MAC) */ + { + struct device_node *dp; + struct resource r; + + dp = of_find_node_by_name(NULL, "valkyrie"); + if (dp == 0) + return 0; + + if (of_address_to_resource(dp, 0, &r)) { + printk(KERN_ERR "can't find address for valkyrie\n"); + return 0; + } + + frame_buffer_phys = r.start; + cmap_regs_phys = r.start + 0x304000; + flags = _PAGE_WRITETHRU; + } +#endif /* ppc (!CONFIG_MAC) */ + + p = kzalloc(sizeof(*p), GFP_ATOMIC); + if (p == 0) + return -ENOMEM; + + /* Map in frame buffer and registers */ + if (!request_mem_region(frame_buffer_phys, 0x100000, "valkyriefb")) { + kfree(p); + return 0; + } + p->total_vram = 0x100000; + p->frame_buffer_phys = frame_buffer_phys; + p->frame_buffer = __ioremap(frame_buffer_phys, p->total_vram, flags); + p->cmap_regs_phys = cmap_regs_phys; + p->cmap_regs = ioremap(p->cmap_regs_phys, 0x1000); + p->valkyrie_regs_phys = cmap_regs_phys+0x6000; + p->valkyrie_regs = ioremap(p->valkyrie_regs_phys, 0x1000); + err = -ENOMEM; + if (p->frame_buffer == NULL || p->cmap_regs == NULL + || p->valkyrie_regs == NULL) { + printk(KERN_ERR "valkyriefb: couldn't map resources\n"); + goto out_free; + } + + valkyrie_choose_mode(p); + mac_vmode_to_var(default_vmode, default_cmode, &p->info.var); + err = valkyrie_init_info(&p->info, p); + if (err < 0) + goto out_free; + valkyrie_init_fix(&p->info.fix, p); + if (valkyriefb_set_par(&p->info)) + /* "can't happen" */ + printk(KERN_ERR "valkyriefb: can't set default video mode\n"); + + if ((err = register_framebuffer(&p->info)) != 0) + goto out_cmap_free; + + fb_info(&p->info, "valkyrie frame buffer device\n"); + return 0; + + out_cmap_free: + fb_dealloc_cmap(&p->info.cmap); + out_free: + if (p->frame_buffer) + iounmap(p->frame_buffer); + if (p->cmap_regs) + iounmap(p->cmap_regs); + if (p->valkyrie_regs) + iounmap(p->valkyrie_regs); + kfree(p); + return err; +} + +/* + * Get the monitor sense value. + */ +static int read_valkyrie_sense(struct fb_info_valkyrie *p) +{ + int sense, in; + + out_8(&p->valkyrie_regs->msense.r, 0); /* release all lines */ + __delay(20000); + sense = ((in = in_8(&p->valkyrie_regs->msense.r)) & 0x70) << 4; + /* drive each sense line low in turn and collect the other 2 */ + out_8(&p->valkyrie_regs->msense.r, 4); /* drive A low */ + __delay(20000); + sense |= ((in = in_8(&p->valkyrie_regs->msense.r)) & 0x30); + out_8(&p->valkyrie_regs->msense.r, 2); /* drive B low */ + __delay(20000); + sense |= ((in = in_8(&p->valkyrie_regs->msense.r)) & 0x40) >> 3; + sense |= (in & 0x10) >> 2; + out_8(&p->valkyrie_regs->msense.r, 1); /* drive C low */ + __delay(20000); + sense |= ((in = in_8(&p->valkyrie_regs->msense.r)) & 0x60) >> 5; + + out_8(&p->valkyrie_regs->msense.r, 7); + + return sense; +} + +/* + * This routine takes a user-supplied var, + * and picks the best vmode/cmode from it. + */ + +/* [bkn] I did a major overhaul of this function. + * + * Much of the old code was "swiped by jonh from atyfb.c". Because + * macmodes has mac_var_to_vmode, I felt that it would be better to + * rework this function to use that, instead of reinventing the wheel to + * add support for vmode 17. This was reinforced by the fact that + * the previously swiped atyfb.c code is no longer there. + * + * So, I swiped and adapted platinum_var_to_par (from platinumfb.c), replacing + * most, but not all, of the old code in the process. One side benefit of + * swiping the platinumfb code is that we now have more comprehensible error + * messages when a vmode/cmode switch fails. (Most of the error messages are + * platinumfb.c, but I added two of my own, and I also changed some commas + * into colons to make the messages more consistent with other Linux error + * messages.) In addition, I think the new code *might* fix some vmode- + * switching oddities, but I'm not sure. + * + * There may be some more opportunities for cleanup in here, but this is a + * good start... + */ + +static int valkyrie_var_to_par(struct fb_var_screeninfo *var, + struct fb_par_valkyrie *par, const struct fb_info *fb_info) +{ + int vmode, cmode; + struct valkyrie_regvals *init; + struct fb_info_valkyrie *p = (struct fb_info_valkyrie *) fb_info; + + if (mac_var_to_vmode(var, &vmode, &cmode) != 0) { + printk(KERN_ERR "valkyriefb: can't do %dx%dx%d.\n", + var->xres, var->yres, var->bits_per_pixel); + return -EINVAL; + } + + /* Check if we know about the wanted video mode */ + if (vmode < 1 || vmode > VMODE_MAX || !valkyrie_reg_init[vmode-1]) { + printk(KERN_ERR "valkyriefb: vmode %d not valid.\n", vmode); + return -EINVAL; + } + + if (cmode != CMODE_8 && cmode != CMODE_16) { + printk(KERN_ERR "valkyriefb: cmode %d not valid.\n", cmode); + return -EINVAL; + } + + if (var->xres_virtual > var->xres || var->yres_virtual > var->yres + || var->xoffset != 0 || var->yoffset != 0) { + return -EINVAL; + } + + init = valkyrie_reg_init[vmode-1]; + if (init->pitch[cmode] == 0) { + printk(KERN_ERR "valkyriefb: vmode %d does not support " + "cmode %d.\n", vmode, cmode); + return -EINVAL; + } + + if (valkyrie_vram_reqd(vmode, cmode) > p->total_vram) { + printk(KERN_ERR "valkyriefb: not enough ram for vmode %d, " + "cmode %d.\n", vmode, cmode); + return -EINVAL; + } + + par->vmode = vmode; + par->cmode = cmode; + par->init = init; + par->xres = var->xres; + par->yres = var->yres; + par->vxres = par->xres; + par->vyres = par->yres; + + return 0; +} + +static void valkyrie_init_fix(struct fb_fix_screeninfo *fix, struct fb_info_valkyrie *p) +{ + memset(fix, 0, sizeof(*fix)); + strcpy(fix->id, "valkyrie"); + fix->mmio_start = p->valkyrie_regs_phys; + fix->mmio_len = sizeof(struct valkyrie_regs); + fix->type = FB_TYPE_PACKED_PIXELS; + fix->smem_start = p->frame_buffer_phys + 0x1000; + fix->smem_len = p->total_vram; + + fix->type_aux = 0; + fix->ywrapstep = 0; + fix->ypanstep = 0; + fix->xpanstep = 0; + +} + +/* Fix must already be inited above */ +static void valkyrie_par_to_fix(struct fb_par_valkyrie *par, + struct fb_fix_screeninfo *fix) +{ + fix->smem_len = valkyrie_vram_reqd(par->vmode, par->cmode); + fix->visual = (par->cmode == CMODE_8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR; + fix->line_length = par->vxres << par->cmode; + /* ywrapstep, xpanstep, ypanstep */ +} + +static int __init valkyrie_init_info(struct fb_info *info, + struct fb_info_valkyrie *p) +{ + info->fbops = &valkyriefb_ops; + info->screen_base = p->frame_buffer + 0x1000; + info->flags = FBINFO_DEFAULT; + info->pseudo_palette = p->pseudo_palette; + info->par = &p->par; + return fb_alloc_cmap(&info->cmap, 256, 0); +} + + +/* + * Parse user specified options (`video=valkyriefb:') + */ +int __init valkyriefb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!strncmp(this_opt, "vmode:", 6)) { + int vmode = simple_strtoul(this_opt+6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX) + default_vmode = vmode; + } + else if (!strncmp(this_opt, "cmode:", 6)) { + int depth = simple_strtoul(this_opt+6, NULL, 0); + switch (depth) { + case 8: + default_cmode = CMODE_8; + break; + case 15: + case 16: + default_cmode = CMODE_16; + break; + } + } + } + return 0; +} + +module_init(valkyriefb_init); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/valkyriefb.h b/drivers/video/fbdev/valkyriefb.h new file mode 100644 index 000000000000..d787441e5a42 --- /dev/null +++ b/drivers/video/fbdev/valkyriefb.h @@ -0,0 +1,200 @@ +/* + * valkyriefb.h: Constants of all sorts for valkyriefb + * + * Created 8 August 1998 by + * Martin Costabel <costabel@wanadoo.fr> and Kevin Schoedel + * + * Vmode-switching changes and vmode 15/17 modifications created 29 August + * 1998 by Barry K. Nathan <barryn@pobox.com>. + * + * vmode 10 changed by Steven Borley <sjb@salix.demon.co.uk>, 14 mai 2000 + * + * Ported to 68k Macintosh by David Huggins-Daines <dhd@debian.org> + * + * 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. + * + * Based directly on: + * + * controlfb.h: Constants of all sorts for controlfb + * Copyright (C) 1998 Daniel Jacobowitz <dan@debian.org> + * + * pmc-valkyrie.h: Console support for PowerMac "control" display adaptor. + * Copyright (C) 1997 Paul Mackerras. + * + * pmc-valkyrie.c: Console support for PowerMac "control" display adaptor. + * Copyright (C) 1997 Paul Mackerras. + * + * and indirectly from: + * + * pmc-control.h: Console support for PowerMac "control" display adaptor. + * Copyright (C) 1997 Paul Mackerras. + * + * pmc-control.c: Console support for PowerMac "control" display adaptor. + * Copyright (C) 1996 Paul Mackerras. + * + * platinumfb.c: Console support for PowerMac "platinum" display adaptor. + * Copyright (C) 1998 Jon Howell + */ + +#ifdef CONFIG_MAC +/* Valkyrie registers are word-aligned on m68k */ +#define VALKYRIE_REG_PADSIZE 3 +#else +#define VALKYRIE_REG_PADSIZE 7 +#endif + +/* + * Structure of the registers for the Valkyrie colormap registers. + */ +struct cmap_regs { + unsigned char addr; + char pad1[VALKYRIE_REG_PADSIZE]; + unsigned char lut; +}; + +/* + * Structure of the registers for the "valkyrie" display adaptor. + */ + +struct vpreg { /* padded register */ + unsigned char r; + char pad[VALKYRIE_REG_PADSIZE]; +}; + + +struct valkyrie_regs { + struct vpreg mode; + struct vpreg depth; + struct vpreg status; + struct vpreg reg3; + struct vpreg intr; + struct vpreg reg5; + struct vpreg intr_enb; + struct vpreg msense; +}; + +/* + * Register initialization tables for the valkyrie display. + * + * Dot clock rate is + * 3.9064MHz * 2**clock_params[2] * clock_params[1] / clock_params[0]. + */ +struct valkyrie_regvals { + unsigned char mode; + unsigned char clock_params[3]; + int pitch[2]; /* bytes/line, indexed by color_mode */ + int hres; + int vres; +}; + +#ifndef CONFIG_MAC +/* Register values for 1024x768, 75Hz mode (17) */ +/* I'm not sure which mode this is (16 or 17), so I'm defining it as 17, + * since the equivalent mode in controlfb (which I adapted this from) is + * also 17. Just because MacOS can't do this on Valkyrie doesn't mean we + * can't! :) + * + * I was going to use 12, 31, 3, which I found by myself, but instead I'm + * using 11, 28, 3 like controlfb, for consistency's sake. + */ + +static struct valkyrie_regvals valkyrie_reg_init_17 = { + 15, + { 11, 28, 3 }, /* pixel clock = 79.55MHz for V=74.50Hz */ + { 1024, 0 }, + 1024, 768 +}; + +/* Register values for 1024x768, 72Hz mode (15) */ +/* This used to be 12, 30, 3 for pixel clock = 78.12MHz for V=72.12Hz, but + * that didn't match MacOS in the same video mode on this chip, and it also + * caused the 15" Apple Studio Display to not work in this mode. While this + * mode still doesn't match MacOS exactly (as far as I can tell), it's a lot + * closer now, and it works with the Apple Studio Display. + * + * Yes, even though MacOS calls it "72Hz", in reality it's about 70Hz. + */ +static struct valkyrie_regvals valkyrie_reg_init_15 = { + 15, + { 12, 29, 3 }, /* pixel clock = 75.52MHz for V=69.71Hz? */ + /* I interpolated the V=69.71 from the vmode 14 and old 15 + * numbers. Is this result correct? + */ + { 1024, 0 }, + 1024, 768 +}; + +/* Register values for 1024x768, 60Hz mode (14) */ +static struct valkyrie_regvals valkyrie_reg_init_14 = { + 14, + { 15, 31, 3 }, /* pixel clock = 64.58MHz for V=59.62Hz */ + { 1024, 0 }, + 1024, 768 +}; +#endif /* !defined CONFIG_MAC */ + +/* Register values for 832x624, 75Hz mode (13) */ +static struct valkyrie_regvals valkyrie_reg_init_13 = { + 9, + { 23, 42, 3 }, /* pixel clock = 57.07MHz for V=74.27Hz */ + { 832, 0 }, + 832, 624 +}; + +/* Register values for 800x600, 72Hz mode (11) */ +static struct valkyrie_regvals valkyrie_reg_init_11 = { + 13, + { 17, 27, 3 }, /* pixel clock = 49.63MHz for V=71.66Hz */ + { 800, 0 }, + 800, 600 +}; + +/* Register values for 800x600, 60Hz mode (10) */ +static struct valkyrie_regvals valkyrie_reg_init_10 = { + 12, + { 25, 32, 3 }, /* pixel clock = 40.0015MHz, + used to be 20,53,2, pixel clock 41.41MHz for V=59.78Hz */ + { 800, 1600 }, + 800, 600 +}; + +/* Register values for 640x480, 67Hz mode (6) */ +static struct valkyrie_regvals valkyrie_reg_init_6 = { + 6, + { 14, 27, 2 }, /* pixel clock = 30.13MHz for V=66.43Hz */ + { 640, 1280 }, + 640, 480 +}; + +/* Register values for 640x480, 60Hz mode (5) */ +static struct valkyrie_regvals valkyrie_reg_init_5 = { + 11, + { 23, 37, 2 }, /* pixel clock = 25.14MHz for V=59.85Hz */ + { 640, 1280 }, + 640, 480 +}; + +static struct valkyrie_regvals *valkyrie_reg_init[VMODE_MAX] = { + NULL, + NULL, + NULL, + NULL, + &valkyrie_reg_init_5, + &valkyrie_reg_init_6, + NULL, + NULL, + NULL, + &valkyrie_reg_init_10, + &valkyrie_reg_init_11, + NULL, + &valkyrie_reg_init_13, +#ifndef CONFIG_MAC + &valkyrie_reg_init_14, + &valkyrie_reg_init_15, + NULL, + &valkyrie_reg_init_17, +#endif +}; diff --git a/drivers/video/fbdev/vermilion/Makefile b/drivers/video/fbdev/vermilion/Makefile new file mode 100644 index 000000000000..cc21a656153d --- /dev/null +++ b/drivers/video/fbdev/vermilion/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_FB_LE80578) += vmlfb.o +obj-$(CONFIG_FB_CARILLO_RANCH) += crvml.o + +vmlfb-objs := vermilion.o +crvml-objs := cr_pll.o diff --git a/drivers/video/fbdev/vermilion/cr_pll.c b/drivers/video/fbdev/vermilion/cr_pll.c new file mode 100644 index 000000000000..ebc6e6e0dd0f --- /dev/null +++ b/drivers/video/fbdev/vermilion/cr_pll.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) Intel Corp. 2007. + * All Rights Reserved. + * + * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to + * develop this driver. + * + * This file is part of the Carillo Ranch video subsystem driver. + * The Carillo Ranch video subsystem driver 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. + * + * The Carillo Ranch video subsystem driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Thomas Hellstrom <thomas-at-tungstengraphics-dot-com> + * Alan Hourihane <alanh-at-tungstengraphics-dot-com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include "vermilion.h" + +/* The PLL Clock register sits on Host bridge */ +#define CRVML_DEVICE_MCH 0x5001 +#define CRVML_REG_MCHBAR 0x44 +#define CRVML_REG_MCHEN 0x54 +#define CRVML_MCHEN_BIT (1 << 28) +#define CRVML_MCHMAP_SIZE 4096 +#define CRVML_REG_CLOCK 0xc3c +#define CRVML_CLOCK_SHIFT 8 +#define CRVML_CLOCK_MASK 0x00000f00 + +static struct pci_dev *mch_dev; +static u32 mch_bar; +static void __iomem *mch_regs_base; +static u32 saved_clock; + +static const unsigned crvml_clocks[] = { + 6750, + 13500, + 27000, + 29700, + 37125, + 54000, + 59400, + 74250, + 120000 + /* + * There are more clocks, but they are disabled on the CR board. + */ +}; + +static const u32 crvml_clock_bits[] = { + 0x0a, + 0x09, + 0x08, + 0x07, + 0x06, + 0x05, + 0x04, + 0x03, + 0x0b +}; + +static const unsigned crvml_num_clocks = ARRAY_SIZE(crvml_clocks); + +static int crvml_sys_restore(struct vml_sys *sys) +{ + void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK; + + iowrite32(saved_clock, clock_reg); + ioread32(clock_reg); + + return 0; +} + +static int crvml_sys_save(struct vml_sys *sys) +{ + void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK; + + saved_clock = ioread32(clock_reg); + + return 0; +} + +static int crvml_nearest_index(const struct vml_sys *sys, int clock) +{ + int i; + int cur_index = 0; + int cur_diff; + int diff; + + cur_diff = clock - crvml_clocks[0]; + cur_diff = (cur_diff < 0) ? -cur_diff : cur_diff; + for (i = 1; i < crvml_num_clocks; ++i) { + diff = clock - crvml_clocks[i]; + diff = (diff < 0) ? -diff : diff; + if (diff < cur_diff) { + cur_index = i; + cur_diff = diff; + } + } + return cur_index; +} + +static int crvml_nearest_clock(const struct vml_sys *sys, int clock) +{ + return crvml_clocks[crvml_nearest_index(sys, clock)]; +} + +static int crvml_set_clock(struct vml_sys *sys, int clock) +{ + void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK; + int index; + u32 clock_val; + + index = crvml_nearest_index(sys, clock); + + if (crvml_clocks[index] != clock) + return -EINVAL; + + clock_val = ioread32(clock_reg) & ~CRVML_CLOCK_MASK; + clock_val = crvml_clock_bits[index] << CRVML_CLOCK_SHIFT; + iowrite32(clock_val, clock_reg); + ioread32(clock_reg); + + return 0; +} + +static struct vml_sys cr_pll_ops = { + .name = "Carillo Ranch", + .save = crvml_sys_save, + .restore = crvml_sys_restore, + .set_clock = crvml_set_clock, + .nearest_clock = crvml_nearest_clock, +}; + +static int __init cr_pll_init(void) +{ + int err; + u32 dev_en; + + mch_dev = pci_get_device(PCI_VENDOR_ID_INTEL, + CRVML_DEVICE_MCH, NULL); + if (!mch_dev) { + printk(KERN_ERR + "Could not find Carillo Ranch MCH device.\n"); + return -ENODEV; + } + + pci_read_config_dword(mch_dev, CRVML_REG_MCHEN, &dev_en); + if (!(dev_en & CRVML_MCHEN_BIT)) { + printk(KERN_ERR + "Carillo Ranch MCH device was not enabled.\n"); + pci_dev_put(mch_dev); + return -ENODEV; + } + + pci_read_config_dword(mch_dev, CRVML_REG_MCHBAR, + &mch_bar); + mch_regs_base = + ioremap_nocache(mch_bar, CRVML_MCHMAP_SIZE); + if (!mch_regs_base) { + printk(KERN_ERR + "Carillo Ranch MCH device was not enabled.\n"); + pci_dev_put(mch_dev); + return -ENODEV; + } + + err = vmlfb_register_subsys(&cr_pll_ops); + if (err) { + printk(KERN_ERR + "Carillo Ranch failed to initialize vml_sys.\n"); + pci_dev_put(mch_dev); + return err; + } + + return 0; +} + +static void __exit cr_pll_exit(void) +{ + vmlfb_unregister_subsys(&cr_pll_ops); + + iounmap(mch_regs_base); + pci_dev_put(mch_dev); +} + +module_init(cr_pll_init); +module_exit(cr_pll_exit); + +MODULE_AUTHOR("Tungsten Graphics Inc."); +MODULE_DESCRIPTION("Carillo Ranch PLL Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/vermilion/vermilion.c b/drivers/video/fbdev/vermilion/vermilion.c new file mode 100644 index 000000000000..048a66640b03 --- /dev/null +++ b/drivers/video/fbdev/vermilion/vermilion.c @@ -0,0 +1,1175 @@ +/* + * Copyright (c) Intel Corp. 2007. + * All Rights Reserved. + * + * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to + * develop this driver. + * + * This file is part of the Vermilion Range fb driver. + * The Vermilion Range fb driver 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. + * + * The Vermilion Range fb driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Thomas Hellström <thomas-at-tungstengraphics-dot-com> + * Michel Dänzer <michel-at-tungstengraphics-dot-com> + * Alan Hourihane <alanh-at-tungstengraphics-dot-com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <linux/mmzone.h> + +/* #define VERMILION_DEBUG */ + +#include "vermilion.h" + +#define MODULE_NAME "vmlfb" + +#define VML_TOHW(_val, _width) ((((_val) << (_width)) + 0x7FFF - (_val)) >> 16) + +static struct mutex vml_mutex; +static struct list_head global_no_mode; +static struct list_head global_has_mode; +static struct fb_ops vmlfb_ops; +static struct vml_sys *subsys = NULL; +static char *vml_default_mode = "1024x768@60"; +static struct fb_videomode defaultmode = { + NULL, 60, 1024, 768, 12896, 144, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED +}; + +static u32 vml_mem_requested = (10 * 1024 * 1024); +static u32 vml_mem_contig = (4 * 1024 * 1024); +static u32 vml_mem_min = (4 * 1024 * 1024); + +static u32 vml_clocks[] = { + 6750, + 13500, + 27000, + 29700, + 37125, + 54000, + 59400, + 74250, + 120000, + 148500 +}; + +static u32 vml_num_clocks = ARRAY_SIZE(vml_clocks); + +/* + * Allocate a contiguous vram area and make its linear kernel map + * uncached. + */ + +static int vmlfb_alloc_vram_area(struct vram_area *va, unsigned max_order, + unsigned min_order) +{ + gfp_t flags; + unsigned long i; + + max_order++; + do { + /* + * Really try hard to get the needed memory. + * We need memory below the first 32MB, so we + * add the __GFP_DMA flag that guarantees that we are + * below the first 16MB. + */ + + flags = __GFP_DMA | __GFP_HIGH; + va->logical = + __get_free_pages(flags, --max_order); + } while (va->logical == 0 && max_order > min_order); + + if (!va->logical) + return -ENOMEM; + + va->phys = virt_to_phys((void *)va->logical); + va->size = PAGE_SIZE << max_order; + va->order = max_order; + + /* + * It seems like __get_free_pages only ups the usage count + * of the first page. This doesn't work with fault mapping, so + * up the usage count once more (XXX: should use split_page or + * compound page). + */ + + memset((void *)va->logical, 0x00, va->size); + for (i = va->logical; i < va->logical + va->size; i += PAGE_SIZE) { + get_page(virt_to_page(i)); + } + + /* + * Change caching policy of the linear kernel map to avoid + * mapping type conflicts with user-space mappings. + */ + set_pages_uc(virt_to_page(va->logical), va->size >> PAGE_SHIFT); + + printk(KERN_DEBUG MODULE_NAME + ": Allocated %ld bytes vram area at 0x%08lx\n", + va->size, va->phys); + + return 0; +} + +/* + * Free a contiguous vram area and reset its linear kernel map + * mapping type. + */ + +static void vmlfb_free_vram_area(struct vram_area *va) +{ + unsigned long j; + + if (va->logical) { + + /* + * Reset the linear kernel map caching policy. + */ + + set_pages_wb(virt_to_page(va->logical), + va->size >> PAGE_SHIFT); + + /* + * Decrease the usage count on the pages we've used + * to compensate for upping when allocating. + */ + + for (j = va->logical; j < va->logical + va->size; + j += PAGE_SIZE) { + (void)put_page_testzero(virt_to_page(j)); + } + + printk(KERN_DEBUG MODULE_NAME + ": Freeing %ld bytes vram area at 0x%08lx\n", + va->size, va->phys); + free_pages(va->logical, va->order); + + va->logical = 0; + } +} + +/* + * Free allocated vram. + */ + +static void vmlfb_free_vram(struct vml_info *vinfo) +{ + int i; + + for (i = 0; i < vinfo->num_areas; ++i) { + vmlfb_free_vram_area(&vinfo->vram[i]); + } + vinfo->num_areas = 0; +} + +/* + * Allocate vram. Currently we try to allocate contiguous areas from the + * __GFP_DMA zone and puzzle them together. A better approach would be to + * allocate one contiguous area for scanout and use one-page allocations for + * offscreen areas. This requires user-space and GPU virtual mappings. + */ + +static int vmlfb_alloc_vram(struct vml_info *vinfo, + size_t requested, + size_t min_total, size_t min_contig) +{ + int i, j; + int order; + int contiguous; + int err; + struct vram_area *va; + struct vram_area *va2; + + vinfo->num_areas = 0; + for (i = 0; i < VML_VRAM_AREAS; ++i) { + va = &vinfo->vram[i]; + order = 0; + + while (requested > (PAGE_SIZE << order) && order < MAX_ORDER) + order++; + + err = vmlfb_alloc_vram_area(va, order, 0); + + if (err) + break; + + if (i == 0) { + vinfo->vram_start = va->phys; + vinfo->vram_logical = (void __iomem *) va->logical; + vinfo->vram_contig_size = va->size; + vinfo->num_areas = 1; + } else { + contiguous = 0; + + for (j = 0; j < i; ++j) { + va2 = &vinfo->vram[j]; + if (va->phys + va->size == va2->phys || + va2->phys + va2->size == va->phys) { + contiguous = 1; + break; + } + } + + if (contiguous) { + vinfo->num_areas++; + if (va->phys < vinfo->vram_start) { + vinfo->vram_start = va->phys; + vinfo->vram_logical = + (void __iomem *)va->logical; + } + vinfo->vram_contig_size += va->size; + } else { + vmlfb_free_vram_area(va); + break; + } + } + + if (requested < va->size) + break; + else + requested -= va->size; + } + + if (vinfo->vram_contig_size > min_total && + vinfo->vram_contig_size > min_contig) { + + printk(KERN_DEBUG MODULE_NAME + ": Contiguous vram: %ld bytes at physical 0x%08lx.\n", + (unsigned long)vinfo->vram_contig_size, + (unsigned long)vinfo->vram_start); + + return 0; + } + + printk(KERN_ERR MODULE_NAME + ": Could not allocate requested minimal amount of vram.\n"); + + vmlfb_free_vram(vinfo); + + return -ENOMEM; +} + +/* + * Find the GPU to use with our display controller. + */ + +static int vmlfb_get_gpu(struct vml_par *par) +{ + mutex_lock(&vml_mutex); + + par->gpu = pci_get_device(PCI_VENDOR_ID_INTEL, VML_DEVICE_GPU, NULL); + + if (!par->gpu) { + mutex_unlock(&vml_mutex); + return -ENODEV; + } + + mutex_unlock(&vml_mutex); + + if (pci_enable_device(par->gpu) < 0) + return -ENODEV; + + return 0; +} + +/* + * Find a contiguous vram area that contains a given offset from vram start. + */ +static int vmlfb_vram_offset(struct vml_info *vinfo, unsigned long offset) +{ + unsigned long aoffset; + unsigned i; + + for (i = 0; i < vinfo->num_areas; ++i) { + aoffset = offset - (vinfo->vram[i].phys - vinfo->vram_start); + + if (aoffset < vinfo->vram[i].size) { + return 0; + } + } + + return -EINVAL; +} + +/* + * Remap the MMIO register spaces of the VDC and the GPU. + */ + +static int vmlfb_enable_mmio(struct vml_par *par) +{ + int err; + + par->vdc_mem_base = pci_resource_start(par->vdc, 0); + par->vdc_mem_size = pci_resource_len(par->vdc, 0); + if (!request_mem_region(par->vdc_mem_base, par->vdc_mem_size, "vmlfb")) { + printk(KERN_ERR MODULE_NAME + ": Could not claim display controller MMIO.\n"); + return -EBUSY; + } + par->vdc_mem = ioremap_nocache(par->vdc_mem_base, par->vdc_mem_size); + if (par->vdc_mem == NULL) { + printk(KERN_ERR MODULE_NAME + ": Could not map display controller MMIO.\n"); + err = -ENOMEM; + goto out_err_0; + } + + par->gpu_mem_base = pci_resource_start(par->gpu, 0); + par->gpu_mem_size = pci_resource_len(par->gpu, 0); + if (!request_mem_region(par->gpu_mem_base, par->gpu_mem_size, "vmlfb")) { + printk(KERN_ERR MODULE_NAME ": Could not claim GPU MMIO.\n"); + err = -EBUSY; + goto out_err_1; + } + par->gpu_mem = ioremap_nocache(par->gpu_mem_base, par->gpu_mem_size); + if (par->gpu_mem == NULL) { + printk(KERN_ERR MODULE_NAME ": Could not map GPU MMIO.\n"); + err = -ENOMEM; + goto out_err_2; + } + + return 0; + +out_err_2: + release_mem_region(par->gpu_mem_base, par->gpu_mem_size); +out_err_1: + iounmap(par->vdc_mem); +out_err_0: + release_mem_region(par->vdc_mem_base, par->vdc_mem_size); + return err; +} + +/* + * Unmap the VDC and GPU register spaces. + */ + +static void vmlfb_disable_mmio(struct vml_par *par) +{ + iounmap(par->gpu_mem); + release_mem_region(par->gpu_mem_base, par->gpu_mem_size); + iounmap(par->vdc_mem); + release_mem_region(par->vdc_mem_base, par->vdc_mem_size); +} + +/* + * Release and uninit the VDC and GPU. + */ + +static void vmlfb_release_devices(struct vml_par *par) +{ + if (atomic_dec_and_test(&par->refcount)) { + pci_disable_device(par->gpu); + pci_disable_device(par->vdc); + } +} + +/* + * Free up allocated resources for a device. + */ + +static void vml_pci_remove(struct pci_dev *dev) +{ + struct fb_info *info; + struct vml_info *vinfo; + struct vml_par *par; + + info = pci_get_drvdata(dev); + if (info) { + vinfo = container_of(info, struct vml_info, info); + par = vinfo->par; + mutex_lock(&vml_mutex); + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + vmlfb_free_vram(vinfo); + vmlfb_disable_mmio(par); + vmlfb_release_devices(par); + kfree(vinfo); + kfree(par); + mutex_unlock(&vml_mutex); + } +} + +static void vmlfb_set_pref_pixel_format(struct fb_var_screeninfo *var) +{ + switch (var->bits_per_pixel) { + case 16: + var->blue.offset = 0; + var->blue.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->red.offset = 10; + var->red.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + break; + case 32: + var->blue.offset = 0; + var->blue.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->transp.offset = 24; + var->transp.length = 0; + break; + default: + break; + } + + var->blue.msb_right = var->green.msb_right = + var->red.msb_right = var->transp.msb_right = 0; +} + +/* + * Device initialization. + * We initialize one vml_par struct per device and one vml_info + * struct per pipe. Currently we have only one pipe. + */ + +static int vml_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct vml_info *vinfo; + struct fb_info *info; + struct vml_par *par; + int err = 0; + + par = kzalloc(sizeof(*par), GFP_KERNEL); + if (par == NULL) + return -ENOMEM; + + vinfo = kzalloc(sizeof(*vinfo), GFP_KERNEL); + if (vinfo == NULL) { + err = -ENOMEM; + goto out_err_0; + } + + vinfo->par = par; + par->vdc = dev; + atomic_set(&par->refcount, 1); + + switch (id->device) { + case VML_DEVICE_VDC: + if ((err = vmlfb_get_gpu(par))) + goto out_err_1; + pci_set_drvdata(dev, &vinfo->info); + break; + default: + err = -ENODEV; + goto out_err_1; + break; + } + + info = &vinfo->info; + info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK; + + err = vmlfb_enable_mmio(par); + if (err) + goto out_err_2; + + err = vmlfb_alloc_vram(vinfo, vml_mem_requested, + vml_mem_contig, vml_mem_min); + if (err) + goto out_err_3; + + strcpy(info->fix.id, "Vermilion Range"); + info->fix.mmio_start = 0; + info->fix.mmio_len = 0; + info->fix.smem_start = vinfo->vram_start; + info->fix.smem_len = vinfo->vram_contig_size; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.ypanstep = 1; + info->fix.xpanstep = 1; + info->fix.ywrapstep = 0; + info->fix.accel = FB_ACCEL_NONE; + info->screen_base = vinfo->vram_logical; + info->pseudo_palette = vinfo->pseudo_palette; + info->par = par; + info->fbops = &vmlfb_ops; + info->device = &dev->dev; + + INIT_LIST_HEAD(&vinfo->head); + vinfo->pipe_disabled = 1; + vinfo->cur_blank_mode = FB_BLANK_UNBLANK; + + info->var.grayscale = 0; + info->var.bits_per_pixel = 16; + vmlfb_set_pref_pixel_format(&info->var); + + if (!fb_find_mode + (&info->var, info, vml_default_mode, NULL, 0, &defaultmode, 16)) { + printk(KERN_ERR MODULE_NAME ": Could not find initial mode\n"); + } + + if (fb_alloc_cmap(&info->cmap, 256, 1) < 0) { + err = -ENOMEM; + goto out_err_4; + } + + err = register_framebuffer(info); + if (err) { + printk(KERN_ERR MODULE_NAME ": Register framebuffer error.\n"); + goto out_err_5; + } + + printk("Initialized vmlfb\n"); + + return 0; + +out_err_5: + fb_dealloc_cmap(&info->cmap); +out_err_4: + vmlfb_free_vram(vinfo); +out_err_3: + vmlfb_disable_mmio(par); +out_err_2: + vmlfb_release_devices(par); +out_err_1: + kfree(vinfo); +out_err_0: + kfree(par); + return err; +} + +static int vmlfb_open(struct fb_info *info, int user) +{ + /* + * Save registers here? + */ + return 0; +} + +static int vmlfb_release(struct fb_info *info, int user) +{ + /* + * Restore registers here. + */ + + return 0; +} + +static int vml_nearest_clock(int clock) +{ + + int i; + int cur_index; + int cur_diff; + int diff; + + cur_index = 0; + cur_diff = clock - vml_clocks[0]; + cur_diff = (cur_diff < 0) ? -cur_diff : cur_diff; + for (i = 1; i < vml_num_clocks; ++i) { + diff = clock - vml_clocks[i]; + diff = (diff < 0) ? -diff : diff; + if (diff < cur_diff) { + cur_index = i; + cur_diff = diff; + } + } + return vml_clocks[cur_index]; +} + +static int vmlfb_check_var_locked(struct fb_var_screeninfo *var, + struct vml_info *vinfo) +{ + u32 pitch; + u64 mem; + int nearest_clock; + int clock; + int clock_diff; + struct fb_var_screeninfo v; + + v = *var; + clock = PICOS2KHZ(var->pixclock); + + if (subsys && subsys->nearest_clock) { + nearest_clock = subsys->nearest_clock(subsys, clock); + } else { + nearest_clock = vml_nearest_clock(clock); + } + + /* + * Accept a 20% diff. + */ + + clock_diff = nearest_clock - clock; + clock_diff = (clock_diff < 0) ? -clock_diff : clock_diff; + if (clock_diff > clock / 5) { +#if 0 + printk(KERN_DEBUG MODULE_NAME ": Diff failure. %d %d\n",clock_diff,clock); +#endif + return -EINVAL; + } + + v.pixclock = KHZ2PICOS(nearest_clock); + + if (var->xres > VML_MAX_XRES || var->yres > VML_MAX_YRES) { + printk(KERN_DEBUG MODULE_NAME ": Resolution failure.\n"); + return -EINVAL; + } + if (var->xres_virtual > VML_MAX_XRES_VIRTUAL) { + printk(KERN_DEBUG MODULE_NAME + ": Virtual resolution failure.\n"); + return -EINVAL; + } + switch (v.bits_per_pixel) { + case 0 ... 16: + v.bits_per_pixel = 16; + break; + case 17 ... 32: + v.bits_per_pixel = 32; + break; + default: + printk(KERN_DEBUG MODULE_NAME ": Invalid bpp: %d.\n", + var->bits_per_pixel); + return -EINVAL; + } + + pitch = ALIGN((var->xres * var->bits_per_pixel) >> 3, 0x40); + mem = pitch * var->yres_virtual; + if (mem > vinfo->vram_contig_size) { + return -ENOMEM; + } + + switch (v.bits_per_pixel) { + case 16: + if (var->blue.offset != 0 || + var->blue.length != 5 || + var->green.offset != 5 || + var->green.length != 5 || + var->red.offset != 10 || + var->red.length != 5 || + var->transp.offset != 15 || var->transp.length != 1) { + vmlfb_set_pref_pixel_format(&v); + } + break; + case 32: + if (var->blue.offset != 0 || + var->blue.length != 8 || + var->green.offset != 8 || + var->green.length != 8 || + var->red.offset != 16 || + var->red.length != 8 || + (var->transp.length != 0 && var->transp.length != 8) || + (var->transp.length == 8 && var->transp.offset != 24)) { + vmlfb_set_pref_pixel_format(&v); + } + break; + default: + return -EINVAL; + } + + *var = v; + + return 0; +} + +static int vmlfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct vml_info *vinfo = container_of(info, struct vml_info, info); + int ret; + + mutex_lock(&vml_mutex); + ret = vmlfb_check_var_locked(var, vinfo); + mutex_unlock(&vml_mutex); + + return ret; +} + +static void vml_wait_vblank(struct vml_info *vinfo) +{ + /* Wait for vblank. For now, just wait for a 50Hz cycle (20ms)) */ + mdelay(20); +} + +static void vmlfb_disable_pipe(struct vml_info *vinfo) +{ + struct vml_par *par = vinfo->par; + + /* Disable the MDVO pad */ + VML_WRITE32(par, VML_RCOMPSTAT, 0); + while (!(VML_READ32(par, VML_RCOMPSTAT) & VML_MDVO_VDC_I_RCOMP)) ; + + /* Disable display planes */ + VML_WRITE32(par, VML_DSPCCNTR, + VML_READ32(par, VML_DSPCCNTR) & ~VML_GFX_ENABLE); + (void)VML_READ32(par, VML_DSPCCNTR); + /* Wait for vblank for the disable to take effect */ + vml_wait_vblank(vinfo); + + /* Next, disable display pipes */ + VML_WRITE32(par, VML_PIPEACONF, 0); + (void)VML_READ32(par, VML_PIPEACONF); + + vinfo->pipe_disabled = 1; +} + +#ifdef VERMILION_DEBUG +static void vml_dump_regs(struct vml_info *vinfo) +{ + struct vml_par *par = vinfo->par; + + printk(KERN_DEBUG MODULE_NAME ": Modesetting register dump:\n"); + printk(KERN_DEBUG MODULE_NAME ": \tHTOTAL_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_HTOTAL_A)); + printk(KERN_DEBUG MODULE_NAME ": \tHBLANK_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_HBLANK_A)); + printk(KERN_DEBUG MODULE_NAME ": \tHSYNC_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_HSYNC_A)); + printk(KERN_DEBUG MODULE_NAME ": \tVTOTAL_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_VTOTAL_A)); + printk(KERN_DEBUG MODULE_NAME ": \tVBLANK_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_VBLANK_A)); + printk(KERN_DEBUG MODULE_NAME ": \tVSYNC_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_VSYNC_A)); + printk(KERN_DEBUG MODULE_NAME ": \tDSPCSTRIDE : 0x%08x\n", + (unsigned)VML_READ32(par, VML_DSPCSTRIDE)); + printk(KERN_DEBUG MODULE_NAME ": \tDSPCSIZE : 0x%08x\n", + (unsigned)VML_READ32(par, VML_DSPCSIZE)); + printk(KERN_DEBUG MODULE_NAME ": \tDSPCPOS : 0x%08x\n", + (unsigned)VML_READ32(par, VML_DSPCPOS)); + printk(KERN_DEBUG MODULE_NAME ": \tDSPARB : 0x%08x\n", + (unsigned)VML_READ32(par, VML_DSPARB)); + printk(KERN_DEBUG MODULE_NAME ": \tDSPCADDR : 0x%08x\n", + (unsigned)VML_READ32(par, VML_DSPCADDR)); + printk(KERN_DEBUG MODULE_NAME ": \tBCLRPAT_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_BCLRPAT_A)); + printk(KERN_DEBUG MODULE_NAME ": \tCANVSCLR_A : 0x%08x\n", + (unsigned)VML_READ32(par, VML_CANVSCLR_A)); + printk(KERN_DEBUG MODULE_NAME ": \tPIPEASRC : 0x%08x\n", + (unsigned)VML_READ32(par, VML_PIPEASRC)); + printk(KERN_DEBUG MODULE_NAME ": \tPIPEACONF : 0x%08x\n", + (unsigned)VML_READ32(par, VML_PIPEACONF)); + printk(KERN_DEBUG MODULE_NAME ": \tDSPCCNTR : 0x%08x\n", + (unsigned)VML_READ32(par, VML_DSPCCNTR)); + printk(KERN_DEBUG MODULE_NAME ": \tRCOMPSTAT : 0x%08x\n", + (unsigned)VML_READ32(par, VML_RCOMPSTAT)); + printk(KERN_DEBUG MODULE_NAME ": End of modesetting register dump.\n"); +} +#endif + +static int vmlfb_set_par_locked(struct vml_info *vinfo) +{ + struct vml_par *par = vinfo->par; + struct fb_info *info = &vinfo->info; + struct fb_var_screeninfo *var = &info->var; + u32 htotal, hactive, hblank_start, hblank_end, hsync_start, hsync_end; + u32 vtotal, vactive, vblank_start, vblank_end, vsync_start, vsync_end; + u32 dspcntr; + int clock; + + vinfo->bytes_per_pixel = var->bits_per_pixel >> 3; + vinfo->stride = ALIGN(var->xres_virtual * vinfo->bytes_per_pixel, 0x40); + info->fix.line_length = vinfo->stride; + + if (!subsys) + return 0; + + htotal = + var->xres + var->right_margin + var->hsync_len + var->left_margin; + hactive = var->xres; + hblank_start = var->xres; + hblank_end = htotal; + hsync_start = hactive + var->right_margin; + hsync_end = hsync_start + var->hsync_len; + + vtotal = + var->yres + var->lower_margin + var->vsync_len + var->upper_margin; + vactive = var->yres; + vblank_start = var->yres; + vblank_end = vtotal; + vsync_start = vactive + var->lower_margin; + vsync_end = vsync_start + var->vsync_len; + + dspcntr = VML_GFX_ENABLE | VML_GFX_GAMMABYPASS; + clock = PICOS2KHZ(var->pixclock); + + if (subsys->nearest_clock) { + clock = subsys->nearest_clock(subsys, clock); + } else { + clock = vml_nearest_clock(clock); + } + printk(KERN_DEBUG MODULE_NAME + ": Set mode Hfreq : %d kHz, Vfreq : %d Hz.\n", clock / htotal, + ((clock / htotal) * 1000) / vtotal); + + switch (var->bits_per_pixel) { + case 16: + dspcntr |= VML_GFX_ARGB1555; + break; + case 32: + if (var->transp.length == 8) + dspcntr |= VML_GFX_ARGB8888 | VML_GFX_ALPHAMULT; + else + dspcntr |= VML_GFX_RGB0888; + break; + default: + return -EINVAL; + } + + vmlfb_disable_pipe(vinfo); + mb(); + + if (subsys->set_clock) + subsys->set_clock(subsys, clock); + else + return -EINVAL; + + VML_WRITE32(par, VML_HTOTAL_A, ((htotal - 1) << 16) | (hactive - 1)); + VML_WRITE32(par, VML_HBLANK_A, + ((hblank_end - 1) << 16) | (hblank_start - 1)); + VML_WRITE32(par, VML_HSYNC_A, + ((hsync_end - 1) << 16) | (hsync_start - 1)); + VML_WRITE32(par, VML_VTOTAL_A, ((vtotal - 1) << 16) | (vactive - 1)); + VML_WRITE32(par, VML_VBLANK_A, + ((vblank_end - 1) << 16) | (vblank_start - 1)); + VML_WRITE32(par, VML_VSYNC_A, + ((vsync_end - 1) << 16) | (vsync_start - 1)); + VML_WRITE32(par, VML_DSPCSTRIDE, vinfo->stride); + VML_WRITE32(par, VML_DSPCSIZE, + ((var->yres - 1) << 16) | (var->xres - 1)); + VML_WRITE32(par, VML_DSPCPOS, 0x00000000); + VML_WRITE32(par, VML_DSPARB, VML_FIFO_DEFAULT); + VML_WRITE32(par, VML_BCLRPAT_A, 0x00000000); + VML_WRITE32(par, VML_CANVSCLR_A, 0x00000000); + VML_WRITE32(par, VML_PIPEASRC, + ((var->xres - 1) << 16) | (var->yres - 1)); + + wmb(); + VML_WRITE32(par, VML_PIPEACONF, VML_PIPE_ENABLE); + wmb(); + VML_WRITE32(par, VML_DSPCCNTR, dspcntr); + wmb(); + VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start + + var->yoffset * vinfo->stride + + var->xoffset * vinfo->bytes_per_pixel); + + VML_WRITE32(par, VML_RCOMPSTAT, VML_MDVO_PAD_ENABLE); + + while (!(VML_READ32(par, VML_RCOMPSTAT) & + (VML_MDVO_VDC_I_RCOMP | VML_MDVO_PAD_ENABLE))) ; + + vinfo->pipe_disabled = 0; +#ifdef VERMILION_DEBUG + vml_dump_regs(vinfo); +#endif + + return 0; +} + +static int vmlfb_set_par(struct fb_info *info) +{ + struct vml_info *vinfo = container_of(info, struct vml_info, info); + int ret; + + mutex_lock(&vml_mutex); + list_move(&vinfo->head, (subsys) ? &global_has_mode : &global_no_mode); + ret = vmlfb_set_par_locked(vinfo); + + mutex_unlock(&vml_mutex); + return ret; +} + +static int vmlfb_blank_locked(struct vml_info *vinfo) +{ + struct vml_par *par = vinfo->par; + u32 cur = VML_READ32(par, VML_PIPEACONF); + + switch (vinfo->cur_blank_mode) { + case FB_BLANK_UNBLANK: + if (vinfo->pipe_disabled) { + vmlfb_set_par_locked(vinfo); + } + VML_WRITE32(par, VML_PIPEACONF, cur & ~VML_PIPE_FORCE_BORDER); + (void)VML_READ32(par, VML_PIPEACONF); + break; + case FB_BLANK_NORMAL: + if (vinfo->pipe_disabled) { + vmlfb_set_par_locked(vinfo); + } + VML_WRITE32(par, VML_PIPEACONF, cur | VML_PIPE_FORCE_BORDER); + (void)VML_READ32(par, VML_PIPEACONF); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + if (!vinfo->pipe_disabled) { + vmlfb_disable_pipe(vinfo); + } + break; + case FB_BLANK_POWERDOWN: + if (!vinfo->pipe_disabled) { + vmlfb_disable_pipe(vinfo); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vmlfb_blank(int blank_mode, struct fb_info *info) +{ + struct vml_info *vinfo = container_of(info, struct vml_info, info); + int ret; + + mutex_lock(&vml_mutex); + vinfo->cur_blank_mode = blank_mode; + ret = vmlfb_blank_locked(vinfo); + mutex_unlock(&vml_mutex); + return ret; +} + +static int vmlfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct vml_info *vinfo = container_of(info, struct vml_info, info); + struct vml_par *par = vinfo->par; + + mutex_lock(&vml_mutex); + VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start + + var->yoffset * vinfo->stride + + var->xoffset * vinfo->bytes_per_pixel); + (void)VML_READ32(par, VML_DSPCADDR); + mutex_unlock(&vml_mutex); + + return 0; +} + +static int vmlfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + u32 v; + + if (regno >= 16) + return -EINVAL; + + if (info->var.grayscale) { + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + if (info->fix.visual != FB_VISUAL_TRUECOLOR) + return -EINVAL; + + red = VML_TOHW(red, info->var.red.length); + blue = VML_TOHW(blue, info->var.blue.length); + green = VML_TOHW(green, info->var.green.length); + transp = VML_TOHW(transp, info->var.transp.length); + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + switch (info->var.bits_per_pixel) { + case 16: + ((u32 *) info->pseudo_palette)[regno] = v; + break; + case 24: + case 32: + ((u32 *) info->pseudo_palette)[regno] = v; + break; + } + return 0; +} + +static int vmlfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct vml_info *vinfo = container_of(info, struct vml_info, info); + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + int ret; + + ret = vmlfb_vram_offset(vinfo, offset); + if (ret) + return -EINVAL; + + pgprot_val(vma->vm_page_prot) |= _PAGE_PCD; + pgprot_val(vma->vm_page_prot) &= ~_PAGE_PWT; + + return vm_iomap_memory(vma, vinfo->vram_start, + vinfo->vram_contig_size); +} + +static int vmlfb_sync(struct fb_info *info) +{ + return 0; +} + +static int vmlfb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + return -EINVAL; /* just to force soft_cursor() call */ +} + +static struct fb_ops vmlfb_ops = { + .owner = THIS_MODULE, + .fb_open = vmlfb_open, + .fb_release = vmlfb_release, + .fb_check_var = vmlfb_check_var, + .fb_set_par = vmlfb_set_par, + .fb_blank = vmlfb_blank, + .fb_pan_display = vmlfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = vmlfb_cursor, + .fb_sync = vmlfb_sync, + .fb_mmap = vmlfb_mmap, + .fb_setcolreg = vmlfb_setcolreg +}; + +static struct pci_device_id vml_ids[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, VML_DEVICE_VDC)}, + {0} +}; + +static struct pci_driver vmlfb_pci_driver = { + .name = "vmlfb", + .id_table = vml_ids, + .probe = vml_pci_probe, + .remove = vml_pci_remove, +}; + +static void __exit vmlfb_cleanup(void) +{ + pci_unregister_driver(&vmlfb_pci_driver); +} + +static int __init vmlfb_init(void) +{ + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options(MODULE_NAME, &option)) + return -ENODEV; +#endif + + printk(KERN_DEBUG MODULE_NAME ": initializing\n"); + mutex_init(&vml_mutex); + INIT_LIST_HEAD(&global_no_mode); + INIT_LIST_HEAD(&global_has_mode); + + return pci_register_driver(&vmlfb_pci_driver); +} + +int vmlfb_register_subsys(struct vml_sys *sys) +{ + struct vml_info *entry; + struct list_head *list; + u32 save_activate; + + mutex_lock(&vml_mutex); + if (subsys != NULL) { + subsys->restore(subsys); + } + subsys = sys; + subsys->save(subsys); + + /* + * We need to restart list traversal for each item, since we + * release the list mutex in the loop. + */ + + list = global_no_mode.next; + while (list != &global_no_mode) { + list_del_init(list); + entry = list_entry(list, struct vml_info, head); + + /* + * First, try the current mode which might not be + * completely validated with respect to the pixel clock. + */ + + if (!vmlfb_check_var_locked(&entry->info.var, entry)) { + vmlfb_set_par_locked(entry); + list_add_tail(list, &global_has_mode); + } else { + + /* + * Didn't work. Try to find another mode, + * that matches this subsys. + */ + + mutex_unlock(&vml_mutex); + save_activate = entry->info.var.activate; + entry->info.var.bits_per_pixel = 16; + vmlfb_set_pref_pixel_format(&entry->info.var); + if (fb_find_mode(&entry->info.var, + &entry->info, + vml_default_mode, NULL, 0, NULL, 16)) { + entry->info.var.activate |= + FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW; + fb_set_var(&entry->info, &entry->info.var); + } else { + printk(KERN_ERR MODULE_NAME + ": Sorry. no mode found for this subsys.\n"); + } + entry->info.var.activate = save_activate; + mutex_lock(&vml_mutex); + } + vmlfb_blank_locked(entry); + list = global_no_mode.next; + } + mutex_unlock(&vml_mutex); + + printk(KERN_DEBUG MODULE_NAME ": Registered %s subsystem.\n", + subsys->name ? subsys->name : "unknown"); + return 0; +} + +EXPORT_SYMBOL_GPL(vmlfb_register_subsys); + +void vmlfb_unregister_subsys(struct vml_sys *sys) +{ + struct vml_info *entry, *next; + + mutex_lock(&vml_mutex); + if (subsys != sys) { + mutex_unlock(&vml_mutex); + return; + } + subsys->restore(subsys); + subsys = NULL; + list_for_each_entry_safe(entry, next, &global_has_mode, head) { + printk(KERN_DEBUG MODULE_NAME ": subsys disable pipe\n"); + vmlfb_disable_pipe(entry); + list_move_tail(&entry->head, &global_no_mode); + } + mutex_unlock(&vml_mutex); +} + +EXPORT_SYMBOL_GPL(vmlfb_unregister_subsys); + +module_init(vmlfb_init); +module_exit(vmlfb_cleanup); + +MODULE_AUTHOR("Tungsten Graphics"); +MODULE_DESCRIPTION("Initialization of the Vermilion display devices"); +MODULE_VERSION("1.0.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/vermilion/vermilion.h b/drivers/video/fbdev/vermilion/vermilion.h new file mode 100644 index 000000000000..43d11ec197fc --- /dev/null +++ b/drivers/video/fbdev/vermilion/vermilion.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) Intel Corp. 2007. + * All Rights Reserved. + * + * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to + * develop this driver. + * + * This file is part of the Vermilion Range fb driver. + * The Vermilion Range fb driver 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. + * + * The Vermilion Range fb driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Thomas Hellström <thomas-at-tungstengraphics-dot-com> + */ + +#ifndef _VERMILION_H_ +#define _VERMILION_H_ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/atomic.h> +#include <linux/mutex.h> + +#define VML_DEVICE_GPU 0x5002 +#define VML_DEVICE_VDC 0x5009 + +#define VML_VRAM_AREAS 3 +#define VML_MAX_XRES 1024 +#define VML_MAX_YRES 768 +#define VML_MAX_XRES_VIRTUAL 1040 + +/* + * Display controller registers: + */ + +/* Display controller 10-bit color representation */ + +#define VML_R_MASK 0x3FF00000 +#define VML_R_SHIFT 20 +#define VML_G_MASK 0x000FFC00 +#define VML_G_SHIFT 10 +#define VML_B_MASK 0x000003FF +#define VML_B_SHIFT 0 + +/* Graphics plane control */ +#define VML_DSPCCNTR 0x00072180 +#define VML_GFX_ENABLE 0x80000000 +#define VML_GFX_GAMMABYPASS 0x40000000 +#define VML_GFX_ARGB1555 0x0C000000 +#define VML_GFX_RGB0888 0x18000000 +#define VML_GFX_ARGB8888 0x1C000000 +#define VML_GFX_ALPHACONST 0x02000000 +#define VML_GFX_ALPHAMULT 0x01000000 +#define VML_GFX_CONST_ALPHA 0x000000FF + +/* Graphics plane start address. Pixel aligned. */ +#define VML_DSPCADDR 0x00072184 + +/* Graphics plane stride register. */ +#define VML_DSPCSTRIDE 0x00072188 + +/* Graphics plane position register. */ +#define VML_DSPCPOS 0x0007218C +#define VML_POS_YMASK 0x0FFF0000 +#define VML_POS_YSHIFT 16 +#define VML_POS_XMASK 0x00000FFF +#define VML_POS_XSHIFT 0 + +/* Graphics plane height and width */ +#define VML_DSPCSIZE 0x00072190 +#define VML_SIZE_HMASK 0x0FFF0000 +#define VML_SIZE_HSHIFT 16 +#define VML_SISE_WMASK 0x00000FFF +#define VML_SIZE_WSHIFT 0 + +/* Graphics plane gamma correction lookup table registers (129 * 32 bits) */ +#define VML_DSPCGAMLUT 0x00072200 + +/* Pixel video output configuration register */ +#define VML_PVOCONFIG 0x00061140 +#define VML_CONFIG_BASE 0x80000000 +#define VML_CONFIG_PIXEL_SWAP 0x04000000 +#define VML_CONFIG_DE_INV 0x01000000 +#define VML_CONFIG_HREF_INV 0x00400000 +#define VML_CONFIG_VREF_INV 0x00100000 +#define VML_CONFIG_CLK_INV 0x00040000 +#define VML_CONFIG_CLK_DIV2 0x00010000 +#define VML_CONFIG_ESTRB_INV 0x00008000 + +/* Pipe A Horizontal total register */ +#define VML_HTOTAL_A 0x00060000 +#define VML_HTOTAL_MASK 0x1FFF0000 +#define VML_HTOTAL_SHIFT 16 +#define VML_HTOTAL_VAL 8192 +#define VML_HACTIVE_MASK 0x000007FF +#define VML_HACTIVE_SHIFT 0 +#define VML_HACTIVE_VAL 4096 + +/* Pipe A Horizontal Blank register */ +#define VML_HBLANK_A 0x00060004 +#define VML_HBLANK_END_MASK 0x1FFF0000 +#define VML_HBLANK_END_SHIFT 16 +#define VML_HBLANK_END_VAL 8192 +#define VML_HBLANK_START_MASK 0x00001FFF +#define VML_HBLANK_START_SHIFT 0 +#define VML_HBLANK_START_VAL 8192 + +/* Pipe A Horizontal Sync register */ +#define VML_HSYNC_A 0x00060008 +#define VML_HSYNC_END_MASK 0x1FFF0000 +#define VML_HSYNC_END_SHIFT 16 +#define VML_HSYNC_END_VAL 8192 +#define VML_HSYNC_START_MASK 0x00001FFF +#define VML_HSYNC_START_SHIFT 0 +#define VML_HSYNC_START_VAL 8192 + +/* Pipe A Vertical total register */ +#define VML_VTOTAL_A 0x0006000C +#define VML_VTOTAL_MASK 0x1FFF0000 +#define VML_VTOTAL_SHIFT 16 +#define VML_VTOTAL_VAL 8192 +#define VML_VACTIVE_MASK 0x000007FF +#define VML_VACTIVE_SHIFT 0 +#define VML_VACTIVE_VAL 4096 + +/* Pipe A Vertical Blank register */ +#define VML_VBLANK_A 0x00060010 +#define VML_VBLANK_END_MASK 0x1FFF0000 +#define VML_VBLANK_END_SHIFT 16 +#define VML_VBLANK_END_VAL 8192 +#define VML_VBLANK_START_MASK 0x00001FFF +#define VML_VBLANK_START_SHIFT 0 +#define VML_VBLANK_START_VAL 8192 + +/* Pipe A Vertical Sync register */ +#define VML_VSYNC_A 0x00060014 +#define VML_VSYNC_END_MASK 0x1FFF0000 +#define VML_VSYNC_END_SHIFT 16 +#define VML_VSYNC_END_VAL 8192 +#define VML_VSYNC_START_MASK 0x00001FFF +#define VML_VSYNC_START_SHIFT 0 +#define VML_VSYNC_START_VAL 8192 + +/* Pipe A Source Image size (minus one - equal to active size) + * Programmable while pipe is enabled. + */ +#define VML_PIPEASRC 0x0006001C +#define VML_PIPEASRC_HMASK 0x0FFF0000 +#define VML_PIPEASRC_HSHIFT 16 +#define VML_PIPEASRC_VMASK 0x00000FFF +#define VML_PIPEASRC_VSHIFT 0 + +/* Pipe A Border Color Pattern register (10 bit color) */ +#define VML_BCLRPAT_A 0x00060020 + +/* Pipe A Canvas Color register (10 bit color) */ +#define VML_CANVSCLR_A 0x00060024 + +/* Pipe A Configuration register */ +#define VML_PIPEACONF 0x00070008 +#define VML_PIPE_BASE 0x00000000 +#define VML_PIPE_ENABLE 0x80000000 +#define VML_PIPE_FORCE_BORDER 0x02000000 +#define VML_PIPE_PLANES_OFF 0x00080000 +#define VML_PIPE_ARGB_OUTPUT_MODE 0x00040000 + +/* Pipe A FIFO setting */ +#define VML_DSPARB 0x00070030 +#define VML_FIFO_DEFAULT 0x00001D9C + +/* MDVO rcomp status & pads control register */ +#define VML_RCOMPSTAT 0x00070048 +#define VML_MDVO_VDC_I_RCOMP 0x80000000 +#define VML_MDVO_POWERSAVE_OFF 0x00000008 +#define VML_MDVO_PAD_ENABLE 0x00000004 +#define VML_MDVO_PULLDOWN_ENABLE 0x00000001 + +struct vml_par { + struct pci_dev *vdc; + u64 vdc_mem_base; + u64 vdc_mem_size; + char __iomem *vdc_mem; + + struct pci_dev *gpu; + u64 gpu_mem_base; + u64 gpu_mem_size; + char __iomem *gpu_mem; + + atomic_t refcount; +}; + +struct vram_area { + unsigned long logical; + unsigned long phys; + unsigned long size; + unsigned order; +}; + +struct vml_info { + struct fb_info info; + struct vml_par *par; + struct list_head head; + struct vram_area vram[VML_VRAM_AREAS]; + u64 vram_start; + u64 vram_contig_size; + u32 num_areas; + void __iomem *vram_logical; + u32 pseudo_palette[16]; + u32 stride; + u32 bytes_per_pixel; + atomic_t vmas; + int cur_blank_mode; + int pipe_disabled; +}; + +/* + * Subsystem + */ + +struct vml_sys { + char *name; + + /* + * Save / Restore; + */ + + int (*save) (struct vml_sys * sys); + int (*restore) (struct vml_sys * sys); + + /* + * PLL programming; + */ + + int (*set_clock) (struct vml_sys * sys, int clock); + int (*nearest_clock) (const struct vml_sys * sys, int clock); +}; + +extern int vmlfb_register_subsys(struct vml_sys *sys); +extern void vmlfb_unregister_subsys(struct vml_sys *sys); + +#define VML_READ32(_par, _offset) \ + (ioread32((_par)->vdc_mem + (_offset))) +#define VML_WRITE32(_par, _offset, _value) \ + iowrite32(_value, (_par)->vdc_mem + (_offset)) + +#endif diff --git a/drivers/video/fbdev/vesafb.c b/drivers/video/fbdev/vesafb.c new file mode 100644 index 000000000000..6170e7f58640 --- /dev/null +++ b/drivers/video/fbdev/vesafb.c @@ -0,0 +1,522 @@ +/* + * framebuffer driver for VBE 2.0 compliant graphic boards + * + * switching to graphics mode happens at boot time (while + * running in real mode, see arch/i386/boot/video.S). + * + * (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/screen_info.h> + +#include <video/vga.h> +#include <asm/io.h> +#include <asm/mtrr.h> + +#define dac_reg (0x3c8) +#define dac_val (0x3c9) + +/* --------------------------------------------------------------------- */ + +static struct fb_var_screeninfo vesafb_defined = { + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .right_margin = 32, + .upper_margin = 16, + .lower_margin = 4, + .vsync_len = 4, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo vesafb_fix = { + .id = "VESA VGA", + .type = FB_TYPE_PACKED_PIXELS, + .accel = FB_ACCEL_NONE, +}; + +static int inverse __read_mostly; +static int mtrr __read_mostly; /* disable mtrr */ +static int vram_remap; /* Set amount of memory to be used */ +static int vram_total; /* Set total amount of memory */ +static int pmi_setpal __read_mostly = 1; /* pmi for palette changes ??? */ +static int ypan __read_mostly; /* 0..nothing, 1..ypan, 2..ywrap */ +static void (*pmi_start)(void) __read_mostly; +static void (*pmi_pal) (void) __read_mostly; +static int depth __read_mostly; +static int vga_compat __read_mostly; +/* --------------------------------------------------------------------- */ + +static int vesafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ +#ifdef __i386__ + int offset; + + offset = (var->yoffset * info->fix.line_length + var->xoffset) / 4; + + __asm__ __volatile__( + "call *(%%edi)" + : /* no return value */ + : "a" (0x4f07), /* EAX */ + "b" (0), /* EBX */ + "c" (offset), /* ECX */ + "d" (offset >> 16), /* EDX */ + "D" (&pmi_start)); /* EDI */ +#endif + return 0; +} + +static int vesa_setpalette(int regno, unsigned red, unsigned green, + unsigned blue) +{ + int shift = 16 - depth; + int err = -EINVAL; + +/* + * Try VGA registers first... + */ + if (vga_compat) { + outb_p(regno, dac_reg); + outb_p(red >> shift, dac_val); + outb_p(green >> shift, dac_val); + outb_p(blue >> shift, dac_val); + err = 0; + } + +#ifdef __i386__ +/* + * Fallback to the PMI.... + */ + if (err && pmi_setpal) { + struct { u_char blue, green, red, pad; } entry; + + entry.red = red >> shift; + entry.green = green >> shift; + entry.blue = blue >> shift; + entry.pad = 0; + __asm__ __volatile__( + "call *(%%esi)" + : /* no return value */ + : "a" (0x4f09), /* EAX */ + "b" (0), /* EBX */ + "c" (1), /* ECX */ + "d" (regno), /* EDX */ + "D" (&entry), /* EDI */ + "S" (&pmi_pal)); /* ESI */ + err = 0; + } +#endif + + return err; +} + +static int vesafb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + int err = 0; + + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= info->cmap.len) + return 1; + + if (info->var.bits_per_pixel == 8) + err = vesa_setpalette(regno,red,green,blue); + else if (regno < 16) { + switch (info->var.bits_per_pixel) { + case 16: + if (info->var.red.offset == 10) { + /* 1:5:5:5 */ + ((u32*) (info->pseudo_palette))[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + } else { + /* 0:5:6:5 */ + ((u32*) (info->pseudo_palette))[regno] = + ((red & 0xf800) ) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + } + break; + case 24: + case 32: + red >>= 8; + green >>= 8; + blue >>= 8; + ((u32 *)(info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + break; + } + } + + return err; +} + +static void vesafb_destroy(struct fb_info *info) +{ + fb_dealloc_cmap(&info->cmap); + if (info->screen_base) + iounmap(info->screen_base); + release_mem_region(info->apertures->ranges[0].base, info->apertures->ranges[0].size); +} + +static struct fb_ops vesafb_ops = { + .owner = THIS_MODULE, + .fb_destroy = vesafb_destroy, + .fb_setcolreg = vesafb_setcolreg, + .fb_pan_display = vesafb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int vesafb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + + if (! strcmp(this_opt, "inverse")) + inverse=1; + else if (! strcmp(this_opt, "redraw")) + ypan=0; + else if (! strcmp(this_opt, "ypan")) + ypan=1; + else if (! strcmp(this_opt, "ywrap")) + ypan=2; + else if (! strcmp(this_opt, "vgapal")) + pmi_setpal=0; + else if (! strcmp(this_opt, "pmipal")) + pmi_setpal=1; + else if (! strncmp(this_opt, "mtrr:", 5)) + mtrr = simple_strtoul(this_opt+5, NULL, 0); + else if (! strcmp(this_opt, "nomtrr")) + mtrr=0; + else if (! strncmp(this_opt, "vtotal:", 7)) + vram_total = simple_strtoul(this_opt+7, NULL, 0); + else if (! strncmp(this_opt, "vremap:", 7)) + vram_remap = simple_strtoul(this_opt+7, NULL, 0); + } + return 0; +} + +static int vesafb_probe(struct platform_device *dev) +{ + struct fb_info *info; + int i, err; + unsigned int size_vmode; + unsigned int size_remap; + unsigned int size_total; + char *option = NULL; + + /* ignore error return of fb_get_options */ + fb_get_options("vesafb", &option); + vesafb_setup(option); + + if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) + return -ENODEV; + + vga_compat = (screen_info.capabilities & 2) ? 0 : 1; + vesafb_fix.smem_start = screen_info.lfb_base; + vesafb_defined.bits_per_pixel = screen_info.lfb_depth; + if (15 == vesafb_defined.bits_per_pixel) + vesafb_defined.bits_per_pixel = 16; + vesafb_defined.xres = screen_info.lfb_width; + vesafb_defined.yres = screen_info.lfb_height; + vesafb_fix.line_length = screen_info.lfb_linelength; + vesafb_fix.visual = (vesafb_defined.bits_per_pixel == 8) ? + FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + + /* size_vmode -- that is the amount of memory needed for the + * used video mode, i.e. the minimum amount of + * memory we need. */ + size_vmode = vesafb_defined.yres * vesafb_fix.line_length; + + /* size_total -- all video memory we have. Used for mtrr + * entries, resource allocation and bounds + * checking. */ + size_total = screen_info.lfb_size * 65536; + if (vram_total) + size_total = vram_total * 1024 * 1024; + if (size_total < size_vmode) + size_total = size_vmode; + + /* size_remap -- the amount of video memory we are going to + * use for vesafb. With modern cards it is no + * option to simply use size_total as that + * wastes plenty of kernel address space. */ + size_remap = size_vmode * 2; + if (vram_remap) + size_remap = vram_remap * 1024 * 1024; + if (size_remap < size_vmode) + size_remap = size_vmode; + if (size_remap > size_total) + size_remap = size_total; + vesafb_fix.smem_len = size_remap; + +#ifndef __i386__ + screen_info.vesapm_seg = 0; +#endif + + if (!request_mem_region(vesafb_fix.smem_start, size_total, "vesafb")) { + printk(KERN_WARNING + "vesafb: cannot reserve video memory at 0x%lx\n", + vesafb_fix.smem_start); + /* We cannot make this fatal. Sometimes this comes from magic + spaces our resource handlers simply don't know about */ + } + + info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev); + if (!info) { + release_mem_region(vesafb_fix.smem_start, size_total); + return -ENOMEM; + } + platform_set_drvdata(dev, info); + info->pseudo_palette = info->par; + info->par = NULL; + + /* set vesafb aperture size for generic probing */ + info->apertures = alloc_apertures(1); + if (!info->apertures) { + err = -ENOMEM; + goto err; + } + info->apertures->ranges[0].base = screen_info.lfb_base; + info->apertures->ranges[0].size = size_total; + + printk(KERN_INFO "vesafb: mode is %dx%dx%d, linelength=%d, pages=%d\n", + vesafb_defined.xres, vesafb_defined.yres, vesafb_defined.bits_per_pixel, vesafb_fix.line_length, screen_info.pages); + + if (screen_info.vesapm_seg) { + printk(KERN_INFO "vesafb: protected mode interface info at %04x:%04x\n", + screen_info.vesapm_seg,screen_info.vesapm_off); + } + + if (screen_info.vesapm_seg < 0xc000) + ypan = pmi_setpal = 0; /* not available or some DOS TSR ... */ + + if (ypan || pmi_setpal) { + unsigned short *pmi_base; + pmi_base = (unsigned short*)phys_to_virt(((unsigned long)screen_info.vesapm_seg << 4) + screen_info.vesapm_off); + pmi_start = (void*)((char*)pmi_base + pmi_base[1]); + pmi_pal = (void*)((char*)pmi_base + pmi_base[2]); + printk(KERN_INFO "vesafb: pmi: set display start = %p, set palette = %p\n",pmi_start,pmi_pal); + if (pmi_base[3]) { + printk(KERN_INFO "vesafb: pmi: ports = "); + for (i = pmi_base[3]/2; pmi_base[i] != 0xffff; i++) + printk("%x ",pmi_base[i]); + printk("\n"); + if (pmi_base[i] != 0xffff) { + /* + * memory areas not supported (yet?) + * + * Rules are: we have to set up a descriptor for the requested + * memory area and pass it in the ES register to the BIOS function. + */ + printk(KERN_INFO "vesafb: can't handle memory requests, pmi disabled\n"); + ypan = pmi_setpal = 0; + } + } + } + + if (vesafb_defined.bits_per_pixel == 8 && !pmi_setpal && !vga_compat) { + printk(KERN_WARNING "vesafb: hardware palette is unchangeable,\n" + " colors may be incorrect\n"); + vesafb_fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + } + + vesafb_defined.xres_virtual = vesafb_defined.xres; + vesafb_defined.yres_virtual = vesafb_fix.smem_len / vesafb_fix.line_length; + if (ypan && vesafb_defined.yres_virtual > vesafb_defined.yres) { + printk(KERN_INFO "vesafb: scrolling: %s using protected mode interface, yres_virtual=%d\n", + (ypan > 1) ? "ywrap" : "ypan",vesafb_defined.yres_virtual); + } else { + printk(KERN_INFO "vesafb: scrolling: redraw\n"); + vesafb_defined.yres_virtual = vesafb_defined.yres; + ypan = 0; + } + + /* some dummy values for timing to make fbset happy */ + vesafb_defined.pixclock = 10000000 / vesafb_defined.xres * 1000 / vesafb_defined.yres; + vesafb_defined.left_margin = (vesafb_defined.xres / 8) & 0xf8; + vesafb_defined.hsync_len = (vesafb_defined.xres / 8) & 0xf8; + + vesafb_defined.red.offset = screen_info.red_pos; + vesafb_defined.red.length = screen_info.red_size; + vesafb_defined.green.offset = screen_info.green_pos; + vesafb_defined.green.length = screen_info.green_size; + vesafb_defined.blue.offset = screen_info.blue_pos; + vesafb_defined.blue.length = screen_info.blue_size; + vesafb_defined.transp.offset = screen_info.rsvd_pos; + vesafb_defined.transp.length = screen_info.rsvd_size; + + if (vesafb_defined.bits_per_pixel <= 8) { + depth = vesafb_defined.green.length; + vesafb_defined.red.length = + vesafb_defined.green.length = + vesafb_defined.blue.length = + vesafb_defined.bits_per_pixel; + } + + printk(KERN_INFO "vesafb: %s: " + "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n", + (vesafb_defined.bits_per_pixel > 8) ? + "Truecolor" : (vga_compat || pmi_setpal) ? + "Pseudocolor" : "Static Pseudocolor", + screen_info.rsvd_size, + screen_info.red_size, + screen_info.green_size, + screen_info.blue_size, + screen_info.rsvd_pos, + screen_info.red_pos, + screen_info.green_pos, + screen_info.blue_pos); + + vesafb_fix.ypanstep = ypan ? 1 : 0; + vesafb_fix.ywrapstep = (ypan>1) ? 1 : 0; + + /* request failure does not faze us, as vgacon probably has this + * region already (FIXME) */ + request_region(0x3c0, 32, "vesafb"); + +#ifdef CONFIG_MTRR + if (mtrr) { + unsigned int temp_size = size_total; + unsigned int type = 0; + + switch (mtrr) { + case 1: + type = MTRR_TYPE_UNCACHABLE; + break; + case 2: + type = MTRR_TYPE_WRBACK; + break; + case 3: + type = MTRR_TYPE_WRCOMB; + break; + case 4: + type = MTRR_TYPE_WRTHROUGH; + break; + default: + type = 0; + break; + } + + if (type) { + int rc; + + /* Find the largest power-of-two */ + temp_size = roundup_pow_of_two(temp_size); + + /* Try and find a power of two to add */ + do { + rc = mtrr_add(vesafb_fix.smem_start, temp_size, + type, 1); + temp_size >>= 1; + } while (temp_size >= PAGE_SIZE && rc == -EINVAL); + } + } +#endif + + switch (mtrr) { + case 1: /* uncachable */ + info->screen_base = ioremap_nocache(vesafb_fix.smem_start, vesafb_fix.smem_len); + break; + case 2: /* write-back */ + info->screen_base = ioremap_cache(vesafb_fix.smem_start, vesafb_fix.smem_len); + break; + case 3: /* write-combining */ + info->screen_base = ioremap_wc(vesafb_fix.smem_start, vesafb_fix.smem_len); + break; + case 4: /* write-through */ + default: + info->screen_base = ioremap(vesafb_fix.smem_start, vesafb_fix.smem_len); + break; + } + if (!info->screen_base) { + printk(KERN_ERR + "vesafb: abort, cannot ioremap video memory 0x%x @ 0x%lx\n", + vesafb_fix.smem_len, vesafb_fix.smem_start); + err = -EIO; + goto err; + } + + printk(KERN_INFO "vesafb: framebuffer at 0x%lx, mapped to 0x%p, " + "using %dk, total %dk\n", + vesafb_fix.smem_start, info->screen_base, + size_remap/1024, size_total/1024); + + info->fbops = &vesafb_ops; + info->var = vesafb_defined; + info->fix = vesafb_fix; + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE | + (ypan ? FBINFO_HWACCEL_YPAN : 0); + + if (!ypan) + info->fbops->fb_pan_display = NULL; + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + err = -ENOMEM; + goto err; + } + if (register_framebuffer(info)<0) { + err = -EINVAL; + fb_dealloc_cmap(&info->cmap); + goto err; + } + fb_info(info, "%s frame buffer device\n", info->fix.id); + return 0; +err: + if (info->screen_base) + iounmap(info->screen_base); + framebuffer_release(info); + release_mem_region(vesafb_fix.smem_start, size_total); + return err; +} + +static int vesafb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + + unregister_framebuffer(info); + framebuffer_release(info); + + return 0; +} + +static struct platform_driver vesafb_driver = { + .driver = { + .name = "vesa-framebuffer", + .owner = THIS_MODULE, + }, + .probe = vesafb_probe, + .remove = vesafb_remove, +}; + +module_platform_driver(vesafb_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/vfb.c b/drivers/video/fbdev/vfb.c new file mode 100644 index 000000000000..70a897b1e458 --- /dev/null +++ b/drivers/video/fbdev/vfb.c @@ -0,0 +1,610 @@ +/* + * linux/drivers/video/vfb.c -- Virtual frame buffer device + * + * Copyright (C) 2002 James Simmons + * + * Copyright (C) 1997 Geert Uytterhoeven + * + * 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/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <linux/fb.h> +#include <linux/init.h> + + /* + * RAM we reserve for the frame buffer. This defines the maximum screen + * size + * + * The default can be overridden if the driver is compiled as a module + */ + +#define VIDEOMEMSIZE (1*1024*1024) /* 1 MB */ + +static void *videomemory; +static u_long videomemorysize = VIDEOMEMSIZE; +module_param(videomemorysize, ulong, 0); + +/********************************************************************** + * + * Memory management + * + **********************************************************************/ +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + +static struct fb_var_screeninfo vfb_default = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 8, + .red = { 0, 8, 0 }, + .green = { 0, 8, 0 }, + .blue = { 0, 8, 0 }, + .activate = FB_ACTIVATE_TEST, + .height = -1, + .width = -1, + .pixclock = 20000, + .left_margin = 64, + .right_margin = 64, + .upper_margin = 32, + .lower_margin = 32, + .hsync_len = 64, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo vfb_fix = { + .id = "Virtual FB", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 1, + .accel = FB_ACCEL_NONE, +}; + +static bool vfb_enable __initdata = 0; /* disabled by default */ +module_param(vfb_enable, bool, 0); + +static int vfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int vfb_set_par(struct fb_info *info); +static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info); +static int vfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int vfb_mmap(struct fb_info *info, + struct vm_area_struct *vma); + +static struct fb_ops vfb_ops = { + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_check_var = vfb_check_var, + .fb_set_par = vfb_set_par, + .fb_setcolreg = vfb_setcolreg, + .fb_pan_display = vfb_pan_display, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_mmap = vfb_mmap, +}; + + /* + * Internal routines + */ + +static u_long get_line_length(int xres_virtual, int bpp) +{ + u_long length; + + length = xres_virtual * bpp; + length = (length + 31) & ~31; + length >>= 3; + return (length); +} + + /* + * Setting the video mode has been split into two parts. + * First part, xxxfb_check_var, must not write anything + * to hardware, it should only verify and adjust var. + * This means it doesn't alter par but it does use hardware + * data from it to check this var. + */ + +static int vfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + u_long line_length; + + /* + * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal! + * as FB_VMODE_SMOOTH_XPAN is only used internally + */ + + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = info->var.xoffset; + var->yoffset = info->var.yoffset; + } + + /* + * Some very basic checks + */ + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + if (var->bits_per_pixel <= 1) + var->bits_per_pixel = 1; + else 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 <= 24) + var->bits_per_pixel = 24; + else if (var->bits_per_pixel <= 32) + var->bits_per_pixel = 32; + else + return -EINVAL; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + /* + * Memory limit + */ + line_length = + get_line_length(var->xres_virtual, var->bits_per_pixel); + if (line_length * var->yres_virtual > videomemorysize) + return -ENOMEM; + + /* + * Now that we checked it we alter var. The reason being is that the video + * mode passed in might not work but slight changes to it might make it + * work. This way we let the user know what is acceptable. + */ + switch (var->bits_per_pixel) { + case 1: + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGBA 5551 */ + if (var->transp.length) { + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 10; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + } else { /* RGB 565 */ + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 11; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + break; + case 24: /* RGB 888 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: /* RGBA 8888 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + return 0; +} + +/* This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + */ +static int vfb_set_par(struct fb_info *info) +{ + info->fix.line_length = get_line_length(info->var.xres_virtual, + info->var.bits_per_pixel); + return 0; +} + + /* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ + +static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + /* + * Program hardware... do anything you want with transp + */ + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = + (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* Directcolor: + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * {hardwarespecific} contains width of RAMDAC + * cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset) + * RAMDAC[X] is programmed to (red, green, blue) + * + * Pseudocolor: + * var->{color}.offset is 0 unless the palette index takes less than + * bits_per_pixel bits and is stored in the upper + * bits of the pixel value + * var->{color}.length is set so that 1 << length is the number of available + * palette entries + * cmap is not used + * RAMDAC[X] is programmed to (red, green, blue) + * + * Truecolor: + * does not use DAC. Usually 3 are present. + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * cmap is programmed to (red << red.offset) | (green << green.offset) | + * (blue << blue.offset) | (transp << transp.offset) + * RAMDAC does not exist + */ +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + + if (regno >= 16) + return 1; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + switch (info->var.bits_per_pixel) { + case 8: + break; + case 16: + ((u32 *) (info->pseudo_palette))[regno] = v; + break; + case 24: + case 32: + ((u32 *) (info->pseudo_palette))[regno] = v; + break; + } + return 0; + } + return 0; +} + + /* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ + +static int vfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->vmode & FB_VMODE_YWRAP) { + if (var->yoffset >= info->var.yres_virtual || + var->xoffset) + return -EINVAL; + } else { + if (var->xoffset + info->var.xres > info->var.xres_virtual || + var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + } + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + return 0; +} + + /* + * Most drivers don't need their own mmap function + */ + +static int vfb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + if (size > info->fix.smem_len) + return -EINVAL; + if (offset > info->fix.smem_len - size) + return -EINVAL; + + pos = (unsigned long)info->fix.smem_start + offset; + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; + +} + +#ifndef MODULE +/* + * The virtual framebuffer driver is only enabled if explicitly + * requested by passing 'video=vfb:' (or any actual options). + */ +static int __init vfb_setup(char *options) +{ + char *this_opt; + + vfb_enable = 0; + + if (!options) + return 1; + + vfb_enable = 1; + + if (!*options) + return 1; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + /* Test disable for backwards compatibility */ + if (!strcmp(this_opt, "disable")) + vfb_enable = 0; + } + return 1; +} +#endif /* MODULE */ + + /* + * Initialisation + */ + +static int vfb_probe(struct platform_device *dev) +{ + struct fb_info *info; + int retval = -ENOMEM; + + /* + * For real video cards we use ioremap. + */ + if (!(videomemory = rvmalloc(videomemorysize))) + return retval; + + /* + * VFB must clear memory to prevent kernel info + * leakage into userspace + * VGA-based drivers MUST NOT clear memory if + * they want to be able to take over vgacon + */ + memset(videomemory, 0, videomemorysize); + + info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev); + if (!info) + goto err; + + info->screen_base = (char __iomem *)videomemory; + info->fbops = &vfb_ops; + + retval = fb_find_mode(&info->var, info, NULL, + NULL, 0, NULL, 8); + + if (!retval || (retval == 4)) + info->var = vfb_default; + vfb_fix.smem_start = (unsigned long) videomemory; + vfb_fix.smem_len = videomemorysize; + info->fix = vfb_fix; + info->pseudo_palette = info->par; + info->par = NULL; + info->flags = FBINFO_FLAG_DEFAULT; + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) + goto err1; + + retval = register_framebuffer(info); + if (retval < 0) + goto err2; + platform_set_drvdata(dev, info); + + fb_info(info, "Virtual frame buffer device, using %ldK of video memory\n", + videomemorysize >> 10); + return 0; +err2: + fb_dealloc_cmap(&info->cmap); +err1: + framebuffer_release(info); +err: + rvfree(videomemory, videomemorysize); + return retval; +} + +static int vfb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + unregister_framebuffer(info); + rvfree(videomemory, videomemorysize); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + return 0; +} + +static struct platform_driver vfb_driver = { + .probe = vfb_probe, + .remove = vfb_remove, + .driver = { + .name = "vfb", + }, +}; + +static struct platform_device *vfb_device; + +static int __init vfb_init(void) +{ + int ret = 0; + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("vfb", &option)) + return -ENODEV; + vfb_setup(option); +#endif + + if (!vfb_enable) + return -ENXIO; + + ret = platform_driver_register(&vfb_driver); + + if (!ret) { + vfb_device = platform_device_alloc("vfb", 0); + + if (vfb_device) + ret = platform_device_add(vfb_device); + else + ret = -ENOMEM; + + if (ret) { + platform_device_put(vfb_device); + platform_driver_unregister(&vfb_driver); + } + } + + return ret; +} + +module_init(vfb_init); + +#ifdef MODULE +static void __exit vfb_exit(void) +{ + platform_device_unregister(vfb_device); + platform_driver_unregister(&vfb_driver); +} + +module_exit(vfb_exit); + +MODULE_LICENSE("GPL"); +#endif /* MODULE */ diff --git a/drivers/video/fbdev/vga16fb.c b/drivers/video/fbdev/vga16fb.c new file mode 100644 index 000000000000..283d335a759f --- /dev/null +++ b/drivers/video/fbdev/vga16fb.c @@ -0,0 +1,1464 @@ +/* + * linux/drivers/video/vga16.c -- VGA 16-color framebuffer driver + * + * Copyright 1999 Ben Pfaff <pfaffben@debian.org> and Petr Vandrovec <VANDROVE@vc.cvut.cz> + * Based on VGA info at http://www.goodnet.com/~tinara/FreeVGA/home.htm + * Based on VESA framebuffer (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + * 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/delay.h> +#include <linux/fb.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/screen_info.h> + +#include <asm/io.h> +#include <video/vga.h> + +#define VGA_FB_PHYS 0xA0000 +#define VGA_FB_PHYS_LEN 65536 + +#define MODE_SKIP4 1 +#define MODE_8BPP 2 +#define MODE_CFB 4 +#define MODE_TEXT 8 + +/* --------------------------------------------------------------------- */ + +/* + * card parameters + */ + +struct vga16fb_par { + /* structure holding original VGA register settings when the + screen is blanked */ + struct { + unsigned char SeqCtrlIndex; /* Sequencer Index reg. */ + unsigned char CrtCtrlIndex; /* CRT-Contr. Index reg. */ + unsigned char CrtMiscIO; /* Miscellaneous register */ + unsigned char HorizontalTotal; /* CRT-Controller:00h */ + unsigned char HorizDisplayEnd; /* CRT-Controller:01h */ + unsigned char StartHorizRetrace;/* CRT-Controller:04h */ + unsigned char EndHorizRetrace; /* CRT-Controller:05h */ + unsigned char Overflow; /* CRT-Controller:07h */ + unsigned char StartVertRetrace; /* CRT-Controller:10h */ + unsigned char EndVertRetrace; /* CRT-Controller:11h */ + unsigned char ModeControl; /* CRT-Controller:17h */ + unsigned char ClockingMode; /* Seq-Controller:01h */ + } vga_state; + struct vgastate state; + unsigned int ref_count; + int palette_blanked, vesa_blanked, mode, isVGA; + u8 misc, pel_msk, vss, clkdiv; + u8 crtc[VGA_CRT_C]; +}; + +/* --------------------------------------------------------------------- */ + +static struct fb_var_screeninfo vga16fb_defined = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 4, + .activate = FB_ACTIVATE_TEST, + .height = -1, + .width = -1, + .pixclock = 39721, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +/* name should not depend on EGA/VGA */ +static struct fb_fix_screeninfo vga16fb_fix = { + .id = "VGA16 VGA", + .smem_start = VGA_FB_PHYS, + .smem_len = VGA_FB_PHYS_LEN, + .type = FB_TYPE_VGA_PLANES, + .type_aux = FB_AUX_VGA_PLANES_VGA4, + .visual = FB_VISUAL_PSEUDOCOLOR, + .xpanstep = 8, + .ypanstep = 1, + .line_length = 640 / 8, + .accel = FB_ACCEL_NONE +}; + +/* The VGA's weird architecture often requires that we read a byte and + write a byte to the same location. It doesn't matter *what* byte + we write, however. This is because all the action goes on behind + the scenes in the VGA's 32-bit latch register, and reading and writing + video memory just invokes latch behavior. + + To avoid race conditions (is this necessary?), reading and writing + the memory byte should be done with a single instruction. One + suitable instruction is the x86 bitwise OR. The following + read-modify-write routine should optimize to one such bitwise + OR. */ +static inline void rmw(volatile char __iomem *p) +{ + readb(p); + writeb(1, p); +} + +/* Set the Graphics Mode Register, and return its previous value. + Bits 0-1 are write mode, bit 3 is read mode. */ +static inline int setmode(int mode) +{ + int oldmode; + + oldmode = vga_io_rgfx(VGA_GFX_MODE); + vga_io_w(VGA_GFX_D, mode); + return oldmode; +} + +/* Select the Bit Mask Register and return its value. */ +static inline int selectmask(void) +{ + return vga_io_rgfx(VGA_GFX_BIT_MASK); +} + +/* Set the value of the Bit Mask Register. It must already have been + selected with selectmask(). */ +static inline void setmask(int mask) +{ + vga_io_w(VGA_GFX_D, mask); +} + +/* Set the Data Rotate Register and return its old value. + Bits 0-2 are rotate count, bits 3-4 are logical operation + (0=NOP, 1=AND, 2=OR, 3=XOR). */ +static inline int setop(int op) +{ + int oldop; + + oldop = vga_io_rgfx(VGA_GFX_DATA_ROTATE); + vga_io_w(VGA_GFX_D, op); + return oldop; +} + +/* Set the Enable Set/Reset Register and return its old value. + The code here always uses value 0xf for this register. */ +static inline int setsr(int sr) +{ + int oldsr; + + oldsr = vga_io_rgfx(VGA_GFX_SR_ENABLE); + vga_io_w(VGA_GFX_D, sr); + return oldsr; +} + +/* Set the Set/Reset Register and return its old value. */ +static inline int setcolor(int color) +{ + int oldcolor; + + oldcolor = vga_io_rgfx(VGA_GFX_SR_VALUE); + vga_io_w(VGA_GFX_D, color); + return oldcolor; +} + +/* Return the value in the Graphics Address Register. */ +static inline int getindex(void) +{ + return vga_io_r(VGA_GFX_I); +} + +/* Set the value in the Graphics Address Register. */ +static inline void setindex(int index) +{ + vga_io_w(VGA_GFX_I, index); +} + +static void vga16fb_pan_var(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct vga16fb_par *par = info->par; + u32 xoffset, pos; + + xoffset = var->xoffset; + if (info->var.bits_per_pixel == 8) { + pos = (info->var.xres_virtual * var->yoffset + xoffset) >> 2; + } else if (par->mode & MODE_TEXT) { + int fh = 16; // FIXME !!! font height. Fugde for now. + pos = (info->var.xres_virtual * (var->yoffset / fh) + xoffset) >> 3; + } else { + if (info->var.nonstd) + xoffset--; + pos = (info->var.xres_virtual * var->yoffset + xoffset) >> 3; + } + vga_io_wcrt(VGA_CRTC_START_HI, pos >> 8); + vga_io_wcrt(VGA_CRTC_START_LO, pos & 0xFF); + /* if we support CFB4, then we must! support xoffset with pixel + * granularity if someone supports xoffset in bit resolution */ + vga_io_r(VGA_IS1_RC); /* reset flip-flop */ + vga_io_w(VGA_ATT_IW, VGA_ATC_PEL); + if (info->var.bits_per_pixel == 8) + vga_io_w(VGA_ATT_IW, (xoffset & 3) << 1); + else + vga_io_w(VGA_ATT_IW, xoffset & 7); + vga_io_r(VGA_IS1_RC); + vga_io_w(VGA_ATT_IW, 0x20); +} + +static void vga16fb_update_fix(struct fb_info *info) +{ + if (info->var.bits_per_pixel == 4) { + if (info->var.nonstd) { + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.line_length = info->var.xres_virtual / 2; + } else { + info->fix.type = FB_TYPE_VGA_PLANES; + info->fix.type_aux = FB_AUX_VGA_PLANES_VGA4; + info->fix.line_length = info->var.xres_virtual / 8; + } + } else if (info->var.bits_per_pixel == 0) { + info->fix.type = FB_TYPE_TEXT; + info->fix.type_aux = FB_AUX_TEXT_CGA; + info->fix.line_length = info->var.xres_virtual / 4; + } else { /* 8bpp */ + if (info->var.nonstd) { + info->fix.type = FB_TYPE_VGA_PLANES; + info->fix.type_aux = FB_AUX_VGA_PLANES_CFB8; + info->fix.line_length = info->var.xres_virtual / 4; + } else { + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.line_length = info->var.xres_virtual; + } + } +} + +static void vga16fb_clock_chip(struct vga16fb_par *par, + unsigned int pixclock, + const struct fb_info *info, + int mul, int div) +{ + static const struct { + u32 pixclock; + u8 misc; + u8 seq_clock_mode; + } *ptr, *best, vgaclocks[] = { + { 79442 /* 12.587 */, 0x00, 0x08}, + { 70616 /* 14.161 */, 0x04, 0x08}, + { 39721 /* 25.175 */, 0x00, 0x00}, + { 35308 /* 28.322 */, 0x04, 0x00}, + { 0 /* bad */, 0x00, 0x00}}; + int err; + + pixclock = (pixclock * mul) / div; + best = vgaclocks; + err = pixclock - best->pixclock; + if (err < 0) err = -err; + for (ptr = vgaclocks + 1; ptr->pixclock; ptr++) { + int tmp; + + tmp = pixclock - ptr->pixclock; + if (tmp < 0) tmp = -tmp; + if (tmp < err) { + err = tmp; + best = ptr; + } + } + par->misc |= best->misc; + par->clkdiv = best->seq_clock_mode; + pixclock = (best->pixclock * div) / mul; +} + +#define FAIL(X) return -EINVAL + +static int vga16fb_open(struct fb_info *info, int user) +{ + struct vga16fb_par *par = info->par; + + if (!par->ref_count) { + memset(&par->state, 0, sizeof(struct vgastate)); + par->state.flags = VGA_SAVE_FONTS | VGA_SAVE_MODE | + VGA_SAVE_CMAP; + save_vga(&par->state); + } + par->ref_count++; + + return 0; +} + +static int vga16fb_release(struct fb_info *info, int user) +{ + struct vga16fb_par *par = info->par; + + if (!par->ref_count) + return -EINVAL; + + if (par->ref_count == 1) + restore_vga(&par->state); + par->ref_count--; + + return 0; +} + +static int vga16fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct vga16fb_par *par = info->par; + u32 xres, right, hslen, left, xtotal; + u32 yres, lower, vslen, upper, ytotal; + u32 vxres, xoffset, vyres, yoffset; + u32 pos; + u8 r7, rMode; + int shift; + int mode; + u32 maxmem; + + par->pel_msk = 0xFF; + + if (var->bits_per_pixel == 4) { + if (var->nonstd) { + if (!par->isVGA) + return -EINVAL; + shift = 3; + mode = MODE_SKIP4 | MODE_CFB; + maxmem = 16384; + par->pel_msk = 0x0F; + } else { + shift = 3; + mode = 0; + maxmem = 65536; + } + } else if (var->bits_per_pixel == 8) { + if (!par->isVGA) + return -EINVAL; /* no support on EGA */ + shift = 2; + if (var->nonstd) { + mode = MODE_8BPP | MODE_CFB; + maxmem = 65536; + } else { + mode = MODE_SKIP4 | MODE_8BPP | MODE_CFB; + maxmem = 16384; + } + } else + return -EINVAL; + + xres = (var->xres + 7) & ~7; + vxres = (var->xres_virtual + 0xF) & ~0xF; + xoffset = (var->xoffset + 7) & ~7; + left = (var->left_margin + 7) & ~7; + right = (var->right_margin + 7) & ~7; + hslen = (var->hsync_len + 7) & ~7; + + if (vxres < xres) + vxres = xres; + if (xres + xoffset > vxres) + xoffset = vxres - xres; + + var->xres = xres; + var->right_margin = right; + var->hsync_len = hslen; + var->left_margin = left; + var->xres_virtual = vxres; + var->xoffset = xoffset; + + xres >>= shift; + right >>= shift; + hslen >>= shift; + left >>= shift; + vxres >>= shift; + xtotal = xres + right + hslen + left; + if (xtotal >= 256) + FAIL("xtotal too big"); + if (hslen > 32) + FAIL("hslen too big"); + if (right + hslen + left > 64) + FAIL("hblank too big"); + par->crtc[VGA_CRTC_H_TOTAL] = xtotal - 5; + par->crtc[VGA_CRTC_H_BLANK_START] = xres - 1; + par->crtc[VGA_CRTC_H_DISP] = xres - 1; + pos = xres + right; + par->crtc[VGA_CRTC_H_SYNC_START] = pos; + pos += hslen; + par->crtc[VGA_CRTC_H_SYNC_END] = pos & 0x1F; + pos += left - 2; /* blank_end + 2 <= total + 5 */ + par->crtc[VGA_CRTC_H_BLANK_END] = (pos & 0x1F) | 0x80; + if (pos & 0x20) + par->crtc[VGA_CRTC_H_SYNC_END] |= 0x80; + + yres = var->yres; + lower = var->lower_margin; + vslen = var->vsync_len; + upper = var->upper_margin; + vyres = var->yres_virtual; + yoffset = var->yoffset; + + if (yres > vyres) + vyres = yres; + if (vxres * vyres > maxmem) { + vyres = maxmem / vxres; + if (vyres < yres) + return -ENOMEM; + } + if (yoffset + yres > vyres) + yoffset = vyres - yres; + var->yres = yres; + var->lower_margin = lower; + var->vsync_len = vslen; + var->upper_margin = upper; + var->yres_virtual = vyres; + var->yoffset = yoffset; + + if (var->vmode & FB_VMODE_DOUBLE) { + yres <<= 1; + lower <<= 1; + vslen <<= 1; + upper <<= 1; + } + ytotal = yres + lower + vslen + upper; + if (ytotal > 1024) { + ytotal >>= 1; + yres >>= 1; + lower >>= 1; + vslen >>= 1; + upper >>= 1; + rMode = 0x04; + } else + rMode = 0x00; + if (ytotal > 1024) + FAIL("ytotal too big"); + if (vslen > 16) + FAIL("vslen too big"); + par->crtc[VGA_CRTC_V_TOTAL] = ytotal - 2; + r7 = 0x10; /* disable linecompare */ + if (ytotal & 0x100) r7 |= 0x01; + if (ytotal & 0x200) r7 |= 0x20; + par->crtc[VGA_CRTC_PRESET_ROW] = 0; + par->crtc[VGA_CRTC_MAX_SCAN] = 0x40; /* 1 scanline, no linecmp */ + if (var->vmode & FB_VMODE_DOUBLE) + par->crtc[VGA_CRTC_MAX_SCAN] |= 0x80; + par->crtc[VGA_CRTC_CURSOR_START] = 0x20; + par->crtc[VGA_CRTC_CURSOR_END] = 0x00; + if ((mode & (MODE_CFB | MODE_8BPP)) == MODE_CFB) + xoffset--; + pos = yoffset * vxres + (xoffset >> shift); + par->crtc[VGA_CRTC_START_HI] = pos >> 8; + par->crtc[VGA_CRTC_START_LO] = pos & 0xFF; + par->crtc[VGA_CRTC_CURSOR_HI] = 0x00; + par->crtc[VGA_CRTC_CURSOR_LO] = 0x00; + pos = yres - 1; + par->crtc[VGA_CRTC_V_DISP_END] = pos & 0xFF; + par->crtc[VGA_CRTC_V_BLANK_START] = pos & 0xFF; + if (pos & 0x100) + r7 |= 0x0A; /* 0x02 -> DISP_END, 0x08 -> BLANK_START */ + if (pos & 0x200) { + r7 |= 0x40; /* 0x40 -> DISP_END */ + par->crtc[VGA_CRTC_MAX_SCAN] |= 0x20; /* BLANK_START */ + } + pos += lower; + par->crtc[VGA_CRTC_V_SYNC_START] = pos & 0xFF; + if (pos & 0x100) + r7 |= 0x04; + if (pos & 0x200) + r7 |= 0x80; + pos += vslen; + par->crtc[VGA_CRTC_V_SYNC_END] = (pos & 0x0F) & ~0x10; /* disabled IRQ */ + pos += upper - 1; /* blank_end + 1 <= ytotal + 2 */ + par->crtc[VGA_CRTC_V_BLANK_END] = pos & 0xFF; /* 0x7F for original VGA, + but some SVGA chips requires all 8 bits to set */ + if (vxres >= 512) + FAIL("vxres too long"); + par->crtc[VGA_CRTC_OFFSET] = vxres >> 1; + if (mode & MODE_SKIP4) + par->crtc[VGA_CRTC_UNDERLINE] = 0x5F; /* 256, cfb8 */ + else + par->crtc[VGA_CRTC_UNDERLINE] = 0x1F; /* 16, vgap */ + par->crtc[VGA_CRTC_MODE] = rMode | ((mode & MODE_TEXT) ? 0xA3 : 0xE3); + par->crtc[VGA_CRTC_LINE_COMPARE] = 0xFF; + par->crtc[VGA_CRTC_OVERFLOW] = r7; + + par->vss = 0x00; /* 3DA */ + + par->misc = 0xE3; /* enable CPU, ports 0x3Dx, positive sync */ + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + par->misc &= ~0x40; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + par->misc &= ~0x80; + + par->mode = mode; + + if (mode & MODE_8BPP) + /* pixel clock == vga clock / 2 */ + vga16fb_clock_chip(par, var->pixclock, info, 1, 2); + else + /* pixel clock == vga clock */ + vga16fb_clock_chip(par, var->pixclock, info, 1, 1); + + var->red.offset = var->green.offset = var->blue.offset = + var->transp.offset = 0; + var->red.length = var->green.length = var->blue.length = + (par->isVGA) ? 6 : 2; + var->transp.length = 0; + var->activate = FB_ACTIVATE_NOW; + var->height = -1; + var->width = -1; + var->accel_flags = 0; + return 0; +} +#undef FAIL + +static int vga16fb_set_par(struct fb_info *info) +{ + struct vga16fb_par *par = info->par; + u8 gdc[VGA_GFX_C]; + u8 seq[VGA_SEQ_C]; + u8 atc[VGA_ATT_C]; + int fh, i; + + seq[VGA_SEQ_CLOCK_MODE] = 0x01 | par->clkdiv; + if (par->mode & MODE_TEXT) + seq[VGA_SEQ_PLANE_WRITE] = 0x03; + else + seq[VGA_SEQ_PLANE_WRITE] = 0x0F; + seq[VGA_SEQ_CHARACTER_MAP] = 0x00; + if (par->mode & MODE_TEXT) + seq[VGA_SEQ_MEMORY_MODE] = 0x03; + else if (par->mode & MODE_SKIP4) + seq[VGA_SEQ_MEMORY_MODE] = 0x0E; + else + seq[VGA_SEQ_MEMORY_MODE] = 0x06; + + gdc[VGA_GFX_SR_VALUE] = 0x00; + gdc[VGA_GFX_SR_ENABLE] = 0x00; + gdc[VGA_GFX_COMPARE_VALUE] = 0x00; + gdc[VGA_GFX_DATA_ROTATE] = 0x00; + gdc[VGA_GFX_PLANE_READ] = 0; + if (par->mode & MODE_TEXT) { + gdc[VGA_GFX_MODE] = 0x10; + gdc[VGA_GFX_MISC] = 0x06; + } else { + if (par->mode & MODE_CFB) + gdc[VGA_GFX_MODE] = 0x40; + else + gdc[VGA_GFX_MODE] = 0x00; + gdc[VGA_GFX_MISC] = 0x05; + } + gdc[VGA_GFX_COMPARE_MASK] = 0x0F; + gdc[VGA_GFX_BIT_MASK] = 0xFF; + + for (i = 0x00; i < 0x10; i++) + atc[i] = i; + if (par->mode & MODE_TEXT) + atc[VGA_ATC_MODE] = 0x04; + else if (par->mode & MODE_8BPP) + atc[VGA_ATC_MODE] = 0x41; + else + atc[VGA_ATC_MODE] = 0x81; + atc[VGA_ATC_OVERSCAN] = 0x00; /* 0 for EGA, 0xFF for VGA */ + atc[VGA_ATC_PLANE_ENABLE] = 0x0F; + if (par->mode & MODE_8BPP) + atc[VGA_ATC_PEL] = (info->var.xoffset & 3) << 1; + else + atc[VGA_ATC_PEL] = info->var.xoffset & 7; + atc[VGA_ATC_COLOR_PAGE] = 0x00; + + if (par->mode & MODE_TEXT) { + fh = 16; // FIXME !!! Fudge font height. + par->crtc[VGA_CRTC_MAX_SCAN] = (par->crtc[VGA_CRTC_MAX_SCAN] + & ~0x1F) | (fh - 1); + } + + vga_io_w(VGA_MIS_W, vga_io_r(VGA_MIS_R) | 0x01); + + /* Enable graphics register modification */ + if (!par->isVGA) { + vga_io_w(EGA_GFX_E0, 0x00); + vga_io_w(EGA_GFX_E1, 0x01); + } + + /* update misc output register */ + vga_io_w(VGA_MIS_W, par->misc); + + /* synchronous reset on */ + vga_io_wseq(0x00, 0x01); + + if (par->isVGA) + vga_io_w(VGA_PEL_MSK, par->pel_msk); + + /* write sequencer registers */ + vga_io_wseq(VGA_SEQ_CLOCK_MODE, seq[VGA_SEQ_CLOCK_MODE] | 0x20); + for (i = 2; i < VGA_SEQ_C; i++) { + vga_io_wseq(i, seq[i]); + } + + /* synchronous reset off */ + vga_io_wseq(0x00, 0x03); + + /* deprotect CRT registers 0-7 */ + vga_io_wcrt(VGA_CRTC_V_SYNC_END, par->crtc[VGA_CRTC_V_SYNC_END]); + + /* write CRT registers */ + for (i = 0; i < VGA_CRTC_REGS; i++) { + vga_io_wcrt(i, par->crtc[i]); + } + + /* write graphics controller registers */ + for (i = 0; i < VGA_GFX_C; i++) { + vga_io_wgfx(i, gdc[i]); + } + + /* write attribute controller registers */ + for (i = 0; i < VGA_ATT_C; i++) { + vga_io_r(VGA_IS1_RC); /* reset flip-flop */ + vga_io_wattr(i, atc[i]); + } + + /* Wait for screen to stabilize. */ + mdelay(50); + + vga_io_wseq(VGA_SEQ_CLOCK_MODE, seq[VGA_SEQ_CLOCK_MODE]); + + vga_io_r(VGA_IS1_RC); + vga_io_w(VGA_ATT_IW, 0x20); + + vga16fb_update_fix(info); + return 0; +} + +static void ega16_setpalette(int regno, unsigned red, unsigned green, unsigned blue) +{ + static const unsigned char map[] = { 000, 001, 010, 011 }; + int val; + + if (regno >= 16) + return; + val = map[red>>14] | ((map[green>>14]) << 1) | ((map[blue>>14]) << 2); + vga_io_r(VGA_IS1_RC); /* ! 0x3BA */ + vga_io_wattr(regno, val); + vga_io_r(VGA_IS1_RC); /* some clones need it */ + vga_io_w(VGA_ATT_IW, 0x20); /* unblank screen */ +} + +static void vga16_setpalette(int regno, unsigned red, unsigned green, unsigned blue) +{ + outb(regno, VGA_PEL_IW); + outb(red >> 10, VGA_PEL_D); + outb(green >> 10, VGA_PEL_D); + outb(blue >> 10, VGA_PEL_D); +} + +static int vga16fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + struct vga16fb_par *par = info->par; + int gray; + + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= 256) + return 1; + + gray = info->var.grayscale; + + if (gray) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + if (par->isVGA) + vga16_setpalette(regno,red,green,blue); + else + ega16_setpalette(regno,red,green,blue); + return 0; +} + +static int vga16fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + vga16fb_pan_var(info, var); + return 0; +} + +/* The following VESA blanking code is taken from vgacon.c. The VGA + blanking code was originally by Huang shi chao, and modified by + Christoph Rimek (chrimek@toppoint.de) and todd j. derr + (tjd@barefoot.org) for Linux. */ + +static void vga_vesa_blank(struct vga16fb_par *par, int mode) +{ + unsigned char SeqCtrlIndex = vga_io_r(VGA_SEQ_I); + unsigned char CrtCtrlIndex = vga_io_r(VGA_CRT_IC); + + /* save original values of VGA controller registers */ + if(!par->vesa_blanked) { + par->vga_state.CrtMiscIO = vga_io_r(VGA_MIS_R); + //sti(); + + par->vga_state.HorizontalTotal = vga_io_rcrt(0x00); /* HorizontalTotal */ + par->vga_state.HorizDisplayEnd = vga_io_rcrt(0x01); /* HorizDisplayEnd */ + par->vga_state.StartHorizRetrace = vga_io_rcrt(0x04); /* StartHorizRetrace */ + par->vga_state.EndHorizRetrace = vga_io_rcrt(0x05); /* EndHorizRetrace */ + par->vga_state.Overflow = vga_io_rcrt(0x07); /* Overflow */ + par->vga_state.StartVertRetrace = vga_io_rcrt(0x10); /* StartVertRetrace */ + par->vga_state.EndVertRetrace = vga_io_rcrt(0x11); /* EndVertRetrace */ + par->vga_state.ModeControl = vga_io_rcrt(0x17); /* ModeControl */ + par->vga_state.ClockingMode = vga_io_rseq(0x01); /* ClockingMode */ + } + + /* assure that video is enabled */ + /* "0x20" is VIDEO_ENABLE_bit in register 01 of sequencer */ + vga_io_wseq(0x01, par->vga_state.ClockingMode | 0x20); + + /* test for vertical retrace in process.... */ + if ((par->vga_state.CrtMiscIO & 0x80) == 0x80) + vga_io_w(VGA_MIS_W, par->vga_state.CrtMiscIO & 0xef); + + /* + * Set <End of vertical retrace> to minimum (0) and + * <Start of vertical Retrace> to maximum (incl. overflow) + * Result: turn off vertical sync (VSync) pulse. + */ + if (mode & FB_BLANK_VSYNC_SUSPEND) { + vga_io_wcrt(VGA_CRTC_V_SYNC_START, 0xff); + vga_io_wcrt(VGA_CRTC_V_SYNC_END, 0x40); + /* bits 9,10 of vert. retrace */ + vga_io_wcrt(VGA_CRTC_OVERFLOW, par->vga_state.Overflow | 0x84); + } + + if (mode & FB_BLANK_HSYNC_SUSPEND) { + /* + * Set <End of horizontal retrace> to minimum (0) and + * <Start of horizontal Retrace> to maximum + * Result: turn off horizontal sync (HSync) pulse. + */ + vga_io_wcrt(VGA_CRTC_H_SYNC_START, 0xff); + vga_io_wcrt(VGA_CRTC_H_SYNC_END, 0x00); + } + + /* restore both index registers */ + outb_p(SeqCtrlIndex, VGA_SEQ_I); + outb_p(CrtCtrlIndex, VGA_CRT_IC); +} + +static void vga_vesa_unblank(struct vga16fb_par *par) +{ + unsigned char SeqCtrlIndex = vga_io_r(VGA_SEQ_I); + unsigned char CrtCtrlIndex = vga_io_r(VGA_CRT_IC); + + /* restore original values of VGA controller registers */ + vga_io_w(VGA_MIS_W, par->vga_state.CrtMiscIO); + + /* HorizontalTotal */ + vga_io_wcrt(0x00, par->vga_state.HorizontalTotal); + /* HorizDisplayEnd */ + vga_io_wcrt(0x01, par->vga_state.HorizDisplayEnd); + /* StartHorizRetrace */ + vga_io_wcrt(0x04, par->vga_state.StartHorizRetrace); + /* EndHorizRetrace */ + vga_io_wcrt(0x05, par->vga_state.EndHorizRetrace); + /* Overflow */ + vga_io_wcrt(0x07, par->vga_state.Overflow); + /* StartVertRetrace */ + vga_io_wcrt(0x10, par->vga_state.StartVertRetrace); + /* EndVertRetrace */ + vga_io_wcrt(0x11, par->vga_state.EndVertRetrace); + /* ModeControl */ + vga_io_wcrt(0x17, par->vga_state.ModeControl); + /* ClockingMode */ + vga_io_wseq(0x01, par->vga_state.ClockingMode); + + /* restore index/control registers */ + vga_io_w(VGA_SEQ_I, SeqCtrlIndex); + vga_io_w(VGA_CRT_IC, CrtCtrlIndex); +} + +static void vga_pal_blank(void) +{ + int i; + + for (i=0; i<16; i++) { + outb_p(i, VGA_PEL_IW); + outb_p(0, VGA_PEL_D); + outb_p(0, VGA_PEL_D); + outb_p(0, VGA_PEL_D); + } +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ +static int vga16fb_blank(int blank, struct fb_info *info) +{ + struct vga16fb_par *par = info->par; + + switch (blank) { + case FB_BLANK_UNBLANK: /* Unblank */ + if (par->vesa_blanked) { + vga_vesa_unblank(par); + par->vesa_blanked = 0; + } + if (par->palette_blanked) { + par->palette_blanked = 0; + } + break; + case FB_BLANK_NORMAL: /* blank */ + vga_pal_blank(); + par->palette_blanked = 1; + break; + default: /* VESA blanking */ + vga_vesa_blank(par, blank); + par->vesa_blanked = 1; + break; + } + return 0; +} + +static void vga_8planes_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + u32 dx = rect->dx, width = rect->width; + char oldindex = getindex(); + char oldmode = setmode(0x40); + char oldmask = selectmask(); + int line_ofs, height; + char oldop, oldsr; + char __iomem *where; + + dx /= 4; + where = info->screen_base + dx + rect->dy * info->fix.line_length; + + if (rect->rop == ROP_COPY) { + oldop = setop(0); + oldsr = setsr(0); + + width /= 4; + line_ofs = info->fix.line_length - width; + setmask(0xff); + + height = rect->height; + + while (height--) { + int x; + + /* we can do memset... */ + for (x = width; x > 0; --x) { + writeb(rect->color, where); + where++; + } + where += line_ofs; + } + } else { + char oldcolor = setcolor(0xf); + int y; + + oldop = setop(0x18); + oldsr = setsr(0xf); + setmask(0x0F); + for (y = 0; y < rect->height; y++) { + rmw(where); + rmw(where+1); + where += info->fix.line_length; + } + setcolor(oldcolor); + } + setmask(oldmask); + setsr(oldsr); + setop(oldop); + setmode(oldmode); + setindex(oldindex); +} + +static void vga16fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + int x, x2, y2, vxres, vyres, width, height, line_ofs; + char __iomem *dst; + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if (!rect->width || !rect->height || rect->dx > vxres || rect->dy > vyres) + return; + + /* We could use hardware clipping but on many cards you get around + * hardware clipping by writing to framebuffer directly. */ + + x2 = rect->dx + rect->width; + y2 = rect->dy + rect->height; + x2 = x2 < vxres ? x2 : vxres; + y2 = y2 < vyres ? y2 : vyres; + width = x2 - rect->dx; + + switch (info->fix.type) { + case FB_TYPE_VGA_PLANES: + if (info->fix.type_aux == FB_AUX_VGA_PLANES_VGA4) { + + height = y2 - rect->dy; + width = rect->width/8; + + line_ofs = info->fix.line_length - width; + dst = info->screen_base + (rect->dx/8) + rect->dy * info->fix.line_length; + + switch (rect->rop) { + case ROP_COPY: + setmode(0); + setop(0); + setsr(0xf); + setcolor(rect->color); + selectmask(); + + setmask(0xff); + + while (height--) { + for (x = 0; x < width; x++) { + writeb(0, dst); + dst++; + } + dst += line_ofs; + } + break; + case ROP_XOR: + setmode(0); + setop(0x18); + setsr(0xf); + setcolor(0xf); + selectmask(); + + setmask(0xff); + while (height--) { + for (x = 0; x < width; x++) { + rmw(dst); + dst++; + } + dst += line_ofs; + } + break; + } + } else + vga_8planes_fillrect(info, rect); + break; + case FB_TYPE_PACKED_PIXELS: + default: + cfb_fillrect(info, rect); + break; + } +} + +static void vga_8planes_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + char oldindex = getindex(); + char oldmode = setmode(0x41); + char oldop = setop(0); + char oldsr = setsr(0xf); + int height, line_ofs, x; + u32 sx, dx, width; + char __iomem *dest; + char __iomem *src; + + height = area->height; + + sx = area->sx / 4; + dx = area->dx / 4; + width = area->width / 4; + + if (area->dy < area->sy || (area->dy == area->sy && dx < sx)) { + line_ofs = info->fix.line_length - width; + dest = info->screen_base + dx + area->dy * info->fix.line_length; + src = info->screen_base + sx + area->sy * info->fix.line_length; + while (height--) { + for (x = 0; x < width; x++) { + readb(src); + writeb(0, dest); + src++; + dest++; + } + src += line_ofs; + dest += line_ofs; + } + } else { + line_ofs = info->fix.line_length - width; + dest = info->screen_base + dx + width + + (area->dy + height - 1) * info->fix.line_length; + src = info->screen_base + sx + width + + (area->sy + height - 1) * info->fix.line_length; + while (height--) { + for (x = 0; x < width; x++) { + --src; + --dest; + readb(src); + writeb(0, dest); + } + src -= line_ofs; + dest -= line_ofs; + } + } + + setsr(oldsr); + setop(oldop); + setmode(oldmode); + setindex(oldindex); +} + +static void vga16fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy; + int x, x2, y2, old_dx, old_dy, vxres, vyres; + int height, width, line_ofs; + char __iomem *dst = NULL; + char __iomem *src = NULL; + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if (area->dx > vxres || area->sx > vxres || area->dy > vyres || + area->sy > vyres) + return; + + /* clip the destination */ + old_dx = area->dx; + old_dy = area->dy; + + /* + * We could use hardware clipping but on many cards you get around + * hardware clipping by writing to framebuffer directly. + */ + x2 = area->dx + area->width; + y2 = area->dy + area->height; + dx = area->dx > 0 ? area->dx : 0; + dy = area->dy > 0 ? area->dy : 0; + x2 = x2 < vxres ? x2 : vxres; + y2 = y2 < vyres ? y2 : vyres; + width = x2 - dx; + height = y2 - dy; + + if (sx + dx < old_dx || sy + dy < old_dy) + return; + + /* update sx1,sy1 */ + sx += (dx - old_dx); + sy += (dy - old_dy); + + /* the source must be completely inside the virtual screen */ + if (sx + width > vxres || sy + height > vyres) + return; + + switch (info->fix.type) { + case FB_TYPE_VGA_PLANES: + if (info->fix.type_aux == FB_AUX_VGA_PLANES_VGA4) { + width = width/8; + height = height; + line_ofs = info->fix.line_length - width; + + setmode(1); + setop(0); + setsr(0xf); + + if (dy < sy || (dy == sy && dx < sx)) { + dst = info->screen_base + (dx/8) + dy * info->fix.line_length; + src = info->screen_base + (sx/8) + sy * info->fix.line_length; + while (height--) { + for (x = 0; x < width; x++) { + readb(src); + writeb(0, dst); + dst++; + src++; + } + src += line_ofs; + dst += line_ofs; + } + } else { + dst = info->screen_base + (dx/8) + width + + (dy + height - 1) * info->fix.line_length; + src = info->screen_base + (sx/8) + width + + (sy + height - 1) * info->fix.line_length; + while (height--) { + for (x = 0; x < width; x++) { + dst--; + src--; + readb(src); + writeb(0, dst); + } + src -= line_ofs; + dst -= line_ofs; + } + } + } else + vga_8planes_copyarea(info, area); + break; + case FB_TYPE_PACKED_PIXELS: + default: + cfb_copyarea(info, area); + break; + } +} + +#define TRANS_MASK_LOW {0x0,0x8,0x4,0xC,0x2,0xA,0x6,0xE,0x1,0x9,0x5,0xD,0x3,0xB,0x7,0xF} +#define TRANS_MASK_HIGH {0x000, 0x800, 0x400, 0xC00, 0x200, 0xA00, 0x600, 0xE00, \ + 0x100, 0x900, 0x500, 0xD00, 0x300, 0xB00, 0x700, 0xF00} + +#if defined(__LITTLE_ENDIAN) +static const u16 transl_l[] = TRANS_MASK_LOW; +static const u16 transl_h[] = TRANS_MASK_HIGH; +#elif defined(__BIG_ENDIAN) +static const u16 transl_l[] = TRANS_MASK_HIGH; +static const u16 transl_h[] = TRANS_MASK_LOW; +#else +#error "Only __BIG_ENDIAN and __LITTLE_ENDIAN are supported in vga-planes" +#endif + +static void vga_8planes_imageblit(struct fb_info *info, const struct fb_image *image) +{ + char oldindex = getindex(); + char oldmode = setmode(0x40); + char oldop = setop(0); + char oldsr = setsr(0); + char oldmask = selectmask(); + const char *cdat = image->data; + u32 dx = image->dx; + char __iomem *where; + int y; + + dx /= 4; + where = info->screen_base + dx + image->dy * info->fix.line_length; + + setmask(0xff); + writeb(image->bg_color, where); + readb(where); + selectmask(); + setmask(image->fg_color ^ image->bg_color); + setmode(0x42); + setop(0x18); + for (y = 0; y < image->height; y++, where += info->fix.line_length) + writew(transl_h[cdat[y]&0xF] | transl_l[cdat[y] >> 4], where); + setmask(oldmask); + setsr(oldsr); + setop(oldop); + setmode(oldmode); + setindex(oldindex); +} + +static void vga_imageblit_expand(struct fb_info *info, const struct fb_image *image) +{ + char __iomem *where = info->screen_base + (image->dx/8) + + image->dy * info->fix.line_length; + struct vga16fb_par *par = info->par; + char *cdat = (char *) image->data; + char __iomem *dst; + int x, y; + + switch (info->fix.type) { + case FB_TYPE_VGA_PLANES: + if (info->fix.type_aux == FB_AUX_VGA_PLANES_VGA4) { + if (par->isVGA) { + setmode(2); + setop(0); + setsr(0xf); + setcolor(image->fg_color); + selectmask(); + + setmask(0xff); + writeb(image->bg_color, where); + rmb(); + readb(where); /* fill latches */ + setmode(3); + wmb(); + for (y = 0; y < image->height; y++) { + dst = where; + for (x = image->width/8; x--;) + writeb(*cdat++, dst++); + where += info->fix.line_length; + } + wmb(); + } else { + setmode(0); + setop(0); + setsr(0xf); + setcolor(image->bg_color); + selectmask(); + + setmask(0xff); + for (y = 0; y < image->height; y++) { + dst = where; + for (x=image->width/8; x--;){ + rmw(dst); + setcolor(image->fg_color); + selectmask(); + if (*cdat) { + setmask(*cdat++); + rmw(dst++); + } + } + where += info->fix.line_length; + } + } + } else + vga_8planes_imageblit(info, image); + break; + case FB_TYPE_PACKED_PIXELS: + default: + cfb_imageblit(info, image); + break; + } +} + +static void vga_imageblit_color(struct fb_info *info, const struct fb_image *image) +{ + /* + * Draw logo + */ + struct vga16fb_par *par = info->par; + char __iomem *where = + info->screen_base + image->dy * info->fix.line_length + + image->dx/8; + const char *cdat = image->data; + char __iomem *dst; + int x, y; + + switch (info->fix.type) { + case FB_TYPE_VGA_PLANES: + if (info->fix.type_aux == FB_AUX_VGA_PLANES_VGA4 && + par->isVGA) { + setsr(0xf); + setop(0); + setmode(0); + + for (y = 0; y < image->height; y++) { + for (x = 0; x < image->width; x++) { + dst = where + x/8; + + setcolor(*cdat); + selectmask(); + setmask(1 << (7 - (x % 8))); + fb_readb(dst); + fb_writeb(0, dst); + + cdat++; + } + where += info->fix.line_length; + } + } + break; + case FB_TYPE_PACKED_PIXELS: + cfb_imageblit(info, image); + break; + default: + break; + } +} + +static void vga16fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + if (image->depth == 1) + vga_imageblit_expand(info, image); + else + vga_imageblit_color(info, image); +} + +static void vga16fb_destroy(struct fb_info *info) +{ + iounmap(info->screen_base); + fb_dealloc_cmap(&info->cmap); + /* XXX unshare VGA regions */ + framebuffer_release(info); +} + +static struct fb_ops vga16fb_ops = { + .owner = THIS_MODULE, + .fb_open = vga16fb_open, + .fb_release = vga16fb_release, + .fb_destroy = vga16fb_destroy, + .fb_check_var = vga16fb_check_var, + .fb_set_par = vga16fb_set_par, + .fb_setcolreg = vga16fb_setcolreg, + .fb_pan_display = vga16fb_pan_display, + .fb_blank = vga16fb_blank, + .fb_fillrect = vga16fb_fillrect, + .fb_copyarea = vga16fb_copyarea, + .fb_imageblit = vga16fb_imageblit, +}; + +#ifndef MODULE +static int __init vga16fb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + } + return 0; +} +#endif + +static int vga16fb_probe(struct platform_device *dev) +{ + struct fb_info *info; + struct vga16fb_par *par; + int i; + int ret = 0; + + printk(KERN_DEBUG "vga16fb: initializing\n"); + info = framebuffer_alloc(sizeof(struct vga16fb_par), &dev->dev); + + if (!info) { + ret = -ENOMEM; + goto err_fb_alloc; + } + info->apertures = alloc_apertures(1); + if (!info->apertures) { + ret = -ENOMEM; + goto err_ioremap; + } + + /* XXX share VGA_FB_PHYS and I/O region with vgacon and others */ + info->screen_base = (void __iomem *)VGA_MAP_MEM(VGA_FB_PHYS, 0); + + if (!info->screen_base) { + printk(KERN_ERR "vga16fb: unable to map device\n"); + ret = -ENOMEM; + goto err_ioremap; + } + + printk(KERN_INFO "vga16fb: mapped to 0x%p\n", info->screen_base); + par = info->par; + + par->isVGA = screen_info.orig_video_isVGA; + par->palette_blanked = 0; + par->vesa_blanked = 0; + + i = par->isVGA? 6 : 2; + + vga16fb_defined.red.length = i; + vga16fb_defined.green.length = i; + vga16fb_defined.blue.length = i; + + /* name should not depend on EGA/VGA */ + info->fbops = &vga16fb_ops; + info->var = vga16fb_defined; + info->fix = vga16fb_fix; + /* supports rectangles with widths of multiples of 8 */ + info->pixmap.blit_x = 1 << 7 | 1 << 15 | 1 << 23 | 1 << 31; + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE | + FBINFO_HWACCEL_YPAN; + + i = (info->var.bits_per_pixel == 8) ? 256 : 16; + ret = fb_alloc_cmap(&info->cmap, i, 0); + if (ret) { + printk(KERN_ERR "vga16fb: unable to allocate colormap\n"); + ret = -ENOMEM; + goto err_alloc_cmap; + } + + if (vga16fb_check_var(&info->var, info)) { + printk(KERN_ERR "vga16fb: unable to validate variable\n"); + ret = -EINVAL; + goto err_check_var; + } + + vga16fb_update_fix(info); + + info->apertures->ranges[0].base = VGA_FB_PHYS; + info->apertures->ranges[0].size = VGA_FB_PHYS_LEN; + + if (register_framebuffer(info) < 0) { + printk(KERN_ERR "vga16fb: unable to register framebuffer\n"); + ret = -EINVAL; + goto err_check_var; + } + + fb_info(info, "%s frame buffer device\n", info->fix.id); + platform_set_drvdata(dev, info); + + return 0; + + err_check_var: + fb_dealloc_cmap(&info->cmap); + err_alloc_cmap: + iounmap(info->screen_base); + err_ioremap: + framebuffer_release(info); + err_fb_alloc: + return ret; +} + +static int vga16fb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) + unregister_framebuffer(info); + + return 0; +} + +static struct platform_driver vga16fb_driver = { + .probe = vga16fb_probe, + .remove = vga16fb_remove, + .driver = { + .name = "vga16fb", + }, +}; + +static struct platform_device *vga16fb_device; + +static int __init vga16fb_init(void) +{ + int ret; +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("vga16fb", &option)) + return -ENODEV; + + vga16fb_setup(option); +#endif + ret = platform_driver_register(&vga16fb_driver); + + if (!ret) { + vga16fb_device = platform_device_alloc("vga16fb", 0); + + if (vga16fb_device) + ret = platform_device_add(vga16fb_device); + else + ret = -ENOMEM; + + if (ret) { + platform_device_put(vga16fb_device); + platform_driver_unregister(&vga16fb_driver); + } + } + + return ret; +} + +static void __exit vga16fb_exit(void) +{ + platform_device_unregister(vga16fb_device); + platform_driver_unregister(&vga16fb_driver); +} + +MODULE_DESCRIPTION("Legacy VGA framebuffer device driver"); +MODULE_LICENSE("GPL"); +module_init(vga16fb_init); +module_exit(vga16fb_exit); + + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/drivers/video/fbdev/via/Makefile b/drivers/video/fbdev/via/Makefile new file mode 100644 index 000000000000..159f26e6adb5 --- /dev/null +++ b/drivers/video/fbdev/via/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the VIA framebuffer driver (for Linux Kernel 2.6) +# + +obj-$(CONFIG_FB_VIA) += viafb.o + +viafb-y :=viafbdev.o hw.o via_i2c.o dvi.o lcd.o ioctl.o accel.o \ + via_utility.o vt1636.o global.o tblDPASetting.o viamode.o \ + via-core.o via-gpio.o via_modesetting.o via_clock.o \ + via_aux.o via_aux_edid.o via_aux_vt1636.o via_aux_vt1632.o \ + via_aux_vt1631.o via_aux_vt1625.o via_aux_vt1622.o via_aux_vt1621.o \ + via_aux_sii164.o via_aux_ch7301.o diff --git a/drivers/video/fbdev/via/accel.c b/drivers/video/fbdev/via/accel.c new file mode 100644 index 000000000000..4b67b8e6030a --- /dev/null +++ b/drivers/video/fbdev/via/accel.c @@ -0,0 +1,547 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/via-core.h> +#include "global.h" + +/* + * Figure out an appropriate bytes-per-pixel setting. + */ +static int viafb_set_bpp(void __iomem *engine, u8 bpp) +{ + u32 gemode; + + /* Preserve the reserved bits */ + /* Lowest 2 bits to zero gives us no rotation */ + gemode = readl(engine + VIA_REG_GEMODE) & 0xfffffcfc; + switch (bpp) { + case 8: + gemode |= VIA_GEM_8bpp; + break; + case 16: + gemode |= VIA_GEM_16bpp; + break; + case 32: + gemode |= VIA_GEM_32bpp; + break; + default: + printk(KERN_WARNING "viafb_set_bpp: Unsupported bpp %d\n", bpp); + return -EINVAL; + } + writel(gemode, engine + VIA_REG_GEMODE); + return 0; +} + + +static int hw_bitblt_1(void __iomem *engine, u8 op, u32 width, u32 height, + u8 dst_bpp, u32 dst_addr, u32 dst_pitch, u32 dst_x, u32 dst_y, + u32 *src_mem, u32 src_addr, u32 src_pitch, u32 src_x, u32 src_y, + u32 fg_color, u32 bg_color, u8 fill_rop) +{ + u32 ge_cmd = 0, tmp, i; + int ret; + + if (!op || op > 3) { + printk(KERN_WARNING "hw_bitblt_1: Invalid operation: %d\n", op); + return -EINVAL; + } + + if (op != VIA_BITBLT_FILL && !src_mem && src_addr == dst_addr) { + if (src_x < dst_x) { + ge_cmd |= 0x00008000; + src_x += width - 1; + dst_x += width - 1; + } + if (src_y < dst_y) { + ge_cmd |= 0x00004000; + src_y += height - 1; + dst_y += height - 1; + } + } + + if (op == VIA_BITBLT_FILL) { + switch (fill_rop) { + case 0x00: /* blackness */ + case 0x5A: /* pattern inversion */ + case 0xF0: /* pattern copy */ + case 0xFF: /* whiteness */ + break; + default: + printk(KERN_WARNING "hw_bitblt_1: Invalid fill rop: " + "%u\n", fill_rop); + return -EINVAL; + } + } + + ret = viafb_set_bpp(engine, dst_bpp); + if (ret) + return ret; + + if (op != VIA_BITBLT_FILL) { + if (src_x & (op == VIA_BITBLT_MONO ? 0xFFFF8000 : 0xFFFFF000) + || src_y & 0xFFFFF000) { + printk(KERN_WARNING "hw_bitblt_1: Unsupported source " + "x/y %d %d\n", src_x, src_y); + return -EINVAL; + } + tmp = src_x | (src_y << 16); + writel(tmp, engine + 0x08); + } + + if (dst_x & 0xFFFFF000 || dst_y & 0xFFFFF000) { + printk(KERN_WARNING "hw_bitblt_1: Unsupported destination x/y " + "%d %d\n", dst_x, dst_y); + return -EINVAL; + } + tmp = dst_x | (dst_y << 16); + writel(tmp, engine + 0x0C); + + if ((width - 1) & 0xFFFFF000 || (height - 1) & 0xFFFFF000) { + printk(KERN_WARNING "hw_bitblt_1: Unsupported width/height " + "%d %d\n", width, height); + return -EINVAL; + } + tmp = (width - 1) | ((height - 1) << 16); + writel(tmp, engine + 0x10); + + if (op != VIA_BITBLT_COLOR) + writel(fg_color, engine + 0x18); + + if (op == VIA_BITBLT_MONO) + writel(bg_color, engine + 0x1C); + + if (op != VIA_BITBLT_FILL) { + tmp = src_mem ? 0 : src_addr; + if (dst_addr & 0xE0000007) { + printk(KERN_WARNING "hw_bitblt_1: Unsupported source " + "address %X\n", tmp); + return -EINVAL; + } + tmp >>= 3; + writel(tmp, engine + 0x30); + } + + if (dst_addr & 0xE0000007) { + printk(KERN_WARNING "hw_bitblt_1: Unsupported destination " + "address %X\n", dst_addr); + return -EINVAL; + } + tmp = dst_addr >> 3; + writel(tmp, engine + 0x34); + + if (op == VIA_BITBLT_FILL) + tmp = 0; + else + tmp = src_pitch; + if (tmp & 0xFFFFC007 || dst_pitch & 0xFFFFC007) { + printk(KERN_WARNING "hw_bitblt_1: Unsupported pitch %X %X\n", + tmp, dst_pitch); + return -EINVAL; + } + tmp = VIA_PITCH_ENABLE | (tmp >> 3) | (dst_pitch << (16 - 3)); + writel(tmp, engine + 0x38); + + if (op == VIA_BITBLT_FILL) + ge_cmd |= fill_rop << 24 | 0x00002000 | 0x00000001; + else { + ge_cmd |= 0xCC000000; /* ROP=SRCCOPY */ + if (src_mem) + ge_cmd |= 0x00000040; + if (op == VIA_BITBLT_MONO) + ge_cmd |= 0x00000002 | 0x00000100 | 0x00020000; + else + ge_cmd |= 0x00000001; + } + writel(ge_cmd, engine); + + if (op == VIA_BITBLT_FILL || !src_mem) + return 0; + + tmp = (width * height * (op == VIA_BITBLT_MONO ? 1 : (dst_bpp >> 3)) + + 3) >> 2; + + for (i = 0; i < tmp; i++) + writel(src_mem[i], engine + VIA_MMIO_BLTBASE); + + return 0; +} + +static int hw_bitblt_2(void __iomem *engine, u8 op, u32 width, u32 height, + u8 dst_bpp, u32 dst_addr, u32 dst_pitch, u32 dst_x, u32 dst_y, + u32 *src_mem, u32 src_addr, u32 src_pitch, u32 src_x, u32 src_y, + u32 fg_color, u32 bg_color, u8 fill_rop) +{ + u32 ge_cmd = 0, tmp, i; + int ret; + + if (!op || op > 3) { + printk(KERN_WARNING "hw_bitblt_2: Invalid operation: %d\n", op); + return -EINVAL; + } + + if (op != VIA_BITBLT_FILL && !src_mem && src_addr == dst_addr) { + if (src_x < dst_x) { + ge_cmd |= 0x00008000; + src_x += width - 1; + dst_x += width - 1; + } + if (src_y < dst_y) { + ge_cmd |= 0x00004000; + src_y += height - 1; + dst_y += height - 1; + } + } + + if (op == VIA_BITBLT_FILL) { + switch (fill_rop) { + case 0x00: /* blackness */ + case 0x5A: /* pattern inversion */ + case 0xF0: /* pattern copy */ + case 0xFF: /* whiteness */ + break; + default: + printk(KERN_WARNING "hw_bitblt_2: Invalid fill rop: " + "%u\n", fill_rop); + return -EINVAL; + } + } + + ret = viafb_set_bpp(engine, dst_bpp); + if (ret) + return ret; + + if (op == VIA_BITBLT_FILL) + tmp = 0; + else + tmp = src_pitch; + if (tmp & 0xFFFFC007 || dst_pitch & 0xFFFFC007) { + printk(KERN_WARNING "hw_bitblt_2: Unsupported pitch %X %X\n", + tmp, dst_pitch); + return -EINVAL; + } + tmp = (tmp >> 3) | (dst_pitch << (16 - 3)); + writel(tmp, engine + 0x08); + + if ((width - 1) & 0xFFFFF000 || (height - 1) & 0xFFFFF000) { + printk(KERN_WARNING "hw_bitblt_2: Unsupported width/height " + "%d %d\n", width, height); + return -EINVAL; + } + tmp = (width - 1) | ((height - 1) << 16); + writel(tmp, engine + 0x0C); + + if (dst_x & 0xFFFFF000 || dst_y & 0xFFFFF000) { + printk(KERN_WARNING "hw_bitblt_2: Unsupported destination x/y " + "%d %d\n", dst_x, dst_y); + return -EINVAL; + } + tmp = dst_x | (dst_y << 16); + writel(tmp, engine + 0x10); + + if (dst_addr & 0xE0000007) { + printk(KERN_WARNING "hw_bitblt_2: Unsupported destination " + "address %X\n", dst_addr); + return -EINVAL; + } + tmp = dst_addr >> 3; + writel(tmp, engine + 0x14); + + if (op != VIA_BITBLT_FILL) { + if (src_x & (op == VIA_BITBLT_MONO ? 0xFFFF8000 : 0xFFFFF000) + || src_y & 0xFFFFF000) { + printk(KERN_WARNING "hw_bitblt_2: Unsupported source " + "x/y %d %d\n", src_x, src_y); + return -EINVAL; + } + tmp = src_x | (src_y << 16); + writel(tmp, engine + 0x18); + + tmp = src_mem ? 0 : src_addr; + if (dst_addr & 0xE0000007) { + printk(KERN_WARNING "hw_bitblt_2: Unsupported source " + "address %X\n", tmp); + return -EINVAL; + } + tmp >>= 3; + writel(tmp, engine + 0x1C); + } + + if (op == VIA_BITBLT_FILL) { + writel(fg_color, engine + 0x58); + } else if (op == VIA_BITBLT_MONO) { + writel(fg_color, engine + 0x4C); + writel(bg_color, engine + 0x50); + } + + if (op == VIA_BITBLT_FILL) + ge_cmd |= fill_rop << 24 | 0x00002000 | 0x00000001; + else { + ge_cmd |= 0xCC000000; /* ROP=SRCCOPY */ + if (src_mem) + ge_cmd |= 0x00000040; + if (op == VIA_BITBLT_MONO) + ge_cmd |= 0x00000002 | 0x00000100 | 0x00020000; + else + ge_cmd |= 0x00000001; + } + writel(ge_cmd, engine); + + if (op == VIA_BITBLT_FILL || !src_mem) + return 0; + + tmp = (width * height * (op == VIA_BITBLT_MONO ? 1 : (dst_bpp >> 3)) + + 3) >> 2; + + for (i = 0; i < tmp; i++) + writel(src_mem[i], engine + VIA_MMIO_BLTBASE); + + return 0; +} + +int viafb_setup_engine(struct fb_info *info) +{ + struct viafb_par *viapar = info->par; + void __iomem *engine; + u32 chip_name = viapar->shared->chip_info.gfx_chip_name; + + engine = viapar->shared->vdev->engine_mmio; + if (!engine) { + printk(KERN_WARNING "viafb_init_accel: ioremap failed, " + "hardware acceleration disabled\n"); + return -ENOMEM; + } + + switch (chip_name) { + case UNICHROME_CLE266: + case UNICHROME_K400: + case UNICHROME_K800: + case UNICHROME_PM800: + case UNICHROME_CN700: + case UNICHROME_CX700: + case UNICHROME_CN750: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + viapar->shared->hw_bitblt = hw_bitblt_1; + break; + case UNICHROME_VX800: + case UNICHROME_VX855: + case UNICHROME_VX900: + viapar->shared->hw_bitblt = hw_bitblt_2; + break; + default: + viapar->shared->hw_bitblt = NULL; + } + + viapar->fbmem_free -= CURSOR_SIZE; + viapar->shared->cursor_vram_addr = viapar->fbmem_free; + viapar->fbmem_used += CURSOR_SIZE; + + viapar->fbmem_free -= VQ_SIZE; + viapar->shared->vq_vram_addr = viapar->fbmem_free; + viapar->fbmem_used += VQ_SIZE; + +#if defined(CONFIG_VIDEO_VIA_CAMERA) || defined(CONFIG_VIDEO_VIA_CAMERA_MODULE) + /* + * Set aside a chunk of framebuffer memory for the camera + * driver. Someday this driver probably needs a proper allocator + * for fbmem; for now, we just have to do this before the + * framebuffer initializes itself. + * + * As for the size: the engine can handle three frames, + * 16 bits deep, up to VGA resolution. + */ + viapar->shared->vdev->camera_fbmem_size = 3*VGA_HEIGHT*VGA_WIDTH*2; + viapar->fbmem_free -= viapar->shared->vdev->camera_fbmem_size; + viapar->fbmem_used += viapar->shared->vdev->camera_fbmem_size; + viapar->shared->vdev->camera_fbmem_offset = viapar->fbmem_free; +#endif + + viafb_reset_engine(viapar); + return 0; +} + +void viafb_reset_engine(struct viafb_par *viapar) +{ + void __iomem *engine = viapar->shared->vdev->engine_mmio; + int highest_reg, i; + u32 vq_start_addr, vq_end_addr, vq_start_low, vq_end_low, vq_high, + vq_len, chip_name = viapar->shared->chip_info.gfx_chip_name; + + /* Initialize registers to reset the 2D engine */ + switch (viapar->shared->chip_info.twod_engine) { + case VIA_2D_ENG_M1: + highest_reg = 0x5c; + break; + default: + highest_reg = 0x40; + break; + } + for (i = 0; i <= highest_reg; i += 4) + writel(0x0, engine + i); + + /* Init AGP and VQ regs */ + switch (chip_name) { + case UNICHROME_K8M890: + case UNICHROME_P4M900: + case UNICHROME_VX800: + case UNICHROME_VX855: + case UNICHROME_VX900: + writel(0x00100000, engine + VIA_REG_CR_TRANSET); + writel(0x680A0000, engine + VIA_REG_CR_TRANSPACE); + writel(0x02000000, engine + VIA_REG_CR_TRANSPACE); + break; + + default: + writel(0x00100000, engine + VIA_REG_TRANSET); + writel(0x00000000, engine + VIA_REG_TRANSPACE); + writel(0x00333004, engine + VIA_REG_TRANSPACE); + writel(0x60000000, engine + VIA_REG_TRANSPACE); + writel(0x61000000, engine + VIA_REG_TRANSPACE); + writel(0x62000000, engine + VIA_REG_TRANSPACE); + writel(0x63000000, engine + VIA_REG_TRANSPACE); + writel(0x64000000, engine + VIA_REG_TRANSPACE); + writel(0x7D000000, engine + VIA_REG_TRANSPACE); + + writel(0xFE020000, engine + VIA_REG_TRANSET); + writel(0x00000000, engine + VIA_REG_TRANSPACE); + break; + } + + /* Enable VQ */ + vq_start_addr = viapar->shared->vq_vram_addr; + vq_end_addr = viapar->shared->vq_vram_addr + VQ_SIZE - 1; + + vq_start_low = 0x50000000 | (vq_start_addr & 0xFFFFFF); + vq_end_low = 0x51000000 | (vq_end_addr & 0xFFFFFF); + vq_high = 0x52000000 | ((vq_start_addr & 0xFF000000) >> 24) | + ((vq_end_addr & 0xFF000000) >> 16); + vq_len = 0x53000000 | (VQ_SIZE >> 3); + + switch (chip_name) { + case UNICHROME_K8M890: + case UNICHROME_P4M900: + case UNICHROME_VX800: + case UNICHROME_VX855: + case UNICHROME_VX900: + vq_start_low |= 0x20000000; + vq_end_low |= 0x20000000; + vq_high |= 0x20000000; + vq_len |= 0x20000000; + + writel(0x00100000, engine + VIA_REG_CR_TRANSET); + writel(vq_high, engine + VIA_REG_CR_TRANSPACE); + writel(vq_start_low, engine + VIA_REG_CR_TRANSPACE); + writel(vq_end_low, engine + VIA_REG_CR_TRANSPACE); + writel(vq_len, engine + VIA_REG_CR_TRANSPACE); + writel(0x74301001, engine + VIA_REG_CR_TRANSPACE); + writel(0x00000000, engine + VIA_REG_CR_TRANSPACE); + break; + default: + writel(0x00FE0000, engine + VIA_REG_TRANSET); + writel(0x080003FE, engine + VIA_REG_TRANSPACE); + writel(0x0A00027C, engine + VIA_REG_TRANSPACE); + writel(0x0B000260, engine + VIA_REG_TRANSPACE); + writel(0x0C000274, engine + VIA_REG_TRANSPACE); + writel(0x0D000264, engine + VIA_REG_TRANSPACE); + writel(0x0E000000, engine + VIA_REG_TRANSPACE); + writel(0x0F000020, engine + VIA_REG_TRANSPACE); + writel(0x1000027E, engine + VIA_REG_TRANSPACE); + writel(0x110002FE, engine + VIA_REG_TRANSPACE); + writel(0x200F0060, engine + VIA_REG_TRANSPACE); + + writel(0x00000006, engine + VIA_REG_TRANSPACE); + writel(0x40008C0F, engine + VIA_REG_TRANSPACE); + writel(0x44000000, engine + VIA_REG_TRANSPACE); + writel(0x45080C04, engine + VIA_REG_TRANSPACE); + writel(0x46800408, engine + VIA_REG_TRANSPACE); + + writel(vq_high, engine + VIA_REG_TRANSPACE); + writel(vq_start_low, engine + VIA_REG_TRANSPACE); + writel(vq_end_low, engine + VIA_REG_TRANSPACE); + writel(vq_len, engine + VIA_REG_TRANSPACE); + break; + } + + /* Set Cursor Image Base Address */ + writel(viapar->shared->cursor_vram_addr, engine + VIA_REG_CURSOR_MODE); + writel(0x0, engine + VIA_REG_CURSOR_POS); + writel(0x0, engine + VIA_REG_CURSOR_ORG); + writel(0x0, engine + VIA_REG_CURSOR_BG); + writel(0x0, engine + VIA_REG_CURSOR_FG); + return; +} + +void viafb_show_hw_cursor(struct fb_info *info, int Status) +{ + struct viafb_par *viapar = info->par; + u32 temp, iga_path = viapar->iga_path; + + temp = readl(viapar->shared->vdev->engine_mmio + VIA_REG_CURSOR_MODE); + switch (Status) { + case HW_Cursor_ON: + temp |= 0x1; + break; + case HW_Cursor_OFF: + temp &= 0xFFFFFFFE; + break; + } + switch (iga_path) { + case IGA2: + temp |= 0x80000000; + break; + case IGA1: + default: + temp &= 0x7FFFFFFF; + } + writel(temp, viapar->shared->vdev->engine_mmio + VIA_REG_CURSOR_MODE); +} + +void viafb_wait_engine_idle(struct fb_info *info) +{ + struct viafb_par *viapar = info->par; + int loop = 0; + u32 mask; + void __iomem *engine = viapar->shared->vdev->engine_mmio; + + switch (viapar->shared->chip_info.twod_engine) { + case VIA_2D_ENG_H5: + case VIA_2D_ENG_M1: + mask = VIA_CMD_RGTR_BUSY_M1 | VIA_2D_ENG_BUSY_M1 | + VIA_3D_ENG_BUSY_M1; + break; + default: + while (!(readl(engine + VIA_REG_STATUS) & + VIA_VR_QUEUE_BUSY) && (loop < MAXLOOP)) { + loop++; + cpu_relax(); + } + mask = VIA_CMD_RGTR_BUSY | VIA_2D_ENG_BUSY | VIA_3D_ENG_BUSY; + break; + } + + while ((readl(engine + VIA_REG_STATUS) & mask) && (loop < MAXLOOP)) { + loop++; + cpu_relax(); + } + + if (loop >= MAXLOOP) + printk(KERN_ERR "viafb_wait_engine_idle: not syncing\n"); +} diff --git a/drivers/video/fbdev/via/accel.h b/drivers/video/fbdev/via/accel.h new file mode 100644 index 000000000000..79d5e10cc835 --- /dev/null +++ b/drivers/video/fbdev/via/accel.h @@ -0,0 +1,211 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __ACCEL_H__ +#define __ACCEL_H__ + +#define FB_ACCEL_VIA_UNICHROME 50 + +/* MMIO Base Address Definition */ +#define MMIO_VGABASE 0x8000 +#define MMIO_CR_READ (MMIO_VGABASE + 0x3D4) +#define MMIO_CR_WRITE (MMIO_VGABASE + 0x3D5) +#define MMIO_SR_READ (MMIO_VGABASE + 0x3C4) +#define MMIO_SR_WRITE (MMIO_VGABASE + 0x3C5) + +/* HW Cursor Status Define */ +#define HW_Cursor_ON 0 +#define HW_Cursor_OFF 1 + +#define CURSOR_SIZE (8 * 1024) +#define VQ_SIZE (256 * 1024) + +#define VIA_MMIO_BLTBASE 0x200000 +#define VIA_MMIO_BLTSIZE 0x200000 + +/* Defines for 2D registers */ +#define VIA_REG_GECMD 0x000 +#define VIA_REG_GEMODE 0x004 +#define VIA_REG_SRCPOS 0x008 +#define VIA_REG_DSTPOS 0x00C +/* width and height */ +#define VIA_REG_DIMENSION 0x010 +#define VIA_REG_PATADDR 0x014 +#define VIA_REG_FGCOLOR 0x018 +#define VIA_REG_BGCOLOR 0x01C +/* top and left of clipping */ +#define VIA_REG_CLIPTL 0x020 +/* bottom and right of clipping */ +#define VIA_REG_CLIPBR 0x024 +#define VIA_REG_OFFSET 0x028 +/* color key control */ +#define VIA_REG_KEYCONTROL 0x02C +#define VIA_REG_SRCBASE 0x030 +#define VIA_REG_DSTBASE 0x034 +/* pitch of src and dst */ +#define VIA_REG_PITCH 0x038 +#define VIA_REG_MONOPAT0 0x03C +#define VIA_REG_MONOPAT1 0x040 +/* from 0x100 to 0x1ff */ +#define VIA_REG_COLORPAT 0x100 + +/* defines for VIA 2D registers for vt3353/3409 (M1 engine)*/ +#define VIA_REG_GECMD_M1 0x000 +#define VIA_REG_GEMODE_M1 0x004 +#define VIA_REG_GESTATUS_M1 0x004 /* as same as VIA_REG_GEMODE */ +#define VIA_REG_PITCH_M1 0x008 /* pitch of src and dst */ +#define VIA_REG_DIMENSION_M1 0x00C /* width and height */ +#define VIA_REG_DSTPOS_M1 0x010 +#define VIA_REG_LINE_XY_M1 0x010 +#define VIA_REG_DSTBASE_M1 0x014 +#define VIA_REG_SRCPOS_M1 0x018 +#define VIA_REG_LINE_K1K2_M1 0x018 +#define VIA_REG_SRCBASE_M1 0x01C +#define VIA_REG_PATADDR_M1 0x020 +#define VIA_REG_MONOPAT0_M1 0x024 +#define VIA_REG_MONOPAT1_M1 0x028 +#define VIA_REG_OFFSET_M1 0x02C +#define VIA_REG_LINE_ERROR_M1 0x02C +#define VIA_REG_CLIPTL_M1 0x040 /* top and left of clipping */ +#define VIA_REG_CLIPBR_M1 0x044 /* bottom and right of clipping */ +#define VIA_REG_KEYCONTROL_M1 0x048 /* color key control */ +#define VIA_REG_FGCOLOR_M1 0x04C +#define VIA_REG_DSTCOLORKEY_M1 0x04C /* as same as VIA_REG_FG */ +#define VIA_REG_BGCOLOR_M1 0x050 +#define VIA_REG_SRCCOLORKEY_M1 0x050 /* as same as VIA_REG_BG */ +#define VIA_REG_MONOPATFGC_M1 0x058 /* Add BG color of Pattern. */ +#define VIA_REG_MONOPATBGC_M1 0x05C /* Add FG color of Pattern. */ +#define VIA_REG_COLORPAT_M1 0x100 /* from 0x100 to 0x1ff */ + +/* VIA_REG_PITCH(0x38): Pitch Setting */ +#define VIA_PITCH_ENABLE 0x80000000 + +/* defines for VIA HW cursor registers */ +#define VIA_REG_CURSOR_MODE 0x2D0 +#define VIA_REG_CURSOR_POS 0x2D4 +#define VIA_REG_CURSOR_ORG 0x2D8 +#define VIA_REG_CURSOR_BG 0x2DC +#define VIA_REG_CURSOR_FG 0x2E0 + +/* VIA_REG_GEMODE(0x04): GE mode */ +#define VIA_GEM_8bpp 0x00000000 +#define VIA_GEM_16bpp 0x00000100 +#define VIA_GEM_32bpp 0x00000300 + +/* VIA_REG_GECMD(0x00): 2D Engine Command */ +#define VIA_GEC_NOOP 0x00000000 +#define VIA_GEC_BLT 0x00000001 +#define VIA_GEC_LINE 0x00000005 + +/* Rotate Command */ +#define VIA_GEC_ROT 0x00000008 + +#define VIA_GEC_SRC_XY 0x00000000 +#define VIA_GEC_SRC_LINEAR 0x00000010 +#define VIA_GEC_DST_XY 0x00000000 +#define VIA_GEC_DST_LINRAT 0x00000020 + +#define VIA_GEC_SRC_FB 0x00000000 +#define VIA_GEC_SRC_SYS 0x00000040 +#define VIA_GEC_DST_FB 0x00000000 +#define VIA_GEC_DST_SYS 0x00000080 + +/* source is mono */ +#define VIA_GEC_SRC_MONO 0x00000100 +/* pattern is mono */ +#define VIA_GEC_PAT_MONO 0x00000200 +/* mono src is opaque */ +#define VIA_GEC_MSRC_OPAQUE 0x00000000 +/* mono src is transparent */ +#define VIA_GEC_MSRC_TRANS 0x00000400 +/* pattern is in frame buffer */ +#define VIA_GEC_PAT_FB 0x00000000 +/* pattern is from reg setting */ +#define VIA_GEC_PAT_REG 0x00000800 + +#define VIA_GEC_CLIP_DISABLE 0x00000000 +#define VIA_GEC_CLIP_ENABLE 0x00001000 + +#define VIA_GEC_FIXCOLOR_PAT 0x00002000 + +#define VIA_GEC_INCX 0x00000000 +#define VIA_GEC_DECY 0x00004000 +#define VIA_GEC_INCY 0x00000000 +#define VIA_GEC_DECX 0x00008000 +/* mono pattern is opaque */ +#define VIA_GEC_MPAT_OPAQUE 0x00000000 +/* mono pattern is transparent */ +#define VIA_GEC_MPAT_TRANS 0x00010000 + +#define VIA_GEC_MONO_UNPACK 0x00000000 +#define VIA_GEC_MONO_PACK 0x00020000 +#define VIA_GEC_MONO_DWORD 0x00000000 +#define VIA_GEC_MONO_WORD 0x00040000 +#define VIA_GEC_MONO_BYTE 0x00080000 + +#define VIA_GEC_LASTPIXEL_ON 0x00000000 +#define VIA_GEC_LASTPIXEL_OFF 0x00100000 +#define VIA_GEC_X_MAJOR 0x00000000 +#define VIA_GEC_Y_MAJOR 0x00200000 +#define VIA_GEC_QUICK_START 0x00800000 + +/* defines for VIA 3D registers */ +#define VIA_REG_STATUS 0x400 +#define VIA_REG_CR_TRANSET 0x41C +#define VIA_REG_CR_TRANSPACE 0x420 +#define VIA_REG_TRANSET 0x43C +#define VIA_REG_TRANSPACE 0x440 + +/* VIA_REG_STATUS(0x400): Engine Status */ + +/* Command Regulator is busy */ +#define VIA_CMD_RGTR_BUSY 0x00000080 +/* 2D Engine is busy */ +#define VIA_2D_ENG_BUSY 0x00000002 +/* 3D Engine is busy */ +#define VIA_3D_ENG_BUSY 0x00000001 +/* Virtual Queue is busy */ +#define VIA_VR_QUEUE_BUSY 0x00020000 + +/* VIA_REG_STATUS(0x400): Engine Status for H5 */ +#define VIA_CMD_RGTR_BUSY_H5 0x00000010 /* Command Regulator is busy */ +#define VIA_2D_ENG_BUSY_H5 0x00000002 /* 2D Engine is busy */ +#define VIA_3D_ENG_BUSY_H5 0x00001FE1 /* 3D Engine is busy */ +#define VIA_VR_QUEUE_BUSY_H5 0x00000004 /* Virtual Queue is busy */ + +/* VIA_REG_STATUS(0x400): Engine Status for VT3353/3409 */ +#define VIA_CMD_RGTR_BUSY_M1 0x00000010 /* Command Regulator is busy */ +#define VIA_2D_ENG_BUSY_M1 0x00000002 /* 2D Engine is busy */ +#define VIA_3D_ENG_BUSY_M1 0x00001FE1 /* 3D Engine is busy */ +#define VIA_VR_QUEUE_BUSY_M1 0x00000004 /* Virtual Queue is busy */ + +#define MAXLOOP 0xFFFFFF + +#define VIA_BITBLT_COLOR 1 +#define VIA_BITBLT_MONO 2 +#define VIA_BITBLT_FILL 3 + +int viafb_setup_engine(struct fb_info *info); +void viafb_reset_engine(struct viafb_par *viapar); +void viafb_show_hw_cursor(struct fb_info *info, int Status); +void viafb_wait_engine_idle(struct fb_info *info); + +#endif /* __ACCEL_H__ */ diff --git a/drivers/video/fbdev/via/chip.h b/drivers/video/fbdev/via/chip.h new file mode 100644 index 000000000000..d32a5076c20f --- /dev/null +++ b/drivers/video/fbdev/via/chip.h @@ -0,0 +1,176 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +#ifndef __CHIP_H__ +#define __CHIP_H__ + +#include "global.h" + +/***************************************/ +/* Definition Graphic Chip Information */ +/***************************************/ + +#define PCI_VIA_VENDOR_ID 0x1106 + +/* Define VIA Graphic Chip Name */ +#define UNICHROME_CLE266 1 +#define UNICHROME_CLE266_DID 0x3122 +#define CLE266_REVISION_AX 0x0A +#define CLE266_REVISION_CX 0x0C + +#define UNICHROME_K400 2 +#define UNICHROME_K400_DID 0x7205 + +#define UNICHROME_K800 3 +#define UNICHROME_K800_DID 0x3108 + +#define UNICHROME_PM800 4 +#define UNICHROME_PM800_DID 0x3118 + +#define UNICHROME_CN700 5 +#define UNICHROME_CN700_DID 0x3344 + +#define UNICHROME_CX700 6 +#define UNICHROME_CX700_DID 0x3157 +#define CX700_REVISION_700 0x0 +#define CX700_REVISION_700M 0x1 +#define CX700_REVISION_700M2 0x2 + +#define UNICHROME_CN750 7 +#define UNICHROME_CN750_DID 0x3225 + +#define UNICHROME_K8M890 8 +#define UNICHROME_K8M890_DID 0x3230 + +#define UNICHROME_P4M890 9 +#define UNICHROME_P4M890_DID 0x3343 + +#define UNICHROME_P4M900 10 +#define UNICHROME_P4M900_DID 0x3371 + +#define UNICHROME_VX800 11 +#define UNICHROME_VX800_DID 0x1122 + +#define UNICHROME_VX855 12 +#define UNICHROME_VX855_DID 0x5122 + +#define UNICHROME_VX900 13 +#define UNICHROME_VX900_DID 0x7122 + +/**************************************************/ +/* Definition TMDS Trasmitter Information */ +/**************************************************/ + +/* Definition TMDS Trasmitter Index */ +#define NON_TMDS_TRANSMITTER 0x00 +#define VT1632_TMDS 0x01 +#define INTEGRATED_TMDS 0x42 + +/* Definition TMDS Trasmitter I2C Slave Address */ +#define VT1632_TMDS_I2C_ADDR 0x10 + +/**************************************************/ +/* Definition LVDS Trasmitter Information */ +/**************************************************/ + +/* Definition LVDS Trasmitter Index */ +#define NON_LVDS_TRANSMITTER 0x00 +#define VT1631_LVDS 0x01 +#define VT1636_LVDS 0x0E +#define INTEGRATED_LVDS 0x41 + +/* Definition Digital Transmitter Mode */ +#define TX_DATA_12_BITS 0x01 +#define TX_DATA_24_BITS 0x02 +#define TX_DATA_DDR_MODE 0x04 +#define TX_DATA_SDR_MODE 0x08 + +/* Definition LVDS Trasmitter I2C Slave Address */ +#define VT1631_LVDS_I2C_ADDR 0x70 +#define VT3271_LVDS_I2C_ADDR 0x80 +#define VT1636_LVDS_I2C_ADDR 0x80 + +struct tmds_chip_information { + int tmds_chip_name; + int tmds_chip_slave_addr; + int output_interface; + int i2c_port; +}; + +struct lvds_chip_information { + int lvds_chip_name; + int lvds_chip_slave_addr; + int output_interface; + int i2c_port; +}; + +/* The type of 2D engine */ +enum via_2d_engine { + VIA_2D_ENG_H2, + VIA_2D_ENG_H5, + VIA_2D_ENG_M1, +}; + +struct chip_information { + int gfx_chip_name; + int gfx_chip_revision; + enum via_2d_engine twod_engine; + struct tmds_chip_information tmds_chip_info; + struct lvds_chip_information lvds_chip_info; + struct lvds_chip_information lvds_chip_info2; +}; + +struct tmds_setting_information { + int iga_path; + int h_active; + int v_active; + int max_pixel_clock; +}; + +struct lvds_setting_information { + int iga_path; + int lcd_panel_hres; + int lcd_panel_vres; + int display_method; + int device_lcd_dualedge; + int LCDDithering; + int lcd_mode; + u32 vclk; /*panel mode clock value */ +}; + +struct GFX_DPA_SETTING { + int ClkRangeIndex; + u8 DVP0; /* CR96[3:0] */ + u8 DVP0DataDri_S1; /* SR2A[5] */ + u8 DVP0DataDri_S; /* SR1B[1] */ + u8 DVP0ClockDri_S1; /* SR2A[4] */ + u8 DVP0ClockDri_S; /* SR1E[2] */ + u8 DVP1; /* CR9B[3:0] */ + u8 DVP1Driving; /* SR65[3:0], Data and Clock driving */ + u8 DFPHigh; /* CR97[3:0] */ + u8 DFPLow; /* CR99[3:0] */ + +}; + +struct VT1636_DPA_SETTING { + u8 CLK_SEL_ST1; + u8 CLK_SEL_ST2; +}; +#endif /* __CHIP_H__ */ diff --git a/drivers/video/fbdev/via/debug.h b/drivers/video/fbdev/via/debug.h new file mode 100644 index 000000000000..86eacc2017f3 --- /dev/null +++ b/drivers/video/fbdev/via/debug.h @@ -0,0 +1,41 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#ifndef VIAFB_DEBUG +#define VIAFB_DEBUG 0 +#endif + +#if VIAFB_DEBUG +#define DEBUG_MSG(f, a...) printk(f, ## a) +#else +#define DEBUG_MSG(f, a...) +#endif + +#define VIAFB_WARN 0 +#if VIAFB_WARN +#define WARN_MSG(f, a...) printk(f, ## a) +#else +#define WARN_MSG(f, a...) +#endif + +#endif /* __DEBUG_H__ */ diff --git a/drivers/video/fbdev/via/dvi.c b/drivers/video/fbdev/via/dvi.c new file mode 100644 index 000000000000..7789553952d3 --- /dev/null +++ b/drivers/video/fbdev/via/dvi.c @@ -0,0 +1,478 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/via-core.h> +#include <linux/via_i2c.h> +#include "global.h" + +static void tmds_register_write(int index, u8 data); +static int tmds_register_read(int index); +static int tmds_register_read_bytes(int index, u8 *buff, int buff_len); +static void dvi_get_panel_size_from_DDCv1( + struct tmds_chip_information *tmds_chip, + struct tmds_setting_information *tmds_setting); +static int viafb_dvi_query_EDID(void); + +static inline bool check_tmds_chip(int device_id_subaddr, int device_id) +{ + return tmds_register_read(device_id_subaddr) == device_id; +} + +void viafb_init_dvi_size(struct tmds_chip_information *tmds_chip, + struct tmds_setting_information *tmds_setting) +{ + DEBUG_MSG(KERN_INFO "viafb_init_dvi_size()\n"); + + viafb_dvi_sense(); + if (viafb_dvi_query_EDID() == 1) + dvi_get_panel_size_from_DDCv1(tmds_chip, tmds_setting); + + return; +} + +bool viafb_tmds_trasmitter_identify(void) +{ + unsigned char sr2a = 0, sr1e = 0, sr3e = 0; + + /* Turn on ouputting pad */ + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_K8M890: + /*=* DFP Low Pad on *=*/ + sr2a = viafb_read_reg(VIASR, SR2A); + viafb_write_reg_mask(SR2A, VIASR, 0x03, BIT0 + BIT1); + break; + + case UNICHROME_P4M900: + case UNICHROME_P4M890: + /* DFP Low Pad on */ + sr2a = viafb_read_reg(VIASR, SR2A); + viafb_write_reg_mask(SR2A, VIASR, 0x03, BIT0 + BIT1); + /* DVP0 Pad on */ + sr1e = viafb_read_reg(VIASR, SR1E); + viafb_write_reg_mask(SR1E, VIASR, 0xC0, BIT6 + BIT7); + break; + + default: + /* DVP0/DVP1 Pad on */ + sr1e = viafb_read_reg(VIASR, SR1E); + viafb_write_reg_mask(SR1E, VIASR, 0xF0, BIT4 + + BIT5 + BIT6 + BIT7); + /* SR3E[1]Multi-function selection: + 0 = Emulate I2C and DDC bus by GPIO2/3/4. */ + sr3e = viafb_read_reg(VIASR, SR3E); + viafb_write_reg_mask(SR3E, VIASR, 0x0, BIT5); + break; + } + + /* Check for VT1632: */ + viaparinfo->chip_info->tmds_chip_info.tmds_chip_name = VT1632_TMDS; + viaparinfo->chip_info-> + tmds_chip_info.tmds_chip_slave_addr = VT1632_TMDS_I2C_ADDR; + viaparinfo->chip_info->tmds_chip_info.i2c_port = VIA_PORT_31; + if (check_tmds_chip(VT1632_DEVICE_ID_REG, VT1632_DEVICE_ID)) { + /* + * Currently only support 12bits,dual edge,add 24bits mode later + */ + tmds_register_write(0x08, 0x3b); + + DEBUG_MSG(KERN_INFO "\n VT1632 TMDS ! \n"); + DEBUG_MSG(KERN_INFO "\n %2d", + viaparinfo->chip_info->tmds_chip_info.tmds_chip_name); + DEBUG_MSG(KERN_INFO "\n %2d", + viaparinfo->chip_info->tmds_chip_info.i2c_port); + return true; + } else { + viaparinfo->chip_info->tmds_chip_info.i2c_port = VIA_PORT_2C; + if (check_tmds_chip(VT1632_DEVICE_ID_REG, VT1632_DEVICE_ID)) { + tmds_register_write(0x08, 0x3b); + DEBUG_MSG(KERN_INFO "\n VT1632 TMDS ! \n"); + DEBUG_MSG(KERN_INFO "\n %2d", + viaparinfo->chip_info-> + tmds_chip_info.tmds_chip_name); + DEBUG_MSG(KERN_INFO "\n %2d", + viaparinfo->chip_info-> + tmds_chip_info.i2c_port); + return true; + } + } + + viaparinfo->chip_info->tmds_chip_info.tmds_chip_name = INTEGRATED_TMDS; + + if ((viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) && + ((viafb_display_hardware_layout == HW_LAYOUT_DVI_ONLY) || + (viafb_display_hardware_layout == HW_LAYOUT_LCD_DVI))) { + DEBUG_MSG(KERN_INFO "\n Integrated TMDS ! \n"); + return true; + } + + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_K8M890: + viafb_write_reg(SR2A, VIASR, sr2a); + break; + + case UNICHROME_P4M900: + case UNICHROME_P4M890: + viafb_write_reg(SR2A, VIASR, sr2a); + viafb_write_reg(SR1E, VIASR, sr1e); + break; + + default: + viafb_write_reg(SR1E, VIASR, sr1e); + viafb_write_reg(SR3E, VIASR, sr3e); + break; + } + + viaparinfo->chip_info-> + tmds_chip_info.tmds_chip_name = NON_TMDS_TRANSMITTER; + viaparinfo->chip_info->tmds_chip_info. + tmds_chip_slave_addr = VT1632_TMDS_I2C_ADDR; + return false; +} + +static void tmds_register_write(int index, u8 data) +{ + viafb_i2c_writebyte(viaparinfo->chip_info->tmds_chip_info.i2c_port, + viaparinfo->chip_info->tmds_chip_info.tmds_chip_slave_addr, + index, data); +} + +static int tmds_register_read(int index) +{ + u8 data; + + viafb_i2c_readbyte(viaparinfo->chip_info->tmds_chip_info.i2c_port, + (u8) viaparinfo->chip_info->tmds_chip_info.tmds_chip_slave_addr, + (u8) index, &data); + return data; +} + +static int tmds_register_read_bytes(int index, u8 *buff, int buff_len) +{ + viafb_i2c_readbytes(viaparinfo->chip_info->tmds_chip_info.i2c_port, + (u8) viaparinfo->chip_info->tmds_chip_info.tmds_chip_slave_addr, + (u8) index, buff, buff_len); + return 0; +} + +/* DVI Set Mode */ +void viafb_dvi_set_mode(const struct fb_var_screeninfo *var, + u16 cxres, u16 cyres, int iga) +{ + struct fb_var_screeninfo dvi_var = *var; + const struct fb_videomode *rb_mode; + int maxPixelClock; + + maxPixelClock = viaparinfo->shared->tmds_setting_info.max_pixel_clock; + if (maxPixelClock && PICOS2KHZ(var->pixclock) / 1000 > maxPixelClock) { + rb_mode = viafb_get_best_rb_mode(var->xres, var->yres, 60); + if (rb_mode) + viafb_fill_var_timing_info(&dvi_var, rb_mode); + } + + viafb_fill_crtc_timing(&dvi_var, cxres, cyres, iga); +} + +/* Sense DVI Connector */ +int viafb_dvi_sense(void) +{ + u8 RegSR1E = 0, RegSR3E = 0, RegCR6B = 0, RegCR91 = 0, + RegCR93 = 0, RegCR9B = 0, data; + int ret = false; + + DEBUG_MSG(KERN_INFO "viafb_dvi_sense!!\n"); + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) { + /* DI1 Pad on */ + RegSR1E = viafb_read_reg(VIASR, SR1E); + viafb_write_reg(SR1E, VIASR, RegSR1E | 0x30); + + /* CR6B[0]VCK Input Selection: 1 = External clock. */ + RegCR6B = viafb_read_reg(VIACR, CR6B); + viafb_write_reg(CR6B, VIACR, RegCR6B | 0x08); + + /* CR91[4] VDD On [3] Data On [2] VEE On [1] Back Light Off + [0] Software Control Power Sequence */ + RegCR91 = viafb_read_reg(VIACR, CR91); + viafb_write_reg(CR91, VIACR, 0x1D); + + /* CR93[7] DI1 Data Source Selection: 1 = DSP2. + CR93[5] DI1 Clock Source: 1 = internal. + CR93[4] DI1 Clock Polarity. + CR93[3:1] DI1 Clock Adjust. CR93[0] DI1 enable */ + RegCR93 = viafb_read_reg(VIACR, CR93); + viafb_write_reg(CR93, VIACR, 0x01); + } else { + /* DVP0/DVP1 Pad on */ + RegSR1E = viafb_read_reg(VIASR, SR1E); + viafb_write_reg(SR1E, VIASR, RegSR1E | 0xF0); + + /* SR3E[1]Multi-function selection: + 0 = Emulate I2C and DDC bus by GPIO2/3/4. */ + RegSR3E = viafb_read_reg(VIASR, SR3E); + viafb_write_reg(SR3E, VIASR, RegSR3E & (~0x20)); + + /* CR91[4] VDD On [3] Data On [2] VEE On [1] Back Light Off + [0] Software Control Power Sequence */ + RegCR91 = viafb_read_reg(VIACR, CR91); + viafb_write_reg(CR91, VIACR, 0x1D); + + /*CR9B[4] DVP1 Data Source Selection: 1 = From secondary + display.CR9B[2:0] DVP1 Clock Adjust */ + RegCR9B = viafb_read_reg(VIACR, CR9B); + viafb_write_reg(CR9B, VIACR, 0x01); + } + + data = (u8) tmds_register_read(0x09); + if (data & 0x04) + ret = true; + + if (ret == false) { + if (viafb_dvi_query_EDID()) + ret = true; + } + + /* Restore status */ + viafb_write_reg(SR1E, VIASR, RegSR1E); + viafb_write_reg(CR91, VIACR, RegCR91); + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) { + viafb_write_reg(CR6B, VIACR, RegCR6B); + viafb_write_reg(CR93, VIACR, RegCR93); + } else { + viafb_write_reg(SR3E, VIASR, RegSR3E); + viafb_write_reg(CR9B, VIACR, RegCR9B); + } + + return ret; +} + +/* Query Flat Panel's EDID Table Version Through DVI Connector */ +static int viafb_dvi_query_EDID(void) +{ + u8 data0, data1; + int restore; + + DEBUG_MSG(KERN_INFO "viafb_dvi_query_EDID!!\n"); + + restore = viaparinfo->chip_info->tmds_chip_info.tmds_chip_slave_addr; + viaparinfo->chip_info->tmds_chip_info.tmds_chip_slave_addr = 0xA0; + + data0 = (u8) tmds_register_read(0x00); + data1 = (u8) tmds_register_read(0x01); + if ((data0 == 0) && (data1 == 0xFF)) { + viaparinfo->chip_info-> + tmds_chip_info.tmds_chip_slave_addr = restore; + return EDID_VERSION_1; /* Found EDID1 Table */ + } + + return false; +} + +/* Get Panel Size Using EDID1 Table */ +static void dvi_get_panel_size_from_DDCv1( + struct tmds_chip_information *tmds_chip, + struct tmds_setting_information *tmds_setting) +{ + int i, restore; + unsigned char EDID_DATA[18]; + + DEBUG_MSG(KERN_INFO "\n dvi_get_panel_size_from_DDCv1 \n"); + + restore = tmds_chip->tmds_chip_slave_addr; + tmds_chip->tmds_chip_slave_addr = 0xA0; + for (i = 0x25; i < 0x6D; i++) { + switch (i) { + case 0x36: + case 0x48: + case 0x5A: + case 0x6C: + tmds_register_read_bytes(i, EDID_DATA, 10); + if (!(EDID_DATA[0] || EDID_DATA[1])) { + /* The first two byte must be zero. */ + if (EDID_DATA[3] == 0xFD) { + /* To get max pixel clock. */ + tmds_setting->max_pixel_clock = + EDID_DATA[9] * 10; + } + } + break; + + default: + break; + } + } + + DEBUG_MSG(KERN_INFO "DVI max pixelclock = %d\n", + tmds_setting->max_pixel_clock); + tmds_chip->tmds_chip_slave_addr = restore; +} + +/* If Disable DVI, turn off pad */ +void viafb_dvi_disable(void) +{ + if (viaparinfo->chip_info-> + tmds_chip_info.output_interface == INTERFACE_TMDS) + /* Turn off TMDS power. */ + viafb_write_reg(CRD2, VIACR, + viafb_read_reg(VIACR, CRD2) | 0x08); +} + +static void dvi_patch_skew_dvp0(void) +{ + /* Reset data driving first: */ + viafb_write_reg_mask(SR1B, VIASR, 0, BIT1); + viafb_write_reg_mask(SR2A, VIASR, 0, BIT4); + + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_P4M890: + { + if ((viaparinfo->tmds_setting_info->h_active == 1600) && + (viaparinfo->tmds_setting_info->v_active == + 1200)) + viafb_write_reg_mask(CR96, VIACR, 0x03, + BIT0 + BIT1 + BIT2); + else + viafb_write_reg_mask(CR96, VIACR, 0x07, + BIT0 + BIT1 + BIT2); + break; + } + + case UNICHROME_P4M900: + { + viafb_write_reg_mask(CR96, VIACR, 0x07, + BIT0 + BIT1 + BIT2 + BIT3); + viafb_write_reg_mask(SR1B, VIASR, 0x02, BIT1); + viafb_write_reg_mask(SR2A, VIASR, 0x10, BIT4); + break; + } + + default: + { + break; + } + } +} + +static void dvi_patch_skew_dvp_low(void) +{ + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_K8M890: + { + viafb_write_reg_mask(CR99, VIACR, 0x03, BIT0 + BIT1); + break; + } + + case UNICHROME_P4M900: + { + viafb_write_reg_mask(CR99, VIACR, 0x08, + BIT0 + BIT1 + BIT2 + BIT3); + break; + } + + case UNICHROME_P4M890: + { + viafb_write_reg_mask(CR99, VIACR, 0x0F, + BIT0 + BIT1 + BIT2 + BIT3); + break; + } + + default: + { + break; + } + } +} + +/* If Enable DVI, turn off pad */ +void viafb_dvi_enable(void) +{ + u8 data; + + switch (viaparinfo->chip_info->tmds_chip_info.output_interface) { + case INTERFACE_DVP0: + viafb_write_reg_mask(CR6B, VIACR, 0x01, BIT0); + viafb_write_reg_mask(CR6C, VIACR, 0x21, BIT0 + BIT5); + dvi_patch_skew_dvp0(); + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + tmds_register_write(0x88, 0x3b); + else + /*clear CR91[5] to direct on display period + in the secondary diplay path */ + via_write_reg_mask(VIACR, 0x91, 0x00, 0x20); + break; + + case INTERFACE_DVP1: + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + viafb_write_reg_mask(CR93, VIACR, 0x21, BIT0 + BIT5); + + /*fix dvi cann't be enabled with MB VT5718C4 - Al Zhang */ + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + tmds_register_write(0x88, 0x3b); + else + /*clear CR91[5] to direct on display period + in the secondary diplay path */ + via_write_reg_mask(VIACR, 0x91, 0x00, 0x20); + + /*fix DVI cannot enable on EPIA-M board */ + if (viafb_platform_epia_dvi == 1) { + viafb_write_reg_mask(CR91, VIACR, 0x1f, 0x1f); + viafb_write_reg_mask(CR88, VIACR, 0x00, BIT6 + BIT0); + if (viafb_bus_width == 24) { + if (viafb_device_lcd_dualedge == 1) + data = 0x3F; + else + data = 0x37; + viafb_i2c_writebyte(viaparinfo->chip_info-> + tmds_chip_info.i2c_port, + viaparinfo->chip_info-> + tmds_chip_info.tmds_chip_slave_addr, + 0x08, data); + } + } + break; + + case INTERFACE_DFP_HIGH: + if (viaparinfo->chip_info->gfx_chip_name != UNICHROME_CLE266) + via_write_reg_mask(VIACR, CR97, 0x03, 0x03); + + via_write_reg_mask(VIACR, 0x91, 0x00, 0x20); + break; + + case INTERFACE_DFP_LOW: + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + break; + + dvi_patch_skew_dvp_low(); + via_write_reg_mask(VIACR, 0x91, 0x00, 0x20); + break; + + case INTERFACE_TMDS: + /* Turn on Display period in the panel path. */ + viafb_write_reg_mask(CR91, VIACR, 0, BIT7); + + /* Turn on TMDS power. */ + viafb_write_reg_mask(CRD2, VIACR, 0, BIT3); + break; + } + + if (viaparinfo->tmds_setting_info->iga_path == IGA2) { + /* Disable LCD Scaling */ + viafb_write_reg_mask(CR79, VIACR, 0x00, BIT0); + } +} diff --git a/drivers/video/fbdev/via/dvi.h b/drivers/video/fbdev/via/dvi.h new file mode 100644 index 000000000000..4c6bfba57d11 --- /dev/null +++ b/drivers/video/fbdev/via/dvi.h @@ -0,0 +1,65 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __DVI_H__ +#define __DVI_H__ + +/*Definition TMDS Device ID register*/ +#define VT1632_DEVICE_ID_REG 0x02 +#define VT1632_DEVICE_ID 0x92 + +#define GET_DVI_SIZE_BY_SYSTEM_BIOS 0x01 +#define GET_DVI_SIZE_BY_VGA_BIOS 0x02 +#define GET_DVI_SZIE_BY_HW_STRAPPING 0x03 + +/* Definition DVI Panel ID*/ +/* Resolution: 640x480, Channel: single, Dithering: Enable */ +#define DVI_PANEL_ID0_640X480 0x00 +/* Resolution: 800x600, Channel: single, Dithering: Enable */ +#define DVI_PANEL_ID1_800x600 0x01 +/* Resolution: 1024x768, Channel: single, Dithering: Enable */ +#define DVI_PANEL_ID1_1024x768 0x02 +/* Resolution: 1280x768, Channel: single, Dithering: Enable */ +#define DVI_PANEL_ID1_1280x768 0x03 +/* Resolution: 1280x1024, Channel: dual, Dithering: Enable */ +#define DVI_PANEL_ID1_1280x1024 0x04 +/* Resolution: 1400x1050, Channel: dual, Dithering: Enable */ +#define DVI_PANEL_ID1_1400x1050 0x05 +/* Resolution: 1600x1200, Channel: dual, Dithering: Enable */ +#define DVI_PANEL_ID1_1600x1200 0x06 + +/* Define the version of EDID*/ +#define EDID_VERSION_1 1 +#define EDID_VERSION_2 2 + +#define DEV_CONNECT_DVI 0x01 +#define DEV_CONNECT_HDMI 0x02 + +int viafb_dvi_sense(void); +void viafb_dvi_disable(void); +void viafb_dvi_enable(void); +bool viafb_tmds_trasmitter_identify(void); +void viafb_init_dvi_size(struct tmds_chip_information *tmds_chip, + struct tmds_setting_information *tmds_setting); +void viafb_dvi_set_mode(const struct fb_var_screeninfo *var, + u16 cxres, u16 cyres, int iga); + +#endif /* __DVI_H__ */ diff --git a/drivers/video/fbdev/via/global.c b/drivers/video/fbdev/via/global.c new file mode 100644 index 000000000000..3102171c1674 --- /dev/null +++ b/drivers/video/fbdev/via/global.c @@ -0,0 +1,50 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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 "global.h" +int viafb_platform_epia_dvi = STATE_OFF; +int viafb_device_lcd_dualedge = STATE_OFF; +int viafb_bus_width = 12; +int viafb_display_hardware_layout = HW_LAYOUT_LCD_DVI; +int viafb_DeviceStatus = CRT_Device; +int viafb_hotplug; +int viafb_refresh = 60; +int viafb_refresh1 = 60; +int viafb_lcd_dsp_method = LCD_EXPANDSION; +int viafb_lcd_mode = LCD_OPENLDI; +int viafb_CRT_ON = 1; +int viafb_DVI_ON; +int viafb_LCD_ON ; +int viafb_LCD2_ON; +int viafb_SAMM_ON; +int viafb_dual_fb; +unsigned int viafb_second_xres = 640; +unsigned int viafb_second_yres = 480; +int viafb_hotplug_Xres = 640; +int viafb_hotplug_Yres = 480; +int viafb_hotplug_bpp = 32; +int viafb_hotplug_refresh = 60; +int viafb_primary_dev = None_Device; +int viafb_lcd_panel_id = LCD_PANEL_ID_MAXIMUM + 1; +struct fb_info *viafbinfo; +struct fb_info *viafbinfo1; +struct viafb_par *viaparinfo; +struct viafb_par *viaparinfo1; + diff --git a/drivers/video/fbdev/via/global.h b/drivers/video/fbdev/via/global.h new file mode 100644 index 000000000000..275dbbbd6b81 --- /dev/null +++ b/drivers/video/fbdev/via/global.h @@ -0,0 +1,80 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __GLOBAL_H__ +#define __GLOBAL_H__ + +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/console.h> +#include <linux/timer.h> + +#include "debug.h" + +#include "viafbdev.h" +#include "chip.h" +#include "accel.h" +#include "share.h" +#include "dvi.h" +#include "viamode.h" +#include "hw.h" + +#include "lcd.h" +#include "ioctl.h" +#include "via_utility.h" +#include "vt1636.h" +#include "tblDPASetting.h" + +/* External struct*/ + +extern int viafb_platform_epia_dvi; +extern int viafb_device_lcd_dualedge; +extern int viafb_bus_width; +extern int viafb_display_hardware_layout; +extern struct offset offset_reg; +extern struct viafb_par *viaparinfo; +extern struct viafb_par *viaparinfo1; +extern struct fb_info *viafbinfo; +extern struct fb_info *viafbinfo1; +extern int viafb_DeviceStatus; +extern int viafb_refresh; +extern int viafb_refresh1; +extern int viafb_lcd_dsp_method; +extern int viafb_lcd_mode; + +extern int viafb_CRT_ON; +extern unsigned int viafb_second_xres; +extern unsigned int viafb_second_yres; +extern int viafb_hotplug_Xres; +extern int viafb_hotplug_Yres; +extern int viafb_hotplug_bpp; +extern int viafb_hotplug_refresh; +extern int viafb_primary_dev; + +extern int viafb_lcd_panel_id; + +#endif /* __GLOBAL_H__ */ diff --git a/drivers/video/fbdev/via/hw.c b/drivers/video/fbdev/via/hw.c new file mode 100644 index 000000000000..22450908306c --- /dev/null +++ b/drivers/video/fbdev/via/hw.c @@ -0,0 +1,2134 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/via-core.h> +#include <asm/olpc.h> +#include "global.h" +#include "via_clock.h" + +static struct pll_limit cle266_pll_limits[] = { + {19, 19, 4, 0}, + {26, 102, 5, 0}, + {53, 112, 6, 0}, + {41, 100, 7, 0}, + {83, 108, 8, 0}, + {87, 118, 9, 0}, + {95, 115, 12, 0}, + {108, 108, 13, 0}, + {83, 83, 17, 0}, + {67, 98, 20, 0}, + {121, 121, 24, 0}, + {99, 99, 29, 0}, + {33, 33, 3, 1}, + {15, 23, 4, 1}, + {37, 121, 5, 1}, + {82, 82, 6, 1}, + {31, 84, 7, 1}, + {83, 83, 8, 1}, + {76, 127, 9, 1}, + {33, 121, 4, 2}, + {91, 118, 5, 2}, + {83, 109, 6, 2}, + {90, 90, 7, 2}, + {93, 93, 2, 3}, + {53, 53, 3, 3}, + {73, 117, 4, 3}, + {101, 127, 5, 3}, + {99, 99, 7, 3} +}; + +static struct pll_limit k800_pll_limits[] = { + {22, 22, 2, 0}, + {28, 28, 3, 0}, + {81, 112, 3, 1}, + {86, 166, 4, 1}, + {109, 153, 5, 1}, + {66, 116, 3, 2}, + {93, 137, 4, 2}, + {117, 208, 5, 2}, + {30, 30, 2, 3}, + {69, 125, 3, 3}, + {89, 161, 4, 3}, + {121, 208, 5, 3}, + {66, 66, 2, 4}, + {85, 85, 3, 4}, + {141, 161, 4, 4}, + {177, 177, 5, 4} +}; + +static struct pll_limit cx700_pll_limits[] = { + {98, 98, 3, 1}, + {86, 86, 4, 1}, + {109, 208, 5, 1}, + {68, 68, 2, 2}, + {95, 116, 3, 2}, + {93, 166, 4, 2}, + {110, 206, 5, 2}, + {174, 174, 7, 2}, + {82, 109, 3, 3}, + {117, 161, 4, 3}, + {112, 208, 5, 3}, + {141, 202, 5, 4} +}; + +static struct pll_limit vx855_pll_limits[] = { + {86, 86, 4, 1}, + {108, 208, 5, 1}, + {110, 208, 5, 2}, + {83, 112, 3, 3}, + {103, 161, 4, 3}, + {112, 209, 5, 3}, + {142, 161, 4, 4}, + {141, 176, 5, 4} +}; + +/* according to VIA Technologies these values are based on experiment */ +static struct io_reg scaling_parameters[] = { + {VIACR, CR7A, 0xFF, 0x01}, /* LCD Scaling Parameter 1 */ + {VIACR, CR7B, 0xFF, 0x02}, /* LCD Scaling Parameter 2 */ + {VIACR, CR7C, 0xFF, 0x03}, /* LCD Scaling Parameter 3 */ + {VIACR, CR7D, 0xFF, 0x04}, /* LCD Scaling Parameter 4 */ + {VIACR, CR7E, 0xFF, 0x07}, /* LCD Scaling Parameter 5 */ + {VIACR, CR7F, 0xFF, 0x0A}, /* LCD Scaling Parameter 6 */ + {VIACR, CR80, 0xFF, 0x0D}, /* LCD Scaling Parameter 7 */ + {VIACR, CR81, 0xFF, 0x13}, /* LCD Scaling Parameter 8 */ + {VIACR, CR82, 0xFF, 0x16}, /* LCD Scaling Parameter 9 */ + {VIACR, CR83, 0xFF, 0x19}, /* LCD Scaling Parameter 10 */ + {VIACR, CR84, 0xFF, 0x1C}, /* LCD Scaling Parameter 11 */ + {VIACR, CR85, 0xFF, 0x1D}, /* LCD Scaling Parameter 12 */ + {VIACR, CR86, 0xFF, 0x1E}, /* LCD Scaling Parameter 13 */ + {VIACR, CR87, 0xFF, 0x1F}, /* LCD Scaling Parameter 14 */ +}; + +static struct io_reg common_vga[] = { + {VIACR, CR07, 0x10, 0x10}, /* [0] vertical total (bit 8) + [1] vertical display end (bit 8) + [2] vertical retrace start (bit 8) + [3] start vertical blanking (bit 8) + [4] line compare (bit 8) + [5] vertical total (bit 9) + [6] vertical display end (bit 9) + [7] vertical retrace start (bit 9) */ + {VIACR, CR08, 0xFF, 0x00}, /* [0-4] preset row scan + [5-6] byte panning */ + {VIACR, CR09, 0xDF, 0x40}, /* [0-4] max scan line + [5] start vertical blanking (bit 9) + [6] line compare (bit 9) + [7] scan doubling */ + {VIACR, CR0A, 0xFF, 0x1E}, /* [0-4] cursor start + [5] cursor disable */ + {VIACR, CR0B, 0xFF, 0x00}, /* [0-4] cursor end + [5-6] cursor skew */ + {VIACR, CR0E, 0xFF, 0x00}, /* [0-7] cursor location (high) */ + {VIACR, CR0F, 0xFF, 0x00}, /* [0-7] cursor location (low) */ + {VIACR, CR11, 0xF0, 0x80}, /* [0-3] vertical retrace end + [6] memory refresh bandwidth + [7] CRTC register protect enable */ + {VIACR, CR14, 0xFF, 0x00}, /* [0-4] underline location + [5] divide memory address clock by 4 + [6] double word addressing */ + {VIACR, CR17, 0xFF, 0x63}, /* [0-1] mapping of display address 13-14 + [2] divide scan line clock by 2 + [3] divide memory address clock by 2 + [5] address wrap + [6] byte mode select + [7] sync enable */ + {VIACR, CR18, 0xFF, 0xFF}, /* [0-7] line compare */ +}; + +static struct fifo_depth_select display_fifo_depth_reg = { + /* IGA1 FIFO Depth_Select */ + {IGA1_FIFO_DEPTH_SELECT_REG_NUM, {{SR17, 0, 7} } }, + /* IGA2 FIFO Depth_Select */ + {IGA2_FIFO_DEPTH_SELECT_REG_NUM, + {{CR68, 4, 7}, {CR94, 7, 7}, {CR95, 7, 7} } } +}; + +static struct fifo_threshold_select fifo_threshold_select_reg = { + /* IGA1 FIFO Threshold Select */ + {IGA1_FIFO_THRESHOLD_REG_NUM, {{SR16, 0, 5}, {SR16, 7, 7} } }, + /* IGA2 FIFO Threshold Select */ + {IGA2_FIFO_THRESHOLD_REG_NUM, {{CR68, 0, 3}, {CR95, 4, 6} } } +}; + +static struct fifo_high_threshold_select fifo_high_threshold_select_reg = { + /* IGA1 FIFO High Threshold Select */ + {IGA1_FIFO_HIGH_THRESHOLD_REG_NUM, {{SR18, 0, 5}, {SR18, 7, 7} } }, + /* IGA2 FIFO High Threshold Select */ + {IGA2_FIFO_HIGH_THRESHOLD_REG_NUM, {{CR92, 0, 3}, {CR95, 0, 2} } } +}; + +static struct display_queue_expire_num display_queue_expire_num_reg = { + /* IGA1 Display Queue Expire Num */ + {IGA1_DISPLAY_QUEUE_EXPIRE_NUM_REG_NUM, {{SR22, 0, 4} } }, + /* IGA2 Display Queue Expire Num */ + {IGA2_DISPLAY_QUEUE_EXPIRE_NUM_REG_NUM, {{CR94, 0, 6} } } +}; + +/* Definition Fetch Count Registers*/ +static struct fetch_count fetch_count_reg = { + /* IGA1 Fetch Count Register */ + {IGA1_FETCH_COUNT_REG_NUM, {{SR1C, 0, 7}, {SR1D, 0, 1} } }, + /* IGA2 Fetch Count Register */ + {IGA2_FETCH_COUNT_REG_NUM, {{CR65, 0, 7}, {CR67, 2, 3} } } +}; + +static struct rgbLUT palLUT_table[] = { + /* {R,G,B} */ + /* Index 0x00~0x03 */ + {0x00, 0x00, 0x00}, {0x00, 0x00, 0x2A}, {0x00, 0x2A, 0x00}, {0x00, + 0x2A, + 0x2A}, + /* Index 0x04~0x07 */ + {0x2A, 0x00, 0x00}, {0x2A, 0x00, 0x2A}, {0x2A, 0x15, 0x00}, {0x2A, + 0x2A, + 0x2A}, + /* Index 0x08~0x0B */ + {0x15, 0x15, 0x15}, {0x15, 0x15, 0x3F}, {0x15, 0x3F, 0x15}, {0x15, + 0x3F, + 0x3F}, + /* Index 0x0C~0x0F */ + {0x3F, 0x15, 0x15}, {0x3F, 0x15, 0x3F}, {0x3F, 0x3F, 0x15}, {0x3F, + 0x3F, + 0x3F}, + /* Index 0x10~0x13 */ + {0x00, 0x00, 0x00}, {0x05, 0x05, 0x05}, {0x08, 0x08, 0x08}, {0x0B, + 0x0B, + 0x0B}, + /* Index 0x14~0x17 */ + {0x0E, 0x0E, 0x0E}, {0x11, 0x11, 0x11}, {0x14, 0x14, 0x14}, {0x18, + 0x18, + 0x18}, + /* Index 0x18~0x1B */ + {0x1C, 0x1C, 0x1C}, {0x20, 0x20, 0x20}, {0x24, 0x24, 0x24}, {0x28, + 0x28, + 0x28}, + /* Index 0x1C~0x1F */ + {0x2D, 0x2D, 0x2D}, {0x32, 0x32, 0x32}, {0x38, 0x38, 0x38}, {0x3F, + 0x3F, + 0x3F}, + /* Index 0x20~0x23 */ + {0x00, 0x00, 0x3F}, {0x10, 0x00, 0x3F}, {0x1F, 0x00, 0x3F}, {0x2F, + 0x00, + 0x3F}, + /* Index 0x24~0x27 */ + {0x3F, 0x00, 0x3F}, {0x3F, 0x00, 0x2F}, {0x3F, 0x00, 0x1F}, {0x3F, + 0x00, + 0x10}, + /* Index 0x28~0x2B */ + {0x3F, 0x00, 0x00}, {0x3F, 0x10, 0x00}, {0x3F, 0x1F, 0x00}, {0x3F, + 0x2F, + 0x00}, + /* Index 0x2C~0x2F */ + {0x3F, 0x3F, 0x00}, {0x2F, 0x3F, 0x00}, {0x1F, 0x3F, 0x00}, {0x10, + 0x3F, + 0x00}, + /* Index 0x30~0x33 */ + {0x00, 0x3F, 0x00}, {0x00, 0x3F, 0x10}, {0x00, 0x3F, 0x1F}, {0x00, + 0x3F, + 0x2F}, + /* Index 0x34~0x37 */ + {0x00, 0x3F, 0x3F}, {0x00, 0x2F, 0x3F}, {0x00, 0x1F, 0x3F}, {0x00, + 0x10, + 0x3F}, + /* Index 0x38~0x3B */ + {0x1F, 0x1F, 0x3F}, {0x27, 0x1F, 0x3F}, {0x2F, 0x1F, 0x3F}, {0x37, + 0x1F, + 0x3F}, + /* Index 0x3C~0x3F */ + {0x3F, 0x1F, 0x3F}, {0x3F, 0x1F, 0x37}, {0x3F, 0x1F, 0x2F}, {0x3F, + 0x1F, + 0x27}, + /* Index 0x40~0x43 */ + {0x3F, 0x1F, 0x1F}, {0x3F, 0x27, 0x1F}, {0x3F, 0x2F, 0x1F}, {0x3F, + 0x3F, + 0x1F}, + /* Index 0x44~0x47 */ + {0x3F, 0x3F, 0x1F}, {0x37, 0x3F, 0x1F}, {0x2F, 0x3F, 0x1F}, {0x27, + 0x3F, + 0x1F}, + /* Index 0x48~0x4B */ + {0x1F, 0x3F, 0x1F}, {0x1F, 0x3F, 0x27}, {0x1F, 0x3F, 0x2F}, {0x1F, + 0x3F, + 0x37}, + /* Index 0x4C~0x4F */ + {0x1F, 0x3F, 0x3F}, {0x1F, 0x37, 0x3F}, {0x1F, 0x2F, 0x3F}, {0x1F, + 0x27, + 0x3F}, + /* Index 0x50~0x53 */ + {0x2D, 0x2D, 0x3F}, {0x31, 0x2D, 0x3F}, {0x36, 0x2D, 0x3F}, {0x3A, + 0x2D, + 0x3F}, + /* Index 0x54~0x57 */ + {0x3F, 0x2D, 0x3F}, {0x3F, 0x2D, 0x3A}, {0x3F, 0x2D, 0x36}, {0x3F, + 0x2D, + 0x31}, + /* Index 0x58~0x5B */ + {0x3F, 0x2D, 0x2D}, {0x3F, 0x31, 0x2D}, {0x3F, 0x36, 0x2D}, {0x3F, + 0x3A, + 0x2D}, + /* Index 0x5C~0x5F */ + {0x3F, 0x3F, 0x2D}, {0x3A, 0x3F, 0x2D}, {0x36, 0x3F, 0x2D}, {0x31, + 0x3F, + 0x2D}, + /* Index 0x60~0x63 */ + {0x2D, 0x3F, 0x2D}, {0x2D, 0x3F, 0x31}, {0x2D, 0x3F, 0x36}, {0x2D, + 0x3F, + 0x3A}, + /* Index 0x64~0x67 */ + {0x2D, 0x3F, 0x3F}, {0x2D, 0x3A, 0x3F}, {0x2D, 0x36, 0x3F}, {0x2D, + 0x31, + 0x3F}, + /* Index 0x68~0x6B */ + {0x00, 0x00, 0x1C}, {0x07, 0x00, 0x1C}, {0x0E, 0x00, 0x1C}, {0x15, + 0x00, + 0x1C}, + /* Index 0x6C~0x6F */ + {0x1C, 0x00, 0x1C}, {0x1C, 0x00, 0x15}, {0x1C, 0x00, 0x0E}, {0x1C, + 0x00, + 0x07}, + /* Index 0x70~0x73 */ + {0x1C, 0x00, 0x00}, {0x1C, 0x07, 0x00}, {0x1C, 0x0E, 0x00}, {0x1C, + 0x15, + 0x00}, + /* Index 0x74~0x77 */ + {0x1C, 0x1C, 0x00}, {0x15, 0x1C, 0x00}, {0x0E, 0x1C, 0x00}, {0x07, + 0x1C, + 0x00}, + /* Index 0x78~0x7B */ + {0x00, 0x1C, 0x00}, {0x00, 0x1C, 0x07}, {0x00, 0x1C, 0x0E}, {0x00, + 0x1C, + 0x15}, + /* Index 0x7C~0x7F */ + {0x00, 0x1C, 0x1C}, {0x00, 0x15, 0x1C}, {0x00, 0x0E, 0x1C}, {0x00, + 0x07, + 0x1C}, + /* Index 0x80~0x83 */ + {0x0E, 0x0E, 0x1C}, {0x11, 0x0E, 0x1C}, {0x15, 0x0E, 0x1C}, {0x18, + 0x0E, + 0x1C}, + /* Index 0x84~0x87 */ + {0x1C, 0x0E, 0x1C}, {0x1C, 0x0E, 0x18}, {0x1C, 0x0E, 0x15}, {0x1C, + 0x0E, + 0x11}, + /* Index 0x88~0x8B */ + {0x1C, 0x0E, 0x0E}, {0x1C, 0x11, 0x0E}, {0x1C, 0x15, 0x0E}, {0x1C, + 0x18, + 0x0E}, + /* Index 0x8C~0x8F */ + {0x1C, 0x1C, 0x0E}, {0x18, 0x1C, 0x0E}, {0x15, 0x1C, 0x0E}, {0x11, + 0x1C, + 0x0E}, + /* Index 0x90~0x93 */ + {0x0E, 0x1C, 0x0E}, {0x0E, 0x1C, 0x11}, {0x0E, 0x1C, 0x15}, {0x0E, + 0x1C, + 0x18}, + /* Index 0x94~0x97 */ + {0x0E, 0x1C, 0x1C}, {0x0E, 0x18, 0x1C}, {0x0E, 0x15, 0x1C}, {0x0E, + 0x11, + 0x1C}, + /* Index 0x98~0x9B */ + {0x14, 0x14, 0x1C}, {0x16, 0x14, 0x1C}, {0x18, 0x14, 0x1C}, {0x1A, + 0x14, + 0x1C}, + /* Index 0x9C~0x9F */ + {0x1C, 0x14, 0x1C}, {0x1C, 0x14, 0x1A}, {0x1C, 0x14, 0x18}, {0x1C, + 0x14, + 0x16}, + /* Index 0xA0~0xA3 */ + {0x1C, 0x14, 0x14}, {0x1C, 0x16, 0x14}, {0x1C, 0x18, 0x14}, {0x1C, + 0x1A, + 0x14}, + /* Index 0xA4~0xA7 */ + {0x1C, 0x1C, 0x14}, {0x1A, 0x1C, 0x14}, {0x18, 0x1C, 0x14}, {0x16, + 0x1C, + 0x14}, + /* Index 0xA8~0xAB */ + {0x14, 0x1C, 0x14}, {0x14, 0x1C, 0x16}, {0x14, 0x1C, 0x18}, {0x14, + 0x1C, + 0x1A}, + /* Index 0xAC~0xAF */ + {0x14, 0x1C, 0x1C}, {0x14, 0x1A, 0x1C}, {0x14, 0x18, 0x1C}, {0x14, + 0x16, + 0x1C}, + /* Index 0xB0~0xB3 */ + {0x00, 0x00, 0x10}, {0x04, 0x00, 0x10}, {0x08, 0x00, 0x10}, {0x0C, + 0x00, + 0x10}, + /* Index 0xB4~0xB7 */ + {0x10, 0x00, 0x10}, {0x10, 0x00, 0x0C}, {0x10, 0x00, 0x08}, {0x10, + 0x00, + 0x04}, + /* Index 0xB8~0xBB */ + {0x10, 0x00, 0x00}, {0x10, 0x04, 0x00}, {0x10, 0x08, 0x00}, {0x10, + 0x0C, + 0x00}, + /* Index 0xBC~0xBF */ + {0x10, 0x10, 0x00}, {0x0C, 0x10, 0x00}, {0x08, 0x10, 0x00}, {0x04, + 0x10, + 0x00}, + /* Index 0xC0~0xC3 */ + {0x00, 0x10, 0x00}, {0x00, 0x10, 0x04}, {0x00, 0x10, 0x08}, {0x00, + 0x10, + 0x0C}, + /* Index 0xC4~0xC7 */ + {0x00, 0x10, 0x10}, {0x00, 0x0C, 0x10}, {0x00, 0x08, 0x10}, {0x00, + 0x04, + 0x10}, + /* Index 0xC8~0xCB */ + {0x08, 0x08, 0x10}, {0x0A, 0x08, 0x10}, {0x0C, 0x08, 0x10}, {0x0E, + 0x08, + 0x10}, + /* Index 0xCC~0xCF */ + {0x10, 0x08, 0x10}, {0x10, 0x08, 0x0E}, {0x10, 0x08, 0x0C}, {0x10, + 0x08, + 0x0A}, + /* Index 0xD0~0xD3 */ + {0x10, 0x08, 0x08}, {0x10, 0x0A, 0x08}, {0x10, 0x0C, 0x08}, {0x10, + 0x0E, + 0x08}, + /* Index 0xD4~0xD7 */ + {0x10, 0x10, 0x08}, {0x0E, 0x10, 0x08}, {0x0C, 0x10, 0x08}, {0x0A, + 0x10, + 0x08}, + /* Index 0xD8~0xDB */ + {0x08, 0x10, 0x08}, {0x08, 0x10, 0x0A}, {0x08, 0x10, 0x0C}, {0x08, + 0x10, + 0x0E}, + /* Index 0xDC~0xDF */ + {0x08, 0x10, 0x10}, {0x08, 0x0E, 0x10}, {0x08, 0x0C, 0x10}, {0x08, + 0x0A, + 0x10}, + /* Index 0xE0~0xE3 */ + {0x0B, 0x0B, 0x10}, {0x0C, 0x0B, 0x10}, {0x0D, 0x0B, 0x10}, {0x0F, + 0x0B, + 0x10}, + /* Index 0xE4~0xE7 */ + {0x10, 0x0B, 0x10}, {0x10, 0x0B, 0x0F}, {0x10, 0x0B, 0x0D}, {0x10, + 0x0B, + 0x0C}, + /* Index 0xE8~0xEB */ + {0x10, 0x0B, 0x0B}, {0x10, 0x0C, 0x0B}, {0x10, 0x0D, 0x0B}, {0x10, + 0x0F, + 0x0B}, + /* Index 0xEC~0xEF */ + {0x10, 0x10, 0x0B}, {0x0F, 0x10, 0x0B}, {0x0D, 0x10, 0x0B}, {0x0C, + 0x10, + 0x0B}, + /* Index 0xF0~0xF3 */ + {0x0B, 0x10, 0x0B}, {0x0B, 0x10, 0x0C}, {0x0B, 0x10, 0x0D}, {0x0B, + 0x10, + 0x0F}, + /* Index 0xF4~0xF7 */ + {0x0B, 0x10, 0x10}, {0x0B, 0x0F, 0x10}, {0x0B, 0x0D, 0x10}, {0x0B, + 0x0C, + 0x10}, + /* Index 0xF8~0xFB */ + {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, + 0x00, + 0x00}, + /* Index 0xFC~0xFF */ + {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, + 0x00, + 0x00} +}; + +static struct via_device_mapping device_mapping[] = { + {VIA_LDVP0, "LDVP0"}, + {VIA_LDVP1, "LDVP1"}, + {VIA_DVP0, "DVP0"}, + {VIA_CRT, "CRT"}, + {VIA_DVP1, "DVP1"}, + {VIA_LVDS1, "LVDS1"}, + {VIA_LVDS2, "LVDS2"} +}; + +/* structure with function pointers to support clock control */ +static struct via_clock clock; + +static void load_fix_bit_crtc_reg(void); +static void init_gfx_chip_info(int chip_type); +static void init_tmds_chip_info(void); +static void init_lvds_chip_info(void); +static void device_screen_off(void); +static void device_screen_on(void); +static void set_display_channel(void); +static void device_off(void); +static void device_on(void); +static void enable_second_display_channel(void); +static void disable_second_display_channel(void); + +void viafb_lock_crt(void) +{ + viafb_write_reg_mask(CR11, VIACR, BIT7, BIT7); +} + +void viafb_unlock_crt(void) +{ + viafb_write_reg_mask(CR11, VIACR, 0, BIT7); + viafb_write_reg_mask(CR47, VIACR, 0, BIT0); +} + +static void write_dac_reg(u8 index, u8 r, u8 g, u8 b) +{ + outb(index, LUT_INDEX_WRITE); + outb(r, LUT_DATA); + outb(g, LUT_DATA); + outb(b, LUT_DATA); +} + +static u32 get_dvi_devices(int output_interface) +{ + switch (output_interface) { + case INTERFACE_DVP0: + return VIA_DVP0 | VIA_LDVP0; + + case INTERFACE_DVP1: + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + return VIA_LDVP1; + else + return VIA_DVP1; + + case INTERFACE_DFP_HIGH: + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + return 0; + else + return VIA_LVDS2 | VIA_DVP0; + + case INTERFACE_DFP_LOW: + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + return 0; + else + return VIA_DVP1 | VIA_LVDS1; + + case INTERFACE_TMDS: + return VIA_LVDS1; + } + + return 0; +} + +static u32 get_lcd_devices(int output_interface) +{ + switch (output_interface) { + case INTERFACE_DVP0: + return VIA_DVP0; + + case INTERFACE_DVP1: + return VIA_DVP1; + + case INTERFACE_DFP_HIGH: + return VIA_LVDS2 | VIA_DVP0; + + case INTERFACE_DFP_LOW: + return VIA_LVDS1 | VIA_DVP1; + + case INTERFACE_DFP: + return VIA_LVDS1 | VIA_LVDS2; + + case INTERFACE_LVDS0: + case INTERFACE_LVDS0LVDS1: + return VIA_LVDS1; + + case INTERFACE_LVDS1: + return VIA_LVDS2; + } + + return 0; +} + +/*Set IGA path for each device*/ +void viafb_set_iga_path(void) +{ + int crt_iga_path = 0; + + if (viafb_SAMM_ON == 1) { + if (viafb_CRT_ON) { + if (viafb_primary_dev == CRT_Device) + crt_iga_path = IGA1; + else + crt_iga_path = IGA2; + } + + if (viafb_DVI_ON) { + if (viafb_primary_dev == DVI_Device) + viaparinfo->tmds_setting_info->iga_path = IGA1; + else + viaparinfo->tmds_setting_info->iga_path = IGA2; + } + + if (viafb_LCD_ON) { + if (viafb_primary_dev == LCD_Device) { + if (viafb_dual_fb && + (viaparinfo->chip_info->gfx_chip_name == + UNICHROME_CLE266)) { + viaparinfo-> + lvds_setting_info->iga_path = IGA2; + crt_iga_path = IGA1; + viaparinfo-> + tmds_setting_info->iga_path = IGA1; + } else + viaparinfo-> + lvds_setting_info->iga_path = IGA1; + } else { + viaparinfo->lvds_setting_info->iga_path = IGA2; + } + } + if (viafb_LCD2_ON) { + if (LCD2_Device == viafb_primary_dev) + viaparinfo->lvds_setting_info2->iga_path = IGA1; + else + viaparinfo->lvds_setting_info2->iga_path = IGA2; + } + } else { + viafb_SAMM_ON = 0; + + if (viafb_CRT_ON && viafb_LCD_ON) { + crt_iga_path = IGA1; + viaparinfo->lvds_setting_info->iga_path = IGA2; + } else if (viafb_CRT_ON && viafb_DVI_ON) { + crt_iga_path = IGA1; + viaparinfo->tmds_setting_info->iga_path = IGA2; + } else if (viafb_LCD_ON && viafb_DVI_ON) { + viaparinfo->tmds_setting_info->iga_path = IGA1; + viaparinfo->lvds_setting_info->iga_path = IGA2; + } else if (viafb_LCD_ON && viafb_LCD2_ON) { + viaparinfo->lvds_setting_info->iga_path = IGA2; + viaparinfo->lvds_setting_info2->iga_path = IGA2; + } else if (viafb_CRT_ON) { + crt_iga_path = IGA1; + } else if (viafb_LCD_ON) { + viaparinfo->lvds_setting_info->iga_path = IGA2; + } else if (viafb_DVI_ON) { + viaparinfo->tmds_setting_info->iga_path = IGA1; + } + } + + viaparinfo->shared->iga1_devices = 0; + viaparinfo->shared->iga2_devices = 0; + if (viafb_CRT_ON) { + if (crt_iga_path == IGA1) + viaparinfo->shared->iga1_devices |= VIA_CRT; + else + viaparinfo->shared->iga2_devices |= VIA_CRT; + } + + if (viafb_DVI_ON) { + if (viaparinfo->tmds_setting_info->iga_path == IGA1) + viaparinfo->shared->iga1_devices |= get_dvi_devices( + viaparinfo->chip_info-> + tmds_chip_info.output_interface); + else + viaparinfo->shared->iga2_devices |= get_dvi_devices( + viaparinfo->chip_info-> + tmds_chip_info.output_interface); + } + + if (viafb_LCD_ON) { + if (viaparinfo->lvds_setting_info->iga_path == IGA1) + viaparinfo->shared->iga1_devices |= get_lcd_devices( + viaparinfo->chip_info-> + lvds_chip_info.output_interface); + else + viaparinfo->shared->iga2_devices |= get_lcd_devices( + viaparinfo->chip_info-> + lvds_chip_info.output_interface); + } + + if (viafb_LCD2_ON) { + if (viaparinfo->lvds_setting_info2->iga_path == IGA1) + viaparinfo->shared->iga1_devices |= get_lcd_devices( + viaparinfo->chip_info-> + lvds_chip_info2.output_interface); + else + viaparinfo->shared->iga2_devices |= get_lcd_devices( + viaparinfo->chip_info-> + lvds_chip_info2.output_interface); + } + + /* looks like the OLPC has its display wired to DVP1 and LVDS2 */ + if (machine_is_olpc()) + viaparinfo->shared->iga2_devices = VIA_DVP1 | VIA_LVDS2; +} + +static void set_color_register(u8 index, u8 red, u8 green, u8 blue) +{ + outb(0xFF, 0x3C6); /* bit mask of palette */ + outb(index, 0x3C8); + outb(red, 0x3C9); + outb(green, 0x3C9); + outb(blue, 0x3C9); +} + +void viafb_set_primary_color_register(u8 index, u8 red, u8 green, u8 blue) +{ + viafb_write_reg_mask(0x1A, VIASR, 0x00, 0x01); + set_color_register(index, red, green, blue); +} + +void viafb_set_secondary_color_register(u8 index, u8 red, u8 green, u8 blue) +{ + viafb_write_reg_mask(0x1A, VIASR, 0x01, 0x01); + set_color_register(index, red, green, blue); +} + +static void set_source_common(u8 index, u8 offset, u8 iga) +{ + u8 value, mask = 1 << offset; + + switch (iga) { + case IGA1: + value = 0x00; + break; + case IGA2: + value = mask; + break; + default: + printk(KERN_WARNING "viafb: Unsupported source: %d\n", iga); + return; + } + + via_write_reg_mask(VIACR, index, value, mask); +} + +static void set_crt_source(u8 iga) +{ + u8 value; + + switch (iga) { + case IGA1: + value = 0x00; + break; + case IGA2: + value = 0x40; + break; + default: + printk(KERN_WARNING "viafb: Unsupported source: %d\n", iga); + return; + } + + via_write_reg_mask(VIASR, 0x16, value, 0x40); +} + +static inline void set_ldvp0_source(u8 iga) +{ + set_source_common(0x6C, 7, iga); +} + +static inline void set_ldvp1_source(u8 iga) +{ + set_source_common(0x93, 7, iga); +} + +static inline void set_dvp0_source(u8 iga) +{ + set_source_common(0x96, 4, iga); +} + +static inline void set_dvp1_source(u8 iga) +{ + set_source_common(0x9B, 4, iga); +} + +static inline void set_lvds1_source(u8 iga) +{ + set_source_common(0x99, 4, iga); +} + +static inline void set_lvds2_source(u8 iga) +{ + set_source_common(0x97, 4, iga); +} + +void via_set_source(u32 devices, u8 iga) +{ + if (devices & VIA_LDVP0) + set_ldvp0_source(iga); + if (devices & VIA_LDVP1) + set_ldvp1_source(iga); + if (devices & VIA_DVP0) + set_dvp0_source(iga); + if (devices & VIA_CRT) + set_crt_source(iga); + if (devices & VIA_DVP1) + set_dvp1_source(iga); + if (devices & VIA_LVDS1) + set_lvds1_source(iga); + if (devices & VIA_LVDS2) + set_lvds2_source(iga); +} + +static void set_crt_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x00; + break; + case VIA_STATE_STANDBY: + value = 0x10; + break; + case VIA_STATE_SUSPEND: + value = 0x20; + break; + case VIA_STATE_OFF: + value = 0x30; + break; + default: + return; + } + + via_write_reg_mask(VIACR, 0x36, value, 0x30); +} + +static void set_dvp0_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0xC0; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x1E, value, 0xC0); +} + +static void set_dvp1_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x30; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x1E, value, 0x30); +} + +static void set_lvds1_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x03; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x2A, value, 0x03); +} + +static void set_lvds2_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x0C; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x2A, value, 0x0C); +} + +void via_set_state(u32 devices, u8 state) +{ + /* + TODO: Can we enable/disable these devices? How? + if (devices & VIA_LDVP0) + if (devices & VIA_LDVP1) + */ + if (devices & VIA_DVP0) + set_dvp0_state(state); + if (devices & VIA_CRT) + set_crt_state(state); + if (devices & VIA_DVP1) + set_dvp1_state(state); + if (devices & VIA_LVDS1) + set_lvds1_state(state); + if (devices & VIA_LVDS2) + set_lvds2_state(state); +} + +void via_set_sync_polarity(u32 devices, u8 polarity) +{ + if (polarity & ~(VIA_HSYNC_NEGATIVE | VIA_VSYNC_NEGATIVE)) { + printk(KERN_WARNING "viafb: Unsupported polarity: %d\n", + polarity); + return; + } + + if (devices & VIA_CRT) + via_write_misc_reg_mask(polarity << 6, 0xC0); + if (devices & VIA_DVP1) + via_write_reg_mask(VIACR, 0x9B, polarity << 5, 0x60); + if (devices & VIA_LVDS1) + via_write_reg_mask(VIACR, 0x99, polarity << 5, 0x60); + if (devices & VIA_LVDS2) + via_write_reg_mask(VIACR, 0x97, polarity << 5, 0x60); +} + +u32 via_parse_odev(char *input, char **end) +{ + char *ptr = input; + u32 odev = 0; + bool next = true; + int i, len; + + while (next) { + next = false; + for (i = 0; i < ARRAY_SIZE(device_mapping); i++) { + len = strlen(device_mapping[i].name); + if (!strncmp(ptr, device_mapping[i].name, len)) { + odev |= device_mapping[i].device; + ptr += len; + if (*ptr == ',') { + ptr++; + next = true; + } + } + } + } + + *end = ptr; + return odev; +} + +void via_odev_to_seq(struct seq_file *m, u32 odev) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(device_mapping); i++) { + if (odev & device_mapping[i].device) { + if (count > 0) + seq_putc(m, ','); + + seq_puts(m, device_mapping[i].name); + count++; + } + } + + seq_putc(m, '\n'); +} + +static void load_fix_bit_crtc_reg(void) +{ + viafb_unlock_crt(); + + /* always set to 1 */ + viafb_write_reg_mask(CR03, VIACR, 0x80, BIT7); + /* line compare should set all bits = 1 (extend modes) */ + viafb_write_reg_mask(CR35, VIACR, 0x10, BIT4); + /* line compare should set all bits = 1 (extend modes) */ + viafb_write_reg_mask(CR33, VIACR, 0x06, BIT0 + BIT1 + BIT2); + /*viafb_write_reg_mask(CR32, VIACR, 0x01, BIT0); */ + + viafb_lock_crt(); + + /* If K8M800, enable Prefetch Mode. */ + if ((viaparinfo->chip_info->gfx_chip_name == UNICHROME_K800) + || (viaparinfo->chip_info->gfx_chip_name == UNICHROME_K8M890)) + viafb_write_reg_mask(CR33, VIACR, 0x08, BIT3); + if ((viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) + && (viaparinfo->chip_info->gfx_chip_revision == CLE266_REVISION_AX)) + viafb_write_reg_mask(SR1A, VIASR, 0x02, BIT1); + +} + +void viafb_load_reg(int timing_value, int viafb_load_reg_num, + struct io_register *reg, + int io_type) +{ + int reg_mask; + int bit_num = 0; + int data; + int i, j; + int shift_next_reg; + int start_index, end_index, cr_index; + u16 get_bit; + + for (i = 0; i < viafb_load_reg_num; i++) { + reg_mask = 0; + data = 0; + start_index = reg[i].start_bit; + end_index = reg[i].end_bit; + cr_index = reg[i].io_addr; + + shift_next_reg = bit_num; + for (j = start_index; j <= end_index; j++) { + /*if (bit_num==8) timing_value = timing_value >>8; */ + reg_mask = reg_mask | (BIT0 << j); + get_bit = (timing_value & (BIT0 << bit_num)); + data = + data | ((get_bit >> shift_next_reg) << start_index); + bit_num++; + } + if (io_type == VIACR) + viafb_write_reg_mask(cr_index, VIACR, data, reg_mask); + else + viafb_write_reg_mask(cr_index, VIASR, data, reg_mask); + } + +} + +/* Write Registers */ +void viafb_write_regx(struct io_reg RegTable[], int ItemNum) +{ + int i; + + /*DEBUG_MSG(KERN_INFO "Table Size : %x!!\n",ItemNum ); */ + + for (i = 0; i < ItemNum; i++) + via_write_reg_mask(RegTable[i].port, RegTable[i].index, + RegTable[i].value, RegTable[i].mask); +} + +void viafb_load_fetch_count_reg(int h_addr, int bpp_byte, int set_iga) +{ + int reg_value; + int viafb_load_reg_num; + struct io_register *reg = NULL; + + switch (set_iga) { + case IGA1: + reg_value = IGA1_FETCH_COUNT_FORMULA(h_addr, bpp_byte); + viafb_load_reg_num = fetch_count_reg. + iga1_fetch_count_reg.reg_num; + reg = fetch_count_reg.iga1_fetch_count_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIASR); + break; + case IGA2: + reg_value = IGA2_FETCH_COUNT_FORMULA(h_addr, bpp_byte); + viafb_load_reg_num = fetch_count_reg. + iga2_fetch_count_reg.reg_num; + reg = fetch_count_reg.iga2_fetch_count_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIACR); + break; + } + +} + +void viafb_load_FIFO_reg(int set_iga, int hor_active, int ver_active) +{ + int reg_value; + int viafb_load_reg_num; + struct io_register *reg = NULL; + int iga1_fifo_max_depth = 0, iga1_fifo_threshold = + 0, iga1_fifo_high_threshold = 0, iga1_display_queue_expire_num = 0; + int iga2_fifo_max_depth = 0, iga2_fifo_threshold = + 0, iga2_fifo_high_threshold = 0, iga2_display_queue_expire_num = 0; + + if (set_iga == IGA1) { + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_K800) { + iga1_fifo_max_depth = K800_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = K800_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + K800_IGA1_FIFO_HIGH_THRESHOLD; + /* If resolution > 1280x1024, expire length = 64, else + expire length = 128 */ + if ((hor_active > 1280) && (ver_active > 1024)) + iga1_display_queue_expire_num = 16; + else + iga1_display_queue_expire_num = + K800_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_PM800) { + iga1_fifo_max_depth = P880_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = P880_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + P880_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + P880_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + + /* If resolution > 1280x1024, expire length = 64, else + expire length = 128 */ + if ((hor_active > 1280) && (ver_active > 1024)) + iga1_display_queue_expire_num = 16; + else + iga1_display_queue_expire_num = + P880_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CN700) { + iga1_fifo_max_depth = CN700_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = CN700_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + CN700_IGA1_FIFO_HIGH_THRESHOLD; + + /* If resolution > 1280x1024, expire length = 64, + else expire length = 128 */ + if ((hor_active > 1280) && (ver_active > 1024)) + iga1_display_queue_expire_num = 16; + else + iga1_display_queue_expire_num = + CN700_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) { + iga1_fifo_max_depth = CX700_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = CX700_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + CX700_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + CX700_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_K8M890) { + iga1_fifo_max_depth = K8M890_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = K8M890_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + K8M890_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + K8M890_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_P4M890) { + iga1_fifo_max_depth = P4M890_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = P4M890_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + P4M890_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + P4M890_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_P4M900) { + iga1_fifo_max_depth = P4M900_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = P4M900_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + P4M900_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + P4M900_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_VX800) { + iga1_fifo_max_depth = VX800_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = VX800_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + VX800_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + VX800_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_VX855) { + iga1_fifo_max_depth = VX855_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = VX855_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + VX855_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + VX855_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_VX900) { + iga1_fifo_max_depth = VX900_IGA1_FIFO_MAX_DEPTH; + iga1_fifo_threshold = VX900_IGA1_FIFO_THRESHOLD; + iga1_fifo_high_threshold = + VX900_IGA1_FIFO_HIGH_THRESHOLD; + iga1_display_queue_expire_num = + VX900_IGA1_DISPLAY_QUEUE_EXPIRE_NUM; + } + + /* Set Display FIFO Depath Select */ + reg_value = IGA1_FIFO_DEPTH_SELECT_FORMULA(iga1_fifo_max_depth); + viafb_load_reg_num = + display_fifo_depth_reg.iga1_fifo_depth_select_reg.reg_num; + reg = display_fifo_depth_reg.iga1_fifo_depth_select_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIASR); + + /* Set Display FIFO Threshold Select */ + reg_value = IGA1_FIFO_THRESHOLD_FORMULA(iga1_fifo_threshold); + viafb_load_reg_num = + fifo_threshold_select_reg. + iga1_fifo_threshold_select_reg.reg_num; + reg = + fifo_threshold_select_reg. + iga1_fifo_threshold_select_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIASR); + + /* Set FIFO High Threshold Select */ + reg_value = + IGA1_FIFO_HIGH_THRESHOLD_FORMULA(iga1_fifo_high_threshold); + viafb_load_reg_num = + fifo_high_threshold_select_reg. + iga1_fifo_high_threshold_select_reg.reg_num; + reg = + fifo_high_threshold_select_reg. + iga1_fifo_high_threshold_select_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIASR); + + /* Set Display Queue Expire Num */ + reg_value = + IGA1_DISPLAY_QUEUE_EXPIRE_NUM_FORMULA + (iga1_display_queue_expire_num); + viafb_load_reg_num = + display_queue_expire_num_reg. + iga1_display_queue_expire_num_reg.reg_num; + reg = + display_queue_expire_num_reg. + iga1_display_queue_expire_num_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIASR); + + } else { + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_K800) { + iga2_fifo_max_depth = K800_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = K800_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + K800_IGA2_FIFO_HIGH_THRESHOLD; + + /* If resolution > 1280x1024, expire length = 64, + else expire length = 128 */ + if ((hor_active > 1280) && (ver_active > 1024)) + iga2_display_queue_expire_num = 16; + else + iga2_display_queue_expire_num = + K800_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_PM800) { + iga2_fifo_max_depth = P880_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = P880_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + P880_IGA2_FIFO_HIGH_THRESHOLD; + + /* If resolution > 1280x1024, expire length = 64, + else expire length = 128 */ + if ((hor_active > 1280) && (ver_active > 1024)) + iga2_display_queue_expire_num = 16; + else + iga2_display_queue_expire_num = + P880_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CN700) { + iga2_fifo_max_depth = CN700_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = CN700_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + CN700_IGA2_FIFO_HIGH_THRESHOLD; + + /* If resolution > 1280x1024, expire length = 64, + else expire length = 128 */ + if ((hor_active > 1280) && (ver_active > 1024)) + iga2_display_queue_expire_num = 16; + else + iga2_display_queue_expire_num = + CN700_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) { + iga2_fifo_max_depth = CX700_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = CX700_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + CX700_IGA2_FIFO_HIGH_THRESHOLD; + iga2_display_queue_expire_num = + CX700_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_K8M890) { + iga2_fifo_max_depth = K8M890_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = K8M890_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + K8M890_IGA2_FIFO_HIGH_THRESHOLD; + iga2_display_queue_expire_num = + K8M890_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_P4M890) { + iga2_fifo_max_depth = P4M890_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = P4M890_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + P4M890_IGA2_FIFO_HIGH_THRESHOLD; + iga2_display_queue_expire_num = + P4M890_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_P4M900) { + iga2_fifo_max_depth = P4M900_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = P4M900_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + P4M900_IGA2_FIFO_HIGH_THRESHOLD; + iga2_display_queue_expire_num = + P4M900_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_VX800) { + iga2_fifo_max_depth = VX800_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = VX800_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + VX800_IGA2_FIFO_HIGH_THRESHOLD; + iga2_display_queue_expire_num = + VX800_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_VX855) { + iga2_fifo_max_depth = VX855_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = VX855_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + VX855_IGA2_FIFO_HIGH_THRESHOLD; + iga2_display_queue_expire_num = + VX855_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_VX900) { + iga2_fifo_max_depth = VX900_IGA2_FIFO_MAX_DEPTH; + iga2_fifo_threshold = VX900_IGA2_FIFO_THRESHOLD; + iga2_fifo_high_threshold = + VX900_IGA2_FIFO_HIGH_THRESHOLD; + iga2_display_queue_expire_num = + VX900_IGA2_DISPLAY_QUEUE_EXPIRE_NUM; + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_K800) { + /* Set Display FIFO Depath Select */ + reg_value = + IGA2_FIFO_DEPTH_SELECT_FORMULA(iga2_fifo_max_depth) + - 1; + /* Patch LCD in IGA2 case */ + viafb_load_reg_num = + display_fifo_depth_reg. + iga2_fifo_depth_select_reg.reg_num; + reg = + display_fifo_depth_reg. + iga2_fifo_depth_select_reg.reg; + viafb_load_reg(reg_value, + viafb_load_reg_num, reg, VIACR); + } else { + + /* Set Display FIFO Depath Select */ + reg_value = + IGA2_FIFO_DEPTH_SELECT_FORMULA(iga2_fifo_max_depth); + viafb_load_reg_num = + display_fifo_depth_reg. + iga2_fifo_depth_select_reg.reg_num; + reg = + display_fifo_depth_reg. + iga2_fifo_depth_select_reg.reg; + viafb_load_reg(reg_value, + viafb_load_reg_num, reg, VIACR); + } + + /* Set Display FIFO Threshold Select */ + reg_value = IGA2_FIFO_THRESHOLD_FORMULA(iga2_fifo_threshold); + viafb_load_reg_num = + fifo_threshold_select_reg. + iga2_fifo_threshold_select_reg.reg_num; + reg = + fifo_threshold_select_reg. + iga2_fifo_threshold_select_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIACR); + + /* Set FIFO High Threshold Select */ + reg_value = + IGA2_FIFO_HIGH_THRESHOLD_FORMULA(iga2_fifo_high_threshold); + viafb_load_reg_num = + fifo_high_threshold_select_reg. + iga2_fifo_high_threshold_select_reg.reg_num; + reg = + fifo_high_threshold_select_reg. + iga2_fifo_high_threshold_select_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIACR); + + /* Set Display Queue Expire Num */ + reg_value = + IGA2_DISPLAY_QUEUE_EXPIRE_NUM_FORMULA + (iga2_display_queue_expire_num); + viafb_load_reg_num = + display_queue_expire_num_reg. + iga2_display_queue_expire_num_reg.reg_num; + reg = + display_queue_expire_num_reg. + iga2_display_queue_expire_num_reg.reg; + viafb_load_reg(reg_value, viafb_load_reg_num, reg, VIACR); + + } + +} + +static struct via_pll_config get_pll_config(struct pll_limit *limits, int size, + int clk) +{ + struct via_pll_config cur, up, down, best = {0, 1, 0}; + const u32 f0 = 14318180; /* X1 frequency */ + int i, f; + + for (i = 0; i < size; i++) { + cur.rshift = limits[i].rshift; + cur.divisor = limits[i].divisor; + cur.multiplier = clk / ((f0 / cur.divisor)>>cur.rshift); + f = abs(get_pll_output_frequency(f0, cur) - clk); + up = down = cur; + up.multiplier++; + down.multiplier--; + if (abs(get_pll_output_frequency(f0, up) - clk) < f) + cur = up; + else if (abs(get_pll_output_frequency(f0, down) - clk) < f) + cur = down; + + if (cur.multiplier < limits[i].multiplier_min) + cur.multiplier = limits[i].multiplier_min; + else if (cur.multiplier > limits[i].multiplier_max) + cur.multiplier = limits[i].multiplier_max; + + f = abs(get_pll_output_frequency(f0, cur) - clk); + if (f < abs(get_pll_output_frequency(f0, best) - clk)) + best = cur; + } + + return best; +} + +static struct via_pll_config get_best_pll_config(int clk) +{ + struct via_pll_config config; + + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CLE266: + case UNICHROME_K400: + config = get_pll_config(cle266_pll_limits, + ARRAY_SIZE(cle266_pll_limits), clk); + break; + case UNICHROME_K800: + case UNICHROME_PM800: + case UNICHROME_CN700: + config = get_pll_config(k800_pll_limits, + ARRAY_SIZE(k800_pll_limits), clk); + break; + case UNICHROME_CX700: + case UNICHROME_CN750: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + case UNICHROME_VX800: + config = get_pll_config(cx700_pll_limits, + ARRAY_SIZE(cx700_pll_limits), clk); + break; + case UNICHROME_VX855: + case UNICHROME_VX900: + config = get_pll_config(vx855_pll_limits, + ARRAY_SIZE(vx855_pll_limits), clk); + break; + } + + return config; +} + +/* Set VCLK*/ +void viafb_set_vclock(u32 clk, int set_iga) +{ + struct via_pll_config config = get_best_pll_config(clk); + + if (set_iga == IGA1) + clock.set_primary_pll(config); + if (set_iga == IGA2) + clock.set_secondary_pll(config); + + /* Fire! */ + via_write_misc_reg_mask(0x0C, 0x0C); /* select external clock */ +} + +struct via_display_timing var_to_timing(const struct fb_var_screeninfo *var, + u16 cxres, u16 cyres) +{ + struct via_display_timing timing; + u16 dx = (var->xres - cxres) / 2, dy = (var->yres - cyres) / 2; + + timing.hor_addr = cxres; + timing.hor_sync_start = timing.hor_addr + var->right_margin + dx; + timing.hor_sync_end = timing.hor_sync_start + var->hsync_len; + timing.hor_total = timing.hor_sync_end + var->left_margin + dx; + timing.hor_blank_start = timing.hor_addr + dx; + timing.hor_blank_end = timing.hor_total - dx; + timing.ver_addr = cyres; + timing.ver_sync_start = timing.ver_addr + var->lower_margin + dy; + timing.ver_sync_end = timing.ver_sync_start + var->vsync_len; + timing.ver_total = timing.ver_sync_end + var->upper_margin + dy; + timing.ver_blank_start = timing.ver_addr + dy; + timing.ver_blank_end = timing.ver_total - dy; + return timing; +} + +void viafb_fill_crtc_timing(const struct fb_var_screeninfo *var, + u16 cxres, u16 cyres, int iga) +{ + struct via_display_timing crt_reg = var_to_timing(var, + cxres ? cxres : var->xres, cyres ? cyres : var->yres); + + if (iga == IGA1) + via_set_primary_timing(&crt_reg); + else if (iga == IGA2) + via_set_secondary_timing(&crt_reg); + + viafb_load_fetch_count_reg(var->xres, var->bits_per_pixel / 8, iga); + if (viaparinfo->chip_info->gfx_chip_name != UNICHROME_CLE266 + && viaparinfo->chip_info->gfx_chip_name != UNICHROME_K400) + viafb_load_FIFO_reg(iga, var->xres, var->yres); + + viafb_set_vclock(PICOS2KHZ(var->pixclock) * 1000, iga); +} + +void viafb_init_chip_info(int chip_type) +{ + via_clock_init(&clock, chip_type); + init_gfx_chip_info(chip_type); + init_tmds_chip_info(); + init_lvds_chip_info(); + + /*Set IGA path for each device */ + viafb_set_iga_path(); + + viaparinfo->lvds_setting_info->display_method = viafb_lcd_dsp_method; + viaparinfo->lvds_setting_info->lcd_mode = viafb_lcd_mode; + viaparinfo->lvds_setting_info2->display_method = + viaparinfo->lvds_setting_info->display_method; + viaparinfo->lvds_setting_info2->lcd_mode = + viaparinfo->lvds_setting_info->lcd_mode; +} + +void viafb_update_device_setting(int hres, int vres, int bpp, int flag) +{ + if (flag == 0) { + viaparinfo->tmds_setting_info->h_active = hres; + viaparinfo->tmds_setting_info->v_active = vres; + } else { + + if (viaparinfo->tmds_setting_info->iga_path == IGA2) { + viaparinfo->tmds_setting_info->h_active = hres; + viaparinfo->tmds_setting_info->v_active = vres; + } + + } +} + +static void init_gfx_chip_info(int chip_type) +{ + u8 tmp; + + viaparinfo->chip_info->gfx_chip_name = chip_type; + + /* Check revision of CLE266 Chip */ + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) { + /* CR4F only define in CLE266.CX chip */ + tmp = viafb_read_reg(VIACR, CR4F); + viafb_write_reg(CR4F, VIACR, 0x55); + if (viafb_read_reg(VIACR, CR4F) != 0x55) + viaparinfo->chip_info->gfx_chip_revision = + CLE266_REVISION_AX; + else + viaparinfo->chip_info->gfx_chip_revision = + CLE266_REVISION_CX; + /* restore orignal CR4F value */ + viafb_write_reg(CR4F, VIACR, tmp); + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) { + tmp = viafb_read_reg(VIASR, SR43); + DEBUG_MSG(KERN_INFO "SR43:%X\n", tmp); + if (tmp & 0x02) { + viaparinfo->chip_info->gfx_chip_revision = + CX700_REVISION_700M2; + } else if (tmp & 0x40) { + viaparinfo->chip_info->gfx_chip_revision = + CX700_REVISION_700M; + } else { + viaparinfo->chip_info->gfx_chip_revision = + CX700_REVISION_700; + } + } + + /* Determine which 2D engine we have */ + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_VX800: + case UNICHROME_VX855: + case UNICHROME_VX900: + viaparinfo->chip_info->twod_engine = VIA_2D_ENG_M1; + break; + case UNICHROME_K8M890: + case UNICHROME_P4M900: + viaparinfo->chip_info->twod_engine = VIA_2D_ENG_H5; + break; + default: + viaparinfo->chip_info->twod_engine = VIA_2D_ENG_H2; + break; + } +} + +static void init_tmds_chip_info(void) +{ + viafb_tmds_trasmitter_identify(); + + if (INTERFACE_NONE == viaparinfo->chip_info->tmds_chip_info. + output_interface) { + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CX700: + { + /* we should check support by hardware layout.*/ + if ((viafb_display_hardware_layout == + HW_LAYOUT_DVI_ONLY) + || (viafb_display_hardware_layout == + HW_LAYOUT_LCD_DVI)) { + viaparinfo->chip_info->tmds_chip_info. + output_interface = INTERFACE_TMDS; + } else { + viaparinfo->chip_info->tmds_chip_info. + output_interface = + INTERFACE_NONE; + } + break; + } + case UNICHROME_K8M890: + case UNICHROME_P4M900: + case UNICHROME_P4M890: + /* TMDS on PCIE, we set DFPLOW as default. */ + viaparinfo->chip_info->tmds_chip_info.output_interface = + INTERFACE_DFP_LOW; + break; + default: + { + /* set DVP1 default for DVI */ + viaparinfo->chip_info->tmds_chip_info + .output_interface = INTERFACE_DVP1; + } + } + } + + DEBUG_MSG(KERN_INFO "TMDS Chip = %d\n", + viaparinfo->chip_info->tmds_chip_info.tmds_chip_name); + viafb_init_dvi_size(&viaparinfo->shared->chip_info.tmds_chip_info, + &viaparinfo->shared->tmds_setting_info); +} + +static void init_lvds_chip_info(void) +{ + viafb_lvds_trasmitter_identify(); + viafb_init_lcd_size(); + viafb_init_lvds_output_interface(&viaparinfo->chip_info->lvds_chip_info, + viaparinfo->lvds_setting_info); + if (viaparinfo->chip_info->lvds_chip_info2.lvds_chip_name) { + viafb_init_lvds_output_interface(&viaparinfo->chip_info-> + lvds_chip_info2, viaparinfo->lvds_setting_info2); + } + /*If CX700,two singel LCD, we need to reassign + LCD interface to different LVDS port */ + if ((UNICHROME_CX700 == viaparinfo->chip_info->gfx_chip_name) + && (HW_LAYOUT_LCD1_LCD2 == viafb_display_hardware_layout)) { + if ((INTEGRATED_LVDS == viaparinfo->chip_info->lvds_chip_info. + lvds_chip_name) && (INTEGRATED_LVDS == + viaparinfo->chip_info-> + lvds_chip_info2.lvds_chip_name)) { + viaparinfo->chip_info->lvds_chip_info.output_interface = + INTERFACE_LVDS0; + viaparinfo->chip_info->lvds_chip_info2. + output_interface = + INTERFACE_LVDS1; + } + } + + DEBUG_MSG(KERN_INFO "LVDS Chip = %d\n", + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name); + DEBUG_MSG(KERN_INFO "LVDS1 output_interface = %d\n", + viaparinfo->chip_info->lvds_chip_info.output_interface); + DEBUG_MSG(KERN_INFO "LVDS2 output_interface = %d\n", + viaparinfo->chip_info->lvds_chip_info.output_interface); +} + +void viafb_init_dac(int set_iga) +{ + int i; + u8 tmp; + + if (set_iga == IGA1) { + /* access Primary Display's LUT */ + viafb_write_reg_mask(SR1A, VIASR, 0x00, BIT0); + /* turn off LCK */ + viafb_write_reg_mask(SR1B, VIASR, 0x00, BIT7 + BIT6); + for (i = 0; i < 256; i++) { + write_dac_reg(i, palLUT_table[i].red, + palLUT_table[i].green, + palLUT_table[i].blue); + } + /* turn on LCK */ + viafb_write_reg_mask(SR1B, VIASR, 0xC0, BIT7 + BIT6); + } else { + tmp = viafb_read_reg(VIACR, CR6A); + /* access Secondary Display's LUT */ + viafb_write_reg_mask(CR6A, VIACR, 0x40, BIT6); + viafb_write_reg_mask(SR1A, VIASR, 0x01, BIT0); + for (i = 0; i < 256; i++) { + write_dac_reg(i, palLUT_table[i].red, + palLUT_table[i].green, + palLUT_table[i].blue); + } + /* set IGA1 DAC for default */ + viafb_write_reg_mask(SR1A, VIASR, 0x00, BIT0); + viafb_write_reg(CR6A, VIACR, tmp); + } +} + +static void device_screen_off(void) +{ + /* turn off CRT screen (IGA1) */ + viafb_write_reg_mask(SR01, VIASR, 0x20, BIT5); +} + +static void device_screen_on(void) +{ + /* turn on CRT screen (IGA1) */ + viafb_write_reg_mask(SR01, VIASR, 0x00, BIT5); +} + +static void set_display_channel(void) +{ + /*If viafb_LCD2_ON, on cx700, internal lvds's information + is keeped on lvds_setting_info2 */ + if (viafb_LCD2_ON && + viaparinfo->lvds_setting_info2->device_lcd_dualedge) { + /* For dual channel LCD: */ + /* Set to Dual LVDS channel. */ + viafb_write_reg_mask(CRD2, VIACR, 0x20, BIT4 + BIT5); + } else if (viafb_LCD_ON && viafb_DVI_ON) { + /* For LCD+DFP: */ + /* Set to LVDS1 + TMDS channel. */ + viafb_write_reg_mask(CRD2, VIACR, 0x10, BIT4 + BIT5); + } else if (viafb_DVI_ON) { + /* Set to single TMDS channel. */ + viafb_write_reg_mask(CRD2, VIACR, 0x30, BIT4 + BIT5); + } else if (viafb_LCD_ON) { + if (viaparinfo->lvds_setting_info->device_lcd_dualedge) { + /* For dual channel LCD: */ + /* Set to Dual LVDS channel. */ + viafb_write_reg_mask(CRD2, VIACR, 0x20, BIT4 + BIT5); + } else { + /* Set to LVDS0 + LVDS1 channel. */ + viafb_write_reg_mask(CRD2, VIACR, 0x00, BIT4 + BIT5); + } + } +} + +static u8 get_sync(struct fb_var_screeninfo *var) +{ + u8 polarity = 0; + + if (!(var->sync & FB_SYNC_HOR_HIGH_ACT)) + polarity |= VIA_HSYNC_NEGATIVE; + if (!(var->sync & FB_SYNC_VERT_HIGH_ACT)) + polarity |= VIA_VSYNC_NEGATIVE; + return polarity; +} + +static void hw_init(void) +{ + int i; + + inb(VIAStatus); + outb(0x00, VIAAR); + + /* Write Common Setting for Video Mode */ + viafb_write_regx(common_vga, ARRAY_SIZE(common_vga)); + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CLE266: + viafb_write_regx(CLE266_ModeXregs, NUM_TOTAL_CLE266_ModeXregs); + break; + + case UNICHROME_K400: + viafb_write_regx(KM400_ModeXregs, NUM_TOTAL_KM400_ModeXregs); + break; + + case UNICHROME_K800: + case UNICHROME_PM800: + viafb_write_regx(CN400_ModeXregs, NUM_TOTAL_CN400_ModeXregs); + break; + + case UNICHROME_CN700: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + viafb_write_regx(CN700_ModeXregs, NUM_TOTAL_CN700_ModeXregs); + break; + + case UNICHROME_CX700: + case UNICHROME_VX800: + viafb_write_regx(CX700_ModeXregs, NUM_TOTAL_CX700_ModeXregs); + break; + + case UNICHROME_VX855: + case UNICHROME_VX900: + viafb_write_regx(VX855_ModeXregs, NUM_TOTAL_VX855_ModeXregs); + break; + } + + /* magic required on VX900 for correct modesetting on IGA1 */ + via_write_reg_mask(VIACR, 0x45, 0x00, 0x01); + + /* probably this should go to the scaling code one day */ + via_write_reg_mask(VIACR, 0xFD, 0, 0x80); /* VX900 hw scale on IGA2 */ + viafb_write_regx(scaling_parameters, ARRAY_SIZE(scaling_parameters)); + + /* Fill VPIT Parameters */ + /* Write Misc Register */ + outb(VPIT.Misc, VIA_MISC_REG_WRITE); + + /* Write Sequencer */ + for (i = 1; i <= StdSR; i++) + via_write_reg(VIASR, i, VPIT.SR[i - 1]); + + viafb_write_reg_mask(0x15, VIASR, 0xA2, 0xA2); + + /* Write Graphic Controller */ + for (i = 0; i < StdGR; i++) + via_write_reg(VIAGR, i, VPIT.GR[i]); + + /* Write Attribute Controller */ + for (i = 0; i < StdAR; i++) { + inb(VIAStatus); + outb(i, VIAAR); + outb(VPIT.AR[i], VIAAR); + } + + inb(VIAStatus); + outb(0x20, VIAAR); + + load_fix_bit_crtc_reg(); +} + +int viafb_setmode(void) +{ + int j, cxres = 0, cyres = 0; + int port; + u32 devices = viaparinfo->shared->iga1_devices + | viaparinfo->shared->iga2_devices; + u8 value, index, mask; + struct fb_var_screeninfo var2; + + device_screen_off(); + device_off(); + via_set_state(devices, VIA_STATE_OFF); + + hw_init(); + + /* Update Patch Register */ + + if ((viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266 + || viaparinfo->chip_info->gfx_chip_name == UNICHROME_K400) + && viafbinfo->var.xres == 1024 && viafbinfo->var.yres == 768) { + for (j = 0; j < res_patch_table[0].table_length; j++) { + index = res_patch_table[0].io_reg_table[j].index; + port = res_patch_table[0].io_reg_table[j].port; + value = res_patch_table[0].io_reg_table[j].value; + mask = res_patch_table[0].io_reg_table[j].mask; + viafb_write_reg_mask(index, port, value, mask); + } + } + + via_set_primary_pitch(viafbinfo->fix.line_length); + via_set_secondary_pitch(viafb_dual_fb ? viafbinfo1->fix.line_length + : viafbinfo->fix.line_length); + via_set_primary_color_depth(viaparinfo->depth); + via_set_secondary_color_depth(viafb_dual_fb ? viaparinfo1->depth + : viaparinfo->depth); + via_set_source(viaparinfo->shared->iga1_devices, IGA1); + via_set_source(viaparinfo->shared->iga2_devices, IGA2); + if (viaparinfo->shared->iga2_devices) + enable_second_display_channel(); + else + disable_second_display_channel(); + + /* Update Refresh Rate Setting */ + + /* Clear On Screen */ + + if (viafb_dual_fb) { + var2 = viafbinfo1->var; + } else if (viafb_SAMM_ON) { + viafb_fill_var_timing_info(&var2, viafb_get_best_mode( + viafb_second_xres, viafb_second_yres, viafb_refresh1)); + cxres = viafbinfo->var.xres; + cyres = viafbinfo->var.yres; + var2.bits_per_pixel = viafbinfo->var.bits_per_pixel; + } + + /* CRT set mode */ + if (viafb_CRT_ON) { + if (viaparinfo->shared->iga2_devices & VIA_CRT + && viafb_SAMM_ON) + viafb_fill_crtc_timing(&var2, cxres, cyres, IGA2); + else + viafb_fill_crtc_timing(&viafbinfo->var, 0, 0, + (viaparinfo->shared->iga1_devices & VIA_CRT) + ? IGA1 : IGA2); + + /* Patch if set_hres is not 8 alignment (1366) to viafb_setmode + to 8 alignment (1368),there is several pixels (2 pixels) + on right side of screen. */ + if (viafbinfo->var.xres % 8) { + viafb_unlock_crt(); + viafb_write_reg(CR02, VIACR, + viafb_read_reg(VIACR, CR02) - 1); + viafb_lock_crt(); + } + } + + if (viafb_DVI_ON) { + if (viaparinfo->shared->tmds_setting_info.iga_path == IGA2 + && viafb_SAMM_ON) + viafb_dvi_set_mode(&var2, cxres, cyres, IGA2); + else + viafb_dvi_set_mode(&viafbinfo->var, 0, 0, + viaparinfo->tmds_setting_info->iga_path); + } + + if (viafb_LCD_ON) { + if (viafb_SAMM_ON && + (viaparinfo->lvds_setting_info->iga_path == IGA2)) { + viafb_lcd_set_mode(&var2, cxres, cyres, + viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info); + } else { + /* IGA1 doesn't have LCD scaling, so set it center. */ + if (viaparinfo->lvds_setting_info->iga_path == IGA1) { + viaparinfo->lvds_setting_info->display_method = + LCD_CENTERING; + } + viafb_lcd_set_mode(&viafbinfo->var, 0, 0, + viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info); + } + } + if (viafb_LCD2_ON) { + if (viafb_SAMM_ON && + (viaparinfo->lvds_setting_info2->iga_path == IGA2)) { + viafb_lcd_set_mode(&var2, cxres, cyres, + viaparinfo->lvds_setting_info2, + &viaparinfo->chip_info->lvds_chip_info2); + } else { + /* IGA1 doesn't have LCD scaling, so set it center. */ + if (viaparinfo->lvds_setting_info2->iga_path == IGA1) { + viaparinfo->lvds_setting_info2->display_method = + LCD_CENTERING; + } + viafb_lcd_set_mode(&viafbinfo->var, 0, 0, + viaparinfo->lvds_setting_info2, + &viaparinfo->chip_info->lvds_chip_info2); + } + } + + if ((viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) + && (viafb_LCD_ON || viafb_DVI_ON)) + set_display_channel(); + + /* If set mode normally, save resolution information for hot-plug . */ + if (!viafb_hotplug) { + viafb_hotplug_Xres = viafbinfo->var.xres; + viafb_hotplug_Yres = viafbinfo->var.yres; + viafb_hotplug_bpp = viafbinfo->var.bits_per_pixel; + viafb_hotplug_refresh = viafb_refresh; + + if (viafb_DVI_ON) + viafb_DeviceStatus = DVI_Device; + else + viafb_DeviceStatus = CRT_Device; + } + device_on(); + if (!viafb_SAMM_ON) + via_set_sync_polarity(devices, get_sync(&viafbinfo->var)); + else { + via_set_sync_polarity(viaparinfo->shared->iga1_devices, + get_sync(&viafbinfo->var)); + via_set_sync_polarity(viaparinfo->shared->iga2_devices, + get_sync(&var2)); + } + + clock.set_engine_pll_state(VIA_STATE_ON); + clock.set_primary_clock_source(VIA_CLKSRC_X1, true); + clock.set_secondary_clock_source(VIA_CLKSRC_X1, true); + +#ifdef CONFIG_FB_VIA_X_COMPATIBILITY + clock.set_primary_pll_state(VIA_STATE_ON); + clock.set_primary_clock_state(VIA_STATE_ON); + clock.set_secondary_pll_state(VIA_STATE_ON); + clock.set_secondary_clock_state(VIA_STATE_ON); +#else + if (viaparinfo->shared->iga1_devices) { + clock.set_primary_pll_state(VIA_STATE_ON); + clock.set_primary_clock_state(VIA_STATE_ON); + } else { + clock.set_primary_pll_state(VIA_STATE_OFF); + clock.set_primary_clock_state(VIA_STATE_OFF); + } + + if (viaparinfo->shared->iga2_devices) { + clock.set_secondary_pll_state(VIA_STATE_ON); + clock.set_secondary_clock_state(VIA_STATE_ON); + } else { + clock.set_secondary_pll_state(VIA_STATE_OFF); + clock.set_secondary_clock_state(VIA_STATE_OFF); + } +#endif /*CONFIG_FB_VIA_X_COMPATIBILITY*/ + + via_set_state(devices, VIA_STATE_ON); + device_screen_on(); + return 1; +} + +int viafb_get_refresh(int hres, int vres, u32 long_refresh) +{ + const struct fb_videomode *best; + + best = viafb_get_best_mode(hres, vres, long_refresh); + if (!best) + return 60; + + if (abs(best->refresh - long_refresh) > 3) { + if (hres == 1200 && vres == 900) + return 49; /* OLPC DCON only supports 50 Hz */ + else + return 60; + } + + return best->refresh; +} + +static void device_off(void) +{ + viafb_dvi_disable(); + viafb_lcd_disable(); +} + +static void device_on(void) +{ + if (viafb_DVI_ON == 1) + viafb_dvi_enable(); + if (viafb_LCD_ON == 1) + viafb_lcd_enable(); +} + +static void enable_second_display_channel(void) +{ + /* to enable second display channel. */ + viafb_write_reg_mask(CR6A, VIACR, 0x00, BIT6); + viafb_write_reg_mask(CR6A, VIACR, BIT7, BIT7); + viafb_write_reg_mask(CR6A, VIACR, BIT6, BIT6); +} + +static void disable_second_display_channel(void) +{ + /* to disable second display channel. */ + viafb_write_reg_mask(CR6A, VIACR, 0x00, BIT6); + viafb_write_reg_mask(CR6A, VIACR, 0x00, BIT7); + viafb_write_reg_mask(CR6A, VIACR, BIT6, BIT6); +} + +void viafb_set_dpa_gfx(int output_interface, struct GFX_DPA_SETTING\ + *p_gfx_dpa_setting) +{ + switch (output_interface) { + case INTERFACE_DVP0: + { + /* DVP0 Clock Polarity and Adjust: */ + viafb_write_reg_mask(CR96, VIACR, + p_gfx_dpa_setting->DVP0, 0x0F); + + /* DVP0 Clock and Data Pads Driving: */ + viafb_write_reg_mask(SR1E, VIASR, + p_gfx_dpa_setting->DVP0ClockDri_S, BIT2); + viafb_write_reg_mask(SR2A, VIASR, + p_gfx_dpa_setting->DVP0ClockDri_S1, + BIT4); + viafb_write_reg_mask(SR1B, VIASR, + p_gfx_dpa_setting->DVP0DataDri_S, BIT1); + viafb_write_reg_mask(SR2A, VIASR, + p_gfx_dpa_setting->DVP0DataDri_S1, BIT5); + break; + } + + case INTERFACE_DVP1: + { + /* DVP1 Clock Polarity and Adjust: */ + viafb_write_reg_mask(CR9B, VIACR, + p_gfx_dpa_setting->DVP1, 0x0F); + + /* DVP1 Clock and Data Pads Driving: */ + viafb_write_reg_mask(SR65, VIASR, + p_gfx_dpa_setting->DVP1Driving, 0x0F); + break; + } + + case INTERFACE_DFP_HIGH: + { + viafb_write_reg_mask(CR97, VIACR, + p_gfx_dpa_setting->DFPHigh, 0x0F); + break; + } + + case INTERFACE_DFP_LOW: + { + viafb_write_reg_mask(CR99, VIACR, + p_gfx_dpa_setting->DFPLow, 0x0F); + break; + } + + case INTERFACE_DFP: + { + viafb_write_reg_mask(CR97, VIACR, + p_gfx_dpa_setting->DFPHigh, 0x0F); + viafb_write_reg_mask(CR99, VIACR, + p_gfx_dpa_setting->DFPLow, 0x0F); + break; + } + } +} + +void viafb_fill_var_timing_info(struct fb_var_screeninfo *var, + const struct fb_videomode *mode) +{ + var->pixclock = mode->pixclock; + var->xres = mode->xres; + var->yres = mode->yres; + var->left_margin = mode->left_margin; + var->right_margin = mode->right_margin; + var->hsync_len = mode->hsync_len; + var->upper_margin = mode->upper_margin; + var->lower_margin = mode->lower_margin; + var->vsync_len = mode->vsync_len; + var->sync = mode->sync; +} diff --git a/drivers/video/fbdev/via/hw.h b/drivers/video/fbdev/via/hw.h new file mode 100644 index 000000000000..3be073c58b03 --- /dev/null +++ b/drivers/video/fbdev/via/hw.h @@ -0,0 +1,676 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __HW_H__ +#define __HW_H__ + +#include <linux/seq_file.h> + +#include "viamode.h" +#include "global.h" +#include "via_modesetting.h" + +#define viafb_read_reg(p, i) via_read_reg(p, i) +#define viafb_write_reg(i, p, d) via_write_reg(p, i, d) +#define viafb_write_reg_mask(i, p, d, m) via_write_reg_mask(p, i, d, m) + +/* VIA output devices */ +#define VIA_LDVP0 0x00000001 +#define VIA_LDVP1 0x00000002 +#define VIA_DVP0 0x00000004 +#define VIA_CRT 0x00000010 +#define VIA_DVP1 0x00000020 +#define VIA_LVDS1 0x00000040 +#define VIA_LVDS2 0x00000080 + +/* VIA output device power states */ +#define VIA_STATE_ON 0 +#define VIA_STATE_STANDBY 1 +#define VIA_STATE_SUSPEND 2 +#define VIA_STATE_OFF 3 + +/* VIA output device sync polarity */ +#define VIA_HSYNC_NEGATIVE 0x01 +#define VIA_VSYNC_NEGATIVE 0x02 + +/**********************************************************/ +/* Definition IGA2 Design Method of CRTC Shadow Registers */ +/**********************************************************/ +#define IGA2_HOR_TOTAL_SHADOW_FORMULA(x) ((x/8)-5) +#define IGA2_HOR_BLANK_END_SHADOW_FORMULA(x, y) (((x+y)/8)-1) +#define IGA2_VER_TOTAL_SHADOW_FORMULA(x) ((x)-2) +#define IGA2_VER_ADDR_SHADOW_FORMULA(x) ((x)-1) +#define IGA2_VER_BLANK_START_SHADOW_FORMULA(x) ((x)-1) +#define IGA2_VER_BLANK_END_SHADOW_FORMULA(x, y) ((x+y)-1) +#define IGA2_VER_SYNC_START_SHADOW_FORMULA(x) (x) +#define IGA2_VER_SYNC_END_SHADOW_FORMULA(x, y) (x+y) + +/* Define Register Number for IGA2 Shadow CRTC Timing */ + +/* location: {CR6D,0,7},{CR71,3,3} */ +#define IGA2_SHADOW_HOR_TOTAL_REG_NUM 2 +/* location: {CR6E,0,7} */ +#define IGA2_SHADOW_HOR_BLANK_END_REG_NUM 1 +/* location: {CR6F,0,7},{CR71,0,2} */ +#define IGA2_SHADOW_VER_TOTAL_REG_NUM 2 +/* location: {CR70,0,7},{CR71,4,6} */ +#define IGA2_SHADOW_VER_ADDR_REG_NUM 2 +/* location: {CR72,0,7},{CR74,4,6} */ +#define IGA2_SHADOW_VER_BLANK_START_REG_NUM 2 +/* location: {CR73,0,7},{CR74,0,2} */ +#define IGA2_SHADOW_VER_BLANK_END_REG_NUM 2 +/* location: {CR75,0,7},{CR76,4,6} */ +#define IGA2_SHADOW_VER_SYNC_START_REG_NUM 2 +/* location: {CR76,0,3} */ +#define IGA2_SHADOW_VER_SYNC_END_REG_NUM 1 + +/* Define Fetch Count Register*/ + +/* location: {SR1C,0,7},{SR1D,0,1} */ +#define IGA1_FETCH_COUNT_REG_NUM 2 +/* 16 bytes alignment. */ +#define IGA1_FETCH_COUNT_ALIGN_BYTE 16 +/* x: H resolution, y: color depth */ +#define IGA1_FETCH_COUNT_PATCH_VALUE 4 +#define IGA1_FETCH_COUNT_FORMULA(x, y) \ + (((x*y)/IGA1_FETCH_COUNT_ALIGN_BYTE) + IGA1_FETCH_COUNT_PATCH_VALUE) + +/* location: {CR65,0,7},{CR67,2,3} */ +#define IGA2_FETCH_COUNT_REG_NUM 2 +#define IGA2_FETCH_COUNT_ALIGN_BYTE 16 +#define IGA2_FETCH_COUNT_PATCH_VALUE 0 +#define IGA2_FETCH_COUNT_FORMULA(x, y) \ + (((x*y)/IGA2_FETCH_COUNT_ALIGN_BYTE) + IGA2_FETCH_COUNT_PATCH_VALUE) + +/* Staring Address*/ + +/* location: {CR0C,0,7},{CR0D,0,7},{CR34,0,7},{CR48,0,1} */ +#define IGA1_STARTING_ADDR_REG_NUM 4 +/* location: {CR62,1,7},{CR63,0,7},{CR64,0,7} */ +#define IGA2_STARTING_ADDR_REG_NUM 3 + +/* Define Display OFFSET*/ +/* These value are by HW suggested value*/ +/* location: {SR17,0,7} */ +#define K800_IGA1_FIFO_MAX_DEPTH 384 +/* location: {SR16,0,5},{SR16,7,7} */ +#define K800_IGA1_FIFO_THRESHOLD 328 +/* location: {SR18,0,5},{SR18,7,7} */ +#define K800_IGA1_FIFO_HIGH_THRESHOLD 296 +/* location: {SR22,0,4}. (128/4) =64, K800 must be set zero, */ + /* because HW only 5 bits */ +#define K800_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 0 + +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define K800_IGA2_FIFO_MAX_DEPTH 384 +/* location: {CR68,0,3},{CR95,4,6} */ +#define K800_IGA2_FIFO_THRESHOLD 328 +/* location: {CR92,0,3},{CR95,0,2} */ +#define K800_IGA2_FIFO_HIGH_THRESHOLD 296 +/* location: {CR94,0,6} */ +#define K800_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 128 + +/* location: {SR17,0,7} */ +#define P880_IGA1_FIFO_MAX_DEPTH 192 +/* location: {SR16,0,5},{SR16,7,7} */ +#define P880_IGA1_FIFO_THRESHOLD 128 +/* location: {SR18,0,5},{SR18,7,7} */ +#define P880_IGA1_FIFO_HIGH_THRESHOLD 64 +/* location: {SR22,0,4}. (128/4) =64, K800 must be set zero, */ + /* because HW only 5 bits */ +#define P880_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 0 + +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define P880_IGA2_FIFO_MAX_DEPTH 96 +/* location: {CR68,0,3},{CR95,4,6} */ +#define P880_IGA2_FIFO_THRESHOLD 64 +/* location: {CR92,0,3},{CR95,0,2} */ +#define P880_IGA2_FIFO_HIGH_THRESHOLD 32 +/* location: {CR94,0,6} */ +#define P880_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 128 + +/* VT3314 chipset*/ + +/* location: {SR17,0,7} */ +#define CN700_IGA1_FIFO_MAX_DEPTH 96 +/* location: {SR16,0,5},{SR16,7,7} */ +#define CN700_IGA1_FIFO_THRESHOLD 80 +/* location: {SR18,0,5},{SR18,7,7} */ +#define CN700_IGA1_FIFO_HIGH_THRESHOLD 64 +/* location: {SR22,0,4}. (128/4) =64, P800 must be set zero, + because HW only 5 bits */ +#define CN700_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 0 +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define CN700_IGA2_FIFO_MAX_DEPTH 96 +/* location: {CR68,0,3},{CR95,4,6} */ +#define CN700_IGA2_FIFO_THRESHOLD 80 +/* location: {CR92,0,3},{CR95,0,2} */ +#define CN700_IGA2_FIFO_HIGH_THRESHOLD 32 +/* location: {CR94,0,6} */ +#define CN700_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 128 + +/* For VT3324, these values are suggested by HW */ +/* location: {SR17,0,7} */ +#define CX700_IGA1_FIFO_MAX_DEPTH 192 +/* location: {SR16,0,5},{SR16,7,7} */ +#define CX700_IGA1_FIFO_THRESHOLD 128 +/* location: {SR18,0,5},{SR18,7,7} */ +#define CX700_IGA1_FIFO_HIGH_THRESHOLD 128 +/* location: {SR22,0,4} */ +#define CX700_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 124 + +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define CX700_IGA2_FIFO_MAX_DEPTH 96 +/* location: {CR68,0,3},{CR95,4,6} */ +#define CX700_IGA2_FIFO_THRESHOLD 64 +/* location: {CR92,0,3},{CR95,0,2} */ +#define CX700_IGA2_FIFO_HIGH_THRESHOLD 32 +/* location: {CR94,0,6} */ +#define CX700_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 128 + +/* VT3336 chipset*/ +/* location: {SR17,0,7} */ +#define K8M890_IGA1_FIFO_MAX_DEPTH 360 +/* location: {SR16,0,5},{SR16,7,7} */ +#define K8M890_IGA1_FIFO_THRESHOLD 328 +/* location: {SR18,0,5},{SR18,7,7} */ +#define K8M890_IGA1_FIFO_HIGH_THRESHOLD 296 +/* location: {SR22,0,4}. */ +#define K8M890_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 124 + +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define K8M890_IGA2_FIFO_MAX_DEPTH 360 +/* location: {CR68,0,3},{CR95,4,6} */ +#define K8M890_IGA2_FIFO_THRESHOLD 328 +/* location: {CR92,0,3},{CR95,0,2} */ +#define K8M890_IGA2_FIFO_HIGH_THRESHOLD 296 +/* location: {CR94,0,6} */ +#define K8M890_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 124 + +/* VT3327 chipset*/ +/* location: {SR17,0,7} */ +#define P4M890_IGA1_FIFO_MAX_DEPTH 96 +/* location: {SR16,0,5},{SR16,7,7} */ +#define P4M890_IGA1_FIFO_THRESHOLD 76 +/* location: {SR18,0,5},{SR18,7,7} */ +#define P4M890_IGA1_FIFO_HIGH_THRESHOLD 64 +/* location: {SR22,0,4}. (32/4) =8 */ +#define P4M890_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 32 +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define P4M890_IGA2_FIFO_MAX_DEPTH 96 +/* location: {CR68,0,3},{CR95,4,6} */ +#define P4M890_IGA2_FIFO_THRESHOLD 76 +/* location: {CR92,0,3},{CR95,0,2} */ +#define P4M890_IGA2_FIFO_HIGH_THRESHOLD 64 +/* location: {CR94,0,6} */ +#define P4M890_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 32 + +/* VT3364 chipset*/ +/* location: {SR17,0,7} */ +#define P4M900_IGA1_FIFO_MAX_DEPTH 96 +/* location: {SR16,0,5},{SR16,7,7} */ +#define P4M900_IGA1_FIFO_THRESHOLD 76 +/* location: {SR18,0,5},{SR18,7,7} */ +#define P4M900_IGA1_FIFO_HIGH_THRESHOLD 76 +/* location: {SR22,0,4}. */ +#define P4M900_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 32 +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define P4M900_IGA2_FIFO_MAX_DEPTH 96 +/* location: {CR68,0,3},{CR95,4,6} */ +#define P4M900_IGA2_FIFO_THRESHOLD 76 +/* location: {CR92,0,3},{CR95,0,2} */ +#define P4M900_IGA2_FIFO_HIGH_THRESHOLD 76 +/* location: {CR94,0,6} */ +#define P4M900_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 32 + +/* For VT3353, these values are suggested by HW */ +/* location: {SR17,0,7} */ +#define VX800_IGA1_FIFO_MAX_DEPTH 192 +/* location: {SR16,0,5},{SR16,7,7} */ +#define VX800_IGA1_FIFO_THRESHOLD 152 +/* location: {SR18,0,5},{SR18,7,7} */ +#define VX800_IGA1_FIFO_HIGH_THRESHOLD 152 +/* location: {SR22,0,4} */ +#define VX800_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 64 +/* location: {CR68,4,7},{CR94,7,7},{CR95,7,7} */ +#define VX800_IGA2_FIFO_MAX_DEPTH 96 +/* location: {CR68,0,3},{CR95,4,6} */ +#define VX800_IGA2_FIFO_THRESHOLD 64 +/* location: {CR92,0,3},{CR95,0,2} */ +#define VX800_IGA2_FIFO_HIGH_THRESHOLD 32 +/* location: {CR94,0,6} */ +#define VX800_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 128 + +/* For VT3409 */ +#define VX855_IGA1_FIFO_MAX_DEPTH 400 +#define VX855_IGA1_FIFO_THRESHOLD 320 +#define VX855_IGA1_FIFO_HIGH_THRESHOLD 320 +#define VX855_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 160 + +#define VX855_IGA2_FIFO_MAX_DEPTH 200 +#define VX855_IGA2_FIFO_THRESHOLD 160 +#define VX855_IGA2_FIFO_HIGH_THRESHOLD 160 +#define VX855_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 320 + +/* For VT3410 */ +#define VX900_IGA1_FIFO_MAX_DEPTH 400 +#define VX900_IGA1_FIFO_THRESHOLD 320 +#define VX900_IGA1_FIFO_HIGH_THRESHOLD 320 +#define VX900_IGA1_DISPLAY_QUEUE_EXPIRE_NUM 160 + +#define VX900_IGA2_FIFO_MAX_DEPTH 192 +#define VX900_IGA2_FIFO_THRESHOLD 160 +#define VX900_IGA2_FIFO_HIGH_THRESHOLD 160 +#define VX900_IGA2_DISPLAY_QUEUE_EXPIRE_NUM 320 + +#define IGA1_FIFO_DEPTH_SELECT_REG_NUM 1 +#define IGA1_FIFO_THRESHOLD_REG_NUM 2 +#define IGA1_FIFO_HIGH_THRESHOLD_REG_NUM 2 +#define IGA1_DISPLAY_QUEUE_EXPIRE_NUM_REG_NUM 1 + +#define IGA2_FIFO_DEPTH_SELECT_REG_NUM 3 +#define IGA2_FIFO_THRESHOLD_REG_NUM 2 +#define IGA2_FIFO_HIGH_THRESHOLD_REG_NUM 2 +#define IGA2_DISPLAY_QUEUE_EXPIRE_NUM_REG_NUM 1 + +#define IGA1_FIFO_DEPTH_SELECT_FORMULA(x) ((x/2)-1) +#define IGA1_FIFO_THRESHOLD_FORMULA(x) (x/4) +#define IGA1_DISPLAY_QUEUE_EXPIRE_NUM_FORMULA(x) (x/4) +#define IGA1_FIFO_HIGH_THRESHOLD_FORMULA(x) (x/4) +#define IGA2_FIFO_DEPTH_SELECT_FORMULA(x) (((x/2)/4)-1) +#define IGA2_FIFO_THRESHOLD_FORMULA(x) (x/4) +#define IGA2_DISPLAY_QUEUE_EXPIRE_NUM_FORMULA(x) (x/4) +#define IGA2_FIFO_HIGH_THRESHOLD_FORMULA(x) (x/4) + +/************************************************************************/ +/* LCD Timing */ +/************************************************************************/ + +/* 500 ms = 500000 us */ +#define LCD_POWER_SEQ_TD0 500000 +/* 50 ms = 50000 us */ +#define LCD_POWER_SEQ_TD1 50000 +/* 0 us */ +#define LCD_POWER_SEQ_TD2 0 +/* 210 ms = 210000 us */ +#define LCD_POWER_SEQ_TD3 210000 +/* 2^10 * (1/14.31818M) = 71.475 us (K400.revA) */ +#define CLE266_POWER_SEQ_UNIT 71 +/* 2^11 * (1/14.31818M) = 142.95 us (K400.revB) */ +#define K800_POWER_SEQ_UNIT 142 +/* 2^13 * (1/14.31818M) = 572.1 us */ +#define P880_POWER_SEQ_UNIT 572 + +#define CLE266_POWER_SEQ_FORMULA(x) ((x)/CLE266_POWER_SEQ_UNIT) +#define K800_POWER_SEQ_FORMULA(x) ((x)/K800_POWER_SEQ_UNIT) +#define P880_POWER_SEQ_FORMULA(x) ((x)/P880_POWER_SEQ_UNIT) + +/* location: {CR8B,0,7},{CR8F,0,3} */ +#define LCD_POWER_SEQ_TD0_REG_NUM 2 +/* location: {CR8C,0,7},{CR8F,4,7} */ +#define LCD_POWER_SEQ_TD1_REG_NUM 2 +/* location: {CR8D,0,7},{CR90,0,3} */ +#define LCD_POWER_SEQ_TD2_REG_NUM 2 +/* location: {CR8E,0,7},{CR90,4,7} */ +#define LCD_POWER_SEQ_TD3_REG_NUM 2 + +/* LCD Scaling factor*/ +/* x: indicate setting horizontal size*/ +/* y: indicate panel horizontal size*/ + +/* Horizontal scaling factor 10 bits (2^10) */ +#define CLE266_LCD_HOR_SCF_FORMULA(x, y) (((x-1)*1024)/(y-1)) +/* Vertical scaling factor 10 bits (2^10) */ +#define CLE266_LCD_VER_SCF_FORMULA(x, y) (((x-1)*1024)/(y-1)) +/* Horizontal scaling factor 10 bits (2^12) */ +#define K800_LCD_HOR_SCF_FORMULA(x, y) (((x-1)*4096)/(y-1)) +/* Vertical scaling factor 10 bits (2^11) */ +#define K800_LCD_VER_SCF_FORMULA(x, y) (((x-1)*2048)/(y-1)) + +/* location: {CR9F,0,1},{CR77,0,7},{CR79,4,5} */ +#define LCD_HOR_SCALING_FACTOR_REG_NUM 3 +/* location: {CR79,3,3},{CR78,0,7},{CR79,6,7} */ +#define LCD_VER_SCALING_FACTOR_REG_NUM 3 +/* location: {CR77,0,7},{CR79,4,5} */ +#define LCD_HOR_SCALING_FACTOR_REG_NUM_CLE 2 +/* location: {CR78,0,7},{CR79,6,7} */ +#define LCD_VER_SCALING_FACTOR_REG_NUM_CLE 2 + +struct io_register { + u8 io_addr; + u8 start_bit; + u8 end_bit; +}; + +/***************************************************** +** Define IGA2 Shadow Display Timing **** +*****************************************************/ + +/* IGA2 Shadow Horizontal Total */ +struct iga2_shadow_hor_total { + int reg_num; + struct io_register reg[IGA2_SHADOW_HOR_TOTAL_REG_NUM]; +}; + +/* IGA2 Shadow Horizontal Blank End */ +struct iga2_shadow_hor_blank_end { + int reg_num; + struct io_register reg[IGA2_SHADOW_HOR_BLANK_END_REG_NUM]; +}; + +/* IGA2 Shadow Vertical Total */ +struct iga2_shadow_ver_total { + int reg_num; + struct io_register reg[IGA2_SHADOW_VER_TOTAL_REG_NUM]; +}; + +/* IGA2 Shadow Vertical Addressable Video */ +struct iga2_shadow_ver_addr { + int reg_num; + struct io_register reg[IGA2_SHADOW_VER_ADDR_REG_NUM]; +}; + +/* IGA2 Shadow Vertical Blank Start */ +struct iga2_shadow_ver_blank_start { + int reg_num; + struct io_register reg[IGA2_SHADOW_VER_BLANK_START_REG_NUM]; +}; + +/* IGA2 Shadow Vertical Blank End */ +struct iga2_shadow_ver_blank_end { + int reg_num; + struct io_register reg[IGA2_SHADOW_VER_BLANK_END_REG_NUM]; +}; + +/* IGA2 Shadow Vertical Sync Start */ +struct iga2_shadow_ver_sync_start { + int reg_num; + struct io_register reg[IGA2_SHADOW_VER_SYNC_START_REG_NUM]; +}; + +/* IGA2 Shadow Vertical Sync End */ +struct iga2_shadow_ver_sync_end { + int reg_num; + struct io_register reg[IGA2_SHADOW_VER_SYNC_END_REG_NUM]; +}; + +/* IGA1 Fetch Count Register */ +struct iga1_fetch_count { + int reg_num; + struct io_register reg[IGA1_FETCH_COUNT_REG_NUM]; +}; + +/* IGA2 Fetch Count Register */ +struct iga2_fetch_count { + int reg_num; + struct io_register reg[IGA2_FETCH_COUNT_REG_NUM]; +}; + +struct fetch_count { + struct iga1_fetch_count iga1_fetch_count_reg; + struct iga2_fetch_count iga2_fetch_count_reg; +}; + +/* Starting Address Register */ +struct iga1_starting_addr { + int reg_num; + struct io_register reg[IGA1_STARTING_ADDR_REG_NUM]; +}; + +struct iga2_starting_addr { + int reg_num; + struct io_register reg[IGA2_STARTING_ADDR_REG_NUM]; +}; + +struct starting_addr { + struct iga1_starting_addr iga1_starting_addr_reg; + struct iga2_starting_addr iga2_starting_addr_reg; +}; + +/* LCD Power Sequence Timer */ +struct lcd_pwd_seq_td0 { + int reg_num; + struct io_register reg[LCD_POWER_SEQ_TD0_REG_NUM]; +}; + +struct lcd_pwd_seq_td1 { + int reg_num; + struct io_register reg[LCD_POWER_SEQ_TD1_REG_NUM]; +}; + +struct lcd_pwd_seq_td2 { + int reg_num; + struct io_register reg[LCD_POWER_SEQ_TD2_REG_NUM]; +}; + +struct lcd_pwd_seq_td3 { + int reg_num; + struct io_register reg[LCD_POWER_SEQ_TD3_REG_NUM]; +}; + +struct _lcd_pwd_seq_timer { + struct lcd_pwd_seq_td0 td0; + struct lcd_pwd_seq_td1 td1; + struct lcd_pwd_seq_td2 td2; + struct lcd_pwd_seq_td3 td3; +}; + +/* LCD Scaling Factor */ +struct _lcd_hor_scaling_factor { + int reg_num; + struct io_register reg[LCD_HOR_SCALING_FACTOR_REG_NUM]; +}; + +struct _lcd_ver_scaling_factor { + int reg_num; + struct io_register reg[LCD_VER_SCALING_FACTOR_REG_NUM]; +}; + +struct _lcd_scaling_factor { + struct _lcd_hor_scaling_factor lcd_hor_scaling_factor; + struct _lcd_ver_scaling_factor lcd_ver_scaling_factor; +}; + +struct pll_limit { + u16 multiplier_min; + u16 multiplier_max; + u8 divisor; + u8 rshift; +}; + +struct rgbLUT { + u8 red; + u8 green; + u8 blue; +}; + +struct lcd_pwd_seq_timer { + u16 td0; + u16 td1; + u16 td2; + u16 td3; +}; + +/* Display FIFO Relation Registers*/ +struct iga1_fifo_depth_select { + int reg_num; + struct io_register reg[IGA1_FIFO_DEPTH_SELECT_REG_NUM]; +}; + +struct iga1_fifo_threshold_select { + int reg_num; + struct io_register reg[IGA1_FIFO_THRESHOLD_REG_NUM]; +}; + +struct iga1_fifo_high_threshold_select { + int reg_num; + struct io_register reg[IGA1_FIFO_HIGH_THRESHOLD_REG_NUM]; +}; + +struct iga1_display_queue_expire_num { + int reg_num; + struct io_register reg[IGA1_DISPLAY_QUEUE_EXPIRE_NUM_REG_NUM]; +}; + +struct iga2_fifo_depth_select { + int reg_num; + struct io_register reg[IGA2_FIFO_DEPTH_SELECT_REG_NUM]; +}; + +struct iga2_fifo_threshold_select { + int reg_num; + struct io_register reg[IGA2_FIFO_THRESHOLD_REG_NUM]; +}; + +struct iga2_fifo_high_threshold_select { + int reg_num; + struct io_register reg[IGA2_FIFO_HIGH_THRESHOLD_REG_NUM]; +}; + +struct iga2_display_queue_expire_num { + int reg_num; + struct io_register reg[IGA2_DISPLAY_QUEUE_EXPIRE_NUM_REG_NUM]; +}; + +struct fifo_depth_select { + struct iga1_fifo_depth_select iga1_fifo_depth_select_reg; + struct iga2_fifo_depth_select iga2_fifo_depth_select_reg; +}; + +struct fifo_threshold_select { + struct iga1_fifo_threshold_select iga1_fifo_threshold_select_reg; + struct iga2_fifo_threshold_select iga2_fifo_threshold_select_reg; +}; + +struct fifo_high_threshold_select { + struct iga1_fifo_high_threshold_select + iga1_fifo_high_threshold_select_reg; + struct iga2_fifo_high_threshold_select + iga2_fifo_high_threshold_select_reg; +}; + +struct display_queue_expire_num { + struct iga1_display_queue_expire_num + iga1_display_queue_expire_num_reg; + struct iga2_display_queue_expire_num + iga2_display_queue_expire_num_reg; +}; + +struct iga2_shadow_crtc_timing { + struct iga2_shadow_hor_total hor_total_shadow; + struct iga2_shadow_hor_blank_end hor_blank_end_shadow; + struct iga2_shadow_ver_total ver_total_shadow; + struct iga2_shadow_ver_addr ver_addr_shadow; + struct iga2_shadow_ver_blank_start ver_blank_start_shadow; + struct iga2_shadow_ver_blank_end ver_blank_end_shadow; + struct iga2_shadow_ver_sync_start ver_sync_start_shadow; + struct iga2_shadow_ver_sync_end ver_sync_end_shadow; +}; + +/* device ID */ +#define CLE266_FUNCTION3 0x3123 +#define KM400_FUNCTION3 0x3205 +#define CN400_FUNCTION2 0x2259 +#define CN400_FUNCTION3 0x3259 +/* support VT3314 chipset */ +#define CN700_FUNCTION2 0x2314 +#define CN700_FUNCTION3 0x3208 +/* VT3324 chipset */ +#define CX700_FUNCTION2 0x2324 +#define CX700_FUNCTION3 0x3324 +/* VT3204 chipset*/ +#define KM800_FUNCTION3 0x3204 +/* VT3336 chipset*/ +#define KM890_FUNCTION3 0x3336 +/* VT3327 chipset*/ +#define P4M890_FUNCTION3 0x3327 +/* VT3293 chipset*/ +#define CN750_FUNCTION3 0x3208 +/* VT3364 chipset*/ +#define P4M900_FUNCTION3 0x3364 +/* VT3353 chipset*/ +#define VX800_FUNCTION3 0x3353 +/* VT3409 chipset*/ +#define VX855_FUNCTION3 0x3409 +/* VT3410 chipset*/ +#define VX900_FUNCTION3 0x3410 + +struct IODATA { + u8 Index; + u8 Mask; + u8 Data; +}; + +struct pci_device_id_info { + u32 vendor; + u32 device; + u32 chip_index; +}; + +struct via_device_mapping { + u32 device; + const char *name; +}; + +extern int viafb_SAMM_ON; +extern int viafb_dual_fb; +extern int viafb_LCD2_ON; +extern int viafb_LCD_ON; +extern int viafb_DVI_ON; +extern int viafb_hotplug; + +struct via_display_timing var_to_timing(const struct fb_var_screeninfo *var, + u16 cxres, u16 cyres); +void viafb_fill_crtc_timing(const struct fb_var_screeninfo *var, + u16 cxres, u16 cyres, int iga); +void viafb_set_vclock(u32 CLK, int set_iga); +void viafb_load_reg(int timing_value, int viafb_load_reg_num, + struct io_register *reg, + int io_type); +void via_set_source(u32 devices, u8 iga); +void via_set_state(u32 devices, u8 state); +void via_set_sync_polarity(u32 devices, u8 polarity); +u32 via_parse_odev(char *input, char **end); +void via_odev_to_seq(struct seq_file *m, u32 odev); +void init_ad9389(void); +/* Access I/O Function */ +void viafb_lock_crt(void); +void viafb_unlock_crt(void); +void viafb_load_fetch_count_reg(int h_addr, int bpp_byte, int set_iga); +void viafb_write_regx(struct io_reg RegTable[], int ItemNum); +void viafb_load_FIFO_reg(int set_iga, int hor_active, int ver_active); +void viafb_set_dpa_gfx(int output_interface, struct GFX_DPA_SETTING\ + *p_gfx_dpa_setting); + +int viafb_setmode(void); +void viafb_fill_var_timing_info(struct fb_var_screeninfo *var, + const struct fb_videomode *mode); +void viafb_init_chip_info(int chip_type); +void viafb_init_dac(int set_iga); +int viafb_get_refresh(int hres, int vres, u32 float_refresh); +void viafb_update_device_setting(int hres, int vres, int bpp, int flag); + +void viafb_set_iga_path(void); +void viafb_set_primary_color_register(u8 index, u8 red, u8 green, u8 blue); +void viafb_set_secondary_color_register(u8 index, u8 red, u8 green, u8 blue); +void viafb_get_fb_info(unsigned int *fb_base, unsigned int *fb_len); + +#endif /* __HW_H__ */ diff --git a/drivers/video/fbdev/via/ioctl.c b/drivers/video/fbdev/via/ioctl.c new file mode 100644 index 000000000000..ea1c51428823 --- /dev/null +++ b/drivers/video/fbdev/via/ioctl.c @@ -0,0 +1,116 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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 "global.h" + +int viafb_ioctl_get_viafb_info(u_long arg) +{ + struct viafb_ioctl_info viainfo; + + memset(&viainfo, 0, sizeof(struct viafb_ioctl_info)); + + viainfo.viafb_id = VIAID; + viainfo.vendor_id = PCI_VIA_VENDOR_ID; + + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CLE266: + viainfo.device_id = UNICHROME_CLE266_DID; + break; + + case UNICHROME_K400: + viainfo.device_id = UNICHROME_K400_DID; + break; + + case UNICHROME_K800: + viainfo.device_id = UNICHROME_K800_DID; + break; + + case UNICHROME_PM800: + viainfo.device_id = UNICHROME_PM800_DID; + break; + + case UNICHROME_CN700: + viainfo.device_id = UNICHROME_CN700_DID; + break; + + case UNICHROME_CX700: + viainfo.device_id = UNICHROME_CX700_DID; + break; + + case UNICHROME_K8M890: + viainfo.device_id = UNICHROME_K8M890_DID; + break; + + case UNICHROME_P4M890: + viainfo.device_id = UNICHROME_P4M890_DID; + break; + + case UNICHROME_P4M900: + viainfo.device_id = UNICHROME_P4M900_DID; + break; + } + + viainfo.version = VERSION_MAJOR; + viainfo.revision = VERSION_MINOR; + + if (copy_to_user((void __user *)arg, &viainfo, sizeof(viainfo))) + return -EFAULT; + + return 0; +} + +/* Hot-Plug Priority: DVI > CRT*/ +int viafb_ioctl_hotplug(int hres, int vres, int bpp) +{ + int DVIsense, status = 0; + DEBUG_MSG(KERN_INFO "viafb_ioctl_hotplug!!\n"); + + if (viaparinfo->chip_info->tmds_chip_info.tmds_chip_name != + NON_TMDS_TRANSMITTER) { + DVIsense = viafb_dvi_sense(); + + if (DVIsense) { + DEBUG_MSG(KERN_INFO "DVI Attached...\n"); + if (viafb_DeviceStatus != DVI_Device) { + viafb_DVI_ON = 1; + viafb_CRT_ON = 0; + viafb_LCD_ON = 0; + viafb_DeviceStatus = DVI_Device; + viafb_set_iga_path(); + return viafb_DeviceStatus; + } + status = 1; + } else + DEBUG_MSG(KERN_INFO "DVI De-attached...\n"); + } + + if ((viafb_DeviceStatus != CRT_Device) && (status == 0)) { + viafb_CRT_ON = 1; + viafb_DVI_ON = 0; + viafb_LCD_ON = 0; + + viafb_DeviceStatus = CRT_Device; + viafb_set_iga_path(); + return viafb_DeviceStatus; + } + + return 0; +} diff --git a/drivers/video/fbdev/via/ioctl.h b/drivers/video/fbdev/via/ioctl.h new file mode 100644 index 000000000000..6010d10b59e8 --- /dev/null +++ b/drivers/video/fbdev/via/ioctl.h @@ -0,0 +1,203 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __IOCTL_H__ +#define __IOCTL_H__ + +#ifndef __user +#define __user +#endif + +/* VIAFB IOCTL definition */ +#define VIAFB_GET_INFO_SIZE 0x56494101 /* 'VIA\01' */ +#define VIAFB_GET_INFO 0x56494102 /* 'VIA\02' */ +#define VIAFB_HOTPLUG 0x56494103 /* 'VIA\03' */ +#define VIAFB_SET_HOTPLUG_FLAG 0x56494104 /* 'VIA\04' */ +#define VIAFB_GET_RESOLUTION 0x56494105 /* 'VIA\05' */ +#define VIAFB_GET_SAMM_INFO 0x56494107 /* 'VIA\07' */ +#define VIAFB_TURN_ON_OUTPUT_DEVICE 0x56494108 /* 'VIA\08' */ +#define VIAFB_TURN_OFF_OUTPUT_DEVICE 0x56494109 /* 'VIA\09' */ +#define VIAFB_GET_DEVICE 0x5649410B +#define VIAFB_GET_DRIVER_VERSION 0x56494112 /* 'VIA\12' */ +#define VIAFB_GET_CHIP_INFO 0x56494113 /* 'VIA\13' */ +#define VIAFB_GET_DEVICE_INFO 0x56494115 + +#define VIAFB_GET_DEVICE_SUPPORT 0x56494118 +#define VIAFB_GET_DEVICE_CONNECT 0x56494119 +#define VIAFB_GET_PANEL_SUPPORT_EXPAND 0x5649411A +#define VIAFB_GET_DRIVER_NAME 0x56494122 +#define VIAFB_GET_DEVICE_SUPPORT_STATE 0x56494123 +#define VIAFB_GET_GAMMA_LUT 0x56494124 +#define VIAFB_SET_GAMMA_LUT 0x56494125 +#define VIAFB_GET_GAMMA_SUPPORT_STATE 0x56494126 +#define VIAFB_SYNC_SURFACE 0x56494130 +#define VIAFB_GET_DRIVER_CAPS 0x56494131 +#define VIAFB_GET_IGA_SCALING_INFO 0x56494132 +#define VIAFB_GET_PANEL_MAX_SIZE 0x56494133 +#define VIAFB_GET_PANEL_MAX_POSITION 0x56494134 +#define VIAFB_SET_PANEL_SIZE 0x56494135 +#define VIAFB_SET_PANEL_POSITION 0x56494136 +#define VIAFB_GET_PANEL_POSITION 0x56494137 +#define VIAFB_GET_PANEL_SIZE 0x56494138 + +#define None_Device 0x00 +#define CRT_Device 0x01 +#define LCD_Device 0x02 +#define DVI_Device 0x08 +#define CRT2_Device 0x10 +#define LCD2_Device 0x40 + +#define OP_LCD_CENTERING 0x01 +#define OP_LCD_PANEL_ID 0x02 +#define OP_LCD_MODE 0x03 + +/*SAMM operation flag*/ +#define OP_SAMM 0x80 + +#define LCD_PANEL_ID_MAXIMUM 23 + +#define STATE_ON 0x1 +#define STATE_OFF 0x0 +#define STATE_DEFAULT 0xFFFF + +#define MAX_ACTIVE_DEV_NUM 2 + +struct device_t { + unsigned short crt:1; + unsigned short dvi:1; + unsigned short lcd:1; + unsigned short samm:1; + unsigned short lcd_dsp_cent:1; + unsigned char lcd_mode:1; + unsigned short epia_dvi:1; + unsigned short lcd_dual_edge:1; + unsigned short lcd2:1; + + unsigned short primary_dev; + unsigned char lcd_panel_id; + unsigned short xres, yres; + unsigned short xres1, yres1; + unsigned short refresh; + unsigned short bpp; + unsigned short refresh1; + unsigned short bpp1; + unsigned short sequence; + unsigned short bus_width; +}; + +struct viafb_ioctl_info { + u32 viafb_id; /* for identifying viafb */ +#define VIAID 0x56494146 /* Identify myself with 'VIAF' */ + u16 vendor_id; + u16 device_id; + u8 version; + u8 revision; + u8 reserved[246]; /* for future use */ +}; + +struct viafb_ioctl_mode { + u32 xres; + u32 yres; + u32 refresh; + u32 bpp; + u32 xres_sec; + u32 yres_sec; + u32 virtual_xres_sec; + u32 virtual_yres_sec; + u32 refresh_sec; + u32 bpp_sec; +}; +struct viafb_ioctl_samm { + u32 samm_status; + u32 size_prim; + u32 size_sec; + u32 mem_base; + u32 offset_sec; +}; + +struct viafb_driver_version { + int iMajorNum; + int iKernelNum; + int iOSNum; + int iMinorNum; +}; + +struct viafb_ioctl_lcd_attribute { + unsigned int panel_id; + unsigned int display_center; + unsigned int lcd_mode; +}; + +struct viafb_ioctl_setting { + /* Enable or disable active devices */ + unsigned short device_flag; + /* Indicate which device should be turn on or turn off. */ + unsigned short device_status; + unsigned int reserved; + /* Indicate which LCD's attribute can be changed. */ + unsigned short lcd_operation_flag; + /* 1: SAMM ON 0: SAMM OFF */ + unsigned short samm_status; + /* horizontal resolution of first device */ + unsigned short first_dev_hor_res; + /* vertical resolution of first device */ + unsigned short first_dev_ver_res; + /* horizontal resolution of second device */ + unsigned short second_dev_hor_res; + /* vertical resolution of second device */ + unsigned short second_dev_ver_res; + /* refresh rate of first device */ + unsigned short first_dev_refresh; + /* bpp of first device */ + unsigned short first_dev_bpp; + /* refresh rate of second device */ + unsigned short second_dev_refresh; + /* bpp of second device */ + unsigned short second_dev_bpp; + /* Indicate which device are primary display device. */ + unsigned int primary_device; + unsigned int struct_reserved[35]; + struct viafb_ioctl_lcd_attribute lcd_attributes; +}; + +struct _UTFunctionCaps { + unsigned int dw3DScalingState; + unsigned int reserved[31]; +}; + +struct _POSITIONVALUE { + unsigned int dwX; + unsigned int dwY; +}; + +struct _panel_size_pos_info { + unsigned int device_type; + int x; + int y; +}; + +extern int viafb_LCD_ON; +extern int viafb_DVI_ON; + +int viafb_ioctl_get_viafb_info(u_long arg); +int viafb_ioctl_hotplug(int hres, int vres, int bpp); + +#endif /* __IOCTL_H__ */ diff --git a/drivers/video/fbdev/via/lcd.c b/drivers/video/fbdev/via/lcd.c new file mode 100644 index 000000000000..5d21ff436ec8 --- /dev/null +++ b/drivers/video/fbdev/via/lcd.c @@ -0,0 +1,1005 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/via-core.h> +#include <linux/via_i2c.h> +#include "global.h" + +#define viafb_compact_res(x, y) (((x)<<16)|(y)) + +/* CLE266 Software Power Sequence */ +/* {Mask}, {Data}, {Delay} */ +static const int PowerSequenceOn[3][3] = { + {0x10, 0x08, 0x06}, {0x10, 0x08, 0x06}, {0x19, 0x1FE, 0x01} +}; +static const int PowerSequenceOff[3][3] = { + {0x06, 0x08, 0x10}, {0x00, 0x00, 0x00}, {0xD2, 0x19, 0x01} +}; + +static struct _lcd_scaling_factor lcd_scaling_factor = { + /* LCD Horizontal Scaling Factor Register */ + {LCD_HOR_SCALING_FACTOR_REG_NUM, + {{CR9F, 0, 1}, {CR77, 0, 7}, {CR79, 4, 5} } }, + /* LCD Vertical Scaling Factor Register */ + {LCD_VER_SCALING_FACTOR_REG_NUM, + {{CR79, 3, 3}, {CR78, 0, 7}, {CR79, 6, 7} } } +}; +static struct _lcd_scaling_factor lcd_scaling_factor_CLE = { + /* LCD Horizontal Scaling Factor Register */ + {LCD_HOR_SCALING_FACTOR_REG_NUM_CLE, {{CR77, 0, 7}, {CR79, 4, 5} } }, + /* LCD Vertical Scaling Factor Register */ + {LCD_VER_SCALING_FACTOR_REG_NUM_CLE, {{CR78, 0, 7}, {CR79, 6, 7} } } +}; + +static bool lvds_identify_integratedlvds(void); +static void fp_id_to_vindex(int panel_id); +static int lvds_register_read(int index); +static void load_lcd_scaling(int set_hres, int set_vres, int panel_hres, + int panel_vres); +static void lcd_patch_skew_dvp0(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +static void lcd_patch_skew_dvp1(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +static void lcd_patch_skew(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information *plvds_chip_info); + +static void integrated_lvds_disable(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +static void integrated_lvds_enable(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +static void lcd_powersequence_off(void); +static void lcd_powersequence_on(void); +static void fill_lcd_format(void); +static void check_diport_of_integrated_lvds( + struct lvds_chip_information *plvds_chip_info, + struct lvds_setting_information + *plvds_setting_info); + +static inline bool check_lvds_chip(int device_id_subaddr, int device_id) +{ + return lvds_register_read(device_id_subaddr) == device_id; +} + +void viafb_init_lcd_size(void) +{ + DEBUG_MSG(KERN_INFO "viafb_init_lcd_size()\n"); + + fp_id_to_vindex(viafb_lcd_panel_id); + viaparinfo->lvds_setting_info2->lcd_panel_hres = + viaparinfo->lvds_setting_info->lcd_panel_hres; + viaparinfo->lvds_setting_info2->lcd_panel_vres = + viaparinfo->lvds_setting_info->lcd_panel_vres; + viaparinfo->lvds_setting_info2->device_lcd_dualedge = + viaparinfo->lvds_setting_info->device_lcd_dualedge; + viaparinfo->lvds_setting_info2->LCDDithering = + viaparinfo->lvds_setting_info->LCDDithering; +} + +static bool lvds_identify_integratedlvds(void) +{ + if (viafb_display_hardware_layout == HW_LAYOUT_LCD_EXTERNAL_LCD2) { + /* Two dual channel LCD (Internal LVDS + External LVDS): */ + /* If we have an external LVDS, such as VT1636, we should + have its chip ID already. */ + if (viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) { + viaparinfo->chip_info->lvds_chip_info2.lvds_chip_name = + INTEGRATED_LVDS; + DEBUG_MSG(KERN_INFO "Support two dual channel LVDS! " + "(Internal LVDS + External LVDS)\n"); + } else { + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name = + INTEGRATED_LVDS; + DEBUG_MSG(KERN_INFO "Not found external LVDS, " + "so can't support two dual channel LVDS!\n"); + } + } else if (viafb_display_hardware_layout == HW_LAYOUT_LCD1_LCD2) { + /* Two single channel LCD (Internal LVDS + Internal LVDS): */ + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name = + INTEGRATED_LVDS; + viaparinfo->chip_info->lvds_chip_info2.lvds_chip_name = + INTEGRATED_LVDS; + DEBUG_MSG(KERN_INFO "Support two single channel LVDS! " + "(Internal LVDS + Internal LVDS)\n"); + } else if (viafb_display_hardware_layout != HW_LAYOUT_DVI_ONLY) { + /* If we have found external LVDS, just use it, + otherwise, we will use internal LVDS as default. */ + if (!viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) { + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name = + INTEGRATED_LVDS; + DEBUG_MSG(KERN_INFO "Found Integrated LVDS!\n"); + } + } else { + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name = + NON_LVDS_TRANSMITTER; + DEBUG_MSG(KERN_INFO "Do not support LVDS!\n"); + return false; + } + + return true; +} + +bool viafb_lvds_trasmitter_identify(void) +{ + if (viafb_lvds_identify_vt1636(VIA_PORT_31)) { + viaparinfo->chip_info->lvds_chip_info.i2c_port = VIA_PORT_31; + DEBUG_MSG(KERN_INFO + "Found VIA VT1636 LVDS on port i2c 0x31\n"); + } else { + if (viafb_lvds_identify_vt1636(VIA_PORT_2C)) { + viaparinfo->chip_info->lvds_chip_info.i2c_port = + VIA_PORT_2C; + DEBUG_MSG(KERN_INFO + "Found VIA VT1636 LVDS on port gpio 0x2c\n"); + } + } + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) + lvds_identify_integratedlvds(); + + if (viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) + return true; + /* Check for VT1631: */ + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name = VT1631_LVDS; + viaparinfo->chip_info->lvds_chip_info.lvds_chip_slave_addr = + VT1631_LVDS_I2C_ADDR; + + if (check_lvds_chip(VT1631_DEVICE_ID_REG, VT1631_DEVICE_ID)) { + DEBUG_MSG(KERN_INFO "\n VT1631 LVDS ! \n"); + DEBUG_MSG(KERN_INFO "\n %2d", + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name); + DEBUG_MSG(KERN_INFO "\n %2d", + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name); + return true; + } + + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name = + NON_LVDS_TRANSMITTER; + viaparinfo->chip_info->lvds_chip_info.lvds_chip_slave_addr = + VT1631_LVDS_I2C_ADDR; + return false; +} + +static void fp_id_to_vindex(int panel_id) +{ + DEBUG_MSG(KERN_INFO "fp_get_panel_id()\n"); + + if (panel_id > LCD_PANEL_ID_MAXIMUM) + viafb_lcd_panel_id = panel_id = + viafb_read_reg(VIACR, CR3F) & 0x0F; + + switch (panel_id) { + case 0x0: + viaparinfo->lvds_setting_info->lcd_panel_hres = 640; + viaparinfo->lvds_setting_info->lcd_panel_vres = 480; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x1: + viaparinfo->lvds_setting_info->lcd_panel_hres = 800; + viaparinfo->lvds_setting_info->lcd_panel_vres = 600; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x2: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1024; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x3: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1280; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x4: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1280; + viaparinfo->lvds_setting_info->lcd_panel_vres = 1024; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x5: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1400; + viaparinfo->lvds_setting_info->lcd_panel_vres = 1050; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x6: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1600; + viaparinfo->lvds_setting_info->lcd_panel_vres = 1200; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x8: + viaparinfo->lvds_setting_info->lcd_panel_hres = 800; + viaparinfo->lvds_setting_info->lcd_panel_vres = 480; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x9: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1024; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0xA: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1024; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0xB: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1024; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0xC: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1280; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0xD: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1280; + viaparinfo->lvds_setting_info->lcd_panel_vres = 1024; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0xE: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1400; + viaparinfo->lvds_setting_info->lcd_panel_vres = 1050; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0xF: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1600; + viaparinfo->lvds_setting_info->lcd_panel_vres = 1200; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0x10: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1366; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0x11: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1024; + viaparinfo->lvds_setting_info->lcd_panel_vres = 600; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x12: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1280; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x13: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1280; + viaparinfo->lvds_setting_info->lcd_panel_vres = 800; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x14: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1360; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0x15: + viaparinfo->lvds_setting_info->lcd_panel_hres = 1280; + viaparinfo->lvds_setting_info->lcd_panel_vres = 768; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 1; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + case 0x16: + viaparinfo->lvds_setting_info->lcd_panel_hres = 480; + viaparinfo->lvds_setting_info->lcd_panel_vres = 640; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + break; + case 0x17: + /* OLPC XO-1.5 panel */ + viaparinfo->lvds_setting_info->lcd_panel_hres = 1200; + viaparinfo->lvds_setting_info->lcd_panel_vres = 900; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 0; + break; + default: + viaparinfo->lvds_setting_info->lcd_panel_hres = 800; + viaparinfo->lvds_setting_info->lcd_panel_vres = 600; + viaparinfo->lvds_setting_info->device_lcd_dualedge = 0; + viaparinfo->lvds_setting_info->LCDDithering = 1; + } +} + +static int lvds_register_read(int index) +{ + u8 data; + + viafb_i2c_readbyte(VIA_PORT_2C, + (u8) viaparinfo->chip_info->lvds_chip_info.lvds_chip_slave_addr, + (u8) index, &data); + return data; +} + +static void load_lcd_scaling(int set_hres, int set_vres, int panel_hres, + int panel_vres) +{ + int reg_value = 0; + int viafb_load_reg_num; + struct io_register *reg = NULL; + + DEBUG_MSG(KERN_INFO "load_lcd_scaling()!!\n"); + + /* LCD Scaling Enable */ + viafb_write_reg_mask(CR79, VIACR, 0x07, BIT0 + BIT1 + BIT2); + + /* Check if expansion for horizontal */ + if (set_hres < panel_hres) { + /* Load Horizontal Scaling Factor */ + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CLE266: + case UNICHROME_K400: + reg_value = + CLE266_LCD_HOR_SCF_FORMULA(set_hres, panel_hres); + viafb_load_reg_num = + lcd_scaling_factor_CLE.lcd_hor_scaling_factor. + reg_num; + reg = lcd_scaling_factor_CLE.lcd_hor_scaling_factor.reg; + viafb_load_reg(reg_value, + viafb_load_reg_num, reg, VIACR); + break; + case UNICHROME_K800: + case UNICHROME_PM800: + case UNICHROME_CN700: + case UNICHROME_CX700: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + case UNICHROME_CN750: + case UNICHROME_VX800: + case UNICHROME_VX855: + case UNICHROME_VX900: + reg_value = + K800_LCD_HOR_SCF_FORMULA(set_hres, panel_hres); + /* Horizontal scaling enabled */ + viafb_write_reg_mask(CRA2, VIACR, 0xC0, BIT7 + BIT6); + viafb_load_reg_num = + lcd_scaling_factor.lcd_hor_scaling_factor.reg_num; + reg = lcd_scaling_factor.lcd_hor_scaling_factor.reg; + viafb_load_reg(reg_value, + viafb_load_reg_num, reg, VIACR); + break; + } + + DEBUG_MSG(KERN_INFO "Horizontal Scaling value = %d", reg_value); + } else { + /* Horizontal scaling disabled */ + viafb_write_reg_mask(CRA2, VIACR, 0x00, BIT7); + } + + /* Check if expansion for vertical */ + if (set_vres < panel_vres) { + /* Load Vertical Scaling Factor */ + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CLE266: + case UNICHROME_K400: + reg_value = + CLE266_LCD_VER_SCF_FORMULA(set_vres, panel_vres); + viafb_load_reg_num = + lcd_scaling_factor_CLE.lcd_ver_scaling_factor. + reg_num; + reg = lcd_scaling_factor_CLE.lcd_ver_scaling_factor.reg; + viafb_load_reg(reg_value, + viafb_load_reg_num, reg, VIACR); + break; + case UNICHROME_K800: + case UNICHROME_PM800: + case UNICHROME_CN700: + case UNICHROME_CX700: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + case UNICHROME_CN750: + case UNICHROME_VX800: + case UNICHROME_VX855: + case UNICHROME_VX900: + reg_value = + K800_LCD_VER_SCF_FORMULA(set_vres, panel_vres); + /* Vertical scaling enabled */ + viafb_write_reg_mask(CRA2, VIACR, 0x08, BIT3); + viafb_load_reg_num = + lcd_scaling_factor.lcd_ver_scaling_factor.reg_num; + reg = lcd_scaling_factor.lcd_ver_scaling_factor.reg; + viafb_load_reg(reg_value, + viafb_load_reg_num, reg, VIACR); + break; + } + + DEBUG_MSG(KERN_INFO "Vertical Scaling value = %d", reg_value); + } else { + /* Vertical scaling disabled */ + viafb_write_reg_mask(CRA2, VIACR, 0x00, BIT3); + } +} + +static void via_pitch_alignment_patch_lcd(int iga_path, int hres, int bpp) +{ + unsigned char cr13, cr35, cr65, cr66, cr67; + unsigned long dwScreenPitch = 0; + unsigned long dwPitch; + + dwPitch = hres * (bpp >> 3); + if (dwPitch & 0x1F) { + dwScreenPitch = ((dwPitch + 31) & ~31) >> 3; + if (iga_path == IGA2) { + if (bpp > 8) { + cr66 = (unsigned char)(dwScreenPitch & 0xFF); + viafb_write_reg(CR66, VIACR, cr66); + cr67 = viafb_read_reg(VIACR, CR67) & 0xFC; + cr67 |= + (unsigned + char)((dwScreenPitch & 0x300) >> 8); + viafb_write_reg(CR67, VIACR, cr67); + } + + /* Fetch Count */ + cr67 = viafb_read_reg(VIACR, CR67) & 0xF3; + cr67 |= (unsigned char)((dwScreenPitch & 0x600) >> 7); + viafb_write_reg(CR67, VIACR, cr67); + cr65 = (unsigned char)((dwScreenPitch >> 1) & 0xFF); + cr65 += 2; + viafb_write_reg(CR65, VIACR, cr65); + } else { + if (bpp > 8) { + cr13 = (unsigned char)(dwScreenPitch & 0xFF); + viafb_write_reg(CR13, VIACR, cr13); + cr35 = viafb_read_reg(VIACR, CR35) & 0x1F; + cr35 |= + (unsigned + char)((dwScreenPitch & 0x700) >> 3); + viafb_write_reg(CR35, VIACR, cr35); + } + } + } +} +static void lcd_patch_skew_dvp0(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + if (VT1636_LVDS == plvds_chip_info->lvds_chip_name) { + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_P4M900: + viafb_vt1636_patch_skew_on_vt3364(plvds_setting_info, + plvds_chip_info); + break; + case UNICHROME_P4M890: + viafb_vt1636_patch_skew_on_vt3327(plvds_setting_info, + plvds_chip_info); + break; + } + } +} +static void lcd_patch_skew_dvp1(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + if (VT1636_LVDS == plvds_chip_info->lvds_chip_name) { + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CX700: + viafb_vt1636_patch_skew_on_vt3324(plvds_setting_info, + plvds_chip_info); + break; + } + } +} +static void lcd_patch_skew(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information *plvds_chip_info) +{ + DEBUG_MSG(KERN_INFO "lcd_patch_skew\n"); + switch (plvds_chip_info->output_interface) { + case INTERFACE_DVP0: + lcd_patch_skew_dvp0(plvds_setting_info, plvds_chip_info); + break; + case INTERFACE_DVP1: + lcd_patch_skew_dvp1(plvds_setting_info, plvds_chip_info); + break; + case INTERFACE_DFP_LOW: + if (UNICHROME_P4M900 == viaparinfo->chip_info->gfx_chip_name) { + viafb_write_reg_mask(CR99, VIACR, 0x08, + BIT0 + BIT1 + BIT2 + BIT3); + } + break; + } +} + +/* LCD Set Mode */ +void viafb_lcd_set_mode(const struct fb_var_screeninfo *var, u16 cxres, + u16 cyres, struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + int set_iga = plvds_setting_info->iga_path; + int mode_bpp = var->bits_per_pixel; + int set_hres = cxres ? cxres : var->xres; + int set_vres = cyres ? cyres : var->yres; + int panel_hres = plvds_setting_info->lcd_panel_hres; + int panel_vres = plvds_setting_info->lcd_panel_vres; + u32 clock; + struct via_display_timing timing; + struct fb_var_screeninfo panel_var; + const struct fb_videomode *mode_crt_table, *panel_crt_table; + + DEBUG_MSG(KERN_INFO "viafb_lcd_set_mode!!\n"); + /* Get mode table */ + mode_crt_table = viafb_get_best_mode(set_hres, set_vres, 60); + /* Get panel table Pointer */ + panel_crt_table = viafb_get_best_mode(panel_hres, panel_vres, 60); + viafb_fill_var_timing_info(&panel_var, panel_crt_table); + DEBUG_MSG(KERN_INFO "bellow viafb_lcd_set_mode!!\n"); + if (VT1636_LVDS == plvds_chip_info->lvds_chip_name) + viafb_init_lvds_vt1636(plvds_setting_info, plvds_chip_info); + clock = PICOS2KHZ(panel_crt_table->pixclock) * 1000; + plvds_setting_info->vclk = clock; + + if (set_iga == IGA2 && (set_hres < panel_hres || set_vres < panel_vres) + && plvds_setting_info->display_method == LCD_EXPANDSION) { + timing = var_to_timing(&panel_var, panel_hres, panel_vres); + load_lcd_scaling(set_hres, set_vres, panel_hres, panel_vres); + } else { + timing = var_to_timing(&panel_var, set_hres, set_vres); + if (set_iga == IGA2) + /* disable scaling */ + via_write_reg_mask(VIACR, 0x79, 0x00, + BIT0 + BIT1 + BIT2); + } + + if (set_iga == IGA1) + via_set_primary_timing(&timing); + else if (set_iga == IGA2) + via_set_secondary_timing(&timing); + + /* Fetch count for IGA2 only */ + viafb_load_fetch_count_reg(set_hres, mode_bpp / 8, set_iga); + + if ((viaparinfo->chip_info->gfx_chip_name != UNICHROME_CLE266) + && (viaparinfo->chip_info->gfx_chip_name != UNICHROME_K400)) + viafb_load_FIFO_reg(set_iga, set_hres, set_vres); + + fill_lcd_format(); + viafb_set_vclock(clock, set_iga); + lcd_patch_skew(plvds_setting_info, plvds_chip_info); + + /* If K8M800, enable LCD Prefetch Mode. */ + if ((viaparinfo->chip_info->gfx_chip_name == UNICHROME_K800) + || (UNICHROME_K8M890 == viaparinfo->chip_info->gfx_chip_name)) + viafb_write_reg_mask(CR6A, VIACR, 0x01, BIT0); + + /* Patch for non 32bit alignment mode */ + via_pitch_alignment_patch_lcd(plvds_setting_info->iga_path, set_hres, + var->bits_per_pixel); +} + +static void integrated_lvds_disable(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + bool turn_off_first_powersequence = false; + bool turn_off_second_powersequence = false; + if (INTERFACE_LVDS0LVDS1 == plvds_chip_info->output_interface) + turn_off_first_powersequence = true; + if (INTERFACE_LVDS0 == plvds_chip_info->output_interface) + turn_off_first_powersequence = true; + if (INTERFACE_LVDS1 == plvds_chip_info->output_interface) + turn_off_second_powersequence = true; + if (turn_off_second_powersequence) { + /* Use second power sequence control: */ + + /* Turn off power sequence. */ + viafb_write_reg_mask(CRD4, VIACR, 0, BIT1); + + /* Turn off back light. */ + viafb_write_reg_mask(CRD3, VIACR, 0xC0, BIT6 + BIT7); + } + if (turn_off_first_powersequence) { + /* Use first power sequence control: */ + + /* Turn off power sequence. */ + viafb_write_reg_mask(CR6A, VIACR, 0, BIT3); + + /* Turn off back light. */ + viafb_write_reg_mask(CR91, VIACR, 0xC0, BIT6 + BIT7); + } + + /* Power off LVDS channel. */ + switch (plvds_chip_info->output_interface) { + case INTERFACE_LVDS0: + { + viafb_write_reg_mask(CRD2, VIACR, 0x80, BIT7); + break; + } + + case INTERFACE_LVDS1: + { + viafb_write_reg_mask(CRD2, VIACR, 0x40, BIT6); + break; + } + + case INTERFACE_LVDS0LVDS1: + { + viafb_write_reg_mask(CRD2, VIACR, 0xC0, BIT6 + BIT7); + break; + } + } +} + +static void integrated_lvds_enable(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + DEBUG_MSG(KERN_INFO "integrated_lvds_enable, out_interface:%d\n", + plvds_chip_info->output_interface); + if (plvds_setting_info->lcd_mode == LCD_SPWG) + viafb_write_reg_mask(CRD2, VIACR, 0x00, BIT0 + BIT1); + else + viafb_write_reg_mask(CRD2, VIACR, 0x03, BIT0 + BIT1); + + switch (plvds_chip_info->output_interface) { + case INTERFACE_LVDS0LVDS1: + case INTERFACE_LVDS0: + /* Use first power sequence control: */ + /* Use hardware control power sequence. */ + viafb_write_reg_mask(CR91, VIACR, 0, BIT0); + /* Turn on back light. */ + viafb_write_reg_mask(CR91, VIACR, 0, BIT6 + BIT7); + /* Turn on hardware power sequence. */ + viafb_write_reg_mask(CR6A, VIACR, 0x08, BIT3); + break; + case INTERFACE_LVDS1: + /* Use second power sequence control: */ + /* Use hardware control power sequence. */ + viafb_write_reg_mask(CRD3, VIACR, 0, BIT0); + /* Turn on back light. */ + viafb_write_reg_mask(CRD3, VIACR, 0, BIT6 + BIT7); + /* Turn on hardware power sequence. */ + viafb_write_reg_mask(CRD4, VIACR, 0x02, BIT1); + break; + } + + /* Power on LVDS channel. */ + switch (plvds_chip_info->output_interface) { + case INTERFACE_LVDS0: + { + viafb_write_reg_mask(CRD2, VIACR, 0, BIT7); + break; + } + + case INTERFACE_LVDS1: + { + viafb_write_reg_mask(CRD2, VIACR, 0, BIT6); + break; + } + + case INTERFACE_LVDS0LVDS1: + { + viafb_write_reg_mask(CRD2, VIACR, 0, BIT6 + BIT7); + break; + } + } +} + +void viafb_lcd_disable(void) +{ + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) { + lcd_powersequence_off(); + /* DI1 pad off */ + viafb_write_reg_mask(SR1E, VIASR, 0x00, 0x30); + } else if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) { + if (viafb_LCD2_ON + && (INTEGRATED_LVDS == + viaparinfo->chip_info->lvds_chip_info2.lvds_chip_name)) + integrated_lvds_disable(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info2); + if (INTEGRATED_LVDS == + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) + integrated_lvds_disable(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info); + if (VT1636_LVDS == viaparinfo->chip_info-> + lvds_chip_info.lvds_chip_name) + viafb_disable_lvds_vt1636(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info); + } else if (VT1636_LVDS == + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) { + viafb_disable_lvds_vt1636(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info); + } else { + /* Backlight off */ + viafb_write_reg_mask(SR3D, VIASR, 0x00, 0x20); + /* 24 bit DI data paht off */ + viafb_write_reg_mask(CR91, VIACR, 0x80, 0x80); + } + + /* Disable expansion bit */ + viafb_write_reg_mask(CR79, VIACR, 0x00, 0x01); + /* Simultaneout disabled */ + viafb_write_reg_mask(CR6B, VIACR, 0x00, 0x08); +} + +static void set_lcd_output_path(int set_iga, int output_interface) +{ + switch (output_interface) { + case INTERFACE_DFP: + if ((UNICHROME_K8M890 == viaparinfo->chip_info->gfx_chip_name) + || (UNICHROME_P4M890 == + viaparinfo->chip_info->gfx_chip_name)) + viafb_write_reg_mask(CR97, VIACR, 0x84, + BIT7 + BIT2 + BIT1 + BIT0); + case INTERFACE_DVP0: + case INTERFACE_DVP1: + case INTERFACE_DFP_HIGH: + case INTERFACE_DFP_LOW: + if (set_iga == IGA2) + viafb_write_reg(CR91, VIACR, 0x00); + break; + } +} + +void viafb_lcd_enable(void) +{ + viafb_write_reg_mask(CR6B, VIACR, 0x00, BIT3); + viafb_write_reg_mask(CR6A, VIACR, 0x08, BIT3); + set_lcd_output_path(viaparinfo->lvds_setting_info->iga_path, + viaparinfo->chip_info->lvds_chip_info.output_interface); + if (viafb_LCD2_ON) + set_lcd_output_path(viaparinfo->lvds_setting_info2->iga_path, + viaparinfo->chip_info-> + lvds_chip_info2.output_interface); + + if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266) { + /* DI1 pad on */ + viafb_write_reg_mask(SR1E, VIASR, 0x30, 0x30); + lcd_powersequence_on(); + } else if (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CX700) { + if (viafb_LCD2_ON && (INTEGRATED_LVDS == + viaparinfo->chip_info->lvds_chip_info2.lvds_chip_name)) + integrated_lvds_enable(viaparinfo->lvds_setting_info2, \ + &viaparinfo->chip_info->lvds_chip_info2); + if (INTEGRATED_LVDS == + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) + integrated_lvds_enable(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info); + if (VT1636_LVDS == viaparinfo->chip_info-> + lvds_chip_info.lvds_chip_name) + viafb_enable_lvds_vt1636(viaparinfo-> + lvds_setting_info, &viaparinfo->chip_info-> + lvds_chip_info); + } else if (VT1636_LVDS == + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) { + viafb_enable_lvds_vt1636(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info); + } else { + /* Backlight on */ + viafb_write_reg_mask(SR3D, VIASR, 0x20, 0x20); + /* 24 bit DI data paht on */ + viafb_write_reg_mask(CR91, VIACR, 0x00, 0x80); + /* LCD enabled */ + viafb_write_reg_mask(CR6A, VIACR, 0x48, 0x48); + } +} + +static void lcd_powersequence_off(void) +{ + int i, mask, data; + + /* Software control power sequence */ + viafb_write_reg_mask(CR91, VIACR, 0x11, 0x11); + + for (i = 0; i < 3; i++) { + mask = PowerSequenceOff[0][i]; + data = PowerSequenceOff[1][i] & mask; + viafb_write_reg_mask(CR91, VIACR, (u8) data, (u8) mask); + udelay(PowerSequenceOff[2][i]); + } + + /* Disable LCD */ + viafb_write_reg_mask(CR6A, VIACR, 0x00, 0x08); +} + +static void lcd_powersequence_on(void) +{ + int i, mask, data; + + /* Software control power sequence */ + viafb_write_reg_mask(CR91, VIACR, 0x11, 0x11); + + /* Enable LCD */ + viafb_write_reg_mask(CR6A, VIACR, 0x08, 0x08); + + for (i = 0; i < 3; i++) { + mask = PowerSequenceOn[0][i]; + data = PowerSequenceOn[1][i] & mask; + viafb_write_reg_mask(CR91, VIACR, (u8) data, (u8) mask); + udelay(PowerSequenceOn[2][i]); + } + + udelay(1); +} + +static void fill_lcd_format(void) +{ + u8 bdithering = 0, bdual = 0; + + if (viaparinfo->lvds_setting_info->device_lcd_dualedge) + bdual = BIT4; + if (viaparinfo->lvds_setting_info->LCDDithering) + bdithering = BIT0; + /* Dual & Dithering */ + viafb_write_reg_mask(CR88, VIACR, (bdithering | bdual), BIT4 + BIT0); +} + +static void check_diport_of_integrated_lvds( + struct lvds_chip_information *plvds_chip_info, + struct lvds_setting_information + *plvds_setting_info) +{ + /* Determine LCD DI Port by hardware layout. */ + switch (viafb_display_hardware_layout) { + case HW_LAYOUT_LCD_ONLY: + { + if (plvds_setting_info->device_lcd_dualedge) { + plvds_chip_info->output_interface = + INTERFACE_LVDS0LVDS1; + } else { + plvds_chip_info->output_interface = + INTERFACE_LVDS0; + } + + break; + } + + case HW_LAYOUT_DVI_ONLY: + { + plvds_chip_info->output_interface = INTERFACE_NONE; + break; + } + + case HW_LAYOUT_LCD1_LCD2: + case HW_LAYOUT_LCD_EXTERNAL_LCD2: + { + plvds_chip_info->output_interface = + INTERFACE_LVDS0LVDS1; + break; + } + + case HW_LAYOUT_LCD_DVI: + { + plvds_chip_info->output_interface = INTERFACE_LVDS1; + break; + } + + default: + { + plvds_chip_info->output_interface = INTERFACE_LVDS1; + break; + } + } + + DEBUG_MSG(KERN_INFO + "Display Hardware Layout: 0x%x, LCD DI Port: 0x%x\n", + viafb_display_hardware_layout, + plvds_chip_info->output_interface); +} + +void viafb_init_lvds_output_interface(struct lvds_chip_information + *plvds_chip_info, + struct lvds_setting_information + *plvds_setting_info) +{ + if (INTERFACE_NONE != plvds_chip_info->output_interface) { + /*Do nothing, lcd port is specified by module parameter */ + return; + } + + switch (plvds_chip_info->lvds_chip_name) { + + case VT1636_LVDS: + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CX700: + plvds_chip_info->output_interface = INTERFACE_DVP1; + break; + case UNICHROME_CN700: + plvds_chip_info->output_interface = INTERFACE_DFP_LOW; + break; + default: + plvds_chip_info->output_interface = INTERFACE_DVP0; + break; + } + break; + + case INTEGRATED_LVDS: + check_diport_of_integrated_lvds(plvds_chip_info, + plvds_setting_info); + break; + + default: + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_K8M890: + case UNICHROME_P4M900: + case UNICHROME_P4M890: + plvds_chip_info->output_interface = INTERFACE_DFP_LOW; + break; + default: + plvds_chip_info->output_interface = INTERFACE_DFP; + break; + } + break; + } +} + +bool viafb_lcd_get_mobile_state(bool *mobile) +{ + unsigned char __iomem *romptr, *tableptr, *biosptr; + u8 core_base; + /* Rom address */ + const u32 romaddr = 0x000C0000; + u16 start_pattern; + + biosptr = ioremap(romaddr, 0x10000); + start_pattern = readw(biosptr); + + /* Compare pattern */ + if (start_pattern == 0xAA55) { + /* Get the start of Table */ + /* 0x1B means BIOS offset position */ + romptr = biosptr + 0x1B; + tableptr = biosptr + readw(romptr); + + /* Get the start of biosver structure */ + /* 18 means BIOS version position. */ + romptr = tableptr + 18; + romptr = biosptr + readw(romptr); + + /* The offset should be 44, but the + actual image is less three char. */ + /* pRom += 44; */ + romptr += 41; + + core_base = readb(romptr); + + if (core_base & 0x8) + *mobile = false; + else + *mobile = true; + /* release memory */ + iounmap(biosptr); + + return true; + } else { + iounmap(biosptr); + return false; + } +} diff --git a/drivers/video/fbdev/via/lcd.h b/drivers/video/fbdev/via/lcd.h new file mode 100644 index 000000000000..5c988a063ad5 --- /dev/null +++ b/drivers/video/fbdev/via/lcd.h @@ -0,0 +1,89 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +#ifndef __LCD_H__ +#define __LCD_H__ + +/*Definition TMDS Device ID register*/ +#define VT1631_DEVICE_ID_REG 0x02 +#define VT1631_DEVICE_ID 0x92 + +#define VT3271_DEVICE_ID_REG 0x02 +#define VT3271_DEVICE_ID 0x71 + +/* Definition DVI Panel ID*/ +/* Resolution: 640x480, Channel: single, Dithering: Enable */ +#define LCD_PANEL_ID0_640X480 0x00 +/* Resolution: 800x600, Channel: single, Dithering: Enable */ +#define LCD_PANEL_ID1_800X600 0x01 +/* Resolution: 1024x768, Channel: single, Dithering: Enable */ +#define LCD_PANEL_ID2_1024X768 0x02 +/* Resolution: 1280x768, Channel: single, Dithering: Enable */ +#define LCD_PANEL_ID3_1280X768 0x03 +/* Resolution: 1280x1024, Channel: dual, Dithering: Enable */ +#define LCD_PANEL_ID4_1280X1024 0x04 +/* Resolution: 1400x1050, Channel: dual, Dithering: Enable */ +#define LCD_PANEL_ID5_1400X1050 0x05 +/* Resolution: 1600x1200, Channel: dual, Dithering: Enable */ +#define LCD_PANEL_ID6_1600X1200 0x06 +/* Resolution: 1366x768, Channel: single, Dithering: Disable */ +#define LCD_PANEL_ID7_1366X768 0x07 +/* Resolution: 1024x600, Channel: single, Dithering: Enable*/ +#define LCD_PANEL_ID8_1024X600 0x08 +/* Resolution: 1280x800, Channel: single, Dithering: Enable*/ +#define LCD_PANEL_ID9_1280X800 0x09 +/* Resolution: 800x480, Channel: single, Dithering: Enable*/ +#define LCD_PANEL_IDA_800X480 0x0A +/* Resolution: 1360x768, Channel: single, Dithering: Disable*/ +#define LCD_PANEL_IDB_1360X768 0x0B +/* Resolution: 480x640, Channel: single, Dithering: Enable */ +#define LCD_PANEL_IDC_480X640 0x0C +/* Resolution: 1200x900, Channel: single, Dithering: Disable */ +#define LCD_PANEL_IDD_1200X900 0x0D + + +extern int viafb_LCD2_ON; +extern int viafb_LCD_ON; +extern int viafb_DVI_ON; + +void viafb_disable_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +void viafb_enable_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +void viafb_lcd_disable(void); +void viafb_lcd_enable(void); +void viafb_init_lcd_size(void); +void viafb_init_lvds_output_interface(struct lvds_chip_information + *plvds_chip_info, + struct lvds_setting_information + *plvds_setting_info); +void viafb_lcd_set_mode(const struct fb_var_screeninfo *var, u16 cxres, + u16 cyres, struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +bool viafb_lvds_trasmitter_identify(void); +void viafb_init_lvds_output_interface(struct lvds_chip_information + *plvds_chip_info, + struct lvds_setting_information + *plvds_setting_info); +bool viafb_lcd_get_mobile_state(bool *mobile); + +#endif /* __LCD_H__ */ diff --git a/drivers/video/fbdev/via/share.h b/drivers/video/fbdev/via/share.h new file mode 100644 index 000000000000..65c65c611e0a --- /dev/null +++ b/drivers/video/fbdev/via/share.h @@ -0,0 +1,332 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __SHARE_H__ +#define __SHARE_H__ + +#include "via_modesetting.h" + +/* Define Bit Field */ +#define BIT0 0x01 +#define BIT1 0x02 +#define BIT2 0x04 +#define BIT3 0x08 +#define BIT4 0x10 +#define BIT5 0x20 +#define BIT6 0x40 +#define BIT7 0x80 + +/* Video Memory Size */ +#define VIDEO_MEMORY_SIZE_16M 0x1000000 + +/* + * Lengths of the VPIT structure arrays. + */ +#define StdCR 0x19 +#define StdSR 0x04 +#define StdGR 0x09 +#define StdAR 0x14 + +#define PatchCR 11 + +/* Display path */ +#define IGA1 1 +#define IGA2 2 + +/* Define Color Depth */ +#define MODE_8BPP 1 +#define MODE_16BPP 2 +#define MODE_32BPP 4 + +#define GR20 0x20 +#define GR21 0x21 +#define GR22 0x22 + +/* Sequencer Registers */ +#define SR01 0x01 +#define SR10 0x10 +#define SR12 0x12 +#define SR15 0x15 +#define SR16 0x16 +#define SR17 0x17 +#define SR18 0x18 +#define SR1B 0x1B +#define SR1A 0x1A +#define SR1C 0x1C +#define SR1D 0x1D +#define SR1E 0x1E +#define SR1F 0x1F +#define SR20 0x20 +#define SR21 0x21 +#define SR22 0x22 +#define SR2A 0x2A +#define SR2D 0x2D +#define SR2E 0x2E + +#define SR30 0x30 +#define SR39 0x39 +#define SR3D 0x3D +#define SR3E 0x3E +#define SR3F 0x3F +#define SR40 0x40 +#define SR43 0x43 +#define SR44 0x44 +#define SR45 0x45 +#define SR46 0x46 +#define SR47 0x47 +#define SR48 0x48 +#define SR49 0x49 +#define SR4A 0x4A +#define SR4B 0x4B +#define SR4C 0x4C +#define SR52 0x52 +#define SR57 0x57 +#define SR58 0x58 +#define SR59 0x59 +#define SR5D 0x5D +#define SR5E 0x5E +#define SR65 0x65 + +/* CRT Controller Registers */ +#define CR00 0x00 +#define CR01 0x01 +#define CR02 0x02 +#define CR03 0x03 +#define CR04 0x04 +#define CR05 0x05 +#define CR06 0x06 +#define CR07 0x07 +#define CR08 0x08 +#define CR09 0x09 +#define CR0A 0x0A +#define CR0B 0x0B +#define CR0C 0x0C +#define CR0D 0x0D +#define CR0E 0x0E +#define CR0F 0x0F +#define CR10 0x10 +#define CR11 0x11 +#define CR12 0x12 +#define CR13 0x13 +#define CR14 0x14 +#define CR15 0x15 +#define CR16 0x16 +#define CR17 0x17 +#define CR18 0x18 + +/* Extend CRT Controller Registers */ +#define CR30 0x30 +#define CR31 0x31 +#define CR32 0x32 +#define CR33 0x33 +#define CR34 0x34 +#define CR35 0x35 +#define CR36 0x36 +#define CR37 0x37 +#define CR38 0x38 +#define CR39 0x39 +#define CR3A 0x3A +#define CR3B 0x3B +#define CR3C 0x3C +#define CR3D 0x3D +#define CR3E 0x3E +#define CR3F 0x3F +#define CR40 0x40 +#define CR41 0x41 +#define CR42 0x42 +#define CR43 0x43 +#define CR44 0x44 +#define CR45 0x45 +#define CR46 0x46 +#define CR47 0x47 +#define CR48 0x48 +#define CR49 0x49 +#define CR4A 0x4A +#define CR4B 0x4B +#define CR4C 0x4C +#define CR4D 0x4D +#define CR4E 0x4E +#define CR4F 0x4F +#define CR50 0x50 +#define CR51 0x51 +#define CR52 0x52 +#define CR53 0x53 +#define CR54 0x54 +#define CR55 0x55 +#define CR56 0x56 +#define CR57 0x57 +#define CR58 0x58 +#define CR59 0x59 +#define CR5A 0x5A +#define CR5B 0x5B +#define CR5C 0x5C +#define CR5D 0x5D +#define CR5E 0x5E +#define CR5F 0x5F +#define CR60 0x60 +#define CR61 0x61 +#define CR62 0x62 +#define CR63 0x63 +#define CR64 0x64 +#define CR65 0x65 +#define CR66 0x66 +#define CR67 0x67 +#define CR68 0x68 +#define CR69 0x69 +#define CR6A 0x6A +#define CR6B 0x6B +#define CR6C 0x6C +#define CR6D 0x6D +#define CR6E 0x6E +#define CR6F 0x6F +#define CR70 0x70 +#define CR71 0x71 +#define CR72 0x72 +#define CR73 0x73 +#define CR74 0x74 +#define CR75 0x75 +#define CR76 0x76 +#define CR77 0x77 +#define CR78 0x78 +#define CR79 0x79 +#define CR7A 0x7A +#define CR7B 0x7B +#define CR7C 0x7C +#define CR7D 0x7D +#define CR7E 0x7E +#define CR7F 0x7F +#define CR80 0x80 +#define CR81 0x81 +#define CR82 0x82 +#define CR83 0x83 +#define CR84 0x84 +#define CR85 0x85 +#define CR86 0x86 +#define CR87 0x87 +#define CR88 0x88 +#define CR89 0x89 +#define CR8A 0x8A +#define CR8B 0x8B +#define CR8C 0x8C +#define CR8D 0x8D +#define CR8E 0x8E +#define CR8F 0x8F +#define CR90 0x90 +#define CR91 0x91 +#define CR92 0x92 +#define CR93 0x93 +#define CR94 0x94 +#define CR95 0x95 +#define CR96 0x96 +#define CR97 0x97 +#define CR98 0x98 +#define CR99 0x99 +#define CR9A 0x9A +#define CR9B 0x9B +#define CR9C 0x9C +#define CR9D 0x9D +#define CR9E 0x9E +#define CR9F 0x9F +#define CRA0 0xA0 +#define CRA1 0xA1 +#define CRA2 0xA2 +#define CRA3 0xA3 +#define CRD2 0xD2 +#define CRD3 0xD3 +#define CRD4 0xD4 + +/* LUT Table*/ +#define LUT_DATA 0x3C9 /* DACDATA */ +#define LUT_INDEX_READ 0x3C7 /* DACRX */ +#define LUT_INDEX_WRITE 0x3C8 /* DACWX */ +#define DACMASK 0x3C6 + +/* Definition Device */ +#define DEVICE_CRT 0x01 +#define DEVICE_DVI 0x03 +#define DEVICE_LCD 0x04 + +/* Device output interface */ +#define INTERFACE_NONE 0x00 +#define INTERFACE_ANALOG_RGB 0x01 +#define INTERFACE_DVP0 0x02 +#define INTERFACE_DVP1 0x03 +#define INTERFACE_DFP_HIGH 0x04 +#define INTERFACE_DFP_LOW 0x05 +#define INTERFACE_DFP 0x06 +#define INTERFACE_LVDS0 0x07 +#define INTERFACE_LVDS1 0x08 +#define INTERFACE_LVDS0LVDS1 0x09 +#define INTERFACE_TMDS 0x0A + +#define HW_LAYOUT_LCD_ONLY 0x01 +#define HW_LAYOUT_DVI_ONLY 0x02 +#define HW_LAYOUT_LCD_DVI 0x03 +#define HW_LAYOUT_LCD1_LCD2 0x04 +#define HW_LAYOUT_LCD_EXTERNAL_LCD2 0x10 + +/* Definition CRTC Timing Index */ +#define H_TOTAL_INDEX 0 +#define H_ADDR_INDEX 1 +#define H_BLANK_START_INDEX 2 +#define H_BLANK_END_INDEX 3 +#define H_SYNC_START_INDEX 4 +#define H_SYNC_END_INDEX 5 +#define V_TOTAL_INDEX 6 +#define V_ADDR_INDEX 7 +#define V_BLANK_START_INDEX 8 +#define V_BLANK_END_INDEX 9 +#define V_SYNC_START_INDEX 10 +#define V_SYNC_END_INDEX 11 +#define H_TOTAL_SHADOW_INDEX 12 +#define H_BLANK_END_SHADOW_INDEX 13 +#define V_TOTAL_SHADOW_INDEX 14 +#define V_ADDR_SHADOW_INDEX 15 +#define V_BLANK_SATRT_SHADOW_INDEX 16 +#define V_BLANK_END_SHADOW_INDEX 17 +#define V_SYNC_SATRT_SHADOW_INDEX 18 +#define V_SYNC_END_SHADOW_INDEX 19 + +/* LCD display method +*/ +#define LCD_EXPANDSION 0x00 +#define LCD_CENTERING 0x01 + +/* LCD mode +*/ +#define LCD_OPENLDI 0x00 +#define LCD_SPWG 0x01 + +struct crt_mode_table { + int refresh_rate; + int h_sync_polarity; + int v_sync_polarity; + struct via_display_timing crtc; +}; + +struct io_reg { + int port; + u8 index; + u8 mask; + u8 value; +}; + +#endif /* __SHARE_H__ */ diff --git a/drivers/video/fbdev/via/tblDPASetting.c b/drivers/video/fbdev/via/tblDPASetting.c new file mode 100644 index 000000000000..73bb554e7c1e --- /dev/null +++ b/drivers/video/fbdev/via/tblDPASetting.c @@ -0,0 +1,86 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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 "global.h" + +struct GFX_DPA_SETTING GFX_DPA_SETTING_TBL_VT3324[] = { +/* ClkRange, DVP0, DVP0DataDriving, DVP0ClockDriving, DVP1, + DVP1Driving, DFPHigh, DFPLow */ +/* CR96, SR2A[5], SR1B[1], SR2A[4], SR1E[2], CR9B, + SR65, CR97, CR99 */ + /* LCK/VCK < 30000000 will use this value */ + {DPA_CLK_RANGE_30M, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00}, + /* 30000000 < LCK/VCK < 50000000 will use this value */ + {DPA_CLK_RANGE_30_50M, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00}, + /* 50000000 < LCK/VCK < 70000000 will use this value */ + {DPA_CLK_RANGE_50_70M, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00}, + /* 70000000 < LCK/VCK < 100000000 will use this value */ + {DPA_CLK_RANGE_70_100M, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00}, + /* 100000000 < LCK/VCK < 15000000 will use this value */ + {DPA_CLK_RANGE_100_150M, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00}, + /* 15000000 < LCK/VCK will use this value */ + {DPA_CLK_RANGE_150M, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0E, 0x00, + 0x00}, +}; + +struct GFX_DPA_SETTING GFX_DPA_SETTING_TBL_VT3327[] = { +/* ClkRange,DVP0, DVP0DataDriving, DVP0ClockDriving, DVP1, + DVP1Driving, DFPHigh, DFPLow */ +/* CR96, SR2A[5], SR1B[1], SR2A[4], SR1E[2], CR9B, + SR65, CR97, CR99 */ +/* LCK/VCK < 30000000 will use this value */ +{DPA_CLK_RANGE_30M, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x08, 0x01}, +/* 30000000 < LCK/VCK < 50000000 will use this value */ +{DPA_CLK_RANGE_30_50M, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x08, 0x01}, +/* 50000000 < LCK/VCK < 70000000 will use this value */ +{DPA_CLK_RANGE_50_70M, 0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x08, 0x01}, +/* 70000000 < LCK/VCK < 100000000 will use this value */ +{DPA_CLK_RANGE_70_100M, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x08, 0x03}, +/* 100000000 < LCK/VCK < 15000000 will use this value */ +{DPA_CLK_RANGE_100_150M, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02}, +/* 15000000 < LCK/VCK will use this value */ +{DPA_CLK_RANGE_150M, 0x00, 0x20, 0x00, 0x10, 0x00, 0x03, 0x00, 0x0D, 0x03}, +}; + +/* For VT3364: */ +struct GFX_DPA_SETTING GFX_DPA_SETTING_TBL_VT3364[] = { +/* ClkRange,DVP0, DVP0DataDriving, DVP0ClockDriving, DVP1, + DVP1Driving, DFPHigh, DFPLow */ +/* CR96, SR2A[5], SR1B[1], SR2A[4], SR1E[2], CR9B, + SR65, CR97, CR99 */ +/* LCK/VCK < 30000000 will use this value */ +{DPA_CLK_RANGE_30M, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x08}, +/* 30000000 < LCK/VCK < 50000000 will use this value */ +{DPA_CLK_RANGE_30_50M, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x08}, +/* 50000000 < LCK/VCK < 70000000 will use this value */ +{DPA_CLK_RANGE_50_70M, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x08}, +/* 70000000 < LCK/VCK < 100000000 will use this value */ +{DPA_CLK_RANGE_70_100M, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x08}, +/* 100000000 < LCK/VCK < 15000000 will use this value */ +{DPA_CLK_RANGE_100_150M, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x08}, +/* 15000000 < LCK/VCK will use this value */ +{DPA_CLK_RANGE_150M, 0x01, 0x00, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x08}, +}; diff --git a/drivers/video/fbdev/via/tblDPASetting.h b/drivers/video/fbdev/via/tblDPASetting.h new file mode 100644 index 000000000000..6db61519cb5d --- /dev/null +++ b/drivers/video/fbdev/via/tblDPASetting.h @@ -0,0 +1,45 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef _TBLDPASETTING_H_ +#define _TBLDPASETTING_H_ +#include "global.h" + +#define DPA_CLK_30M 30000000 +#define DPA_CLK_50M 50000000 +#define DPA_CLK_70M 70000000 +#define DPA_CLK_100M 100000000 +#define DPA_CLK_150M 150000000 + +enum DPA_RANGE { + DPA_CLK_RANGE_30M, + DPA_CLK_RANGE_30_50M, + DPA_CLK_RANGE_50_70M, + DPA_CLK_RANGE_70_100M, + DPA_CLK_RANGE_100_150M, + DPA_CLK_RANGE_150M +}; + +extern struct GFX_DPA_SETTING GFX_DPA_SETTING_TBL_VT3324[6]; +extern struct GFX_DPA_SETTING GFX_DPA_SETTING_TBL_VT3327[]; +extern struct GFX_DPA_SETTING GFX_DPA_SETTING_TBL_VT3364[6]; + +#endif diff --git a/drivers/video/fbdev/via/via-core.c b/drivers/video/fbdev/via/via-core.c new file mode 100644 index 000000000000..6e274825fb31 --- /dev/null +++ b/drivers/video/fbdev/via/via-core.c @@ -0,0 +1,790 @@ +/* + * Copyright 1998-2009 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + * Copyright 2009 Jonathan Corbet <corbet@lwn.net> + */ + +/* + * Core code for the Via multifunction framebuffer device. + */ +#include <linux/via-core.h> +#include <linux/via_i2c.h> +#include <linux/via-gpio.h> +#include "global.h" + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/pm.h> +#include <asm/olpc.h> + +/* + * The default port config. + */ +static struct via_port_cfg adap_configs[] = { + [VIA_PORT_26] = { VIA_PORT_I2C, VIA_MODE_I2C, VIASR, 0x26 }, + [VIA_PORT_31] = { VIA_PORT_I2C, VIA_MODE_I2C, VIASR, 0x31 }, + [VIA_PORT_25] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x25 }, + [VIA_PORT_2C] = { VIA_PORT_GPIO, VIA_MODE_I2C, VIASR, 0x2c }, + [VIA_PORT_3D] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x3d }, + { 0, 0, 0, 0 } +}; + +/* + * The OLPC XO-1.5 puts the camera power and reset lines onto + * GPIO 2C. + */ +static struct via_port_cfg olpc_adap_configs[] = { + [VIA_PORT_26] = { VIA_PORT_I2C, VIA_MODE_I2C, VIASR, 0x26 }, + [VIA_PORT_31] = { VIA_PORT_I2C, VIA_MODE_I2C, VIASR, 0x31 }, + [VIA_PORT_25] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x25 }, + [VIA_PORT_2C] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x2c }, + [VIA_PORT_3D] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x3d }, + { 0, 0, 0, 0 } +}; + +/* + * We currently only support one viafb device (will there ever be + * more than one?), so just declare it globally here. + */ +static struct viafb_dev global_dev; + + +/* + * Basic register access; spinlock required. + */ +static inline void viafb_mmio_write(int reg, u32 v) +{ + iowrite32(v, global_dev.engine_mmio + reg); +} + +static inline int viafb_mmio_read(int reg) +{ + return ioread32(global_dev.engine_mmio + reg); +} + +/* ---------------------------------------------------------------------- */ +/* + * Interrupt management. We have a single IRQ line for a lot of + * different functions, so we need to share it. The design here + * is that we don't want to reimplement the shared IRQ code here; + * we also want to avoid having contention for a single handler thread. + * So each subdev driver which needs interrupts just requests + * them directly from the kernel. We just have what's needed for + * overall access to the interrupt control register. + */ + +/* + * Which interrupts are enabled now? + */ +static u32 viafb_enabled_ints; + +static void viafb_int_init(void) +{ + viafb_enabled_ints = 0; + + viafb_mmio_write(VDE_INTERRUPT, 0); +} + +/* + * Allow subdevs to ask for specific interrupts to be enabled. These + * functions must be called with reg_lock held + */ +void viafb_irq_enable(u32 mask) +{ + viafb_enabled_ints |= mask; + viafb_mmio_write(VDE_INTERRUPT, viafb_enabled_ints | VDE_I_ENABLE); +} +EXPORT_SYMBOL_GPL(viafb_irq_enable); + +void viafb_irq_disable(u32 mask) +{ + viafb_enabled_ints &= ~mask; + if (viafb_enabled_ints == 0) + viafb_mmio_write(VDE_INTERRUPT, 0); /* Disable entirely */ + else + viafb_mmio_write(VDE_INTERRUPT, + viafb_enabled_ints | VDE_I_ENABLE); +} +EXPORT_SYMBOL_GPL(viafb_irq_disable); + +/* ---------------------------------------------------------------------- */ +/* + * Currently, the camera driver is the only user of the DMA code, so we + * only compile it in if the camera driver is being built. Chances are, + * most viafb systems will not need to have this extra code for a while. + * As soon as another user comes long, the ifdef can be removed. + */ +#if defined(CONFIG_VIDEO_VIA_CAMERA) || defined(CONFIG_VIDEO_VIA_CAMERA_MODULE) +/* + * Access to the DMA engine. This currently provides what the camera + * driver needs (i.e. outgoing only) but is easily expandable if need + * be. + */ + +/* + * There are four DMA channels in the vx855. For now, we only + * use one of them, though. Most of the time, the DMA channel + * will be idle, so we keep the IRQ handler unregistered except + * when some subsystem has indicated an interest. + */ +static int viafb_dma_users; +static DECLARE_COMPLETION(viafb_dma_completion); +/* + * This mutex protects viafb_dma_users and our global interrupt + * registration state; it also serializes access to the DMA + * engine. + */ +static DEFINE_MUTEX(viafb_dma_lock); + +/* + * The VX855 DMA descriptor (used for s/g transfers) looks + * like this. + */ +struct viafb_vx855_dma_descr { + u32 addr_low; /* Low part of phys addr */ + u32 addr_high; /* High 12 bits of addr */ + u32 fb_offset; /* Offset into FB memory */ + u32 seg_size; /* Size, 16-byte units */ + u32 tile_mode; /* "tile mode" setting */ + u32 next_desc_low; /* Next descriptor addr */ + u32 next_desc_high; + u32 pad; /* Fill out to 64 bytes */ +}; + +/* + * Flags added to the "next descriptor low" pointers + */ +#define VIAFB_DMA_MAGIC 0x01 /* ??? Just has to be there */ +#define VIAFB_DMA_FINAL_SEGMENT 0x02 /* Final segment */ + +/* + * The completion IRQ handler. + */ +static irqreturn_t viafb_dma_irq(int irq, void *data) +{ + int csr; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&global_dev.reg_lock); + csr = viafb_mmio_read(VDMA_CSR0); + if (csr & VDMA_C_DONE) { + viafb_mmio_write(VDMA_CSR0, VDMA_C_DONE); + complete(&viafb_dma_completion); + ret = IRQ_HANDLED; + } + spin_unlock(&global_dev.reg_lock); + return ret; +} + +/* + * Indicate a need for DMA functionality. + */ +int viafb_request_dma(void) +{ + int ret = 0; + + /* + * Only VX855 is supported currently. + */ + if (global_dev.chip_type != UNICHROME_VX855) + return -ENODEV; + /* + * Note the new user and set up our interrupt handler + * if need be. + */ + mutex_lock(&viafb_dma_lock); + viafb_dma_users++; + if (viafb_dma_users == 1) { + ret = request_irq(global_dev.pdev->irq, viafb_dma_irq, + IRQF_SHARED, "via-dma", &viafb_dma_users); + if (ret) + viafb_dma_users--; + else + viafb_irq_enable(VDE_I_DMA0TDEN); + } + mutex_unlock(&viafb_dma_lock); + return ret; +} +EXPORT_SYMBOL_GPL(viafb_request_dma); + +void viafb_release_dma(void) +{ + mutex_lock(&viafb_dma_lock); + viafb_dma_users--; + if (viafb_dma_users == 0) { + viafb_irq_disable(VDE_I_DMA0TDEN); + free_irq(global_dev.pdev->irq, &viafb_dma_users); + } + mutex_unlock(&viafb_dma_lock); +} +EXPORT_SYMBOL_GPL(viafb_release_dma); + + +#if 0 +/* + * Copy a single buffer from FB memory, synchronously. This code works + * but is not currently used. + */ +void viafb_dma_copy_out(unsigned int offset, dma_addr_t paddr, int len) +{ + unsigned long flags; + int csr; + + mutex_lock(&viafb_dma_lock); + init_completion(&viafb_dma_completion); + /* + * Program the controller. + */ + spin_lock_irqsave(&global_dev.reg_lock, flags); + viafb_mmio_write(VDMA_CSR0, VDMA_C_ENABLE|VDMA_C_DONE); + /* Enable ints; must happen after CSR0 write! */ + viafb_mmio_write(VDMA_MR0, VDMA_MR_TDIE); + viafb_mmio_write(VDMA_MARL0, (int) (paddr & 0xfffffff0)); + viafb_mmio_write(VDMA_MARH0, (int) ((paddr >> 28) & 0xfff)); + /* Data sheet suggests DAR0 should be <<4, but it lies */ + viafb_mmio_write(VDMA_DAR0, offset); + viafb_mmio_write(VDMA_DQWCR0, len >> 4); + viafb_mmio_write(VDMA_TMR0, 0); + viafb_mmio_write(VDMA_DPRL0, 0); + viafb_mmio_write(VDMA_DPRH0, 0); + viafb_mmio_write(VDMA_PMR0, 0); + csr = viafb_mmio_read(VDMA_CSR0); + viafb_mmio_write(VDMA_CSR0, VDMA_C_ENABLE|VDMA_C_START); + spin_unlock_irqrestore(&global_dev.reg_lock, flags); + /* + * Now we just wait until the interrupt handler says + * we're done. + */ + wait_for_completion_interruptible(&viafb_dma_completion); + viafb_mmio_write(VDMA_MR0, 0); /* Reset int enable */ + mutex_unlock(&viafb_dma_lock); +} +EXPORT_SYMBOL_GPL(viafb_dma_copy_out); +#endif + +/* + * Do a scatter/gather DMA copy from FB memory. You must have done + * a successful call to viafb_request_dma() first. + */ +int viafb_dma_copy_out_sg(unsigned int offset, struct scatterlist *sg, int nsg) +{ + struct viafb_vx855_dma_descr *descr; + void *descrpages; + dma_addr_t descr_handle; + unsigned long flags; + int i; + struct scatterlist *sgentry; + dma_addr_t nextdesc; + + /* + * Get a place to put the descriptors. + */ + descrpages = dma_alloc_coherent(&global_dev.pdev->dev, + nsg*sizeof(struct viafb_vx855_dma_descr), + &descr_handle, GFP_KERNEL); + if (descrpages == NULL) { + dev_err(&global_dev.pdev->dev, "Unable to get descr page.\n"); + return -ENOMEM; + } + mutex_lock(&viafb_dma_lock); + /* + * Fill them in. + */ + descr = descrpages; + nextdesc = descr_handle + sizeof(struct viafb_vx855_dma_descr); + for_each_sg(sg, sgentry, nsg, i) { + dma_addr_t paddr = sg_dma_address(sgentry); + descr->addr_low = paddr & 0xfffffff0; + descr->addr_high = ((u64) paddr >> 32) & 0x0fff; + descr->fb_offset = offset; + descr->seg_size = sg_dma_len(sgentry) >> 4; + descr->tile_mode = 0; + descr->next_desc_low = (nextdesc&0xfffffff0) | VIAFB_DMA_MAGIC; + descr->next_desc_high = ((u64) nextdesc >> 32) & 0x0fff; + descr->pad = 0xffffffff; /* VIA driver does this */ + offset += sg_dma_len(sgentry); + nextdesc += sizeof(struct viafb_vx855_dma_descr); + descr++; + } + descr[-1].next_desc_low = VIAFB_DMA_FINAL_SEGMENT|VIAFB_DMA_MAGIC; + /* + * Program the engine. + */ + spin_lock_irqsave(&global_dev.reg_lock, flags); + init_completion(&viafb_dma_completion); + viafb_mmio_write(VDMA_DQWCR0, 0); + viafb_mmio_write(VDMA_CSR0, VDMA_C_ENABLE|VDMA_C_DONE); + viafb_mmio_write(VDMA_MR0, VDMA_MR_TDIE | VDMA_MR_CHAIN); + viafb_mmio_write(VDMA_DPRL0, descr_handle | VIAFB_DMA_MAGIC); + viafb_mmio_write(VDMA_DPRH0, + (((u64)descr_handle >> 32) & 0x0fff) | 0xf0000); + (void) viafb_mmio_read(VDMA_CSR0); + viafb_mmio_write(VDMA_CSR0, VDMA_C_ENABLE|VDMA_C_START); + spin_unlock_irqrestore(&global_dev.reg_lock, flags); + /* + * Now we just wait until the interrupt handler says + * we're done. Except that, actually, we need to wait a little + * longer: the interrupts seem to jump the gun a little and we + * get corrupted frames sometimes. + */ + wait_for_completion_timeout(&viafb_dma_completion, 1); + msleep(1); + if ((viafb_mmio_read(VDMA_CSR0)&VDMA_C_DONE) == 0) + printk(KERN_ERR "VIA DMA timeout!\n"); + /* + * Clean up and we're done. + */ + viafb_mmio_write(VDMA_CSR0, VDMA_C_DONE); + viafb_mmio_write(VDMA_MR0, 0); /* Reset int enable */ + mutex_unlock(&viafb_dma_lock); + dma_free_coherent(&global_dev.pdev->dev, + nsg*sizeof(struct viafb_vx855_dma_descr), descrpages, + descr_handle); + return 0; +} +EXPORT_SYMBOL_GPL(viafb_dma_copy_out_sg); +#endif /* CONFIG_VIDEO_VIA_CAMERA */ + +/* ---------------------------------------------------------------------- */ +/* + * Figure out how big our framebuffer memory is. Kind of ugly, + * but evidently we can't trust the information found in the + * fbdev configuration area. + */ +static u16 via_function3[] = { + CLE266_FUNCTION3, KM400_FUNCTION3, CN400_FUNCTION3, CN700_FUNCTION3, + CX700_FUNCTION3, KM800_FUNCTION3, KM890_FUNCTION3, P4M890_FUNCTION3, + P4M900_FUNCTION3, VX800_FUNCTION3, VX855_FUNCTION3, VX900_FUNCTION3, +}; + +/* Get the BIOS-configured framebuffer size from PCI configuration space + * of function 3 in the respective chipset */ +static int viafb_get_fb_size_from_pci(int chip_type) +{ + int i; + u8 offset = 0; + u32 FBSize; + u32 VideoMemSize; + + /* search for the "FUNCTION3" device in this chipset */ + for (i = 0; i < ARRAY_SIZE(via_function3); i++) { + struct pci_dev *pdev; + + pdev = pci_get_device(PCI_VENDOR_ID_VIA, via_function3[i], + NULL); + if (!pdev) + continue; + + DEBUG_MSG(KERN_INFO "Device ID = %x\n", pdev->device); + + switch (pdev->device) { + case CLE266_FUNCTION3: + case KM400_FUNCTION3: + offset = 0xE0; + break; + case CN400_FUNCTION3: + case CN700_FUNCTION3: + case CX700_FUNCTION3: + case KM800_FUNCTION3: + case KM890_FUNCTION3: + case P4M890_FUNCTION3: + case P4M900_FUNCTION3: + case VX800_FUNCTION3: + case VX855_FUNCTION3: + case VX900_FUNCTION3: + /*case CN750_FUNCTION3: */ + offset = 0xA0; + break; + } + + if (!offset) + break; + + pci_read_config_dword(pdev, offset, &FBSize); + pci_dev_put(pdev); + } + + if (!offset) { + printk(KERN_ERR "cannot determine framebuffer size\n"); + return -EIO; + } + + FBSize = FBSize & 0x00007000; + DEBUG_MSG(KERN_INFO "FB Size = %x\n", FBSize); + + if (chip_type < UNICHROME_CX700) { + switch (FBSize) { + case 0x00004000: + VideoMemSize = (16 << 20); /*16M */ + break; + + case 0x00005000: + VideoMemSize = (32 << 20); /*32M */ + break; + + case 0x00006000: + VideoMemSize = (64 << 20); /*64M */ + break; + + default: + VideoMemSize = (32 << 20); /*32M */ + break; + } + } else { + switch (FBSize) { + case 0x00001000: + VideoMemSize = (8 << 20); /*8M */ + break; + + case 0x00002000: + VideoMemSize = (16 << 20); /*16M */ + break; + + case 0x00003000: + VideoMemSize = (32 << 20); /*32M */ + break; + + case 0x00004000: + VideoMemSize = (64 << 20); /*64M */ + break; + + case 0x00005000: + VideoMemSize = (128 << 20); /*128M */ + break; + + case 0x00006000: + VideoMemSize = (256 << 20); /*256M */ + break; + + case 0x00007000: /* Only on VX855/875 */ + VideoMemSize = (512 << 20); /*512M */ + break; + + default: + VideoMemSize = (32 << 20); /*32M */ + break; + } + } + + return VideoMemSize; +} + + +/* + * Figure out and map our MMIO regions. + */ +static int via_pci_setup_mmio(struct viafb_dev *vdev) +{ + int ret; + /* + * Hook up to the device registers. Note that we soldier + * on if it fails; the framebuffer can operate (without + * acceleration) without this region. + */ + vdev->engine_start = pci_resource_start(vdev->pdev, 1); + vdev->engine_len = pci_resource_len(vdev->pdev, 1); + vdev->engine_mmio = ioremap_nocache(vdev->engine_start, + vdev->engine_len); + if (vdev->engine_mmio == NULL) + dev_err(&vdev->pdev->dev, + "Unable to map engine MMIO; operation will be " + "slow and crippled.\n"); + /* + * Map in framebuffer memory. For now, failure here is + * fatal. Unfortunately, in the absence of significant + * vmalloc space, failure here is also entirely plausible. + * Eventually we want to move away from mapping this + * entire region. + */ + if (vdev->chip_type == UNICHROME_VX900) + vdev->fbmem_start = pci_resource_start(vdev->pdev, 2); + else + vdev->fbmem_start = pci_resource_start(vdev->pdev, 0); + ret = vdev->fbmem_len = viafb_get_fb_size_from_pci(vdev->chip_type); + if (ret < 0) + goto out_unmap; + + /* try to map less memory on failure, 8 MB should be still enough */ + for (; vdev->fbmem_len >= 8 << 20; vdev->fbmem_len /= 2) { + vdev->fbmem = ioremap_wc(vdev->fbmem_start, vdev->fbmem_len); + if (vdev->fbmem) + break; + } + + if (vdev->fbmem == NULL) { + ret = -ENOMEM; + goto out_unmap; + } + return 0; +out_unmap: + iounmap(vdev->engine_mmio); + return ret; +} + +static void via_pci_teardown_mmio(struct viafb_dev *vdev) +{ + iounmap(vdev->fbmem); + iounmap(vdev->engine_mmio); +} + +/* + * Create our subsidiary devices. + */ +static struct viafb_subdev_info { + char *name; + struct platform_device *platdev; +} viafb_subdevs[] = { + { + .name = "viafb-gpio", + }, + { + .name = "viafb-i2c", + }, +#if defined(CONFIG_VIDEO_VIA_CAMERA) || defined(CONFIG_VIDEO_VIA_CAMERA_MODULE) + { + .name = "viafb-camera", + }, +#endif +}; +#define N_SUBDEVS ARRAY_SIZE(viafb_subdevs) + +static int via_create_subdev(struct viafb_dev *vdev, + struct viafb_subdev_info *info) +{ + int ret; + + info->platdev = platform_device_alloc(info->name, -1); + if (!info->platdev) { + dev_err(&vdev->pdev->dev, "Unable to allocate pdev %s\n", + info->name); + return -ENOMEM; + } + info->platdev->dev.parent = &vdev->pdev->dev; + info->platdev->dev.platform_data = vdev; + ret = platform_device_add(info->platdev); + if (ret) { + dev_err(&vdev->pdev->dev, "Unable to add pdev %s\n", + info->name); + platform_device_put(info->platdev); + info->platdev = NULL; + } + return ret; +} + +static int via_setup_subdevs(struct viafb_dev *vdev) +{ + int i; + + /* + * Ignore return values. Even if some of the devices + * fail to be created, we'll still be able to use some + * of the rest. + */ + for (i = 0; i < N_SUBDEVS; i++) + via_create_subdev(vdev, viafb_subdevs + i); + return 0; +} + +static void via_teardown_subdevs(void) +{ + int i; + + for (i = 0; i < N_SUBDEVS; i++) + if (viafb_subdevs[i].platdev) { + viafb_subdevs[i].platdev->dev.platform_data = NULL; + platform_device_unregister(viafb_subdevs[i].platdev); + } +} + +/* + * Power management functions + */ +#ifdef CONFIG_PM +static LIST_HEAD(viafb_pm_hooks); +static DEFINE_MUTEX(viafb_pm_hooks_lock); + +void viafb_pm_register(struct viafb_pm_hooks *hooks) +{ + INIT_LIST_HEAD(&hooks->list); + + mutex_lock(&viafb_pm_hooks_lock); + list_add_tail(&hooks->list, &viafb_pm_hooks); + mutex_unlock(&viafb_pm_hooks_lock); +} +EXPORT_SYMBOL_GPL(viafb_pm_register); + +void viafb_pm_unregister(struct viafb_pm_hooks *hooks) +{ + mutex_lock(&viafb_pm_hooks_lock); + list_del(&hooks->list); + mutex_unlock(&viafb_pm_hooks_lock); +} +EXPORT_SYMBOL_GPL(viafb_pm_unregister); + +static int via_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct viafb_pm_hooks *hooks; + + if (state.event != PM_EVENT_SUSPEND) + return 0; + /* + * "I've occasionally hit a few drivers that caused suspend + * failures, and each and every time it was a driver bug, and + * the right thing to do was to just ignore the error and suspend + * anyway - returning an error code and trying to undo the suspend + * is not what anybody ever really wants, even if our model + *_allows_ for it." + * -- Linus Torvalds, Dec. 7, 2009 + */ + mutex_lock(&viafb_pm_hooks_lock); + list_for_each_entry_reverse(hooks, &viafb_pm_hooks, list) + hooks->suspend(hooks->private); + mutex_unlock(&viafb_pm_hooks_lock); + + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return 0; +} + +static int via_resume(struct pci_dev *pdev) +{ + struct viafb_pm_hooks *hooks; + + /* Get the bus side powered up */ + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + if (pci_enable_device(pdev)) + return 0; + + pci_set_master(pdev); + + /* Now bring back any subdevs */ + mutex_lock(&viafb_pm_hooks_lock); + list_for_each_entry(hooks, &viafb_pm_hooks, list) + hooks->resume(hooks->private); + mutex_unlock(&viafb_pm_hooks_lock); + + return 0; +} +#endif /* CONFIG_PM */ + +static int via_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int ret; + + ret = pci_enable_device(pdev); + if (ret) + return ret; + + /* + * Global device initialization. + */ + memset(&global_dev, 0, sizeof(global_dev)); + global_dev.pdev = pdev; + global_dev.chip_type = ent->driver_data; + global_dev.port_cfg = adap_configs; + if (machine_is_olpc()) + global_dev.port_cfg = olpc_adap_configs; + + spin_lock_init(&global_dev.reg_lock); + ret = via_pci_setup_mmio(&global_dev); + if (ret) + goto out_disable; + /* + * Set up interrupts and create our subdevices. Continue even if + * some things fail. + */ + viafb_int_init(); + via_setup_subdevs(&global_dev); + /* + * Set up the framebuffer device + */ + ret = via_fb_pci_probe(&global_dev); + if (ret) + goto out_subdevs; + return 0; + +out_subdevs: + via_teardown_subdevs(); + via_pci_teardown_mmio(&global_dev); +out_disable: + pci_disable_device(pdev); + return ret; +} + +static void via_pci_remove(struct pci_dev *pdev) +{ + via_teardown_subdevs(); + via_fb_pci_remove(pdev); + via_pci_teardown_mmio(&global_dev); + pci_disable_device(pdev); +} + + +static struct pci_device_id via_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CLE266_DID), + .driver_data = UNICHROME_CLE266 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_K400_DID), + .driver_data = UNICHROME_K400 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_K800_DID), + .driver_data = UNICHROME_K800 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_PM800_DID), + .driver_data = UNICHROME_PM800 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CN700_DID), + .driver_data = UNICHROME_CN700 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CX700_DID), + .driver_data = UNICHROME_CX700 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CN750_DID), + .driver_data = UNICHROME_CN750 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_K8M890_DID), + .driver_data = UNICHROME_K8M890 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_P4M890_DID), + .driver_data = UNICHROME_P4M890 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_P4M900_DID), + .driver_data = UNICHROME_P4M900 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_VX800_DID), + .driver_data = UNICHROME_VX800 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_VX855_DID), + .driver_data = UNICHROME_VX855 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_VX900_DID), + .driver_data = UNICHROME_VX900 }, + { } +}; +MODULE_DEVICE_TABLE(pci, via_pci_table); + +static struct pci_driver via_driver = { + .name = "viafb", + .id_table = via_pci_table, + .probe = via_pci_probe, + .remove = via_pci_remove, +#ifdef CONFIG_PM + .suspend = via_suspend, + .resume = via_resume, +#endif +}; + +static int __init via_core_init(void) +{ + int ret; + + ret = viafb_init(); + if (ret) + return ret; + viafb_i2c_init(); + viafb_gpio_init(); + return pci_register_driver(&via_driver); +} + +static void __exit via_core_exit(void) +{ + pci_unregister_driver(&via_driver); + viafb_gpio_exit(); + viafb_i2c_exit(); + viafb_exit(); +} + +module_init(via_core_init); +module_exit(via_core_exit); diff --git a/drivers/video/fbdev/via/via-gpio.c b/drivers/video/fbdev/via/via-gpio.c new file mode 100644 index 000000000000..e408679081ab --- /dev/null +++ b/drivers/video/fbdev/via/via-gpio.c @@ -0,0 +1,316 @@ +/* + * Support for viafb GPIO ports. + * + * Copyright 2009 Jonathan Corbet <corbet@lwn.net> + * Distributable under version 2 of the GNU General Public License. + */ + +#include <linux/spinlock.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/via-core.h> +#include <linux/via-gpio.h> +#include <linux/export.h> + +/* + * The ports we know about. Note that the port-25 gpios are not + * mentioned in the datasheet. + */ + +struct viafb_gpio { + char *vg_name; /* Data sheet name */ + u16 vg_io_port; + u8 vg_port_index; + int vg_mask_shift; +}; + +static struct viafb_gpio viafb_all_gpios[] = { + { + .vg_name = "VGPIO0", /* Guess - not in datasheet */ + .vg_io_port = VIASR, + .vg_port_index = 0x25, + .vg_mask_shift = 1 + }, + { + .vg_name = "VGPIO1", + .vg_io_port = VIASR, + .vg_port_index = 0x25, + .vg_mask_shift = 0 + }, + { + .vg_name = "VGPIO2", /* aka DISPCLKI0 */ + .vg_io_port = VIASR, + .vg_port_index = 0x2c, + .vg_mask_shift = 1 + }, + { + .vg_name = "VGPIO3", /* aka DISPCLKO0 */ + .vg_io_port = VIASR, + .vg_port_index = 0x2c, + .vg_mask_shift = 0 + }, + { + .vg_name = "VGPIO4", /* DISPCLKI1 */ + .vg_io_port = VIASR, + .vg_port_index = 0x3d, + .vg_mask_shift = 1 + }, + { + .vg_name = "VGPIO5", /* DISPCLKO1 */ + .vg_io_port = VIASR, + .vg_port_index = 0x3d, + .vg_mask_shift = 0 + }, +}; + +#define VIAFB_NUM_GPIOS ARRAY_SIZE(viafb_all_gpios) + +/* + * This structure controls the active GPIOs, which may be a subset + * of those which are known. + */ + +struct viafb_gpio_cfg { + struct gpio_chip gpio_chip; + struct viafb_dev *vdev; + struct viafb_gpio *active_gpios[VIAFB_NUM_GPIOS]; + const char *gpio_names[VIAFB_NUM_GPIOS]; +}; + +/* + * GPIO access functions + */ +static void via_gpio_set(struct gpio_chip *chip, unsigned int nr, + int value) +{ + struct viafb_gpio_cfg *cfg = container_of(chip, + struct viafb_gpio_cfg, + gpio_chip); + u8 reg; + struct viafb_gpio *gpio; + unsigned long flags; + + spin_lock_irqsave(&cfg->vdev->reg_lock, flags); + gpio = cfg->active_gpios[nr]; + reg = via_read_reg(VIASR, gpio->vg_port_index); + reg |= 0x40 << gpio->vg_mask_shift; /* output enable */ + if (value) + reg |= 0x10 << gpio->vg_mask_shift; + else + reg &= ~(0x10 << gpio->vg_mask_shift); + via_write_reg(VIASR, gpio->vg_port_index, reg); + spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); +} + +static int via_gpio_dir_out(struct gpio_chip *chip, unsigned int nr, + int value) +{ + via_gpio_set(chip, nr, value); + return 0; +} + +/* + * Set the input direction. I'm not sure this is right; we should + * be able to do input without disabling output. + */ +static int via_gpio_dir_input(struct gpio_chip *chip, unsigned int nr) +{ + struct viafb_gpio_cfg *cfg = container_of(chip, + struct viafb_gpio_cfg, + gpio_chip); + struct viafb_gpio *gpio; + unsigned long flags; + + spin_lock_irqsave(&cfg->vdev->reg_lock, flags); + gpio = cfg->active_gpios[nr]; + via_write_reg_mask(VIASR, gpio->vg_port_index, 0, + 0x40 << gpio->vg_mask_shift); + spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); + return 0; +} + +static int via_gpio_get(struct gpio_chip *chip, unsigned int nr) +{ + struct viafb_gpio_cfg *cfg = container_of(chip, + struct viafb_gpio_cfg, + gpio_chip); + u8 reg; + struct viafb_gpio *gpio; + unsigned long flags; + + spin_lock_irqsave(&cfg->vdev->reg_lock, flags); + gpio = cfg->active_gpios[nr]; + reg = via_read_reg(VIASR, gpio->vg_port_index); + spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); + return reg & (0x04 << gpio->vg_mask_shift); +} + + +static struct viafb_gpio_cfg viafb_gpio_config = { + .gpio_chip = { + .label = "VIAFB onboard GPIO", + .owner = THIS_MODULE, + .direction_output = via_gpio_dir_out, + .set = via_gpio_set, + .direction_input = via_gpio_dir_input, + .get = via_gpio_get, + .base = -1, + .ngpio = 0, + .can_sleep = 0 + } +}; + +/* + * Manage the software enable bit. + */ +static void viafb_gpio_enable(struct viafb_gpio *gpio) +{ + via_write_reg_mask(VIASR, gpio->vg_port_index, 0x02, 0x02); +} + +static void viafb_gpio_disable(struct viafb_gpio *gpio) +{ + via_write_reg_mask(VIASR, gpio->vg_port_index, 0, 0x02); +} + +#ifdef CONFIG_PM + +static int viafb_gpio_suspend(void *private) +{ + return 0; +} + +static int viafb_gpio_resume(void *private) +{ + int i; + + for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) + viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); + return 0; +} + +static struct viafb_pm_hooks viafb_gpio_pm_hooks = { + .suspend = viafb_gpio_suspend, + .resume = viafb_gpio_resume +}; +#endif /* CONFIG_PM */ + +/* + * Look up a specific gpio and return the number it was assigned. + */ +int viafb_gpio_lookup(const char *name) +{ + int i; + + for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i++) + if (!strcmp(name, viafb_gpio_config.active_gpios[i]->vg_name)) + return viafb_gpio_config.gpio_chip.base + i; + return -1; +} +EXPORT_SYMBOL_GPL(viafb_gpio_lookup); + +/* + * Platform device stuff. + */ +static int viafb_gpio_probe(struct platform_device *platdev) +{ + struct viafb_dev *vdev = platdev->dev.platform_data; + struct via_port_cfg *port_cfg = vdev->port_cfg; + int i, ngpio = 0, ret; + struct viafb_gpio *gpio; + unsigned long flags; + + /* + * Set up entries for all GPIOs which have been configured to + * operate as such (as opposed to as i2c ports). + */ + for (i = 0; i < VIAFB_NUM_PORTS; i++) { + if (port_cfg[i].mode != VIA_MODE_GPIO) + continue; + for (gpio = viafb_all_gpios; + gpio < viafb_all_gpios + VIAFB_NUM_GPIOS; gpio++) + if (gpio->vg_port_index == port_cfg[i].ioport_index) { + viafb_gpio_config.active_gpios[ngpio] = gpio; + viafb_gpio_config.gpio_names[ngpio] = + gpio->vg_name; + ngpio++; + } + } + viafb_gpio_config.gpio_chip.ngpio = ngpio; + viafb_gpio_config.gpio_chip.names = viafb_gpio_config.gpio_names; + viafb_gpio_config.vdev = vdev; + if (ngpio == 0) { + printk(KERN_INFO "viafb: no GPIOs configured\n"); + return 0; + } + /* + * Enable the ports. They come in pairs, with a single + * enable bit for both. + */ + spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); + for (i = 0; i < ngpio; i += 2) + viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); + spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); + /* + * Get registered. + */ + viafb_gpio_config.gpio_chip.base = -1; /* Dynamic */ + ret = gpiochip_add(&viafb_gpio_config.gpio_chip); + if (ret) { + printk(KERN_ERR "viafb: failed to add gpios (%d)\n", ret); + viafb_gpio_config.gpio_chip.ngpio = 0; + } +#ifdef CONFIG_PM + viafb_pm_register(&viafb_gpio_pm_hooks); +#endif + return ret; +} + + +static int viafb_gpio_remove(struct platform_device *platdev) +{ + unsigned long flags; + int ret = 0, i; + +#ifdef CONFIG_PM + viafb_pm_unregister(&viafb_gpio_pm_hooks); +#endif + + /* + * Get unregistered. + */ + if (viafb_gpio_config.gpio_chip.ngpio > 0) { + ret = gpiochip_remove(&viafb_gpio_config.gpio_chip); + if (ret) { /* Somebody still using it? */ + printk(KERN_ERR "Viafb: GPIO remove failed\n"); + return ret; + } + } + /* + * Disable the ports. + */ + spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); + for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) + viafb_gpio_disable(viafb_gpio_config.active_gpios[i]); + viafb_gpio_config.gpio_chip.ngpio = 0; + spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); + return ret; +} + +static struct platform_driver via_gpio_driver = { + .driver = { + .name = "viafb-gpio", + }, + .probe = viafb_gpio_probe, + .remove = viafb_gpio_remove, +}; + +int viafb_gpio_init(void) +{ + return platform_driver_register(&via_gpio_driver); +} + +void viafb_gpio_exit(void) +{ + platform_driver_unregister(&via_gpio_driver); +} diff --git a/drivers/video/fbdev/via/via_aux.c b/drivers/video/fbdev/via/via_aux.c new file mode 100644 index 000000000000..4a0a55cdac3d --- /dev/null +++ b/drivers/video/fbdev/via/via_aux.c @@ -0,0 +1,88 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * infrastructure for devices connected via I2C + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +struct via_aux_bus *via_aux_probe(struct i2c_adapter *adap) +{ + struct via_aux_bus *bus; + + if (!adap) + return NULL; + + bus = kmalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) + return NULL; + + bus->adap = adap; + INIT_LIST_HEAD(&bus->drivers); + + via_aux_edid_probe(bus); + via_aux_vt1636_probe(bus); + via_aux_vt1632_probe(bus); + via_aux_vt1631_probe(bus); + via_aux_vt1625_probe(bus); + via_aux_vt1622_probe(bus); + via_aux_vt1621_probe(bus); + via_aux_sii164_probe(bus); + via_aux_ch7301_probe(bus); + + return bus; +} + +void via_aux_free(struct via_aux_bus *bus) +{ + struct via_aux_drv *pos, *n; + + if (!bus) + return; + + list_for_each_entry_safe(pos, n, &bus->drivers, chain) { + if (pos->cleanup) + pos->cleanup(pos); + + list_del(&pos->chain); + kfree(pos->data); + kfree(pos); + } + + kfree(bus); +} + +const struct fb_videomode *via_aux_get_preferred_mode(struct via_aux_bus *bus) +{ + struct via_aux_drv *pos; + const struct fb_videomode *mode = NULL; + + if (!bus) + return NULL; + + list_for_each_entry(pos, &bus->drivers, chain) { + if (pos->get_preferred_mode) + mode = pos->get_preferred_mode(pos); + } + + return mode; +} diff --git a/drivers/video/fbdev/via/via_aux.h b/drivers/video/fbdev/via/via_aux.h new file mode 100644 index 000000000000..a8de3f038cea --- /dev/null +++ b/drivers/video/fbdev/via/via_aux.h @@ -0,0 +1,93 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * infrastructure for devices connected via I2C + */ + +#ifndef __VIA_AUX_H__ +#define __VIA_AUX_H__ + + +#include <linux/list.h> +#include <linux/i2c.h> +#include <linux/fb.h> + + +struct via_aux_bus { + struct i2c_adapter *adap; /* the I2C device to access the bus */ + struct list_head drivers; /* drivers for devices on this bus */ +}; + +struct via_aux_drv { + struct list_head chain; /* chain to support multiple drivers */ + + struct via_aux_bus *bus; /* the I2C bus used */ + u8 addr; /* the I2C slave address */ + + const char *name; /* human readable name of the driver */ + void *data; /* private data of this driver */ + + void (*cleanup)(struct via_aux_drv *drv); + const struct fb_videomode* (*get_preferred_mode) + (struct via_aux_drv *drv); +}; + + +struct via_aux_bus *via_aux_probe(struct i2c_adapter *adap); +void via_aux_free(struct via_aux_bus *bus); +const struct fb_videomode *via_aux_get_preferred_mode(struct via_aux_bus *bus); + + +static inline bool via_aux_add(struct via_aux_drv *drv) +{ + struct via_aux_drv *data = kmalloc(sizeof(*data), GFP_KERNEL); + + if (!data) + return false; + + *data = *drv; + list_add_tail(&data->chain, &data->bus->drivers); + return true; +} + +static inline bool via_aux_read(struct via_aux_drv *drv, u8 start, u8 *buf, + u8 len) +{ + struct i2c_msg msg[2] = { + {.addr = drv->addr, .flags = 0, .len = 1, .buf = &start}, + {.addr = drv->addr, .flags = I2C_M_RD, .len = len, .buf = buf} }; + + return i2c_transfer(drv->bus->adap, msg, 2) == 2; +} + + +/* probe functions of existing drivers - should only be called in via_aux.c */ +void via_aux_ch7301_probe(struct via_aux_bus *bus); +void via_aux_edid_probe(struct via_aux_bus *bus); +void via_aux_sii164_probe(struct via_aux_bus *bus); +void via_aux_vt1636_probe(struct via_aux_bus *bus); +void via_aux_vt1632_probe(struct via_aux_bus *bus); +void via_aux_vt1631_probe(struct via_aux_bus *bus); +void via_aux_vt1625_probe(struct via_aux_bus *bus); +void via_aux_vt1622_probe(struct via_aux_bus *bus); +void via_aux_vt1621_probe(struct via_aux_bus *bus); + + +#endif /* __VIA_AUX_H__ */ diff --git a/drivers/video/fbdev/via/via_aux_ch7301.c b/drivers/video/fbdev/via/via_aux_ch7301.c new file mode 100644 index 000000000000..1cbe5037a6b0 --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_ch7301.c @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for Chrontel CH7301 DVI Transmitter + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "CH7301 DVI Transmitter"; + + +static void probe(struct via_aux_bus *bus, u8 addr) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = addr, + .name = name}; + u8 tmp; + + if (!via_aux_read(&drv, 0x4B, &tmp, 1) || tmp != 0x17) + return; + + printk(KERN_INFO "viafb: Found %s at address 0x%x\n", name, addr); + via_aux_add(&drv); +} + +void via_aux_ch7301_probe(struct via_aux_bus *bus) +{ + probe(bus, 0x75); + probe(bus, 0x76); +} diff --git a/drivers/video/fbdev/via/via_aux_edid.c b/drivers/video/fbdev/via/via_aux_edid.c new file mode 100644 index 000000000000..754d4509033f --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_edid.c @@ -0,0 +1,100 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * generic EDID driver + */ + +#include <linux/slab.h> +#include <linux/fb.h> +#include "via_aux.h" +#include "../edid.h" + + +static const char *name = "EDID"; + + +static void query_edid(struct via_aux_drv *drv) +{ + struct fb_monspecs *spec = drv->data; + unsigned char edid[EDID_LENGTH]; + bool valid = false; + + if (spec) { + fb_destroy_modedb(spec->modedb); + } else { + spec = kmalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return; + } + + spec->version = spec->revision = 0; + if (via_aux_read(drv, 0x00, edid, EDID_LENGTH)) { + fb_edid_to_monspecs(edid, spec); + valid = spec->version || spec->revision; + } + + if (!valid) { + kfree(spec); + spec = NULL; + } else + printk(KERN_DEBUG "EDID: %s %s\n", spec->manufacturer, spec->monitor); + + drv->data = spec; +} + +static const struct fb_videomode *get_preferred_mode(struct via_aux_drv *drv) +{ + struct fb_monspecs *spec = drv->data; + int i; + + if (!spec || !spec->modedb || !(spec->misc & FB_MISC_1ST_DETAIL)) + return NULL; + + for (i = 0; i < spec->modedb_len; i++) { + if (spec->modedb[i].flag & FB_MODE_IS_FIRST && + spec->modedb[i].flag & FB_MODE_IS_DETAILED) + return &spec->modedb[i]; + } + + return NULL; +} + +static void cleanup(struct via_aux_drv *drv) +{ + struct fb_monspecs *spec = drv->data; + + if (spec) + fb_destroy_modedb(spec->modedb); +} + +void via_aux_edid_probe(struct via_aux_bus *bus) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = 0x50, + .name = name, + .cleanup = cleanup, + .get_preferred_mode = get_preferred_mode}; + + query_edid(&drv); + + /* as EDID devices can be connected/disconnected just add the driver */ + via_aux_add(&drv); +} diff --git a/drivers/video/fbdev/via/via_aux_sii164.c b/drivers/video/fbdev/via/via_aux_sii164.c new file mode 100644 index 000000000000..ca1b35f033b1 --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_sii164.c @@ -0,0 +1,54 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for Silicon Image SiI 164 PanelLink Transmitter + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "SiI 164 PanelLink Transmitter"; + + +static void probe(struct via_aux_bus *bus, u8 addr) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = addr, + .name = name}; + /* check vendor id and device id */ + const u8 id[] = {0x01, 0x00, 0x06, 0x00}, len = ARRAY_SIZE(id); + u8 tmp[len]; + + if (!via_aux_read(&drv, 0x00, tmp, len) || memcmp(id, tmp, len)) + return; + + printk(KERN_INFO "viafb: Found %s at address 0x%x\n", name, addr); + via_aux_add(&drv); +} + +void via_aux_sii164_probe(struct via_aux_bus *bus) +{ + u8 i; + + for (i = 0x38; i <= 0x3F; i++) + probe(bus, i); +} diff --git a/drivers/video/fbdev/via/via_aux_vt1621.c b/drivers/video/fbdev/via/via_aux_vt1621.c new file mode 100644 index 000000000000..38eca8479898 --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_vt1621.c @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for VIA VT1621(M) TV Encoder + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "VT1621(M) TV Encoder"; + + +void via_aux_vt1621_probe(struct via_aux_bus *bus) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = 0x20, + .name = name}; + u8 tmp; + + if (!via_aux_read(&drv, 0x1B, &tmp, 1) || tmp != 0x02) + return; + + printk(KERN_INFO "viafb: Found %s\n", name); + via_aux_add(&drv); +} diff --git a/drivers/video/fbdev/via/via_aux_vt1622.c b/drivers/video/fbdev/via/via_aux_vt1622.c new file mode 100644 index 000000000000..8c79c68ba683 --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_vt1622.c @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for VIA VT1622(M) Digital TV Encoder + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "VT1622(M) Digital TV Encoder"; + + +static void probe(struct via_aux_bus *bus, u8 addr) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = addr, + .name = name}; + u8 tmp; + + if (!via_aux_read(&drv, 0x1B, &tmp, 1) || tmp != 0x03) + return; + + printk(KERN_INFO "viafb: Found %s at address 0x%x\n", name, addr); + via_aux_add(&drv); +} + +void via_aux_vt1622_probe(struct via_aux_bus *bus) +{ + probe(bus, 0x20); + probe(bus, 0x21); +} diff --git a/drivers/video/fbdev/via/via_aux_vt1625.c b/drivers/video/fbdev/via/via_aux_vt1625.c new file mode 100644 index 000000000000..03eb30165d36 --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_vt1625.c @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for VIA VT1625(M) HDTV Encoder + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "VT1625(M) HDTV Encoder"; + + +static void probe(struct via_aux_bus *bus, u8 addr) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = addr, + .name = name}; + u8 tmp; + + if (!via_aux_read(&drv, 0x1B, &tmp, 1) || tmp != 0x50) + return; + + printk(KERN_INFO "viafb: Found %s at address 0x%x\n", name, addr); + via_aux_add(&drv); +} + +void via_aux_vt1625_probe(struct via_aux_bus *bus) +{ + probe(bus, 0x20); + probe(bus, 0x21); +} diff --git a/drivers/video/fbdev/via/via_aux_vt1631.c b/drivers/video/fbdev/via/via_aux_vt1631.c new file mode 100644 index 000000000000..06e742f1f723 --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_vt1631.c @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for VIA VT1631 LVDS Transmitter + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "VT1631 LVDS Transmitter"; + + +void via_aux_vt1631_probe(struct via_aux_bus *bus) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = 0x38, + .name = name}; + /* check vendor id and device id */ + const u8 id[] = {0x06, 0x11, 0x91, 0x31}, len = ARRAY_SIZE(id); + u8 tmp[len]; + + if (!via_aux_read(&drv, 0x00, tmp, len) || memcmp(id, tmp, len)) + return; + + printk(KERN_INFO "viafb: Found %s\n", name); + via_aux_add(&drv); +} diff --git a/drivers/video/fbdev/via/via_aux_vt1632.c b/drivers/video/fbdev/via/via_aux_vt1632.c new file mode 100644 index 000000000000..d24f4cd97401 --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_vt1632.c @@ -0,0 +1,54 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for VIA VT1632 DVI Transmitter + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "VT1632 DVI Transmitter"; + + +static void probe(struct via_aux_bus *bus, u8 addr) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = addr, + .name = name}; + /* check vendor id and device id */ + const u8 id[] = {0x06, 0x11, 0x92, 0x31}, len = ARRAY_SIZE(id); + u8 tmp[len]; + + if (!via_aux_read(&drv, 0x00, tmp, len) || memcmp(id, tmp, len)) + return; + + printk(KERN_INFO "viafb: Found %s at address 0x%x\n", name, addr); + via_aux_add(&drv); +} + +void via_aux_vt1632_probe(struct via_aux_bus *bus) +{ + u8 i; + + for (i = 0x08; i <= 0x0F; i++) + probe(bus, i); +} diff --git a/drivers/video/fbdev/via/via_aux_vt1636.c b/drivers/video/fbdev/via/via_aux_vt1636.c new file mode 100644 index 000000000000..9e015c101d4d --- /dev/null +++ b/drivers/video/fbdev/via/via_aux_vt1636.c @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * driver for VIA VT1636 LVDS Transmitter + */ + +#include <linux/slab.h> +#include "via_aux.h" + + +static const char *name = "VT1636 LVDS Transmitter"; + + +void via_aux_vt1636_probe(struct via_aux_bus *bus) +{ + struct via_aux_drv drv = { + .bus = bus, + .addr = 0x40, + .name = name}; + /* check vendor id and device id */ + const u8 id[] = {0x06, 0x11, 0x45, 0x33}, len = ARRAY_SIZE(id); + u8 tmp[len]; + + if (!via_aux_read(&drv, 0x00, tmp, len) || memcmp(id, tmp, len)) + return; + + printk(KERN_INFO "viafb: Found %s\n", name); + via_aux_add(&drv); +} diff --git a/drivers/video/fbdev/via/via_clock.c b/drivers/video/fbdev/via/via_clock.c new file mode 100644 index 000000000000..db1e39277e32 --- /dev/null +++ b/drivers/video/fbdev/via/via_clock.c @@ -0,0 +1,368 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * clock and PLL management functions + */ + +#include <linux/kernel.h> +#include <linux/via-core.h> +#include <asm/olpc.h> +#include "via_clock.h" +#include "global.h" +#include "debug.h" + +const char *via_slap = "Please slap VIA Technologies to motivate them " + "releasing full documentation for your platform!\n"; + +static inline u32 cle266_encode_pll(struct via_pll_config pll) +{ + return (pll.multiplier << 8) + | (pll.rshift << 6) + | pll.divisor; +} + +static inline u32 k800_encode_pll(struct via_pll_config pll) +{ + return ((pll.divisor - 2) << 16) + | (pll.rshift << 10) + | (pll.multiplier - 2); +} + +static inline u32 vx855_encode_pll(struct via_pll_config pll) +{ + return (pll.divisor << 16) + | (pll.rshift << 10) + | pll.multiplier; +} + +static inline void cle266_set_primary_pll_encoded(u32 data) +{ + via_write_reg_mask(VIASR, 0x40, 0x02, 0x02); /* enable reset */ + via_write_reg(VIASR, 0x46, data & 0xFF); + via_write_reg(VIASR, 0x47, (data >> 8) & 0xFF); + via_write_reg_mask(VIASR, 0x40, 0x00, 0x02); /* disable reset */ +} + +static inline void k800_set_primary_pll_encoded(u32 data) +{ + via_write_reg_mask(VIASR, 0x40, 0x02, 0x02); /* enable reset */ + via_write_reg(VIASR, 0x44, data & 0xFF); + via_write_reg(VIASR, 0x45, (data >> 8) & 0xFF); + via_write_reg(VIASR, 0x46, (data >> 16) & 0xFF); + via_write_reg_mask(VIASR, 0x40, 0x00, 0x02); /* disable reset */ +} + +static inline void cle266_set_secondary_pll_encoded(u32 data) +{ + via_write_reg_mask(VIASR, 0x40, 0x04, 0x04); /* enable reset */ + via_write_reg(VIASR, 0x44, data & 0xFF); + via_write_reg(VIASR, 0x45, (data >> 8) & 0xFF); + via_write_reg_mask(VIASR, 0x40, 0x00, 0x04); /* disable reset */ +} + +static inline void k800_set_secondary_pll_encoded(u32 data) +{ + via_write_reg_mask(VIASR, 0x40, 0x04, 0x04); /* enable reset */ + via_write_reg(VIASR, 0x4A, data & 0xFF); + via_write_reg(VIASR, 0x4B, (data >> 8) & 0xFF); + via_write_reg(VIASR, 0x4C, (data >> 16) & 0xFF); + via_write_reg_mask(VIASR, 0x40, 0x00, 0x04); /* disable reset */ +} + +static inline void set_engine_pll_encoded(u32 data) +{ + via_write_reg_mask(VIASR, 0x40, 0x01, 0x01); /* enable reset */ + via_write_reg(VIASR, 0x47, data & 0xFF); + via_write_reg(VIASR, 0x48, (data >> 8) & 0xFF); + via_write_reg(VIASR, 0x49, (data >> 16) & 0xFF); + via_write_reg_mask(VIASR, 0x40, 0x00, 0x01); /* disable reset */ +} + +static void cle266_set_primary_pll(struct via_pll_config config) +{ + cle266_set_primary_pll_encoded(cle266_encode_pll(config)); +} + +static void k800_set_primary_pll(struct via_pll_config config) +{ + k800_set_primary_pll_encoded(k800_encode_pll(config)); +} + +static void vx855_set_primary_pll(struct via_pll_config config) +{ + k800_set_primary_pll_encoded(vx855_encode_pll(config)); +} + +static void cle266_set_secondary_pll(struct via_pll_config config) +{ + cle266_set_secondary_pll_encoded(cle266_encode_pll(config)); +} + +static void k800_set_secondary_pll(struct via_pll_config config) +{ + k800_set_secondary_pll_encoded(k800_encode_pll(config)); +} + +static void vx855_set_secondary_pll(struct via_pll_config config) +{ + k800_set_secondary_pll_encoded(vx855_encode_pll(config)); +} + +static void k800_set_engine_pll(struct via_pll_config config) +{ + set_engine_pll_encoded(k800_encode_pll(config)); +} + +static void vx855_set_engine_pll(struct via_pll_config config) +{ + set_engine_pll_encoded(vx855_encode_pll(config)); +} + +static void set_primary_pll_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x20; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x2D, value, 0x30); +} + +static void set_secondary_pll_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x08; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x2D, value, 0x0C); +} + +static void set_engine_pll_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x02; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x2D, value, 0x03); +} + +static void set_primary_clock_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x20; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x1B, value, 0x30); +} + +static void set_secondary_clock_state(u8 state) +{ + u8 value; + + switch (state) { + case VIA_STATE_ON: + value = 0x80; + break; + case VIA_STATE_OFF: + value = 0x00; + break; + default: + return; + } + + via_write_reg_mask(VIASR, 0x1B, value, 0xC0); +} + +static inline u8 set_clock_source_common(enum via_clksrc source, bool use_pll) +{ + u8 data = 0; + + switch (source) { + case VIA_CLKSRC_X1: + data = 0x00; + break; + case VIA_CLKSRC_TVX1: + data = 0x02; + break; + case VIA_CLKSRC_TVPLL: + data = 0x04; /* 0x06 should be the same */ + break; + case VIA_CLKSRC_DVP1TVCLKR: + data = 0x0A; + break; + case VIA_CLKSRC_CAP0: + data = 0xC; + break; + case VIA_CLKSRC_CAP1: + data = 0x0E; + break; + } + + if (!use_pll) + data |= 1; + + return data; +} + +static void set_primary_clock_source(enum via_clksrc source, bool use_pll) +{ + u8 data = set_clock_source_common(source, use_pll) << 4; + via_write_reg_mask(VIACR, 0x6C, data, 0xF0); +} + +static void set_secondary_clock_source(enum via_clksrc source, bool use_pll) +{ + u8 data = set_clock_source_common(source, use_pll); + via_write_reg_mask(VIACR, 0x6C, data, 0x0F); +} + +static void dummy_set_clock_state(u8 state) +{ + printk(KERN_INFO "Using undocumented set clock state.\n%s", via_slap); +} + +static void dummy_set_clock_source(enum via_clksrc source, bool use_pll) +{ + printk(KERN_INFO "Using undocumented set clock source.\n%s", via_slap); +} + +static void dummy_set_pll_state(u8 state) +{ + printk(KERN_INFO "Using undocumented set PLL state.\n%s", via_slap); +} + +static void dummy_set_pll(struct via_pll_config config) +{ + printk(KERN_INFO "Using undocumented set PLL.\n%s", via_slap); +} + +static void noop_set_clock_state(u8 state) +{ +} + +void via_clock_init(struct via_clock *clock, int gfx_chip) +{ + switch (gfx_chip) { + case UNICHROME_CLE266: + case UNICHROME_K400: + clock->set_primary_clock_state = dummy_set_clock_state; + clock->set_primary_clock_source = dummy_set_clock_source; + clock->set_primary_pll_state = dummy_set_pll_state; + clock->set_primary_pll = cle266_set_primary_pll; + + clock->set_secondary_clock_state = dummy_set_clock_state; + clock->set_secondary_clock_source = dummy_set_clock_source; + clock->set_secondary_pll_state = dummy_set_pll_state; + clock->set_secondary_pll = cle266_set_secondary_pll; + + clock->set_engine_pll_state = dummy_set_pll_state; + clock->set_engine_pll = dummy_set_pll; + break; + case UNICHROME_K800: + case UNICHROME_PM800: + case UNICHROME_CN700: + case UNICHROME_CX700: + case UNICHROME_CN750: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + case UNICHROME_VX800: + clock->set_primary_clock_state = set_primary_clock_state; + clock->set_primary_clock_source = set_primary_clock_source; + clock->set_primary_pll_state = set_primary_pll_state; + clock->set_primary_pll = k800_set_primary_pll; + + clock->set_secondary_clock_state = set_secondary_clock_state; + clock->set_secondary_clock_source = set_secondary_clock_source; + clock->set_secondary_pll_state = set_secondary_pll_state; + clock->set_secondary_pll = k800_set_secondary_pll; + + clock->set_engine_pll_state = set_engine_pll_state; + clock->set_engine_pll = k800_set_engine_pll; + break; + case UNICHROME_VX855: + case UNICHROME_VX900: + clock->set_primary_clock_state = set_primary_clock_state; + clock->set_primary_clock_source = set_primary_clock_source; + clock->set_primary_pll_state = set_primary_pll_state; + clock->set_primary_pll = vx855_set_primary_pll; + + clock->set_secondary_clock_state = set_secondary_clock_state; + clock->set_secondary_clock_source = set_secondary_clock_source; + clock->set_secondary_pll_state = set_secondary_pll_state; + clock->set_secondary_pll = vx855_set_secondary_pll; + + clock->set_engine_pll_state = set_engine_pll_state; + clock->set_engine_pll = vx855_set_engine_pll; + break; + + } + + if (machine_is_olpc()) { + /* The OLPC XO-1.5 cannot suspend/resume reliably if the + * IGA1/IGA2 clocks are set as on or off (memory rot + * occasionally happens during suspend under such + * configurations). + * + * The only known stable scenario is to leave this bits as-is, + * which in their default states are documented to enable the + * clock only when it is needed. + */ + clock->set_primary_clock_state = noop_set_clock_state; + clock->set_secondary_clock_state = noop_set_clock_state; + } +} diff --git a/drivers/video/fbdev/via/via_clock.h b/drivers/video/fbdev/via/via_clock.h new file mode 100644 index 000000000000..88714ae0d157 --- /dev/null +++ b/drivers/video/fbdev/via/via_clock.h @@ -0,0 +1,76 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * clock and PLL management functions + */ + +#ifndef __VIA_CLOCK_H__ +#define __VIA_CLOCK_H__ + +#include <linux/types.h> + +enum via_clksrc { + VIA_CLKSRC_X1 = 0, + VIA_CLKSRC_TVX1, + VIA_CLKSRC_TVPLL, + VIA_CLKSRC_DVP1TVCLKR, + VIA_CLKSRC_CAP0, + VIA_CLKSRC_CAP1, +}; + +struct via_pll_config { + u16 multiplier; + u8 divisor; + u8 rshift; +}; + +struct via_clock { + void (*set_primary_clock_state)(u8 state); + void (*set_primary_clock_source)(enum via_clksrc src, bool use_pll); + void (*set_primary_pll_state)(u8 state); + void (*set_primary_pll)(struct via_pll_config config); + + void (*set_secondary_clock_state)(u8 state); + void (*set_secondary_clock_source)(enum via_clksrc src, bool use_pll); + void (*set_secondary_pll_state)(u8 state); + void (*set_secondary_pll)(struct via_pll_config config); + + void (*set_engine_pll_state)(u8 state); + void (*set_engine_pll)(struct via_pll_config config); +}; + + +static inline u32 get_pll_internal_frequency(u32 ref_freq, + struct via_pll_config pll) +{ + return ref_freq / pll.divisor * pll.multiplier; +} + +static inline u32 get_pll_output_frequency(u32 ref_freq, + struct via_pll_config pll) +{ + return get_pll_internal_frequency(ref_freq, pll) >> pll.rshift; +} + +void via_clock_init(struct via_clock *clock, int gfx_chip); + +#endif /* __VIA_CLOCK_H__ */ diff --git a/drivers/video/fbdev/via/via_i2c.c b/drivers/video/fbdev/via/via_i2c.c new file mode 100644 index 000000000000..dd53058bbbb7 --- /dev/null +++ b/drivers/video/fbdev/via/via_i2c.c @@ -0,0 +1,295 @@ +/* + * Copyright 1998-2009 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/platform_device.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/module.h> +#include <linux/via-core.h> +#include <linux/via_i2c.h> + +/* + * There can only be one set of these, so there's no point in having + * them be dynamically allocated... + */ +#define VIAFB_NUM_I2C 5 +static struct via_i2c_stuff via_i2c_par[VIAFB_NUM_I2C]; +static struct viafb_dev *i2c_vdev; /* Passed in from core */ + +static void via_i2c_setscl(void *data, int state) +{ + u8 val; + struct via_port_cfg *adap_data = data; + unsigned long flags; + + spin_lock_irqsave(&i2c_vdev->reg_lock, flags); + val = via_read_reg(adap_data->io_port, adap_data->ioport_index) & 0xF0; + if (state) + val |= 0x20; + else + val &= ~0x20; + switch (adap_data->type) { + case VIA_PORT_I2C: + val |= 0x01; + break; + case VIA_PORT_GPIO: + val |= 0x82; + break; + default: + printk(KERN_ERR "viafb_i2c: specify wrong i2c type.\n"); + } + via_write_reg(adap_data->io_port, adap_data->ioport_index, val); + spin_unlock_irqrestore(&i2c_vdev->reg_lock, flags); +} + +static int via_i2c_getscl(void *data) +{ + struct via_port_cfg *adap_data = data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&i2c_vdev->reg_lock, flags); + if (adap_data->type == VIA_PORT_GPIO) + via_write_reg_mask(adap_data->io_port, adap_data->ioport_index, + 0, 0x80); + if (via_read_reg(adap_data->io_port, adap_data->ioport_index) & 0x08) + ret = 1; + spin_unlock_irqrestore(&i2c_vdev->reg_lock, flags); + return ret; +} + +static int via_i2c_getsda(void *data) +{ + struct via_port_cfg *adap_data = data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&i2c_vdev->reg_lock, flags); + if (adap_data->type == VIA_PORT_GPIO) + via_write_reg_mask(adap_data->io_port, adap_data->ioport_index, + 0, 0x40); + if (via_read_reg(adap_data->io_port, adap_data->ioport_index) & 0x04) + ret = 1; + spin_unlock_irqrestore(&i2c_vdev->reg_lock, flags); + return ret; +} + +static void via_i2c_setsda(void *data, int state) +{ + u8 val; + struct via_port_cfg *adap_data = data; + unsigned long flags; + + spin_lock_irqsave(&i2c_vdev->reg_lock, flags); + val = via_read_reg(adap_data->io_port, adap_data->ioport_index) & 0xF0; + if (state) + val |= 0x10; + else + val &= ~0x10; + switch (adap_data->type) { + case VIA_PORT_I2C: + val |= 0x01; + break; + case VIA_PORT_GPIO: + val |= 0x42; + break; + default: + printk(KERN_ERR "viafb_i2c: specify wrong i2c type.\n"); + } + via_write_reg(adap_data->io_port, adap_data->ioport_index, val); + spin_unlock_irqrestore(&i2c_vdev->reg_lock, flags); +} + +int viafb_i2c_readbyte(u8 adap, u8 slave_addr, u8 index, u8 *pdata) +{ + int ret; + u8 mm1[] = {0x00}; + struct i2c_msg msgs[2]; + + if (!via_i2c_par[adap].is_active) + return -ENODEV; + *pdata = 0; + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + msgs[0].addr = msgs[1].addr = slave_addr / 2; + mm1[0] = index; + msgs[0].len = 1; msgs[1].len = 1; + msgs[0].buf = mm1; msgs[1].buf = pdata; + ret = i2c_transfer(&via_i2c_par[adap].adapter, msgs, 2); + if (ret == 2) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + return ret; +} + +int viafb_i2c_writebyte(u8 adap, u8 slave_addr, u8 index, u8 data) +{ + int ret; + u8 msg[2] = { index, data }; + struct i2c_msg msgs; + + if (!via_i2c_par[adap].is_active) + return -ENODEV; + msgs.flags = 0; + msgs.addr = slave_addr / 2; + msgs.len = 2; + msgs.buf = msg; + ret = i2c_transfer(&via_i2c_par[adap].adapter, &msgs, 1); + if (ret == 1) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + return ret; +} + +int viafb_i2c_readbytes(u8 adap, u8 slave_addr, u8 index, u8 *buff, int buff_len) +{ + int ret; + u8 mm1[] = {0x00}; + struct i2c_msg msgs[2]; + + if (!via_i2c_par[adap].is_active) + return -ENODEV; + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + msgs[0].addr = msgs[1].addr = slave_addr / 2; + mm1[0] = index; + msgs[0].len = 1; msgs[1].len = buff_len; + msgs[0].buf = mm1; msgs[1].buf = buff; + ret = i2c_transfer(&via_i2c_par[adap].adapter, msgs, 2); + if (ret == 2) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + return ret; +} + +/* + * Allow other viafb subdevices to look up a specific adapter + * by port name. + */ +struct i2c_adapter *viafb_find_i2c_adapter(enum viafb_i2c_adap which) +{ + struct via_i2c_stuff *stuff = &via_i2c_par[which]; + + return &stuff->adapter; +} +EXPORT_SYMBOL_GPL(viafb_find_i2c_adapter); + + +static int create_i2c_bus(struct i2c_adapter *adapter, + struct i2c_algo_bit_data *algo, + struct via_port_cfg *adap_cfg, + struct pci_dev *pdev) +{ + algo->setsda = via_i2c_setsda; + algo->setscl = via_i2c_setscl; + algo->getsda = via_i2c_getsda; + algo->getscl = via_i2c_getscl; + algo->udelay = 10; + algo->timeout = 2; + algo->data = adap_cfg; + + sprintf(adapter->name, "viafb i2c io_port idx 0x%02x", + adap_cfg->ioport_index); + adapter->owner = THIS_MODULE; + adapter->class = I2C_CLASS_DDC; + adapter->algo_data = algo; + if (pdev) + adapter->dev.parent = &pdev->dev; + else + adapter->dev.parent = NULL; + /* i2c_set_adapdata(adapter, adap_cfg); */ + + /* Raise SCL and SDA */ + via_i2c_setsda(adap_cfg, 1); + via_i2c_setscl(adap_cfg, 1); + udelay(20); + + return i2c_bit_add_bus(adapter); +} + +static int viafb_i2c_probe(struct platform_device *platdev) +{ + int i, ret; + struct via_port_cfg *configs; + + i2c_vdev = platdev->dev.platform_data; + configs = i2c_vdev->port_cfg; + + for (i = 0; i < VIAFB_NUM_PORTS; i++) { + struct via_port_cfg *adap_cfg = configs++; + struct via_i2c_stuff *i2c_stuff = &via_i2c_par[i]; + + i2c_stuff->is_active = 0; + if (adap_cfg->type == 0 || adap_cfg->mode != VIA_MODE_I2C) + continue; + ret = create_i2c_bus(&i2c_stuff->adapter, + &i2c_stuff->algo, adap_cfg, + NULL); /* FIXME: PCIDEV */ + if (ret < 0) { + printk(KERN_ERR "viafb: cannot create i2c bus %u:%d\n", + i, ret); + continue; /* Still try to make the rest */ + } + i2c_stuff->is_active = 1; + } + + return 0; +} + +static int viafb_i2c_remove(struct platform_device *platdev) +{ + int i; + + for (i = 0; i < VIAFB_NUM_PORTS; i++) { + struct via_i2c_stuff *i2c_stuff = &via_i2c_par[i]; + /* + * Only remove those entries in the array that we've + * actually used (and thus initialized algo_data) + */ + if (i2c_stuff->is_active) + i2c_del_adapter(&i2c_stuff->adapter); + } + return 0; +} + +static struct platform_driver via_i2c_driver = { + .driver = { + .name = "viafb-i2c", + }, + .probe = viafb_i2c_probe, + .remove = viafb_i2c_remove, +}; + +int viafb_i2c_init(void) +{ + return platform_driver_register(&via_i2c_driver); +} + +void viafb_i2c_exit(void) +{ + platform_driver_unregister(&via_i2c_driver); +} diff --git a/drivers/video/fbdev/via/via_modesetting.c b/drivers/video/fbdev/via/via_modesetting.c new file mode 100644 index 000000000000..0b414b09b9b4 --- /dev/null +++ b/drivers/video/fbdev/via/via_modesetting.c @@ -0,0 +1,230 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + * Copyright 2010 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * basic modesetting functions + */ + +#include <linux/kernel.h> +#include <linux/via-core.h> +#include "via_modesetting.h" +#include "share.h" +#include "debug.h" + + +void via_set_primary_timing(const struct via_display_timing *timing) +{ + struct via_display_timing raw; + + raw.hor_total = timing->hor_total / 8 - 5; + raw.hor_addr = timing->hor_addr / 8 - 1; + raw.hor_blank_start = timing->hor_blank_start / 8 - 1; + raw.hor_blank_end = timing->hor_blank_end / 8 - 1; + raw.hor_sync_start = timing->hor_sync_start / 8; + raw.hor_sync_end = timing->hor_sync_end / 8; + raw.ver_total = timing->ver_total - 2; + raw.ver_addr = timing->ver_addr - 1; + raw.ver_blank_start = timing->ver_blank_start - 1; + raw.ver_blank_end = timing->ver_blank_end - 1; + raw.ver_sync_start = timing->ver_sync_start - 1; + raw.ver_sync_end = timing->ver_sync_end - 1; + + /* unlock timing registers */ + via_write_reg_mask(VIACR, 0x11, 0x00, 0x80); + + via_write_reg(VIACR, 0x00, raw.hor_total & 0xFF); + via_write_reg(VIACR, 0x01, raw.hor_addr & 0xFF); + via_write_reg(VIACR, 0x02, raw.hor_blank_start & 0xFF); + via_write_reg_mask(VIACR, 0x03, raw.hor_blank_end & 0x1F, 0x1F); + via_write_reg(VIACR, 0x04, raw.hor_sync_start & 0xFF); + via_write_reg_mask(VIACR, 0x05, (raw.hor_sync_end & 0x1F) + | (raw.hor_blank_end << (7 - 5) & 0x80), 0x9F); + via_write_reg(VIACR, 0x06, raw.ver_total & 0xFF); + via_write_reg_mask(VIACR, 0x07, (raw.ver_total >> 8 & 0x01) + | (raw.ver_addr >> (8 - 1) & 0x02) + | (raw.ver_sync_start >> (8 - 2) & 0x04) + | (raw.ver_blank_start >> (8 - 3) & 0x08) + | (raw.ver_total >> (9 - 5) & 0x20) + | (raw.ver_addr >> (9 - 6) & 0x40) + | (raw.ver_sync_start >> (9 - 7) & 0x80), 0xEF); + via_write_reg_mask(VIACR, 0x09, raw.ver_blank_start >> (9 - 5) & 0x20, + 0x20); + via_write_reg(VIACR, 0x10, raw.ver_sync_start & 0xFF); + via_write_reg_mask(VIACR, 0x11, raw.ver_sync_end & 0x0F, 0x0F); + via_write_reg(VIACR, 0x12, raw.ver_addr & 0xFF); + via_write_reg(VIACR, 0x15, raw.ver_blank_start & 0xFF); + via_write_reg(VIACR, 0x16, raw.ver_blank_end & 0xFF); + via_write_reg_mask(VIACR, 0x33, (raw.hor_sync_start >> (8 - 4) & 0x10) + | (raw.hor_blank_end >> (6 - 5) & 0x20), 0x30); + via_write_reg_mask(VIACR, 0x35, (raw.ver_total >> 10 & 0x01) + | (raw.ver_sync_start >> (10 - 1) & 0x02) + | (raw.ver_addr >> (10 - 2) & 0x04) + | (raw.ver_blank_start >> (10 - 3) & 0x08), 0x0F); + via_write_reg_mask(VIACR, 0x36, raw.hor_total >> (8 - 3) & 0x08, 0x08); + + /* lock timing registers */ + via_write_reg_mask(VIACR, 0x11, 0x80, 0x80); + + /* reset timing control */ + via_write_reg_mask(VIACR, 0x17, 0x00, 0x80); + via_write_reg_mask(VIACR, 0x17, 0x80, 0x80); +} + +void via_set_secondary_timing(const struct via_display_timing *timing) +{ + struct via_display_timing raw; + + raw.hor_total = timing->hor_total - 1; + raw.hor_addr = timing->hor_addr - 1; + raw.hor_blank_start = timing->hor_blank_start - 1; + raw.hor_blank_end = timing->hor_blank_end - 1; + raw.hor_sync_start = timing->hor_sync_start - 1; + raw.hor_sync_end = timing->hor_sync_end - 1; + raw.ver_total = timing->ver_total - 1; + raw.ver_addr = timing->ver_addr - 1; + raw.ver_blank_start = timing->ver_blank_start - 1; + raw.ver_blank_end = timing->ver_blank_end - 1; + raw.ver_sync_start = timing->ver_sync_start - 1; + raw.ver_sync_end = timing->ver_sync_end - 1; + + via_write_reg(VIACR, 0x50, raw.hor_total & 0xFF); + via_write_reg(VIACR, 0x51, raw.hor_addr & 0xFF); + via_write_reg(VIACR, 0x52, raw.hor_blank_start & 0xFF); + via_write_reg(VIACR, 0x53, raw.hor_blank_end & 0xFF); + via_write_reg(VIACR, 0x54, (raw.hor_blank_start >> 8 & 0x07) + | (raw.hor_blank_end >> (8 - 3) & 0x38) + | (raw.hor_sync_start >> (8 - 6) & 0xC0)); + via_write_reg_mask(VIACR, 0x55, (raw.hor_total >> 8 & 0x0F) + | (raw.hor_addr >> (8 - 4) & 0x70), 0x7F); + via_write_reg(VIACR, 0x56, raw.hor_sync_start & 0xFF); + via_write_reg(VIACR, 0x57, raw.hor_sync_end & 0xFF); + via_write_reg(VIACR, 0x58, raw.ver_total & 0xFF); + via_write_reg(VIACR, 0x59, raw.ver_addr & 0xFF); + via_write_reg(VIACR, 0x5A, raw.ver_blank_start & 0xFF); + via_write_reg(VIACR, 0x5B, raw.ver_blank_end & 0xFF); + via_write_reg(VIACR, 0x5C, (raw.ver_blank_start >> 8 & 0x07) + | (raw.ver_blank_end >> (8 - 3) & 0x38) + | (raw.hor_sync_end >> (8 - 6) & 0x40) + | (raw.hor_sync_start >> (10 - 7) & 0x80)); + via_write_reg(VIACR, 0x5D, (raw.ver_total >> 8 & 0x07) + | (raw.ver_addr >> (8 - 3) & 0x38) + | (raw.hor_blank_end >> (11 - 6) & 0x40) + | (raw.hor_sync_start >> (11 - 7) & 0x80)); + via_write_reg(VIACR, 0x5E, raw.ver_sync_start & 0xFF); + via_write_reg(VIACR, 0x5F, (raw.ver_sync_end & 0x1F) + | (raw.ver_sync_start >> (8 - 5) & 0xE0)); +} + +void via_set_primary_address(u32 addr) +{ + DEBUG_MSG(KERN_DEBUG "via_set_primary_address(0x%08X)\n", addr); + via_write_reg(VIACR, 0x0D, addr & 0xFF); + via_write_reg(VIACR, 0x0C, (addr >> 8) & 0xFF); + via_write_reg(VIACR, 0x34, (addr >> 16) & 0xFF); + via_write_reg_mask(VIACR, 0x48, (addr >> 24) & 0x1F, 0x1F); +} + +void via_set_secondary_address(u32 addr) +{ + DEBUG_MSG(KERN_DEBUG "via_set_secondary_address(0x%08X)\n", addr); + /* secondary display supports only quadword aligned memory */ + via_write_reg_mask(VIACR, 0x62, (addr >> 2) & 0xFE, 0xFE); + via_write_reg(VIACR, 0x63, (addr >> 10) & 0xFF); + via_write_reg(VIACR, 0x64, (addr >> 18) & 0xFF); + via_write_reg_mask(VIACR, 0xA3, (addr >> 26) & 0x07, 0x07); +} + +void via_set_primary_pitch(u32 pitch) +{ + DEBUG_MSG(KERN_DEBUG "via_set_primary_pitch(0x%08X)\n", pitch); + /* spec does not say that first adapter skips 3 bits but old + * code did it and seems to be reasonable in analogy to 2nd adapter + */ + pitch = pitch >> 3; + via_write_reg(VIACR, 0x13, pitch & 0xFF); + via_write_reg_mask(VIACR, 0x35, (pitch >> (8 - 5)) & 0xE0, 0xE0); +} + +void via_set_secondary_pitch(u32 pitch) +{ + DEBUG_MSG(KERN_DEBUG "via_set_secondary_pitch(0x%08X)\n", pitch); + pitch = pitch >> 3; + via_write_reg(VIACR, 0x66, pitch & 0xFF); + via_write_reg_mask(VIACR, 0x67, (pitch >> 8) & 0x03, 0x03); + via_write_reg_mask(VIACR, 0x71, (pitch >> (10 - 7)) & 0x80, 0x80); +} + +void via_set_primary_color_depth(u8 depth) +{ + u8 value; + + DEBUG_MSG(KERN_DEBUG "via_set_primary_color_depth(%d)\n", depth); + switch (depth) { + case 8: + value = 0x00; + break; + case 15: + value = 0x04; + break; + case 16: + value = 0x14; + break; + case 24: + value = 0x0C; + break; + case 30: + value = 0x08; + break; + default: + printk(KERN_WARNING "via_set_primary_color_depth: " + "Unsupported depth: %d\n", depth); + return; + } + + via_write_reg_mask(VIASR, 0x15, value, 0x1C); +} + +void via_set_secondary_color_depth(u8 depth) +{ + u8 value; + + DEBUG_MSG(KERN_DEBUG "via_set_secondary_color_depth(%d)\n", depth); + switch (depth) { + case 8: + value = 0x00; + break; + case 16: + value = 0x40; + break; + case 24: + value = 0xC0; + break; + case 30: + value = 0x80; + break; + default: + printk(KERN_WARNING "via_set_secondary_color_depth: " + "Unsupported depth: %d\n", depth); + return; + } + + via_write_reg_mask(VIACR, 0x67, value, 0xC0); +} diff --git a/drivers/video/fbdev/via/via_modesetting.h b/drivers/video/fbdev/via/via_modesetting.h new file mode 100644 index 000000000000..f6a6503da3b3 --- /dev/null +++ b/drivers/video/fbdev/via/via_modesetting.h @@ -0,0 +1,61 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + * Copyright 2010 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +/* + * basic modesetting functions + */ + +#ifndef __VIA_MODESETTING_H__ +#define __VIA_MODESETTING_H__ + +#include <linux/types.h> + + +#define VIA_PITCH_SIZE (1<<3) +#define VIA_PITCH_MAX 0x3FF8 + + +struct via_display_timing { + u16 hor_total; + u16 hor_addr; + u16 hor_blank_start; + u16 hor_blank_end; + u16 hor_sync_start; + u16 hor_sync_end; + u16 ver_total; + u16 ver_addr; + u16 ver_blank_start; + u16 ver_blank_end; + u16 ver_sync_start; + u16 ver_sync_end; +}; + + +void via_set_primary_timing(const struct via_display_timing *timing); +void via_set_secondary_timing(const struct via_display_timing *timing); +void via_set_primary_address(u32 addr); +void via_set_secondary_address(u32 addr); +void via_set_primary_pitch(u32 pitch); +void via_set_secondary_pitch(u32 pitch); +void via_set_primary_color_depth(u8 depth); +void via_set_secondary_color_depth(u8 depth); + +#endif /* __VIA_MODESETTING_H__ */ diff --git a/drivers/video/fbdev/via/via_utility.c b/drivers/video/fbdev/via/via_utility.c new file mode 100644 index 000000000000..35458a5eadc8 --- /dev/null +++ b/drivers/video/fbdev/via/via_utility.c @@ -0,0 +1,242 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/via-core.h> +#include "global.h" + +void viafb_get_device_support_state(u32 *support_state) +{ + *support_state = CRT_Device; + + if (viaparinfo->chip_info->tmds_chip_info.tmds_chip_name == VT1632_TMDS) + *support_state |= DVI_Device; + + if (viaparinfo->chip_info->lvds_chip_info.lvds_chip_name == VT1631_LVDS) + *support_state |= LCD_Device; +} + +void viafb_get_device_connect_state(u32 *connect_state) +{ + bool mobile = false; + + *connect_state = CRT_Device; + + if (viafb_dvi_sense()) + *connect_state |= DVI_Device; + + viafb_lcd_get_mobile_state(&mobile); + if (mobile) + *connect_state |= LCD_Device; +} + +bool viafb_lcd_get_support_expand_state(u32 xres, u32 yres) +{ + unsigned int support_state = 0; + + switch (viafb_lcd_panel_id) { + case LCD_PANEL_ID0_640X480: + if ((xres < 640) && (yres < 480)) + support_state = true; + break; + + case LCD_PANEL_ID1_800X600: + if ((xres < 800) && (yres < 600)) + support_state = true; + break; + + case LCD_PANEL_ID2_1024X768: + if ((xres < 1024) && (yres < 768)) + support_state = true; + break; + + case LCD_PANEL_ID3_1280X768: + if ((xres < 1280) && (yres < 768)) + support_state = true; + break; + + case LCD_PANEL_ID4_1280X1024: + if ((xres < 1280) && (yres < 1024)) + support_state = true; + break; + + case LCD_PANEL_ID5_1400X1050: + if ((xres < 1400) && (yres < 1050)) + support_state = true; + break; + + case LCD_PANEL_ID6_1600X1200: + if ((xres < 1600) && (yres < 1200)) + support_state = true; + break; + + case LCD_PANEL_ID7_1366X768: + if ((xres < 1366) && (yres < 768)) + support_state = true; + break; + + case LCD_PANEL_ID8_1024X600: + if ((xres < 1024) && (yres < 600)) + support_state = true; + break; + + case LCD_PANEL_ID9_1280X800: + if ((xres < 1280) && (yres < 800)) + support_state = true; + break; + + case LCD_PANEL_IDA_800X480: + if ((xres < 800) && (yres < 480)) + support_state = true; + break; + + case LCD_PANEL_IDB_1360X768: + if ((xres < 1360) && (yres < 768)) + support_state = true; + break; + + case LCD_PANEL_IDC_480X640: + if ((xres < 480) && (yres < 640)) + support_state = true; + break; + + default: + support_state = false; + break; + } + + return support_state; +} + +/*====================================================================*/ +/* Gamma Function Implementation*/ +/*====================================================================*/ + +void viafb_set_gamma_table(int bpp, unsigned int *gamma_table) +{ + int i, sr1a; + int active_device_amount = 0; + int device_status = viafb_DeviceStatus; + + for (i = 0; i < sizeof(viafb_DeviceStatus) * 8; i++) { + if (device_status & 1) + active_device_amount++; + device_status >>= 1; + } + + /* 8 bpp mode can't adjust gamma */ + if (bpp == 8) + return ; + + /* Enable Gamma */ + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CLE266: + case UNICHROME_K400: + viafb_write_reg_mask(SR16, VIASR, 0x80, BIT7); + break; + + case UNICHROME_K800: + case UNICHROME_PM800: + case UNICHROME_CN700: + case UNICHROME_CX700: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + viafb_write_reg_mask(CR33, VIACR, 0x80, BIT7); + break; + } + sr1a = (unsigned int)viafb_read_reg(VIASR, SR1A); + viafb_write_reg_mask(SR1A, VIASR, 0x0, BIT0); + + /* Fill IGA1 Gamma Table */ + outb(0, LUT_INDEX_WRITE); + for (i = 0; i < 256; i++) { + outb(gamma_table[i] >> 16, LUT_DATA); + outb(gamma_table[i] >> 8 & 0xFF, LUT_DATA); + outb(gamma_table[i] & 0xFF, LUT_DATA); + } + + /* If adjust Gamma value in SAMM, fill IGA1, + IGA2 Gamma table simultaneous. */ + /* Switch to IGA2 Gamma Table */ + if ((active_device_amount > 1) && + !((viaparinfo->chip_info->gfx_chip_name == + UNICHROME_CLE266) && + (viaparinfo->chip_info->gfx_chip_revision < 15))) { + viafb_write_reg_mask(SR1A, VIASR, 0x01, BIT0); + viafb_write_reg_mask(CR6A, VIACR, 0x02, BIT1); + + /* Fill IGA2 Gamma Table */ + outb(0, LUT_INDEX_WRITE); + for (i = 0; i < 256; i++) { + outb(gamma_table[i] >> 16, LUT_DATA); + outb(gamma_table[i] >> 8 & 0xFF, LUT_DATA); + outb(gamma_table[i] & 0xFF, LUT_DATA); + } + } + viafb_write_reg(SR1A, VIASR, sr1a); +} + +void viafb_get_gamma_table(unsigned int *gamma_table) +{ + unsigned char color_r, color_g, color_b; + unsigned char sr1a = 0; + int i; + + /* Enable Gamma */ + switch (viaparinfo->chip_info->gfx_chip_name) { + case UNICHROME_CLE266: + case UNICHROME_K400: + viafb_write_reg_mask(SR16, VIASR, 0x80, BIT7); + break; + + case UNICHROME_K800: + case UNICHROME_PM800: + case UNICHROME_CN700: + case UNICHROME_CX700: + case UNICHROME_K8M890: + case UNICHROME_P4M890: + case UNICHROME_P4M900: + viafb_write_reg_mask(CR33, VIACR, 0x80, BIT7); + break; + } + sr1a = viafb_read_reg(VIASR, SR1A); + viafb_write_reg_mask(SR1A, VIASR, 0x0, BIT0); + + /* Reading gamma table to get color value */ + outb(0, LUT_INDEX_READ); + for (i = 0; i < 256; i++) { + color_r = inb(LUT_DATA); + color_g = inb(LUT_DATA); + color_b = inb(LUT_DATA); + gamma_table[i] = + ((((u32) color_r) << 16) | + (((u16) color_g) << 8)) | color_b; + } + viafb_write_reg(SR1A, VIASR, sr1a); +} + +void viafb_get_gamma_support_state(int bpp, unsigned int *support_state) +{ + if (bpp == 8) + *support_state = None_Device; + else + *support_state = CRT_Device | DVI_Device | LCD_Device; +} diff --git a/drivers/video/fbdev/via/via_utility.h b/drivers/video/fbdev/via/via_utility.h new file mode 100644 index 000000000000..f23be1708c14 --- /dev/null +++ b/drivers/video/fbdev/via/via_utility.h @@ -0,0 +1,34 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ +#ifndef __VIAUTILITY_H__ +#define __VIAUTILITY_H__ + +/* These functions are used to get information about device's state */ +void viafb_get_device_support_state(u32 *support_state); +void viafb_get_device_connect_state(u32 *connect_state); +bool viafb_lcd_get_support_expand_state(u32 xres, u32 yres); + +/* These function are used to access gamma table */ +void viafb_set_gamma_table(int bpp, unsigned int *gamma_table); +void viafb_get_gamma_table(unsigned int *gamma_table); +void viafb_get_gamma_support_state(int bpp, unsigned int *support_state); + +#endif /* __VIAUTILITY_H__ */ diff --git a/drivers/video/fbdev/via/viafbdev.c b/drivers/video/fbdev/via/viafbdev.c new file mode 100644 index 000000000000..325c43c6ff97 --- /dev/null +++ b/drivers/video/fbdev/via/viafbdev.c @@ -0,0 +1,2176 @@ +/* + * Copyright 1998-2009 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/seq_file.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/via-core.h> +#include <linux/via_i2c.h> +#include <asm/olpc.h> + +#define _MASTER_FILE +#include "global.h" + +static char *viafb_name = "Via"; +static u32 pseudo_pal[17]; + +/* video mode */ +static char *viafb_mode; +static char *viafb_mode1; +static int viafb_bpp = 32; +static int viafb_bpp1 = 32; + +static unsigned int viafb_second_offset; +static int viafb_second_size; + +static int viafb_accel = 1; + +/* Added for specifying active devices.*/ +static char *viafb_active_dev; + +/*Added for specify lcd output port*/ +static char *viafb_lcd_port = ""; +static char *viafb_dvi_port = ""; + +static void retrieve_device_setting(struct viafb_ioctl_setting + *setting_info); +static int viafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); + +static struct fb_ops viafb_ops; + +/* supported output devices on each IGP + * only CX700, VX800, VX855, VX900 were documented + * VIA_CRT should be everywhere + * VIA_6C can be onle pre-CX700 (probably only on CLE266) as 6C is used for PLL + * source selection on CX700 and later + * K400 seems to support VIA_96, VIA_DVP1, VIA_LVDS{1,2} as in viamode.c + */ +static const u32 supported_odev_map[] = { + [UNICHROME_CLE266] = VIA_CRT | VIA_LDVP0 | VIA_LDVP1, + [UNICHROME_K400] = VIA_CRT | VIA_DVP0 | VIA_DVP1 | VIA_LVDS1 + | VIA_LVDS2, + [UNICHROME_K800] = VIA_CRT | VIA_DVP0 | VIA_DVP1 | VIA_LVDS1 + | VIA_LVDS2, + [UNICHROME_PM800] = VIA_CRT | VIA_DVP0 | VIA_DVP1 | VIA_LVDS1 + | VIA_LVDS2, + [UNICHROME_CN700] = VIA_CRT | VIA_DVP0 | VIA_DVP1 | VIA_LVDS1 + | VIA_LVDS2, + [UNICHROME_CX700] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, + [UNICHROME_CN750] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, + [UNICHROME_K8M890] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, + [UNICHROME_P4M890] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, + [UNICHROME_P4M900] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, + [UNICHROME_VX800] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, + [UNICHROME_VX855] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, + [UNICHROME_VX900] = VIA_CRT | VIA_DVP1 | VIA_LVDS1 | VIA_LVDS2, +}; + +static void viafb_fill_var_color_info(struct fb_var_screeninfo *var, u8 depth) +{ + var->grayscale = 0; + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + var->transp.msb_right = 0; + var->nonstd = 0; + switch (depth) { + case 8: + var->bits_per_pixel = 8; + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case 15: + var->bits_per_pixel = 16; + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case 16: + var->bits_per_pixel = 16; + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + break; + case 24: + var->bits_per_pixel = 32; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case 30: + var->bits_per_pixel = 32; + var->red.offset = 20; + var->green.offset = 10; + var->blue.offset = 0; + var->red.length = 10; + var->green.length = 10; + var->blue.length = 10; + break; + } +} + +static void viafb_update_fix(struct fb_info *info) +{ + u32 bpp = info->var.bits_per_pixel; + + info->fix.visual = + bpp == 8 ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + info->fix.line_length = ALIGN(info->var.xres_virtual * bpp / 8, + VIA_PITCH_SIZE); +} + +static void viafb_setup_fixinfo(struct fb_fix_screeninfo *fix, + struct viafb_par *viaparinfo) +{ + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + strcpy(fix->id, viafb_name); + + fix->smem_start = viaparinfo->fbmem; + fix->smem_len = viaparinfo->fbmem_free; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->type_aux = 0; + fix->visual = FB_VISUAL_TRUECOLOR; + + fix->xpanstep = fix->ywrapstep = 0; + fix->ypanstep = 1; + + /* Just tell the accel name */ + viafbinfo->fix.accel = FB_ACCEL_VIA_UNICHROME; +} +static int viafb_open(struct fb_info *info, int user) +{ + DEBUG_MSG(KERN_INFO "viafb_open!\n"); + return 0; +} + +static int viafb_release(struct fb_info *info, int user) +{ + DEBUG_MSG(KERN_INFO "viafb_release!\n"); + return 0; +} + +static inline int get_var_refresh(struct fb_var_screeninfo *var) +{ + u32 htotal, vtotal; + + htotal = var->left_margin + var->xres + var->right_margin + + var->hsync_len; + vtotal = var->upper_margin + var->yres + var->lower_margin + + var->vsync_len; + return PICOS2KHZ(var->pixclock) * 1000 / (htotal * vtotal); +} + +static int viafb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int depth, refresh; + struct viafb_par *ppar = info->par; + u32 line; + + DEBUG_MSG(KERN_INFO "viafb_check_var!\n"); + /* Sanity check */ + /* HW neither support interlacte nor double-scaned mode */ + if (var->vmode & FB_VMODE_INTERLACED || var->vmode & FB_VMODE_DOUBLE) + return -EINVAL; + + /* the refresh rate is not important here, as we only want to know + * whether the resolution exists + */ + if (!viafb_get_best_mode(var->xres, var->yres, 60)) { + DEBUG_MSG(KERN_INFO + "viafb: Mode %dx%dx%d not supported!!\n", + var->xres, var->yres, var->bits_per_pixel); + return -EINVAL; + } + + depth = fb_get_color_depth(var, &info->fix); + if (!depth) + depth = var->bits_per_pixel; + + if (depth < 0 || depth > 32) + return -EINVAL; + else if (!depth) + depth = 24; + else if (depth == 15 && viafb_dual_fb && ppar->iga_path == IGA1) + depth = 15; + else if (depth == 30) + depth = 30; + else if (depth <= 8) + depth = 8; + else if (depth <= 16) + depth = 16; + else + depth = 24; + + viafb_fill_var_color_info(var, depth); + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + line = ALIGN(var->xres_virtual * var->bits_per_pixel / 8, + VIA_PITCH_SIZE); + if (line > VIA_PITCH_MAX || line * var->yres_virtual > ppar->memsize) + return -EINVAL; + + /* Based on var passed in to calculate the refresh, + * because our driver use some modes special. + */ + refresh = viafb_get_refresh(var->xres, var->yres, + get_var_refresh(var)); + + /* Adjust var according to our driver's own table */ + viafb_fill_var_timing_info(var, + viafb_get_best_mode(var->xres, var->yres, refresh)); + if (var->accel_flags & FB_ACCELF_TEXT && + !ppar->shared->vdev->engine_mmio) + var->accel_flags = 0; + + return 0; +} + +static int viafb_set_par(struct fb_info *info) +{ + struct viafb_par *viapar = info->par; + int refresh; + DEBUG_MSG(KERN_INFO "viafb_set_par!\n"); + + viafb_update_fix(info); + viapar->depth = fb_get_color_depth(&info->var, &info->fix); + viafb_update_device_setting(viafbinfo->var.xres, viafbinfo->var.yres, + viafbinfo->var.bits_per_pixel, 0); + + if (viafb_dual_fb) { + viafb_update_device_setting(viafbinfo1->var.xres, + viafbinfo1->var.yres, viafbinfo1->var.bits_per_pixel, + 1); + } else if (viafb_SAMM_ON == 1) { + DEBUG_MSG(KERN_INFO + "viafb_second_xres = %d, viafb_second_yres = %d, bpp = %d\n", + viafb_second_xres, viafb_second_yres, viafb_bpp1); + + viafb_update_device_setting(viafb_second_xres, + viafb_second_yres, viafb_bpp1, 1); + } + + refresh = get_var_refresh(&info->var); + if (viafb_dual_fb && viapar->iga_path == IGA2) { + viafb_bpp1 = info->var.bits_per_pixel; + viafb_refresh1 = refresh; + } else { + viafb_bpp = info->var.bits_per_pixel; + viafb_refresh = refresh; + } + + if (info->var.accel_flags & FB_ACCELF_TEXT) + info->flags &= ~FBINFO_HWACCEL_DISABLED; + else + info->flags |= FBINFO_HWACCEL_DISABLED; + viafb_setmode(); + viafb_pan_display(&info->var, info); + + return 0; +} + +/* Set one color register */ +static int viafb_setcolreg(unsigned regno, unsigned red, unsigned green, +unsigned blue, unsigned transp, struct fb_info *info) +{ + struct viafb_par *viapar = info->par; + u32 r, g, b; + + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) { + if (regno > 255) + return -EINVAL; + + if (!viafb_dual_fb || viapar->iga_path == IGA1) + viafb_set_primary_color_register(regno, red >> 8, + green >> 8, blue >> 8); + + if (!viafb_dual_fb || viapar->iga_path == IGA2) + viafb_set_secondary_color_register(regno, red >> 8, + green >> 8, blue >> 8); + } else { + if (regno > 15) + return -EINVAL; + + r = (red >> (16 - info->var.red.length)) + << info->var.red.offset; + b = (blue >> (16 - info->var.blue.length)) + << info->var.blue.offset; + g = (green >> (16 - info->var.green.length)) + << info->var.green.offset; + ((u32 *) info->pseudo_palette)[regno] = r | g | b; + } + + return 0; +} + +static int viafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct viafb_par *viapar = info->par; + u32 vram_addr = viapar->vram_addr + + var->yoffset * info->fix.line_length + + var->xoffset * info->var.bits_per_pixel / 8; + + DEBUG_MSG(KERN_DEBUG "viafb_pan_display, address = %d\n", vram_addr); + if (!viafb_dual_fb) { + via_set_primary_address(vram_addr); + via_set_secondary_address(vram_addr); + } else if (viapar->iga_path == IGA1) + via_set_primary_address(vram_addr); + else + via_set_secondary_address(vram_addr); + + return 0; +} + +static int viafb_blank(int blank_mode, struct fb_info *info) +{ + DEBUG_MSG(KERN_INFO "viafb_blank!\n"); + /* clear DPMS setting */ + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + /* Screen: On, HSync: On, VSync: On */ + /* control CRT monitor power management */ + via_set_state(VIA_CRT, VIA_STATE_ON); + break; + case FB_BLANK_HSYNC_SUSPEND: + /* Screen: Off, HSync: Off, VSync: On */ + /* control CRT monitor power management */ + via_set_state(VIA_CRT, VIA_STATE_STANDBY); + break; + case FB_BLANK_VSYNC_SUSPEND: + /* Screen: Off, HSync: On, VSync: Off */ + /* control CRT monitor power management */ + via_set_state(VIA_CRT, VIA_STATE_SUSPEND); + break; + case FB_BLANK_POWERDOWN: + /* Screen: Off, HSync: Off, VSync: Off */ + /* control CRT monitor power management */ + via_set_state(VIA_CRT, VIA_STATE_OFF); + break; + } + + return 0; +} + +static int viafb_ioctl(struct fb_info *info, u_int cmd, u_long arg) +{ + union { + struct viafb_ioctl_mode viamode; + struct viafb_ioctl_samm viasamm; + struct viafb_driver_version driver_version; + struct fb_var_screeninfo sec_var; + struct _panel_size_pos_info panel_pos_size_para; + struct viafb_ioctl_setting viafb_setting; + struct device_t active_dev; + } u; + u32 state_info = 0; + u32 *viafb_gamma_table; + char driver_name[] = "viafb"; + + u32 __user *argp = (u32 __user *) arg; + u32 gpu32; + + DEBUG_MSG(KERN_INFO "viafb_ioctl: 0x%X !!\n", cmd); + printk(KERN_WARNING "viafb_ioctl: Please avoid this interface as it is unstable and might change or vanish at any time!\n"); + memset(&u, 0, sizeof(u)); + + switch (cmd) { + case VIAFB_GET_CHIP_INFO: + if (copy_to_user(argp, viaparinfo->chip_info, + sizeof(struct chip_information))) + return -EFAULT; + break; + case VIAFB_GET_INFO_SIZE: + return put_user((u32)sizeof(struct viafb_ioctl_info), argp); + case VIAFB_GET_INFO: + return viafb_ioctl_get_viafb_info(arg); + case VIAFB_HOTPLUG: + return put_user(viafb_ioctl_hotplug(info->var.xres, + info->var.yres, + info->var.bits_per_pixel), argp); + case VIAFB_SET_HOTPLUG_FLAG: + if (copy_from_user(&gpu32, argp, sizeof(gpu32))) + return -EFAULT; + viafb_hotplug = (gpu32) ? 1 : 0; + break; + case VIAFB_GET_RESOLUTION: + u.viamode.xres = (u32) viafb_hotplug_Xres; + u.viamode.yres = (u32) viafb_hotplug_Yres; + u.viamode.refresh = (u32) viafb_hotplug_refresh; + u.viamode.bpp = (u32) viafb_hotplug_bpp; + if (viafb_SAMM_ON == 1) { + u.viamode.xres_sec = viafb_second_xres; + u.viamode.yres_sec = viafb_second_yres; + u.viamode.virtual_xres_sec = viafb_dual_fb ? viafbinfo1->var.xres_virtual : viafbinfo->var.xres_virtual; + u.viamode.virtual_yres_sec = viafb_dual_fb ? viafbinfo1->var.yres_virtual : viafbinfo->var.yres_virtual; + u.viamode.refresh_sec = viafb_refresh1; + u.viamode.bpp_sec = viafb_bpp1; + } else { + u.viamode.xres_sec = 0; + u.viamode.yres_sec = 0; + u.viamode.virtual_xres_sec = 0; + u.viamode.virtual_yres_sec = 0; + u.viamode.refresh_sec = 0; + u.viamode.bpp_sec = 0; + } + if (copy_to_user(argp, &u.viamode, sizeof(u.viamode))) + return -EFAULT; + break; + case VIAFB_GET_SAMM_INFO: + u.viasamm.samm_status = viafb_SAMM_ON; + + if (viafb_SAMM_ON == 1) { + if (viafb_dual_fb) { + u.viasamm.size_prim = viaparinfo->fbmem_free; + u.viasamm.size_sec = viaparinfo1->fbmem_free; + } else { + if (viafb_second_size) { + u.viasamm.size_prim = + viaparinfo->fbmem_free - + viafb_second_size * 1024 * 1024; + u.viasamm.size_sec = + viafb_second_size * 1024 * 1024; + } else { + u.viasamm.size_prim = + viaparinfo->fbmem_free >> 1; + u.viasamm.size_sec = + (viaparinfo->fbmem_free >> 1); + } + } + u.viasamm.mem_base = viaparinfo->fbmem; + u.viasamm.offset_sec = viafb_second_offset; + } else { + u.viasamm.size_prim = + viaparinfo->memsize - viaparinfo->fbmem_used; + u.viasamm.size_sec = 0; + u.viasamm.mem_base = viaparinfo->fbmem; + u.viasamm.offset_sec = 0; + } + + if (copy_to_user(argp, &u.viasamm, sizeof(u.viasamm))) + return -EFAULT; + + break; + case VIAFB_TURN_ON_OUTPUT_DEVICE: + if (copy_from_user(&gpu32, argp, sizeof(gpu32))) + return -EFAULT; + if (gpu32 & CRT_Device) + via_set_state(VIA_CRT, VIA_STATE_ON); + if (gpu32 & DVI_Device) + viafb_dvi_enable(); + if (gpu32 & LCD_Device) + viafb_lcd_enable(); + break; + case VIAFB_TURN_OFF_OUTPUT_DEVICE: + if (copy_from_user(&gpu32, argp, sizeof(gpu32))) + return -EFAULT; + if (gpu32 & CRT_Device) + via_set_state(VIA_CRT, VIA_STATE_OFF); + if (gpu32 & DVI_Device) + viafb_dvi_disable(); + if (gpu32 & LCD_Device) + viafb_lcd_disable(); + break; + case VIAFB_GET_DEVICE: + u.active_dev.crt = viafb_CRT_ON; + u.active_dev.dvi = viafb_DVI_ON; + u.active_dev.lcd = viafb_LCD_ON; + u.active_dev.samm = viafb_SAMM_ON; + u.active_dev.primary_dev = viafb_primary_dev; + + u.active_dev.lcd_dsp_cent = viafb_lcd_dsp_method; + u.active_dev.lcd_panel_id = viafb_lcd_panel_id; + u.active_dev.lcd_mode = viafb_lcd_mode; + + u.active_dev.xres = viafb_hotplug_Xres; + u.active_dev.yres = viafb_hotplug_Yres; + + u.active_dev.xres1 = viafb_second_xres; + u.active_dev.yres1 = viafb_second_yres; + + u.active_dev.bpp = viafb_bpp; + u.active_dev.bpp1 = viafb_bpp1; + u.active_dev.refresh = viafb_refresh; + u.active_dev.refresh1 = viafb_refresh1; + + u.active_dev.epia_dvi = viafb_platform_epia_dvi; + u.active_dev.lcd_dual_edge = viafb_device_lcd_dualedge; + u.active_dev.bus_width = viafb_bus_width; + + if (copy_to_user(argp, &u.active_dev, sizeof(u.active_dev))) + return -EFAULT; + break; + + case VIAFB_GET_DRIVER_VERSION: + u.driver_version.iMajorNum = VERSION_MAJOR; + u.driver_version.iKernelNum = VERSION_KERNEL; + u.driver_version.iOSNum = VERSION_OS; + u.driver_version.iMinorNum = VERSION_MINOR; + + if (copy_to_user(argp, &u.driver_version, + sizeof(u.driver_version))) + return -EFAULT; + + break; + + case VIAFB_GET_DEVICE_INFO: + + retrieve_device_setting(&u.viafb_setting); + + if (copy_to_user(argp, &u.viafb_setting, + sizeof(u.viafb_setting))) + return -EFAULT; + + break; + + case VIAFB_GET_DEVICE_SUPPORT: + viafb_get_device_support_state(&state_info); + if (put_user(state_info, argp)) + return -EFAULT; + break; + + case VIAFB_GET_DEVICE_CONNECT: + viafb_get_device_connect_state(&state_info); + if (put_user(state_info, argp)) + return -EFAULT; + break; + + case VIAFB_GET_PANEL_SUPPORT_EXPAND: + state_info = + viafb_lcd_get_support_expand_state(info->var.xres, + info->var.yres); + if (put_user(state_info, argp)) + return -EFAULT; + break; + + case VIAFB_GET_DRIVER_NAME: + if (copy_to_user(argp, driver_name, sizeof(driver_name))) + return -EFAULT; + break; + + case VIAFB_SET_GAMMA_LUT: + viafb_gamma_table = memdup_user(argp, 256 * sizeof(u32)); + if (IS_ERR(viafb_gamma_table)) + return PTR_ERR(viafb_gamma_table); + viafb_set_gamma_table(viafb_bpp, viafb_gamma_table); + kfree(viafb_gamma_table); + break; + + case VIAFB_GET_GAMMA_LUT: + viafb_gamma_table = kmalloc(256 * sizeof(u32), GFP_KERNEL); + if (!viafb_gamma_table) + return -ENOMEM; + viafb_get_gamma_table(viafb_gamma_table); + if (copy_to_user(argp, viafb_gamma_table, + 256 * sizeof(u32))) { + kfree(viafb_gamma_table); + return -EFAULT; + } + kfree(viafb_gamma_table); + break; + + case VIAFB_GET_GAMMA_SUPPORT_STATE: + viafb_get_gamma_support_state(viafb_bpp, &state_info); + if (put_user(state_info, argp)) + return -EFAULT; + break; + case VIAFB_SYNC_SURFACE: + DEBUG_MSG(KERN_INFO "lobo VIAFB_SYNC_SURFACE\n"); + break; + case VIAFB_GET_DRIVER_CAPS: + break; + + case VIAFB_GET_PANEL_MAX_SIZE: + if (copy_from_user(&u.panel_pos_size_para, argp, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + u.panel_pos_size_para.x = u.panel_pos_size_para.y = 0; + if (copy_to_user(argp, &u.panel_pos_size_para, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + break; + case VIAFB_GET_PANEL_MAX_POSITION: + if (copy_from_user(&u.panel_pos_size_para, argp, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + u.panel_pos_size_para.x = u.panel_pos_size_para.y = 0; + if (copy_to_user(argp, &u.panel_pos_size_para, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + break; + + case VIAFB_GET_PANEL_POSITION: + if (copy_from_user(&u.panel_pos_size_para, argp, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + u.panel_pos_size_para.x = u.panel_pos_size_para.y = 0; + if (copy_to_user(argp, &u.panel_pos_size_para, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + break; + case VIAFB_GET_PANEL_SIZE: + if (copy_from_user(&u.panel_pos_size_para, argp, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + u.panel_pos_size_para.x = u.panel_pos_size_para.y = 0; + if (copy_to_user(argp, &u.panel_pos_size_para, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + break; + + case VIAFB_SET_PANEL_POSITION: + if (copy_from_user(&u.panel_pos_size_para, argp, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + break; + case VIAFB_SET_PANEL_SIZE: + if (copy_from_user(&u.panel_pos_size_para, argp, + sizeof(u.panel_pos_size_para))) + return -EFAULT; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void viafb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct viafb_par *viapar = info->par; + struct viafb_shared *shared = viapar->shared; + u32 fg_color; + u8 rop; + + if (info->flags & FBINFO_HWACCEL_DISABLED || !shared->hw_bitblt) { + cfb_fillrect(info, rect); + return; + } + + if (!rect->width || !rect->height) + return; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) + fg_color = ((u32 *)info->pseudo_palette)[rect->color]; + else + fg_color = rect->color; + + if (rect->rop == ROP_XOR) + rop = 0x5A; + else + rop = 0xF0; + + DEBUG_MSG(KERN_DEBUG "viafb 2D engine: fillrect\n"); + if (shared->hw_bitblt(shared->vdev->engine_mmio, VIA_BITBLT_FILL, + rect->width, rect->height, info->var.bits_per_pixel, + viapar->vram_addr, info->fix.line_length, rect->dx, rect->dy, + NULL, 0, 0, 0, 0, fg_color, 0, rop)) + cfb_fillrect(info, rect); +} + +static void viafb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct viafb_par *viapar = info->par; + struct viafb_shared *shared = viapar->shared; + + if (info->flags & FBINFO_HWACCEL_DISABLED || !shared->hw_bitblt) { + cfb_copyarea(info, area); + return; + } + + if (!area->width || !area->height) + return; + + DEBUG_MSG(KERN_DEBUG "viafb 2D engine: copyarea\n"); + if (shared->hw_bitblt(shared->vdev->engine_mmio, VIA_BITBLT_COLOR, + area->width, area->height, info->var.bits_per_pixel, + viapar->vram_addr, info->fix.line_length, area->dx, area->dy, + NULL, viapar->vram_addr, info->fix.line_length, + area->sx, area->sy, 0, 0, 0)) + cfb_copyarea(info, area); +} + +static void viafb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct viafb_par *viapar = info->par; + struct viafb_shared *shared = viapar->shared; + u32 fg_color = 0, bg_color = 0; + u8 op; + + if (info->flags & FBINFO_HWACCEL_DISABLED || !shared->hw_bitblt || + (image->depth != 1 && image->depth != viapar->depth)) { + cfb_imageblit(info, image); + return; + } + + if (image->depth == 1) { + op = VIA_BITBLT_MONO; + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + fg_color = + ((u32 *)info->pseudo_palette)[image->fg_color]; + bg_color = + ((u32 *)info->pseudo_palette)[image->bg_color]; + } else { + fg_color = image->fg_color; + bg_color = image->bg_color; + } + } else + op = VIA_BITBLT_COLOR; + + DEBUG_MSG(KERN_DEBUG "viafb 2D engine: imageblit\n"); + if (shared->hw_bitblt(shared->vdev->engine_mmio, op, + image->width, image->height, info->var.bits_per_pixel, + viapar->vram_addr, info->fix.line_length, image->dx, image->dy, + (u32 *)image->data, 0, 0, 0, 0, fg_color, bg_color, 0)) + cfb_imageblit(info, image); +} + +static int viafb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct viafb_par *viapar = info->par; + void __iomem *engine = viapar->shared->vdev->engine_mmio; + u32 temp, xx, yy, bg_color = 0, fg_color = 0, + chip_name = viapar->shared->chip_info.gfx_chip_name; + int i, j = 0, cur_size = 64; + + if (info->flags & FBINFO_HWACCEL_DISABLED || info != viafbinfo) + return -ENODEV; + + /* LCD ouput does not support hw cursors (at least on VN896) */ + if ((chip_name == UNICHROME_CLE266 && viapar->iga_path == IGA2) || + viafb_LCD_ON) + return -ENODEV; + + viafb_show_hw_cursor(info, HW_Cursor_OFF); + + if (cursor->set & FB_CUR_SETHOT) { + temp = (cursor->hot.x << 16) + cursor->hot.y; + writel(temp, engine + VIA_REG_CURSOR_ORG); + } + + if (cursor->set & FB_CUR_SETPOS) { + yy = cursor->image.dy - info->var.yoffset; + xx = cursor->image.dx - info->var.xoffset; + temp = yy & 0xFFFF; + temp |= (xx << 16); + writel(temp, engine + VIA_REG_CURSOR_POS); + } + + if (cursor->image.width <= 32 && cursor->image.height <= 32) + cur_size = 32; + else if (cursor->image.width <= 64 && cursor->image.height <= 64) + cur_size = 64; + else { + printk(KERN_WARNING "viafb_cursor: The cursor is too large " + "%dx%d", cursor->image.width, cursor->image.height); + return -ENXIO; + } + + if (cursor->set & FB_CUR_SETSIZE) { + temp = readl(engine + VIA_REG_CURSOR_MODE); + if (cur_size == 32) + temp |= 0x2; + else + temp &= ~0x2; + + writel(temp, engine + VIA_REG_CURSOR_MODE); + } + + if (cursor->set & FB_CUR_SETCMAP) { + fg_color = cursor->image.fg_color; + bg_color = cursor->image.bg_color; + if (chip_name == UNICHROME_CX700 || + chip_name == UNICHROME_VX800 || + chip_name == UNICHROME_VX855 || + chip_name == UNICHROME_VX900) { + fg_color = + ((info->cmap.red[fg_color] & 0xFFC0) << 14) | + ((info->cmap.green[fg_color] & 0xFFC0) << 4) | + ((info->cmap.blue[fg_color] & 0xFFC0) >> 6); + bg_color = + ((info->cmap.red[bg_color] & 0xFFC0) << 14) | + ((info->cmap.green[bg_color] & 0xFFC0) << 4) | + ((info->cmap.blue[bg_color] & 0xFFC0) >> 6); + } else { + fg_color = + ((info->cmap.red[fg_color] & 0xFF00) << 8) | + (info->cmap.green[fg_color] & 0xFF00) | + ((info->cmap.blue[fg_color] & 0xFF00) >> 8); + bg_color = + ((info->cmap.red[bg_color] & 0xFF00) << 8) | + (info->cmap.green[bg_color] & 0xFF00) | + ((info->cmap.blue[bg_color] & 0xFF00) >> 8); + } + + writel(bg_color, engine + VIA_REG_CURSOR_BG); + writel(fg_color, engine + VIA_REG_CURSOR_FG); + } + + if (cursor->set & FB_CUR_SETSHAPE) { + struct { + u8 data[CURSOR_SIZE]; + u32 bak[CURSOR_SIZE / 4]; + } *cr_data = kzalloc(sizeof(*cr_data), GFP_ATOMIC); + int size = ((cursor->image.width + 7) >> 3) * + cursor->image.height; + + if (!cr_data) + return -ENOMEM; + + if (cur_size == 32) { + for (i = 0; i < (CURSOR_SIZE / 4); i++) { + cr_data->bak[i] = 0x0; + cr_data->bak[i + 1] = 0xFFFFFFFF; + i += 1; + } + } else { + for (i = 0; i < (CURSOR_SIZE / 4); i++) { + cr_data->bak[i] = 0x0; + cr_data->bak[i + 1] = 0x0; + cr_data->bak[i + 2] = 0xFFFFFFFF; + cr_data->bak[i + 3] = 0xFFFFFFFF; + i += 3; + } + } + + switch (cursor->rop) { + case ROP_XOR: + for (i = 0; i < size; i++) + cr_data->data[i] = cursor->mask[i]; + break; + case ROP_COPY: + + for (i = 0; i < size; i++) + cr_data->data[i] = cursor->mask[i]; + break; + default: + break; + } + + if (cur_size == 32) { + for (i = 0; i < size; i++) { + cr_data->bak[j] = (u32) cr_data->data[i]; + cr_data->bak[j + 1] = ~cr_data->bak[j]; + j += 2; + } + } else { + for (i = 0; i < size; i++) { + cr_data->bak[j] = (u32) cr_data->data[i]; + cr_data->bak[j + 1] = 0x0; + cr_data->bak[j + 2] = ~cr_data->bak[j]; + cr_data->bak[j + 3] = ~cr_data->bak[j + 1]; + j += 4; + } + } + + memcpy_toio(viafbinfo->screen_base + viapar->shared-> + cursor_vram_addr, cr_data->bak, CURSOR_SIZE); + kfree(cr_data); + } + + if (cursor->enable) + viafb_show_hw_cursor(info, HW_Cursor_ON); + + return 0; +} + +static int viafb_sync(struct fb_info *info) +{ + if (!(info->flags & FBINFO_HWACCEL_DISABLED)) + viafb_wait_engine_idle(info); + return 0; +} + +static int get_primary_device(void) +{ + int primary_device = 0; + /* Rule: device on iga1 path are the primary device. */ + if (viafb_SAMM_ON) { + if (viafb_CRT_ON) { + if (viaparinfo->shared->iga1_devices & VIA_CRT) { + DEBUG_MSG(KERN_INFO "CRT IGA Path:%d\n", IGA1); + primary_device = CRT_Device; + } + } + if (viafb_DVI_ON) { + if (viaparinfo->tmds_setting_info->iga_path == IGA1) { + DEBUG_MSG(KERN_INFO "DVI IGA Path:%d\n", + viaparinfo-> + tmds_setting_info->iga_path); + primary_device = DVI_Device; + } + } + if (viafb_LCD_ON) { + if (viaparinfo->lvds_setting_info->iga_path == IGA1) { + DEBUG_MSG(KERN_INFO "LCD IGA Path:%d\n", + viaparinfo-> + lvds_setting_info->iga_path); + primary_device = LCD_Device; + } + } + if (viafb_LCD2_ON) { + if (viaparinfo->lvds_setting_info2->iga_path == IGA1) { + DEBUG_MSG(KERN_INFO "LCD2 IGA Path:%d\n", + viaparinfo-> + lvds_setting_info2->iga_path); + primary_device = LCD2_Device; + } + } + } + return primary_device; +} + +static void retrieve_device_setting(struct viafb_ioctl_setting + *setting_info) +{ + + /* get device status */ + if (viafb_CRT_ON == 1) + setting_info->device_status = CRT_Device; + if (viafb_DVI_ON == 1) + setting_info->device_status |= DVI_Device; + if (viafb_LCD_ON == 1) + setting_info->device_status |= LCD_Device; + if (viafb_LCD2_ON == 1) + setting_info->device_status |= LCD2_Device; + + setting_info->samm_status = viafb_SAMM_ON; + setting_info->primary_device = get_primary_device(); + + setting_info->first_dev_bpp = viafb_bpp; + setting_info->second_dev_bpp = viafb_bpp1; + + setting_info->first_dev_refresh = viafb_refresh; + setting_info->second_dev_refresh = viafb_refresh1; + + setting_info->first_dev_hor_res = viafb_hotplug_Xres; + setting_info->first_dev_ver_res = viafb_hotplug_Yres; + setting_info->second_dev_hor_res = viafb_second_xres; + setting_info->second_dev_ver_res = viafb_second_yres; + + /* Get lcd attributes */ + setting_info->lcd_attributes.display_center = viafb_lcd_dsp_method; + setting_info->lcd_attributes.panel_id = viafb_lcd_panel_id; + setting_info->lcd_attributes.lcd_mode = viafb_lcd_mode; +} + +static int __init parse_active_dev(void) +{ + viafb_CRT_ON = STATE_OFF; + viafb_DVI_ON = STATE_OFF; + viafb_LCD_ON = STATE_OFF; + viafb_LCD2_ON = STATE_OFF; + /* 1. Modify the active status of devices. */ + /* 2. Keep the order of devices, so we can set corresponding + IGA path to devices in SAMM case. */ + /* Note: The previous of active_dev is primary device, + and the following is secondary device. */ + if (!viafb_active_dev) { + if (machine_is_olpc()) { /* LCD only */ + viafb_LCD_ON = STATE_ON; + viafb_SAMM_ON = STATE_OFF; + } else { + viafb_CRT_ON = STATE_ON; + viafb_SAMM_ON = STATE_OFF; + } + } else if (!strcmp(viafb_active_dev, "CRT+DVI")) { + /* CRT+DVI */ + viafb_CRT_ON = STATE_ON; + viafb_DVI_ON = STATE_ON; + viafb_primary_dev = CRT_Device; + } else if (!strcmp(viafb_active_dev, "DVI+CRT")) { + /* DVI+CRT */ + viafb_CRT_ON = STATE_ON; + viafb_DVI_ON = STATE_ON; + viafb_primary_dev = DVI_Device; + } else if (!strcmp(viafb_active_dev, "CRT+LCD")) { + /* CRT+LCD */ + viafb_CRT_ON = STATE_ON; + viafb_LCD_ON = STATE_ON; + viafb_primary_dev = CRT_Device; + } else if (!strcmp(viafb_active_dev, "LCD+CRT")) { + /* LCD+CRT */ + viafb_CRT_ON = STATE_ON; + viafb_LCD_ON = STATE_ON; + viafb_primary_dev = LCD_Device; + } else if (!strcmp(viafb_active_dev, "DVI+LCD")) { + /* DVI+LCD */ + viafb_DVI_ON = STATE_ON; + viafb_LCD_ON = STATE_ON; + viafb_primary_dev = DVI_Device; + } else if (!strcmp(viafb_active_dev, "LCD+DVI")) { + /* LCD+DVI */ + viafb_DVI_ON = STATE_ON; + viafb_LCD_ON = STATE_ON; + viafb_primary_dev = LCD_Device; + } else if (!strcmp(viafb_active_dev, "LCD+LCD2")) { + viafb_LCD_ON = STATE_ON; + viafb_LCD2_ON = STATE_ON; + viafb_primary_dev = LCD_Device; + } else if (!strcmp(viafb_active_dev, "LCD2+LCD")) { + viafb_LCD_ON = STATE_ON; + viafb_LCD2_ON = STATE_ON; + viafb_primary_dev = LCD2_Device; + } else if (!strcmp(viafb_active_dev, "CRT")) { + /* CRT only */ + viafb_CRT_ON = STATE_ON; + viafb_SAMM_ON = STATE_OFF; + } else if (!strcmp(viafb_active_dev, "DVI")) { + /* DVI only */ + viafb_DVI_ON = STATE_ON; + viafb_SAMM_ON = STATE_OFF; + } else if (!strcmp(viafb_active_dev, "LCD")) { + /* LCD only */ + viafb_LCD_ON = STATE_ON; + viafb_SAMM_ON = STATE_OFF; + } else + return -EINVAL; + + return 0; +} + +static int parse_port(char *opt_str, int *output_interface) +{ + if (!strncmp(opt_str, "DVP0", 4)) + *output_interface = INTERFACE_DVP0; + else if (!strncmp(opt_str, "DVP1", 4)) + *output_interface = INTERFACE_DVP1; + else if (!strncmp(opt_str, "DFP_HIGHLOW", 11)) + *output_interface = INTERFACE_DFP; + else if (!strncmp(opt_str, "DFP_HIGH", 8)) + *output_interface = INTERFACE_DFP_HIGH; + else if (!strncmp(opt_str, "DFP_LOW", 7)) + *output_interface = INTERFACE_DFP_LOW; + else + *output_interface = INTERFACE_NONE; + return 0; +} + +static void parse_lcd_port(void) +{ + parse_port(viafb_lcd_port, &viaparinfo->chip_info->lvds_chip_info. + output_interface); + /*Initialize to avoid unexpected behavior */ + viaparinfo->chip_info->lvds_chip_info2.output_interface = + INTERFACE_NONE; + + DEBUG_MSG(KERN_INFO "parse_lcd_port: viafb_lcd_port:%s,interface:%d\n", + viafb_lcd_port, viaparinfo->chip_info->lvds_chip_info. + output_interface); +} + +static void parse_dvi_port(void) +{ + parse_port(viafb_dvi_port, &viaparinfo->chip_info->tmds_chip_info. + output_interface); + + DEBUG_MSG(KERN_INFO "parse_dvi_port: viafb_dvi_port:%s,interface:%d\n", + viafb_dvi_port, viaparinfo->chip_info->tmds_chip_info. + output_interface); +} + +#ifdef CONFIG_FB_VIA_DIRECT_PROCFS + +/* + * The proc filesystem read/write function, a simple proc implement to + * get/set the value of DPA DVP0, DVP0DataDriving, DVP0ClockDriving, DVP1, + * DVP1Driving, DFPHigh, DFPLow CR96, SR2A[5], SR1B[1], SR2A[4], SR1E[2], + * CR9B, SR65, CR97, CR99 + */ +static int viafb_dvp0_proc_show(struct seq_file *m, void *v) +{ + u8 dvp0_data_dri = 0, dvp0_clk_dri = 0, dvp0 = 0; + dvp0_data_dri = + (viafb_read_reg(VIASR, SR2A) & BIT5) >> 4 | + (viafb_read_reg(VIASR, SR1B) & BIT1) >> 1; + dvp0_clk_dri = + (viafb_read_reg(VIASR, SR2A) & BIT4) >> 3 | + (viafb_read_reg(VIASR, SR1E) & BIT2) >> 2; + dvp0 = viafb_read_reg(VIACR, CR96) & 0x0f; + seq_printf(m, "%x %x %x\n", dvp0, dvp0_data_dri, dvp0_clk_dri); + return 0; +} + +static int viafb_dvp0_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_dvp0_proc_show, NULL); +} + +static ssize_t viafb_dvp0_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + char buf[20], *value, *pbuf; + u8 reg_val = 0; + unsigned long length, i; + if (count < 1) + return -EINVAL; + length = count > 20 ? 20 : count; + if (copy_from_user(&buf[0], buffer, length)) + return -EFAULT; + buf[length - 1] = '\0'; /*Ensure end string */ + pbuf = &buf[0]; + for (i = 0; i < 3; i++) { + value = strsep(&pbuf, " "); + if (value != NULL) { + if (kstrtou8(value, 0, ®_val) < 0) + return -EINVAL; + DEBUG_MSG(KERN_INFO "DVP0:reg_val[%l]=:%x\n", i, + reg_val); + switch (i) { + case 0: + viafb_write_reg_mask(CR96, VIACR, + reg_val, 0x0f); + break; + case 1: + viafb_write_reg_mask(SR2A, VIASR, + reg_val << 4, BIT5); + viafb_write_reg_mask(SR1B, VIASR, + reg_val << 1, BIT1); + break; + case 2: + viafb_write_reg_mask(SR2A, VIASR, + reg_val << 3, BIT4); + viafb_write_reg_mask(SR1E, VIASR, + reg_val << 2, BIT2); + break; + default: + break; + } + } else { + break; + } + } + return count; +} + +static const struct file_operations viafb_dvp0_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_dvp0_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = viafb_dvp0_proc_write, +}; + +static int viafb_dvp1_proc_show(struct seq_file *m, void *v) +{ + u8 dvp1 = 0, dvp1_data_dri = 0, dvp1_clk_dri = 0; + dvp1 = viafb_read_reg(VIACR, CR9B) & 0x0f; + dvp1_data_dri = (viafb_read_reg(VIASR, SR65) & 0x0c) >> 2; + dvp1_clk_dri = viafb_read_reg(VIASR, SR65) & 0x03; + seq_printf(m, "%x %x %x\n", dvp1, dvp1_data_dri, dvp1_clk_dri); + return 0; +} + +static int viafb_dvp1_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_dvp1_proc_show, NULL); +} + +static ssize_t viafb_dvp1_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + char buf[20], *value, *pbuf; + u8 reg_val = 0; + unsigned long length, i; + if (count < 1) + return -EINVAL; + length = count > 20 ? 20 : count; + if (copy_from_user(&buf[0], buffer, length)) + return -EFAULT; + buf[length - 1] = '\0'; /*Ensure end string */ + pbuf = &buf[0]; + for (i = 0; i < 3; i++) { + value = strsep(&pbuf, " "); + if (value != NULL) { + if (kstrtou8(value, 0, ®_val) < 0) + return -EINVAL; + switch (i) { + case 0: + viafb_write_reg_mask(CR9B, VIACR, + reg_val, 0x0f); + break; + case 1: + viafb_write_reg_mask(SR65, VIASR, + reg_val << 2, 0x0c); + break; + case 2: + viafb_write_reg_mask(SR65, VIASR, + reg_val, 0x03); + break; + default: + break; + } + } else { + break; + } + } + return count; +} + +static const struct file_operations viafb_dvp1_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_dvp1_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = viafb_dvp1_proc_write, +}; + +static int viafb_dfph_proc_show(struct seq_file *m, void *v) +{ + u8 dfp_high = 0; + dfp_high = viafb_read_reg(VIACR, CR97) & 0x0f; + seq_printf(m, "%x\n", dfp_high); + return 0; +} + +static int viafb_dfph_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_dfph_proc_show, NULL); +} + +static ssize_t viafb_dfph_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + int err; + u8 reg_val; + err = kstrtou8_from_user(buffer, count, 0, ®_val); + if (err) + return err; + + viafb_write_reg_mask(CR97, VIACR, reg_val, 0x0f); + return count; +} + +static const struct file_operations viafb_dfph_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_dfph_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = viafb_dfph_proc_write, +}; + +static int viafb_dfpl_proc_show(struct seq_file *m, void *v) +{ + u8 dfp_low = 0; + dfp_low = viafb_read_reg(VIACR, CR99) & 0x0f; + seq_printf(m, "%x\n", dfp_low); + return 0; +} + +static int viafb_dfpl_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_dfpl_proc_show, NULL); +} + +static ssize_t viafb_dfpl_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + int err; + u8 reg_val; + err = kstrtou8_from_user(buffer, count, 0, ®_val); + if (err) + return err; + + viafb_write_reg_mask(CR99, VIACR, reg_val, 0x0f); + return count; +} + +static const struct file_operations viafb_dfpl_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_dfpl_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = viafb_dfpl_proc_write, +}; + +static int viafb_vt1636_proc_show(struct seq_file *m, void *v) +{ + u8 vt1636_08 = 0, vt1636_09 = 0; + switch (viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) { + case VT1636_LVDS: + vt1636_08 = + viafb_gpio_i2c_read_lvds(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info, 0x08) & 0x0f; + vt1636_09 = + viafb_gpio_i2c_read_lvds(viaparinfo->lvds_setting_info, + &viaparinfo->chip_info->lvds_chip_info, 0x09) & 0x1f; + seq_printf(m, "%x %x\n", vt1636_08, vt1636_09); + break; + default: + break; + } + switch (viaparinfo->chip_info->lvds_chip_info2.lvds_chip_name) { + case VT1636_LVDS: + vt1636_08 = + viafb_gpio_i2c_read_lvds(viaparinfo->lvds_setting_info2, + &viaparinfo->chip_info->lvds_chip_info2, 0x08) & 0x0f; + vt1636_09 = + viafb_gpio_i2c_read_lvds(viaparinfo->lvds_setting_info2, + &viaparinfo->chip_info->lvds_chip_info2, 0x09) & 0x1f; + seq_printf(m, " %x %x\n", vt1636_08, vt1636_09); + break; + default: + break; + } + return 0; +} + +static int viafb_vt1636_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_vt1636_proc_show, NULL); +} + +static ssize_t viafb_vt1636_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + char buf[30], *value, *pbuf; + struct IODATA reg_val; + unsigned long length, i; + if (count < 1) + return -EINVAL; + length = count > 30 ? 30 : count; + if (copy_from_user(&buf[0], buffer, length)) + return -EFAULT; + buf[length - 1] = '\0'; /*Ensure end string */ + pbuf = &buf[0]; + switch (viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) { + case VT1636_LVDS: + for (i = 0; i < 2; i++) { + value = strsep(&pbuf, " "); + if (value != NULL) { + if (kstrtou8(value, 0, ®_val.Data) < 0) + return -EINVAL; + switch (i) { + case 0: + reg_val.Index = 0x08; + reg_val.Mask = 0x0f; + viafb_gpio_i2c_write_mask_lvds + (viaparinfo->lvds_setting_info, + &viaparinfo-> + chip_info->lvds_chip_info, + reg_val); + break; + case 1: + reg_val.Index = 0x09; + reg_val.Mask = 0x1f; + viafb_gpio_i2c_write_mask_lvds + (viaparinfo->lvds_setting_info, + &viaparinfo-> + chip_info->lvds_chip_info, + reg_val); + break; + default: + break; + } + } else { + break; + } + } + break; + default: + break; + } + switch (viaparinfo->chip_info->lvds_chip_info2.lvds_chip_name) { + case VT1636_LVDS: + for (i = 0; i < 2; i++) { + value = strsep(&pbuf, " "); + if (value != NULL) { + if (kstrtou8(value, 0, ®_val.Data) < 0) + return -EINVAL; + switch (i) { + case 0: + reg_val.Index = 0x08; + reg_val.Mask = 0x0f; + viafb_gpio_i2c_write_mask_lvds + (viaparinfo->lvds_setting_info2, + &viaparinfo-> + chip_info->lvds_chip_info2, + reg_val); + break; + case 1: + reg_val.Index = 0x09; + reg_val.Mask = 0x1f; + viafb_gpio_i2c_write_mask_lvds + (viaparinfo->lvds_setting_info2, + &viaparinfo-> + chip_info->lvds_chip_info2, + reg_val); + break; + default: + break; + } + } else { + break; + } + } + break; + default: + break; + } + return count; +} + +static const struct file_operations viafb_vt1636_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_vt1636_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = viafb_vt1636_proc_write, +}; + +#endif /* CONFIG_FB_VIA_DIRECT_PROCFS */ + +static int viafb_sup_odev_proc_show(struct seq_file *m, void *v) +{ + via_odev_to_seq(m, supported_odev_map[ + viaparinfo->shared->chip_info.gfx_chip_name]); + return 0; +} + +static int viafb_sup_odev_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_sup_odev_proc_show, NULL); +} + +static const struct file_operations viafb_sup_odev_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_sup_odev_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static ssize_t odev_update(const char __user *buffer, size_t count, u32 *odev) +{ + char buf[64], *ptr = buf; + u32 devices; + bool add, sub; + + if (count < 1 || count > 63) + return -EINVAL; + if (copy_from_user(&buf[0], buffer, count)) + return -EFAULT; + buf[count] = '\0'; + add = buf[0] == '+'; + sub = buf[0] == '-'; + if (add || sub) + ptr++; + devices = via_parse_odev(ptr, &ptr); + if (*ptr == '\n') + ptr++; + if (*ptr != 0) + return -EINVAL; + if (add) + *odev |= devices; + else if (sub) + *odev &= ~devices; + else + *odev = devices; + return count; +} + +static int viafb_iga1_odev_proc_show(struct seq_file *m, void *v) +{ + via_odev_to_seq(m, viaparinfo->shared->iga1_devices); + return 0; +} + +static int viafb_iga1_odev_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_iga1_odev_proc_show, NULL); +} + +static ssize_t viafb_iga1_odev_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + u32 dev_on, dev_off, dev_old, dev_new; + ssize_t res; + + dev_old = dev_new = viaparinfo->shared->iga1_devices; + res = odev_update(buffer, count, &dev_new); + if (res != count) + return res; + dev_off = dev_old & ~dev_new; + dev_on = dev_new & ~dev_old; + viaparinfo->shared->iga1_devices = dev_new; + viaparinfo->shared->iga2_devices &= ~dev_new; + via_set_state(dev_off, VIA_STATE_OFF); + via_set_source(dev_new, IGA1); + via_set_state(dev_on, VIA_STATE_ON); + return res; +} + +static const struct file_operations viafb_iga1_odev_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_iga1_odev_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = viafb_iga1_odev_proc_write, +}; + +static int viafb_iga2_odev_proc_show(struct seq_file *m, void *v) +{ + via_odev_to_seq(m, viaparinfo->shared->iga2_devices); + return 0; +} + +static int viafb_iga2_odev_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, viafb_iga2_odev_proc_show, NULL); +} + +static ssize_t viafb_iga2_odev_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + u32 dev_on, dev_off, dev_old, dev_new; + ssize_t res; + + dev_old = dev_new = viaparinfo->shared->iga2_devices; + res = odev_update(buffer, count, &dev_new); + if (res != count) + return res; + dev_off = dev_old & ~dev_new; + dev_on = dev_new & ~dev_old; + viaparinfo->shared->iga2_devices = dev_new; + viaparinfo->shared->iga1_devices &= ~dev_new; + via_set_state(dev_off, VIA_STATE_OFF); + via_set_source(dev_new, IGA2); + via_set_state(dev_on, VIA_STATE_ON); + return res; +} + +static const struct file_operations viafb_iga2_odev_proc_fops = { + .owner = THIS_MODULE, + .open = viafb_iga2_odev_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = viafb_iga2_odev_proc_write, +}; + +#define IS_VT1636(lvds_chip) ((lvds_chip).lvds_chip_name == VT1636_LVDS) +static void viafb_init_proc(struct viafb_shared *shared) +{ + struct proc_dir_entry *iga1_entry, *iga2_entry, + *viafb_entry = proc_mkdir("viafb", NULL); + + shared->proc_entry = viafb_entry; + if (viafb_entry) { +#ifdef CONFIG_FB_VIA_DIRECT_PROCFS + proc_create("dvp0", 0, viafb_entry, &viafb_dvp0_proc_fops); + proc_create("dvp1", 0, viafb_entry, &viafb_dvp1_proc_fops); + proc_create("dfph", 0, viafb_entry, &viafb_dfph_proc_fops); + proc_create("dfpl", 0, viafb_entry, &viafb_dfpl_proc_fops); + if (IS_VT1636(shared->chip_info.lvds_chip_info) + || IS_VT1636(shared->chip_info.lvds_chip_info2)) + proc_create("vt1636", 0, viafb_entry, + &viafb_vt1636_proc_fops); +#endif /* CONFIG_FB_VIA_DIRECT_PROCFS */ + + proc_create("supported_output_devices", 0, viafb_entry, + &viafb_sup_odev_proc_fops); + iga1_entry = proc_mkdir("iga1", viafb_entry); + shared->iga1_proc_entry = iga1_entry; + proc_create("output_devices", 0, iga1_entry, + &viafb_iga1_odev_proc_fops); + iga2_entry = proc_mkdir("iga2", viafb_entry); + shared->iga2_proc_entry = iga2_entry; + proc_create("output_devices", 0, iga2_entry, + &viafb_iga2_odev_proc_fops); + } +} +static void viafb_remove_proc(struct viafb_shared *shared) +{ + struct proc_dir_entry *viafb_entry = shared->proc_entry, + *iga1_entry = shared->iga1_proc_entry, + *iga2_entry = shared->iga2_proc_entry; + + if (!viafb_entry) + return; + + remove_proc_entry("output_devices", iga2_entry); + remove_proc_entry("iga2", viafb_entry); + remove_proc_entry("output_devices", iga1_entry); + remove_proc_entry("iga1", viafb_entry); + remove_proc_entry("supported_output_devices", viafb_entry); + +#ifdef CONFIG_FB_VIA_DIRECT_PROCFS + remove_proc_entry("dvp0", viafb_entry);/* parent dir */ + remove_proc_entry("dvp1", viafb_entry); + remove_proc_entry("dfph", viafb_entry); + remove_proc_entry("dfpl", viafb_entry); + if (IS_VT1636(shared->chip_info.lvds_chip_info) + || IS_VT1636(shared->chip_info.lvds_chip_info2)) + remove_proc_entry("vt1636", viafb_entry); +#endif /* CONFIG_FB_VIA_DIRECT_PROCFS */ + + remove_proc_entry("viafb", NULL); +} +#undef IS_VT1636 + +static int parse_mode(const char *str, u32 devices, u32 *xres, u32 *yres) +{ + const struct fb_videomode *mode = NULL; + char *ptr; + + if (!str) { + if (devices == VIA_CRT) + mode = via_aux_get_preferred_mode( + viaparinfo->shared->i2c_26); + else if (devices == VIA_DVP1) + mode = via_aux_get_preferred_mode( + viaparinfo->shared->i2c_31); + + if (mode) { + *xres = mode->xres; + *yres = mode->yres; + } else if (machine_is_olpc()) { + *xres = 1200; + *yres = 900; + } else { + *xres = 640; + *yres = 480; + } + return 0; + } + + *xres = simple_strtoul(str, &ptr, 10); + if (ptr[0] != 'x') + return -EINVAL; + + *yres = simple_strtoul(&ptr[1], &ptr, 10); + if (ptr[0]) + return -EINVAL; + + return 0; +} + + +#ifdef CONFIG_PM +static int viafb_suspend(void *unused) +{ + console_lock(); + fb_set_suspend(viafbinfo, 1); + viafb_sync(viafbinfo); + console_unlock(); + + return 0; +} + +static int viafb_resume(void *unused) +{ + console_lock(); + if (viaparinfo->shared->vdev->engine_mmio) + viafb_reset_engine(viaparinfo); + viafb_set_par(viafbinfo); + if (viafb_dual_fb) + viafb_set_par(viafbinfo1); + fb_set_suspend(viafbinfo, 0); + + console_unlock(); + return 0; +} + +static struct viafb_pm_hooks viafb_fb_pm_hooks = { + .suspend = viafb_suspend, + .resume = viafb_resume +}; + +#endif + +static void i2c_bus_probe(struct viafb_shared *shared) +{ + /* should be always CRT */ + printk(KERN_INFO "viafb: Probing I2C bus 0x26\n"); + shared->i2c_26 = via_aux_probe(viafb_find_i2c_adapter(VIA_PORT_26)); + + /* seems to be usually DVP1 */ + printk(KERN_INFO "viafb: Probing I2C bus 0x31\n"); + shared->i2c_31 = via_aux_probe(viafb_find_i2c_adapter(VIA_PORT_31)); + + /* FIXME: what is this? */ + if (!machine_is_olpc()) { + printk(KERN_INFO "viafb: Probing I2C bus 0x2C\n"); + shared->i2c_2C = via_aux_probe(viafb_find_i2c_adapter(VIA_PORT_2C)); + } + + printk(KERN_INFO "viafb: Finished I2C bus probing"); +} + +static void i2c_bus_free(struct viafb_shared *shared) +{ + via_aux_free(shared->i2c_26); + via_aux_free(shared->i2c_31); + via_aux_free(shared->i2c_2C); +} + +int via_fb_pci_probe(struct viafb_dev *vdev) +{ + u32 default_xres, default_yres; + struct fb_var_screeninfo default_var; + int rc; + u32 viafb_par_length; + + DEBUG_MSG(KERN_INFO "VIAFB PCI Probe!!\n"); + memset(&default_var, 0, sizeof(default_var)); + viafb_par_length = ALIGN(sizeof(struct viafb_par), BITS_PER_LONG/8); + + /* Allocate fb_info and ***_par here, also including some other needed + * variables + */ + viafbinfo = framebuffer_alloc(viafb_par_length + + ALIGN(sizeof(struct viafb_shared), BITS_PER_LONG/8), + &vdev->pdev->dev); + if (!viafbinfo) { + printk(KERN_ERR"Could not allocate memory for viafb_info.\n"); + return -ENOMEM; + } + + viaparinfo = (struct viafb_par *)viafbinfo->par; + viaparinfo->shared = viafbinfo->par + viafb_par_length; + viaparinfo->shared->vdev = vdev; + viaparinfo->vram_addr = 0; + viaparinfo->tmds_setting_info = &viaparinfo->shared->tmds_setting_info; + viaparinfo->lvds_setting_info = &viaparinfo->shared->lvds_setting_info; + viaparinfo->lvds_setting_info2 = + &viaparinfo->shared->lvds_setting_info2; + viaparinfo->chip_info = &viaparinfo->shared->chip_info; + + i2c_bus_probe(viaparinfo->shared); + if (viafb_dual_fb) + viafb_SAMM_ON = 1; + parse_lcd_port(); + parse_dvi_port(); + + viafb_init_chip_info(vdev->chip_type); + /* + * The framebuffer will have been successfully mapped by + * the core (or we'd not be here), but we still need to + * set up our own accounting. + */ + viaparinfo->fbmem = vdev->fbmem_start; + viaparinfo->memsize = vdev->fbmem_len; + viaparinfo->fbmem_free = viaparinfo->memsize; + viaparinfo->fbmem_used = 0; + viafbinfo->screen_base = vdev->fbmem; + + viafbinfo->fix.mmio_start = vdev->engine_start; + viafbinfo->fix.mmio_len = vdev->engine_len; + viafbinfo->node = 0; + viafbinfo->fbops = &viafb_ops; + viafbinfo->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + viafbinfo->pseudo_palette = pseudo_pal; + if (viafb_accel && !viafb_setup_engine(viafbinfo)) { + viafbinfo->flags |= FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_IMAGEBLIT; + default_var.accel_flags = FB_ACCELF_TEXT; + } else { + viafbinfo->flags |= FBINFO_HWACCEL_DISABLED; + default_var.accel_flags = 0; + } + + if (viafb_second_size && (viafb_second_size < 8)) { + viafb_second_offset = viaparinfo->fbmem_free - + viafb_second_size * 1024 * 1024; + } else { + viafb_second_size = 8; + viafb_second_offset = viaparinfo->fbmem_free - + viafb_second_size * 1024 * 1024; + } + + parse_mode(viafb_mode, viaparinfo->shared->iga1_devices, + &default_xres, &default_yres); + if (viafb_SAMM_ON == 1) + parse_mode(viafb_mode1, viaparinfo->shared->iga2_devices, + &viafb_second_xres, &viafb_second_yres); + + default_var.xres = default_xres; + default_var.yres = default_yres; + default_var.xres_virtual = default_xres; + default_var.yres_virtual = default_yres; + default_var.bits_per_pixel = viafb_bpp; + viafb_fill_var_timing_info(&default_var, viafb_get_best_mode( + default_var.xres, default_var.yres, viafb_refresh)); + viafb_setup_fixinfo(&viafbinfo->fix, viaparinfo); + viafbinfo->var = default_var; + + if (viafb_dual_fb) { + viafbinfo1 = framebuffer_alloc(viafb_par_length, + &vdev->pdev->dev); + if (!viafbinfo1) { + printk(KERN_ERR + "allocate the second framebuffer struct error\n"); + rc = -ENOMEM; + goto out_fb_release; + } + viaparinfo1 = viafbinfo1->par; + memcpy(viaparinfo1, viaparinfo, viafb_par_length); + viaparinfo1->vram_addr = viafb_second_offset; + viaparinfo1->memsize = viaparinfo->memsize - + viafb_second_offset; + viaparinfo->memsize = viafb_second_offset; + viaparinfo1->fbmem = viaparinfo->fbmem + viafb_second_offset; + + viaparinfo1->fbmem_used = viaparinfo->fbmem_used; + viaparinfo1->fbmem_free = viaparinfo1->memsize - + viaparinfo1->fbmem_used; + viaparinfo->fbmem_free = viaparinfo->memsize; + viaparinfo->fbmem_used = 0; + + viaparinfo->iga_path = IGA1; + viaparinfo1->iga_path = IGA2; + memcpy(viafbinfo1, viafbinfo, sizeof(struct fb_info)); + viafbinfo1->par = viaparinfo1; + viafbinfo1->screen_base = viafbinfo->screen_base + + viafb_second_offset; + + default_var.xres = viafb_second_xres; + default_var.yres = viafb_second_yres; + default_var.xres_virtual = viafb_second_xres; + default_var.yres_virtual = viafb_second_yres; + default_var.bits_per_pixel = viafb_bpp1; + viafb_fill_var_timing_info(&default_var, viafb_get_best_mode( + default_var.xres, default_var.yres, viafb_refresh1)); + + viafb_setup_fixinfo(&viafbinfo1->fix, viaparinfo1); + viafb_check_var(&default_var, viafbinfo1); + viafbinfo1->var = default_var; + viafb_update_fix(viafbinfo1); + viaparinfo1->depth = fb_get_color_depth(&viafbinfo1->var, + &viafbinfo1->fix); + } + + viafb_check_var(&viafbinfo->var, viafbinfo); + viafb_update_fix(viafbinfo); + viaparinfo->depth = fb_get_color_depth(&viafbinfo->var, + &viafbinfo->fix); + default_var.activate = FB_ACTIVATE_NOW; + rc = fb_alloc_cmap(&viafbinfo->cmap, 256, 0); + if (rc) + goto out_fb1_release; + + if (viafb_dual_fb && (viafb_primary_dev == LCD_Device) + && (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266)) { + rc = register_framebuffer(viafbinfo1); + if (rc) + goto out_dealloc_cmap; + } + rc = register_framebuffer(viafbinfo); + if (rc) + goto out_fb1_unreg_lcd_cle266; + + if (viafb_dual_fb && ((viafb_primary_dev != LCD_Device) + || (viaparinfo->chip_info->gfx_chip_name != + UNICHROME_CLE266))) { + rc = register_framebuffer(viafbinfo1); + if (rc) + goto out_fb_unreg; + } + DEBUG_MSG(KERN_INFO "fb%d: %s frame buffer device %dx%d-%dbpp\n", + viafbinfo->node, viafbinfo->fix.id, default_var.xres, + default_var.yres, default_var.bits_per_pixel); + + viafb_init_proc(viaparinfo->shared); + viafb_init_dac(IGA2); + +#ifdef CONFIG_PM + viafb_pm_register(&viafb_fb_pm_hooks); +#endif + return 0; + +out_fb_unreg: + unregister_framebuffer(viafbinfo); +out_fb1_unreg_lcd_cle266: + if (viafb_dual_fb && (viafb_primary_dev == LCD_Device) + && (viaparinfo->chip_info->gfx_chip_name == UNICHROME_CLE266)) + unregister_framebuffer(viafbinfo1); +out_dealloc_cmap: + fb_dealloc_cmap(&viafbinfo->cmap); +out_fb1_release: + if (viafbinfo1) + framebuffer_release(viafbinfo1); +out_fb_release: + i2c_bus_free(viaparinfo->shared); + framebuffer_release(viafbinfo); + return rc; +} + +void via_fb_pci_remove(struct pci_dev *pdev) +{ + DEBUG_MSG(KERN_INFO "via_pci_remove!\n"); + fb_dealloc_cmap(&viafbinfo->cmap); + unregister_framebuffer(viafbinfo); + if (viafb_dual_fb) + unregister_framebuffer(viafbinfo1); + viafb_remove_proc(viaparinfo->shared); + i2c_bus_free(viaparinfo->shared); + framebuffer_release(viafbinfo); + if (viafb_dual_fb) + framebuffer_release(viafbinfo1); +} + +#ifndef MODULE +static int __init viafb_setup(void) +{ + char *this_opt; + char *options; + + DEBUG_MSG(KERN_INFO "viafb_setup!\n"); + + if (fb_get_options("viafb", &options)) + return -ENODEV; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + + if (!strncmp(this_opt, "viafb_mode1=", 12)) { + viafb_mode1 = kstrdup(this_opt + 12, GFP_KERNEL); + } else if (!strncmp(this_opt, "viafb_mode=", 11)) { + viafb_mode = kstrdup(this_opt + 11, GFP_KERNEL); + } else if (!strncmp(this_opt, "viafb_bpp1=", 11)) { + if (kstrtouint(this_opt + 11, 0, &viafb_bpp1) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_bpp=", 10)) { + if (kstrtouint(this_opt + 10, 0, &viafb_bpp) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_refresh1=", 15)) { + if (kstrtoint(this_opt + 15, 0, &viafb_refresh1) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_refresh=", 14)) { + if (kstrtoint(this_opt + 14, 0, &viafb_refresh) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_lcd_dsp_method=", 21)) { + if (kstrtoint(this_opt + 21, 0, + &viafb_lcd_dsp_method) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_lcd_panel_id=", 19)) { + if (kstrtoint(this_opt + 19, 0, + &viafb_lcd_panel_id) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_accel=", 12)) { + if (kstrtoint(this_opt + 12, 0, &viafb_accel) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_SAMM_ON=", 14)) { + if (kstrtoint(this_opt + 14, 0, &viafb_SAMM_ON) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_active_dev=", 17)) { + viafb_active_dev = kstrdup(this_opt + 17, GFP_KERNEL); + } else if (!strncmp(this_opt, + "viafb_display_hardware_layout=", 30)) { + if (kstrtoint(this_opt + 30, 0, + &viafb_display_hardware_layout) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_second_size=", 18)) { + if (kstrtoint(this_opt + 18, 0, &viafb_second_size) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, + "viafb_platform_epia_dvi=", 24)) { + if (kstrtoint(this_opt + 24, 0, + &viafb_platform_epia_dvi) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, + "viafb_device_lcd_dualedge=", 26)) { + if (kstrtoint(this_opt + 26, 0, + &viafb_device_lcd_dualedge) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_bus_width=", 16)) { + if (kstrtoint(this_opt + 16, 0, &viafb_bus_width) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_lcd_mode=", 15)) { + if (kstrtoint(this_opt + 15, 0, &viafb_lcd_mode) < 0) + return -EINVAL; + } else if (!strncmp(this_opt, "viafb_lcd_port=", 15)) { + viafb_lcd_port = kstrdup(this_opt + 15, GFP_KERNEL); + } else if (!strncmp(this_opt, "viafb_dvi_port=", 15)) { + viafb_dvi_port = kstrdup(this_opt + 15, GFP_KERNEL); + } + } + return 0; +} +#endif + +/* + * These are called out of via-core for now. + */ +int __init viafb_init(void) +{ + u32 dummy_x, dummy_y; + int r = 0; + + if (machine_is_olpc()) + /* Apply XO-1.5-specific configuration. */ + viafb_lcd_panel_id = 23; + +#ifndef MODULE + r = viafb_setup(); + if (r < 0) + return r; +#endif + if (parse_mode(viafb_mode, 0, &dummy_x, &dummy_y) + || !viafb_get_best_mode(dummy_x, dummy_y, viafb_refresh) + || parse_mode(viafb_mode1, 0, &dummy_x, &dummy_y) + || !viafb_get_best_mode(dummy_x, dummy_y, viafb_refresh1) + || viafb_bpp < 0 || viafb_bpp > 32 + || viafb_bpp1 < 0 || viafb_bpp1 > 32 + || parse_active_dev()) + return -EINVAL; + + printk(KERN_INFO + "VIA Graphics Integration Chipset framebuffer %d.%d initializing\n", + VERSION_MAJOR, VERSION_MINOR); + return r; +} + +void __exit viafb_exit(void) +{ + DEBUG_MSG(KERN_INFO "viafb_exit!\n"); +} + +static struct fb_ops viafb_ops = { + .owner = THIS_MODULE, + .fb_open = viafb_open, + .fb_release = viafb_release, + .fb_check_var = viafb_check_var, + .fb_set_par = viafb_set_par, + .fb_setcolreg = viafb_setcolreg, + .fb_pan_display = viafb_pan_display, + .fb_blank = viafb_blank, + .fb_fillrect = viafb_fillrect, + .fb_copyarea = viafb_copyarea, + .fb_imageblit = viafb_imageblit, + .fb_cursor = viafb_cursor, + .fb_ioctl = viafb_ioctl, + .fb_sync = viafb_sync, +}; + + +#ifdef MODULE +module_param(viafb_mode, charp, S_IRUSR); +MODULE_PARM_DESC(viafb_mode, "Set resolution (default=640x480)"); + +module_param(viafb_mode1, charp, S_IRUSR); +MODULE_PARM_DESC(viafb_mode1, "Set resolution (default=640x480)"); + +module_param(viafb_bpp, int, S_IRUSR); +MODULE_PARM_DESC(viafb_bpp, "Set color depth (default=32bpp)"); + +module_param(viafb_bpp1, int, S_IRUSR); +MODULE_PARM_DESC(viafb_bpp1, "Set color depth (default=32bpp)"); + +module_param(viafb_refresh, int, S_IRUSR); +MODULE_PARM_DESC(viafb_refresh, + "Set CRT viafb_refresh rate (default = 60)"); + +module_param(viafb_refresh1, int, S_IRUSR); +MODULE_PARM_DESC(viafb_refresh1, + "Set CRT refresh rate (default = 60)"); + +module_param(viafb_lcd_panel_id, int, S_IRUSR); +MODULE_PARM_DESC(viafb_lcd_panel_id, + "Set Flat Panel type(Default=1024x768)"); + +module_param(viafb_lcd_dsp_method, int, S_IRUSR); +MODULE_PARM_DESC(viafb_lcd_dsp_method, + "Set Flat Panel display scaling method.(Default=Expandsion)"); + +module_param(viafb_SAMM_ON, int, S_IRUSR); +MODULE_PARM_DESC(viafb_SAMM_ON, + "Turn on/off flag of SAMM(Default=OFF)"); + +module_param(viafb_accel, int, S_IRUSR); +MODULE_PARM_DESC(viafb_accel, + "Set 2D Hardware Acceleration: 0 = OFF, 1 = ON (default)"); + +module_param(viafb_active_dev, charp, S_IRUSR); +MODULE_PARM_DESC(viafb_active_dev, "Specify active devices."); + +module_param(viafb_display_hardware_layout, int, S_IRUSR); +MODULE_PARM_DESC(viafb_display_hardware_layout, + "Display Hardware Layout (LCD Only, DVI Only...,etc)"); + +module_param(viafb_second_size, int, S_IRUSR); +MODULE_PARM_DESC(viafb_second_size, + "Set secondary device memory size"); + +module_param(viafb_dual_fb, int, S_IRUSR); +MODULE_PARM_DESC(viafb_dual_fb, + "Turn on/off flag of dual framebuffer devices.(Default = OFF)"); + +module_param(viafb_platform_epia_dvi, int, S_IRUSR); +MODULE_PARM_DESC(viafb_platform_epia_dvi, + "Turn on/off flag of DVI devices on EPIA board.(Default = OFF)"); + +module_param(viafb_device_lcd_dualedge, int, S_IRUSR); +MODULE_PARM_DESC(viafb_device_lcd_dualedge, + "Turn on/off flag of dual edge panel.(Default = OFF)"); + +module_param(viafb_bus_width, int, S_IRUSR); +MODULE_PARM_DESC(viafb_bus_width, + "Set bus width of panel.(Default = 12)"); + +module_param(viafb_lcd_mode, int, S_IRUSR); +MODULE_PARM_DESC(viafb_lcd_mode, + "Set Flat Panel mode(Default=OPENLDI)"); + +module_param(viafb_lcd_port, charp, S_IRUSR); +MODULE_PARM_DESC(viafb_lcd_port, "Specify LCD output port."); + +module_param(viafb_dvi_port, charp, S_IRUSR); +MODULE_PARM_DESC(viafb_dvi_port, "Specify DVI output port."); + +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/video/fbdev/via/viafbdev.h b/drivers/video/fbdev/via/viafbdev.h new file mode 100644 index 000000000000..f6b2ddf56e94 --- /dev/null +++ b/drivers/video/fbdev/via/viafbdev.h @@ -0,0 +1,110 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __VIAFBDEV_H__ +#define __VIAFBDEV_H__ + +#include <linux/proc_fs.h> +#include <linux/fb.h> +#include <linux/spinlock.h> + +#include "via_aux.h" +#include "ioctl.h" +#include "share.h" +#include "chip.h" +#include "hw.h" + +#define VERSION_MAJOR 2 +#define VERSION_KERNEL 6 /* For kernel 2.6 */ + +#define VERSION_OS 0 /* 0: for 32 bits OS, 1: for 64 bits OS */ +#define VERSION_MINOR 4 + +#define VIAFB_NUM_I2C 5 + +struct viafb_shared { + u32 iga1_devices; + u32 iga2_devices; + + struct proc_dir_entry *proc_entry; /*viafb proc entry */ + struct proc_dir_entry *iga1_proc_entry; + struct proc_dir_entry *iga2_proc_entry; + struct viafb_dev *vdev; /* Global dev info */ + + /* I2C busses that may have auxiliary devices */ + struct via_aux_bus *i2c_26; + struct via_aux_bus *i2c_31; + struct via_aux_bus *i2c_2C; + + /* All the information will be needed to set engine */ + struct tmds_setting_information tmds_setting_info; + struct lvds_setting_information lvds_setting_info; + struct lvds_setting_information lvds_setting_info2; + struct chip_information chip_info; + + /* hardware acceleration stuff */ + u32 cursor_vram_addr; + u32 vq_vram_addr; /* virtual queue address in video ram */ + int (*hw_bitblt)(void __iomem *engine, u8 op, u32 width, u32 height, + u8 dst_bpp, u32 dst_addr, u32 dst_pitch, u32 dst_x, u32 dst_y, + u32 *src_mem, u32 src_addr, u32 src_pitch, u32 src_x, u32 src_y, + u32 fg_color, u32 bg_color, u8 fill_rop); +}; + +struct viafb_par { + u8 depth; + u32 vram_addr; + + unsigned int fbmem; /*framebuffer physical memory address */ + unsigned int memsize; /*size of fbmem */ + u32 fbmem_free; /* Free FB memory */ + u32 fbmem_used; /* Use FB memory size */ + u32 iga_path; + + struct viafb_shared *shared; + + /* All the information will be needed to set engine */ + /* depreciated, use the ones in shared directly */ + struct tmds_setting_information *tmds_setting_info; + struct lvds_setting_information *lvds_setting_info; + struct lvds_setting_information *lvds_setting_info2; + struct chip_information *chip_info; +}; + +extern int viafb_SAMM_ON; +extern int viafb_dual_fb; +extern int viafb_LCD2_ON; +extern int viafb_LCD_ON; +extern int viafb_DVI_ON; +extern int viafb_hotplug; + +u8 viafb_gpio_i2c_read_lvds(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information + *plvds_chip_info, u8 index); +void viafb_gpio_i2c_write_mask_lvds(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information + *plvds_chip_info, struct IODATA io_data); +int via_fb_pci_probe(struct viafb_dev *vdev); +void via_fb_pci_remove(struct pci_dev *pdev); +/* Temporary */ +int viafb_init(void); +void viafb_exit(void); +#endif /* __VIAFBDEV_H__ */ diff --git a/drivers/video/fbdev/via/viamode.c b/drivers/video/fbdev/via/viamode.c new file mode 100644 index 000000000000..0666ab01cf4a --- /dev/null +++ b/drivers/video/fbdev/via/viamode.c @@ -0,0 +1,383 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/via-core.h> +#include "global.h" + +struct io_reg CN400_ModeXregs[] = { {VIASR, SR10, 0xFF, 0x01}, +{VIASR, SR15, 0x02, 0x02}, +{VIASR, SR16, 0xBF, 0x08}, +{VIASR, SR17, 0xFF, 0x1F}, +{VIASR, SR18, 0xFF, 0x4E}, +{VIASR, SR1A, 0xFB, 0x08}, +{VIASR, SR1E, 0x0F, 0x01}, +{VIASR, SR2A, 0xFF, 0x00}, +{VIACR, CR32, 0xFF, 0x00}, +{VIACR, CR33, 0xFF, 0x00}, +{VIACR, CR35, 0xFF, 0x00}, +{VIACR, CR36, 0x08, 0x00}, +{VIACR, CR69, 0xFF, 0x00}, +{VIACR, CR6A, 0xFF, 0x40}, +{VIACR, CR6B, 0xFF, 0x00}, +{VIACR, CR88, 0xFF, 0x40}, /* LCD Panel Type */ +{VIACR, CR89, 0xFF, 0x00}, /* LCD Timing Control 0 */ +{VIACR, CR8A, 0xFF, 0x88}, /* LCD Timing Control 1 */ +{VIACR, CR8B, 0xFF, 0x69}, /* LCD Power Sequence Control 0 */ +{VIACR, CR8C, 0xFF, 0x57}, /* LCD Power Sequence Control 1 */ +{VIACR, CR8D, 0xFF, 0x00}, /* LCD Power Sequence Control 2 */ +{VIACR, CR8E, 0xFF, 0x7B}, /* LCD Power Sequence Control 3 */ +{VIACR, CR8F, 0xFF, 0x03}, /* LCD Power Sequence Control 4 */ +{VIACR, CR90, 0xFF, 0x30}, /* LCD Power Sequence Control 5 */ +{VIACR, CR91, 0xFF, 0xA0}, /* 24/12 bit LVDS Data off */ +{VIACR, CR96, 0xFF, 0x00}, +{VIACR, CR97, 0xFF, 0x00}, +{VIACR, CR99, 0xFF, 0x00}, +{VIACR, CR9B, 0xFF, 0x00} +}; + +/* Video Mode Table for VT3314 chipset*/ +/* Common Setting for Video Mode */ +struct io_reg CN700_ModeXregs[] = { {VIASR, SR10, 0xFF, 0x01}, +{VIASR, SR15, 0x02, 0x02}, +{VIASR, SR16, 0xBF, 0x08}, +{VIASR, SR17, 0xFF, 0x1F}, +{VIASR, SR18, 0xFF, 0x4E}, +{VIASR, SR1A, 0xFB, 0x82}, +{VIASR, SR1B, 0xFF, 0xF0}, +{VIASR, SR1F, 0xFF, 0x00}, +{VIASR, SR1E, 0xFF, 0x01}, +{VIASR, SR22, 0xFF, 0x1F}, +{VIASR, SR2A, 0x0F, 0x00}, +{VIASR, SR2E, 0xFF, 0xFF}, +{VIASR, SR3F, 0xFF, 0xFF}, +{VIASR, SR40, 0xF7, 0x00}, +{VIASR, CR30, 0xFF, 0x04}, +{VIACR, CR32, 0xFF, 0x00}, +{VIACR, CR33, 0x7F, 0x00}, +{VIACR, CR35, 0xFF, 0x00}, +{VIACR, CR36, 0xFF, 0x31}, +{VIACR, CR41, 0xFF, 0x80}, +{VIACR, CR42, 0xFF, 0x00}, +{VIACR, CR55, 0x80, 0x00}, +{VIACR, CR5D, 0x80, 0x00}, /*Horizontal Retrace Start bit[11] should be 0*/ +{VIACR, CR68, 0xFF, 0x67}, /* Default FIFO For IGA2 */ +{VIACR, CR69, 0xFF, 0x00}, +{VIACR, CR6A, 0xFD, 0x40}, +{VIACR, CR6B, 0xFF, 0x00}, +{VIACR, CR77, 0xFF, 0x00}, /* LCD scaling Factor */ +{VIACR, CR78, 0xFF, 0x00}, /* LCD scaling Factor */ +{VIACR, CR79, 0xFF, 0x00}, /* LCD scaling Factor */ +{VIACR, CR9F, 0x03, 0x00}, /* LCD scaling Factor */ +{VIACR, CR88, 0xFF, 0x40}, /* LCD Panel Type */ +{VIACR, CR89, 0xFF, 0x00}, /* LCD Timing Control 0 */ +{VIACR, CR8A, 0xFF, 0x88}, /* LCD Timing Control 1 */ +{VIACR, CR8B, 0xFF, 0x5D}, /* LCD Power Sequence Control 0 */ +{VIACR, CR8C, 0xFF, 0x2B}, /* LCD Power Sequence Control 1 */ +{VIACR, CR8D, 0xFF, 0x6F}, /* LCD Power Sequence Control 2 */ +{VIACR, CR8E, 0xFF, 0x2B}, /* LCD Power Sequence Control 3 */ +{VIACR, CR8F, 0xFF, 0x01}, /* LCD Power Sequence Control 4 */ +{VIACR, CR90, 0xFF, 0x01}, /* LCD Power Sequence Control 5 */ +{VIACR, CR91, 0xFF, 0xA0}, /* 24/12 bit LVDS Data off */ +{VIACR, CR96, 0xFF, 0x00}, +{VIACR, CR97, 0xFF, 0x00}, +{VIACR, CR99, 0xFF, 0x00}, +{VIACR, CR9B, 0xFF, 0x00}, +{VIACR, CR9D, 0xFF, 0x80}, +{VIACR, CR9E, 0xFF, 0x80} +}; + +struct io_reg KM400_ModeXregs[] = { + {VIASR, SR10, 0xFF, 0x01}, /* Unlock Register */ + {VIASR, SR16, 0xFF, 0x08}, /* Display FIFO threshold Control */ + {VIASR, SR17, 0xFF, 0x1F}, /* Display FIFO Control */ + {VIASR, SR18, 0xFF, 0x4E}, /* GFX PREQ threshold */ + {VIASR, SR1A, 0xFF, 0x0a}, /* GFX PREQ threshold */ + {VIASR, SR1F, 0xFF, 0x00}, /* Memory Control 0 */ + {VIASR, SR1B, 0xFF, 0xF0}, /* Power Management Control 0 */ + {VIASR, SR1E, 0xFF, 0x01}, /* Power Management Control */ + {VIASR, SR20, 0xFF, 0x00}, /* Sequencer Arbiter Control 0 */ + {VIASR, SR21, 0xFF, 0x00}, /* Sequencer Arbiter Control 1 */ + {VIASR, SR22, 0xFF, 0x1F}, /* Display Arbiter Control 1 */ + {VIASR, SR2A, 0xFF, 0x00}, /* Power Management Control 5 */ + {VIASR, SR2D, 0xFF, 0xFF}, /* Power Management Control 1 */ + {VIASR, SR2E, 0xFF, 0xFF}, /* Power Management Control 2 */ + {VIACR, CR33, 0xFF, 0x00}, + {VIACR, CR55, 0x80, 0x00}, + {VIACR, CR5D, 0x80, 0x00}, + {VIACR, CR36, 0xFF, 0x01}, /* Power Mangement 3 */ + {VIACR, CR68, 0xFF, 0x67}, /* Default FIFO For IGA2 */ + {VIACR, CR6A, 0x20, 0x20}, /* Extended FIFO On */ + {VIACR, CR88, 0xFF, 0x40}, /* LCD Panel Type */ + {VIACR, CR89, 0xFF, 0x00}, /* LCD Timing Control 0 */ + {VIACR, CR8A, 0xFF, 0x88}, /* LCD Timing Control 1 */ + {VIACR, CR8B, 0xFF, 0x2D}, /* LCD Power Sequence Control 0 */ + {VIACR, CR8C, 0xFF, 0x2D}, /* LCD Power Sequence Control 1 */ + {VIACR, CR8D, 0xFF, 0xC8}, /* LCD Power Sequence Control 2 */ + {VIACR, CR8E, 0xFF, 0x36}, /* LCD Power Sequence Control 3 */ + {VIACR, CR8F, 0xFF, 0x00}, /* LCD Power Sequence Control 4 */ + {VIACR, CR90, 0xFF, 0x10}, /* LCD Power Sequence Control 5 */ + {VIACR, CR91, 0xFF, 0xA0}, /* 24/12 bit LVDS Data off */ + {VIACR, CR96, 0xFF, 0x03}, /* DVP0 ; DVP0 Clock Skew */ + {VIACR, CR97, 0xFF, 0x03}, /* DFP high ; DFPH Clock Skew */ + {VIACR, CR99, 0xFF, 0x03}, /* DFP low ; DFPL Clock Skew*/ + {VIACR, CR9B, 0xFF, 0x07} /* DVI on DVP1 ; DVP1 Clock Skew*/ +}; + +/* For VT3324: Common Setting for Video Mode */ +struct io_reg CX700_ModeXregs[] = { {VIASR, SR10, 0xFF, 0x01}, +{VIASR, SR15, 0x02, 0x02}, +{VIASR, SR16, 0xBF, 0x08}, +{VIASR, SR17, 0xFF, 0x1F}, +{VIASR, SR18, 0xFF, 0x4E}, +{VIASR, SR1A, 0xFB, 0x08}, +{VIASR, SR1B, 0xFF, 0xF0}, +{VIASR, SR1E, 0xFF, 0x01}, +{VIASR, SR2A, 0xFF, 0x00}, +{VIASR, SR2D, 0xC0, 0xC0}, /* delayed E3_ECK */ +{VIACR, CR32, 0xFF, 0x00}, +{VIACR, CR33, 0xFF, 0x00}, +{VIACR, CR35, 0xFF, 0x00}, +{VIACR, CR36, 0x08, 0x00}, +{VIACR, CR47, 0xC8, 0x00}, /* Clear VCK Plus. */ +{VIACR, CR69, 0xFF, 0x00}, +{VIACR, CR6A, 0xFF, 0x40}, +{VIACR, CR6B, 0xFF, 0x00}, +{VIACR, CR88, 0xFF, 0x40}, /* LCD Panel Type */ +{VIACR, CR89, 0xFF, 0x00}, /* LCD Timing Control 0 */ +{VIACR, CR8A, 0xFF, 0x88}, /* LCD Timing Control 1 */ +{VIACR, CRD4, 0xFF, 0x81}, /* Second power sequence control */ +{VIACR, CR8B, 0xFF, 0x5D}, /* LCD Power Sequence Control 0 */ +{VIACR, CR8C, 0xFF, 0x2B}, /* LCD Power Sequence Control 1 */ +{VIACR, CR8D, 0xFF, 0x6F}, /* LCD Power Sequence Control 2 */ +{VIACR, CR8E, 0xFF, 0x2B}, /* LCD Power Sequence Control 3 */ +{VIACR, CR8F, 0xFF, 0x01}, /* LCD Power Sequence Control 4 */ +{VIACR, CR90, 0xFF, 0x01}, /* LCD Power Sequence Control 5 */ +{VIACR, CR91, 0xFF, 0x80}, /* 24/12 bit LVDS Data off */ +{VIACR, CR96, 0xFF, 0x00}, +{VIACR, CR97, 0xFF, 0x00}, +{VIACR, CR99, 0xFF, 0x00}, +{VIACR, CR9B, 0xFF, 0x00} +}; + +struct io_reg VX855_ModeXregs[] = { +{VIASR, SR10, 0xFF, 0x01}, +{VIASR, SR15, 0x02, 0x02}, +{VIASR, SR16, 0xBF, 0x08}, +{VIASR, SR17, 0xFF, 0x1F}, +{VIASR, SR18, 0xFF, 0x4E}, +{VIASR, SR1A, 0xFB, 0x08}, +{VIASR, SR1B, 0xFF, 0xF0}, +{VIASR, SR1E, 0x07, 0x01}, +{VIASR, SR2A, 0xF0, 0x00}, +{VIASR, SR58, 0xFF, 0x00}, +{VIASR, SR59, 0xFF, 0x00}, +{VIASR, SR2D, 0xC0, 0xC0}, /* delayed E3_ECK */ +{VIACR, CR32, 0xFF, 0x00}, +{VIACR, CR33, 0x7F, 0x00}, +{VIACR, CR35, 0xFF, 0x00}, +{VIACR, CR36, 0x08, 0x00}, +{VIACR, CR69, 0xFF, 0x00}, +{VIACR, CR6A, 0xFD, 0x60}, +{VIACR, CR6B, 0xFF, 0x00}, +{VIACR, CR88, 0xFF, 0x40}, /* LCD Panel Type */ +{VIACR, CR89, 0xFF, 0x00}, /* LCD Timing Control 0 */ +{VIACR, CR8A, 0xFF, 0x88}, /* LCD Timing Control 1 */ +{VIACR, CRD4, 0xFF, 0x81}, /* Second power sequence control */ +{VIACR, CR91, 0xFF, 0x80}, /* 24/12 bit LVDS Data off */ +{VIACR, CR96, 0xFF, 0x00}, +{VIACR, CR97, 0xFF, 0x00}, +{VIACR, CR99, 0xFF, 0x00}, +{VIACR, CR9B, 0xFF, 0x00}, +{VIACR, CRD2, 0xFF, 0xFF} /* TMDS/LVDS control register. */ +}; + +/* Video Mode Table */ +/* Common Setting for Video Mode */ +struct io_reg CLE266_ModeXregs[] = { {VIASR, SR1E, 0xF0, 0x00}, +{VIASR, SR2A, 0x0F, 0x00}, +{VIASR, SR15, 0x02, 0x02}, +{VIASR, SR16, 0xBF, 0x08}, +{VIASR, SR17, 0xFF, 0x1F}, +{VIASR, SR18, 0xFF, 0x4E}, +{VIASR, SR1A, 0xFB, 0x08}, + +{VIACR, CR32, 0xFF, 0x00}, +{VIACR, CR35, 0xFF, 0x00}, +{VIACR, CR36, 0x08, 0x00}, +{VIACR, CR6A, 0xFF, 0x80}, +{VIACR, CR6A, 0xFF, 0xC0}, + +{VIACR, CR55, 0x80, 0x00}, +{VIACR, CR5D, 0x80, 0x00}, + +{VIAGR, GR20, 0xFF, 0x00}, +{VIAGR, GR21, 0xFF, 0x00}, +{VIAGR, GR22, 0xFF, 0x00}, + +}; + +/* Mode:1024X768 */ +struct io_reg PM1024x768[] = { {VIASR, 0x16, 0xBF, 0x0C}, +{VIASR, 0x18, 0xFF, 0x4C} +}; + +struct patch_table res_patch_table[] = { + {ARRAY_SIZE(PM1024x768), PM1024x768} +}; + +/* struct VPITTable { + unsigned char Misc; + unsigned char SR[StdSR]; + unsigned char CR[StdCR]; + unsigned char GR[StdGR]; + unsigned char AR[StdAR]; + };*/ + +struct VPITTable VPIT = { + /* Msic */ + 0xC7, + /* Sequencer */ + {0x01, 0x0F, 0x00, 0x0E}, + /* Graphic Controller */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0xFF}, + /* Attribute Controller */ + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x01, 0x00, 0x0F, 0x00} +}; + +/********************/ +/* Mode Table */ +/********************/ + +static const struct fb_videomode viafb_modes[] = { + {NULL, 60, 480, 640, 40285, 72, 24, 19, 1, 48, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2, 0, 0, 0}, + {NULL, 75, 640, 480, 31746, 120, 16, 16, 1, 64, 3, 0, 0, 0}, + {NULL, 85, 640, 480, 27780, 80, 56, 25, 1, 56, 3, 0, 0, 0}, + {NULL, 100, 640, 480, 23167, 104, 40, 25, 1, 64, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 120, 640, 480, 19081, 104, 40, 31, 1, 64, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 720, 480, 37426, 88, 16, 13, 1, 72, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 720, 576, 30611, 96, 24, 17, 1, 72, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 800, 600, 25131, 88, 40, 23, 1, 128, 4, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 75, 800, 600, 20202, 160, 16, 21, 1, 80, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 85, 800, 600, 17790, 152, 32, 27, 1, 64, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 100, 800, 600, 14667, 136, 48, 32, 1, 88, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 120, 800, 600, 11911, 144, 56, 39, 1, 88, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 800, 480, 33602, 96, 24, 10, 3, 72, 7, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 848, 480, 31565, 104, 24, 12, 3, 80, 5, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 856, 480, 31517, 104, 16, 13, 1, 88, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1024, 512, 24218, 136, 32, 15, 1, 104, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1024, 600, 20423, 144, 40, 18, 1, 104, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1024, 768, 15385, 160, 24, 29, 3, 136, 6, 0, 0, 0}, + {NULL, 75, 1024, 768, 12703, 176, 16, 28, 1, 96, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 85, 1024, 768, 10581, 208, 48, 36, 1, 96, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 100, 1024, 768, 8825, 184, 72, 42, 1, 112, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 75, 1152, 864, 9259, 256, 64, 32, 1, 128, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1280, 768, 12478, 200, 64, 23, 1, 136, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 50, 1280, 768, 15342, 184, 56, 19, 1, 128, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 960, 600, 21964, 128, 32, 15, 3, 96, 6, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1000, 600, 20803, 144, 40, 18, 1, 104, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1024, 576, 21278, 144, 40, 17, 1, 104, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1088, 612, 18825, 152, 48, 16, 3, 104, 5, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1152, 720, 14974, 168, 56, 19, 3, 112, 6, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1200, 720, 14248, 184, 56, 22, 1, 128, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 49, 1200, 900, 17703, 21, 11, 1, 1, 32, 10, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1280, 600, 16259, 184, 56, 18, 1, 128, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1280, 800, 11938, 200, 72, 22, 3, 128, 6, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1280, 1024, 9262, 248, 48, 38, 1, 112, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 75, 1280, 1024, 7409, 248, 16, 38, 1, 144, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 85, 1280, 1024, 6351, 224, 64, 44, 1, 160, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1360, 768, 11759, 208, 72, 22, 3, 136, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1368, 768, 11646, 216, 72, 23, 1, 144, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 50, 1368, 768, 14301, 200, 56, 19, 1, 144, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1368, 768, 11646, 216, 72, 23, 1, 144, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1440, 900, 9372, 232, 80, 25, 3, 152, 6, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 75, 1440, 900, 7311, 248, 96, 33, 3, 152, 6, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1440, 1040, 7993, 248, 96, 33, 1, 152, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1600, 900, 8449, 256, 88, 26, 3, 168, 5, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1600, 1024, 7333, 272, 104, 32, 1, 168, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1680, 1050, 6832, 280, 104, 30, 3, 176, 6, 0, 0, 0}, + {NULL, 75, 1680, 1050, 5339, 296, 120, 40, 3, 176, 6, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1792, 1344, 4883, 328, 128, 46, 1, 200, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1856, 1392, 4581, 352, 96, 43, 1, 224, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1920, 1440, 4273, 344, 128, 56, 1, 208, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 75, 1920, 1440, 3367, 352, 144, 56, 1, 224, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 2048, 1536, 3738, 376, 152, 49, 3, 224, 4, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1280, 720, 13484, 216, 112, 20, 5, 40, 5, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 50, 1280, 720, 16538, 176, 48, 17, 1, 128, 3, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1920, 1080, 5776, 328, 128, 32, 3, 200, 5, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1920, 1200, 5164, 336, 136, 36, 3, 200, 6, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 60, 1400, 1050, 8210, 232, 88, 32, 3, 144, 4, FB_SYNC_VERT_HIGH_ACT, 0, 0}, + {NULL, 75, 1400, 1050, 6398, 248, 104, 42, 3, 144, 4, FB_SYNC_VERT_HIGH_ACT, 0, 0} }; + +static const struct fb_videomode viafb_rb_modes[] = { + {NULL, 60, 1360, 768, 13879, 80, 48, 14, 3, 32, 5, FB_SYNC_HOR_HIGH_ACT, 0, 0}, + {NULL, 60, 1440, 900, 11249, 80, 48, 17, 3, 32, 6, FB_SYNC_HOR_HIGH_ACT, 0, 0}, + {NULL, 60, 1400, 1050, 9892, 80, 48, 23, 3, 32, 4, FB_SYNC_HOR_HIGH_ACT, 0, 0}, + {NULL, 60, 1600, 900, 10226, 80, 48, 18, 3, 32, 5, FB_SYNC_HOR_HIGH_ACT, 0, 0}, + {NULL, 60, 1680, 1050, 8387, 80, 48, 21, 3, 32, 6, FB_SYNC_HOR_HIGH_ACT, 0, 0}, + {NULL, 60, 1920, 1080, 7212, 80, 48, 23, 3, 32, 5, FB_SYNC_HOR_HIGH_ACT, 0, 0}, + {NULL, 60, 1920, 1200, 6488, 80, 48, 26, 3, 32, 6, FB_SYNC_HOR_HIGH_ACT, 0, 0} }; + +int NUM_TOTAL_CN400_ModeXregs = ARRAY_SIZE(CN400_ModeXregs); +int NUM_TOTAL_CN700_ModeXregs = ARRAY_SIZE(CN700_ModeXregs); +int NUM_TOTAL_KM400_ModeXregs = ARRAY_SIZE(KM400_ModeXregs); +int NUM_TOTAL_CX700_ModeXregs = ARRAY_SIZE(CX700_ModeXregs); +int NUM_TOTAL_VX855_ModeXregs = ARRAY_SIZE(VX855_ModeXregs); +int NUM_TOTAL_CLE266_ModeXregs = ARRAY_SIZE(CLE266_ModeXregs); +int NUM_TOTAL_PATCH_MODE = ARRAY_SIZE(res_patch_table); + + +static const struct fb_videomode *get_best_mode( + const struct fb_videomode *modes, int n, + int hres, int vres, int refresh) +{ + const struct fb_videomode *best = NULL; + int i; + + for (i = 0; i < n; i++) { + if (modes[i].xres != hres || modes[i].yres != vres) + continue; + + if (!best || abs(modes[i].refresh - refresh) < + abs(best->refresh - refresh)) + best = &modes[i]; + } + + return best; +} + +const struct fb_videomode *viafb_get_best_mode(int hres, int vres, int refresh) +{ + return get_best_mode(viafb_modes, ARRAY_SIZE(viafb_modes), + hres, vres, refresh); +} + +const struct fb_videomode *viafb_get_best_rb_mode(int hres, int vres, + int refresh) +{ + return get_best_mode(viafb_rb_modes, ARRAY_SIZE(viafb_rb_modes), + hres, vres, refresh); +} diff --git a/drivers/video/fbdev/via/viamode.h b/drivers/video/fbdev/via/viamode.h new file mode 100644 index 000000000000..dd19106698e7 --- /dev/null +++ b/drivers/video/fbdev/via/viamode.h @@ -0,0 +1,63 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef __VIAMODE_H__ +#define __VIAMODE_H__ + +#include "global.h" + +struct VPITTable { + unsigned char Misc; + unsigned char SR[StdSR]; + unsigned char GR[StdGR]; + unsigned char AR[StdAR]; +}; + +struct patch_table { + int table_length; + struct io_reg *io_reg_table; +}; + +extern int NUM_TOTAL_CN400_ModeXregs; +extern int NUM_TOTAL_CN700_ModeXregs; +extern int NUM_TOTAL_KM400_ModeXregs; +extern int NUM_TOTAL_CX700_ModeXregs; +extern int NUM_TOTAL_VX855_ModeXregs; +extern int NUM_TOTAL_CLE266_ModeXregs; +extern int NUM_TOTAL_PATCH_MODE; + +extern struct io_reg CN400_ModeXregs[]; +extern struct io_reg CN700_ModeXregs[]; +extern struct io_reg KM400_ModeXregs[]; +extern struct io_reg CX700_ModeXregs[]; +extern struct io_reg VX800_ModeXregs[]; +extern struct io_reg VX855_ModeXregs[]; +extern struct io_reg CLE266_ModeXregs[]; +extern struct io_reg PM1024x768[]; +extern struct patch_table res_patch_table[]; +extern struct VPITTable VPIT; + +const struct fb_videomode *viafb_get_best_mode(int hres, int vres, + int refresh); +const struct fb_videomode *viafb_get_best_rb_mode(int hres, int vres, + int refresh); + +#endif /* __VIAMODE_H__ */ diff --git a/drivers/video/fbdev/via/vt1636.c b/drivers/video/fbdev/via/vt1636.c new file mode 100644 index 000000000000..ee2903b472cf --- /dev/null +++ b/drivers/video/fbdev/via/vt1636.c @@ -0,0 +1,244 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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/via-core.h> +#include <linux/via_i2c.h> +#include "global.h" + +static const struct IODATA common_init_data[] = { +/* Index, Mask, Value */ + /* Set panel power sequence timing */ + {0x10, 0xC0, 0x00}, + /* T1: VDD on - Data on. Each increment is 1 ms. (50ms = 031h) */ + {0x0B, 0xFF, 0x40}, + /* T2: Data on - Backlight on. Each increment is 2 ms. (210ms = 068h) */ + {0x0C, 0xFF, 0x31}, + /* T3: Backlight off -Data off. Each increment is 2 ms. (210ms = 068h)*/ + {0x0D, 0xFF, 0x31}, + /* T4: Data off - VDD off. Each increment is 1 ms. (50ms = 031h) */ + {0x0E, 0xFF, 0x68}, + /* T5: VDD off - VDD on. Each increment is 100 ms. (500ms = 04h) */ + {0x0F, 0xFF, 0x68}, + /* LVDS output power up */ + {0x09, 0xA0, 0xA0}, + /* turn on back light */ + {0x10, 0x33, 0x13} +}; + +/* Index, Mask, Value */ +static const struct IODATA dual_channel_enable_data = {0x08, 0xF0, 0xE0}; +static const struct IODATA single_channel_enable_data = {0x08, 0xF0, 0x00}; +static const struct IODATA dithering_enable_data = {0x0A, 0x70, 0x50}; +static const struct IODATA dithering_disable_data = {0x0A, 0x70, 0x00}; +static const struct IODATA vdd_on_data = {0x10, 0x20, 0x20}; +static const struct IODATA vdd_off_data = {0x10, 0x20, 0x00}; + +u8 viafb_gpio_i2c_read_lvds(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information *plvds_chip_info, + u8 index) +{ + u8 data; + + viafb_i2c_readbyte(plvds_chip_info->i2c_port, + plvds_chip_info->lvds_chip_slave_addr, index, &data); + return data; +} + +void viafb_gpio_i2c_write_mask_lvds(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information + *plvds_chip_info, struct IODATA io_data) +{ + int index, data; + + index = io_data.Index; + data = viafb_gpio_i2c_read_lvds(plvds_setting_info, plvds_chip_info, + index); + data = (data & (~io_data.Mask)) | io_data.Data; + + viafb_i2c_writebyte(plvds_chip_info->i2c_port, + plvds_chip_info->lvds_chip_slave_addr, index, data); +} + +void viafb_init_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information *plvds_chip_info) +{ + int reg_num, i; + + /* Common settings: */ + reg_num = ARRAY_SIZE(common_init_data); + for (i = 0; i < reg_num; i++) + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, + plvds_chip_info, common_init_data[i]); + + /* Input Data Mode Select */ + if (plvds_setting_info->device_lcd_dualedge) + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, + plvds_chip_info, dual_channel_enable_data); + else + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, + plvds_chip_info, single_channel_enable_data); + + if (plvds_setting_info->LCDDithering) + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, + plvds_chip_info, dithering_enable_data); + else + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, + plvds_chip_info, dithering_disable_data); +} + +void viafb_enable_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, plvds_chip_info, + vdd_on_data); +} + +void viafb_disable_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, plvds_chip_info, + vdd_off_data); +} + +bool viafb_lvds_identify_vt1636(u8 i2c_adapter) +{ + u8 Buffer[2]; + + DEBUG_MSG(KERN_INFO "viafb_lvds_identify_vt1636.\n"); + + /* Sense VT1636 LVDS Transmiter */ + viaparinfo->chip_info->lvds_chip_info.lvds_chip_slave_addr = + VT1636_LVDS_I2C_ADDR; + + /* Check vendor ID first: */ + if (viafb_i2c_readbyte(i2c_adapter, VT1636_LVDS_I2C_ADDR, + 0x00, &Buffer[0])) + return false; + viafb_i2c_readbyte(i2c_adapter, VT1636_LVDS_I2C_ADDR, 0x01, &Buffer[1]); + + if (!((Buffer[0] == 0x06) && (Buffer[1] == 0x11))) + return false; + + /* Check Chip ID: */ + viafb_i2c_readbyte(i2c_adapter, VT1636_LVDS_I2C_ADDR, 0x02, &Buffer[0]); + viafb_i2c_readbyte(i2c_adapter, VT1636_LVDS_I2C_ADDR, 0x03, &Buffer[1]); + if ((Buffer[0] == 0x45) && (Buffer[1] == 0x33)) { + viaparinfo->chip_info->lvds_chip_info.lvds_chip_name = + VT1636_LVDS; + return true; + } + + return false; +} + +static int get_clk_range_index(u32 Clk) +{ + if (Clk < DPA_CLK_30M) + return DPA_CLK_RANGE_30M; + else if (Clk < DPA_CLK_50M) + return DPA_CLK_RANGE_30_50M; + else if (Clk < DPA_CLK_70M) + return DPA_CLK_RANGE_50_70M; + else if (Clk < DPA_CLK_100M) + return DPA_CLK_RANGE_70_100M; + else if (Clk < DPA_CLK_150M) + return DPA_CLK_RANGE_100_150M; + else + return DPA_CLK_RANGE_150M; +} + +static void set_dpa_vt1636(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information *plvds_chip_info, + struct VT1636_DPA_SETTING *p_vt1636_dpa_setting) +{ + struct IODATA io_data; + + io_data.Index = 0x09; + io_data.Mask = 0x1F; + io_data.Data = p_vt1636_dpa_setting->CLK_SEL_ST1; + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, + plvds_chip_info, io_data); + + io_data.Index = 0x08; + io_data.Mask = 0x0F; + io_data.Data = p_vt1636_dpa_setting->CLK_SEL_ST2; + viafb_gpio_i2c_write_mask_lvds(plvds_setting_info, plvds_chip_info, + io_data); +} + +void viafb_vt1636_patch_skew_on_vt3324( + struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + struct VT1636_DPA_SETTING dpa = {0x00, 0x00}, dpa_16x12 = {0x0B, 0x03}, + *pdpa; + int index; + + DEBUG_MSG(KERN_INFO "viafb_vt1636_patch_skew_on_vt3324.\n"); + + /* Graphics DPA settings: */ + index = get_clk_range_index(plvds_setting_info->vclk); + viafb_set_dpa_gfx(plvds_chip_info->output_interface, + &GFX_DPA_SETTING_TBL_VT3324[index]); + + /* LVDS Transmitter DPA settings: */ + if (plvds_setting_info->lcd_panel_hres == 1600 && + plvds_setting_info->lcd_panel_vres == 1200) + pdpa = &dpa_16x12; + else + pdpa = &dpa; + + set_dpa_vt1636(plvds_setting_info, plvds_chip_info, pdpa); +} + +void viafb_vt1636_patch_skew_on_vt3327( + struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + struct VT1636_DPA_SETTING dpa = {0x00, 0x00}; + int index; + + DEBUG_MSG(KERN_INFO "viafb_vt1636_patch_skew_on_vt3327.\n"); + + /* Graphics DPA settings: */ + index = get_clk_range_index(plvds_setting_info->vclk); + viafb_set_dpa_gfx(plvds_chip_info->output_interface, + &GFX_DPA_SETTING_TBL_VT3327[index]); + + /* LVDS Transmitter DPA settings: */ + set_dpa_vt1636(plvds_setting_info, plvds_chip_info, &dpa); +} + +void viafb_vt1636_patch_skew_on_vt3364( + struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info) +{ + int index; + + DEBUG_MSG(KERN_INFO "viafb_vt1636_patch_skew_on_vt3364.\n"); + + /* Graphics DPA settings: */ + index = get_clk_range_index(plvds_setting_info->vclk); + viafb_set_dpa_gfx(plvds_chip_info->output_interface, + &GFX_DPA_SETTING_TBL_VT3364[index]); +} diff --git a/drivers/video/fbdev/via/vt1636.h b/drivers/video/fbdev/via/vt1636.h new file mode 100644 index 000000000000..4c1314e57468 --- /dev/null +++ b/drivers/video/fbdev/via/vt1636.h @@ -0,0 +1,44 @@ +/* + * Copyright 1998-2008 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + + * 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, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTIES OR REPRESENTATIONS; 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. + */ + +#ifndef _VT1636_H_ +#define _VT1636_H_ +#include "chip.h" +bool viafb_lvds_identify_vt1636(u8 i2c_adapter); +void viafb_init_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, struct lvds_chip_information *plvds_chip_info); +void viafb_enable_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +void viafb_disable_lvds_vt1636(struct lvds_setting_information + *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +void viafb_vt1636_patch_skew_on_vt3324( + struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +void viafb_vt1636_patch_skew_on_vt3327( + struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); +void viafb_vt1636_patch_skew_on_vt3364( + struct lvds_setting_information *plvds_setting_info, + struct lvds_chip_information *plvds_chip_info); + +#endif diff --git a/drivers/video/fbdev/vt8500lcdfb.c b/drivers/video/fbdev/vt8500lcdfb.c new file mode 100644 index 000000000000..a8f2b280f796 --- /dev/null +++ b/drivers/video/fbdev/vt8500lcdfb.c @@ -0,0 +1,502 @@ +/* + * linux/drivers/video/vt8500lcdfb.c + * + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * Based on skeletonfb.c and pxafb.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <video/of_display_timing.h> + +#include "vt8500lcdfb.h" +#include "wmt_ge_rops.h" + +#ifdef CONFIG_OF +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/memblock.h> +#endif + + +#define to_vt8500lcd_info(__info) container_of(__info, \ + struct vt8500lcd_info, fb) + +static int vt8500lcd_set_par(struct fb_info *info) +{ + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + int reg_bpp = 5; /* 16bpp */ + int i; + unsigned long control0; + + if (!fbi) + return -EINVAL; + + if (info->var.bits_per_pixel <= 8) { + /* palettized */ + info->var.red.offset = 0; + info->var.red.length = info->var.bits_per_pixel; + info->var.red.msb_right = 0; + + info->var.green.offset = 0; + info->var.green.length = info->var.bits_per_pixel; + info->var.green.msb_right = 0; + + info->var.blue.offset = 0; + info->var.blue.length = info->var.bits_per_pixel; + info->var.blue.msb_right = 0; + + info->var.transp.offset = 0; + info->var.transp.length = 0; + info->var.transp.msb_right = 0; + + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.line_length = info->var.xres_virtual / + (8/info->var.bits_per_pixel); + } else { + /* non-palettized */ + info->var.transp.offset = 0; + info->var.transp.length = 0; + info->var.transp.msb_right = 0; + + if (info->var.bits_per_pixel == 16) { + /* RGB565 */ + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.red.msb_right = 0; + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->var.blue.msb_right = 0; + } else { + /* Equal depths per channel */ + info->var.red.offset = info->var.bits_per_pixel + * 2 / 3; + info->var.red.length = info->var.bits_per_pixel / 3; + info->var.red.msb_right = 0; + info->var.green.offset = info->var.bits_per_pixel / 3; + info->var.green.length = info->var.bits_per_pixel / 3; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = info->var.bits_per_pixel / 3; + info->var.blue.msb_right = 0; + } + + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.bits_per_pixel > 16 ? + info->var.xres_virtual << 2 : + info->var.xres_virtual << 1; + } + + for (i = 0; i < 8; i++) { + if (bpp_values[i] == info->var.bits_per_pixel) { + reg_bpp = i; + continue; + } + } + + control0 = readl(fbi->regbase) & ~0xf; + writel(0, fbi->regbase); + while (readl(fbi->regbase + 0x38) & 0x10) + /* wait */; + writel((((info->var.hsync_len - 1) & 0x3f) << 26) + | ((info->var.left_margin & 0xff) << 18) + | (((info->var.xres - 1) & 0x3ff) << 8) + | (info->var.right_margin & 0xff), fbi->regbase + 0x4); + writel((((info->var.vsync_len - 1) & 0x3f) << 26) + | ((info->var.upper_margin & 0xff) << 18) + | (((info->var.yres - 1) & 0x3ff) << 8) + | (info->var.lower_margin & 0xff), fbi->regbase + 0x8); + writel((((info->var.yres - 1) & 0x400) << 2) + | ((info->var.xres - 1) & 0x400), fbi->regbase + 0x10); + writel(0x80000000, fbi->regbase + 0x20); + writel(control0 | (reg_bpp << 1) | 0x100, fbi->regbase); + + return 0; +} + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int vt8500lcd_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) { + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + int ret = 1; + unsigned int val; + if (regno >= 256) + return -EINVAL; + + if (info->var.grayscale) + red = green = blue = + (19595 * red + 38470 * green + 7471 * blue) >> 16; + + switch (fbi->fb.fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + u32 *pal = fbi->fb.pseudo_palette; + + val = chan_to_field(red, &fbi->fb.var.red); + val |= chan_to_field(green, &fbi->fb.var.green); + val |= chan_to_field(blue, &fbi->fb.var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + writew((red & 0xf800) + | ((green >> 5) & 0x7e0) + | ((blue >> 11) & 0x1f), + fbi->palette_cpu + sizeof(u16) * regno); + break; + } + + return ret; +} + +static int vt8500lcd_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + + if (cmd == FBIO_WAITFORVSYNC) { + /* Unmask End of Frame interrupt */ + writel(0xffffffff ^ (1 << 3), fbi->regbase + 0x3c); + ret = wait_event_interruptible_timeout(fbi->wait, + readl(fbi->regbase + 0x38) & (1 << 3), HZ / 10); + /* Mask back to reduce unwanted interrupt traffic */ + writel(0xffffffff, fbi->regbase + 0x3c); + if (ret < 0) + return ret; + if (ret == 0) + return -ETIMEDOUT; + } + + return ret; +} + +static int vt8500lcd_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + unsigned pixlen = info->fix.line_length / info->var.xres_virtual; + unsigned off = pixlen * var->xoffset + + info->fix.line_length * var->yoffset; + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + + writel((1 << 31) + | (((info->var.xres_virtual - info->var.xres) * pixlen / 4) << 20) + | (off >> 2), fbi->regbase + 0x20); + return 0; +} + +/* + * vt8500lcd_blank(): + * Blank the display by setting all palette values to zero. Note, + * True Color modes do not really use the palette, so this will not + * blank the display in all modes. + */ +static int vt8500lcd_blank(int blank, struct fb_info *info) +{ + int i; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR || + info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) + for (i = 0; i < 256; i++) + vt8500lcd_setcolreg(i, 0, 0, 0, 0, info); + case FB_BLANK_UNBLANK: + if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR || + info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) + fb_set_cmap(&info->cmap, info); + } + return 0; +} + +static struct fb_ops vt8500lcd_ops = { + .owner = THIS_MODULE, + .fb_set_par = vt8500lcd_set_par, + .fb_setcolreg = vt8500lcd_setcolreg, + .fb_fillrect = wmt_ge_fillrect, + .fb_copyarea = wmt_ge_copyarea, + .fb_imageblit = sys_imageblit, + .fb_sync = wmt_ge_sync, + .fb_ioctl = vt8500lcd_ioctl, + .fb_pan_display = vt8500lcd_pan_display, + .fb_blank = vt8500lcd_blank, +}; + +static irqreturn_t vt8500lcd_handle_irq(int irq, void *dev_id) +{ + struct vt8500lcd_info *fbi = dev_id; + + if (readl(fbi->regbase + 0x38) & (1 << 3)) + wake_up_interruptible(&fbi->wait); + + writel(0xffffffff, fbi->regbase + 0x38); + return IRQ_HANDLED; +} + +static int vt8500lcd_probe(struct platform_device *pdev) +{ + struct vt8500lcd_info *fbi; + struct resource *res; + struct display_timings *disp_timing; + void *addr; + int irq, ret; + + struct fb_videomode of_mode; + u32 bpp; + dma_addr_t fb_mem_phys; + unsigned long fb_mem_len; + void *fb_mem_virt; + + ret = -ENOMEM; + fbi = NULL; + + fbi = devm_kzalloc(&pdev->dev, sizeof(struct vt8500lcd_info) + + sizeof(u32) * 16, GFP_KERNEL); + if (!fbi) { + dev_err(&pdev->dev, "Failed to initialize framebuffer device\n"); + return -ENOMEM; + } + + strcpy(fbi->fb.fix.id, "VT8500 LCD"); + + fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fbi->fb.fix.xpanstep = 0; + fbi->fb.fix.ypanstep = 1; + fbi->fb.fix.ywrapstep = 0; + fbi->fb.fix.accel = FB_ACCEL_NONE; + + fbi->fb.var.nonstd = 0; + fbi->fb.var.activate = FB_ACTIVATE_NOW; + fbi->fb.var.height = -1; + fbi->fb.var.width = -1; + fbi->fb.var.vmode = FB_VMODE_NONINTERLACED; + + fbi->fb.fbops = &vt8500lcd_ops; + fbi->fb.flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_YPAN + | FBINFO_VIRTFB + | FBINFO_PARTIAL_PAN_OK; + fbi->fb.node = -1; + + addr = fbi; + addr = addr + sizeof(struct vt8500lcd_info); + fbi->fb.pseudo_palette = addr; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + return -ENODEV; + } + + res = request_mem_region(res->start, resource_size(res), "vt8500lcd"); + if (res == NULL) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + return -EBUSY; + } + + fbi->regbase = ioremap(res->start, resource_size(res)); + if (fbi->regbase == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + ret = -EBUSY; + goto failed_free_res; + } + + disp_timing = of_get_display_timings(pdev->dev.of_node); + if (!disp_timing) { + ret = -EINVAL; + goto failed_free_io; + } + + ret = of_get_fb_videomode(pdev->dev.of_node, &of_mode, + OF_USE_NATIVE_MODE); + if (ret) + goto failed_free_io; + + ret = of_property_read_u32(pdev->dev.of_node, "bits-per-pixel", &bpp); + if (ret) + goto failed_free_io; + + /* try allocating the framebuffer */ + fb_mem_len = of_mode.xres * of_mode.yres * 2 * (bpp / 8); + fb_mem_virt = dma_alloc_coherent(&pdev->dev, fb_mem_len, &fb_mem_phys, + GFP_KERNEL); + if (!fb_mem_virt) { + pr_err("%s: Failed to allocate framebuffer\n", __func__); + ret = -ENOMEM; + goto failed_free_io; + } + + fbi->fb.fix.smem_start = fb_mem_phys; + fbi->fb.fix.smem_len = fb_mem_len; + fbi->fb.screen_base = fb_mem_virt; + + fbi->palette_size = PAGE_ALIGN(512); + fbi->palette_cpu = dma_alloc_coherent(&pdev->dev, + fbi->palette_size, + &fbi->palette_phys, + GFP_KERNEL); + if (fbi->palette_cpu == NULL) { + dev_err(&pdev->dev, "Failed to allocate palette buffer\n"); + ret = -ENOMEM; + goto failed_free_io; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no IRQ defined\n"); + ret = -ENODEV; + goto failed_free_palette; + } + + ret = request_irq(irq, vt8500lcd_handle_irq, 0, "LCD", fbi); + if (ret) { + dev_err(&pdev->dev, "request_irq failed: %d\n", ret); + ret = -EBUSY; + goto failed_free_palette; + } + + init_waitqueue_head(&fbi->wait); + + if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) { + dev_err(&pdev->dev, "Failed to allocate color map\n"); + ret = -ENOMEM; + goto failed_free_irq; + } + + fb_videomode_to_var(&fbi->fb.var, &of_mode); + + fbi->fb.var.xres_virtual = of_mode.xres; + fbi->fb.var.yres_virtual = of_mode.yres * 2; + fbi->fb.var.bits_per_pixel = bpp; + + ret = vt8500lcd_set_par(&fbi->fb); + if (ret) { + dev_err(&pdev->dev, "Failed to set parameters\n"); + goto failed_free_cmap; + } + + writel(fbi->fb.fix.smem_start >> 22, fbi->regbase + 0x1c); + writel((fbi->palette_phys & 0xfffffe00) | 1, fbi->regbase + 0x18); + + platform_set_drvdata(pdev, fbi); + + ret = register_framebuffer(&fbi->fb); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to register framebuffer device: %d\n", ret); + goto failed_free_cmap; + } + + /* + * Ok, now enable the LCD controller + */ + writel(readl(fbi->regbase) | 1, fbi->regbase); + + return 0; + +failed_free_cmap: + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); +failed_free_irq: + free_irq(irq, fbi); +failed_free_palette: + dma_free_coherent(&pdev->dev, fbi->palette_size, + fbi->palette_cpu, fbi->palette_phys); +failed_free_io: + iounmap(fbi->regbase); +failed_free_res: + release_mem_region(res->start, resource_size(res)); + return ret; +} + +static int vt8500lcd_remove(struct platform_device *pdev) +{ + struct vt8500lcd_info *fbi = platform_get_drvdata(pdev); + struct resource *res; + int irq; + + unregister_framebuffer(&fbi->fb); + + writel(0, fbi->regbase); + + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); + + irq = platform_get_irq(pdev, 0); + free_irq(irq, fbi); + + dma_free_coherent(&pdev->dev, fbi->palette_size, + fbi->palette_cpu, fbi->palette_phys); + + iounmap(fbi->regbase); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + kfree(fbi); + + return 0; +} + +static const struct of_device_id via_dt_ids[] = { + { .compatible = "via,vt8500-fb", }, + {} +}; + +static struct platform_driver vt8500lcd_driver = { + .probe = vt8500lcd_probe, + .remove = vt8500lcd_remove, + .driver = { + .owner = THIS_MODULE, + .name = "vt8500-lcd", + .of_match_table = of_match_ptr(via_dt_ids), + }, +}; + +module_platform_driver(vt8500lcd_driver); + +MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>"); +MODULE_DESCRIPTION("LCD controller driver for VIA VT8500"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, via_dt_ids); diff --git a/drivers/video/fbdev/vt8500lcdfb.h b/drivers/video/fbdev/vt8500lcdfb.h new file mode 100644 index 000000000000..36ca3ca09d83 --- /dev/null +++ b/drivers/video/fbdev/vt8500lcdfb.h @@ -0,0 +1,34 @@ +/* + * linux/drivers/video/vt8500lcdfb.h + * + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +struct vt8500lcd_info { + struct fb_info fb; + void __iomem *regbase; + void __iomem *palette_cpu; + dma_addr_t palette_phys; + size_t palette_size; + wait_queue_head_t wait; +}; + +static int bpp_values[] = { + 1, + 2, + 4, + 8, + 12, + 16, + 18, + 24, +}; diff --git a/drivers/video/fbdev/vt8623fb.c b/drivers/video/fbdev/vt8623fb.c new file mode 100644 index 000000000000..5c7cbc6c6236 --- /dev/null +++ b/drivers/video/fbdev/vt8623fb.c @@ -0,0 +1,958 @@ +/* + * linux/drivers/video/vt8623fb.c - fbdev driver for + * integrated graphic core in VIA VT8623 [CLE266] chipset + * + * Copyright (c) 2006-2007 Ondrej Zajicek <santiago@crfreenet.org> + * + * 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. + * + * Code is based on s3fb, some parts are from David Boucher's viafb + * (http://davesdomain.org.uk/viafb/) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/svga.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/console.h> /* Why should fb driver call console functions? because console_lock() */ +#include <video/vga.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +struct vt8623fb_info { + char __iomem *mmio_base; + int mtrr_reg; + struct vgastate state; + struct mutex open_lock; + unsigned int ref_count; + u32 pseudo_palette[16]; +}; + + + +/* ------------------------------------------------------------------------- */ + +static const struct svga_fb_format vt8623fb_formats[] = { + { 0, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 0, + FB_TYPE_TEXT, FB_AUX_TEXT_SVGA_STEP8, FB_VISUAL_PSEUDOCOLOR, 16, 16}, + { 4, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_PSEUDOCOLOR, 16, 16}, + { 4, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 1, + FB_TYPE_INTERLEAVED_PLANES, 1, FB_VISUAL_PSEUDOCOLOR, 16, 16}, + { 8, {0, 6, 0}, {0, 6, 0}, {0, 6, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_PSEUDOCOLOR, 8, 8}, +/* {16, {10, 5, 0}, {5, 5, 0}, {0, 5, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 4, 4}, */ + {16, {11, 5, 0}, {5, 6, 0}, {0, 5, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 4, 4}, + {32, {16, 8, 0}, {8, 8, 0}, {0, 8, 0}, {0, 0, 0}, 0, + FB_TYPE_PACKED_PIXELS, 0, FB_VISUAL_TRUECOLOR, 2, 2}, + SVGA_FORMAT_END +}; + +static const struct svga_pll vt8623_pll = {2, 127, 2, 7, 0, 3, + 60000, 300000, 14318}; + +/* CRT timing register sets */ + +static struct vga_regset vt8623_h_total_regs[] = {{0x00, 0, 7}, {0x36, 3, 3}, VGA_REGSET_END}; +static struct vga_regset vt8623_h_display_regs[] = {{0x01, 0, 7}, VGA_REGSET_END}; +static struct vga_regset vt8623_h_blank_start_regs[] = {{0x02, 0, 7}, VGA_REGSET_END}; +static struct vga_regset vt8623_h_blank_end_regs[] = {{0x03, 0, 4}, {0x05, 7, 7}, {0x33, 5, 5}, VGA_REGSET_END}; +static struct vga_regset vt8623_h_sync_start_regs[] = {{0x04, 0, 7}, {0x33, 4, 4}, VGA_REGSET_END}; +static struct vga_regset vt8623_h_sync_end_regs[] = {{0x05, 0, 4}, VGA_REGSET_END}; + +static struct vga_regset vt8623_v_total_regs[] = {{0x06, 0, 7}, {0x07, 0, 0}, {0x07, 5, 5}, {0x35, 0, 0}, VGA_REGSET_END}; +static struct vga_regset vt8623_v_display_regs[] = {{0x12, 0, 7}, {0x07, 1, 1}, {0x07, 6, 6}, {0x35, 2, 2}, VGA_REGSET_END}; +static struct vga_regset vt8623_v_blank_start_regs[] = {{0x15, 0, 7}, {0x07, 3, 3}, {0x09, 5, 5}, {0x35, 3, 3}, VGA_REGSET_END}; +static struct vga_regset vt8623_v_blank_end_regs[] = {{0x16, 0, 7}, VGA_REGSET_END}; +static struct vga_regset vt8623_v_sync_start_regs[] = {{0x10, 0, 7}, {0x07, 2, 2}, {0x07, 7, 7}, {0x35, 1, 1}, VGA_REGSET_END}; +static struct vga_regset vt8623_v_sync_end_regs[] = {{0x11, 0, 3}, VGA_REGSET_END}; + +static struct vga_regset vt8623_offset_regs[] = {{0x13, 0, 7}, {0x35, 5, 7}, VGA_REGSET_END}; +static struct vga_regset vt8623_line_compare_regs[] = {{0x18, 0, 7}, {0x07, 4, 4}, {0x09, 6, 6}, {0x33, 0, 2}, {0x35, 4, 4}, VGA_REGSET_END}; +static struct vga_regset vt8623_fetch_count_regs[] = {{0x1C, 0, 7}, {0x1D, 0, 1}, VGA_REGSET_END}; +static struct vga_regset vt8623_start_address_regs[] = {{0x0d, 0, 7}, {0x0c, 0, 7}, {0x34, 0, 7}, {0x48, 0, 1}, VGA_REGSET_END}; + +static struct svga_timing_regs vt8623_timing_regs = { + vt8623_h_total_regs, vt8623_h_display_regs, vt8623_h_blank_start_regs, + vt8623_h_blank_end_regs, vt8623_h_sync_start_regs, vt8623_h_sync_end_regs, + vt8623_v_total_regs, vt8623_v_display_regs, vt8623_v_blank_start_regs, + vt8623_v_blank_end_regs, vt8623_v_sync_start_regs, vt8623_v_sync_end_regs, +}; + + +/* ------------------------------------------------------------------------- */ + + +/* Module parameters */ + +static char *mode_option = "640x480-8@60"; + +#ifdef CONFIG_MTRR +static int mtrr = 1; +#endif + +MODULE_AUTHOR("(c) 2006 Ondrej Zajicek <santiago@crfreenet.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("fbdev driver for integrated graphics core in VIA VT8623 [CLE266]"); + +module_param(mode_option, charp, 0644); +MODULE_PARM_DESC(mode_option, "Default video mode ('640x480-8@60', etc)"); +module_param_named(mode, mode_option, charp, 0); +MODULE_PARM_DESC(mode, "Default video mode e.g. '648x480-8@60' (deprecated)"); + +#ifdef CONFIG_MTRR +module_param(mtrr, int, 0444); +MODULE_PARM_DESC(mtrr, "Enable write-combining with MTRR (1=enable, 0=disable, default=1)"); +#endif + + +/* ------------------------------------------------------------------------- */ + +static void vt8623fb_tilecursor(struct fb_info *info, struct fb_tilecursor *cursor) +{ + struct vt8623fb_info *par = info->par; + + svga_tilecursor(par->state.vgabase, info, cursor); +} + +static struct fb_tile_ops vt8623fb_tile_ops = { + .fb_settile = svga_settile, + .fb_tilecopy = svga_tilecopy, + .fb_tilefill = svga_tilefill, + .fb_tileblit = svga_tileblit, + .fb_tilecursor = vt8623fb_tilecursor, + .fb_get_tilemax = svga_get_tilemax, +}; + + +/* ------------------------------------------------------------------------- */ + + +/* image data is MSB-first, fb structure is MSB-first too */ +static inline u32 expand_color(u32 c) +{ + return ((c & 1) | ((c & 2) << 7) | ((c & 4) << 14) | ((c & 8) << 21)) * 0xFF; +} + +/* vt8623fb_iplan_imageblit silently assumes that almost everything is 8-pixel aligned */ +static void vt8623fb_iplan_imageblit(struct fb_info *info, const struct fb_image *image) +{ + u32 fg = expand_color(image->fg_color); + u32 bg = expand_color(image->bg_color); + const u8 *src1, *src; + u8 __iomem *dst1; + u32 __iomem *dst; + u32 val; + int x, y; + + src1 = image->data; + dst1 = info->screen_base + (image->dy * info->fix.line_length) + + ((image->dx / 8) * 4); + + for (y = 0; y < image->height; y++) { + src = src1; + dst = (u32 __iomem *) dst1; + for (x = 0; x < image->width; x += 8) { + val = *(src++) * 0x01010101; + val = (val & fg) | (~val & bg); + fb_writel(val, dst++); + } + src1 += image->width / 8; + dst1 += info->fix.line_length; + } +} + +/* vt8623fb_iplan_fillrect silently assumes that almost everything is 8-pixel aligned */ +static void vt8623fb_iplan_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + u32 fg = expand_color(rect->color); + u8 __iomem *dst1; + u32 __iomem *dst; + int x, y; + + dst1 = info->screen_base + (rect->dy * info->fix.line_length) + + ((rect->dx / 8) * 4); + + for (y = 0; y < rect->height; y++) { + dst = (u32 __iomem *) dst1; + for (x = 0; x < rect->width; x += 8) { + fb_writel(fg, dst++); + } + dst1 += info->fix.line_length; + } +} + + +/* image data is MSB-first, fb structure is high-nibble-in-low-byte-first */ +static inline u32 expand_pixel(u32 c) +{ + return (((c & 1) << 24) | ((c & 2) << 27) | ((c & 4) << 14) | ((c & 8) << 17) | + ((c & 16) << 4) | ((c & 32) << 7) | ((c & 64) >> 6) | ((c & 128) >> 3)) * 0xF; +} + +/* vt8623fb_cfb4_imageblit silently assumes that almost everything is 8-pixel aligned */ +static void vt8623fb_cfb4_imageblit(struct fb_info *info, const struct fb_image *image) +{ + u32 fg = image->fg_color * 0x11111111; + u32 bg = image->bg_color * 0x11111111; + const u8 *src1, *src; + u8 __iomem *dst1; + u32 __iomem *dst; + u32 val; + int x, y; + + src1 = image->data; + dst1 = info->screen_base + (image->dy * info->fix.line_length) + + ((image->dx / 8) * 4); + + for (y = 0; y < image->height; y++) { + src = src1; + dst = (u32 __iomem *) dst1; + for (x = 0; x < image->width; x += 8) { + val = expand_pixel(*(src++)); + val = (val & fg) | (~val & bg); + fb_writel(val, dst++); + } + src1 += image->width / 8; + dst1 += info->fix.line_length; + } +} + +static void vt8623fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + if ((info->var.bits_per_pixel == 4) && (image->depth == 1) + && ((image->width % 8) == 0) && ((image->dx % 8) == 0)) { + if (info->fix.type == FB_TYPE_INTERLEAVED_PLANES) + vt8623fb_iplan_imageblit(info, image); + else + vt8623fb_cfb4_imageblit(info, image); + } else + cfb_imageblit(info, image); +} + +static void vt8623fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + if ((info->var.bits_per_pixel == 4) + && ((rect->width % 8) == 0) && ((rect->dx % 8) == 0) + && (info->fix.type == FB_TYPE_INTERLEAVED_PLANES)) + vt8623fb_iplan_fillrect(info, rect); + else + cfb_fillrect(info, rect); +} + + +/* ------------------------------------------------------------------------- */ + + +static void vt8623_set_pixclock(struct fb_info *info, u32 pixclock) +{ + struct vt8623fb_info *par = info->par; + u16 m, n, r; + u8 regval; + int rv; + + rv = svga_compute_pll(&vt8623_pll, 1000000000 / pixclock, &m, &n, &r, info->node); + if (rv < 0) { + fb_err(info, "cannot set requested pixclock, keeping old value\n"); + return; + } + + /* Set VGA misc register */ + regval = vga_r(par->state.vgabase, VGA_MIS_R); + vga_w(par->state.vgabase, VGA_MIS_W, regval | VGA_MIS_ENB_PLL_LOAD); + + /* Set clock registers */ + vga_wseq(par->state.vgabase, 0x46, (n | (r << 6))); + vga_wseq(par->state.vgabase, 0x47, m); + + udelay(1000); + + /* PLL reset */ + svga_wseq_mask(par->state.vgabase, 0x40, 0x02, 0x02); + svga_wseq_mask(par->state.vgabase, 0x40, 0x00, 0x02); +} + + +static int vt8623fb_open(struct fb_info *info, int user) +{ + struct vt8623fb_info *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + void __iomem *vgabase = par->state.vgabase; + + memset(&(par->state), 0, sizeof(struct vgastate)); + par->state.vgabase = vgabase; + par->state.flags = VGA_SAVE_MODE | VGA_SAVE_FONTS | VGA_SAVE_CMAP; + par->state.num_crtc = 0xA2; + par->state.num_seq = 0x50; + save_vga(&(par->state)); + } + + par->ref_count++; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +static int vt8623fb_release(struct fb_info *info, int user) +{ + struct vt8623fb_info *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + mutex_unlock(&(par->open_lock)); + return -EINVAL; + } + + if (par->ref_count == 1) + restore_vga(&(par->state)); + + par->ref_count--; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +static int vt8623fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int rv, mem, step; + + /* Find appropriate format */ + rv = svga_match_format (vt8623fb_formats, var, NULL); + if (rv < 0) + { + fb_err(info, "unsupported mode requested\n"); + return rv; + } + + /* Do not allow to have real resoulution larger than virtual */ + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + /* Round up xres_virtual to have proper alignment of lines */ + step = vt8623fb_formats[rv].xresstep - 1; + var->xres_virtual = (var->xres_virtual+step) & ~step; + + /* Check whether have enough memory */ + mem = ((var->bits_per_pixel * var->xres_virtual) >> 3) * var->yres_virtual; + if (mem > info->screen_size) + { + fb_err(info, "not enough framebuffer memory (%d kB requested, %d kB available)\n", + mem >> 10, (unsigned int) (info->screen_size >> 10)); + return -EINVAL; + } + + /* Text mode is limited to 256 kB of memory */ + if ((var->bits_per_pixel == 0) && (mem > (256*1024))) + { + fb_err(info, "text framebuffer size too large (%d kB requested, 256 kB possible)\n", + mem >> 10); + return -EINVAL; + } + + rv = svga_check_timings (&vt8623_timing_regs, var, info->node); + if (rv < 0) + { + fb_err(info, "invalid timings requested\n"); + return rv; + } + + /* Interlaced mode not supported */ + if (var->vmode & FB_VMODE_INTERLACED) + return -EINVAL; + + return 0; +} + + +static int vt8623fb_set_par(struct fb_info *info) +{ + u32 mode, offset_value, fetch_value, screen_size; + struct vt8623fb_info *par = info->par; + u32 bpp = info->var.bits_per_pixel; + + if (bpp != 0) { + info->fix.ypanstep = 1; + info->fix.line_length = (info->var.xres_virtual * bpp) / 8; + + info->flags &= ~FBINFO_MISC_TILEBLITTING; + info->tileops = NULL; + + /* in 4bpp supports 8p wide tiles only, any tiles otherwise */ + info->pixmap.blit_x = (bpp == 4) ? (1 << (8 - 1)) : (~(u32)0); + info->pixmap.blit_y = ~(u32)0; + + offset_value = (info->var.xres_virtual * bpp) / 64; + fetch_value = ((info->var.xres * bpp) / 128) + 4; + + if (bpp == 4) + fetch_value = (info->var.xres / 8) + 8; /* + 0 is OK */ + + screen_size = info->var.yres_virtual * info->fix.line_length; + } else { + info->fix.ypanstep = 16; + info->fix.line_length = 0; + + info->flags |= FBINFO_MISC_TILEBLITTING; + info->tileops = &vt8623fb_tile_ops; + + /* supports 8x16 tiles only */ + info->pixmap.blit_x = 1 << (8 - 1); + info->pixmap.blit_y = 1 << (16 - 1); + + offset_value = info->var.xres_virtual / 16; + fetch_value = (info->var.xres / 8) + 8; + screen_size = (info->var.xres_virtual * info->var.yres_virtual) / 64; + } + + info->var.xoffset = 0; + info->var.yoffset = 0; + info->var.activate = FB_ACTIVATE_NOW; + + /* Unlock registers */ + svga_wseq_mask(par->state.vgabase, 0x10, 0x01, 0x01); + svga_wcrt_mask(par->state.vgabase, 0x11, 0x00, 0x80); + svga_wcrt_mask(par->state.vgabase, 0x47, 0x00, 0x01); + + /* Device, screen and sync off */ + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + svga_wcrt_mask(par->state.vgabase, 0x36, 0x30, 0x30); + svga_wcrt_mask(par->state.vgabase, 0x17, 0x00, 0x80); + + /* Set default values */ + svga_set_default_gfx_regs(par->state.vgabase); + svga_set_default_atc_regs(par->state.vgabase); + svga_set_default_seq_regs(par->state.vgabase); + svga_set_default_crt_regs(par->state.vgabase); + svga_wcrt_multi(par->state.vgabase, vt8623_line_compare_regs, 0xFFFFFFFF); + svga_wcrt_multi(par->state.vgabase, vt8623_start_address_regs, 0); + + svga_wcrt_multi(par->state.vgabase, vt8623_offset_regs, offset_value); + svga_wseq_multi(par->state.vgabase, vt8623_fetch_count_regs, fetch_value); + + /* Clear H/V Skew */ + svga_wcrt_mask(par->state.vgabase, 0x03, 0x00, 0x60); + svga_wcrt_mask(par->state.vgabase, 0x05, 0x00, 0x60); + + if (info->var.vmode & FB_VMODE_DOUBLE) + svga_wcrt_mask(par->state.vgabase, 0x09, 0x80, 0x80); + else + svga_wcrt_mask(par->state.vgabase, 0x09, 0x00, 0x80); + + svga_wseq_mask(par->state.vgabase, 0x1E, 0xF0, 0xF0); // DI/DVP bus + svga_wseq_mask(par->state.vgabase, 0x2A, 0x0F, 0x0F); // DI/DVP bus + svga_wseq_mask(par->state.vgabase, 0x16, 0x08, 0xBF); // FIFO read threshold + vga_wseq(par->state.vgabase, 0x17, 0x1F); // FIFO depth + vga_wseq(par->state.vgabase, 0x18, 0x4E); + svga_wseq_mask(par->state.vgabase, 0x1A, 0x08, 0x08); // enable MMIO ? + + vga_wcrt(par->state.vgabase, 0x32, 0x00); + vga_wcrt(par->state.vgabase, 0x34, 0x00); + vga_wcrt(par->state.vgabase, 0x6A, 0x80); + vga_wcrt(par->state.vgabase, 0x6A, 0xC0); + + vga_wgfx(par->state.vgabase, 0x20, 0x00); + vga_wgfx(par->state.vgabase, 0x21, 0x00); + vga_wgfx(par->state.vgabase, 0x22, 0x00); + + /* Set SR15 according to number of bits per pixel */ + mode = svga_match_format(vt8623fb_formats, &(info->var), &(info->fix)); + switch (mode) { + case 0: + fb_dbg(info, "text mode\n"); + svga_set_textmode_vga_regs(par->state.vgabase); + svga_wseq_mask(par->state.vgabase, 0x15, 0x00, 0xFE); + svga_wcrt_mask(par->state.vgabase, 0x11, 0x60, 0x70); + break; + case 1: + fb_dbg(info, "4 bit pseudocolor\n"); + vga_wgfx(par->state.vgabase, VGA_GFX_MODE, 0x40); + svga_wseq_mask(par->state.vgabase, 0x15, 0x20, 0xFE); + svga_wcrt_mask(par->state.vgabase, 0x11, 0x00, 0x70); + break; + case 2: + fb_dbg(info, "4 bit pseudocolor, planar\n"); + svga_wseq_mask(par->state.vgabase, 0x15, 0x00, 0xFE); + svga_wcrt_mask(par->state.vgabase, 0x11, 0x00, 0x70); + break; + case 3: + fb_dbg(info, "8 bit pseudocolor\n"); + svga_wseq_mask(par->state.vgabase, 0x15, 0x22, 0xFE); + break; + case 4: + fb_dbg(info, "5/6/5 truecolor\n"); + svga_wseq_mask(par->state.vgabase, 0x15, 0xB6, 0xFE); + break; + case 5: + fb_dbg(info, "8/8/8 truecolor\n"); + svga_wseq_mask(par->state.vgabase, 0x15, 0xAE, 0xFE); + break; + default: + printk(KERN_ERR "vt8623fb: unsupported mode - bug\n"); + return (-EINVAL); + } + + vt8623_set_pixclock(info, info->var.pixclock); + svga_set_timings(par->state.vgabase, &vt8623_timing_regs, &(info->var), 1, 1, + (info->var.vmode & FB_VMODE_DOUBLE) ? 2 : 1, 1, + 1, info->node); + + memset_io(info->screen_base, 0x00, screen_size); + + /* Device and screen back on */ + svga_wcrt_mask(par->state.vgabase, 0x17, 0x80, 0x80); + svga_wcrt_mask(par->state.vgabase, 0x36, 0x00, 0x30); + svga_wseq_mask(par->state.vgabase, 0x01, 0x00, 0x20); + + return 0; +} + + +static int vt8623fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *fb) +{ + switch (fb->var.bits_per_pixel) { + case 0: + case 4: + if (regno >= 16) + return -EINVAL; + + outb(0x0F, VGA_PEL_MSK); + outb(regno, VGA_PEL_IW); + outb(red >> 10, VGA_PEL_D); + outb(green >> 10, VGA_PEL_D); + outb(blue >> 10, VGA_PEL_D); + break; + case 8: + if (regno >= 256) + return -EINVAL; + + outb(0xFF, VGA_PEL_MSK); + outb(regno, VGA_PEL_IW); + outb(red >> 10, VGA_PEL_D); + outb(green >> 10, VGA_PEL_D); + outb(blue >> 10, VGA_PEL_D); + break; + case 16: + if (regno >= 16) + return 0; + + if (fb->var.green.length == 5) + ((u32*)fb->pseudo_palette)[regno] = ((red & 0xF800) >> 1) | + ((green & 0xF800) >> 6) | ((blue & 0xF800) >> 11); + else if (fb->var.green.length == 6) + ((u32*)fb->pseudo_palette)[regno] = (red & 0xF800) | + ((green & 0xFC00) >> 5) | ((blue & 0xF800) >> 11); + else + return -EINVAL; + break; + case 24: + case 32: + if (regno >= 16) + return 0; + + /* ((transp & 0xFF00) << 16) */ + ((u32*)fb->pseudo_palette)[regno] = ((red & 0xFF00) << 8) | + (green & 0xFF00) | ((blue & 0xFF00) >> 8); + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int vt8623fb_blank(int blank_mode, struct fb_info *info) +{ + struct vt8623fb_info *par = info->par; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + fb_dbg(info, "unblank\n"); + svga_wcrt_mask(par->state.vgabase, 0x36, 0x00, 0x30); + svga_wseq_mask(par->state.vgabase, 0x01, 0x00, 0x20); + break; + case FB_BLANK_NORMAL: + fb_dbg(info, "blank\n"); + svga_wcrt_mask(par->state.vgabase, 0x36, 0x00, 0x30); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + case FB_BLANK_HSYNC_SUSPEND: + fb_dbg(info, "DPMS standby (hsync off)\n"); + svga_wcrt_mask(par->state.vgabase, 0x36, 0x10, 0x30); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + case FB_BLANK_VSYNC_SUSPEND: + fb_dbg(info, "DPMS suspend (vsync off)\n"); + svga_wcrt_mask(par->state.vgabase, 0x36, 0x20, 0x30); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + case FB_BLANK_POWERDOWN: + fb_dbg(info, "DPMS off (no sync)\n"); + svga_wcrt_mask(par->state.vgabase, 0x36, 0x30, 0x30); + svga_wseq_mask(par->state.vgabase, 0x01, 0x20, 0x20); + break; + } + + return 0; +} + + +static int vt8623fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct vt8623fb_info *par = info->par; + unsigned int offset; + + /* Calculate the offset */ + if (info->var.bits_per_pixel == 0) { + offset = (var->yoffset / 16) * info->var.xres_virtual + + var->xoffset; + offset = offset >> 3; + } else { + offset = (var->yoffset * info->fix.line_length) + + (var->xoffset * info->var.bits_per_pixel / 8); + offset = offset >> ((info->var.bits_per_pixel == 4) ? 2 : 1); + } + + /* Set the offset */ + svga_wcrt_multi(par->state.vgabase, vt8623_start_address_regs, offset); + + return 0; +} + + +/* ------------------------------------------------------------------------- */ + + +/* Frame buffer operations */ + +static struct fb_ops vt8623fb_ops = { + .owner = THIS_MODULE, + .fb_open = vt8623fb_open, + .fb_release = vt8623fb_release, + .fb_check_var = vt8623fb_check_var, + .fb_set_par = vt8623fb_set_par, + .fb_setcolreg = vt8623fb_setcolreg, + .fb_blank = vt8623fb_blank, + .fb_pan_display = vt8623fb_pan_display, + .fb_fillrect = vt8623fb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = vt8623fb_imageblit, + .fb_get_caps = svga_get_caps, +}; + + +/* PCI probe */ + +static int vt8623_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct pci_bus_region bus_reg; + struct resource vga_res; + struct fb_info *info; + struct vt8623fb_info *par; + unsigned int memsize1, memsize2; + int rc; + + /* Ignore secondary VGA device because there is no VGA arbitration */ + if (! svga_primary_device(dev)) { + dev_info(&(dev->dev), "ignoring secondary device\n"); + return -ENODEV; + } + + /* Allocate and fill driver data structure */ + info = framebuffer_alloc(sizeof(struct vt8623fb_info), &(dev->dev)); + if (! info) { + dev_err(&(dev->dev), "cannot allocate memory\n"); + return -ENOMEM; + } + + par = info->par; + mutex_init(&par->open_lock); + + info->flags = FBINFO_PARTIAL_PAN_OK | FBINFO_HWACCEL_YPAN; + info->fbops = &vt8623fb_ops; + + /* Prepare PCI device */ + + rc = pci_enable_device(dev); + if (rc < 0) { + dev_err(info->device, "cannot enable PCI device\n"); + goto err_enable_device; + } + + rc = pci_request_regions(dev, "vt8623fb"); + if (rc < 0) { + dev_err(info->device, "cannot reserve framebuffer region\n"); + goto err_request_regions; + } + + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = pci_resource_len(dev, 0); + info->fix.mmio_start = pci_resource_start(dev, 1); + info->fix.mmio_len = pci_resource_len(dev, 1); + + /* Map physical IO memory address into kernel space */ + info->screen_base = pci_iomap(dev, 0, 0); + if (! info->screen_base) { + rc = -ENOMEM; + dev_err(info->device, "iomap for framebuffer failed\n"); + goto err_iomap_1; + } + + par->mmio_base = pci_iomap(dev, 1, 0); + if (! par->mmio_base) { + rc = -ENOMEM; + dev_err(info->device, "iomap for MMIO failed\n"); + goto err_iomap_2; + } + + bus_reg.start = 0; + bus_reg.end = 64 * 1024; + + vga_res.flags = IORESOURCE_IO; + + pcibios_bus_to_resource(dev->bus, &vga_res, &bus_reg); + + par->state.vgabase = (void __iomem *) vga_res.start; + + /* Find how many physical memory there is on card */ + memsize1 = (vga_rseq(par->state.vgabase, 0x34) + 1) >> 1; + memsize2 = vga_rseq(par->state.vgabase, 0x39) << 2; + + if ((16 <= memsize1) && (memsize1 <= 64) && (memsize1 == memsize2)) + info->screen_size = memsize1 << 20; + else { + dev_err(info->device, "memory size detection failed (%x %x), suppose 16 MB\n", memsize1, memsize2); + info->screen_size = 16 << 20; + } + + info->fix.smem_len = info->screen_size; + strcpy(info->fix.id, "VIA VT8623"); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.ypanstep = 0; + info->fix.accel = FB_ACCEL_NONE; + info->pseudo_palette = (void*)par->pseudo_palette; + + /* Prepare startup mode */ + + kparam_block_sysfs_write(mode_option); + rc = fb_find_mode(&(info->var), info, mode_option, NULL, 0, NULL, 8); + kparam_unblock_sysfs_write(mode_option); + if (! ((rc == 1) || (rc == 2))) { + rc = -EINVAL; + dev_err(info->device, "mode %s not found\n", mode_option); + goto err_find_mode; + } + + rc = fb_alloc_cmap(&info->cmap, 256, 0); + if (rc < 0) { + dev_err(info->device, "cannot allocate colormap\n"); + goto err_alloc_cmap; + } + + rc = register_framebuffer(info); + if (rc < 0) { + dev_err(info->device, "cannot register framebuffer\n"); + goto err_reg_fb; + } + + fb_info(info, "%s on %s, %d MB RAM\n", + info->fix.id, pci_name(dev), info->fix.smem_len >> 20); + + /* Record a reference to the driver data */ + pci_set_drvdata(dev, info); + +#ifdef CONFIG_MTRR + if (mtrr) { + par->mtrr_reg = -1; + par->mtrr_reg = mtrr_add(info->fix.smem_start, info->fix.smem_len, MTRR_TYPE_WRCOMB, 1); + } +#endif + + return 0; + + /* Error handling */ +err_reg_fb: + fb_dealloc_cmap(&info->cmap); +err_alloc_cmap: +err_find_mode: + pci_iounmap(dev, par->mmio_base); +err_iomap_2: + pci_iounmap(dev, info->screen_base); +err_iomap_1: + pci_release_regions(dev); +err_request_regions: +/* pci_disable_device(dev); */ +err_enable_device: + framebuffer_release(info); + return rc; +} + +/* PCI remove */ + +static void vt8623_pci_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + + if (info) { + struct vt8623fb_info *par = info->par; + +#ifdef CONFIG_MTRR + if (par->mtrr_reg >= 0) { + mtrr_del(par->mtrr_reg, 0, 0); + par->mtrr_reg = -1; + } +#endif + + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + + pci_iounmap(dev, info->screen_base); + pci_iounmap(dev, par->mmio_base); + pci_release_regions(dev); +/* pci_disable_device(dev); */ + + framebuffer_release(info); + } +} + + +#ifdef CONFIG_PM +/* PCI suspend */ + +static int vt8623_pci_suspend(struct pci_dev* dev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct vt8623fb_info *par = info->par; + + dev_info(info->device, "suspend\n"); + + console_lock(); + mutex_lock(&(par->open_lock)); + + if ((state.event == PM_EVENT_FREEZE) || (par->ref_count == 0)) { + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; + } + + fb_set_suspend(info, 1); + + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, state)); + + mutex_unlock(&(par->open_lock)); + console_unlock(); + + return 0; +} + + +/* PCI resume */ + +static int vt8623_pci_resume(struct pci_dev* dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct vt8623fb_info *par = info->par; + + dev_info(info->device, "resume\n"); + + console_lock(); + mutex_lock(&(par->open_lock)); + + if (par->ref_count == 0) + goto fail; + + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + + if (pci_enable_device(dev)) + goto fail; + + pci_set_master(dev); + + vt8623fb_set_par(info); + fb_set_suspend(info, 0); + +fail: + mutex_unlock(&(par->open_lock)); + console_unlock(); + + return 0; +} +#else +#define vt8623_pci_suspend NULL +#define vt8623_pci_resume NULL +#endif /* CONFIG_PM */ + +/* List of boards that we are trying to support */ + +static struct pci_device_id vt8623_devices[] = { + {PCI_DEVICE(PCI_VENDOR_ID_VIA, 0x3122)}, + {0, 0, 0, 0, 0, 0, 0} +}; + +MODULE_DEVICE_TABLE(pci, vt8623_devices); + +static struct pci_driver vt8623fb_pci_driver = { + .name = "vt8623fb", + .id_table = vt8623_devices, + .probe = vt8623_pci_probe, + .remove = vt8623_pci_remove, + .suspend = vt8623_pci_suspend, + .resume = vt8623_pci_resume, +}; + +/* Cleanup */ + +static void __exit vt8623fb_cleanup(void) +{ + pr_debug("vt8623fb: cleaning up\n"); + pci_unregister_driver(&vt8623fb_pci_driver); +} + +/* Driver Initialisation */ + +static int __init vt8623fb_init(void) +{ + +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("vt8623fb", &option)) + return -ENODEV; + + if (option && *option) + mode_option = option; +#endif + + pr_debug("vt8623fb: initializing\n"); + return pci_register_driver(&vt8623fb_pci_driver); +} + +/* ------------------------------------------------------------------------- */ + +/* Modularization */ + +module_init(vt8623fb_init); +module_exit(vt8623fb_cleanup); diff --git a/drivers/video/fbdev/w100fb.c b/drivers/video/fbdev/w100fb.c new file mode 100644 index 000000000000..10951c82f6ed --- /dev/null +++ b/drivers/video/fbdev/w100fb.c @@ -0,0 +1,1637 @@ +/* + * linux/drivers/video/w100fb.c + * + * Frame Buffer Device for ATI Imageon w100 (Wallaby) + * + * Copyright (C) 2002, ATI Corp. + * Copyright (C) 2004-2006 Richard Purdie + * Copyright (c) 2005 Ian Molton + * Copyright (c) 2006 Alberto Mardegan + * + * Rewritten for 2.6 by Richard Purdie <rpurdie@rpsys.net> + * + * Generic platform support by Ian Molton <spyro@f2s.com> + * and Richard Purdie <rpurdie@rpsys.net> + * + * w32xx support by Ian Molton + * + * Hardware acceleration support by Alberto Mardegan + * <mardy@users.sourceforge.net> + * + * 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. + * + */ + +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <video/w100fb.h> +#include "w100fb.h" + +/* + * Prototypes + */ +static void w100_suspend(u32 mode); +static void w100_vsync(void); +static void w100_hw_init(struct w100fb_par*); +static void w100_pwm_setup(struct w100fb_par*); +static void w100_init_clocks(struct w100fb_par*); +static void w100_setup_memory(struct w100fb_par*); +static void w100_init_lcd(struct w100fb_par*); +static void w100_set_dispregs(struct w100fb_par*); +static void w100_update_enable(void); +static void w100_update_disable(void); +static void calc_hsync(struct w100fb_par *par); +static void w100_init_graphic_engine(struct w100fb_par *par); +struct w100_pll_info *w100_get_xtal_table(unsigned int freq); + +/* Pseudo palette size */ +#define MAX_PALETTES 16 + +#define W100_SUSPEND_EXTMEM 0 +#define W100_SUSPEND_ALL 1 + +#define BITS_PER_PIXEL 16 + +/* Remapped addresses for base cfg, memmapped regs and the frame buffer itself */ +static void *remapped_base; +static void *remapped_regs; +static void *remapped_fbuf; + +#define REMAPPED_FB_LEN 0x15ffff + +/* This is the offset in the w100's address space we map the current + framebuffer memory to. We use the position of external memory as + we can remap internal memory to there if external isn't present. */ +#define W100_FB_BASE MEM_EXT_BASE_VALUE + + +/* + * Sysfs functions + */ +static ssize_t flip_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct w100fb_par *par=info->par; + + return sprintf(buf, "%d\n",par->flip); +} + +static ssize_t flip_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int flip; + struct fb_info *info = dev_get_drvdata(dev); + struct w100fb_par *par=info->par; + + flip = simple_strtoul(buf, NULL, 10); + + if (flip > 0) + par->flip = 1; + else + par->flip = 0; + + w100_update_disable(); + w100_set_dispregs(par); + w100_update_enable(); + + calc_hsync(par); + + return count; +} + +static DEVICE_ATTR(flip, 0644, flip_show, flip_store); + +static ssize_t w100fb_reg_read(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long regs, param; + regs = simple_strtoul(buf, NULL, 16); + param = readl(remapped_regs + regs); + printk("Read Register 0x%08lX: 0x%08lX\n", regs, param); + return count; +} + +static DEVICE_ATTR(reg_read, 0200, NULL, w100fb_reg_read); + +static ssize_t w100fb_reg_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long regs, param; + sscanf(buf, "%lx %lx", ®s, ¶m); + + if (regs <= 0x2000) { + printk("Write Register 0x%08lX: 0x%08lX\n", regs, param); + writel(param, remapped_regs + regs); + } + + return count; +} + +static DEVICE_ATTR(reg_write, 0200, NULL, w100fb_reg_write); + + +static ssize_t fastpllclk_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct w100fb_par *par=info->par; + + return sprintf(buf, "%d\n",par->fastpll_mode); +} + +static ssize_t fastpllclk_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct w100fb_par *par=info->par; + + if (simple_strtoul(buf, NULL, 10) > 0) { + par->fastpll_mode=1; + printk("w100fb: Using fast system clock (if possible)\n"); + } else { + par->fastpll_mode=0; + printk("w100fb: Using normal system clock\n"); + } + + w100_init_clocks(par); + calc_hsync(par); + + return count; +} + +static DEVICE_ATTR(fastpllclk, 0644, fastpllclk_show, fastpllclk_store); + +/* + * Some touchscreens need hsync information from the video driver to + * function correctly. We export it here. + */ +unsigned long w100fb_get_hsynclen(struct device *dev) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct w100fb_par *par=info->par; + + /* If display is blanked/suspended, hsync isn't active */ + if (par->blanked) + return 0; + else + return par->hsync_len; +} +EXPORT_SYMBOL(w100fb_get_hsynclen); + +static void w100fb_clear_screen(struct w100fb_par *par) +{ + memset_io(remapped_fbuf + (W100_FB_BASE-MEM_WINDOW_BASE), 0, (par->xres * par->yres * BITS_PER_PIXEL/8)); +} + + +/* + * Set a palette value from rgb components + */ +static int w100fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *info) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + 7471 * blue) >> 16; + + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < MAX_PALETTES) { + u32 *pal = info->pseudo_palette; + + val = (red & 0xf800) | ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11); + pal[regno] = val; + ret = 0; + } + return ret; +} + + +/* + * Blank the display based on value in blank_mode + */ +static int w100fb_blank(int blank_mode, struct fb_info *info) +{ + struct w100fb_par *par = info->par; + struct w100_tg_info *tg = par->mach->tg; + + switch(blank_mode) { + + case FB_BLANK_NORMAL: /* Normal blanking */ + case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ + case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ + case FB_BLANK_POWERDOWN: /* Poweroff */ + if (par->blanked == 0) { + if(tg && tg->suspend) + tg->suspend(par); + par->blanked = 1; + } + break; + + case FB_BLANK_UNBLANK: /* Unblanking */ + if (par->blanked != 0) { + if(tg && tg->resume) + tg->resume(par); + par->blanked = 0; + } + break; + } + return 0; +} + + +static void w100_fifo_wait(int entries) +{ + union rbbm_status_u status; + int i; + + for (i = 0; i < 2000000; i++) { + status.val = readl(remapped_regs + mmRBBM_STATUS); + if (status.f.cmdfifo_avail >= entries) + return; + udelay(1); + } + printk(KERN_ERR "w100fb: FIFO Timeout!\n"); +} + + +static int w100fb_sync(struct fb_info *info) +{ + union rbbm_status_u status; + int i; + + for (i = 0; i < 2000000; i++) { + status.val = readl(remapped_regs + mmRBBM_STATUS); + if (!status.f.gui_active) + return 0; + udelay(1); + } + printk(KERN_ERR "w100fb: Graphic engine timeout!\n"); + return -EBUSY; +} + + +static void w100_init_graphic_engine(struct w100fb_par *par) +{ + union dp_gui_master_cntl_u gmc; + union dp_mix_u dp_mix; + union dp_datatype_u dp_datatype; + union dp_cntl_u dp_cntl; + + w100_fifo_wait(4); + writel(W100_FB_BASE, remapped_regs + mmDST_OFFSET); + writel(par->xres, remapped_regs + mmDST_PITCH); + writel(W100_FB_BASE, remapped_regs + mmSRC_OFFSET); + writel(par->xres, remapped_regs + mmSRC_PITCH); + + w100_fifo_wait(3); + writel(0, remapped_regs + mmSC_TOP_LEFT); + writel((par->yres << 16) | par->xres, remapped_regs + mmSC_BOTTOM_RIGHT); + writel(0x1fff1fff, remapped_regs + mmSRC_SC_BOTTOM_RIGHT); + + w100_fifo_wait(4); + dp_cntl.val = 0; + dp_cntl.f.dst_x_dir = 1; + dp_cntl.f.dst_y_dir = 1; + dp_cntl.f.src_x_dir = 1; + dp_cntl.f.src_y_dir = 1; + dp_cntl.f.dst_major_x = 1; + dp_cntl.f.src_major_x = 1; + writel(dp_cntl.val, remapped_regs + mmDP_CNTL); + + gmc.val = 0; + gmc.f.gmc_src_pitch_offset_cntl = 1; + gmc.f.gmc_dst_pitch_offset_cntl = 1; + gmc.f.gmc_src_clipping = 1; + gmc.f.gmc_dst_clipping = 1; + gmc.f.gmc_brush_datatype = GMC_BRUSH_NONE; + gmc.f.gmc_dst_datatype = 3; /* from DstType_16Bpp_444 */ + gmc.f.gmc_src_datatype = SRC_DATATYPE_EQU_DST; + gmc.f.gmc_byte_pix_order = 1; + gmc.f.gmc_default_sel = 0; + gmc.f.gmc_rop3 = ROP3_SRCCOPY; + gmc.f.gmc_dp_src_source = DP_SRC_MEM_RECTANGULAR; + gmc.f.gmc_clr_cmp_fcn_dis = 1; + gmc.f.gmc_wr_msk_dis = 1; + gmc.f.gmc_dp_op = DP_OP_ROP; + writel(gmc.val, remapped_regs + mmDP_GUI_MASTER_CNTL); + + dp_datatype.val = dp_mix.val = 0; + dp_datatype.f.dp_dst_datatype = gmc.f.gmc_dst_datatype; + dp_datatype.f.dp_brush_datatype = gmc.f.gmc_brush_datatype; + dp_datatype.f.dp_src2_type = 0; + dp_datatype.f.dp_src2_datatype = gmc.f.gmc_src_datatype; + dp_datatype.f.dp_src_datatype = gmc.f.gmc_src_datatype; + dp_datatype.f.dp_byte_pix_order = gmc.f.gmc_byte_pix_order; + writel(dp_datatype.val, remapped_regs + mmDP_DATATYPE); + + dp_mix.f.dp_src_source = gmc.f.gmc_dp_src_source; + dp_mix.f.dp_src2_source = 1; + dp_mix.f.dp_rop3 = gmc.f.gmc_rop3; + dp_mix.f.dp_op = gmc.f.gmc_dp_op; + writel(dp_mix.val, remapped_regs + mmDP_MIX); +} + + +static void w100fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + union dp_gui_master_cntl_u gmc; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_fillrect(info, rect); + return; + } + + gmc.val = readl(remapped_regs + mmDP_GUI_MASTER_CNTL); + gmc.f.gmc_rop3 = ROP3_PATCOPY; + gmc.f.gmc_brush_datatype = GMC_BRUSH_SOLID_COLOR; + w100_fifo_wait(2); + writel(gmc.val, remapped_regs + mmDP_GUI_MASTER_CNTL); + writel(rect->color, remapped_regs + mmDP_BRUSH_FRGD_CLR); + + w100_fifo_wait(2); + writel((rect->dy << 16) | (rect->dx & 0xffff), remapped_regs + mmDST_Y_X); + writel((rect->width << 16) | (rect->height & 0xffff), + remapped_regs + mmDST_WIDTH_HEIGHT); +} + + +static void w100fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy; + u32 h = area->height, w = area->width; + union dp_gui_master_cntl_u gmc; + + if (info->state != FBINFO_STATE_RUNNING) + return; + if (info->flags & FBINFO_HWACCEL_DISABLED) { + cfb_copyarea(info, area); + return; + } + + gmc.val = readl(remapped_regs + mmDP_GUI_MASTER_CNTL); + gmc.f.gmc_rop3 = ROP3_SRCCOPY; + gmc.f.gmc_brush_datatype = GMC_BRUSH_NONE; + w100_fifo_wait(1); + writel(gmc.val, remapped_regs + mmDP_GUI_MASTER_CNTL); + + w100_fifo_wait(3); + writel((sy << 16) | (sx & 0xffff), remapped_regs + mmSRC_Y_X); + writel((dy << 16) | (dx & 0xffff), remapped_regs + mmDST_Y_X); + writel((w << 16) | (h & 0xffff), remapped_regs + mmDST_WIDTH_HEIGHT); +} + + +/* + * Change the resolution by calling the appropriate hardware functions + */ +static void w100fb_activate_var(struct w100fb_par *par) +{ + struct w100_tg_info *tg = par->mach->tg; + + w100_pwm_setup(par); + w100_setup_memory(par); + w100_init_clocks(par); + w100fb_clear_screen(par); + w100_vsync(); + + w100_update_disable(); + w100_init_lcd(par); + w100_set_dispregs(par); + w100_update_enable(); + w100_init_graphic_engine(par); + + calc_hsync(par); + + if (!par->blanked && tg && tg->change) + tg->change(par); +} + + +/* Select the smallest mode that allows the desired resolution to be + * displayed. If desired, the x and y parameters can be rounded up to + * match the selected mode. + */ +static struct w100_mode *w100fb_get_mode(struct w100fb_par *par, unsigned int *x, unsigned int *y, int saveval) +{ + struct w100_mode *mode = NULL; + struct w100_mode *modelist = par->mach->modelist; + unsigned int best_x = 0xffffffff, best_y = 0xffffffff; + unsigned int i; + + for (i = 0 ; i < par->mach->num_modes ; i++) { + if (modelist[i].xres >= *x && modelist[i].yres >= *y && + modelist[i].xres < best_x && modelist[i].yres < best_y) { + best_x = modelist[i].xres; + best_y = modelist[i].yres; + mode = &modelist[i]; + } else if(modelist[i].xres >= *y && modelist[i].yres >= *x && + modelist[i].xres < best_y && modelist[i].yres < best_x) { + best_x = modelist[i].yres; + best_y = modelist[i].xres; + mode = &modelist[i]; + } + } + + if (mode && saveval) { + *x = best_x; + *y = best_y; + } + + return mode; +} + + +/* + * w100fb_check_var(): + * Get the video params out of 'var'. If a value doesn't fit, round it up, + * if it's too big, return -EINVAL. + */ +static int w100fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct w100fb_par *par=info->par; + + if(!w100fb_get_mode(par, &var->xres, &var->yres, 1)) + return -EINVAL; + + if (par->mach->mem && ((var->xres*var->yres*BITS_PER_PIXEL/8) > (par->mach->mem->size+1))) + return -EINVAL; + + if (!par->mach->mem && ((var->xres*var->yres*BITS_PER_PIXEL/8) > (MEM_INT_SIZE+1))) + return -EINVAL; + + var->xres_virtual = max(var->xres_virtual, var->xres); + var->yres_virtual = max(var->yres_virtual, var->yres); + + if (var->bits_per_pixel > BITS_PER_PIXEL) + return -EINVAL; + else + var->bits_per_pixel = BITS_PER_PIXEL; + + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = var->transp.length = 0; + + var->nonstd = 0; + var->height = -1; + var->width = -1; + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + var->pixclock = 0x04; /* 171521; */ + + return 0; +} + + +/* + * w100fb_set_par(): + * Set the user defined part of the display for the specified console + * by looking at the values in info.var + */ +static int w100fb_set_par(struct fb_info *info) +{ + struct w100fb_par *par=info->par; + + if (par->xres != info->var.xres || par->yres != info->var.yres) { + par->xres = info->var.xres; + par->yres = info->var.yres; + par->mode = w100fb_get_mode(par, &par->xres, &par->yres, 0); + + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.line_length = par->xres * BITS_PER_PIXEL / 8; + + mutex_lock(&info->mm_lock); + if ((par->xres*par->yres*BITS_PER_PIXEL/8) > (MEM_INT_SIZE+1)) { + par->extmem_active = 1; + info->fix.smem_len = par->mach->mem->size+1; + } else { + par->extmem_active = 0; + info->fix.smem_len = MEM_INT_SIZE+1; + } + mutex_unlock(&info->mm_lock); + + w100fb_activate_var(par); + } + return 0; +} + + +/* + * Frame buffer operations + */ +static struct fb_ops w100fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = w100fb_check_var, + .fb_set_par = w100fb_set_par, + .fb_setcolreg = w100fb_setcolreg, + .fb_blank = w100fb_blank, + .fb_fillrect = w100fb_fillrect, + .fb_copyarea = w100fb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_sync = w100fb_sync, +}; + +#ifdef CONFIG_PM +static void w100fb_save_vidmem(struct w100fb_par *par) +{ + int memsize; + + if (par->extmem_active) { + memsize=par->mach->mem->size; + par->saved_extmem = vmalloc(memsize); + if (par->saved_extmem) + memcpy_fromio(par->saved_extmem, remapped_fbuf + (W100_FB_BASE-MEM_WINDOW_BASE), memsize); + } + memsize=MEM_INT_SIZE; + par->saved_intmem = vmalloc(memsize); + if (par->saved_intmem && par->extmem_active) + memcpy_fromio(par->saved_intmem, remapped_fbuf + (W100_FB_BASE-MEM_INT_BASE_VALUE), memsize); + else if (par->saved_intmem) + memcpy_fromio(par->saved_intmem, remapped_fbuf + (W100_FB_BASE-MEM_WINDOW_BASE), memsize); +} + +static void w100fb_restore_vidmem(struct w100fb_par *par) +{ + int memsize; + + if (par->extmem_active && par->saved_extmem) { + memsize=par->mach->mem->size; + memcpy_toio(remapped_fbuf + (W100_FB_BASE-MEM_WINDOW_BASE), par->saved_extmem, memsize); + vfree(par->saved_extmem); + } + if (par->saved_intmem) { + memsize=MEM_INT_SIZE; + if (par->extmem_active) + memcpy_toio(remapped_fbuf + (W100_FB_BASE-MEM_INT_BASE_VALUE), par->saved_intmem, memsize); + else + memcpy_toio(remapped_fbuf + (W100_FB_BASE-MEM_WINDOW_BASE), par->saved_intmem, memsize); + vfree(par->saved_intmem); + } +} + +static int w100fb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct w100fb_par *par=info->par; + struct w100_tg_info *tg = par->mach->tg; + + w100fb_save_vidmem(par); + if(tg && tg->suspend) + tg->suspend(par); + w100_suspend(W100_SUSPEND_ALL); + par->blanked = 1; + + return 0; +} + +static int w100fb_resume(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + struct w100fb_par *par=info->par; + struct w100_tg_info *tg = par->mach->tg; + + w100_hw_init(par); + w100fb_activate_var(par); + w100fb_restore_vidmem(par); + if(tg && tg->resume) + tg->resume(par); + par->blanked = 0; + + return 0; +} +#else +#define w100fb_suspend NULL +#define w100fb_resume NULL +#endif + + +int w100fb_probe(struct platform_device *pdev) +{ + int err = -EIO; + struct w100fb_mach_info *inf; + struct fb_info *info = NULL; + struct w100fb_par *par; + struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + unsigned int chip_id; + + if (!mem) + return -EINVAL; + + /* Remap the chip base address */ + remapped_base = ioremap_nocache(mem->start+W100_CFG_BASE, W100_CFG_LEN); + if (remapped_base == NULL) + goto out; + + /* Map the register space */ + remapped_regs = ioremap_nocache(mem->start+W100_REG_BASE, W100_REG_LEN); + if (remapped_regs == NULL) + goto out; + + /* Identify the chip */ + printk("Found "); + chip_id = readl(remapped_regs + mmCHIP_ID); + switch(chip_id) { + case CHIP_ID_W100: printk("w100"); break; + case CHIP_ID_W3200: printk("w3200"); break; + case CHIP_ID_W3220: printk("w3220"); break; + default: + printk("Unknown imageon chip ID\n"); + err = -ENODEV; + goto out; + } + printk(" at 0x%08lx.\n", (unsigned long) mem->start+W100_CFG_BASE); + + /* Remap the framebuffer */ + remapped_fbuf = ioremap_nocache(mem->start+MEM_WINDOW_BASE, MEM_WINDOW_SIZE); + if (remapped_fbuf == NULL) + goto out; + + info=framebuffer_alloc(sizeof(struct w100fb_par), &pdev->dev); + if (!info) { + err = -ENOMEM; + goto out; + } + + par = info->par; + platform_set_drvdata(pdev, info); + + inf = dev_get_platdata(&pdev->dev); + par->chip_id = chip_id; + par->mach = inf; + par->fastpll_mode = 0; + par->blanked = 0; + + par->pll_table=w100_get_xtal_table(inf->xtal_freq); + if (!par->pll_table) { + printk(KERN_ERR "No matching Xtal definition found\n"); + err = -EINVAL; + goto out; + } + + info->pseudo_palette = kmalloc(sizeof (u32) * MAX_PALETTES, GFP_KERNEL); + if (!info->pseudo_palette) { + err = -ENOMEM; + goto out; + } + + info->fbops = &w100fb_ops; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT; + info->node = -1; + info->screen_base = remapped_fbuf + (W100_FB_BASE-MEM_WINDOW_BASE); + info->screen_size = REMAPPED_FB_LEN; + + strcpy(info->fix.id, "w100fb"); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.type_aux = 0; + info->fix.accel = FB_ACCEL_NONE; + info->fix.smem_start = mem->start+W100_FB_BASE; + info->fix.mmio_start = mem->start+W100_REG_BASE; + info->fix.mmio_len = W100_REG_LEN; + + if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { + err = -ENOMEM; + goto out; + } + + par->mode = &inf->modelist[0]; + if(inf->init_mode & INIT_MODE_ROTATED) { + info->var.xres = par->mode->yres; + info->var.yres = par->mode->xres; + } + else { + info->var.xres = par->mode->xres; + info->var.yres = par->mode->yres; + } + + if(inf->init_mode &= INIT_MODE_FLIPPED) + par->flip = 1; + else + par->flip = 0; + + info->var.xres_virtual = info->var.xres; + info->var.yres_virtual = info->var.yres; + info->var.pixclock = 0x04; /* 171521; */ + info->var.sync = 0; + info->var.grayscale = 0; + info->var.xoffset = info->var.yoffset = 0; + info->var.accel_flags = 0; + info->var.activate = FB_ACTIVATE_NOW; + + w100_hw_init(par); + + if (w100fb_check_var(&info->var, info) < 0) { + err = -EINVAL; + goto out; + } + + if (register_framebuffer(info) < 0) { + err = -EINVAL; + goto out; + } + + err = device_create_file(&pdev->dev, &dev_attr_fastpllclk); + err |= device_create_file(&pdev->dev, &dev_attr_reg_read); + err |= device_create_file(&pdev->dev, &dev_attr_reg_write); + err |= device_create_file(&pdev->dev, &dev_attr_flip); + + if (err != 0) + fb_warn(info, "failed to register attributes (%d)\n", err); + + fb_info(info, "%s frame buffer device\n", info->fix.id); + return 0; +out: + if (info) { + fb_dealloc_cmap(&info->cmap); + kfree(info->pseudo_palette); + } + if (remapped_fbuf != NULL) + iounmap(remapped_fbuf); + if (remapped_regs != NULL) + iounmap(remapped_regs); + if (remapped_base != NULL) + iounmap(remapped_base); + if (info) + framebuffer_release(info); + return err; +} + + +static int w100fb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct w100fb_par *par=info->par; + + device_remove_file(&pdev->dev, &dev_attr_fastpllclk); + device_remove_file(&pdev->dev, &dev_attr_reg_read); + device_remove_file(&pdev->dev, &dev_attr_reg_write); + device_remove_file(&pdev->dev, &dev_attr_flip); + + unregister_framebuffer(info); + + vfree(par->saved_intmem); + vfree(par->saved_extmem); + kfree(info->pseudo_palette); + fb_dealloc_cmap(&info->cmap); + + iounmap(remapped_base); + iounmap(remapped_regs); + iounmap(remapped_fbuf); + + framebuffer_release(info); + + return 0; +} + + +/* ------------------- chipset specific functions -------------------------- */ + + +static void w100_soft_reset(void) +{ + u16 val = readw((u16 *) remapped_base + cfgSTATUS); + writew(val | 0x08, (u16 *) remapped_base + cfgSTATUS); + udelay(100); + writew(0x00, (u16 *) remapped_base + cfgSTATUS); + udelay(100); +} + +static void w100_update_disable(void) +{ + union disp_db_buf_cntl_wr_u disp_db_buf_wr_cntl; + + /* Prevent display updates */ + disp_db_buf_wr_cntl.f.db_buf_cntl = 0x1e; + disp_db_buf_wr_cntl.f.update_db_buf = 0; + disp_db_buf_wr_cntl.f.en_db_buf = 0; + writel((u32) (disp_db_buf_wr_cntl.val), remapped_regs + mmDISP_DB_BUF_CNTL); +} + +static void w100_update_enable(void) +{ + union disp_db_buf_cntl_wr_u disp_db_buf_wr_cntl; + + /* Enable display updates */ + disp_db_buf_wr_cntl.f.db_buf_cntl = 0x1e; + disp_db_buf_wr_cntl.f.update_db_buf = 1; + disp_db_buf_wr_cntl.f.en_db_buf = 1; + writel((u32) (disp_db_buf_wr_cntl.val), remapped_regs + mmDISP_DB_BUF_CNTL); +} + +unsigned long w100fb_gpio_read(int port) +{ + unsigned long value; + + if (port==W100_GPIO_PORT_A) + value = readl(remapped_regs + mmGPIO_DATA); + else + value = readl(remapped_regs + mmGPIO_DATA2); + + return value; +} + +void w100fb_gpio_write(int port, unsigned long value) +{ + if (port==W100_GPIO_PORT_A) + writel(value, remapped_regs + mmGPIO_DATA); + else + writel(value, remapped_regs + mmGPIO_DATA2); +} +EXPORT_SYMBOL(w100fb_gpio_read); +EXPORT_SYMBOL(w100fb_gpio_write); + +/* + * Initialization of critical w100 hardware + */ +static void w100_hw_init(struct w100fb_par *par) +{ + u32 temp32; + union cif_cntl_u cif_cntl; + union intf_cntl_u intf_cntl; + union cfgreg_base_u cfgreg_base; + union wrap_top_dir_u wrap_top_dir; + union cif_read_dbg_u cif_read_dbg; + union cpu_defaults_u cpu_default; + union cif_write_dbg_u cif_write_dbg; + union wrap_start_dir_u wrap_start_dir; + union cif_io_u cif_io; + struct w100_gpio_regs *gpio = par->mach->gpio; + + w100_soft_reset(); + + /* This is what the fpga_init code does on reset. May be wrong + but there is little info available */ + writel(0x31, remapped_regs + mmSCRATCH_UMSK); + for (temp32 = 0; temp32 < 10000; temp32++) + readl(remapped_regs + mmSCRATCH_UMSK); + writel(0x30, remapped_regs + mmSCRATCH_UMSK); + + /* Set up CIF */ + cif_io.val = defCIF_IO; + writel((u32)(cif_io.val), remapped_regs + mmCIF_IO); + + cif_write_dbg.val = readl(remapped_regs + mmCIF_WRITE_DBG); + cif_write_dbg.f.dis_packer_ful_during_rbbm_timeout = 0; + cif_write_dbg.f.en_dword_split_to_rbbm = 1; + cif_write_dbg.f.dis_timeout_during_rbbm = 1; + writel((u32) (cif_write_dbg.val), remapped_regs + mmCIF_WRITE_DBG); + + cif_read_dbg.val = readl(remapped_regs + mmCIF_READ_DBG); + cif_read_dbg.f.dis_rd_same_byte_to_trig_fetch = 1; + writel((u32) (cif_read_dbg.val), remapped_regs + mmCIF_READ_DBG); + + cif_cntl.val = readl(remapped_regs + mmCIF_CNTL); + cif_cntl.f.dis_system_bits = 1; + cif_cntl.f.dis_mr = 1; + cif_cntl.f.en_wait_to_compensate_dq_prop_dly = 0; + cif_cntl.f.intb_oe = 1; + cif_cntl.f.interrupt_active_high = 1; + writel((u32) (cif_cntl.val), remapped_regs + mmCIF_CNTL); + + /* Setup cfgINTF_CNTL and cfgCPU defaults */ + intf_cntl.val = defINTF_CNTL; + intf_cntl.f.ad_inc_a = 1; + intf_cntl.f.ad_inc_b = 1; + intf_cntl.f.rd_data_rdy_a = 0; + intf_cntl.f.rd_data_rdy_b = 0; + writeb((u8) (intf_cntl.val), remapped_base + cfgINTF_CNTL); + + cpu_default.val = defCPU_DEFAULTS; + cpu_default.f.access_ind_addr_a = 1; + cpu_default.f.access_ind_addr_b = 1; + cpu_default.f.access_scratch_reg = 1; + cpu_default.f.transition_size = 0; + writeb((u8) (cpu_default.val), remapped_base + cfgCPU_DEFAULTS); + + /* set up the apertures */ + writeb((u8) (W100_REG_BASE >> 16), remapped_base + cfgREG_BASE); + + cfgreg_base.val = defCFGREG_BASE; + cfgreg_base.f.cfgreg_base = W100_CFG_BASE; + writel((u32) (cfgreg_base.val), remapped_regs + mmCFGREG_BASE); + + wrap_start_dir.val = defWRAP_START_DIR; + wrap_start_dir.f.start_addr = WRAP_BUF_BASE_VALUE >> 1; + writel((u32) (wrap_start_dir.val), remapped_regs + mmWRAP_START_DIR); + + wrap_top_dir.val = defWRAP_TOP_DIR; + wrap_top_dir.f.top_addr = WRAP_BUF_TOP_VALUE >> 1; + writel((u32) (wrap_top_dir.val), remapped_regs + mmWRAP_TOP_DIR); + + writel((u32) 0x2440, remapped_regs + mmRBBM_CNTL); + + /* Set the hardware to 565 colour */ + temp32 = readl(remapped_regs + mmDISP_DEBUG2); + temp32 &= 0xff7fffff; + temp32 |= 0x00800000; + writel(temp32, remapped_regs + mmDISP_DEBUG2); + + /* Initialise the GPIO lines */ + if (gpio) { + writel(gpio->init_data1, remapped_regs + mmGPIO_DATA); + writel(gpio->init_data2, remapped_regs + mmGPIO_DATA2); + writel(gpio->gpio_dir1, remapped_regs + mmGPIO_CNTL1); + writel(gpio->gpio_oe1, remapped_regs + mmGPIO_CNTL2); + writel(gpio->gpio_dir2, remapped_regs + mmGPIO_CNTL3); + writel(gpio->gpio_oe2, remapped_regs + mmGPIO_CNTL4); + } +} + + +struct power_state { + union clk_pin_cntl_u clk_pin_cntl; + union pll_ref_fb_div_u pll_ref_fb_div; + union pll_cntl_u pll_cntl; + union sclk_cntl_u sclk_cntl; + union pclk_cntl_u pclk_cntl; + union pwrmgt_cntl_u pwrmgt_cntl; + int auto_mode; /* system clock auto changing? */ +}; + + +static struct power_state w100_pwr_state; + +/* The PLL Fout is determined by (XtalFreq/(M+1)) * ((N_int+1) + (N_fac/8)) */ + +/* 12.5MHz Crystal PLL Table */ +static struct w100_pll_info xtal_12500000[] = { + /*freq M N_int N_fac tfgoal lock_time */ + { 50, 0, 1, 0, 0xe0, 56}, /* 50.00 MHz */ + { 75, 0, 5, 0, 0xde, 37}, /* 75.00 MHz */ + {100, 0, 7, 0, 0xe0, 28}, /* 100.00 MHz */ + {125, 0, 9, 0, 0xe0, 22}, /* 125.00 MHz */ + {150, 0, 11, 0, 0xe0, 17}, /* 150.00 MHz */ + { 0, 0, 0, 0, 0, 0}, /* Terminator */ +}; + +/* 14.318MHz Crystal PLL Table */ +static struct w100_pll_info xtal_14318000[] = { + /*freq M N_int N_fac tfgoal lock_time */ + { 40, 4, 13, 0, 0xe0, 80}, /* tfgoal guessed */ + { 50, 1, 6, 0, 0xe0, 64}, /* 50.05 MHz */ + { 57, 2, 11, 0, 0xe0, 53}, /* tfgoal guessed */ + { 75, 0, 4, 3, 0xe0, 43}, /* 75.08 MHz */ + {100, 0, 6, 0, 0xe0, 32}, /* 100.10 MHz */ + { 0, 0, 0, 0, 0, 0}, +}; + +/* 16MHz Crystal PLL Table */ +static struct w100_pll_info xtal_16000000[] = { + /*freq M N_int N_fac tfgoal lock_time */ + { 72, 1, 8, 0, 0xe0, 48}, /* tfgoal guessed */ + { 80, 1, 9, 0, 0xe0, 13}, /* tfgoal guessed */ + { 95, 1, 10, 7, 0xe0, 38}, /* tfgoal guessed */ + { 96, 1, 11, 0, 0xe0, 36}, /* tfgoal guessed */ + { 0, 0, 0, 0, 0, 0}, +}; + +static struct pll_entries { + int xtal_freq; + struct w100_pll_info *pll_table; +} w100_pll_tables[] = { + { 12500000, &xtal_12500000[0] }, + { 14318000, &xtal_14318000[0] }, + { 16000000, &xtal_16000000[0] }, + { 0 }, +}; + +struct w100_pll_info *w100_get_xtal_table(unsigned int freq) +{ + struct pll_entries *pll_entry = w100_pll_tables; + + do { + if (freq == pll_entry->xtal_freq) + return pll_entry->pll_table; + pll_entry++; + } while (pll_entry->xtal_freq); + return 0; +} + + +static unsigned int w100_get_testcount(unsigned int testclk_sel) +{ + union clk_test_cntl_u clk_test_cntl; + + udelay(5); + + /* Select the test clock source and reset */ + clk_test_cntl.f.start_check_freq = 0x0; + clk_test_cntl.f.testclk_sel = testclk_sel; + clk_test_cntl.f.tstcount_rst = 0x1; /* set reset */ + writel((u32) (clk_test_cntl.val), remapped_regs + mmCLK_TEST_CNTL); + + clk_test_cntl.f.tstcount_rst = 0x0; /* clear reset */ + writel((u32) (clk_test_cntl.val), remapped_regs + mmCLK_TEST_CNTL); + + /* Run clock test */ + clk_test_cntl.f.start_check_freq = 0x1; + writel((u32) (clk_test_cntl.val), remapped_regs + mmCLK_TEST_CNTL); + + /* Give the test time to complete */ + udelay(20); + + /* Return the result */ + clk_test_cntl.val = readl(remapped_regs + mmCLK_TEST_CNTL); + clk_test_cntl.f.start_check_freq = 0x0; + writel((u32) (clk_test_cntl.val), remapped_regs + mmCLK_TEST_CNTL); + + return clk_test_cntl.f.test_count; +} + + +static int w100_pll_adjust(struct w100_pll_info *pll) +{ + unsigned int tf80; + unsigned int tf20; + + /* Initial Settings */ + w100_pwr_state.pll_cntl.f.pll_pwdn = 0x0; /* power down */ + w100_pwr_state.pll_cntl.f.pll_reset = 0x0; /* not reset */ + w100_pwr_state.pll_cntl.f.pll_tcpoff = 0x1; /* Hi-Z */ + w100_pwr_state.pll_cntl.f.pll_pvg = 0x0; /* VCO gain = 0 */ + w100_pwr_state.pll_cntl.f.pll_vcofr = 0x0; /* VCO frequency range control = off */ + w100_pwr_state.pll_cntl.f.pll_ioffset = 0x0; /* current offset inside VCO = 0 */ + w100_pwr_state.pll_cntl.f.pll_ring_off = 0x0; + + /* Wai Ming 80 percent of VDD 1.3V gives 1.04V, minimum operating voltage is 1.08V + * therefore, commented out the following lines + * tf80 meant tf100 + */ + do { + /* set VCO input = 0.8 * VDD */ + w100_pwr_state.pll_cntl.f.pll_dactal = 0xd; + writel((u32) (w100_pwr_state.pll_cntl.val), remapped_regs + mmPLL_CNTL); + + tf80 = w100_get_testcount(TESTCLK_SRC_PLL); + if (tf80 >= (pll->tfgoal)) { + /* set VCO input = 0.2 * VDD */ + w100_pwr_state.pll_cntl.f.pll_dactal = 0x7; + writel((u32) (w100_pwr_state.pll_cntl.val), remapped_regs + mmPLL_CNTL); + + tf20 = w100_get_testcount(TESTCLK_SRC_PLL); + if (tf20 <= (pll->tfgoal)) + return 1; /* Success */ + + if ((w100_pwr_state.pll_cntl.f.pll_vcofr == 0x0) && + ((w100_pwr_state.pll_cntl.f.pll_pvg == 0x7) || + (w100_pwr_state.pll_cntl.f.pll_ioffset == 0x0))) { + /* slow VCO config */ + w100_pwr_state.pll_cntl.f.pll_vcofr = 0x1; + w100_pwr_state.pll_cntl.f.pll_pvg = 0x0; + w100_pwr_state.pll_cntl.f.pll_ioffset = 0x0; + continue; + } + } + if ((w100_pwr_state.pll_cntl.f.pll_ioffset) < 0x3) { + w100_pwr_state.pll_cntl.f.pll_ioffset += 0x1; + } else if ((w100_pwr_state.pll_cntl.f.pll_pvg) < 0x7) { + w100_pwr_state.pll_cntl.f.pll_ioffset = 0x0; + w100_pwr_state.pll_cntl.f.pll_pvg += 0x1; + } else { + return 0; /* Error */ + } + } while(1); +} + + +/* + * w100_pll_calibration + */ +static int w100_pll_calibration(struct w100_pll_info *pll) +{ + int status; + + status = w100_pll_adjust(pll); + + /* PLL Reset And Lock */ + /* set VCO input = 0.5 * VDD */ + w100_pwr_state.pll_cntl.f.pll_dactal = 0xa; + writel((u32) (w100_pwr_state.pll_cntl.val), remapped_regs + mmPLL_CNTL); + + udelay(1); /* reset time */ + + /* enable charge pump */ + w100_pwr_state.pll_cntl.f.pll_tcpoff = 0x0; /* normal */ + writel((u32) (w100_pwr_state.pll_cntl.val), remapped_regs + mmPLL_CNTL); + + /* set VCO input = Hi-Z, disable DAC */ + w100_pwr_state.pll_cntl.f.pll_dactal = 0x0; + writel((u32) (w100_pwr_state.pll_cntl.val), remapped_regs + mmPLL_CNTL); + + udelay(400); /* lock time */ + + /* PLL locked */ + + return status; +} + + +static int w100_pll_set_clk(struct w100_pll_info *pll) +{ + int status; + + if (w100_pwr_state.auto_mode == 1) /* auto mode */ + { + w100_pwr_state.pwrmgt_cntl.f.pwm_fast_noml_hw_en = 0x0; /* disable fast to normal */ + w100_pwr_state.pwrmgt_cntl.f.pwm_noml_fast_hw_en = 0x0; /* disable normal to fast */ + writel((u32) (w100_pwr_state.pwrmgt_cntl.val), remapped_regs + mmPWRMGT_CNTL); + } + + /* Set system clock source to XTAL whilst adjusting the PLL! */ + w100_pwr_state.sclk_cntl.f.sclk_src_sel = CLK_SRC_XTAL; + writel((u32) (w100_pwr_state.sclk_cntl.val), remapped_regs + mmSCLK_CNTL); + + w100_pwr_state.pll_ref_fb_div.f.pll_ref_div = pll->M; + w100_pwr_state.pll_ref_fb_div.f.pll_fb_div_int = pll->N_int; + w100_pwr_state.pll_ref_fb_div.f.pll_fb_div_frac = pll->N_fac; + w100_pwr_state.pll_ref_fb_div.f.pll_lock_time = pll->lock_time; + writel((u32) (w100_pwr_state.pll_ref_fb_div.val), remapped_regs + mmPLL_REF_FB_DIV); + + w100_pwr_state.pwrmgt_cntl.f.pwm_mode_req = 0; + writel((u32) (w100_pwr_state.pwrmgt_cntl.val), remapped_regs + mmPWRMGT_CNTL); + + status = w100_pll_calibration(pll); + + if (w100_pwr_state.auto_mode == 1) /* auto mode */ + { + w100_pwr_state.pwrmgt_cntl.f.pwm_fast_noml_hw_en = 0x1; /* reenable fast to normal */ + w100_pwr_state.pwrmgt_cntl.f.pwm_noml_fast_hw_en = 0x1; /* reenable normal to fast */ + writel((u32) (w100_pwr_state.pwrmgt_cntl.val), remapped_regs + mmPWRMGT_CNTL); + } + return status; +} + +/* freq = target frequency of the PLL */ +static int w100_set_pll_freq(struct w100fb_par *par, unsigned int freq) +{ + struct w100_pll_info *pll = par->pll_table; + + do { + if (freq == pll->freq) { + return w100_pll_set_clk(pll); + } + pll++; + } while(pll->freq); + return 0; +} + +/* Set up an initial state. Some values/fields set + here will be overwritten. */ +static void w100_pwm_setup(struct w100fb_par *par) +{ + w100_pwr_state.clk_pin_cntl.f.osc_en = 0x1; + w100_pwr_state.clk_pin_cntl.f.osc_gain = 0x1f; + w100_pwr_state.clk_pin_cntl.f.dont_use_xtalin = 0x0; + w100_pwr_state.clk_pin_cntl.f.xtalin_pm_en = 0x0; + w100_pwr_state.clk_pin_cntl.f.xtalin_dbl_en = par->mach->xtal_dbl ? 1 : 0; + w100_pwr_state.clk_pin_cntl.f.cg_debug = 0x0; + writel((u32) (w100_pwr_state.clk_pin_cntl.val), remapped_regs + mmCLK_PIN_CNTL); + + w100_pwr_state.sclk_cntl.f.sclk_src_sel = CLK_SRC_XTAL; + w100_pwr_state.sclk_cntl.f.sclk_post_div_fast = 0x0; /* Pfast = 1 */ + w100_pwr_state.sclk_cntl.f.sclk_clkon_hys = 0x3; + w100_pwr_state.sclk_cntl.f.sclk_post_div_slow = 0x0; /* Pslow = 1 */ + w100_pwr_state.sclk_cntl.f.disp_cg_ok2switch_en = 0x0; + w100_pwr_state.sclk_cntl.f.sclk_force_reg = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_disp = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_mc = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_extmc = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_cp = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_e2 = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_e3 = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_idct = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.sclk_force_bist = 0x0; /* Dynamic */ + w100_pwr_state.sclk_cntl.f.busy_extend_cp = 0x0; + w100_pwr_state.sclk_cntl.f.busy_extend_e2 = 0x0; + w100_pwr_state.sclk_cntl.f.busy_extend_e3 = 0x0; + w100_pwr_state.sclk_cntl.f.busy_extend_idct = 0x0; + writel((u32) (w100_pwr_state.sclk_cntl.val), remapped_regs + mmSCLK_CNTL); + + w100_pwr_state.pclk_cntl.f.pclk_src_sel = CLK_SRC_XTAL; + w100_pwr_state.pclk_cntl.f.pclk_post_div = 0x1; /* P = 2 */ + w100_pwr_state.pclk_cntl.f.pclk_force_disp = 0x0; /* Dynamic */ + writel((u32) (w100_pwr_state.pclk_cntl.val), remapped_regs + mmPCLK_CNTL); + + w100_pwr_state.pll_ref_fb_div.f.pll_ref_div = 0x0; /* M = 1 */ + w100_pwr_state.pll_ref_fb_div.f.pll_fb_div_int = 0x0; /* N = 1.0 */ + w100_pwr_state.pll_ref_fb_div.f.pll_fb_div_frac = 0x0; + w100_pwr_state.pll_ref_fb_div.f.pll_reset_time = 0x5; + w100_pwr_state.pll_ref_fb_div.f.pll_lock_time = 0xff; + writel((u32) (w100_pwr_state.pll_ref_fb_div.val), remapped_regs + mmPLL_REF_FB_DIV); + + w100_pwr_state.pll_cntl.f.pll_pwdn = 0x1; + w100_pwr_state.pll_cntl.f.pll_reset = 0x1; + w100_pwr_state.pll_cntl.f.pll_pm_en = 0x0; + w100_pwr_state.pll_cntl.f.pll_mode = 0x0; /* uses VCO clock */ + w100_pwr_state.pll_cntl.f.pll_refclk_sel = 0x0; + w100_pwr_state.pll_cntl.f.pll_fbclk_sel = 0x0; + w100_pwr_state.pll_cntl.f.pll_tcpoff = 0x0; + w100_pwr_state.pll_cntl.f.pll_pcp = 0x4; + w100_pwr_state.pll_cntl.f.pll_pvg = 0x0; + w100_pwr_state.pll_cntl.f.pll_vcofr = 0x0; + w100_pwr_state.pll_cntl.f.pll_ioffset = 0x0; + w100_pwr_state.pll_cntl.f.pll_pecc_mode = 0x0; + w100_pwr_state.pll_cntl.f.pll_pecc_scon = 0x0; + w100_pwr_state.pll_cntl.f.pll_dactal = 0x0; /* Hi-Z */ + w100_pwr_state.pll_cntl.f.pll_cp_clip = 0x3; + w100_pwr_state.pll_cntl.f.pll_conf = 0x2; + w100_pwr_state.pll_cntl.f.pll_mbctrl = 0x2; + w100_pwr_state.pll_cntl.f.pll_ring_off = 0x0; + writel((u32) (w100_pwr_state.pll_cntl.val), remapped_regs + mmPLL_CNTL); + + w100_pwr_state.pwrmgt_cntl.f.pwm_enable = 0x0; + w100_pwr_state.pwrmgt_cntl.f.pwm_mode_req = 0x1; /* normal mode (0, 1, 3) */ + w100_pwr_state.pwrmgt_cntl.f.pwm_wakeup_cond = 0x0; + w100_pwr_state.pwrmgt_cntl.f.pwm_fast_noml_hw_en = 0x0; + w100_pwr_state.pwrmgt_cntl.f.pwm_noml_fast_hw_en = 0x0; + w100_pwr_state.pwrmgt_cntl.f.pwm_fast_noml_cond = 0x1; /* PM4,ENG */ + w100_pwr_state.pwrmgt_cntl.f.pwm_noml_fast_cond = 0x1; /* PM4,ENG */ + w100_pwr_state.pwrmgt_cntl.f.pwm_idle_timer = 0xFF; + w100_pwr_state.pwrmgt_cntl.f.pwm_busy_timer = 0xFF; + writel((u32) (w100_pwr_state.pwrmgt_cntl.val), remapped_regs + mmPWRMGT_CNTL); + + w100_pwr_state.auto_mode = 0; /* manual mode */ +} + + +/* + * Setup the w100 clocks for the specified mode + */ +static void w100_init_clocks(struct w100fb_par *par) +{ + struct w100_mode *mode = par->mode; + + if (mode->pixclk_src == CLK_SRC_PLL || mode->sysclk_src == CLK_SRC_PLL) + w100_set_pll_freq(par, (par->fastpll_mode && mode->fast_pll_freq) ? mode->fast_pll_freq : mode->pll_freq); + + w100_pwr_state.sclk_cntl.f.sclk_src_sel = mode->sysclk_src; + w100_pwr_state.sclk_cntl.f.sclk_post_div_fast = mode->sysclk_divider; + w100_pwr_state.sclk_cntl.f.sclk_post_div_slow = mode->sysclk_divider; + writel((u32) (w100_pwr_state.sclk_cntl.val), remapped_regs + mmSCLK_CNTL); +} + +static void w100_init_lcd(struct w100fb_par *par) +{ + u32 temp32; + struct w100_mode *mode = par->mode; + struct w100_gen_regs *regs = par->mach->regs; + union active_h_disp_u active_h_disp; + union active_v_disp_u active_v_disp; + union graphic_h_disp_u graphic_h_disp; + union graphic_v_disp_u graphic_v_disp; + union crtc_total_u crtc_total; + + /* w3200 doesn't like undefined bits being set so zero register values first */ + + active_h_disp.val = 0; + active_h_disp.f.active_h_start=mode->left_margin; + active_h_disp.f.active_h_end=mode->left_margin + mode->xres; + writel(active_h_disp.val, remapped_regs + mmACTIVE_H_DISP); + + active_v_disp.val = 0; + active_v_disp.f.active_v_start=mode->upper_margin; + active_v_disp.f.active_v_end=mode->upper_margin + mode->yres; + writel(active_v_disp.val, remapped_regs + mmACTIVE_V_DISP); + + graphic_h_disp.val = 0; + graphic_h_disp.f.graphic_h_start=mode->left_margin; + graphic_h_disp.f.graphic_h_end=mode->left_margin + mode->xres; + writel(graphic_h_disp.val, remapped_regs + mmGRAPHIC_H_DISP); + + graphic_v_disp.val = 0; + graphic_v_disp.f.graphic_v_start=mode->upper_margin; + graphic_v_disp.f.graphic_v_end=mode->upper_margin + mode->yres; + writel(graphic_v_disp.val, remapped_regs + mmGRAPHIC_V_DISP); + + crtc_total.val = 0; + crtc_total.f.crtc_h_total=mode->left_margin + mode->xres + mode->right_margin; + crtc_total.f.crtc_v_total=mode->upper_margin + mode->yres + mode->lower_margin; + writel(crtc_total.val, remapped_regs + mmCRTC_TOTAL); + + writel(mode->crtc_ss, remapped_regs + mmCRTC_SS); + writel(mode->crtc_ls, remapped_regs + mmCRTC_LS); + writel(mode->crtc_gs, remapped_regs + mmCRTC_GS); + writel(mode->crtc_vpos_gs, remapped_regs + mmCRTC_VPOS_GS); + writel(mode->crtc_rev, remapped_regs + mmCRTC_REV); + writel(mode->crtc_dclk, remapped_regs + mmCRTC_DCLK); + writel(mode->crtc_gclk, remapped_regs + mmCRTC_GCLK); + writel(mode->crtc_goe, remapped_regs + mmCRTC_GOE); + writel(mode->crtc_ps1_active, remapped_regs + mmCRTC_PS1_ACTIVE); + + writel(regs->lcd_format, remapped_regs + mmLCD_FORMAT); + writel(regs->lcdd_cntl1, remapped_regs + mmLCDD_CNTL1); + writel(regs->lcdd_cntl2, remapped_regs + mmLCDD_CNTL2); + writel(regs->genlcd_cntl1, remapped_regs + mmGENLCD_CNTL1); + writel(regs->genlcd_cntl2, remapped_regs + mmGENLCD_CNTL2); + writel(regs->genlcd_cntl3, remapped_regs + mmGENLCD_CNTL3); + + writel(0x00000000, remapped_regs + mmCRTC_FRAME); + writel(0x00000000, remapped_regs + mmCRTC_FRAME_VPOS); + writel(0x00000000, remapped_regs + mmCRTC_DEFAULT_COUNT); + writel(0x0000FF00, remapped_regs + mmLCD_BACKGROUND_COLOR); + + /* Hack for overlay in ext memory */ + temp32 = readl(remapped_regs + mmDISP_DEBUG2); + temp32 |= 0xc0000000; + writel(temp32, remapped_regs + mmDISP_DEBUG2); +} + + +static void w100_setup_memory(struct w100fb_par *par) +{ + union mc_ext_mem_location_u extmem_location; + union mc_fb_location_u intmem_location; + struct w100_mem_info *mem = par->mach->mem; + struct w100_bm_mem_info *bm_mem = par->mach->bm_mem; + + if (!par->extmem_active) { + w100_suspend(W100_SUSPEND_EXTMEM); + + /* Map Internal Memory at FB Base */ + intmem_location.f.mc_fb_start = W100_FB_BASE >> 8; + intmem_location.f.mc_fb_top = (W100_FB_BASE+MEM_INT_SIZE) >> 8; + writel((u32) (intmem_location.val), remapped_regs + mmMC_FB_LOCATION); + + /* Unmap External Memory - value is *probably* irrelevant but may have meaning + to acceleration libraries */ + extmem_location.f.mc_ext_mem_start = MEM_EXT_BASE_VALUE >> 8; + extmem_location.f.mc_ext_mem_top = (MEM_EXT_BASE_VALUE-1) >> 8; + writel((u32) (extmem_location.val), remapped_regs + mmMC_EXT_MEM_LOCATION); + } else { + /* Map Internal Memory to its default location */ + intmem_location.f.mc_fb_start = MEM_INT_BASE_VALUE >> 8; + intmem_location.f.mc_fb_top = (MEM_INT_BASE_VALUE+MEM_INT_SIZE) >> 8; + writel((u32) (intmem_location.val), remapped_regs + mmMC_FB_LOCATION); + + /* Map External Memory at FB Base */ + extmem_location.f.mc_ext_mem_start = W100_FB_BASE >> 8; + extmem_location.f.mc_ext_mem_top = (W100_FB_BASE+par->mach->mem->size) >> 8; + writel((u32) (extmem_location.val), remapped_regs + mmMC_EXT_MEM_LOCATION); + + writel(0x00007800, remapped_regs + mmMC_BIST_CTRL); + writel(mem->ext_cntl, remapped_regs + mmMEM_EXT_CNTL); + writel(0x00200021, remapped_regs + mmMEM_SDRAM_MODE_REG); + udelay(100); + writel(0x80200021, remapped_regs + mmMEM_SDRAM_MODE_REG); + udelay(100); + writel(mem->sdram_mode_reg, remapped_regs + mmMEM_SDRAM_MODE_REG); + udelay(100); + writel(mem->ext_timing_cntl, remapped_regs + mmMEM_EXT_TIMING_CNTL); + writel(mem->io_cntl, remapped_regs + mmMEM_IO_CNTL); + if (bm_mem) { + writel(bm_mem->ext_mem_bw, remapped_regs + mmBM_EXT_MEM_BANDWIDTH); + writel(bm_mem->offset, remapped_regs + mmBM_OFFSET); + writel(bm_mem->ext_timing_ctl, remapped_regs + mmBM_MEM_EXT_TIMING_CNTL); + writel(bm_mem->ext_cntl, remapped_regs + mmBM_MEM_EXT_CNTL); + writel(bm_mem->mode_reg, remapped_regs + mmBM_MEM_MODE_REG); + writel(bm_mem->io_cntl, remapped_regs + mmBM_MEM_IO_CNTL); + writel(bm_mem->config, remapped_regs + mmBM_CONFIG); + } + } +} + +static void w100_set_dispregs(struct w100fb_par *par) +{ + unsigned long rot=0, divider, offset=0; + union graphic_ctrl_u graphic_ctrl; + + /* See if the mode has been rotated */ + if (par->xres == par->mode->xres) { + if (par->flip) { + rot=3; /* 180 degree */ + offset=(par->xres * par->yres) - 1; + } /* else 0 degree */ + divider = par->mode->pixclk_divider; + } else { + if (par->flip) { + rot=2; /* 270 degree */ + offset=par->xres - 1; + } else { + rot=1; /* 90 degree */ + offset=par->xres * (par->yres - 1); + } + divider = par->mode->pixclk_divider_rotated; + } + + graphic_ctrl.val = 0; /* w32xx doesn't like undefined bits */ + switch (par->chip_id) { + case CHIP_ID_W100: + graphic_ctrl.f_w100.color_depth=6; + graphic_ctrl.f_w100.en_crtc=1; + graphic_ctrl.f_w100.en_graphic_req=1; + graphic_ctrl.f_w100.en_graphic_crtc=1; + graphic_ctrl.f_w100.lcd_pclk_on=1; + graphic_ctrl.f_w100.lcd_sclk_on=1; + graphic_ctrl.f_w100.low_power_on=0; + graphic_ctrl.f_w100.req_freq=0; + graphic_ctrl.f_w100.portrait_mode=rot; + + /* Zaurus needs this */ + switch(par->xres) { + case 240: + case 320: + default: + graphic_ctrl.f_w100.total_req_graphic=0xa0; + break; + case 480: + case 640: + switch(rot) { + case 0: /* 0 */ + case 3: /* 180 */ + graphic_ctrl.f_w100.low_power_on=1; + graphic_ctrl.f_w100.req_freq=5; + break; + case 1: /* 90 */ + case 2: /* 270 */ + graphic_ctrl.f_w100.req_freq=4; + break; + default: + break; + } + graphic_ctrl.f_w100.total_req_graphic=0xf0; + break; + } + break; + case CHIP_ID_W3200: + case CHIP_ID_W3220: + graphic_ctrl.f_w32xx.color_depth=6; + graphic_ctrl.f_w32xx.en_crtc=1; + graphic_ctrl.f_w32xx.en_graphic_req=1; + graphic_ctrl.f_w32xx.en_graphic_crtc=1; + graphic_ctrl.f_w32xx.lcd_pclk_on=1; + graphic_ctrl.f_w32xx.lcd_sclk_on=1; + graphic_ctrl.f_w32xx.low_power_on=0; + graphic_ctrl.f_w32xx.req_freq=0; + graphic_ctrl.f_w32xx.total_req_graphic=par->mode->xres >> 1; /* panel xres, not mode */ + graphic_ctrl.f_w32xx.portrait_mode=rot; + break; + } + + /* Set the pixel clock source and divider */ + w100_pwr_state.pclk_cntl.f.pclk_src_sel = par->mode->pixclk_src; + w100_pwr_state.pclk_cntl.f.pclk_post_div = divider; + writel((u32) (w100_pwr_state.pclk_cntl.val), remapped_regs + mmPCLK_CNTL); + + writel(graphic_ctrl.val, remapped_regs + mmGRAPHIC_CTRL); + writel(W100_FB_BASE + ((offset * BITS_PER_PIXEL/8)&~0x03UL), remapped_regs + mmGRAPHIC_OFFSET); + writel((par->xres*BITS_PER_PIXEL/8), remapped_regs + mmGRAPHIC_PITCH); +} + + +/* + * Work out how long the sync pulse lasts + * Value is 1/(time in seconds) + */ +static void calc_hsync(struct w100fb_par *par) +{ + unsigned long hsync; + struct w100_mode *mode = par->mode; + union crtc_ss_u crtc_ss; + + if (mode->pixclk_src == CLK_SRC_XTAL) + hsync=par->mach->xtal_freq; + else + hsync=((par->fastpll_mode && mode->fast_pll_freq) ? mode->fast_pll_freq : mode->pll_freq)*100000; + + hsync /= (w100_pwr_state.pclk_cntl.f.pclk_post_div + 1); + + crtc_ss.val = readl(remapped_regs + mmCRTC_SS); + if (crtc_ss.val) + par->hsync_len = hsync / (crtc_ss.f.ss_end-crtc_ss.f.ss_start); + else + par->hsync_len = 0; +} + +static void w100_suspend(u32 mode) +{ + u32 val; + + writel(0x7FFF8000, remapped_regs + mmMC_EXT_MEM_LOCATION); + writel(0x00FF0000, remapped_regs + mmMC_PERF_MON_CNTL); + + val = readl(remapped_regs + mmMEM_EXT_TIMING_CNTL); + val &= ~(0x00100000); /* bit20=0 */ + val |= 0xFF000000; /* bit31:24=0xff */ + writel(val, remapped_regs + mmMEM_EXT_TIMING_CNTL); + + val = readl(remapped_regs + mmMEM_EXT_CNTL); + val &= ~(0x00040000); /* bit18=0 */ + val |= 0x00080000; /* bit19=1 */ + writel(val, remapped_regs + mmMEM_EXT_CNTL); + + udelay(1); /* wait 1us */ + + if (mode == W100_SUSPEND_EXTMEM) { + /* CKE: Tri-State */ + val = readl(remapped_regs + mmMEM_EXT_CNTL); + val |= 0x40000000; /* bit30=1 */ + writel(val, remapped_regs + mmMEM_EXT_CNTL); + + /* CLK: Stop */ + val = readl(remapped_regs + mmMEM_EXT_CNTL); + val &= ~(0x00000001); /* bit0=0 */ + writel(val, remapped_regs + mmMEM_EXT_CNTL); + } else { + writel(0x00000000, remapped_regs + mmSCLK_CNTL); + writel(0x000000BF, remapped_regs + mmCLK_PIN_CNTL); + writel(0x00000015, remapped_regs + mmPWRMGT_CNTL); + + udelay(5); + + val = readl(remapped_regs + mmPLL_CNTL); + val |= 0x00000004; /* bit2=1 */ + writel(val, remapped_regs + mmPLL_CNTL); + + writel(0x00000000, remapped_regs + mmLCDD_CNTL1); + writel(0x00000000, remapped_regs + mmLCDD_CNTL2); + writel(0x00000000, remapped_regs + mmGENLCD_CNTL1); + writel(0x00000000, remapped_regs + mmGENLCD_CNTL2); + writel(0x00000000, remapped_regs + mmGENLCD_CNTL3); + + val = readl(remapped_regs + mmMEM_EXT_CNTL); + val |= 0xF0000000; + val &= ~(0x00000001); + writel(val, remapped_regs + mmMEM_EXT_CNTL); + + writel(0x0000001d, remapped_regs + mmPWRMGT_CNTL); + } +} + +static void w100_vsync(void) +{ + u32 tmp; + int timeout = 30000; /* VSync timeout = 30[ms] > 16.8[ms] */ + + tmp = readl(remapped_regs + mmACTIVE_V_DISP); + + /* set vline pos */ + writel((tmp >> 16) & 0x3ff, remapped_regs + mmDISP_INT_CNTL); + + /* disable vline irq */ + tmp = readl(remapped_regs + mmGEN_INT_CNTL); + + tmp &= ~0x00000002; + writel(tmp, remapped_regs + mmGEN_INT_CNTL); + + /* clear vline irq status */ + writel(0x00000002, remapped_regs + mmGEN_INT_STATUS); + + /* enable vline irq */ + writel((tmp | 0x00000002), remapped_regs + mmGEN_INT_CNTL); + + /* clear vline irq status */ + writel(0x00000002, remapped_regs + mmGEN_INT_STATUS); + + while(timeout > 0) { + if (readl(remapped_regs + mmGEN_INT_STATUS) & 0x00000002) + break; + udelay(1); + timeout--; + } + + /* disable vline irq */ + writel(tmp, remapped_regs + mmGEN_INT_CNTL); + + /* clear vline irq status */ + writel(0x00000002, remapped_regs + mmGEN_INT_STATUS); +} + +static struct platform_driver w100fb_driver = { + .probe = w100fb_probe, + .remove = w100fb_remove, + .suspend = w100fb_suspend, + .resume = w100fb_resume, + .driver = { + .name = "w100fb", + }, +}; + +module_platform_driver(w100fb_driver); + +MODULE_DESCRIPTION("ATI Imageon w100 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/w100fb.h b/drivers/video/fbdev/w100fb.h new file mode 100644 index 000000000000..fffae7b4f6e9 --- /dev/null +++ b/drivers/video/fbdev/w100fb.h @@ -0,0 +1,928 @@ +/* + * linux/drivers/video/w100fb.h + * + * Frame Buffer Device for ATI w100 (Wallaby) + * + * Copyright (C) 2002, ATI Corp. + * Copyright (C) 2004-2005 Richard Purdie + * Copyright (c) 2005 Ian Molton <spyro@f2s.com> + * + * Modified to work with 2.6 by Richard Purdie <rpurdie@rpsys.net> + * + * w32xx support by Ian Molton + * + * 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. + * + */ + +#if !defined (_W100FB_H) +#define _W100FB_H + +/* Block CIF Start: */ +#define mmCHIP_ID 0x0000 +#define mmREVISION_ID 0x0004 +#define mmWRAP_BUF_A 0x0008 +#define mmWRAP_BUF_B 0x000C +#define mmWRAP_TOP_DIR 0x0010 +#define mmWRAP_START_DIR 0x0014 +#define mmCIF_CNTL 0x0018 +#define mmCFGREG_BASE 0x001C +#define mmCIF_IO 0x0020 +#define mmCIF_READ_DBG 0x0024 +#define mmCIF_WRITE_DBG 0x0028 +#define cfgIND_ADDR_A_0 0x0000 +#define cfgIND_ADDR_A_1 0x0001 +#define cfgIND_ADDR_A_2 0x0002 +#define cfgIND_DATA_A 0x0003 +#define cfgREG_BASE 0x0004 +#define cfgINTF_CNTL 0x0005 +#define cfgSTATUS 0x0006 +#define cfgCPU_DEFAULTS 0x0007 +#define cfgIND_ADDR_B_0 0x0008 +#define cfgIND_ADDR_B_1 0x0009 +#define cfgIND_ADDR_B_2 0x000A +#define cfgIND_DATA_B 0x000B +#define cfgPM4_RPTR 0x000C +#define cfgSCRATCH 0x000D +#define cfgPM4_WRPTR_0 0x000E +#define cfgPM4_WRPTR_1 0x000F +/* Block CIF End: */ + +/* Block CP Start: */ +#define mmSCRATCH_UMSK 0x0280 +#define mmSCRATCH_ADDR 0x0284 +#define mmGEN_INT_CNTL 0x0200 +#define mmGEN_INT_STATUS 0x0204 +/* Block CP End: */ + +/* Block DISPLAY Start: */ +#define mmLCD_FORMAT 0x0410 +#define mmGRAPHIC_CTRL 0x0414 +#define mmGRAPHIC_OFFSET 0x0418 +#define mmGRAPHIC_PITCH 0x041C +#define mmCRTC_TOTAL 0x0420 +#define mmACTIVE_H_DISP 0x0424 +#define mmACTIVE_V_DISP 0x0428 +#define mmGRAPHIC_H_DISP 0x042C +#define mmGRAPHIC_V_DISP 0x0430 +#define mmVIDEO_CTRL 0x0434 +#define mmGRAPHIC_KEY 0x0438 +#define mmBRIGHTNESS_CNTL 0x045C +#define mmDISP_INT_CNTL 0x0488 +#define mmCRTC_SS 0x048C +#define mmCRTC_LS 0x0490 +#define mmCRTC_REV 0x0494 +#define mmCRTC_DCLK 0x049C +#define mmCRTC_GS 0x04A0 +#define mmCRTC_VPOS_GS 0x04A4 +#define mmCRTC_GCLK 0x04A8 +#define mmCRTC_GOE 0x04AC +#define mmCRTC_FRAME 0x04B0 +#define mmCRTC_FRAME_VPOS 0x04B4 +#define mmGPIO_DATA 0x04B8 +#define mmGPIO_CNTL1 0x04BC +#define mmGPIO_CNTL2 0x04C0 +#define mmLCDD_CNTL1 0x04C4 +#define mmLCDD_CNTL2 0x04C8 +#define mmGENLCD_CNTL1 0x04CC +#define mmGENLCD_CNTL2 0x04D0 +#define mmDISP_DEBUG 0x04D4 +#define mmDISP_DB_BUF_CNTL 0x04D8 +#define mmDISP_CRC_SIG 0x04DC +#define mmCRTC_DEFAULT_COUNT 0x04E0 +#define mmLCD_BACKGROUND_COLOR 0x04E4 +#define mmCRTC_PS2 0x04E8 +#define mmCRTC_PS2_VPOS 0x04EC +#define mmCRTC_PS1_ACTIVE 0x04F0 +#define mmCRTC_PS1_NACTIVE 0x04F4 +#define mmCRTC_GCLK_EXT 0x04F8 +#define mmCRTC_ALW 0x04FC +#define mmCRTC_ALW_VPOS 0x0500 +#define mmCRTC_PSK 0x0504 +#define mmCRTC_PSK_HPOS 0x0508 +#define mmCRTC_CV4_START 0x050C +#define mmCRTC_CV4_END 0x0510 +#define mmCRTC_CV4_HPOS 0x0514 +#define mmCRTC_ECK 0x051C +#define mmREFRESH_CNTL 0x0520 +#define mmGENLCD_CNTL3 0x0524 +#define mmGPIO_DATA2 0x0528 +#define mmGPIO_CNTL3 0x052C +#define mmGPIO_CNTL4 0x0530 +#define mmCHIP_STRAP 0x0534 +#define mmDISP_DEBUG2 0x0538 +#define mmDEBUG_BUS_CNTL 0x053C +#define mmGAMMA_VALUE1 0x0540 +#define mmGAMMA_VALUE2 0x0544 +#define mmGAMMA_SLOPE 0x0548 +#define mmGEN_STATUS 0x054C +#define mmHW_INT 0x0550 +/* Block DISPLAY End: */ + +/* Block GFX Start: */ +#define mmDST_OFFSET 0x1004 +#define mmDST_PITCH 0x1008 +#define mmDST_Y_X 0x1038 +#define mmDST_WIDTH_HEIGHT 0x1198 +#define mmDP_GUI_MASTER_CNTL 0x106C +#define mmBRUSH_OFFSET 0x108C +#define mmBRUSH_Y_X 0x1074 +#define mmDP_BRUSH_FRGD_CLR 0x107C +#define mmSRC_OFFSET 0x11AC +#define mmSRC_PITCH 0x11B0 +#define mmSRC_Y_X 0x1034 +#define mmDEFAULT_PITCH_OFFSET 0x10A0 +#define mmDEFAULT_SC_BOTTOM_RIGHT 0x10A8 +#define mmDEFAULT2_SC_BOTTOM_RIGHT 0x10AC +#define mmSC_TOP_LEFT 0x11BC +#define mmSC_BOTTOM_RIGHT 0x11C0 +#define mmSRC_SC_BOTTOM_RIGHT 0x11C4 +#define mmGLOBAL_ALPHA 0x1210 +#define mmFILTER_COEF 0x1214 +#define mmMVC_CNTL_START 0x11E0 +#define mmE2_ARITHMETIC_CNTL 0x1220 +#define mmDP_CNTL 0x11C8 +#define mmDP_CNTL_DST_DIR 0x11CC +#define mmDP_DATATYPE 0x12C4 +#define mmDP_MIX 0x12C8 +#define mmDP_WRITE_MSK 0x12CC +#define mmENG_CNTL 0x13E8 +#define mmENG_PERF_CNT 0x13F0 +/* Block GFX End: */ + +/* Block IDCT Start: */ +#define mmIDCT_RUNS 0x0C00 +#define mmIDCT_LEVELS 0x0C04 +#define mmIDCT_CONTROL 0x0C3C +#define mmIDCT_AUTH_CONTROL 0x0C08 +#define mmIDCT_AUTH 0x0C0C +/* Block IDCT End: */ + +/* Block MC Start: */ +#define mmMEM_CNTL 0x0180 +#define mmMEM_ARB 0x0184 +#define mmMC_FB_LOCATION 0x0188 +#define mmMEM_EXT_CNTL 0x018C +#define mmMC_EXT_MEM_LOCATION 0x0190 +#define mmMEM_EXT_TIMING_CNTL 0x0194 +#define mmMEM_SDRAM_MODE_REG 0x0198 +#define mmMEM_IO_CNTL 0x019C +#define mmMC_DEBUG 0x01A0 +#define mmMC_BIST_CTRL 0x01A4 +#define mmMC_BIST_COLLAR_READ 0x01A8 +#define mmTC_MISMATCH 0x01AC +#define mmMC_PERF_MON_CNTL 0x01B0 +#define mmMC_PERF_COUNTERS 0x01B4 +/* Block MC End: */ + +/* Block BM Start: */ +#define mmBM_EXT_MEM_BANDWIDTH 0x0A00 +#define mmBM_OFFSET 0x0A04 +#define mmBM_MEM_EXT_TIMING_CNTL 0x0A08 +#define mmBM_MEM_EXT_CNTL 0x0A0C +#define mmBM_MEM_MODE_REG 0x0A10 +#define mmBM_MEM_IO_CNTL 0x0A18 +#define mmBM_CONFIG 0x0A1C +#define mmBM_STATUS 0x0A20 +#define mmBM_DEBUG 0x0A24 +#define mmBM_PERF_MON_CNTL 0x0A28 +#define mmBM_PERF_COUNTERS 0x0A2C +#define mmBM_PERF2_MON_CNTL 0x0A30 +#define mmBM_PERF2_COUNTERS 0x0A34 +/* Block BM End: */ + +/* Block RBBM Start: */ +#define mmWAIT_UNTIL 0x1400 +#define mmISYNC_CNTL 0x1404 +#define mmRBBM_STATUS 0x0140 +#define mmRBBM_CNTL 0x0144 +#define mmNQWAIT_UNTIL 0x0150 +/* Block RBBM End: */ + +/* Block CG Start: */ +#define mmCLK_PIN_CNTL 0x0080 +#define mmPLL_REF_FB_DIV 0x0084 +#define mmPLL_CNTL 0x0088 +#define mmSCLK_CNTL 0x008C +#define mmPCLK_CNTL 0x0090 +#define mmCLK_TEST_CNTL 0x0094 +#define mmPWRMGT_CNTL 0x0098 +#define mmPWRMGT_STATUS 0x009C +/* Block CG End: */ + +/* default value definitions */ +#define defWRAP_TOP_DIR 0x00000000 +#define defWRAP_START_DIR 0x00000000 +#define defCFGREG_BASE 0x00000000 +#define defCIF_IO 0x000C0902 +#define defINTF_CNTL 0x00000011 +#define defCPU_DEFAULTS 0x00000006 +#define defHW_INT 0x00000000 +#define defMC_EXT_MEM_LOCATION 0x07ff0000 +#define defTC_MISMATCH 0x00000000 + +#define W100_CFG_BASE 0x0 +#define W100_CFG_LEN 0x10 +#define W100_REG_BASE 0x10000 +#define W100_REG_LEN 0x2000 +#define MEM_INT_BASE_VALUE 0x100000 +#define MEM_EXT_BASE_VALUE 0x800000 +#define MEM_INT_SIZE 0x05ffff +#define MEM_WINDOW_BASE 0x100000 +#define MEM_WINDOW_SIZE 0xf00000 + +#define WRAP_BUF_BASE_VALUE 0x80000 +#define WRAP_BUF_TOP_VALUE 0xbffff + +#define CHIP_ID_W100 0x57411002 +#define CHIP_ID_W3200 0x56441002 +#define CHIP_ID_W3220 0x57441002 + +/* Register structure definitions */ + +struct wrap_top_dir_t { + u32 top_addr : 23; + u32 : 9; +} __attribute__((packed)); + +union wrap_top_dir_u { + u32 val : 32; + struct wrap_top_dir_t f; +} __attribute__((packed)); + +struct wrap_start_dir_t { + u32 start_addr : 23; + u32 : 9; +} __attribute__((packed)); + +union wrap_start_dir_u { + u32 val : 32; + struct wrap_start_dir_t f; +} __attribute__((packed)); + +struct cif_cntl_t { + u32 swap_reg : 2; + u32 swap_fbuf_1 : 2; + u32 swap_fbuf_2 : 2; + u32 swap_fbuf_3 : 2; + u32 pmi_int_disable : 1; + u32 pmi_schmen_disable : 1; + u32 intb_oe : 1; + u32 en_wait_to_compensate_dq_prop_dly : 1; + u32 compensate_wait_rd_size : 2; + u32 wait_asserted_timeout_val : 2; + u32 wait_masked_val : 2; + u32 en_wait_timeout : 1; + u32 en_one_clk_setup_before_wait : 1; + u32 interrupt_active_high : 1; + u32 en_overwrite_straps : 1; + u32 strap_wait_active_hi : 1; + u32 lat_busy_count : 2; + u32 lat_rd_pm4_sclk_busy : 1; + u32 dis_system_bits : 1; + u32 dis_mr : 1; + u32 cif_spare_1 : 4; +} __attribute__((packed)); + +union cif_cntl_u { + u32 val : 32; + struct cif_cntl_t f; +} __attribute__((packed)); + +struct cfgreg_base_t { + u32 cfgreg_base : 24; + u32 : 8; +} __attribute__((packed)); + +union cfgreg_base_u { + u32 val : 32; + struct cfgreg_base_t f; +} __attribute__((packed)); + +struct cif_io_t { + u32 dq_srp : 1; + u32 dq_srn : 1; + u32 dq_sp : 4; + u32 dq_sn : 4; + u32 waitb_srp : 1; + u32 waitb_srn : 1; + u32 waitb_sp : 4; + u32 waitb_sn : 4; + u32 intb_srp : 1; + u32 intb_srn : 1; + u32 intb_sp : 4; + u32 intb_sn : 4; + u32 : 2; +} __attribute__((packed)); + +union cif_io_u { + u32 val : 32; + struct cif_io_t f; +} __attribute__((packed)); + +struct cif_read_dbg_t { + u32 unpacker_pre_fetch_trig_gen : 2; + u32 dly_second_rd_fetch_trig : 1; + u32 rst_rd_burst_id : 1; + u32 dis_rd_burst_id : 1; + u32 en_block_rd_when_packer_is_not_emp : 1; + u32 dis_pre_fetch_cntl_sm : 1; + u32 rbbm_chrncy_dis : 1; + u32 rbbm_rd_after_wr_lat : 2; + u32 dis_be_during_rd : 1; + u32 one_clk_invalidate_pulse : 1; + u32 dis_chnl_priority : 1; + u32 rst_read_path_a_pls : 1; + u32 rst_read_path_b_pls : 1; + u32 dis_reg_rd_fetch_trig : 1; + u32 dis_rd_fetch_trig_from_ind_addr : 1; + u32 dis_rd_same_byte_to_trig_fetch : 1; + u32 dis_dir_wrap : 1; + u32 dis_ring_buf_to_force_dec : 1; + u32 dis_addr_comp_in_16bit : 1; + u32 clr_w : 1; + u32 err_rd_tag_is_3 : 1; + u32 err_load_when_ful_a : 1; + u32 err_load_when_ful_b : 1; + u32 : 7; +} __attribute__((packed)); + +union cif_read_dbg_u { + u32 val : 32; + struct cif_read_dbg_t f; +} __attribute__((packed)); + +struct cif_write_dbg_t { + u32 packer_timeout_count : 2; + u32 en_upper_load_cond : 1; + u32 en_chnl_change_cond : 1; + u32 dis_addr_comp_cond : 1; + u32 dis_load_same_byte_addr_cond : 1; + u32 dis_timeout_cond : 1; + u32 dis_timeout_during_rbbm : 1; + u32 dis_packer_ful_during_rbbm_timeout : 1; + u32 en_dword_split_to_rbbm : 1; + u32 en_dummy_val : 1; + u32 dummy_val_sel : 1; + u32 mask_pm4_wrptr_dec : 1; + u32 dis_mc_clean_cond : 1; + u32 err_two_reqi_during_ful : 1; + u32 err_reqi_during_idle_clk : 1; + u32 err_global : 1; + u32 en_wr_buf_dbg_load : 1; + u32 en_wr_buf_dbg_path : 1; + u32 sel_wr_buf_byte : 3; + u32 dis_rd_flush_wr : 1; + u32 dis_packer_ful_cond : 1; + u32 dis_invalidate_by_ops_chnl : 1; + u32 en_halt_when_reqi_err : 1; + u32 cif_spare_2 : 5; + u32 : 1; +} __attribute__((packed)); + +union cif_write_dbg_u { + u32 val : 32; + struct cif_write_dbg_t f; +} __attribute__((packed)); + + +struct intf_cntl_t { + unsigned char ad_inc_a : 1; + unsigned char ring_buf_a : 1; + unsigned char rd_fetch_trigger_a : 1; + unsigned char rd_data_rdy_a : 1; + unsigned char ad_inc_b : 1; + unsigned char ring_buf_b : 1; + unsigned char rd_fetch_trigger_b : 1; + unsigned char rd_data_rdy_b : 1; +} __attribute__((packed)); + +union intf_cntl_u { + unsigned char val : 8; + struct intf_cntl_t f; +} __attribute__((packed)); + +struct cpu_defaults_t { + unsigned char unpack_rd_data : 1; + unsigned char access_ind_addr_a : 1; + unsigned char access_ind_addr_b : 1; + unsigned char access_scratch_reg : 1; + unsigned char pack_wr_data : 1; + unsigned char transition_size : 1; + unsigned char en_read_buf_mode : 1; + unsigned char rd_fetch_scratch : 1; +} __attribute__((packed)); + +union cpu_defaults_u { + unsigned char val : 8; + struct cpu_defaults_t f; +} __attribute__((packed)); + +struct crtc_total_t { + u32 crtc_h_total : 10; + u32 : 6; + u32 crtc_v_total : 10; + u32 : 6; +} __attribute__((packed)); + +union crtc_total_u { + u32 val : 32; + struct crtc_total_t f; +} __attribute__((packed)); + +struct crtc_ss_t { + u32 ss_start : 10; + u32 : 6; + u32 ss_end : 10; + u32 : 2; + u32 ss_align : 1; + u32 ss_pol : 1; + u32 ss_run_mode : 1; + u32 ss_en : 1; +} __attribute__((packed)); + +union crtc_ss_u { + u32 val : 32; + struct crtc_ss_t f; +} __attribute__((packed)); + +struct active_h_disp_t { + u32 active_h_start : 10; + u32 : 6; + u32 active_h_end : 10; + u32 : 6; +} __attribute__((packed)); + +union active_h_disp_u { + u32 val : 32; + struct active_h_disp_t f; +} __attribute__((packed)); + +struct active_v_disp_t { + u32 active_v_start : 10; + u32 : 6; + u32 active_v_end : 10; + u32 : 6; +} __attribute__((packed)); + +union active_v_disp_u { + u32 val : 32; + struct active_v_disp_t f; +} __attribute__((packed)); + +struct graphic_h_disp_t { + u32 graphic_h_start : 10; + u32 : 6; + u32 graphic_h_end : 10; + u32 : 6; +} __attribute__((packed)); + +union graphic_h_disp_u { + u32 val : 32; + struct graphic_h_disp_t f; +} __attribute__((packed)); + +struct graphic_v_disp_t { + u32 graphic_v_start : 10; + u32 : 6; + u32 graphic_v_end : 10; + u32 : 6; +} __attribute__((packed)); + +union graphic_v_disp_u{ + u32 val : 32; + struct graphic_v_disp_t f; +} __attribute__((packed)); + +struct graphic_ctrl_t_w100 { + u32 color_depth : 3; + u32 portrait_mode : 2; + u32 low_power_on : 1; + u32 req_freq : 4; + u32 en_crtc : 1; + u32 en_graphic_req : 1; + u32 en_graphic_crtc : 1; + u32 total_req_graphic : 9; + u32 lcd_pclk_on : 1; + u32 lcd_sclk_on : 1; + u32 pclk_running : 1; + u32 sclk_running : 1; + u32 : 6; +} __attribute__((packed)); + +struct graphic_ctrl_t_w32xx { + u32 color_depth : 3; + u32 portrait_mode : 2; + u32 low_power_on : 1; + u32 req_freq : 4; + u32 en_crtc : 1; + u32 en_graphic_req : 1; + u32 en_graphic_crtc : 1; + u32 total_req_graphic : 10; + u32 lcd_pclk_on : 1; + u32 lcd_sclk_on : 1; + u32 pclk_running : 1; + u32 sclk_running : 1; + u32 : 5; +} __attribute__((packed)); + +union graphic_ctrl_u { + u32 val : 32; + struct graphic_ctrl_t_w100 f_w100; + struct graphic_ctrl_t_w32xx f_w32xx; +} __attribute__((packed)); + +struct video_ctrl_t { + u32 video_mode : 1; + u32 keyer_en : 1; + u32 en_video_req : 1; + u32 en_graphic_req_video : 1; + u32 en_video_crtc : 1; + u32 video_hor_exp : 2; + u32 video_ver_exp : 2; + u32 uv_combine : 1; + u32 total_req_video : 9; + u32 video_ch_sel : 1; + u32 video_portrait : 2; + u32 yuv2rgb_en : 1; + u32 yuv2rgb_option : 1; + u32 video_inv_hor : 1; + u32 video_inv_ver : 1; + u32 gamma_sel : 2; + u32 dis_limit : 1; + u32 en_uv_hblend : 1; + u32 rgb_gamma_sel : 2; +} __attribute__((packed)); + +union video_ctrl_u { + u32 val : 32; + struct video_ctrl_t f; +} __attribute__((packed)); + +struct disp_db_buf_cntl_rd_t { + u32 en_db_buf : 1; + u32 update_db_buf_done : 1; + u32 db_buf_cntl : 6; + u32 : 24; +} __attribute__((packed)); + +union disp_db_buf_cntl_rd_u { + u32 val : 32; + struct disp_db_buf_cntl_rd_t f; +} __attribute__((packed)); + +struct disp_db_buf_cntl_wr_t { + u32 en_db_buf : 1; + u32 update_db_buf : 1; + u32 db_buf_cntl : 6; + u32 : 24; +} __attribute__((packed)); + +union disp_db_buf_cntl_wr_u { + u32 val : 32; + struct disp_db_buf_cntl_wr_t f; +} __attribute__((packed)); + +struct gamma_value1_t { + u32 gamma1 : 8; + u32 gamma2 : 8; + u32 gamma3 : 8; + u32 gamma4 : 8; +} __attribute__((packed)); + +union gamma_value1_u { + u32 val : 32; + struct gamma_value1_t f; +} __attribute__((packed)); + +struct gamma_value2_t { + u32 gamma5 : 8; + u32 gamma6 : 8; + u32 gamma7 : 8; + u32 gamma8 : 8; +} __attribute__((packed)); + +union gamma_value2_u { + u32 val : 32; + struct gamma_value2_t f; +} __attribute__((packed)); + +struct gamma_slope_t { + u32 slope1 : 3; + u32 slope2 : 3; + u32 slope3 : 3; + u32 slope4 : 3; + u32 slope5 : 3; + u32 slope6 : 3; + u32 slope7 : 3; + u32 slope8 : 3; + u32 : 8; +} __attribute__((packed)); + +union gamma_slope_u { + u32 val : 32; + struct gamma_slope_t f; +} __attribute__((packed)); + +struct mc_ext_mem_location_t { + u32 mc_ext_mem_start : 16; + u32 mc_ext_mem_top : 16; +} __attribute__((packed)); + +union mc_ext_mem_location_u { + u32 val : 32; + struct mc_ext_mem_location_t f; +} __attribute__((packed)); + +struct mc_fb_location_t { + u32 mc_fb_start : 16; + u32 mc_fb_top : 16; +} __attribute__((packed)); + +union mc_fb_location_u { + u32 val : 32; + struct mc_fb_location_t f; +} __attribute__((packed)); + +struct clk_pin_cntl_t { + u32 osc_en : 1; + u32 osc_gain : 5; + u32 dont_use_xtalin : 1; + u32 xtalin_pm_en : 1; + u32 xtalin_dbl_en : 1; + u32 : 7; + u32 cg_debug : 16; +} __attribute__((packed)); + +union clk_pin_cntl_u { + u32 val : 32; + struct clk_pin_cntl_t f; +} __attribute__((packed)); + +struct pll_ref_fb_div_t { + u32 pll_ref_div : 4; + u32 : 4; + u32 pll_fb_div_int : 6; + u32 : 2; + u32 pll_fb_div_frac : 3; + u32 : 1; + u32 pll_reset_time : 4; + u32 pll_lock_time : 8; +} __attribute__((packed)); + +union pll_ref_fb_div_u { + u32 val : 32; + struct pll_ref_fb_div_t f; +} __attribute__((packed)); + +struct pll_cntl_t { + u32 pll_pwdn : 1; + u32 pll_reset : 1; + u32 pll_pm_en : 1; + u32 pll_mode : 1; + u32 pll_refclk_sel : 1; + u32 pll_fbclk_sel : 1; + u32 pll_tcpoff : 1; + u32 pll_pcp : 3; + u32 pll_pvg : 3; + u32 pll_vcofr : 1; + u32 pll_ioffset : 2; + u32 pll_pecc_mode : 2; + u32 pll_pecc_scon : 2; + u32 pll_dactal : 4; + u32 pll_cp_clip : 2; + u32 pll_conf : 3; + u32 pll_mbctrl : 2; + u32 pll_ring_off : 1; +} __attribute__((packed)); + +union pll_cntl_u { + u32 val : 32; + struct pll_cntl_t f; +} __attribute__((packed)); + +struct sclk_cntl_t { + u32 sclk_src_sel : 2; + u32 : 2; + u32 sclk_post_div_fast : 4; + u32 sclk_clkon_hys : 3; + u32 sclk_post_div_slow : 4; + u32 disp_cg_ok2switch_en : 1; + u32 sclk_force_reg : 1; + u32 sclk_force_disp : 1; + u32 sclk_force_mc : 1; + u32 sclk_force_extmc : 1; + u32 sclk_force_cp : 1; + u32 sclk_force_e2 : 1; + u32 sclk_force_e3 : 1; + u32 sclk_force_idct : 1; + u32 sclk_force_bist : 1; + u32 busy_extend_cp : 1; + u32 busy_extend_e2 : 1; + u32 busy_extend_e3 : 1; + u32 busy_extend_idct : 1; + u32 : 3; +} __attribute__((packed)); + +union sclk_cntl_u { + u32 val : 32; + struct sclk_cntl_t f; +} __attribute__((packed)); + +struct pclk_cntl_t { + u32 pclk_src_sel : 2; + u32 : 2; + u32 pclk_post_div : 4; + u32 : 8; + u32 pclk_force_disp : 1; + u32 : 15; +} __attribute__((packed)); + +union pclk_cntl_u { + u32 val : 32; + struct pclk_cntl_t f; +} __attribute__((packed)); + + +#define TESTCLK_SRC_PLL 0x01 +#define TESTCLK_SRC_SCLK 0x02 +#define TESTCLK_SRC_PCLK 0x03 +/* 4 and 5 seem to by XTAL/M */ +#define TESTCLK_SRC_XTAL 0x06 + +struct clk_test_cntl_t { + u32 testclk_sel : 4; + u32 : 3; + u32 start_check_freq : 1; + u32 tstcount_rst : 1; + u32 : 15; + u32 test_count : 8; +} __attribute__((packed)); + +union clk_test_cntl_u { + u32 val : 32; + struct clk_test_cntl_t f; +} __attribute__((packed)); + +struct pwrmgt_cntl_t { + u32 pwm_enable : 1; + u32 : 1; + u32 pwm_mode_req : 2; + u32 pwm_wakeup_cond : 2; + u32 pwm_fast_noml_hw_en : 1; + u32 pwm_noml_fast_hw_en : 1; + u32 pwm_fast_noml_cond : 4; + u32 pwm_noml_fast_cond : 4; + u32 pwm_idle_timer : 8; + u32 pwm_busy_timer : 8; +} __attribute__((packed)); + +union pwrmgt_cntl_u { + u32 val : 32; + struct pwrmgt_cntl_t f; +} __attribute__((packed)); + +#define SRC_DATATYPE_EQU_DST 3 + +#define ROP3_SRCCOPY 0xcc +#define ROP3_PATCOPY 0xf0 + +#define GMC_BRUSH_SOLID_COLOR 13 +#define GMC_BRUSH_NONE 15 + +#define DP_SRC_MEM_RECTANGULAR 2 + +#define DP_OP_ROP 0 + +struct dp_gui_master_cntl_t { + u32 gmc_src_pitch_offset_cntl : 1; + u32 gmc_dst_pitch_offset_cntl : 1; + u32 gmc_src_clipping : 1; + u32 gmc_dst_clipping : 1; + u32 gmc_brush_datatype : 4; + u32 gmc_dst_datatype : 4; + u32 gmc_src_datatype : 3; + u32 gmc_byte_pix_order : 1; + u32 gmc_default_sel : 1; + u32 gmc_rop3 : 8; + u32 gmc_dp_src_source : 3; + u32 gmc_clr_cmp_fcn_dis : 1; + u32 : 1; + u32 gmc_wr_msk_dis : 1; + u32 gmc_dp_op : 1; +} __attribute__((packed)); + +union dp_gui_master_cntl_u { + u32 val : 32; + struct dp_gui_master_cntl_t f; +} __attribute__((packed)); + +struct rbbm_status_t { + u32 cmdfifo_avail : 7; + u32 : 1; + u32 hirq_on_rbb : 1; + u32 cprq_on_rbb : 1; + u32 cfrq_on_rbb : 1; + u32 hirq_in_rtbuf : 1; + u32 cprq_in_rtbuf : 1; + u32 cfrq_in_rtbuf : 1; + u32 cf_pipe_busy : 1; + u32 eng_ev_busy : 1; + u32 cp_cmdstrm_busy : 1; + u32 e2_busy : 1; + u32 rb2d_busy : 1; + u32 rb3d_busy : 1; + u32 se_busy : 1; + u32 re_busy : 1; + u32 tam_busy : 1; + u32 tdm_busy : 1; + u32 pb_busy : 1; + u32 : 6; + u32 gui_active : 1; +} __attribute__((packed)); + +union rbbm_status_u { + u32 val : 32; + struct rbbm_status_t f; +} __attribute__((packed)); + +struct dp_datatype_t { + u32 dp_dst_datatype : 4; + u32 : 4; + u32 dp_brush_datatype : 4; + u32 dp_src2_type : 1; + u32 dp_src2_datatype : 3; + u32 dp_src_datatype : 3; + u32 : 11; + u32 dp_byte_pix_order : 1; + u32 : 1; +} __attribute__((packed)); + +union dp_datatype_u { + u32 val : 32; + struct dp_datatype_t f; +} __attribute__((packed)); + +struct dp_mix_t { + u32 : 8; + u32 dp_src_source : 3; + u32 dp_src2_source : 3; + u32 : 2; + u32 dp_rop3 : 8; + u32 dp_op : 1; + u32 : 7; +} __attribute__((packed)); + +union dp_mix_u { + u32 val : 32; + struct dp_mix_t f; +} __attribute__((packed)); + +struct eng_cntl_t { + u32 erc_reg_rd_ws : 1; + u32 erc_reg_wr_ws : 1; + u32 erc_idle_reg_wr : 1; + u32 dis_engine_triggers : 1; + u32 dis_rop_src_uses_dst_w_h : 1; + u32 dis_src_uses_dst_dirmaj : 1; + u32 : 6; + u32 force_3dclk_when_2dclk : 1; + u32 : 19; +} __attribute__((packed)); + +union eng_cntl_u { + u32 val : 32; + struct eng_cntl_t f; +} __attribute__((packed)); + +struct dp_cntl_t { + u32 dst_x_dir : 1; + u32 dst_y_dir : 1; + u32 src_x_dir : 1; + u32 src_y_dir : 1; + u32 dst_major_x : 1; + u32 src_major_x : 1; + u32 : 26; +} __attribute__((packed)); + +union dp_cntl_u { + u32 val : 32; + struct dp_cntl_t f; +} __attribute__((packed)); + +struct dp_cntl_dst_dir_t { + u32 : 15; + u32 dst_y_dir : 1; + u32 : 15; + u32 dst_x_dir : 1; +} __attribute__((packed)); + +union dp_cntl_dst_dir_u { + u32 val : 32; + struct dp_cntl_dst_dir_t f; +} __attribute__((packed)); + +#endif + diff --git a/drivers/video/fbdev/wm8505fb.c b/drivers/video/fbdev/wm8505fb.c new file mode 100644 index 000000000000..537d199612af --- /dev/null +++ b/drivers/video/fbdev/wm8505fb.c @@ -0,0 +1,421 @@ +/* + * WonderMedia WM8505 Frame Buffer device driver + * + * Copyright (C) 2010 Ed Spiridonov <edo.rus@gmail.com> + * Based on vt8500lcdfb.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/memblock.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <video/of_display_timing.h> + +#include "wm8505fb_regs.h" +#include "wmt_ge_rops.h" + +#define DRIVER_NAME "wm8505-fb" + +#define to_wm8505fb_info(__info) container_of(__info, \ + struct wm8505fb_info, fb) +struct wm8505fb_info { + struct fb_info fb; + void __iomem *regbase; + unsigned int contrast; +}; + + +static int wm8505fb_init_hw(struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + int i; + + /* I know the purpose only of few registers, so clear unknown */ + for (i = 0; i < 0x200; i += 4) + writel(0, fbi->regbase + i); + + /* Set frame buffer address */ + writel(fbi->fb.fix.smem_start, fbi->regbase + WMT_GOVR_FBADDR); + writel(fbi->fb.fix.smem_start, fbi->regbase + WMT_GOVR_FBADDR1); + + /* + * Set in-memory picture format to RGB + * 0x31C sets the correct color mode (RGB565) for WM8650 + * Bit 8+9 (0x300) are ignored on WM8505 as reserved + */ + writel(0x31c, fbi->regbase + WMT_GOVR_COLORSPACE); + writel(1, fbi->regbase + WMT_GOVR_COLORSPACE1); + + /* Virtual buffer size */ + writel(info->var.xres, fbi->regbase + WMT_GOVR_XRES); + writel(info->var.xres_virtual, fbi->regbase + WMT_GOVR_XRES_VIRTUAL); + + /* black magic ;) */ + writel(0xf, fbi->regbase + WMT_GOVR_FHI); + writel(4, fbi->regbase + WMT_GOVR_DVO_SET); + writel(1, fbi->regbase + WMT_GOVR_MIF_ENABLE); + writel(1, fbi->regbase + WMT_GOVR_REG_UPDATE); + + return 0; +} + +static int wm8505fb_set_timing(struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + int h_start = info->var.left_margin; + int h_end = h_start + info->var.xres; + int h_all = h_end + info->var.right_margin; + int h_sync = info->var.hsync_len; + + int v_start = info->var.upper_margin; + int v_end = v_start + info->var.yres; + int v_all = v_end + info->var.lower_margin; + int v_sync = info->var.vsync_len; + + writel(0, fbi->regbase + WMT_GOVR_TG); + + writel(h_start, fbi->regbase + WMT_GOVR_TIMING_H_START); + writel(h_end, fbi->regbase + WMT_GOVR_TIMING_H_END); + writel(h_all, fbi->regbase + WMT_GOVR_TIMING_H_ALL); + writel(h_sync, fbi->regbase + WMT_GOVR_TIMING_H_SYNC); + + writel(v_start, fbi->regbase + WMT_GOVR_TIMING_V_START); + writel(v_end, fbi->regbase + WMT_GOVR_TIMING_V_END); + writel(v_all, fbi->regbase + WMT_GOVR_TIMING_V_ALL); + writel(v_sync, fbi->regbase + WMT_GOVR_TIMING_V_SYNC); + + writel(1, fbi->regbase + WMT_GOVR_TG); + + return 0; +} + + +static int wm8505fb_set_par(struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + if (!fbi) + return -EINVAL; + + if (info->var.bits_per_pixel == 32) { + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.red.msb_right = 0; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->var.blue.msb_right = 0; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.xres_virtual << 2; + } else if (info->var.bits_per_pixel == 16) { + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.red.msb_right = 0; + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->var.blue.msb_right = 0; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.xres_virtual << 1; + } + + wm8505fb_set_timing(info); + + writel(fbi->contrast<<16 | fbi->contrast<<8 | fbi->contrast, + fbi->regbase + WMT_GOVR_CONTRAST); + + return 0; +} + +static ssize_t contrast_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + return sprintf(buf, "%d\n", fbi->contrast); +} + +static ssize_t contrast_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + unsigned long tmp; + + if (kstrtoul(buf, 10, &tmp) || (tmp > 0xff)) + return -EINVAL; + fbi->contrast = tmp; + + wm8505fb_set_par(info); + + return count; +} + +static DEVICE_ATTR(contrast, 0644, contrast_show, contrast_store); + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int wm8505fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) { + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + int ret = 1; + unsigned int val; + if (regno >= 256) + return -EINVAL; + + if (info->var.grayscale) + red = green = blue = + (19595 * red + 38470 * green + 7471 * blue) >> 16; + + switch (fbi->fb.fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &fbi->fb.var.red); + val |= chan_to_field(green, &fbi->fb.var.green); + val |= chan_to_field(blue, &fbi->fb.var.blue); + + pal[regno] = val; + ret = 0; + } + break; + } + + return ret; +} + +static int wm8505fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + writel(var->xoffset, fbi->regbase + WMT_GOVR_XPAN); + writel(var->yoffset, fbi->regbase + WMT_GOVR_YPAN); + return 0; +} + +static int wm8505fb_blank(int blank, struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + switch (blank) { + case FB_BLANK_UNBLANK: + wm8505fb_set_timing(info); + break; + default: + writel(0, fbi->regbase + WMT_GOVR_TIMING_V_SYNC); + break; + } + + return 0; +} + +static struct fb_ops wm8505fb_ops = { + .owner = THIS_MODULE, + .fb_set_par = wm8505fb_set_par, + .fb_setcolreg = wm8505fb_setcolreg, + .fb_fillrect = wmt_ge_fillrect, + .fb_copyarea = wmt_ge_copyarea, + .fb_imageblit = sys_imageblit, + .fb_sync = wmt_ge_sync, + .fb_pan_display = wm8505fb_pan_display, + .fb_blank = wm8505fb_blank, +}; + +static int wm8505fb_probe(struct platform_device *pdev) +{ + struct wm8505fb_info *fbi; + struct resource *res; + struct display_timings *disp_timing; + void *addr; + int ret; + + struct fb_videomode mode; + u32 bpp; + dma_addr_t fb_mem_phys; + unsigned long fb_mem_len; + void *fb_mem_virt; + + fbi = devm_kzalloc(&pdev->dev, sizeof(struct wm8505fb_info) + + sizeof(u32) * 16, GFP_KERNEL); + if (!fbi) { + dev_err(&pdev->dev, "Failed to initialize framebuffer device\n"); + return -ENOMEM; + } + + strcpy(fbi->fb.fix.id, DRIVER_NAME); + + fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fbi->fb.fix.xpanstep = 1; + fbi->fb.fix.ypanstep = 1; + fbi->fb.fix.ywrapstep = 0; + fbi->fb.fix.accel = FB_ACCEL_NONE; + + fbi->fb.fbops = &wm8505fb_ops; + fbi->fb.flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_XPAN + | FBINFO_HWACCEL_YPAN + | FBINFO_VIRTFB + | FBINFO_PARTIAL_PAN_OK; + fbi->fb.node = -1; + + addr = fbi; + addr = addr + sizeof(struct wm8505fb_info); + fbi->fb.pseudo_palette = addr; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + fbi->regbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fbi->regbase)) + return PTR_ERR(fbi->regbase); + + disp_timing = of_get_display_timings(pdev->dev.of_node); + if (!disp_timing) + return -EINVAL; + + ret = of_get_fb_videomode(pdev->dev.of_node, &mode, OF_USE_NATIVE_MODE); + if (ret) + return ret; + + ret = of_property_read_u32(pdev->dev.of_node, "bits-per-pixel", &bpp); + if (ret) + return ret; + + fb_videomode_to_var(&fbi->fb.var, &mode); + + fbi->fb.var.nonstd = 0; + fbi->fb.var.activate = FB_ACTIVATE_NOW; + + fbi->fb.var.height = -1; + fbi->fb.var.width = -1; + + /* try allocating the framebuffer */ + fb_mem_len = mode.xres * mode.yres * 2 * (bpp / 8); + fb_mem_virt = dmam_alloc_coherent(&pdev->dev, fb_mem_len, &fb_mem_phys, + GFP_KERNEL); + if (!fb_mem_virt) { + pr_err("%s: Failed to allocate framebuffer\n", __func__); + return -ENOMEM; + } + + fbi->fb.var.xres_virtual = mode.xres; + fbi->fb.var.yres_virtual = mode.yres * 2; + fbi->fb.var.bits_per_pixel = bpp; + + fbi->fb.fix.smem_start = fb_mem_phys; + fbi->fb.fix.smem_len = fb_mem_len; + fbi->fb.screen_base = fb_mem_virt; + fbi->fb.screen_size = fb_mem_len; + + fbi->contrast = 0x10; + ret = wm8505fb_set_par(&fbi->fb); + if (ret) { + dev_err(&pdev->dev, "Failed to set parameters\n"); + return ret; + } + + if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) { + dev_err(&pdev->dev, "Failed to allocate color map\n"); + return -ENOMEM; + } + + wm8505fb_init_hw(&fbi->fb); + + platform_set_drvdata(pdev, fbi); + + ret = register_framebuffer(&fbi->fb); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to register framebuffer device: %d\n", ret); + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); + return ret; + } + + ret = device_create_file(&pdev->dev, &dev_attr_contrast); + if (ret < 0) + fb_warn(&fbi->fb, "failed to register attributes (%d)\n", ret); + + fb_info(&fbi->fb, "%s frame buffer at 0x%lx-0x%lx\n", + fbi->fb.fix.id, fbi->fb.fix.smem_start, + fbi->fb.fix.smem_start + fbi->fb.fix.smem_len - 1); + + return 0; +} + +static int wm8505fb_remove(struct platform_device *pdev) +{ + struct wm8505fb_info *fbi = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_contrast); + + unregister_framebuffer(&fbi->fb); + + writel(0, fbi->regbase); + + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); + + return 0; +} + +static const struct of_device_id wmt_dt_ids[] = { + { .compatible = "wm,wm8505-fb", }, + {} +}; + +static struct platform_driver wm8505fb_driver = { + .probe = wm8505fb_probe, + .remove = wm8505fb_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = wmt_dt_ids, + }, +}; + +module_platform_driver(wm8505fb_driver); + +MODULE_AUTHOR("Ed Spiridonov <edo.rus@gmail.com>"); +MODULE_DESCRIPTION("Framebuffer driver for WMT WM8505"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, wmt_dt_ids); diff --git a/drivers/video/fbdev/wm8505fb_regs.h b/drivers/video/fbdev/wm8505fb_regs.h new file mode 100644 index 000000000000..4dd41668c6d1 --- /dev/null +++ b/drivers/video/fbdev/wm8505fb_regs.h @@ -0,0 +1,76 @@ +/* + * GOVR registers list for WM8505 chips + * + * Copyright (C) 2010 Ed Spiridonov <edo.rus@gmail.com> + * Based on VIA/WonderMedia wm8510-govrh-reg.h + * http://github.com/projectgus/kernel_wm8505/blob/wm8505_2.6.29/ + * drivers/video/wmt/register/wm8510/wm8510-govrh-reg.h + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef _WM8505FB_REGS_H +#define _WM8505FB_REGS_H + +/* + * Color space select register, default value 0x1c + * BIT0 GOVRH_DVO_YUV2RGB_ENABLE + * BIT1 GOVRH_VGA_YUV2RGB_ENABLE + * BIT2 GOVRH_RGB_MODE + * BIT3 GOVRH_DAC_CLKINV + * BIT4 GOVRH_BLANK_ZERO + */ +#define WMT_GOVR_COLORSPACE 0x1e4 +/* + * Another colorspace select register, default value 1 + * BIT0 GOVRH_DVO_RGB + * BIT1 GOVRH_DVO_YUV422 + */ +#define WMT_GOVR_COLORSPACE1 0x30 + +#define WMT_GOVR_CONTRAST 0x1b8 +#define WMT_GOVR_BRGHTNESS 0x1bc /* incompatible with RGB? */ + +/* Framubeffer address */ +#define WMT_GOVR_FBADDR 0x90 +#define WMT_GOVR_FBADDR1 0x94 /* UV offset in YUV mode */ + +/* Offset of visible window */ +#define WMT_GOVR_XPAN 0xa4 +#define WMT_GOVR_YPAN 0xa0 + +#define WMT_GOVR_XRES 0x98 +#define WMT_GOVR_XRES_VIRTUAL 0x9c + +#define WMT_GOVR_MIF_ENABLE 0x80 +#define WMT_GOVR_FHI 0xa8 +#define WMT_GOVR_REG_UPDATE 0xe4 + +/* + * BIT0 GOVRH_DVO_OUTWIDTH + * BIT1 GOVRH_DVO_SYNC_POLAR + * BIT2 GOVRH_DVO_ENABLE + */ +#define WMT_GOVR_DVO_SET 0x148 + +/* Timing generator? */ +#define WMT_GOVR_TG 0x100 + +/* Timings */ +#define WMT_GOVR_TIMING_H_ALL 0x108 +#define WMT_GOVR_TIMING_V_ALL 0x10c +#define WMT_GOVR_TIMING_V_START 0x110 +#define WMT_GOVR_TIMING_V_END 0x114 +#define WMT_GOVR_TIMING_H_START 0x118 +#define WMT_GOVR_TIMING_H_END 0x11c +#define WMT_GOVR_TIMING_V_SYNC 0x128 +#define WMT_GOVR_TIMING_H_SYNC 0x12c + +#endif /* _WM8505FB_REGS_H */ diff --git a/drivers/video/fbdev/wmt_ge_rops.c b/drivers/video/fbdev/wmt_ge_rops.c new file mode 100644 index 000000000000..b0a9f34b2e01 --- /dev/null +++ b/drivers/video/fbdev/wmt_ge_rops.c @@ -0,0 +1,182 @@ +/* + * linux/drivers/video/wmt_ge_rops.c + * + * Accelerators for raster operations using WonderMedia Graphics Engine + * + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/platform_device.h> +#include "fb_draw.h" + +#define GE_COMMAND_OFF 0x00 +#define GE_DEPTH_OFF 0x04 +#define GE_HIGHCOLOR_OFF 0x08 +#define GE_ROPCODE_OFF 0x14 +#define GE_FIRE_OFF 0x18 +#define GE_SRCBASE_OFF 0x20 +#define GE_SRCDISPW_OFF 0x24 +#define GE_SRCDISPH_OFF 0x28 +#define GE_SRCAREAX_OFF 0x2c +#define GE_SRCAREAY_OFF 0x30 +#define GE_SRCAREAW_OFF 0x34 +#define GE_SRCAREAH_OFF 0x38 +#define GE_DESTBASE_OFF 0x3c +#define GE_DESTDISPW_OFF 0x40 +#define GE_DESTDISPH_OFF 0x44 +#define GE_DESTAREAX_OFF 0x48 +#define GE_DESTAREAY_OFF 0x4c +#define GE_DESTAREAW_OFF 0x50 +#define GE_DESTAREAH_OFF 0x54 +#define GE_PAT0C_OFF 0x88 /* Pattern 0 color */ +#define GE_ENABLE_OFF 0xec +#define GE_INTEN_OFF 0xf0 +#define GE_STATUS_OFF 0xf8 + +static void __iomem *regbase; + +void wmt_ge_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + unsigned long fg, pat; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR) + fg = ((u32 *) (p->pseudo_palette))[rect->color]; + else + fg = rect->color; + + pat = pixel_to_pat(p->var.bits_per_pixel, fg); + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + writel(p->var.bits_per_pixel == 32 ? 3 : + (p->var.bits_per_pixel == 8 ? 0 : 1), regbase + GE_DEPTH_OFF); + writel(p->var.bits_per_pixel == 15 ? 1 : 0, regbase + GE_HIGHCOLOR_OFF); + writel(p->fix.smem_start, regbase + GE_DESTBASE_OFF); + writel(p->var.xres_virtual - 1, regbase + GE_DESTDISPW_OFF); + writel(p->var.yres_virtual - 1, regbase + GE_DESTDISPH_OFF); + writel(rect->dx, regbase + GE_DESTAREAX_OFF); + writel(rect->dy, regbase + GE_DESTAREAY_OFF); + writel(rect->width - 1, regbase + GE_DESTAREAW_OFF); + writel(rect->height - 1, regbase + GE_DESTAREAH_OFF); + + writel(pat, regbase + GE_PAT0C_OFF); + writel(1, regbase + GE_COMMAND_OFF); + writel(rect->rop == ROP_XOR ? 0x5a : 0xf0, regbase + GE_ROPCODE_OFF); + writel(1, regbase + GE_FIRE_OFF); +} +EXPORT_SYMBOL_GPL(wmt_ge_fillrect); + +void wmt_ge_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + if (p->state != FBINFO_STATE_RUNNING) + return; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + writel(p->var.bits_per_pixel > 16 ? 3 : + (p->var.bits_per_pixel > 8 ? 1 : 0), regbase + GE_DEPTH_OFF); + + writel(p->fix.smem_start, regbase + GE_SRCBASE_OFF); + writel(p->var.xres_virtual - 1, regbase + GE_SRCDISPW_OFF); + writel(p->var.yres_virtual - 1, regbase + GE_SRCDISPH_OFF); + writel(area->sx, regbase + GE_SRCAREAX_OFF); + writel(area->sy, regbase + GE_SRCAREAY_OFF); + writel(area->width - 1, regbase + GE_SRCAREAW_OFF); + writel(area->height - 1, regbase + GE_SRCAREAH_OFF); + + writel(p->fix.smem_start, regbase + GE_DESTBASE_OFF); + writel(p->var.xres_virtual - 1, regbase + GE_DESTDISPW_OFF); + writel(p->var.yres_virtual - 1, regbase + GE_DESTDISPH_OFF); + writel(area->dx, regbase + GE_DESTAREAX_OFF); + writel(area->dy, regbase + GE_DESTAREAY_OFF); + writel(area->width - 1, regbase + GE_DESTAREAW_OFF); + writel(area->height - 1, regbase + GE_DESTAREAH_OFF); + + writel(0xcc, regbase + GE_ROPCODE_OFF); + writel(1, regbase + GE_COMMAND_OFF); + writel(1, regbase + GE_FIRE_OFF); +} +EXPORT_SYMBOL_GPL(wmt_ge_copyarea); + +int wmt_ge_sync(struct fb_info *p) +{ + int loops = 5000000; + while ((readl(regbase + GE_STATUS_OFF) & 4) && --loops) + cpu_relax(); + return loops > 0 ? 0 : -EBUSY; +} +EXPORT_SYMBOL_GPL(wmt_ge_sync); + +static int wmt_ge_rops_probe(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + return -ENODEV; + } + + /* Only one ROP engine is presently supported. */ + if (unlikely(regbase)) { + WARN_ON(1); + return -EBUSY; + } + + regbase = ioremap(res->start, resource_size(res)); + if (regbase == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + return -EBUSY; + } + + writel(1, regbase + GE_ENABLE_OFF); + printk(KERN_INFO "Enabled support for WMT GE raster acceleration\n"); + + return 0; +} + +static int wmt_ge_rops_remove(struct platform_device *pdev) +{ + iounmap(regbase); + return 0; +} + +static const struct of_device_id wmt_dt_ids[] = { + { .compatible = "wm,prizm-ge-rops", }, + { /* sentinel */ } +}; + +static struct platform_driver wmt_ge_rops_driver = { + .probe = wmt_ge_rops_probe, + .remove = wmt_ge_rops_remove, + .driver = { + .owner = THIS_MODULE, + .name = "wmt_ge_rops", + .of_match_table = wmt_dt_ids, + }, +}; + +module_platform_driver(wmt_ge_rops_driver); + +MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>"); +MODULE_DESCRIPTION("Accelerators for raster operations using " + "WonderMedia Graphics Engine"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, wmt_dt_ids); diff --git a/drivers/video/fbdev/wmt_ge_rops.h b/drivers/video/fbdev/wmt_ge_rops.h new file mode 100644 index 000000000000..f73ec6377a46 --- /dev/null +++ b/drivers/video/fbdev/wmt_ge_rops.h @@ -0,0 +1,28 @@ +#ifdef CONFIG_FB_WMT_GE_ROPS + +extern void wmt_ge_fillrect(struct fb_info *info, + const struct fb_fillrect *rect); +extern void wmt_ge_copyarea(struct fb_info *info, + const struct fb_copyarea *area); +extern int wmt_ge_sync(struct fb_info *info); + +#else + +static inline int wmt_ge_sync(struct fb_info *p) +{ + return 0; +} + +static inline void wmt_ge_fillrect(struct fb_info *p, + const struct fb_fillrect *rect) +{ + sys_fillrect(p, rect); +} + +static inline void wmt_ge_copyarea(struct fb_info *p, + const struct fb_copyarea *area) +{ + sys_copyarea(p, area); +} + +#endif diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c new file mode 100644 index 000000000000..901014bbc821 --- /dev/null +++ b/drivers/video/fbdev/xen-fbfront.c @@ -0,0 +1,719 @@ +/* + * Xen para-virtual frame buffer device + * + * Copyright (C) 2005-2006 Anthony Liguori <aliguori@us.ibm.com> + * Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <armbru@redhat.com> + * + * Based on linux/drivers/video/q40fb.c + * + * 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: + * + * Switch to grant tables when they become capable of dealing with the + * frame buffer. + */ + +#include <linux/console.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> + +#include <asm/xen/hypervisor.h> + +#include <xen/xen.h> +#include <xen/events.h> +#include <xen/page.h> +#include <xen/interface/io/fbif.h> +#include <xen/interface/io/protocols.h> +#include <xen/xenbus.h> +#include <xen/platform_pci.h> + +struct xenfb_info { + unsigned char *fb; + struct fb_info *fb_info; + int x1, y1, x2, y2; /* dirty rectangle, + protected by dirty_lock */ + spinlock_t dirty_lock; + int nr_pages; + int irq; + struct xenfb_page *page; + unsigned long *mfns; + int update_wanted; /* XENFB_TYPE_UPDATE wanted */ + int feature_resize; /* XENFB_TYPE_RESIZE ok */ + struct xenfb_resize resize; /* protected by resize_lock */ + int resize_dpy; /* ditto */ + spinlock_t resize_lock; + + struct xenbus_device *xbdev; +}; + +#define XENFB_DEFAULT_FB_LEN (XENFB_WIDTH * XENFB_HEIGHT * XENFB_DEPTH / 8) + +enum { KPARAM_MEM, KPARAM_WIDTH, KPARAM_HEIGHT, KPARAM_CNT }; +static int video[KPARAM_CNT] = { 2, XENFB_WIDTH, XENFB_HEIGHT }; +module_param_array(video, int, NULL, 0); +MODULE_PARM_DESC(video, + "Video memory size in MB, width, height in pixels (default 2,800,600)"); + +static void xenfb_make_preferred_console(void); +static int xenfb_remove(struct xenbus_device *); +static void xenfb_init_shared_page(struct xenfb_info *, struct fb_info *); +static int xenfb_connect_backend(struct xenbus_device *, struct xenfb_info *); +static void xenfb_disconnect_backend(struct xenfb_info *); + +static void xenfb_send_event(struct xenfb_info *info, + union xenfb_out_event *event) +{ + u32 prod; + + prod = info->page->out_prod; + /* caller ensures !xenfb_queue_full() */ + mb(); /* ensure ring space available */ + XENFB_OUT_RING_REF(info->page, prod) = *event; + wmb(); /* ensure ring contents visible */ + info->page->out_prod = prod + 1; + + notify_remote_via_irq(info->irq); +} + +static void xenfb_do_update(struct xenfb_info *info, + int x, int y, int w, int h) +{ + union xenfb_out_event event; + + memset(&event, 0, sizeof(event)); + event.type = XENFB_TYPE_UPDATE; + event.update.x = x; + event.update.y = y; + event.update.width = w; + event.update.height = h; + + /* caller ensures !xenfb_queue_full() */ + xenfb_send_event(info, &event); +} + +static void xenfb_do_resize(struct xenfb_info *info) +{ + union xenfb_out_event event; + + memset(&event, 0, sizeof(event)); + event.resize = info->resize; + + /* caller ensures !xenfb_queue_full() */ + xenfb_send_event(info, &event); +} + +static int xenfb_queue_full(struct xenfb_info *info) +{ + u32 cons, prod; + + prod = info->page->out_prod; + cons = info->page->out_cons; + return prod - cons == XENFB_OUT_RING_LEN; +} + +static void xenfb_handle_resize_dpy(struct xenfb_info *info) +{ + unsigned long flags; + + spin_lock_irqsave(&info->resize_lock, flags); + if (info->resize_dpy) { + if (!xenfb_queue_full(info)) { + info->resize_dpy = 0; + xenfb_do_resize(info); + } + } + spin_unlock_irqrestore(&info->resize_lock, flags); +} + +static void xenfb_refresh(struct xenfb_info *info, + int x1, int y1, int w, int h) +{ + unsigned long flags; + int x2 = x1 + w - 1; + int y2 = y1 + h - 1; + + xenfb_handle_resize_dpy(info); + + if (!info->update_wanted) + return; + + spin_lock_irqsave(&info->dirty_lock, flags); + + /* Combine with dirty rectangle: */ + if (info->y1 < y1) + y1 = info->y1; + if (info->y2 > y2) + y2 = info->y2; + if (info->x1 < x1) + x1 = info->x1; + if (info->x2 > x2) + x2 = info->x2; + + if (xenfb_queue_full(info)) { + /* Can't send right now, stash it in the dirty rectangle */ + info->x1 = x1; + info->x2 = x2; + info->y1 = y1; + info->y2 = y2; + spin_unlock_irqrestore(&info->dirty_lock, flags); + return; + } + + /* Clear dirty rectangle: */ + info->x1 = info->y1 = INT_MAX; + info->x2 = info->y2 = 0; + + spin_unlock_irqrestore(&info->dirty_lock, flags); + + if (x1 <= x2 && y1 <= y2) + xenfb_do_update(info, x1, y1, x2 - x1 + 1, y2 - y1 + 1); +} + +static void xenfb_deferred_io(struct fb_info *fb_info, + struct list_head *pagelist) +{ + struct xenfb_info *info = fb_info->par; + struct page *page; + unsigned long beg, end; + int y1, y2, miny, maxy; + + miny = INT_MAX; + maxy = 0; + list_for_each_entry(page, pagelist, lru) { + beg = page->index << PAGE_SHIFT; + end = beg + PAGE_SIZE - 1; + y1 = beg / fb_info->fix.line_length; + y2 = end / fb_info->fix.line_length; + if (y2 >= fb_info->var.yres) + y2 = fb_info->var.yres - 1; + if (miny > y1) + miny = y1; + if (maxy < y2) + maxy = y2; + } + xenfb_refresh(info, 0, miny, fb_info->var.xres, maxy - miny + 1); +} + +static struct fb_deferred_io xenfb_defio = { + .delay = HZ / 20, + .deferred_io = xenfb_deferred_io, +}; + +static int xenfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + u32 v; + + if (regno > info->cmap.len) + return 1; + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); +#undef CNVT_TOHW + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + + switch (info->var.bits_per_pixel) { + case 16: + case 24: + case 32: + ((u32 *)info->pseudo_palette)[regno] = v; + break; + } + + return 0; +} + +static void xenfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + struct xenfb_info *info = p->par; + + sys_fillrect(p, rect); + xenfb_refresh(info, rect->dx, rect->dy, rect->width, rect->height); +} + +static void xenfb_imageblit(struct fb_info *p, const struct fb_image *image) +{ + struct xenfb_info *info = p->par; + + sys_imageblit(p, image); + xenfb_refresh(info, image->dx, image->dy, image->width, image->height); +} + +static void xenfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + struct xenfb_info *info = p->par; + + sys_copyarea(p, area); + xenfb_refresh(info, area->dx, area->dy, area->width, area->height); +} + +static ssize_t xenfb_write(struct fb_info *p, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct xenfb_info *info = p->par; + ssize_t res; + + res = fb_sys_write(p, buf, count, ppos); + xenfb_refresh(info, 0, 0, info->page->width, info->page->height); + return res; +} + +static int +xenfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct xenfb_info *xenfb_info; + int required_mem_len; + + xenfb_info = info->par; + + if (!xenfb_info->feature_resize) { + if (var->xres == video[KPARAM_WIDTH] && + var->yres == video[KPARAM_HEIGHT] && + var->bits_per_pixel == xenfb_info->page->depth) { + return 0; + } + return -EINVAL; + } + + /* Can't resize past initial width and height */ + if (var->xres > video[KPARAM_WIDTH] || var->yres > video[KPARAM_HEIGHT]) + return -EINVAL; + + required_mem_len = var->xres * var->yres * xenfb_info->page->depth / 8; + if (var->bits_per_pixel == xenfb_info->page->depth && + var->xres <= info->fix.line_length / (XENFB_DEPTH / 8) && + required_mem_len <= info->fix.smem_len) { + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + return 0; + } + return -EINVAL; +} + +static int xenfb_set_par(struct fb_info *info) +{ + struct xenfb_info *xenfb_info; + unsigned long flags; + + xenfb_info = info->par; + + spin_lock_irqsave(&xenfb_info->resize_lock, flags); + xenfb_info->resize.type = XENFB_TYPE_RESIZE; + xenfb_info->resize.width = info->var.xres; + xenfb_info->resize.height = info->var.yres; + xenfb_info->resize.stride = info->fix.line_length; + xenfb_info->resize.depth = info->var.bits_per_pixel; + xenfb_info->resize.offset = 0; + xenfb_info->resize_dpy = 1; + spin_unlock_irqrestore(&xenfb_info->resize_lock, flags); + return 0; +} + +static struct fb_ops xenfb_fb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = xenfb_write, + .fb_setcolreg = xenfb_setcolreg, + .fb_fillrect = xenfb_fillrect, + .fb_copyarea = xenfb_copyarea, + .fb_imageblit = xenfb_imageblit, + .fb_check_var = xenfb_check_var, + .fb_set_par = xenfb_set_par, +}; + +static irqreturn_t xenfb_event_handler(int rq, void *dev_id) +{ + /* + * No in events recognized, simply ignore them all. + * If you need to recognize some, see xen-kbdfront's + * input_handler() for how to do that. + */ + struct xenfb_info *info = dev_id; + struct xenfb_page *page = info->page; + + if (page->in_cons != page->in_prod) { + info->page->in_cons = info->page->in_prod; + notify_remote_via_irq(info->irq); + } + + /* Flush dirty rectangle: */ + xenfb_refresh(info, INT_MAX, INT_MAX, -INT_MAX, -INT_MAX); + + return IRQ_HANDLED; +} + +static int xenfb_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + struct xenfb_info *info; + struct fb_info *fb_info; + int fb_size; + int val; + int ret = 0; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (info == NULL) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure"); + return -ENOMEM; + } + + /* Limit kernel param videoram amount to what is in xenstore */ + if (xenbus_scanf(XBT_NIL, dev->otherend, "videoram", "%d", &val) == 1) { + if (val < video[KPARAM_MEM]) + video[KPARAM_MEM] = val; + } + + /* If requested res does not fit in available memory, use default */ + fb_size = video[KPARAM_MEM] * 1024 * 1024; + if (video[KPARAM_WIDTH] * video[KPARAM_HEIGHT] * XENFB_DEPTH / 8 + > fb_size) { + video[KPARAM_WIDTH] = XENFB_WIDTH; + video[KPARAM_HEIGHT] = XENFB_HEIGHT; + fb_size = XENFB_DEFAULT_FB_LEN; + } + + dev_set_drvdata(&dev->dev, info); + info->xbdev = dev; + info->irq = -1; + info->x1 = info->y1 = INT_MAX; + spin_lock_init(&info->dirty_lock); + spin_lock_init(&info->resize_lock); + + info->fb = vzalloc(fb_size); + if (info->fb == NULL) + goto error_nomem; + + info->nr_pages = (fb_size + PAGE_SIZE - 1) >> PAGE_SHIFT; + + info->mfns = vmalloc(sizeof(unsigned long) * info->nr_pages); + if (!info->mfns) + goto error_nomem; + + /* set up shared page */ + info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!info->page) + goto error_nomem; + + /* abusing framebuffer_alloc() to allocate pseudo_palette */ + fb_info = framebuffer_alloc(sizeof(u32) * 256, NULL); + if (fb_info == NULL) + goto error_nomem; + + /* complete the abuse: */ + fb_info->pseudo_palette = fb_info->par; + fb_info->par = info; + + fb_info->screen_base = info->fb; + + fb_info->fbops = &xenfb_fb_ops; + fb_info->var.xres_virtual = fb_info->var.xres = video[KPARAM_WIDTH]; + fb_info->var.yres_virtual = fb_info->var.yres = video[KPARAM_HEIGHT]; + fb_info->var.bits_per_pixel = XENFB_DEPTH; + + fb_info->var.red = (struct fb_bitfield){16, 8, 0}; + fb_info->var.green = (struct fb_bitfield){8, 8, 0}; + fb_info->var.blue = (struct fb_bitfield){0, 8, 0}; + + fb_info->var.activate = FB_ACTIVATE_NOW; + fb_info->var.height = -1; + fb_info->var.width = -1; + fb_info->var.vmode = FB_VMODE_NONINTERLACED; + + fb_info->fix.visual = FB_VISUAL_TRUECOLOR; + fb_info->fix.line_length = fb_info->var.xres * XENFB_DEPTH / 8; + fb_info->fix.smem_start = 0; + fb_info->fix.smem_len = fb_size; + strcpy(fb_info->fix.id, "xen"); + fb_info->fix.type = FB_TYPE_PACKED_PIXELS; + fb_info->fix.accel = FB_ACCEL_NONE; + + fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + + ret = fb_alloc_cmap(&fb_info->cmap, 256, 0); + if (ret < 0) { + framebuffer_release(fb_info); + xenbus_dev_fatal(dev, ret, "fb_alloc_cmap"); + goto error; + } + + fb_info->fbdefio = &xenfb_defio; + fb_deferred_io_init(fb_info); + + xenfb_init_shared_page(info, fb_info); + + ret = xenfb_connect_backend(dev, info); + if (ret < 0) { + xenbus_dev_fatal(dev, ret, "xenfb_connect_backend"); + goto error_fb; + } + + ret = register_framebuffer(fb_info); + if (ret) { + xenbus_dev_fatal(dev, ret, "register_framebuffer"); + goto error_fb; + } + info->fb_info = fb_info; + + xenfb_make_preferred_console(); + return 0; + +error_fb: + fb_deferred_io_cleanup(fb_info); + fb_dealloc_cmap(&fb_info->cmap); + framebuffer_release(fb_info); +error_nomem: + if (!ret) { + ret = -ENOMEM; + xenbus_dev_fatal(dev, ret, "allocating device memory"); + } +error: + xenfb_remove(dev); + return ret; +} + +static void xenfb_make_preferred_console(void) +{ + struct console *c; + + if (console_set_on_cmdline) + return; + + console_lock(); + for_each_console(c) { + if (!strcmp(c->name, "tty") && c->index == 0) + break; + } + console_unlock(); + if (c) { + unregister_console(c); + c->flags |= CON_CONSDEV; + c->flags &= ~CON_PRINTBUFFER; /* don't print again */ + register_console(c); + } +} + +static int xenfb_resume(struct xenbus_device *dev) +{ + struct xenfb_info *info = dev_get_drvdata(&dev->dev); + + xenfb_disconnect_backend(info); + xenfb_init_shared_page(info, info->fb_info); + return xenfb_connect_backend(dev, info); +} + +static int xenfb_remove(struct xenbus_device *dev) +{ + struct xenfb_info *info = dev_get_drvdata(&dev->dev); + + xenfb_disconnect_backend(info); + if (info->fb_info) { + fb_deferred_io_cleanup(info->fb_info); + unregister_framebuffer(info->fb_info); + fb_dealloc_cmap(&info->fb_info->cmap); + framebuffer_release(info->fb_info); + } + free_page((unsigned long)info->page); + vfree(info->mfns); + vfree(info->fb); + kfree(info); + + return 0; +} + +static unsigned long vmalloc_to_mfn(void *address) +{ + return pfn_to_mfn(vmalloc_to_pfn(address)); +} + +static void xenfb_init_shared_page(struct xenfb_info *info, + struct fb_info *fb_info) +{ + int i; + int epd = PAGE_SIZE / sizeof(info->mfns[0]); + + for (i = 0; i < info->nr_pages; i++) + info->mfns[i] = vmalloc_to_mfn(info->fb + i * PAGE_SIZE); + + for (i = 0; i * epd < info->nr_pages; i++) + info->page->pd[i] = vmalloc_to_mfn(&info->mfns[i * epd]); + + info->page->width = fb_info->var.xres; + info->page->height = fb_info->var.yres; + info->page->depth = fb_info->var.bits_per_pixel; + info->page->line_length = fb_info->fix.line_length; + info->page->mem_length = fb_info->fix.smem_len; + info->page->in_cons = info->page->in_prod = 0; + info->page->out_cons = info->page->out_prod = 0; +} + +static int xenfb_connect_backend(struct xenbus_device *dev, + struct xenfb_info *info) +{ + int ret, evtchn, irq; + struct xenbus_transaction xbt; + + ret = xenbus_alloc_evtchn(dev, &evtchn); + if (ret) + return ret; + irq = bind_evtchn_to_irqhandler(evtchn, xenfb_event_handler, + 0, dev->devicetype, info); + if (irq < 0) { + xenbus_free_evtchn(dev, evtchn); + xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler"); + return irq; + } + again: + ret = xenbus_transaction_start(&xbt); + if (ret) { + xenbus_dev_fatal(dev, ret, "starting transaction"); + goto unbind_irq; + } + ret = xenbus_printf(xbt, dev->nodename, "page-ref", "%lu", + virt_to_mfn(info->page)); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + evtchn); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "protocol", "%s", + XEN_IO_PROTO_ABI_NATIVE); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "feature-update", "1"); + if (ret) + goto error_xenbus; + ret = xenbus_transaction_end(xbt, 0); + if (ret) { + if (ret == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, ret, "completing transaction"); + goto unbind_irq; + } + + xenbus_switch_state(dev, XenbusStateInitialised); + info->irq = irq; + return 0; + + error_xenbus: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, ret, "writing xenstore"); + unbind_irq: + unbind_from_irqhandler(irq, info); + return ret; +} + +static void xenfb_disconnect_backend(struct xenfb_info *info) +{ + /* Prevent xenfb refresh */ + info->update_wanted = 0; + if (info->irq >= 0) + unbind_from_irqhandler(info->irq, info); + info->irq = -1; +} + +static void xenfb_backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + struct xenfb_info *info = dev_get_drvdata(&dev->dev); + int val; + + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateInitialised: + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + case XenbusStateUnknown: + break; + + case XenbusStateInitWait: +InitWait: + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateConnected: + /* + * Work around xenbus race condition: If backend goes + * through InitWait to Connected fast enough, we can + * get Connected twice here. + */ + if (dev->state != XenbusStateConnected) + goto InitWait; /* no InitWait seen yet, fudge it */ + + if (xenbus_scanf(XBT_NIL, info->xbdev->otherend, + "request-update", "%d", &val) < 0) + val = 0; + if (val) + info->update_wanted = 1; + + if (xenbus_scanf(XBT_NIL, dev->otherend, + "feature-resize", "%d", &val) < 0) + val = 0; + info->feature_resize = val; + break; + + case XenbusStateClosed: + if (dev->state == XenbusStateClosed) + break; + /* Missed the backend's CLOSING state -- fallthrough */ + case XenbusStateClosing: + xenbus_frontend_closed(dev); + break; + } +} + +static const struct xenbus_device_id xenfb_ids[] = { + { "vfb" }, + { "" } +}; + +static DEFINE_XENBUS_DRIVER(xenfb, , + .probe = xenfb_probe, + .remove = xenfb_remove, + .resume = xenfb_resume, + .otherend_changed = xenfb_backend_changed, +); + +static int __init xenfb_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + /* Nothing to do if running in dom0. */ + if (xen_initial_domain()) + return -ENODEV; + + if (!xen_has_pv_devices()) + return -ENODEV; + + return xenbus_register_frontend(&xenfb_driver); +} + +static void __exit xenfb_cleanup(void) +{ + xenbus_unregister_driver(&xenfb_driver); +} + +module_init(xenfb_init); +module_exit(xenfb_cleanup); + +MODULE_DESCRIPTION("Xen virtual framebuffer device frontend"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("xen:vfb"); diff --git a/drivers/video/fbdev/xilinxfb.c b/drivers/video/fbdev/xilinxfb.c new file mode 100644 index 000000000000..553cff2f3f4c --- /dev/null +++ b/drivers/video/fbdev/xilinxfb.c @@ -0,0 +1,509 @@ +/* + * Xilinx TFT frame buffer driver + * + * Author: MontaVista Software, Inc. + * source@mvista.com + * + * 2002-2007 (c) MontaVista Software, Inc. + * 2007 (c) Secret Lab Technologies, Ltd. + * 2009 (c) Xilinx Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +/* + * This driver was based on au1100fb.c by MontaVista rewritten for 2.6 + * by Embedded Alley Solutions <source@embeddedalley.com>, which in turn + * was based on skeletonfb.c, Skeleton for a frame buffer device by + * Geert Uytterhoeven. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/slab.h> + +#ifdef CONFIG_PPC_DCR +#include <asm/dcr.h> +#endif + +#define DRIVER_NAME "xilinxfb" + + +/* + * Xilinx calls it "TFT LCD Controller" though it can also be used for + * the VGA port on the Xilinx ML40x board. This is a hardware display + * controller for a 640x480 resolution TFT or VGA screen. + * + * The interface to the framebuffer is nice and simple. There are two + * control registers. The first tells the LCD interface where in memory + * the frame buffer is (only the 11 most significant bits are used, so + * don't start thinking about scrolling). The second allows the LCD to + * be turned on or off as well as rotated 180 degrees. + * + * In case of direct BUS access the second control register will be at + * an offset of 4 as compared to the DCR access where the offset is 1 + * i.e. REG_CTRL. So this is taken care in the function + * xilinx_fb_out32 where it left shifts the offset 2 times in case of + * direct BUS access. + */ +#define NUM_REGS 2 +#define REG_FB_ADDR 0 +#define REG_CTRL 1 +#define REG_CTRL_ENABLE 0x0001 +#define REG_CTRL_ROTATE 0x0002 + +/* + * The hardware only handles a single mode: 640x480 24 bit true + * color. Each pixel gets a word (32 bits) of memory. Within each word, + * the 8 most significant bits are ignored, the next 8 bits are the red + * level, the next 8 bits are the green level and the 8 least + * significant bits are the blue level. Each row of the LCD uses 1024 + * words, but only the first 640 pixels are displayed with the other 384 + * words being ignored. There are 480 rows. + */ +#define BYTES_PER_PIXEL 4 +#define BITS_PER_PIXEL (BYTES_PER_PIXEL * 8) + +#define RED_SHIFT 16 +#define GREEN_SHIFT 8 +#define BLUE_SHIFT 0 + +#define PALETTE_ENTRIES_NO 16 /* passed to fb_alloc_cmap() */ + +/* ML300/403 reference design framebuffer driver platform data struct */ +struct xilinxfb_platform_data { + u32 rotate_screen; /* Flag to rotate display 180 degrees */ + u32 screen_height_mm; /* Physical dimensions of screen in mm */ + u32 screen_width_mm; + u32 xres, yres; /* resolution of screen in pixels */ + u32 xvirt, yvirt; /* resolution of memory buffer */ + + /* Physical address of framebuffer memory; If non-zero, driver + * will use provided memory address instead of allocating one from + * the consistent pool. */ + u32 fb_phys; +}; + +/* + * Default xilinxfb configuration + */ +static struct xilinxfb_platform_data xilinx_fb_default_pdata = { + .xres = 640, + .yres = 480, + .xvirt = 1024, + .yvirt = 480, +}; + +/* + * Here are the default fb_fix_screeninfo and fb_var_screeninfo structures + */ +static struct fb_fix_screeninfo xilinx_fb_fix = { + .id = "Xilinx", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .accel = FB_ACCEL_NONE +}; + +static struct fb_var_screeninfo xilinx_fb_var = { + .bits_per_pixel = BITS_PER_PIXEL, + + .red = { RED_SHIFT, 8, 0 }, + .green = { GREEN_SHIFT, 8, 0 }, + .blue = { BLUE_SHIFT, 8, 0 }, + .transp = { 0, 0, 0 }, + + .activate = FB_ACTIVATE_NOW +}; + + +#define BUS_ACCESS_FLAG 0x1 /* 1 = BUS, 0 = DCR */ +#define LITTLE_ENDIAN_ACCESS 0x2 /* LITTLE ENDIAN IO functions */ + +struct xilinxfb_drvdata { + + struct fb_info info; /* FB driver info record */ + + phys_addr_t regs_phys; /* phys. address of the control + registers */ + void __iomem *regs; /* virt. address of the control + registers */ +#ifdef CONFIG_PPC_DCR + dcr_host_t dcr_host; + unsigned int dcr_len; +#endif + void *fb_virt; /* virt. address of the frame buffer */ + dma_addr_t fb_phys; /* phys. address of the frame buffer */ + int fb_alloced; /* Flag, was the fb memory alloced? */ + + u8 flags; /* features of the driver */ + + u32 reg_ctrl_default; + + u32 pseudo_palette[PALETTE_ENTRIES_NO]; + /* Fake palette of 16 colors */ +}; + +#define to_xilinxfb_drvdata(_info) \ + container_of(_info, struct xilinxfb_drvdata, info) + +/* + * The XPS TFT Controller can be accessed through BUS or DCR interface. + * To perform the read/write on the registers we need to check on + * which bus its connected and call the appropriate write API. + */ +static void xilinx_fb_out32(struct xilinxfb_drvdata *drvdata, u32 offset, + u32 val) +{ + if (drvdata->flags & BUS_ACCESS_FLAG) { + if (drvdata->flags & LITTLE_ENDIAN_ACCESS) + iowrite32(val, drvdata->regs + (offset << 2)); + else + iowrite32be(val, drvdata->regs + (offset << 2)); + } +#ifdef CONFIG_PPC_DCR + else + dcr_write(drvdata->dcr_host, offset, val); +#endif +} + +static u32 xilinx_fb_in32(struct xilinxfb_drvdata *drvdata, u32 offset) +{ + if (drvdata->flags & BUS_ACCESS_FLAG) { + if (drvdata->flags & LITTLE_ENDIAN_ACCESS) + return ioread32(drvdata->regs + (offset << 2)); + else + return ioread32be(drvdata->regs + (offset << 2)); + } +#ifdef CONFIG_PPC_DCR + else + return dcr_read(drvdata->dcr_host, offset); +#endif + return 0; +} + +static int +xilinx_fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *fbi) +{ + u32 *palette = fbi->pseudo_palette; + + if (regno >= PALETTE_ENTRIES_NO) + return -EINVAL; + + if (fbi->var.grayscale) { + /* Convert color to grayscale. + * grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = + (red * 77 + green * 151 + blue * 28 + 127) >> 8; + } + + /* fbi->fix.visual is always FB_VISUAL_TRUECOLOR */ + + /* We only handle 8 bits of each color. */ + red >>= 8; + green >>= 8; + blue >>= 8; + palette[regno] = (red << RED_SHIFT) | (green << GREEN_SHIFT) | + (blue << BLUE_SHIFT); + + return 0; +} + +static int +xilinx_fb_blank(int blank_mode, struct fb_info *fbi) +{ + struct xilinxfb_drvdata *drvdata = to_xilinxfb_drvdata(fbi); + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + /* turn on panel */ + xilinx_fb_out32(drvdata, REG_CTRL, drvdata->reg_ctrl_default); + break; + + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + /* turn off panel */ + xilinx_fb_out32(drvdata, REG_CTRL, 0); + default: + break; + + } + return 0; /* success */ +} + +static struct fb_ops xilinxfb_ops = +{ + .owner = THIS_MODULE, + .fb_setcolreg = xilinx_fb_setcolreg, + .fb_blank = xilinx_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* --------------------------------------------------------------------- + * Bus independent setup/teardown + */ + +static int xilinxfb_assign(struct platform_device *pdev, + struct xilinxfb_drvdata *drvdata, + struct xilinxfb_platform_data *pdata) +{ + int rc; + struct device *dev = &pdev->dev; + int fbsize = pdata->xvirt * pdata->yvirt * BYTES_PER_PIXEL; + + if (drvdata->flags & BUS_ACCESS_FLAG) { + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drvdata->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drvdata->regs)) + return PTR_ERR(drvdata->regs); + + drvdata->regs_phys = res->start; + } + + /* Allocate the framebuffer memory */ + if (pdata->fb_phys) { + drvdata->fb_phys = pdata->fb_phys; + drvdata->fb_virt = ioremap(pdata->fb_phys, fbsize); + } else { + drvdata->fb_alloced = 1; + drvdata->fb_virt = dma_alloc_coherent(dev, PAGE_ALIGN(fbsize), + &drvdata->fb_phys, GFP_KERNEL); + } + + if (!drvdata->fb_virt) { + dev_err(dev, "Could not allocate frame buffer memory\n"); + return -ENOMEM; + } + + /* Clear (turn to black) the framebuffer */ + memset_io((void __iomem *)drvdata->fb_virt, 0, fbsize); + + /* Tell the hardware where the frame buffer is */ + xilinx_fb_out32(drvdata, REG_FB_ADDR, drvdata->fb_phys); + rc = xilinx_fb_in32(drvdata, REG_FB_ADDR); + /* Endianess detection */ + if (rc != drvdata->fb_phys) { + drvdata->flags |= LITTLE_ENDIAN_ACCESS; + xilinx_fb_out32(drvdata, REG_FB_ADDR, drvdata->fb_phys); + } + + /* Turn on the display */ + drvdata->reg_ctrl_default = REG_CTRL_ENABLE; + if (pdata->rotate_screen) + drvdata->reg_ctrl_default |= REG_CTRL_ROTATE; + xilinx_fb_out32(drvdata, REG_CTRL, + drvdata->reg_ctrl_default); + + /* Fill struct fb_info */ + drvdata->info.device = dev; + drvdata->info.screen_base = (void __iomem *)drvdata->fb_virt; + drvdata->info.fbops = &xilinxfb_ops; + drvdata->info.fix = xilinx_fb_fix; + drvdata->info.fix.smem_start = drvdata->fb_phys; + drvdata->info.fix.smem_len = fbsize; + drvdata->info.fix.line_length = pdata->xvirt * BYTES_PER_PIXEL; + + drvdata->info.pseudo_palette = drvdata->pseudo_palette; + drvdata->info.flags = FBINFO_DEFAULT; + drvdata->info.var = xilinx_fb_var; + drvdata->info.var.height = pdata->screen_height_mm; + drvdata->info.var.width = pdata->screen_width_mm; + drvdata->info.var.xres = pdata->xres; + drvdata->info.var.yres = pdata->yres; + drvdata->info.var.xres_virtual = pdata->xvirt; + drvdata->info.var.yres_virtual = pdata->yvirt; + + /* Allocate a colour map */ + rc = fb_alloc_cmap(&drvdata->info.cmap, PALETTE_ENTRIES_NO, 0); + if (rc) { + dev_err(dev, "Fail to allocate colormap (%d entries)\n", + PALETTE_ENTRIES_NO); + goto err_cmap; + } + + /* Register new frame buffer */ + rc = register_framebuffer(&drvdata->info); + if (rc) { + dev_err(dev, "Could not register frame buffer\n"); + goto err_regfb; + } + + if (drvdata->flags & BUS_ACCESS_FLAG) { + /* Put a banner in the log (for DEBUG) */ + dev_dbg(dev, "regs: phys=%pa, virt=%p\n", + &drvdata->regs_phys, drvdata->regs); + } + /* Put a banner in the log (for DEBUG) */ + dev_dbg(dev, "fb: phys=%llx, virt=%p, size=%x\n", + (unsigned long long)drvdata->fb_phys, drvdata->fb_virt, fbsize); + + return 0; /* success */ + +err_regfb: + fb_dealloc_cmap(&drvdata->info.cmap); + +err_cmap: + if (drvdata->fb_alloced) + dma_free_coherent(dev, PAGE_ALIGN(fbsize), drvdata->fb_virt, + drvdata->fb_phys); + else + iounmap(drvdata->fb_virt); + + /* Turn off the display */ + xilinx_fb_out32(drvdata, REG_CTRL, 0); + + return rc; +} + +static int xilinxfb_release(struct device *dev) +{ + struct xilinxfb_drvdata *drvdata = dev_get_drvdata(dev); + +#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) + xilinx_fb_blank(VESA_POWERDOWN, &drvdata->info); +#endif + + unregister_framebuffer(&drvdata->info); + + fb_dealloc_cmap(&drvdata->info.cmap); + + if (drvdata->fb_alloced) + dma_free_coherent(dev, PAGE_ALIGN(drvdata->info.fix.smem_len), + drvdata->fb_virt, drvdata->fb_phys); + else + iounmap(drvdata->fb_virt); + + /* Turn off the display */ + xilinx_fb_out32(drvdata, REG_CTRL, 0); + +#ifdef CONFIG_PPC_DCR + /* Release the resources, as allocated based on interface */ + if (!(drvdata->flags & BUS_ACCESS_FLAG)) + dcr_unmap(drvdata->dcr_host, drvdata->dcr_len); +#endif + + return 0; +} + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +static int xilinxfb_of_probe(struct platform_device *pdev) +{ + const u32 *prop; + u32 tft_access = 0; + struct xilinxfb_platform_data pdata; + int size; + struct xilinxfb_drvdata *drvdata; + + /* Copy with the default pdata (not a ptr reference!) */ + pdata = xilinx_fb_default_pdata; + + /* Allocate the driver data region */ + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + /* + * To check whether the core is connected directly to DCR or BUS + * interface and initialize the tft_access accordingly. + */ + of_property_read_u32(pdev->dev.of_node, "xlnx,dcr-splb-slave-if", + &tft_access); + + /* + * Fill the resource structure if its direct BUS interface + * otherwise fill the dcr_host structure. + */ + if (tft_access) { + drvdata->flags |= BUS_ACCESS_FLAG; + } +#ifdef CONFIG_PPC_DCR + else { + int start; + start = dcr_resource_start(pdev->dev.of_node, 0); + drvdata->dcr_len = dcr_resource_len(pdev->dev.of_node, 0); + drvdata->dcr_host = dcr_map(pdev->dev.of_node, start, drvdata->dcr_len); + if (!DCR_MAP_OK(drvdata->dcr_host)) { + dev_err(&pdev->dev, "invalid DCR address\n"); + return -ENODEV; + } + } +#endif + + prop = of_get_property(pdev->dev.of_node, "phys-size", &size); + if ((prop) && (size >= sizeof(u32)*2)) { + pdata.screen_width_mm = prop[0]; + pdata.screen_height_mm = prop[1]; + } + + prop = of_get_property(pdev->dev.of_node, "resolution", &size); + if ((prop) && (size >= sizeof(u32)*2)) { + pdata.xres = prop[0]; + pdata.yres = prop[1]; + } + + prop = of_get_property(pdev->dev.of_node, "virtual-resolution", &size); + if ((prop) && (size >= sizeof(u32)*2)) { + pdata.xvirt = prop[0]; + pdata.yvirt = prop[1]; + } + + if (of_find_property(pdev->dev.of_node, "rotate-display", NULL)) + pdata.rotate_screen = 1; + + dev_set_drvdata(&pdev->dev, drvdata); + return xilinxfb_assign(pdev, drvdata, &pdata); +} + +static int xilinxfb_of_remove(struct platform_device *op) +{ + return xilinxfb_release(&op->dev); +} + +/* Match table for of_platform binding */ +static struct of_device_id xilinxfb_of_match[] = { + { .compatible = "xlnx,xps-tft-1.00.a", }, + { .compatible = "xlnx,xps-tft-2.00.a", }, + { .compatible = "xlnx,xps-tft-2.01.a", }, + { .compatible = "xlnx,plb-tft-cntlr-ref-1.00.a", }, + { .compatible = "xlnx,plb-dvi-cntlr-ref-1.00.c", }, + {}, +}; +MODULE_DEVICE_TABLE(of, xilinxfb_of_match); + +static struct platform_driver xilinxfb_of_driver = { + .probe = xilinxfb_of_probe, + .remove = xilinxfb_of_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = xilinxfb_of_match, + }, +}; + +module_platform_driver(xilinxfb_of_driver); + +MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); +MODULE_DESCRIPTION("Xilinx TFT frame buffer driver"); +MODULE_LICENSE("GPL"); |